// title: Vocal Clouds (work in progress) // author: emergent // description: // A sample-based granular synthesizer to make a cloud-like texture from short vocal samples, which can then be played with a MIDI controller // // ## ToDo: // // * add MIDI controls for more parameters (e.g. density, detune, pan, grain position) // * add mechanism to choose different vocal samples // * add mechanism to balance amplitude variations brought about by different grain durations and densities // * possibly add more controls for rate manipulation/overtone generation & grain position // * add mechanism to choose between Dust and Impulse as grain triggers (Dust for a slightly more glitchy texture, Impulse for smoother sound) // code: // Vocal clouds - Proof of concept ( // configure server s = Server.local; s.options.sampleRate_(44100); s.options.memSize_(65536 * 4); s.newBusAllocators; // global variables ~out = 0; ~path = PathName(thisProcess.nowExecutingPath).parentPath++"/vocal-samples/"; // expects folder "vocal-samples" with audio samples in the same folder as the project file // make buffers ~makeBuffers = { ~buf = Array.new; PathName(~path).entries.do({ arg path; ~buf = ~buf.add(Buffer.read(s, path.fullPath)); }); }; // make buses ~makeBuses = { ~revBus = Bus.audio(s, 2); // effects bus }; // make groups ~makeNodes = { s.bind({ ~src = Group.new(s); ~efx = Group.after(~src); ~reverb = Synth.new(\reverb, [\in, ~revBus, \out, ~out], ~efx); }); }; // clean up on ServerQuit ~cleanup = { s.freeAll; MIDIdef.freeAll; s.newBusAllocators; ServerBoot.removeAll; ServerTree.removeAll; ServerQuit.removeAll; }; // MIDI stuff! ~bend = 8192; // initializing global variable for pitch bend wheel ~mod = 0; // global variable MIDIIn.connectAll; ~notes = Array.fill(128, {nil}); MIDIdef.noteOn(\on, { arg val, num, chan, src; ~notes[num] = Synth.new(\voxgrains, [ \baserate, (num-60).midiratio, \amp, val.linexp(1,127,0.02,0.3), //velocity detection \gate, 1, \bend, ~bend.linlin(0,16383,-2,2), \basedur, ~mod.linlin(0,127,0.07,2), \detune, 0.025, \dens, 12, \out, ~revBus ], ~src ); }); MIDIdef.noteOff(\off, { arg val, num, chan, src; ~notes[num].set(\gate, 0); ~notes[num] = nil; //probably optional, just to be on the safe side }); MIDIdef.bend(\bendTest, { arg val, chan, src; ~bend = val; ~notes.do{arg synth; synth.set(\bend, val.linlin(0,16383,-2,2))}; }, chan:0); //chan:0 makes the def only listen to events on chan 0 MIDIdef.cc(\modWheelTest, { arg val, chan, num; ~mod = val; ~notes.do{arg synth; synth.set(\basedur, val.linlin(0, 127, 0.07, 2))}; }, 1); // register functions ServerBoot.add(~makeBuses); ServerBoot.add(~makeBuffers); ServerQuit.add(~cleanup); s.waitForBoot({ s.sync; SynthDef.new(\voxgrains, { var sig, env, rate, dur, pan; env = Env.asr(\atk.ir(0.12), 1, \rel.ir(1)).kr(2, \gate.kr(1)); rate = \baserate.kr(1) * LFNoise1.kr(\dens.kr(36) * 1.01).exprange(\baserate.kr(1) * 4).round(\baserate.kr(1)) + LFNoise1.kr(100).bipolar(\detune.kr(0.025)) * \bend.kr(~bend).midiratio; // randomly generated overtones dur = \basedur.kr(0.5) + LFNoise1.ar(50).bipolar(0.02); pan = \basepan.kr(0) + LFNoise1.ar(100).bipolar(0.25); // don't assign values greater than +- 0.7 to basepan! sig = GrainBuf.ar( 2, // 2-channel output Dust.kr(\dens.kr(36)), // using Dust as trigger dur, \buf.ir(~buf[5]), // choose buffer from which grains are taken rate, \pos.kr(0.1), 2, // linear interpolation pan, \grainenv.ir(-1), // using built-in envelope \maxgrains.kr(128) ); sig = sig * env * \amp.kr(0.12); sig = Splay.ar(sig); Out.ar(\out.ir(~out), sig); // possibly add dry/wet control via additional out here }).add; SynthDef.new(\reverb, { var sig; sig = JPverb.ar( In.ar(\in.ir(~revBus), 2), \rtime.kr(4), \damp.kr(0.75), \size.kr(4.5), \earlyDiff.kr(0.8), \modDepth.kr(0.12), \modFreq.kr(2), \low.kr(1), \mid.kr(0.9), \high.kr(0.8) ); sig = sig * \revAmp.kr(0.5); Out.ar(\out.ir(~out), sig); }).add; s.sync; ServerTree.add(~makeNodes); s.freeAll; s.sync; "done".postln; }); ) s.quit; // quit performance