Created
July 30, 2019 22:31
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
( | |
var nvoices = 7; | |
var pg; | |
var effect; | |
var buffers; | |
var voices; | |
var mixBus; | |
var phases; | |
var levels; | |
var seek_tasks; | |
// disk read | |
var readBuf = { arg i, path; | |
if(buffers[i].notNil, { | |
if (File.exists(path), { | |
// TODO: load stereo files and duplicate GrainBuf for stereo granulation | |
var newbuf = Buffer.readChannel(context.server, path, 0, -1, [0], { | |
voices[i].set(\buf, newbuf); | |
buffers[i].free; | |
buffers[i] = newbuf; | |
}); | |
}); | |
}); | |
}; | |
var alloc = { |engine| | |
buffers = Array.fill(nvoices, { arg i; | |
Buffer.alloc( | |
context.server, | |
context.server.sampleRate * 1, | |
); | |
}); | |
SynthDef(\synth, { | |
arg out, phase_out, level_out, buf, | |
gate=0, pos=0, speed=1, jitter=0, | |
size=0.1, density=20, pitch=1, spread=0, gain=1, envscale=1, | |
freeze=0, t_reset_pos=0; | |
var grain_trig; | |
var jitter_sig; | |
var buf_dur; | |
var pan_sig; | |
var buf_pos; | |
var pos_sig; | |
var sig; | |
var env; | |
var level; | |
grain_trig = Impulse.kr(density); | |
buf_dur = BufDur.kr(buf); | |
pan_sig = TRand.kr(trig: grain_trig, | |
lo: spread.neg, | |
hi: spread); | |
jitter_sig = TRand.kr(trig: grain_trig, | |
lo: buf_dur.reciprocal.neg * jitter, | |
hi: buf_dur.reciprocal * jitter); | |
buf_pos = Phasor.kr(trig: t_reset_pos, | |
rate: buf_dur.reciprocal / ControlRate.ir * speed, | |
resetPos: pos); | |
pos_sig = Wrap.kr(Select.kr(freeze, [buf_pos, pos])); | |
sig = GrainBuf.ar(2, grain_trig, size, buf, pitch, pos_sig + jitter_sig, 2, pan_sig); | |
env = EnvGen.kr(Env.asr(1, 1, 1), gate: gate, timeScale: envscale); | |
level = env; | |
Out.ar(out, sig * level * gain); | |
Out.kr(phase_out, pos_sig); | |
// ignore gain for level out | |
Out.kr(level_out, level); | |
}).add; | |
SynthDef(\effect, { | |
arg in, out, mix=0.5, room=0.5, damp=0.5; | |
var sig = In.ar(in, 2); | |
sig = FreeVerb.ar(sig, mix, room, damp); | |
Out.ar(out, sig); | |
}).add; | |
context.server.sync; | |
// mix bus for all synth outputs | |
mixBus = Bus.audio(context.server, 2); | |
effect = Synth.new(\effect, [\in, mixBus.index, \out, context.out_b.index], target: context.xg); | |
phases = Array.fill(nvoices, { arg i; Bus.control(context.server); }); | |
levels = Array.fill(nvoices, { arg i; Bus.control(context.server); }); | |
pg = ParGroup.head(context.xg); | |
voices = Array.fill(nvoices, { arg i; | |
Synth.new(\synth, [ | |
\out, mixBus.index, | |
\phase_out, phases[i].index, | |
\level_out, levels[i].index, | |
\buf, buffers[i], | |
], target: pg); | |
}); | |
context.server.sync; | |
engine.addCommand("reverb_mix", "f", { arg msg; effect.set(\mix, msg[1]); }); | |
engine.addCommand("reverb_room", "f", { arg msg; effect.set(\room, msg[1]); }); | |
engine.addCommand("reverb_damp", "f", { arg msg; effect.set(\damp, msg[1]); }); | |
engine.addCommand("read", "is", { arg msg; | |
readBuf.value(msg[1] - 1, msg[2]); | |
}); | |
engine.addCommand("seek", "if", { arg msg; | |
var voice = msg[1] - 1; | |
var lvl, pos; | |
var seek_rate = 1 / 750; | |
seek_tasks[voice].stop; | |
// TODO: async get | |
lvl = levels[voice].getSynchronous(); | |
if (false, { // disable seeking until fully implemented | |
var step; | |
var target_pos; | |
// TODO: async get | |
pos = phases[voice].getSynchronous(); | |
voices[voice].set(\freeze, 1); | |
target_pos = msg[2]; | |
step = (target_pos - pos) * seek_rate; | |
seek_tasks[voice] = Routine { | |
while({ abs(target_pos - pos) > abs(step) }, { | |
pos = pos + step; | |
voices[voice].set(\pos, pos); | |
seek_rate.wait; | |
}); | |
voices[voice].set(\pos, target_pos); | |
voices[voice].set(\freeze, 0); | |
voices[voice].set(\t_reset_pos, 1); | |
}; | |
seek_tasks[voice].play(); | |
}, { | |
pos = msg[2]; | |
voices[voice].set(\pos, pos); | |
voices[voice].set(\t_reset_pos, 1); | |
voices[voice].set(\freeze, 0); | |
}); | |
}); | |
engine.addCommand("gate", "ii", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\gate, msg[2]); | |
}); | |
engine.addCommand("speed", "if", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\speed, msg[2]); | |
}); | |
engine.addCommand("jitter", "if", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\jitter, msg[2]); | |
}); | |
engine.addCommand("size", "if", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\size, msg[2]); | |
}); | |
engine.addCommand("density", "if", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\density, msg[2]); | |
}); | |
engine.addCommand("pitch", "if", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\pitch, msg[2]); | |
}); | |
engine.addCommand("spread", "if", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\spread, msg[2]); | |
}); | |
engine.addCommand("volume", "if", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\gain, msg[2]); | |
}); | |
engine.addCommand("envscale", "if", { arg msg; | |
var voice = msg[1] - 1; | |
voices[voice].set(\envscale, msg[2]); | |
}); | |
nvoices.do({ arg i; | |
engine.addPoll(("phase_" ++ (i+1)).asSymbol, { | |
var val = phases[i].getSynchronous; | |
val | |
}); | |
engine.addPoll(("level_" ++ (i+1)).asSymbol, { | |
var val = levels[i].getSynchronous; | |
val | |
}); | |
}); | |
seek_tasks = Array.fill(nvoices, { arg i; | |
Routine {} | |
}); | |
}; | |
var free = { | |
voices.do({ arg voice; voice.free; }); | |
phases.do({ arg bus; bus.free; }); | |
levels.do({ arg bus; bus.free; }); | |
buffers.do({ arg b; b.free; }); | |
effect.free; | |
mixBus.free; | |
}; | |
[alloc, free]; | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment