100 языков спидран: Эпизод 64: ChucK

ChucK — это язык музыкального программирования. Поскольку в музыке все зависит от времени, он описывает себя как «Язык музыкального программирования с сильной синхронизацией, одновременным исполнением и «на лету»».

Программы, описанные в этом эпизоде, будут издавать много звуков, но из-за технических ограничений вы не сможете их услышать, если только не скачаете их и не запустите локально. Вы можете установить ChucK с помощью brew install chuck или аналогичной команды, так что это не должно быть слишком сложно.

Это не первая моя встреча с ChucK, когда-то давно я настраивал его для использования танцевального коврика DDR в качестве музыкального инструмента, но это было давно, поэтому я не помню всех деталей.

Здравствуй, мир!

ChucK не поддерживает #!, нам нужно запускать скрипты с chuck hello.ck. Вот очень простой:

SinOsc hello => dac;
420.0 => hello.freq;
1::second => now;
Войти в полноэкранный режим Выйти из полноэкранного режима

Как видите, здесь много =>s. Цитируя документацию «Оператор ChucK (=>) — это сильно перегруженный оператор, который, в зависимости от задействованных типов, выполняет различные действия», и это практически все.

Вы уже можете видеть, как он выполняет множество различных ролей:

  • мы создаем генератор синусоидальной волны hello, затем используем оператор chuck для подключения его к dac (аудиовыход)
  • мы добавляем число 420.0 в hello.freq, чтобы изменить частоту синусоиды на 420.0 Гц
  • добавляем 1::second к now, чтобы программа выполнялась в течение 1 секунды с текущими настройками

Loops

Поскольку вы не можете слышать звуки, я попытаюсь вывести происходящее на терминал:

SinOsc sine_wave => dac;

for (0 => int i; i<=12*5; i++) {
  Math.pow(2.0, i/12.0) * 55.0 => float freq;
  freq => sine_wave.freq;
  <<<"Playing frequency", freq>>>;
  0.1::second => now;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вот что происходит, 0,1 секунды на итерацию:

Playing frequency 55.000000
Playing frequency 58.270470
Playing frequency 61.735413
Playing frequency 65.406391
Playing frequency 69.295658
Playing frequency 73.416192
Playing frequency 77.781746
Playing frequency 82.406889
Playing frequency 87.307058
Playing frequency 92.498606
Playing frequency 97.998859
Playing frequency 103.826174
Playing frequency 110.000000
Playing frequency 116.540940
Playing frequency 123.470825
Playing frequency 130.812783
Playing frequency 138.591315
Playing frequency 146.832384
Playing frequency 155.563492
Playing frequency 164.813778
Playing frequency 174.614116
Playing frequency 184.997211
Playing frequency 195.997718
Playing frequency 207.652349
Playing frequency 220.000000
Playing frequency 233.081881
Playing frequency 246.941651
Playing frequency 261.625565
Playing frequency 277.182631
Playing frequency 293.664768
Playing frequency 311.126984
Playing frequency 329.627557
Playing frequency 349.228231
Playing frequency 369.994423
Playing frequency 391.995436
Playing frequency 415.304698
Playing frequency 440.000000
Playing frequency 466.163762
Playing frequency 493.883301
Playing frequency 523.251131
Playing frequency 554.365262
Playing frequency 587.329536
Playing frequency 622.253967
Playing frequency 659.255114
Playing frequency 698.456463
Playing frequency 739.988845
Playing frequency 783.990872
Playing frequency 830.609395
Playing frequency 880.000000
Playing frequency 932.327523
Playing frequency 987.766603
Playing frequency 1046.502261
Playing frequency 1108.730524
Playing frequency 1174.659072
Playing frequency 1244.507935
Playing frequency 1318.510228
Playing frequency 1396.912926
Playing frequency 1479.977691
Playing frequency 1567.981744
Playing frequency 1661.218790
Playing frequency 1760.000000
Войти в полноэкранный режим Выход из полноэкранного режима

Вы можете видеть, что это уже делает несколько вещей:

  • забрасывает что-то в переменную int или float, присваивает ей значение.
  • В ChucK есть циклы while и циклы for в стиле C, только в нем нет присваивания =, вам нужно использовать оператор => chuck, и он идет в обратном направлении.
  • В западной музыке ноты постоянно кратны друг другу, это кратное равно 2^(1/12), поэтому формула Math.pow(2.0, i/12.0) * base_freq.

Инструменты

ChucK, конечно, поставляется с большим количеством предопределенных инструментов, основанных либо на физических моделях, либо на математических формулах. Давайте попробуем ситар, так как он имеет довольно простые элементы управления:

Sitar sitar => dac;

while(true) {
  Math.random2(60, 80) => float note;
  Std.mtof(note) => sitar.freq;
  Math.random2f(0.5, 0.1) => sitar.noteOn;

  <<<"Playing note", note>>>;
  0.2::second => now;
}
Войти в полноэкранный режим Выйти из полноэкранного режима
$ chuck instrument.ck
Playing note 64.000000
Playing note 74.000000
Playing note 71.000000
Playing note 75.000000
Playing note 65.000000
Playing note 67.000000
...
Войти в полноэкранный режим Выйти из полноэкранного режима

Шаг за шагом:

  • создаем Sitar и подключаем его к выходу dac. На самом деле мы не будем этим заниматься, но мы также можем подключать инструменты к различным фильтрам, микшерам, анализаторам, динамически отключать их и так далее.

Фибоначчи

Мы могли бы сделать это обычным способом, но давайте сделаем это более запутанным способом, с помощью shreds (потоков ChucK) и событий:

class FibEvent extends Event {
  int argument;
  int result;
}

fun void reporter(string name, FibEvent e) {
  while(true) {
    // wait for event
    e => now;
    // report what we got the data
    <<<name, "reporting that fib of", e.argument, "is", e.result>>>;
  }
}

// the event
FibEvent e;

// spawn two listeners
spork ~ reporter("a", e);
spork ~ reporter("b", e);

1 => int a;
1 => int b;
1 => int i;

while(true) {
  0.1::second => now;

  i => e.argument;
  a => e.result;
  e.signal(); // send it to one of the reporters

  a + b => int c;
  b => a;
  c => b;
  i + 1 => i;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Сообщает обычную последовательность Фибоначчи:

$ chuck fib.ck
a reporting that fib of 1 is 1
b reporting that fib of 2 is 1
a reporting that fib of 3 is 2
b reporting that fib of 4 is 3
a reporting that fib of 5 is 5
b reporting that fib of 6 is 8
a reporting that fib of 7 is 13
b reporting that fib of 8 is 21
a reporting that fib of 9 is 34
b reporting that fib of 10 is 55
a reporting that fib of 11 is 89
b reporting that fib of 12 is 144
a reporting that fib of 13 is 233
b reporting that fib of 14 is 377
a reporting that fib of 15 is 610
b reporting that fib of 16 is 987
a reporting that fib of 17 is 1597
b reporting that fib of 18 is 2584
a reporting that fib of 19 is 4181
b reporting that fib of 20 is 6765
...
Вход в полноэкранный режим Выход из полноэкранного режима

Шаг за шагом:

  • мы можем создать пользовательское событие FibEvent, наследующее от базового Event, с некоторыми дополнительными полями
  • порождаем несколько клочков с помощью spork ~ reporter("a", e) — честно говоря, я не думаю, что переименование всего в случайном порядке, как это делает ChucK, является особенно хорошей практикой
  • внутри потока reporter, e => now; ждет сигнала от события e — это еще одна перегрузка =>
  • в главном событии мы имеем бесконечный цикл, вычисляющий числа Фибоначчи
  • интересной частью является e.signal(), который сигнализирует одному из слушателей о готовности события; есть также e.broadcast(), который сигнализирует всем слушателям.

Образцы

ChucK, очевидно, может загружать файлы сэмплов, такие как .wav. Давайте пройдемся по всему списку примеров, включенных в пакет brew:

[
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/special/geetar.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter8/audio/kick_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter8/audio/hihat_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/snare_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/cowbell_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/kick_04.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/clap_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/hihat_02.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_03.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_02.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/cowbell_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/click_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/click_02.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/stereo_fx_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/kick_04.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/stereo_fx_03.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/kick_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/clap_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/hihat_04.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/hihat_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/hihat_02.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter5/audio/snare_03.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter5/audio/stereo_fx_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter5/audio/kick_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/snare_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/snare_03.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/snare_02.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/cowbell_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/click_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/click_02.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/stereo_fx_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/stereo_fx_03.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/kick_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/clap_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/hihat_04.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/hihat_01.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/hihat.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/hihat-open.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/kick.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/snare.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/snare-chili.wav",
  "/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/snare-hop.wav"
] @=> string files[];

SndBuf buf => dac;

for(0 => int i; i<files.size(); i++) {
  // read a .wav
  files[i] => buf.read;
  // start at the beginning
  0 => buf.pos;
  <<<i, files[i]>>>;
  // wait however long buf is, to let it finish playing
  buf.length() => now;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы назначить непримитивные типы, нам нужно сделать @=> вместо =>.

На выходе мы получим множество звуков и их файлов:

$ chuck samples.ck
0 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/special/geetar.wav
1 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter8/audio/kick_01.wav
2 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter8/audio/hihat_01.wav
3 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/snare_01.wav
4 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/cowbell_01.wav
5 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/kick_04.wav
6 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/clap_01.wav
7 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/hihat_02.wav
8 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_01.wav
9 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_03.wav
10 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_02.wav
11 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/cowbell_01.wav
...
Вход в полноэкранный режим Выход из полноэкранного режима

FizzBuzz

Классический FizzBuzz:

for (1 => int i; i<=100; i++) {
  if (i % 15 == 0) {
    chout <= "FizzBuzz" <= IO.newline();
  } else if (i % 5 == 0) {
    chout <= "Buzz" <= IO.newline();
  } else if (i % 3 == 0) {
    chout <= "Fizz" <= IO.newline();
  } else {
    chout <= i <= IO.newline();
  }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Нам пришлось заменить <<< >>>> на вывод в стиле C++. <<< >>>> выводится только на stderr и делает какое-то странное форматирование, поэтому мы не можем использовать его здесь.

ChucK переименовал stdout и stderr в chout и cherr, потому что ему просто нравится делать такие глупые переименования.

С этими изменениями мы получим точно такой же результат, как и в FizzBuzz.

Аудио FizzBuzz

Вот ChucK, воспроизводящий FizzBuzz в аудио форме. Fizz и Buzz соответствуют аудиосэмплам, числа — нотам на ситаре. Все это звучит так же ужасно, как вы, вероятно, себе представляете.

Sitar sitar => dac;

SndBuf fizz => dac;
SndBuf buzz => dac;

"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/hihat_04.wav" => fizz.read;
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/snare-chili.wav" => buzz.read;

fun void playFizz() {
  0 => fizz.pos;
  fizz.length() => now;
}

fun void playBuzz() {
  0 => buzz.pos;
  buzz.length() => now;
}

for (1 => int i; i<=100; i++) {
  if (i % 15 == 0) {
    playFizz();
    playBuzz();
  } else if (i % 5 == 0) {
    playBuzz();
  } else if (i % 3 == 0) {
    playFizz();
  } else {
    Std.mtof(20+i) => sitar.freq;
    Math.random2f(0.5, 0.1) => sitar.noteOn;
    0.1::second => now;
  }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Стоит ли вам использовать ChucK?

Ниша ChucK настолько далека от того, чем я обычно занимаюсь, что я не могу сказать, насколько он хорош.

Ему определенно не хватает функциональности для любых задач, не связанных с музыкой, таких как файлы, строки и т.д.

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

Код

Все примеры кода для серии будут находиться в этом репозитории.

Код для эпизода ChucK доступен здесь.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *