Saving state after latest Lyon Algorave
This is the current state of the system after the 27/04 algorave. NOTE: this is my personal live coding system, it is not fine-tuned for general usage. You might have to update paths and various parts of the code to get it to run on your system.
This commit is contained in:
@ -20,10 +20,6 @@
|
|||||||
],
|
],
|
||||||
url: "https://raphaelforment.fr",
|
url: "https://raphaelforment.fr",
|
||||||
isCompatible: {Main.versionAtLeast(3, 1)},
|
isCompatible: {Main.versionAtLeast(3, 1)},
|
||||||
preInstall: {|data|
|
|
||||||
File.mkdir("~/.config/livecoding/samples/");
|
|
||||||
"/!\\ BuboQuark: Creating folder at ~./config/livecoding/samples/".postln;
|
|
||||||
},
|
|
||||||
postUninstall: {
|
postUninstall: {
|
||||||
"/!\\ BuboQuark: Samples at '~/.config/livecoding/samples/' must be deleted manually".warn;
|
"/!\\ BuboQuark: Samples at '~/.config/livecoding/samples/' must be deleted manually".warn;
|
||||||
},
|
},
|
||||||
|
|||||||
BIN
Classes/.DS_Store
vendored
Normal file
BIN
Classes/.DS_Store
vendored
Normal file
Binary file not shown.
332
Classes/Bank.sc
332
Classes/Bank.sc
@ -1,335 +1,3 @@
|
|||||||
/*
|
|
||||||
* This file is taken from: https://gist.github.com/scztt/73a2ae402d9765294ae8f72979d1720e
|
|
||||||
* I have added a method to list the samples in the bank.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Bank : Singleton {
|
|
||||||
// classvar <>root, <>extensions, <>lazyLoading=true;
|
|
||||||
// var <paths, buffers, <channels, <foundRoot, <foundRootModTime, markersCache, atCache;
|
|
||||||
//
|
|
||||||
// *initClass {
|
|
||||||
// root = "/Users/bubo/.config/livecoding/samples".standardizePath;
|
|
||||||
// extensions = ["wav", "aiff", "aif", "flac", "mp3"];
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// *list {
|
|
||||||
// PathName(Bank.root).entries.do({
|
|
||||||
// arg item; item.folderName.postln;
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// *new {
|
|
||||||
// |path, channels|
|
|
||||||
// ^super.new(path, channels);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// printOn {
|
|
||||||
// |stream|
|
|
||||||
// super.printOn(stream);
|
|
||||||
// stream << "[%]".format(paths.size)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// init {
|
|
||||||
// buffers = [];
|
|
||||||
// ServerQuit.add(this);
|
|
||||||
// ServerBoot.add(this);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// lazyLoading_{
|
|
||||||
//
|
|
||||||
// |lazy|
|
|
||||||
//
|
|
||||||
// if (lazyLoading != lazy) {
|
|
||||||
// lazyLoading = lazy;
|
|
||||||
// this.prUpdateBuffers();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// buffers {
|
|
||||||
// ^paths.size.collect {
|
|
||||||
// |i|
|
|
||||||
// this.bufferAt(i)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// gui {
|
|
||||||
// var view, button, name;
|
|
||||||
//
|
|
||||||
// this.lazyLoading = false;
|
|
||||||
//
|
|
||||||
// view = View().layout_(GridLayout.rows());
|
|
||||||
// paths.do {
|
|
||||||
// |path, i|
|
|
||||||
// name = PathName(path).fileNameWithoutExtension;
|
|
||||||
// view.layout.add(
|
|
||||||
// DragSource()
|
|
||||||
// .object_("%(%)[%]".format(
|
|
||||||
// this.class.name,
|
|
||||||
// channels !? {
|
|
||||||
// "'%', %".format(this.name, channels)
|
|
||||||
// } ?? {
|
|
||||||
// "'%'".format(this.name)
|
|
||||||
// },
|
|
||||||
// name.quote
|
|
||||||
// ))
|
|
||||||
// .string_(name)
|
|
||||||
// .canFocus_(true)
|
|
||||||
// .font_(Font("M+ 2c", 10, false))
|
|
||||||
// .minWidth_(100)
|
|
||||||
// .mouseDownAction_({ |v| if (v.focus) { this.bufferAt(i).play } })
|
|
||||||
// .keyDownAction_({ this.bufferAt(i).play })
|
|
||||||
// .focusGainedAction_({ this.bufferAt(i).play }),
|
|
||||||
// (i / 4).floor,
|
|
||||||
// i % 4
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
// ScrollView(bounds:500@600).canvas_(view).front;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// set {
|
|
||||||
// |inChannels|
|
|
||||||
// var currentRoot, currentExtensions, foundPaths=[], attempts = List();
|
|
||||||
//
|
|
||||||
// if (channels != inChannels) {
|
|
||||||
// channels = inChannels;
|
|
||||||
// this.clear();
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (foundRootModTime.notNil) {
|
|
||||||
// if (File.mtime(foundRoot) == foundRootModTime) {
|
|
||||||
// ^this; // no changes, so early return!
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// currentExtensions = this.class.extensions;
|
|
||||||
// currentRoot = thisProcess.nowExecutingPath;
|
|
||||||
//
|
|
||||||
// if (currentRoot.notNil) {
|
|
||||||
// currentRoot = PathName(currentRoot).parentPath;
|
|
||||||
// foundPaths = Require.resolvePaths(name.asString, currentRoot, currentExtensions, attempts);
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (currentRoot.notNil) {
|
|
||||||
// currentRoot = currentRoot +/+ name.asString;
|
|
||||||
// foundPaths = Require.resolvePaths("*", currentRoot, currentExtensions, attempts);
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (foundPaths.isEmpty) {
|
|
||||||
// currentRoot = this.class.root;
|
|
||||||
// foundPaths = Require.resolvePaths(name.asString, currentRoot, currentExtensions, attempts);
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (foundPaths.isEmpty) {
|
|
||||||
// currentRoot = currentRoot +/+ name.asString;
|
|
||||||
// foundPaths = Require.resolvePaths("*", currentRoot, currentExtensions, attempts);
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (foundPaths.isEmpty) {
|
|
||||||
// foundRoot = nil;
|
|
||||||
// foundRootModTime = nil;
|
|
||||||
// "No samples found, attempted paths: ".warn;
|
|
||||||
// attempts.do {
|
|
||||||
// |a|
|
|
||||||
// "\t%.{%}".format(a, currentExtensions.join(",")).warn
|
|
||||||
// };
|
|
||||||
// } {
|
|
||||||
// foundRoot = currentRoot;
|
|
||||||
// foundRootModTime = File.mtime(foundRoot) ?? {0};
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// foundPaths = foundPaths.sort({
|
|
||||||
// |a, b|
|
|
||||||
// var pair;
|
|
||||||
// #a, b = [a, b].collect {
|
|
||||||
// |p|
|
|
||||||
// p = p.toLower;
|
|
||||||
// p = p.split($.).first;
|
|
||||||
// p = p.split($/).reverse;
|
|
||||||
// };
|
|
||||||
// pair = [a, b].flop.detect({
|
|
||||||
// |pair|
|
|
||||||
// pair[0] != pair[1]
|
|
||||||
// });
|
|
||||||
// pair !? {
|
|
||||||
// pair[0] < pair[1]
|
|
||||||
// } ?? { false }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// if (paths != foundPaths) {
|
|
||||||
// paths = foundPaths;
|
|
||||||
// atCache = ();
|
|
||||||
// this.prUpdateBuffers();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// clear {
|
|
||||||
// paths = [];
|
|
||||||
// atCache = ();
|
|
||||||
// this.prUpdateBuffers()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// bufferAt {
|
|
||||||
// |index|
|
|
||||||
// ^buffers !? {
|
|
||||||
// buffers[index] ?? {
|
|
||||||
// if (Server.default.serverRunning) {
|
|
||||||
// if (channels.isNil) {
|
|
||||||
// buffers[index] = Buffer.read(Server.default, paths[index])
|
|
||||||
// } {
|
|
||||||
// buffers[index] = Buffer.readChannel(Server.default, paths[index], channels:Array.series(channels));
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
// buffers[index];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// at {
|
|
||||||
// |key|
|
|
||||||
// var index;
|
|
||||||
//
|
|
||||||
// if (key.isArray && { key.isString.not }) {
|
|
||||||
// ^key.collect(this.at(_))
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (key.isInteger) {
|
|
||||||
// index = key
|
|
||||||
// } {
|
|
||||||
// index = atCache[key.asSymbol];
|
|
||||||
// if (index.isNil) {
|
|
||||||
// index = paths.detectIndex({
|
|
||||||
// |path|
|
|
||||||
// key.asString.toLower.replace("*", ".*").matchRegexp(
|
|
||||||
// path.asString.toLower
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// atCache[key.asSymbol] = index;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// ^this.bufferAt(index);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// markers {
|
|
||||||
// ^markersCache ?? {
|
|
||||||
// markersCache = paths.collect({
|
|
||||||
// |path|
|
|
||||||
// SoundFile(path).extractMarkers
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// wrapAt {
|
|
||||||
// |index|
|
|
||||||
// if (index.isInteger) {
|
|
||||||
// index = index % buffers.size;
|
|
||||||
// };
|
|
||||||
// ^this.at(index);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// do {
|
|
||||||
// |...args|
|
|
||||||
// buffers.size.collect(this.bufferAt(_)).do(*args)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// collect {
|
|
||||||
// |...args|
|
|
||||||
// ^buffers.size.collect(this.bufferAt(_)).collect(*args)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// prUpdateBuffers {
|
|
||||||
// if (Server.default.serverBooting or: {
|
|
||||||
// Server.default.hasBooted && Server.default.serverRunning.not
|
|
||||||
// }) {
|
|
||||||
// Server.default.doWhenBooted {
|
|
||||||
// this.prUpdateBuffers();
|
|
||||||
// };
|
|
||||||
// ^this;
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// if (Server.default.serverRunning.not) {
|
|
||||||
// buffers = [];
|
|
||||||
// } {
|
|
||||||
// if (paths.size > buffers.size) { buffers = buffers.extend(paths.size) };
|
|
||||||
//
|
|
||||||
// paths.do {
|
|
||||||
// |path, i|
|
|
||||||
// var buffer;
|
|
||||||
//
|
|
||||||
// buffer = buffers[i];
|
|
||||||
//
|
|
||||||
// if (path.notNil) {
|
|
||||||
// if (lazyLoading.not) {
|
|
||||||
// this.bufferAt(i)
|
|
||||||
// }
|
|
||||||
// } {
|
|
||||||
// if (buffer.notNil) {
|
|
||||||
// buffer.free;
|
|
||||||
// buffers[i] = buffer = nil;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// buffers.extend(paths.size);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// doOnServerBoot {
|
|
||||||
// if (paths.size > 0) {
|
|
||||||
// buffers = [];
|
|
||||||
// this.prUpdateBuffers();
|
|
||||||
// "***Loaded samples for %***".format(this.asString).postln;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// doOnServerQuit {
|
|
||||||
// buffers = [];
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pat {
|
|
||||||
// |keyPat|
|
|
||||||
// ^Pindex(Pseq([this], inf), keyPat)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// asBuffer {
|
|
||||||
// ^this.singleSampleWrap(nil)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// asControlInput {
|
|
||||||
// |...args|
|
|
||||||
// ^this.prSingleSampleWrap(\asControlInput, *args)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// play {
|
|
||||||
// |...args|
|
|
||||||
// ^this.prSingleSampleWrap(\play, *args)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// prSingleSampleWrap {
|
|
||||||
// |method ...args|
|
|
||||||
// var buffer;
|
|
||||||
// if (buffers.size == 1) {
|
|
||||||
// buffer = this.bufferAt(0);
|
|
||||||
//
|
|
||||||
// if (method.isNil) {
|
|
||||||
// ^buffer
|
|
||||||
// } {
|
|
||||||
// if (buffer.numFrames.isNil) {
|
|
||||||
// fork {
|
|
||||||
// Server.default.sync;
|
|
||||||
// buffer.performList(method, args)
|
|
||||||
// };
|
|
||||||
// ^nil;
|
|
||||||
// } {
|
|
||||||
// ^buffer.performList(method, args)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } {
|
|
||||||
// Error("Trying to % a bank with multiple buffers".format(method)).throw;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
Bank : Singleton {
|
Bank : Singleton {
|
||||||
classvar <>root, <>extensions, <>lazyLoading=true;
|
classvar <>root, <>extensions, <>lazyLoading=true;
|
||||||
var <paths, buffers, <channels, <foundRoot, <foundRootModTime, markersCache, atCache;
|
var <paths, buffers, <channels, <foundRoot, <foundRootModTime, markersCache, atCache;
|
||||||
|
|||||||
@ -7,23 +7,25 @@ Boot {
|
|||||||
|
|
||||||
*new {
|
*new {
|
||||||
arg configPath, samplePath, serverOptions;
|
arg configPath, samplePath, serverOptions;
|
||||||
var p; var c; var t; var s;
|
var p; var c; var t; var s; var d; var e;
|
||||||
BuboUtils.fancyPrint(BuboUtils.banner, 40);
|
BuboUtils.fancyPrint(BuboUtils.banner, 40);
|
||||||
|
MIDIClient.init;
|
||||||
|
|
||||||
if (serverOptions == nil,
|
if (serverOptions == nil,
|
||||||
{
|
{
|
||||||
"-> Booting using default server configuration".postln;
|
"-> Booting using default server configuration".postln;
|
||||||
s = Server.default;
|
s = Server.default;
|
||||||
s.options.numBuffers = (2048 * 2048) * 2; // Some arbitrary number
|
s.options.numBuffers = (2048 * 2048) * 2;
|
||||||
|
s.options.maxLogins = 1;
|
||||||
s.options.memSize = 8192 * 64;
|
s.options.memSize = 8192 * 64;
|
||||||
s.options.numWireBufs = 2048;
|
s.options.numWireBufs = 2048;
|
||||||
|
s.options.outDevice = "BlackHole 64ch";
|
||||||
s.options.maxNodes = 1024 * 32;
|
s.options.maxNodes = 1024 * 32;
|
||||||
s.options.numOutputBusChannels = 16;
|
s.options.numOutputBusChannels = 24;
|
||||||
s.options.numInputBusChannels = 16;
|
s.options.numInputBusChannels = 16;
|
||||||
s.options.outDevice = "BlackHole 16ch";
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"-> Booting using user server configuration".postln;
|
"-> Booting using custom server configuration".postln;
|
||||||
s = Server.default;
|
s = Server.default;
|
||||||
// Imposing a very high number of buffers!
|
// Imposing a very high number of buffers!
|
||||||
serverOptions.numBuffers = (2048 * 512) * 2;
|
serverOptions.numBuffers = (2048 * 512) * 2;
|
||||||
@ -44,20 +46,42 @@ Boot {
|
|||||||
this.samplePath = samplePath ? "/Users/bubo/.config/livecoding/samples";
|
this.samplePath = samplePath ? "/Users/bubo/.config/livecoding/samples";
|
||||||
|
|
||||||
// Setting up the audio samples/buffers manager
|
// Setting up the audio samples/buffers manager
|
||||||
Bank.lazyLoading = false;
|
Bank.lazyLoading = true;
|
||||||
Bank.root = this.samplePath;
|
Bank.root = this.samplePath;
|
||||||
|
|
||||||
// Post actions: installing behavior after server boot
|
// Post actions: installing behavior after server boot
|
||||||
Server.default.waitForBoot({
|
Server.default.waitForBoot({
|
||||||
|
d = ();
|
||||||
|
// Exceptional Dual Sardine Boot
|
||||||
|
d.dirt = SuperDirt(2, s);
|
||||||
|
d.dirt.fileExtensions = ["wav","aif","aiff","aifc","mp3"];
|
||||||
|
d.dirt.loadSoundFiles("/Users/bubo/Library/Application\ Support/Sardine/SON/*");
|
||||||
|
d.dirt.doNotReadYet = true;
|
||||||
|
d.dirt.start(57120, [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]);
|
||||||
|
(
|
||||||
|
d.d1 = d.dirt.orbits[0]; d.d2 = d.dirt.orbits[1]; d.d3 = d.dirt.orbits[2];
|
||||||
|
d.d4 = d.dirt.orbits[3]; d.d5 = d.dirt.orbits[4]; d.d6 = d.dirt.orbits[5];
|
||||||
|
d.d7 = d.dirt.orbits[6]; d.d8 = d.dirt.orbits[7]; d.d9 = d.dirt.orbits[8];
|
||||||
|
d.d10 = d.dirt.orbits[9]; d.d11 = d.dirt.orbits[10]; d.d12 = d.dirt.orbits[11];
|
||||||
|
);
|
||||||
|
d.dirt.soundLibrary.addMIDI(\midi, MIDIOut.newByName("MIDI", "Bus 1"));
|
||||||
|
d.dirt.soundLibrary.addMIDI(\midi2, MIDIOut.newByName("MIDI", "Bus 2"));
|
||||||
|
s.latency = 0.3;
|
||||||
|
|
||||||
|
// Resume normal boot sequence
|
||||||
"-> Loading config from: %".format(configPath ? (this.localPath +/+ "Startup.scd")).postln;
|
"-> Loading config from: %".format(configPath ? (this.localPath +/+ "Startup.scd")).postln;
|
||||||
(configPath ? (this.localPath +/+ "Startup.scd")).load;
|
(configPath ? (this.localPath +/+ "Startup.scd")).load;
|
||||||
Safety.all;
|
|
||||||
Safety(s).defName = \safeLimit;
|
|
||||||
Safety.setLimit(1);
|
|
||||||
BuboUtils.fancyPrint(BuboUtils.ready, 40);
|
BuboUtils.fancyPrint(BuboUtils.ready, 40);
|
||||||
this.installServerTreeBehavior();
|
this.installServerTreeBehavior();
|
||||||
this.clock.enableMeterSync();
|
this.clock.enableMeterSync();
|
||||||
|
Safety.all;
|
||||||
|
Safety(s).defName = \safeLimit;
|
||||||
|
Safety.setLimit(1);
|
||||||
|
e = currentEnvironment;
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*installServerTreeBehavior {
|
*installServerTreeBehavior {
|
||||||
|
|||||||
@ -4,4 +4,14 @@
|
|||||||
^this.beatDur
|
^this.beatDur
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod {
|
||||||
|
arg modulo;
|
||||||
|
^this.beats % modulo
|
||||||
|
}
|
||||||
|
|
||||||
|
modbar {
|
||||||
|
arg modulo;
|
||||||
|
^this.bar % modulo
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
BuboUtils {
|
BuboUtils {
|
||||||
|
|
||||||
*banner {
|
*banner {
|
||||||
var banner = "┳┓ ┓ ┳┓\n"
|
var banner = "┳┓ ┓ ┳┓ ┓ ┳┓\n"
|
||||||
"┣┫┓┏┣┓┏┓ ┣┫┏┓┏┓╋\n"
|
"┣┫┓┏┣┓┏┓┣┫┓┏┣┓┏┓ ┣┫┏┓┏┓╋\n"
|
||||||
"┻┛┗┻┗┛┗┛ ┻┛┗┛┗┛┗";
|
"┻┛┗┻┗┛┗┛┻┛┗┻┗┛┗┛ ┻┛┗┛┗┛┗";
|
||||||
^banner
|
^banner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,4 +3,3 @@ p = currentEnvironment;
|
|||||||
c = currentEnvironment.clock;
|
c = currentEnvironment.clock;
|
||||||
"Loading SynthDefs".postln;
|
"Loading SynthDefs".postln;
|
||||||
"Synthdefs.scd".loadRelative;
|
"Synthdefs.scd".loadRelative;
|
||||||
m = MIDIControl();
|
|
||||||
|
|||||||
@ -226,4 +226,46 @@ f.vardel = {
|
|||||||
d.tides = z;
|
d.tides = z;
|
||||||
);
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
z = SynthDef('pink', {
|
||||||
|
arg out;
|
||||||
|
var pink = PinkTrombone.ar(
|
||||||
|
noiseSource: BPF.ar(WhiteNoise.ar(), \noiseFilter.kr(2000)),
|
||||||
|
freq: \freq.kr(800),
|
||||||
|
tenseness: \tenseness.kr(0.4),
|
||||||
|
tongueIndex: \tongueIndex.kr(30),
|
||||||
|
tongueDiameter: \tongueDiameter.kr(3.5),
|
||||||
|
constrictionX: \constrictionX.kr(1.5),
|
||||||
|
constrictionY: \constrictionY.kr(2.5),
|
||||||
|
fricativeIntens: \fricativeIntens.kr(1.5)
|
||||||
|
);
|
||||||
|
var env = Env.perc(\attack.kr(0.01), releaseTime: \release.kr(2.0)).kr(doneAction: 2);
|
||||||
|
var sound = pink * env;
|
||||||
|
Out.ar(out, Pan2.ar(sound, pos: \pan.kr(0.0)))
|
||||||
|
}).add;
|
||||||
|
d.pink = z;
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
(
|
||||||
|
z = SynthDef('kick', {
|
||||||
|
|out=0, freq, mul=512, vsweep=0.5, hold=0.25, release=0.25, amp=0.5, pan=0|
|
||||||
|
var p0, p1, p, freq0, freq1, freqEnv, sig;
|
||||||
|
p0 = 0.006699687;
|
||||||
|
p1 = 0.00001884606;
|
||||||
|
p = (1-vsweep)*p0 + (vsweep*p1);
|
||||||
|
freq1 = freq;
|
||||||
|
freq0 = freq1 * mul;
|
||||||
|
freqEnv = EnvGen.ar(Env([0,1], [1.0], [0]));
|
||||||
|
freqEnv = freq1 + ((freq0-freq1)/(1.0 + (freqEnv/p)));
|
||||||
|
sig = SinOsc.ar(freqEnv);
|
||||||
|
sig = sig * EnvGen.ar(Env([1,1,0], [hold,release], [0,0]), doneAction: Done.freeSelf) * amp;
|
||||||
|
sig = Pan2.ar(sig, pan);
|
||||||
|
Out.ar(out, sig);
|
||||||
|
}).add;
|
||||||
|
d.kick = z;
|
||||||
|
);
|
||||||
|
|
||||||
z = nil; // We don't need that variable anymore
|
z = nil; // We don't need that variable anymore
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,135 +1,135 @@
|
|||||||
ControllerValue {
|
// ControllerValue {
|
||||||
|
//
|
||||||
/*
|
// /*
|
||||||
* A ControllerValue represents a MIDI Controller value.
|
// * A ControllerValue represents a MIDI Controller value.
|
||||||
* It has a minimum and maximum value, and a curve. This
|
// * It has a minimum and maximum value, and a curve. This
|
||||||
* is used to convert from the MIDI value to a value that
|
// * is used to convert from the MIDI value to a value that
|
||||||
* is considered usable by the user.
|
// * is considered usable by the user.
|
||||||
*
|
// *
|
||||||
* The curve is similar to the one used by the Env object.
|
// * The curve is similar to the one used by the Env object.
|
||||||
*/
|
// */
|
||||||
|
//
|
||||||
var <>min = 0;
|
// var <>min = 0;
|
||||||
var <>max = 1;
|
// var <>max = 1;
|
||||||
var <>curve = 0;
|
// var <>curve = 0;
|
||||||
var <>currentValue;
|
// var <>currentValue;
|
||||||
var <>bipolar = false;
|
// var <>bipolar = false;
|
||||||
|
//
|
||||||
*new {
|
// *new {
|
||||||
arg min, max, curve;
|
// arg min, max, curve;
|
||||||
^super.new.init()
|
// ^super.new.init()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
init {
|
// init {
|
||||||
this.min = min;
|
// this.min = min;
|
||||||
this.max = max;
|
// this.max = max;
|
||||||
this.curve = curve;
|
// this.curve = curve;
|
||||||
this.currentValue = Bus.control;
|
// this.currentValue = Bus.control;
|
||||||
this.bipolar = false;
|
// this.bipolar = false;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
set {
|
// set {
|
||||||
arg value;
|
// arg value;
|
||||||
// If bipolar is true, then the value must go from -1 to 1
|
// // If bipolar is true, then the value must go from -1 to 1
|
||||||
var conversion = value.lincurve(
|
// var conversion = value.lincurve(
|
||||||
inMin: 0,
|
// inMin: 0,
|
||||||
inMax: 127,
|
// inMax: 127,
|
||||||
outMin: this.min.neg,
|
// outMin: this.min.neg,
|
||||||
outMax: this.max,
|
// outMax: this.max,
|
||||||
curve: this.curve
|
// curve: this.curve
|
||||||
);
|
// );
|
||||||
this.currentValue.set(conversion);
|
// this.currentValue.set(conversion);
|
||||||
^this.currentValue;
|
// ^this.currentValue;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
MIDIControl {
|
// MIDIControl {
|
||||||
|
//
|
||||||
/*
|
// /*
|
||||||
* This is my personal MIDI controller interface. I am using a
|
// * This is my personal MIDI controller interface. I am using a
|
||||||
* MIDIMix. It has 8 faders, 24 knobs, and 16 buttons. I am only
|
// * MIDIMix. It has 8 faders, 24 knobs, and 16 buttons. I am only
|
||||||
* using the knobs and faders. Two buttons are used to change "bank"
|
// * using the knobs and faders. Two buttons are used to change "bank"
|
||||||
* (increments the CC number value).
|
// * (increments the CC number value).
|
||||||
*/
|
// */
|
||||||
|
//
|
||||||
var <>currentBank = 0;
|
// var <>currentBank = 0;
|
||||||
var <>values;
|
// var <>values;
|
||||||
|
//
|
||||||
*new {
|
// *new {
|
||||||
^super.new.init()
|
// ^super.new.init()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
init {
|
// init {
|
||||||
this.values = IdentityDictionary.new();
|
// this.values = IdentityDictionary.new();
|
||||||
this.connect(); this.installCallbacks();
|
// this.connect(); this.installCallbacks();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
getInit {
|
// getInit {
|
||||||
arg number;
|
// arg number;
|
||||||
if (this.values[number] == nil) {
|
// if (this.values[number] == nil) {
|
||||||
this.values[number] = ControllerValue.new(
|
// this.values[number] = ControllerValue.new(
|
||||||
min: 0, max: 127, curve: 0
|
// min: 0, max: 127, curve: 0
|
||||||
);
|
// );
|
||||||
^this.values[number]
|
// ^this.values[number]
|
||||||
} {
|
// } {
|
||||||
^this.values[number]
|
// ^this.values[number]
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
setCurve {
|
// setCurve {
|
||||||
arg number, curve;
|
// arg number, curve;
|
||||||
this.getInit(number).curve = curve;
|
// this.getInit(number).curve = curve;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
setBounds {
|
// setBounds {
|
||||||
arg number, min, max;
|
// arg number, min, max;
|
||||||
var controller = this.getInit(number);
|
// var controller = this.getInit(number);
|
||||||
controller.min = min;
|
// controller.min = min;
|
||||||
controller.max = max;
|
// controller.max = max;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
at {
|
// at {
|
||||||
arg number;
|
// arg number;
|
||||||
var control = this.getInit(number);
|
// var control = this.getInit(number);
|
||||||
var choices = (
|
// var choices = (
|
||||||
value: this.getInit(number).currentValue.getSynchronous,
|
// value: this.getInit(number).currentValue.getSynchronous,
|
||||||
bus: this.getInit(number).currentValue,
|
// bus: this.getInit(number).currentValue,
|
||||||
map: this.getInit(number).currentValue.asMap,
|
// map: this.getInit(number).currentValue.asMap,
|
||||||
kr: In.kr(this.getInit(number).currentValue),
|
// kr: In.kr(this.getInit(number).currentValue),
|
||||||
);
|
// );
|
||||||
^choices
|
// ^choices
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
connect {
|
// connect {
|
||||||
MIDIClient.init;
|
// MIDIClient.init;
|
||||||
MIDIIn.connectAll(verbose: true);
|
// MIDIIn.connectAll(verbose: true);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
installCallbacks {
|
// installCallbacks {
|
||||||
MIDIIn.addFuncTo(\control, {
|
// MIDIIn.addFuncTo(\control, {
|
||||||
arg src, chan, num, val;
|
// arg src, chan, num, val;
|
||||||
("CONTROL:" + (num + (this.currentBank * 24)) + "=>" + val).postln;
|
// ("CONTROL:" + (num + (this.currentBank * 24)) + "=>" + val).postln;
|
||||||
this.getInit(num + (this.currentBank * 24)).set(val);
|
// this.getInit(num + (this.currentBank * 24)).set(val);
|
||||||
});
|
// });
|
||||||
MIDIIn.addFuncTo(\noteOn, {
|
// MIDIIn.addFuncTo(\noteOn, {
|
||||||
arg src, chan, num, val;
|
// arg src, chan, num, val;
|
||||||
"Changing bank".postln;
|
// "Changing bank".postln;
|
||||||
if (chan == 8 && num == 22) {
|
// if (chan == 8 && num == 22) {
|
||||||
if (this.currentBank > 0) {
|
// if (this.currentBank > 0) {
|
||||||
this.currentBank = this.currentBank - 1;
|
// this.currentBank = this.currentBank - 1;
|
||||||
};
|
// };
|
||||||
this.currentBank.postln;
|
// this.currentBank.postln;
|
||||||
};
|
// };
|
||||||
if (chan == 8 && num == 24) {
|
// if (chan == 8 && num == 24) {
|
||||||
if (this.currentBank < 3) {
|
// if (this.currentBank < 3) {
|
||||||
this.currentBank = this.currentBank + 1;
|
// this.currentBank = this.currentBank + 1;
|
||||||
};
|
// };
|
||||||
this.currentBank.postln;
|
// this.currentBank.postln;
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
|
|||||||
502
Classes/Patterns/Pmod.sc
Normal file
502
Classes/Patterns/Pmod.sc
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
Pmod : Pattern {
|
||||||
|
|
||||||
|
classvar defHashLRU, <defCache, <defNames, <defNamesFree, defCount=0, maxDefNames=100;
|
||||||
|
var <>synthName, <>patternPairs, <rate, <>channels, asValues=false;
|
||||||
|
|
||||||
|
*new {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^super.newCopyArgs(synthName, pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
*kr {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\control)
|
||||||
|
}
|
||||||
|
|
||||||
|
*kr1 {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\control).channels_(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
*kr2 {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\control).channels_(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
*kr3 {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\control).channels_(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
*kr4 {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\control).channels_(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
*ar {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\audio)
|
||||||
|
}
|
||||||
|
|
||||||
|
*ar1 {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\audio).channels_(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
*ar2 {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\audio).channels_(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
*ar3 {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\audio).channels_(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
*ar4 {
|
||||||
|
|synthName ... pairs|
|
||||||
|
^this.new(synthName, *pairs).rate_(\audio).channels_(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
*initClass {
|
||||||
|
defCache = ();
|
||||||
|
defNames = ();
|
||||||
|
defHashLRU = LinkedList();
|
||||||
|
defNamesFree = IdentitySet();
|
||||||
|
(1..16).do {
|
||||||
|
|n|
|
||||||
|
[\kr, \ar].do {
|
||||||
|
|rate|
|
||||||
|
this.wrapSynth(
|
||||||
|
rate: rate,
|
||||||
|
func: { \value.perform(rate, (0 ! n)) },
|
||||||
|
channels: n,
|
||||||
|
defName: "Pmod_constant_%_%".format(n, rate).asSymbol,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap a func in fade envelope / provide XOut
|
||||||
|
*wrapSynth {
|
||||||
|
|rate, func, channels, defName|
|
||||||
|
var hash, def, args;
|
||||||
|
|
||||||
|
defName = defName ?? {
|
||||||
|
hash = [func, rate].hash;
|
||||||
|
defHashLRU.remove(hash);
|
||||||
|
defHashLRU.addFirst(hash);
|
||||||
|
defNames[hash] ?? {
|
||||||
|
defNames[hash] = this.getDefName();
|
||||||
|
defNames[hash]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (defCache[defName].isNil) {
|
||||||
|
|
||||||
|
def = SynthDef(defName, {
|
||||||
|
var fadeTime, paramLag, fade, sig;
|
||||||
|
|
||||||
|
fadeTime = \fadeTime.kr(0);
|
||||||
|
paramLag = \paramLag.ir(0);
|
||||||
|
|
||||||
|
fade = Env([1, 1, 0], [0, fadeTime], releaseNode:1).kr(gate:\gate.kr(1), doneAction:2);
|
||||||
|
sig = SynthDef.wrap(func, paramLag ! func.def.argNames.size);
|
||||||
|
sig = sig.asArray.flatten;
|
||||||
|
|
||||||
|
if (channels.isNil) {
|
||||||
|
channels = sig.size;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rate.isNil) {
|
||||||
|
rate = sig.rate.switch(\audio, \ar, \control, \kr);
|
||||||
|
};
|
||||||
|
|
||||||
|
\channels.ir(channels); // Unused, but helpful to see channelization for debugging
|
||||||
|
|
||||||
|
sig = sig.collect {
|
||||||
|
|channel|
|
||||||
|
if ((channel.rate == \scalar) && (rate == \ar)) {
|
||||||
|
channel = DC.ar(channel);
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((channel.rate == \audio) && (rate == \kr)) {
|
||||||
|
channel = A2K.kr(channel);
|
||||||
|
"Pmod output is \audio, \control rate expected".warn;
|
||||||
|
} {
|
||||||
|
if ((channel.rate == \control) && (rate == \ar)) {
|
||||||
|
channel = K2A.ar(channel);
|
||||||
|
"Pmod output is \control, \audio rate expected".warn;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sig.shape != [channels]) {
|
||||||
|
sig.reshape(channels);
|
||||||
|
};
|
||||||
|
|
||||||
|
XOut.perform(rate, \out.kr(0), fade, sig);
|
||||||
|
});
|
||||||
|
args = def.asSynthDesc.controlNames.flatten.asArray;
|
||||||
|
defCache[defName] = [rate, channels, def, args];
|
||||||
|
} {
|
||||||
|
#rate, channels, def, args = defCache[defName];
|
||||||
|
};
|
||||||
|
|
||||||
|
def.add;
|
||||||
|
|
||||||
|
^(
|
||||||
|
instrument: defName,
|
||||||
|
args: [\value, \fadeTime, \paramLag, \out] ++ args,
|
||||||
|
pr_rate: rate,
|
||||||
|
pr_channels: channels,
|
||||||
|
pr_instrumentHash: hash ?? { [func, rate].hash },
|
||||||
|
hasGate: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rate_{
|
||||||
|
|r|
|
||||||
|
rate = (
|
||||||
|
control: \kr,
|
||||||
|
audio: \ar,
|
||||||
|
kr: \kr,
|
||||||
|
ar: \kr
|
||||||
|
)[r]
|
||||||
|
}
|
||||||
|
|
||||||
|
embedInStream {
|
||||||
|
|inEvent|
|
||||||
|
var server, synthStream, streamPairs, endVal, cleanup,
|
||||||
|
synthGroup, newSynthGroup, modGroup, newModGroup,
|
||||||
|
buses, currentArgs, currentBuses, currentSize, currentEvent, fadeTime,
|
||||||
|
nextEvent, nextSynth, streamAsValues, currentChannels, currentRate, cleanupFunc;
|
||||||
|
|
||||||
|
// CAVEAT: Server comes from initial inEvent and cannot be changed later on.
|
||||||
|
server = inEvent[\server] ?? { Server.default };
|
||||||
|
server = server.value;
|
||||||
|
|
||||||
|
streamAsValues = asValues;
|
||||||
|
|
||||||
|
// Setup pattern pairs
|
||||||
|
streamPairs = patternPairs.copy;
|
||||||
|
endVal = streamPairs.size - 1;
|
||||||
|
forBy (1, endVal, 2) { |i| streamPairs[i] = streamPairs[i].asStream };
|
||||||
|
synthStream = synthName.asStream;
|
||||||
|
|
||||||
|
// Prepare busses
|
||||||
|
buses = List();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cleanupFunc = Thunk({
|
||||||
|
currentEvent !? {
|
||||||
|
if (currentEvent[\isPlaying].asBoolean) {
|
||||||
|
currentEvent.release(currentEvent[\fadeTime])
|
||||||
|
};
|
||||||
|
|
||||||
|
this.recycleDefName(currentEvent);
|
||||||
|
{
|
||||||
|
newModGroup !? _.free;
|
||||||
|
buses.do(_.free);
|
||||||
|
}.defer(currentEvent[\fadeTime] ? 10)
|
||||||
|
{
|
||||||
|
newSynthGroup !? _.free;
|
||||||
|
}.defer(5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cleanup = EventStreamCleanup();
|
||||||
|
cleanup.addFunction(inEvent, cleanupFunc);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Prepare groups, reusing input group if possible.
|
||||||
|
// This is the group that the outer event - the one whose parameters
|
||||||
|
// we're modulating - is playing to.
|
||||||
|
//
|
||||||
|
// If newSynthGroup.notNil, then we allocated and we must clean up.
|
||||||
|
if (inEvent.keys.includes(\group)) {
|
||||||
|
synthGroup = inEvent.use({ ~group.value });
|
||||||
|
} {
|
||||||
|
inEvent[\group] = synthGroup = newSynthGroup ?? {
|
||||||
|
newSynthGroup = Group(server.asTarget);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare modGroup, which is our modulation group and lives before
|
||||||
|
// synthGroup.
|
||||||
|
// If newModGroup.notNil, then we allocated and we must clean up
|
||||||
|
if (inEvent.keys.includes(\modGroup)) {
|
||||||
|
modGroup = inEvent[\modGroup];
|
||||||
|
} {
|
||||||
|
inEvent[\modGroup] = modGroup = newModGroup ?? {
|
||||||
|
newModGroup = Group(synthGroup.asTarget, \addBefore);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// We must set group/addAction early, so they are passed to the .next()
|
||||||
|
// of child streams.
|
||||||
|
nextEvent = ();
|
||||||
|
nextEvent[\synthDesc] = nil;
|
||||||
|
nextEvent[\msgFunc] = nil;
|
||||||
|
nextEvent[\group] = modGroup;
|
||||||
|
nextEvent[\addAction] = \addToHead;
|
||||||
|
nextEvent[\resend] = false;
|
||||||
|
|
||||||
|
// Get nexts
|
||||||
|
nextSynth = synthStream.next(nextEvent.copy);
|
||||||
|
nextSynth = this.prepareSynth(nextSynth);
|
||||||
|
nextEvent = this.prNext(streamPairs, nextEvent);
|
||||||
|
|
||||||
|
if (inEvent.isNil || nextEvent.isNil || nextSynth.isNil) {
|
||||||
|
^cleanup.exit(inEvent);
|
||||||
|
} {
|
||||||
|
cleanup.update(inEvent);
|
||||||
|
|
||||||
|
nextEvent.putAll(nextSynth);
|
||||||
|
|
||||||
|
// 1. We need argument names in order to use (\type, \set).
|
||||||
|
// 2. We need size to determine if we need to allocate more busses for e.g.
|
||||||
|
// an event like (freq: [100, 200]).
|
||||||
|
currentArgs = nextEvent[\instrument].asArray.collect(_.asSynthDesc).collect(_.controlNames).flatten.asSet.asArray;
|
||||||
|
currentSize = nextEvent.atAll(currentArgs).maxValue({ |v| v.isArray.if(v.size, 1) }).max(1);
|
||||||
|
|
||||||
|
currentChannels = nextSynth[\pr_channels];
|
||||||
|
currentRate = nextSynth[\pr_rate];
|
||||||
|
|
||||||
|
buses.first !? {
|
||||||
|
|bus|
|
||||||
|
var busRate = switch(bus.rate, \audio, \ar, \control, \kr, bus.rate);
|
||||||
|
if (busRate != currentRate) {
|
||||||
|
Error("Cannot use Synths of different rates in a single Pmod (% vs %)".format(
|
||||||
|
bus.rate, currentRate
|
||||||
|
)).throw;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(currentSize - buses.size).do {
|
||||||
|
if (currentRate == \ar) {
|
||||||
|
buses = buses.add(Bus.audio(server, currentChannels))
|
||||||
|
} {
|
||||||
|
buses = buses.add(Bus.control(server, currentChannels))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
currentBuses = buses.collect(_.index).extend(currentSize);
|
||||||
|
if (currentBuses.size == 1) { currentBuses = currentBuses[0] };
|
||||||
|
|
||||||
|
// If we've got a different instrument than last time, send a new one,
|
||||||
|
// else just set the parameters of the existing.
|
||||||
|
if (nextEvent[\resend]
|
||||||
|
or: {nextEvent[\pr_instrumentHash] != currentEvent.tryPerform(\at, \pr_instrumentHash)})
|
||||||
|
{
|
||||||
|
nextEvent[\parentType] = \note;
|
||||||
|
nextEvent[\type] = \note;
|
||||||
|
nextEvent[\sustain] = nil;
|
||||||
|
nextEvent[\sendGate] = false;
|
||||||
|
nextEvent[\fadeTime] = fadeTime = nextEvent[\fadeTime] ?? 0;
|
||||||
|
nextEvent[\out] = currentBuses;
|
||||||
|
nextEvent[\group] = modGroup;
|
||||||
|
nextEvent[\addAction] = \addToHead; // SUBTLE: new synths before old, so OLD synth is responsible for fade-out
|
||||||
|
|
||||||
|
// Free existing synth
|
||||||
|
currentEvent !? {
|
||||||
|
|e|
|
||||||
|
// Assumption: If \hasGate -> false, then synth will free itself.
|
||||||
|
if (e[\isPlaying].asBoolean && e[\hasGate]) {
|
||||||
|
e[\sendGate] = true;
|
||||||
|
e.release(nextEvent[\fadeTime]);
|
||||||
|
e[\isPlaying] = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
nextEvent[\parentType] = \set;
|
||||||
|
nextEvent[\type] = \set;
|
||||||
|
nextEvent[\id] = currentEvent[\id];
|
||||||
|
nextEvent[\args] = currentEvent[\args];
|
||||||
|
nextEvent[\out] = currentEvent[\out];
|
||||||
|
};
|
||||||
|
|
||||||
|
nextEvent.parent ?? { nextEvent.parent = Event.parentEvents.default };
|
||||||
|
|
||||||
|
// SUBTLE: If our inEvent didn't have a group, we set its group here.
|
||||||
|
// We do this late so previous uses of inEvent aren't disrupted.
|
||||||
|
if (newSynthGroup.notNil) {
|
||||||
|
inEvent[\group] = newSynthGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Yield our buses via .asMap
|
||||||
|
inEvent = currentSize.collect({
|
||||||
|
|i|
|
||||||
|
var group;
|
||||||
|
{
|
||||||
|
if (i == 0) {
|
||||||
|
cleanup.addFunction(currentEnvironment, cleanupFunc)
|
||||||
|
};
|
||||||
|
// In this context, ~group refers to the event being modulated,
|
||||||
|
// not the Pmod event.
|
||||||
|
|
||||||
|
~group = ~group.value;
|
||||||
|
if (~group.notNil and: { ~group != synthGroup }) {
|
||||||
|
modGroup.moveBefore(~group.asGroup)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (nextEvent[\isPlaying].asBoolean.not) {
|
||||||
|
currentEvent = nextEvent;
|
||||||
|
nextEvent[\isPlaying] = true;
|
||||||
|
nextEvent.playAndDelta(cleanup, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (streamAsValues) {
|
||||||
|
buses[i].getSynchronous;
|
||||||
|
} {
|
||||||
|
buses[i].asMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (currentSize == 1) {
|
||||||
|
inEvent = inEvent[0].yield;
|
||||||
|
} {
|
||||||
|
inEvent = inEvent.yield;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
^cleanup.exit(inEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This roughly follows the logic of Pbind
|
||||||
|
prNext {
|
||||||
|
|streamPairs, inEvent|
|
||||||
|
var event, endVal;
|
||||||
|
|
||||||
|
event = this.prScrubEvent(inEvent);
|
||||||
|
endVal = streamPairs.size - 1;
|
||||||
|
|
||||||
|
forBy (0, endVal, 2) { arg i;
|
||||||
|
var name = streamPairs[i];
|
||||||
|
var stream = streamPairs[i+1];
|
||||||
|
var streamout = stream.next(event);
|
||||||
|
if (streamout.isNil) { ^inEvent };
|
||||||
|
|
||||||
|
if (name.isSequenceableCollection) {
|
||||||
|
if (name.size > streamout.size) {
|
||||||
|
("the pattern is not providing enough values to assign to the key set:" + name).warn;
|
||||||
|
^inEvent
|
||||||
|
};
|
||||||
|
name.do { arg key, i;
|
||||||
|
event.put(key, streamout[i]);
|
||||||
|
};
|
||||||
|
}{
|
||||||
|
event.put(name, streamout);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
^event;
|
||||||
|
}
|
||||||
|
|
||||||
|
recycleDefName {
|
||||||
|
|event|
|
||||||
|
var hash, name;
|
||||||
|
if (defHashLRU.size > maxDefNames) {
|
||||||
|
hash = defHashLRU.pop();
|
||||||
|
name = defNames[hash];
|
||||||
|
defNames[hash] = nil;
|
||||||
|
defCache[name] = nil;
|
||||||
|
defNamesFree.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*getDefName {
|
||||||
|
if (defNamesFree.notEmpty) {
|
||||||
|
^defNamesFree.pop()
|
||||||
|
} {
|
||||||
|
defCount = defCount + 1;
|
||||||
|
^"Pmod_unique_%".format(defCount).asSymbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrub parent event of Pmod-specific values like group - these will disrupt
|
||||||
|
// the way we set up our groups and heirarchy.
|
||||||
|
prScrubEvent {
|
||||||
|
|event|
|
||||||
|
event[\modGroup] = nil;
|
||||||
|
^event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert an item from our instrument stream into a SynthDef name.
|
||||||
|
// This can possible add a new SynthDef if supplied with e.g. a function.
|
||||||
|
prepareSynth {
|
||||||
|
|synthVal|
|
||||||
|
var synthDesc, synthOutput;
|
||||||
|
^case
|
||||||
|
{ synthVal.isKindOf(Array) } {
|
||||||
|
synthVal.collect(this.prepareSynth(_)).reduce({
|
||||||
|
|a, b|
|
||||||
|
a.merge(b, {
|
||||||
|
|a, b|
|
||||||
|
a.asArray.add(b)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{ synthVal.isKindOf(SimpleNumber) } {
|
||||||
|
var constRate = rate ?? { \ar }; // default to \ar, because this works for both ar and kr mappings;
|
||||||
|
var constChannels = channels ?? { 1 };
|
||||||
|
|
||||||
|
this.class.wrapSynth(
|
||||||
|
channels: constChannels, rate: constRate,
|
||||||
|
defName: "Pmod_constant_%_%".format(constChannels, constRate).asSymbol
|
||||||
|
).putAll((
|
||||||
|
value: synthVal
|
||||||
|
))
|
||||||
|
}
|
||||||
|
{ synthVal.isKindOf(Symbol) } {
|
||||||
|
synthDesc = synthVal.asSynthDesc;
|
||||||
|
synthOutput = synthDesc.outputs.detect({ |o| o.startingChannel == \out });
|
||||||
|
|
||||||
|
if (synthOutput.isNil) {
|
||||||
|
Error("Synth '%' needs at least one output, connected to an \out synth parameter".format(synthVal)).throw;
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
instrument: synthVal,
|
||||||
|
args: synthDesc.controlNames.flatten.asSet.asArray,
|
||||||
|
pr_instrumentHash: synthVal.identityHash,
|
||||||
|
pr_rate: synthOutput.rate.switch(\audio, \ar, \control, \kr),
|
||||||
|
pr_channels: synthOutput.numberOfChannels
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{ synthVal.isKindOf(AbstractFunction) } {
|
||||||
|
this.class.wrapSynth(rate, synthVal, channels)
|
||||||
|
}
|
||||||
|
{ synthVal.isNil } {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
{
|
||||||
|
synthVal.putAll(this.prepareSynth(synthVal[\instrument]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asValues {
|
||||||
|
asValues = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
expand {
|
||||||
|
^(
|
||||||
|
Pfunc({
|
||||||
|
|in|
|
||||||
|
var thunk;
|
||||||
|
|
||||||
|
if (in.isArray) { in = in[0] };
|
||||||
|
thunk = Thunk({
|
||||||
|
in.value
|
||||||
|
});
|
||||||
|
|
||||||
|
this.channels.collect {
|
||||||
|
|i|
|
||||||
|
{
|
||||||
|
thunk.value.asArray[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) <> this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user