//practical sound analysis - fredrik olofsson
//organized by lullcec 28-30oct 2011, hangar, barcelona
//--session #02
//--time-domain approaches, mapping/scaling/filtering
//--thresholds
{var src= SinOsc.ar(100); src}.plot(0.1)
{var src= SinOsc.ar(100); src>0.9}.plot(0.1)
{var src= SinOsc.ar(100); [src>0.2, src]}.plot(0.1)
//--amplitude
{var src= SinOsc.ar(100); [Amplitude.ar(src, 0.01, 0.01), src]}.plot(0.1)
{var src= SinOsc.ar(100); [Amplitude.ar(src, 0.01, 0.1), src]}.plot(0.1)
{var src= Impulse.ar(100); [Amplitude.ar(src, 0, 0.01), src]}.plot(0.1)
{Amplitude.ar(SinOsc.ar(100), 0.01, 0.1).poll; DC.ar(0)}.play //note not 1.0
//attack and release settings
{var src= BrownNoise.ar; [Amplitude.ar(src), src]}.plot(0.1)
{var src= BrownNoise.ar; [Amplitude.ar(src, 0.001, 0.001), src]}.plot(0.1)
{var src= BrownNoise.ar; [Amplitude.ar(src, 0.0001, 0.01), src]}.plot(0.1)
{var src= BrownNoise.ar; [Amplitude.ar(src, 0.1, 0.2), src]}.plot(0.1)
{var src= DelayN.ar(SoundIn.ar, 1, 1); [Amplitude.ar(src, MouseX.kr(0.0001, 1, 1), MouseY.kr(0.0001, 1, 1)), src]}.scope(bufsize: 256)
//mouse x/y controls sensitivity of the amplitude tracker. left channel scope show tracker, right channel scope mic
//try moving the mouse around the screen and compare left and righ channels on the scope
//note: Amplitude can often be used just as well in control rate (.kr) for less cpu
//--frequency
//cheap pitch tracking
{ZeroCrossing.ar(SinOsc.ar(100)).poll; DC.ar(0)}.play //the estimated frequency. (amount of crossings per second divided by 2 note: the semantics are not correct - it should be 200 crossings)
{ZeroCrossing.ar(SoundIn.ar).poll; DC.ar(0)}.play //simple and crude pitch estimation of mic input
//the big problem is that it does not deal with overtones
//good pitch tracking
{Pitch.kr(Mix(SinOsc.ar([100, 200]))).poll; DC.ar(0)}.play; //notice .kr and double output
{Pitch.kr(SoundIn.ar)[0].poll; DC.ar(0)}.play; //frequency in hz (or last stable pitch)
{Pitch.kr(SoundIn.ar)[1].poll; DC.ar(0)}.play; //pitch detected or not (use as trigger)
{Pitch.kr(SoundIn.ar, clar:1)[1].poll; DC.ar(0)}.play //clarity flag (note: might be >1 at times)
//see Pitch helpfile
{Pitch.kr(SoundIn.ar, minFreq: 90, maxFreq: 150).poll; DC.ar(0)}.play
{Pitch.kr(SoundIn.ar, median: 70)[0].poll; DC.ar(0)}.play
//test setting minFreq and maxFreq
//read about the clar argument
//try the built-in median smooth functionality
//--triggers
//counting
{PulseCount.ar(SoundIn.ar>0.5).poll; DC.ar(0)}.play //tap the mic
{PulseCount.ar(Amplitude.ar(SoundIn.ar, 0.01, 0.1)>0.2).poll; DC.ar(0)}.play
{Stepper.ar(Amplitude.ar(SoundIn.ar, 0.01, 0.1)>0.2, 4, 4, 10).poll; DC.ar(0)}.play //setting attack and releasetime to get a better count
{SinOsc.ar(Stepper.ar(Amplitude.ar(SoundIn.ar, 0.01, 0.1)>0.2, 0, 0, 7).linexp(0, 7, 300, 3000), 0, 0.1)}.play
//time since last trig in seconds
{Timer.ar(Amplitude.ar(SoundIn.ar, 0.01, 0.1)>0.2).poll; DC.ar(0)}.play
//--peak following
{Peak.ar(SoundIn.ar, MouseButton.kr).poll; DC.ar(0)}.play
{var src= BrownNoise.ar; [Peak.ar(src), src]}.plot(0.01)
{PeakFollower.ar(SoundIn.ar, MouseX.kr(0, 1)).poll; DC.ar(0)}.play
{var src= BrownNoise.ar; [PeakFollower.ar(src, 0.99), src]}.plot(0.1)
{RunningMin.ar(SoundIn.ar, MouseButton.kr).poll; DC.ar(0)}.play
{RunningMax.ar(SoundIn.ar, MouseButton.kr).poll; DC.ar(0)}.play
{RunningSum.rms(SinOsc.ar(100, 0, MouseX.kr(0, 1)), 50).poll; DC.ar(0)}.play
{RunningSum.rms(SoundIn.ar, 50).poll; DC.ar(0)}.play
{Slope.ar(SoundIn.ar).poll; DC.ar(0)}.play
{Median.ar(10, SoundIn.ar.abs).poll; DC.ar(0)}.play //used as amp follower - note the .abs
//--more on thresholds and triggers
//amount of change in the signal
{HPZ1.ar(SinOsc.ar(0.2)).poll; DC.ar(0)}.play
{var src= LFSaw.ar(200); [HPZ1.ar(src), src]}.plot
{var src= LFSaw.ar(200); [HPZ1.ar(src).abs, src]}.plot //use abs to get a trigger for signal change
//a common way of detecting big changes in signals
{var src= BrownNoise.ar; [HPZ1.ar(src).abs>0.06, src]}.plot
//(also see Changed in sc3.5)
{var src= SoundIn.ar; Pan2.ar(SinOsc.ar(200)*EnvGen.ar(Env.perc, HPZ1.ar(src).abs.poll>0.1), 1)}.play //tap the mic
//hysteresis
{var src= BrownNoise.ar; [Schmidt.ar(src, -0.2, 0.2), src>0, src]}.plot //compare the first and second channel
//removing double triggers
{var src= Dust.ar(1000); [Trig1.ar(src, 0.001), src]}.plot
{var src= Dust.ar(1000); [src.lagud(0, 0.001)>0.01, src]}.plot //alternative
//--scaling, mapping, filtering
linlin
linexp
explin
lag
lag2
lagud
midicps
cpsmidi
degreeToKey
lincurve
curvelin
//etc. see UGen helpfile
//map from a linear range, to another linear range
{BrownNoise.ar.linlin(-1, 1, 0, 100)}.plot(minval:0, maxval:100)
{BrownNoise.ar.linexp(-1, 1, 400, 4000)}.plot(minval:400, maxval:4000)
{BrownNoise.ar.lincurve(-1, 1, 400, 4000, 10)}.plot(minval:400, maxval: 4000)
{BrownNoise.ar.lag(0.004)}.plot
{BrownNoise.ar.lag(0.004)}.plot
{BrownNoise.ar.lagud(0, 0.01)}.plot
//use headphones!
//filtering
{var pct= Pitch.kr(DelayN.ar(SoundIn.ar, 1, 1)); SinOsc.ar(pct[0], 0, pct[1]*0.1)!2}.play
{var pct= Pitch.kr(DelayN.ar(SoundIn.ar, 1, 1)); SinOsc.ar(pct[0].lag(0.5), 0, pct[1].lag(0.1)*0.1)!2}.play
{var pct= Pitch.kr(DelayN.ar(SoundIn.ar, 1, 1)); SinOsc.ar(pct[0].lag(MouseX.kr(0, 1)), 0, pct[1].lag(0.1)*0.1)!2}.play
{var pct= Pitch.kr(DelayN.ar(SoundIn.ar, 1, 1)); SinOsc.ar(pct[0].lagud(MouseX.kr(0, 1), MouseY.kr(0, 1)), 0, pct[1].lag(0.1)*0.1)!2}.play
{var pct= Pitch.kr(DelayN.ar(SoundIn.ar, 1, 1)); SinOsc.ar(pct[0].round(100).max(100), 0, pct[1].lag(0.1)*0.1)!2}.play
{var pct= Pitch.kr(DelayN.ar(SoundIn.ar, 1, 1)); Mix(SinOsc.ar(pct[0]*[1, 2, 3, 4], 0, pct[1].lag([0.1, 0.2, 0.3, 0.4])*[0.4, 0.3, 0.2, 0.1]*0.1))!2}.play
{var pct= Pitch.kr(DelayN.ar(SoundIn.ar, 1, 1)); SinOsc.ar(pct[0].lag(0.1).cpsmidi.round.poll.midicps, 0, pct[1].lag(0.1)*0.1)!2}.play
{var src= BrownNoise.ar; [src, src.sqrt, src.pow(2)]}.plot
//scaling
{var src= Amplitude.ar(DelayN.ar(SoundIn.ar, 1, 1), 0.01, 0.1); SinOsc.ar(src.poll.linexp(0, 1, 200, 5000), 0, src*0.1)!2}.play
{var src= Amplitude.ar(DelayN.ar(SoundIn.ar, 1, 1), 0.01, 0.1); SinOsc.ar(src.poll.linexp(0, 1, 5000, 200), 0, src.lag(0.1)*0.1)!2}.play
//downsample / sample&hold
{var src= Amplitude.ar(DelayN.ar(SoundIn.ar, 1, 1), 0.01, 0.1); SinOsc.ar(Latch.ar(src, Impulse.ar(4)).poll.linexp(0, 1, 200, 5000), 0, src.lag(0.1)*0.1)!2}.play
//conditions (one way to do it)
//this example will only play if pitches are between 1500 and 1600hz
{var pct= Pitch.kr(DelayN.ar(SoundIn.ar, 1, 1)); SinOsc.ar(pct[0].poll, 0, ((pct[0]>1500)*(pct[0]<1600)*pct[1]).lag(0.1)*0.1)!2}.play
//--exercise - drive synthesis
//tap mic to play melody
{Pan2.ar(SinOsc.ar(Stepper.ar(Amplitude.ar(SoundIn.ar, 0.01, 0.1)>0.2, 0, 1, 4, 1)*100, 0, 0.1), 1)}.play
//play noise when stable pitch detected
{Pan2.ar(PinkNoise.ar(Pitch.kr(SoundIn.ar, clar:1)[1].lag(0.1).poll*0.2), 1)}.play
//see PracticalSoundAnalysis02examples.html
//--freq-domain, more mapping
FFT Overview
s.freqscope //should open a window (osx: make sure the internal server is running)
/*
//--spectrogram (quark, osx only)
(
w= Window("spectrogram", Rect(10, 10, 800, 600), false).front;
a= Spectrogram(w, Rect(0, 0, 800, 600), 4096, Color.white, Color.black, 25, 12000);
a.start;
a.intensity= 3;
a.rate= 50;
CmdPeriod.doOnce({a.free; if(w.isClosed.not, {w.close})});
)
{DelayN.ar(SinOsc.ar(MouseX.kr(200, 2000)), 1, 1)}.play //loud!
*/
//--basic fft in sc
Server.default= Server.internal;
s.boot
s.freqscope
(
SynthDef(\brick, {
var src, chain, buf;
buf= LocalBuf(2048); //window size - may change
src= WhiteNoise.ar;
chain= FFT(buf, src);
chain= PV_BrickWall(chain, MouseX.kr(-1, 1));
Out.ar(0, IFFT(chain)*0.1!2);
}).add;
)
Synth(\brick)
//loudness
(
SynthDef(\loudness, {
var src, chain, buf;
buf= LocalBuf(2048);
src= SoundIn.ar;
chain= FFT(buf, src);
Loudness.kr(chain, 0.25, 6).poll;
Out.ar(0, DC.ar(0)); //silence
}).add;
)
Synth(\loudness)
//centroid - sort of sound 'brightness' analysis
(
SynthDef(\centroid, {
var src, chain, buf;
buf= LocalBuf(2048);
src= SoundIn.ar;
chain= FFT(buf, src);
SpecCentroid.kr(chain).poll;
Out.ar(0, src);
}).add;
)
Synth(\centroid)
//spectral flatness - sort of timbre analysis (0 is pure, 1 is irregular noisy)
(
SynthDef(\flatness, {
var src, chain, buf;
buf= LocalBuf(2048);
src= SoundIn.ar;
chain= FFT(buf, src);
SpecFlatness.kr(chain).poll;
Out.ar(0, src);
}).add;
)
Synth(\flatness)
FFT Overview //check helpfile
//see also SpecPcile for measuring spectral roll-off
//--fft onset detectors
PV_HainsworthFoote
PV_JensenAndersen
//but Onsets is the recommended one...
(
SynthDef(\onsets, {
var src, chain, buf, trig;
buf= LocalBuf(512); //smaller window for less latency
src= SoundIn.ar;
chain= FFT(buf, src);
trig= Onsets.kr(chain, 0.75); //threshold
src.poll(K2A.ar(trig)); //trigger
}).add;
)
Synth(\onsets) //tap mic
(
SynthDef(\onsets2, {
var src, chain, buf, trig;
buf= LocalBuf(512);
src= SoundIn.ar;
chain= FFT(buf, src);
trig= Onsets.kr(chain, MouseX.kr(0, 1).poll); //mouse x sets threshold
////////////
trig= Trig1.kr(trig, 0.1); //timed gate
Out.ar(1, SinOsc.ar(TExpRand.kr(400, 4000, trig), 0, EnvGen.ar(Env.perc, trig))*0.1);
}).add;
)
Synth(\onsets2)
//--exercise - onset detection
//suggested exercises...
// * write your own program that uses Onsets to start/stop/transform sound in some way
// * code a tap tempo patch using Onsets
// * use Loudness, SpecCentroid, SpecFlatness on mic input to drive synthesis
//don't forget to pre- and post process the sound/data (filter, doubletrig, adaptive threshold etc)