Во второй части мы рассмотрели базовую структуру и базовые алгоритмы генерации лупов музыкальных нот и сэмплов.
Теперь мы хотим объединить несколько лупов, чтобы они воспроизводились одновременно. В конце концов, это и есть песня — набор музыкальных частей, воспроизводимых последовательно, но также и параллельно.
- Бас
- Барабаны
- Линии синтезатора/гитара/фортепиано
- Вокал
Чтобы воспроизвести музыкальные партии последовательно, мы просто структурируем их одну за другой в наших сценариях Sonic Pi. Однако для параллельного воспроизведения музыкальных партий нам необходимо создать и запустить «поток» для выполнения этой музыкальной части.
н.б. Темп (ударов в минуту) для Sonic Pi по умолчанию составляет 60 ударов в минуту, мы можем изменить это, но пока мы собираемся оставить его прежним. Почему это важно? Потому что все музыкальные партии, которые вы создаете, будут синхронизированы с этим темпом.
Потоки
Следующая программа имеет один поток (он называется основным потоком), который запускает цикл. Вы не можете выйти из цикла, пока не нажмете кнопку «Стоп».
loop do sample :
bass_hit_csleep 1 end
Однако теперь мы хотим создать еще один цикл, который воспроизводится в такт этому. Итак, добавим следующее…
loop do sample :
bass_hit_csleep 1 end
loop do sample :
elec_blipsleep 1 end
Мы по-прежнему слышим только бас, а не звон, почему? Это связано с тем, что воспроизводится только первая петля и никогда не достигает второй. Мы можем исправить это двумя способами:
- Наслаивайте звуки внутри одного цикла. Это хорошо, но если нужно, чтобы они изменяли их независимо друг от друга — произносили разные утверждения для сна — тогда это не сработает.
- Используйте конструкцию, называемую потоками — ура! Это запустит наши циклы независимо друг от друга, чтобы они воспроизводились одновременно.
Для этого мы используем конструкцию in_thread следующим образом:
in_thread do loop do sample :bass_hit_c sleep 1 end end loop do sample :elec_blip sleep 1 end
Мы могли бы также обернуть наш последний цикл в оператор in_thread, но, поскольку у нас есть основной поток для его выполнения, в этом нет никакого реального смысла, кроме согласованности. Одна нить может играть одну роль.
Если нам нужно добавить еще одну музыкальную часть, нам также нужно обернуть ее в оператор in_thread.
in_thread do loop do sample :drum_heavy_kick sleep 1 end end in_thread do loop do sample :bass_hit_c sleep 1 end end loop do sample :elec_blip sleep 1 end
Если бы мы использовали Ableton или аналогичный секвенсор, мы бы увидели это графически следующим образом.
Буферы!
Есть несколько способов организовать работу с Sonic Pi. Все может выйти из-под контроля довольно быстро, когда мы добавим больше музыкальных частей и тем. Наш сценарий будет расти, и им станет трудно управлять. Это распространенная проблема в разработке программного обеспечения, а также:)
Вы можете использовать Буферы как отдельные области для кодирования. В дополнение к разделу, который мы написали выше, мы также можем выбрать новый буфер (Buffer 1), вставить следующее и запустить его, пока воспроизводится Buffer 0.
loop do use_synth :blade play 80 sleep 0.5 end
Это не только очищает вещи. Это также дает нам возможность джемовать с дополнительными строками (буфер 1) поверх фоновой дорожки (буфер 0).
Функции
Вы также можете структурировать свой код в своем буфере в функции, чтобы помочь навести порядок. Функция позволяет вам хранить связанные элементы кода вместе. Поначалу странная вещь в отношении функций заключается в том, что вам нужно объявлять, но также и вызывать их.
Основное определение функции выглядит следующим образом:
def this_is_my_function_name() end
Цикл, подобный приведенному выше (лезвие :blade), запускается немедленно. Если мы сейчас объявим его в рамках функции, он больше не будет работать. например
def play_the_blades() loop do use_synth :blade play 80 sleep 0.5 end end
Чтобы заставить эту функцию играть, нам нужно ее вызвать. Это довольно просто, нам просто нужно указать имя функции с помощью набора скобок:
play_the_blades()
Зачем тебе это?
- Он сохраняет ваш код структурированным и организованным.
- Это может уменьшить повторяющийся код, который вам может понадобиться написать
Скажем, мы хотим сыграть ноту, но каждый раз используя другой тип синтезатора.
Для этого мы можем объявить список синтезаторов на выбор в виде массива:
synths = [:blade, :hoover, :growl]
Затем мы могли бы выбирать случайное имя синтезатора каждый раз, когда мы запускаем, используя следующее:
synth_name = synths[rand(3)].to_sym()
Это будет:
- генерирует число от 0 до 2 (индексы массива начинаются с 0) — rand(3)
- найти имя синтезатора в массиве — synths[rand(3)]
- а затем преобразовать это в символ — synths[rand(3)].to_sym()
Наконец, нам нужно вызвать функцию с именем синтезатора:
play_the_blades(synth_name)
а затем реструктурируем нашу программу так, чтобы цикл находился вне функции play_the_blades и был:
- Сгенерировать имя синтезатора
- Воспроизвести функцию, передающую имя синтезатора в качестве параметра
Чтобы определить параметр как принимающий параметр, нам нужно сделать следующее:
def play_the_blades(synth_name)
а затем используйте это имя_синтеза, чтобы выбрать правильный синтезатор следующим образом:
use_synth synth_name
Наша окончательная программа выглядит так:
def play_the_blades(synth_name) use_synth synth_name play 80 sleep 0.5 end synths = [:blade, :hoover, :growl] loop do synth_name = synths[rand(3)].to_sym() play_the_blades(synth_name) end
Хорошо, это звучит немного странно, но это демонстрирует некоторые мощные конструкции в структурировании вещей, а именно:
- Функции
- Параметры функции
- Рандомизация
- Выбор чего-либо из массива
- Создание символа из строки
Вывод
Мы очень много рассмотрели в этой третьей части.
- Потоки — которые позволяют нам воспроизводить несколько частей.
- Буферы, которые позволяют нам экспериментировать и джемовать с несколькими музыкальными линиями.
- Функции — которые позволяют нам очищать наши части и уменьшать дублирование кода.
Это охватывает основы Sonic Pi, в части 4 мы рассмотрим создание более сложных аранжировок с использованием некоторых реальных входных данных. А пока…