Hello again,
I’ve made decent headway on this. My overarching goal is to sync up file output for audio file recording (in my case using RecNodeProxy, and the Pattern MIDI output above.
I’ve created/modified a class to try and handle all of the recording. I’ve added the last entry from @scztt (thank you) into the class init. All of this works. However, my ‘record’ method in my class starts recording audio on grid as it should when I execute it. The MIDI however is getting it’s ‘starttime’ in the .addNote() function from what was originally the TempoClock.default
- this meant that when I’d record, and depending upon how much time elapsed the midi was WAY later in time.
So, I guess my question and what I don’t understand is the best way to use clocks, and probably one in my class just for recording that can still be referenced everywhere. It’d also be nice to have control over a master Tempo that changes while recording - but I don’t think I’m quite understanding clocks (even after reading through the docs and threads here numerous times).
Source files - I think I’m also unsure about scope of what the Pdef Events can see or not. I added a key reference to a clock both to each \midi Pbind as well as a var in the Class. Not totally sure about that. I feel like I know just enough to be dangerous at this point…
NOTE: I mentioned all was working fine above - that was before some of these attempts at not using the default clock. That was where I was getting the audio stems out right at ‘record’ but the MIDI notes were being written out at current logical time (I think).
Class File:
HypoRecorder {
var <>nodes;
var <>midiTracks;
var <>smf;
var <>folder;
var <>recClock;
var <>headerFormat = "aiff", <>sampleFormat = "float";
*new { |subfolder = nil |
^super.newCopyArgs().init(subfolder)
}
init { | subfolder = nil |
nodes = ();
if(subfolder != nil,
{folder = Platform.userAppSupportDir +/+ "Recordings" +/+ Document.current.title +/+ subfolder },
{folder = Platform.userAppSupportDir +/+ "Recordings" +/+ Document.current.title }
);
File.mkdir(folder);
Event.addEventType(
\midi,
Event.eventTypes[\midi].addFunc({
if (~recordTarget.notNil) {
~recordTarget.addNote(
noteNumber: ~midinote,
velo: ~amp.range(0, 127),
startTime: ~mclock.beats,
dur: ~dur,
channel: ~chan,
track: ~chan,
);
~recordTarget.midiEvents.dopostln;
}
}),
() // if you want some recording-related properties to have default values, pass them here
);
}
free {
nodes.do(_.clear);
nodes = nil;
}
add { |proxies, midis = 2, clock |
midiTracks = midis;
if(clock.isNil, { recClock = TempoClock.default }, { recClock = clock });
this.prepareNodes(proxies);
{ this.open(proxies) }.defer(0.5);
}
prepareNodes { |proxies|
proxies.do{ |proxy, i|
var n = Ndef(proxy);
n.play;
n.postln;
nodes.add(
i -> RecNodeProxy.newFrom(n, 2)
);
}
}
open { |proxies |
var dateTime = Date.getDate.format("%Y%m%d-%Hh%m");
proxies.do{ |proxy, i|
var fileName = ("%/%-%.%").format(
folder, dateTime, proxy.asCompileString, headerFormat
);
nodes[i].open(fileName, headerFormat, sampleFormat);
};
smf = SimpleMIDIFile(folder +/+ dateTime ++ "midi.mid");
}
record { |paused=false |
smf.init1(midiTracks, recClock.tempo * 60, "4/4");
smf.timeMode = \seconds;
nodes.do(_.record(paused, recClock, -1));
}
stop {
this.close
}
close {
nodes.do(_.close);
smf.midiEvents.dopostln;
smf.adjustEndOfTrack;
smf.metaEvents.dopostln;
smf.write;
}
pause {
nodes.do(_.pause)
}
unpause {
nodes.do(_.unpause)
}
closeOne { |node|
}
}
Usage File:
(
SynthDef(\bplay,
{arg out = 0, buf = 0, rate = 1, amp = 0.5, pan = 0, pos = 0, rel=15;
var sig,env=1;
sig = Mix.ar(PlayBuf.ar(2,buf,BufRateScale.ir(buf) * rate,1,BufDur.kr(buf)*pos*44100,doneAction:2));
env = EnvGen.ar(Env.linen(0.0,rel,0),doneAction:0);
sig = sig * env;
sig = sig * amp;
Out.ar(out,Pan2.ar(sig.dup,pan));
}).add;
)
~rec = HypoRecorder('test');
~recClock = TempoClock.new(TempoClock.default.tempo);
~rec.add([\hh, \b], 3, ~recClock);
~rec.record();
~rec.stop;
(
Ndef(\lead, { | out, freq = 48, relTime = 2 |
var sig = 0, temp, env, curv;
out.postln;
// curv = [\step, \sin, \wel].scramble;
env = EnvGen.kr(Env.perc(0.5, releaseTime:relTime, curve: \step), doneAction: 2);
8.do{ | i |
temp = LFPulse.ar(freq + Rand(0, i), LFPulse.kr(Rand(0, i).round(rrand(0.125, i))).midicps)!2 / 8;
sig = sig + temp * env * 0.9;
Out.ar(out, sig * 0.05);
}
})
)
(
Ndef(\lead).play;
Pdef(\leadmidi, Pbind(
\type, \midi,
\midiout, m,
\midicmd, \noteOn,
\chan, 3,
\mclock, ~recClock,
\recordTarget, ~rec.smf,
));
Pdef(\leadseq,
Pbind(
\dur, Pseq([0.125, 0.5, 1, 2, 0.25, 0.125, 0.125, 0.5, Rest(4), Rest(2), Rest(1)].scramble, inf),
\degree, Pseq(Scale.hijaz.degrees.mirror.scramble -5, inf),
\octave, Pwhite(2, 4, inf).round(1),
\relTime, Pseq([1, 2, 3, 0.5], inf),
\out, Pfunc(Ndef(\lead).bus.index),
\group, Pfunc(Ndef(\lead).group),
))
)
(
Pdef(\lead,
Pdef(\leadmidi) <> Pdef(\leadseq);
)
)
Ndef(\lead).play;
Ndef(\lead)[1] = \xset -> Pdef(\lead);
Pdef(\leadseq).play;
Pdef(\lead).play(quant: -1);
(
Ndef(\hh).play;
Pdef(\hhmidi,
Pbind(
\type, \midi,
\midiout, m,
\midicmd, \noteOn,
\chan, 0,
\mclock, ~recClock,
\recordTarget, ~rec.smf,
));
Pdef(\hhsynth,
Pbind(
\instrument, \bplay,
\out, Pfunc(Ndef(\hh).bus.index),
\group, Pfunc(Ndef(\hh).group),
\buf, d["Hats"][1],
));
Pdef(\hhseq,
Pbind(
\dur, Pbjorklund2(Pseq([3, 5, 7, 9, 11], inf), Pseq([12, 4, 6, 7], inf)),
));
)
(
Pdef(\hh,
Ppar([
Pdef(\hhmidi),
Pdef(\hhsynth),
])
<> PtimeClutch(Pdef(\hhseq))
);
)
(
Ndef(\b).play;
Pdef(\bmidi,
Pbind(
\type, \midi,
\midiout, m,
\midicmd, \noteOn,
\chan, 1,
\mclock, ~recClock,
\recordTarget, ~rec.smf,
));
Pdef(\bsynth,
Pbind(
\out, Pfunc(Ndef(\b).bus.index),
\group, Pfunc(Ndef(\b).group),
\instrument, \bplay,
\buf, d["Bass Drums"][5],
));
Pdef(\bseq,
Pbind(
// \dur, 0.125,
\dur, 1,
\amp, 0.4
));
)
(
Pdef(\b,
Ppar([
Pdef(\bmidi),
Pdef(\bsynth),
])
<> PtimeClutch(Pdef(\bseq))
)
)
Pdef(\hh).play(quant: -1);
Pdef(\hh).stop;
Ndef(\hh)[10] = \filter -> { | in | DelayC.ar(in, 3, 5) };
Ndef(\hh).set(\wet10, 0.5);
Pdef(\b).play(quant: -1);
Pdef(\b).stop;
Pdef(\leadmidi).play;
Ndef(\b)[10] = \filter -> { | in | DelayC.ar(in, 6, 5) };
Ndef(\b).set(\wet10, 0.5);