Программная генерация звуков

Звук можно представить ввиде бесконечного количества волн различной частоты
и амплитуды. Волны, в свою очередь, могут иметь практически любую форму.
Из самых распространенных и чаще всего используемых можно назвать: синусоидальная (sine), квадратная (square), пилообразная(saw), треугольная (triangle), и шум (noise). Сначала попробуем разобраться с основными параметрами волны: период и амплитуда.



Частота (измеряется в герцах) — количество периодов в секунду. Например при частоте 44100Hz количество периодов равно 44100. Теперь когда основные термины изучены перейдем непосредственно к алгоритму генерации волн.

1) Sine волна

float samplerate; // частота сэмпла
float wavefrequency; // частота волны
float wavevolume; // громкость волны

float period=samplerate/wavefrequency/2; //вычисляем период волны
float pi=3.14; //число pi
int n;

for(int a=0;a<samplelenght;a++) //устанавливаем цикл на длину сэмпла
{
n=wavevolume*sin(a*pi/period); //вычисление sine-волны
buffer[a]=n; //заносим вычисленное значение в буфер
}


Например нам надо получить 16-килобайтный синусоидальный звук частотой 1000Hz и при этом качество сэмпла должно быть 44100Hz, тогда наши параметры будут иметь следующие значения: samplerate=44100, wavefrequency=1000, samplelenght=16384.

Особенного пояснения требует параметр wavevolume. Издавна известно, что качество звука пропорционально зависит от его битности (8-bit, 16-bit, 24-bit и т.д.). Для 8-bit — значение от 0..255, для 16-ти – 0...65536, для 24-х – 0...16777216. Какой выбрать? Смотря какая у вас задача, но меньше 16-ти я бы не посоветовал (хотя бывают и исключения – когда надо сократить объем сэмпла взамен качеству).

2) Пилообразная волна (saw)

float sr_2m=samplerate/wavefrequency; //Вычисляем период
int c=0; //Специальная переменная для проверки
//на окончание периода

for(int a=0;a<samplelenght;a++) //цикл на длину сэмпла
{
if(c>=sr_2m) c=0; //если период закончился, то начать следующий
buffer[a]=wavevolume*(c/period)-wavevolume; //вычисление волны
c++;
}


3) Треугольная (triangle)

float period=samplerate/wavefrequency/2; //вычисляем период волны
int c=period*2;
int c2=-1;
float sr_2m=period;
float sr=samplerate/wavefrequency/4;

for(int a=0;a<samplelenght;a++)
{
if(c>sr_2m) c=sr_2m,c2=-1;
if(c<0) c=0,c2=1;
buffer[a]=wavevolume*(c/sr)-wavevolume;
c+=c2;
}



4) Шум (noise)

srand(wavevolume);
s1=mv.samplerate/wor,psc=s1+1,wov>>=7;

if(tabcnt==0)
{
for(ps=0;ps<mv.samplelenght;ps++)
{
if(psc>s1)
{
psc-=s1;
n=256*((rand()%(wov+1))-wov/2);
}
buffer[ps]=n;
psc++;
}
}


Теперь усложним задачу — сложим две волны разной частоты и амплитуды.


char buffer[16384];

float tone1=65,tone2=131;
float samplerate=44100/2; // Частота дискретизации
int a,b,amp1=128,amp2=64;

for(a=0;a<16384;a++)
{
b=amp1*sin(a*pi/samplerate/tone1)+amp2*sin(a*pi/samplerate/tone2);
if(b>128) b=128;
if(b<-128) b=-128;
buffer[a]=b;
}



С таким сэмплом уже можно писать мелодии. В принципе, это основа wave
synthesis при помощи которой можно создать любой звук.

И напоследок пара советов:

1) Для получения более насыщенного звука складывайте волны с различными
формами: sine+square, triangle+saw, или вот такой вот монстр: saw+square+
triangle+saw.
2) Перкусия (hat) лучше всего получается путем сложения noise+sine.
Поделиться публикацией

Комментарии 40

    +5
    Ждал аудио-примеров
      +19
      Ждал дабстеп
        0
        Исходник Reason?
          0
          growl wobble можно и без reason'а сделать, одним ableton'ом :-)
      +1
      Помню ещё на pascal была функция в модуле Crt: sound(n) где n — частота, которая включала внутренний динамик; nosound выключала. Тоже пробовал разные звуки получать.
        0
        Удалось заставить его разговаривать?
          0
          Разговаривать врятли, а вот простая, но красивая волна (сначала повышение, потом понижение частоты с одинаковым шагом) девчонкам нравилась.
            +8
            Чёрт, а я думал девчонкам нравятся мотоциклы и брутальные музыканты, и программировал втихаря. А оно вон как… Полжизни коту под хвост.
              0
              Вы не поверите, но некоторым девчонкам и самим программировать нравится.
            +3
            Таким манером точно получалось заставить разговаривать ZX-Spectrum. Только там управление динамиком шло через бит в одном из служебных портов вывода. Через него же управлялся выход на магнитофон и цвет бордюра. А благодаря наличию управлению цветом бордюра можно было создавать незабываемые эффекты с полосами в такт звуку.
              +4
              Под DOS на PC динамик тоже разговаривал. Была прога-заподлянка, произносившая фразу «здравствуй, жопа!». Технически это было реализовано как прямое обращение к порту динамика и формирование ШИМ-сигнала. Несущая частота в районе десятка кГц была не слышна, а огибающая давала как раз речевой сигнал.
          +2
          Незачем предвычислять амплитуды отсчетов на весь семпл. Достаточно вычислить один период и повторить нужное число раз.
          Если код выполняется на микроконтроллере, например, то даже вычисление синуса — часто непозволительная роскошь. Используют один раз вычисленную таблицу синусов, которая хранится в EEPROM.
            +3
            Незачем предвычислять амплитуды отсчетов на весь семпл.

            Как категорично. Нет, если мы говорим о синтезе именно звука, то есть того, что принято слышать ушами, лучше считать напрямую.
            1. На компьютере проще посчитать sin, чем брать число из таблицы, которая еще должна в кэш влезать.
            2. На контроллере лучше использовать следующий алгоритм, основанный на решении дифференциального уравнения, в котором синусы в цикле вообще не нужны:
            Init:
            y1=sin(ip-w)
            y2=sin(ip-2*w)

            Loop:
            y0 = b1*y1 - y2
            y2 = y1
            y1 = y0
              0
              > Незачем предвычислять амплитуды отсчетов на весь семпл.
              Это был скорее комментарий к приведенному в статье коду. Если нужно сгенерировать 1000 периодов синусоиды, совершенно незачем повторять цикл 1000 раз, достаточно обсчитать один период. Нет, даже четверть периода, а потом периодически менять знак результата и переворачивать порядок отсчетов.
              Если генерим что-то посложнее синусоиды, этот трюк может не сработать.

              А за подход с решением (точнее, аппроксимацией, так?) дифура на ходу — отдельное спасибо!
                +4
                Лучше повторить цикл 1000 раз. Это преждевременная оптимизация, которая может обернуться головной болью…
                1. Округления. Для ноты си 466,16 герц и Fs=44100 Гц период волны занимает 44100/466,16=94,6 семплов. Нецелое число. Если взять период в 94 семпла, то итоговая частота будет 44100/94 =469 Гц. Три герца — это не так мало, особенно в аккорде, плюс спектр будет уже не точным синусом, а уши отлично слышат шум даже на -40—-60 дБ.
                2. Хорошо, мы сделали lookup-таблицу для ноты си. Что делать с остальными нотами, если их потребуется проиграть? Переинтерполировать ноту си? Это медленнее FSIN, не говоря уже о SSE. Взять очень-большую-таблицу, чтобы можно было обойтись интерполяцией методом ближайшего? Это тоже по скорости на уровне FSIN из-за того, что такая таблица размером с кэш процессора.
                Генерить таблицу каждый раз при нажатии новой клавиши? Так делают многие современные VSTi, которые лагают именно при нажатии новой клавиши, а потом срабатывает мемоизация и звук идет ровный, и это очень бесит.
                3. Придумали выход — множество мелких lookup-таблиц с n периодов (чтобы не было ошибок округления) для всех или нескольких последних звучавших нот. Ok, это работает, но если юзер захочет изменить питч, то придется все пересчитывать или ресемплировать на лету. Что опять же медленнее FSIN.
            +2
            Скажите, а куда можно запихать этот buffer что-бы послушать, что получилось?
              +3
              Можно немножечко погуглить по словам WAVE, RIFF, PCM, чтобы получить ссылки вроде: раз, два, три — где вполне нормально описан формат, известный как *.wav и потом его слушать в чём угодно.

              PS если всё же понадобится помощь помощь — обращайтесь :)
                0
                Можно заглянуть сюда articles.org.ru/cfaq/index.php?qid=2447
                там сохранился архив из моих старых зметок по delphi. В этой какраз преобразование звуков через libsndfile и его проигрывание
                  0
                  Рекомендую экспериментировать во флэше. Просто и быстро.
                  +4
                  Примеры.

                  Sine + Square: moon.fm/ayj/
                  Triangle + Saw: moon.fm/ayl/
                  Saw + Square + Triangle + Saw: moon.fm/ayk/
                    –1
                    Замечательно!
                    А noise + saw будет, наверное, вдох Вейдера.
                      0
                      Ссылки эти в пост рекомендую вставить
                        +7
                        Сделано будет по автора усмотрению.
                      +9
                      Автор, мне кажется тебе стоит спрятать статью в черновики и дописать. Очень сыро написано, информации практически нет. Профессионалы читать не будут, а новички просто ничего не поймут.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          +2
                          Пианино — это основная частота (синус) + несколько гармоник. Проше всего снять спектр настоящего рояля и по нему выставить амплитуды гармоник. Да, еще нужно обеспечить резкую атаку и плавное затухание семпла, иначе орган получится.
                          • НЛО прилетело и опубликовало эту надпись здесь
                            +4
                          • НЛО прилетело и опубликовало эту надпись здесь
                            • НЛО прилетело и опубликовало эту надпись здесь
                                +1
                                Ты хотя бы написал, на каком языке всё это написано. Непонятно же, как применять.
                                  0
                                  А как это все дело заставить звучать? например у меня на ноутбуке?)
                                    +1
                                    а как же aliasing?
                                      –1
                                      За что уважал турбопаскаль и, пардон, бейсик, так это за встроенные средства управления звуком, хоть и примитивные. В паскале был sound(n), а в MSX Basic — PLAY «TnVnOnCDEFGAB», а также более продвинутые средства управления несущими, огибающими и еще какой-то хренью. А попробуй сейчас с нуля быстро закодь воспроизведения звука, формируемого в реальном времени…
                                        0
                                        Самое клевое было, что PLAY в бейсике мог играть даже фоном, пока остальная часть программы продолжада выполняться.
                                        +9
                                        Очень хреново написано. Во-первых, стоило бы объяснить, зачем и почему «звук можно представить в виде бесконечного набора волн». Обычно раскладывают звук на частотные составляющие по той причине, что человеческое ухо (для которого мы и пишем этот звук) воспринимает именно частотный спектр, и это помогает понять, как создать что-то похожее.

                                        Во-вторых, опять же, из чистых синусоид и треугольников получится очень хреновый и электронный звук. Еще нужны как минимум фильтры, генераторы огибащих, тремоло/вибрато и изменение этих характеристик во времени (это важно — практически любой звук, который не меняется во времени, на слух звучит как электроорган, и может быть скучным. Естественные звуки весьма изменчивы).

                                        В-третьих, есть и другие виды синтеза звука, например физическое моделирование, а то что описано — это самое начало аддитивного синтеза.

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

                                        В пятых, кто-то в комментариях спрашивал про звук пианино. Тут есть хитрость. В данном случае звук состоит из короткого острого начального кусочка (атака), который сразу распознается ухом, и последующего длительного затухающего сигнала, который тоже не так прост, как кажется — его спектр меняется со временем. Обычно семплерные синтезаторы хранят в памяти именно этот начальный кусочек в виде семпла (выделенного их реального звука пианино), а затухающую часть синтезируют. Эта технология довольно-таки старая, по моему в 80-х придумана, забыл, как называется.

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

                                        Пример такой программы — Modartt Pianoteq — сделано довольно-таки неплохо, оно гремит и гудит, и издает вполне похожие на настоящие звуки.

                                        Ну и третий способ — полностью семплерное пианино, просто в студии записывают звук каждой клавиши, нажатой с разной силой. Такие пианины легко опознать по многогигабайтному объему дистрибутива :) Из минусов семплов — «machine gun effect», когда для одной ноты, играющей несколько раз подряд через котороткие промежутки, используют один и тот же семпл, ухо слышит подвох. Поэтому каждую ноту надо записать несколько раз и чередовать семплы случайно.

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

                                        А кому из хабраюзеров интересно почитать про основы синтеза звука — почитайте, например вот этот цикл статей: www.soundonsound.com/sos/allsynthsecrets.htm
                                          +3
                                          Всё таки хабр удивительный ресурс. Иногда комментарии оказываются полезнее самой статьи.
                                          0
                                          Давайте разделять 2 темы — что такое звук и как этот звук программировать.
                                          Начать надо, по-моему, с первого. Для этого очень поможет книга «How to make a noise»
                                          noisesculpture.com/how-to-make-a-noise

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

                                          Короче книжка незаменимая.

                                          А статья бесцельная эта — для кого она?
                                            +1
                                            Статья не для кого, а для чего. Для инвайта.
                                            Инвайт получен, а от автора ни одного комментария.
                                            +1
                                            А можно попросить раскрыть тему для людей, совсем далеких от программирования? был бы очень признателен, да и не только я один.

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

                                            Самое читаемое