Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
270 lines (246 sloc) 8.68 KB
Engine_Pedalboard : CroneEngine {
var allPedalDefinitions;
var allPedalIds;
var boardIds;
var optionalPedalLookup;
var pedalSetupState;
var pedalDetails;
var buses;
var passThru;
var inputStage;
var inputAmp = 1;
var outputStage;
var outputAmp = 1;
var numInputChannels = 2;
*new { arg context, doneCallback;
^super.new(context, doneCallback);
}
alloc {
// Set up pedal definitions and commands
allPedalDefinitions = [
AmpSimulatorPedal,
AutoWahPedal,
BitcrusherPedal,
ChorusPedal,
CloudsPedal,
CompressorPedal,
DelayPedal,
DistortionPedal,
EqualizerPedal,
FlangerPedal,
LoFiPedal,
OverdrivePedal,
PhaserPedal,
PitchShifterPedal,
ReverbPedal,
RingModulatorPedal,
RingsPedal,
SubBoostPedal,
SustainPedal,
TremoloPedal,
TunerPedal,
VibratoPedal,
WavefolderPedal,
];
allPedalIds = List.new;
optionalPedalLookup = Dictionary.new;
pedalSetupState = Dictionary.new;
pedalDetails = Dictionary.new;
allPedalDefinitions.do({|pedalDefinition|
if (pedalDefinition.addOnBoot, {
pedalDefinition.addDef(context);
pedalSetupState[pedalDefinition.id] = 1;
}, {
optionalPedalLookup[pedalDefinition.id] = pedalDefinition;
pedalSetupState[pedalDefinition.id] = 2;
});
allPedalIds.add(pedalDefinition.id);
pedalDetails[pedalDefinition.id] = Dictionary.new;
pedalDetails[pedalDefinition.id][\arguments] = Dictionary.new;
pedalDefinition.arguments.do({|argument|
this.addCommand(pedalDefinition.id ++ "_" ++ argument, "f", {|msg|
pedalDetails[pedalDefinition.id][\arguments].add(argument -> msg[1]);
if (pedalDetails[pedalDefinition.id][\synth].notNil, {
pedalDetails[pedalDefinition.id][\synth].set(argument, msg[1]);
});
});
});
this.addPoll(pedalDefinition.id ++ "_ready_poll", {
pedalSetupState[pedalDefinition.id];
});
});
// Make a simple passthru synth for the empty board
SynthDef(\passThru, {|inL, inR, out, amp=1|
Out.ar(out, [In.ar(inL), In.ar(inR)] * amp);
}).add;
context.server.sync;
boardIds = List[];
buses = List.new;
// Start the buses with a bus coming from inputStage and a bus going to outputStage
buses.add(Bus.audio(context.server, 2));
buses.add(Bus.audio(context.server, 2));
// make the inputStage and outputStage and connect the input and output buses
inputStage = Synth.new(\passThru, [
\inL, this.getInL,
\inR, this.getInR,
\out, buses[0].index,
\amp, inputAmp,
], context.xg);
outputStage = Synth.new(\passThru, [
\inL, buses[1].index,
\inR, buses[1].index + 1,
\out, context.out_b.index,
\amp, outputAmp,
], inputStage, \addAfter);
// Set up commands for board management
this.addCommand("add_pedal_definition", "s", {|msg| this.addPedalDefinition(msg[1]);});
this.addCommand("add_pedal", "s", {|msg| this.addPedal(msg[1]);});
this.addCommand("insert_pedal_at_index", "is", {|msg| this.insertPedalAtIndex(msg[1], msg[2]);});
this.addCommand("remove_pedal_at_index", "ii", {|msg| this.removePedalAtIndex(msg[1], msg[2] == 1);});
this.addCommand("swap_pedal_at_index", "is", {|msg| this.swapPedalAtIndex(msg[1], msg[2]);});
this.addCommand("set_num_input_channels", "i", {|msg| this.setNumInputChannels(msg[1]);});
this.addCommand("set_input_amp", "f", {|msg| this.setInputAmp(msg[1]);});
this.addCommand("set_output_amp", "f", {|msg| this.setOutputAmp(msg[1]);});
this.buildNoPedalState;
// TODO: before outs, put a basic Limiter.ar(mixdown, 1.0) ?
// Use this line to test CPU load:
// fork { loop { [context.server.peakCPU, context.server.avgCPU].postln; 3.wait } };
}
buildNoPedalState {
// Have a no-op "pedal" in the middle to connect buses[1] to buses[2]
passThru = Synth.new(\passThru, [
\inL, buses[0].index,
\inR, buses[0].index + 1,
\out, buses[1].index,
], inputStage, \addAfter);
}
addPedalDefinition {|pedalId|
try {
optionalPedalLookup[pedalId].addDef(context);
pedalSetupState[pedalId] = 1;
} {
pedalSetupState[pedalId] = 0;
}
}
addPedal {|pedalId|
this.insertPedalAtIndex(boardIds.size, pedalId);
}
insertPedalAtIndex {|index, pedalId|
var inL, inR, out, target, addAction = \addAfter, indexToRemove = -1;
// Don't allow inserting beyond the end of the board (other than adding just onto the end)
if (index > boardIds.size, {
index = boardIds.size;
});
// If the pedal is already elsewhere in the chain, remove it first.
// TODO: this could be made somewhat more "correct" by saving synths on a board array, rather than in the id lookup.
// This would mean a rethinking of how the addCommand works (likely: loop over board until match is found)
if (pedalDetails[pedalId][\synth].notNil, {
boardIds.do({|item, i| if (item == pedalId, { indexToRemove = i; }); });
});
if (indexToRemove != -1, {
// No work to do if the pedal is already in the right place
if (indexToRemove == index, { ^this; });
this.removePedalAtIndex(indexToRemove);
// This now results in the pedal not being put at exactly `index`,
// but for now it's necessary given how we don't allow duplicate pedals
if (index > indexToRemove, { index = index - 1 });
});
// Okay, enough edge cases. Now for the real insertion.
// Our inputs are always the bus at our target index
inL = buses[index].index;
inR = buses[index].index + 1;
// If there's pedals, we have to make a bus as our output to patch in to the existing pedals
// (if there's no pedals yet, there's already a bus at buses[1] waiting for us to use)
if (boardIds.size != 0, {
buses.insert(index + 1, Bus.audio(context.server, 2));
});
// Our output is the bus at index+1
out = buses[index + 1].index;
// We add ourselves after the prior pedal (or after the inputStage if there's no pedals yet)
if (index == 0, {
target = inputStage;
}, {
target = pedalDetails[boardIds[index - 1]][\synth];
});
if (pedalDetails[pedalId][\synth].notNil, {
pedalDetails[pedalId][\synth].moveAfter(target);
pedalDetails[pedalId][\synth].set(\inL, inL, \inR, inR, \out, out);
}, {
pedalDetails[pedalId][\synth] = Synth.new(
pedalId,
pedalDetails[pedalId][\arguments].merge((inL: inL, inR: inR, out: out)).getPairs,
target,
addAction
);
});
if (index == boardIds.size, {
// If we're inserting at the end, we set the outputStage to have its inputs as our outputs
outputStage.set(\inL, out, \inR, out + 1);
}, {
// Otherwise we set the pedal after us's inputs as our outputs
pedalDetails[boardIds[index]][\synth].set(\inL, out, \inR, out + 1);
});
if (boardIds.size == 0, {
// If there used to be no pedals, we have to free up the passThru synth
passThru.free;
});
boardIds.insert(index, pedalId);
}
removePedalAtIndex {|index, shouldNotFree = false|
if (boardIds.size == 1, {
this.buildNoPedalState;
}, {
// Set the pedal (or output stage) after us to have our inputs as its new inputs
var inputBus = buses[index];
if (index == (boardIds.size - 1), {
outputStage.set(\inL, inputBus.index, \inR, inputBus.index + 1);
}, {
var nextPedal = pedalDetails[boardIds[index + 1]][\synth];
nextPedal.set(\inL, inputBus.index, \inR, inputBus.index + 1);
});
// Free up the bus we were using as an output
buses[index + 1].free;
buses.removeAt(index + 1);
});
if (shouldNotFree, {}, {
pedalDetails[boardIds[index]][\synth].free;
pedalDetails[boardIds[index]][\synth] = nil;
});
boardIds.removeAt(index);
}
swapPedalAtIndex {|index, newPedalId|
if (index < boardIds.size, {
this.removePedalAtIndex(index);
});
this.insertPedalAtIndex(index, newPedalId);
}
setNumInputChannels {|numChannelsArg|
numInputChannels = numChannelsArg;
inputStage.set(\inL, this.getInL, \inR, this.getInR);
}
getInL {
^context.in_b[0].index;
}
getInR {
if (numInputChannels == 1, {
^context.in_b[0].index;
}, {
^context.in_b[1].index;
});
}
setInputAmp {|amp|
inputAmp = amp;
inputStage.set(\amp, inputAmp);
}
setOutputAmp {|amp|
outputAmp = amp;
outputStage.set(\amp, outputAmp);
}
free {
buses.do({|bus| bus.free; });
allPedalIds.do({|pedalId| pedalDetails[pedalId][\synth].free;});
outputStage.free;
passThru.free;
inputStage.free;
}
}