Spectral delays

Hi, I’ve been using a great (set of) spectral delays (Soundmagic-spectral) on Reaper - designed by a certain Michael Norris, if anyone knows this. Anyhow, I’d like to use something similar live via SC, but my maths/physics are pretty poor. I wonder if anyone can give me any help, or point me towards a quark, or better still documentation which would help me start building something similar using SC?

Check out PV_BinDelay in sc3-plug-ins.

1 Like

Hi Josh, I had a brief look, but the example just gave me an error in the post window:

ERROR: Variable 'playbuf' not defined.
  in interpreted text
  line 28 char 34:

          in = PlayBuf.ar(1, playbuf, loop: 1);
                                    
          chain = FFT(bufnum, in, 0.25);

I’ll have a proper look later as I probably didn’t read the code correctly. Anyhow, if needed come back with some questions.

Thanks again - joesh

Yeah - that example needs all sorts of updating. I’ll post something soon.

/*
Josh Parmenter

www.realizedsound.net/josh

*/

1 Like

For running the example try the following:

(
Routine.run({
    // use as multislider - del time vals on top, feedback on the bottom
    // max delay time is 1 second, delaytime and fb are initialized to 0.0
    // and are controlled by the GUI. The multi-slider on the top controls
    // each bins delay time, the bottom controls FB.

    var size, awin, bsl, car, dar, fsl, maxdel, synth, cond, playbuf;

    size = 128;
    maxdel = 0.5;
    cond = Condition.new;
    s.sendMsg(\b_alloc, b=s.bufferAllocator.alloc(1), size * 2);
    s.sendMsg(\b_alloc, c=s.bufferAllocator.alloc(1), size);
    s.sendMsg(\b_alloc, d=s.bufferAllocator.alloc(1), size);
	
	playbuf = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

    y = Array.fill(size, {0.0});
    s.sendBundle(0.1, [\b_setn, d, 0, size] ++ y);

    z = Array.fill(size, {arg i; 0.0});
    s.sendBundle(0.1, [\b_setn, c, 0, size] ++ z);
    s.sync(cond);

    synth = SynthDef("help-noopFFT", { arg inbus,out=0,bufnum=0,   dels=0, fb=0;
        var in, chain;
        in = Impulse.ar(0.33);
        in = PlayBuf.ar(1, playbuf, loop: 1);
        chain = FFT(bufnum, in, 0.25);
        chain = PV_BinDelay(chain, maxdel, dels, fb, 0.25);
        Out.ar(out,
            in + IFFT(chain) * 0.0625// inverse FFT
        );
    }).play(s,[\out,0,\bufnum,b,\dels, c, \fb, d, \inbus, s.options.numOutputBusChannels]);
    {
    awin = GUI.window.new("test", Rect(200 , 450, 10 + (size * 1), 10 + (size * 2)));
    awin.view.decorator = FlowLayout(awin.view.bounds);

    bsl = GUI.multiSliderView.new(awin, Rect(0, 0, size * 1, size * 1));
    bsl.action = {arg xb; ("Deltime index: " ++ xb.index ++" value: " ++
            (xb.currentvalue * maxdel)).postln;
        s.sendMsg(\b_set, c, xb.index, xb.currentvalue * maxdel);
        };

    fsl = GUI.multiSliderView.new(awin, Rect(0, 0, size * 1, size * 1));
    fsl.action = {arg xb; ("FB index: " ++ xb.index ++" value: " ++ xb.currentvalue).postln;
        s.sendMsg(\b_set, d, xb.index, xb.currentvalue);
        };

    car = Array.new;
    size.do({arg i;
        car = car.add(0);
    });
    bsl.value_(car);

    dar = Array.new;
    size.do({arg i;
        dar = dar.add(0);
    });
    fsl.value_(car);

    bsl.xOffset_(5);
    bsl.thumbSize_(12.0);
    fsl.xOffset_(5);
    fsl.thumbSize_(12.0);

    // value axis size of each blip in pixels
    bsl.valueThumbSize_(15.0);
    // index axis size of each blip in pixels
    bsl.indexThumbSize_( bsl.bounds.width / car.size );
    bsl.gap = 0;

    // value axis size of each blip in pixels
    fsl.valueThumbSize_(15.0);
    // index axis size of each blip in pixels
    fsl.indexThumbSize_( fsl.bounds.width / dar.size );
    fsl.gap = 0;

    awin.front;

    awin.onClose_({
        synth.free;
        s.sendMsg(\b_free, b);
        s.sendMsg(\b_free, c);
        s.sendMsg(\b_free, d);
        })
    }.defer;
    })
)
1 Like

BTW, the SoundMagic Spectral is based on a software by Trevor Wishart and other called Composers Desktop Project (CDP), which presents many other spectral transformations, but it operates only in non real time contexts.

You can call CDP functions from within SuperCollider using the .runInTerminal method.

1 Like

@josh if you could also make a simpler example it would be really nice as well !

In this UGen, is it possible somehow to operate among different FFT window frames ?

Lots of thanks for all these PV Ugens!

best,
Fellipe
/= |\/| |\/|

Hi Fellipe, thanks for the info (Trevor Wishart’s CDP), I’ll look into that.

1 Like

OK - here is (hopefully) a better more current example. If this helps, please let me know and I’ll update the helpfile:

(
// use as multislider - del time vals on top, feedback on the bottom
// max delay time is 1 second, delaytime and fb are initialized to 0.0
// and are controlled by the GUI. The multi-slider on the top controls
// each bins delay time, the bottom controls FB.

s.boot;
s.doWhenBooted({
	var size, fftSize, awin, delaySilder, fbSlider, maxdel, synth, cond, playbuf;
	var setup, onReadyFunc, fftBuffer, delTimeBuffer, fbAmtBuffer, createGUI, liveInputMix;

	size = 128;
	fftSize = size * 2;
	maxdel = 0.5;
	cond = Condition.new;
	liveInputMix = 0.0; // change to 1 to hear sound from live input, otherwise, an Impulse

	SynthDef(\helpBinDelay, { arg inbus=0, inMix = 0.0, out=0, fftBuf=0, delayBuf=0, fbBuf=0;
		var in, chain;
		in = (Impulse.ar(0.33) * (1.0 - inMix)) + (In.ar(inbus, 1) * inMix);
		chain = FFT(fftBuf, in, 0.25);
		chain = PV_BinDelay(chain, maxdel, delayBuf, fbBuf, 0.25);
		Out.ar(out,
			in + IFFT(chain) * -24.dbamp// inverse FFT
		);
	}).add;

	/* Functions that allocate buffers and call onReadyFunc when done */
	setup = {
		Routine.run({
			"Allocating FFT buffer".postln;
			fftBuffer = Buffer.alloc(s, fftSize, 1);
			"Allocating DelTime buffer".postln;
			delTimeBuffer = Buffer.alloc(s, size, 1);
			"Allocating FB buffer".postln;
			fbAmtBuffer = Buffer.alloc(s, size, 1);
			s.sync(cond);
			onReadyFunc.value()
		});
	};

	createGUI = {

		awin = Window("test", Rect(200 , 450, 10 + (size * 1), 10 + (size * 2)));
		awin.view.decorator = FlowLayout(awin.view.bounds);

		delaySilder = MultiSliderView(awin, Rect(0, 0, size * 1, size * 1));
		delaySilder.action = {arg xb;
			("Deltime index: " ++ xb.index ++" value: " ++ (xb.currentvalue * maxdel)).postln;
			delTimeBuffer.set(xb.index, xb.currentvalue * maxdel)
		};

		fbSlider = MultiSliderView(awin, Rect(0, 0, size * 1, size * 1));
		fbSlider.action = {arg xb;
			("FB index: " ++ xb.index ++" value: " ++ xb.currentvalue).postln;
			fbAmtBuffer.set(xb.index, xb.currentvalue)
		};

		[delaySilder, fbSlider].do({arg thisSliderView;
			var initDataArray;
			initDataArray = Array.fill(size, {0.0});
			thisSliderView.value_(initDataArray);
			thisSliderView.xOffset_(5);
			thisSliderView.thumbSize_(12.0);

			// value axis size of each blip in pixels
			thisSliderView.valueThumbSize_(15.0);
			// index axis size of each blip in pixels
			thisSliderView.indexThumbSize_( thisSliderView.bounds.width / initDataArray.size );
			thisSliderView.gap = 0;
		});

		awin.front;

		awin.onClose_({
			synth.free;
			fftBuffer.free;
			fbAmtBuffer.free;
			delTimeBuffer.free;
		})
	};

	onReadyFunc = {
		createGUI.defer();

		synth = Synth(\helpBinDelay, [
			\inbus, s.options.numOutputBusChannels,
			\inMix, liveInputMix,
			\out, 0,
			\fftBuf, fftBuffer.bufnum,
			\delayBuf, delTimeBuffer.bufnum,
			\fbBuf, fbAmtBuffer.bufnum,
		]);
	};

	setup.value();
})
)
3 Likes

Thanks Josh, I’ll give it a try in the next few days and get back to you.

Joesh

1 Like

Hi Josh, just tried the code (with and without live input) and it works fine.

Thanks - Joesh

cool! thanks. Also - you can do much larger bin sizes - 128 is a nice small test. of course, as that gets higher, the more latency there is in RT!

That’s great Josh, thanks. I’ll give the code a good workout on Wednesday afternoon (my free afternoon from teaching).

Whoa! I would love to see more on calling CDP functions in SC. Is there a library in SC already or should I install CDP and run from supercollider!

@me91 If you are on a Mac or Linux it is easier because the method .runInTerminal works under these operating systems.

For a temporal process you can run something like the following:

(    
"modify radical 2 /Users/yourusername/Desktop/trumpet.wav /Users/yourusername/Desktop/mdfRad.wav 16 0.25".runInTerminal;
)

For a spectral process, first you create the phase vocoder file, then you process it and then you re-synthesize:

 ( // Analysis
"pvoc anal 1 /Users/yourusername/Desktop/trumpet.wav /Users/yourusername/Desktop/trumpet.ana -c1024".runInTerminal;
)

( // Processing
"glisten glisten /Users/yourusername/Desktop/trumpet.ana /Users/yourusername/Desktop/trumpet_glis.ana 4 250 -p3 -d0.7 -v0.25".runInTerminal;
)

( // Re-synthesize
"pvoc synth /Users/yourusername/Desktop/trumpet_glis.ana /Users/yourusername/Desktop/trumpet_glis.wav".runInTerminal;
)

best,
Fellipe
/= |\/| |\/|

1 Like

hey, is it also possible to use it without the GUI and control it via PbindFx with for example Index.ar to acces the buffers? I have made a first try but dont quite understand how the fbSlider.action, the delaySilder.action and Array.fill could be tranferred accordingly. thanks :slight_smile:

(
~size = 128;
~fftSize = 2 * ~size;

~fftBuffer = Buffer.alloc(s, ~fftSize, 1);
~delTimeBuffer = Buffer.alloc(s, ~size, 1);
~fbAmtBuffer = Buffer.alloc(s, ~size, 1);
)

(
SynthDef(\spectralDelay, {
	arg pan=0, amp=0.5, in=0, inMix=0, out=0,
	fftBuf=0, delayBuf=0, fbBuf=0, maxdel=0.5, freq=150, delBufIndex=0, fbIndex=0;

	var gainEnv = \gainEnv.kr(Env.newClear(8).asArray);
	var sig, input, chain;
	//input = In.ar(in, 2) * inMix;
	gainEnv = EnvGen.kr(gainEnv, doneAction:2);
	input = Saw.ar(freq) * gainEnv;

	chain = FFT(fftBuf, input, 0.25);
	chain = PV_BinDelay(chain, maxdel, Index.ar(delayBuf, delBufIndex), Index.ar(fbBuf, fbIndex), 0.25);
	sig = in + IFFT(chain);
	sig = Pan2.ar(sig, pan, amp);
	Out.ar(out, sig);
}).add;
)

(
Pdef(\spectral_delay,
	Pbind(
		\instrument, \spectralDelay,

		\inMix, 1,
		\fftBuf, ~fftBuffer,
		\delayBuf, ~delTimeBuffer,
		\fbBuf, ~fbAmtBuffer,

		\delBufIndex, 0,
		\fbIndex, 0,

		\scale, Scale.minor,
		\root, 1,
		\octave, Prand([[3,4,5,6]], inf),
		\degree, Prand([[0,2,4,7]], inf),

		\atk, 0.01,

		\gainEnv, Pfunc{|e|
			var rel = (1 - e.atk);
			Env.perc(e.atk, rel);
		},
		\out, 0,
		\amp, 0.5,
	),
).play;
)
1 Like