Запись музыкальных нот в файл WAV - PullRequest
48 голосов
/ 12 февраля 2011

Меня интересует, как брать музыкальные ноты (например, A, B, C # и т. Д.) Или аккорды (несколько нот одновременно) и записывать их в файл wav.

Насколько я понимаю, каждая нота имеет определенную частоту, связанную с ней (для идеального тона) - например, A4 (A выше среднего C) составляет 440 Гц (полный список 2/3 пути вниз Это Page ).

Если мое понимание верно, этот шаг находится в частотной области, и поэтому требуется обратное быстрое преобразование Фурье, применяемое к нему для генерации эквивалента во временной области?

То, что я хочу знать, это:

  • Как работают аккорды? Являются ли они средним из смол?
  • Какова продолжительность воспроизведения каждой указанной ноты, если содержимое файла wav представляет собой сигнал?
  • как результат преобразования нескольких заметок в обратное БПФ в массив байтов, составляющих данные в файле wav?
  • любая другая соответствующая информация, относящаяся к этому.

Спасибо за любую помощь, которую вы можете оказать. Если приводить примеры кода, я использую C #, и код, который я сейчас использую для создания файлов WAV, выглядит следующим образом:

int channels = 1;
int bitsPerSample = 8;
//WaveFile is custom class to create a wav file.
WaveFile file = new WaveFile(channels, bitsPerSample, 11025);

int seconds = 60;
int samples = 11025 * seconds; //Create x seconds of audio

// Sound Data Size = Number Of Channels * Bits Per Sample * Samples

byte[] data = new byte[channels * bitsPerSample/8 * samples];

//Creates a Constant Sound
for(int i = 0; i < data.Length; i++)
{
    data[i] = (byte)(256 * Math.Sin(i));
}
file.SetData(data, samples);

Это создает (как-то) постоянный звук - но я не совсем понимаю, как код соотносится с результатом.

Ответы [ 3 ]

115 голосов
/ 12 февраля 2011

Вы на правильном пути.

Давайте рассмотрим ваш пример:

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(256 * Math.Sin(i));

ОК, у вас есть 11025 выборок в секунду. У вас есть образцы на 60 секунд. Каждый образец представляет собой число от 0 до 255, которое представляет собой небольшое изменение давления воздуха в точке пространства в данный момент времени.

Подождите минуту, хотя синус изменяется от -1 до 1, поэтому сэмплы идут от -256 до +256, и это больше, чем диапазон байта, поэтому здесь происходит что-то глупое. Давайте переделаем ваш код, чтобы образец находился в правильном диапазоне.

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(128 + 127 * Math.Sin(i));

Теперь у нас есть плавно изменяющиеся данные, которые находятся в диапазоне от 1 до 255, поэтому мы находимся в диапазоне байтов.

Попробуйте и посмотрите, как это звучит. Это должно звучать намного более "гладко".

Человеческое ухо обнаруживает невероятно незначительные изменения давления воздуха. Если эти изменения образуют повторяющийся паттерн , то частота , с которой повторяется паттерн, интерпретируется улиткой в ​​вашем ухе как определенный тон. размер изменения давления интерпретируется как объем .

Ваша форма волны имеет длину шестьдесят секунд. Изменение переходит от наименьшего изменения 1 к наибольшему изменению 255. Где находятся пики ? То есть где образец достигает значения 255 или близко к нему?

Хорошо, синус равен 1 при π / 2, 5π / 2, 9π / 2, 13π / 2 и так далее. Так что пики всегда, когда я рядом с одним из них. То есть в 2, 8, 14, 20, ...

Как далеко они во времени? Каждый образец составляет 1/11025-ю секунды, поэтому пики составляют примерно 2/11025 = примерно 570 микросекунд между каждым пиком. Сколько пиков в секунду? 11025 / 2π = 1755 Гц. (Герц - это мера частоты; сколько пиков в секунду). 1760 Гц на две октавы выше А 440, так что это слегка ровный тон А.

Как работают аккорды? Являются ли они средним из смол?

Нет. Аккорд А440 и октавы выше, A880 не эквивалентен 660 Гц. Вы не средний шаг . Вы сумма форма волны .

Подумайте о давлении воздуха. Если у вас есть один вибрирующий источник, который нагнетает давление вверх и вниз 440 раз в секунду, и другой, который нагнетает давление вверх и вниз 880 раз в секунду, сеть будет отличаться от вибрации 660 раз в секунду. Это равно сумме давлений в любой данный момент времени. Помните, что все это WAV-файл: большой список изменений давления воздуха .

Предположим, вы хотите сделать октаву ниже вашего образца. Какая частота? В два раза меньше. Итак, давайте сделаем это вдвое реже:

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(128 + 127 * Math.Sin(i/2.0)); 

Обратите внимание, что это должно быть 2.0, а не 2. Мы не хотим целочисленного округления! 2.0 сообщает компилятору, что вы хотите получить результат с плавающей запятой, а не целые числа.

Если вы сделаете это, вы получите пики вдвое реже: при i = 4, 16, 28 ... и, следовательно, тон будет на полную октаву ниже. (Каждая октава вниз делит пополам частоту; каждая октава вверх удваивает it.)

Попробуйте и посмотрите, как вы получите тот же тон, на октаву ниже.

Теперь сложите их вместе.

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(128 + 127 * Math.Sin(i)) + 
            (byte)(128 + 127 * Math.Sin(i/2.0)); 

Это, наверное, звучало как дерьмо. Что случилось? Мы снова переполнились ; сумма была больше 256 во многих точках. Уменьшить вдвое объем обеих волн :

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(128 + (63 * Math.Sin(i/2.0) + 63 * Math.Sin(i))); 

Лучше. «63 sin x + 63 sin y» находится между -126 и +126, поэтому это не может переполнить байт.

(То есть - это среднее значение: мы, по сути, берем среднее вклад в давление каждого тона , а не среднее значение частот .)

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

Последнее выражение сложно и трудно читаемо. Давайте разберем его на код, который легче читать. Но сначала подытожим историю:

  • 128 находится на полпути между низким давлением (0) и высоким давлением (255).
  • громкость тона - максимальное давление, достигаемое волной
  • тон - синусоидальная волна данногочастота
  • частота в Гц - это частота дискретизации (11025), деленная на 2π

Итак, давайте соберем ее вместе:

double sampleFrequency = 11025.0;
double multiplier = 2.0 * Math.PI / sampleFrequency;
int volume = 20;

// initialize the data to "flat", no change in pressure, in the middle:
for(int i = 0; i < data.Length; i++)
  data[i] = 128;

// Add on a change in pressure equal to A440:
for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 440.0))); 

// Add on a change in pressure equal to A880:

for(int i = 0; i < data.Length; i++)
  data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 880.0))); 

И вот вы;Теперь вы можете генерировать любой желаемый тон любой частоты и громкости.Чтобы создать аккорд, сложите их вместе, убедившись, что вы не слишком громкие и не переполняете байт.

Как узнать частоту ноты, отличную от A220, A440, A880 и т. Д.?Каждый полутон вверх умножает предыдущую частоту на 12-й корень из 2. Поэтому вычислите 12-й корень из 2, умножьте его на 440, и это A #.Умножьте A # на корень 12 из 2, то есть B. B, умноженный на 12-й корень 2, это C, затем C # и так далее.Сделайте это 12 раз, и, поскольку это 12-й корень из 2, вы получите 880, что вдвое больше, чем вы начали.

Какова продолжительность воспроизведения каждой указанной ноты, когда содержимоеWAV-файл представляет собой сигнал?

Просто заполните пробное пространство, где звучит тон.Предположим, что вы хотите воспроизвести A440 в течение 30 секунд, а затем A880 в течение 30 секунд:

// initialize the data to "flat", no change in pressure, in the middle:
for(int i = 0; i < data.Length; i++)
  data[i] = 128;

// Add on a change in pressure equal to A440 for 30 seconds:
for(int i = 0; i < data.Length / 2; i++)
  data[i] = (data[i] + volume * Math.Sin(i * multiplier * 440.0))); 

// Add on a change in pressure equal to A880 for the other 30 seconds:

for(int i = data.Length / 2; i < data.Length; i++)
  data[i] = (byte)(data[i] + volume * Math.Sin(i * multiplier * 880.0))); 

как результат того, что несколько нот обратного преобразования FFT преобразованы в массив байтов, которые составляютданные в wav-файле?

Обратное БПФ просто строит синусоиды и складывает их вместе, как мы делаем здесь.Вот и все!

любая другая важная информация, относящаяся к этому?

Смотрите мои статьи на эту тему.

http://blogs.msdn.com/b/ericlippert/archive/tags/music/

Части с первой по третью объясняют, почему у пианино двенадцать нот на октаву.

Часть четвертая относится к вашему вопросу;вот где мы создаем WAV-файл с нуля.

Обратите внимание, что в моем примере я использую 44100 выборок в секунду, а не 11025, и я использую 16-битные выборки с диапазоном от -16000 до +16000 вместо 8битовые выборки, которые варьируются от 0 до 255. Но помимо этих деталей, они в основном такие же, как и у вас.

Я бы порекомендовал перейти на более высокую битовую скорость, если вы собираетесь использовать какой-либо сложный сигнал;8 бит с частотой 11K сэмплов в секунду будут звучать ужасно для сложных сигналов.Качество CD - 16 бит на семпл с частотой дискретизации 44 Кбайт в секунду.

И, честно говоря, намного проще понять математику, если вы делаете это в знаковых шортах, а не в беззнаковых байтах.

В пятой части представлен интересный пример слуховой иллюзии.

Кроме того, попробуйте смотреть свои волновые формы с помощью визуализации «scope» в Windows Media Player.Это даст вам хорошее представление о том, что на самом деле происходит.

ОБНОВЛЕНИЕ:

Я заметил, что при добавлении двух заметок вместе вы можете получить шум,из-за того, что переход между двумя сигналами слишком резкий (например, заканчивается в верхней части одной и начинается в нижней части следующей).Как можно решить эту проблему?

Отличный вопрос для продолжения.

По сути, здесь происходит мгновенный переход от (скажем) высокого давления к низкому давлениюуслышал как "поп".Есть несколько способов справиться с этим.

Техника 1: Сдвиг фазы

Одним из способов было бы "сдвиг фазы" последующего тона на некоторую небольшую величину, чтобы разница между начальным значением последующеготон и конечное значение предыдущего тона.Вы можете добавить член сдвига фаз, например, так:

  data[i] = (data[i] + volume * Math.Sin(phaseshift + i * multiplier * 440.0))); 

Если сдвиг фаз равен нулю, очевидно, это не изменится.Фазовый сдвиг 2π (или любой, даже кратный π) также не меняется, так как sin имеет период 2π.Каждое значение между 0 и 2π сдвигается, когда тон «начинается» немного дальше по волне.

Выяснить, что такое правильный сдвиг фазы, может быть немного сложно.Если вы прочтете мои статьи о генерации «непрерывно нисходящего» тона иллюзии Шепарда, вы увидите, что я использовал простое исчисление, чтобы убедиться, что все меняется непрерывно без каких-либо всплесков.Вы можете использовать аналогичные методы, чтобы выяснить, что такое правильный сдвиг, чтобы заставить исчезнуть поп.

Я пытаюсь выяснить, как генерировать значение сдвига фазы.Правильно ли использовать "ArcSin (((первая выборка данных новой заметки) - (последняя выборка данных предыдущей заметки)) / noteVolume)"?

Ну, первое, что нужно понять, это то, что не может быть"правильной ценностью".Если конечная нота очень громкая и заканчивается на пике, а начальная нота очень тихая, в новом тоне может не быть точки, соответствующей значению старого тона.

Предполагается, что есть решение, что это?У вас есть конечная выборка, назовите ее y, и вы хотите найти фазовый сдвиг x такой, что

y = v * sin(x + i * freq)

, когда i равен нулю.Так что это

x = arcsin(y / v)

Однако , это может быть не совсем правильно!Предположим, у вас есть

sine wave 1

и вы хотите добавить

sine wave 2

Есть два возможных сдвига фаз :

sine wave 3

и

Sine wave 4

Сделайте дикое предположение, какой из них звучит лучше.: -)

Выяснить, находитесь ли вы на "ход вверх" или "вниз ход" волны, может быть немного сложно.Если вы не хотите заниматься реальной математикой, вы можете сделать несколько простых эвристических операций, например: «Признак различия между последовательными точками данных изменился на переходе?»

Техника 2:Конверт ADSR

Если вы моделируете что-то, что должно звучать как настоящий инструмент, вы можете получить хорошие результаты, изменив громкость следующим образом.

То, что вы хотите сделать, этоесть четыре различных раздела для каждой ноты, называемые атака, распад, сустейн и релиз.Громкость ноты, сыгранной на инструменте, может быть смоделирована следующим образом:

     /\
    /  \__________
   /              \
  /                \
   A  D   S       R

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

Если вы сделаете это, то всплывающего окна не будет, потому что начало и конец каждой ноты равны нулю.объем.Релиз гарантирует это.

Разные инструменты имеют разные «конверты».Трубный орган, например, имеет невероятно короткие атаки, разрушение и высвобождение;это все сустейн, а сустейн бесконечен.Ваш существующий код подобен органу.Сравните с, скажем, пианино.Снова короткая атака, короткое затухание, короткое освобождение, но звук постепенно становится тише во время сустейна.

Секции атаки, затухания и выпуска могут быть очень короткими, слишком короткими, чтобы их можно было услышать, но достаточно длинными, чтобы предотвратить появление.Поэкспериментируйте с изменением громкости во время воспроизведения ноты и посмотрите, что произойдет.

5 голосов
/ 12 февраля 2011

Вы на правильном пути.:)

Аудиосигнал

Вам не нужно делать обратное БПФ (вы могли бы, но вам нужно найти библиотеку для нее или реализовать ее, плюсгенерирование сигнала в качестве входного сигнала к нему).Намного проще получить ожидаемый результат от IFFT, который представляет собой синусоидальный сигнал с заданной частотой.

Аргумент синуса зависит как от сгенерированной ноты, так и от частоты дискретизации генерируемого вами волнового файла (часто равного 44100 Гц, в вашем примере вы используете 11025 Гц).

Для тона 1 Гц необходим синусоидальный сигнал с периодом, равным одной секунде.При частоте 44100 Гц частота дискретизации составляет 44100 в секунду, а это значит, что нам необходим синусоидальный сигнал с периодом, равным 44100 выборкам.Поскольку период синуса равен Тау (2 * Пи), мы получаем:

sin(44100*f) = sin(tau)
44100*f = tau
f = tau / 44100 = 2*pi / 44100

Для 440 Гц получаем:

sin(44100*f) = sin(440*tau)
44100*f = 440*tau
f = 440 * tau / 44100 = 440 * 2 * pi / 44100

В C # этобыло бы что-то вроде этого:

double toneFreq = 440d;
double f = toneFreq * 2d * Math.PI / 44100d;
for (int i = 0; i<data.Length; i++)
    data[i] = (byte)(128 + 127*Math.Sin(f*i));

ПРИМЕЧАНИЕ. Я не проверял это, чтобы проверить правильность кода.Я постараюсь сделать это и исправить любые ошибки. Обновление: Я обновил код до чего-то, что работает.Извините за боль в ушах; -)

Аккорды

Аккорды представляют собой комбинацию нот (см., Например, Малый аккорд в Википедии ).Таким образом, сигнал будет комбинацией (суммой) синусов с разными частотами.

Чистые тоны

Тем не менее, эти тоны и аккорды не будут звучать естественно, поскольку традиционные инструменты не воспроизводят одночастотные тоны.Вместо этого, когда вы играете на А4, наблюдается широкое распределение частот с концентрацией около 440 Гц.См. Например Тембр .

2 голосов
/ 20 февраля 2014

Никто еще не упомянул алгоритм щипковых струн Karplus Strong.

Карплус – Сильный синтез струн Это чрезвычайно простой метод для генерации реалистичного звучного струнного звука. Я написал полифонические музыкальные инструменты / MIDI-плееры реального времени, используя это.

Вы делаете это так:

Во-первых, какую частоту вы хотите симулировать? Допустим, концертный шаг А = 440 Гц

Предположим, ваша частота дискретизации составляет 44,1 кГц, то есть 44100/440 = 100,25 выборок на длину волны.

Давайте округлим это до ближайшего целого числа: 100 и создадим кольцевой буфер длиной 100.

Таким образом, он будет удерживать одну стоячую волну с частотой ~ 440 Гц (заметьте, она не точная, есть способы обойти это).

Заполнить его случайным статическим значением от -1 до +1 и:

DECAY = 0.99
while( n < 99999 )
    outbuf[n++] = buf[k]

    newVal = DECAY  *  ( buf[k] + buf_prev ) / 2

    buf_prev = buf[k]
    buf[k] = newVal

    k = (k+1) % 100

Это удивительный алгоритм, потому что он очень прост и генерирует суперзвук.

Лучший способ понять, что происходит, это осознать, что случайная статика во временной области является белым шумом; случайная статика в частотной области. Вы можете представить его как композицию множества волн различной (случайной) частоты.

Частоты, близкие к 440 Гц (или 2 * 440 Гц, 3 * 440 Гц и т. Д.), Будут создавать конструктивные помехи для самих себя, поскольку они снова и снова проходят вокруг кольца. Так они будут сохранены. Другие частоты будут разрушительно мешать друг другу.

Кроме того, усреднение действует как фильтр нижних частот - представьте, что ваша последовательность равна +1 -1 +1 -1 +1 -1, если вы усредняете пары, тогда каждое среднее значение получается равным 0. Но если у вас медленнее волна, как 0 0,2 0,3 0,33 0,3 0,2 ... затем усреднение по-прежнему приводит к волне. Чем дольше волна, тем больше ее энергия сохраняется - то есть усреднение вызывает меньшее затухание.

Таким образом, усреднение можно рассматривать как очень простой фильтр нижних частот.

Конечно, есть сложности: необходимость выбора целочисленной длины буфера приводит к количественному определению возможных частот, которое становится заметным в верхней части пианино. Все преодолимо, но становится трудно!

Ссылки:

Delicious Max / MSP Tutorial 1: Karplus-Strong

Алгоритм Карплуса-Стронга

Насколько я понимаю, JOS является ведущим мировым авторитетом в области генерации синтетических тонов, все дороги ведут на его сайт. Но имейте в виду, что это очень сложно и требует математики университетского уровня.

...