AudioKit — это аудиофреймворк, разработанный аудиоинженерами, программистами и музыкантами специально для iOS и OSX. Он предоставляет инструменты для обработки и синтезирования звука. Под капотом — это смесь из Swift, Objective-C, C++ и С, и Audio Unit API от Apple. Потрясающие (и довольно сложные) технологии завёрнуты в очень дружелюбное Swift API, к которому можно обращаться прямо из Xcode Playgrounds!
В этом туториале мы отправимся в путешествие по AudioKit, а заодно и истории синтезирования звука. Вы узнаете об основах физики звука и том, как работали первые синтезаторы, такие как Hammond Organ. Также будут рассмотрены и современные техники, например, семплинг.
Приготовьте ваш любимый напиток, устраивайтесь поудобнее и вперёд!
Первый шаг
Начиная с AudioKit 3.6, настройка плейграундов для работы с фреймворком стала совсем простой. Скачайте и разархивируйте стартовый плейграунд по ссылке.
Путешествие начнётся с нескольких шагов по настройки плейграундов для работы с AudioKit.
Откройте AudioKitPlaygrounds.xcodeproj файл в Xcode. Нажмите кнопку + в левом нижнем углу экрана, выберите File… и далее Blank в секции Playground, сохраните новый файл в папке Playgrounds вместе с демонстрационными плейграундами.
Только что добавленный плейграунд запустится и будет выглядеть примерно так:
Замените сгенерированный код на следующий:
import AudioKitPlaygrounds
import AudioKit
let oscillator = AKOscillator()
AudioKit.output = oscillator
try AudioKit.start()
oscillator.start()
sleep(10)
Плейграунд не будет запускаться до тех пор, пока вы, хотя бы, один раз не соберёте проект, для этого можно использовать пункт меню Product / Build или комбинацию клавиш ⌘-B. После этого запустите плейграунд ещё раз и услышите гудящий на протяжении 10 секунд звук. (Прим. переводчика: иногда ошибка не пропадает, и нужно перейти в другой плейграунд, и вернуться обратно, чтобы всё заработало). Вы можете использовать Плей/Стоп кнопку слева внизу окна плейграунда, для того чтобы остановить плейграунд или запустить его заново.
Заметка: Если вы всё ещё видите ошибки, попробуйте перезапустить Xcode. К сожалению, фреймворки и плейграунды не очень хорошо дружат друг с другом и могут вести себя непредсказуемо.
Осцилляторы и физика звука
Человечество воспроизводит музыку с помощью различных физических объектов на протяжении тысяч лет. Многим привычным для нас инструментам, таким как гитара или барабаны, насчитываются сотни лет. Первый зафиксированный опыт использования электронных схем для генерации звука был проведён в 1874 году Илайшей Грей. Илайша работал в области телекоммуникаций, и он изобрёл осциллятор — самый простой из музыкальных синтезаторов. C него мы и начнём наше погружение.
Нажмите правой кнопкой мыши на ваш плейграунд, выберите New Playground Page и замените сгенерированный код приведённым ниже:
import AudioKitPlaygrounds
import AudioKit
import PlaygroundSupport
// 1. Create an oscillator
let oscillator = AKOscillator()
// 2. Start the AudioKit 'engine'
AudioKit.output = oscillator
try AudioKit.start()
// 3. Start the oscillator
oscillator.start()
PlaygroundPage.current.needsIndefiniteExecution = true
Плейграунд начнёт генерировать непрерывное гудение — так и задумывалось. Вы можете нажать стоп, чтобы прекратить его.
Это практически то же самое, что мы делали в предыдущем плейграунде, но в этот раз мы погрузимся в детали.
Рассмотрим все шаги по порядку:
- Тут мы создаём осциллятор. Осциллятор является наследником
AKNode
. Ноды — это элементы, из которых строится аудиоцепочка, генерирующая и модифицирующая звук. - Тут мы связываем выход нашей последней ноды с движком AudioKit. В нашем случае всего одна нода. Дальше фреймворк направит выход ноды на устройство воспроизведения звука.
- Ну и стартуем осциллятор, на выход он начинает отправлять звуковую волну.
Осциллятор генерирует повторяющийся сигнал, который не останавливается. В этом плейграунде AKOscillator
генерирует синусоиду. Цифровой сигнал синусоиды при помощи AudioKit отправляется на колонки или наушники. В итоге, мы слышим звук который колеблется на той же частоте, что и сгенерированная нами синусоида. Звучит эта синусоида не очень музыкально.
За звук этого осциллятора отвечают два параметра: амплитуда — высота синусоиды, определяет громкость звука и частота — от неё зависит высота звука.
В плейграунде добавьте следующие строчки кода прямо после создания осциллятора:
oscillator.frequency = 300
oscillator.amplitude = 0.5
Звук изменился, теперь он звучит в два раза тише и намного ниже. Частота (frequency) сигнала измеряется в герцах (количество повторений в секунду) и определяет высоту ноты. Амплитуда (amplitude) задаётся от 0 до 1 и отвечает за громкость.
Изобретение Илайши Грея было зафиксировано в первом в истории патенте на электронный музыкальный инструмент.
Много лет спустя Лев Термен изобрёл немного странный музыкальный инструмент, используемый и по сей день, — терменвокс. В терменвоксе вы можете изменять частоту звука движениями рук над инструментом. Чтобы понять, как звучит терменвокс, вы можете послушать композицию Vibrations коллектива Beach Boys, звук терменвокса там ни с чем не перепутаешь.
Вы можете имитировать звук терменвокса, для этого добавьте следующий код к настройкам осциллятора:
oscillator.rampDuration = 0.2
И замените строчку стартующую AudioKit (try AudioKit.start()
) на следующие:
let performance = AKPeriodicFunction(every: 0.5) {
oscillator.frequency =
oscillator.frequency == 500 ? 100 : 500
}
try AudioKit.start(withPeriodicFunctions: performance)
performance.start()
Свойство rampDuration
позволяет осциллятору плавно менять значения его параметров (например, частоты или амплитуды). AKPeriodicFunction
— полезная утилита из AudioKit для периодического выполнения кода. В нашем примере она меняет частоту синусоиды с 500Hz на 100Hz каждые 0.5 секунды.
Поздравляю! Вы только что сделали свой первый терменвокс. Простой осциллятор может генерировать музыкальные ноты, но звучит он не очень приятно. Есть много факторов, которые влияют на звук физических инструментов, таких как, например, пианино. И дальше мы рассмотрим некоторые из них.
Звуковые огибающие
Когда музыкальный инструмент исполняет ноту, громкость её звучания меняется со временем, а характер изменений отличается от инструмента к инструменту. Модель, которая может имитировать этот эффект называется Attack-Decay-Sustain-Release(ADSR) огибающая (Прим. пер.: синтезаторы, доступные к покупке никогда не локализуются, поэтому название кривой такое, каким его можно увидеть на панели настоящего синтезатора).
ADSR-огибающая состоит из:
- Attack: атака или время, за которое звук набирает максимальную громкость
- Decay: дикей или время за которое громкость опустится от максимальной до основной
- Sustain: основная громкость, в отличие от предыдущих двух параметров не является временем, после того, как атака и дикей пройдут и до того, как вы отпустите клавишу синтезатора звук будет генерироваться с этой громкостью
- Release: релиз или время, за которое громкость станет нулевой
В пианино звук извлекается молоточками, стучащими по струнам, и поэтому в нём быстрые или, ещё говорят, короткие атака и дикей. У скрипки могут быть долгие атака, дикей и сустейн, музыкант может влиять на эти параметры тем, как он использует смычок.
Одним из первых электронных музыкальных инструментов был Hammond Novachord. Этот инструмент был изготовлен в 1939 году, состоял из 163 вакуумных ламп и более 1000 конденсаторов, а весил он 230 килограмм. К сожалению, было изготовлено всего несколько сотен его экземпляров, и он так и не получил коммерческого успеха.
Кликните правой кнопкой мыши на плейграунд Journey, выберите New Playground Page и создайте новую страничку с именем ADSR. Замените сгенерированный код следующим:
import AudioKitPlaygrounds
import AudioKit
import PlaygroundSupport
let oscillator = AKOscillator()
В нём просто создаётся осциллятор, с которым мы уже знакомы. Дальше добавьте следующий код в конец плейграунда:
let envelope = AKAmplitudeEnvelope(oscillator)
envelope.attackDuration = 0.01
envelope.decayDuration = 0.1
envelope.sustainLevel = 0.1
envelope.releaseDuration = 0.3
Тут создаётся AKAmplitudeEnvelope
, который задаёт ADSR-огибающую. Параметры длительности (attackDuration, decayDuration, releaseDuration) указываются в секундах, а громкость (sustainLevel) задаётся в интервале от 0 до 1.
AKAmplitudeEnvelope
— это наследник AKNode
точно так же, как и AKOscillator
.В коде выше осциллятор мы передаём в инициализатор ноды огибающих, тем самым соединяя ноды.
Дальше добавьте следующий код:
AudioKit.output = envelope
let performance = AKPeriodicFunction(every: 0.5) {
if (envelope.isStarted) {
envelope.stop()
} else {
envelope.start()
}
}
try AudioKit.start(withPeriodicFunctions: performance)
performance.start()
oscillator.start()
PlaygroundPage.current.needsIndefiniteExecution = true
Он запустит AudioKit, но на этот раз на его вход мы подаём выход с ADSR-ноды. Для того чтобы слышать ADSR-эффект, мы постоянно включаем и выключаем ноду с помощью AKPeriodicFunction
.
Теперь вы можете слышать как циклически играется нота, но в этот раз она немного больше похожа на пианино.
Цикл выполняется два раза в секунду на каждой итерации, стартуя или останавливая ADSR. Когда ADSR стартует с быстрой атакой, громкость достигает максимальной величины за 0.01 секунду, после этого громкость за 0.1 секунды снижается до основного уровня и находится там в течении 0.5 секунды, и в конце затухает за 0.3 секунды.
Вы можете самостоятельно поиграться с параметрами и попробовать воспроизвести, например, звук скрипки.
Звуки, с которыми мы познакомились, основываются на синусоиде, которую генерирует AKOscillator
. Несмотря на то, что ASDR помогает сгладить резкое звучание, назвать эти звуки музыкальными всё ещё нельзя.
Дальше мы рассмотрим как получают более глубокое звучание.
Аддитивный синтез
Каждый музыкальный инструмент обладает своим особым звуком или тембром. Тембр — это то, что отличает звуки пианино от звуков скрипки, даже несмотря на то, что играть они могут одну и ту же ноту. Важным параметром тембра является звуковой спектр. Звуковой спектр описывает диапазон воспроизводимых частот, которые, суммируясь, дают ноту. Наш текущий плейграунд использует осциллятор, который звучит только на одной частоте, и звучит он довольно искусственно.
Можно получить более живой звук, используя набор осцилляторов для воспроизведения одной ноты. Такой подход известен как аддитивный синтез, и это тема нашего следующего плейграунда.
Нажмите правой кнопкой мыши на плейграунд, выберите New Playground Page и создайте новую страницу с названием Additive Synthesis. Замените сгенерированный код следующим:
import AudioKitPlaygrounds
import AudioKit
import AudioKitUI
import PlaygroundSupport
func createAndStartOscillator(frequency: Double) -> AKOscillator {
let oscillator = AKOscillator()
oscillator.frequency = frequency
return oscillator
}
Для аддитивного синтеза вам потребуется несколько осцилляторов, для этого будем использовать. createAndStartOscillator
Дальше добавьте код:
let frequencies = (1...5).map { $0 * 261.63 }
Здесь мы берём интервал чисел от 1 до 5 и умножаем каждое на число 261.53 — частоту ноты До. Получившиеся кратные частоты называются гармоники.
Теперь добавим код:
let oscillators = frequencies.map {
createAndStartOscillator(frequency: $0)
}
Тут мы создали осциллятор под каждую из используемых нами частот.
Чтобы объединить осцилляторы добавьте такой код:
let mixer = AKMixer()
oscillators.forEach { $0.connect(to: mixer) }
AKMixer
— это ещё один вид AudioKit-нод. Он принимает на вход сигналы с одной или нескольких нод и объединяет их в один.
Добавьте вот такой код:
let envelope = AKAmplitudeEnvelope(mixer)
envelope.attackDuration = 0.01
envelope.decayDuration = 0.1
envelope.sustainLevel = 0.1
envelope.releaseDuration = 0.3
AudioKit.output = envelope
let performance = AKPeriodicFunction(every: 0.5) {
if (envelope.isStarted) {
envelope.stop()
} else {
envelope.start()
}
}
try AudioKit.start(withPeriodicFunctions: performance)
performance.start()
oscillators.forEach { $0.start() }
С этим кодом всё должно быть понятно: добавляем ADSR к выходу миксера, выводим через AudioKit и периодически включаем/выключаем.
Для того, чтобы хорошо разобраться с аддитивным синтезом, будет полезно поиграть с различными комбинациями этих частот. И в этом нам идеально подойдёт такая возможность плейграундов как Live View.
Для этого добавьте код:
class LiveView: AKLiveViewController {
override func viewDidLoad() {
addTitle("Harmonics")
oscillators.forEach {
oscillator in
let harmonicSlider = AKSlider(
property: "(oscillator.frequency) Hz",
value: oscillator.amplitude
) { amplitude in
oscillator.amplitude = amplitude
}
addView(harmonicSlider)
}
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = LiveView()
В AudioKit есть классы специально для удобной работы в плейграундах. Мы в нашем примере используем AKLiveViewController
, с помощью него мы вертикально размещаем элементы. А так же для каждого осциллятора мы создаём AKSlider
. Слайдеры инициализируются значениями частоты и амплитуды осцилляторов, и вызывают блок при взаимодействии с ними. В блоке каждого слайдера мы меняем амплитуду соответствующего осциллятора. Так просто можно добавить интерактивности в ваши плейграунды.
Для того чтобы увидеть результаты выполнения плейграунда нужно чтобы Live View отображалось на экране. Для этого выберите кнопку с пересекающимися окружностями в верхней правой части окна и убедитесь что для Live View выбран правильный плейграунд.
Для того чтобы поменять тембр вашего инструмента, вы можете менять значения каждого слайдера по отдельности. Для реалистичного звучания предлагаю вам попробовать конфигурацию, изображенную на скриншоте выше.
Одним из первых синтезаторов, использовавших аддитивный синтез был Teleharmonium и весил он 200 тонн! Его невероятный вес и размеры, скорей всего, и стали причиной его безвестности. Более успешный Hammond орган использовал схожие тоновые колёса, но был намного меньших размеров. Изобретённый в 1935 году, он до сих пор широко известен как популярный в эпоху прогрессивного рока инструмент.
Тоновое колесо — это вращающийся диск с небольшими впадинами по краю и звукосниматель, расположенный рядом с краем. У органа Hammond был целый набор тоновых колёс, которые могли вращаться с разными скоростями. Довольно необычный способ генерировать звук является, скорее, электромеханическим, чем электронным.
Для того чтобы генерировать более реалистичный звуковой спектр, есть ещё несколько техник: частотная модуляция (Frequency Modulation или FM) и широтно-импульсная модуляция (Pulse Width Modulation или PWM), обе техники доступны в AudioKit в классах AKFMOscillator
and AKPWMOscillator
соответственно. Предлагаю вам попробовать обе, подставив их вместо AKOscillator
, которые мы использовали ранее.
Полифония
В 1970-х началось движение от модульных синтезаторов, состоящих из отдельных осцилляторов огибающих и фильтров, к микропроцессорным. Вместо использования аналоговых схем звук начали генерировать в цифровом формате. Это позволило сделать синтезаторы более дешёвыми и компактными, а синтезаторы таких брендов как Yamaha — очень популярными.
Все наши плейграунды были ограничены воспроизведением одной ноты за раз. Многие инструменты способны играть более одной ноты за раз, их называют полифоническими. Инструменты, которые способны играть только одну ноту, называют монофоническими.
Для того чтобы получить полифонический звук, можно создать несколько осцилляторов и направить их в миксер, но в AudioKit для этого есть более подходящий способ.
Создайте новую страницу в плейграунд и назовите её Polyphony. Заменить сгенерированный код следующим:
import AudioKitPlaygrounds
import AudioKit
import AudioKitUI
import PlaygroundSupport
let bank = AKOscillatorBank()
AudioKit.output = bank
try AudioKit.start()
Тут мы создали банк осцилляторов AKOscillatorBank
. Если перейти в объявление класса, то можно обнаружить, что он является наследником AKPolyphonicNode
, который, в свою очередь, является наследником уже известного нам AKNode
, а также реализует протокол AKPolyphonic
.
В итоге, банк осцилляторов — это такая же AudioKit-нода, как и рассмотренные нами ранее. Её выход можно направлять в микшеры, огибающие или любые другие фильтры и эффекты. Протокол AKPolyphonic
описывает как воспроизводить ноты на полифонической ноде, рассмотрим его подробнее.
Для того чтобы проверить наш осциллятор, нам нужен способ сыграть несколько нот одновременно. Это совсем не сложно!
Добавьте следующий код в плейграунд и убедитесь, что Live View открыто:
class LiveView: AKLiveViewController, AKKeyboardDelegate
{
override func viewDidLoad() {
let keyboard = AKKeyboardView(width: 440, height: 100)
addView(keyboard)
}
}
PlaygroundPage.current.liveView = LiveView()
PlaygroundPage.current.needsIndefiniteExecution = true
Когда плейграунд скомпилируется, вы увидите следующее:
Круто, да? Музыкальная клавиатура прямо в плейграунде!
AKKeyboardView
— это ещё одна утилита из AudioKit, которая упрощает исследование возможностей фреймворка. Нажмите на клавиши и вы обнаружите, что они не издают никаких звуков.
Обновите setUp
вашего PlaygroundView
на следующий:
let keyboard = AKKeyboardView(width: 440, height: 100)
keyboard.delegate = self
addView(keyboard)
Это сделает наше PlaygroundView
делегатом клавиатуры и позволит реагировать на нажатия клавиш.
Обновите объявление класса следующим образом:
class LiveView: AKLiveViewController, AKKeyboardDelegate
Также добавьте пару методов сразу после setUp
:
func noteOn(note: MIDINoteNumber) {
bank.play(noteNumber: note, velocity: 80)
}
func noteOff(note: MIDINoteNumber) {
bank.stop(noteNumber: note)
}
Каждый раз, когда вы жмёте на клавишу, вызывается метод noteOn
, всё, что он делает, это говорит банку осцилляторов начать воспроизводить ноту, соответственно, в методе noteOff
происходит остановка проигрывания ноты.
Зажмите мышкой и проведите по клавишам, вы услышите прекрасное крещендо (музыкальный термин, обозначающий постепенное увеличение силы звука). Банк осцилляторов уже содержит встроенный ADSR-эффект, в результате затухание одной ноты смешивается с атакой следующей и звучит довольно приятно.
Вы могли заметить, что ноты, которые выдавали нам клавиши, поступали не в виде частоты. Они объявлены как MIDINoteNumber
. Если вы перейдёте в объявление этого типа, то увидите следующее:
public typealias MIDINoteNumber = Int
MIDI расшифровывается как Musical Instrument Digital Interface (цифровой интерфейс музыкальных инструментов). Это широко распространённый формат взаимодействия музыкальных инструментов между собой. Номера нот соответствуют нотам на стандартной музыкальной клавиатуре. Второй параметр — это велосити (velocity) соответствует силе удара по клавише. Чем ниже значение, тем более мягкое касание по клавише, тем тише итоговый звук.
В завершении нужно включить полифонический режим на клавишах. Добавьте следующий код в метод setUp
:
keyboard.polyphonicMode = true
Теперь вы можете играть несколько нот одновременно, как изображено на картинке:
Это, кстати, До-мажор :)
AudioKit начал свою историю довольно давно. Сегодня он использует Soundpipe и код из Csound (проекта MIT, который был запущен в 1985 году). Удивительно, что код, который сейчас мы запускаем в плейграундах и добавляем на iPhone, был написан почти 30 лет назад.
Сэмплирование
Техники синтезирования звука, которые мы исследовали ранее, пытаются воссоздать реалистичный звук, используя простые базовые блоки: осцилляторы, фильтры и миксеры. В начале 1970-х развитие компьютерных мощностей привело к тому, что появился новый подход — сэмплирование звука, целью которого является создание цифровой копию звука.
Сэмплирование — довольно простая технология, схожая с цифровой фотографией. В процессе сэмплирования с равномерными интервалами записывается амплитуда звуковых волн:
Два параметра влияют на то, насколько точно был записан звук:
- Bit depth: или разрядность, количество отдельных уровней громкости, которые сэмплер может воспроизвести
- Sample rate: или частота дискретизации, говорит о том как часто производятся замеры амплитуды. Измеряется в герцах.
Давайте изучим эти свойства в новом плейграунде. Создайте новую страницу и назовите её «Samples». Замените сгенерированный код следующим:
import AudioKitPlaygrounds
import AudioKit
import PlaygroundSupport
let file = try AKAudioFile(readFileName: "climax-disco-part2.wav", baseDir: .resources)
let player = try AKAudioPlayer(file: file)
player.looping = true
Приведённый код загружает сэмпл, создаёт аудиоплеер и ставит его на бесконечное воспроизведение.
Архив с WAV-файлом для этого туториала доступен по ссылке. Скачайте его и разархивируйте в папку Resources вашего плейграунда.
Затем добавьте следующий код в конец плейграунда:
AudioKit.output = player
try AudioKit.start()
player.play()
PlaygroundPage.current.needsIndefiniteExecution = true
Это соединит ваш аудиоплеер с AudioKit, остаётся лишь прибавить громкость и наслаждаться!
Этот небольшой семпл состоит из большого набора звуков, создать которые с помощью осцилляторов было бы настоящим вызовом.
MP3-звук, который мы обычно слышим, использует высокую разрядность и частоту дискретизации, поэтому звук очень яркий и чистый. Для того чтобы поэкспериментировать с этими параметрами добавьте следующий код сразу после кода создания аудио плеера:
let bitcrusher = AKBitCrusher(player)
bitcrusher.bitDepth = 16
bitcrusher.sampleRate = 40000
Так же поправьте вывод в AudioKit:
AudioKit.output = bitcrusher
Звук нашего плейграунда поменялся. Это тот же семпл, но теперь он звучит совсем плоско.
AKBitCrusher
— это эффект AudioKit, который эмулирует уменьшение разрядности и частоты дискретизации. Звуки, которые получаются похожими на те, что издавали старые компьютеры на подобие ZX Spectrum или BBC Micro. У них было всего несколько килобайт памяти и намного более медленные процессоры, чем сегодняшние.
В нашем последнем эксперименте мы соберём несколько нод вместе для того, чтобы получить эффект стереозадержки(анг. delay). Для начала удалите три строчки, в которых мы создали и настроили AKBitCrusher
. А затем добавьте следующее:
let delay = AKDelay(player)
delay.time = 0.1
delay.dryWetMix = 1
Это создаст эффект задержки в 0.1 секунду с нашим семплом в качестве входа. Параметр dryWetMix
говорит как надо смешать исходный сигнал и сигнал с задержкой. В нашем случае величина 1 говорит, что на выходе мы будем слышать только сигнал с задержкой.
Дальше добавьте следующее:
let leftPan = AKPanner(player, pan: -1)
let rightPan = AKPanner(delay, pan: 1)
AKPanner
даёт возможность переместить звук вправо, влево или куда-то посередине. В нашем примере мы выводим звук с задержкой влево а оригинальный звук вправо.
Последним шагом мы смешаем оба сигнала и выведем в AudioKit. Замените строчку, которая выводила AKBitCrusher
в AudioKit на такой код:
let mix = AKMixer(leftPan, rightPan)
AudioKit.output = mix
Таким образом, мы проиграем наш семпл, но с небольшой задержкой между правым и левым каналами.
Что дальше?
В этом туториале мы затронули совсем небольшой кусочек возможностей AudioKit. Попробуйте исследовать различные фильтры, питч-шифтер, реверб, графический эквалайзер, чтобы посмотреть какие ещё эффекты бывают. Проявив немного креативности, вы можете создавать свои собственные звуки, электронные инструменты или игровые эффекты.
Законченный плейграунд можно найти по ссылке.
Автор: antonpoderechin