Last active
September 10, 2015 16:44
-
-
Save scztt/a4f5fd94a43c5d4fe4ed to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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