Добро пожаловать в мир Sonic Pi. Надеюсь, что вам настолько же не терпится начать создавать безумные звуки, насколько мне не терпится показать вам, как это делается. Это будет веселое путешествие, в котором ты узнаешь много нового о музыке, синтезе, программировании и производительности программ, композиции и о много другом.
Постойте, как же я мог быть таким невоспитанным! Позвольте мне представиться - я Sam Aaron - тот самый парень, который создал Sonic Pi. Меня можно найти по имени @samaaron в Твиттере, и я был бы очень счастлив обменяться там с вами приветствиями. Вам, возможно, также было бы интересно узнать больше о моих лайв-кодинг выступлениях, где я сам пишу на Sonic Pi перед зрителями.
Если у вас появятся какие-либо мысли или идеи о том, как улучшить Sonic Pi, то, пожалуйста, передайте мне их. Обратная связь - это очень полезно. Никогда не знаешь, вдруг ваша идея может вырасти в нечто действительно большое!
Этот учебник разделён на главы, сгруппированные по категориям. Несмотря на то, что я написал его от простого к сложному, можно свободно прыгать туда-сюда как вам удобно. Если вам кажется, что здесь чего-то не хватает, обязательно дайте мне об этом знать, и я посмотрю, что можно сделать для следующей версии.
Наконец, наблюдать за чужим кодингом в режиме реального времени это отличный способ учиться. Я регулярно вещаю в прямом эфире на http://youtube.com/samaaron, так что не забывайте появляться там, чтобы поприветствовать всех и задать все свои многочисленные вопросы :-)
Что ж, давайте начнём…
Одним из самых захватывающих аспектов Sonic Pi является то, что для создания музыки, вы можете писать и изменять код здесь и сейчас. Это похоже на настоящее выступление с гитарой. Значит, при достаточной тренировке, вы сможете взять Sonic Pi с собой на сцену и выступать с ней.
Прежде, чем мы перейдем к деталям того, как Sonic Pi работает, я бы хотел поделиться с вами личным опытом того, что значит “лайв-кодинг”. Не переживайте, если вы пока чего-то из этого (или ничего) не понимаете. Просто держитесь крепче за стул и получайте удовольствие…
Для начала скопируйте следующий код в пустой буфер:
live_loop :flibble do
sample :bd_haus, rate: 1
sleep 0.5
end
Теперь нажмите кнопку Выполнить
, и вы услышите приятный быстрый барабанный бой. В любой момент нажмите кнопку Остановить
, чтобы остановить звук. Но не нажимайте её пока что… Вместо этого сделайте вот что:
Убедитесь, что барабаны по-прежнему звучат
Измените значение sleep
с 0.5
на значение побольше, например 1
.
Еще раз нажмите кнопку Выполнить
Можно заметить, что темп барабанов изменился.
И, наконец, запомните этот момент - это первый, и, наверняка, не последний ваш лайв-кодинг на Sonic Pi…
Ладно, это было довольно просто. Давайте добавим что-нибудь ещё в наш микс. Сверху от sample :bd_haus
добавьте строку sample :ambi_choir, rate: 0.3
. Ваш код должен выглядеть так:
live_loop :flibble do
sample :ambi_choir, rate: 0.3
sample :bd_haus, rate: 1
sleep 1
end
Время поиграться. Поменяйте частоту. Что происходит, когда вы используете высокие, малые, или отрицательные значения? Посмотрите, что произойдет, когда вы поменяете значение rate:
для сэмпла :ambi_choir
на незначительную величину (например до 0.29
). Что произойдет, если вы выберите очень маленькое значение для sleep
? Проверьте, получится ли заставить его играть так быстро, что ваш компьютер прекратит свою работу и выдаст ошибку из-за того, что он не справится с этим (если это всё же произошло, то просто выберите значение побольше для sleep
и снова нажмите Выполнить
).
Попробуйте закомментировать одну из строк sample
. Для этого добавьте #
в ее начало:
live_loop :flibble do
sample :ambi_choir, rate: 0.3
# sample :bd_haus, rate: 1
sleep 1
end
Обратите внимание, компьютер проигнорировал её, поэтому мы её не слышим. Это называется комментарием. В Sonic Pi мы можем использовать комментарии, чтобы убирать или добавлять штуки в микс.
Напоследок, позвольте мне оставить вам кое-что забавное, с чем можно поиграть. Возьмите код, приведенный ниже, и скопируйте его в свободный буфер. Не пытайтесь пока полностью разобраться в нем. Просто обратите внимание, что в нём два цикла - то есть две вещи играют по кругу одновременно. Теперь ваш черёд делать то, что получается у вас лучше всего - экспериментировать и развлекаться. Вот пара советов:
Попробуйте изменить синее значение rate:
, чтобы услышать, как изменится звук.
Попробуйте изменить время для sleep
и вы услышите, как оба цикла повторяются по кругу с разными темпами.
Попробуйте раскомментировать строку примера (удалите знак #
) и наслаждайтесь звуками гитары на заднем плане.
Попробуйте поменять синее значение любого из mix:
на число в промежутке от 0
(что соответствует исключению из общего микса) до 1
(полностью сочетается).
Не забывайте нажимать Выполнить
, и вы услышите изменения в следующий раз, когда петля начнётся заново. Не расстраивайтесь, если в конце музыка будет испорчена. Нажмите Остановить
, удалите код из буфера, вставьте свежую копию, и вы снова готовы к творчеству. На ошибках научиться можно быстрее всего…
Не забывайте нажимать Выполнить
, вы будете слышать изменения на каждой следующей итерации цикла. Не бойтесь ломать музыкальную композицию. Всегда можно нажать Остановить
, удалить код из буфера и вставить свежую копию. Не забывайте - процесс творчества тесно связан с совершением ошибок…
live_loop :guit do
with_fx :echo, mix: 0.3, phase: 0.25 do
sample :guit_em9, rate: 0.5
end
# sample :guit_em9, rate: -0.5
sleep 8
end
live_loop :boom do
with_fx :reverb, room: 1 do
sample :bd_boom, amp: 10, rate: 1
end
sleep 8
end
Продолжайте играть и экспериментировать до тех пор, пока вам не станет любопытно, как это на самом деле работает, и вы не начнёте думать, что ещё можно сделать. Тогда вы будете готовы к чтению оставшейся части этого учебника.
Так чего же вы ждёте…
У Sonic Pi очень простой интерфейс для кодирования музыки. Мы потратим совсем немного времени на его изучение.
A - Управление воспроизведением B - Управление редактором C - Помощь и справка D - Редактор кода E - Панель настроек F - Просмотр сообщений G - Справочная система F - Просмотр сообщений
Эти розовые кнопки - главные элементы для запуска и остановки звука. Есть кнопка Выполнить для запуска кода из редактора, Остановить для остановки всего выполняющегося кода, Сохранить для сохранения кода в файл и Запись для создания записи того, что вы слышите в звуковой файл в формате WAV.
Оранжевые кнопки дают вам возможность манипулировать редактором кода. Кнопки Размер + и Размер - делают текст больше и меньше. Кнопка Выровнять автоматически выравнивает код, от чего он выглядит более профессионально.
Данные синие кнопки дают вам доступ к информации и настройкам. Кнопка Информация открывает справочное окно, в котором находится информация о самом Sonic Pi - главной команде разработчиков, истории, помощниках и сообществе. Кнопка Помощь включает систему помощи (F), а кнопка Параметры включает панель настроек, где можно менять некоторые базовые параметры приложения.
Это область, в которой вы будете писать свой код и сочинять/исполнять музыку. Это простой текстовый редактор, где можно написать код, удалить его, вырезать, вставить и так далее. Представьте, что это простая версия Word или Google Docs. Редактор автоматически раскрасит слова в зависимости от их значения в коде. Поначалу это может казаться странным, но скоро вы увидите, насколько это помогает. К примеру, глядя на что-то синее ты будешь знать, что это число.
В Sonic Pi есть множество настраиваемых параметров. Доступ к ним можно получить, нажав кнопку Параметры в наборе кнопок “Помощь и справка”. Она переключит видимость панели настроек, в которой представлено множество вариантов, доступных для изменения. Примерами являются режим моно, инвертированное стерео, включение вывода детальных сообщений, ползунок громкости и выбор аудиовыхода Raspberry Pi.
Когда твой код выполняется, информация о том, что делает программа, будет выводиться в лог сообщений. По умолчанию, ты будешь видеть новое сообщение для каждого воспроизводимого звука с точным указанием времени, когда он начался. Это очень полезно для отладки кода и понимания того, что он делает.
Наконец, одна из самых важных частей интерфейса Sonic Pi - это справочная система, которую можно увидеть в нижней части окна приложения. Её можно выводить и прятать, нажимая синюю кнопку Помощь. В справочную систему входит помощь и информация обо все аспектах Sonic Pi, включая этот учебник, список доступных синтезаторов, сэмплов, эффектов, примеры и полный перечень всех функций Sonic Pi для кодирования музыки.
В области видимости можно наблюдать за воспроизводимым звуком. Вы с лёгкостью заметите, что пилообразная волна действительно напоминает пилу, а простейшие сигналы выглядят как синусоидальные волны. Вы также можете увидеть разницу между громкими и тихими звуками, глядя на размер выводимых линий. Существует три области видимости, с которыми можно повозиться - стандартная (комбинация левого и правого каналов), стерео (каждый канал обособлен) и, наконец, область видимости фигур Лиссажу, которая показывает отношения фаз левого и правого каналов, а также позволяет создавать красивые изображения при помощи звука. (https://ru.wikipedia.org/wiki/%D0%A4%D0%B8%D0%B3%D1%83%D1%80%D1%8B_%D0%9B%D0%B8%D1%81%D1%81%D0%B0%D0%B6%D1%83).
Sonic Pi стремится воодушевить вас изучать компьютеры и музыку через игру и экспериментирование. Самое главное, чтобы вам было весело, и тогда, прежде, чем вы это поймёте, вы случайно научитесь кодировать, сочинять и исполнять.
Коль скоро мы об этом заговорили, позвольте дать вам один совет, который я усвоил за годы занятий лайвкодингом - ошибок нет, есть только возможности. Часто мне приходилось слышать это про джаз, но для живого кодинга это так же справедливо. Неважно, насколько велик ваш опыт - от полного новичка до бывалого алгорейвера - вы будете запускать код с совершенно неожиданным результатом. Он может звучать безумно круто, тогда вы можете гордиться им. Однако, он также может получиться совершенно невыносимым для слуха и быть не к месту. Не важно, что так произошло. Важно, что вы сделаете с этим потом. Возьмите звук, умело обработайте его, и превратите в нечто ошеломляющее. Публика будет неистовствовать.
Когда вы только учитесь, очень хочется делать удивительные вещи уже сейчас. Но попридержите эту мысль и смотрите на нее, как на далекую цель, которую вы достигнете позже. Пока что, вместо этого думайте о самом простом, что вы можете написать, что было бы забавно и приносило бы некое удовлетворение. Каждый раз это будет небольшим шагом к тому поразительному, что есть в вашем воображении. Как только у вас появилась идея о простом шаге, попробуйте сделать его, играйте с тем, что получилось и разбирайтесь, какие новые идеи он дает вам. Вскоре у вас появится огромное количество способов повеселиться, а обучение пойдет в гору.
Но не забывайте делиться своими достижениями с другими!
Довольно вступлений. Пора экспериментировать со звуком.
В этой главе мы будем рассматривать основы запуска и управления синтами. Синт - это сокращение от синтезатор, что само по себе означает модное словечко для чего-то, что издает звук. Обычно, синты довольно сложны в использовании (особенно аналоговые, в которых много соединений проводами и модулей). Sonic Pi предоставляет такие же возможности, но очень простым и доступным способом.
Не обманывайтесь кажущейся простотой интерфейса Sonic Pi. С ним, если захочется, можно добраться до самых потаенных глубин очень сложной манипуляции звуком. Итак, придержите свои шляпы…
Посмотрите на этот код:
play 70
Отсюда всё берет свое начало. Давайте, скопируйте и вставьте его в окно кода вверху приложения (большое белое пространство под кнопкой Выполнить
). Затем, нажмите Выполнить
…
Впечатляет. Нажмите заново. Снова. И снова…
Ого, с ума сойти, уверен, вы бы могли просидеть так целый день. Но подождите, до того, как вы растворитесь в бесконечном потоке звуков, попробуйте изменить число:
play 75
Слышите разницу? Попробуйте число поменьше:
play 60
Вот, меньшее число дает более низкий звук, а большее число дает более высокий звук. Прямо как на пианино. Клавиши в нижней части пианино (слева) звучат ниже, а клавиши в верхней части пианино (правая сторона) - выше. На самом деле, числа имеют прямое отношение к клавишам пианино. play 47
означает “сыграй сорок седьмую ноту на пианино”. То есть play 48
- это на одну ноту выше (следующая клавиша справа). Так сложилось, что нота “До” четвертой октавы имеет номер 60. Попробуйте сыграть её: play 60
.
Не волнуйтесь, если для вас это ничего не значит - у меня было то же самое, когда я только начинал. Сейчас важно то, что меньшие числа дают более низкие звуки, а большие числа дают более высокие звуки.
Играть одну ноту весьма забавно, но играть несколько нот сразу ещё лучше. Попробуйте:
play 72
play 75
play 79
Звучит джазово! Итак, когда вы пишете play
много раз, все ноты играют в одно и то же время. Попробуйте самостоятельно - какие числа звучат вместе хорошо? Какие звучат ужасно? Экспериментируйте, исследуйте и вы выясните это сами.
Играть ноты и аккорды весело, а как насчет мелодий? Что, если вы хотите играть одну ноту за другой, а не все вместе? Что же, это легко. Просто нужно вставить sleep
между ними:
play 72
sleep 1
play 75
sleep 1
play 79
Как мило, маленькое арпеджио. Что же значит 1
в sleep 1
? Ну, это означает продолжительность паузы. Вообще-то, это означает задержку на время одной доли такта, но, пока что будем считать, что это значит спать одну секунду. А вдруг мы захотим, чтобы наше арпеджио стало немного быстрее? Тогда надо указать значение для паузы покороче. Как насчет половины, то есть 0.5
:
play 72
sleep 0.5
play 75
sleep 0.5
play 79
Можно заметить: мелодия стала играть быстрее. Попробуйте сами поменять время и используйте разные продолжительности и ноты.
Интересно попробовать значения между нотами, например play 52.3
или play 52.63
. Совсем не нужно держаться стандартных целых чисел. Развлекайтесь и получайте удовольствие.
Для тех, кто уже немного знаком с нотной грамотой (не волнуйтесь, если вы не знаете о чём речь - для веселья она вам не нужна), может быть интересно записать мелодию, используя названия нот, такие как “До” (C) и “Фа-диез” (F#), а не числа. Sonic Pi предусмотрел это. Можно делать так:
play :C
sleep 0.5
play :D
sleep 0.5
play :E
Просто помните, что перед нотой должно стоять двоеточие :
. Тогда она станет розовой. Ещё можно указывать октаву, добавляя её номер после названия ноты:
play :C3
sleep 0.5
play :D3
sleep 0.5
play :E4
Если хотите сделать ноту на полтона выше, поставьте s
после её имени, то есть play :Fs3
. А когда нужен бемоль, обозначьте это при помощи b
, то есть play :Eb3
.
А теперь, дайте себе волю, и как следует повеселитесь, придумывая собственные мелодии.
В дополнение к возможности выбрать какую ноту играть или какой сэмпл включать, Sonic Pi предоставляет целый арсенал настроек для создания и управления звуком. Многие из этих настроек описаны в этом учебнике, и есть обширная документация по каждой из них в справочной системе. Пока что, мы познакомимся всего лишь с двумя самыми полезными: амплитуда и панорамирование. Вначале посмотрим, что же такое эти настройки на самом деле.
Sonic Pi поддерживает регулировки для своих синтезаторов. Они представляют собой элементы управления, для управления которыми вы передаёте атрибуты функции play
. Эти атрибуты изменяют и контролируют выводимый звук. У каждого синтезатора свой собственный набор регулировок для тонкой настройки звука. Но, есть общие наборы регулировок, одинаковые для разных синтезаторов, такие как amp:
, pan:
и параметры огибающей (обсуждается в другой главе).
Каждая регулировка состоит из двух частей: название регулировки (элемента управления) и её значение (деление, на которое вы хотите установить ручку регулировки). Например, может быть настройка с названием cheese:
, а ее желаемым значением будет 1
.
Настройки передаются вызовам play
после запятой ,
далее следует название, например amp:
(не забудьте двоеточие :
), а потом ее значение. Например:
play 50, cheese: 1
(Замечание: cheese:
- не настоящая настройка, мы просто используем ее для примера).
Можно передавать много настроек, разделяя их запятыми:
play 50, cheese: 1, beans: 0.5
Порядок регулировок не важен, так что следующий пример ничем не отличается:
play 50, beans: 0.5, cheese: 1
Регуляторы, о которых синтезатор не знает, попросту пропускаются (такие как cheese
и beans
- очевидно нелепые названия для настроек!)
Если вы случайно укажите одну и ту же настройку с разными значениями, то победит последнее. Например, значением beans:
будет 2
, а не 0.5
:
play 50, beans: 0.5, cheese: 3, eggs: 0.1, beans: 2
Множество вещей в Sonic Pi гибко регулируемо, потратьте немного времени на изучение того, как использовать их, и будете “настроены”! Давайте позабавимся с нашим первым регулятором - amp:
.
Амплитуда - компьютерное представление громкости звука. Высокая амплитуда даёт громкий звук, а низкая амплитуда даёт тихий звук. Так же, как Sonic Pi использует числа для обозначения нот, числами обозначается и амплитуда. При значении амплитуды 0 получается тишина (ничего не будет слышно), а амплитуда 1 - это нормальная громкость. Можно, также, увеличить амплитуду до 2, 10 или 100. Но стоит отметить, что когда общая амплитуда становится слишком высокой, то Sonic Pi включает так называемый компрессор, чтобы сжать ее и не дать звуку сделаться слишком громким для ваших ушей. Часто это может делать звук непонятным и странным. Так что постарайтесь использовать значения амплитуды в промежутке от 0 до 0.5, чтобы избегать компрессии.
Чтобы изменить амплитуду звука, можно воспользоваться настройкой amp:
. Например, передай 0.5, чтобы играть на половинной громкости:
play 60, amp: 0.5
Чтобы играть на удвоенной громкости, передай 2:
play 60, amp: 2
Регулировка amp:
изменяет тот вызов play
, с которым она идёт вместе. Поэтому в следующем примере первый вызов произойдет на половинной громкости, а второй вернется к значению по умолчанию (1):
play 60, amp: 0.5
sleep 0.5
play 65
Конечно, можно задавать разные значения amp:
для каждого вызова play
:
play 50, amp: 0.1
sleep 0.25
play 55, amp: 0.2
sleep 0.25
play 57, amp: 0.4
sleep 0.25
play 62, amp: 1
Еще одной забавной настройкой, которую стоит использовать, является pan:
. Она контролирует смещение звука в стереобазе. Смещение звука влево означает, что вы услышите его из левого динамика, а смещение вправо - что он будет выходить из правого динамика. Мы используем -1 для полного смещения влево, 0 для центра, а 1 - для полного смещения вправо в стереобазе. Безусловно, мы можем указывать любое значение в промежутке между -1 и 1 для точного позиционирования нашего звука.
Давай получим звук из левого динамика:
play 60, pan: -1
Теперь, повторим его из правого:
play 60, pan: 1
Наконец, выведем его снова по центру (позиция по умолчанию):
play 60, pan: 0
Попробуйте сами сделать что-нибудь весёлое, меняя амплитуду и панорамирование ваших звуков!
До сего момента мы, по большей части, развлекались с простыми звуками. Однако, простые тона уже начали вас утомлять. Разве это все, что Sonic Pi может предложить? Определенно, у лайвкодинга есть больше возможностей, чем просто гудение. Да, есть. И в этой главе мы познакомимся с захватывающим набором звуков, доступных в Sonic Pi.
В Sonic Pi есть ряд инструментов, называемых синтами, что есть сокращение от термина “синтезатор”. В то время, как сэмплы представляют собой предварительно записанные звуки, синты могут генерировать новые звуки в зависимости от того, как вы ими управляете (это мы изучим далее в этом учебнике). Синты Sonic Pi - очень могущественные и яркие инструменты. Играть с ними очень весело. Вначале, давайте научимся выбирать какой синтезатор мы будем использовать.
Пилообразная волна - забавный звук. Давайте попробуем его:
use_synth :saw
play 38
sleep 0.25
play 50
sleep 0.25
play 62
sleep 0.25
Попробуйте другой - prophet:
use_synth :prophet
play 38
sleep 0.25
play 50
sleep 0.25
play 62
sleep 0.25
Как насчет смешивания обоих. Сначала один, а потом другой:
use_synth :saw
play 38
sleep 0.25
play 50
sleep 0.25
use_synth :prophet
play 57
sleep 0.25
А теперь вместе:
use_synth :tb303
play 38
sleep 0.25
use_synth :dsaw
play 50
sleep 0.25
use_synth :prophet
play 57
sleep 0.25
Как вы могли заметить, команда use_synth
влияет только на следующие вызовы play
. Представьте, что это большой выключатель - новые вызовы play
будут играть только на используемом синте. На новый синт можно переключиться с помощью use_synth
.
Чтобы увидеть, какие синты есть в Sonic Pi, загляните во вкладку Synths в меню слева внизу (рядом с Fx). Там их больше 20 на выбор. Вот несколько моих любимых:
:prophet
:dsaw
:fm
:tb303
:pulse
Попробуйте попереключаться между синтами, пока ваша музыка играет. Позабавьтесь со смешиванием синтов, чтобы получать новые звуки. Еще можно использовать разные синтезаторы для разных частей музыки.
В одной из предыдущих глав мы изучали, как можно использовать команду sleep
для того, чтобы контролировать, когда начинается звук. Однако, до этого момента мы не могли настраивать продолжительность наших звуков.
В качестве простого и мощного способа управления длительностью наших звуков Sonic Pi предоставляет нам понятие ADSR амплитудной огибающей (Дальше в этой главе - ADSR). Амплитудная огибающая предлагает контроль по двум параметрам:
управление длительностью звука управление амплитудой звука
Длительность - это отрезок времени, в течении которого продолжается звук. Большая продолжительность означает, что вы слышите звук дольше. Все звуки в Sonic Pi имеют контролируемую продолжительность. Поэтому, управляя огибающей, вы управляете продолжительностью.
ADSR-огибающая задает не только длительность, но еще и позволяет тонкую настройку амплитуды сигнала. Все слышимые звуки начинаются и заканчиваются тишиной, а в промежутке находится изменяющаяся часть звука. Огибающие дают вам возможность плавно изменять и удерживать амплитуду разных частей звука. Это похоже на написание инструкции о том, в каком порядке понижать и повышать громкость гитарного усилителя. Например, вы можете попросить “начать с тишины, потом медленно повышать до полной громкости, держать её на этой отметке короткое время, а потом быстро вернуть на тихий уровень”. Sonic Pi дает возможность запрограммировать такое поведение очень точно с помощью огибающих.
Итак, как мы видели раньше, амплитуда 0 - это тишина, а амплитуда 1 - нормальная громкость.
Теперь, пришла пора взглянуть на каждую часть огибающей по порядку.
Единственная часть огибающей, установленная по умолчанию, - это время затухания. Оно обозначает продолжительность времени, за которое звук полностью исчезнет. У всех синтов затухание настроено на 1, что значит по умолчанию звук длится в течение одной доли такта (что при темпе по умолчанию в 60 составляет 1 секунду):
play 70
Ноту будет слышно одну секунду. Можете измерить сами :-) В полной, и более явной записи, то же самое будет выглядеть так:
play 70, release: 1
Заметьте, что звук получился точно таким же (длился ровно 1 секунду). Очень легко изменять длительность, изменяя значение регулировки release:
:
play 60, release: 2
Можно cделать звучание синта крайне непродолжительным, указав очень маленькое значение затухания:
play 60, release: 0.2
Длительность затухания звука называется фазой затухания (release phase) и, по умолчанию, представляет из себя линейный переход (т.e. прямую линию). Следующая диаграмма иллюстрирует такой переход:
Вертикальная линия на левом краю диаграммы показывает, что звук начинается при амплитуде 0, но сразу после этого начинает выводиться на полной громкости (это фаза атаки, которую мы рассмотрим далее). Как только достигнута максимальная амплитуда, она спадает прямой линией до нуля, занимая время, указанное для release:
. Чем продолжительнее время затухания, тем дольше будет стихать синт
Таким образом, вы можете менять длительность звучания своих звуков, изменяя время затухания. Потратьте немного времени, пробуя добавлять время затухания к собственной музыке.
По умолчанию фаза атаки равна 0 для всех синтов, что значит их амплитуда меняется от 0 до 1 мгновенно. Поэтому начальный звук синта похож на удар. Но вам может понадобиться плавно вывести звук. Этого можно добиться при помощи регулировки attack:
. Попробуйте плавно извлекать какие-нибудь звуки:
play 60, attack: 2
sleep 3
play 65, attack: 0.5
Можно пользоваться несколькими регуляторами в одно и то же время. Например, для короткой атаки и долгого затухания попробуйте:
play 60, attack: 0.7, release: 4
Эта краткая атака и длинное затухание иллюстрируются следующей диаграммой:
Конечно, можно и наоборот. Попробуйте долгую атаку и короткое затухание:
play 60, attack: 4, release: 0.7
В конце-концов, для кратких звуков можно сделать короткими и атаку, и затухание.
play 60, attack: 0.5, release: 0.5
Вдобавок, к заданию времени атаки и затухания, можно указать время задержки, чтобы управлять фазой задержки. Это отрезок времени, в течение которого звук остается на полной громкости в промежутке между фазами атаки и затухания.
play 60, attack: 0.3, sustain: 1, release: 1
Задержка полезна для важных звуков, которые вы хотели бы полностью выделить при сведении, прежде чем они перейдут в необязательную фазу затухания. Конечно же, совершенно допустимо устанавливать и attack:
и release:
в 0 и попросту использовать задержку, чтобы не иметь абсолютно никакого нарастания и затухания у звука. Но стоит заметить, что затухание 0 может давать щелчки в аудио, и часто лучше все-таки указывать очень маленькое значение, такое как 0.2.
Для дополнительного уровня контроля можно указать время спада. Это фаза огибающей, которая помещается между фазами атаки и задержки. Она задает участок времени, когда амплитуда будет падать от уровня attack_level:
до decay_level:
(если явно не установить его, то он будет совпадать с sustain_level:
). По умолчанию, уровни decay:
и attack:
установлена в 0, а уровень затухания в 1, поэтому надо указывать время спада, чтобы оно имело какой-либо эффект:
play 60, attack: 0.1, attack_level: 1, decay: 0.2, sustain_level: 0.4, sustain: 1, release: 0.5
Еще один, последний трюк, состоит в том, что хотя настройки decay_level:
изначально совпадают со значением sustain_level:
, вы можете явно присвоить им разные значения для полного контроля над огибающей. Это позволяет создавать огибающие наподобие следующей:
play 60, attack: 0.1, attack_level: 1, decay: 0.2, decay_level: 0.3, sustain: 1, sustain_level: 0.4, release: 0.5
Можно, к тому же, устанавливать decay_level:
выше, чем sustain_level:
:
play 60, attack: 0.1, attack_level: 0.1, decay: 0.2, decay_level: 1, sustain: 0.5, sustain_level: 0.8, release: 1.5
Итак, подведем итог. ADSR-огибающие в Sonic Pi состоят из следующих фаз:
Attack - атака - время, за которое амплитуда растет от 0 до attack_level
,
Decay - спад - время, когда амплитуда переходит от attack_level
до decay_level
,
Sustain - задержка - время изменения амплитуды с decay_level
на sustain_level
,
Release - затухание - время падения амплитуда с sustain_level
до 0
Важно помнить, что длительность звука - это сумма продолжительностей всех фаз. Соответственно, такой звук будет длиться 0.5 + 1 + 2 + 0.5 = 4 доли такта:
play 60, attack: 0.5, attack_level: 1, decay: 1, sustain_level: 0.4, sustain: 2, release: 0.5
Теперь пришло время поиграть с огибающими, добавляя их в свои звуки…
Другой отличный способ сочинять музыку - использовать заранее записанные звуки. В лучших традициях хип-хопа, мы называем такие записанные звуки сэмплами. То есть, если вы возьмёте микрофон, выйдете наружу и запишете мягкие звуки ударов дождя о навес, вы создадите сэмпл.
В Sonic Pi можно делать много забавных вещей с сэмплами. Помимо 90 встроенных сэмплов со свободной лицензией, программа даёт вам работать и играть с собственными. Перейдем же к делу..
Простые звуки - это только начало. Кое-что весьма весёлое - это проигрывание сэмплов. Попробуйте:
sample :ambi_lunar_land
Sonic Pi включает множество сэмплов. Их можно использовать так же, как вы используете команду play
. Чтобы проиграть несколько сэмплов и нот, просто запишите их друг под другом:
play 36
play 48
sample :ambi_lunar_land
sample :ambi_drone
Если их нужно разделить во времени, используйте команду sleep
:
sample :ambi_lunar_land
sleep 1
play 48
sleep 0.5
play 36
sample :ambi_drone
sleep 1
play 36
Обратите внимание, что Sonic Pi не ждёт, пока закончится один звук, прежде чем начать играть следующий. Команда sleep
всего лишь описывает разделение моментов, когда звуки начинаются. За счет этого их можно легко совмещать, создавая интересные эффекты наложения. Далее в этом учебнике мы рассмотрим управление длительностью звуков при помощи огибающих.
Есть два способа познакомиться с набором сэмплов, включённых в Sonic Pi. Во-первых, вы можете воспользоваться системой помощи. Щелкните Samples в левом нижнем меню, выберите желаемую категорию, и после этого вы увидите список доступных звуков.
С другой стороны, можно задействовать систему автодополнения. Просто начните набирать название группы сэмплов, например sample :ambi_
, и появится выпадающее меню с названиями сэмплов на выбор. Попробуйте такие варианты приставки:
:ambi_
:bass_
:elec_
:perc_
:guit_
:drum_
:misc_
:bd_
Попробуйте создать собственные композиции из сэмплов!
Также, как и с синтами, мы можем легко контролировать наши сэмплы при помощи параметров. Механизм передачи параметров сэмплам точно такой же. Вспомним наших друзей amp:
и pan:
.
Можно менять амплитуду сэмплов точно так же, как и синтов:
sample :ambi_lunar_land, amp: 0.5
Ещё один параметр сэмплов, доступный для использования - pan:
. Например, вот так мы бы сыграли амен-брейк в левом канале, а потом на середине проиграли бы его снова, но в правом канале:
sample :loop_amen, pan: -1
sleep 0.877
sample :loop_amen, pan: 1
Обратите внимание, что 0.877 - это половина продолжительности сэмпла в секундах.
Наконец, если вы сбросите регулировки какого-нибудь синта на настройки по умолчанию, (как это сделать при помощи use_synth_defaults
, рассмотрим далее), то сэмпл не обратит на это внимания.
Раз уж теперь мы умеем играть на разнообразных синтах и воспроизводить всевозможные сэмплы, пришло время научиться управлять ими обоими, чтобы музыка получалась ещё более уникальной и интересной. Вначале, давайте рассмотрим возможность растягивать и сжимать сэмплы.
Сэмплы - это предварительно записанные звуки в виде чисел, описывающих, как колеблется мембрана динамика, чтобы воспроизвести звук. Мембрана движется внутрь и наружу, так что числами всего лишь нужно задать насколько втянутой или выпуклой она должна быть в каждый момент времени. Для правдоподобного воспроизведения звука, обычно сэмпл хранит много тысяч чисел для каждой секунды! Sonic Pi берет список этих чисел и передаёт их с нужной скоростью, чтобы двигать мембраной динамика наружу и внутрь так, чтобы получился нужный звук. Но можно неплохо поразвлечься, меняя звук за счет изменения скорости, с которой числа идут в динамик.
Попробуем проверить это на одном из природных звуков: :ambi_choir
. Чтобы воспроизвести его с обычной скоростью, можно передавать команде sample
настройку rate:
:
sample :ambi_choir, rate: 1
Этот пример воспроизводит сэмпл с нормальной скоростью (1). Пока что ничего особенного. Однако, мы можем свободно изменить это число на что-то другое. Как насчет 0.5
:
sample :ambi_choir, rate: 0.5
Ого! Это что такое происходит? Ну, две вещи. Во-первых - сэмпл воспроизводится в два раза дольше. Во-вторых - звук стал ниже на октаву. Давайте разберём это более детально.
Сэмпл, который забавно растягивать и сжимать, - это амен-брейк. С нормальной скоростью мы можем легко себе представить его в составе трека драм-н-бейс:
sample :loop_amen
Но, задавая скорость, можно переключать и жанры. Попробуй половинное значение, чтобы получить старый-добрый хип-хоп:
sample :loop_amen, rate: 0.5
А если ускориться, то мы попадаем на территорию джангл:
sample :loop_amen, rate: 1.5
А теперь гвоздь программы - проверим, что произойдет, если мы зададим отрицательную скорость:
sample :loop_amen, rate: -1
Ничего себе! Он стал играть задом-наперёд! Попробуйте воспроизвести разные сэмплы на разных скоростях. Попробуйте очень быстрые скорости. Попробуйте несуразно медленные. Узнайте, какие интересные звуки можно создать.
Полезная мысленная аналогия сэмплам - пружина. Темп воспроизведения - это как сжатие и растягивание пружины. Если воспроизводить сэмпл со скоростью 2, то вы будете сжимать пружину до половины ее нормальной длины. Следовательно, воспроизведение сэмпла займёт половину времени, ведь он стал короче. Если играть сэмпл на половине обычной скорости, то вы растягиваете пружину в два раза. Поэтому сэмпл длится в два раза дольше, ведь он стал длиннее. Чем больше вы сдавливаете, тем короче он получается (скорость выше), а чем больше вы растягиваете, тем длиннее он становится (скорость ниже).
Сжатие пружины увеличивает ее плотность (число витков на сантиметр) - это совпадает с тем, почему сэмпл начинает звучать с высокой частотой. Растягивание пружины снижает ее плотность, и это похоже на то, как частота звука понижается.
(Эта часть приведена для тех, кому интересны детали. Всем остальным её можно пропустить…)
Как мы видели раньше, представление сэмпла - это очень длинный список чисел, которые описывают движение мембраны динамика во времени. Можно взять эту последовательность чисел и нарисовать по ней график, который будет выглядеть примерно так:
Вы могли видеть похожие картинки до этого. Они называются волновой формой сэмпла. Это просто числовой график. Обычно подобные формы включают 44100 точек с данными на каждую секунду времени (из-за теоремы сэмплирования Найквиста-Шэннона). Итак, если сэмпл длится две секунды, то форма волны будет состоять из 88200 чисел, которые мы бы передавали динамической головке со скоростью 44100 значения в секунду. Конечно, мы могли бы удвоить темп передачи, тогда воспроизведение бы заняло всего одну секунду. А еще мы могли бы проигрывать его в два раза медленнее, что означало бы 22500 точек в секунду, и весь процесс бы занял четыре секунды.
На длительность сэмпла влияет скорость воспроизведения:
Удвоение темпа уполовинивает время воспроизведения, Сокращение темпа вдвое удваивает время воспроизведения, Если задать темп, равный четверти от нормального, то время учетверится, Если скорость воспроизведения равна 1/10 от нормальной, то время воспроизведения увеличится в 10 раз.
Мы можем представить это отношение формулой:
новая_длительность_сэмпла = (1 / скорость) * длительность_сэмпла
Изменение темпа воспроизведения, к тому же, влияет на высоту сэмпла. Частота формы сигнала и высота звука определяются тем, как часто сигнал движется вверх и вниз. Наш мозг каким-то образом превращает быстрые перемещения мембраны в высокие ноты, а медленные - в низкие. Именно поэтому иногда вы можете видеть вибрации большого басового динамика, когда он издает чрезвыйчайно низкие звуки. На самом деле он перемещается намного медленнее внутрь и наружу, чем динамик, воспроизводящий высокие ноты.
Если взять форму сигнала и сжать ее, то она будет колебаться вверх и вниз большее количество раз за секунду. За счет этого звук получится более высоким. Выходит, что удвоение движений вверх-вниз (колебаний) удваивает частоту. Поэтому проигрывание сэмпла на удвоенной скорости удваивает его слышимую частоту. Точно так же половинная скорость уменьшает частоту вдвое. Другие значения скорости влияют на частоту соответсвующе.
Также возможно изменять длительность и амплитуду сэмпла, используя ADSR-огибающие. Но это работает немного по-другому, чем ADSR-огибающие синтов. Огибающие сэмплов позволяют уменьшать громкость и длительность, но не увеличивать их. Сэмпл остановится, либо когда его воспроизведение завершено, либо когда огибающая закончилась - в зависимости от того, что произошло раньше. Так что если задать очень долгое затухание, то звучание сэмпла не удлинится.
Вернемся к нашему верному другу амен-брейку:
sample :loop_amen
Без дополнительных регулировок мы слышим полный сэмпл на полной громкости. Если мы хотим его постепенно усиливать в течение одной секунды, то можно задейстовать параметр attack:
:
sample :loop_amen, attack: 1
Для более краткого нарастания, выберите значение атаки поменьше:
sample :loop_amen, attack: 0.3
Отличие поведения ADSR-огибающей сэмпла от стандартной огибающей синта лежит в значении сустейна. В обычной огибающей синта сустейн сам по себе устанавливался в 0, если мы не указывали его вручную. Для сэмплов оно также настраивается “автомагически”, и равняется времени, необходимому для завершения сэмпла. Поэтому мы слышим полный сэмпл, когда никаких настроек не задаётся. Если бы значения атаки, спада, удержания и затухания равнялись 0, то мы бы и намёка на сэмпл не услышали. Так что Sonic Pi вычисляет сколько времени длится сэмпл, вычитает длительности атаки, спада и затухания, и присваивает результат фазе удержания. Если атака, спад и затухание вместе дают значение больше продолжительности сэмпла, то удержание устанавливается в 0.
Для изучения этого явления рассмотрим наш амен-брейк более детально. Если спросить Sonic Pi сколько длится сэмпл:
print sample_duration :loop_amen
Он напечатает 1.753310657596372
, что означает собственное время звучания сэмпла в секундах. Просто для удобства округлим его до 1.75
. Затем, если установить затухание в 0.75
, произойдет кое-что неожиданное:
sample :loop_amen, release: 0.75
Первая секунда сэмпла будет воспроизводиться на полной громкости, а потом последует постепенное затухание в течение 0.75 секунды. Это и есть пример автоудержания в действии. Само по себе затухание всегда работает с конца сэмпла. Если бы наш сэмпл длился 10.75 секунды, то первые 10 секунд он бы играл с полной амплитудой, а потом затухал оставшиеся 0.75 секунды.
Запомните: по умолчанию release:
отсчитывает затухание от конца сэмпла.
Можно использовать вместе attack:
, release:
и поведение автоудержания для постепенного усиления и затухания при воспроизведении сэмпла:
sample :loop_amen, attack: 0.75, release: 0.75
Так как полная продолжительность равняется 1.75 секунды, а наши фазы атаки и затухания суммарно дают 1.5 секунды, то удержание автоматически устанавливается в 0.25 секунды. Так мы легко можем плавно усиливать и гасить сэмпл.
Вернуться к нормальному режиму ADSR-огибающей довольно просто: достаточно вручную задать 0 для sustain:
:
sample :loop_amen, sustain: 0, release: 0.75
После этого сэмпл звучит в общей сложности всего лишь 0.75 секунды. При обычных нулевых значениях attack:
и decay:
громкость сэмпла сразу же достигает полного значения, удерживается там в течение 0 секунд, а потом за 0.75 секунды полностью затихает во время фазы затухания.
Хороший эффект от данного поведения получается, если мы хотим превратить продолжительные сэмплы в более короткие. Это даст им более отрывистое ударное звучание. Рассмотрим сэмпл :drum_cymbal_open
:
sample :drum_cymbal_open
Можно слышать, что тарелка продолжает звенеть некоторое время. С помощью огибающей можно сделать её более резкой:
sample :drum_cymbal_open, attack: 0.01, sustain: 0, release: 0.1
А еще можно изображать удар по тарелке и его глушение за счет увеличения периода удержания:
sample :drum_cymbal_open, attack: 0.01, sustain: 0.3, release: 0.1
А теперь, ступайте, и как следует позабавьтесь, добавляя огибающие к сэмплам. Попробуйте изменять и скорость, чтобы получить действительно интересные результаты.
Этот раздел станет заключительным в нашем исследовании проигрывателя сэмплов Sonic Pi. Давайте кратко повторим уже изученное. Пока мы только разбирались в том, как включать сэмплы:
sample :loop_amen
Потом мы рассмотрели то, как мы можем менять темп сэмплов, например для воспроизведения их на половинной скорости:
sample :loop_amen, rate: 0.5
Затем, мы увидели, как можно постепенно усиливать сэмпл (совместим это с уменьшённой вдвое скоростью):
sample :loop_amen, rate: 0.5, attack: 1
Еще мы узнали как сделать начало сэмпла более резким за счет задания sustain:
явного значения и установки коротких атаки и затухания:
sample :loop_amen, rate: 2, attack: 0.01, sustain: 0, release: 0.35
Однако, вам бы понравилось, если бы не надо было всякий раз начинать сэмпл с начала? Разве не здорово было бы, если бы можно было играть сэмпл не до конца?
Возможно выбрать произвольную начальную точку для сэмпла, задавая ее в качестве значения в диапазоне от 0 до 1. 0 - начало сэмпла, 1 - его конец, а 0.5 - это середина. Попробуем сыграть только вторую половину амен-брейка:
sample :loop_amen, start: 0.5
А как насчет последней четверти:
sample :loop_amen, start: 0.75
Похожим образом разрешается выбирать конечную точку сэмпла как значение между 0 и 1. Прервем амен-брейк на середине:
sample :loop_amen, finish: 0.5
Естественно, мы можем комбинировать обе этих регулировки, чтобы проигрывать произвольные сегменты аудио-файла. Что, если выбрать небольшой участок в центре:
sample :loop_amen, start: 0.4, finish: 0.6
Что случится, если установить старт после финиша?
sample :loop_amen, start: 0.6, finish: 0.4
Круто! Он играет задом-наперед!
Можно объединить эту новую способность играть выбранные сегменты аудио с нашим знакомым rate:
. Например, таким образом воспроизведется очень маленькая секция из середины амен-брейка очень медленно:
sample :loop_amen, start: 0.5, finish: 0.7, rate: 0.2
Наконец, все это возможно соединить с известными нами ADSR-огибающими, чтобы получить интересный результат:
sample :loop_amen, start: 0.5, finish: 0.8, rate: -0.2, attack: 0.3, release: 1
Попробуйте также комбинировать сэмплы, не ограничивайте свою фантазию…
Коль скоро встроенные сэмплы дали тебе быструю возможность разобраться с ними, тебе может захотеться поэкспериментировать с другими записанными звуками в твоей музыке. Sonic Pi полностью поддерживает эту функцию. Но сперва кратко обсудим переносимость твоего произведения.
Когда ты сочиняешь свои композиции только с использованием встроенных синтов и сэмплов, то программный код - это все что нужно для надежного исполнения твоей музыки. Просто задумайся над этим на минуту - это же просто удивительно! Простой кусок текста, который можно отправить по электронной почте или засунуть в Gist являет собой все, что нужно для воспроизведения твоих звуков. Поэтому их очень легко распространять среди твоих друзей, потому что им всего лишь нужно заполучить код.
С другой стороны, если начинать использовать собственные записанные сэмплы, то такая переносимость теряется. Это происходит из-за того, что для воспроизведения твоей музыки другим людям не только нужен твой код, но также и сэмплы. Этот факт ограничивает других в их возможностях изменять и экспериментировать с твоими работами. Конечно, знание этого не должно тебя останавливать от использования собственных сэмплов. Просто помни об этом.
Так как все-таки проиграть любой WAV, AIFF или FLAC с твоего компьютера? Все что нужно, это передать путь к его файлу команде sample
:
# Raspberry Pi, Mac, Linux
sample "/Users/sam/Desktop/my-sound.wav"
# Windows
sample "C:/Users/sam/Desktop/my-sound.wav"
Sonic Pi автоматически загрузит и воспроизведет сэмпл. Вместе с этим можно передавать все стандартные параметры, которые ты привык использовать с sample
:
# Raspberry Pi, Mac, Linux
sample "/Users/sam/Desktop/my-sound.wav", rate: 0.5, amp: 0.3
# Windows
sample "C:/Users/sam/Desktop/my-sound.wav", rate: 0.5, amp: 0.3
Замечание: этот раздел руководства рассказывает о расширенных возможностях работы с большими директориями ваших собственных сэмплов. Рекомендуем ознакомится с ним в тех случаях, если вы загрузили или купили набор сэмплов и хотите использовать их в Sonic Pi
Вы можете смело пропустить этот раздел, если вам хватает встроенных сэмплов
Работая с большими папками внешних сэмплов, вы можете ощутить неудобства при набирании полного пути к каждому отдельному сэмплу.
Например, предположим, что на вашем компьютере у вас есть папка:
/path/to/my/samples/
Когда мы посмотрим в директорию, то увидим следующие сэмплы:
100_A#_melody1.wav
100_A#_melody2.wav
100_A#_melody3.wav
120_A#_melody4.wav
120_Bb_guit1.wav
120_Bb_piano1.wav
Как правило, для того, чтобы проиграть сэмпл пианино, мы можем использовать полный путь:
sample "/path/to/my/samples/120_Bb_piano1.wav"
Если мы хотим проиграть гитарный сэмпл, мы также можем использовать его полный путь:
sample "/path/to/my/samples/120_Bb_guit.wav"
Тем не менее, оба этих вызова требуют от нас знания имён сэмплов внутри нашей директории. Что если мы просто хотим быстро прослушать каждый сэмпл по очереди?
Если мы хотим проиграть первый сэмпл в директории, мы просто должны указать имя директории в sample
и установить индекс в значение 0
следующим образом:
sample "/path/to/my/samples/", 0
Мы также можем создать ярлык (shortcut) нашей директории, используя переменную:
samps = "/path/to/my/samples/"
sample samps, 0
Теперь, если мы хотим проиграть второй сэмпл в директории, нам нужно прибавить 1 к нашему индексу:
samps = "/path/to/my/samples/"
sample samps, 1
Заметьте, что нам больше не нужно знать имени сэмпла в директории - мы просто должны знать название директории (или иметь её ярлык). Если мы запросим индекс, больший, чем количество сэмплов в директории, порядок сэмплов в директории будет замкнут и пойдёт по следующему кругу. Таким образом, вне зависимости от номера, мы получим один из сэмплов в директории.
Обычно, индексирования достаточно, но иногда нам необходимо сортировать и организовывать сэмплы. К счастью, большинство наборов сэмплов структурированы. Давайте ещё раз взглянем на имена сэмплов в нашей директории:
100_A#_melody1.wav
100_A#_melody2.wav
100_A#_melody3.wav
120_A#_melody4.wav
120_Bb_guit1.wav
120_Bb_piano1.wav
Заметьте, что в их именах содержится некоторая информация. Во-первых, у нас есть BMP сэмпла (удары в секунду) в начале имени. Таким образом, сэмпл пианино имеет BPM равный 120, а первые три мелодии - 100 BPM. Имена сэмплов также содержат тональность. Таким образом, гитарный сэмпл имеет тон Bb а мелодии A#. Эта информация очень полезна для сведения этих сэмплов в нашем коде. Например, мы знаем, что сэмпл пианино содержит 120 BPM и тональность Bb.
Оказывается, мы можем использовать это конкретное соглашение об именах наших наборов сэмплов в коде, чтобы помочь нам отфильтровать те, которые нам нужны. Например, если мы работаем с 120 BPM, мы можем отфильтровать все образцы, содержащие строку 120
, со следующим:
samps = "/path/to/my/samples/"
sample samps, "120"
Первое совпадение будет проиграно. Если мы хотим, чтобы заиграло второе совпадение, нам нужно просто изменить индекс:
samps = "/path/to/my/samples/"
sample samps, "120", 1
Более того, мы можем использовать несколько фильтров сразу. Например, если мы хотим сэмпл, имя которого содержит подстроки “120” и “A#”, мы можем легко добиться этого следующим кодом:
samps = "/path/to/my/samples/"
sample samps, "120", "A#"
Наконец, мы по-прежнему можем добавлять свойства к вызову нашего сэмпла
:
samps = "/path/to/my/samples/"
sample samps, "120", "Bb", 1, lpf: 70, amp: 2
Система аргументов фильтров сэмплов понимает два типа информации: источники и фильтры. Источники представляют собой информацию, используемую для создания списка потенциальных совпадений. Источники бывают двух видов:
“/path/to/samples” - строка, представляющая валидный путь к директории “/path/to/samples/foo.wav” - строка, представляющая валидный путь к сэмплу
Функция sample
сначала собирает все источники и использует их для создания большого списка совпадений. Этот список создается, сначала добавляя все допустимые пути, а затем добавляя все допустимые файлы .flac
, .aif
, .aiff
, .wav
, .wave
, содержащиеся в каталогах.
Например, посмотрите на следующий код:
samps = "/path/to/my/samples/"
samps2 = "/path/to/my/samples2/"
path = "/path/to/my/samples3/foo.wav"
sample samps, samps2, path, 0
Например, здесь мы комбинируем сэмплы из двух каталогов и добавляем конкретный сэмпл. Если "/path/to/my/samples/"
содержит 3 сэмпла и "/path/to/my/samples2/"
содержит 12, в сумме мы получим 16 потенциальных сэмплов на индексирование и фильтрацию (3 + 12 + 1).
По умолчанию в список совпадений попадают только файлы примеров в каталоге. Иногда у вас может быть несколько вложенных папок с сэмплами, которые вы хотите искать и фильтровать внутри. Поэтому вы можете выполнить рекурсивный поиск всех образцов во всех подпапках конкретной папки, добавив **
в конец пути:
samps = "/path/to/nested/samples/**"
sample samps, 0
Обратите внимание, что поиск в очень большом наборе папок может занять много времени. Однако, содержимое всех источников папок кэшируется, поэтому задержка будет выполняться только в первый раз.
Наконец, заметьте, что источники должны идти первыми. Если не передан источник, тогда набор встроенных сэмплов будет выбран как список совпадений по умолчанию.
Как только вы получили список совпадений, вы можете использовать следующие фильтры, чтобы уменьшить выборку:
"foo"
Строки будут фильтроваться при появлении подстроки в имени файла (минус путь к каталогу и расширение).
/fo[oO]/
Регулярное выражение будет фильтровать соответствие имени файла шаблону (минус путь к каталогу и расширение).
:foo
- Ключевое слово будет фильтровать совпадения по тому, является ли ключевое слово прямым соответствием имени файла (минус путь к каталогу и расширение).
lambda{|a| ... }
- Процесс с одним аргументом будет обрабатываться как фильтр списка совпадений или функция-генератор. Он принимает текущий и должен вернуть новый список совпадений (список действительных путей к сэмплам).
1
- Числа будут выбирать совпадение с этим индексом (зацикливаться, если необходимо).
Например, мы можем отфильтровать все образцы в каталоге, содержащем строку "foo"
, и воспроизвести первый совпадающий образец на половинной скорости:
sample "/path/to/samples", "foo", rate: 0.5
См. Справку по sample
для множества подробных примеров использования. Обратите внимание, что порядок фильтров соблюдается.
Наконец, вы можете использовать списки везде, где вы можете разместить источник или фильтр. Список будет автоматически сплющиваться, и содержимое будет рассматриваться как обычные источники и фильтры. Поэтому следующие вызовы sample
семантически эквивалентны:
sample "/path/to/dir", "100", "C#"
sample ["/path/to/dir", "100", "C#"]
sample "/path/to/dir", ["100", "C#"]
sample ["/path/to/dir", ["100", ["C#"]]]
Это расширенный раздел для людей, которым нужны дополнительные возможности для манипулирования и использования наборов сэмплов. Если большая часть этого раздела не имеет особого смысла, не беспокойтесь. Скорее всего, вам пока не нужна какая-либо из этих функций. Однако, вы будете знать, когда это понадобится, и вы можете вернуться и перечитать это, когда начнете работать с большими каталогами сэмплов.
Отличный способ сделать вашу музыку несколько более интересной - это использовать случайные числа. В Sonic Pi есть замечательная функция добавления случайности в музыку, но прежде, чем мы начнем, нужно усвоить неприятную истину: в Sonic Pi случайности не являются по-настоящему случайными. Что же, спрашивается, это значит? Давайте разберёмся.
Очень полезная случайная функция называется rrand
. Она вернет случайное значение в диапазоне двух чисел - минимума и максимума (rrand
это сокращение от “ранжированный генератор случайных чисел”). Попробуем проиграть какую-нибудь случайную ноту:
play rrand(50, 95)
Ого, получился неожиданный звук. Он воспроизвел ноту 83.7527
. Прекрасная случайная нота в промежутке между 50 и 95. Постойте-ка, я только что точно предсказал случайную ноту, которая и у вас получилась? Что-то тут нечисто. Попробуем снова. Что? Он снова выбрал 83.7527
? Это явно не случайно!
Правда в том, что выбор не случаен, он псевдо-случаен. Sonic Pi выдаёт повторяющиеся псевдо-случайные числа, чтобы быть уверенным, что музыка, которую вы создаёте на своей машине, будет звучать точно так же на всех других машинах. Это очень полезное свойство, даже если вы добавляете совсем немного случайности в свои композиции.
Конечно же, в конкретном музыкальном произведении, если бы 83.7527
“случайно” выбиралась каждый раз, это было бы не очень интересно. Но все-таки этого не происходит. Попробуйте следующее:
loop do
play rrand(50, 95)
sleep 0.5
end
Ура! Наконец-то это звучит случайно. В течение одного запуска, последующие вызовы функции генератора случайных чисел будут возвращать случайные значения. Однако, следующий прогон создаст точно такую же последовательность случайных чисел и будет звучать идентично. Это как если бы код Sonic Pi всякий раз возвращался назад во времени в тот момент, когда нажимается кнопка Run. Это День Сурка для музыкального синтеза!
Прекрасной иллюстрацией рандомизации в действии является пример колоколов, в которые как будто вселились призраки. Он повторяет :perc_bell
сэмпл, выбирая случайную скорость и задержку между звуками:
loop do
sample :perc_bell, rate: (rrand 0.125, 1.5)
sleep rrand(0.2, 2)
end
Еще один забавный пример случайности - управление частотой среза фильтра синта случайным образом. Отличный синт, на котором можно это попробовать, - это эмулятор :tb303
:
use_synth :tb303
loop do
play 50, release: 0.1, cutoff: rrand(60, 120)
sleep 0.125
end
Что, если вам не нравится последовательность случайных чисел, сгенерированная Sonic Pi? Ну, очень легко выбрать другую начальную точку, если использовать use_random_seed
.Начальная точка по умолчанию равна 0, так что для другого случайного результата выбирайте другую точку!
Сравните следующее:
5.times do
play rrand(50, 100)
sleep 0.5
end
Каждый раз, когда этот код выполняется, вы будете слышать один и тот же набор нот. Чтобы получить другой порядок просто поменяйте начальную точку:
use_random_seed 40
5.times do
play rrand(50, 100)
sleep 0.5
end
В итоге получится другая последовательность из 5 нот. Изменяя начальную точку и слушая результат, можено выбрать что-то, что вам понравится. Потом, когда вы поделитесь результатом с другими, они услышат в точности то же самое, что и вы.
Давайте познакомимся с некоторыми другими случайными функциями.
Очень часто приходится выбирать вещь случайным образом из списка. Например, мне бы могло захотеться проиграть одну ноту из набора: 60, 65 и 72. Я могу достичь этого с помощью функции choose
, которая позволяет мне выбрать предмет из списка. Вначале, надо добавить мои числа в список, что достигается заключением их в квадратные скобки и разделением запятыми: [60, 65, 72]
. Затем, надо просто передать их choose
:
choose([60, 65, 72])
Послушаем, на что это похоже:
loop do
play choose([60, 65, 72])
sleep 1
end
Мы уже видели rrand
, но пробежимся по ней вновь. Она возвращает случайную величину в промежутке двух чисел, не включая границы. Это значит, что никогда не будет выбрано самое большое или самое маленькое значение - всегда нечто между ними. Число всегда будет дробным, то есть не целым. Примеры дробных чисел, возвращаемых rrand(20, 110)
:
87.5054931640625 86.05255126953125 61.77825927734375
Время от времени вам может понадобится получить целое случайное число, а не дробное. Тут-то и придёт на помощь rrand_i
. Она работает так же, как rrand
, но включает граничные значения (то есть она может выбирать минимальные и максимальные числа). Примеры, полученные из rrand_i(20, 110)
:
88 86 62
Вернет случайное дробное число от 0 (включительно), до максимального указанного вами. По умолчанию числа лежат в промежутке от 0 до 1. Такая функция полезна для выбора случайного усиления:
loop do
play 60, amp: rand
sleep 0.25
end
Относится к rrand_i
, как rrand_i
к rrand
. То есть она выбирает случайное целое число в диапазоне от 0 до максимального значение, которое вы укажете.
Иногда нужно изобразить бросание игральных костей - это частный случай rrand_i
, когда минимальное значение всегда 1. В вызов dice
требуется передать количество сторон кости. Стандартная игральная кость имеет 6 сторон, так что dice(6)
будет работать очень похожим образом - возвращать 1, 2, 3, 4, 5 или 6. Однако, прямо как в ролевых играх в стиле фэнтези, пользу может принести и кость с 4 сторонами, и с 12 сторонами или с 20 сторонами. Может даже потребуется кость со 120 гранями!
Наконец, может потребоваться сымитировать вероятность выпадания наибольшей из граней игральной кости, например, 6 для обычной кости. Функция one_in
возвращает истину с вероятностью один к числу сторон кости. Следовательно, для one_in(6)
шанс получить истину составит 1 к 5. В остальных 5 из 6 случаев вернётся ложь. Истинные и ложные значения очень полезны для логических выражений if
, которые мы рассмотрим в одной из следующих глав нашего учебника.
А теперь, пришла пора добавить немного неразберихи в вашу музыку за счет случайностей!
После того, как мы изучили основы создания звука при помощи play
и sample
, и сочинения простых мелодий и ритмов, расставляя задержки между ними, вам могло стать интересно, а что же ещё мир программирования может нам предложить…
Скажем так, вас ожидает захватывающее путешествие! Оказывается, что основные структуры программирования, такие как циклы, условные выражения, функции и потоки выполнения, предлагают нам очень мощные инструменты для выражения собственных музыкальных идей.
Ограничимся лишь основами…
Структура, которая будет попадаться вам в Sonic Pi особенно часто, - это блок. Блоки позволяют нам делать разные полезные вещи с большими участками кода. Например, при помощи параметров синтов и сэмплов мы могли изменять что-то, что происходило в одной строке. Однако, иногда нам нужно сделать что-то значительное, что касается множества строк кода. Например, мы можем решить играть их по кругу, добавить эффект реверберации всего к одному из пяти повторов и так далее. Рассмотрим следующий код:
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
Чтобы сделать что-то с куском кода, нам надо сказать Sonic Pi, где этот блок кода начинается, и где он заканчивается. Для его начала мы используем do
, а для обозначения конца - end
. Пример:
do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
Все же он ещё не является завершённым и не будет работать (попробуйте и получите сообщение об ошибке), так как мы не сообщили Sonic Pi, что мы хотим делать с этим блоком *do/end. Этого можно добиться, если написать некий специальный код перед do
. Далее мы увидим много примеров этих специальных кусочков кода. Пока лишь важно понять, что оборачивание вашего кода в do
и end
сообщает Sonic Pi, что вы хочтите сделать что-то особенное с этим блоком.
До этого момента мы потратили немало времени, пробуя разные звуки, которые можно производить внутри блоков play
и sample
. Мы также изучили как разносить эти звуки во времени, используя sleep
.
Как мы уже смогли выяснить, даже с этими простейшими блоками кода можно долго забавляться. Однако, совершенно новое измерение веселья откроется для вас, когда вы начнёте использовать мощь блоков кода для структурирования вашей музыки. В нескольких последующих главах мы рассмотрим некоторые из этих новых инструментов. Вначале идут итерации и циклы.
Доводилось ли вам писать код, который вы бы хотели повторить несколько раз? Например, у вас могло получиться нечто подобное:
play 50
sleep 0.5
sample :elec_blup
sleep 0.5
play 62
sleep 0.25
Что, если мы захотим повторить это три раза? Что ж, можно было бы выбрать простейший путь и попросту продублировать код трижды:
play 50
sleep 0.5
sample :elec_blup
sleep 0.5
play 62
sleep 0.25
play 50
sleep 0.5
sample :elec_blup
sleep 0.5
play 62
sleep 0.25
play 50
sleep 0.5
sample :elec_blup
sleep 0.5
play 62
sleep 0.25
Этот код просто огромен! Как быть, если нам захочется поменять сэмпл на :elec_plip
? Придется найти все места с изначальным :elec_blup
и переписать их. Что ещё важнее, каким образом повторить кусок кода 50 раз или 1000? Это было бы и впрямь целая куча кода. Гигантское число строк пришлось бы изменять для каждой правки.
На самом деле сделать повторение кода должно быть не сложнее, чем сказать сделай это три раза. В общем-то, так оно и есть. Помните ли вы о нашем старом знакомом, блоке кода? Его можно использовать для отметки начала и конца кода, который необходимо повторить три раза. Мы добавим специальную конструкцию 3.times
. Так что, вместо записи делай это три раза, мы напишем 3.times do
. Это не очень сложно. Просто не забывайте указать end
в конце кода, который надо повторить:
3.times do
play 50
sleep 0.5
sample :elec_blup
sleep 0.5
play 62
sleep 0.25
end
Ну, разве это не лучше дублирования? Много красивых структур может быть создано с использованием этой инструкции:
4.times do
play 50
sleep 0.5
end
8.times do
play 55, release: 0.2
sleep 0.25
end
4.times do
play 50
sleep 0.5
end
Еще можно поместить итерации внутрь других итераций и получить интересный рисунок. Например:
4.times do
sample :drum_heavy_kick
2.times do
sample :elec_blip2, rate: 2
sleep 0.25
end
sample :elec_snare
4.times do
sample :drum_tom_mid_soft
sleep 0.125
end
end
Если потребуется повторить что-то много раз, придётся указывать очень большие значения, такие как 1000.times do
. В этом случае, наверное, лучше попросить Sonic Pi повторять код до бесконечности (или, как минимум, до того момента, пока вы не нажмёте кнопку “Остановить”!). Давайте зациклим амен-брейк:
loop do
sample :loop_amen
sleep sample_duration :loop_amen
end
О циклах важно знать то, что они работают как чёрные дыры внутри кода. Как только исполнение кода попадает в цикл, оно не cможет покинуть его до тех пор, пока вы не остановите его. Код просто будет повторяться по кругу снова и снова вечно. Это значит, что вы никогда не услышите код, написанный после цикла. К примеру, тарелка после этого цикла никогда не прозвучит:
loop do
play 50
sleep 1
end
sample :drum_cymbal_open
Теперь, пришла пора создать интересные структуры из вашего кода при помощи итераций и циклов!
Часто, помимо игры произвольных нот (см. предыдущую главу), вы также будете хотеть принимать произвольные решения и выполнять либо одну, либо другую ветку кода, в зависимости от результата. Например, вы можете решить случайным образом сыграть звук барабана или звук тарелки. Мы можем достичь этого при помощи выражения if
.
Итак, давайте подбросим монетку: орел - играем на барабане, решка - бьём по тарелке. Это просто. Можно сымитировать это действие, используя функцию one_in
(представлена в разделе, посвящённом случайности) с вероятностью один к двум: one_in(2)
. Затем, мы можем использовать результат, чтобы выбрать один из двух участков кода: один - для звука барабана, другой - для тарелки:
loop do
if one_in(2)
sample :drum_heavy_kick
else
sample :drum_cymbal_closed
end
sleep 0.5
end
Отметим, что выражение if
состоит из трёх частей:
Вопрос, на который нужно получить ответ Первый вариант исполняемого кода (выбирается, если ответ на вопрос “Да”) Второй вариант исполняемого кода (если ответ “Нет”)
Обычно, в программных языках ответ “Да” представлен термином true
(истина), а false
(ложь) означает “Нет”. То есть нам нужно задать вопрос, который даёт true
или false
ответ. Это именно то, что делает one_in
.
Обратите внимание, что первый вариант действия размещается между if
и else
, а второй вариант находится между else
и end
. Как и в блоках do/end, допускается многострочный код. Например:
loop do
if one_in(2)
sample :drum_heavy_kick
sleep 0.5
else
sample :drum_cymbal_closed
sleep 0.25
end
end
В этом случае мы добавляем задержку в течение разного количества времени в зависимости от сделанного выбора.
Иногда вам требуется условно выполнить всего лишь одну строчку кода. Это возможно, если написать if
и сразу же ваш вопрос. Например:
use_synth :dsaw
loop do
play 50, amp: 0.3, release: 2
play 53, amp: 0.3, release: 2 if one_in(2)
play 57, amp: 0.3, release: 2 if one_in(3)
play 60, amp: 0.3, release: 2 if one_in(4)
sleep 1.5
end
Если запустить этот код, то каждый раз будут звучать разные аккорды, в которых вероятность появления каждой ноты будет различна.
Итак, вы написали чумовую бас-партию и заводной ритм. Как сыграть обе эти части одновременно? Одно из решений - переплести их вместе вручную - играть небольшую чать баса, затем немного от ударных, потом еще бас… Однако, вскоре становится очень сложно даже думать о том, как разместить это все во времени, особенно если вы начнётк добавлять новые элементы.
Что если бы Sonic Pi мог соединять партии инструментов за вас автоматически? Вообще-то это возможно, для этого вам надо использовать специальную вещь, которая называется поток выполнения.
Дабы не усложнять наш пример, представим, что это и есть крутой ритм и потрясающая бас-партия:
loop do
sample :drum_heavy_kick
sleep 1
end
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
Как мы уже обсуждали, циклы - это черные дыры в программе. Как только начинает выполняться цикл, выйти из него уже нельзя, не нажав кнопку “Остановить”. Как сыграть сразу оба цикла в одно и то же время? Надо сообщить Sonic Pi, что мы хотим выполнять что-то параллельно с остальной частью кода. Вот тут-то нам и придут на помощь потоки выполнения.
in_thread do
loop do
sample :drum_heavy_kick
sleep 1
end
end
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
Обернув первый цикл в do/end блок типа in_thread
, мы заставляем Sonic Pi выполнять содержимое блока do/end точно в то же самое время, когда начинается следующее выражение после блока do/end. В нашем примере таковым является второй цикл. Попробуйте, и вы услышите, как ударные и бас переплетаются вместе!
Теперь, что если мы хотим добавить синт поверх. Что-то типа:
in_thread do
loop do
sample :drum_heavy_kick
sleep 1
end
end
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
loop do
use_synth :zawa
play 52, release: 2.5, phase: 2, amp: 0.5
sleep 2
end
Опять та же самая проблема. Первый цикл воспроизводится вместе со вторым из-за in_thread
. Но третий цикл не достигается. То есть нам нужен ещё один поток:
in_thread do
loop do
sample :drum_heavy_kick
sleep 1
end
end
in_thread do
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
end
loop do
use_synth :zawa
play 52, release: 2.5, phase: 2, amp: 0.5
sleep 2
end
Вас может это удивить, но при нажатии кнопки “Выполнить” также создаётся поток выполнения кода. Вот почему её многократное нажатие наслаивает звуки друг на друга. Запуски автоматически наслаивают звуки, потому что они сами являются потоками.
По мере овладевания секретами мастерства Sonic Pi вы узнаете, что потоки - это самые важные строительные блоки вашей музыки. Не последним их качеством является способность изолировать от других потоков то, что называется текущей установкой. Что это значит? Ну, когда вы переключаете синт при помощи use_synth
, на самом деле вы изменяете синт в текущем потоке - ни в одном другом потоке он не изменится. Давайте посмотрим на это в действии:
play 50
sleep 1
in_thread do
use_synth :tb303
play 50
end
sleep 1
play 50
Обратите внимание, как звук в середине отличается от остальных. Команда use_synth
применилась только к тому потоку, где она была выполнена, а не к окружающему главному потоку выполнения программы.
Когда вы создаёте новый поток, вызывая in_thread
, то он автоматически наследует все текущие настройки из родительского потока. Проверим это:
use_synth :tb303
play 50
sleep 1
in_thread do
play 55
end
Заметили, что вторая нота воспроизводится на :tb303
, несмотря на то, что это происходит в отдельном потоке? Любые регулировки, устанавливаемые различными use_*
функциями, будут вести себя аналогично.
При создании, потоки наследуют все установки от своего родителя, но любые последующие изменения не имеют обратного эффекта.
Наконец, мы можем называть наши потоки:
in_thread(name: :bass) do
loop do
use_synth :prophet
play chord(:e2, :m7).choose, release: 0.6
sleep 0.5
end
end
in_thread(name: :drums) do
loop do
sample :elec_snare
sleep 1
end
end
Посмотрите на панель сообщений, когда этот код выполняется. Видите, как появляются сообщения, в которые включены имена потоков?
[Run 36, Time 4.0, Thread :bass]
|- synth :prophet, {release: 0.6, note: 47}
Последнее, что вам нужно узнать об именованных потоках, это то, что только один поток с определённым именем может выполняться одновременно. Рассмотрим следующий код:
in_thread do
loop do
sample :loop_amen
sleep sample_duration :loop_amen
end
end
Скопируйте этот фрагмент в пустой буфер редактора и нажмите кнопку “Выполнить”. Нажмите её еще пару раз. Получилась какафония от того, что много повторений амен-брейка смешались вместе. Ладно, нажимайте “Остановить”.
Это то самое поведение, которое нам не раз уже доводилось наблюдать. Если нажимать кнопку “Выполнить”, то новый звук накладывается поверх уже играющего. Поэтому, если у вас есть цикл, и вы нажмёте кнопку “Выполнить” три раза, то получится три слоя циклов, звучащих вместе.
Но ситуация меняется с именованными потоками:
in_thread(name: :amen) do
loop do
sample :loop_amen
sleep sample_duration :loop_amen
end
end
Теперь попробуте понажимать кнопку “Выполнить” с этим кодом. Вы услышите только один цикл амен-брейк. В сообщениях вы также увидите:
==> Skipping thread creation: thread with name :amen already exists.
Sonic Pi сообщает вам, что поток с именем :amen
уже существует, так что он не создаст ещё одного такого же.
Сейчас такое поведение может не показаться вам таким уж полезным. Но в дальнейшем вы ещё вспомните о нём, когда мы начнем лайвкодинг…
Когда вы станете писать большое количество кода, вам потребуется способ организовать и структурировать элементы так, чтобы они выглядели более аккуратно, и их было бы проще понять. Функции - это очень мощный инструмент достижения этой цели. Они позволяют нам давать имена фрагментам кода. Посмотрим, как это работает.
define :foo do
play 50
sleep 1
play 55
sleep 2
end
Мы определили новую функцию, называющуюся foo
. Мы сделали это при помощи нашего старого знакомого - блока do/end и магического слова define
, за которым следует имя нашей функции. Необязательно было называть ее foo
. Можно было придумать какое угодно название, к примеру bar
или baz
. Но, вообще-то, лучше всего, когда имя что-то означает, например main_section
(главная секция) или lead_riff
(солирующий рифф).
Не забывайте ставить двоеточие :
перед именем функции, когда вы определяете её.
Когда наша функция определена, мы можем вызвать её, просто написав её имя:
define :foo do
play 50
sleep 1
play 55
sleep 0.5
end
foo
sleep 1
2.times do
foo
end
Можно использовать foo
внутри повторяющихся блоков, а также везде, где мы могли бы использовать play
или sample
. Это даёт нам отличный способ самовыражения и создания новых обозначений в наших композициях.
Пока что, всякий раз при нажатии кнопки “Выполнить”, Sonic Pi начинал с чистого листа. Ему не известно ничего, кроме того, что находится в буфере редактора. Нельзя ссылаться на код в другом буфере или другом потоке. Однако, функции меняют это. Когда вы определяете функцию, Sonic Pi запоминает ее. Попробуем. Удалите весь код в буфере и замените его вот на что:
foo
Нажмите кнопку “Выполнить”, и убедитесь, что ваша функция играет. Куда же пропал код? Откуда Sonic Pi знал, что играть? Он просто вспомнил вашу функцию даже после её удаления из буфера. Он запомнил что вы напечатали. Это работает только с функциями, которые создаются при помощи define
(и defonce
)
Вам, возможно, будет интересно узнать, что вашу функцию можно научить принимать аргументы, прямо как rrand
принимает значения минимума и максимума. Посмотрим как это выглядит:
define :my_player do |n|
play n
end
my_player 80
sleep 0.5
my_player 90
Пример не очень интересный, но он показывает, что имеется в виду. Мы создали нашу собственную версию play
с параметрами и назвали её my_player
.
Параметры должны описываться после ключевого слова do
блока define
. Они должны быть окружены вертикальными стойками |
и разделены запятыми ,
. Любые слова могут служить именами параметров.
Магия происходит внутри блока define
. Там можно использовать имена параметров, как если бы они были настоящими значениями. В этом примере я проигрываю ноту n
. Вы можете думать о параметрах, как о неких обещаниях о том, что при выполнении кода они будут заменены их значениями. Это достигается передачей параметра функции при её вызове. Чтобы сыграть ноту 80, я пишу my_player 80
. Тогда внутри определения функции n
меняется на 80, так что play n
превращается в play 80
. В следующий раз, когда я вызываю my_player 90
, вместо n
появляется 90, поэтому play n
становится play 90
.
Рассмотрим что-нибудь поинтереснее:
define :chord_player do |root, repeats|
repeats.times do
play chord(root, :minor), release: 0.3
sleep 0.5
end
end
chord_player :e3, 2
sleep 0.5
chord_player :a3, 3
chord_player :g3, 4
sleep 0.5
chord_player :e3, 3
Я использовал переменную repeats
, как если бы она была числом в строке repeats.times do
. Ещё я использовал root
в качестве названия ноты для play
.
Видите, как можно писать нечто очень выразительное и в то же время простое для чтения, если перенести большую часть логики в функцию!
A useful thing to do in your code is to create names for things. Sonic Pi makes this very easy: you write the name you wish to use, an equal sign (=
), then the thing you want to remember:
sample_name = :loop_amen
В этом примере мы “запомнили” обозначение :loop_amen
в переменной sample_name
. Теперь мы можем пользоваться sample_name
везде, где мы могли бы указать :loop_amen
. Например:
sample_name = :loop_amen
sample sample_name
Есть три главных причины для использования переменных в Sonic Pi: присвоение значения, управление повторами и захват результатов операций.
Когда вы пишете код, вы говорите компьютеру сделать что-то. Пока компьютер это понимает - всё в порядке. Но всё же важно помнить, что код читает не только компьютер. Другие люди, возможно, захотят тоже прочесть его и попробовать разобраться, что в нём происходит. Скорее всего и вы сами будете читать и пытаться понять свой собственный код в будущем. Сейчас всё кажется очевидным, но для остальных, или же для вас самих в будущем, это может быть не так!
Одним из способов помочь другим понять, что делает ваш код, - это оставлять комментарии (как мы это видели в предыдущей главе). Ещё один способ - давать осмысленные имена переменным. Посмотрите на этот код:
sleep 1.7533
Зачем указано число 1.7533
? Откуда оно взялось? Что оно означает? Теперь взгляните на этот пример:
loop_amen_duration = 1.7533
sleep loop_amen_duration
Вот теперь стало ясно, что значит 1.7533
. Это же продолжительность сэмпла :loop_amen
! Конечно, вы могли бы заметить, почему бы просто не написать так:
sleep sample_duration(:loop_amen)
Это, безусловно, очень хороший способ выразить намерения кода.
Часто вы видите много повторений в своем коде. Когда необходимо что-то поменять, то делать это приходится во многих местах. Посмотрите на следующее:
sample :loop_amen
sleep sample_duration(:loop_amen)
sample :loop_amen, rate: 0.5
sleep sample_duration(:loop_amen, rate: 0.5)
sample :loop_amen
sleep sample_duration(:loop_amen)
Много чего мы делаем с :loop_amen
! Но что если мы захотим послушать как это будет звучать с другим замкнутым сэмплом, таким как :loop_garzul
? Пришлось бы найти и заменить все :loop_amen
на :loop_garzul
. Этим можно заняться, если больше нечего делать. А как насчет живого выступления? Иногда время - это непозволительная роскошь. Особенно, если вы хотите, чтобы люди продолжали танцевать.
Ну, а если бы вы записали свой код так:
sample_name = :loop_amen
sample sample_name
sleep sample_duration(sample_name)
sample sample_name, rate: 0.5
sleep sample_duration(sample_name, rate: 0.5)
sample sample_name
sleep sample_duration(sample_name)
Он делает совершенно то же самое (попробуйте сами). Зато тут есть возможность заменить строку sample_name = :loop_amen
на sample_name = :loop_garzul
, что приведёт к реальным изменениям во многих местах. Вот что даёт нам магия переменных.
Наконец, хороший повод использовать переменные - захватывать результат чего-либо. К примеру, вам может понадобится что-то сделать с продолжительностью сэмпла:
sd = sample_duration(:loop_amen)
Теперь можно вставлять sd
везде, где нам нужна длительность сэмпла :loop_amen
.
Вероятно, ещё более важно то, что переменная разрешает нам сохранить результат вызова play
или sample
:
s = play 50, release: 8
Мы захватили и запомнили s
как переменную, которая позволяет управлять синтом, пока он играет:
s = play 50, release: 8
sleep 2
control s, note: 62
В следующей главе мы рассмотрим подробнее управление синтами.
Whilst variables are great for giving things names and capturing the results of things, it is important to know that they should typically only be used locally within a thread. For example, don’t do this:
a = (ring 6, 5, 4, 3, 2, 1)
live_loop :shuffled do
a = a.shuffle
sleep 0.5
end
live_loop :sorted do
a = a.sort
sleep 0.5
puts "sorted: ", a
end
In the above example we assign a ring of numbers to a variable a
and then used it within two separate live_loop
s. In the first live loop every 0.5
s we sort the ring (to (ring 1, 2, 3, 4, 5, 6)
) and then print it out to the log. If you run the code, you’ll find that the printed list is not always sorted!. This may surprise you - especially that sometimes the list is printed as sorted, and sometimes it is not. This is called non-deterministic behaviour and is the result of a rather nasty problem called a race-condition. The problem is due to the fact that the second live loop is also manipulating the list (in this case shuffling it) and by the time the list is printed, sometimes it has just been sorted and sometimes it has just been shuffled. Both live loops are racing to do something different to the same variable and every time round a different loop ‘wins’.
There are two solutions to this. Firstly, don’t use the same variable in multiple live loops or threads. For example, the following code will always print a sorted list as each live loop has its own separate variable:
live_loop :shuffled do
a = (ring 6, 5, 4, 3, 2, 1)
a = a.shuffle
sleep 0.5
end
live_loop :sorted do
a = (ring 6, 5, 4, 3, 2, 1)
a = a.sort
sleep 0.5
puts "sorted: ", a
end
However, sometimes we do want to share things across threads. For example, the current key, BPM, synth etc. In these cases, the solution is to use Sonic Pi’s special thread-safe state system via the fns get
and set
. This is discussed later on in section 10.
Когда у вас начнёт достаточно хорошо получаться лайвкодинг с большим количеством потоков, выполняющихся одновременно, вы, скорее всего, заметите, что очень легко допустить ошибку, которая может прервать поток. Ничего страшного в этом нет, так как поток может быть перезапущен простым нажатием кнопки “Выполнить”. Однако, после перезапуска потока он будет играть невпопад с другими.
Как мы обсуждали ранее, новые потоки, созданные при помощи in_thread
наследуют все настройки от родительского потока. Они включают и текущую метку времени. Это означает, что потоки всегда синхронизированы друг с другом после одновременного их запуска.
Но, когда поток стартует отдельно от родительского, внутри него ведётся собственный отсчет времени, который вряд ли совпадёт с каким-либо из других активных потоков.
В Sonic Pi решением этой проблемы являются функции cue
и sync
.
cue
позволяет нам отправлять сигнал пульса другим потокам. По умолчанию, другие потоки не слушают и пропускают эти сообщения. Однако, поток может легко заявить о своей заинтересованности, вызвав функцию sync
.
Важно осознавать, что функция sync
похожа на sleep
, ведь она останавливает текущий поток, и тот некоторое время не выполняется. Для sleep
время простоя указывается явно, но вызвав sync
, вы не знаете сколько времени оно продлится, потому что sync
ждёт следующий cue
от другого потока. Это может случиться скоро, а может и нет.
Давайте выясним немного больше деталей:
in_thread do
loop do
cue :tick
sleep 1
end
end
in_thread do
loop do
sync :tick
sample :drum_heavy_kick
end
end
Здесь у нас есть два потока. Один работает как метроном. Он не играет никаких звуков, только отправляет :tick
сигнал каждую долю такта. Второй поток синхронизуется с сообщениями tick
. Когда он получает :tick
сообщение, то наследует временную отметку потока, вызвавшего cue
, и продолжает выполнение.
В результате мы будем слышать сэмпл :drum_heavy_kick
в тот самый момент, когда другой поток отправляет :tick
сигнал. Даже если два этих потока стартовали не одновременно, это всё равно будет происходить:
in_thread do
loop do
cue :tick
sleep 1
end
end
sleep(0.3)
in_thread do
loop do
sync :tick
sample :drum_heavy_kick
end
end
Результатом вызова sleep
будет расхождение второго потока с первым. Однако, так как мы используем cue
и sync
, то мы автоматически синхронизируем потоки и избегаем любых случайных временных сдвигов.
Для сигналов cue
можно использовать любые названия, а не только :tick
. Просто следите за тем, чтобы все остальные потоки вызывали sync
с правильным именем. Иначе они остановятся навсегда (или, по крайней мере, пока вы не нажмёте кнопку “Остановить”).
Попробуем что-нибудь с несколькими названиями cue
:
in_thread do
loop do
cue [:foo, :bar, :baz].choose
sleep 0.5
end
end
in_thread do
loop do
sync :foo
sample :elec_beep
end
end
in_thread do
loop do
sync :bar
sample :elec_flip
end
end
in_thread do
loop do
sync :baz
sample :elec_blup
end
end
Тут у нас есть цикл с функцией cue
, отправляющей пульс. Случайным образом этот пульс может быть назван :foo
, :bar
или :baz
. Ещё есть три цикла в потоках, которые синхронизируются с каждым из этих сигналов независимо и воспроизводят разные сэмплы. Чистый эффект от этого кода в том, что каждые полсекунды мы слышим звук, так как каждый из потоков sync
случайным образом синхронизируется с потоком cue
и проигрывает свой сэмпл.
Конечно же, код будет работать даже если расположить потоки в обратном порядке, поскольку потоки будут дожидаться следующего cue
.
Одной из наиболее стоящих и весёлых вещей в Sonic Pi является возможность легко добавить студийные эффекты к вашим звукам. Например, можно добавить реверберацию к частям вашей пьесы или эхо, или даже исказить или заставить колебаться ваши басы.
Sonic Pi даёт очень простой, но вместе с тем очень мощный способ добавления эффектов. Можно даже строить цепочки из них (таким образом можно пропустить звук через дисторшн, потом эхо и в конце реверберацию) и настраивать каждый из них по отдельности при помощи регулировок (как синты и сэмплы). Можно даже менять параметры эффектов пока они используются. Так, к примеру, реверберация баса может усиливаться в течение трэка…
Не переживайте, если это кажется вам слишком сложным. Всё станет намного понятнее после того, как вы попробуете это сами. Но пока полезной аналогией будет служить гитарный эффект. Есть много видов гитарных эффектов. Одни добавляют реверберацию, другие искажение и так далее. Гитарист подключает свой инструмент к педали, например дисторшн, затем другим кабелем добавляет в цепь педаль реверберации. После этого выход реверберации соединяется с усилителем:
Гитара -> Дисторшн -> Реверберация -> Усилитель
Это называется каскадом эффектов. И это поддерживается в Sonic Pi. Вдобавок, обычно на каждой педали есть ручки и ползунки для управления применяемым эффектом. В Sonic Pi есть то же самое. Представьте себе музыканта, который что-то играет, а кто-то другой управляет эффектами. Такой способ тоже реализован в Sonic Pi, но тут вместо кого-то другого вступает в дело компьютер.
Давайте исследовать эффекты!
В этом разделе мы рассмотрим пару эффектов - реверберацию и эхо. Мы увидим, как их использовать, как настраивать и как строить из них цепочки.
Система эффектов Sonic Pi использует блоки. Так что, если ты еще не прочел главу 5.1, было бы неплохо её просмотреть и вернуться назад.
Нужно написать специальный блок with_fx :reverb
, если мы хотим использовать реверберацию. Примерно так:
with_fx :reverb do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
Теперь запустите этот код, и вы услышите как он звучит с реверберацией. Неплохо, правда? Все что угодно будет звучать отлично с реверберацией.
Посмотрим, что произойдет, если поместить код вне блока do/end:
with_fx :reverb do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
sleep 1
play 55
Обратите внимание, как последний play 55
играется без реверберации. Это происходит потому, что эта строка находится за пределами блока do/end, то есть она не пропускается через эффект.
Точно так же, если играть звуки перед блоком do/end, то и они не будут обрабатываться эффектом:
play 55
sleep 1
with_fx :reverb do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
sleep 1
play 55
Есть множество эффектов на выбор. Как насчет эхо?
with_fx :echo do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
Одна из сильных сторон блоков эффектов Sonic Pi в том, что у них есть регулировки, похожие на те, что мы уже видели у play
и sample
. Например, рассмотрим забавный параметр phase:
, который представляет собой длительность эхо в долях такта. Сделаем эхо помедленнее:
with_fx :echo, phase: 0.5 do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
Или побыстрее:
with_fx :echo, phase: 0.125 do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
Заставим эхо затихать дольше, установив время decay:
на 8 долей такта:
with_fx :echo, phase: 0.5, decay: 8 do
play 50
sleep 0.5
sample :elec_plip
sleep 0.5
play 62
end
Блоки эффектов можно вкладывать друг в друга, что позволяет очень легко собирать из них каскады эффектов. Например, что если вы захотите пропустить некоторый код через эхо, а потом через реверберацию? Легко! Просто поместите один эффект внутри другого:
with_fx :reverb do
with_fx :echo, phase: 0.5, decay: 8 do
play 50
sleep 0.5
sample :elec_blup
sleep 0.5
play 62
end
end
Представьте как звук вытекает изнутри наружу. Весь звук из внутреннего блока do/end (такой как play 50
) вначале попадает в эффект эхо, а затем звук с эффектом эхо в свою очередь отправляется в эффект реверберации.
Можно создавать очень глубокие вложения, чтобы получать непредсказуемые результаты. Однако, имейте в виду, что эффекты требуют больших ресурсов. При вложении они работают в одно и то же время. Так что используйте эффекты разумно, особенно на платформах с низким количеством ресурсов (например Raspberry Pi).
В Sonic Pi встроено большое количество эффектов на выбор. Чтобы увидеть доступные, щелкните мышью по FX в левом нижнем углу справочной системы. Вот некоторые из моих любимых:
wobble, reverb, echo, distortion, slicer
Теперь вы можете добавлять эффекты везде, где только захочется!
Хотя снаружи они выглядят обманчиво просто, внутри эффекты устроены довольно сложно. Их простота часто побуждает людей злоупотреблять их использованием. Это вполне нормально, если у вас мощный компьютер. Но если, как и я, вы импровизируете на Raspberry Pi, то нужно быть аккуратным с нагрузкой. Иначе ритм может начать прерываться.
Рассмотрим следующий код:
loop do
with_fx :reverb do
play 60, release: 0.1
sleep 0.125
end
end
В этом примере мы играем ноту 60 с очень коротким временем затухания. То есть это очень короткая нота. Нам нужна реверберация, поэтому мы обернули её в блок reverb. Пока что все хорошо. За исключением…
Посмотрим, что делает код. Во-первых, у нас есть цикл loop
. Это означает, что всё, что внутри него, повторяется бесконечно. Во-вторых, есть блок with_fx
. Его роль в том, чтобы создавать новый эффект реверберации каждый раз, когда он выполняется. Как будто для каждого щипка гитарной струны включается собственная педаль реверберации. Здорово, что так можно делать, но это не всегда то, что нам нужно. Например, такой код будет трудно выполнять на Raspberry Pi. Создание эффекта реверберации и ожидание момента, когда он должен быть остановлен и удален, - обо всем позаботится за тебя блок with_fx
. Но на это требуются ресурсы процессора, а они могут быть ограничены.
Как изменить код так, чтобы он больше походил на обычное подключение, где у нашего гитариста всего одна педаль реверберации, через которую и пропускаются все звуки? Легко:
with_fx :reverb do
loop do
play 60, release: 0.1
sleep 0.125
end
end
Мы помещаем наш цикл внутрь блока with_fx
. Таким образом создается всего один единственный эффект для всех нот, извлекаемых в цикле. Этот вариант намного более эффективен, и Raspberry Pi с ним хорошо справится.
В качестве компромисса можно предложить использовать with_fx
на протяжении нескольких повторений внутри цикла:
loop do
with_fx :reverb do
16.times do
play 60, release: 0.1
sleep 0.125
end
end
end
То есть новый эффект реверберации будет создаваться через каждые 16 нот.
This is such a common pattern that with_fx
supports an opt to do exactly this but without having to write the 16.times
block:
loop do
with_fx :reverb, reps: 16 do
play 60, release: 0.1
sleep 0.125
end
end
Both the reps: 16
and 16.times do
examples will behave identically. The reps: 16
essentially repeats the code in the do/end
block 16 times so you can use them both interchangeably and choose the one that feels best for you.
Помните, ошибок нет, есть только возможности. Однако, каждый из этих подходов даст разный звук и разные показатели производительности. Поэтому попрактикуйтесь, а затем использте тот способ, который звучит лучше всего для вас, и в то же время укладывается в ограничения быстродействия вашей платформы.
До этого момента мы разбирали, как включать синты и сэмплы, и то, как менять их регулировки, такие как амплитуда, панорамирование, параметры огибающей и т.д. Каждый звук, по сути, является отдельным звуком с собственными настройками, действующими в течение всей его продолжительности.
Вот бы было здорово, если бы вы могли менять параметры воспроизводящегося звука, как вы може подтягивать вибрирующую струну гитары, правда?
Вам повезло - этот раздел покажет, как это можно сделаеть в Sonic Pi.
Раньше мы изучали только как включать звуки и добавлять эффекты. Однако, в Sonic Pi есть возможность управлять воспроизводимыми звуками. Этого можно достичь, сохранив ссылку на синт в переменной:
s = play 60, release: 5
В этом примере мы создали локальную переменную среды выполнения s
, которая представляет собой синт, играющий ноту 60. Обратите внимание, что она локальна для текущего контекста - нельзя получить к ней доступ из других контекстов, таких как функции.
После того, как у нас появилась s
, мы можем управлять ей с помощью функции control
:
s = play 60, release: 5
sleep 0.5
control s, note: 65
sleep 0.5
control s, note: 67
sleep 3
control s, note: 72
Заметьте, что мы запускаем не четыре разных синта, а один и просто изменяем высоту звука в то время, как он продолжает играть.
Можно передавать функции control
любые из стандартных настроек, то есть доступен контроль над такими параметрами, как amp:
, cutoff:
или pan:
.
Некоторые регулировки нельзя менять после включения синта. Это относится ко всем параметрам ADSR-огибающей. Из документации в справочной системе можно узнать какие параметры разрешено менять, а какие нет. Если документация говорит Нельзя изменять после установки, это значит, что после запуска синта настройке нельзя задавать новое значение.
Также в Sonic Pi доступен контроль над эффектами, хотя он и достигается немного другим способом:
with_fx :reverb do |r|
play 50
sleep 0.5
control r, mix: 0.7
play 55
sleep 1
control r, mix: 0.9
sleep 1
play 62
end
Мы используем параметры блока do/end вместо переменных. Нам надо задать уникальное имя для выбранного эффекта, окружив его вертикальными чертами |
. Затем мы можем ссылаться на него изнутри охватывающего блока do/end. Так же работают и параметризованные функции.
Пришла пора немного поуправлять синтами и эффектами!
Вы могли заметить, что есть множество настроек, имя которых оканчивается на _slide
, пока занимались изучением синтов и специальных эффектов. Возможно, вы даже пытались ими воспользоваться и заметили, что ничего не происходит. Это от того, что они не являются обычными параметрами. Эти настройки особенные потому, что они работают только тогда, когда вы контролируете синт так, как мы видели в предыдущем разделе.
Рассмотрим пример:
s = play 60, release: 5
sleep 0.5
control s, note: 65
sleep 0.5
control s, note: 67
sleep 3
control s, note: 72
Вы можете слышать, как высота звука синта сразу же меняется, как только происходит вызов control
. Однако, что если мы захотим, чтобы параметр менялся плавно. Поскольку мы управляем значением note:
, то для добавления плавности надо задать параметр синтезатора note_slide
:
s = play 60, release: 5, note_slide: 1
sleep 0.5
control s, note: 65
sleep 0.5
control s, note: 67
sleep 3
control s, note: 72
Теперь мы слышим, как ноты подтягиваются в промежутках между вызовами control
. Звучит неплохо, не так ли? Можно ускорить скольжение звука, если указать более короткий интервал времени, например note_slide: 0.2
, или замедлить его, установив большее значение.
Для каждой настройки, которой можно управлять, есть соответствующий _slide
параметр, с которым можно поиграть.
Если вы задали значение _slide
для играющего синтезатора, то оно запомнится и будет использоваться всякий раз, когда соответствующая настройка меняется. Чтобы прекратить плавное изменение звука, надо установить значение _slide
в 0 перед следующим вызовом control
.
Можно делать плавное изменение эффектов:
with_fx :wobble, phase: 1, phase_slide: 5 do |e|
use_synth :dsaw
play 50, release: 5
control e, phase: 0.025
end
Пришла пора добавить немного плавности к вашей музыке…
Очень полезный инструмент в инструментарии программиста - это структуры данных.
Иногда вы, возможно, захотите определить и использовать несколько разных вещей. Например, вы можете посчитать полезным иметь последовательность нот, чтобы играть их одну за другой. Языки программирования имеют структуры данных, чтобы вы могли делать это.
Существует множество интересных и экзотических структур данных, доступных для программистов, и люди постоянно изобретают новые. Впрочем, пока что мы нуждаемся в рассмотрении одной очень простой структуры данных - списка.
Давайте рассмотрим его более подробно. Мы узнаем про его базовую форму, а так же как списки могут быть использованы для представления гамм и аккордов.
В этом разделе мы рассмотрим очень полезную структуру данных - список. Мы очень кратко познакомились с ним ранее в разделе о рандомизации, тогда мы случайным образом выбирали ноты из списка, чтобы играть их:
play choose([50, 55, 62])
В этом разделе мы также изучим использование списков для представления аккордов и гамм. Сначала давайте вспомним, как мы можем сыграть аккорд. Помните, что если мы не используем sleep
, все звуки воспроизводятся одновременно:
play 52
play 55
play 59
Давайте посмотрим на другой способ представления этого кода.
Один из вариантов - разместить все ноы в виде списка: [52, 55, 59]
. Наша дружественная функция play
достаточно умна, чтобы понимать, как играть список нот. Попробуйте это:
play [52, 55, 59]
О, это уже приятнее читать. Воспроизведение списка нот не мешает вам использовать любые обычные параметры:
play [52, 55, 59], amp: 0.3
Конечно, мы также можем использовать традиционные имена нот вместо номеров MIDI:
play [:E3, :G3, :B3]
Теперь те из вас, кому повезло изучать теорию музыки, смогут подтвердить, что аккорд ми минор был сыгран в 3-й октаве.
Еще одной полезной особенностью списка является возможность получать оттуда информацию. Это может звучать немного странно, но это не сложнее чьей-либо просьбы открыть книгу на странице 23. Применительно к списку, говорят - “Какой элемент находится по индексу 23”? Единственная странность заключается в том, что в программировании индексы начинаются с 0, а не с 1.
Мы не считаем индекы списка 1, 2, 3… вместо этого мы считаем 0, 1, 2…
Давайте рассмотрим это чуть более подробно. Взгляните на этот список:
[52, 55, 59]
Здесь нет ничего страшного. Теперь, какой второй элемент в этом списке? Конечно, это 55
. Это было легко. Давайте посмотрим, что нам ответит компьютер на аналогичный вопрос:
puts [52, 55, 59][1]
Это выглядит немного странно, если вы никогда не видели ничего подобного раньше. Поверьте мне, это не очень сложно. Строка выше состоит из трёх частей: слово puts
, наш список [52, 55, 59]
и наш индекс [1]
. Сначала мы говорим puts
потому что хотим, чтобы Sonic Pi напечатал ответ для нас в журнал. Далее, мы даём ему наш список, и, наконец, наш индекс запрашивает второй элемент. Мы должны окружить индекс квадратными скобками и т.к. счёт начинается с 0
, индекс второго элемента это 1
. Посмотрите:
# indexes: 0 1 2
[52, 55, 59]
Попробуйте выполнить код puts [52, 55, 59][1]
и вы увидите, что 55
появится в журнале. Измените индекс 1
на другой, попробуйте более длинные списки и подумайте, как вы сможете использовать список в своём следующем сочинении. Например, какие музыкальные структуры могут быть представлены в виде ряда чисел…
Sonic Pi имеет встроенную поддержку названий аккордов, которая возвращает списки. Попробуйте сами:
play chord(:E3, :minor)
Теперь мы действительно проникаем куда-то. Это выглядит намного симпатичнее, чем сырые списки (и это легче читать). А как на счёт других аккордов, Sonic Pi их поддерживает? Да, и много. Попробуйте некоторые из них:
chord(:E3, :m7)
chord(:E3, :minor)
chord(:E3, :dim7)
chord(:E3, :dom7)
Мы можем легко превратить аккорды в арпеджио с функцией play_pattern
:
play_pattern chord(:E3, :m7)
Хорошо, но не очень весело - она играла очень медленно. play_pattern
будет играть каждую ноту из списка, вызывая sleep 1
после каждого вызова play
. Мы можем использовать другую функцию - play_pattern_timed
, чтобы устанавливать наше собственное время и повысить скорость:
play_pattern_timed chord(:E3, :m7), 0.25
Мы можем также передавать разное время в виде списка, который будет рассматриваться как кольцо:
play_pattern_timed chord(:E3, :m13), [0.25, 0.5]
Это эквивалентно:
play 52
sleep 0.25
play 55
sleep 0.5
play 59
sleep 0.25
play 62
sleep 0.5
play 66
sleep 0.25
play 69
sleep 0.5
play 73
Что бы вы предпочли написать?
Sonic Pi поддерживает широкий диапазон гамм. Как насчет сыграть мажорную гамму от ноты С3?
play_pattern_timed scale(:c3, :major), 0.125, release: 0.1
Мы даже можем запросить больше октав:
play_pattern_timed scale(:c3, :major, num_octaves: 3), 0.125, release: 0.1
Как насчёт пентатоники?
play_pattern_timed scale(:c3, :major_pentatonic, num_octaves: 3), 0.125, release: 0.1
Использование аккордов и гамм - отличный способ ограничить случайный выбор для получения чего-то более осмысленного. Поиграйте с примером, который выбирает случайные ноты из минорного аккорда:
use_synth :tb303
loop do
play choose(chord(:E3, :minor)), release: 0.3, cutoff: rrand(60, 120)
sleep 0.25
end
Попробуйте другие названия аккордов и диапазоны частот среза фильтра.
Чтобы выяснить, какие гаммы и аккорды поддерживаются Sonic Pi, просто нажмите кнопку lang внизу слева от этого учебника и затем выберите аккорд или гамму в списке. Прокручивайте вниз информацию в главной панели, пока не увидите длинный список аккордов или гамм (в зависимости от того, что вы ищете).
Получайте удовольствие и помните: здесь нет ошибок, только возможности.
Интересной разновидностью списков являются кольца. Если вы раньше занимались программированием, то наверняка сталкивались с кольцевым буфером или кольцевым массивом. Здесь мы узнаем про кольцевой список - это быстро и просто.
В предыдущем разделе о списках мы видели, как можно извлечь из них элементы, используя механизм индексирования:
puts [52, 55, 59][1]
Так, а что произойдет, если вы захотите извлечь индекс 100
? Там явно нет элемента с индексом 100, так как у нашего списка только три элемента. Поэтому Sonic Pi вернет вам nil
, что означает “ничто”.
Однако, представьте, что у вас есть счётчик, который, подобно номеру текущего такта, постоянно увеличивается. Давайте создадим наш счётчик и наш список:
counter = 0
notes = [52, 55, 59]
Теперь мы можем использовать счётчик, чтобы получить доступ к нотам в нашем списке:
puts notes[counter]
Здорово, мы получили 52
. Теперь давайте увеличим наш счётчик и получим другую ноту:
counter = (inc counter)
puts notes[counter]
Супер, теперь мы получили 55
и если мы сделаем так снова, получим 59
. Однако, если после этого мы увеличим счётчик ещё раз, мы выйдем за пределы нашего списка и получим nil
. Но что, если нам нужно вернуться к началу списка и снова начинать извлекать элементы по кругу? Вот для этого и нужны кольца.
Мы можем создавать кольца одним из двух способов. Либо используем функцию ring
с элементами кольца в качестве параметров:
(ring 52, 55, 59)
Либо можем взять обычный список и преобразовать его в кольцо, передав ему .ring
сообщение:
[52, 55, 59].ring
Как только мы получили кольцо, мы можем использовать его точно так же, как если бы мы использовали обычный список, за исключением того, что можно использовать отрицательные индексы и индексы, превышающие размеры кольца. И они будут повторяться по кругу и всегда указывать на один из элементов кольца:
(ring 52, 55, 59)[0] #=> 52
(ring 52, 55, 59)[1] #=> 55
(ring 52, 55, 59)[2] #=> 59
(ring 52, 55, 59)[3] #=> 52
(ring 52, 55, 59)[-1] #=> 59
Допустим, мы используем переменную для хранения номера текущего такта. Мы можем использовать её в качестве индекса в нашем кольце для извлечения нот, или времени затухания или чего-нибудь полезного, что мы сохранили в нашем кольце, независимо от номера такта, в котором мы находимся в данный момент.
Полезно знать, что списки, возвращаемые scale
и chord
также являются кольцами и позволяют получить к ним доступ с помощью произвольных индексов.
В дополнение к ring
существует ряд других функций, которые будут создавать кольца для нас.
range
предлагает вам указать начальную точку, конечную точку и размер шага.
bools
позволяет использовать 1
и 0
, чтобы кратко представлять логические переменные.
knit
позволяет связывать в последовательности повторяющиеся значения.
spread
создает кольцо логических значений с Евклидовым распределением.
Взгляните на их соответствующую документацию для получения дополнительной информации.
Помимо конструкторов, таких как range
и spread
, есть и другой способ создания новых колец - манипулирование существующими кольцами.
Чтобы исследовать это, возьмём простое кольцо:
(ring 10, 20, 30, 40, 50)
Что, если мы хотим перевернуть его задом наперёд? Тогда мы используем связанный метод .reverse
, который разворачивает кольцо в обратную сторону:
(ring 10, 20, 30, 40, 50).reverse #=> (ring 50, 40, 30, 20, 10)
Так, а что, если мы захотим получить первые три элемента кольца?
(ring 10, 20, 30, 40, 50).take(3) #=> (ring 10, 20, 30)
И, наконец, что, если мы захотим перемешать элементы кольца?
(ring 10, 20, 30, 40, 50).shuffle #=> (ring 40, 30, 10, 50, 20)
Это очень мощный способ создания новых колец. Однако, реальная мощь приходит, когда вы связываете несколько методов вместе.
Как на счёт перемешать кольцо, отбросить 1 элемент, а затем получить первые 3 элемента?
Давайте сделаем это в несколько этапов:
(ring 10, 20, 30, 40, 50)
- наше исходное кольцо
(ring 10, 20, 30, 40, 50).shuffle
- перемешиваем - (ring 40, 30, 10, 50, 20)
(ring 10, 20, 30, 40, 50).shuffle.drop(1)
- отбрасываем 1 элемент - (ring 30, 10, 50, 20)
(ring 10, 20, 30, 40, 50).shuffle.drop(1).take(3)
- берём 3 первых элемента - (ring 30, 10, 50)
Как вы видете, можно создавать длинные связки из этих методов просто склеивая их вместе. Мы можем комбинировать их в любом порядке, в каком захотим, создавая чрезвычайно богатый и мощный способ генерации новых колец из существующих.
Наши кольца обладают одним очень важным свойством. Оно заключается в том, что связанные методы, описанные в этом разделе не изменяют кольца, они возвращают новые кольца. Это значит, что вы можете разделять кольцо между потоками выполнения и использовать разные методы в разных потоках, зная, что не повлияете на другой поток, использующий то же самое кольцо.
Вот список доступных для вас связанных методов:
.reverse
- возвращает отсортированную в обратном порядке версию кольца
.sort
- возвращает отсортированную версию кольца
.shuffle
- возвращает отсортированную в случайном порядке версию кольца
.pick(3)
- returns a ring with the results of calling .choose
3 times
.pick
- similar to .pick(3)
only the size defaults to the same as the original ring
.take(5)
- возвращает кольцо, содержащее первые 5 элементов исходного кольца
.drop(3)
- возвращает кольцо, содержащее все элементы исходного кольца, кроме первых 3 элементов
.butlast
- возвращает новое кольцо с отсутствующим последним элементом
.drop_last(3)
- возвращает новое кольцо с отсутствующими 3 последними элементами
.take_last(6)
- возвращает кольцо, содержащее последние 6 элементов исходного кольца
.stretch(2)
- повторяет каждый элемент кольца дважды
.repeat(3)
- повторяет всё кольцо 3 раза
.mirror
- добавляет кольцо к перевёрнутой версии себя
.reflect
- то же, что и .mirror
, но не дублирует средний элемент
.scale(2)
- returns a new ring with all elements multiplied by 2 (assumes ring contains numbers only)
Конечно, связанные методы, принимающие числовые аргументы, могут принимать и другие числа! Так что не стесняйтесь вызывать .drop(5)
вместо .drop(3)
, если вы хотите отбросить первые 5 элементов кольца.
Один из наиболее интересных аспектов Sonic Pi заключается в том, что он позволяет писать и модифицировать код вживую, играть музыку, как если бы вы выступали с гитарой. Одно из преимуществ такого подхода состоит в том, что он даёт вам больше обратной связи во время сочинения (просто начни выполнение цикла и продолжай настройку до тех пор, пока звук не станет идеальным). Однако, главное преимущество заключается в том, что вы можете взять Sonic Pi на сцену и выступать с ним.
В этом разделе мы рассмотрим основы превращения статического кода ваших сочинений в динамичное исполнение.
Держитесь за своё кресло…
Теперь мы узнали достаточно, чтобы начать по-настоящему развлекаться. В этом разделе мы будем черпать из всех предыдущих разделов и узнаем, как вы сможете делать свои музыкальные композиции живыми и превращать их в выступления. Для этого нам понадобится 3 основных компонента:
Умение писать код, который создаёт звук - ПРОВЕРЬТЕ! Умение писать функции - ПРОВЕРЬТЕ! Умение использовать (именовать) потоки - ПРОВЕРЬТЕ!
Чудненько, давайте начнём. Будем вживую кодировать наши первые звуки. Начнем с простого. Сначала нам нужна функция, содержащая код, который мы хотим проиграть. Ещё мы хотим зациклить вызовы этой функции в отдельном потоке:
define :my_loop do
play 50
sleep 1
end
in_thread(name: :looper) do
loop do
my_loop
end
end
Если это выглядит для вас довольно сложным, вернитесь и перечитайте разделы о функциях и потоках. Это не сложно, если раньше вы уже прокручивал эти вещи у себя в голове.
То, что мы здесь имеем - это определение функции, которая всего лишь играет ноту 50 и немного ждёт. Затем мы определили именованную нить, названную :looper
, которая бесконечно выполняет цикл с функцией my_loop
.
Если вы выполните этот код, то услышите ноту 50 повторяющуюся снова и снова…
Итак, вот где начинается веселье. В то время, как код по-прежнему выполняется, изменим 50 на другое число, скажем, на 55, и снова нажмём кнопку “Выполнить”. Ого! Всё изменилось! Вживую!
Это не добавило новый звуковой слой, так как мы используем именованные потоки выполнения, которые допускают только один поток для каждого имени. Кроме того, звук изменился, потому что мы переопределили функцию. Мы дали :my_loop
новое определение. Когда нить :looper
повторяла цикл, она просто вызвала новое определение.
Попробуйте изменить это снова - измените ноту, измените время сна. Как на счёт добавления инструкции use_synth
? Например, измените код на:
define :my_loop do
use_synth :tb303
play 50, release: 0.3
sleep 0.25
end
Сейчас это звучит довольно интересно, но мы можем оживить это ещё больше. Вместо того, чтобы играть одну и ту же ноту снова и снова, попробуем сыграть аккорд:
define :my_loop do
use_synth :tb303
play chord(:e3, :minor), release: 0.3
sleep 0.5
end
Как на счёт воспроизведения случайных нот из аккорда:
define :my_loop do
use_synth :tb303
play choose(chord(:e3, :minor)), release: 0.3
sleep 0.25
end
Или использования случайных значений частоты среза:
define :my_loop do
use_synth :tb303
play choose(chord(:e3, :minor)), release: 0.2, cutoff: rrand(60, 130)
sleep 0.25
end
Наконец, добавим немного барабанов:
define :my_loop do
use_synth :tb303
sample :drum_bass_hard, rate: rrand(0.5, 2)
play choose(chord(:e3, :minor)), release: 0.2, cutoff: rrand(60, 130)
sleep 0.25
end
Теперь это становится увлекательным!
Однако, прежде чем вы начнёте живое кодирование с функциями и потоками, остановите то, что вы сейчас делаете, и прочтите следующий раздел live_loop
, который изменит манеру вашего кодирования в Sonic Pi навсегда…
Окей, этот раздел учебника - настоящая жемчужина. Если вы решите прочесть только один раздел, это должен быть именно этот раздел. Если вы прочли предыдущий раздел - основы лайвкодинга, live_loop
- это простой способ сделать всё то же самое, но без необходимости писать так много кода.
Если вы не читали предыдущий раздел, live_loop
- это лучший способ поджемовать с Sonic Pi.
Давайте поиграем. Наберите следующее в новом буфере:
live_loop :foo do
play 60
sleep 1
end
Теперь нажмите кнопку “Выполнить”. Вы услышите базовый звуковой сигнал на каждую долю такта. Здесь нет ничего весёлого. Однако, пока не нажимайте “Остановить”. Измените 60
на 65
и нажмите “Выполнить” снова.
Ого! Тон изменился автоматически без пропуска доли. Это и есть лайвкодинг.
Почему бы не изменить звук так, чтобы он больше походил на бас? Просто обновите свой код пока тот играет:
live_loop :foo do
use_synth :prophet
play :e1, release: 8
sleep 8
end
Затем нажмите “Выполнить”.
Давайте добавим изменяющуюся частоту среза:
live_loop :foo do
use_synth :prophet
play :e1, release: 8, cutoff: rrand(70, 130)
sleep 8
end
Нажмите “Выполнить” снова.
Добавьте какие-нибудь барабаны:
live_loop :foo do
sample :loop_garzul
use_synth :prophet
play :e1, release: 8, cutoff: rrand(70, 130)
sleep 8
end
Измените ноту с e1
на c1
:
live_loop :foo do
sample :loop_garzul
use_synth :prophet
play :c1, release: 8, cutoff: rrand(70, 130)
sleep 8
end
Теперь хватит слушать меня и начните играь сами! Веселись!
Рассмотрим следующий живой цикл:
live_loop :foo do
play 50
sleep 1
end
Вы, наверное, заинтересовались, почему он называется :foo
. Это имя имеет значение, поскольку оно показывает, что этот живой цикл отличается от всех других живых циклов.
Не может быть двух живых циклов, выполняющихся под одним именем.
Это значит, что если мы хотим иметь несколько одновременно выполняющихся циклов, нам просто нужно дать им разные имена:
live_loop :foo do
use_synth :prophet
play :c1, release: 8, cutoff: rrand(70, 130)
sleep 8
end
live_loop :bar do
sample :bd_haus
sleep 0.5
end
Теперь можно обновлять и изменять каждый живой цикл отдельно и всё будет прекрасно работать.
Есть один важный момент, который вы, наверное, уже заметили - все живые циклы автоматически работают с механизмом распарралеливания потоков, который мы исследовали ранее. Всякий раз, когда живой цикл начинает выполняться, он генерирует новое cue
событие с названием цикла. Следовательно, мы можем sync
эти сигналы, чтобы гарантировать нашим циклам синхронизацию без необходимости что-либо останавливать.
Рассмотрим этот плохо синхронизированный код:
live_loop :foo do
play :e4, release: 0.5
sleep 0.4
end
live_loop :bar do
sample :bd_haus
sleep 1
end
Давайте посмотрим, сможем ли мы исправить тайминг и синхронизировать его без остановки воспроизведения. Сначала исправим время сна в цикле :foo
на что-то вроде 0.5
:
live_loop :foo do
play :e4, release: 0.5
sleep 0.5
end
live_loop :bar do
sample :bd_haus
sleep 1
end
Но мы ещё не совсем закончили, ведь вы заметили, что такты пока не выстроились правильно. Это происходит потому, что циклы не в фазе. Давайте исправим это, синхронизировав один цикл с другим:
live_loop :foo do
play :e4, release: 0.5
sleep 0.5
end
live_loop :bar do
sync :foo
sample :bd_haus
sleep 1
end
Ого, теперь всё прекрасно синхронизируется по времени и всё это без остановки.
А теперь вперёд! Кодируйте вживую с живыми петлями!
Однажды, кодируя вживую, вы, вероятно, найдёте себя делающими множество вещей с помощью циклов, основанных на кольцах. Вы будете помещать в кольца мелодии, ритмы, последовательности аккордов, тембральные изменения и т.д и т.п.
Sonic Pi предоставляет очень удобный инструмент для работы с кольцами внутри live_loop
. Он называется системой тиков. В разделе о кольцах мы говорили о счётчике, который постоянно увеличиватся, подобно номеру текущего такта. Тик как раз и реализует эту идею. Давайте рассмотрим примеры:
counter = 0
live_loop :arp do
play (scale :e3, :minor_pentatonic)[counter], release: 0.1
counter += 1
sleep 0.125
end
Это эквивалентно:
live_loop :arp do
play (scale :e3, :minor_pentatonic).tick, release: 0.1
sleep 0.125
end
Здесь мы просто берём минорную пентатонику, построенную от e3, и перебираем каждый элемент кольца. Это делается путём добавления .tick
в конец объявления гаммы. Тик является локальным для живого цикла, поэтому каждый живой цикл может иметь свой собственный, независимый тик:
live_loop :arp do
play (scale :e3, :minor_pentatonic).tick, release: 0.1
sleep 0.125
end
live_loop :arp2 do
use_synth :dsaw
play (scale :e2, :minor_pentatonic, num_octaves: 3).tick, release: 0.25
sleep 0.25
end
Вы также можете вызвать tick
как стандартную функцию и использовать возвращаемое ею значение в качестве индекса:
live_loop :arp do
idx = tick
play (scale :e3, :minor_pentatonic)[idx], release: 0.1
sleep 0.125
end
Однако, гораздо изящнее вызывать .tick
в конце объявления кольцевого списка. Функция tick
полезна, когда вы хотите сделать что-то необычное с величиной тика или, если нужно, использовать тики для чего-то отличного от индексирования в кольцах.
Магическая особенность тика заключается в том, что он не только возвращает новый индекс (или элемент кольцевого списка по этому индексу), он также гарантирует, что в следующий раз, когда вы вызовете тик, вы получите следующее значение. Посмотрите примеры для функции tick
в разделе “Язык” документации, чтобы узнать о различных способах работы с ней. Впрочем, сейчас необходимо отметить, что иногда вам захочется просто посмотреть на текущее значение тика и не увеличивать его. Это доступно с помощью Функции look
. Вы можете вызвать look
как стандартную функцию путем добавления .look
в конец кольцевого списка.
Наконец, иногда вам может потребоваться использовать более одного тика внутри живой петли. Это достигается путем предоставления вашему тику имени:
live_loop :arp do
play (scale :e3, :minor_pentatonic).tick(:foo), release: 0.1
sleep (ring 0.125, 0.25).tick(:bar)
end
Здесь мы используем два тика - один для игры нот, другой для времени сна. Так как они оба находятся в одной и той же живой петле, чтобы держать их раздельно, нам нужно дать им уникальные имена. Это тот же самое, что и именование live_loop
- мы просто передаем функции аргумент с префиксом :
. В примере выше мы назвали один тик :foo
а другой :bar
. Если мы хотим применить функцию look
к одному из тиков, нам нужно передать имя тика функции look
.
Большая часть мощности системы тиков не будет полезной, пока вы ещё только начинаете осваивать Sonic Pi. Не пытайтесь сразу выучить всё в этом разделе. Просто сосредоточьтесь на одном тике для одного кольца. Это даст вам больше радости и простоты применения тиков для колец в твоих live_loop
.
Взгляните на документацию для функции tick
, там много полезных примеров. Счастливых тиков!
Often it is useful to have information that is shared across multiple threads or live loops. For example, you might want to share a notion of the current key, BPM or even more abstract concepts such as the current ‘complexity’ (which you’d potentially interpret in different ways across different threads). We also don’t want to lose any of our existing determinism guarantees when doing this. In other words, we’d still like to be able to share code with others and know exactly what they’ll hear when they run it. At the end of Section 5.6 of this tutorial we briefly discussed why we should not use variables to share information across threads due to a loss of determinism (in turn due to race conditions).
Sonic Pi’s solution to the problem of easily working with global variables in a deterministic way is through a novel system it calls Time State. This might sound complex and difficult (in fact, in the UK, programming with multiple threads and shared memory is typically a university level subject). However, as you’ll see, just like playing your first note, Sonic Pi makes it incredibly simple to share state across threads whilst still keeping your programs thread-safe and deterministic..
Meet get
and set
…
Sonic Pi has a global memory store called Time State. The two main things you do with it are to set
information and get
information. Let’s dive deeper…
To store information into the Time State we need two things:
the information we want to store, a unique name (key) for the information.
For example, we might want to store the number 3000
with the key :intensity
. This is possible using the set
function:
set :intensity, 3000
We can use any name for our key. If information has already been stored with that key, our new set
will override it:
set :intensity, 1000
set :intensity, 3000
In the above example, as we stored both numbers under the same key, the last call to set
‘wins’, so the number associated with :intensity
will be 3000
as the first call to set
is effectively overridden.
To fetch information from the Time State we just need the key we used to set
it, which in our case is :intensity
. We then just need to call get[:intensity]
which we can see by printing out the result to the log:
print get[:intensity] #=> prints 3000
Notice that calls to get
can return information that was set
in a previous run. Once a piece of information has been set
it is available until either the information is overridden (just like we clobbered the :intensity
value of 1000
to 3000
above) or Sonic Pi is closed.
The main benefit of the Time State system is that it can be safely used across threads or live loops. For example, you could have one live loop setting information and another one getting it:
live_loop :setter do
set :foo, rrand(70, 130)
sleep 1
end
live_loop :getter do
puts get[:foo]
sleep 0.5
end
The nice thing about using get
and set
across threads like this is that it will always produce the same result every time you hit run. Go on, try it. See if you get the following in your log:
{run: 0, time: 0.0}
└─ 125.72265625
{run: 0, time: 0.5}
└─ 125.72265625
{run: 0, time: 1.0}
└─ 76.26220703125
{run: 0, time: 1.5}
└─ 76.26220703125
{run: 0, time: 2.0}
└─ 114.93408203125
{run: 0, time: 2.5}
└─ 114.93408203125
{run: 0, time: 3.0}
└─ 75.6048583984375
{run: 0, time: 3.5}
└─ 75.6048583984375
~~~~
Try running it a few times - see, it's the same every time. This is
what we call deterministic behaviour and it's really very important
when we want to share our music as code and know that the person
playing the code is hearing exactly what we wanted them to hear (just
like playing an MP3 or internet stream sounds the same for all
listeners).
## A Simple Deterministic State System
Back in Section 5.6 we discussed why using variables across threads can
lead to random behaviour. This stops us from being able to reliably
reproduce code such as this:
## An Example of Non-Deterministic Behaviour ## (due to race conditions caused by multiple ## live loops manipulating the same variable ## at the same time). ## ## If you run this code you’ll notice ## that the list that’s printed is ## not always sorted! a = (ring 6, 5, 4, 3, 2, 1)
live_loop :shuffled do a = a.shuffle sleep 0.5 end
live_loop :sorted do a = a.sort sleep 0.5 puts “sorted: “, a end ~~~~
Let’s take a look at how this might look using get
and set
:
## An Example of Deterministic Behaviour
## (despite concurrent access of shared state)
## using Sonic Pi's new Time State system.
##
## When this code is executed, the list that's
## printed is always sorted!
set :a, (ring 6, 5, 4, 3, 2, 1)
live_loop :shuffled do
set :a, get[:a].shuffle
sleep 0.5
end
live_loop :sorted do
set :a, get[:a].sort
sleep 0.5
puts "sorted: ", get[:a]
end
Notice how this code is pretty much identical to the version using a variable before it. However when you run the code, it behaves as you would expect with any typical Sonic Pi code - it does the same thing every time in this case thanks to the Time State system.
Therefore, when sharing information across live loops and threads, use get
and set
instead of variables for deterministic, reproducible behaviour.
Section 5.7 introduced the functions cue
and sync
when dealing with the issue of synchronising threads. What it didn’t explain was that it is the Time State system which provides this functionality. It just so happens that set
is actually a variation of cue
and is built on top of the same core functionality which is to insert information into the Time State system. Additionally, sync
is also designed in such a way that it works seamlessly with Time State - any information that we plan to store in Time State we can sync on. In other words - we sync
on events yet to be inserted into Time State.
Let’s take a quick look at how to use sync
to wait for new events to be added to Time State:
in_thread do
sync :foo
sample :ambi_lunar_land
end
sleep 2
set :foo, 1
In this example first we create a thread which waits for a :foo
event to be added to the Time State. After this thread declaration we sleep for 2 beats and then set
:foo
to be 1
. This then releases the sync
which then moves to the next line which is to trigger the :ambi_lunar_land
sample.
Note that sync
always waits for future events and that it will block the current thread waiting for a new event. Also, it will inherit the logical time of the thread which triggered it via set
or cue
so it may also be used to sync time.
In the example above we set :foo
to 1
which we did nothing with. We can actually get this value from the thread calling sync
:
in_thread do
amp = sync :foo
sample :ambi_lunar_land, amp: amp
end
sleep 2
set :foo, 0.5
Note that values that are passed through set
and cue
must be thread safe - i.e. immutable rings, numbers, symbols or frozen strings. Sonic Pi will throw an error if the value you are attempting to store in the Time State is not valid.
When getting and setting information into the Time State, it’s possible to use more complex keys than basic symbols such as :foo
and :bar
. You can also use URL style strings called paths such as "/foo/bar/baz"
. Once we start working with paths, we can then start taking advantage of Sonic Pi’s sophisticated pattern matching system to get
and sync
with ‘similar’ rather than ‘identical’ paths. Let’s take a look.
Let’s assume we want to wait for the next event that has three path segments:
sync "/*/*/*"
This will match any Time State event with exactly three path segments, regardless of their names. For example:
cue "/foo/bar/baz"
cue "/foo/baz/quux"
cue "/eggs/beans/toast"
cue "/moog/synths/rule"
However, it will not match paths with fewer or more path segments. The following will not match:
cue "/foo/bar"
cue "/foo/baz/quux/quaax"
cue "/eggs"
Each *
means any content. So we could match paths with just one segment with /*
or paths with five segments with /*/*/*/*/*
If we know what the segment is going to start or finish with, we can use a *
in addition to a partial segment name. For example: "/foo/b*/baz"
will match any path that has three segments, the first of which is foo
, the last baz
and the middle segment can be anything that starts with b
. So, it would match:
cue "/foo/bar/baz"
cue "/foo/baz/baz"
cue "/foo/beans/baz"
However, it wouldn’t match the following:
cue "/foo/flibble/baz"
cue "/foo/abaz/baz"
cue "/foo/beans/baz/eggs"
You can also place the *
at the start of the segment to specify the last characters of a segment: "/foo/*zz/baz"
which will match any 3 segment cue
or set
where the first segment is foo
, the last is baz
and the middle segment ends with zz
such as "cue "/foo/whizz/baz"
.
Sometimes you don’t know how many path segments you want to match. In these cases you can use the powerful double star: **
such as "/foo/**/baz"
which will match:
cue "/foo/bar/baz"
cue "/foo/bar/beans/baz"
cue "/foo/baz"
cue "/foo/a/b/c/d/e/f/baz"
You can use the ?
character to match against a single char such as "/?oo/bar/baz"
which will match:
cue "/foo/bar/baz"
cue "/goo/bar/baz"
cue "/too/bar/baz"
cue "/woo/bar/baz"
If you know that a segment may be one of a select number of words, you can use the {
and }
matchers to specify a list of choices such as "/foo/{bar,beans,eggs}/quux"
which will only match the following:
cue "/foo/bar/quux"
cue "/foo/beans/quux"
cue "/foo/eggs/quux"
Finally, you can match against a selection of letters if you use the [
and ]
matchers to specify a list of choices such as "/foo/[abc]ux/baz"
which will match only:
cue "/foo/aux/baz"
cue "/foo/bux/baz"
cue "/foo/cux/baz"
You can also use the -
character to specify ranges of letters. For example "/foo/[a-e]ux/baz"
which will match only:
cue "/foo/aux/baz"
cue "/foo/bux/baz"
cue "/foo/cux/baz"
cue "/foo/dux/baz"
cue "/foo/eux/baz"
When calling sync
or get
you are free to combine matchers in any order you see fit to powerfully match any Time State event created by cue
or set
. Let’s look at a crazy example:
in_thread do
sync "/?oo/[a-z]*/**/ba*/{quux,quaax}/"
sample :loop_amen
end
sleep 1
cue "/foo/beans/a/b/c/d/e/bark/quux/"
For those curious, these matching rules are based on the Open Sound Control pattern matching specification which is explained in detail here: http://opensoundcontrol.org/spec-1_0
Once you’ve mastered converting code to music, you might wonder - what’s next? Sometimes the constraints of working purely within Sonic Pi’s syntax and sound system can be exciting and put you into a new creative position. However, sometimes it is essential to break out of the code into the real world. We want two extra things:
To be able to convert actions in the real world into Sonic Pi events to code with To be able to use Sonic Pi’s strong timing model and semantics to control and manipulate objects in the real world
Luckily there’s a protocol that’s been around since the 80s that enables exactly this kind of interaction - MIDI. There’s an incredible number of external devices including keyboards, controllers, sequencers, and pro audio software that all support MIDI. We can use MIDI to receive data and also use it to send data.
Sonic Pi provides full support for the MIDI protocol enabling you to connect your live code to the real world. Let’s explore it further…
In this section we will learn how to connect a MIDI controller to send events into Sonic Pi to control our synths and sounds. Go and grab a MIDI controller such as a keyboard or control surface and let’s get physical!
In order to get information from an external MIDI device into Sonic Pi we first need to connect it to our computer. Typically this will be via a USB connection, although older equipment will have a 5-pin DIN connector for which you’ll need hardware support for your computer (for example, some sound cards have MIDI DIN connectors). Once you’ve connected your device, launch Sonic Pi and take a look at the IO section of the Preferences panel. You should see your device listed there. If not, try hitting the ‘Reset MIDI’ button and see if it appears. If you’re still not seeing anything, the next thing to try is to consult your operating system’s MIDI config to see if it sees your device. Failing all that, feel free to ask questions in the public chat room: http://gitter.im/samaaron/sonic-pi
Once your device is connected, Sonic Pi will automatically receive events. You can see for yourself by manipulating your MIDI device and looking at the cue logger in the bottom right of the application window below the log (if this isn’t visible go to Preferences->Editor->Show & Hide and enable the ‘Show cue log’ tickbox). You’ll see a stream of events such as:
/midi/nanokey2_keyboard/0/1/note_off [55, 64]
/midi/nanokey2_keyboard/0/1/note_on [53, 102]
/midi/nanokey2_keyboard/0/1/note_off [57, 64]
/midi/nanokey2_keyboard/0/1/note_off [53, 64]
/midi/nanokey2_keyboard/0/1/note_on [57, 87]
/midi/nanokey2_keyboard/0/1/note_on [55, 81]
/midi/nanokey2_keyboard/0/1/note_on [53, 96]
/midi/nanokey2_keyboard/0/1/note_off [55, 64]
Once you can see a stream of messages like this, you’ve successfully connected your MIDI device. Congratulations, let’s see what we can do with it!
These events are broken into two sections. Firstly there’s the name of the event such as /midi/nanokey2_keyboard/0/1/note_on
and secondly there’s the values of the event such as [18, 62]
. Interestingly, these are the two things we need to store information in Time State. Sonic Pi automatically inserts incoming MIDI events into Time State. This means you can get
the latest MIDI value and also sync
waiting for the next MIDI value using everything we learned in section 10 of this tutorial.
Now we’ve connected a MIDI device, seen its events in the cue log and discovered that our knowledge of Time State is all we need to work with the events, we can now start having fun. Let’s build a simple MIDI piano:
live_loop :midi_piano do
note, velocity = sync "/midi/nanokey2_keyboard/0/1/note_on"
synth :piano, note: note
end
There’s a few things going on in the code above including some issues. Firstly, we have a simple live_loop
which will repeat forever running the code between the do
/end
block. This was introduced in Section 9.2. Secondly, we’re calling sync
to wait for the next matching Time State event. We use a string representing the MIDI message we’re looking for (which is the same as was displayed in the cue logger). Notice that this long string is provided to you by Sonic Pi’s autocompletion system, so you don’t have to type it all out by hand. In the log we saw that there were two values for each MIDI note on event, so we assign the result to two separate variables note
and velocity
. Finally we trigger the :piano
synth passing our note.
Now, you try it. Type in the code above, replace the sync key with a string matching your specific MIDI device and hit Run. Hey presto, you have a working piano! However, you’ll probably notice a couple of problems: firstly all the notes are the same volume regardless of how hard you hit the keyboard. This can be easily fixed by using the velocity MIDI value and converting it to an amplitude. Given that MIDI has a range of 0->127, to convert this number to a value between 0->1 we just need to divide it by 127:
live_loop :midi_piano do
note, velocity = sync "/midi/nanokey2_keyboard/0/1/note_on"
synth :piano, note: note, amp: velocity / 127.0
end
Update the code and hit Run again. Now the velocity of the keyboard is honoured. Next, let’s get rid of that pesky pause.
Before we can remove the pause, we need to know why it’s there. In order to keep all the synths and FX well-timed across a variety of differently capable CPUs, Sonic Pi schedules the audio in advance by 0.5s by default. (Note that this added latency can be configured via the fns set_sched_ahead_time!
and use_sched_ahead_time
). This 0.5s latency is being added to our :piano
synth triggers as it is added to all synths triggered by Sonic Pi. Typically we really want this added latency as it means all synths will be well timed. However, this only makes sense for synths triggered by code using play
and sleep
. In this case, we’re actually triggering the :piano
synth with our external MIDI device and therefore don’t want Sonic Pi to control the timing for us. We can turn off this latency with the command use_real_time
which disables the latency for the current thread. This means you can use real time mode for live loops that have their timing controlled by sync
ing with external devices, and keep the default latency for all other live loops. Let’s see:
live_loop :midi_piano do
use_real_time
note, velocity = sync "/midi/nanokey2_keyboard/0/1/note_on"
synth :piano, note: note, amp: velocity / 127.0
end
Update your code to match the code above and hit Run again. Now we have a low latency piano with variable velocity coded in just 5 lines. Wasn’t that easy!
Finally, as our MIDI events are going straight into the Time State, we can also use the get
fn to retrieve the last seen value. This doesn’t block the current thread and returns nil
if there’s no value to be found (which you can override by passing a default value - see the docs for get
). Remember that you can call get
in any thread at any time to see the latest matching Time State value. You can even use time_warp
to jump back in time and call get
to see past events…
The exciting thing now is that you can now use the same code structures to sync
and get
MIDI information from any MIDI device and do whatever you want with the values. You can now choose what your MIDI device will do!
In addition to receiving MIDI events we can also send out MIDI events to trigger and control external hardware synths, keyboards and other devices. Sonic Pi provides a full set of fns for sending various MIDI messages such as:
Note on - midi_note_on
Note off - midi_note_off
Control change - midi_cc
Pitch bend - midi_pitch_bend
Clock ticks - midi_clock_tick
There are many other supported MIDI messages too - check out the API documentation for all the other fns that start with midi_
.
In order to send a MIDI message to an external device, we must first have connected it. Check out the subsection ‘Connecting a MIDI Controller’ in section 11.1 for further details. Note that if you’re using USB, connecting to a device which you’re sending to (rather than receiving from) is the same procedure. However, if you’re using the classic DIN connectors, make sure you connect to the MIDI out port of your computer. You should see your MIDI device listed in the preferences pane.
The many midi_*
fns work just like play
, sample
and synth
in that they send a message at the current (logical) time. For example, to spread out calls to the midi_*
fns you need to use sleep
just like you did with play
. Let’s take a look:
midi_note_on :e3, 50
This will send a MIDI note on event to the connected MIDI device with velocity 50. (Note that Sonic Pi will automatically convert notes in the form :e3
to their corresponding MIDI number such as 52 in this case.)
If your connected MIDI device is a synthesiser, you should be able to hear it playing a note. To disable it use midi_note_off
:
midi_note_off :e3
By default, Sonic Pi will send each MIDI message to all connected devices on all MIDI channels. This is to make it easy to work with a single connected device without having to configure anything. However, sometimes a MIDI device will treat MIDI channels in a special way (perhaps each note has a separate channel) and also you may wish to connect more than one MIDI device at the same time. In more complicated setups, you may wish to be more selective about which MIDI device receives which message(s) and on which channel.
We can specify which device to send to using the port:
opt, using the device name as displayed in the preferences:
midi_note_on :e3, port: "moog_minitaur"
We can also specify which channel to send to using the channel:
opt (using a value in the range 1-16):
midi_note_on :e3, channel: 3
Of course we can also specify both at the same time to send to a specific device on a specific channel:
midi_note_on :e3, port: "moog_minitaur", channel: 5
Finally, a really fun thing to do is to connect the audio output of your MIDI synthesiser to one of the audio inputs of your soundcard. You can then control your synth with code using the midi_*
fns and also manipulate the audio using live_audio
and FX:
with_fx :reverb, room: 1 do
live_audio :moog
end
live_audio :moog_trigger do
use_real_time
midi (octs :e1, 3).tick, sustain: 0.1
sleep 0.125
end
(The fn midi
is available as a handy shortcut to sending both note on and note off events with a single command. Check out its documentation for further information).
In addition to MIDI, another way to get information in and out of Sonic Pi is via the network using a simple protocol called OSC - Open Sound Control. This will let you send messages to and from external programs (both running on your computer and on external computers) which opens up the potential for control way beyond MIDI which has limitations due to its 1980s design.
For example, you could write a program in another programming language which sends and receives OSC (there are OSC libraries for pretty much every common language) and work directly with Sonic Pi. What you can use this for is only limited by your imagination.
By default when Sonic Pi is launched it listens to port 4559 for incoming OSC messages from programs on the same computer. This means that without any configuration, you can send Sonic Pi an OSC message and it will be displayed in the cue log just like incoming MIDI messages. This also means that any incoming OSC message is also automatically added to the Time State which means you can also use get
and sync
to work with the incoming data - just like with MIDI.
Let’s build a basic OSC listener:
live_loop :foo do
use_real_time
a, b, c = sync "/osc/trigger/prophet"
synth :prophet, note: a, cutoff: b, sustain: c
end
In this example we described an OSC path "/osc/trigger/prophet"
which we’re syncing on. This can be any valid OSC path (all letters and numbers are supported and the /
is used like in a URL to break up the path to multiple words). The /osc
prefix is added by Sonic Pi to all incoming OSC messages, so we need to send an OSC message with the path /trigger/prophet
for our sync
to stop blocking and the prophet synth to be triggered.
We can send OSC to Sonic Pi from any programming language that has an OSC library. For example, if we’re sending OSC from Python we might do something like this:
from pythonosc import osc_message_builder
from pythonosc import udp_client
sender = udp_client.SimpleUDPClient('127.0.0.1', 4559)
sender.send_message('/trigger/prophet', [70, 100, 8])
Or, if we’re sending OSC from Clojure we might do something like this from the REPL:
(use 'overtone.core)
(def c (osc-client "127.0.0.1" 4559))
(osc-send c "/trigger/prophet" 70 100 8)
For security reasons, by default Sonic Pi does not let remote machines send it OSC messages. However, you can enable support for remote machines in Preferences->IO->Network->Receive Remote OSC Messages. Once you’ve enabled this, you can receive OSC messages from any computer on your network. Typically the sending machine will need to know your IP address (a unique identifier for your computer on your network - kind of like a phone number or an email address). You can discover the IP address of your computer by looking at the IO section of the preferences pane. (If your machine happens to have more than one IP address, hovering the mouse over the listed address will pop up with a list of all known addresses).
Note, some programs such as TouchOSC for iPhone and Android support sending OSC as a standard feature. So, once you’re listening to remote machines and know your IP address you can instantly start sending messages from apps like TouchOSC which enable you to build your own custom touch controls with sliders, buttons, dials etc. This can provide you with an enormous range of input options.
In addition to receiving OSC and working with it using Time State, we can also send out OSC messages in time with our music (just like we can send out MIDI messages in time with our music). We just need to know which IP address and port we’re sending to. Let’s give it a try:
use_osc "localhost", 4559
osc "/hello/world"
If you run the code above, you’ll notice that Sonic Pi is sending itself an OSC message! This is because we set the IP address to the current machine and the port to the default OSC in port. This is essentially the same as posting a letter to yourself - the OSC packet is created, leaves Sonic Pi, gets to the network stack of the operating system which then routes the packed back to Sonic Pi and then it’s received as a standard OSC message and is visible in the cue logger as the incoming message "/osc/hello/world
. (Notice how Sonic Pi automatically prefixes all incoming OSC messages with /osc
.)
Of course, sending OSC messages to ourselves may be fun but it’s not that useful. The real benefit starts when we send messages to other programs:
use_osc "localhost", 123456
osc "/hello/world"
In this case we’re assuming there’s another program on the same machine listening to port 123456. If there is, then it will receive a "/hello/world
OSC message with which it can do what it wants.
If our program is running on another machine, we need to know its IP address which we use instead of "localhost"
:
use_osc "192.168.10.23", 123456
osc "/hello/world"
Now we can send OSC messages to any device reachable to us via our local networks and even the internet!
So far, in terms of sound production, we’ve explored triggering synths and recorded sounds via the fns play
, synth
and sample
. These have then generated audio which has played through our stereo speaker system. However, many computers also have the ability to input sound, perhaps through a microphone, in addition to the ability to send sound out to more than two speakers. Often, this capability is made possible through the use of an external sound card - these are available for all platforms. In this section of the tutorial we’ll take a look at how we can take advantage of these external sound cards and effortlessly work with multiple channels of audio in and out of Sonic Pi.
One simple (and perhaps familiar) way of accessing sound inputs is using our friend synth
by specifying the :sound_in
synth:
synth :sound_in
This will operate just like any synth such as synth :dsaw
with the exception that the audio generated will be read directly from the first input of your system’s sound card. On laptops, this is typically the built-in microphone, but if you have an external sound card, you can plug any audio input to the first input.
One thing you might notice is that just like synth :dsaw
the :sound_in
synth only lasts for 1 beat as it has a standard envelope. If you’d like to keep it open for a little longer, change the ADSR envelope settings. For example the following will keep the synth open for 8 beats before closing the connection:
synth :sound_in, sustain: 8
Of course, just like any normal synth, you can easily layer on effects with the FX block:
with_fx :reverb do
with_fx :distortion do
synth :sound_in, sustain: 8
end
end
If you have plugged in a guitar to your first input, you should be able to hear it with distortion and reverb until the synth terminates as expected.
You are free to use the :sound_in
synth as many times as you like concurrently (just like you would do with any normal synth). For example, the following will play two :sound_in
synths at the same time - one through distortion and one through reverb:
with_fx :distortion do
synth :sound_in, sustain: 8
end
with_fx :reverb do
synth :sound_in, sustain: 8
end
You can select which audio input you want to play with the input:
opt. You can also specify a stereo input (two consecutive inputs) using the :sound_in_stereo
synth. For example, if you have a sound card with at least three inputs, you can treat the first two as a stereo stream and add distortion and the third as a mono stream and add reverb with the following code:
with_fx :distortion do
synth :sound_in_stereo, sustain: 8, input: 1
end
with_fx :reverb do
synth :sound_in, sustain: 8, input: 3
end
However, although this is a useful technique, there are a couple of limitations to this approach. Firstly, it only works for a specific duration (due to it having an ADSR envelope) and secondly, there’s no way to switch the FX around once the synth has been triggered. Both of these things are typical requests when working with external audio feeds such as microphones, guitars and external synthesisers. We’ll therefore take a look at Sonic Pi’s solution to the problem of manipulating a (potentially) infinite stream of live audio input: live_audio
.
The :sound_in
synth as described in the previous section provides a very flexible and familiar method for working with input audio. However, as also discussed it has a few issues when working with a single input of audio as a single instrument (such as a voice or guitar). By far the best approach to working with a single continuous stream of audio is to use live_audio
.
live_audio
shares a couple of core design constraints with live_loop
(hence the similar name). Firstly it must have a unique name and secondly only one live_audio
stream with that name may exist at any one time. Let’s take a look:
live_audio :foo
This code will act in a similar fashion to synth :sound_in
with some key differences: it runs forever (until you explicitly stop it) and you can move it to new FX contexts dynamically.
On initial triggering live_audio
works exactly as you might expect it to work with FX. For example, to start a live audio stream with added reverb simply use a :reverb
FX block:
with_fx :reverb do
live_audio :foo
end
However, given that live_audio
runs forever (at least until you stop it) it would be pretty limiting if, like typical synths, the live audio was bound within the :reverb
FX for its entire existence. Luckily this is not the case and it was designed to be easy to move between different FX. Let’s try it. Run the code above to hear live audio coming directly from the first input of your sound card. Note, if you’re using a laptop, this will typically be out of your built-in microphone, so it’s recommended to use headphones to stop feedback.
Now, whilst you’re still hearing the audio live from the sound card with reverb, change the code to the following:
with_fx :echo do
live_audio :foo
end
Now, hit Run, and you’ll immediately hear the audio played through the echo FX and no longer through reverb. If you wanted them both, just edit the code again and hit Run:
with_fx :reverb do
with_fx :echo do
live_audio :foo
end
end
It’s important to point out that you can call live_audio :foo
from any thread or live loop and it will move the live audio synth to that thread’s current FX context. You could therefore easily have multiple live loops calling live_audio :foo
at different times resulting in the FX context being automatically swapped around for some interesting results.
Unlike standard synths, as live_audio
has no envelope, it will continue running forever (even if you delete the code, just like a function is still defined in memory if you delete the code in the editor). To stop it, you need to use the :stop
arg:
live_audio :foo, :stop
It can easily be restarted by calling it without the :stop
arg again:
live_audio :foo
Additionally all running live audio synths are stopped when you hit the global Stop button (as with all other running synths and FX).
With respect to audio channels, by default live_audio
acts similarly to the :sound_in
synth in that it takes a single mono input stream of audio and converts it to a stereo stream using the specified panning. However, just like :sound_in_stereo
it’s also possible to tell live_audio
to read two consecutive audio inputs and treat them as the left and right channels directly. This is achieved via the :stereo
opt. For example, to treat input 2 as the left signal and input 3 as the right signal, you need to configure the input:
opt to 2 and enable stereo mode as follows:
live_audio :foo, stereo: true, input: 2
Note that once you have started a live audio stream in stereo mode, you cannot change it to mono without stopping and starting. Similarly, if you start it in the default mono mode, you can’t switch to stereo without starting and stopping the stream.
So far in this section we’ve looked at how to get multiple streams of audio into Sonic Pi - either through the use of the :sound_in
synth or via the powerful live_audio
system. In addition to working with multiple streams of input audio, Sonic Pi can also output multiple streams of audio. This is achieved via the :sound_out
FX.
Let’s quickly recap on how Sonic Pi’s synths and FX output their audio to their current FX context. For example, consider the following:
with_fx :reverb do # C
with_fx :echo do # B
sample :bd_haus # A
end
end
The simplest way to understand what’s happening with the audio stream is to start at the innermost audio context and work our way out. In this case, the innermost context is labelled A
and is the :bd_haus
sample being triggered. The audio for this goes directly into its context which is B
- the :echo
FX. This then adds echo to the incoming audio and outputs it to its context which is C
- the :reverb
FX. This then adds reverb to the incoming audio and outputs to its context which is the top level - the left and right speakers (outputs 1 and 2 in your audio card). The audio flows outwards with a stereo signal all the way through.
The above behaviour is true for all synths (including live_audio
) and the majority of FX with the exception of :sound_out
. The :sound_out
FX does two things. Firstly it outputs its audio to its external context as described above. Secondly it also outputs its audio directly to an output on your sound card. Let’s take a look:
with_fx :reverb do # C
with_fx :sound_out, output: 3 do # B
sample :bd_haus # A
end
end
In this example, our :bd_haus
sample outputs its audio to its external context which is the :sound_out
FX. This in turn outputs its audio to its external context the :reverb
FX (as expected). However, it also outputs a mono mix to the 3rd output of the system’s soundcard. The audio generated within :sound_out
therefore has two destinations - the :reverb
FX and audio card output 3.
As we’ve seen, by default, the :sound_out
FX outputs a mono mix of the stereo input to a specific channel in addition to passing the stereo feed to the outer context (as expected). If outputting a mono mix isn’t precisely what you want to do, there are a number of alternative options. Firstly, by using the mode:
opt you can choose to output just the left or just the right input signal to the audio card. Or you can use the :sound_out_stereo
FX to output to two consecutive sound card outputs. See the function documentation for more information and examples.
As we have also seen, the default behaviour for :sound_out
and :sound_out_stereo
is to send the audio both to their external context (as is typical of all FX) and to the specified output on your soundcard. However, occasionally you may wish to only send to the output on your soundcard and not to the external context (and therefore not have any chance of the sound being mixed and sent to the standard output channels 1 and 2). This is possible by using the standard FX opt amp:
which operates on the audio after the FX has been able to manipulate the audio:
with_fx :sound_out, output: 3, amp: 0 do # B
sample :loop_amen # A
end
In the above example, the :loop_amen
sample is sent to its outer context, the :sound_out
FX. This then sends a mono mix to audio card output 3 and then multiplies the audio by 0 which essentially silences it. It is this silenced signal which is then sent out to the :sound_out
’s outer context which is the standard output. Therefore with this code, the default output channels will not receive any audio, and channel 3 will receive a mono mix of the amen drum break.
На этом и завершается вводное руководство по Sonic Pi. Надеюсь, вы узнали что-нибудь по пути. Не волнуйтесь, если чувствуете, что не всё поняли, просто играйте и веселитесь, и всё придёт в своё время. Не стесняйтесь снова погрузиться в чтение этого учебника, когда у вас возникнут вопросы, которые могут быть раскрыты в одной из глав.
Если у вас есть вопросы, которые в учебнике освещены не были, то, пожалуйста, зайдите на Sonic Pi forums и задайте свой вопрос там. Вы обязательно найдёте там кого-нибудь доброжелательного и готового протянуть руку помощи.
Наконец, я также приглашаю вас более детально рассмотреть остальную документацию этой справочной системы. Есть ряд особенностей, которые не были охвачены в этом учебнике, но ожидают вашего открытия.
Так играйте, веселитесь, делитесь кодом, выступайте для своих друзей, показывайте свои экраны и помните:
Здесь нет ошибок, только возможности.
В Приложении A собраны статьи о Sonic Pi, выходившие в журнале MagPi.
Эти статьи не были задуманы для чтения в каком-либо определённом порядке и содержат много перекрёстных ссылок на материалы из разных глав этого учебника. Вместо того, чтобы пытаться рассказать вам обо всём в Sonic Pi, эти статьи фокусируются на определённом аспекте и подают это в весёлой и доступной форме.
Вы можете прочесть эти статьи в великолепном, профессионально оформленном виде, бесплатно загрузив их в формате PDF из MagPi
Если вы не находите интересующую вас тему в этих статьях - почему бы не предложить свою? Самый простой способ сделать это - твитнуть в @Sonic_Pi. Кто знает, возможно предложенная вами тема станет предметом следующей статьи!
Самый главный урок в обучении работе с Sonic Pi заключается в том, что здесь действительно нет ошибок. Лучший способ узнать это - пробовать, пробовать и ещё раз пробовать. Перестаньте беспокоиться о том, звучит ли ваш код хорошо или нет, и начните экспериментировать с как можно большим количеством различных синтезаторов, нот, эффектов и параметров. Вы откроете для себя множество вещей, которые заставят вас смеяться, потому что они звучат просто ужасно, но некоторые - настоящие жемчужины, звучащие просто потрясающе. Просто отбросьте то, что не нравится, и продолжайте делать то, что делаете. Чем больше “ошибок” вы позволите себе совершить, тем быстрее вы научитесь и откроете ваш собственный уникальный звук.
Допустим, вы уже освоили основы создания звука в Sonic Pi с помощью sample
и play
. Что дальше? Знаете ли вы, что Sonic Pi поддерживает более 27 студийных эффектов для изменения звука вашего кода? Эффекты похожи на причудливые фильтры изображений в графических редакторах, за исключением того, что вместо размытия или замены чего-то цветного чёрно-белым, вы можете использовать в своём звуке такие вещи, как реверберация, искажения и эхо. Думайте об этом, как о подключении кабеля от вашей гитары к педали эффектов, которую вы выбрали, а затем к усилителью. К счастью, Sonic Pi делает использование эффектов очень простым и не требует никаких проводов! Всё что вам нужно сделать - это выбрать, какой именно фрагмент кода вы бы обработали эффектом, и обернуть его в FX код. Давайте рассмотрим пример. Скажем, у вас есть следующий код:
sample :loop_garzul
16.times do
sample :bd_haus
sleep 0.5
end
Если вы хотите добавить эффект в :loop_garzul
сэмпл, просто поместите его внутрь with_fx
блока, как здесь:
with_fx :flanger do
sample :loop_garzul
end
16.times do
sample :bd_haus
sleep 0.5
end
Теперь, если вы захотите добавить эффект к басовому барабану, оберните его with_fx
тоже:
with_fx :flanger do
sample :loop_garzul
end
with_fx :echo do
16.times do
sample :bd_haus
sleep 0.5
end
end
Помните, вы можете обернуть любой код в with_fx
, и любые созданные звуки будут проходить через выбранный эффект.
Для того, чтобы создать действительно уникальный кодированный звук, вы, вероятно, захотите узнать как модифицировать и контролировать синтезаторы и эффекты. Например, вы можете захотеть изменить длительность ноты, добавить больше реверберации, или изменить время между повторами эхо. К счастью, Sonic Pi даёт вам удивительный уровень контроля с помощью специальных штук, называемых параметры. Давайте окинем их беглым взглядом. Скопируйте этот код в свободный буфер и нажмите “Выполнить”:
sample :guit_em9
О, прекрасный звук гитары! Теперь, давайте начнём играть с ним. Как насчёт изменения его скорости?
sample :guit_em9, rate: 0.5
Эй, что это за rate: 0.5
я добавил в конце? Это называется параметр. Все синтезаторы и эффекты в Sonic Pi поддерживают их, и их тут множество. Они также доступны и для эффектов. Попробуйте это:
with_fx :flanger, feedback: 0.6 do
sample :guit_em9
end
Теперь, попробуйте увеличить обратную связь до 1, чтобы услышать какие-то сумасшедшие звуки! Читайте документацию для получения полной информации о всех многочисленных параметрах, доступных для вас.
Лучший способ быстро экспериментировать и исследовать Sonic Pi - кодирование в реальном времени. Это позволяет вам начать выполнять какой-то код, постоянно изменяя и подправляя его в то время, как он звучит. Например, если вы не знаете, что делает с сэмплом параметр частоты среза фильтра, просто играйте его по кругу и экспериментируйте. Давайте пробовать! Скопируйте этот код в буфер вашего Sonic Pi:
live_loop :experiment do
sample :loop_amen, cutoff: 70
sleep 1.75
end
Теперь, нажмите “Выполнить” и вы будете слышать чуть приглушенный барабанный брейк. Измените значение cutoff:
на 80
и нажмите “Выполнить” снова. Слышите разницу? Попробуйте 90
, 100
, 110
…
Как только вы зависните с live_loop
, вы уже никогда не вернётесь обратно. Всякий раз, когда я делаю лайвкодинг концерт, я завишу от live_loop
так, как ударник зависит от своих палочек. Для получения дополнительной информации о лайвкодинге, проверьте раздел 9, встроенного учебника.
Наконец, ещё одна вещь, которую я люблю делать, это давать Sonic Pi сочинять за меня музыку. Действительно крутой способ сделать это - использовать рандомизацию. Это может показаться сложным, но на самом деле это не так. Давайте проверим. Скопируйте это в свободное рабочее пространство:
live_loop :rand_surfer do
use_synth :dsaw
notes = (scale :e2, :minor_pentatonic, num_octaves: 2)
16.times do
play notes.choose, release: 0.1, cutoff: rrand(70, 120)
sleep 0.125
end
end
Когда вы запустите это, вы услышите постоянный поток случайных нот гаммы :Е2 :minor_pentatonic
, сыгранную :dsaw
синтезатором. “Подожди, подожди! Это не мелодия”, слышу я ваш возглас! Ну, это первая часть магического трюка. Каждый раз, когда мы проходим круг цикла live_loop
, мы можем сказать Sonic Pi сбросить случайный поток к известной начальной точке. Это как будто возвращаться во времени в ТАРДИС С ДОКТОРОМ к определённой точке во времени и пространстве. Давайте попробуем это - добавьте строку use_random_seed 1
к live_loop
:
live_loop :rand_surfer do
use_random_seed 1
use_synth :dsaw
notes = (scale :e2, :minor_pentatonic, num_octaves: 2)
16.times do
play notes.choose, release: 0.1, cutoff: rrand(70, 120)
sleep 0.125
end
end
Теперь каждый раз, как цикл live_loop
повторяется, случайный поток будет сброшен. Это значит, что он выбирает одни и те же 16 нот каждый раз. Мгновенная мелодия. Теперь это действительно захватывающий бит. Изменитьте значение отправной точки с 1
на другое число. Скажем, 4923
. Ого! Другая мелодия! Так, просто изменяя одну цифру (отправную точку), вы можете открыть столько мелодических комбинаций, сколько сможете себе представить! Это и есть магия кода.
Лазерные лучи прорезали дым так же глубоко, как сабвуфер прокачивал низами танцующие тела. В атмосфере царила пьянящая смесь синтезаторов и танца. Однако, что-то в этом ночном клубе было не так… Яркими красками над кабиной диджея светился футуристический текст, движущийся, танцующий, вспыхивающий. Но это не были фантастические визуальные эффекты, это была лишь проекция Sonic Pi, запущенного на Raspberry Pi. Обитатель кабины диджея не крутил дисков, он писал, редактировал и запускал код. В реальном времени. Это был лайвкодинг.
Это может звучать как притянутая за уши история из футуристичного ночного клуба, но кодирование музыки - это развивающийся тренд, часто называемый Лайвкодинг (http://toplap.org). Одним из новых направлений этого способа создания музыки является Алгорэйв (http://algorave.com) - мероприятие, где такие артисты как я, кодируют музыку для танцующих людей. Однако, вам не надо быть в ночном клубе, чтобы кодировать в реальном времени - с Sonic Pi v2.6+ вы можете делать это в любом месте, в которое сможете взять ваш Raspberry Pi и пару наушников или колонок. Как только вы дойдёте до конца этой статьи, вы будете программировать свой собственный бит и изменять его вживую. То, где вы окажетесь после этого, может быть ограничено только вашим воображением.
Ключ к лайвкодингу с Sonic Pi - овладение циклами live_loop
. Давайте посмотрим на один из них:
live_loop :beats do
sample :bd_haus
sleep 0.5
end
Есть 4 основные составляющие циклов live_loop
. Первая - это имя. Наш live_loop
называется :beats
. Вы вольны называть свой live_loop
так, как вам угодно. С ума сойти. Подойдите к этому творчески. Обычно я использую имена циклов, что-то говорящие о музыке, которую они делают. Вторая состовляющая - это слово do
, которое отмечает, где цикл начинается. Третья состовляющая - слово end
, которое отмечает, где цикл заканчивается, и, наконец, есть тело цикла live_loop
, которое описывает, что цикл повторяет - это часть кода между do
и end
. В данном случае мы многократно воспроизводим сэмпл бас бочки и ждём пол тактовой доли. Это производит приятный ровный ритм. Скопируйте этот код в пустой Sonic Pi буфер и нажмите “Выполнить”. Бум, Бум, Бум!.
Итак, что такого особенного в цикле live_loop
? Он отдалённо напоминает усовершенствованный цикл loop
. Преимущество live_loop
в том, что вы можете изменять его на лету. Это значит, что пока выполняется цикл, вы можете изменить то, что он делает. Это и есть секрет лайвкодинга с Sonic Pi. Давайте играть:
live_loop :choral_drone do
sample :ambi_choir, rate: 0.4
sleep 1
end
Теперь, нажмите кнопку “Выполнить” или Alt-r
. Вы услышите великолепный звук хора. Теперь, пока он ещё играет, измените скорость с 0.4
на 0.38
. Нажмите “Выполнить” снова. Ого! Вы слышите, как хор сменил ноту? Измените скорость обратно на 0.4
, чтобы вернуться к тому, что было раньше. Теперь, снизьте скорость до 0.2
, потом до 0.19
, а затем обратно до 0.4
. Видите, как изменение на лету одного лишь параметра, даёт вам реальный контроль над музыкой? Теперь поиграйте со скоростью самостоятельно - выберите свои собственные значения. Попробуйте отрицательные числа, попробуйте очень маленькие и очень большие числа. Повеселитесь!
Один из самых важных аспектов цикла live_loop
в том, что в нём должна быть задержка. Рассмотрим следующий код:
live_loop :infinite_impossibilities do
sample :ambi_choir
end
Если вы попробуете запустить этот код, вы сразу увидите жалобу Sonic Pi на то, что в live_loop
нет sleep
. Это ругается система безопасности! Воспользуйтесь моментом, чтобы подумать о том, что этот код просит сделать компьютер. Правильно, он просит компьютер играть бесконечное количество хоровых сэмплов за нулевое время. Без системы безопасности бедный компьютер будет пытаться сделать это, что приведёт к аварии. Поэтому помните, ваш live_loop
должен содержать sleep
.
Музыка полна вещей, происходящих одновременно. Барабаны звучат в то же самое время, что и бас, вокал, гитары… В информатике это называется параллелизм и Sonic Pi обеспечивает нам удивительно простой способ играть разные вещи в одно и то же время. Просто используйте больше, чем один live_loop
!
live_loop :beats do
sample :bd_tek
with_fx :echo, phase: 0.125, mix: 0.4 do
sample :drum_cymbal_soft, sustain: 0, release: 0.1
sleep 0.5
end
end
live_loop :bass do
use_synth :tb303
synth :tb303, note: :e1, release: 4, cutoff: 120, cutoff_attack: 1
sleep 4
end
Здесь у нас есть два цикла live_loop
, один цикл делает быстрый ритм, а другой - медленный сумасшедший басовый звук.
Одна из особенностей использования нескольких циклов live_loop
состоит в том, что каждый из них обладает своим собственным независимым временем. Это значит, что с помощью этого легко создавать интересные полиритмические структуры и даже поиграть с фазировкой в стиле Стива Рейха (Steve Reich). Зацени:
# Steve Reich's Piano Phase
notes = (ring :E4, :Fs4, :B4, :Cs5, :D5, :Fs4, :E4, :Cs5, :B4, :Fs4, :D5, :Cs5)
live_loop :slow do
play notes.tick, release: 0.1
sleep 0.3
end
live_loop :faster do
play notes.tick, release: 0.1
sleep 0.295
end
Каждую из этих статей мы будем заканчивать примером в виде фрагмента музыкального произведения, который вырисовывается из всех представленных в статье идей. Прочтите этот код и посмотрите, сможете ли вы понять, что он делает. Затем скопируйте его в свежий буфер Sonic Pi, нажмите “Выполнить” и послушайте, как он самом деле звучит. Наконец, измените одно из чисел или закомментируйте/раскомментируйте какие-нибудь строки. Посмотрите, сможете ли вы использовать этот пример в качестве отправной точки для нового выступления, и самое главное, веселитесь! До встречи в следующий раз…
with_fx :reverb, room: 1 do
live_loop :time do
synth :prophet, release: 8, note: :e1, cutoff: 90, amp: 3
sleep 8
end
end
live_loop :machine do
sample :loop_garzul, rate: 0.5, finish: 0.25
sample :loop_industrial, beat_stretch: 4, amp: 1
sleep 4
end
live_loop :kik do
sample :bd_haus, amp: 2
sleep 0.5
end
with_fx :echo do
live_loop :vortex do
# use_random_seed 800
notes = (scale :e3, :minor_pentatonic, num_octaves: 3)
16.times do
play notes.choose, release: 0.1, amp: 1.5
sleep 0.125
end
end
end
Одной из самых захватывающих и разрушающих технических разработок современной музыки, стало изобретение сэмплеров. Это были коробки, которые позволяли записывать в них любой звук, а затем воспроизводить и управлять этим звуком множеством различных способов. Например, можно взять старую записать, найти барабанное соло (или брэйк), записать его в свой сэмплер и воспроизводить на половинной скорости, обеспечивая основу для своих собственных ритмов. Так зарождался ранний хип-хоп, и сегодня практически невозможно найти электронную музыку, в составе которой нет сэмплов в том или ином виде. Использование сэмплов - это действительно отличный способ легко внедрять новые интересные элементы в ваше лайвкодинг выступление.
Так где же можно получить сэмплер? Ну, у вас уже есть один - это ваш Raspberry Pi! Идущее в комплекте приложение для лайвкодинга Sonic Pi имеет чрезвычайно мощный сэмплер, встроенный в его ядро. Давайте с ним поиграем!
Один из самых узнаваемых сэмплов классических барабанных брейков называется Амен-брейк. Он впервые прозвучал в 1969 году в песне “Аминь, Брат”, исполнителя “Winstons”, как часть барабанного брейка. Тем не менее, когда он был обнаружен ранними хип-хоп музыкантами в 80-х годах и начал использоваться в сэмплерах, он стал активно внедряться и в другие стили, такие как драм-эн-бэйс, брейкбит, хардкор, техно и брейк-кор.
Я уверен, что вы будете взволнованы, узнав, что он также входит в состав Sonic Pi. Очистите буфер и поместите в него следующий код:
sample :loop_amen
Нажмите Выполнить и бум! Вы слушаете один из самых влиятельных барабанных брейков в истории танцевальной музыки. Однако, этот сэмпл не стал бы известен, будучи сыгран сам по себе, он был выбран для того, чтобы стать петлёй.
Давайте зациклим амен-брейк, используя наш старый добрый цикл live_loop
, представленный в прошлом выпуске этого журнала:
live_loop :amen_break do
sample :loop_amen
sleep 2
end
ОК, мы его зациклили, но при каждом повторе есть неприятная пауза. Это потому, что мы сказали Sonic Pi ждать 2
тактовые доли, а с учётом того, что по умолчанию BPM (ударов в минуту) равно 60, сэмпл :loop_amen
длится только 1.753
удара. Поэтому мы имеем тишину 2 - 1.753 = 0.247
удара. Даже не смотря на то, что она довольно короткая, она всё равно заметна.
Чтобы устранить эту проблему, мы можем воспользоваться параметром beat_stretch:
, чтобы сказать Sonic Pi растянуть (или сжать) сэмпл согласно установленнму количеству ударов.
Функции Sonic Pi sample
и synth
дают вам контроль через необязательные параметры, такие как amp:
, cutoff:
и release:
. Однако, термин “необязательный параметр” - это слишком длинное название, поэтому для простоты будем называть их просто параметры.
live_loop :amen_break do
sample :loop_amen, beat_stretch: 2
sleep 2
end
Теперь мы танцуем! Хотя, возможно, вы захотите ускорить или замедлить его, чтобы придать звуку иной характер.
ОК, но что, если мы хотим изменить стиль на олдскульный хип-хоп или брейк-кор? Один из простейших способов сделать это - играть со временем - или, другими словами, сдвинуть темп. В Sonic Pi это супер просто - добавьте use_bpm
в живой цикл:
live_loop :amen_break do
use_bpm 30
sample :loop_amen, beat_stretch: 2
sleep 2
end
Пока вы будете читать рэп под этот медленный ритм, обратите внимание, что мы до сих пор ждём 2 удара, и хотя наш BPM 30, всё продолжает звучать пропорционально. Параметр beat_stretch
работает с текущим BPM, чтобы быть уверенным, что всё будет работать исправно.
Теперь самое интересное. Пока цикл всё ещё исполняется, изменми 30
в строке use_bpm 30
на50
. Ого, всё ускорилось, но продолжает звучать равномерно во времени! Попытайтесь разогнаться ещё быстрее - до 80, до 120, или, сойдите с ума, и дайте 200!
Теперь, когда мы можем зацикливать сэмплы в реальном времени, давайте взглянем на некоторые из самых забавных параметров, предоставляемых сэмплером. Первый - cutoff:
, который контролирует частоту среза фильтра. По умолчанию эта возможность отключена, но вы можете легко её включить:
live_loop :amen_break do
use_bpm 50
sample :loop_amen, beat_stretch: 2, cutoff: 70
sleep 2
end
Давайте изменим параметр cutoff:
. Например, увеличте его до 100, нажмите Выполнить и ждите, пока круг цикла завершится, чтобы услышать изменения в звуке. Обратите внимание, что низкие значения, такие как 50, дают сочный звук и басы, а высокие значения, типа 100 и 120, дают более полное звучание и больше высокочастотных составляющих. Это происходит потому, что параметр cutoff:
срубает высокочастотную область звука так же, как газонокосилка срезает верхнюю часть травы. Параметр cutoff:
определяет сколько “травы” оставлять.
Еще один отличный инструмент, с которым можно поиграть - это эффект slicer
. Он позволяет “нарезать” звук сэмпла. Оберните строку sample
кодом эффекта, как здесь:
live_loop :amen_break do
use_bpm 50
with_fx :slicer, phase: 0.25, wave: 0, mix: 1 do
sample :loop_amen, beat_stretch: 2, cutoff: 100
end
sleep 2
end
Обратите внимание, как звук стал более упругим (вы можете услышать оригинальное звучание сэмпла без эффекта, установив параметр mix:
в 0
.) Теперь поиграйтесь с параметром phase:
. Это размер (в ударах) нарезки. Меньшее значение, такое как 0.125
, нарежет сэмпл более короткими кусками и в большем количестве, чем значение 0.5
, при котором нарезка будет реже и толще. Обратите внимание, что удвоения значений phase
, как правило, всегда звучат хорошо. Наконец, измените параметр wave:
, выбрав из 0, 1 или 2, и послушайте, как меняется звук. Это три разные формы волны. 0 - это пилообразная волна, (резко начинается, плавно затухает), 1 - прямоугольная волна (резко начинается, резко обрывается), и 2 - треугольная волна (плавно нарастает, плавно затухает).
Наконец, давайте вернёмся назад во времени, и вновь посетим раннюю Бристольскую драм-эн-бэйс сцену. Не волнуйтесь о том, что всё это значит, просто введите этот код в пустой буфер, нажмите “Выполнить”, и начинайте лайвкодинг. Изменяйте значения параметров и подумайте, где бы вы смогли применить это. Пожалуйста, поделитесь тем, что у вас получилось! Увидимся в следующий раз…
use_bpm 100
live_loop :amen_break do
p = [0.125, 0.25, 0.5].choose
with_fx :slicer, phase: p, wave: 0, mix: rrand(0.7, 1) do
r = [1, 1, 1, -1].choose
sample :loop_amen, beat_stretch: 2, rate: r, amp: 2
end
sleep 2
end
live_loop :bass_drum do
sample :bd_haus, cutoff: 70, amp: 1.5
sleep 0.5
end
live_loop :landing do
bass_line = (knit :e1, 3, [:c1, :c2].choose, 1)
with_fx :slicer, phase: [0.25, 0.5].choose, invert_wave: 1, wave: 0 do
s = synth :square, note: bass_line.tick, sustain: 4, cutoff: 60
control s, cutoff_slide: 4, cutoff: 120
end
sleep 4
end
Какой бы звук вы не использовали, солирующий синтезатор всегда играет главную роль в любом электронном треке. В статье из прошлого номера мы рассматривали, как кодировать барабанные биты. В этой статье мы узнаем, как кодировать три основных компонента синтезаторных риффов - тембр, мелодию и ритм.
Итак, подключите ваш Paspberry Pi, откройте Sonic Pi версии v2.6+ и давайте пошумим!
Неотъемлемой частью любого синтезаторного риффа является его тембр. Мы можем изменять тембр в Sonic Pi двумя способами - выбирая различные синты для резкой смены тембра, и изменяя различные синтовые параметры для более тонкой настройки. Мы также можем использовать эффекты, но об этом в другой раз…
Давайте создадим простой живой цикл, в котором будет постоянно изменяться текущий синт:
live_loop :timbre do
use_synth (ring :tb303, :blade, :prophet, :saw, :beep, :tri).tick
play :e2, attack: 0, release: 0.5, cutoff: 100
sleep 0.5
end
Взгляните на код. Мы просто перебираем по кругу элементы кольцевого списка, содержащего имена синтов (цикл переключает синт на следующий в списке снова и снова). Мы передаём имена этих синтов функции use_synth
, которая и переключает текущий синтезатор цикла live_loop
. Ещё мы играем ноту :Е2
(ми второй октавы), со временем затухания 0.5 удара (пол секунды с ВРМ по умолчанию 60) и с параметром cutoff:
, равным 100.
Слышите, разные синтезаторы звучат совершенно по-разному, хотя играют одну и ту же ноту? Теперь давайте поэкспериментируем. Измените время затухания на большее или меньшее значение. Изменяйте оба параметра - attack:
и release:
, чтобы посмотреть, как сильно разные их значения изменяют звук. Наконец, измените параметр cutoff:
, чтобы услышать, что разные значения среза фильтра также радикально изменяют тембр синта (значения между 60 и 130 обычно звучат хорошо). Посмотрите, сколько разнообразных звуков можно создать, изменяя всего лишь несколько параметров. Как только вы это освоите, откройте вкладку “Синтезаторы” справочной системы, и взгляните на полный список синтезаторов и их параметров, чтобы увидеть, какую мощь вы держите в руках.
Тембр - это просто слово, описывающее звук. Если вы сыграете одну и ту же ноту на разных инструментах, таких как скрипка, гитара, или пианино, высота звука будет одинаковой, но качество звуков будет разным. Это качество звука - то, что позволяет вам слышать разницу между пианино и гитарой - и есть тембр.
Ещё одним важным аспектом нашего солирующего синтезатора является выбор нот, которые он будет играть. Если у вас уже есть хорошая мелодическая идея, то вы можете просто создать кольцо с вашими нотами, и воспроизводить его по кругу:
live_loop :riff do
use_synth :prophet
riff = (ring :e3, :e3, :r, :g3, :r, :r, :r, :a3)
play riff.tick, release: 0.5, cutoff: 80
sleep 0.25
end
Здесь мы поместили нашу мелодию в кольцевой список, содержащий ноты, такие как :е3
и паузы, обозначенные :r
. Затем, мы использовали .tick
, чтобы получить повторяющийся рифф.
Не всегда бывает легко придумать хороший рифф с нуля. Вместо этого, иногда проще сказать Sonic Pi играть случайный рифф, и выбрать тот, который понравится вам больше всего. Для этого нам необходимо объединить три вещи: кольца, рандомизацю и отправную точку (зерно) рандомизаци. Взгляните на пример:
live_loop :random_riff do
use_synth :dsaw
use_random_seed 3
notes = (scale :e3, :minor_pentatonic).shuffle
play notes.tick, release: 0.25, cutoff: 80
sleep 0.25
end
Здесь происходит несколько вещей - давайте рассмотрим их по очереди. Во-первых, мы указли, что используем зерно рандомизаци 3. Что это значит? Ну, смысл в том, что когда мы устанавливаем зерно, мы можем предсказать, какой будет рандомизаця в следующий раз. Той же самой, что и в тот раз, когда мы устанавливали зерно в 3! Ещё одна вещь, которую полезно знать, что перемешивание нот кольца будет произведено тем же способом. В примере выше, мы, по сути, вызываем “третье перемешивание” из стандартного списка перемешиваний, который остаётся неизменным всякий раз, когда мы устанавливаем зерно в то же значение перед перемешиванием. Наконец, мы просто вызываем по кругу наши перемешанные ноты, чтобы получить рифф.
Вот где начинается веселье. Если мы меняем значение зерно рандомизаци на другое, скажем 3000, мы получаем совершенно другое перемешивание нот. Так что теперь очень легко исследовать новые мелодии. Просто выберите список нот, которые вы хотите перемешать (гаммы представляют собой отличную отправную точку), а затем выберите зерно рандомизаци, с которого вы хотите начать перемешивание. Если вам не нравится мелодия, просто измените одну из этих двух вещей и попробуйте снова. Повторяйте до тех пор, пока вам понравится то, что вы слышите!
Случайный выбор в Sonic Pi на самом деле не является случайным, это то, что обычно называется псевдо рандомизация. Представьте, что вы бросали игральные кости 100 раз и записывали результат каждого броска на лист бумаги. Sonic Pi имеет эквивалент этого списка результатов, которые он использует, когда вы запрашиваете случайные значения. Только вместо бросков настоящих костей, он выбирает следующее значение из списка. Настройка зерна рандомизаци - это просто прыжок в определённое место этого списка.
Ещё один важный аспект нашго риффа, это ритм - т.е. последовательность нот и пауз. Как мы видели раньше, мы можем использовать :r
в наших кольцах, чтобы вставить паузу. Ещё один очень мощный способ - использовать методы колец, но о них мы поговорим в следующий раз. Сегодня мы будем использовать рандомизацию, чтобы помочь нам найти подходящий ритм. Вместо того, чтобы играть каждую ноту, мы можем играть ноту с заданной вероятностью. Давайте посмотрим:
live_loop :random_riff do
use_synth :dsaw
use_random_seed 30
notes = (scale :e3, :minor_pentatonic).shuffle
16.times do
play notes.tick, release: 0.2, cutoff: 90 if one_in(2)
sleep 0.125
end
end
Очень полезно знать функцию one_in
, которая возвращает нам true
или false
с заданной вероятностью. Здесь мы используем значение 2, поэтому в среднем один раз каждые два вызова, one_in
возвратит true
. Другими словами, она будет возвращать true
в 50% случаев. Если использовать более высокие значения, false
будет возвращаться чаще, добавляя в рифф больше пауз.
Заметим, что мы добавили повторы 16.times
. Это потому, что мы хотим сбрасывать наше зерно рандомизаци каждые 16 нот. Так наш ритм будет повторяться каждые 16 ударов. Это не влияет на перемешивание, т.к. оно выполняется сразу после того, как установлено зерно рандомизаци. Мы можем использовать разное количество повторов для изменения длины риффа. Попробуйте изменить 16 на 8 или даже на 4 или 3 и посмотрите, как это повлияет на ритм.
ОК, теперь давайте совместим всё, чему мы научились, в один финальный пример. Увидимся в следующий раз!
live_loop :random_riff do
# uncomment to bring in:
# synth :blade, note: :e4, release: 4, cutoff: 100, amp: 1.5
use_synth :dsaw
use_random_seed 43
notes = (scale :e3, :minor_pentatonic, num_octaves: 2).shuffle.take(8)
8.times do
play notes.tick, release: rand(0.5), cutoff: rrand(60, 130) if one_in(2)
sleep 0.125
end
end
live_loop :drums do
use_random_seed 500
16.times do
sample :bd_haus, rate: 2, cutoff: 110 if rand < 0.35
sleep 0.125
end
end
live_loop :bd do
sample :bd_haus, cutoff: 100, amp: 3
sleep 0.5
end
Невозможно исследовать историю электронной танцевальной музыки, и не заметить огромного влияния крошечного синтезатора Roland TB-303. Это секретный ингридиент оригинального кислотного баса. Классическое визжание и чавканье басовых риффов TB-303 можно услышать в ранней чикагской хаус сцене благодаря таким музыкантам, как Plastikman, Squarepusher и Aphex Twin.
Интересно, что инженеры Roland никогда не предполагали, что TB-303 будет использоваться в танцевальной музыке. Он изначально создавался для гитаристов. Они расчитывали, что гитаристы будут программировать TB-303 играть басовые линии в джемах. К сожалению, существовал ряд проблем: они были немного неудобными для программирования, не очень хорошо звучали в качестве замены бас-гитары и были довольно дорогими. Решив сократить убытки, Roland прекратили их выпуск после продажи 10 000 экземпляров, и после нескольких лет лежания на полках, эи синтезаторы можно было найти только в магазинах “секонд хенд”. Эти одинокие, отвергнутые TB-303 ждали, когда их обнаружит новое поколение экспериментаторов, которые начали применять их способом, который в Roland и представить не могли, создавая новые сумасшедшие звуки. Так зародился эйсид хаус.
Хотя получить на руки оригинал TB-303 не так уж и просто, вам будет приятно узнать, что вы можете превратить ваш Raspberry Pi в один из них, используя мощь Sonic Pi. Узрите свет Sonic Pi, забросив этот код в пустой буфер и нажав “Выполнить”:
use_synth :tb303
play :e1
Мгновенный кислотный бас! Давайте с ним поиграем…
Во-первых, построим живой арпеджиатор, чтобы играть было веселей. В прошлой статье мы узнали, что риффы могут быть простыми кольцами нот, извлекающимися одна за другой. Давайте создадим живой цикл, который сделает для нас это:
use_synth :tb303
live_loop :squelch do
n = (ring :e1, :e2, :e3).tick
play n, release: 0.125, cutoff: 100, res: 0.8, wave: 0
sleep 0.125
end
Для начала рассмотрим код построчно:
В первой строке мы задаём синтезатор по умолчанию tb303
с помощью функции use_synth
.
Во второй строке мы создаем живой цикл :squelch
, который будет просто повторяться снова и снова.
Третья строка - та, где мы создаём наш рифф - кольцо нот (ми в октавах 1, 2 и 3), которые мы извлекаем по кругу с помощью .tick
. Мы определяем n
для представления текущей ноты в риффе. Знак равенства присваивает значение справа, имени, находящемуся слева. Это значение будет отличаться каждый круг цикла. Первый круг n
будет иметь значение :е1
. Второй круг - :е2
, затем :е3
, потом снова :е1
и так круг за кругом.
Четвёртая строка - место, где мы включаем наш :tb303
. У нас здесь есть несколько интересных параметров: release:
, cutoff:
, res:
и wave:
, которые мы обсудим чуть позже.
В пятой строке появляется sleep
- мы просим цикл замыкать круг каждые 0.125
удара, или 8 раз в секунду с BPM по умолчанию 60.
Строка шесть - это end
живого цикла. Мы просто говорим Sonic Pi, что здесь конец живого цикла.
В то время, как вы всё ещё пытаетесь понять, что здесь происходит, поместите этот код в пустой буфер, и нажмите кнопку “Выполнить”. Вы должны услышать :tb303
в действии. Теперь пора начинать лайвкодинг.
В то время, как цикл исполняется, измените параметр cutoff:
на 110
. Затем нажмите кнопку “Выполнить” снова. Вы должны услышать, что звук стал немного жёстче и более “хлюпающим”. Наберите 120
и нажмите “Выполнить”. Затем 130
. Слушайте, как более высокие значения делают звук более пронзительным и интенсивным. Наконец, понизьте значение до 80
, если захотите отдохнуть. Затем повторите это столько раз, сколько захотите. Не волнуйтесь, я всё ещё буду здесь…
Ещё один параметр, с которым стоит поиграть, это res:
. Он контролирует уровень резонанса фильтра. Для кислотных басовых партий характерен высокий резонанс. В настоящее время наш res:
установлен в 0.8
. Попробуйте изменить его на 0.85
, затем на 0.9
, и наконец, на 0.95
. Вы можете обнаружить, что предельные значения cutoff:
, такие как 110
или выше, помагают легче услышать различия. Наконец, сойдите с ума и установите 0.999
, чтобы получить действительно безумный звук. С этим значением res:
вы слышите так много резонанса, что он начинает звучать сам по себе!
Наконец, для резкого изменения тембра попробуйте изменить параметр wawe:
на 1
. Это выбор формы волны исходного генератора. Значение по умолчанию - 0
- пилообразная волна, 1
- прямоугольная волна, и 2
- треугольная форма волны.
Конечно, пробуйте разные риффы, изменяя ноты в кольце, или же выбирая ноты из гамм, или аккордов. Веселитесь с вашим первым кислотным басовым синтезатором.
Конструкция оригинальных TB-303 на самом деле довольно проста. Как вы можете видеть из следующей диаграммы, главных составляющих всего 4.
Первой является волновой герератор - сырая основа звука. В этом примере у нас прямоугольная волна. Дальше идёт генератор амплитудной огибающей, который контролирует амплитуду прямоугольной волны во времени. Он доступен в Sonic Pi в виде параметров attack:
, decay:
, sustain:
и release:
. Для получения более подробной информации об этих параметрах читайте раздел 2.4 “Длительность с помощью огибающих” встроенного учебника. Затем, мы пропускаем наш амплитудно модулированный сигнал через резонансный низкочастотный фильтр. Это срубает высокочастотную составляющую, а также добавляет этот приятный резонансный эффект. Теперь начинается самое весёлое. Значение среза этого фильтра может управляться своей собственной огибающей! Это значит, что мы имеем удивительный контроль над тембром звука, играя двумя этими огибающими. Давайте приступим:
use_synth :tb303
with_fx :reverb, room: 1 do
live_loop :space_scanner do
play :e1, cutoff: 100, release: 7, attack: 1, cutoff_attack: 4, cutoff_release: 4
sleep 8
end
end
В синтезаторе :tb303
, для каждого стандартного параметра огибающей есть cutoff_
эквивалент. Так, для изменения времени атаки фильтра, мы можем использовать параметр cutoff_attack:
. Скопируйте приведенный выше код в пустой буфер, и нажмите “Выполнить”. Вы услышите безумный плавающий звук. Теперь поиграем. Попробуйте изменять время cutoff_attack:
на 1
, а затем на 0.5
. Потом попробуйте 8
.
Заметьте, что я пропустил звук через :reverb
эффект для создания дополнительной атмосферы - попробуйте другие эффекты, чтобы посмотреть, как это работает!
Наконец, ниже расположен фрагмент кода, который я сочинил, используя идеи этой статьи. Скопируйте его в свой буфер, послушайте, а потом начните лайвкодинг, внося свои собственные изменения. Посмотрим, какие безумные звуки вы сможете создать с помощью этого! Увидимся в следующий раз…
use_synth :tb303
use_debug false
with_fx :reverb, room: 0.8 do
live_loop :space_scanner do
with_fx :slicer, phase: 0.25, amp: 1.5 do
co = (line 70, 130, steps: 8).tick
play :e1, cutoff: co, release: 7, attack: 1, cutoff_attack: 4, cutoff_release: 4
sleep 8
end
end
live_loop :squelch do
use_random_seed 3000
16.times do
n = (ring :e1, :e2, :e3).tick
play n, release: 0.125, cutoff: rrand(70, 130), res: 0.9, wave: 1, amp: 0.8
sleep 0.125
end
end
end
Привет, и с возвращением! В предыдущих упражнениях мы сосредотачивались исключительно на музыкальных возможностях Sonic Pi - (превращая ваш Raspberry Pi в готовый к выступлению музыкальный инструмент). К этому моменту мы научились:
Кодировать в реальном времени, Создавать различные ритмы, Генерировать мощные солирующие синты, Воссоздавать знаменитые кислотные басы TB-303.
Однако, есть ещё множество аспектов создания музыки, которые нужно показать вам (что я и сделаю в будущем). Но в этом месяце мы рассмотрим особенность Sonic Pi, о наличии которой вы, наверное, даже и не подозревали: управлять Minecraft.
ОК, давайте начнём. Подключите ваш Raspberry Pi, запустите Minecraft Pi и создайте новый мир. Теперь запустите Sonic Pi, измените размеры окон и разместите их так, чтобы вы могли видеть одновременно и Minecraft Pi, и Sonic Pi.
В пустом буфере наберите следующее:
mc_message "Hello Minecraft from Sonic Pi!"
Теперь, нажмите “Выполнить”. Бум! Ваше сообщение появилось в Minecraft! Ну как, это было легко? Теперь, прекратите на мгновение чтение этой статьи и поиграйте с вашими собственными сообщениями. Веселитесь!
Теперь давайте исследовать мир Minecraft. Стандартный вариант - дотянуться до мыши и клавиатуры и начать перемещаться в мире Minecraft. Это работает, но это довольно медленно и скучно. Было бы гораздо интереснее, если бы у нас было что-то вроде телепортатора. Благодаря Sonic Pi, мы имеем один. Попробуйте это:
mc_teleport 80, 40, 100
Боже мой! Это был долгий путь вверх. Если вы не находились в режиме полёта, то должны были упасть обратно на землю. Если вы дважды нажмёте пробел для входа в режим полёта и телепортируетесь снова, вы останетесь висеть на том месте, куда вы в телепортировались.
Итак, что же означают эти цифры? У нас есть три числа, которые описывают координаты места, куда мы хотим переместиться. Мы даём каждой цифре имя - х, y и z:
x - как далеко от левого края (в нашем примере - 80) y - как высоко (в нашем примере - 40) z - как далеко вглубь мира (в нашем примере - 100)
Выбирая различные значения для x, y и z, мы можем телепортироваться куда угодно. Попробуйте! Выбирайте различные цифры и смотрите, где вы окажетесь. Если экран стал чёрным, это произошло потому, что вы телепортировали себя под землю или внутрь скалы. Просто выбирите значение y
выше, чтобы оказаться снова над землёй. Продолжайте исследования, пока не найдёте место, которое вам понравится…
Используя эти идеи, построим телепортатор, который издаёт весёлый звук телепортации, пока переносит нас сквозь мир Minecraft:
mc_message "Preparing to teleport...."
sample :ambi_lunar_land, rate: -1
sleep 1
mc_message "3"
sleep 1
mc_message "2"
sleep 1
mc_message "1"
sleep 1
mc_teleport 90, 20, 10
mc_message "Whoooosh!"
Сейчас, когда вы нашли хорошее место, начнём строительство. Вы могли бы делать то, к чему привыкли, и начать яростно кликать мышкой, распологая блоки под курсором. Или вы могли бы использовать магию Sonic Pi. Попробуйте это:
x, y, z = mc_location
mc_set_block :melon, x, y + 5, z
Теперь посмотри вверх! В небе дынный блок! Найдите минутку, чтобы посмотреть на код. Что он делает? На первой строке мы определили текущее местоположение как переменные x, y и z. Они соответствуют нашим координатам, описанным выше. Мы использовали эти координаты в функции mc_set_block
, которая поместила блок, который мы выбрали, по указанным координатам. Чтобы указать сделать что-то выше в небе, нам просто нужно увеличить значение y
, вот почему мы добавляем 5 к нему. Сделаем длинный след из этих блоков:
live_loop :melon_trail do
x, y, z = mc_location
mc_set_block :melon, x, y-1, z
sleep 0.125
end
Теперь переключитесь на Minecraft, убедитесь что вы находитесь в режиме полёта (двойное нажатие на пробел, если нет) и летайте по всему миру. Обернитесь, чтобы увидеть длинный след дынных блоков! Посмотрите, какие извилистые структуры вы можете сделать в небе.
Тем из вас, кто следит за этими статьями в течение последних нескольких месяцев, наверное снесёт крышу к этому моменту. След дынных блоков это очень здорово, но самая захватывающая часть предыдущего пример заключается в том, что вы можете использовать live_loop
с Minecraft! Для тех, кто не знает, цикл live_loop
в Sonic Pi - особая магическая способность, которой нет других языках программирования. Он позволяет запускать несколько циклов в одно и то же время и изменять их, пока они выполняются. Они невероятно мощные и удивительно весёлые. Я использую live_loop
, чтобы исполнять музыку в ночных клубах с Sonic Pi - диджеи используют диски, а я циклы live_loop
:-) Тем не менее, сегодня мы собираемся кодировать в реальном времени и музыку, и Minecraft.
Давайте начнём. Запустите приведенный выше код и начните делать свой дынный след снова. Теперь, не останавливая код, просто измените :melon
на :brick
и нажмите “Выполнить”. Вуаля, вы теперь делаете кирпичную дорогу. Это было просто! Добавим немного музыки в это? Легко. Попробуйте это:
live_loop :bass_trail
tick
x, y, z = mc_location
b = (ring :melon, :brick, :glass).look
mc_set_block b, x, y -1, z
note = (ring :e1, :e2, :e3).look
use_synth :tb303
play note, release: 0.1, cutoff: 70
sleep 0.125
end
Теперь, пока это играет, начнём вносить изменения в наш код. Измените тип блока. Попробуйте :water
, :grass
или другой ваш любимый тип блока. Также, попробуйте изменть значение отсечки фильтра с 70
до 80
и затем до 100
. Разве это не весело?
Давайте объединим всё, что мы видели до сих пор, с небольшой дополнительной магией. Совместим наши способности к телепортации с размещением блоков и музыкой, сделав музыкальное видео Minecraft. Не волнуйтесь, если вы не всё здесь понимаете, просто введите код в буфер и играйте, изменяя значения, пока код выполняется. Повеселитесь и увидимся в следующий раз…
live_loop :note_blocks do
mc_message "This is Sonic Minecraft"
with_fx :reverb do
with_fx :echo, phase: 0.125, reps: 32 do
tick
x = (range 30, 90, step: 0.1).look
y = 20
z = -10
mc_teleport x, y, z
ns = (scale :e3, :minor_pentatonic)
n = ns.shuffle.choose
bs = (knit :glass, 3, :sand, 1)
b = bs.look
synth :beep, note: n, release: 0.1
mc_set_block b, x+20, n-60+y, z+10
mc_set_block b, x+20, n-60+y, z-10
sleep 0.25
end
end
end
live_loop :beats do
sample :bd_haus, cutoff: 100
sleep 0.5
end
После нашего краткого экскурса в сказочный мир кодинга Minecraft в прошлом месяце, давайте снова помузицируем. Сегодня мы собираемся перенести классический оперный танцевальный ритм в 21-й век, используя грандиозную силу кода.
Переместимся на машине времени в 1875 год. Композитор Бизе как раз закончил свою последнюю оперу “Кармен”. К сожалению, как и множество других прогрессивных, ломающих стереотипы музыкальных произведений, публика поначалу её не приняла, ведь она была слишком дерзкой и непохожей на другие оперы того времени. Печально, но Бизе умер за десять лет до того, как эта опера заработала ошеломляющий международный успех и стала одной из самых известных и часто исполняемых опер всех времён. Из сочувствия к этой трагедии, возьмем одну из главных тем оперы “Кармен”, и преобразуем её в современный формат музыки, который также слишком возмутителен и чужд большинству людей нашего времени - кодированную музыку!
Пытаться кодировать всю оперу было бы сложноватой задачей для одной статьи, так что давайте сосредоточимся на одной из самых известных её частей - басовой партии Хабанеры:
Если вы ещё не изучали нотной записи, это будет выглядеть крайне непонятным для вас. Однако, как программисты, мы видим нотную запись просто как другую форму кода, содержащего инструкции не для компьютера, а для музыканта. Поэтому нам нужно найти способ декодировать его.
Ноты упорядочиваются слева направо, как слова в этой статьи, но имеют разную высоту. Высота относительно нотной партитуры обозначает высоту тона. Чем выше нота находится в партитуре, тем выше высоты тона.
Мы уже знаем, как изменить высоту ноты в Sonic Pi - либо используя цифры, такие как play 75
и play 80
, либо используя названия нот: play :Е
и play :F
. К счастью, каждой вертикальной позиции музыкальной партитуры соответствует определенная нота. Взгляните на эту удобную таблицу:
Ноты представляют собой чрезвычайно богатый и выразительный вид кода, способного объединить сразу множество вещей. Поэтому дя вас не должно стать большим удивлением, что партитура может не только сказать вам, какие ноты играть, но также когда ноты играть не нужно . В программировании это эквивалентно nil
или null
- отсутствию значения. Другими словами, пауза - это отсутствие ноты.
Если вы внимательно посмотрите на партитуру, вы увидите, что это на самом деле сочетание горизонтальных линий с черными точками, которые представляют собой ноты, коротые нужно играть, и волнистых штук, которые представляют собой паузы. К счастью, Sonic Pi имеет очень удобное представление для пауз: :r
, так, если мы выполним: play :r
, он на самом деле сыграет тишину! Мы могли бы также написать play :rest
, play nil
или play false
- это всё эквивалентные способы представления пауз.
Наконец, нужно узнать ещё одну вещь - как расшифровывать в партитуре длительности нот. В оригинальной партитуре мы видим, что ноты связаны толстыми линиями, называемыми рёбрами. Вторая нота имеет два таких ребра, что означает, что она длится 16-ю часть такта. Другие ноты имеют одно ребро, которое означает, что они дляться 8-ю часть такта. Паузы имеют два волнистых флажка, которые означают, что они также длятся 16-ю часть такта.
Когда мы пытаемся расшифровать и изучить новые вещи, очень удобный приём - сделать всё как можно более однородным, чтобы увидеть соотношения и структуры. Например, если мы перепишем нашу партитуру одними только 16-ми длительностями, вы сможете увидеть, что она превращается в простую последовательность нот и пауз.
Теперь мы в состоянии начать переводить эту басовую партию для Sonic Pi. Давайте закодируем эти ноты и паузы в кольцо:
(ring :d, :r, :r, :a, :f5, :r, :a, :r)
Теперь посмотрим, как это звучит. Закиньте это кольцо в живой цикл, чтобы можно было последовательно извлекать элементы кольца:
live_loop :habanera do
play (ring :d, :r, :r, :a, :f5, :r, :a, :r).tick
sleep 0.25
end
Легендарный, мгновенно узнаваемый рифф возвращается к жизни сквозь ваши колонки. Потребовалось немало усилий, чтобы добраться сюда, но это того стоило!
Теперь у нас есть басовая партия. Давайте воссоздадим атмосферу оперной сцены. Попробуем осуществить это с :blade
, мрачным солирующим синтом в стиле 80-х. Давайте испробуем его, извлекая ноту :d
, пропущенную через эффекты нарезки и реверберации:
live_loop :habanera do
use_synth :fm
use_transpose -12
play (ring :d, :r, :r, :a, :f5, :r, :a, :r).tick
sleep 0.25
end
with_fx :reverb do
live_loop :space_light do
with_fx :slicer, phase: 0.25 do
synth :blade, note: :d, release: 8, cutoff: 100, amp: 2
end
sleep 8
end
end
Теперь, попробуйте подставить туда другие ноты басовой партии::a
и :f5
. Помните, вам не надо нажимать кнопку “Остановить”, просто измените код в то время, как музыка играет, и нажмите “Выполнить” снова. Также, попробуйте разные значения параметра phase:
нарезки, такие как 0.5
, 0.75
и 1
.
Наконец, давайте объединим все идеи этой статьи в новый ремикс Хабанеры. Вы можете заметить, что я включил ещё одну часть басовой партии как комментарий. Как только вы набрали всё это в буфере, нажмите “Выполнить”, чтобы прослушать композицию. Затем, не нажимая кнопку “Остановить”, раскомментируйте вторую строку, удалив знак #
, и нажмите “Выполнить” снова - как это удивительно! Итак, начинайте лайвкодинг и веселитесь!
use_debug false
bizet_bass = (ring :d, :r, :r, :a, :f5, :r, :a, :r)
#bizet_bass = (ring :d, :r, :r, :Bb, :g5, :r, :Bb, :r)
with_fx :reverb, room: 1, mix: 0.3 do
live_loop :bizet do
with_fx :slicer, phase: 0.125 do
synth :blade, note: :d4, release: 8,
cutoff: 100, amp: 1.5
end
16.times do
tick
play bizet_bass.look, release: 0.1
play bizet_bass.look - 12, release: 0.3
sleep 0.125
end
end
end
live_loop :ind do
sample :loop_industrial, beat_stretch: 1,
cutoff: 100, rate: 1
sleep 1
end
live_loop :drums do
sample :bd_haus, cutoff: 110
synth :beep, note: 49, attack: 0,
release: 0.1
sleep 0.5
end
Все вы играли в Minecraft. Все строили удивительные сооружения, конструировали хитрые ловушки и даже создавали контролируемые транспортные линии. Но, держу пари, вы не догадывались, что могли бы использовать Minecraft для создания удивительных визуальных эффектов как профессиональный виджей.
Если вашим единственным способом игры с Minecraft была модификация игрового мира с помощью мышки, вам наверняка было бы трудно изменять его достаточно быстро. К счастью, ваш Raspberry Pi поставляется с версией Minecraft, управляемой с помощью кода. Он также содержит приложение Sonic Pi, которое делает кодирование Minecraft не только лёгким, но и невероятно весёлым.
В этой статье я покажу вам некоторые советы и трюки, использующиеся в выступлениях в ночных клубах и концертных площадках по всему миру.
Итак, начнём…
Давайте начнём с простого разминочного упражнения, дабы освежить в памяти основы. Во-первых, подключите ваш Raspberry Pi и запустите Sonic Pi вместе с Minecraft. В Minecraft создайте новый мир, а в Sonic Pi выберите пустой буфер и наберите там этот код:
mc_message "Let's get started..."
Нажмите кнопку “Выполнить”, и вы увидите сообщение в окне Minecraft. Хорошо, мы готовы начать, давайте повеселимся……
Когда мы используем Minecraft для создания визуальных эффектов, мы стараемся думать о том, чтобы это одновременно выглядело интересно и легко генерировалось с помощью кода. Интересный трюк - создать песчаную бурю, сбрасывая песчаные блоки с неба. Для этого нам необходимы несколько базовых функций:
sleep
- для того, чтобы вставить задержку между действиями
mc_location
- для нахождения нашего текущего местоположения
mc_set_block
- чтобы поместить песчаный блок в заданное место
rrand
- для генерации случайных чисел в заданном диапазоне
live_loop
- позволяет сделать дождь из песка непрерывным
Если вы не знакомы с какой-либо из встроенных функций, например rrand
, просто введите её название в свой буфер, кликните на нём, а затем используйте клавиатурное сочетание Ctrl-i
, вызвающее встроенную документацию. В качестве альтернативы вы можете переместиться к вкладке язык справочной системы, и найти нужную функцию прямо там, как и другие интересные вещи.
Прежде чем высвободить всю мощь бури, сделаем небольшой дождь. Возьмём ваше текущее местоположение и используем его для создания нескольких песчаных блоков в небе неподалеку:
x, y, z = mc_location
mc_set_block :sand, x, y + 20, z + 5
sleep 2
mc_set_block :sand, x, y + 20, z + 6
sleep 2
mc_set_block :sand, x, y + 20, z + 7
sleep 2
mc_set_block :sand, x, y + 20, z + 8
Когда вы нажмете “Выполнить”, вам, возможно, придется немного осмотреться, так как блоки могут начать падать позади вас в зависимости от того, в каком направлении вы в настоящее время смотрите. Не волнуйтесь, если вы пропустили их, просто нажмите “Выполнить” снова, и вы получите ещё одну порцию песчаного дождя - просто убедитесь, что вы смотрите в правильном направлении!
Давайте быстро посмотрим, что тут у нас происходит. На первой строке мы получили местоположение Стива в виде координат с помощью функции mc_location
и присвоили их переменным х
, у
, и z
. На последующих строках мы использовали функциию mc_set_block
, чтобы поместить немного песка в тех же координатах, что и Стив но с некоторыми изменениями. Мы выбрали ту же координату x
, но координата y
стала на 20 блоков выше, а затем последовательно увеличивали z
, чтобы песок падал в линию, удаляющуюся от Стива.
Почему бы вам самим не поиграть с этим кодом? Попробуйте добавить больше линий, изменените время задержки, попробуйте смешать :sand
с :gravel
и выбирать разные координаты. Просто экспериментируйте и веселитесь!
Хорошо, пришло время получить бушующую бурю, высвободив мощь live_loop
- магической способности Sonic Pi, раскрывающей всю силу лайвкодинга - изменения кода в реальном времени!
live_loop :sand_storm do
x, y, z = mc_location
xd = rrand(-10, 10)
zd = rrand(-10, 10)
co = rrand(70, 130)
synth :cnoise, attack: 0, release: 0.125, cutoff: co
mc_set_block :sand, x + xd, y + 20, z + zd
sleep 0.125
end
Это весело! Мы выполняем круг цикла довольно быстро (8 раз в секунду), и во время каждого круга мы находим местоположение Стива также, как и раньше, но в этот раз генерируем 3 случайных значения:
xd
- разница для x
, которая будет находиться между -10 и 10
zd
- разница для z
, которая также будет между -10 и 10
co
- значение среза фильтра низких частот между 70 и 130
Затем мы используем эти случайные значения в функциях synth
и mc_set_block
, что даёт нам песок, падающий случайным образом вокруг Стива вместе с перкуссионным звуком, похожим на дождь, от синтезатора :cnoise
.
Для тех из вас, кто ещё новичок в живых циклах - именно здесь и начинается самое интересное. Пока код выполняется и песок сыплется вниз, попробуйте изменить одно из значений. Например, время задержки на 0.25
, или тип блока с :sand
на :gravel
. Теперь нажмите “Выполнить” снова. Эй! Всё изменилось без остановки кода. Это и есть та самая дверь к выступлениям в роли виджея. Продолжайте тренироваться и что-нибудь изменять. Насколько разнообразными можно сделать визуальные эффекты, не останавливая кода?
Наконец, еще один отличный способ создания интересных визуальных эффектов - генерировать огромные повторяющиеся стены, парящие рядом. Для этого эффекта нам нужно перейти от размещения блоков случайным образом, к размещению их в упорядоченном виде. Мы можем сделать это с помощью двух вложеных итераций (нажмите кнопку “Помощь”, чтобы перейти в раздел 5.2 учебника “Итерации И Циклы”, чтобы узнать больше об итерациях). Аргумент |xd|
после do
означает, что xd
будет соответствовать номеру итерации. Поэтому сначала это будет 0, потом 1, потом 2… и т. д. Совмещая две итерации вместе, мы можем сгенерировать любые координаты. Затем мы произвольно выбираем типы блоков из кольцевого списка для создания интересного эффекта:
x, y, z = mc_location
bs = (ring :gold, :diamond, :glass)
10.times do |xd|
10.times do |yd|
mc_set_block bs.choose, x + xd, y + yd, z
end
end
Довольно аккуратно. В то время как мы здесь веселимся, попробуйте изменить bs.choose
, на bs.tick
, чтобы перейти от случайного выбора к последовательному. Попробуйте изменять типы блоков более радикально. Вы можете захотеть делать это с live_loop
, чтобы узоры продолжали изменяться автоматически.
Теперь, измените обе итерации с 10.times
на 100.times
и нажмите “Выполнить”. Бабах! Огромные исполинские стены с хаотично расположенными кирпичами. Представьте сколько времени потребуется вам, чтобы построить их вручную с помощью мыши! Дважды нажмите пробел, чтобы переключиться в режим полёта и начните парить для создания замечательных визуальных эффектов. Не останавливайтесь на этом, используйте своё воображение, чтобы придумать интересные идеи, а затем, использовав силу кода Sonic Pi, сделайте это реальностью. Когда вы достаточно попрактикуетесь, приглушите свет и покажите шоу своим друзьям!
Back in episode 4 of this tutorial series we took a brief look at randomisation whilst coding up some sizzling synth riffs. Given that randomisation is such an important part of my live coding DJ sets I thought it would be useful to cover the fundamentals in much greater detail. So, get your lucky hat on and let’s surf some random streams!
The first thing to learn which might really surprise you when playing with Sonic Pi’s randomisation functions is that they’re not actually really random. What does this actually mean? Well, let’s try a couple of tests. First, imagine a number in your head between 0 and 1. Keep it there and don’t tell me. Now let me guess… was it 0.321567
? No? Bah, I’m clearly no good at this. Let me have another go, but let’s ask Sonic Pi to choose a number this time. Fire up Sonic Pi v2.7+ and ask it for a random number but again don’t tell me:
print rand
Now for the reveal… was it 0.75006103515625
? Yes! Ha, I can see you’re a little sceptical. Perhaps it was just a lucky guess. Let’s try again. Press the Run button again and see what we get… What? 0.75006103515625
again? This clearly can’t be random! You’re right, it’s not.
What’s going on here? The fancy computer science word here is determinism. This just means that nothing is by chance and everything is destined to be. Your version of Sonic Pi is destined to always return 0.75006103515625
in the program above. This might sound pretty useless, but let me assure you that it’s one of the most powerful parts of Sonic Pi. If you stick at it you’ll learn how to rely on the deterministic nature of Sonic Pi’s randomisation as a fundamental building block for your compositions and live coded DJ sets.
When Sonic Pi boots it actually loads into memory a sequence of 441,000 pre-generated random values. When you call a random function such as rand
or rrand
, this random stream is used to generate your result. Each call to a random function consumes a value from this stream. Therefore the 10th call to a random function will use the 10th value from the stream. Also, every time you press the Run button, the stream is reset for that run. This is why I could predict the result to rand
and why the ‘random’ melody was the same every time. Everybody’s version of Sonic Pi uses the exact same random stream which is very important when we start sharing our pieces with each other.
Let’s use this knowledge to generate a repeatable random melody:
8.times do
play rrand_i(50, 95)
sleep 0.125
end
Type this into a spare buffer and hit Run. You’ll hear a melody consisting of ‘random’ notes between 50 and 95. When it’s finished, hit Run again to hear exactly the same melody again.
Sonic Pi comes with a number of useful functions for working with the random stream. Here’s a list of some of the most useful:
rand
- Simply returns the next value in the random stream
rrand
- Returns a random value within a range
rrand_i
- Returns a random whole number within a range
one_in
- Returns true or false with the given probability
dice
- Imitates rolling a dice and returns a value between 1 and 6
choose
- Chooses a random value from a list
Check out their documentation in the Help system for detailed information and examples.
Whilst the ability to repeat a sequence of chosen notes is essential to allow you to replay a riff on the dance floor, it might not be exactly the riff you want. Wouldn’t it be great if we could try a number of different riffs and choose the one we liked best? This is where the real magic starts.
We can manually set the stream with the fn use_random_seed
. In Computer Science, a random seed is the starting point from which a new stream of random values can sprout out and blossom. Let’s try it:
use_random_seed 0
3.times do
play rrand_i(50, 95)
sleep 0.125
end
Great, we get the first three notes of our random melody above: 84
, 83
and 71
. However, we can now change the seed to something else. How about this:
use_random_seed 1
3.times do
play rrand_i(50, 95)
sleep 0.125
end
Interesting, we get 83
, 71
and 61
. You might notice that the first two numbers here are the same as the last two numbers before - this isn’t a coincidence.
Remember that the random stream is just a giant list of ‘pre-rolled’ values. Using a random seed simply jumps us to a point in that list. Another way of thinking about it is to imagine a huge deck of pre-shuffled cards. Using a random seed is cutting the deck at a particular point. The fabulous part of this is that it’s precisely this ability to jump around the random stream which gives us huge power when making music.
Let’s revisit our random melody of 8 notes with this new stream resetting power, but let’s also throw in a live loop so we can experiment live whilst it’s playing:
live_loop :random_riff do
use_random_seed 0
8.times do
play rrand_i(50, 95), release: 0.1
sleep 0.125
end
end
Now, whilst it’s still playing, change the seed value from 0
to something else. Try 100
, what about 999
. Try your own values, experiment and play around - see which seed generates the riff you like best.
This month’s tutorial has been quite a technical dive into the workings of Sonic Pi’s randomisation functionality. Hopefully it has given you some insight into how it works and how you can start using randomisation in a reliable way to create repeatable patterns within your music. It’s important to stress that you can use repeatable randomisation anywhere you want. For example, you can randomise the amplitude of notes, the timing of the rhythm, the amount of reverb, the current synth, the mix of an FX, etc. etc. In the future we’ll take a close look at some of these applications, but for now let me leave you with a short example.
Type the following into a spare buffer, hit Run, and then start changing the seeds around, hit Run again (whilst it’s still playing) and explore the different sounds, rhythms and melodies you can make. When you find a nice one, remember the seed number so you can get back to it. Finally, when you’ve found a few seeds you like, put on a live coded performance for your friends by simply switching between your favourite seeds to create a full piece.
live_loop :random_riff do
use_random_seed 10300
use_synth :prophet
s = [0.125, 0.25, 0.5].choose
8.times do
r = [0.125, 0.25, 1, 2].choose
n = (scale :e3, :minor).choose
co = rrand(30, 100)
play n, release: r, cutoff: co
sleep s
end
end
live_loop :drums do
use_random_seed 2001
16.times do
r = rrand(0.5, 10)
sample :drum_bass_hard, rate: r, amp: rand
sleep 0.125
end
end
So far during this series we’ve focussed on triggering sounds. We’ve discovered that we can trigger the many synths built into Sonic Pi with play
or synth
and how to trigger pre-recorded samples with sample
. We’ve also looked at how we can wrap these triggered sounds within studio FX such as reverb and distortion using the with_fx
command. Combine this with Sonic Pi’s incredibly accurate timing system and you can produce a vast array of sounds, beats and riffs. However, once you’ve carefully selected a particular sound’s options and triggered it, there’s no ability to mess with it whilst it’s playing right? Wrong! Today you’re going to learn something very powerful - how to control running synths.
Let’s create a nice simple sound. Fire up Sonic Pi and in a fresh buffer type the following:
synth :prophet, note: :e1, release: 8, cutoff: 100
Now press the Run button at the top left to hear a lovely rumbling synth sound. Go ahead, press it again a few times to get a feel for it. OK, done? Let’s start controlling it!
A little known feature in Sonic Pi is that the fns play
, synth
and sample
, return something called a SynthNode
which represents a running sound. You can capture one of these SynthNode
s using a standard variable and then control it at a later point in time. For example, let’s change the value of the cutoff:
opt after 1 beat:
sn = synth :prophet, note: :e1, release: 8, cutoff: 100
sleep 1
control sn, cutoff: 130
Let’s look at each line in turn:
Firstly we trigger the :prophet
synth using the synth
fn as normal. However we also capture the result in a variable called sn
. We could have called this variable something completely different such as synth_node
or jane
- the name doesn’t matter. However, it’s important to choose a name that’s meaningful to you for your performances and for people reading your code. I chose sn
as it’s a nice short mnemonic for synth node.
On line 2 we have a standard sleep
command. This does nothing special - it just asks the computer to wait for 1 beat before moving onto the next line.
Line 3 is where the control fun starts. Here, we use the control
fn to tell our running SynthNode
to change the cutoff value to 130
. If you hit the Run button, you’ll hear the :prophet
synth start playing as before, but after 1 beat it will shift to sound a lot brighter.
Modulatable Options
Most of Sonic Pi’s synths and FX opts may be changed after being triggered. However, this isn’t the case for all of them. For example, the envelope opts attack:
, decay:
, sustain:
and release:
can only be set when triggering the synth. Figuring out which opts can and can’t be changed is simple - just head to the documentation for a given synth or FX and then scroll down to the individual option documentation and look for the phrases “May be changed whilst playing” or “Can not be changed once set”. For example, the documentation for the :beep
synth’s attack:
opt makes it clear that it’s not possible to change it:
Default: 0 Must be zero or greater Can not be changed once set Scaled with current BPM value
Whilst a synth is running you’re not limited to changing it only once - you’re free to change it as many times as you like. For example, we can turn our :prophet
into a mini arpeggiator with the following:
notes = (scale :e3, :minor_pentatonic)
sn = synth :prophet, note: :e1, release: 8, cutoff: 100
sleep 1
16.times do
control sn, note: notes.tick
sleep 0.125
end
In this snippet of code we just added a couple of extra things. First we defined a new variable called notes
which contains the notes we’d like to cycle through (an arpeggiator is just a fancy name for something that cycles through a list of notes in order). Secondly we replaced our single call to control
with an iteration calling it 16 times. In each call to control
we .tick
through our ring of notes
which will automatically repeat once we get to the end (thanks to the fabulous power of Sonic Pi’s rings). For a bit of variety try replacing .tick
with .choose
and see if you can hear the difference.
Note that we can change multiple opts simultaneously. Try changing the control line to the following and listen for the difference:
control sn, note: notes.tick, cutoff: rrand(70, 130)
When we control a SynthNode
, it responds exactly on time and instantly changes the value of the opt to the new one as if you’d pressed a button or flicked a switch requesting the change. This can sound rhythmical and percussive - especially if the opt controls an aspect of the timbre such as cutoff:
. However, sometimes you don’t want the change to happen instantaneously. Instead, you might want to smoothly move from the current value to the new one as if you’d moved a slider or dial. Of course, Sonic Pi can also do this too using the _slide:
opts.
Each opt that can be modified also has a special corresponding _slide:
opt that allows you to specify a slide time. For example, amp:
has amp_slide:
and cutoff:
has cutoff_slide:
. These slide opts work slightly differently than all the other opts in that they tell the synth note how to behave next time they are controlled. Let’s take a look:
sn = synth :prophet, note: :e1, release: 8, cutoff: 70, cutoff_slide: 2
sleep 1
control sn, cutoff: 130
Notice how this example is exactly the same as before except with the addition of cutoff_slide:
. This is saying that next time this synth has its cutoff:
opt controlled, it will take 2 beats to slide from the current value to the new one. Therefore, when we use control
you can hear the cutoff slide from 70 to 130. It creates an interesting dynamic feel to the sound. Now, try changing the cutoff_slide:
time to a shorter value such as 0.5 or a longer value such as 4 to see how it changes the sound. Remember, you can slide any of the modifiable opts in exactly this way and each _slide:
value can be totally different so you can have the cutoff sliding slowly, the amp sliding fast and the pan sliding somewhere in between if that’s what you’re looking to create…
Let’s look at a short example which demonstrates the power of controlling synths after they’ve been triggered. Notice that you can also slide FX just like synths although with a slightly different syntax. Check out section 7.2 of the built-in tutorial for more information on controlling FX.
Copy the code into a spare buffer and take a listen. Don’t stop there though - play around with the code. Change the slide times, change the notes, the synth, the FX and the sleep times and see if you can turn it into something completely different!
live_loop :moon_rise do
with_fx :echo, mix: 0, mix_slide: 8 do |fx|
control fx, mix: 1
notes = (scale :e3, :minor_pentatonic, num_octaves: 2).shuffle
sn = synth :prophet , sustain: 8, note: :e1, cutoff: 70, cutoff_slide: 8
control sn, cutoff: 130
sleep 2
32.times do
control sn, note: notes.tick, pan: rrand(-1, 1)
sleep 0.125
end
end
end
Last month in this series we took a deep technical dive into the randomisation system underpinning Sonic Pi. We explored how we can use it to deterministically add new levels of dynamic control over our code. This month we’re going to continue our technical dive and turn our attention to Sonic Pi’s unique tick system. By the end of this article you’ll be ticking your way through rhythms and riffs on your way to being a live coding DJ.
When making music we often want to do a different thing depending on which beat it is. Sonic Pi has a special beat counting system called tick
to give you precise control over when a beat actually occurs and even supports multiple beats with their own tempos.
Let’s have a play - to advance the beat we just need to call tick
. Open up a fresh buffer, type in the following and hit Run:
puts tick #=> 0
This will return the current beat: 0
. Notice that even if you press the Run button a few times it will always return 0
. This is because each run starts a fresh beat counting from 0. However, whilst the run is still active, we can advance the beat as many times as we want:
puts tick #=> 0
puts tick #=> 1
puts tick #=> 2
Whenever you see the symbol #=>
at the end of a line of code it means that that line will log the text on the right-hand-side. For example, puts foo #=> 0
means the code puts foo
prints 0
to the log at that point in the program.
We’ve seen that tick
does two things. It increments (adds one) and returns the current beat. Sometimes we just want to look at the current beat without having to increment it which we can do via look
:
puts tick #=> 0
puts tick #=> 1
puts look #=> 1
puts look #=> 1
In this code we tick the beat up twice and then call look
twice. We’ll see the following values in the log: 0
, 1
, 1
, 1
. The first two tick
s returned 0
, then 1
as expected, then the two look
s just returned the last beat value twice which was 1
.
So now we can advance the beat with tick
and check the beat with look
. What next? We need something to tick over. Sonic Pi uses rings for representing riffs, melodies and rhythms and the tick system has been specifically designed to work very closely with them. In fact, rings have their own dot version of tick
which does two things. Firstly, it acts like a regular tick and increments the beat. Secondly it looks up the ring value using the beat as the index. Let’s take a look:
puts (ring :a, :b, :c).tick #=> :a
.tick
is a special dot version of tick
which will return the first value of the ring :a
. We can grab each of the values in the ring by calling .tick
multiple times:
puts (ring :a, :b, :c).tick #=> :a
puts (ring :a, :b, :c).tick #=> :b
puts (ring :a, :b, :c).tick #=> :c
puts (ring :a, :b, :c).tick #=> :a
puts look #=> 3
Take a look at the log and you’ll see :a
, :b
, :c
and then :a
again. Notice that look
returns 3
. Calls to .tick
act just like they are regular calls to tick
- they increment the local beat.
The real power comes when you mix tick
with rings and live_loop
s. When combined we have all the tools we need to both build and understand a simple arpegiator. We need just four things:
A ring containing the notes we want to loop over. A means of incrementing and obtaining the beat. The ability to play a note based on the current beat. A loop structure to keep the arpegiator repeating.
These concepts can all be found in the following code:
notes = (ring 57, 62, 55, 59, 64)
live_loop :arp do
use_synth :dpulse
play notes.tick, release: 0.2
sleep 0.125
end
Let’s look at each of these lines. First we define our ring of notes which we’ll continually play. We then create a live_loop
called :arp
which loops round for us. Each time round the live_loop
we set our synth to :dpulse
and then play the next note in our ring using .tick
. Remember that this will increment our beat counter and use the latest beat value as an index into our notes ring. Finally, we wait for an eighth of a beat before looping round again.
A really important thing to know is that tick
s are local to the live_loop
. This means that each live_loop
has its own independent beat counter. This is much more powerful than having a global metronome and beat. Let’s take a look at this in action:
notes = (ring 57, 62, 55, 59, 64)
with_fx :reverb do
live_loop :arp do
use_synth :dpulse
play notes.tick + 12, release: 0.1
sleep 0.125
end
end
live_loop :arp2 do
use_synth :dsaw
play notes.tick - 12, release: 0.2
sleep 0.75
end
A big cause of confusion with Sonic Pi’s tick system is when people want to tick over multiple rings in the same live_loop
:
use_bpm 300
use_synth :blade
live_loop :foo do
play (ring :e1, :e2, :e3).tick
play (scale :e3, :minor_pentatonic).tick
sleep 1
end
Even though each live_loop
has its own independent beat counter, we’re calling .tick
twice within the same live_loop
. This means that the beat will be incremented twice every time round. This can produce some interesting polyrhythms but is often not what you want. There are two solutions to this problem. One option is to manually call tick
at the start of the live_loop
and then use .look
to look up the current beat in each live_loop
. The second solution is to pass a unique name to each call to .tick
such as .tick(:foo)
. Sonic Pi will then create and track a separate beat counter for each named tick you use. That way you can work with as many beats as you need! See the section on named ticks in 9.4 of the built-in tutorial for more information.
Let’s bring all this knowledge of tick
s, ring
s and live_loop
s together for a final fun example. As usual, don’t treat this as a finished piece. Start changing things and play around with it and see what you can turn it into. See you next time…
use_bpm 240
notes = (scale :e3, :minor_pentatonic).shuffle
live_loop :foo do
use_synth :blade
with_fx :reverb, reps: 8, room: 1 do
tick
co = (line 70, 130, steps: 32).tick(:cutoff)
play (octs :e3, 3).look, cutoff: co, amp: 2
play notes.look, amp: 4
sleep 1
end
end
live_loop :bar do
tick
sample :bd_ada if (spread 1, 4).look
use_synth :tb303
co = (line 70, 130, steps: 16).look
r = (line 0.1, 0.5, steps: 64).mirror.look
play notes.look, release: r, cutoff: co
sleep 0.5
end
Way back in episode 3 of this Sonic Pi series we looked at how to loop, stretch and filter one of the most famous drum breaks of all time - the Amen Break. In this tutorial we’re going to take this one step further and learn how to slice it up, shuffle the slices and glue it back together in a completely new order. If that sounds a bit crazy to you, don’t worry, it will all become clear and you’ll soon master a powerful new tool for your live coded sets.
Before we get started let’s just take a brief moment to understand how to work with samples. By now, you’ve all hopefully played with Sonic Pi’s powerful sampler. If not, there’s no time like the present! Boot up your Raspberry Pi, launch Sonic Pi from the Programming menu, type the following into a fresh buffer and then hit the Run button to hear a pre-recorded drum beat:
sample :loop_amen
A recording of a sound is simply represented as data - lots of numbers between -1 and 1 which represent the peaks and troughs of the sound wave. If we play those numbers back in order, we get the original sound. However, what’s to stop us from playing them back in a different order and creating a new sound?
How are samples actually recorded? It’s actually pretty simple once you understand the basic physics of sound. When you make a sound - for example by hitting a drum, the noise travels through the air in a similar fashion to how the surface of a lake ripples when you throw a pebble into it. When those ripples reach your ears, your eardrum moves sympathetically and converts those movements into the sound you hear. If we wish to record and play back the sound, we therefore need a way of capturing, storing and reproducing those ripples. One way is to use a microphone which acts like an eardrum and moves back and forth as the sound ripples hit it. The microphone then converts its position into a tiny electric signal which is then measured many times a second. These measurements are then represented as a series of numbers between -1 and 1.
If we were to plot a visualisation of the sound it would be a simple graph of data with time on the x axis and microphone/speaker position as a value between -1 and 1 on the y axis. You can see an example of such a graph at the top of the diagram.
So, how do we code Sonic Pi to play a sample back in a different order? To answer this question we need to take a look at the start:
and finish:
opts for sample
. These let us control the start and finish positions of our playback of the numbers which represent the sound. The values for both of these opts are represented as a number between 0
and 1
where 0
represents the start of the sample and 1
is the end. So, to play the first half of the Amen Break, we just need to specify a finish:
of 0.5
:
sample :loop_amen, finish: 0.5
We can add in a start:
value to play an even smaller section of the sample:
sample :loop_amen, start: 0.25, finish: 0.5
For fun, you can even have the finish:
opt’s value be before start:
and it will play the section backwards:
sample :loop_amen, start: 0.5, finish: 0.25
Now that we know that a sample is simply a list of numbers that can be played back in any order and also how to play a specific part of a sample we can now start having fun playing a sample back in the ‘wrong’ order.
Let’s take our Amen Break and chop it up into 8 equally-sized slices and then shuffle the pieces around. Take a look at the diagram: at the top A) represents the graph of our original sample data. Chopping it into 8 slices gives us B) - notice that we’ve given each slice a different colour to help distinguish them. You can see each slice’s start and finish values at the top. Finally C) is one possible re-ordering of the slices. We can then play this back to create a new beat. Take a look at the code to do this:
live_loop :beat_slicer do
slice_idx = rand_i(8)
slice_size = 0.125
s = slice_idx * slice_size
f = s + slice_size
sample :loop_amen, start: s, finish: f
sleep sample_duration :loop_amen, start: s, finish: f
end
we choose a random slice to play which should be a random number between 0 and 7 (remember that we start counting at 0). Sonic Pi has a handy function for exactly this: rand_i(8)
. We then store this random slice index in the variable slice_idx
.
We define our slice_size
which is 1/8 or 0.125. The slice_size
is necessary for us to convert our slice_idx
into a value between 0 and 1 so we can use it as our start:
opt.
We calculate the start position s
by multiplying the slice_idx
by the slice_size
.
We calculate the finish position f
by adding the slice_size
to the start position s
.
We can now play the sample slice by plugging in the s
and f
values into the start:
and finish:
opts for sample
.
Before we play the next slice we need to know how long to sleep
which should be the duration of the sample slice. Luckily, Sonic Pi has us covered with sample_duration
which accepts all the same opts as sample
and simply returns the duration. Therefore, by passing sample_duration
our start:
and finish:
opts, we can find out the duration of a single slice.
We wrap all of this code in a live_loop
so that we continue to pick new random slices to play.
Let’s combine everything we’ve seen so far into a final example which demonstrates how we can take a similar approach to combine randomly sliced beats with some bass to create the start of an interesting track. Now it’s your turn - take the code below as a starting point and see if you can take it in your own direction and create something new…
live_loop :sliced_amen do
n = 8
s = line(0, 1, steps: n).choose
f = s + (1.0 / n)
sample :loop_amen, beat_stretch: 2, start: s, finish: f
sleep 2.0 / n
end
live_loop :acid_bass do
with_fx :reverb, room: 1, reps: 32, amp: 0.6 do
tick
n = (octs :e0, 3).look - (knit 0, 3 * 8, -4, 3 * 8).look
co = rrand(70, 110)
synth :beep, note: n + 36, release: 0.1, wave: 0, cutoff: co
synth :tb303, note: n, release: 0.2, wave: 0, cutoff: co
sleep (ring 0.125, 0.25).look
end
end
In a previous episode of this Sonic Pi series we explored the power of randomisation to introduce variety, surprise and change into our live coded tracks and performances. For example, we randomly picked notes from a scale to create never-ending melodies. Today we’re going to learn a new technique which uses randomisation for rhythm - probabilistic beats!
Before we can start making new beats and synth rhythms we need to take a quick dive into the basics of probability. This might sound daunting and complicated, but really it’s just as simple as rolling a dice - honestly! When you take a regular 6 sided board game dice and roll it what’s actually happening? Well, firstly you’ll roll either a 1, 2, 3, 4, 5 or 6 with exactly the same chance of getting any of the numbers. In fact, given that it’s a 6 sided dice, on average (if you roll lots and lots of times) you’ll throw a 1 every 6 throws. This means you have a 1 in 6 chance of throwing a 1. We can emulate dice rolls in Sonic Pi with the fn dice
. Let’s roll one 8 times:
8.times do
puts dice
sleep 1
end
Notice how the log prints values between 1 and 6 just as if we’d rolled a real dice ourselves.
Послушаем, на что это похоже:
live_loop :random_beat do
sample :drum_snare_hard if dice == 1
sleep 0.125
end
Let’s quickly go over each line to make sure everything is very clear. First we create a new live_loop
called :random_beat
which will continually repeat the two lines between do
and end
. The first of these lines is a call to sample
which will play a pre-recorded sound (the :drum_snare_hard
sound in this case). However, this line has a special conditional if
ending. This means that the line will only be executed if the statement on the right hand side of the if
is true
. The statement in this case is dice == 1
. This calls our dice
function which, as we have seen, returns a value between 1 and 6. We then use the equality operator ==
to check to see if this value is 1
. If it is 1
, then the statement resolves to true
and our snare drum sounds, if it isn’t 1
then the statement resolves to false
and the snare is skipped. The second line simply waits for 0.125
seconds before rolling the dice again.
Those of you that have played role play games will be familiar with lots of strangely shaped dice with different ranges. For example there is the tetrahedron shaped dice which has 4 sides and even a 20 sided dice in the shape of a icosahedron. The number of sides on the dice changes the chance, or probability of rolling a 1. The fewer sides, the more likely you are to roll a 1 and the more sides the less likely. For example, with a 4 sided dice, there’s a one in 4 chance of rolling a 1 and with a 20 sided dice there’s a one in 20 chance. Luckily, Sonic Pi has the handy one_in
fn for describing exactly this. Let’s play:
live_loop :different_probabilities do
sample :drum_snare_hard if one_in(6)
sleep 0.125
end
Start the live loop above and you’ll hear the familiar random rhythm. However, don’t stop the code running. Instead, change the 6
to a different value such as 2
or 20
and hit the Run
button again. Notice that lower numbers mean the snare drum sounds more frequently and higher numbers mean the snare triggers fewer times. You’re making music with probabilities!
Things get really exciting when you combine multiple samples being triggered with different probabilities. For example:
live_loop :multi_beat do
sample :elec_hi_snare if one_in(6)
sample :drum_cymbal_closed if one_in(2)
sample :drum_cymbal_pedal if one_in(3)
sample :bd_haus if one_in(4)
sleep 0.125
end
Again, run the code above and then start changing the probabilities to modify the rhythm. Also, try changing the samples to create an entirely new feel. For example try changing :drum_cymbal_closed
to :bass_hit_c
for extra bass!
Next, we can use our old friend use_random_seed
to reset the random stream after 8 iterations to create a regular beat. Type the following code to hear a much more regular and repeating rhythm. Once you hear the beat, try changing the seed value from 1000
to another number. Notice how different numbers generate different beats.
live_loop :multi_beat do
use_random_seed 1000
8.times do
sample :elec_hi_snare if one_in(6)
sample :drum_cymbal_closed if one_in(2)
sample :drum_cymbal_pedal if one_in(3)
sample :bd_haus if one_in(4)
sleep 0.125
end
end
One thing I tend to do with this kind of structure is to remember which seeds sound good and make a note of them. That way I can easily re-create my rhythms in future practice sessions or performances.
Finally, we can throw in some random bass to give it some nice melodic content. Notice that we can also use our newly discovered probabilistic sequencing method on synths just as well as samples. Don’t leave it at that though - tweak the numbers and make your own track with the power of probabilities!
live_loop :multi_beat do
use_random_seed 2000
8.times do
c = rrand(70, 130)
n = (scale :e1, :minor_pentatonic).take(3).choose
synth :tb303, note: n, release: 0.1, cutoff: c if rand < 0.9
sample :elec_hi_snare if one_in(6)
sample :drum_cymbal_closed if one_in(2)
sample :drum_cymbal_pedal if one_in(3)
sample :bd_haus, amp: 1.5 if one_in(4)
sleep 0.125
end
end
This month we’re going to take a deep dive into one of Sonic Pi’s most powerful and flexible audio FX - the :slicer
. By the end of this article you will have learned how to manipulate the overall volume of parts of our live coded sound in powerful new ways. This will allow you to create new rhythmic and timbral structures and broaden your sonic possibilities.
So, what does the :slicer
FX actually do? One way to think about it is that it’s just like having someone play around with the volume control on your TV or home hi-fi. Let’s take a look but first, listen to the deep growl of the following code which triggers the :prophet
synth:
synth :prophet, note: :e1, release: 8, cutoff: 70
synth :prophet, note: :e1 + 4, release: 8, cutoff: 80
Now, let’s pipe it through the :slicer
FX:
with_fx :slicer do
synth :prophet, note: :e1, release: 8, cutoff: 70
synth :prophet, note: :e1 + 4, release: 8, cutoff: 80
end
Hear how the slicer acts like it’s muting and unmuting the audio with a regular beat. Also, notice how the :slicer
affects all the audio generated between the do
/end
blocks. You can control the speed at which it turns the audio on and off with the phase:
opt which is short for phase duration. Its default value is 0.25
which means 4 times a second at the default BPM of 60. Let’s make it faster:
with_fx :slicer, phase: 0.125 do
synth :prophet, note: :e1, release: 8, cutoff: 70
synth :prophet, note: :e1 + 4, release: 8, cutoff: 80
end
Now, play with different phase:
durations yourself. Try longer and shorter values. See what happens when you choose a really short value. Also, try different synths such as :beep
or :dsaw
and different notes. Take a look at the following diagram to see how different phase:
values change the number of amplitude changes per beat.
Phase duration is the length of time for one on/off cycle. Therefore smaller values will make the FX switch on and off much faster than larger values. Good values to start playing with are 0.125
, 0.25
, 0.5
and 1
.
By default, the :slicer
FX uses a square wave to manipulate the amplitude through time. This is why we hear the amplitude on for a period, then immediately off for a period, then back on again. It turns out that the square wave is just one of 4 different control waves that are supported by :slicer
. The others are saw, triangle and (co)sine. Take a look at the diagram below to see what these look like. We can also hear what they sound like. For example, the following code uses (co)sine as the control wave. Hear how the sound doesn’t turn on and off abruptly but instead smoothly fades in and out:
with_fx :slicer, phase: 0.5, wave: 3 do
synth :dsaw, note: :e3, release: 8, cutoff: 120
synth :dsaw, note: :e2, release: 8, cutoff: 100
end
Have a play with the different wave forms by changing the wave:
opt to 0
for saw, 1
for square, 2
for triangle and 3
for sine. See how different waves sound with different phase:
opts too.
Each of these waves can be inverted with the invert_wave:
opt which flips it on the y axis. For example, in a single phase the saw wave typically starts high, and slowly goes down before jumping back to the top. With invert_wave: 1
it will start low and slowly go up before jumping back down again. Additionally, the control wave can be started at different points with the phase_offset:
opt which should be a value between 0
and 1
. By playing around with phase:
, wave:
, invert_wave:
and phase_offset
opts you can dramatically change how the amplitude is modified through time.
By default, :slicer
switches between amplitude values 1
(fully loud) and 0
(silent). This can be changed with the amp_min:
and amp_max:
opts. You can use this alongside the sine wave setting to create a simple tremolo effect:
with_fx :slicer, amp_min: 0.25, amp_max: 0.75, wave: 3, phase: 0.25 do
synth :saw, release: 8
end
This is just like grabbing the volume knob on your hi-fi and moving it up and down just a little so the sound ‘wobbles’ in and out.
One of :slicer
’s powerful features is its ability to use probability to choose whether or not to turn the slicer on or off. Before the :slicer
FX starts a new phase it rolls a dice and based on the result either uses the selected control wave or keeps the amplitude off. Let’s take a listen:
with_fx :slicer, phase: 0.125, probability: 0.6 do
synth :tb303, note: :e1, cutoff_attack: 8, release: 8
synth :tb303, note: :e2, cutoff_attack: 4, release: 8
synth :tb303, note: :e3, cutoff_attack: 2, release: 8
end
Hear how we now have an interesting rhythm of pulses. Try changing the probability:
opt to a different value between 0
and 1
. Values closer to 0
will have more space between each sound due to the likelihood of the sound being triggered being much lower.
Another thing to notice is that the probability system in the FX is just like the randomisation system accessible via fns such as rand
and shuffle
. They are both completely deterministic. This means that each time you hit Run you’ll hear exactly the same rhythm of pulses for a given probability. If you would like to change things around you can use the seed:
opt to select a different starting seed. This works exactly the same as use_random_seed
but only affects that particular FX.
Finally, you can change the ‘resting’ position of the control wave when the probability test fails from 0
to any other position with the prob_pos:
opt:
with_fx :slicer, phase: 0.125, probability: 0.6, prob_pos: 1 do
synth :tb303, note: :e1, cutoff_attack: 8, release: 8
synth :tb303, note: :e2, cutoff_attack: 4, release: 8
synth :tb303, note: :e3, cutoff_attack: 2, release: 8
end
One really fun thing to do is to use :slicer
to chop a drum beat in and out:
with_fx :slicer, phase: 0.125 do
sample :loop_mika
end
This allows us to take any sample and create new rhythmical possibilites which is a lot of fun. However, one thing to be careful about is to make sure that the tempo of the sample matches the current BPM in Sonic Pi otherwise the slicing will sound totally off. For example, try swapping :loop_mika
with the loop_amen
sample to hear how bad this can sound when the tempos don’t align.
As we have already seen, changing the default BPM with use_bpm
will make all the sleep times and synth envelope durations grow or shrink to match the beat. The :slicer
FX honours this too, as the phase:
opt is actually measured in beats not seconds. We can therefore fix the issue with loop_amen
above by changing the BPM to match the sample:
use_sample_bpm :loop_amen
with_fx :slicer, phase: 0.125 do
sample :loop_amen
end
Let’s apply all these ideas into a final example that only uses the :slicer
FX to create an interesting combination. Go ahead, start changing it and make it into your own piece!
live_loop :dark_mist do
co = (line 70, 130, steps: 8).tick
with_fx :slicer, probability: 0.7, prob_pos: 1 do
synth :prophet, note: :e1, release: 8, cutoff: co
end
with_fx :slicer, phase: [0.125, 0.25].choose do
sample :guit_em9, rate: 0.5
end
sleep 8
end
live_loop :crashing_waves do
with_fx :slicer, wave: 0, phase: 0.25 do
sample :loop_mika, rate: 0.5
end
sleep 16
end
In this month’s Sonic Pi tutorial we’re going to take a look at how you can start treating Sonic Pi like a real instrument. We therefore need to start thinking of code in a completely different way. Live coders think of code in a similar way to how violinists think of their bow. In fact, just like a violinist can apply various bowing techniques to create different sounds (long slow motions vs short fast hits) we will explore five of the basic live coding techniques that Sonic Pi enables. By the end of this article you’ll be able to start practicing for your own live coded performances.
The first tip to live coding with Sonic Pi is to start using the shortcuts. For example, instead of wasting valuable time reaching for the mouse, moving it over to the Run button and clicking, you can simply press alt
and r
at the same time which is much faster and keeps your fingers at the keyboard ready for the next edit. You can find out the shortcuts for the main buttons at the top by hovering the mouse over them. See section 10.2 of the built-in tutorial for the full list of shortcuts.
When performing, one fun thing to do is to add a bit of flair with your arm motion when hitting shortcuts. For example, it’s often good to communicate to the audience when you’re about to make a change - so embellish your movement when hitting alt-r
just like a guitarist would do when hitting a big power chord.
Now you can trigger code instantly with the keyboard, you can instantly apply this skill for our second technique which is to layer your sounds manually. Instead of ‘composing’ using lots of calls to play
, and sample
separated by calls to sleep
we will have one call to play
which we will manually trigger using alt-r
. Let’s try it. Type the following code into a fresh buffer:
synth :prophet, note: :e1, release: 8, cutoff: 100
Now, hit Run
and whilst the sound is playing, modify the code in order to drop down four notes by changing it to the following:
synth :prophet, note: :e1, release: 8, cutoff: 100
Now, hit Run
again, to hear both sounds playing at the same time. This is because Sonic Pi’s Run
button doesn’t wait for any previous code to finish, but instead starts the code running at the same time. This means you can easily layer lots of sounds manually with minor or major modifications between each trigger. For example, try changing both the note:
and the cutoff:
opts and then re-trigger.
Также вы можете опробовать эту технику с длинными абстрактными сэмплами:
sample :ambi_lunar_land
Try starting the sample off, and then progressively halving the rate:
opt between hitting Run
from 1
to 0.5
to 0.25
to 0.125
and then even try some negative values such as -0.5
. Layer the sounds together and see where you can take it. Finally, try adding some FX.
When performing, working with simple lines of code in this way means that an audience new to Sonic Pi has a good chance to follow what you’re doing and relate the code that they can read to the sounds they are hearing.
When working with more rhythmic music, it can often be hard to manually trigger everything and keep good time. Instead, it is often better to use a live_loop
. This provides repetition for your code whilst also giving the ability to edit the code for the next time round the loop. They also will run at the same time as other live_loop
s which means you can layer them together both with each other and manual code triggers. Take a look at section 9.2 of the built-in tutorial for more information about working with live loops.
When performing, remember to make use of live_loop
’s sync:
opt to allow you to recover from accidental runtime mistakes which stop the live loop running due to an error. If you already have the sync:
opt pointing to another valid live_loop
, then you can quickly fix the error and re-run the code to re-start things without missing a beat.
One of Sonic Pi’s best kept secrets is that it has a master mixer through which all sound flows. This mixer has both a low pass filter and a high pass filter built-in, so you can easily perform global modifications to the sound. The master mixer’s functionality can be accessed via the fn set_mixer_control!
. For example, whilst some code is running and making sound, enter this into a spare buffer and hit Run
:
set_mixer_control! lpf: 50
After you run this code, all existing and new sounds will have a low pass filter applied to them and will therefore sound more muffled. Note that this means that the new mixer values stick until they are changed again. However, if you want, you can always reset the mixer back to its default state with reset_mixer!
. Some of the currently supported opts are: pre_amp:
, lpf:
hpf:
, and amp:
. For the full list, see the built-in docs for set_mixer_control!
.
Use the mixer’s *_slide
opts to slide one or many opts values over time. For example, to slowly slide the mixer’s low pass filter down from the current value to 30, use the following:
set_mixer_control! lpf_slide: 16, lpf: 30
Затем вы можете быстро перескочить обратно к высокому значению:
set_mixer_control! lpf_slide: 1, lpf: 130
When performing, it’s often useful to keep a buffer free for working with the mixer like this.
The most important technique for live coding is practice. The most common attribute across professional musicians of all kinds is that they practice playing with their instruments - often for many hours a day. Practice is just as important for a live coder as a guitarist. Practice allows your fingers to memorise certain patterns and common edits so you can type and work with them more fluently. Practice also gives you opportunities to explore new sounds and code constructs.
When performing, you’ll find the more practice you do, the easier it will be for you to relax into the gig. Practice will also give you a wealth of experience to draw from. This can help you understand which kinds of modifications will be interesting and also work well with the current sounds.
This month, instead of giving you a final example that combines all the things discussed, let’s part by setting down a challenge. See if you can spend a week practicing one of these ideas every day. For example, one day practice manual triggers, the next do some basic live_loop
work and the following day play around with the master mixer. Then repeat. Don’t worry if things feel slow and clunky at first - just keep practicing and before you know it you’ll be live coding for a real audience.
Last month we took a look at five important techniques for mastering live coding - in other words, we explored how we could use Sonic Pi to approach code in the same way we would approach a musical instrument. One of the important concepts that we discussed was practice. This month we’re going to take a deeper dive into understanding why live coding practice is important and how you might start.
The most important piece of advice is to make sure you practice regularly. As a rule I typically practice for 1-2 hours a day, but 20 mins is just fine when you’re starting out. Little but often is what you’re aiming for - so if you can only manage 10 minutes, that’s a great start.
Practice tip #1 - start to develop a practice routine. Find a nice time in the day that works for you and try and practice at that time as many days of the week as you can. Before long you’ll be looking forward to your regular session.
If you watch a professional musician performing on stage you’ll likely notice a few things. Firstly, when they play they don’t stare at their instrument. Their fingers, arms and bodies know which keys to press, strings to pluck or drums to hit without them having to think about it too much. This is known as “muscle memory” and although it might sound like something only professionals can do - it’s just the same as when you first learned to walk or ride a bike - practicing through repetition. Live coders use muscle memory to free their minds from having to think about where to move their fingers so they can focus on the music. This is called touch-typing - typing without having to look at the keyboard.
Практический совет #2 - тренируйтесь слепой печати. Есть множество приложений, вебсайтов и даже игр, которые могут помочь вам в этом деле. Найдите то, которое вам нравится и поупражняйтесь в нём, пока у вас не получится набирать текст не глядя.
The body of a musician is conditioned for playing their instrument. For example, a trumpet player needs to be able to blow hard, a guitar player needs to be able to grip the fretboard with strength and a drummer needs to be able to continually hit the drums for long periods of time. So, what’s physical about live coding? Just like DJs, live coders typically perform whilst standing up and some even dance whilst they code! If you practice live coding whilst sitting at a desk and then have to get up and stand at a gig, you’ll likely find the difference very difficult and frustrating.
Practice tip #3 - stand whilst you practice. The easiest way to do this is to use a standing height desk. However, if like me you don’t have one at home, there’s a couple of low-fi options. The approach I take is to use an ironing board which happens to work rather well. Another is to stack some boxes or large books on a normal desk and place your keyboard on top of that. Also, make sure you stretch before you start practicing and try and dance a little during the session. Remember, nobody is watching you, so have fun and you’ll feel much more natural on stage.
Most instruments require some assembly and tuning before they can be played. Unless you’re a rockstar with a bus full of roadies, you’ll have to set up your own instrument before your gig. This is often a stressful time and it is easy for problems to occur. One way to help with this is to incorporate the setup process into your practice sessions.
Practice tip #4 - treat setting up as an important part of your practice. For example, have a box or bag that you can keep your Raspberry Pi and keyboard in etc. Before each practice session, take out all the parts, connect everything, and work through the boot process until you have Sonic Pi running and can make sounds. Once you’ve finished practicing, take the time to carefully pack everything away afterwards. This may take some time at first, but before long you’ll be able to setup and pack everything away incredibly quickly without having to think about it.
Once you’ve set up and are ready to start making music, you might find yourself struggling to know where to start. One problem many people face is that they might have a good idea of the kinds of sounds they want to make, but are frustrated that they can’t produce them. Some people don’t even know what kind of sounds they want to make! The first thing to do is not to worry - this is very common and happens to every musician - even if they’ve been practicing for a long time. It is much more important to be making sounds you don’t like than not making any sounds at all.
Practice tip #5 - spend time making sounds and music you don’t like. Try to make time to explore new sounds and ideas. Don’t worry that it might sound terrible if it’s not the style you’re looking for. When you’re experimenting like this you increase the chance of stumbling over a sound or combination of sounds which you love! Even if 99% of the sounds you make are bad, that 1% might be the riff or intro to your new track. Forget the things you don’t like and remember the parts you do. This is even easier when you’re making music with code - just hit save!
Many musicians can look at a musical score and hear the music in their head without having to play it. This is a very useful skill and it’s well worth incorporating into your live coding practice sessions. The imporant point is to be able to have some understanding of what the code is going to sound like. You don’t need to be able to hear it exactly in your head, but instead it’s useful to know if the code is going to be fast, slow, loud, rhythmic, melodic, random, etc. The final goal is then to be able to reverse this process - to be able to hear music in your head and know what code to write to make it. It may take you a long time to master this, but once you do, you’ll be able to improvise on stage and express your ideas fluently.
Practice tip #6 - write some code into Sonic Pi but don’t hit the Run button. Instead, try to imagine what sound it is going to produce. Then, hit Run, listen, and think about what you got right and what you didn’t. Keep repeating this until it become a natural part of your coding process. When I practice I normally have a good idea of what the code will sound like. However, I still am occasionally surprised, and then I’ll stop and spend some time thinking about why I was wrong. Each time this happens, I learn new tricks which allow me to express myself in new ways.
A common problem when practicing is to become distracted with other things. Practicing is hard and requires real discipline regardless of the kind of music you’re making - from jazz to classical to EDM. If you’re struggling to get started or make progress, it’s often too easy to hop on social media, or look something up on the internet etc. If you’ve set yourself a target of 20 minutes of practice, it’s important to try and spend all that time being as productive as possible.
Practice tip #7 - before you start practicing remove as many distractions as possible. For example, disconnect from the internet, put your phone in another room and try to practice in a quiet place where you’re unlikely to be disturbed. Try to focus on coding music and you can return to your distractions when you’ve finished.
When you are practicing, you’ll often find your mind is full of new exciting ideas - new musical directions, new sounds to try out, new functions to write, etc. These ideas are often so interesting that you might stop what you’re doing and start working on the idea. This is another form of distraction!
Practice tip #8 - keep a practice diary by your keyboard. When you get an exciting new idea, temporarily pause your practice session, quickly jot the idea down, then forget about it and carry on practicing. You can then spend some quality time thinking about and working on your ideas after you’ve finished practicing.
Try to establish a practice routine which incorporates as many of these ideas as possible. Try to keep the sessions as fun as possible but be aware that some practice sessions will be hard and feel a little like work. However, it will all be worth it once you’ve created your first piece or given your first performance. Remember, practice is the key to success!
When people discover Sonic Pi, one of the first things they learn is how simple it is to play pre-recorded sounds using the sample
function. For example, you can play an industrial drum loop, hear the sound of a choir or even listen to a vinyl scratch all via a single line of code. However, many people don’t realise that you can actually vary the speed that the sample is played back at for some powerful effects and a whole new level of control over your recorded sounds. So, fire up a copy of Sonic Pi and let’s get started stretching some samples!
To modify the playback rate of a sample we need to use the rate:
opt:
sample :guit_em9, rate: 0.5
If we specify a rate:
of 1
then the sample is played back at the normal rate. If we want to play it back at half speed we simply use a rate:
of 0.5
:
sample :guit_em9, rate: 0.5
choose
In addition to making the sound longer and lower using a small rate, we can use higher rates to make the sound shorter and higher. Let’s play with a drum loop this time. First, take a listen to how it sounds at the default rate of 1
:
sample :loop_amen, rate: -1
Now, let’s speed it up a little:
sample :loop_amen, rate: 1.5
Ha! We just moved musical genres from old-skool techno to jungle. Notice how the pitch of each drum hit is higher as well as how the whole rhythm speeds up. Now, try even higher rates and see how high and short you can make the drum loop. For example, if you use a rate of 100
, the drum loop turns into a click!
Now, I’m sure many of you are thinking the same thing right now… “what if you use a negative number for the rate?”. Great question! Let’s think about this for a moment. If our rate:
opt signifies the speed with which the sample is played back, 1
being normal speed, 2
being double speed, 0.5
being half speed, -1
must mean backwards! Let’s try it on a snare. First, play it back at the normal rate:
sample :elec_filt_snare, rate: 1
Круто! Он играет задом-наперед!
sample :elec_filt_snare, rate: -1
Of course, you can play it backwards twice as fast with a rate of -2
or backwards at half speed with a rate of -0.5
. Now, play around with different negative rates and have fun. It’s particularly amusing with the :misc_burp
sample!
One of the effects of rate modification on samples is that faster rates result in the sample sounding higher in pitch and slower rates result in the sample sounding lower in pitch. Another place you may have heard this effect in every day life is when you’re cycling or driving past a beeping pedestrian crossing - as you’re heading towards the sound source the pitch is higher than when you’re moving away from the sound - the so-called Doppler effect. Why is this?
Let’s consider a simple beep which is represented by a sine wave. If we use an oscilloscope to plot a beep, we’ll see something like Figure A. If we plot a beep an octave higher, we’ll see Figure B and an octave lower will look like Figure C. Notice that the waves of higher notes are more compact and the waves of lower notes are more spread out.
A sample of a beep is nothing more than a lot of numbers (x, y, coordinates) which when plotted onto a graph will re-draw the original curves. See figure D where each circle represents a coordinate. To turn the coordinates back into audio, the computer works through each x value and sends the corresponding y value to the speakers. The trick here is that the rate at which the computer works through the x numbers does not have to be the same as the rate with which they were recorded. In other words, the space (representing an amount of time) between each circle can be stretched or compressed. So, if the computer walks through the x values faster than the original rate, it will have the effect of squashing the circles closer together which will result in a higher sounding beep. It will also make the beep shorter as we will work through all the circles faster. This is shown in Figure E.
Finally, one last thing to know is that a mathematician called Fourier proved that any sound is actually lots and lots of sine waves all combined together. Therefore, when we compress and stretch any recorded sound we’re actually stretching and compressing many sine waves all at the same time in exactly this manner.
As we’ve seen, using a faster rate will make the sound higher in pitch and a slower rate will make the sound lower in pitch. A very simple and useful trick is to know that doubling the rate actually results in the pitch being an octave higher and inversely halving the rate results in the pitch being an octave lower. This means that for melodic samples, playing it alongside itself at double/half rates actually sounds rather nice:
sample :bass_trance_c, rate: 1
sample :bass_trance_c, rate: 2
sample :bass_trance_c, rate: 0.5
However, what if we just want to alter the rate such that the pitch goes up one semitone (one note up on a piano)? Sonic Pi makes this very easy via the rpitch:
opt:
sample :bass_trance_c
sample :bass_trance_c, rpitch: 3
sample :bass_trance_c, rpitch: 7
If you take a look at the log on the right, you’ll notice that an rpitch:
of 3
actually corresponds to a rate of 1.1892
and a rpitch:
of 7
corresponds to a rate of 1.4983
. Finally, we can even combine rate:
and rpitch:
opts:
sample :ambi_choir, rate: 0.25, rpitch: 3
sleep 3
sample :ambi_choir, rate: 0.25, rpitch: 5
sleep 2
sample :ambi_choir, rate: 0.25, rpitch: 6
sleep 1
sample :ambi_choir, rate: 0.25, rpitch: 1
Let’s take a look at a simple piece which combines these ideas. Copy it into an empty Sonic Pi buffer, hit play, listen to it for a while and then use it as a starting point for your own piece. See how much fun it is to manipulate the playback rate of samples. As an added exercise try recording your own sounds and play around with the rate to see what crazy sounds you can make.
sample :guit_em9
This is the first of a short series of articles on how to use Sonic Pi for sound design. We’ll be take a quick tour of a number of different techniques available for you to craft your own unique sound. The first technique we’ll look at is called additive synthesis. This may sound complicated - but if we expand each word slightly the meaning pops right out. Firstly, additive means a combination of things and secondly synthesis means to create sound. Additive synthesis therefore means nothing more complicated than combining existing sounds to create new ones. This synthesis technique dates back a very long time - for example, pipe organs in the middle ages had lots of slightly different sounding pipes which you could enable or disable with stops. Pulling out the stop for a given pipe ‘added it to the mix’ making the sound richer and more complex. Now, let’s see how we can pull out all the stops with Sonic Pi.
Let’s start with the most basic sound there is - the humble pure-toned sine wave:
synth :sine, note: :d3
Now, let’s see how this sounds combined with a square wave:
synth :sine, note: :d3
synth :square, note: :d3
Notice how the two sounds combine to form a new, richer sound. Of course, we don’t have to stop there, we can add as many sounds as we need. However, we need to be careful with how many sounds we add together. Just like when we mix paints to create new colours, adding too many colours will result in a messy brown, similarly - adding too many sounds together will result in a muddy sound.
Let’s add something to make it sound a little brighter. We could use a triangle wave at an octave higher (for that high bright sound) yet only play it at amp 0.4
so it adds something extra to the sound rather than taking it over:
synth :sine, note: :d3
synth :square, note: :d3
synth :tri, note: :d4, amp: 0.4
Now, try creating your own sounds by combining 2 or more synths at different octaves and amplitudes. Also, note that you can play around with each synth’s opts to modify each source sound before it is mixed in for even more combinations of sounds.
So far, when combining our different synths we’ve used either the same pitch or switched octave. How might it sound if we didn’t stick to octaves but instead chose a slightly higher or lower note? Let’s try it:
detune = 0.7
synth :square, note: :e3
synth :square, note: :e3 + detune
If we detune our square waves by 0.7 notes we hear something that perhaps doesn’t sound in tune or correct - a ‘bad’ note. However, as we move closer to 0 it will sound less and less out of tune as the pitches of the two waves get closer and more similar. Try it for yourself! Change the detune:
opt value from 0.7
to 0.5
and listen to the new sound. Try 0.2
, 0.1
, 0.05
, 0
. Each time you change the value, take a listen and see if you can hear how the sound is changing. Notice that low detune values such as 0.1
produce a really nice ‘thick’ sound, with both slightly different pitches interacting with each other in interesting, often surprising, ways.
:dsaw
Another way we can finely craft our sound is to use a different envelope and options for each synth trigger. For example this will allow you to make some aspects of the sound percussive and other aspects ring out for a period of time.
detune = 0.1
synth :square, note: :e1, release: 2
synth :square, note: :e1 + detune, amp: 2, release: 2
synth :gnoise, release: 2, amp: 1, cutoff: 60
synth :gnoise, release: 0.5, amp: 1, cutoff: 100
synth :noise, release: 0.2, amp: 1, cutoff: 90
In the example above I have mixed in a noisy percussive element to the sound along with some more persistent background rumbling. This was achieved firstly by using two noise synths with middling cutoff values (90
and 100
) using short release times along with a noise with a longer release time but with a low cutoff value (which makes the noise less crisp and more rumbly.)
Let’s combine all these techniques to see if we can use additive synthesis to re-create a basic bell sound. I’ve broken this example into four sections. Firstly we have the ‘hit’ section which is the initial onset part of the bell sound - so uses a short envelope (e.g. a release:
of around 0.1
). Next we have the long ringing section in which I’m using the pure sound of the sine wave. Notice that I’m often increasing the note by roughly 12
and 24
which are the number of notes in one and two octaves. I have also thrown in a couple of low sine waves to give the sound some bass and depth. Finally, I used define
to wrap my code in a function which I can then use to play a melody. Try playing your own melody and also messing around with the contents of the :bell
function until you create your own crazy sound to play with!
define :bell do |n| # Triangle waves for the ‘hit’ synth :tri, note: n - 12, release: 0.1 synth :tri, note: n + 0.1, release: 0.1 synth :tri, note: n - 0.1, release: 0.1 synth :tri, note: n, release: 0.2
# Sine waves for the ‘ringing’ synth :sine, note: n + 24, release: 2 synth :sine, note: n + 24.1, release: 2 synth :sine, note: n + 24.2, release: 0.5 synth :sine, note: n + 11.8, release: 2 synth :sine, note: n, release: 2
# Low sine waves for the bass synth :sine, note: n - 11.8, release: 2 synth :sine, note: n - 12, release: 2 end
bell :e3 sleep 1 bell :c2 sleep 1 bell :d3 sleep 1 bell :g2
This is the second in a series of articles on how to use Sonic Pi for sound design. Last month we looked at additive synthesis which we discovered was the simple act of playing multiple sounds at the same time to make a new combined sound. For example we could combine different sounding synths or even the same synth at different pitches to build a new complex sound from simple ingredients. This month we’ll look at a new technique commonly called subtractive synthesis which is simply the act of taking an existing complex sound and removing parts of it to create something new. This is a technique which is commonly associated with the sound of analog synthesisers of the 1960s and 1970s but also with the recent renaissance of modular analog synths through popular standards such as Eurorack.
Despite this sounding like a particularly complicated and advanced technique, Sonic Pi makes it surprisingly simple and easy - so let’s dive right in.
For a sound to work well with subtractive synthesis, it typically needs to be fairly rich and interesting. This doesn’t mean we need something hugely complex - in fact, just a standard :square
or :saw
wave will do:
synth :saw, note: :e2, release: 4
Notice that this sound is already pretty interesting and contains many different frequencies above :e2
(the second E on a piano) which add to create the timbre. If that didn’t make much sense to you, try comparing it with the :beep
:
synth :beep, note: :e2, release: 4
As the :beep
synth is just a sine wave, you’ll hear a much purer tone and only at :e2
and none of the high crispy/buzzy sounds which you heard in the :saw
. It’s this buzziness and variation from a pure sine wave that we can play with when we use subtractive synthesis.
Once we have our raw source signal, the next step is to pass it through a filter of some kind which will modify the sound by removing or reducing parts of it. One of the most common filters used for subtractive synthesis is something called a low pass filter. This will allow all the low parts of the sound through but will reduce or remove the higher parts. Sonic Pi has a powerful yet simple to use FX system that includes a low pass filter, called :lpf
. Let’s play with it:
with_fx :lpf, cutoff: 100 do
synth :saw, note: :e2, release: 4
end
If you listen carefully you’ll hear how some of that buzziness and crispiness has been removed. In fact, all the frequencies in the sound above note 100
have been reduced or removed and only the ones below are still present in the sound. Try changing that cutoff:
point to lower notes, say 70
and then 50
and compare the sounds.
Of course, the :lpf
isn’t the only filter you can use to manipulate the source signal. Another important FX is the high pass filter referred to as :hpf
in Sonic Pi. This does the opposite to :lpf
in that it lets the high parts of the sound through and cuts off the low parts.
with_fx :hpf, cutoff: 90 do
synth :saw, note: :e2, release: 4
end
Notice how this sounds much more buzzy and raspy now that all the low frequency sounds have been removed. Play around with the cutoff value - notice how lower values let more of the original bass parts of the source signal through and higher values sound increasingly tinny and quiet.
:prophet
So far we’ve just produced fairly static sounds. In other words, the sound doesn’t change in any way for the entirety of its duration. Often you might want some movement in the sound to give the timbre some life. One way to achieve this is via filter modulation - changing the filter’s options through time. Luckily Sonic Pi gives you powerful tools to manipulate an FX’s opts through time. For example, you can set a slide time to each modulatable opt to specify how long it should take for the current value to linearly slide to the target value:
with_fx :lpf, cutoff: 50 do |fx|
control fx, cutoff_slide: 3, cutoff: 130
synth :prophet, note: :e2, sustain: 3.5
end
Let’s take a quick look at what’s going on here. Firstly we start an :lpf
FX block as normal with an initial cutoff:
of a very low 20
. However, the first line also finishes with the strange |fx|
at the end. This is an optional part of the with_fx
syntax which allows you to directly name and control the running FX synth. Line 2 does exactly this and controls the FX to set the cutoff_slide:
opt to 4 and the new target cutoff:
to be 130
. The FX will now start sliding the cutoff:
opt’s value from 50
to 130
over a period of 3 beats. Finally we also trigger a source signal synth so we can hear the effect of the modulated low pass filter.
This is just a very basic taster of what’s possible when you use filters to modify and change a source sound. Try playing with Sonic Pi’s many built-in FX to see what crazy sounds you can design. If your sound feels too static, remember you can start modulating the options to create some movement.
Let’s finish by designing a function which will play a new sound created with subtractive synthesis. See if you can figure out what’s going on here - and for the advanced Sonic Pi readers out there - see if you can work out why I wrapped everything inside a call to at
(please send answers to @samaaron on Twitter).
rrand
Этот раздел будет охватывать некоторые очень полезные - на самом деле даже необходимые знания для получения максимальной отдачи от вашего опыта работы с Sonic Pi.
Мы поговорим о том, как получить пользу от множества доступных вам клавиатурных сочетаний, как делиться своими работами, а ещё, я дам вам несколько советов о выступлениях с Sonic Pi.
Sonic Pi такой же инструмент, как и среда разработки. Следовательно, клавиатурные сочетания моуг сделать работу c Sonic Pi намного более эффективной и естественной - особенно когда вы играете вживую перед аудиторией.
Многое в Sonic Pi может контролироваться с помощью клавиатуры. Когда вы лучше познакомитесь с работой и выступлениями с Sonic Pi, вы, вероятно, начнёте использовать клавиатурные сочетания всё чаще и чаще. Лично я печатаю вслепую (рекомендую обдумать возможность обучения этому навыку) и огорчаюсь всякий раз, когда мне нужно тянуться за мышкой, так как это меня замедляет. Поэтому я использую все эти клавиатурные сочетания на постоянной основе!
Следовательно, если вы изучите эти клавиатурные сочетания, вы узнаете, как эффективно использовать клавиатуру, и вы начнёте кодировать вживую как профи в кратчайшие сроки.
Однако, не пытайтесь выучить все сочетания за один раз, просто пробуйте и запоминайте те сочетания, которые вы используете наиболее часто, а затем продолжайте добавлять новые в свою практику.
Представьте, что вы учитесь играть на кларнете. Вы ожидаете, что все кларнеты производятся со схожими элементами управления и аппликатурой. Если нет, вам будет трудно переключаться между различными кларнетами и вы будете вынуждены использовать всего один.
К сожалению три основные операционные системы (Linux, Mac ОС X и Windows) поставляются с собственными стандартами по умолчанию для таких действий, как вырезать, вставить и т. д. Sonic Pi будет пытаться соблюдать эти стандарты. Однако, вместо попыток соответствовать стандартам текущей платформы, Sonic Pi приоритетное внимание уделяет согласованности между платформами. Это значит, что если вы изучите сочетания клавиш во время игры с Sonic Pi на Raspberry Pi, то сможете перейти на Mac или PC и чувствовать себя там как дома.
Частью понятия согласованности является именование клавиатурных сочетаний. В Sonic Pi мы используем имена Control и Meta, ссылаясь на две главные комбинации клавиш. На всех платформах Control один и тот же (Ctrl). Однако, на Linux и Windows, Meta - это клавиша Alt, в то время как на Mac, Meta - это клавиша Command. Для единообразия мы будем использовать термин Meta - только не забудьте сопоставить его с соответствующей клавишей вашей операционной системы.
Чтобы сохранить вещи простыми и читабельными, мы будем использовать аббревиатуры - С- для Control плюс ещё одна клавиша, и М- - для Meta плюс ещё одна клавиша. Например, если клавиатурное сочетание требует от нас нажать одновременно Meta и r, мы напишем это как M-r
. Символ - означает “одновременно с”.
Ниже приведены клавиатурные сочетания, которые я считаю самыми полезными.
Вместо того, чтобы постоянно тянуться за мышью, чтобы выполнить свой код, вы можешьте просто нажать M-r
. Аналогично, чтобы остановить выполнение кода, вы можешьте нажать M-s
.
Я действительно потерялся бы без навигационных клавиш. Поэтому я настоятельно рекомендую вам потратить время, чтобы выучить их. Эти клавиатурные комбинации работают чрезвычайно хорошо в том случае, если вы освоили слепую печать, так как они используют обычные буквы и не нужно перемещать руку на мышь или клавиши со стрелками на вашей клавиатуре.
Вы можете двигаться к началу строки, используя C-a
, к концу строки, используя C-e
, на строку вверх - C-p
, на строку вниз - C-n
, на символ вперёд - C-f
, и на символ назад - C-b
.
Чтобы применить автовыравнивание к вашему коду, просто нажмите M-m
Для переключения к справочной системе вы можете нажать M-i
. Однако, гораздо полезнее знать сочетание C-i
, которое будет искать слово под курсором в документации и отображать её, если что-нибудь найдет. Мгновенная помощь!
Чтобы увидеть полный список клавиатурных сочетаний, загляните в раздел 10.2 Шпаргалка по клавиатурным сочетаниям.
Ниже приводится обзор основных клавиатурных сочетаний, доступных в Sonic Pi. Пожалуйста, посмотрите раздел 10.1 для подготовки и мотивации.
В этом списке мы используем следующие соглашения (где Meta называется Alt в Windows/Linux или Cmd в Mac):
C-a
значит удерживая клавишу Control, нажмите клавишу a. И после того, как обе клваиши будут нажаты одновременно, отпустите их.
M-r
значит удерживая клавишу Meta, нажмите клавишу r. И после того, как обе клваиши будут нажаты одновременно, отпустите их.
S-M-z
значит удерживая клавиши Shift и Meta, нажмите клавишу z. И после того, как все три клваиши будут нажаты одновременно, отпустите их.
C-M-f
значит удерживая клавиши Control и Meta, нажмите клавишу f. И после того, как все три клваиши будут нажаты одновременно, отпустите их.
M-r
- Выполнить код
M-s
- Остановить выполнение кода
M-i
- Показать/скрыть справочную систему
M-p
- Показать/скрыть настройки
M-{
- Переключить буфер влево
M-}
- Переключить буфер вправо
M-+
- Увеличить размер текста в текущем буфере
M--
- Уменьшить размер текста в текущем буфере
M-a
- Выбрать всё
M-c
- Копировать выделение в буфер обмена
M-]
- Копировать выделение в буфер обмена
M-x
- Вырезать выделение в буфер обмена
C-]
- Вырезать выделение в буфер обмена
C-k
- Вырезать от курсора до конца строки
M-v
- Вставить из буфера обмена в редактор
C-y
- Вставить из буфера обмена в редактор
C-SPACE
- Установить метку. Теперь навигация управляет областью выделения. Используйте C-g
для отмены.
M-m
- Выровнять весь текст
Tab
- Выровнять текущую линию/выделение
C-l
- Отобразить буфер с текущей строкой по центру экрана
M-/
- Комментировать/раскомментировать текущую строку
C-t
- Перемещение/замена символов
M-u
- Преобразовать следующее слово (или выделение) в верхний регистр
M-l
- Преобразовать следующее слово (или выделение) в нижний регистр
C-a
- Перейти в начало строки
C-e
- Перейти в конец строки
C-p
- Перейти на предыдущую строку
C-n
- Перейти на следующую строку
C-f
- Перейти вперёд на один символ
C-b
- Перейти назад на один символ
M-f
- Перейти вперёд на одно слово
M-b
- Перейти назад на одно слово
C-M-n
- Переместить строку или выделение вниз
C-M-p
- Переместить строку или выделение вверх
S-M-u
- Подняться на 10 строк
S-M-d
- Опуститься на 10 строк
M-<
- Перейти в начало буфера
M->
- Перейти в конец буфера
C-h
- Удалить предыдущий символ
C-d
- Удалить следующий символ
C-i
- Показать документацию по слову под курсором
M-z
- Отмена
S-M-z
- Повтор
C-g
- Escape
S-M-f
- Переключение полноэкранного режима
S-M-b
- Показать/скрыть кнопоки
S-M-l
- Показать/скрыть журнал
S-M-m
- Переключение между светлым и тёмным режимом
S-M-s
- Сохранить содержание буфера в файл
S-M-o
- Загрузить содержания из файла в буфер
Sonic Pi создан для обучения и обмена друг с другом.
Как только вы научитесь кодировать музыку, делитесь своими композициями. Это так же просто, как отправка электронной почты, содержащей ваш код. Пожалуйста, поделитесь своим кодом с окружающими, чтобы они могли учиться с помощью вашей работы или использовать её части в своём миксе.
Если вы не уверены, какой способ поделиться своей работой с остальными лучший, я рекомендую размещать свой код на GitHub и музыку на SoundCloud. Таким образом вы сможете охватить большую аудиторию.
GitHub - это сайт для обмена и работы с кодом. Он используется профессиональными разработчиками, а также художниками для обмена и совместной работы с кодом. Самый простой способ поделиться новым куском кода (или даже незавершённым куском) - создать Gist. Gist - это простой способ загружать свой код в удобной форме, чтобы другие могли увидеть, скопировать и поделиться им.
Еще один отличный способ поделиться вашей работой - сделать аудио запись и загрузить её на SoundCloud. После того как вы загрузили свою запись, другие пользователи могут комментировать и обсуждать её. Я также рекомендую поместить ссылку на ваш код в Gist в описание трека.
Для того, чтобы записать свою работу, нажмите кнопку Запись
в панели инструментов, и запись начнётся немедленно. Нажмите кнопку Выполнить
, чтобы запустить свой код, если он ещё не запущен. Когда вы закончите запись, нажмите мигающую кнопку Запись
снова, и вам будет предложено ввести имя файла. Запись будет сохранена в формате WAV-файл, который можно редактировать и конвертировать любым аудиоредактором с открытым исходным кодом (попробуйте, например, Audacity).
Я призываю вас делиться своей работой и на самом деле надеюсь, что мы все будем учить друг друга новым трюкам и развиваться вместе с Sonic Pi. Я действительно очень воодушевлён тем, что у вас будет, что показать мне.
Один из самых интересных аспектов Sonic Pi заключается в том, что он позволяет использовать код как музыкальный инструмент. Это значит, что написание кода вживую может теперь рассматриваться как новый вид музыкального исполнения.
Мы называем это Лайвкодинг.
Я рекомендую вам показывать сой экран аудитории во время лайвкодинга. Это как играть на гитаре, но скрывая свои пальцы и струны. Когда я практикуюсь дома, я использую Raspberry Pi и небольшой мини-проектор, направленный на стену моей гостинной. Вы можете использовать свой телевизор или один из своих школьных/рабочих проекторов для своего шоу. Попробуйте, это очень весело.
Играйте не просто для себя - соберите группу! Это очень весело - играть с другими. Один человек может делать бит, другой - эмбиент фон, и т.д. Посмотрите, какие интересные сочетания звуков вы сможете получить вместе.
Кодирование вживую не такая уж и новинка - небольшое количество людей делало это в течение последних нескольких лет, обычно используя самописные системы, которые они сконструировали для себя сами. Отличное место для того, чтобы узнать больше о других людях, занимающихся лайвкодингом и их системах, это TOPLAP.
Ещё один отличный ресурс для изучения лайвкодинга, это Algorave. Здесь вы можете найти всё о конкретном направлении лайвкодинга - создании музыки в ночных клубах.
Sonic Pi теперь поддерживает простой API для взаимодействия с Minecraft Pi - специальным изданием Minecraft, которое устанавливается по умолчанию в Raspbian - операционную систему на основе Linux.
Интеграция в Minecraft Pi была разработана с целью быть безумно простой в использовании. Все, что вам нужно сделать, это запустить Minecraft Pi и создать мир. Затем можно свободно использовать mc_*
функции так, как если бы вы могли использовать play
и synth
. Не надо ничего импортировать или устанавливать какие-либо библиотеки - все готово и работает из коробки.
Minecraft Pi API отвечает за управление подключением в приложении Sonic Pi. Это означает, что вам не надо беспокоиться ни о чем. Если вы попытаетесь использовать Minecraft API, пока Minecraft не запущен, Sonic Pi вежливо сообщит вам об этом. Аналогично, если закрыть Minecraft Pi в то время, как всё ещё повторяется цикл live_loop
, использующий Minecraft API, цикл остановится и Sonic Pi вежливо скажет вам, что не может подключиться к Minecraft. Чтобы переподключиться, просто запустите Minecraft снова и Sonic Pi автоматически обнаружит и заново создаст подключение.
Minecraft Pi API был разработан для безупречной работы с циклами live_loop
. Это значит, что можно синхронизировать изменения в вашем Minecraft Pi мире с изменениями звука в Sonic Pi. Мгновенные, основанные на Minecraft, музыкальные клипы! Заметим, однако, что Minecraft Pi - альфа версия программы и, как известно, немного глючит. Если у вас возникнут проблемы, просто перезапустите Minecraft Pi и продолжайте как ни в чём не бывало. Sonic Pi позаботится о том, чтобы автоматически соедениться c Minecraft Pi.
Настоятельно рекомендуется, использовать Raspberry Pi 2, если вы хотите запускать Sonic Pi и Minecraft в одно и то же время - особенно если вы хотите использовать звуковые возможности Sonic Pi.
На данном этапе, Sonic Pi поддерживает базовые манипуляции блоками и игроком, подробно описанные в разделе 11.1. Поддержка обратных вызовов событий, вызванных взаимодействием игрока с миром, планируется в будущих релизах.
Sonic Pi в настоящее время поддерживает следующие основные взаимосвязи с Minecraft Pi:
Отображение сообщений в чате Установка позиции пользователя Получение позиции пользователя Установка типа блока по заданным координатам Получение типа блока по заданным координатам
Давайте рассмотрим каждую из них по очереди.
Давайте посмотрим, как легко управлять Minecraft Pi из Sonic Pi. Во-первых, убедись, что и Minecraft Pi, и Sonic Pi, открыты в одно и то же время, а также, что вы вошли в мир Minecraft и можете там передвигаться.
Просто наберите в пустом буфере Sonic Pi следующий код:
mc_message "Hello from Sonic Pi"
Когда вы нажмёте кнопку Выполнить, вы увидите своё сообщение в окне Minecraft. Поздравляю, вы написали свой первый код в Minecraft! Это было просто, не так ли?
Теперь давайте попробуем немного магии. Телепортируем себя куда-нибудь! Наберите следующее:
mc_teleport 50, 50, 50
Когда вы нажмёте Выполнить - бум! Вы мгновенно переноситесь на новое место. Скорее всего, это будет где-то в небе, и вы упадёте на землю, либо на сушу, или в воду. Что это за цифры: 50, 50, 50
? Это координаты места, в которыое вы пытаетесь телепортироваться. Давайте сделаем короткий перерыв, чтобы выяснить, что такое координаты и как они работают, потому что они очень, очень важны для программирования Minecraft.
Представьте пиратскую карту с большой X
меткой на месте расположения каких-нибудь сокровищ. Точное местоположение X
может быть описано с помощью двух чисел - как далеко по карте слева направо и как далеко по карте снизу вверх. Например 10см
по горизонтали и 8 см
вверх. Эти числа 10
и 8
- это координаты. Вы можете легко представить местоположение других тайников с сокровищами, описанное другой парой чисел. Возможно, есть большой сундук золота на 2
по горизонтали и 9
по вертикали…
Но, в Minecraft двух чисел не достаточно. Нам также нужно знать как высоко мы находимся. Следовательно, нам нужно три числа:
Как далеко с права на лево в мире - x
Как далеко от передней до задней части мира - z
Как высоко мы находимся в мире - y
Ещё один ньюанс - мы описываем эти координаты в таком порядке: x
, y
, z
.
Давайте поиграем с координатами. Переместитесь в приятное место на карте Minecraft, а потом переключитесь на Sonic Pi. Введите следующее:
puts mc_location
Когда вы нажмёте кнопку Выполнить вы увидите, что координаты текущего положения отобразятся в окне журнала. Запишите их, затем двигайтесь вперёд в мире Minecraft и попробуйте получить координаы снова. Обратите внимание, как они изменились! А сейчас, я рекомендую вам потратить некоторое время на повторение именно этого - немного переместитесь в мире, посмотрите координаты и повтори это снова. Делайте это до тех пор, пока не начнёте чувствовать, как изменяются координаты, когда вы перемещаетесь. Как только вы поймёте, как координаты работают, программирование с Minecraft API будет совершенной мелочью.
Теперь, когда вы знаете, как найти текущую позицию и телепортироваться, используя координаты, у вас есть все инструменты, чтобы начать строить различные вещи в Minecraft с помощью кода. Допустим, вы хочтите сделать блок с координатами 40
, 50
, 60
стеклянным. Это супер просто:
mc_set_block :glass, 40, 50, 60
Ха-ха, это действительно было просто. Увидеть дело рук своих проще простого - телепортируйтесь рядом и посмотрите:
mc_teleport 35, 50, 60
Теперь повернитесь и ты увидите свой стеклянный блок! Попробуйте изменить его на алмаз:
mc_set_block :diamond, 40, 50, 60
Если вы смотрели в правильном направлении, то могли увидеть эти изменения соими глазами! Это начало чего-то захватывающего…
Давай рассмотрим ещё одну, последнюю вещь, прежде чем перейдём к чему-то немного более сложному. Задавая набор координат, можно указать какого типа должен быть блок. Давайте попробуем это с алмазным блоком, который мы только что создали:
puts mc_get_block 40, 50, 60
Эй! Это :diamond
. Попробуйте изменить его обратно на стекло и спросите Minecraft об этом снова - он ответит :glass
? Я уверен, что да :-)
Перед тем, как пойти неистово кодировать на Minecraft Pi, вы могли бы найти этот список доступных типов блоков полезным:
:air
:stone
:grass
:dirt
:cobblestone
:wood_plank
:sapling
:bedrock
:water_flowing
:water
:water_stationary
:lava_flowing
:lava
:lava_stationary
:sand
:gravel
:gold_ore
:iron_ore
:coal_ore
:wood
:leaves
:glass
:lapis
:lapis_lazuli_block
:sandstone
:bed
:cobweb
:grass_tall
:flower_yellow
:flower_cyan
:mushroom_brown
:mushroom_red
:gold_block
:gold
:iron_block
:iron
:stone_slab_double
:stone_slab
:brick
:brick_block
:tnt
:bookshelf
:moss_stone
:obsidian
:torch
:fire
:stairs_wood
:chest
:diamond_ore
:diamond_block
:diamond
:crafting_table
:farmland
:furnace_inactive
:furnace_active
:door_wood
:ladder
:stairs_cobblestone
:door_iron
:redstone_ore
:snow
:ice
:snow_block
:cactus
:clay
:sugar_cane
:fence
:glowstone_block
:bedrock_invisible
:stone_brick
:glass_pane
:melon
:fence_gate
:glowing_obsidian
:nether_reactor_core