// title: MIDI arpeggiator demo // author: wondersluyter // description: // Roughly based on the stock ableton arpeggiator behavior. A lot of variation is possible combining custom ~chordSort and ~notePat functions. // code: ( /////////////////////// // input parameters // ///////////////////////////////////////////////////////// ~hold = false; ~clock = TempoClock(120/60); ~rate = 1/24; ~swing = 0.1; ~jitter = 0.1; ~legato = 2; //~chordSort = { |a, b| a.time < b.time }; // in order played ~chordSort = { |a, b| a.num < b.num }; // lowest to highest //~chordSort = { |a, b| a.num > b.num }; // highest to lowest ~notePat = { |chord| // play chord forwards and backwards if (chord.size > 1) { chord[0..chord.size-2] ++ chord.reverse[0..chord.size-2] } { chord } }; // ~notePat = { |chord| // interlace first note // var temp; // if (chord.size == 0) { // chord // } { // temp = chord.removeAt(0); // if (chord.size == 0) { // chord // } { // [temp.dup(chord.size), chord].lace // }; // } // }; //~notePat = { |chord| chord }; // play chord forwards //~notePat = { |chord| chord.reverse }; // play chord backwards ~transpose = 9.1; ~steps = 1; // # of transpositions applied -- negative goes up and down ~offset = 3; // offset into note pattern ~repeats = inf; //~velocityFunc = { |vel| vel }; // velocity as played //~velocityFunc = { 64 }; // constant velocity ~velocityFunc = { |vel, timeSincePlayed, beats| // something weirder var ret = vel; if (beats % 1 < 0.1) { ret = (ret * 2) } { if (beats % 0.5 < 0.2) { ret = (ret * 0.5) } { if (beats % 0.33333 < 0.1) { ret = (ret * 1.7) }; }; }; ret = ret * (1 / (1 + (timeSincePlayed * 0.1))); ret; }; ///////////////////////////////////////////////////////// // MIDI MIDIClient.init; MIDIIn.connectAll; ~notes = nil ! 127; MIDIdef.noteOn(\keyOn, { |vel, num| ~notes.do { |note, i| if (note.notNil) { if (note.pressed.not) { ~notes[i] = nil; }; }; }; if (~notes.select(_.notNil).select(_.pressed) == []) { ~repeat_i = 0; }; ~notes[num] = (num: num, vel: vel, time: thisThread.seconds, pressed: true); }); MIDIdef.noteOff(\keyOff, { |vel, num| if (~hold) { if (~notes[num].notNil) { ~notes[num].pressed = false; }; } { ~notes[num] = nil; }; }); // Pattern ~repeat_i = 0; ~ptr = 0; ~i = 0; Pdef(\arp).stop; Pdef(\arp, Pbind( \instrument, \default, \note_obj, Pfunc { // construct chord with chordSort and notePat var chord = ~notePat.(~notes.select(_.notNil).sort(~chordSort)); var ret; var newChord; // apply offset if (~offset > 0) { chord = chord.reverse; }; ~offset.abs.do { var temp = chord.pop; chord = ([temp] ++ chord); }; if (chord == [nil]) { chord = [] }; if (~offset > 0) { chord = chord.reverse; }; // apply transposition steps newChord = chord; ~steps.abs.do { |i| newChord = newChord ++ chord.collect({ |note| note = note.copy; note.num = note.num + (~transpose * (i + 1)) }); }; if (~steps.sign.isNegative) { (~steps.abs - 1).do { |i| i = ~steps.abs - 2 - i; newChord = newChord ++ chord.collect({ |note| note = note.copy; note.num = note.num + (~transpose * (i + 1)) }); } }; chord = newChord; // fetch appropriate note object from chord if (chord.size > 0) { if (~ptr >= chord.size) { ~ptr = ~ptr % chord.size; ~repeat_i = ~repeat_i + 1; }; if (~repeat_i >= ~repeats) { ret = (num: Rest(), vel: 0, time: 0); ~ptr = 0; } { ret = chord[~ptr]; ~ptr = ~ptr + 1; } } { ret = (num: Rest(), vel: 0, time: 0); ~ptr = 0; }; ret; }, \midinote, Pfunc { |event| event[\note_obj].num }, \db, Pfunc { |event| var note = event[\note_obj]; ~velocityFunc.(note.vel, thisThread.seconds - note.time, ~clock.beats).linlin(0, 127, -24, 0); }, \dur, Pfunc { var base = ~rate * 4 * (1 + ~jitter.rand2); var ret = if (~i == 0) { base * (1 + ~swing) } { base * (1 - ~swing) }; ~i = (~i + 1) % 2; ret; }, \legato, Pfunc { ~legato } )).play(~clock); ) Pdef(\arp).stop