diff --git a/release-packaging/Classes/FluidBufLoudness.sc b/release-packaging/Classes/FluidBufLoudness.sc new file mode 100644 index 0000000..402139e --- /dev/null +++ b/release-packaging/Classes/FluidBufLoudness.sc @@ -0,0 +1,21 @@ +FluidBufLoudness{ + *process { arg server, source, startFrame = 0, numFrames = -1, startChan = 0, numChans = -1, features, kWeighting = 1, truePeak = 1, winSize = 1024, hopSize = 512, action; + + var maxWinSize = winSize.nextPowerOfTwo; + + source = source.asUGenInput; + features = features.asUGenInput; + + source.isNil.if {"FluidBufPitch: Invalid source buffer".throw}; + features.isNil.if {"FluidBufPitch: Invalid features buffer".throw}; + + server = server ? Server.default; + + forkIfNeeded{ + server.sendMsg(\cmd, \BufLoudness, source, startFrame, numFrames, startChan, numChans, features, kWeighting, truePeak, winSize, hopSize, maxWinSize); + server.sync; + features = server.cachedBufferAt(features); features.updateInfo; server.sync; + action.value(features); + }; + } +} diff --git a/release-packaging/Classes/FluidLoudness.sc b/release-packaging/Classes/FluidLoudness.sc new file mode 100644 index 0000000..ef75218 --- /dev/null +++ b/release-packaging/Classes/FluidLoudness.sc @@ -0,0 +1,17 @@ +FluidLoudness : MultiOutUGen { + *kr { arg in = 0, kWeighting = 1, truePeak = 1, winSize = 1024, hopSize = 512, maxWinSize = 16384; + ^this.multiNew('control', in.asAudioRateInput(this), kWeighting, truePeak, winSize, hopSize, maxWinSize); + } + + init {arg ...theInputs; + inputs = theInputs; + ^this.initOutputs(2,rate); + } + + checkInputs { + if(inputs.at(5).rate != 'scalar') { + ^(": maxWinSize cannot be modulated."); + }; + ^this.checkValidInputs; + } +} diff --git a/release-packaging/HelpSource/Classes/FluidBufLoudness.schelp b/release-packaging/HelpSource/Classes/FluidBufLoudness.schelp new file mode 100644 index 0000000..59c3c4b --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidBufLoudness.schelp @@ -0,0 +1,116 @@ +TITLE:: FluidBufLoudness +SUMMARY:: A Selection of Pitch Descriptors on a Buffer +CATEGORIES:: Libraries>FluidDecomposition +RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/SpecCentroid, Classes/SpecFlatness, Classes/SpecCentroid, Classes/SpecPcile + + +DESCRIPTION:: +This class implements three popular pitch descriptors, computed as frequency and the confidence in its value. It is part of the Fluid Decomposition Toolkit of the FluCoMa project.FOOTNOTE:: This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899).:: + +The process will return a multichannel buffer with two channels per input channel, one for pitch and one for the pitch tracking confidence. Each sample represents a value, which is every hopSize. Its sampling rate is sourceSR / hopSize. + +CLASSMETHODS:: + +METHOD:: process + This is the method that calls for the pitch descriptor to be calculated on a given source buffer. + +ARGUMENT:: server + The server on which the buffers to be processed are allocated. + +ARGUMENT:: source + The index of the buffer to use as the source material to be pitch-tracked. The different channels of multichannel buffers will be processing sequentially. + +ARGUMENT:: startFrame + Where in the srcBuf should the process start, in sample. + +ARGUMENT:: numFrames + How many frames should be processed. + +ARGUMENT:: startChan + For multichannel srcBuf, which channel should be processed first. + +ARGUMENT:: numChans + For multichannel srcBuf, how many channel should be processed. + +ARGUMENT:: features + The destination buffer for the pitch descriptors. + +ARGUMENT:: kWeighting +(describe argument here) + +ARGUMENT:: truePeak +(describe argument here) + +ARGUMENT:: winSize +(describe argument here) + +ARGUMENT:: hopSize +(describe argument here) + +ARGUMENT:: action + A Function to be evaluated once the offline process has finished and all Buffer's instance variables have been updated on the client side. The function will be passed [features] as an argument. + +RETURNS:: + Nothing, as the destination buffer is declared in the function call. + +EXAMPLES:: + +code:: +// create a buffer with a short clicking sinusoidal burst (220Hz) starting at frame 8192 for 1024 frames +( +b = Buffer.sendCollection(s, (Array.fill(8192,{0}) ++ (Signal.sineFill(1203,[0,0,0,0,0,1],[0,0,0,0,0,0.5pi]).takeThese({|x,i|i>1023})) ++ Array.fill(8192,{0}))); +c = Buffer.new(s); +) + +// listen to the source and look at the buffer +b.play; b.plot; + d = Buffer.alloc(s,44100) +// run the process with basic parameters +( +Routine{ + t = Main.elapsedTime; + FluidBufLoudness.process(s, d, features: c); + (Main.elapsedTime - t).postln; +}.play +) + +// look at the analysis +c.plot(minval:-130, maxval:6) +// plot with a different range to appreciate the confidence: +c.plot + +// The values are interleaved [pitch,confidence] in the buffer as they are on 2 channels: to get to the right frame, divide the SR of the input by the hopesize, then multiply by 2 because of the channel interleaving +// here we are querying from one frame before (the signal starts at 8192, which is frame 16 (8192/512), therefore starting the query at frame 15, which is index 30. +c.getn(30,10,{|x|x.postln}) + +// observe that the first frame is silent, as expected. The next frame's confidence is low-ish, because the window is half full (window of 1024, overlap of 512). Then a full window is analysed, with strong confidence. Then another half full window, then silence, as expected. +:: + +STRONG::A stereo buffer example.:: +CODE:: + +// load two very different files +( +b = Buffer.read(s,File.realpath(FluidBufLoudness.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav"); +c = Buffer.read(s,File.realpath(FluidBufLoudness.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav"); +) + +// composite one on left one on right as test signals +FluidBufCompose.process(s, c, numFrames:b.numFrames, startFrame:555000,destStartChan:1, destination:b) +b.play + +// create a buffer as destinations +c = Buffer.new(s); + +//run the process on them +( +Routine{ + t = Main.elapsedTime; + FluidBufLoudness.process(s, b, features: c, winSize: 17640, hopSize:4410); + (Main.elapsedTime - t).postln; +}.play +) + +// look at the buffer: [pitch,confidence] for left then [pitch,confidence] for right +c.plot(minval:-130, maxval:6) +:: \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidLoudness.schelp b/release-packaging/HelpSource/Classes/FluidLoudness.schelp new file mode 100644 index 0000000..b1d3131 --- /dev/null +++ b/release-packaging/HelpSource/Classes/FluidLoudness.schelp @@ -0,0 +1,100 @@ +TITLE:: FluidLoudness +SUMMARY:: A Selection of Pitch Descriptors in Real-Time +CATEGORIES:: Libraries>FluidDecomposition +RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/Pitch + +DESCRIPTION:: +This class implements three popular pitch descriptors, computed as frequency and the confidence in its value. It is part of the Fluid Decomposition Toolkit of the FluCoMa project.FOOTNOTE:: This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899).:: + + The process will return a multichannel control steam with [pitch, confidence] values, which will be repeated if no change happens within the algorithm, i.e. when the hopSize is larger than the server's kr period. + +CLASSMETHODS:: + +METHOD:: kr + The audio rate in, control rate out version of the object. + +ARGUMENT:: in + The audio to be processed. + +ARGUMENT:: kWeighting +(describe argument here) + +ARGUMENT:: truePeak +(describe argument here) + +ARGUMENT:: winSize +(describe argument here) + +ARGUMENT:: hopSize +(describe argument here) + +ARGUMENT:: maxWinSize +(describe argument here) + +RETURNS:: + A 2-channel KR signal with the [pitch, confidence] descriptors. The latency is winSize. + + +EXAMPLES:: + + +code:: +//create a monitoring bus for the descriptors +b = Bus.new(\control,0,2); + +//create a monitoring window for the values +( +w = Window("Loudness Monitor", Rect(10, 10, 220, 65)).front; + +c = Array.fill(2, {arg i; StaticText(w, Rect(10, i * 25 + 10, 135, 20)).background_(Color.grey(0.7)).align_(\right)}); +c[0].string = ("Loudness: "); +c[1].string = ("Peak: "); + +a = Array.fill(2, {arg i; + StaticText(w, Rect(150, i * 25 + 10, 60, 20)).background_(Color.grey(0.7)).align_(\center); +}); +) + +//routine to update the parameters +( +r = Routine { + { + + b.get({ arg val; + { + if(w.isClosed.not) { + val.do({arg item,index; + a[index].string = item.round(0.01)}) + } + }.defer + }); + + 0.1.wait; + }.loop + +}.play +) + +//test signals, all in one synth +( +x = { + arg freq=220, type = 0, noise = 0; + var source = PinkNoise.ar(noise) + Select.ar(type,[SinOsc.ar(freq,mul:0.1), VarSaw.ar(freq,mul:0.1), Saw.ar(freq,0.1), Pulse.ar(freq,mul:0.1), Mix.new(Array.fill(8, {arg i; SinOsc.ar(LFNoise1.kr(0.1.rand,10,220*(i+1)),mul:(i+1).reciprocal * 0.1)}))]); + Out.kr(b, FluidLoudness.kr(source,winSize:17640,hopSize:4410,maxWinSize:17640)); + source.dup; +}.play; +) + +// the built-in is slightly better on pure sinewaves +x.set(\freq, 440) + +// adding harmonics, by changing to triangle (1), saw (2) or square (3) shows that spectral algo are more resilient when signal are richer +x.set(\type, 1) +x.set(\type, 2) +x.set(\type, 3) + +// adding noise shows the comparative sturdiness of the spectral pitch tracker +x.set(\noise, 0.05) + +//if latency is no issue, getting a higher winSize will stabilise the algorithm even more +::