Переменные

Присвоение имён полезно для вашего кода. С 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.