//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)