ZZZDevice : ZZZ{ classvar < clockTypes = #[\zeroFive, \minusFiveFive, \korgZeroFive, \korgMinusFiveFive]; classvar < playTypes = #[\beat, \bar]; var < outs, < tempoBusses, < clocks, < deviceGroup, < gateBusses, < gates, < tempos, clocksTempoMap; *new{ arg channels, server; if(channels.isNil, { ZZZError("ZZZDevice: Initialized without setting channels parameter!").throw; }); if(server.isNil, { ZZZError("ZZZDevice: Initialized without setting server parameter!").throw; }); ^super.newCopyArgs(channels, server).init; } init{ ("Initializing ZZZDevice...").postln; clocks = Dictionary(); gates = Dictionary(); gateBusses = Dictionary(); outs = Dictionary(); tempos = Dictionary(); tempoBusses = Dictionary(); clocksTempoMap = Dictionary(); (1..super.channels.size).do({|item, i| outs.add(item -> super.channels[i]); }); // add Group for all Synths used by ZZZDevice instance super.server.doWhenBooted({deviceGroup = Group.new()}); } outputOnHardware{ |output| if(output.isNil || output.isInteger.not,{ ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1).").throw; }); if(output < 1 || output > outs.size,{ ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1, <"++outs.size++").").throw; }); ^outs.at(output); } addTempo{|slot, tempo| if(slot.isNil || slot.isInteger.not, { ZZZError("ZZZDevice: The provided slot number is not valid (needs to be >= 1).").throw; }); if(slot < 1, { ZZZError("ZZZDevice: The provided slot number is not valid (needs to be >= 1).").throw; }); if(tempo.isNil || tempo.isFloat.not, { ZZZError("ZZZDevice: The provided tempo is not valid (needs to be >= 0.0).").throw; }); if(tempo < 0.0, { ZZZError("ZZZDevice: The provided tempo is not valid (needs to be >= 0.0).").throw; }); if(tempos.at(slot).notNil, { ZZZError("ZZZDevice: Cannot add tempo "++ tempo ++" in slot "++ slot ++". There is a TempoBusClock already.").throw; }); ("Create new TempoBusClock in slot "++ slot++" with tempo "++tempo).postln; tempoBusses.add(slot -> Bus.control()); if(super.server.hasShmInterface,{ tempoBusses.at(slot).setSynchronous(tempo); },{ tempoBusses.at(slot).set(tempo); }); tempos.add(slot -> TempoBusClock.new(control: tempoBusses.at(slot))); } removeTempo{|slot, playType = \beat, quant = 1| var removeFunc; if(slot.isNil || slot.isInteger.not, { ZZZError("ZZZDevice: The provided slot number is not valid (needs to be >= 1).").throw; }); if(slot < 1, { ZZZError("ZZZDevice: The provided slot number is not valid (needs to be >= 1).").throw; }); clocksTempoMap.keysValuesDo({|key, value| if(value == slot, { this.removeClock(output: key, playType: playType, quant: quant); }); }); removeFunc = { tempos.at(slot).clear; tempos.at(slot).stop; tempos.removeAt(slot); tempoBusses.at(slot).free; tempoBusses.removeAt(slot); }; if(playType == \beat, { tempos.at(slot).play(removeFunc, quant: quant); }); if(playType == \bar, { tempos.at(slot).playNextBar(removeFunc); }); } setTempo{|slot, tempo| if(slot.isNil || slot.isInteger.not || slot < 1, { ZZZError("ZZZDevice: The provided slot number is not valid (needs to be >= 1).").throw; }); if(tempo.isNil || tempo.isFloat.not || tempo < 0.0, { ZZZError("ZZZDevice: The provided tempo is not valid (needs to be >= 0.0).").throw; }); if(tempos.at(slot).isNil, { ZZZError("ZZZDevice: Cannot set tempo "++ tempo ++" at slot "++ slot ++". There is no TempoBusClock.").throw; }); if(super.server.hasShmInterface,{ tempoBusses.at(slot).setSynchronous(tempo); },{ tempoBusses.at(slot).set(tempo); }); } addClock{ |output, slot, type = \zeroFive, playType = \beat, quant = 1, replace = false| var clockFunc, synthName, synthArgs; if(slot.isNil || slot.isInteger.not || slot < 1, { ZZZError("ZZZDevice: The provided slot number is not valid (needs to be >= 1).").throw; }); if(slot < 1, { ZZZError("ZZZDevice: The provided slot number is not valid (needs to be >= 1).").throw; }); if(tempos.at(slot).isNil, { ZZZError("ZZZDevice: There is no tempo at slot "++slot).throw; }); if(output.isNil || output.isInteger.not || output < 1, { ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1).").throw; }); if(output < 1, { ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1).").throw; }); if(clockTypes.includes(type).not, { ZZZError("ZZZDevice: Unrecognized clock type "++ type ++". Only the following are understood: "++ clockTypes).throw; }); if(playTypes.includes(playType).not, { ZZZError("ZZZDevice: Unrecognized playType "++ type ++". Only the following are understood: "++ playTypes).throw; }); if(clocks.at(output).notNil && replace.not, { ZZZError("Cannot add clock on output "++ output ++". There is a "++clocks.at(output).defName++" playing and replacement was not requested.").throw; }); if(clocks.at(output).isNil && replace, { ZZZError("Cannot replace clock on output "++ output ++". There is no clock playing.").throw; }); // add \ZZZClock Synth to add clocking for various standards // the standard (derived from MIDI SYNC) uses 24 pulses per quarter note if(type == \zeroFive, { synthName = \ZZZClock; synthArgs = [\out, this.outputOnHardware(output: output), \bus, tempoBusses.at(slot), \mul, 0.5]; }); // some devices react to the range of minus five to plus five if(type == \minusFiveFive, { synthName = \ZZZClock; synthArgs = [\out, this.outputOnHardware(output: output), \bus, tempoBusses.at(slot), \add, -0.5]; }); // Korg uses 48 pulses per quarter note if(type == \korgZeroFive, { synthName = \ZZZClockKorg; synthArgs = [\out, this.outputOnHardware(output: output), \bus, tempoBusses.at(slot), \mul, 0.5]; }); // some devices react to the range of minus five to plus five if(type == \korgMinusFiveFive, { synthName = \ZZZClockKorg; synthArgs = [\out, this.outputOnHardware(output: output), \bus, tempoBusses.at(slot), \add, -0.5]; }); clockFunc = { var synth; if(replace, { synth = Synth.replace(clocks.at(output), synthName, synthArgs, sameID: true).onFree({ clocks.removeAt(output); clocksTempoMap.removeAt(output); }); },{ synth = Synth(synthName, synthArgs, target: this.deviceGroup).onFree({ clocks.removeAt(output); clocksTempoMap.removeAt(output); }); }); clocks.add(output -> synth); clocksTempoMap.add(output -> slot); }; // add Synth on next beat if(playType == \beat, { tempos.at(slot).play(clockFunc, quant: quant); }); // add Synth on next bar if(playType == \bar, { tempos.at(slot).playNextBar(clockFunc); }); } removeClock{ |output, playType = \beat, quant = 1| var removeFunc; if(output.isNil || output.isInteger.not || output < 1, { ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1).").throw; }); if(clocks.at(output).isNil, { ZZZError("ZZZDevice: There is no clock at output "++output++" to remove.").throw; }); if(playType == \beat, { tempos.at(clocksTempoMap.at(output)).play({clocks.at(output).free}, quant: quant); }); if(playType == \bar, { tempos.at(clocksTempoMap.at(output)).playNextBar({clocks.at(output).free}); }); } setClockTempo{ |output, slot, playType = \beat, quant = 1, type = \zeroFive| this.addClock(output: output, slot: slot, playType: playType, quant: quant, replace: true, type: type); } addGate{ |output, input = 1.0| if(output.isNil || output.isInteger.not, { ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1).").throw; }); if(output < 1, { ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1).").throw; }); if(input.isNil || input.isFloat.not, { ZZZError("ZZZDevice: The provided value for input is not valid (needs to be 0.0 < input <= 1.0).").throw; }); if(input < 0.0 || input > 1.0, { ZZZError("ZZZDevice: The provided value for input is not valid (needs to be 0.0 < input <= 1.0).").throw; }); if(gates.at(output).notNil, { ZZZError("ZZZDevice: Cannot add gate on output "++ output ++". There is a "++gates.at(output).defName++" already.").throw; }); gateBusses.add(output -> Bus.control()); if(super.server.hasShmInterface,{ gateBusses.at(output).setSynchronous(input); },{ gateBusses.at(output).set(input); }); gates.add( output -> Synth(\ZZZGate, [\out, this.outputOnHardware(output: output), \bus, gateBusses.at(output)]).onFree({ gateBusses.removeAt(output); gates.removeAt(output); }) ); } removeGate{ |output| if(output.isNil || output.isInteger.not, { ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1).").throw; }); if(output < 1, { ZZZError("ZZZDevice: The provided output number is not valid (needs to be >= 1).").throw; }); if(gates.at(output).isNil, { ZZZError("ZZZDevice: There is no gate at the provided output "++output).throw; }); gates.at(output).free; } postAll{ this.postClocks; this.postGates; this.postOuts; this.postTempos; } postClocks{ ("Clocks").postln; ("------").postln; ("output -> tempo slot (value)").postln; clocks.keysValuesDo({|key, value| if(value.isRunning, { if(super.server.hasShmInterface,{ (key.asString++" -> "++clocksTempoMap.at(key)++" ("++tempoBusses.at(clocksTempoMap.at(key)).getSynchronous++")").postln; },{ tempoBusses.at(clocksTempoMap.at(key)).get({ |values| (key.asString++" -> "++clocksTempoMap.at(key)++" ("++values++")").postln; }); }); },{ (key.asString++" -> "++clocksTempoMap.at(key)++" ("++Nil++")").postln; }); }); ("------").postln; } postGates{ ("Gates").postln; ("-----").postln; ("output -> value").postln; gates.keysValuesDo({|key, value| if(value.isRunning, { if(super.server.hasShmInterface,{ (key.asString++" -> "++gateBusses.at(key).getSynchronous).postln; },{ gateBusses.at(key).get({ |values| (key.asString++" -> "++values).postln; }); }); },{ (key.asString++" -> "++Nil).postln; }); }); ("-----").postln; } postOuts{ ("Outputs").postln; ("-------").postln; ("hardware channel -> server output bus channel").postln; outs.keysValuesDo({|key, value| (key.asString++" -> "++value.asString).postln; }); ("-------").postln; } postTempos{ ("Tempos").postln; ("------").postln; ("slot -> speed").postln; tempos.keysValuesDo({|key, value| if(value.isRunning, { if(super.server.hasShmInterface,{ (key.asString++" -> "++tempoBusses.at(key).getSynchronous).postln; },{ tempoBusses.at(key).get({ |values| (key.asString++" -> "++values).postln; }); }); },{ (key.asString++" -> "++Nil).postln; }); }); ("------").postln; } }