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.
503 lines
12 KiB
Python
503 lines
12 KiB
Python
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
|
|
)
|
|
}
|
|
}
|