Функции

Когда вы станете писать большое количество кода, вам потребуется способ организовать и структурировать элементы так, чтобы они выглядели более аккуратно, и их было бы проще понять. Функции - это очень мощный инструмент достижения этой цели. Они позволяют нам давать имена фрагментам кода. Посмотрим, как это работает.

Определение Функций

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 знал, что играть? Он просто вспомнил вашу функцию даже после её удаления из буфера. Он запомнил что вы напечатали. Это работает только с функциями, которые создаются при помощи definedefonce)

Параметризованные функции

Вам, возможно, будет интересно узнать, что вашу функцию можно научить принимать аргументы, прямо как 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.

Видите, как можно писать нечто очень выразительное и в то же время простое для чтения, если перенести большую часть логики в функцию!