Skip to content

Instantly share code, notes, and snippets.

@scztt
Last active September 10, 2015 16:44
Show Gist options
  • Save scztt/a4f5fd94a43c5d4fe4ed to your computer and use it in GitHub Desktop.
Save scztt/a4f5fd94a43c5d4fe4ed to your computer and use it in GitHub Desktop.
// Settings
~recordPath = "~/Desktop".standardizePath;
~recordName = "sc_capture"; // e.g. recordings will be named "sc_capture [...].aiff"
~preroll = 0.5; // seconds to capture before sound starts
~bufferLength = s.sampleRate * 60;
// Threshold settings
// These can be changed while recorder is running, to refine threshold values
Ndef(\threshold).source = -24; // Record threshold, in dB
Ndef(\durThreshold).source = 0.2; // Minimum amount of time above \threshold for it to trigger
Ndef(\tail).source = 2; // How long after sound has stopped to record
Ndef(\maxDur).source = 30; // Max recording length, seconds
// Generate view
~view ?? {
~view = View(bounds:Rect(300, 300)).minSize_(150@80).layout_(VLayout(
~levelMeter = LevelIndicator().value_(0.51).drawsPeak_(true).peakLevel_(0.5),
~recordingIndicator = DragSink().string_("recording")
));
CmdPeriod.doOnce({ ~view !? { ~view.close } });
~view.onClose_({ ~view = nil });
~view.front;
};
/// Start recording synths
{
~recBuf = ~recBuf ?? { Buffer.alloc(s, ~bufferLength, 1) };
s.sync();
Ndef(\bufferPosition, {
Phasor.ar(1, 1, 0, ~bufferLength);
});
Ndef(\input, {
|outBuffer|
var threshold, durThreshold, tail, maxDur, bufferPosition;
var in, amp, recordTrigger;
threshold = Ndef(\threshold).kr;
durThreshold = Ndef(\durThreshold).kr;
tail = Ndef(\tail).kr;
maxDur = Ndef(\maxDur).kr;
bufferPosition = Ndef(\bufferPosition).ar;
in = SoundIn.ar(0) * 10;
BufWr.ar(in, outBuffer, bufferPosition);
amp = Amplitude.kr(in, attackTime:0.1, releaseTime:2).ampdb;
amp = (amp - threshold);
recordTrigger = Slew.kr(
in: amp.max(0).min(2),
up: 1 / durThreshold,
dn: 1 / tail
add: -0.01
) > 1;
SendReply.kr(recordTrigger - 0.5, '/record/on', [bufferPosition]);
SendReply.kr(0.5 - recordTrigger, '/record/off', [bufferPosition]);
SendReply.kr(Impulse.kr(20), '/level', amp);
recordTrigger;
}).set(
\outBuffer, ~recBuf.bufnum,
\bufferPosition, Ndef(\bufferPosition)
);
~makeRecordSynth = {
| inBuffer, outBuffer, timeOffset |
var delayedRecLocation, sig, bufferPosition;
Line.kr(0, 1, Ndef(\maxDur).kr, doneAction:2); // auto-free after maxDur
bufferPosition = Ndef(\bufferPosition).ar;
delayedRecLocation = (bufferPosition - timeOffset) % BufFrames.ir(inBuffer);
sig = BufRd.ar(1, ~recBuf.bufnum, delayedRecLocation, loop:1);
DiskOut.ar(outBuffer, sig);
sig
};
~recordSynth = nil;
~recordingCounter = 0;
OSCdef('/level', {
|msg|
{
~levelMeter.warning_(0.5).critical_(1).warning_(0.5);
~levelMeter.value = msg[3].linlin(-40, 40, 0, 1);
}.defer
}, '/level');
OSCdef('/record/on', {
|msg|
var outBuffer;
var recordingNum, recordingName, recordingPath, startTime;
{ ~recordingIndicator.background = Color.green(1, 0.5) }.defer;
startTime = Date.localtime;
recordingNum = ~recordingCounter;
~recordingCounter = ~recordingCounter + 1;
recordingName = "(%-% %.%.%)".format(
startTime.month,
startTime.day,
startTime.hour,
startTime.minute,
startTime.second,
);
recordingName = recordingName ++ ".aiff";
recordingPath = ~recordPath +/+ recordingName;
// If we're currently recording, stop that one...
if (~recordSynth.notNil) {
~recordSynth.free;
};
outBuffer = Buffer.alloc(s, 65536, 1);
outBuffer.updateInfo({
|outbuffer|
// Roll back to when the sound started, plus a little extra to catch the onset
var timeOffset = (Ndef(\durThreshold).bus.getSynchronous + ~preroll) * s.sampleRate;
"%: Started recording to file '%'".format(startTime.hourStamp, recordingName).postln;
outBuffer.write(recordingPath, "aiff", "int24", 0, 0, leaveOpen:true);
~recordSynth = ~makeRecordSynth.play(
args: [
\inBuffer, ~recBuf.bufnum,
\outBuffer, outBuffer.bufnum,
\timeOffset, timeOffset
]
);
~recordSynth.onFree({
"%: Finished recording to file %".format(Date().localtime.hourStamp, recordingName).postln;
~recordSynth = nil;
// Defer closing/freeing the buffer to make sure we get the data;
fork {
0.5.wait;
outBuffer.close;
0.5.wait;
outBuffer.free;
}
});
});
}, '/record/on');
OSCdef('/record/off', {
// On record off, just free the old synth - the onFree function above will take care of
// cleanup, saving the buffer, etc.
{ ~recordingIndicator.background = Color.clear }.defer;
if (~recordSynth.notNil) {
~recordSynth.free;
}
}, '/record/off');
}.fork(AppClock);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment