32 bands deconstruction / FFT noise reconstruction

Hey,

Could someone get me on tracks there, I am afraid it’s well beyond my FFT - language side capacities:

Mono wave file >>> 32 bands FILTERBANK split >>> FFT spectral analysis of each band >>> extract the noise (inharmonic, unstable) component of each band >>> Mix the 32 extracted noises together (with an independant level and pan control for each noise) >>> Output a stereo signal (or file)

Looking for the richest sound quality, what would be the most relavant: real-time or non real-time way ?

Thanks a lot

1 Like

Hi,

I’d go server-side and use PV ugens. For splitting with filters you can use the BandSplitter Quark. But you can also split with FFT. I’m following a FFT band EQ example I’ve suggested here:

https://www.listarc.bham.ac.uk/lists/sc-users/msg66848.html

( https://www.listarc.bham.ac.uk/lists/sc-users-old/msg66848.html )

The new class is very similar, but, in contrast to PV_BinPeakEQ, it only returns the signal below a threshold from the regarded band. It uses the same trick with Impulse.ar, which is described in the old thread.

PV_BinMagBelow : PV_ChainUGen {

	*new { |buffer, loBin, hiBin, threshold|
		^this.multiNew(\control, buffer, loBin, hiBin, threshold)
	}

	*new1 { |rate, buffer, loBin, hiBin, threshold|
		var bufSize, wipe_1, wipe_2, chain_band, chain_lo, chain_hi, chain_mul, mul, size;

		bufSize = buffer.fftSize;
		size = buffer.asArray.size;

		chain_band = size.collect { LocalBuf(bufSize) };
		chain_lo = size.collect { LocalBuf(bufSize) };
		chain_hi = size.collect { LocalBuf(bufSize) };
		mul = Impulse.ar(SampleRate.ir * 2 / bufSize);
		chain_mul = FFT(LocalBuf(bufSize), mul);

		chain_band = PV_Copy(buffer, chain_band);
		chain_lo = PV_Copy(buffer, chain_lo);
		chain_hi = PV_Copy(buffer, chain_hi);

		wipe_1 = loBin * 2 / bufSize;
		chain_band = PV_BrickWall(chain_band, wipe_1);
		chain_lo = PV_BrickWall(chain_lo, wipe_1 - 1);

		wipe_2 = hiBin * 2 / bufSize;
		chain_band = PV_BrickWall(chain_band, wipe_2 - 1);
		chain_hi = PV_BrickWall(chain_hi, wipe_2);

		chain_band = PV_MagBelow(chain_band, threshold);
		^chain_band[0]
	}
}

Then, after recompile

(
SynthDef(\magBelow_5_band, { |out = 0, in, amp = 1, mix = 1|
	// use NamedControl syntax here, more convenient with larger array args
	var loFreqs = \loFreqs.kr([0, 1000, 2000, 5000, 10000]);
	var hiFreqs = \hiFreqs.kr([1000, 2000, 5000, 10000, 20000]);
	var thrs = \thrs.kr(100 ! 5);
	var pans = \pans.kr(0 ! 5);
	var amps = \amps.kr(1 ! 5);

	var inSig = In.ar(in, 1);
	var loBins, hiBins, binRange;
	var bufSize = 1024;
	var chain = FFT(LocalBuf(bufSize), inSig), multiChain, multi, stereo;
	var chains = { LocalBuf(bufSize) } ! 5;

	binRange = s.sampleRate / bufSize;
	loBins = (loFreqs / binRange).round;
	hiBins = (hiFreqs / binRange).round;

	chains = chains.collect { |x| PV_Copy(chain, x) };
	multiChain = 5.collect { |i| PV_BinMagBelow(chains[i], loBins[i], hiBins[i], thrs[i]) };

	multi = IFFT(multiChain);
	stereo = Mix(multi.collect { |x, i| Pan2.ar(x, pans[i], amps[i]) });

	XOut.ar(out, mix, stereo * amp * EnvGate());
}).add;
)


// start fx silently
y = Synth(\magBelow_5_band)

// start mono source, mix of harmonic sound + noise
x = { Saw.ar(50, 0.1) + WhiteNoise.ar(0.03) }.play


s.freqscope

// for the threshold argument you'd have to find out a scaling
y.set(\thrs, 1!5);

// different thresholds
y.set(\thrs, [5, 0, 0.2, 10, 0]);

// weight the remaining bands
y.set(\amps, ([0.2, 0, 5, 0.1, 1]) );

// pan
y.set(\pans, ([-1, 0, 1, 0, 0]) );

// general amp
y.set(\amp, 0.5);



x.release

y.release

Thinking about it, it would be worth generalizing such band-relative operations.

BTW I’m not sure if you get much benefit with 32 bands (though, easy to try, just exchange the number 5 and the arrays of that size - avoid empty bands in the low). But it might make sense having a look at the Bark scale ( https://en.wikipedia.org/wiki/Bark_scale ). Last term a student of mine had the idea to FFT-split according to this scale and distribute to the 24 speakers of our concert room. Due to the cancelling of all concerts the project had to be done in a binaural format, but this worked quite well.

Regards

Daniel

2 Likes

If you are looking for something that can split the spectrum into noise/harmonic (stochastic/sinusoidal) content, a more specific solution can be using Spectral Modeling Synthesis (SMS), there is a real time implementation on SuperCollider that works quite well:

https://doc.sccode.org/Classes/SMS.html

If you want to dig deep into this, there is this course by Xavier Serra (who invented the SMS) which is free and super interesting:

3 Likes

… oops, this is unnecessary here because PV_MagBelow takes the threshold as a number. In the PV_BinPeakEQ example it’s about multiplying with a constant via PV_MagMul which requires a second buffer.

For the noise extraction, wouldn’t PV_NoiseSynthF be the thing? Surprised it hasn’t come up in this thread yet.

hjh

Thanks all of you, Daniel, right, with 8 bands it was totally enough, and the BandSplitter + PV_NoiseSynthF did the trick