ivan-onys дал отличный ответ, но есть небольшое дополнение к нему: этот скрипт будет производить звук в 4 раза короче, чем ожидалось, потому что метод записи Pyaudio требует строковых данных float32, но когда вы передаете массив numpy этому методу,он преобразует весь массив как сущность в строку, поэтому вы должны сами преобразовать данные в массиве numpy в последовательность байтов следующим образом:
samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32).tobytes()
, а также изменить эту строку:
stream.write(samples)