Permalink
Cannot retrieve contributors at this time
| ( | |
| var version = "2.0"; | |
| var defaultNumTaps = 10; | |
| var moduleInputRefDelimiter = $*; | |
| var obsoleteModuleInputRefDelimiter = $/; | |
| var moduleOutputRefDelimiter = $/; | |
| var moduleParameterRefDelimiter = $.; | |
| var moduleVisualRefDelimiter = $=; | |
| var moduleSampleSlotRefDelimiter = $:; | |
| var init = { |opts| | |
| var instance = (); | |
| var moduleDefs, autostartServer, trace, group, inBus, outBus, numTaps; | |
| # moduleDefs, autostartServer, trace, group, inBus, outBus, numTaps = parseOpts.value(opts ? ()); | |
| "R is initializing... ".postln; | |
| instance[\trace] = trace; | |
| instance[\group] = group; | |
| instance[\inBus] = inBus; | |
| instance[\outBus] = outBus; | |
| instance[\numTaps] = numTaps; | |
| instance[\moduleDefs] = moduleDefs ? []; | |
| instance[\moduleDefs] = instance[\moduleDefs]++coreModules++stdModules; | |
| instance[\moduleDefs] = validateAndExpandModuleDefs.value(instance[\moduleDefs]); | |
| instance[\macros] = (); | |
| instance[\modules] = []; | |
| if (Server.default.serverRunning) { | |
| { | |
| initServerResources.value(instance); | |
| "R initialized successfully.".postln; | |
| }.forkIfNeeded; | |
| } { | |
| if (autostartServer) { | |
| postAutobootingServerInformation.value; | |
| Server.default.waitForBoot { | |
| initServerResources.value(instance); | |
| "R initialized successfully.".postln; | |
| }; | |
| } { | |
| "R initialization failed.".postln; | |
| postServerNotRunningError.value; | |
| }; | |
| }; | |
| instance; | |
| }; | |
| var parseOpts = { |opts| | |
| [ | |
| opts[\moduleDefs], | |
| opts[\autostartServer] ? true, | |
| opts[\trace] ? false, | |
| opts[\group] ? Server.default.defaultGroup, | |
| opts[\inBus] ? Server.default.options.numOutputBusChannels, | |
| opts[\outBus] ? 0, | |
| opts[\numTaps] ? defaultNumTaps | |
| ] | |
| }; | |
| var validateAndExpandModuleDefs = { |moduleDefs| | |
| moduleDefs.collect { |moduleDef| | |
| var expandedModuleDef; | |
| var description; | |
| var experimental; | |
| var inputs; | |
| var inputsDocSection; | |
| var outputs; | |
| var outputsDocSection; | |
| var params; | |
| var paramsDocSection; | |
| var visuals; | |
| var visualsDocSection; | |
| var sampleSlots; | |
| var sampleSlotsDocSection; | |
| expandedModuleDef = (); | |
| if (moduleDef[\name].notNil) { | |
| expandedModuleDef[\name] = moduleDef[\name]; | |
| } { | |
| Error("Module definition requires a name").throw; | |
| }; | |
| experimental = moduleDef[\experimental]; | |
| if (experimental.notNil) { | |
| expandedModuleDef[\experimental] = experimental.value; | |
| }; | |
| description = moduleDef[\description]; | |
| if (description.notNil) { | |
| expandedModuleDef[\description] = description.value; | |
| }; | |
| if (moduleDef[\ugenGraphFunc].notNil) { | |
| expandedModuleDef[\ugenGraphFunc] = moduleDef[\ugenGraphFunc]; | |
| } { | |
| Error("Module definition requires an ugenGraphFunc").throw; | |
| }; | |
| inputs = moduleDef[\inputs]; | |
| if (inputs.notNil) { | |
| // TODO: validate against ugenGraphFunc | |
| expandedModuleDef[\inputs] = inputs.value; | |
| }; | |
| inputsDocSection = moduleDef[\inputsDocSection]; | |
| if (inputsDocSection.notNil) { | |
| expandedModuleDef[\inputsDocSection] = inputsDocSection.value; | |
| }; | |
| outputs = moduleDef[\outputs]; | |
| if (outputs.notNil) { | |
| // TODO: validate against ugenGraphFunc | |
| expandedModuleDef[\outputs] = outputs.value; | |
| }; | |
| outputsDocSection = moduleDef[\outputsDocSection]; | |
| if (outputsDocSection.notNil) { | |
| expandedModuleDef[\outputsDocSection] = outputsDocSection.value; | |
| }; | |
| params = moduleDef[\params]; | |
| if (params.notNil) { | |
| // TODO: validate against ugenGraphFunc | |
| expandedModuleDef[\params] = params.value; | |
| }; | |
| paramsDocSection = moduleDef[\paramsDocSection]; | |
| if (paramsDocSection.notNil) { | |
| expandedModuleDef[\paramsDocSection] = paramsDocSection.value; | |
| }; | |
| visuals = moduleDef[\visuals]; | |
| if (visuals.notNil) { | |
| // TODO: validate against ugenGraphFunc | |
| expandedModuleDef[\visuals] = visuals.value; | |
| }; | |
| visualsDocSection = moduleDef[\visualsDocSection]; | |
| if (visualsDocSection.notNil) { | |
| expandedModuleDef[\visualsDocSection] = visualsDocSection.value; | |
| }; | |
| sampleSlots = moduleDef[\sampleSlots]; | |
| if (sampleSlots.notNil) { | |
| // TODO: validate against ugenGraphFunc | |
| expandedModuleDef[\sampleSlots] = sampleSlots.value; | |
| }; | |
| sampleSlotsDocSection = moduleDef[\sampleSlotsDocSection]; | |
| if (sampleSlotsDocSection.notNil) { | |
| expandedModuleDef[\sampleSlotsDocSection] = sampleSlotsDocSection.value; | |
| }; | |
| expandedModuleDef; | |
| }; | |
| }; | |
| var initServerResources = { |instance| | |
| if (instance[\trace]) { Char.nl.post; }; | |
| sendSynthDefsToServer.value(instance[\moduleDefs], instance[\trace]); // TODO: it should be possible to decrease r init time by sending synthdefs lazily to server (add this as an option?) | |
| instance[\topGroup] = Group.tail(instance[\group]); | |
| Server.default.sync; | |
| instance[\taps] = Array.fill(instance[\numTaps]) { (bus: Bus.control, active: false) }; | |
| }; | |
| var sendSynthDefsToServer = { |moduleDefs, trace| | |
| moduleDefs do: { |moduleDef| | |
| sendModuleSynthDefToServer.value(moduleDef, trace); | |
| }; | |
| SynthDef(\r_tapout, { |in, out| | |
| Out.kr(out, A2K.kr(In.ar(in))); | |
| }).add; | |
| SynthDef(\r_patch, { |in, out| | |
| Out.ar(out, In.ar(in, 1)); | |
| }).add; | |
| SynthDef(\r_patch_feedback, { |in, out| | |
| Out.ar(out, InFeedback.ar(in, 1)); | |
| }).add; | |
| }; | |
| var newCommand = { |self, name, kind| | |
| var moduleDef = lookupModuleDefByKind.value(self, kind.asSymbol); | |
| /* | |
| inline function optimization | |
| */ | |
| var spec; | |
| var group; | |
| var inputPatchCordGroup; | |
| var processingGroup; | |
| var tapGroup; | |
| var inbusses; | |
| var outbusses; | |
| var visualbusses; | |
| var sampleSlotBuffers; | |
| var module; | |
| var finalizeFunc; | |
| if (moduleDef.isNil) { | |
| postUnableToCreateDueToInvalidModuleTypeError.value(name, kind); | |
| } { | |
| if (lookupModuleByName.value(self, name).notNil) { | |
| postUnableToCreateDueToAlreadyExistingModuleError.value(name); | |
| } { | |
| if (isValidModuleName.value(name)) { | |
| /* var */ spec = getModuleDefSpec.value(moduleDef); | |
| /* var */ group = Group.tail(self[\topGroup]); | |
| /* var */ inputPatchCordGroup = spec[\inputs].notEmpty.if { Group.tail(group) }; | |
| /* var */ processingGroup = Group.tail(group); | |
| /* var */ tapGroup = Group.tail(group); | |
| /* var */ inbusses = spec[\inputs].collect { |input| input -> Bus.audio }; | |
| /* var */ outbusses = spec[\outputs].collect { |output| output -> Bus.audio }; | |
| /* var */ visualbusses = spec[\visuals].collect { |visual| visual -> Bus.control }; | |
| /* var */ sampleSlotBuffers = spec[\sampleSlots].collect { |sampleSlotAssoc| | |
| var sampleSlotName = sampleSlotAssoc.key; | |
| var sampleSlotSpec = sampleSlotAssoc.value; | |
| sampleSlotName -> sampleSlotSpec[\Channels].collect { |channelCount| | |
| var buffer = Buffer.alloc(numChannels: channelCount, numFrames: 1); // TODO: allocating one frame to suppress warning message | |
| if (self[\trace]) { | |
| "buffer % created for sample slot % (channel count %)".format(buffer.bufnum, (name++":"++sampleSlotName).quote, channelCount); | |
| }; | |
| channelCount -> buffer; | |
| }; | |
| }; | |
| /* var */ module = ( | |
| name: name.asSymbol, | |
| kind: kind.asSymbol, | |
| moduleDef: moduleDef, | |
| serverContext: ( | |
| group: group, | |
| inputPatchCordGroup: inputPatchCordGroup, | |
| processingGroup: processingGroup, | |
| tapGroup: tapGroup, | |
| inbusses: inbusses, | |
| outbusses: outbusses, | |
| visualbusses: visualbusses, | |
| sampleSlotBuffers: sampleSlotBuffers | |
| ), | |
| inputPatchCords: asDict.value( | |
| spec[\inputs].collect { |input| input -> () } // TODO: better to bundle this together with inbusses (?) | |
| ), | |
| ); | |
| /* var */ finalizeFunc = { | |
| module[\synth] = Synth( | |
| getModuleDefSynthDefName.value(moduleDef), | |
| getDefaultModuleSynthArgs.value( | |
| moduleDef, | |
| inbusses, | |
| outbusses, | |
| visualbusses, | |
| ( | |
| mainInBus: self[\inBus], | |
| mainOutBus: self[\outBus] | |
| ), | |
| sampleSlotBuffers | |
| ), | |
| target: processingGroup | |
| ); | |
| self[\modules] = self[\modules].add(module); | |
| }; | |
| if (module[\serverContext][\sampleSlotBuffers].notEmpty) { | |
| forkIfNeeded { // server.sync required to suppress "Buffer UGen: no buffer data" message for SynthDefs using samples | |
| Server.default.sync; | |
| finalizeFunc.value; | |
| }; | |
| } { | |
| finalizeFunc.value; | |
| }; | |
| } { | |
| postModuleNameIsInvalidError.value(name); | |
| }; | |
| }; | |
| }; | |
| }; | |
| var connectCommand = { |self, moduleOutputRef, moduleInputRef| | |
| /* | |
| inline function optimization | |
| */ | |
| var sourceModuleRef, outputRef; | |
| var destModuleRef, inputRef; | |
| var sourceModule, destModule; | |
| var sourceModuleIndex, destModuleIndex; | |
| if (isValidModuleOutputName.value(moduleOutputRef).not) { | |
| postInvalidModuleOutputNameError.value(moduleOutputRef); | |
| } { | |
| if (isValidModuleInputName.value(moduleInputRef).not) { | |
| postInvalidModuleInputNameError.value(moduleInputRef); | |
| } { | |
| if (isConnected.value(self, moduleOutputRef, moduleInputRef)) { | |
| postInputAndOutputIsConnectedError.value(moduleOutputRef, moduleInputRef); | |
| } { | |
| /* | |
| var sourceModuleRef, outputRef; | |
| var destModuleRef, inputRef; | |
| var sourceModule, destModule; | |
| var sourceModuleIndex, destModuleIndex; | |
| */ | |
| # sourceModuleRef, outputRef = parseModuleOutputReference.value(moduleOutputRef); | |
| # destModuleRef, inputRef = parseModuleInputReference.value(moduleInputRef); | |
| sourceModule = lookupModuleByName.value(self, sourceModuleRef); | |
| if (sourceModule.isNil) { | |
| postModuleNotFoundError.value(sourceModuleRef, self[\modules]); | |
| } { | |
| sourceModuleIndex = self[\modules].indexOf(sourceModule); | |
| destModule = lookupModuleByName.value(self, destModuleRef); | |
| if (destModule.isNil) { | |
| postModuleNotFoundError.value(destModuleRef, self[\modules]); | |
| } { | |
| destModuleIndex = self[\modules].indexOf(destModule); | |
| if (moduleHasOutputNamed.value(sourceModule, outputRef).not) { | |
| postInvalidModuleOutputError.value(sourceModule, outputRef); | |
| } { | |
| if (moduleHasInputNamed.value(destModule, inputRef).not) { | |
| postInvalidModuleInputError.value(destModule, inputRef); | |
| } { | |
| destModule[\inputPatchCords][inputRef.asSymbol][moduleOutputRef.asSymbol] = Synth( | |
| if (sourceModuleIndex >= destModuleIndex, \r_patch_feedback, \r_patch), | |
| [ | |
| \in, sourceModule[\serverContext][\outbusses].detect { |busAssoc| busAssoc.key == outputRef.asSymbol }.value, | |
| \out, destModule[\serverContext][\inbusses].detect { |busAssoc| busAssoc.key == inputRef.asSymbol }.value, | |
| \level, 1.0 | |
| ], | |
| destModule[\serverContext][\inputPatchCordGroup], | |
| \addToTail | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| var disconnectCommand = { |self, moduleOutputRef, moduleInputRef| | |
| disconnect.value(self, moduleOutputRef, moduleInputRef); | |
| }; | |
| var disconnect = { |self, moduleOutputRef, moduleInputRef| | |
| var sourceModuleRef, outputRef; | |
| var destModuleRef, inputRef; | |
| var sourceModule, destModule; | |
| if (isValidModuleOutputName.value(moduleOutputRef).not) { | |
| postInvalidModuleOutputNameError.value(moduleOutputRef); | |
| } { | |
| if (isValidModuleInputName.value(moduleInputRef).not) { | |
| postInvalidModuleInputNameError.value(moduleInputRef); | |
| } { | |
| # sourceModuleRef, outputRef = parseModuleOutputReference.value(moduleOutputRef); // the fact that this module and output exists is inferred due to isConnected call | |
| # destModuleRef, inputRef = parseModuleInputReference.value(moduleInputRef); // the fact that this module and input exists is inferred due to isConnected call | |
| sourceModule = lookupModuleByName.value(self, sourceModuleRef); | |
| if (sourceModule.isNil) { | |
| postModuleNotFoundError.value(sourceModuleRef, self[\modules]); | |
| } { | |
| destModule = lookupModuleByName.value(self, destModuleRef); | |
| if (destModule.isNil) { | |
| postModuleNotFoundError.value(destModuleRef, self[\modules]); | |
| } { | |
| if (moduleHasOutputNamed.value(sourceModule, outputRef).not) { | |
| postInvalidModuleOutputError.value(sourceModule, outputRef); | |
| } { | |
| if (moduleHasInputNamed.value(destModule, inputRef).not) { | |
| postInvalidModuleInputError.value(destModule, inputRef); | |
| } { | |
| if (isConnected.value(self, moduleOutputRef, moduleInputRef)) { | |
| destModule = lookupModuleByName.value(self, destModuleRef); | |
| destModule[\inputPatchCords][inputRef.asSymbol][moduleOutputRef.asSymbol].free; | |
| destModule[\inputPatchCords][inputRef.asSymbol][moduleOutputRef.asSymbol] = nil; | |
| } { | |
| postInputAndOutputIsNotConnectedError.value(moduleOutputRef, moduleInputRef); | |
| }; | |
| }; | |
| }; | |
| }; | |
| }; | |
| }; | |
| }; | |
| }; | |
| var deleteCommand = { |self, moduleRef| | |
| if (isValidModuleName.value(moduleRef)) { | |
| deleteModule.value(self, moduleRef); | |
| } { | |
| postModuleNameIsInvalidError.value(moduleRef); | |
| }; | |
| }; | |
| var setCommand = { |self, moduleParameterRef, value| | |
| var moduleRef, parameterRef, module, spec; | |
| if (isValidModuleParameterName.value(moduleParameterRef).not) { | |
| postInvalidModuleParameterNameError.value(moduleParameterRef); | |
| } { | |
| # moduleRef, parameterRef = parseModuleParameterReference.value(moduleParameterRef); | |
| module = lookupModuleByName.value(self, moduleRef); | |
| if (module.isNil) { | |
| postModuleNotFoundError.value(moduleRef, self[\modules]); | |
| } { | |
| spec = getModuleDefSpec.value(module[\moduleDef]); | |
| if (spec[\parameters].includes(parameterRef.asSymbol)) { | |
| setModuleParam.value(module, parameterRef, value); | |
| } { | |
| postInvalidModuleParameterError.value(module, parameterRef, ) | |
| } | |
| }; | |
| }; | |
| }; | |
| var bulksetCommand = { |self, bundle| | |
| /* | |
| TODO | |
| if (isValidBundle.value(bundle).not) { | |
| postInvalidBundleError.value(name); | |
| } { | |
| */ | |
| Server.default.makeBundle(nil) { // TODO: consider udp package size limitations and bulksetCommand | |
| bundle.asString.split($ ).clump(2).do { |cmd, i| | |
| setCommand.value(self, cmd[0], cmd[1]); // TODO: use internal method? | |
| } | |
| }; | |
| /* | |
| }; | |
| */ | |
| }; | |
| var newmacroCommand = { |self, name, bundle| | |
| var macro; | |
| var bus; | |
| var moduleParameterRefs; | |
| if (isValidMacroName.value(name).not) { | |
| postInvalidMacroNameError.value(name); | |
| } { | |
| moduleParameterRefs = bundle.asString.split($ ); | |
| if (areValidMacroModuleParameters.value(moduleParameterRefs).not) { | |
| postInvalidMacroModuleParametersError.value(name, moduleParameterRefs); | |
| } { | |
| if (moduleParametersHaveConsistentControlSpecs.value(self, moduleParameterRefs).not) { | |
| postModuleParameterControlSpecsAreInconsistent.value(name, moduleParameterRefs); | |
| } { | |
| bus = Bus.control; | |
| macro = ( | |
| moduleParameterRefs: moduleParameterRefs, | |
| bus: bus, | |
| controlSpec: getControlSpecByModuleParameterRef.value(self, moduleParameterRefs.first) | |
| ); | |
| Server.default.makeBundle(nil) { | |
| macro[\moduleParameterRefs].do { |moduleParameterRef| | |
| var moduleRef, parameterRef, module, spec; | |
| # moduleRef, parameterRef = parseModuleParameterReference.value(moduleParameterRef); | |
| module = lookupModuleByName.value(self, moduleRef); | |
| if (module.isNil) { // TODO: to be handled above | |
| postModuleNotFoundError.value(moduleRef, self[\modules]); | |
| } { | |
| spec = getModuleDefSpec.value(module[\moduleDef]); | |
| if (spec[\parameters].includes(parameterRef.asSymbol)) { // TODO: to be handled above | |
| mapModuleParam.value(module, parameterRef, bus); | |
| } { | |
| postInvalidModuleParameterError.value(module, parameterRef) | |
| } | |
| } | |
| } | |
| }; | |
| self[\macros][name.asSymbol] = macro; | |
| }; | |
| }; | |
| }; | |
| }; | |
| var deletemacroCommand = { |self, name| | |
| var macros; | |
| if (isValidMacroName.value(name).not) { | |
| postInvalidMacroNameError.value(name); | |
| } { | |
| macros = self[\macros]; | |
| macros[name.asSymbol][\bus].free; | |
| macros[name.asSymbol][\moduleParameterRefs].do { | |
| // TODO: unmap control | |
| // Reset to current bus value or default module def parameter controlspec value - or is this needed at all? | |
| }; | |
| macros[name.asSymbol] = nil; | |
| }; | |
| }; | |
| var macrosetCommand = { |self, name, value| | |
| if (isValidMacroName.value(name).not) { | |
| postInvalidMacroNameError.value(name); | |
| } { | |
| // TODO: validate presence of macro | |
| // TODO: controlSpecs are not regarded here! only allow macro creation of params with same controlSpec in newmacro and constrain here? | |
| // TODO: map using the ControlSpec! | |
| self[\macros][name.asSymbol][\bus].set(value); | |
| }; | |
| }; | |
| var readsampleCommand = { |self, moduleSampleSlotRef, path| | |
| // TODO: this.lookupSampleSlot(...) | |
| var moduleRef, sampleSlotRef; | |
| var module; | |
| /* | |
| inline function optimization | |
| */ | |
| var sampleSlotBuffers; | |
| var channelCount; | |
| var buffer; | |
| if (isValidModuleSampleSlotName.value(moduleSampleSlotRef).not) { | |
| postInvalidModuleSampleSlotNameError.value(moduleSampleSlotRef); | |
| } { | |
| # moduleRef, sampleSlotRef = parseModuleSampleSlotReference.value(moduleSampleSlotRef); | |
| module = lookupModuleByName.value(self, moduleRef); | |
| if (module.isNil) { | |
| postModuleNotFoundError.value(moduleRef, self[\modules]); | |
| } { | |
| if (moduleHasSampleSlotNamed.value(module, sampleSlotRef).not) { | |
| postInvalidModuleSampleSlotError.value(module, sampleSlotRef); | |
| } { | |
| if (File.exists(path)) { | |
| /* var */ sampleSlotBuffers = lookupSampleSlotBuffersByName.value(module, sampleSlotRef); // TODO: naming, for clarity, this is not the complete sampleslotbuffers object, but only for one sampleslot | |
| // TODO: ensure that path is a soundfile? -- done below? | |
| /* var */ channelCount = getSoundFileNumChannels.value(path); | |
| if (channelCount.isNil) { | |
| postFileIsNotASoundFileError.value(path); | |
| } { | |
| if (sampleSlotBuffers.keys.includes(channelCount)) { // TODO: moduleSampleSlotSupportsChannelCount.value(channelCount) | |
| /* var */ buffer = sampleSlotBuffers[channelCount]; | |
| buffer.allocRead(path); | |
| forkIfNeeded { | |
| Server.default.sync; | |
| buffer.updateInfo(path); | |
| Server.default.sync; | |
| if (self[\trace]) { | |
| "sample % (% channels) loaded into sample slot % (buffer %)" | |
| .format(path.quote, channelCount, (module[\name].asString++":"++sampleSlotRef).quote, buffer.bufnum, moduleRef).inform; | |
| }; | |
| setModuleSampleSlotChannelCount.value(module, sampleSlotRef, channelCount) | |
| }; | |
| } { | |
| postModuleSampleSlotDoesNotSupportChannelCountError.value(module, sampleSlotRef, channelCount); | |
| } | |
| } | |
| } { | |
| postFileDoesNotExistError.value(path); | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| /* | |
| TODO | |
| how to know what buffer (channel count) to use when writing? | |
| writesampleCommand { |moduleSampleSlotRef, path| // TODO: factor out Command prefix | |
| }; | |
| */ | |
| var tapoutputCommand = { |self, index, moduleOutputRef| | |
| if (isValidModuleOutputName.value(moduleOutputRef).not) { | |
| postInvalidModuleOutputNameError.value(moduleOutputRef); | |
| } { | |
| ifTapIndexWithinBoundsDo.value(self, index) { | |
| var moduleRef, outputRef; | |
| var module; | |
| /* | |
| inline function optimization | |
| */ | |
| var moduleOutputBus; | |
| var targetGroup; | |
| var tap; | |
| // TODO: validate output ref | |
| # moduleRef, outputRef = parseModuleOutputReference.value(moduleOutputRef); | |
| module = lookupModuleByName.value(self, moduleRef); | |
| if (module.isNil) { | |
| postModuleNotFoundError.value(moduleRef, self[\modules]); | |
| } { | |
| if (moduleHasOutputNamed.value(module, outputRef).not) { | |
| postInvalidModuleOutputError.value(module, outputRef); | |
| } { | |
| /* | |
| TODO: test that this work and then remove | |
| var moduleServerContext = module[\serverContext]; // TODO: extract this elsewhere too | |
| var moduleOutputBus = moduleServerContext[\outbusses].detect { |busAssoc| busAssoc.key == outputRef.asSymbol }.value; // TODO: DRY | |
| var targetGroup = moduleServerContext[\tapGroup]; | |
| */ | |
| /* var */ moduleOutputBus = lookupModuleOutputBus.value(module, outputRef); | |
| /* var */ targetGroup = lookupModuleTapGroup.value(module); | |
| /* var */ tap = self[\taps][index]; | |
| if (tapIsSet.value(self, index)) { | |
| clearTap.value(self, index); | |
| }; | |
| tap[\synth] = Synth( | |
| defName: \r_tapout, | |
| args: [\in, moduleOutputBus, \out, tap[\bus]], | |
| target: targetGroup, | |
| addAction: \addToHead | |
| ); | |
| tap[\moduleOutputRef] = moduleOutputRef; | |
| tap[\active] = true; | |
| }; | |
| }; | |
| }; | |
| }; | |
| }; | |
| var tapclearCommand = { |self, index| | |
| ifTapIndexWithinBoundsDo.value(self, index) { | |
| clearTap.value(self, index); | |
| }; | |
| }; | |
| var getTapBus = { |self, tapIndex| // TODO: this is currently public API | |
| self[\taps][tapIndex][\bus] | |
| }; | |
| var getVisualBus = { |self, moduleVisualRef| // TODO: this is currently public API | |
| var moduleRef, visualRef; | |
| var module; | |
| /* | |
| inline function optimization | |
| */ | |
| var serverContext, visualbusses, bus; | |
| if (isValidModuleVisualName.value(moduleVisualRef).not) { | |
| postInvalidModuleVisualNameError.value(moduleVisualRef); | |
| } { | |
| # moduleRef, visualRef = parseModuleVisualReference.value(moduleVisualRef); | |
| module = lookupModuleByName.value(self, moduleRef); | |
| if (module.isNil) { | |
| postModuleNotFoundError.value(moduleRef); | |
| } { | |
| if (moduleHasVisualNamed.value(module, visualRef).not) { | |
| postInvalidModuleVisualError.value(module, visualRef) | |
| } { | |
| /* var serverContext, visualbusses, bus; */ | |
| serverContext = module[\serverContext]; | |
| visualbusses = serverContext[\visualbusses]; | |
| bus = visualbusses.detect { |busAssoc| busAssoc.key == visualRef.asSymbol }.value; | |
| bus | |
| }; | |
| }; | |
| }; | |
| }; | |
| /* Parsing / constructing references */ | |
| var parseModuleParameterReference = { |reference| | |
| reference.asString.split(moduleParameterRefDelimiter); | |
| }; | |
| var constructModuleParameterReference = { |moduleRef, parameterRef| | |
| (moduleRef.asString++moduleParameterRefDelimiter.asString++parameterRef.asString).asSymbol; | |
| }; | |
| var parseModuleInputReference = { |reference| | |
| var destModuleRef, input; | |
| if (reference.asString.includes(obsoleteModuleInputRefDelimiter)) { | |
| "% as delimiter for module input references is deprecated. Use % instead, like so: %.".format(obsoleteModuleInputRefDelimiter.asString.quote, moduleInputRefDelimiter.asString.quote, reference.asString.split(obsoleteModuleInputRefDelimiter).join(moduleInputRefDelimiter)).inform; | |
| # destModuleRef, input = reference.asString.split(obsoleteModuleInputRefDelimiter); | |
| } { | |
| # destModuleRef, input = reference.asString.split(moduleInputRefDelimiter); | |
| }; | |
| [destModuleRef, input]; | |
| }; | |
| var constructModuleInputReference = { |moduleRef, inputRef| | |
| (moduleRef.asString++moduleInputRefDelimiter.asString++inputRef.asString).asSymbol; | |
| }; | |
| var parseModuleOutputReference = { |reference| | |
| reference.asString.split(moduleOutputRefDelimiter); | |
| }; | |
| var constructModuleOutputReference = { |moduleRef, outputRef| | |
| (moduleRef.asString++moduleOutputRefDelimiter.asString++outputRef.asString).asSymbol; | |
| }; | |
| var parseModuleVisualReference = { |reference| | |
| reference.asString.split(moduleVisualRefDelimiter); | |
| }; | |
| var parseModuleSampleSlotReference = { |reference| | |
| reference.asString.split(moduleSampleSlotRefDelimiter); | |
| }; | |
| /* Guts */ | |
| var lookupModuleOutputBus = { |module, outputRef| | |
| var moduleServerContext = module[\serverContext]; | |
| moduleServerContext[\outbusses].detect { |busAssoc| busAssoc.key == outputRef.asSymbol }.value; | |
| }; | |
| var lookupModuleTapGroup = { |module| | |
| module[\serverContext][\tapGroup]; | |
| }; | |
| var getSoundFileNumChannels = { |path| | |
| var numChannels; | |
| var soundFile = SoundFile.openRead(path); | |
| // TODO: ensure that path is a soundfile | |
| if (soundFile.notNil) { | |
| numChannels = soundFile.numChannels; | |
| soundFile.close; | |
| }; | |
| numChannels; | |
| }; | |
| var lookupSampleSlotBuffersByName = { |module, sampleSlotName| | |
| var sampleSlotBufferDict = asDict.value(module[\serverContext][\sampleSlotBuffers]); | |
| asDict.value(sampleSlotBufferDict[sampleSlotName.asSymbol]); | |
| }; | |
| /* | |
| TODO | |
| lookupSampleSlot { |moduleSampleSlotRef| | |
| }; | |
| */ | |
| // TODO: consider exploding this in each occurence, or adopt this way of handling validations everywhere | |
| var ifTapIndexWithinBoundsDo = { |self, tapIndex, func| | |
| if ((0 <= tapIndex) and: (tapIndex < self[\numTaps])) { | |
| func.value; | |
| } { | |
| postTapIndexNotWithinBoundsError.value(self, tapIndex); | |
| }; | |
| }; | |
| var deleteModule = { |self, moduleRef| | |
| var moduleToDelete = lookupModuleByName.value(self, moduleRef); | |
| /* | |
| inline function optimization | |
| */ | |
| var serverContext; | |
| if (moduleToDelete.isNil) { | |
| postModuleNotFoundError.value(moduleRef, self[\modules]); | |
| } { | |
| /* var */ serverContext = moduleToDelete[\serverContext]; | |
| clearTapsForModule.value(self, moduleRef); | |
| disconnectModulePatchCords.value(self, moduleToDelete); | |
| moduleToDelete[\synth].free; | |
| serverContext[\group].free; | |
| serverContext[\inbusses] do: { |inputBusAssoc| inputBusAssoc.value.free }; | |
| serverContext[\outbusses] do: { |outputBusAssoc| outputBusAssoc.value.free }; | |
| serverContext[\visualbusses] do: { |visualBusAssoc| visualBusAssoc.value.free }; | |
| forkIfNeeded { | |
| Server.default.sync; // server.sync required to suppress "Buffer UGen: no buffer data" message for SynthDefs using samples | |
| serverContext[\sampleSlotBuffers] do: { |sampleSlotAssoc| | |
| var sampleSlotName = sampleSlotAssoc.key; | |
| var sampleSlotBuffers = sampleSlotAssoc.value; | |
| sampleSlotBuffers do: { |sampleSlotBufferAssoc| | |
| var channelCount = sampleSlotBufferAssoc.key; | |
| var buffer = sampleSlotBufferAssoc.value; | |
| var bufnum = buffer.bufnum; | |
| buffer.free; | |
| if (self[\trace]) { | |
| "buffer % for sample slot % (channel count %) freed".format(bufnum, (moduleToDelete[\name]++":"++sampleSlotName).quote, channelCount); | |
| }; | |
| } | |
| }; | |
| self[\modules].remove(moduleToDelete); | |
| } | |
| }; | |
| }; | |
| var clearTapsForModule = { |self, name| | |
| self[\taps].do { |tap, tapIndex| | |
| var moduleRef, output; | |
| # moduleRef, output = parseModuleOutputReference.value(tap[\moduleOutputRef]); | |
| if (moduleRef == name) { | |
| clearTap.value(self, tapIndex); | |
| }; | |
| }; | |
| }; | |
| var clearTap = { |self, tapIndex| | |
| var tap = self[\taps][tapIndex]; | |
| tap[\synth].free; | |
| tap[\synth] = nil; | |
| tap[\moduleOutputRef] = nil; | |
| }; | |
| var tapIsSet = { |self, index| | |
| self[\taps][index][\active]; | |
| }; | |
| var moduleHasParameterNamed = { |module, name| | |
| getModuleDefSpec.value(module[\moduleDef])[\parameters].any { |input| input == name.asSymbol } | |
| }; | |
| var moduleHasInputNamed = { |module, name| | |
| getModuleDefSpec.value(module[\moduleDef])[\inputs].any { |input| input == name.asSymbol } | |
| }; | |
| var moduleHasOutputNamed = { |module, name| | |
| getModuleDefSpec.value(module[\moduleDef])[\outputs].any { |output| output == name.asSymbol } | |
| }; | |
| var moduleHasVisualNamed = { |module, name| | |
| getModuleDefSpec.value(module[\moduleDef])[\visuals].any { |visual| visual == name.asSymbol } | |
| }; | |
| var moduleHasSampleSlotNamed = { |module, name| | |
| getModuleDefSpec.value(module[\moduleDef])[\sampleSlots].any { |sampleSlotAssoc| | |
| sampleSlotAssoc.key == name.asSymbol | |
| } | |
| }; | |
| var isConnected = { |self, moduleOutputRef, moduleInputRef| | |
| var destModuleRef, inputRef, destModule; | |
| /* | |
| inline function optimization | |
| */ | |
| var patchCordsForModuleInput; | |
| # destModuleRef, inputRef = parseModuleInputReference.value(moduleInputRef); | |
| destModule = lookupModuleByName.value(self, destModuleRef); | |
| if (destModule.isNil) { | |
| postModuleNotFoundError.value(destModuleRef, self[\modules]); | |
| false; // TODO: correct? throw error instead? | |
| } { | |
| /* var */ patchCordsForModuleInput = destModule[\inputPatchCords][inputRef.asSymbol]; | |
| if (patchCordsForModuleInput.isNil) { | |
| postInvalidModuleInputError.value(destModule, inputRef); | |
| false; // TODO: correct? throw error instead? | |
| } { | |
| patchCordsForModuleInput.keys.any { |patchedModuleOutput| patchedModuleOutput == moduleOutputRef.asSymbol }; | |
| } | |
| } | |
| }; | |
| var disconnectModulePatchCords = { |self, moduleToDisconnect| | |
| var spec = getModuleDefSpec.value(moduleToDisconnect[\moduleDef]); | |
| var moduleInputs = spec[\inputs].collect { |input| | |
| constructModuleInputReference.value(moduleToDisconnect[\name], input); | |
| }; | |
| var moduleOutputs = spec[\outputs].collect { |output| | |
| constructModuleOutputReference.value(moduleToDisconnect[\name], output); | |
| }; | |
| [getAllModuleOutputs.value(self), moduleInputs].allTuples.select { |tuple| | |
| isConnected.value(self, tuple[0], tuple[1]); | |
| } ++ [moduleOutputs, getAllModuleInputs.value(self)].allTuples.select { |tuple| | |
| isConnected.value(self, tuple[0], tuple[1]); | |
| }.do { |tuple| | |
| disconnect.value(self, tuple[0], tuple[1]); | |
| } | |
| }; | |
| var getAllModuleOutputs = { |self| | |
| self[\modules] | |
| .collect { |module| | |
| var spec = getModuleDefSpec.value(module[\moduleDef]); | |
| spec[\outputs].collect { |output| | |
| constructModuleOutputReference.value(module[\name], output); | |
| }.asArray | |
| }.flatten | |
| }; | |
| var getAllModuleInputs = { |self| | |
| self[\modules] | |
| .collect { |module| | |
| var spec = getModuleDefSpec.value(module[\moduleDef]); | |
| spec[\inputs].collect { |input| | |
| constructModuleInputReference.value(module[\name], input); | |
| }.asArray | |
| }.flatten | |
| }; | |
| var isValidModuleInputName = { |ref| | |
| var module, input; | |
| ref = ref.asString; | |
| if ( | |
| ref.includes(moduleInputRefDelimiter) | |
| or: | |
| ref.includes(obsoleteModuleInputRefDelimiter) // provide backwards copatibility | |
| ) { | |
| # module, input = parseModuleInputReference.value(ref); | |
| isValidModuleName.value(module) and: isValidInOutputName.value(input); | |
| } { | |
| false | |
| }; | |
| }; | |
| var isValidModuleOutputName = { |ref| | |
| var module, output; | |
| ref = ref.asString; | |
| if (ref.includes(moduleOutputRefDelimiter)) { | |
| # module, output = parseModuleOutputReference.value(ref); | |
| isValidModuleName.value(module) and: isValidInOutputName.value(output); | |
| } { | |
| false | |
| }; | |
| }; | |
| var isValidModuleParameterName = { |ref| | |
| var module, parameter; | |
| ref = ref.asString; | |
| if (ref.includes(moduleParameterRefDelimiter)) { | |
| # module, parameter = parseModuleParameterReference.value(ref); | |
| isValidModuleName.value(module) and: isValidParameterName.value(parameter); | |
| } { | |
| false | |
| }; | |
| }; | |
| var isValidModuleVisualName = { |ref| | |
| var module, visual; | |
| ref = ref.asString; | |
| if (ref.includes(moduleVisualRefDelimiter)) { | |
| # module, visual = parseModuleVisualReference.value(ref); | |
| isValidModuleName.value(module) and: isValidVisualName.value(visual); | |
| } { | |
| false | |
| }; | |
| }; | |
| var isValidModuleSampleSlotName = { |ref| | |
| var module, sampleSlot; | |
| ref = ref.asString; | |
| if (ref.includes(moduleSampleSlotRefDelimiter)) { | |
| # module, sampleSlot = parseModuleParameterReference.value(ref); | |
| isValidModuleName.value(module) and: isValidSampleSlotName.value(sampleSlot); | |
| } { | |
| false | |
| }; | |
| }; | |
| var isValidMacroName = { |name| | |
| isValidName.value(name); | |
| }; | |
| var areValidMacroModuleParameters = { |moduleParameterRefs| | |
| moduleParameterRefs.every { |moduleParameterRef| | |
| isValidModuleParameterName.value(moduleParameterRef); | |
| }; | |
| }; | |
| var moduleParametersHaveConsistentControlSpecs = { |self, moduleParameterRefs| | |
| var firstControlSpec; | |
| var consistent; | |
| var i; | |
| if (moduleParameterRefs.size > 1 ) { | |
| consistent = true; | |
| i = 1; | |
| firstControlSpec = getControlSpecByModuleParameterRef.value(self, moduleParameterRefs.first); | |
| while { consistent and: (i < moduleParameterRefs.size) } { | |
| consistent = (firstControlSpec == getControlSpecByModuleParameterRef.value(self, moduleParameterRefs[i])); | |
| i = i + 1; | |
| }; | |
| consistent; | |
| } { | |
| true; | |
| }; | |
| }; | |
| var getControlSpecByModuleParameterRef = { |self, moduleParameterRef| | |
| var moduleRef, parameterRef; | |
| var module; | |
| # moduleRef, parameterRef = parseModuleParameterReference.value(moduleParameterRef); | |
| module = lookupModuleByName.value(self, moduleRef); | |
| getControlSpecForModuleParameter.value(module, parameterRef); | |
| }; | |
| var getControlSpecForModuleParameter = { |module, parameterRef| | |
| var paramControlSpecs = getModuleDefParamControlSpecs.value(module[\moduleDef]); | |
| paramControlSpecs[parameterRef]; | |
| }; | |
| var isValidModuleName = { |name| | |
| isValidName.value(name); | |
| }; | |
| var isValidInOutputName = { |name| | |
| isValidName.value(name); | |
| }; | |
| var isValidParameterName = { |name| | |
| isValidName.value(name); | |
| }; | |
| var isValidSampleSlotName = { |name| | |
| isValidName.value(name); | |
| }; | |
| var isValidVisualName = { |name| | |
| isValidName.value(name); | |
| }; | |
| var isValidName = { |name| | |
| name = name.asString; | |
| name.every { |char| | |
| // Range($a.ascii, $z.ascii).includes(char.ascii) or: Range($A.ascii, $Z.ascii).includes(char.ascii) or: Range($0.ascii, $9.ascii).includes(char.ascii) // TODO: file sc Range bug | |
| var char2 = char.ascii; | |
| (($a.ascii <= char2) and: (char2 <= $z.ascii)) or: (($A.ascii <= char2) and: (char2 <=$Z.ascii)) or: (($0.ascii <= char2) and: (char2 <= $9.ascii)) or: (char == $_) | |
| } | |
| }; | |
| var lookupModuleByName = { |self, name| | |
| name = name.asSymbol; | |
| self[\modules].detect { |module| module[\name] == name }; | |
| }; | |
| var getDefaultModuleSynthArgs = { |moduleDef, inbusses, outbusses, visualbusses, ioContext, sampleSlotBuffers| | |
| var defaults = getModuleDefDefaultSynthArgs.value(moduleDef, inbusses, outbusses, visualbusses, sampleSlotBuffers); | |
| case | |
| { moduleDef[\name] == 'SoundIn' } { | |
| defaults ++ [\internal_In, ioContext[\mainInBus]] | |
| } | |
| { moduleDef[\name] == 'SoundIn' } { | |
| defaults ++ [\internal_Out, ioContext[\mainOutBus]] | |
| } ?? defaults | |
| }; | |
| var lookupModuleDefByKind = { |self, kind| | |
| self[\moduleDefs].detect { |moduleDef| | |
| getModuleDefName.value(moduleDef) == kind | |
| } | |
| }; | |
| var free = { |self| | |
| deleteAllModules.value(self); | |
| self[\taps] do: { |tap| | |
| tap[\bus].free; | |
| }; | |
| self[\topGroup].free; | |
| }; | |
| var deleteAllModules = { |self| | |
| self[\modules].collect(_.name) do: { |modulename| // collect used to dup here since deleteCommand removes entries in modules | |
| deleteModule.value(self, modulename) | |
| }; | |
| }; | |
| var generateDocsForAllModules = { |self| | |
| generateDocsForModuleDefs.value(self[\moduleDefs].sortBy(\name)); | |
| }; | |
| var generateDocsForModuleDefs = { |moduleDefs, includeExperimentalModules=false| | |
| if (includeExperimentalModules) { | |
| moduleDefs | |
| } { | |
| moduleDefs.reject { |moduleDef| moduleDef[\experimental].asBoolean } | |
| }.collect { |moduleDef| | |
| generateDocsForModuleDef.value(moduleDef); | |
| }.join("\n") | |
| }; | |
| /* RModule stuff */ | |
| var setModuleParam = { |module, parameter, value| | |
| var moduleDef = module[\moduleDef]; | |
| var params = getModuleDefParams.value(moduleDef); | |
| var paramControlSpecs = getModuleDefParamControlSpecs.value(moduleDef); | |
| var param = params.detect { |param| param.key == parameter.asSymbol }; | |
| /* | |
| inline optimization | |
| */ | |
| var name, controlSpec, constrainedParamValue; | |
| if (param.notNil) { // TODO: DRY | |
| name = ("param_"++param.key.asString).asSymbol; | |
| controlSpec = paramControlSpecs[name]; | |
| constrainedParamValue = controlSpec.constrain(value); | |
| module[\synth].set(name, constrainedParamValue); | |
| } | |
| }; | |
| var mapModuleParam = { |module, parameter, bus| var moduleDef = module[\moduleDef]; | |
| var params = getModuleDefParams.value(moduleDef); | |
| var paramControlSpecs = getModuleDefParamControlSpecs.value(moduleDef); | |
| var param = params.detect { |param| param.key == parameter.asSymbol }; | |
| var name; | |
| if (param.notNil) { // TODO: DRY | |
| name = ("param_"++param.key.asString).asSymbol; | |
| module[\synth].map(name, bus); | |
| } | |
| }; | |
| var setModuleSampleSlotChannelCount = { |module, sampleSlotName, channelCount| | |
| var name; | |
| name = ("numchannels_"++sampleSlotName.asString).asSymbol; | |
| module[\synth].set(name, channelCount.asInteger); | |
| }; | |
| var generateDocsForModuleDef = { |moduleDef| | |
| "###" + moduleDef[\name].asString ++ if (moduleDef[\experimental].asBoolean, " (experimental)", "") ++ "\n" ++ | |
| "\n" ++ | |
| (moduleDef[\description] ? "[insert brief module description here]") ++ "\n" ++ | |
| "\n" ++ | |
| generateInputsDocSectionForModuleDef.value(moduleDef) ++ | |
| generateOutputsDocSectionForModuleDef.value(moduleDef) ++ | |
| generateParametersDocSectionForModuleDef.value(moduleDef) ++ | |
| generateVisualsDocSectionForModuleDef.value(moduleDef) ++ | |
| generateSampleSlotsDocSectionForModuleDef.value(moduleDef) | |
| }; | |
| var generateInputsDocSectionForModuleDef = { |moduleDef| | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var inputs = spec[\inputs]; | |
| if (inputs.notEmpty) { | |
| "- Inputs:" ++ | |
| "\n" ++ | |
| ( | |
| moduleDef[\inputsDocSection] ? | |
| ( | |
| inputs.collect { |input| | |
| "\t- " ++ generateInputDocSection.value(moduleDef, input) | |
| }.join($\n) | |
| ) | |
| ) ++ | |
| "\n" | |
| } { | |
| "" | |
| }; | |
| }; | |
| var generateInputDocSection = { |moduleDef, inputRef| | |
| var inputDescriptions = getModuleDefInputDescriptions.value(moduleDef); | |
| if (inputDescriptions.notNil) { | |
| var description = inputDescriptions[inputRef]; | |
| ("`" ++ inputRef.asString ++ "`") ++ if (description.notNil) {": " ++ description } { "" } | |
| } { | |
| ("`" ++ inputRef.asString ++ "`") | |
| }; | |
| }; | |
| var generateOutputsDocSectionForModuleDef = { |moduleDef| | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var outputs = spec[\outputs]; | |
| if (outputs.notEmpty) { | |
| "- Outputs:" ++ | |
| "\n" ++ | |
| ( | |
| moduleDef[\outputsDocSection] ? | |
| ( | |
| outputs.collect { |output| | |
| "\t- " ++ generateOutputDocSection.value(moduleDef, output) | |
| }.join($\n) | |
| ) | |
| ) ++ "\n" | |
| } { | |
| "" | |
| }; | |
| }; | |
| var generateOutputDocSection = { |moduleDef, outputRef| | |
| var outputDescriptions = getModuleDefOutputDescriptions.value(moduleDef); | |
| if (outputDescriptions.notNil) { | |
| var description = outputDescriptions[outputRef]; | |
| ("`" ++ outputRef.asString ++ "`") ++ if (description.notNil) {": " ++ description } { "" } | |
| } { | |
| ("`" ++ outputRef.asString ++ "`") | |
| }; | |
| }; | |
| var generateParametersDocSectionForModuleDef = { |moduleDef| | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var parameters = spec[\parameters]; | |
| if (parameters.notEmpty) { | |
| "- Parameters:" ++ | |
| "\n" ++ | |
| ( | |
| moduleDef[\paramsDocSection] ? | |
| ( | |
| parameters.collect { |parameter| | |
| "\t- " ++ generateParameterDocSection.value(moduleDef, parameter) | |
| }.join($\n) | |
| ) | |
| ) ++ "\n" | |
| } { | |
| "" | |
| }; | |
| }; | |
| var generateParameterDocSection = { |moduleDef, parameterRef| | |
| var description = getModuleDefParamDescriptions.value(moduleDef)[parameterRef]; | |
| ("`" ++ parameterRef.asString ++ "`") ++ if (description.notNil) {": " ++ description } { "" } | |
| }; | |
| var generateVisualsDocSectionForModuleDef = { |moduleDef| | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var visuals = spec[\visuals]; | |
| if (visuals.notEmpty) { | |
| "- Visuals:" ++ | |
| "\n" ++ | |
| ( | |
| moduleDef[\visualDocs] ? | |
| ( | |
| visuals.collect { |visual| | |
| //"\t- `" ++ visual ++ "`" // TODO: include description derived from ControlSpec | |
| "\t- " ++ generateVisualDocSection.value(moduleDef, visual) | |
| }.join($\n) | |
| ) | |
| ) ++ "\n" | |
| } { | |
| "" | |
| }; | |
| }; | |
| var generateVisualDocSection = { |moduleDef, visualRef| | |
| var visualDescriptions = getModuleDefVisualDescriptions.value(moduleDef); | |
| if (visualDescriptions.notNil) { | |
| var description = visualDescriptions[visualRef]; | |
| ("`" ++ visualRef.asString ++ "`") ++ if (description.notNil) {": " ++ description } { "" } | |
| } { | |
| ("`" ++ visualRef.asString ++ "`") | |
| }; | |
| }; | |
| var generateSampleSlotsDocSectionForModuleDef = { |moduleDef| | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var sampleSlotDetails = spec[\sampleSlots]; | |
| if (sampleSlotDetails.notEmpty) { | |
| var sampleSlots = sampleSlotDetails.collect { |sampleSlot| // TODO: duct tape fix of bug since getModuleDefSpec \sampleSlots does not return the same as other keys | |
| sampleSlot.key | |
| }; | |
| "- Sample Slots:" ++ | |
| "\n" ++ | |
| ( | |
| moduleDef[\sampleSlotDocs] ? | |
| ( | |
| // TODO: bugs out | |
| sampleSlots.collect { |sampleSlot| | |
| "\t- `" ++ sampleSlot ++ "`" // TODO: include description derived from ControlSpec | |
| // TODO "\t- " ++ generateSampleSlotDocSection.value(moduleDef, sampleSlot) | |
| }.join($\n) | |
| ) | |
| ) ++ "\n" | |
| } { | |
| "" | |
| }; | |
| }; | |
| var generateSampleSlotDocSection = { |moduleDef, sampleSlotRef| | |
| var sampleSlotDescriptions = getModuleDefSampleSlotDescriptions.value(moduleDef); | |
| if (sampleSlotDescriptions.notNil) { | |
| var description = sampleSlotDescriptions[sampleSlotRef]; | |
| ("`" ++ sampleSlotRef.asString ++ "`") ++ if (description.notNil) {": " ++ description } { "" } | |
| } { | |
| ("`" ++ sampleSlotRef.asString ++ "`") | |
| }; | |
| }; | |
| var sendModuleSynthDefToServer = { |moduleDef, trace| | |
| var name = getModuleDefName.value(moduleDef); | |
| var synthDefName = getModuleDefSynthDefName.value(moduleDef); | |
| var ugenGraphFunc = getModuleDefUgenGraphFunc.value(moduleDef); | |
| var lagTimes = getModuleDefSynthDefLagTimes.value(moduleDef); | |
| var metadata = getModuleDefSynthDefMetadata.value(moduleDef); | |
| if (trace) { | |
| "Module %: spawning SynthDef %...".format(name.asString.quote, synthDefName.asString.quote).postln; | |
| }; | |
| SynthDef(synthDefName, ugenGraphFunc, lagTimes, metadata: metadata).add; | |
| if (trace) { | |
| "...OK. SynthDef % was sent to server".format(synthDefName.asString.quote).postln; | |
| "".postln; | |
| }; | |
| }; | |
| var getModuleDefSynthDefName = { |moduleDef| | |
| ("r_" ++ moduleDef[\name].asString).asSymbol | |
| }; | |
| var getModuleDefSynthDefLagTimes = { |moduleDef| | |
| var paramLagTimes = getModuleDefParamLagTimes.value(moduleDef); | |
| moduleDef[\ugenGraphFunc].def.argNames.collect { |argName| | |
| if ( argName.asString.beginsWith("param_") ) { | |
| paramLagTimes[argName] | |
| } { nil } | |
| }; | |
| }; | |
| var getModuleDefSynthDefMetadata = { |moduleDef| | |
| (specs: getModuleDefParamControlSpecs.value(moduleDef)) | |
| }; | |
| var getModuleDefSpec = { |moduleDef| | |
| var sampleSlotDict = moduleDef[\sampleSlots] !? { |sampleSlots| asDict.value(sampleSlots) }; | |
| ( | |
| inputs: lookupModuleDefArgNameSuffixesPrefixedBy.value(moduleDef, 'in_'), | |
| outputs: lookupModuleDefArgNameSuffixesPrefixedBy.value(moduleDef, 'out_'), | |
| parameters: lookupModuleDefArgNameSuffixesPrefixedBy.value(moduleDef, 'param_'), | |
| visuals: lookupModuleDefArgNameSuffixesPrefixedBy.value(moduleDef, 'visual_'), | |
| sampleSlots: lookupModuleDefArgNameSuffixesPrefixedBy.value(moduleDef, 'numchannels_').collect { |sampleSlotName| | |
| var spec = sampleSlotDict[sampleSlotName]; | |
| sampleSlotName -> spec | |
| }, | |
| ) | |
| }; | |
| var lookupModuleDefArgNameSuffixesPrefixedBy = { |moduleDef, token| | |
| token = token.asString; | |
| moduleDef[\ugenGraphFunc].def.argNames.select { |argName| | |
| argName.asString.beginsWith(token) | |
| }.collect { |argName| | |
| argName.asString[token.size..].asSymbol | |
| } | |
| }; | |
| var getModuleDefDefaultSynthArgs = { |moduleDef, inbusses, outbusses, visualbusses, sampleSlotBuffers| | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var paramControlSpecs = getModuleDefParamControlSpecs.value(moduleDef); | |
| ( | |
| spec[\inputs].collect { |inputName| | |
| [ | |
| ("in_"++inputName.asString).asSymbol, | |
| inbusses.detect { |busAssoc| busAssoc.key == inputName.asSymbol }.value | |
| ] // TODO: report error when busAssoc not found | |
| } ++ | |
| spec[\outputs].collect { |outputName| | |
| [ | |
| ("out_"++outputName.asString).asSymbol, | |
| outbusses.detect { |busAssoc| busAssoc.key == outputName.asSymbol }.value | |
| ] // TODO: report error when busAssoc not found | |
| } ++ | |
| spec[\parameters].collect { |parameterName| | |
| var name = ("param_"++parameterName.asString).asSymbol; | |
| var controlSpec = paramControlSpecs[name]; // TODO: report error when controlSpec is not found / or rely on .asSpec | |
| [name, controlSpec.default] | |
| } ++ | |
| spec[\visuals].collect { |visualName| | |
| var name = ("visual_"++visualName.asString).asSymbol; | |
| [ | |
| name, | |
| visualbusses.detect { |busAssoc| busAssoc.key == visualName.asSymbol }.value | |
| ] | |
| } | |
| ++ | |
| sampleSlotBuffers.collect { |sampleSlotBuffersPerChannelAssoc| | |
| var sampleSlotName = sampleSlotBuffersPerChannelAssoc.key; | |
| var buffersPerChannel = sampleSlotBuffersPerChannelAssoc.value; | |
| [ | |
| ("bufnums_"++sampleSlotName.asString).asSymbol, | |
| buffersPerChannel.collect { |channelCountBufferAssoc| | |
| var buffer = channelCountBufferAssoc.value; | |
| buffer | |
| } | |
| ] | |
| } | |
| ).flatten | |
| }; | |
| var getModuleDefParamLagTimes = { |moduleDef| | |
| var params = getModuleDefParams.value(moduleDef); | |
| if (params.notNil) { | |
| asDict.value( | |
| params.collect { |assoc| | |
| var assocValue = assoc.value; | |
| ("param_"++assoc.key).asSymbol -> if (assocValue.class == Event) { assocValue[\LagTime] } { nil } | |
| } | |
| ) | |
| } | |
| }; | |
| var getModuleDefParamControlSpecs = { |moduleDef| | |
| var params = getModuleDefParams.value(moduleDef); | |
| if (params.notNil) { | |
| asDict.value( | |
| params.collect { |assoc| | |
| var assocValue = assoc.value; | |
| ("param_"++assoc.key).asSymbol -> if (assocValue.class == ControlSpec) { // TODO: DRY this up | |
| assocValue | |
| } { | |
| // TODO: Event assumed | |
| assocValue[\Spec] | |
| } | |
| } | |
| ) | |
| } | |
| }; | |
| var getModuleDefParamDescriptions = { |moduleDef| | |
| var params = getModuleDefParams.value(moduleDef); | |
| if (params.notNil) { | |
| asDict.value( | |
| params.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| }; | |
| } | |
| ) | |
| } | |
| }; | |
| var getModuleDefInputDescriptions = { |moduleDef| | |
| var inputs = getModuleDefInputs.value(moduleDef); | |
| if (inputs.notNil) { | |
| asDict.value( | |
| inputs.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| } | |
| } | |
| ) | |
| } | |
| }; | |
| var getModuleDefOutputDescriptions = { |moduleDef| | |
| var outputs = getModuleDefOutputs.value(moduleDef); | |
| if (outputs.notNil) { | |
| asDict.value( | |
| outputs.collect { |paramAssoc| | |
| var assocValue = paramAssoc.value; | |
| paramAssoc.key -> if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| } | |
| } | |
| ) | |
| } | |
| }; | |
| var getModuleDefVisualDescriptions = { |moduleDef| | |
| var visuals = getModuleDefVisuals.value(moduleDef); | |
| if (visuals.notNil) { | |
| asDict.value( | |
| visuals.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| } | |
| } | |
| ) | |
| } | |
| }; | |
| var getModuleDefSampleSlotDescriptions = { |moduleDef| | |
| var sampleSlot = getModuleDefVisuals.value(moduleDef); | |
| if (sampleSlot.notNil) { | |
| asDict.value( | |
| sampleSlot.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| } | |
| } | |
| ) | |
| } | |
| }; | |
| var getModuleDefName = { |moduleDef| | |
| moduleDef[\name] | |
| }; | |
| var getModuleDefUgenGraphFunc = { |moduleDef| | |
| moduleDef[\ugenGraphFunc] | |
| }; | |
| var getModuleDefParams = { |moduleDef| | |
| moduleDef[\params] | |
| }; | |
| var getModuleDefInputs = { |moduleDef| | |
| moduleDef[\inputs] | |
| }; | |
| var getModuleDefOutputs = { |moduleDef| | |
| moduleDef[\outputs] | |
| }; | |
| var getModuleDefVisuals = { |moduleDef| | |
| moduleDef[\visuals] | |
| }; | |
| var getModuleDefSampleSlots = { |moduleDef| | |
| moduleDef[\sampleSlots] | |
| }; | |
| /* Information */ | |
| var postAutobootingServerInformation = { | |
| "Booting Server %".format(Server.default).inform; | |
| }; | |
| /* Errors */ | |
| var postServerNotRunningError = { | |
| "Server % is not running".format(Server.default).error; | |
| }; | |
| var postTapIndexNotWithinBoundsError = { |self, tapIndex| | |
| "tap index not within bounds: tapIndex % referred, only % taps available".format(tapIndex, self[\numTaps]).error; | |
| }; | |
| var postModuleSampleSlotDoesNotSupportChannelCountError = { |module, sampleSlotRef, channelCount| | |
| var sampleSlotBuffers = lookupSampleSlotBuffersByName.value(module, sampleSlotRef); // TODO: naming, for clarity, this is not the complete sampleslotbuffers object, but only for one sampleslot | |
| "sample slot % for % module named % does not support channel count % (supported channel counts are %)".format( | |
| sampleSlotRef.asString.quote, | |
| module[\kind].asString.quote, | |
| module[\name].asString.quote, | |
| channelCount, | |
| sampleSlotBuffers.keys.join(", ") | |
| ).error; | |
| }; | |
| var postFileDoesNotExistError = { |path| | |
| "file % does not exist.".format(path.quote).error; | |
| }; | |
| var postFileIsNotASoundFileError = { |path| | |
| "file % is not a sound file.".format(path.quote).error; | |
| }; | |
| var postInvalidMacroNameError = { |name| | |
| "macro name % is invalid.".format(name.asString.quote).error; | |
| }; | |
| var postInvalidMacroModuleParametersError = { |name, moduleParameterRefs| | |
| var invalidRefs = moduleParameterRefs.select { |moduleParameterRef| | |
| isValidModuleParameterName.value(moduleParameterRef).not; | |
| }; | |
| "macro % not created: module parameter refs % are invalid".format( | |
| name.asString.quote, | |
| invalidRefs.collect { |ref| ref.asString }.join(", ").quote | |
| ).error; // TODO: if none, this looks weird, fix so that it says "no possible sample slots" | |
| }; | |
| var postModuleParameterControlSpecsAreInconsistent = { |name, moduleParameterRefs| | |
| // TODO | |
| }; | |
| var postInvalidModuleSampleSlotError = { |module, sampleSlotRef| | |
| var moduleDef = module[\moduleDef]; | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var possibleSampleSlotsList = spec[\sampleSlots].collect{ |item| item.asString.quote }.join(", "); | |
| "invalid sample slot % for % module named % (possible sample slots are %)".format( | |
| sampleSlotRef.asString.quote, | |
| module[\kind].asString.quote, | |
| module[\name].asString.quote, | |
| possibleSampleSlotsList | |
| ).error; // TODO: if none, this looks weird, fix so that it says "no possible sample slots" | |
| }; | |
| var postInvalidModuleVisualNameError = { |ref| | |
| "module visual reference % does not conform to module visual syntax ([ModuleName]%[VisualName])".format( | |
| ref.asString.quote, | |
| moduleVisualRefDelimiter.asString | |
| ).error; | |
| }; | |
| var postInvalidModuleSampleSlotNameError = { |ref| | |
| "module sample slot reference % does not conform to module sample slot syntax ([ModuleName]%[SampleSlotName])".format( | |
| ref.asString.quote, | |
| moduleParameterRefDelimiter.asString | |
| ).error; | |
| }; | |
| var postInvalidModuleParameterNameError = { |ref| | |
| "module parameter reference % does not conform to module parameter syntax ([ModuleName]%[ParameterName])".format( | |
| ref.asString.quote, | |
| moduleParameterRefDelimiter.asString | |
| ).error; | |
| }; | |
| var postInvalidModuleOutputNameError = { |ref| | |
| "module output reference % does not conform to module output syntax ([ModuleName]%[OutputName])".format( | |
| ref.asString.quote, | |
| moduleOutputRefDelimiter.asString | |
| ).error; | |
| }; | |
| var postInvalidModuleInputNameError = { |ref| | |
| "module input reference % does not conform to module input syntax ([ModuleName]%[InputName])".format( | |
| ref.asString.quote, | |
| moduleInputRefDelimiter.asString | |
| ).error; | |
| }; | |
| var postInputAndOutputIsConnectedError = { |moduleOutputRef, moduleInputRef| | |
| "module out % is already connected to module in %".format( | |
| moduleOutputRef.asString.quote, | |
| moduleInputRef.asString.quote | |
| ).error; | |
| }; | |
| var postInputAndOutputIsNotConnectedError = { |moduleOutputRef, moduleInputRef| | |
| "module out % is not connected to module in %".format( | |
| moduleOutputRef.asString.quote, | |
| moduleInputRef.asString.quote | |
| ).error; | |
| }; | |
| var postModuleNameIsInvalidError = { |name| | |
| "module name % is invalid.".format(name.asString.quote).error; | |
| }; | |
| var postUnableToCreateDueToAlreadyExistingModuleError = { |name| | |
| "unable to create %. module named % already exists".format(name.asString.quote, name.asString.quote).error; | |
| }; | |
| var postUnableToCreateDueToInvalidModuleTypeError = { |name, kind| | |
| "unable to create %. invalid module type %".format(name.asString.quote, kind.asString.quote).error; | |
| }; | |
| var postInvalidModuleVisualError = { |module, visualRef| | |
| var moduleDef = module[\moduleDef]; | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var possibleVisualsList = spec[\visuals].collect{ |item| item.asString.quote }.join(", "); | |
| "invalid visual % for % module named % (possible visuals are %)".format( | |
| visualRef.asString.quote, | |
| module[\kind].asString.quote, | |
| module[\name].asString.quote, | |
| possibleVisualsList | |
| ).error; // TODO: if no visuals are applicable don't say "possible visuals are ", but "module has no visuals". same with parameters and ins / outs | |
| }; | |
| var postInvalidModuleOutputError = { |module, outputRef| | |
| var moduleDef = module[\moduleDef]; | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var possibleOutputsList = spec[\outputs].collect{ |item| item.asString.quote }.join(", "); | |
| "invalid output % for % module named % (possible outputs are %)".format( | |
| outputRef.asString.quote, | |
| module[\kind].asString.quote, | |
| module[\name].asString.quote, | |
| possibleOutputsList | |
| ).error; | |
| }; | |
| var postInvalidModuleInputError = { |module, inputRef| | |
| var moduleDef = module[\moduleDef]; | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| var possibleInputsList = spec[\inputs].collect{ |item| item.asString.quote }.join(", "); | |
| "invalid input % for % module named % (possible inputs are %)".format( | |
| inputRef.asString.quote, | |
| module[\kind].asString.quote, | |
| module[\name].asString.quote, | |
| possibleInputsList | |
| ).error; | |
| }; | |
| var postModuleNotFoundError = { |moduleRef, modules| | |
| var moduleList = modules.collect { |module| module[\name].asString }.join(", "); | |
| "module named % not found among modules %".format( | |
| moduleRef.asString.quote, | |
| moduleList.quote | |
| ).error; // TODO: if no modules have been created this still says "among modules " | |
| }; | |
| var postInvalidModuleParameterError = { |module, parameterRef| | |
| var spec = getModuleDefSpec.value(module[\moduleDef]); | |
| "parameter % not valid for module named % (kind: % has parameters %)".format( | |
| parameterRef.asString.quote, | |
| module[\name].asString.quote, | |
| module[\kind].asString.quote, | |
| spec[\parameters].collect { |parameterName| parameterName.asString.quote }.join(", ") | |
| ).error; // TODO: add clarifying array to all other parameter % not valid occurrences | |
| }; | |
| /* Standard modules */ | |
| var coreModules = [ | |
| ( // TODO: make this a singleton | |
| name: 'SoundIn', | |
| description: "Stereo sound input", | |
| outputs: [ | |
| 'Left' -> ( | |
| Description: "Audio signal from left R audio input." | |
| ), | |
| 'Right' -> ( | |
| Description: "Audio signal from right R audio input." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| out_Left, | |
| out_Right, | |
| internal_In | |
| | | |
| var in = In.ar(internal_In, 2); | |
| Out.ar(out_Left, in[0]); | |
| Out.ar(out_Right, in[1]); | |
| }, | |
| examples: [ | |
| [ | |
| "Channel reverser", | |
| nil, | |
| "Create modules", | |
| ('new' -> ['In', 'SoundIn']), | |
| ('new' -> ['Out', 'SoundOut']), | |
| nil, | |
| "Reverse channels", | |
| ('connect' -> ['In/Left', 'Out*Right']), | |
| ('connect' -> ['In/Right', 'Out*Left']), | |
| ] | |
| ], | |
| ), | |
| ( // TODO: make this a singleton | |
| name: 'SoundOut', | |
| description: "Stereo sound output", | |
| inputs: [ | |
| 'Left' -> ( | |
| Description: "Audio signal to left R audio output." | |
| ), | |
| 'Right' -> ( | |
| Description: "Audio signal to right R audio output." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Left, | |
| in_Right, | |
| internal_Out | |
| | | |
| Out.ar(internal_Out, [In.ar(in_Left), In.ar(in_Right)]); // TODO: remove DC to not mess up monitors | |
| }, | |
| examples: [ | |
| [ | |
| "Audition a sine oscillator", | |
| nil, | |
| "Create modules", | |
| ('new' -> ['Osc', 'SineOsc']), | |
| ('new' -> ['Out', 'SoundOut']), | |
| nil, | |
| "Connect modules", | |
| ('connect' -> ['Osc/Out', 'Out*Right']), | |
| ('connect' -> ['Osc/Out', 'Out*Left']), | |
| ] | |
| ], | |
| /* | |
| params: [ | |
| 'Gain' -> \db.asSpec.copy.maxval_(12).default_(0), | |
| ] | |
| */ | |
| ) | |
| ]; | |
| var stdModules = [ | |
| ( | |
| // Status: tested | |
| name: '44Matrix', | |
| description: "4x4 matrix signal router.", | |
| inputsDocSection: "\t- `In1` ... `In4`: Signal inputs.", | |
| inputs: { generateMatrixModulesInputs.value(4) }, | |
| outputsDocSection: "\t- `Out1` ... `Out4`: Signal outputs.", | |
| outputs: { generateMatrixModulesOutputs.value(4) }, | |
| paramsDocSection: | |
| "\t- `FadeTime`: Fade time in milliseconds (range: `0` - `100000` ms) applied when an input is switched on to or off from an output. Default is `5` ms." ++ "\n" ++ | |
| "\t- `Gate_1_1` ... `Gate_4_4`: Toggles that determine what inputs (first number) are switched on to outputs (second number).", | |
| params: { generateMatrixModulesParams.value(4, 4) }, | |
| ugenGraphFunc: { | |
| | | |
| in_In1, | |
| in_In2, | |
| in_In3, | |
| in_In4, | |
| out_Out1, | |
| out_Out2, | |
| out_Out3, | |
| out_Out4, | |
| param_FadeTime, | |
| param_Gate_1_1, | |
| param_Gate_1_2, | |
| param_Gate_1_3, | |
| param_Gate_1_4, | |
| param_Gate_2_1, | |
| param_Gate_2_2, | |
| param_Gate_2_3, | |
| param_Gate_2_4, | |
| param_Gate_3_1, | |
| param_Gate_3_2, | |
| param_Gate_3_3, | |
| param_Gate_3_4, | |
| param_Gate_4_1, | |
| param_Gate_4_2, | |
| param_Gate_4_3, | |
| param_Gate_4_4 | |
| | | |
| var sigs = [In.ar(in_In1), In.ar(in_In2), In.ar(in_In3), In.ar(in_In4)]; | |
| Out.ar( | |
| out_Out1, | |
| (sigs[0] * Lag.kr(param_Gate_1_1, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_1, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_1, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_1, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out2, | |
| (sigs[0] * Lag.kr(param_Gate_1_2, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_2, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_2, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_2, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out3, | |
| (sigs[0] * Lag.kr(param_Gate_1_3, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_3, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_3, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_3, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out4, | |
| (sigs[0] * Lag.kr(param_Gate_1_4, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_4, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_4, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_4, param_FadeTime/1000)) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: '88Matrix', | |
| description: "8x8 matrix signal router.", | |
| inputsDocSection: "\t- `In1` ... `In8`: Signal inputs.", | |
| inputs: { generateMatrixModulesInputs.value(8) }, | |
| outputsDocSection: "\t- `Out1` ... `Out8`: Signal outputs.", | |
| outputs: { generateMatrixModulesOutputs.value(8) }, | |
| paramsDocSection: | |
| "\t- `FadeTime`: Fade time in milliseconds (range: `0`-`100000` ms) applied when an input is switched on to or off from an output. Default is `5` ms." ++ "\n" ++ | |
| "\t- `Gate_1_1` ... `Gate_8_8`: Toggles that determine what inputs (first number) are switched on to outputs (second number).", | |
| params: { generateMatrixModulesParams.value(8, 8) }, | |
| ugenGraphFunc: { | |
| | | |
| in_In1, | |
| in_In2, | |
| in_In3, | |
| in_In4, | |
| in_In5, | |
| in_In6, | |
| in_In7, | |
| in_In8, | |
| out_Out1, | |
| out_Out2, | |
| out_Out3, | |
| out_Out4, | |
| out_Out5, | |
| out_Out6, | |
| out_Out7, | |
| out_Out8, | |
| param_FadeTime, | |
| param_Gate_1_1, | |
| param_Gate_1_2, | |
| param_Gate_1_3, | |
| param_Gate_1_4, | |
| param_Gate_1_5, | |
| param_Gate_1_6, | |
| param_Gate_1_7, | |
| param_Gate_1_8, | |
| param_Gate_2_1, | |
| param_Gate_2_2, | |
| param_Gate_2_3, | |
| param_Gate_2_4, | |
| param_Gate_2_5, | |
| param_Gate_2_6, | |
| param_Gate_2_7, | |
| param_Gate_2_8, | |
| param_Gate_3_1, | |
| param_Gate_3_2, | |
| param_Gate_3_3, | |
| param_Gate_3_4, | |
| param_Gate_3_5, | |
| param_Gate_3_6, | |
| param_Gate_3_7, | |
| param_Gate_3_8, | |
| param_Gate_4_1, | |
| param_Gate_4_2, | |
| param_Gate_4_3, | |
| param_Gate_4_4, | |
| param_Gate_4_5, | |
| param_Gate_4_6, | |
| param_Gate_4_7, | |
| param_Gate_4_8, | |
| param_Gate_5_1, | |
| param_Gate_5_2, | |
| param_Gate_5_3, | |
| param_Gate_5_4, | |
| param_Gate_5_5, | |
| param_Gate_5_6, | |
| param_Gate_5_7, | |
| param_Gate_5_8, | |
| param_Gate_6_1, | |
| param_Gate_6_2, | |
| param_Gate_6_3, | |
| param_Gate_6_4, | |
| param_Gate_6_5, | |
| param_Gate_6_6, | |
| param_Gate_6_7, | |
| param_Gate_6_8, | |
| param_Gate_7_1, | |
| param_Gate_7_2, | |
| param_Gate_7_3, | |
| param_Gate_7_4, | |
| param_Gate_7_5, | |
| param_Gate_7_6, | |
| param_Gate_7_7, | |
| param_Gate_7_8, | |
| param_Gate_8_1, | |
| param_Gate_8_2, | |
| param_Gate_8_3, | |
| param_Gate_8_4, | |
| param_Gate_8_5, | |
| param_Gate_8_6, | |
| param_Gate_8_7, | |
| param_Gate_8_8 | |
| | | |
| var sigs = [In.ar(in_In1), In.ar(in_In2), In.ar(in_In3), In.ar(in_In4), In.ar(in_In5), In.ar(in_In6), In.ar(in_In7), In.ar(in_In8)]; | |
| Out.ar( | |
| out_Out1, | |
| (sigs[0] * Lag.kr(param_Gate_1_1, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_1, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_1, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_1, param_FadeTime/1000)) + | |
| (sigs[4] * Lag.kr(param_Gate_5_1, param_FadeTime/1000)) + | |
| (sigs[5] * Lag.kr(param_Gate_6_1, param_FadeTime/1000)) + | |
| (sigs[6] * Lag.kr(param_Gate_7_1, param_FadeTime/1000)) + | |
| (sigs[7] * Lag.kr(param_Gate_8_1, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out2, | |
| (sigs[0] * Lag.kr(param_Gate_1_2, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_2, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_2, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_2, param_FadeTime/1000)) + | |
| (sigs[4] * Lag.kr(param_Gate_5_2, param_FadeTime/1000)) + | |
| (sigs[5] * Lag.kr(param_Gate_6_2, param_FadeTime/1000)) + | |
| (sigs[6] * Lag.kr(param_Gate_7_2, param_FadeTime/1000)) + | |
| (sigs[7] * Lag.kr(param_Gate_8_2, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out3, | |
| (sigs[0] * Lag.kr(param_Gate_1_3, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_3, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_3, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_3, param_FadeTime/1000)) + | |
| (sigs[4] * Lag.kr(param_Gate_5_3, param_FadeTime/1000)) + | |
| (sigs[5] * Lag.kr(param_Gate_6_3, param_FadeTime/1000)) + | |
| (sigs[6] * Lag.kr(param_Gate_7_3, param_FadeTime/1000)) + | |
| (sigs[7] * Lag.kr(param_Gate_8_3, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out4, | |
| (sigs[0] * Lag.kr(param_Gate_1_4, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_4, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_4, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_4, param_FadeTime/1000)) + | |
| (sigs[4] * Lag.kr(param_Gate_5_4, param_FadeTime/1000)) + | |
| (sigs[5] * Lag.kr(param_Gate_6_4, param_FadeTime/1000)) + | |
| (sigs[6] * Lag.kr(param_Gate_7_4, param_FadeTime/1000)) + | |
| (sigs[7] * Lag.kr(param_Gate_8_4, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out5, | |
| (sigs[0] * Lag.kr(param_Gate_1_5, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_5, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_5, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_5, param_FadeTime/1000)) + | |
| (sigs[4] * Lag.kr(param_Gate_5_5, param_FadeTime/1000)) + | |
| (sigs[5] * Lag.kr(param_Gate_6_5, param_FadeTime/1000)) + | |
| (sigs[6] * Lag.kr(param_Gate_7_5, param_FadeTime/1000)) + | |
| (sigs[7] * Lag.kr(param_Gate_8_5, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out6, | |
| (sigs[0] * Lag.kr(param_Gate_1_6, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_6, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_6, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_6, param_FadeTime/1000)) + | |
| (sigs[4] * Lag.kr(param_Gate_5_6, param_FadeTime/1000)) + | |
| (sigs[5] * Lag.kr(param_Gate_6_6, param_FadeTime/1000)) + | |
| (sigs[6] * Lag.kr(param_Gate_7_6, param_FadeTime/1000)) + | |
| (sigs[7] * Lag.kr(param_Gate_8_6, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out7, | |
| (sigs[0] * Lag.kr(param_Gate_1_7, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_7, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_7, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_7, param_FadeTime/1000)) + | |
| (sigs[4] * Lag.kr(param_Gate_5_7, param_FadeTime/1000)) + | |
| (sigs[5] * Lag.kr(param_Gate_6_7, param_FadeTime/1000)) + | |
| (sigs[6] * Lag.kr(param_Gate_7_7, param_FadeTime/1000)) + | |
| (sigs[7] * Lag.kr(param_Gate_8_7, param_FadeTime/1000)) | |
| ); | |
| Out.ar( | |
| out_Out8, | |
| (sigs[0] * Lag.kr(param_Gate_1_8, param_FadeTime/1000)) + | |
| (sigs[1] * Lag.kr(param_Gate_2_8, param_FadeTime/1000)) + | |
| (sigs[2] * Lag.kr(param_Gate_3_8, param_FadeTime/1000)) + | |
| (sigs[3] * Lag.kr(param_Gate_4_8, param_FadeTime/1000)) + | |
| (sigs[4] * Lag.kr(param_Gate_5_8, param_FadeTime/1000)) + | |
| (sigs[5] * Lag.kr(param_Gate_6_8, param_FadeTime/1000)) + | |
| (sigs[6] * Lag.kr(param_Gate_7_8, param_FadeTime/1000)) + | |
| (sigs[7] * Lag.kr(param_Gate_8_8, param_FadeTime/1000)) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| // Inspiration from A-140 | |
| // TODO: is it possible to implement Retrig(?) | |
| name: 'ADSREnv', | |
| description: "ADSR Envelope.", | |
| inputs: [ | |
| 'Gate' -> ( | |
| Description: "Gate control input. A signal > 0 triggers envelope." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Envelope signal: `0` ... `0.8`." | |
| ) | |
| ], | |
| params: [ | |
| 'Attack' -> ( | |
| Spec: ControlSpec(0.1, 2000, 'exp', 0, 5, "ms"), | |
| LagTime: 0.1, | |
| Description: "Attack time. Range `0.1` - `2000` ms. Default is `5`." | |
| ), | |
| 'Decay' -> ( | |
| Spec: ControlSpec(0.1, 8000, 'exp', 0, 200, "ms"), | |
| LagTime: 0.1, | |
| Description: "Decay time. Range `0.1` - `8000` ms. Default is `200`." | |
| ), | |
| 'Sustain' -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 0, 0.5, ""), | |
| LagTime: 0.1, | |
| Description: "Sustain level `0` - `1.0`. Default is `0.5`." | |
| ), | |
| 'Release' -> ( | |
| Spec: ControlSpec(0.1, 8000, 'exp', 0, 200, "ms"), | |
| LagTime: 0.1, | |
| Description: "Release time. Range `0.1` - `8000` ms. Default is `200`." | |
| ), | |
| 'Gate' -> ( | |
| Spec: ControlSpec(0, 1, step: 1, default: 0), // TODO: DRY the gate/reset/boolean specs | |
| Description: "Scriptable gate. When parameter goes from 0 to a positive value a gate is triggered." | |
| ) | |
| // 'Curve' -> ControlSpec(-10, 10, 'lin', -4), TODO | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Gate, | |
| // TODO: in_Retrig, | |
| out_Out, | |
| // TODO: out_OutInverse, | |
| param_Attack, | |
| param_Decay, | |
| param_Sustain, | |
| param_Release, | |
| param_Gate/*, | |
| param_Curve TODO */ | |
| | | |
| var sig_Gate = In.ar(in_Gate); // TODO: gate threshold on A-148 is 3V = 0.3 | |
| var curve = -4; // TODO: default is -4, exp approximation -13.81523 !? | |
| Out.ar( | |
| out_Out, | |
| EnvGen.ar( | |
| Env.adsr(param_Attack/1000, param_Decay/1000, param_Sustain, param_Release/1000, curve: curve), | |
| // TODO ((sig_Gate > 0) + (param_Gate > 0)) > 0, | |
| ((sig_Gate > 0) + (K2A.ar(param_Gate) > 0)) > 0, // TODO: Is K2A really needed? | |
| levelScale: 0.8 // TODO: ~ 8 V | |
| ) | |
| ); | |
| } | |
| ), | |
| ( | |
| // status: untested | |
| // TODO: This is ADSREnv with Retrig input for testing | |
| name: 'ADSREnv2', | |
| experimental: true, | |
| params: [ | |
| 'Attack' -> ( | |
| Spec: ControlSpec(0.1, 2000, 'exp', 0, 5, "ms"), | |
| LagTime: 0.1 | |
| ), | |
| 'Decay' -> ( | |
| Spec: ControlSpec(0.1, 8000, 'exp', 0, 200, "ms"), | |
| LagTime: 0.1 | |
| ), | |
| 'Sustain' -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 0, 0.5, ""), | |
| LagTime: 0.1 | |
| ), | |
| 'Release' -> ( | |
| Spec: ControlSpec(0.1, 8000, 'exp', 0, 200, "ms"), | |
| LagTime: 0.1 | |
| ) | |
| // 'Curve' -> ControlSpec(-10, 10, 'lin', -4), TODO | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Gate, | |
| in_Retrig, | |
| out_Out, | |
| // TODO: out_OutInverse, | |
| param_Attack, | |
| param_Decay, | |
| param_Sustain, | |
| param_Release | |
| /* param_Curve TODO */ | |
| | | |
| var sig_Gate = In.ar(in_Gate); | |
| var sig_Retrig = In.ar(in_Retrig); | |
| var curve = -4; // TODO: default is -4, exp approximation -13.81523 !? | |
| var gate = (sig_Gate > 0) + ((-1)*Trig1.ar(sig_Retrig, 1/SampleRate.ir)); | |
| Out.ar( | |
| out_Out, | |
| EnvGen.ar( | |
| Env.adsr(param_Attack/1000, param_Decay/1000, param_Sustain, param_Release/1000, curve: curve), | |
| // TODO ((sig_Gate > 0) + (param_Gate > 0)) > 0, | |
| // ((sig_Gate > 0) + (K2A.ar(param_Gate) > 0)) > 0, | |
| gate, | |
| levelScale: 0.8 // TODO: ~ 8 V | |
| ) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| // TODO: test Exp input more | |
| name: 'Amp', | |
| description: "Simple amplifier with level parameter and exponential or linear gain modulation.", | |
| inputs: [ | |
| 'Exp' -> ( | |
| Description: "Gain modulation control input (logarithmic)." | |
| ), | |
| 'Lin' -> ( | |
| Description: "Gain modulation control input (linear)." | |
| ), | |
| 'In' -> ( | |
| Description: "Input signal to attenuate." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Attenuated signal." | |
| ) | |
| ], | |
| params: [ | |
| 'Level' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Amplifier level `0` - `1.0`." | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Exp, | |
| in_Lin, | |
| in_In, | |
| out_Out, | |
| param_Level | |
| | | |
| var sig_Exp = In.ar(in_Exp); | |
| var sig_Lin = In.ar(in_Lin); | |
| var sig_In = In.ar(in_In); | |
| var curveSpec = ControlSpec.new(0, 1, 13.81523); // TODO: exp approximation, dunno about this? | |
| Out.ar( | |
| out_Out, | |
| sig_In * (param_Level + sig_Lin.clip(0, 0.5) + curveSpec.map(sig_Exp).clip(0, 0.5)).clip(0, 0.5) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: partly tested, exp mode needs more testing | |
| // Inspiration from A-130/A-131 | |
| name: 'Amp2', | |
| description: "Amplifier with two inputs, level parameter and variable exponential or linear gain modulation.", | |
| inputs: [ | |
| 'GainModulation' -> ( | |
| Description: "Control input for gain modulation." | |
| ), | |
| 'In1' -> ( | |
| Description: "Audio input 1." | |
| ), | |
| 'In2' -> ( | |
| Description: "Audio input 2." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Attenuated signal." | |
| ) | |
| ], | |
| params: [ | |
| 'Gain' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Initial gain `0` - `1.0`." | |
| ), | |
| 'GainModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Gain modulation amount `0` - `1.0`." | |
| ), | |
| 'In1' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Audio input 1 level `0` - `1.0`." | |
| ), | |
| 'In2' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Audio input 2 level `0` - `1.0`." | |
| ), | |
| 'Out' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Audio output level `0` - `1.0`." | |
| ), | |
| 'Mode' -> ( | |
| Spec: ControlSpec.new(0, 1, 'lin', 1, 0), | |
| Description: "`0` or `1` representing linear or exponential gain modulation." | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_GainModulation, | |
| in_In1, | |
| in_In2, | |
| out_Out, | |
| param_Gain, | |
| param_GainModulation, | |
| param_In1, | |
| param_In2, | |
| param_Out, | |
| param_Mode | |
| | | |
| var sig_In1 = In.ar(in_In1); | |
| var sig_In2 = In.ar(in_In2); | |
| var sig_GainModulation = In.ar(in_GainModulation); // TODO: bipolar unmap | |
| var curveSpec = ControlSpec.new(0, 1, 13.81523); // TODO: exp approximation, dunno about this? | |
| var gainGain = SelectX.kr(param_Mode, [param_Gain, curveSpec.map(param_Gain)]); // TODO: exp hack | |
| var in1Gain = SelectX.kr(param_Mode, [param_In1, curveSpec.map(param_In1)]); // TODO: exp hack | |
| var in2Gain = SelectX.kr(param_Mode, [param_In2, curveSpec.map(param_In2)]); // TODO: exp hack | |
| var outGain = SelectX.kr(param_Mode, [param_Out, curveSpec.map(param_Out)]); // TODO: exp hack | |
| var inMix = (sig_In1 * in1Gain) + (sig_In2 * in2Gain); | |
| Out.ar( | |
| out_Out, | |
| inMix * (gainGain + (sig_GainModulation * param_GainModulation)) * outGain | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'BPFilter', | |
| description: "Resonant bandpass SVF filter.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ), | |
| 'FM' -> ( | |
| Description: "Control input for frequency modulation." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Description: "Control input for resonance modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Filtered audio signal." | |
| ) | |
| ], | |
| params: [ | |
| 'AudioLevel' -> ( | |
| Spec: \amp.asSpec.copy.default_(1), // TODO: updated | |
| LagTime: 0.1, | |
| Description: "Audio level `0` ... `1.0`. Default is `1`." | |
| ), | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| LagTime: 0.1, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz. Default is `440` Hz." | |
| ), | |
| 'Resonance' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance `0` ... `1.0`. Default is `0`." | |
| ), | |
| 'FM' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Frequency modulation amount `-1.0` ... `1.0`." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance modulation amount `-1.0` ... `1.0`." | |
| ), | |
| ], | |
| visuals: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_FM, | |
| in_ResonanceModulation, | |
| out_Out, | |
| param_AudioLevel, | |
| param_Frequency, | |
| param_Resonance, | |
| param_FM, | |
| param_ResonanceModulation, | |
| visual_Frequency | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_FM = In.ar(in_FM); | |
| var sig_ResonanceModulation = In.ar(in_ResonanceModulation); | |
| var frequencySpec = \widefreq.asSpec; | |
| var resonanceSpec = \unipolar.asSpec; | |
| var sig_In_Atten = sig_In * param_AudioLevel; | |
| var frequency = frequencySpec.map(frequencySpec.unmap(param_Frequency) + (sig_FM * param_FM)); | |
| var resonance = resonanceSpec.map(resonanceSpec.unmap(param_Resonance) + (sig_ResonanceModulation * param_ResonanceModulation)); | |
| Out.kr(visual_Frequency, frequency); | |
| Out.ar( | |
| out_Out, | |
| SVF.ar( | |
| sig_In_Atten, | |
| frequency, | |
| resonance, | |
| lowpass: 0, | |
| bandpass: 1, | |
| highpass: 0, | |
| notch: 0, | |
| peak: 0 | |
| ) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'BRFilter', | |
| description: "Resonant bandreject (Notch) SVF filter.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ), | |
| 'FM' -> ( | |
| Description: "Control input for frequency modulation." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Description: "Control input for resonance modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Filtered audio signal." | |
| ) | |
| ], | |
| params: [ | |
| 'AudioLevel' -> ( | |
| Spec: \amp.asSpec.copy.default_(1), // TODO: updated | |
| LagTime: 0.1, | |
| Description: "Audio level `0` ... `1.0`. Default is `1`." | |
| ), | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| LagTime: 0.1, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz. Default is `440` Hz." | |
| ), | |
| 'Resonance' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance `0` ... `1.0`. Default is `0`." | |
| ), | |
| 'FM' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Frequency modulation amount `-1.0` ... `1.0`." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance modulation amount `-1.0` ... `1.0`." | |
| ), | |
| ], | |
| visuals: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_FM, | |
| in_ResonanceModulation, | |
| out_Out, | |
| param_AudioLevel, | |
| param_Frequency, | |
| param_Resonance, | |
| param_FM, | |
| param_ResonanceModulation, | |
| visual_Frequency | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_FM = In.ar(in_FM); | |
| var sig_ResonanceModulation = In.ar(in_ResonanceModulation); | |
| var frequencySpec = \widefreq.asSpec; | |
| var resonanceSpec = \unipolar.asSpec; | |
| var sig_In_Atten = sig_In * param_AudioLevel; | |
| var frequency = frequencySpec.map(frequencySpec.unmap(param_Frequency) + (sig_FM * param_FM)); | |
| var resonance = resonanceSpec.map(resonanceSpec.unmap(param_Resonance) + (sig_ResonanceModulation * param_ResonanceModulation)); | |
| Out.kr(visual_Frequency, frequency); | |
| Out.ar( | |
| out_Out, | |
| SVF.ar( | |
| sig_In_Atten, | |
| frequency, | |
| resonance, | |
| lowpass: 0, | |
| bandpass: 0, | |
| highpass: 0, | |
| notch: 1, | |
| peak: 0 | |
| ) | |
| ); | |
| } | |
| ), | |
| ( | |
| // status: untested | |
| name: 'Comp2', | |
| experimental: true, | |
| inputs: [ | |
| 'Left' -> ( | |
| Description: "Left audio input" | |
| ), | |
| 'Right' -> ( | |
| Description: "Right audio input." | |
| ), | |
| 'SideChain' -> ( | |
| Description: "Side chain control input." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Left' -> ( | |
| Description: "Left compressed audio signal." | |
| ), | |
| 'Right' -> ( | |
| Description: "Right compressed audio signal." | |
| ), | |
| ], | |
| params: [ | |
| 'Threshold' -> ( | |
| Spec: \db.asSpec.copy.default_(-10), | |
| Description: "Treshold." | |
| ), | |
| 'Attack' -> ( | |
| Spec: ControlSpec(0.1, 250, 'exp', 0, 10, "ms"), | |
| Description: "Attack time." | |
| ), | |
| 'Release' -> ( | |
| Spec: ControlSpec(0.1, 1000, 'exp', 0, 100, "ms"), | |
| Description: "Release time." | |
| ), | |
| 'Ratio' -> ( | |
| Spec: ControlSpec(0, 20, default: 3), | |
| Description: "Compression ratio." | |
| ), | |
| 'MakeUp' -> ( | |
| Spec: \db.asSpec.copy.maxval_(20).default_(0), | |
| Description: "Make up level." | |
| ), | |
| ], | |
| visuals: [ | |
| 'GR' -> ( | |
| Description: "Gain reduction." | |
| ), | |
| ], | |
| ugenGraphFunc: { | | |
| param_Threshold, | |
| param_Attack, | |
| param_Release, | |
| param_Ratio, | |
| param_MakeUp, | |
| in_Left, | |
| in_Right, | |
| in_SideChain, | |
| connectedin_SideChain, | |
| out_Left, | |
| out_Right, | |
| visual_GR | |
| | | |
| var sig_Left = In.ar(in_Left, 1); | |
| var sig_Right = In.ar(in_Right, 1); | |
| var sig_dc = DC.ar(1); | |
| var control = (sig_Left + sig_Right) / 2; // TODO: possibly insert external side chain here | |
| // TODO: possibly insert sidechain HPF filter here | |
| var compressed = Compander.ar( | |
| [sig_Left, sig_Right, sig_dc], | |
| control, | |
| param_Threshold.dbamp, | |
| slopeAbove: 1/param_Ratio, | |
| clampTime: param_Attack/1000, | |
| relaxTime: param_Release/1000 | |
| ); | |
| Out.ar(out_Left, compressed[0] * param_MakeUp.dbamp); | |
| Out.ar(out_Right, compressed[1] * param_MakeUp.dbamp); | |
| Out.kr(visual_GR, compressed[2].ampdb); | |
| } | |
| ), | |
| ( | |
| // Status: untested | |
| name: 'DbMixer', | |
| description: "Mixer suited for audio signals.", | |
| inputsDocSection: "\t- `In1` ... `In4`: Audio inputs 1 ... 4.", | |
| inputs: { | |
| 4.collect { |index| | |
| ("In"++(index+1)) -> ( | |
| Description: "Audio input" + (index+1) ++ "." | |
| ) | |
| }; | |
| }, | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Mixed signal." | |
| ), | |
| ], | |
| paramsDocSection: | |
| "\t- `In1` ... `In4`: Audio input 1 ... 4 levels TODO: range/spec." ++ "\n" ++ | |
| "\t- `Out`: Output level TODO: range/spec.", | |
| params: { | |
| 4.collect { |index| | |
| ("In"++(index+1)).asSymbol -> ( | |
| Spec: \db.asSpec, | |
| LagTime: 0, | |
| Description: "Audio input" + (index+1) + "level TODO: range/spec." | |
| ) | |
| } ++ | |
| [ | |
| 'Out' -> ( | |
| Spec: \db.asSpec, | |
| LagTime: 0, | |
| Description: "Output level TODO: range/spec." | |
| ) | |
| ] | |
| }, | |
| ugenGraphFunc: { | |
| | | |
| in_In1, | |
| in_In2, | |
| in_In3, | |
| in_In4, | |
| out_Out, | |
| param_In1, | |
| param_In2, | |
| param_In3, | |
| param_In4, | |
| param_Out | |
| | | |
| var sig_In1 = In.ar(in_In1); // TODO: Add lag here instead | |
| var sig_In2 = In.ar(in_In2); | |
| var sig_In3 = In.ar(in_In3); | |
| var sig_In4 = In.ar(in_In4); | |
| Out.ar( | |
| out_Out, | |
| ( | |
| (sig_In1 * param_In1.dbamp) + | |
| (sig_In2 * param_In2.dbamp) + | |
| (sig_In3 * param_In3.dbamp) + | |
| (sig_In4 * param_In4.dbamp) | |
| ) * param_Out.dbamp | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'Decimate', | |
| experimental: true, | |
| description: "Sample rate and bit depth reducer.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ), | |
| // TODO: call this RateModulation, DepthModulation, SmoothModulation? | |
| 'Rate' -> ( | |
| Description: "TODO." | |
| ), | |
| 'Depth' -> ( | |
| Description: "TODO." | |
| ), | |
| 'Smooth' -> ( | |
| Description: "TODO." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Reduced signal." | |
| ), | |
| ], | |
| params: [ | |
| 'Rate' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "TODO." | |
| ), | |
| 'Depth' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "TODO." | |
| ), | |
| 'Smooth' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "TODO." | |
| ), | |
| 'RateModulation' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "TODO." | |
| ), | |
| 'DepthModulation' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "TODO." | |
| ), | |
| 'SmoothModulation' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "TODO." | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_Rate, | |
| in_Depth, | |
| in_Smooth | |
| out_Out, | |
| param_Rate, | |
| param_Depth, | |
| param_Smooth, | |
| param_RateModulation, | |
| param_DepthModulation, | |
| param_SmoothModulation | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_Rate = In.ar(in_Rate); | |
| var sig_Depth = In.ar(in_Rate); | |
| var sig_Smooth = In.ar(in_Rate); | |
| // TODO: 48000 should be SampleRate.ir? | |
| Out.ar(out_Out, | |
| SmoothDecimator.ar(Decimator.ar(sig_In, 48000, (param_Depth + (sig_Depth * param_Depth)) * 24), (param_Rate + (sig_Rate * param_RateModulation)) * 48000, param_Smooth + (sig_Smooth * param_SmoothModulation)) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: partly tested. TODO: what modulation input range should be used? | |
| name: 'Delay', | |
| description: "Delay line.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ), | |
| 'DelayTimeModulation' -> ( | |
| Description: "Control signal for delay time modulation." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Delayed signal." | |
| ), | |
| ], | |
| params: [ | |
| 'DelayTime' -> ( | |
| Spec: ControlSpec(0.1, 5000, 'exp', 0, 300, "ms"), | |
| LagTime: 0.25, | |
| Description: "Delay time `0.1` ... `5000` ms." | |
| ), | |
| 'DelayTimeModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Delay time modulation amount." | |
| ), | |
| ], | |
| visuals: [ | |
| 'DelayTime' -> ( | |
| Spec: ControlSpec(0.1, 5000, 'exp', 0, 300, "ms"), | |
| Description: "Delay time." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_DelayTimeModulation, | |
| out_Out, | |
| param_DelayTime, | |
| param_DelayTimeModulation, | |
| visual_DelayTime | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_DelayTimeModulation = In.ar(in_DelayTimeModulation); | |
| var delayTimeSpec = ControlSpec(0.1, 5000, 'exp', 0, 300, "ms"); | |
| var delayTimeMs = delayTimeSpec.map( | |
| delayTimeSpec.unmap(param_DelayTime) + (sig_DelayTimeModulation * param_DelayTimeModulation) | |
| ); | |
| var delayed = DelayC.ar(sig_In, maxdelaytime: delayTimeSpec.maxval/1000, delaytime: delayTimeMs/1000); // TODO: ControlDur.ir minimum | |
| Out.ar(out_Out, delayed); | |
| Out.kr(visual_DelayTime, delayTimeMs); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'EQBP', | |
| description: "Non-resonant, variable width bandpass filter.", | |
| params: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| LagTime: 0.1, | |
| Description: "TODO" | |
| ), | |
| 'Bandwidth' -> ( | |
| Spec: ControlSpec.new(0, 10), | |
| LagTime: 0.1, | |
| Description: "TODO" | |
| ), | |
| 'FM' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.01, | |
| Description: "TODO" | |
| ), | |
| 'BandwidthModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.01, | |
| Description: "TODO" | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_FM, | |
| in_BandwidthModulation, | |
| out_Out, | |
| param_Frequency, | |
| param_Bandwidth, | |
| param_FM, | |
| param_BandwidthModulation | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_FM = In.ar(in_FM); | |
| var sig_BandwidthModulation = In.ar(in_BandwidthModulation); | |
| var frequencySpec = \widefreq.asSpec; | |
| var bandwidthSpec = ControlSpec.new(0, 10); | |
| var frequency = frequencySpec.map(frequencySpec.unmap(param_Frequency) + (sig_FM * param_FM)); | |
| var bandwidth = bandwidthSpec.map(bandwidthSpec.unmap(param_Bandwidth) + (sig_BandwidthModulation * param_BandwidthModulation)); | |
| Out.ar( | |
| out_Out, | |
| BBandPass.ar( | |
| sig_In, | |
| frequency, | |
| bandwidth, | |
| ) | |
| ); | |
| } | |
| ), | |
| ( | |
| name: 'EnvF', | |
| experimental: true, | |
| description: "Envelope follower.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Input signal" | |
| ) | |
| ], | |
| outputs: [ | |
| 'Env' -> ( | |
| Description: "Envelope follower control output signal." | |
| ), | |
| 'Gate' -> ( | |
| Description: "Gte control output signal." | |
| ) | |
| ], | |
| params: [ | |
| /* | |
| 'Attack' -> ( | |
| Spec: ControlSpec(0.1, 2000, 'exp', 0, 100, "ms"), | |
| LagTime: 0.1 | |
| ), | |
| 'Decay' -> ( | |
| Spec: ControlSpec(0.1, 8000, 'exp', 0, 200, "ms"), | |
| LagTime: 0.1 | |
| ), | |
| */ | |
| 'Attack' -> ( | |
| Spec: ControlSpec(0.1, 2000, 'exp', 0, 100, "ms"), | |
| Description: "Envelope follower attack time." | |
| ), | |
| 'Decay' -> ( | |
| Spec: ControlSpec(0.1, 8000, 'exp', 0, 200, "ms"), | |
| Description: "Envelope follower decay time." | |
| ), | |
| 'Sensitivity' -> ( | |
| Spec: ControlSpec(0, 1, default: 0.5), | |
| Description: "Envelope follower sensitivity." | |
| ), | |
| 'Threshold' -> ( | |
| Spec: ControlSpec(0, 1, default: 0.5), | |
| Description: "Envelope follower threshold." | |
| ), | |
| ], | |
| ugenGraphFunc: { | | |
| param_Attack, | |
| param_Decay, | |
| param_Sensitivity, | |
| param_Threshold, | |
| in_In, | |
| out_Env, | |
| out_Gate | |
| | | |
| var sig_In = In.ar(in_In, 1); | |
| var env = Lag3UD.ar(abs(sig_In), param_Attack/1000, param_Decay/1000) * 5 * param_Sensitivity; // TODO: Tryout Amplitude ugen instead | |
| Out.ar(out_Env, env); | |
| Out.ar(out_Gate, Trig1.ar(abs(sig_In) > param_Threshold)); | |
| } | |
| ), | |
| ( | |
| // Status: not tested | |
| // TODO: Fix parameter names | |
| name: 'FMVoice', | |
| experimental: true, | |
| description: "FM synthesis voice.", | |
| params: { | |
| var numOscs = 3; | |
| var params = [ | |
| 'Freq' -> \freq.asSpec, | |
| 'Timbre' -> ControlSpec(0, 5, 'lin', nil, 1, "") | |
| ]; | |
| numOscs.do { |oscnum| | |
| params = params.addAll( | |
| [ | |
| "Osc%Gain".format(oscnum+1) -> \amp.asSpec, | |
| "Osc%Partial".format(oscnum+1) -> ControlSpec(0.5, 12, 'lin', 0.5, 1, ""), | |
| "Osc%Fixed".format(oscnum+1) -> ControlSpec(0, 1, 'lin', 1, 0, ""), | |
| "Osc%Fixedfreq".format(oscnum+1) -> \widefreq.asSpec, | |
| "Osc%Index".format(oscnum+1) -> ControlSpec(0, 24, 'lin', 0, 3, ""), | |
| "Osc%Outlevel".format(oscnum+1) -> \amp.asSpec, | |
| "Mod_To_Osc%Freq".format(oscnum+1) -> \bipolar.asSpec, | |
| "Mod_To_Osc%Gain".format(oscnum+1) -> \bipolar.asSpec, | |
| ] | |
| ); | |
| numOscs.do { |dest| | |
| params = params.add( | |
| "Osc%_To_Osc%Freq".format(oscnum+1, dest+1) -> \amp.asSpec | |
| ); | |
| }; | |
| }; | |
| params.collect { |assoc| | |
| assoc.key.asSymbol -> assoc.value | |
| }; | |
| }, | |
| ugenGraphFunc: { | |
| | | |
| in_Modulation, | |
| out_Out, | |
| param_Freq, // TODO: Frequency | |
| param_Timbre, | |
| param_Osc1Gain, | |
| param_Osc1Partial, | |
| param_Osc1Fixed, | |
| param_Osc1Fixedfreq, | |
| param_Osc1Index, | |
| param_Osc1Outlevel, | |
| param_Osc1_To_Osc1Freq, | |
| param_Osc1_To_Osc2Freq, | |
| param_Osc1_To_Osc3Freq, | |
| param_Osc2Gain, | |
| param_Osc2Partial, | |
| param_Osc2Fixed, | |
| param_Osc2Fixedfreq, | |
| param_Osc2Index, | |
| param_Osc2Outlevel, | |
| param_Osc2_To_Osc1Freq, | |
| param_Osc2_To_Osc2Freq, | |
| param_Osc2_To_Osc3Freq, | |
| param_Osc3Gain, | |
| param_Osc3Partial, | |
| param_Osc3Fixed, | |
| param_Osc3Fixedfreq, | |
| param_Osc3Index, | |
| param_Osc3Outlevel, | |
| param_Osc3_To_Osc3Freq, | |
| param_Osc3_To_Osc2Freq, | |
| param_Osc3_To_Osc1Freq, | |
| param_Mod_To_Osc1Gain, | |
| param_Mod_To_Osc2Gain, | |
| param_Mod_To_Osc3Gain, | |
| param_Mod_To_Osc1Freq, | |
| param_Mod_To_Osc2Freq, | |
| param_Mod_To_Osc3Freq | |
| | | |
| var sig; | |
| var osc1, osc2, osc3; | |
| var osc1freq, osc2freq, osc3freq; | |
| var osc1freqbasemod, osc2freqbasemod, osc3freqbasemod; | |
| var oscfeedback = LocalIn.ar(3); | |
| var sig_Modulation = In.ar(in_Modulation); | |
| osc1freq = Select.kr(param_Osc1Fixed, [param_Freq*param_Osc1Partial, param_Osc1Fixedfreq]); | |
| osc2freq = Select.kr(param_Osc2Fixed, [param_Freq*param_Osc2Partial, param_Osc2Fixedfreq]); | |
| osc3freq = Select.kr(param_Osc3Fixed, [param_Freq*param_Osc3Partial, param_Osc3Fixedfreq]); | |
| osc1freqbasemod = param_Osc1Index * osc1freq * param_Timbre; | |
| osc2freqbasemod = param_Osc2Index * osc2freq * param_Timbre; | |
| osc3freqbasemod = param_Osc3Index * osc3freq * param_Timbre; | |
| osc1 = SinOsc.ar( | |
| osc1freq | |
| + (osc1freqbasemod * oscfeedback[0] * param_Osc1_To_Osc1Freq) | |
| + (osc1freqbasemod * oscfeedback[1] * param_Osc2_To_Osc1Freq) | |
| + (osc1freqbasemod * oscfeedback[2] * param_Osc3_To_Osc1Freq) | |
| + (osc1freqbasemod * sig_Modulation * param_Mod_To_Osc1Freq) | |
| ) * (param_Osc1Gain + (param_Mod_To_Osc1Gain * sig_Modulation)); | |
| osc2 = SinOsc.ar( | |
| osc2freq | |
| + (osc2freqbasemod * osc1 * param_Osc1_To_Osc2Freq) | |
| + (osc2freqbasemod * oscfeedback[1] * param_Osc2_To_Osc2Freq) | |
| + (osc2freqbasemod * oscfeedback[2] * param_Osc3_To_Osc2Freq) | |
| + (osc2freqbasemod * sig_Modulation * param_Mod_To_Osc2Freq) | |
| ) * (param_Osc2Gain + (param_Mod_To_Osc2Gain * sig_Modulation)); | |
| osc3 = SinOsc.ar( | |
| osc3freq | |
| + (osc3freqbasemod * osc1 * param_Osc1_To_Osc3Freq) | |
| + (osc3freqbasemod * osc2 * param_Osc2_To_Osc3Freq) | |
| + (osc3freqbasemod * oscfeedback[2] * param_Osc3_To_Osc3Freq) | |
| + (osc3freqbasemod * sig_Modulation * param_Mod_To_Osc3Freq) | |
| ) * (param_Osc3Gain + (param_Mod_To_Osc3Gain * sig_Modulation)); | |
| sig = (osc1 * param_Osc1Outlevel) + (osc2 * param_Osc2Outlevel) + (osc3 * param_Osc3Outlevel); | |
| LocalOut.ar([osc1, osc2, osc3]); | |
| Out.ar(out_Out, sig); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'FreqGate', | |
| description: "CV/Gate like thing.", | |
| outputs: [ | |
| 'Frequency' -> ( | |
| Description: "Frequency control signal." | |
| ), | |
| 'Gate' -> ( | |
| Description: "Gate control signal." | |
| ), | |
| 'Trig' -> ( | |
| Description: "Trig control signal." | |
| ) | |
| ], | |
| params: [ | |
| 'Frequency' -> ( | |
| Spec: \freq.asSpec, | |
| Description: "Frequency parameter." | |
| ), | |
| 'Gate' -> ( | |
| Spec: ControlSpec(0, 1, step: 1, default: 0), // TODO: DRY the gate/reset/boolean specs | |
| Description: "Gate parameter." | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| out_Frequency, | |
| out_Gate, | |
| out_Trig, | |
| param_Frequency, | |
| param_Gate | |
| | | |
| var sig_Gate = K2A.ar(param_Gate); | |
| var octs = ((param_Frequency.cpsoct-3)/10).clip(-0.5, 0.5); // 0.1 = 1 oct | |
| Out.ar(out_Frequency, K2A.ar(octs)); | |
| Out.ar(out_Gate, sig_Gate); | |
| Out.ar(out_Trig, Trig.ar(sig_Gate, 1/SampleRate.ir)); // TODO: too short a trig? Do the 1/60 thing? | |
| } | |
| ), | |
| ( | |
| // Status: partly tested. TODO: review modulation input mapping | |
| name: 'FShift', | |
| description: "Frequency shifter.", | |
| inputs: [ | |
| 'Left' -> ( | |
| Description: "Left audio input." | |
| ), | |
| 'Right' -> ( | |
| Description: "Right audio input." | |
| ), | |
| 'FM' -> ( | |
| Description: "Control signal for frequency shift modulation." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Left' -> ( | |
| Description: "Left shifted signal." | |
| ), | |
| 'Right' -> ( | |
| Description: "Right shifted signal." | |
| ), | |
| ], | |
| params: [ | |
| 'Frequency' -> ( | |
| Spec: ControlSpec(-2000, 2000, 'lin', 0, 0, "Hz"), | |
| LagTime: 0.1, | |
| Description: "Frequency shift. `-2000` Hz ... `+2000` Hz." | |
| ), | |
| 'FM' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Frequency modulation amount. `-1.0` ... `+1.0`." | |
| ), | |
| ], | |
| visuals: [ | |
| 'Frequency' -> ( | |
| Spec: ControlSpec(-2000, 2000, 'lin', 0, 0, "Hz"), | |
| Description: "Frequency shift." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Left, | |
| in_Right, | |
| in_FM, | |
| out_Left, | |
| out_Right, | |
| param_Frequency, | |
| param_FM, | |
| visual_Frequency | |
| | | |
| var frequencySpec = ControlSpec(-2000, 2000, 'lin', 0, 0, "Hz"); | |
| var sig_Left = In.ar(in_Left); | |
| var sig_Right = In.ar(in_Right); | |
| var sig_FM = In.ar(in_FM); | |
| var frequency = frequencySpec.constrain(param_Frequency + (sig_FM * 2000 * param_FM)); | |
| var shifted = FreqShift.ar([sig_Left, sig_Right], frequency); | |
| Out.ar(out_Left, shifted[0]); | |
| Out.ar(out_Right, shifted[1]); | |
| Out.kr(visual_Frequency, frequency); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'HPFilter', | |
| description: "Resonant highpass SVF filter.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ), | |
| 'FM' -> ( | |
| Description: "Control input for frequency modulation." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Description: "Control input for resonance modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Filtered audio signal." | |
| ) | |
| ], | |
| params: [ | |
| 'AudioLevel' -> ( | |
| Spec: \amp.asSpec.copy.default_(1), // TODO: updated | |
| LagTime: 0.1, | |
| Description: "Audio level `0` ... `1.0`. Default is `1`." | |
| ), | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| LagTime: 0.1, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz. Default is `440` Hz." | |
| ), | |
| 'Resonance' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance `0` ... `1.0`. Default is `0`." | |
| ), | |
| 'FM' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Frequency modulation amount `-1.0` ... `1.0`." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance modulation amount `-1.0` ... `1.0`." | |
| ), | |
| ], | |
| visuals: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_FM, | |
| in_ResonanceModulation, | |
| out_Out, | |
| param_AudioLevel, | |
| param_Frequency, | |
| param_Resonance, | |
| param_FM, | |
| param_ResonanceModulation, | |
| visual_Frequency | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_FM = In.ar(in_FM); | |
| var sig_ResonanceModulation = In.ar(in_ResonanceModulation); | |
| var frequencySpec = \widefreq.asSpec; | |
| var resonanceSpec = \unipolar.asSpec; | |
| var sig_In_Atten = sig_In * param_AudioLevel; | |
| var frequency = frequencySpec.map(frequencySpec.unmap(param_Frequency) + (sig_FM * param_FM)); | |
| var resonance = resonanceSpec.map(resonanceSpec.unmap(param_Resonance) + (sig_ResonanceModulation * param_ResonanceModulation)); | |
| Out.kr(visual_Frequency, frequency); | |
| Out.ar( | |
| out_Out, | |
| SVF.ar( | |
| sig_In_Atten, | |
| frequency, | |
| resonance, | |
| lowpass: 0, | |
| bandpass: 0, | |
| highpass: 1, | |
| notch: 0, | |
| peak: 0 | |
| ) | |
| ); | |
| } | |
| ), | |
| /* | |
| ( | |
| name: 'Limit2', | |
| params: [ | |
| 'Threshold' -> \db.asSpec.copy.default_(-10), | |
| 'Attack' -> ControlSpec(0.1, 250, 'exp', 0, 10, "ms"), | |
| 'Release' -> ControlSpec(0.1, 1000, 'exp', 0, 100, "ms"), | |
| 'Ratio' -> ControlSpec(0, 20, default: 3), | |
| 'MakeUp' -> \db.asSpec.copy.maxval_(20).default_(0) | |
| ], | |
| ugenGraphFunc: { | | |
| param_Threshold, | |
| param_Attack, | |
| param_Release, | |
| param_Ratio, | |
| param_MakeUp, | |
| in_Left, | |
| in_Right, | |
| in_SideChain, | |
| connectedin_SideChain, | |
| out_Left, | |
| out_Right, | |
| visual_GR | |
| | | |
| var sig_Left = In.ar(in_Left, 1); | |
| var sig_Right = In.ar(in_Right, 1); | |
| var sig_dc = DC.ar(1); | |
| var control = (sig_Left + sig_Right) / 2; // TODO: possibly insert external side chain here | |
| // TODO: possibly insert sidechain HPF filter here | |
| var compressed = Compander.ar( | |
| [sig_Left, sig_Right, sig_dc], | |
| control, | |
| param_Threshold.dbamp, | |
| slopeAbove: 1/param_Ratio, | |
| clampTime: param_Attack/1000, | |
| relaxTime: param_Release/1000 | |
| ); | |
| Out.ar(out_Left, compressed[0] * param_MakeUp.dbamp); | |
| Out.ar(out_Right, compressed[1] * param_MakeUp.dbamp); | |
| Out.kr(visual_GR, compressed[2].ampdb); | |
| } | |
| ), | |
| */ | |
| ( | |
| // Status: tested | |
| name: 'LinMixer', | |
| description: "Mixer suited for control signals.", | |
| inputsDocSection: "\t- `In1` ... `In4`: Audio inputs 1 ... 4.", | |
| inputs: { | |
| 4.collect { |index| | |
| ("In"++(index+1)) -> ( | |
| Description: "Audio input" + (index+1) ++ "." | |
| ) | |
| }; | |
| }, | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Mixed signal." | |
| ), | |
| ], | |
| paramsDocSection: | |
| "\t- `In1` ... `In4`: Audio input 1 ... 4 levels TODO: range/spec." ++ "\n" ++ | |
| "\t- `Out`: Output level TODO: range/spec.", | |
| params: { | |
| 4.collect { |index| | |
| ("In"++(index+1)).asSymbol -> ( | |
| Spec: \unipolar.asSpec.copy.default_(1), | |
| LagTime: 0.1, | |
| Description: "Audio input" + (index+1) + "level TODO: range/spec." | |
| ) | |
| } ++ | |
| [ | |
| 'Out' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(1), | |
| LagTime: 0.1, | |
| Description: "Output level TODO: range/spec." | |
| ) | |
| ] | |
| }, | |
| ugenGraphFunc: { | |
| | | |
| in_In1, | |
| in_In2, | |
| in_In3, | |
| in_In4, | |
| out_Out, | |
| param_In1, | |
| param_In2, | |
| param_In3, | |
| param_In4, | |
| param_Out | |
| | | |
| var sig_In1 = In.ar(in_In1); | |
| var sig_In2 = In.ar(in_In2); | |
| var sig_In3 = In.ar(in_In3); | |
| var sig_In4 = In.ar(in_In4); | |
| Out.ar( | |
| out_Out, | |
| ( | |
| (sig_In1 * param_In1) + | |
| (sig_In2 * param_In2) + | |
| (sig_In3 * param_In3) + | |
| (sig_In4 * param_In4) | |
| ) * param_Out | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'LPFilter', | |
| description: "Resonant lowpass SVF filter.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ), | |
| 'FM' -> ( | |
| Description: "Control input for frequency modulation." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Description: "Control input for resonance modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Filtered audio signal." | |
| ) | |
| ], | |
| params: [ | |
| 'AudioLevel' -> ( | |
| Spec: \amp.asSpec.copy.default_(1), // TODO: updated | |
| LagTime: 0.1, | |
| Description: "Audio level `0` ... `1.0`. Default is `1`." | |
| ), | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| LagTime: 0.1, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz. Default is `440` Hz." | |
| ), | |
| 'Resonance' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance `0` ... `1.0`. Default is `0`." | |
| ), | |
| 'FM' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Frequency modulation amount `-1.0` ... `1.0`." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance modulation amount `-1.0` ... `1.0`." | |
| ), | |
| ], | |
| visuals: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_FM, | |
| in_ResonanceModulation, | |
| out_Out, | |
| param_AudioLevel, | |
| param_Frequency, | |
| param_Resonance, | |
| param_FM, | |
| param_ResonanceModulation, | |
| visual_Frequency | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_FM = In.ar(in_FM); | |
| var sig_ResonanceModulation = In.ar(in_ResonanceModulation); | |
| var frequencySpec = \widefreq.asSpec; | |
| var resonanceSpec = \unipolar.asSpec; | |
| var sig_In_Atten = sig_In * param_AudioLevel; | |
| var frequency = frequencySpec.map(frequencySpec.unmap(param_Frequency) + (sig_FM * param_FM)); | |
| var resonance = resonanceSpec.map(resonanceSpec.unmap(param_Resonance) + (sig_ResonanceModulation * param_ResonanceModulation)); | |
| Out.kr(visual_Frequency, frequency); | |
| Out.ar( | |
| out_Out, | |
| SVF.ar( | |
| sig_In_Atten, | |
| frequency, | |
| resonance, | |
| lowpass: 1, | |
| bandpass: 0, | |
| highpass: 0, | |
| notch: 0, | |
| peak: 0 | |
| ) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'LPLadder', | |
| description: "Lowpass ladder filter.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ), | |
| 'FM' -> ( | |
| Description: "Control input for frequency modulation." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Description: "Control input for resonance modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Filtered audio signal." | |
| ) | |
| ], | |
| params: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| LagTime: 0.1, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz. Default is `440` Hz." | |
| ), | |
| 'Resonance' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance `0` ... `1.0`. Default is `0`." | |
| ), | |
| 'FM' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Frequency modulation amount `-1.0` ... `1.0`." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance modulation amount `-1.0` ... `1.0`." | |
| ), | |
| ], | |
| visuals: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_FM, | |
| in_ResonanceModulation, | |
| out_Out, | |
| param_Frequency, | |
| param_Resonance, | |
| param_FM, | |
| param_ResonanceModulation, | |
| visual_Frequency | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_FM = In.ar(in_FM); | |
| var sig_ResonanceModulation = In.ar(in_ResonanceModulation); | |
| var frequencySpec = \widefreq.asSpec; | |
| var resonanceSpec = \unipolar.asSpec; | |
| var frequency = frequencySpec.map(frequencySpec.unmap(param_Frequency) + (sig_FM * param_FM)); | |
| var resonance = resonanceSpec.map(resonanceSpec.unmap(param_Resonance) + (sig_ResonanceModulation * param_ResonanceModulation)); | |
| var sig = MoogLadder.ar( | |
| sig_In, | |
| frequency, | |
| resonance, | |
| ); | |
| Out.ar(out_Out, sig); | |
| Out.kr(visual_Frequency, frequency); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'MGain', | |
| description: "Audio fader with db gain control and mute.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Attenuated audio signal." | |
| ) | |
| ], | |
| params: [ | |
| 'Gain' -> ( | |
| Spec: \db.asSpec.copy.maxval_(12).default_(0), | |
| // TODO LagTime: 0.1 | |
| Description: "Attenuation control. `-inf` ... `+12` dB." | |
| ), | |
| 'Mute' -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 1, 0, ""), | |
| // TODO LagTime: 0.1 | |
| Description: "If `1` signal is muted, otherwise not." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| out_Out, | |
| param_Gain, | |
| param_Mute | |
| | | |
| var sig_In = In.ar(in_In); | |
| var gain = param_Gain.dbamp * Lag.kr(Select.kr(param_Mute, [1, 0], 0.01)); | |
| Out.ar(out_Out, sig_In * gain); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| // Inspiration from A-121 | |
| name: 'MMFilter', | |
| description: "Resonant SVF multimode filter.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio input." | |
| ), | |
| 'FM' -> ( | |
| Description: "Control input for frequency modulation." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Description: "Control input for resonance modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Notch' -> ( | |
| Description: "Band-reject filtered audio signal." | |
| ), | |
| 'Highpass' -> ( | |
| Description: "Highpass filtered audio signal." | |
| ), | |
| 'Bandpass' -> ( | |
| Description: "Bandpass filtered audio signal." | |
| ), | |
| 'Lowpass' -> ( | |
| Description: "Lowpass filtered audio signal." | |
| ), | |
| ], | |
| params: [ | |
| 'AudioLevel' -> ( | |
| Spec: \amp.asSpec.copy.default_(1), // TODO: updated | |
| LagTime: 0.1, | |
| Description: "Audio level `0` ... `1.0`. Default is `1`." | |
| ), | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| LagTime: 0.1, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz. Default is `440` Hz." | |
| ), | |
| 'Resonance' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance `0` ... `1.0`. Default is `0`." | |
| ), | |
| 'FM' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Frequency modulation amount `-1.0` ... `1.0`." | |
| ), | |
| 'ResonanceModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Resonance modulation amount `-1.0` ... `1.0`." | |
| ), | |
| ], | |
| visuals: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| Description: "Cutoff frequency `0.1` ... `20000` Hz." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_FM, | |
| in_ResonanceModulation, | |
| out_Notch, | |
| out_Highpass, | |
| out_Bandpass, | |
| out_Lowpass, | |
| param_AudioLevel, | |
| param_Frequency, | |
| param_Resonance, | |
| param_FM, | |
| param_ResonanceModulation, | |
| visual_Frequency | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_FM = In.ar(in_FM); | |
| var sig_ResonanceModulation = In.ar(in_ResonanceModulation); | |
| var frequencySpec = \widefreq.asSpec; | |
| var resonanceSpec = \unipolar.asSpec; | |
| var sig_In_Atten = sig_In * param_AudioLevel; | |
| var frequency = frequencySpec.map(frequencySpec.unmap(param_Frequency) + (sig_FM * param_FM)); | |
| var resonance = resonanceSpec.map(resonanceSpec.unmap(param_Resonance) + (sig_ResonanceModulation * param_ResonanceModulation)); | |
| Out.kr(visual_Frequency, frequency); | |
| Out.ar( | |
| out_Notch, | |
| SVF.ar( | |
| sig_In_Atten, | |
| frequency, | |
| resonance, | |
| lowpass: 0, | |
| bandpass: 0, | |
| highpass: 0, | |
| notch: 1, | |
| peak: 0 | |
| ) | |
| ); | |
| Out.ar( | |
| out_Highpass, | |
| SVF.ar( | |
| sig_In_Atten, | |
| frequency, | |
| resonance, | |
| lowpass: 0, | |
| bandpass: 0, | |
| highpass: 1, | |
| notch: 0, | |
| peak: 0 | |
| ) | |
| ); | |
| Out.ar( | |
| out_Bandpass, | |
| SVF.ar( | |
| sig_In_Atten, | |
| frequency, | |
| resonance, | |
| lowpass: 0, | |
| bandpass: 1, | |
| highpass: 0, | |
| notch: 0, | |
| peak: 0 | |
| ) | |
| ); | |
| Out.ar( | |
| out_Lowpass, | |
| SVF.ar( | |
| sig_In_Atten, | |
| frequency, | |
| resonance, | |
| lowpass: 1, | |
| bandpass: 0, | |
| highpass: 0, | |
| notch: 0, | |
| peak: 0 | |
| ) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: partly tested, TODO: test in_Reset together with param_Reset | |
| // Inspiration from A-145 | |
| name: 'MultiLFO', | |
| description: "LFO featuring multiple waveforms.", | |
| inputs: [ | |
| 'Reset' -> ( | |
| Description: "Audio rate reset trigger: when signal is changed from 0 to 1 the LFO is retriggered.", | |
| Range: [0, 1] | |
| ), | |
| ], | |
| outputs: [ | |
| 'InvSaw' -> ( | |
| Description: "Inverted saw signal output.", | |
| Range: [-0.25, 0.25] | |
| ), | |
| 'Saw' -> ( | |
| Description: "Saw signal output.", | |
| Range: [-0.25, 0.25] | |
| ), | |
| 'Sine' -> ( | |
| Description: "Sine signal output.", | |
| Range: [-0.25, 0.25] | |
| ), | |
| 'Triangle' -> ( | |
| Description: "Triangle signal output.", | |
| Range: [-0.25, 0.25] | |
| ), | |
| 'Pulse' -> ( | |
| Description: "Pulse signal output.", | |
| Range: [-0.25, 0.25] | |
| ), | |
| ], | |
| params: [ | |
| 'Frequency' -> ( | |
| Description: "LFO Frequency `0.01` Hz ... `50` Hz.", | |
| Spec: ControlSpec(0.01, 50, 'exp', 0, 1, "Hz"), | |
| LagTime: 0.01 | |
| ), | |
| 'Reset' -> ( | |
| Description: "Manual LFO reset trigger.", | |
| Spec: \unipolar.asSpec.copy.step_(1), // TODO: generalize booleans (or triggers) | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Reset, | |
| out_InvSaw, | |
| out_Saw, | |
| out_Sine, | |
| out_Triangle, | |
| out_Pulse, | |
| param_Frequency, | |
| param_Reset | |
| | | |
| var sig_Reset = In.ar(in_Reset); | |
| // TODO: remove var retrig = (Trig.ar(sig_Reset) + Trig.kr(param_Reset)) > 0; // TODO: remove param? | |
| var retrig = (Trig.ar(sig_Reset, 1/SampleRate.ir) + Trig.ar(param_Reset, 1/SampleRate.ir)) > 0; // TODO: remove param? | |
| var invSawPhase = Phasor.ar(retrig, -1/SampleRate.ir*param_Frequency, 0.5, -0.5, 0.5); // TODO: Retrig in middle of saw ramp? | |
| var invSawSig = invSawPhase * 0.5; // +- 2.5V | |
| var sawPhase = Phasor.ar(retrig, 1/SampleRate.ir*param_Frequency, -0.5, 0.5, -0.5); // TODO: Retrig in middle of saw ramp? | |
| var sawSig = sawPhase * 0.5; // +- 2.5V | |
| var sinePhase = Phasor.ar(retrig, 1/SampleRate.ir*param_Frequency); | |
| var sineSig = SinOsc.ar(0, sinePhase.linlin(0, 1, 0, 2pi), 0.25); // +- 2.5V | |
| var trianglePhase = Phasor.ar(retrig, 1/SampleRate.ir*param_Frequency*2, -0.5, 1.5, -0.5); | |
| var triangleSig = trianglePhase.fold(-0.5, 0.5) * 0.5; // +- 2.5V | |
| var pulsePhase = Phasor.ar(retrig, 1/SampleRate.ir*param_Frequency*1, 0, 1, 0); | |
| var pulseSig = ((pulsePhase < 0.5)*0.5)-0.25; // +- 2.5V | |
| Out.ar( | |
| out_InvSaw, | |
| invSawSig | |
| ); | |
| Out.ar( | |
| out_Saw, | |
| sawSig | |
| ); | |
| Out.ar( | |
| out_Sine, | |
| sineSig | |
| ); | |
| Out.ar( | |
| out_Triangle, | |
| triangleSig | |
| ); | |
| Out.ar( | |
| out_Pulse, | |
| pulseSig | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| // Inspiration from A-110 (but no Sync input) | |
| name: 'MultiOsc', | |
| description: "Oscillator featuring multiple waveforms.", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| 'PWM' -> ( | |
| Description: "Control signal for pulse width modulation." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Sine' -> ( | |
| Description: "Sine wave oscillator output." | |
| ), | |
| 'Triangle' -> ( | |
| Description: "Triangle wave oscillator output." | |
| ), | |
| 'Saw' -> ( | |
| Description: "Saw wave oscillator output." | |
| ), | |
| 'Pulse' -> ( | |
| Description: "Pulse wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'PulseWidth' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(0.5), | |
| LagTime: 0.01, | |
| Description: "Pulse oscillator pulse width." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| 'PWM' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.01, | |
| Description: "Pulse width modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| in_PWM, | |
| out_Sine, | |
| out_Triangle, | |
| out_Saw, | |
| out_Pulse, | |
| param_Range, | |
| param_Tune, | |
| param_FM, | |
| param_PulseWidth, | |
| param_PWM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var sig_PWM = In.ar(in_PWM); | |
| var fullRange = ControlSpec.new(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( // TODO: optimization possibility - implement overridable set handlers and do this calculation in sclang rather than server? | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps | |
| ); | |
| var pulseWidth = ( | |
| param_PulseWidth + (sig_PWM * param_PWM) | |
| // .clip(0, 1); // TODO: remove ? | |
| ).linlin(0, 1, 0.05, 0.95); // TODO: ??add to other Pulse oscs too | |
| Out.ar( | |
| out_Sine, | |
| SinOsc.ar(frequency) * 0.5 | |
| ); | |
| Out.ar( | |
| out_Triangle, | |
| LFTri.ar(frequency) * 0.5 // not band limited | |
| ); | |
| Out.ar( | |
| out_Saw, | |
| Saw.ar(frequency) * 0.5 | |
| ); | |
| Out.ar( | |
| out_Pulse, | |
| Pulse.ar(frequency, pulseWidth / 2) * 0.5 | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: experimental | |
| // Inspiration from A-110 (but no Sync input) | |
| name: 'MultiOscExp', | |
| experimental: true, | |
| description: "Oscillator featuring multiple waveforms (w/ linear FM features).", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| 'LinFM' -> ( | |
| Description: "Control signal for linear frequency modulation." | |
| ), | |
| 'PWM' -> ( | |
| Description: "Control signal for pulse width modulation." | |
| ), | |
| /* | |
| TODO | |
| 'PM' -> ( | |
| Description: "Control signal for phase modulation" | |
| ), | |
| */ | |
| ], | |
| outputs: [ | |
| 'Sine' -> ( | |
| Description: "Sine wave oscillator output." | |
| ), | |
| 'Triangle' -> ( | |
| Description: "Triangle wave oscillator output." | |
| ), | |
| 'Saw' -> ( | |
| Description: "Saw wave oscillator output." | |
| ), | |
| 'Pulse' -> ( | |
| Description: "Pulse wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'PulseWidth' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(0.5), | |
| LagTime: 0.01, | |
| Description: "Pulse oscillator pulse width." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| 'LinFM' -> ( | |
| Spec: ControlSpec.new(0, 10000, 'lin', nil, 0, "Hz"), | |
| LagTime: 0.2, | |
| Description: "Linear frequency modulation amount." | |
| ), | |
| /* | |
| TODO | |
| 'PM' -> ( | |
| Spec: ControlSpec.new(0, 5, 'lin', 0.01, 0, ""), | |
| LagTime: 0.2 | |
| ), | |
| */ | |
| 'PWM' -> ( | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.01, | |
| Description: "Pulse width modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| in_LinFM, | |
| in_PWM, | |
| out_Sine, | |
| out_Triangle, | |
| out_Saw, | |
| out_Pulse, | |
| param_Range, | |
| param_Tune, | |
| param_FM, | |
| param_LinFM, | |
| param_PulseWidth, | |
| param_PWM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var sig_LinFM = In.ar(in_LinFM); | |
| var sig_PWM = In.ar(in_PWM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( // TODO: optimization - implement overridable set handlers and do this calculation in sclang rather than server | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps + (sig_LinFM * param_LinFM) | |
| ); | |
| var pulseWidth = ( | |
| param_PulseWidth + (sig_PWM * param_PWM) | |
| ).clip(0, 1); | |
| Out.ar( | |
| out_Sine, | |
| SinOsc.ar(frequency) * 0.5 | |
| ); | |
| Out.ar( | |
| out_Triangle, | |
| LFTri.ar(frequency) * 0.5 // not band limited | |
| ); | |
| Out.ar( | |
| out_Saw, | |
| Saw.ar(frequency) * 0.5 | |
| ); | |
| Out.ar( | |
| out_Pulse, | |
| Pulse.ar(frequency, pulseWidth / 2) * 0.5 | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested, TODO: make a script | |
| name: 'Noise', | |
| description: "White noise generator.", | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Noise signal." | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| out_Out | |
| | | |
| Out.ar( | |
| out_Out, | |
| WhiteNoise.ar | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: not tested | |
| name: 'OGain', | |
| description: "8-in/8-out audio fader with db gain control and mute.", | |
| inputsDocSection: "\t- `In1` ... `In8`: Audio inputs.", | |
| inputs: { | |
| 8.collect { |index| | |
| ("In"++(index+1)).asSymbol -> ( | |
| Description: "Audio input" + (index+1) ++ "." | |
| ) | |
| }; | |
| }, | |
| outputsDocSection: "\t- `Out1` ... `Out8`: Attenuated audio signal outputs.", | |
| outputs: { | |
| 8.collect { |index| | |
| ("Out"++(index+1)).asSymbol -> ( | |
| Description: "Attenuated audio signal output" + (index+1) ++ "." | |
| ) | |
| }; | |
| }, | |
| params: [ | |
| 'Gain' -> ( | |
| Spec: \db.asSpec.copy.maxval_(12).default_(0), | |
| // TODO LagTime: 0.1 | |
| Description: "Attenuation control. `-inf` ... `+12` dB." | |
| ), | |
| 'Mute' -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 1, 0, ""), | |
| // TODO LagTime: 0.1 | |
| Description: "If `1` signal is muted, otherwise not." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In1, | |
| in_In2, | |
| in_In3, | |
| in_In4, | |
| in_In5, | |
| in_In6, | |
| in_In7, | |
| in_In8, | |
| out_Out1, | |
| out_Out2, | |
| out_Out3, | |
| out_Out4, | |
| out_Out5, | |
| out_Out6, | |
| out_Out7, | |
| out_Out8, | |
| param_Gain, | |
| param_Mute | |
| | | |
| var sig_In1 = In.ar(in_In1); | |
| var sig_In2 = In.ar(in_In2); | |
| var sig_In3 = In.ar(in_In3); | |
| var sig_In4 = In.ar(in_In4); | |
| var sig_In5 = In.ar(in_In5); | |
| var sig_In6 = In.ar(in_In6); | |
| var sig_In7 = In.ar(in_In7); | |
| var sig_In8 = In.ar(in_In8); | |
| var gain = param_Gain.dbamp * Lag.kr(Select.kr(param_Mute, [1, 0], 0.01)); | |
| Out.ar(out_Out1, sig_In1 * gain); | |
| Out.ar(out_Out2, sig_In2 * gain); | |
| Out.ar(out_Out3, sig_In3 * gain); | |
| Out.ar(out_Out4, sig_In4 * gain); | |
| Out.ar(out_Out5, sig_In5 * gain); | |
| Out.ar(out_Out6, sig_In6 * gain); | |
| Out.ar(out_Out7, sig_In7 * gain); | |
| Out.ar(out_Out8, sig_In8 * gain); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'Pan', | |
| description: "Stereo panner with monophonic input.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Mono audio signal." | |
| ), | |
| 'PositionModulation' -> ( | |
| Description: "Right audio signal." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Left' -> ( | |
| Description: "Left audio signal." | |
| ), | |
| 'Right' -> ( | |
| Description: "Right audio signal." | |
| ), | |
| ], | |
| params: [ | |
| 'Position' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Pan position `-1.0` ... `1.0` referring to left ... right panning." | |
| ), | |
| 'PositionModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Pan position modulation amount `-1.0` ... `1.0`." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_PositionModulation, | |
| out_Left, | |
| out_Right, | |
| param_Position, | |
| param_PositionModulation | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_PositionModulation = In.ar(in_PositionModulation); | |
| var sig = Pan2.ar(sig_In, param_Position + (sig_PositionModulation * param_PositionModulation)); | |
| Out.ar(out_Left, sig[0]); | |
| Out.ar(out_Right, sig[1]); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| // TODO: or just extend LinMixer, make it bipolar? | |
| name: 'PolMixer', | |
| experimental: true, | |
| inputsDocSection: "\t- `In1` ... `In4`: Audio inputs 1 ... 4", | |
| inputs: { | |
| 4.collect { |index| | |
| ("In"++(index+1)).asSymbol -> ( | |
| Description: "Audio input" + (index+1) ++ "." | |
| ) | |
| }; | |
| }, | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Mixed signal." | |
| ), | |
| ], | |
| paramsDocSection: | |
| "\t- `In1` ... `In4`: Audio input 1 ... 4 levels TODO: range/spec." ++ "\n" ++ | |
| "\t- `Out`: Output level TODO: range/spec.", | |
| params: { | |
| 4.collect { |index| | |
| ("In"++(index+1)).asSymbol -> ( | |
| Spec: \bipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Audio input" + (index+1) + "level TODO: range/spec." | |
| ) | |
| } ++ | |
| [ | |
| 'Out' -> ( // TODO: 0 is default, is that right? | |
| Spec: \unipolar.asSpec, | |
| LagTime: 0.1, | |
| Description: "Output level TODO: range/spec." | |
| ) | |
| ] | |
| }, | |
| ugenGraphFunc: { | |
| | | |
| in_In1, | |
| in_In2, | |
| in_In3, | |
| in_In4, | |
| out_Out, | |
| param_In1, | |
| param_In2, | |
| param_In3, | |
| param_In4, | |
| param_Out | |
| | | |
| var sig_In1 = In.ar(in_In1); | |
| var sig_In2 = In.ar(in_In2); | |
| var sig_In3 = In.ar(in_In3); | |
| var sig_In4 = In.ar(in_In4); | |
| Out.ar( | |
| out_Out, | |
| ( | |
| (sig_In1 * param_In1) + | |
| (sig_In2 * param_In2) + | |
| (sig_In3 * param_In3) + | |
| (sig_In4 * param_In4) | |
| ) * param_Out | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'PNoise', | |
| description: "Pink noise generator.", | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Noise signal." | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| out_Out | |
| | | |
| Out.ar( | |
| out_Out, | |
| PinkNoise.ar | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: partly tested. TODO: review modulation input mapping | |
| name: 'PShift', | |
| description: "Pitch shifter.", | |
| inputs: [ | |
| 'Left' -> ( | |
| Description: "Left audio signal." | |
| ), | |
| 'Right' -> ( | |
| Description: "Right audio signal." | |
| ), | |
| 'PitchRatioModulation' -> ( | |
| Description: "Pitch ratio modulation amount." | |
| ), | |
| 'PitchDispersionModulation' -> ( | |
| Description: "Pitch dispersion modulation amount." | |
| ), | |
| 'TimeDispersionModulation' -> ( | |
| Description: "Time dispersion modulation amount." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Left' -> ( | |
| Description: "Left processed audio signal." | |
| ), | |
| 'Right' -> ( | |
| Description: "Right processed audio signal." | |
| ), | |
| ], | |
| params: [ | |
| 'PitchRatio' -> ( | |
| Spec: ControlSpec(0, 4, default: 1), | |
| LagTime: 0.1, | |
| Description: "Pitch ratio: `0` ... `4`. Default is `1`." | |
| ), | |
| 'PitchDispersion' -> ( | |
| Spec: ControlSpec(0, 4), | |
| LagTime: 0.1, | |
| Description: "Pitch dispersion: `0` ... `4`. Default is `0`." | |
| ), | |
| 'TimeDispersion' -> ( | |
| Spec: ControlSpec(0, 1), | |
| LagTime: 0.1, | |
| Description: "Time dispersion: `0` ... `1`. Default is `0`." | |
| ), | |
| 'PitchRatioModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| Description: "Pitch ratio modulation amount: `-1` ... `1`." | |
| ), | |
| 'PitchDispersionModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| Description: "Pitch dispersion modulation amount: `-1` ... `1`." | |
| ), | |
| 'TimeDispersionModulation' -> ( | |
| Spec: \bipolar.asSpec, | |
| Description: "Time dispersion modulation amount: `-1` ... `1`." | |
| ), | |
| ], | |
| visuals: [ | |
| 'PitchRatio' -> ( | |
| Spec: ControlSpec(0, 4, default: 1), | |
| Description: "Time dispersion modulation amount: `-1` ... `1`." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Left, | |
| in_Right, | |
| in_PitchRatioModulation, | |
| in_PitchDispersionModulation, | |
| in_TimeDispersionModulation, | |
| out_Left, | |
| out_Right, | |
| param_PitchRatio, | |
| param_PitchDispersion, | |
| param_TimeDispersion, | |
| param_PitchRatioModulation, | |
| param_PitchDispersionModulation, | |
| param_TimeDispersionModulation, | |
| visual_PitchRatio | |
| // TODO: visual_PitchDispersion | |
| // TODO: visual_TimeDispersion | |
| | | |
| var pitchRatioSpec = ControlSpec(0, 4, default: 1); | |
| var pitchDispersionSpec = ControlSpec(0, 4); | |
| var timeDispersionSpec = ControlSpec(0, 1); | |
| var sig_Left = In.ar(in_Left); | |
| var sig_Right = In.ar(in_Right); | |
| var sig_PitchRatioModulation = In.ar(in_PitchRatioModulation); | |
| var sig_PitchDispersionModulation = In.ar(in_PitchDispersionModulation); | |
| var sig_TimeDispersionModulation = In.ar(in_TimeDispersionModulation); | |
| var pitchRatio = pitchRatioSpec.map( | |
| pitchRatioSpec.unmap(param_PitchRatio) + | |
| (sig_PitchRatioModulation * param_PitchRatioModulation) | |
| ); | |
| var shifted = PitchShift.ar( | |
| [sig_Left, sig_Right], | |
| 0.2, | |
| pitchRatio, | |
| pitchDispersionSpec.map( | |
| pitchDispersionSpec.unmap(param_PitchDispersion) + | |
| (sig_PitchDispersionModulation * param_PitchDispersionModulation) | |
| ), | |
| timeDispersionSpec.map( | |
| timeDispersionSpec.unmap(param_TimeDispersion) + | |
| (sig_TimeDispersionModulation * param_TimeDispersionModulation) | |
| ) / 5 // time dispersion cannot exceed windowSize (0.2) | |
| ); | |
| Out.ar(out_Left, shifted[0]); | |
| Out.ar(out_Right, shifted[1]); | |
| Out.kr(visual_PitchRatio, pitchRatio); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'PulseOsc', | |
| description: "Pulse/square oscillator with pulse width control.", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| 'PWM' -> ( | |
| Description: "Control signal for pulse width modulation." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Pulse wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'PulseWidth' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(0.5), | |
| LagTime: 0.01, | |
| Description: "Pulse oscillator Pulse width." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| 'PWM' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(0.4), | |
| LagTime: 0.01, | |
| Description: "Pulse width modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| in_PWM, | |
| out_Out, | |
| param_Range, | |
| param_Tune, | |
| param_FM, | |
| param_PulseWidth, | |
| param_PWM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var sig_PWM = In.ar(in_PWM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps | |
| ); | |
| var pulseWidth = ( | |
| param_PulseWidth + (sig_PWM * param_PWM) | |
| ).clip(0, 1); | |
| Out.ar( | |
| out_Out, | |
| Pulse.ar(frequency, pulseWidth / 2) * 0.5 | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: experimental | |
| name: 'PulseOscExp', | |
| experimental: true, | |
| description: "Pulse/square oscillator with pulse width control (w/ linear FM features).", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| 'LinFM' -> ( | |
| Description: "Control signal for linear frequency modulation." | |
| ), | |
| 'PWM' -> ( | |
| Description: "Control signal for pulse width modulation." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Pulse wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'PulseWidth' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(0.5), | |
| LagTime: 0.01, | |
| Description: "Pulse oscillator pulse width." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| 'PWM' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(0.4), | |
| LagTime: 0.01, | |
| Description: "Pulse width modulation amount." | |
| ), | |
| 'LinFM' -> ( | |
| Spec: ControlSpec.new(0, 10000, 'lin', nil, 0, "Hz"), | |
| LagTime: 0.2, | |
| Description: "Linear frequency modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| in_LinFM, | |
| in_PWM, | |
| out_Out, | |
| param_Range, | |
| param_Tune, | |
| param_FM, | |
| param_LinFM, | |
| param_PulseWidth, | |
| param_PWM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var sig_LinFM = In.ar(in_LinFM); | |
| var sig_PWM = In.ar(in_PWM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps + (sig_LinFM * param_LinFM) | |
| ); | |
| var pulseWidth = ( | |
| param_PulseWidth + (sig_PWM * param_PWM) | |
| ).clip(0, 1); | |
| Out.ar( | |
| out_Out, | |
| Pulse.ar(frequency, pulseWidth / 2) * 0.5 | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: not tested | |
| name: 'QGain', | |
| description: "4-in/4-out audio fader with db gain control and mute.", | |
| inputsDocSection: "\t- `In1` ... `In4`: Audio inputs", | |
| inputs: { | |
| 4.collect { |index| | |
| ("In"++(index+1)).asSymbol -> ( | |
| Description: "Audio input" + (index+1) | |
| ) | |
| }; | |
| }, | |
| outputsDocSection: "\t- `Out1` ... `Out4`: Attenuated audio signal outputs", | |
| outputs: { | |
| 4.collect { |index| | |
| ("Out"++(index+1)).asSymbol -> ( | |
| Description: "Attenuated audio signal output" + (index+1) ++ "." | |
| ) | |
| }; | |
| }, | |
| params: [ | |
| 'Gain' -> ( | |
| Spec: \db.asSpec.copy.maxval_(12).default_(0), | |
| // TODO LagTime: 0.1 | |
| Description: "Attenuation control. `-inf` ... `+12` dB." | |
| ), | |
| 'Mute' -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 1, 0, ""), | |
| // TODO LagTime: 0.1 | |
| Description: "If `1` signal is muted, otherwise not." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In1, | |
| in_In2, | |
| in_In3, | |
| in_In4, | |
| out_Out1, | |
| out_Out2, | |
| out_Out3, | |
| out_Out4, | |
| param_Gain, | |
| param_Mute | |
| | | |
| var sig_In1 = In.ar(in_In1); | |
| var sig_In2 = In.ar(in_In2); | |
| var sig_In3 = In.ar(in_In3); | |
| var sig_In4 = In.ar(in_In4); | |
| var gain = param_Gain.dbamp * Lag.kr(Select.kr(param_Mute, [1, 0], 0.01)); | |
| Out.ar(out_Out1, sig_In1 * gain); | |
| Out.ar(out_Out2, sig_In2 * gain); | |
| Out.ar(out_Out3, sig_In3 * gain); | |
| Out.ar(out_Out4, sig_In4 * gain); | |
| } | |
| ), | |
| ( | |
| // status: untested | |
| // From https://en.wikibooks.org/wiki/Designing_Sound_in_SuperCollider/Schroeder_reverb | |
| name: 'Rev1', | |
| experimental: true, | |
| description: "Schroeder reverb.", | |
| params: [ | |
| 'Volume' -> ( | |
| Spec: \db.asSpec.copy.maxval_(12).default_(-10), | |
| LagTime: 0.1 | |
| ), | |
| 'PreDelay' -> ( | |
| Spec: ControlSpec(1, 1000, default: 64, units: "ms"), | |
| LagTime: 0.5 | |
| ), | |
| 'DelTime_1' -> ( | |
| Spec: ControlSpec(64, 1000, default: 101, units: "ms"), // minimum = s.options.blockize | |
| LagTime: 0.5 | |
| ), | |
| 'DelTime_2' -> ( | |
| Spec: ControlSpec(64, 1000, default: 143, units: "ms"), // minimum = s.options.blockize | |
| LagTime: 0.5 | |
| ), | |
| 'DelTime_3' -> ( | |
| Spec: ControlSpec(64, 1000, default: 165, units: "ms"), // minimum = s.options.blockize | |
| LagTime: 0.5 | |
| ), | |
| 'DelTime_4' -> ( | |
| Spec: ControlSpec(64, 1000, default: 177, units: "ms"), // minimum = s.options.blockize | |
| LagTime: 0.5 | |
| ), | |
| 'DelAtten_1' -> ( | |
| Spec: \unipolar.asSpec.copy.maxval_(0.5).default_(0.4), | |
| LagTime: 0.1 | |
| ), | |
| 'DelAtten_2' -> ( | |
| Spec: \unipolar.asSpec.copy.maxval_(0.5).default_(0.37), | |
| LagTime: 0.1 | |
| ), | |
| 'DelAtten_3' -> ( | |
| Spec: \unipolar.asSpec.copy.maxval_(0.5).default_(0.333), | |
| LagTime: 0.1 | |
| ), | |
| 'DelAtten_4' -> ( | |
| Spec: \unipolar.asSpec.copy.maxval_(0.5).default_(0.3), | |
| LagTime: 0.1 | |
| ) | |
| ], | |
| ugenGraphFunc: { | | |
| param_Volume, | |
| param_PreDelay, | |
| param_DelTime_1, | |
| param_DelTime_2, | |
| param_DelTime_3, | |
| param_DelTime_4, | |
| param_DelAtten_1, | |
| param_DelAtten_2, | |
| param_DelAtten_3, | |
| param_DelAtten_4, | |
| in_Left, | |
| in_Right, | |
| out_Left, | |
| out_Right | |
| | | |
| var sig_Left = In.ar(in_Left, 1); | |
| var sig_Right = In.ar(in_Right, 1); | |
| var amp = param_Volume.dbamp; | |
| // Here we give delay times in milliseconds, convert to seconds, | |
| // then compensate with ControlDur for the one-block delay | |
| // which is always introduced when using the LocalIn/Out fdbk loop | |
| var deltimes = [param_DelTime_1, param_DelTime_2, param_DelTime_3, param_DelTime_4] * 0.001 - ControlDur.ir; | |
| // Read our 4-channel delayed signals back from the feedback loop | |
| var delrd = LocalIn.ar(4); | |
| // This will be our eventual output, which will also be recirculated | |
| var output = [sig_Left, sig_Right] + delrd[[0,1]]; | |
| // Cross-fertilise the four delay lines with each other: | |
| var sig = [output[0]+output[1], output[0]-output[1], delrd[2]+delrd[3], delrd[2]-delrd[3]]; | |
| sig = [sig[0]+sig[2], sig[1]+sig[3], sig[0]-sig[2], sig[1]-sig[3]]; | |
| // Attenutate the delayed signals so they decay: | |
| sig = sig * [param_DelAtten_1, param_DelAtten_2, param_DelAtten_3, param_DelAtten_4]; | |
| // Apply the delays and send the signals into the feedback loop | |
| LocalOut.ar(DelayC.ar(sig, deltimes, deltimes)); | |
| output = DelayC.ar(output, 1, param_PreDelay/1000); | |
| Out.ar(out_Left, output[0] * amp); | |
| Out.ar(out_Right, output[1] * amp); | |
| } | |
| ), | |
| ( | |
| // Status: partly tested | |
| name: 'RingMod', | |
| description: "Ring modulator.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio signal input." | |
| ), | |
| 'Carrier' -> ( | |
| Description: "Carrier signal input." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Ring modulated audio signal." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_Carrier, // TODO: naming? | |
| out_Out | |
| | | |
| Out.ar( | |
| out_Out, | |
| In.ar(in_In) * In.ar(in_Carrier) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: partly tested | |
| name: 'SampHold', | |
| description: "Sample and hold module.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio signal input." | |
| ), | |
| 'Trig' -> ( | |
| Description: "Trigger signal input." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Processed audio signal." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| in_Trig, | |
| out_Out | |
| | | |
| var sig_In = In.ar(in_In); | |
| var sig_Trig = In.ar(in_Trig); | |
| Out.ar( | |
| out_Out, | |
| Latch.ar(sig_In, sig_Trig) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'SawOsc', | |
| description: "Sawtooth oscillator.", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Saw wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| out_Out, | |
| param_Range, | |
| param_Tune, | |
| param_FM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( // TODO: optimization possibility - implement overridable set handlers and do this calculation in sclang rather than server? | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps | |
| ); | |
| Out.ar( | |
| out_Out, | |
| Saw.ar(frequency) * 0.5 | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: experimental | |
| name: 'SawOscExp', | |
| experimental: true, | |
| description: "Sawtooth oscillator (w/ linear FM features).", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| 'LinFM' -> ( | |
| Description: "Control signal for linear frequency modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Saw wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| 'LinFM' -> ( | |
| Spec: ControlSpec.new(0, 10000, 'lin', nil, 0, "Hz"), | |
| LagTime: 0.2, | |
| Description: "Linear frequency modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| in_LinFM, | |
| out_Out, | |
| param_Range, | |
| param_Tune, | |
| param_FM, | |
| param_LinFM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var sig_LinFM = In.ar(in_LinFM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( // TODO: optimization - implement overridable set handlers and do this calculation in sclang rather than server | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps + (sig_LinFM * param_LinFM) | |
| ); | |
| Out.ar( | |
| out_Out, | |
| Saw.ar(frequency) * 0.5 | |
| ); | |
| } | |
| ), | |
| ( | |
| // TODO: align out and param naming wrt Xyz_1 vs Xyz1 | |
| // Status: not tested | |
| // Inspiration from A-155 | |
| name: 'Seq1', | |
| experimental: true, | |
| description: "Control signal sequencer.", | |
| inputs: [ | |
| 'Clock' -> ( | |
| Description: "Clock input." | |
| ), | |
| 'Reset' -> ( | |
| Description: "Reset input." | |
| ), | |
| 'SampleAndHoldCtrl1' -> ( | |
| Description: "Sample and hold control 1." | |
| ), | |
| 'GlideCtrl1' -> ( | |
| Description: "Glide control input 1." | |
| ), | |
| 'SampleAndHoldCtrl2' -> ( | |
| Description: "Sample and hold control 2." | |
| ), | |
| 'GlideCtrl2' -> ( | |
| Description: "Glide control input 2." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Trig1' -> ( | |
| Description: "Trigger output 1." | |
| ), | |
| 'Trig2' -> ( | |
| Description: "Trigger output 2." | |
| ), | |
| 'Gate' -> ( | |
| Description: "Gate output." | |
| ), | |
| 'PreOut1' -> ( | |
| Description: "Pre-output 1." | |
| ), | |
| 'Out1' -> ( | |
| Description: "Output 1." | |
| ), | |
| 'PreOut2' -> ( | |
| Description: "Pre-output 2." | |
| ), | |
| 'Out2' -> ( | |
| Description: "Output 2." | |
| ) | |
| ], | |
| params: { | |
| var numSteps = 8; | |
| var numRows = 2; | |
| var result = [ | |
| 'Reset' -> ( | |
| Spec: \unipolar.asSpec.copy.step_(1), // TODO | |
| Description: "Manual reset." | |
| ), | |
| 'Step' -> ( | |
| Spec: \unipolar.asSpec.copy.step_(1), // TODO | |
| Description: "Manual forward step." | |
| ), | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(0, 2, step: 1, default: 0), // 0 = 1V, 1 = 2V, 2 = 4V | |
| Description: "Output range: `0`, `1` or `2`, specifying ranges `0.1`, `0.2`, `0.4` respectively." | |
| ), | |
| 'Scale' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(1), // TODO | |
| Description: "TODO" | |
| ), | |
| 'Glide_1' -> ( | |
| Spec: \unipolar.asSpec, // TODO: longer, up to 10 seconds lag as in Softube slew | |
| Description: "Glide time 1." | |
| ), | |
| 'Glide_2' -> ( | |
| Spec: \unipolar.asSpec, // TODO: longer, up to 10 seconds lag as in Softube slew | |
| Description: "Glide time 2." | |
| ) | |
| ] ++ | |
| ( | |
| numRows.collect { |rowIndex| | |
| numSteps.collect { |stepIndex| | |
| "Trig_"++(rowIndex+1)++"_"++(stepIndex+1) -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 1, 0, ""), | |
| Description: "Tigger for row"+(rowIndex+1)+"step"+(stepIndex+1) | |
| ) | |
| } ++ | |
| numSteps.collect { |stepIndex| | |
| "Value_"++(rowIndex+1)++"_"++(stepIndex+1) -> ( | |
| Spec: \bipolar.asSpec, // TODO: probably make this unipolar to comply with original module | |
| Description: "Value for row"+(rowIndex+1)+"step"+(stepIndex+1) | |
| ) | |
| } | |
| }.flatten ++ | |
| numSteps.collect { |stepIndex| | |
| "Gate_"++(stepIndex+1) -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 1, 0, ""), // TODO: DRY the gate/reset/boolean specs | |
| Description: "Gate for step"+(stepIndex+1) | |
| ) | |
| } | |
| ).collect { |assoc| assoc.key.asSymbol -> assoc.value }; | |
| result; // TODO: remember bug, perform validation that ensures array includes _symbol_ -> definition associations | |
| }, | |
| visuals: [ | |
| 'Position' -> ( | |
| Spec: \unipolar.asSpec, | |
| Description: "Sequencer position: `0` ... `7`." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Clock, | |
| in_Reset, | |
| in_SampleAndHoldCtrl1, | |
| in_GlideCtrl1, | |
| in_SampleAndHoldCtrl2, | |
| in_GlideCtrl2, | |
| out_Trig1, | |
| out_Trig2, | |
| out_Gate, | |
| out_PreOut1, | |
| out_Out1, | |
| out_PreOut2, | |
| out_Out2, | |
| param_Reset, | |
| param_Step, | |
| param_Range, | |
| param_Scale, | |
| param_Glide_1, | |
| param_Glide_2, | |
| param_Trig_1_1, | |
| param_Trig_1_2, | |
| param_Trig_1_3, | |
| param_Trig_1_4, | |
| param_Trig_1_5, | |
| param_Trig_1_6, | |
| param_Trig_1_7, | |
| param_Trig_1_8, | |
| param_Trig_2_1, | |
| param_Trig_2_2, | |
| param_Trig_2_3, | |
| param_Trig_2_4, | |
| param_Trig_2_5, | |
| param_Trig_2_6, | |
| param_Trig_2_7, | |
| param_Trig_2_8, | |
| param_Value_1_1, | |
| param_Value_1_2, | |
| param_Value_1_3, | |
| param_Value_1_4, | |
| param_Value_1_5, | |
| param_Value_1_6, | |
| param_Value_1_7, | |
| param_Value_1_8, | |
| param_Value_2_1, | |
| param_Value_2_2, | |
| param_Value_2_3, | |
| param_Value_2_4, | |
| param_Value_2_5, | |
| param_Value_2_6, | |
| param_Value_2_7, | |
| param_Value_2_8, | |
| param_Gate_1, | |
| param_Gate_2, | |
| param_Gate_3, | |
| param_Gate_4, | |
| param_Gate_5, | |
| param_Gate_6, | |
| param_Gate_7, | |
| param_Gate_8, | |
| visual_Position | |
| | | |
| var sig_Clock = In.ar(in_Clock); | |
| var sig_Reset = In.ar(in_Reset); | |
| var sig_SampleAndHoldCtrl1 = In.ar(in_SampleAndHoldCtrl1); | |
| var sig_GlideCtrl1 = In.ar(in_GlideCtrl1); | |
| var sig_SampleAndHoldCtrl2 = In.ar(in_SampleAndHoldCtrl2); | |
| var sig_GlideCtrl2 = In.ar(in_GlideCtrl2); | |
| var reset = (Trig1.ar(sig_Reset, 1/SampleRate.ir) + Trig.ar(param_Reset, 1/SampleRate.ir)) > 0; // TODO: remove param? | |
| var clock = (Trig1.ar(sig_Clock, 1/SampleRate.ir) + Trig.ar(param_Step, 1/SampleRate.ir)) > 0; // TODO: remove param? | |
| var trigSeq1 = Dseq( | |
| [ | |
| param_Trig_1_1, param_Trig_1_2, param_Trig_1_3, param_Trig_1_4, param_Trig_1_5, param_Trig_1_6, param_Trig_1_7, param_Trig_1_8 | |
| ], | |
| inf | |
| ); | |
| var trigSeq2 = Dseq( | |
| [ | |
| param_Trig_2_1, param_Trig_2_2, param_Trig_2_3, param_Trig_2_4, param_Trig_2_5, param_Trig_2_6, param_Trig_2_7, param_Trig_2_8 | |
| ], | |
| inf | |
| ); | |
| var gateSeq = Dseq( | |
| [ | |
| param_Gate_1, param_Gate_2, param_Gate_3, param_Gate_4, param_Gate_5, param_Gate_6, param_Gate_7, param_Gate_8 | |
| ], | |
| inf | |
| ); | |
| var valueSeq1 = Dseq( | |
| [ | |
| param_Value_1_1, param_Value_1_2, param_Value_1_3, param_Value_1_4, param_Value_1_5, param_Value_1_6, param_Value_1_7, param_Value_1_8 | |
| ], | |
| inf | |
| ); | |
| var valueSeq2 = Dseq( | |
| [ | |
| param_Value_2_1, param_Value_2_2, param_Value_2_3, param_Value_2_4, param_Value_2_5, param_Value_2_6, param_Value_2_7, param_Value_2_8 | |
| ], | |
| inf | |
| ); | |
| var trig1 = Demand.ar(clock, reset, trigSeq1) * clock; // TODO: clock usage here means trig length is determined by outside clock signal. right or wrong? | |
| // var trig1 = Trig.ar(Demand.ar(clock, reset, trigSeq1) * clock, 1/SampleRate.ir); | |
| var trig2 = Demand.ar(clock, reset, trigSeq2) * clock; // TODO: clock usage here means trig length is determined by outside clock signal. right or wrong? | |
| // var trig2 = Trig.ar(Demand.ar(clock, reset, trigSeq2) * clock, 1/SampleRate.ir); | |
| var gate = Latch.ar(Demand.ar(clock, reset, gateSeq), clock); | |
| var freq1 = Demand.ar(clock, reset, valueSeq1) * SelectX.kr(param_Range, [0.1, 0.2, 0.4]); | |
| var freq2 = Demand.ar(clock, reset, valueSeq2) * param_Scale; // TODO: Scale 0 .. 6.5V ? | |
| // var latchedFreq1 = Latch.ar(freq1, trig1); | |
| var latchedFreq1 = Latch.ar(freq1, sig_SampleAndHoldCtrl1); // TODO: consider semi-modular | |
| // var latchedFreq2 = Latch.ar(freq2, trig2); | |
| var latchedFreq2 = Latch.ar(freq2, sig_SampleAndHoldCtrl2); // TODO: consider semi-modular | |
| // TODO: remove phase | |
| var phaseSeq = Dseq([ 0, 1, 2, 3, 4, 5, 6, 7 ], inf); | |
| var phase = Demand.ar(clock, reset, phaseSeq); | |
| Out.kr(visual_Position, phase); | |
| Out.ar( | |
| out_Trig1, | |
| Trig1.ar(trig1, 1/60) * 0.5 // TODO: ~5V, TODO: 1/60 second trig length | |
| ); | |
| Out.ar( | |
| out_Trig2, | |
| Trig1.ar(trig2, 1/60) * 0.5 // TODO: ~5V, TODO: 1/60 second trig length | |
| ); | |
| Out.ar( | |
| out_Gate, | |
| gate | |
| ); | |
| Out.ar( | |
| out_PreOut1, | |
| freq1 | |
| ); | |
| Out.ar( | |
| out_Out1, | |
| SelectX.ar(sig_GlideCtrl1, [Lag.ar(latchedFreq1, param_Glide_1), latchedFreq1]) | |
| ); | |
| Out.ar( | |
| out_PreOut2, | |
| freq2 | |
| ); | |
| Out.ar( | |
| out_Out2, | |
| SelectX.ar(sig_GlideCtrl2, [Lag.ar(latchedFreq2, param_Glide_2), latchedFreq2]) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'SGain', | |
| description: "2-in/2-out audio fader with db gain control and mute.", | |
| inputs: [ | |
| 'Left' -> ( | |
| Description: "Left channel input." | |
| ), | |
| 'Right' -> ( | |
| Description: "Right channel input." | |
| ) | |
| ], | |
| outputs: [ | |
| 'Left' -> ( | |
| Description: "Attenuated left channel signal." | |
| ), | |
| 'Right' -> ( | |
| Description: "Attenuated right channel signal." | |
| ) | |
| ], | |
| params: [ | |
| 'Gain' -> ( | |
| Spec: \db.asSpec.copy.maxval_(12).default_(0), | |
| // TODO LagTime: 0.1 | |
| Description: "Attenuation control. `-inf` ... `+12` dB." | |
| ), | |
| 'Mute' -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 1, 0, ""), | |
| // TODO LagTime: 0.1 | |
| Description: "If `1` signal is muted, otherwise not." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Left, | |
| in_Right, | |
| out_Left, | |
| out_Right, | |
| param_Gain, | |
| param_Mute | |
| | | |
| var sig_Left = In.ar(in_Left); | |
| var sig_Right = In.ar(in_Right); | |
| var gain = param_Gain.dbamp * Lag.kr(Select.kr(param_Mute, [1, 0], 0.01)); | |
| Out.ar(out_Left, sig_Left * gain); | |
| Out.ar(out_Right, sig_Right * gain); | |
| } | |
| ), | |
| ( | |
| // Status: partly tested, TODO: test in_Reset together with param_Reset | |
| // Inspiration from A-145 | |
| name: 'SineLFO', | |
| description: "Sine LFO", | |
| inputs: [ | |
| 'Reset' -> ( | |
| Description: "Audio rate reset trigger: when signal is changed from 0 to 1 the LFO is retriggered.", | |
| Range: [0, 1] | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Sine output", | |
| Range: [-0.25, 0.25] | |
| ), | |
| ], | |
| params: [ | |
| 'Frequency' -> ( | |
| Description: "Frequency `0.01` Hz .. `50` Hz.", | |
| Spec: ControlSpec(0.01, 50, 'exp', 0, 1, "Hz"), | |
| LagTime: 0.01 | |
| ), | |
| 'Reset' -> ( | |
| Description: "Script reset trigger: when value is changed from `0` to `1` the LFO is retriggered.", | |
| Spec: \unipolar.asSpec.copy.step_(1), // TODO: generalize booleans (or triggers) | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Reset, | |
| out_Out, | |
| param_Frequency, | |
| param_Reset | |
| | | |
| var sig_Reset = In.ar(in_Reset); | |
| // TODO: remove var retrig = (Trig.ar(sig_Reset) + Trig.kr(param_Reset)) > 0; // TODO: remove param? | |
| var retrig = (Trig.ar(sig_Reset, 1/SampleRate.ir) + Trig.ar(param_Reset, 1/SampleRate.ir)) > 0; // TODO: remove param? | |
| var sinePhase = Phasor.ar(retrig, 1/SampleRate.ir*param_Frequency); | |
| var sineSig = SinOsc.ar(0, sinePhase.linlin(0, 1, 0, 2pi), 0.25); // +- 2.5V | |
| Out.ar( | |
| out_Out, | |
| sineSig | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'SineOsc', | |
| description: "Sine oscillator", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Sine wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| out_Out, | |
| param_Range, | |
| param_Tune, | |
| param_FM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( // TODO: optimization possibility - implement overridable set handlers and do this calculation in sclang rather than server? | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps | |
| ); | |
| Out.ar( | |
| out_Out, | |
| SinOsc.ar(frequency) * 0.5 | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'SineOscExp', | |
| experimental: true, | |
| description: "Sine oscillator (w/ linear FM features)", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| 'LinFM' -> ( | |
| Description: "Control signal for linear frequency modulation." | |
| ), | |
| 'PM' -> ( | |
| Description: "Control signal for phase modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Sine wave oscillator output.", | |
| Range: [-0.5, 0.5] | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| 'LinFM' -> ( | |
| Spec: ControlSpec.new(0, 10000, 'lin', nil, 0, "Hz"), | |
| LagTime: 0.2, | |
| Description: "Linear frequency modulation amount." | |
| ), | |
| 'PM' -> ( | |
| Spec: ControlSpec.new(0, 5, 'lin', 0.01, 0, ""), | |
| LagTime: 0.2, | |
| Description: "Phase modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| in_LinFM, | |
| in_PM, | |
| out_Out, | |
| param_Range, | |
| param_Tune, | |
| param_FM, | |
| param_LinFM, | |
| param_PM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var sig_LinFM = In.ar(in_LinFM); | |
| var sig_PM = In.ar(in_PM); | |
| //var fullRange = ControlSpec(12.midicps, 120.midicps); -- removed by andrew (allow higher frequencies for FM synthesis?) | |
| var frequency = ( //fullRange.constrain( -- removed by andrew | |
| ( // TODO: optimization - implement overridable set handlers and do this calculation in sclang rather than server | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps + (sig_LinFM * param_LinFM) | |
| ); | |
| Out.ar( | |
| out_Out, | |
| SinOsc.ar(frequency, (sig_PM * param_PM * 30).mod(2pi)) * 0.5 | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'Slew', | |
| description: "Slew rate limiter.", | |
| inputs: [ | |
| 'In' -> ( | |
| Description: "Audio signal imput." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Processed audio signal." | |
| ), | |
| ], | |
| params: [ | |
| 'Time' -> ( | |
| Spec: ControlSpec.new(0, 60000, units: "ms"), | |
| LagTime: 0.1, | |
| Description: "Slew time `0` ... `60 000` ms.", | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_In, | |
| out_Out, | |
| param_Time | |
| | | |
| var sig_In = In.ar(in_In); | |
| Out.ar( | |
| out_Out, | |
| Lag.ar(sig_In, param_Time/1000) | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: untested | |
| name: 'SPVoice', | |
| experimental: true, | |
| inputs: [ | |
| 'Gate' -> ( | |
| Description: "Control signal for gate", | |
| ), | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation", | |
| ), | |
| ], | |
| outputs: [ | |
| 'Left' -> ( | |
| Description: "Left audio signal", | |
| ), | |
| 'Right' -> ( | |
| Description: "Right audio signal", | |
| ), | |
| ], | |
| params: [ | |
| 'Gate' -> ( | |
| Spec: ControlSpec(0, 1, step: 1, default: 0), // TODO: DRY the gate/reset/boolean specs | |
| Description: "Manual gate." | |
| ), | |
| 'SampleStart' -> ( | |
| Spec: \unipolar.asSpec, | |
| Description: "Sample start position normalized to `0` ... `1`." | |
| ), | |
| 'SampleEnd' -> ( | |
| Spec: \unipolar.asSpec.copy.default_(1), | |
| Description: "Sample end position normalized to `0` ... `1`." | |
| ), | |
| 'LoopPoint' -> ( | |
| Spec: \unipolar.asSpec, | |
| Description: "Loop point within sample start and end position normalized to `0` ... `1`." | |
| ), | |
| 'LoopEnable' -> ( | |
| Spec: ControlSpec(0, 1, step: 1, default: 0), | |
| Description: "Loop enabled. `1` means enabled, `0` not." | |
| ), | |
| 'Frequency' -> ( | |
| Spec: \freq.asSpec, | |
| Description: "Frequency." | |
| ), | |
| 'RootFrequency' -> ( | |
| Spec: \freq.asSpec, // default root is 440 = A4 | |
| Description: "Root frequency." | |
| ), | |
| 'Volume' -> ( | |
| Spec: \db.asSpec.copy.default_(-10), | |
| Description: "Volume." | |
| ), | |
| 'Pan' -> ( | |
| Spec: \pan.asSpec, | |
| Description: "Pan position." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? nah: enfore unipolar on other modules | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| ], | |
| sampleSlots: [ | |
| 'Sample' -> ( | |
| Channels: [1, 2], | |
| Description: "Mono or stereo sample to play." | |
| ) // TODO: include metadata on whether samples are supposed to be enabled for read, write or both, and whether to set a default size for initial sample(s) (to set up a fixed recording buffer, etc) | |
| ], | |
| visuals: [ | |
| 'Phase' -> ( | |
| Spec: \unipolar.asSpec, | |
| Description: "Sample phase normalized to `0` ... `1`." | |
| ), | |
| 'Gate' -> ( | |
| Spec: ControlSpec(0, 1, step: 1), // TODO: DRY the gate/reset/boolean specs | |
| Description: "Gate: `0` or `1`." | |
| ), | |
| 'Playing' -> ( | |
| Spec: ControlSpec(0, 1, step: 1), // TODO: DRY the gate/reset/boolean specs | |
| Description: "Playing: `0` or `1`." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_Gate, | |
| in_FM, | |
| out_Left, | |
| out_Right, | |
| // param_Bufnum, // TODO | |
| param_Gate, | |
| param_SampleStart, // start point of playing back sample normalized to 0..1 | |
| param_SampleEnd, // end point of playing back sample normalized to 0..1. sampleEnd prior to sampleStart will play sample reversed | |
| param_LoopPoint, // loop point position between sampleStart and sampleEnd expressed in 0..1 | |
| param_LoopEnable, // loop enabled switch (1 = play looped, 0 = play oneshot). argument is fixed once gated | |
| param_Frequency, | |
| param_RootFrequency, | |
| param_Volume, | |
| param_Pan, | |
| param_FM, | |
| visual_Phase, | |
| visual_Gate, | |
| visual_Playing, | |
| numchannels_Sample, | |
| bufnums_Sample=#[0, 0] | |
| | | |
| var sig_Gate = In.ar(in_Gate); | |
| var sig_FM = In.ar(in_FM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); // TODO: nicked from Osc implementations | |
| var frequency = fullRange.constrain( // TODO: variant of version in Osc implementations | |
| ( // TODO: optimization - implement overridable set handlers and do this calculation in sclang rather than server | |
| param_Frequency.cpsoct + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps | |
| ); | |
| // var gate = param_Gate; | |
| var gate = ((sig_Gate > 0) + (K2A.ar(param_Gate) > 0)) > 0; | |
| var latched_sampleStart = Latch.ar(K2A.ar(param_SampleStart), gate); // parameter only has effect at time synth goes from gate 0 to 1 | |
| var latched_sampleEnd = Latch.ar(K2A.ar(param_SampleEnd), gate); // parameter only has effect at time synth goes from gate 0 to 1 | |
| var latched_loopPoint = Latch.ar(K2A.ar(param_LoopPoint), gate); // parameter only has effect at time synth goes from gate 0 to 1 | |
| var latched_loopEnable = Latch.ar(K2A.ar(param_LoopEnable), gate); // parameter only has effect at time synth goes from gate 0 to 1 | |
| // TODO: remove, bufnums are to be constant var latched_bufnum = Latch.ar(K2A.ar(param_Bufnum), gate); // parameter only has effect at time synth goes from gate 0 to 1 | |
| var rate = frequency/param_RootFrequency; | |
| var direction = (latched_sampleEnd-latched_sampleStart).sign; // 1 = forward, -1 = backward | |
| var leftmostSamplePosExtent = min(latched_sampleStart, latched_sampleEnd); | |
| var rightmostSamplePosExtent = max(latched_sampleStart, latched_sampleEnd); | |
| var mono_bufnum = bufnums_Sample[0]; | |
| var stereo_bufnum = bufnums_Sample[1]; | |
| /* | |
| var monoSampleIsLoaded = Latch.ar( | |
| (numchannels_Sample > 0) * (numchannels_Sample < 2), | |
| gate | |
| ); | |
| var stereoSampleIsLoaded = Latch.ar( | |
| (numchannels_Sample > 1) * (numchannels_Sample < 3), | |
| gate | |
| ); | |
| var bufnum = Latch.ar( | |
| (monoSampleIsLoaded * mono_bufnum) | |
| + | |
| (stereoSampleIsLoaded * stereo_bufnum), | |
| gate | |
| ); // "fixes" bufnum to correct channelcount | |
| */ | |
| var monoSampleIsLoaded = (numchannels_Sample > 0) * (numchannels_Sample < 2); | |
| var stereoSampleIsLoaded = (numchannels_Sample > 1) * (numchannels_Sample < 3); | |
| var bufnum = (monoSampleIsLoaded * mono_bufnum) + (stereoSampleIsLoaded * stereo_bufnum); | |
| // var onset = Latch.ar(sampleStart, Impulse.ar(0)); // "fixes" onset to sample start at the time of spawning the synth, whereas sample end and *absolute* loop position (calculated from possibly modulating start and end positions) may vary | |
| var onset = Latch.ar(latched_sampleStart, gate); // "fixes" onset to sample start at the time of spawning the synth, whereas sample end and *absolute* loop position (calculated from possibly modulating start and end positions) may vary | |
| //var bufDur = BufDur.kr(bufnums_Sample); | |
| var bufDur = BufDur.kr(bufnum); | |
| var bufDurDiv = Select.kr(bufDur > 0, [1, bufDur]); // weird way to avoid divide by zero in second sweep argument in the rare case of a buffer having 0 samples which stalls scsynth. there's gotta be a better way to work around this (by not dividing by bufDur) ... TODO | |
| var sweep = Sweep.ar(gate, rate/bufDurDiv*direction); // sample duration normalized to 0..1 (sweeping 0..1 sweeps entire sample). | |
| var oneshotPhase = onset + sweep; // align phase to actual onset (fixed sample start at the time of spawning the synth) | |
| var fwdOneshotPhaseDone = ((oneshotPhase > latched_sampleEnd) * (direction > (-1))) > 0; // condition fulfilled if phase is above current sample end and direction is positive | |
| var revOneshotPhaseDone = ((oneshotPhase < latched_sampleEnd) * (direction < 0)) > 0; // condition fulfilled if phase is above current sample end and direction is positive | |
| var loopPhaseStartTrig = (fwdOneshotPhaseDone + revOneshotPhaseDone) > 0; | |
| var oneshotSize = rightmostSamplePosExtent-leftmostSamplePosExtent; | |
| var loopOffset = latched_loopPoint*oneshotSize; // loop point normalized to entire sample 0..1 | |
| var loopSize = (1-latched_loopPoint)*oneshotSize; // TODO: this should be fixed / latch for every initialized loop phase / run | |
| var absoluteLoopPoint = latched_sampleStart + (loopOffset * direction); // TODO: this should be fixed / latch for every initialized loop phase / run | |
| var loopPhaseOnset = Latch.ar(oneshotPhase, loopPhaseStartTrig); | |
| var loopPhase = (oneshotPhase-loopPhaseOnset).wrap(0, loopSize * direction) + absoluteLoopPoint; // TODO | |
| // var loopPhase = oneshotPhase.wrap(sampleStart, sampleEnd); | |
| /* | |
| TODO: debugging | |
| loopPhaseStartTrig.poll(label: 'loopPhaseStartTrig'); | |
| absoluteLoopPoint.poll(label: 'absoluteLoopPoint'); | |
| loopPhaseOnset.poll(label: 'loopPhaseOnset'); | |
| oneshotPhase.poll(label: 'oneshotPhase'); | |
| loopPhase.poll(label: 'loopPhase'); | |
| loopSize.poll(label: 'loopSize'); | |
| */ | |
| var phase = Select.ar(loopPhaseStartTrig, [oneshotPhase, loopPhase]); | |
| var isForwardDirectionAndOkPlaying = ((fwdOneshotPhaseDone < 1) + (latched_loopEnable > 0)) > 0; // basically: as long as direction is forward and phaseFromStart < sampleEnd or latched_loopEnable == 1, continue playing (audition sound) | |
| var isReversedDirectionAndOkPlaying = ((revOneshotPhaseDone < 1) + (latched_loopEnable > 0)) > 0; // basically: as long as direction is backward and phaseFromStart > sampleEnd or latched_loopEnable == 1, continue playing (audition sound) | |
| var stillPlaying = isForwardDirectionAndOkPlaying * isReversedDirectionAndOkPlaying * gate; | |
| var sig = ( | |
| ( | |
| BufRd.ar( // TODO: tryout BLBufRd | |
| 1, | |
| mono_bufnum, | |
| phase.linlin(0, 1, 0, BufFrames.kr(mono_bufnum)), | |
| interpolation: 4 | |
| ) ! 2 * monoSampleIsLoaded | |
| ) + | |
| ( | |
| BufRd.ar( // TODO: tryout BLBufRd | |
| 2, | |
| stereo_bufnum, | |
| phase.linlin(0, 1, 0, BufFrames.kr(stereo_bufnum)), | |
| interpolation: 4 | |
| ) * stereoSampleIsLoaded | |
| ) | |
| ); | |
| //SendTrig.kr(Impulse.kr(60),0,phase); | |
| /* | |
| var sig = BLBufRd.ar( | |
| bufnum, | |
| phase.linlin(0, 1, 0, BufFrames.kr(bufnum)), | |
| 2 | |
| ) ! 2; // TODO: tryout BLBufRd | |
| */ | |
| /* | |
| sig = sig * (((fwdOneshotPhaseDone < 1) + (latched_loopEnable > 0)) > 0); // basically: as long as direction is forward and phaseFromStart < sampleEnd or latched_loopEnable == 1, continue playing (audition sound) | |
| sig = sig * (((revOneshotPhaseDone < 1) + (latched_loopEnable > 0)) > 0); // basically: as long as direction is backward and phaseFromStart > sampleEnd or latched_loopEnable == 1, continue playing (audition sound) | |
| */ | |
| /* | |
| sig = sig * (((fwdOneshotPhaseDone < 1) + (latched_loopEnable > 0)) > 0) * gate; // basically: as long as direction is forward and phaseFromStart < sampleEnd or latched_loopEnable == 1, continue playing (audition sound) | |
| sig = sig * (((revOneshotPhaseDone < 1) + (latched_loopEnable > 0)) > 0) * gate; // basically: as long as direction is backward and phaseFromStart > sampleEnd or latched_loopEnable == 1, continue playing (audition sound) | |
| */ | |
| sig = sig * stillPlaying; | |
| sig = Balance2.ar(sig[0], sig[1], param_Pan); | |
| sig = sig * param_Volume.dbamp; | |
| Out.ar(out_Left, sig[0]); | |
| Out.ar(out_Right, sig[1]); | |
| Out.kr(visual_Phase, Gate.ar(phase, stillPlaying)); | |
| Out.kr(visual_Gate, gate); | |
| Out.kr(visual_Playing, stillPlaying); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'TestGen', | |
| description: "Test sound generator.", | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Sine wave signal or noise output." | |
| ) | |
| ], | |
| params: [ | |
| 'Frequency' -> ( | |
| Spec: \widefreq.asSpec, | |
| LagTime: 0, // TODO: all lag times have to be 0 if params with \db spec (such as param_Amplitude) are used (fixed by resorting to NamedControls in R?) | |
| Description: "Sine wave frequency." | |
| ), | |
| 'Amplitude' -> ( | |
| Spec: \db.asSpec, | |
| Description: "Audio output signal amplitude." | |
| ), | |
| 'Wave' -> ( | |
| Spec: ControlSpec(0, 1, step: 1, default: 0), | |
| Description: "Wave: `0` means sine wave, `1` white noise." | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| out_Out, | |
| param_Frequency, | |
| param_Amplitude, | |
| param_Wave | |
| | | |
| Out.ar(out_Out, SelectX.ar(param_Wave, [SinOsc.ar(param_Frequency), WhiteNoise.ar]) * param_Amplitude.dbamp); | |
| } | |
| ), | |
| ( | |
| // Status: tested | |
| name: 'TriOsc', | |
| description: "Triangle oscillator (non-bandlimited).", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Triangle wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| out_Out, | |
| param_Range, | |
| param_Tune, | |
| param_FM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( // TODO: optimization possibility - implement overridable set handlers and do this calculation in sclang rather than server? | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps | |
| ); | |
| Out.ar( | |
| out_Out, | |
| LFTri.ar(frequency) * 0.5 // TODO: not band-limited | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: experimental | |
| name: 'TriOscExp', | |
| experimental: true, | |
| description: "Triangle oscillator (non-bandlimited, w/ linear FM features).", | |
| inputs: [ | |
| 'FM' -> ( | |
| Description: "Control signal for frequency modulation." | |
| ), | |
| 'LinFM' -> ( | |
| Description: "Control signal for linear frequency modulation." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Out' -> ( | |
| Description: "Triangle wave oscillator output." | |
| ), | |
| ], | |
| params: [ | |
| 'Range' -> ( | |
| Spec: ControlSpec.new(-2, 2, 'lin', 1, 0), | |
| LagTime: 0.01, | |
| Description: "`-2` ... `+2` octaves." | |
| ), | |
| 'Tune' -> ( | |
| Spec: ControlSpec.new(-600, 600, 'lin', 0, 0, "cents"), | |
| LagTime: 0.01, | |
| Description: "`-1200` ... `+1200` cents." | |
| ), | |
| 'FM' -> ( | |
| Spec: \unipolar.asSpec, // TODO: bipolar? | |
| LagTime: 0.01, | |
| Description: "Frequency modulation amount." | |
| ), | |
| 'LinFM' -> ( | |
| Spec: ControlSpec.new(0, 10000, 'lin', nil, 0, "Hz"), | |
| LagTime: 0.2, | |
| Description: "Linear frequency modulation amount." | |
| ), | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_FM, | |
| in_LinFM, | |
| out_Out, | |
| param_Range, | |
| param_Tune, | |
| param_FM, | |
| param_LinFM | |
| | | |
| var sig_FM = In.ar(in_FM); | |
| var sig_LinFM = In.ar(in_LinFM); | |
| var fullRange = ControlSpec(12.midicps, 120.midicps); | |
| var frequency = fullRange.constrain( | |
| ( // TODO: optimization - implement overridable set handlers and do this calculation in sclang rather than server | |
| 3 + | |
| param_Range + | |
| (param_Tune / 1200) + | |
| (sig_FM * 10 * param_FM) // 0.1 = 1 oct | |
| ).octcps + (sig_LinFM * param_LinFM) | |
| ); | |
| Out.ar( | |
| out_Out, | |
| LFTri.ar(frequency) * 0.5 // TODO: not band-limited | |
| ); | |
| } | |
| ), | |
| ( | |
| // Status: not tested | |
| name: 'XFader', | |
| description: "Crossfader.", | |
| inputs: [ | |
| 'InALeft' -> ( | |
| Description: "Signal A left audio signal." | |
| ), | |
| 'InARight' -> ( | |
| Description: "Signal A right audio signal." | |
| ), | |
| 'InBLeft' -> ( | |
| Description: "Signal B left audio signal." | |
| ), | |
| 'InBRight' -> ( | |
| Description: "Signal B right audio signal." | |
| ), | |
| ], | |
| outputs: [ | |
| 'Left' -> ( | |
| Description: "Crossfaded left audio signal." | |
| ), | |
| 'Right' -> ( | |
| Description: "Crossfaded right audio signal." | |
| ), | |
| ], | |
| params: [ | |
| 'Fade' -> ( | |
| Spec: \bipolar.asSpec, // TODO: remove need for .asSpec | |
| Description: "Fader position: `-1` fully attenuates stereo signal A, `1` fully attenuates stereo signal B, anything in between mixes the signals." | |
| ), | |
| 'TrimA' -> ( | |
| Spec: \db.asSpec.copy.maxval_(12), | |
| Description: "Signal A trim." | |
| ), | |
| 'TrimB' -> ( | |
| Spec: \db.asSpec.copy.maxval_(12), | |
| Description: "Signal B trim." | |
| ), | |
| 'Master' -> ( | |
| Spec: \db.asSpec.copy.maxval_(12), | |
| Description: "Master output level." | |
| ) | |
| ], | |
| ugenGraphFunc: { | |
| | | |
| in_InALeft, | |
| in_InARight, | |
| in_InBLeft, | |
| in_InBRight, | |
| out_Left, | |
| out_Right, | |
| param_Fade, | |
| param_TrimA, | |
| param_TrimB, | |
| param_Master | |
| | | |
| var sig_InALeft = In.ar(in_InALeft); | |
| var sig_InARight = In.ar(in_InARight); | |
| var sig_InBLeft = In.ar(in_InBLeft); | |
| var sig_InBRight = In.ar(in_InBRight); | |
| var sig_inA = [sig_InALeft, sig_InARight] * param_TrimA.dbamp; | |
| var sig_inB = [sig_InBLeft, sig_InBRight] * param_TrimB.dbamp; | |
| var sig = XFade2.ar(sig_inA, sig_inB, param_Fade, param_Master.dbamp); | |
| Out.ar(out_Left, sig[0]); | |
| Out.ar(out_Right, sig[1]); | |
| } | |
| ) | |
| ]; | |
| var generateMatrixModulesParams = { |numInputs, numOutputs| | |
| var result = [ | |
| 'FadeTime' -> ( | |
| Spec: ControlSpec(0, 1000, 'lin', 0, 5, "ms"), | |
| Description: "Fade time in milliseconds (range: `0`-`100000` ms) applied when an input is switched on to or off from an output. Default is `5` ms." | |
| ) | |
| ] ++ numInputs.collect { |inIndex| | |
| numOutputs.collect { |outIndex| | |
| "Gate_"++(outIndex+1)++"_"++(inIndex+1) -> ( | |
| Spec: ControlSpec(0, 1, 'lin', 1, 0, ""), | |
| Description: ("Toggle that determine whether input " ++ (inIndex+1) ++ " is switched on to output " ++ (outIndex+1)) ++ "." | |
| ) | |
| } | |
| }.flatten.collect { |assoc| assoc.key.asSymbol -> assoc.value }; | |
| result; | |
| }; | |
| var generateMatrixModulesInputs = { |numInputs| | |
| numInputs.collect { |index| | |
| ("In"++(index+1)).asSymbol -> ( | |
| Description: "Input " ++ (index+1) | |
| ) | |
| }; | |
| }; | |
| var generateMatrixModulesOutputs = { |numOutputs| | |
| numOutputs.collect { |index| | |
| ("Out"++(index+1)).asSymbol -> ( | |
| Description: "Output " ++ (index+1) | |
| ) | |
| }; | |
| }; | |
| var getModuleDefs = { |self| | |
| self[\moduleDefs] | |
| }; | |
| // TODO: report *all* specs, deduce to param specs in r_specs.scd | |
| // TODO: this can be rewritten to a function returning a subset of getCoreStdModuleMetadata | |
| var getCoreStdModuleSpecs = { | |
| // TODO: this is used since "param_" is prefixed in function with same name above | |
| var getModuleDefParamControlSpecs = { |moduleDef| | |
| var params = getModuleDefParams.value(moduleDef); | |
| if (params.notNil) { | |
| asDict.value( | |
| params.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> if (assocValue.class == ControlSpec) { // TODO: DRY this up | |
| assocValue | |
| } { | |
| // TODO: Event assumed | |
| assocValue[\Spec] | |
| }; | |
| } | |
| ) | |
| } | |
| }; | |
| var moduleDefs = validateAndExpandModuleDefs.value(coreModules++stdModules); | |
| asDict.value( | |
| moduleDefs.collect { |moduleDef| | |
| var moduleRef = moduleDef[\name]; | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| moduleRef -> getModuleDefParamControlSpecs.value(moduleDef); | |
| } | |
| ); | |
| }; | |
| // TODO: reports *all* specs, param specs to be deduced in r_specs.scd | |
| var getCoreStdModuleMetadata = { | |
| // TODO: this is used since "param_" is prefixed in function getModuleDefParamControlSpecs above | |
| var getModuleDefParamMetadata = { |moduleDef| | |
| var params = getModuleDefParams.value(moduleDef); | |
| if (params.notNil) { | |
| // TODO asDict.value( | |
| params.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> ( | |
| Spec: if (assocValue.class == ControlSpec) { // TODO: DRY this up | |
| assocValue | |
| } { | |
| // TODO: Event assumed | |
| assocValue[\Spec] | |
| }, | |
| Description: if (assocValue.class == Event) { // TODO: DRY this up | |
| assocValue[\Description] | |
| } | |
| ) | |
| } | |
| //) | |
| } | |
| }; | |
| var getModuleDefInputMetadata = { |moduleDef| | |
| var inputs = getModuleDefInputs.value(moduleDef); | |
| if (inputs.notNil) { | |
| // TODO asDict.value( | |
| inputs.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> ( | |
| Description: if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| } | |
| ) | |
| } | |
| //) | |
| } | |
| }; | |
| var getModuleDefOutputMetadata = { |moduleDef| | |
| var outputs = getModuleDefOutputs.value(moduleDef); | |
| if (outputs.notNil) { | |
| // TODO asDict.value( | |
| outputs.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> ( | |
| Description: if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| } | |
| ) | |
| } | |
| //) | |
| } | |
| }; | |
| var getModuleDefVisualMetadata = { |moduleDef| | |
| var visuals = getModuleDefVisuals.value(moduleDef); | |
| if (visuals.notNil) { | |
| // TODO asDict.value( | |
| visuals.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> ( | |
| Description: if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| }, | |
| Spec: if (assocValue.class == Event) { | |
| assocValue[\Spec] | |
| } { | |
| nil | |
| }, | |
| ) | |
| } | |
| //) | |
| } | |
| }; | |
| var getModuleDefSampleSlotMetadata = { |moduleDef| | |
| var sampleSlots = getModuleDefSampleSlots.value(moduleDef); | |
| if (sampleSlots.notNil) { | |
| // TODO asDict.value( | |
| sampleSlots.collect { |assoc| | |
| var assocValue = assoc.value; | |
| assoc.key -> ( | |
| Description: if (assocValue.class == Event) { | |
| assocValue[\Description] | |
| } { | |
| nil | |
| } | |
| ) | |
| } | |
| //) | |
| } | |
| }; | |
| var moduleDefs = validateAndExpandModuleDefs.value(coreModules++stdModules); | |
| // TODO: asDict.value( | |
| moduleDefs.collect { |moduleDef| | |
| var moduleRef = moduleDef[\name]; | |
| var spec = getModuleDefSpec.value(moduleDef); | |
| moduleRef -> ( | |
| parameters: getModuleDefParamMetadata.value(moduleDef), | |
| inputs: getModuleDefInputMetadata.value(moduleDef), | |
| outputs: getModuleDefOutputMetadata.value(moduleDef), | |
| visuals: getModuleDefVisualMetadata.value(moduleDef), | |
| sampleSlots: getModuleDefSampleSlotMetadata.value(moduleDef) | |
| ) | |
| }; | |
| // ); | |
| }; | |
| var asDict = { |arrayOfAssociations| | |
| IdentityDictionary.newFrom( arrayOfAssociations.collect { |assoc| [ assoc.key, assoc.value ] }.flatten ); | |
| }; | |
| var module = IdentityDictionary[ | |
| \init -> init, | |
| \free -> free, | |
| \newCommand -> newCommand, | |
| \connectCommand -> connectCommand, | |
| \disconnectCommand -> disconnectCommand, | |
| \deleteCommand -> deleteCommand, | |
| \setCommand -> setCommand, | |
| \bulksetCommand -> bulksetCommand, | |
| \newmacroCommand -> newmacroCommand, | |
| \deletemacroCommand -> deletemacroCommand, | |
| \macrosetCommand -> macrosetCommand, | |
| \readsampleCommand -> readsampleCommand, | |
| \tapoutputCommand -> tapoutputCommand, | |
| \tapclearCommand -> tapclearCommand, | |
| \getTapBus -> getTapBus, | |
| \getVisualBus -> getVisualBus, | |
| \generateDocsForAllModules -> generateDocsForAllModules, | |
| \getCoreStdModuleSpecs -> getCoreStdModuleSpecs, | |
| \getCoreStdModuleMetadata -> getCoreStdModuleMetadata | |
| ]; | |
| module | |
| ) |