Я тут уже примерно месяц с небольшим самостоятельно осваиваю не самую, мягко говоря, простую программу для создания электронной музыки под названием Supercollider. На этой неделе перешёл от теории к практике. Сегодня вот целый день провозился с весьма, казалось бы, простой задачей - записать то, что я играю на MIDI-клавиатуре, чтобы потом воспроизвести. Я искал какие-то уже готовые способы сделать это нехитрое дело, но, кажется, в эстетике этой программы столь попсовым способом вводить данные как-то не очень пользуются.

Пришлось ваять самому, продираясь через весьма запутанную и многочисленную документацию. Самое приятное, что к ночи оно-таки получилось, посему хочется как-то поделиться своим успехом. Итак, вот как это выглядит на языке Supercollider в моём исполнении, с подробными комментариями:
Жесть// Вступительная часть. Включаем двигатели 
Server.default=s=Server.local;
s.boot;
MIDIClient.init;
MIDIIn.connect;
// Далее следует подпрограмма записи последовательности нот. Полифония мнимая - нажатые одновременно ноты записываются как нажатые по очереди, но столь быстро, что на слух это незаметно.
(
var on, off;
x = List.new;
y = List.new;
z = List.new;
o = List.new;
t = List.new;
on = Routine({ //бесконечный цикл ожидания нажатых нот
var event, count;
loop {
event = MIDIIn.waitNoteOn; // само ожидание нажатия
x.add(event.b); y.add(event.c); z.add(Main.elapsedTime); //номер ноты прибавляется к списку x, громкость к списку y, а в список z записывается абсолютное время события.
}
}).play;
off = Routine({ // примерно то же самое для отжатия нот
var event, count;
loop {
event = MIDIIn.waitNoteOff;
o.add(event.b); t.add(Main.elapsedTime); // номер ноты добавляется в список o, абсолютное время события в список t. Скорость отжатия меня в данный момент не интересует, хотя моя волшебная клавиатура Novation Remote 61 SL (ну надо же похвалиться
) чувствительна не только к скорости нажатия клавиши, но и к скорости отжатия - достаточно редкое свойство.
}
}).play;
q = {on.stop; off.stop;}; // специальная переменная для завершения бесконечных циклов, когда запись будет закончена. А то они так и будут писать вечно. Переменные из одной латинской буквы действуют и вне подпрограмм. Если бы я ввел, например, переменную quit, я бы потом остановить циклы уже не смог, ибо Supercollider "забыл" бы о существовании такой переменной вне круглых скобок.
)
q.value //когда запись закончена, я запускаю отдельно эту строку. Она возвращает значение переменной q и останавливает циклы.
// добавим простейший синтезатор, чтобы звуки стали слышны (в дальнейшем надо будет усовершенствовать программу, чтобы я мог слышать себя и во время записи)
SynthDef(\my, {arg freq, amp, sus;
var env, env_gen, syn;
env = Env.triangle(sus, amp); //треугольная огибающая (ну например)
env_gen = EnvGen.kr(env, doneAction:2); //doneAction - важная вещь, благодаря этому параметру синтезатор для каждой отдельно взятой ноты выгрузится из памяти, когда закончит свою работу
syn = SinOsc.ar(freq, mul: env_gen); // сам синтезатор (обычная синусоида)
Out.ar(0, Pan2.ar(syn))}).load(s); // вывод на аудиовыход с панорамированием по центру
// ну, дальше самое интересное - как же последовательность воспроизвести.
(
var note, vel, vel_now, sheddd, noteoff, noteofftime, index, offthis, diff;
note = x.asArray; //задаём местные переменные, чтобы оригинальная запись осталась в неприкосновенности, заодно превращая лист в массив
vel = y.asArray;
sheddd = z.asArray-z.at(0); //важный момент - превращение абсолютного времени в относительное. Из каждого показателя времени вычитается время начала записи (первое число массива), таким образом первое число в массиве становится равным 0, а остальные отсчитывают время уже от этого 0
noteoff = o.asArray;
noteofftime = t.asArray-z.at(0); //то же самое, что и с временем нажатия
// далее запускаем часы
SystemClock.sched(0.0, {
index = noteoff.indexOf(note.at(0)); //то, над чем я думал дольше всего. Полагаю, это не лучший способ, но тем не менее. В массиве номеров отжатых нот ищется ближайший к началу номер, совпадающей с текущей нажатой нотой, и берётся его позиция в массиве.
offthis = noteofftime.at(index); //теперь в массиве времён отжатия находим время на той же самой позиции: очевидно, что они совпадают, потому что в оба массива записи производились одновременно
noteoff.removeAt(index); noteofftime.removeAt(index); //забрав значения, сразу удаляем их в массиве - два раза одну и ту же ноту отжимать не надо
vel_now = vel.at(0); //потом, наверное, уберу. боялся, что компьютер неправильно поймёт операцию деления чуть ниже...
Synth(\my, [\freq, note.at(0).midicps, \amp, vel_now/127, \sus, offthis-sheddd.at(0)]); //ну вот и сам синтезатор, наконец. зазвучала нота. громкость делится на 127, потому что здесь для громкости положены лишь значения от 0 до 1, громче уже зашкаливает, в MIDI же шкала от 0 до 127. А чтобы вычислить длительность ноты, вычитаем время нажатия из времени отжатия данной ноты.
note.removeAt(0); // удаляем из массивов остальные упоминания, касающиеся этой ноты
vel.removeAt(0);
diff = sheddd.at(0); //собираюсь удалить и время нажатия, но оно мне ещё пригодится, посему сохраняю его в отдельной переменной
sheddd.removeAt(0);
if(sheddd.at(0) == nil, {nil}, {sheddd.at(0) - diff}); //удалив время нажатия предыдущей ноты, вычисляю, сколько времени прошло до начала следующей ноты. SystemClock.sched автоматически перезапустится спустя именно это количество времени. А оператор if понадобился для того, чтобы завершить цикл, когда уже последняя нота "уйдёт в печать". Великое Ничто в Supercollider обозначается словом "nil", и при попытке что-либо вычесть из него он выдаёт ошибку, посему когда дело дойдёт до nil, nil и останется. Спасибо, что дочитали до конца 
})
)