Присвоение имён полезно для вашего кода. С Sonic Pi это очень просто. Вы пишете желаемое имя, знак равенства, а затем то, что нужно запомнить:
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
В следующей главе мы рассмотрим подробнее управление синтами.
Хотя переменные хороши для присвоения имени и сохранения результатов, важно знать что они обычно должны использоваться в потоке. Например, не делайте так:
a = (ring 6, 5, 4, 3, 2, 1)
live_loop :sorted do
a = a.sort
sleep 0.5
puts "sorted: ", a
end
live_loop :shuffled do
a = a.shuffle
sleep 0.5
end
В примере выше мы присвоили ряд чисел переменной ‘a’ и затем использовали её в двух отдельных ‘live_loop`s’. В первом живом цикле каждые ‘0,5’ секунды мы сортируем кольцевой список (1, 2, 3, 4, 5, 6), затем отображаем результат в журнале. Если ты запустишь код, ты увидишь, что выведенный список не всегда отсортирован!. Это может быть для тебя сюрпризом, ведь иногда список выводится отсортированным, а иногда нет. Это называется недетерминированным поведением и является результатом неприятной проблемы называемой гонкой состояний. Проблема заключается в том, что второй живой цикл также работает с нашим кольцевым списком (в данном случае перемешивает его) и на момент вывода на экран список может быть как отсортирован, так и перемешан. Оба живых цикла соревнуются сделать разные вещи с одной переменной и каждый раз ‘побеждает’ разный цикл.
Есть два решения. Первое: не использовать одну переменную в разных живых циклах или потоках. Например, код ниже всегда выводит отсортированный список, поскольку каждый живой цикл имеет собственную отдельную переменную:
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
Однако, иногда мы хотим поделиться чем-то между потоками. Например, текущей тональностью, BMP, синтом и т.д. В этом случае решением будет воспользоваться специальными потоко-безопасными состояниями системы Sonic Pi через функции ‘get’ и ‘set. Это будет осуждаться в разделе 10.