PV Ugens and DXEnvFan (miSCellaneous)

Hello again,

probably a combination of two different things: I’ve tried Ex 1f now with PV_BufRd / PV_BinBufRd and didn’t encounter any server crashes. Independent from this there’s a method missing preventing PV_BinGap and PV_BinRange from working within a chain, but this can easily be added (for variants with PV_BinGap see second block of code, replace in miSCellaneous_lib’s file PV_UGens.sc and recompile before playing examples 1 and 2).

Regarding the server crashes, check for following possible mistakes:

.) it needs an Array of different LocalBufs, so

{ LocalBuf(~windowSize) } ! ~maxOverlap

and not

LocalBuf(~windowSize) ! ~maxOverlap

.) In contrast to PV_BufRd PV_BinBufRd needs an FFT before

.) some type of multichannel mismatch (e.g. using a nested array where a flat one were needed)

For the sake of simplicity the examples below use 12 analysis buffers derived from a single buffer played at different rates.

// analysis adapted from PV_BufRd help file
// prepare 12 analysis buffers from one sound file played at 12 different rates


s.boot

(
~path = Platform.resourceDir +/+ "sounds/a11wlk01.wav";
~windowSize = 1024;
~hopSize = 0.25;

~soundfile = SoundFile.new(~path);
~soundfile.openRead;
~soundfile.close;

~rates = (1..12) * 0.1 + 0.4;

// need FFT buffers from different lengths
~recBufs = ~rates.collect { |rate, i|
	Buffer.alloc(s, (~soundfile.duration / rate).calcPVRecSize(~windowSize, ~hopSize));
};

// one sound buffer is enough in this case
~soundBuf = Buffer.read(s, ~path);

// Hann window
~winType = 1;
)

// analysis SynthDef (in addition uses rate)
(
SynthDef(\pvrec_2, { arg recBuf, soundBufnum, rate = 1;
    var in, chain, bufnum;
    bufnum = LocalBuf.new(~windowSize);
    Line.kr(1, 1, BufDur.kr(soundBufnum) / rate, doneAction: 2);
    in = PlayBuf.ar(1, soundBufnum, rate * BufRateScale.kr(soundBufnum), loop: 0);
    chain = FFT(bufnum, in, ~hopSize, ~winType);
    chain = PV_RecordBuf(chain, recBuf, 0, 1, 0, ~hopSize, ~winType);
    }).add;
)


// make analysis of same buffer played back at different speeds

(
~rates.do { |rate, i| Synth(\pvrec_2, [\recBuf, ~recBufs[i], \soundBufnum, ~soundBuf, \rate, rate]) }
)

// playback

(
SynthDef(\pvplay, { arg out = 0, recBuf = 1;
    var in, chain, bufnum;
    bufnum = LocalBuf.new(1024);
    chain = PV_BufRd(bufnum, recBuf, MouseX.kr(0.0, 1.0));
    Out.ar(out, IFFT(chain, 1).dup);
    }).add;
)

// check with a specific rate

b = Synth("pvplay", [\out, 0, \recBuf, ~recBufs[10]]);

b.free;


// Ex. 1, Granulation with PV_BufRd

// check also with PV_BinGap uncommented
// therefore it needs adapted class definition

(
~maxOverlap = ~rates.size;
~audioBus = Bus.audio(s, ~maxOverlap);

// overlap only settable in SC versions >= 3.9

SynthDef(\gran_1f_a, { |out = 0, posLo = 0.1, posHi = 0.9,
	posRateE = 0, posRateM = 1, overlap = 2, trigRate = 1,
	panMax = 0.8, loBin = 10, hiBin = 10, amp = 1|
	var sig, bpFreq, dUgen, bufDur, pos, posRate, playbuf, env, maxOverlap = ~maxOverlap;
	var chains, fftBufs = { LocalBuf(~windowSize) } ! ~maxOverlap;

	posRate = 10 ** posRateE * posRateM;

	// phasor bounds must be between 0 and 1
	pos = Phasor.ar(0, posRate * SampleDur.ir, posLo, posHi);

	// multichannel trigger
	env = DXEnvFan.ar(
		Dseq((0..maxOverlap-1), inf),
		trigRate.reciprocal,
		size: maxOverlap,
		maxWidth: maxOverlap,
		width: (Main.versionAtLeast(3, 9)).if { overlap }{ 2 },
		// option to avoid unwanted triggers
		zeroThr: 0.002,
		// take equalPower = 0 for non-squared sine envelopes
		// more efficient with helper bus
		equalPower: 0,
		bus: ~audioBus
	);


	chains = PV_BufRd(fftBufs, ~recBufs, pos);

	// check with PV_BinGap
	// chains = PV_BinGap(chains, loBin, hiBin);

	playbuf = IFFT(chains, ~winType);

	// generate grains by multiplying with envelope
	sig = playbuf * env;

	// generate array of 12 stereo signals
	sig = Pan2.ar(sig, Demand.ar(env, 0, Dseq([-1, 1], inf) * panMax));

	// mix to out
	Out.ar(0, Mix(sig) * amp)
}, metadata: (
	specs: (
		posLo: [0.01, 0.99, \lin, 0.01, 0],
		posHi: [0.01, 0.99, \lin, 0.01, 0.5],
		posRateE: [-3, 4, \lin, 1, -1],
		posRateM: [0.1, 10, \exp, 0.01, 1.35],
		trigRate: [1, 200, \lin, 0.01, 90],
		overlap: [0.2, 12, \lin, 0.01, 7],
		panMax: [0.0, 1, \lin, 0.005, 0.75],
		loBin: [0, 200, \lin, 1, 5],
		hiBin: [0, 200, \lin, 1, 12],
		amp: [0.0, 3, \lin, 0.005, 1]
	)
)).add;
)

(
\gran_1f_a.sVarGui.gui(
	tryColumnNum: 1,
	synthColorGroups: (0..9).clumps([4,2,1,2,1])
)
)


// Ex. 2, Granulation with PV_BinBufRd

// check also with PV_BinGap uncommented
// therefore it needs adapted class definition


(
~maxOverlap = ~rates.size;
~audioBus = Bus.audio(s, ~maxOverlap);

SynthDef(\gran_1f_b, { |out = 0, posLo = 0.1, posHi = 0.9,
	posRateE = 0, posRateM = 1, overlap = 2, trigRate = 1,
	panMax = 0.8, loBin = 10, hiBin = 10, clear = 0.0, amp = 1|
	var sig, bpFreq, dUgen, bufDur, pos, posRate, playbuf, env, maxOverlap = ~maxOverlap;
	var chains, fftBufs = { LocalBuf(~windowSize) } ! ~maxOverlap;

	posRate = 10 ** posRateE * posRateM;

	// phasor bounds must be between 0 and 1
	pos = Phasor.ar(0, posRate * SampleDur.ir, posLo, posHi);

	// multichannel trigger
	env = DXEnvFan.ar(
		Dseq((0..maxOverlap-1), inf),
		trigRate.reciprocal,
		size: maxOverlap,
		maxWidth: maxOverlap,
		width: (Main.versionAtLeast(3, 9)).if { overlap }{ 2 },
		// option to avoid unwanted triggers
		zeroThr: 0.002,
		// take equalPower = 0 for non-squared sine envelopes
		// more efficient with helper bus
		equalPower: 0,
		bus: ~audioBus
	);

	// need FFT before PV_BinBufRd !
	chains = FFT(fftBufs, PlayBuf.ar(1, ~soundBuf, ~rates, loop: 1));
	chains = PV_BinBufRd(chains, ~recBufs, pos, 0, 2, 10, clear);

	// check with PV_BinGap
	// chains = PV_BinGap(chains, loBin, hiBin);

	playbuf = IFFT(chains, ~winType);

	// generate grains by multiplying with envelope
	sig = playbuf * env;

	// generate array of ~maxOverlap stereo signals
	sig = Pan2.ar(sig, Demand.ar(env, 0, Dseq([-1, 1], inf) * panMax));

	// mix to out
	Out.ar(0, Mix(sig) * amp)
}, metadata: (
	specs: (
		posLo: [0.01, 0.99, \lin, 0.01, 0],
		posHi: [0.01, 0.99, \lin, 0.01, 0.5],
		posRateE: [-3, 4, \lin, 1, -1],
		posRateM: [0.1, 10, \exp, 0.01, 1.35],
		trigRate: [1, 200, \lin, 0.01, 90],
		overlap: [0.2, 12, \lin, 0.01, 7],
		panMax: [0.0, 1, \lin, 0.005, 0.75],
		clear: [0, 1, \lin, 1, 0],
		loBin: [0, 200, \lin, 1, 5],
		hiBin: [0, 200, \lin, 1, 12],
		amp: [0.0, 3, \lin, 0.005, 1]
	)
)).add;
)

(
\gran_1f_b.sVarGui.gui(
	tryColumnNum: 1,
	synthColorGroups: (0..10).clumps([4,2,1,1,2,1])
)
)

s.freqscope

Here the modified code for PV_UGens.sc within miSCellaneous, replace and recompile

PV_BinRange : PV_ChainUGen {

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

	*new1 { |rate, buffer, loBin, hiBin|
		var wipe, bufSize, chain_clipped;

		bufSize = buffer.miSC_getFFTbufSize;
		chain_clipped = LocalBuf(bufSize);
		chain_clipped = PV_Copy(buffer, chain_clipped);

		wipe = loBin * 2 / bufSize;
		chain_clipped = PV_BrickWall(chain_clipped, wipe);

		wipe = hiBin * 2 / bufSize - 1;
		chain_clipped = PV_BrickWall(chain_clipped, wipe);

		^chain_clipped
	}
}

PV_BinGap : PV_ChainUGen {

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

	*new1 { |rate, buffer, loBin, hiBin|
		var wipe, bufSize, chain_gap_1, chain_gap_2;

		bufSize = buffer.miSC_getFFTbufSize;
		chain_gap_1 = LocalBuf(bufSize);
		chain_gap_2 = LocalBuf(bufSize);

		chain_gap_1 = PV_Copy(buffer, chain_gap_1);
		chain_gap_2 = PV_Copy(buffer, chain_gap_2);

		wipe = loBin - 1 * 2 / bufSize - 1;
		chain_gap_1 = PV_BrickWall(chain_gap_1, wipe);

		wipe = hiBin + 1 * 2 / bufSize;
		chain_gap_2 = PV_BrickWall(chain_gap_2, wipe);

		^PV_Add(chain_gap_1, chain_gap_2);
	}
}


+ FFT {
	miSC_getFFTbufSize {
		^this.fftSize
	}
}

+ PV_ChainUGen {
	miSC_getFFTbufSize {
		^this.inputs[0].miSC_getFFTbufSize
	}
}

+ LocalBuf {
	miSC_getFFTbufSize {
		^this.inputs[1]
	}
}
1 Like