Подтвердить что ты не робот

Как производить точный тон и тишину?

У меня есть проект С#, который воспроизводит код Морзе для RSS-каналов. Я пишу его с помощью Managed DirectX, только чтобы обнаружить, что Managed DirectX устарел и устарел. Задача, которую я имею, - играть в чистые синусоидальные всплески, чередующиеся с периодами молчания (код), которые точно рассчитаны по их длительности. Мне нужно иметь возможность вызывать функцию, которая воспроизводит чистый тон в течение многих миллисекунд, затем Thread.Sleep(), затем воспроизводит другой и т.д. В самом быстром случае тона и пространства могут быть короткими, как 40 мс.

Он отлично работает в Managed DirectX. Чтобы получить точно настроенный тон, я создаю 1 сек. синусоидальной волны во вторичный буфер, а затем для воспроизведения тона определенной продолжительности, которую я ищу вперед в течение х миллисекунд в конце буфера, тогда играйте.

Я пробовал System.Media.SoundPlayer. Это проигравший [edit - см. Мой ответ ниже], потому что вам нужно играть(), Sleep(), а затем Stop() для произвольной длины тона. В результате получается слишком длинный тон, зависящий от нагрузки процессора. Для фактического прекращения тона требуется неопределенное количество времени.

Затем я приступил к длительной попытке использовать NAudio 1.3. Я закончил с резидентным потоком памяти, предоставляющим данные тона, и снова искал вперед, оставляя желаемую длину тона, оставшегося в потоке, а затем играл. Это некоторое время работало нормально в классе DirectSoundOut (см. Ниже), но класс WaveOut быстро умирает с внутренним утверждением, говорящим, что буферы все еще находятся в очереди, несмотря на PlayerStopped = true. Это странно, так как я играю до конца, а затем ожидаю того же времени между окончанием тона и началом следующего. Вы могли бы подумать, что через 80 мс после начала воспроизведения мелодии в 40 мс он не будет иметь буферы в очереди.

DirectSoundOut работает хорошо, но его проблема в том, что для каждого тонального всплеска Play() он отжимает отдельный поток. В конце концов (5 минут или около того) он просто перестает работать. Вы можете видеть поток после потока после выхода потока в окне вывода при запуске проекта в VS2008 IDE. Я не создаю новые объекты во время игры, я просто ищу() поток тембра, затем снова и снова звоню в Play(), поэтому я не думаю, что это проблема с потерянными буферами/тем, что накапливается до тех пор, пока оно не задохнется.

У меня нет терпения по этому поводу, поэтому я прошу в надежде, что кто-то здесь столкнулся с аналогичным требованием и может направить меня в направлении с вероятным решением.

4b9b3361

Ответ 1

Я не могу в это поверить... Я вернулся в System.Media.SoundPlayer и получил его, чтобы сделать то, что я хочу... никакая гигантская библиотека зависимостей с 95% неиспользованным кодом и/или причудами, ожидающими обнаружения:-). Кроме того, он работает на MacOSX под Mono (2.6)!!! [неправильный - нет звука, задаст отдельный вопрос]

Я использовал MemoryStream и BinaryWriter для создания WAV файла в комплекте с заголовком RIFF и фрагментацией. Нет необходимости в "факте", это 16-разрядные образцы с частотой 44100 Гц. Итак, теперь у меня есть MemoryStream с 1000 мс образцов в нем и завернутый BinaryReader.

В файле RIFF есть две длины 4 байта /32 бита, общая длина которых составляет 4 байта в потоке (сразу после "RIFF" в ASCII) и "длина данных" непосредственно перед выборки байтов данных. Моя стратегия заключалась в том, чтобы искать в потоке и использовать BinaryWriter для изменения двух длин, чтобы обмануть SoundPlayer, считая, что аудиопоток - это только длина/продолжительность, которую я хочу, а затем Play(). В следующий раз продолжительность будет иной, поэтому еще раз перепишите длины в MemoryStream с помощью BinaryWriter, Flush() и снова вызовите Play().

Когда я это пробовал, я не мог заставить SoundPlayer видеть изменения в потоке, даже если я установил его свойство Stream. Я был вынужден создать новый SoundPlayer... каждые 40 миллисекунд??? Нет.

Ну, я хочу вернуться к этому коду сегодня и начал смотреть на членов SoundPlayer. Я увидел "SoundLocation" и прочитал его. Там сказано, что побочным эффектом установки SoundLocation было бы исключить свойство Stream, и наоборот для Stream. Поэтому я добавил строку кода, чтобы установить свойство SOundLocation на что-то фиктивное, "x", а затем установить свойство Stream в мой (только что измененный) MemoryStream. Черт, если бы он не подбирал это и не играл ровно столько, сколько я просил. Кажется, нет никаких сумасшедших побочных эффектов, таких как мертвое время после или увеличение памяти, или??? Это займет 1-2 миллисекунды, чтобы выполнить настройку потока WAV, а затем загрузить/запустить плеер, но он очень маленький и цена правильная!

Я также использовал свойство Frequency, которое повторно генерирует образцы и использует трюк Seek/BinaryWriter для наложения старых данных в RIFF/WAV MemoryStream с тем же количеством выборок, но на другую частоту, и снова сделал то же самое вещь для свойства Amplitude.

Этот проект находится на SourceForge. Вы можете получить код С# для этого взлома в SPTones.CS от эту страницу в браузере SVN. Спасибо всем, кто предоставил информацию об этом, в том числе @arke, чье мышление было близко к моему. Я ценю это.

Ответ 2

Лучше всего просто генерировать синусоидальные волны и тишину вместе в буфер, который вы играете. То есть, всегда играйте что-нибудь, но пишите все, что вам нужно, в этот буфер.

Вы знаете выборку, и, учитывая выборку, вы можете рассчитать количество образцов, которые вам нужно написать.

uint numSamples = timeWantedInSeconds * sampleRate;

Это количество образцов, которые вам нужны, чтобы генерировать синусоидальную волну или тишину, в зависимости от того. Затем просто заполните буфер при необходимости. Таким образом, вы получите наиболее точное время.

Ответ 3

Попробуйте использовать XNA.

Вам нужно будет предоставить файл или поток для статического тона, который вы можете выполнить. Затем вы можете изменить высоту и громкость этого тона.

Поскольку XNA создан для игр, у него не будет проблем вообще с задержками в 40 мс.

Ответ 4

Достаточно легко преобразовать из ManagedDX в SlimDX...

Изменить: что останавливает вас, кстати, просто предгенерирует "n" образцы синусоидальной волны? (Где n является самым близким к количеству миллисекунд, которое вы хотите). Это действительно не так долго, чтобы генерировать данные. Более того, если у вас есть буфер 22 КГц, и вы хотите, чтобы последние 100 образцов вы просто не отправляли "buffer + 21950" и задавали длину буфера 100 экземплярами?