| 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
This comment has been minimized.
basic demonstration in supercollider of: