«Abusing Convolution2 to make pitch» by jamshark70

on 31 Mar'12 20:36 in convolution comb granular theory

Inspired by Formlet, which does something like formant synthesis by outputting short sinusoidal grains in response to impulses, I thought, what if we replace the sine grains with an arbitrary impulse response?

You could do that with TGrains, but as the pitch goes up, so does the number of overlapping grains and CPU use along with it. But, convolution of a chain of impulses should be the same as granular synthesis, if the grain contents are used as the convolution kernel (impulse response).

The first example does this with a buffer containing BrownNoise (shaped by a Hanning envelope). The second example adds timbre control by crossfading between three different Convolution2 results. To avoid clicks in the output when crossing buffer boundaries, I had to ensure that even-numbered buffers always go into XFade2's leftmost input and odd ones into the second input.

(Note, the second example could be made more efficient/scalable by using just two Convolution2 units, and providing a trigger to the third input whenever its calculated buffer number changes. Exercise for the reader...)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// fixed (unchanging) impulse response
// cmd-. to stop

(
s.waitForBoot {
	var c = Condition.new, cleanfunc = { CmdPeriod.remove(cleanfunc); b.free };
	fork {
		b = Buffer.alloc(s, 2048, 1);
		CmdPeriod.add(cleanfunc);
		s.sync;
		a = {
			RecordBuf.ar(BrownNoise.ar * EnvGen.ar(Env(#[0, 1, 0], #[0.5, 0.5], \sin), timeScale: b.duration, doneAction: 2), b, loop: 0);
			0
		}.play;
		OSCpathResponder(s.addr, ['/n_end', a.nodeID], { |time, resp, msg|
			resp.remove;
			c.unhang;
		}).add;
		c.hang;
		a = {
			var freq = MouseX.kr(150, 450, warp: 1, lag: 0.1);
			LeakDC.ar(Convolution2.ar(Impulse.ar(freq), b, framesize: b.numFrames) * -35.dbamp) ! 2;
		}.play;
	};
};
)



// multiple impulse responses for timbre control
(
s.waitForBoot {
	var c = Condition.new, cleanfunc = { CmdPeriod.remove(cleanfunc); b.free };
	fork {
		b = Buffer.allocConsecutive(3, s, 2048, 1);
		CmdPeriod.add(cleanfunc);
		s.sync;
		a = {
			var sig = [BrownNoise.ar, PinkNoise.ar, WhiteNoise.ar];
			sig.do { |chan, i|
				RecordBuf.ar(chan * EnvGen.ar(Env(#[0, 1, 0], #[0.5, 0.5], \sin), timeScale: b[i].duration, doneAction: 2), b[i], loop: 0);
				0
			};
		}.play;
		OSCpathResponder(s.addr, ['/n_end', a.nodeID], { |time, resp, msg|
			resp.remove;
			c.unhang;
		}).add;
		c.hang;
		a = {
			var freq = MouseX.kr(150, 450, warp: 1, lag: 0.1),
				index = MouseY.kr(0, 1.99, lag: 0.1),
				iWhole = index.trunc(2),
				iFrac = index - iWhole,
				iAdjust = iFrac >= 1.0,
				trig = Impulse.ar(freq),
				convolvers = b.collect { |buf, i|
					LeakDC.ar(Convolution2.ar(trig, buf, framesize: buf.numFrames))
				};
			(
				XFade2.ar(
					Select.ar(iWhole + (2 * iAdjust), convolvers),
					Select.ar(iWhole + 1, convolvers),
					iFrac.fold(0, 1) * 2 - 1
				) * -30.dbamp
			) ! 2;
		}.play;
	};
};
)
raw 1859 chars (focus & ctrl+a+c to copy)
reception
comments