// Basic audio features computation and display // By Perry R. Cook, 2012 // Also include a simple 1-NN classifier // Time: power, waveform peaks, zero crossings, // Freq: flux, centroid, tilt, rolloffs // Run this from inside MiniAudicle, // or with MAUI-Enabled ChucK 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 => Gain inGain => FFT fft; 100.0 => inGain.gain; 2048 => int fftSize => fft.size; fftSize / 2 => int halfSize; Windowing.hann(2048) => fft.window; 1024 => int hopSize; MAUI_View myWinder; myWinder.name("Basic Audio Features Display"); myWinder.size(550,270); MAUI_Button exit; exit.name("Exit"); exit.size(150,70); exit.position(410,200); exit.toggleType(); myWinder.addElement(exit); MAUI_Slider sliders[10]; MAUI_Button enables[10]; MAUI_Button states[5]; MAUI_LED classes[5]; setupGUI(); spork ~ countZCs(0.1 :: second); spork ~ doPeaks(0.1 :: second); spork ~ updateFFT(); float vect[10]; float neighbors[5][10]; for (0=>int i; i<5; i++) { for (0=>int j; j<10; j++) { Std.rand2f(0.0,1.0) => neighbors[i][i]; } } while (notDone) { 0.1 :: second => now; 100.0*rms => vect[0] => sliders[0].value; zXings => vect[1] => sliders[5].value; pNeg => vect[2] => sliders[1].value; pPos => vect[3] => sliders[6].value; flux => vect[4] => sliders[2].value; centroid => vect[5] => sliders[3].value; tilt => vect[6] => sliders[4].value; rolloff30 => vect[7] => sliders[7].value; rolloff60 => vect[8] => sliders[8].value, rolloff90 => vect[9] => sliders[9].value; if (exit.state()) 0 => notDone; 100000.0 => float bestdist; -1 => int which; for (0 => int i; i < 5; i++) { classes[i].unlight(); 0.0 => float dist; for (0=>int j;j<10;j++) { enables[j].state()*(vect[j]-neighbors[i][j]) => float temp; temp*temp +=> dist; } if (dist < bestdist) { dist => bestdist; i => which; } } classes[which].light(); train(); } myWinder.destroy(); fun void train() { for (0 => int i; i<5; i++) { if (states[i].state()) { for (0 => int j; j < 10; j++) { vect[j] => neighbors[i][j]; } } } } 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; 0.3 => rff30.percent; 0.6 => rff60.percent; 0.9 => rff90.percent; UAnaBlob blob; halfSize/2 => float xbar; // we know this while (notDone) { hopSize :: samp => now; 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; fft.upchuck() @=> blob; blob.fvals() @=> float mags[]; 0.0 => float ybar; for (0 => int i; i < halfSize; i++) { if (mags[i] > 0.00000001) Math.log10(mags[i]) => mags[i]; else -8.0 => mags[i]; mags[i] +=> ybar; } ybar / halfSize => ybar; 0.0 => tilt; for (0 => int i; i < halfSize; i++) { (i - xbar)*(mags[i]-ybar) +=> tilt; } tilt / halfSize / halfSize => tilt; // <<< ybar, tilt >>>; } } // 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; } } } fun void setupGUI() { for (0 => int i; i < 5; i++) { enables[i].position(0,i*50+10); enables[i].size(60,60); enables[i].toggleType(); 1 => enables[i].state; myWinder.addElement(enables[i]); sliders[i].position(30,i*50); sliders[i].size(200,60); sliders[i].range(0.0,1.0); myWinder.addElement(sliders[i]); enables[i+5].position(210,i*50+10); enables[i+5].size(60,60); enables[i+5].toggleType(); 1 => enables[i+5].state; myWinder.addElement(enables[i+5]); sliders[i+5].position(240,i*50); sliders[i+5].size(200,60); sliders[i+5].range(0.0,1.0); myWinder.addElement(sliders[i+5]); states[i].size(80,70); states[i].position(425,i*40); states[i].name(Std.itoa(i+1)); myWinder.addElement(states[i]); classes[i].position(480,i*40+2); myWinder.addElement(classes[i]); } "RMS Energy" => sliders[0].name; "Zero Xings" => sliders[5].name; "Negative Peak" => sliders[1].name; "Positive Peak" => sliders[6].name; "Spectral Flux" => sliders[2].name; "Spectral Centroid" => sliders[3].name; "Spectral Tilt" => sliders[4].name; "30% Rolloff" => sliders[7].name; "60% Rolloff" => sliders[8].name; "90% Rolloff" => sliders[9].name; sliders[1].range(-1.0,0.0); sliders[4].range(-1.0,0.0); myWinder.display(); }