// Even better 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 January 2014 adc => Gain gin => LPF l1 => LPF l2 => Gain g => 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,270.0); MAUI_Slider high; high.name("Peak"); high.range(0.0,1.0); high.position(0.0,0.0); MAUI_Slider thresh; thresh.name("Threshold"); thresh.range(0.0,1.0); thresh.position(0.0,60.0); MAUI_Slider hyst; hyst.name("Hyst"); hyst.range(0.0,1.0); hyst.position(0.0,120.0); MAUI_Slider pit; pit.name("Pitch"); pit.range(50.0,600.0); pit.position(0.0,180.0); MAUI_Button exit; exit.name("Exit"); exit.position(220.0,0.0); myWinder.addElement(high); myWinder.addElement(thresh); myWinder.addElement(hyst); myWinder.addElement(pit); myWinder.addElement(exit); myWinder.display(); 0.8 => hyst.value; 0.2 => thresh.value; 0.0 => float peak; 0.0 => float peaktarg; 101.0 => float pitch; 101.0 => float pitchtarg; 101.0 => float lastpitch; spork ~ getPeak(); spork ~ guessPitch(0.1); spork ~ smoothStuff(); 1 => int running; float temp; while (running) { 0.1 :: second => now; peak => high.value => s.gain; pitchtarg/lastpitch => temp; if (temp<1.0) 1.0/temp => temp; if (temp > 1.01) pitch => pit.value => s.freq; (1-exit.state())=>running; } fun void smoothStuff() { while (running) { 0.001 :: second => now; (peak*0.95) + (0.05*peaktarg) => peak; (pitch*0.95) + (0.05*pitchtarg) => pitch; // pitchtarg => pitch; } } fun void guessPitch(float howoften) { 0.0 => float thissamp; 0.0 => float lastsamp; 0.8 => float myHyst; 0 => int numZeroes; 0 => int ticker; howoften * 44100.0 - 1.0 => float window; while (running) { g.last() - (myHyst*peaktarg) => thissamp; if (thissamp < 0.0) 0.0 => thissamp; if (lastsamp == 0.0 && thissamp > 0.0) 1 +=> numZeroes; thissamp => lastsamp; 1 +=> ticker; if (ticker>window) { if (peaktarg > thresh.value()) { 1.0 * numZeroes / howoften => pitchtarg; } if (pitchtarg < 50.0) 50.0 => pitchtarg; if (pitchtarg > 600.0) 600.0 => pitchtarg; hyst.value() => myHyst; 0 => ticker => numZeroes; } 1 :: samp => now; } } fun void getPeak() { float max; while (running) { 1 :: samp => now; max*0.99 => max => peaktarg; if (g.last() > max) g.last() => max; } } myWinder.destroy();