Taking the question at face value, the answer has to be no – because in SC, UGens are never responsible for MIDI input or output.
MIDI I/O is strictly the responsibility of the language client. UGens are for server-side signal processing. There is no connection between the two.
But of course the language is able to receive MIDI data, and store it in a format of your choosing. But there’s no built-in “MIDI sequence” object.
The most basic approach would be to keep a list of incoming MIDI messages. But IMO this format is not convenient for playback. MIDI represents the note is a pair of messages, separated in time. If you record the messages, then it’s your responsibility to pair up note on/off.
But, as a basic demo:
// simple MIDI recorder
(
l = List.new;
MIDIdef.noteOn(\recOn, { |vel, num|
l.add([\on, SystemClock.seconds, num, vel]);
});
MIDIdef.noteOff(\recOff, { |vel, num|
l.add([\off, SystemClock.seconds, num, vel]);
});
)
// stop recording
MIDIdef(\recOn).free; MIDIdef(\recOff).free;
// convert raw times to deltas
(
var deltas = l.flop.at(1).differentiate.put(0, 0);
l.do { |row, i| row[1] = deltas[i] };
)
// playback is inconvenient because note-off doesn't match up with note-on
(
var synths = Array.newClear(128);
r = Routine {
l.do { |row|
var type, delta, num, vel;
#type, delta, num, vel = row;
if(type == \on) {
s.makeBundle(s.latency, {
if(synths[num].notNil) {
synths[num].release;
};
synths[num] = Synth(\default, [freq: num.midicps, amp: vel/127]);
});
} {
s.makeBundle(s.latency, {
synths[num].release;
synths[num] = nil;
});
};
delta.wait;
};
}.play;
)
But this way it would be easy to interleave CC data.
SimpleMIDIFile in the wslib quark might be easier for this.
For my own purposes, I wanted notes to be represented as a single object rather than pairs of messages. So, using the ddwMIDI quark (these are not base classes – you would have to install the extension; see help files about Quarks for details), you can do this:
MIDIPort.init;
MIDIPort.sources; // find index of your keyboard: mine is 4
c = MIDIChannel([4, \omni]);
m = MIDIRecSocket([4, \omni]);
// recording is live now, play stuff
// when finished:
b = m.stopRecord;
// that returned a MIDIRecBuf with your notes
b.dumpSeq // already in deltas; also each note knows its 'length'
// much easier to sequence
(
p = Pbind(
\instrument, \default,
\seqnote, Pseq(b.notes),
// this translates SequenceNote data
// into entries that the default Event understands
#[midinote, dur, sustain, amp], Pfunc { |ev|
var n = ev[\seqnote];
[n.freq, n.dur, n.length, n.args]
}
).play;
)
But I didn’t include CC recording in MIDIRecSocket/MIDIRecBuf.
hjh