Во второй части мы рассмотрели базовую структуру и базовые алгоритмы генерации лупов музыкальных нот и сэмплов.

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

  • Бас
  • Барабаны
  • Линии синтезатора/гитара/фортепиано
  • Вокал

Чтобы воспроизвести музыкальные партии последовательно, мы просто структурируем их одну за другой в наших сценариях Sonic Pi. Однако для параллельного воспроизведения музыкальных партий нам необходимо создать и запустить «поток» для выполнения этой музыкальной части.

н.б. Темп (ударов в минуту) для Sonic Pi по умолчанию составляет 60 ударов в минуту, мы можем изменить это, но пока мы собираемся оставить его прежним. Почему это важно? Потому что все музыкальные партии, которые вы создаете, будут синхронизированы с этим темпом.

Потоки

Следующая программа имеет один поток (он называется основным потоком), который запускает цикл. Вы не можете выйти из цикла, пока не нажмете кнопку «Стоп».

loop do
  sample :bass_hit_c
  sleep 1
end

Однако теперь мы хотим создать еще один цикл, который воспроизводится в такт этому. Итак, добавим следующее…

loop do
  sample :bass_hit_c
  sleep 1
end
loop do
  sample :elec_blip
  sleep 1
end

Мы по-прежнему слышим только бас, а не звон, почему? Это связано с тем, что воспроизводится только первая петля и никогда не достигает второй. Мы можем исправить это двумя способами:

  1. Наслаивайте звуки внутри одного цикла. Это хорошо, но если нужно, чтобы они изменяли их независимо друг от друга — произносили разные утверждения для сна — тогда это не сработает.
  2. Используйте конструкцию, называемую потоками — ура! Это запустит наши циклы независимо друг от друга, чтобы они воспроизводились одновременно.

Для этого мы используем конструкцию 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 мы рассмотрим создание более сложных аранжировок с использованием некоторых реальных входных данных. А пока…