Read a sound file with with TGrains

hi,

I am trying to use TGrains as a granular sound file reader. As a starting point I would like it to be able to reconstruct the original sound through a variable number of grains. At the moment I am struggling a bit with trigger rate:

s.boot;
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

(
Ndef(\grainRead,{

	var grains = 10;
	var trig = Impulse.ar(BufDur.kr(b));
	var position = grains.collect{|i| BufDur.kr(b) / grains * (1 + i) };
	var positionArray = Dseq(0+position, inf);
    var dur = BufDur.kr(b) / grains;
    TGrains.ar(2, trig, b, 1, positionArray, dur, 0, 0.8, 4);
})
)

Ndef(\grainRead).play

Ultimately, I would like to be able to change grains value but keep generating a sound as close to the original as possible

all the best

Marcin

The mathematical conditions for perfect reconstruction of a signal from windowed grains are known as “Constant Overlap-Add.” See Overlap-Add Decomposition and COLA Examples. Essentially, this problem is addressed by ensuring the window and the overlap factor are fixed and satisfy the COLA conditions. This implies that the inter-onset intervals of grains (hop size) and the grain duration (window size) are proportional. If the grain rate is varied over time then the COLA conditions are much harder to satisfy, but the deviations from the COLA conditions will only last as long as they’re being modulated.

Also, Dseq is demand rate and can’t be plugged into anywhere. You need to wrap it in a Demand.kr and time it with trig.

1 Like

i had this patch lying around which recreates the original with overlap set to 8 and higher trigger rates using Playbuf, maybe useful to you:

(
var timingInformation = { |numChannels, trig, grainRate|
	var arrayOfTrigsAndPhases = numChannels.collect{ |i|
		var localTrig = PulseDivider.ar(trig, numChannels, i);
		var hasTriggered = PulseCount.ar(localTrig) > 0;
		var localPhase = Sweep.ar(localTrig, grainRate * hasTriggered);
		[localTrig, localPhase];
	};
	var trigsAndPhasesArray = arrayOfTrigsAndPhases.flop;
	(\trigger : trigsAndPhasesArray[0], \phase: trigsAndPhasesArray[1]);
};

var hanningWindow = { |phase|
	(1 - (phase * 2pi).cos) / 2 * (phase < 1);
};

SynthDef(\granular, { |sndBuf|

	var numChannels = 8;

	var tFreq = \tFreq.kr(10);
	var trig = Impulse.ar(tFreq);
	var grainRate = tFreq / \overlap.kr(1);

	var timings = timingInformation.(numChannels, trig, grainRate);
	var grainWindows = hanningWindow.(timings.phase);

	var pos = Phasor.ar(
		trig: 0,
		rate: \posRate.kr(1) * BufRateScale.kr(sndBuf) * SampleDur.ir / BufDur.kr(sndBuf),
		start: \posLo.kr(0),
		end: \posHi.kr(1)
	);

	var sig = PlayBuf.ar(
		numChannels: 1,
		bufnum: sndBuf,
		rate: \playBackRate.kr(1),
		trigger: timings.trigger,
		startPos: pos * BufFrames.kr(sndBuf),
		loop: 1
	);

	sig = sig * grainWindows;

	sig = Pan2.ar(sig, \pan.kr(0));
	sig = sig.sum;

	sig = sig * \amp.kr(-10.dbamp);

	sig = LeakDC.ar(sig);
	OffsetOut.ar(\out.kr(0), sig);
}).add;
)

b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

(
Synth(\granular, [
	\tFreq, 1000,
	\overlap, 8,
	\sndBuf, b,
	\amp, -25.dbamp
]);
)

(
Synth(\granular, [
	\tFreq, 10,
	\posRate, 1,
	\playBackRate, 1,
	\overlap, 0.5,
	\sndBuf, b,
	\amp, -5.dbamp
]);
)
1 Like

thanks for these resources @nathan. @dietcv this does the job, thanks!!!