Skip to content

Instantly share code, notes, and snippets.

@antonhornquist
Last active September 6, 2019 11:16
Show Gist options
  • Save antonhornquist/5b405f53afdc111a0217db6a4de84af4 to your computer and use it in GitHub Desktop.
Save antonhornquist/5b405f53afdc111a0217db6a4de84af4 to your computer and use it in GitHub Desktop.
(
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|
var commands, polls;
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.new;
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;
commands = [
'reverb_mix' -> (
format: "f",
func: { arg msg; effect.set(\mix, msg[1]); }
),
'reverb_room' -> (
format: "f",
func: { arg msg; effect.set(\room, msg[1]); }
),
'reverb_damp' -> (
format: "f",
func: { arg msg; effect.set(\damp, msg[1]); }
),
'read' -> (
format: "is",
func: { arg msg;
readBuf.value(msg[1] - 1, msg[2]);
}
),
'seek' -> (
format: "if",
func: { 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);
});
}
),
'gate' -> (
format: "ii",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\gate, msg[2]);
}
),
'speed' -> (
format: "if",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\speed, msg[2]);
}
),
'jitter' -> (
format: "if",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\jitter, msg[2]);
}
),
'size' -> (
format: "if",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\size, msg[2]);
}
),
'density' -> (
format: "if",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\density, msg[2]);
}
),
'pitch' -> (
format: "if",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\pitch, msg[2]);
}
),
'spread' -> (
format: "if",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\spread, msg[2]);
}
),
'volume' -> (
format: "if",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\gain, msg[2]);
}
),
'envscale' -> (
format: "if",
func: { arg msg;
var voice = msg[1] - 1;
voices[voice].set(\envscale, msg[2]);
}
),
];
polls = [];
nvoices.do({ arg i;
polls = polls.add(
("phase_" ++ (i+1)).asSymbol -> (
func: {
var val = phases[i].getSynchronous;
val
}
)
);
polls = polls.add(
("level_" ++ (i+1)).asSymbol -> (
func: {
var val = levels[i].getSynchronous;
val
}
)
);
});
seek_tasks = Array.fill(nvoices, { arg i;
Routine {}
});
[commands, polls];
};
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