// title: Abusing Convolution2 to make pitch // author: jamshark70 // description: // 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...) // code: // 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; }; }; )