// Chugin-based zero-crossing based pitch detector // uses hysteresis (crossings of some level located // between zero and the current/recent peak) // Also has pitch smoothing, tracks peak, controls sine // by Perry R. Cook, updated March 2014 adc => Gain gin => LPF l1 => LPF l2 => TDFeatures tdf => blackhole; 1.0 => l1.Q => l2.Q; 600.0 => l1.freq => l2.freq; 8.0 => gin.gain; // global input gain SinOsc s => dac; MAUI_View myWinder; myWinder.size(300.0,300.0); MAUI_Slider high; high.name("PositivePeak"); high.range(0.0,1.0); high.position(0,0); MAUI_Slider low; low.name("NegativePeak"); low.range(-1.0,0.0); low.position(0,50); MAUI_Slider thresh; thresh.name("Threshold"); thresh.range(0.0,1.0); thresh.position(0,100); MAUI_Slider hyst; hyst.name("Hyst"); hyst.range(0.0,1.0); hyst.position(0,150); MAUI_Slider pit; pit.name("Pitch"); pit.range(50.0,600.0); pit.position(0,200); MAUI_Button exit; exit.name("Exit"); exit.position(220.0,0.0); myWinder.addElement(high); myWinder.addElement(low); myWinder.addElement(thresh); myWinder.addElement(hyst); myWinder.addElement(pit); myWinder.addElement(exit); myWinder.display(); 0.0 => hyst.value; 0.2 => thresh.value; 0.0 => float ppeak; 0.0 => float npeak; 101.0 => float pitch; 101.0 => float pitchtarg; 101.0 => float lastpitch; 1 => int running; float temp; spork ~ smoothStuff(); while (running) { 4410 :: samp => now; ppeak => high.value => s.gain; npeak => low.value; tdf.pitch() => pitchtarg; pitchtarg/lastpitch => temp; if (temp<1.0) 1.0/temp => temp; if (temp > 1.01) pitch => pit.value => s.freq; (1-exit.state())=>running; hyst.value() => tdf.hyst; } fun void smoothStuff() { while (running) { 0.001 :: second => now; (ppeak*0.95) + (0.05*tdf.peakp()) => ppeak; (npeak*0.95) + (0.05*tdf.peakn()) => npeak; (pitch*0.95) + (0.05*pitchtarg) => pitch; // pitchtarg => pitch; } } myWinder.destroy();