// Time: power, peaks, zero crossings, pitch/periodicity // Freq: flux, centroid, tilt, rolloffs // Freq: Formants, subband energy // Freq: HNR, harmonicity // Hybr: cepstral coefficients, pitch peak 1 => int notDone; float pNeg, pPos; // global min and max peak variables float zXings; // global zero crossing (pos+neg) variable float rms, flux, centroid, tilt, rolloff30, rolloff60, rolloff90; adc => FFT fft; 2048 => int fftSize => fft.size; Windowing.hann(2048) => fft.window; 1024 => int hopSize; 10 => int NUMCEPS; float cepcoeffs[NUMCEPS+1]; spork ~ countZCs(0.1 :: second); spork ~ doPeaks(0.1 :: second); spork ~ updateFFT(); MAUI_View myWinder; myWinder.name("Features Display"); myWinder.size(600,600); MAUI_Button exit; exit.name("Exit"); exit.position(500,500); exit.toggleType(); myWinder.addElement(exit); MAUI_Slider sliders[10][2]; setupGUI(); fun void setupGUI() { int i; for (0 => i; i < 5; i++) { sliders[i][0].position(0,i*50); sliders[i][0].size(200,60); sliders[i][0].range(0.0,1.0); myWinder.addElement(sliders[i][0]); sliders[i][1].position(210,i*50); sliders[i][1].size(200,60); sliders[i][1].range(0.0,1.0); myWinder.addElement(sliders[i][1]); } for (5 => i; i < 10; i++) { sliders[i][0].position(0,i*50+75); sliders[i][0].size(200,60); sliders[i][0].range(-2.0,2.0); myWinder.addElement(sliders[i][0]); sliders[i][1].position(210,i*50+75); sliders[i][1].size(200,60); sliders[i][1].range(-2.0,2.0); myWinder.addElement(sliders[i][1]); } "RMS Energy" => sliders[0][0].name; "Zero Xings" => sliders[0][1].name; "Negative Peak" => sliders[1][0].name; "Positive Peak" => sliders[1][1].name; "Spectral Flux" => sliders[2][0].name; "Spectral Centroid" => sliders[3][0].name; "Spectral Tilt" => sliders[4][0].name; "30% Rolloff" => sliders[2][1].name; "60% Rolloff" => sliders[3][1].name; "90% Rolloff" => sliders[4][1].name; for (0 => i; i < 5; i++) { "Cepstr"+Std.itoa(i+1) => sliders[5+i][0].name; "Cepstr"+Std.itoa(i+6) => sliders[5+i][1].name; } sliders[1][0].range(-1.0,0.0); myWinder.display(); } while (notDone) { 0.1 :: second => now; 100.0*rms => sliders[0][0].value; zXings => sliders[0][1].value; pNeg => sliders[1][0].value; pPos => sliders[1][1].value; flux => sliders[2][0].value; centroid => sliders[3][0].value; tilt => sliders[4][0].value; rolloff30 => sliders[2][1].value; rolloff60 => sliders[3][1].value, rolloff90 => sliders[4][1].value; for (0 => int i; i < 5; i++) { cepcoeffs[i+1] => sliders[5+i][0].value; cepcoeffs[i+5+1] => sliders[5+i][1].value; } if (exit.state()) 0 => notDone; } myWinder.destroy(); fun void updateFFT() { fft =^ RMS rmess => blackhole; fft =^ Flux flx => blackhole; fft =^ Centroid cntrd => blackhole; fft =^ RollOff rff30 => blackhole; fft =^ RollOff rff60 => blackhole; fft =^ RollOff rff90 => blackhole; fft =^ IFFT fft2 => blackhole; fftSize => fft2.size; UAnaBlob blob; 0.3 => rff30.percent; 0.6 => rff60.percent; 0.9 => rff90.percent; while (notDone) { hopSize :: samp => now; fft.upchuck() @=> blob; rmess.upchuck(); rmess.fval(0) => rms; cntrd.upchuck(); cntrd.fval(0) => centroid; flx.upchuck(); flx.fval(0) => flux; rff30.upchuck(); rff30.fval(0) => rolloff30; rff60.upchuck(); rff60.fval(0) => rolloff60; rff90.upchuck(); rff90.fval(0) => rolloff90; for (0 => int i; i < fft.size()/2; i++) { #(Math.log(blob.fvals()[i]),0.0) => blob.cvals()[i]; } fft2.upchuck() @=> blob; // Inverse FFT blob.fvals() @=> float m[]; Math.log(m[0]*m[0]/fftSize) => m[0]; if (m[0] < 1.0) 0 => m[0]; // Math.log(m[0]*m[0]/fftSize/fftSize) => m[0]; for (1 => int i; i < fft.size()/2; i++) { // Math.log(m[i] * m[i] / fftSize / fftSize)/m[0] => m[i]; Math.log(m[i] * m[i] / fftSize)/m[0] => m[i]; } 1.0 => m[0]; for (1 => int i; i < NUMCEPS+1; i++) { m[i] => cepcoeffs[i]; } } } // Count total (posgoing + neggoing) zero crossings // updated per duration, saved as normalized per second // If you wanna use this for (bad) pitch, divide it by 2 fun void countZCs(dur howOften) { // one pole "traps" all ZCs adc => ZeroX zX => FullRect zRect => OnePole zCount => blackhole; -1.0 => zCount.a1; // set up one pole as 1.0 => zCount.b0; // unity gain integrator while (notDone) { howOften => now; // count ZCs for duration zCount.last() / (howOften / samp) => zXings; 0.0 => zCount.a1 => zCount.b0; // empty out 1.0 :: samp => now; // the integrator -1.0 => zCount.a1; // set integrator 1.0 => zCount.b0; // back to counting } } // track maximum positive and negative peaks // within every howOften duration window fun void doPeaks(dur howOften) { 0.0 :: samp => dur pTimer; 0.0 => float pMin; 0.0 => float pMax; adc => Gain pIn => blackhole; while (notDone) { 1.0 :: samp => now; 1.0 :: samp +=> pTimer; if (pTimer >= howOften) { pMin => pNeg; pMax => pPos; 0.0 => pMin => pMax; 0.0 :: second => pTimer; } else { pIn.last() => float temp; if (temp > pMax) temp => pMax; if (temp < pMin) temp => pMin; } } }