Как создать звук с помощью одного лишь кода

Программный синтез звука
Синтез звука
Генерация звука
Web Audio API
Осциллятор
ADSR-огибающая
Звук окружает нас повсюду, но задумывались ли вы, как его можно создать искусственно? Мы привыкли к тому, что звук исходит из окружающей среды, но его можно сгенерировать и с помощью кода. В этом блоге мы разберём основы программного синтеза звука, начиная с математического описания и заканчивая использованием специализированных инструментов и библиотек. Вы узнаете, как генерировать звук с нуля, управлять его параметрами и применять различные техники синтеза.
Что такое звук?
Прежде чем приступить к программному синтезу, давайте разберёмся, что такое звук. Если говорить простыми словами, звук — это колебания воздуха. Когда мы говорим, играем на инструменте или даже просто хлопаем в ладоши, мы создаём волны в воздухе. Эти волны можно изобразить в виде графика, что делает их удобными для математического описания.
Изображение одного из хакатонов проведенных 7bits
Но как именно можно представить звук с точки зрения кода? Для этого нам потребуется немного математики.
Как описать звук математически?
Самый простой вид звуковой волны — это синусоида, которая описывается формулой:

f (x) = sin (x) или f (x) = cos (x)

Такие волны называют гармоническими. Они лежат в основе звукового синтеза. Генерируя синусоидальные волны, можно создать искусственный звук.
Однако простая синусоида редко встречается в реальной жизни. В большинстве случаев звук представляет собой сложную комбинацию волн с разными частотами и амплитудами. Для их создания нам понадобится специальный инструмент.
Осциллятор — основной инструмент синтеза
Чтобы генерировать звук программно, используется осциллятор. Он создаёт простую волну с заданной частотой.
Но реальный звук сложнее, чем просто синусоида. Он содержит дополнительные волны — гармоники. Это частоты, кратные основному тону. Они формируют тембр звука и позволяют отличать, например, звук пианино от гитары, даже если они играют одну и ту же ноту.
Смешивая основные и дополнительные волны, можно получить разные формы сигнала:
  • Синусоида — чистый, мягкий звук.
  • Квадратная волна — резкий, похожий на сигнал ретро-игр.
  • Треугольная волна — более приглушённый, но всё же резкий звук.
Пилообразная волна — насыщенный звук с большим количеством гармоник, используется, например, в синтезаторах.
Но просто создать волну недостаточно — важно ещё и контролировать её громкость и продолжительность. Здесь на помощь приходит ADSR-огибающая.
Управление звучанием — ADSR-огибающая
Чтобы звук не просто включался и выключался мгновенно, а звучал естественно, используется ADSR-огибающая. Она определяет, как звук развивается во времени:
  • Attack (подъём) — время, за которое звук набирает громкость.
  • Decay (спад) — небольшое ослабление после пика.
  • Sustain (поддержка) — уровень громкости, который держится, пока нота звучит.
  • Release (затухание) — постепенное исчезновение звука.
Благодаря ADSR можно сделать звук плавным и реалистичным.
Языки и инструменты для звукового программирования
Если обобщить всё сказанное выше, то получается, что для создания звука нам нужен осциллятор, и на него накидываются различные эффекты. В мире IT есть множество инструментов, реализующих осциллятор на разных языках. Есть даже отдельные языки для написания музыки.
Одним из самых известных является CSound. Вышел он в 1986 году и написан на C, что сказывается на его синтаксисе. Это довольно комплексный низкоуровневый язык близкий к машинному коду. Но как и во многих низкоуровневых языках, благодаря этому на CSound можно сгенерировать почти любой звук — было бы желание. Вот пример лишь одной из композиций, для которой использовался данный язык: BT All That Makes Us Human Continues.
Для этого языка есть отдельное API, позволяющее подключать его к другим языкам таким как Python, Java или Lua. За счёт этого данный язык иногда используют в связке с нейронными сетями, чтобы генерировать музыку на ходу.
Есть ещё один довольно известный язык: Pure Data. Он уже гораздо легче в понимании для человека, хоть по сравнению с CSound и имеет чуть менее обширную функциональность. Данный язык является визуальным, т. е. работа с ним ведётся с помощью нод, которые надо соединять друг с другом. Не смотря на визуальную нотацию он всё ещё является полным по Тьюрингу, т. е. в нём можно писать полноценные программы, используя логические операторы, циклы и т. д.
Он используется для создания интерактивных звуковых и видеоинсталляций, композиций и аудиовизуальных проектов. С помощью него нередко пишут звуковые эффекты для инструментов, либо же новые плагины для синтезаторов. Pure Data позволяет пользователям контролировать звук, видео и графику в реальном времени, открывая новые возможности для творчества.
Пример мелодии, написанной на Pure Data вы можете услышать здесь: Pure Data ambient.
Работа со звуком в браузере: Web Audio API
В случае если вам понадобилось написать музыку в браузере, для этого уже есть готовый инструмент Web Audio API, который работает с большинством браузеров. Это позволяет вам написать мелодию на JS не подключая никаких библиотек. Давайте подробно разберёмся с тем, как в нём работать со звуком. Для начала нам нужно инициализировать AudioContext. Именно через него и будет вестись вся работа со звуком.
var context = new (window.AudioContext || window.webkitAudioContext)();
var gainNode= context.createGain();
gainNode.gain.value = 0.5; // Установка громкости
gainNode.connect(context.destination);
Далее создаём осциллятор. Указываем тип его волны и подключаем его к нашему контексту.
oscillator = context.createOscillator();
oscillator.type = 'sine';
oscillator.connect(gainNode);
Ну и дальше просто создаём массив нот и по очереди играем каждую из них
var frequencies = [
261.63, // C
293.66, // D
329.63, // E
349.23, // F
392.00, // G
440.00  // A
];
// Воспроизведение первой ноты
oscillator.frequency.value = frequencies[0];
oscillator.start(now);
// Воспроизведение остальных нот
for (var i = 1; i < frequencies.length; i++) {
// Изменение частоты осциллятора для следующей ноты
oscillator.frequency.setValueAtTime (frequencies[i], now + i * noteDuration);
}
// Остановка осциллятора после последней ноты
oscillator.stop(now + frequencies.length * noteDuration);
Закрывается контекст через функцию close().
            context.close().then(() => {...}).catch((error) => {...});
Дальше уже по мере вашей усидчивости можно создать множество вещей: сложную мелодию, генератор звуков или даже свой секвенсор — инструмент, который позволяет записывать, редактировать и воспроизводить последовательности музыкальных событий (нот, параметров синтеза, эффектов и т. д.).
Но возникает вопрос: можно ли работать со звуком быстрее, без постоянной необходимости открывать/закрывать осцилляторы и аудио контексты? Сам факт того, что с Web Audio API можно работать через JS уже говорит о том, что да, можно. Существует множество библиотек и фреймворков, созданных для того, чтобы не приходилось постоянно думать о таких низкоуровневый вещах: howler. js, Webaudiox. js, Tone.js.
У нас наибольшее количество опыта было именно с последним фреймворком. Он предоставляет обширную функциональность для создания уникального звука. В нём уже заготовлено много эффектов и синтезаторов, которые работают сразу «из коробки». Tone. js также позволяет задавать очередь прослушивания звуков не только через секунды, но и через доли тактов, что позволяет писать музыку в гораздо более удобном формате. Вот пример той же программы, что была написана выше, но только через Tone. Js:
const synth = new Tone.PolySynth(Tone.Synth).toDestination();
const now = Tone.now();
synth.triggerAttack("D4", now);
synth.triggerAttack("F4", now + 0.5);
synth.triggerAttack("A4", now + 1);
synth.triggerAttack("C5", now + 1.5);
synth.triggerAttack("E5", now + 2);
synth.triggerRelease(["D4", "F4", "A4", "C5", "E5"], now + 4);
Как можно видеть, код читается гораздо приятнее, чем если бы мы делали то же самое через Web Audio API.
Заключение
Программный синтез звука — это мощный инструмент, который открывает огромные возможности. От простейших синусоидальных волн до сложных композиций с эффектами — всё это можно создать с помощью кода. В зависимости от ваших потребностей можно использовать разные языки и библиотеки, будь то низкоуровневый CSound, визуальный Pure Data или удобный Web Audio API с Tone.js.
Теперь, вооружившись этими знаниями, вы можете начать эксперименты и создать свою уникальную музыкальную программу!