Last active
July 29, 2021 04:43
-
-
Save catfact/a659f515a64a6c8852b7b500760c3e74 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
Routine { | |
s = Server.default; | |
s.waitForBoot; | |
// max buffer duration | |
~dur = 8.0; | |
// 4-channel buffer: pitch, clar, amp, flatness | |
~buf = Buffer.alloc(s, s.sampleRate * ~dur, 4); | |
// record analysis | |
SynthDef.new(\ana_rec, { | |
arg buf, in=0, trig=0, loop=1, run=0; | |
var snd, hz, clar, amp, flat; | |
snd = SoundIn.ar(in); | |
amp = Amplitude.kr(snd); | |
#hz, clar = Pitch.kr(snd, clar:1); | |
flat = SpecFlatness.kr(FFT(LocalBuf(1024), snd)); | |
// just record everything at audio rate | |
RecordBuf.ar(K2A.ar([amp, hz, clar, flat]), buf, trigger:trig, loop:loop, run:run); | |
}).send(s); | |
// play back analysis results as oscilaltor parameters | |
// NB: in var/arg names, "pitch" is logarithmic frequency and "hz" is linear. | |
SynthDef.new(\ana_play, { | |
//--- arguments | |
arg buf, // analysis buffer | |
out=0, pan=0, level=0.25, | |
// playback controls | |
rate=1, trig=0, pos=0, loop=1, | |
// quantization amounts | |
pitch_quant=0, amp_quant=0, timbre_quant=0, | |
// logarithmic pitch must be scaled around an arbitray reference point | |
pitch_center = 60, // A440 | |
// amp scaling (pre-quantization) | |
amp_mul=0.0, amp_add=0.5, | |
// pitch scaling (pre-quantization) | |
pitch_mul=1.0, pitch_add=0.0, | |
// clarity threhsold for pitch hysteresis | |
clar_thresh = 0.9; | |
//--- variables | |
var pb; // playback from analysis buffer | |
var amp, hz, clar, flat; // analysis channels | |
var osc; // oscaillator output | |
var pitch; // intermediate log pitch value | |
// quantized parameters | |
var quant_hz, quant_amp, quant_timbre; | |
pb = PlayBuf.ar(4, buf, rate, trig, pos, loop); | |
amp = pb[0]; | |
hz = pb[1]; | |
clar = pb[2]; | |
flat = pb[3]; | |
// hold pitch when clarity is low | |
////////////////////// | |
// reader exercise: | |
// might want more sophisticated pitch hysteresis / filtering algorithm | |
hz = Gate.ar(hz, clar > clar_thresh); | |
// apply quantizations | |
quant_amp = (amp * amp_mul + amp_add).round(amp_quant); | |
pitch = hz.cpsmidi; | |
pitch = (pitch - pitch_center) * pitch_mul + pitch_center + pitch_add; | |
quant_hz = pitch.round(pitch_quant).midicps.min(12000).max(0); | |
///////////////////// | |
// reader exercise: | |
// linear spectral flatness doesn't really map well to this "timbre" parameter. | |
// might want to put in log scale and clamp bounds | |
quant_timbre = flat.round(timbre_quant); | |
////////////////////// | |
// reader exercise 2: | |
// smoothing times could be parameterized | |
quant_amp = LagUD.ar(quant_amp, 0.001, 0.1); | |
quant_hz = Lag.ar(quant_hz, 0.01); | |
quant_timbre = Lag.ar(quant_timbre, 0.1); | |
osc = SelectX.ar(quant_timbre, [SinOsc.ar(quant_hz), Saw.ar(quant_hz)]) * quant_amp; | |
Out.ar(out, Pan2.ar(osc, pan, level)); | |
}).send(s); | |
s.sync; | |
~ana_rec_s = Synth.new(\ana_rec, [\buf, ~buf], s); | |
~ana_play_s = Synth.new(\ana_play, [\buf, ~buf], s); | |
//////////////// | |
//////////////// | |
//// a very basic GUI | |
{ | |
~specs = [ | |
(name: \rate, min:-8, max:8, step:0.125), | |
(name: \pitch_add, min:-24, max:24, step:1), | |
(name: \pitch_quant, min:0, max:12, step:0.125), | |
(name: \pitch_center, min:1, max:100, step:0.1), | |
(name: \pitch_mul, min:-2.0, max:2.0, step:0.0625) | |
//... add more entries here for more param controls | |
]; | |
w = Window.new(bounds:Rect(0, 0, 300, 400)); | |
l = FlowLayout( w.view.bounds, 10@10, 20@5 ); | |
w.view.decorator = l; | |
Button(w, 80@40) | |
.states_([["record", Color.black, Color.green], ["stop", Color.white, Color.red]]) | |
.action_({ |v| | |
v.value.postln; | |
if(v.value > 0, { | |
~ana_rec_s.set(\run, 1); | |
~ana_rec_s.set(\trig, 0); | |
~ana_rec_s.set(\trig, 1); | |
}, { | |
~ana_rec_s.set(\run, 0); | |
}); | |
}); | |
// collection of number boxes | |
n = (); | |
~specs.do({ |spec| | |
l.nextLine; | |
StaticText(w, 80@40).string_(spec.name.asString); | |
n[spec.name] = NumberBox(w, 120@40) | |
.action_({|v| ~ana_play_s.set(spec.name, v.value); }) | |
.clipLo_(spec.min) | |
.clipHi_(spec.max) | |
.decimals_(3) | |
.step_(spec.step) | |
}); | |
w.front; | |
// initialize numboxes to current values | |
~specs.do({ |spec| | |
spec.postln; | |
~ana_play_s.get(spec.name, {|val| | |
val.postln; | |
{ n[spec.name].value_(val); }.defer; | |
}); | |
}); | |
}.defer; | |
}.play; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
basic demonstration in supercollider of: