diff --git a/.gitattributes.txt b/.gitattributes.txt new file mode 100644 index 0000000..22003a5 --- /dev/null +++ b/.gitattributes.txt @@ -0,0 +1 @@ +Help/* linguist-vendored \ No newline at end of file diff --git a/.gitignore.txt b/.gitignore.txt new file mode 100644 index 0000000..134ee11 --- /dev/null +++ b/.gitignore.txt @@ -0,0 +1,4 @@ + +# ignore gits in folders + +.git \ No newline at end of file diff --git a/Classes/DUGens/Dwalk.sc b/Classes/DUGens/Dwalk.sc new file mode 100644 index 0000000..558b767 --- /dev/null +++ b/Classes/DUGens/Dwalk.sc @@ -0,0 +1,87 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +Dwalk : UGen { + + *new { |stepsPerDir = 1, stepWidth = 1, stepMode = 0, start = 0, lo = -inf, hi = inf, + startDir = 1, dirChangeMode = 0, withDirs = 0, stepsPerDirMulUGen = 1, + stepsPerDirAddUGen = 0, dUniqueBufSize = 1048576| + ^super.new.init(stepsPerDir, stepWidth, stepMode, start, lo, hi, startDir, + dirChangeMode, withDirs, stepsPerDirMulUGen, stepsPerDirAddUGen, dUniqueBufSize) + } + + init { |stepsPerDir, stepWidth, stepMode, start, lo, hi, startDir, dirChangeMode, + withDirs, stepsPerDirMulUGen, stepsPerDirAddUGen, dUniqueBufSize| + + var dir, dirFunc, steps1, steps2, dir1, dir2, lastDir, lastIndex, + writeDir, writeIndex, dirChange, initZero; + + dirFunc = { Dseq((startDir.sign > 0).if { [1, -1] }{ [-1, 1] }, inf) }; + + withDirs = [0, false].includes(withDirs).not; + + withDirs.if { + steps1 = Dunique(stepsPerDir, dUniqueBufSize); + steps2 = steps1; + }{ + steps1 = stepsPerDir; + }; + + (stepMode == 0).if { + dir1 = Dstutter(steps1 * stepsPerDirMulUGen + stepsPerDirAddUGen, dirFunc.() * stepWidth); + }{ + dir1 = Dstutter(steps1 * stepsPerDirMulUGen + stepsPerDirAddUGen, dirFunc.()) * stepWidth; + }; + withDirs.if { dir2 = Dstutter(steps2 * stepsPerDirMulUGen + stepsPerDirAddUGen, dirFunc.()) }; + + lastDir = LocalBuf(1).set(0); + lastIndex = LocalBuf(1).set(start); + + (dirChangeMode == 0).if { + + dir = Dstutter(2, dir1); + initZero = Dstutter(2, Dseq([0, Dseq([1], inf)])); + + dirChange = (dir - Dbufrd(lastDir) * initZero).sign.abs; + dirChange maxCountNum)), doneAction: doneAction); + + allowFadeEnd = allowFadeEnd.asBoolean; + allowFadeEnd.if { + done = {} ! overlapChannelSize; + doneDelayTime = Timer.perform(fadeRate, trig) + }; + + // the array of bus indices + // new indices are polled from drate input 'inOut' + + inOut = { |i| + var initTrig, fadeTrig, demand; + + // first initForwardNum inOut indices must show up immediately for overlap + initTrig = ((i < initForwardNum).if { Impulse }{ DC }).perform(fadeRate, 0); + fadeTrig = (index + initForwardNum - 1 % overlapChannelSize - i).abs < thr; + + demand = Demand.perform( + fadeRate, + initTrig + fadeTrig, + 0, + inOut + ); + allowFadeEnd.if { done[i] = Done.kr(demand) }; + demand + } ! overlapChannelSize; + + // strange trigger logic needed here, allowFadeEnd = 0 saves a bit CPU + allowFadeEnd.if { + done = TDelay.kr( + DelayL.kr(done.sum, ControlDur.ir, ControlDur.ir), + A2K.kr(doneDelayTime) + ); + done = Trig1.kr(done > thr, inf) + }; + + // compensate PanAz ar bug with panPos and orientation in older SC versions + compOldPanAz = ((fadeRate == \kr) || (Main.versionAtLeast(3, 9))).not; + channelStep = compOldPanAz.if { 2 / width }{ 2 } / overlapChannelSize; + + // workhorse 1: angle stepping performed by DemandEnvGen + panPos = dynOutOffset + DemandEnvGen.perform(fadeRate, indexDrate, durs) * channelStep; + + // workhorse 2: channel distribution performed by PanAz + weight = PanAz.perform( + fadeRate, + overlapChannelSize, + DC.perform(fadeRate, 1), + allowFadeEnd.if { Gate.perform(fadeRate, panPos, 0.5 - done) }{ panPos }, + 1, + width, + // compensate PanAz ar bug with panPos and orientation in older SC versions + compOldPanAz.if { (1 - width) * 0.5 }{ 0 }; + ); + + // with extreme short durations occasional large values have been observed + // they are catched by Gate + weight = Gate.perform(fadeRate, weight, (weight >= 0) * (weight <= 1)); + + zeroThr.notNil.if { weight = weight * (weight > zeroThr) }; + + // lincurve helper Function + lincurve = { |x| x.lincurve_3_9(0, 1, 0, 1, curve * dir.sign) }; + + allowTypeSeq.asBoolean.not.if { + // static xfade type is most efficient + // determined by Integers for 'sine' and 'equalPower' + + // among static xfades most efficient for equalPower = sin = 1, + // that's what is done by PanAz, other cases treated here + + equalPower.asBoolean.not.if { + dir = HPZ1.perform(fadeRate, weight); + weight = sine.asBoolean.if { + lincurve.(weight.squared.pow(power)) + }{ + lincurve.((weight.asin * 2 / pi).pow(power)) + }; + }{ + sine.asBoolean.not.if { weight = (weight.asin * 2 / pi).sqrt } + } + }{ + // if xfade types should be sequenced + // all must run in parallel - this has to be allowed explicitely + (fadeMode > 0).if { + sine = Dstutter(2, sine); + equalPower = Dstutter(2, equalPower); + power = Dstutter(2, power); + curve = Dstutter(2, curve); + }; + sine = Demand.perform(fadeRate, trig, 0, sine); + equalPower = Demand.perform(fadeRate, trig, 0, equalPower); + power = Demand.perform(fadeRate, trig, 0, power); + curve = Demand.perform(fadeRate, trig, 0, curve); + dir = HPZ1.perform(fadeRate, weight); + + weight = Select.perform(fadeRate, sine * 2 + equalPower, [ + lincurve.((weight.asin * 2 / pi).pow(power)), + (weight.asin * 2 / pi).sqrt, + lincurve.(weight.squared.pow(power)), + weight + ]) + }; + + // difference between DX classes + ^case + { (type == \mix) || (type == \mixIn) }{ + Mix({ |i| + var in = (type == \mix).if { + Select.perform(outRate, inOut[i], channelsArray) + }{ + In.perform(outRate, inOut[i]) + }; + var muteAtInit = initForwardNum + count >= i; + weight[i] * in * muteAtInit; + } ! overlapChannelSize) + } + { (type == \fanOut) || (type == \envFanOut) }{ + source = (type == \fanOut).if { channelsArray }{ 1 }; + { |i| + var muteAtInit = initForwardNum + count >= i; + Out.perform(outRate, inOut[i], weight[i] * source * muteAtInit); + } ! overlapChannelSize; + } + { (type == \fan) || (type == \envFan) }{ + bus.isNil.if { + // for larger sizes we have a ugen number growth of quadratic order, + // thus rather pass a bus then + dc = { |i| DC.perform(fadeRate, i) ! overlapChannelSize } ! size; + weightAtInit = { |i| initMuteIndex + count >= i } ! overlapChannelSize; + weight = weight * weightAtInit; + + env = { |i| Mix(((dc[i] - (inOut.round)).abs < thr) * weight) } ! size; + + (type == \envFan).if { + env + }{ + // for multichannel source we need to "convolve" output + channelsArray = channelsArray.asArray; + size.collect { |i| + var sum = 0; + ([i + 1, channelsArray.size, size - i + channelsArray.size - 1].minItem).do { |j| + sum = sum + (env[i-j] * channelsArray[j]) + }; + sum + } + } + }{ + source = (type == \fan).if { channelsArray }{ 1 }; + { |i| + var weightAtInit = initMuteIndex + count >= i; + Out.perform( + outRate, bus + inOut[i] + thr, + weight[i] * weightAtInit * source + ) + } ! overlapChannelSize; + In.perform(outRate, bus, size); + } + } + } + + // restrict to possible drate arguments + *checkForWrongDrateInput { |maxArgSize, inOut, fadeTime, stepTime, sine, equalPower, power, curve| + + if ((maxArgSize > 1) and: { + [inOut, fadeTime, stepTime, sine, equalPower, power, curve].any { |x| + x.isKindOf(DUGen) or: { + x.isKindOf(SequenceableCollection) and: { + // do deep check, but only for DUGens + (x.size < maxArgSize) and: { x.flat.any { |y| y.isKindOf(DUGen) } } + } + } + } + }) { + SimpleInitError("For multichannel expansion demand rate " ++ + "ugens, that are needed more than once, have to be " ++ + "wrapped into Functions, see help file" + ).throw + }; + } + + *checkForWrongWidthInput { |maxWidth, width| + maxWidth.isNumber.not.if { + SimpleInitError("maxWidth must be a number").throw + }; + ((maxWidth.isNumber && width.isNumber) and: { width > maxWidth }).if { + SimpleInitError("width must not be larger than maxWidth").throw + }; + (Main.versionAtLeast(3, 9)).not and: { + width.isNumber.not.if { + SimpleInitError("In SC versions before 3.9 width is not modulatable, " ++ + "it must be a number" + ).throw + } + }; + } + + *checkForWrongFadeTimeInput { |fadeTime, stepTime, fadeMode| + ((fadeTime.isNumber && stepTime.isNumber && ([3, 4].includes(fadeMode))) and: { + fadeTime >= stepTime }).if { + SimpleInitError("fadeTime must not be larger than stepTime").throw + } + } + + *unbubbleInputIfNecessary { |... argList| + ^argList.collect { |item| + ((item.isKindOf(SequenceableCollection)) and: { item.size == 1 }).if { + item[0] + }{ + item + }; + } + } + +} + + +DXMix : AbstractDX { + + *ar { |in, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0| + + ^this.xr(\mix, \ar, in, channelsArrayRef, fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, nil, nil, zeroThr, doneAction + ); + } + + *kr { |in, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \kr, maxFadeNum = inf, maxWidth = 2, + width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, + zeroThr = nil, doneAction = 0| + + ^this.xr(\mix, \kr, in, channelsArrayRef, fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, nil, nil, zeroThr, doneAction + ); + } +} + +DXMixIn : AbstractDX { + + *ar { |in, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0| + + ^this.xr(\mixIn, \ar, in, `[], fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, nil, nil, zeroThr, doneAction + ); + } + + *kr { |in, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \kr, maxFadeNum = inf, maxWidth = 2, + width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, + zeroThr = nil, doneAction = 0| + + ^this.xr(\mixIn, \kr, in, `[], fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, nil, nil, zeroThr, doneAction + ); + } +} + + +DXFan : AbstractDX { + + *ar { |out, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, size = 2, bus = nil, zeroThr = nil, doneAction = 0| + + ^this.xr(\fan, \ar, out, channelsArrayRef, fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, size, bus, zeroThr, doneAction + ); + } + + *kr { |out, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \kr, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, size = 2, bus = nil, zeroThr = nil, doneAction = 0| + + ^this.xr(\fan, \kr, out, channelsArrayRef, fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, size, bus, zeroThr, doneAction + ); + } +} + + +DXEnvFan : AbstractDX { + + *ar { |out, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, size = 2, bus = nil, zeroThr = nil, doneAction = 0| + + ^this.xr(\envFan, \ar, out, `[], fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, maxDynOutOffset, + dynOutOffset, allowFadeEnd, size, bus, zeroThr, doneAction + ); + } + + *kr { |out, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \kr, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, size = 2, bus = nil, zeroThr = nil, doneAction = 0| + + ^this.xr(\envFan, \kr, out, `[], fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, size, bus, zeroThr, doneAction + ); + } +} + + +DXFanOut : AbstractDX { + + *ar { |out, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0| + + ^this.xr(\fanOut, \ar, out, channelsArrayRef, fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, nil, nil, zeroThr, doneAction + ); + } + + *kr { |out, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \kr, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0| + + ^this.xr(\fanOut, \kr, out, channelsArrayRef, fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, nil, nil, zeroThr, doneAction + ); + } +} + + +DXEnvFanOut : AbstractDX { + + *ar { |out, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0| + + ^this.xr(\envFanOut, \ar, out, `[], fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, nil, nil, zeroThr, doneAction + ); + } + + *kr { |out, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, + power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \kr, + maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, + dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0| + + ^this.xr(\envFanOut, \kr, out, `[], fadeTime, stepTime, fadeMode, sine, equalPower, + power, curve, allowTypeSeq, fadeRate, maxFadeNum, maxWidth, width, initOutOffset, + maxDynOutOffset, dynOutOffset, allowFadeEnd, nil, nil, zeroThr, doneAction + ); + } +} + + + ++Object { + miSC_Dmultiply { ^this } +} + ++Function { + miSC_Dmultiply { |n| ^{ this } ! n } +} + ++SequenceableCollection { + miSC_Dmultiply { |n| ^{ |i| this.wrapAt(i) } ! n } +} + diff --git a/Classes/DX/extUGen.sc b/Classes/DX/extUGen.sc new file mode 100644 index 0000000..0c95880 --- /dev/null +++ b/Classes/DX/extUGen.sc @@ -0,0 +1,94 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// this provides the newer version of lincurve for older SC versions with DX UGens +// Credits to Tim Blechmann and Julian Rohrhuber + + ++ UGen { + lincurve_3_9 { arg inMin = 0, inMax = 1, outMin = 0, outMax = 1, curve = -4, clip = \minmax; + var grow, a, b, scaled, curvedResult; + if (curve.isNumber and: { abs(curve) < 0.125 }) { + ^this.linlin(inMin, inMax, outMin, outMax, clip) + }; + grow = exp(curve); + a = outMax - outMin / (1.0 - grow); + b = outMin + a; + scaled = (this.prune(inMin, inMax, clip) - inMin) / (inMax - inMin); + + curvedResult = b - (a * pow(grow, scaled)); + + if (curve.rate == \scalar) { + ^curvedResult + } { + ^Select.perform(this.methodSelectorForRate, abs(curve) >= 0.125, [ + this.linlin(inMin, inMax, outMin, outMax, clip), + curvedResult + ]) + } + } +} + ++ AbstractFunction { + lincurve_3_9 { arg inMin = 0, inMax = 1, outMin = 0, outMax = 1, curve = -4, clip = \minmax; + ^this.composeNAryOp('lincurve_3_9', [inMin, inMax, outMin, outMax, curve, clip]) + } +} + ++ SequenceableCollection { + lincurve_3_9 { arg ... args; ^this.multiChannelPerform('lincurve_3_9', *args) } +} + ++ SimpleNumber { + lincurve_3_9 { arg inMin = 0, inMax = 1, outMin = 0, outMax = 1, curve = -4, clip = \minmax; + var grow, a, b, scaled; + switch(clip, + \minmax, { + if (this <= inMin, { ^outMin }); + if (this >= inMax, { ^outMax }); + }, + \min, { + if (this <= inMin, { ^outMin }); + }, + \max, { + if (this >= inMax, { ^outMax }); + } + ); + if (abs(curve) < 0.001) { + // If the value should be clipped, it has already been clipped (above). + // If we got this far, then linlin does not need to do any clipping. + // Inlining the formula here makes it even faster. + ^(this-inMin)/(inMax-inMin) * (outMax-outMin) + outMin; + }; + + grow = exp(curve); + a = outMax - outMin / (1.0 - grow); + b = outMin + a; + scaled = (this - inMin) / (inMax - inMin); + + ^b - (a * pow(grow, scaled)); + } +} + diff --git a/Classes/Enumeration/extInteger.sc b/Classes/Enumeration/extInteger.sc new file mode 100644 index 0000000..b55c5cb --- /dev/null +++ b/Classes/Enumeration/extInteger.sc @@ -0,0 +1,67 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Integer { + enum { |pool, function = true, evalAtZero = false, type = 0, order = true, maxNum = inf| + // type 0: one array for all levels + // type 1: array of pools (size must equal receiver) + + var allCols, currentCol, currentIndex = 0, indexCol, + endOfEnum = false, currentPool, check, item, count = 0; + + order.not.if { + pool = (type == 0).if { pool.scramble }{ pool.collect(_.scramble) }; + }; + indexCol = -1!this; + currentCol = 0!this; + while { endOfEnum.not }{ + indexCol[currentIndex] = indexCol[currentIndex] + 1; + currentPool = (type == 0).if { pool }{ pool[currentIndex] }; + (indexCol[currentIndex] >= (currentPool.size)).if { + indexCol[currentIndex] = -1; + currentIndex = currentIndex - 1; + }{ + item = currentPool.at(indexCol[currentIndex]); + + ((currentIndex == 0) && evalAtZero.not).if { + true + }{ + function.(item, currentIndex, currentCol, indexCol) + }.if { + currentCol[currentIndex] = item; + (currentIndex == (this - 1)).if { + allCols = allCols.add(currentCol.deepCopy); + count = count + 1; + (count == maxNum).if { endOfEnum = true }; + }{ + currentIndex = currentIndex + 1 + } + } + }; + (currentIndex == -1).if { endOfEnum = true }; + }; + ^allCols; + } +} diff --git a/Classes/FFT/PV_UGens.sc b/Classes/FFT/PV_UGens.sc new file mode 100644 index 0000000..ea2db04 --- /dev/null +++ b/Classes/FFT/PV_UGens.sc @@ -0,0 +1,75 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +PV_BinRange : PV_ChainUGen { + + *new { |buffer, loBin, hiBin| + ^this.multiNew(\control, buffer, loBin, hiBin) + } + + *new1 { |rate, buffer, loBin, hiBin| + var wipe, bufSize, chain_clipped; + + bufSize = buffer.miSC_getFFTbufSize; + chain_clipped = LocalBuf(bufSize); + chain_clipped = PV_Copy(buffer, chain_clipped); + + wipe = loBin * 2 / bufSize; + chain_clipped = PV_BrickWall(chain_clipped, wipe); + + wipe = hiBin * 2 / bufSize - 1; + chain_clipped = PV_BrickWall(chain_clipped, wipe); + + ^chain_clipped + } +} + +PV_BinGap : PV_ChainUGen { + + *new { |buffer, loBin, hiBin| + ^this.multiNew(\control, buffer, loBin, hiBin) + } + + *new1 { |rate, buffer, loBin, hiBin| + var wipe, bufSize, chain_gap_1, chain_gap_2; + + bufSize = buffer.miSC_getFFTbufSize; + chain_gap_1 = LocalBuf(bufSize); + chain_gap_2 = LocalBuf(bufSize); + + chain_gap_1 = PV_Copy(buffer, chain_gap_1); + chain_gap_2 = PV_Copy(buffer, chain_gap_2); + + wipe = loBin - 1 * 2 / bufSize - 1; + chain_gap_1 = PV_BrickWall(chain_gap_1, wipe); + + wipe = hiBin + 1 * 2 / bufSize; + chain_gap_2 = PV_BrickWall(chain_gap_2, wipe); + + ^PV_Add(chain_gap_1, chain_gap_2); + } +} + + diff --git a/Classes/FFT/extFFT.sc b/Classes/FFT/extFFT.sc new file mode 100644 index 0000000..a6c25e4 --- /dev/null +++ b/Classes/FFT/extFFT.sc @@ -0,0 +1,31 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++ FFT { + miSC_getFFTbufSize { + ^this.fftSize + } +} + diff --git a/Classes/FFT/extLocalBuf.sc b/Classes/FFT/extLocalBuf.sc new file mode 100644 index 0000000..ab9e8ed --- /dev/null +++ b/Classes/FFT/extLocalBuf.sc @@ -0,0 +1,30 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++ LocalBuf { + miSC_getFFTbufSize { + ^this.inputs[1] + } +} diff --git a/Classes/FFT/extPV_ChainUGen.sc b/Classes/FFT/extPV_ChainUGen.sc new file mode 100644 index 0000000..ded5b20 --- /dev/null +++ b/Classes/FFT/extPV_ChainUGen.sc @@ -0,0 +1,31 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++ PV_ChainUGen { + miSC_getFFTbufSize { + ^this.inputs[0].miSC_getFFTbufSize + } +} + diff --git a/Classes/FFT/extSequenceableCollection.sc b/Classes/FFT/extSequenceableCollection.sc new file mode 100644 index 0000000..b77a987 --- /dev/null +++ b/Classes/FFT/extSequenceableCollection.sc @@ -0,0 +1,30 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++ SequenceableCollection { + miSC_getFFTbufSize { + ^this.collect { |x| x.miSC_getFFTbufSize } + } +} diff --git a/Classes/HS/HelpSynth.sc b/Classes/HS/HelpSynth.sc new file mode 100755 index 0000000..c54cafd --- /dev/null +++ b/Classes/HS/HelpSynth.sc @@ -0,0 +1,338 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +HS : HelpSynth {} + +HelpSynth { + classvar <>all; + var <>ugenFuncs, <>helpSynthDefs, <>playingHelpSynths, + <>size, <>bus, <>server, <>helpSynthString, <>controlNames, + <>trigSynths, <>trigSynthDef, <>trigString, <>initstring, <>inputErrString, <>postOSC, + <>trigMsgIDCounter = -1, <>demandIndexOffset = 0, <>lastDemandIndexOffset = 0, <>inputCheck, + <>isListening = false, <>isPlaying = false, <>isRunning = false, <>isPHelpSynthPlaying = false, + <>hasJustStartedWithPause = false, + + <>granularity = 200, // time grains per second, quantization for bookkeeping (demand message comprehension) + // not used for scheduling events + <>initSeconds, // time grains reset at PHS player start + + <>helpSynthAhead = 0.0001, + <>cleanupDelay = 0.01, + <>sequentialSchedShift = 0.0001, // scheduling events at same logical time + <>demandLatency, // = help synth latency + <>respondLatency, // time given to the response to be safely received by the client on time (at total latency) + <>totalLatency, // demandLatency + respondLatency = + // total (client side) logical difference between demand event and receive event + + <>mainPHSplayer, <>otherPHSusePlayers, + <>demandStreams_indexOffsets, <>receiveStreams_indexOffsets, + + // OSC bookkeeping when PHSparPlayer or PHSusePlayer involved: + + <>trigMsgIDs_times, // IdentityDictionary: trigMsgID -> time (in grains) + <>times_demandNums, // IdentityDictionary: time (in grains) -> demand num / array of (hs indexed) demand nums + <>times_vals, // IdentityDictionary: time (in grains) -> synth value / synth value array + + // Lists with one item for each demand index, they may grow with PHSusePlayers involved + <>times_durs, // List of IdentityDictionaries: time (in grains) -> duration (in beats) + <>timeQueues; // List of PriorityQueues + *basicNew { |server, ugenFunc, demandLatency = 0.15, respondLatency = 0.15, postOSC = false, granularity = 200, inputCheck = true| + ^super.new.ugenFuncs_(ugenFunc).server_(server).demandLatency_(demandLatency) + .respondLatency_(respondLatency).postOSC_(postOSC).granularity_(granularity).inputCheck_(inputCheck); + } + + *new { |server, ugenFunc, demandLatency = 0.15, respondLatency = 0.15, postOSC = false, granularity = 200, inputCheck = true| + ^this.basicNew(server, ugenFunc, demandLatency, respondLatency, postOSC, granularity, inputCheck).commonInit.addInit; + } + + *add { |helpSynth| + var old; + old = all.findMatch(helpSynth); + old.notNil.if { all.remove(old) }; + all.add(helpSynth); + } + + *remove { |helpSynth| all.remove(helpSynth) } + + *clear { all = Set[] } + + add { this.class.add(this) } + + remove { all.remove(this) } + + inputError {|str| + Error("\n Wrong HS input - " ++ str ++ "\n").throw + } + + commonInputCheck { + (server.isMemberOf(Server) and: { server.serverRunning }).not.if { + this.inputError("server must be a running Server") + }; + (demandLatency.isKindOf(Number) and: { demandLatency > 0 } ).not.if { + this.inputError("demandLatency must be a Number > 0") + }; + (respondLatency.isKindOf(Number) and: { respondLatency > 0 } ).not.if { + this.inputError("respondLatency must be a Number > 0") + }; + (postOSC.isNil or: { postOSC.isKindOf(Boolean) } ).not.if { + this.inputError("postOSC must be a Boolean") + }; + (granularity.isKindOf(Integer) and: { granularity > 0 } ).not.if { + this.inputError("granularity must be an Integer > 0") + }; + } + + addInputCheck { + ugenFuncs.isMemberOf(Function).not.if { + this.inputError("ugenFunc must be a (ugenGraph)Function") + }; + } + + commonInit { + inputCheck.if { this.commonInputCheck }; + totalLatency = demandLatency + respondLatency; + trigMsgIDs_times = IdentityDictionary.new; + + times_vals = IdentityDictionary.new; + times_demandNums = IdentityDictionary.new; + demandStreams_indexOffsets = IdentityDictionary.new; + receiveStreams_indexOffsets = IdentityDictionary.new; + times_durs = List.new; + timeQueues = List.new; + + this.class.all.isNil.if { this.class.all = Set[] }; + this.add; + + otherPHSusePlayers = List.new; + initstring = Main.elapsedTime.asString; + helpSynthString = "hs" ++ initstring; + trigString = "trig" ++ initstring; + trigSynthDef = this.makeTrigSynthDef; + trigSynths = List.new; + this.resetInitSeconds; + } + + addInit { + size = 1; + inputCheck.if { this.addInputCheck }; + helpSynthDefs = [(ugenFuncs.asSynthDef.name = helpSynthString).perform(Main.versionAtMost(3,3).if { \memStore }{ \add })]; + helpSynthDefs.miSC_areControlRateSynthDefs.not.if { Error("help synths should be control rate \n").throw }; + controlNames = ugenFuncs.def.argNames; + playingHelpSynths = Array.newClear(1); + } + + resetInitSeconds { initSeconds = Main.elapsedTime; } + + allocBus { + // PHSplayer controls allocation and deallocation + bus.isNil.if { + bus = Bus.control(server, size); + }{ + "Bus already allocated".warn; + } + } + + makeTrigSynthDef { + ^SynthDef(trigString ++ "control", { |t_tr = 0, trigMsgID, inBus| + SendTrig.kr(t_tr, trigMsgID, In.kr(inBus,1)) + }, [\tr]).send(server); + } + + listen { |num = 1, latency| + // installs additional num trigger synths for sending osc messages + // every demand index gets its own trigger synth + var ensureListenFactor = 0.8; + this.isListening.not.if { isListening = true; }; + num.do({ |i| + var trigSynth = Synth.basicNew(trigString ++ \control.asString, server); + server.sendBundle(latency ?? demandLatency * ensureListenFactor, + trigSynth.newMsg(args: [\inBus, bus] )) ; + trigSynths.add(trigSynth); + + times_durs.add(IdentityDictionary.new); + timeQueues.add(PriorityQueue.new); + // CmdPeriod.remove(trigSynth); + }); + } + + // makeResponder { |trigSynthID, trigMsgID| + // ^OSCpathResponder(server.addr, ['/tr', trigSynthID, trigMsgID], { |time, responder, message| + // var timeGrains, demandIndices, val = message[3]; + // + // timeGrains = trigMsgIDs_times.removeAt(trigMsgID); + // postOSC.if { + // "time in grains: ".post; timeGrains.post; + // " val: ".post; val.postln; + // }; + // times_vals.put(timeGrains, val); + // responder.remove; + // }); + // } + + makeResponder { |trigSynthID, trigMsgID| + ^OSCFunc( + { |message| + var timeGrains, demandIndices, val = message[3]; + + timeGrains = trigMsgIDs_times.removeAt(trigMsgID); + postOSC.if { + "time in grains: ".post; timeGrains.post; + " val: ".post; val.postln; + }; + times_vals.put(timeGrains, val); + }, + '/tr', + server.addr, + argTemplate: [trigSynthID, trigMsgID] + ).oneShot; + } + + updateDemandIndexOffsets { |pHelpSynthUse| + lastDemandIndexOffset = demandIndexOffset; + demandIndexOffset = demandIndexOffset + pHelpSynthUse.relDemandIndexUserNums.size; + } + + play { |args, latency| + var synth = Synth.basicNew(helpSynthString, server); + playingHelpSynths.put(0, synth); + server.sendBundle(latency ?? demandLatency, synth.newMsg(server, + [\i_out, bus.index, \out, bus] ++ (args ?? []) )); + // CmdPeriod.remove(synth); + isPlaying = true; + isRunning = true; + } + + schedRun { |flag, latency| +// this.isPlaying.not.if { Error("HS not being played \n").throw }; +// server.sendBundle(latency ?? demandLatency, playingHelpSynths.at(0).runMsg(flag)); +// isRunning = flag; + + this.isPlaying.if { + server.sendBundle(latency ?? demandLatency, playingHelpSynths.at(0).runMsg(flag)); + isRunning = flag; + }; + + } + + sleep { |latency| + this.isListening.if { + trigSynths.do({|x| server.sendBundle(latency ?? demandLatency, x.freeMsg); }); + trigSynths = List.new; + isListening = false; + } + } + + stop { |latency| + this.isPlaying.if { + playingHelpSynths.do({ |x| x.notNil.if { server.sendBundle(latency ?? demandLatency, x.freeMsg) } }); isPlaying = false; + isRunning = false; + }; + SystemClock.sched((latency ?? demandLatency) + cleanupDelay, { this.clearBookkeeping; }); + } + + clearBookkeeping { + trigMsgIDs_times.clear; + times_vals.clear; + times_durs.clear; + timeQueues.clear; + times_demandNums.clear; + demandStreams_indexOffsets.clear; + receiveStreams_indexOffsets.clear; + + otherPHSusePlayers.clear; + mainPHSplayer = nil; + playingHelpSynths.clear; + demandIndexOffset = 0; + lastDemandIndexOffset = 0; + trigMsgIDCounter = -1; + } + + free { |latency| + var late = latency ?? demandLatency; + this.isPHelpSynthPlaying.if { + mainPHSplayer.free; // calls free again + }{ + this.stop(late).sleep(late).busFree(late); + } + } + + busFree { |latency| + SystemClock.sched((latency ?? demandLatency) + cleanupDelay, { bus.notNil.if { bus.free; bus = nil; } }) + } + + makeAdaptedQuants { |clock, quant, adaptQuant = true, quantBufferTime = 0.2, startQuantDelay = 0| + // quantBufferTime, startQuantDelay in seconds + // startQuantDelay used for resume in case of help synth switch + var startQuant, demandQuant, receiveQuant, qu, ph, off, + beatsToNextTimeOnGrid, periodShiftFactor, secureNextTime, + nextTimeOnGridCheckSum, nextTimeOnGridIsPossible; + + (quant.isKindOf(Quant) || quant.isNil).not.if { + Error("quant must be instance of Quant or nil").throw; + }; + + qu = quant.isNil.if { 0 } { quant.quant ?? 0 }; + ph = quant.isNil.if { 0 } { quant.phase ?? 0 }; + off = quant.isNil.if { 0 } { quant.timingOffset ?? 0 }; + + adaptQuant.if { + [qu,ph,off].do({|x| + ((x.isNumber) && (x >= 0)).not.if { Error("quant, phase and offset must be numbers >= 0").throw; } + }); + + beatsToNextTimeOnGrid = (clock.nextTimeOnGrid(qu,ph) - clock.beats); + // nextTimeToGrid must allow total latency to take place before + nextTimeOnGridCheckSum = beatsToNextTimeOnGrid - ((totalLatency + quantBufferTime + helpSynthAhead) * clock.tempo); + nextTimeOnGridIsPossible = (nextTimeOnGridCheckSum > 0); + + // secureNextTime in beats ! + (qu == 0).if { + // qu = 0 and ph = 0: "as soon as possible" + // ph > 0 is overruled, if it doesn't allow required latency + secureNextTime = max( (totalLatency + quantBufferTime + helpSynthAhead) * clock.tempo, ph); + }{ + nextTimeOnGridIsPossible.if { + secureNextTime = helpSynthAhead * clock.tempo + beatsToNextTimeOnGrid; + }{ + // step to the nearest period with enough headroom + periodShiftFactor = (nextTimeOnGridCheckSum.neg / qu).ceil; + secureNextTime = periodShiftFactor * qu + beatsToNextTimeOnGrid + (helpSynthAhead * clock.tempo); + } + } + }; + startQuant = Quant(qu, clock.miSC_getPhaseFromTimeToNextBeat(qu, secureNextTime - ((helpSynthAhead + totalLatency - startQuantDelay) * clock.tempo)), off); + demandQuant = Quant(qu, clock.miSC_getPhaseFromTimeToNextBeat(qu, secureNextTime - (totalLatency * clock.tempo)), off); + receiveQuant = Quant(qu, clock.miSC_getPhaseFromTimeToNextBeat(qu, secureNextTime), off); + ^[startQuant, demandQuant, receiveQuant]; + } + + currentAdaptedQuant { |clock, quant, adaptQuant = true, quantBufferTime = 0.2| + ^this.makeAdaptedQuants(clock, quant, adaptQuant, quantBufferTime).at(2); + } + + nextTrigMsgID { ^trigMsgIDCounter = trigMsgIDCounter + 1 } + isPHSplaying { ^this.isPHelpSynthPlaying } + isPHSplaying_ {|x| this.isPHelpSynthPlaying_(x) } +} + diff --git a/Classes/HS/HelpSynthPar.sc b/Classes/HS/HelpSynthPar.sc new file mode 100755 index 0000000..7fe4b9b --- /dev/null +++ b/Classes/HS/HelpSynthPar.sc @@ -0,0 +1,217 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +HSpar : HelpSynthPar {} + +HelpSynthPar : HelpSynth { + var <>currentSwitchIndex, <>currentHSindices, <>lastIndex, <>lastPause, <>switchCounter = -1, + + // OSC bookkeeping when PHSparPlayer or PHSusePlayer involved: + + // size fixed at init time: + + <>hsRunnings, // array of booleans: HelpSynth running status + + // variable size: + + <>times_switchIndices, // IdentityDictionary: time (in grains) -> switch index + <>trigMsgIDs_hsIndices, // IdentityDictionary: trigMsgID -> HelpSynth index + <>times_hsIndices, // IdentityDictionary: time (in grains) -> HelpSynth index array + // indicates all HelpSynth indices triggered at a time + + // Lists with one item for each demand index, they may grow with PHSusePlayers involved + <>times_theseHSindices; // List of IdentityDictionaries: time (in grains) -> HelpSynth index array + // for each demand index and time it indicates all triggered HelpSynth indices + + + *basicNew { |server, ugenFuncs, demandLatency = 0.15, respondLatency = 0.15, postOSC = false, granularity = 200, inputCheck = true| + ^super.basicNew(server, ugenFuncs, demandLatency, respondLatency, postOSC, granularity).inputCheck_(inputCheck); + } + + *new { |server, ugenFuncs, demandLatency = 0.15, respondLatency = 0.15, postOSC = false, granularity = 200, inputCheck = true| + ^this.basicNew(server, ugenFuncs, demandLatency, respondLatency, postOSC, granularity, inputCheck).commonInit.addInit; + } + + inputError {|str| + Error("\n Wrong HSpar input - " ++ str ++ "\n").throw + } + + addInputCheck { + (ugenFuncs.isKindOf(SequenceableCollection) and: + { ugenFuncs.every(_.isMemberOf(Function)) } ).not.if { + this.inputError("ugenFuncs must be a SequenceableCollection of (ugenGraph)Functions") + }; + } + + addInit { + inputCheck.if { this.addInputCheck }; + size = ugenFuncs.size; + times_switchIndices = IdentityDictionary.new; + trigMsgIDs_hsIndices = IdentityDictionary.new; + times_hsIndices = IdentityDictionary.new; + playingHelpSynths = Array.newClear(size); + times_theseHSindices = List.new; + hsRunnings = Array.fill(size, false); + + helpSynthDefs = ugenFuncs.collect {|item,i| (item.asSynthDef.name = helpSynthString ++ + "_" ++ i.asString).perform(Main.versionAtMost(3,3).if { \memStore }{ \add }) + }; + helpSynthDefs.miSC_areControlRateSynthDefs.not.if { Error("help synths should be control rate \n").throw }; + controlNames = ugenFuncs.collect({|x| x.def.argNames}); + } + + sendNewMsg { |synth, args, bus| + server.sendBundle(demandLatency, synth.newMsg(server, [\i_out, bus, \out, bus] ++ args)); + } + + listen { |num = 1, latency| + // num: number of demand indices + // installs additional trigger synths for sending osc messages + // every demand index gets trigger synths for each help synth + var ensureListenFactor = 0.8, trigSynth, counter = 0; + this.isListening.not.if { isListening = true; }; + num.do({|i| + helpSynthDefs.do({|x,j| + trigSynth = Synth.basicNew(trigString ++ \control.asString, server); + server.sendBundle(demandLatency, + trigSynth.newMsg(args: [\inBus, bus.index + counter] )) ; + counter = counter + 1; + trigSynths.add(trigSynth); + // CmdPeriod.remove(trigSynth); + }); + times_durs.add(IdentityDictionary.new); + timeQueues.add(PriorityQueue.new); + times_theseHSindices.add(IdentityDictionary.new); + }); + } + +/* makeResponder { |trigSynthID, trigMsgID| + ^OSCpathResponder(server.addr, ['/tr', trigSynthID, trigMsgID], { |time, responder, message| var timeGrains, hsIndex, hsIndices, val = message[3], vals; + + timeGrains = trigMsgIDs_times.removeAt(trigMsgID); + hsIndex = trigMsgIDs_hsIndices.removeAt(trigMsgID); + postOSC.if { + "time in grains: ".post; timeGrains.post; + " hsIndex: ".post; hsIndex.post; + " val: ".post; val.postln; + }; + hsIndices = times_hsIndices.removeAt(timeGrains); + vals = times_vals.removeAt(timeGrains); + vals = (vals ?? Array.newClear(size)); + vals[hsIndex] = val; + times_vals.put(timeGrains, vals); + responder.remove; + } + ) + }*/ + + makeResponder { |trigSynthID, trigMsgID| + ^OSCFunc( + { |message| + var timeGrains, hsIndex, hsIndices, val = message[3], vals; + + timeGrains = trigMsgIDs_times.removeAt(trigMsgID); + hsIndex = trigMsgIDs_hsIndices.removeAt(trigMsgID); + postOSC.if { + "time in grains: ".post; timeGrains.post; + " hsIndex: ".post; hsIndex.post; + " val: ".post; val.postln; + }; + hsIndices = times_hsIndices.removeAt(timeGrains); + vals = times_vals.removeAt(timeGrains); + vals = (vals ?? Array.newClear(size)); + vals[hsIndex] = val; + times_vals.put(timeGrains, vals); + }, + '/tr', + server.addr, + argTemplate: [trigSynthID, trigMsgID] + ).oneShot + } + + play { |index, args, switchOn, switchOff, set, hsStartIndices, latency| + var synth, late = latency ?? demandLatency; + + this.isListening.not.if { Error("HelpSynthPar not listening \n").throw }; + switchCounter = switchCounter + 1; + (switchCounter == 0).if { + // .start could have been called before + this.isPlaying.not.if { + size.do({|i| + synth = Synth.newPaused(helpSynthString ++ "_" ++ i.asString, + [\i_out, bus.index + i, \out, bus.index + i] ++ ((index == i).if { args } { [] }), + server); + playingHelpSynths.put(i,synth); + // CmdPeriod.remove(synth); + }) + }; + (switchOn.if { hsStartIndices ++ [index] } { hsStartIndices }).asSet.do({|i| + server.sendBundle(late, playingHelpSynths[i].runMsg(true)); + hsRunnings[i] = true; + }); + }{ + // pause last synth only if index changes + (lastPause && (lastIndex != index)).if { + server.sendBundle(late, playingHelpSynths[lastIndex].runMsg(false)); + hsRunnings[index] = false; + }; + set.if { + switchOn.if { + server.sendBundle(late, (playingHelpSynths[index]).setMsg(*args), playingHelpSynths[index].runMsg(true)); + hsRunnings[index] = true; + }{ + server.sendBundle(late, (playingHelpSynths[index]).setMsg(*args)); + } + }; + }; + lastIndex = index; + lastPause = switchOff; + isPlaying = true; + } + + schedRun { |flag, latency, hsIndices| + // this.isPlaying.not.if { Error("PHSpar not being played \n").throw }; + + this.isPlaying.if { + server.sendBundle(latency ?? demandLatency, + *hsIndices.collect({|x| hsRunnings[x] = flag; playingHelpSynths[x].runMsg(flag)}) + ); + }; + } + + clearBookkeeping { + this.superPerform(\clearBookkeeping); + times_switchIndices.clear; + trigMsgIDs_hsIndices.clear; + times_theseHSindices.clear; + times_hsIndices.clear; + hsRunnings.fill(false); + + switchCounter = -1; + lastIndex = nil; + lastPause = nil; + currentSwitchIndex = nil; + } +} diff --git a/Classes/HS/PHelpSynth.sc b/Classes/HS/PHelpSynth.sc new file mode 100644 index 0000000..2572007 --- /dev/null +++ b/Classes/HS/PHelpSynth.sc @@ -0,0 +1,77 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +PHS : PHelpSynth {} + +PHelpSynth : PHelpSynthUse { + var <>helpSynthArgs; + + *basicNew { |helpSynth, helpSynthArgs ... pbindArgs| + ^super.basicNew(helpSynth, *pbindArgs).helpSynthArgs_(helpSynthArgs); + } + + *new { |helpSynth, helpSynthArgs ... pbindArgs| + ^this.basicNew(helpSynth, helpSynthArgs, *pbindArgs).commonInit; + } + + inputError { |str| + Error("\nWrong PHS input - " ++ str ++ "\n").throw + } + + addInputCheck { + helpSynth.isKindOf(HelpSynthPar).if { + this.inputError("helpSynth must be an instance of HS") + }; + (helpSynthArgs.isNil || (helpSynthArgs.isKindOf(SequenceableCollection) and: { + helpSynthArgs.miSC_isPossibleHelpSynthArgs(helpSynth.controlNames) })).not.if { + this.inputError("helpSynthArgs must be collection of key / value pairs, \n" ++ + "keys: control names of defined help synth \n" ++ + "values: control values or functions which produce them \n") + }; + } + + play { |clock, quant, hsPlay| + // dummy arg hsPlay + var warnMsg = "HS already playing - use " ++ + "PHSuse for using a playing or pausing HS (or stop and free by Cmd-.)\n"; + (helpSynth.isPlaying && helpSynth.hasJustStartedWithPause.not).if { + warnMsg.warn + }{ + helpSynth.isPHSplaying = true; + ^PHelpSynthPlayer(this).play(clock, quant.asQuant, true); + } + } + + // for use with VarGui + asTask { |clock, quant, hsStop = false, hsPlay = true, newEnvir = true, removeCtrWithCmdPeriod = true| + var envir, task, player = this, controller; + envir = newEnvir.if { () }{ currentEnvironment }; + task = envir.use { Task { 1e5.wait } }; + controller = SimpleController(task) + .put(\userPlayed, { envir.use { player = player.play(clock: clock, quant: quant.asQuant, hsPlay: hsPlay) } }) + .put(\userStopped, { player.stop(hsStop) }); + removeCtrWithCmdPeriod.if { CmdPeriod.doOnce { controller.remove } }; ^task + } + +} diff --git a/Classes/HS/PHelpSynthPar.sc b/Classes/HS/PHelpSynthPar.sc new file mode 100644 index 0000000..89bcc62 --- /dev/null +++ b/Classes/HS/PHelpSynthPar.sc @@ -0,0 +1,132 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +PHSpar : PHelpSynthPar {} + +PHelpSynthPar : PHelpSynthParUse { + var <>startPattern, <>switchDur, <>switchIndex, <>helpSynthArgs, <>switchOn, <>switchOff, <>set, <>hsStartIndices; + + *basicNew { |helpSynthPar, switchDur = 10000, switchIndex = 0, helpSynthArgs, pbindArgs, hsIndices, switchOn = false, switchOff = false, set = true, hsStartIndices = \all| + ^super.basicNew(helpSynthPar, pbindArgs, hsIndices).switchDur_(switchDur).switchIndex_(switchIndex).helpSynthArgs_(helpSynthArgs) + .switchOn_(switchOn).switchOff_(switchOff).set_(set).hsStartIndices_(hsStartIndices); + } + + *new { |helpSynthPar, switchDur = 10000, switchIndex = 0, helpSynthArgs, pbindArgs, hsIndices, switchOn = false, switchOff = false, set = true, hsStartIndices = \all| + ^this.basicNew(helpSynthPar, switchDur, switchIndex, helpSynthArgs, pbindArgs, hsIndices, switchOn, switchOff, set, hsStartIndices) + .commonInit.addInit; + } + + inputError { |str| + Error("\nWrong PHSpar input - " ++ str ++ "\n").throw + } + + addInputCheck { + var helpSynthArgsMsg = "helpSynthArgs must be collection of: nils or collections of key / value pairs, \n" ++ + "size must equal the number of help synths. \n" ++ + "keys: control names of the corresponding help synth \n" ++ + "values: control values or patterns/streams which produce them \n"; + + helpSynth.isKindOf(HelpSynthPar).not.if { + this.inputError("helpSynthPar must be an instance of HSpar") + }; + ((switchDur.isKindOf(Number) and: { switchDur > 0 }) || switchDur.isKindOf(Pattern) || switchDur.isKindOf(Stream) ).not.if { + this.inputError("switchDur must be number > 0 or a pattern / stream producing numbers") + }; + ((switchIndex.isKindOf(Number) and: { (switchIndex >= 0) && (switchIndex < helpSynth.size) }) || + switchIndex.isKindOf(Pattern) || switchIndex.isKindOf(Stream) ).not.if { + this.inputError("switchIndex must be a possible HS synth index or a pattern / stream producing such indices") + }; + (switchOn.isKindOf(Boolean) || switchOn.isKindOf(Pattern) || switchOn.isKindOf(Stream) ).not.if { + this.inputError("switchOn must be boolean or a pattern / stream producing booleans") + }; + (switchOff.isKindOf(Boolean) || switchOff.isKindOf(Pattern) || switchOff.isKindOf(Stream) ).not.if { + this.inputError("switchOff must be boolean or a pattern / stream producing booleans") + }; + (set.isKindOf(Boolean) || set.isKindOf(Pattern) || set.isKindOf(Stream) ).not.if { + this.inputError("set must be boolean or a pattern / stream producing booleans") + }; + hsStartIndices.miSC_isKindOfPossibleHSstartIndices(helpSynth.size).not.if { + this.inputError("hsStartIndices must be collection of possible help synth indices, \\all or \\none.") + }; + (helpSynthArgs.isNil || + (helpSynthArgs.isKindOf(SequenceableCollection) and: { + (helpSynthArgs.size == helpSynth.size) && + helpSynthArgs.every({|x,i| x.isNil or: { + x.isKindOf(SequenceableCollection) and: { + x.miSC_isPossibleHelpSynthParArgs(helpSynth.controlNames[i]) + } + }}) + } + ) + ).not.if { + this.inputError(helpSynthArgsMsg); + }; + } + + addInit { + hsStartIndices = hsStartIndices.miSC_normalizeHSstartIndices(helpSynth.size); + startPattern = Pbind( + \dur, switchDur, + \index, switchIndex, + \switchOn, switchOn, + \switchOff, switchOff, + \setArgs, set, + \hsStartIndices, hsStartIndices, + \theseArgs, Pswitch1(helpSynth.size.collect({|i| + (helpSynthArgs.isNil or: { helpSynthArgs[i].size == 0 }).if { [\dummyArg, 0] }{ Pclutch(Ptuple(helpSynthArgs[i]), Pkey(\setArgs)) }}), + Pkey(\index)), + \dummy, Pfunc({|e| e.use { + var args = [~index, ~theseArgs, ~switchOn, ~switchOff, ~setArgs, ~hsStartIndices] ; + helpSynth.currentSwitchIndex = ~index; + helpSynth.play(*args); + }; + }), + \type, \rest); + } + + play { |clock, quant, hsPlay| + // dummy arg hsPlay + var warnMsg = "HSpar already playing - use " ++ + "PHSparUse for using a playing or pausing HSpar (or stop and free by Cmd-.)\n"; + (helpSynth.isPlaying && helpSynth.hasJustStartedWithPause.not).if { + warnMsg.warn + }{ + helpSynth.isPHSplaying = true; + ^PHSparPlayer(this).play(clock, quant.asQuant, true); + } + } + + // for use with VarGui + asTask { |clock, quant, hsStop = false, hsPlay = true, switchStop = false, newEnvir = true, removeCtrWithCmdPeriod = true| + var envir, task, player = this, controller; + envir = newEnvir.if { () }{ currentEnvironment }; + task = envir.use { Task { 1e5.wait } }; + controller = SimpleController(task) + .put(\userPlayed, { envir.use { player = player.play(clock: clock, quant: quant.asQuant, hsPlay: hsPlay) } }) + .put(\userStopped, { player.stop(hsStop, switchStop) }); + removeCtrWithCmdPeriod.if { CmdPeriod.doOnce { controller.remove } }; ^task + } +} + diff --git a/Classes/HS/PHelpSynthParPlayer.sc b/Classes/HS/PHelpSynthParPlayer.sc new file mode 100644 index 0000000..02c2d10 --- /dev/null +++ b/Classes/HS/PHelpSynthParPlayer.sc @@ -0,0 +1,185 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +PHSparPlayer : PHelpSynthParPlayer {} + +PHelpSynthParPlayer : PHelpSynthUsePlayer { + var <>helpSynthPlayer, <>isSwitchPlaying = false; + + *new { arg pHelpSynthPar; + ^super.new(pHelpSynthPar); + } + + play { |clock, quant, hsPlay = true, switchPlay = true, pbindPlay = true, quantBufferTime = 0.2| + var startQuant, demandQuant, receiveQuant, latestQuant, thisClock = clock ?? TempoClock.default, hsPlayIndices, adaptQuant = true; + + (justPlaying.not && justStopping.not).if { + justPlaying = true; + #startQuant, demandQuant, receiveQuant = + helpSynth.makeAdaptedQuants(thisClock, quant.asQuant, adaptQuant, quantBufferTime, switchResumeDelay ); + latestQuant = [startQuant, demandQuant, receiveQuant].sort({|x,y| x.phase <= y.phase}).last; + Task({ justPlaying = false; }).play(thisClock, quant: latestQuant); + + isPlaying.not.if { + ((hsPlay == true) && (switchPlay == true)).not.if { + Error("hsPlay and switchPlay must be true at first call of play - other values at resume only").throw; + }; + // helpSynth.allocBus; + helpSynth.hasJustStartedWithPause.not.if { helpSynth.allocBus }; + + CmdPeriod.add(this); + helpSynth.isListening.not.if { + helpSynth.listen(pHelpSynth.relDemandIndexUserNums.size); + }; + helpSynth.updateDemandIndexOffsets(pHelpSynth); + helpSynth.resetInitSeconds; + + #helpSynthPlayer, helpSynthDemandPlayer, helpSynthReceivePlayer = + [\startPattern, \demandPattern, \receivePattern].collect({|x| pHelpSynth.perform(x).asEventStreamPlayer}); + helpSynth.demandStreams_indexOffsets.put(helpSynthDemandPlayer.originalStream, helpSynth.lastDemandIndexOffset); + helpSynth.receiveStreams_indexOffsets.put(helpSynthReceivePlayer.originalStream, helpSynth.lastDemandIndexOffset); + + helpSynthPlayer.play(clock, quant: startQuant); + isSwitchPlaying = true; + + pbindPlay.if { + helpSynthDemandPlayer.play(clock, quant: demandQuant); + helpSynthReceivePlayer.play(clock, quant: receiveQuant); + isPbindPlaying = true; + }; + + helpSynth.mainPHSplayer = this; + // players are stopped separately with CmdPeriod (free) + [helpSynthPlayer, helpSynthDemandPlayer, helpSynthReceivePlayer].do({|x| CmdPeriod.remove(x)}); + helpSynth.isPHSplaying = true; + isPlaying = true; + + helpSynth.hasJustStartedWithPause.if { helpSynth.hasJustStartedWithPause = false }; + }{ + hsPlayIndices = this.hsInputIndices(hsPlay); + helpSynth.schedRun(true, helpSynth.demandLatency + quantBufferTime, hsPlayIndices); + + (switchPlay && isSwitchPlaying.not).if { helpSynthPlayer.play(clock, quant: startQuant); isSwitchPlaying = true; }; + switchResumeDelay = 0; + + (pbindPlay && isPbindPlaying.not).if { + helpSynthDemandPlayer.play(clock, quant: demandQuant); + helpSynthReceivePlayer.play(clock, quant: receiveQuant); + isPbindPlaying = true; + }; + } + } + } + + hsInputIndices { |hsInput| + var size = helpSynth.size; + case + { hsInput == \switch } { ^[helpSynth.currentSwitchIndex] } + { (hsInput == \none) || (hsInput == false) } { ^[] } + { (hsInput == \all) || (hsInput == true) } { ^(0..size-1) } + { hsInput.isInteger and: { (hsInput < size) && (hsInput >= 0) }} { ^[hsInput] } + { hsInput.isKindOf(Collection) and: + { hsInput.every({|x| x.isInteger and: { (x < size) && (x >= 0) } }) }} { ^hsInput } + { true } { Error.throw("Wrong hsStop / hsPlay input - must be one of: \\all, \\none, \\switch, true, " ++ + "false, a valid HSpar index (integer) or a collection of valid HSpar indices") } + } + + stop { |hsStop = false, switchStop = false, pbindStop = true, addAction| + var clock = helpSynthReceivePlayer.clock, someHSstop, + hsStopIndices, nextDemandAbs, stopAbs, stopBufferTime = 0.15; + + (justPlaying.not && justStopping.not).if { + justStopping = true; + hsStopIndices = this.hsInputIndices(hsStop); + someHSstop = (hsStop != []).if { true }{ false }; + + // stopAbs: basic stopping delay, 8 cases of stopping ... + stopAbs = stopBufferTime * clock.tempo + clock.beats; + + clock.schedAbs(stopAbs, { + (isPbindPlaying && pbindStop).if { + // now look at next demand from delayed stopping point + nextDemandAbs = helpSynthDemandPlayer.nextBeat; + clock.schedAbs(nextDemandAbs, { + (isSwitchPlaying && switchStop).if { + // next switch resume will be delayed by time to next switch from this demand time + switchResumeDelay = (helpSynthPlayer.nextBeat - clock.beats) / clock.tempo; + helpSynthPlayer.stop; + isSwitchPlaying = false; + }{ + switchResumeDelay = 0; + }; + clock.schedAbs((cleanupDelay + max(helpSynth.totalLatency, switchResumeDelay)) * clock.tempo + nextDemandAbs, { + justStopping = false; + }); + someHSstop.if { helpSynth.schedRun(false, helpSynth.demandLatency, hsStopIndices); }; + nil; + }); + helpSynthDemandPlayer.stop; + clock.schedAbs(helpSynth.totalLatency * clock.tempo + stopAbs, { helpSynthReceivePlayer.stop; isPbindPlaying = false; }); + clock.schedAbs((helpSynth.totalLatency + cleanupDelay) * clock.tempo + stopAbs, { addAction.value; nil; }); + }{ + (isSwitchPlaying && switchStop).if { + switchResumeDelay = (helpSynthPlayer.nextBeat - clock.beats) / clock.tempo; + helpSynthPlayer.stop; + isSwitchPlaying = false; + }{ + switchResumeDelay = 0; + }; + clock.schedAbs((cleanupDelay + switchResumeDelay) * clock.tempo + stopAbs, { justStopping = false; }); + someHSstop.if { helpSynth.schedRun(false, helpSynth.demandLatency, hsStopIndices); }; + }; + nil; + }); + }; + } + + pause { |hsStop = true, switchStop = false, pbindStop = true, addAction| + this.stop(hsStop, switchStop, pbindStop, addAction); + } + + cmdPeriod { this.free } + + free { + isPlaying.not.if { + // "Player not playing".warn; + }{ + helpSynthPlayer.stop; + helpSynthDemandPlayer.stop; + helpSynthReceivePlayer.stop; + helpSynth.isPHelpSynthPlaying = false; + isPlaying = false; + isPbindPlaying = false; + isSwitchPlaying = false; + + helpSynth.otherPHSusePlayers.do(_.free); + SystemClock.sched(helpSynth.totalLatency + cleanupDelay, { this.clearBookkeeping; }); + helpSynth.free(helpSynth.totalLatency + cleanupDelay) + }; + CmdPeriod.remove(this); + } +} + + diff --git a/Classes/HS/PHelpSynthParUse.sc b/Classes/HS/PHelpSynthParUse.sc new file mode 100644 index 0000000..08817f7 --- /dev/null +++ b/Classes/HS/PHelpSynthParUse.sc @@ -0,0 +1,210 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +PHSparUse : PHelpSynthParUse {} + +PHelpSynthParUse : PHelpSynthUse { + + *basicNew { |helpSynth, pbindArgs, hsIndices| + ^super.basicNew(helpSynth, *pbindArgs).hsIndices_(hsIndices); + } + + *new { |helpSynth, pbindArgs, hsIndices| + ^this.basicNew(helpSynth, pbindArgs, hsIndices).commonInit; + } + + inputError { |str| + Error("\n Wrong PHSparUse input - " ++ str ++ "\n").throw + } + + pbindHelpMsg { + ^"pbindArgs must be collection of the form: [dur1, pbindData1, ... , durN, pbindDataN] \n" ++ + "dur i: duration value or pattern / stream of durations for corresponding Pbind(s) \n" ++ + "pbindData i: a collection of Pbind pairs or a collection of Pbind pair collections, \n" ++ + "defining possibly several Pbinds with same event timing \n" + } + + getReservedKeys { ^[\demandIndex, \timeGrains, \vals, \theseIndices, \switchIndex, \theseVals, \thisVal, \dur, \val, \receiveCleanup] } + + addInputCheck { + var hsIndicesMsg = "hsIndices must be collection of possible hsIndex inputs, \n" ++ + "its size must equal the number of demands in pbindArgs. \n" ++ + "Each hsIndex must either be a possible integer HelpSynth index, one of the symbols \\all, \\switch, \n" ++ + "a collection of unequal HelpSynth indices or a pattern / stream, which produces such items. \n"; + helpSynth.isKindOf(HelpSynthPar).not.if { + this.inputError("helpSynthPar must be an instance of HSpar") + }; + (hsIndices.isNil || (hsIndices.isKindOf(SequenceableCollection) and: { + (hsIndices.size == (pbindArgs.size.div(2))) and: { + hsIndices.miSC_isKindOfPossibleHSindices(pbindArgs.size.div(2), helpSynth.size) + } + }) + ).not.if { + this.inputError(hsIndicesMsg) + }; + } + + makeDemandPattern { |relDemandIndex, demandDur, demandIndexUserNum, demandHSIndices| + ^Pbind( + \dur, demandDur, + \demandHSIndices, demandHSIndices, + \type, \rest, + \dummy, Pfunc({|e| e.use({ + var timeGrains = ((thisThread.seconds - helpSynth.initSeconds) * helpSynth.granularity).round.asInteger, + trigSynth, trigMsgID, trigSynthID, responder, demandIndex, thisDemandIndexOffset, switchIndex, demandNum, x, + oldHSindices, newHSindices, theseHSindices; + + // only one trigger at a time, but it may cover more than one demand + thisDemandIndexOffset = helpSynth.demandStreams_indexOffsets.at(thisThread.miSC_getParent); + demandIndex = thisDemandIndexOffset + relDemandIndex; + + // make entry times_durs + helpSynth.times_durs.at(demandIndex).put(timeGrains, ~dur ); + + // put value in prioirity queue once for each demand index + (helpSynth.timeQueues.at(demandIndex)).put(timeGrains, timeGrains); + + switchIndex = helpSynth.times_switchIndices.at(timeGrains) ?? + (x = helpSynth.currentSwitchIndex; helpSynth.times_switchIndices.put(timeGrains, x); x); + + oldHSindices = helpSynth.times_hsIndices.at(timeGrains); + theseHSindices = case + { ~demandHSIndices.isNil || (~demandHSIndices == \switch) } { [switchIndex] } + { ~demandHSIndices == \all } { (0..(helpSynth.size - 1)) } + { true } { ~demandHSIndices.asArray }; + + helpSynth.times_theseHSindices.at(demandIndex).put(timeGrains, theseHSindices); + + newHSindices = oldHSindices.isNil.if { + theseHSindices.asSet + }{ + theseHSindices.asSet - oldHSindices.asSet; + }; + + demandNum = helpSynth.times_demandNums.removeAt(timeGrains) ?? 0; + // make entry times_hsIndices + helpSynth.times_hsIndices.put(timeGrains, (oldHSindices ++ newHSindices).asArray.sort); + + newHSindices.do({|i| + var trigSynthIndex = thisDemandIndexOffset * helpSynth.size + i; + + trigMsgID = helpSynth.nextTrigMsgID; + trigSynthID = helpSynth.trigSynths.at(trigSynthIndex).nodeID; + responder = helpSynth.makeResponder(trigSynthID, trigMsgID); + responder.add; + + // make entry trigMsgIDs_times + helpSynth.trigMsgIDs_times.put(trigMsgID, timeGrains); + + // make entry trigMsgIDs_hsIndices + helpSynth.trigMsgIDs_hsIndices.put(trigMsgID, i); + + helpSynth.playingHelpSynths.at(i).isNil.if { + Error("trying to poll a value from a help synth not playing, as " ++ + "it hasn't been switched before (maybe set hsStartIndices = \all)").throw; + }{ + helpSynth.server.sendBundle(helpSynth.demandLatency, ["/n_set", trigSynthID, \t_tr, 1], + ["/n_set", trigSynthID, \trigMsgID, trigMsgID]); + } + }); + demandNum = demandNum + 1; + + // make entry times_demandNums + helpSynth.times_demandNums.put(timeGrains, demandNum); }) + }) + ) + } + + makeReceivePatternProtoArgs { |relDemandIndex, repeats| + ^[\demandIndex, + Pstutter(repeats, Pfunc({ |e| e.use { + helpSynth.receiveStreams_indexOffsets.at(thisThread.miSC_getParent.miSC_getParent) + relDemandIndex; + }})).asStream, + \timeGrains, + Pstutter(repeats, Pfunc({ |e| e.use { + this.helpSynth.timeQueues.at(~demandIndex).pop.asInteger; + }})).asStream, + \vals, + Pstutter(repeats, Pfunc({|e| e.use { + helpSynth.times_vals.at(~timeGrains); + }})).asStream, + \theseIndices, + Pstutter(repeats, Pfunc({|e| e.use { + helpSynth.times_theseHSindices.at(~demandIndex).at(~timeGrains); + }})).asStream, + \switchIndex, + Pstutter(repeats, Pfunc({|e| e.use { + helpSynth.times_switchIndices.at(~timeGrains); + }})).asStream, + \theseVals, + Pstutter(repeats, Pfunc({|e| e.use { + ~vals.at(~theseIndices); + }})).asStream, + \thisVal, + Pstutter(repeats, Pfunc({|e| e.use { + ~theseVals.at(0) + }})).asStream, + \dur, + Pstutter(repeats, Pfunc({ |e| e.use { + var maybeDur; + maybeDur = (helpSynth.times_durs.at(~demandIndex)).at(~timeGrains); + maybeDur ?? { Error(" no registered duration at time in ms: " ++ ~timeGrains.asString).throw; } + }})).asStream, + \val, + Pstutter(repeats, Pfunc({ |e| e.use { + var maybeVal, theseDemandIndices; + + maybeVal = ~vals.at(~switchIndex); + maybeVal ?? ~thisVal ?? { Error(" no registered duration at time in ms: " ++ ~timeGrains.asString).throw; } + }})).asStream, + \receiveCleanup, + Pstutter(repeats, Pfunc({ |e| e.use { + var demandNum; + + (helpSynth.times_durs.at(~demandIndex)).removeAt(~timeGrains); + (helpSynth.times_theseHSindices.at(~demandIndex)).removeAt(~timeGrains); + demandNum = helpSynth.times_demandNums.removeAt(~timeGrains); + (demandNum == 1).if { + helpSynth.times_vals.removeAt(~timeGrains); + helpSynth.times_switchIndices.removeAt(~timeGrains); + }{ + helpSynth.times_demandNums.put(~timeGrains, demandNum - 1) + } + }})).asStream + ] + } + + play { |clock, quant| + var warnMsg = "No PHSparPlayer - " ++ + "Playing a PHSparUse needs a PHSpar being played\n"; + + helpSynth.isPHSplaying.not.if { + warnMsg.warn; + }{ + ^PHelpSynthUsePlayer(this).play(clock, quant.asQuant); + } + } +} + diff --git a/Classes/HS/PHelpSynthPlayer.sc b/Classes/HS/PHelpSynthPlayer.sc new file mode 100644 index 0000000..9070e30 --- /dev/null +++ b/Classes/HS/PHelpSynthPlayer.sc @@ -0,0 +1,146 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + + +PHSplayer : PHelpSynthPlayer {} + +PHelpSynthPlayer : PHelpSynthUsePlayer { + + *new { arg pHelpSynth; + ^super.new(pHelpSynth); + } + + play { |clock, quant, hsPlay = true, pbindPlay = true, quantBufferTime = 0.2| + var startQuant, demandQuant, receiveQuant, thisClock = clock ?? TempoClock.default, adaptQuant = true, latestQuant, hsStartFunc; + + (justPlaying.not && justStopping.not).if { + justPlaying = true; + #startQuant, demandQuant, receiveQuant = + helpSynth.makeAdaptedQuants(thisClock, quant.asQuant, adaptQuant, quantBufferTime); + latestQuant = [startQuant, demandQuant, receiveQuant].sort({|x,y| x.phase <= y.phase}).last; + Task({ justPlaying = false; }).play(thisClock, quant: latestQuant); + + isPlaying.not.if { + (hsPlay == true).not.if { + Error("hsPlay must be true at first call of play - false at resume only").throw; + }; + helpSynth.hasJustStartedWithPause.not.if { helpSynth.allocBus }; + + CmdPeriod.add(this); + helpSynth.isListening.not.if { + helpSynth.listen(pHelpSynth.relDemandIndexUserNums.size); + }; + helpSynth.updateDemandIndexOffsets(pHelpSynth); + helpSynth.resetInitSeconds; + + hsStartFunc = helpSynth.hasJustStartedWithPause.if { + + { helpSynth.schedRun(true, helpSynth.demandLatency) } + }{ + { helpSynth.play(pHelpSynth.helpSynthArgs ?? [], helpSynth.demandLatency) } + }; + + Task(hsStartFunc).play(clock, quant: startQuant); + + #helpSynthDemandPlayer, helpSynthReceivePlayer = + [\demandPattern, \receivePattern].collect({|x| pHelpSynth.perform(x).asEventStreamPlayer}); + helpSynth.demandStreams_indexOffsets.put(helpSynthDemandPlayer.originalStream, helpSynth.lastDemandIndexOffset); + helpSynth.receiveStreams_indexOffsets.put(helpSynthReceivePlayer.originalStream, helpSynth.lastDemandIndexOffset); + + pbindPlay.if { + helpSynthDemandPlayer.play(clock, quant: demandQuant); + helpSynthReceivePlayer.play(clock, quant: receiveQuant); + isPbindPlaying = true; + }; + + helpSynth.mainPHSplayer = this; + // players are freed separately with CmdPeriod (free) + // [helpSynthDemandPlayer, helpSynthReceivePlayer].do({|x| CmdPeriod.remove(x)}); + helpSynth.isPHSplaying = true; + isPlaying = true; + + helpSynth.hasJustStartedWithPause.if { helpSynth.hasJustStartedWithPause = false }; + }{ + hsPlay.if { Task({ helpSynth.schedRun(true, helpSynth.demandLatency) }).play(clock, quant: startQuant); }; + (pbindPlay && isPbindPlaying.not).if { + helpSynthDemandPlayer.play(clock, quant: demandQuant); + helpSynthReceivePlayer.play(clock, quant: receiveQuant); + isPbindPlaying = true; + } + } + } + } + + stop { |hsStop = false, pbindStop = true, addAction| + var clock = helpSynthReceivePlayer.clock, hsStopLatency, + nextDemandAbs, stopAbs, stopBufferTime = 0.1; + + (justPlaying.not && justStopping.not).if { + justStopping = true; + stopAbs = stopBufferTime * clock.tempo + clock.beats; + hsStopLatency = helpSynth.demandLatency * clock.tempo; + clock.schedAbs(stopAbs, { + (isPbindPlaying && pbindStop).if { + nextDemandAbs = helpSynthDemandPlayer.nextBeat; + hsStop.if { + helpSynth.schedRun(false, nextDemandAbs - clock.beats / clock.tempo + hsStopLatency); + }; + helpSynthDemandPlayer.stop; + clock.schedAbs(helpSynth.totalLatency * clock.tempo + clock.beats, { helpSynthReceivePlayer.stop; isPbindPlaying = false; }); + clock.schedAbs((helpSynth.totalLatency + cleanupDelay) * clock.tempo + clock.beats, { addAction.value; nil; }); + clock.schedAbs((helpSynth.totalLatency + cleanupDelay) * clock.tempo + nextDemandAbs, { justStopping = false; }); + }{ + clock.schedAbs(cleanupDelay * clock.tempo + stopAbs, { justStopping = false; }); + hsStop.if { helpSynth.schedRun(false, hsStopLatency); }; + }; + nil; + }) + } + } + + pause { |hsStop = true, pbindStop = true, addAction| + this.stop(hsStop, pbindStop, addAction); + } + + cmdPeriod { this.free } + + free { + isPlaying.not.if { + // "Player not playing".warn; + }{ + helpSynthDemandPlayer.stop; + helpSynthReceivePlayer.stop; + helpSynth.isPHelpSynthPlaying = false; + isPlaying = false; + isPbindPlaying = false; + + helpSynth.otherPHSusePlayers.do(_.free); + SystemClock.sched(helpSynth.totalLatency + cleanupDelay, { this.clearBookkeeping; }); + helpSynth.free(helpSynth.totalLatency + cleanupDelay); + }; + CmdPeriod.remove(this); + } +} + diff --git a/Classes/HS/PHelpSynthUse.sc b/Classes/HS/PHelpSynthUse.sc new file mode 100644 index 0000000..be79cf5 --- /dev/null +++ b/Classes/HS/PHelpSynthUse.sc @@ -0,0 +1,230 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +PHSuse : PHelpSynthUse {} + +PHelpSynthUse { + var <>helpSynth, <>pbindArgs, <>demandPattern, <>receivePattern, <>relDemandIndexUserNums, <>hsIndices; + + *basicNew { |helpSynth ... pbindArgs| + ^super.new.helpSynth_(helpSynth).pbindArgs_(pbindArgs); + } + + *new { |helpSynth ... pbindArgs| + ^this.basicNew(helpSynth, *pbindArgs).commonInit; + } + + inputError { |str| + Error("\n Wrong PHSuse input - " ++ str ++ "\n").throw + } + + pbindHelpMsg { + ^"pbindArgs (not collected) must be of the form: dur1, pbindData1, ... , durN, pbindDataN \n" ++ + "dur i: duration value or pattern / stream of durations for corresponding Pbind(s) \n" ++ + "pbindData i: a collection of Pbind pairs or a collection of Pbind pair collections, \n" ++ + "defining possibly several Pbinds with same event timing \n" + } + + getReservedKeys { ^[\demandIndex, \timeGrains, \dur, \val, \receiveCleanup] } + + commonInputCheck { + var reservedKeys = this.getReservedKeys; + + (pbindArgs.isKindOf(SequenceableCollection) and: { pbindArgs.miSC_isPossiblePbindArgs }).not.if { + this.inputError(this.pbindHelpMsg) + }; + pbindArgs.miSC_hasNoReservedKeysInPbindArgs(reservedKeys).not.if { + this.inputError("Pbind keys must not contain one of the reserved keys: \n" ++ + reservedKeys.asString ++ "\n\n" ++ this.pbindHelpMsg + ) + }; + } + + addInputCheck { + helpSynth.isKindOf(HelpSynthPar).if { + this.inputError("helpSynth must be an instance of HS") + }; + } + + commonInit { + helpSynth.isKindOf(HelpSynth).not.if { + this.inputError("helpSynth must be an instance of HS or HSpar") + }; + helpSynth.inputCheck.if { this.commonInputCheck.addInputCheck }; + relDemandIndexUserNums = List.new; + # demandPattern, receivePattern = this.makeDemandAndReceivePatterns(pbindArgs); + } + + makeDemandPattern { |relDemandIndex, durPat, demandIndexUserNum, hsIndices| + ^Pbind( + \dur, durPat, + \type, \rest, + \dummy, Pfunc({|e| + var timeGrains = ((thisThread.seconds - helpSynth.initSeconds) * helpSynth.granularity).round.asInteger, + trigSynth, trigMsgID, trigSynthID, demandNum, responder, demandIndex; + + // only one trigger at a time, but it may cover more than one demand + demandIndex = helpSynth.demandStreams_indexOffsets.at(thisThread.miSC_getParent) + relDemandIndex; + trigSynthID = helpSynth.trigSynths.at(demandIndex).nodeID; + + // make entry times_durs + helpSynth.times_durs.at(demandIndex).put(timeGrains, e.use({~dur}) ); + + // put value in prioirity queue once for each demand index + (helpSynth.timeQueues.at(demandIndex)).put(timeGrains, timeGrains); + + // check demand indices of this time + demandNum = helpSynth.times_demandNums.removeAt(timeGrains); + + demandNum.isNil.if { + // no trigMsg yet scheduled for this time + // make responder to expect this trigMsg from the trigger synth from now on + trigMsgID = helpSynth.nextTrigMsgID; + + responder = helpSynth.makeResponder(trigSynthID, trigMsgID); + responder.add; + + // make entry times_demandNums + helpSynth.times_demandNums.put(timeGrains, 1); + + // make entry trigMsgIDs_times + helpSynth.trigMsgIDs_times.put(trigMsgID, timeGrains); + + helpSynth.server.sendBundle(helpSynth.demandLatency, ["/n_set", trigSynthID, \t_tr, 1], + ["/n_set", trigSynthID, \trigMsgID, trigMsgID] + ); + }{ // more than one demand at a time grain, but ... no further msg, update times_demandNums + helpSynth.times_demandNums.put(timeGrains, demandNum + 1); + }; + }) + ) + } + + makeReceivePatternProtoArgs { |relDemandIndex, repeats| + ^[\demandIndex, + Pstutter(repeats, Pfunc({ |e| e.use { + helpSynth.receiveStreams_indexOffsets.at(thisThread.miSC_getParent.miSC_getParent) + relDemandIndex; + }})).asStream, + \timeGrains, + Pstutter(repeats, Pfunc({ |e| e.use { + helpSynth.timeQueues.at(~demandIndex).pop.asInteger; + }})).asStream, + \dur, + Pstutter(repeats, Pfunc({ |e| e.use { + var maybeDur; + maybeDur = (helpSynth.times_durs.at(~demandIndex)).at(~timeGrains); + + maybeDur.isNil.if { + ("WARNING: no registered duration at time in ms: " + ++ ~timeGrains.asString).postln; + ~type = \rest; + }; + maybeDur + }})).asStream, + \val, + Pstutter(repeats, Pfunc({ |e| e.use { + var maybeVal, theseDemandIndices; + maybeVal = (helpSynth.times_vals).at(~timeGrains); + maybeVal.isNil.if { + ("WARNING: no registered value at time in ms: " + ++ ~timeGrains.asString).postln; + }; + maybeVal + }})).asStream, + \receiveCleanup, + Pstutter(repeats, Pfunc({ |e| e.use { + var demandNum; + (helpSynth.times_durs.at(~demandIndex)).removeAt(~timeGrains); + demandNum = helpSynth.times_demandNums.removeAt(~timeGrains); + (demandNum == 1).if { + helpSynth.times_vals.removeAt(~timeGrains); + }{ + helpSynth.times_demandNums.put(~timeGrains, demandNum - 1) + } + }})).asStream + ] + } + + makeDemandAndReceivePatterns { + var demandPatternArgs = List.new, receivePatternArgs = List.new, + durPat, relDemandIndexUserNum, pairsOrArrayOf, demandCounter = 1, receiveCounter = 1, protoArgs; + + pbindArgs.do({|item,i| + (i.even).if { + durPat = item; + pairsOrArrayOf = pbindArgs @ (i+1); + relDemandIndexUserNum = pairsOrArrayOf.every( {|x| x.isKindOf(Array) } ).if { + pairsOrArrayOf.size + }{ + pairsOrArrayOf = [pairsOrArrayOf]; + 1; + }; + relDemandIndexUserNums.add(relDemandIndexUserNum); + demandPatternArgs.add(helpSynth.sequentialSchedShift * demandCounter); + demandPatternArgs.add(this.makeDemandPattern(i.div(2), durPat, + relDemandIndexUserNum, hsIndices.notNil.if { hsIndices[i.div(2)] }{ \switch } )); + demandCounter = demandCounter + 1; + // catch responses for a demand index at a time once + protoArgs = this.makeReceivePatternProtoArgs(i.div(2), pairsOrArrayOf.size); + + relDemandIndexUserNum.do({ |j| + receivePatternArgs.add(helpSynth.sequentialSchedShift * receiveCounter); + receivePatternArgs.add(Pbind(*(protoArgs ++ (pairsOrArrayOf @ j)))); + receiveCounter = receiveCounter + 1; + }); + }; + }); + ^[Ptpar(demandPatternArgs), Ptpar(receivePatternArgs)]; + } + + play { |clock, quant| + var warnMsg = "No PHSplayer - " ++ + "Playing a PHSuse needs a PHS being played\n"; + helpSynth.isPHSplaying.not.if { + warnMsg.warn; + }{ + ^PHelpSynthUsePlayer(this).play(clock, quant.asQuant); + } + } + + // for use with VarGui + asTask { |clock, quant, newEnvir = true, removeCtrWithCmdPeriod = true| + var envir, task, player = this, controller; + envir = newEnvir.if { () }{ currentEnvironment }; + task = envir.use { Task { 1e5.wait } }; + controller = SimpleController(task) + .put(\userPlayed, { + this.helpSynth.isPHelpSynthPlaying.if { + envir.use { player = player.play(clock: clock, quant: quant.asQuant) } + }{ + // PHSusePlayer must wait for PHSplayer + { task.stop; }.defer(0.05); + } + }) + .put(\userStopped, { player.stop }); + removeCtrWithCmdPeriod.if { CmdPeriod.doOnce { controller.remove } }; ^task + } +} + diff --git a/Classes/HS/PHelpSynthUsePlayer.sc b/Classes/HS/PHelpSynthUsePlayer.sc new file mode 100644 index 0000000..a8be22b --- /dev/null +++ b/Classes/HS/PHelpSynthUsePlayer.sc @@ -0,0 +1,114 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +PHSusePlayer : PHelpSynthUsePlayer {} + +PHelpSynthUsePlayer { + var <>pHelpSynth, <>helpSynth, <>helpSynthDemandPlayer, <>helpSynthReceivePlayer, <>trigMsgIDqueue, + <>isPlaying, <>isPbindPlaying, <>demandIndexOffset, <>switchResumeDelay = 0, <>cleanupDelay = 0.01, + <>justPlaying = false, <>justStopping = false; + + *new { arg pHelpSynthUse; + ^super.new.pHelpSynth_(pHelpSynthUse).helpSynth_(pHelpSynthUse.helpSynth) + .trigMsgIDqueue_(PriorityQueue.new) + .isPlaying_(false).isPbindPlaying_(false); + // isPlaying: player already started, also true when pbinds pausing - isPbindPlaying: pbinds are playing + } + + play { |clock, quant, quantBufferTime = 0.2| + var startQuant, demandQuant, receiveQuant, thisClock = clock ?? TempoClock.default, adaptQuant = true, latestQuant; + + (justPlaying.not && justStopping.not).if { + justPlaying = true; + #startQuant, demandQuant, receiveQuant = + helpSynth.makeAdaptedQuants(thisClock, quant.asQuant, adaptQuant, quantBufferTime); + latestQuant = [demandQuant, receiveQuant].sort({|x,y| x.phase <= y.phase}).last; + Task({ justPlaying = false; }).play(thisClock, quant: latestQuant); + + isPlaying.not.if { + CmdPeriod.add(this); + helpSynth.listen(pHelpSynth.relDemandIndexUserNums.size); + helpSynth.updateDemandIndexOffsets(pHelpSynth); + + helpSynthDemandPlayer = pHelpSynth.demandPattern.play(clock, quant: demandQuant); + helpSynthReceivePlayer = pHelpSynth.receivePattern.play(clock, quant: receiveQuant); + helpSynth.demandStreams_indexOffsets.put(helpSynthDemandPlayer.originalStream, helpSynth.lastDemandIndexOffset); + helpSynth.receiveStreams_indexOffsets.put(helpSynthReceivePlayer.originalStream, helpSynth.lastDemandIndexOffset); + + // [helpSynthDemandPlayer, helpSynthReceivePlayer].do({|x| CmdPeriod.remove(x)}); + + helpSynth.otherPHSusePlayers.add(this); + isPlaying = true; + isPbindPlaying = true; + }{ + isPbindPlaying.not.if { + helpSynthDemandPlayer.play(clock, quant: demandQuant); + helpSynthReceivePlayer.play(clock, quant: receiveQuant); + isPbindPlaying = true; + } + }; + } + } + + clearBookkeeping { + trigMsgIDqueue.clear; + } + + stop { |addAction| + var clock = helpSynthReceivePlayer.clock, hsStopLatency, stopAbs, stopBufferTime = 0.1; + + (justPlaying.not && justStopping.not).if { + stopAbs = stopBufferTime * clock.tempo + clock.beats; + isPbindPlaying.if { + justStopping = true; + clock.schedAbs(stopAbs, { + helpSynthDemandPlayer.stop; + clock.schedAbs(helpSynth.totalLatency * clock.tempo + clock.beats, { helpSynthReceivePlayer.stop; isPbindPlaying = false; }); + clock.schedAbs((helpSynth.totalLatency + cleanupDelay) * clock.tempo + clock.beats, { addAction.value; justStopping = false; }); + }); + } + } + } + + pause { |addAction| + this.stop(addAction); + } + + cmdPeriod { this.free } + + free { + isPlaying.not.if { + // "Player not playing".warn; + }{ + helpSynthDemandPlayer.stop; + helpSynthReceivePlayer.stop; + isPlaying = false; + isPbindPlaying = false; + + SystemClock.sched(helpSynth.totalLatency + cleanupDelay, { this.clearBookkeeping; }); + }; + CmdPeriod.remove(this); + } + +} diff --git a/Classes/HS/extHelpSynth.sc b/Classes/HS/extHelpSynth.sc new file mode 100644 index 0000000..1ca8a01 --- /dev/null +++ b/Classes/HS/extHelpSynth.sc @@ -0,0 +1,79 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++ HelpSynth { + newPaused { |pHelpSynth, args, latency| + var synth = Synth.basicNew(helpSynthString, server).register; + playingHelpSynths.put(0, synth); + hasJustStartedWithPause = true; + bus.isNil.if { bus = Bus.control(server, size) }; + isListening.not.if { + this.listen(pHelpSynth.relDemandIndexUserNums.size); + }; + server.sendBundle(latency ?? demandLatency, synth.newMsg(server, + [\i_out, bus.index, \out, bus] ++ (args ?? []) ), synth.runMsg(false)); + // CmdPeriod.remove(synth); + isPlaying = true; + isRunning = false; + ^playingHelpSynths[0] + } +} + ++ PHelpSynth { + newPaused { |args, latency| ^helpSynth.newPaused(this, args, latency); } +} + + ++ HelpSynthPar { + newPaused { |pHelpSynthPar, args, latency| + // args = [ args1, args2, ... ] + var synth; + + hasJustStartedWithPause = true; + bus.isNil.if { bus = Bus.control(server, size) }; + isListening.not.if { + this.listen(pHelpSynthPar.relDemandIndexUserNums.size); + }; + + size.do { |i| + synth = Synth.basicNew(helpSynthString ++ "_" ++ i.asString, server).register; + playingHelpSynths.put(i, synth); + // CmdPeriod.remove(synth); + server.sendBundle(latency ?? demandLatency, synth.newMsg(server, + [\i_out, bus.index + i, \out, bus.index + i] ++ ((args ?? []).at(i)) ?? []), + synth.runMsg(false) + ); + hsRunnings[i] = false; + }; + isPlaying = true; + // isRunning = false; + ^playingHelpSynths; + } + +} + ++ PHelpSynthPar { + newPaused { |args, latency| ^helpSynth.newPaused(this, args, latency); } + +} diff --git a/Classes/HS/extObject.sc b/Classes/HS/extObject.sc new file mode 100644 index 0000000..d9a0975 --- /dev/null +++ b/Classes/HS/extObject.sc @@ -0,0 +1,67 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// input checks + ++ Object { + + miSC_isKindOfFixedHSindex { |size| + ^(this.isKindOf(Integer) and: { (this >= 0) && (this < size) }) || + [\switch, \all].includes(this.asSymbol) + } + + miSC_isKindOfFixedHSindices { |size| + ^this.miSC_isKindOfFixedHSindex(size) || ( + this.isKindOf(SequenceableCollection) and: { + this.every({ |x| + x.isKindOf(Integer) && (x >= 0) && (x < size) + }) && + (this.asSet.size == this.size) + } + ) + } + + miSC_isKindOfPossibleHSindices { |demandNum, hsNum| + ^this.isKindOf(SequenceableCollection) and: { + (this.size == demandNum) && + this.every({|x| + x.isKindOf(Pattern) || x.isKindOf(Stream) || x.miSC_isKindOfFixedHSindices(hsNum) + }) + } + } + + miSC_isKindOfPossibleHSstartIndices { |size| + ^(this.isKindOf(SequenceableCollection) and: { this.miSC_isIndexSubset(size) }) || + (this.isKindOf(Integer) and: { this <= (size-1) }) || [\none, \all].includes(this.asSymbol) + } + + miSC_normalizeHSstartIndices { |size| // suppose this.miSC_isKindOfPossibleHSstartIndices was true + case + { this == \all }{ ^(0..(size-1)) } + { this == \none }{ ^[] } + { true }{ ^this.asCollection } + } +} + diff --git a/Classes/HS/extSequenceableCollection.sc b/Classes/HS/extSequenceableCollection.sc new file mode 100644 index 0000000..b307578 --- /dev/null +++ b/Classes/HS/extSequenceableCollection.sc @@ -0,0 +1,89 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// input checks + ++ SequenceableCollection { + + miSC_isKeysAndValues { |requiredKeys| + ^this.size.even and: { this.select({|x,i| i.even }).every({|x| + (x.isKindOf(Symbol) || x.isKindOf(String)) && (requiredKeys.isNil or: { requiredKeys.includes(x.asSymbol) }) + })} + } + + miSC_isPossiblePbindData { + ^this.miSC_isKeysAndValues or: { + this.every({|x| x.isKindOf(SequenceableCollection) and: { x.miSC_isKeysAndValues }}) + } + } + + miSC_isPossiblePbindArgs { + ^this.size.even and: { + this.every({|x,i| + (i.even and: { (x.isKindOf(Number) and: { x > 0 }) or: { x.isKindOf(Pattern) or: { x.isKindOf(Stream) }}}) or: { + i.odd and: { x.isKindOf(SequenceableCollection) and: { x.miSC_isPossiblePbindData } } + } + }) + } + } + + miSC_hasNoReservedKeys { |reservedKeys| // suppose this.miSC_isKeysAndValues was true + ^(this.select({|x,i| i.even }).asSet & (reservedKeys ?? Set[])).size == 0 + } + + miSC_hasNoReservedKeysInPbindData { |reservedKeys| // suppose this.miSC_isPossiblePbindData was true + ^(this.miSC_isKeysAndValues and: { this.miSC_hasNoReservedKeys(reservedKeys) }) or: { + this.every({|x| x.isKindOf(SequenceableCollection) and: { x.miSC_hasNoReservedKeys(reservedKeys) }}) + } + } + + miSC_hasNoReservedKeysInPbindArgs { |reservedKeys| // suppose this.miSC_isPossiblePbindArgs was true + ^this.every({|x,i| + i.even or: { x.miSC_hasNoReservedKeysInPbindData(reservedKeys) } }) + } + + miSC_isPossibleHelpSynthArgs { |requiredKeys| + ^this.miSC_isKeysAndValues(requiredKeys) and: { this.every({|x,i| + (i.odd and: { x.isKindOf(Number) or: { x.isKindOf(Function) }}) || i.even + }) + } + } + + miSC_isPossibleHelpSynthParArgs { |requiredKeys| + ^this.miSC_isKeysAndValues(requiredKeys) and: { this.every({|x,i| + (i.odd and: { x.isKindOf(Number) or: { x.isKindOf(Pattern) or: { x.isKindOf(Stream) }}}) || i.even + }) + } + } + + miSC_areControlRateSynthDefs { + ^this.every({|x| x.children.last.rate == \control }) + } + + miSC_isIndexSubset {|size| + ^(this.asSet.size == this.size) && this.every({|x| x.isKindOf(Integer) && (x >= 0) && (x buf.numFrames).if { + SimpleInitError("wrong inInit/outInit data, size too large").throw + }{ + buf.set(initItem.reverse, buf.numFrames-(initItem.size)) + } + }{ + SimpleInitError( + "wrong inInit/outInit data," ++ + "inconsistent array, see help" + ).throw + } + } + { true }{ + SimpleInitError( + "wrong inInit/outInit data, " ++ + "see help for conventions" + ) + } + }; + + var makeDepthArray = { |depth, buf| + case + { depth.isKindOf(Integer) }{ (0..depth-1) } + { depth.isKindOf(SequenceableCollection) }{ + depth.every(_.isKindOf(Integer)).if { + (depth.size > buf.numFrames).if { + SimpleInitError("wrong inDepth/outDepth, size too large").throw + }{ + depth + } + }{ + SimpleInitError( + "wrong inDepth/outDepth data," ++ + "inconsistent array, see help" + ).throw + } + } + { true }{ + SimpleInitError( + "wrong inDepth/outDepth data, " ++ + "see help for conventions" + ) + } + }; + + [inInit, outInit].do { |init, i| + var buffers = [inBufs, outBufs][i]; + var ioSize = [inBufs.size, outBufs.size][i]; + init.isKindOf(SequenceableCollection).if { + (init.size > ioSize).if { + SimpleInitError( + "wrong inInit/outInit data, " ++ + "size too large, for setting a number of init values " ++ + "per in/out you need double brackets - " + "check conventions in help" + ).throw + }{ + (init.size < ioSize).if { + "".postln; + ("NOTE: " ++ ((i == 0).if { "inInit " }{ "outInit " }) ++ + "size smaller than " ++ + ((i == 0).if { "size of in, " }{ "outSize, " }) ++ + "index wrapping applied").postln; + }; + buffers.do { |bufs, j| + bufs.do { |buf| initSingleBuf.(init.wrapAt(j), buf) } + } + } + }{ + buffers.do { |bufs| + bufs.do { |buf| initSingleBuf.(init, buf) } + } + } + }; + + ^[inDepth, outDepth].collect { |depth, i| + var buffers = [inBufs, outBufs][i]; + var ioSize = [inBufs.size, outBufs.size][i]; + depth.isKindOf(SequenceableCollection).if { + (depth.size > ioSize).if { + SimpleInitError( + "wrong inDepth/outDepth data, " ++ + "size too large, for setting a number of inDepth/outDepth indices " ++ + "per buffer you need double brackets - " + "check conventions in help" + ).throw + }{ + (depth.size < ioSize).if { + "".postln; + ("NOTE: " ++ ((i == 0).if { "inDepth " }{ "outDepth " }) ++ + "size smaller than " ++ + ((i == 0).if { "size of in, " }{ "outSize, " }) ++ + "index wrapping applied").postln; + }; + buffers.collect { |bufs, j| + makeDepthArray.(depth.wrapAt(j), bufs[0]) + } + } + }{ + buffers.collect { |bufs| makeDepthArray.(depth, bufs[0]) } + } + }; + } + + + *ar { |func, in, outSize = 0, inDepth = 1, outDepth = 2, inInit, outInit, + blockSize = 64, blockFactor = 1, graphOrderType = 1, + leakDC = true, leakCoef = 0.995| + + var inSize, inBufsTemp, inBufs, outBufs, iteratedOut, outType, + bufSize, inDepthFlop, outDepthFlop, makeBlockCount, makeFeedSig, + blockCount, bufOffset, outSig, doThis, orderSym; + + // some helper funtions: + // we want be able to handle signals of size 0 as resp. within 'in' and 'out', + // but for data processing it's easier have all in unified nested arrays. + // Preparations are a bit lengthy, but core bufrd/wr loop is short. + + // makeBufs encodes into unified nesting + var makeBufs_0 = { |num, bufSize| { LocalBuf(bufSize).clear } ! max(1, num) }; + var makeBufs = { |num, bufSize| + num.isKindOf(Integer).if { + max(1, num).collect { makeBufs_0.(1, bufSize) } + }{ + num.collect(makeBufs_0.(_, bufSize)) + } + }; + + var getOutType = { |outSize| + outSize.isNumber.if { + (outSize == 0).if { 0 }{ 1 } + }{ + 2 + } + }; + + var outDispatch = { |sig, i, j, type| + (type == 0).if { + sig + }{ + (type == 1).if { + sig[i] + }{ + (sig[i].size == 0).if { sig[i] }{ sig[i][j] } + } + } + }; + + // indexDispatch and shapeSig decode from unified nesting + var indexDispatch = { |sig, i, j| + case + { sig.size == 0 }{ sig } + { sig[i].size == 0 }{ sig[i] } + { true }{ sig[i][j] } + }; + + var shapeSig = { |sig, sizes| + sizes.isKindOf(SequenceableCollection).if { + sizes.collect { |size, j| + (size == 0).if { sig[j][0] }{ sig[j] } + } + }{ + (sizes == 0).if { sig[0][0] }{ sig.collect(_[0]) } + } + }; + + // nils in the depth matrix indicate positions to insert zeros. + // This can save many ugens in the case of multichannel feedback/feedforward + // with differentiated lookback size. + + var fillWithNils = { |array| + var maxSize = array.collect(_.size).maxItem; + array.collect(_.extend(maxSize, nil)) + }; + + // lookback beyond blockSize + + // basically all the same in the core procedure below, + // but the buffer indices to be used need a looping offset + + makeBlockCount = { + Main.versionAtLeast(3, 9).if { + Duty.kr(ControlDur.ir, 0, Dseq((0..blockFactor-1), inf)) + }{ + // correct init bug for older SC version + Duty.kr(ControlDur.ir, 0, Dseq((0..blockFactor-1), inf) - 1 % blockFactor) + } + }; + blockCount = (blockFactor > 1).if { makeBlockCount.() }{ 0 }; + bufOffset = blockCount * blockSize; + + inSize = in.isKindOf(SequenceableCollection).if { in.collect(_.size) }{ in.size }; + bufSize = blockSize * blockFactor; + + inBufsTemp = makeBufs.(inSize, bufSize); + inBufs = makeBufs.(inSize, bufSize); + outBufs = makeBufs.(outSize, bufSize); + outType = getOutType.(outSize); + + #inDepth, outDepth = this.checkInits(inBufs, outBufs, inInit, outInit, inDepth, outDepth); + + // depths were passed per signal, but we rather want to iterate over lookback indices + + inDepthFlop = fillWithNils.(inDepth).flop; + outDepthFlop = fillWithNils.(outDepth).flop; + + + // trick for buffering feedforward audio signals: + // store in temporary buffers first and write with kr to 'real' buffers thereafter. + // That way we can use the 'real' buffers in the iteration process below, + // which is based on the order of the synthdef graph. + + // the temporary buffers cannot be used for reference to feedforward data in the + // iteration over blockSize, as they are overriden immediately. + + // same structure to bring encoded feedback and feedforward signals + // to desired format for func's 'in' and 'out' + + makeFeedSig = { |depthFlop, bufs, sizes, i| + var sig; + depthFlop.collect { |depths, j| + sig = bufs.collect { |buf, k| + depths[k].isNil.if { [0] }{ + BufRd.kr(1, buf, (i-depths[k]) % bufSize) + } + }; + shapeSig.(sig, sizes); + } + }; + + + // begin of core definition + // 3 versions of implementation + + // with graphOrderType = 1 (default), buffer reading and writing is forced + // to its desired order with the use of sum and 1 + var l = bufOffset + i; + + // prepare data from 'in' signals + // force writers after temporary writer + inBufs.do { |bufs, j| + bufs.do { |buf, k| + doThis = orderSym.applyTo( + doThis, + BufWr.kr(BufRd.kr(1, inBufsTemp[j][k], l), buf, l) + ) + } + }; + + // here comes actual fb/ff relation into play + // it can refer to former 'in'/'out' values (see makeFeedSig definition) + + iteratedOut = func.value( + makeFeedSig.(inDepthFlop, inBufs, inSize, l), + makeFeedSig.(outDepthFlop, outBufs, outSize, l), + i + ); + + // the calculated next sample(s) written to buffer(s) + outBufs.do { |bufs, j| + bufs.do { |buf, k| + doThis = orderSym.applyTo( + doThis, + BufWr.kr(outDispatch.(iteratedOut, j, k, outType), buf, l) + ) + } + }; + }; + // move through buffers(s), make ar signal + // ' 1 + var l = bufOffset + i; + + // prepare data from 'in' signals + inBufs.collect { |bufs, j| + bufs.collect { |buf, k| + BufWr.kr(BufRd.kr(1, inBufsTemp[j][k], l), buf, l) + } + }; + // here comes actual fb/ff relation into play + // it can refer to former 'in'/'out' values (see makeFeedSig definition) + iteratedOut = func.value( + makeFeedSig.(inDepthFlop, inBufs, inSize, l), + makeFeedSig.(outDepthFlop, outBufs, outSize, l), + i + ); + // the calculated next sample(s) written to buffer(s) + outBufs.collect { |bufs, j| + bufs.collect { |buf, k| + BufWr.kr(outDispatch.(iteratedOut, j, k, outType), buf, l) + } + } + }; + // move through buffers(s), make ar signal + outSig = BufRd.ar(1, shapeSig.(outBufs, outSize), Phasor.ar(0, 1, 0, bufSize)); + } + + { true }{ SimpleInitError("wrong graphOrderType, must be 0, 1 or 2, see help").throw }; + + ^leakDC.if { LeakDC.ar(outSig, leakCoef) }{ outSig } + } + + +// The kr variant provides a nearly identical interface, +// but without args blockSize and blockFactor, +// therefore the implementation, although much more straight, +// has subtle differences: +// buffer sizes are taken from depth params, +// in samples don't have to be buffered temporarily +// instead inBufs have to be rewritten, +// also some helper functions are slightly different. + + +*kr { |func, in, outSize = 0, inDepth = 1, outDepth = 2, inInit, outInit, + graphOrderType = 1, leakDC = true, leakCoef = 0.995| + + var inSize, inBufs, outBufs, iteratedOut, outType, unshapedOut, + inDepthFlop, outDepthFlop, makeFeedSig, blockCount, shapedOutSig, + outSig, doThis, orderSym; + + // some helper funtions: + // we want be able to handle signals of size 0 as resp. within 'in' and 'out', + // but for data processing it's easier have all in unified nested arrays. + // Preparations are a bit lengthy, but core bufrd/wr loop is short. + + // makeBufs encodes into unified nesting + var makeBufs_0 = { |num, bufSize| { LocalBuf(bufSize).clear } ! max(1, num) }; + + + // different in the kr case, must take bufSizes from depth + var makeBufs = { |num, depth| + num.isKindOf(Integer).if { + max(1, num).collect { |i| + makeBufs_0.(1, depth.miSC_maybeWrapAt(i).asArray.maxItem) + } + }{ + num.collect { |x, i| + makeBufs_0.(x, depth.miSC_maybeWrapAt(i).asArray.maxItem) + } + } + }; + + var getOutType = { |outSize| + outSize.isNumber.if { + (outSize == 0).if { 0 }{ 1 } + }{ + 2 + } + }; + + var outDispatch = { |sig, i, j, type| + (type == 0).if { + sig + }{ + (type == 1).if { + sig[i] + }{ + (sig[i].size == 0).if { sig[i] }{ sig[i][j] } + } + } + }; + + // indexDispatch and shapeSig decode from unified nesting + var indexDispatch = { |sig, i, j| + case + { sig.size == 0 }{ sig } + { sig[i].size == 0 }{ sig[i] } + { true }{ sig[i][j] } + }; + + var shapeSig = { |sig, sizes| + sizes.isKindOf(SequenceableCollection).if { + sizes.collect { |size, j| + (size == 0).if { sig[j][0] }{ sig[j] } + } + }{ + (sizes == 0).if { sig[0][0] }{ sig.collect(_[0]) } + } + }; + + // nils in the depth matrix indicate positions to insert zeros. + // This can save many ugens in the case of multichannel feedback/feedforward + // with differentiated lookback size. + + var fillWithNils = { |array| + var maxSize = array.collect(_.size).maxItem; + array.collect(_.extend(maxSize, nil)) + }; + + // blockCount works different here from ar: + // it counts absolutely, modulo is taken per Buffer + + blockCount = Main.versionAtLeast(3, 9).if { + Duty.kr(ControlDur.ir, 0, Dseries(0, 1, inf)) + }{ + // correct init bug for older SC version + Duty.kr(ControlDur.ir, 0, Dseries(0, 1, inf) - 1) + }; + + inSize = in.isKindOf(SequenceableCollection).if { in.collect(_.size) }{ in.size }; + + // convention: indices start from 0 and increase with time, + // thus an init array is stored in reverse order (see checkInits) + inBufs = makeBufs.(inSize, inDepth); + outBufs = makeBufs.(outSize, outDepth); + + outType = getOutType.(outSize); + + #inDepth, outDepth = this.checkInits(inBufs, outBufs, inInit, outInit, inDepth, outDepth); + + // depths were passed per signal, but we rather want to iterate over lookback indices + + inDepthFlop = fillWithNils.(inDepth).flop; + outDepthFlop = fillWithNils.(outDepth).flop; + + // same structure to bring encoded feedback and feedforward signals + // to desired format for func's 'in' and 'out' + + // differs from ar ! + makeFeedSig = { |depthFlop, ioBufs, sizes, blockCount| + var sig; + depthFlop.collect { |depths, j| + sig = ioBufs.collect { |bufs, k| + depths[k].isNil.if { [0] }{ + // differs from ar here + BufRd.kr(1, bufs, (blockCount-depths[k]) % (bufs.collect(_.numFrames))) + } + }; + shapeSig.(sig, sizes); + } + }; + + + // begin of core definition + // 3 versions of implementation + + // with graphOrderType = 1 (default), buffer reading and writing is forced + // to its desired order with the use of sum and 1) and: { argList0.flat.any(_.isNumber.not) }).if { + err = SimpleInitError("initial ODE Function values have to be " ++ + "calculated in language and there are non-number items (UGens) " ++ + "contained in argList. Either take an intType where initial " ++ + "calculation is not necessary (e.g. \\sym2, \\sym4, etc.) or pass an " + "additional argList0 arg with numbers only").class_("Fb1_ODE").throw; + }; + + (stepDepth > 1).if { + var templateString = + "A multi-step procedure is selected, so initial ODE solution " ++ + "approximations have to be calculated in language, "; + var rateString = (rate == \audio).if { "sampleRate" }{ "controlRate" }; + + // for multi-step procedures we need the first values (resp. arrays of values) + dt0 = dt0 ?? { + var s = Server.default; + var dtRate = s.notNil.if { + (rate == \audio).if { + s.sampleRate + }{ + s.sampleRate / s.options.blockSize + } + }; + + dtRate.isNil.if { + err = SimpleInitError(templateString ++ + "but no default server is found, so " ++ + "an assumption has to be made for first delta t, " ++ + "please define dt0 in secs, recommended: " ++ + "tMul at start / planned " ++ rateString).class_("Fb1_ODE").throw; + }{ + tMul.isNumber.not.if { + err = SimpleInitError(templateString ++ + "but there are non-number items (UGens) contained in tMul, " ++ + "please define dt0 in secs, recommended: " ++ + "tMul at start / planned " ++ rateString).class_("Fb1_ODE").throw; + }{ + (templateString ++ + "but dt0 is not defined, " ++ + "tMul divided by default server's " ++ rateString ++ + " is taken instead." + ).postln; + tMul / dtRate + } + } + }; + }{ + // also for single-step procedures we must evaluate the ODE Function for x_d intTypes + // but for stepDepth == 1 dt0 is not used, no problem if it's a UGen or nil + }; + + + outInit = odeDef.makeOutInit(intType, init_intType, t0, y0, dt0, argList0, true); + + (rate == \audio).if { + op = \ar; + + // we have to build the Fb1 function in a way that it reads ar args. + // convention: composeArIn first (indices possibly defined in compose Function), + // then detected ar args from argList, then the integrated time (Sweep) and tMul (if ar) + + // This "external" time integration is absolutely necessary as + // a binary operator summing of dts leads to drifts !! + + composeArIn = composeArIn.asArray; + tSum = t0 + Sweep.ar(0, tMul); + + argListFlatAr = argList.flatten.select(_.miSC_isAr); + + fb1_in = composeArIn ++ argListFlatAr ++ tSum ++ (tMul.miSC_isAr.if { tMul }); + + fb1_inDepth = (1 ! composeArIn.size) ++ + (1 ! argListFlatAr.size) ++ 2 ++ (tMul.miSC_isAr.if { 1 }); + + fb1_inInit = (nil ! composeArIn.size) ++ + (nil ! argListFlatAr.size) ++ t0 ++ (tMul.miSC_isAr.if { [nil] }); + + // used in the Fb1 Function + basicDelta = SampleDur.ir; + + // in this Function integration method and ODE Function are composed + // it will get passed the out arg from the Fb1 Function + fb1_intFunc = odeDef.dispatchIntFunc(intType); + + + // core: everything packed into the Fb1 function, which contructs the graph iteratively + fb1_func = { |in, out| + var lastDt, count = composeArIn.size - 1; + composeIns = composeArIn.size.collect { |i| in[0][i] }; + + #argIns, count = argList.miSC_adaptFb1ArgList(in[0], count); + + tSumInOld = in[1][count + 1]; + tMulIn = tMul.miSC_isAr.if { in[0][count + 2] }{ tMul }; + + fb1_intFunc.(out, tSumInOld, basicDelta * tMulIn, argIns) + .miSC_fb1_perform(compose, composeIns, size, outSize); + }; + + // wrong blockSize can lead to confusion, better check + sBlockSize = Server.default.notNil.if { + Server.default.options.blockSize + }; + + blockSize.isNil.if { + sBlockSize.isNil.if { + err = SimpleInitError( + "no default blockSize detected and no blockSize passed") + .class_("Fb1_ODE").throw; + }{ + "".postln; + ("Fb1_ODE: taking default server's blockSize " ++ sBlockSize.asString).postln; + "".postln; + blockSize = sBlockSize; + } + }{ + (blockSize.isInteger and: { blockSize.isPowerOfTwo }).if { + (sBlockSize != blockSize).if { + sBlockSize.notNil.if { + "".postln; + ("blockSize " ++ blockSize.asString ++ "passed to Fb1_ODE " ++ + "unequal to default Server's blockSize " ++ + sBlockSize.asString ++ " - intended ?").warn; + "".postln; + }{ + "".postln; + ("Fb1_ODE: no default blockSize detected, " ++ + "taking passed blockSize " ++ blockSize.asString).warn; + "".postln; + } + } + }{ + err = SimpleInitError("blockSize must be integer power of 2") + .class_("Fb1_ODE").throw; + } + }; + + // blockFactor has to be chosen correctly, + // especially relevant for small blockSizes: + + // e.g. for blockSize 4 a blockFactor of 1 is sufficient + // only for stepDepth 1-4; + // for blockSize 1 we need a blockFactor equal to stepDepth, + // in general: + + blockFactor = (stepDepth - 1).div(blockSize) + 1; + + sig = Fb1.ar(fb1_func, fb1_in, + outSize: outSize, + inDepth: fb1_inDepth, + outDepth: outDepth, + inInit: fb1_inInit, + outInit: outInit, + blockSize: blockSize, + blockFactor: blockFactor, + graphOrderType: graphOrderType, + leakDC: leakDC, + leakCoef: leakCoef + ); + + }{ + op = \kr; + + // things are more straight here + // still "external" time integration is better ! + tSum = t0 + Sweep.kr(0, tMul); + + // used in the Fb1 Function + basicDelta = ControlDur.ir; + + // in this Function integration method and ODE Function are composed + // it will get passed the out arg from the Fb1 Function + fb1_intFunc = odeDef.dispatchIntFunc(intType); + + // core: everything packed into the Fb1 function, which contructs the graph iteratively + fb1_func = { |in, out| + fb1_intFunc.(out, in[1] /* last time */, basicDelta * tMul, argList) + .miSC_fb1_perform(compose, [], size, outSize); + }; + + // pass integrated time via 'in' as we need last one + sig = Fb1.kr(fb1_func, tSum, + outSize: outSize, + inDepth: 2, + outDepth: outDepth, + inInit: t0, + outInit: outInit, + graphOrderType: graphOrderType, + leakDC: leakDC, + leakCoef: leakCoef + ) + }; + + // optional default scaling (makes sense for systems like Lorenz) + + outScale = withOutScale.if { + [odeDef.outScale, odeDef.diffOutScale] + }{ + [1, 1] + }; + + // options for derivation and time return + + sig = (sig[..size-1] * outScale[0]) ++ + (withDiffChannels.if { + sig[rawIntSize-size..rawIntSize-1] * outScale[1] + }{ [] }) ++ + (withTimeChannel.if { tSum }{ [] }); + + sig = (outSize == 1).if { sig[0] }{ sig }; + + // might be necessary + ^sig 1).if { + z0 = y0 ++ this.(nil, t0, y0, *args); + }; + withTime.if { z0 = z0 ++ [t0] }; + ^z0 + } + + makeOutInit { |fb1_intType, init_intType = \sym8, t0, y0, dt, args, withTime = true| + var z0 = this.makeFirstOutInit(fb1_intType, t0, y0, args, withTime); + var depth = Fb1_ODEintdef.at(fb1_intType).stepDepth; + + z0 = (depth == 1).if { + [z0] + }{ + // produces startValues for multi-step procedures like + // Adams-Bashforth und Adams-Moulton + this.nextN(depth - 1, init_intType, t0, z0, dt, args, withTime, true) + }; + // the result is reversed (last values first) and flopped + // in order to be prepared as Fb1 outInit arg + ^z0.reverse.flop + } + + getIntSize { |intType| + ^size * Fb1_ODEintdef.at(intType).sizeFactor; + } + + dispatchIntFunc { |intType| + var multiStep = Fb1_ODEintdef.at(intType).stepDepth > 1; + var n = this.getIntSize(intType); + var comp = this.compose(intType); // step Function from ODE and intType + var size = this.size; + + // this Function is used inside Fb1's Function + // z gets the 'out' arg + + // oldT comes from the external time integrator (Sweep), + // which is necessary to avoid drifts that occur with + // plain summation via binary operators + + ^{ |z, oldT, dt, argList| + var y; + // multi-step integration: need previous arrays from Fb1 'out' + // one-step integration: need only one previous array from Fb1 'out' + multiStep.if { y = z.drop(1) }{ y = z[1] }; + comp.(oldT, y, dt, size, *argList); + } + } + +} + diff --git a/Classes/Nonlinear/Fb1_ODEintdef.sc b/Classes/Nonlinear/Fb1_ODEintdef.sc new file mode 100644 index 0000000..a2408ba --- /dev/null +++ b/Classes/Nonlinear/Fb1_ODEintdef.sc @@ -0,0 +1,606 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +Fb1_ODEintdef { + + // container for stepper Functions representing numeric ODE solvers + // composition of stepper with ODE Function gives the Function to be used within Fb1 + + classvar all, initKeys; + + var 1 + // not to be confused with single-step procedures with sub-steps (like Runge-Kutta) + + // sizeFactor indicates whether ODE function values are buffered + // for some procedures this is necessary (ab, abm, pec, pece etc.) + // for others there exist procedures without and with buffering (e.g. so there is rk3 and rk3_d) + + *new { |name, function, stepDepth = 1, sizeFactor = 1| + ^super.newCopyArgs(name, function, stepDepth, sizeFactor) + .initFb1_ODEintdef(name); + } + + *at { |key| ^all.at(key) } + + *keys { |key| ^all.keys.copy } + + *remove { |key| initKeys.includes(key).not.if { all.put(key, nil) } } + + // remove all added Fb1_ODEintdefs + *reset { all.do { |x| this.remove(x.name) } } + + *postAll { + var nameStrings = (this.keys.asArray.collect { |x| x.asString }).sort; + "\nCurrent Fb1_ODEintdefs: \n".postln; + nameStrings.do { |str| str.postln; }; + "".postln; + } + + *initClass { + var symplecticOp; + + all = IdentityDictionary.new; + + // constructors need unified args, + // thus time and size are always defined and not necessarily needed + + // y is expected to be an array (state vector from the ODE system) + // for muiltistep procedures it expects + // an array of arrays (including previous states) + + // the _d variants produce channel(s) for the differential (resp. ODE function values) + // whenever the main integration doesn't need to employ such anyway + + // It's recommended to work with symplectic integrators + // as they are better for long-time stability, which is necessary for oscillatory solutions! + // Also it's easy to get symplectic integration of higher order, + // simply adapt for order 2k by dividing dt and repeat operation + + symplecticOp = { |order, odeDef, t, y, dt, size, args| + var newArgs = y.copy, yMid = y.copy, yNew = 0!size, or = 1/order; + + or = 1/order; + + div(order, 2).do { |k| + (k > 0).if { + newArgs = yNew.copy; + yMid = yNew.copy; + }; + for(0, size-1, { |i| + (i > 0).if { newArgs[i-1] = yMid[i-1] }; + yMid[i] = odeDef.(i, t, newArgs, *args) * dt * or + yMid[i]; + }); + for(size-1, 0, { |i| + (i + 1 < size).if { newArgs[i+1] = yNew[i+1] }; + yNew[i] = odeDef.(i, t, newArgs, *args) * dt * or + yMid[i]; + }); + }; + yNew + }; + + Fb1_ODEintdef(\sym2, + { |odeDef, t, y, dt, size ... args| + symplecticOp.(2, odeDef, t, y, dt, size, args); + }, 1, 1); + + + // in the '_d' variants y has doubled size but that doesn't matter as + // size is passed to symplecticOp + Fb1_ODEintdef(\sym2_d, + { |odeDef, t, y, dt, size ... args| + var yNew = symplecticOp.(2, odeDef, t, y, dt, size, args); + var fNew = fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + Fb1_ODEintdef(\sym4, + { |odeDef, t, y, dt, size ... args| + symplecticOp.(4, odeDef, t, y, dt, size, args); + }, 1, 1); + + + Fb1_ODEintdef(\sym4_d, + { |odeDef, t, y, dt, size ... args| + var yNew = symplecticOp.(4, odeDef, t, y, dt, size, args); + var fNew = fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + Fb1_ODEintdef(\sym6, + { |odeDef, t, y, dt, size ... args| + symplecticOp.(6, odeDef, t, y, dt, size, args); + }, 1, 1); + + + Fb1_ODEintdef(\sym6_d, + { |odeDef, t, y, dt, size ... args| + var yNew = symplecticOp.(6, odeDef, t, y, dt, size, args); + var fNew = fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + Fb1_ODEintdef(\sym8, + { |odeDef, t, y, dt, size ... args| + symplecticOp.(8, odeDef, t, y, dt, size, args); + }, 1, 1); + + + Fb1_ODEintdef(\sym8_d, + { |odeDef, t, y, dt, size ... args| + var yNew = symplecticOp.(8, odeDef, t, y, dt, size, args); + var fNew = fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + Fb1_ODEintdef(\sym12, + { |odeDef, t, y, dt, size ... args| + symplecticOp.(12, odeDef, t, y, dt, size, args); + }, 1, 1); + + + Fb1_ODEintdef(\sym12_d, + { |odeDef, t, y, dt, size ... args| + var yNew = symplecticOp.(12, odeDef, t, y, dt, size, args); + var fNew = fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + Fb1_ODEintdef(\sym16, + { |odeDef, t, y, dt, size ... args| + symplecticOp.(16, odeDef, t, y, dt, size, args); + }, 1, 1); + + + Fb1_ODEintdef(\sym16_d, + { |odeDef, t, y, dt, size ... args| + var yNew = symplecticOp.(16, odeDef, t, y, dt, size, args); + var fNew = fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + Fb1_ODEintdef(\sym32, + { |odeDef, t, y, dt, size ... args| + symplecticOp.(32, odeDef, t, y, dt, size, args); + }, 1, 1); + + + Fb1_ODEintdef(\sym32_d, + { |odeDef, t, y, dt, size ... args| + var yNew = symplecticOp.(32, odeDef, t, y, dt, size, args); + var fNew = fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + Fb1_ODEintdef(\sym64, + { |odeDef, t, y, dt, size ... args| + symplecticOp.(64, odeDef, t, y, dt, size, args); + }, 1, 1); + + + Fb1_ODEintdef(\sym64_d, + { |odeDef, t, y, dt, size ... args| + var yNew = symplecticOp.(64, odeDef, t, y, dt, size, args); + var fNew = fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + // Euler simple (1-step RK), not recommended ! + Fb1_ODEintdef(\eu, + { |odeDef, t, y, dt, size ... args| + odeDef.(nil, t, y, *args) * dt + y + }, 1, 1); + + Fb1_ODEintdef(\eu_d, + { |odeDef, t, y, dt, size ... args| + var yy, ff, yNew, fNew; + yy = y[0..size-1]; + ff = y[size..2*size-1]; + yNew = ff * dt + yy; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + // Euler modified (2-step RK) + Fb1_ODEintdef(\eum, + { |odeDef, t, y, dt, size ... args| + var yy = odeDef.value(nil, t, y, *args) * dt * 0.5 + y; + odeDef.(nil, dt * 0.5 + t, yy, *args) * dt + y; + }, 1, 1); + + Fb1_ODEintdef(\eum_d, + { |odeDef, t, y, dt, size ... args| + var yy, ff, z, yNew, fNew; + yy = y[0..size-1]; + ff = y[size..2*size-1]; + z = ff * dt * 0.5 + yy; + yNew = odeDef.(nil, dt * 0.5 + t, z, *args) * dt + yy; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + // Euler improved (Heun), 2-step RK + Fb1_ODEintdef(\eui, + { |odeDef, t, y, dt, size ... args| + var e = odeDef.(nil, t, y, *args); + var z = e * dt + y; + (odeDef.(nil, t + dt, z, *args) + e) * dt * 0.5 + y + }, 1, 1); + + Fb1_ODEintdef(\eui_d, + { |odeDef, t, y, dt, size ... args| + var yy, ff, z, yNew, fNew; + yy = y[0..size-1]; + ff = y[size..2*size-1]; + z = ff * dt + yy; + yNew = (odeDef.(nil, t + dt, z, *args) + ff) * dt * 0.5 + yy; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + // variants of prediction-evaluation-correction + // using explicit Euler and trapezoidal rule + Fb1_ODEintdef(\pec, + { |odeDef, t, y, dt, size ... args| + var tn = t + dt; + var y0 = y[0..size-1]; + var y1 = y[size..2*size-1]; + var p = y1 * dt + y0; + var pe = odeDef.(nil, tn, p, *args); + var pec = (y1 + pe) * dt * 0.5 + y0; + pec ++ pe + }, 1, 2); + + Fb1_ODEintdef(\pecec, + { |odeDef, t, y, dt, size ... args| + var tn = t + dt; + var y0 = y[0..size-1]; + var y1 = y[size..2*size-1]; + var p = y1 * dt + y[0..size-1]; + var pe = odeDef.(nil, tn, p, *args); + var pec = (y1 + pe) * dt * 0.5 + y0; + var pece = odeDef.(nil, tn, pec, *args); + var pecec = (y1 + pece) * dt * 0.5 + y0; + pecec ++ pece + }, 1, 2); + + Fb1_ODEintdef(\pece, + { |odeDef, t, y, dt, size ... args| + var tn = t + dt; + var y0 = y[0..size-1]; + var y1 = y[size..2*size-1]; + var p = y1 * dt + y0; + var pe = odeDef.(nil, tn, p, *args); + var pec = (y1 + pe) * dt * 0.5 + y0; + var pece = odeDef.(nil, tn, pec, *args); + pec ++ pece + }, 1, 2); + + Fb1_ODEintdef(\pecece, + { |odeDef, t, y, dt, size ... args| + var tn = t + dt; + var y0 = y[0..size-1]; + var y1 = y[size..2*size-1]; + var p = y1 * dt + y0; + var pe = odeDef.(nil, tn, p, *args); + var pec = (y1 + pe) * dt * 0.5 + y0; + var pece = odeDef.(nil, tn, pec, *args); + var pecec = (y1 + pece) * dt * 0.5 + y0; + var pecece = odeDef.(nil, tn, pecec, *args); + pecec ++ pecece + }, 1, 2); + + + // Runge-Kutta 3-step + Fb1_ODEintdef(\rk3, + { |odeDef, t, y, dt, size ... args| + var k1 = odeDef.(nil, t, y, *args); + var k2 = odeDef.(nil, dt * 0.5 + t, k1 * dt * 0.5 + y, *args); + var k3 = odeDef.(nil, dt + t, dt * 2 * k2 - (dt * k1) + y, *args); + (k1 + (4 * k2) + k3) * dt / 6 + y + }, 1, 1); + + Fb1_ODEintdef(\rk3_d, + { |odeDef, t, y, dt, size ... args| + var k1, k2, k3, yy, ff, yNew, fNew; + yy = y[0..size-1]; + ff = y[size..2*size-1]; + k1 = ff; + k2 = odeDef.(nil, dt * 0.5 + t, k1 * dt * 0.5 + yy, *args); + k3 = odeDef.(nil, dt + t, dt * 2 * k2 - (dt * k1) + yy, *args); + yNew = (k1 + (4 * k2) + k3) * dt / 6 + yy; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + // Runge-Kutta 3-step (Heun) + Fb1_ODEintdef(\rk3h, + { |odeDef, t, y, dt, size ... args| + var k1 = odeDef.(nil, t, y, *args); + var k2 = odeDef.(nil, dt / 3 + t, k1 * dt / 3 + y, *args); + var k3 = odeDef.(nil, dt * 2/3 + t, dt * (2/3) * k2 + y, *args); + (3 * k3 + k1) * dt * 0.25 + y + }, 1, 1); + + Fb1_ODEintdef(\rk3h_d, + { |odeDef, t, y, dt, size ... args| + var k1, k2, k3, yy, ff, yNew, fNew; + yy = y[0..size-1]; + ff = y[size..2*size-1]; + k1 = ff; + k2 = odeDef.(nil, dt / 3 + t, k1 * dt / 3 + yy, *args); + k3 = odeDef.(nil, dt * 2/3 + t, dt * (2/3) * k2 + yy, *args); + yNew = (3 * k3 + k1) * dt * 0.25 + yy; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + // Runge-Kutta 4-step (classical) + Fb1_ODEintdef(\rk4, + { |odeDef, t, y, dt, size ... args| + var k1 = odeDef.(nil, t, y, *args); + var k2 = odeDef.(nil, dt * 0.5 + t, k1 * dt * 0.5 + y, *args); + var k3 = odeDef.(nil, dt * 0.5 + t, k2 * dt * 0.5 + y, *args); + var k4 = odeDef.(nil, dt + t, k3 * dt + y, *args); + ((k2 + k3) * 2 + k1 + k4) * dt / 6 + y + }, 1, 1); + + Fb1_ODEintdef(\rk4_d, + { |odeDef, t, y, dt, size ... args| + var k1, k2, k3, k4, yy, ff, yNew, fNew; + yy = y[0..size-1]; + ff = y[size..2*size-1]; + k1 = ff; + k2 = odeDef.(nil, dt * 0.5 + t, k1 * dt * 0.5 + yy, *args); + k3 = odeDef.(nil, dt * 0.5 + t, k2 * dt * 0.5 + yy, *args); + k4 = odeDef.(nil, dt + t, k3 * dt + yy, *args); + yNew = ((k2 + k3) * 2 + k1 + k4) * dt / 6 + yy; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); + + + // Adams-Bashforth multi-step + // ab Functions are not optimized for iterated language use + // doesn't matter though if merged into the UGenGraphFunction + + // y expects array of arrays ! + // y[0] contains previous y and and previous function values + + Fb1_ODEintdef(\ab2, + { |odeDef, t, y, dt, size ... args| + var yy, ff, yNew, fNew; + yy = y.collect(_[0..size-1]); // prev y + ff = y.collect(_[size..2*size-1]); // prev function values + yNew = (3 * ff[0] - ff[1]) * dt * 0.5 + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 2, 2); + + Fb1_ODEintdef(\ab3, + { |odeDef, t, y, dt, size ... args| + var yy, ff, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + yNew = (23/12 * ff[0] - (16/12 * ff[1]) + (5/12 * ff[2])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 3, 2); + + Fb1_ODEintdef(\ab4, + { |odeDef, t, y, dt, size ... args| + var yy, ff, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + yNew = (55/24 * ff[0] - (59/24 * ff[1]) + (37/24 * ff[2]) - + (9/24 * ff[3])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 4, 2); + + Fb1_ODEintdef(\ab5, + { |odeDef, t, y, dt, size ... args| + var yy, ff, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + yNew = (1901/720 * ff[0] - (2774/720 * ff[1]) + (2616/720 * ff[2]) - + (1274/720 * ff[3]) + (251/720 * ff[4])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 5, 2); + + Fb1_ODEintdef(\ab6, + { |odeDef, t, y, dt, size ... args| + var yy, ff, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + yNew = (4277/1440 * ff[0] - (7923/1440 * ff[1]) + (9982/1440 * ff[2]) - + (7298/1440 * ff[3]) + (2877/1440 * ff[4]) - (475/1440 * ff[5])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 6, 2); + + // Adams-Bashforth-Moulton predictor corrector + // theory suggests to combine k-step predictor with (k-1) step corrector + // y expects array of arrays ! + // y[0] contains previous y and and previous function values + + Fb1_ODEintdef(\abm21, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); // prev y + ff = y.collect(_[size..2*size-1]); // prev function values + p = (3 * ff[0] - ff[1]) * dt * 0.5 + yy[0]; // Adams-Bashforth predictor + fp = odeDef.(nil, t + dt, p, *args); + yNew = (fp + ff[0]) * dt * 0.5 + yy[0]; // Adams-Moulton corrector + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 2, 2); + + Fb1_ODEintdef(\abm22, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (3 * ff[0] - ff[1]) * dt * 0.5 + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((5/12 * fp) + (8/12 * ff[0]) - (ff[1]/12)) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 2, 2); + + Fb1_ODEintdef(\abm32, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (23/12 * ff[0] - (4/3 * ff[1]) + (5/12 * ff[2])) * dt + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((5/12 * fp) + (2/3 * ff[0]) - (ff[1]/12)) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 3, 2); + + Fb1_ODEintdef(\abm33, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (23/12 * ff[0] - (4/3 * ff[1]) + (5/12 * ff[2])) * dt + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((3/8 * fp) + (19/24 * ff[0]) - (5/24 * ff[1]) + (ff[2]/24)) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 3, 2); + + Fb1_ODEintdef(\abm43, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (55/24 * ff[0] - (59/24 * ff[1]) + (37/24 * ff[2]) - + (3/8 * ff[3])) * dt + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((3/8 * fp) + (19/24 * ff[0]) - (5/24 * ff[1]) + (ff[2]/24)) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 4, 2); + + Fb1_ODEintdef(\abm44, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (55/24 * ff[0] - (59/24 * ff[1]) + (37/24 * ff[2]) - + (3/8 * ff[3])) * dt + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((251/720 * fp) + (646/720 * ff[0]) - (264/720 * ff[1]) + + (106/720 * ff[2]) - (19/720 * ff[3])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 4, 2); + + Fb1_ODEintdef(\abm54, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (1901/720 * ff[0] - (2774/720 * ff[1]) + (2616/720 * ff[2]) - + (1274/720 * ff[3]) + (251/720 * ff[4])) * dt + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((251/720 * fp) + (646/720 * ff[0]) - (264/720 * ff[1]) + + (106/720 * ff[2]) - (19/720 * ff[3])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 5, 2); + + Fb1_ODEintdef(\abm55, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (1901/720 * ff[0] - (2774/720 * ff[1]) + (2616/720 * ff[2]) - + (1274/720 * ff[3]) + (251/720 * ff[4])) * dt + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((475/1440 * fp) + (1427/1440 * ff[0]) - (798/1440 * ff[1]) + + (482/1440 * ff[2]) - (173/1440 * ff[3]) + (27/1440 * ff[4])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 5, 2); + + Fb1_ODEintdef(\abm65, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (4277/1440 * ff[0] - (7923/1440 * ff[1]) + (9982/1440 * ff[2]) - + (7298/1440 * ff[3]) + (2877/1440 * ff[4]) - (475/1440 * ff[5])) * dt + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((475/1440 * fp) + (1427/1440 * ff[0]) - (798/1440 * ff[1]) + + (482/1440 * ff[2]) - (173/1440 * ff[3]) + (27/1440 * ff[4])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 6, 2); + + Fb1_ODEintdef(\abm66, + { |odeDef, t, y, dt, size ... args| + var yy, ff, p, fp, yNew, fNew; + yy = y.collect(_[0..size-1]); + ff = y.collect(_[size..2*size-1]); + p = (4277/1440 * ff[0] - (7923/1440 * ff[1]) + (9982/1440 * ff[2]) - + (7298/1440 * ff[3]) + (2877/1440 * ff[4]) - (475/1440 * ff[5])) * dt + yy[0]; + fp = odeDef.(nil, t + dt, p, *args); + yNew = ((19087/60480 * fp) + (65112/60480 * ff[0]) - (46461/60480 * ff[1]) + + (37504/60480 * ff[2]) - (20211/60480 * ff[3]) + (6312/60480 * ff[4]) - + (863/60480 * ff[5])) * dt + yy[0]; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 6, 2); + + initKeys = all.keys; + } + + initFb1_ODEintdef { |name| + + (initKeys.notNil and: { initKeys.includes(name) }).if { + var err = SimpleInitError("Fb1_ODEintdef of this name already exists"); + err.class_("Fb1_ODEintdef"); + err.throw; + }; + + all.put(name, this) + } +} diff --git a/Classes/Nonlinear/GFIS.sc b/Classes/Nonlinear/GFIS.sc new file mode 100644 index 0000000..bda02bf --- /dev/null +++ b/Classes/Nonlinear/GFIS.sc @@ -0,0 +1,55 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +GFIS : UGen { + + *new { |outRate, func, init, n = 1, nOut, leakDC = true, leakCoef = 0.995| + var sigs, outSig; + nOut = nOut ?? { n }; + sigs = 0!n; + n.do { |i| + sigs[i] = (i == 0).if { + func.(init, 0) + }{ + func.(sigs[i-1], i) + } + }; + outSig = nOut.isKindOf(SequenceableCollection).if { + nOut.collect { |i| Select.perform(outRate, i-1, sigs) } + }{ + Select.perform(outRate, nOut-1, sigs) + }; + ^leakDC.if { LeakDC.perform(outRate, outSig, leakCoef) }{ outSig } + } + + *ar { |func, init, n = 1, nOut, leakDC = true, leakCoef = 0.995| + ^this.new(\ar, func, init, n, nOut, leakDC, leakCoef) + } + + *kr { |func, init, n = 1, nOut, leakDC = true, leakCoef = 0.995| + ^this.new(\kr, func, init, n, nOut, leakDC, leakCoef) + } +} + diff --git a/Classes/Nonlinear/extObject.sc b/Classes/Nonlinear/extObject.sc new file mode 100644 index 0000000..e1830e9 --- /dev/null +++ b/Classes/Nonlinear/extObject.sc @@ -0,0 +1,29 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Object { + miSC_isAr { ^false } +} + diff --git a/Classes/Nonlinear/extSequenceableCollection.sc b/Classes/Nonlinear/extSequenceableCollection.sc new file mode 100644 index 0000000..ea153a6 --- /dev/null +++ b/Classes/Nonlinear/extSequenceableCollection.sc @@ -0,0 +1,86 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++SequenceableCollection { + + miSC_adaptFb1ArgList { |in, countOffset| + // expects argList as receiver + var count = countOffset, adaptedFb1ArgList; + adaptedFb1ArgList = this.collect { |argItem, i| + argItem.isCollection.if { + argItem.collect { |y, j| + y.miSC_isAr.if { + count = count + 1; + in[count] + }{ + y + } + } + }{ + argItem.miSC_isAr.if { + count = count + 1; + in[count] + }{ + argItem + } + } + }; + // need count again in Fb1 + ^[adaptedFb1ArgList, count] + } + + +// method for applying functions on numeric approximations with Fb1_ODE + + miSC_fb1_perform { |thing, in, size, outSize| + var valueArray, addArray, composed; + ^thing.isNil.if { + this + }{ + valueArray = this[0..size-1]; + addArray = this[size..outSize-1]; + + composed = thing.isKindOf(SequenceableCollection).if { + // if an array is passed to compose, then + // a Function in it applies to the whole current y (which is an array) + // whereas an operator applies to the respective component + valueArray.collect { |x, i| + var thingI = thing.miSC_maybeWrapAt(i); + thingI.isKindOf(Function).if { + thingI.applyTo(valueArray, in) + }{ + thingI.applyTo(x, in) + } + } + }{ + thing.applyTo(valueArray, in) + }; + composed ++ addArray + } + } +} + + + diff --git a/Classes/Nonlinear/extUGen.sc b/Classes/Nonlinear/extUGen.sc new file mode 100644 index 0000000..5077722 --- /dev/null +++ b/Classes/Nonlinear/extUGen.sc @@ -0,0 +1,28 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++UGen { + miSC_isAr { ^this.rate == 'audio' } +} diff --git a/Classes/Other/DIdev.sc b/Classes/Other/DIdev.sc new file mode 100644 index 0000000..64c015c --- /dev/null +++ b/Classes/Other/DIdev.sc @@ -0,0 +1,125 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// this code now looks straight, but it was hard to figure out ... +// taking the correct stutterings is the trick + + +// weights accept array of drate ugens +// currently this handles the case which is needed for DIdev, +// but not an arbitrary input of possibly nested drate ugens, +// therefore only recommended for use in this context + +Dwrand_DIdev : DUGen { + *new { |list, weights, normalize = 0, repeats = 1| + var partialWeights, index = 0, sum = 1, dwhite; + + (normalize != 0).if { + sum = 0; + // weights have to be stuttered selectively as weights[list.size-1] won't be needed below + weights = weights.collect { |w, i| Dstutter((i + 1 < list.size).if { 2 }{ 1 }, w) }; + list.size.do { |i| sum = sum + Dswitch1(weights, i) }; + }; + + dwhite = Dstutter(list.size - 1, Dwhite(0, sum, repeats)); + + partialWeights = 0 ! (list.size - 1); + partialWeights[0] = Dstutter(list.size - 1, Dswitch1(weights, 0)); + (list.size - 2).do { |i| + partialWeights[i+1] = Dstutter(list.size - 2 - i, Dswitch1(weights, i+1)) + partialWeights[i]; + }; + + (list.size - 1).do { |i| index = index + (dwhite > partialWeights[i]) }; + + ^Dswitch1(list, index) + } +} + + + +// similar trigger tricks + +// triggering difs triggers (maxLookBack * src, maxLookBack * phase) +// triggering probs triggers (maxHiDev - maxLoDev + 1) * difs + +// triggering Dbufwr in addition triggers src and phase, so all in all we need +// maxLookBack * maxSpan + 1 as stutter number for src and phase +// whereas maxSpan = (maxHiDev - maxLoDev + 1) + + +DIdev : DUGen { + + *new { |in = 0, maxLookBack = 5, minLoDev = -5, maxHiDev = 5, lookBack, loDev, hiDev, thr = 1e-3, length = inf| + + var buf = LocalBuf(maxLookBack + 1).clear.set(inf!(maxLookBack + 1)), add, + phase, src, dif, difs, probs, wr, maxSpan = maxHiDev - minLoDev + 1, + indicatorLoDev = 1, indicatorHiDev = 1, sel; + + src = Dstutter(maxLookBack * maxSpan + 1, in); + phase = Dstutter(maxLookBack * maxSpan + 1, Dseries(0)); + + loDev.isKindOf(DUGen).if { loDev = Dstutter(maxSpan, loDev) }; + hiDev.isKindOf(DUGen).if { hiDev = Dstutter(maxSpan, hiDev) }; + lookBack.isKindOf(DUGen).if { lookBack = Dstutter(maxLookBack * maxSpan, lookBack) }; + + // buffer is rewritten cyclically, so look back like this + difs = { |i| Dbufrd(buf, phase - i - 1 % (maxLookBack + 1)) - src } ! maxLookBack; + + // for each index between bounds this indicates if there's an occurrence in the past, + // if yes then probs is set to 0 + + probs = { |i| + var ind = 0; + // consider dynamic lookBack here + maxLookBack.do { |j| ind = ind + (((difs[j] - minLoDev - i).abs < thr) * + (lookBack.isNil.if { + 1 + }{ + sel = UGen.miSC_methodSelectorForRate(lookBack.rate); + (sel == \ar).if { SimpleInputError("no audio rate input allowed as lookBack arg").throw }; + DC.perform(sel, j) < lookBack + })) + }; + 1 - ind.sign + } ! maxSpan; + + // generate indicator arrays if certain -- possibly dynamic -- deviations are given + loDev.notNil.if { + sel = UGen.miSC_methodSelectorForRate(loDev.rate); + indicatorLoDev = { |i| loDev <= DC.perform(sel, i + minLoDev) } ! maxSpan + }; + hiDev.notNil.if { + sel = UGen.miSC_methodSelectorForRate(hiDev.rate); + indicatorHiDev = { |i| DC.perform(sel, i + minLoDev) <= hiDev } ! maxSpan + }; + + probs = (probs * indicatorLoDev * indicatorHiDev); + + add = Dwrand_DIdev((minLoDev..maxHiDev), probs, 1, length); + + ^Dbufwr(src + add, buf, phase); + } +} + diff --git a/Classes/Other/extPlatform.sc b/Classes/Other/extPlatform.sc new file mode 100644 index 0000000..2e66cad --- /dev/null +++ b/Classes/Other/extPlatform.sc @@ -0,0 +1,51 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++ Platform { + *miSCellaneousDirs { |withWarnings = true| + var dirs = (Platform.userExtensionDir +/+ "miSCellaneous*").pathMatch ++ + (Platform.systemExtensionDir +/+ "miSCellaneous*").pathMatch ++ + (Platform.userExtensionDir +/+ "miSCellaneous*" +/+ "miSCellaneous*").pathMatch ++ + (Platform.systemExtensionDir +/+ "miSCellaneous*" +/+ "miSCellaneous*").pathMatch ++ + (Platform.userAppSupportDir +/+ "quarks" +/+ "miSCellaneous*").pathMatch ++ + (Platform.systemAppSupportDir +/+ "quarks" +/+ "miSCellaneous*").pathMatch ++ + (Platform.userAppSupportDir +/+ "downloaded-quarks" +/+ "miSCellaneous*").pathMatch ++ + (Platform.systemAppSupportDir +/+ "downloaded-quarks" +/+ "miSCellaneous*").pathMatch; + dirs = dirs.select { |p| PathName(p).isFolder and: { PathName(p +/+ "Classes").isFolder } }; + withWarnings.if { + case + { dirs.size == 0 }{ + "\nWARNING: no directory beginning with name 'miSCellaneous' found within extension directories\n".postln; + } + { dirs.size > 1 }{ + ("\nWARNING: more than one directory beginning with name 'miSCellaneous' found within extension directories,\n" ++ + "there might be a discrepancy\n").postln; + } + { true }{ } + }; + ^dirs + } +} + diff --git a/Classes/Other/extUGen.sc b/Classes/Other/extUGen.sc new file mode 100644 index 0000000..3a7002d --- /dev/null +++ b/Classes/Other/extUGen.sc @@ -0,0 +1,31 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++ UGen { + *miSC_methodSelectorForRate { |rate| + var sel = this.methodSelectorForRate(rate); + ^(sel == \new).if { \kr }{ sel } + } +} diff --git a/Classes/Patterns/AddEventTypes_PbindFx.sc b/Classes/Patterns/AddEventTypes_PbindFx.sc new file mode 100644 index 0000000..ff508cb --- /dev/null +++ b/Classes/Patterns/AddEventTypes_PbindFx.sc @@ -0,0 +1,282 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +AddEventTypes_PbindFx { + + *initClass { + StartUp.add { + AddEventTypes_PbindFx.makeZeroSynthDef; + AddEventTypes_PbindFx.makeSplitZeroSynthDef; + AddEventTypes_PbindFx.makeSplitSynthDefs(8, 8); + }; + AddEventTypes_PbindFx.makeEventType_pbindFx; + } + + *makeEventType_pbindFx { + Event.addEventType(\pbindFx, + Event.default.eventTypes[\note] <> { |server| + // The array ~fxOrder contains the topological order of effect graph as indices, + // 0 (src) is not contained, the smallest effect index is 1. + // The dictionary ~fxPredecessors contains the fx graph's predecessors, + // also those of \o (out). + // The dictionary ~fxSuccessors contains the fx graph's successors, + // also those of 0 (src). + + var fxNum, // size of ~fxOrder + fxBuses, // array of fx input buses (contains nils if there is no in !), + // size = fxNum + splitBuses, // array of split synth input buses, + // contains nils for effects without split, + // size = fxNum, but different to fxBuses the first item refers to the source + // the last fx in topological order never needs a split + splitFactors, // array of split synths' split factors, 1 means no split, size = fxNum + fxOrderPositions, // dictionary of fx index position in ~fxOrder (+ 1, contains 0 too) + firstFxIds, lastFxIds, // these arrays (size = fxNum) keep track of zero synth ordering + // in fxGroup, needed as zero synths are established before fx synths, + // relevant is first and last zero synth per fx slot + + cleanupDelaySums, // array of cleanup delays after src and fxs, + // therefore contains fxNum + 1 items, + // this array is ordered in the case of an fx sequence but not for a general fx graph, + // thus we need a variable for this: + latestCleanupIndex, + withFxs, startFxBundle, startZeroBundle = [], cleanupClockTempo, + baseLatency, hasGate, gateDelay, cleanupDelta, skipjack, startTime, zeroSynthOverlap, + fxGroupId, srcGroupId, startSplitBundle, startSplitZeroBundle, splitSynthIds; + + ~cleanupDelay = ~cleanupDelay ?? { PbindFx.defaultSourceCleanupDelay }; + ~cleanupDt = ~cleanupDt ?? { PbindFx.defaultCleanupDt }; + ~cleanupClock = ~cleanupClock ?? { SystemClock }; + ~freePerGroup = ~freePerGroup ?? { false }; + + cleanupClockTempo = (~cleanupClock === SystemClock).if { 1 }{ ~cleanupClock.tempo }; + zeroSynthOverlap = server.options.blockSize / server.sampleRate; + + // unifying ~fxOrder order data (with topo order etc) has been defined in + // AbstractPbindFx / miSC_getFxOrderData + + ~fxOrder = ~fxOrder.asArray; + fxOrderPositions = IdentityDictionary(); + fxOrderPositions.put(0, 0); + ~fxOrder.do { |x,i| fxOrderPositions.put(x, i+1) }; + + fxNum = ~fxOrder.size; + withFxs = ((fxNum == 1) && (~fxOrder[0] == 0)).not; + + withFxs.if { + cleanupDelaySums = [~cleanupDelay]; + baseLatency = (~latency ?? { server.latency }); + + // fxGroup: group for all synths related to the event + // srcGroup: placed at top of fxGroup + // in general this node order wil be established (index shift from code): + + // fxGroup: + // srcGroup: + // (splitZero #1 writes 0 to splitbus, in case src will write to such) + // zero #1 writes 0 to bus(es), to which src (or split of src) will write + // src writes to whatever buses + // (split #1 in case src writes to more than one bus) + // (splitZero #2 writes 0 to splitbus, in case fx #1 will write to such) + // (zero #2 writes 0 to bus(es), to which fx #1 (or split of fx #1) will write) + // fx #1 reads from buses #1, writes to whatever buses + // (split #2 in case fx #1 writes to more than one bus) + + // ... + + // (splitZero #n writes 0 to splitbus, in case fx #n-1 will write to such) + // (zero #n writes 0 to bus(es), to which fx #n-1 (or split of fx #n-1) will write) + + // fx #n-1 reads from whatever, writes to whatever + // (split #n in case fx #n-1 writes to more than one bus) + // fx #n reads from whatever, writes to out + + fxGroupId = server.nextNodeID; + srcGroupId = server.nextNodeID; + server.sendBundle(nil, + [21, fxGroupId, ~addAction ?? { 0 }, ~group.value.asNodeID], + [21, srcGroupId, 0, fxGroupId] + ); + + // Per convention \cleanupDelay of src synth indicates: + // maximum release time in case of a gated envelope + // maximum synth duration in case of a fixed length envelope. + // gateDelay adds delay times depending on env type, + // it refers to beats and thus will be added to ~timingOffset. + + ~instrument = ~instrument.asSymbol; + hasGate = SynthDescLib.global[~instrument].controlNames.includes(\gate); + gateDelay = hasGate.if { ~sustain.value }{ 0 }; + + // reserve buses with right sizes, + // this looks at fx SynthDef's in sizes + + #fxBuses, splitBuses, splitFactors, cleanupDelaySums = ~fxOrder.miSC_getFxBusData( + ~fxPredecessors, ~fxSuccessors, fxOrderPositions, + ~fxEvents, ~instrument, ~otherBusArgs, ~cleanupDelay, server + ); + + latestCleanupIndex = cleanupDelaySums.maxIndex; + // avoid cleanup ambivalence with two latest cleanups + cleanupDelaySums[latestCleanupIndex] = cleanupDelaySums[latestCleanupIndex] + 0.01; + + // startFxBundle and startZerobundle are just collected and scheduled afterwards + // freeing with freeFxBundle and freeZeroBundle is either + // (1) scheduled at once by freeing fxGroup (with option ~freePerGroup == true) or + // (2) scheduled separately (with option ~freePerGroup == false) + // (1) and (2) is done within the methods + + #startZeroBundle, startSplitBundle, startSplitZeroBundle, firstFxIds, lastFxIds, splitSynthIds = + ~fxOrder.miSC_makeStartZeroBundle( + ~fxPredecessors, ~fxSuccessors, fxOrderPositions, fxBuses, splitBuses, splitFactors, + fxNum, srcGroupId, fxGroupId, baseLatency, cleanupDelaySums, latestCleanupIndex, + gateDelay, ~out, ~freePerGroup, ~timingOffset, ~lag, zeroSynthOverlap, server + ); + + startFxBundle = ~fxOrder.miSC_makeStartFxBundle(~fxSuccessors, + fxOrderPositions, fxBuses, splitBuses, splitFactors, ~fxEvents, srcGroupId, + fxGroupId, firstFxIds, lastFxIds, baseLatency, cleanupDelaySums, latestCleanupIndex, + gateDelay, ~out, ~freePerGroup, ~timingOffset, ~lag, server + ); + + // now start bundles that have been collected + // schedule zero synths, split synths and split zero synths slightly before fxs synths + schedBundleArrayOnClock( + ~timingOffset, thisThread.clock, startZeroBundle, ~lag, server, + baseLatency - zeroSynthOverlap + ); + + schedBundleArrayOnClock( + ~timingOffset, thisThread.clock, startSplitZeroBundle, ~lag, server, + baseLatency - (zeroSynthOverlap * 0.75) + ); + + schedBundleArrayOnClock( + ~timingOffset, thisThread.clock, startSplitBundle, ~lag, server, + baseLatency - (zeroSynthOverlap * 0.5) + ); + + // schedule fx synths + schedBundleArrayOnClock( + ~timingOffset, thisThread.clock, startFxBundle, ~lag, server, baseLatency + ); + + // free buses of fx chain after summed cleanup time + + cleanupDelta = baseLatency + cleanupDelaySums.last * cleanupClockTempo; + startTime = ~cleanupClock.seconds; + + skipjack = SkipJack( + dt: ~cleanupDt * cleanupClockTempo, + clock: ~cleanupClock + ); + skipjack.stopTest = { + (skipjack.clock.seconds - startTime > cleanupDelta).if { + fxBuses.do(_.free); + splitBuses.do(_.free); + // skipjacks stopped by stopTest are not automatically removed + skipjack.miSC_cleanup; + true + }{ + false + }; + }; + + // route source synth out into fx chain + + // source synth(s), which will refer to ~group and ~addAction, + // will be placed at tail of srcGroup or before split synth (also in srcGroup), + // so anyway before fx synths + + (splitFactors[0] > 1).if { + ~out = splitBuses[0].index; + ~group = [splitSynthIds[0]]; + ~addAction = \addBefore; + + }{ + // ~out = fxBuses[0].index; + ~out = fxBuses[fxOrderPositions[~fxSuccessors[0].first] - 1].index; + ~group = [srcGroupId]; + ~addAction = \addToTail; + }; + + }; + server; + } + ) + } + + *makeZeroSynthDef { + SynthDef(\pbindFx_zero, { |out| + ReplaceOut.ar(out, DC.ar(0)) + }, [\ir]).store; + } + + // like \pbindFx_zero, but makes order more clear when doing s.queryAllNodes while running + *makeSplitZeroSynthDef { + SynthDef(\pbindFx_splitZero, { |out| + ReplaceOut.ar(out, DC.ar(0)) + }, [\ir]).store; + } + + *makeSplitSynthDef { |splitNum, channelNum| + SynthDef("pbindFx_split_" ++ splitNum ++ "x" ++ channelNum, { |in| + var out = \out.kr(0!splitNum); + Out.ar(out, In.ar(in, channelNum)) + }, [\ir]).store; + } + + *makeSplitSynthDefs { |maxSplitNum, maxChannelNum| + for(2, maxSplitNum, { |i| + for(1, maxSplitNum, { |j| + this.makeSplitSynthDef(i,j) + }) + }); + } + +} + + ++SkipJack { + miSC_cleanup { + all.remove(this); + CmdPeriod.remove(this); + if( verbose ) { ("SkipJack" + name + "stopped.").postcln }; + } +} + + ++SequenceableCollection { + miSC_collectEvalWithoutKeyPairs { |syms, ev| + var coll; + this.pairsDo { |k,v| syms.includes(k).not.if { coll = coll.addAll([k, ev.use { v.() }]) } }; + ^coll + } +} + + + + + diff --git a/Classes/Patterns/EventShortcuts.sc b/Classes/Patterns/EventShortcuts.sc new file mode 100644 index 0000000..b526efa --- /dev/null +++ b/Classes/Patterns/EventShortcuts.sc @@ -0,0 +1,263 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +EventShortcuts { + classvar prefixFunc); + } + } + } + +} + + + + diff --git a/Classes/Patterns/MemoRoutine.sc b/Classes/Patterns/MemoRoutine.sc new file mode 100644 index 0000000..93d13bc --- /dev/null +++ b/Classes/Patterns/MemoRoutine.sc @@ -0,0 +1,78 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +MemoRoutine : Stream { + + var <>routine, <>lastValues, <>count, <>copyItems, <>copySets; + + *new { |func, stackSize = (512), bufSize = 1, copyItems = 0, copySets = 1| + ^super.new.routine_(Routine(func, stackSize)).lastValues_({}!bufSize).count_(0) + .copyItems_(copyItems.miSC_getCopyCode).copySets_(copySets.miSC_getCopyCode) + } + + next { |inval| + var newLastValues, val = routine.next(inval); + newLastValues = this.lastValues.shift(1, val.miSC_copy(copyItems, copySets)); + this.lastValues = newLastValues; + count = count + 1; + ^val + } + + value { |inval| + var newLastValues, val = routine.next(inval); + newLastValues = this.lastValues.shift(1, val.miSC_copy(copyItems, copySets)); + this.lastValues = newLastValues; + count = count + 1; + ^val + } + + resume { |inval| + var newLastValues, val = routine.next(inval); + newLastValues = this.lastValues.shift(1, val.miSC_copy(copyItems, copySets)); + this.lastValues = newLastValues; + count = count + 1; + ^val + } + + run { |inval| + var newLastValues, val = routine.next(inval); + newLastValues = this.lastValues.shift(1, val.miSC_copy(copyItems, copySets)); + this.lastValues = newLastValues; + count = count + 1; + ^val + } + + bufSize { ^lastValues.size } + + at { |i| ^lastValues[i] } + + lastValue { ^lastValues[0] } + + reset { routine.reset } + + stop { routine.stop } +} + + + diff --git a/Classes/Patterns/OtherPatterns.sc b/Classes/Patterns/OtherPatterns.sc new file mode 100755 index 0000000..7d3b021 --- /dev/null +++ b/Classes/Patterns/OtherPatterns.sc @@ -0,0 +1,275 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +Pshufn : ListPattern { + embedInStream { |inval| + var item, stream; + repeats.value(inval).do { |j| + (0..(list.size-1)).scramble.do { |i| + item = list.wrapAt(i); + inval = item.embedInStream(inval); + }; + }; + ^inval; + } +} + +PlaceAll : Ppatlace { + *new { |list, repeats = 1, offset = 0| + var laceList = list.miSC_makeLaceList; + ^super.new(laceList, repeats, offset); + + } +} + + +////////////////// + + +PmonoPar : Plazy { + *new { |setPatternPairs, defname = \default, offset = 1e-6| + ^super.new({ + var g = Group(), cleanup = EventStreamCleanup.new; + Pseq([ + Pbind( + \instrument, defname, + \type, Pseq([\on]), + \dur, offset, + \group, g, + \cleanup, Pfunc { |e| cleanup.addFunction(e, { g.release }) } + ), + Ptpar(setPatternPairs.collect { |p, j| + var pairs, keys = p.select { |x,i| i.even }, keysAfterDur; + keysAfterDur = keys.copyToEnd(keys.indexOf(\dur) + 1); + #[note, degree, midinote].any { |x| keysAfterDur.includes(x) }.if { + keysAfterDur = keysAfterDur.add(\freq) + }; + pairs = [\type, \set] ++ p ++ + keys.includes(\args).not.if { [\args, keysAfterDur] } ++ + [\id, g]; + [j * offset, Pbind(*pairs)] + }.flatten), + Pbind(\instrument, defname, \type, Pseq([\off]), \dur, offset, \group, g) + ]) + }) + } +} + +PpolyPar : Plazy { + *new { |setPatternPairs, defNames = #[default], order, offset = 1e-6| + ^super.new({ + var cleanup, size = defNames.size, groups, hasSynthsForAllStreams, hasNoSynthsForAnyStream; + + cleanup = EventStreamCleanup.new; + + order = order ?? { (0..size-1) }; + groups = nil ! size; + size.do { |i| groups[order[i]] = (i != 0).if { Group(groups[order[i-1]], \addAfter) }{ Group() } }; + + hasSynthsForAllStreams = setPatternPairs.every { |x| x.includes(\synths) }; + hasNoSynthsForAnyStream = setPatternPairs.every { |x| x.includes(\synths).not }; + + (hasSynthsForAllStreams.not && (hasNoSynthsForAnyStream && (size == setPatternPairs.size)).not).if { + SimpleInitError("\\synths must be either defined for all streams or the number of streams must " ++ + "equal the number of defNames.\n" ++ "In that case each setting stream refers to the " ++ + "synth at the same position of defNames." + ).throw + }; + + Pseq( + groups.collect { |g, i| + Pbind( + \instrument, defNames[i], + \type, Pseq([\on]), + \dur, offset, + \group, g, + \cleanup, Pfunc { |e| cleanup.addFunction(e, { g.release }) } + ) + } ++ + + Ptpar(setPatternPairs.collect { |p, j| + var pairs, keys = p.select { |x,i| i.even }, keysAfterDur; + keysAfterDur = keys.copyToEnd(keys.indexOf(\dur) + 1); + #[note, degree, midinote].any { |x| keysAfterDur.includes(x) }.if { + keysAfterDur = keysAfterDur.add(\freq) + }; + pairs = hasNoSynthsForAnyStream.if { [\synths, j] }{ [] } ++ + [\type, \set] ++ p ++ + keys.includes(\args).not.if { [\args, keysAfterDur] } ++ + [\id, Pfunc { |e| groups[e[\synths].asArray] } ]; + [j * offset, Pbind(*pairs)] + }.flatten) ++ + + groups.collect { |g, i| + Pbind(\instrument, defNames[i], \type, Pseq([\off]), \dur, offset, \id, g) + } + ) + }) + } +} + + +////////////////// + + +PSloop : Pattern { + var <>psPat, <>lookBack, <>doSkip, <>loopFunc, <>loopFuncPerItem, <>loopFuncAsGoFunc, <>goFunc, <>indices; + *new { |srcPat, length = inf, bufSize = 1, lookBack = 0, doSkip = 0, + loopFunc, loopFuncPerItem = 0, loopFuncAsGoFunc = 0, goFunc, indices, copyItems = 0, copySets = 1| + ^super.new.psPat_(PStream(srcPat, length, bufSize, copyItems, copySets)) + .lookBack_(lookBack).doSkip_(doSkip).loopFunc_(loopFunc) + .loopFuncPerItem_(loopFuncPerItem).loopFuncAsGoFunc_(loopFuncAsGoFunc) + .goFunc_(goFunc).indices_(indices); + } + embedInStream { |inval| + var psStream = psPat.asStream; + var lookBackStream, lastVals, lookBackVal, oldLookBackVal, lookBackValClipped, loopLength, + doSkipVal, count, mappedIndex, indicesStream, rawVal, mappedVal, + currentIndices, currentIndexOrder, getCurrentIndexOrder, loopFuncStream, currentLoopFunc, + loopFuncPerItemVal, goFuncStream, currentGoFunc, loopFuncAsGoFuncVal; + + getCurrentIndexOrder = { |nextIndices, indexbase| + nextIndices.isKindOf(Function).if { nextIndices.(indexbase) }{ nextIndices } + }; + + lookBackStream = case + { lookBack.isKindOf(Function) }{ Pfunc(lookBack) } + { lookBack.isKindOf(Pattern) }{ lookBack }.asStream; + + indicesStream = case + { indices.isKindOf(Function) || indices.isKindOf(SequenceableCollection) }{ Pn(indices) } + { indices.isKindOf(Pattern) }{ indices }.asStream; + + loopFuncStream = case + { loopFunc.isKindOf(Function) }{ Pn(loopFunc) } + { loopFunc.isKindOf(Pattern) }{ loopFunc }.asStream; + + goFuncStream = case + { goFunc.isKindOf(Function) }{ Pn(goFunc) } + { goFunc.isKindOf(Pattern) }{ goFunc }.asStream; + + lookBackVal = lookBackStream.next; + + while { + psPat.memoRoutine.notNil.if { lastVals = psPat.lastValues }; + lookBackVal.notNil + }{ + (lookBackVal > 0).if { + count = 0; + lookBackValClipped = min(lookBackVal, psPat.bufSize - 1); + currentIndices = indicesStream.next; + currentIndexOrder = getCurrentIndexOrder.(currentIndices, lookBackValClipped); + currentLoopFunc = loopFuncStream.next; + + while { lookBackVal > 0 }{ + lookBackValClipped = min(lookBackVal, psPat.bufSize - 1); + currentIndices.notNil.if { + mappedIndex = currentIndexOrder[count].clip(0, lookBackValClipped - 1); + loopLength = currentIndexOrder.size; + }{ + mappedIndex = count.mod(lookBackValClipped); + loopLength = lookBackValClipped; + }; + // index has to be reversed as PS buffers last at first + rawVal = lastVals.at(lookBackValClipped - 1 - mappedIndex); + mappedVal = currentLoopFunc.notNil.if { currentLoopFunc.(rawVal) }{ rawVal }; + + inval = mappedVal.embedInStream(inval); + count = count + 1; + loopFuncPerItemVal = loopFuncPerItem.(); + (loopFuncPerItemVal.miSC_check || (count >= loopLength)).if { + currentLoopFunc = loopFuncStream.next + }; + doSkipVal = doSkip.(); + (doSkipVal.miSC_check || (count >= loopLength)).if { + oldLookBackVal = lookBackValClipped; + lookBackVal = lookBackStream.next; + lookBackValClipped = min(lookBackVal, psPat.bufSize - 1); + ((oldLookBackVal != lookBackValClipped) || (count >= loopLength)).if { + count = 0; + (lookBackVal != 0).if { + currentIndices = indicesStream.next; + currentIndexOrder = getCurrentIndexOrder.(currentIndices, lookBackValClipped); + } + } + } + } + }{ + loopFuncAsGoFuncVal = loopFuncAsGoFunc.(); + currentGoFunc = loopFuncAsGoFuncVal.miSC_check.if { loopFuncStream }{ goFuncStream }.next; + + rawVal = psStream.next(inval); + mappedVal = currentGoFunc.notNil.if { currentGoFunc.(rawVal) }{ rawVal }; + + inval = mappedVal.embedInStream(inval); + lookBackVal = lookBackStream.next; + } + }; + ^inval; + } +} + + +////////////////// + + + +// This adapts an idea of James Harkins' PnNilSafe (ddwPatterns quark) for Psym, +// If all patterns in the Dictinonary return nil Psym's embedInStream +// can produce an infinite loop. +// It doesn't yield, so wrapping Psym into a PnNilSafe doesn't help in that case. +// Instead a check with logical time can be built into Psym itself. + +PsymNilSafe : Psym { + + var <>maxNull; + + *new { arg pattern, dict, maxNull = 128; + ^super.new(pattern).dict_(dict ?? { currentEnvironment }).maxNull_(maxNull) + } + + embedInStream { |inval| + var str, outval, pat, counter = 0, saveLogicalTime; + str = pattern.asStream; + while { + outval = str.next(inval); + outval.notNil + } { + pat = this.getPattern(outval); + saveLogicalTime = thisThread.clock.beats; + inval = pat.embedInStream(inval); + + if(thisThread.clock.beats == saveLogicalTime) { + counter = counter + 1; + if(counter > maxNull) { ^inval }; + } { + counter = 0; + }; + + }; + ^inval + } +} + diff --git a/Classes/Patterns/PIdev.sc b/Classes/Patterns/PIdev.sc new file mode 100644 index 0000000..79586f3 --- /dev/null +++ b/Classes/Patterns/PIdev.sc @@ -0,0 +1,92 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +PIdev : Pattern { + + var <>pattern, <>maxLookBack = 3, <>loDev = -3, <>hiDev = 3, + <>lookBack, <>thr = 1e-3, <>length = inf; + + *new { |pattern, maxLookBack = 3, loDev = -3, hiDev = 3, + lookBack, thr = 1e-3, length = inf| + ^super.newCopyArgs(pattern, maxLookBack, loDev, hiDev, + lookBack, thr, length) + } + + embedInStream { arg inval; + var outval, srcVal, lookBackVal, loDevVal, hiDevVal, thrVal, count = 0, + difs, devs, probs, add; + var buf = inf ! (maxLookBack + 1); + var srcStr = pattern.asStream; + var lookBackStr = lookBack.asStream; + var loDevStr = loDev.asStream; + var hiDevStr = hiDev.asStream; + var thrStr = thr.asStream; + + length.value(inval).do { + srcVal = srcStr.next(inval); + if(srcVal.isNil) { ^inval }; + + lookBackVal = lookBackStr.next(inval) ?? { maxLookBack }; + loDevVal = loDevStr.next(inval); + hiDevVal = hiDevStr.next(inval); + thrVal = thrStr.next(inval); + + // this looks a bit different than in DIdev + + devs = (loDevVal.ceil..hiDevVal.floor); + + probs = devs.collect { |dev| + var j = 0, prob = 1, dif; + while { j < lookBackVal }{ + dif = buf[count - j - 1 % (maxLookBack + 1)] - srcVal - dev; + (dif.abs <= thrVal).if { j = lookBackVal; prob = 0 }; + j = j + 1 + }; + prob + }; + + add = devs.wchoose(probs.normalizeSum); + + outval = srcVal + add; + buf[count % (maxLookBack + 1)] = outval; + + count = count + 1; + inval = outval.yield; + }; + ^inval; + } + + storeArgs { ^[pattern, maxLookBack, loDev, hiDev, lookBack, thr, length] } +} + + +PLIdev : PIdev { + var <>envir; + *new { |pattern, maxLookBack = 3, loDev = -3, hiDev = 3, lookBack, thr = 1e-3, length = inf, envir = \current| + ^super.new(PL(pattern, 1, inf, envir), maxLookBack, PL(loDev, 1, inf, envir), PL(hiDev, 1, inf, envir), + PL(lookBack, 1, inf, envir), PL(thr, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[pattern, maxLookBack, loDev, hiDev, lookBack, thr, length, envir] } +} diff --git a/Classes/Patterns/PL.sc b/Classes/Patterns/PL.sc new file mode 100755 index 0000000..a828ad5 --- /dev/null +++ b/Classes/Patterns/PL.sc @@ -0,0 +1,757 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +PL_Pattern : Pattern { + var <>envir; + *new { |envir = \current| ^super.new.envir_(envir) } + + storeArgs { ^[envir] } + + data { ^this.envir.miSC_getEnvir[this.item] } +} + +PL_ListPattern : PL_Pattern { + var <>list, <>repeats = inf, <>cutItems; + *new { |list, repeats = inf, cutItems = true, envir = \current| + ^super.newCopyArgs(envir, list, repeats, cutItems) + } + storeArgs { ^[list, repeats, cutItems, envir] } +} + + +// "stream-save" Pn + +PL_Pn : PL_Pattern { + var <>item, <>repeats; + *new { |item, repeats = inf, envir = \current| + ^super.newCopyArgs(envir, item, repeats) + } + embedInStream { |inval| + var next; + item.isKindOf(Stream).if { + repeats.value(inval).do { + while { + next = item.next(inval); + next.notNil; + }{ + inval = next.yield; + }; + item.reset + }; + }{ + repeats.value(inval).do { inval = item.embedInStream(inval) } + }; + ^inval; + } + + storeArgs { ^[item, repeats, envir] } +} + + + +// PL_PproxyNth: Pattern for embedding the nth element of a PL_ListPattern + +// If nth element is updated just at the time a pattern or item at that position is being embedded, +// then it is replaced ad hoc if flag cutItems equals 1 or true, otherwise it might be embedded at next occasion. + + +PL_PproxyNth : PL_Pattern { + var <>listProxy, <>index, <>type, <>doWrap, <>cutItems; + *new { |listProxy, index, type = 1, doWrap = true, cutItems = true, envir = \current| + // type: repeats factor for objects other than Patterns and Streams to be embedded + ^super.newCopyArgs(envir, listProxy, index, type, doWrap, cutItems) + } + + nonListError { |src| + Error("PL_ListPattern requires list or reference to a " ++ + "non-empty collection; received " ++ src.asString ++ ".").throw; + } + + embedInStream { |inval| + var stream, next, src, rep, count = 0; + + listProxy.listHasChanged.not.if { + src = listProxy.source(index, doWrap); + rep = src.miSC_repTypeFactor(type); + stream = PL_Pn(src, rep, envir).asStream; + while { + listProxy.update(index); + // must check next value as we don't want replacement if stream is at end + next = listProxy.listHasChanged.not.if { + stream.next(inval) + }; + // possible replacement of nth item: + // third condition with count is necessary as otherwise a former replacement + // wouldn't be detected with cutItems == false + (listProxy.itemHasChanged && next.notNil && (cutItems.miSC_check || (count == 0))).if { + src = listProxy.source(index, doWrap); + rep = src.miSC_repTypeFactor(type); + stream = PL_Pn(src, rep, envir).asStream; + next = listProxy.listHasChanged.not.if { stream.next(inval) }; + }; + + next.isNil.if { listProxy.itemHasChanged = false; false }{ true }; + }{ + count = count + 1; + inval = next.yield; + }; + }; + ^inval + } + + storeArgs { ^[listProxy, index, type, doWrap, cutItems, envir] } +} + + + +PL_Pproxy : PL_Pattern { + var <>itemProxy, <>type, <>repeats; + *new { |itemProxy, type = inf, repeats = 1, envir = \current| + // type = 1 or inf, defines how to embed + // items other than Patterns or Streams + ^super.newCopyArgs(envir, itemProxy, type, repeats) + } + embedInStream { |inval| + var stream, next, src, repTypeFactor; + src = itemProxy.source; + repTypeFactor = src.miSC_repTypeFactor(type); + stream = PL_Pn(src, repTypeFactor * repeats, envir).asStream; + while { + itemProxy.update; + itemProxy.itemHasChanged.if { + src = itemProxy.source; + repTypeFactor = src.miSC_repTypeFactor(type); + stream = PL_Pn(src, repTypeFactor * repeats, envir).asStream; + itemProxy.itemHasChanged = false; + }; + next = stream.next(inval); + next.isNil.if { ^inval }{ true }; + }{ + inval = next.yield; + }; + ^inval + } + + storeArgs { ^[itemProxy, type, repeats, envir] } +} + + +// PL_ includes option for embed type +// type = 1 or inf, defines how to embed +// items other than Patterns or Streams + +PL : PL_Pattern { + var <>item, <>repeats, <>type; + *new { |item, repeats = inf, type = 1, envir = \current| + ^super.newCopyArgs(envir, item, repeats, type) + } + + embedInStream { |inval| + var itemProxy, repeatsProxy, stream, next; + itemProxy = PL_Proxy(item, envir); + repeatsProxy = PL_Proxy(repeats, envir); + stream = PL_Pproxy(itemProxy, type, repeatsProxy.source.value(inval), envir).asStream; + while { + next = stream.next(inval); + next.isNil.if { ^inval }{ true }; + }{ + inval = next.yield; + }; + ^inval + } + + storeArgs { ^[item, repeats, type, envir] } +} + + +// The following implementations are based on the corresponding classes in the main library +// Thanks to all authors, especially J.Harkins + + +PLseq : PL_ListPattern { + var <>offset; + *new { |list, repeats = inf, offset = 0, cutItems = true, envir = \current| + ^super.new(list, repeats, cutItems, envir).offset_(offset); + } + embedInStream { |inval| + var item, listProxy, offsetProxy, repeatsProxy, cutItemsProxy, ref = `true; + listProxy = PL_ListProxy(list, envir).update; + // avoid an infinite loop case + listProxy.streamSources.isNil.if { ^inval }; + + offsetProxy = PL_Proxy(offset, envir); + repeatsProxy = PL_Proxy(repeats, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + (inval.eventAt('reverse') == true).if { + repeatsProxy.source.value(inval).do { |j| + listProxy.reverseDo ({ |x,i| + inval = PL_PproxyNth(listProxy, i + offsetProxy.value, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + listProxy.listHasChanged.if { + ref.value = false; + listProxy.listHasChanged = false; + }; + }, ref.value = true); + }; + }{ + repeatsProxy.source.value(inval).do { |j| + listProxy.do ({ |x, i| + item = PL_PproxyNth(listProxy, i + offsetProxy.value, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + inval = item; + listProxy.listHasChanged.if { + ref.value = false; + listProxy.listHasChanged = false; + }; + }, ref.value = true); + }; + }; + ^inval; + } + storeArgs { ^[list, repeats, offset, cutItems, envir] } +} + + + +PLser : PLseq { + + embedInStream { |inval| + var item, listProxy, offsetProxy, repeatsProxy, cutItemsProxy, j = 0; + + listProxy = PL_ListProxy(list, envir).update; + offsetProxy = PL_Proxy(offset, envir); + repeatsProxy = PL_Proxy(repeats, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + (inval.eventAt('reverse') == true).if { + listProxy.reverseDoN ({ |x,i| + inval = PL_PproxyNth(listProxy, i - j + offsetProxy.value, + cutItems: cutItemsProxy.value, envir: envir) + .embedInStream(inval); + listProxy.listHasChanged.if { + listProxy.listHasChanged = false; + j = i; + }; + }, repeatsProxy.source.value(inval)); + }{ + listProxy.doN ({ |x,i| + inval = PL_PproxyNth(listProxy, i - j + offsetProxy.value, + cutItems: cutItemsProxy.value, envir: envir) + .embedInStream(inval); + listProxy.listHasChanged.if { + listProxy.listHasChanged = false; + j = i; + }; + }, repeatsProxy.source.value(inval)); + }; + ^inval; + } +} + + +PLshuf : PL_ListPattern { + *new { |list, repeats = inf, cutItems = true, envir = \current| + ^super.new(list, repeats, cutItems, envir) + } + embedInStream { |inval| + var item, stream, order, listProxy, repeatsProxy, cutItemsProxy; + listProxy = PL_ListProxy(list, envir).update; + repeatsProxy = PL_Proxy(repeats, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + order = (0..(listProxy.listSource.size-1)).scramble; + + repeatsProxy.source.value(inval).do { |j| + listProxy.listHasChanged.if { + order = (0..(listProxy.listSource.size-1)).scramble; + listProxy.listHasChanged = false; + }; + order.do { |i| + inval = PL_PproxyNth(listProxy, i, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + }; + }; + ^inval; + } + + storeArgs { ^[list, repeats, cutItems, envir] } +} + + +PLshufn : PLshuf { + + embedInStream { |inval| + var item, stream, order, listProxy, repeatsProxy, cutItemsProxy; + + listProxy = PL_ListProxy(list, envir).update; + repeatsProxy = PL_Proxy(repeats, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + repeatsProxy.source.value(inval).do { |j| + order = (0..(listProxy.listSource.size-1)).scramble; + order.do { |i| + inval = PL_PproxyNth(listProxy, i, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + listProxy.listHasChanged = false; + }; + }; + ^inval; + } +} + +PLrand : PL_ListPattern { + *new { |list, repeats = inf, cutItems = true, envir = \current| + ^super.new(list, repeats, cutItems, envir) + } + + embedInStream { |inval| + var index, listProxy, repeatsProxy, cutItemsProxy; + + listProxy = PL_ListProxy(list, envir).update; + repeatsProxy = PL_Proxy(repeats, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + repeatsProxy.source.value(inval).do { |i| + index = listProxy.size.rand; + inval = PL_PproxyNth(listProxy, index, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + listProxy.listHasChanged = false; + }; + ^inval; + } +} + + +PLxrand : PL_ListPattern { + *new { |list, repeats = inf, cutItems = true, envir = \current| + ^super.new(list, repeats, cutItems, envir) + } + + embedInStream { |inval| + var item, size, index, listProxy, repeatsProxy, cutItemsProxy; + + listProxy = PL_ListProxy(list, envir).update; + repeatsProxy = PL_Proxy(repeats, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + index = listProxy.size.rand; + repeatsProxy.source.value(inval).do { |i| + size = listProxy.size; + index = (index + (size - 1).rand + 1) % size; + inval = PL_PproxyNth(listProxy, index, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + listProxy.listHasChanged = false; + }; + ^inval; + } +} + + +PLwrand : PL_ListPattern { + var <>weights; + *new { |list, weights, repeats = inf, cutItems = true, envir = \current| + ^super.new(list, repeats, cutItems, envir).weights_(weights) + } + + embedInStream { |inval| + var item, weightsStream, nextWeight, listProxy, weightsProxy, repeatsProxy, cutItemsProxy; + + listProxy = PL_ListProxy(list, envir).update; + repeatsProxy = PL_Proxy(repeats, envir); + weightsProxy = PL_Proxy(weights, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + weightsStream = PL_Pproxy(weightsProxy, envir: envir).asStream; + + repeatsProxy.source.value(inval).do { |i| + nextWeight = weightsStream.next; + nextWeight.isNil.if { ^inval }; + inval = PL_PproxyNth(listProxy, nextWeight.windex, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + listProxy.listHasChanged = false; + }; + ^inval + } + + storeArgs { ^[list, weights, repeats, cutItems, envir] } +} + +PLswitch : PL_Pattern { + var <>list, <>which = 0, <>cutItems; + *new { |list, which = 0, cutItems = true, envir = \current| + ^super.newCopyArgs(envir, list, which, cutItems) + } + embedInStream { |inval| + var item, index, listProxy, indexProxy, indexStream, cutItemsProxy; + listProxy = PL_ListProxy(list, envir).update; + indexProxy = PL_Proxy(which, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + indexStream = PL_Pproxy(indexProxy, envir: envir).asStream; + + while { + (index = indexStream.next(inval)).notNil; + }{ + inval = PL_PproxyNth(listProxy, index, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + listProxy.listHasChanged = false; + }; + ^inval; + } + storeArgs { ^[list, which, cutItems, envir] } +} + + +PLswitch1 : PLswitch { + + embedInStream { |inval| + var index, outval, indexProxy, listProxy, streamList, indexStream, cutItemsProxy; + + listProxy = PL_ListProxy(list, envir).update; + indexProxy = PL_Proxy(which, envir); + indexStream = PL_Pproxy(indexProxy, envir: envir).asStream; + cutItemsProxy = PL_Proxy(cutItems, envir); + + streamList = listProxy.size.collect { |i| + PL_PproxyNth(listProxy, i, inf, cutItems: cutItems, envir: envir).asStream + }; + + loop { + listProxy.update.listHasChanged.if { + streamList = listProxy.size.collect { |i| + PL_PproxyNth(listProxy, i, inf, + cutItems: cutItemsProxy.value, envir: envir).asStream + }; + listProxy.listHasChanged = false; + }; + (index = indexStream.next).isNil.if { ^inval }; + outval = streamList[index].next(inval); + outval.isNil.if { ^inval }; + inval = outval.yield; + }; + } +} + + +PLtuple : PL_ListPattern { + + embedInStream { |inval| + var item, streamList, tuple, outval, listProxy, repeatsProxy, cutItemsProxy; + listProxy = PL_ListProxy(list, envir).update; + repeatsProxy = PL_Proxy(repeats, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + repeatsProxy.source.value(inval).do { |j| + var sawNil = false; + streamList = listProxy.size.collect { |i| + PL_PproxyNth(listProxy, i, inf, + cutItems: cutItemsProxy.value, envir: envir).asStream + }; + + while { + listProxy.update.listHasChanged.if { + streamList = listProxy.size.collect { |i| + PL_PproxyNth(listProxy, i, inf, + cutItems: cutItemsProxy.value, envir: envir).asStream + }; + listProxy.listHasChanged = false; + }; + tuple = Array.new(streamList.size); + streamList.do { |stream, j| + outval = stream.next(inval); + outval.isNil.if { sawNil = true; }; + tuple.add(outval); + }; + sawNil.not + }{ + inval = yield(tuple); + }; + }; + ^inval; + } +} + + +PLslide : PL_ListPattern { + var <>len, <>step, <>start, <>wrapAtEnd; + *new { |list, repeats = inf, len = 3, step = 1, start = 0, wrapAtEnd = true, + cutItems = true, envir = \current| + ^super.new(list, repeats).len_(len).step_(step) + .start_(start).wrapAtEnd_(wrapAtEnd).cutItems_(cutItems).envir_(envir); + } + embedInStream { |inval| + var item, pos, stepStream, stepVal, lengthStream, lengthVal, next, stream, + listProxy, repeatsProxy, lengthProxy, stepProxy, + startProxy, wrapAtEndProxy, cutItemsProxy; + + listProxy = PL_ListProxy(list, envir).update; + repeatsProxy = PL_Proxy(repeats, envir); + lengthProxy = PL_Proxy(len, envir); + stepProxy = PL_Proxy(step, envir); + startProxy = PL_Proxy(start, envir); + wrapAtEndProxy = PL_Proxy(wrapAtEnd, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + stepStream = PL_Pproxy(stepProxy, envir: envir).asStream; + lengthStream = PL_Pproxy(lengthProxy, envir: envir).asStream; + pos = startProxy.source.value; + + repeatsProxy.source.value(inval).do { + lengthVal = lengthStream.next(inval); + lengthVal.isNil.if { ^inval }; + wrapAtEndProxy.source.value.if { + lengthVal.do { |j| + item = PL_PproxyNth(listProxy, pos + j, + cutItems: cutItemsProxy.value, envir: envir); + inval = item.embedInStream(inval); + listProxy.listHasChanged = false; + } + }{ + lengthVal.do { |j| + listProxy.source(pos + j, false).isNil; + stream = PL_PproxyNth(listProxy, pos + j, doWrap: false, + cutItems: cutItemsProxy.value, envir: envir).asStream; + while { + next = stream.next; + listProxy.source(pos + j, false).isNil.if { ^inval }; + next.notNil + }{ + inval = next.embedInStream(inval); + } + }; + }; + stepVal = stepStream.next(inval); + stepVal.isNil.if { ^inval }; + pos = pos + stepVal; + }; + ^inval; + } + + storeArgs { ^[list, repeats, len, step, start, wrapAtEnd, cutItems, envir] } +} + + +PLwalk : PL_ListPattern { + + var <>start, <>step, <>direction; + *new { |list, step, direction = 1, start = 0, cutItems = true, envir = \current| + ^super.new(list).start_(start) + .step_(step ?? { Prand([-1, 1], inf) }) + .direction_(direction).cutItems_(cutItems).envir_(envir); + } + + embedInStream { |inval| + var step, index, stepStream, directionStream, direction, + listProxy, stepProxy, directionProxy, startProxy, cutItemsProxy; + + listProxy = PL_ListProxy(list, envir).update; + stepProxy = PL_Proxy(this.step, envir); + directionProxy = PL_Proxy(this.direction, envir); + startProxy = PL_Proxy(start, envir); + cutItemsProxy = PL_Proxy(cutItems, envir); + + stepStream = PL_Pproxy(stepProxy, envir: envir).asStream; + directionStream = PL_Pproxy(directionProxy, envir: envir).asStream; + + index = startProxy.source.value; + direction = directionStream.next(inval) ? 1; + + while { + step = stepStream.next(inval); + step.notNil + }{ + inval = PL_PproxyNth(listProxy, index, + cutItems: cutItemsProxy.value, envir: envir).embedInStream(inval); + listProxy.listHasChanged = false; + + step = step * direction; + (((index + step) < 0) or: { (index + step) >= listProxy.size }).if { + direction = directionStream.next(inval) ? 1; + step = step.abs * direction.sign; + }; + index = (index + step) % listProxy.size; + }; + ^inval; + } + + storeArgs { ^[list, step, direction, start, cutItems, envir] } +} + +////////////////////// + +PLwhite : Pwhite { + var <>envir; + *new { |lo = 0.0, hi = 1.0, length = inf, envir = \current| + ^super.new(PL(lo, 1, inf, envir), PL(hi, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[lo, hi, length, envir] } +} + + +PLlprand : Plprand { + var <>envir; + *new { |lo = 0.0, hi = 1.0, length = inf, envir = \current| + ^super.new(PL(lo, 1, inf, envir), PL(hi, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[lo, hi, length, envir] } +} + + +PLhprand : Phprand { + var <>envir; + *new { |lo = 0.0, hi = 1.0, length = inf, envir = \current| + ^super.new(PL(lo, 1, inf, envir), PL(hi, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[lo, hi, length, envir] } +} + +PLmeanrand : Pmeanrand { + var <>envir; + *new { |lo = 0.0, hi = 1.0, length = inf, envir = \current| + ^super.new(PL(lo, 1, inf, envir), PL(hi, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[lo, hi, length, envir] } +} + + +PLbrown : Pbrown { + var <>envir; + *new { |lo = 0.0, hi = 1.0, step = 0.125, length = inf, envir = \current| + ^super.new(PL(lo, 1, inf, envir), PL(hi, 1, inf, envir), PL(step, 1, inf, envir), + PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[lo, hi, step, length, envir] } +} + + +PLgbrown : Pgbrown { + var <>envir; + *new { |lo = 0.0, hi = 1.0, step = 0.125, length = inf, envir = \current| + ^super.new(PL(lo, 1, inf, envir), PL(hi, 1, inf, envir), PL(step, 1, inf, envir), + PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[lo, hi, step, length, envir] } +} + +////////////////////// + +PLseries : Pseries { + var <>envir; + *new { |start = 0, step = 1, length = inf, envir = \current| + ^super.new(PL(start, 1, inf, envir).asStream, PL(step, 1, inf, envir), + PL(length, length, inf, envir).asStream).envir_(envir) + } + storeArgs { ^[start, step, length, envir] } +} + + +PLgeom : Pgeom { + var <>envir; + *new { |start = 0, grow = 1, length = inf, envir = \current| + ^super.new(PL(start, 1, inf, envir).asStream, PL(grow, 1, inf, envir), + PL(length, length, inf, envir).asStream).envir_(envir) + } + storeArgs { ^[start, grow, length, envir] } +} + +////////////////////// + + +PLbeta : Pbeta { + var <>envir; + *new { |lo = 0.0, hi = 1.0, prob1 = 1, prob2 = 1, length = inf, envir = \current| + ^super.new(PL(lo, 1, inf, envir), PL(hi, 1, inf, envir), PL(prob1, 1, inf, envir), + PL(prob2, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[lo, hi, prob1, prob2, length, envir] } +} + +PLcauchy : Pcauchy { + var <>envir; + *new { |mean = 0.0, spread = 1.0, length = inf, envir = \current| + ^super.new(PL(mean, 1, inf, envir), PL(spread, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[mean, spread, length, envir] } +} + +PLgauss : Pgauss { + var <>envir; + *new { |mean = 0.0, dev = 1, length = inf, envir = \current| + ^super.new(PL(mean, 1, inf, envir), PL(dev, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[mean, dev, length, envir] } +} + + +PLpoisson : Ppoisson { + var <>envir; + *new { |mean = 1, length = inf, envir = \current| + ^super.new(PL(mean, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[mean, length, envir] } +} + +PLexprand : Pexprand { + var <>envir; + *new { |lo = 0.0001, hi = 1.0, length = inf, envir = \current| + ^super.new(PL(lo, 1, inf, envir), PL(hi, 1, inf, envir), PL(length, length, 1, envir)).envir_(envir) + } + storeArgs { ^[lo, hi, length, envir] } +} + +////////////////////// + +PLnaryop : Pnaryop { + var <>envir; + *new { |operator, pat, arglist, envir = \current| + ^super.new(operator, PL(pat, 1, inf, envir), arglist.collect(PL(_, 1, inf, envir)) ).envir_(envir) + } +} + +PLnaryFunc : Pnaryop { + var <>envir; + *new { |func, pat, arglist, envir = \current| + ^super.new(\miSC_applyNaryFuncProxy, + PL(pat, 1, inf, envir), [PL(func, 1, inf, envir)] ++ arglist.collect(PL(_, 1, inf, envir)) ).envir_(envir) + } +} + + +////////////////////// + +PLn : Pn { + var <>item, <>repeats, <>envir; + *new { |item, repeats = inf, envir = \current| + ^super.newCopyArgs( + Plazy({ envir.miSC_getEnvir[item] }), + PL(repeats, repeats, 1, envir) + ) + } + storeArgs { ^[item, repeats, envir] } +} + + + diff --git a/Classes/Patterns/PL_ListProxy.sc b/Classes/Patterns/PL_ListProxy.sc new file mode 100644 index 0000000..44ff8f3 --- /dev/null +++ b/Classes/Patterns/PL_ListProxy.sc @@ -0,0 +1,176 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +PL_ListProxy { + var <>symbol, <>listSource, <>streamSources, <>index, + <>itemHasChanged, <>listHasChanged, <>wrongList, <>envir; + + *new { |thing, envir = \current| + ^(thing.isKindOf(Symbol)).if { + super.new.symbol_(thing).envir_(envir.miSC_getEnvir) + }{ + thing.isKindOf(SequenceableCollection).not.if { + SimpleInputError("List input of PL_ListPattern must be SequenceableCollection.").throw; + }{ + super.new.listSource_(thing).streamSources_(thing) + } + }; + } + + + get { ^(symbol.isNil).if { listSource }{ envir[symbol] } } + + update { |i| + var currentList, j, k; + symbol.notNil.if { + currentList = this.get; + currentList.isKindOf(SequenceableCollection).not.if { + (currentList !== wrongList).if { + ("Embedding of PL_ListPattern encountered reference ('" ++ + symbol.asString ++ "') to an item not kind of a SequenceableCollection, " ++ + "reference ignored.").warn; + wrongList = currentList + } + }{ + (currentList !== listSource).if { + listHasChanged = index.notNil; + listSource = currentList; + streamSources = listSource.copy; + }{ + i.isNil.not.if { + j = i % currentList.size; + k = i % streamSources.size; + (currentList[j] !== streamSources[k]).if { + itemHasChanged = true; + streamSources[k] = currentList[j]; + }{ + itemHasChanged = false; + } + } + }; + } + }{ + index.isNil.if { listHasChanged = false; itemHasChanged = false } + }; + index = i; + ^this; + } + + do { |function, ref = (`true)| + var i = 0, size = this.streamSources.size; + while { + (i < size) and: { ref.() } + } { + function.value(this.source(i), i); + i = i + 1; + } + } + + doN { |function, n, ref = (`true)| + var i = 0; + while { + (i < n) and: { ref.() } + } { + function.value(this.source(i), i); + i = i + 1; + } + } + + reverseDo { |function, ref = (`true)| + var size = this.streamSources.size, i, j = 0; + i = size - 1; + while { + (i >= 0) and: { ref.() } + } { + function.value(this.source(i), j); + i = i - 1; + j = j + 1; + } + } + + reverseDoN { |function, n, ref = (`true)| + var size = this.streamSources.size, i, j = 0; + i = size - 1; + while { + (j < n) and: { ref.() } + } { + function.value(this.source(i), j); + i = i - 1; + j = j + 1; + } + } + + + size { ^this.listSource.size } + + value { |i| ^streamSources.wrapAt(i).value } + + source { |i, doWrap = true| + (streamSources.size > 0).if { + ^streamSources.perform(doWrap.if { \wrapAt }{ \at }, i) + }{ + Error("PL_ListPattern requires list or reference to a " ++ + "non-empty collection; received " ++ streamSources ++ ".").throw; + } + } + +} + + +PL_Proxy { + var <>symbol, <>streamSource, <>itemHasChanged, <>envir; + + *new { |thing, envir = \current, exclude = #[]| + ^((thing.isKindOf(Symbol)) && (exclude.includes(thing).not)).if { + super.new.symbol_(thing).envir_(envir.miSC_getEnvir) + }{ + super.new.streamSource_(thing) + } + } + + get { ^(symbol.isNil).if { streamSource }{ envir[symbol] } } + + update { + var currentItem; + symbol.notNil.if { + currentItem = this.get; + itemHasChanged = (currentItem !== streamSource).if { + streamSource = currentItem; + true; + }{ + false + }; + }{ + itemHasChanged = false; + }; + ^this + } + + value { this.update; ^streamSource.value } + + source { this.update; ^streamSource } +} + + + diff --git a/Classes/Patterns/PLbindef.sc b/Classes/Patterns/PLbindef.sc new file mode 100755 index 0000000..9cba15f --- /dev/null +++ b/Classes/Patterns/PLbindef.sc @@ -0,0 +1,132 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// Instances of this class are constructed implicitely and used for updating the PLbindef + +PLbindefEnvironment : Environment { + + var plbindefParEnvironment, <>plbindefArray; + + *new { |n = 8, proto, parent, know = true| + ^super.new(n).proto_(proto).parent_(parent).know_(know) + } + + put { |key, obj| + plbindefArray.do { |i, j| + plbindefParEnvironment[i].put(key, obj.miSC_maybeWrapAt(j)) + } + } + + at { |key| + ^(key.asString.last == $_).if { + // for subarray prototype setting we need normal 'at' + this.superPerform(\at, key) + }{ + // for subarray prototype getting we pull from PLbindefParEnvironments + plbindefArray.collect { |i| plbindefParEnvironment[i][key] } + } + } + + play { |clock, protoEvent, quant, doReset = false| + plbindefArray.do { |i, j| + plbindefParEnvironment[i].play( + clock.miSC_maybeWrapAt(j), + protoEvent.miSC_maybeWrapAt(j), + quant.miSC_maybeWrapAt(j), + doReset.miSC_maybeWrapAt(j) + ); + } + } + + reset { plbindefArray.do { |i| plbindefParEnvironment[i].reset } } + stop { plbindefArray.do { |i| plbindefParEnvironment[i].stop } } + clear { plbindefArray.do { |i| plbindefParEnvironment[i].clear } } +} + diff --git a/Classes/Patterns/PS.sc b/Classes/Patterns/PS.sc new file mode 100755 index 0000000..6282f4a --- /dev/null +++ b/Classes/Patterns/PS.sc @@ -0,0 +1,141 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// It might look odd that copying is triggered twice in PSx patterns, but - +// especially important with event patterns: copying within the Pfunc function ensures +// that buffered items of the *source* are not altered and second copying +// (delegated to the MemoRoutine) ensures that items buffered in the PSx are not +// altered. It would of be possible to differentiate by defining two further +// copy flags. + + +PStream : Plazy { + var <>srcPat, <>length, <>lengthStream, <>bufSize, <>copyItems, <>copySets, <>memoRoutine; + + *new { |srcPat, length = inf, bufSize = 1, copyItems = 0, copySets = 1| + ^super.new.srcPat_(srcPat).length_(length).bufSize_(bufSize) + .copyItems_(copyItems).copySets_(copySets) + } + storeArgs { ^[srcPat, length, bufSize, copyItems, copySets] } + + embedInStream { | inval| + memoRoutine.isNil.if { + lengthStream = length.asStream; + // the MemoRoutine shouldn't be instantiated before streamification (allows dynamic scoping) + // new streams should refer to the same MemoRoutine + memoRoutine = MemoRoutine( + { |inval| srcPat.embedInStream(inval) }, + bufSize: bufSize, + copyItems: copyItems, + copySets: copySets + ) + }; + func = { |inval| + Pfinval(lengthStream.next(inval), Pfunc { memoRoutine.next(inval).miSC_copy(copyItems, copySets) }) + }; + ^func.value(inval).embedInStream(inval) + } + + at { |i| ^memoRoutine.lastValues[i] } + + lastValues { ^memoRoutine.lastValues } + + lastValue { ^memoRoutine.lastValues[0] } + + count { ^memoRoutine.count } + + bufSeq { |dropNils = true| + var seq = memoRoutine.lastValues.reverse; + ^dropNils.if { seq.reject(_.isNil) }{ seq } + } + +} + + +PS : PStream {} + +Pstream : PStream {} + +PSdup : PStream { + var <>psxPat; + + *new { |psxPat, length = inf, bufSize = 1, copyItems = 0, copySets = 1| + ^super.new( + psxPat.isKindOf(PStream).not.if { SimpleInitError("PSdup requires PSx source pattern").throw }; + Pfunc { psxPat.lastValue.miSC_copy(copyItems, copySets) }, length, bufSize, copyItems, copySets + ).psxPat_(psxPat); + } + storeArgs { ^[psxPat, length, bufSize, copyItems, copySets] } +} + + +PSrecur : PStream { + var <>start, <>recurFunc, <>recurFuncStream; + *new { |recurFunc, length = inf, bufSize, start, copyItems = 0, copySets = 1| + + // adapt bufSize if not given + bufSize = bufSize ?? { + (start.isNil || (start.isKindOf(SequenceableCollection).not)).if { 1 }{ start.size }; + }; + // make start an array + (start.notNil && (start.isKindOf(SequenceableCollection).not)).if { start = [start] }; + ^super.new(nil, length, bufSize, copyItems, copySets).start_(start).recurFunc_(recurFunc) + } + storeArgs { ^[recurFunc, length, bufSize, start, copyItems, copySets] } + + embedInStream { | inval| + var replaceSize; + // the MemoRoutine shouldn't be instantiated before streamification (allows dynamic scoping) + // new streams should refer to the same MemoRoutine + memoRoutine.isNil.if { + lengthStream = length.asStream; + recurFuncStream = recurFunc.asStream; + + memoRoutine = MemoRoutine({ |inval| + Pfunc { + recurFuncStream.next.(memoRoutine.lastValues, memoRoutine.count).miSC_copy(copyItems, copySets) + }.embedInStream(inval) + }, + bufSize: bufSize, + copyItems: copyItems, + copySets: copySets + ); + start.notNil.if { + replaceSize = min(start.size, memoRoutine.lastValues.size); + replaceSize.do { |i| memoRoutine.lastValues[i] = start[start.size - 1 - i] }; + }; + }; + + func = { |inval| + Pfinval(lengthStream.next(inval), Pfunc { memoRoutine.next(inval) }) + }; + ^func.value(inval).embedInStream(inval) + } + +} + + + + diff --git a/Classes/Patterns/PSPdiv.sc b/Classes/Patterns/PSPdiv.sc new file mode 100755 index 0000000..9cbfe48 --- /dev/null +++ b/Classes/Patterns/PSPdiv.sc @@ -0,0 +1,210 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +PSPdiv : Pspawner { + classvar <>shiftDelta = 1e-8; + var <>pulse, <>evPat, <>div, <>divBase, <>divType; + + *new { |pulse, evPat, div = 1, divBase = 1, divType = \seq| + ^super.newCopyArgs.pspDivInit(pulse, evPat, div, divBase, divType) + } + + pspDivInit { |pulse, evPat, div, divBase, divType| + var divPS, divBasePS, divTypePS, sizes, maxSize; + + pulse.isKindOf(Number).if { pulse = Pn(pulse) }; + evPat = evPat.asArray; + div = div.asArray; + divBase = divBase.asArray; + divType = divType.asArray; + // div, divBase and divTape: size must be equal and equal 1 or evPat.size + + sizes = [evPat, div, divBase, divType].collect(_.size).asSet; + maxSize = sizes.maxItem; + + ((sizes.size > 2) or: { (sizes.size == 2) && (sizes.includes(1).not) }).if { + SimpleInitError("sizes of evPat, div, divBase, divType must equal 1 or n > 1").throw; + }; + + routineFunc = { |sp| + var pulseStr = pulse.asStream; + var evPats = evPat.miSC_unifySize(maxSize) + .collect { |p| p.isKindOf(Pattern).if { p.asStream }{ p } }; + var divStrs = div.miSC_unifySize(maxSize).collect { |x| x.asStream }; + var divBaseStrs = divBase.miSC_unifySize(maxSize).collect(_.asStream); + var divTypeStrs = divType.miSC_unifySize(maxSize).collect(_.asStream); + var pulseCount = 0; + var queueCount = 0; + // queue orders queueCount + var queue = PriorityQueue.new; + var nextPulseDurs = []; + var nextDiv, nextDivBase, nextDivType, s, + nextDivs, nextDivBases, nextDivTypes, + currentIndices, nextDivBaseDur, maxDivBase, + nextEvPatDurs, nextQueueStep, nextPulseStep, + nextPulseDurSum, nextPulseDurSums, suspendIndices; + + (0..evPats.size-1).do { |i| queue.put(0, i) }; + + block { |break| loop { + s = 0; + nextDiv = nil; + nextDivBase = nil; + nextDivType = nil; + nextDivs = nil; + nextDivBases = nil; + nextDivTypes = nil; + currentIndices = nil; + nextDivBaseDur = nil; + maxDivBase = nil; + nextEvPatDurs = nil; + nextQueueStep = nil; + nextPulseStep = nil; + nextPulseDurSum = nil; + nextPulseDurSums = nil; + suspendIndices = nil; + + while { queueCount == queue.topPriority }{ + currentIndices = currentIndices.asArray ++ (queue.pop); + }; + + (currentIndices.size != 0).if { + currentIndices.sort; + + maxDivBase = 0; + + currentIndices.do { |i| + nextDiv = divStrs[i].next; + nextDivBase = divBaseStrs[i].next; + nextDivType = divTypeStrs[i].next; + + ((nextDiv.isNil) || (nextDivBase.isNil) || (nextDivType.isNil)).if { + suspendIndices = suspendIndices.add(i); + }{ + + nextDivs = nextDivs.add(nextDiv); + nextDivBases = nextDivBases.add(nextDivBase); + nextDivTypes = nextDivTypes.add(nextDivType); + + (nextDivBase > maxDivBase).if { maxDivBase = nextDivBase }; + + (evPats[i].isKindOf(Function) or: + { (evPats[i].state == 0) || (evPats[i].state == 5) }).if { + queue.put( + (nextDivType == 'par').if { 1 }{ nextDivBase } + queueCount, + i + ); + }{ + suspendIndices = suspendIndices.add(i); + } + }; + }; + + currentIndices.removeAll(suspendIndices); + + (currentIndices.size == 0).if { + sp.suspendAll; + break.value; + }; + + (queueCount >= pulseCount).if { + nextPulseStep = maxDivBase; + nextPulseDurs = pulseStr.nextN(nextPulseStep); + }{ + nextPulseStep = maxDivBase - pulseCount + queueCount; + nextPulseDurs = nextPulseDurs ++ + pulseStr.nextN(nextPulseStep); + }; + + nextQueueStep = (queue.topPriority - queueCount).round.asInteger; + + nextPulseDurSums = nextPulseDurs.collect { |d| s = s + d }; + nextPulseDurSum = nextPulseDurSums[nextQueueStep - 1]; + + // sprout subpatterns + + currentIndices.do { |i, j| + nextDivBaseDur = nextPulseDurSums[nextDivBases[j] - 1]; + nextEvPatDurs = nextEvPatDurs + .add(this.miSC_calcDivs(nextDivs[j], nextDivBaseDur)); + + sp.par( + Pspawner { |sp| + (i * shiftDelta).wait; + + sp.par( + evPats[i].isKindOf(Function).if { + evPats[i].( + nextEvPatDurs[i], + nextDivs[j], + nextDivBases[j], + nextDivTypes[j] + ) + }{ + Pbindf( + Pfin(nextEvPatDurs[i].size, evPats[i]), + \dur, Pseq(nextEvPatDurs[j]) + ) + } + ) + } + ); + } + + }{ + sp.suspendAll; + break.value; + }; + queueCount = queueCount + nextQueueStep; + pulseCount = pulseCount + nextPulseStep; + nextPulseDurs = nextPulseDurs.drop(nextQueueStep); + nextPulseDurSum.wait; + } } + }; + ^this + } + + miSC_calcDivs { |div, nextDivBaseDur| + ^case + { div.isKindOf(Integer) }{ (nextDivBaseDur / div) ! div } + { div.isKindOf(Array) }{ div.normalizeSum * nextDivBaseDur } + { div.isKindOf(Function) }{ div.(nextDivBaseDur) } + { true }{ "div item of wrong type: must be Integer, Array or Function" } + } + +} + + + ++SequenceableCollection { + miSC_unifySize { |size| + ^(this.size != size).if { this ++ (this[0] ! (size - 1)) }{ this } + } +} + ++Object { + miSC_unifySize { |size| ^this ! size } +} + diff --git a/Classes/Patterns/PbindFx.sc b/Classes/Patterns/PbindFx.sc new file mode 100644 index 0000000..7296ed3 --- /dev/null +++ b/Classes/Patterns/PbindFx.sc @@ -0,0 +1,111 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +AbstractPbindFx : Pchain { + + classvar <>defaultSourceCleanupDelay = 0.3; + classvar <>defaultFxCleanupDelay = 0.05; + classvar <>defaultCleanupDt = 0.2; + + *new { |pbindData ...fxData| + var srcPat, fxPat, pairs, fxEventsPat; + + srcPat = case { pbindData.isKindOf(SequenceableCollection) }{ + Pbind(*pbindData) + }{ pbindData.isKindOf(Pattern) }{ + pbindData + }{ true }{ + SimpleInitError("pbindData must be event pattern or list of patternpairs").throw + }; + srcPat = srcPat.eventShortcuts; + + fxEventsPat = Ptuple(fxData.collect { |fxDataI, i| + var fxPatI = case { fxDataI.isKindOf(SequenceableCollection) }{ + Pbind(*fxDataI) + }{ fxDataI.isKindOf(Pattern) }{ + fxDataI + }{ true }{ + SimpleInitError("fxData item must be event pattern or list of patternpairs").throw + }; + + // need to apply here as data is encapsulated in Events + // normal replacement mechanism wouldn't work + + fxPatI = fxPatI.eventShortcuts; + + // start not before fx demanded with Pclutch_PbindFx + // (slight change of Pclutch default behaviour) + Pclutch_PbindFx( + Pevent(fxPatI <> Pbind(\cleanupDelay, defaultFxCleanupDelay)), + Pfunc { |e| e.fxOrder.miSC_getClutch(i+1) }, + 0 + ) + }); + + fxPat = Pbind( + \fxDataSize, fxData.size, + \defaultFxOrder, [0], + [\fxOrder, \fxPredecessors, \fxSuccessors], + Pfunc { |e| + (e.fxOrder ?? { e.defaultFxOrder }).value.miSC_getFxOrderData(e.fxDataSize); + }, + \type, \pbindFx, + \fxEvents, fxEventsPat + ); + + ^super.new(fxPat, srcPat); + } + +} + +PbindFx : AbstractPbindFx { + + *new { |pbindData ... fxData| + ^super.new(pbindData, *fxData) + } +} + +// receiver supposed to be clutchIndex + ++ Integer { + miSC_getClutch { |index| + ^(index == this).if { true }{ false } + } +} + +// receiver supposed to be clutchIndices + ++ SequenceableCollection { + + miSC_getClutch { |index| + ^this.includes(index).if { true }{ false } + } + + miSC_atKey { |key| + var i = this.indexOf(key); + ^i !? { this[i + 1] } + } +} + diff --git a/Classes/Patterns/Pclutch_PbindFx.sc b/Classes/Patterns/Pclutch_PbindFx.sc new file mode 100644 index 0000000..fec8557 --- /dev/null +++ b/Classes/Patterns/Pclutch_PbindFx.sc @@ -0,0 +1,53 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +Pclutch_PbindFx : FilterPattern { + var <>connected, <>start; + *new { arg pattern, connected = true, start; + ^super.new(pattern).connected_(connected).start_(start) + } + storeArgs { ^[ pattern, connected, start ] } + embedInStream { arg inval; + var clutchStream = connected.asStream; + var stream = pattern.asStream; + var startStream = start.asStream; + var outval, clutch, hasStarted = false; + while { + clutch = clutchStream.next(inval); + clutch.notNil + } { + if(clutch === true or: { clutch == 1 }) { + hasStarted = true; + outval = stream.next(inval); + if(outval.isNil) { ^inval }; + inval = outval.yield; + } { + hasStarted.not.if { outval = startStream.next(inval) }; + inval = outval.copy.yield; + }; + } + } +} + diff --git a/Classes/Patterns/extDictionary.sc b/Classes/Patterns/extDictionary.sc new file mode 100644 index 0000000..a450042 --- /dev/null +++ b/Classes/Patterns/extDictionary.sc @@ -0,0 +1,30 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++ Dictionary { + miSC_getEnvir { ^this } +} + + diff --git a/Classes/Patterns/extEvent.sc b/Classes/Patterns/extEvent.sc new file mode 100755 index 0000000..8f5c198 --- /dev/null +++ b/Classes/Patterns/extEvent.sc @@ -0,0 +1,34 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Event { + + on { + this[\dur] ?? { this.put(\dur, inf) }; + ^this.play + } + off { |releaseTime| ^this.release(releaseTime) } + +} diff --git a/Classes/Patterns/extIdentityDictionary.sc b/Classes/Patterns/extIdentityDictionary.sc new file mode 100644 index 0000000..e20159d --- /dev/null +++ b/Classes/Patterns/extIdentityDictionary.sc @@ -0,0 +1,47 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + + ++IdentityDictionary { + + miSC_replaceKeys { |dict| + var r; + this.copy.keysValuesDo { |k, v| + r = dict.at(k); + r.notNil.if { + this.put(r, v); + this.removeAt(k); + } + } + } + + eventShortcuts { + ^EventShortcuts.miSC_replaceShortcuts(this) + } + +} + + + diff --git a/Classes/Patterns/extObject.sc b/Classes/Patterns/extObject.sc new file mode 100644 index 0000000..1795980 --- /dev/null +++ b/Classes/Patterns/extObject.sc @@ -0,0 +1,43 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Object { + miSC_check { ^(this.value == 1) || (this.value == true) } + + miSC_getEnvir { + SimpleInputError("Only Dictionary or Symbols 'top' ('t') or 'current' ('c') " ++ + "allowed as envir input.").throw; + } + + miSC_repTypeFactor { |type = inf| ^type } + + miSC_applyNaryFuncProxy { |funcProxy...args| ^funcProxy.source.value(this, *args) } + + miSC_makeLacePat { + ^PL(this) + } + + eventShortcuts { ^this } +} + diff --git a/Classes/Patterns/extObject_2.sc b/Classes/Patterns/extObject_2.sc new file mode 100755 index 0000000..ea72e23 --- /dev/null +++ b/Classes/Patterns/extObject_2.sc @@ -0,0 +1,55 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Set { + miSC_copy { |copyItems = 0, copySets = 1| + var obj = (copySets == 1).if { this.shallowCopy }{ this }; + ^obj.array_(obj.array.collect(_.miSC_copy(copyItems))) } +} + + ++Object { + + miSC_getCopyCode { + ^IdentityDictionary[ + (0 -> 0), + (1 -> 1), + (2 -> 2), + (false -> 0), + (true -> 1), + (\false -> 0), + (\true -> 1), + (\deep -> 2), + ].at(this) ? 0 + } + + + miSC_copy { |copyItems = 0| + ^(copyItems == 2).if { this.deepCopy }{ (copyItems == 1).if { this.copy }{ this } } + } + + + miSC_maybeWrapAt { ^this } +} + diff --git a/Classes/Patterns/extPattern.sc b/Classes/Patterns/extPattern.sc new file mode 100644 index 0000000..f910a3c --- /dev/null +++ b/Classes/Patterns/extPattern.sc @@ -0,0 +1,39 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + + ++Pattern { + miSC_repTypeFactor { |type| ^1 } + + eventShortcuts { + ^Pcollect({ |e| EventShortcuts.miSC_replaceShortcuts(e) }, this) + } + + symplay { |dict, clock, protoEvent, quant, maxNull = 128| + ^PsymNilSafe(this, dict ?? { currentEnvironment }, maxNull).play(clock, protoEvent, quant) + } + +} + diff --git a/Classes/Patterns/extPbindFx_1.sc b/Classes/Patterns/extPbindFx_1.sc new file mode 100644 index 0000000..6d89f5f --- /dev/null +++ b/Classes/Patterns/extPbindFx_1.sc @@ -0,0 +1,250 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++SequenceableCollection { + + miSC_makeStartFxBundle { |successors, fxOrderPositions, fxBuses, splitBuses, + splitFactors, fxEvents, srcGroupId, fxGroupId, firstFxIds, lastFxIds, baseLatency, + cleanupDelaySums, latestCleanupIndex, gateDelay, out, freePerGroup, timingOffset, lag, server| + + var outBus, msgFunc, hasAmp, hasFreq, removeSyms, actionNum, referenceId, + fxPairs, fxBundle, fxIds, fxEvent, startFxBundle, freeFxBundle; + + // fxOrder is receiver + this.do { |fxIndex, i| + fxEvent = fxEvents[fxIndex-1]; + + // prepare fxPairs from msgFunc + // fx should not have default amp and freq + + msgFunc = SynthDescLib.global[fxEvent[\fx].asSymbol].msgFunc; + fxPairs = fxEvent.use { msgFunc.valueEnvir }; + + hasAmp = #[amp, db].any { |x| fxEvent.keys.includes(x) }; + hasFreq = #[degree, note, midinote, freq].any { |x| fxEvent.keys.includes(x) }; + + removeSyms = [\out] ++ (hasAmp.not.if { \amp }) ++ (hasFreq.not.if { \freq }); + fxPairs = fxPairs.miSC_collectEvalWithoutKeyPairs(removeSyms, fxEvent); + + outBus = (i < (this.size-1)).if { + // index shift as split arrays also refer to src + (splitFactors[i+1] > 1).if { + splitBuses[i+1].index + }{ + (successors[fxIndex].first == \o).if { + out.asControlInput + }{ + fxBuses[fxOrderPositions[successors[fxIndex].first] - 1].index + } + } + }{ + out.asControlInput + }; + + fxBundle = fxPairs.asArray.flop; + fxIds = fxIds.add({ server.nextNodeID } ! (fxBundle.size)); + + // if fx has an array value do generate multiple fx + + fxBundle.do { |fxMsg, j| + + #firstFxIds, lastFxIds, referenceId, actionNum = i.miSC_fxIdRank( + firstFxIds, lastFxIds, fxIds.last[j], srcGroupId + ); + + startFxBundle = startFxBundle ++ [ + ([\s_new, fxEvent[\fx].asSymbol, fxIds.last[j], actionNum, referenceId] ++ + fxMsg ++ (fxBuses[i].isNil).if { [] }{ [\in, fxBuses[i].index] } ++ + [\out, outBus]).asOSCArgArray + ]; + + }; + + // latest fx synth per chain will be freed anyway by group (i == latestCleanupIndex), + // others will be freed before if ~freePerGroup == true + + ((i == (latestCleanupIndex - 1)) or: { freePerGroup.not }).if { + freeFxBundle = ((i == (latestCleanupIndex - 1)).if { + [11, fxGroupId] + }{ + [11, fxIds.last.last] + }).flop; + + schedBundleArrayOnClock(timingOffset + gateDelay, + thisThread.clock, freeFxBundle, lag, server, + baseLatency + cleanupDelaySums[i+1] + ) + }; + }; + + ^startFxBundle + } + + + + + miSC_makeStartZeroBundle { |predecessors, successors, fxOrderPositions, fxBuses, splitBuses, + splitFactors, fxNum, srcGroupId, fxGroupId, baseLatency, cleanupDelaySums, latestCleanupIndex, + gateDelay, out, freePerGroup, timingOffset, lag, zeroSynthOverlap, server| + + var index, numChannels, fxZeroSynthIds, actionNum, referenceId, firstFxIds, + lastFxIds, predecessorsI, prependIndex, freeZeroBundle, startZeroBundle, + startSplitBundle, splitSynthIds, freeSplitBundle, fxOutIndices, splitName, channelNum, + startSplitZeroBundle, fxSplitZeroSynthIds, freeSplitZeroBundle; + + // this data structure keeps track of zero synth ordering in *fxGroup*, + // needed as zero synths are established before fx synths, + // relevant is first and last zero synth per fx slot + + firstFxIds = {} ! fxNum; + lastFxIds = {} ! fxNum; + + this.do { |fxIndex, i| + predecessorsI = predecessors[fxIndex].asArray; + + (predecessorsI.size != 0).if { + // prepare buses and zero synth ids + index = fxBuses[i].index; + numChannels = fxBuses[i].numChannels; + fxZeroSynthIds = numChannels.collect { server.nextNodeID }; + + predecessorsI.includes(0).if { + + // if fx reads from src, zero synth(s) must be before src (head of src group) + actionNum = 0; + startZeroBundle = startZeroBundle ++ fxZeroSynthIds.collect { |id, j| + [\s_new, \pbindFx_zero, id, actionNum, srcGroupId, "out", index + j] + }; + }{ + // find first fx in order which writes to this fx, + // zero synth(s) must must be before it + actionNum = 2; + + prependIndex = fxNum-1; + predecessorsI.do { |x| + (fxOrderPositions[x] - 1 < prependIndex).if { + prependIndex = fxOrderPositions[x] - 1 + } + }; + + fxZeroSynthIds.do { |id, j| + #firstFxIds, lastFxIds, referenceId, actionNum = prependIndex.miSC_zeroIdRank( + firstFxIds, lastFxIds, id, fxGroupId + ); + + startZeroBundle = startZeroBundle ++ [ + [\s_new, \pbindFx_zero, id, actionNum, referenceId, "out", index + j] + ] + } + } + }; + + // for latest fx synth (i == (latestCleanupIndex-1)) zero synth(s) are freed by group (freeFxBundle) + // other zero synths will be freed separately except if ~freePerGroup == true, + // freeing is scheduled with overlap + + ((i != (latestCleanupIndex - 1)) and: { freePerGroup.not and: { (predecessorsI.size != 0) } }).if { + freeZeroBundle = (fxZeroSynthIds.collect([11, _])); + schedBundleArrayOnClock(timingOffset + gateDelay, + thisThread.clock, freeZeroBundle, lag, server, + baseLatency + cleanupDelaySums[i+1] + zeroSynthOverlap + ); + }; + }; + + splitSynthIds = {} ! fxNum; + + splitFactors.do { |splitFactor, i| + + (splitFactor > 1).if { + splitSynthIds[i] = server.nextNodeID; + fxOutIndices = successors[i].asArray.collect { |s| + (s == \o).if { + out.asControlInput + }{ + fxBuses[fxOrderPositions[s] - 1].index + } + }; + + channelNum = fxBuses[fxOrderPositions[successors[i].first] - 1].numChannels; + splitName = \pbindFx_split_ ++ splitFactor.asString ++ "x" ++ channelNum.asString; + + (i == 0).if { + startSplitBundle = startSplitBundle ++ [ + [\s_new, splitName, splitSynthIds[i], 1, srcGroupId, + "in", splitBuses[i].index, "out", fxOutIndices].asOSCArgArray + ]; + fxSplitZeroSynthIds = splitBuses[i].numChannels.collect { server.nextNodeID }; + fxSplitZeroSynthIds.do { |id, j| + startSplitZeroBundle = startSplitZeroBundle ++ [ + [\s_new, \pbindFx_splitZero, id, 0, srcGroupId, + "out", splitBuses[i].index + j].asOSCArgArray + ] + } + }{ + #firstFxIds, lastFxIds, referenceId, actionNum = i.miSC_fxIdRank( + firstFxIds, lastFxIds, splitSynthIds[i], fxGroupId + ); + + startSplitBundle = startSplitBundle ++ [ + [\s_new, splitName, splitSynthIds[i], actionNum, referenceId, + "in", splitBuses[i].index, "out", fxOutIndices].asOSCArgArray + ]; + + fxSplitZeroSynthIds = splitBuses[i].numChannels.collect { server.nextNodeID }; + + fxSplitZeroSynthIds.do { |id, j| + #firstFxIds, lastFxIds, referenceId, actionNum = (i-1).miSC_zeroIdRank( + firstFxIds, lastFxIds, id, fxGroupId + ); + + startSplitZeroBundle = startSplitZeroBundle ++ [ + [\s_new, \pbindFx_splitZero, id, actionNum, referenceId, "out", + splitBuses[i].index + j] + ] + } + }; + + // free split and splitZero synths + // last splitFactor must not be other than 1 + + ((i != latestCleanupIndex) and: { freePerGroup.not }).if { + freeSplitBundle = [[11, splitSynthIds[i]]]; + schedBundleArrayOnClock(timingOffset + gateDelay, + thisThread.clock, freeSplitBundle, lag, server, + baseLatency + cleanupDelaySums[i] + ); + freeSplitZeroBundle = (fxSplitZeroSynthIds.collect([11, _])); + schedBundleArrayOnClock(timingOffset + gateDelay, + thisThread.clock, freeSplitZeroBundle, lag, server, + baseLatency + cleanupDelaySums[i] + zeroSynthOverlap + ); + } + } + }; + + ^[startZeroBundle, startSplitBundle, startSplitZeroBundle, firstFxIds, lastFxIds, splitSynthIds]; + } + +} diff --git a/Classes/Patterns/extPbindFx_2.sc b/Classes/Patterns/extPbindFx_2.sc new file mode 100644 index 0000000..6221b1d --- /dev/null +++ b/Classes/Patterns/extPbindFx_2.sc @@ -0,0 +1,185 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Symbol { + + miSC_checkPbindFxBusMatch { |sym, firstIsSource = false, secondIsLast = false, + firstOtherBusArgs, secondOtherBusArgs| + + var firstOutputs = SynthDescLib.global[this].outputs; + var secondInputs = SynthDescLib.global[sym].inputs; + var secondOutputs = SynthDescLib.global[sym].outputs; + var firstInputs = SynthDescLib.global[this].inputs; + + var areFirstInputsOk, areFirstOutputsOk, areSecondInputsOk, areSecondOutputsOk, + firstOutNumberOfChannels, secondInNumberOfChannels, connIo, ioRef = `nil; + + var inSelector = { |io| io.type != LocalIn }; + var outSelector = { |io| io.type != LocalOut }; + + // crucial check for inputs/outputs: + // counting number of ioTypes (\in or \out), must be 1 + // looking if otherBusArgs allow further ins and outs + // ioRef gets the relevant connection between fxs in second call of ioCheck + + var ioCheck = { |inOutputs, ioType, ioRef, otherArgs| + var count = 0; + inOutputs.every { |io| + (io.startingChannel == ioType).if { ioRef.value = io; count = count + 1 }; + (io.startingChannel == ioType) or: { + otherArgs.isNil.if { false } { otherArgs.includes(io.startingChannel) }; + }; + } and: { count == 1 } + }; + + firstInputs = firstInputs.select(inSelector); + firstOutputs = firstOutputs.select(outSelector); + secondInputs = secondInputs.select(inSelector); + secondOutputs = secondOutputs.select(outSelector); + + areFirstInputsOk = firstIsSource.if { + firstOtherBusArgs.isNil.if { + firstInputs.size == 0 + }{ + firstInputs.every { |io| firstOtherBusArgs.includes(io.startingChannel) } + } + }{ + // if first node (received symbol) denotes effect, + //its inputs are checked within other match + true + }; + + areFirstOutputsOk = ioCheck.(firstOutputs, \out, ioRef, firstOtherBusArgs); + areSecondInputsOk = ioCheck.(secondInputs, \in, ioRef, secondOtherBusArgs); + + connIo = ioRef.value; + + // now connIo is only relevant if all 4 checks are ok + + areSecondOutputsOk = secondIsLast.if { + ioCheck.(secondOutputs, \out, ioRef, secondOtherBusArgs) + }{ + // if second node denotes effect before last effect, + // its outputs are checked within other match + true + }; + + ((areFirstInputsOk && areFirstOutputsOk && areSecondInputsOk && areSecondOutputsOk) and: { + (firstOutputs[0].numberOfChannels) <= (connIo.numberOfChannels) + }).not.if { + SimpleInitError("PbindFx bus mismatch, source and fx synths must match " ++ + "in/out conventions: \n\n" ++ + ".) \tFor source and fx SynthDefs there must be only one out ugen " ++ + "using 'out' as bus arg, \n" ++ + "\tLocalOut is allowed, other out ugens can be admitted by \\otherBusArgs. \n" ++ + ".) \tSource SynthDefs must not have in ugens, except LocalIn or \n" ++ + "\tthey are admitted by \\otherBusArgs. \n" ++ + ".) \tFx synths must read from buses with ugen In.ar(in, ...) " ++ + "refering to the bus arg 'in', \n\tthere must not be other in ugens " ++ + "within the fx SynthDef, \n\texcept LocalIn or they are admitted by \\otherBusArgs.\n" ++ + ".) \tNumber of out channels of preceeding source / fx synth" ++ + " must not be greater than \n" ++ + "\tthe number of the following fx synth's in channels,\n\tthis is checked for " ++ + "all connections of an fx graph." + ).throw; + }; + ^connIo.first.numberOfChannels + } +} + + ++SequenceableCollection { + + // order as receiver + miSC_getFxBusData { |predecessors, successors, fxOrderPositions, fxEvents, srcInstrument, + otherBusArgs, cleanupDelay, server| + var cleanupDelaySums = [0], fxBuses, splitBuses, + splitFactors, fxNum = this.size; + + // 0 (src) can have a split, but last fx in topo order cannot + splitBuses = {} ! fxNum; + splitFactors = {} ! fxNum; + + splitFactors[0] = successors[0].asArray.size; + (splitFactors[0] > 1).if { + splitBuses[0] = Bus.audio( + server, + SynthDescLib.global[srcInstrument].outputs.first.numberOfChannels + ) + }; + + this.do { |fxIndex, i| + var inSize, inst, prevInst, prevInsts, fxEvent = fxEvents[fxIndex-1], maxPrevCleanupDelaySum = 0; + inst = (fxEvents[fxIndex-1][\fx]).asSymbol; + + prevInsts = predecessors[fxIndex].asArray.collect { |j| + // to get the new cleanupDelaySum, we have to add to the + // max cleanupDelaySum of the predecessors + + // all cleanupDelaySums are calculated without source cleanupDelay first, + // which is added afterwards, that way branches with no ins have proper cleanupDelay also (think) + var k = fxOrderPositions[j]; + + (cleanupDelaySums[k] >= maxPrevCleanupDelaySum).if { + maxPrevCleanupDelaySum = cleanupDelaySums[k] + }; + + (j == 0).if { srcInstrument }{ (fxEvents[j - 1][\fx]).asSymbol } + }; + + prevInsts.do { |prevInst| + inSize = prevInst.miSC_checkPbindFxBusMatch( + inst, + i == 0, + i+1 == fxNum, + (i == 0).if { otherBusArgs }{ fxEvents[this[i-1]-1][\otherBusArgs] }, + fxEvents[fxIndex-1][\otherBusArgs] + ) + }; + + // is nil if there is no in + fxBuses = fxBuses.add(inSize.notNil.if { Bus.audio(server, inSize) }); + + cleanupDelaySums = cleanupDelaySums.add( + fxEvent[\cleanupDelay] + maxPrevCleanupDelaySum + ); + + (i < (this.size-1)).if { + splitFactors[i+1] = successors[fxIndex].asArray.size; + (splitFactors[i+1] > 1).if { + splitBuses[i+1] = Bus.audio( + server, + SynthDescLib.global[inst].outputs.first.numberOfChannels; + ) + } + } + }; + // adjust cleanupDelaySums to source (see comments above) + cleanupDelaySums = cleanupDelaySums + cleanupDelay; + ^[fxBuses, splitBuses, splitFactors, cleanupDelaySums] + } +} + + diff --git a/Classes/Patterns/extPbindFx_3.sc b/Classes/Patterns/extPbindFx_3.sc new file mode 100644 index 0000000..e167af1 --- /dev/null +++ b/Classes/Patterns/extPbindFx_3.sc @@ -0,0 +1,305 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Object { + + miSC_checkFxOrder { ^false } + + miSC_fxOrderWarnString { + ^"wrong fxOrder data type: \n" ++ + "It must be an Integer, SequenceableCollection or IdentityDictionary, \n" ++ + "see PbindFx help for details and examples.\n" + } + + miSC_getFxOrderData { |fxDataSize, maxSplitSize = 8, doCheck = true| + + // this makes an input check if doCheck == true + + // receiver can be + // (1) an Integer indicating an effect (or source) + // (2) a SequenceableCollection indicating an effect chain + // (3) an IdentityDictionary indicating an effect graph, + + // output is an array [x,y,z] with + // x: topological ordering of graph, + // this is either [0] (no effect) or an array of fx indices where 0 as head is dropped + + // y: an IdentityDictionary of predesessors (node \o added for 'out') + // z: an IdentityDictionary of successors (node \o added for 'out') + // main job is case (3) + + ^doCheck.if { + this.miSC_checkFxOrder(fxDataSize, maxSplitSize) + }{ + true + }.if { + + this.miSC_getFxOrders; + }{ + "".postln; "NO FX APPLIED".warn; + this.miSC_fxOrderWarnString.postln; + [[0], IdentityDictionary[\o -> 0], IdentityDictionary[0 -> \o]] + } + } +} + + ++Integer { + + miSC_checkFxOrder { |maxIndex| ^(this >= 0) and: { this <= maxIndex } } + + miSC_fxOrderWarnString { + ^"fxOrder given as Integer failed: \n" ++ + "It must be greater or equal 0 and smaller or equal size of fxData, \n" ++ + "see PbindFx help for other possibilities of definition and examples.\n" + } + + miSC_getTopoOrder { + ^(this == 0).if { [0] }{ [this] } + } + + miSC_getPredecessors { + ^(this == 0).if { + IdentityDictionary[\o -> 0] + }{ + IdentityDictionary[\o -> this, this -> 0] + } + } + + miSC_getSuccessors { + ^(this == 0).if { + IdentityDictionary[0 -> \o] + }{ + IdentityDictionary[0 -> this, this -> \o] + } + } + + miSC_getFxOrders { + ^[this.miSC_getTopoOrder, this.miSC_getPredecessors, this.miSC_getSuccessors] + } +} + ++SequenceableCollection { + + miSC_checkFxOrder { |maxIndex| + ^this.every { |x,i| + x.isInteger and: { (x >= 1) or: { (i == 0) and: { x == 0 } } + and: { x <= maxIndex } + } + } + } + + miSC_fxOrderWarnString { + ^"fxOrder given as SequenceableCollection failed: \n" ++ + "It must contain Integers greater or equal 1 and smaller " ++ + "or equal size of fxData, \n" ++ + "see PbindFx help for other possibilities of definition and examples.\n" + } + + miSC_getTopoOrder { + ^(this[0] == 0).if { + (this.size == 1).if { [0] }{ this.drop(1) } + }{ + this + } + } + + miSC_getPredecessors { + var dict = IdentityDictionary(); + this.do { |x,i| + (i != 0).if { dict.put(x, this[i-1]) } + }; + dict.put(this[0], 0); + dict.put(\o, this.last); + ^dict + } + + miSC_getSuccessors { + var dict = IdentityDictionary(); + this.do { |x,i| + dict.put(x, (i != (this.size-1)).if { this[i+1] }{ \o }) + }; + dict.put(0, this[0]); + ^dict + } + + miSC_getFxOrders { + ^[this.miSC_getTopoOrder, this.miSC_getPredecessors, this.miSC_getSuccessors] + } +} + + ++IdentityDictionary { + + miSC_checkFxOrder { |maxIndex, maxSplitSize| + ^this.miSC_keysValuesEvery { |k,v,i| + k.isInteger and: { k >= 0 } and: { k <= maxIndex } and: { + (v.isInteger and: { v >= 1 } and: { v <= maxIndex }) or: { v == \o } or: { + v.isKindOf(Collection) and: { + v.every { |x| + (x.isInteger and: { x >= 1 } and: { x <= maxIndex }) or: + { x == \o } + } and: { + v.size == v.asSet.size + } and: { + v.size <= maxSplitSize + } + } + } + } + } + } + + miSC_fxOrderWarnString { + ^"fxOrder given as IdentityDictionary failed: \n" ++ + "Keys must be Integers greater or equal 0 and smaller or equal size of " ++ + "fxData, \nvalues must be Integers greater or equal 1 and smaller " ++ + "or equal size of fxData or Symbol \o or \n" ++ + "SequenceableCollection thereof, see PbindFx help for other possibilities " ++ + "of definition and examples.\n" + } + + miSC_keysValuesEvery { |function| + this.keysValuesDo { |k,v,i| if (function.value(k,v,i).not) { ^false } } + ^true; + } + + miSC_keysValuesAny { |function| + this.keysValuesDo { |k,v,i| if (function.value(k,v,i)) { ^true } } + ^false; + } + + miSC_putAppend { |k, v| + this[k].isNil.if { + this.put(k,v) + }{ + this[k].isKindOf(Collection).if { + this[k] = this[k].add(v) + }{ + this.put(k, [this[k], v]) + } + }; + ^this + } + + miSC_removeDrop { |k, v| + this[k].isNil.if { + this + }{ + (this[k].isKindOf(Collection) and: { this[k].size >= 2 }).if { + this[k].remove(v) + }{ + this.put(k, nil) + } + }; + ^this + } + + // already checked: well-defined + miSC_getPredecessors { + var dict = IdentityDictionary.new; + this.keysValuesDo { |k, v| + v.isKindOf(Collection).if { + v.do { |x| dict.miSC_putAppend(x, k) } + }{ + dict.miSC_putAppend(v, k) + } + }; + ^dict + } + + // adds \o (~ out) to nodes, which have no successors + miSC_getSuccessors { + var graph = this.as(IdentityDictionary).deepCopy, vals; + vals = graph.values; + + vals.do { |v| + v.isKindOf(Collection).if { + v.do { |w| + (graph[w].isNil and: { w != \o }).if { + graph.miSC_putAppend(w, \o) + } + } + }{ + (graph[v].isNil and: { v != \o }).if { graph.miSC_putAppend(v, \o) } + } + } + ^graph + } + + // we suppose there are no doubled edges, this has been checked in miSC_checkFxOrder + miSC_getTopoOrder { |predecessors, successors| + var graph, node, order, nodesWithoutIns; + + graph = successors ?? { this.miSC_getSuccessors }; + predecessors = predecessors ?? { graph.miSC_getPredecessors }; + + // if 0 is in graph, we want an ordering with it at head + graph.keys.includes(0).if { + // already checked that 0 is no successor + nodesWithoutIns = nodesWithoutIns.add(0) + }; + + graph.keysValuesDo { |k,v| + (k != 0).if { + predecessors[k].isNil.if { nodesWithoutIns = nodesWithoutIns.add(k) } + } + }; + + while { nodesWithoutIns.size != 0 }{ + node = nodesWithoutIns[0]; + nodesWithoutIns = nodesWithoutIns.drop(1); + order = order.add(node); + graph[node].asArray.do { |succ| + predecessors.miSC_removeDrop(succ, node); + predecessors[succ].isNil.if { + nodesWithoutIns = nodesWithoutIns.add(succ) + }; + }; + graph.put(node, nil); + }; + ^(graph.size == 0).if { order.reject(_==\o) } + } + + miSC_getFxOrders { + var order, predecessors, successors, col; + + successors = this.miSC_getSuccessors; + predecessors = successors.miSC_getPredecessors; + + order = this.miSC_getTopoOrder(predecessors.deepCopy, successors.deepCopy); + ((order.size > 1) and: (order[0] == 0)).if { order.remove(0) }; + + ^order.isNil.if { + "".postln; "NO FX APPLIED".warn; + "cyclic graph !".postln; + [[0], IdentityDictionary[\o -> 0], IdentityDictionary[0 -> \o]] + }{ + [order, predecessors, successors] + } + } +} + + diff --git a/Classes/Patterns/extPbindFx_4.sc b/Classes/Patterns/extPbindFx_4.sc new file mode 100644 index 0000000..69c79fa --- /dev/null +++ b/Classes/Patterns/extPbindFx_4.sc @@ -0,0 +1,87 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// subroutines used to establish a proper ordering of +// fx synths, zero synths, split synths and zero synths for split synths + ++Integer { + + // receiver is index + // find the right position for zero synth(s) #i (related to inbus of fx # i), + // this order is established before fitting in fx synths + + miSC_zeroIdRank { |firstFxIds, lastFxIds, nodeId, groupId| + var idAtTail = false, j, referenceId, actionNum; + firstFxIds[this].isNil.if { + j = this + 1; + while { (j <= (firstFxIds.size-1)) and: { idAtTail.not } }{ + firstFxIds[j].notNil.if { + referenceId = firstFxIds[j]; + idAtTail = true; + actionNum = 2; + }; + j = j + 1; + }; + idAtTail.not.if { + referenceId = groupId; + actionNum = 1; + }; + lastFxIds[this] = nodeId; + }{ + referenceId = firstFxIds[this]; + actionNum = 2; + }; + firstFxIds[this] = nodeId; + ^[firstFxIds, lastFxIds, referenceId, actionNum] + } + + + // receiver is index + // find the right position for split and fx synth(s) #i + + miSC_fxIdRank { |firstFxIds, lastFxIds, nodeId, groupId| + var idAtHead = false, j, referenceId, actionNum; + lastFxIds[this].isNil.if { + j = this - 1; + while { (j >= 0) and: { idAtHead.not } }{ + lastFxIds[j].notNil.if { + referenceId = lastFxIds[j]; + idAtHead = true; + }; + j = j - 1; + }; + idAtHead.not.if { referenceId = groupId }; + lastFxIds[this] = nodeId; + }{ + referenceId = lastFxIds[this]; + }; + actionNum = 3; + lastFxIds[this] = nodeId; + ^[firstFxIds, lastFxIds, referenceId, actionNum] + } + +} + + diff --git a/Classes/Patterns/extSequenceableCollection.sc b/Classes/Patterns/extSequenceableCollection.sc new file mode 100644 index 0000000..92a1a5a --- /dev/null +++ b/Classes/Patterns/extSequenceableCollection.sc @@ -0,0 +1,65 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++SequenceableCollection { + miSC_makeLacePat { + ^Ppatlace(this.collect(_.miSC_makeLacePat), inf) + } + miSC_makeLaceList { |repeats = inf| + ^this.collect(_.miSC_makeLacePat) + } + + ev { + var ev = (); + this.pairsDo { |k,v| ev.put(k, ev.use { v.() }) }; + ^ev + } + on { ^this.ev.on } + + pa { + var a; + this.pairsDo { |k,v| + a = a.add(k); + a = a.add(v.isKindOf(Function).if { Pfunc { |e| e.use { v.() } } }{ v }); + }; + ^a + } + + p { ^Pbind(*this.pa)} + pm { |sym| ^Pmono(sym, *this.pa)} + pma { |sym| ^PmonoArtic(sym, *this.pa)} + pbf { |sym| ^Pbindef(sym, *this.pa)} + + pp { |clock, protoEvent, quant| ^this.p.play(clock, protoEvent, quant)} + ppm { |sym, clock, protoEvent, quant| ^this.pm(sym).play(clock, protoEvent, quant)} + ppma { |sym, clock, protoEvent, quant| ^this.pma(sym).play(clock, protoEvent, quant)} + ppbf { |sym, clock, protoEvent, quant| ^this.pbf(sym).play(clock, protoEvent, quant)} + + eventShortcuts { ^this.collect(_.eventShortcuts) } + + miSC_maybeWrapAt { |i| ^this.wrapAt(i) } + +} + diff --git a/Classes/Patterns/extStream.sc b/Classes/Patterns/extStream.sc new file mode 100644 index 0000000..1403cba --- /dev/null +++ b/Classes/Patterns/extStream.sc @@ -0,0 +1,29 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + + ++Stream { + miSC_repTypeFactor { |type| ^1 } +} diff --git a/Classes/Patterns/extSymbol.sc b/Classes/Patterns/extSymbol.sc new file mode 100644 index 0000000..f4b9845 --- /dev/null +++ b/Classes/Patterns/extSymbol.sc @@ -0,0 +1,40 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Symbol { + miSC_getEnvir { + ((this == \c) or: { this == \current }).if { + ^currentEnvironment + }{ + ((this == \t) or: { this == \top }).if { + ^topEnvironment + }{ + SimpleInputError("Only Symbols 'top' ('t') or 'current' ('c') " ++ + "allowed as envir symbols.").throw; + } + } + } +} + diff --git a/Classes/Sieves/Psieve.sc b/Classes/Sieves/Psieve.sc new file mode 100755 index 0000000..660d990 --- /dev/null +++ b/Classes/Sieves/Psieve.sc @@ -0,0 +1,363 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +Psieve : Pattern { + + classvar <>limit = 65536; + + var <>items, <>offsets, <>limit, <>maxLength, + <>withOffsets = false, <>doSum = false, <>op, <>difIndex; + + + embedInStream { |inval| + var goOn = true, last, count = 0, counts, itemStreams, opStream, nextOp, + yielder, next, maxLengthNum, minIndices, min, leftIndices, counter, + difIndexStream, nextDifIndex, indicesQueue = PriorityQueue.new, + // faster than storing indices in an array + minIndicesDict = IdentityDictionary.new; + + offsets.isNil.if { offsets = 0!(items.size) }; + + // if Sieve is passed adjust offset + items.do { |item, i| + item.isKindOf(Sieve).if { + // set to nil with empty Sieve + offsets[i] = (item.mode == \points).if { + (item.list.size != 0).if { offsets[i] + item.list[0] }; + }{ + item.offset.notNil.if { offsets[i] + item.offset }; + } + } + }; + + itemStreams = items.collect(_.miSC_streamifySieveItems); + counts = offsets; + counts.do { |offset, i| + ((offset.notNil) and: { offset <= limit }).if { + indicesQueue.put(offset, i); + leftIndices = leftIndices.add(i); + } + }; + + opStream = op.asStream; + difIndexStream = difIndex.asStream; + maxLengthNum = maxLength.value; + + leftIndices.notNil.if { while { goOn }{ + + // partial sums stored in PriorityQueue + min = indicesQueue.topPriority.round.asInteger; + minIndices = indicesQueue.pop; + + // if more than one min is found, indices are collected in Dictionary + while { indicesQueue.topPriority == min }{ + minIndices.isInteger.if { + minIndicesDict.clear; + minIndices = minIndicesDict.put(minIndices, true) + }; + minIndicesDict.put(indicesQueue.pop, true) + }; + + // yielder function chooses output type + yielder = { + doSum.if { + inval = min.yield; + last = min; + count = count + 1; + }{ + last.notNil.if { + inval = (min - last).yield; + count = count + 1; + }; + last = min + } + }; + + nextOp = opStream.next; + nextOp.isNil.if { ^inval }; + + // operator decides what happens at this partial sum + case + { nextOp == \u } + { yielder.() } + { nextOp == \s } + { (minIndices.size == items.size) if: { yielder.() } } + { nextOp == \sd } + { minIndices.isInteger if: { yielder.() } } + { nextOp == \d } + { + // select the receiver of logical difference + difIndex.isNil.if { + nextDifIndex = 0 + }{ + nextDifIndex = difIndexStream.next; + nextDifIndex.isNil.if { ^inval }; + }; + (minIndices == nextDifIndex) if: { yielder.() } + }; + + // faster than minIndices.asArray.do { ... + counter = { |i| + next = itemStreams[i].next; + next.notNil.if { + counts[i] = counts[i] + next; + (counts[i] <= limit).if { indicesQueue.put(counts[i], i) } + }; + (next.isNil or: { counts[i] > limit }).if { leftIndices.remove(i) }; + }; + minIndices.isInteger.if { counter.(minIndices) }{ minIndices.keysDo(counter.(_)) }; + + case + { nextOp == \u } + { (leftIndices.size == 0).if { goOn = false } } + { nextOp == \s } + { (leftIndices.size < (items.size)).if { goOn = false } } + { nextOp == \sd } + { (leftIndices.size == 0).if { goOn = false } } + { nextOp == \d } + { (leftIndices[0] != 0).if { goOn = false } }; + + (count >= maxLengthNum).if { goOn = false } + } }; + ^inval + } + + + *miSC_checkOffsetGenList { |genList, limit| + var offsets, items, classes = [Integer, Pattern, Stream, Sieve]; + genList.size.do { |i| + i.even.if { + items = items.add(genList[i]) + }{ + offsets = offsets.add(genList[i]) + } + }; + items.every { |x| classes.any(x.isKindOf(_)) }.not.if { + SimpleInitError( + "Sieve base items must be Integers > 0, Sieves or: \n" ++ + "Patterns or Streams to make Integers > 0 (intervals)" + ).throw + }; + items.any { |x| x.isKindOf(Integer) and: { x <= 0 } }.if { + SimpleInitError("Integers as sieve bases must be > 0").throw + }; + offsets.every { |x| x.isKindOf(Integer) }.not.if { + SimpleInitError("Offsets must be Integers").throw + }; + limit = limit ?? { this.limit }; + ^[items, offsets, limit] + } + + + *miSC_checkGenList { |genList, limit| + var items = genList, classes = [Integer, Pattern, Stream, Sieve]; + + items.every { |x| classes.any(x.isKindOf(_)) }.not.if { + SimpleInitError( + "Sieve base items must be Integers > 0, Sieves or: \n" ++ + "Patterns or Streams to make Integers > 0 (intervals)" + ).throw + }; + items.any { |x| x.isKindOf(Integer) and: { x <= 0 } }.if { + SimpleInitError("Integers as sieve bases must be > 0").throw + }; + limit = limit ?? { this.limit }; + ^[items, limit] + } +} + + +PSVunion : Psieve { + *new { |genList, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, true, \u) + } +} + + +PSVunion_o : Psieve { + *new { |genList, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, true, \u) + } +} + +PSVunion_i : Psieve { + *new { |genList, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, false, \u) + } +} + +PSVunion_oi : Psieve { + *new { |genList, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, false, \u) + } +} + + +PSVsect : Psieve { + *new { |genList, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, true, \s) + } +} + + +PSVsect_o : Psieve { + *new { |genList, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, true, \s) + } +} + +PSVsect_i : Psieve { + *new { |genList, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, false, \s) + } +} + +PSVsect_oi : Psieve { + *new { |genList, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, false, \s) + } +} + + + +PSVsymdif : Psieve { + *new { |genList, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, true, \sd) + } +} + + +PSVsymdif_o : Psieve { + *new { |genList, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, true, \sd) + } +} + +PSVsymdif_i : Psieve { + *new { |genList, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, false, \sd) + } +} + +PSVsymdif_oi : Psieve { + *new { |genList, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, false, \sd) + } +} + + + +PSVdif : Psieve { + *new { |genList, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, true, \d) + } +} + + +PSVdif_o : Psieve { + *new { |genList, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, true, \d) + } +} + +PSVdif_i : Psieve { + *new { |genList, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, false, \d) + } +} + +PSVdif_oi : Psieve { + *new { |genList, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, false, \d) + } +} + + +PSVop : Psieve { + *new { |genList, op = \u, difIndex = 0, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, true, op, difIndex) + } +} + + +PSVop_o : Psieve { + *new { |genList, op = \u, difIndex = 0, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, true, op, difIndex) + } +} + +PSVop_i : Psieve { + *new { |genList, op = \u, difIndex = 0, maxLength = inf, limit| + var items, lim; + #items, lim = this.miSC_checkGenList(genList, limit); + ^super.newCopyArgs(items, nil, lim, maxLength, false, false, op, difIndex) + } +} + +PSVop_oi : Psieve { + *new { |genList, op = \u, difIndex = 0, maxLength = inf, limit| + var items, offsets, lim; + #items, offsets, lim = this.miSC_checkOffsetGenList(genList, limit); + ^super.newCopyArgs(items, offsets, lim, maxLength, true, false, op, difIndex) + } +} + + diff --git a/Classes/Sieves/Sieve.sc b/Classes/Sieves/Sieve.sc new file mode 100755 index 0000000..2950d2d --- /dev/null +++ b/Classes/Sieves/Sieve.sc @@ -0,0 +1,601 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +Sieve { + + classvar <>limit = 65536; + + var > { |addOffset| ^this.shift(addOffset) } + + + shiftTo { |targetOffset| + ^(mode == \points).if { + (list.size != 0).if { this.shift(targetOffset - list[0]) }{ this } + }{ + offset.isNil.if { this }{ this.shift(targetOffset - offset) } + } + } + + >>! { |targetOffset| ^this.shiftTo(targetOffset) } + + shiftToZero { ^list.shiftTo(0) } + + + // we want to copy the Sieve's list too + copy { ^this.deepCopy } + + // new Sieve, same list object + weakCopy { ^this.superPerform(\copy) } + + // take over mode and offset from Sieve and pass an appropriate array + copyWith { |seqCollection, withCheck = true| + var new = this.weakCopy; + + seqCollection.isKindOf(SequenceableCollection).not.if { + SimpleInitError( + "copyWith expects SequenceableCollection as first arg" + ).throw + }; + + seqCollection.every(_.isInteger).not.if { + SimpleInitError( + "SequenceableCollection must consist of Integers" + ).throw + }; + + (mode == \points).if { + withCheck.if { + (seqCollection.every { |point, i| + (point > 0) and: { (i == 0) or: { point > seqCollection[i-1] } } + }).not.if { + SimpleInitError( + "With mode = \points SequenceableCollection " ++ + "must be strictly ascending" + ).throw + } + }; + }{ + withCheck.if { + seqCollection.every(_>0).not.if { + SimpleInitError( + "With mode = \intervals SequenceableCollection" ++ + "must consist of positive Integers" + ).throw + } + }; + } + ^new.miSC_setList(seqCollection.asList) + } + + // apply operator or Function to List and pass the result + // to a new Sieve with copied mode and offset + copyApplyTo { |operator, withCheck = true| + ^this.copyWith(operator.applyTo(this.list), withCheck) + } + + // equality also considers different modes + == { |that| + ^(this.mode == that.mode).if { + list == that.list and: { this.offset == that.offset } + }{ + (this.mode == \points).if { + this == that.weakCopy.toPoints + }{ + that == this.weakCopy.toPoints + } + } + } + + + // segment methods are most efficient for receiver of mode == \points, + // output is new Sieve object of mode \points anyway + segmentGreaterEqual { |lo| + var index, point, sieve, doCheck = true; + sieve = Sieve.newEmpty; + ^(mode == \points).if { + (list.size != 0).if { + index = list.miSC_lowestIndexForWhichGreaterEqual(lo); + index.notNil.if { sieve.miSC_setList(list.copyRange(index, list.size-1)) } + }; + sieve + }{ + offset.notNil.if { + point = offset; + (offset >= lo).if { sieve.list.add(point); doCheck = false }; + + list.do { |interval| + point = point + interval; + doCheck.if { + (point >= lo).if { doCheck = false; sieve.list.add(point) } + }{ + sieve.list.add(point) + } + } + }; + sieve + }.miSC_setMode(\points); + } + + segmentLessEqual { |hi| + var index, point, sieve; + sieve = Sieve.newEmpty; + ^(mode == \points).if { + (list.size != 0).if { + index = list.miSC_highestIndexForWhichLessEqual(hi); + index.notNil.if { sieve.miSC_setList(list.copyRange(0, index)) } + }; + sieve + }{ + offset.notNil.if { + point = offset; + index = 0; + while { index <= (list.size-1) }{ + point = point + list[index]; + (point <= hi).if { sieve.list.add(point) }{ index = list.size - 1 }; + index = index + 1; + } + }; + sieve + }.miSC_setMode(\points); + } + + // extra method is more efficient than to apply segmentLessEqual and segmentGreaterEqual + segmentBetweenEqual { |lo, hi| + var index, point, sieve, doLoCheck = true, loIndex, hiIndex; + sieve = Sieve.newEmpty; + ^(lo > hi).if { + sieve + }{ + (mode == \points).if { + (list.size != 0).if { + loIndex = list.miSC_lowestIndexForWhichGreaterEqual(lo); + hiIndex = list.miSC_highestIndexForWhichLessEqual(hi); + (loIndex.isNil or: { hiIndex.isNil }).if { + sieve + }{ + sieve.miSC_setList(list.copyRange(loIndex, hiIndex)) + } + }{ + sieve + } + }{ + offset.notNil.if { + point = offset; + index = 0; + while { index <= (list.size-1) }{ + point = point + list[index]; + + doLoCheck.if { + (point >= lo).if { + doLoCheck = false; + (point <= hi).if { + sieve.list.add(point) + }{ + index = list.size - 1 + }; + } + }{ + (point <= hi).if { + sieve.list.add(point) + }{ + index = list.size - 1 + }; + }; + index = index + 1; + } + }; + sieve + } + }.miSC_setMode(\points); + } + + + segmentGreater { |lo| ^this.segmentGreaterEqual(lo + 1) } + + >=! { |lo| ^this.segmentGreaterEqual(lo) } + >! { |lo| ^this.segmentGreaterEqual(lo + 1) } + + + segmentLess { |hi| ^this.segmentLessEqual(hi - 1) } + + <=! { |hi| ^this.segmentLessEqual(hi) } + =! { |bounds| ^this.segmentBetweenEqual(*bounds) } + <>! { |bounds| ^this.segmentBetweenEqual(bounds[0] + 1, bounds[1] - 1) } + + + + printOn { arg stream; + if (stream.atLimit, { ^this }); + stream << "Sieve(" ; + stream << (this.mode.asString ++ ", "); + (this.mode == \intervals).if { stream << (this.offset.asString ++ ", ") }; + list.printOn(stream); + stream << ")" ; + } + + storeOn { arg stream; + var thisMode; + if (stream.atLimit, { ^this }); + + stream << "[ " ; + list.storeItemsOn(stream); + stream << " ]" ; + + stream << ".toSieve("; + stream << offset.asString; + stream << ", \\"; + thisMode = this.mode.asString; + stream << thisMode; + stream << ", \\"; + stream << thisMode; + stream << ")"; + } + + // private + + // These setters don't check the Sieve's state, + // the user should not directly set mode and offset. + + miSC_setMode { |v| mode = v } + miSC_setOffset { |v| offset = v } + miSC_setList { |v| list = v } + + miSC_sieveOp { |args, withOffsets = false, doSum = false, op| + var goOn = true, last, counts, items, offsets, limit, next, + minIndices, min, leftIndices, counter, minIndicesDict = IdentityDictionary.new, + indicesQueue = PriorityQueue.new; + + #items, offsets, limit = (withOffsets.if { + this.miSC_checkOffsetArgs(args); + }{ + this.miSC_checkArgs(args); + }); + + offsets.isNil.if { offsets = 0!(items.size) }; + + // adjust offset if Sieve is passed + items.do { |item, i| + item.isKindOf(Sieve).if { + // set to nil with empty Sieve + offsets[i] = (item.mode == \points).if { + (item.list.size != 0).if { offsets[i] + item.list[0] }; + }{ + item.offset.notNil.if { offsets[i] + item.offset }; + } + } + }; + + items = items.collect(_.miSC_streamifySieveItems); + counts = offsets; + counts.do { |offset, i| + ((offset.notNil) and: { offset <= limit }).if { + indicesQueue.put(offset, i); + leftIndices = leftIndices.add(i); + } + }; + + mode = doSum.if { \points }{ \intervals }; + + leftIndices.notNil.if { while { goOn }{ + // partial sums stored in PriorityQueue + min = indicesQueue.topPriority.round.asInteger; + minIndices = indicesQueue.pop; + + // if more than one min is found, indices are collected in Dictionary + while { indicesQueue.topPriority == min }{ + minIndices.isInteger.if { + minIndicesDict.clear; + minIndices = minIndicesDict.put(minIndices, true) + }; + minIndicesDict.put(indicesQueue.pop, true) + }; + + // operator decides what happens at this partial sum + doSum.if { + case + { op == \u } + { list.add(min); last = min } + { op == \s } + { (minIndices.size == items.size).if { list.add(min); last = min } } + { op == \sd } + { minIndices.isInteger.if { list.add(min); last = min } } + { op == \d } + { (minIndices == 0).if { list.add(min); last = min } } + }{ + case + { op == \u } + { + last.notNil.if { list.add(min - last) }{ offset = min }; + last = min + } + { op == \s } + { + (minIndices.size == items.size).if { + last.notNil.if { list.add(min - last) }{ offset = min }; + last = min + } + } + { op == \sd } + { + minIndices.isInteger.if { + last.notNil.if { list.add(min - last) }{ offset = min }; + last = min + } + } + { op == \d } + { + (minIndices == 0).if { + last.notNil.if { list.add(min - last) }{ offset = min }; + last = min + } + } + }; + + // faster than minIndices.asArray.do { ... + counter = { |i| + next = items[i].next; + next.notNil.if { + counts[i] = counts[i] + next; + (counts[i] <= limit).if { indicesQueue.put(counts[i], i) } + }; + (next.isNil or: { counts[i] > limit }).if { leftIndices.remove(i) }; + }; + minIndices.isInteger.if { counter.(minIndices) }{ minIndices.keysDo(counter.(_)) }; + + case + { op == \u } + { (leftIndices.size == 0).if { goOn = false } } + { op == \s } + { (leftIndices.size < (items.size)).if { goOn = false } } + { op == \sd } + { (leftIndices.size == 0).if { goOn = false } } + { op == \d } + { (leftIndices[0] != 0).if { goOn = false } }; + } }; + + ^this + } + + + miSC_checkOffsetArgs { |args| + var offsets, items, limit, maxSize = args.size, + classes = [Integer, Pattern, Stream, Sieve]; + args.size.odd.if { + limit = args.last.value; + limit.isKindOf(Integer).not.if { + SimpleInitError( + "limit -- given as last item of an odd number " ++ + "of args --\n must be or eveluate to an Integer").throw + }; + maxSize = maxSize - 1 + }{ + limit = Sieve.limit + }; + maxSize.do { |i| + i.even.if { + items = items.add(args[i]) + }{ + offsets = offsets.add(args[i]) + } + }; + items.every { |x| classes.any(x.isKindOf(_)) }.not.if { + SimpleInitError( + "Sieve base items must be Integers > 0, Sieves or: \n" ++ + "Patterns or Streams to make Integers > 0 (intervals)" + ).throw + }; + items.any { |x| x.isKindOf(Integer) and: { x <= 0 } }.if { + SimpleInitError("Integers as sieve bases must be > 0").throw + }; + offsets.every { |x| x.isKindOf(Integer) }.not.if { + SimpleInitError("Offsets must be Integers").throw + }; + ^[items, offsets, limit] + } + + + miSC_checkArgs { |args| + var items = args, limit, + classes = [Integer, Pattern, Stream, Sieve]; + + args.last.isKindOf(Ref).if { + items = items.drop(-1); + // if new gets a Ref eval twice + limit = args.last.value.value; + limit.isKindOf(Integer).not.if { + SimpleInitError("limit -- given as last arg wrapped " ++ + "into a Ref object --\n" ++ + "must be an Integer" + ).throw + } + }{ + limit = Sieve.limit + }; + items.every { |x| classes.any(x.isKindOf(_)) }.not.if { + SimpleInitError( + "Sieve base items must be Integers > 0, Sieves or: \n" ++ + "Patterns or Streams to make Integers > 0 (intervals)" + ).throw + }; + items.any { |x| x.isKindOf(Integer) and: { x <= 0 } }.if { + SimpleInitError("Integers as sieve bases must be > 0").throw + }; + ^[items, nil, limit] + } +} + ++SequenceableCollection { + + // iterated bisection, expects ordered list + miSC_lowestIndexForWhichGreaterEqual { |lo| + var goOn = true, loIndex = 0, hiIndex = this.size - 1, newIndex, index; + + (this[hiIndex] < lo).if { goOn = false }; + (this[loIndex] >= lo).if { goOn = false; index = 0 }; + + while { goOn }{ + ((hiIndex - loIndex) <= 1).if { goOn = false; index = hiIndex }; + newIndex = div(hiIndex + loIndex, 2); + + (this[newIndex] == lo).if { goOn = false; index = newIndex }; + (this[newIndex] < lo).if { loIndex = newIndex }{ hiIndex = newIndex }; + }; + + ^index + } + + // iterated bisection, expects ordered list + miSC_highestIndexForWhichLessEqual { |hi| + var goOn = true, loIndex = 0, hiIndex = this.size - 1, newIndex, index; + + (this[loIndex] > hi).if { goOn = false }; + (this[hiIndex] <= hi).if { goOn = false; index = hiIndex }; + + while { goOn }{ + ((hiIndex - loIndex) <= 1).if { goOn = false; index = loIndex }; + newIndex = div(hiIndex + loIndex, 2); + + (this[newIndex] == hi).if { goOn = false; index = newIndex }; + (this[newIndex] > hi).if { hiIndex = newIndex }{ loIndex = newIndex }; + }; + + ^index + } + +} + diff --git a/Classes/Sieves/lcmExtensions.sc b/Classes/Sieves/lcmExtensions.sc new file mode 100644 index 0000000..e72c682 --- /dev/null +++ b/Classes/Sieves/lcmExtensions.sc @@ -0,0 +1,117 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Integer { + + lcmByGcd { |...args| + var lcm, thr = 2 ** 31 - 1, postWarning = false, returnNil = false; + (args.size == 0).if { ^(this == 0).if { 0 }{ this.abs } }; + (args.every { |i| i.isKindOf(Integer) }).not.if { "lcmByGcd requires Integers".error }; + args.any(_ == 0).if { ^0 }; + lcm = args.inject(this, { |x, y, i| + // z must be float for reliable threshold check, thus '/' instead of 'div' + var z = x / gcd(x.isFloat.if { x.round.asInteger }{ x }, y) * y; + (z > thr).if { + (i < (args.size-1)).if { + ("exceeding int32 bound within iteration, " ++ + "reduce number of args or try lcmByFactors").warn; + returnNil = true; + }; + postWarning = true; + }; + z + }); + returnNil.if { ^nil }; + ^postWarning.if { + "exceeding int32 bound, returning float".warn; + lcm + }{ + lcm.round.asInteger + } + } + + lcmByFactors { |...args| + var lcm, f, factors, argFactors, argListFactors, lcmFactors, + lcmFactorDict = IdentityDictionary.new, + postWarning = false, thr = 2 ** 31 - 1; + + (args.size == 0).if { ^(this == 0).if { 0 }{ this.abs } }; + (args.every { |i| i.isKindOf(Integer) }).not.if { "lcmByGcd requires Integers".error }; + args.any(_ == 0).if { ^0 }; + + factors = this.abs.factors; + argListFactors = args.abs.collect(_.factors); + + f = { |dict| + { |x| + var m = dict.at(x); + dict.put(x, m.isNil.if { 1 }{ m + 1 }); + } + }; + factors.do(f.(lcmFactorDict)); + + // collect prime factors and occurences + argListFactors.do { |argFactors| + var argFactorDict = IdentityDictionary.new; + argFactors.do(f.(argFactorDict)); + + argFactorDict.keysValuesDo{ |k,v,i| + var m = lcmFactorDict.at(k); + lcmFactorDict.put(k, m.isNil.if { v }{ max(m,v) }) + }; + }; + + lcmFactors = []; + lcmFactorDict.keysValuesDo { |k,v| v.do { lcmFactors = lcmFactors.add(k) } }; + lcmFactors.sort; + + // threshold check, if result exceeds int32 bound return float + lcmFactors.do { |x,i| + (lcmFactors.size > 1).if { + (i == 0).if { + lcm = x.asFloat; + }{ + lcm = lcm * x; + (lcm > thr).if { postWarning = true }; + } + }{ + ^[x, lcmFactors, [factors, argListFactors]] + } + }; + ^postWarning.if { + "exceeding int32 bound, returning float".warn; + [lcm, lcmFactors, [factors, argListFactors]] + }{ + [lcm.round.asInteger, lcmFactors, [factors, argListFactors]] + } + } + +} + ++SequenceableCollection { + + lcmByGcd { ^this[0].lcmByGcd(*this[1..]) } + lcmByFactors { ^this[0].first.lcmByFactors(*this[1..]) } +} + diff --git a/Classes/Sieves/periods.sc b/Classes/Sieves/periods.sc new file mode 100644 index 0000000..0bc7dbc --- /dev/null +++ b/Classes/Sieves/periods.sc @@ -0,0 +1,160 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++SequenceableCollection { + + + miSC_isSymmetricRange { |i, j| + var count = 0, limit = div(j - i, 2); + + while { count <= limit }{ + (this[i + count] != this[j - count]).if { ^false }; + count = count + 1 + }; + ^true + } + + miSC_isQuasiSymmetricRange { |i, j| + ^this.miSC_isSymmetricRange(i + 1, j) + } + + // expects j >= i + miSC_symmetryType { |i, j| + ^((j == i) or: { + (0..j - i - 1).every { |k| this[i + k + 1] == this[i] } + }).if { + 3 + }{ + this.miSC_isSymmetricRange(i, j).if { + 2 + }{ + this.miSC_isQuasiSymmetricRange(i, j).if { 1 }{ 0 } + } + } + } + + miSC_smallestPeriodLength { + var length = 1, i, z = this.size; + + while { length + 1 <= z }{ + i = 0; + while { i + 1 <= z }{ + (this[i % length] != this[i]).if { i = z }; + i = i + 1; + (i == z).if { ^length } + }; + (length + 1 == z).if { + ^length + 1 + }{ + length = length + 1 + + } + }; + ^1 + } + + miSC_checkSymmetricPeriods { + var length = this.miSC_smallestPeriodLength, offset = 0, z = this.size, + type = 0, goOn = true, periods, symmetries, completions, halfPeriodStart, + symTypeSymbols = IdentityDictionary[ + 0 -> 'asym', + 1 -> 'quasisym', + 2 -> 'sym', + 3 -> 'identic' + ]; + + while { goOn }{ + type = this.miSC_symmetryType(offset, offset + length - 1); + (type != 0).if { + // if type is quasi-symmetric and length odd there might be a symmetric period + // starting from the middle - check if it exists and if it's in the range, + // if so prefer symmetric period representation + (length.odd and: { type == 1 }).if { + halfPeriodStart = offset + div(length, 2) + 1; + ((halfPeriodStart + length <= z) and: { + this[halfPeriodStart] == this[halfPeriodStart - 1] + }).if { type = 2; offset = halfPeriodStart }; + }; + goOn = false + }{ + offset = offset + 1 + }; + (offset + length > z).if { goOn = false }; + }; + + (type == 0).if { + offset = 0; + periods = this.clump(length); + symmetries = 0 ! periods.size; + completions = true ! periods.size; + + // check symmetry of incomplete period, it can be of any type + (periods.last.size != periods.first.size).if { + completions[periods.size - 1] = false; + symmetries[periods.size - 1] = + periods.last.miSC_symmetryType(0, periods.last.size - 1); + } + + }{ + periods = ((offset > 0).if { [this[0..offset-1].asArray] }) ++ (this.drop(offset).clump(length)); + completions = periods.collect { |p| p.size == length }; + symmetries = type ! periods.size; + + // check symmetries of incomplete periods, they can be of any type + completions.first.not.if { + symmetries[0] = periods[0].miSC_symmetryType(0, periods[0].size - 1) + }; + + completions.last.not.if { + symmetries[periods.size - 1] = + periods.last.miSC_symmetryType(0, periods.last.size - 1) + }; + }; + ^[periods, symmetries.collect { |i| symTypeSymbols[i] }, completions] + } + + miSC_checkCharacteristicPeriod { + var periods, symmetries, completions, index, offset; + #periods, symmetries, completions = this.miSC_checkSymmetricPeriods; + index = completions.indexOfEqual(true); + offset = (index > 0).if { periods.first.size }{ 0 }; + ^[ + periods[index], + offset, + periods[index].size.odd.if { \odd }{ \even }, + symmetries[index] + ] + } +} + + ++Sieve { + checkSymmetricPeriods { ^this.weakCopy.toIntervals.list.miSC_checkSymmetricPeriods } + checkCharacteristicPeriod { ^this.weakCopy.toIntervals.list.miSC_checkCharacteristicPeriod } + plotCharacteristicPeriod { + ^this.weakCopy.toIntervals.list.miSC_checkCharacteristicPeriod.at(0) + .toSieve(\intervals, \intervals).plot + } +} diff --git a/Classes/Sieves/streamifySieveItems.sc b/Classes/Sieves/streamifySieveItems.sc new file mode 100755 index 0000000..ece6210 --- /dev/null +++ b/Classes/Sieves/streamifySieveItems.sc @@ -0,0 +1,48 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Sieve { + miSC_streamifySieveItems { + ^(this.list.size != 0).if { + (this.mode == \intervals).if { + Pseq(this.list).asStream + }{ + Pdiff(Pseq(this.list)).asStream + } + } + } +} + ++Pattern { + miSC_streamifySieveItems { ^this.asStream } +} + ++Integer { + miSC_streamifySieveItems { ^this.asStream } +} + ++Stream { + miSC_streamifySieveItems { ^this } +} diff --git a/Classes/Sieves/toSieve.sc b/Classes/Sieves/toSieve.sc new file mode 100755 index 0000000..1db23ae --- /dev/null +++ b/Classes/Sieves/toSieve.sc @@ -0,0 +1,115 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Object { + miSC_isPointSel { ^((this == \points) or: { this == \p }) } + miSC_isIntervalSel { ^((this == \intervals) or: { this == \i }) } +} + ++Integer { + toSieve { ^Sieve.newEmpty.miSC_setList(List[this]).miSC_setMode(\points) } +} + + + ++SequenceableCollection { + + toSieve { |fromMode = \points, toMode = \points, addOffset = 0, withCheck = true| + + var sieve, newOffset, pointCol, intervalCol; + + addOffset.isInteger.not.if { + SimpleInitError("Argument addOffset must be Integer").throw + }; + this.every(_.isInteger).not.if { + SimpleInitError( + "SequenceableCollection must consist of Integers" + ).throw + }; + + case { fromMode.miSC_isPointSel }{ + pointCol = List.newUsing(this + addOffset); + intervalCol = pointCol.differentiate.drop(1); + withCheck.if { + intervalCol.every(_>0).not.if { + SimpleInitError( + "With fromMode = \points SequenceableCollection " ++ + "must be strictly ascending" + ).throw + } + }; + + case { toMode.miSC_isPointSel }{ + ^Sieve.newEmpty.miSC_setList(pointCol).miSC_setMode(\points) + } + { toMode.miSC_isIntervalSel }{ + newOffset = pointCol[0]; + ^Sieve.newEmpty.miSC_setList(intervalCol).miSC_setOffset(newOffset) + } + + { true }{ + SimpleInitError( + "Allowed Symbols for toMode: \points, \p, \intervals, \i" + ).throw + }; + } + { fromMode.miSC_isIntervalSel }{ + + withCheck.if { + this.every(_>0).not.if { + SimpleInitError( + "With fromMode = \intervals SequenceableCollection " ++ + "must consist of positive Integers" + ).throw + } + }; + + case { toMode.miSC_isPointSel }{ + sieve = Sieve.newEmpty.miSC_setMode(\points); + sieve.list.add(addOffset); + this.do { |interval| var point = interval + sieve.list.last; sieve.list.add(point) }; + ^sieve + } + { toMode.miSC_isIntervalSel }{ + ^Sieve.newEmpty.miSC_setList(this).miSC_setOffset(addOffset) + } + + { true }{ + SimpleInitError( + "Allowed Symbols for toMode: " ++ + "\points, \p, \intervals, \i" + ).throw + }; + } + + { true }{ + SimpleInitError( + "allowed Symbols for fromMode: \points, \p, \intervals, \i" + ).throw + }; + + } +} + diff --git a/Classes/VarGui/SimpleInitError.sc b/Classes/VarGui/SimpleInitError.sc new file mode 100644 index 0000000..bf9f9a7 --- /dev/null +++ b/Classes/VarGui/SimpleInitError.sc @@ -0,0 +1,41 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +SimpleInitError : Error { + + var <>class; + + errorString { + ^"INPUT ERROR in building an instance of " ++ class.asString ++ ":" + } + + reportError { + "".postln; + this.errorString.postln; + "".postln; + what.postln; + "".postln; + } +} diff --git a/Classes/VarGui/SimpleInputError.sc b/Classes/VarGui/SimpleInputError.sc new file mode 100644 index 0000000..ad5c095 --- /dev/null +++ b/Classes/VarGui/SimpleInputError.sc @@ -0,0 +1,49 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +SimpleInputError : Error { + + var <>methodString; + + errorString { + ^"INPUT ERROR in method " ++ methodString ++ " :" + } + + method_ { |method| methodString = method.miSC_errorString; ^this } + + reportError { + "".postln; + this.errorString.postln; + "".postln; + what.postln; + "".postln; + } +} + + ++Method { + miSC_errorString { ^this.ownerClass.name.asString ++ "::" ++ this.name.asString } +} + + diff --git a/Classes/VarGui/VarGui.sc b/Classes/VarGui/VarGui.sc new file mode 100644 index 0000000..f3102e8 --- /dev/null +++ b/Classes/VarGui/VarGui.sc @@ -0,0 +1,469 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +VarGui { + var <>varCtr, <>synthCtr, <>synthIDs, <>server, <>streams, <>synthCtrIndices, + <>varCtrNum, <>varNum, <>varColorNum, <>synthCtrNum, synthColorNum, + hasConnectedVarEnvirGroups, hasConnectedSynthCtrGroups, firstSynthCtrIndices, + <>varStrings, varCtrSizes, synthCtrSizes, <>synthCtrStrings, <>saveData, <>envirs, + <>varEnvirIndices, varEnvirGroups, assumePlaying, <>assumeRunning, + <>assumeEnded, envirs, streamEnvirs, window, colors, <>varCtrColorPairs, <>synthCtrColorPairs, <>synthCtrSliders, + <>varCtrSliders, <>synthSliderDict, <>varSliderDict, <>synthNameBoxes; + + *new { |varCtr, synthCtr, stream, synth, clock, quant, varEnvirGroups, envir, synthCtrGroups, + assumePlaying, assumeRunning, assumeEnded, server| + ^super.new.init(varCtr, synthCtr, stream, synth, clock, quant, varEnvirGroups, envir, synthCtrGroups, + assumePlaying, assumeRunning, assumeEnded, server); + } + *load { |pathname, filename, stream, synth, clock, quant, varEnvirGroups, envir, synthCtrGroups, + assumePlaying, assumeRunning, assumeEnded, server| + var loadData = (pathname ++ filename).load; + ^super.new.init(loadData[0], loadData[1], stream, synth, clock, quant, varEnvirGroups ?? loadData[2], envir, + synthCtrGroups ?? loadData[3], assumePlaying, assumeRunning, assumeEnded, server); + } + *load_old { |pathname, filename, stream, synth, clock, quant, varEnvirGroups, envir, synthCtrGroups, + assumePlaying, assumeRunning, assumeEnded, server| + var loadData = (pathname ++ filename).load; + ^super.new.init(loadData[0], loadData[1].select { |x,i| i.odd }, stream, synth, clock, quant, + varEnvirGroups ?? loadData[2], envir, synthCtrGroups ?? loadData[3], assumePlaying, + assumeRunning, assumeEnded, server); + } + init { |varCtr, synthCtr, stream, synth, clock, quant, varEnvirGroups, envir, synthCtrGroups, + assumePlaying, assumeRunning, assumeEnded, server| + this.server = server ?? { Server.default }; + + this.checkArgs(varCtr, synthCtr, stream, synth, varEnvirGroups, envir, + synthCtrGroups, assumePlaying, assumeRunning, assumeEnded); + + saveData = [ + // varCtr, synthCtr may have been flattened, so store mapping info as groupings + // miSC_collectCopy for nested arrays with unclear pointer structure + this.varCtr.miSC_collectCopy, + this.synthCtr.miSC_collectCopy, + varEnvirGroups ?? { + ((this.varCtr.size != 0) and: { varEnvirIndices.maxItem > 0 }).if { + varEnvirIndices.miSC_groupingFromIndices + } + }, + synthCtrGroups ?? { + ((synthCtrSynthIndices.size != 0) and: { synthCtrSynthIndices.maxItem > 0 }).if { + synthCtrSynthIndices.miSC_groupingFromIndices + } + } + ]; + + this.varCtr.do { |item,i| + var x, envir; + (i % 2 == 0).if { + this.varCtr[i+1].every(_.isSequenceableCollection).if { + envir = envirs[varEnvirIndices[i.div(2)]]; + x = envir[item]; + varCtrSizes = varCtrSizes.add(this.varCtr[i+1].size); + // build array in this envir with this name if not already there + ((x.isSequenceableCollection) and: { x.size == (this.varCtr[i+1].size) }).not.if { + envir[item] = Array.newClear(this.varCtr[i+1].size) + } + }{ + varCtrSizes = varCtrSizes.add(1); + }; + } + }; + + this.synthCtr.do { |item,i| + i.even.if { + this.synthCtr[i+1].every(_.isSequenceableCollection).if { + synthCtrSizes = synthCtrSizes.add(this.synthCtr[i+1].size); + }{ + synthCtrSizes = synthCtrSizes.add(1); + }; + synthCtrIndices = synthCtrIndices.add((synths[synthCtrSynthIndices[i.div(2)]]).miSC_getCtrIndex(item)); + } + }; + + // number of variables (keys) counting multiple keys but disregarding arrays: + varNum = this.varCtr.size.div(2); + + // number of variable controls counting multiple keys and regarding arrays: + varCtrNum = varCtrSizes.asArray.sum; + streamPlayerNum = streams.size; + + // number of synth controls counting multiple keys in different synths and regarding arrays: + synthCtrNum = synthCtrSizes.asArray.sum; + synthPlayerNum = synths.size; + + clocks = case + { clock.isNil }{ streams.collect {|x| x.clock ?? TempoClock.default } } + { clock.isSequenceableCollection }{ clock.collect {|x,i| x ?? streams[i].clock ?? TempoClock.default } } + { clock.isKindOf(Clock) }{ clock!(streamPlayerNum) }; + + quants = case + { quant.isNil }{ Quant.default!(streamPlayerNum) } + { quant.isSequenceableCollection }{ quant.collect(_.asQuant) } + { true }{ quant.asQuant!(streamPlayerNum) }; + + ^this + } + + + + gui { |sliderHeight = 18, sliderWidth, labelWidth = 75, numberWidth = 45, + sliderType = \standard, sliderMode = \jump, playerHeight = 18, precisionSpan = 10, stepEqualZeroDiv = 100, + tryColumnNum = 1, allowSynthsBreak = true, allowVarsBreak = true, allowSynthBreak = true, + allowArrayBreak = true, allowEnvirBreak = true, minPartitionSize = 0, + sliderPriority = \var, playerPriority = \stream, streamPlayerGroups, synthPlayerGroups, comboPlayerGroups, + font = "Helvetica", colorsLo, colorsHi, colorDeviation, ctrButtonGreyCenter, ctrButtonGreyDev, greyMode = false, + varColorGroups, synthColorGroups, name, updateNow = true, + sliderFontHeight = 12, playerFontHeight = 12, maxWindowWidth = 1500, maxWindowHeight = 700| + var windowName, tempColors, totalHeight, totalWidth, heightK, + minSliderFontHeight = 10, maxSliderFontHeight = 16, minFontSliderHeight = 12, maxFontSliderHeight = 24, + + cview, pview, pviewBounds, varColorNum, synthColorNum, colorNum, + colorDeviationDefault = 0.12, greyModeColorDeviationDefault = 0.0, varColorGroupsWasNil, groups, + colorsLoDefault = 0.25, greyModeColorsLoDefault = 0.4, colorsHiDefault = 0.7, greyModeColorsHiDefault = 0.6, + ctrButtonGreyCenterDefault = 0.5, ctrButtonGreyDevDefault = 0.8, greyModeCtrButtonGreyCenterDefault = 0.5, + greyModeCtrButtonGreyDevDefault = 0.65, + + synthNumWidth = 20, leftMargin = 40, columnIndent = 40, + rightMargin = 40, upperMargin = 30, lowerMargin = 30, gap = 3, + maxColumnNum, columnNum, possibleColumnBreakIndices, maxSliderWidth = 270, minSliderWidth = 100, + columnBreakIndices, effectiveSliderWidth, sliderSectionHeight = 0, + + xPosMin = 100, xPosMax = 500, yPosMin = 100, yPosMax = 500, + fontColor = Color.black, colorAreaUsage = 0.25, colorSteps = 1000, numColorExp = 0.4, k; + + window.notNil.if { SimpleInitError("Only one gui window from one VarGui at a time").class_(VarGui).throw; }; + + // div inits + + varColorGroups.notNil.if { varCtrNum.miSC_checkColorGroups(varColorGroups) }; + synthColorGroups.notNil.if { synthCtrNum.miSC_checkColorGroups(synthColorGroups) }; + + colorDeviation = colorDeviation ?? { + (varColorGroups.notNil || synthColorGroups.notNil).if { + 0 + }{ + greyMode.if { greyModeColorDeviationDefault }{ colorDeviationDefault } + } + }; + + + (greyMode && (varColorGroups.notNil || synthColorGroups.notNil)).if { + SimpleInitError("If a color grouping is passed greyMode must not be true").throw; + }; + + varColorGroupsWasNil = varColorGroups.isNil; + + // overrule standard color grouping scheme if one color grouping is given + varColorGroups = varColorGroups ?? { + groups = this.initVarCtrColorGroups; + synthColorGroups.isNil.if { groups }{ groups.flatten(1).collect(_.asArray) } + }; + synthColorGroups = synthColorGroups ?? { + groups = this.initSynthCtrColorGroups; + varColorGroupsWasNil.if { groups }{ groups.flatten(1).collect(_.asArray) } + }; + + + // check if wslib slider classes are known for respective sliderType + case + { (sliderType == \smooth) } + { + \EZSmoothSlider.asClass.isNil.if { + SimpleInitError("EZSmoothSlider unknown, wslib not installed ?").throw; + } + } + { (sliderType == \round) } + { + \EZRoundSlider.asClass.isNil.if { + SimpleInitError("EZRoundSlider unknown, wslib not installed ?").throw; + } + } + { sliderType == \standard }{ } + { true } + { SimpleInitError("sliderType must be one of \standard, \smooth or \round, "" ++ "" + last both need wslib installed") }; + case + { (sliderMode == \move) } + { + (sliderType == \standard).if { + SimpleInitError("sliderMode \move only with EZSmoothSlider or EZRoundSlider (wslib)").throw; + } + } + + { sliderMode == \jump }{ } + { true } + { SimpleInitError("sliderType must be one of \jump or \move, "" ++ "" + latter needs wslib installed") }; + + varCtrColorPairs = varCtrNum.miSC_colorDeviationPairs(varColorGroups); + synthCtrColorPairs = synthCtrNum.miSC_colorDeviationPairs(synthColorGroups); + + maxColumnNum = ((maxWindowWidth - leftMargin - rightMargin + columnIndent) / (minSliderWidth + labelWidth + gap)).floor; + tryColumnNum = min(tryColumnNum, maxColumnNum).asInteger; + + possibleColumnBreakIndices = this.possibleColumnBreakIndices( + sliderPriority, allowSynthsBreak, allowVarsBreak, allowSynthBreak, allowArrayBreak, allowEnvirBreak, minPartitionSize); + columnBreakIndices = (possibleColumnBreakIndices.size <= (tryColumnNum - 1)).if { + possibleColumnBreakIndices; + }{ + (tryColumnNum == 1).if { [] }{ (varCtrNum + synthCtrNum).miSC_eqPart(possibleColumnBreakIndices, tryColumnNum) }; + }; + columnNum = (synthCtrNum + varCtrNum == 0).if { 0 }{ columnBreakIndices.size + 1 }; + + sliderWidth !? { maxSliderWidth = sliderWidth; minSliderWidth = sliderWidth; }; + effectiveSliderWidth = this.effectiveSliderWidth(columnNum, maxColumnNum, minSliderWidth, maxSliderWidth); + + colorsLo = colorsLo ?? { greyMode.if { greyModeColorsLoDefault }{ colorsLoDefault } }; + colorsHi = colorsHi ?? { greyMode.if { greyModeColorsHiDefault }{ colorsHiDefault } }; + + + ctrButtonGreyCenter = ctrButtonGreyCenter ?? { greyMode.if { greyModeCtrButtonGreyCenterDefault }{ ctrButtonGreyCenterDefault } }; + ctrButtonGreyDev = ctrButtonGreyDev ?? { + greyMode.if { greyModeCtrButtonGreyDevDefault }{ ctrButtonGreyDevDefault } + }; + + varColorNum = (this.varCtrColorPairs.collect(_[0]).maxItem ?? - 1) + 1; + synthColorNum = (this.synthCtrColorPairs.collect(_[0]).maxItem ?? - 1) + 1; + colorNum = varColorNum + synthColorNum + 2; + + totalWidth = effectiveSliderWidth * columnNum + ((columnNum - 1) * columnIndent) + leftMargin + rightMargin + 3 + 500; + + tempColors = colorNum.miSC_distinctColors(colorsLo, colorsHi, colorsLo, colorsHi, colorsLo, colorsHi, colorAreaUsage, greyMode); + + windowName = this.windowName(name, sliderPriority, playerPriority); + window = GUI.window.new(windowName, Rect(rrand(xPosMin, xPosMax), rrand(yPosMin, yPosMax), totalWidth, 80), resizable: false).front; + + window.onClose_({ + #window, playerSection, colors, varCtrColorPairs, varSliderDict, synthSliderDict, + synthCtrColorPairs, synthCtrSliders, varCtrSliders, synthNameBoxes = Array.newClear(10); + }); + + + cview = GUI.compositeView.new(window, Rect(0, 0, totalWidth, 80)); + cview.decorator = FlowLayout(cview.bounds, leftMargin @ upperMargin, gap @ gap); + + // build slider section + + sliderSectionHeight = this.placeSliders(sliderPriority, sliderType, cview, columnBreakIndices, + effectiveSliderWidth, sliderHeight, labelWidth, numberWidth, synthNumWidth, tempColors, numColorExp, + columnIndent, gap, leftMargin, varColorNum, synthColorNum, colorDeviation, greyMode, fontColor); + + heightK = (maxSliderFontHeight - minSliderFontHeight) / (maxFontSliderHeight - minFontSliderHeight); + sliderFontHeight = sliderFontHeight ?? case + { sliderHeight < minFontSliderHeight }{ minSliderFontHeight } + { sliderHeight > maxFontSliderHeight }{ maxSliderFontHeight } + { true } { (sliderHeight - minFontSliderHeight) * heightK + minSliderFontHeight }; + cview.decorator.reset; + cview.decorator.left_((effectiveSliderWidth + columnIndent) * columnNum + leftMargin); + cview.decorator.shift(y: gap); + + pviewBounds = Rect(0, 0, 450, 1000); + pview = CompositeView(cview, pviewBounds); + pview.decorator = FlowLayout(pviewBounds, 0 @ 0); + pview.background_(Color.new255(0, 150, 150, 0)); + + // build player section + + playerSection = try { VarGuiPlayerSection.new(this, pview, playerHeight, streamPlayerGroups, synthPlayerGroups, comboPlayerGroups, + ctrButtonGreyCenter, ctrButtonGreyDev, fontColor, playerPriority); + }{ + |error| + { window.close }.defer(0.5); + error.throw; + }; + + totalHeight = max(sliderSectionHeight, playerSection.totalHeight) + lowerMargin + upperMargin; + totalWidth = effectiveSliderWidth * columnNum + (columnNum * columnIndent) + playerSection.totalWidth + leftMargin + rightMargin + 3; + + cview.bounds = Rect(0, 0, totalWidth, totalHeight); + + cview.background = tempColors[colorNum - [1,2].choose]; + // if Gradient will be reimplemented in qt: + // Gradient(tempColors[colorNum - 1], tempColors[colorNum - 2], \v, colorSteps); + + window.bounds = Rect(rrand(xPosMin, xPosMax), rrand(yPosMin, yPosMax), totalWidth, totalHeight); + this.font(font, sliderFontHeight, playerFontHeight); + + [varCtrSliders, synthCtrSliders].do { |sliders| + sliders.do { |ez| + ez.miSC_adaptToControlStep(precisionSpan, stepEqualZeroDiv).miSC_mode_(sliderMode); + } + }; + + this.initSynthSliderDict; + this.initVarSliderDict; + + updateNow.if { this.update }; + ^this + } + + update { + varCtrSliders.do({|item| item.action.value(item) }); + synthCtrSliders.do({|item| item.action.value(item) }); + ^this + } + + font {|font = "Helvetica", sliderFontHeight = 16, playerFontHeight = 12| + var x; + varCtrSliders.do({|item| + item.labelView.font_(Font(font, sliderFontHeight)); + item.numberView.font_(Font(font, sliderFontHeight)); + }); + synthCtrSliders.do({|item| + item.labelView.font_(Font(font, sliderFontHeight)); + item.numberView.font_(Font(font, sliderFontHeight)); + }); + synthNameBoxes.do({|item| + item.font_(Font(font, sliderFontHeight)).refresh; + }); + + (x = playerSection.mouseActionButton) !? { x.font_(Font(font, playerFontHeight)); x.refresh }; + (x = playerSection.envirIndexButton) !? { x.font_(Font(font, playerFontHeight)); x.refresh }; + (x = playerSection.latencyButton) !? { x.font_(Font(font, playerFontHeight)); x.refresh }; + + playerSection.basicNewViews.do(_.font_(Font(font, playerFontHeight))); + (x = playerSection.modeNameView) !? { x.font_(Font(font, playerFontHeight)); x.refresh }; + + playerSection.streamEnvirViews.do(_.font_(Font(font, playerFontHeight))); + playerSection.streamTypeTexts.do(_.font_(Font(font, playerFontHeight))); + playerSection.synthRateTexts.do(_.font_(Font(font, playerFontHeight))); + + playerSection.optionViews.do(_.font_(Font(font, playerFontHeight))); + playerSection.optionViewTexts.do(_.font_(Font(font, playerFontHeight))); + + (x = playerSection.saveButton) !? { x.font_(Font(font, playerFontHeight)); x.refresh }; + (x = playerSection.updateButton) !? { x.font_(Font(font, playerFontHeight)); x.refresh }; + (x = playerSection.stopButton) !? { x.font_(Font(font, playerFontHeight)); x.refresh }; + + ^this + } + + + windowName {|name, sliderPriority, playerPriority| + var a,b; + ^(name ?? { + a = case + { (varCtrNum != 0) && (synthCtrNum != 0) } + { (sliderPriority == \var).if { "variable and synth control" }{ "synth and variable control" } } + { (varCtrNum == 0) && (synthCtrNum != 0) } { "synth control" } + { (varCtrNum != 0) && (synthCtrNum == 0) } { "variable control" } + { (varCtrNum == 0) && (synthCtrNum == 0) } { "no control" }; + b = case + { (streamPlayerNum != 0) && (synthPlayerNum != 0) } + { (playerPriority == \stream).if { "stream and synth players" }{ "synth and stream players" } } + { (streamPlayerNum == 0) && (synthPlayerNum != 0) } + { "synth player" ++ ((synthPlayerNum != 1).if { "s" }{ "" }) } + { (streamPlayerNum != 0) && (synthPlayerNum == 0) } + { "stream player" ++ ((streamPlayerNum != 1).if { "s" }{ "" }) } + { (streamPlayerNum == 0) && (synthPlayerNum == 0) } { "no players" }; + a ++ " | " ++ b; + }).asString; + } + + initVarCtrColorGroups { // logical variable control color grouping + var j = 0, col, k; + col = Array.newClear((varEnvirIndices.size != 0).if { + varEnvirIndices.maxItem + 1 + }{ + 0 + }); + + varCtr.do {|x,i| + i.odd.if { + k = varEnvirIndices[i.div(2)]; + x[0].isSequenceableCollection.if { + col[k] = col[k].add(x.collect {|y| j = j + 1; j - 1 }); + }{ + col[k] = col[k].add(j); + j = j + 1; + } + } + }; + // special case there could be nils if some synths without control + ^col.reject { |x| x.isNil } + } + + initSynthCtrColorGroups { // logical synth control color grouping + var j = 0, col, k; + + col = Array.newClear((synthCtrSynthIndices.size != 0).if { + synthCtrSynthIndices.maxItem + 1 + }{ + 0 + }); + + synthCtr.do {|x,i| + i.odd.if { + k = synthCtrSynthIndices[i.div(2)]; + x[0].isSequenceableCollection.if { + col[k] = col[k].add(x.collect {|y| j = j + 1; j - 1 }); + }{ + col[k] = col[k].add(j); + j = j + 1; + } + } + }; + // special case there could be nils if some synths without control + ^col.reject { |x| x.isNil } + } + + initSynthSliderDict { + var dict = MultiLevelIdentityDictionary.new; + var synthNum = this.synthCtrSynthIndices.asSet, relSynthCtrIndices, + sliderIndex = 0, key, size; + synthNum.do { |synthIndex| + relSynthCtrIndices = this.synthCtrSynthIndices.findAll([synthIndex]); + relSynthCtrIndices.do { |i| + key = synthCtr[i * 2]; + size = synthCtrSizes[i]; + dict.put(synthIndex, key, \size, size); + dict.put(synthIndex, key, \sliderIndex, sliderIndex); + sliderIndex = sliderIndex + size; + } + }; + this.synthSliderDict = dict; + } + + initVarSliderDict { + var dict = MultiLevelIdentityDictionary.new; + var sliderIndex = 0, size; + varCtr.pairsDo { |key, spec, i| + + size = varCtrSizes[i.div(2)]; + dict[key, \varOccurrences].isNil.if { + dict.put(key, \varNum, 0, \sliderIndex, sliderIndex); + dict.put(key, \varNum, 0, \size, size); + dict.put(key, \varOccurrences, 1); + }{ + dict.put(key, \varNum, dict[key, \varOccurrences], \sliderIndex, sliderIndex); + dict.put(key, \varNum, dict[key, \varOccurrences], \size, size); + dict.put(key, \varOccurrences, dict[key, \varOccurrences] + 1); + }; + sliderIndex = sliderIndex + size; + }; + this.varSliderDict = dict; + } +} + diff --git a/Classes/VarGui/VarGuiPlayerSection.sc b/Classes/VarGui/VarGuiPlayerSection.sc new file mode 100755 index 0000000..5d9d548 --- /dev/null +++ b/Classes/VarGui/VarGuiPlayerSection.sc @@ -0,0 +1,1193 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +VarGuiPlayerSection { + var <>varGui, <>streamPlayerGroups, <>synthPlayerGroups, <>comboPlayerGroups, synths, ctrButtonGreyCenter, <>ctrButtonGreyDev, <>playerPriority, placeSynthPlayers, placeStreamPlayers, + iconViewGroup, makeIconViewGroup, = 10).if { " e" }{ " e " } ++ + (varGui.streamEnvirIndices[i]).asString).background_(liteGreyBackgroundColor).stringColor_(fontColor); + + streamViewGroups[i] = Array.fill(3, { CompositeView(playerView, rectOut) }); + streamViewGroups[i].do(_.background = liteGreyBackgroundColor); + + streamPlayActions[i] = { |b| + ([-1,1,3].includes(streamStates[i])).if { + varGui.streams[i].play(varGui.clocks[i], quant: varGui.quants[i]); + } + }; + streamPauseActions[i] = { |b| + ([-1,0].includes(streamStates[i])).if { + varGui.streams[i].pause; + } + }; + streamResetActions[i] = { |b| + + varGui.streams[i].reset; + // reset type play + + (streamResetModeButtons[i].value == 0).if { + [-1,1,2,3].includes(streamStates[i]).if { + varGui.streams[i].play(varGui.clocks[i], quant: varGui.quants[i]); + }{ + { this.playerGuiAction(streamViewGroups[i], 2, \thisHi); }.defer; + { this.playerGuiAction(streamViewGroups[i], 2, \thisLo); }.defer(0.1); + }; + }{ // reset type pause + [0,1,2].includes(streamStates[i]).if { + + (streamStates[i] != 0).if { + streamStates[i] = 3; + { this.playerGuiAction(streamViewGroups[i], 1, \thisResetOtherLo); }.defer; + }{ + streamStates[i] = 3; + varGui.streams[i].pause; + }; + }; + }; + }; + + streamButtonGroups[i] = [ + Button(streamViewGroups[i][0], rectIn).states_([["", fontColor, playPlayerButtonColor]]) + .action_({ |b,mod| varGui.server.listSendBundle(latencies[latencyIndex], + this.dispatchCollect(mouseDownOn.not, mod, synthPlayActions, streamPlayActions, \stream, i)) }) + .mouseDownAction_({ |b,x,y,mod| varGui.server.listSendBundle(latencies[latencyIndex], + this.dispatchCollect(mouseDownOn, mod, synthPlayActions, streamPlayActions, \stream, i)) }), + Button(streamViewGroups[i][1], rectIn).states_([["", fontColor, pausePlayerButtonColor]]) + .action_({ |b,mod| varGui.server.listSendBundle(latencies[latencyIndex], + this.dispatchCollect(mouseDownOn.not, mod, synthPauseActions, streamPauseActions, \stream, i)) }) + .mouseDownAction_({ |b,x,y,mod| varGui.server.listSendBundle(latencies[latencyIndex], + this.dispatchCollect(mouseDownOn, mod, synthPauseActions, streamPauseActions, \stream, i)) }), + Button(streamViewGroups[i][2], rectIn).states_([["", fontColor, resetPlayerButtonColor]]) + .action_({ |b,mod| varGui.server.listSendBundle(latencies[latencyIndex], + this.dispatchCollect(mouseDownOn.not, mod, synthStopActions, streamResetActions, \stream, i, 1, true)) }) + .mouseDownAction_({ |b,x,y,mod| varGui.server.listSendBundle(latencies[latencyIndex], + this.dispatchCollect(mouseDownOn, mod, synthStopActions, streamResetActions, \stream, i, 1, true)) }), + ]; + + playerView.decorator.shift(x: columnGroupGap); + + streamResetModeViews[i] = CompositeView(playerView, modeRectOut); + + streamResetModeActions[i] = { |b| + b.value.switch( + 0, { streamResetModeViews[i].background = playBackgroundColor }, + 1, { streamResetModeViews[i].background = pauseBackgroundColor } + ); + }; + + { + var v = 0; + streamResetModeButtons[i] = + Button(streamResetModeViews[i], modeRectIn).states_([["", fontColor, modeButtonColor], ["", fontColor, modeButtonColor]]) + .action_({|b| this.dispatchPerform(streamResetModeButtons, modifier, streamResetModeActions, \streamReset, \stream, i); }) + // qt small button workaround + .mouseDownAction_({|b,x,y,m| modifier = m; v = 1-v; b.valueAction_(v) }); + }.(); + + [1,0].do {|x| streamResetModeButtons[i].valueAction_(x) }; + + playerView.decorator.shift(x: columnGroupGap + modeRectOut.width + decoratorGapX); + streamTypeTexts[i] = StaticText(playerView, smallRectOut).string_(varGui.streams[i].isKindOf(Task).if { "task" }{ "esp" }) + .background_(liteGreyBackgroundColor).stringColor_(fontColor).align_(\center); + + playerView.decorator.nextLine; + + streamSimpleControllers[i] = SimpleController(x).put(\userPlayed, {|n| + ([-1,1,2,3].includes(streamStates[i])).if { + { this.playerGuiAction(streamViewGroups[i], 0, \thisHiOtherLo); }.defer; + streamStates[i] = 0; + }; + }).put(\userStopped, {|n| + ([0,3].includes(streamStates[i])).if { + { + (streamStates[i] == 3).if { + this.playerGuiAction(streamViewGroups[i], 1, \thisResetOtherLo); + }{ + this.playerGuiAction(streamViewGroups[i], 1, \thisHiOtherLo); + }; + }.defer; + (streamStates[i] != 3).if { streamStates[i] = 1 } + }; + }).put(\stopped, {|n| + varGui.streams[i].streamHasEnded.if { + { this.playerGuiAction(streamViewGroups[i], 2, \thisStopOtherLo); }.defer; + streamStates[i] = 2; + }{ + (streamStates[i] != 3).if { streamStates[i] = 1 } + } + // e.g. ESP started by quant ... + }).put(\playing, {|n| + { this.playerGuiAction(streamViewGroups[i], 0, \thisHiOtherLo); }.defer; + streamStates[i] = 0; + }); + + + streamStates[i] = case // init EventStreamPlayer and Task states + { varGui.streams[i].streamHasEnded }{ { this.playerGuiAction(streamViewGroups[i], 2, \thisStopOtherLo); }.defer; 2 } + { varGui.streams[i].miSC_getIsWaiting } + { { this.playerGuiAction(streamViewGroups[i], 1, \thisPlayOtherLo); }.defer; 1 } + { varGui.streams[i].wasStopped } + { { this.playerGuiAction(streamViewGroups[i], 1, \thisHiOtherLo); }.defer; 1 } + { true }{ { this.playerGuiAction(streamViewGroups[i], 0, \thisHiOtherLo); }.defer; 0 }; + + }); + }; + + makeIconViewGroup = { + playerView.decorator.shift(x: basicNewRect.width + decoratorGapX); + iconViewGroup = Array.fill(3, { UserView(playerView, rectOut).background_(liteGreyBackgroundColor) }); + playerView.decorator.shift(x: columnGroupGap); + modeNameView = StaticText(playerView, modeNameRect).string_( + ((varGui.synthPlayerNum != 0) && (varGui.streamPlayerNum == 0) && (synthRenewModeStates.every(_==false))).if { + "no modes" // + }{ + "modes" + }).background_(liteGreyBackgroundColor).stringColor_(fontColor).align_(\center); + }; + + + (playerPriority == \synth).if { + placeSynthPlayers.(); + (varGui.synths.size != 0).if { + 1.do { playerView.decorator.nextLine }; + makeIconViewGroup.(); + 1.do { playerView.decorator.nextLine }; + + }; + (varGui.streams.size != 0).if { + (varGui.synths.size != 0).if { playerView.decorator.nextLine }; + placeStreamPlayers.(); + (varGui.synths.size == 0).if { + playerView.decorator.nextLine; + makeIconViewGroup.(); + }; + }; + }{ + placeStreamPlayers.(); + (varGui.streams.size != 0).if { + 1.do { playerView.decorator.nextLine }; + makeIconViewGroup.(); + 1.do { playerView.decorator.nextLine }; + }; + (varGui.synths.size != 0).if { + (varGui.streams.size != 0).if { playerView.decorator.nextLine }; + placeSynthPlayers.(); + (varGui.streams.size == 0).if { + playerView.decorator.nextLine; + makeIconViewGroup.(); + }; + }; + }; + + ((varGui.streamPlayerNum + varGui.synthPlayerNum) > 0).if { this.drawPlayers }; + + // cleanup + + stopByCmdPeriodActions.add({ { varGui.streams.do({|x,i| this.playerGuiAction(streamViewGroups[i], 1, \thisHiOtherLo); }) }.defer; }); + ((varGui.synthPlayerNum + varGui.streamPlayerNum) > 0).if { + stopByCmdPeriodActions.add({ + stopButton.doAction; + }) + }; + stopByCmdPeriodActions.do(CmdPeriod.add(_)); + + + playerView.onClose_({ + stopByCmdPeriodActions.do(CmdPeriod.remove(_)); + { + synthResponders.do({ |x| x.do { |y| y.disable; y.remove } }); + streamSimpleControllers.do(_.remove); + }.defer(0.2); + }); + + ((varGui.streamPlayerNum + varGui.synthPlayerNum) > 0).if { playerView.decorator.shift(y: optionViewsGapTop) }; + + // option section + + latencies = [0.1, varGui.server.latency, nil]; + latencyIndex = 0; + + optionViewMakers = [{ + mouseActionButton = Button(playerView, smallRectOut) + .states_([["on", fontColor, optionHiColor], ["off", fontColor, optionLoColor]]) + .action_({ |b| mouseDownOn = b.value.switch(0, { true }, 1, { false }) }).value_(1).valueAction_(0); + },{ + var v = 0; + envirIndexButton = Button(playerView, smallRectOut) + .states_([["on", fontColor, optionHiColor], ["off", fontColor, optionLoColor]]) + .action_({ |b| showEnvirIndex = b.value.switch( + 0, { varGui.refreshVarCtrSliderLabels(true); true }, + 1, { varGui.refreshVarCtrSliderLabels(false); false } + ) }) + .mouseDownAction_({ |b| v = 1 - v; b.valueAction_(v) }) // qt small button workaround + .value_((varGui.envirs.size > 1).if { 1 }{ 0 }).valueAction_((varGui.envirs.size > 1).if { 0 }{ 1 }); + },{ + var v = 1; + latencyButton = Button(playerView, smallRectOut) + .states_([["c", fontColor, optionHiColor], ["s", fontColor, optionHiColor], ["nil", fontColor, optionLoColor]]) + .action_({ |b| + b.value.switch( + 0, { latencyIndex = 0; + customLatencyBox.background_(optionHiColor); + serverLatencyBox.background_(optionLoColor); }, + 1, { latencyIndex = 1; + customLatencyBox.background_(optionLoColor); + serverLatencyBox.background_(optionHiColor); }, + 2, { latencyIndex = 2; + customLatencyBox.background_(optionLoColor); + serverLatencyBox.background_(optionLoColor); } + ) + }) + .mouseDownAction_({ |b| v = v + 1 % 3; b.valueAction_(v) }) // qt small button workaround + .value_(2); + },{ + customLatencyBox = NumberBox(playerView, smallRectOut).clipLo_(0.03).clipHi_(2).value_(0.1) + .background_(optionLoColor).stringColor_(fontColor).normalColor_(fontColor); + customLatencyBox.scroll_step = 0.01; + customLatencyBox.action_({|n| latencies[0] = n.value; }); + },{ + serverLatencyBox = NumberBox(playerView, smallRectOut).clipLo_(0.0).clipHi_(2) + .background_(optionLoColor) + .stringColor_(fontColor) + .normalColor_(fontColor) + .value_(serverLatency ?? { varGui.server.latency }); + serverLatencyBox.scroll_step = 0.01; + serverLatencyBox.action_({|n| varGui.server.latency = n.value; latencies[1] = n.value; }); + }]; + + 5.do {|i| + var str; + str = i.switch( + 0, { " player action by mouse down " }, + 1, { " show envir index in vars' name fields " }, + 2, { " bundle latency (synth players)" }, + 3, { " custom bundle latency" }, + 4, { " server latency (esplayer players)" } + ); + + (((i == 1) && (varGui.varCtrNum != 0)) || + ((i == 2) && (varGui.synthPlayerNum != 0)) || + ((i == 3) && (varGui.synthPlayerNum != 0)) || + ((i == 4) && (varGui.synthPlayerNum + varGui.streamPlayerNum != 0))).if { + optionViewTexts = optionViewTexts.add(StaticText(playerView, Rect(0, 0, optionViewWidth, fieldHeight)) + .string_(str).background_(liteGreyBackgroundColor).stringColor_(fontColor)); + playerView.decorator.shift(x: columnGroupGap); + optionViews = optionViews.add(optionViewMakers[i].()); + playerView.decorator.nextLine; + optionViewNum = optionViewNum + 1; + }; + }; + + totalHeight = totalHeight + optionViewsGapTop + (optionViewNum * fieldHeight) + + ((optionViewNum + 1) * decoratorGapX) + optionViewsGapBottom + globalButtonHeight; + + + playerView.decorator.shift(y: optionViewsGapBottom); + + globalButtonWidth = columnGroupGap + modeRectOut.width + decoratorGapX + 20; + globalButtonGap = (totalWidth - (3 * globalButtonWidth) - (2 * decoratorGapX)) / 2; + + ((varGui.synthCtrNum + varGui.varCtrNum) > 0).if { + updateButton = Button(playerView, globalButtonWidth @ globalButtonHeight); + updateButton.states = [["update", fontColor, updateButtonColor]]; + updateButton.action = { this.varGui.update }; + }{ + playerView.decorator.shift(globalButtonWidth + decoratorGapX) + }; + + playerView.decorator.shift(globalButtonGap); + + ((varGui.synthCtrNum + varGui.varCtrNum) > 0).if { + saveButton = Button(playerView, globalButtonWidth @ globalButtonHeight); + saveButton.states = [["save as", fontColor, saveButtonColor]]; + saveButton.action = { + Dialog.savePanel({ arg path; + File(path, "w").write(varGui.saveData.asCompileString).close; + },{ }) + } + }{ + playerView.decorator.shift(globalButtonWidth + decoratorGapX) + }; + + playerView.decorator.shift(globalButtonGap); + + ((varGui.synthPlayerNum + varGui.streamPlayerNum) > 0).if { + stopButton = Button(playerView, globalButtonWidth @ globalButtonHeight); + stopButton.states = [["stop", fontColor, stopButtonColor]]; + stopButton.action = { + varGui.streams.do {|item| /*item.free;*/ item.stop; }; + varGui.synthIDs.do {|item, i| + synthActionStates[i] = 3; + + parentHelpSynths[i].isNil.if { + (synthStates[i] != 2).if { + (synthStates[i] == -1).if { + // basic new + this.updateSynth(i); + varGui.server.listSendBundle(nil, [synths[i].newMsg, synths[i].miSC_freeMsg]); + }{ + varGui.server.sendMsg("/n_free", item.asNodeID); + } + } + }{ + // synth derived from HS + synths[i].isPlaying.if { varGui.server.sendBundle(nil, synths[i].miSC_runMsg(false)) } + } + } + } + }; + ^this + } + + playerGuiAction {|viewGroup, j, type| + type.switch( + \allLo, { 3.do({|x| (viewGroup[x]).background = buttonLoColor}); }, + \thisHiOtherLo, { viewGroup[j].background = buttonHiColor; + Set[0,1,2].remove(j).do({|x| (viewGroup[x]).background = buttonLoColor}); }, + \thisResetOtherLo, { viewGroup[j].background = resetBackgroundColor; + Set[0,1,2].remove(j).do({|x| (viewGroup[x]).background = buttonLoColor}); }, + \thisPlayOtherLo, { viewGroup[j].background = playBackgroundColor; + Set[0,1,2].remove(j).do({|x| (viewGroup[x]).background = buttonLoColor}); }, + \thisHi, { viewGroup[j].background = buttonHiColor; }, + \thisLo, { viewGroup[j].background = buttonLoColor; }, + \thisStopOtherLo, { viewGroup[j].background = stopBackgroundColor; + Set[0,1,2].remove(j).do({|x| (viewGroup[x]).background = buttonLoColor}); } + ); + ^this + } + + + dispatchPerform { |buttons, mod, actions, mode, reg, i| + var num, group, isInSameGroup, isOfSameState; + (mod.isInteger and: { mod.isShift }).if { + num = actions.size; + group = ((reg == \synth).if { synthPlayerGroups }{ streamPlayerGroups }).detect(_.includes(i)); + num.collect { |j| + isInSameGroup = group.includes(i); + ((mod.isAlt && isInSameGroup) || (mod.isAlt.not)).if { + ((mode == \synthRenew) && // exclude cases when renew mode button greyed by stop mode + ((synthStopModeButtons[j].value == 0) || + ((synthStopModeButtons[j].value != 0) && (synthStopModeButtons[i].value == 0)) + ) + ).not.if { + // no action if synth from HS + ((reg == \stream) or: { parentHelpSynths[j].isNil }).if { + (j != i).if { buttons[j].value = buttons[i].value; }; + actions[j].(buttons[i], mod); + } + }{ + (j == i).if { buttons[j].value = buttons[j].value - 1 % 3; }; + }; + }; + }; + + }{ + actions[i].(buttons[i], mod); + }; + ^this + } + + dispatchCollect { |doIt, mod, synthActions, streamActions, reg, i, flattenNum = 1, stopButtonAction = false| + var num, group, actions, isInSameGroup, isOfSameState, synthState, comboOffset, firstActions, secondActions, + synthStopModeState, synthRenewModeState, streamState, streamResetModeState; + + ^doIt.if { + actions = (reg == \synth).if { synthActions }{ streamActions }; + (mod.isInteger and: { mod.isShift }).if { + (mod.miSC_isPseudoCaps).if { + comboOffset = (reg == \synth).if { + (playerPriority == \synth).if { 0 }{ varGui.streamPlayerNum }; + }{ + (playerPriority == \synth).if { varGui.synthPlayerNum }{ 0 } + }; + (playerPriority == \synth).if { + firstActions = synthActions; + secondActions = streamActions; + }{ + firstActions = streamActions; + secondActions = synthActions; + }; + + ((mod.isAlt).if { comboPlayerGroups.detect(_.includes(i + comboOffset)) } { synthActions.size + streamActions.size }) + .collect { |j| + // don't stop synth from HS indirectly with shift + caps + (j >= firstActions.size).if { + (((playerPriority == \synth) || stopButtonAction.not) or: + { parentHelpSynths[j - firstActions.size].isNil }).if { + secondActions[j - firstActions.size].(); + } + }{ + (((playerPriority == \stream) || stopButtonAction.not) or: { parentHelpSynths[j].isNil }).if { + firstActions[j].(); + } + } + }.flatten(flattenNum); + }{ + num = actions.size; + group = ((reg == \synth).if { synthPlayerGroups }{ streamPlayerGroups }).detect(_.includes(i)); + + synthState = synthStates[i]; + synthStopModeState = synthStopModeButtons[i].(); + synthRenewModeState = synthRenewModeButtons[i].(); + streamState = streamStates[i]; + streamResetModeState = streamResetModeButtons[i].(); + + num.collect { |j| + isInSameGroup = group.includes(j); + + isOfSameState = (reg == \synth).if { + ((synthStates[j] == synthState) && + (((synthStopModeState == 0) && (synthStopModeButtons[j].() == 0)) || + ((synthStopModeState == synthStopModeButtons[j].()) && + (synthRenewModeState == synthRenewModeButtons[j].())))) + }{ + ((streamStates[j] == streamState) && (streamResetModeButtons[j].() == streamResetModeState)) + }; + // don't stop synth from HS indirectly with shift + // lookup parentHelpSynths only if reg = \synth + ( ((reg == \stream) || stopButtonAction.not) or: { parentHelpSynths[j].isNil } ).if { + case + { mod.isCtrl && mod.isAlt } + { (isInSameGroup && isOfSameState).if { actions[j].(); } } + { mod.isCtrl } + { isOfSameState.if { actions[j].(); } } + { mod.isAlt } + { isInSameGroup.if { actions[j].(); } } + { true } + { actions[j].(); } + }; + }.flatten(flattenNum); + } + }{ + actions[i].(); + }; + }; + } + +} + + diff --git a/Classes/VarGui/extBoolean.sc b/Classes/VarGui/extBoolean.sc new file mode 100644 index 0000000..928b1a7 --- /dev/null +++ b/Classes/VarGui/extBoolean.sc @@ -0,0 +1,43 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + + ++Boolean { + + miSC_dispatchCollect { |doGrouping, funcs, i, groups, flattenNum = 1| + var groupIndex; + ^this.if { + doGrouping.if { + groupIndex = groups.detectIndex { |g| g.includes(i) }; + (groups[groupIndex] + .collect { |j| funcs[j].value }).flatten(flattenNum); + }{ + funcs[i].value; + }; + }; + } +} + + diff --git a/Classes/VarGui/extCollection.sc b/Classes/VarGui/extCollection.sc new file mode 100644 index 0000000..5d12ceb --- /dev/null +++ b/Classes/VarGui/extCollection.sc @@ -0,0 +1,49 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + + ++Collection { + + miSC_enumerateIndices { + (this.size == 1).if { + ^(0..(this[0]-1)); + }{ + ^[(0..(this[0]-1)), this.copyToEnd(1).miSC_enumerateIndices] + .allTuples.collect(_.flatten) + } + } + + miSC_isGrouping {|n| + var f; + this.every({|x| x.isKindOf(Collection) and: { x.every({|y| y.isInteger})} }).not.if { ^false }; + f = this.asSequenceableCollection.collect(_.asSequenceableCollection).flatten; + ((f.size == n) and: { f.every({|x| (x>=0) && (x= 0 and <= 1, deviation > 0 and <= 1 + var a = deviation.linlin(0, 1, greyCenter!3, [this.red, this.green, this.blue]) ++ [this.alpha]; + ^Color(*a); + } + + miSC_dim { |ctrButtonGreyCenter, ctrButtonGreyDev| + ^this.miSC_iplToGrey(ctrButtonGreyCenter, ctrButtonGreyDev) + } + +} + + diff --git a/Classes/VarGui/extControlSpec.sc b/Classes/VarGui/extControlSpec.sc new file mode 100644 index 0000000..10497aa --- /dev/null +++ b/Classes/VarGui/extControlSpec.sc @@ -0,0 +1,39 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++ ControlSpec { + + // redundant naming as method also applied to Symbols refering to specs + miSC_specAsArray { |synthName, metaKey = \specs, useGlobalSpecs = true| + var s, i, typeString, indices; + s = this.asCompileString; + i = s.findAll("'"); + typeString = s[(i[0]+1)..(i[1]-1)]; + ^[this.minval, this.maxval, typeString.asSymbol, this.step, this.default] + } +} + + + diff --git a/Classes/VarGui/extDictionary.sc b/Classes/VarGui/extDictionary.sc new file mode 100644 index 0000000..327d128 --- /dev/null +++ b/Classes/VarGui/extDictionary.sc @@ -0,0 +1,30 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Dictionary { + miSC_maybePutPairs { arg ...args; + args.pairsDo { |key, val| this.at(key).isNil.if { this.put(key, val) } }; + ^this + } +} diff --git a/Classes/VarGui/extEZSlider.sc b/Classes/VarGui/extEZSlider.sc new file mode 100644 index 0000000..e6466b3 --- /dev/null +++ b/Classes/VarGui/extEZSlider.sc @@ -0,0 +1,84 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++EZSlider { + + miSC_colorize { |color, numColorExp = 0.4, fontColor| + var light = color.miSC_exp(numColorExp), backGroundColor; + backGroundColor = (\EZSmoothSlider.asClass.notNil and: + { this.isMemberOf(EZSmoothSlider) }).if { + this.sliderView.hiliteColor_(color); + light + }{ + color + }; + this.setColors(stringBackground: light, sliderBackground: backGroundColor, + numBackground: light, stringColor: fontColor, + numStringColor: fontColor, numNormalColor: fontColor); + ^this + } + + // number of indicated digits derived from controlSpec step + + miSC_adaptToControlStep { |precision = 10, stepEqualZeroDiv = 100| + var x, stepString, range, estimatedControlStep; + + estimatedControlStep = (controlSpec.step.abs < (10 ** precision.neg)).if { + (controlSpec.clipHi - controlSpec.clipLo) / stepEqualZeroDiv + }{ + controlSpec.step + }; + stepString = estimatedControlStep.miSC_decimalStrings(precision); + this.round = 10 ** stepString.last.size.neg; + range = controlSpec.constrain(controlSpec.clipHi) - + controlSpec.constrain(controlSpec.clipLo); + + (range < (10 ** precision.neg)).if { + // dummy controlspec should look inactive everywhere + this.sliderView.enabled = false; + this.numberView.enabled = false; + this.isMemberOf(EZSlider).if { + this.sliderView.thumbSize = 0; + } + }{ + sliderView.step = 1 / (range / estimatedControlStep).round; + }; + this.isMemberOf(EZSlider).if { + numberView.maxDecimals = stepString.last.size + }; + // take modifiers for multiple slider handling + sliderView.alt_scale_(1); + sliderView.ctrl_scale_(1); + sliderView.shift_scale_(1); + numberView.alt_scale_(1); + numberView.ctrl_scale_(1); + numberView.shift_scale_(1); + ^this + } + + miSC_mode_ { |type| + // subclasses EZSmoothSlider, EZRoundSlider may not be defined + // so distinguish here ... + ^this.isMemberOf(EZSlider).if { this }{ this.sliderView.perform(\mode_, type); this } } +} diff --git a/Classes/VarGui/extFloat.sc b/Classes/VarGui/extFloat.sc new file mode 100644 index 0000000..a1d106c --- /dev/null +++ b/Classes/VarGui/extFloat.sc @@ -0,0 +1,77 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + + ++Float { + miSC_decimalStrings { |precision = 8| + var precString = this.asStringPrec(precision), prePointString, postPointString, + signString, manString, expString, posOfPoint, shift; + + #manString, expString = precString.split($e); + (manString[0] == $-).if { + manString = manString.drop(1); + signString = "-"; + }; + + expString.isNil.if { + // no exponent + posOfPoint = precString.find("."); + posOfPoint.isNil.if { + prePointString = precString; + }{ + prePointString = precString.copyFromStart(posOfPoint-1); + postPointString = precString.copyToEnd(posOfPoint+1); + } + }{ + // with exponent + shift = expString.drop(1).asInteger; + (expString[0] == $+).if { + (manString.size == 1).if { + // no comma + prePointString = manString ++ ($0!shift).join; + }{ + (shift < (manString.size-2)).if { + prePointString = manString[0].asString ++ manString.copyRange(2, shift+1); + postPointString = manString.copyToEnd(shift+1); + }{ + prePointString = manString[0].asString ++ manString.copyToEnd(2) ++ + ($0 ! (shift - (manString.size-2))).join; } + } + }{ + prePointString = "0"; + (manString.size == 1).if { + // no comma + postPointString = ($0!(shift-1)).join ++ manString; + }{ + postPointString = ($0!(shift-1)).join ++ + manString[0].asString ++ manString.copyToEnd(2); + } + } + }; + ^[signString, prePointString, postPointString] + } + +} + diff --git a/Classes/VarGui/extFunction.sc b/Classes/VarGui/extFunction.sc new file mode 100644 index 0000000..3c0c4ef --- /dev/null +++ b/Classes/VarGui/extFunction.sc @@ -0,0 +1,34 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++ Function { + + // workaround for backwards compatibility + + miSC_performWithEnvir { |selector, envir| + if(selector === \value) { ^this.valueWithEnvir(envir) }; + ^super.miSC_performWithEnvir(selector, envir) + } + +} diff --git a/Classes/VarGui/extFunctionDef.sc b/Classes/VarGui/extFunctionDef.sc new file mode 100644 index 0000000..89e34b5 --- /dev/null +++ b/Classes/VarGui/extFunctionDef.sc @@ -0,0 +1,34 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++ FunctionDef { + + // workaround for backwards compatibility + + miSC_makeEnvirFromArgs { + var argNames, argVals; + argNames = this.argNames; + argVals = this.prototypeFrame.keep(argNames.size); ^().putPairs([argNames, argVals].flop.flatten) + } +} diff --git a/Classes/VarGui/extInteger_1.sc b/Classes/VarGui/extInteger_1.sc new file mode 100644 index 0000000..38477ee --- /dev/null +++ b/Classes/VarGui/extInteger_1.sc @@ -0,0 +1,114 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Integer { + + miSC_groupIndices {|groups| + var i = groups.size; + ^(this.collect({|j| + groups.detectIndex({ |item, k| item.includes(j) }) ?? { i = i + 1; i - 1 }; + })) + } + + miSC_cubeDivision {|dim| // division of unit cube fine enough for given number + var divCeiling = (this ** dim.reciprocal).ceil.asInteger, + div, prod, prod2, i = 0; + div = divCeiling ! dim; + prod = div.product; + prod2 = prod * (div[0] - 1) / div[0]; + while ({ + (i <= (dim-1)) && (prod >= this) && (prod2 >= this) + },{ + div[i] = div[i] - 1; + i = i + 1; + prod = prod2; + prod2 = prod * (div[i] - 1) / div[i]; + }); + ^div; + } + + miSC_selectCubeComponent {|index, areaUsage| + var lo, mid, hi; + mid = (index + 0.5) / this; + ^rrand(mid - (areaUsage / this / 2), mid + (areaUsage / this / 2)); + } + + miSC_distinctCubePoints {|dim, areaUsage = 0.9| // just for handsome integers ! + var div = this.miSC_cubeDivision(dim); + ^div.miSC_enumerateIndices.scramble.copyFromStart(this - 1) + .collect { |x| + x.collect { |y,i| + div[i].miSC_selectCubeComponent(y, areaUsage) + }; + }; + } + + miSC_distinctColors {|redLo = 0.4, redHi = 0.7, greenLo = 0.4, greenHi = 0.7, + blueLo = 0.4, blueHi = 0.7, areaUsage = 0.9, greyMode = false| // just for handsome integers ! + var cubePoints = greyMode.if { (0..this-1).scramble / (this - 1) }{ this.miSC_distinctCubePoints(3, areaUsage) }; + + ^this.collect {|i| + Color(greyMode.if { cubePoints[i] }{ cubePoints[i][0] } * (redHi - redLo) + redLo, + greyMode.if { cubePoints[i] }{ cubePoints[i][1] } * (greenHi - greenLo) + greenLo, + greyMode.if { cubePoints[i] }{ cubePoints[i][2] } * (blueHi - blueLo) + blueLo); + } + } + + + miSC_colorDeviationPairs {|colorGroups| + // expects double nested collection with all indices occuring + // outer groups mean related colors, inner groups same color + var pairs = Array.newClear(this), n; + colorGroups.do {|group,i| + n = group.size; + group.do {|item, j| + item.isNumber.if { + pairs[item] = [i, n - j - 1]; }{ + item.do {|jtem| pairs[jtem] = [i, n - j - 1] }; + }; + }; + }; + ^pairs; + } + + miSC_getCtrIndex { |argName| ^nil } + + + miSC_runMsg { arg flag = true; + ^[12, this, flag.binaryValue]; + } + + miSC_setMsg { arg ... args; + ^[15, this] ++ args.asOSCArgArray; + } + + miSC_setnMsg { arg ... args; + ^[16, this] ++ Node.setnMsgArgs(*args); + } + + miSC_freeMsg { ^[11, this] } + + +} diff --git a/Classes/VarGui/extInteger_2.sc b/Classes/VarGui/extInteger_2.sc new file mode 100644 index 0000000..33c1e10 --- /dev/null +++ b/Classes/VarGui/extInteger_2.sc @@ -0,0 +1,97 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Integer { + + + miSC_partMax { |part| + ^(part.size + 1).collect {|i| + case + { i == 0 }{ part[i] } + { i == part.size }{ this - part[i - 1] } + { true }{ part[i] - part[i - 1] } + }.maxItem; + } + + + miSC_eqPart { |part, k| + // search for quite equal partitions, needed for column break + // part expects ordered list that describes a partition of the integer by indices (ascending numbers > 0, < given integer) + // gives a new partition with k indices + var eqPart, subPart, eqSubPart, newEqSubPart, min, newMin, div, l, m, r, deltaLo, deltaHi, eqIndex; + case + { k == (part.size + 1) } + { eqPart = part } + { k > (part.size + 1) } + { Error("no possible partition").throw; } + { k == 2 } + { + m = part.size; + r = part.detectIndex(_ >= (this/2)); + case + { r.isNil } + { eqPart = [part[m-1]] } + { r == 0 } + { eqPart = [part[0]] } + { true }{ + deltaLo = (this/2) - part[r - 1]; + deltaHi = part[r] - (this/2); + eqPart = (deltaLo >= deltaHi).if { [part[r]] }{ [part[r - 1]] }; + } + } + { true } + { + // search around k-th part, then go on recursively + + l = 0; + div = this/k; + while { + (r.isNil) && (l < (part.size - k)) + }{ + (part[l] >= div).if { r = l }; + l = l + 1; + }; + + ((r.isNil) || (r == 0)).if { + eqIndex = ((r.isNil).if { part.size - k }{ 0 }); + subPart = part.drop(eqIndex + 1) - part[eqIndex]; + eqSubPart = (this - part[eqIndex]).miSC_eqPart(subPart, k - 1); + }{ + [r - 1, r].do {|i,j| + subPart = part.drop(i+1) - part[i]; + newEqSubPart = (this - part[i]).miSC_eqPart(subPart, k - 1); + newMin = max(part[i], (this - part[i]).miSC_partMax(newEqSubPart)); + ((j == 0) or: { newMin < min }).if { + min = newMin; + eqIndex = i; + eqSubPart = newEqSubPart; + }; + }; + }; + eqPart = [part[eqIndex]] ++ (eqSubPart + part[eqIndex]); }; + ^eqPart + } + + +} diff --git a/Classes/VarGui/extInteger_3.sc b/Classes/VarGui/extInteger_3.sc new file mode 100644 index 0000000..af273d5 --- /dev/null +++ b/Classes/VarGui/extInteger_3.sc @@ -0,0 +1,51 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++Integer { + miSC_isPseudoCaps { ^this.isCmd } + + miSC_checkColorGroups { |colorGrouping| + var flattenedColl, bool = false; + colorGrouping.isSequenceableCollection.not.if { + SimpleInputError("varColorGroups / synthColorGroups must be SequenceableCollection").throw + }; + colorGrouping.every { |x| x.isSequenceableCollection.not }.if { + SimpleInputError("varColorGroups / synthColorGroups must be grouping").throw + }; + + flattenedColl = colorGrouping.flatten(1); + flattenedColl.any { |x| x.isSequenceableCollection.not }.if { + (flattenedColl.every { |x| x.isInteger and: { x < this } } and: + { flattenedColl.asSet.size == this }).if { + bool = true; + }; + }; + bool.not.if { + SimpleInitError("varColorGroups / synthColorGroups must be correct grouping of number " ++ + this.asString ++ ". E.g. [[2], [0,3], [1,4]] is a valid grouping of 5. You can use method " ++ + "clumps, e.g. (0..4).clumps([1,3,2]).").throw + }; + } +} diff --git a/Classes/VarGui/extNil.sc b/Classes/VarGui/extNil.sc new file mode 100644 index 0000000..3971e8e --- /dev/null +++ b/Classes/VarGui/extNil.sc @@ -0,0 +1,27 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Nil { + miSC_asSet { ^Set[] } +} diff --git a/Classes/VarGui/extNode.sc b/Classes/VarGui/extNode.sc new file mode 100644 index 0000000..4aae743 --- /dev/null +++ b/Classes/VarGui/extNode.sc @@ -0,0 +1,45 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +// doubling, keep extension for Integer private + ++Node { + + miSC_runMsg { arg flag=true; + ^[12, nodeID,flag.binaryValue]; + } + + miSC_setMsg { arg ... args; + ^[15, nodeID] ++ args.asOSCArgArray; + } + + miSC_setnMsg { arg ... args; + ^[16, nodeID] ++ Node.setnMsgArgs(*args); + } + + miSC_freeMsg { ^[11, nodeID] } + +} + + diff --git a/Classes/VarGui/extObject.sc b/Classes/VarGui/extObject.sc new file mode 100644 index 0000000..435283f --- /dev/null +++ b/Classes/VarGui/extObject.sc @@ -0,0 +1,48 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Object { + + miSC_isGrouping { ^false } + + miSC_getCtrIndex { |argName| ^nil } + + miSC_collectCopy { ^this } + + // workaround for backwards compatibility + + miSC_performWithEnvir { |selector, envir| + var argNames, args; + var method = this.class.findRespondingMethodFor(selector); + if(method.isNil) { ^this.doesNotUnderstand(selector) }; + + envir = method.miSC_makeEnvirFromArgs.putAll(envir); + argNames = method.argNames.drop(1); + args = envir.atAll(argNames); + ^this.performList(selector, args) + } + + miSC_defNameAsArray { ^this.asArray } + +} diff --git a/Classes/VarGui/extOther.sc b/Classes/VarGui/extOther.sc new file mode 100644 index 0000000..d8e364d --- /dev/null +++ b/Classes/VarGui/extOther.sc @@ -0,0 +1,57 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// nonprivate, no prefix "miSC_" + ++SequenceableCollection { + + // actually could be named pairsDup + + specPairsDup { |num| + ^this.clump(2).collect(_!num).flatten(2) + } + + specPairsDupGroups { |num| + ^(0..(this.size.div(2) * num - 1)).clump(num).flop + } +} + + ++Event { + asESP { } +} + ++EventStreamPlayer { + asESP { ^this } +} + ++Stream { + asESP { |protoEvent| ^this.asEventStreamPlayer(protoEvent) } +} + ++Pattern { + asESP { |protoEvent| ^this.asEventStreamPlayer(protoEvent) } +} + diff --git a/Classes/VarGui/extPauseStream.sc b/Classes/VarGui/extPauseStream.sc new file mode 100644 index 0000000..981023d --- /dev/null +++ b/Classes/VarGui/extPauseStream.sc @@ -0,0 +1,32 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++PauseStream { + + miSC_getIsWaiting { + ^isWaiting + } + +} diff --git a/Classes/VarGui/extSequenceableCollection_1.sc b/Classes/VarGui/extSequenceableCollection_1.sc new file mode 100644 index 0000000..b9149bf --- /dev/null +++ b/Classes/VarGui/extSequenceableCollection_1.sc @@ -0,0 +1,58 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++SequenceableCollection { + + miSC_isGrouping { |n| + var f; + this.every { |x| + x.isKindOf(Collection) and: { x.every {|y| y.isInteger} } + }.not.if { ^false }; + f = this.collect(_.asSequenceableCollection).flatten.asSet; + ((f.size == n) and: { f.every {|x| (x >= 0) && (x < n) } }).not.if { ^false }; + ^true + } + + miSC_isConnected { + // expects grouping + var y; + this.every { |x| + y = x.sort; + (y.size > 0).if { + (y.last - y.first + 1) == y.size + }{ + true + } + }.if { ^true }{ ^false }; + } + + miSC_groupingFromIndices { + var max = this.maxItem, groups; + groups = Array.newClear(max + 1); + this.do { |x,i| groups[x] = groups[x].add(i) }; + ^groups + } + + +} diff --git a/Classes/VarGui/extSequenceableCollection_2.sc b/Classes/VarGui/extSequenceableCollection_2.sc new file mode 100644 index 0000000..22dba87 --- /dev/null +++ b/Classes/VarGui/extSequenceableCollection_2.sc @@ -0,0 +1,200 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++SequenceableCollection { + + miSC_pfuncPbindsFromTuples { | pBefore, pReplace, pAfter, instrument, + excludeDur = false, excludeLegato = false, post = false, trace = false | + + // method for a SequenceableCollection of tuples [symbol, n], + // where n > 1 indicates an arrayed arg + + // miSC_pfuncPbindsFromTuples and miSC_sVarGuiSpecsFromTuples exist + // to avoid multiple lookups in SynthDef metadata by pVarGui + + + var pat, keyPfuncPairs = List.new, replaceIndex; + this.do { |keyNumPair| + keyPfuncPairs.add(keyNumPair[0]).add(Pfunc { + var key, num, val; + #key, num = keyNumPair; + val = currentEnvironment[key]; + (num > 1).if { [val] }{ val } + }) + }; + keyPfuncPairs = pBefore.asArray ++ + (instrument !? [\instrument, instrument]).asArray ++ + excludeDur.if { [] }{ [\dur, Pfunc { ~dur }] } ++ + excludeLegato.if { [] }{ [\legato, Pfunc { ~legato }] } ++ + keyPfuncPairs ++ pAfter.asArray; + + pReplace.pairsDo { |key, pat| + replaceIndex = keyPfuncPairs.indexOf(key); + replaceIndex.notNil.if { keyPfuncPairs[replaceIndex + 1] = pat } + }; + post.if { + ["", "Pbind pairs:", ""].do(_.postln); + keyPfuncPairs.pairsDo { |key, pat| + Char.tab.post; + key.post; + Char.tab.post; + pat.postln; + }; + "".postln; + }; + pat = Pbind(*keyPfuncPairs); + ^trace.if { pat.trace }{ pat } + } + + + miSC_specAsArray { |synthName, metaKey = \specs, useGlobalSpecs = true| + this.every { |x| x.isKindOf(Symbol) || x.isKindOf(String) }.if { + ^this.collect(_.miSC_specAsArray(synthName, metaKey, useGlobalSpecs)) + }{ + ^this.copy + } + } + + miSC_oddSpecAsArray { |synthName, metaKey = \specs, useGlobalSpecs = true| + var c = Array.newClear(this.size); + ^c.do { |item, i| + i.odd.if { + c[i] = this[i].miSC_specAsArray.copy + }{ + c[i] = this[i] + } + } + } + + miSC_sVarGuiData { | ...args | + var sVarGuiArgs = [\ctrBefore, \ctrReplace, \ctrAfter, \exclude, \metaKey, \useGlobalSpecs, \num], + synthCtr = [], synth = [], server, input; + + this.every { |x| (x.isKindOf(Symbol) || x.isKindOf(String)) }.not.if { + SimpleInitError("Items of SequenceableCollection as receiver of method " ++ + "sVarGui must be Symbol or String").class_(VarGui).throw + }; + input = this.collect(_.asSymbol); + + // last arg may be Server + + args.last.isKindOf(Server).if { + server = args.last; + args = args.drop(-1); + }; + (args == []).if { args = ()!(this.size) }; + + // this doesn't check args itself but only its arrangement as collection of dictionaries + + (((args.size == 0) || (args.size == this.size)) && args.every { |x| + x.isKindOf(Dictionary) and: { x.keys.isSubsetOf(sVarGuiArgs) } }).not.if { + SimpleInitError("Args of method aSequenceableCollection.sVarGui must be Dictionaries." ++ + " Their keys must be valid arg names of aSymbol.sVarGui: ctrBefore, ctrReplace, " ++ + "ctrAfter, exclude, metaKey, useGlobalSpecs, num.").class_(VarGui).throw + }; + + args.do { |dict, i| + dict = dict.copy; + dict.miSC_maybePutPairs(\metaKey, \specs, \useGlobalSpecs, true, \num, 1); + + synthCtr = synthCtr ++ (input[i].miSC_performWithEnvir(\sVarGuiSpecs, dict)); + synth = synth ++ (input[i] ! (dict[\num] ?? { 1 })); + + }; + ^[synthCtr, synth, server]; + } + + sVarGui { | ...args | + var data = this.miSC_sVarGuiData(*args); + ^VarGui(synthCtr: data[0], synth: data[1], server: data[2]); + } + + miSC_pVarGuiData { | ...args | + var pVarGuiArgs = [\ctrBefore, \ctrReplace, \ctrAfter, \durCtr, \legatoCtr, + \pBefore, \pReplace, \pAfter, \exclude, \excludeGate, \clock, \quant, + \metaKey, \useGlobalSpecs, \post, \trace, \num], data, + varCtr = [], stream = [], clock = [], quant = [], input; + + // this doesn't check args itself but only its arrangement as collection of dictionaries + + this.every { |x| (x.isKindOf(Symbol) || x.isKindOf(String)) }.not.if { + SimpleInitError("Items of SequenceableCollection as receiver of method " ++ + "pVarGui must be Symbol or String").class_(VarGui).throw + }; + input = this.collect(_.asSymbol); + + (args == []).if { args = ()!(this.size) }; + + (((args.size == 0) || (args.size == this.size)) && args.every { |x| + x.isKindOf(Dictionary) and: { x.keys.isSubsetOf(pVarGuiArgs) } }).not.if { + SimpleInitError("Args of method aSequenceableCollection.pVarGui must be Dictionaries." ++ + " Their keys must be valid arg names of aSymbol.sVarGui: ctrBefore, ctrReplace, ctrAfter, " ++ + "durCtr, legatoCtr, pBefore, pReplace, pAfter, exclude, excludeGate, " ++ + "clock, quant, metaKey, useGlobalSpecs, post, trace, num.").class_(VarGui).throw + }; + + args.do { |dict, i| + dict = dict.copy; + dict.miSC_maybePutPairs(\durCtr, #[0.05, 3, \exp, 0, 0.2], \legatoCtr, #[0.1, 5, \exp, 0, 0.8], + \excludeGate, true, \metaKey, \specs, \useGlobalSpecs, true, \post, false, \trace, false, \num, 1); + + data = input[i].miSC_performWithEnvir(\miSC_pVarGuiData, dict); + varCtr = varCtr ++ data[0]; + stream = stream ++ data[1]; + clock = clock ++ data[2]; + quant = quant ++ data[3]; + }; + ^[varCtr, stream, clock, quant]; + } + + pVarGui { | ...args | + var data = this.miSC_pVarGuiData(*args); + ^VarGui(data[0], stream: data[1], clock: data[2], quant: data[3]) + } + + psVarGui { | pVarGuiArgDictionaries, sVarGuiArgDictionaries, server | + var streamVarGuiData, synthVarGuiData, src, pDicts, sDicts, wrapper; + src = this.collect(_.miSC_defNameAsArray); + wrapper = { |x| x.isKindOf(Dictionary).if { [x] }{ x.asArray } }; + pDicts = wrapper.(pVarGuiArgDictionaries); + sDicts = wrapper.(sVarGuiArgDictionaries); + + ((src.size == 2) && src.every { |x| x.every { |y| y.isKindOf(Symbol) || y.isKindOf(String) } }).not.if { + SimpleInitError("Receiver of method psVarGui / spVarGui must be SequenceableCollection of " ++ + "two items that may be Symbols / Strings or SequenceableCollections thereof").class_(VarGui).throw + }; + + streamVarGuiData = src[0].miSC_pVarGuiData(*pDicts); + synthVarGuiData = src[1].miSC_sVarGuiData(*sDicts); + + ^VarGui(streamVarGuiData[0], synthVarGuiData[0], streamVarGuiData[1], synthVarGuiData[1], + clock: streamVarGuiData[2], quant: streamVarGuiData[3], server: server) + } + + spVarGui { | sVarGuiArgDictionaries, pVarGuiArgDictionaries, server | + ^this.reverse.psVarGui(pVarGuiArgDictionaries, sVarGuiArgDictionaries, server); + } + +} + diff --git a/Classes/VarGui/extSimpleNumber.sc b/Classes/VarGui/extSimpleNumber.sc new file mode 100644 index 0000000..1d140a9 --- /dev/null +++ b/Classes/VarGui/extSimpleNumber.sc @@ -0,0 +1,30 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++ SimpleNumber { + miSC_specAsArray { ^[this, this, \lin, 0, this] } + + miSC_decimalStrings { ^this.asFloat.miSC_decimalStrings } +} + diff --git a/Classes/VarGui/extString_1.sc b/Classes/VarGui/extString_1.sc new file mode 100644 index 0000000..b24c841 --- /dev/null +++ b/Classes/VarGui/extString_1.sc @@ -0,0 +1,30 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++ String { + + miSC_specAsArray { |synthName, metaKey = \specs, useGlobalSpecs = true| + ^this.asSymbol.miSC_specAsArray(synthName, metaKey, useGlobalSpecs); + } +} diff --git a/Classes/VarGui/extString_2.sc b/Classes/VarGui/extString_2.sc new file mode 100644 index 0000000..84d3838 --- /dev/null +++ b/Classes/VarGui/extString_2.sc @@ -0,0 +1,66 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++ String { + + pfuncPbinds { | pBefore, pReplace, pAfter, exclude, excludeGate = true, + excludeDur = false, excludeLegato = false, metaKey = \specs, useGlobalSpecs = true + post = false, trace = false, num = 1 | + ^this.asSymbol.pfuncPbinds(pBefore, pReplace, pAfter, exclude, excludeGate, + excludeDur, excludeLegato, metaKey, useGlobalSpecs, post, trace, num) + } + + sVarGuiSpecs { | ctrBefore, ctrReplace, ctrAfter, exclude, + metaKey = \specs, useGlobalSpecs = true, num = 1 | + ^this.asSymbol.sVarGuiSpecs(ctrBefore, ctrReplace, ctrAfter, exclude, + metaKey, useGlobalSpecs, num) + } + + pVarGuiSpecs { | ctrBefore, ctrReplace, ctrAfter, durCtr = #[0.05, 3, \exp, 0, 0.2], + legatoCtr = #[0.1, 5, \exp, 0, 0.8], exclude, excludeGate = true, + metaKey = \specs, useGlobalSpecs = true, num = 1| + ^this.asSymbol.pVarGuiSpecs(ctrBefore, ctrReplace, ctrAfter, durCtr, legatoCtr, + exclude, excludeGate, metaKey, useGlobalSpecs, num) + } + + sVarGui { | ctrBefore, ctrReplace, ctrAfter, exclude, + metaKey = \specs, useGlobalSpecs = true, num = 1, server| + ^this.asSymbol.sVarGui(ctrBefore, ctrReplace, ctrAfter, exclude, metaKey, + useGlobalSpecs, num, server) + } + + pVarGui { | ctrBefore, ctrReplace, ctrAfter, + durCtr = #[0.05, 3, \exp, 0, 0.2], legatoCtr = #[0.1, 5, \exp, 0, 0.8], + pBefore, pReplace, pAfter, exclude, excludeGate = true, clock, quant, + metaKey = \specs, useGlobalSpecs = true, post = false, trace = false, num = 1| + ^this.asSymbol.pVarGui(ctrBefore, ctrReplace, ctrAfter, durCtr, legatoCtr, + pBefore, pReplace, pAfter, exclude, excludeGate, clock, quant, + metaKey, useGlobalSpecs, post, trace, num) + } + + miSC_defNameAsArray { ^[this] } + +} + + diff --git a/Classes/VarGui/extSymbol_1.sc b/Classes/VarGui/extSymbol_1.sc new file mode 100644 index 0000000..399a42e --- /dev/null +++ b/Classes/VarGui/extSymbol_1.sc @@ -0,0 +1,35 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Symbol { + miSC_getCtrIndex { |argName| + var ctrIndex, synthDesc = SynthDescLib.global.at(this); + (synthDesc.notNil and: { this.asString[0..4] != "temp_" }).if { + ctrIndex = synthDesc.controls.collect({|x| x.name.asSymbol }).indexOf(argName.asSymbol); + }; + ^ctrIndex; + } +} + + diff --git a/Classes/VarGui/extSymbol_2.sc b/Classes/VarGui/extSymbol_2.sc new file mode 100644 index 0000000..478ef3d --- /dev/null +++ b/Classes/VarGui/extSymbol_2.sc @@ -0,0 +1,287 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++ Symbol { + + miSC_specAsArray { | synthName, metaKey = \specs, useGlobalSpecs = true | + var desc, spec; + + desc = SynthDescLib.global[synthName.asSymbol]; + spec = desc.tryPerform(\metadata).tryPerform(\at, metaKey) ?? + { useGlobalSpecs.if { Spec.specs[this] } }; + spec.isNil.if { SimpleInputError("no spec found for key " ++ this.asString) + .method_(thisMethod).throw; }; + ^spec.miSC_specAsArray + } + + + miSC_expandCtrs { | item, i, metaKey = \specs, useGlobalSpecs = true| + ^item.(i).miSC_specAsArray(this, metaKey, useGlobalSpecs) + } + + miSC_expandCtrPairs { | item, i, metaKey = \specs, useGlobalSpecs = true| + ^item.(i).collect { |x,j| + j.odd.if { this.miSC_expandCtrs(x, metaKey, useGlobalSpecs) }{ x } + } + } + + miSC_findGlobalControlSpec { + var item = Spec.specs[this]; + ^item.isKindOf(ControlSpec).if { item }{ nil }; } + + miSC_getControlTuples { | exclude, metaKey = \specs, useGlobalSpecs = true | + // returns an array of tuples [symbol, n], where n > 1 indicates an arrayed arg + var symbol, desc, controls, maybeValidArrayedArg; + desc = SynthDescLib.global[this]; + desc.isNil.if { + ^SimpleInputError("SynthDef unknown to SynthDescLib.global (forgot to add ?)") + .method_(thisMethod).throw; + }{ + exclude = exclude.asArray; + exclude = exclude ++ this.miSC_controlsToExclude(metaKey, useGlobalSpecs); + + controls = List[]; + maybeValidArrayedArg = false; + desc.controls.do { |control, i| + symbol = control.name.asSymbol; + (symbol == '?').if { + maybeValidArrayedArg.if { + controls.last[1] = controls.last[1] + 1; } + }{ + exclude.includes(symbol).if { + maybeValidArrayedArg = false; + }{ + controls.add([symbol, 1]); + maybeValidArrayedArg = true; + } + } + }; + ^controls + } + } + + + miSC_controlsToExclude { | metaKey = \specs, useGlobalSpecs = true | + // for associated SynthDef: gives List of control name keys with + // (case useGlobalSpecs = true) no metadata and no global ControlSpecs defined + // (case useGlobalSpecs = false) no metadata defined + + var desc, controls, controlsToExclude = List.new, key, specData; + desc = SynthDescLib.global[this]; + controls = desc.controls; + controls.do { |controlName| + key = controlName.name.asSymbol; + (key != '?').if { + specData = desc.metadata.tryPerform(\at, metaKey).tryPerform(\at, key); + specData.isNil.if { + (useGlobalSpecs.not or: { key.miSC_findGlobalControlSpec.isNil }).if { + controlsToExclude.add(key) } + } + } + }; + ^controlsToExclude + } + + miSC_sVarGuiSpecsFromTuples { | tuples, exclude, metaKey = \specs | + + // expects SequenceableCollection of tuples [symbol, n], + // where n > 1 indicates an arrayed arg + + // miSC_pfuncPbindsFromTuples and miSC_sVarGuiSpecsFromTuples exist + // to avoid multiple lookups in SynthDef metadata by pVarGui + + var specData, varGuiSpecs = List.new, key, num; + + tuples.do { |keyNumPair| + #key, num = keyNumPair; + varGuiSpecs.add(key); + specData = SynthDescLib.global[this].metadata.tryPerform(\at, metaKey).tryPerform(\at, key); + specData.isNil.if { + (specData = key.miSC_findGlobalControlSpec).isNil.if { + SimpleInputError("no metaData for key " ++ key.asString).method_(thisMethod).throw; + } + }; + specData = specData.miSC_specAsArray; + (num > 1).if { specData = specData ! num }; + varGuiSpecs.add(specData); + }; + ^varGuiSpecs + } + + + pfuncPbinds { | pBefore, pReplace, pAfter, exclude, excludeGate = true, + excludeDur = false, excludeLegato = false, metaKey = \specs, useGlobalSpecs = true, + post = false, trace = false, num = 1 | + var excludeI, controlTuples, excludeFreq, pitchKeys = #[\note, \midinote, \degree]; + pBefore = pBefore ?? []; + pReplace = pReplace ?? []; + pAfter = pAfter ?? []; + exclude = exclude ?? []; + + ^num.collect { |i| + + // detect reserved pbind pitch controls that determine freq, then set flag excludeFreq + excludeFreq = pitchKeys.any { |k| pBefore.(i).includes(k) or: { pAfter.(i).includes(k) } }; + excludeI = exclude.(i).asArray; + + // controls without defined metadata / associated global ControlSpec are excluded, + // they could be included as explicitely given pattern pairs and controls + + controlTuples = this.miSC_getControlTuples( + excludeI ++ (excludeGate.(i).if { [\gate] }{ [] }) ++ + (excludeFreq.if { [\freq] }{ [] }), + metaKey.(i), useGlobalSpecs.(i) + ); + + controlTuples.miSC_pfuncPbindsFromTuples( + pBefore.(i), pReplace.(i), pAfter.(i), this, + excludeI.includes(\dur), excludeI.includes(\legato), post.(i), trace.(i) + ); + }; + } + + + sVarGuiSpecs { | ctrBefore, ctrReplace, ctrAfter, exclude, + metaKey = \specs, useGlobalSpecs = true, num = 1 | + var excludeI, controlTuples, expand, + ctrs = Array.newClear(num), replaceIndex; + + ctrBefore = ctrBefore ?? []; + ctrReplace = ctrReplace ?? []; + ctrAfter = ctrAfter ?? []; + exclude = exclude ?? []; + + num.do { |i| + excludeI = exclude.(i).asArray; + controlTuples = this.miSC_getControlTuples( + excludeI, metaKey.(i), useGlobalSpecs.(i) + ); + expand = { |x| this.miSC_expandCtrPairs(x, i, metaKey.(i), useGlobalSpecs.(i)) }; + + ctrs[i] = expand.(ctrBefore) ++ + this.miSC_sVarGuiSpecsFromTuples(controlTuples, excludeI, metaKey.(i)) ++ + expand.(ctrAfter); + + expand.(ctrReplace) + .pairsDo { |key, ctr| + replaceIndex = ctrs[i].indexOf(key); + replaceIndex.notNil.if { ctrs[i][replaceIndex + 1] = ctr }; + }; + }; + ^ctrs; + } + + pVarGuiSpecs { | ctrBefore, ctrReplace, ctrAfter, durCtr = #[0.05, 3, \exp, 0, 0.2], + legatoCtr = #[0.1, 5, \exp, 0, 0.8], exclude, excludeGate = true, + metaKey = \specs, useGlobalSpecs = true, num = 1| + + ^this.sVarGuiSpecs({ |i| ctrBefore.(i) ++ [\dur, durCtr.(i), \legato, legatoCtr.(i)] }, + ctrReplace, ctrAfter, { |i| exclude.(i).asArray ++ (excludeGate.(i).if { [\gate] }{ [] }) }, + metaKey, useGlobalSpecs, num); + } + + + sVarGui { | ctrBefore, ctrReplace, ctrAfter, exclude, + metaKey = \specs, useGlobalSpecs = true, num = 1, server| + ^VarGui( + synthCtr: this.sVarGuiSpecs(ctrBefore, ctrReplace, ctrAfter, exclude, + metaKey, useGlobalSpecs, num), + synth: this ! num, + server: server + ); + } + + + miSC_pVarGuiData { | ctrBefore, ctrReplace, ctrAfter, + durCtr = #[0.05, 3, \exp, 0, 0.2], legatoCtr = #[0.1, 5, \exp, 0, 0.8], + pBefore, pReplace, pAfter, exclude, excludeGate = true, clock, quant, + metaKey = \specs, useGlobalSpecs = true, post = false, trace = false, num = 1| + + var addedPairs, excludeI, controlTuples, excludeFreq = false, + pitchKeys = #[\note, \midinote, \degree], expand1, expand2, + ctrs = Array.newClear(num), streams = Array.newClear(num), replaceIndex; + + ctrBefore = ctrBefore ?? []; + ctrReplace = ctrReplace ?? []; + ctrAfter = ctrAfter ?? []; + pBefore = pBefore ?? []; + pReplace = pReplace ?? []; + pAfter = pAfter ?? []; + exclude = exclude ?? []; + + num.do { |i| + // detect reserved pbind pitch controls that determine freq, then set flag excludeFreq + excludeFreq = pitchKeys.any { |k| pBefore.(i).includes(k) or: { pAfter.(i).includes(k) } }; + excludeI = exclude.(i).asArray; + + // controls without defined metadata / associated global ControlSpec are excluded, + // they could be included as explicitely given pattern pairs and controls + + controlTuples = this.miSC_getControlTuples( + excludeI ++ (excludeGate.(i).if { [\gate] }{ [] }) ++ + (excludeFreq.if { [\freq] }{ [] }), + metaKey.(i), useGlobalSpecs.(i) + ); + + expand1 = { |x| this.miSC_expandCtrPairs(x, i, metaKey.(i), useGlobalSpecs.(i)) }; + expand2 = { |x| this.miSC_expandCtrs(x, i, metaKey.(i), useGlobalSpecs.(i)) }; + + ctrs[i] = expand1.(ctrBefore) ++ + (excludeI.includes(\dur).not.if { + [\dur, expand2.(durCtr.(i))] }{ [] }) ++ + (excludeI.includes(\legato).not.if { + [\legato, expand2.(legatoCtr.(i))] }{ [] }) ++ + this.miSC_sVarGuiSpecsFromTuples(controlTuples, excludeI, metaKey.(i)) ++ + expand1.(ctrAfter); + + + expand1.(ctrReplace) + .pairsDo { |key, ctr| + replaceIndex = ctrs[i].indexOf(key); + replaceIndex.notNil.if { ctrs[i][replaceIndex + 1] = ctr }; + }; + + streams[i] = controlTuples.miSC_pfuncPbindsFromTuples( + pBefore.(i), pReplace.(i), pAfter.(i), this, + excludeI.includes(\dur), excludeI.includes(\legato), post.(i), trace.(i) + ); + }; + ^[ctrs, streams, num.collect(clock.(_)), num.collect(quant.(_))] + } + + pVarGui { | ctrBefore, ctrReplace, ctrAfter, + durCtr = #[0.05, 3, \exp, 0, 0.2], legatoCtr = #[0.1, 5, \exp, 0, 0.8], + pBefore, pReplace, pAfter, exclude, excludeGate = true, clock, quant, + metaKey = \specs, useGlobalSpecs = true, post = false, trace = false, num = 1| + + var data = this.miSC_pVarGuiData(ctrBefore, ctrReplace, ctrAfter, durCtr, legatoCtr, + pBefore, pReplace, pAfter, exclude, excludeGate, clock, quant, metaKey, + useGlobalSpecs, post, trace, num); + + ^VarGui(data[0], stream: data[1], clock: data[2], quant: data[3]); + } + +} + + diff --git a/Classes/VarGui/extSynth.sc b/Classes/VarGui/extSynth.sc new file mode 100644 index 0000000..a8987f6 --- /dev/null +++ b/Classes/VarGui/extSynth.sc @@ -0,0 +1,30 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++Synth { + + miSC_getCtrIndex { |argName| ^this.defName.asSymbol.miSC_getCtrIndex(argName) } +} + + diff --git a/Classes/VarGui/extVarGuiPlayerSection.sc b/Classes/VarGui/extVarGuiPlayerSection.sc new file mode 100644 index 0000000..068fd8f --- /dev/null +++ b/Classes/VarGui/extVarGuiPlayerSection.sc @@ -0,0 +1,84 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++VarGuiPlayerSection { + + drawPlayers { + + iconViewGroup[0].drawFunc = { + var playX1 = 0.38, playY1 = 0.3, fac = 1, + x = iconViewGroup[0].bounds.width, y = iconViewGroup[0].bounds.height; + Pen.moveTo(Point(x * playX1, y * playY1)); + Pen.lineTo(Point(x * (1-playX1), y * 0.5)); + Pen.lineTo(Point(x * playX1, (1-playY1) * y)); + Pen.width = 1.4 * fac; + Pen.strokeColor = Color.black; + Pen.stroke; + }; + + iconViewGroup[1].drawFunc = { + var playX2 = 0.43, playY2 = 0.2, fac = 1, + x = iconViewGroup[1].bounds.width, y = iconViewGroup[1].bounds.height; + Pen.moveTo(Point(x * playX2, y * playY2)); + Pen.lineTo(Point(x * playX2, y * (1-playY2))); + Pen.moveTo(Point(x * (1-playX2), y * playY2)); + Pen.lineTo(Point(x * (1-playX2), y * (1-playY2))); + Pen.width = 1.7 * fac; + Pen.strokeColor = Color.black; + Pen.stroke; + }; + + iconViewGroup[2].drawFunc = { + var playX3a = 0.16, playX3a2 = 0.32, playY3a = 0.3, playX3b = 0.45, playY3b = 0.9, playX3c = 0.64, playY3c = 0.3, fac = 1, + x = iconViewGroup[2].bounds.width, y = iconViewGroup[2].bounds.height; + + Pen.moveTo(Point(x * playX3a, y * playY3a)); + Pen.lineTo(Point(x * playX3a, y * (1-playY3a))); + Pen.lineTo(Point(x * playX3a2, y * (1-playY3a))); + Pen.lineTo(Point(x * playX3a2, y * playY3a)); + Pen.lineTo(Point(x * playX3a - (1 * fac), y * playY3a)); + + Pen.width = 1.4 * fac; + Pen.strokeColor = Color.black; + Pen.stroke; + + Pen.moveTo(Point(x * playX3b, y * playY3b)); + Pen.lineTo(Point(x * (1-playX3b), y * (1-playY3b))); + + Pen.width = 1 * fac; + Pen.strokeColor = Color.black; + Pen.stroke; + + Pen.moveTo(Point(x * (1-playX3a), y * playY3c)); + Pen.lineTo(Point(x * playX3c, y * 0.5)); + Pen.lineTo(Point(x * (1-playX3a), y * (1-playY3c))); + + Pen.width = 1.2 * fac; + Pen.strokeColor = Color.black; + Pen.stroke; + }; + ^this + } + +} diff --git a/Classes/VarGui/extVarGui_1.sc b/Classes/VarGui/extVarGui_1.sc new file mode 100644 index 0000000..1fb625d --- /dev/null +++ b/Classes/VarGui/extVarGui_1.sc @@ -0,0 +1,385 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++VarGui { + + checkSingleSynthInput { |synth| + case + { [Symbol, String].any(synth.isKindOf(_)) } + { + SynthDescLib.global[synth.asSymbol].isNil.if { + SimpleInitError("Synth given as " ++ synth.class.asString ++ " " ++ synth.asString ++ + " not contained " ++ + "in global SynthDescLib (forgot SynthDef.add, former .memStore ?)") + .class_(VarGui).throw; + } + } + { [Synth, Integer].any(synth.isKindOf(_)).not } + { SimpleInitError("Single synth input item must be synthdef (given as Symbol or String) or " ++ + "Synth (directly or as nodeID integer)").class_(VarGui).throw }; + ^this + } + + // not a complete input check, but avoid some nearby discrepances + + checkArgs {|varCtr, synthCtr, stream, synth, varEnvirGroups, envir, + synthCtrGroups, assumePlaying, assumeRunning, assumeEnded| + var j = 0, c, vars, synthCtrs, envirSet, synthStateVars, synthStateStrings, synthInputDup = false, result; + + case + { synth.isNil } + { + (this.synthCtr.size != 0).if { + SimpleInitError("synthCtr input but no synth input. " ++ + "Synth must be Symbol, String (indicating a Synthdef known by SynthDesLib), " ++ + "Synth or Integer (nodeID) or collection thereof.").class_(VarGui).throw; + }{ + synths = []; + }; + } + { synth.isKindOf(SequenceableCollection) } + { + synths = synth; + } + { [Synth, Integer, Symbol, String].any(synth.isKindOf(_)) } + { + synths = [synth]; + } + { true }{ SimpleInitError("Synth must be Symbol, String (indicating a Synthdef known by SynthDesLib), " ++ + "Synth or Integer (nodeID) or collection thereof.").class_(VarGui).throw; }; + + synths.do {|x| this.checkSingleSynthInput(x) }; + + + // prepare use of metadata for synthCtr if necessary + + (synthCtr.isNil && synth.notNil).if { synthCtr = (nil ! max(1, synth.size)) }; + + (synthCtr.isKindOf(SequenceableCollection) and: { synthCtr.size > 0 } and: + { (synthCtr.first.isSequenceableCollection) || (synthCtr.first.isNil) }).if { + synthCtr.do { |specPairs, i| + var sym; + specPairs.isNil.if { + synthCtr[i] = case + { [Symbol, String].any(synths[i].isKindOf(_)) } + { synths[i].asSymbol.sVarGuiSpecs.flatten; } + { synths[i].isKindOf(Synth) } + { + sym = synths[i].defName.asSymbol; + SynthDescLib.global[sym].notNil.if { + sym.sVarGuiSpecs.flatten; + }{ + [] + }; + }; + specPairs = synthCtr[i]; + } + } + }; + + + // this.synthCtr stores flattened (ungrouped) ctrs + + this.synthCtr = case + { synthCtr.isNil }{ [] } + { synthCtr.first.isKindOf(SequenceableCollection) }{ + // check if ctrs given as keys to be looked up as global ControlSpec + c = synthCtr.flatten; + c.do { |item, i| i.odd.if { c[i] = c[i].miSC_specAsArray } }; + } + { true }{ synthCtr }; + + + synthCtrs = this.synthCtr.select {|x,i| i.even }; + + + synthStateVars = [assumePlaying, assumeRunning, assumeEnded]; + synthStateStrings = ["assumePlaying", "assumeRunning", "assumeEnded"]; + + synthStateStrings.do {|x,i| + case + { synthStateVars[i].isKindOf(SequenceableCollection) } + { + (synthStateVars[i].every {|x| [Boolean, Nil].any(x.isKindOf(_)) } && + (synthStateVars[i].size == synths.size)).not.if { + SimpleInitError(x ++ " input as collection must equal number of synths, " ++ + "items must be Boolean or Nil").class_(VarGui).throw; + }{ + this.perform((x ++ "_").asSymbol, synthStateVars[i]); + } + } + { [Boolean, Nil].any(synthStateVars[i].isKindOf(_)) } + { + this.perform((x ++ "_").asSymbol, synthStateVars[i].dup(synths.size)); + } + { true }{ SimpleInitError(x ++ " must be Boolean, Nil or collection thereof, " ++ + "in this case size must corespond with synthCtr").class_(VarGui).throw; }; + }; + + this.assumeEnded.do {|x,i| + ((x == true) && ((this.assumePlaying[i] == true) || (this.assumeRunning[i] == true))).if { + SimpleInitError("Contradictory assumptions about synth state, there must not be a player " ++ " + with assumeEnded set true " ++ + "together with assumePlaying or " ++ "assumeRunning set true").class_(VarGui).throw; + }; + ((this.assumePlaying[i] == false) && (this.assumeRunning[i] == true)).if { + SimpleInitError("Contradictory assumptions about synth state, there must not be a " ++ " + player with assumePlaying set false " ++ + "and assumeRunning set true").class_(VarGui).throw; + }; + }; + + synthStateStrings.do {|x,i| + this.perform(x.asSymbol).do {|y,j| + (([Symbol, String].any(synths[j].isKindOf(_))) && (y.notNil)).if { + SimpleInitError("There must not be any assume flag set to a Boolean for a " ++ " + Symbol / String as synth input, " ++ + "assumptions are only allowed for Synths and nodeIDs.").class_(VarGui).throw; + } + } + }; + + synthIDs = synths.collect {|x| [Synth, Integer].any(x.isKindOf(_)).if { x.asNodeID } }; + + stream = stream.miSC_defNameAsArray; + streamEnvirs = List.new; + streamEnvirIndices = Array.newClear(stream.size); + streams = Array.newClear(stream.size); + + stream.do {|x, i| + var e, k; + case + { x.isKindOf(Function) }{ e = Environment.new; streams[i] = e.use { Task(x) } } + { x.isKindOf(Pattern) }{ e = Environment.new; streams[i] = e.use { x.asEventStreamPlayer } } + { x.isKindOf(Symbol) || x.isKindOf(String) } + { e = Environment.new; streams[i] = e.use { x.asSymbol.pfuncPbinds.at(0).asEventStreamPlayer } } + { true }{ + x.isKindOf(PauseStream).not.if { + SimpleInitError("Stream must be Task, Task function, Pattern, " ++ + "EventStreamPlayer, Symbol, String or collection of such").class_(VarGui).throw; + }{ + e = x.originalStream.miSC_getEnvironment; + k = streamEnvirs.detectIndex(_ === e); + streams[i] = x; + } + }; + k.notNil.if { + streamEnvirIndices[i] = k; + }{ + streamEnvirs = streamEnvirs.add(e); + streamEnvirIndices[i] = j; + j = j + 1; + }; + }; + + envir.isNil.if { + envirs = (stream.size == 0).if { [currentEnvironment] }{ streamEnvirs }; + }{ + (stream.size != 0).if { SimpleInitError("Passing an envir arg only " ++ + "allowed without stream players").class_(VarGui).throw }; + envirs = case + { envir.isKindOf(Environment) }{ [envir] } + { envir.isSequenceableCollection }{ envir } + { true }{ SimpleInitError("Envir arg must be Environment or SequenceableCollection") + .class_(VarGui).throw; }; + + envirSet = IdentitySet.new; + envirs.do { |envir| envirSet = envirSet.add(envir) }; + + ((envirs.any(_.isKindOf(Environment).not)) || (envirSet.size != envirs.size)).if { + SimpleInitError("Envir arg must be Environment or SequenceableCollection of " ++ + "non-identical Environments").class_(VarGui).throw; + }; + + }; + + + // prepare use of metadata for varCtr if necessary + // this.varCtr stores flattened (ungrouped) ctrs + + (varCtr.isNil && stream.notNil).if { varCtr = (nil ! max(1, stream.size)) }; + + (varCtr.isKindOf(SequenceableCollection) and: { varCtr.size > 0 } and: + { (varCtr.first.isSequenceableCollection) || (varCtr.first.isNil) }).if { + + // case grouped varCtr input, some items may be nil, will look for metadata then + varEnvirGroups.notNil.if { + SimpleInitError("varEnvirGroups must not be given if varCtr is already grouped, " ++ + "means a collection of specPair collections").class_(VarGui).throw; + }; + hasConnectedVarEnvirGroups = true; + envir.isNil.if { + (stream.size != 0).if { + ((varCtr.size) > (streamEnvirs.size)).if { + SimpleInitError("Size of varCtr exceeds number of implicitely given stream envirs") + .class_(VarGui).throw; + } + }{ + ((varCtr.size) > 1).if { + SimpleInitError("Size of varCtr > 1 but no implicitely or explicitely given envirs") + .class_(VarGui).throw; + } + } + }{ // can only happen if no streams + ((varCtr.size) > (envirs.size)).if { + SimpleInitError("Size of varCtr exceeds number of explicitely given envirs") + .class_(VarGui).throw; + }; + }; + varCtr.do { |specPairs, i| + var envirKeys, c; + + specPairs.isNil.if { + varCtr[i] = ([Symbol, String].any(stream[i].isKindOf(_))).if { + stream[i].asSymbol.pVarGuiSpecs.flatten; + }{ + [] + }; + specPairs = varCtr[i]; + }; + + this.varCtr = varCtr.flatten; + + specPairs.do { |item, j| + j.even.if { + varEnvirIndices = varEnvirIndices.add(i); + envirKeys = envirKeys.add(item); + } + }; + (envirKeys.miSC_asSet.size != envirKeys.size).if { + SimpleInitError("Bad definition: specPairs defined for one envir contain " ++ + "var name more than once").class_(VarGui).throw; + } + }; + vars = this.varCtr.select {|x,i| i.even }; + + }{ + + this.varCtr = varCtr.isNil.if { [] }{ varCtr }; + vars = this.varCtr.select {|x,i| i.even }; + + varEnvirGroups.isNil.if { + (vars.size != vars.miSC_asSet.size).if { + SimpleInitError("varCtr contains multiple keys but varEnvirGroups not defined") + .class_(VarGui).throw; + }; + hasConnectedVarEnvirGroups = true; + varEnvirIndices = vars.size.collect { 0 }; + }{ + varEnvirGroups.miSC_isGrouping(vars.size).not.if { + SimpleInitError("varEnvirGroups must be a valid grouping of number of vars. " ++ + "E.g. [[3,0], [1,2,4]] is a valid grouping of 5").class_(VarGui).throw; + }; + hasConnectedVarEnvirGroups = varEnvirGroups.miSC_isConnected; + (varEnvirGroups.size > envirs.size).if { + SimpleInitError("Size of varEnvirGroups must less or equal " ++ envirs.size.asString ++ + ", the number of " ++ (envir.notNil.if { "ex" }{ "im" }) ++ "plicitely given envirs") + .class_(VarGui).throw; + }; + varEnvirIndices = Array.newClear(vars.size); + varEnvirGroups.do { |x,i| + var y = vars[x]; + (y.size != y.miSC_asSet.size).if { + SimpleInitError("Bad definition: envirGroup " ++ x.asString ++ + " contains var name more than once").class_(VarGui).throw; + }; + x.do(varEnvirIndices[_] = i); + }; + }; + }; + // check if ctrs given as keys to be looked up as global ControlSpec + this.varCtr.do { |item, i| i.odd.if { this.varCtr[i] = this.varCtr[i].miSC_specAsArray } }; + + synthCtr.first.isSequenceableCollection.if { + synthCtrGroups.notNil.if { + SimpleInitError("synthCtrGroups must not be given if synthCtr is already grouped, " ++ + "means a collection of specPair collections").class_(VarGui).throw; + }; + hasConnectedSynthCtrGroups = true; + + (synthCtr.size > synths.size).if { + SimpleInitError("Size of synthCtr exceeds number of given synths").class_(VarGui).throw; + }; + synthCtr.do { |specPairs, i| + var synthKeys; + specPairs.do { |item, j| + j.even.if { + synthCtrSynthIndices = synthCtrSynthIndices.add(i); + synthKeys = synthKeys.add(item); + } + }; + (synthKeys.miSC_asSet.size != synthKeys.size).if { + SimpleInitError("Bad definition: specPairs defined for one synth " ++ + "contain ctr name more than once").class_(VarGui).throw; + } + }; + + }{ + synthCtrGroups.isNil.if { + (synthCtrs.size != synthCtrs.miSC_asSet.size).if { + SimpleInitError("synthCtr contains multiple keys but synthCtrGroups not defined") + .class_(VarGui).throw; + }; + hasConnectedSynthCtrGroups = true; + synthCtrSynthIndices = synthCtrs.size.collect { 0 }; + }{ + synthCtrGroups.miSC_isGrouping(synthCtrs.size).not.if { + SimpleInitError("synthCtrGroups must be a valid grouping of number of synthCtrs. " ++ + "E.g. [[3,0], [1,2,4]] is a valid grouping of 5").class_(VarGui).throw; + }; + hasConnectedSynthCtrGroups = synthCtrGroups.miSC_isConnected; + (synthCtrGroups.size > synths.size).if { + SimpleInitError("Size of synthCtrGroups must be less or equal " ++ synths.size.asString ++ + ", the number of given synths").class_(VarGui).throw; + }; + synthCtrSynthIndices = Array.newClear(synthCtrs.size); + synthCtrGroups.do { |x,i| + var y = synthCtrs[x]; + (y.size != y.miSC_asSet.size).if { + SimpleInitError("Bad definition: synthCtrGroup " ++ x.asString ++ + " contains synth controlname more than once").class_(VarGui).throw; + }; + x.do(synthCtrSynthIndices[_] = i); + }; + }; + }; + + // check if ctrs given as keys to be looked up as global ControlSpec + this.synthCtr.do { |item, i| i.odd.if { this.synthCtr[i] = this.synthCtr[i].miSC_specAsArray } }; + + hasConnectedSynthCtrGroups.if { + firstSynthCtrIndices = synths.collect { |synth, i| synthCtrSynthIndices.tryPerform(\indexOf,i) }; + }; + + (envir.notNil && (varEnvirIndices.miSC_asSet.size < envirs.size)).if { + SimpleInitError("Bad definition: unused explicitely given envirs").class_(VarGui).throw; + }; + + varColorNum = varEnvirIndices.miSC_asSet.size; + synthColorNum = synthCtrSynthIndices.miSC_asSet.size; + + ^this; + } +} + diff --git a/Classes/VarGui/extVarGui_2.sc b/Classes/VarGui/extVarGui_2.sc new file mode 100644 index 0000000..6ea9941 --- /dev/null +++ b/Classes/VarGui/extVarGui_2.sc @@ -0,0 +1,141 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++VarGui { + + possibleColumnBreakIndices {|sliderPriority, allowSynthsBreak, allowVarsBreak, + allowSynthBreak, allowArrayBreak, allowEnvirBreak, minPartitionSize| + var possibleVarBreakIndices = [], possibleSynthBreakIndices = [], possibleBreakIndices, modSynthCtr, modSynthCtrNums, + indOffset, indices1, indices2, priority, + k = 0, // iterate controls + l = 0; // iterate vars + + // collection [0] means possible break if there are controls of other type before + // []: don't break apart this control type + + priority = case + { sliderPriority.asSymbol == \var }{ \var } + { sliderPriority.asSymbol == \synth }{ \synth } + { true }{ Error("sliderPriority must be set to var or synth").throw }; + + modSynthCtr = synthCtr; + modSynthCtrNums = synthCtrNum; + + forBy(0, varCtr.size - 2, 2, {|i| + varCtr[i+1].every(_.isSequenceableCollection).if { + varCtr[i+1].do({|item,j| + if ((k <= (varCtrNum - minPartitionSize)) && + ((k == 0) || + (allowVarsBreak && + (((j == 0) && (k >= minPartitionSize)) || + ((j >= minPartitionSize) && allowArrayBreak && + (j <= ((varCtr[i+1].size - minPartitionSize))) + ) + ) and: + { allowEnvirBreak || (l == 0) or: { varEnvirIndices[l] != varEnvirIndices[l-1] } } + ) + ),{ + possibleVarBreakIndices = possibleVarBreakIndices.add(k); + } + ); + k = k+1; + }) + }{ + if (((k <= (varCtrNum - minPartitionSize)) && + ((k == 0) || + ((k >= minPartitionSize) && + allowVarsBreak and: + { allowEnvirBreak || (l == 0) or: { varEnvirIndices[l] != varEnvirIndices[l-1] } } + ) + )),{ + possibleVarBreakIndices = possibleVarBreakIndices.add(k); + } + ); + k = k+1; + }; + l = l + 1; + }); + + k = 0; // iterate controls + l = 0; // iterate + + forBy(0, synthCtr.size - 2, 2, {|i| + synthCtr[i+1].every(_.isSequenceableCollection).if { + synthCtr[i+1].do({|item,j| + if ((k <= (synthCtrNum - minPartitionSize)) && + ((k == 0) || + (allowSynthsBreak && + (((j == 0) && (k >= minPartitionSize)) || + ((j >= minPartitionSize) && allowArrayBreak && + (j <= ((synthCtr[i+1].size - minPartitionSize))) + ) + ) and: + { allowSynthBreak || (l == 0) or: { synthCtrSynthIndices[l] != synthCtrSynthIndices[l-1] } } + ) + ),{ + possibleSynthBreakIndices = possibleSynthBreakIndices.add(k); + } + ); + k = k+1; + }) + }{ + if (((k <= (synthCtrNum - minPartitionSize)) && + ((k == 0) || + ((k >= minPartitionSize) && + allowSynthsBreak and: + { allowSynthBreak || (l == 0) or: { synthCtrSynthIndices[l] != synthCtrSynthIndices[l-1] } } + ) + )),{ + possibleSynthBreakIndices = possibleSynthBreakIndices.add(k); + } + ); + k = k+1; + }; + l = l + 1; + }); + + + indices1 = (priority == \var).if { possibleVarBreakIndices }{ possibleSynthBreakIndices }; + indices2 = (priority == \var).if { possibleSynthBreakIndices }{ possibleSynthBreakIndices }; + + (priority == \var).if { + indices1 = possibleVarBreakIndices; + indices2 = possibleSynthBreakIndices; + indOffset = varCtrNum; + }{ + indices1 = possibleSynthBreakIndices; + indices2 = possibleVarBreakIndices; + indOffset = synthCtrNum; + }; + + possibleBreakIndices = case + { (indices1.size == 0) && (indices2.size == 0) }{ [] } + { (indices1.size == 0) } + { ((indices2[0] != 0).if { indices2 }{ indices2.drop(1) }) + indOffset } + { true } + { ((indices1[0] != 0).if { indices1 }{ indices1.drop(1) }) ++ (indices2 + indOffset) }; + + ^possibleBreakIndices; + } +} diff --git a/Classes/VarGui/extVarGui_3.sc b/Classes/VarGui/extVarGui_3.sc new file mode 100644 index 0000000..da7a3f1 --- /dev/null +++ b/Classes/VarGui/extVarGui_3.sc @@ -0,0 +1,228 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + ++VarGui { + + effectiveSliderWidth {|columnNum, maxColumnNum, minSliderWidth, maxSliderWidth| + var k,d; + ^[0,1].includes(columnNum).if { + maxSliderWidth + }{ + k = (maxSliderWidth - minSliderWidth)/(1 - maxColumnNum); + d = maxSliderWidth - k; + columnNum * k + d; + } + } + + placeSliders {|sliderPriority, sliderType, cview, columnBreakIndices, + sliderWidth, sliderHeight, labelWidth, numberWidth, + synthNumWidth, tempColors, numColorExp, columnIndent, + gap, leftMargin, varColorNum, synthColorNum, colorDeviation, greyMode, fontColor| + var k = 0, m = 0, c = 0, r = 0, rMax = 0, columnBreakCheck, placeVarSliders, + placeSynthSliders, afterBreak = false, counterUpdate, sliderClass, + tempCtrColors, orderedCtrColorPairs, colorDeviationNums, synthOffset, varOffset, + nextLinePlusMaybeLabel, mod = 256; + + sliderClass = sliderType.switch( + \standard, { EZSlider }, + \smooth, { EZSmoothSlider }, + \round, { EZRoundSlider } + ); + + // counters: + // m: sliders + // k: slider per type + // c: column + // r, rMax: max row number, also counts gap between var and synth sliders + + cview.keyModifiersChangedAction_({ |view, modifiers| mod = modifiers}); + + colorDeviationNums = Array.newClear(varColorNum + synthColorNum); + + orderedCtrColorPairs = ((varColorNum + synthColorNum) != 0).if { + (sliderPriority == \var).if { + varCtrColorPairs ++ ((synthCtrColorPairs.size != 0).if { synthCtrColorPairs + [[varColorNum, 0]] }) + }{ + synthCtrColorPairs ++ ((varCtrColorPairs.size != 0).if { varCtrColorPairs + [[synthColorNum, 0]] }) + }; + }{ + [] + }; + + orderedCtrColorPairs.do {|x,i| + (colorDeviationNums[x[0]].isNil or: { colorDeviationNums[x[0]] < x[1] }).if { + colorDeviationNums[x[0]] = x[1] + }; + }; + colorDeviationNums = colorDeviationNums + 1; + + tempCtrColors = colorDeviationNums.collect {|x,i| + var c = tempColors[i], p = greyMode.if { (0..x-1).scramble/(x-1) } { x.miSC_distinctCubePoints(3) }; + x.collect({|j| + var q = (greyMode.not.if { p[j] }{ p[j]!3 }).linlin(0,1, 1 / (1 + colorDeviation), (1 + colorDeviation)); + Color(c.red * q[0], c.green * q[1], c.blue * q[2]); + }) + }; + + columnBreakCheck = { |i| + (r > rMax).if { rMax = r }; + (i == columnBreakIndices[c]).if { + cview.decorator.reset; + c = c + 1; + r = 0; + afterBreak = true; + } + }; + + counterUpdate = { + k = k+1; + m = m+1; + r = r+1; + afterBreak = false; + columnBreakCheck.(m); + }; + + nextLinePlusMaybeLabel = {|synthIndex, j, l = 0| + cview.decorator.nextLine; + + (hasConnectedSynthCtrGroups.not or: { ((firstSynthCtrIndices.includes(j) && (l == 0)) || afterBreak) }).if { cview.decorator.shift((sliderWidth + columnIndent) * c - synthNumWidth - gap); + synthNameBoxes = synthNameBoxes.add( + StaticText(cview, Rect(0, 0, synthNumWidth, sliderHeight)) + .background_(tempColors[synthCtrColorPairs[k][0] + synthOffset].miSC_exp(numColorExp)) + .string_( (hasConnectedSynthCtrGroups && afterBreak and: { firstSynthCtrIndices.includes(j).not } ).if { + "(" ++ (synthIndex.asString) ++ ")" + }{ + synthIndex.asString + }) + .align_(\center).stringColor_(fontColor) + ); + }; + /* avoid shift here */ + cview.decorator.left_((sliderWidth + columnIndent) * c + leftMargin); + }; + + placeVarSliders = { + forBy(0, varCtr.size - 2, 2, {|i| + var str, color; + + ((k == 0) && (m != 0) && afterBreak.not).if { r = r+1; cview.decorator.shift(y: sliderHeight + gap); }; + color = tempCtrColors[varCtrColorPairs[k][0] + varOffset].at(varCtrColorPairs[k][1]); + + varCtr[i+1].every(_.isSequenceableCollection).if { + varCtr[i+1].do({|item,j| + str = varCtr[i].asString ++ "[" ++ j.asString ++ "]" ++ " "; + cview.decorator.nextLine; + cview.decorator.shift((sliderWidth + columnIndent) * c); + varStrings = varStrings.add(str); + varCtrSliders = varCtrSliders.add( + sliderClass.new(cview, sliderWidth @ sliderHeight, str, + ControlSpec(*(varCtr[i+1][j].copyFromStart(3))), + {|ez| this.performSliderActions(varCtr[i], ez.value, \var, mod, i.div(2), j) }, + varCtr[i+1][j].at(4), labelWidth: labelWidth, numberWidth: numberWidth + ).miSC_colorize(color, numColorExp, fontColor) + ); + counterUpdate.(); + }) + }{ + str = varCtr[i].asString ++ " "; + cview.decorator.nextLine; + cview.decorator.shift((sliderWidth + columnIndent) * c); + varStrings = varStrings.add(str); + + varCtrSliders = varCtrSliders.add( sliderClass.new(cview, sliderWidth @ sliderHeight, str, + ControlSpec(*varCtr[i+1].copyFromStart(3)), + {|ez| this.performSliderActions(varCtr[i], ez.value, \var, mod, i.div(2), nil) }, + varCtr[i+1].at(4), labelWidth: labelWidth, numberWidth: numberWidth + ).miSC_colorize(color, numColorExp, fontColor) + ); + counterUpdate.(); + }; + }); + }; + + placeSynthSliders = { + forBy(0, synthCtr.size - 2, 2, {|j| + var str, color, synthIndex = synthCtrSynthIndices[j.div(2)]; + + ((k == 0) && (m != 0) && afterBreak.not).if { r = r+1; cview.decorator.shift(y: sliderHeight + gap); }; + color = tempCtrColors[synthCtrColorPairs[k][0] + synthOffset].at(synthCtrColorPairs[k][1]); + + synthCtr[j+1].every(_.isSequenceableCollection).if { + synthCtr[j+1].do({|item,l| + nextLinePlusMaybeLabel.(synthIndex, j.div(2), l); + str = synthCtr[j].asString ++ "[" ++ l.asString ++ "]" ++ " "; + synthCtrStrings = synthCtrStrings.add(str); + synthCtrSliders = synthCtrSliders.add( sliderClass.new(cview, sliderWidth @ sliderHeight, str, + ControlSpec(*synthCtr[j+1][l].copyFromStart(3)), + {|ez| this.performSliderActions(synthCtr[j], ez.value, \synth, mod, j.div(2), l) }, + synthCtr[j+1][l].at(4), labelWidth: labelWidth, numberWidth: numberWidth + ).miSC_colorize(color, numColorExp, fontColor) + ); + counterUpdate.(); + }) + }{ + nextLinePlusMaybeLabel.(synthIndex, j.div(2)); + str = synthCtr[j].asString ++ " "; + synthCtrStrings = synthCtrStrings.add(str); + synthCtrSliders = synthCtrSliders.add( sliderClass.new(cview, sliderWidth @ sliderHeight, str, + ControlSpec(*synthCtr[j+1].copyFromStart(3)), + {|ez| this.performSliderActions(synthCtr[j], ez.value, \synth, mod, j.div(2), nil) }, + synthCtr[j+1].at(4), labelWidth: labelWidth, numberWidth: numberWidth + ).miSC_colorize(color, numColorExp, fontColor) + ); + counterUpdate.(); + } + }); + }; + + (sliderPriority == \var).if { + synthOffset = varColorNum; + varOffset = 0; + placeVarSliders.(); + k = 0; + placeSynthSliders.() + }{ + synthOffset = 0; + varOffset = synthColorNum; + placeSynthSliders.(); + k = 0; + placeVarSliders.(); + }; + + // slider section height + ^rMax * (sliderHeight + gap) + (2 * gap); + } + + refreshVarCtrSliderLabels {|showEnvirIndices = false| + var sliderCount = 0; + varEnvirIndices.do {|x,i| + varCtrSizes[i].do {|j| + varCtrSliders[sliderCount].set(varStrings[sliderCount] ++ (showEnvirIndices.if { " " ++ x.asString }{ "" })); + sliderCount = sliderCount + 1; + } + }; + ^this + } +} diff --git a/Classes/VarGui/extVarGui_4.sc b/Classes/VarGui/extVarGui_4.sc new file mode 100644 index 0000000..bf40573 --- /dev/null +++ b/Classes/VarGui/extVarGui_4.sc @@ -0,0 +1,216 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++VarGui { + + performVarArrayActions {|i, k, offset, symbol, value| + var clippedValue; + varCtrSliders[offset + k].value_(value); + clippedValue = varCtrSliders[offset + k].value; + envirs.at(varEnvirIndices[i.div(2)]).at(symbol).put(k, clippedValue); + (saveData[0][i][k]).put(4, clippedValue); + } + + performSynthArrayActions {|j, k, offset, value| + var clippedValue; + synthCtrSliders[offset + k].value_(value); + clippedValue = synthCtrSliders[offset + k].value; + (saveData[1][j][k]).put(4, clippedValue); + } + + + performSliderActions {|symbol, value, reg, mod, symbolIndex, arrayIndex| + // if reg == \var: symbolIndex = index of var + // if reg == \synth: symbolIndex = index of synth + + var msgList, ii, k = 0, sliderOffset = 0, modType, collectType, ctrIndex, synthIndex, clippedValue; + + modType = case + { (mod.isShift) && (mod.isCtrl) }{ 0 } + { (mod.isShift) }{ 1 } + { true }{ 2 }; + + ((reg == \var) || (mod.miSC_isPseudoCaps)).if { + forBy(0, saveData[0].size - 2, 2, {|i| + ii = i.div(2); + (saveData[0][i] == symbol).if { + (((symbolIndex == ii) && (reg == \var)) || (mod.isAlt)).if { + arrayIndex.isNil.if { + varCtrSliders[sliderOffset].value_(value); + clippedValue = varCtrSliders[sliderOffset].value; + envirs.at(varEnvirIndices[ii]).put(symbol, clippedValue); + (saveData[0][i+1]).put(4, clippedValue); + }{ + saveData[0][i+1].do {|x,k| + modType.switch( + 0, { (k <= arrayIndex).if { this.performVarArrayActions(i+1, k, sliderOffset, symbol, value) } }, + 1, { (k >= arrayIndex).if { this.performVarArrayActions(i+1, k, sliderOffset, symbol, value) } }, + 2, { (arrayIndex == k).if { this.performVarArrayActions(i+1, k, sliderOffset, symbol, value) } } + ); + } } + } + }; + sliderOffset = sliderOffset + saveData[0][i+1][0].isSequenceableCollection.if { saveData[0][i+1].size }{ 1 }; + }) + }; + + sliderOffset = 0; + + ((reg == \synth) || (mod.miSC_isPseudoCaps)).if { + forBy(0, saveData[1].size - 2, 2, {|j| + (saveData[1][j] == symbol).if { + + synthIndex = synthCtrSynthIndices[j.div(2)]; + + (((symbolIndex == j.div(2)) && (reg == \synth)) || (mod.isAlt)).if { + arrayIndex.isNil.if { + synthCtrSliders[sliderOffset].value_(value); + clippedValue = synthCtrSliders[sliderOffset].value; + [-1,2].includes(playerSection.synthStates[synthIndex]).not.if { + msgList = msgList.add([\n_set, (synthIDs[synthIndex]).asNodeID, symbol, clippedValue]); + }; + (saveData[1][j+1]).put(4, clippedValue); + }{ + saveData[1][j+1].do {|x,k| + modType.switch( + 0, { (k <= arrayIndex).if { this.performSynthArrayActions(j+1, k, sliderOffset, value) } }, + 1, { (k >= arrayIndex).if { this.performSynthArrayActions(j+1, k, sliderOffset, value) } }, + 2, { (arrayIndex == k).if { this.performSynthArrayActions(j+1, k, sliderOffset, value) } } + ); + }; + + [-1,2].includes(playerSection.synthStates[synthIndex]).not.if { + ctrIndex = synthCtrIndices[j.div(2)]; + collectType = modType.switch( + 0, { 1 }, + 1, { ctrIndex.notNil.if { 2 }{ 3 } }, + 2, { ctrIndex.notNil.if { 0 }{ 1 } } + ); + msgList = msgList.add(this.makeMsgList(synthIDs[synthIndex], symbol, ctrIndex, arrayIndex, collectType, value, j+1)); + }; + + } + } + }; + sliderOffset = sliderOffset + saveData[1][j+1][0].isSequenceableCollection.if { saveData[1][j+1].size }{ 1 }; + }) + }; + + server.listSendBundle(nil, msgList); + ^this; + } + + makeMsgList {|synth, argName, ctrIndex, arrayIndex, collectType, value, saveDataIndex| + // synth may be Synth or nodeID + // collectType expects \up, \down or \this (from arrayIndex) + + var msgList, size = (saveData[1][saveDataIndex]).size; + + // 0: \this && (ctrIndex.notNil) - this + // 1: \down - this + down + // 2: \up && (ctrIndex.notNil) - this + up + // 3: \up && (ctrIndex.isNil) - this + up + down + + collectType.switch( + 0, { ^[\n_set, synth.asNodeID, ctrIndex + arrayIndex, value] }, + 1, { ^[\n_setn, synth.asNodeID, argName, arrayIndex + 1] ++ saveData[1][saveDataIndex][0..arrayIndex].collect(_.at(4)) }, + 2, { ^[\n_setn, synth.asNodeID, ctrIndex + arrayIndex, size - arrayIndex] ++ saveData[1][saveDataIndex][arrayIndex..(size-1)].collect(_.at(4)) }, + 3, { ^[\n_setn, synth.asNodeID, argName, size] ++ saveData[1][saveDataIndex][0..(size-1)].collect(_.at(4)) } + ); + } + + // + // updateVarSliders {|key, varNum = 0, val, indexOffset = 0, updateNow = true| + // // varNum: multiple occurence index, val may be sequenceable collection + // var varStartIndex, sliderStartIndex, slider; + // varStartIndex = this.varCtr.select({|x,i| i.even}).findAll([key]).at(varNum); + // sliderStartIndex = varCtrSizes.keep(max(0, varStartIndex)).sum; + // val.asArray.do {|x,i| + // slider = varCtrSliders[sliderStartIndex + indexOffset + i]; + // slider.value = x; + // updateNow.if { slider.action.value(slider) } + // }; + // ^this + // } + // + // updateSynthSliders {|key, synthIndex, val, indexOffset = 0, updateNow = true| + // // val may be sequenceable collection + // var keyStartIndex, sliderStartIndex, slider; + // keyStartIndex = this.synthCtr.select({|x,i| i.even}).findAll([key]) + // .select { |k| synthCtrSynthIndices[k] == synthIndex }.first; + // sliderStartIndex = synthCtrSizes.keep(max(0, keyStartIndex)).sum; + // val.asArray.do {|x,i| + // slider = synthCtrSliders[sliderStartIndex + indexOffset + i]; + // slider.value = x; + // updateNow.if { slider.action.value(slider) } + // }; + // ^this + // } + + updateSynthSliders {|key, synthIndex, val, indexOffset = 0, updateNow = true| + // val may be sequenceable collection + var sliderStartIndex, slider; + sliderStartIndex = synthSliderDict[synthIndex, key, \sliderIndex]; + + val.asArray.do {|x,i| + slider = synthCtrSliders[sliderStartIndex + indexOffset + i]; + slider.value = x; + updateNow.if { slider.action.value(slider) } + }; + ^this + } + + updateVarSliders {|key, varNum = 0, val, indexOffset = 0, updateNow = true| + // varNum: multiple occurence index, val may be sequenceable collection + var sliderStartIndex, slider; + sliderStartIndex = varSliderDict[key, \varNum, varNum, \sliderIndex]; + + val.asArray.do {|x,i| + slider = varCtrSliders[sliderStartIndex + indexOffset + i]; + slider.value = x; + updateNow.if { slider.action.value(slider) } + }; + ^this + } + + makeSaveValueMsgList {|synthIndex| + var msgList; + saveData[1].do {|item, j| + j.even.if { + (synthCtrSynthIndices[j.div(2)] == synthIndex).if { + saveData[1][j+1][0].isSequenceableCollection.not.if { + msgList = msgList.add([\n_set, (synthIDs[synthIndex]).asNodeID, + saveData[1][j], saveData[1][j+1][4]]); + }{ + msgList = msgList.add(this.makeMsgList(synthIDs[synthIndex], item, nil, + saveData[1][j+1].size - 1, 1, saveData[1][j+1].last.at(4), j+1)); + } + } + } + }; + ^msgList + } + +} + diff --git a/Classes/VarGui/extVarGui_5.sc b/Classes/VarGui/extVarGui_5.sc new file mode 100644 index 0000000..6987d8e --- /dev/null +++ b/Classes/VarGui/extVarGui_5.sc @@ -0,0 +1,74 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++SequenceableCollection { + + miSC_partitionIndex { |index| + var i = 0, sum = this.first; + ^(this.size != 0).if { + while { (index + 1) > sum }{ + i = i + 1; + (i == this.size).if { + sum = index + 1; + i = nil; + }{ + sum = sum + this[i]; + } + }; + i; + }; + } +} + ++VarGui { + envirFromSliderIndex { |sliderIndex| + var varIndex = varCtrSizes.miSC_partitionIndex(sliderIndex), envir; + ^envirs[varEnvirIndices[varIndex]]; + } + + addSliderAction { |function, type, index, envir| + // index means sliderIndex + // if type == nil suppose \var + // if index == nil suppose all indices of that type + var view, sliders, i; + type = type ?? \var; + sliders = (type == \var).if { + this.varCtrSliders + }{ + this.synthCtrSliders + }; + (index.isNil.if { sliders }{ sliders[[index]] }).do { |slider, j| + view = slider.sliderView; + i = index ?? j; + envir = envir ?? { + (type == \var).if { + this.envirFromSliderIndex(i) + }{ + currentEnvironment; + } + }; + view.mouseUpAction = function.inEnvir(envir); + }; + } +} diff --git a/Classes/VarGui/extVarGui_6.sc b/Classes/VarGui/extVarGui_6.sc new file mode 100644 index 0000000..7619557 --- /dev/null +++ b/Classes/VarGui/extVarGui_6.sc @@ -0,0 +1,176 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + ++ VarGui { + + midiBindVarSlider { |key, varNum, indexOffset, varSliderIndex, ccNum, chan, srcID| + ^MIDIFunc.cc({ |...args| + var val, num, chan, src, spec, slider; + #val, num, chan, src = args; + slider = this.varCtrSliders[varSliderIndex]; + spec = slider.controlSpec; + val = spec.map(val / 127); + + { this.updateVarSliders (key, varNum, val, indexOffset) }.defer; + }, ccNum, chan, srcID); + } + + midiBindSynthSlider { |key, synthIndex, indexOffset, synthSliderIndex, ccNum, chan, srcID| + ^MIDIFunc.cc({ |...args| + var val, num, chan, src, spec, slider; + #val, num, chan, src = args; + slider = this.synthCtrSliders[synthSliderIndex]; + spec = slider.controlSpec; + val = spec.map(val / 127); + + { this.updateSynthSliders (key, synthIndex, val, indexOffset) }.defer; + }, ccNum, chan, srcID); + } + + startMIDIlearn { + var varSliderMIDIFuncs = this.varCtrSliders.collect {}; + // store srcData to allow relearning with different input + var varSliderSrcData = this.varCtrSliders.collect {}; + var varGrouping = this.initVarCtrColorGroups; + var varKeys = this.varCtr.select { |x,i| i.even }; + var varSliderIndexKeyDict = IdentityDictionary.new; + + var synthSliderMIDIFuncs = this.synthCtrSliders.collect {}; + var synthSliderSrcData = this.synthCtrSliders.collect {}; + var synthGrouping = this.initSynthCtrColorGroups; + var synthKeys = this.synthCtr.select { |x,i| i.even }; + var synthSliderIndexKeyDict = IdentityDictionary.new; + var keyOccurences = IdentityDictionary.new, varNum, midiLearnFunc; + var keyIndex = 0, key; + + varGrouping.do { |group, envirIndex| + // need occurence number of keys for updating sliders + + group.do { |x, i| + key = varKeys[keyIndex]; + varNum = keyOccurences[key] ?? { 0 }; + + x.isKindOf(SequenceableCollection).if { + x.do { |item, j| varSliderIndexKeyDict.put(item, [key, j, varNum]) } + }{ + varSliderIndexKeyDict.put(x, [key, 0, varNum]) + }; + varNum = varNum + 1; + keyOccurences.put(key, varNum); + keyIndex = keyIndex + 1; + }; + }; + keyIndex = 0; + + synthGrouping.do { |group, synthIndex| + group.do { |x, i| + x.isKindOf(SequenceableCollection).if { + x.do { |item, j| synthSliderIndexKeyDict.put(item, [synthKeys[keyIndex], j, synthIndex]) } + }{ + synthSliderIndexKeyDict.put(x, [synthKeys[keyIndex], 0, synthIndex]) + }; + keyIndex = keyIndex + 1 + } + }; + + midiLearnFunc = MIDIFunc.cc({ |val, num, chan, src| + { + var srcData = [num, chan, src]; + this.varCtrSliders.do { |slider, i| + slider.sliderView.hasFocus.if { + varSliderMIDIFuncs[i].isKindOf(MIDIFunc).not.if { + varSliderMIDIFuncs[i] = this.midiBindVarSlider( + varSliderIndexKeyDict[i][0], + varSliderIndexKeyDict[i][2], + varSliderIndexKeyDict[i][1], + i, + num, + chan, + src + ); + this.window.onClose_(this.window.onClose <> { + varSliderMIDIFuncs[i].free; + varSliderMIDIFuncs[i] = nil; + }); + varSliderSrcData[i] = srcData; + }{ + (srcData != varSliderSrcData[i]).if { + varSliderMIDIFuncs[i].free; + varSliderMIDIFuncs[i] = nil; + varSliderSrcData[i] = srcData; + } + + } + } + }; + + this.synthCtrSliders.do { |slider, i| + slider.sliderView.hasFocus.if { + synthSliderMIDIFuncs[i].isKindOf(MIDIFunc).not.if { + synthSliderMIDIFuncs[i] = this.midiBindSynthSlider( + synthSliderIndexKeyDict[i][0], + synthSliderIndexKeyDict[i][2], + synthSliderIndexKeyDict[i][1], + i, + num, + chan, + src + ); + this.window.onClose_(this.window.onClose <> { + synthSliderMIDIFuncs[i].free; + synthSliderMIDIFuncs[i] = nil; + }); + synthSliderSrcData[i] = srcData; + }{ + (srcData != synthSliderSrcData[i]).if { + synthSliderMIDIFuncs[i].free; + synthSliderMIDIFuncs[i] = nil; + synthSliderSrcData[i] = srcData; + } + + } + } + } + }.defer + }, nil); + // use instance variable in next version + this.addDependant('midiLearnFunc' -> midiLearnFunc) + } + + stopMIDIlearn { + // use instance variable in next version + var assocs = this.dependants.select { |d| d.isKindOf(Association) and: { d.key == 'midiLearnFunc' } }; + assocs.do { |a| + a.value.free; + this.dependants.remove(a) + } + } + +} + + + + + + diff --git a/Classes/WaveFolding/SmoothClip.sc b/Classes/WaveFolding/SmoothClip.sc new file mode 100755 index 0000000..aab527d --- /dev/null +++ b/Classes/WaveFolding/SmoothClip.sc @@ -0,0 +1,150 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// Dynamic waveshaping with line and sine fragment: + +// As math is easier for the case lo == -1, hi == +1, +// we regard this case first and do linear mapping / remapping for other bounds: +// Take tangent through zero for abs(x) < given tangent point x0 +// and sine fragment values above x0 and below 1, clip for abs values above 1. +// The transfer function is antisymmetrical, its slope tends to 0 for x -> +-1. +// Source and target range can be shifted with fromLo, fromHi, toLo, toHi. + +// The sine fragment with minimum at x = 1 and f(1) = 1 has the form +// f(x) = p sin(pi/2 x) - p + 1 +// f'(x) = p pi/2 cos(pi/2 x) + +// Thus the tangent through x0 has the form +// p pi/2 cos(pi/2 x0) x = p sin(pi/2 x0) - p + 1 + +// So for a given tangent point 0 <= x0 < 1 we can calculate p as below. + +// 'amount' indicates the amount of smoothing: +// amount == 0: in signal isn't altered +// amount == 1: in signal is shaped by full sine fragment +// amount expected to be >= 0 and <= 1 + + +SmoothClipS : UGen { + + *ar { |in, lo = -1, hi = 1, amount = 0.5, delta = 0.00001| + ^this.multiNew(\audio, in, lo, hi, amount, delta) + } + + *kr { |in, lo = -1, hi = 1, amount = 0.5, delta = 0.00001| + ^this.multiNew(\control, in, lo, hi, amount, delta) + } + + *new1 { |rate, in, lo = -1, hi = 1, amount = 0.5, delta = 0.00001| + var w, p, x0, slope, case, realSmooth, linSig, sineSig, + fromFactor, fromOffset, dif, sum, selector = this.methodSelectorForRate(rate); + + // map to unit range + dif = hi - lo; + sum = hi + lo; + // avoid zero division with lo = hi + dif = max(dif.abs, delta) * ((dif >= DC.perform(selector, 0)) * 2 - 1); + fromFactor = 2 / dif; + fromOffset = fromFactor * hi.neg + 1; + + in = fromFactor * in + fromOffset; + + // this excludes the border case p -> inf for x0 -> 1 + x0 = min(1 - amount, 1 - delta); + + w = x0 * pi * 0.5; + p = 1 / (1 - sin(w) + (w * cos(w))); + slope = p * pi * 0.5 * cos(w); + linSig = slope * in; + sineSig = sin(in.abs * pi * 0.5) - 1 * p + 1 * in.sign; + + // distinct and remap to passed range + case = (in.abs > x0) + (in.abs >= 1) + (in <= -1); + ^Select.perform(selector, case, [ + linSig, + sineSig, + DC.perform(selector, 1), + DC.perform(selector, -1) + ] * dif + sum * 0.5); + } +} + + +// Dynamic waveshaping with quadratic polynomial. + +// As math is easier for the case lo == -1, hi == +1, +// we regard this case first and do linear mapping / remapping for other bounds: +// Take tangent through zero for abs(x) < given tangent point x0 +// and parable values above x0 and below 1, clip for abs values above 1. +// The transfer function is antisymmetrical, its slope tends to 0 for x -> +-1. + +// 'amount' indicates the amount of smoothing: +// amount == 0: in signal isn't altered +// amount == 1: in signal is shaped by full parable fragment +// amount expected to be >= 0 and <= 1 + +SmoothClipQ : UGen { + *ar { |in, lo = -1, hi = 1, amount = 0.5, delta = 0.00001| + ^this.multiNew(\audio, in, lo, hi, amount, delta) + } + + *kr { |in, lo = -1, hi = 1, amount = 0.5, delta = 0.00001| + ^this.multiNew(\control, in, lo, hi, amount, delta) + } + + *new1 { |rate, in, lo = -1, hi = 1, amount = 0.5, delta = 0.00001| + var p, x0, slope, realSmooth, linSig, parableSig, case, + fromFactor, fromOffset, dif, sum, selector = this.methodSelectorForRate(rate); + + // map to unit range + dif = hi - lo; + sum = hi + lo; + // avoid zero division with lo = hi + dif = max(dif.abs, delta) * ((dif >= DC.perform(selector, 0)) * 2 - 1); + fromFactor = 2 / dif; + fromOffset = fromFactor * hi.neg + 1; + + in = fromFactor * in + fromOffset; + + // this excludes the border case p -> inf for x0 -> 1 + x0 = min(1 - amount, 1 - delta); + + p = 1 / (x0 * x0 - 1); + slope = 2 * p * (x0 - 1); + + linSig = slope * in; + parableSig = (in.abs - 1).squared * p + 1 * in.sign; + + // distinct and remap to passed range + case = (in.abs > x0) + (in.abs >= 1) + (in <= -1); + ^Select.perform(selector, case, [ + linSig, + parableSig, + DC.perform(selector, 1), + DC.perform(selector, -1) + ] * dif + sum * 0.5); + } +} + diff --git a/Classes/WaveFolding/SmoothFold.sc b/Classes/WaveFolding/SmoothFold.sc new file mode 100755 index 0000000..aed9374 --- /dev/null +++ b/Classes/WaveFolding/SmoothFold.sc @@ -0,0 +1,157 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +// SmoothFoldS uses sine smoothing SmoothClipS at corners + +SmoothFoldS : UGen { + *ar { |in, lo = -1, hi = 1, foldRange = 1, smoothAmount = 0.5, delta = 0.00001| + ^this.multiNew(\audio, in, lo, hi, foldRange, smoothAmount, delta) + } + + *kr { |in, lo = -1, hi = 1, foldRange = 1, smoothAmount = 0.5, delta = 0.00001| + ^this.multiNew(\control, in, lo, hi, foldRange, smoothAmount, delta) + } + + *new1 { |rate, in, lo = -1, hi = 1, foldRange = 1, smoothAmount = 0.5, delta = 0.00001| + var case, foldRangeAbs, thr_1, thr_2, selector = this.methodSelectorForRate(rate); + + // case = 0 for inSig in [lo, hi], 1 for inSig < lo and 2 for inSig > hi + case = (in < lo) + (in > hi * 2); + + // thr_1 is the upper limit of the lower fold range + // thr_2 is the lower limit of the upper fold range + // thr_2 can be smaller than thr_1 + + foldRangeAbs = (hi - lo) * foldRange; + thr_1 = lo + foldRangeAbs; + thr_2 = hi - foldRangeAbs; + + ^Select.perform(selector, case, [ + SmoothClipS.perform(selector, in, lo, hi, smoothAmount, delta), + SmoothClipS.perform(selector, Fold.perform(selector, in, lo, thr_1), lo, thr_1, smoothAmount, delta), + SmoothClipS.perform(selector, Fold.perform(selector, in, thr_2, hi), thr_2, hi, smoothAmount, delta) + ]); + } +} + +// variant with two border ranges + +SmoothFoldS2 : UGen { + *ar { |in, lo = -1, hi = 1, foldRangeLo = 1, foldRangeHi = 1, smoothAmount = 0.5, delta = 0.00001| + ^this.multiNew(\audio, in, lo, hi, foldRangeLo, foldRangeHi, smoothAmount, delta) + } + + *kr { |in, lo = -1, hi = 1, foldRangeLo = 1, foldRangeHi = 1, smoothAmount = 0.5, delta = 0.00001| + ^this.multiNew(\control, in, lo, hi, foldRangeLo, foldRangeHi, smoothAmount, delta) + } + + *new1 { |rate, in, lo = -1, hi = 1, foldRangeLo = 1, foldRangeHi = 1, smoothAmount = 0.5, delta = 0.00001| + var case, rangeAbs, thr_1, thr_2, selector = this.methodSelectorForRate(rate); + + // case = 0 for inSig in [lo, hi], 1 for inSig < lo and 2 for inSig > hi + case = (in < lo) + (in > hi * 2); + + // thr_1 is the upper limit of the lower fold range + // thr_2 is the lower limit of the upper fold range + // thr_2 can be smaller than thr_1 + + rangeAbs = hi - lo; + thr_1 = lo + (rangeAbs * foldRangeLo); + thr_2 = hi - (rangeAbs * foldRangeHi); + + ^Select.perform(selector, case, [ + SmoothClipS.perform(selector, in, lo, hi, smoothAmount, delta), + SmoothClipS.perform(selector, Fold.perform(selector, in, lo, thr_1), lo, thr_1, smoothAmount, delta), + SmoothClipS.perform(selector, Fold.perform(selector, in, thr_2, hi), thr_2, hi, smoothAmount, delta) + ]); + } +} + + +// SmoothFoldQ uses quadratic smoothing SmoothClipQ at corners + +SmoothFoldQ : UGen { + *ar { |in, lo = -1, hi = 1, foldRange = 1, smoothAmount = 0.5, delta = 0.00001| + ^this.multiNew(\audio, in, lo, hi, foldRange, smoothAmount, delta) + } + + *kr { |in, lo = -1, hi = 1, foldRange = 1, smoothAmount = 0.5, delta = 0.00001| + ^this.multiNew(\control, in, lo, hi, foldRange, smoothAmount, delta) + } + + *new1 { |rate, in, lo = -1, hi = 1, foldRange = 1, smoothAmount = 0.5, delta = 0.00001| + var case, foldRangeAbs, thr_1, thr_2, selector = this.methodSelectorForRate(rate); + + // case = 0 for inSig in [lo, hi], 1 for inSig < lo and 2 for inSig > hi + case = (in < lo) + (in > hi * 2); + + // thr_1 is the upper limit of the lower fold range + // thr_2 is the lower limit of the upper fold range + // thr_2 can be smaller than thr_1 + + foldRangeAbs = (hi - lo) * foldRange; + thr_1 = lo + foldRangeAbs; + thr_2 = hi - foldRangeAbs; + + ^Select.perform(selector, case, [ + SmoothClipQ.perform(selector, in, lo, hi, smoothAmount, delta), + SmoothClipQ.perform(selector, Fold.perform(selector, in, lo, thr_1), lo, thr_1, smoothAmount, delta), + SmoothClipQ.perform(selector, Fold.perform(selector, in, thr_2, hi), thr_2, hi, smoothAmount, delta) + ]); + } +} + +// variant with two border ranges + +SmoothFoldQ2 : UGen { + *ar { |in, lo = -1, hi = 1, foldRangeLo = 1, foldRangeHi = 1, smoothAmount = 0.5, delta = 0.00001| + ^this.multiNew(\audio, in, lo, hi, foldRangeLo, foldRangeHi, smoothAmount, delta) + } + + *kr { |in, lo = -1, hi = 1, foldRangeLo = 1, foldRangeHi = 1, smoothAmount = 0.5, delta = 0.00001| + ^this.multiNew(\control, in, lo, hi, foldRangeLo, foldRangeHi, smoothAmount, delta) + } + + *new1 { |rate, in, lo = -1, hi = 1, foldRangeLo = 1, foldRangeHi = 1, smoothAmount = 0.5, delta = 0.00001| + var case, rangeAbs, thr_1, thr_2, selector = this.methodSelectorForRate(rate); + + // case = 0 for inSig in [lo, hi], 1 for inSig < lo and 2 for inSig > hi + case = (in < lo) + (in > hi * 2); + + // thr_1 is the upper limit of the lower fold range + // thr_2 is the lower limit of the upper fold range + // thr_2 can be smaller than thr_1 + + rangeAbs = hi - lo; + thr_1 = lo + (rangeAbs * foldRangeLo); + thr_2 = hi - (rangeAbs * foldRangeHi); + + ^Select.perform(selector, case, [ + SmoothClipQ.perform(selector, in, lo, hi, smoothAmount, delta), + SmoothClipQ.perform(selector, Fold.perform(selector, in, lo, thr_1), lo, thr_1, smoothAmount, delta), + SmoothClipQ.perform(selector, Fold.perform(selector, in, thr_2, hi), thr_2, hi, smoothAmount, delta) + ]); + } +} diff --git a/Classes/ZeroX/TZeroXBufRd.sc b/Classes/ZeroX/TZeroXBufRd.sc new file mode 100644 index 0000000..09dbfb3 --- /dev/null +++ b/Classes/ZeroX/TZeroXBufRd.sc @@ -0,0 +1,185 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +TZeroXBufRd : AbstractZeroXBufRd { + + *ar { |sndBuf, zeroXBuf, bufMix, trig, zeroX = 0, xNum = 1, xRep = 1, power = 1, mul = 1, add = 0, + rate = 1, dir = 1, interpl = 4, overlapSize = 10, length = inf, maxTime = inf, + att = 0, rel = 1, curve = -4, doneAction = 0| + var thisX, nextX, baseX, sndBufIndex, phase, phaseTrig, phaseOffset, time, env, sndBufs, + zeroXBufs, zeroXDiff, rateSig, dirSig, sndBufIndexSig, bufSize, zeroXBufSize, + outSize, args, trigCount, sig; + + args = [ + zeroX, xNum, xRep, power, mul, add, rate, dir, interpl, overlapSize, + length, maxTime, att, rel, curve, doneAction + ]; + + bufSize = (sndBuf.size == 0).if { 1 }{ sndBuf.size }; + zeroXBufSize = (zeroXBuf.size == 0).if { 1 }{ zeroXBuf.size }; + + (zeroXBufSize != bufSize).if { + SimpleInitError("sizes of sndBuf and zeroXBuf (single channel buffers!) must correspond") + .class_(ZeroXBufWr).throw + }; + + // if bufMix is undefined then outSize = bufSize + // else outSize is given by bufMix size + #bufMix, outSize = this.checkBufMix(bufMix, bufSize); + + // args might be passd as Function of drate ugens, then expand + #zeroX, xNum, xRep, power, mul, add, rate, dir, interpl, overlapSize, + length, maxTime, att, rel, curve, doneAction = + this.unbubbleInputIfNecessary(*args); + + this.checkForWrongDrateInput(outSize, zeroX, xNum, xRep, power, mul, add, rate, dir); + #zeroX, xNum, xRep, power, mul, add, rate, dir = + [zeroX, xNum, xRep, power, mul, add, rate, dir] + .collect { |x| x.miSC_Dmultiply2(outSize) }; + + // need everything in outSize + mul = outSize.collect { |i| mul.miSC_maybeWrapAt(i) }; + add = outSize.collect { |i| add.miSC_maybeWrapAt(i) }; + power = outSize.collect { |i| power.miSC_maybeWrapAt(i) }; + + trig = outSize.collect { |i| trig.miSC_maybeWrapAt(i) }; + xNum = outSize.collect { |i| xNum.miSC_maybeWrapAt(i) }; + xRep = outSize.collect { |i| xRep.miSC_maybeWrapAt(i) }; + + trigCount = outSize.collect { |i| PulseCount.ar(trig[i]) - 1 }; + + overlapSize = outSize.collect { |i| overlapSize.miSC_maybeWrapAt(i) }; + length = outSize.collect { |i| length.miSC_maybeWrapAt(i) }; + maxTime = outSize.collect { |i| maxTime.miSC_maybeWrapAt(i) }; + att = outSize.collect { |i| att.miSC_maybeWrapAt(i) }; + rel = outSize.collect { |i| rel.miSC_maybeWrapAt(i) }; + curve = outSize.collect { |i| curve.miSC_maybeWrapAt(i) }; + doneAction = outSize.collect { |i| doneAction.miSC_maybeWrapAt(i) }; + + // interpl always refers to sndBufs, not to bufMix size + interpl = bufSize.collect { |i| interpl.miSC_maybeWrapAt(i) }; + + time = Sweep.ar(Impulse.ar(0)); + + env = EnvGen.ar( + Env.asr(att, 1, rel, curve), + (trigCount + 1 <= length) * (time <= maxTime), + doneAction: doneAction + ); + + // core: + // a bit easier than with ZeroXBufRd, Dunique isn't needed here. + // Some drate ugens are polled more than once, + // we can "multiply" them with Dstutter. + + rate = outSize.collect { |i| + var r = rate.miSC_maybeWrapAt(i); + r.isNumber.if { Dstutter(inf, r) }{ Dstutter(2, r) }; + }; + dir = outSize.collect { |i| + var d = dir.miSC_maybeWrapAt(i); + d.isNumber.if { Dstutter(inf, d) }{ Dstutter(3, d) }; + }; + + zeroXBufs = zeroXBuf.asArray; + sndBufs = sndBuf.asArray; + + // buffer switch option with bufMix + sndBufIndex = bufMix.collect { |b| + b.isNumber.if { Dstutter(inf, b) }{ Dstutter(3, b) } + }; + + // determine distance between zero crossings + zeroX = outSize.collect { |i| Dstutter(2, zeroX.miSC_maybeWrapAt(i)) }; + + thisX = outSize.collect { |i| + Dstutter(2, Dbufrd(Dswitch1(zeroXBufs, sndBufIndex[i]), zeroX[i])); + }; + nextX = outSize.collect { |i| + Dstutter(2, Dbufrd(Dswitch1(zeroXBufs, sndBufIndex[i]), zeroX[i] + xNum[i])); + }; + + // if dir = -1, base is second zero crossing + zeroXDiff = Dstutter(1, max((nextX - thisX / rate).abs.round), 2); + baseX = Dstutter(1, thisX * (1 + dir) + (nextX * (1 - dir)) / 2); + + sig = outSize.collect { |i| + overlapSize[i].collect { |j| + var localTrig, localZeroXDiff, localPhaseOffset, localRate, localDir, + localXRep, localOff, localGate, localGateDur, localPhase, localSndBufIndex, + localPower, localMul, localAdd, parBuf; + + localTrig = trig[i] * ((trigCount[i] % (overlapSize[i]) - DC.ar(j)).abs < 0.01); + + localZeroXDiff = Demand.ar(localTrig, 0, zeroXDiff[i]); + localPhaseOffset = Demand.ar(localTrig, 0, baseX[i]); + localRate = Demand.ar(localTrig, 0, rate[i]); + localDir = Demand.ar(localTrig, 0, dir[i]); + localXRep = xRep[i].isNumber.if { xRep[i] }{ Demand.ar(localTrig, 0, xRep[i]) }; + + // gate for wavesets, workaround with Sweep because of Trig1 init issue + localGateDur = localXRep * localZeroXDiff * SampleDur.ir; + localGate = Sweep.ar(localTrig) < localGateDur; + + // the combination of localGate and Sweep + modulo implements repetitions + localPhase = Sweep.ar( + localTrig, + SampleRate.ir * localRate + ) % (localZeroXDiff * localRate) * localDir.sign + localPhaseOffset; + + localSndBufIndex = Demand.ar(localTrig, 0, sndBufIndex[i]); + + localPower = power[i].isNumber.if { power[i] }{ Demand.ar(localTrig, 0, power[i]) }; + localMul = mul[i].isNumber.if { mul[i] }{ Demand.ar(localTrig, 0, mul[i]) }; + localAdd = add[i].isNumber.if { add[i] }{ Demand.ar(localTrig, 0, add[i]) }; + + bufMix[i].isNumber.if { + BufRd.ar(1, sndBufs[bufMix[i]], localPhase, 0, interpl[bufMix[i]]) + }{ + // in case of switched buffers we must select from all buffer readers + // (BufRd doesn't allow ar buffer switching) + parBuf = bufSize.collect { |k| BufRd.ar(1, sndBufs[k], localPhase, 0, interpl[k]) }; + Select.ar(localSndBufIndex, parBuf) + } ** localPower * localMul + localAdd * localGate; + }.sum + } * env; + + ^sig.unbubble + } + +} + + ++Object { + miSC_Dmultiply2 { ^this } +} + ++Function { + miSC_Dmultiply2 { |n| ^this ! n } +} + ++SequenceableCollection { + miSC_Dmultiply2 { |n| ^{ |i| this.wrapAt(i) } ! n } +} diff --git a/Classes/ZeroX/ZeroXBufRd.sc b/Classes/ZeroX/ZeroXBufRd.sc new file mode 100644 index 0000000..3b6821c --- /dev/null +++ b/Classes/ZeroX/ZeroXBufRd.sc @@ -0,0 +1,225 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +AbstractZeroXBufRd : UGen { + + *checkBufMix { |bufMix, bufSize| + var outSize; + bufMix.isNil.if { + bufMix = (0..bufSize-1); + outSize = bufSize; + }{ + bufMix.isKindOf(SequenceableCollection).if { + outSize = bufMix.size + }{ + outSize = 1; + bufMix = bufMix.asArray + } + }; + ^[bufMix, outSize] + } + + *unbubbleInputIfNecessary { |... argList| + ^argList.collect { |item| + ((item.isKindOf(SequenceableCollection)) and: { item.size == 1 }).if { + item[0] + }{ + item + }; + } + } + + // restrict to possible drate arguments + *checkForWrongDrateInput { |maxArgSize ... args| + + if ((maxArgSize > 1) and: { + args.any { |x| + x.isKindOf(DUGen) or: { + x.isKindOf(SequenceableCollection) and: { + // do deep check, but only for DUGens + (x.size < maxArgSize) and: { x.flat.any { |y| y.isKindOf(DUGen) } } + } + } + } + }) { + SimpleInitError("For multichannel expansion demand rate " ++ + "ugens, that are needed more than once, have to be " ++ + "wrapped into Functions, see help file" + ).class_(this).throw + }; + } + + *dUniquifyIfNecessary { |item, dUniqueBufSize| + ^case + { item.isKindOf(DUGen) }{ Dunique(item, dUniqueBufSize) } + { item.isKindOf(UGen) }{ item } + { true }{ Dstutter(inf, item) } + } +} + + +ZeroXBufRd : AbstractZeroXBufRd { + + *ar { |sndBuf, zeroXBuf, bufMix, zeroX = 0, power = 1, mul = 1, add = 0, rate = 1, rateMul = 1, dir = 1, + interpl = 4, dUniqueBufSize = 1048576, length = inf, maxTime = inf, att = 0, rel = 1, + curve = -4, doneAction = 0| + var thisX, nextX, baseX, sndBufIndex, phase, phaseTrig, phaseOffset, count, time, + zeroXDiff, rateSig, rateMulSig, dirSig, sndBufIndexSig, bufSize, zeroXBufSize, + outSize, args, sig, env, sndBufs, zeroXBufs; + + args = [ + zeroX, power, mul, add, rate, rateMul, dir, interpl, dUniqueBufSize, + length, maxTime, att, rel, curve, doneAction + ]; + + bufSize = (sndBuf.size == 0).if { 1 }{ sndBuf.size }; + zeroXBufSize = (zeroXBuf.size == 0).if { 1 }{ zeroXBuf.size }; + + (zeroXBufSize != bufSize).if { + SimpleInitError("sizes of sndBuf and zeroXBuf (single channel buffers!) must correspond") + .class_(ZeroXBufWr).throw + }; + + // if bufMix is undefined then outSize = bufSize + // else outSize is given by bufMix size + #bufMix, outSize = this.checkBufMix(bufMix, bufSize); + + // args might be passd as Function of drate ugens, then expand + #zeroX, power, mul, add, rate, rateMul, dir, interpl, dUniqueBufSize, + length, maxTime, att, rel, curve, doneAction = + this.unbubbleInputIfNecessary(*args); + this.checkForWrongDrateInput(outSize, zeroX, power, mul, rateMul, add, rate, dir); + #zeroX, power, mul, add, rate, rateMul, dir, interpl, dUniqueBufSize = + [zeroX, power, mul, add, rate, rateMul, dir, interpl, dUniqueBufSize] + .collect { |x| x.miSC_Dmultiply2(outSize) }; + + // need everything in outSize + mul = outSize.collect { |i| mul.miSC_maybeWrapAt(i) }; + add = outSize.collect { |i| add.miSC_maybeWrapAt(i) }; + power = outSize.collect { |i| power.miSC_maybeWrapAt(i) }; + + dUniqueBufSize = outSize.collect { |i| dUniqueBufSize.miSC_maybeWrapAt(i) }; + length = outSize.collect { |i| length.miSC_maybeWrapAt(i) }; + maxTime = outSize.collect { |i| maxTime.miSC_maybeWrapAt(i) }; + att = outSize.collect { |i| att.miSC_maybeWrapAt(i) }; + rel = outSize.collect { |i| rel.miSC_maybeWrapAt(i) }; + curve = outSize.collect { |i| curve.miSC_maybeWrapAt(i) }; + doneAction = outSize.collect { |i| doneAction.miSC_maybeWrapAt(i) }; + + // interpl always refers to sndBufs, not to bufMix size + interpl = bufSize.collect { |i| interpl.miSC_maybeWrapAt(i) }; + + // begin of the subtle part: + // drate ugens are polled more than once, + // if they are polled in sync we can "multiply" them with Dstutter + // otherwise we need Dunique, this is the case for rate and sndBufIndex (bufMix), + // which are polled again after the half waveset length for which they are needed + + rate = outSize.collect { |i| + var r = rate.miSC_maybeWrapAt(i); + this.dUniquifyIfNecessary(r, dUniqueBufSize[i]); + }; + rateMul = outSize.collect { |i| + var rMul = rateMul.miSC_maybeWrapAt(i); + this.dUniquifyIfNecessary(rMul, dUniqueBufSize[i]); + }; + dir = outSize.collect { |i| + var d = dir.miSC_maybeWrapAt(i); + this.dUniquifyIfNecessary(d, dUniqueBufSize[i]); + }; + + zeroXBufs = zeroXBuf.asArray; + sndBufs = sndBuf.asArray; + + // buffer switch option with bufMix + + sndBufIndex = bufMix.asArray.collect { |b, i| + this.dUniquifyIfNecessary(b, dUniqueBufSize[i]); + }; + + // determine distance between zero crossings + zeroX = outSize.collect { |i| Dstutter(2, zeroX.miSC_maybeWrapAt(i)) }; + + thisX = outSize.collect { |i| + Dstutter(2, Dbufrd(Dswitch1(zeroXBufs, sndBufIndex[i]), zeroX[i])) + }; + + nextX = outSize.collect { |i| + Dstutter(2, Dbufrd(Dswitch1(zeroXBufs, sndBufIndex[i]), zeroX[i] + 1)) + }; + + // if dir = -1, baseX is second zero crossing + baseX = Dstutter(1, thisX * (1 + dir) + (nextX * (1 - dir)) / 2); + // take half wavesets of minimum length 2, otherwise no trigger + zeroXDiff = Dstutter(1, max((nextX - thisX / (rate * rateMul)).abs.round, 2)); + + // trigger for syncing, add 1 to baseX as zero should trigger too ... + phaseTrig = TDuty.ar(SampleDur.ir * zeroXDiff, 0, baseX + 1); + // ... need value again, correct + phaseOffset = Latch.ar(phaseTrig - 1, phaseTrig); + + count = PulseCount.ar(phaseTrig); + time = Sweep.ar(Impulse.ar(0)); + + env = EnvGen.ar( + Env.asr(att, 1, rel, curve), + (count <= length) * (time <= maxTime), + doneAction: doneAction + ); + + // Demand doesn't multichannel expand + rateSig = phaseTrig.collect { |x, i| Demand.ar(x, 0, rate[i]) }; + rateMulSig = phaseTrig.collect { |x, i| Demand.ar(x, 0, rateMul[i]) }; + dirSig = phaseTrig.collect { |x, i| Demand.ar(x, 0, dir[i]) }; + + // phasor for waveset reading + phase = Sweep.ar( + phaseTrig, + SampleRate.ir * rateSig * rateMulSig * dirSig + ) + phaseOffset; + + power = power.collect { |p, i| p.isNumber.if { p }{ Demand.ar(phaseTrig[i], 0, p) } }; + mul = mul.collect { |m, i| m.isNumber.if { m }{ Demand.ar(phaseTrig[i], 0, m) } }; + add = add.collect { |a, i| a.isNumber.if { a }{ Demand.ar(phaseTrig[i], 0, a) } }; + + sndBufIndexSig = phaseTrig.collect { |x, i| Demand.ar(x, 0, sndBufIndex[i]) }; + + sig = outSize.collect { |i| + var parBuf; + bufMix[i].isNumber.if { + BufRd.ar(1, sndBufs[bufMix[i]], phase[i], 0, interpl[bufMix[i]]) + }{ + // in case of switched buffers we must select from all buffer readers + // (BufRd doesn't allow ar buffer switching) + parBuf = bufSize.collect { |j| BufRd.ar(1, sndBufs[j], phase[i], 0, interpl[j]) }; + Select.ar(sndBufIndexSig[i], parBuf) + } + }; + + sig = sig ** power.abs * mul + add * env; + ^sig.unbubble + } +} + diff --git a/Classes/ZeroX/ZeroXBufWr.sc b/Classes/ZeroX/ZeroXBufWr.sc new file mode 100644 index 0000000..59f61f7 --- /dev/null +++ b/Classes/ZeroX/ZeroXBufWr.sc @@ -0,0 +1,155 @@ + +/* + This file is part of miSCellaneous, a program library for SuperCollider 3 + + Created: 2020-07-08, version 0.24 + Copyright (C) 2009-2020 Daniel Mayer + Email: daniel-mayer@email.de + URL: http://daniel-mayer.at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +ZeroXBufWr : UGen { + + *ar { |in, sndBuf, zeroXBuf, startWithZeroX = 0, adjustZeroXs = 0, doneAction = 0| + var size, bufSize, zeroXBufSize, signToggle, sampleCount, zeroX, zeroXCount, + sampleCountSteps, zeroXPositions, newZeroXs, heldZeroXs, write, out, gate, initTrig; + + size = in.size; + + bufSize = (sndBuf.size == 0).if { 1 }{ sndBuf.size }; + zeroXBufSize = (zeroXBuf.size == 0).if { 1 }{ zeroXBuf.size }; + + (zeroXBufSize != bufSize).if { + SimpleInitError("sizes of sndBuf and zeroXBuf (single channel buffers!) must correspond") + .class_(ZeroXBufWr).throw + }; + + (max(size, 1) != bufSize).if { + SimpleInitError("size of in must correpond to number of single channel buffers") + .class_(ZeroXBufWr).throw + }; + + // signToggle always starts with 1, makes it easier to handle all cases + initTrig = Impulse.ar(0); + signToggle = Select.ar( + Latch.ar(in > 0, initTrig), + [in <= 0, in > 0] + ); + + // need to start with index 0, thus '- 1' + sampleCount = Sweep.ar(0, SampleRate.ir) - 1; + + // zeroX indicates triggers for the counter: + // differentiate behaviour at start and later + // according to flags startWithZeroX and adjustZeroXs + + // adjustZeroXs = -1: indicate all zeroXs, dont't write sound buffer + // adjustZeroXs = 0: indicate all zeroXs, write sound buffer + // adjustZeroXs = 1: indicate all zeroXs, write sound buffer, set to 0 there + // adjustZeroXs = 2: indicate zeroXs with min distance of 2, set to 0 there + + sndBuf = sndBuf.asArray; + adjustZeroXs = adjustZeroXs.asArray; + + zeroX = sndBuf.collect { |buf, i| + var sig, sampleToggle, flag = adjustZeroXs.miSC_maybeWrapAt(i); + + sig = Select.ar( + sampleCount.sign, + [ + // with flag 1 always start with zeroX + DC.ar(startWithZeroX.miSC_maybeWrapAt(i)), + // slope of signToggle indicates zeroX, if not at start + Slope.ar(signToggle.miSC_maybeWrapAt(i)).abs + ] + ) > 0; + + (flag == 2).if { + // take zeroXs with minimum distance 2 + sampleToggle = Duty.ar(SampleDur.ir, sig, Dseq([1, 0], inf)); + sig = sig * sampleToggle + }; + + sig + }; + + // workaround due to a Integrator init bug + zeroXCount = Integrator.ar( + Select.ar( + sampleCount.sign, + [DC.ar(0), zeroX] + ) + ) + Latch.ar(zeroX, initTrig); + zeroXCount = zeroXCount.asArray - 1; + + newZeroXs = Select.ar( + zeroX, + [DC.ar(0), sampleCount] + ).asArray; + + // if zeroXCount doesn't increase, the position shouldn't either: + // BufWr overwrites the same buffer position with the same value + + // 1 - zeroX is a trigger for start of same sign + heldZeroXs = Latch.ar(Delay1.ar(newZeroXs), 1 - zeroX).asArray; + + zeroXPositions = Select.ar( + zeroX, + [heldZeroXs, sampleCount] + ).asArray; + + out = in; + in = in.asArray; + zeroXBuf = zeroXBuf.asArray; + + write = DC.ar(0); + sndBuf.do { |buf, i| + var flag = adjustZeroXs.miSC_maybeWrapAt(i); + + case + // dont't write sound buffer + { flag == -1 }{ } + + // write sound buffer from in signal + { flag == 0 }{ write = (BufWr.ar(in[i], buf, sampleCount, 0) 0 } }{ + // ignore garbage + ((zeroXArray[count] >= 0) and: { zeroXArray[count] < this.numFrames }).if { + this.set(zeroXArray[count], 0) + }; + lastZeroX = zeroXArray[count]; + count = count + 1; + }; + "zero setting messages sent".postln; + action.value(zeroXArray); + } + ) + } +} + + ++ SequenceableCollection { + adjustZeroXs { |zeroXBufs, action| + this.do { |buf, i| buf.adjustZeroXs(zeroXBufs[i], action.miSC_maybeWrapAt(i)) } + } +} + + + \ No newline at end of file diff --git a/Help/Buffer Granulation.html b/Help/Buffer Granulation.html new file mode 100755 index 0000000..1d243f6 --- /dev/null +++ b/Help/Buffer Granulation.html @@ -0,0 +1,1714 @@ + + + + + + + + + + + +

Buffer Granulation different approaches to buffer granulation with gui examples

+


+

Part of: miSCellaneous

+


+

See also: Live Granulation, Introduction to miSCellaneous, VarGui, VarGui shortcut builds, PLx suite, PbindFx, kitchen studies, Sieves and Psieve patterns, PSPdiv, DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFan, DXFanOut, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

As with many things in SC work can be done by language or server. Speaking about granulation in general this mainly concerns the control of single grains, their rate, timing, length and other parameters. Regarding buffer granulation specifically this task can e.g. be taken over by ugens like TGrains and GrainBuf, the latter additionally accepting a grain envelope arg. The SC plugin distribution contains further variations (see BhobUGens, JoshUGens). By using granulation ugens in a SynthDef granular textures can be produced with a single Synth, including grain parameter sequencing with demand rate ugens, the DX suite opens additional genuine options in this regard. Alternatively SynthDefs for single grains can be defined with PlayBuf or BufRd in order to control the whole buffer granulation process from language side, using Patterns, Tasks or Routines. What is the best way? To a large extent this is a question of personal preference. Regarding CPU performance granulation ugens have an advantage, whereas e.g. pattern-driven granulation allows concise control over the sequencing of granulation parameters in a clear syntax. If pattern-driven granulation is becoming CPU-critical you might want to consider the event type \grain, a lightweight variant of default type \note (see comment in the source file Event.sc). For granulation with Tasks see example 3 in VarGui.  

+


+

Also hybrid strategies are possible, e.g. language controlled setting of a single granulation Synth or involving extra control synths in a language-driven granulation process, further options are Pspawner / Pspawn and Wavesets. VarGui can be used to integrate these setups in single GUIs. Together with this file (miSCellaneous v0.7) I reinvented a color grouping option that overrides automatic color grouping (VarGui, Ex. 7). The latter is based on the logical structure of ordinary and array controls, synths and environments. This is useful for VarGuis up to a medium number of controls per Synth / Pattern / Task and also for a large number of such items, but it doesn't handle cases well where there are many controls per item. Not barely an aesthetic detail, it is much more convenient for experimenting to group all sliders of a Synth that, say, have to do with a bandpass filter, within one color. 

+


+

Types of variables

+


+

In general interpreter variables are prefered in examples below, wherever possible. If evaluated with example code only their values are passed and further changing of used variables doesn't affect already generated gui instances. On the contrary repeated evaluation of an interpreter variable, e.g. from a Pfunc, is unsafe - variable's value could change while gui has not yet been closed - so in concerned examples values are passed to the event definition, in Ex. 2c a variable is declared for the same reason. 

+

The use of environmental variables is following the way VarGui is handling them. Per default every EventStreamPlayer derived from a passed Pattern is run in a separate newly generated Environment, where variables are being set and Streams from Pfuncs and PLx patterns are reading from. See Event patterns and Functions, PLx suite and VarGui.

+


+


+

WARNING: 

+


+

1.) Be careful with amplitudes, especially with buffers you haven't granulated before !

+

Also keep in mind that a granular cloud moving through a buffer can suddenly become louder and other controls than amp (e.g. buffer position, trigger rate, bandpass parameters) can cause a raise of amplitude too.

+


+

2.) I haven't used below setups for live performances. Although all of them work stable for me as they are, in general hangs can occasionally happen with pattern-driven setups. Often this can be tracked down to sequences of extremely short event durations (and/or long grain durations). Where this can happen as a side-effect, thresholds can be built in, e.g. example 2d (Wavesets) has a parameter maxTrigRate. 

+

Another possible source of hangs is careless deep nesting of Patterns where mistakes can easily occur. Starting with clear Pattern structures is recommended - and if more complications are involved: testing without sound first, after saving your patch, might be a good idea.

+


+


+

NOTE: 

+


+

All variants from this tutorial can be applied to a buffer, which is occasionally (or continuously) filled with live input. Vice versa variants from Live Granulation can of course be applied to any signal, thus also to any playback of a buffer. 

+


+

All examples below expect mono buffers. Buffer paths refering to the included sample suppose that you have installed via quarks or moved miSCellaneous lib into the user or system extensions directory directly (not into a subfolder), if not so or you have moved the sample file somewhere else you'd have to change paths accordingly.

+


+

Due to a bug in SC 3.7 / 3.8 TGrains didn't response to amp changes, I changed the examples accordingly, the bug is fixed in 3.9.

+


+

Windows 7:  While other parts of miSCellaneous lib were running fine I was unable to allocate buffers with the SC 3.5.4 Windows binary in August 2012. Meanwhile this issue has been solved (SC 3.6.6, February 2014). There also seemed to be accuracy issues with OffsetOut on Windows with SC 3.6 still, but not with newer versions.

+


+

+

Credits 

+


+

Thanks for contributions and inspirations by SCers Alberto de Campo (Wavesets), James Harkins (Patterns), Ron Kuivila (Pspawner), Sergio Luque (stochastic distributions), Josh Parmenter (granulation plugins), Bhob Rainey (granulation plugins).

+


+


+

References

+


+

[1] de Campo, Alberto. "Microsound" In: Wilson, S., Cottle, D. and Collins, N. (eds). 2011. 

+

The SuperCollider Book. Cambridge, MA: MIT Press, 463-504.

+

+

[2] Luque, Sergio (2006). Stochastic Synthesis, Origins and Extensions. Institute of Sonology, Royal Conservatory, The Netherlands. 

+

http://sergioluque.com

+


+

[3] Roads, Curtis (2001). Microsound. Cambridge, MA: MIT Press.

+

+

[4] Wishart, Trevor (1994). Audible Design. York: Orpheus The Pantomime Ltd. 

+


+

[5] Xenakis, Iannis (1992). Formalized Music. Hillsdale, NY: Pendragon Press, 2nd Revised edition.

+


+


+

+

1.) Granulation with Ugens

+


+

Example 1a:   Basic buffer granulation Synth

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// basic SynthDef suited for pitch-shift and time-stretch

+

// buffer position given relatively (posLo and posHi between 0 and 1)

+

// posDev: maximum amount of deviation from (moving) grain center position

+

// posDev = 0 can lead to comb filter effects (which may be nice sometimes)

+


+

// passing control specs as metadata allows for VarGui shortcut build method sVarGui

+

// metadata specs can be overwritten by arg ctrReplace

+

// alternatively control specs may be passed as synthCtr arg to a build with VarGui( ... )

+


+

(

+

SynthDef(\gran_1a, { arg out = 0, bufNum = 0, posLo = 0.0, posHi = 1.0, 

+

posRate = 1, posDev = 0.01, trigRate = 100, granDur = 0.1, rate = 1.0, 

+

panMax = 1, amp = 0.1, interp = 4;

+

+

var trig, pan, pos, bufDur, bufDurSection, posDif;

+

+

posDif = posHi - posLo;

+

bufDur = BufDur.kr(bufNum);

+

bufDurSection = bufDur * posDif;

+

trig = Impulse.kr(trigRate);

+

pos = posLo * bufDur +

+

(Phasor.ar(0, BufRateScale.kr(bufNum) * posRate / SampleRate.ir, posLo * bufDur, posHi * bufDur) + 

+

(TRand.kr(-0.5 * posDev, 0.5 * posDev, trig) * bufDur)).mod(bufDurSection);  

+

pan = Demand.kr(trig, 0, Dseq([panMax, panMax.neg],inf) * 0.999);

+

Out.ar(out, TGrains.ar(2, trig, bufNum, rate, pos, granDur, pan, 1, interp) * amp);

+

}, metadata: (

+

specs: (

+

posLo: [0.01, 0.99, \lin, 0.01, 0],

+

posHi: [0.01, 0.99, \lin, 0.01, 1],

+

posRate: [0.1, 2, \lin, 0.01, 1],

+

posDev: [0, 0.2, 5, 0, 0.01],

+

granDur: [0.01, 0.3, \lin, 0.01, 0.1],

+

trigRate: [1, 200, \lin, 0.01, 100], 

+

rate: [0.1, 2, \lin, 0.01, 1],

+

panMax: [0.0, 1, \lin, 0.005, 0.8],

+

amp: [0.0, 0.5, \lin, 0.005, 0.25]

+

)

+

)

+

).add;

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+

)

+


+


+

// start from GUI

+


+

\gran_1a.sVarGui([\bufNum, b.bufnum]).gui;

+


+


+


+

Example 1b:   More deviations

+


+


+

// In example 1a only a deviation from the grain center position was implemented.

+

// With additional deviation controls a greater plasticity of sound can be achieved,

+

// here deviations are added for trigRate (LFO with oscillation freq and deviation max),

+

// grain duration and rate (TRand, equally weighted random deviation with given max).

+

// Deviations intervals could be defined alternatively, 

+

// e.g. (1/(1+maxDev), 1+maxDev) with 0 < maxDev

+

// instead of (1-maxDev, 1+mexDev) with 0 < maxDev < 1

+


+

// posRate control range is widened by inventing two controls for 

+

// mantissa and exponent, so posRate = 1 for init param pair 

+

// posRateE = 0 and posRateM = 1

+


+

(

+

SynthDef(\gran_1b, { arg out = 0, bufNum = 0, posLo = 0.0, posHi = 1.0, 

+

posRateE = 0, posRateM = 1, posDev = 0.01, trigRate = 100, trigRateDev = 0, 

+

trigRateOsc = 1, granDur = 0.1, granDurDev = 0, rate = 1.0, rateDev = 0, 

+

panMax = 1, amp = 0.1, interp = 4;

+

+

var trig, pan, pos, bufDur, bufDurSection, posDif, posRate;

+

+

posDif = posHi - posLo;

+

bufDur = BufDur.kr(bufNum);

+

bufDurSection = bufDur * posDif;

+

trig = Impulse.kr(LFDNoise3.kr(trigRateOsc, trigRate * trigRateDev, trigRate));

+

posRate = 10 ** posRateE * posRateM;

+

pos = posLo * bufDur +

+

(Phasor.ar(0, BufRateScale.kr(bufNum) * posRate / SampleRate.ir, posLo * bufDur, posHi * bufDur) + 

+

(TRand.kr(-0.5, 0.5, trig) * posDev * bufDur)).mod(bufDurSection);  

+

pan = Demand.kr(trig, 0, Dseq([panMax, panMax.neg],inf) * 0.999);

+

Out.ar(out, TGrains.ar(2, trig, bufNum, rate * (TRand.kr(-1, 1.0, trig) * rateDev + 1), pos, 

+

granDur * (TRand.kr(-1, 1.0, trig) * granDurDev + 1), pan, 1, interp) * amp);

+

}, metadata: (

+

specs: (

+

posLo: [0.01, 0.99, \lin, 0.01, 0],

+

posHi: [0.01, 0.99, \lin, 0.01, 1],

+

posRateE: [-3, 4, \lin, 1, 0],

+

posRateM: [0.1, 10, \exp, 0.01, 1],

+

posDev: [0, 0.2, 5, 0, 0.05],

+

trigRate: [1, 200, \lin, 0.01, 100], 

+

trigRateDev: [0.0, 1, \lin, 0.01, 0],

+

trigRateOsc: [0.1, 2, \lin, 0.01, 3],

+

granDur: [0.01, 0.3, \lin, 0.01, 0.1],

+

granDurDev: [0.0, 0.95, \lin, 0.01, 0],

+


+

rate: [0.1, 2, \lin, 0.01, 1],

+

rateDev: [0.0, 0.99, \linear, 0.01, 0.05],

+

panMax: [0.0, 1, \lin, 0.005, 0.8],

+

amp: [0.0, 0.5, \lin, 0.005, 0.25]

+

)

+

)

+

).add;

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+

)

+


+


+

// start from GUI

+

// use color grouping for better overview

+


+

\gran_1b.sVarGui([\bufNum, b.bufnum]).gui(synthColorGroups: (0..14).clumps([1,5,3,2,2,1,1]) )

+


+


+


+

Example 1c:   Buffer granulation Synth with external control synths

+


+


+

// This is a more modular approach, once having a basic granulation synth

+

// it can be linked with arbitrary control synths, here

+

// also moving through the buffer is controlled externally.

+

// Depending on LFOs it might be worth defining 

+

// audio buses for control to get higher precision.

+


+


+

(

+

SynthDef(\gran_1c, { arg out = 0, bufNum = 0, amp = 0.1, pos = 0.5, posDev = 0.01, 

+

trigRate = 100, granDur = 0.1, rate = 1, panMax = 1, interp = 4;

+

var trig, pan;

+

trig = Impulse.kr(trigRate);

+

pan = Demand.kr(trig, 0, Dseq([panMax, panMax.neg],inf) * 0.999);

+

pos = (pos * BufDur.kr(bufNum) * WhiteNoise.kr(posDev, 1));

+

Out.ar(out, TGrains.ar(2, trig, bufNum, rate, pos, granDur, pan, 1, interp) * amp);

+

}).add;

+


+


+

// LFO synthdef for switching between 3 types: 

+

// 0: LFDNoise3 (smooth random movement)

+

// 1: SinOsc

+

// 2: LFSaw (works as phasor for position)

+


+

SynthDef(\lfo, { |out = 0, lfoType = 0, freq = 1, lo = 0, hi = 1| 

+

var ctrl;

+

ctrl = Select.kr(lfoType, [

+

LFDNoise3,

+

SinOsc,

+

LFSaw

+

].collect { |x| x.kr(freq, mul: hi-lo/2, add: hi+lo/2) });

+

    Out.kr(out, ctrl); 

+

}).add; 

+


+

// multichannel control bus 

+

c = Bus.control(s, 5);

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+

)

+


+


+

// In contrast to Ex. 1a and 1b VarGui is called explicitely

+

// as control specs should differ.

+


+

// Also here the granulation Synth is generated explicitely and not by the interface

+

// as its controls have to be mapped to buses

+


+

(

+

// start granulation synth paused and register

+

// to let VarGui know its state

+


+

x = Synth.newPaused(\gran_1c, [\bufNum, b]).register;

+


+

// map args to be controlled to consecutive subbuses

+

x.map(*[[\pos, \trigRate, \granDur, \rate, \panMax], (0..4).collect(c.subBus(_))].flop.flat)

+

)

+


+

(

+

// Open VarGui interface, granulation synth (#5) is paused

+

// (orange button on yellow background).

+

// Shift-clicking one of the green buttons runs

+

// granulation synth and control synths (#0 - #4).

+

// All synths can be paused (orange button) and resumed.

+


+

// NOTE: When stopping the granulation synth 

+

// the linkage with control synths is lost! 

+

// A new synth generated by the interface 

+

// (by pressing the blue button of #5) is not

+

// automatically mapped to control buses.

+

// On the other hand control synths might be stopped and 

+

// newly generated.

+

 

+


+

VarGui(synthCtr: [[

+

\pos, 0, // dummy spec for labelling 

+

\out, c.index,

+

\lfoType, [0, 2, \lin, 1, 0], 

+

// as LFOs are unified, original movement tempo is 

+

// indicated not by 1 but by 1 / buffer duration

+

\freq, [0.01, 20, \exp, 0.0, 1/b.duration],

+

\lo, [0.0, 1, \lin, 0, 0.1],

+

\hi, [0.0, 1, \lin, 0, 0.9]

+

],[

+

\trigRate, 1,

+

\out, c.index + 1,

+

\lfoType, [0, 2, \lin, 1, 0], 

+

\freq, [0.001, 0.5, \exp, 0.0, 0.2],

+

\lo, [1, 100, \lin, 0, 7],

+

\hi, [1, 100, \lin, 0, 40]

+

],[

+

\granDur, 2,

+

\out, c.index + 2,

+

\lfoType, [0, 2, \lin, 1, 1], 

+

\freq, [0.001, 0.5, \exp, 0.0, 0.4],

+

\lo, [0.01, 0.2, \lin, 0, 0.1],

+

\hi, [0.01, 0.2, \lin, 0, 0.15]

+

],[

+

\rate, 3,

+

\out, c.index + 3,

+

\lfoType, [0, 2, \lin, 1, 0], 

+

\freq, [0.001, 0.1, \exp, 0.0, 0.03],

+

\lo, [0.1, 3, \lin, 0, 0.6],

+

\hi, [0.1, 3, \lin, 0, 1.1]

+

],[

+

\panMax, 4,

+

\out, c.index + 4,

+

\lfoType, [0, 2, \lin, 1, 0], 

+

\freq, [0.001, 0.5, \exp, 0.0, 0.5],

+

\lo, [0.0, 1, \lin, 0, 0.06],

+

\hi, [0.0, 1, \lin, 0, 0.9]

+

],[

+

\out, 0,

+

\bufNum, b.bufnum,

+

  \posDev, [0.001, 0.2, \exp, 0, 0.02],

+

  \amp, [0.0, 2, \lin, 0, 0.5]

+

]],

+

  // 5 lfo synths are generated by the interface (synthdef name passed)

+

// but granulation Synth is passed directly as object

+

synth: \lfo!5 ++ x

+

).gui(sliderPriority: \synth, playerPriority: \synth); 

+

)

+


+


+

Example 1d:   Buffer granulation Synth with demand rate ugens

+


+


+

// Repeated grain triggering within a synth can be defined by demand rate ugens, 

+

// which is comfortable in connection with granular ugens.

+


+

// E.g. with TGrains you can trigger parameters like grain duration, playback rate, 

+

// position, panning and amp, single grains will keep their params if they overlap.

+

// (Note that in the below implementation changes of demand rate array fields will apply

+

// also not before the next call of those fields in the synth)

+


+

// More refined per-grain control, e.g. per-grain filtering with filter parameter streams,

+

// can be done by using a multichannel trick, see Ex. 1e.

+


+

(

+

// length of demand rate sequence, you might want to check larger sizes

+

// in connection with gui arg tryColumnNum > 1

+

~n = 5;

+


+

// posRate control range is widened by inventing two controls for 

+

// mantissa and exponent, so posRate = 1 for init param pair 

+

// posRateE = 0 and posRateM = 1

+


+

SynthDef(\gran_1d, { |out = 0, soundBuf, posLo = 0.1, posHi = 0.3, 

+

posRateE = 0, posRateM = 1, granDurMul = 1, rateMul = 1, panMul = 1, amp = 0.5, interp = 2| 

+

+

var signal, bufDur, granDur, granGate, relGranDurs, pos, overlap, overlaps, overlapSeq, 

+

pan, rate, relRates, rateSeq, posRate, granDurSeq; 

+

+

// array args for demand rate sequencing, short form of NamedControl

+

relGranDurs = \relGranDurs.kr(0.1!~n);

+

relRates = \relRates.kr(1!~n); 

+

overlaps = \overlaps.kr(1!~n); 

+


+

// Dstutter (or Dunique) necessary as granDurSeq is polled twice: granGate and granDur

+

granDurSeq = Dstutter(2, Dseq(relGranDurs, inf));  

+

rateSeq = Dseq(relRates, inf); 

+

overlapSeq = Dseq(overlaps, inf); 

+

+

granGate = TDuty.ar(granDurSeq * granDurMul); 

+

granDur = Demand.ar(granGate, 0, granDurSeq * granDurMul);

+

rate = Demand.ar(granGate, 0, rateSeq) * rateMul; 

+

pan = Demand.ar(granGate, 0, Dseq([1, -1], inf)) * 0.999 * panMul; 

+

overlap = Demand.ar(granGate, 0, overlapSeq);

+


+

bufDur = BufDur.kr(soundBuf);

+

posRate = 10 ** posRateE * posRateM;

+


+

pos = Phasor.ar(0, BufRateScale.kr(soundBuf) * posRate / (SampleRate.ir * bufDur), posLo, posHi);

+

signal = TGrains.ar(2, granGate, soundBuf, rate, pos * bufDur, granDur * overlap, pan, 1, interp);

+

+

Out.ar(out, signal * amp); 

+

}

+

).add; 

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+

)

+


+

(

+

// relGranDurs are multiplied with granDurMul, analogously relRates with rateMul.

+

// Changes of these params apply immediately in contrast to the array args 

+

// used by demand rate ugens which are used with next demand.

+


+

// check out moving a number of sliders of one array by using

+

// Shift, Alt + Shift and Ctrl + Shift, see VarGui help Ex. 1c.

+

 

+


+

VarGui(synthCtr: [

+

soundBuf: b.bufnum,

+

+

posLo: [0, 1, \lin, 0, 0.1],

+

posHi: [0, 1, \lin, 0, 0.9],

+

posRateE: [-3, 4, \lin, -1, 0],

+

posRateM: [0.1, 10, \exp, 0.01, 0.5],

+


+

// generating control specifications depending on index

+

relGranDurs: { |i| [0.01, 0.1, \lin, 0, i * 0.005 + 0.02] } ! ~n,

+

granDurMul: [0.03, 2, \lin, 0, 0.25],

+

overlaps: [0.1, 5, \lin, 0, 1.5] ! ~n,

+

+

relRates: { |i| [0.1, 1.5, \lin, 0, (5-i) * 0.1 + 0.5] } ! ~n,

+

rateMul: [0.1, 2, \lin, 0, 0.5],

+

+

panMul: [0, 1, \lin, 0, 0.6],

+

amp: [0.0, 3, \lin, 0, 2]

+

],

+

synth: \gran_1d

+

).gui(

+

tryColumnNum: 1,

+

// color grouping for better overview

+

synthColorGroups: (0..(~n*3+8)).clumps([1,4,~n+1,~n,~n+1,1,1]), 

+

labelWidth: 90,

+

sliderWidth: 280 

+

);

+

)

+


+


+


+

Example 1e:   Buffer granulation Synth with per-grain effect processing (TGrains)

+


+


+

// TGrains and other granular ugens allow per-grain processing

+

// for a limited number of parameters (pos, rate etc.)

+

// Nevertheless it is possible to apply arbitrary effects with

+

// per-grain parameter changes, even if grains overlap.

+

// This can be achieved by defining the granulation output

+

// as a multichannel signal and appropriate triggering of fx parameters.

+

// The example is adapted from a recommendation of Julian Rohrhuber.

+


+

// The method is elegant but also a bit tricky in terms of

+

// multichannel triggering and channel routing.

+


+

(

+

// the multichannel size and equivalently:

+

// the maximum number of overlapping grains that might get

+

// different fx parameters have to be fixed.

+

// For convenience of later L/R-spatialization we take an 

+

// even number ~n = 2 * ~m

+


+

~m = 5; 

+

~n = 2 * ~m; 

+


+

SynthDef(\gran_1e, { |out = 0, soundBuf, posLo = 0.1, posHi = 0.9, 

+

posRateE = 0, posRateM = 1, rate = 1, panMax = 0.8, bpRQ = 0.1, bpLo = 50, bpHi = 5000,

+

amp = 1, bpFund = 100, overlap = 2, trigRate = 1, interp = 2| 

+

var sig, sigL, sigR, bpFreq, chan, bpFreqSeqs, dUgen,

+

trig, trigs, bufDur, pos, posRate; 

+

+

trig = Impulse.ar(trigRate);

+

// we need a multichannel trigger that steps through all consecutive channels

+

trigs = { |i| PulseDivider.ar(trig, ~n, ~n-1-i) } ! ~n; 

+


+

chan = Demand.ar(trig, 0, Dseq((0..~n-1), inf)); 

+

+

posRate = 10 ** posRateE * posRateM;

+

bufDur = BufDur.kr(soundBuf);

+

pos = Phasor.ar(0, BufRateScale.kr(soundBuf) * posRate * SampleDur.ir / bufDur, posLo, posHi);

+

+

sig = TGrains.ar(~n, trig, soundBuf, rate, pos * bufDur, overlap/trigRate, 

+

// Panning convention is that from PanAz,

+

// speakers should be from 0 to 2, but (orientation)

+

// 1/n has to be substracted for n speakers.

+

// If this isn't done correctly grains are spread onto more than one channel

+

// and per-grain application of fxs fails.

+

chan.linlin(0, ~n-1, -1/~n, (2*~n - 3)/~n), 1, interp);

+


+

dUgen = Dwhite(0.0, 1);

+

sig = sig.collect { |ch, i| 

+

// this is the place to define fxs per channel/grain

+

// multichannel trigger is polling from a single demand ugen

+

bpFreq = Demand.ar(trigs[i], 0, dUgen).linlin(0, 1, bpLo, bpHi); 

+

+

// amplitude compensation for lower rq of bandpass filter

+

BPF.ar(ch, bpFreq, bpRQ, (bpRQ ** -1) * (400 / bpFreq ** 0.5)); 

+

};

+

+

// routing to two channels ...

+

sigL = Mix(((0..(~m-1)) * 2).collect(sig[_])); 

+

sigR = Mix(((0..(~m-1)) * 2 + 1).collect(sig[_])); 

+

+

// ... in order to have L/R-spreading with panMax as in other examples

+

Out.ar(0, Pan2.ar(sigL, panMax.neg) + Pan2.ar(sigR, panMax) * amp) 

+

}).add;

+


+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+

) 

+


+


+

(

+

VarGui(synthCtr: [

+

soundBuf: b.bufnum,

+

posLo: [0, 1, \lin, 0, 0.2],

+

posHi: [0, 1, \lin, 0, 0.5],

+

posRateE: [-3, 4, \lin, -1, -1],

+

posRateM: [0.1, 10, \exp, 0.01, 0.8],

+

overlap: [0.1, ~n, \lin, 0, 12],

+

trigRate: [1, 100, \lin, 0, 45],

+

rate: [0.1, 2, \lin, 0, 1],

+

+

bpRQ: [0.05, 1, \lin, 0, 0.25],

+

bpLo: [50.0, 5000, \exp, 0, 50],

+

bpHi: [50.0, 5000, \exp, 0, 5000],

+

panMax: [0, 1, \lin, 0, 0.85],

+

amp: [0.0, 3, \lin, 0, 1]

+

],

+

synth: \gran_1e

+

).gui(

+

tryColumnNum: 1,

+

synthColorGroups: (0..12).clumps([1,4,2,1,3,1,1]) 

+

);

+

)

+


+


+


+

Example 1f:   Buffer granulation Synth with per-grain effect processing (DXEnvFan)

+


+


+

// DXEnvFan generates a multichannel envelope which can be used as trigger for granulation and fxs on grains.

+

// It encapsulates the trigger logic, which has been used explicitely in Ex. 1e.

+

// DX ugens can be used for a variety of microsound techniques, see their help files.

+


+

(

+

~maxOverlap = 12;

+

a = Bus.audio(s, ~maxOverlap);

+


+

// overlap only settable in SC versions >= 3.9

+


+

SynthDef(\gran_1f, { |out = 0, soundBuf, bus = 0, posLo = 0.1, posHi = 0.9,

+

    posRateE = 0, posRateM = 1, overlap = 2, trigRate = 1, rate = 1, 

+

bpRQ = 0.1, bpLo = 50, bpHi = 5000, panMax = 0.8, amp = 1|

+

    var sig, bpFreq, dUgen, bufDur, pos, posRate, playbuf, env, maxOverlap = ~maxOverlap;

+


+

    posRate = 10 ** posRateE * posRateM;

+

    bufDur = BufDur.kr(soundBuf);

+

    pos = Phasor.ar(0, BufRateScale.kr(soundBuf) * posRate * SampleDur.ir / bufDur, posLo, posHi);

+


+

    // multichannel trigger

+

    env = DXEnvFan.ar(

+

        Dseq((0..maxOverlap-1), inf),

+

        trigRate.reciprocal,

+

        size: maxOverlap,

+

        maxWidth: maxOverlap,

+

        width: (Main.versionAtLeast(3, 9)).if { overlap }{ 2 },

+

        // option to avoid unwanted triggers

+

        zeroThr: 0.002,

+

        // take equalPower = 0 for non-squared sine envelopes

+

        // more efficient with helper bus

+

        equalPower: 0,

+

        bus: a

+

    );

+

    // multichannel playback, pos is triggered for each grain

+

    playbuf = PlayBuf.ar(1, soundBuf, rate, env, pos * BufFrames.ir(soundBuf), 1);

+


+

    dUgen = Dwhite(0, 1);

+

    // multichannel trigger used to poll values from drate ugen

+

    bpFreq = Demand.ar(env, 0, dUgen).linlin(0, 1, bpLo, bpHi);

+


+

    // generate grains by multiplying with envelope

+

    sig = playbuf * env;

+


+

    // different frequency on each grain channel

+

    sig = BPF.ar(sig, bpFreq, bpRQ, (bpRQ ** -1) * (400 / bpFreq ** 0.5));

+


+

    // generate array of 5 stereo signals

+

    sig = Pan2.ar(sig, Demand.ar(env, 0, Dseq([-1, 1], inf) * panMax));

+


+

    // mix to out

+

    Out.ar(0, Mix(sig) * amp)

+

}, metadata: (

+

    specs: (

+

        posLo: [0.01, 0.99, \lin, 0.01, 0],

+

        posHi: [0.01, 0.99, \lin, 0.01, 0.5],

+

        posRateE: [-3, 4, \lin, 1, -1],

+

        posRateM: [0.1, 10, \exp, 0.01, 1.35],

+

        trigRate: [1, 200, \lin, 0.01, 90],

+

        overlap: [0.2, 12, \lin, 0.01, 7],

+

        rate: [0.1, 2, \lin, 0.01, 0.75],

+

        panMax: [0.0, 1, \lin, 0.005, 0.75],

+

        bpLo: [100, 5000, \lin, 0, 300],

+

        bpHi: [100, 5000, \lin, 0, 3000],

+

        bpRQ: [0.05, 1, \lin, 0, 0.18],

+

        amp: [0.0, 3, \lin, 0.005, 1]

+

    )

+

)).add;

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+

)

+


+

(

+

\gran_1f.sVarGui([\soundBuf, b.bufnum]).gui(

+

    tryColumnNum: 1,

+

    synthColorGroups: (0..12).clumps([1,4,2,1,3,1,1])

+

)

+

)

+


+

Example 1g: Buffer granulation with (half) wavesets: ZeroXBufRd

+


+

// movement through the buffer of zero crossings

+

// with a slow pos rate we get repetitions of half wavesets

+


+

// load sound buffer

+


+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+


+

// allocate zeroX buffer

+


+

(

+

z = Buffer.alloc(s, b.duration * 44100 / 5, 1);

+

s.scope;

+

)

+


+


+

// write zero crossings, but no need to overwrite sound buffer

+

// this is caused by setting adjustZeroXs to -1

+


+

(

+

{

+

// use LeakDC to avoid extremely long half wavesets

+

var src = LeakDC.ar(PlayBuf.ar(1, b, BufRateScale.ir(b)));

+

ZeroXBufWr.ar(src, b, z, adjustZeroXs: -1, doneAction: 2);

+

}.play

+

)

+


+


+

// instead adjust from lang and get zeroXs

+


+

b.adjustZeroXs(z, { |z| ~zeroXs = z.reject(_==0) })

+


+

// get number of zeroXs

+


+

~zeroXNum = ~zeroXs.size

+


+


+

(

+

SynthDef(\gran_1g, { |out = 0, soundBuf, zeroXBuf, zeroXNum, posLo = 0.1, posHi = 0.9,

+

    posRateE = 0, posRateM = 1, rate = 1, amp = 1|

+

    var sig, bufDur, pos, posRate;

+


+

    posRate = 10 ** posRateE * posRateM;

+

bufDur = BufDur.kr(soundBuf);

+


+

// move through the buffer of zero crossings

+

// the tempo (rate) refers to the sound buffer

+

pos = Phasor.ar(

+

0,

+

BufRateScale.kr(soundBuf) * posRate * SampleDur.ir / bufDur,

+

posLo,

+

posHi

+

) * zeroXNum;

+


+

sig = ZeroXBufRd.ar(

+

soundBuf,

+

zeroXBuf,

+

0!2,

+

pos,

+

mul: amp,

+

rate: rate * [1, 1.01]

+

     );

+

     Out.ar(0, sig * amp)

+

}, metadata: (

+

    specs: (

+

        posLo: [0.01, 0.99, \lin, 0.01, 0.1],

+

        posHi: [0.01, 0.99, \lin, 0.01, 0.5],

+

        posRateE: [-2, 2, \lin, 1, -1],

+

        posRateM: [0.1, 10, \exp, 0.01, 3],

+

        rate: [0.3, 3, \lin, 0.01, 1],

+

        amp: [0.0, 2, \lin, 0.005, 1]

+

)

+

)

+

).add;

+


+

\gran_1g.sVarGui([\soundBuf, b.bufnum, \zeroXBuf, z.bufnum, \zeroXNum, ~zeroXNum]).gui

+

)

+


+


+

Example 1h: Buffer granulation with (half) wavesets: TZeroXBufRd

+


+

// buffer preparations from Ex. 1g

+


+

// again a movement through the buffer of zero crossings is implemented

+

// the repetition of wavesets though is defined by xNum and xRep

+


+

// with high trigger rates, xNum and xRep values and low rates you might

+

// have to increase overlapSize, see TZeroXBufRd help for a discussion of this topic

+


+

(

+

SynthDef(\gran_1h, { |out = 0, soundBuf, zeroXBuf, zeroXNum, posLo = 0.1, posHi = 0.9,

+

    posRateE = 0, posRateM = 1, xNum = 1, xRep = 1, rate = 1, trigRate = 100, amp = 1|

+

    var sig, bufDur, pos, posRate;

+


+

    posRate = 10 ** posRateE * posRateM;

+


+

bufDur = BufDur.kr(soundBuf);

+

pos = Phasor.ar(

+

0, 

+

BufRateScale.kr(soundBuf) * posRate * SampleDur.ir / bufDur, 

+

posLo, 

+

posHi

+

) * zeroXNum;

+


+

// move through the buffer of zero crossings

+

// the tempo (rate) refers to the sound buffer

+

sig = TZeroXBufRd.ar(

+

soundBuf,

+

zeroXBuf,

+

0!2, // bufMix arg triggers stereo

+

trig: Impulse.ar(trigRate),

+

zeroX: pos,

+

xNum: xNum,

+

xRep: xRep,

+

mul: amp,

+

rate: rate * [1, 1.01], // a bit of decorrelation 

+

overlapSize: 20 // larger overlapSize

+

     );

+

// by overlapping a DC can easily accumulate, so do leak

+

Out.ar(0, LeakDC.ar(sig) * amp)

+

}, metadata: (

+

specs: (

+

posLo: [0.01, 0.99, \lin, 0.01, 0.1],

+

posHi: [0.01, 0.99, \lin, 0.01, 0.5],

+

posRateE: [-2, 2, \lin, 1, -1],

+

posRateM: [0.1, 10, \exp, 0.01, 1],

+

rate: [0.3, 2, \lin, 0.01, 1],

+

trigRate: [5, 120, \lin, 0, 50],

+

xNum: [1, 5, \lin, 1, 2],

+

xRep: [1, 5, \lin, 1, 3],

+

amp: [0.0, 2, \lin, 0, 1]

+

)

+

)

+

).add;

+


+

\gran_1h.sVarGui([\soundBuf, b.bufnum, \zeroXBuf, z.bufnum, \zeroXNum, ~zeroXNum]).gui

+

)

+


+


+

2.) Granulation driven by language

+


+


+

NOTE: 

+


+

Language-driven sequencing is not sample-exact in realtime (with NRT synthesis it is). This is related to hardware control and cannot be overcome currently. However for most practical purposes this might not be relevant. It is anyway a far less strong effect than the inaccuracies related to Out.ar and the combination of OffsetOut.ar and In.ar (see examples 2a-d in Live Granulation)

+


+


+

Example 2a:   Basic buffer granulation Pbind

+


+


+

// Control parameters like in 1a, but implemented with a SynthDef for

+

// playing single grains and an appropriate Pbind,

+

// OffsetOut used for exact timing.

+


+

(

+

SynthDef(\gran_2a, { |out = 0, pos = 0, sndBuf = 0, windowBuf = 1, granDur = 0.1, 

+

rate = 1, loop = 1, panMax = 0, amp = 1| 

+

var window, src;

+

src = PlayBuf.ar(1, sndBuf, BufRateScale.kr(sndBuf) * rate, 

+

1, round(pos * BufFrames.kr(sndBuf)), loop, 2);

+

window = BufRd.ar(1, windowBuf, 

+

EnvGen.ar(Env([0, BufFrames.kr(windowBuf)], [granDur]), 

+

doneAction: 2), loop, 4); 

+

OffsetOut.ar(out, Pan2.ar(src, panMax, amp) * window); 

+

}).add; 

+


+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+

w = Buffer.sendCollection(s, Signal.hanningWindow(1024));

+

)

+


+


+

// Determining the correct buffer position depending on

+

// posRate, posLo and posHi needs a little calculation.

+

// In 1a this was done inside the ugen by a Phasor.

+

// See Ex. 3b, 3c for doing position movement with a separate Synth.

+


+

// PL placeholder patterns used, could also be Pfunc { ~ ... }

+


+

(

+

p = Pbind( 

+

\instrument, \gran_2a, 

+

\sndBuf, b,

+

\windowBuf, w, 

+

+

\dur, 1 / PL(\trigRate), 

+

\granDur, PL(\granDur), 

+

\time, Ptime(),

+

\pos, Pfunc { |e|

+

var relTime = ~posRate * e.time / e.sndBuf.duration, relDif;

+

relDif = ~posHi - ~posLo;

+

relTime + rand2(~posDev) % relDif + ~posLo;

+

},

+

\rate, PL(\rate),  

+

\amp, PL(\amp),

+

\panMax, PLseq([-1,1]) * PL(\panMax),

+

\out, 0

+

);

+


+

VarGui([

+

\posLo, [0.0, 0.99, \lin, 0.01, 0],

+

\posHi, [0.0, 0.99, \lin, 0.01, 1],

+

\posRate, [0.1, 2, \lin, 0.01, 1],

+

\posDev, [0, 0.2, 5, 0, 0.01],

+

\trigRate, [1, 200, \lin, 0.01, 120],

+

\granDur, [0.01, 0.3, \lin, 0.005, 0.06], 

+

\rate, [0.1, 3, \lin, 0.01, 1],

+

\panMax, [0.0, 1, \lin, 0.0, 0.8],

+

\amp, [0.0, 1, \lin, 0.01, 0.25]

+

], stream: p

+

).gui(varColorGroups: (0..8).clumps([4,1,1,1,1,1]))

+

)

+


+


+


+

Example 2b:   Switching between stochastic distributions

+


+


+

// Extended basic SynthDef from Ex.2a with bandpass filter

+


+

(

+

SynthDef(\gran_2b, { |out = 0, pos = 0, sndBuf = 0, windowBuf = 1, granDur = 0.1, 

+

rate = 1, loop = 1, panMax = 0, amp = 1, bpFreq = 500, bpRQ = 0.5, bpWet = 1| 

+

var window, granSrc, src;

+

granSrc = PlayBuf.ar(1, sndBuf, BufRateScale.kr(sndBuf) * rate, 

+

1, round(pos * BufFrames.kr(sndBuf)), loop, 2);

+

window = BufRd.ar(1, windowBuf, 

+

EnvGen.ar(Env([0, BufFrames.kr(windowBuf)], [granDur]), 

+

doneAction: 2), loop, 4);

+

// do amplitude compensation, estimation like in Wavesets example by Alberto de Campo 

+

src = (BPF.ar(granSrc, bpFreq, bpRQ, mul: (bpRQ ** -1) * (400 / bpFreq ** 0.5)) * 

+

bpWet + (granSrc * (1 - bpWet)));

+


+

OffsetOut.ar(out, Pan2.ar(src, panMax, amp) * window); 

+

}).add; 

+


+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+

w = Buffer.sendCollection(s, Signal.hanningWindow(1024));

+

)

+


+


+

// random types

+


+

// 0: low value

+

// 1: low or high value, evenly distributed

+

// 2: evenly distributed

+

// 3: linear decrease from mean value

+

// 4: exponential distribution

+

// 5: beta distribution, default parameter 0.3 centers value at the borders

+

// 6: brownian movement (of first order) 

+

// 7: brownian movement of second order (stepsize itself generated by brownian movement) 

+


+

// A second order brownian movement much more tends to get stuck at the

+

// borders than a normal (first order) brownian movement

+

// E.g. see Sergio Luque's presentation of Xenakis's stochastic synthesis:

+

// "Stochastic Synthesis, Origins and Extensions", pp 25-28 

+

// http://sergioluque.com

+


+


+

// Function that generates an array of PLx patterns

+

// of different random types (see PLx suite).

+

// These placeholders can refer to environmental variables

+

// to be set by the VarGui interface later on.

+


+

(

+

d = { |keyLo, keyHi, betaProb = 0.3, brownStepFac = 0.01,

+

brown2Ratio = 1, brown2StepFac = 0.01|

+

// keyLo and keyHi must be Symbols,

+

// other args may be Symbols

+

var patLo, patHi, patDif;

+

+

// avoid lo-hi reversing with Pbrown

+

patLo = min(PL(keyLo), PL(keyHi));

+

patHi = max(PL(keyLo), PL(keyHi));

+

patDif = patHi - patLo;

+

[ 

+

PL(keyLo),

+

Pfunc { currentEnvironment[[keyLo, keyHi].choose] },

+

PLwhite(keyLo, keyHi),

+

PLmeanrand(keyLo, keyHi),

+

PLexprand(keyLo, keyHi), 

+

PLbeta(keyLo, keyHi, betaProb, betaProb), 

+

PLbrown(patLo, patHi, patDif * PL(brownStepFac)), 

+

PLbrown(patLo, patHi, 

+

PLbrown(

+

    patDif.neg * PL(brown2Ratio) / 2, 

+

    patDif * PL(brown2Ratio) / 2,

+

    patDif * PL(brown2Ratio) * PL(brown2StepFac)

+

    )

+

) 

+

]

+

};

+

)

+


+

// trigrate, granDur, rate and bpFreq are 

+

// chosen between bounds according to the 

+

// random distribution type notated with suffix D

+


+

// single grains are filtered with a bandpass

+

// amount of effect controlled with bpWet

+


+

(

+

p = Pbind( 

+

\instrument, \gran_2b, 

+

\sndBuf, b,

+

\windowBuf, w, 

+

+

\dur, 1 / PLswitch1(d.(\trigRateLo, \trigRateHi), \trigRateD), 

+

\granDur, PLswitch1(d.(\granDurLo, \granDurHi), \granDurD), 

+

\time, Ptime(),

+

\posRate, PL(\posRate),  

+

\pos, Pfunc { |e|

+

var relTime = ~posRate * e.time / e.sndBuf.duration, relDif;

+

relDif = ~posHi - ~posLo;

+

relTime + rand2(~posDev) % relDif + ~posLo;

+

},

+

\rate, PLswitch1(d.(\rateLo, \rateHi), \rateD),

+

\bpFreq, PLswitch1(d.(\bpFreqLo, \bpFreqHi), \bpFreqD),

+

\bpRQ, PL(\bpRQ),

+

\bpWet, PL(\bpWet),

+

 

+

\amp, PL(\amp),

+

\panMax, PLseq([-1,1]) * PL(\panMax),

+

\out, 0

+

);

+


+

VarGui([

+

\posLo, [0.0, 0.99, \lin, 0.01, 0.21],

+

\posHi, [0.0, 0.99, \lin, 0.01, 0.47],

+

\posRate, [0.1, 2, \lin, 0.01, 0.2],

+

\posDev, [0, 0.2, 5, 0, 0.002],

+


+

\trigRateLo, [1, 200, \lin, 0.01, 21],

+

\trigRateHi, [1, 200, \lin, 0.01, 155],

+

\trigRateD, [0, 7, \lin, 1, 6],

+

+

\granDurLo, [0.01, 0.6, \exp, 0.0, 0.037], 

+

\granDurHi, [0.01, 0.6, \exp, 0.0, 0.4], 

+

\granDurD, [0, 7, \lin, 1, 6],

+

+

\rateLo, [0.1, 3, \lin, 0.01, 1.09],

+

\rateHi, [0.1, 3, \lin, 0.01, 1.63],

+

\rateD, [0, 7, \lin, 1, 1],

+

+

\bpFreqLo, [50, 10000, \exp, 0.1, 54],

+

\bpFreqHi, [50, 10000, \exp, 0.1,8275],

+

\bpFreqD, [0, 7, \lin, 1, 1],

+

\bpRQ, [0.01, 0.99, \lin, 0.0, 0.07], 

+

\bpWet, [0.0, 1, \linear, 0.0, 0.23],

+

+

\panMax, [0.0, 1, \lin, 0.0, 0.85],

+

\amp, [0.0, 1, \lin, 0.01, 0.25]

+

], stream: p

+

).gui(varColorGroups: (0..19).clumps([4,3,3,3,5,1,1]))

+

)

+


+


+

Example 2c:   Generating granular phrases with Pspawner

+


+


+

// This example needs SynthDef \gran_2b and Function d to be taken from Ex. 2b

+


+


+

(

+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+

w = Buffer.sendCollection(s, Signal.hanningWindow(1024));

+

)

+


+

// A simple form of Pspawner is used to generate phrases.

+

// Phrase length params are taken a bit roughly as sustain and 

+

// rest times also depend on randomly varying grain lengths.

+

// spSustain controls medium sustain time (without grain length overhead)

+

// spLegato controls medium legato factor (disregarding reduction by grain length overhead)

+

// spDev is causing separate random deviation of spSustain and spLegato 

+

// between 1/(1+spDev) and 1+spDev

+


+

// random distribution switching is restricted here to

+

// types 6 and 7 (random walks of first and second order)

+

// to force individual sound qualities of phrases.

+


+

(

+

// declare var here as pattern is repeatedly evaluated from within the Pspawner,

+

// interpreter variable would be unsafe if running examples in parallel

+


+

var p = Pbind( 

+

\instrument, \gran_2b, 

+

\sndBuf, b,

+

\windowBuf, w, 

+

+

\dur, 1 / PLswitch1(d.(\trigRateLo, \trigRateHi), \trigRateD), 

+

\granDur, PLswitch1(d.(\granDurLo, \granDurHi), \granDurD), 

+

\time, Ptime(),

+

\posRate, PL(\posRate),  

+

+

// random timeOffset added with each spawning

+

\pos, Pfunc { |e|

+

var relTime = ~posRate * e.time / e.sndBuf.duration + e.timeOffset, relDif;

+

relDif = ~posHi - ~posLo;

+

relTime + rand2(~posDev) % relDif + ~posLo;

+

},

+

\rate, PLswitch1(d.(\rateLo, \rateHi), \rateD),

+

\bpFreq, PLswitch1(d.(\bpFreqLo, \bpFreqHi), \bpFreqD),

+

\bpRQ, PL(\bpRQ),

+

\bpWet, PL(\bpWet),

+

 

+

\amp, PL(\amp),

+

\panMax, PLseq([-1,1]) * PL(\panMax),

+

\out, 0

+

);

+


+

q = Pspawner({ |sp|

+

var randomizer = { |x| var y = rand(x); 0.5.coin.if { 1 + y }{ 1 / (1 + y) } },

+

sus, legato, delta; 

+

+

loop {

+

sus = ~spSustain * randomizer.(~spDev);

+

legato = ~spLegato * randomizer.(~spDev);

+

+

// take random offset for each phrase

+

sp.par(Pfindur(sus, Psetpre(\timeOffset, 5.0.rand, p)));

+

delta = sus / (~spLegato * randomizer.(~spDev));

+

sp.wait(delta)

+

}

+

});

+


+

VarGui([

+

\posLo, [0.0, 0.99, \lin, 0.01, 0.16],

+

\posHi, [0.0, 0.99, \lin, 0.01, 0.41],

+

\posRate, [0.1, 2, \lin, 0.01, 1.4],

+

\posDev, [0, 0.2, 5, 0, 0.0017],

+


+

\trigRateLo, [1, 200, \lin, 0.01, 70],

+

\trigRateHi, [1, 200, \lin, 0.01, 150],

+

\trigRateD, [6, 7, \lin, 1, 7],

+

+

\granDurLo, [0.01, 0.6, \exp, 0.0, 0.02], 

+

\granDurHi, [0.01, 0.6, \exp, 0.0, 0.11], 

+

\granDurD, [6, 7, \lin, 1, 6],

+

+

\rateLo, [0.1, 3, \lin, 0.01, 0.2],

+

\rateHi, [0.1, 3, \lin, 0.01, 1.86],

+

\rateD, [6, 7, \lin, 1, 7],

+

+

\bpFreqLo, [50, 10000, \exp, 0.1, 67],

+

\bpFreqHi, [50, 10000, \exp, 0.1, 5885],

+

\bpFreqD, [6, 7, \lin, 1, 6],

+

\bpRQ, [0.01, 0.99, \lin, 0.0, 0.17], 

+

\bpWet, [0.0, 1, \linear, 0.0, 0.38],

+

+

\spSustain, [0.2, 2, \linear, 0.0, 0.884],

+

\spLegato, [0.6, 1.2, \linear, 0.0, 0.996],

+

\spDev, [0.0, 1, \linear, 0.0, 0.41],

+


+

\panMax, [0.0, 1, \lin, 0.0, 0.85],

+

\amp, [0.0, 1, \lin, 0.01, 0.35]

+

], stream: q

+

).gui(varColorGroups: (0..22).clumps([4,3,3,3,5,3,1,1]) )

+

)

+


+


+


+

Example 2d:   Wave sets

+


+


+

// For this example you need Alberto de Campo's Wavesets class 

+

// (Quark extension, implementation following definitions of Trevor Wishart)

+

// and the Function d from Ex. 2b

+


+

// the wave set player synth optionally adds a BPF applied to the signal, 

+

// amount can be controlled with bpWet

+

// attack and release time > 0 for smoothening

+


+

(

+

SynthDef(\wsPlayer, { arg out = 0, buf = 0, start = 0, length = 441, 

+

rate = 1, att = 0.03, rel = 0.03, wvDur = 1, panMax = 0, amp = 0.2, 

+

delayL = 0.0, delayR = 0.0, bpFreq = 500, bpRQ = 0.5, bpWet = 1; 

+

var phasor, env, granSrc, src, attEff, relEff, sus;

+

+

phasor = Phasor.ar(0, BufRateScale.ir(buf) * rate, 0, length) + start;

+

attEff = min(att, wvDur/2);

+

relEff = min(rel, wvDur/2);

+

sus = wvDur - attEff - relEff;

+


+

env = EnvGen.ar(Env([0, 1, 1, 0], [attEff, sus, relEff], \sine), doneAction: 2);

+

granSrc = BufRd.ar(1, buf, phasor);

+

src = (BPF.ar(granSrc, bpFreq, bpRQ, mul: (bpRQ ** -1) * (400 / bpFreq ** 0.5)) * 

+

bpWet + (granSrc * (1 - bpWet)));

+

OffsetOut.ar(out, Pan2.ar(src, panMax, amp) * env);

+

}).add;

+


+


+

a = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+

b = Buffer.read(s, a);

+

w = Wavesets.from(a);

+


+

// relative positions of zero crossings

+

x = w.fracXings.drop(-1) / w.numFrames;

+

)

+


+


+

// Note that trigRate / event duration is not controlled directly in this setup,

+

// it's derived from wave set length (may vary) and legato factor. 

+

// For short wave sets and a low legato value durs could become so short that 

+

// a sudden mass of grains could cause hangs.

+

// To avoid this a parameter maxTrigRate is invented.

+


+

// As wavesets start at distinguished positions in the buffer and 

+

// - together with legato - durations are determined,

+

// an additionally given position rate in general cannot result in a "correct"

+

// looping through the buffer, there will be a deviation.

+

// Here two types of approximation can be choosen with posType:

+

// Type 0 takes the waveset nearest to the calculated exact position.

+

// Type 1 linearly maps positions to wave set indices.

+

// As wave sets are of different length the latter is a rough heuristic

+

// accelerating buffer parts with relatively low frequencies. 

+


+

(

+

p = Pbind(

+

\instrument, \wsPlayer,

+

+

// refering to interpreter variables within Pfuncs 

+

// when running several examples in parallel is unsafe,

+

// so pass them here, then access from within the event

+

+

\b, b,

+

\w, w,

+

\x, x,

+

+

\time, Ptime(),

+

\posLo, PL(\posLo),

+

\posHi, PL(\posHi),

+

\posRate, PL(\posRate),

+

+

\pos, Pfunc { |e| (e.time * e.posRate / e.b.duration) % 

+

(e.posHi - e.posLo) + e.posLo },

+


+

// Estimation of ws index from relative position, see explanation above,

+

// indexIn might be a bottleneck with large buffers,

+

// also a more rough estimation of ws index could be used:

+

// \startWs, Pfunc { |e| e.pos.linlin(0, 1, 0, e.w.xings.size - 2).round.asInteger }

+

+

\startWs, Pfunc { |e| 

+

(~posType == 0).if {

+

e.x.indexIn(e.pos)

+

}{

+

e.pos.linlin(e.posLo, e.posHi, e.x.indexIn(e.posLo), e.x.indexIn(e.posHi))

+

.round.asInteger 

+

}

+

},

+


+

\numWs, PLswitch1(d.(\numWsLo, \numWsHi), \numWsD),

+

\repeats, PLswitch1(d.(\repeatsLo, \repeatsHi), \repeatsD),

+

+

\bpFreq, PLswitch1(d.(\bpFreqLo, \bpFreqHi), \bpFreqD),

+

\bpRQ, PL(\bpRQ),

+

\bpWet, PL(\bpWet),

+

+

\rate, PLswitch1(d.(\rateLo, \rateHi), \rateD),

+

\data, Pfunc { |e| e.w.frameFor(e.startWs, e.numWs)  },

+

+

\buf, b.bufnum,

+

\start, Pkey(\data).collect(_[0]), // startFrame

+

\length, Pkey(\data).collect(_[1]), // length (frameNum)

+

\wvDur, Pkey(\data).collect(_[2]) * Pkey(\repeats), // sustain time

+

+

\calculatedDur, Pkey(\wvDur) / PLswitch1(d.(\legatoLo, \legatoHi), \legatoD),

+

\dur, Pfunc { |e| max(e.calculatedDur, 1 / ~maxTrigRate) },

+


+

\panMax, PLseq([-1,1]) * PL(\panMax),

+

\amp, PL(\amp),

+

\out, 0

+

);

+


+


+

VarGui([

+

\posLo, [0, 1, \lin, 0.0, 0.15],

+

\posHi, [0, 1, \lin, 0.0, 0.45],

+

\posRate, [0.0, 2, \lin, 0.01, 0.25],

+

\posType, [0, 1, \lin, 1, 0],

+

+

\numWsLo, [1, 100, \lin, 1, 5],

+

\numWsHi, [1, 100, \lin, 1, 23],

+

\numWsD, [0, 7, \lin, 1, 6],

+

+

\repeatsLo, [1, 4, \lin, 1, 1],

+

\repeatsHi, [1, 4, \lin, 1, 2],

+

\repeatsD, [0, 7, \lin, 1, 6],

+

+

\maxTrigRate, [1, 250, \lin, 1, 200],

+

+

\bpFreqLo, [50, 10000, \exp, 0.1, 67],

+

\bpFreqHi, [50, 10000, \exp, 0.1, 9600],

+

\bpFreqD, [0, 7, \lin, 1, 7],

+

\bpRQ, [0.01, 0.99, \lin, 0.005, 0.22],

+

\bpWet, [0.0, 1, \lin, 0.005, 0.0],

+

+

\rateLo, [0.05, 2, \lin, 0.0, 0.32],

+

\rateHi, [0.05, 2, \lin, 0.0, 1.3],

+

\rateD, [0, 7, \lin, 1, 7],

+

+

\att, [0.0, 0.05, \lin, 0.001, 0.001],  

+

\rel, [0.0, 0.05, \lin, 0.001, 0.001], 

+

+

\panMax, [0.0, 1, \lin, 0, 0.8],

+

\legatoLo, [0.3, 25, \exp, 0, 0.4],

+

\legatoHi, [0.3, 25, \exp, 0, 5.5],

+

\legatoD, [0, 7, \lin, 1, 7],

+

\amp, [0.0, 2.0, \lin, 0.0, 0.7]  

+

], 

+

stream: p

+

).gui(varColorGroups: (0..25).clumps([4,3,3,1,5,3,2,1,3,1]))

+

)

+


+


+


+

3.) Hybrid Implementations

+


+

Example 3a:   Granulation with ugen plus step sequencing

+


+


+

// Here the trigger for the TGrains ugen comes from a Pbind

+

// which also generates rates like a step sequencer

+


+


+

(

+

SynthDef(\gran_3a, { arg out = 0, posLo = 0.0, posHi = 1.0, 

+

posRate = 1, posDev = 0.01, bufNum = 0, t_trig = 0, 

+

granDur = 0.1, t_rate = 1.0, rateDev = 0, 

+

panMax = 1, amp = 0.1, interp = 4;

+

+

var pan, pos, bufDur, bufDurSection, posDif;

+

+

posDif = posHi - posLo;

+

bufDur = BufDur.kr(bufNum);

+

bufDurSection = bufDur * posDif;

+

pos = posLo * bufDur +

+

(Phasor.ar(0, BufRateScale.kr(bufNum) * posRate / SampleRate.ir, posLo * bufDur, posHi * bufDur) + 

+

(TRand.kr(-0.5, 0.5, t_trig) * posDev * bufDur)).mod(bufDurSection);  

+

pan = Demand.kr(t_trig, 0, Dseq([panMax, panMax.neg], inf) * 0.999);

+

Out.ar(out, TGrains.ar(2, t_trig, bufNum, t_rate, pos, granDur, pan, 1, interp) * amp);

+

}, metadata: (

+

specs: (

+

posLo: [0.01, 0.99, \lin, 0.01, 0.2],

+

posHi: [0.01, 0.99, \lin, 0.01, 0.75],

+

posRate: [0.1, 2, \lin, 0.01, 1],

+

posDev: [0, 0.2, 5, 0, 0.01],

+

panMax: [0.0, 1, \lin, 0.005, 0.8],

+

amp: [0.0, 1, \lin, 0.005, 0.5]

+

)

+

)

+

).add;

+


+


+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+

)

+


+


+

// As the setting Pbind needs to know the Synth's nodeID

+

// the Synth has to be started explicitely and passed to the VarGui later on

+

// (VarGui takes Synths as well as SynthDefs, passing a SynthDef is recommended in general).

+

// The Synth starts silently as t_trig defaults to 0.

+


+


+

(

+

x = Synth(\gran_3a, [\bufNum, b]).register;

+


+

p = Pbind(

+

\type, \set,

+

\id, x,

+

\args, [\t_trig, \t_rate, \granDur],

+

+

\dur, PL(\dur),

+

\granDur, Pkey(\dur) * PL(\legato),

+

\t_trig, 1,

+

\t_rate, PLseq(\midi).midiratio

+

);

+

)

+


+


+

// Do start and pause with the Pbind (EventStreamPlayer) player.

+

// If you stop the Synth you cannot resume audio with a new Synth

+

// as the EventStreamPlayer has lost the correct nodeID

+

// (however the Synth can be paused and resumed).

+


+


+

(

+

VarGui(varCtr: [

+

\dur, [0.01, 0.1, \lin, 0, 0.05], 

+

\legato, [0.3, 3, \lin, 0, 2], 

+

\midi, [-12, 12, \lin, 1, 1] ! 8 

+

], synth: x, stream: p

+

).gui;

+

)

+


+


+

Example 3b:   Using external control synths

+


+


+

// Example needs SynthDef from Ex. 2a

+


+

// Also with language-driven granulation 

+

// controls can be delegated to separate Synths,

+

// which output to control busses.

+

// Control inputs of single synths can read from

+

// these busses (comfortably use aBus.asMap in the Pbind)

+

// or synths can read from busses with In.kr (needs extra definition).

+


+

// A nearby parameter to determine with a separate synth is grain position

+


+

(

+

SynthDef(\bufPhasor, { |out = 0, sndBuf = 0, posRate = 1, posLo = 0, posHi = 1, posDev = 0.01| 

+

var pos, posDif;

+

posDif = posHi - posLo;

+

pos = Phasor.ar(1, posRate * BufRateScale.kr(sndBuf) / BufFrames.kr(sndBuf), 0, posDif) 

+

+ WhiteNoise.kr(posDev / 2) % posDif + posLo;

+

Out.kr(out, A2K.kr(pos)); 

+

} 

+

).add; 

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+

w = Buffer.sendCollection(s, Signal.hanningWindow(1024));

+

c = Bus.control(s,1);

+

)

+


+

// in GUI start EventStreamPlayer and bufPhasor Synth

+


+

(

+

p = Pbind( 

+

\instrument, \gran_2a, 

+

\sndBuf, b,

+

\windowBuf, w, 

+

+

\dur, 1 / PL(\trigRate), 

+

\granDur, PL(\granDur), 

+

+

\pos, c.asMap,

+

\rate, PL(\rate),  

+

\amp, PL(\amp),

+

\panMax, PLseq([-1,1]) * PL(\panMax),

+

\out, 0

+

);

+


+

VarGui([

+

\trigRate, [1, 200, \lin, 0.01, 50],

+

\granDur, [0.01, 0.3, \lin, 0.005, 0.12], 

+

\rate, [0.1, 3, \lin, 0.01, 1],

+

\panMax, [0.0, 1, \lin, 0.0, 0.8],

+

\amp, [0.0, 1, \lin, 0.01, 0.3]

+

],[

+

\out, c.index,

+

\sndBuf, b.bufnum,

+

\posLo, [0, 1, \linear, 0.005, 0], 

+

\posHi, [0, 1, \linear, 0.005, 1],

+

\posRate, [0.1, 2, \linear, 0.01, 1],

+

\posDev, [0, 0.2, 5, 0, 0.01]

+

], 

+

p, \bufPhasor

+

).gui(sliderPriority: \synth, playerPriority: \synth);

+

)

+


+


+


+

Example 3c:   Switching between ugens of external control synths

+


+


+

// Example needs SynthDef from Ex. 2a

+


+

// external control synth for position as in 3a, but with 

+

// different types of movement to select

+


+

(

+

SynthDef(\bufPosLFO, { |out = 0, lfoType = 0, freq = 1, posLo = 0, posHi = 1, posDev = 0.01| 

+

var pos, posDif;

+

posDif = posHi - posLo;

+

pos = WhiteNoise.kr(posDev / 2) + Select.kr(lfoType,

+

[LFDNoise0, LFDNoise1, LFDNoise3].collect(_.kr(freq, posDif))) % posDif + posLo;

+

Out.kr(out, pos); 

+

}).add; 

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+

w = Buffer.sendCollection(s, Signal.hanningWindow(1024));

+

c = Bus.control(s,1);

+

)

+


+

// added parallel grains

+

// in GUI start bufPhasor Synth before or together with EventStreamPlayer

+


+

// buffer position movement can be forward and backward

+

// lfoType 0: LFDNoise0 (jumps)

+

// lfoType 1: LFDNoise1 (linear interpolation)

+

// lfoType 2: LFDNoise3 (cubic interpolation, smooth, useful in many control contexts)

+

 

+

(

+

p = Pbind( 

+

\instrument, \gran_2a, 

+

\sndBuf, b,

+

\windowBuf, w, 

+

+

\dur, 1 / PL(\trigRate), 

+

\granDur, PL(\granDur), 

+

+

\pos, c.asMap,

+

\rate, PL(\rate) * PL(\midiAdd).midiratio,

+

\amp, PL(\amp),

+

\panMax, PLseq([-1,1]) * PL(\panMax),

+

\out, 0

+

);

+


+

VarGui([

+

\trigRate, [1, 200, \lin, 0.01, 80],

+

\granDur, [0.01, 0.3, \lin, 0.005, 0.195], 

+

\rate, [0.1, 1.5, \lin, 0.01, 0.6],

+

\midiAdd, [-5, 0].collect([-12, 12, \lin, 1, _]),

+

\panMax, [0.0, 1, \lin, 0.0, 0.95],

+

\amp, [0.0, 1, \lin, 0.01, 0.15]

+

],[

+

\out, c.index,

+

\posLo, [0, 1, \linear, 0.005, 0.15], 

+

\posHi, [0, 1, \linear, 0.005, 0.43],

+

\posDev, [0, 0.2, 5, 0, 0.0],

+

\freq, [0.01, 2, \lin, 0, 0.96],

+

\lfoType, [0, 2, \lin, 1, 2]

+

], 

+

p, \bufPosLFO

+

).gui(sliderPriority: \synth, playerPriority: \synth);

+

)

+


+


+


+

Example 3d:   Pattern-driven sequencing of granular events using demand rate ugens

+


+

// This is basically the same as SynthDef \gran_1d with an additional envelope,

+

// grain position phasor is left out, position is determined by a phasor synth via bus and mapping.

+


+

(

+

// length of demand rate sequence, you might want to check larger sizes

+

// in connection with gui arg tryColumnNum > 1

+

~n = 5;

+


+


+

SynthDef(\gran_3d, { |out = 0, soundBuf, envBuf, att = 0.1, sus = 1, rel = 1, pos = 0.5,

+

granDurMul = 1, rateMul = 1, overlapMul = 1, panMul = 1, amp = 0.5, interp = 2| 

+

+

var signal, bufDur, granDur, granGate, relGranDurs, overlap, relOverlaps, overlapSeq, 

+

pan, rate, relRates, rateSeq, granDurSeq; 

+

+

// array args for demand rate sequencing, short form of NamedControl

+

relGranDurs = \relGranDurs.kr(0.1!~n);

+

relRates = \relRates.kr(1!~n); 

+

relOverlaps = \relOverlaps.kr(1!~n); 

+


+

// Dstutter (or Dunique) necessary as granDurSeq is polled twice: granGate and granDur

+

granDurSeq = Dstutter(2, Dseq(relGranDurs, inf));  

+

rateSeq = Dseq(relRates, inf); 

+

overlapSeq = Dseq(relOverlaps, inf); 

+

+

granGate = TDuty.ar(granDurSeq * granDurMul); 

+

granDur = Demand.ar(granGate, 0, granDurSeq * granDurMul);

+

rate = Demand.ar(granGate, 0, rateSeq) * rateMul; 

+

pan = Demand.ar(granGate, 0, Dseq([1, -1], inf)) * 0.999 * panMul; 

+

overlap = Demand.ar(granGate, 0, overlapSeq) * overlapMul;

+


+

bufDur = BufDur.kr(soundBuf);

+

signal = TGrains.ar(2, granGate, soundBuf, rate, pos * bufDur, granDur * overlap, pan, 1, interp) *

+

EnvGen.kr(Env([0, 1, 1, 0], [att, sus, rel]), doneAction: 2);

+

+

Out.ar(out, signal * amp); 

+

}

+

).add; 

+


+

SynthDef(\bufPhasor_2, { |out = 0, sndBuf = 0, posRateE = 0, posRateM = 1, 

+

posLo = 0, posHi = 1, posDev = 0.01| 

+

+

var pos, posDif, posRate;

+

posDif = posHi - posLo;

+

posRate = 10 ** posRateE * posRateM;

+

pos = Phasor.ar(1, posRate * BufRateScale.kr(sndBuf) / BufFrames.kr(sndBuf), 0, posDif) 

+

+ WhiteNoise.kr(posDev / 2) % posDif + posLo;

+

Out.kr(out, A2K.kr(pos)); 

+

} 

+

).add; 

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+

c = Bus.control(s,1);

+

)

+


+


+

// Pattern control of granular events

+


+

// Every granular event gets a duration between durLo and durHi (exp distribution)

+

// and an envelope according to att, sus and rel.

+

// One of four events is randomly defined as rest. 

+


+

// arg arrays relGranDurs, relRates and relOverlaps determine

+

// demand rate sequencing for granulation as in Ex. 1d.

+

// Per event they are multiplied with corresponding factors

+

// limited by granDurMulLo/Hi, rateMulLo/Hi and overlapMulLo/Hi

+


+

// start phasor synth (first row in player console) before event stream player

+

// event stream player might start with rest 

+


+


+

(

+

// Passing arrayed args with an event pattern requires

+

// wrapping them into an array or Ref object.

+

// This is necessary to distinguish from triggering 

+

// several synths per event.

+

// .collect(`_) is short for .collect { |x| Ref(x) }

+

// .collect([_]) or .collect { |x| [x] } would also be possible

+


+

p = Pbind(

+

\instrument, \gran_3d,

+

\soundBuf, b,

+

\pos, c.asMap,

+

+

\dur, PLexprand(\durLo, \durHi),

+

\type, PLshufn(\note!3 ++ \rest),

+

\att, PL(\att),

+

\sus, PL(\sus),

+

\rel, PL(\rel),

+


+

+

\relGranDurs, PL(\relGranDurs).collect(`_),

+

\granDurMul, PLwhite(\granDurMulLo, \granDurMulHi),

+


+

\relOverlaps, PL(\relOverlaps).collect(`_),

+

\overlapMul, PLwhite(\overlapMulLo, \overlapMulHi),

+

+

\relRates, PL(\relRates).collect(`_),

+

\rateMul, PLwhite(\rateMulLo, \rateMulHi),

+

+

\panMul, PL(\panMul),

+

\amp, PL(\amp)

+

);

+


+

VarGui([

+

\durLo, [0.05, 1.5, \lin, 0, 0.16],

+

\durHi, [0.05, 1.5, \lin, 0, 1.2],

+

\att, [0.01, 0.6, \lin, 0, 0.4],

+

\sus, [0.01, 0.6, \lin, 0, 0.05],

+

\rel, [0.01, 0.6, \lin, 0, 0.5],

+

\relGranDurs, { |i| [0.01, 0.1, \lin, 0, i * 0.005 + 0.02] } ! ~n,

+

\granDurMulLo, [0.03, 2, \lin, 0, 0.05],

+

\granDurMulHi, [0.03, 2, \lin, 0, 1.8],

+

+

\relRates, { |i| [0.1, 1.5, \lin, 0, (5-i) * 0.1 + 0.5] } ! ~n,

+

\rateMulLo, [0.1, 2, \lin, 0, 0.5],

+

\rateMulHi, [0.1, 2, \lin, 0, 1.5],

+


+

\relOverlaps, [0.5, 3, \lin, 0, 2] ! ~n,

+

\overlapMulLo, [0.1, 2, \lin, 0, 0.25],

+

\overlapMulHi, [0.1, 2, \lin, 0, 1.8],

+

+

\panMul, [0.0, 1, \lin, 0.0, 0.8],

+

\amp, [0.0, 3, \lin, 0.01, 1.8]

+

],[

+

\out, c.index,

+

\sndBuf, b.bufnum,

+

\posLo, [0, 1, \lin, 0.005, 0.1], 

+

\posHi, [0, 1, \lin, 0.005, 0.9],

+

\posRateE, [-3, 4, \lin, -1, 0],

+

\posRateM, [0.1, 10, \exp, 0.01, 1],

+

\posDev, [0, 0.2, 5, 0, 0.01]

+

], 

+

p, \bufPhasor_2

+

).gui(

+

tryColumnNum: 2,

+

sliderPriority: \synth, 

+

playerPriority: \synth,

+

varColorGroups: (0..(~n*3+12)).clumps([2,3,~n+2,~n+2,~n+2,1,1]), 

+

synthColorGroups: (0..6).clumps([1,1,2,2,1]), 

+

labelWidth: 90,

+

sliderWidth: 300

+

);    

+

)

+


+


+

4.) Extensions of Setups

+


+


+

.) Changing ranges and scaling

+


+

This concerns all control parameters in question. E.g. the layering of long grains (> 200 ms) in connection with small rates of position changes (posRate) often has interesting effects. One may want to drop the term granulation in that case, though it's the same structure of synthesis. For such parameters with a very large coefficient boundHi / boundLo one may take exponential scaling, and if this is not fine enough you can invent a control pair of mantissa and exponent (as for posRate in Ex. 1b).

+

+

.) Parameter linkage

+


+

On the one hand a logical restriction, on the other hand it can make sense from a musical / perceptional point of view. E.g. shortening of grains could be linked with a raise of rate (as low frequencies might fail to unfold in short grains). Anyway parameter linkage is reducing complexity - it's a trade-off between simplicity of the interface and exclusion of certain constellations which should be considered from case to case.

+

+

.) Inventing and extending controls and LFO changes dependant on specific buffers and parameter sets

+


+

Say one has started playing around with a certain buffer and a general granulation patch. A parameter set that gives an interesting sound may react in a very interesting way on a change of a single parameter e.g., trigger rate. Then it may be an option to build in a control or a LFO specifically designed for that parameter - LFO is meant here in a general sense, it could be a LFO in a Synth, a dedicated LFO synth or defined by a rapidly sequencing Pattern.

+


+

.) Iterated granulation

+


+

Granulation of buffers themselves resulting from buffer granulation can give interesting effects.

+


+

.) Spatialization

+


+

For the sake of ease and comparison a L/R-switch per grain with one panning parameter was used in the examples above. Needless to say that spatial scattering of grains remains a large field of experimentation. Generally spoken spatialization can be part of the synthesis process or carried through independently afterwards, but also a combination of both approaches is feasible.

+

+

.) Effects

+


+

Effect processing can be applied to all or single grains in a language-driven granulation setup. There can be one or more effects, serial or in parallel, defined outside or inside the Synth playing the buffer, as with the BPF in Ex 2b - 2d. In these examples the bandpass is applied in general, but also a sequencing of effects can be done. And even in the case of just one effect (e.g. reverb) a decent sequencing of Fx- / noFx-events can sound very interesting. This can be done with PbindFx (as in the project kitchen studies), sequencing not more than one effect per grain can be done straightly: continously running effect synths read from different buses and events with different out values cause the player synths to output to these buses. 

+

 

+

 .) Micro rhythm

+


+

See Xenakis' suggestion of Sieves with rhythm generation as a special application, a granulation example is contained in the example section of: Sieves and Psieve patterns. PSPdiv, a dynamic multi-layer pulse divider, which is based on Pspawner, can also be used in this context, see the last example of PSPdiv. 

+

 

+

 

+

 

+

 

+

 

+ + diff --git a/Help/DIdev.html b/Help/DIdev.html new file mode 100755 index 0000000..00d7b08 --- /dev/null +++ b/Help/DIdev.html @@ -0,0 +1,449 @@ + + + + + + + + + + + +

DIdev pseudo drate ugen, searches for numbers with integer distance from a source signal, optionally avoiding repetitions within a span

+


+

Part of: miSCellaneous

+


+

Inherits from: DUGen

+


+

DIdev / PIdef / PLIdev search for numbers with integer distance from a source signal / pattern up to a given deviation. Repetitions within a lookback span are avoided, DIdev / PIdef / PLIdev randomly choose from possible solutions. Intended for search within integer grids (pitches, indices etc.), however applications with non-integer sources are possible, see examples.

+


+

NOTE: DIdev needs at least a SC version >= 3.7.0 for proper working.

+


+

NOTE: It's the user's responsibility to pass a combination of deviation and lookback values that allows a possible choice, see examples.

+


+

NOTE: In contrast to PIdev and PLIdev, DIdev needs to know maximum deviations (minLoDev, maxHiDev) beforehand. Together with maxLookBack they determine multichannel sizes, so a relatively high number of ugens might be involved. 

+


+

See also: Idev suite, PIdev, PLIdev

+


+


+

Creation / Class Methods

+


+

Creates a new DIdev object.

+

+

*new (in, maxLookBack = 3, minLoDev = -3, maxHiDev = 3, lookBack, loDev, hiDev, thr = 1e-3, length = inf)

+

+

in - The source signal, integer distances are calculated from the value of this signal with each trigger.

+

Can be demand rate or other ugen.

+

maxLookBack - Integer, the maximum lookback span. Cannot be modulated, defaults to 3.

+

minLoDev - Integer, the minimum low deviation (must not be exceeded by any loDev value). 

+

Should be negative, cannot be modulated, defaults to -3.

+

maxHiDev - Integer, the maximum high deviation (must not be exceeded by any hiDev value).  

+

Should be positive, cannot be modulated, defaults to 3.

+

lookBack - Determines the current lookback span for avoiding repetitions.

+

Can be modulated (demand rate or other ugen, no ar) but must not exceed maxLookBack.

+

If no value is passed, then maxLookBack is taken.

+

loDev - Determines the current low deviation for the search.

+

Can be modulated (demand rate or other ugen) but must not exceed minLoDev.

+

If not specified, them minLoDev is taken. 

+

hiDev - Determines the current high deviation for the search.

+

Can be modulated (demand rate or other ugen) but must not exceed maxHiDev.

+

If not specified, then maxHiDev is taken. 

+

thr - Threshold for equality comparison. Can be modulated (demand rate or other ugen).

+

Defaults to 1e-3. 

+

length - Number of repeats. Defaults to inf. 

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

Ex.1) Basic usage: random choice within region without repetitions

+

+

// constant source (72), max deviation +/- 3

+

// no repetition within 5 pitches

+


+

(

+

x = {

+

var trig = Impulse.ar(5);

+

var midi = Demand.ar(trig, 0, DIdev(72, 2, -1, 2));

+

midi.poll(5, label: \midi);

+

SinOsc.ar(midi.midicps.lag(0.007)) ! 2 * 0.1

+

}.play;

+

)

+


+

x.release

+


+


+


+

Ex.2) Variable deviations and lookBack

+


+

(

+

x = {

+

|loDev = -6, hiDev = 5, lookBack = 2|

+

var trig = Impulse.ar(5);

+

// define maxLookBack, minLoDev and maxHiDev

+

var midi = Demand.ar(trig, 0, DIdev(72, 11, -6, 5, lookBack, loDev, hiDev));

+

midi.poll(5, label: \midi);

+

SinOsc.ar(midi.midicps.lag(0.007)) ! 2 * 0.1

+

}.play;

+

)

+


+


+

// as lookBack equals 2, this defines a fixed sequence (up or down anyway)

+


+

(

+

x.set(\loDev, -1);

+

x.set(\hiDev, 1)

+

)

+


+


+

// widen range

+


+

(

+

x.set(\loDev, -6);

+

x.set(\hiDev, 5);

+

)

+


+


+

// force a twelve-tone row

+


+

x.set(\lookBack, 11)

+


+


+

// contradictory input, lookBack 11 not possible within range, causes repetitions

+


+

(

+

x.set(\loDev, -3);

+

x.set(\hiDev, 2)

+

)

+


+

x.release

+


+


+

Ex.3) Moving source signal

+


+

(

+

x = {

+

|loDev = -1, hiDev = 1, lookBack = 2|

+

var trig = Impulse.ar(7);

+

// define maxLookBack, minLoDev and maxHiDev

+

// source is rounded to integers here

+

var midi = Demand.ar(trig, 0, DIdev(SinOsc.ar(0.2, 0, 15, 82).round, 11, -6, 5, lookBack, loDev, hiDev));

+

midi.poll(7, label: \midi);

+

SinOsc.ar(midi.midicps.lag(0.007)) ! 2 * 0.1

+

}.play;

+

)

+


+


+

// widen range and increase lookBack

+


+

(

+

x.set(\loDev, -6);

+

x.set(\hiDev, 5);

+

x.set(\lookBack, 10);

+

)

+


+

x.release

+


+


+


+

Ex.4) Dynamic deviation range and lookBack

+


+

// lookBack and deviations coupled here

+

// maxLookBack, minLoDev and maxHiDev must be large enough

+


+

(

+

x = {

+

var trig = Impulse.ar(7);

+

var dev = SinOsc.ar(0.1, -pi/2).range(1, 5);

+

var midi = Demand.ar(trig, 0, 

+

DIdev(78, 10, -5, 5, 

+

SinOsc.kr(0.1, -pi/2).range(1, 5).round.poll(label: \lookBack),

+

dev.neg.poll(label: \loDev),

+

dev.poll(label: \hiDev)

+

)

+

);

+

    SinOsc.ar(midi.lag(0.007).midicps, 0, 0.1) ! 2

+

}.play;

+

)

+


+

x.release

+


+


+

// loDev and hiDev can be demand rate

+


+

(

+

x = {

+

    var trig = Impulse.ar(5);

+

var hiDev = Dseq([1, 10], inf);

+

    var midi = Demand.ar(trig, 0,

+

        DIdev(78, 10, -10, 10,

+

1,

+

            Dseq([-10, 5], inf),

+

            Dseq([-5, 10], inf)

+

        )

+

    );

+

    SinOsc.ar(midi.lag(0.007).midicps, 0, 0.1) ! 2

+

}.play;

+

)

+


+

x.release

+


+


+

// lookBack can also be demand rate

+


+

(

+

x = {

+

    var trig = Impulse.ar(5);

+

var hiDev = Dseq([1, 10], inf);

+

    var midi = Demand.ar(trig, 0,

+

DIdev(70, 10, -15, 15,

+

Dstutter(4, Dseq([1, 3], inf)),

+

Dstutter(4, Dseq([-9, 7], inf)),

+

Dstutter(4, Dseq([-8, 10], inf))

+

        )

+

    );

+

midi.poll(trig);

+

SinOsc.ar(midi.lag(0.007).midicps, 0, 0.1) ! 2 * EnvGen.ar(Env.asr(0.15))

+

}.play;

+

)

+


+

x.release

+


+


+


+

Ex.5) Non-integer source

+


+

(

+

x = {

+

|lookBack = 3, thr = 1|

+

var trig = Impulse.ar(7);

+

// for a non-integer source it makes sense to take a sufficiently large threshold thr

+

var midi = Demand.ar(trig, 0, DIdev(SinOsc.ar(0.2, 0, 15, 82), 5, -6, 5, lookBack, thr: thr));

+

midi.poll(7, label: \midi);

+

SinOsc.ar(midi.midicps.lag(0.007)) ! 2 * 0.1

+

}.play;

+

)

+


+

// close floats can occur here 

+

x.set(\thr, 0.01)

+


+

// not here

+

x.set(\thr, 2)

+


+

x.release

+


+


+


+
Ex.6) Multichannel expansion

+

 

+


+

// nothing especially implemented

+


+

(

+

x = {

+

var trig = Impulse.ar(7);

+

var in = [0, 8.5];

+

var maxLookBack = [1, 3];

+

var loDev = [-1, -5];

+

var hiDev = [1, 5];

+

var midi = { |i| Demand.ar(trig, 0,

+

DIdev(

+

in[i] + SinOsc.ar(0.1, -pi/2).range(65, 85).round,

+

maxLookBack: maxLookBack[i],

+

loDev: loDev[i],

+

hiDev: hiDev[i]

+

).dpoll(label: "midi_" ++ (i == 0).if { "lo" }{ "hi"})

+

) } ! 2;

+

SinOsc.ar(midi.lag(0.007).midicps, 0, 0.1)

+

}.play;

+

)

+


+

x.release

+


+


+

Ex.7) Application to other params: rhythm

+

 

+

// if we have indexed data for whatever, we can slide over it

+

// prepare some rhythms in order

+

// use them for SynthDef

+


+

(

+

~rhythmBase = [

+

[1, 1],

+

[2, 1, 1],

+

[1, 1, 2],

+

].collect(_.normalizeSum);

+


+

~rhythms = ~rhythmBase *.x [1, 2];

+

~rhythmNum = ~rhythms.size;

+

~rhythms = ~rhythms.scramble;

+


+

"rhythm types: ".postln;

+

~rhythms.do { |r, i| i.post; ": ".post; r.postln };

+


+

SynthDef(\sine_rhythm, { |out = 0, freq = 400, att = 0.01, rel = 0.1, gate = 1, amp = 0.2|

+

var trig, loDev = -1, hiDev = 1, sig, src,

+

rhy = ~rhythmNum.collect { |i| Dseq(~rhythms[i], 1) };

+


+

trig = TDuty.ar(

+

Dswitch(rhy,

+

// be careful not to exceed range of rhythm indices, which can result in server quit

+

Dpoll(

+

DIdev(SinOsc.ar(0.1).range(loDev.abs, ~rhythmNum - hiDev - 1).round, 2, loDev, hiDev),

+

'rhythm type'

+

)

+

) * 0.3

+

);

+

src = SinOsc.ar(Demand.ar(trig, 0, LFDNoise3.ar([0.5, 3], [10, 15], [60, 85])).midicps);

+

sig = Decay2.ar(trig, att, rel) * src;

+

Out.ar(out, sig * EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2) * amp);

+

}).add;

+

)

+


+

x = Synth(\sine_rhythm)

+


+

x.release

+


+


+

Ex.8) Proof of concept

+


+

(

+

// Function to check an array for repetitions within a maximum test span

+


+

~repetitionCheck = { |array, maxTestSpan|

+

maxTestSpan.do { |i|

+

var result = (array.drop(i+1) - array).drop((i+1).neg).includes(0).not;

+

("no repetitions within a span of " ++ (i+2).asString ++ " items: ").post;

+

result.postln;

+

}

+

};

+


+

// prepare buffer to store DIdev values 

+


+

n = 10000; // buffer size

+

r = 10000; // trigger rate

+

b = Buffer.alloc(s, n);

+

)

+


+


+

// run to store, wait until finished (a bit more than 1 second)

+

(

+

{

+

var trig = Impulse.ar(r);

+

Demand.ar(trig, 0, Dbufwr(DIdev(Dbrown(0, 20, 0.3).round, 5, -7, 7), b, Dseries(0, 1, n), 0));

+

Line.ar(dur: n / r + 0.1, doneAction: 2);

+

0

+

}.play;

+

)

+


+

// move data to language

+


+

b.loadToFloatArray(action: { |x| a = x.asInteger; "array filled \n".postln; })

+


+


+

// no repetitions within a maximum span of 6 (maxLookBack was 5)

+


+

~repetitionCheck.(a, 10);

+


+


+


+

Ex.9) Switching signals with DXMix

+


+

// source of 10 stereo granulations:

+

// they differ in position movement, trigrate and rate

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+

(

+

x = {

+

var pos = { |i| SinOsc.ar(0.02, pi/5 * i).range(0.1, 0.8) } ! 10;

+

var sig;

+


+

sig = pos.collect { |p, i|

+

TGrains.ar(

+

2,

+

trigger: Impulse.ar(i * 10 + 30),

+

bufnum: b,

+

rate: LFDNoise3.ar(0.1).range(0.3, 1.5),

+

centerPos: p * BufDur.ir(b),

+

dur: 0.1,

+

pan: Dseq([-1, 1], inf)

+

)

+

};

+

// switch between stereo sources with DXMix

+

DXMix.ar(

+

Dpoll(DIdev(SinOsc.ar(0.1).range(2, 7).round, 2, -2, 1)),

+

`sig,

+

fadeMode: 1,

+

stepTime: 0.03,

+

fadeTime: 0.002

+

) * 5

+

}.play

+

)

+


+

x.release

+


+


+

// sine stereo sources

+


+

(

+

x = {

+

var sig = (1..20).scramble.collect { |i| 

+

SinOsc.ar(

+

[i, 20-i] * 100 * LFDNoise3.ar(0.1).range(0.97, 1.03), 

+

0, 

+

0.05 * LFDNoise3.ar(1 ! 2).range(0.1, 1)

+

)

+

};

+

DXMix.ar(

+

Dpoll(DIdev(SinOsc.ar(0.05).range(2, 17).round, 2, -2, 2)),

+

`sig,

+

fadeMode: 1,

+

stepTime: 0.03,

+

fadeTime: 0.002

+

) ;

+

}.play

+

)

+


+

x.release

+


+


+ + diff --git a/Help/DX suite.html b/Help/DX suite.html new file mode 100755 index 0000000..9b3a302 --- /dev/null +++ b/Help/DX suite.html @@ -0,0 +1,169 @@ + + + + + + + + + + + +

DX suite pseudo ugens for crossfaded mixing and fanning according to demand-rate control  

+


+

Part of: miSCellaneous

+


+

See also: DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFan, DXFanOut, Buffer Granulation, Live Granulation, PbindFx, kitchen studies, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+

DX (Demand XFade) ugens are built upon DemandEnvGen and PanAz and can hence use their dynamic control options. Their user interface and underlying ugen structure is almost identical, due to multichannel expansion they are capable of triggering complex signal flows. As demand rate sequencing can be performed fast, DX ugens can, beneath their functionality as signal distribution controllers in the medium or large time-scale, be used as genuin synthesis tools in the area of microsound, e.g. for fast switching between sources as well as different processings of them. DXEnvFan and DXEnvFanOut give specific options as they can be used as multichannel envelopes and triggers at the same time. This e.g. enables server-side granulation techniques for arbitrary sound sources that are difficult to handle with granulation ugens alone, such es effect sequencing per grain and others. A related application, not necessarily in a granular time-scale, is crossfaded playback from different buffer positions. Finally DX fanning ugens allow server-side definition of spatial movements by crossfading between non-adjacent channels respectively buses.

+


+

NOTE: As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the DX suite overview and go through the help file examples in this order: DXMix - DXMixIn - DXEnvFan - DXEnvFanOut - DXFan - DXFanOut. Some general conventions are treated in detail in the following examples: fades and steps in DXMix help, Ex.2 – width and offset arguments in DXMix help, Ex.3 – multichannel expansion in DXMix help, Ex.6 – crossfade types in DXEnvFan help, Ex.1.

+


+

NOTE: PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice.

+


+

NOTE: Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See Ex.8 from DXMix help and Ex.2, Ex.4 from DXEnvFan help for aspects of CPU demand.

+


+

NOTE: In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See DXFan help Ex.4 for aspects of granulation with high trigger rates / short grain durations.

+


+

NOTE: The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours.

+


+

CREDITS: Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz.

+


+


+

Ex.1) DXMix, crossfade sequencing

+


+

// Crossfaded switching between sources, there exists a number of options, 

+

// e.g. for fade mode (inclusion of steps = plateau phases), curve type and width.

+

// The syntax of passing the sources within a Ref object is necessary

+

// to distinguish from multichannel expansion.

+


+


+

(

+

{

+

DXMix.ar(

+

Dseq([1, 0, 1, 2], inf),

+

`([SinOsc.ar(100), WhiteNoise.ar(), LFTri.ar(100)]),

+

stepTime: 0.015,

+

fadeTime: 0.015,

+

fadeMode: 1, // alternate steps and fades

+

sine: 0, // sine type or not

+

equalPower: 0 // square-rooted (equal power) or not

+

)

+

}.plot(0.12)

+

)

+


+


+


+

Ex.2) Comparison of fanning DX ugens

+

+

// Crossfading source signals with DXFan,

+

// result is a multichannel signal of size that has to be passed

+


+

(

+

{

+

DXFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

SinOsc.ar(500),

+

size: 8,

+

fadeTime: 0.01

+

)

+

}.plot(0.1)

+

)

+


+

(

+

{

+

DXFan.ar(

+

Dseq([2, 0, 4, 6], inf),

+

`(SinOsc.ar([300, 700])),

+

size: 8,

+

fadeTime: 0.01

+

)

+

}.plot(0.1)

+

)

+


+


+

// DXEnvFan returns a multichannel envelope,

+

// the size has to be passed.

+

// Without any options it defaults to the square-rooted (equal power) sine type.

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

)

+

}.plot(0.1)

+

)

+


+

// proof of concept with other fanning DX ugens:

+


+

// for getting the same result with DXFan pass a DC as source

+

(

+

{

+

DXFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

DC.ar(1),

+

size: 8,

+

fadeTime: 0.01

+

)

+

}.plot(0.1)

+

)

+


+

// envelopes can also be sent to buses, for plotting we can get them back with In,

+

// here the size is considered via the bus.

+


+

(

+

a = Bus.audio(s, 8);

+

{

+

DXEnvFanOut.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf) + a.index,

+

fadeTime: 0.01

+

);

+

In.ar(a, 8)

+

}.plot(0.1)

+

)

+


+

// analogously with DXFanOut and DC input

+


+

(

+

b = Bus.audio(s, 8);

+

{

+

DXFanOut.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf) + b.index,

+

DC.ar(1),

+

fadeTime: 0.01

+

);

+

In.ar(b, 8)

+

}.plot(0.1)

+

)

+


+

(

+

a.free;

+

b.free;

+

)

+


+


+


+


+ + diff --git a/Help/DXEnvFan.html b/Help/DXEnvFan.html new file mode 100755 index 0000000..a79d5c7 --- /dev/null +++ b/Help/DXEnvFan.html @@ -0,0 +1,789 @@ + + + + + + + + + + + +

DXEnvFan returns crossfade envelopes according to demand-rate control

+


+

Part of: miSCellaneous

+


+

Inherits from: AbstractDX

+


+

DXEnvFan returns the multichannel envelope signal, which is used by DXMix / DXMixIn / DXFan / DXFanOut implicitely. It can be used as envelope and trigger at the same time, which leads to applications such as crossfading PlayBufs and different kinds of granulation, using a buffer or not. 

+


+

NOTE: As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the DX suite overview and go through the help file examples in this order: DXMix - DXMixIn - DXEnvFan - DXEnvFanOut - DXFan - DXFanOut. Some general conventions are treated in detail in the following examples: fades and steps in DXMix help, Ex.2 – width and offset arguments in DXMix help, Ex.3 – multichannel expansion in DXMix help, Ex.6 – crossfade types in DXEnvFan help, Ex.1.

+


+

NOTE: PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice.

+


+

NOTE: Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See Ex.8 from DXMix help and Ex.2, Ex.4 from DXEnvFan help for aspects of CPU demand.

+


+

NOTE: In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See DXFan help Ex.4 for aspects of granulation with high trigger rates / short grain durations.

+


+

NOTE: The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours.

+


+

CREDITS: Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz.

+


+


+

See also: DX suite, DXMix, DXMixIn, DXEnvFanOut, DXFan, DXFanOut, Buffer Granulation, Live Granulation, PbindFx, kitchen studies, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

Creation / Class Methods

+


+

*ar (out, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, size = 2, bus = nil, zeroThr = nil, doneAction = 0)

+

+

out - Determines the sequence of channels between which the signal should be crossfaded.

+

An channel index, a demand rate or other ugen returning channel indices or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of out

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

fadeTime - A fade time, a demand rate or other ugens returning fade times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of fadeTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of fadeTime depends on fadeMode.

+

fadeTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

stepTime - A step time, a demand rate or other ugens returning step times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of stepTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of stepTime depends on fadeMode.

+

stepTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

fadeMode - Integers between 0 and 4 or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

fadeMode = 0: only fadeTimes are used, no steps

+

fadeMode = 1: alternate steps and fades, begin with step; stepTime means time without fade

+

fadeMode = 2: alternate fades and steps, begin with fade; stepTime means time without fade

+

fadeMode = 3: alternate steps and fades, begin with step; stepTime means sum of step and fade,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

fadeMode = 4: alternate fades and steps, begin with fade; stepTime means sum of fade and step,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

Defaults to 0.

+

sine - Determines the crossfade type: sine-based or not.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning sine numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of sine

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

equalPower - Determines if crossfading of equal power type (square root) should be applied.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning equalPower numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of equalPower

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

power - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude. If power and curve are passed, power applies before.

+

A positive Number or a demand rate or other ugen returning positive power numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of power

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

curve - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude according to the lincurve mapping. 

+

If power and curve are passed, power applies before.

+

A Number or a demand rate or other ugen returning curve numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of curve

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Calculation of curvature is not giving reliable results when width and / or dynOutOffset are 

+

being modulated at the same time.

+

Defaults to 0. 

+

allowTypeSeq - Enables sequencing of sine, equalPower, power and curve with 

+

demand rate ugens and modulating of sine and equalPower with other ugens.

+

A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion.

+

Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0.

+

fadeRate - One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

Defaults to \ar.

+

maxFadeNum - Integer determining the maximum number of fades, after which doneAction applies. 

+

A SequenceableCollection causes multichannel expansion. Not modulatable.

+

Defaults to inf.

+

maxWidth - An Integer determining the maximum width or

+

a SequenceableCollection of such, causing multichannel expansion, width goes into PanAz's width arg.

+

maxWidth increases the internally used and potentially needed number of parallel channels. Not modulatable.

+

Determines the size of the returned multichannel signal.

+

Defaults to 2.

+

width - Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, 

+

causing multichannel expansion. Not modulatable in versions earlier than SC 3.9.

+

It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed maxWidth.

+

In case of  DXEnvFan's use as trigger you might want to set it really smaller than maxWidth.

+

Defaults to 2.

+

initOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines an initial offset for PanAz's pos arg.

+

This can be useful for a start with full or reduced width, see examples. 

+

Not modulatable. Defaults to 0.

+

maxDynOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the maximum dynOutOffset to be expected.

+

maxDynOutOffset increases the internally used and potentially needed number of parallel channels. 

+

Not modulatable. Defaults to 1.

+

dynOutOffset - UGen, Integer or Float or

+

a SequenceableCollection of such, causing multichannel expansion.

+

By passing a ugen the movement between channels can be modulated.

+

Note that a ugen's output must not exceed maxDynOutOffset.

+

Defaults to 0.

+

allowFadeEnd - Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion.

+

Determines if a demand rate input to out with finite length will be monitored, which needs a quite complicated 

+

trigger logic and more running ugens. If set to 0, the behaviour after the end of out is undefined.

+

Defaults to 1.

+

size - Integer or a SequenceableCollection of such, causing multichannel expansion. 

+

Determines the size of the returned multichannel envelope signal.

+

Not modulatable. Defaults to 2.

+

bus - Bus, bus index or a SequenceableCollection of such, causing multichannel expansion. 

+

Determines whether a private multichannel bus should be used for channel switching.

+

This is recommended for larger width sizes (> 10 or so) as otherwise the number of ugens 

+

might result in an overflow error.

+

Not modulatable. Defaults to nil.

+

zeroThr - A Number or a ugen returning zeroThr numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

Determines if output values below this threshold are replaced by 0.

+

This makes sense if the output signal is used as trigger (e.g. with DXEnvFan).

+

In the case of low power numbers small inaccuracies are amplified, this is avoided

+

with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power.

+

As this requires more ugens running in parallel it is disabled by default = nil.

+

doneAction - Integer or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the doneAction after maxFadeNum is exceeded.

+

Defaults to 0.

+


+

*kr (out, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, size = 2, bus = nil, zeroThr = nil, doneAction = 0)

+

+


+

+


+

Examples

+


+

(

+

// load with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.reboot;

+

)

+

+


+

Ex.1) Basic types of crossfades

+


+

// For normal crossfades the default values - equal power panning of sine type - should be fine,

+

// other types of crossfade envelopes can be taken for other purposes such as granular synthesis.

+


+


+

// default width 2, default equal power crossfading

+


+

// employs crossfading from PanAz, which results in envelopes of sine type, from which

+

// the square root is taken

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// when dropping equalPower we get the pure sine envelope

+

// higher width

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

maxWidth: 8,

+

width: 5.5,

+

equalPower: 0,

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// type sine = 0 switches to a linear curve base,

+

// with default equalPower = 1 this leads to a square root envelope

+


+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

sine: 0,

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// a linear curve base without equalPower means a linear crossfade

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

sine: 0,

+

equalPower: 0,

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// if equalPower is set to 0 you can choose a power

+

// check alternative values for sine and power also

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

equalPower: 0,

+

sine: 0,  // 1

+

power: 3, // 0.3

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// power can be a ugen too

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

equalPower: 0,

+

sine: 1,  // 1

+

power: SinOsc.ar(10, -pi/2).range(0.5, 10),

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// if equalPower is set to 0 you can also choose a curve arg

+

// which is passed as curvature to lincurve

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

equalPower: 0,

+

curve: 3,  // -3

+

sine: 0,

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// curve can be a ugen too

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

curve: Line.ar(5, -5, 0.1),  // -3

+

sine: 0,

+

equalPower: 0,

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// Passing a dynOutOffset arg - wobbled sliding

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

dynOutOffset: SinOsc.ar(150).range(0, 1),

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// This is not recommended:

+

// With dynOutOffset or modulated width a curve arg leads to irregularities

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

dynOutOffset: SinOsc.ar(150).range(0, 1),

+

equalPower: 0,

+

curve: 1,

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// out can also be a ugen, but note that with equalPower (default)

+

// blending the same channel leads to amplitude pikes,

+

// to avoid that take a linear crossfade

+


+

(

+

{

+

DXEnvFan.ar(

+

LFDNoise3.ar(15).range(0, 7),

+

size: 8,

+

fadeTime: 0.01,

+

stepTime: 0.02,

+

fadeMode: 1,

+

sine: 0, 

+

equalPower: 1  // compare 0

+

)

+

}.plot(1)

+

)

+


+


+

Ex.2) Sequencing crossfade types and parameters

+


+

// sequencing crossfade types and parameters must be allowed explicitely as 

+

// it is more CPU-costy

+


+

// alternate linear and pure sine

+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

allowTypeSeq: 1,

+

sine: Dseq([0, 1], inf),

+

equalPower: 0, // Dseq([0, 1], inf)

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+

// curvature sequencing

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

allowTypeSeq: 1,

+

sine: 0,

+

equalPower: 0, 

+

curve: Dseq([-3, -3, 3, 3], inf),

+

// fadeMode: 1,

+

// stepTime: 0.015

+

)

+

}.plot(0.1)

+

)

+


+


+Ex.3) The 'zeroThr' arg

+


+

// There might occur small inaccuracies in the calculation of crossfade values.

+

// This is irrelevant in most cases, but if you use the output signal as trigger

+

// it is definitely unwanted.

+

// You can supress such if you set an appropriate zero threshold.

+


+


+

// The effect of unwanted envelope segments especially becomes apparent

+

// with small power values that blow up values near 0.

+

// Values below the zeroThr are set to 0 before applying power or curvature.

+


+

// Compare version without passing zeroThr.

+


+

(

+

{

+

DXEnvFan.ar(

+

Dseq([3,2,1,4,5,6,7,0], inf),

+

size: 8,

+

fadeTime: 0.01,

+

equalPower: 0,

+

sine: 0,

+

power: 0.3,

+

fadeMode: 1,

+

stepTime: 0.015,

+

zeroThr: 0.002

+

)

+

}.plot(0.3)

+

)

+


+


+


+

Ex.4) The 'bus' arg

+


+

// As channel switching with DXEnvFan / DXFan is implemented by "watcher ugens",

+

// it becomes costy if the number of output channels increases (growth of quadratic order).

+

// The effort is lowered significantly if you pass a reserved bus for this or use DXEnvFanOut,

+

// this also concerns DXFan / DXFanOut.

+


+

(

+

// load with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numPrivateAudioBusChannels = 256;

+

s.options.memSize = 8192 * 4;

+

s.options.numWireBufs = 512;

+

s.reboot;

+

)

+


+


+

// on my machine this example needs ca. 2.5 % CPU (818 ugens) ...

+


+

(

+

x = {

+

    DXEnvFan.ar(

+

        Dshuf((0..29), inf),

+

        size: 30,

+

        fadeTime: 0.005

+

    ) * 0.25

+

}.play

+

)

+


+

x.release

+


+

// ... whereas this needs ca. 0.6 % CPU (167 ugens)

+


+

(

+

a = Bus.audio(s, 30);

+


+

x = {

+

    DXEnvFan.ar(

+

        Dshuf((0..29), inf),

+

        size: 30,

+

        fadeTime: 0.005,

+

        bus: a

+

    ) * 0.25

+

}.play

+

)

+


+

x.release

+


+

(

+

a.free;

+

b.free;

+

)

+


+


+


+

// care has to be taken with buses and multichannel expansion:

+


+

// here two buses have to passed as otherwise we get a wrong result,

+

// the same bus would be taken for different calculations at the same time

+


+

(

+

a = Bus.audio(s, 8);

+

b = Bus.audio(s, 8);

+


+

{

+

Mix(DXEnvFan.ar(

+

[Dseq((0..7), inf), Dseq((7..0), inf)],

+

size: 8,

+

fadeTime: 0.01,

+

equalPower: 0,

+

bus: [a, b]

+

))

+

}.plot(0.1)

+

)

+


+

(

+

a.free;

+

b.free;

+

)

+


+


+


+

Ex.5) PlayBuf crossfading

+


+

// Here a two channel envelope signal is used to crossfade between

+

// two channels of a PlayBuf, as rates are close we get a trill effect.

+


+

// Note that the envelope signal is used three times:

+


+

// as a trigger to the startPos within PlayBuf

+

// as a trigger for the demand ugen that determines the startPos within PlayBuf

+

// as an envelope for the PlayBuf

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+

(

+

x = {

+

var sig, env = DXEnvFan.ar(

+

Dseq([0, 1], inf),

+

fadeMode: 3,

+

stepTime: 0.05,

+

fadeTime: 0.01,

+

// to ensure right triggering set zeroThr

+

zeroThr: 0.002

+

);

+


+

sig = PlayBuf.ar(

+

1,

+

b,

+

[1, 1.1] * BufRateScale.kr(b),

+

env,

+

Demand.ar(

+

env,

+

0,

+

Dstutter(

+

5,

+

Dwhite(0.1, 0.9)

+

) * BufFrames.ir(b)

+

)

+

) * 1.2 * env;

+

// do a bit correlation

+

Splay.ar(sig, 0.8)

+

}.play

+

)

+


+

x.release

+


+


+

// L and R get different mixes,

+

// both take the same envelope trigger which switches

+

// between PlayBufs of different rates, the difference between L and R

+

// comes from different random start positions.

+


+

(

+

x = {

+

var buf, env = DXEnvFan.ar(

+

Dseq([Dshuf((0..4)), Dshuf((5..7))], inf),

+

size: 8,

+

fadeMode: 3,

+

stepTime: 0.2,

+

fadeTime: 0.02,

+

zeroThr: 0.002

+

);

+


+

{ 

+

Mix(PlayBuf.ar(

+

1,

+

b,

+

{ |j| j / 4 + 0.3 } ! 8 * BufRateScale.kr(b),

+

env,

+

Demand.ar(env, 0, Drand((2..5)/10, inf) * BufFrames.ir(b)),

+

loop: 0,

+

) * env) 

+

} ! 2 ;

+

}.play

+

)

+


+


+

x.release

+


+

// variant with multichannel expansion

+

// two 8 ch trigger envelopes are used for L and R mixes

+

(

+

x = {

+

var buf, env = DXEnvFan.ar(

+

[Dseq((0..7), inf), Diwhite(0, 7)],

+

size: 8,

+

fadeMode: 3,

+

stepTime: 0.2,

+

fadeTime: 0.02,

+

zeroThr: 0.002

+

);

+


+

{ |i| Mix(PlayBuf.ar(

+

1,

+

b,

+

{ |j| j / 4 + 0.3 } ! 8 * BufRateScale.kr(b),

+

env[i],

+

// same positions go parallel

+

Demand.ar(env[i], 0, Dstutter(8, Dseq((2..5)/10, inf)) * BufFrames.ir(b)),

+

loop: 0,

+

) * env[i]) } ! 2 ;

+

}.play

+

)

+


+

x.release

+


+


+


+

Ex.6) Granulation, sequencing of fxs and fx parameters per grain

+


+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+

// sequencing of band pass frequency

+

// see also Buffer Granulation tutorial, Ex. 1f

+


+

(

+

a = Bus.audio(s, 8);

+


+

x = {

+

    var sig, env = DXEnvFan.ar(

+

        Dseq((0..7), inf),

+

        size: 8,

+

maxWidth: 8,

+

width: 5,

+

        fadeTime: 0.01,

+

        // to ensure right triggering set zeroThr

+

        zeroThr: 0.002,

+

        bus: a

+

    );

+


+

    sig = PlayBuf.ar(

+

        1,

+

        b,

+

        BufRateScale.kr(b),

+

        env,

+

        Demand.ar(env, 0, Dbrown(0.2, 0.5, 0.0025) * BufFrames.ir(b)),

+

        loop: 0,

+

    ) * env;

+

    

+

    // multichannel trigger polls from single sequencing source

+

// ensure non-zero filter freq for later triggers with max

+

sig = BPF.ar(sig, Demand.ar(env, 0, Dbrown(200, 2000, 100)).max(200), 0.1);

+


+

    // do a bit correlation

+

    Splay.ar(sig, 0.6) * 7

+

}.play

+

)

+


+

x.release

+


+

a.free

+


+


+

// sequencing of different fxs

+


+

(

+

a = Bus.audio(s, 8);

+


+

x = {

+

    var sig, env = DXEnvFan.ar(

+

        Drand((0..7), inf),

+

        size: 8,        

+

        maxWidth: 8,

+

        // oscillation of overlap

+

        width: LFDNoise3.ar(1).range(2, 6),

+

        fadeTime: 0.015,

+

        // to ensure right triggering set zeroThr

+

        zeroThr: 0.002,

+

        bus: a

+

    );

+


+

    sig = PlayBuf.ar(

+

        1,

+

        b,

+

        BufRateScale.kr(b),

+

        env,

+

        Demand.ar(env, 0, Dbrown(0.2, 0.5, 0.005) * BufFrames.ir(b)),

+

        loop: 0,

+

    ) * env;

+


+

    // selection of fxs as well as fx params triggered with envelope

+

    // ring modulation, bit crusher and band pass

+

    sig = Select.ar(Demand.ar(env, 0, Dstutter(Diwhite(5, 30), Dxrand([0, 1, 2], inf))), [

+

        sig * SinOsc.ar(Demand.ar(env, 0, Dbrown(250, 1000, 200))) * 1.5,

+

        sig.round(2 ** Demand.ar(env, 0, Dbrown(-1, -4, 1))).lag(0.005) * 1.5,

+

// ensure non-zero filter freq for later triggers with max

+

BPF.ar(sig, Demand.ar(env, 0, Dbrown(200, 2000, 200)).max(200), 0.2) * 5

+

    ]);

+


+

    // do a bit correlation

+

    Splay.ar(sig, 0.6)

+

}.play

+

)

+


+

x.release

+


+

a.free

+

 

+


+

Ex.7) Multichannel expansion

+


+


+

// Not as complicated options as with DXMix

+

// we get an array of multichannel envelopes, here a mix of them

+


+

(

+

{

+

Mix(DXEnvFan.ar(

+

(0..3).collect { |i| Dseq((0..3).rotate(i), inf) },

+

fadeTime: [0.01, 0.05],

+

size: 4,

+

width: 1

+

))

+

}.plot(0.2)

+

)

+


+

// see Ex.4 for a delicate case with bus args

+


+


+


+


+ + diff --git a/Help/DXEnvFanOut.html b/Help/DXEnvFanOut.html new file mode 100755 index 0000000..b0002e4 --- /dev/null +++ b/Help/DXEnvFanOut.html @@ -0,0 +1,283 @@ + + + + + + + + + + + +

DXEnvFanOut sends crossfade envelopes to out buses according to demand-rate control

+


+

Part of: miSCellaneous

+


+

Inherits from: AbstractDX

+


+

DXEnvFanOut sends multichannel envelopes, which are used by DXMix / DXMixIn / DXFan / DXFanOut implicitely, to a sequence of out buses, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. It can be used as envelope and trigger at the same time, which leads to applications such as crossfading PlayBufs and different kinds of granulation, using a buffer or not. 

+


+

NOTE: As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the DX suite overview and go through the help file examples in this order: DXMix - DXMixIn - DXEnvFan - DXEnvFanOut - DXFan - DXFanOut. Some general conventions are treated in detail in the following examples: fades and steps in DXMix help, Ex.2 – width and offset arguments in DXMix help, Ex.3 – multichannel expansion in DXMix help, Ex.6 – crossfade types in DXEnvFan help, Ex.1.

+


+

NOTE: PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice.

+


+

NOTE: Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See Ex.8 from DXMix help and Ex.2, Ex.4 from DXEnvFan help for aspects of CPU demand.

+


+

NOTE: In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See DXFan help Ex.4 for aspects of granulation with high trigger rates / short grain durations.

+


+

NOTE: The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours.

+


+

CREDITS: Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz.

+


+


+

See also: DX suite, DXMix, DXMixIn, DXEnvFan, DXFan, DXFanOut, Buffer Granulation, Live Granulation, PbindFx, kitchen studies, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

Creation / Class Methods

+


+

*ar (out, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0)

+

+

out - Determines the sequence of buses between which the envelope should be crossfaded.

+

A bus index, a demand rate or other ugen returning bus indices or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of out

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

fadeTime - A fade time, a demand rate or other ugens returning fade times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of fadeTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of fadeTime depends on fadeMode.

+

fadeTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

stepTime - A step time, a demand rate or other ugens returning step times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of stepTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of stepTime depends on fadeMode.

+

stepTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

fadeMode - Integers between 0 and 4 or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

fadeMode = 0: only fadeTimes are used, no steps

+

fadeMode = 1: alternate steps and fades, begin with step; stepTime means time without fade

+

fadeMode = 2: alternate fades and steps, begin with fade; stepTime means time without fade

+

fadeMode = 3: alternate steps and fades, begin with step; stepTime means sum of step and fade,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

fadeMode = 4: alternate fades and steps, begin with fade; stepTime means sum of fade and step,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

Defaults to 0.

+

sine - Determines the crossfade type: sine-based or not.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning sine numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of sine

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

equalPower - Determines if crossfading of equal power type (square root) should be applied.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning equalPower numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of equalPower

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

power - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude. If power and curve are passed, power applies before.

+

A positive Number or a demand rate or other ugen returning positive power numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of power

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

curve - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude according to the lincurve mapping. 

+

If power and curve are passed, power applies before.

+

A Number or a demand rate or other ugen returning curve numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of curve

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Calculation of curvature is not giving reliable results when width and / or dynOutOffset are 

+

being modulated at the same time.

+

Defaults to 0. 

+

allowTypeSeq - Enables sequencing of sine, equalPower, power and curve with 

+

demand rate ugens and modulating of sine and equalPower with other ugens.

+

A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion.

+

Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0.

+

fadeRate - One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

Defaults to \ar.

+

maxFadeNum - Integer determining the maximum number of fades, after which doneAction applies. 

+

A SequenceableCollection causes multichannel expansion. Not modulatable.

+

Defaults to inf.

+

maxWidth - An Integer determining the maximum width or

+

a SequenceableCollection of such, causing multichannel expansion, width goes into PanAz's width arg.

+

maxWidth increases the internally used and potentially needed number of parallel channels. Not modulatable.

+

Determines the size of the returned multichannel signal.

+

Defaults to 2.

+

width - Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, 

+

causing multichannel expansion. Not modulatable in versions earlier than SC 3.9.

+

It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed maxWidth.

+

In case of  DXEnvFan's use as trigger you might want to set it really smaller than maxWidth.

+

Defaults to 2.

+

initOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines an initial offset for PanAz's pos arg.

+

This can be useful for a start with full or reduced width, see examples. 

+

Not modulatable. Defaults to 0.

+

maxDynOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the maximum dynOutOffset to be expected.

+

maxDynOutOffset increases the internally used and potentially needed number of parallel channels. 

+

Not modulatable. Defaults to 1.

+

dynOutOffset - UGen, Integer or Float or

+

a SequenceableCollection of such, causing multichannel expansion.

+

By passing a ugen the movement between buses can be modulated.

+

Note that a ugen's output must not exceed maxDynOutOffset.

+

Defaults to 0.

+

allowFadeEnd - Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion.

+

Determines if a demand rate input to out with finite length will be monitored, which needs a quite complicated 

+

trigger logic and more running ugens. If set to 0, the behaviour after the end of out is undefined.

+

Defaults to 1.

+

zeroThr - A Number or a ugen returning zeroThr numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

Determines if output values below this threshold are replaced by 0.

+

This makes sense if the output signal is used as trigger (e.g. with DXEnvFan).

+

In the case of low power numbers small inaccuracies are amplified, this is avoided

+

with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power.

+

As this requires more ugens running in parallel it is disabled by default = nil.

+

doneAction - Integer or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the doneAction after maxFadeNum is exceeded.

+

Defaults to 0.

+


+

*kr (out, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0)

+

+


+

+


+

Examples

+


+

// NOTE: As with DXFan / DXFanOut examples from DXEnvFan help also can be written with DXEnvFanOut,

+

// but the latter doesn't need size and bus args as out buses are addressed directly.

+

// There is a small difference also with multichannel expansion, see last example below.

+


+

(

+

// load with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.reboot;

+

)

+


+


+

Ex.1) Basic usage

+


+

// play SinOscs silently and wait for granulation envelopes

+


+

(

+

a = Bus.audio(s, 10);

+

x = { Splay.ar(In.ar(a, 10) * SinOsc.ar((3..12) * 100, 0, 0.05)) }.play

+

)

+


+


+

// send envelopes to buses

+


+

(

+

z = {

+

DXEnvFanOut.ar(

+

Dseq((0..5), inf) + Dwhite(1, 4) + a.index,

+

fadeTime: 0.05,

+

width: 1,

+

initOutOffset: -0.5

+

)

+

}.play

+

)

+


+

x.release;

+


+

// cleanup

+


+

(

+

z.free;

+

a.free;

+

)

+


+


+

Ex.2) Multichannel expansion

+


+


+

// play SinOscs silently and wait for granulation envelopes

+


+

(

+

a = Bus.audio(s, 10);

+

x = { Splay.ar(In.ar(a, 10) * SinOsc.ar((3..12) * 100, 0, 0.05)) }.play

+

)

+


+

// send envelope to buses in parallel

+


+

(

+

z = {

+

DXEnvFanOut.ar(

+

[0, 3] + Dseq((0..3), inf) + Dwhite(0, 3) + a.index,

+

fadeTime: 0.05,

+

width: 1,

+

initOutOffset: -0.5

+

)

+

}.play

+

)

+


+

x.release;

+


+

// cleanup

+


+

(

+

z.free;

+

a.free;

+

)

+


+


+

// Ex.7 from DXEnvFan help looks a bit different,

+

// as DXEnvFanOut is an out ugen we don't need to mix

+


+

(

+

a = Bus.audio(s, 4);

+

{

+

DXEnvFanOut.ar(

+

(0..3).collect { |i| Dseq((0..3).rotate(i), inf) } + a.index,

+

fadeTime: [0.01, 0.05],

+

width: 1

+

);

+

In.ar(a, 4)

+

}.plot(0.2)

+

)

+


+

a.free

+


+


+ + diff --git a/Help/DXFan.html b/Help/DXFan.html new file mode 100755 index 0000000..e88caa6 --- /dev/null +++ b/Help/DXFan.html @@ -0,0 +1,393 @@ + + + + + + + + + + + +

DXFan crossfades signals within a multichannel array according to demand-rate control

+


+

Part of: miSCellaneous

+


+

Inherits from: AbstractDX

+


+

DXFan crossfades signals to a sequence of channels, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. 

+


+

NOTE: As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the DX suite overview and go through the help file examples in this order: DXMix - DXMixIn - DXEnvFan - DXEnvFanOut - DXFan - DXFanOut. Some general conventions are treated in detail in the following examples: fades and steps in DXMix help, Ex.2 – width and offset arguments in DXMix help, Ex.3 – multichannel expansion in DXMix help, Ex.6 – crossfade types in DXEnvFan help, Ex.1.

+


+

NOTE: PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice.

+


+

NOTE: Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See Ex.8 from DXMix help and Ex.2, Ex.4 from DXEnvFan help for aspects of CPU demand.

+


+

NOTE: In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See DXFan help Ex.4 for aspects of granulation with high trigger rates / short grain durations.

+


+

NOTE: The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours.

+


+

CREDITS: Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz.

+


+


+

See also: DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFanOut, Buffer Granulation, Live Granulation, PbindFx, kitchen studies, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

Creation / Class Methods

+


+

*ar (out, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, size = 2, bus = nil, zeroThr = nil, doneAction = 0)

+

+

out - Determines the sequence of channels between which the signal should be crossfaded.

+

A channel index, a demand rate or other ugen returning channel indices or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of out

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

channelsArrayRef - The signal to be crossfaded. 

+

A single channel can be passed as such, an array must be wrapped into a 

+

Ref object to avoid multichannel expansion.

+

In this case the multichannel signal is crossfaded from one block of adjacent channels to the next,

+

whereby the lowest channel index follows the base sequence defined by out.

+

A SequenceableCollection causes multichannel expansion, whereby single items of the collection

+

can itself be Ref objects containing multichannel signals.

+

fadeTime - A fade time, a demand rate or other ugens returning fade times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of fadeTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of fadeTime depends on fadeMode.

+

fadeTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

stepTime - A step time, a demand rate or other ugens returning step times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of stepTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of stepTime depends on fadeMode.

+

stepTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

fadeMode - Integers between 0 and 4 or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

fadeMode = 0: only fadeTimes are used, no steps

+

fadeMode = 1: alternate steps and fades, begin with step; stepTime means time without fade

+

fadeMode = 2: alternate fades and steps, begin with fade; stepTime means time without fade

+

fadeMode = 3: alternate steps and fades, begin with step; stepTime means sum of step and fade,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

fadeMode = 4: alternate fades and steps, begin with fade; stepTime means sum of fade and step,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

Defaults to 0.

+

sine - Determines the crossfade type: sine-based or not.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning sine numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of sine

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

equalPower - Determines if crossfading of equal power type (square root) should be applied.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning equalPower numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of equalPower

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

power - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude. If power and curve are passed, power applies before.

+

A positive Number or a demand rate or other ugen returning positive power numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of power

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

curve - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude according to the lincurve mapping. 

+

If power and curve are passed, power applies before.

+

A Number or a demand rate or other ugen returning curve numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of curve

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Calculation of curvature is not giving reliable results when width and / or dynOutOffset are 

+

being modulated at the same time.

+

Defaults to 0. 

+

allowTypeSeq - Enables sequencing of sine, equalPower, power and curve with 

+

demand rate ugens and modulating of sine and equalPower with other ugens.

+

A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion.

+

Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0.

+

fadeRate - One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

Defaults to \ar.

+

maxFadeNum - Integer determining the maximum number of fades, after which doneAction applies. 

+

A SequenceableCollection causes multichannel expansion. Not modulatable.

+

Defaults to inf.

+

maxWidth - An Integer determining the maximum width or

+

a SequenceableCollection of such, causing multichannel expansion, width goes into PanAz's width arg.

+

maxWidth increases the internally used and potentially needed number of parallel channels. Not modulatable.

+

Defaults to 2.

+

width - Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, 

+

causing multichannel expansion. Not modulatable in versions earlier than SC 3.9.

+

It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed maxWidth.

+

Defaults to 2.

+

initOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines an initial offset for PanAz's pos arg.

+

This can be useful for a start with full or reduced width, see examples. 

+

Not modulatable. Defaults to 0.

+

maxDynOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the maximum dynOutOffset to be expected.

+

maxDynOutOffset increases the internally used and potentially needed number of parallel channels. 

+

Not modulatable. Defaults to 1.

+

dynOutOffset - UGen, Integer or Float or

+

a SequenceableCollection of such, causing multichannel expansion.

+

By passing a ugen the movement between channels can be modulated.

+

Note that a ugen's output must not exceed maxDynOutOffset.

+

Defaults to 0.

+

allowFadeEnd - Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion.

+

Determines if a demand rate input to out with finite length will be monitored, which needs a quite complicated 

+

trigger logic and more running ugens. If set to 0, the behaviour after the end of out is undefined.

+

Defaults to 1.

+

size - Integer or a SequenceableCollection of such, causing multichannel expansion. 

+

Determines the size of the returned multichannel signal.

+

Not modulatable. Defaults to 2.

+

bus - Bus, bus index or a SequenceableCollection of such, causing multichannel expansion. 

+

Determines whether a private multichannel bus should be used for channel switching.

+

This is recommended for larger width sizes (> 10 or so) as otherwise the number of ugens 

+

might result in an overflow error.

+

Not modulatable. Defaults to nil.

+

zeroThr - A Number or a ugen returning zeroThr numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

Determines if output values below this threshold are replaced by 0.

+

This makes sense if the output signal is used as trigger (e.g. with DXEnvFan).

+

In the case of low power numbers small inaccuracies are amplified, this is avoided

+

with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power.

+

As this requires more ugens running in parallel it is disabled by default = nil.

+

doneAction - Integer or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the doneAction after maxFadeNum is exceeded.

+

Defaults to 0.

+


+

*kr (out, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, size = 2, bus = nil, zeroThr = nil, doneAction = 0)

+

+


+

+


+

Examples

+


+

(

+

// load with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.reboot;

+

)

+


+

// Note that, as with DXEnvFan, higher values passed to 'size' and 'maxWidth'

+

// cause a significant growth of the number of used ugens.

+

// In this case consider passing a bus arg or the use of DXFanOut,

+

// see Ex.4 from DXEnvFan help.

+


+


+

Ex.1) Basic usage: simple crossfade

+

+

// crossfading a mono source between two outs

+


+

(

+

x = {

+

DXFan.ar(

+

Dseq([0, 1], inf),

+

PinkNoise.ar(0.05),

+

fadeTime: 1

+

)

+

}.play

+

)

+


+

x.release

+


+


+

// Crossfading a stereo source between two outs,

+

// for more than two channels size has to be passed.

+


+

(

+

{

+

DXFan.ar(

+

Dseq([0, 2], inf),

+

`(Saw.ar(50 * [1, 5], 0.03)),

+

size: 4,

+

fadeTime: 0.1

+

)

+

}.plot(0.2)

+

)

+


+


+

// crossfading a mono source between several outs

+


+

(

+

{

+

DXFan.ar(

+

Dseq([0, 3, 1, 2], inf),

+

BPF.ar(PinkNoise.ar(), LFDNoise3.ar(1).range(100, 2000), 0.1, 0.7),

+

size: 4,

+

fadeTime: 0.05

+

)

+

}.plot(0.2)

+

)

+


+


+

// sliding over several channels, increased width

+


+

(

+

{

+

DXFan.ar(

+

Dseq([0, 3, 1, 2], inf),

+

BPF.ar(PinkNoise.ar(), LFDNoise3.ar(1).range(100, 2000), 0.1, 0.7),

+

size: 4,

+

fadeTime: 0.02,

+

maxWidth: 4,

+

width: 3

+

)

+

}.plot(0.2)

+

)

+


+


+

Ex.2) Multichannel expansion

+


+

// crossfading mono sources to alternate channels

+

// note that the result of DXFan, due to multichannel expansion, 

+

// is an array of two stereo arrays which is then mixed to one stereo array.

+


+

(

+

x = {

+

var lfo = { LFDNoise3.kr(0.2) };

+

Mix(DXFan.ar(

+

[Dseq([0, 1], inf), Dseq([1, 0], inf)],

+

[

+

LFTri.ar(150 * lfo.().range(1, 2), 0, 0.05),

+

BPF.ar(PinkNoise.ar(), lfo.().range(1000, 3000), 0.1, 0.7)

+

],

+

fadeTime: 0.07,

+

width: 1

+

))

+

}.play

+

)

+


+

x.release

+


+


+

// crossfading stereo sources to channel pairs (4ch), polyrhythm of fadeTimes.

+

// Similar to the above example the result of DXFan, due to multichannel expansion, 

+

// is an array of two 4-channel arrays which is then mixed to one 4-channel array.

+


+

(

+

x = {

+

var lfo = { LFDNoise3.kr(0.2) };

+

Mix(DXFan.ar(

+

[Dseq([0, 2], inf), Dseq([2, 0], inf)],

+

[

+

`({ |i| LFTri.ar(150 * lfo.().range(0.5, 2), 0, 0.05) } ! 2) ,

+

`({ |i| BPF.ar(PinkNoise.ar(), lfo.().range(500, 1500) * (i + 1), 0.1, 0.7) } ! 2)

+

],

+

size: 4,

+

fadeTime: [Dseq([0.06, 0.06, 0.03], inf), Dseq([0.05, 0.05, 0.17], inf)],

+

width: 1

+

))

+

}.play;

+

)

+


+

x.release

+


+


+

Ex.3) Granulation by crossfading to different channels

+


+

// mono source crossfaded to 8 channels

+


+

(

+

{

+

var lfo = LFDNoise3.kr(0.2);

+

DXFan.ar(

+

Dshuf((0..7), inf),

+

SinOsc.ar(lfo.range(800, 1500), 0, 0.03),

+

size: 8,

+

fadeTime: 0.01

+

)

+

}.plot(0.2)

+

)

+


+


+

// crossfading stereo pairs to every second out

+


+

(

+

{

+

var lfo = LFDNoise3.kr(0.2);

+

DXFan.ar(

+

Dseq((0, 2..6), inf),

+

`(SinOsc.ar(lfo.range(800, 1500) * [1, 1.02], 0, 0.03)),

+

size: 8,

+

fadeTime: 0.01

+

) ;

+

}.plot(0.2)

+

)

+


+


+

Ex.4) Granulation with high trigger rates and / or short grain durations

+


+


+

// fadeTimes should be above blockSize

+

// for shorter grains you can use smaller blocksizes

+


+

(

+

s.options.blockSize = 2;

+

s.reboot;

+

)

+


+

// as the effect is AM on single outs we get side bands

+


+

(

+

s.doWhenBooted {

+

x = {

+

var lfo = LFDNoise3.ar(0.2);

+

DXFan.ar(

+

Dseq((0..7), inf),

+

SinOsc.ar(lfo.range(800, 1500), 0, 0.03),

+

size: 8,

+

fadeTime: 0.0002

+

)

+

}.play

+

};

+

)

+


+

x.release

+


+


+

// go back to default

+


+

(

+

s.options.blockSize = 64;

+

s.reboot;

+

)

+


+


+ + diff --git a/Help/DXFanOut.html b/Help/DXFanOut.html new file mode 100755 index 0000000..4b4878b --- /dev/null +++ b/Help/DXFanOut.html @@ -0,0 +1,346 @@ + + + + + + + + + + + +

DXFanOut crossfades signals between out buses according to demand-rate control

+


+

Part of: miSCellaneous

+


+

Inherits from: AbstractDX

+


+

DXFanOut crossfades signals to a sequence of out buses, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. 

+


+

NOTE: As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the DX suite overview and go through the help file examples in this order: DXMix - DXMixIn - DXEnvFan - DXEnvFanOut - DXFan - DXFanOut. Some general conventions are treated in detail in the following examples: fades and steps in DXMix help, Ex.2 – width and offset arguments in DXMix help, Ex.3 – multichannel expansion in DXMix help, Ex.6 – crossfade types in DXEnvFan help, Ex.1.

+


+

NOTE: PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice.

+


+

NOTE: Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See Ex.8 from DXMix help and Ex.2, Ex.4 from DXEnvFan help for aspects of CPU demand.

+


+

NOTE: In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See DXFan help Ex.4 for aspects of granulation with high trigger rates / short grain durations.

+


+

NOTE: The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours.

+


+

CREDITS: Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz.

+


+


+

See also: DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFan, Buffer Granulation, Live Granulation, PbindFx, kitchen studies, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

Creation / Class Methods

+


+

*ar (out, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0)

+

+

out - Determines the sequence of buses between which the signal should be crossfaded.

+

An out bus, a demand rate or other ugen returning out buses or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of out

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

channelsArrayRef - The signals to be crossfaded. 

+

A single channel doesn't can be passed as such, an array must be wrapped into a 

+

Ref object to avoid multichannel expansion.

+

In this case the multichannel signal is crossfaded from one block of adjacent buses to the next,

+

whereby the lowest bus index follows the base sequence defined by out.

+

A SequenceableCollection causes multichannel expansion, whereby single items of the collection

+

can itself be Ref objects containing multichannel signals.

+

fadeTime - A fade time, a demand rate or other ugens returning fade times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of fadeTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of fadeTime depends on fadeMode.

+

fadeTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

stepTime - A step time, a demand rate or other ugens returning step times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of stepTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of stepTime depends on fadeMode.

+

stepTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

fadeMode - Integers between 0 and 4 or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

fadeMode = 0: only fadeTimes are used, no steps

+

fadeMode = 1: alternate steps and fades, begin with step; stepTime means time without fade

+

fadeMode = 2: alternate fades and steps, begin with fade; stepTime means time without fade

+

fadeMode = 3: alternate steps and fades, begin with step; stepTime means sum of step and fade,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

fadeMode = 4: alternate fades and steps, begin with fade; stepTime means sum of fade and step,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

Defaults to 0.

+

sine - Determines the crossfade type: sine-based or not.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning sine numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of sine

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

equalPower - Determines if crossfading of equal power type (square root) should be applied.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning equalPower numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of equalPower

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

power - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude. If power and curve are passed, power applies before.

+

A positive Number or a demand rate or other ugen returning positive power numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of power

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

curve - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude according to the lincurve mapping. 

+

If power and curve are passed, power applies before.

+

A Number or a demand rate or other ugen returning curve numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of curve

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Calculation of curvature is not giving reliable results when width and / or dynOutOffset are 

+

being modulated at the same time.

+

Defaults to 0. 

+

allowTypeSeq - Enables sequencing of sine, equalPower, power and curve with 

+

demand rate ugens and modulating of sine and equalPower with other ugens.

+

A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion.

+

Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0.

+

fadeRate - One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

Defaults to \ar.

+

maxFadeNum - Integer determining the maximum number of fades, after which doneAction applies. 

+

A SequenceableCollection causes multichannel expansion. Not modulatable.

+

Defaults to inf.

+

maxWidth - An Integer determining the maximum width or

+

a SequenceableCollection of such, causing multichannel expansion, width goes into PanAz's width arg.

+

maxWidth increases the internally used and potentially needed number of parallel channels. Not modulatable.

+

Defaults to 2.

+

width - Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, 

+

causing multichannel expansion. Not modulatable in versions earlier than SC 3.9.

+

It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed maxWidth.

+

Defaults to 2.

+

initOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determins an initial offset for PanAz's pos arg.

+

This can be useful for a start with full or reduced width, see examples. 

+

Not modulatable. Defaults to 0.

+

maxDynOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determins the maximum dynOutOffset to be expected.

+

maxDynOutOffset increases the internally used and potentially needed number of parallel channels. 

+

Not modulatable. Defaults to 1.

+

dynOutOffset - UGen, Integer or Float or

+

a SequenceableCollection of such, causing multichannel expansion.

+

By passing a ugen the movement between buses can be modulated.

+

Note that a ugen's output must not exceed maxDynOutOffset.

+

Defaults to 0.

+

allowFadeEnd - Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion.

+

Determines if a demand rate input to out with finite length will be monitored, which needs a quite complicated 

+

trigger logic and more running ugens. If set to 0, the behaviour after the end of out is undefined.

+

Defaults to 1.

+

zeroThr - A Number or a ugen returning zeroThr numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

Determines if output values below this threshold are replaced by 0.

+

This makes sense if the output signal is used as trigger (e.g. with DXEnvFan).

+

In the case of low power numbers small inaccuracies are amplified, this is avoided

+

with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power.

+

As this requires more ugens running in parallel it is disabled by default = nil.

+

doneAction - Integer or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the doneAction after maxFadeNum is exceeded.

+

Defaults to 0.

+


+

*kr (out, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0)

+

+


+

+


+

Examples

+


+

(

+

// load with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.reboot;

+

)

+


+


+

// NOTE: Examples from DXFan help also can be written with DXFanOut,

+

// DXFanOut doesn't need a size arg as the out bus is addressed directly.

+

// There is a small difference though with multichannel expansion, see example below.

+


+


+

Ex.1) Multichannel expansion

+


+

// Compare with Ex. 2 from DXFan help.

+

// As DXFanOut, other than DXFan, doesn't return an array, a mix is not necessary, 

+

// by internal use of Out ugens the signal is mixed to the referred buses anyway.

+


+

// For the same reason a normal release doesn't work, to get this option we can add an EnvGate.

+


+

(

+

x = {

+

var lfo = { LFDNoise3.kr(0.2) };

+

DXFanOut.ar(

+

[Dseq([0, 1], inf), Dseq([1, 0], inf)],

+

[

+

LFTri.ar(150 * lfo.().range(1, 2), 0, 0.05),

+

BPF.ar(PinkNoise.ar(), lfo.().range(1000, 3000), 0.1, 0.7)

+

] * EnvGate(),

+

fadeTime: 0.07,

+

width: 1

+

)

+

}.play

+

)

+


+

x.release

+


+

+

Ex.2) Fast crossfading between fx processings ("fx granulation")

+


+

// To be distinguished by fx processing of grains!

+

// define fxs to read from buses

+


+

(

+

a = Bus.audio(s, 6);

+


+

SynthDef(\resample, { |out = 0, in, lfoFreq = 0.2, resampleLo = 500, resampleHi = 2000,

+

lag = 0.001, mix = 1, amp = 0.1|

+

var sig, inSig = In.ar(in, 2), lfo;

+

lfo = LFDNoise3.kr(lfoFreq).range(resampleLo, resampleHi);

+

sig = Latch.ar(inSig, Impulse.ar(lfo)).lag(lag);

+

    Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp);

+

}).add;

+


+

SynthDef(\ring, { |out = 0, in, lfoFreq = 0.2, modFreqLo = 50, modFreqHi = 2000,

+

mix = 1, amp = 0.1|

+

var sig, mod, lfo, src, inSig = In.ar(in, 2);

+

lfo = LFDNoise3.kr(lfoFreq).range(modFreqLo, modFreqHi);

+

mod = SinOsc.ar(lfo);

+

sig = inSig * mod;

+

Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp);

+

}).add;

+


+

SynthDef(\rectifier, { |out = 0, in, amount = 0, mix = 1, amp = 0.1|

+

var sig, inSig = In.ar(in, 2);

+

// need collect as if ugen doesn't take arrays

+

sig = inSig.collect { |x,i| x.ceil.if(x, x.abs * amount) };

+

   Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp);

+

}).add;

+

)

+


+

// start fxs first

+

(

+

u = Synth(\resample, [in: a.subBus(0, 2)]);

+

v = Synth(\ring, [in: a.subBus(2, 2)]);

+

w = Synth(\rectifier, [in: a.subBus(4, 2)]);

+

)

+


+


+

// start source, crossfade between fx buses

+

// play with MouseX, fx and source period are in integer relation

+


+

(

+

x = {

+

var seq = Demand.kr(

+

Impulse.kr(5),

+

0,

+

Dxrand([60, 62, 65.5, 66, 68.5], inf) + Drand([-12, 0, 12], inf)

+

).midicps.lag(0.015);

+


+

DXFanOut.ar(

+

Dseq([0, 1, 2], inf) * 2 + a.index,

+

`(SinOsc.ar(

+

seq * [1, 1.01],

+

0,

+

1

+

) * EnvGate()),

+

fadeTime: 0.2 / (MouseX.kr(5, 25).round.poll),

+

width: 1.2

+

)

+

}.play

+

)

+


+


+

// cleanup

+


+

x.release;

+


+

[u, v, w].do(_.free);

+


+


+


+

// granulated source + fast fx crossfades

+


+

// load sound file

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+


+

// start fxs first

+

(

+

u = Synth(\resample, [in: a.subBus(0, 2), lag: 0.01, mix: 0.6]);

+

v = Synth(\ring, [in: a.subBus(2, 2), mix: 1]);

+

)

+


+

// feed DXFanOut with granulated signal

+

// control two trigger rates with MouseX and MouseY

+

(

+

x = {

+

var trig = Impulse.ar(MouseY.kr(20, 100).poll(2, label: 'source trigrate'));

+

var pos = BufDur.kr(b) * LFDNoise3.ar(0.6).range(0.1, 0.5);

+

var sig = TGrains.ar(2, trig, b, 1, pos, 0.5, Dseq([-1, 1], inf) * 0.8, 3);

+

DXFanOut.ar(

+

Dseq([0, 2], inf) + a.index,

+

`(sig * EnvGate()),

+

fadeTime: 0.2 / (MouseX.kr(5, 100).round.poll(2, label: 'fx trigrate')),

+

width: 1.5

+

)

+

}.play

+

)

+


+

// cleanup

+


+

x.release;

+


+

[u, v].do(_.free);

+


+

a.free;

+


+


+ + diff --git a/Help/DXMix.html b/Help/DXMix.html new file mode 100755 index 0000000..f92bc17 --- /dev/null +++ b/Help/DXMix.html @@ -0,0 +1,1036 @@ + + + + + + + + + + + +

DXMix crossfades between signals according to demand-rate control

+


+

Part of: miSCellaneous

+


+

Inherits from: AbstractDX

+


+

DXMix crossfades between signals according to a sequence of indices, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. 

+


+

NOTE: As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the DX suite overview and go through the help file examples in this order: DXMix - DXMixIn - DXEnvFan - DXEnvFanOut - DXFan - DXFanOut. Some general conventions are treated in detail in the following examples: fades and steps in DXMix help, Ex.2 – width and offset arguments in DXMix help, Ex.3 – multichannel expansion in DXMix help, Ex.6 – crossfade types in DXEnvFan help, Ex.1.

+


+

NOTE: PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice.

+


+

NOTE: Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See Ex.8 from DXMix help and Ex.2, Ex.4 from DXEnvFan help for aspects of CPU demand.

+


+

NOTE: In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See DXFan help Ex.4 for aspects of granulation with high trigger rates / short grain durations.

+


+

NOTE: The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours.

+


+

CREDITS: Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz.

+


+


+

See also: DX suite, DXMixIn, DXEnvFan, DXEnvFanOut, DXFan, DXFanOut , Buffer Granulation, Live Granulation, PbindFx, kitchen studies, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

Creation / Class Methods

+


+

*ar (in, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0)

+

+

in - Determines the sequence of signals to be crossfaded.

+

A demand rate or other ugen returning channel array indices, a single channel array index or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of in

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

channelsArrayRef - The signals to be crossfaded. 

+

To avoid multichannel expansion of this arg, the signals to be crossfaded must be wrapped into a Ref object.

+

If the Ref object contains an array of signal arrays, these arrays are crossfaded.

+

A SequenceableCollection causes multichannel expansion, whereby single items of the collection

+

can itself be Ref objects containing multichannel signals.

+

See multichannel examples below.

+

fadeTime - A fade time, a demand rate or other ugens returning fade times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of fadeTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of fadeTime depends on fadeMode.

+

fadeTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

stepTime - A step time, a demand rate or other ugens returning step times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of stepTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of stepTime depends on fadeMode.

+

stepTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

fadeMode - Integers between 0 and 4 or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

fadeMode = 0: only fadeTimes are used, no steps

+

fadeMode = 1: alternate steps and fades, begin with step; stepTime means time without fade

+

fadeMode = 2: alternate fades and steps, begin with fade; stepTime means time without fade

+

fadeMode = 3: alternate steps and fades, begin with step; stepTime means sum of step and fade,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

fadeMode = 4: alternate fades and steps, begin with fade; stepTime means sum of fade and step,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

Defaults to 0.

+

sine - Determines the crossfade type: sine-based or not.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning sine numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of sine

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

equalPower - Determines if crossfading of equal power type (square root) should be applied.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning equalPower numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of equalPower

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

power - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude. If power and curve are passed, power applies before.

+

A positive Number or a demand rate or other ugen returning positive power numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of power

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

curve - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude according to the lincurve mapping. 

+

If power and curve are passed, power applies before.

+

A Number or a demand rate or other ugen returning curve numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of curve

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Calculation of curvature is not giving reliable results when width and / or dynOutOffset are 

+

being modulated at the same time.

+

Defaults to 0. 

+

allowTypeSeq - Enables sequencing of sine, equalPower, power and curve with 

+

demand rate ugens and modulating of sine and equalPower with other ugens.

+

A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion.

+

Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0.

+

fadeRate - One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

Defaults to \ar. 

+

maxFadeNum - Integer determining the maximum number of fades, after which doneAction applies. 

+

A SequenceableCollection causes multichannel expansion. Not modulatable.

+

Defaults to inf.

+

maxWidth - An Integer determining the maximum width or

+

a SequenceableCollection of such, causing multichannel expansion, width goes into PanAz's width arg.

+

maxWidth increases the internally used and potentially needed number of parallel channels. Not modulatable.

+

Defaults to 2.

+

width - Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, 

+

causing multichannel expansion. Not modulatable in versions earlier than SC 3.9.

+

It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed maxWidth.

+

Defaults to 2.

+

initOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines an initial offset for PanAz's pos arg.

+

This can be useful for a start with full or reduced width. 

+

Not modulatable. Defaults to 0.

+

maxDynOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the maximum dynOutOffset to be expected.

+

maxDynOutOffset increases the internally used and potentially needed number of parallel channels. 

+

Not modulatable. Defaults to 1.

+

dynOutOffset - UGen, Integer or Float or

+

a SequenceableCollection of such, causing multichannel expansion.

+

By passing a ugen the movement between buses can be modulated.

+

Note that a ugen's output must not exceed maxDynOutOffset.

+

Defaults to 0.

+

allowFadeEnd - Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion.

+

Determines if a demand rate input to in with finite length will be monitored, which needs a quite complicated 

+

trigger logic and more running ugens. If set to 0, the behaviour after the end of in is undefined.

+

Defaults to 1.

+

zeroThr - A Number or a ugen returning zeroThr numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

Determines if output values below this threshold are replaced by 0.

+

This makes sense if the output signal is used as trigger (e.g. with DXEnvFan).

+

In the case of low power numbers small inaccuracies are amplified, this is avoided

+

with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power.

+

As this requires more ugens running in parallel it is disabled by default = nil.

+

doneAction - Integer or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the doneAction after maxFadeNum is exceeded.

+

Defaults to 0.

+


+

*kr (in, channelsArrayRef, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0)

+

+


+

+


+

Examples

+


+

(

+

// load with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.reboot;

+

)

+


+

Ex.1) Basic usage: simple crossfade

+

+


+

// crossfading between 2 mono sources

+

// the array of channels to crossfade must be put into a Ref

+


+

(

+

x = {

+

DXMix.ar(

+

Dseq([0, 1], inf),

+

`[

+

Saw.ar(LFDNoise3.kr(0.1).range(40, 90), 0.03),

+

PinkNoise.ar(0.05)

+

],

+

fadeTime: 3

+

) ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// sources can itself be multichannel arrays

+

// fadeTimes passed as demand rate ugen

+


+

(

+

x = {

+

var lfo = LFDNoise3.kr(0.2);

+

DXMix.ar(

+

Dseq([0, 1], inf),

+

`[

+

Saw.ar(lfo.range(40, 90) * [1, 1.02], 0.03),

+

{ BPF.ar(PinkNoise.ar(0.05), lfo.range(200, 5000), 0.1) } ! 2

+

],

+

fadeTime: Dwhite(3, 7)

+

)

+

}.play

+

)

+


+

x.release

+


+


+


+

// So far all examples default to width = 2, PanAz's default width.

+

// It means that at most two channels overlap during crossfade.

+


+

// If higher values of width should be taken, maxWidth must be increased,

+

// in order to ensure a sufficiently large number of channels used for overlapping internally.

+

// See Ex.3 for a detailled explanation of the width arg.

+


+

// sliding over a sequence of channel indices

+


+

(

+

x = {

+

var lfo = LFDNoise3.kr(0.2);

+

DXMix.ar(

+

Dseq([0, 2, 1, 3], inf),

+

`[

+

Saw.ar(lfo.range(40, 70) * [1, 1.02], 0.02),

+

{ BPF.ar(PinkNoise.ar(0.05), lfo.range(200, 5000), 0.1) } ! 2,

+

Pulse.ar(lfo.range(40, 70) * [1, 1.02], 0.5, 0.02),

+

{ BPF.ar(Dust.ar(100), lfo.range(200, 5000), 0.1) } ! 2

+

],

+

fadeTime: Dwhite(3, 7),

+

maxWidth: 5,

+

width: 4

+

)

+

}.play

+

)

+


+

x.release

+


+


+


+


+

Ex.2) Basic usage: fades and steps

+

+


+

// in Ex. 1 we always used fadeMode's default 0, where only fades are polled

+

// You might instead want to alternate steps (sections of no fade) with fades:

+


+

// fadeMode = 0: only fadeTimes are used, no steps

+

// fadeMode = 1: alternate steps and fades, begin with step; stepTime means time without fade

+

// fadeMode = 2: alternate fades and steps, begin with fade; stepTime means time without fade

+

// fadeMode = 3: alternate steps and fades, begin with step; stepTime means sum of step and fade,

+

// thus stepTime must be larger than fadeTime

+

// fadeMode = 4: alternate fades and steps, begin with fade; stepTime means sum of fade and step,

+

// thus stepTime must be larger than fadeTime

+


+


+

// Check different fadeModes here, different effects with same times:

+


+

// With fadeMode = 2 or 4 we start with a short fade, which gives the impression of a pickup.

+

// With fadeMode = 3 or 4 the tempo is faster, as fadeTime is subtracted from stepTime.

+

// With these modes it's the user's responsibility to limit fadeTimes.

+


+


+

// NOTE: fadeTimes and stepTimes must be larger than the duration of a control cycle

+

// with default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec.

+

// If you go below, the step mechanism is messed up you get jumps and clicks.

+


+

// Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime,

+

// which is calculated by stepTime - fadeTime, is larger than this threshold.

+


+

(

+

x = {

+

DXMix.ar(

+

Dxrand((0..19), inf),

+

`(Saw.ar((1..10) * 100, 0.1) ++ SinOsc.ar((1..10) * 200, 0, 0.15)),

+

fadeTime: 0.05,

+

stepTime: 0.15,

+

fadeMode: 1

+


+

// check other fadeModes

+


+

// fadeMode: 2

+

// fadeMode: 3

+

// fadeMode: 4

+

)

+

// short fade-in with Function.play to make initial DXMix fade (fadeModes 2 and 4) audible

+

}.play(fadeTime: 0.005)

+

)

+


+

x.release

+


+


+

// some overtone fun with large width, independant DXMixs for L and R

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

{ 

+

DXMix.ar(

+

Dxrand((0..15), inf),

+

`(Saw.ar((1..8) * 70 * lfo, 0.05) ++ SinOsc.ar((1..8) * 68.5 * lfo, 0, 0.075)),

+

fadeTime: 0.02,

+

stepTime: 0.18,

+

fadeMode: 1,

+

width: 7,

+

maxWidth: 8

+

) 

+

} ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// ugen arguments can also be passed to 'in'

+

// the 'initOutOffset' arg is explained along the next examples

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

{ 

+

DXMix.ar(

+

LFDNoise3.ar(2).range(0, 15),

+

`(LFTri.ar((1..8) * 70 * lfo, 0, 0.05) ++ SinOsc.ar((1..8) * 68.5 * lfo, 0, 0.075)),

+

fadeTime: 0.01,

+

stepTime: 0.15,

+

fadeMode: 1,

+

width: 7,

+

maxWidth: 8,

+

equalPower: 0, // better here with sines, see curvature options in DXEnvFan help, Ex.1

+

initOutOffset: -4

+

) 

+

} ! 2

+

}.play

+

)

+


+

x.release

+


+


+


+

// stepTime controlled by ugen

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

{ 

+

DXMix.ar(

+

LFDNoise3.ar(2).range(0, 15),

+

`(LFTri.ar((1..8) * 70 * lfo, 0, 0.05) ++ SinOsc.ar((1..8) * 68.5 * lfo, 0, 0.075)),

+

fadeTime: 0.01,

+

stepTime: SinOsc.ar(0.2).range(0.1, 0.5),

+

fadeMode: 1,

+

width: 3,

+

maxWidth: 8,

+

initOutOffset: -3

+

) 

+

} ! 2

+

}.play

+

)

+


+

x.release

+


+


+


+

Ex.3) Width and offset arguments for channel span

+


+

// The width parameter determines the number of channels, which are 

+

// maximally affected by the crossfade, it uses the convention of PanAz's width arg. 

+

// The graphics below explain the overlap scheme of DXMix and cousins,

+

// for DXMix the indices denote the channels to be mixed,

+

// for DXMixIn the indices denote the buses to be mixed,

+

// for DXFanOut the buses to which the signal will be spread,

+

// for DXFan the channels to which the signal will be spread,

+

// for DXEnvFanOut the buses to which the envelope will be spread.

+

// for DXEnvFan the channels to which the envelope will be spread.

+


+

// Let's look at a DXMix example with different width values:

+


+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

DXMix.ar(

+

Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf),

+

`(Saw.ar((1..8) * 70 * lfo, 0.075)),

+

fadeTime: 0.12,

+

stepTime: 0.38,

+

fadeMode: 1,

+

width: 2,

+

// check other width values below maxWidth

+


+

maxWidth: 8

+

) ! 2

+

}.play

+

)

+


+

x.release

+


+

// Width = 2 means that the crossfade only affects adjacent channels / signals / buses. 

+

// In the graphic the center position of the movement is marked with bold letters, 

+

// a diamond sign denotes, that the center lies in the middle of two channels, 

+

// which are then equally weighted. Squared brackets enclose the active channel numbers.

+

// So the succession of two rows describes one fade.

+


+

// width = 2:

+


+

+


+

// For an arbitrary integer width n the number of active channels lies between n-1 and n, 

+

// here a scheme of the number of affected channels:

+


+


+

width number of channels affected with

+

center position directly at channel

+


+

2 1

+

3 3

+

4 3

+

5 5

+

6 5

+

7 7

+

8 7

+

...

+


+

width number of channels affected with

+

center position exactly between two channels

+


+

2 2

+

3 2

+

4 4

+

5 4

+

6 6

+

7 6

+

8 8

+

...

+


+


+

// DXMix and cousins take over the logic of PanAz. However there's a little difference at 

+

// start with width > 2. As we are sliding over a index sequence which has a beginning, 

+

// it seems natural that the start index should be the middle of the span, which is covered according to width. 

+

// So from width > 2 onwards there's a "left side" of the channel span, which hasn't been generated so far, 

+

// thus the full width is not reached before an entrance phase which increases with the width value.

+


+

// width = 3:

+


+

+


+


+

// width = 4:

+


+

+


+


+

// If one wants to start width full width there's the possibility to do this by 

+

// passing an initOutOffset argument.

+


+

// Here we start with initOutOffset = 1, fadeMode = 1 (step first),

+

// index = 4 (5th partial) is the middle of the channel span with most weight,

+

// which can be clearly perceived.

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

DXMix.ar(

+

Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf),

+

`(Saw.ar((1..8) * 70 * lfo, 0.075)),

+

fadeTime: 0.1,

+

stepTime: 2,

+

fadeMode: 1,

+

width: 3,

+

maxWidth: 8,

+

initOutOffset: 1

+

) ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// overlap scheme with width = 3 and initOutOffset = 1:

+


+

+


+


+

// Here we start with initOutOffset = 0.5, fadeMode = 1 (step first),

+

// channels with indices 0 and 4 (base tone and 5th partial) are equally weighted.

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

DXMix.ar(

+

Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf),

+

`(Saw.ar((1..8) * 70 * lfo, 0.075)),

+

fadeTime: 0.1,

+

stepTime: 2,

+

fadeMode: 1,

+

width: 3,

+

maxWidth: 8,

+

initOutOffset: 0.5

+

) ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// overlap scheme with width = 3 and initOutOffset = 0.5:

+


+


+

+


+


+


+

// It might also be desirable to start with a fade from silence.

+

// This can be done with a negative initOutOffset:

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

DXMix.ar(

+

Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf),

+

`(Saw.ar((1..8) * 70 * lfo, 0.075)),

+

fadeTime: 2,

+

width: 2,

+

initOutOffset: -1

+

) ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// overlap scheme with width = 2 and initOutOffset = -1:

+


+


+

+


+


+


+

// width can also be modulated (suited rather for lfos, only enabled from SC 3.9 onwards)

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

DXMix.ar(

+

Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf),

+

`(Saw.ar((1..8) * 70 * lfo, 0.05)),

+

fadeTime: 2,

+

width: SinOsc.ar(LFDNoise3.ar(1).range(0.2, 10)).range(2, 5),

+

maxWidth: 5

+

) ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// for faster modulations take dynOutOffset,

+

// maxDynOutOffset must be set properly

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

{ 

+

DXMix.ar(

+

Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf),

+

`(SinOsc.ar((1..8) * 140 * lfo, 0, 0.05)),

+

fadeTime: 2,

+

width: 2,

+

maxDynOutOffset: 2,

+

dynOutOffset: SinOsc.ar(LFDNoise3.ar(1).range(0.2, 25)).range(0, 2)

+

) 

+

} ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// You can also control the movement of the channel span entirely by 

+

// passing a ugen to dynOutOffset, therefore set fadeTime to inf. 

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

{ 

+

DXMix.ar(

+

Dseq([0, 4, 1, 5, 2, 6, 3, 7, 0], inf),

+

`(SinOsc.ar((1..8) * 140 * lfo, 0, 0.05)),

+

fadeTime: inf,

+

width: 2,

+

maxDynOutOffset: 7,

+

dynOutOffset: SinOsc.ar(0.2).range(0, 7)

+

) 

+

} ! 2

+

}.play

+

)

+


+

x.release

+


+


+

Ex.4) Switching between PlayBufs

+


+

// This can go towards granulation

+


+

// load sound file

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+


+

// switch between looping PlayBufs

+


+

(

+

x = {

+

var sig = {

+

DXMix.ar(

+

Dxrand([0, 1, 2], inf),

+

`(PlayBuf.ar(1, b, BufRateScale.kr(b) * [0.4, 1, 2.2], loop: 1)),

+

stepTime: 0.2,

+

fadeTime: 0.01,

+

fadeMode: 1,

+

width: 2

+

) } ! 2;

+

// do a bit correlation

+

Splay.ar(sig, 0.8)

+

}.play

+

)

+


+

x.release

+


+


+

Ex.5) Granulation

+


+


+

// granulation by fast fading between channels

+

// here single channels contain PlayBufs with ordered rates

+


+

// load sound file

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+


+

// widths (default 2) means overlap, thus grain length = 0.02

+


+

(

+

x = {

+

    var durs, trig, sig, thr = 0.5;

+

    sig = { |i|

+

        DXMix.ar(

+

            Dseq((49..0), inf),

+

            `(PlayBuf.ar(

+

                    1,

+

                    b,

+

                    BufRateScale.kr(b) * ({ |j| j / 500 + 0.7 + (i * 0.02) } ! 50),

+

                    loop: 1

+

                )),

+

            fadeTime: 0.01

+

        )

+

    } ! 2;

+

    // do a bit correlation

+

    Splay.ar(sig, 0.8)

+

}.play

+

)

+


+

x.release

+


+


+

// microsound with synthesized sources

+

// fadeTime change between very fast and medium length

+


+

// compare version with dynOutOffset

+


+

(

+

// universal lfo, several instances to be used, thus put into a Function

+


+

l = { LFDNoise3.ar(LFDNoise3.ar(1).exprange(0.1, 30)).range(0.5, 3) };

+


+

// pool of sources and arguments for channel array

+


+

e = (

+

GrayNoise: [0.1],

+

Dust: [500],

+

BrownNoise: [0.1],

+

Pulse: [exprand(50, 500) * l],

+

SinOsc: [exprand(50, 500) * l],

+

Saw: [exprand(50, 500) * l]

+

);

+


+

x = {

+

var durs, trig, sig, thr = 0.5;

+

sig = { |i|

+

DXMix.ar(

+

Dseq((0..19), inf),

+

`(

+

// generate array of oscillators to slide over

+

{

+

var sym = e.keys.choose;

+

sym.asClass.performList(\ar, e[sym]);

+

} ! 20

+

),

+

fadeTime: Dstutter(

+

Diwhite(2, 5),

+

Dwhite(0.01, 1).linexp(0.01, 1, 0.01, 1)

+

),

+

width: 2,

+

maxWidth: 2,

+

// add oscillation between adjacent channels of sequence

+

// dynOutOffset: SinOsc.ar(LFDNoise3.ar(1).exprange(1, 100)).range(0, 1)

+

) * 0.1

+

} ! 2;

+

// do a bit correlation

+

Splay.ar(sig, 0.8) * 2

+

}.play

+

)

+


+


+

x.release

+


+


+

Ex.6) Multichannel expansion

+


+

// If a multiple demand rate ugen is implicitely needed, it must be wrapped into a Function

+


+

// Because of the array passed to fadeTime, we need two Dseqs to poll from.

+

// As only one demand rate ugen is passed as 'in' arg, it must be wrapped into a Function.

+


+

(

+

x = {

+

DXMix.ar(

+

{ Dseq([0, 1, 2], inf) },

+

// equivalent:

+

// [Dseq([0, 1, 2], inf), Dseq([0, 1, 2], inf)],

+

`[

+

Saw.ar(LFDNoise3.kr(0.1).range(40, 90), 0.03),

+

Pulse.ar(LFDNoise3.kr(0.1).range(80, 180), 0.5, 0.03),

+

PinkNoise.ar(0.05)

+

],

+

fadeTime: [0.03, 3]

+

)

+

}.play

+

)

+


+

x.release

+


+


+

// L and R switching between same sources

+


+

(

+

x = {

+

var lfo = { LFDNoise3.kr(0.2) };

+

DXMix.ar(

+

[Dseq([0, 1, 2], inf), Dseq([2, 3, 1], inf)],

+

`[

+

Saw.ar(lfo.().range(40, 90), 0.03),

+

BPF.ar(PinkNoise.ar(0.05), lfo.().range(200, 5000), 0.1),

+

BPF.ar(Crackle.ar(1.95, 0.5), lfo.().range(200, 5000), 0.1),

+

Pulse.ar(lfo.().range(40, 90), 0.5, 0.03)

+

],

+

fadeTime: { Dwhite(3, 7) }

+

)

+

}.play

+

)

+


+

x.release

+


+


+

// L and R switching between different sources

+


+

(

+

x = {

+

var lfo = { LFDNoise3.kr(0.2) };

+

DXMix.ar(

+

[Dseq([0, 1], inf), Dseq([1, 0], inf)],

+

[

+

`[

+

Saw.ar(lfo.().range(40, 90), 0.03),

+

BPF.ar(PinkNoise.ar(0.05), lfo.().range(200, 5000), 0.1)

+

],

+

`[

+

Saw.ar(lfo.().range(40, 90), 0.03),

+

BPF.ar(PinkNoise.ar(0.05), lfo.().range(200, 5000), 0.1)

+

]

+

],

+

fadeTime: { Dwhite(3, 7) }

+

)

+

}.play

+

)

+


+

x.release

+


+


+

// last example, written in a more condensed way

+


+

(

+

x = {

+

var lfo = { LFDNoise3.kr(0.2) };

+

DXMix.ar(

+

[Dseq([0, 1], inf), Dseq([1, 0], inf)],

+

[

+

{ Saw.ar(lfo.().range(40, 90), 0.03) } ! 2,

+

{ BPF.ar(PinkNoise.ar(0.05), lfo.().range(200, 5000), 0.1) } ! 2

+

].flop.collect(`_),

+

fadeTime: { Dwhite(3, 7) }

+

)

+

}.play

+

)

+


+

x.release

+


+


+

// overtone series - L up, R down

+

// decreasing fundamental 

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

DXMix.ar(

+

[Dseq((0..15), inf), Dseq((15..0), inf)],

+

`(SinOsc.ar((1..16) * 120 * lfo, 0, 0.05)),

+

fadeTime: 0.1,

+

equalPower: 0

+

)

+

}.play

+

)

+


+

x.release

+


+


+


+

// more complicated expansions:

+

// DXMix produces a nested array of two stereo fades,

+

// it is mixed to a flat stereo array

+


+

(

+

x = {

+

var lfo = { LFDNoise3.kr(0.2) };

+

Mix(DXMix.ar(

+

[Dseq([0, 1], inf), Dseq([2, 3], inf)],

+

`[

+

Saw.ar(lfo.().range(40, 90) * [1, 3], 0.03),

+

{ BPF.ar(BrownNoise.ar(0.05), lfo.().range(100, 500), 0.3) } ! 2,

+

SinOsc.ar(lfo.().range(200, 500) * [2, 3], 0, 0.03),

+

{ BPF.ar(Dust.ar(300), lfo.().range(200, 5000), 0.1) } ! 2

+

],

+

fadeTime: [Dwhite(3, 7), Dwhite(2, 5)]

+

))

+

}.play

+

)

+


+

x.release

+


+


+

// similar situation as above, but the two stereo fades are referring to the same channels

+

// the result is a kind of accentuation

+


+

(

+

x = {

+

var lfo = LFDNoise3.kr(0.2);

+

Mix(DXMix.ar(

+

[Dseq([0, 1], inf), Dseq([1, 1, 1, 0], inf)],

+

`[

+

Saw.ar(lfo.range(40, 90) * [1, 3], 0.025),

+

{ BPF.ar(PinkNoise.ar(0.05), lfo.range(500, 5000), 0.1, 5) } ! 2

+

],

+

fadeTime: [Dwhite(3, 7), 0.1],

+

// width < 2 produces fade gaps

+

width: [2, 0.7]

+

))

+

}.play

+

)

+


+

x.release

+


+


+

// interesting multichannel expansions are possible with drate ugens based on nested arrays,

+

// e.g. coupling of streams 

+


+

(

+

x = {

+

var lfo = { LFDNoise3.kr(0.2) };

+

Mix(DXMix.ar(

+

Dxrand([[0, 1], [1, 0], [2, 3], [3, 2]], inf),

+

`[

+

Saw.ar(lfo.().range(40, 90) * [1, 3], 0.02),

+

{ BPF.ar(PinkNoise.ar(1), lfo.().range(500, 5000), 0.02, 1) } ! 2,

+

SinOsc.ar(lfo.().range(400, 900) * [1, 1.2], 0, 0.01),

+

{ BPF.ar(ClipNoise.ar(0.2), lfo.().range(500, 5000), 0.02, 1) } ! 2

+

],

+

fadeTime: 0.1,

+

// width < 2 produces fade gaps

+

width: 1

+

))

+

}.play

+

)

+


+

x.release

+


+


+

Ex.7) Stopping and doneAction

+


+

// The done action is invoked after maxFadeNum.

+


+

(

+

x = {

+

var lfo = LFDNoise3.kr(0.2);

+

DXMix.ar(

+

Dseq([0, 1], inf),

+

`[

+

Saw.ar(lfo.range(40, 90) * [1, 1.02], 0.03),

+

{ BPF.ar(PinkNoise.ar(0.05), lfo.range(200, 5000), 0.1) } ! 2

+

],

+

fadeTime: 0.5,

+

stepTime: 1,

+

fadeMode: 0, // check with other modes too

+

maxFadeNum: 5,

+

doneAction: 2

+

)

+

}.play

+

)

+


+

x.release

+


+


+

Ex.8) Saving CPU

+


+

// This might be a topic if you're running many fades in parallel and 

+

// fadeTimes are not extremely short (and hence audio rate doesn't make a difference).

+


+

// Here my machine needs ca. 2 % CPU for fadeRate = \kr

+

// and ca. 6 % CPU for fadeRate = \ar.

+


+

// 10 parallel sequences of overtone fading

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

var sig = DXMix.ar(

+

{ Drand((0..15), inf) },

+

`(SinOsc.ar((1..16) * 70 * lfo, 0, 0.2 / (1..16).sqrt)),

+

fadeTime: 1 / (1..10),

+

fadeRate: \kr,  // check CPU difference to \ar

+

maxWidth: 4,

+

width: 3

+

);

+

Splay.ar(sig)

+

}.play

+

)

+


+

x.release

+


+


+

// monitoring demand rate ugens needs a quite complicated trigger logic in this context

+

// per default you can pass finite drate ugens for 'in' - 

+

// observe that layers will stop after different times as fadeTimes are different

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

var sig = DXMix.ar(

+

{ Drand((0..15), 20) },

+

`(SinOsc.ar((1..16) * 70 * lfo, 0, 0.2 / (1..16).sqrt)),

+

fadeTime: 1 / (1..10),

+

fadeRate: \kr,  // also check CPU difference to \ar

+

maxWidth: 4,

+

width: 3

+

);

+

Splay.ar(sig)

+

}.play

+

)

+


+

x.release

+


+


+

// if you don't need this option you can save a number of ugens with allowFadeEnd = 0, compare

+


+

(

+

x = {

+

var lfo = XLine.ar(1, 0.5, 60);

+

var sig = DXMix.ar(

+

{ Drand((0..15), 20) },

+

`(SinOsc.ar((1..16) * 70 * lfo, 0, 0.2 / (1..16).sqrt)),

+

fadeTime: 1 / (1..10),

+

fadeRate: \kr,  // also check CPU difference to \ar

+

maxWidth: 4,

+

width: 3,

+

allowFadeEnd: 0

+

);

+

Splay.ar(sig)

+

}.play

+

)

+


+

x.release

+


+

// Also see Ex.2, Ex.4 from DXEnvFan help for CPU aspects.

+

// Note that the use of the options allowTypeSeq and zeroThr also needs more CPU.

+

// Rescaling, which is necessary for SC versions before 3.9 with audio rate, is also more CPU-costy.

+


+


+


+


+


+


+ + diff --git a/Help/DXMixIn.html b/Help/DXMixIn.html new file mode 100755 index 0000000..397d7bf --- /dev/null +++ b/Help/DXMixIn.html @@ -0,0 +1,286 @@ + + + + + + + + + + + +

DXMixIn crossfades between signals from buses according to demand-rate control

+


+

Part of: miSCellaneous

+


+

Inherits from: AbstractDX

+


+

DXMixIn crossfades between signals from buses according to a sequence of indices, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. 

+


+

NOTE: As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the DX suite overview and go through the help file examples in this order: DXMix - DXMixIn - DXEnvFan - DXEnvFanOut - DXFan - DXFanOut. Some general conventions are treated in detail in the following examples: fades and steps in DXMix help, Ex.2 – width and offset arguments in DXMix help, Ex.3 – multichannel expansion in DXMix help, Ex.6 – crossfade types in DXEnvFan help, Ex.1.

+


+

NOTE: PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice.

+


+

NOTE: Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See Ex.8 from DXMix help and Ex.2, Ex.4 from DXEnvFan help for aspects of CPU demand.

+


+

NOTE: In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See DXFan help Ex.4 for aspects of granulation with high trigger rates / short grain durations.

+


+

NOTE: The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours.

+


+

CREDITS: Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz.

+


+


+

See also: DX suite, DXMix, DXEnvFan, DXEnvFanOut, DXFan, DXFanOut , Buffer Granulation, Live Granulation, PbindFx, kitchen studies, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

Creation / Class Methods

+


+

*ar (in, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0)

+

+

in - Determines the sequence of signals to be crossfaded.

+

A demand rate or other ugen returning bus indices, a single bus index or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of in

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

fadeTime - A fade time, a demand rate or other ugens returning fade times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of fadeTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of fadeTime depends on fadeMode.

+

fadeTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

stepTime - A step time, a demand rate or other ugens returning step times or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of stepTime

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

The interpretation of stepTime depends on fadeMode.

+

stepTime must be larger than the duration of a control cycle.

+

Defaults to 1.

+

fadeMode - Integers between 0 and 4 or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

fadeMode = 0: only fadeTimes are used, no steps

+

fadeMode = 1: alternate steps and fades, begin with step; stepTime means time without fade

+

fadeMode = 2: alternate fades and steps, begin with fade; stepTime means time without fade

+

fadeMode = 3: alternate steps and fades, begin with step; stepTime means sum of step and fade,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

fadeMode = 4: alternate fades and steps, begin with fade; stepTime means sum of fade and step,

+

thus stepTime must be larger than fadeTime,

+

the difference must be larger than the duration of a control cycle

+

Defaults to 0.

+

sine - Determines the crossfade type: sine-based or not.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning sine numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of sine

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

equalPower - Determines if crossfading of equal power type (square root) should be applied.

+

A Boolean, 0 or 1 or a demand rate or other ugen returning equalPower numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of equalPower

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Modulating this arg is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

power - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude. If power and curve are passed, power applies before.

+

A positive Number or a demand rate or other ugen returning positive power numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of power

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Defaults to 1. 

+

curve - This only comes into play if equalPower equals 0, then it's applied to the 

+

crossfade amplitude according to the lincurve mapping. 

+

If power and curve are passed, power applies before.

+

A Number or a demand rate or other ugen returning curve numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

If in this case the overall multichannel size is larger than the size of curve

+

and the latter contains demand rate ugens, they must all be wrapped into Functions.

+

Sequencing this arg with demand rate ugens is only possible if allowTypeSeq equals 1.

+

Calculation of curvature is not giving reliable results when width and / or dynOutOffset are 

+

being modulated at the same time.

+

Defaults to 0. 

+

allowTypeSeq - Enables sequencing of sine, equalPower, power and curve with 

+

demand rate ugens and modulating of sine and equalPower with other ugens.

+

A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion.

+

Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0.

+

fadeRate - One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or

+

a SequenceableCollection of such, causing multichannel expansion. Not modulatable.

+

Defaults to \ar. 

+

maxFadeNum - Integer determining the maximum number of fades, after which doneAction applies. 

+

A SequenceableCollection causes multichannel expansion. Not modulatable.

+

Defaults to inf.

+

maxWidth - An Integer determining the maximum width or

+

a SequenceableCollection of such, causing multichannel expansion, width goes into PanAz's width arg.

+

maxWidth increases the internally used and potentially needed number of parallel channels. Not modulatable.

+

Defaults to 2.

+

width - Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, 

+

causing multichannel expansion. Not modulatable in versions earlier than SC 3.9.

+

It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed maxWidth.

+

Defaults to 2.

+

initOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines an initial offset for PanAz's pos arg.

+

This can be useful for a start with full or reduced width, see examples. 

+

Not modulatable. Defaults to 0.

+

maxDynOutOffset - An Integer or Float or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the maximum dynOutOffset to be expected.

+

maxDynOutOffset increases the internally used and potentially needed number of parallel channels. 

+

Not modulatable. Defaults to 1.

+

dynOutOffset - UGen, Integer or Float or

+

a SequenceableCollection of such, causing multichannel expansion.

+

By passing a ugen the movement between buses can be modulated.

+

Note that a ugen's output must not exceed maxDynOutOffset.

+

Defaults to 0.

+

allowFadeEnd - Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion.

+

Determines if a demand rate input to in with finite length will be monitored, which needs a quite complicated 

+

trigger logic and more running ugens. If set to 0, the behaviour after the end of in is undefined.

+

Defaults to 1.

+

zeroThr - A Number or a ugen returning zeroThr numbers or 

+

a SequenceableCollection of such, causing multichannel expansion.

+

Determines if output values below this threshold are replaced by 0.

+

This makes sense if the output signal is used as trigger (e.g. with DXEnvFan).

+

In the case of low power numbers small inaccuracies are amplified, this is avoided

+

with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power.

+

As this requires more ugens running in parallel it is disabled by default = nil.

+

doneAction - Integer or a SequenceableCollection of such, causing multichannel expansion.

+

Determines the doneAction after maxFadeNum is exceeded.

+

Defaults to 0.

+


+

*kr (in, fadeTime = 1, stepTime = 1, fadeMode = 0, sine = 1, equalPower = 1, power = 1, curve = 0, allowTypeSeq = 0, fadeRate = \ar, maxFadeNum = inf, maxWidth = 2, width = 2, initOutOffset = 0, maxDynOutOffset = 1, dynOutOffset = 0, allowFadeEnd = 1, zeroThr = nil, doneAction = 0)

+

+


+

+


+

Examples

+


+

(

+

// load with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.reboot;

+

)

+


+


+

Ex.1) Crossfaded mixing from buses

+

+


+

// play to buses silently

+


+

(

+

a = Bus.audio(s, 5);

+


+

x = {

+

Out.ar(a.index, [

+

BrownNoise.ar(0.1),

+

PinkNoise.ar(0.2),

+

WhiteNoise.ar(0.05),

+

ClipNoise.ar(0.03),

+

GrayNoise.ar(0.05)

+

])

+

}.play

+

)

+


+

// mix from buses, mind node order

+


+

(

+

y = {

+

DXMixIn.ar(

+

Dxrand((0..4), inf) + a.index,

+

fadeTime: 0.5,

+

)

+

}.play(addAction: \addToTail)

+

)

+


+

y.release

+


+


+

// cleanup

+


+

(

+

x.free;

+

a.free;

+

)

+


+


+


+


+

Ex.2) Multichannel expansion

+

+

// play to buses silently

+


+

(

+

a = Bus.audio(s, 5);

+

x = { Out.ar(a.index, SinOsc.ar((3..7) * 100, 0, 0.05)) }.play

+

)

+


+

// mix the source

+

// a Drate ugen with arrays causes multichannel expansion

+

// thus the drate ugen for fadeTime needs to be wrapped into a Function

+


+

(

+

z = {

+

DXMixIn.ar(

+

Drand([[0, 1], [2, 3], [4, 0], [1, 2], [3, 4]], inf) + a.index,

+

fadeTime: { Dwhite(0.5, 5) }

+

)

+

}.play(addAction: \addToTail)

+

)

+


+

// stop DXMixIn, source still running

+


+

z.release

+


+


+

// get source with other rhythm

+


+

(

+

z = {

+

DXMixIn.ar(

+

Dseq([[0, 1], [2, 3], [4, 0], [1, 2], [3, 4]], inf) + a.index,

+

fadeTime: [0.05, 2]

+

)

+

}.play(addAction: \addToTail)

+

)

+


+

z.release

+


+

// cleanup

+


+

(

+

x.free;

+

a.free;

+

)

+


+


+


+


+ + diff --git a/Help/Dwalk.html b/Help/Dwalk.html new file mode 100755 index 0000000..916208f --- /dev/null +++ b/Help/Dwalk.html @@ -0,0 +1,367 @@ + + + + + + + + + + + +

Dwalk demand rate ugen for (random) walks

+


+

+

Part of: miSCellaneous

+


+

+

Inherits from: UGen

+


+
Dwalk can be used for general purposes in a way similar to Pwalk, but can also return data in a form, which is especially suited for usage with ZeroXBufRd. A sequence of Integers (stepsPerDir) determines the number of consequential additions of numbers (stepWidth) before directional changes. Dwalk returns the current sum and, optionally, the current direction (plus or minus 1).

+


+

+

For buffer modulation with ZeroXBufRd a stepWidth of 1 allows stepping through adjacent segments. There are two ways to achieve smooth directional changes of the read pointer: (1) at zero crossings of the original waveform together with inverting it or (2) at the positions where the waveform changes direction (up/down) or in other words: its slope crosses zero.

+


+NOTE: For implementational reasons Dwalk doesn't multichannel expand, use duplication of Functions instead.

+


+

+

NOTE: If withDirs is set to 1 and many values are polled, extended memory size is recommended: a high bufSize is needed for Dunique (as – and especially – with ZeroXBufRd).

+


+NOTE: Ordinary ugens for stepWidth and steps per direction (via stepsPerDirMulUGen and stepsPerDirAddUGen) should be avoided in combination with ZeroXBufRd. 

+


+

+

See also: ZeroXBufRd

+


+

+


+

+

Creation / Class Methods

+


+

+

*new (stepsPerDir = 1, stepWidth = 1, stepMode = 0, start = 0, lo = -inf, hi = inf, startDir = 1, dirChangeMode = 0, 

+

withDirs = 0, stepsPerDirMulUGen = 1, stepsPerDirAddUGen = 0, dUniqueBufSize = 1048576)

+

+

Creates a new Dwalk object.

+

+

stepsPerDir - Integer or demand rate ugen determining the integer number of steps into one direction.

+

If withDirs equals 0 also an ordinary ugens might be passed. If withDirs equals 1 a Dunique is used internally,

+

in this case for passing an ordinary ugen alone or in combination with a demand rate ugen use 

+

stepsPerDirMulUGen or stepsPerDirAddUGen. Defaults to 1.

+

stepWidth - Number, demand rate or other ugen determining the step width. Defaults to 1.

+

How stepWidth is interpreted in case of a demand rate or other ugen depends on the value of stepMode.

+

An ordinary ugens should not be chosen when using Dwalk for ZeroXBufRd.

+

stepMode - Determines how stepWidth is interpreted in case of a demand rate or other ugen:

+

0 – one step width is taken for all steps into one direction

+

1 – step width changes per step.

+

Defaults to 0.

+

start - The start value, which is returned anyway. Defaults to 0.

+

lo - Low boundary value, causes clipping. Directional changes apply after clipping.

+

Might also be demand rate or other ugen, defaults to -inf.

+

hi - High boundary value, causes clipping. Directional changes apply after clipping.

+

Might also be demand rate or other ugen, defaults to inf.

+

startDir - The start direction, which is returned anyway. Defaults to 1.

+

dirChangeMode - Determines if directional changes should apply immediately (1) or 

+

after a repetition of the current value (0, default). The latter corresponds to the convention of

+

ZeroXBufRd: if a segment should be played in reverse direction, the index (the zeroX arg)

+

would stay the same whereas the direction (the dir arg) is multiplied with -1. 

+

withDirs - 0, 1, false or true. Defaults to 0.

+

Determines if direction values (plus or minus 1) should be returned.

+

In this case the result is an array of two demand rate ugens returning 

+

the summed numbers and the direction values.

+

If set to 1 a Dunique is used internally and stepsPerDir must not get an ordinary ugen.  

+

stepsPerDirMulUGen - Ordinary ugen to be multiplied with stepsPerDir.

+

This option is only necessary in the case that withDirs equals 1 and 

+

should not be used in combination with ZeroXBufRd.

+

The number of steps per direction is calculated by 

+

stepsPerDir * stepsPerDirMulUGen + stepsPerDirAddUGen.

+

Defaults to 1.

+

stepsPerDirAddUGen - Ordinary ugen to be added to stepsPerDir.

+

This option is only necessary in the case that withDirs equals 1 and 

+

should not be used in combination with ZeroXBufRd.

+

The number of steps per direction is calculated by 

+

stepsPerDir * stepsPerDirMulUGen + stepsPerDirAddUGen.

+

Defaults to 0.

+

dUniqueBufSize - Determines the buffer size for a Dunique object which has to be used in case

+

that withDirs is set to 1. Defaults to 1048576. 

+

+


+

+

Examples

+


+

+

(

+

// boot with extended resources

+


+

+

s = Server.local;

+

Server.default = s;

+

s.options.memSize = 8192 * 32; 

+

s.reboot;

+

)

+

+


+

+

Ex.1) Basic usage

+

+

// dirChangeMode == 1 (immediate direction change)
+
// note that here we get two resulting sequences (withDirs == 1):

+

// the actual number sequence and the sequence of directions (+-1)

+


+

+

// stepsPerDir sequence: 

+

// 1, 2, 3, 1, 2, 3, 1...

+


+

+

// step sequence with default stepWidth == 1: 

+

// 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, ...
+

+

// resulting number sequence: 

+

// 0, 1, 0, -1, 0, 1, 2, 1, 2, 3, 2, 1, 0, 1, ...
+

+

(

+

{

+

var x = Dwalk(Dseq([1, 2, 3], inf), dirChangeMode: 1, withDirs: 1);

+

Duty.ar(SampleDur.ir, 0, x)

+

}.plot(0.002)

+

)

+


+

+


+

+

// For usage with ZeroXBufRd the dirChangeMode flag needs to be set to 0 (default)

+

// in order to avoid jumps in the resulting waveform 

+

+

// Let's regard how such a sequence looks like

+


+(

+

{

+

var x = Dwalk(Dseq([1, 2, 3], inf), withDirs: 1);

+

Duty.ar(SampleDur.ir, 0, x)

+

}.plot(0.002)

+

)

+


+

+

// It might be counterintuitive that the number sequence starts with two 0s,

+

// but it makes sense if we regard the input and the definition:

+


+

+

// stepsPerDir sequence: 

+

// 1, 2, 3, 1, 2, 3, 1...

+


+

+

// step sequence with default stepWidth == 1: 

+

// 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, ...
+

+

// The start value (which is always returned) defaults to 0, 

+

// for the next value to return, a directional change in the step sequence is encountered,

+

// because of dirChangeMode 0 again 0 is returned.

+

// The next step is -1, which is no change in direction, thus -1 is effectively added and returned.

+

// Then the direction is changed to 1, so -1 is returned once more – and so on.

+


+

+

// resulting number sequence: 

+

// 0, 0, -1, -1, 0, 1, 1, 1, 2, 2, 1, 0, 0, 0, -1, -1, ...
+

+


+

+

//////////////////////////////////////////////////////////

+


+

+


+

+

// changing stepWidth with default stepMode 0:

+

// slopes vary, but stay the same within one direction

+


+

+

(

+

{

+

var x = Dwalk(Dseq([1, 2, 3], inf), Dwhite(0.1, 0.5), dirChangeMode: 1);

+

Duty.ar(SampleDur.ir, 0, x)

+

}.plot(0.002)

+

)

+


+

+


+

+

// changing stepWidth with default stepMode 1:

+

// slopes can vary also within one direction

+


+

+

(

+

{

+

var x = Dwalk(Dseq([1, 2, 3], inf), Dwhite(0.1, 0.5), stepMode: 1, dirChangeMode: 1);

+

Duty.ar(SampleDur.ir, 0, x)

+

}.plot(0.002)

+

)

+


+
+

+

// also ordinary ugens might be passed to stepWidth ...

+


+

+

(

+

{

+

var x = Dwalk(Dseq([1, 2, 3], inf), SinOsc.ar(1000).range(0.1, 1), dirChangeMode: 1);

+

Duty.ar(SampleDur.ir, 0, x)

+

}.plot(0.003)

+

)

+


+

+


+

+

// ... or to stepsPerDir, but note that it must be an integer (or will be rounded to one),

+

// quantisation can lead to surprising patterns

+


+

+

(

+

{

+

var x = Dwalk(SinOsc.ar(1000).range(1, 5), dirChangeMode: 1);

+

Duty.ar(SampleDur.ir, 0, x)

+

}.plot(0.1)

+

)

+


+

+

(

+

{

+

var x = Dwalk(SinOsc.ar(1001).range(1, 5), dirChangeMode: 1);

+

Duty.ar(SampleDur.ir, 0, x)

+

}.plot(0.1)

+

)

+


+

+

// special case of an ordinary ugen involved in determining the steps per direction plus withDirs set to 1:

+

// in this case internally a Dunique is used which must not get an ordinary ugen input,

+

// hence the arguments stepsPerDirMulUGen and stepsPerDirAddUGen must be used.
+

+

(

+

{

+

var x = Dwalk(stepsPerDirMulUGen: SinOsc.ar(1000).range(1, 5).round, dirChangeMode: 1, withDirs: 1);

+

Duty.ar(SampleDur.ir, 0, x)

+

}.plot(0.1)

+

)

+


+

+


+

+


+
Ex.2) Stochastic synthesis

+


+
// similar to DemandEnvGen Dwalk can be used for stochastic synthesis textures 

+


+

+

// LeakDC recommended, though it obfuscates the original waveform

+


+

+

(

+

y = {

+

    var x = {

+

Dwalk(

+

Dstutter(Dwhite(1, 10), Dwhite(5, 50)),

+

0.01,

+

lo: -1,

+

hi: 1

+

) } ! 2;

+

LeakDC.ar(Duty.ar(SampleDur.ir, 0, x)) * 0.5

+

}.play

+

)

+


+

+

y.release

+


+

+


+

+

(

+

y = {

+

var x = {

+

Dwalk(

+

Dstutter(Dwhite(3, 10), Drand((1..200), inf)),

+

Dstutter(Dstutter(5, Dwhite(1, 30)), Dseq((1..70) ** 2, inf) / 30000),

+

lo: -1,

+

hi: 1

+

) } ! 2;

+

LeakDC.ar(Duty.ar(SampleDur.ir, 0, x)) * 0.3

+

}.play

+

)

+


+

+

y.release

+


+

+


+

+

// glitchy

+


+

+

(

+

y = {

+

var x = {

+

Dwalk(

+

Dstutter(Dwhite(3, 10), Drand((1..200), inf)),

+

Dstutter(Dstutter(5, Dwhite(1, 30)), Dseq((1..70) ** 2, inf) / 30000),

+

lo: SinOsc.ar(0.1).range(-1, 0),

+

hi: Dstutter(

+

Drand([

+

Dstutter(Dwhite(10, 15), Drand([Dseq((200..1)), Dseq((150..1))], inf)),

+

Dwhite(5000, 40000, 1)

+

], inf),

+

Dseq([0, 1], inf)

+

)

+

) } ! 2;

+

LeakDC.ar(Duty.ar(SampleDur.ir, 0, x)) * 0.3

+

}.play

+

)

+


+

+

y.release

+


+

+


+
Ex.3) Smooth concatenation of adjacent wavesets

+


+
// See ZeroXBufRd help, Ex. 9

+


+
+

+

Ex.4) Smooth concatenation of adjacent segments restricted by turning points resp. local minima or maxima

+


+
// See ZeroXBufRd help, Ex. 10

+


+

+


+

+ + diff --git a/Help/Event patterns and Functions.html b/Help/Event patterns and Functions.html new file mode 100755 index 0000000..a52aea3 --- /dev/null +++ b/Help/Event patterns and Functions.html @@ -0,0 +1,338 @@ + + + + + + + + + + + +

Event patterns and Functions using event patterns with functional elements

+


+

Part of: miSCellaneous

+


+

See also: PLx suite, VarGui, VarGui shortcut builds, HS with VarGui

+


+


+

This is a tutorial file about event patterns with functional code. Features concerning scope are regarded along a line from Functions to Streams, Streams of Events and EventStreamPlayers. These general characteristics allow to generate a parametrized family of EventStreamPlayers from a single event pattern definition and are used as a base for controlling EventStreamPlayers with VarGui. For a convenience way to generate Patterns with environment-dependent reference see PLx suite.

+


+

Functions in Environments

+


+

Environments are a standard way to separate namespaces in SuperCollider. Functions may include environmental variables and then dynamic scope comes into play: variables get the value from the context in which the Function is evaluated. Anyway a specific environment can be linked with the Function, the latter will hold for Streams polled from Patterns with certain functional code    it's just the environment in which the stream has been generated from the pattern.

+


+

Take a simple Function with one argument and assign it to the interpreter variable f:

+


+

f = { |x| x*2 + ~a };

+


+

// equivalent:

+


+

f = { |x| x*2 + currentEnvironment[\a] };

+


+

// now set the variable in the Environment (context) in which to evaluate the Function later on

+


+

~a = 1;

+


+

// equivalent:

+

// currentEnvironment[\a] = 1;

+


+

// not surprising the result of the following operation:

+


+

f.value(1); 

+


+

// or shorter: 

+

// f.(1)

+


+

-> 3

+


+

// now evaluation in new environment, value taken from there:

+


+

(

+

e = Environment[\a -> 5];

+

e.use { f.value(1) };

+

)

+


+

-> 7

+


+

// with inEnvir a new Function is bound to a specific Environment

+


+

(

+

g = f.inEnvir(e);

+

g.(1);

+

)

+


+

-> 7

+


+


+

f.(1);

+


+

-> 3

+


+


+

So either by evaluating the original Function in different Environments or, equivalently, by deriving new Functions from the original attached to different Environments we create a parametrized family of Functions. The Function's basic behaviour is pretty simple (x*2), in dependance of the context it is just varied (a linear offset is added). With more environmental variables a much greater complexity could be introduced, though possibly obscuring the basic behaviour.

+


+

This priciple can now easily be extended to Streams, Streams of Events and EventStreamPlayers. We define the basic behaviour of an event stream with an event pattern (Pbind), then enrich the event pattern with functional code and can have a huge variety of EventStreamPlayers in different Environments with different parameter sets – all playable and modifiable in realtime (in parallel or independentely) and all polled from one single event pattern definition!  Step by step:

+


+


+

Streams in Environments

+


+

What happens in the case of a Stream that will repeatedly evaluate a Function?

+


+

// the Pfunc just describes what a Stream, polled from it, should do (evaluate a Function by the method next)

+

// the Pfunc itself is not bound to a specific Environment

+


+

p = Pfunc { ~a };

+


+

// by making a Stream an Environment (the current) is attached

+


+

(

+

q = p.asStream;

+

~a = 1;

+

q.next;

+

)

+


+

-> 1

+


+

// next value of (Func)Stream demanded in new Environment will be taken from old defining context 

+


+

(

+

~a = 2;

+

e = Environment[\a -> 5];

+

e.use { q.next };

+

)

+


+

-> 2

+


+


+

// vice versa Stream defined in new Environment takes next value from there 

+

// also if evaluated in other Environment

+


+

(

+

r = e.use { p.asStream };

+

r.next;

+

)

+


+


+

-> 5

+


+


+

Indeed, Streams derived from the simple Pfunc don't do much here, they just reflect values in the Environment in which they are defined. As with Functions in the first example Pfuncs can be combined with other Patterns to modify their behaviour. So a family of related Streams (common attributes) can be polled from a single pattern.

+


+


+

// try above with new pattern 

+

// here the common attributes are: period length = 2, magnitude relation of items = 1:10) 

+


+

p = Pseq([1,10], inf) * Pfunc { ~a };

+


+


+

An even more generalized operation is the use of Pcollect, which defines the following Stream behaviour: Output of the source stream will be taken as input by the collecting stream, as with Pfunc the Function defined in Pcollect will live in the Environment in which the Stream has been polled from the Pattern. The above could also be written like this:

+


+


+

p = Pcollect({ |x| x * ~a }, Pseq([1,10], inf));

+


+

p = Pseq([1,10], inf).collect({ |x| x * ~a });

+


+

p = Pseq([1,10], inf).collect(_*~a); // short form (partial application), don't overrely on it in more complicated cases

+


+


+

There is another Pattern engaging Functions, thus allowing to benefit from environment-dependance: Plazy. it evaluates a Function that returns a Pattern to be embedded in a stream. For repeated embedding the Plazy can be wrapped into a Pn. If a generated Pattern repeats infinitely, there will not be any further evaluation. So when using Plazy have a close look at the inner Pattern's repeat arg.

+


+


+

// with each evaluation of Plazy's Function a new random sequence of length = 2 will be generated and repeated 3 times

+

// maximum range of the whole sequence is controlled by the environmental variable ~a

+


+

(

+

p = Pn(Plazy { Pseq({ rand2(~a) } ! 2, 3) });

+

q = p.asStream;

+

~a = 3;

+

q.nextN(60);

+

)

+


+

// instead of using an Environment we can use an instance of its subclass Event which has handy syntax. 

+

// new empty Event:  

+


+

();

+


+

// new Event with var ~a set, range enlarged:  

+


+

(

+

e = (a: 10);

+

r = e.use { p.asStream };

+

r.nextN(60);

+

)

+


+

// compare, both streams are demanded values in same Environment, but they are attached to different ones

+


+

q.nextN(60);

+


+


+

This example was still similar to the ones before, basically a loop modified by a factor. But imagine that a Plazy could give out arbitrary new Patterns, the choice itself could depend on an environment-dependant parameter.

+


+


+

Event streams in Environments

+


+

One step closer to the sound – let's define an event pattern with single patterns that contain functional code. Again the pattern itself is neutral concerning the relation to Environments ...

+


+

(

+

p = Pbind(

+

\dur, Pfunc { ~a },

+

\midinote, Pfunc { ~b }

+

);

+

)

+


+

// ... but the stream is not neutral. It's bound to the current environment.

+


+

(

+

q = p.asStream;

+


+

~a = 0.5;

+

~b = 60;

+

)

+


+

// define a new Environment – an Event, as syntax is handy.

+


+

e = (a: 1, b: 70);

+


+

// So we have two surrounding Events as variable spaces, in which Streams of Events can live.

+

// Don't be confused by that, the stream-generated Events are a different story.

+


+

// now check a next element of the event stream in the new environment (event). 

+

// The method next must itself be passed an event -

+

// the result stems from the former current event, in which the event stream was generated

+


+

e.use { q.next(()) };

+


+

-> ( 'dur': 0.5, 'midinote': 60 )

+


+

// double check - also the second event stream belongs to its defining context

+


+

(

+

e.use { r = p.asStream; };

+

r.next(());

+

)

+


+

-> ( 'dur': 1, 'midinote': 70 )

+


+


+

EventStreamPlayers in Environments

+


+


+

Not much will change by the last transition, EventStreamPlayers are also attached to environments.

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

(

+

p = Pbind(

+

\dur, Pfunc { ~a },

+

\midinote, Pfunc { ~b }

+

);

+

)

+


+

// define two Environments (Events) with EventStreamPlayers attached to it

+


+

(

+

e = (a: 0.2, b: 60);

+

f = (a: 0.4, b: 67);

+


+

e.use { q = p.asEventStreamPlayer };

+

f.use { r = p.asEventStreamPlayer };

+

)

+


+


+

// play the players with different init data in sync

+


+

(

+

q.play(quant: 0.2);

+

r.play(quant: 0.2);

+

)

+


+


+

// now change envir variables while playing

+

// the stream will take new values at the next evaluation

+


+

f.b = 65;

+


+

e.b = 62;

+


+

(

+

q.stop;

+

r.stop;

+

)

+


+


+

The VarGui interface can be used to control running EventStreamPlayers in that way. They may be passed directly, then their attached Environments will be taken for variable setting: 

+


+


+

// start playing from gui

+


+

(

+

v = VarGui([[

+

\a, [0.2, 0.8, \lin, 0.2, 0.2],

+

\b, [48, 80, \lin, 1, 58]

+

],[

+

\a, [0.2, 0.8, \lin, 0.2, 0.4],

+

\b, [48, 80, \lin, 1, 67]

+

]],

+

stream: [q, r], quant: 0.2

+

).gui;

+

)

+


+

// so you could still set from outside

+

+

f.b = 65;

+

+

+

But also the event pattern can be passed directly (recommended). Then new separate Environments will be generated automatically, 

+

changing envir variables by accident is very unlikely.

+


+


+

(

+

v = VarGui({ [

+

\a, [0.2, 0.8, \lin, 0.2, 0.2 * (4.rand + 1) ],

+

\b, [48, 80, \lin, 1, 48.rrand(80) ]

+

] } ! 10, 

+

stream: p ! 10, quant: 0.2

+

).gui(tryColumnNum: 2);

+

)

+


+

// however, environments are accessible if necessary

+

+

v.envirs;

+


+

 

+


+


+


+


+


+ + diff --git a/Help/Event patterns and LFOs.html b/Help/Event patterns and LFOs.html new file mode 100644 index 0000000..60ccda8 --- /dev/null +++ b/Help/Event patterns and LFOs.html @@ -0,0 +1,582 @@ + + + + + + + + + + + +

Event patterns and LFOs summary of some LFO control strategies for event patterns 

+


+

Part of: miSCellaneous

+


+

See also: Working with HS and HSpar, VarGui, VarGui shortcut builds, HS with VarGui

+


+


+

Basic distinction: synths generated by an event pattern can be controlled directly by a LFO (synths wired or mapped to control buses) or on a per-event base. The latter can be achieved by language-only strategies or with help of control synths. For the sake of clarity and comparison most examples use the default instrument and pitch as control parameter. 

+


+


+

1.) Control by new values per event

+


+

Example 1a:   Functions of time

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

(

+

// LFO defined as Function

+


+

f = { |x| (x * 2).sin * 3 + (x * 0.3).sin }; 

+


+

p = Pbind( 

+

    \dev, Ptime().collect(f), // current time passed to function

+

    \midinote, Pkey(\dev) + 60 + [0, 4], 

+

    \amp, 0.05, 

+

    \dur, 0.15 

+

).play; 

+

)

+


+

p.stop;

+


+


+

////////////////////////// 

+


+

// example with GUI

+


+


+

(

+

// parametric control function

+


+

f = { |x| (x * ~a[0]).sin * ~b[0] + ((x * ~a[1]).sin * ~b[1]) }; 

+


+

p = Pbind( 

+

    \dev, Ptime().collect(f), 

+

    \midinote, Pkey(\dev) + 60 + [0, 4], 

+

    \amp, 0.05,

+

    \dur, 0.15 

+

); 

+


+

v = VarGui([

+

\a, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2,

+

\b, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2],

+

stream: p

+

).gui

+

)

+


+

// add a hook that re-plots control function after every slider change

+


+

(

+

u = Plotter(bounds: Rect(200, 200, 700, 500));

+


+

// evaluation points

+


+

x = (0, 0.1..20);

+


+

// EventStreamPlayer is run in separate Environment, 

+

// function values have to be polled from there,

+

// evaluate function initially and add it as mouseUp slider action

+


+

g = { u.value_(f.inEnvir(v.envirs.first).(x)).refresh };

+


+

g.();

+


+

v.addSliderAction(g);

+

)

+


+


+


+

////////////////////////// 

+


+

// GUI example with two LFOs

+


+


+

(

+

// parametric control function

+


+

f = { |x| (x * ~a[0]).sin * ~b[0] + ((x * ~a[1]).sin * ~b[1]) }; 

+


+

p = Pbind( 

+

    \dev, Ptime().collect(f), 

+

    \midinote, Pkey(\dev) + Pfunc { ~add }, 

+

    \amp, 0.05,

+

    \dur, 0.15 

+

); 

+


+

// add some harmonies

+


+

q = Padd(\midinote, [-12, -7, 0, 5], p);

+

r = Padd(\midinote, [0, 2.5], p);

+


+

v = VarGui([70, 55].collect { |x| [

+

\a, { [0, 5, \lin, 0.01, rrand(0.0, 5)] } ! 2,

+

\b, { [0, 5, \lin, 0.01, rrand(0.0, 5)] } ! 2,

+

\add, [x-2, x+2, \lin, 0.01, x]]

+

},

+

stream: [r,q], quant: 0.15

+

).gui

+

)

+


+

// add a hook that re-plots control function after every slider change

+


+

(

+

u = Plotter(bounds: Rect(200, 200, 700, 500));

+


+

// evaluation points

+


+

x = (0, 0.1..20);

+


+

// EventStreamPlayer is run in separate Environment, 

+

// function values have to be polled from there,

+

// evaluate function initially and add it as mouseUp slider action

+


+

g = { u.value_(v.envirs.collect { |e| f.inEnvir(e).(x) }).refresh };

+


+


+

g.();

+


+

v.addSliderAction(g);

+

)

+


+


+


+

Example 1b:   Envelopes

+


+


+

(

+

e = Env([0, 10, 0], [2,1], \sin);

+

e.plot;

+

)

+


+

// currently (SC 3.4.4) this will play the envelope once 

+

// and then continue with the end value

+


+

(

+

p = Pbind( 

+

    \dev, e, 

+

    \midinote, Pkey(\dev) + 60 + [0, 4], 

+

    \amp, 0.05, 

+

    \dur, 0.15 

+

).play; 

+

)

+


+

p.stop;

+


+


+

// you can use modulo calculus for looping

+


+

(

+

p = Pbind( 

+

    \dev, Ptime().collect { |t| e[t % (e.times.sum)] }, 

+

    \midinote, Pkey(\dev) + 60 + [0, 4], 

+

    \amp, 0.05, 

+

    \dur, 0.15 

+

).play; 

+

)

+


+

p.stop;

+


+


+

////////////////////////// 

+


+

// example with GUI

+


+

(

+

n = 5;

+


+

p = Pbind( 

+

    \dev, Ptime().collect { |t| e[t % (e.times.sum)] }, 

+

    \midinote, Pkey(\dev) + 60 + [0, 4], 

+

    \amp, 0.05, 

+

    \dur, 0.15 

+

);

+


+

v = VarGui([

+

\levels, { [-15, 15, \lin, 0.01, rrand(-15.0, 15)] } ! n,

+

\times, { [0.5, 3, \lin, 0.01, rrand(0.5, 3)] } ! (n-1),

+

\curves, { [0, 7, \lin, 1, rrand(0, 7)] } ! (n-1),

+

\stretch, [0.1, 10, \lin, 0.01, 1]],

+

stream: p

+

).gui;

+

)

+


+

// add a hook that plots envelope after every slider change

+


+

(

+

u = Plotter(bounds: Rect(200, 200, 700, 500));

+


+

g = { 

+

v.envirs.first.use { e = Env(~levels, ~times * ~stretch, ~curves) };

+

u.value_(e.asSignal).refresh; 

+

};

+


+

g.();

+


+

v.addSliderAction(g);

+

)

+


+


+


+


+

Example 1c:   SharedOut / shared memory

+


+


+

SharedOut will be deprecated in future releases of SC and replaced by a shared memory mechanism (Tim Blechmann). 

+

SharedOut is at least included in version 3.4.4.

+


+


+

// internal server needed for all examples with SharedOut,

+

// shared memory also works with local server

+


+

(

+

s = Server.internal;

+

Server.default = s;

+

s.boot;

+

)

+


+

// start LFO

+


+

x = { SharedOut.kr(0, LFDNoise3.kr(0.3, 20, 70)) }.play;

+


+


+

// play event pattern

+


+

(

+

p = Pbind(

+

\dur, 0.15, 

+

\midinote, Pfunc { s.getSharedControl(0) }

+

).play;

+

)

+

+

(

+

p.stop;

+

x.free;

+

)

+


+

////////////////////////// 

+


+

// example with GUI

+


+

// ATTENTION: this version of the example works with SC versions > 3.4.4

+

// in which SharedOut is still supported and which already include 

+

// the fix of a minor bug which blocked adding of SynthDefs.

+

// See below for an equivalent example with shared memory.

+


+

// If you're using a version <= 3.4.4 you can fix it by yourself, 

+

// adding this method to SharedOut and recompile:

+

// *numFixedArgs { ^1 }

+


+

// ... or take the example version below the following version

+


+

(

+

s = Server.internal;

+

Server.default = s;

+

s.boot;

+

)

+


+

(

+

SynthDef(\control, { |midiCenter, dev, devFreq| SharedOut.kr(0, LFDNoise3.kr(devFreq, dev, midiCenter)) }).add;

+


+

p = Pbind(

+

\dur, 0.15, 

+

\midinote, Pfunc { s.getSharedControl(0) } + [0, 4]

+

);

+


+

// start control synth before stream

+

+

v = VarGui(synthCtr: [

+

\midiCenter, [50, 80, \lin, 0.01, 70],

+

\dev, [0, 20, \lin, 0.01, 20],

+

\devFreq, [0, 3, \lin, 0.01, 0.5]],

+

synth: \control, stream: p

+

).gui(playerPriority: \synth);

+

)

+


+


+

// this version of the example works also with SC versions <= 3.4.4

+

  

+

(

+

SynthDef(\control, { |midiCenter, dev, devFreq| SharedOut.kr(0, LFDNoise3.kr(devFreq, dev, midiCenter)) }).send(s);

+

)

+


+

(

+

x = Synth(\control).register;  

+


+

// or start paused:

+

// x = Synth.newPaused(\control).register; 

+


+

p = Pbind(

+

\dur, 0.15, 

+

\midinote, Pfunc { s.getSharedControl(0) } + [0, 4]

+

);

+

)

+


+

(

+

v = VarGui(synthCtr: [

+

\midiCenter, [50, 80, \lin, 0.01, 70],

+

\dev, [0, 20, \lin, 0.01, 20],

+

\devFreq, [0, 3, \lin, 0.01, 0.5]],

+

synth: x, stream: p

+

).gui(playerPriority: \synth);

+

)

+


+


+


+

// shared memory example, SC version >= 3.5

+

// start LFO

+


+

c = Bus.control(s, 1);

+


+

x = { Out.kr(c, LFDNoise3.kr(0.3, 20, 70)) }.play;

+


+


+

// play event pattern

+


+

(

+

p = Pbind(

+

\dur, 0.15, 

+

\midinote, Pfunc { c.getSynchronous }

+

).play;

+

)

+

       

+

(

+

p.stop;

+

x.free;

+

)

+


+

Example 1d:   HS / PHS and related

+


+

With HS server values can be used in PHS objects which mimic event patterns. This is achieved by an OSC demand and respond mechanism which introduces a small amount of additional latency. It works with local and internal server, see Working with HS and HSpar for further details. Using the HS family with VarGui is discussed in HS with VarGui. 

+

The HS / PHS approach would especially be of interest if control behaviour could more easily be defined by server means than in SC lang (e.g. specific and / or nested UGens) but data should also be further manipulated in the language (e.g. for some kind of combinatorial use such as harmonic or polyphonic calculations).

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// a HS contains the control synth definition but will also hold playing Synth instances

+


+

h = HS(s, { |midiCenter = 70, dev = 20, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) });

+


+


+

// a PHS refers to a HS and, when played, takes control over the control synth

+


+

p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) + [0, 4] ]).play;

+


+


+

// stop player and control synth 

+


+

p.free; 

+


+


+

Methods of linked playing / stopping and resuming (stream + help synth) are supported as well as reference to an already playing HS by PHSuse. Various kinds of control synth control and synth value reference are possible with two or more help synths (see HSpar, PHSpar and PHSparUse). 

+


+


+

Example 1e:   audio synths reading from a control bus, discretized

+


+

Derived from (2a), disadvantage: SynthDef must be adapted to control needs beforehand.

+


+

(

+

c = Bus.control(s,1);

+

d = Bus.control(s,1);

+


+

SynthDef(\perc_1e, {|amp = 0.1, bus, att = 0.01, rel = 1|

+

var in = In.kr(bus, 1);

+

Out.ar(0, (SinOsc.ar(Latch.kr(in, in).midicps, 0, amp) * 

+

EnvGen.ar(Env.perc(att, rel), doneAction: 2))!2)

+

}).add;

+

)

+


+

(

+

x = { Out.kr(c, LFDNoise3.kr(1, 5, 75)) }.play;

+

y = { Out.kr(d, LFDNoise3.kr(1, 5, 65)) }.play;

+


+

p = Pbind(

+

\instrument, \perc_1e,

+

\dur, 0.3,

+

\amp, 0.07,

+

\bus, [c, d]

+

).play;

+

)

+


+

(

+

p.stop;

+

x.free;

+

y.free;

+

)

+


+


+

Example 1f:   Pseg

+


+

Pseg can work like a kind of meta pattern for LFO-like control, patterns are used to pass envelope data.

+


+

(

+

p = Pbind(

+

    \note, Pseg(Pseq([0, Pwhite(3, 10, 1)], inf), Pseq([1, 3],inf), 'lin'),

+

    \dur, 0.2

+

).play;

+

)

+


+

p.stop;

+


+


+

2.) Continuous LFO control

+


+


+

Example 2a:   audio synths reading from a control bus

+


+

The disadvantage of this strategy (compared to 2b) is that synth definitions have to be written especially for control purposes. It must be known in advance which parameters should be controlled by another synth.

+


+

(

+

c = Bus.control(s,1);

+


+

SynthDef(\perc_2a, {|amp = 0.1, bus = 0, att = 0.01, rel = 0.25|

+

Out.ar(0, (SinOsc.ar(In.kr(bus, 1).midicps, 0, amp) * 

+

EnvGen.ar(Env.perc(att, rel), doneAction: 2))!2)

+

}).add;

+

)

+


+

(

+

x = { Out.kr(c, LFDNoise3.kr(3, 10, 65)) }.play;

+


+

p = Pbind(

+

\instrument, \perc_2a,

+

\dur, 0.3,

+

\bus, c

+

).play;

+

)

+


+

(

+

p.stop;

+

x.free;

+

)

+


+


+


+

Example 2b:   audio synths mapped to a control bus

+


+

In general more practical than (2a), though by SC vs 3.4.4 reserved keys (e.g. \freq) can't be mapped to a bus, under these circumstances args would have to be renamed.

+


+

(

+

c = Bus.control(s,1);

+

d = Bus.control(s,1);

+


+

SynthDef(\perc_2b, {|amp = 0.1, midi = 60, att = 0.01, rel = 0.25|

+

Out.ar(0, (SinOsc.ar(midi.midicps, 0, amp) * 

+

EnvGen.ar(Env.perc(att, rel), doneAction: 2))!2)

+

}).add;

+

)

+


+

(

+

x = { Out.kr(c, LFDNoise3.kr(1, 5, 75)) }.play;

+

y = { Out.kr(d, LFDNoise3.kr(1, 5, 65)) }.play;

+


+

p = Pbind(

+

\instrument, \perc_2b,

+

\dur, 0.3,

+

\midi, [c, d].collect(_.asMap)

+

).play;

+

)

+


+

(

+

p.stop;

+

x.free;

+

y.free;

+

)

+


+


+

////////////////////////// 

+


+

// example with GUI

+


+


+

(

+

c = Bus.control(s,1);

+

d = Bus.control(s,1);

+


+

SynthDef(\perc_2b, {|amp = 0.1, midi = 60, att = 0.01, rel = 0.25|

+

Out.ar(0, (SinOsc.ar(midi.midicps, 0, amp) * 

+

EnvGen.ar(Env.perc(att, rel), doneAction: 2))!2)

+

}).add;

+


+

SynthDef(\control_2b, { |midiCenter = 70, dev = 20, devFreq = 1, out = 0| 

+

Out.kr(out, LFDNoise3.kr(devFreq, dev, midiCenter)) 

+

}).add;

+

)

+


+

(

+

p = Pbind(

+

\instrument, \perc_2b,

+

\dur, Pfunc { ~dur },

+

// following values will be collections of two elements

+

\amp, Pfunc { ~amp },

+

\att, Pfunc { ~att },

+

\rel, Pfunc { ~rel },

+

\midi, [c, d].collect(_.asMap)

+

);

+


+

// in gui start control synths before stream player !

+


+

v = VarGui([

+

\dur, [0.05, 0.5, \lin, 0.005, 0.2],

+

// setting envir variables to collections of two elements

+

\amp, [0, 0.1, \lin, 0.005, 0.07] ! 2,

+

\att, [0.005, 0.1, \lin, 0.005, 0.01] ! 2,

+

\rel, [0.005, 0.5, \lin, 0.005, 0.1] ! 2

+

],

+

2.collect { |i| 

+

var bus = [c,d][i].index;

+

[\midiCenter, [60, 80, \lin, 0.01, [65, 75][i] ],

+

\dev, [0, 10, \lin, 0.01, 10],

+

\devFreq, [0, 3, \lin, 0.01, 0.5],

+

\out, [bus, bus, \lin, 1, bus]] 

+

}, p, \control_2b ! 2 

+

).gui(sliderPriority: \synth, playerPriority: \synth);

+

)

+


+


+


+ + diff --git a/Help/Event patterns and array args.html b/Help/Event patterns and array args.html new file mode 100644 index 0000000..b703a00 --- /dev/null +++ b/Help/Event patterns and array args.html @@ -0,0 +1,680 @@ + + + + + + + + + + + +

Event patterns and array args setting and passing arrays and envelopes via patterns

+


+

Part of: miSCellaneous

+


+


+

This tutorial covers some use cases of array args, especially passing arrays to synths with patterns.

+


+


+

Examples

+


+

1.) Alternative writing of arrayed args in SynthDefs

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// SynthDef with array of overtone weights,

+

// weights can additionally be influenced with a dampExp arg unequal to 0, 

+

// see examples below.

+


+

(

+

// The standard way to define array args: 

+

// when appearing within the list of args, a literal Array must be used,

+

// shortcut (1..8) generates an Array with Integers from 1 to 8

+


+

SynthDef(\array_1a, { |out = 0, freq = 440, otAmps = #[1,1,1,1,1,1,1,1], dampExp = 0,

+

att = 0.01, rel = 0.6, amp = 0.1, gate = 1, freqLag = 0.02, otLag = 0.02|

+

var sig, env, freqs, amps;

+

freqs = (freq * (1..8)).clip(20, 20000).lag(freqLag);

+

amps = ((otAmps / ((1..8) ** dampExp)).normalizeSum * amp).lag(otLag);

+

sig = SinOsc.ar(freqs, 0, amps);

+

env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2);

+

Out.ar(out, Splay.ar(sig) * env)

+

}).add

+

)

+


+

// Note that SynthDef \array_1a uses the array arg's size (8) at two further places (arrays (1..8)).

+

// As a literal Array, by its nature, requires the explicit writing of its items,

+

// this becomes arkward with larger arrays and/or rewriting the SynthDef with other sizes.

+

// For those reasons the use of NamedControl turns out to be very convenient with array args,

+

// it also gives an easy way of lagging.

+


+


+

(

+

// Alternative to SynthDef \array_1a using NamedControl:

+

// Although a NamedControl can be written at any position within the SynthDef,

+

// it's a useful convention to invent it directly after the args list

+

// by assigning it to a variable of the same name (so code with literal Array args can be reused easily).

+

// With NamedControl the array size can now also be written as a variable,

+

// which allows to define SynthDefs with different array sizes on the fly.

+

// Also note NamedControl's shortcuts aSymbol.kr / aSymbol.kr

+

// which make its use even more comfortable.

+


+

// define size for SynthDef

+

n = 8;

+


+

// define SynthDef

+

// shortcut 1!n (short for 1.dup(n)) generates an Array of size n filled with 1.

+


+

SynthDef(\array_1b, { |out = 0, freq = 440, dampExp = 0,

+

att = 0.01, rel = 0.6, amp = 0.1, gate = 1, freqLag = 0.02, otLag = 0.02|

+

var otAmps = NamedControl.kr(\otAmps, 1!n); // shortcut: otAmps = \otAmps.kr(1!n);

+

var sig, env, freqs, amps;

+

freqs = (freq * (1..n)).clip(20, 20000).lag(freqLag);

+

amps = ((otAmps / ((1..n) ** dampExp)).normalizeSum * amp).lag(otLag);

+

sig = SinOsc.ar(freqs, 0, amps);

+

env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2);

+

Out.ar(out, Splay.ar(sig) * env)

+

}).add

+

)

+


+


+

// As array sizes of SynthDefs are fixed you might want to define for a number

+

// of Integers and name the SynthDefs appropriately.

+

// Here's such a "SynthDef factory":

+

// we get SynthDefs of names \array_1b_1, ..., \array_1b_16 with corresponding array sizes,

+

// see Ex. 3b for a use case.

+


+

(

+

(1..16).do { |n|

+

var name = \array_1b ++ \_ ++ n;

+

SynthDef(name, { |out = 0, freq = 440, dampExp = 0,

+

att = 0.01, rel = 0.6, amp = 0.1, gate = 1, freqLag = 0.02, otLag = 0.02|

+

var otAmps = NamedControl.kr(\otAmps, 1!n); // shortcut: otAmps = \otAmps.kr(1!n);

+

var sig, env, freqs, amps;

+

freqs = (freq * (1..n)).clip(20, 20000).lag(freqLag);

+

amps = ((otAmps / ((1..n) ** dampExp)).normalizeSum * amp).lag(otLag);

+

sig = SinOsc.ar(freqs, 0, amps);

+

env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2);

+

Out.ar(out, Splay.ar(sig) * env)

+

}).add

+

}

+

)

+


+


+


+

2.) Setting array args and array fields of a running synth

+


+


+

Ex. 2a: Setting array args of a running synth

+


+


+

// start Synth, SynthDefs \array_1a, \array_1b and \array_1b_8 are equivalent

+


+

x = Synth(\array_1a, [freq: 300, amp: 0.2])

+


+


+

// only odd partials (clarinet-like)

+


+

x.set(\otAmps, [1, 0, 1, 0, 1, 0, 1, 0])

+


+


+

// emphasize one partial

+


+

x.set(\otAmps, [1, 0, 1, 0, 1, 5, 1, 0])

+


+


+

// only lower ones

+


+

x.set(\otAmps, [1, 1, 1, 1, 0, 0, 0, 0])

+


+


+

// default again

+


+

x.set(\otAmps, [1, 1, 1, 1, 1, 1, 1, 1])

+


+


+


+

// force lower partials with dampExp > 0

+


+

x.set(\dampExp, 1.5)

+


+


+

// force higher partials with dampExp < 0

+


+

x.set(\dampExp, -1.5)

+


+


+

// default again

+


+

x.set(\dampExp, 0)

+


+


+

// release

+


+

x.release

+


+


+

Ex. 2b: Setting array args of a running synth with Pbind of event type \set

+


+


+

// For many use cases Pmono (see 2c) is the most practical solution

+

// as it doesn't require explicit starting of a synth.

+

// However sometimes it is necessary to access the running synth itself,

+

// then a Pbind with event type \set is a good choice.

+


+


+

// start Synth silently

+


+

x = Synth(\array_1a, [freq: 200, amp: 0])

+


+


+

// Pbind of event type set:

+

// args to be set must be listed.

+

// Note that the array arg requires double brackets (syntactic distinction from setting multiple nodes)

+


+

(

+

p = Pbind(

+

\dur, 0.2,

+

\type, \set,

+

\id, x,

+

\args, #[freq, amp, dampExp, otAmps], // must be given explicitely

+

\midinote, Pwhite(45.0, 70), // for midinote freq must be set

+

\amp, 0.3,

+

\dampExp, Pwhite(0, 3),

+

\otAmps, [[2, 1, 3, 2, 1, 2, 2, 1]] // double brackets for array arg !

+

).play

+

)

+


+

(

+

// stop EventStreamPlayer and release Synth

+


+

p.stop;

+

x.release;

+

)

+


+


+


+


+

// start Synth silently

+


+

x = Synth(\array_1a, [freq: 200, amp: 0]);

+


+


+

// Sequencing the array arg:

+

// rising spectral shape, falling fundamental

+


+

(

+

// Array for Pseq, we need an array of doubly bracketed arrays or

+

// ref'd arrays as in Ex. 2c.

+


+

o = [

+

[[1, 1, 0, 0, 0, 0, 0, 0]],

+

[[1, 0, 1, 0, 0, 0, 0, 0]],

+

[[1, 1, 0, 1, 0, 0, 0, 0]],

+

[[1, 0, 1, 0, 1, 0, 0, 0]],

+

[[1, 1, 0, 1, 0, 1, 0, 0]],

+

[[1, 0, 1, 0, 1, 0, 1, 0]],

+

[[1, 0, 0, 1, 0, 1, 0, 1]]

+

];

+


+

p = Pbind(

+

\dur, 0.2,

+

\type, \set,

+

\id, x,

+

\args, #[freq, amp, otAmps], // must be given explicitely

+

\midinote, Pn(Plazy { Pseq((70..50)) / rrand(1, 1.2) }), 

+

\amp, 0.3,

+

\otAmps, Pseq(o, 30) 

+

).play

+

)

+


+

(

+

// stop EventStreamPlayer and release Synth

+


+

p.stop;

+

x.release;

+

)

+


+


+

Ex. 2c: Setting array args of a running synth with Pmono

+


+


+

// Rewriting second example of 2b, shorter with Pmono.

+

// Instead of doubly bracketed arrays you can choose Refs of Arrays also.

+

// Note that this alternative is not valid for a single nested Array as 

+

// in the first example of 2b.

+


+

(

+

o = [

+

`[1, 1, 0, 0, 0, 0, 0, 0],

+

`[1, 0, 1, 0, 0, 0, 0, 0],

+

`[1, 1, 0, 1, 0, 0, 0, 0],

+

`[1, 0, 1, 0, 1, 0, 0, 0],

+

`[1, 1, 0, 1, 0, 1, 0, 0],

+

`[1, 0, 1, 0, 1, 0, 1, 0],

+

`[1, 0, 0, 1, 0, 1, 0, 1]

+

];

+


+

p = Pmono(\array_1a,

+

\dur, 0.2,

+

\midinote, Pn(Plazy { Pseq((70..50)) / rrand(1, 1.2) }), 

+

\amp, 0.3,

+

\otAmps, Pseq(o, 30) 

+

).play

+

)

+


+


+

// stop EventStreamPlayer from Pmono (synth is released also)

+


+

p.stop;

+


+


+


+

Ex. 2d: Setting i-th fields of a running synth

+


+


+

// start Synth

+


+

x = Synth(\array_1a, [freq: 200, amp: 0.3])

+


+


+

// by default otAmps equals [1, 1, 1, 1, 1, 1, 1, 1]

+


+

// Since SC 3.6.x there exists method seti for setting single elements of arrays:

+

// turn off high overtones

+


+

x.seti(\otAmps, 7, 0)

+


+

x.seti(\otAmps, 6, 0)

+


+

x.seti(\otAmps, 5, 0)

+


+


+

x.release;

+


+


+

// With older SC versions you can use the following helper functions to achieve the same.

+

// This requires that the SynthDef has been added in order to have control arg info in SynthDescLib.

+


+

(

+

~getCtlIndex = { |defName, argName, index| 

+

    var x = SynthDescLib.global.at(defName.asSymbol).controls 

+

        .collect({|x| x.name.asSymbol }).indexOf(argName.asSymbol); 

+

    x !? { x + index }; 

+

}; 

+


+

~setiID = { |server, defname, nodeID, key, index, value| 

+

    server.sendMsg(15, nodeID, ~getCtlIndex.(defname, key, index), value); 

+

}; 

+


+


+

~seti = { |node, key, index, value| 

+

    ~setiID.(node.server, node.defName, node.nodeID, key, index, value); 

+

}; 

+

)

+


+


+

// start Synth

+


+

x = Synth(\array_1a, [freq: 200, amp: 0.5])

+


+


+

// turn off high overtones

+


+

~seti.(x, \otAmps, 7, 0)

+


+

~seti.(x, \otAmps, 6, 0)

+


+

~seti.(x, \otAmps, 5, 0)

+


+

x.release;

+


+


+


+

Ex. 2e: Setting i-th fields of a running synth with patterns

+


+


+

// For SC versions before invention of method seti use helper functions from Ex. 2d in Pbind.

+


+

// start Synth

+


+

x = Synth(\array_1a, [freq: 200, amp: 0.3])

+


+


+

// sequence setting of single fields

+

// As this is currently not integrated with Pmono or event type \set

+

// it must be done explicitely. Method 'makeBundle' with server latency arg

+

// ensures that array field setting is done in parallel to

+

// other settings triggered by the event.

+


+

(

+

// continously subtract and add 7 overtones

+


+

p = Pbind(

+

\type, \rest,

+

\dur, 0.2,

+

\otAmp, Pstutter(7, Pseq([0,1], inf)),

+

\amp, 0.3,

+

\i, Pn(Pshuf((1..7))),

+

// use Function ~seti (2d) for SC versions without method seti:

+

\do, Pfunc { |e| s.makeBundle(s.latency, { ~seti.(x, \otAmps, e.i, e.otAmp) }) }

+

// for newer SC versions with method seti you can use this line instead:

+

// \do, Pfunc { |e| s.makeBundle(s.latency, { x.seti(\otAmps, e.i, e.otAmp) }) } 

+

   ).trace.play

+

)

+


+

// stop player and synth

+


+

(

+

p.stop;

+

x.release;

+

)

+


+


+

// This approach can be extended by setting more than one field per event.

+

// In some cases this might be a reasonable alternative to setting whole arrays

+

// of running synths (2b, 2c), which requires more OSC traffic.

+


+

// start Synth from Ex. 1 with 16 overtones

+


+

x = Synth(\array_1b_16, [freq: 100, amp: 0.3])

+


+

(

+

// lists for bookkeeping of substracted and added overtones

+

a = (1..16).asList;

+

b = List[];

+


+

// Function to shovel indices from list to list

+


+

f = { |list1, list2|

+

var x = list1.choose;

+

list1.remove(x);

+

list2.add(x);

+

x

+

};

+


+

// continously subtract and add 5 x 3 overtones

+


+

p = Pbind(

+

\type, \rest,

+

\dur, 0.2,

+

\otAmp, Pstutter(5, Pseq([0,1], inf)),

+

\amp, 0.3,

+

// shoveling indices from a to b and back, outputting sorted index tripels

+

\i, Pseq([

+

Pfinval(5, Pclump(3, Pfunc { f.(a, b) } )),

+

Pfinval(5, Pclump(3, Pfunc { f.(b, a) } ))

+

], inf).collect(_.sort),

+

// use Function ~seti (2d) for SC versions without method seti:

+

\do, Pfunc { |e| s.makeBundle(s.latency, { e.i.do { |j| ~seti.(x, \otAmps, j, e.otAmp) } }) }   

+

// for newer SC versions with method seti you can use this line instead:

+

// \do, Pfunc { |e| s.makeBundle(s.latency, { e.i.do { |j| x.seti(\otAmps, j, e.otAmp) } }) } 

+

).trace.play

+

)

+


+

// stop player and synth

+


+

(

+

p.stop;

+

x.release;

+

)

+


+


+


+

Ex. 2f: Alternatives with demand ugens

+


+

// Besides from passing arrays, array sequencing can be 

+

// done within synths by demand ugens.

+

// This is saving OSC bandwidth, especially with large arrays and short durations,

+

// for more complicated sequencing tasks coding might be harder than with patterns.

+


+

(

+

// array sequencing within SynthDef

+


+

q = [

+

[1, 1, 0, 0, 0, 0, 0, 0],

+

[1, 0, 1, 0, 0, 0, 0, 0],

+

[1, 1, 0, 1, 0, 0, 0, 0],

+

[1, 0, 1, 0, 1, 0, 0, 0],

+

[1, 1, 0, 1, 0, 1, 0, 0],

+

[1, 0, 1, 0, 1, 0, 1, 0],

+

[1, 0, 0, 1, 0, 1, 0, 1]

+

];

+


+

// duration will have to be passed with a key unequal to reserved keyword \dur

+


+

SynthDef(\array_1c, { |out = 0, freq = 440, dampExp = 0, duration = 0.2,

+

att = 0.01, rel = 0.6, amp = 0.1, gate = 1, freqLag = 0.02, otLag = 0.02|

+

var otAmps, sig, env, freqs, amps, arr;

+

otAmps = Demand.kr(Impulse.kr(1 / duration), 0, Dseq(q, inf));

+

freqs = (freq * (1..8)).clip(20, 20000).lag(freqLag);

+

amps = ((otAmps / ((1..8) ** dampExp)).normalizeSum * amp).lag(otLag);

+

sig = SinOsc.ar(freqs, 0, amps);

+

env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2);

+

Out.ar(out, Splay.ar(sig) * env)

+

}).add;

+


+

// SynthDef for complete server-side sequencing 

+

// takes midiseq as array arg

+


+

SynthDef(\array_1d, { |out = 0, dampExp = 0, duration = 0.2, divLo = 1, divHi = 1.2,

+

att = 0.01, rel = 0.6, amp = 0.3, gate = 1, freqLag = 0.02, otLag = 0.02|

+

var midiseq = \midiseq.kr((70..50));

+

var trig, otAmps, sig, env, freq, freqs, amps, arr;

+

trig = Impulse.kr(1 / duration);

+

otAmps = Demand.kr(trig, 0, Dseq(q, inf));

+

freq = Demand.kr(trig, 0, Dseq(midiseq, inf) / 

+

Dstutter(midiseq.size, Dwhite(divLo, divHi))).midicps;

+

freqs = (freq * (1..8)).clip(20, 20000).lag(freqLag);

+

amps = ((otAmps / ((1..8) ** dampExp)).normalizeSum * amp).lag(otLag);

+

sig = SinOsc.ar(freqs, 0, amps);

+

env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2);

+

Out.ar(out, Splay.ar(sig) * env)

+

}).add

+

)

+


+

(

+

// equivalent to Ex 2c

+

// still using a pattern for non-array sequencing

+


+

p = Pmono(\array_1c,

+

\dur, 0.2,

+

\duration, Pkey(\dur),

+

\midinote, Pn(Plazy { Pseq((70..50)) / rrand(1, 1.2) }), 

+

\amp, 0.3

+

).play

+

)

+


+

p.stop;

+


+


+

// equivalent, now all done by synth

+


+

x = Synth(\array_1d)

+


+


+

// set midi sequence 

+


+

x.set(\midiseq, (50..70))

+


+

x.set(\midiseq, (70..50))

+


+

x.set(\midiseq, (70..50).scramble)

+


+

x.release

+


+


+


+

3.) Sequencing synths with array args by Pbind

+


+


+

Ex. 3a: Sequencing synths of same definition with array args by Pbind

+


+


+

// Using a Pbind with array args is straightforward,

+

// it's just important to remember that arrays must be in 

+

// double brackets (or wrapped into Refs). 

+

// Written explicitely it looks a bit odd as you end up with 

+

// three brackets at begin and end:

+

// Pseq([[[1, 1, 0, 0, 0, 0, 0, 0]], ... , [[1, 0, 0, 1, 0, 1, 0, 1]]], 100)

+


+


+

(

+

// Array o from definitions in Ex. 2b / 2c.

+


+

o = [

+

[[1, 1, 0, 0, 0, 0, 0, 0]],

+

[[1, 0, 1, 0, 0, 0, 0, 0]],

+

[[1, 1, 0, 1, 0, 0, 0, 0]],

+

[[1, 0, 1, 0, 1, 0, 0, 0]],

+

[[1, 1, 0, 1, 0, 1, 0, 0]],

+

[[1, 0, 1, 0, 1, 0, 1, 0]],

+

[[1, 0, 0, 1, 0, 1, 0, 1]]

+

];

+


+

// generating multiple nodes per event is also no problem,

+

// arrays from o are sent to both nodes here:

+


+

p = Pbind(

+

\instrument, \array_1a, 

+

\dur, 0.2,

+

\amp, 0.3,

+

\stepsPerOctave, 5,

+

\octave, 4,

+

\note, Pstutter(7, Pn(Pshuf((0..4)))) + [0, -4], 

+

\otAmps, Pseq(o, 100)

+

).trace.play

+

)

+


+

p.stop;

+


+


+

Ex. 3b: Sequencing synths with different array arg sizes by Pbind

+


+


+

// By using the Array o in the last examples we did a kind of zeropadding:

+

// setting unused elements to zero.

+

// When altering one running synth (as with event type \set or Pmono)

+

// one can only save OSC messages if less than all fields are changed, see Ex. 2e.

+


+

// When continuously generating new synths – as with 'normal' Pbind of type \note –

+

// there is another alternative: using SynthDefs of dedicated array arg sizes per event.

+

// This is the use case of a "SynthDef factory" as described in Ex. 1

+


+


+

(

+

// needs SynthDefs from "factory" in Ex. 1

+


+

q = [

+

[[1, 1, 1]],

+

[[1, 1, 1, 1, 1]],

+

[[1, 0, 1, 0, 1, 0, 1, 0, 1]],

+

[[1, 1, 0, 1, 0, 1, 0, 1, 0, 1]]

+

];

+


+

p = Pbind(

+

\otAmps, Pn(Pshuf(q), 100),

+

\size, Pkey(\otAmps).collect { |x| x[0].size.asSymbol },

+

\instrument, Pkey(\size).collect { |x| \array_1b_ ++ x }, // SynthDef depends on chosen otAmp array

+

\dur, 0.2,

+

\amp, 0.3,

+

\stepsPerOctave, 7,

+

\octave, 4,

+

\note, Pstutter(3, Pn(Pshuf((0..4)))) + Pn(Pshuf([[0, 2], [0, 4, 8], [0, -3, -5, -8]])) 

+

).trace.play

+

)

+


+

p.stop;

+


+


+


+

Ex. 4: Sequencing synths with envelope array args by Pbind 

+


+


+

// Env objects have a representation in a special Array format –

+

// which you can get with anEnv.asArray – the task of passing Env data to synths can thus be

+

// reduced to the task of passing Arrays in this special format.

+

// Nevertheless direct passing of Envs is possible also.

+


+

(

+

// NamedControl is recommended in that case as a literal Array of special format would be impractical.

+

// Define a SynthDef with maximum envelope size you expect to pass

+


+

SynthDef(\envArray_1, { |out = 0, freq = 440, amp = 0.1, timeScale = 1, gate = 1|

+

var sig, env, envArray, envSig;

+

envArray = Env([0, 1, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0]).asArray; // works also without '.asArray'

+

env = \env.kr(envArray);  // shortcut for NamedControl.kr(\env, envArray)

+

envSig = EnvGen.kr(env, gate, timeScale: timeScale, doneAction: 2); 

+

sig = Saw.ar([freq, freq * 2.01], amp); 

+

Out.ar(out, sig * envSig)

+

}).add

+

)

+


+

// Pbind uses event type \on to avoid setting gate to 0 and receiving messages "node not found",

+

// synths are ended by envelopes anyway.

+


+


+

( 

+

p = Pbind( 

+

\type, Pshuf([\on, \on, \rest], inf),

+

\instrument, \envArray_1, 

+

\dur, Pn(Pshuf([1, 1/2, 1/2]), 30), 

+

\midinote, Pn(Pshuf((40..80))) + Pn(Pshuf([[0.5, 11.5], [0, 4], [0, -7], [-0.5, -9.5]])),

+

+

// envData contains env types, determined by levels and times.

+

// Times are only relations, within the synthdef they are scaled by the timeScale arg.

+

\envData, Pn(Pshuf([ 

+

[[0, 1, 0], [1, 1]], 

+

[[0, 1, 1/4, 1, 0], [1, 1, 1, 1]], 

+

[[0, 1, 1/4, 1, 1/4, 1, 0], [1, 1, 1, 1, 1, 1]]

+

])),

+

+

// *x splits the envData array into levels and times, Env is converted in to an Array automatically

+

\env, Pkey(\envData).collect { |x| Env(*x) }, 

+

+

// when wanting to pass the Array explicitely it would require

+

// wrapping into an additional Array which is necessary for passing:

+

// \env, Pkey(\envData).collect { |x| [Env(*x).asArray] }, 

+

+

\timeScale, Pfunc { |e| e.dur / e.envData[1].sum } 

+

).trace.play; 

+

) 

+


+

p.stop;

+


+


+


+ + diff --git a/Help/EventShortcuts.html b/Help/EventShortcuts.html new file mode 100644 index 0000000..b9d982a --- /dev/null +++ b/Help/EventShortcuts.html @@ -0,0 +1,322 @@ + + + + + + + + + + + +

EventShortcuts holds default and user-defined dictionaries of shortcuts for events and event patterns 

+


+

Part of: miSCellaneous

+


+

Inherits from: Object

+


+

Container for dictionaries of shortcuts for the event framework, which can be defined by the user. Shortcuts might be for event keywords or any other (e.g. synth args). At every time one shortcut dictionary is current, but it's only active if EventShortcuts is turned on. Dictionaries are encapsulated and can only be accessed via copies and posting to prevent unintended changes. For event keywords see James Harkins' Practical Guide to Patterns (especially chapters PG_07_Value_Conversions and PG_08_Event_Types_and_Parameters).

+


+


+

See also: Other event and pattern shortcuts, PLx and live coding with Strings

+


+


+

Some Important Issues

+


+

Implementation of shortcuts works like this: if you don't turn EventShortcuts on at all, nothing is changed in the event framework – if you turn it on a mapping function is prepended to every event type function, if this has not been done before in this session and the event type function hasn't been newly defined since last prepending – if you turn it off the prepended function is still there, but does no mapping. 

+

Therfore shortcuts won't work automatically after new definitions of event types. You'd have to turn EventShortcuts on again or apply the method prefixEventTypes. Quite obviously, switching between different shortcut dictionaries might cause a mess while playing (or pausing and resuming) patterns with these shortcuts. But these are exceptional cases, a typical usage would be defining your personal shortcut dictionary (e.g. in the startup file), turning EventShortcuts on and playing therewith, then maybe turning it off and on again on occasion. 

+


+

NOTE: Some pattern classes (e.g. Ppar) don't work correctly with EventShortcuts, but this can be circumvented by applying shortcuts to source event patterns before, see method Pattern::eventShortcuts.

+


+


+

Class Methods

+


+

*add (name, dict, overwrite)

+

+

Adds a new named IdentityDictionary of shortcuts.

+

+

name - Symbol or String.

+

dict - IdentityDictionary of abbreviations (keys given as Symbols) and original names (values given as Symbols).

+

overwrite - Boolean. Determines if a shortcut dictionary of that name – if existing at all – is overwritten. Defaults to false.

+


+


+

*addOnBase (baseName, newName, dict, overwrite)

+

+

Adds a new named IdentityDictionary of shortcuts based on the copy of an existing one.

+

+

baseName - Symbol or String. Name of the shortcut dictionary to build upon.

+

newName - Symbol or String. Name of the new shortcut set.

+

dict - IdentityDictionary of new or/and additional abbreviations (keys given as Symbols) 

+

and original names (values given as Symbols).

+

overwrite - Boolean. Determines if a shortcut dictionary of that name – if existing at all – is overwritten. Defaults to false.

+

Attention: overwrite only determines overwriting of an old dictionary of the same name. It doesn't influence

+

the overwriting in the copy of the base dictionary itself, as exactly this is a main aim of the method

+

(you might want to replace the association 's'-> 'strum' by 's' -> 'server').

+


+


+

*remove (name)

+

+

Removes a named IdentityDictionary of that name.

+

+

name - Symbol or String.

+


+


+

*removeAll

+

+

Removes all IdentityDictionaries except \default.

+

+


+

*copyDict (name)

+

+

Returns a copy of a shortcut dictionary of that name (if stored).

+


+

name - Symbol or String.

+

+


+

*copyCurrentDict (name)

+

+

Returns a copy of the current shortcut dictionary of that name.

+


+

name - Symbol or String.

+

+


+

*copyAllDicts

+

+

Returns an IdentityDictionary of copies of all stored shortcut dictionaries.

+

+

+

*post (name)

+

+

Posts the shortcut dictionary of that name (if stored).

+


+

name - Symbol or String.

+

+

+

*postCurrent

+

+

Posts the current shortcut dictionary.

+

+

+

*postAll

+

+

Posts all shortcut dictionaries.

+

+

+

*makeCurrent (name)

+


+

Makes the shortcut dictionary of that name current (if stored).

+

+

name - Symbol or String.

+

+


+

*on

+

+

Turns on the shortcut mechanism, making it ready for events / patterns to be played.

+

Also invokes prefixEventTypes.

+


+


+

*off

+

+

Turns the shortcut mechanism off.

+


+


+

*prefixEventTypes

+

+

Puts the remapping function before all event type functions. Therefore a newly defined event type won't work

+

with shortcuts before this has been called (directly or via on).

+


+

+

*current

+

+

Returns the Symbol of the current shortcut dictionary.

+

+

+

*dictNames

+

+

Returns the Symbols of all shortcut dictionaries.

+

+


+

*state

+

+

Returns the current state (\on or \off).

+

+

+


+

+

Examples

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// turn shortcuts on

+


+

EventShortcuts.on

+


+


+

// post all, right now only \default exists

+


+

EventShortcuts.postAll

+


+


+

// play an Event with midinote 70

+


+

(m: 70).play

+


+


+

// play a Pbind

+


+

(

+

Pbind(

+

\m, Pwhite(60, 90, 20) + [0, -7], // midinote

+

\p, Pwhite(-1.0, 1), // pan

+

\l, 3, // legato

+

\s, 0.1, // strum

+

\d, 0.5 // dur

+

).play

+

)

+


+


+


+

// define new dictionary based on \default with two different shortcuts

+


+

EventShortcuts.addOnBase(\default, \mine, (s: \scale, t: \ctranspose))

+


+


+

// it isn't current yet, make it 

+


+

EventShortcuts.makeCurrent(\mine)

+


+


+

// play Pbind with new shortcuts, using key/value notation here

+


+

(

+

p = Pbind(*[

+

de: Prand([[0, 1, 26, 27], [-6, 4, 10], [0, 8, 14]], inf), // degree

+

t: Pwhite(0, 1) + Pwrand([0, -10, 10], [0.9, 0.05, 0.05], 200), // ctranspose

+

d: 0.2,  // dur

+

s: Scale.chromatic24 // scale

+

]).play

+

)

+


+


+

// define a SynthDef

+


+

(

+

SynthDef(\test, { |out = 0, freq = 440, width = 0.5, amp = 0.05, gate = 1, 

+

att = 0.05, rel = 1, pan = 0|

+

Out.ar(out, Pan2.ar(Pulse.ar(freq, width, amp), pan) * 

+

EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction:2)

+

)

+

}).add

+

)

+


+


+

// you can also define shortcuts for synthdef args 

+


+

(

+

EventShortcuts.addOnBase(\mine, \mine, (w: \width, r: \rel), true);

+

EventShortcuts.makeCurrent(\mine);

+

)

+


+


+

(

+

Pbind(*[

+

i: \test, // instrument

+

n: Prand([0, 4, 7], inf), // note 

+

o: Pwhite(5, 6), // octave

+

dt: Pwhite(0, 20), // detune (in cent)

+

l: 0.2, // legato

+

d: 0.2, // dur

+

w: Pseq((5, 10..40)/100, 4), // width (synth arg)

+

r: Pseq([0.1, 0.5], inf), // rel (synth arg)

+

]).play

+

)

+


+


+

// works also with other event patterns: Pmono, PmonoArtic, Pbindef

+


+

(

+

Pmono(\default, *[

+

d: 0.03, // dur

+

a: 0.3, // amp

+

dt: Pseq([0, 10], inf),  // detune in Hz

+

p: Pseq((-100, -95..100)/100, 1)  // pan

+

]).play

+

)

+


+


+

// further shortcuts for playing events and patterns are collected here: Other event and pattern shortcuts

+

// e.g. you can directly play a Pbind derived from an Array:

+


+

(

+

[

+

n: Pshuf((1..12)), // note 

+

d: 0.5, // dur

+

l: 3, // legato 

+

o: Pwhite(4, 7) // octave

+

].pp

+

)

+


+

// also possible with Pbindef, though you shouldn't change current shortcut

+

// if you still want to refer later on to a Pbindef defined before the change

+


+


+

(

+

Pbindef(\x, *[

+

d: Prand([1,1,2]/5, inf), // dur

+

m: Pwhite(50, 80), // midinote

+

ct: Prand([[0, 4], [0, 5]], inf) // ctranspose

+

]).play

+

)

+


+

Pbindef(\x, \d, Prand([1,1,1,2,3]/7, inf))

+


+

Pbindef(\x, \ct, [0, 5, 8, 13, 16])

+


+

Pbindef(\x).stop;

+


+


+

// turns off and ensures that default shortcuts are active when EventShortcuts is 

+

// turned on next time in this session 

+


+

(

+

EventShortcuts.off;

+

EventShortcuts.makeCurrent(\default);

+

)

+


+


+ + diff --git a/Help/Fb1.html b/Help/Fb1.html new file mode 100755 index 0000000..5c3cc15 --- /dev/null +++ b/Help/Fb1.html @@ -0,0 +1,1585 @@ + + + + + + + + + + + +

Fb1 single sample feedback / feedforward pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

Fb1 provides an interface for single sample feedback and feedforward at audio and control rate, the defining relation with (formal) access to previous samples is passed as a Function, which might involve additional UGens. Fb1.ar works with arbitrary blockSizes and also allows to refer to samples earlier than one blockSize before. This includes linear filter definitions of arbitrary length with dynamic coefficients as well as all kinds of nonlinear calculations of feedback and feedforward data (FOS and SOS UGens cover the linear case with lengths 1 and 2, LTI the general linear case). 

+


+

Fb1 at control rate exists since miSCellaneous v0.22, for compatibility reasons I left the convention that Fb1.new generates an ar UGen, so Fb1.new is now equivalent to Fb1.ar and Fb1.kr is possible in addition (See Ex.5). Fb1 is the base of an ordinary differential equation integrator framework for initial value problems, that also came with v0.22, see Fb1_ODE help for an introduction.

+


+

HISTORY AND CREDITS: There have been long discussions on single-sample feedback in SC. The most simple, but CPU-intense strategy is setting the server's blockSize to 1. Julian Rohrhuber gave a number of examples with Dbufrd / Dbufwr. SC's folder 'Examples' contains the files single_sample_feedback.scd and single_sample_feedback_02.scd. Special solutions are also possible with Delay1, Delay2 and other UGens. This particular implementation is based on Nathaniel Virgo's suggestion of iteratively writing to and reading from Buffers of blockSize – big credit for this! See also Nathaniel Virgo's Feedback quark for his feedback classes Fb and FbNode. Thanks also to James Harkins for his remarks on graph order. See Ex.1a for the basic feedback implementation principle. I implemented the ar feedforward option by temporary buffers for ar writing and kr reading, the feedback / feedforward relation can now be passed via a Function with 'in' and 'out' args. That way the syntax looks very similar to the common notations used for filter descriptions and also applies directly to the multichannel case. Most other options of Fb1 are for special multichannel handling and differentiated lookback definitions, which can help to save a lot of UGens.

+


+

WARNING: Be careful with amplitudes, feedback can become loud! It is highly recommended to take measures to avoid blowup, e.g. by limiting operators (tanh, softclip, distort) and/or using MasterFX from the JITLibExtensions quark. Also consider that short iteration cycles can produce loud high pitches, wrapping lopass filters is useful! 

+


+

NOTE: The convenience of direct definition of the feedback / feedforward relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. You might also want to experiment with blockSizes smaller than 64 and larger than 1 (e.g. 8, 16 or 32). Check the graphOrderType arg, other values might cause considerable CPU saving and/or shortening of synthdef compile time. 

+


+

See also: GFIS, Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

Creation / Class Methods

+

+

*new (func, in, outSize = 0, inDepth = 1, outDepth = 2, inInit, outInit, blockSize = 64, blockFactor = 1, graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+


+

Creates a new Fb1 ar object.

+

+

func - The Function to define the feedback / feedforward relation. The Function should take 

+

the two arguments 'in' and 'out', both understood as nested multichannel signals,

+

additionally a block index is passed (Ex. 3e). 

+

Each 'in' / 'out' item of the arrays represents current or previous samples,

+

for all points in time the samples are passed in specific array shapes,

+

which are determined by the shapes of in and outSize.

+

Allowed are pure signals (size = 0) and nested SequenceableCollections at maximum:

+

e.g. outSize can be 0, 3, or [0, 2, 5], accordingly in signals can be of sizes 

+

0, i or [i1, ... , in] with i, ij >= 0.

+

Note that 'in' and 'out' only formally represent ar feedback and feedforward signals,

+

technically kr UGens (BufRd.kr) are passed, the ar signals are reconstructed at the end 

+

by reading (arrays of) Buffers.

+

The Function should return the multichannel UGen to be referred to with 'out',

+

the shapes of the returned UGens and outSize must be the same.

+

Furthermore the meaning of 'in' and 'out' depends on the inDepth and outDepth arguments.

+

If an Integer is passed to them (default), the indices of 'in' resp. 'out'  correspond to the 

+

lookback indices:

+

E.g. out[1] refers to the last output sample(s) (of shape outSize), out[2] to the output sample(s) before 

+

the last output sample(s) etc. This is compliant with the convention of writing out[i-1], out[i-2] etc., 

+

out[0] refers to out[i-blockSize]. 

+

For a multichannel 'in' / 'out' signal, depth can be differentiated, which saves UGens 

+

in the case of "gaps" in the recursion:

+

E.g. for a three-channel out signal outDepth can look like [3, [7, 18], [2, 5, 6]].

+

Then out[1] is a three-channel signal, whereby out[1][0] corresponds to out[i-1] of the

+

first, out[1][1] to out[i-18] of the second and out[1][2] to out[i-5] of the third component.

+

If the size of inDepth / outDepth is smaller than outSize, wrapping is applied.

+

As a result double-bracketing can be used to define specific lookback indices

+

for all components of the multichannel signal:

+

E.g. if outDepth equals [[7, 18]] for a three-channel signal then out[0] means 

+

the three-channel signal out[i-7] and out[1] means out[i-18].

+

See Ex. 3a for multichannel feedback / feedforward.

+

in - A single ar input signal or a SequenceableCollection of ar input signals to be referred to 

+

with func (feedforward data). See Ex. 3a for multichannel feedback / feedforward.

+

outSize - Integer or SequenceableCollection thereof, 

+

the size(s) defined by the UGen(s) returned by func. 

+

It's the user's responsibilty to pass the correct size(s)! 

+

Defaults to 0.

+

inDepth - Integer or SequenceableCollection of Integers or SequenceableCollections thereof, 

+

this determines the behaviour of func (see there).

+

If an Integer is passed, it means the maximum storage size for feedforward data. 

+

If a SequenceableCollection is passed, lookback indices for feedforward data 

+

can be differentiated, its items can again be Integers or SequenceableCollections (see func). 

+

Usually the inner SequenceableCollections should be ordered, but this is not compulsory.

+

Defaults to 1 (no lookback). See Ex. 3c.

+

outDepth - Integer or SequenceableCollection of Integers or SequenceableCollections thereof, 

+

this determines the behaviour of func (see there).

+

If an Integer is passed, it means the maximum storage size for feedback data. 

+

If a SequenceableCollection is passed, lookback indices for feedback data 

+

can be differentiated, its items can again be Integers or SequenceableCollections (see func). 

+

Usually the inner SequenceableCollections should be ordered, but this is not compulsory.

+

Defaults to 2 (look back to last sample at maximum). See Ex. 3c.

+

inInit - Number or SequenceableCollection, feedforward init data.

+

If a Number is passed, it means the previous init value for the calculation of the first sample(s),

+

if the size of in is larger than 1, this init value is taken for all components of the multichannel signal.

+

If a SequenceableCollection is passed, this differentiates the init values 

+

for a multichannel signal 'in' used by func. Then the components must be Numbers 

+

(again defining one init value) or SequenceableCollections, which

+

define a lookback collection: first Number is the previous value, second the value before and so on.

+

If the size of inInit is smaller than the size of in, wrapping is applied, that way a double-bracket array, 

+

e.g. [[3, 0, 1]], defines the same init sequence for all components of a multichannel in. See Ex. 3b.

+

outInit - Number or SequenceableCollection, feedback init data.

+

If a Number is passed, it means the previous init value for the calculation of the first sample(s),

+

if outSize is larger than 1, this init value is taken for all components of the 

+

multichannel signal 'out' used by func. 

+

If a SequenceableCollection is passed, this differentiates the init values 

+

for this multichannel signal. Then the components must be Numbers (again defining one init value) 

+

or SequenceableCollections, which define a lookback collection: 

+

first Number is the previous value, second the value before and so on.

+

If the size of outInit is smaller than outSize, wrapping is applied, that way a double-bracket array, 

+

e.g. [[3, 0, 1]], defines the same init sequence for all components of the multichannel signal 

+

'out' used by func. See Ex. 3b.

+

blockSize - Integer, this should be the server blockSize. It's the user's responsibility to pass

+

the correct number. However it might be interesting to experiment with other values.

+

Defaults to 64. See Ex. 3d.

+

blockFactor - Integer. For a value > 1 this allows for lookback indices larger than blockSize, up to 

+

blockSize * blockFactor - 1. It's the user's responsibility to pass correct Integers in this case. 

+

Defaults to 1. See Ex. 3d.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens and in all my examples I didn't encounter cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output.

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+

 

+

*ar (func, in, outSize = 0, inDepth = 1, outDepth = 2, inInit, outInit, blockSize = 64, blockFactor = 1, graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+


+

equivalent to *new

+


+


+

*kr (func, in, outSize = 0, inDepth = 1, outDepth = 2, inInit, outInit, graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+


+

Creates a new Fb1 kr object.

+

+

func - The Function to define the feedback / feedforward relation. The Function should take 

+

the two arguments 'in' and 'out', both understood as nested multichannel signals,

+

additionally a block index is passed (Ex. 3e). 

+

Each 'in' / 'out' item of the arrays represents current or previous control samples,

+

for all points in time the control samples are passed in specific array shapes,

+

which are determined by the shapes of in and outSize.

+

Allowed are pure signals (size = 0) and nested SequenceableCollections at maximum:

+

e.g. outSize can be 0, 3, or [0, 2, 5], accordingly in signals can be of sizes 

+

0, i or [i1, ... , in] with i, ij >= 0.

+

Note that 'in' and 'out' only formally represent feedback and feedforward signals,

+

technically kr UGens (BufRd.kr) are passed.

+

The Function should return the multichannel UGen to be referred to with 'out',

+

the shapes of the returned UGens and outSize must be the same.

+

Furthermore the meaning of 'in' and 'out' depends on the inDepth and outDepth arguments.

+

If an Integer is passed to them (default), the indices of 'in' resp. 'out'  correspond to the 

+

lookback indices:

+

E.g. out[1] refers to the last control output sample(s) (of shape outSize), out[2] to the control output sample(s) before 

+

the last control output sample(s) etc. This is compliant with the convention of writing out[i-1], out[i-2] etc.

+

For a multichannel 'in' / 'out' signal, depth can be differentiated, which saves UGens 

+

in the case of "gaps" in the recursion:

+

E.g. for a three-channel out signal outDepth can look like [3, [7, 18], [2, 5, 6]].

+

Then out[1] is a three-channel signal, whereby out[1][0] corresponds to out[i-1] of the

+

first, out[1][1] to out[i-18] of the second and out[1][2] to out[i-5] of the third component.

+

If the size of inDepth / outDepth is smaller than outSize, wrapping is applied.

+

As a result double-bracketing can be used to define specific lookback indices

+

for all components of the multichannel signal:

+

E.g. if outDepth equals [[7, 18]] for a three-channel signal then out[0] means 

+

the three-channel signal out[i-7] and out[1] means out[i-18].

+

See Ex. 3a for multichannel feedback / feedforward.

+

in - A single kr input signal or a SequenceableCollection of kr input signals to be referred to 

+

with func (feedforward data). See Ex. 3a for multichannel feedback / feedforward.

+

outSize - Integer or SequenceableCollection thereof, 

+

the size(s) defined by the UGen(s) returned by func. 

+

It's the user's responsibilty to pass the correct size(s)! 

+

Defaults to 0.

+

inDepth - Integer or SequenceableCollection of Integers or SequenceableCollections thereof, 

+

this determines the behaviour of func (see there).

+

If an Integer is passed, it means the maximum storage size for feedforward data. 

+

If a SequenceableCollection is passed, lookback indices for feedforward data 

+

can be differentiated, its items can again be Integers or SequenceableCollections (see func). 

+

Usually the inner SequenceableCollections should be ordered, but this is not compulsory.

+

Defaults to 1 (no lookback). See Ex. 3c.

+

outDepth - Integer or SequenceableCollection of Integers or SequenceableCollections thereof, 

+

this determines the behaviour of func (see there).

+

If an Integer is passed, it means the maximum storage size for feedback data. 

+

If a SequenceableCollection is passed, lookback indices for feedback data 

+

can be differentiated, its items can again be Integers or SequenceableCollections (see func). 

+

Usually the inner SequenceableCollections should be ordered, but this is not compulsory.

+

Defaults to 2 (look back to last control sample at maximum). See Ex. 3c.

+

inInit - Number or SequenceableCollection, feedforward init data.

+

If a Number is passed, it means the previous init value for the calculation of the first control sample(s),

+

if the size of in is larger than 1, this init value is taken for all components of the multichannel signal.

+

If a SequenceableCollection is passed, this differentiates the init values 

+

for a multichannel signal 'in' used by func. Then the components must be Numbers 

+

(again defining one init value) or SequenceableCollections, which

+

define a lookback collection: first Number is the previous value, second the value before and so on.

+

If the size of inInit is smaller than the size of in, wrapping is applied, that way a double-bracket array, 

+

e.g. [[3, 0, 1]], defines the same init sequence for all components of a multichannel in. See Ex. 3b.

+

outInit - Number or SequenceableCollection, feedback init data.

+

If a Number is passed, it means the previous init value for the calculation of the first control sample(s),

+

if outSize is larger than 1, this init value is taken for all components of the 

+

multichannel signal 'out' used by func. 

+

If a SequenceableCollection is passed, this differentiates the init values 

+

for this multichannel signal. Then the components must be Numbers (again defining one init value) 

+

or SequenceableCollections, which define a lookback collection: 

+

first Number is the previous value, second the value before and so on.

+

If the size of outInit is smaller than outSize, wrapping is applied, that way a double-bracket array, 

+

e.g. [[3, 0, 1]], defines the same init sequence for all components of the multichannel signal 

+

'out' used by func. See Ex. 3b.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens and in all my examples I didn't encounter cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output.

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+


+

+

Overview - what can / cannot be done ?

+


+

// What can be done:

+


+

// The feedback / feedforward relation is defined within func,

+

// it's important to note that this Function is applied in a very special way

+

// to build the feedback relation into the synthdef graph.

+


+

// Let n be the given blockSize, then

+


+

// 1.) ar / kr: func (only formally) takes over previous (multichannel) out samples for calculation of

+

// next (multichannel) out samples via its 'out' arg,

+

// technically BufRd.krs are passed to 'out' arg, in the ar case signals are reconstructed thereafter

+

// 2.) ar: func is applied n times to establish the iteration in the synthdef graph

+

// 3.) ar / kr: unary and binary operators are the basic tools for this calculation

+

// 4.) ar / kr: func can take over modulating kr UGens from outside via simple reference,

+

// for ar no linear interpolation in this case though, you might therefore consider (5)

+

// 5.) ar / kr: func can take over feedforward UGens with reference to their past data from outside via Fb1's and func's 'in' arg

+

// 6.) ar / kr: func can contain explicitely defined kr UGens.

+

// ar: note that for every UGen in func, n instances are built into the SynthDef graph!

+

// 7.)ar / kr: kr UGens in func can be applied to data passed via 'in' or 'out'

+

// 8.)ar: func's index argument can be used to specify the feedback / feedforward relation per block index

+


+


+

// What cannot / shouldn't be done (ar case considerations)

+


+

// Writing ar UGens in func that produce a time-varying signal itself

+

// (e.g. SinOsc.ar, in contrast to SinOsc.kr and operator UGens like '+', '*' etc.) -

+

// instead, if such ar UGens aren't applied to data from inside func, 

+

// they can be passed via Fb1's and func's 'in' arg.

+

// It remains the case of such ar UGens that should process data that is provided by func

+

// (e.g. letting the fb out modulate a parameter of a VarSaw.ar). 

+

// This is currently not possible and I don't have a clear picture if and how 

+

// it would be possible at all or if it would make much sense.

+


+


+


+

Examples 1) Proof of concept

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// Examples 1b-1d are just a comparison of standard filters vs. explicit definition with Fb1

+

// to show its functioning.

+

// Mostly there is no benefit in doing so in practice as standard filters UGens need less ressources.

+

// The real power of Fb1 lies in the potential to define nonlinear feedback and feedforward relations (2a-2f).

+

// Other than that you can use it to define higher order linear filters for which no classes exist.

+


+

// check blockSize before

+


+

(

+

if (s.options.blockSize != 64) {

+

s.options.blockSize = 64;

+

s.quit.reboot;

+

}

+

)

+


+

Ex.1a) Basic principle

+

+

// The original form of the following example is by Nathaniel Virgo 

+

// and shows the underlying principle for feedback alone.

+

// Succesively Buffers are set with new values at kr,

+

// at the end buffers are read with an ar Phasor,

+

// thereby the order of UGens is crucial.

+

// As James Harkins remarked in the below thread,

+

// plugging the writers into the final reader forces it,

+

// it can be done with summing, but other operations than '+' are also possible.

+


+

// The example also works without this precautionary measure, at least on OSX, SC 3.9.3.

+

// The option graphOrderType allows to turn forced ordering off or to choose an

+

// alternative order-forcing operation, see Ex. 4.

+


+

// https://www.listarc.bham.ac.uk/lists/sc-users-2011/msg01337.html

+

// https://www.listarc.bham.ac.uk/lists/sc-users-2011/msg01363.html

+


+


+

(

+

x = {

+

var buf1 = LocalBuf(64);

+

var buf2 = LocalBuf(64);

+

var x1, x2;

+

var writer_1 = DC.ar(0);

+

var writer_2 = DC.ar(0);

+

SetBuf(buf1, [1], 63); 

+

SetBuf(buf2, [0], 63);

+

x1 = BufRd.kr(1, buf1, 63);

+

x2 = BufRd.kr(1, buf2, 63);

+

64.do { |i|

+

x1 = x1 + (0.2 * x2);

+

x2 = tanh(x2 * 1.2 - (0.1 * x1));

+


+

writer_1 = writer_1 + BufWr.kr(x1, buf1, i);

+

writer_2 = writer_2 + BufWr.kr(x2, buf2, i);

+

};

+

// '<!' ensures that BufWrs are placed before the final reader

+

BufRd.ar(1, [buf1 <! writer_1, buf2 <! writer_2], Phasor.ar(0, 1, 0, 64)) * 0.1;

+

}.play;

+

)

+


+


+

x.release

+


+


+

// same written with Fb1

+

// per default LeakDC is applied, turn off here

+


+

(

+

y = {

+

Fb1({ |in, out|

+

var x1, x2; // define variables to adapt naming convention of above

+

// refer to last out samples

+

#x1, x2 = out[1];

+

x1 = x1 + (0.2 * x2);

+

x2 = tanh(x2 * 1.2 - (0.1 * x1));

+

[x1, x2]

+

}, outSize: 2, outInit: [1, 0], leakDC: false) * 0.1

+

}.play

+

)

+


+

y.release

+


+


+

// check if it's really the same, the difference should run silently (Fb1 without LeakDC)

+


+

(

+

z = {

+

var buf1 = LocalBuf(64);

+

var buf2 = LocalBuf(64);

+

var x1, x2;

+

var writer_1 = DC.ar(0);

+

var writer_2 = DC.ar(0);

+

SetBuf(buf1, [1], 63); 

+

SetBuf(buf2, [0], 63);

+

x1 = BufRd.kr(1, buf1, 63);

+

x2 = BufRd.kr(1, buf2, 63);

+


+

64.do { |i|

+

x1 = x1 + (0.2 * x2);

+

x2 = tanh(x2 * 1.2 - (0.1 * x1));

+


+

writer_1 = writer_1 + BufWr.kr(x1, buf1, i);

+

writer_2 = writer_2 + BufWr.kr(x2, buf2, i);

+

};

+

+

BufRd.ar(1, [buf1 <! writer_1, buf2 <! writer_2], Phasor.ar(0, 1, 0, 64)) -

+

Fb1({ |in, out|

+

var x1, x2; // define variables to adapt naming convention of above

+

#x1, x2 = out[1];

+

x1 = x1 + (0.2 * x2);

+

x2 = tanh(x2 * 1.2 - (0.1 * x1));

+

[x1, x2]

+

}, outSize: 2, outInit: [1, 0], leakDC: false) * 0.1;

+

}.play;

+

)

+


+

z.release

+


+


+


+

Ex.1b) OnePole

+

+

// OnePole as lopass

+


+

s.freqscope;

+


+

x = { OnePole.ar(WhiteNoise.ar(0.3), 0.95) }.play;

+


+

x.release

+


+

// OnePole is implemented by out(i) = ((1 - abs(coef)) * in(i)) + (coef * out(i-1))

+

// so it can be written with Fb1

+


+

y = { Fb1({ |in, out| (in[0] * 0.05) + (out[1] * 0.95) }, WhiteNoise.ar(0.3), leakDC: false) }.play

+


+

y.release

+


+


+

// difference check: this falls silent (Fb1 without LeakDC)

+


+

(

+

z = {

+

var src = WhiteNoise.ar(0.5);

+

Fb1({ |in, out| (in[0] * 0.05) + (out[1] * 0.95) }, src, leakDC: false) - OnePole.ar(src, 0.95)

+

}.play

+

)

+


+

z.release

+


+


+

// stereo case: 'in' is passed a stereo noise,

+

// in addition we have to pass outSize: 2,

+

// then the Function can be written in the same way

+

// in[0] and out[1] now mean stereo signals

+


+

u = { Fb1({ |in, out| (in[0] * 0.05) + (out[1] * 0.95) }, WhiteNoise.ar(0.3!2), 2) }.play

+


+

u.release

+


+


+


+

// difference check with dynamic coefficient

+

// pass the ar signal via Fb1's 'in' arg

+

// difference nearly 0, even with fast modulation

+


+

(

+

v = {

+

var src = WhiteNoise.ar(0.5);

+

var mod = LFDNoise3.ar(50).range(0.7, 0.99);

+

(

+

Fb1({ |in, out| 

+

var src, mod;

+

#src, mod = in[0];

+

(src * (1 - mod)) + (out[1] * mod) 

+

}, [src, mod], leakDC: false) -

+

OnePole.ar(src, mod)

+

).poll

+

}.play

+

)

+


+

v.release

+


+


+


+

// Now check same with kr signal for coefficient,

+

// here we can directly take over the signal in func,

+

// difference is not zero though, because OnePole

+

// does linear interpolation with mod (Fb1 doesn't)!

+


+

(

+

w = {

+

var src = WhiteNoise.ar(0.5);

+

var mod = LFDNoise3.kr(50).range(0.7, 0.99);

+

(

+

Fb1({ |in, out| (in[0] * (1 - mod)) + (out[1] * mod) }, src, leakDC: false) -

+

OnePole.ar(src, mod)

+

).poll

+

}.play

+

)

+


+

w.release

+


+


+

// proof of concept with kr:

+

// If OnePole doesn't interpolate (as with Latch),

+

// signals are the same

+


+


+

(

+

q = {

+

var src = WhiteNoise.ar(0.5);

+

var mod = LFDNoise3.kr(50).range(0.7, 0.99);

+

(

+

Fb1({ |in, out| (in[0] * (1 - mod)) + (out[1] * mod) }, src, leakDC: false) -

+

OnePole.ar(src, Latch.ar(mod, TDuty.ar(ControlDur.ir)))

+

).poll

+

}.play

+

)

+


+

q.release

+


+


+
Ex.1c) SOS

+

+

// SOS as bandreject, note its convention:

+

// SOS.ar(in, a0, a1, a2, b1, b2, mul, add)

+

// out(i) = (a0 * in(i)) + (a1 * in(i-1)) + (a2 * in(i-2)) + (b1 * out(i-1)) + (b2 * out(i-2))

+


+

s.freqscope;

+


+

(

+

x = {

+

var a = [-0.6, 0.5, -0.7]; // feedforward coefficients a0, a1, a2

+

var b = [0.5, -0.1]; // feedback coefficients b1, b2

+

var src = Saw.ar(200, 0.1);

+

SOS.ar(src, *(a ++ b))

+

}.play

+

)

+


+

x.release

+


+

// written with Fb1

+


+

(

+

y = {

+

var a = [-0.6, 0.5, -0.7];

+

var b = [0.5, -0.1];

+

var src = Saw.ar(200, 0.1);

+

// out[0] (= out[i-blockSize]) is passed formally to allow taking over the usual index convention

+

// here we drop it as b1 = b[0] and b2 = b[1]

+

// as we look back two samples for feedforward and feedback we need to pass depths 3

+

Fb1({ |in, out| (in * a).sum + (out.drop(1) * b).sum }, src, 0, 3, 3, leakDC: false)

+

}.play

+

)

+


+

y.release

+


+


+

// difference check: this falls silent

+


+

(

+

z = {

+

var a = [-0.6, 0.5, -0.7];

+

var b = [0.5, -0.1];

+

var src = Saw.ar(200, 0.1);

+

(

+

Fb1({ |in, out| (in * a).sum + (out.drop(1) * b).sum }, src, 0, 3, 3, leakDC: false) -

+

SOS.ar(src, *(a ++ b)) // '*' splits the array into single items needed as UGen args

+

).poll 

+

}.play

+

)

+


+

z.release

+


+


+

Ex.1d) LTI

+


+
// You need to have the SC-3 plugins installed in order to use LTI,

+

// which is part of Nick Collins' SLUGens.

+


+

// Example with coefficients from Nick Collins' helpfile,

+

// here 'a' is used for feedback coefficients.

+


+

// LTI needs data in buffers

+


+

(

+

a = [0.02, -0.01]; // feedback coefficients

+

b = [1, 0.7, 0, 0, 0, 0, -0.8, 0, 0, 0, 0, 0.9] ++

+

[0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0.25, 0.1, 0.25]; // feedforward coefficients

+

c = Buffer.sendCollection(s, a, 1);

+

d = Buffer.sendCollection(s, b, 1);

+

)

+


+

x = { LTI.ar(Saw.ar(50, 0.1), c.bufnum, d.bufnum) }.play

+


+

x.release

+


+


+

// Because of the large inDepth (25 !) a straight takeover of data is very CPU-demanding with Fb1

+


+

(

+

y = {

+

var src = Saw.ar(50, 0.1);

+

Fb1({ |in, out| (in * b).sum + (out.drop(1) * a).sum }, src, 0, b.size, 3, leakDC: false)

+

}.play

+

)

+


+

y.release

+


+


+

// This is an example, where passing lookback indices as depth arg helps a lot (more than 1000 UGens less!)

+


+

(

+

z = {

+

var src = Saw.ar(50, 0.1);

+

var ff = b.reject(_ == 0); // [1, 0.7, -0.8, 0.9, -0.5, 0.25, 0.1, 0.25]

+

+

// for SC < 3.7 you can write (0..b.size-1).select { |i| b[i] != 0 }

+

var indices = b.selectIndices(_ != 0); // [0, 1, 6, 11, 15, 22, 23, 24]

+

+

Fb1({ |in, out| (in * ff).sum + (out.drop(1) * a).sum }, src, 0, [indices], 3, leakDC: false)

+

}.play

+

)

+


+


+

z.release

+


+


+

// difference check, run silently

+


+

(

+

u = {

+

var src = Saw.ar(50, 0.1);

+

var ff = [1, 0.7, -0.8, 0.9, -0.5, 0.25, 0.1, 0.25];

+

var indices = [0, 1, 6, 11, 15, 22, 23, 24];

+

(Fb1({ |in, out| (in * ff).sum + (out.drop(1) * a).sum }, src, 0, [indices], 3, leakDC: false) -

+

LTI.ar(src, c.bufnum, d.bufnum)).poll

+

}.play

+

)

+


+

u.release

+


+

// The filter alternatives with Fb1 mainly make sense in the higher order case when coefficients should be modulated 

+


+


+
Examples 2) Nonlinear feedback operations

+


+

// This is an interesting field of exploration as it cannot easily be done with SC otherwise.

+

// Here the time-varying potential of unary and binary operators comes into play,

+

// normally they are applied to time-varying signals, but here their iteration on a

+

// single-sample base is itself an essential part of producing the variation in time.

+

// Good candidates are already simple operators and their combinations, also with + and -, 

+

// e.g. *, /, **, %, trigonometric operators etc.

+


+


+

Ex.2a) %

+

+

s.scope

+


+

// start fb fx Synth

+


+(

+

~bus = Bus.audio(s, 2);

+


+


+

y = {

+

var inSig = In.ar(~bus, 2);

+

var lfo = SinOsc.ar(0.1).linexp(-1, 1, 50, 500);

+

LPF.ar(

+

Fb1({ |in, out|

+

(out[2] * 0.7) + // factors > 1 blow up !

+

// only the next line is limited with softclip

+

+

// in[0][0] is the current stereo signal from the bus

+

// in[1][0] is the previous

+

// in[0][1] is the current mono lfo, passed with the 'in' arg

+

+

// func returns a stereo signal, so outSize must be passed 2

+

// inDepth [2, 1] because we use inSig[0], inSig[1] and lfo[0]

+

// outDepth 3 because we use out[2]

+

// note that it would be cheaper to use out[0] and outDepth: [[2]],

+

// but a bit more difficult to read

+

+

((in[0][0] - in[1][0]) % 0.01 * in[0][1]).softclip

+

}, [inSig, lfo], 2, [2, 1], 3

+

), 15000) * 0.1

+

}.play

+

)

+


+

// start source

+


+

x = { Out.ar(~bus, SinOsc.ar([60, 60.1]) * EnvGate.new) }.play

+


+

// stop source and start new one

+


+

x.release

+


+

x = { Out.ar(~bus, Saw.ar(SinOsc.ar(0.07).linlin(-1, 1, [100, 100.01], 100.2) * EnvGate.new)) }.play

+


+


+

x.release

+


+

(

+

y.free;

+

~bus.free;

+

)

+


+


+

Ex.2b) sin

+

+

// sin can work as a limiter as well as a nonlinear dynamics engine,

+

// here it causes a wavefolding-like effect

+


+

s.scope

+


+

// start fb fx Synth

+


+


+

(

+

~bus = Bus.audio(s, 1);

+


+

y = {

+

var inSig = In.ar(~bus);

+

var lfo = SinOsc.ar(0.1, -pi/2).linexp(-1, 1, [5, 10], 100) * 100;

+

LPF.ar(

+

Fb1({ |in, out|

+

// here out[0] refers to out[i-2] because of outDepth: [[2]]

+

(out[0] * 0.7) + // factors > 1 blow up !

+

// here in[0][0] and in[1][0] are current and previous mono inSig,

+

// but in[0][1] is stereo, so again a stereo signal is returned by func,

+

// which must be indicated with the outSize arg

+

((in[0][0] - in[1][0]) * in[0][1]).sin

+

}, [inSig,  lfo], 2, [2, 1], [[2]]

+

) * 0.05, 15000)

+

}.play

+

)

+


+


+

// start source

+


+

x = { Out.ar(~bus, SinOsc.ar(60) * EnvGate.new) }.play;

+


+

x.release;

+


+

// stop source and start new one

+


+

x = { Out.ar(~bus, Saw.ar(SinOsc.ar(0.05).linlin(-1, 1, 100, 100.02) * EnvGate.new)) }.play

+


+

x.release

+


+

(

+

y.free;

+

~bus.free;

+

)

+


+


+

Ex.2c) * 

+

+

// rather irrational concatenation of simple operations

+

// depth changes cause frequency changes

+


+


+

s.scope

+


+

// start fb fx Synth

+


+(

+

~bus = Bus.audio(s, 1);

+


+

y = {

+

var inSig = In.ar(~bus);

+

var lfo = { LFDNoise3.ar(0.07).linexp(-1, 1, 1, 5) } ! 2;

+

var i = Demand.kr(Dust.kr(0.5), 0, Dxrand((0..2), inf));

+

var sig = Fb1({ |in, out|

+

(

+

in[0][1] * (

+

in[0][0] * 0.12 + (

+

// changes between out[i-23], out[i-41] and out[i-60] cause frequency changes,

+

// Select depends on a signal from outside, not previous samples as in Ex. 2f

+

(in[1][0].squared - Select.kr(i, out).squared).sqrt

+

)

+

) 

+

).tanh

+

}, [inSig, lfo], 2, [2, 1], [[23, 41, 60]]) * 0.1;

+

// add frequency modulation by delay modulation

+

// lopass filtering with lag

+

DelayC.ar(sig, 0.2, LFDNoise3.ar(1).range(0.01, 0.1)).lag(0.0005)

+

}.play

+

)

+


+

// start source

+


+

x = { Out.ar(~bus, LFTri.ar(LFDNoise0.ar(5).exprange(1, 100)) * EnvGate.new) }.play;

+


+

x.release

+


+

(

+

y.free;

+

~bus.free;

+

)

+


+


+

Ex.2d) / 

+

+


+

// With divisions we must avoid division by zero resp. blowup, here it's max in the divisor.

+

// The example also establishes a cross-feedback of the two channels by using reverse.

+


+

s.scope

+


+

// start fb fx Synth

+


+(

+

~bus = Bus.audio(s, 2);

+


+

y = {

+

var inSig = In.ar(~bus, 2);

+

var lfo = LFDNoise3.ar(1).linexp(-1, 1, 0.2, 10);

+

LPF.ar(

+

Fb1({ |in, out|

+

(in[1][0] * in[0][1] / max(0.001, (in[1][0] - out[1].reverse).abs)).tanh

+

}, [inSig, lfo], 2, [2, 1], 2

+

), 15000) * 0.1

+

}.play

+

)

+


+

// start source

+


+

x = { Out.ar(~bus, SinOsc.ar(LFDNoise3.ar(0.1!2).range(100, 101)) * EnvGate.new) }.play

+


+

x.release

+


+

(

+

y.free;

+

~bus.free;

+

)

+


+


+

Ex.2e) **

+

+

// exponentiation can also be interesting

+

// here an area of instability is crossed by a stereo lfo 

+


+

s.scope

+


+

// start fb fx Synth

+


+(

+

~bus = Bus.audio(s, 1);

+


+

y = {

+

var inSig = In.ar(~bus);

+

var lfo = { LFDNoise3.ar(0.5).linexp(-1, 1, 0.1, 150) } ! 2;

+


+

Fb1({ |in, out|

+

in[1][0] * 0.07 +

+

// out[0] refers to out[i-2] because of outDepth: [[2]]

+

(2 ** (in[1][0] - out[0] * in[0][1]).abs).tanh

+

}, [inSig, lfo], 2, [2, 1], [[2]]) *

+

// avoid bump at start 

+

EnvGen.ar(Env.asr(2))

+

}.play

+

)

+


+


+

// start source

+


+

x = { Out.ar(~bus, LFTri.ar(60) * EnvGate.new) }.play;

+


+


+

x.release

+


+

(

+

y.free;

+

~bus.free;

+

)

+


+


+

Ex.2f) Conditional feedback

+

+

// defining the next sample depending on some characteristics of the previous one(s)

+

// This can be done with the if UGen and Select.

+

// 'if' doesn't support multichannel expansion, so take Select here

+


+

s.scope

+


+

// noisy texture with beeps

+


+

(

+

x = {

+

var src = LFDNoise3.ar(1, 0.1);

+

// ar modulators to be passed (avoid annoying steady tone caused by kr)

+

var mod1 = LFDNoise3.ar(1).range(0.01, 0.2);

+


+

// already slight difference results in quite strong stereo decorrelation

+

var mod2 = LFDNoise3.ar(1).range([0.0001, 0.0002],  0.0049);

+


+

Fb1({ |in, out|

+

// give same names as above for better readability

+

var src = in[0][0];

+

var mod1 = in[0][1];

+

var mod2 = in[0][2];

+

softclip(

+

Select.kr(

+

// as mod2 is stereo we get stereo expansion

+

// and in turn different selections

+

+

// outDepth = [[1, 6]]

+

// so out[0] refers to out[i-1], out[1] to out[i-6]

+

+

out[0] % 0.005 < mod2,

+

[out[1].neg * mod1, out[0] * 0.1]

+

) + src + out[0]

+

)

+

// lopass filtering with lag

+

}, [src, mod1, mod2], 2, 1, [[1, 6]]).lag(0.001) * 0.5

+

}.play

+

)

+


+

x.release

+


+


+


+

Examples 3) Conventions and args

+


+

Ex.3a) Multichannel feedback / feedforward

+


+

// in and out can be multichannel signals of arbitrary size or collections thereof,

+

// however arbitrary nesting is not supported,

+

// outSize arg has to be passed explicitely,

+

// size of in arg is taken over automatically.

+


+
// also mind the difference between size 0 and 1:

+

// with outSize of 1 or [0] Fb1 returns an array 

+


+

// here out is of size 3, in of sizes [3, 0]

+


+

(

+

x = {

+

var inSig = SinOsc.ar(LFDNoise3.ar(0.01 ! 3).range(100, 101)); // 3 channel in signal

+

var lfo = LFDNoise3.ar(0.1).linexp(-1, 1, 0.2, 10);

+

var sig;

+

sig = LPF.ar(

+

Fb1({ |in, out|

+

// in[0][0] and in[1][0] represent current and previous 3 channel samples from inSig

+

// in[0][1] represents current sample from lfo

+

// rotate causes cross-feedback of 3 channels

+

// with reverse only first and last would cross

+

(in[1][0] * in[0][1] / max(0.001, (in[1][0] - out[1].rotate(1)).abs)).tanh

+

// outSize 3 has to be passed

+

}, [inSig, lfo], 3, [2, 1], 2

+

), 15000) * 0.1;

+

Splay.ar(sig)

+

}.play

+

)

+


+

x.release

+


+


+

// again out is of size 3, in of sizes [3, 3]

+


+

(

+

x = {

+

var inSig = SinOsc.ar(LFDNoise3.ar(0.01 ! 3).range(100, 101)); // 3 channel in signal

+

var mod = SinOsc.ar([1, 2.001, 3.999] * 120).linexp(-1, 1, 0.2, 10);

+

var sig;

+

sig = LPF.ar(

+

Fb1({ |in, out|

+

// in[0][0] and in[1][0] represent current and previous 3 channel samples from inSig

+

// in[0][1] represents current 3 samples from mod

+

// rotate causes cross-feedback of 3 channels

+

// with reverse only first and last would cross

+

(in[1][0] * in[0][1] / max(0.001, (in[1][0] - out[1].rotate(1)).abs)).tanh

+

// outSize has to be passed

+

}, [inSig, mod], 3, [2, 1], 2

+

), 15000) * 0.05;

+

Splay.ar(sig)

+

}.play

+

)

+


+

x.release

+


+


+

// it's also possible to let func return an array of (multichannel) signals,

+

// outSize must be set accordingly, here [2, 0],

+

// whereby the mono within the array is a "helper feedback"

+


+

(

+

x = {

+

var inSig = SinOsc.ar(LFDNoise3.ar(1.5 ! 2).exprange(50, 100));

+

var lfo = LFDNoise3.ar(5).linexp(-1, 1, 0.5, 100);

+

var sig;

+

sig = LPF.ar(

+

Fb1({ |in, out|

+

// in[1][0] represents previous 2 channel samples from inSig

+

// in[0][1] represents current sample from lfo

+

+

// reverse causes cross-feedback of 2 main channels

+

// main feedback crosses with helper feedback

+

[

+

// for main feedback use helper feedback 

+

(out[1][1] / max(0.001, (in[1][0] - out[1][0].reverse).abs)).tanh,

+

// for helper feedback use first channel of main feedback

+

(out[1][0][0] + 0.1 / max(0.01, ((in[0][1].abs)))).tanh

+

]

+

// outSize [2, 0] has to be passed

+

}, [inSig, lfo], [2, 0], [2, 1], 2

+

), 12000) * 0.2;

+

// return main feedback

+

sig[0]

+

}.play

+

)

+


+

x.release

+


+


+


+

// here 2 x stereo, outSize == [2, 2]

+


+

(

+

x = {

+

// two stereo sources

+

var in_1 = SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(100, 101));

+

var in_2 = SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(150, 151));

+

var lfo = LFDNoise3.ar(0.1).linexp(-1, 1, 0.2, 10);

+

var sig;

+

sig = LPF.ar(

+

Fb1({ |in, out|

+

// rename for better readability

+


+

// previous ins are stereo

+

var prevIn_1 = in[1][0];

+

var prevIn_2 = in[1][1];

+


+

// mono lfo

+

var lfo = in[0][2];

+


+

// out is 2 x 2 (see below)

+

var prevOut_1 = out[1][0]; // stereo

+

var prevOut_2 = out[1][1]; // stereo

+


+

// we return an array of two stereo signals

+

[

+

(prevIn_1 * lfo / max(0.001, (prevIn_1 - prevOut_1.reverse).abs)),

+

(prevIn_2 * lfo / max(0.001, (prevIn_2 - prevOut_2.reverse).abs))

+

].tanh

+


+

// outSize [2, 2] has to be passed

+

}, [in_1, in_2, lfo], [2, 2], 2, 2

+

), 15000) * 0.05;

+

sig[0] + sig[1]; // mix together

+

// sig[0];

+

// sig[1];

+

}.play

+

)

+


+

x.release

+


+


+

// variant: cross feedback within first stereo out (a) plus

+

// cross feedback the stereo signals with each other (b)

+


+

// at the end take only first stereo out

+

// because of (b) the 150 Hz of in_2 are contained in the resulting signal

+


+

(

+

x = {

+

var in_1 = SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(100, 101));

+

var in_2 = SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(150, 151));

+

var lfo = LFDNoise3.ar(0.1).linexp(-1, 1, 0.2, 10);

+

var sig;

+

sig = LPF.ar(

+

Fb1({ |in, out|

+

// rename for better readability

+


+

// previous ins are stereo

+

var prevIn_1 = in[1][0];

+

var prevIn_2 = in[1][1];

+


+

// mono lfo

+

var lfo = in[0][2];

+


+

// out is 2 x 2 (see below)

+

var prevOut_1 = out[1][0]; // stereo

+

var prevOut_2 = out[1][1]; // stereo

+


+

// we return an array of two stereo signals

+


+

[

+

(prevIn_1 * lfo / max(0.001, (prevIn_1 - prevOut_2.reverse).abs)),

+

(prevIn_2 * lfo / max(0.001, (prevIn_2 - prevOut_1).abs))

+

].tanh

+


+

// outSize has to be passed

+

}, [in_1, in_2, lfo], [2, 2], 2, 2

+

), 15000) * 0.05;

+

sig[0]

+

}.play

+

)

+


+

x.release

+


+


+


+

Ex.3b) inInit / outInit

+


+


+

// linear congruential generator

+


+

// this is not a strict linear congruential generator

+

// as the server doesn't know integers, it's done with floats,

+

// all is blurred by floating point inaccuracy

+

// however interesting results can be obtained

+


+

// different start values can produce different orbits

+


+

// WARNING: can produce loud high pitches with certain init values and factors

+

// as a result of short iteration cycles, take LPF !

+


+


+

// same init value for both channels

+


+

(

+

x = {

+

var sig = Fb1({ |in, out|

+

out[1] * 5.239 % 1

+

},

+

outSize: 2,

+

outInit: 1 // [1] is equivalent

+

).tanh * 0.2;

+

LPF.ar(sig, 2000)

+

}.play

+

)

+


+

x.release

+


+


+

// other iteration sequence by different init value

+


+

(

+

x = {

+

var sig = Fb1({ |in, out|

+

out[1] * 5.239 % 1

+

},

+

outSize: 2,

+

outInit: 2

+

).tanh * 0.2;

+

LPF.ar(sig, 2000)

+

}.play

+

)

+


+

x.release

+


+


+

// defining different init values per channel

+


+

(

+

x = {

+

var sig = Fb1({ |in, out|

+

out[1] * 5.239 % 1

+

},

+

outSize: 2,

+

outInit: [1, 2]

+

).tanh * 0.2;

+

LPF.ar(sig, 2000)

+

}.play

+

)

+


+

x.release

+


+


+

// mono, two init values for one channel (previous and previous of previous)

+

// this needs double brackets for outInit

+


+

(

+

x = {

+

var sig = Fb1({ |in, out|

+

out[1] * 2 + (out[2] * 3) % 1.01

+

},

+

outSize: 1,

+

outInit: [[2, 6]], 

+

outDepth: 3 // needed as we look back for 2 values

+

).tanh * 0.2;

+

LPF.ar(sig, 2000)

+

}.play

+

)

+


+

x.release

+


+


+

// stereo, different arrays of init values per channel

+


+

(

+

x = {

+

var sig = Fb1({ |in, out|

+

out[1] * 2 + (out[2] * 3) % 1.01

+

},

+

outSize: 2,

+

outInit: [[3, 1], [2, 5]], 

+

outDepth: 3 

+

).tanh * 0.2;

+

LPF.ar(sig, 2000)

+

}.play

+

)

+


+

x.release

+


+


+

// inInit values can be defined in the same way as outInit

+


+

// want a trigger at start in connection with Dust

+


+

(

+

x = {

+

var trig = Dust.ar(0.3);

+

var src = SinOsc.ar(1000);

+

var sig = Fb1({ |in, out|

+

var tr = in[1][0];

+

var mod = in[0][1];

+

(out[1] + tr * (mod * 0.02 + 0.999999))

+

},

+

in: [trig, src],

+

inDepth: 2,

+

// here both in buffers get init values, only the first is relevant

+

inInit: 1,  // [1, 0] doesn't make a difference 

+

).lag(0.005).tanh * 0.5;

+

sig

+

}.play

+

)

+


+

x.release

+


+

// inInit and outInit cannot be differentiated for a multichannel component of a multichannel in / out.

+

// If you want to do such, you'd have to split the inner 

+

// multichannel component and differentiate the outer one. 

+


+


+

Ex.3c) inDepth / outDepth

+


+

// Normally, and like in most previous examples,

+

// out[j] and in[j] in func refer to samples out[i-j] and in[i-j],

+

// in[0] to the current input samples.

+

// The lookback size can be determined by passing Integers to inDepth / outDepth, e.g.

+

// with inDepth = 2 you can refer to in[0] and in[i-1]

+

// with outDepth = 3 you can refer to out[i-1] and out[i-2], 

+

// out[0] refers to out[i-blockSize].

+


+

// When refering not to previous but to earlier samples,

+

// passing specified inDepth / outDepth indices is saving UGens.

+


+


+

// Here in[0] refers to in[0], the current sample(s), and

+

// in[1] refers to in[i-56].

+


+

(

+

x = {

+

var src = SinOsc.ar(500 * LFDNoise3.ar(5));

+

var sig = Fb1({ |in, out|

+

(out[1] / max(0.01, (in[1] - in[0]))).tanh

+

},

+

in: src,

+

inDepth: [[0, 56]],

+

outInit: 1

+

) ! 2 * 0.1 ;

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

// looking back less far changes the sound colour

+


+

(

+

x = {

+

var src = SinOsc.ar(500 * LFDNoise3.ar(5));

+

var sig = Fb1({ |in, out|

+

(out[1] / max(0.01, (in[1] - in[0]))).tanh

+

},

+

in: src,

+

inDepth: [[0, 29]],

+

outInit: 1

+

) ! 2 * 0.1;

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

// differentiate inDepth per channel

+


+

(

+

x = {

+

// stereo in

+

var src = SinOsc.ar(500 * LFDNoise3.ar(5!2));

+

var sig = Fb1({ |in, out|

+

(out[1] / max(0.01, (in[1] - in[0]))).tanh

+

},

+

outSize: 2,

+

in: src,

+

inDepth: [[0, 29], [0, 56]],

+

outInit: 1

+

) * [0.2, 0.1];

+

sig

+

}.play

+

)

+


+

x.release

+


+

// inDepth and outDepth cannot be differentiated for a multichannel component of a multichannel in / out.

+

// If you want to do such, you'd have to split the inner 

+

// multichannel component and differentiate the outer one. 

+


+


+

Ex.3d) blockSize / blockFactor

+


+

// Normally Fb1's blockSize should equal the server's current blockSize,

+

// which can be set as a server option, per default it's 64.

+


+

// If you are using a different blockSize, you can either 

+

// reset it for the examples in this helpfile ...

+


+

(

+

if (s.options.blockSize != 64) {

+

s.options.blockSize = 64;

+

s.quit.reboot;

+

}

+

)

+


+

// ... or run the examples with passing a different blockSize, e.g. with: 

+


+

Fb1(..., blockSize: s.options.blockSize)

+


+


+

// You can however try to creativily use a "wrong" blockSize and play with artefacts, 

+

// variant of Ex. 3c 

+


+

(

+

x = {

+

// stereo in

+

var src = SinOsc.ar(300 * LFDNoise3.ar(1!2));

+

var sig = Fb1({ |in, out|

+

(out[1] / max(0.01, (in[1] - in[0]))).tanh

+

},

+

outSize: 2,

+

in: src,

+

inDepth: [[0, 15], [0, 19]],

+

outInit: 1,

+

blockSize: 33

+

) * 0.2;

+

LPF.ar(sig, 3000)

+

}.play

+

)

+


+

x.release

+


+

// variant of Ex. 3c

+

// suppose a blockSize of 64, to look back to in[i-150] set blockFactor to 3.

+


+

(

+

x = {

+

// stereo in

+

var src = SinOsc.ar(500 * LFDNoise3.ar([0.3, 7]));

+

var sig = Fb1({ |in, out|

+

(out[1] / max(0.001, (in[1] - in[0]))).distort

+

},

+

outSize: 2,

+

in: src,

+

inDepth: [[0, 150], [0, 29]],

+

outInit: 1,

+

blockFactor: 3

+

) * [0.07, 0.15];

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

Ex.3e) func index

+


+

// func can take an index as third arg, it runs from 0 to blockSize - 1

+

// this can be used to define the feedback relation depending on it

+


+

// note that inDepth is set to [2, 1] as we look back to inSig[i-1] (in[1][0]),

+

// but not to lfo[i-1] (in[0][1] == lfo[0]), this saves 128 UGens !

+


+


+

(

+

x = {

+

var inSig = SinOsc.ar([50, 50.1]);

+

var lfo = SinOsc.ar(LFDNoise3.ar(0.1).range(0, 500)).range(0, [100, 105]);

+

LPF.ar(

+

Fb1({ |in, out, i|

+

(

+

// establish alternating feedback relations in the synthdef graph

+

i.odd.if {

+

in[0][0] * in[0][1] + out[1] 

+

}{

+

(in[0][0] - in[1][0]) * out[1]

+

}

+

).tanh

+

}, [inSig, lfo], 2, [2, 1], 2

+

) * 0.1, 12000)

+

}.play

+

)

+


+

x.release

+


+


+

Ex.4) Saving CPU

+


+

// UGens written in func are generated as often as blockSize.

+

// Therefore, if possible, references to kr UGens outside save resources.

+

// In addition look for hidden unnecessary operations, which can add hundreds of UGens

+


+


+

// 2440 UGens (with blockSize == 64)

+

// deliberately bad, deterministic lfo is generated blockSize times

+


+


+

(

+

x = {

+

var sig, src;

+

src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25);

+

sig = Fb1({ |in, out|

+

var a = in[0];

+

var b = out[1];

+

var lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01);

+

softclip((a * a * a) + (a * a) + a + (a * a * b)  / max(lfo, a + b));

+

}, src, 2) * 0.1;

+

LPF.ar(sig, 3000)

+

}.play

+

)

+


+

x.release

+


+


+


+

// 2188 UGens as (blockSize - 1) * 4 = 63 * 4 = 252 UGens are saved

+


+


+

(

+

x = {

+

var sig, src, lfo;

+

src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25);

+

lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01);

+

sig = Fb1({ |in, out|

+

var a = in[0];

+

var b = out[1];

+

softclip((a * a * a) + (a * a) + a + (a * a * b)  / max(lfo, a + b));

+

}, src, 2) * 0.1;

+

LPF.ar(sig, 3000)

+

}.play

+

)

+


+

x.release

+


+


+


+

// still bad though when looking at the the algebraic identity

+

// a^3 + a^2 + a + (a * a * b) = ((a + b) * a + a) * a + a

+

// these are 8 vs 5 operations, so with stereo we can save further 

+

// ((8 - 5) * 2) * 64 = 384 UGens

+


+


+

// 1804 UGens

+


+

(

+

x = {

+

var sig, src, lfo;

+

src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25);

+

lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01);

+

sig = Fb1({ |in, out|

+

var a = in[0];

+

var b = out[1];

+

// with SC's L/R-precendence we can write without brackets

+

softclip(a + b * a + a * a + a / max(lfo, a + b));

+

}, src, 2) * 0.1;

+

LPF.ar(sig, 3000)

+

}.play

+

)

+


+

x.release

+


+


+

// without forcing the topological order of BufRds and BufWrs further UGens are saved,

+

// this might or might not be the same result, might be distorted in worst case,

+

// however in all my tests I didn't encounter a single example where it was different

+


+

// 1713 UGens

+


+


+

(

+

x = {

+

var sig, src, lfo;

+

src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25);

+

lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01);

+

sig = Fb1({ |in, out|

+

var a = in[0];

+

var b = out[1];

+

// with SC's L/R-precendence we can write without brackets

+

softclip(a + b * a + a * a + a / max(lfo, a + b));

+

}, src, 2, graphOrderType: 0) * 0.1;

+

LPF.ar(sig, 3000)

+

}.play

+

)

+


+

x.release

+


+


+

// check if it's the same - run silently

+


+

(

+

x = {

+

var sig, src, lfo;

+

src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25);

+

lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01);

+

sig = Fb1({ |in, out|

+

var a = in[0];

+

var b = out[1];

+

// with SC's L/R-precendence we can write without brackets

+

softclip(a + b * a + a * a + a / max(lfo, a + b));

+

}, src, 2, graphOrderType: 0) -

+

Fb1({ |in, out|

+

var a = in[0];

+

var b = out[1];

+

softclip(a + b * a + a * a + a / max(lfo, a + b));

+

}, src, 2) * 0.1;

+

LPF.ar(sig, 3000)

+

}.play

+

)

+


+

x.release

+


+


+

// see also Ex.5 for saving CPU with kr

+


+


+


+

Ex.5) Control rate

+


+


+

// Ex.4 with kr and K2A

+


+

(

+

x = {

+

var sig, src, lfo;

+

src = SinOsc.kr(90 * LFDNoise3.kr(0.3!2).range(0.98, 1.02)) * SinOsc.kr(45.25);

+

lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01);

+

sig = Fb1.kr({ |in, out|

+

var a = in[0];

+

var b = out[1];

+

// with SC's L/R-precendence we can write without brackets

+

softclip(a + b * a + a * a + a / max(lfo, a + b));

+

}, src, 2, graphOrderType: 0) * 0.1;

+

LPF.ar(K2A.ar(sig), 3000)

+

}.play

+

)

+


+

x.release

+


+


+

// FM with same signal

+


+

(

+

x = {

+

var sig, src, lfo;

+

src = SinOsc.kr(90 * LFDNoise3.kr(0.3!2).range(0.98, 1.02)) * SinOsc.kr(45.25);

+

lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01);

+

sig = Fb1.kr({ |in, out|

+

var a = in[0];

+

var b = out[1];

+

// with SC's L/R-precendence we can write without brackets

+

softclip(a + b * a + a * a + a / max(lfo, a + b));

+

}, src, 2, graphOrderType: 0) * 0.1;

+

SinOsc.ar(2000 * sig, 0, 0.1) 

+

}.play

+

)

+


+

x.release

+


+


+


+


+ + diff --git a/Help/Fb1_Duffing.html b/Help/Fb1_Duffing.html new file mode 100755 index 0000000..4614781 --- /dev/null +++ b/Help/Fb1_Duffing.html @@ -0,0 +1,324 @@ + + + + + + + + + + + +

Fb1_Duffing Duffing pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+

+

Fb1_ODE wrapper for the Duffing ODE system with external f:

+


+

y'(t) = w(t)

+

w'(t) =  f(t) + (gamma * cos(omega * t)) - (delta * w(t)) - (beta * y(t)^3) - (alpha * y(t)) 

+


+

coming from the 2nd order equation

+


+

y''(t) + (delta * y'(t)) + (alpha * y(t)) + (beta * y(t)^3) =  (gamma * cos(omega * t)) + f(t)

+


+

It returns a 2-channel signal. See Fb1_ODE help for general information about Fb1 ODE integrator UGens.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [2], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [3]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved.  

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol

+


+

References:

+


+

[1] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[2] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[3] https://git.iem.at/davidpirro/henri

+


+


+

Creation / Class Methods

+


+

*new (f = 0, alpha = 0, beta = 1, gamma = 1, delta = 1, omega = 1, tMul = 1, t0 = 0, y0 = #[1, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_Duffing ar object.

+

+

f - Defaults to 0.

+

alpha - Defaults to 0.

+

beta - Defaults to 1.

+

gamma - Defaults to 1.

+

delta - Defaults to 1.

+

omega - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[1, 0].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use 

+

a reduced server blockSize in order to reduce SynthDef compile time.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (f = 0, alpha = 0, beta = 1, gamma = 1, delta = 1, omega = 1, tMul = 1, t0 = 0, y0 = #[1, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (f = 0, alpha = 0, beta = 1, gamma = 1, delta = 1, omega = 1, tMul = 1, t0 = 0, y0 = #[1, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_Duffing kr object.

+

+

f - Defaults to 0.

+

alpha - Defaults to 0.

+

beta - Defaults to 1.

+

gamma - Defaults to 1.

+

delta - Defaults to 1.

+

omega - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[1, 0].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-control-block base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples

+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\Duffing, { |t, y, f = 0, alpha = 0, beta = 1, gamma = 1, delta = 1,

+

omega = 1|

+

[

+

{ y[1] },

+

{ gamma * cos(omega * t) + f - (delta * y[1]) -

+

((beta * y[0] * y[0] - alpha) * y[0]) }

+

]

+

}, 0, [1, 0], 1, 1);

+


+


+


+

// steady external force, play alpha and beta

+


+

(

+

x = {

+

var sig = Fb1_Duffing.ar(

+

0.7, MouseX.kr(1, 3), MouseY.kr(1, 3), 2, 0, 0.8, tMul: 650);

+

Limiter.ar(sig[0..1], 0.3) * EnvGen.ar(Env.asr(0.1, curve: 3))

+

}.play

+

)

+


+

x.release

+


+


+

// oscillating force, modulate tMul

+


+

(

+

x = {

+

var sig = Fb1_Duffing.ar(

+

SinOsc.ar(3).range(1, 5), 1, 1, 2, 0, 0.8,

+

tMul: LFDNoise0.kr(1).range(700, 2000).lag(0.2)

+

);

+

Limiter.ar(sig[0..1], 0.2) * EnvGen.ar(Env.asr(0.1, curve: 3))

+

}.play

+

)

+


+

x.release

+


+


+


+


+


+ + diff --git a/Help/Fb1_Hopf.html b/Help/Fb1_Hopf.html new file mode 100755 index 0000000..a8f1ec4 --- /dev/null +++ b/Help/Fb1_Hopf.html @@ -0,0 +1,359 @@ + + + + + + + + + + + +

Fb1_Hopf Hopf pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

Fb1_ODE wrapper for the Hopf ODE system with external f:

+


+

v'(t) = (mu - (v(t)^2 + w(t)^2)) * v(t) - (theta * w(t)) + f(t)

+

w'(t) = (mu - (v(t)^2 + w(t)^2)) * w(t) + (theta * v(t)) 

+


+

It returns a 2-channel signal. See Fb1_ODE help for general information about Fb1 ODE integrator UGens.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [4], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [5]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. 

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Righetti, Ludovic; Buchli, Jonas; Ijspeert, Auke Jan (2009): 

+

"Adaptive Frequency Oscillators and Applications". 

+

The Open Cybernetics and Systemics Journal, 3, 64-69.

+

https://www.researchgate.net/publication/41666931_Adaptive_Frequency_Oscillators_and_Applications_Open_Access

+

Summary: https://biorob.epfl.ch/research/research-dynamical/page-36365-en-html/

+

[2] Nachstedt, Timo; Tetzlaff, Christian; Manoonpong, Poramate (2017): 

+

"Fast Dynamical Coupling Enhances Frequency Adaptation of Oscillators for Robotic Locomotion Control". 

+

Frontiers in Neurorobotics. Published online 2017 Mar 21

+

https://www.frontiersin.org/articles/10.3389/fnbot.2017.00014/full

+

[3] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[4] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[5] https://git.iem.at/davidpirro/henri

+

+


+

Creation / Class Methods

+


+

*new (f = 0, mu = 1, theta = 1, tMul = 1, t0 = 0, y0 = #[1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_Hopf ar object.

+

+

f - Defaults to 0.

+

mu - Defaults to 1.

+

theta - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[1, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use 

+

a reduced server blockSize in order to reduce SynthDef compile time.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (f = 0, mu = 1, theta = 1, tMul = 1, t0 = 0, y0 = #[1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (f = 0, mu = 1, theta = 1, tMul = 1, t0 = 0, y0 = #[1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_Hopf kr object.

+

+

f - Defaults to 0.

+

mu - Defaults to 1.

+

theta - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[1, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-control-block base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples

+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\Hopf, { |t, y, f = 0, mu = 1, theta = 1|

+

var u, v;

+

u = y[0] * y[0] + (y[1] * y[1]);

+

v = mu - u;

+

[

+

{ v * y[0] - (theta * y[1]) + f },

+

{ v * y[1] + (theta * y[0]) },

+

]

+

}, 0, [1, 1], 0.3, 0.3);  // scaling as standard params lead to high level output

+


+


+


+

// change param mu and static external force (f = mu = 0: harmonic oscillation)

+


+

x = { Fb1_Hopf(MouseX.kr(0, 1).poll, MouseY.kr(1.5, 5).poll, 1, 200 * 2pi) * 0.2 }.play

+


+

x.release

+


+


+

// f fed with a SinOsc with frequency coupled to tMul by an integer factor,

+

// mu movement can tilt over the waveform

+


+

(

+

SynthDef(\hopf, { |out, freqFactor = 1,

+

srcAmp = 1, mu = 1, theta = 1, tMul = 1, amp = 0.05|

+

var sig;

+

sig = Fb1_Hopf.ar(

+

SinOsc.ar(tMul / freqFactor) * srcAmp,

+

mu.lag(1), theta, tMul

+

);

+

Out.ar(out, Limiter.ar(sig[0..1]) * amp.lag(1))

+

}, metadata: (

+

specs: (

+

freqFactor: [5, 30, \lin, 1, 18],

+

srcAmp: [5, 100, \lin, 0, 27],

+

mu: [0, 50, \lin, 0, 25],

+

theta: [0.1, 7, \lin, 0, 3.8],

+

tMul: [200, 1000, \lin, 0, 450],

+

amp: [0.01, 0.07, \db, 0, 0.05]

+

)

+

)).add;

+


+

\hopf.sVarGui.gui

+

)

+


+


+

// focussing on the tilt effect:

+

// LFO crossing a critical boundary of Hopf's mu parameter

+


+

(

+

x = {

+

var freq = LFDNoise3.kr(0.2).exprange(200, 700), sig;

+

sig = Fb1_Hopf.ar(

+

f: SinOsc.ar(freq) * 30,

+

mu: SinOsc.ar(SinOsc.ar(0.1, -pi/2).exprange(0.2, 20), -pi/2).range(5, 30).poll,

+

theta: 1,

+

tMul: freq

+

);

+

Limiter.ar(sig[0..1]) * 0.1 * EnvGen.ar(Env.asr(0.1, curve: 3))

+

}.play

+

)

+


+

x.release

+


+ + diff --git a/Help/Fb1_HopfA.html b/Help/Fb1_HopfA.html new file mode 100755 index 0000000..07f256a --- /dev/null +++ b/Help/Fb1_HopfA.html @@ -0,0 +1,366 @@ + + + + + + + + + + + +

Fb1_Hopf A Adaptive Hopf pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

This is an adaptive variant of Hopf, i.e. it can hold the frequency of an oscillating input f after it vanishes. The model refers to [1], see also [2] (4.1.) for an enhancement implemented with Fb1_HopfAFDC. The parameter naming follows the convention of [2].

+


+

v'(t) = (mu - (v(t)^2 + w(t)^2)) * v(t) - (theta(t) * w(t)) + f(t)

+

w'(t) = (mu - (v(t)^2 + w(t)^2)) * w(t) + (theta(t) * v(t)) 

+

theta'(t) = -eta * f(t) * w(t) / sqrt(v(t)^2 + w(t)^2)

+


+

It returns a 3-channel signal. See Fb1_ODE help for general information about Fb1 ODE integrator UGens.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [4], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [5]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved.  

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Righetti, Ludovic; Buchli, Jonas; Ijspeert, Auke Jan (2009): 

+

"Adaptive Frequency Oscillators and Applications". 

+

The Open Cybernetics and Systemics Journal, 3, 64-69.

+

https://www.researchgate.net/publication/41666931_Adaptive_Frequency_Oscillators_and_Applications_Open_Access

+

Summary: https://biorob.epfl.ch/research/research-dynamical/page-36365-en-html/

+

[2] Nachstedt, Timo; Tetzlaff, Christian; Manoonpong, Poramate (2017): 

+

"Fast Dynamical Coupling Enhances Frequency Adaptation of Oscillators for Robotic Locomotion Control". 

+

Frontiers in Neurorobotics. Published online 2017 Mar 21

+

https://www.frontiersin.org/articles/10.3389/fnbot.2017.00014/full

+

[3] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[4] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[5] https://git.iem.at/davidpirro/henri

+

+


+

Creation / Class Methods

+


+

*new (f = 0, mu = 0, eta = 1, tMul = 1, t0 = 0, y0 = #[1, 1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_HopfA ar object.

+

+

f - Defaults to 0.

+

mu - Defaults to 1.

+

eta - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 3.

+

Defaults to #[1, 1, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use 

+

a reduced server blockSize in order to reduce SynthDef compile time.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (f = 0, mu = 1, eta = 1, tMul = 1, t0 = 0, y0 = #[1, 1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (f = 0, mu = 1, eta = 1, tMul = 1, t0 = 0, y0 = #[1, 1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_HopfA kr object.

+

+

f - Defaults to 0.

+

mu - Defaults to 1.

+

eta - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 3.

+

Defaults to #[1, 1, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-control-block base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples

+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\HopfA, { |t, y, f = 0, mu = 1, eta = 1|

+

var u, v;

+

u = y[0] * y[0] + (y[1] * y[1]);

+

v = mu - u;

+

[

+

{ v * y[0] - (y[2] * y[1]) + f },

+

{ v * y[1] + (y[2] * y[0]) },

+

{ f.neg * y[1] * eta / sqrt(u) }

+

]

+

}, 0, [1, 1, 1], 0.3, 0.3);  // scaling as standard params lead to high level output

+


+


+


+

// reasonable adaption with sine source

+

// mouse left turns source down, but oscillator still keeps frequency

+

// move mouse to right to turn source on again

+


+

(

+

x = {

+

var ratio = Demand.ar(

+

Impulse.ar(3), 0,

+

Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf)

+

).midiratio;

+

var hold = (MouseX.kr(0, 1) > 0.1).poll;

+

var src = SinOsc.ar(ratio * 200);

+

var sig = Fb1_HopfA.ar(src * hold.lag(0.5), 0.15, 2, 800);

+

sig[0] ! 2 * 0.5

+

}.play

+

)

+


+

x.release

+


+


+

// with Saw the result is less exact, sometimes Hopf jumps to higher partials

+


+

(

+

x = {

+

var ratio = Demand.ar(

+

Impulse.ar(3), 0,

+

Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf)

+

).midiratio;

+

var hold = (MouseX.kr(0, 1) > 0.1).poll;

+

var src = Saw.ar(ratio * 200);

+

var sig = Fb1_HopfA.ar(src * hold.lag(0.5), 0.15, 2, 800);

+

sig[0] ! 2 * 0.5

+

}.play

+

)

+


+

x.release

+


+


+

// an inexact adaption can be used though to generate interesting irregular variations

+

// listen for a while

+


+

(

+

x = {

+

var ratio = Demand.ar(

+

Impulse.ar(3), 0,

+

Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf)

+

).midiratio;

+

var src = Saw.ar(ratio * 200, 0.3);

+

var sig = Fb1_HopfA.ar(src, 8, 10.5, 600);

+

sig[0] ! 2 * 0.3

+

}.play

+

)

+


+

x.release

+


+


+ + diff --git a/Help/Fb1_HopfAFDC.html b/Help/Fb1_HopfAFDC.html new file mode 100755 index 0000000..4c4ef30 --- /dev/null +++ b/Help/Fb1_HopfAFDC.html @@ -0,0 +1,406 @@ + + + + + + + + + + + +

Fb1_Hopf AFDC Adaptive Hopf pseudo ugen with fast dynamical coupling

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

This is an adaptive variant of Hopf, which enhances the adaption mechanism of [1], see [2] (4.1.) for a description. The parameter naming follows the convention of [2].

+


+

v'(t) = (mu - (v(t)^2 + w(t)^2)) * v(t) - (theta(t) * w(t)) + p(t)

+

w'(t) = (mu - (v(t)^2 + w(t)^2)) * w(t) + (theta(t) * v(t)) 

+

tau * beta'(t) = beta0 - beta(t) + (kappa * p(t) * v(t))

+

tau * epsilon'(t) = epsilon0 - epsilon(t) + (kappa * f(t) * p(t))

+

theta'(t) = -eta * p(t) * w(t) / sqrt(v(t)^2 + w(t)^2)

+


+

with 

+


+

p(t) = (epsilon(t) * f(t)) - (beta(t) * v(t))

+


+

It returns a 5-channel signal. See Fb1_ODE help for general information about Fb1 ODE integrator UGens.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [4], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [5]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved.  

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Righetti, Ludovic; Buchli, Jonas; Ijspeert, Auke Jan (2009): 

+

"Adaptive Frequency Oscillators and Applications". 

+

The Open Cybernetics and Systemics Journal, 3, 64-69.

+

https://www.researchgate.net/publication/41666931_Adaptive_Frequency_Oscillators_and_Applications_Open_Access

+

Summary: https://biorob.epfl.ch/research/research-dynamical/page-36365-en-html/

+

[2] Nachstedt, Timo; Tetzlaff, Christian; Manoonpong, Poramate (2017): 

+

"Fast Dynamical Coupling Enhances Frequency Adaptation of Oscillators for Robotic Locomotion Control". 

+

Frontiers in Neurorobotics. Published online 2017 Mar 21

+

https://www.frontiersin.org/articles/10.3389/fnbot.2017.00014/full

+

[3] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[4] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[5] https://git.iem.at/davidpirro/henri

+

+


+

Creation / Class Methods

+


+

*new (f = 0, mu = 1, eta = 1, tau = 1, kappa = 1, tMul = 1, t0 = 0, y0 = #[1, 1, 0.01, 0.01, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_HopfAFDC ar object.

+

+

f - Defaults to 0.

+

mu - Defaults to 1.

+

eta - Defaults to 1.

+

tau - Defaults to 1.

+

kappa - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 5.

+

Defaults to #[1, 1, 0.01, 0.01, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use 

+

a reduced server blockSize in order to reduce SynthDef compile time.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (f = 0, mu = 1, eta = 1, tau = 1, kappa = 1, tMul = 1, t0 = 0, y0 = #[1, 1, 0.01, 0.01, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (f = 0, mu = 1, eta = 1, tau = 1, kappa = 1, tMul = 1, t0 = 0, y0 = #[1, 1, 0.01, 0.01, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_HopfAFDC kr object.

+

+

f - Defaults to 0.

+

mu - Defaults to 1.

+

eta - Defaults to 1.

+

tau - Defaults to 1.

+

kappa - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 5.

+

Defaults to #[1, 1, 0.01, 0.01, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-control-block base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples

+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+

// beta0 and epsilon0 are taken over as components of indices 2 and 3 from y0

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\HopfAFDC, { |t, y, f = 0, mu = 1,

+

eta = 1, tau = 1, kappa = 1, beta0 = 0.01, epsilon0 = 0.01|

+

var p, u, v;

+

u = y[0] * y[0] + (y[1] * y[1]);

+

v = mu - u;

+

p = y[3] * f - (y[2] * y[0]);

+

[

+

{ v * y[0] - (y[4] * y[1]) + p },

+

{ v * y[1] + (y[4] * y[0]) },

+

{ beta0 - y[2] + (kappa * p * y[0]) / tau },

+

{ epsilon0 - y[3] + (kappa * p * f) / tau },

+

{ p.neg * y[1] * eta / sqrt(u) }

+

]

+

}, 0, [1, 1, 0.01, 0.01, 1], 0.3, 0.3);

+


+


+


+

// adaption to sine source (R) by Fb1_HopfAFDC (L)

+

// mouse left turns source down for the Hopf

+

// for comparison the source continues in the right channel

+

// move mouse to right to turn source on again

+


+

(

+

x = {

+

var ratio = Demand.ar(

+

Impulse.ar(3), 0,

+

Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf)

+

).midiratio;

+

var hold = (MouseX.kr(0, 1) > 0.1).poll;

+

var src = SinOsc.ar(ratio * 200);

+

var sig = Fb1_HopfAFDC.ar(src * hold.lag(0.1), 0.2, 0.6, 1, 50, 150) *

+

EnvGen.ar(Env.asr(0.05));

+

[sig[0] * 0.7, src * 0.1]

+

}.play

+

)

+


+

x.release

+


+


+

// in contrast to Fb1_HopfA adaption to the Saw works quite well

+


+

(

+

x = {

+

var ratio = Demand.ar(

+

Impulse.ar(3), 0,

+

Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf)

+

).midiratio;

+

var hold = (MouseX.kr(0, 1) > 0.1).poll;

+

var src = Saw.ar(ratio * 200);

+

var sig = Fb1_HopfAFDC.ar(src * hold.lag(0.1), 0.2, 0.6, 1, 50, 150) *

+

EnvGen.ar(Env.asr(0.05));

+

[sig[0] * 0.5, src * 0.04]

+

}.play

+

)

+


+

x.release

+


+


+

// it works still with faster tempo and bigger jumps

+

// however big jumps and/or the hold gate make the system instable,

+

// compose with clip helps

+


+

(

+

x = {

+

var ratio = Demand.ar(

+

Impulse.ar(7), 0,

+

Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf) + Drand([0, 12, 24], inf)

+

).midiratio;

+

var hold = MouseX.kr(0, 1) > 0.1;

+

var src = Saw.ar(ratio * 200);

+

var tMul = 150;

+

var sig = Fb1_HopfAFDC.ar(src * hold.lag(0.1), 0.2, 0.6, 1, 50, tMul,

+

compose: { |x| x.clip2(100) }

+

) * EnvGen.ar(Env.asr(0.05));

+

[sig[0] * 0.5, src * 0.04]

+

}.play

+

)

+


+

x.release

+


+


+

// confused adaption by feeding with phase modulated signal

+


+

(

+

x = {

+

var ratio = Demand.ar(

+

Impulse.ar(3), 0,

+

Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf)

+

).midiratio;

+

var src = SinOsc.ar(ratio * 200, SinOsc.ar(200).range(0.2, 1));

+

var sig = Fb1_HopfAFDC.ar(src, 1.5, 1, 1, 100, 200) *

+

EnvGen.ar(Env.asr(0.05));

+

sig[0] ! 2 * 0.3

+

}.play

+

)

+


+

x.release

+


+ + diff --git a/Help/Fb1_Lorenz.html b/Help/Fb1_Lorenz.html new file mode 100755 index 0000000..80282e8 --- /dev/null +++ b/Help/Fb1_Lorenz.html @@ -0,0 +1,358 @@ + + + + + + + + + + + +

Fb1_Lorenz Lorenz pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

Fb1_ODE wrapper for the Lorenz ODE system:

+


+

u'(t) = s * (v(t) - u(t))

+

v'(t) = (u(t) * (r - w(t))) - v(t)

+

w'(t) = (u(t) * v(t)) - (b * w(t))

+


+

It returns a 3-channel signal. See Fb1_ODE help for general information about Fb1 ODE integrator UGens.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [4], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [5]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. 

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[2] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[3] https://git.iem.at/davidpirro/henri

+


+


+

Creation / Class Methods

+


+

*new (s = 10, r = 30, b = 2, tMul = 1, t0 = 0, y0 = #[1, 1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_Lorenz ar object.

+

+

s - Defaults to 10.

+

r - Defaults to 30.

+

b - Defaults to 2.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 3.

+

Defaults to #[1, 1, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use 

+

a reduced server blockSize in order to reduce SynthDef compile time.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (s = 10, r = 30, b = 2, tMul = 1, t0 = 0, y0 = #[1, 1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (s = 10, r = 30, b = 2, tMul = 1, t0 = 0, y0 = #[1, 1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_Lorenz kr object.

+

+

s - Defaults to 10.

+

r - Defaults to 30.

+

b - Defaults to 2.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 3.

+

Defaults to #[1, 1, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-control-block base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples

+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\Lorenz, { |t, y, s = 10, r = 30, b = 2|

+

[

+

{ (y[1] - y[0]) * s },

+

{ (r - y[2]) * y[0] - y[1] },

+

{ (y[0] * y[1]) - (b * y[2]) }

+

]

+

}, 0, [1, 1, 1], 0.01, 0.01); // strong scaling as standard params lead to high level output

+


+


+

// Lorenz as controller 

+


+

(

+

x = {

+

var s = 10, r = 30, b = 3, tMul = MouseX.kr(0.1, 50), sig;

+

sig = Fb1_Lorenz.kr(s, r, b, tMul,

+

withScaling: false,

+

leakDC: false

+

).tanh.linlin(-1, 1, 100, LFDNoise3.kr(0.3).range(700, 1000));

+

SinOsc.ar(sig[0..1], 0, 0.2)

+

}.play;

+

)

+


+

x.release

+


+


+

// two channel audio with default params

+


+

x = { Fb1_Lorenz.ar(tMul: MouseX.kr(50, 300).poll)[0..1] }.play;

+


+

x.release

+


+


+

// with oscillating parameters the results can be quite diverse

+


+

(

+

x = {

+

var sig, s;

+

s = SinOsc.ar(200, 0, 15, 10);

+

sig = Fb1_Lorenz.ar(s, 22, 1, 170, y0: [1, 0, 0]);

+

Limiter.ar(sig[0..1]) * EnvGen.ar(Env.asr(0.1, curve: 3));

+

}.play

+

)

+


+

x.release

+


+


+

(

+

x = {

+

var sig, s;

+

s = SinOsc.ar(100, 0, 9, 10);

+

sig = Fb1_Lorenz.ar(s, 22, 1, 170, y0: [1, 0, 0]);

+

Limiter.ar(sig[0..1]) * EnvGen.ar(Env.asr(0.1, curve: 3));

+

}.play

+

)

+


+

x.release

+


+


+

(

+

x = {

+

var sig, s;

+

s = SinOsc.ar(100, 0, 10, 6);

+

sig = Fb1_Lorenz.ar(s, 20, 1.5, 170, y0: [1, 1, 0]);

+

Limiter.ar(sig[0..1]) * EnvGen.ar(Env.asr(0.1, curve: 3));

+

}.play

+

)

+


+

x.release

+


+


+ + diff --git a/Help/Fb1_MSD.html b/Help/Fb1_MSD.html new file mode 100755 index 0000000..c1b9413 --- /dev/null +++ b/Help/Fb1_MSD.html @@ -0,0 +1,324 @@ + + + + + + + + + + + +

Fb1_MSD mass spring damper model pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

Fb1_ODE wrapper for the mass spring damper equation of motion:

+


+

y''(t) * mass = f(t) - (dampen * y'(t)) - (spring * y(t)) 

+


+

It returns a 2-channel signal with position and velocity. Fb1_SD with unified mass is slightly more efficient than Fb1_MSD and you can always rewrite. See Fb1_ODE help for general information about Fb1 ODE integrator UGens and further MSD examples.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [2], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [3]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved.  

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[2] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[3] https://git.iem.at/davidpirro/henri

+


+


+

Creation / Class Methods

+


+

*new (f = 0, mass = 1, spring = 1, dampen = 0, tMul = 1, t0 = 0, y0 = #[0, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_MSD ar object.

+

+

f - External force. Defaults to 0.

+

mass - Mass. Defaults to 1.

+

spring - Spring stiffness. Defaults to 1.

+

dampen - Dampening factor. Defaults to 0.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[0, 0].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use 

+

a reduced server blockSize in order to reduce SynthDef compile time.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (f = 0, mass = 1, spring = 1, dampen = 0, tMul = 1, t0 = 0, y0 = #[0, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (f = 0, mass = 1, spring = 1, dampen = 0, tMul = 1, t0 = 0, y0 = #[0, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_MSD kr object.

+

+

f - External force. Defaults to 0.

+

mass - Mass. Defaults to 1.

+

spring - Spring stiffness. Defaults to 1.

+

dampen - Dampening factor. Defaults to 0.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[0, 0].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-control-block base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples

+


+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\MSD, { |t, y, f = 0, mass = 1, spring = 1, dampen = 0|

+

[

+

{ y[1] },

+

{ f - (dampen * y[1]) - (spring * y[0]) / mass }

+

]

+

}, 0, [0, 0], 1, 1);

+


+


+


+

// 2nd channel (velocity) generates decaying sine wave

+

// (position does also, but is softer)

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 1500, dampen = 0.003,

+

sig = Fb1_MSD.ar(f, mass, spring, dampen);

+

sig[1] ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// oscillation as external force

+

// stays while spring oscillation decays

+


+

(

+

x = {

+

var f = SinOsc.ar(500, 0, 0.3), mass = 0.001, spring = 1500, dampen = 0.003,

+

sig = Fb1_MSD.ar(f, mass, spring, dampen);

+

sig[1] ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// MSD as modulator, kr saves ugens

+

// usable if, as here, it produces rather low frequencies

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 200, dampen = 0.003,

+

sig = Fb1_MSD.kr(f, mass, spring, dampen);

+

SinOsc.ar(sig[1].linlin(-1, 1, [100, 90], 700), 0, 0.2)

+

}.play

+

)

+


+

x.release

+


+ + diff --git a/Help/Fb1_ODE.html b/Help/Fb1_ODE.html new file mode 100755 index 0000000..e5ab963 --- /dev/null +++ b/Help/Fb1_ODE.html @@ -0,0 +1,1129 @@ + + + + + + + + + + + +

Fb1_ODE general ordinary differential equation integrator pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

Pseudo ugen for integrating / audifying ordinary (systems of) differential equations with initial values in realtime, based on the Fb1 single sample feedback class. It makes use of Fb1_ODEdef and Fb1_ODEintdef, containers for ODEs and numerical solution methods, which provide an interface for adding new ODE systems or integration methods interactively. There exists a huge number of ODE models in different areas, e.g. have a look at the SIAM publication Exploring ODEs [1], it approaches the topic from an experimental point of view, which is quite related to the demands of practical audification / synthesis / processing. And of course it's fun to define your own ODEs, see this help file and Fb1_ODEdef.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [2], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [3]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: Fb1_ODE in its plain form (without tMul modulation, use of compose etc.) produces audio data as a numerical integration of an ODE initial value problem, defined by Fb1_ODEdef and a numerical procedure, defined by Fb1_ODEintdef. This of course supposes well-defined ODE systems and it should be kept in mind that the Fb1_ODE framework doesn't perform any mathematical checks regarding the principal existence and uniqueness of a solution of a given Fb1_ODEdef. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved.  

+


+

See also: Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[2] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[3] https://git.iem.at/davidpirro/henri

+


+


+

Creation / Class Methods

+


+

*new (name, argList, tMul = 1, t0, y0, intType = \sym2, compose, composeArIn, dt0, argList0, 

+

init_intType = \sym8, withOutScale = true, withDiffChannels = false, withTimeChannel = false, 

+

blockSize, graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_ODE ar object.

+

+

name - The name of the ODE defined and stored via Fb1_ODEdef. 

+

Can be one of the predefined ODEs, for which wrappers exist, or a self-defined one.

+

Expects a Symbol.

+

argList - The argList to be passed to the ODE. Can contain ar or kr UGens and

+

SequenceableCollections thereof.

+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

If no value is passed, the default value of the referred Fb1_ODEdef is taken, usually 0.

+

y0 - Initial value of the ODE. 

+

Expects number or array of correct size, which is determined by the Fb1_ODEdef.

+

If no value is passed, the default value of the referred Fb1_ODEdef is taken.

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE 

+

with a reduced server blockSize in order to reduce SynthDef compile time.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (name, argList, tMul = 1, t0, y0, intType = \sym2, compose, composeArIn, dt0, argList0, 

+

init_intType = \sym8, withOutScale = true, withDiffChannels = false, withTimeChannel = false, 

+

blockSize, graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (name, argList, tMul = 1, t0, y0, intType = \sym4, compose, dt0, argList0, 

+

init_intType = \sym8, withOutScale = true, withDiffChannels = false, withTimeChannel = false, 

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_ODE kr object.

+

+

name - The name of the ODE defined and stored via Fb1_ODEdef. 

+

Can be one of the predefined ODEs, for which wrappers exist, or a self-defined one.

+

Expects a Symbol.

+

argList - The argList to be passed to the. Can contain kr UGens and

+

SequenceableCollections of thereof.

+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting usable oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

If no value is passed, the default value of the referred Fb1_ODEdef is taken, usually 0.

+

y0 - Initial value of the ODE. 

+

Expects number or array of correct size, which is determined by the Fb1_ODEdef.

+

If no value is passed, the default value of the referred Fb1_ODEdef is taken.

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples 1) Proof of concept

+


+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+


+

Ex.1a) The harmonic oscillator

+


+

// A sine/cos function is the solution of the second order differential equation

+


+

// y''(t) = -y(t)

+


+

// with the standard substitution ...

+


+

// w(t) = y'(t)

+


+

// ... this can be reformulated as a system of two equations of first order

+


+

// y'(t) = w(t)

+

// w'(t) = -y(t)

+


+

// In general an ODE can be written like this

+

// where Y and F are meant to be a vector-valued functions,

+

// F given and Y to be found

+


+

// Y'(t) = F(t, Y)

+


+


+

// For the corresponding Fb1_ODEdef the Function (F) must take time and - as here we have size 2 - 

+

// an array as arguments plus optional further arguments and 

+

// output an array of same size, which can be implemented by the evaluation of:

+


+

(

+

Fb1_ODEdef(\harmonic, { |t, y|

+

[y[1], y[0].neg]

+

}, 0, [0, 1], 1, 1); // default init values t0, y0 and scaling factors for output

+

)

+


+

// The stereo output is a pair of 90-degree-shifted sine waves.

+

// As one wave length would be 2pi seconds we multiply time by a factor 500 * 2pi to get 500 Hz.

+

// Changed blockSize is detected automatically.

+


+

x = { Fb1_ODE.ar(\harmonic, tMul: 500 * 2pi) * 0.1 }.play

+


+

x.release

+


+


+

// as with Fb1, method 'new' is equivalent to 'ar'

+


+

x = { Fb1_ODE(\harmonic, tMul: 500 * 2pi) * 0.1 }.play

+


+

x.release

+


+


+

// a default frequency could equivalently also be built into the model

+


+

(

+

Fb1_ODEdef(\harmonic_2, { |t, y|

+

[y[1], y[0].neg] * 500 * 2pi

+

}, 0, [0, 1], 1, 1); // default init values t0, y0 and scaling factors for output

+

)

+


+

x = { Fb1_ODE.ar(\harmonic_2) * 0.1 }.play

+


+

x.release

+


+


+


+

Ex.1b) Extending the harmonic oscillator to the mass-spring-damper model

+

+

// The mass-spring-damper model with externally applied force

+

// is described by the second order differential equation

+


+

// y''(t) * mass = f(t) - (dampen * y'(t)) - (spring * y(t))

+


+

// where dampen means the dampening factor and spring the spring stiffness.

+

// Again standard substitution 

+


+

// w(t) = y'(t)

+


+

// leads to the system of two first order equations

+


+

// y'(t) = w(t)

+

// w'(t) = (f(t) - (dampen * w(t)) - (spring * y(t))) / mass

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\MSD, { |t, y, f = 0, mass = 1, spring = 1, dampen = 0|

+

[

+

{ y[1] },

+

{ f - (dampen * y[1]) - (spring * y[0]) / mass }

+

]

+

}, 0, [0, 0], 1, 1);

+


+


+


+

// Here the resulting oscillation, which describes the position, is used for FM,

+

// the deflection converges to a value of 0.004, so take no LeakDC.

+


+

// LR difference results from slightly different linear mapping

+


+

(

+

x = {

+

// we need extreme values for fast oscillation

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+


+

// for t0, y0 default values are taken

+

sig = Fb1_ODE.ar(\MSD, 

+

[f, mass, spring, dampen],

+

leakDC: false

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.005, [100, 101], 700), 0, 0.1)

+

}.play

+

)

+


+

x.release

+


+


+

// for audible sound production take a time multiplier and a scaling factor

+

// and get a decaying sine, leakDC now true by default

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+

sig = Fb1_ODE.ar(\MSD, [f, mass, spring, dampen], tMul: 10);

+

Line.kr(dur: 10, doneAction: 2);

+

sig[0] * 20

+

}.play

+

)

+


+

x.release

+


+


+

// as MSD is predefined together with the wrapper class Fb1_MSD

+

// one can equivalently write

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+

sig = Fb1_MSD.ar(f, mass, spring, dampen, tMul: 10);

+

Line.kr(dur: 10, doneAction: 2);

+

sig[0] * 20

+

}.play

+

)

+


+

x.release

+


+


+

Ex.2) Numeric integration type

+

+

// For most cases the symmetric symplectic procedures with prefix 'sym' should be used (default 'sym2').

+

// They deliver best results with regard to long-term stability, see links at top.

+

// In case you suspect instability coming from numerics 

+

// you can take symplectic variants of higher order: 

+

// \sym2, \sym4, \sym6, \sym8, \sym12, \sym16, \sym32, \sym64.

+


+

// Note that already a simple harmonic oscillator can fail with a standard procedure

+


+

// This is decaying after a few seconds with classical Runge-Kutta 3rd order !

+

// ATTENTION: with other non-symplectic procedures this can lead to immediate blowups !

+

// Euler variants are especially bad.

+


+

(

+

Fb1_ODEdef(\harmonic, { |t, y|

+

[y[1], y[0].neg]

+

}, 0, [0, 1], 1, 1); 

+

)

+


+

(

+

x = {

+

var sig = Fb1_ODE.ar(\harmonic,

+

tMul: 1000 * 2pi,

+

intType: \rk3

+

);

+

Limiter.ar(sig) * 0.1 * EnvGen.ar(Env.asr(0.1, curve: 3))

+

}.play

+

)

+


+

x.release

+


+


+
Examples 3) Modulations

+


+

// All optional arguments of the Fb1_ODE can take UGens at audio or control rate,

+

// tMul can be modulated at both rates as well.

+


+


+

Ex.3a) Argument modulation

+

+

// additional oscillation of mass, this is already a quite extreme blurring

+

// operations of such kind can easily result in derailing the oscillaton

+


+

// here it's interesting to hear quite a difference between ar and kr variants

+


+

// ar modulation

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+

sig = Fb1_ODE.ar(\MSD, 

+

[f, mass * LFTri.ar(200).range(0.1, 2), spring, dampen],

+

leakDC: false

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.005, [100, 101], 700), 0, 0.1)

+

}.play

+

)

+

+

x.release

+


+


+

// much less smooth with kr

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+

sig = Fb1_ODE.ar(\MSD, 

+

[f, mass * LFTri.kr(200).range(0.1, 2), spring, dampen],

+

leakDC: false

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.005, [100, 101], 700), 0, 0.1)

+

}.play

+

)

+


+

x.release

+


+


+

// due to Fb1's design, above kr modulator is not interpolated

+

// an interpolated kr variant though gives an even more blurred result 

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+

sig = Fb1_ODE.ar(\MSD, 

+

[f, mass * K2A.ar(LFTri.kr(200).range(0.1, 2)), spring, dampen],

+

leakDC: false

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.005, [100, 101], 700), 0, 0.1)

+

}.play

+

)

+


+

x.release

+


+


+

Ex.3b) Time modulation

+


+


+

// kr modulation of time

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+

sig = Fb1_ODE.ar(\MSD, 

+

[f, mass, spring, dampen],

+

tMul: SinOsc.kr(0.6).range(0.5, 1.5),

+

leakDC: false

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.005, [100, 101], 700), 0, 0.1)

+

}.play

+

)

+


+

x.release

+


+

// ar modulation of time

+

// including mainly negative multipliers

+

// questionable physical sense, but can be fun

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+

sig = Fb1_ODE.ar(\MSD, 

+

[f, mass, spring, dampen],

+

tMul: SinOsc.ar(5).range(-5, 1.5),

+

leakDC: false,

+

withTimeChannel: true // include time as third channel

+

);

+

sig[2].poll;

+

SinOsc.ar(sig[0].linlin(0, 0.005, [100, 101], 700), 0, 0.1);

+

}.play

+

)

+


+

x.release

+


+


+

Ex.4) Initial values

+

+

// Initial values - system state y0 at time t0: y(t0) = y0 - are essential for an ODE solution.

+

// In this example with the MSD model (and constant f !) time isn't explicit,

+

// therefore it makes no difference if we start at t = 0 or t = 10, time is only shifted.

+


+

// However the position, described here by y, is time-dependent,

+

// so it makes a difference to start with different values

+


+

// y'(t) = w(t)

+

// w'(t) = (f(t) - (dampen * w(t)) - (spring * y(t))) / mass

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 50, dampen = 0.0005,

+


+

sig = Fb1_ODE.ar(\MSD,

+

[f, mass, spring, dampen],

+

t0: 10,  // this doesn't make a difference to default 0

+

y0: [0.2, 0],  // this does ! (compare with default [0, 0])

+

leakDC: false

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.005, [100, 101], 700), 0, 0.1)

+

}.play

+

)

+


+

x.release

+


+


+

Examples 5) The 'compose' argument

+


+

// This allows to build an additional Function into the Fb1 feedback loop.

+

// It is applied to every array of samples that is result and 

+

// next input of the numeric integration procedure.

+

// Obviously then correct integration of the ODE cannot be performed anymore, 

+

// but the option has still its value. 

+


+


+

Ex.5a) Handling instable systems

+


+

// mass spring damper with extra term y[0] * y[1] -

+

// out of interest, no physical argument for that

+


+

(

+

Fb1_ODEdef(\MSD_2, { |t, y, f = 0, mass = 1, spring = 1, dampen = 0|

+

[

+

y[1], 

+

f - (dampen * y[1]) - (spring * y[0]) + (y[0] * y[1]) / mass

+

]

+

}, 0, [0, 0], 1, 1);

+

)

+


+


+

// System derails after some seconds and produces nans.

+


+

(

+

x = {

+

    var f = 0.5, mass = 0.001, spring = 150, dampen = 0.001;

+

    var sig = Fb1_ODE.ar(\MSD_2, [f, mass, spring, dampen], leakDC: false).poll;

+

    LeakDC.ar(SinOsc.ar(sig[0].linlin(0, 0.006, [50, 50.1], 700), 0, 0.1))

+

}.play

+

)

+


+

x.release

+


+


+


+

// Keeping the system alive with clipping at LFO-controlled frequency.

+

// Note that the compose Function is passed an array as size of MSD_2 is 2,

+

// clip2 applied to an array again returns an array which is necessary,

+

// the Function must preserve the system size.

+


+

(

+

x = {

+

var f = 0.5, mass = 0.001, spring = 150, dampen = 0.001;

+

var lfo = LFDNoise3.kr(2).exprange(0.2, 1500);

+

var sig = Fb1_ODE.ar(\MSD_2, 

+

[f, mass, spring, dampen], 

+

leakDC: false,

+

compose: { |y| y.clip2(lfo) }

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.006, [50, 50.1], 700).poll, 0, 0.1)

+

}.play

+

)

+


+

x.release

+


+


+

// if an operator is passed via a Symbol, it applies to all channels

+


+

(

+

x = {

+

var f = 0.5, mass = 0.001, spring = 150, dampen = 0.001;

+

var sig = Fb1_ODE.ar(\MSD_2, 

+

[f, mass, spring, dampen], 

+

leakDC: false,

+

compose: \softclip

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.006, [50, 50.1], 700).poll, 0, 0.1)

+

}.play

+

)

+


+

x.release

+


+


+

Ex.5b) Other options of the 'compose' argument

+


+

// You might want to pass ar signals to the composition Function.

+

// This has to be done via the composeArIn argument, 

+

// its UGens are then passed as second argument to the compose Function.

+


+

// This somewhat odd way of passing is necessary as feeding ar signals into Fb1 is not trivial

+

// and must also be done via an extra 'in' argument - which in turn is used by Fb1_ODE.

+


+

(

+

x = {

+

var f = 0.5, mass = 0.001, spring = 150, dampen = 0.001;

+

var mod = SinOsc.ar(SinOsc.ar(LFDNoise3.ar(0.1).range(50, 1000)).range(10, 5000), 0, 0.1, 1);

+

var sig = Fb1_ODE.ar(\MSD,

+

[f, mass, spring, dampen],

+

leakDC: false,

+

compose: { |y, in| (y * in).tanh },

+

composeArIn: mod

+

);

+

SinOsc.ar(sig[0].linlin(0, 0.006, [50, 50.1], 700), 0, 0.07)

+

}.play

+

)

+


+

x.release

+


+


+

// note that above operation is totally different from applying the ring modulation + tanh

+

// after the integration, here the feedback process is much simpler:

+


+

(

+

x = {

+

var f = 0.5, mass = 0.001, spring = 150, dampen = 0.001;

+

var mod = SinOsc.ar(SinOsc.ar(LFDNoise3.ar(0.1).range(50, 1000)).range(10, 5000), 0, 0.1, 1);

+

var sig = (Fb1_ODE.ar(\MSD,

+

[f, mass, spring, dampen],

+

leakDC: false

+

) * mod).tanh;

+

SinOsc.ar(sig[0].linlin(0, 0.006, [50, 50.1], 700), 0, 0.07)

+

}.play

+

)

+


+

x.release

+


+


+

// The compose arg can also take an array of Functions and/or operators,

+

// the composeArIn arg can take an array of UGens,

+

// the compose Function(s) can refer to the components of the passed arrays. 

+


+

(

+

x = {

+

var f = 0.5, mass = 0.001, spring = 150, dampen = 0.001;

+

var mod = SinOsc.ar(SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(50, 100)).range(10, 5000), 0, 0.1, 1);

+

var sig = Fb1_ODE.ar(\MSD,

+

[f, mass, spring, dampen],

+

leakDC: false,

+

compose: [

+

{ |y, in| (y[0] * in[0]).softclip },

+

{ |y, in| ((y[0] + y[1]) * in[1]).softclip }

+

],

+

composeArIn: mod // mod is a stereo signal !

+

);

+

SinOsc.ar(sig[0..1].linlin(0, 0.006, 50, LFDNoise1.ar(0.1).range(700, 2000)), 0, 0.05)

+

}.play

+

)

+


+

x.release

+


+


+

// composeArIn's array can contain arrays also

+


+

(

+

x = {

+

var f = 0.5, mass = 0.001, spring = 150, dampen = 0.001;

+

var mod0 = Saw.ar(LFDNoise1.ar(1).exprange(3, 3000), 0.05);

+

var mod1 = SinOsc.ar(SinOsc.ar(LFDNoise3.ar(1 ! 2).range(1, 20)).range(1, 3000), 0, 0.1, 1);

+

var sig = Fb1_ODE.ar(\MSD,

+

[f, mass, spring, dampen],

+

leakDC: false,

+

compose: [

+

{ |y, in| (y[0] * in[1][0]).softclip },

+

{ |y, in| ((y[0] + y[1]) * in[1][1] + in[0]).softclip }

+

],

+

composeArIn: [mod0, mod1] // mod1 is itself a stereo signal !

+

);

+

SinOsc.ar(sig[0..1].linlin(0, 0.006, 50, LFDNoise1.ar(0.1).range(700, 1500)), 0, 0.05);

+

}.play

+

)

+


+

x.release

+


+


+


+

Examples 6) Channels with additional information

+


+

Ex.6a) The differential channels 

+

+

// The optional differential channel(s) contain the differential(s) at time t,

+

// in other terms: the value(s) F(t, Y) of the system

+

// Y'(t) = F(t, Y)

+


+

// For many numeric integration methods these values are buffered anyway,

+

// but not for the symplectic procedures.

+

// Therefore there exist variants with suffix '_d' which can be used in that case

+


+

// an oscillation produced by a Hopf ODE (see also Fb1_Hopf)

+

+

(

+

x = {

+

Fb1_ODE.ar(\Hopf,

+

[1, 2, 1],

+

1000,

+

intType: \sym2_d, // no difference to sym2 in the first two channels

+

) * 0.5

+

}.play

+

)

+


+

x.release

+

+

// The system has size two, so with differential we get 4 out channels,

+

// take the differential only, it gives a different sound colour (more partials).

+

+

(

+

x = {

+

Fb1_ODE.ar(\Hopf,

+

[1, 2, 1],

+

1000,

+

intType: \sym2_d,

+

withDiffChannels: true,

+

)[2..3] * 0.5

+

}.play

+

)

+


+

x.release

+

+

+

// The differential channels are basically buffering the slope of the 

+

// original (integration/solution) signal as a by-product, 

+

// slope could also be calculated with the Slope UGen.

+

// Note that we scaled time by a factor 1000, so the result of Slope

+

// has to be divided by the same factor.

+

+

(

+

x = {

+

Slope.ar(

+

Fb1_ODE.ar(\Hopf,

+

[1, 2, 1],

+

1000,

+

intType: \sym2_d,

+

withDiffChannels: true

+

)[0..1] * 0.5

+

) / 1000

+

}.play

+

)

+


+

x.release

+

+

+

Ex.6b) The time channel

+

+

// tMul also multiplies the integrated time, so here we proceed with 500 * 2pi per second ...

+


+

(

+

Fb1_ODEdef(\harmonic, { |t, y|

+

[y[1], y[0].neg]

+

}, 0, [0, 1], 1, 1); 

+

)

+

+

(

+

x = { 

+

var sig = Fb1_ODE.ar(\harmonic, 

+

tMul: 500 * 2pi,

+

withTimeChannel: true

+

);

+

sig[2].poll;

+

sig[0..1] * 0.1 // don't want to have time as audio output !

+

}.play

+

)

+

+

x.release

+

+

+

// ... whereas here time is scaled by the ODE

+

+

(

+

Fb1_ODEdef(\harmonic_2, { |t, y|

+

[y[1], y[0].neg] * 500 * 2pi

+

}, 0, [0, 1], 1, 1); 

+

)

+

+

(

+

x = { 

+

var sig = Fb1_ODE.ar(\harmonic_2, 

+

tMul: 1,

+

withTimeChannel: true

+

);

+

sig[2].poll;

+

sig[0..1] * 0.1 // don't want to have time as audio output !

+

}.play

+

)

+

+

x.release

+


+

+

// time integration might be of interest with modulations of tMul

+

+

(

+

x = {

+

var sig = Fb1_ODE.ar(\harmonic_2,

+

tMul: SinOsc.kr(0.2).exprange(0.2, 2),

+

withTimeChannel: true

+

);

+

sig[2].poll;

+

sig[0..1] * 0.07 // don't want to have time as audio output !

+

}.play

+

)

+


+

x.release

+


+


+

Ex.7) The 'withOutScale' argument

+


+


+

// It determines if default scaling parameters for the ODE integration signal and differential(s) should be applied

+

// In many cases this doesn't make a difference when the default scaling values equal 1.

+


+

// Certain ODEs though produce a very high amplitude level with usual standard params, like Lorenz.

+

// So it makes sense to scale the output down by default.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\Lorenz, { |t, y, s = 10, r = 30, b = 2|

+

[

+

{ (y[1] - y[0]) * s },

+

{ (r - y[2]) * y[0] - y[1] },

+

{ (y[0] * y[1]) - (b * y[2]) }

+

]

+

}, 0, [1, 1, 1], 0.01, 0.01); // scaling parameters 0.01

+


+


+

// So there's no problem to use Lorenz with standard params as

+

// 'withOutScale' defaults to true

+


+

(

+

x = {

+

var s = 10, r = 30, b = 3, tMul = MouseX.kr(50, 150, 1).poll,

+

sig;

+

sig = Fb1_ODE.ar(\Lorenz, [s, r, b], tMul);

+

sig[0..1]

+

}.play;

+

)

+


+

x.release

+


+


+

// WARNING: it's not recommended to set 'withOutScale' to false

+

// as in cases like this you get very high levels !

+

// So here we have to reduce the level outside the Fb1_ODE

+


+

(

+

x = {

+

var s = 10, r = 30, b = 3, tMul = MouseX.kr(50, 150, 1).poll,

+

sig;

+

sig = Fb1_ODE.ar(\Lorenz, [s, r, b], tMul, withOutScale: false);

+

sig[0..1] / 100

+

}.play;

+

)

+


+

x.release

+


+


+

// WARNING: 'withOutScale' does not implement a general safety net functionality, 

+

// withOutScale's default value true does by no means say that output is limited. 

+

// The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

// under different circumstances, always lead to high out levels. 

+


+


+

+

Examples 8) ODEs from mechanics

+


+

Ex.8a) The driven pendulum

+


+

// The driven pendulum is interesting as it's a quite simple model

+

// that includes parameter zones of chaotic behaviour:

+

// http://lampx.tugraz.at/~hadley/physikm/apps/numerical_integration/pendulum.en.php

+


+

// y here denotes the angle

+


+

// y''(t) + (y'(t) / q) + sin(y(t)) = a * cos(omega * t)

+


+

// which translates to

+


+

// y'(t) = w(t)

+

// w'(t) = (-w(t) / q) - sin(y(t)) + (a * cos(omega * t))

+


+


+

(

+

Fb1_ODEdef(\DrivenPendulum, { |t, y, q = 1, omega = 0, a = 1|

+

[

+

{ y[1] },

+

{ y[1] / q.neg - sin(y[0]) + (a * cos(omega * t)) }

+

]

+

}, 0, [0, 0], 1, 1);

+

)

+


+


+

// move the mouse through a zone of chaotic behaviour between 0.65 and 0.68

+


+

(

+

x = { 

+

var sig = Fb1_ODE.ar(\DrivenPendulum, [2.3, MouseX.kr(0.65, 0.68).lag(2).poll, 1.6],

+

2000,

+

0, [0, 1],

+

);

+

sig.poll;

+

sig[1] ! 2 * 0.1

+

}.play

+

)

+


+

x.release

+


+


+

Ex.8b) The reduced two body problem

+


+

// Two planets are moving around the common barycenter, thus the system can be simplified

+

// to an ODE which describes the changes of the the relative vector between the masses.

+


+

// E.g. see https://evgenii.com/blog/two-body-problem-simulator

+


+


+

(

+

Fb1_ODEdef(\TwoBodyReduced, { |t, y, m1, m2|

+

var v = -1 - (m1 / m2);

+

var r = y[0..1].squared.sum.sqrt;

+

[

+

{ y[2] },

+

{ y[3] },

+

{ v * y[0] / r.cubed },

+

{ v * y[1] / r.cubed }

+

]

+

}, 0, [0, 0, 0, 0], 1, 1);

+

)

+


+

// It turns out that this system is numerically very sensitive.

+

// Standard procedures are failing soon: here classical Runge-Kutta 4rth order

+

// leads to a glissando before it collapses.

+

// Even 2nd order symplectic shows movement of wave forms, 

+

// though it keeps frequency and preserves volume 

+


+

// here we take only the first two components which represent the plane coordinates

+

// of the difference vector

+


+

// sym8 still shows a slow morphing of wave forms

+

// you can try with \sym64, very inefficient, but even more steady

+


+

(

+

x = {

+

var m = [1, 1.5];

+

Fb1_ODE.ar(\TwoBodyReduced, m,

+

100, 0, [0, 0.2, 2, 1],

+

intType: \sym8

+

)[0..1]

+

}.play

+

)

+


+

s.scope

+


+

x.release

+


+


+

Examples 9) ODEs from population dynamics

+


+

Ex.9a) The Lotka-Volterra equations

+


+

// They describe populations of predators and prey, e.g. foxes and rabbits.

+

// https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations

+


+


+

// v'(t) = (alpha * v(t)) - (beta * v(t) * w(t))

+

// w'(t) = (delta * v(t) * w(t)) - (gamma * w(t))

+


+


+

(

+

Fb1_ODEdef(\LotkaVolterra, { |t, y, alpha = 0.1, beta = 0.1,

+

gamma = 0.1, delta = 0.1|

+

[

+

{ y[0] * (beta.neg * y[1] + alpha) },

+

{ y[1].neg * (delta.neg * y[0] + gamma) }

+

]

+

}, 0, [1, 1], 1, 1);

+

)

+


+

// with standard parameter usage the audio results are brass-like and not spectacular

+


+

(

+

x = {

+

Fb1_ODE.ar(\LotkaVolterra,

+

[0.2, MouseX.kr(0.1, 0.9), MouseY.kr(0.1, 0.9), 0.8],

+

tMul: 3000

+

) * 0.1

+

}.play

+

)

+


+

x.release

+


+


+

Ex.9b) The Hastings-Powell equations

+


+

// This is an extension of the Lotka-Volterra model and

+

// considers the fact that rabbits must eat too.

+

// It is described in the article

+


+

// Alan Hastings and Thomas Powell (1991). 

+

// "Chaos in a Three-Species Food Chain" Ecology 72(3): 896-903

+


+

// and on page 165 of the book "Exploring ODEs"

+


+

// Trefethen, Lloyd N.; Birkisson, Ásgeir; Driscoll, Tobin A. (2017):

+

// Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics

+

// Free download from:

+

// http://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+


+

// We take over initial values and parameters from the example given there:

+


+

(

+

Fb1_ODEdef(\CarrotsRabbitsFoxes, { |t, y, a1 = 5, a2 = 0.1,

+

b1 = 0.1, b2 = 2, d1 = 0.4, d2 = 0.01|

+

var p = a1 * y[0] / (1 + (b1 * y[0])) * y[1];

+

var q = a2 * y[1] / (1 + (b2 * y[1])) * y[2];

+

[

+

{ y[0] * (1 - y[0]) - p }, // carrots

+

{ p - q - (d1 * y[1])  }, // rabbits

+

{ q - (d2 * y[2])  } // foxes

+

]

+

}, 0, [0.4, 1, 9], 1, 1);

+

)

+


+


+

// In stereo we hear population oscillations of carrots and rabbits,

+

// it takes some fractions of a second at begin after a state is reached.

+

// Check with mouse and see post window:

+

// b1 goes through a zone of chaotic behaviour between 2.5 and 3.5

+


+

(

+

x = {

+

var a1 = 5, a2 = 0.1, b1 = MouseX.kr(2.5, 3.5).poll, b2 = 2,

+

d1 = 0.4, d2 = 0.01, tMul = 5000, amp = 0.2;

+

Fb1_ODE.ar(\CarrotsRabbitsFoxes,

+

[a1, a2, b1, b2, d1, d2],

+

tMul, 0, [0.4, 1, 9]

+

)[0..1] * amp

+

}.play

+

)

+


+

x.release

+


+


+


+


+ + diff --git a/Help/Fb1_ODEdef.html b/Help/Fb1_ODEdef.html new file mode 100755 index 0000000..22116b9 --- /dev/null +++ b/Help/Fb1_ODEdef.html @@ -0,0 +1,441 @@ + + + + + + + + + + + +

Fb1_ODEdef container for ordinary differential equation definitions

+


+

Part of: miSCellaneous

+


+

To be used to define ODE systems that can then be audified with Fb1_ODE. See Fb1_ODE help for general information about Fb1 ODE integrator UGens.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [2], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [3]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE help Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: Fb1_ODE in its plain form (without tMul modulation, use of compose etc.) produces audio data as a numerical integration of an ODE initial value problem, defined by Fb1_ODEdef and a numerical procedure, defined by Fb1_ODEintdef. This of course supposes well-defined ODE systems and it should be kept in mind that the Fb1_ODE framework doesn't perform any mathematical checks regarding the principal existence and uniqueness of a solution of a given Fb1_ODEdef. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved.  

+


+

See also: Fb1_ODE, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[2] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[3] https://git.iem.at/davidpirro/henri

+


+


+

Creation / Class Methods

+


+

*new (name, function, t0, y0, outScale = 1, diffOutScale = 1)

+

+

Creates a new Fb1_ODEdef object and stores it in a Dictionary for further usage with Fb1_ODE.

+

+

name - The name of the ODE, expects a Symbol.

+

Default Fb1_ODEdefs cannot be overwritten.

+

function - The implementation of the function F which describes the ODE given as 

+

Y'(t) = F(t, Y(t)) where Y, F can be a single-valued or vector-valued.

+

It must take time as first and a system state (possibly an array) as second parameter 

+

and optional further args that can also be arrays.

+

The system state is expected to have the size of the system (given by y0's size), 

+

the function's return value must also equal this size.

+

If the system size is greater than 1 the components of the output array

+

should be rather written as Functions, as this optimizes the compile process

+

with symplectic integration procedures, it isn't compulsory though.

+

See examples below and in the source file Fb1_ODEdef.sc.  

+

t0 - Number. Default initial time value. 

+

y0 - Default initial value of the ODE. 

+

Expects number or array and determines the size of the system, function's second arg

+

and return value must be of same size.

+

outScale - Number that determines the default multiplier for the integration signal 

+

produced by Fb1_ODE when the latter's withOutScale flag is set to true (default).

+

Defaults to 1. Especially thought for systems like Lorenz which produce high levels

+

with standard parameters, then it makes sense to set outScale to a value smaller than 1.

+

WARNING: outScale / diffOutScale / withOutScale do not implement a 

+

general safety net functionality, withOutScale's default value true does by no means say 

+

that output is limited. Scaling values of Fb1_ODEdefs can only be average assumptions and can, 

+

under different circumstances, always lead to high out levels.

+

diffOutScale - Number that determines the default multiplier for the differential signal 

+

produced by Fb1_ODE when the latter's withOutScale flag is set to true (default).

+

Defaults to 1. Especially thought for systems like Lorenz which produce high levels

+

with standard parameters, then it makes sense to set diffOutScale to a value smaller than 1.

+

WARNING: outScale / diffOutScale / withOutScale do not implement a 

+

general safety net functionality, withOutScale's default value true does by no means say 

+

that output is limited. Scaling values of Fb1_ODEdefs can only be average assumptions and can, 

+

under different circumstances, always lead to high out levels.

+


+


+

*at (key)

+

+

Returns the Fb1_ODEdef instance of the Symbol key if it exists.

+


+

*keys

+

+

Returns an array of all keys of currently stored Fb1_ODEdefs.

+

+

*postAll

+

+

Posts all keys of currently stored Fb1_ODEdefs.

+


+

*remove (key)

+

+

Removes the Fb1_ODEdef of the Symbol key from the Dictionary.

+

+

*reset

+

+

Removes all Fb1_ODEdefs other than the predefined ones from the Dictionary.

+

+


+

+

Instance Methods

+


+

next (intType, t, y, dt, args)

+

+

Method for language-side integration, gives next value based on previous data.

+

+

intType - Integration type.

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym', like 'sym2') is highly recommended.

+

For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Multi-step procedures are not implemented, remaining:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

t - Previous time, expects Number.

+

y - Previous state, expects Number or Array of system size.

+

dt - Number. Time delta. 

+

args - List of additional args to be passed to Fb1_ODEdef's function.

+


+


+

nextN (n, intType, t, y, dt, args, withTime = false, includeStart = true)

+

+

Method for language-side integration, gives an array of next n values (arrays) based on previous data.

+

Last values (arrays) are stored at last position. For intTypes with sizeFactor 2 the differential is included.

+

See Examples.

+

+

n - Integer. Number of next values (resp. value arrays) to be calculated.

+

intType - Integration type.

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym', like 'sym2') is highly recommended.

+

For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Multi-step procedures are not implemented, remaining:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

t - Previous time, expects Number.

+

y - Previous state, expects Number or Array of system size.

+

dt - Number. Time delta. 

+

args - List of additional args to be passed to Fb1_ODEdef's function.

+

withTime - Boolean. Determines if integrated time should be included. 

+

Defaults to false.

+

includeStart - Boolean. Determines if start value(s) should be included.

+

Defaults to true.

+


+


+

name 

+

+

Getter for the Fb1_ODEdef's name.

+


+

function 

+

+

Getter for the Fb1_ODEdef's function.

+


+

t0 

+

+

Getter for the Fb1_ODEdef's t0.

+


+

y0 

+

+

Getter for the Fb1_ODEdef's y0.

+


+

outScale 

+

+

Getter for the Fb1_ODEdef's outScale.

+


+

diffOutScale 

+

+

Getter for the Fb1_ODEdef's diffOutScale.

+


+

size 

+

+

Getter for the Fb1_ODEdef's system size.

+


+

+


+

Examples 1) Defining new ODEs

+


+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+


+

Ex.1a) Extending the harmonic oscillator

+


+

// This seems to be an interesting strategy.

+

// We can e.g. try to multiply one of its equations with a term near 1,

+

// so start with rather small k and investigate

+


+

// y'(t) = w(t)

+

// w'(t) = -y(t) * (1 + (k * w(t))

+


+

 

+

(

+

Fb1_ODEdef(\harmonic_ext_1, { |t, y, k|

+

[

+

y[1], 

+

y[0].neg * (1 + (k * y[1]))

+

]

+

}, 0, [0, 1], 1, 1); 

+

)

+


+


+

// brassy sound

+


+

(

+

x = {

+

var sig = Fb1_ODE.ar(\harmonic_ext_1,

+

[1], 1500, 0, [0, 1],

+

) * 0.1;

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

// with oscillating k the system tends to become unstable

+

// but with softclip per sample it can be kept 

+

// (see chapter 'compose' option in Fb1_ODE help)

+


+

(

+

x = {

+

var sig = Fb1_ODE.ar(\harmonic_ext_1,

+

// k oscillates between 1 and 3

+

[SinOsc.ar(50).lincurve(-1, 1, 1, 3, 2)],

+

1500, 0, [0, 1],

+

compose: \softclip

+

) * 0.1;

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

// an oscillation between 1 and higher values totally changes the spectrum

+


+

(

+

x = {

+

var sig = Fb1_ODE.ar(\harmonic_ext_1,

+

// k oscillates between 1 and 10

+

[SinOsc.ar(50).lincurve(-1, 1, 1, 10, 2)],

+

1500, 0, [0, 1],

+

compose: \softclip

+

) * 0.1;

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

// play with the two states

+


+

(

+

x = {

+

var sig = Fb1_ODE.ar(\harmonic_ext_1,

+

// upper oscillation bound for k oscillates itself between 2 and 15

+

[SinOsc.ar(50).lincurve(-1, 1, 1, SinOsc.ar(SinOsc.ar(0.2).exprange(0.2, 15)).range(2, 15), 2)],

+

1500, 0, [0, 1],

+

compose: \softclip

+

) * 0.1;

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

Ex.1b) Extending exponential decay

+


+

// exponential decay is described by the equation

+

// y'(t) = -y(t)

+


+

// an oscillating decay can e.g. be got by

+

// y'(t) = -y(t) * sin(t)

+

// the analytic solution includes a log of the sine,

+

// so we get more partials

+


+


+

(

+

Fb1_ODEdef(\exp_decay_raw, { |t, y|

+

y.neg * sin(t)

+

}, 0, 1, 1, 1);

+

)

+


+


+

(

+

x = {

+

var sig = Fb1_ODE.ar(\exp_decay_raw,

+

tMul: 100 * 2pi,

+

compose: \softclip

+

) ! 2;

+

Line.kr(dur: 10, doneAction: 2);

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

// multiplication with a second sine with multiplied time leads to strange and interesting results

+


+

(

+

Fb1_ODEdef(\exp_decay_extended, { |t, y, k|

+

y.neg * (sin(t) * sin(k * t))

+

}, 0, 1, 1, 1);

+

)

+


+


+


+

// ATTENTION: danger of blowup, can be reduced with softclip composition per sample

+

// constant values lead to ring modulation-like effects ...

+


+

(

+

x = {

+

var sig = Fb1_ODE.ar(\exp_decay_extended,

+

[2.7], 100 * 2pi, 0, 1,

+

compose: \softclip

+

);

+

Line.kr(dur: 10, doneAction: 2);

+

sig ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// ... whereas modulations produce more complex changing spectra

+

(

+

x = {

+

var sig = Fb1_ODE.ar(\exp_decay_extended,

+

[SinOsc.ar(120).range(3, 3.01)], 100 * 2pi, 0, 1,

+

compose: \softclip

+

);

+

Line.kr(dur: 10, doneAction: 2);

+

sig ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// for decorrelated stereo we can expand to two independent equations

+

// k should be of size 2

+


+

(

+

Fb1_ODEdef(\exp_decay_extended_2, { |t, y, k|

+

[

+

y[0].neg * (sin(t) * sin(k[0] * t)),

+

y[1].neg * (sin(t) * sin(k[1] * t))

+

]

+

}, 0, [1, 1], 1, 1);

+

)

+


+

(

+

x = {

+

var sig = Fb1_ODE.ar(\exp_decay_extended_2,

+

[SinOsc.ar(120).range([3, 3.01], [3.01, 3.02])], 100 * 2pi, 0, [1, 1],

+

compose: \softclip

+

);

+

Line.kr(dur: 10, doneAction: 2);

+

sig

+

}.play

+

)

+


+

x.release

+


+


+

Ex.2) Language-side integration

+


+

// Possible though not optimized.

+

// For longer sections it's probably better to employ Fb1 ODE solvers, 

+

// store audio in a buffer and load it into a float array,

+

// for quick tests it might be useful though. 

+


+

// integrate begin of a Lorenz system, last array is last state, so flop for plot

+


+

(

+

a = Fb1_ODEdef.at(\Lorenz).nextN(

+

n: 1000,

+

intType: \sym2,

+

t: 0, 

+

y: [1, 1, 1],

+

dt: 1/100,

+

args: [30, 12, 2]

+

);

+


+

a.flop.plot

+

)

+


+


+


+


+ + diff --git a/Help/Fb1_ODEintdef.html b/Help/Fb1_ODEintdef.html new file mode 100755 index 0000000..d2923ca --- /dev/null +++ b/Help/Fb1_ODEintdef.html @@ -0,0 +1,180 @@ + + + + + + + + + + + +

Fb1_ODEintdef container for ordinary differential equation numeric integrators

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

For the optional definition of ODE integration procedures that can then be used with Fb1_ODE. This is an advanced feature, the built-in symplectic procedures should suffice for most use cases. See the source code in Fb1_ODEintdef.sc for the way integrators are defined and Fb1_ODE help for general information about Fb1 ODE integrator UGens.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [2], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [3]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE help Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: Fb1_ODE in its plain form (without tMul modulation, use of compose etc.) produces audio data as a numerical integration of an ODE initial value problem, defined by Fb1_ODEdef and a numerical procedure, defined by Fb1_ODEintdef. This of course supposes well-defined ODE systems and it should be kept in mind that the Fb1_ODE framework doesn't perform any mathematical checks regarding the principal existence and uniqueness of a solution of a given Fb1_ODEdef. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved.  

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[2] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[3] https://git.iem.at/davidpirro/henri

+


+


+

Creation / Class Methods

+


+

*new (name, function, stepDepth = 1, sizeFactor = 1)

+

+

Creates a new Fb1_ODEintdef object and stores it in a Dictionary for further usage with Fb1_ODE.

+

+

name - The name of the integration procedure, expects a Symbol.

+

Default Fb1_ODEintdefs cannot be overwritten.

+

function - The implementation of the Function ("stepper") which describes the numeric procedure. 

+

It must take the arguments odeDef, t, y, dt and ... args for further ODE args and

+

must return the new integration value(s). For evaluating the ODE function 

+

the method Fb1_ODEdef::value is used which performs the evaluation depending on its first argument.

+

stepDepth - Integer. Values greater than one are for multi-step procedures. 

+

Defaults to 1. 

+

sizeFactor - Integer. 1 means that the procedure buffers only integration values,

+

2 means that the differential is buffered too. 

+

Defaults to 1. 

+


+


+

*at (key)

+

+

Returns the Fb1_ODEintdef instance of the Symbol key if it exists.

+


+

*keys

+

+

Returns an array of all keys of currently stored Fb1_ODEintdefs.

+

+

*postAll

+

+

Posts all keys of currently stored Fb1_ODEintdefs.

+


+

*remove (key)

+

+

Removes the Fb1_ODEintdef of the Symbol key from the Dictionary.

+

+

*reset

+

+

Removes all Fb1_ODEintdefs other than the predefined ones from the Dictionary.

+

+


+

+

Instance Methods

+


+

name 

+

+

Getter for the Fb1_ODEintdef's name.

+


+

function 

+

+

Getter for the Fb1_ODEintdef's function.

+


+

stepDepth

+

+

Getter for the Fb1_ODEintdef's stepDepth.

+


+

sizeFactor 

+

+

Getter for the Fb1_ODEintdef's sizeFactor.

+


+

+


+

Examples

+


+

// This shows how the classical 4-step Runge-Kutta is implemented,

+

// quite straight it follows its mathematical definition.

+


+

// t: the old time

+

// y: the old state (array)

+

// dt: the integration step

+

// size: the size of the ODE (not used here, but needs to be there)

+

// ... args is for further args of the ODE

+


+

// The odeDef argument expects the Fb1_ODEdef object.

+

// The evaluation of the ODE function is implemented by odeDef::value.

+

// This method takes a first argument i which determines the kind of evaluation:

+

// For i == nil it returns the evaluation value(s) as Array,

+

// for a number it only returns the ith component.

+

// The distinction is necessary as the symplectic procedures refer to

+

// the single components of the ODE system.

+


+

// There exist also symplectic Runge-Kutta procedures which I didn't test so far.

+

// they would probably also need that option.

+


+

Fb1_ODEintdef(\rk4,

+

{ |odeDef, t, y, dt, size ... args|

+

var k1 = odeDef.(nil, t, y, *args);

+

var k2 = odeDef.(nil, dt * 0.5 + t, k1 * dt * 0.5 + y, *args);

+

var k3 = odeDef.(nil, dt * 0.5 + t, k2 * dt * 0.5 + y, *args);

+

var k4 = odeDef.(nil, dt + t, k3 * dt + y, *args);

+

((k2 + k3) * 2 + k1 + k4) * dt / 6 + y

+

}, 1, 1);

+


+


+

// The same procedure as '_d' variant which stores the differential

+

// The argument y is now passed an array of doubled size with the

+

// last differential values in the second half of it.

+

// Finally the new differential value is calculated and appended to the integration.

+


+

Fb1_ODEintdef(\rk4_d,

+

{ |odeDef, t, y, dt, size ... args|

+

var k1, k2, k3, k4, yy, ff, yNew, fNew;

+

yy = y[0..size-1];

+

ff = y[size..2*size-1];

+

k1 = ff;

+

k2 = odeDef.(nil, dt * 0.5 + t, k1 * dt * 0.5 + yy, *args);

+

k3 = odeDef.(nil, dt * 0.5 + t, k2 * dt * 0.5 + yy, *args);

+

k4 = odeDef.(nil, dt + t, k3 * dt + yy, *args);

+

yNew = ((k2 + k3) * 2 + k1 + k4) * dt / 6 + yy;

+

fNew = odeDef.(nil, t + dt, yNew, *args);

+

yNew ++ fNew

+

}, 1, 2);

+


+ + diff --git a/Help/Fb1_SD.html b/Help/Fb1_SD.html new file mode 100755 index 0000000..a4a9f7b --- /dev/null +++ b/Help/Fb1_SD.html @@ -0,0 +1,325 @@ + + + + + + + + + + + +

Fb1_SD spring damper model pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

Fb1_ODE wrapper for the spring damper equation of motion with unit mass:

+


+

y''(t) = f(t) - (dampen * y'(t)) - (spring * y(t)) 

+


+

It returns a 2-channel signal with position and velocity. Fb1_SD is slightly more efficient than Fb1_MSD and you can always rewrite. See Fb1_ODE help for general information about Fb1 ODE integrator UGens and further MSD examples.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [2], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [3]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. 

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_VanDerPol, Fb1_Duffing

+


+

References:

+


+

[1] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[2] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[3] https://git.iem.at/davidpirro/henri

+


+


+

Creation / Class Methods

+


+

*new (f = 0, spring = 1, dampen = 0, tMul = 1, t0 = 0, y0 = #[0, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_SD ar object.

+

+

f - External force. Defaults to 0.

+

spring - Spring stiffness. Defaults to 1.

+

dampen - Dampening factor. Defaults to 0.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[0, 0].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use 

+

a reduced server blockSize in order to reduce SynthDef compile time.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (f = 0, spring = 1, dampen = 0, tMul = 1, t0 = 0, y0 = #[0, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (f = 0, spring = 1, dampen = 0, tMul = 1, t0 = 0, y0 = #[0, 0],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_SD kr object.

+

+

f - External force. Defaults to 0.

+

spring - Spring stiffness. Defaults to 1.

+

dampen - Dampening factor. Defaults to 0.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[0, 0].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-control-block base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples

+


+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\SD, { |t, y, f = 0, spring = 1, dampen = 0|

+

[

+

{ y[1] },

+

{ f - (dampen * y[1]) - (spring * y[0]) }

+

]

+

}, 0, [0, 0], 1, 1);

+


+


+


+

// 2nd channel (velocity) generates decaying sine wave

+

// (position does also, but is softer)

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 1500, dampen = 0.003,

+

sig = Fb1_SD.ar(f/mass, spring/mass, dampen/mass);

+

sig[1] ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// oscillation as external force

+

// stays while spring oscillation decays

+


+

(

+

x = {

+

var f = SinOsc.ar(500, 0, 0.3), mass = 0.001, spring = 1500, dampen = 0.003,

+

sig = Fb1_SD.ar(f/mass, spring/mass, dampen/mass);

+

sig[1] ! 2

+

}.play

+

)

+


+

x.release

+


+


+

// SD as modulator, kr saves ugens

+

// usable if, as here, it produces rather low frequencies

+


+

(

+

x = {

+

var f = 0.2, mass = 0.001, spring = 200, dampen = 0.003,

+

sig = Fb1_SD.kr(f/mass, spring/mass, dampen/mass);

+

SinOsc.ar(sig[1].linlin(-1, 1, [100, 90], 700), 0, 0.2)

+

}.play

+

)

+


+

x.release

+


+


+


+ + diff --git a/Help/Fb1_VanDerPol.html b/Help/Fb1_VanDerPol.html new file mode 100755 index 0000000..1e3bf69 --- /dev/null +++ b/Help/Fb1_VanDerPol.html @@ -0,0 +1,348 @@ + + + + + + + + + + + +

Fb1_VanDerPol Van der Pol pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

Fb1_ODE wrapper for the Van der Pol ODE system with external f:

+


+

y'(t) = w(t)

+

w'(t) = f(t) + (mu * (1 - y(t)^2) * w(t)) - (theta^2 * y(t)) 

+


+

coming from the 2nd order equation

+


+

y''(t) = f(t) + (mu * (1 - y(t)^2) * y'(t)) - (theta^2 * y(t)) 

+


+

It returns a 2-channel signal. The parameter naming follows the convention of [2]. As described in [1] and [2] adaptive variants can be made in the same way as for Hopf and other oscillators. See Fb1_ODE help for general information about Fb1 ODE integrator UGens.

+


+

HISTORY AND CREDITS: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation [4], pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri [5]. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse.  

+


+

WARNING: The usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See Fb1_ODE Ex.5) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. 

+


+

NOTE: The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved.  

+


+

See also: Fb1_ODE, Fb1_ODEdef, Fb1_ODEintdef, Fb1, Fb1_MSD, Fb1_SD, Fb1_Lorenz, Fb1_Hopf, Fb1_HopfA, Fb1_HopfAFDC, Fb1_Duffing

+


+

References:

+


+

[1] Righetti, Ludovic; Buchli, Jonas; Ijspeert, Auke Jan (2009): 

+

"Adaptive Frequency Oscillators and Applications". 

+

The Open Cybernetics and Systemics Journal, 3, 64-69.

+

https://www.researchgate.net/publication/41666931_Adaptive_Frequency_Oscillators_and_Applications_Open_Access

+

Summary: https://biorob.epfl.ch/research/research-dynamical/page-36365-en-html/

+

[2] Nachstedt, Timo; Tetzlaff, Christian; Manoonpong, Poramate (2017): 

+

"Fast Dynamical Coupling Enhances Frequency Adaptation of Oscillators for Robotic Locomotion Control". 

+

Frontiers in Neurorobotics. Published online 2017 Mar 21

+

https://www.frontiersin.org/articles/10.3389/fnbot.2017.00014/full

+

[3] Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): 

+

Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics.

+

Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf

+

[4] Pirrò, David (2017). Composing Interactions. Dissertation. 

+

Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz.

+

Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf

+

[5] https://git.iem.at/davidpirro/henri

+


+


+

Creation / Class Methods

+


+

*new (f = 0, mu = 1, theta = 1, tMul = 1, t0 = 0, y0 = #[1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_VanDerPol ar object.

+

+

f - Defaults to 0.

+

mu - Defaults to 1.

+

theta - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / sample duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr / ar UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[1, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym2. For more accurate integration you can try symplectic procedures of

+

higher order like \sym4, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-sample base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

A Function can optionally take a second argument which is for ar UGens passed via composeArIn.

+

composeArIn - ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both.

+

This is the way to use ar UGens within a Function passed to compose.

+

UGens are passed to the Function's second argument.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be derived from the default server's properties

+

(sample duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

blockSize - Integer, this should be the server blockSize. 

+

If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). 

+

So explicitely passing a blockSize should only be necessary in special cases,

+

e.g. when compiling before booting.

+

However for a pleasant workflow for ar usage it's recommended to use 

+

a reduced server blockSize in order to reduce SynthDef compile time.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+

 

+


+

*ar (f = 0, mu = 1, theta = 1, tMul = 1, t0 = 0, y0 = #[1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Equivalent to *new.

+

+

*kr (f = 0, mu = 1, theta = 1, tMul = 1, t0 = 0, y0 = #[1, 1],

+

intType = \sym2, compose, composeArIn, dt0, argList0, init_intType = \sym8,

+

withOutScale = true, withDiffChannels = false, withTimeChannel = false, blockSize,

+

graphOrderType = 1, leakDC = true, leakCoef = 0.995)

+

+

Creates a new Fb1_VanDerPol kr object.

+

+

f - Defaults to 0.

+

mu - Defaults to 1.

+

theta - Defaults to 1.

+


+

tMul - Time multiplier, which determines velocity of proceeding in the dynamic system.

+

The default value 1 means that the step delta of time equals 1 / control duration.

+

For getting audible oscillations you might, depending on the ODE definition, have to pass

+

much higher values. Can also be a kr UGen and might also be negative.

+

t0 - Initial time. Expects Number. 

+

Defaults to 0.

+

y0 - Initial value of the ODE. 

+

Expects array of size 2.

+

Defaults to #[1, 1].

+

intType - Integration type. 

+

Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef.

+

The use of symplectic procedures (with prefix 'sym') is highly recommended.

+

Defaults to \sym4. For more accurate integration you can try symplectic procedures of

+

higher order like \sym6, \sym8 etc. Families of integration procedures:

+

Symplectic: 

+

\sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, 

+

\sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d

+

Euler: 

+

\eu, \eu_d, \eum, \eum_d, \eui, \eui_d

+

Prediction-Evaluation-Correction: 

+

\pec, \pece, \pecec, \pecece

+

Runge-Kutta: 

+

\rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d

+

Adams-Bashforth: 

+

\ab2, \ab3, \ab4, \ab5, \ab6

+

Adams-Bashforth-Moulton: 

+

\abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66

+

compose - Operator(s) / Function(s) to be applied to the system value on a per-control-block base.

+

This of course blurs the numeric procedure but can be used for containing or in a creative way.

+

Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof.

+

The Functions are in any case expected to take an array (the system state) as first argument.

+

If only one Function is given it must output an array of same (system) size.

+

Within a SequenceableCollection a Function must output a value of size 0.

+

Within a SequenceableCollection an operator Symbol is applied only to the corresponding component.

+

dt0 - First time delta in seconds to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

In case of a multi-step procedure a dt0 value will be assumed from the default server's properties

+

(control duration * tMul).

+

argList0 - Initial argList value(s) to be used for the language-side calculation of 

+

first values of a multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

If no UGens are passed to argList, values will be assumed from passed argList Numbers.

+

init_intType - Integration type for language-side calculation of first values of a 

+

multi-step intType procedure.

+

This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred.

+

Defaults to \sym8.

+

withOutScale - Boolean. Determines if the Fb1_ODEdef's default scaling values for

+

integration and differential signals should be applied to the output.

+

Defaults to true.

+

WARNING: withOutScale does not implement a general safety net functionality, 

+

withOutScale's default value true does by no means say that output is limited. 

+

The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, 

+

under different circumstances, always lead to high out levels. 

+

withDiffChannels - Boolean. Determines if channel(s) with differential value(s) should be returned.

+

This is only applicable with integration types with a sizeFactor == 2, which means that for every sample

+

the values of the differential are buffered. As by default this is not the case for all predefined 

+

numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d.

+

Defaults to false.

+

withTimeChannel - Boolean. Determines if accumulated time is output in an additional channel. 

+

WARNING: with constant tMul it produces an ascending DC which is not affected by leakDC 

+

if this is set to true. Might especially be of interest if time is modulated.

+

Defaults to false.

+

graphOrderType - 0, 1 or 2. 

+

Determines if topological order of generated BufRd and BufWr instances

+

in the SynthDef graph is forced by additional UGens. 

+

Type 0: forced graph order is turned off.

+

Type 1 (default): graph order is forced by summation and <!.

+

Type 2: graph order is forced by <! operators only.

+

Default 1 is recommended, but with CPU-intense SynthDefs it might be worth trying it with the value 0. 

+

This saves a lot of UGens though there might be exceptional cases with different results.

+

Type 2 can shorten the SynthDef compilation time for certain graphs with a large number of UGens,

+

which can be lengthy with type 1.

+

However, CPU usage doesn't directly correspond to the number of UGens.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output (except time channel).

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+


+


+

Examples

+


+

// reboot with reduced blockSize

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.options.blockSize = 8;

+

s.reboot;

+

)

+


+

// In the source code of Fb1_ODEdef.sc the corresponding Fb1_ODEdef looks like this:

+

// the Function brackets within the array are not absolutely necessary, 

+

// but compile process is faster.

+


+

// don't evaluate, already stored

+


+

Fb1_ODEdef(\VanDerPol, { |t, y, f = 0, mu = 1, theta = 1|

+

var y0s;

+

y0s = y[0] * y[0];

+

[

+

{ y[1] },

+

{ mu * (1 - y0s) * y[1] + f - (theta * theta * y[0]) }

+

]

+

}, 0, [1, 1], 0.05, 0.05);  // scaling as standard params lead to high level output

+


+


+


+

// without external force

+

// control param mu (for high values of mu the oscillator crashes)

+

// mu == 0: harmonic oscillator

+


+

x = { Fb1_VanDerPol.ar(0, MouseX.kr(0, 3).poll, 1, 500) }.play

+


+

x.release

+


+


+


+

// sine source example with GUI

+


+

(

+

SynthDef(\VanDerPol, { |out, srcFreq = 1, srcAmp = 1, mu = 1, theta = 1, tMul = 1, amp = 1|

+

var sig;

+

sig = Fb1_VanDerPol.ar(

+

SinOsc.ar(srcFreq) * srcAmp,

+

mu, theta, tMul,

+

) * EnvGen.ar(Env.asr);

+

Out.ar(out, sig[0] ! 2 * amp)

+

}, metadata: (

+

specs: (

+

srcFreq: [30, 100, \lin, 1, 50],

+

srcAmp: [0, 1, \lin, 0, 0.5],

+

mu: [0, 15, \lin, 0, 3.6],

+

theta: [0.1, 7, \lin, 0, 1.9],

+

tMul: [1, 500, \lin, 0, 325],

+

amp: [0.0, 1, \db, 0, 0.5]

+

)

+

)).add;

+


+

\VanDerPol.sVarGui.gui

+

)

+


+

s.scope(2)

+


+ + diff --git a/Help/GFIS.html b/Help/GFIS.html new file mode 100755 index 0000000..9da61ac --- /dev/null +++ b/Help/GFIS.html @@ -0,0 +1,538 @@ + + + + + + + + + + + +

GFIS generalized functional iteration synthesis pseudo ugen

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

The GFIS class implements functional iteration synthesis as pseudo ugen loosely based on the description by Agostino Di Scipio ([1], [2]), who used the abbreviation FIS and pointed to its rich potential. Yari Marimoto has written a plugin implementation of the main sine-map iteration model, which is included in the trnsnd quark under the same name. The GFIS pseudo ugen implementation allows settings which go beyond functional iteration in a strict sense. 

+

Principle idea of synthesis: given a parametrized non-linear function, time-variance of init values and/or parameter sets with fixed iteration depth n can produce interesting waveforms. Due to the highly non-linear dynamics involved, a great amount of unpredictability invites to experiment and exploration – depending on the characteristics of the time-varying signal, results span from brittle noisy textures to drones with rich spectral movements. The cited papers mainly describe strict iteration with sine and mention iterated waveshaping, but as the GFIS class implementation just takes an arbitrary Function it's easy to blur the concept, e.g. by altering the Function and/or the parametrization depending on the iteration level and/or applying iteration on multichannel signals, crossing their data etc. Also interesting – and probably not widely explored - is the use of functional iteration as controller / modulator / engine for other synthesis methods.

+


+

WARNING: Be careful with amplitudes, in general higher numbers of iteration produce signals with more energy and due to the non-linear dynamics signals can suddenly become loud! Also go sure that your function doesn't allow blowup with iteration (this at least doesn't happen with the standard examples of the sine map model). 

+


+

[1] Di Scipio, Agostino (1999). "Synthesis Of Environmental Sound Textures by Iterated Nonlinear Functions" in: Proceedings of the 2nd COST G-6 Workshop on Digital Audio Effects (DAFx99), NTNU, Trondheim, December 9-11, 1999.

+

[2] Di Scipio, Agostino (2001). "Iterated Nonlinear Functions as a Sound-Generating Engine" Leonardo, Vol. 34, No. 3 (2001), pp. 249-254, MIT Press.

+


+

See also: Fb1

+


+

Creation / Class Methods

+


+

*ar (func, init, n = 1, nOut, leakDC = true, leakCoef = 0.995)

+

+

func - Function used to establish the iteration by applying it n times 

+

at build time of the synthdef graph. The Function should take two arguments: 

+

the signal and an optional index. It should return the signal used for iteration.

+

The signal can be multichannel, then the Function might take that into account

+

and refer to single channels of the signal arg - but the Function might also ignore 

+

it and rely on multichannel expansion, see examples.

+

Note that UGens written within func are instantiated n times, this is usually

+

not what you want for iterating the same parametrical function, with determined

+

signals it's a waste of CPU and for random UGens the result is different.

+

For the strict interpretation of FIS define the parameter signal outside and

+

refer to it from inside func.

+

init - Init value for the iteration, can also be a SequenceableCollection.

+

n - Integer, the maximum iteration number, it determines how often the Function

+

is used for building the synthdef graph, hence this value is not modulatable. 

+

nOut - Integer or SequenceableCollection of Integers.

+

An Integer determines the iteration level of the returned signal.

+

That way you can define a maximum iteration number n and switch between

+

lower ones, however n iterations are permanently calculated. 

+

In general switching will cause clicks, so this is an option for testing primarily.

+

A SequenceableCollection of level indices will produce a

+

multichannel signal, which in turn allows defining smooth transitions

+

between signals of different iteration levels.

+

leakDC - Boolean. Determines if a LeakDC is applied to the output.

+

If the parameter signal doesn't change (which can e.g. happen with a LFDNoise UGen) 

+

the result will in general be a DC offset, hence DC leaking is recommended.

+

Defaults to true.

+

leakCoef - Number, the leakDC coefficient. Defaults to 0.995.

+


+

*kr (func, init, n = 1, nOut, leakDC = true, leakCoef = 0.995)

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

Examples 1) The sine map model

+

+

// These examples use an iterated sine map as described by Agostino Di Scipio.

+

// For the sine map sin(r * x) values of r varying between 2 and 4 are interesting.

+

// Driven by LFNoise parametrizations as in the first examples we get noise textures.

+


+


+

Ex.1a) Time-varying the factor r

+


+


+

(

+

y = {

+

var r = LFDNoise3.ar(10).range(3.5, 4);

+

GFIS.ar({ |x| sin(r * x) }, 0.3, 9) * 0.1 ! 2

+

}.play

+

)

+


+

y.release

+


+


+

// this is not "classical" FIS:

+

// for each iteration a different parametrization is taken !

+

// As LFDNoise UGens aren't coupled, pulsations are less unique

+


+

(

+

y = {

+

GFIS.ar({ |x| sin(LFDNoise3.ar(10).range(3.5, 4) * x) }, 0.3, 9) * 0.1 ! 2

+

}.play

+

)

+


+

y.release

+


+


+


+

// less iterations

+


+

(

+

y = {

+

var r = LFDNoise3.ar(10).range(3.5, 4);

+

GFIS.ar({ |x| sin(r * x) }, 0.3, 7) * 0.1 ! 2

+

}.play

+

)

+


+

y.release

+


+


+


+

// different init value

+


+

(

+

y = {

+

var r = LFDNoise3.ar(10).range(3.5, 4);

+

GFIS.ar({ |x| sin(r * x) }, 0.85, 7) * 0.1 ! 2

+

}.play

+

)

+


+

y.release

+


+


+


+

// higher iteration gives sections with more high frequencies in the spectrum

+

// even higher numbers soon lead to (interrupted) white noise

+


+

(

+

y = {

+

var r = LFDNoise3.ar(3).range(3.5, 4);

+

GFIS.ar({ |x| sin(r * x) }, 0.3, 12) * 0.03 ! 2

+

}.play

+

)

+


+

y.release

+


+


+

// higher iteration numbers can partially be "equilibrated" with lower r

+

// this leads to different sounds, here a more "airy" noise

+


+

(

+

y = {

+

var r = LFDNoise3.ar(7).range(2.9, 3.1);

+

GFIS.ar({ |x| sin(r * x) }, 0.3, 15) * 0.1 ! 2

+

}.play

+

)

+


+

y.release

+


+


+

// granular-like noise burst textures

+


+

(

+

y = {

+

var r = LFDNoise3.ar(50).range(2.5, 3);

+

GFIS.ar({ |x| sin(r * x) }, 0.3, 15) * 0.1 ! 2

+

}.play

+

)

+


+

y.release

+


+


+
Ex.1b) Time-varying init values

+


+

// mono

+


+

(

+

y = {

+

var i = LFDNoise3.ar(7).range(0.2, 0.9);

+

GFIS.ar({ |x| sin(3.2 * x) }, i, 10) * 0.1 ! 2

+

}.play

+

)

+


+

y.release

+


+


+

// stereo init is propagated to a stereo signal

+


+

(

+

y = {

+

var i = LFDNoise3.ar(7).range(0.2, 0.9) * [1, 1.01];

+

GFIS.ar({ |x| sin(3.2 * x) }, i, 10) * 0.1

+

}.play

+

)

+


+

y.release

+


+


+

Ex.1c) Time-varying init values and factor r

+


+

(

+

y = {

+

var i = LFDNoise3.ar(7).range(0.2, 0.9) * [1, 1.01];

+

var r = LFDNoise3.ar(2).range(3.2, 3.6) * [1, 1.01];

+

GFIS.ar({ |x| sin(r * x) }, i, 9) * 0.1

+

}.play

+

)

+


+

y.release

+


+


+

Ex.1d) Producing pitch by periodically oscillating parameters

+


+

// variants of phase glitter

+

// note that here again the "lazy" FIS with different oscillators per iteration is used

+


+

(

+

y = {

+

var osc = SinOsc.ar(30);

+

GFIS.ar({ |x| sin((osc + LFDNoise3.ar(0.15)).linlin(-2, 2, 3.2, 4) * x) }, [0.5, 0.505], 9) * 0.1

+

}.play

+

)

+


+

y.release

+


+

(

+

y = {

+

var osc = SinOsc.ar([30, 30.5]);

+

GFIS.ar({ |x| sin((osc + LFDNoise3.ar(0.15)).linlin(-2, 2, 3.2, 4) * x) }, 0.5, 9) * 0.1

+

}.play

+

)

+


+

y.release

+


+


+

(

+

y = {

+

var osc = SinOsc.ar([30, 30.5]);

+

GFIS.ar({ |x| sin((osc + LFDNoise3.ar(0.15)).linlin(-2, 2, 3.2, 4) * x) }, [0.5, 0.505], 9) * 0.1

+

}.play

+

)

+


+

y.release

+


+


+

// harmonics as different oscillation frequencies per iteration

+


+

(

+

y = {

+

var oscMod = SinOsc.ar(0.1).range(30.01, 30.3);

+

GFIS.ar({ |x, i| 

+

sin((SinOsc.ar([30, oscMod] * (i+1)) + LFDNoise3.ar(0.15)).linlin(-2, 2, 3, 4) * x) 

+

}, [0.5, 0.502], 7) * 0.1

+

}.play

+

)

+


+

y.release

+


+


+


+

// Pulse as oscillator

+


+

(

+

y = {

+

var factors = Demand.ar(

+

TDuty.ar(Dxrand([4, 5, 7], inf)), 

+

0, 

+

Dseq([Dseq([1.3, 1.4], 2), 0.5], inf)

+

).lag(0.7);

+

var osc = Pulse.ar([70, 70 * factors]).lag(0.001);

+

LPF.ar(

+

GFIS.ar({ |x| 

+

sin((osc + LFDNoise3.ar(0.15)).linlin(-2, 2, 3.2, 4) * x) 

+

}, [0.5, 0.505], 9) * 0.1,

+

9000

+

)

+

}.play

+

)

+


+


+

y.release

+


+


+

Examples 2) The waveshaping model - iteration via buffered data

+


+


+

// a Buffer can be filled with an arbitrary mathematical function or audio data

+


+

// load audio

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+


+

// load to array

+


+

b.loadToFloatArray(action: { |array| a = array; "done".postln });

+


+


+

// take short snippet, normalize between 0 and 1

+

// that's most practical when we map to buffer index later on

+


+

// the sound of the snippet is quite irrelevant

+

// more oscillations in general produce more noise with iteration

+

// start trying with sine-like forms

+


+

d = a[3150..3300].normalize;

+


+

d.plot;

+


+


+

// fill new buffer for iteration

+


+

c = Buffer.loadCollection(s, d)

+


+


+


+

Ex.2a) Time-varying init values

+


+

// result of BufRd is used as index for the next BufRd

+

// needs Buffer c prepared above

+


+

(

+

y = {

+

GFIS.ar(

+

{ |x| BufRd.ar(1, c, c.numFrames * x) },

+

(LFDNoise3.ar(3) * [1, 1.1]).range(0.5, 0.51), 7

+

) * 0.2

+

}.play

+

)

+


+

y.release

+


+


+

Ex.2b) Time-varying index deviation

+


+

// needs Buffer c prepared above

+


+

(

+

y = {

+

var add = LFDNoise3.ar(0.3) * [0.05, 0.0505];

+

GFIS.ar(

+

{ |x| BufRd.ar(1, c, c.numFrames * (x + add)) }, 

+

0.5, 7

+

) * 0.2

+

}.play

+

)

+


+

y.release

+


+


+

Ex.2c) Time-varying init values and index deviation

+


+

// needs Buffer c prepared above

+


+

(

+

y = {

+

var add = LFDNoise3.ar(0.3) * [0.05, 0.0505];

+

GFIS.ar(

+

{ |x| BufRd.ar(1, c, c.numFrames * (x + add)) }, 

+

(LFDNoise3.ar(0.3) * [1, 1.01]).range(0.5, 0.505), 7

+

) * 0.2

+

}.play

+

)

+


+

y.release

+


+

Examples 3) GFIS as controller / modulator / engine for other synthesis

+


+

// this can transfer the instable characteristics to other sound worlds

+


+


+

Ex.3a) FM 

+


+

(

+

y = {

+

var mod = GFIS.ar({ |x| sin(LFDNoise3.ar(1).range(2.9, 4) * x) }, 0.3, 7);

+

SinOsc.ar(mod * [50, 51] * 70 + 300) * LFDNoise3.ar(2).range(0.2, 0.05)

+

}.play

+

)

+


+

y.release

+


+


+

Ex.3b) Buffer modulation phase controlled by GFIS 

+


+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

b = Buffer.read(s, p);

+


+

(

+

y = {

+

var pos = 0.5; // other pos offset will give totally different sounds

+

var osc = GFIS.ar({ |x| sin(LFDNoise3.ar(10).range(3.2, 4) * x) }, 0.3, 6) * 0.1;

+

// GFIS involves a LeakDC, but not BufRd

+

LeakDC.ar(BufRd.ar(1, b, b.numFrames * (osc * [0.0120, 0.0125] * 5 + pos), interpolation: 4)) * 0.1

+

}.play

+

)

+


+

y.release

+


+


+

Ex.3c) Iterated GFIS 

+


+

// augmentation of non-linearity:

+

// GFIS itself used as time-varying control of another GFIS

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+


+

// load to array and fill new buffer for iteration

+


+

b.loadToFloatArray(action: { |array| a = array; "done".postln });

+


+

d = a[3150..3300].normalize;

+


+

c = Buffer.loadCollection(s, d);

+


+


+

(

+

y = {

+

var r = LFDNoise3.ar(3).range(2.7, 3.4);

+

var add = GFIS.ar({ |x| sin(r * x) }, 0.5, 3) * [0.05, 0.055];

+

GFIS.ar(

+

{ |x| BufRd.ar(1, c, c.numFrames * (x + add)) }, 

+

0.5, 7

+

) * 0.2

+

}.play

+

)

+


+

y.release

+


+


+

Ex.4) The nOut arg

+


+


+

// nOut allows for switching between iteration levels up to maximum n

+


+

(

+

y = {

+

|nOut = 10|

+

GFIS.ar({ |x| sin(LFDNoise1.ar(20).range(3, 3.5) * x) }, [0.2, 0.3], 10, nOut) * 0.1

+

}.play

+

)

+


+


+

y.set(\nOut, 9)

+


+

y.set(\nOut, 8)

+


+

y.release

+


+


+

// it can be passed an array of levels, 

+

// the resulting multichannel signal can e.g. be used for crossfaded switching with DXMix

+


+

// switch between 3 mono signals and double them

+


+

(

+

y = {

+

var src = GFIS.ar({ |x| sin(LFDNoise3.ar(30).range(3, 4) * x) }, 0.2, 9, [5, 7, 9]) * 0.1;

+

DXMix.ar(Dseq([0, 1, 2], inf), `src, fadeTime: 0.01, stepTime: 0.02, fadeMode: 1) ! 2

+

}.play

+

)

+


+

y.release;

+


+

// switch between 3 stereo signals

+


+

(

+

y = {

+

var src = GFIS.ar({ |x| sin(LFDNoise3.ar(30).range(3, 4) * x) }, [0.2, 0.21], 9, [5, 7, 9]) * 0.1;

+

DXMix.ar(Dseq([0, 1, 2], inf), `src, fadeTime: 0.01, stepTime: 0.02, fadeMode: 1)

+

}.play

+

)

+


+

y.release;

+


+


+
Ex.5) Comparison FIS / GFIS

+


+// FIS is contained in the trnsnd quark

+

// first example of its helpfile

+


+

{ FIS.ar(LinExp.ar(LFTri.ar(0.1), -1, 1, 1, 4), LFNoise2.ar(300).range(0, 1), 3, 0.1) }.play;

+


+

// the same with GFIS requires definition of varying params outside func 

+


+(

+

y = {

+

var r = LinExp.ar(LFTri.ar(0.1), -1, 1, 1, 4);

+

var i = LFNoise2.ar(300).range(0, 1);

+

GFIS.ar({ |x| sin(r * x) }, i, 3, 3, false) * 0.1

+

}.play

+

)

+


+

y.release

+


+

// proof of concept, difference is silent as it should

+


+

(

+

z = {

+

var r = LinExp.ar(LFTri.ar(0.1), -1, 1, 1, 4);

+

var i = LFNoise2.ar(300).range(0, 1);

+

GFIS.ar({ |x| sin(r * x) }, i, 3, 3, false) * 0.1 - FIS.ar(r, i, 3, 0.1)

+

}.play

+

)

+


+

z.release

+


+


+


+


+ + diff --git a/Help/HS with VarGui.html b/Help/HS with VarGui.html new file mode 100644 index 0000000..ffecf08 --- /dev/null +++ b/Help/HS with VarGui.html @@ -0,0 +1,388 @@ + + + + + + + + + + + +

HS with VarGui using HS / HSpar with VarGui 

+


+

Part of: miSCellaneous

+


+

See also: Working with HS and HSpar, VarGui, VarGui shortcut builds, Event patterns and LFOs, Event patterns and Functions

+


+


+

It is recommended to get familiar with the concepts of the HS / PHS and HSpar / PHSpar classes before combining them with VarGui, see Working with HS and HSpar for an overview. 

+

Alternative control setups with event patterns and VarGui examples are discussed in Event patterns and LFOs.

+


+


+

Differences to other control setups with event patterns and VarGui 

+


+

Instances of HS and PHS are hybrid objects in the sense that their functionality is not interchangeable with that of some (one might think at a first glance) related objects (Synths and Pbinds). A HS contains a SynthDef but will also hold Synths derived thereof. A PHS holds Pbind pairs but, when played, instantiates two EventStreamPlayers to be synchronized, also taking control over the used HS. A played PHSpar even controls a third player switching between running control synths. 

+


+

On the other hand VarGui is already prepared to accept synths / synth definitions and pbinds / eventstream players / tasks functions / tasks for generating synth and stream players. For plugging these concepts together it seems practical to use objects (resp. introduce adapting ones) that fit into what's already there. This can be achieved by two methods:

+


+

1.) .newPaused, applicable to PHS / PHSpar makes the used HS / HSpar generate a new paused Synth (or possibly several in case of PHSpar). Synths are returned by the method and can be passed to VarGui, which automatically detects their derivation from HS / HSpar and adapts gui functionality.

+


+

2.) .asTask, applicable to PHS / PHSpar and PHSuse / PHSparUse returns a wrapper Task for compound players of the PHS family. Playing and stopping the wrapper Task invokes playing and stopping behaviour of the underlying compound players, per default also taking control over playing and stopping the help synth(s).

+


+

An alternative approach to link HS and HSpar with VarGui is shown in Ex. 3.

+


+


+


+

Example 1a:   HS / PHS

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

(

+

h = HS(s, { |midiCenter = 70, dev = 20, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) });

+


+

p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) ]);

+


+

// Instantiation and return of a new paused Synth from HS's synth definition

+


+

x = p.newPaused;

+


+

// second intermediate object:

+

// PHS's asTask method generates a wrapping task

+

// which will invoke expected player actions by notification

+


+

// Note asTask's flag newEnvir, it determines if 

+

// the player should run in a newly generated environment, defaults to true.

+

// This allows to take one PHS definition with varibles to be set in

+

// different environments (as with Pbind). 

+


+

t = p.asTask;   

+


+

// also compare play / stop behaviour with these options (default was hsStop: false, hsPlay: true):

+

// hsPlay = false will run Synth together with stream only for the first time

+


+

// t = p.asTask(hsStop: true, hsPlay: true);

+

// t = p.asTask(hsStop: true, hsPlay: false);

+

// t = p.asTask(hsStop: false, hsPlay: false);

+

)

+


+

(

+

// VarGui detects that a Synth derived from a HS has been passed:

+

// synth player stop / reset button and mode buttons are greyed out,

+

// pause button background color is blue as Synth is new and paused.

+

// Playing and stopping the stream player will also cause the synth's running 

+

// (and stopping with asTask's option hsStop = true),

+

// however playing and stopping the help synth separately is still possible.

+


+

// For Synths derived from HS / HSpar pushing the general stop button will 

+

// only cause pausing - other synths will be freed - whereas Cmd. will free all synths,

+

// but ends HS gui control. Then a new VarGui would have to polled from reevaluated x and t. 

+


+

v = VarGui([], 

+

[[\midiCenter, [50, 65, \lin, 0.01, 60],

+

\dev, [0, 10, \lin, 0.01, 10],

+

\devFreq, [0, 3, \lin, 0.01, 0.5] ]],

+

t, x 

+

).gui;

+

)

+


+

// free resources, PHSplayer is wrapped, so use Cmd-. or this

+


+

h.free; 

+


+


+

Example 1b:   HS / PHS and PHSuse

+


+


+

(

+

h = HS(s, { |midiCenter = 70, dev = 20, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) });

+


+

p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) ]);

+

q = PHSuse(h, 0.3, [ \midinote, Pkey(\val) + 5 ]);

+

r = PHSuse(h, 0.4, [ \midinote, Pkey(\val) + 10 ]);

+


+

x = p.newPaused;

+


+

// quant arg for syncing

+


+

t = [p,q,r].collect(_.asTask(quant: 0.3));

+

)

+


+

(

+

// The PHSplayer has control over the help synth,

+

// PHSusePlayers can't be played before,

+

// though it's possible to start all players together with shift-click.

+


+

// For the same reason hsStop and hsPlay options are only available for the PHS-wrapping Task.

+


+

// After PHSplayer has been started (and maybe paused) PHSusePlayers can be

+

// played and stopped separately. 

+


+


+

v = VarGui([],

+

[[ \midiCenter, [50, 65, \lin, 0.01, 60],

+

\dev, [0, 10, \lin, 0.01, 10],

+

\devFreq, [0, 3, \lin, 0.01, 0.5] ]],

+

t, x 

+

).gui;

+

)

+


+

h.free; 

+


+


+

Example 2a:   HSpar / PHSpar

+


+


+

(

+

h = HSpar(s, [

+

{ |midiCenter = 65, dev = 10, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }, 

+

{ |midiCenter = 90, dev = 10, devFreq = 1| LFSaw.kr(devFreq, 0, dev, midiCenter) }

+

]);

+


+

// switching behaviour defined by hsIndices

+

+

p = PHSpar(h, 

+

pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [-4, 5] ]],

+

hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf))  

+

]);

+

+

// for PHSpar this returns a collection of new paused help synths

+


+

x = p.newPaused;

+


+

t = p.asTask(quant: 0.3, hsStop: true);

+


+

// You can specify start and stop options for selected Synths of HSpar 

+

// with indices or collections of indices, e.g.:

+

// t = p.asTask(quant: 0.3, hsStop: 0);

+

)

+


+

(

+

// Playing and stopping the PHSparPlayer's wrapper Task affects both help synths in parallel.

+


+

v = VarGui([],

+

[[54.1, 3.85, 1.5], [60, 7.8, 2.7]].collect { |y| [ 

+

\midiCenter, [50, 65, \lin, 0.01, y[0]],

+

\dev, [0, 10, \lin, 0.01, y[1]],

+

\devFreq, [0, 3, \lin, 0.01, y[2]]]

+

}, t, x 

+

).gui;

+

)

+


+

h.free; 

+


+


+

Example 2b:   HSpar / PHSpar and PHSparUse

+


+


+

(

+

h = HSpar(s, [

+

{ |midiCenter = 65, dev = 10, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }, 

+

{ |midiCenter = 90, dev = 10, devFreq = 1| LFSaw.kr(devFreq, 0, dev, midiCenter) }

+

]);

+

+

p = PHSpar(h, 

+

pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [-4, 5] ]],

+

hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf))  

+

]);

+


+

x = p.newPaused;

+


+

q = PHSparUse(h,  

+

pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [0, 9] ]],

+

hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) ] 

+

);

+


+

r = PHSparUse(h,  

+

pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [2.5, 7] ]],

+

hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) ] 

+

);

+


+

t = [p,q,r].collect(_.asTask(quant: 0.3));

+

)

+


+

(

+

v = VarGui([],

+

[60, 75].collect { |y| [  

+

\midiCenter, [55, 85, \lin, 0.01, y],

+

\dev, [0, 10, \lin, 0.01, 10],

+

\devFreq, [0, 3, \lin, 0.01, 0.5]]

+

},

+

t, x 

+

).gui;

+

)

+


+

h.free; 

+


+


+

Example 2c:   HSpar / PHSpar and PHSparUse with switch pattern

+


+


+

(

+

h = HSpar(s, [

+

{ |midiCenter = 65, dev = 10, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }, 

+

{ |midiCenter = 90, dev = 10, devFreq = 1| LFSaw.kr(devFreq, 0, dev, midiCenter) }

+

]);

+

+

+

// define event pattern to switch between the two help synths,

+

// switch times (average and random deviation) to be controlled from gui as well as

+

// chord structure determined by a single interval parameter 

+


+

p = PHSpar(h, 

+

switchDur: Pfunc { ~swTmMid * (1 + rand2(~swTmDev)) },

+

switchIndex: Pseq([0,1], inf),

+

pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + Pfunc { [~int.neg, 0, ~int] } ]],

+

switchOn: true, // help synths to be resumed when active and ...

+

switchOff: true // ... to be paused when left - will be reflected by VarGui's button background colors

+

);

+

+

x = p.newPaused;

+


+

// chord structure of other pbinds will be taken from same variable ~int, so ...

+


+

q = PHSparUse(h, [ 0.15, [ \midinote, Pkey(\val) + Pfunc { ~int.neg / 3 + [~int.neg, 0, ~int] } ]]);

+

r = PHSparUse(h, [ 0.15, [ \midinote, Pkey(\val) + Pfunc { ~int * 3 / 5 + [~int.neg, 0, ~int] } ]]);

+


+

// ... players must be in the same environment, this is ensured by setting .asTask's flag newEnvir to false,

+

// all will be played and set in currentEnvironment.

+


+

t = [p,q,r].collect(_.asTask(quant: 0.3, newEnvir: false));

+

)

+


+

(

+

v = VarGui([

+

\swTmMid, [0.3, 1.5, \lin, 0.01, 1],

+

\swTmDev, [0, 0.5, \lin, 0.01, 0.5],

+

\int, [0, 12, \lin, 0.01, 8]

+

],

+

[60, 75].collect { |y| [  

+

\midiCenter, [55, 85, \lin, 0.01, y],

+

\dev, [0, 10, \lin, 0.01, 10],

+

\devFreq, [0, 3, \lin, 0.01, 0.5]]

+

},

+

t, x 

+

).gui;

+

)

+


+

// As can be seen in gui stopping with red stop button won't stop switching.

+

// Player had been started with asTask's default option switchStop = false

+

// and, as wrapped in a task, is not directly accessible.

+

// Anyway you can, as always, stop and free resources with Cmd-. or

+


+

h.free; 

+


+


+


+

Example 3:   Help Synths not passed explicitely

+


+


+

This is an alternative approach if syncing control synths and streams is not important - whereas resetting control synths becomes an option.

+


+

(

+

// Actual help synths can be defined separately and fed into

+

// formal help synths via control busses

+


+

// control synths hard-wired to allocated busses

+


+

c = Bus.control(s, 2).index;

+


+

SynthDef(\control_1, { |midiStart = 65, midiDiff = 15, duration = 10| 

+

Out.kr(c, XLine.kr(midiStart, midiStart + midiDiff, duration) ) }).add;

+

SynthDef(\control_2, { |midiStart = 90, midiDiff = 15, duration = 10| 

+

Out.kr(c+1, XLine.kr(midiStart, midiStart - midiDiff, duration) ) }).add;

+


+

h = HSpar(s, [{ In.kr(c) }, { In.kr(c+1) }] );

+


+

// PHSpar and PHSparUse defined in same way as above but ...

+

// ... PHSparPlayer wrapper Task defaults to hsStop = false,

+

// so reading values from bus c is not stopped with pausing and

+

// PHSparUsePlayers can still get new values from "external" control synths

+


+

p = PHSpar(h, 

+

pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [-4, 5] ]],

+

hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf))  

+

]).asTask(quant: 0.3);

+


+


+

q = PHSparUse(h,  

+

pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [0, 9] ]],

+

hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) ] 

+

).asTask(quant: 0.3);

+


+

r = PHSparUse(h,  

+

pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [2.5, 7] ]],

+

hsIndices: [ Pstutter(Prand([2,3,5, 11], inf), Pseq([0,1], inf)) ] 

+

).asTask(quant: 0.3);

+

)

+


+


+

(

+

// Now control synths have to be started before streams !

+

// Caps + shift click for parallel play action will work on cocoa, 

+

// you can choose custom bundle latency (c) and set it slightly below server latency

+


+

v = VarGui([],

+

[60, 80].collect { |x| [ 

+

\midiStart, [50, 95, \lin, 0.01, x],

+

\midiDiff, [0, 30, \lin, 0.01, 15],

+

\duration, [1, 20, \lin, 0.01, 15]]

+

}, [p,q,r], [\control_1, \control_2]

+

).gui(playerPriority: \synth, sliderPriority: \synth);

+


+

// modes of "external" control synths can be set

+

// resetting (starting new synths of same definition) is also possible

+

)

+


+

// additional synths engaged here, so cleanup with Cmd-. or stopping in gui plus ...

+


+

h.free;

+


+


+ + diff --git a/Help/HS.html b/Help/HS.html new file mode 100755 index 0000000..bfd8fbd --- /dev/null +++ b/Help/HS.html @@ -0,0 +1,277 @@ + + + + + + + + + + + +

HS (HelpSynth) object for use of synth values in the language by event patterns

+


+

Part of: miSCellaneous

+


+

Inherits from: Object

+


+

To be used in connection with PHS / PHSuse to play event patterns using synth values. Holds a singular synth definition, keeps track of OSC traffic when PHS / PHSuse are played. For using several help synths in parallel or setting controllers of a singular help synth see HSpar and related.

+


+

See also: Working with HS and HSpar, HS with VarGui, PHS, PHSuse, PHSplayer, PHSusePlayer

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (server, ugenFunc, demandLatency, respondLatency, postOSC, granularity, inputCheck)

+

+

Creates a new HS object.

+

+

server - Must be running server.

+

ugenFunc - Function that defines the synth. 

+

demandLatency - Latency of help synth in seconds. Default value 0.15. 

+

respondLatency - Time in seconds, given to the response to be received by the client on time. 

+

Default value 0.15.

+

postOSC - Boolean for posting of (server to client) OSC messages. Defaults to false.

+

granularity - Time grains per second, quantization for bookkeeping. Defaults to 200.

+

inputCheck - Boolean for checking input data. Defaults to true.

+

+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// make a new HS 

+

// This compiles the SynthDef and prepares the HS to be played with a PHS,

+

// no Synth object is yet instantiated.

+

+

h = HS(s, { LFDNoise3.kr(0.7, 15, 75) });

+


+

// A PHS, like a Pbind, defines event stream behaviour, only durations are given separately.

+

// Here values are demanded just by one stream with constant duration, 

+

// but more than one duration pattern per HS and more than one Pbind per duration pattern

+

// may be defined - see the PHS help file for examples. 

+

// The play method of a PHS creates a PHSplayer which plays a synth from HS's synth definition, 

+

// synth values are accessible within the PHS definition by the variable ~val, e.g. by Pkey(\val).

+

// If HS's synthdef has no args, this must be specified in the PHS by nil or [].

+

+

p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) ]).play;

+

+

// also:

+

// p = PHS(h, [], 0.15, [ \midinote, Pfunc { |e| e.use { ~val } }  ]).play;

+

+

// stop allows resuming, cleanup with free or Cmd-. 

+

+

p.stop; // HS synth still playing 

+

+

p.play; // resume the PHSplayer

+

+

p.stop(true); // HS synth also paused 

+


+

p.play; // resume the PHSplayer together with HS synth

+

+

p.free; // also stop HS synth and cleanup - PHSplayer and HS object are freed

+


+


+

Accessing Instance Variables

+

+

demandLatency_(arg)

+

demandLatency

+

+

Latency of help synth in seconds. Defaults to 0.15. 

+

+

respondLatency_(arg)

+

respondLatency

+

+

Time (in seconds) given to the response to be received by the client on time. Defaults to 0.15.

+

+

postOSC_(arg)

+

postOSC

+

+

Defines whether OSC messages should be posted or not. Boolean. Defaults to false.

+

+

granularity_(arg)

+

granularity

+

+

Integer. Time grains per second, quantization for bookkeeping. Defaults to 200.

+


+

inputCheck_(arg)

+

inputCheck

+

+

Boolean for checking input data. Defaults to true.

+

+

+

Status control

+


+

listen(num, latency)

+

+

Make HS ready to poll values from a synth, demanded by possibly more than one stream. 

+

Therefore num trigger synths are run, ready to receive triggers for sending help synth values back to client,

+

but a synth from HS's ugenFunc definition is not yet playing after that.

+

If PHS / PHSuse are played there is no need to call listen explicitely.

+

+

play(args, latency)

+

+

Play a synth derived from HS's ugenFunc definition. HS must already be listening.

+

If PHS / PHSuse are played there is no need to call play explicitely.

+

+

clearBookkeeping

+

+

Cleanup OSC bookkeeping.

+

+

stop

+

+

Free the playing help synth and cleanup OSC bookkeeping.

+


+

sleep

+

+

Free the listening trigger synths.

+

+

busFree

+

+

Deallocate the bus.

+

+

free

+

+

Free all related PHSplayers / PHSusePlayers, then stop, sleep and busFree.

+


+


+

Note: stop and free will normally be done by the respective player methods, see PHSplayer.    

+

+

+

+

Examples

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// define a tendency for chord density

+


+

h = HS(s, { LFDNoise3.kr(0.5, 4, 5) });

+


+


+

// language operations used for building chords from synth values

+


+

(

+

p = PHS(h, [], 0.18, 

+

[\midinote, Pkey(\val).collect {|x| Array.series(x.round, 60 - (x * 1.2), 5) + Array.rand(x.round, -0.8, 0.8)  },

+

\legato, 0.6, \amp, 0.05]

+

).play;

+

)

+

+

// cleanup (freeing buses and nodes) by pressing Cmd-. or explicitely

+

+

p.free;

+


+


+

/////////////////////////////////////////////////////////////////////

+


+

// example with other than default instrument

+

// define synth to move between two sounds

+


+

(

+

SynthDef(\simpleMorph, {|out = 0, freq = 440, amp = 0.1, morphQ = 0|

+

var src, normalMorphQ = morphQ.mod(1);

+

src = Saw.ar(freq, mul: amp) * (1 - normalMorphQ) + (SinOsc.ar(freq, mul: amp) * normalMorphQ);

+

Out.ar(out, src ! 2 * EnvGen.ar(Env.perc, doneAction: 2));

+

}).add;

+

)

+


+

// HelpSynth definition, values between 0 and 1

+

// Clock and Quant for syncing

+


+

(

+

h = HS(s, { LFTri.kr(0.5, mul: 0.5, add: 0.5) });

+


+

c = TempoClock.new;

+

q = 0.2;

+

)

+


+

// help synth values for pitch and synth arg \morphQ 

+

 

+

(

+

p = PHS(h, [], 0.2, 

+

[\instrument, \simpleMorph, 

+

\midinote, Pkey(\val) * 30 + 60 + [0, 9] + Pxrand([0, 1.5, -1.5], inf), 

+

\amp, 0.07,

+

\morphQ, Pkey(\val) ]

+

).play(c,q);

+

)

+


+


+

// sync it with a normal pbind (EventStreamPlayer) with default instrument

+

// no use of HS / PHS

+


+

(

+

r = Pbind(

+

\dur, 0.2, 

+

\midinote, Pxrand([58, 60, 61, 63, 64, 67, 69, 70], inf) + [-7, 0, 5, 9] + Pseq([0.2, -0.2],inf), 

+

\amp, 0.03

+

).play(c, quant: q);

+

)

+


+

// pause and stop are synonym

+


+

p.pause;

+


+


+

// resume p in sync

+


+

p.play(c, q);

+


+


+

// stop and free

+


+

(

+

r.stop;

+

p.free;

+

)

+


+


+ + diff --git a/Help/HSpar.html b/Help/HSpar.html new file mode 100755 index 0000000..6e26a79 --- /dev/null +++ b/Help/HSpar.html @@ -0,0 +1,139 @@ + + + + + + + + + + + +

HSpar (HelpSynthPar) object for use of synth values in the language by event patterns

+


+

Part of: miSCellaneous

+


+

Inherits from: HS (HelpSynth)

+


+

To be used in connection with PHSpar / PHSparUse to play event patterns using synth values. Holds synth definitions, keeps track of OSC traffic when PHSpar / PHSparUse are played. For using a singular help synth see HS and related.

+


+

See also: Working with HS and HSpar, HS with VarGui, PHSpar, PHSparUse, PHSparPlayer, PHSusePlayer

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (server, ugenFuncs, demandLatency, respondLatency, postOSC, granularity, inputCheck)

+

+

Creates a new HSpar object.

+

+

server - Must be running server.

+

ugenFuncs - Collection of functions that define the synths. 

+

demandLatency - Latency of help synths in seconds. Default value 0.15. 

+

respondLatency - Time in seconds, given to the response to be received by the client on time. Default value 0.15.

+

postOSC - Boolean for posting of (server to client) OSC messages. Defaults to false.

+

granularity - Time grains per second, quantization for bookkeeping. Defaults to 200.

+

inputCheck - Boolean for checking input data. Defaults to true.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// define a HSpar with two help synth definitions 

+

+

(

+

h = HSpar(s, [ { |freq = 1, dev = 10, center = 70|

+

LFDNoise3.kr(freq, dev, center) },

+

{ |freq = 1, dev = 5, center = 70, addFreq = 0.1, addDev = 5|

+

LFTri.kr(freq, 0, dev, center) + SinOsc.kr(addFreq, 0, addDev) } 

+

]);

+

)

+


+

// define a PHSpar to switch between these two

+

+

(

+

p = PHSpar(h, 

+

Pwhite(2.5, 3), // switch duration

+

Pseq([0,1],inf), // switch indices

+

// helpSynthArgs:  always default values for help synth #0, always set help synth #1

+

  [nil, [\freq, 1.2, \center, Pwhite(60, 80)] ], 

+

  // pbindArgs:  different from PHS, they have to be given as collection; ~val refers to current switch

+

  [0.1, [\midinote, Pkey(\val) + [0, 5], \legato, 0.2, \amp, [0.1, 0.07]]] 

+

).play;  

+

)

+

+

// stop and free HSpar by player

+

+

p.free;

+


+


+

+

/////////////////////////////////////////////////

+


+


+

// HSpar with only one help synth

+

+

h = HSpar(s, [ { |freq = 1, dev = 5, center = 65| LFDNoise3.kr(freq, dev, center) } ] );

+


+


+

// to be used for setting synth args

+

+

(

+

p = PHSpar(h,

+

Pwhite(0.15, 0.35, inf), // switch durations

+

0, // switch index

+

  [[\center, Pwhite(65, 80)]], // set args

+

  [0.1, [\midinote, Pkey(\val) + Pseq([[0,-7], -2],inf), \legato, 0.3, \amp, 0.05 ]]

+

  ).play           

+

)

+

+

// stop and free HSpar by player

+

+

p.free;

+


+


+ + diff --git a/Help/Idev suite.html b/Help/Idev suite.html new file mode 100755 index 0000000..3540be9 --- /dev/null +++ b/Help/Idev suite.html @@ -0,0 +1,49 @@ + + + + + + + + + + + +

Idev suite classes for number search with integer distance from a source, optionally avoiding repetitions within a span

+


+

Part of: miSCellaneous

+


+

See also: DIdev, PIdev, PLIdev

+


+

DIdev / PIdef / PLIdev search for numbers with integer distance from a source signal / pattern up to a given deviation. Repetitions within a lookback span are avoided, DIdev / PIdef / PLIdev randomly choose from possible solutions. Intended for search within integer grids (pitches, indices etc.), however applications with non-integer sources are possible, see examples.

+


+

The main musical idea of these classes is, that it's often unwanted to have certain characteristics repeated within a perceptional time window. This might apply to melodic lines as well as to rhythmic patterns or sequences of timbre and can be continued in the domain of microsound. The principle can easily be understood with pitches, therefore most examples are of this kind. 

+


+

In the following example integers are searched within the neighbourhood of a rounded sine source. The hi and lo deviation is constant (+/-5) and a lookBack value of 3 garantuees that there are no repetitions of integer values within a group of 4 (e.g. find a closest repetition within the last 5 points). In general lookback and deviations can be dynamic, the source doesn't need to be rounded and the comparison threshold for the repetition check can also be passed as a dynamic argument.

+


+


+


+

+


+


+


+

NOTE: It's the user's responsibility to pass a combination of deviation and lookback values that allows a possible choice, see examples.

+


+

NOTE: In contrast to PIdev and PLIdev, DIdev needs to know maximum deviations (minLoDev, maxHiDev) beforehand. Together with maxLookBack they determine multichannel sizes, which might be CPU-consuming. 

+


+


+ + diff --git a/Help/Introduction to miSCellaneous.html b/Help/Introduction to miSCellaneous.html new file mode 100755 index 0000000..6cbc0f9 --- /dev/null +++ b/Help/Introduction to miSCellaneous.html @@ -0,0 +1,589 @@ + + + + + + + + + + + +

Introduction to miSCellaneous overview, references and examples

+


+

Part of: miSCellaneous

+


+

At the beginning of 2017, it was almost 10 years ago when I did my first steps in systematically ordering and extending personal SC tools. At that time I already had some experience in SC, but project-oriented composition was my main focus. Meanwhile miSCellaneous lib has grown and when I look at its readme file, although I always tried to keep stuff documented properly, I doubt that the connections between the different classes would be easy to get at a first glance. Topics accumulated, some help files have become huge (VarGui, PbindFx) and a lot of examples and documented (extra-)features might hide the basic motivations. As I was asked by colleagues at the Institute of Electronic Music and Acoustic Graz (IEM) we organized a one-day lecture/workshop on miSCellaneous lib on November 5th, 2016, and this tutorial summarizes the overview given then. At that point I'd like to say that I'm very grateful to IEM Graz and the interested SC community from here and abroad for support and feedback, we are going to have further SC meetings at the same place – Hanns Holger Rutz already continued in December. There's also a focus on artistic research here at IEM, and research projects, events and discussions are lighting an ongoing discourse in the field between art and science, creating an inspiring environment.

+

Concerning the structure of this file: after a brief history and a grouping of content the tour will guide through selected topics in an order which, hopefully, will outline the central ideas. It will start with VarGui, PLx, then go over PbindFx, EventShortcuts to live coding aspects of PLx, continue with independent classes, class families, methods and SC tutorials and end with the Buffer Granulation tutorial, which again integrates some of the previous topics. PbindFx gets more space, as it's in my recent focus and you can use it for a number of things which are not, or at least not easily doable in plain SC. To a large extent the tour consists of references to selected already existing examples, which are spread over various help files. Conitinuative, but less central topics are marked as such, as well as some legacy code, which is still working, but not very relevant, as other possibilities have been invented meanwhile. A few new examples have been added, the reader is invited to check the exercises and to have fun with his/her favourite instrument resp. effect SynthDefs. 

+


+

History

+


+

In 2007 / 08 I was working at ZKM Karlsruhe, preparing a piece for flute and multichannel electronics (Lokale Orbits / Solo 3). Then I thought it would be useful to order and document my tools, by doing so my programming would – hopefully – not only be useful for me but for others too and I would be able to give something back to the open source community, from which I have been getting so much valuable input. Also I noticed that a better structuring of code together with the need of documentation stimulated reflection and development and significantly improved the functionality of my tools also just for personal use. 

+

Lokale Orbits is a series of works for small instrumentations and multichannel tape where recordings with involved musicians were the base for granular processings. From the beginning miSCellaneous lib was developed in parallel to the needs that came from that artistic motivation. The first particular motivation was related to the fundamental architecture of SC – the division between language and server – and its consequences for granular synthesis. I never wanted to spend much time in gui programming, but I wanted to have a multi-purpose gui that would easily let me experiment with granular synthesis driven by language (patterns in particular) and server (granulation ugens). It should also be easy to combine these different controls in a single patch, e.g. fine-tune the parameters of a LFO and those of a Pattern resp. the derived running EventStreamPlayer at the same time. This need led to the development of the VarGui interface. It is the the oldest part and, so to speak, the kernel of miSCellaneous, already contained in the first public release of 2009. A player section and a number of features was added in 2011. Thus VarGui doesn't use SC's extended gui features which came up with Qt and if I had to build something from scratch I'd certainly look for a revised code structure, nevertheless VarGui reliably served my needs quite well over a long period of time. The twofold control option (environmental variables and synth args) turned out to be useful in many contexts, also the handling of arrays (environmental variables as well as synth args) is quite practical and allows a quick instantiation of huge slider+player guis. 

+

Between 2009 and 2016 a number of pattern families was added, some of them with granular synthesis in mind. The one I'm using most is the PLx proxy pattern family which takes advantage of environmental variables and dynamic scoping and goes well together with the VarGui interface concerning control of running Patterns/EventStreamPlayers. PLx also opens nice opportunities for live-coding with very condensed syntax. I'm not doing this on stage but I see live-coding as valuable part of a dynamic compositional process. As a side remark, PLx patterns mirror most of SC's main lib patterns and can also be used as non-proxies. In contrast to main lib's list patterns they default to repeats = inf, which probably saved myself the typing of many thousands of 'infs' over the years.

+

Another part of miSCellaneous is a number of tutorials, which I added from time to time. Some refer to general SC topics, independent from miSCellaneous (e.g. Event patterns and array args), others to topics specific to miSCellaneous (e.g. PLx and live coding with Strings) and in some I tried a general overview of principal SC strategies, but also used examples with features of miSCellaneous (Buffer Granulation). Finally there's other stuff that doesn't fall into the above categories, e.g. the class EventShortcuts for customized shortcuts with Events and event patterns. Classes related to nonlinear dynamics (single sample feedback with Fb1, generalized functional iteration synthesis with GFIS) have been added in 2018, Fb1_ODE and related, a framework for general ordinary differential equation integration of initial value problems in 2019.

+


+


+


+

Groups of content

+


+

.) VarGui (2009), multi-purpose slider / player gui. Can have sliders for control of synth parameters and 

+

environmental variables as well as a player section for control of Synths, Tasks or 

+

EventStreamPlayers derived from Patterns.

+


+


+

.) Pattern classes/class families:

+


+

.) PHSx (2009): pattern-like objects using synth values in language,

+

still working but a bit outdated as we have synchronous buses now

+

+

.) PLx (2012): dynamic scope proxy patterns,

+

especially suited for VarGui control, also live coding

+

+

.) PSx (2014): patterns acting like streams and remembering last values,

+

good for certain types of nested pattern use and recursion

+

+

.) PmonoPar, PpolyPar (2015): differently timed setting of event streams

+

+

.) PbindFx (2015): sequencing arbitrary effects and effect graphs

+

+

.) PLbindef, PLbindefPar (2016): proxies based on Pbindef

+

allowing shortcut replacement syntax

+

+

.) PSVx (2016): a pattern implementation of Xenakis sieves related to the class Sieve,

+

Psieve patterns enable an unusual "realtime sieve modification".

+

Interesting for many applications, e.g. granular rhythms, 

+

though I didn't have the time to experiment a lot yet.

+

+

.) PSPdiv (2017): a dynamic multi-layer pulse divider based on Pspawner

+

+

.) Other pattern classes

+

+

+

.) Tutorials:

+


+

.) Event patterns and Functions (2011)

+


+

.) Event patterns and LFOs (2011)

+


+

.) Buffer Granulation Tutorial (2012)

+

different strategies of buffer granulation and control

+

+

.) Event patterns and array args (2015)

+


+

.) PLx and live coding with Strings (2016)

+


+

.) Sieves and Psieve patterns (2016)

+


+

.) kitchen studies (2016)

+

commented source code of six short pieces from a kitchen sound using PbindFx

+


+

.) Live Granulation Tutorial (2017)

+

different strategies of live granulation and control

+

+

.) Other tutorials

+

+


+

.) Other topics:

+


+

.) enum (2013): general enumeration tool

+


+

.) EventShortcuts (2014): user-defined keywords for events and event patterns

+


+

.) Smooth Clipping and Folding (2017)

+


+

.) DX suite (2017): pseudo ugens for crossfaded mixing and fanning with drate control

+


+

.) Idev suite (2018): patterns and drate ugen searching for numbers with integer distance from a source pattern / signal

+


+

.) Fb1, GFIS (2018): single sample feedback and generalized functional iteration synthesis

+


+

.) Fb1_ODE (2019): general ordinary differential equation integration

+


+

.) ZeroXBufWr / ZeroXBufRd / TZeroXBufRd (2020): playing sequences of segments between zero crossings with demand rate control

+


+

.) Other

+


+


+

Tour 1 – VarGui

+


+

The VarGui class help file as well VarGui shortcut builds are both bloated with information, so I'd like to give just a few examples and references here in order to show its basic features.

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

Tour 1a: synth control

+


+

// quick control of default synth, specs are globally known

+

// note that amp is set to 0 by its default spec

+

// start by pressing the green button

+


+

VarGui(synth: \default).gui

+


+


+

// alternative writing, see help file VarGui shortcut builds 

+


+

\default.sVarGui.gui     

+


+


+

For basic control of a self-defined Synth/SynthDef by passing control specs see the example "synth definition with raising frequency" in VarGui.

+


+

For control of multiple Synths/SynthDefs see the example in the section "discussion" of the same file below.

+


+

It's often more practical to pass control specs as SynthDef metadata as done in Ex. 1a of Buffer Granulation 

+


+


+

Exercise 

+


+

Control your own (sustained) SynthDef with VarGui, either use SynthDef metadata or VarGui's argument 'synthCtr' for passing specs. 

+


+


+

Tour 1b: environmental variables and pattern control

+


+

For basic control of an environmental variable in combination with a Pbind run the example in the discussion section of VarGui's method 'new'.

+


+


+

// after having evaluated p, consider the subtle differences of these variants:

+

// play with slider values and start and pause ad libitum.

+

// The quant argument ensures synchronization when starting players separately.

+


+

// one array variable in one environment, one player

+

v = VarGui([\midi, [50, 70, \lin, 1, 55] ! 3], stream: p, quant: 0.2).gui;

+


+

// three single variables in three environments, three players

+

v = VarGui([\midi, [50, 70, \lin, 1, 55]] ! 3, stream: p!3, quant: 0.2).gui;

+


+

// three array variables in three environments, three players

+

v = VarGui([\midi, [50, 70, \lin, 1, 55] ! 3] ! 3, stream: p!3, quant: 0.2).gui;

+


+


+

These examples show the application of dynamic scoping. A Function has been defined with an "unconnected" free variable and the players evaluate the Function in different Environments provided by the VarGui. Setting the sliders affects the variable of the same name in different Environments of different players.

+


+

This example used a Pfunc, but it works the same with PLx patterns, we take a look at them and leave VarGui for a moment.

+


+


+

Tour 2: PLx suite

+


+

Pdef and Pdefn are main lib's proxies for replacement of event patterns and non-event patterns. PL is the most general PLx proxy, taking over some combined functionality of Pdef and Pdefn on the base of dynamic scoping, go through the examples of the help file PL to see how it works with value and event patterns. 

+


+

Though what neither PL nor Pdef/Pdefn can provide is the replacement of pure lists or list items in the case of list patterns. There's a main lib workaround with a combination of Pn and Plazy (PLx suite, Ex. 1a), but it isn't satisfying for several reasons, so I suppose that PLx list patterns like PLseq are amongst the most useful ones of the whole PLx family, e.g. see PLx suite, Ex. 1b. Other PLx list patterns like PLrand, PLwrand, PLser, PLshuf, PLshufn, PLswitch etc. work similar.

+


+

There exists also a number of non-list PLx patterns, have a look at PLwhite as a typical example.

+

  

+

 

+

Exercise 

+


+

Play your own enveloped SynthDef (or take default SynthDef with params: freq, amp, pan) with some PLx patterns and perform live replacements as in the examples above.

+


+


+

Tour 3: PLx patterns used with VarGui

+


+

See VarGui, Ex. 1a, for basic step sequencing with PLseq, the array variable is implicitely defined in VarGui's first arg.

+


+

See VarGui, Ex. 1c for multiple players and array variables and control of multiple sliders and buttons with modifier keys (note that not all functionality might be available on all platforms).

+


+

See VarGui, Ex. 4a, for a sequencing setup with some PLs used. But more interesting here is that each synth reads is base frequency from a control bus, which gets its data from a separate synth. Synth and EventStreamPlayer are both controlled from the gui. The two slider blocks on the left side concern Synth settings (above) and variable setting for the Pbind / EventStreamPlayer (below). Accordingly we have two players on the right side. Try running the player and starting and stopping the control synth.

+

 

+

Buffer Granulation, Ex. 2a, shows basic language-driven granulation, gui values are taken over by PL patterns and Pfunc.

+

 

+

 

+

Exercise 

+


+

Write a combination of an event pattern and PLx patterns as above with your own SynthDef and play it with a VarGui. Note that it's not necessary to control all args by the gui, nor is it necessary to control parameters directly: you can e.g. control bounds for midinotes in the gui and define the calculation for the actual midinote (e.g. random selection) in the event pattern:

+


+

\midinote, PLwhite(\midiLo, \midiHi)

+


+


+

Tour 4: PbindFx

+


+

PbindFx is an event pattern for effect handling on per-event base. There are other ways for working with event patterns and effects, already possible with main lib, but they have disadvantages: with Pfx and Pfxb there is no built-in way to sequence effect types or effect parameters, you could also route the event's audio to effect buses, but for overlapping events with different fx graphs/params you'd have to define additional buses beforehand as in Ex. 4a.

+


+


+

Ex. 4a: sequencing fx params by using different fx buses

+


+

// first go to PbindFx, Ex. 1a, reboot server with extended ressources and evaluate source and fx synths.

+


+

// start fxs 

+

(

+

a = Bus.audio(s, 2);

+

b = Bus.audio(s, 2);

+


+

x = Synth(\echo, [decayTime: 1.5, echoDelta: 0.15, in: a]);

+

y = Synth(\echo, [decayTime: 5, echoDelta: 0.1, in: b]);

+

)

+


+

// play pattern, the two effects allow switching between them

+

(

+

p = Pbind(

+

\dur, 0.5,

+

\instrument, \source,

+

\note, Pshuf((0..11), inf) + Pseq([[0.2, 14.2], [0, 4], [0, 4]], inf),

+

\octave, Pwhite(3, 6),

+

\out, Pseq([b, a, a], inf)  // here we do fx sequencing

+

).play

+

)

+


+

p.stop

+


+

// we need to do cleanup manually here

+


+

[x, y, a, b].do(_.free);

+


+


+

Ex. 4b: sequencing fx params by using PbindFx

+


+

// Ex. 4a translated to PbindFx syntax –

+

// though it's not exactly identical as fxs are processed in not only two but

+

// more parallel fx synths

+


+

(

+

p = PbindFx([

+

\dur, 0.5,

+

\instrument, \source,

+

\note, PLshuf((0..11)) + PLseq([[0.2, 14.2], [0, 4], [0, 4]]),

+

\octave, Pwhite(3, 6),

+

\fxOrder, 1 // always fx #1 (echo)

+

],[

+

\fx, \echo,

+

\decayTime, PLseq([5, 1.5, 1.5]),

+

\echoDelta, PLseq([0.1, 0.15, 0.15]),

+

\cleanupDelay, 5.2,    // with short default echo would be cut

+


+

// this would save a bit CPU

+

// \cleanupDelay, PLseq([5, 1.5, 1.5]) + 0.2

+

]

+

).play

+

)

+


+


+

// cleanup (delayed freeing of synths and buses) is done automatically

+

// watch server window:

+


+

p.stop

+


+


+

// now run the same example with the following variant of 'echoDelta',

+

// obviously the result cannot be achieved with an approach like in Ex. 4a

+


+

\echoDelta, Pwhite(0.05, 0.2)

+


+


+


+

Ex. 4c: alternation of fx / non-fx by defining a fxOrder sequence

+


+

(

+

p = PbindFx([

+

\dur, 0.2,

+

\instrument, \source,

+

\note, PLshuf((0..11)) + PLseq([[0.4, 14.4], [0, 4], [0, 4]]),

+

\octave, Pwhite(3, 6),

+


+

// fxOrder = 0 means no fx

+

\fxOrder,  PLseq([0, 0, 1]),

+


+

// if no fx, we need to compensate the unified echo delay of 0.2

+

\lag, Pif(Pkey(\fxOrder) > 0, 0, 0.2)

+

],[

+

\fx, \echo,

+

\decayTime, PLseq([5, 1.5, 1.5]),

+

\echoDelta, Pwhite(0.04, 0.12),

+

\cleanupDelay, PLseq([5, 1.5, 1.5]) + 0.2

+

]

+

).play

+

)

+


+

p.stop

+


+


+

Exercise 

+


+

Play your own (percussive + stereo) SynthDef (or instrument 'source') with an arbitrary sequencing of no fx and echos (by defining 'fxOrder') and echo params as in Ex. 4c. Note that in fx 'echo' the max echoDelta defaults to 0.2.

+


+


+

Tour 4d: principle of operation

+


+

For each event several issues have to be internally considered by PbindFx: building and checking of fx graphs (no cycles !), cleaning buses from possibly remaining residual audio (adding "zero synths"), splitting (in case of parallel parts in the fx graph), grouping of all event-related synths and checking accumulated cleanup delay times. You might skip that for the moment, but for a more detailled overview see PbindFx, as well as for a listing of conventions.

+


+


+

Tour 4e: sequential application of fxs (fixed fx chains)

+


+

See PbindFx, examples 1a and 1b.

+


+


+


+

Tour 4f: sequencing different fx chains

+


+

See PbindFx, examples 2a-d.

+


+

Exercise 

+


+

Play your own (percussive + stereo) SynthDef (or instrument 'source') with an arbitrary sequencing of fx chains (your fxs and/or fxs from PbindFx help). Maybe just extend your last own example.

+


+


+

Tour 4g: parallel effects and arbitrary effect graphs

+


+

It seems to be a still underestimated option to design effect graphs different from simple chains. In a classical DAW interface a pile of slots for effects in chain is common, although more differentiated possibilities are also there. Considering event sequencing with PbindFx we can select fx graphs per event / grain, in addition to the sequencing of fx parameters itself. See PbindFx, Ex. 10a for a parallel application of echo, note the syntax of the event graph, passed as Event to 'fxOrder'.

+


+

Exercise 

+


+

Compare the sound of the two fx graphs given in Ex. 10a, also try the following or similar, how do the corresponding fx graphs look like ?

+


+

\fxOrder, `(0: 1, 1: [2, 3], 2: 4),

+

+

\fxOrder, `(0: 1, 1: [2, 3, \o], 2: 4),

+


+

\fxOrder, `(0: 1, 1: [2, 3], 3: 4, 2: 3),

+

+

\fxOrder, `(0: 1, 1: [2, 3], 3: 2, 2: 4),

+


+

\fxOrder, `(0: 1, 1: [2, 3, \o], 3: 4, 2: 3),

+


+


+


+

See PbindFx, Ex. 10b and 10c for modulation graphs and their sequencing.

+


+


+

Exercise 

+


+

Play your own (percussive + stereo) SynthDef (or instrument 'source') with an arbitrary sequencing of fx *graphs* (your fxs and/or fxs from PbindFx help). Maybe just extend your last own example.

+


+


+

Tour 4h: implicit parallelism of single effects

+


+

This can simple be done by passing arrays within fxData, see PbindFx, Ex. 4a. 

+


+


+

Tour 4i: different effects with parallel PbindFxs

+


+

A further option for parallelism, a typical case would be the application of different effects to the notes of a chord, see PbindFx, Ex. 3b (rather straight than 3a). 

+


+


+

Tour 4j: PbindFx and replacements

+


+

See PbindFx, Ex. 7a for replacement of key streams.

+


+

Up to now PbindFx got lists as args in all examples. As args can be event patterns too, they can also be proxy patterns which opens the door for various unusual kinds of source and fx replacements, see PbindFx, Ex. 7b and 7c.

+


+


+

Exercise 

+


+

Take one of your previous working PbindFx examples and rewrite its args (arrays of key/value pairs) as PL or Pbindef proxies. Run the example and replace source and/or fx patterns on the fly.

+


+


+

Tour 4k: continuative topics

+


+

For use with VarGui see PbindFx, Ex. 8.

+


+

For using one fx SynthDef in more than one fxData see PbindFx, Ex. 4.

+


+

For source and fxs reading from external buses (ar or kr) see PbindFx, Ex. 6a-c.

+


+

For using value conversions with fxData see PbindFx, Ex. 9.

+


+


+

Tour 4l: kitchen studies, a granular synthesis application

+


+

One motivation for the development of PbindFx was the idea to explore granular synthesis variants with differentiated effect processings. The fixed media composition kitchen studies collects six short pieces with different fxs and handlings of fx sequencing, each derived from the kitchen sound of five seconds, which is already contained in miSCellaneous lib. At the same time kitchen studies is an ongoing artistic research project: the commented source code is published in the tutorial kitchen studies, compressed versions of the original piece as a whole and its parts can be found on my website http://daniel-mayer.at, a further documentation of the compositional process will follow as publication in the artistic research database Research Catalogue (http://researchcatalogue.net).

+


+


+

Tour 5: EventShortcuts: less typing with Events and event patterns

+


+

EventShortcuts is an interface for defining your own shortcut keys for often used keyword in Events and event patterns. For example you might want to write 'inst' or just 'i' instead of 'instrument' etc., then define your collection of shortcuts – e.g. in your startup or a certain load file – and activate it on occasion. There is also a default shortcuts dictionary, see its content and play a simple example:

+


+


+

EventShortcuts.on;

+


+

EventShortcuts.postAll;

+


+

x = Pbind(\m, Pwhite(60, 90), \d, 0.2).play;

+


+


+

For examples of (re-)defining or extending shortcut dictionaries see EventShortcuts.

+


+

Exercise 

+


+

Take one of your favourite SynthDefs with non-standard arguments (other than freq, amp, pan, etc.) and write an event pattern example, using these arguments. Then choose useful abbreviations, define a new shortcut dictionary with these (either by extending the default or by defining a new one), make it current and run the event pattern example with abbreviated keys. 

+


+


+

Tour 6: live coding

+


+

This was not my main focus from the beginning, but it turned out that PLx patterns, in combination with EventShortcuts and/or Character sequencing (tour 6b) open up live coding possibilities with very condensed syntax.

+


+


+

Tour 6a: PLbindef and PLbindefPar

+


+

PLbindef is a wrapper class for Pbindef, which allows replacements in pseudo-method syntax in a newly created Environment.

+


+

EventShortcuts.on;

+


+

y = PLbindef(\x, \d, 0.2, \m, Pwhite(60, 90)).play;

+


+

// now 'x' is also the name of a new Environment in the current Environment,

+

// its pseudo-methods can be used for setting

+


+

~x.m = PLseq((60..70))

+


+

y.stop

+


+

For further examples see PLbindef.

+


+

PLbindefPar is also based on Pbindef, but unlike PLbindef it's not a plain wrapper: it employs a number of Pbindefs in parallel, which allows control of polyphonic, additive or granular structures, see PLbindefPar. WARNING: if you're piling up a lot of layers, be careful with amplitudes, the amplitude values are taken for the single layers, so you'd have to reduce them accordingly !

+

 

+


+

Tour 6b: PLx and live coding with Strings

+


+

Already in plain SC Strings as Arrays of Characters can be used for sequencing. PLx patterns fit and continuate this concept, see PLx and live coding with Strings for some options, also in connection with PLbindef, PLbindefPar and PbindFx.

+


+


+

Tour 7: independent classes, class families and methods

+


+

Tour 7a: PSx stream patterns

+


+

These are a bit paradoxical classes: Patterns which behave as if they were Streams, thus have an internal state and remember its last value(s), which can e.g. be used for recursion or certain demands of repeated embedding as in the following example:

+


+

(

+

// PS gets source and length patterns as args

+


+

p = PLseq([

+

PS(PLseq((1..5)), PLseq([1, 2])),

+

PS(PLseq((1..5) * 100), PLrand((3..5)))

+

]);

+


+

p.asStream.nextN(100)

+

)

+


+

// this can also be written with a combination of Streams and Patterns,

+

// but it needs more typing (same with Pswitch1 variants)

+


+

(

+

a = Pseq((1..5), inf).asStream;

+

b = Pseq((1..5) * 100, inf).asStream;

+


+

p = Pseq([

+

Pfuncn({ a.next }, Pseq([1, 2], inf).asStream), 

+

Pfuncn({ b.next }, Prand((3..5), inf).asStream),

+

], inf);

+


+

p.asStream.nextN(100)

+

)

+


+

See PSx stream patterns for an overview.

+


+


+

Tour 7b: PSVx sieve patterns and Sieves

+


+

Sieves, recommended by Iannis Xenakis as generative principles for arbitrary musical parameters, are implemented twice: with the class Sieve and Psieve patterns, which adapt the sieve calculus for realtime interactions. The tutorial Sieves and Psieve patterns starts from scratch, thus can be studied completely independent from other miSCellaneous stuff and most other SC requirements. The last chapter gives some audio examples, granular rhythms might be an especially interesting application.

+


+


+

Tour 7c: PmonoPar and PpolyPar

+


+

PmonoPar follows the Pmono convention of a single synth, being set but extends it to an arbitrary number of differently timed setting streams. With PpolyPar the number of continously running synths is arbitrary as well and setting streams can switch the synths to set. That way complicated fx variations can be achieved by a paradigm different from PbindFx.

+


+


+

Tour 7d: enum

+


+

A general tool, which can be used for many enumeration and optimization problems (sets, partitions, graphs etc.), melodic shapes and scales are possible musical applications, see enum.

+


+


+

Tour 7e: HS / HSpar / PHS / PHSpar

+


+

A framework for using server-generated values in Pbind-like objects in the language. A bit outdated now, as synchronous bus gives easy access to server values, however the double-latency mechanism provides a good accuracy of values – better than synchronous bus with standard hardware settings if this is needed – by passing a high granularity parameter. A lower value minimizes OSC traffic if this is more important. See Working with HS and HSpar.

+


+


+

Tour 7f: PSPdiv

+


+

A dynamic multi-layer pulse divider based on Pspawner, as the latter it supports parallel and sequential sprouting of sub-patterns. Might be used for line ornamentation, polyrhythmical structures, granulation etc., see PSPdiv.

+


+


+

Tour 7g: Smooth Clipping and Folding

+


+

A suite of pseudo ugens, see Smooth Clipping and Folding.

+


+


+

Tour 7h: DX suite

+


+

pseudo ugens for crossfaded mixing and fanning according to demand-rate control, see DX suite.

+


+


+

Tour 7i: Idev suite

+


+

patterns and drate ugen searching for numbers with integer distance from a source pattern / signal, see Idev suite.

+


+


+

Tour 7j: Nonlinear dynamics

+


+

pseudo ugens for single sample feedback and generalized functional iteration synthesis, see Fb1 and GFIS. 

+

General ordinary differential equation integration, see Fb1_ODE and related classes.

+


+

Tour 7k: ZeroXBufWr / ZeroXBufRd / TZeroXBufRd

+


+

pseudo ugens for zero crossing analysis and playing sequences of segments between them with demand rate control, see ZeroXBufWr, ZeroXBufRd, TZeroXBufRd.

+


+


+


+

Tour 8: (Rather) independent SC tutorials

+


+

Tour 8a: Event patterns and Functions

+


+

This tutorial is about dynamic scope, comparing the behaviour of Functions, Streams and EventStreamPlayers in Environments. It's thus treating some preconditions which are relevant for PLx suite and VarGui, see Event patterns and Functions.

+


+


+

Tour 8b: Event patterns and LFOs

+


+

Continuous (with LFO) and discrete ("LFO-like") control strategies for event patterns are compared in Event patterns and LFOs.

+


+


+

Tour 8c: Event patterns and array args

+


+

SynthDef array args seem to be a sometimes confusing topic, especially when it's about (pseudo-)variable array lengths and Envelopes, this is even more the case when it comes to the sequencing of such synths. For this reason, and not at last to remind myself to the subtle syntax differences, I wrote this tutorial: Event patterns and array args.

+


+


+

Tour 9: Buffer Granulation and Live Granulation – tutorials

+


+

Actually scheduled as a general overview of granulation possibilities in SC, also collecting various ideas from the sc-users mailing list discussions, I have to admit that in its current form many examples, especially in the Buffer granulation tutorial, require one or more features of miSCellaneous and thus might not always be easy to follow. Then again it would be hard to write such (gui) examples without presuppositions and at the same time with a clearly representable amount of code. However I hope that based on this guided tour it might be easier to step through for people who haven't used this lib before, see Buffer Granulation and Live Granulation.

+


+


+ + diff --git a/Help/Live Granulation.html b/Help/Live Granulation.html new file mode 100755 index 0000000..e332eb4 --- /dev/null +++ b/Help/Live Granulation.html @@ -0,0 +1,938 @@ + + + + + + + + + + + +

Live Granulation different approaches to live granulation with gui examples 

+


+

Part of: miSCellaneous

+


+

See also: Buffer Granulation, Introduction to miSCellaneous, VarGui, VarGui shortcut builds, PLx suite, PbindFx, kitchen studies, Sieves and Psieve patterns, PSPdiv, DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFan, DXFanOut, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+


+

This file is a complement to the Buffer Granulation tutorial. It contains variants of granulation that are applied to an audio signal directly, without the explicit use of a buffer. As with buffer granulation there exist language-driven and server-driven as well as hybrid variants. Especially pattern-based live granulation raises certain accuracy issues, which are summarized in this tutorial. 

+


+

WARNING: 

+


+

1.) Be careful with amplitudes ! 

+

To reduce feedback I've built in tanh, LPF and delay into examples with SoundIn, 

+

though better use headphones to avoid feedback at all. 

+

For tips on reducing feedback or creative use of feedback see Nathaniel Virgo's Feedback quark.

+

+

2.) Avoid too early freeing of audio buses:

+

a) When there are still running synths, unintendedly sound might be routed to a processing resp. feedback chain.

+

b) For the same reason don't free buses if they are hard-wired to SynthDefs, you still want to use.

+

You can free buses on oaccasion if you are sure that nothing is running and you won't need them again.

+

Keep in mind that you can also (re-)start the server with a higher number of audio buses available (Ex. 2c)

+

+

3.) I haven't used below setups for live performances. Although all of them work stable for me as they are, in general hangs can occasionally happen with pattern-driven setups. Often this can be tracked down to sequences of extremely short event durations (and/or long grain durations). Where this can happen as a side-effect, thresholds can be built in. 

+

Another possible source of hangs is careless deep nesting of Patterns where mistakes can easily occur. Starting with clear Pattern structures is recommended - and if more complications are involved: testing without sound first, after saving your patch, might be a good idea.

+


+


+

NOTE: 

+


+

With the exception of Exs. 2d all examples use SoundIn, supposing to use input from your computer resp. soundcard. Depending on your setup you might have to change the standard input (bus = 0) passed to VarGui resp. the SoundIn ugen. Use headphones to avoid feedback!

+


+


+

NOTE: 

+


+

All live granulation variants of this file can of course be applied to any signal, thus also to any playback of a buffer. Vice versa all variants from Buffer Granulation can be applied to a buffer, which is occasionally (or continuously) filled with live input.

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

1.) Live granulation scheduled by server

+


+

This can be done with the GrainIn ugen or self-defined enveloping. An advantage of server-driven live granulation is, that accuracy issues and their workarounds, as described in Exs. 2a-2d, are avoided. As a restriction, sequenced processing of grains, e.g. as with PbindFx, is more difficult.

+


+


+

Example 1a:   Basic live granulation with GrainIn

+


+

(

+

~audioBus_ex_1a = Bus.audio(s, 1);

+


+

// pass envelopes with buffer, hanning is GrainIn's default anyway

+

h = Signal.hanningWindow(1000);

+

e = Buffer.loadCollection(s, h);

+


+

SynthDef(\soundIn, { |out, in, ampIn = 1|

+

Out.ar(out, SoundIn.ar(in) * ampIn)

+

}).add;

+


+

SynthDef(\live_gran_1a, { |out, in, envBuf, trigRate = 50, overlap = 0.5, panMax = 0.5,

+

panType = 0, amp = 1, minGrainDur = 0.001|

+

var inSig, sig, trig, grainDur, pan;

+


+

inSig = In.ar(in, 1);

+

// reduce feedback

+

inSig = DelayC.ar(LPF.ar(inSig.tanh, 2000), 0.1, 0.01);

+


+

trig = Impulse.ar(trigRate);

+

grainDur = max(trigRate.reciprocal * overlap, minGrainDur);

+


+

// select L/R or random sequencing

+

pan = Demand.ar(

+

trig,

+

0,

+

Dswitch1([

+

Dseq([1, -1], inf),

+

Dwhite(-1, 1)

+

], panType)

+

) * min(panMax, 0.999);

+


+

sig = GrainIn.ar(

+

2,

+

trig,

+

grainDur,

+

inSig,

+

pan,

+

envBuf

+

);

+

Out.ar(out, sig * EnvGate.new * amp);

+

}).add;

+

)

+


+


+

// to avoid feedback use with headphones 

+


+

(

+

// start granulation (first row) before to ensure right order

+

VarGui(

+

synthCtr: [

+

[

+

\envBuf, e.bufnum,

+

\in, ~audioBus_ex_1a.index,

+

\trigRate, [5, 500, \lin, 0, 50],

+

\overlap, [0.05, 3, \lin, 0, 0.5],

+

\panType, [0, 1, \lin, 1, 0],

+

\panMax, [0, 1, \lin, 0, 0.5],

+

\amp, [0, 1, \lin, 0, 0.1]

+

],[

+

\out, ~audioBus_ex_1a.index,

+

// you might have to pass a different 'in' bus for SoundIn

+

// depending on your setup

+

\in, 0,

+

\ampIn, [0, 1, \lin, 0, 1]

+

]

+

],

+

synth: [\live_gran_1a, \soundIn]

+

).gui

+

)

+


+


+

Even with overlapping grains, sequenced effect processing per grain is possible with GrainIn, but requires more effort, see Ex. 1e in the Buffer Granulation tutorial.

+


+


+

Example 1b:   Live granulation with server-driven enveloping

+


+

In this example only non-overlapping grains are regarded, for overlapping a multichannel approach as in Ex. 1e of the Buffer Granulation tutorial can be applied. In comparison to live granulation example 1a there is hardly an advantage in this basic variant. However intermediate processings can be built into this SynthDef, which aren't possible in the above example, where granulation is encapsulated in a UGen, manipulation of the envelope signal could be one.

+


+

(

+

~audioBus_ex_1b = Bus.audio(s, 1);

+


+

// pass envelopes with buffer

+

h = Signal.hanningWindow(1000);

+

e = Buffer.loadCollection(s, h);

+


+

SynthDef(\soundIn, { |out, in, ampIn = 1|

+

Out.ar(out, SoundIn.ar(in) * ampIn)

+

}).add;

+


+

// suppose overlap < 1

+


+

SynthDef(\live_gran_1b, { |out, in, envBuf, trigRate = 50, overlap = 0.5, panMax = 0.5,

+

panType = 0, amp = 1, minGrainDur = 0.001, interpolation = 4|

+

var inSig, env, numFrames = BufFrames.kr(envBuf), startTrig, grainDur,

+

latchedTrigRate, latchedStartTrig, latchedOverlap, pan;

+


+

inSig = In.ar(in, 1);

+

// reduce feedback

+

inSig = DelayC.ar(LPF.ar(inSig.tanh, 2000), 0.1, 0.01);

+


+

startTrig = Impulse.ar(trigRate);

+

// why this ? - shape of envelope shouldn't be changed while application

+

latchedTrigRate = Latch.ar(K2A.ar(trigRate), startTrig);

+

latchedStartTrig = Impulse.ar(latchedTrigRate);

+

latchedOverlap = Latch.ar(K2A.ar(overlap), startTrig);

+


+

latchedOverlap = max(latchedOverlap, latchedTrigRate * minGrainDur);

+

grainDur = (latchedOverlap / latchedTrigRate);

+


+

env = BufRd.ar(

+

1,

+

envBuf,

+

Sweep.ar(

+

latchedStartTrig,

+

latchedOverlap.reciprocal * latchedTrigRate * numFrames,

+

).clip(0, numFrames - 1),

+

interpolation: interpolation

+

);

+


+

pan = Demand.ar(

+

latchedStartTrig,

+

0,

+

Dswitch1([

+

Dseq([1, -1], inf),

+

Dwhite(-1, 1)

+

], panType)

+

) * panMax * 0.999;

+


+

Out.ar(out, Pan2.ar(inSig * env * amp, pan) * EnvGate.new);

+

}).add

+

)

+


+


+

// to avoid feedback use with headphones 

+


+

(

+

// start granulation (first row) before to ensure right order

+

VarGui(

+

synthCtr: [

+

[

+

\envBuf, e.bufnum,

+

\in, ~audioBus_ex_1b.index,

+

\trigRate, [5, 500, \lin, 0, 50],

+

\overlap, [0.05, 0.99, \lin, 0, 0.5],

+

\panType, [0, 1, \lin, 1, 0],

+

\panMax, [0, 1, \lin, 0, 0.5],

+

\amp, [0, 1, \lin, 0, 0.1]

+

],[

+

\out, ~audioBus_ex_1b.index,

+

// you might have to pass a different 'in' bus for SoundIn

+

// depending on your setup

+

\in, 0,

+

\ampIn, [0, 1, \lin, 0, 1]

+

]

+

],

+

synth: [\live_gran_1b, \soundIn]

+

).gui

+

)

+


+


+


+

+

2.) Live granulation driven by language

+


+

The crucial point of this strategy is a kind of incompatibility of In.ar and OffsetOut. Normally we use OffsetOut for exact buffer granulation with patterns, as the start of the synth (the grain) is corrected by a shift (standard Out.ar is only able to start at control block boundaries). However, when In.ar and OffsetOut.ar are used in the same synth the read input signal is also shifted, which results in a correct grain distance but an fluctuating input delay. This can be overcome by a little trick: we can use OffsetOut to send a trigger to a bus, which indicates its correct delay. Then the trigger can be read in the same synth and trigger again a gated envelope for the input signal, the resulting grain can be output with normal Out.ar. So grain distances are correct and input is not fluctuating (see Ex. 2d for an accuracy comparison). Nevertheless language-driven sequencing is not sample-exact in realtime, no matter if In.ar is used or not, this is related to hardware control and cannot be overcome. This might be an issue with a strictly periodic input signal and very short grain distances. You can e.g. check out with granulation of a fixed-pitch triangular wave or similar, every few seconds or so there will happen an audible jump due to an irregular time interval, which is necessary for clock calibration; at that point there will, in general, also be a phase shift of the input signal (see last example of 2d). This might not be noticed with an input of spoken voice e.g.

+

Control block length is also a boundary for gated envelopes defined with EnvGen – here envelopes are established with an env buffer used by BufRd and Sweep ugens, a flexible method as Envelopes shape can be defined arbitrarily in language.

+


+


+

Example 2a:   Basic Pbind live granulation

+


+

At that point I haven't seen a solution to pass the trigger bus as argument, in the example it is hard-wired. As the SynthDef uses a SetResetFF ugen, which relies on two triggers within control block length, grainDelta shouldn't be shorter than that. If shorter grainDeltas are required this would be possible with alternating SynthDefs and buses. For the same reason this SynthDef should only be used once at the same time by a Pbind/EventStreamPlayer. Alternatively – as in Ex. 2b – a SynthDef factory can produce a number of structurally equal SynthDefs bound to a number of buses.

+


+


+

(

+

// input bus and trigger bus

+

// trigger bus must be hard-wired in SynthDef though

+


+

~audioBus_ex_2a = Bus.audio(s, 1);

+

~trigBus_ex_2a = Bus.audio(s, 1);

+


+

// pass envelopes with buffer

+

h = Signal.hanningWindow(1000);

+

e = Buffer.loadCollection(s, h);

+


+

SynthDef(\soundIn, { |out, in, ampIn = 1|

+

Out.ar(out, SoundIn.ar(in) * ampIn)

+

}).add;

+


+

SynthDef(\live_gran_2a, { |out, inBus, envBuf, grainDelta, pan = 0,

+

overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4|

+


+

var inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate,

+

defaultOffset = ControlRate.ir.reciprocal;

+

inSig = In.ar(inBus, 1);

+

+

// reduce feedback

+

inSig = LPF.ar(inSig.tanh, 2000);

+


+

// low overlap bound given by minimal grain duration

+

overlap = max(overlap, minGrainDur / grainDelta);

+


+

// impulse for offset check

+

OffsetOut.ar(~trigBus_ex_2a, Impulse.ar(0));

+


+

// additional gate to start with 0 in delayed case

+

// this is necessary as OffsetOut outputs impulse twice

+

gate = SetResetFF.ar(In.ar(~trigBus_ex_2a));

+


+

env = BufRd.ar(

+

1,

+

envBuf,

+

Sweep.ar(

+

In.ar(~trigBus_ex_2a),

+

(overlap * grainDelta).reciprocal * numFrames,

+

).clip(0, numFrames - 1),

+

interpolation: interpolation

+

);

+

// to finish synth

+

Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);

+


+

// shifted accurate output

+

Out.ar(out, Pan2.ar(env * gate * inSig * amp, pan));

+

}).add

+

)

+


+


+

// to avoid feedback use with headphones 

+


+

// start synth and event stream player

+


+

(

+

p = Pbind(

+

\instrument, \live_gran_2a,

+

\inBus, ~audioBus_ex_2a,

+

\envBuf, e,

+

\dur, Pfunc { ~trigRate.reciprocal },

+

\grainDelta, Pkey(\dur),

+

\overlap, Pfunc { ~overlap },

+

\amp, Pfunc { ~amp },

+

\pan, Pfunc { ~panMax } * PLseq([1, -1]),

+

\addAction, \addToTail

+

);

+


+

VarGui([

+

\trigRate, [20, 500, \lin, 0, 100],

+

\overlap, [0.1, 2, \lin, 0, 0.5],

+

\panMax, [0, 1, \lin, 0, 0.5],

+

\amp, [0, 1, \lin, 0, 0.1]

+

],[

+

\out, ~audioBus_ex_2a.index,

+

// you might have to pass a different 'in' bus for SoundIn

+

// depending on your setup

+

\in, 0,

+

\ampIn, [0, 1, \lin, 0, 1]

+

], p, \soundIn

+

).gui

+

)

+


+


+


+


+

Example 2b:   Parallel Pbind live granulation

+


+

As trigger buses have to be hard-wired, a SynthDef factory produces a number of structurally equal SynthDefs bound to a number of buses. In this variant the granulation is combined with a bandpass filter.

+


+

(

+

// input bus and trigger bus

+

// trigger bus must be hard-wired in SynthDef though

+


+

~audioBus_ex_2b = Bus.audio(s, 1);

+


+

// pass envelopes with buffer

+

h = Signal.hanningWindow(1000);

+

e = Buffer.loadCollection(s, h);

+


+

SynthDef(\soundIn, { |out, in, ampIn = 1|

+

Out.ar(out, SoundIn.ar(in) * ampIn)

+

}).add;

+


+

// "SynthDef factory", as each SynthDef neads hard-wired bus, make 3 of each

+


+

~trigBus_ex_2b = { Bus.audio(s, 1) } ! 3;

+


+

{ |i|

+

var name = \live_gran_2b ++ "_" ++ i.asString;

+

SynthDef(name, { |out, inBus, envBuf, grainDelta, pan = 0, rq = 0.1, center = 500,

+

overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4|

+


+

var sig, inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate,

+

defaultOffset = ControlRate.ir.reciprocal;

+

inSig = In.ar(inBus, 1);

+

+

// reduce feedback

+

inSig = LPF.ar(inSig.tanh, 2000);

+


+

// amplitude compensation for lower rq of bandpass filter

+

inSig = BPF.ar(inSig, center, rq, (rq ** -1) * (400 / center ** 0.5));

+


+

// low overlap bound given by minimal grain duration

+

overlap = max(overlap, minGrainDur / grainDelta);

+


+

// impulse for offset check

+

OffsetOut.ar(~trigBus_ex_2b[i], Impulse.ar(0));

+


+

// additional gate to start with 0 in delayed case

+

// this is necessary as OffsetOut outputs impulse twice

+

gate = SetResetFF.ar(In.ar(~trigBus_ex_2b[i]));

+


+

env = BufRd.ar(

+

1,

+

envBuf,

+

Sweep.ar(

+

In.ar(~trigBus_ex_2b[i]),

+

(overlap * grainDelta).reciprocal * numFrames,

+

).clip(0, numFrames - 1),

+

interpolation: interpolation

+

);

+

// to finish synth

+

Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);

+


+

sig = env * gate * inSig * amp;

+


+

// shifted accurate output

+

Out.ar(out, Pan2.ar(sig, pan));

+

}).add

+

} ! 3

+

)

+


+


+

// to avoid feedback use with headphones 

+


+

// start synth and event stream players

+


+

(

+

// pattern maker, we need the three different instruments

+

p = { |i| Pbind(

+

\instrument, \live_gran_2b ++ "_" ++ i.asString,

+

\inBus, ~audioBus_ex_2b,

+

\envBuf, e,

+

\dur, Pfunc { ~trigRate.reciprocal },

+

\grainDelta, Pkey(\dur),

+

\overlap, Pfunc { ~overlap },

+

\rq, Pfunc { ~rq },

+

\center, Pfunc { ~center },

+

\amp, Pfunc { ~amp },

+

\pan, Pfunc { ~panMax } * PLseq([1, -1]),

+

\addAction, \addToTail

+

) };

+


+

VarGui([

+

\trigRate, [20, 500, \lin, 0, 100],

+

\overlap, [0.1, 2, \lin, 0, 0.5],

+

\panMax, [0, 1, \lin, 0, 0.5],

+

\center, [100, 3000, \exp, 0, 800],

+

\rq, [0.1, 1, \lin, 0, 0.1],

+

\amp, [0, 1, \lin, 0, 0.1]

+

] ! 3,[

+

\out, ~audioBus_ex_2b.index,

+

// you might have to pass a different 'in' bus for SoundIn

+

// depending on your setup

+

\in, 0,

+

\ampIn, [0, 1, \lin, 0, 1]

+

], p ! 3, \soundIn

+

).gui

+

)

+


+


+


+

Example 2c:   Live granulation with PbindFx

+


+


+

// extended ressources needed

+


+

(

+

s.options.numPrivateAudioBusChannels = 1024;

+

s.options.memSize = 8192 * 16;

+

s.reboot;

+

)

+


+


+

(

+

// input bus and trigger bus

+

// trigger bus must be hard-wired in SynthDef though

+


+

~audioBus_ex_2c = Bus.audio(s, 1);

+

~trigBus_ex_2c = Bus.audio(s, 1);

+


+

// pass envelopes with buffer

+

h = Signal.hanningWindow(1000);

+

e = Buffer.loadCollection(s, h);

+


+

SynthDef(\soundIn, { |out, in, ampIn = 1|

+

Out.ar(out, SoundIn.ar(in) * ampIn)

+

}).add;

+


+


+

// two fx SynthDefs

+


+

SynthDef(\resample, { |out = 0, in, mix = 0.5, amp = 1, resampleRate = 44100|

+

    var sig, inSig = In.ar(in, 2);

+

    sig = Latch.ar(inSig, Impulse.ar(resampleRate));

+

    Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp);

+

}).add;

+


+

SynthDef(\bpf, { |out = 0, in, freq = 440, rq = 0.1, amp = 1, mix = 1|

+

    var sig, inSig = In.ar(in, 2);

+

    sig = BPF.ar(inSig, freq, rq, (rq ** -1) * (400 / freq ** 0.5));

+

    Out.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp);

+

}).add;

+


+


+

SynthDef(\live_gran_2c, { |out, inBus, envBuf, grainDelta, pan = 0,

+

overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4|

+


+

var inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate,

+

defaultOffset = ControlRate.ir.reciprocal;

+

inSig = In.ar(inBus, 1);

+


+

// reduce feedback

+

inSig = LPF.ar(inSig.tanh, 2000);

+


+

// low overlap bound given by minimal grain duration

+

overlap = max(overlap, minGrainDur / grainDelta);

+


+

// impulse for offset check

+

OffsetOut.ar(~trigBus_ex_2c, Impulse.ar(0));

+


+

// additional gate to start with 0 in delayed case

+

// this is necessary as OffsetOut outputs impulse twice

+

gate = SetResetFF.ar(In.ar(~trigBus_ex_2c));

+


+

env = BufRd.ar(

+

1,

+

envBuf,

+

Sweep.ar(

+

In.ar(~trigBus_ex_2c),

+

(overlap * grainDelta).reciprocal * numFrames,

+

).clip(0, numFrames - 1),

+

interpolation: interpolation

+

);

+

// to finish synth

+

Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);

+


+

// shifted accurate output

+

Out.ar(out, Pan2.ar(env * gate * inSig * amp, pan));

+

}).add

+

)

+


+


+

(

+

// start without fx

+


+

~fxOrder = 0;

+


+

// playing the PbindFx in a new group ensures that soundIn synth is placed before when started later

+


+

g = Group.new;

+


+

p = PbindFx([

+

\instrument, \live_gran_2c,

+

\inBus, ~audioBus_ex_2c,

+

// allow hard-wired bus (bus connections check)

+

\otherBusArgs, [\inBus, ~trigBus_ex_2c.index.asFloat],

+

\envBuf, e,

+

\dur, 1 / PL(\trigRate),

+

\grainDelta, Pkey(\dur),

+

\overlap, PL(\overlap),

+

\amp, PL(\amp),

+

\pan, PL(\panMax) * PLseq([1, -1]),

+

\fxOrder, PL(\fxOrder, envir: 't'),

+

\cleanupDelay, 0.1,

+


+

\group, g

+

],[

+

\fx, \resample,

+

\mix, 1,

+

\amp, PL(\amp_resample),

+

\resampleRate, PL(\resampleRate_resample),

+

\cleanupDelay, 0.01

+

],[

+

\fx, \bpf,

+

\freq, PL(\freq_bpf),

+

\rq, PL(\rq_bpf),

+

\mix, 1,

+

\amp, PL(\amp_bpf),

+

\cleanupDelay, 0.01

+

]

+

);

+


+

VarGui([

+

\trigRate, [20, 500, \lin, 0, 100],

+

\overlap, [0.1, 2, \lin, 0, 0.5],

+

\panMax, [0, 1, \lin, 0, 0.5],

+

\amp, [0, 1, \lin, 0, 0.1],

+


+

\resampleRate_resample, [200, 3000, \exp, 0, 500],

+

\amp_resample, [0, 1, \lin, 0, 1],

+


+

\freq_bpf, [50, 3000, \exp, 0, 200],

+

\rq_bpf, [0.1, 1, \lin, 0, 0.3],

+

\amp_bpf, [0, 1, \lin, 0, 1]

+

],[

+

\out, ~audioBus_ex_2c.index,

+

// you might have to pass a different 'in' bus for SoundIn

+

// depending on your setup

+

\in, 0,

+

\ampIn, [0, 1, \lin, 0, 1]

+

], p, \soundIn

+

).gui(

+

    varColorGroups: (0..8).clumps([4, 2, 3]),

+

    tryColumnNum: 1,

+

    labelWidth: 140,

+

    sliderWidth: 350

+

)

+

)

+


+


+

// check fxs and their params, mix is fixed to 1

+


+

// resample

+


+

~fxOrder = 1

+


+


+

// band pass

+


+

~fxOrder = 2

+


+


+

// resample -> band pass

+


+

~fxOrder = [1, 2]

+


+


+

// band pass -> resample 

+


+

~fxOrder = [2, 1]

+


+


+

// fx alternations

+

// Pstutter with repeats equal 2 ensures L/R balance

+


+

~fxOrder = Pstutter(2, PLseq([1, 2]))

+


+

~fxOrder = Pstutter(2, PLseq([[1, 2], 1]))

+


+

~fxOrder = Pstutter(2, PLseq([[1, 2], 2]))

+


+


+

~fxOrder = Pstutter(2, PLseq([[2, 1], 1]))

+


+

~fxOrder = Pstutter(2, PLseq([[2, 1], 2]))

+


+


+

~fxOrder = Pstutter(2, PLseq([[1, 2], [2, 1]]))

+


+


+


+


+

Example 2d:   Accuracy comparison

+


+

This example compares the inaccuracies occuring with "normal" usage of Out.ar and the usage of In.ar + OffsetOut.ar with the strategy recommended in Ex. 2a. Run the three examples, the audio output is played and recorded into three files in the recordings directory (you get the path with thisProcess.platform.recordingsDir).

+


+


+

// test with source of added sines, inaccuracies with straight use of Out.ar

+


+

(

+

~audioBus_ex_2d1 = Bus.audio(s, 1);

+


+

// pass envelopes with buffer

+

// envelope with sharp attack to make effect more clear

+

h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000);

+

e = Buffer.loadCollection(s, h);

+


+

// sine as test signal

+

SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in|

+

freq = freq * (1..8);

+

Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new);

+

Out.ar(granOut, SinOsc.ar(freq).sum * amp);

+

}).add;

+


+

// envelopes in left channel, granulation in right

+

SynthDef(\live_gran_shaky_1, { |out, inBus, envBuf, grainDelta,

+

overlap = 1, amp = 1, interpolation = 4|

+


+

var inSig, offset, env, numFrames = BufFrames.ir(envBuf),

+

defaultOffset = ControlRate.ir.reciprocal;

+

inSig = In.ar(inBus, 1);

+

+

// reduce feedback with sound in source

+

inSig = LPF.ar(inSig.tanh, 2000);

+


+

env = BufRd.ar(

+

1,

+

envBuf,

+

Sweep.ar(

+

1,

+

(overlap * grainDelta).reciprocal * numFrames,

+

).clip(0, numFrames - 1),

+

interpolation: interpolation

+

);

+

// to finish synth

+

Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);

+


+

Out.ar(out + 1, inSig * env);

+

}).add;

+


+


+

p = Pfindur(1, Pbind(

+

\instrument, \live_gran_shaky_1,

+

\inBus, ~audioBus_ex_2d1,

+

\envBuf, e,

+

\dur, 0.005,

+

\grainDelta, Pkey(\dur),

+

\overlap, 0.5,

+

\amp, 0.1,

+

\addAction, \addToTail

+

));

+

)

+


+


+

(

+

// record live input and granulation (which comes with delay due to normal pbind latency)

+

// see the file in an editor then:

+

// distances are irregular due to Out ugen's accuracy limit control block boundary,

+

// though source signal is not delayed

+


+

{

+

~date = Date.getDate.stamp;

+

~fileName = thisProcess.platform.recordingsDir +/+

+

("live_gran_shaky_1" ++ "_" ++ ~date ++ ".aiff");

+

s.record(~fileName);

+

s.sync;

+


+

x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d1]);

+

p.play;

+


+

1.5.wait;

+

x.release;

+

s.stopRecording;

+

}.fork

+

)

+


+


+


+

/////////////////////////

+


+

// test with source of added sines, inaccuracies with use of In.ar + OffsetOut.ar 

+


+

(

+

~audioBus_ex_2d2 = Bus.audio(s, 1);

+


+

// pass envelopes with buffer

+

// envelope with sharp attack to make effect more clear

+

h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000);

+

e = Buffer.loadCollection(s, h);

+


+

// sine as test signal

+

SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in|

+

freq = freq * (1..8);

+

Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new);

+

Out.ar(granOut, SinOsc.ar(freq).sum * amp);

+

}).add;

+


+

// envelopes in left channel, granulation in right

+

SynthDef(\live_gran_shaky_2, { |out, inBus, envBuf, grainDelta,

+

overlap = 1, amp = 1, interpolation = 4|

+


+

var inSig, offset, env, numFrames = BufFrames.ir(envBuf),

+

defaultOffset = ControlRate.ir.reciprocal;

+

inSig = In.ar(inBus, 1);

+


+

// reduce feedback with sound in source

+

inSig = LPF.ar(inSig.tanh, 2000);

+


+

env = BufRd.ar(

+

1,

+

envBuf,

+

Sweep.ar(

+

1,

+

(overlap * grainDelta).reciprocal * numFrames,

+

).clip(0, numFrames - 1),

+

interpolation: interpolation

+

);

+

// to finish synth

+

Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);

+


+

OffsetOut.ar(out + 1, inSig * env);

+

}).add;

+


+


+

p = Pfindur(1, Pbind(

+

\instrument, \live_gran_shaky_2,

+

\inBus, ~audioBus_ex_2d2,

+

\envBuf, e,

+

\dur, 0.005,

+

\grainDelta, Pkey(\dur),

+

\overlap, 0.5,

+

\amp, 0.1,

+

\addAction, \addToTail

+

));

+

)

+


+


+

// record live input and granulation (which comes with delay due to normal pbind latency)

+

// see the file in an editor then:

+

// distances are regular due to OffsetOut, but source signal is delayed

+

// between 0 and control block length

+


+

(

+

{

+

~date = Date.getDate.stamp;

+

~fileName = thisProcess.platform.recordingsDir +/+

+

("live_gran_shaky_2" ++ "_" ++ ~date ++ ".aiff");

+

s.record(~fileName);

+

s.sync;

+


+

x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d2]);

+

p.play;

+


+

1.5.wait;

+

x.release;

+

s.stopRecording;

+

}.fork

+

)

+


+


+


+

/////////////////////////

+


+

// test with source of added sines, granulation done as in Ex. 2a  

+


+

(

+

// input bus and trigger bus

+

// trigger bus must be hard-wired in SynthDef though

+


+

~audioBus_ex_2d3 = Bus.audio(s, 1);

+

~trigBus_ex_2d3 = Bus.audio(s, 1);

+


+


+

// pass envelopes with buffer

+

// envelope with sharp attack to make effect more clear

+

h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000);

+

e = Buffer.loadCollection(s, h);

+


+

// sine as test signal

+

SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in|

+

freq = freq * (1..8);

+

Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new);

+

Out.ar(granOut, SinOsc.ar(freq).sum * amp);

+

}).add;

+


+

// envelopes in left channel, granulation in right

+

SynthDef(\live_gran_ok, { |out, inBus, envBuf, grainDelta, gate,

+

overlap = 1, amp = 1, interpolation = 4|

+


+

var inSig, offset, env, numFrames = BufFrames.ir(envBuf),

+

defaultOffset = ControlRate.ir.reciprocal;

+

inSig = In.ar(inBus, 1);

+


+

// reduce feedback with sound in source

+

inSig = LPF.ar(inSig.tanh, 2000);

+


+

// impulse for offset check

+

OffsetOut.ar(~trigBus_ex_2d3, Impulse.ar(0));

+


+

// necessary additional gate to start with 0 in delayed case

+

gate = SetResetFF.ar(In.ar(~trigBus_ex_2d3));

+


+

env = BufRd.ar(

+

1,

+

envBuf,

+

Sweep.ar(

+

In.ar(~trigBus_ex_2d3),

+

(overlap * grainDelta).reciprocal * numFrames,

+

).clip(0, numFrames - 1),

+

interpolation: interpolation

+

);

+

// to finish synth

+

Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2);

+


+

// shifted accurate output

+

Out.ar(out + 1, inSig * env * gate);

+

}).add;

+


+


+

p = Pfindur(1, Pbind(

+

\instrument, \live_gran_ok,

+

\inBus, ~audioBus_ex_2d3,

+

\envBuf, e,

+

\dur, 0.005,

+

\grainDelta, Pkey(\dur),

+

\overlap, 0.5,

+

\amp, 0.1,

+

\addAction, \addToTail

+

));

+

)

+


+


+

(

+

// record live input and granulation (which comes with delay due to normal pbind latency)

+

// see the file in an editor then:

+

// distances are regular due to OffsetOut, and source signal is not delayed

+


+

// However also in this case, because of hardware output calibration, it happens that phase shifts

+

// occur in granulation.

+

// This is currently unavoidable in SC's realtime mode, but probably irrelevant in most cases of 

+

// live granulation, where there is no strictly periodic input signal

+


+

{

+

~date = Date.getDate.stamp;

+

~fileName = thisProcess.platform.recordingsDir +/+

+

("\live_gran_ok" ++ "_" ++ ~date ++ ".aiff");

+

s.record(~fileName);

+

s.sync;

+


+

x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d3]);

+

p.play;

+


+

1.5.wait;

+

x.release;

+

s.stopRecording;

+

}.fork

+

)

+


+


+ + diff --git a/Help/MemoRoutine.html b/Help/MemoRoutine.html new file mode 100755 index 0000000..bdc5700 --- /dev/null +++ b/Help/MemoRoutine.html @@ -0,0 +1,266 @@ + + + + + + + + + + + +

MemoRoutine Routine-like object which stores last values

+


+

Part of: miSCellaneous

+


+

Inherits from: Stream

+


+

A MemoRoutine behaves like a Routine, though it is not defined as a subclass of it. It stores last values, the maximum buffer size can be defined. MemoRoutine is internally used by PSx and related Pattern classes, which therefore also remember their last value(s). 

+


+

See also: Routine, PSx stream patterns, PS, PSdup, PSrecur

+


+


+

Creation / Class Methods

+


+

*new (func, stackSize, bufSize, copyItems, copySets)

+

+

Creates a MemoRoutine instance with the given Function.

+

+

func - Function to instantiate the Routine with.

+

stackSize - Call stack depth, defaults to 512.

+

bufSize - Number of last values to store, defaults to 1.

+

copyItems - Determines if and how to copy items, which are returned by method next 

+

(run, value, resume) and which are either non-Sets or member of Sets, into the buffer. 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). 

+

Other values are interpreted as 0. Defaults to 0.

+

0: storage of original item 

+

1: storage of copied item

+

2: storage of deepCopied item

+

copySets - Determines if to copy Sets (and hence Events), 

+

which are returned by method next (run, value, resume), into the buffer. 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). 

+

Other values are interpreted as 0. Defaults to 1.

+

0: storage of original Set 

+

1: storage of copied Set

+

+

NOTE: The distinction of copying items and sets makes sense in the case of event streams.

+

Per default Events are copied (copySets == 1), not their values (copyItems == 0). 

+

By playing Events those are used to store additional data (synth ids, msgFuncs …) 

+

which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use 

+

MemoRoutine - copied Events will not contain this additional data. 

+

If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured 

+

then copying makes no sense, this is the normal case, so copyItems defaults to 0.

+

When going to alter the ouput, you might want to set copyItems to 1 for a MemoRoutine returning 

+

simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set

+

copySets to 1 and copyItems to 2 (an option copySets == 2 doesn't exist as

+

it would be contradictory in combination with copyItems < 2).

+

+

+


+

Instance Methods

+


+

next (inval)

+

+

inval - Same conventions with method yield as with aRoutine.next.

+


+

value (inval)

+

+

Same as next.

+


+

resume (inval)

+

+

Same as next.

+


+

run (inval)

+

+

Same as next.

+

+

lastValue

+

+

Last value.

+


+

lastValues

+

+

Instance variable getter method for array of stored last values.

+

+

at (i)

+

+

Returns ith item of array of last values (keep in mind reversed order: last value first)

+

+

bufSize

+

+

Size of array of last values.

+


+

reset

+

+

Resets the MemoRoutine by resetting its Routine.

+


+

stop

+

+

Stops the MemoRoutine by stopping its Routine.

+


+

count, count_(value)

+

+

Instance variable getter and setter methods.

+

Counts each call of next / value / resume / run. 

+


+

copyItems, copyItems_(value)

+

+

Instance variable getter and setter methods. 

+

If used directly the Integer copy code must be passed (see above).

+

+

copySets, copySets_(value)

+

+

Instance variable getter and setter methods.

+

If used directly the Integer copy code must be passed (see above).

+


+

routine, routine_(value)

+

+

Instance variable getter and setter methods. 

+


+

+

Examples

+


+

(

+

// a MemoRoutine

+


+

m = MemoRoutine(

+

{ |inval| 1000.do { |i| inval = i.yield } },

+

bufSize: 50

+

);

+

)

+


+

m.nextN(5);

+


+


+

// get last value

+


+

m.lastValue;

+


+

m[0];

+


+


+

// get last values, order is from last to earlier

+


+

m.lastValues;

+


+


+

// get value before last value

+


+

m[1];

+


+


+


+

// get more values

+


+

m.nextN(100);

+


+


+


+

// first values are lost now

+


+

m.lastValues;

+


+


+

// as loop in m is restricted you might call .all

+

// as stop is indicated by the return of nil, 

+

// the array lastValues contains nil as first item

+


+

m.all;

+


+

m.lastValues;

+


+


+


+

// Per default a MemoRoutine is just storing the last values,

+

// with structured data types this can cause surprises 

+


+

(

+

// a MemoRoutine

+


+

m = MemoRoutine(

+

{ |inval| var a = [[2,1], [4,3]]; 1000.do { inval = a.choose.yield } },

+

bufSize: 50

+

);

+

)

+


+

// generate some values

+


+

m.nextN(10);

+


+

// apply a method on last values

+


+

(

+

x = m.lastValue;

+

x.sort;

+

)

+


+

// as sort is destructive one of MemoRoutine's arrays is altered now !

+


+

m.nextN(10);

+


+


+


+

// to avoid such you can simply do .copy.sort which doesn't alter lastValues,

+

// but you can also store copied values in the buffer by passing a flag with

+

// instantiation of the MemoRoutine

+


+

(

+

// a MemoRoutine which stores copied arrays

+


+

m = MemoRoutine(

+

{ |inval| var a = [[2,1], [4,3]]; 1000.do { inval = a.choose.yield } },

+

bufSize: 100,

+

copyItems: 1 // 1, true or \true for copy, 2 or \deep for deepCopy

+

);

+

)

+


+

m.nextN(10);

+


+

(

+

x = m.lastValue;

+

x.sort;

+

)

+


+

// order not altered with next items

+


+

m.nextN(10);

+


+

// however the concerned array stored as item in lastValues remains altered

+


+

m[10];

+


+


+


+


+


+ + diff --git a/Help/Other event and pattern shortcuts.html b/Help/Other event and pattern shortcuts.html new file mode 100644 index 0000000..2266217 --- /dev/null +++ b/Help/Other event and pattern shortcuts.html @@ -0,0 +1,297 @@ + + + + + + + + + + + +

Other event and pattern shortcuts various shortcut writings for events and patterns

+


+

Part of: miSCellaneous

+


+

See also: EventShortcuts, PLx and live coding with Strings

+


+

Apart from the class EventShortcuts itself, which handles bookkeeping of shortcut dictionaries, miSCellaneous lib includes some additional shortcut methods for generation and playing of events and different types of event patterns from SequenceableCollections. These methods support functional conventions for reference.

+


+


+

1.) Event shortcuts

+


+

1a: Event methods 

+


+

anEvent.on

+

+

Plays an Event. If dur isn't specified it is set to inf.

+

+

anEvent.off(releaseTime)

+

+

Releases the Event's node. 

+

+

releaseTime - SimpleNumber for release time. Defaults to nil (default release time of event mechanism)

+


+


+


+

Examples

+

+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// start default synth

+


+

x = ().on

+


+


+

// release with 3 seconds releaseTime 

+


+

x.off(3)

+


+


+

// with EventShortcuts turned on this gives a quick way to run synths,

+

// here default synth with replacements 'n' -> 'note' and 'd' -> 'dur'.

+


+


+

EventShortcuts.makeCurrent(\default).on

+


+

(n: [0, 4, 7, 12], d: 3).on

+


+


+

// define SynthDef 

+


+

SynthDef(\x, { |out = 0, freq = 440, amp = 0.05| Out.ar(0, SinOsc.ar(freq, 0, amp) * EnvGate.new) }).add

+


+


+

// run synth, audible beats with equal temperature, pitches given as notes ('n')

+


+

(i: \x, n: [0, 4, 7], d: 3).on

+


+


+

// not with just intonation, pitches given as frequencies ('f')

+


+

(i: \x, f: (4..6) * 100, d: 3).on

+


+


+


+


+

1b: Event-related methods defined for SequenceableCollections

+


+

aSequenceableCollection.ev

+

+

Derives an Event from an array of key/value-pairs. Values might be functions that refer to 

+

prior keys of the array.

+

+

aSequenceableCollection.on

+

+

Derives an Event from an array with method ev and plays it using on.

+

+


+


+


+

Example 

+


+


+

// define an array as event template

+

// references to prior keys can be written as environmental variables in Functions

+

// here: higher midinotes get shorter durations

+


+

(

+

a = [

+

m: { rrand(60, 100) }, // midinote

+

d: { ~m.linlin(60, 90, 3, 0.1) } // dur

+

]

+

)

+


+

// suppose EventShortCuts are turned on

+

// make an Event and see its data, then play, try several times

+


+


+

x = a.ev

+


+

x.on

+


+


+


+

// same can be done in one, try several times

+


+

a.on

+


+


+


+

2: Event-pattern-related methods defined for SequenceableCollections

+


+

aSequenceableCollection.pa

+

+

Expects basically an array of key/value-pairs ready for an event pattern. 

+

Exception: values might be functions that refer to prior keys of the array, thus delivering

+

an shortcut functionality of the common Pfunc { |e|  ... } construct (and similar to Pkey).

+

+

aSequenceableCollection.p

+

+

Derives a Pbind from an array involving pa.

+

+

aSequenceableCollection.pm(sym)

+

+

Derives a Pmono with instrument sym (a Symbol) from an array involving pa.

+

+

aSequenceableCollection.pma(sym)

+

+

Derives a PmonoArtic with instrument sym (a Symbol) from an array involving pa.

+


+

aSequenceableCollection.pbf(sym)

+

+

Derives a Pbindef with reference sym (a Symbol) from an array involving pa.

+


+


+


+

aSequenceableCollection.pp(clock, protoEvent, quant)

+

+

Derives and plays a Pbind from an array involving pa. 

+

Arguments clock, protoEvent and quant are used by Pattern's method play.

+

+

aSequenceableCollection.ppm(sym, clock, protoEvent, quant)

+

+

Derives and plays a Pmono with instrument sym (a Symbol) from an array involving pa.

+

Arguments clock, protoEvent and quant are used by Pattern's method play.

+

+

aSequenceableCollection.ppma(sym, clock, protoEvent, quant)

+

+

Derives and plays a PmonoArtic with instrument sym (a Symbol) from an array involving pa.

+

Arguments clock, protoEvent and quant are used by Pattern's method play.

+


+

aSequenceableCollection.ppbf(sym, clock, protoEvent, quant)

+

+

Derives and plays a Pbindef with reference sym (a Symbol) from an array involving pa.

+

Arguments clock, protoEvent and quant are used by Pattern's method play.

+


+


+


+

Examples

+


+

// use reference convention with Functions

+

// make an array of patternpairs to be used in different event pattern types

+


+

(

+

a = [

+

m: Pwhite(60, 80, 30), // midinote

+

d: { (~m > 70).if { 2 }{ 1 }/8 } // dur

+

].pa

+

)

+


+

Pbind(*a).play

+


+

Pmono(\default, *a).play

+


+


+


+

// define Pbind and play on different channels

+


+

(

+

p = [

+

m: Pwhite(60, 80, 100),

+

d: { (~m > 70).if { 2 }{ 1 }/8 }

+

].p

+

)

+


+

Pbindf(p, \p, -1).play(quant: 1/4) // \p for pan

+


+

Pbindf(p, \p, 1).play(quant: 1/4)

+


+


+


+

// same with Pmono ...

+


+

(

+

[

+

m: Pwhite(60, 80, 30),

+

d: { (~m > 70).if { 2 }{ 1 }/8 }

+

].pm(\default)

+

)

+


+

Pbindf(p, \p, -1).play(quant: 1/4)

+


+

Pbindf(p, \p, 1).play(quant: 1/4)

+


+


+


+

// ... and PmonoArtic

+


+

(

+

p = [

+

m: Pwhite(60, 80, 100),

+

d: { (~m > 70).if { 2 }{ 1 }/8 },

+

l: Pwhite(0.1, 1.5) // legato

+

].pma(\default)

+

)

+


+

Pbindf(p, \p, -1).play(quant: 1/4)

+


+

Pbindf(p, \p, 1).play(quant: 1/4)

+


+


+


+

// analogous definition with Pbindef

+

// example different as Pbindef is pattern and player in one

+


+

(

+

p = [

+

m: Pwhite(60, 80, 100),

+

d: { (~m > 70).if { 2 }{ 1 }/8 }

+

].pbf(\x)

+

)

+


+

Pbindef(\x, \p, -1).play

+


+

Pbindef(\x, \p, 1)

+


+

Pbindef(\x).clear

+


+


+


+

// immediately play Pbind

+

// also play Pmono, PmonoArtic and Pbindef directly with methods ppm, ppma and ppbdf 

+


+

(

+

[

+

m: Pwhite(60, 80, 30),

+

d: { (~m > 70).if { 2 }{ 1 }/8 }

+

].pp

+

)

+


+


+


+


+


+


+


+ + diff --git a/Help/PHS.html b/Help/PHS.html new file mode 100755 index 0000000..b7756ad --- /dev/null +++ b/Help/PHS.html @@ -0,0 +1,222 @@ + + + + + + + + + + + +

PHS (PHelpSynth) defines Pbind(s) for using synth values of a HS

+


+

Part of: miSCellaneous

+


+

Inherits from: PHSuse (PHelpSynthUse)

+


+

Defines Pbind(s) which, when played, can use values of a synth derived from HS's synth definition.

+


+

See also: Working with HS and HSpar, HS with VarGui, HS, PHSuse, PHSplayer, PHSusePlayer

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (helpSynth, helpSynthArgs, dur1, pbindData1, ... , durN, pbindDataN)

+

+

Creates a new PHS object.

+

+

helpSynth - A HS object.

+

helpSynthArgs - Collection of key / value pairs for the HS synth.

+

dur i - Duration value or pattern / stream of durations for corresponding Pbind(s).

+

pbindData i - A collection of Pbind pairs or a collection of Pbind pair collections,

+

defining possibly several Pbinds with the same event timing. 

+


+


+

Status control

+


+

play(clock, quant)

+

+

A PHSplayer object is instantiated and started using the TempoClock clock. 

+

Quant or SimpleNumber quant lets the player step into the quantization as soon as possible, 

+

with respect to the necessary latency. The PHSplayer can be stopped and resumed with several options.

+


+


+

VarGui support

+


+

These two methods should be used when combining HS / PHS with VarGui, see HS with VarGui for examples.

+

+

+

newPaused(args, latency)

+

+

Return a new paused Synth derived from HS's ugenFunc definition, which may be passed to a VarGui object. 

+

VarGui will automatically detect that the Synth origins from a HS definition and gui functionality wil be adapted.

+

+

args - Collection of key / value pairs for the HS synth.

+

latency - SimpleNumber (seconds). 

+


+

+

asTask(clock, quant, hsStop, hsPlay, newEnvir, removeCtrWithCmdPeriod)

+

+

Returns a wrapper Task, which may be passed to a VarGui object. 

+

Playing and stopping the wrapper Task invokes playing and 

+

stopping behaviour of the underlying PHSplayer, per default also taking control over 

+

playing and stopping the help synth.

+

+

clock - TempoClock.

+

quant - Quant or SimpleNumber.

+

hsStop - Boolean. Determines if help synth will stop together with PHSplayer. Defaults to false.

+

hsPlay - Boolean. Determines if help synth will resume together with PHSplayer. Defaults to true.

+

newEnvir - Boolean. Determines if Task will be played in a newly generated environment. Defaults to true.

+

This option especially becomes important when PHS's pbindData contains functional code with 

+

environmental variables.

+

removeCtrWithCmdPeriod - Boolean. Defaults to true. 

+

Determines if notification of PHSplayer will be stopped with CmdPeriod.

+

+

+

Examples

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// define a HS with args

+


+

h = HS(s, { |freq = 0.5, dev = 10, center = 65| LFDNoise3.kr(freq, dev, center) });

+


+


+

// two Pbinds with different event timing reading from one help synth 

+

+

(

+

p = PHS(h, nil, // default help synth args

+

Prand([0.4, 0.2],inf), [ \midinote, Pkey(\val), \amp, 0.08  ],

+

0.1, [ \midinote, Pkey(\val) + 9.5 + Pxrand([0, 2, 5],inf), \amp, 0.06 ]

+

).play;

+

)

+


+

// stop player and free HS, ready to be played again with the same or another PHS

+

+

p.free;

+


+


+

// now with synth args

+

+

(

+

p = PHS(h, [\freq, 2, \dev, 20], // more oscillation

+

Prand([0.4, 0.2],inf), [ \midinote, Pkey(\val) - [0, 5, 10], \amp, 0.06 ],

+

0.1, [ \midinote, Pkey(\val) + 9.5 + Pxrand([0, 4, 7],inf), \amp, 0.06 ]

+

).play;

+

)

+


+


+

// stop player and free HS

+


+

p.free;

+


+


+


+

// let PHS define two Pbinds with same event timing, one mostly pausing

+


+

(

+

p = PHS(h, [], 0.16, 

+

[ [\midinote, Pkey(\val), \amp, 0.1], 

+

  [\midinote, Pkey(\val) + [-7, 4, 7],

+

\amp, Pwrand([0.04, 0.08], [0.8, 0.2], inf),

+

\type, Pkey(\val).collect { |x| x.asInteger.even.if { \note }{ \rest } } ] ]

+

).play;

+

)

+


+


+

// stop player and free HS

+


+

p.free;

+


+


+

// order of execution: three Pbinds defined, scheduling slightly time-shifted internally,

+

// so references can be established

+


+

// printout of variables which may be used within a PHS definition

+

// timeGrains is just a time ID, depending on granularity, not used for scheduling

+

// demandIndex indicates the index of the stream of durations

+

// posted demandIndex = 1, as two pbinds are using duration stream #0 

+


+


+

(

+

p = PHS(h, [], 

+

0.16, 

+

[ [\midinote, Pkey(\val) + Pseq([0,2], inf), \amp, 0.06], 

+

  [\midinote, Pkey(\val) + [-7, 4, 7],

+

\amp, Pwrand([0.06,0.1], [0.8,0.2], inf),

+

// "random" appearance of middle voice chords depending on synth values 

+

\type, Pkey(\val).collect { |x| ~middle = x.asInteger.even.if { \note }{ \rest } } ] ],

+

Pseq([0.32, 0.16], inf), 

+

[ [\midinote, Pkey(\val) + [15, 20, 25],

+

  \legato, 0.2,

+

  \amp, 0.025,

+

  // upper voice is playing in case of middle voice rest 

+

  \type, Pkey(\val).collect { |x| (~middle == \rest).if { \note }{ \rest } },

+

  \post, Pfunc { |e| e.use {

+

"demandIndex: ".post; ~demandIndex.postln;

+

"timeGrains: ".post; ~timeGrains.postln;

+

"dur: ".post; ~dur.postln;

+

"val: ".post; ~val.postln;

+

"================================".postln;

+

}

+

}

+

]]

+

).play;

+

)

+


+


+

// stop player and free HS

+


+

p.free;

+


+


+


+ + diff --git a/Help/PHSpar.html b/Help/PHSpar.html new file mode 100755 index 0000000..709a9ae --- /dev/null +++ b/Help/PHSpar.html @@ -0,0 +1,415 @@ + + + + + + + + + + + +

PHSpar (PHelpSynthPar) defines Pbind(s) for using synth values of a HSpar

+


+

Part of: miSCellaneous

+


+

Inherits from: PHSparUse (PHelpSynthParUse)

+


+

Defines Pbind(s) which, when played, can use values of synths derived from HSpar's synth definitions.

+


+

See also: Working with HS and HSpar, HS with VarGui, HSpar, PHSparUse, PHSparPlayer, PHSusePlayer

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (helpSynthPar, switchDur, switchIndex, helpSynthArgs, pbindArgs, hsIndices, switchOn, switchOff, set, hsStartIndices)

+

+

Creates a new PHSpar object.

+

+

helpSynthPar - A HSpar object.

+

switchDur - Duration value or pattern / stream of durations determining the times of HSpar synth switches. 

+

switchIndex - Index or pattern / stream of indices determining HSpar's synth definition to switch to. 

+

Defaults to 0.

+

helpSynthArgs - Collection of Pbind pair collections (size = number of helpSynthPar's help synths), 

+

defining synth args to be set at switch times.

+

pbindArgs - Collection of the form [ dur1, pbindData1, ... , durN, pbindDataN ] , with

+

dur i - Duration value or pattern / stream of durations for corresponding Pbind(s).

+

pbindData i - A collection of Pbind pairs or a collection of Pbind pair collections, 

+

defining possibly several Pbinds with same event timing. 

+

hsIndices - Per default values are taken from the currently switched help synth. 

+

Explicitely given hsIndices allow reference to values of other help synths from the 

+

corresponding Pbind(s). See the examples below.

+

Expects a collection of valid hsIndex values resp. patterns / streams of valid hsIndex values.

+

A valid hsIndex value is a valid help synth index or a collection of valid help synth indices.

+

The collection's size must equal N, the number of pbindArgs's event timings.

+

switchOn - Boolean or pattern / stream of booleans, determining if switched help synths should be resumed. 

+

Defaults to false.

+

switchOff - Boolean or pattern / stream of booleans, determining if help synths, which are left at a switch, should be paused. 

+

Defaults to false.

+

set - Boolean or pattern / stream of booleans, determining if next synth input values, defined in helpSynthArgs, should be 

+

taken for setting the synth. The first help synth args are always set. Defaults to true.

+

hsStartIndices - A valid help synth index, a collection of valid help synth indices or one of the symbols: \all, \none. 

+

Determines which help synths should be started at the beginning in addition to the one of the first switch index. 

+

Help synths of other than first switch index are played with default args. 

+


+


+

VarGui support

+


+

These two methods should be used when combining HSpar / PHSpar with VarGui, see HS with VarGui for examples.

+

+

+

newPaused(args, latency)

+

+

Return a collection of new paused Synth(s) derived from HSpar's ugenFunc definition(s), which may be passed to a VarGui object. 

+

VarGui will automatically detect the origin from a HSpar definition and gui functionality wil be adapted.

+

+

args - Collection of collection(s) of key / value pairs for the HSpar synth(s).

+

latency - SimpleNumber (seconds). 

+


+


+

asTask(clock, quant, hsStop, hsPlay, switchStop, newEnvir, removeCtrWithCmdPeriod)

+

+

Returns a wrapper Task, which may be passed to a VarGui object. 

+

Playing and stopping the wrapper Task invokes playing and 

+

stopping behaviour of the underlying PHSparPlayer, per default also taking control over 

+

playing and stopping the help synth(s). See HS with VarGui for examples.

+

+

clock - TempoClock.

+

quant - Quant or SimpleNumber.

+

hsStop - Boolean, Integer or SequenceableCollection of Integers determining help synth indices. 

+

Determines if help synth(s) will stop together with PHSparPlayer. Defaults to false.

+

hsPlay - Boolean, Integer or SequenceableCollection of Integers determining help synth indices.

+

Determines if help synth(s) will resume together with PHSparPlayer. Defaults to true.

+

switchStop - Boolean. Determines if switching will stop together with PHSparPlayer. Defaults to true.

+

newEnvir - Boolean. Determines if Task will be played in a newly generated environment. Defaults to true.

+

This option especially becomes important when PHSpar's pbindData contains functional code with 

+

environmental variables.

+

removeCtrWithCmdPeriod - Boolean. Defaults to true. 

+

Determines if notification of PHSparPlayer will be stopped with CmdPeriod.

+


+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// define a HSpar with two help synth definitions 

+

+

(

+

h = HSpar(s, [ { |freq = 1, dev = 5, center = 65|

+

LFDNoise3.kr(freq, dev, center) },

+

{ |freq = 1, dev = 5, center = 75, addFreq = 0.1, addDev = 5|

+

LFTri.kr(freq, 0, dev, center) + SinOsc.kr(addFreq, 0, addDev) } 

+

]);

+

)

+


+

// define a PHSpar to switch between these two

+

// reference to currently switched help synth via ~val

+

// note that pbindArgs, different from PHS, have to be given as collection

+

+

(

+

p = PHSpar(h, 

+

3, // switch duration

+

Pseq([0,1],inf), // switch indices

+

// center of help synth #0 is reset at every switch, default values for help synth #1 :

+

[ [\center, Pseq([65, 90], inf)], nil], 

+

[0.1, [\midinote, Pkey(\val) /* ~val refers to current switch */ + Pseq([0,1],inf), \legato, 0.2 ] ]

+

).play;  

+

)

+

+

// stop and free HSpar

+

+

p.free;

+


+


+


+

// no need to define a switch pattern (default switch index = 0, default switch duration = inf)

+

// hsIndices value \all causes that all help synth values at corresponding Pbind times are accesible via ~vals

+

+

(

+

p = PHSpar(h, 

+

pbindArgs: [0.1, [\midinote, Pkey(\vals) /* collection, thus played as interval */ + Pseq([0,1],inf), 

+

\legato, 0.2 ] ],

+

hsIndices: [ \all ] 

+

).play;  

+

)

+

+

p.free;

+


+


+


+

// this is a bit wasteful as always both help synth values are demanded via the OSCresponder mechanism:

+

// see printed (serverToClient) OSC traffic

+

+

(

+

h.postOSC = true;

+


+

p = PHSpar(h, 

+

pbindArgs: [0.1, [\midinote, Pkey(\vals).collect(_.at(1)) /* take only the second */ + Pseq([0,1],inf), \legato, 0.2 ] ],

+

hsIndices: [ \all ] 

+

).play;  

+

)

+

+

p.free;

+


+


+


+

// the index may be determined by hsIndices 

+

// only the needed help synth value is demanded and can be referenced via ~thisVal:

+

// compare printed OSC traffic 

+


+

(

+

p = PHSpar(h, 

+

pbindArgs: [0.1, [\midinote, Pkey(\thisVal) /* take value determined by hsIndices */ + Pseq([0,1],inf), \legato, 0.2 ] ],

+

hsIndices: [ 1 ] 

+

).play;  

+

)

+


+

(

+

p.free;

+

h.postOSC = false;

+

)

+


+


+

// hsIndices may contain patterns

+

// only the needed help synth value is demanded and can be referenced via ~thisVal (or ~theseVals) :

+


+

(

+

p = PHSpar(h, 

+

pbindArgs: [0.1, [\midinote, Pkey(\thisVal) + Pseq([0,1],inf), \legato, 0.2 ] ],

+

hsIndices: [ Prand([0,1], inf) ] 

+

).play;  

+

)

+


+

p.free;

+


+


+


+

// hsIndices also accepts collections of indices or patterns thereof, referenced by ~theseVals (which is always a collection)

+


+

(

+

p = PHSpar(h, 

+

pbindArgs: [0.1, [\midinote, Pkey(\theseVals) + Pseq([0,1],inf), \legato, 0.2 ] ],

+

hsIndices: [ Pstutter(Pwhite(5,10), Pxrand([0, 1, [0,1]], inf)) ] 

+

).play;  

+

)

+


+

p.free;

+


+


+


+

// two Pbinds with different event timing

+

// hsIndices specified for each

+


+

(

+

p = PHSpar(h, // "+" wraps collections, so if ~theseVals is an interval then ~midinote consists of the two added sevenths [-5, 5], [0, 10], 

+

// otherwise ~midinote is a chord of fourths

+

pbindArgs: [0.2, [\midinote, Pkey(\theseVals) + [-5, 0, 5,10] , \legato, 0.2, \amp, 0.05 ],

+

0.1, [\midinote, Pkey(\thisVal) + Pseq([0,1],inf), \legato, 0.2, \amp, 0.08 ]],

+

hsIndices: [ Pstutter(Pwhite(5,10), Pxrand([0, 1, [0,1]], inf)), 0]

+

).play;  

+

)

+


+

p.free;

+


+


+


+

// the switched indices can be referenced via the hsIndices arg by symbol \switch

+

// \all is synonym to [0,1] in the last example

+


+

(

+

p = PHSpar(h,

+

Pwhite(0.5, 1.5),// switch duration

+

Pseq([0,1],inf), // switch indices

+

[ [], [] ], // default values for help synths

+

// "+" wraps collections, so if ~theseVals is an interval then ~midinote consists of the two added sevenths [-5, 5], [0, 10], 

+

// otherwise ~midinote is a chord of fourths 

+

[ 0.2, [\midinote, Pkey(\theseVals) + [-5, 0, 5,10] , \legato, 0.2, \amp, 0.05 ],

+

    0.1, [\midinote, Pkey(\thisVal) + Pseq([0,1],inf), \legato, 0.2, \amp, 0.08 ] ],

+

  [ Pstutter(Pwhite(5,10), Pxrand([0, 1, \all], inf)), \switch /* single line switches between trill and arpeggio */]

+

).play;  

+

)

+


+

p.free;

+


+


+

//////////////////////////////////////////////////////////////////////////////////

+


+


+

// HSpar with two help synth definitions

+


+

(

+

h = HSpar(s, [ 

+

{ |start = 90, end = 60, dur = 10, add = 0|

+

XLine.kr(start, end, dur) + add; },

+

{ |freq = 0.3, dev = 15, center = 60, phase = 0, add = 0|

+

LFTri.kr(freq, phase, dev, center + add); }

+

]);

+

)

+


+


+

// switch between two help synths

+

// both help synths are run from the start (default hsStartIndices = true)

+

// help synths of switch index are paused when index is left (switchOff: true) and 

+

// resumed when index is given (switchOn: true)

+

// movement downwards needs more than 10 seconds defined by XLine

+


+

(

+

p = PHSpar(h, 

+

Pwhite(0.3,1.0), // switch duration

+

Pseq([0,1], inf), // switch indices,

+

// distinguish help synth values by reference to ~switchIndex

+

pbindArgs: [0.12, [\midinote, Pkey(\val) + Pkey(\switchIndex).collect(switch(_, 0, [0,2.5], 1, [0,7])), \legato, 0.2 ] ],

+

switchOn: true,

+

switchOff: true

+

).play;  

+

)

+


+

p.free;

+


+


+


+

// never pause help synth #0, always pause #1

+

// end value of help synth #0 reached after 10 seconds

+


+

(

+

p = PHSpar(h, 

+

Pwhite(0.3,1.0), // switch duration

+

Pseq([0,1], inf), // switch indices,

+

// distinguish help synth values by reference to ~switchIndex

+

pbindArgs: [0.12, [\midinote, Pkey(\val) + Pkey(\switchIndex).collect(switch(_, 0, [0,2.5], 1, [0,7])), \legato, 0.2 ] ],

+

switchOn: true,

+

switchOff: Pseq([false, true], inf) // next pause value with next switch index

+

).play;  

+

)

+


+

p.free;

+


+


+


+

// hsStartIndices: define which help synths should be run from the beginning (only #1)

+

// help synth #0 is run when switched first (switchOn: true) 

+

// movement downwards starts after 5 seconds

+


+

(

+

p = PHSpar(h, 

+

Pseq([5, Pwhite(0.3,1.0, inf)]), // switch duration

+

Pseq([1,0], inf), // switch indices,

+

// distinguish help synth values by reference to ~switchIndex

+

pbindArgs: [0.12, [\midinote, Pkey(\val) + Pkey(\switchIndex).collect(switch(_, 0, [0,2.5], 1, [0,7])), \legato, 0.2 ] ],

+

switchOn: true,

+

hsStartIndices: 1 // \none (except the switched) or [1] would also do

+

).play;  

+

)

+


+

p.free;

+


+


+


+

// set: determines whether new values should be polled from the streams (defined in helpSynthArgs) at switch time

+

// here only at every second switch a new add value (register change) is set

+


+

(

+

p = PHSpar(h, 

+

Pwhite(0.3, 0.5, inf), // switch duration

+

Pseq([0,1], inf), // switch indices,

+

[ [\add, Pseq([0, 10.5, 21], inf) ], [\add, Pseq([0, 10.5, 21], inf), \dev, 5] ],  // helpSynthArgs

+

// distinguish help synth values by reference to ~switchIndex

+

pbindArgs: [0.12, [\midinote, Pkey(\val) + Pkey(\switchIndex).collect(switch(_, 0, [0,2.5], 1, [0,7])), \legato, 0.2 ] ],

+

set: Pstutter(2, Pseq([true, false], inf))  // next set value with next switch index

+

).play;  

+

)

+


+

p.free;

+


+


+


+

// now things quite mixed up

+


+

// basic pitches chosen by index sequence defined in hsIndices, reference by Pkey(\theseVals)

+

// switchIndex only determines the added interval

+

// if theseVals contains only one value the determined interval is based on this value ([a] + [x, y] = [a + x, a + y]), 

+

// otherwise it is just added to the interval of theseVals ([a, b] + [x, y] = [a + x, b + y])  

+


+

// printout of variables which may be used within a PHSpar definition

+

// timeGrains is just a time ID, depending on granularity, not used for scheduling

+


+

(

+

p = PHSpar(h, 

+

Pwhite(0.3,1.0), // switch duration

+

Pseq([0,1], inf), // switch indices,

+

// distinguish added interval by reference to ~switchIndex

+

pbindArgs: [0.12, [

+

\midinote, Pkey(\theseVals) + Pkey(\switchIndex).collect(switch(_, 0, [0, 2.5], 1, [0, 7])) , 

+

\legato, 0.2,

+

\post, Pfunc {|e| e.use {

+

"demandIndex: ".post; ~demandIndex.postln;

+

"timeGrains: ".post; ~timeGrains.postln;

+

"dur: ".post; ~dur.postln;

+

"switchIndex: ".post; ~switchIndex.postln;

+

"vals: ".post; ~vals.postln;

+

"val: ".post; ~val.postln;

+

"theseIndices: ".post; ~theseIndices.postln;

+

"theseVals: ".post; ~theseVals.postln;

+

"thisVal: ".post; ~thisVal.postln;

+

"================================".postln;

+

}

+

}

+

]],

+

hsIndices: [ Pstutter(Pwhite(5,10), Pxrand([ 0, 1, \all ], inf)) ]

+

).play;  

+

)

+


+

p.free;

+


+


+ + diff --git a/Help/PHSparPlayer.html b/Help/PHSparPlayer.html new file mode 100755 index 0000000..f91123c --- /dev/null +++ b/Help/PHSparPlayer.html @@ -0,0 +1,328 @@ + + + + + + + + + + + +

PHSparPlayer (PHelpSynthParPlayer) PHSpar player object

+


+

Part of: miSCellaneous

+


+

Inherits from: PHSusePlayer (PHelpSynthUsePlayer)

+


+

Implicitely instantiated when PHSpar's play method is called, allows stopping and resuming with options also concerning the help synth.

+


+


+

See also: Working with HS and HSpar, HS with VarGui, HSpar, PHSpar, PHSparUse, PHSusePlayer

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (pHelpSynthPar)

+

+

Creates a new PHSparPlayer object.

+

+

pHelpSynthPar - A PHSpar object.

+


+


+


+

Status control

+


+

play(clock, quant, hsPlay, switchPlay, pbindPlay, quantBufferTime)

+

+

clock - A TempoClock object. If not assigned, takes the default TempoClock.

+

quant - Quant or SimpleNumber. Makes the player start at the next grid that gives enough time for latency. 

+

hsPlay - Boolean, Integer or SequenceableCollection of Integers determining help synth indices. 

+

Determines if help synth(s) should play. Defaults to true.

+

switchPlay - Boolean. Determines if switch pattern should play. Defaults to true.

+

pbindPlay - Boolean. Determines if Pbind(s) should play. Defaults to true.

+

quantBufferTime - SimpleNumber (seconds). Calculated time to include latency for "stepping in" 

+

is lengthened by this value. Defaults to 0.2.

+


+


+

stop(hsStop, switchStop, pbindStop, addAction)

+

+

hsStop - Boolean, Integer or SequenceableCollection of Integers determining help synth indices. 

+

Determines if help synth(s) should pause. Defaults to false.

+

switchStop - Boolean. Determines if switch pattern player should pause. Defaults to false.

+

pbindStop - Boolean. Determines if Pbind player(s) should pause. Defaults to true.

+

addAction - Function to be evaluated at receive time.

+


+


+

pause(hsStop, switchStop, pbindStop, addAction)

+

+

= stop

+


+


+

free

+

+

Stop the PHSparPlayer and all PHSusePlayers that are using the same HSpar, also free the HSpar.

+


+


+

Note: stop (= pause) allows resuming the player - free resets, player can be started again. 

+


+


+

Examples

+


+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// define PHSpar and several users 

+


+

(

+

h = HSpar(s, [ 

+

{ |freq = 2, dev = 7, center = 60|

+

LFDNoise3.kr(freq, dev, center) }, 

+

{ |freq = 5, dev = 10, center = 80|

+

LFDNoise3.kr(freq, dev, center) } 

+

]);

+


+

~p1 = PHSpar(h, 

+

pbindArgs: [0.1, [\midinote, Pkey(\thisVal), \legato, 0.2, \amp, 0.06 ] ],

+

hsIndices: [0] );

+


+

~p2 = PHSparUse(h, 

+

pbindArgs: [0.2, [\midinote, Pkey(\thisVal) - 3,\legato, 0.2, \amp, 0.06  ]],

+

hsIndices: [1] );          

+


+

~p3 = PHSparUse(h, 

+

pbindArgs: [Pn(Pshuf([0.2, 0.1, 0.1], 1), inf), [\midinote, Pkey(\theseVals) + 5, \legato, 0.2, \amp, 0.06 ]],

+

hsIndices: [[0, 1]] );          

+


+

~p4 = PHSparUse(h, 

+

pbindArgs: [Pn(Pshuf([0.2, 0.1, 0.1], 1), inf), [\midinote, Pkey(\theseVals) + 8, \legato, 0.2, \amp, 0.06 ]],

+

hsIndices: [[0, 1]] );          

+


+

c = TempoClock.new;

+

q = 0.8;

+

)

+


+


+

// start PHSparPlayer with help synth #0 

+


+

~x1 = ~p1.play(c,q);

+


+


+

// add a PHSusePlayer using help synth #1

+


+

~x2 = ~p2.play(c,q);

+


+


+

// add a PHSusePlayer using both help synths

+


+

~x3 = ~p3.play(c,q);

+


+


+

// and another one

+


+

~x4 = ~p4.play(c,q);

+


+


+

// stop PHSparPlayer, help synths keep playing

+


+

~x1.stop;

+


+


+

// stop a PHSusePlayer

+


+

~x2.stop;

+


+


+

// and another one, only player ~x4 left

+


+

~x3.stop;

+


+


+

// resume ~x1

+


+

~x1.play(c,q);

+


+


+

// stop only help synth #1

+


+

~x1.stop(hsStop: 1, pbindStop: false);

+


+


+

// stop also help synth #0 and player ~x1

+


+

~x1.stop(hsStop: 0);

+


+


+

// now only one pbind engaged via PHSusePlayer ~x4, but help synths may be controlled via PHSparPlayer ~x1 !  

+

// resume both help synths

+


+


+

~x1.play(hsPlay: [0,1], pbindPlay: false);

+


+


+

// stop them again, using keyword

+


+

~x1.stop(hsStop: \all, pbindStop: false);

+


+


+

// stop last remaining PHSusePlayer via the "leading" PHSparPlayer and free HSpar

+


+

~x1.free;

+


+


+


+


+


+

//////////////////////////////////////////////////// 

+


+


+

(

+

// define HSpar: two sine waves with opposite phases

+


+

h = HSpar(s, [ 

+

{ |freq = 0.25, dev = 5, center = 70|

+

SinOsc.kr(freq, 3pi/2, dev, center) }, 

+

{ |freq = 0.25, dev = 5, center = 70|

+

SinOsc.kr(freq, pi/2, dev, center) }

+

]);

+


+

// one stream shared by both switch indices

+

// goal: repetition of sine wave segment, alternate register, small random add for pitch

+


+

~pitchBaseStream = (Pseq([60,80],inf) + Pwhite(0.0,5.0)).asStream;

+


+

p = PHSpar(h, 2, // switchDur = half phase of sine wave

+

Pseq([0,1], inf), // switchIndex

+

[\center, ~pitchBaseStream] ! 2,

+

[0.25, [\midinote, Pkey(\val) + [-5.25, 0, 4, 7.25] , \legato, 0.2, \amp, 0.06 ]]);

+


+

)

+


+


+

// stopping and resuming with options

+

// if stopped together, help synths, pbinds and switchIndex player are kept in sync for resuming

+

// therefore methods stop and play are blocking the player for some moments, but freeing is always possible

+


+

// switchStop = true is blocking for the whole current switch duration - this could be reduced

+


+

x = p.play;

+


+


+

// stop everything, keep sync: try stopping and resuming several times, leave some moments between

+


+

x.stop(switchStop: true, hsStop: true, pbindStop: true);

+


+

x.play;

+


+


+

// free everything

+


+

x.free;

+


+


+


+

////////////////// 

+


+

x = p.play;

+


+


+

// stop only the pbind 

+

// try stopping and resuming several times - always ascending segments of the sine wave, pbind is "stepping in"

+

// as points of stepping in may differ, low slope in ascending sequences may occur at the beginning or at the end 

+


+

x.stop;  // equivalent to x.stop(switchStop: false, hsStop: false, pbindStop: true)

+


+

x.play;

+


+


+

//

+


+

x.free;

+


+


+


+

////////////////// 

+


+

x = p.play;

+


+


+

// stop everything BUT the pbind 

+

// try stopping and resuming several times - again always ascending segments of the sine wave as

+

// switchIndex player and help synths are stopped and started together

+


+

x.stop(switchStop: true, hsStop: true, pbindStop: false);

+


+

x.play;

+


+


+

//

+


+

x.free;

+


+


+


+

////////////////// 

+


+

x = p.play;

+


+


+

// let only help synths run

+

// try stopping and resuming several times

+

// switchIndex player and help synths are relinked, other sine wave segments establish

+


+

x.stop(switchStop: true, hsStop: false, pbindStop: true);

+


+

x.play;

+


+


+

//

+


+

x.free;

+


+


+ + diff --git a/Help/PHSparUse.html b/Help/PHSparUse.html new file mode 100644 index 0000000..6fe09c7 --- /dev/null +++ b/Help/PHSparUse.html @@ -0,0 +1,222 @@ + + + + + + + + + + + +

PHSparUse (PHelpSynthParUse) defines Pbind(s) for using synth values of a HSpar

+


+

Part of: miSCellaneous

+


+

Inherits from: PHSuse (PHelpSynthUse)

+


+

Defines Pbind(s) which, when played, can use values of synths derived from HSpar's synth definitions. Playing a PHSparUse is just an option for using an already playing help synth of a HSpar, which requires PHSpar first. See Working with HS and HSpar, paragraph "Working Scheme". 

+


+

See also: Working with HS and HSpar, HS with VarGui, HSpar, PHSpar, PHSparPlayer, PHSusePlayer

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (helpSynth, pbindArgs, hsIndices)

+

+

Creates a new PHSparUse object.

+

+

helpSynth - A HSpar object.

+

pbindArgs - Collection of the form [ dur1, pbindData1, ... , durN, pbindDataN ] , with

+

dur i - Duration value or pattern / stream of durations for corresponding Pbind(s).

+

pbindData i - A collection of Pbind pairs or a collection of Pbind pair collections,

+

defining possibly several Pbinds with same event timing. 

+

hsIndices - Per default values are taken from the currently switched help synth.

+

Explicitely given hsIndices allow reference to values of other help synths from the corresponding Pbind(s). 

+

Expects a collection of valid hsIndex values resp. patterns / streams of valid hsIndex values.

+

A valid hsIndex value is a valid help synth index or a collection of valid help synth indices.

+


+


+

Status control

+


+

play(clock, quant)

+

+

A PHSusePlayer object is instantiated and started using the TempoClock clock. 

+

Quant or SimpleNumber quant lets the player step into the quantization as soon as possible, 

+

with respect to the necessary latency. The PHSusePlayer can be stopped and resumed with options.

+


+


+

Examples

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// HSpar with two synth definitions

+


+

(

+

h = HSpar(s, [ { |freq = 1, dev = 10, center = 80|

+

LFDNoise3.kr(freq, dev, center) },

+

{ |freq = 0.5, dev = 5, center = 60, addFreq = 0.1, addDev = 5|

+

LFDNoise3.kr(freq, dev, center) + SinOsc.kr(addFreq, 0, addDev) } 

+

]);

+

+

c = TempoClock(1);

+

q = 0.12;

+

)

+


+


+

// Play Pbind (via PHSpar) to poll values from one or both synths, according to the pattern given to hsIndices. 

+

// Random add with adverb "+.x" adds an interval also to each of two simultaneous synth values.

+


+

(

+

x = PHSpar(h, 

+

pbindArgs: [0.12, [\midinote, Pkey(\theseVals).collect(_ +.x [7, 8, [0,7], [-1,8]].choose), 

+

\legato, 0.2, \amp, 0.05 ]],

+

hsIndices: [ Prand([0,1,\all], inf)] 

+

).play(c,q);

+

)

+


+


+

// "stepping in" with second player at (with respect to latency) next possible time on grid

+


+

(

+

y = PHSparUse(h, 

+

pbindArgs: [ Prand([0.36, 0.48],inf),  

+

[\midinote, Pkey(\theseVals) + [1, 1.75, 2.5, 3.25, 4, 4.75, 5.5, 6.25], 

+

\legato, 0.2, \amp, Prand([0.03, 0.05, 0.07], inf) ]],

+

hsIndices:  [ Pn(Pshuf([0,0,1,1],1), inf) ]

+

).play(c,q);

+

)

+


+


+

// stop x, help synths still playing, producing values for y

+


+

x.stop;

+


+


+

// resume x 

+


+

x.play(c,q);

+


+


+

// freeing the PHSplayer also stops all "related" PHSusePlayers and frees the HS

+


+

x.free;

+


+


+


+

/////////////////////////////////////////////////////////////////////////////////

+


+

// Instead of using PHSuse / PHSparUse objects in order to have seperate players  

+

// it's, of course, also possible to define several HS / HSpar objects independently.

+

// As before players can be synced by Quants.

+


+

(

+

h = HSpar(s, [ 

+

{ |freq = 0.5, dev = 5, center = 70| 

+

LFDNoise3.kr(freq, dev, center) },

+

{ |freq = 0.2, dev = 5, center = 62, addFreq = 0.1, addDev = 2|

+

LFTri.kr(freq, 0, dev, center) + SinOsc.kr(addFreq, 0, addDev) } 

+

]);

+

c = TempoClock.new;

+

q = 0.24;

+

)

+


+


+

// first player using a HSpar

+


+

(

+

x = PHSpar(h, 

+

pbindArgs: [

+

Pstutter(Pwhite(5,10), Pseq([0.12, 0.16], inf)),

+

[\midinote, Pkey(\thisVal) + Pseq([[2, 5], [1, 6], [0, 7]], inf), \legato, 0.2, \amp, 0.07], 

+

0.24, [\midinote, Pkey(\thisVal) + [-6, -1, 3], \legato, 0.2, \amp, 0.07]],

+

hsIndices: [0, 1]

+

).play(c,q);  

+

)

+


+


+

// define an additional HS 

+


+

(

+

k = HS(s, {|start = 90, end = 50, dur = 10| XLine.kr(start, end, dur); });

+

)

+


+


+

// start synced player 

+


+

(

+

r = PHS(k, 

+

[\start, { rrand(85, 100) }], 

+

0.12, [\midinote, Pkey(\val) + [0, 3.5], \legato, 0.2, \amp, 0.07]

+

);

+

y = r.play(c,q);  

+

)

+


+


+

// stop and free (reset) second player and HS  

+


+

y.free;

+


+


+

// play again - a new help synth is started

+


+

y.play(c,q);

+


+


+

// stop and free first player and HSpar 

+


+

x.free;

+


+


+

// stop and free second one and HS 

+


+

y.free;

+


+ + diff --git a/Help/PHSplayer.html b/Help/PHSplayer.html new file mode 100755 index 0000000..0955beb --- /dev/null +++ b/Help/PHSplayer.html @@ -0,0 +1,145 @@ + + + + + + + + + + + +

PHSplayer (PHelpSynthPlayer) PHS player object

+


+

Part of: miSCellaneous

+


+

Inherits from: PHSusePlayer (PHelpSynthUsePlayer)

+


+

Implicitely instantiated when PHS's play method is called, allows stopping and resuming with options also concerning the help synth.

+


+


+

See also: Working with HS and HSpar, HS with VarGui, HS, PHS, PHSuse, PHSusePlayer

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (pHelpSynth)

+

+

Creates a new PHSplayer object.

+

+

pHelpSynth - A PHS object.

+


+


+


+

Status control

+


+

play(clock, quant, hsPlay, pbindPlay, quantBufferTime)

+

+

clock - A TempoClock object. If not assigned, takes the default TempoClock.

+

quant - Quant or SimpleNumber. Makes the player start at the next grid that gives enough time for latency. 

+

hsPlay - Boolean. Determines if help synth should also start. Defaults to true.

+

pbindPlay - Boolean. Determines if Pbind(s) should play. Defaults to true.

+

quantBufferTime - SimpleNumber (seconds). Calculated time to include latency for "stepping in" 

+

is lengthened by this value. Defaults to 0.2.

+


+


+

stop(hsStop, pbindStop, addAction)

+

+

hsStop - Boolean. Determines if help synth should stop. Defaults to false.

+

pbindStop - Boolean. Determines if Pbind player(s) should stop. Defaults to true.

+

addAction - Function to be evaluated at receive time.

+


+


+

pause(hsStop, pbindStop, addAction)

+

+

= stop

+


+


+

free

+

+

Stop the PHSplayer and all PHSusePlayers that are using the same HS, also free the HS.

+


+


+

Note: stop (= pause) allows resuming the player - free resets, player can be started again. 

+


+


+

Examples

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// define HS, PHS - play

+


+

(

+

h = HS(s, { |freq = 0.5, dev = 10, center = 65| LFDNoise3.kr(freq, dev, center) });

+


+

p = PHS(h, nil, // default help synth args

+

Prand([0.4, 0.2],inf), [ \midinote, Pkey(\val), \amp, 0.1  ],

+

0.1, [ \midinote, Pkey(\val) + 9.5 + Pxrand([0, 2, 5],inf), \amp, 0.08 ]

+

).play;

+

)

+


+


+

// stop only help synth

+


+

p.stop(hsStop: true, pbindStop: false);

+


+


+

// stop pbind

+


+

p.stop;

+


+


+

// resume help synth and eventstream player

+


+

p.play(hsPlay: true, pbindPlay: true);

+


+


+

// stop player and free HS

+


+

p.free;

+


+


+ + diff --git a/Help/PHSuse.html b/Help/PHSuse.html new file mode 100755 index 0000000..e89b8b1 --- /dev/null +++ b/Help/PHSuse.html @@ -0,0 +1,169 @@ + + + + + + + + + + + +

PHSuse (PHelpSynthUse) defines Pbind(s) for using synth values of a HS

+


+

Part of: miSCellaneous

+


+

Inherits from: Object

+


+

Defines Pbind(s) which, when played, can use values of a synth derived from HS's synth definition. Playing a PHSuse is just an option for using an already playing help synth of a HS, which requires PHS first. See Working with HS and HSpar, paragraph "Working Scheme". 

+


+

See also: Working with HS and HSpar, HS with VarGui, HS, PHS, PHSplayer, PHSusePlayer

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (helpSynth, dur1, pbindData1, ... , durN, pbindDataN)

+

+

Creates a new PHSuse object.

+

+

helpSynth - A HS object.

+

dur i - Duration value or pattern / stream of durations for corresponding Pbind(s).

+

pbindData i - A collection of Pbind pairs or a collection of Pbind pair collections,

+

defining possibly several Pbinds with same event timing. 

+


+


+

Status control

+


+

play(clock, quant)

+

+

A PHSusePlayer object is instantiated and started using the TempoClock clock. 

+

Quant or SimpleNumber quant lets the player step into the quantization as soon as possible, 

+

with respect to the necessary latency. The PHSusePlayer can be stopped and resumed with options.

+


+


+

VarGui support

+


+

asTask(clock, quant, newEnvir, removeCtrWithCmdPeriod)

+

+

Returns a wrapper Task, which may be passed to a VarGui object. 

+

Playing and stopping the wrapper Task invokes playing and 

+

stopping behaviour of the underlying PHSusePlayer. See HS with VarGui for examples.

+

+

clock - TempoClock.

+

quant - Quant or SimpleNumber.

+

newEnvir - Boolean. Determines if Task will be played in a newly generated environment. Defaults to true.

+

This option especially becomes important when PHSuse's pbindData contains functional code with 

+

environmental variables.

+

removeCtrWithCmdPeriod - Boolean. Defaults to true. 

+

Determines if notification of PHSusePlayer will be stopped with CmdPeriod.

+


+


+

Examples

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// two Pbinds getting values for pitches from a single HS

+


+

h = HS(s, { |freq = 0.5, dev = 10, center = 65| LFDNoise3.kr(freq, dev, center) });

+


+


+

// define Pbinds via PHS and PHSuse separately, 

+

// choose quant that allows synchronization 

+


+

(

+

u = PHS(h, nil, // default help synth args

+

0.15, [ \midinote, Pkey(\val) + [-5, 0], \amp, 0.065 ]);

+


+

v = PHSuse(h, 0.15, 

+

[ \midinote, 130 - Pkey(\val) + [0, 5] /* mirror at center frequency */,

+

  \amp, Pwrand([0.03, 0.07], [0.7, 0.3], inf) ]);

+

+

c = TempoClock(1);

+

q = 0.3;

+

)

+


+


+

// start first player

+


+

x = u.play(c,q);

+


+


+

// "stepping in" with second player at (with respect to latency) next possible time on grid

+


+

y = v.play(c,q);

+


+


+

// stop x, help synth still playing, producing values for y

+


+

x.stop;

+


+


+

// define and play another PHSuse

+


+

(

+

w = PHSuse(h, 0.15, 

+

[ \midinote, 130 - Pkey(\val) + [0, 5] + Pwhite(7.0, 10.0) /* mirror at center frequency + random add */,

+

  \amp, Pwrand([0.03, 0.07], [0.7, 0.3], inf) ]);

+


+

z = w.play(c,q);

+

)

+


+


+

// resume x 

+


+

x.play(c,q);

+


+


+

// freeing the PHSplayer also stops all "related" PHSusePlayers and frees the HS

+


+

x.free;

+


+


+


+


+ + diff --git a/Help/PHSusePlayer.html b/Help/PHSusePlayer.html new file mode 100755 index 0000000..b2501d3 --- /dev/null +++ b/Help/PHSusePlayer.html @@ -0,0 +1,156 @@ + + + + + + + + + + + +

PHSusePlayer (PHelpSynthUsePlayer) player object for PHSuse and PHSparUse 

+


+

Part of: miSCellaneous

+


+

Inherits from: Object

+


+

Implicitely instantiated when PHSuse's or PHSparUse's play method is called.

+


+


+

See also: Working with HS and HSpar, HS with VarGui, PHSuse, PHSparUse

+


+


+

Some Important Issues

+


+

See Working with HS and HSpar

+


+


+

Creation / Class Methods

+


+

*new (pHelpSynthUse)

+

+

Creates a new PHSusePlayer object.

+

+

pHelpSynthUse - A PHSuse or PHSparUse object.

+


+


+


+

Status control

+


+

play(clock, quant, quantBufferTime)

+

+

clock - A TempoClock object. If not assigned, takes the default TempoClock.

+

quant - Quant or SimpleNumber. Makes the player start at the next grid that gives enough time for latency. 

+

quantBufferTime - SimpleNumber (seconds). Calculated time to include latency for "stepping in" 

+

is lengthened by this value. Defaults to 0.2.

+


+


+

stop(addAction)

+

+

addAction - Function to be evaluated at receive time.

+


+


+

pause(addAction)

+

+

= stop

+


+


+

free

+

+

Only free this PHSusePlayer - the PHSplayer / PHSparPlayer, which is using the same HS / HSpar, is not affected.

+


+


+

Note: stop (= pause) allows resuming the player - free resets, player can be started again. 

+


+


+


+

Examples

+


+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// define HS, PHS, PHSuse

+


+

(

+

h = HS(s, { |freq = 0.5, dev = 10, center = 65| LFDNoise3.kr(freq, dev, center) });

+


+

u = PHS(h, nil, // default help synth args

+

// two pbinds with different timing

+

Prand([0.4, 0.2],inf) , [ \midinote, Pkey(\val) + 4, \amp, 0.07  ],

+

0.1, [ \midinote, Pkey(\val) + 15 + Pxrand([0, 2, 5],inf), \amp, 0.04, \legato, 0.5 ]

+

);

+

+

v = PHSuse(h, // two pbinds with different timing

+

Prand([0.4, 0.2],inf) , [ \midinote, Pkey(\val), \amp, 0.07  ],

+

0.1, [ \midinote, Pkey(\val) + 6 + Pxrand([0, 2, 5],inf), \amp, 0.06, \legato, 0.5  ]

+

);

+

+

c = TempoClock(1);

+

q = 0.2;

+

)

+


+


+

// play PHS

+


+

x = u.play(c,q);

+


+


+

// play PHSuse

+


+

y = v.play(c,q);

+


+


+

// stop PHSplayer

+


+

x.stop;

+


+


+

// PHSusePlayer doesn't control HS - HS synth still running, see server window

+


+

y.free;

+


+


+

// PHSplayer controls HS - this also stops HS synth   

+


+

x.free;

+


+


+ + diff --git a/Help/PIdev.html b/Help/PIdev.html new file mode 100755 index 0000000..a9a7d54 --- /dev/null +++ b/Help/PIdev.html @@ -0,0 +1,342 @@ + + + + + + + + + + + +

PIdev pattern searching for numbers with integer distance from a source pattern, optionally avoiding repetitions within a span

+


+

Part of: miSCellaneous

+


+

Inherits from: Pattern

+


+

DIdev / PIdef / PLIdev search for numbers with integer distance from a source signal / pattern up to a given deviation. Repetitions within a lookback span are avoided, DIdev / PIdef / PLIdev randomly choose from possible solutions. Intended for search within integer grids (pitches, indices etc.), however applications with non-integer sources are possible, see examples.

+

 

+

NOTE: It's the user's responsibility to pass a combination of deviation and lookback values that allows a possible choice, see examples.

+


+

NOTE: In contrast to DIdev, PIdev and PLIdev do *not* need to know maximum deviations (minLoDev, maxHiDev) beforehand. Thus the order of arguments is different (here loDev and hiDev before lookBack).

+


+

See also: Idev suite, PLIdev, DIdev

+


+


+

Creation / Class Methods

+


+

Creates a new PIdev object.

+

+

*new (pattern, maxLookBack = 3, loDev = -3, hiDev = 3, lookBack, thr = 1e-3, length = inf)

+

+

pattern - The source value pattern to start search from.

+

maxLookBack - Integer, the maximum lookback span. Fixed, defaults to 3.

+

loDev - Determines the current low deviation for the search. Defaults to -3.

+

hiDev - Determines the current high deviation for the search. Defaults to 3.

+

lookBack - Determines the current lookback span for avoiding repetitions.

+

Can be modulated but must not exceed maxLookBack.

+

If no value is passed, then maxLookBack is taken.

+

thr - Threshold for equality comparison. Can be modulated, defaults to 1e-3. 

+

length - Number of repeats. Defaults to inf. 

+

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

Ex.1) Basic usage: random choice within region without repetitions

+

+

// constant source (72), max deviation +/- 3

+

// no repetition within 5 pitches

+


+

(

+

p = Pbind(

+

\dur, 0.2,

+

\midinote, PIdev(72, 5, -3, 3).trace(prefix: "midi "),

+

).play;

+

)

+


+

p.stop

+


+


+


+

Ex.2) Variable deviations and lookBack

+


+

(

+

~loDev = -6;

+

~hiDev = 5;

+

~lookBack = 2;

+


+

p = Pbind(

+

\dur, 0.2,

+

\midinote, PIdev(72, 11, Pfunc { ~loDev }, Pfunc { ~hiDev }, Pfunc { ~lookBack }).trace(prefix: "midi "),

+

).play;

+

)

+


+

// change on the fly

+

// as lookBack equals 2, this defines a fixed sequence (up or down anyway)

+


+

(

+

~loDev = -1;

+

~hiDev = 1;

+

)

+


+

// widen range

+


+

(

+

~loDev = -6;

+

~hiDev = 5;

+

)

+


+

// force a twelve-tone row

+


+

~lookBack = 11;

+


+


+

// contradictory input, lookBack 11 not possible within range, causes repetitions

+


+

(

+

~loDev = -3;

+

~hiDev = 2;

+

)

+


+

p.stop

+


+


+


+

Ex.3) Moving source signal

+


+

(

+

~loDev = -1;

+

~hiDev = 1;

+

~lookBack = 2;

+


+

p = Pbind(

+

\dur, 1/7,

+

\midinote, PIdev(

+

Pseg(Pseq([65, 90], inf), 5, \sin).round,

+

11,

+

Pfunc { ~loDev },

+

Pfunc { ~hiDev },

+

Pfunc { ~lookBack }

+

).trace(prefix: "midi "),

+

).play;

+

)

+


+

// widen range and increase lookBack

+


+

(

+

~loDev = -6;

+

~hiDev = 5;

+

~lookBack = 10;

+

)

+


+

p.stop;

+


+


+

Ex.4) Dynamic deviation range and lookBack

+


+

// lookBack and deviations coupled here

+

// maxLookBack must be large enough

+


+

(

+

~loDev = -1;

+

~hiDev = 1;

+

~lookBack = 2;

+


+


+

p = Pbind(

+

    \dur, 1/7,

+

    \midinote, PIdev(

+

        78,

+

        10,

+

        Pn(Plazy { ~loDev }),

+

        Pn(Plazy { ~hiDev }).trace(prefix: "absolute deviation "),

+

        Pn(Plazy { ~lookBack })

+

    ).trace(prefix: "midi ");

+

).play

+

)

+


+

// start parameter movement on the fly

+


+

(

+

~loDev = Pseg(Pseq([2, 5].neg, inf), 5, \sin);

+

~hiDev = Pseg(Pseq([2, 5], inf), 5, \sin).trace(prefix: "absolute deviation and lookBack ");

+

~lookBack = Pseg(Pseq([2, 5], inf), 5, \sin);

+

)

+


+

p.stop

+


+


+


+

Ex.5) Non-integer source

+


+

(

+

~loDev = -6;

+

~hiDev = 5;

+

~lookBack = 3;

+

~thr = 1;

+


+

p = Pbind(

+

\dur, 1/7,

+

\midinote, PIdev(

+

Pseg(Pseq([65, 90], inf), 5, \sin),

+

5,

+

Pfunc { ~loDev },

+

Pfunc { ~hiDev },

+

Pfunc { ~lookBack },

+

Pfunc { ~thr }

+

).trace(prefix: "midi "),

+

).play;

+

)

+


+

// close floats can occur here

+

~thr = 0.01

+


+

// not here

+

~thr = 2

+


+


+

p.stop

+


+


+
Ex.6) Multichannel expansion

+

 

+

// larger pitch range and lookBack in upper voice, 

+

// as with most patterns, no automatic array expansion of pattern arguments

+


+

(

+

p = Pbind(

+

\dur, 1/7,

+

\src, Pseg(Pseq([65, 85], inf), 5, \sin).round,

+

\midinote, Ptuple([

+

PIdev(Pkey(\src), 1, -1, 1),

+

PIdev(Pkey(\src) + 8.5, 3, -5, 5)

+

]).trace(prefix: "midi "),

+

).play;

+

)

+


+

p.stop

+


+


+

Ex.7) Application to other params: rhythm

+

 

+

// if we have indexed data of whatever, we can slide over it,

+

// groups of durations as items to be streamed by PIdev

+


+

(

+

~rhythmBase = [

+

[1, 1],

+

[2, 1, 1],

+

[1, 1, 2]

+

].collect(_.normalizeSum);

+


+

~rhythms = ~rhythmBase *.x [1, 2];

+

~rhythmNum = ~rhythms.size;

+

~rhythms = ~rhythms.scramble;

+


+

SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, 

+

rq = 0.05, pan = 0, amp = 0.1|

+

var sig = { WhiteNoise.ar } ! 2;

+

sig = BPF.ar(sig, freq, rq) *

+

EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

+

(rq ** -1) * (250 / (freq ** 0.8));

+

OffsetOut.ar(out, Pan2.ar(sig, pan));

+

}).add;

+

)

+


+

(

+

// rhythmic variation and partial pitch repetition

+

// play for a while to note slow sliding caused by Pseg

+


+

~loDev = -1;

+

~hiDev = 1;

+


+

p = Pbind(

+

\instrument, \noise_grain,

+

\rel, Pexprand(0.05, 0.15),

+

\dur, PIdev(

+

// be careful not to exceed index bounds

+

Pseg(Pseq([~loDev.abs, ~rhythmNum - ~hiDev - 1], inf), 10, \sin, inf).round, 

+

2, // lookBack span, no repetition within 3 items

+

~loDev, 

+

~hiDev

+

).trace(prefix: "rhythm type: ").collect(~rhythms[_]).flatten * 0.3,

+

\midinote, Pstutter(Pwhite(1, 2), Pclump(3, Pxrand((50..100), inf))).flatten + [0, 12],

+

\pan, Pstutter(Pwhite(1, 2), Pclump(3, Pwhite(-1.0, 1))).flatten

+

).play;

+

)

+


+

p.stop

+


+


+


+

Ex.8) Proof of concept

+


+

(

+

// Function to check an array for repetitions within a maximum test span

+


+

~repetitionCheck = { |array, maxTestSpan|

+

maxTestSpan.do { |i|

+

var result = (array.drop(i+1) - array).drop((i+1).neg).includes(0).not;

+

("no repetitions within a span of " ++ (i+2).asString ++ " items: ").post;

+

result.postln;

+

}

+

}

+

)

+


+

// test case

+

// no repetitions within a maximum span of 6 (lookBack == 5)

+


+

(

+

p = PIdev(Pbrown(0, 20, 0.3).round.asInteger, 5, -7, 7).iter;

+

a = p.nextN(10000);

+

a.plot;

+

~repetitionCheck.(a, 10);

+

)

+


+


+

 

+


+


+ + diff --git a/Help/PL.html b/Help/PL.html new file mode 100755 index 0000000..bdcd270 --- /dev/null +++ b/Help/PL.html @@ -0,0 +1,173 @@ + + + + + + + + + + + +

PL dynamic scope placeholder pattern 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_Pattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+

NOTE: PL follows a paradigm of immediate replacement. There are cases though where you might prefer to finish streams or substreams before replacement, especially when syncing comes into play, for these options consider PLn and the cutItems arg of PLx list patterns.

+


+

See also: PLn, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (item, repeats, type, envir)

+

+

Creates a new PL object.

+

+

item - Symbol or other Object. 

+

If a Symbol is passed, item can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

type - Expects 1 or inf. 

+

Defines how items other than Patterns or Streams should be embedded.

+

Defaults to 1. Rather to be used by other PLx Patterns than by the user.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// definition for future reference in arbitrary Environments

+


+

p = Pbind(\midinote, PL(\a), \dur, 0.2);

+


+


+

// prepare current Environment

+


+

(

+

~a = 60;

+

x = p.play;

+

)

+


+


+

// replace with items or patterns

+


+

~a = 58;

+


+


+

// PL had repeats = inf, so Pseq is embedded endlessly

+


+

~a = Pseq([60, 60, 58, 60, 53, 54.5, 56, 58]);

+


+

x.stop;

+


+


+

//////////////////////

+


+


+

// placeholder may also get event patterns

+


+

(

+

p = PL(\a, 1);

+


+

~a = Pbind(

+

\midinote, Pwhite(80, 85),

+

\dur, 0.2

+

);

+


+

x = p.play;

+

)

+


+


+


+

// replace, PL had repeats = 1, so ... 

+


+

(

+

~a = Pbind(

+

\midinote, Pseq((70..65)),

+

\dur, 0.05

+

);

+

)

+


+


+

//////////////////////

+


+


+

// PL may be used in cases where there is no PLx implementation

+


+

// Pseg used for pitch curve, linear interpolation 

+


+

(

+

p = Pbind(

+

\midinote, Pseg(PL(\p), PL(\d), \lin, inf),

+

\dur, 0.1

+

);

+


+

~p = Pshuf((50..75));

+


+

~d = 0.2;

+


+

x = p.play;

+

)

+


+


+

// play with segment length

+


+

~d = 0.4;

+


+

~d = 1;

+


+


+

// can also be replaced by pattern

+


+

~d = Pseq([0.2, 0.4, 1]);

+


+


+

// only two points left for interpolation 

+

// there may be repetitions 

+


+

~p = Pshufn([50, 100]).trace;

+


+

x.stop;

+


+


+ + diff --git a/Help/PLIdev.html b/Help/PLIdev.html new file mode 100755 index 0000000..7d7f4f8 --- /dev/null +++ b/Help/PLIdev.html @@ -0,0 +1,381 @@ + + + + + + + + + + + +

PLIdev dynamic scope pattern searching for numbers with integer distance from a source pattern, optionally avoiding repetitions within a span

+


+

Part of: miSCellaneous

+


+

Inherits from: Pattern

+


+

DIdev / PIdef / PLIdev search for numbers with integer distance from a source signal / pattern up to a given deviation. Repetitions within a lookback span are avoided, DIdev / PIdef / PLIdev randomly choose from possible solutions. Intended for search within integer grids (pitches, indices etc.), however applications with non-integer sources are possible, see examples.

+

 

+

NOTE: It's the user's responsibility to pass a combination of deviation and lookback values that allows a possible choice, see examples.

+


+

NOTE: In contrast to DIdev, PIdev and PLIdev do *not* need to know maximum deviations (minLoDev, maxHiDev) beforehand. Thus the order of arguments is different (here loDev and hiDev before lookBack).

+


+

See also: PLx suite, Idev suite, PIdev, DIdev

+


+


+

Creation / Class Methods

+


+

Creates a new PLIdev object.

+

+

*new (pattern, maxLookBack = 3, loDev = -3, hiDev = 3, lookBack, thr = 1e-3, length = inf, envir = \current)

+

+

pattern - Symbol or PIdev pattern arg. The source value pattern to start search from. 

+

If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. 

+

Can be dynamically replaced by Patterns or Streams.

+

maxLookBack - Integer, the maximum lookback span. Fixed, defaults to 3.

+

loDev - Symbol or PIdev loDev arg. Determines the current low deviation for the search. 

+

If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. 

+

Can be dynamically replaced by Patterns or Streams. Defaults to -3.

+

hiDev - Symbol or PIdev hiDev arg. Determines the current high deviation for the search. 

+

If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. 

+

Can be dynamically replaced by Patterns or Streams. Defaults to 3.

+

lookBack - Symbol or PIdev lookBack arg. Determines the current lookback span for avoiding repetitions.

+

Can be modulated but must not exceed maxLookBack.

+

If no value is passed, then maxLookBack is taken.

+

If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. 

+

Can be dynamically replaced by Patterns or Streams. Defaults to nil.

+

thr - Symbol or PIdev thr arg. Threshold for equality comparison. 

+

If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. 

+

Can be dynamically replaced by Patterns or Streams. Defaults to 1e-3. 

+

length - Symbol or PIdev length arg. Number of repeats. 

+

If a Symbol is passed, a value can be assigned to an envir variable later on. Defaults to inf. 

+

envir - Dictionary or one of the Symbols \top, \t (topEnvironment), \current, \c (currentEnvironment). 

+

Dictionary to be taken for variable reference. Defaults to \current.

+

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

Ex.1) Basic usage: random choice within region without repetitions

+

+

// constant source, max deviation +/- 3

+

// no repetition within 5 pitches

+


+

(

+

~in = 72;

+


+

p = Pbind(

+

\dur, 0.2,

+

\midinote, PLIdev(\in, 5, -3, 3).trace(prefix: "midi "),

+

).play;

+

)

+


+

// change source

+


+

~in = 65

+


+

p.stop

+


+


+

Ex.2) Variable deviations and lookBack

+


+

(

+

~loDev = -6;

+

~hiDev = 5;

+

~lookBack = 2;

+


+

p = Pbind(

+

\dur, 0.2,

+

\midinote, PLIdev(72, 11, \loDev, \hiDev, \lookBack).trace(prefix: "midi "),

+

).play;

+

)

+


+


+

// change on the fly

+

// as lookBack equals 2, this defines a fixed sequence (up or down anyway)

+


+

(

+

~loDev = -1;

+

~hiDev = 1;

+

)

+


+


+

// widen range

+


+

(

+

~loDev = -6;

+

~hiDev = 5;

+

)

+


+


+

// force a twelve-tone row

+


+

~lookBack = 11;

+


+


+

// contradictory input, lookBack 11 not possible within range, causes repetitions

+


+

(

+

~loDev = -3;

+

~hiDev = 2;

+

)

+


+

p.stop

+


+


+


+

Ex.3) Moving source signal

+


+

(

+

~loDev = -1;

+

~hiDev = 1;

+

~lookBack = 2;

+


+

p = Pbind(

+

\dur, 1/7,

+

\midinote, PLIdev(

+

Pseg(Pseq([65, 90], inf), 5, \sin).round,

+

11,

+

\loDev, 

+

\hiDev, 

+

\lookBack

+

).trace(prefix: "midi "),

+

).play;

+

)

+


+


+

// widen range and increase lookBack

+


+

(

+

~loDev = -6;

+

~hiDev = 5;

+

~lookBack = 10;

+

)

+


+

p.stop

+


+


+


+

Ex.4) Dynamic deviation range and lookBack

+


+

// lookBack and deviations coupled here

+

// maxLookBack must be large enough

+


+

(

+

~loDev = -1;

+

~hiDev = 1;

+

~lookBack = 2;

+


+

p = Pbind(

+

\dur, 1/7,

+

\midinote, PLIdev(78, 10, \loDev, \hiDev, \lookBack).trace(prefix: "midi "),

+

).play;

+

)

+


+

// start parameter movement on the fly

+


+

(

+

~loDev = Pseg(Pseq([2, 5].neg, inf), 5, \sin);

+

~hiDev = Pseg(Pseq([2, 5], inf), 5, \sin).trace(prefix: "absolute deviation and lookBack ");

+

~lookBack = Pseg(Pseq([2, 5], inf), 5, \sin);

+

)

+


+

p.stop

+


+


+

Ex.5) Non-integer source

+


+

(

+

~loDev = -6;

+

~hiDev = 5;

+

~lookBack = 3;

+

~thr = 1;

+


+

p = Pbind(

+

\dur, 1/7,

+

\midinote, PLIdev(

+

Pseg(Pseq([65, 90], inf), 5, \sin),

+

5,

+

\loDev,

+

\hiDev,

+

\lookBack,

+

\thr

+

).trace(prefix: "midi "),

+

).play;

+

)

+


+


+

// close floats can occur here

+

~thr = 0.01

+


+


+

// not here

+

~thr = 2

+


+

p.stop

+


+


+
Ex.6) Multichannel expansion

+

 

+

// larger pitch range and lookBack in upper voice 

+

// as with most patterns, no automatic array expansion of pattern arguments

+


+

(

+

~src = Pseg(Pseq([65, 85], inf), 5, \sin).round;

+


+

p = Pbind(

+

\dur, 1/7,

+

\midinote, Ptuple([

+

PLIdev(\src, 1, -1, 1),

+

PLIdev(\src, 3, -5, 5) + 8.5

+

]).trace(prefix: "midi "),

+

).play;

+

)

+


+

p.stop

+


+


+

Ex.7) Application to other params: rhythm

+

 

+

// if we have indexed data of whatever, we can slide over it,

+

// groups of durations as items to be streamed by PLIdev

+


+

(

+

~rhythmBase = [

+

[1, 1],

+

[2, 1, 1],

+

[1, 1, 2]

+

].collect(_.normalizeSum);

+


+

~rhythms = ~rhythmBase *.x [1, 2];

+

~rhythmNum = ~rhythms.size;

+

~rhythms = ~rhythms.scramble;

+


+

SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1,

+

rq = 0.05, pan = 0, amp = 0.1|

+

var sig = { WhiteNoise.ar } ! 2;

+

sig = BPF.ar(sig, freq, rq) *

+

EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

+

(rq ** -1) * (250 / (freq ** 0.8));

+

OffsetOut.ar(out, Pan2.ar(sig, pan));

+

}).add;

+

)

+


+

(

+

// rhythmic variation and partial pitch repetition

+

// play for a while to note slow sliding, caused by Pseg

+


+

~loDev = -1;

+

~hiDev = 1;

+

~maxLookBack = 5; // fixed !

+

~lookBack = 2;

+


+

// be careful not to exceed index bounds

+

~src = Pseg(Pseq([~loDev.abs, ~rhythmNum - ~hiDev - 1], inf), 10, \sin, inf).round;

+


+

p = Pbind(

+

\instrument, \noise_grain,

+

\rel, Pexprand(0.05, 0.15),

+

\dur, PLIdev(\src, ~maxLookBack, \loDev, \hiDev, \lookBack).trace(prefix: "rhythm type: ")

+

.collect(~rhythms[_]).flatten * 0.3,

+

\midinote, Pstutter(Pwhite(1, 2), Pclump(3, Pxrand((50..100), inf))).flatten + [0, 12],

+

\pan, Pstutter(Pwhite(1, 2), Pclump(3, Pwhite(-1.0, 1))).flatten

+

).play;

+

)

+


+


+

// more variation by larger deviation from fixed source and larger lookBack

+


+

(

+

~src = 3;

+

~loDev = -3;

+

~hiDev = 2;

+

~lookBack = 4;

+

)

+


+


+

// fixed cycle with these bounds and lookBack == 1

+


+

(

+

~loDev = -1;

+

~hiDev = 0;

+

~lookBack = 1;

+

)

+


+

// other fixed cycle

+


+

~src = 2;

+


+

p.stop

+


+


+


+

Ex.8) Proof of concept

+


+

(

+

// Function to check an array for repetitions within a maximum test span

+


+

~repetitionCheck = { |array, maxTestSpan|

+

maxTestSpan.do { |i|

+

var result = (array.drop(i+1) - array).drop((i+1).neg).includes(0).not;

+

("no repetitions within a span of " ++ (i+2).asString ++ " items: ").post;

+

result.postln;

+

}

+

}

+

)

+


+

// test case

+

// no repetitions within a maximum span of 6 (maxLookBack == 5)

+


+

(

+

~src = Pbrown(0, 20, 0.3).round.asInteger;

+

p = PLIdev(\src, 5, -7, 7).iter;

+

a = p.nextN(10000);

+

a.plot;

+

~repetitionCheck.(a, 10);

+

)

+


+


+

 

+ + diff --git a/Help/PLbeta.html b/Help/PLbeta.html new file mode 100755 index 0000000..ca73742 --- /dev/null +++ b/Help/PLbeta.html @@ -0,0 +1,140 @@ + + + + + + + + + + + +

PLbeta dynamic scope Pbeta variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pbeta

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pbeta, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (lo, hi, prob1, prob2, length, envir)

+

+

Creates a new PLbeta object.

+

+

lo - Symbol or Pbeta lo arg. Defaults to 0.

+

If a Symbol is passed, lo can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

hi - Symbol or Pbeta hi arg. Defaults to 1.

+

If a Symbol is passed, hi can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

prob1 - Symbol or Pbeta prob1 arg. Defaults to 1.

+

If a Symbol is passed, prob1 can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

prob2 - Symbol or Pbeta prob2 arg. Defaults to 1.

+

If a Symbol is passed, prob2 can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pbeta length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

(

+

p = Pbind(

+

\midinote, PLbeta(\lo, \hi, \p1, \p2),

+

\dur, 0.1

+

);

+

)

+


+

// prepare current Environment

+

// prob values for equal distribution

+


+

(

+

~lo = 60;

+

~hi = 90;

+

~p1 = 1;

+

~p2 = 1;

+

)

+


+


+

// run

+


+

x = p.play;

+


+


+

// replace probabilities, get values close to the bounds

+


+

(

+

~p1 = 0.02;

+

~p2 = 0.02;

+

)

+


+


+

// change between close-to-bounds and equally-distributed

+

// PLseq defaults to repeats = inf

+


+

(

+

~p1 =  Pstutter(10, PLseq([0.01, 1]));

+

~p2 =  Pstutter(10, PLseq([0.01, 1]));

+

)

+


+


+

// moving bounds

+


+

(

+

~lo = PLseq((60..70));

+

~hi = PLseq((80..90));

+

)

+


+

x.stop;

+


+


+ + diff --git a/Help/PLbindef.html b/Help/PLbindef.html new file mode 100755 index 0000000..499093e --- /dev/null +++ b/Help/PLbindef.html @@ -0,0 +1,380 @@ + + + + + + + + + + + +

PLbindef wrapper for Pbindef which allows replacement in object prototyping style

+


+

Part of: miSCellaneous

+


+

Inherits from: Pbindef

+


+

PLbindef works like a normal Pbindef and, like the latter, uses Pdef's global bookkeeping, but replacement of key streams can be done in object prototyping style with a dedicated PLbindefEnvironment, which also holds player methods. This Environment is itself assigned to the PLbindef's name in an Environment of choice, by default the current Environment. Setting can thus be done in very condensed syntax, also in combination with EventShortcuts.

+


+

NOTE: PLbindefs are registered globally in the same Dictionary as Pdefs, Pbindefs and PLbindefPars. It's recommended to do cleanup with 'clear' after using PLbindef / PLbindefPar as in examples below. Otherwise unwanted or strange behaviour might be caused by leftover sources when playing a new PLbindef / PLbindefPar example with same key. With SC >= 3.7 occasional posts of the default parent event occur with Pbindef, so also with PLbindef. I didn't notice any different other behaviour though.

+


+


+

See also: PLx suite, PLbindefPar, PLbindefEnvironment, PLbindefParEnvironment, EventShortcuts, PLx and live coding with Strings

+


+


+

Creation / Class Methods

+


+

*new (...args)

+

+

Creates a new PLbindef object. First arg should be the key, followed by key/value pairs for the Pbindef. The last arg can be an optional environment which determines where the corresponding PLbindefEnvironment should be stored, by default this is the current Environment at instantiation time.

+


+

+

Instance Methods

+


+

clear

+

+

As with Pbindef: stop, but in addition remove name entry from refEnvir and clear sourceEnvir.

+

+

sourceEnvir

+

+

Getter for PLbindef's PLbindefEnvironment.

+

+

refEnvir

+

+

Getter for the Environment where PLbindef's PLbindefEnvironment is associated with PLbindef's key.

+

+


+


+

Examples

+


+

// synthdefs to play with

+


+

(

+

SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1|

+

    var sig = { WhiteNoise.ar } ! 2;

+

    sig = BPF.ar(sig, freq, rq) *

+

        EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

+

        (rq ** -1) * (250 / (freq ** 0.8));

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

SynthDef(\sin_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

    var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2;

+

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

    var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2;

+

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

    OffsetOut.ar(out, sig);

+

}).add;

+

)

+


+


+


+

Ex.1) Setting key streams

+

+

// store PLbindef under key \x

+


+

(

+

PLbindef(\x,

+

\instrument, \sin_grain,

+

\dur, 0.2,

+

\midinote, Pwhite(60, 90)

+

)

+

)

+


+

// Environment has been made and assigned to the variable ~x in currentEnvironment, check

+


+

~x

+


+


+

// now the Environment can also be treated as a player

+


+

~x.play

+


+


+

// set params while playing

+


+

~x.att = Pwhite(0.01, 0.2)

+


+

~x.midinote = 75

+


+

~x.att = 0.01

+


+

~x.dur = 0.02

+


+

~x.midinote = Pbrown(60, 90, 1.5)

+


+


+

// pause

+


+

~x.stop

+


+

// resume

+


+

~x.play

+


+


+


+

// use method 'value' for parallel setting

+


+

~x.(\dur, 0.05, \midinote, Pwhite(70.0, 72))

+


+

~x.stop

+


+

// cleanup

+


+

~x.clear

+


+


+


+

Ex.2) PLbindef with EventShortcuts

+

+

// syntax can be more condensed with EventShortcuts, turn it on, post defaults

+


+

EventShortcuts.on

+


+

EventShortcuts.postCurrent

+


+


+

(

+

PLbindef(\u,

+

\i, \saw_grain,

+

\d, 0.2,

+

\m, Pwhite(60, 90)

+

)

+

)

+


+

~u.play

+


+

~u.d = 0.1

+


+


+

~u.m = Pn(Plazy { Pseries(rrand(50, 70), 1.5, 20) })

+


+

~u.m = ~u.m + [0, 4, 7]

+


+

~u.i = PLseq([\sin_grain, \saw_grain])

+


+


+

~u.a = Pn(Pseries(0.05, 0.01, 20))

+


+

~u.m = 70

+


+


+

// stop + cleanup in one

+


+

~u.clear

+


+


+


+

Ex.3) PLbindef with VarGui

+

+

// start with gui and define pitch sequence with sliders

+


+

(

+

p = PLbindef(\z,

+

\i, \saw_grain,

+

\d, 0.2,

+

\m, PLseq(\midi)

+

);

+


+

VarGui([\midi, [50, 90, \lin, 1, 60] ! 7], stream: p).gui;

+

)

+


+


+

~z.m = PLseq(\midi) + [0, 5, 8]

+


+

~z.m = ~z.m + PLseq([-1, 0, 1] * 12)

+


+

~z.clear

+


+


+

Ex.4) PLbindef with PbindFx

+

+

See also Ex.7c in PbindFx help

+


+

// boot server with extended resources

+


+

(

+

s.options.numPrivateAudioBusChannels = 1024;

+

s.options.memSize = 8192 * 16;

+

s.reboot;

+

)

+


+

(

+

SynthDef(\echo, { |out, in, maxEchoDelta = 0.2, echoDelta = 0.1,

+

    decayTime = 1, amp = 1, mix = 1|

+

    var sig, inSig = In.ar(in, 2);

+

    sig = DelayL.ar(

+

        CombL.ar(inSig, maxEchoDelta, echoDelta, decayTime, amp),

+

        maxEchoDelta,

+

        maxEchoDelta - echoDelta

+

    );

+

    Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+

SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000,

+

    cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1|

+

    var sig, inSig = In.ar(in, 2);

+

    sig = RLPF.ar(

+

        inSig,

+

        LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi),

+

        rq,

+

        amp

+

    ).softclip;

+

    Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+

SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1|

+

    var sig = { WhiteNoise.ar } ! 2;

+

    sig = BPF.ar(sig, freq, rq) *

+

        EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

+

        (rq ** -1) * (250 / (freq ** 0.8));

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

    var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2;

+

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

    OffsetOut.ar(out, sig);

+

}).add;

+

)

+


+

// prepare EventShortcuts for additional keys

+


+

(

+

EventShortcuts.addOnBase(\default, \fxs, (

+

dec: \decayTime,

+

cd: \cleanupDelay,

+

cf: \cutOffMoveFreq,

+

fxo: \fxOrder,

+

dta: \echoDelta

+

), true);

+


+

EventShortcuts.makeCurrent(\fxs);

+


+

EventShortcuts.on;

+

)

+


+


+

(

+

// source and fxs passed as PLbindefs

+


+

k = [\q, \echo, \wah];

+


+

PLbindef(\q,

+

\i, PLrand([\noise_grain, \saw_grain]),

+

\d, 0.25,

+

\m, PLseq([60, 60, 60, 62]),

+

\fxo, PLseq([0, 0, 1, 2]),

+

// echo introduces delay, so do delay if no echo

+

\lag, Pfunc { |e| e.fxo.asArray.includes(1).if { 0 }{ 0.2 } },

+

\a, 0.1,

+

\att, 0.01,

+

\rel, 0.1,

+


+

\cd, Pkey(\att)+ Pkey(\rel) + 0.001

+

);

+


+

PLbindef(\echo,

+

\fx, \echo,

+

\dta, 0.06,

+

\a, 0.5,

+

\dec, Pwhite(0.3, 1.8),

+

\cd, Pkey(\dec)

+

);

+


+

PLbindef(\wah,

+

\fx, \wah,

+

\cf, Pwhite(1, 3),

+

\a, 0.5,

+

\cd, 0.01

+

);

+


+


+

p = PbindFx(*k.collect(PLbindef(_))); // PbindFx(*(k.collect { |x| PLbindef(x) }));

+


+

q = p.play;

+

)

+


+


+


+

// manipulate midinotes

+


+

~q.m = ~q.m + PLseq([0, 12, -12])

+


+

~q.m = ~q.m + PLrand([0, [0, -4]])

+


+

~q.m = ~q.m + PLrand([0, [0, -7]])

+


+


+

// rhythm

+


+

~q.d = PLshufn([1, 1, 2, 1] / 8)

+


+


+

// echo param and fx order sequence

+


+

~echo.dta = Pwhite(0.03, 0.08)

+


+

~q.fxo = PLseq([0, 0, 1, 2, 1, [1, 2]])

+


+


+


+

~q.m = 48

+


+

// produce overtone series with echodelta

+


+

~echo.dta = 1 / 24.midicps / PLseq((1..16))

+


+


+

// this also works in chords ("fx expansion")

+

// source is processed twice per event

+


+

~echo.dta = 1 / [24, 26.7].midicps / PLseq((1..16))

+


+


+


+

// cleanup

+


+

q.stop

+


+

k.do { |i| PLbindef(i).clear }

+


+


+ + diff --git a/Help/PLbindefEnvironment.html b/Help/PLbindefEnvironment.html new file mode 100755 index 0000000..8425a84 --- /dev/null +++ b/Help/PLbindefEnvironment.html @@ -0,0 +1,92 @@ + + + + + + + + + + + +

PLbindefEnvironment Environment made by PLbindef to play and set its sources

+


+

Part of: miSCellaneous

+


+

Inherits from: Environment

+


+

Instances of this class are made as side effect of PLbindef creation, assigned to PLbindef's name in an Environment of choice (by default the current one) and used as player interface as well as for setting PLbindef's sources in condensed prototyping syntax. They are not thought to be created explicitely though. See PLbindef for examples.

+


+

See also: PLx suite, PLbindef, PLbindefPar, PLbindefParEnvironment

+


+


+

Creation / Class Methods

+


+

*new (n, proto, parent, know, name)

+

+

Creates a new PLbindefEnvironment object with arguments of IdentityDictionary. In contrast to the latter know defaults to true, which allows setting sources of the PLbindef in object prototyping style. name is used for the corresponding key of the PLbindef. Normally not to be used explicitely.

+

+

Instance Methods

+


+

put (key, obj)

+

+

Associates obj with Symbol key and updates PLbindef's source.

+

+

superPut (key, obj)

+

+

Associates obj with Symbol key, mimicing IdentityDictionary::put.

+

+

value (...args)

+

+

Expects key/value pairs and applies put.

+

+

name

+

+

Getter for PLbindef's name.

+

+

plbindef

+

+

Getter for the corrresponding PLbindef.

+

+

play (clock, protoEvent, quant, doReset)

+

+

Plays the corresponding PLbindef with passed arguments.

+


+

isPlaying

+

+

Indicates if the corresponding PLbindef is playing.

+


+

resume

+

+

Resumes the corresponding PLbindef.

+


+

reset

+

+

Resets the corresponding PLbindef.

+


+

stop

+

+

Stops the corresponding PLbindef.

+


+

clear

+

+

Clears the corresponding PLbindef.

+

+

+

+ + diff --git a/Help/PLbindefPar.html b/Help/PLbindefPar.html new file mode 100755 index 0000000..88f77d4 --- /dev/null +++ b/Help/PLbindefPar.html @@ -0,0 +1,511 @@ + + + + + + + + + + + +

PLbindefPar container for parallel PLbindefs which allows replacement in object prototyping style

+


+

Part of: miSCellaneous

+


+

Inherits from: Pdef

+


+


+

PLbindefPar employs a number of parallel PLbindefs, replacement of key streams can be done in object prototyping style with a dedicated PLbindefParEnvironment, which also holds player methods. This Environment is itself assigned to the PLbindefPar's name in an Environment of choice, by default the current Environment. Setting can thus be done in very condensed syntax, also in combination with EventShortcuts.

+


+

NOTE: PLbindefPars are registered globally in the same Dictionary as Pdefs, Pbindefs and PLbindefs. In addition for size = n PLbindefs of the same name with indices i = 0, ... n-1 appended are stored globally. It's recommended to do cleanup with 'clear' after using PLbindef / PLbindefPar as in examples below. Otherwise unwanted or strange behaviour might be caused by leftover sources when playing a new PLbindef / PLbindefPar example with same key. With SC >= 3.7 occasional posts of the default parent event occur with Pbindef, so also with PLbindefPar. I didn't notice any different other behaviour though.

+


+


+

See also: PLx suite, PLbindef, PLbindefEnvironment, PLbindefParEnvironment, EventShortcuts, PLx and live coding with Strings

+


+


+

Creation / Class Methods

+


+

*new (...args)

+

+

Creates a new PLbindefPar object. First arg should be the key, followed by the number of parallel PLbindefs and the key/value pairs. Values are assigned to single PLbindefs according to the convention of PLbindefParEnvironment::put, see there and examples below. The last arg can be an optional environment which determines where the corresponding PLbindefParEnvironment should be stored, by default this is the current Environment at instantiation time.

+


+

+

Instance Methods

+


+

clear

+

+

Stop parallel PLbindefs and remove name entry from refEnvir.

+

+

remove

+

+

Removes not only the PLbindefPar but also associated PLbindefs from global entry.

+

+

sourceEnvir

+

+

Getter for PLbindefPar's PLbindefParEnvironment.

+

+

refEnvir

+

+

Getter for the Environment where PLbindefPar's PLbindefParEnvironment is associated with PLbindefPar's key.

+

+

size

+

+

Getter for PLbindefPar's number of parallel PLbindefs.

+

+

+


+

Examples

+


+

// synthdefs to play with

+


+

(

+

SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1|

+

    var sig = { WhiteNoise.ar } ! 2;

+

    sig = BPF.ar(sig, freq, rq) *

+

        EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

+

        (rq ** -1) * (250 / (freq ** 0.8));

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

SynthDef(\sin_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

    var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2;

+

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

    var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2;

+

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

EventShortcuts.on;

+

)

+


+


+


+

Ex.1) Setting key streams

+

+


+

// use with EventShortcuts

+


+

EventShortcuts.on

+


+

(

+

PLbindefPar(\v, 3,

+

    \i, [\saw_grain, \sin_grain, \noise_grain],

+

    \d, 0.8,

+

    \m, [65, 70, 80],

+

    \rq, 0.002,

+

\a, 0.03,

+

    \att, Pwhite(0.05, 0.01)

+

)

+

)

+


+

~v.play

+


+

// set all streams

+


+

~v.d = 0.2

+


+


+

// set single streams

+


+

~v[0].d = 0.1

+


+

~v[2].d = 0.4

+


+


+

// parallel intervals and chords for single streams

+


+

~v[2].m = Pwhite(80, 90) + [0, 9]

+


+

~v[0].m = Pwhite(50, 65) + [0, 5]

+


+

~v[1].m = Pwhite(70, 75.5) + [0, 3, 7]

+


+


+


+

// differentiate setting with collection

+


+

~v.m = [50, Pwhite(90, 80), 96]

+


+


+

// reference to last sources

+


+

~v.m = ~v.m + 1

+


+

~v[2].m = ~v[2].m - Pwhite(0, 5)

+


+


+


+

// pause single streams

+


+

~v[1].isRest = true

+


+

~v[1].isRest = false

+


+


+

// shorter, pause with amplitude setting

+


+

~v[0].a = 0

+


+

~v[0].a = 0.05

+


+


+


+

// differentiate setting with collection produced by Function

+


+

~v.m = { |i| (i * 15) + PLseq((0..9)) + [55, 60] } ! 3

+


+


+

// set only some streams with an index list, passed as first arg to 'value'

+


+

~v.([0, 1], \d, 0.3)

+


+

~v.([1, 2], \d, 0.5)

+


+


+

// set all, they might be time-shifted

+


+

~v.d = 0.2

+


+


+

// hard sync with reset

+


+

~v.reset

+


+

~v.stop

+


+

~v.clear

+


+


+


+

Ex.2) More parallel streams, from granular to additive

+

+

// use EventShortcuts

+


+

EventShortcuts.on

+


+

(

+

PLbindefPar(\y, 12,

+

// wrapped indexing: collection is used for all 12 streams

+

\i, [\saw_grain, \sin_grain, \noise_grain],

+

\d, (1..12)/100,

+

\m, { rrand(60, 90) } ! 12,

+

\a, 0.02,

+

\rq, 0.002

+

)

+

)

+


+

~y.play

+


+

// evaluate more than once

+


+

~y.m = { rrand(60, 90) } ! 12

+


+


+

// refer to current midinotes

+


+

~y.m = ~y.m - 1

+


+


+

~y.d = [1, 2, 4] / 8

+


+

~y.d = ~y.d / 4

+


+

~y.stop

+


+

~y.clear

+


+


+


+

// replacement introducing additive structures

+


+

(

+

PLbindefPar(\add, 12,

+

\i, \sin_grain, 

+

\d, 1, 

+

\m, { Pn(Pseries(rrand(40, 60), Pwhite(1.0, 3), 20)) } ! 12, 

+

\att, 5,

+

\rel, 5,

+

\a, 0.005

+

).play

+

)

+


+

~add.f = { PLseq((1..16)) * (100 + Pwhite(0.0, 3)) } ! 12

+


+

~add.stop

+


+

~add.clear

+


+


+


+


+

Ex.3a) PLbindefPar with VarGui, same control params for parallel streams

+

+

// if different streams should read from one control

+

// it's best to refer via a dedicated key

+


+


+

// start with gui and define pitch sequence with sliders

+


+

(

+

EventShortcuts.on;

+


+

p = PLbindefPar(\w, 3,

+

\i, [\saw_grain, \sin_grain, \noise_grain],

+

\d, 0.25,

+

\n, PLseq(\midi),

+

\m, [70, 75, 80] + Pkey(\n),

+

\a, 0.05,

+

\rq, 0.005

+

);

+


+

VarGui([\midi, [0, 12, \lin, 1, 0] ! 7], stream: p.trace).gui

+

)

+


+

~w.d = [1, 2, 4] / 8

+


+


+

// see difference

+


+

~w.m = [70, 75, 80].collect(Pkey(\n) + _)  // ~w.m = [70, 75, 80].collect { |i| Pkey(\n) + i }

+


+


+

~w.m = [70, 75, 80] + Pkey(\n)

+


+


+

// three streams of chords, all refering to same sequence

+


+

~w.m = { |i| 70 + Pkey(\n) + [0, 7, 14] - (i * 12) } ! 3

+


+


+

// manipulate step source

+


+

~w.n = Pstutter(3, PLseq(\midi))

+


+

~w.n = ~w.n + PLseq([0, 0.5, 1])

+


+


+

~w.d = [1, 3, 4] / 8

+


+


+

// cleanup after stopping with gui

+


+

~w.clear

+


+


+

Ex.3b) PLbindefPar with VarGui, different control params for parallel streams

+

+

// three array controls

+


+

(

+

EventShortcuts.on;

+


+

p = PLbindefPar(\t, 3,

+

\i, [\saw_grain, \sin_grain, \noise_grain],

+

\d, 0.25,

+

\n, { |i| PLseq((\midi ++ i).asSymbol) } ! 3,

+

\m, [60, 75, 90].collect { |i| Pkey(\n) + i },

+

\a, 0.05,

+

\rq, 0.005

+

);

+


+

VarGui(

+

// three array var controls

+

({ |i| [(\midi ++ i).asSymbol, [0, 12, \lin, 1, 0] ! 7] } ! 3).flatten,

+

stream: p

+

).gui

+

)

+


+

~t.d = [1, 3, 2] / 8

+


+

~t.m = [[50, 55, 60], [76, 77, 78, 79], [86, 90, 94]].collect(Pkey(\n) + _)

+


+


+

~t.d = PLrand([1, 3, 2]) / 8

+


+


+

~t[2].a = 0

+


+

~t[0].a = 0

+


+


+

// cleanup after stopping with gui

+


+

~t.clear

+


+


+


+

Ex.4) PLbindefPar with PbindFx

+

+

See also Ex.7c in PbindFx help

+


+

// boot server with extended resources

+


+

(

+

s.options.numPrivateAudioBusChannels = 1024;

+

s.options.memSize = 8192 * 16;

+

s.reboot;

+


+

// fx synths

+


+

SynthDef(\echo, { |out, in, maxEchoDelta = 0.2, echoDelta = 0.1,

+

    decayTime = 1, amp = 1, mix = 1|

+

    var sig, inSig = In.ar(in, 2);

+

    sig = DelayL.ar(

+

        CombL.ar(inSig, maxEchoDelta, echoDelta, decayTime, amp),

+

        maxEchoDelta,

+

        maxEchoDelta - echoDelta

+

    );

+

    Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+

SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000,

+

    cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1|

+

    var sig, inSig = In.ar(in, 2);

+

    sig = RLPF.ar(

+

        inSig,

+

        LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi),

+

        rq,

+

        amp

+

    ).softclip;

+

    Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+


+

// prepare EventShortcuts for additional keys

+


+

EventShortcuts.addOnBase(\default, \fxs, (

+

dec: \decayTime,

+

cd: \cleanupDelay,

+

cf: \cutOffMoveFreq,

+

fxo: \fxOrder,

+

dta: \echoDelta

+

), true);

+


+

EventShortcuts.makeCurrent(\fxs);

+


+

EventShortcuts.on;

+

)

+


+


+

(

+

// source and fxs passed as PLbindefs

+


+

k = [\r, \echo, \wah];

+


+

PLbindefPar(\r, 12,

+

\i, [\noise_grain, \saw_grain],

+

\d, 0.25,

+

\m, PLseq([60, 60, 60, 62]),

+

\fxo, PLseq([0, 0, 1, 2]),

+

// echo introduces delay, so do delay if no echo

+

\lag, Pfunc { |e| e.fxo.asArray.includes(1).if { 0 }{ 0.2 } },

+

\a, 0.03,

+

\att, 0.01,

+

\rel, 0.1,

+


+

\cd, Pkey(\att)+ Pkey(\rel) + 0.001

+

);

+


+

PLbindef(\echo,

+

\fx, \echo,

+

\dta, 0.06,

+

\a, 1,

+

\dec, Pwhite(0.3, 1.8),

+

\cd, Pkey(\dec)

+

);

+


+

PLbindef(\wah,

+

\fx, \wah,

+

\cf, Pwhite(1, 3),

+

\a, 1,

+

\cd, 0.01

+

);

+


+

// all are Pdefs too, globally stored, so we can collect like this

+


+

p = PbindFx(*k.collect(Pdef(_))); // PbindFx(*(k.collect { |x| Pdef(x) }));

+


+

q = p.play;

+

)

+


+


+

// differentiate rhythm

+


+

~r.d = (1..12) / 8

+


+


+

// manipulate midinotes

+


+

~r.m = ~r.m + PLseq([0, 12, -12])

+


+

~r.m = ~r.m + PLrand([0, [0, -4]])

+


+

~r.m = ~r.m + PLrand([0, [0, -7]])

+


+


+


+

// echo param and fx order sequence

+


+

~echo.dta = Pwhite(0.03, 0.08)

+


+

~r.fxo = PLseq([0, 0, 1, 2, 1, [1, 2]])

+


+


+

~r.m = (48, 50..70)

+


+

// produce overtone series with echodelta

+


+

~echo.dta = 1 / 24.midicps / PLseq((1..16))

+


+


+

// this also works in chords ("fx expansion")

+

// source is processed twice per event

+


+

~echo.dta = 1 / [24, 26.7].midicps / PLseq((1..32).mirror)

+


+


+


+

// cleanup

+


+

q.stop

+


+

k.do { |i| Pdef(i).clear }

+


+


+


+ + diff --git a/Help/PLbindefParEnvironment.html b/Help/PLbindefParEnvironment.html new file mode 100755 index 0000000..b5390a8 --- /dev/null +++ b/Help/PLbindefParEnvironment.html @@ -0,0 +1,58 @@ + + + + + + + + + + + +

PLbindefParEnvironment Environment made by PLbindefPar to play and set its sources

+


+

Part of: miSCellaneous

+


+

Inherits from: PLbindefEnvironment

+


+

Instances of this class are made as side effect of PLbindefPar creation, assigned to PLbindefPar's name in an Environment of choice (by default the current one) and used as player interface and to set PLbindefPar's sources in condensed syntax. They are not thought to be created explicitely though. A PLbindefParEnvironment stores the sources passed with keys as well as the "unfolded" sources of single PLbindef streams. This is done by assigning PLbindefEnvironments to every Integer index of the PLbindefPar and updating them additionally. See PLbindefPar for examples.

+


+

See also: PLx suite, PLbindefPar, PLbindef, PLbindefEnvironment

+


+

Creation / Class Methods

+


+

*new (n, proto, parent, know, name, num, plbindef)

+

+

Creates a new PLbindefParEnvironment object with arguments of IdentityDictionary. In contrast to the latter know defaults to true, which allows setting sources of the PLbindefPar in object prototyping style. name is used for the corresponding key of the PLbindefPar. num is the number of parallel patterns in the PLbindefPar.

+

plbindef is the corresponding PLbindefPar. Normally not be used explicitely.

+

+

Instance Methods

+


+

put (key, obj)

+

+

Associates obj with Symbol key and updates PLbindefPar's source as well as the stored PLbindefEnvironments depending on the class of obj. For a SequenceableCollection items are assigned in the PLbindefEnvironments of corresponding indices (method 'wrapAt' is used for handling cases of smaller collections). Other passed objects are assigned in all PLbindefEnvironments. 

+

+

value (...args)

+

+

Expects key/value pairs and applies put. Optionally the first arg can be an Integer or a collection of Integers, specifying the PLbindefs to be set.

+

+

num

+

+

Getter for PLbindefPar's size.

+

+ + diff --git a/Help/PLbrown.html b/Help/PLbrown.html new file mode 100755 index 0000000..37292f4 --- /dev/null +++ b/Help/PLbrown.html @@ -0,0 +1,119 @@ + + + + + + + + + + + +

PLbrown dynamic scope Pbrown variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pbrown

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pbrown, PLgbrown, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (lo, hi, step, length, envir)

+

+

Creates a new PLbrown object.

+

+

lo - Symbol or Pbrown lo arg. Defaults to 0.

+

If a Symbol is passed, lo can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

hi - Symbol or Pbrown hi arg. Defaults to 1.

+

If a Symbol is passed, hi can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

step - Symbol or Pbrown step arg. Defaults to 0.125.

+

If a Symbol is passed, step can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pbrown length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLbrown(\lo, \hi, \step);

+


+


+

// prepare current Environment

+

// PLseq repeats arg defaults to inf

+


+

(

+

~lo = 55;

+

~hi = 80;

+

~step = Pstutter(10, PLseq([0.05, 3.5]));

+

)

+


+


+

// run

+


+

(

+

x = Pbind(

+

\midinote, Ptuple(p!2), 

+

\dur, 0.1

+

).play;

+

)

+


+

// replace

+


+

(

+

~lo = Pseq((50..90));

+

~hi = Pseq((50..90) + 15);

+

)

+


+


+


+ + diff --git a/Help/PLcauchy.html b/Help/PLcauchy.html new file mode 100755 index 0000000..e3391cf --- /dev/null +++ b/Help/PLcauchy.html @@ -0,0 +1,114 @@ + + + + + + + + + + + +

PLcauchy dynamic scope Pcauchy variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pcauchy

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pcauchy, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (mean, spread, length, envir)

+

+

Creates a new PLcauchy object.

+

+

mean - Symbol or Pcauchy mean arg. Defaults to 0.

+

If a Symbol is passed, mean can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

spread - Symbol or Pcauchy spread arg. Defaults to 1.

+

If a Symbol is passed, spread can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pcauchy length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments,

+

// Cauchy distribution is not bounded, so do clip !

+


+

(

+

p = Pbind(

+

\midinote, PLcauchy(\mean, \spread).clip(60, 90),

+

\dur, 0.1

+

);

+

)

+


+

// prepare current Environment

+


+

(

+

~mean = 75;

+

~spread = 0.1;

+

)

+


+


+

// run

+


+

x = p.play;

+


+


+

// move mean value and distribution

+

// PLseq defaults to repeats = inf

+


+

(

+

~mean = PLseq((80, 79.7..70));

+

~spread = PLseq([0, 0, 0, 1]);

+

)

+


+

x.stop;

+


+


+ + diff --git a/Help/PLexprand.html b/Help/PLexprand.html new file mode 100755 index 0000000..37dd118 --- /dev/null +++ b/Help/PLexprand.html @@ -0,0 +1,114 @@ + + + + + + + + + + + +

PLexprand dynamic scope Pexprand variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pexprand

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pexprand, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (lo, hi, length, envir)

+

+

Creates a new PLexprand object.

+

+

lo - Symbol or Pexprand lo arg. Defaults to 0.0001.

+

If a Symbol is passed, lo can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

hi - Symbol or Pexprand hi arg. Defaults to 1.

+

If a Symbol is passed, hi can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pexprand length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments,

+

// intervals grow together with base tone

+


+

(

+

q = PLexprand(\lo, \hi);

+


+

p = Pbind(

+

\midinote, 60 + q + PLtuple([0, q]),

+

\dur, 0.1

+

);

+

)

+


+


+

// prepare current Environment

+


+

(

+

~lo = 0.1;

+

~hi = 10;

+

)

+


+


+

// run

+


+

x = p.play;

+


+


+

// replace  

+


+

~hi = 20;

+


+

x.stop;

+


+


+ + diff --git a/Help/PLgauss.html b/Help/PLgauss.html new file mode 100755 index 0000000..2191620 --- /dev/null +++ b/Help/PLgauss.html @@ -0,0 +1,115 @@ + + + + + + + + + + + +

PLgauss dynamic scope Pgauss variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pgauss

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pgauss, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (mean, dev, length, envir)

+

+

Creates a new PLgauss object.

+

+

mean - Symbol or Pgauss mean arg. Defaults to 0.

+

If a Symbol is passed, mean can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

dev - Symbol or Pgauss dev arg. Defaults to 1.

+

If a Symbol is passed, dev can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pgauss length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments,

+

// Gauss distribution is not bounded, so do clip !

+


+

(

+

p = Pbind(

+

\midinote, PLgauss(\mean, \dev).clip(60, 90),

+

\dur, 0.1

+

);

+

)

+


+

// prepare current Environment

+


+

(

+

~mean = 75;

+

~dev = 0.3;

+

)

+


+


+

// run

+


+

x = p.play;

+


+


+

// move mean value and distribution

+

// PLseq defaults to repeats = inf

+


+

(

+

~mean = PLseq((80, 79.75..70.25));

+

~dev = Pstutter(8, PLseq([4, 0]));

+

)

+


+

x.stop;

+


+


+ + diff --git a/Help/PLgbrown.html b/Help/PLgbrown.html new file mode 100755 index 0000000..4b74030 --- /dev/null +++ b/Help/PLgbrown.html @@ -0,0 +1,126 @@ + + + + + + + + + + + +

PLgbrown dynamic scope Pgbrown variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pgbrown

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pgbrown, PLbrown, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (lo, hi, step, length, envir)

+

+

Creates a new PLgbrown object.

+

+

lo - Symbol or Pgbrown lo arg. Defaults to 0.

+

If a Symbol is passed, lo can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

hi - Symbol or Pgbrown hi arg. Defaults to 1.

+

If a Symbol is passed, hi can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

step - Symbol or Pgbrown step arg. Defaults to 0.125.

+

If a Symbol is passed, step can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pgbrown length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

(

+

p = Pbind(

+

\midinote, PLgbrown(\lo, \hi, \step) + [0, -7.4, -12.7], 

+

\dur, 0.1,

+

\amp, 0.05

+

);

+

)

+


+

// prepare Environments

+


+

(

+

e = (lo: 65, hi: 90, step: 0.01);

+

f = e.copy.put(\step, 0.05);

+

)

+


+


+

// run

+


+

(

+

e.use { x = p.play(quant: 0.2) };

+

f.use { y = p.play(quant: 0.2) };

+

)

+


+

// replace

+


+

(

+

e.lo = 65;

+

f.lo = 65;

+

e.hi = Pwhite(65, 67);

+

f.hi = Pwhite(65, 95);

+

f.step = 0.3

+

)

+


+

y.stop;

+


+

x.stop;

+


+


+ + diff --git a/Help/PLgeom.html b/Help/PLgeom.html new file mode 100755 index 0000000..38a3527 --- /dev/null +++ b/Help/PLgeom.html @@ -0,0 +1,115 @@ + + + + + + + + + + + +

PLgeom dynamic scope Pgeom variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pgeom

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pgeom, PLseries, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (start, grow, length, envir)

+

+

Creates a new PLgeom object.

+

+

start - Symbol or Pgeom start arg. Defaults to 0.

+

If a Symbol is passed, start can be assigned to an envir variable later on.

+

grow - Symbol or Pgeom grow arg. Defaults to 1.

+

If a Symbol is passed, grow can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pgeom length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

(

+

p = Pbind(

+

\freq, Pn(PLgeom(\start, \grow, 50)) % 2000 + 400,

+

\dur, 0.1

+

);

+

)

+


+

// prepare current Environment

+


+

(

+

~start = 100;

+

~grow = 1.1;

+

)

+


+


+

// from ascending to random

+


+

x = p.play;

+


+


+

// replace

+


+

~grow = 0.99;

+


+

~grow = 1.4;

+


+


+

x.stop;

+


+


+


+


+ + diff --git a/Help/PLhprand.html b/Help/PLhprand.html new file mode 100755 index 0000000..974519e --- /dev/null +++ b/Help/PLhprand.html @@ -0,0 +1,118 @@ + + + + + + + + + + + +

PLhprand dynamic scope Phprand variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Phprand

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Phprand, PLlprand, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (lo, hi, length, envir)

+

+

Creates a new PLhprand object.

+

+

lo - Symbol or Phprand lo arg. Defaults to 0.

+

If a Symbol is passed, lo can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

hi - Symbol or Phprand hi arg. Defaults to 1.

+

If a Symbol is passed, hi can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Phprand length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

q = PLhprand(\lo, \hi);

+


+


+

// prepare current Environment

+

// PLseq repeats arg defaults to inf

+


+

(

+

~lo = 60;

+

~hi = PLseq((92..62));

+

)

+


+


+

// run

+


+

(

+

y = Pbind(

+

\midinote, Ptuple(q!5), 

+

\dur, 0.1,

+

\amp, 0.05

+

).play;

+

)

+


+


+

// replace, converging bounds

+


+

(

+

~lo = PLseq((40..70) ++ (70!5));

+

~hi = 70.5;

+

)

+


+

y.stop;

+


+

// compare this example with lo-weighted PLlprand

+


+ + diff --git a/Help/PLlprand.html b/Help/PLlprand.html new file mode 100755 index 0000000..db22d61 --- /dev/null +++ b/Help/PLlprand.html @@ -0,0 +1,118 @@ + + + + + + + + + + + +

PLlprand dynamic scope Plprand variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Plprand

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Plprand, PLhprand, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (lo, hi, length, envir)

+

+

Creates a new PLlprand object.

+

+

lo - Symbol or Plprand lo arg. Defaults to 0.

+

If a Symbol is passed, lo can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

hi - Symbol or Plprand hi arg. Defaults to 1.

+

If a Symbol is passed, hi can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Plprand length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLlprand(\lo, \hi);

+


+


+

// prepare current Environment

+

// PLseq repeats arg defaults to inf

+


+

(

+

~lo = 60;

+

~hi = PLseq((92..62));

+

)

+


+


+

// run

+


+

(

+

x = Pbind(

+

\midinote, Ptuple(p!5), 

+

\dur, 0.1,

+

\amp, 0.05

+

).play;

+

)

+


+


+

// replace, converging bounds

+


+

(

+

~lo = PLseq((40..70) ++ (70!5));

+

~hi = 70.5;

+

)

+


+

x.stop;

+


+


+

// compare this example with hi-weighted PLhprand

+ + diff --git a/Help/PLmeanrand.html b/Help/PLmeanrand.html new file mode 100755 index 0000000..0ce43f6 --- /dev/null +++ b/Help/PLmeanrand.html @@ -0,0 +1,117 @@ + + + + + + + + + + + +

PLmeanrand dynamic scope Pmeanrand variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pmeanrand

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pmeanrand, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (lo, hi, length, envir)

+

+

Creates a new PLmeanrand object.

+

+

lo - Symbol or Pmeanrand lo arg. Defaults to 0.

+

If a Symbol is passed, lo can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

hi - Symbol or Pmeanrand hi arg. Defaults to 1.

+

If a Symbol is passed, hi can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pmeanrand length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLmeanrand(\lo, \hi);

+


+


+

// prepare current Environment

+

// PLseq repeats arg defaults to inf

+


+

(

+

~lo = PLseq((60, 60.25..70));

+

~hi = PLseq((70, 69.75..60));

+

)

+


+


+

// run

+


+

(

+

x = Pbind(

+

\midinote, Ptuple([p, 65]), 

+

\amp, [0.1, 0.06], 

+

\dur, 0.1

+

).play;

+

)

+


+


+

// replace

+


+

(

+

~lo = 60.5;

+

~hi = 61;

+

)

+


+

x.stop;

+


+


+ + diff --git a/Help/PLn.html b/Help/PLn.html new file mode 100755 index 0000000..1f67be9 --- /dev/null +++ b/Help/PLn.html @@ -0,0 +1,254 @@ + + + + + + + + + + + +

PLn dynamic scope placeholder pattern whose streams will be finished before replacements 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pn

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite. In contrast to PL a replacement doesn't take effect immediately, but with the next embedding. This behaviour might be preferred with syncing.

+

NOTE: As sources are finished before next replacements are possible, infinite source streams, other than with other PLx patterns, inhibit further replacements at all. For the option of finishing substreams with PLx list patterns see their cutItems arg and the last example below.

+


+

See also: PL, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (item, repeats, envir)

+

+

Creates a new PLn object.

+

+

item - Symbol or other Object. 

+

If a Symbol is passed, item can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

Ex. 1 Protection of periods

+

+

// compare PL and PLn, start with polling two items from each Stream

+

// Note that Pseq defaults to repeats = 1 and PL/PLn default to repeats = inf

+


+

(

+

~a = Pseq([1, 2, 3]);

+

x = PLn(\a).asStream;

+

y = PL(\a).asStream;

+


+

x.nextN(2);

+

y.nextN(2);

+

)

+


+


+

// replace proxy source 

+


+

~a = ~a * 100;

+


+


+

// Stream from PLn finishes before replacement

+


+

x.nextN(2)

+


+

// With PL substream is replaced immediately

+


+

y.nextN(2)

+


+


+


+

Ex. 2 Protection of periods for syncing

+


+

// playing two interval lines in parallel

+


+

(

+

~a = Pseq([4, 2, 0]) + [0, -5];

+

~b = Pseq([7, 5, 4]) + [0, 5];

+

+

p = Pbind(

+

\dur, Pseq([0.4, 0.2, 0.2], inf),

+

\note, PLn(\a)

+

);

+

+

q = Pbindf(p, \note, PLn(\b));

+


+

x = Ppar([p, q]).play;

+

)

+

+


+

// replace the lower event stream, new notes start after end of embedding Pseq

+

+

~a = Pseq([3, 2, 0]) + [0, -5];

+


+


+

// replace second line

+


+

~b = Pseq([7, 5, 4]) + [5, 12];

+


+


+

// source nil stops event stream after end of embedding Pseq

+


+

~a = nil;

+


+


+


+

Ex. 3 Protection of periods and subperiods with event streams

+


+

// define two Pbinds of same length, play first

+


+

(

+

x = Pbind(

+

\dur, Pseq([0.4, 0.2, 0.2]),

+

\note, Pseq([4, 2, 0]) 

+

);

+


+

y = Pbind(

+

\dur, 0.2,

+

\note, Pseq([7, 7, 5, 4]) 

+

);

+


+

~a = x;

+


+

PLn(\a).play;

+

)

+


+

// switch between them several times, periods are protected

+


+

~a = y;

+


+

~a = x;

+


+


+

// define a sequence of parts, we want to exchange parts later on

+


+

(

+

~trill_1 = Pbind(

+

\dur, 0.4/3,

+

\note, Pseq([7, 9, 7]) 

+

);

+


+

~trill_2 = Pbind(

+

\dur, 0.4/5,

+

\note, Pseq([7, 8, 7, 8, 7]) 

+

);

+


+

~fin = Pbind(

+

\dur, 0.2,

+

\note, Pseq([5, 4]) 

+

);

+


+

~b = [~trill_1, ~fin];

+

)

+


+

// replace with a PLseq

+

// note that source must have repeats = 1,

+

// in order to protect substreams with PLseq and change the behaviour later on

+

// we pass to cutItmes a Function that, for now, returns false

+


+

(

+

~cutItems = false;

+

~a = PLseq(\b, 1, cutItems: \cutItems);

+


+

~b[0] = ~trill_2;

+

)

+


+

// helper function to generate trill patterns

+


+

~trill = { |lo, hi, dur, reps| Pbind(\dur, dur / reps, \note, Pser([lo, hi], reps)) };

+


+


+

// replace with a long slow trill

+


+

~b[0] = ~trill_3 = ~trill.(7, 9, 0.9, 5);

+


+


+


+

// replace back while long trill, 

+

// due to the current value ~cutItems = false it will be completed and

+

// replacement takes effect in next loop

+

+

~b[0] = ~trill_2;

+


+

// replace back

+


+

~b[0] = ~trill_3;

+


+


+

// change replacing behaviour

+


+

~cutItems = true;

+


+


+

// now replacing while long trill will take immediate effect

+


+

~b[0] = ~trill_2;

+


+


+

// stop

+


+

~a = nil

+


+


+


+


+


+

+


+


+


+ + diff --git a/Help/PLnaryFunc.html b/Help/PLnaryFunc.html new file mode 100755 index 0000000..2381555 --- /dev/null +++ b/Help/PLnaryFunc.html @@ -0,0 +1,126 @@ + + + + + + + + + + + +

PLnaryFunc dynamic scope Pnaryop variant for Functions 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pnaryop

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also:, Pnaryop, PLnaryop, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (func, pat, arglist, envir)

+

+

Creates a new PLnaryFunc object.

+

+

func - Symbol or func arg. 

+

If a Symbol is passed, func can be assigned to an envir variable later on.

+

Can be dynamically replaced by a Pattern or Stream.

+

pat - Symbol or pattern arg. 

+

If a Symbol is passed, pattern can be assigned to an envir variable later on.

+

Can be dynamically replaced by a Pattern or Stream.

+

arglist - Symbols or arglist args. 

+

They are starting with input for the Function's second arg,

+

as items from pat will be passed to its first. 

+

If Symbols are passed, arglist args can be assigned to envir variables later on.

+

Can be dynamically replaced by Patterns or Streams.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

(

+

p = Pbind(

+

\midinote, PLnaryFunc(\f, \src, [\dev]),

+

\dur, 0.1

+

);

+

)

+


+


+

// define Environment and play

+


+

(

+

e = (f: { |x,y| x + y }, src: PLseq([61, 62]), dev: Pbrown(-5, 5, 0.2) );

+


+

e.use { x = p.play };

+

)

+


+


+

// replace Function input Patterns

+


+

e.dev = PLseq((0, 0.5..7));

+


+

e.src = PLseq([61,64,65]);

+


+


+

// replace Function 

+


+

e.f = { |x,y| x - y };

+


+

e.f = { |x,y| y * 4 + x };

+


+


+

x.stop;

+


+


+

See also PLx suite Ex. 5b.

+


+


+


+ + diff --git a/Help/PLnaryop.html b/Help/PLnaryop.html new file mode 100755 index 0000000..9816280 --- /dev/null +++ b/Help/PLnaryop.html @@ -0,0 +1,119 @@ + + + + + + + + + + + +

PLnaryop dynamic scope Pnaryop variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pnaryop

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite. For replacing operators dynamically take PLnaryFunc with the operator wrapped into a Function.

+


+

See also:, Pnaryop, PLnaryFunc, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (operator, pat, arglist, envir)

+

+

Creates a new PLnaryop object.

+

+

operator - Symbol or Pnaryop operator arg. 

+

If a Symbol is passed, operator can be assigned to an envir variable later on.

+

pat - Symbol or Pnaryop pattern arg. 

+

If a Symbol is passed, pattern can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

arglist - Symbols or Pnaryop arglist arg. 

+

If Symbols are passed, arglist args can be assigned to envir variables later on.

+

Can be dynamically replaced by Patterns or Streams.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+


+


+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

(

+

p = Pbind(

+

\midinote, PLnaryop('+', \src, [\dev]),

+

\dur, 0.1

+

);

+

)

+


+


+

// define Environment and play

+


+

(

+

e = (src: PLseq([61,62]), dev: Pbrown(-5, 5, 0.2) );

+


+

e.use { x = p.play };

+

)

+


+


+

// replace

+


+

e.dev = PLseq((0, 0.5..7));

+


+

e.dev = Pbrown(-5, 5, 0.2);

+


+

e.src = PLseq([61,64,65]);

+


+


+

x.stop;

+


+


+

See also PLx suite Ex. 5a.

+


+


+


+ + diff --git a/Help/PLpoisson.html b/Help/PLpoisson.html new file mode 100755 index 0000000..c66e87b --- /dev/null +++ b/Help/PLpoisson.html @@ -0,0 +1,107 @@ + + + + + + + + + + + +

PLpoisson dynamic scope Ppoisson variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Ppoisson

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Ppoisson, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (mean, length, envir)

+

+

Creates a new PLpoisson object.

+

+

mean - Symbol or Ppoisson mean arg. Defaults to 1.

+

If a Symbol is passed, mean can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Ppoisson length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments,

+

// Poisson distribution gives positive integer values

+


+

(

+

p = Pbind(

+

\midinote, (PLpoisson(\mean) + 50).clip(60, 90),

+

\dur, 0.1

+

);

+

)

+


+


+

// prepare current Environment

+


+

~mean = 10;

+


+


+

// run

+


+

x = p.play;

+


+


+

// changing mean value 

+


+

~mean = Pstutter(5, PLseq((0, 5..30)));

+


+

x.stop;

+


+


+ + diff --git a/Help/PLrand.html b/Help/PLrand.html new file mode 100755 index 0000000..3f2b47e --- /dev/null +++ b/Help/PLrand.html @@ -0,0 +1,177 @@ + + + + + + + + + + + +

PLrand dynamic scope Prand variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_ListPattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Prand, PLxrand, PLwrand, PLshuf, PLshufn, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, cutItems, envir)

+

+

Creates a new PLrand object.

+

+

list - Symbol or Prand list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Prand repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// define Pattern and prepare Environments

+

+

(

+

p = Pbind(

+

\midinote, PLrand(\a),

+

\dur, 0.2

+

);

+


+

e = (a: (67..72));

+

f = (a: (77..82));

+

)

+


+

// start together or not, but sync anyway

+


+

e.use { x = p.play(quant: 0.2) };

+

f.use { y = p.play(quant: 0.2) };

+


+


+

// replace array elements ...

+


+

f.a[0] = 97;

+


+

e.a[0] = 96;

+


+


+

// ... or arrays 

+


+

f.a = (72, 72.5..77);

+


+

e.a = [60];

+


+

e.a = [Pseq([60, 47.5]) + [0, 5], [61, 65.5]];

+


+

x.stop;

+

y.stop;

+


+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLrand(\a);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 3),

+

\dur, 0.2

+

)

+

];

+


+

x = p.play;

+

)

+


+


+

// replace array element

+


+

(

+

~a[0] = Pbind(

+

\midinote, Pwhite(70, 75, 3) + [0, 5],

+

\dur, 0.15

+

);

+

)

+


+


+

// replace whole array

+


+

(

+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.05

+

),

+

Pbind(

+

\midinote, Pwhite(70, 90, 2),

+

\dur, 0.25

+

)

+

];

+

)

+


+

x.stop;

+


+ + diff --git a/Help/PLseq.html b/Help/PLseq.html new file mode 100755 index 0000000..59c5ceb --- /dev/null +++ b/Help/PLseq.html @@ -0,0 +1,171 @@ + + + + + + + + + + + +

PLseq dynamic scope Pseq variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_ListPattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pseq, PLser, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, offset, cutItems, envir)

+

+

Creates a new PLseq object.

+

+

list - Symbol or Pseq list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Pseq repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

offset - Symbol or Pseq offset arg. Defaults to 0.

+

If a Symbol is passed, offset can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// definition for future reference in arbitrary Environments

+


+

(

+

p = Pbind(

+

\midinote, PLseq(\a),

+

\dur, 0.1

+

);

+

)

+


+


+

// assign a value to variable ~a in current Environment,

+

// play there.

+

// PLseq defaults to repeats = inf, Pseq defaults to repeats = 1

+


+

(

+

~a = (67..70) ++ Pseq((99..70));

+


+

x = p.play;

+

)

+


+


+

// try replacing Pseq stream while chromatic line down,

+

// works immediately with default value cutItems = true

+


+

~a[4] = 73;

+


+


+

// replace whole list

+


+

~a = (60..65);

+


+

x.stop;

+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLseq(\a);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 3),

+

\dur, 0.2

+

)

+

];

+


+

x = p.play;

+

)

+


+


+

// replace array element

+


+

(

+

~a[0] = Pbind(

+

\midinote, Pwhite(70, 75, 3) + [0, 5],

+

\dur, 0.15

+

);

+

)

+


+

// replace whole array

+


+

(

+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.05

+

),

+

Pbind(

+

\midinote, Pwhite(70, 90, 2),

+

\dur, 0.25

+

)

+

];

+

)

+


+

x.stop;

+


+


+


+


+ + diff --git a/Help/PLser.html b/Help/PLser.html new file mode 100755 index 0000000..5162ab2 --- /dev/null +++ b/Help/PLser.html @@ -0,0 +1,144 @@ + + + + + + + + + + + +

PLser dynamic scope Pser variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PLseq

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pser, PLseq, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, offset, cutItems, envir)

+

+

Creates a new PLser object.

+

+

list - Symbol or Pser list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Pser repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

offset - Symbol or Pser offset arg. Defaults to 0.

+

If a Symbol is passed, offset can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

(

+

// PLseq not used as proxy, only taken as it defaults to repeats = inf

+


+

p = Pbind(

+

\midinote, PLseq([PLser(\a, \r), 60, 61]),

+

\dur, 0.2

+

);

+


+

// prepare current Environment

+


+

~a = (67..70);

+

~r = 6;

+

)

+


+

x = p.play;

+


+


+

// replace repeats

+


+

~r = 3;

+


+


+

// replace array elements

+


+

~a[0] = [77, 93.5];

+


+

~a[2] = Pseq((94..96));

+


+


+

// replace whole array

+


+

~a = [55, 52];

+


+

x.stop;

+


+


+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLser(\a, 3);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.1

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 3),

+

\dur, 0.1

+

)

+

];

+


+

x = p.play;

+

)

+


+ + diff --git a/Help/PLseries.html b/Help/PLseries.html new file mode 100755 index 0000000..c8d021c --- /dev/null +++ b/Help/PLseries.html @@ -0,0 +1,120 @@ + + + + + + + + + + + +

PLseries dynamic scope Pseries variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pseries

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pseries, PLgeom, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (start, step, length, envir)

+

+

Creates a new PLseries object.

+

+

start - Symbol or Pseries start arg. Defaults to 0.

+

If a Symbol is passed, start can be assigned to an envir variable later on.

+

step - Symbol or Pseries step arg. Defaults to 1.

+

If a Symbol is passed, step can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pseries length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

(

+

p = Pbind(

+

\midinote, PLseries(0, \step) % 24 + 60,

+

\dur, 0.1

+

);

+

)

+


+

// prepare Environments

+


+

(

+

e = (step: 1);

+

f = e.copy;

+

)

+


+


+

// run

+


+


+

e.use { x = p.play(quant: 0.1) };

+


+

f.use { y = p.play(quant: 0.1) };

+


+


+

// replace

+


+

f.step = 2;

+


+

e.step = Pwhite(0.5, 1.5);

+


+

f.step = Pwhite(1, -3);

+


+


+


+

y.stop;

+


+

x.stop;

+


+


+ + diff --git a/Help/PLshuf.html b/Help/PLshuf.html new file mode 100755 index 0000000..014878e --- /dev/null +++ b/Help/PLshuf.html @@ -0,0 +1,179 @@ + + + + + + + + + + + +

PLshuf dynamic scope Pshuf variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_ListPattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite. A PLshuf stream keeps a permutation until it ends or there is a list replacement. See PLshufn for ongoing choice of new permutations.

+


+

See also: Pshuf, Pshufn, PLshufn, PLrand, PLxrand, PLwrand, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, cutItems, envir)

+

+

Creates a new PLshuf object.

+

+

list - Symbol or Pshuf list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Pshuf repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

(

+

p = Pbind(

+

\midinote, PLshuf(\a),

+

\dur, 0.2

+

);

+


+

// prepare Environment

+


+

~a = (67..72);

+

)

+


+

x = p.play;

+


+


+

// replace array elements ...

+


+

~a[0] = 63;

+


+


+

// ... or whole arrays 

+

// evaluating more than once gives a newly chosen permutation  

+


+

~a = (60..65);

+


+

x.stop;

+


+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLshuf(\a);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 6),

+

\dur, 0.1

+

)

+

];

+


+

x = p.play;

+

)

+


+


+

// replace array element

+


+

(

+

~a[2] = Pbind(

+

\midinote, Pwhite(70, 75, 3) + [0, 5],

+

\dur, 0.15

+

);

+

)

+


+


+

// replace whole array

+


+

(

+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3) + [0, 5],

+

\dur, 0.15

+

),

+

Pbind(

+

\midinote, Pwhite(70, 80, 2) + [0, 4],

+

\dur, 0.25

+

),

+

Pbind(

+

\midinote, Pwhite(95, 100, 6) + [0, -9],

+

\dur, 0.05

+

)

+

];

+

)

+


+

x.stop;

+


+


+


+


+ + diff --git a/Help/PLshufn.html b/Help/PLshufn.html new file mode 100755 index 0000000..cb7e365 --- /dev/null +++ b/Help/PLshufn.html @@ -0,0 +1,188 @@ + + + + + + + + + + + +

PLshufn dynamic scope Pshufn variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PLshuf

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite. A PLshufn stream keeps on choosing permutations for the current list. See PLshuf for keeping one permutation.

+


+


+

See also: Pshufn, Pshuf, PLshuf, PLrand, PLxrand, PLwrand, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, cutItems, envir)

+

+

Creates a new PLshufn object.

+

+

list - Symbol or Pshufn list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Pshufn repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+


+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

(

+

p = Pbind(

+

\midinote, PLshufn(\a),

+

\dur, 0.2

+

);

+


+

// prepare Environments

+


+

e = (a: (67..72));

+

f = (a: (77..82));

+

)

+


+

e.use { x = p.play(quant: 0.2) };

+

f.use { y = p.play(quant: 0.2) };

+


+


+

// replace array elements ...

+


+

e.a[0] = 97;

+

f.a[0] = 98;

+


+


+

// ... or arrays 

+


+

(

+

e.a = [80, 81];

+

f.a = (70..73);

+

)

+


+

e.a[0] = Pwhite(60, 65, 1) + [0, -5, 29.5];

+


+

y.stop;

+

x.stop;

+


+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLshufn(\a);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 6),

+

\dur, 0.1

+

)

+

];

+


+

x = p.play;

+

)

+


+


+

// replace array element

+


+

(

+

~a[2] = Pbind(

+

\midinote, Pwhite(70, 75, 3) + [0, 5],

+

\dur, 0.15

+

);

+

)

+


+

// replace whole array

+


+

(

+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3) + [0, 5],

+

\dur, 0.15

+

),

+

Pbind(

+

\midinote, Pwhite(70, 80, 2) + [0, 4],

+

\dur, 0.25

+

),

+

Pbind(

+

\midinote, Pwhite(95, 100, 6) + [0, -9],

+

\dur, 0.05

+

)

+

];

+

)

+


+

x.stop;

+


+


+


+ + diff --git a/Help/PLslide.html b/Help/PLslide.html new file mode 100755 index 0000000..a7b32e2 --- /dev/null +++ b/Help/PLslide.html @@ -0,0 +1,194 @@ + + + + + + + + + + + +

PLslide dynamic scope Pslide variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_ListPattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pslide, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, len, step, start, wrapAtEnd, cutItems, envir)

+

+

Creates a new PLslide object.

+

+

list - Symbol or Pslide list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Pslide repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

len - Symbol or Pslide len arg. Defaults to 3.

+

If a Symbol is passed, len can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

step - Symbol or Pslide step arg. Defaults to 1.

+

If a Symbol is passed, step can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

start - Symbol or Pslide start arg. Defaults to 0.

+

If a Symbol is passed, start can be assigned to an envir variable later on.

+

wrapAtEnd - Symbol or Pslide wrapAtEnd arg. Defaults to true.

+

If a Symbol is passed, wrapAtEnd can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLslide(\a, inf, \len, \step, \start);

+


+


+

// prepare current Environment

+


+

(

+

~len = 3;

+

~step = 1;

+

~start = 0;

+

~a = [60, 61, 62, 70, 71, 72, 80, 81, 82];

+

)

+


+


+

// run

+


+

(

+

x = Pbind(

+

\midinote, p, 

+

\dur, 0.2

+

).play;

+

)

+


+


+

// replace list elements

+


+

~a[0] = 60.5;

+


+

~a[8] = Pseq([92, 80.5], 1);

+


+


+


+

// len and step can be replaced by Patterns

+

// PLseq defaults to repeats = inf

+


+

~len = Pstutter(5, PLseq([1, 2, 3]));

+


+

(

+

~len = 1;

+

~step = Pstutter(2, PLseq([0, 4]));

+

)

+


+

x.stop;

+


+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLslide(\a, inf, \len, \step, \start);

+


+

~len = 3;

+

~step = 1;

+

~start = 0;

+

~add = 0;

+


+

~a = (60, 62..70).collect { |x|

+

Pbind(

+

\midinote, x + Pseq((0, 0.2..0.8)) + PL(\add),

+

\dur, 0.1

+

);

+

};

+


+

x = p.play;

+

)

+


+


+

// replace slide params an ~add

+


+

~add = [4, 9];

+


+

~step = 2;

+


+

~len = 4;

+


+

~add = [4, 9, 13];

+


+


+


+

// replace array element

+


+

(

+

~a[0] = Pbind(

+

\midinote, 80 + Pseq((0.8, 0.6..0)) + PL(\add),

+

\dur, 0.05

+

);

+

)

+


+

x.stop;

+


+


+


+


+


+ + diff --git a/Help/PLswitch.html b/Help/PLswitch.html new file mode 100755 index 0000000..48515de --- /dev/null +++ b/Help/PLswitch.html @@ -0,0 +1,192 @@ + + + + + + + + + + + +

PLswitch dynamic scope Pswitch variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_Pattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pswitch, PLswitch1, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, which, cutItems, envir)

+

+

Creates a new PLswitch object.

+

+

list - Symbol or Pswitch list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

which - Symbol or Pswitch which arg. Defaults to 0.

+

If a Symbol is passed, which can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLswitch(\a, \w);

+


+


+

// define array

+

// PLseq defaults to repeats = inf

+


+


+

(

+

~a = (70..75) ++ Pshuf((85..80), 2) ++ Pseq((90..94));

+


+

~w = PLseq((0..7));

+


+

x = Pbind(\midinote, p, \dur, 0.1).play;

+

)

+


+

// update array element

+


+

~a[2] = Pseq([86, 85], 2) + [0, 3];

+


+


+

// reverse index pattern

+


+

~w = PLseq((7..0));

+


+


+

// keep in mind that indices are wrapped, no surprise here ...

+


+

~a = (70,72..84);

+


+


+

// ... but with shorter array indices are grouped in 5 + 3

+


+

~a = (70,72..78);

+


+

x.stop;

+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLswitch(\a, \w);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(70, 75, 3),

+

\dur, 0.15

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 3),

+

\dur, 0.1

+

),

+

Pbind(

+

\midinote, Pwhite(90, 95, 6),

+

\dur, 0.05

+

)

+

];

+


+

~w = PLseq((0..3));

+


+

x = p.play;

+

)

+


+


+

// replace index sequence

+


+

~w = PLseq([3, 0]);

+


+

~w = PLseq([1, 2]);

+


+


+

// replace array element

+


+

(

+

~a[2] = Pbind(

+

\midinote, Pwhite(70, 75, 3) + [0, 5],

+

\dur, 0.15

+

);

+

)

+


+


+

// replace whole array

+


+

(

+

~a = [ Pbind(

+

\midinote, Pwhite(60, 65, 3) + [0, 5],

+

\dur, 0.15

+

),

+

Pbind(

+

\midinote, Pwhite(70, 80, 2) + [0, 4],

+

\dur, 0.25

+

),

+

Pbind(

+

\midinote, Pwhite(95, 100, 6) + [0, -9],

+

\dur, 0.05

+

)

+

];

+

)

+


+

x.stop;

+


+


+ + diff --git a/Help/PLswitch1.html b/Help/PLswitch1.html new file mode 100755 index 0000000..1739334 --- /dev/null +++ b/Help/PLswitch1.html @@ -0,0 +1,197 @@ + + + + + + + + + + + +

PLswitch1 dynamic scope Pswitch1 variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PLswitch

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pswitch1, PLswitch, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, which, cutItems, envir)

+

+

Creates a new PLswitch1 object.

+

+

list - Symbol or Pswitch1 list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

which - Symbol or Pswitch1 which arg. Defaults to 0.

+

If a Symbol is passed, which can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLswitch1(\a, \w);

+


+


+

// run Pbind in current Environment

+

// take PLseq as they already default to repeats = inf

+


+

(

+

~a = [PLseq((85..80)), PLseq((65..70))];

+

~w = PLseq([0, 1]);

+


+

x = Pbind(\midinote, p, \dur, 0.1).play;

+

)

+


+

// update array element

+


+

~a[1] = PLseq([96, 95]);

+


+


+

// update array

+


+

~a = [59, 85];

+


+


+

// update index pattern, used to stop also 

+


+

~w = Pseq([0, 0, 1], 5);

+


+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLswitch1(\a, \w);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(70, 75),

+

\dur, 0.15

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85),

+

\dur, 0.1

+

),

+

Pbind(

+

\midinote, Pwhite(90, 95),

+

\dur, 0.05

+

)

+

];

+


+

~w = PLseq((0..3));

+


+

x = p.play;

+

)

+


+


+

// replace index sequence

+


+

~w = PLseq([3, 0]);

+


+

~w = PLseq([1, 2]);

+


+


+

// replace array element

+


+

(

+

~a[2] = Pbind(

+

\midinote, Pwhite(70, 75) + [0, 5],

+

\dur, 0.15

+

);

+

)

+


+


+

// replace whole array

+


+

(

+

~a = [ Pbind(

+

\midinote, Pwhite(60, 65) + [0, 5],

+

\dur, 0.15

+

),

+

Pbind(

+

\midinote, Pwhite(70, 80) + [0, 4],

+

\dur, 0.25

+

),

+

Pbind(

+

\midinote, Pwhite(95, 100) + [0, -9],

+

\dur, 0.05

+

),

+

Pbind(

+

\midinote, Pwhite(95, 100) + [0, -14],

+

\dur, 0.02

+

)

+

];

+

)

+


+

~w = PLshuf((0..3));

+


+

~w = PLshufn((0..3));

+


+


+

x.stop;

+


+


+ + diff --git a/Help/PLtuple.html b/Help/PLtuple.html new file mode 100755 index 0000000..06131ab --- /dev/null +++ b/Help/PLtuple.html @@ -0,0 +1,124 @@ + + + + + + + + + + + +

PLtuple dynamic scope Ptuple variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_ListPattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Ptuple, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, cutItems, envir)

+

+

Creates a new PLtuple object.

+

+

list - Symbol or Ptuple list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Ptuple repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLtuple(\a);

+


+


+

// prepare current Environment

+

// PLtuple defaults to repeats = inf,

+

// so inner Patterns are repeatedly embedded

+


+

~a = [ Pshuf((60..65)), 70, Pshuf((75..80)) ];

+


+


+

// run

+


+

x = Pbind(\midinote, p, \dur, 0.2).trace.play;

+


+


+

// replace elements

+


+

~a[0] = 72.5;

+


+

~a[1] = 74.5;

+


+

~a[2] = Prand([85, 86, 87]);

+


+


+

// replace array

+


+

// Ptuple and PLtuple start with new embedding of ALL patterns

+

// if one ends, so here default repeats = inf of PLshuf has no effect:

+

// new permutation with every loop as Pshuf has repeats = 1

+


+

~a = [ Pshuf((60..65)), 70, PLshuf((75..80)) ];

+


+


+

// both Patterns have repeats = inf,

+

// permutation is kept

+


+

~a = [ PLshuf((60..65)), 70, PLshuf((75..80)) ];

+


+


+

x.stop;

+


+ + diff --git a/Help/PLwalk.html b/Help/PLwalk.html new file mode 100755 index 0000000..5fda89d --- /dev/null +++ b/Help/PLwalk.html @@ -0,0 +1,222 @@ + + + + + + + + + + + +

PLwalk dynamic scope Pwalk variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_ListPattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pwalk, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, stepPattern, directionPattern, startPos, cutItems, envir)

+

+

Creates a new PLwalk object.

+

+

list - Symbol or Pwalk list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

stepPattern - Symbol or Pwalk stepPattern arg. Defaults to Prand([1, -1], inf).

+

If a Symbol is passed, stepPattern can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

directionPattern - Symbol or Pwalk directionPattern arg. Defaults to 1.

+

If a Symbol is passed, directionPattern can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

startPos - Symbol or Pwalk startPos arg. Defaults to 0.

+

If a Symbol is passed, startPos can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLwalk(\a, \step, \dir, \start);

+


+


+

// prepare current Environment

+


+

(

+

~a = (60, 64..102);

+

~step = 1;

+

~start = 70;

+

~dir = -1;

+

)

+


+


+

// run

+


+

(

+

x = Pbind(

+

\midinote, p + [0, 3], 

+

\dur, 0.1, 

+

\legato, 1.2

+

).play;

+

)

+


+


+

// replace with Patterns

+

// PLx ListPatterns default to repeats = inf

+


+

~step = PLrand([1, 2]);

+


+

~dir = PLseq([-1, 1]);

+


+


+

// replace list

+


+

~a = ~a - 1.5;

+


+


+

x.stop;

+


+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+


+

(

+

p = PLwalk(\a, \step, \dir);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 1) + [0, -8.7],

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(70, 75, 2) + [0, -3.3],

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(80, 90, 3) + [0, -4.3],

+

\dur, 0.1

+

),

+

Pbind(

+

\midinote, Pwhite(90, 100, 2) + [0, 5.7],

+

\dur, 0.05

+

)

+

];

+


+

~step = 1;

+


+

~dir = 1;

+


+

x = p.play;

+

)

+


+


+

// replace step pattern, pending between two source patterns

+

// try evaluating several times

+


+

~step = PLseq([1, -1]);

+


+

~step = 1;

+


+


+

// replace array element

+


+

(

+

~step = 1;

+


+

~a[0] = Pbind(

+

\midinote, Pwhite(60, 75, 1) + [0, 5, 14, 19, 22],

+

\dur, 0.15

+

);

+


+

~a[3] = Pbind(

+

\midinote, Pseq((90..95)),

+

\dur, 0.05

+

);

+

)

+


+


+

// replace whole array

+


+

(

+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.05

+

),

+

Pbind(

+

\midinote, Pwhite(70, 90, 2),

+

\dur, 0.25

+

),

+

Pbind(

+

\midinote, Pwhite(70, 75, 3),

+

\dur, 0.05

+

),

+

Pbind(

+

\midinote, Pwhite(80, 95, 3) + [0, -5.3],

+

\dur, 0.3

+

)

+

];

+

)

+


+

x.stop;

+


+


+


+


+


+


+ + diff --git a/Help/PLwhite.html b/Help/PLwhite.html new file mode 100755 index 0000000..0314b10 --- /dev/null +++ b/Help/PLwhite.html @@ -0,0 +1,113 @@ + + + + + + + + + + + +

PLwhite dynamic scope Pwhite variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pwhite

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pwhite, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (lo, hi, length, envir)

+

+

Creates a new PLwhite object.

+

+

lo - Symbol or Pwhite lo arg. Defaults to 0.

+

If a Symbol is passed, lo can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

hi - Symbol or Pwhite hi arg. Defaults to 1.

+

If a Symbol is passed, hi can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

length - Symbol or Pwhite length arg. Defaults to inf.

+

If a Symbol is passed, length can be assigned to an envir variable later on.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// definition for future reference in arbitrary Environments

+


+

p = PLwhite(\lo, \hi);

+


+


+

// prepare current Environment

+

// PLseq repeats defaults to inf

+


+

(

+

~lo = PLseq((60..70));

+

~hi = PLseq((62..72));

+

)

+


+


+

// run

+


+

(

+

x = Pbind(

+

\midinote, p, 

+

\dur, 0.1 

+

).play;

+

)

+


+


+

// replace, converging bounds, stops when ~lo ends (Pseq default repeats = 1)

+


+

(

+

~lo = Pseq((60..80) ++ (80!5));

+

~hi = 80;

+

)

+


+


+


+


+ + diff --git a/Help/PLwrand.html b/Help/PLwrand.html new file mode 100755 index 0000000..64236c6 --- /dev/null +++ b/Help/PLwrand.html @@ -0,0 +1,131 @@ + + + + + + + + + + + +

PLwrand dynamic scope Pwrand variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_ListPattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pwrand, PLrand, PLxrand, PLshuf, PLshufn, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, weights, repeats, cutItems, envir)

+

+

Creates a new PLwrand object.

+

+

list - Symbol or Pwrand list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

weights - Symbol or Pwrand weights arg. 

+

If a Symbol is passed, weights can be assigned to an envir variable later on.

+

Can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Pwrand repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

(

+

p = Pbind(

+

\freq, 50 * PLwrand(\a, \w),

+

\dur, 0.01,

+

\amp, 0.02

+

);

+


+

// prepare (current) Environment

+

// give low overtones more weight

+


+

~a = (1..8); 

+

~w = (8..1).cubed.normalizeSum; 

+

)

+


+

x = p.play;

+


+


+

// reverse overtone weights

+


+

~w = (1..8).cubed.normalizeSum; 

+


+


+

// replace with Pattern for weights,

+

// PLseq taken as repeats defaults to inf

+


+

~w = Pstutter(50, PLseq([(8..1), (1..8)].collect { |x| x.cubed.normalizeSum }));

+


+


+

// replace arrays (must be sufficiently long)

+


+

~a = (3..10);

+

~a = (5..12);

+


+

~a = (1,3..15);

+

~a = (1,4..22);

+

~a = (1,5..29);

+

~a = (1,6..36);

+

~a = (2,7..37);

+

~a = (3,8..38);

+

~a = (4,9..39);

+

~a = (5,10..40);

+


+

x.stop;

+


+


+ + diff --git a/Help/PLx and live coding with Strings.html b/Help/PLx and live coding with Strings.html new file mode 100644 index 0000000..1d113cd --- /dev/null +++ b/Help/PLx and live coding with Strings.html @@ -0,0 +1,778 @@ + + + + + + + + + + + +

PLx and live coding with Strings PLx patterns as placeholders for sequencing with letters

+


+

Part of: miSCellaneous

+


+

See also: PLx suite, PsymNilSafe, PLbindef, PLbindefPar, EventShortcuts

+


+


+

Strings and Chars as high-level representations for musical objects can be used for sequencing with very condensed syntax. This is already possible with standard patterns like Pseq etc. – PLx list patterns fit this concept as their referenced Arrays/Strings can be replaced on the fly. Examples below also use EventShortcuts to minimize typing.

+


+

WARNING: Sequencing with infinite Patterns/Streams has always the potential of hangs. E.g. Psym hangs if all referenced pattern return nil (SC 3.7.2). Here convenience method 'symplay' is suggested: it employs PsymNilSafe, its method 'embedInStream' performs a check like in James Harkins' PnNilSafe from ddwPatterns quark (which can't be used directly this case). 'symplay' thus avoids hangs of that type, see Ex. 2b.

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

(

+

// synthdefs to play with, use of EventShortcuts

+


+

SynthDef(\noise, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1|

+

    var sig = { WhiteNoise.ar } ! 2;

+

    sig = BPF.ar(sig, freq, rq) *

+

        EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

+

        (rq ** -1) * (250 / (freq ** 0.8));

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

SynthDef(\sin, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

    var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2;

+

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+


+

SynthDef(\saw, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

    var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2;

+

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

EventShortcuts.on;

+

)

+


+


+

Ex.1) Straight usage with finite Patterns and Events

+


+

(

+

// use EventShortcuts

+


+

EventShortcuts.on;

+

EventShortcuts.postAll;

+


+

// base Pbind

+


+

~x = Pbind(\d, 0.1, \i, \sin);

+


+

// chars for the string, event patterns or events

+


+

~a = Pbind(\m, Pseries(60, 2, 4)) <> ~x;

+

~b = Pbind(\m, Pseries(80, -2, 8)) <> ~x;

+

~c = (i: \saw, m: [95, 96, 97], d: 0.8);

+


+

// define sequence

+


+

~p = "aab"

+

)

+


+

// symplay wraps into a PsymNilSafe

+

// PLseq defaults to repeats = inf and refers to 'p' in current Environment,

+

// thus the String "aab", the EventStreamPlayer should also get a name for start/stop,

+

// in this case we take the same letter (p) as interpreter variable

+


+

p = PLseq(\p).symplay

+


+


+

// replace String of PLseq

+


+

~p = "aacb"

+


+


+

// use of basic String operations

+


+

~p = ~p ++ "cc"

+


+

~p = ~p.reverse

+


+


+

(

+

// new char and sequence

+


+

~d = Pbind(\m, Pwhite(60, 90, 2), \i, \noise) <> ~x;

+

~p = "adadcb";

+

)

+


+

// replace String

+


+

~p = "bbcbcad"

+


+


+

// new chars

+

// the Function as first arg of Pseries is evaluated with every embedding,

+

// thus movements up and down start on different pitches

+


+

(

+

~a = Pbind(\m, Pseries({ rrand(60, 75) }, 7, 4)) <> ~x;

+

~b = Pbind(\m, Pseries({ rrand(85, 95) }, -5, 8)) <> ~x;

+

~c = Pbind(\i, \noise, \m, Pn((95..100), 2)) <> ~x

+

)

+


+


+

// modify ~b

+


+

~b = Pbind(\i, PLrand([\sin, \saw])) <> ~b;

+


+


+

// modify list, loop goes on

+

// evaluate several times, compare sound and posted String

+


+

~p = ~p.scramble

+


+

p.stop

+


+


+


+

Ex.2) Repeated embedding

+


+

// Control of embedding resp. the number of Events, that one Patterns should produce when played,

+

// is a subtle topic. A basic distinction is whether an embedded sequence should be produced with 

+

// desired behaviour from begin to end (2a) or it should be paused and resumed (2b). 

+

// The latter is the classical behaviour of Streams, but it can be mimiced with PSx stream patterns. 

+

// In any case embedding can be done with varying length, e.g. by defined sequences or by interaction.

+


+


+

Ex.2a) Embedding without continuation

+


+

(

+

// base Pbinds

+

~x = Pbind(\d, 0.15, \i, \sin, \rel, 1.5, \a, 0.02);

+

~y = Pbind(\d, Pn(0.9, 1), \i, \saw, \att, 0.8, \rel, 4, \a, 0.006);

+


+

// variable for repeats arg

+

~ar = 4;

+


+

// descending sequence, note the repeats arg: as with the start arg

+

// the Function is evaluated with every embedding,

+

// ~ar can be a Stream or a value

+

~a = Pbind(\m, Pseries({ rrand(75.0, 95) }, -7, { ~ar.next })) <> ~x;

+


+

// chords without octave doubling

+

~b = Pbind(\m, Pfunc { { [48, 60, 72].choose } ! 9 + (0..12).scramble.drop(3) }) <> ~y;

+


+

// define sequence

+

~p = "ba"

+

)

+


+


+

p = PLseq(\p).symplay

+


+

// change to other repeats number

+


+

~ar = 6

+


+

// make it a sequence with a Stream

+


+

~ar = PLseq([2, 2, 4]).asStream  // or shorter: PLseq([2, 2, 4]).iter

+


+


+

// go back to num and change String

+


+

~ar = 3

+


+

~p = "baaa"

+


+

~p = "baabaaaa"

+


+


+

p.stop

+


+


+


+

Ex.2b) Embedding with continuation

+


+

// This can be done by feeding a stream into a pattern, either directly or with PSx

+


+

(

+

// base Pbind

+

~x = Pbind(\d, 0.2, \rel, 0.5, \a, 0.03);

+


+

// Streams to be continued

+


+

~as = (Pbind(\m, Pn(Pseries(60, 1, 16)), \i, \saw) <> ~x).asStream;

+

~bs = (Pbind(\m, Pn(Pseries(90, -1, 16)), \i, \sin) <> ~x).asStream;

+


+

// variable for repeats args

+

~ar = 4;

+

~br = 4;

+


+

// for getting next values of an event stream we must pass an empty event as arg '.next(())'

+

~a = Pfuncn({ ~as.next(()) }, { ~ar.next });

+

~b = Pfuncn({ ~bs.next(()) }, { ~br.next });

+


+

// define sequence

+

~p = "ab"

+

)

+


+


+

p = PLseq(\p).symplay

+


+


+

// change repeats

+


+

~ar = 2;

+

~br = 1;

+


+


+

// this causes ~a to produce nils, only ~b still returns events

+


+

~ar = nil;

+


+

// This stops the player.

+

// Note that this kind of nil-detection works because 'symplay' employs PsymNilSafe.

+

// A construct like Psym(Pseq("ab", inf), ...).play would hang in that case

+


+

~br = nil;

+


+

// Note that this is different from the case, when the dictionary's key itself is nil.

+

// Then we get silent events, that also would't cause a hang with Psym(Pseq("ab", inf), ...).play

+


+


+

// evaluate above code starting from ~x = ... again and run

+


+

p = PLseq(\p).trace.symplay

+


+

// now we get a rest event

+


+

~a = nil

+


+

// player keeps running silently, stop explicitely

+


+

~b = nil

+


+

p.stop

+


+


+

// same as above written with PSx stream patterns

+


+

(

+

// base Pbind

+

~x = Pbind(\d, 0.2, \rel, 0.5, \a, 0.03);

+


+

// variable for repeats args

+

~ar = 4;

+

~br = 4;

+


+

~a = PS(Pbind(\m, Pn(Pseries(60, 1, 16)), \i, \saw) <> ~x, { ~ar.next });

+

~b = PS(Pbind(\m, Pn(Pseries(90, -1, 16)), \i, \sin) <> ~x, { ~br.next });

+


+

// define sequence

+

~p = "ab"

+

)

+


+


+

p = PLseq(\p).symplay

+


+

// change repeats

+


+

~ar = 2;

+

~br = 1;

+


+

p.stop;

+


+


+

Ex.3) Parallel embedding

+


+

// This can e.g. be done with Ppar, Ptuple or Pspawner, which is most flexible.

+

// There's a tiny isssue here in combination with EventShortcuts, duration keys

+

// should be in full length (You could apply method 'eventShortcuts' inside Ppar,

+

// but that's even more typing in that case, so we just write 'dur' instead of 'd').

+


+

(

+

// base Pbind

+


+

~x = Pbind(\dur, 0.1, \i, \sin);

+

~y = Pbind(\dur, 0.05, \i, \sin);

+


+

// chars for the String, event patterns or events

+


+

~a = Pbind(\m, Pseries({ rrand(60.0, 65) }, 1, 8)) <> ~x;

+

~b = Pbind(\m, Pseries({ rrand(85.0, 95) }, -1, 8)) <> ~y;

+


+

~c = Ppar([~a, ~b]);

+


+

~d = (i: \saw, m: [95, 96, 97], d: 0.8);

+


+

// define sequence

+


+

~p = "accd"

+

)

+


+


+

p = PLseq(\p).symplay

+


+

// equivalent with Pspawner

+


+

~c = Pspawner { |sp| sp.par(~a); sp.par(~b) };

+


+


+

// with Pspawner you have precise control over embedding of subsequences

+


+

~c = Pspawner { |sp| sp.par(~a); 4.do { sp.par(~b) } };

+


+


+

~e = Pspawner { |sp| sp.par(~a); 3.do { sp.seq(~b) } };

+


+

~p = "acde"

+


+

p.stop

+


+


+


+

Ex.4) Use of other PLx list patterns

+


+

// We can keep the String constant and switch to different PLx list patterns.

+


+

(

+

// base Pbind

+


+

~x = Pbind(\d, 0.1, \i, \saw);

+

~y = Pbind(\d, 0.2, \i, \sin);

+


+

// chars for the String, event patterns or events

+


+

~a = Pbind(\m, Pseries(60, Pwhite(1.0, 7.0), 4)) <> ~x;

+

~b = Pbind(\m, Pseries(80, Pwhite(1.0, 7.0), 4)) <> ~y;

+


+

~c = ~x <> ~b;

+

~d = ~y <> ~a;

+


+

~e = Pbind(\i, \noise, \m, Pn(70, 2)) <> ~x;

+


+

// define sequence

+


+

~p = "abcde"

+

)

+


+


+

// we need another proxy in that case, take general PL

+


+

~l = PLseq(\p);

+


+

p = PL(\l).symplay;

+


+


+

// scramble sequence and keep

+


+

~l = PLshuf(\p);

+


+


+

// scramble with every loop

+


+

~l = PLshufn(\p);

+


+


+


+

// weighted random

+


+

~l = PLwrand(\p, [4, 1, 3, 1, 1]/10);

+


+

p.stop;

+


+


+


+

Ex.5) PLbindef and PLbindefPar

+


+

// High-level control of Strings can be combined with replacing key streams with PLbindef/PLbindefPar

+


+


+

Ex.5a) PLbindef

+


+

(

+

// base PLbindef, continued embedding with PS as in Ex. 2b

+


+

~x = PS(PLbindef(\y, \dur, 0.1, \i, \noise, \rq, 0.5, \att, 0.05, \rel, 0.1, \a, 0.05));

+


+

// chars for the String, event patterns or events

+


+

~a = Pbind(\m, Pn(75, 1)) <> ~x;

+

~b = Pbind(\m, Pn(80, 1)) <> ~x;

+


+

~c = Ppar([~a, ~b]);

+


+

// define sequence

+


+

~p = "ab"

+

)

+


+

p = PLseq(\p).symplay

+


+


+

// update PLbindef's rq and amplitude with patterns

+


+

~y.rq = PLseq((100..1).mirror / 1000)

+


+

~y.a = PLseq((20..80).mirror / 1000)

+


+


+

// update String

+


+

~p = "c"

+


+

~p = "ababccc"

+


+


+

// stop and cleanup

+


+

p.stop

+


+

PLbindef(\y).remove

+


+


+


+

Ex.5b) PLbindefPar

+


+

(

+

// data base for two PLbindefPars, continued embedding with PS as in Ex. 2b

+


+

~w = [\dur, 0.1, \i, \noise, \rq, 0.5, \att, 0.05, \rel, 0.1, \a, 0.02];

+


+

~chord = (46, 53..95);

+


+

~a = PS(PLbindefPar(\u, 7, \m, ~chord, *~w), 1);

+

~b = PS(PLbindefPar(\v, 7, \m, ~chord + 2, *~w), 1);

+


+

// define sequence

+


+

~p = "ab"

+

)

+


+

p = PLseq(\p).symplay

+


+


+

~u.rq = 0.005

+


+

~v.i = \saw

+


+


+

// evolving changes

+


+

~u.rq = PLseq((100, 95..5).mirror / 1000)

+


+

~v.a = Pseg(PLseq([0.01, 0.04]), Pwhite(4, 7))

+


+


+

// change sequence per String

+


+

~p = "aabb"

+


+

~p = "aabbabab"

+


+

~p = "aabbcababc"

+


+

~p = "aaaab"

+


+


+

// fade out

+


+

~v.a = Pseg(Pseq([0.02, 0]), 20)

+


+

~u.a = Pseg(Pseq([0.02, 0]), 20)

+


+


+

// stop and cleanup

+


+

(

+

p.stop;

+

PLbindef(\u).remove;

+

PLbindef(\v).remove;

+

)

+


+


+


+

Ex.6) String sequencing with PbindFx

+


+

// Control with Strings can be thought in many ways.

+

// With effects one can e.g. use different Strings for src and fx sequencing.

+


+


+

Ex.6a) PbindFx

+


+

// boot server with extended resources for PbindFx

+


+

(

+

s.options.numPrivateAudioBusChannels = 1024;

+

s.options.memSize = 8192 * 16;

+

s.reboot;

+


+

// fx synths

+


+

SynthDef(\resample, { |out = 0, in, mix = 1, amp = 1,

+

resampleRate = 22050, lagTime = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = Latch.ar(inSig, Impulse.ar(resampleRate)); // resampling

+

// lag in milliseconds for smoothing

+

sig = sig.lag(lagTime * 0.001);

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+

SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000,

+

    cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1|

+

    var sig, inSig = In.ar(in, 2);

+

    sig = RLPF.ar(

+

        inSig,

+

        LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi),

+

        rq,

+

        amp

+

    ).softclip;

+

    Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+


+

// src synths

+


+

SynthDef(\noise, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1|

+

    var sig = { WhiteNoise.ar } ! 2;

+

    sig = BPF.ar(sig, freq, rq) *

+

        EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

+

        (rq ** -1) * (250 / (freq ** 0.8));

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+

SynthDef(\saw, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

    var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2;

+

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

    OffsetOut.ar(out, sig);

+

}).add;

+


+


+

// prepare EventShortcuts for additional keys

+


+

EventShortcuts.addOnBase(\default, \fxExs, (

+

dec: \decayTime,

+

cd: \cleanupDelay,

+

cf: \cutOffMoveFreq,

+

fxo: \fxOrder,

+

rs: \resampleRate

+

), true);

+


+

EventShortcuts.makeCurrent(\fxExs);

+


+

EventShortcuts.on;

+

)

+


+


+


+

(

+

// base Pbind

+

// PbindFx's fxOrder (short: fxo) syntax employed by Symbol mapping:

+

// u: no fx, x: resample, y: wah, z: resample and wah in sequence

+


+

~r = Pbind(

+

\fxo, Psym(PLseq(\fx), (u:0, x:1, y:2, z:[1, 2])),

+

\a, 0.07,

+

\att, 0.01,

+

\rel, 0.3,

+

    \cd, Pkey(\att)+ Pkey(\rel) + 0.1

+

);

+


+

// PS to embed

+


+

~a = PS(Pbind(\i, \saw, \d, 0.1, \m, PLseq([60, 60, 60, 62])) <> ~r, 1);

+


+

~b = PS(Pbind(

+

\i, \noise,

+

\d, 0.2,

+

\m, PLseq([Pwhite(41.0, 50),Pwhite(71.0, 80)]),

+

\rq, 0.01

+

) <> ~r, 1);

+


+

// no cleanupDelay defaults for fxs as they don't delay

+

q = PbindFx(PsymNilSafe(PLseq(\p)), [

+

    \fx, \resample,

+

\mix, 0.3,

+

\rs, Pwhite(200, 500),

+

\a, 1

+

],[

+

   \fx, \wah,

+

\mix, 0.8,

+

\cf, Pwhite(0.5, 5),

+

\a, 0.7

+

]);

+

)

+


+

// start instrument sequence with no fx

+


+

(

+

~fx = "u";

+

~p = "aab";

+

p = q.play;

+

)

+


+

// fx sequences

+


+

~fx = "uuxy"

+


+

~fx = "uyuxzzzz"

+


+


+

~p = "b"

+


+

p.stop

+


+


+


+

Ex.6b) PbindFx and PLbindef

+


+

// There's more fine-tuned control if we can replace key streams also

+


+

(

+

// base pairs for PLbindef

+


+

~r = [

+

\fxo, Psym(PLseq(\fx), (u:0, x:1, y:2, z:[1, 2])),

+

\a, 0.07,

+

\att, 0.01,

+

\rel, 0.3,

+

    \cd, Pkey(\att) + Pkey(\rel) + 0.001

+

];

+


+

// PS to embed

+


+

~a = PS(PLbindef(\aa, \i, \saw, \d, 0.1, \m, PLseq([60, 60, 60, 62]), *~r), 1);

+


+

~b = PS(PLbindef(\bb,

+

\i, \noise,

+

\d, 0.2,

+

\m, PLseq([Pwhite(41.0, 50),Pwhite(71.0, 80)]),

+

\rq, 0.1,

+

*~r

+

), 1);

+


+

// as we have defined fx chars 'x' and 'y' above,

+

// choose related names 'xx' and'yy' for PLbindefs

+


+

q = PbindFx(PsymNilSafe(PLseq(\p)),

+

PLbindef(\xx,

+

    \fx, \resample,

+

\mix, 0.3,

+

\rs, Pwhite(200, 500),

+

\a, 1

+

),

+

PLbindef(\yy,

+

   \fx, \wah,

+

\mix, 0.8,

+

\cf, Pwhite(0.5, 5),

+

\a, 0.7

+

)

+

);

+

)

+


+

// start with no fxs

+


+

(

+

~fx = "u";

+

~p = "aab";

+

p = q.play;

+

)

+


+

// fx sequence

+


+

~fx = "uuxy"

+


+


+

// midinote for ~a and ~b

+


+

~aa.m = [50, 52]

+


+

~bb.m = Pwhite(80, 96)

+


+


+


+

// switch to single fx resample for testing changes of its control streams

+

// resample, test rate

+


+

~fx = "x"

+


+

~xx.rs = Pwhite(1000, 3000)

+


+


+

// same with wah

+


+

~fx = "y"

+


+

~yy.cf = 3

+


+


+

// src changes

+


+

~aa.rel = 0.1

+


+

~bb.rel = 0.5

+


+


+

// further playing

+


+

~fx = "xxy"

+


+

~p = "aabaabaaaabb"

+


+


+

~aa.m = [38, 40]

+


+

~bb.rq = 0.7

+


+

~fx = "z"

+


+


+

// fade out

+


+

~aa.a = Pseg(Pseq([0.04, 0]), 20)

+


+

~bb.a = Pseg(Pseq([0.04, 0]), 20)

+


+


+

// cleanup

+


+

p.stop

+


+

Pdef.removeAll

+


+


+


+


+


+


+


+


+


+ + diff --git a/Help/PLx suite.html b/Help/PLx suite.html new file mode 100755 index 0000000..a4d8eb5 --- /dev/null +++ b/Help/PLx suite.html @@ -0,0 +1,606 @@ + + + + + + + + + + + +

PLx, a dynamic scope Pattern suite dynamic scope Pattern variants 

+


+

Part of: miSCellaneous

+


+

See also: Event patterns and Functions, PLx and live coding with Strings, VarGui, VarGui shortcut builds, Buffer Granulation, Live Granulation

+


+


+

Environmental variables within functions can act as placeholders for values, but also Patterns itself. So Patterns including functional code (e.g. Pfunc, Plazy, Pcollect) can, thanks to dynamic scoping, turn into different Streams, depending on the environment where streamifying happens (Event patterns and Functions). This can be used for getting a whole parametrized family of Streams / EventStreamPlayers from a single pattern definition. Other applications are on-the-fly replacements and gui control of parameters of Pbinds / EventStreamPlayers with VarGui. Nevertheless constructs with Plazy, Pfunc etc. require some redundant typing which is saved by PLx Patterns (lazy evaluation). They are either plain wrapper classes or include variant implementations for the most common pattern types and deliver a more or less unified way for the described kind of placeholding.

+

Unification however can only be approximated as Patterns, even those of one type (e.g. ListPatterns), are defining different behaviour: not all inputs of a source pattern x can be dynamically updated (e.g. the start of a Pseries), not all of them are allowed to be Patterns itself. Implementation and usage may differ a bit from class to class. If there is no PLx implementation of a source Pattern class you can use PL as a general pattern placeholder input, see PL help file for an example. PLbindef and PLbindefPar allow key stream replacements in shortcut object prototyping syntax.

+

NOTE: PLx patterns follow a paradigm of immediate replacement. There are cases though where you might prefer to finish streams or substreams before replacement, especially when syncing comes into play, for these options consider PLn and the cutItems arg of PLx list patterns.

+

A word of caution: feeding a looped process with an invalid input has always the potential to lead to hangs. See PsymNilSafe and PLx and live coding with Strings for some remarks on that.

+


+


+


+

PLx value and event pattern classes

+


+

PL, PLn, PLseq, PLser, PLrand, PLxrand, PLwrand, PLshuf, PLshufn, PLslide, PLtuple, PLwalk, PLswitch, PLswitch1

+


+


+

PLx value pattern classes

+


+

PLwhite, PLlprand, PLhprand, PLmeanrand, PLbrown, PLgbrown, PLseries, PLgeom, PLbeta, PLcauchy, PLgauss, PLpoisson, PLexprand

+


+


+

PLx filter pattern classes

+


+

PLnaryop, PLnaryFunc, PLIdev

+


+


+

PLx subclasses of Pdef

+


+

PLbindef, PLbindefPar

+


+


+


+


+

Example 1a: ListPatterns placeholder constructs with Plazy

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// This is how dynamic scope placeholding of a ListPattern 

+

// could be done with Plazy,

+

// Pn defaults to repeats = inf.

+


+

(

+

p = Pn(Plazy { Pseq(~a, 1) }); 

+


+

~a = (60..70);

+


+

x = Pbind(

+

\midinote, p,

+

\dur, 0.2

+

).play;

+

)

+


+


+

// First drawback: replacing of a new list doesn't have an immediate effect

+

// as the old list is looped through before.

+


+

// Try evaluating this before the end of the original loop.

+


+

~a = (75..84) ++ Pseq([85,86], 10);

+


+


+

// Second drawback: replacing of a single pattern element, 

+

// which corresponds to a stream just being embedded,

+

// doesn't have an immediate effect

+

// as this embedding is finished before.

+


+

// Try evaluating this during the trill, it doesn't have an 

+

// effect before the next loop.

+


+

~a[10] = 91;

+


+

x.stop;

+


+


+

Similar placeholder constructs with Pcollect and Pfunc have similar drawbacks concerning replacement. However, this type of "delayed replacement" might be wanted in some cases and is also possible with PLx patterns, see PLn and the cutItems arg of PLx list patterns.

+


+


+


+

Example 1b: PLx implementation of ListPatterns

+


+


+

PLx Patterns take symbols as input. Derived Streams get the values of the Environment of streamification.

+


+

(

+

p = Pbind(

+

\midinote, PLseq(\a),

+

\dur, 0.2

+

);

+


+

~a = (60..70);

+


+

y = p.play;

+

)

+


+


+

// replacement of the whole list has an immediate effect now,

+

// with Pseq the loop starts with the new list

+


+

~a = (75..84) ++ Pseq([85,86], 10);

+


+


+

// replacing a single element also has an immediate effect 

+

// (as PLseq's cutItems arg defaults to true),

+

// try evaluating during the trill

+


+

~a[10] = 91;

+


+

y.stop;

+


+


+

// PLx ListPattern implementations can also act as 

+

// ordinary ListPatterns if args are not Symbols. 

+

// Difference: repeats arg defaults to inf, 

+

// so you save typing in this case,

+

// but don't apply .all to Streams derived from such Patterns !

+


+

(

+

x = Pbind(

+

\midinote, PLseq((60..70)),

+

\dur, 0.2

+

).play;

+

)

+


+

x.stop;

+


+


+


+

Example 1c: PLx implementation of Non-ListPatterns 

+


+


+

// an explicit definition with Plazy

+


+

p = Pwhite(Pn(Plazy { ~lo }), Pn(Plazy { ~hi }), { ~r });

+


+


+

// similarily done implicitely by PLwhite

+


+

q = PLwhite(\lo, \hi, \r); 

+


+


+

// streamify in envir

+


+

e = (lo: 60, hi: 90, r: 30);

+


+

e.use { q.asStream.all };

+


+


+

// reset repeats to inf and streamify again

+


+

e.use { ~r = inf; x = Pbind(\midinote, q, \dur, 0.2).play };

+


+


+

// replace lower bound by pattern

+


+

e.lo = PLseq([60, 90]);

+


+

x.stop;

+


+


+


+

Example 1d: Plain PL

+


+

A general placeholder that can be updated after instantiation.

+

Its repeats arg defaults to inf.

+


+


+

PL(\a, \r);

+


+

// roughly equivalent to

+


+

Pn(Plazy { ~a }, { ~r });

+


+


+

e = (a: Pseq((60, 62.5..80)));

+


+

e.use { x = Pbind(\midinote, PL(\a), \dur, 0.1).play }

+


+


+

// note that with this replacement new scrambles are chosen 

+

// repeatedly because Pshuf's repeats arg defaults to 1.

+


+

e.a = Pshuf((80, 78.5..65));

+


+


+

// fixed reordering

+


+

e.a = Pshuf((80, 78.5..65), inf);

+


+

e.a = PLshuf((80, 78.5..65));

+


+


+

x.stop;

+


+


+

PL can also be used with Patterns which don't have a PLx implementation.

+

See PL for an example.

+


+


+


+

Example 2: Playing in different Environments 

+


+


+

// Pbind to be streamified differently in different environments

+


+

(

+

p = Pbind(

+

\midinote, PLseq(\a),

+

\dur, PL(\d)

+

);

+


+

e = (a: (67..72), d: 0.1);

+

f = (a: (85..90), d: 0.2);

+

)

+


+


+

// start in sync or individually

+


+

x = e.use { p.play(quant: 0.2) };

+

y = f.use { p.play(quant: 0.2) };

+


+


+

// replacement of array elements ...

+


+

e.a[0] = 95;

+

f.a[0] = [75, 79];

+


+


+

// ... which may also be Patterns

+


+

f.a[0] = Pseq([75, 79], 3);

+


+


+

// replacing the whole array

+


+

f.a = (83..80);

+


+

f.a = (79..75) +.t [0, 3.5];

+


+

e.a = [Pseq([63, 65.5], 4), 85, 87];

+


+

x.stop;

+

y.stop;

+


+


+


+

Example 3: Use with VarGui 

+


+

// basic form of a step sequencer (amp defaults to 0 in associated global ControlSpec)

+


+

(

+

\default.pVarGui(

+

ctrBefore: [\a, [0, 6, \lin, 1, 3] ! 8],

+

pBefore: [\degree, PLseq(\a) ]

+

).gui;

+

)

+


+

See VarGui and VarGui shortcut builds for further examples.

+


+


+


+

Example 4: The repeats arg

+


+


+

PLx Patterns' repeats arg defaults to inf. This makes sense in situations where you want to go on replacing items on the fly. If a PLx Patterns is itself enclosed you may want to set it to a different value. Anyway the resulting number of repeats is the product of outer and inner repeats.

+


+


+


+

// PL(\a) defaults to repeats = inf

+

// Pshuf defaults to repeats = 1, is embedded repeatedly and 

+

// so it continues producing new permutations (like Pshufn)

+


+

(

+

p = Pbind(

+

\midinote, PL(\a),

+

\dur, 0.15

+

);

+


+

~a = Pshuf((60..63));

+

)

+


+


+

x = p.play;

+


+


+

// same effect, but this is normal Pshufn behaviour

+


+

~a = Pshufn((60..63));

+

~a = Pshufn((60..63), inf);

+


+

x.stop;

+


+


+

// PL(\a, 2) demands inner repeats = inf for endless running

+


+

(

+

p = Pbind(

+

\midinote, PL(\a, 2),

+

\dur, 0.15

+

);

+


+

~a = Pshuf((60..63), inf);  

+

)

+


+

// now normal Pshuf behaviour

+


+

x = p.play;

+


+


+

// same achieved with PLshuf((60..63)) as it defaults to repeats = inf

+


+

~a = PLshuf((60..63));

+


+


+

// stop with a Pseq which defaults to repeats = 1, played twice because of PL(\a, 2)

+


+

~a = Pseq((70..65));

+


+


+


+

Example 5a: Updating input of N-ary operators

+


+

One may want to have the choice to update inputs of N-ary operators applied to Patterns too. A common case is clipping. Say you have a Pcauchy pattern (distribution with a relatively high number of outliers) and want to dynamically change its mean value and clip bounds.

+


+


+

// PLcauchy allows updating mean and spread arg (also with patterns)

+

// the collect function will read from envir variables with every new event

+


+

(

+

p = Pbind(

+

\midinote, PLcauchy(\m, \s).collect(_.clip(~lo, ~hi)),

+

\dur, 0.1

+

);

+

)

+


+

// define the environment

+


+

e = (m: 75, s: 1, lo: 60, hi: 90);

+


+

e.use { x = p.play };

+


+

// update upper bound to mean value

+


+

e.hi = 75;

+


+

x.stop;

+


+


+

// above pitch pattern could be written explicitely too with Pnaryop 

+


+

Pnaryop(\clip, PLcauchy(\m, \s), [Pfunc { ~lo }, Pfunc { ~hi }])

+


+

// more powerful: PL allows updating with patterns

+


+

Pnaryop(\clip, PLcauchy(\m, \s), [PL(\lo), PL(\hi)])

+


+

// even shorter: the PLnaryop class expands to the above

+


+

PLnaryop(\clip, PLcauchy(\m, \s), [\lo, \hi])

+


+


+

// In the simple case of updating clip bounds with values 

+

// maybe one would rather use the version with collect.

+


+

// But with Pnaryop you can pass a list of arbitrary patterns as arglist

+

// and with PLnaryop you can dynamically update with arbitrary patterns -

+

// both can be used for more differentiated control of clip bounds

+

// (or args of any other N-ary operator or Function).

+

// Also the source pattern can be replaced.

+


+

(

+

p = Pbind(

+

\midinote, PLnaryop(\clip, \pat, [\lo, \hi]),

+

\dur, 0.1

+

);

+

)

+


+

// define the environment and play

+


+

e = (pat: PLcauchy(\m, \s), m: 75, s: 1, lo: 60, hi: 90);

+


+

e.use { x = p.play };

+


+


+

// compare distributions

+


+

e.pat = PLgauss(\m, \s);

+


+


+

// switch back to Cauchy with more outliers

+


+

e.pat = PLcauchy(\m, \s);

+


+


+

// update bounds, lo bound 85 is mostly gone below, 

+

// so nearly every second event has this midinote

+

// vice versa with hi bound 65

+


+

e.lo = PLseq([60, 85]);

+


+

e.use { ~lo = 60; ~hi = PLseq([65, 90]) };

+


+


+

// clipping to a window that loops through the distribution:

+

// values are taking more or less the wandering clip bounds if lo or hi, 

+

// but are rather randomly distributed between clip bounds around the mean value

+


+

e.use { ~lo = PLseq((50..95)); ~hi = ~lo + 10 };

+


+

x.stop;

+


+


+

For replacing operators dynamically take PLnaryFunc with the operator wrapped into a Function.

+


+


+


+

Example 5b: Updating input of N-ary Functions

+


+

Self-defined functions can be used as with PLnaryop.

+


+


+

(

+

p = Pbind(

+

\midinote, PLnaryFunc(\f, \src, [\b, \c]),

+

\dur, 0.1

+

);

+

)

+


+

// define Environment

+


+

(

+

e = ();

+


+

e.src = Pstutter(3, PLseq((60, 70..90)));

+

e.b = PLseq((0..2));

+

e.c = 0;

+


+

e.f = { |x,y,z| x + y + z };

+

)

+


+


+

// run

+


+

e.use { x = p.play };

+


+


+

// replace function input

+


+

e.b = PLseq((0..1));

+


+

e.c = [-5, 0];

+


+

e.c = PLseq([[-5, 0], [0, 3]]);

+


+

e.b = PLshuf((0..3));

+


+


+

// replace function

+


+

e.f = { |x,y,z| x + (y * 1.2) + z };

+


+

e.f = { |x,y,z| x + y + (z * 1.7) };

+


+


+

x.stop;

+


+


+


+

Example 6: PLbind / PLbindef

+


+


+

// start PLbindef, an special Environment of that name is created for setting

+


+

p = PLbindef(\a, \midinote, 70).play

+


+

// set in it

+


+

~a.midinote = Pwhite(60, 80)

+


+

~a.midinote = ~a.midinote + [0, 4]

+


+

~a.dur = 0.25

+


+

// cleanup

+


+

(

+

p.stop;

+

p.remove;

+

)

+


+


+

// PLbindef sprouts parallel processes

+

// start 4 in unisono

+


+

p = PLbindefPar(\b, 4, \midinote, 60, \dur, 2, \amp, 0.03).play

+


+

// set single streams

+


+

~b[3].midinote = Pwhite(70, 70.5)

+


+

~b[3].dur = 0.05

+


+


+

~b[2].midinote = Pwhite(65.0, 67)

+


+

~b[2].dur = 0.1

+


+


+

// use method value for setting

+


+

~b.([0, 1], \midinote, Pwhite(50, 60), \dur, 0.1)

+


+


+

// general setting, now we probably aren't in sync

+


+

~b.dur = 2

+


+


+

// reset syncs

+


+

p.reset

+


+


+

// stop and cleanup

+


+

(

+

p.stop;

+

p.remove;

+

)

+


+


+


+ + diff --git a/Help/PLxrand.html b/Help/PLxrand.html new file mode 100755 index 0000000..55eff9c --- /dev/null +++ b/Help/PLxrand.html @@ -0,0 +1,187 @@ + + + + + + + + + + + +

PLxrand dynamic scope Pxrand variant 

+


+

Part of: miSCellaneous

+


+

Inherits from: PL_ListPattern

+


+

Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See PLx suite.

+


+

See also: Pxrand, PLrand, PLwrand, PLshuf, PLshufn, Event patterns and Functions, VarGui, VarGui shortcut builds

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, cutItems, envir)

+

+

Creates a new PLxrand object.

+

+

list - Symbol or Pxrand list arg. 

+

If a Symbol is passed, list can be assigned to an envir variable later on.

+

This lists's elements can be dynamically replaced by Patterns or Streams.

+

repeats - Symbol or Prand repeats arg. Defaults to inf.

+

If a Symbol is passed, repeats can be assigned to an envir variable later on.

+

cutItems - Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer.

+

If a Symbol is passed, cutItems can be assigned to an envir variable later on.

+

Determines if list items, which are Patterns or Streams themselves,

+

will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. 

+

The latter is the default behaviour (default value true).

+

For protecting whole lists from immediate replacements see PLn.

+

envir - Dictionary or one of the Symbols

+

\top, \t (topEnvironment), \current, \c (currentEnvironment).

+

Dictionary to be taken for variable reference. Defaults to \current.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// define Pattern and prepare Environments

+

+

(

+

p = Pbind(

+

\midinote, PLxrand(\a),

+

\dur, 0.2

+

);

+


+

e = (a: (67..72));

+

f = (a: (73..78));

+

)

+


+

// start together or not, but sync anyway

+


+

e.use { x = p.play(quant: 0.2) };

+

f.use { y = p.play(quant: 0.2) };

+


+


+

// replace array elements ...

+


+

f.a[0] = Pseq((0, 0.5..3) + 75);

+


+

e.a[0] = Pseq(60 - (0, 0.5..3));

+


+


+

// ... or arrays 

+


+

f.a = (72, 72.5..77);

+


+

e.a = [60];

+


+

e.a = [Pseq([60, 47.5]) + [0, 5], [61, 65.5], [80, 83.5]];

+


+

x.stop;

+

y.stop;

+


+


+


+

//////////////////////

+


+


+

// placeholder may also get lists of event patterns

+


+

(

+

p = PLxrand(\a);

+


+

~a = [ 

+

Pbind(

+

\midinote, Pwhite(60, 65, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(70, 75, 3),

+

\dur, 0.2

+

),

+

Pbind(

+

\midinote, Pwhite(80, 85, 3),

+

\dur, 0.2

+

)

+

];

+


+

x = p.play;

+

)

+


+


+

// replace array element

+


+

(

+

~a[0] = Pbind(

+

\midinote, Pwhite(70, 75, 3) + [0, 5],

+

\dur, 0.15

+

);

+

)

+


+


+

// replace whole array

+


+

(

+

~a = [ 

+

Pbind(

+

\midinote, Pxrand((60..65), 3),

+

\dur, 0.15

+

),

+

Pbind(

+

\midinote, Pxrand((85..95), 3),

+

\dur, 0.05

+

),

+

Pbind(

+

\midinote, Pxrand((70..80), 2),

+

\dur, 0.25

+

)

+

];

+

)

+


+

x.stop;

+


+ + diff --git a/Help/PS.html b/Help/PS.html new file mode 100755 index 0000000..f0121a3 --- /dev/null +++ b/Help/PS.html @@ -0,0 +1,291 @@ + + + + + + + + + + + +

PS (PStream, Pstream) Pattern that behaves like a Stream 

+


+

Part of: miSCellaneous

+


+

Inherits from: Plazy

+


+

See also: MemoRoutine, PSx stream patterns, PSdup, PSrecur, PSloop

+


+


+

In general Patterns are stateless. But e.g. for counted embedding in other Patterns the exception of stream-like behaviour is practical. PS might also be used in cases where Streams must not be passed to certain Patterns.

+


+

NOTE: Name and implementation of former Pstream has changed with miSCellaneous_v0.9, in compliance with other PSx patterns it's been renamed to PS / PStream, however for backwards compatibility Pstream will still work by subclassing.

+


+


+

Creation / Class Methods

+


+

*new (srcPat, length, bufSize, copyItems, copySets)

+

+

Creates a new PS object.

+

+

srcPat - Source pattern, might also be event pattern.

+

length - Number of output items, may be pattern or stream, defaults to inf.

+

bufSize - Size of buffer to store last values, defaults to 1.

+

copyItems - Determines if and how to copy items, which are which are either non-Sets or member of Sets. 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). 

+

Other values are interpreted as 0. Defaults to 0.

+

0: original item 

+

1: copy item

+

2: deepCopy item

+

copySets - Determines if to copy Sets (and hence Events). 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). 

+

Other values are interpreted as 0. Defaults to 1.

+

0: original Set 

+

1: copy Set

+

+

NOTE 1: The distinction of copying items and sets makes sense in the case of event streams.

+

Per default Events are copied (copySets == 1), not their values (copyItems == 0). 

+

By playing Events those are used to store additional data (synth ids, msgFuncs …) 

+

which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use 

+

MemoRoutine - copied Events will not contain this additional data. 

+

If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured 

+

then copying makes no sense, this is the normal case, so copyItems defaults to 0.

+

When going to alter the ouput, you might want to set copyItems to 1 for a PSx returning 

+

simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set

+

copySets to 1 and copyItems to 2 (an option copySets == 2 doesn't exist as

+

it would be contradictory in combination with copyItems < 2).

+


+

NOTE 2: Copy options concern copying into PS's buffer as well as 

+

the output of a Stream derived from the PS. When such a Stream is outputting copies 

+

this prevents unintended altering of items from srcPat. On the other hand

+

storing copies in PS's buffer prevents these from being altered unintendedly.

+


+


+

Instance Methods

+


+

lastValue

+

+

Last value stored in memoRoutine

+


+

lastValues

+

+

Array of last values stored in memoRoutine, latest value is first in array.

+

+

at (i)

+

+

Returns ith item of array of last values stored in memoRoutine

+

(keep in mind reversed order: last value first)

+

+

bufSize

+

+

Size of array of last values.

+


+

bufSeq(dropNils)

+

+

Returns items of the array lastValues, but in the order in which they appeared, i.e. 

+

latest value is first in array. If dropNils is true (default), nils will be rejected from the array.

+


+

srcPat, srcPat_(value)

+

+

Instance variable getter and setter methods. 

+


+

length, length_(value)

+

+

Instance variable getter and setter methods. 

+


+

lengthStream, lengthStream_(value)

+

+

Instance variable getter and setter methods. 

+

+

memoRoutine, memoRoutine_(value)

+

+

Instance variable getter and setter methods. 

+


+

count, count_(value)

+

+

Instance variable getter and setter methods.

+

Counts each call of next / value / resume / run on the memoRoutine. 

+

If several Streams are derived from one PS each call of next on a derived Stream

+

will be counted by the memoRoutine and thus by the PS.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

// PS used to store a sequence of 6 events

+


+

(

+

p = Pbind(

+

\midinote, Pwhite(60, 90, 6),

+

\dur, Prand([0.2, 0.4], inf)

+

);

+


+

// As the sequence ends with nil, nil is also stored in the buffer of PStream's MemoRoutine.

+

// Hence to store the whole sequence we must increase the buffer size by 1.

+


+

q = PS(p, bufSize: 7);

+


+

q.play;

+

)

+


+


+

// values are shifted, so latest are first

+


+

q.lastValues

+


+


+

// method bufSeq gives items in order in which they appeared, dropping nils by default

+


+

q.bufSeq

+


+


+

// repeat original sequence

+


+

Pseq(q.bufSeq).play

+


+


+

// play in reverse order, as Stream has been finished there's also a nil to be dropped

+


+

Pseq(q.lastValues.drop(1)).play

+


+


+

+

// counted embedding of value patterns

+

// PLx variants default to repeats = inf

+


+

(

+

p = PLseq([ 

+

PS(PLseq((60..65)), 3), 

+

PS(PLseq((80..90)), Pwhite(2,5)) 

+

]); 

+


+

x = Pbind(

+

\midinote, p,

+

\dur, 0.1

+

).play;

+

)

+


+

x.stop;

+


+


+

// counted embedding of event patterns

+


+

(

+

p = Pbind(

+

\midinote, PLseq((55..70)) + Pfunc { [0, [4,5].choose] },

+

\dur, 0.2

+

);

+


+

q = Pbind(

+

\midinote, PLseq((80..100)),

+

\dur, 0.05

+

);

+


+

x = PLseq([

+

PS(p, Pwhite(2,6)), 

+

PS(q, Pwhite(2,6)) 

+

]).play;

+

)

+


+

x.stop;

+


+


+

Keep in mind that repeated streamifying of a PS is just like resuming a Stream (yes, PS behaves like ... a Stream).

+

For getting a Stream to start at the beginning as defined by the Pattern enclosed by the PS,

+

you'd have to generate a new PS, e.g. by reevaluating its definition or wrapping 

+

it into a Function.

+


+


+

p = PS(Pseries(), 5);

+


+

// evaluate more than once

+


+

p.asStream.all;

+


+


+


+

// compare

+


+

q = { PS(Pseries(), 5) };

+


+

// evaluate more than once

+


+

q.value.asStream.all;

+


+


+


+

For recursively generating data see PSrecur. Referring to buffered last values of a PS can 

+

easily be done with method .at.

+


+


+

// canonical brown movement

+

// define 3 voices refering to a PS

+

// use separate PS to collect data

+

// plot

+


+

(

+

p = PS(Pbrown(65, 90, 2.1), inf, 16);

+

p.iter.nextN(16);

+


+

q = Pfunc { p[5] - 7 };

+

r = Pfunc { p[10] - 14 };

+

t = Pfunc { p[15] - 21 };

+


+

u = PS(Ptuple([p,q,r,t]), bufSize: 100);

+


+

a = Plotter().superpose_(true).plotMode_(\plines);

+

a.value = u.iter.nextN(100).flop

+

)

+


+

// playback stored pitches

+


+

(

+

v = Pbind(

+

\midinote, Pseq(u.bufSeq),

+

\dur, 0.2

+

).trace.play

+

)

+


+


+


+


+ + diff --git a/Help/PSPdiv.html b/Help/PSPdiv.html new file mode 100755 index 0000000..2985506 --- /dev/null +++ b/Help/PSPdiv.html @@ -0,0 +1,542 @@ + + + + + + + + + + + +

PSPdiv dynamic multi-layer pulse divider 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pspawner

+


+

See also: Pspawner, Sieves and Psieve patterns, Buffer Granulation, Live Granulation

+


+

PSPdiv controls the timing of one or several layers of event patterns by a single pulse pattern. In every layer single pulse durations or integer multiples ('division bases') of pulse durations can be divided by Integers or proportionally. For every layer the event pattern data can be given as event pattern or a function, which is generating an event pattern for every divisional operation. Division bases and divisions as well as the pulse itself can be controlled by patterns. 

+

PSPdiv is built on Pspawner and therefore allows sequential or parallel spawning: the type of embedding can be sequenced by a pattern for every divisional operation. So a single layer alone can produce a number of overlapping sequences.

+


+

Time division in space notation scheme with two layers and embedding of sequential type:

+


+

attachments/PSPdiv/PSPdiv_graph_1.png

+

+

A sequence of regular or irregular pulses is given as pattern of floats, these durations are marked as proportional spaces in the graphic. The sequence of divBases collects groups of pulses and divides them according to the sequence of divs. The resulting durations of each layer are again marked by proportional spacing, common entry points of layers are marked by emphasized vertical lines.

+


+

Special thanks to Ron Kuivila for developing Pspawner, it's such a versatile class !

+


+


+

Creation / Class Methods

+


+

*new (pulse, evPat, div, divBase, divType)

+

+

Creates a new PSPdiv object.

+

+

pulse - Duration or pattern of durations, given as beats. Defaults to 1.

+

+

evPat - An event pattern, a Function generating event patterns or: a SequenceableCollection thereof.

+

If an event pattern is given, the durations of the current division, calculated from current values of 

+

pulse, div and divBase, are inserted with the 'dur' key, there's no use in passing durations in

+

the event pattern in this case.

+

If a Function is given, it will be passed 4 arguments:

+

.) an array of durations of the current division, based on the following arguments

+

.) the current division number div

+

.) the current division base number divBase

+

.) the current division type divType ('seq' or 'par')

+

The Function should return the event pattern to be scheduled for this division. The typical

+

application would be using the array of durations in a Pseq with repeats = 1 as 'dur' value, 

+

though you are free to let it return any kind of event pattern.

+

Layers are slightly shifted, so that the event from a pattern of lower index is calculated

+

before an event from a pattern of higher index, if events are scheduled to happen at the same time.

+


+

div - Integer or array of array of numbers, determining a division of divBase * pulse beats or

+

a pattern returning such or: a SequenceableCollection thereof. Defaults to 1.

+

A single array of numbers is interpreted as indicator for several layers (see note below),

+

hence the doubled array is necessary for a proportional division.

+

+

divBase - Integer determining the number of multiples of pulse to be used as base of division or

+

a pattern returning such or: a SequenceableCollection thereof. Defaults to 1.

+


+

divType - Symbol 'seq' or 'par' or or a pattern returning such or: a SequenceableCollection thereof. 

+

Defaults to 'seq'. Determines the type of embedding according to Pspawner's convention.

+

+

NOTE: If one of the arguments evPat, div, divBase and divType is passed as SequenceableCollection of size > 1,

+

a multitude of layers is assumed and the other args are interpreted accordingly. To avoid ambiguities only one

+

size > 1 is allowed at maximum amongst these args (but more than one of them can be a SequenceableCollection

+

of this size). Consequently a proportional div arg for one layer must be passed in double brackets.

+


+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

Ex. 1:   Basic functionality with one layer

+


+


+

// test SynthDef

+


+

(

+

SynthDef(\varKlank, { |freq = 500, att = 0.01, rel = 0.1, sourceType = 0,

+

ringtime = 1, pan = 0, amp = 0.1|

+

var sig, source = Select.ar(sourceType, [

+

BrownNoise.ar(0.05),

+

Impulse.ar(0)

+

]);

+

sig = DynKlank.ar(`[freq * (1..8), amp * (8..1)/8, ringtime * (1!8)], source);

+

sig = Splay.ar(sig);

+

OffsetOut.ar(0, Balance2.ar(sig[0], sig[1], pan) *

+

EnvGen.ar(Env.perc(att, rel), doneAction: 2))

+

}).add;

+

)

+


+


+

(

+

// base pattern, no need to provide durations, as they are calculated from other PSPdiv args

+

p = Pbind(

+

\instrument, \varKlank,

+

\midinote, Pwhite(65, 90),

+

\amp, 0.1,

+

\att, Pwhite(0.005, 0.02),

+

\rel, 2,

+

\sourceType, 1,

+

\ringtime, 2,

+

\pan, Pwhite(-0.3, 0.3)

+

);

+

)

+


+


+


+

// div = 1, pulse not divided

+


+

x = PSPdiv(0.8, p, 1).play;

+


+

x.stop

+


+


+

// div as array of two items leads to expansion into two layers

+


+

x = PSPdiv(0.8, p, [2, 1]).play;

+


+

x.stop

+


+


+

// here div is interpreted as proportion for one layer

+


+

x = PSPdiv(0.8, p, [[3, 1]] ).play;

+


+

x.stop

+


+


+

// specifying per layer

+


+

x = PSPdiv(0.8, [p, Pbindf(p, \rel, 0.1)], [1, [3, 2, 1]] ).play;

+


+

x.stop

+


+


+


+

// use with PL proxies

+


+

(

+

// defaults, want to replace later on

+

~pulse = 0.8;

+

~div = 1;

+

~divBase = 1;

+

~divType = \seq;

+


+

q = PSPdiv(PL(\pulse), p, PL(\div), PL(\divBase), PL(\divType));

+

)

+


+


+


+

// start playing

+


+

x = q.play

+


+


+

// replace on the fly:

+

// alternating tuplets

+


+

~div = PLseq([2, 3]);

+


+


+

// "dotted" notes, now we don't need double brackets as above as it's the source of the PL,

+

// not the PSPdiv arg

+


+

~div = [3, 1];

+


+


+

// acceleration and deceleration by pulse pattern control

+


+

~pulse = Pseg(PLseq([0.8, 0.3]), 10)

+


+


+

(

+

// use divBase for base length control of tuplets

+

// here this could be written with div sequencing only also

+


+

~pulse = 1;

+


+

~div = PLseq([2, 6]);

+


+

~divBase = PLseq([1, 1, 2]);

+

)

+


+


+

x.stop

+


+


+


+


+

Ex. 2:   Parallel embedding with one layer

+


+

// SynthDef from Ex. 1

+


+

(

+

// default values for proxies

+


+

~pulse = 0.5;

+

~div = PLseq([6, 4]);

+

~divBase = 2;

+

~divType = \par;

+


+


+

// evPat argument now given as Function

+

// this especially makes sense in combination with parallel embedding

+

// otherwise parallel patterns would poll event data from a single event stream

+


+


+

p = { |durs|

+

// use durs calculated from pulse, div and divBase

+

var size = durs.size;

+

Pbind(

+

\dur, Pseq(durs),

+

\instrument, \varKlank,

+

\midinote, Pseq(~baseStream.next + rrand(0, 7) + (0..size)),

+

\amp, 0.1,

+

\att, Pwhite(0.005, 0.02),

+

\rel, ~releaseStream.next,

+

\ringtime, 2,

+

\sourceType, 1,

+

\pan, ~panStream.next

+

)

+

};

+


+

// these streams deliver items for each embedding

+


+

~baseStream = PLseq([60, 70, 80]).iter;

+

~releaseStream = Pn(Pshuf([2, 0.6, 0.2])).iter;

+

~panStream = Pwhite(-0.8, 0.8).iter;

+


+


+

// we still have only one layer, but it will overlap

+


+

q = PSPdiv(PL(\pulse), p, PL(\div), PL(\divBase), PL(\divType));

+

)

+


+


+

// start

+


+

x = q.play

+


+


+

// change to sequential embedding

+


+

~divType = \seq;

+


+


+

// type of embedding can be sequenced too

+


+

~divType = PLrand([\seq, \par, \par]);

+


+


+

// default divBase was 2, allow shorter division bases

+


+

~divBase = PLrand([1, 2]);

+


+


+

x.stop

+


+


+


+

Ex. 3:   Ornamenting a line with a second layer

+


+


+

// SynthDef from Ex. 1

+


+

(

+

p = Pbind(

+

\instrument, \varKlank,

+

// basic melodic line, data stored in variable for use by ornamentation

+

\midinote, (Pn(Pshuf([72, 74, 76, 79, 81])).collect { |x| ~m = x; }) +

+

// random broadening of line

+

PLrand([[-14, 0], [0, 14]]),

+

\amp, 0.15,

+

\att, Pwhite(0.01, 0.02),

+

\rel, 3,

+

\sourceType, 1,

+

\ringtime, 3,

+

\pan, Pwhite(-0.3, 0.3)

+

);

+


+

~releaseStream = Pn(Pshuf([2, 0.5, 0.2])).iter;

+

~panStream = Pwhite(-0.8, 0.8).iter;

+

~dirStream = PLseq([1, -1]).iter;

+


+

// defines trill pattern for every event from melodic line

+


+

q = { |durs|

+

// use durs calculated from pulse, div and divBase

+

var size = durs.size, midibase = ~m;

+

Pbind(

+

\dur, Pseq(durs),

+

\instrument, \varKlank,

+

// define trill alternating above and below basic melodic line

+

\midinote, Pn(rrand(4, 9) * ~dirStream.next, size) + PLseq([0, 1]) + midibase,

+

\amp, 0.1,

+

\att, Pwhite(0.005, 0.02),

+

\rel, ~releaseStream.next,

+

\ringtime, 2,

+

\sourceType, 1,

+

\pan, ~panStream.next

+

)

+

};

+


+

// default values for PL proxies, want to replace later on

+


+

~pulse = PLshufn([1, 1, 2]/2);

+

~div = PLrand([4, 6, 8]);

+

~divBase = 1;

+

~divType = \seq;

+


+

r = PSPdiv(

+

PL(\pulse),

+

[p, q],

+

[1, PL(\div)],

+

[1, PL(\divBase)],

+

[1, PL(\divType)]

+

);

+

)

+


+


+

// start with sequential embedding: trill on every note, variuos divisions

+


+

x = r.play

+


+


+

// change to parallel embedding of trills, overlapping

+


+

(

+

~divBase = 2;

+

~div = PLrand([8, 12, 16]);

+

~divType = \par;

+

)

+


+

x.stop

+


+


+


+

Ex. 4:   Polyrhythmics of several layers

+


+


+

// SynthDef from Ex. 1

+

// three layers using varying pentatonic scales shifted by sixth tones

+


+

(

+

// staccato layer

+

p = Pbind(

+

\instrument, \varKlank,

+

\midinote, PLshufn([0, 2, 4, 7, 9]) + Pn(Pstutter(15, Pwhite(80, 100, 1))) +

+

0.6 + [0, -14],

+

// use some rests

+

\amp, 0.06,

+

\att, Pwhite(0.01, 0.02),

+

\rel, 0.15,

+

\sourceType, 1,

+

\ringtime, 3,

+

\pan, Pwhite(-0.3, 0.3)

+

);

+


+

// long release layer

+

q = Pbind(

+

\instrument, \varKlank,

+

\midinote, PLshufn([0, 2, 4, 7, 9]) + Pn(Pstutter(20, Pwhite(60, 80, 1))) +

+

PLrand([[-14, 0]]) + PLrand([0, 12]),

+

\amp, 0.08,

+

\att, Pwhite(0.01, 0.02),

+

\rel, 2,

+

\sourceType, 1,

+

\ringtime, 2,

+

\pan, Pwhite(-0.3, 0.3)

+

);

+


+

// "drum" layer

+

r = Pbind(

+

\instrument, \varKlank,

+

\midinote, PLshufn([0, 2, 4, 7, 9]) + Pn(Pstutter(25, Pwhite(35, 50, 1))) +

+

0.3 + [-12, 0],

+

\amp, 0.02,

+

\att, Pwhite(0.01, 0.02),

+

\rel, 0.35,

+

\sourceType, 0,

+

\ringtime, 1,

+

\pan, Pwhite(-0.3, 0.3)

+

);

+


+

// default values for PL proxies, want to replace later on

+


+

~pulse = PLshufn([1, 1, 2] * 5/9);

+


+

~div0 = PLshufn([4, 8]);

+

~div1 = PLshufn([2, 3, 4]);

+

~div2 = PLshufn([2, 4]);

+

~divBase = 1;

+

~divType = \seq;

+


+

u = PSPdiv(

+

PL(\pulse),

+

[p, q, r],

+

[PL(\div0), PL(\div1), PL(\div2)],

+

// will be exapanded to array of 3 PLs with same symbol reference

+

PL(\divBase),

+

PL(\divType)

+

);

+

)

+


+


+

// start

+


+

x = u.play

+


+


+

// vary pulse

+


+

~pulse = PLshufn([1, 1.5, 2.5, 3] * 5/9);

+


+

~pulse = 1;

+


+

~div0 = PLshufn([4, 6, 8]);

+


+

~div2 = PLshufn([2, 4, 6]);

+


+

~pulse = PLshufn([1, 1, 9/8]);

+


+

x.stop;

+


+


+


+

Ex. 5:   Granulation, control of many layers  

+


+

// SynthDef from Ex. 1

+


+

(

+

~amp = 0.05;

+

~att = 0.005;

+

~rel = 0.05;

+

~sourceType = 0;

+

~ringtime = 1;

+


+

~pan = PLseq([0.8, -0.8]);

+


+

// Pbind generator, patterns will stick to midinotes

+

p = { |midinote| Pbind(

+

\instrument, \varKlank,

+

\midinote, midinote,

+

\amp, PL(\amp),

+

\att, PL(\att),

+

\rel, PL(\rel),

+

\sourceType, PL(\sourceType),

+

\ringtime, PL(\ringtime),

+

\pan, PL(\pan)

+

) };

+


+

// produce an array of Pbinds, covering a whole tone cluster 

+

q = ((1, 3..51) + 40).collect(p.(_));

+


+

~size = q.size;

+


+

// same pattern source for each PL, 

+

// but for each midinote divisions per pulse can vary

+


+

~div = Pfunc { (2 ** (1..4)).choose.asInteger };

+

~divBase = 1;

+


+

~pulse = 1;

+

~rel = 0.015;

+

~sourceType = 1;

+


+

// as q is an array the args 'div' and 'divBase' will be expanded to arrays

+

u = PSPdiv(PL(\pulse), q, PL(\div), PL(\divBase));

+

)

+


+


+


+

x = u.play

+


+

// this setting exchanges the source of all PL patterns expanded as 'div' arg

+


+

~div = Pfunc { (2 ** (1..6)).choose.asInteger }

+


+


+

// also with divBase, layers are decorrelated as each stream from this pattern acts differently

+


+


+

~divBase = PLrand([1, 2])

+


+

~divBase = PLrand([1, 2, 3])

+


+


+

~pulse = 1/4

+


+

x.stop

+


+


+


+


+


+ + diff --git a/Help/PSVdif.html b/Help/PSVdif.html new file mode 100755 index 0000000..287c092 --- /dev/null +++ b/Help/PSVdif.html @@ -0,0 +1,82 @@ + + + + + + + + + + + +

PSVdif Sieve pattern for difference of integer generators with point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for difference of integer generators with point output. Corresponds to Sieve's method 'dif'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVdif object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+

Examples

+


+


+

p = PSVdif([3, 2], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(7, 30)

+


+

q = PSVdif([1, a], 20)

+


+

q.asStream.all

+


+


+

r = PSVdif([1, a], limit: 15)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVdif_i.html b/Help/PSVdif_i.html new file mode 100755 index 0000000..e1727c4 --- /dev/null +++ b/Help/PSVdif_i.html @@ -0,0 +1,82 @@ + + + + + + + + + + + +

PSVdif_i Sieve pattern for difference of integer generators with interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for difference of integer generators with interval output. Corresponds to Sieve's method 'dif_i'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVdif_i object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned until default summation limit of 65536.

+


+


+


+

Examples

+


+


+

p = PSVdif_i([3, 2], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(7, 30)

+


+

q = PSVdif_i([1, a], 20)

+


+

q.asStream.all

+


+


+

r = PSVdif_i([1, a], limit: 15)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVdif_o.html b/Help/PSVdif_o.html new file mode 100755 index 0000000..cb2811f --- /dev/null +++ b/Help/PSVdif_o.html @@ -0,0 +1,83 @@ + + + + + + + + + + + +

PSVdif_o Sieve pattern for difference of integer generators with offsets and point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for difference of integer generators with offsets and point output. Corresponds to Sieve's method 'dif_o'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVdif_o object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

Offsets must be integers.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+

Examples

+


+


+

p = PSVdif_o([3, 1, 5, -2], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(4, 20)

+


+

q = PSVdif_o([a, 90, 3, 80], 20)

+


+

q.asStream.all

+


+


+

r = PSVdif_o([a, 90, 3, 80], limit: 100)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVdif_oi.html b/Help/PSVdif_oi.html new file mode 100755 index 0000000..c0691f0 --- /dev/null +++ b/Help/PSVdif_oi.html @@ -0,0 +1,83 @@ + + + + + + + + + + + +

PSVdif_oi Sieve pattern for difference of integer generators with offsets and interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for difference of integer generators with offsets and interval output. Corresponds to Sieve's method 'dif_oi'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVdif_oi object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

Offsets must be integers.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned up to default summation limit of 65536.

+


+


+


+

Examples

+


+


+

p = PSVdif_oi([5, 0, 10, -20], 20)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(4, 20)

+


+

q = PSVdif_oi([1, 0, a, -2], 20)

+


+

q.asStream.all

+


+


+

r = PSVdif_oi([1, 0, 10, -2], limit: 20)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVop.html b/Help/PSVop.html new file mode 100755 index 0000000..293b271 --- /dev/null +++ b/Help/PSVop.html @@ -0,0 +1,105 @@ + + + + + + + + + + + +

PSVop Sieve pattern for arbitrary set operations of integer generators with point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for arbitrary set operations of integer generators with point output. 

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, op, difIndex, maxLength, limit)

+

+

Creates a new PSVop object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+


+

op - One of the Symbols 'u', 's', 'sd', 'd' as abbreviations for set operations 'union',

+

'sect', 'symdif', 'dif' or a Pattern/Stream to produce such. Defaults to 'u'.

+


+

difIndex - Integer or a Pattern/Stream to produce such. 

+

Determines the generator from which will be subtracted in case of operation 'dif'.

+

Defaults to 0.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+

Examples

+


+


+

// equivalent

+


+

x = PSVop([3, 5], \sd)

+

y = PSVsymdif([3, 5])

+


+

x.asStream.nextN(15)

+

y.asStream.nextN(15)

+


+


+

// sequencing of logical operations

+


+

p = PSVop([3, 3], Pseq([\u, \sd], inf))

+


+

p.asStream.nextN(15)

+


+


+

// specify difference

+


+

q = PSVop([2, 5], \d, 1)

+


+

q.asStream.nextN(100)

+


+


+

r = PSVop([2, 5], \d, 0)

+


+

r.asStream.nextN(100)

+


+


+


+ + diff --git a/Help/PSVop_i.html b/Help/PSVop_i.html new file mode 100755 index 0000000..39c410a --- /dev/null +++ b/Help/PSVop_i.html @@ -0,0 +1,105 @@ + + + + + + + + + + + +

PSVop_i Sieve pattern for arbitrary set operations of integer generators with interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for arbitrary set operations of integer generators with interval output. 

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, op, difIndex, maxLength, limit)

+

+

Creates a new PSVop_i object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+


+

op - One of the Symbols 'u', 's', 'sd', 'd' as abbreviations for set operations 'union',

+

'sect', 'symdif', 'dif' or a Pattern/Stream to produce such. Defaults to 'u'.

+


+

difIndex - Integer or a Pattern/Stream to produce such. 

+

Determines the generator from which will be subtracted in case of operation 'dif'.

+

Defaults to 0.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned until default summation limit of 65536.

+


+


+


+

Examples

+


+


+

// equivalent

+


+

x = PSVop_i([3, 5], \sd)

+

y = PSVsymdif_i([3, 5])

+


+

x.asStream.nextN(15)

+

y.asStream.nextN(15)

+


+


+

// sequencing of logical operations

+


+

p = PSVop_i([3, 3], Pseq([\u, \sd], inf))

+


+

p.asStream.nextN(15)

+


+


+

// specify difference

+


+

q = PSVop_i([2, 5], \d, 1)

+


+

q.asStream.nextN(100)

+


+


+

r = PSVop_i([2, 5], \d, 0)

+


+

r.asStream.nextN(100)

+


+


+ + diff --git a/Help/PSVop_o.html b/Help/PSVop_o.html new file mode 100755 index 0000000..36cf6a5 --- /dev/null +++ b/Help/PSVop_o.html @@ -0,0 +1,108 @@ + + + + + + + + + + + +

PSVop_o Sieve pattern for arbitrary set operations of integer generators with offsets and point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for arbitrary set operations of integer generators with offsets and point output. 

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, op, difIndex, maxLength, limit)

+

+

Creates a new PSVop_o object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

Offsets must be integers.

+


+

op - One of the Symbols 'u', 's', 'sd', 'd' as abbreviations for set operations 'union',

+

'sect', 'symdif', 'dif' or a Pattern/Stream to produce such. Defaults to 'u'.

+


+

difIndex - Integer or a Pattern/Stream to produce such. 

+

Determines the generator from which will be subtracted in case of operation 'dif'.

+

Defaults to 0.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+

Examples

+


+


+

// equivalent

+


+

x = PSVop_o([3, 1, 5, 0], \sd)

+

y = PSVsymdif_o([3, 1, 5, 0])

+


+

x.asStream.nextN(15)

+

y.asStream.nextN(15)

+


+


+

// sequencing of logical operations

+


+

p = PSVop_o([3, 1, 2, 0], Pseq([\s, \u, \u], inf))

+


+

p.asStream.nextN(15)

+


+


+

// specify difference

+


+

q = PSVop_o([3, 1, 5, 0], \d, 1)

+


+

q.asStream.nextN(10)

+


+


+

r = PSVop_o([3, 1, 5, 0], \d, 0)

+


+

r.asStream.nextN(10)

+


+


+


+


+ + diff --git a/Help/PSVop_oi.html b/Help/PSVop_oi.html new file mode 100755 index 0000000..245769a --- /dev/null +++ b/Help/PSVop_oi.html @@ -0,0 +1,110 @@ + + + + + + + + + + + +

PSVop_oi Sieve pattern for arbitrary set operations of integer generators with offsets and interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for arbitrary set operations of integer generators with offsets and interval output. 

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o 

+


+


+

Creation / Class Methods

+


+

*new (genList, op, difIndex, maxLength, limit)

+

+

Creates a new PSVop_oi object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

Offsets must be integers.

+


+

op - One of the Symbols 'u', 's', 'sd', 'd' as abbreviations for set operations 'union',

+

'sect', 'symdif', 'dif' or a Pattern/Stream to produce such. Defaults to 'u'.

+


+

difIndex - Integer or a Pattern/Stream to produce such. 

+

Determines the generator from which will be subtracted in case of operation 'dif'.

+

Defaults to 0.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned up to default summation limit of 65536.

+


+


+


+


+

Examples

+


+


+


+

// equivalent

+


+

x = PSVop_oi([3, 1, 5, 0], \sd)

+

y = PSVsymdif_oi([3, 1, 5, 0])

+


+

x.asStream.nextN(15)

+

y.asStream.nextN(15)

+


+


+

// sequencing of logical operations

+


+

p = PSVop_oi([3, 1, 2, 0], Pseq([\s, \u, \u], inf))

+


+

p.asStream.nextN(15)

+


+


+

// specify difference

+


+

q = PSVop_oi([3, 1, 5, 0], \d, 1)

+


+

q.asStream.nextN(10)

+


+


+

r = PSVop_oi([3, 1, 5, 0], \d, 0)

+


+

r.asStream.nextN(10)

+


+


+


+


+


+ + diff --git a/Help/PSVsect.html b/Help/PSVsect.html new file mode 100755 index 0000000..c684b08 --- /dev/null +++ b/Help/PSVsect.html @@ -0,0 +1,83 @@ + + + + + + + + + + + +

PSVsect Sieve pattern for intersection of integer generators with point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for intersection of integer generators with point output. Corresponds to Sieve's method 'sect'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVsect object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+


+

Examples

+


+


+

p = PSVsect([3, 5], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(7, 1000)

+


+

q = PSVsect([a, 100], 10)

+


+

q.asStream.all

+


+


+

r = PSVsect([a, 100], limit: 500)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVsect_i.html b/Help/PSVsect_i.html new file mode 100755 index 0000000..3b8c4e0 --- /dev/null +++ b/Help/PSVsect_i.html @@ -0,0 +1,83 @@ + + + + + + + + + + + +

PSVsect_i Sieve pattern for intersection of integer generators with interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for intersection of integer generators with interval output. Corresponds to Sieve's method 'sect_i'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVsect_i object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned until default summation limit of 65536.

+


+


+


+

Examples

+


+


+

p = PSVsect_i([3, 5], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(7, 1000)

+


+

q = PSVsect_i([a, 100], 10)

+


+

q.asStream.all

+


+


+

r = PSVsect_i([a, 100], limit: 500)

+


+

r.asStream.all

+


+


+ + diff --git a/Help/PSVsect_o.html b/Help/PSVsect_o.html new file mode 100755 index 0000000..d5e2102 --- /dev/null +++ b/Help/PSVsect_o.html @@ -0,0 +1,83 @@ + + + + + + + + + + + +

PSVsect_o Sieve pattern for intersection of integer generators with offsets and point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for intersection of integer generators with offsets and point output. Corresponds to Sieve's method 'sect_o'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVsect_o object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

Offsets must be integers.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+

Examples

+


+


+

p = PSVsect_o([3, 1, 5, -2], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(4, 200)

+


+

q = PSVsect_o([a, 90, 10, 100], 20)

+


+

q.asStream.all

+


+


+

r = PSVunion_o([a, 50, 10, -100], limit: 100)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVsect_oi.html b/Help/PSVsect_oi.html new file mode 100755 index 0000000..f399d6f --- /dev/null +++ b/Help/PSVsect_oi.html @@ -0,0 +1,82 @@ + + + + + + + + + + + +

PSVsect_oi Sieve pattern for intersection of integer generators with offsets and interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for intersection of integer generators with offsets and interval output. Corresponds to Sieve's method 'sect_oi'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVsect_oi object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

Offsets must be integers.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned up to default summation limit of 65536.

+


+


+

Examples

+


+


+

p = PSVsect_oi([10, 31, 50, 1], 20)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(4, 20)

+


+

q = PSVsect_oi([a, 20, 10, 0], 5)

+


+

q.asStream.all

+


+


+

r = PSVsect_oi([a, 20, 10, 0], limit: 25)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVsymdif.html b/Help/PSVsymdif.html new file mode 100755 index 0000000..ea64001 --- /dev/null +++ b/Help/PSVsymdif.html @@ -0,0 +1,82 @@ + + + + + + + + + + + +

PSVsymdif Sieve pattern for symmetric difference of integer generators with point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for symmetric difference of integer generators with point output. Corresponds to Sieve's method 'symdif'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVsymdif object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+

Examples

+


+


+

p = PSVsymdif([3, 5], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(7, 50)

+


+

q = PSVsymdif([a, 2], 15)

+


+

q.asStream.all

+


+


+

r = PSVsymdif([a, 2], limit: 20)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVsymdif_i.html b/Help/PSVsymdif_i.html new file mode 100755 index 0000000..30e5255 --- /dev/null +++ b/Help/PSVsymdif_i.html @@ -0,0 +1,84 @@ + + + + + + + + + + + +

PSVsymdif_i Sieve pattern for symmetric difference of integer generators with interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for symmetric difference of integer generators with interval output. Corresponds to Sieve's method 'symdif_i'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVsymdif_i object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned until default summation limit of 65536.

+


+


+


+

Examples

+


+


+

p = PSVsymdif_i([3, 5], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(7, 30)

+


+

q = PSVsymdif_i([a, 100], 10)

+


+

q.asStream.all

+


+


+

r = PSVsymdif_i([a, 100], limit: 1000)

+


+

r.asStream.all

+


+


+


+ + diff --git a/Help/PSVsymdif_o.html b/Help/PSVsymdif_o.html new file mode 100755 index 0000000..0e56efd --- /dev/null +++ b/Help/PSVsymdif_o.html @@ -0,0 +1,84 @@ + + + + + + + + + + + +

PSVsymdif_o Sieve pattern for symmetric difference of integer generators with offsets and point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for symmetric difference of integer generators with offsets and point output. Corresponds to Sieve's method 'symdif_o'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVsymdif_o object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

Offsets must be integers.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+


+

Examples

+


+


+

p = PSVunion_o([3, 1, 5, -2], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(4, 20)

+


+

q = PSVunion_o([a, 90, 10, 100], 20)

+


+

q.asStream.all

+


+


+

r = PSVsymdif_o([a, 90, 10, 80], limit: 100)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVsymdif_oi.html b/Help/PSVsymdif_oi.html new file mode 100755 index 0000000..876f69e --- /dev/null +++ b/Help/PSVsymdif_oi.html @@ -0,0 +1,83 @@ + + + + + + + + + + + +

PSVsymdif_oi Sieve pattern for symmetric difference of integer generators with offsets and interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for symmetric difference of integer generators with offsets and interval output. Corresponds to Sieve's method 'symdif_oi'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVsymdif_oi object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

Offsets must be integers.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned up to default summation limit of 65536.

+


+


+


+

Examples

+


+


+

p = PSVsymdif_oi([10, 0, 50, 1], 20)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(4, 20)

+


+

q = PSVsymdif_oi([a, 0, 10, 100], 20)

+


+

q.asStream.all

+


+


+

r = PSVsymdif_oi([a, 0, 10, 100], limit: 120)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVunion.html b/Help/PSVunion.html new file mode 100755 index 0000000..7e18719 --- /dev/null +++ b/Help/PSVunion.html @@ -0,0 +1,81 @@ + + + + + + + + + + + +

PSVunion Sieve pattern for union of integer generators with point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for union of integer generators with point output. Corresponds to Sieve's method 'union'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVunion object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+

Examples

+


+


+

p = PSVunion([3, 5], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(7, 30)

+


+

q = PSVunion([a, 100], 10)

+


+

q.asStream.all

+


+


+

r = PSVunion([a, 100], limit: 1000)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVunion_i.html b/Help/PSVunion_i.html new file mode 100755 index 0000000..c6d8fa0 --- /dev/null +++ b/Help/PSVunion_i.html @@ -0,0 +1,83 @@ + + + + + + + + + + + +

PSVunion_i Sieve pattern for union of integer generators with interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for union of integer generators with interval output. Corresponds to Sieve's method 'union_i'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVunion_i object.

+

+

genList - An array of generators. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned until default summation limit of 65536.

+


+


+


+


+

Examples

+


+


+

p = PSVunion_i([3, 5], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(7, 30)

+


+

q = PSVunion_i([a, 100], 10)

+


+

q.asStream.all

+


+


+

r = PSVunion_i([a, 100], limit: 1000)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVunion_o.html b/Help/PSVunion_o.html new file mode 100755 index 0000000..7fd73cd --- /dev/null +++ b/Help/PSVunion_o.html @@ -0,0 +1,84 @@ + + + + + + + + + + + +

PSVunion_o Sieve pattern for union of integer generators with offsets and point output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for union of integer generators with offsets and point output. Corresponds to Sieve's method 'union_o'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVunion_o object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

Offsets must be integers.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which integers can be returned by the stream.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+


+


+


+

Examples

+


+


+

p = PSVunion_o([3, 1, 5, -2], 10)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(4, 20)

+


+

q = PSVunion_o([a, 90, 10, 100], 20)

+


+

q.asStream.all

+


+


+

r = PSVunion_o([a, 10, 3, 7], limit: 20)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSVunion_oi.html b/Help/PSVunion_oi.html new file mode 100755 index 0000000..d377410 --- /dev/null +++ b/Help/PSVunion_oi.html @@ -0,0 +1,84 @@ + + + + + + + + + + + +

PSVunion_oi Sieve pattern for union of integer generators with offsets and interval output

+


+

Part of: miSCellaneous

+


+

Inherits from: Psieve

+


+

Pattern for union of integer generators with offsets and interval output. Corresponds to Sieve's method 'union_oi'.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*new (genList, maxLength, limit)

+

+

Creates a new PSVunion_oi object.

+

+

genList - An array of generators and corresponding offsets. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

Offsets must be integers.

+


+

maxLength - Integer. Maximum number of items, which the stream will return.

+

Defaults to inf.

+


+

limit - Integer. Limit up to which intervals can be returned by the stream.

+

If no limit is passed, integer intervals might be returned up to default summation limit of 65536.

+


+


+


+


+

Examples

+


+


+

p = PSVunion_oi([10, 0, 50, 1], 20)

+


+

p.asStream.nextN(15)

+


+


+

a = Sieve(4, 20)

+


+

q = PSVunion_oi([a, 0, 10, 100], 20)

+


+

q.asStream.all

+


+


+

r = PSVunion_oi([a, 0, 10, 100], limit: 120)

+


+

r.asStream.all

+


+ + diff --git a/Help/PSdup.html b/Help/PSdup.html new file mode 100755 index 0000000..0c38a3e --- /dev/null +++ b/Help/PSdup.html @@ -0,0 +1,205 @@ + + + + + + + + + + + +

PSdup Pattern which returns last values from other patterns via PSx

+


+

Part of: miSCellaneous

+


+

Inherits from: PS / PStream

+


+

See also: MemoRoutine, PSx stream patterns, PS, PSrecur, PSloop

+


+


+

PSdup uses the storage functionality of other PSx patterns. To track a value stream or event stream from a pattern you'd have to wrap the pattern into a PSx.

+


+


+

Creation / Class Methods

+


+

*new (psxPat, length, bufSize, copyItems, copySets)

+

+

Creates a new PSdup object.

+

+

psxPat - Source pattern, must be a PSx pattern.

+

length - Number of output items, may be pattern or stream, defaults to inf.

+

bufSize - Size of buffer to store last values, defaults to 1.

+

copyItems - Determines if and how to copy the last items from psxPat's buffer

+

which are either non-Sets or member of Sets. 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). 

+

Other values are interpreted as 0. Defaults to 0.

+

0: original item 

+

1: copy item

+

2: deepCopy item

+

copySets - Determines if and how to copy the last items from psxPat's buffer

+

which are Sets. 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). 

+

Other values are interpreted as 0. Defaults to 1.

+

0: original Set 

+

1: copy Set

+

+

NOTE 1: The distinction of copying items and sets makes sense in the case of event streams.

+

Per default Events are copied (copySets == 1), not their values (copyItems == 0). 

+

By playing Events those are used to store additional data (synth ids, msgFuncs …) 

+

which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use 

+

MemoRoutine - copied Events will not contain this additional data. 

+

If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured 

+

then copying makes no sense, this is the normal case, so copyItems defaults to 0.

+

When going to alter the ouput, you might want to set copyItems to 1 for a PSx returning 

+

simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set

+

copySets to 1 and copyItems to 2 (an option copySets == 2 doesn't exist as

+

it would be contradictory in combination with copyItems < 2).

+


+

NOTE 2: Copy options concern copying into PSdup's buffer as well as 

+

the output of a Stream derived from the PSdup. When such a Stream is outputting copies 

+

this prevents unintended altering of items stored in the buffer of psxPat. On the other hand

+

storing copies in PSdup's buffer prevents these from being altered unintendedly.

+


+


+

Instance Methods

+


+

psxPat, psxPat_(value)

+

+

Instance variable getter and setter methods. 

+


+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// copying of value patterns

+

// PSdup needs a PSx as input, as its value storage is used,

+

// so it's easiest to wrap the original pattern into a PS

+


+

(

+

p = PS(Pseries());

+

q = PSdup(p);

+


+

// PSx patterns have a state and, in this regard, behave like Streams.

+

// However, like other Patterns, for generating they must be made to Streams 

+

// which are the objects actually returning items. 

+


+

x = p.asStream;

+

y = q.asStream;

+

)

+


+

// y follows x (as in the array left is evaluated before right) 

+


+

(

+

a = [];

+

10.do { a = a.add([x.next, y.next]) };

+

a;

+

)

+


+


+

// get values from original Pattern / Stream

+


+

x.nextN(10)

+


+

// now y copies the last value of x resp. p

+


+

y.nextN(10)

+


+


+


+

// data sharing with event patterns

+


+

(

+

p = Pbind(

+

\midinote, Pwhite(60, 90) + PLrand([0, 0, [0, -3]]),

+

\dur, PLrand([0.2, 0.4]),

+

\amp, PLrand([0.07, 0.12, 0.2]),

+

\type, Pfunc { 0.1.coin.if { \rest }{ \note } },

+

\pan, 1

+

);

+


+

q = PS(p);

+


+

// use PSdup as base for alterings

+

// it takes the last event stored in PS(p)

+


+

r = Pbindf(Padd(\midinote, PLrand([1,2,4,5]), PSdup(q)), \pan, -1);

+


+

// as in general with event stream data sharing it's recommended to invent a small delta 

+

// between streams, here this is extended to a clear echo 

+


+

t = Ptpar([0, q, 0.1, r]).trace.play;

+

)

+


+

t.stop;

+


+


+

// data sharing with event patterns, more voices 

+


+


+

(

+

p = Pbind(

+

\midinote, Pwhite(50.0, 65),

+

\dur, PLrand([0.7, 0.9, 1.2]),

+

\amp, PLrand([0.1, 0.15, 0.2]),

+

\legato, 0.1

+

);

+


+

q = PS(p);

+


+

// make pattern maker Function for parametrized alterings

+


+

f = { |div, add| Padd(\midinote, add, Pmul(\dur, 1 / div, PSdup(q))) };

+

d = 0.001;

+


+

t = Ptpar([

+

0, q, 

+

d, f.(2, 5 + PLrand([0, [0, 12.05]])),

+

d*2, f.(4, 7 + PLrand([0, [0, 12.1]])),

+

d*3, f.(8, 10 + PLrand([0, [0, 12.2]]))

+

]).play

+

)

+


+


+

t.stop;

+


+


+


+ + diff --git a/Help/PSloop.html b/Help/PSloop.html new file mode 100755 index 0000000..5353021 --- /dev/null +++ b/Help/PSloop.html @@ -0,0 +1,725 @@ + + + + + + + + + + + +

PSloop Pattern to derive loops from a given Pattern 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pattern

+


+

See also: MemoRoutine, PSx stream patterns, PS, PSdup PSrecur

+


+


+

Although PSloop is not a subclass of PStream, instances of PSloop have a state by storing a PStream of the source pattern in an instance variable. So the specific characteristics of PSx patterns are indirectly inherited by PSloop. This especially concerns the way to generate new, fresh PSloop streams, see examples in PSx stream patterns.

+


+

Note that impossible lookBack and indices values will be clipped, see descriptions of args bufSize, lookBack and indices. 

+


+


+

Creation / Class Methods

+


+

*new (srcPat, length, bufSize, lookBack, doSkip, loopFunc, loopFuncPerItem, loopFuncAsGoFunc, goFunc, indices, copyItems, copySets)

+

+

Creates a new PSloop object.

+

+

srcPat - Source pattern for looping, can be value or event pattern.

+

length - Number of output items, may be Pattern or Stream, defaults to inf.

+


+

bufSize - Integer size of buffer to store last values. Determines and clips the maximum lookBack index. Defaults to 1.

+


+

lookBack - Non-negative Integer determining the depth of looking backwards when looping, may be Pattern or Function also.

+

Default 0 means going on with the Stream of the source pattern srcPat.

+

If no indices is given, a positive Integer lookBack = n causes straight looping from the nth item

+

in the past up to the last item of the Stream.

+

If indices is given, an index array is produced by this function applied to lookBack = n. 

+

The array refers to the nth item in the past by array index 0.

+

If lookBack values are derived from a Pattern or Function, they might be polled at the end of the loops

+

or with every item, this is determined by the current value of doSkip. 

+

lookBack will be clipped by bufSize if it is greater than the latter.

+


+

doSkip - Integer or Boolean or: Function or Stream returning such.

+

Determines if lookBack values are polled at the end of loops (0, false) or within loops (1, true) also.

+

In the latter case a new lookBack value will stop the current loop and start a new one. Defaults to 1.

+

+

loopFunc - Function or Pattern returning Functions, to be applied to items of the loops. 

+

If Functions are streamed by passing a Function-generating Pattern loopFuncPerItem determines if

+

a Function will be applied per item (1, true) or to all items of a loop (0, false).

+

When loopFunc is nil or the loopFunc stream returns nil, no Function is applied.

+

+

loopFuncPerItem - Integer or Boolean or: Function or Stream returning such.

+

If loopFunc is given, loopFuncPerItem determines if a Function will be applied per item (1, true) or 

+

to all items of a loop (0, false).

+


+

loopFuncAsGoFunc - Integer or Boolean or: Function or Stream returning such.

+

Determines if loopFunc will be used for new items outside loops too (1, true).

+

In this case goFunc is ignored (resp. nothing is polled from a goFunc stream) and nil values 

+

from the loopFunc stream are also taken over (no Function is applied outside loops then).

+

+

goFunc - Function or Pattern returning Functions, to be applied to new items outside loops. 

+

When goFunc is nil or the goFunc stream returns nil, no Function is applied.

+

Note that goFunc has no effect when loopFuncAsGoFunc determines to use loopFunc generally.

+


+

indices - SequenceableCollection, Function, or Pattern returning SequenceableCollections or Functions.

+

If no indices is passed (default nil) a positive Integer lookBack = n causes straight looping from the nth item

+

in the past up to the last item of the Stream.

+

A SequenceableCollection passed via indices is taken as array of indices and determines the items of the buffer, 

+

identifying the nth item in the past with array index 0.

+

A Function passed via indices takes lookBack = n as argument and must return an index array which also

+

determines the items of the buffer, identifying the nth item in the past with array index 0.

+

A Pattern passed via indices should be defined to generate Functions or SequenceableCollections, 

+

which are interpreted in the same way as above.

+

In case of a Pattern a new indices value is polled within a loop if current doSkip equals 1 or true.

+

indices will be clipped by lookBack - 1, lookBack itself might be clipped by bufSize.

+

+

copyItems - Argument passed to the PS wrapper of srcPat. See PS.

+

Determines if and how to copy items, which are either non-Sets or member of Sets. 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). 

+

Other values are interpreted as 0. Defaults to 0.

+

0: original item 

+

1: copy item

+

2: deepCopy item

+

+

copySets - Argument passed to the PS wrapper of srcPat. See PS.

+

Determines if and how to copy Sets (and hence Events). 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). 

+

Other values are interpreted as 0. Defaults to 1.

+

0: original Set 

+

1: copy Set

+

+

NOTE 1: The distinction of copying items and sets makes sense in the case of event streams.

+

Per default Events are copied (copySets == 1), not their values (copyItems == 0). 

+

By playing Events those are used to store additional data (synth ids, msgFuncs …) 

+

which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use 

+

MemoRoutine - copied Events will not contain this additional data. 

+

If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured 

+

then copying makes no sense, this is the normal case, so copyItems defaults to 0.

+

When going to alter the ouput, you might want to set copyItems to 1 for a PSx returning 

+

simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set

+

copySets to 1 and copyItems to 2 (an option copySets == 2 doesn't exist as

+

it would be contradictory in combination with copyItems < 2).

+


+

NOTE 2: Copy options concern copying into PS's buffer as well as 

+

the output of a Stream derived from the PS. When such a Stream is outputting copies 

+

this prevents unintended altering of items stored in the buffer of the source. On the other hand

+

storing copies in PSrecur's buffer prevents these from being altered unintendedly.

+


+


+

Instance Methods

+


+

psPat, psPat(value)

+

+

Instance variable getter and setter methods. 

+

psPat  holds a Pstream with args srcPat, length, bufSize, copyItems, copySets.

+


+

lookBack, lookBack(value)

+

+

Instance variable getter and setter methods. 

+


+

doSkip, doSkip(value)

+

+

Instance variable getter and setter methods. 

+


+

loopFunc, loopFunc(value)

+

+

Instance variable getter and setter methods. 

+


+

loopFuncPerItem, loopFuncPerItem(value)

+

+

Instance variable getter and setter methods. 

+


+

loopFuncAsGoFunc, loopFuncAsGoFunc(value)

+

+

Instance variable getter and setter methods. 

+


+

goFunc, goFunc(value)

+

+

Instance variable getter and setter methods. 

+


+

indices, indices(value)

+

+

Instance variable getter and setter methods. 

+


+


+


+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

Ex. 1a:   PSloop used as value pattern

+


+

// straight usage as value pattern for midinotes

+


+

(

+

// lookBack determines number of items to loop, must start with 0 (no loop),

+

// doSkip determines if loops should be completed or not with new lookBack values.

+


+

// Passing both via Functions enables to set on the fly,

+

// enable immediate loop change by setting doSkip to 1

+


+

~lookBack = 0;

+

~doSkip = true;

+


+

p = Pbind(

+

\dur, 1/5,

+

\midinote, PSloop(

+

Prand((60..80), inf), 

+

bufSize: 10, 

+

lookBack: { ~lookBack },

+

doSkip: { ~doSkip }

+

)

+

).play

+

)

+


+

// wait a bit to fill buffer

+

// keep running, set loop lengths immediately

+


+

~lookBack = 4

+


+

~lookBack = 2

+


+

~lookBack = 7

+


+


+

// change to skip per loop (default)

+


+

~doSkip = false

+


+

~lookBack = 3

+


+

~lookBack = 5

+


+

~lookBack = 9

+


+


+

// go on

+


+

~lookBack = 0

+


+

p.stop;

+


+


+


+

Ex. 1b:   PSloop used as event pattern

+


+

// rhythms are looped too

+


+

(

+

~lookBack = 0;

+

~doSkip= 1;

+


+

p = PSloop(

+

Pbind(

+

\dur, Pn(Pshuf([1, 1, 2, Pseq(2!3/3)]/5)),

+

\midinote, Prand((60..80), inf) 

+

), 

+

bufSize: 10, 

+

lookBack: { ~lookBack },

+

doSkip: { ~doSkip}

+

).play

+

)

+


+

// wait a bit to fill buffer

+

// keep running, set loop lengths immediately

+


+

~lookBack = 6

+


+

~lookBack = 5

+


+


+

// go on and loop again

+


+

~lookBack = 0

+


+

~lookBack = 7

+


+


+

p.stop;

+


+


+


+

Ex. 2a:   PSloop used as value pattern, lookBack given as pattern

+


+

// lookBack passed as pattern, 

+

// lookBack pattern must start with zeros, 

+

// otherwise it will immediately stop with buffer values = nil 

+


+

// PSloop as value pattern: in general rhythms aren't looped

+

+

(

+

p = Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, PSloop(

+

Prand((60..80), inf), 

+

bufSize: 10, 

+

lookBack: Pseq([0, 0, 0, 2, 2, 3, 3], inf)

+

)

+

).play

+

)

+


+

p.stop

+


+


+


+

Ex. 2b:   PSloop used as event pattern, lookBack given as pattern

+


+

// rhythms looped too

+


+

(

+

p = PSloop(

+

Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, Prand((60..80), inf) 

+

), 

+

bufSize: 10, 

+

lookBack: Pseq([0, 0, 0, 2, 2, 3, 3], inf)

+

).play

+

)

+


+

p.stop

+


+

+


+

Ex. 3a:   lookBack indices given as array

+


+

// With the indices arg arbitrary series from the buffer can be played.

+

// Passing an array we define an index sequence which doesn't depend on lookBack

+


+

(

+

~lookBack = 0; 

+


+

p = PSloop(

+

Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, Pseq((60..80), inf)

+

), 

+

bufSize: 10, 

+

lookBack: { ~lookBack },

+

indices: [0, 1, 0, 1, 2, 0, 1, 2, 3]

+

).trace.play

+

)

+


+

// wait a bit to fill buffer

+

// start loop with useful lookBack (here > 3)

+


+

~lookBack = 4

+


+

~lookBack = 6

+


+


+

// go on and loop again

+


+

~lookBack = 0;

+


+

~lookBack = 5

+


+


+

p.stop

+


+


+


+

Ex. 3b:   lookBack indices given as Function

+


+

// If a Function is passed to indices it takes the current lookBack arg as argument.

+


+

// mirroring the full lookBack size

+

// start without loop

+


+

(

+

~lookBack = 0;

+


+

p = PSloop(

+

Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, Prand((60..80), inf)

+

), 

+

bufSize: 10, 

+

lookBack: { ~lookBack },

+

indices: { |l| (0..l-1).mirror }

+

).trace.play

+

)

+


+

// wait a bit to fill buffer

+

// mirrored loop i.e. loop with added retrograde

+


+

~lookBack = 3

+


+


+

// go on and loop again

+


+

~lookBack = 0

+


+

~lookBack = 3

+


+

p.stop

+


+


+


+

Ex. 3c:   lookBack indices given as Pattern

+


+

// The indices arg might get a pattern which can generate Functions as well as arrays.

+

// Start with sufficient number of zeros as lookBack to fill buffer.

+


+

(

+

p = PSloop(

+

Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, Prand((60..80), inf)

+

), 

+

bufSize: 10, 

+

lookBack: Pseq([Pn(0, 7), Pstutter(4, Pwhite(3, 5, 1))], inf).trace,

+

indices: Pseq([1!2, { |l| (l-1..0).postln }], inf).trace

+

).trace.play

+

)

+


+

p.stop

+


+


+


+

Ex. 4a:   loopFunc given as Function

+


+

// a Function passed to loopFunc is applied to all items of the looping

+


+

(

+

~lookBack = 0;

+


+

p = Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, PSloop(

+

Prand((60..80), inf),

+

bufSize: 10, 

+

lookBack: { ~lookBack },

+

loopFunc: { |x| x + [0, 5] }

+

)

+

).play

+

)

+


+

// wait a bit to fill buffer

+

// loop with Function applied

+


+

~lookBack = 3

+


+

~lookBack = 6

+


+


+

// go on and stop

+


+

~lookBack = 0

+


+

p.stop

+


+


+


+

Ex. 4b:   loopFunc given as Pattern

+


+

// loopFunc also takes a pattern of Functions, in this case

+

// the arg loopFuncPerItem decides if next Functions are polled per item or per loop.

+


+

(

+

~lookBack = 0;

+

~loopFuncPerItem = true;  // or 1

+


+

p = Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, PSloop(

+

Prand((60..80), inf),

+

bufSize: 10, 

+

lookBack: { ~lookBack },

+

// every number gets a Function that adds an interval of that size

+

loopFunc: Pxrand((3..11), inf).collect { |x| { |y| y - [0, x].postln } },

+

loopFuncPerItem: { ~loopFuncPerItem }

+

)

+

).trace.play

+

)

+


+

// wait a bit to fill buffer

+

// start looping with Functions polled per item 

+


+

~lookBack = 3

+


+

~lookBack = 6

+


+


+

// one interval per loop

+


+

~loopFuncPerItem = false   // or 0

+


+

~lookBack = 9

+


+


+

// switch back to new interval per item

+


+

~loopFuncPerItem = 1

+


+


+

// go on and stop

+


+

~lookBack = 0

+


+

p.stop

+


+


+


+

Ex. 4c:   loopFunc vs. goFunc 

+


+

// Besides loopFunc a separate Function (or Pattern of Functions) can be defined for

+

// application outside loops via the goFunc arg.

+

// The arg loopFuncAsGoFunc decides if loopFunc should be taken for that task

+

// (and goFunc should be ignored in that case).

+


+

(

+

~lookBack = 0;

+

~loopFuncPerItem = false;  // one func per loop

+

~loopFuncAsGoFunc = false;  // start with dedicated goFunc

+


+

p = Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, PSloop(

+

Prand((60..80), inf),

+

bufSize: 10, 

+

lookBack: { ~lookBack },

+

// every number gets a Function that adds an interval of that size

+

loopFunc: Pxrand((3..11), inf).collect { |x| { |y| y - [0, x].postln } },

+

loopFuncPerItem: { ~loopFuncPerItem },

+

goFunc: { |x| [x, x + 2] },

+

loopFuncAsGoFunc: { ~loopFuncAsGoFunc }

+

)

+

).trace.play

+

)

+


+

// wait a bit to fill buffer

+

// start looping with Functions polled per loop 

+


+

~lookBack = 3

+


+

~lookBack = 6

+


+


+

// loop per item

+


+

~loopFuncPerItem = true

+


+


+

// prepare to use loopFunc when going on

+


+

~loopFuncAsGoFunc = true

+


+


+

// go on

+


+

~lookBack = 0

+


+


+

// switch to dedicated goFunc and stop

+


+

~loopFuncAsGoFunc = false

+


+

p.stop

+


+


+


+


+

Ex. 4d:   Turning loopFunc and goFunc on and off

+


+

// There are no separate PSloop flags needed for that:

+

// If loopFunc or goFunc return nil, no Function is applied,

+

// so suited Patterns using flags can be passed as loopFunc / goFunc args.

+


+

(

+

~lookBack = 0;

+

~loopFuncPerItem = false;  // one func per loop

+

~loopFuncAsGoFunc = false;  // start with dedicated goFunc

+


+

~turnOnLoopFunc = true;

+

~turnOnGoFunc = true;

+


+

p = Pbind(

+

\dur, Pn(Pshuf([1, 1, 2]/6)),

+

\midinote, PSloop(

+

Prand((60..80), inf),

+

bufSize: 10, 

+

lookBack: { ~lookBack },

+

// every number gets a Function that adds an interval of that size,

+

// if ~turnOnLoopFunc evaluates to false the stream returns nil instead of a Function

+

loopFunc: Pxrand((3..11), inf).collect { |x| ~turnOnLoopFunc.if { { |y| y - [0, x].postln } } },

+

loopFuncPerItem: { ~loopFuncPerItem },

+

goFunc: Pfunc { ~turnOnGoFunc.if { { |x| [x, x + 2] } } },

+

loopFuncAsGoFunc: { ~loopFuncAsGoFunc }

+

)

+

).trace.play

+

)

+


+

// wait a bit to fill buffer

+

// start looping with Functions polled per loop 

+


+

~lookBack = 3

+


+

~lookBack = 6

+


+


+

// when Functions are polled per loop, turning loopFunc off also works per loop

+


+

~turnOnLoopFunc = false

+


+


+

// use loopFunc again, namely per item

+


+

~turnOnLoopFunc = true

+


+

~loopFuncPerItem = true 

+


+


+

// now turning it off works immediately

+


+

~turnOnLoopFunc = false

+


+


+


+

// go on

+


+

~lookBack = 0

+


+


+

// as loopFunc is now turned off, this applies also when used for new values 

+


+

~loopFuncAsGoFunc = true

+


+


+

// switch to goFunc again

+


+

~loopFuncAsGoFunc = false

+


+


+

// silently turn loopFunc on

+


+

~turnOnLoopFunc = true

+


+


+

// turn goFunc off and again use loopFunc as goFunc

+


+

~turnOnGoFunc = false

+


+

~loopFuncAsGoFunc = true

+


+


+


+

// loop, again with loopFunc per item

+


+

~lookBack = 5

+


+


+

p.stop

+


+


+


+

Ex. 5:   PSloops controlling different sound params

+


+

// Overlapping of loops on different sound params can give interesting polyrhythmic structures

+


+

(

+

SynthDef(\psloop_1, { |out = 0, freq = 440, centDif = 5, rq = 0.1, cutoff = 1000, amp = 0.1

+

att = 0.01, sus = 0.0, rel = 0.01|

+

var sig = Saw.ar(freq * [1, (centDif * 0.01).midiratio], amp);

+

Out.ar(0, BPF.ar(sig, cutoff.clip(30, 8000)) * EnvGen.ar(Env.linen(att, sus, rel), doneAction: 2));

+

}, 0.01!6).add

+

)

+


+

(

+

// Function to generate a standard PSloop with lookBack arg to by controlled by env variable with suffix LB

+

// Pfunc could also be written as: PL((name ++ \LB).asSymbol)

+


+

~psLoop = { |src, name| PSloop(src, bufSize: 10, lookBack: Pfunc { currentEnvironment[(name ++ \LB).asSymbol] }) };

+


+

p = Pbind(

+

\instrument, \psloop_1,

+

\att, 0.01,

+

\amp, Pfunc { ~amp },

+

\sus, ~psLoop.(Prand([0.1, 0.2], inf), \sus),

+

\rel, ~psLoop.(Prand([0.01, 0.1, 1], inf), \rel),

+

\dur, ~psLoop.(Pn(Pshuf([1, 1, 2]/5)), \dur),

+

\centDif, Pshuf([5, -20, 40], inf),

+

\midinote, ~psLoop.(Pxrand((30..50), inf), \midinote),

+

\cutoff, ~psLoop.(Pshuf([500, 1500, 5000], inf), \cutoff)

+

);

+


+

q = Pbindf(p, \midinote, ~psLoop.(Pxrand((50..70), inf), \midinote));

+

r = Pbindf(p, \midinote, ~psLoop.(Pxrand((75..95), inf), \midinote));

+

  

+


+

// VarGui for control of lookBack params of 3 parallel streams

+

// zero values for args with suffix LB mean: no looping

+


+

// play loops by setting several params to non-zero values,

+

// you can grab params of different voices by using the alt key while

+

// moving a slider

+


+

VarGui({ |i| [

+

\susLB, [0, 7, \lin, 1, 0],

+

\relLB, [0, 7, \lin, 1, 0],

+

\durLB, [0, 7, \lin, 1, 0],

+

\midinoteLB, [0, 7, \lin, 1, 0],

+

\cutoffLB, [0, 7, \lin, 1, 0],

+

\amp, [0, 1, \lin, 0, 0.2 + (i * 0.1)]

+

] }!3, stream: [r, q, p], quant: 1/5

+

).gui(labelWidth: 80)

+

)

+


+


+


+


+ + diff --git a/Help/PSrecur.html b/Help/PSrecur.html new file mode 100755 index 0000000..dda8d99 --- /dev/null +++ b/Help/PSrecur.html @@ -0,0 +1,152 @@ + + + + + + + + + + + +

PSrecur Pattern to generate new values with a recursive Function 

+


+

Part of: miSCellaneous

+


+

Inherits from: PS / PStream

+


+

See also: MemoRoutine, PSx stream patterns, PS, PSdup, PSloop

+


+


+

Creation / Class Methods

+


+

*new (recurFunc, length, bufSize, start, copyItems, copySets)

+

+

Creates a new PSrecur object.

+

+

recurFunc - Recursive Function, the array of last values will be passed as first arg, count as second arg.

+

length - Number of output items, may be pattern or stream, defaults to inf.

+

bufSize - Size of buffer to store last values. 

+

If bufSize is not defined and start is not given or a single item, bufSize gets value 1.

+

If bufSize is not defined and start is an array bufSize gets its length.

+

start - Single start item or array ot items used for recursion.

+

copyItems - Determines if and how to copy items generated by recurFunc,

+

which are either non-Sets or member of Sets. 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). 

+

Other values are interpreted as 0. Defaults to 0.

+

0: original item 

+

1: copy item

+

2: deepCopy item

+

copySets - Determines if and how to copy Sets, generated by recurFunc. 

+

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). 

+

Other values are interpreted as 0. Defaults to 1.

+

0: original Set 

+

1: copy Set

+

+

NOTE 1: The distinction of copying items and sets makes sense in the case of event streams.

+

Per default Events are copied (copySets == 1), not their values (copyItems == 0). 

+

By playing Events those are used to store additional data (synth ids, msgFuncs …) 

+

which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use 

+

MemoRoutine - copied Events will not contain this additional data. 

+

If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured 

+

then copying makes no sense, this is the normal case, so copyItems defaults to 0.

+

When going to alter the ouput, you might want to set copyItems to 1 for a PSx returning 

+

simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set

+

copySets to 1 and copyItems to 2 (an option copySets == 2 doesn't exist as

+

it would be contradictory in combination with copyItems < 2).

+


+

NOTE 2: Copy options concern copying into PSrecur's buffer as well as 

+

the output of a Stream derived from the PSrecur. When such a Stream is outputting copies 

+

this prevents unintended altering of items stored in the buffer of the source. On the other hand

+

storing copies in PSrecur's buffer prevents these from being altered unintendedly.

+


+


+

Instance Methods

+


+

recurFuncStream, recurFuncStream_(value)

+

+

Instance variable getter and setter methods. 

+


+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// another way to generate the fibonacci sequence

+

// the first arg passed to the Function is the PSrecur itself,

+

// x[0] refers to its last value, x[1] to the value before the last value 

+


+

(

+

a = PSrecur({ |x| x[0] + x[1] }, start: [0, 1]);

+

b = a.iter;

+

b.nextN(10);

+

)

+


+

// shorter with partial application

+


+

PSrecur(_[0]+_[1], start: [0, 1]).iter.nextN(10);

+


+


+


+

// recursion with event patterns

+

// function calculates an iterated average of past event values for all keys,

+

// events at the end of the array are taken into account most.  

+


+

(

+

u = { |e,f| e.copy.keysValuesChange { |k| e[k]+f[k]/2 } };

+

v = { |a| a.reduce(u) };

+

)

+


+

(

+

// three basic Events

+


+

e = (midinote: 65, dur: 0.75, amp: 0.1, pan: 0);

+

f = (midinote: 58, dur: 0.05, amp: 0.3, pan: -1);

+

g = (midinote: 92, dur: 0.05, amp: 0.4, pan: 1);

+


+

// mostly build "blurred" average of last five events, 

+

// by chance disturbance by choosing a basic event

+


+

p = PSrecur({ |x| 0.8.coin.if { v.(x[..4]) }{ [e,f,f,g].choose } }, start: [e,f,e,g,e]);

+


+

q = p.trace.play;

+

)

+


+

q.stop;

+


+


+


+ + diff --git a/Help/PSx stream patterns.html b/Help/PSx stream patterns.html new file mode 100755 index 0000000..21f6c0a --- /dev/null +++ b/Help/PSx stream patterns.html @@ -0,0 +1,100 @@ + + + + + + + + + + + +

PSx stream patterns Pattern variants that have a state and can remember their last values

+


+

Part of: miSCellaneous

+


+

See also: MemoRoutine, PS, PSdup, PSrecur, PSloop

+


+


+

In general Patterns are thought to have no state. They are defining models for the really generative objects, Streams, which respond to the method next that causes them to return a next value. So, in general, an arbitrary number of Streams might be derived from one Pattern, all behaving as defined by the latter.

+

However there are cases where it is comfortable to have objects that behave like Streams, e.g. resume from their last state if embedded, and at the same time still benefit from everything which is already implemented for Patterns. PSx stream patterns are an attempt to accomplish this. PSx patterns behave like Streams - they resume from last state when repeatedly embedded -, they remember their last value (or a number of last values) and they are real Patterns by subclassing, i.e. operators defined for Patterns can be applied. Internally they use Stream's subclass MemoRoutine which performs value buffering.

+


+


+

PSx value and event pattern classes

+


+

PS, PSdup, PSrecur, PSloop

+


+


+

Example

+


+

(

+

p = Pseq([ 

+

PS(Pseq((1..9), inf), 3), 

+

PS((Pseries() + 1) * 100, Pseq([1,2,3], inf)) 

+

], inf);

+


+

q = p.asStream; 

+


+

q.nextN(50);

+

)

+


+

// ATTENTION:

+


+

// It is important to remember that, differing from normal 

+

// Pattern convention, repeatedly applying .asStream to a 

+

// PSx pattern or a Pattern that encloses a PSx doesn't cause a Stream to begin at the start.

+

// Every new Stream refers to the internally used and previously left off MemoRoutine.

+


+


+

p.asStream.nextN(5);

+


+

p.asStream.nextN(10);

+


+


+

// For getting a totally new Stream you can reevaluate the Pattern definition or

+

// define the Pattern with a wrapping Function:

+


+


+

(

+

a = { 

+

Pseq([ 

+

PS(Pseq((1..9), inf), 3), 

+

PS((Pseries() + 1) * 100, Pseq([1,2,3], inf)) 

+

], inf); 

+

};

+


+

b = a.value.asStream; 

+


+

b.nextN(50);

+

)

+


+

a.value.asStream.nextN(5);

+


+

a.value.asStream.nextN(10);

+


+


+


+ + diff --git a/Help/PV_BinGap.html b/Help/PV_BinGap.html new file mode 100755 index 0000000..41d6f9b --- /dev/null +++ b/Help/PV_BinGap.html @@ -0,0 +1,140 @@ + + + + + + + + + + + +

PV_BinGap pseudo ugen keeping the complement of a spectral range

+


+

Part of: miSCellaneous

+


+

Inherits from: PV_ChainUGen

+


+

Based on PV_BrickWall, but instead of wipe parameters it takes two bin numbers.

+


+

See also: PV_BinRange, FFT Overview

+


+


+

Creation / Class Methods

+


+

*new (buffer, loBin, hiBin)

+

+

Creates a new PV_BinGap object.

+

+

buffer - FFT buffer.

+

loBin - low bin index of excluded range.

+

hiBin - high bin index of excluded range.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// frequencies are rounded to nearest bins

+


+

(

+

f = { |loFreq = 800, hiFreq = 1500, fundFreq = 50, amp = 0.1|

+

var bufSize = 1024, binRange, loBin, hiBin, sig, chain;

+


+

sig = Saw.ar(fundFreq, amp);

+


+

binRange = s.sampleRate / bufSize;

+

loBin = (loFreq / binRange).round;

+

hiBin = (hiFreq / binRange).round;

+


+

chain = FFT(LocalBuf(bufSize), sig);

+

chain = PV_BinGap(chain, loBin, hiBin);

+

IFFT(chain) ! 2;

+

};

+


+

x = f.play;

+


+

s.freqscope;

+

)

+


+

x.set(\loFreq, 300);

+


+

x.set(\hiFreq, 3000);

+


+

x.release;

+


+


+


+

// for multichannel expansion an array of mono buffers must be provided

+


+

(

+

g = { |loFreq = 800, hiFreq = #[1500, 1500] fundFreq = 50, amp = 0.1|

+

var bufSize = 1024, binRange, loBin, hiBin, sig, chain;

+


+

sig = Saw.ar(fundFreq, amp);

+


+

binRange = s.sampleRate / bufSize;

+

loBin = (loFreq / binRange).round;

+

hiBin = (hiFreq / binRange).round;

+


+

chain = FFT({ LocalBuf(bufSize) } ! 2, sig);

+

chain = PV_BinGap(chain, loBin, hiBin);

+

IFFT(chain);

+

};

+


+

x = g.play;

+


+

s.freqscope;

+

)

+


+

x.set(\loFreq, 300);

+


+

x.set(\hiFreq, [1200, 1200]);

+


+

x.set(\hiFreq, [800, 2000]);

+


+

x.set(\hiFreq, [2000, 800]);

+


+

x.release;

+


+


+


+


+ + diff --git a/Help/PV_BinRange.html b/Help/PV_BinRange.html new file mode 100755 index 0000000..6050d92 --- /dev/null +++ b/Help/PV_BinRange.html @@ -0,0 +1,141 @@ + + + + + + + + + + + +

PV_BinRange pseudo ugen keeping a spectral range

+


+

Part of: miSCellaneous

+


+

Inherits from: PV_ChainUGen

+


+

Based on PV_BrickWall, but instead of wipe parameters it takes two bin numbers.

+


+

See also: PV_BinGap, FFT Overview

+


+


+

Creation / Class Methods

+


+

*new (buffer, loBin, hiBin)

+

+

Creates a new PV_BinRange object.

+

+

buffer - FFT buffer.

+

loBin - low bin index of resulting range.

+

hiBin - high bin index of resulting range.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// frequencies are rounded to nearest bins

+


+

(

+

f = { |loFreq = 800, hiFreq = 1500, fundFreq = 50, amp = 0.1|

+

var bufSize = 1024, binRange, loBin, hiBin, sig, chain;

+


+

sig = Saw.ar(fundFreq, amp);

+


+

binRange = s.sampleRate / bufSize;

+

loBin = (loFreq / binRange).round;

+

hiBin = (hiFreq / binRange).round;

+


+

chain = FFT(LocalBuf(bufSize), sig);

+

chain = PV_BinRange(chain, loBin, hiBin);

+

IFFT(chain) ! 2;

+

};

+


+

x = f.play;

+


+

s.freqscope;

+

)

+


+

x.set(\loFreq, 300);

+


+

x.set(\hiFreq, 1000);

+


+

x.release;

+


+


+


+

// for multichannel expansion an array of mono buffers must be provided

+


+

(

+

g = { |loFreq = #[500, 500], hiFreq = 1500, fundFreq = 50, amp = 0.1|

+

var bufSize = 1024, binRange, loBin, hiBin, sig, chain;

+


+

sig = Saw.ar(fundFreq, amp);

+


+

binRange = s.sampleRate / bufSize;

+

loBin = (loFreq / binRange).round;

+

hiBin = (hiFreq / binRange).round;

+


+

chain = FFT({ LocalBuf(bufSize) } ! 2, sig);

+

chain = PV_BinRange(chain, loBin, hiBin);

+

IFFT(chain);

+

};

+


+

x = g.play;

+


+

s.freqscope;

+

)

+


+

x.set(\loFreq, [200, 200]);

+


+

x.set(\loFreq, [300, 1200]);

+


+

x.set(\loFreq, [1200, 300]);

+


+

x.set(\hiFreq, 2000);

+


+

x.release;

+


+


+


+


+ + diff --git a/Help/PbindFx.html b/Help/PbindFx.html new file mode 100755 index 0000000..23ee722 --- /dev/null +++ b/Help/PbindFx.html @@ -0,0 +1,2171 @@ + + + + + + + + + + + +

PbindFx event pattern for effect handling on per-event base 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pchain

+


+See also: Introduction to miSCellaneous
, Buffer Granulation, Live Granulation, kitchen studies, PmonoPar, PpolyPar, DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFan, DXFanOut, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

PbindFx works like a normal Pbind of event type 'note' in most regards, but with the additional option to define a number of effects. Their order and parameters can also be defined with patterns which allows a great flexibility: for each event an arbitrary multichannel effect graph can be applied, mixing sequential and parallel arrangement ad libitum. This requires a relatively high amount of resource management: bus allocation, routing and node ordering as well as delayed cleanup have to be done for each event. All necessary bookkeeping is done automatically, for some critical parameters though it's the user's responsibility to pass meaningful values (e.g. cleanupDelay for reverb has to be defined sufficiently high, otherwise reverb synth and audio bus might be freed before reverb has ended). There is always a tradeoff between flexibility and processing effort, if you won't change fx parameters on a per-event base or you won't reorder your effect arrangement, then you might prefer playing effects from and to predefined buses and control them otherwise, e.g. per LFOs (additionally possible also with PbindFx) or dedicated setting streams, see PmonoPar and PpolyPar. However with the strategy of effects bound to buses you have the same effect arrangements and parameters concerning all source signals sent to the same bus, more variation with such setups needs more (pre-)definition of buses, whereas with PbindFx overlapping events can be processed with different effect arrangements and parameters with no explicit effort.  

+

One possible application of PbindFx is applying effects per grain, for the project kitchen studies I documented the source code of a fixed media piece in six parts, using this technique.

+


+

WARNING:

+

As bus allocation is done dynamically per event, there is a circumvented, but still potential danger of creating feedback loops. To prevent this, additional "zero synths" are started with bus-reading fx synths, playing a zero signal with ReplaceOut to the buses in question, they are placed before those fx / source synth(s), which will play there too. For all test examples, even with deliberately bad values, zero synths turned out to be an effective way to block unwanted input signals and feedback, as they last as long as fx / source synths (they even overlap them a bit). However, with extraordinary parameter values for timing, improperly defined fx / source synths, sloppy audio bus mapping and / or parallel actions that affect resource management globally, feedback, as in any situation of heavy bus repatching, can not be totally excluded. Be aware of that, avoid high levels and be careful with headphones !

+


+


+


+

Creation / Class Methods

+


+

*new (pbindData ... fxData)

+

+

Creates a new PbindFx object.

+

+

pbindData - SequenceableCollection of Pbind's key/value pairs or event pattern.

+

Passing a list saves explicitely typing Pbind, but passing an event pattern is more flexible,

+

as it allows replacement (Ex.7), event pattern filtering (Ex.3a) and similar operations.

+


+

specific keys:

+

+

\fxOrder - For each event it can be given in three ways, for single effects and

+

sequential ordering it may be 

+

(a) an Integer, or 

+

(b) a SequenceableCollection of Integers, 

+

indicating the effect order. Effect counting starts with 1, 0 means no effect (default).

+

E.g. if three effects are passed to fxData, [1], [2], [3], [1, 2], [1, 3], [2, 3], 

+

[2, 1], [3, 1], [3, 2], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1],

+

would represent all possible effect orders with each effect applied once. 

+

Passing single Integers is equivalent to passing them in an array.

+

Data concerning one event can also be given as

+

(c) a Ref object containing an IdentityDictionary, representing the effect graph. 

+

Keys of the dictionary – single Integers – are identified with effects of fxData, values

+

of the dictionary might be single Integers or SequenceableCollections of

+

Integers, indicating a branching of effects (whenever you want a branching of effects

+

or routing two different effects to a third one, the input must be given as a ref'd IdentityDictionary).

+

Within the effect graph an explicit direct out can be denoted with the Symbol \o,

+

0 represents the source. The graph must be acyclic, a check is performed.

+

fxOrder can also be a Pattern, sequencing effect orders of the forms (a) - (c).

+

See principles of operation below and examples 10 (a)-(c).

+

+

\cleanupDelay - Number or Pattern to generate Numbers, taken as seconds.

+

Its meaning differs, depending if source synth (passed by \instrument) has

+

a gated or fixed length envelope. If a gate control is encountered in the SynthDef

+

description, a gated envelope is assumed.

+

In the latter case this arg determines the earliest time after release when

+

cleanup may start. With a fixed-length envelope it determines the earliest time 

+

after synth start when cleanup may start.

+

With effects defined, cleanupDelays of source and effects are summarized.

+

Defaults to the class variable defaultSourceCleanupDelay which defaults to 0.3.

+

Typically you would take the releaseTime of the source synth's / synthdef's 

+

gated envelope, or the maximum overall length of the fixed-length envelope,

+

each plus a small delta.

+

+

\cleanupClock - The clock on which freeing of audio buses is scheduled with SkipJack objects.

+

As the clock should survive CmdPeriod its permanent flag must be set to true.

+

Per default the SystemClock is used, but for certain cases, e.g. granulation 

+

you might want to pass a TempoClock with higher queueSize, see examples below.

+

+

\cleanupDt - Number or Pattern to generate Numbers, taken as seconds.

+

Determines the delta time for cleanup with SkipJack objects. In those intervals 

+

it is checked whether event-specific cleanup delaytimes have been reached 

+

and cleanup (freeing buses) should be performed. Defaults to the class variable 

+

defaultCleanupDt which defaults to 0.2. With higher values more SkipJack objects

+

have to be scheduled at the same time, although with fewer activity.

+

+

\freePerGroup - Boolean, defaults to false.

+

Determines if effects and zero synths of the chain should be freed separately or

+

removed at the end of the chain by freeing the enclosing group.

+

With more effects and longer cleanupDelays the latter leads to a larger number

+

of parallel synths and might be wasteful thus. On the other hand it can be 

+

a useful option with short events and cleanupDelays, e.g. in case of granulation. 

+

See below timing scheme for a more detailled description.

+


+

\otherBusArgs - Takes SequenceableCollection of Symbols, defaults to nil.

+

For all passed symbols the source synth is enabled to read from and write to external buses,

+

which are passed to an arg of that name, LocalIn and LocalOut are allowed anyway.

+

Per default a restrictive check of synth I/O ugens and matching is performed in order to prevent

+

unintended reading from resp. writing to buses. With this option you can allow reading

+

and writing in a controlled way, see Ex. 6a and 6b.

+


+

+

fxData - SequenceableCollection(s) of key/value pairs defining effect sequencing or event pattern.

+

Passing a list saves explicitely typing Pbind, but passing an event pattern is more flexible,

+

as it allows replacement (Ex.7), event pattern filtering (Ex.3a) and similar operations.

+


+

specific keys:

+


+

\fx - Symbol or Pattern to generate Symbols.

+

Determines the effect synthdef.

+


+

\cleanupDelay - Number or Pattern to generate Numbers, taken as seconds.

+

Determines the earliest time after effect node delay when cleanup, 

+

including freeing the effect synth, may start.

+

With effects defined, cleanupDelays of source and effects are summarized.

+

Defaults to the class variable defaultFxCleanupDelay which defaults to 0.05.

+

Typically you would take the maximum delay of the effect synth plus a small delta. 

+

+

\otherBusArgs - Takes SequenceableCollection of Symbols, defaults to nil.

+

For all passed symbols the fx synth is enabled to read from and write to external buses,

+

which are passed to an arg of that name, LocalIn and LocalOut are allowed anyway.

+

Per default a restrictive check of synth I/O ugens and matching is performed in order to prevent

+

unintended reading from resp. writing to buses. With this option you can allow reading

+

in a controlled way, see Ex. 6a and 6b.

+


+


+

*defaultSourceCleanupDelay 

+

+

Get the value of this class variable. Defaults to 0.3.

+


+

*defaultSourceCleanupDelay_(value)

+

+

Set this class variable to value.

+


+

*defaultCleanupDt 

+

+

Get the value of this class variable. Defaults to 0.2.

+


+

*defaultCleanupDt_(value)

+

+

Set this class variable to value. 

+


+

*defaultFxCleanupDelay 

+

+

Get the value of this class variable. Defaults to 0.05.

+


+

*defaultFxCleanupDelay_(value)

+

+

Set this class variable to value. 

+


+


+


+


+

Principle of operation

+


+

Per-event effect routing with PbindFx, example scheme of two effects applied sequentially:

+


+

This can typically be achieved by passing an array [i, j] to \fxOrder, where i and j denote

+

arbitrary unequal positive effect numbers (numbers smaller or equal than the size of fxData).

+

+

+


+

Effects FX#1 and FX#2 read from buses BUS#1 and BUS#2 which are reserved for the duration 

+

of the event plus a cleanup time. BUS#1 and BUS#2 get their data from source synth SRC resp. FX#1. 

+

"Zero synths" Z#1 and Z#2 are placed before them in node order, they are lasting as long as 

+

(in fact a bit longer than) FX#1 and FX#2, playing zero signals to BUS#1 and BUS#2 with ReplaceOut and 

+

cancelling out possible residual signals on those buses, thus blocking feedback. 

+

BUS#1 and BUS#2 might be multichannel buses, in that case a number of mono zero synths 

+

is established for each Z#i. 

+

Two dedicated groups are generated for each event: FXGRP is the container for all

+

event-generated synths and SRCGRP is placed at its head. Z#1 is placed at the head of

+

SRCGRP, other zero synths and effects are sequentially placed upwards from the tail

+

of FXGRP, interlaced in the way described below.

+

Finally source synth(s) are placed at tail of SRCGRP (the group passed further to the note event).

+


+


+

node ordering for n = 2

+


+

FXGRP:

+

SRCGRP:

+

Z#1

+

SRC

+

Z#2 

+

FX#1

+

FX#2

+


+


+

You can also pass a group (or a pattern of groups) to PbindFx, in that case

+

FXGRP is related to the group depending on \addAction (default \addToHead):

+


+


+

general node ordering with n sequentially applied effects for n > 2 

+

group GRP passed to PbindFx with key \group, 

+

default addAction \addToHead

+


+

GRP:

+

  FXGRP:

+

SRCGRP:

+

Z#1

+

SRC

+

Z#2 

+

FX#1

+

...

+

...

+

Z#n

+

FX#n-1

+

FX#n

+


+


+

Note that effect order is arbitrary per event, determined by key \fxOrder, 

+

in these examples FX#i denote the order in the regarded event, 

+

not the order in which the effects are passed to PbindFx.

+

The whole process is implemented via dedicated event type 'pbindFx', 

+

an extension of event type 'note', which is chosen with PbindFx by default.

+

The following schemes refer to server-side timing, additional lang-side offset might be passed with 

+

lag (in seconds) to ensure proper timing for sequences combining delayed and non-delayed effects 

+

e.g. including occasional echo as in several of the examples below.

+


+


+

Timing scheme of PbindFx with two effects applied sequentially, 

+

source synth with gated envelope:

+


+


+

+

+

+

t#0: start SRC, Z#i, FX#i and groups with server-side ordering as described in routing scheme,

+

in fact Z#i are started slightly earlier.

+

t#1: begin of SRC envelope release period, caused by a release message (set gate arg to 0)

+

t#2: end of SRC envelope release period, probably end of source synth (doneAction = 2)

+

t#3: supposed latest end of source synth due to given cleanupDelay of source, added to t#1

+

(cleanupDelay might be passed as a constant maximum release time for all events)

+

t#4: with ~freePerGroup == false (default): freeing of FX#1 and Z#1, the latter a bit later.

+

with ~freePerGroup == true: do nothing

+

t#5: free FXGRP, thus also FX#2 and Z#2.

+


+

For longer effect chains with n effects the case distinction of t#4 applies to all pairs Z#i / FX#i for 1 < i < n:

+

with ~freePerGroup == true they all are freed at last by freeing the group.

+


+


+

Timing scheme of PbindFx with two effects applied sequentially, 

+

source synth with fixed-length envelope:

+


+


+

+

+


+

t#0: start SRC, Z#i, FX#i and groups with server-side ordering as described in routing scheme,

+

in fact Z#i are started slightly earlier.

+

t#1: begin of SRC envelope release period, caused by the synth itself (no setting gate to 0)

+

t#2: end of SRC envelope release period, probably end of source synth (doneAction = 2)

+

t#3: supposed latest end of source synth due to given cleanupDelay of source, added to t#0

+

(cleanupDelay might be passed as a constant maximum envelope length for all events)

+

t#4 - t#5: as with gated envelope

+


+


+

Parallel effect processing, arbitrary effect graphs:

+


+

The most simple case of applying two effects in parallel can be triggered by passing `(0: [1, 2]) to fxOrder.

+

Effect numbers occurring in arrays of dictionary values cause a routing from these effects to out.

+

Symbol \o (for destination out) can also be passed explicitely.

+


+

Branching like this requires the use of a split synth which routes the output to the demanded

+

multitude of fx buses. A suitable predefined split synthdef is chosen according to the number of branches

+

and the channel number of the signal to be splitted – currently both is limited by 8, however

+

synths, which are only playing to out directly, might have an arbitrary number of output channels.

+


+

Different to sequential fx processing, both fx bus zero synths have to be added before the source,

+

additional zero synth(s) for the split bus are prepended.

+


+


+


+

+


+

The following effect graph can be established by fxOrder `(0: [1, 2, 3], 2: [4, \o], 3: 2),

+

split synths, zero synths (for fx buses and split buses) are omitted in this scheme.

+

For each (acyclic) fx graph a topological order is calculated (here it could e.g. be [0,1,3,2,4])

+

and the node ordering, including FXGRP and SRCGRP, similar to the sequential case, 

+

is derived accordingly. Some small differences, especially concerning the cleanup delay times, 

+

have to be taken into account, these details are omitted here.

+


+

+

+


+

Conventions 

+


+

1.) Source instrument and effect SynthDefs must be known to SynthDescLib.global 

+

(e.g. by creating SynthDefs with methods 'add' or 'store')

+


+

2.) Fx SynthDefs must be defined with arg 'out' for the outbus index, arg 'in' for inbus index

+

and use In.ar(in, ...) within the SynthDef.

+

It's up to the user whether to define the effect with dry/wet mix option.

+

Effect synths don't need to have an envelope, their freeing is handled by the event type function.

+

+

3.) Effect chains must be defined properly in terms of in/out channel number.

+

For each event the bus matching of the effect chain is checked, following these conventions:

+

+

.) For source and fx SynthDefs there must be only one out ugen using 'out' as bus arg,

+

LocalOut is allowed, other out ugens can be admitted by \otherBusArgs.

+

.) Source SynthDefs must not have in ugens, except LocalIn or they are admitted by \otherBusArgs. 

+

.) Fxs must read from buses with ugen In.ar(in, ...) refering to the bus arg 'in', 

+

there must not be other in ugens within the fx SynthDef except LocalIn or 

+

they are admitted by \otherBusArgs, see Exs. 6a and 6b.

+

.) Number of out channels of preceeding source / fx synth must not be greater than 

+

the number of the following fx synth's in channels, this is checked for all pairs of an fx graph.

+

.) Mismatches of above points lead to errors.

+

+

4.) Checks of (3) are also based on info in SynthDescLib.global. It is possible to replace SynthDefs 

+

on the fly (Ex. 7a, 7b), in that case I'd recommend to use also methods 'add' or 'store' – 

+

e.g. in case of using 'send' for redefinition no bus matching check is performed and 

+

a possibly wrong routing would be undetected.

+

+

5.) As shown in above graphics the source synth's cleanupDelay is interpretated differentely 

+

with gated and non-gated (fixed-length) envelopes. This is done automatically by looking 

+

for a 'gate' arg in the SynthDef's SynthDesc. It's the users responsibility to use the 

+

conventional arg 'gate' for release in the SynthDef and omit a 'gate' arg with 

+

SynthDefs employing fixed-length envelopes.

+


+

6.) As with normal event patterns the source Pbind / Pbind pairs may be defined with arrays 

+

to produce multiple synths per event, then the whole source signal is routed to the fx graph 

+

(see Ex. 2b, 2c and others). fxData pairs can also be defined with arrays, which causes 

+

parallel processing per node ("implicit parallelism"), see Ex. 4a.

+

For applying different fxs to parallel events (resp. chords) use parallel PbindFxs (Exs. 3a, 3b). 

+

It is of course also possible that fx synths have array args and fx patterns are passing them 

+

with the double-bracketing convention used for these cases (see Event patterns and array args).

+

+

7.) Source instrument and effect SynthDefs are allowed to have args of same name. 

+

There is no problem as key/value pairs are stored in separate lists/events.

+

 

+

8.) PbindFx is implemented per automatically chosen effect type 'pbindFx', which employs 

+

event type 'note', thus an event type must neither be passed to pbindData nor to fxData.

+

+

9.) The value conversion framework can be used for fxData, e.g. by passing \midinote instead of

+

\freq or \db instead of \amp as with Pbind, see Ex.9. Other than with Pbind, freq and amp event defaults 

+

are not passed to fx synths, if no such values are passed via fxData. 

+

In that case default values of fx synths are taken.

+

+

10.) While playing PbindFx you should not play with allocation affecting private buses.

+

Freeing buses is done by SkipJack objects, so you should not forcefully stop 

+

all SkipJack objects while playing PbindFx.

+


+

11.) Keys \group and \addAction may be passed, they determine how the group enclosing 

+

event-generated synths is related to the passed group, see scheme 'Principles of operation'.

+

+

12.) Zero synths are using the SynthDefs 'pbindFx_zero' and 'pbindFx_splitZero' which 

+

are written to disk at startup time, as well as split synths pbindFx_split_axb for a = 2,...,8

+

and b = 1,...,8. Of course these SynthDefs shouldn't be deleted or exchanged.

+

+


+

Resources, troubleshooting

+


+

1.) You might encounter the error message "Meta_Bus:audio: failed to get an audio bus allocated." 

+

As audio buses for effect chains are allocated and freed per event a higher number of 

+

private audio buses is likely to be required (more than default 128). 

+

You might also encounter the error message "exception in real time: alloc failed, 

+

increase server's memory allocation (e.g. via ServerOptions)".

+

Hence it's recommended to set the concerned server options before (re-)booting and working with PbindFx, e.g.:

+

+

(

+

s.options.numPrivateAudioBusChannels = 1024;

+

s.options.memSize = 8192 * 16;

+

s.reboot;

+

)

+


+

2.) You might encounter the warning "Scheduler queue is full."

+

Per default delayed freeing of buses is scheduled on SystemClock, which defaults to queueSize 1024.

+

In case of granulation and/or large cleanup delays it's recommended to pass a cleanup clock with

+

sufficiently large queueSize via pbindData, its permanent flag must be set to true, e.g.:

+

+

...

+

\cleanupClock, t = TempoClock(queueSize: 8192).permanent_(true) 

+

...

+

+

Note that this clock keeps on running and survives CmdPeriod, you should explicitely stop it

+

if you don't need it anymore, hence it should be stored in a variable. In case you haven't done that

+

you can still stop all TempoClocks and remove them from CmdPeriod with

+

+

TempoClock.all.copy.do(_.stop)

+

+

3.) Cleanup delays for source and effects (or, if not passed, their default values) should be sufficiently large

+

(see description of \cleanupDelay), otherwise effects and audio buses might be freed too early

+

and signals cut.

+


+

4.) Some effects, like echo, introduce a delay. If sequencing mixes delayed events

+

with non-delayed events, entries of the latter have to be delayed accordingly to preserve correct timing, 

+

this can be done by setting an offset in seconds with \lag. See examples with echo below.

+


+


+


+

Example 1: Straight usage with unchanged effect order

+


+

Example 1a: Source synth with sustained envelope

+


+


+

// boot server with extended resources

+


+

(

+

s.options.numPrivateAudioBusChannels = 1024;

+

s.options.memSize = 8192 * 16;

+

s.reboot;

+

)

+


+

// basic source and effect synthdefs for this help file

+


+

(

+

// All ins and outs use two channels

+


+

// source synthdef

+

// take releaseTime = decayTime

+


+

SynthDef(\source, { |out = 0, freq = 400, decayTime = 0.5,

+

attackTime = 0.005, amp = 0.1, gate = 1|

+

var env, sig = Decay.ar(Impulse.ar(0), decayTime, Saw.ar(freq));

+

env = EnvGen.ar(Env.asr(attackTime, amp, decayTime, \lin), gate, doneAction: 2);

+

Out.ar(out, sig ! 2 * env)

+

}).add;

+


+


+

// spat fx

+

// This effect introduces a very small delay,

+

// in examples balancing by lag (as it obviously has to be done with echo) is neglected.

+


+

SynthDef(\spat, { |out, in, freq = 1, maxDelayTime = 0.005,

+

amp = 1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = DelayC.ar(

+

inSig,

+

maxDelayTime,

+

{ LFDNoise3.ar(freq, maxDelayTime, maxDelayTime/2) } ! 2,

+

amp

+

);

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+


+

// echo fx, always unified delay maxEchoDelta

+


+

SynthDef(\echo, { |out, in, maxEchoDelta = 0.2, echoDelta = 0.1,

+

decayTime = 1, amp = 1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = DelayL.ar( 

+

CombL.ar(inSig, maxEchoDelta, echoDelta, decayTime, amp), 

+

maxEchoDelta, 

+

maxEchoDelta - echoDelta

+

);

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+


+

// wah-wah fx

+


+

SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000,

+

cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = RLPF.ar(

+

inSig,

+

LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi),

+

rq,

+

amp

+

).softclip;

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+


+

// reverb fx

+

// rough estimation: freeVerb's room arg = decayTime / 10

+


+

SynthDef(\reverb, { |out, in, damp = 0.5,

+

decayTime = 10, amp = 1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

Out.ar(out, FreeVerb.ar(inSig, mix, min(decayTime, 10) / 10, damp, amp));

+

}).add;

+

)

+


+


+

// Fx params are sequenced on a per-event base.

+


+

// see server window: number of groups (divided by 2) indicates 

+

// the number of parallel event chains in action

+

// check while running with s.queryAllNodes

+


+

(

+

p = PbindFx([

+

\instrument, \source,

+

\dur, 0.25,

+

\amp, 0.2,

+

\midinote, Prand([

+

Pwhite(80, 90, 1),

+

Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf),

+

], inf),

+


+

\fxOrder, [1, 2], 

+

// With a sustained envelope \cleanupDelay refers to the maximum release time,

+

// in SynthDef \source releaseTime = decayTime, so take cleanupDelay = decayTime

+

\decayTime, Pwhite(0.2, 2),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

// oscillation of delay -> frequency modulation of source signal

+

\freq, Prand([1, 2, 3], inf),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

// variation by sequencing of params

+

\fx, \wah,

+

\mix, Pseq([0.2, 0.5, 0.7], inf),

+

\cutOffMoveFreq, Pseq([1, 2, 5, 10], inf),

+

\cleanupDelay, 0.01

+

]

+

);

+


+

q = p.play;

+

)

+


+

// see server window and compare "regular" stop 

+

// (descending number of groups, synths and ugens reflects delayed cleanup)

+

// with stopping the same example by Cmd-Period

+


+

q.stop;

+


+


+

Example 1b: Source synth with fixed-length envelope

+


+

// SynthDefs from Ex. 1a plus

+

// SynthDef variation with adsr args

+


+

(

+

SynthDef(\source_adsrFixed, { |out = 0, freq = 400, decayTime = 0.5, 

+

att = 0.005, dec = 0.01, sus = 0.2, rel = 0.3, susLevel = 0.5, amp = 0.1|

+

var env, sig = Saw.ar(freq);

+

env = EnvGen.kr(Env([0, 1, susLevel, susLevel, 0], [att, dec, sus, rel]), doneAction: 2);

+

Out.ar(out, sig ! 2 * env * amp)

+

}).add;

+

)

+


+

// adsr values passed, \cleanupDelay estimated as max of sum

+


+

(

+

p = PbindFx([

+

\instrument, \source_adsrFixed,

+

\dur, 0.25,

+

\att, Pwhite(0.005, 0.01),

+

\dec, Pwhite(0.01, 0.02),

+

\sus, Pwhite(0.02, 0.3),

+

\rel, Pwhite(0.2, 1.5),

+


+

\susLevel, 0.4,

+

\amp, 0.2,

+


+

\midinote, Prand([

+

Pwhite(80, 90, 1),

+

Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf),

+

], inf),

+


+

\fxOrder, [1, 2], 

+

+

// cleanupDelay must be larger than max env length, otherwise events might be cut !

+

// here we do an estimation (att + dec + sus + rel < 2),

+

// but it could be summed from those event values too, e.g. with

+

// \cleanupDelay, Pfunc { |e| e.att + e.dec + e.sus + e.rel }

+

\cleanupDelay, 2

+

],[

+

\fx, \spat,

+

\freq, Prand([1, 2, 3], inf),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \wah,

+

\mix, Pseq([0.2, 0.5, 0.7], inf),

+

\cutOffMoveFreq, Pseq([1, 2, 5, 10], inf),

+

\cleanupDelay, 0.01

+

]

+

);

+


+

q = p.play;

+

)

+


+


+

// check node order while running

+


+

s.queryAllNodes;

+


+

q.stop;

+


+


+

Example 2: Sequencing of different effect chains

+


+

Example 2a: Determined fx sequence

+


+

// PbindFx using spat and echo effects.

+

// Especially relevant keys here are \fxOrder which determines fx sequencing

+

// and \cleanupDelay for proper releaseTimes of source and effects

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

p = PbindFx([

+

\instrument, \source,

+

\dur, 0.5,

+

\amp, 0.3,

+

\midinote, Pwhite(50, 90),

+


+

// fx sequence \spat, \spat + \echo, etc.

+

\fxOrder, Pseq([1, [1,2]], inf),

+


+

// echo is delayed (maxEchoDelta = 0.2),

+

// compensate here by shift when no echo

+

// we need \lag rather than \timingOffset as the whole delaytime calculation refers to seconds

+

\lag, Pseq([0.2, 0], inf),

+


+

// in SynthDef \source releaseTime = decayTime, so take cleanupDelay = decayTime

+

\decayTime, Pseq([1, 0.1], inf),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

// define effect with index 1

+

\fx, \spat,

+

\maxDelayTime, 0.005,

+


+

// oscillation of delay -> frequence modulation of source signal

+

\freq, Pseq([1, 1, 10], inf),

+


+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

// define effect with index 2

+

\fx, \echo,

+

\echoDelta, 0.08,

+

\decayTime, Pwhite(0.8, 3),

+


+

\cleanupDelay, Pkey(\decayTime)

+

]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+

Example 2b: Random fx sequence

+


+

// If more than one synth per event is produced (here by key 'midinote'), 

+

// the effect chain is applied to all of them, see Ex. 3 for applying 

+

// different effects to parallel synths.

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

p = PbindFx([

+

\instrument, \source,

+

\dur, 0.2,

+

\amp, 0.3,

+


+

// downwards tendency + chord sequence

+

\midinote, Pseq((90, 80..50), inf) +

+

Pn(Pshuf([[0, 5], 0, [0, 2.5], [-2.5, 12.5], [-3, 0]])),

+


+

\fxOrder, Prand([1, [1,2], [1,2]], inf),

+


+

// lag must be adapted to maxEchoDelta

+

\lag, Pfunc { |e| e.fxOrder.isArray.if { 0 }{ 0.2 } },

+


+

// echo -> shorter source decay

+

\decayTime, Pfunc { |e| e.fxOrder.isArray.if { 0.1 }{ 0.7 } },

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, Pseq((1..5)/50, inf),

+

\decayTime, 1,

+

\cleanupDelay, Pkey(\decayTime)

+

]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+

Example 2c: Some extensions

+


+

// Additional use of rests, reverb added.

+

// Here the reverb usage is deliberately wasteful, see Ex.2d for an alternative.

+

// The use of Pn + Pshuf (or equivalently Pshufn) gives 

+

// balanced random variation for several key streams

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

p = PbindFx([

+

\instrument, \source,

+

\dur, Pn(Pshuf(0.2!5 ++ Rest(0.2))),

+


+

\midinote, Pseq((90, 80..40), inf) +

+

Pn(Pshuf([[0, 5], 0, [0, 2.5], [-2.5, 12.5], [-3, 0]])),

+


+

\fxOrder, Pn(Pshuf([[1,2], [1,2,3], [3,1], 1])),

+


+

\lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } },

+

\amp, Pfunc { |e| (e.fxOrder != [1,2]).if { 0.3 }{ 0.6 } },

+


+

\decayTime, Pfunc { |e| 

+

rrand(0.3, 0.8) / (e.fxOrder.asArray.includes(2).if { 10 }{ 1 }) 

+

},

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, Pn(Pshuf([1, 1, 1, 5, 20, 50])),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, Pseq((1..5)/50, inf),

+

\decayTime, Pwhite(0.3, 1.8),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \reverb,

+

\mix, 0.3,

+

\damp, 0.1,

+

\decayTime, Pwhite(3.0, 10),

+

\cleanupDelay, Pkey(\decayTime)

+

]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+

Example 2d: Saving resources 

+


+

// If effects have a long cleanup delay, you will get a possibly large number of

+

// overlapping effect chains. E.g. in Ex. 2c many reverb synths can be there in

+

// parallel, the decayTime is controlled by Pwhite(3.0, 10), so it might well be

+

// that reverbs with decayTimes 7.95, 8, and 8.1 are instantiated in parallel,

+

// which doesn't make much difference and is quite wasteful.

+

// Reverb is often placed at the last position of the effect chain,

+

// so a more efficient approach would be the following: do

+

// all effect sequencing without reverb with PbindFx and pipe

+

// the overall out to a permanently running reverb.

+


+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

// start two reverbs with different parameters, read from dedicated buses

+


+

(

+

a = Bus.audio(s, 2);

+

b = Bus.audio(s, 2);

+


+

x = Synth(\reverb, [mix: 0.3, damp: 0.1, decayTime: 3, in: a]);

+

y = Synth(\reverb, [mix: 0.2, damp: 0.1, decayTime: 10, in: b]);

+

)

+

  

+

// play PbindFx 

+

// compare CPU usage with Ex.2c

+


+

(

+

p = PbindFx([

+

\instrument, \source,

+

\dur, Pn(Pshuf(0.2!5 ++ Rest(0.2))),

+


+

\midinote, Pseq((90, 80..40), inf) +

+

Pn(Pshuf([[0, 5], 0, [0, 2.5], 0, [-2.5, 12.5], [-3, 0]])),

+


+

\fxOrder, Pn(Pshuf([1, 2, [1,2]])),

+


+

\lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } },

+

\amp, Pfunc { |e| (e.fxOrder != [1,2]).if { 0.3 }{ 0.6 } },

+


+

\decayTime, Pfunc { |e| 

+

rrand(0.3, 0.8) / (e.fxOrder.asArray.includes(2).if { 10 }{ 1 }) 

+

},

+

\cleanupDelay, Pkey(\decayTime),

+


+

// pipe out to different reverbs resp. 0 (no reverb)

+

\out, Pn(Pshuf([0, 0, a, a, b]))

+

],[

+

\fx, \spat,

+

\freq, Pn(Pshuf([1, 1, 1, 5, 20, 50])),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, Pseq((1..5)/50, inf),

+

\decayTime, Pwhite(0.3, 1.8),

+

\cleanupDelay, Pkey(\decayTime)

+

]

+

);

+


+

q = p.play;

+

)

+


+

// stop

+


+

q.stop;

+


+

// free extra resources

+


+

[x, y, a, b].do(_.free);

+


+


+


+

Example 3: Different effects for parallel synths

+


+

// This can be done with parallel PbindFxs

+


+

Example 3a: Using a template Pbind

+


+

// Here the option of passing a source Pbind instead of a list of Pbind pairs can be used.

+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

// master pattern, fxOrders defines fxOrder for voices of chord

+

f = Pbind(

+

\dur, 0.3,

+

\type, \rest,

+

\fxOrders, Pn(Pshuf([

+

[[1, 3], 2, 1], [1, [1, 3], 2], [2, 1, [1, 3]],

+

[0, 1, 1], [1, 0, 1], [1, 1, 0]

+

]).collect { |o| ~o = o })

+

);

+


+

// core source Pbind

+

a = Pbind(

+

\instrument, \source,

+

\dur, 0.3,

+


+

// reference to fxOrder will be got from Pchains below

+

\lag, Pfunc { |e| e.fxOrder.includes(2).if { 0 }{ 0.2 } },

+

\amp, Pfunc { |e| e.fxOrder.any([2,3].includes(_)).if { 0.3 }{ 0.1 } },

+


+

\decayTime, Pfunc { |e| rrand(0.5, 0.7) / (e.fxOrder.includes(2).if { 10 }{ 1 }) },

+

\cleanupDelay, Pkey(\decayTime)

+

);

+


+

// lists of fx pairs

+

b = [[

+

\fx, \spat,

+

\freq, Pn(Pshuf([1, 2, 3, 10])),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, 3/50,

+

\decayTime, Pwhite(0.3, 1.8),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \wah,

+

\mix, 0.5,

+

\cutOffMoveFreq, Pseq([5, 10], inf),

+

\cleanupDelay, 0.01

+

]];

+


+

// derive three Pchains from core Pbind, 

+

// voice-specific fxOrder will be read from master pattern

+

u = a <> Pbind(

+

\fxOrder, Pfunc { ~o[0].asArray },

+

\midinote, 72

+

);

+


+

v = a <> Pbind(

+

\fxOrder, Pfunc { ~o[1].asArray },

+

\midinote, Pstutter(Pwhite(5, 10), Prand((60..70), inf))

+

);

+


+

w = a <> Pbind(

+

\fxOrder, Pfunc { ~o[2].asArray },

+

\midinote, Pstutter(Pwhite(5, 10), Prand((75..85), inf))

+

);

+


+


+

// play in parallel, master must be before

+


+

d = 0.001;

+


+

q = Ptpar([

+

0, f,

+

d, PbindFx(u, *b),

+

d, PbindFx(v, *b),

+

d, PbindFx(w, *b)

+

]).play;

+

)

+


+

q.stop;

+


+


+


+

Example 3b: Using a PbindFx generator Function

+


+

// Equivalent to Ex. 3a, but might look more straight, as 

+

// fxOrder pair already generated with PbindFx Function.

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

// master pattern for fxOrder

+

f = Pbind(

+

\dur, 0.3,

+

\type, \rest,

+

\fxOrders, Pn(Pshuf([

+

[[1, 3], 2, 1], [1, [1, 3], 2], [2, 1, [1, 3]],

+

[0, 1, 1], [1, 0, 1], [1, 1, 0]

+

]).collect { |o| ~o = o })

+

);

+


+

// PbindFx generator

+


+

g = { |i| PbindFx([

+

\instrument, \source,

+

\dur, 0.3,

+


+

\fxOrder, Pfunc { ~o[i].asArray },

+

\lag, Pfunc { |e| e.fxOrder.includes(2).if { 0 }{ 0.2 } },

+

\amp, Pfunc { |e| e.fxOrder.any([2,3].includes(_)).if { 0.3 }{ 0.1 } },

+


+

\decayTime, Pfunc { |e| rrand(0.5, 0.7) / (e.fxOrder.includes(2).if { 10 }{ 1 }) },

+

\cleanupDelay, Pkey(\decayTime),

+

],[

+

\fx, \spat,

+

\freq, Pn(Pshuf([1, 2, 3, 10])),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, 3/50,

+

\decayTime, Pwhite(0.3, 1.8),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \wah,

+

\mix, 0.5,

+

\cutOffMoveFreq, Pseq([5, 10], inf),

+

\cleanupDelay, 0.01

+

]

+

);

+

};

+


+

// derive three Pbindfs from PbindFx generator,

+

// as PbindFx is a subclass of Pbind, you can apply Pbindf as to Pbind

+


+

u = Pbindf(g.(0), \midinote, 72);

+

v = Pbindf(g.(1), \midinote, Pstutter(Pwhite(5, 10), Prand((60..70), inf)));

+

w = Pbindf(g.(2), \midinote, Pstutter(Pwhite(5, 10), Prand((75..85), inf)));

+


+

d = 0.001;

+

q = Ptpar([0, f, d, u, d, v, d, w]).play;

+

)

+


+

q.stop;

+


+


+


+

Example 4: Applying the same fx SynthDef more than once in a chain

+


+


+

Example 4a: Implicit duplication (= implicit parallelism)

+


+

// Within a graph node parallelism can be established in analogy to 

+

// the generation of multiple synths with Pbind: 

+

// by passing an array as value within fxData.

+


+

(

+

// pitchshift fx, result can easily be controlled by ear

+


+

SynthDef(\pitchShift, { |out, in, windowSize = 0.2, midiShift = 1,

+

    pitchDispersion = 0, timeDispersion = 0, amp = 1, mix = 1|

+

    var sig, inSig = In.ar(in, 2);

+


+

sig = PitchShift.ar(

+

inSig,

+

windowSize,

+

midiShift.midiratio,

+

pitchDispersion,

+

timeDispersion

+

);

+


+

    Out.ar(out, (1 - mix) * inSig + (sig * mix) * amp);

+

}).add;

+


+


+


+

p = PbindFx([

+

\instrument, \source,

+

\dur, 0.5,

+

\amp, 0.2,

+

\midinote, Pwhite(50, 80),

+


+

\fxOrder, 1,

+

        // With a sustained envelope \cleanupDelay refers to the maximum release time,

+

        // in SynthDef \source releaseTime = decayTime, so take cleanupDelay = decayTime

+

\decayTime, 1.5,

+

\cleanupDelay, Pkey(\decayTime)

+

    ],[

+

        // variation by sequencing of params

+

\fx, \pitchShift,

+

\mix, 0.8,

+

// implicitely generate parallel processing with different fx params

+

// the two fxs applied in parallel together with the source result in a sequence of major triads

+

\midiShift, [4, 7],

+

\windowSize, 0.04,

+

\timeDispersion, 0.005,

+

\cleanupDelay, 0.01

+

    ]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+

Example 4b: Explicit duplication

+


+

// By passing the same fx in different fxData args you have all options

+

// by defining the fx graph: in sequence, in parallel or however.

+

// Here is an example for explicit duplication in sequence,

+

// for defining explicit parallelism and arbitrary fx graphs see Ex. 10

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

p = PbindFx([

+

\instrument, \source,

+

\dur, 1/3,

+

\amp, Pseq([0.2, 0.4, 0.3], inf),

+

\midinote, Pseq([

+

Pwhite(35, 48, 1) + [-14, 0, 14],

+

Pshuf([60, 67, 70, 73], 1) + Pshuf([0, 12], 1) + [0, 4, 12],

+

], inf),

+


+

\fxOrder, Pseq([1, [1, 2], [1, 2, 3]], inf),

+

\lag, Pseq([0.17, 0.03, 0.00], inf),

+


+

\decayTime, Pseq([2.5, 0.1, 0.1], inf),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, Prand([1, 2, 3], inf),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, Pwhite(0.06, 0.1),

+

\decayTime, 1,

+


+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \echo,

+

// short delta results in additional frequency

+

\echoDelta, Pwhite(0.005, 0.01),

+

\decayTime, 0.2,

+


+

\cleanupDelay, Pkey(\decayTime)

+

]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+


+

Example 5: Tempo control

+


+

// Tempo control works as with Pbind and can be 

+

// influenced by a number of parameters.

+


+

// As cleanup parameters of PbindFx are passed in seconds,

+

// tempo control can be done independent from making cleanup time changes

+

// (though you might do so as well)

+


+

// The use of a dedicated TempoClock as master tempo control is a good idea.

+

// Setting tempo for individual streams can be done with 'stretch'

+


+

// The following example employs a PbindFx generator Function,

+

// the tempo of streams can be controlled individually and generally.

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

// PbindFx generator Function,

+

// streams will get different midinote offsets,

+

// tempo will be read from a passed array

+


+

g = { |midiOffset, tempoArray, index|

+


+

PbindFx([

+

\instrument, \source,

+

\dur, 0.25,

+

\stretch, Pfunc { 1 / tempoArray[index] },

+

\midinote, Prand([

+

Pwhite(55, 75, 1),

+

Pn(Pshuf([60, 67, 70, 73])),

+

], inf) + midiOffset,

+


+

\fxOrder, Pn(Pshuf([1, 1, [1, 2], [1, 3]])),

+

\lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } },

+

\amp, 0.2,

+


+

\decayTime, Pfunc { |e| rrand(0.3, 0.8) / (e.fxOrder.asArray.includes(2).if { 10 }{ 1 }) },

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, 2,

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, Pseq((1..5)/50, inf),

+

\decayTime, Pwhite(0.3, 1.8),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \wah,

+

\mix, 0.5,

+

\resLo, 400,

+

\reHi, 2000,

+

\cutOffMoveFreq, 10,

+

\cleanupDelay, 0.01

+

]

+

) };

+


+

// master TempoClock

+


+

t = TempoClock.new;

+


+


+

// array for individual tempo factors

+


+

~tempo = [1, 1, 2]/2;

+


+

// start first player on quant grid,

+

// it refers to the tempo factor ~tempo[0]

+

// note that quant refers to the tempo of the TempoClock

+


+

x = g.(-24, ~tempo, 0).play(t, quant: 1);

+

)

+


+

// start other players on quant grid

+


+

y = g.(0, ~tempo, 1).play(t, quant: 1);

+


+

z = g.(12, ~tempo, 2).play(t, quant: 1);

+


+


+

// change player's individual tempos,

+

// note that tempo changes do not necessarily happen on quant grid,

+

// so a rhythmic "phase shift" (which can be nice) might happen

+


+

~tempo[2] = 2/3

+


+

~tempo[1] = 1/3

+


+

~tempo[0] = 1

+


+


+

// set general tempo

+


+

t.tempo = 3/4

+


+


+

// stop players individually

+


+

x.stop;

+


+

y.stop;

+


+

z.stop;

+


+


+

// stop clock

+


+

t.stop;

+


+


+


+


+

Example 6: Further external routing

+


+

// This means the use of buses, which are not internally used by PbindFx's event Function.

+

// As shown in Ex.2d it can be useful to route PbindFx's out to an external reverb,

+

// Vice versa data can be read from external buses, from source synths as well as from fx synths.

+

// Audio routing benefits from the possibility to pass groups to PbindFx.

+

// Then all temporary groups, generated during playing the PbindFx,

+

// are enclosed by it and node order can be clearly defined.

+

// For audio bus routing better use In.ar than mapping with asMap,

+

// that way matching of ins and outs of fx chains is checked and it avoids 

+

// issues with using asMap and stopping the external source (occuring at least in SC 3.6.6). 

+


+


+

Example 6a: Source synth reading audio modulation signals from external buses

+


+

// source synth based on SinOsc and additional ring modulation,

+

// while reading from an audio bus with no signal, it outputs only the sine

+


+

(

+

SynthDef(\source_mod, { |out = 0, freq = 400, decayTime = 0.5,

+

attackTime = 0.005, amp = 0.1, modIn, gate = 1|

+

var env, sig;

+

sig = Decay.ar(Impulse.ar(0), decayTime, SinOsc.ar(freq) * (1 + In.ar(modIn)));

+

env = EnvGen.ar(Env.asr(attackTime, amp, decayTime, \lin), gate, doneAction: 2);

+

Out.ar(out, sig ! 2 * env)

+

}).add;

+

)

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

// new group and bus for the modulation signal

+

g = Group.new;

+

a = Bus.audio(s, 1);

+


+

// play source with spat effect only

+

p = PbindFx([

+

\instrument, \source_mod,

+

\dur, 0.25,

+

\amp, 0.1,

+

\midinote, Prand([

+

Pwhite(80, 90, 1),

+

Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf),

+

], inf),

+

+

\fxOrder, 1, 

+


+

\modIn, a,

+

// to enable this reading the fx chain check must know

+

\otherBusArgs, [\modIn],

+

\group, g,

+


+

\decayTime, Pwhite(0.2, 2),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, Prand([1, 2, 10], inf),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

]

+

);

+


+

q = p.play;

+

)

+


+


+

// while playing PbindFx, play modulation synth to dedicated bus,

+

// placing before group ensures that all generated synths can read from the bus

+


+

x = { Out.ar(a, SinOsc.ar(500, 0, 0.5)) }.play(target: g, addAction: \addBefore);

+


+

// no modulation again

+


+

x.free;

+


+


+

// modulation with other signal

+


+

y = { Out.ar(a, Pulse.ar(700, 0.5, 0.5)) }.play(target: g, addAction: \addBefore);

+


+


+

// add a further modulation signal

+


+

z = { Out.ar(a, Formant.ar(700, 1200, mul: 0.5)) }.play(target: g, addAction: \addBefore);

+


+


+

// formant modulation only

+


+

y.free;

+


+

// stop it also

+


+

z.free;

+


+


+

// stop and PbindFx cleanup

+


+

q.stop;

+


+


+

// cleanup of other resources, free bus and group

+


+

(

+

g.free;

+

a.free;

+

)

+


+


+

Example 6b: Fx synths reading audio modulation signals from external buses

+


+

(

+

// simple sine source synth

+

SynthDef(\source_sine, { |out = 0, freq = 400, decayTime = 0.5,

+

attackTime = 0.005, amp = 0.1, gate = 1|

+

var env, sig;

+

sig = Decay.ar(Impulse.ar(0), decayTime, SinOsc.ar(freq));

+

env = EnvGen.ar(Env.asr(attackTime, amp, decayTime, \lin), gate, doneAction: 2);

+

Out.ar(out, sig ! 2 * env)

+

}).add;

+


+

// ring modulation fx synth, expecting to read the modulation signal from a bus

+

SynthDef(\ring, { |out, in, modIn, amp = 2, mix = 0.5|

+

var sig, inSig = In.ar(in, 2);

+

sig = inSig * In.ar(modIn, 1);

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+

)

+


+

// spat SynthDef from Ex. 1a, see also extended server resources defined there

+


+

(

+

// new group and buses for the modulation signal

+

g = Group.new;

+


+

a = Bus.audio(s, 1);

+

b = Bus.audio(s, 1);

+


+

// two ring modulation fxs

+

p = PbindFx([

+

\instrument, \source_sine,

+

\dur, 0.25,

+

\amp, 0.12,

+

\midinote, Prand([

+

Pwhite(80, 90, 1),

+

Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf),

+

], inf),

+


+

\group, g,

+


+

\fxOrder, Pn(Pshuf([1, [1, 2], [1, 3]])),

+

\decayTime, Pwhite(0.2, 2),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, Prand([1, 2, 10], inf),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \ring,

+

// need the index explicitely here

+

\modIn, a.index,

+

// to enable this reading the fx chain check must know

+

\otherBusArgs, [\modIn],

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \ring,

+

\modIn, b.index,

+

// to enable this reading the fx chain check must know

+

\otherBusArgs, [\modIn],

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

]

+

);

+


+

q = p.play;

+

)

+


+


+

// now live change of modulation as in Ex. 6a, but this applies only to one of three events

+


+

x = { Out.ar(a, SinOsc.ar(500, 0, 0.5)) }.play(target: g, addAction: \addBefore);

+


+


+

// also modulate events where fx reads from b

+


+

y = { Out.ar(b, Pulse.ar(700, 0.5, 0.5)) }.play(target: g, addAction: \addBefore);

+


+


+

// add a further modulation signal

+


+

z = { Out.ar(a, Formant.ar(700, 1200, mul: 0.5)) }.play(target: g, addAction: \addBefore);

+


+


+

// remove two others

+


+

x.free;

+


+

y.free;

+


+


+

// stop it also

+


+

z.free;

+


+


+

// stop and PbindFx cleanup

+


+

q.stop;

+


+


+

// cleanup of other resources, free buses and group

+


+

(

+

g.free;

+

a.free;

+

b.free;

+

)

+


+


+

Example 6c: Controlling effects with LFOs

+


+

// control the amount / mix of an effect continously

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

// bus for LFO control

+

c = Bus.control(s, 1);

+


+

// play source with spat and wah-wah effect,

+

// as initially bus value is zero there is no wah at start

+


+

p = PbindFx([

+

\instrument, \source,

+

\dur, 0.25,

+

\amp, 0.15,

+

\midinote, Prand([

+

Pwhite(80, 90, 1),

+

Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf),

+

], inf),

+

\fxOrder, [1, 2], 

+

\decayTime, Pwhite(0.2, 2),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, 2, 

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \wah,

+

// maps each fx synth's mix input to the control bus

+

\mix, c.asMap,

+

// other params might still be controlled per event

+

\cutOffMoveFreq, Pseq([5, 20], inf),

+

\cleanupDelay, 0.01

+

]

+

);

+


+

q = p.play;

+

)

+


+

// chime in with wah from 0 (start phase == -pi/2)

+


+

x = { Out.kr(c, SinOsc.kr(0.15, -pi/2).range(0, 0.8)) }.play

+


+


+

// stop and free resources

+


+

(

+

q.stop;

+

x.free;

+

c.free;

+

)

+


+


+

Example 7: Replacement

+


+

// Replacement can affect certain key streams only or whole source resp.

+

// fx patterns, the latter can be done with using the option of 

+

// passing source / fx patterns instead of lists.

+


+

Example 7a: Replacement restricted to key streams

+


+

// This can be done with Pbind + Pdefn or Pbind + PL,

+

// a specific possibility with PbindFx is the replacement of \fxOrder

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

~midi = PLseq([60, 60, 60, 62]);

+

~fxs = PLseq([0, 0, 1, 3]);

+


+

// Pdefn(\midi, Pseq([60, 60, 60, 62], inf));

+

// Pdefn(\fxs, Pseq([0, 0, 1, 3], inf));

+


+

p = PbindFx([

+

\instrument, \source,

+

\dur, 0.25,

+

\midinote, PL(\midi), // Pdefn(\midi),

+

\fxOrder, PL(\fxs), // Pdefn(\fxs),

+


+

\lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } },

+

\amp, 0.15,

+


+

\decayTime, 0.2,

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, 2,

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, 0.06,

+

\decayTime, Pwhite(0.3, 1.8),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \wah,

+

\cutOffMoveFreq, Pseq([5, 20], inf),

+

\cleanupDelay, 0.01

+

]

+

);

+


+

q = p.play;

+

)

+


+


+

// exchange midinote and effect sequencing on the fly

+


+

~midi = PLseq([60, 62, 63]);

+


+

~fxs = PLseq([1, [1, 2], [1, 3], [1, 2, 3]]);

+


+

(

+

~midi = PLshufn([60, 62, 63]) +

+

PLshufn([-24, -12, 0]) +

+

PLshufn([0, [0, 7], [0, 7, 12]]);

+

)

+


+


+

// stop and free resources

+


+

q.stop;

+


+


+

Example 7b: Replacement of source and fx patterns

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

// define source and effect patterns

+


+

~midi = PLseq([60, 60, 62, 63]);

+

~fxs = PLseq([1, [1, 2], [1, 3], [1, 2, 3]]);

+


+

~src = Pbind(

+

\instrument, \source,

+

\dur, 0.25,

+

\midinote, PL(\midi),

+

\fxOrder, PL(\fxs),

+


+

\lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } },

+

\amp, 0.15,

+


+

\decayTime, 0.2,

+

\cleanupDelay, Pkey(\decayTime)

+

);

+


+


+

~fx1 = Pbind(

+

\fx, \spat,

+

\freq, 2,

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

);

+


+

~fx2 = Pbind(

+

\fx, \echo,

+

\echoDelta, 0.06,

+

\decayTime, Pwhite(0.3, 1.8),

+

\cleanupDelay, Pkey(\decayTime)

+

);

+


+

~fx3 = Pbind(

+

\fx, \wah,

+

\cutOffMoveFreq, Pseq([5, 20], inf),

+

\cleanupDelay, 0.01

+

);

+


+

// pass PLx or Pdef placeholder patterns to PbindFx

+

p = PbindFx(PL(\src), PL(\fx1), PL(\fx2), PL(\fx3));

+


+

q = p.play;

+

)

+


+


+


+

(

+

// chorus fx

+


+

SynthDef(\chorus, { |out, in, amp = 1, loDelay = 0.001, hiDelay = 0.005,

+

maxDelayTime = 0.1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

inSig = Mix.fill(10, { |i|

+

DelayL.ar(inSig, maxDelayTime, LFDNoise3.ar(2).range(loDelay, hiDelay))

+

});

+

sig = inSig * amp / 2;

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+

)

+


+

// replace on the fly, fxOrder sequencing stays the same, but effect changes

+


+

(

+

~fx3 = Pbind(

+

\fx, \chorus,

+

\maxDelayTime, 0.01,

+

\loDelay, 0.001,

+

\hiDelay, 0.01,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

);

+

)

+


+

(

+

// new source SynthDef

+


+

SynthDef(\source_pulse, { |out = 0, freq = 400, decayTime = 0.5,

+

attackTime = 0.005, amp = 0.1, gate = 1|

+

var env, sig;

+

sig = Decay.ar(Impulse.ar(0), decayTime, Pulse.ar(freq));

+

env = EnvGen.ar(Env.asr(attackTime, amp, decayTime, \lin), gate, doneAction: 2);

+

Out.ar(out, sig ! 2 * env)

+

}).add;

+

)

+


+

// replace source, building of phrases with rests

+


+

(

+

~src = Pbind(

+

\instrument, \source_pulse,

+

\dur, Pn(Pshuf([1, 1, 2, 2, 2, 2, 2, 4, Rest(5)], inf)) / 8,

+

\midinote, PL(\midi) + Pn(Pshuf([-24, -19, 0, 19, 24], inf)),

+

\fxOrder, PL(\fxs),

+


+

\lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } },

+

\amp, 0.1,

+


+

\decayTime, 0.1,

+

\cleanupDelay, Pkey(\decayTime)

+

);

+

)

+


+

q.stop;

+


+


+

Example 7c: Replacement with Pbindef

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

// source and fxs passed as Pbindefs

+


+

// list of instrument symbols

+

i = [\src, \fx1, \fx2, \fx3];

+


+

Pbindef(\src,

+

\instrument, \source,

+

\dur, 0.25,

+

\midinote, Pseq([60, 60, 60, 62], inf),

+

\fxOrder, Pseq([0, 0, 1, 3], inf),

+


+

\lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } },

+

\amp, 0.15,

+


+

\decayTime, 0.2,

+

\cleanupDelay, Pkey(\decayTime)

+

);

+


+

Pbindef(\fx1,

+

\fx, \spat,

+

\freq, 2,

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

);

+


+

Pbindef(\fx2,

+

\fx, \echo,

+

\echoDelta, 0.06,

+

\decayTime, Pwhite(0.3, 1.8),

+

\cleanupDelay, Pkey(\decayTime)

+

);

+


+

Pbindef(\fx3,

+

\fx, \wah,

+

\cutOffMoveFreq, Pseq([5, 20], inf),

+

\cleanupDelay, 0.01

+

);

+


+


+

p = PbindFx(*i.collect { |x| Pbindef(x) });

+


+

q = p.play;

+

)

+


+


+

// replace some of source's key streams

+


+

(

+

Pbindef(\src,

+

\midinote, Pn(Pshuf([60, 62, 63])) +

+

Pn(Pshuf([-24, -12, 0])) +

+

Pn(Pshuf([0, [0, 7], [0, 7, 12]])),

+

\fxOrder, Pseq([[1, 3], [1, 2, 3]], inf)

+

)

+

)

+


+

// replace some of an effect's key streams

+

// note: this kind of multiple key replacement in Pbindef doesn't work with SC 3.5

+


+

(

+

Pbindef(\fx3,

+

\fx, \wah,

+

[\resLo, \resHi], Pwrand([[200, 300], [1500, 2000]], [0.8, 0.2], inf),

+

\cutOffMoveFreq, Pseq([1, 5, 20], inf),

+

\cleanupDelay, 0.01

+

)

+

)

+


+

q.stop;

+


+


+

// before playing again do Pbindef cleanup

+


+

i.do { |x| Pbindef(x).clear };

+


+


+


+

Example 8: GUI control

+


+

// control of fx params with VarGui

+

// control of fx sequencing by code

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

// ensure we are in top envir

+

currentEnvironment = topEnvironment;

+


+

// pattern for fxOrder sequencing

+

// we want to exchange this on the fly later on

+


+

~fxs = PLshufn([1, 2, 3, [2, 3], [1, 2, 3]]);

+


+

p = PbindFx([

+

\instrument, \source,

+

\dur, PL(\dur),

+

\degree, PLshufn(\degree),

+

// Spec returns a float, so write like this

+

\octave, Pfunc { rrand(~octaveLo.asInteger, ~octaveHi.asInteger) },

+


+

// we want to read from code in top envir

+

\fxOrder, PL(\fxs, envir: topEnvironment),

+


+

\lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } },

+

\amp, PL(\amp),

+


+

\attackTime, PLwhite(\attackTimeLo, \attackTimeHi),

+

\decayTime, PLwhite(\decayTimeLo, \decayTimeHi),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, PLwhite(\spatFreqLo, \spatFreqHi),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, PL(\echoDelta),

+

\decayTime, PLwhite(\echoDecayLo, \echoDecayHi),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \wah,

+

\resLo, PLwhite(\wahResLo, \wahResHi),

+

\cutOffMoveFreq, PL(\wahCutOffMoveFreq),

+

\mix, PL(\wahMix),

+

\cleanupDelay, 0.01

+

]

+

);

+


+

v = VarGui([

+

\degree, { |i| [0, 6, \lin, 1, i+2] }!4,

+

\octaveLo, [3, 6, \lin, 1, 4],

+

\octaveHi, [3, 6, \lin, 1, 6],

+

\dur, [0.2, 0.3, \lin, 0, 0.2],

+

\attackTimeLo, [0.01, 0.1, \lin, 0, 0.02],

+

\attackTimeHi, [0.01, 0.1, \lin, 0, 0.05],

+

\amp, [0.0, 0.5, \lin, 0, 0.2],

+


+

\decayTimeLo, [0.1, 0.5, \lin, 0, 0.2],

+

\decayTimeHi, [0.1, 0.5, \lin, 0, 0.4],

+


+

\spatFreqLo, [0.1, 10, \lin, 0, 0.5],

+

\spatFreqHi, [0.1, 10, \lin, 0, 2],

+


+

\echoDelta, [0.03, 0.1, \lin, 0, 0.05],

+

\echoDecayLo, [0.1, 2, \lin, 0, 0.3],

+

\echoDecayHi, [0.1, 2, \lin, 0, 1.8],

+


+

\wahCutOffMoveFreq, [0, 10, \lin, 0, 5],

+

\wahResLo, [100, 3000, \exp, 0, 200],

+

\wahResHi, [100, 3000, \exp, 0, 2000],

+

\wahMix, [0, 1, \lin, 0, 1]

+

], stream: p

+

).gui(

+

sliderWidth: 350,

+

labelWidth: 120,

+

varColorGroups: (0..20).clumps([12, 2, 3, 4]);

+

);

+

)

+


+

// start playing with gui and test params

+

// change of echoDelta necessarily causes a delay as 

+

// delays for events without echo have to be adapted

+


+


+

// change effect order sequencing

+


+

~fxs = PLseq([1, 1, 2, 2, 3, 3]);

+


+

// only spat + wah

+


+

~fxs = [1, 3];

+


+


+

// no effects

+


+

~fxs = 0;

+


+


+

// kind of polyrhythm with effects and pitches

+

// all 4 pitch classes are permanently reordered by PLshufn

+

// fixed effect sequencing

+


+

~fxs = PLseq([1, [1, 2], [1, 2, 3]]);

+


+


+

// stop by gui or explicitely

+


+

v.streams[0].stop;

+


+


+

Example 9: Using value conversions with fx data

+


+

// Effects can produce their own characteristic frequencies.

+

// For this it can be practical to use Event's value conversion framework.

+


+

(

+

// filter bank effect, level of signal very much depends on input frequencies

+

SynthDef(\klank, { |out, in, freq = 400, add = 7, amp = 1, ringTime = 0.1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = DynKlank.ar(`[freq * [1, add.midiratio], nil, ringTime ! 2], inSig) * amp / 100;

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+

)

+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

p = PbindFx([

+

\instrument, \source,

+

\dur, 0.2,

+

\amp, Pseq([0.15, 0.1, 0.1], inf),

+

\midinote, Pn(Pshuf([36, 36, 48, 48, 60, 65, 67])) +

+

Pseq([Pn(0, 40), Pn(7, 10), Pn(-5, 10)], inf) +

+

Pn(Pshuf([0, 0, 0, [0, 7], [0, 9], [0, 14]])),

+

\decayTime, Pwhite(0.8, 1.5),

+

\fxOrder, Pn(Pshuf([1, [1, 2], [1, 2]])),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, Prand([1, 2, 3] / 5, inf),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \klank,

+

\octave, Pwhite(5, 8),

+

// passing notes instead of frequencies is more pleasant here

+

// resulting signal is louder if pitches are near overtones of source

+

\note, Pn(Pshuf([0, 4, 5, 7])), 

+

\add, Prand([7, 12], inf),

+

\decayTime, 0.1,

+

\ringTime, Pwhite(0.1, 0.3),

+

\mix, 0.7,

+

\cleanupDelay, Pkey(\ringTime)

+

]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+

Example 10: Parallel effects and arbitrary effect graphs

+


+

Example 10a: Parallel effects

+


+


+

// here source is routed to echo #1 and echo #2 in parallel,

+

// echo #1 (fx index 2) is a "classical" echo whereas echo #2 (fx index 3), 

+

// due to short echoDelta, results in an additional frequency.

+

// The output of echo #2 is routed to a wah-wah, echo #1 directly to out.

+


+

+


+


+

// SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

p = PbindFx([

+

\instrument, \source,

+

\dur, Pseq([Pn(0.2, { rrand(8, 12) }), Pwhite(2.0, 4.0, 1)], inf),

+

\amp, 0.3,

+

\midinote, Prand([

+

Pwhite(80, 90, 1),

+

Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf),

+

], inf),

+

+

\fxOrder, `(0: 1, 1: [2, 3], 3: 4),

+

// compare with this version, where echo #1 is less present, as it also goes to wah

+

// \fxOrder, `(0: 1, 1: [2, 3], 3: 4, 2: 4),

+

+

\decayTime, 0.1, 

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \spat,

+

\freq, Prand([0.1, 0.8], inf),

+

\maxDelayTime, 0.001,

+

\cleanupDelay, 0.1

+

],[

+

\fx, \echo,

+

\echoDelta, 0.1,

+

\decayTime, 3,

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \echo,

+

\echoDelta, Pwhite(0.01, 0.05),

+

\decayTime, 5,

+

\amp, 0.5,

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \wah,

+

\mix, 0.7,

+

\cutOffMoveFreq, Pseq([1, 2, 5, 10], inf),

+

\cleanupDelay, 0.05

+

]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+

Example 10b: Modulation graphs

+


+

// A generalized modulating effect node has two ins: carrier and modulator.

+

// Fx convention of PbindFx demands one single In ugen per fx synth, but two ins  

+

// can simply be handled by a 2-channel In ugen and hard-panned input signals.

+


+

(

+

// sine source

+

SynthDef(\sine_adsrFixed, { |out = 0, freq = 400, decayTime = 0.5,

+

att = 0.005, dec = 0.01, sus = 0.2, rel = 0.3, susLevel = 0.5, amp = 0.1|

+

var env, sig = SinOsc.ar(freq);

+

env = EnvGen.kr(Env([0, 1, susLevel, susLevel, 0], [att, dec, sus, rel]), doneAction: 2);

+

Out.ar(out, sig ! 2 * env * amp)

+

}).add;

+


+

// amplitude modulation synth

+

SynthDef(\ampMod, { |out, in, dev = 1, amp = 1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = inSig[0] * (inSig[1] * dev + DC.ar(1)) * amp;

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+

// phase modulation synth

+

SynthDef(\phaseMod, { |out, in, maxDelay = 0.1, dev = 1, amp = 1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = DelayC.ar(inSig[0], maxDelay, maxDelay * dev * inSig[1], amp);

+

Out.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+

// modulator synths, no Ins 

+


+

SynthDef(\sineM, { |out, in, freq = 100|

+

    Out.ar(out, [0, SinOsc.ar(freq)]);

+

}).add;

+


+

SynthDef(\pulseM, { |out, in, freq = 100, width = 0.5|

+

Out.ar(out, [0, Pulse.ar(freq, width, 2)]);

+

}).add;

+


+

SynthDef(\sawM, { |out, in, freq = 100|

+

Out.ar(out, [0, Saw.ar(freq)]);

+

}).add;

+

)

+


+


+

// blend of AM events

+


+

// spat SynthDef from Ex. 1a, see also extended server resources defined there

+


+

(

+

p = PbindFx([

+

\instrument, \sine_adsrFixed,

+

\dur, 1,

+

\susLevel, 1,

+

\att, 5,

+

\sus, 0,

+

\rel, 5,

+

\amp, 0.03,

+

\midinote, Pwhite(40, 80),

+

\fxOrder, `(0: 1, 3: 1, 1: 2),

+


+

\decayTime, 1,

+

\cleanupDelay, 12

+

],[

+

\fx, \ampMod,

+

\dev, Pwhite(0.1, 0.6)

+

],[

+

\fx, \spat,

+

\freq, Pwhite(0.2, 2),

+

\maxDelayTime, 0.005,

+

\cleanupDelay, Pkey(\maxDelayTime)

+

],[

+

\fx, \pulseM,

+

\freq, Pwhite(200, 1000)

+

]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+

Example 10c: Modulation graphs, changed per event

+


+

// fx graphs corresponding to fxOrder `(0:1, 4:1, 1:6) and `(0:1, 5:1, 1:6), src = \sine_adsrFixed:

+


+

+

+

+

// fx graph corresponding to fxOrder `(0:2, 3:2, 2:6), src = \sine_adsrFixed:

+

+

+


+

// SynthDefs from Ex. 10b

+

// spat SynthDefs from Ex. 1a, see also extended server resources defined there

+


+

(

+

p = PbindFx([

+

        \instrument, \sine_adsrFixed,

+

        \amp, 0.01,

+

\dur, 0.3,

+

        \susLevel, 1,

+

        \att, 0.01,

+

        \sus, 0.15,

+

\rel, Pwhite(0.3, 1.2),

+

        \amp, 0.05,

+


+

        \midinote, Pwhite(30, 60) + Prand([0, [0, -12.5]], 200),

+


+

// changes between amplitude (pulse and saw) and phase modulation (sine)

+


+

        \fxOrder, Pn(Pshuf([

+

                `(0:1, 4:1, 1:6),

+

                `(0:1, 5:1, 1:6),

+

                `(0:2, 3:2, 2:6)

+

            ])),

+


+

        // equivalent:

+

        // the source stream returns pairs, where the first number indicates

+

        // the modulation type and the second number the modulator,

+

        // the collect function packs the data into the right format of a ref'd Event.

+


+

        // \fxOrder, Pn(Pshuf([ [1, 4], [1, 5], [2, 3] ]))

+

        // .collect { |x| ().putPairs([0, x[0], x[1], x[0], x[0], 6]).asRef },

+


+

        \decayTime, 2,

+

        \cleanupDelay, Pkey(\decayTime)

+

    ],[

+

        \fx, \ampMod,

+

        \dev, Pwhite(0.1, 0.5)

+

    ],[

+

        \fx, \phaseMod,

+

        \dev, Pwhite(0.03, 0.05)

+

    ],[

+

        \fx, \sineM,

+

        \freq, Pwhite(150, 700)

+

    ],[

+

        \fx, \sawM,

+

        \freq, Pwhite(150, 700)

+

    ],[

+

        \fx, \pulseM,

+

        \freq, Pwhite(150, 700)

+

    ],[

+

        \fx, \spat,

+

        \freq, Pwhite(0.1, 1),

+

        \maxDelayTime, 0.005,

+

        \cleanupDelay, Pkey(\maxDelayTime)

+

    ]

+

);

+


+

q = p.play;

+

)

+


+

q.stop;

+


+


+ + diff --git a/Help/PlaceAll.html b/Help/PlaceAll.html new file mode 100755 index 0000000..d8445c2 --- /dev/null +++ b/Help/PlaceAll.html @@ -0,0 +1,112 @@ + + + + + + + + + + + +

PlaceAll Arbitrarily nested embedding of subarrays 

+


+

Part of: miSCellaneous

+


+

Inherits from: Ppatlace

+


+

PlaceAll is integrating Ppatlace and Place (taking items as well as Patterns) and allows an arbitrary depth of nesting arrays.

+


+

See also: Place, Ppatlace

+


+


+

Creation / Class Methods

+


+

*new (list, repeats, offset)

+

+

Creates a new PlaceAll object.

+

+

list - Array which may contain subarrays. Leaves of the array tree may be 

+

Patterns, Streams or other Items to be embedded.

+

repeats - Number of list loops. Defaults to 1.

+

offset - List index offset. Defaults to 0.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

(

+

p = Pbind(

+

\midinote, PlaceAll([[60, 61], 70, [80, [81, 81.5], 82]], inf),

+

\dur, 0.2

+

);

+


+

x = p.play;

+

)

+


+

x.stop;

+


+


+

// to distinguish subarrays from arrays to be taken as output use Refs, 

+

// as wrapping in another array wouldn't do

+


+

(

+

p = Pbind(

+

\midinote, PlaceAll([[60, 61], 70, [80, `[81, 81.5], 82]], inf),

+

\dur, 0.2

+

);

+


+

x = p.play;

+

) 

+


+


+

x.stop;

+


+


+

// Items may also be Patterns or Streams

+


+

(

+

p = Pbind(

+

\midinote, PlaceAll([[60, 61], 70, [80, Pwhite(84.0, 89), 82]], inf),

+

\dur, 0.2

+

);

+


+

x = p.play;

+

) 

+


+


+

x.stop;

+

 

+


+ + diff --git a/Help/PmonoPar.html b/Help/PmonoPar.html new file mode 100755 index 0000000..025a147 --- /dev/null +++ b/Help/PmonoPar.html @@ -0,0 +1,174 @@ + + + + + + + + + + + +

PmonoPar monophonic event pattern for an arbitrary number of timed setting streams 

+


+

Part of: miSCellaneous

+


+

Inherits from: Plazy

+


+

See also: Pmono, PpolyPar, PbindFx

+


+

This is similar to Pmono, but allows an arbitrary number of differently timed setting streams in parallel.

+


+

History: PmonoPar and PpolyPar grew out of discussions on sc-users list, based on an example by Jonatan Liljedahl. Thanks to him, Ron Kuivila, user Monsieur and others for their comments on this – I then suggested classes PsetGroup and PsetFxGroup, which internally use Pgroup. Meanwhile I reworked the implementation, but it's still based on groups. I renamed PsetGroup to PmonoPar – as this makes the functionality more clear – and PsetFxGroup to PpolyPar, as it can be used with or without effect synths, the crucial point is the setting of parallel streams.

+


+

Creation / Class Methods

+


+

*new (setPatternPairs, defname, offset)

+

+

Creates a new PmonoPar object.

+

+

setPatternPairs - SequenceableCollection of SequenceableCollections containing key/value pairs.

+

Each of the inner collections represents the data of one synth setting stream.

+

Per convention key/value pairs written after a pair with \dur will cause setting, pairs before will not.

+

If keys \midinote, \note or \degree are occuring after \dur, they will be converted to a frequency value,

+

which will be used for setting the arg 'freq'.

+

defname - Symbol or String. Name of the SynthDef to be used for the synth being set.

+

Defaults to \default.

+

offset - Number. Offset to be taken for time-shifting synth init and streams. Defaults to 1e-6.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

Ex. 1a: PmonoPar with differently timed streams

+


+

// per convention keys after \dur are the ones to be set

+

// playing ends after end of last stream

+


+

(

+

p = PmonoPar([

+

[

+

\dur, 1.0,

+

\pan, Pser([-0.9, 0, 0.9], 8)  

+

],[

+

\dur, 0.4,

+

\freq, Pexprand(300, 1000, 24)   

+

],[

+

\dur, 0.2,

+

\amp, Pseq([0.05, 0.3], 30)  

+

]

+

]).trace.play;

+

)

+


+


+


+

Ex. 1b: Passing values by using the value conversion framework

+


+

// if \degree, \note or \midinote are occuring after \dur,

+

// a frequency value will be calculated according to Event's usual conversion framework

+

// and used for setting the arg 'freq'.

+


+

(

+

p = PmonoPar([

+

[

+

\dur, 1.0,

+

\pan, Pser([-0.9, 0, 0.9], 8)  

+

],[

+

\dur, 0.4,

+

\midinote, Pseq((70..75), 7)

+

],[

+

\dur, 0.2,

+

\amp, Pseq([0.05, 0.3], 30)  

+

]

+

]).trace.play;

+

)

+


+


+


+

Ex. 2: Data sharing between streams of PmonoPar

+


+

// data sharing with rhythms of coinciding entry points is sure as streams are time-shifted

+


+

(

+

p = PmonoPar([

+

[

+

\dur, Prand([1, 1, 2]/3, inf).collect(~dur = _).trace,   // or:  .collect { |x| ~dur = x } .trace

+

\amp, Pseq([0.2, 0.05], 15).trace   

+

],[

+

\dur, Pfunc { ~dur / 2 },

+

\midinote, Pshuf((60..85))   

+

]

+

]).trace.play;

+

)

+


+


+

Ex. 3: Data sharing between streams of parallel PmonoPars

+


+

// data sharing between streams of same PmonoPar and streams of second PmonoPar,

+

// use of Ptpar ensures that first stream of second PmonoPar comes after first stream of first PmonoPar,

+

// consider also PpolyPar for such type of usage

+


+

(

+

p = PmonoPar([

+

[

+

\dur, Prand([1, 1, 2]/3, 40).collect(~dur = _),

+

\amp, Pseq([0.3, 0.1], 15)   

+

],[

+

\dur, Pfunc { ~dur / 2 },

+

\midinote, Pshuf((50..70))   

+

]

+

]);

+


+

q = PmonoPar([

+

[

+

\dur, Pfunc { ~dur },

+

\amp, Pseq([0.3, 0.1], 15)   

+

],[

+

\dur, Pfunc { ~dur / 3 },

+

\midinote, Pshuf((75..95), 2)   

+

]

+

]);

+


+

r = Ptpar([0, p, 1e-5, q]).trace.play

+

)

+


+


+


+


+ + diff --git a/Help/PpolyPar.html b/Help/PpolyPar.html new file mode 100755 index 0000000..41a327a --- /dev/null +++ b/Help/PpolyPar.html @@ -0,0 +1,497 @@ + + + + + + + + + + + +

PpolyPar polyphonic event pattern for an arbitrary number of timed setting streams 

+


+

Part of: miSCellaneous

+


+

Inherits from: Plazy

+


+

See also: Pmono, PmonoPar, PbindFx

+


+

This is similar to PmonoPar and allows an arbitrary number of monophonic streams as well as an arbitrary number of differently timed setting streams applied to them in parallel. Each setting action can affect an arbitrary combination of running synths. PpolyPar can be used for polyphonic sources alone as well as in combination with effects.

+


+

History: PmonoPar and PpolyPar grew out of discussions on sc-users list, based on an example by Jonatan Liljedahl. Thanks to him, Ron Kuivila, user Monsieur and others for their comments on this – I then suggested classes PsetGroup and PsetFxGroup, which internally use Pgroup. Meanwhile I reworked the implementation, but it's still based on groups. I renamed PsetGroup to PmonoPar – as this makes the functionality more clear – and PsetFxGroup to PpolyPar, as it can be used with or without effect synths, the crucial point is the setting of parallel streams.

+


+


+

Creation / Class Methods

+


+

*new (setPatternPairs, defNames, order, offset)

+

+

Creates a new PpolyPar object.

+

+

setPatternPairs - SequenceableCollection of SequenceableCollections containing key/value pairs.

+

Each of the inner collections represents the data of one synth setting stream.

+

Per convention key/value pairs written after a pair with \dur will cause setting, pairs before will not.

+

If keys \midinote, \note or \degree are occuring after \dur, they will be converted to a frequency value,

+

which will be used for setting the arg 'freq'.

+

Also per convention, if the number of setting streams (the size of setPatternPairs) equals the number

+

of synths (the size of defNames), the settings of stream i will automatically affect synth i.

+

If sizes are unequal, each collection of key/value pairs needs a pair with key \synths and a value.

+

This value can be an Integer, meaning synth i, or a SequenceableCollection of Integers, in which case all of those

+

synths will be affected by the setting. The value can also be a Pattern, so that synths to be set

+

might change from event to event (see examples).

+

defNames - SequenceableCollection of Symbols or Strings. Names of the SynthDefs to be used for the synths being set.

+

Defaults to #[\default].

+

order - SequenceableCollection of Integers indicating the order of nodes passed to defNames.

+

Defaults to nil, in that case an order 0, ... , (defNames.size - 1) is assumed, i.e. synth i comes before synth i+1.

+

offset - Number. Offset to be taken for time-shifting synth inits and streams. Defaults to 1e-6.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

Ex. 1a: PpolyPar with different instruments

+


+

// basic SynthDefs, EnvGate invents a gate arg which is necessary for release

+


+

(

+

SynthDef(\saw, { |freq = 400, freqlag = 0.0, amp = 0.1, amplag = 0.01|

+

Out.ar(0, Saw.ar(Lag.kr(freq, freqlag), VarLag.kr(amp, amplag, warp: 1)) ! 2 * EnvGate());

+

}).add;

+


+

SynthDef(\pulse, { |freq = 400, freqlag = 0.0, amp = 0.1, amplag = 0.05|

+

Out.ar(0, Pulse.ar(Lag.kr(freq, freqlag), mul: VarLag.kr(amp, amplag, warp: 1)) ! 2 * EnvGate());

+

}).add;

+


+

SynthDef(\sine, { |freq = 400, freqlag = 0.0, amp = 0.1, amplag = 0.05|

+

Out.ar(0, SinOsc.ar(Lag.kr(freq, freqlag), 0, mul: VarLag.kr(amp, amplag, warp: 1)) ! 2 * EnvGate());

+

}).add;

+

)

+


+

// simple usage, each stream is setting the corresponding instrument

+


+

// per convention keys after \dur are the ones to be set

+

// note that you'd need \freq (the SynthDef arg) 

+


+

(

+

p = PpolyPar([[

+

\dur, 1/6,

+

\amp, Pwrand([0, 0.07], [1, 7]/8, inf),

+

\midinote, Pshuf((30..68), inf)

+

],[

+

\dur, Prand([1, 1, 2]/4, inf),

+

\amp, Pwrand([0, 0.07, 0.1], [0.3, 0.3, 0.4], inf),

+

\freqlag, 0.2,

+

\amplag, Prand([0.2, 0.4], inf),

+

\midinote, Pbrown(70, 90, 5)

+

]],

+

[\saw, \pulse]

+

).play;

+

)

+


+

p.stop;

+


+


+

Ex. 1b: PpolyPar with different instruments and overlapping settings

+


+

// SynthDefs from Ex. 1a

+


+

(

+

// Function to create midinote patterns in different ranges

+


+

r = { |add = 0| Pstutter(5, Pwhite(60, 70) + add) };

+


+

// each setting stream affects two synths: those of corresponding indices of \synths,

+

// but only with one value: the Integer polled from the Pstutter stream

+


+

p = PpolyPar([[

+

\synths, [0, 1],

+

\dur, 1/6,

+

\midinote, r.(),

+

\amp, 0.04

+

],[

+

\synths, [0, 2],

+

\dur, 1/4,

+

\midinote, r.(10),

+

\amp, 0.03

+

],[

+

\synths, [1, 2],

+

\dur, 1/3,

+

\midinote, r.(20),

+

\amp, 0.05

+

]],

+

[\saw, \pulse, \sine]

+

).play

+

)

+


+

p.stop;

+


+


+

Ex. 1c: PpolyPar with different instruments, overlapping settings with array dispatch

+


+

// SynthDefs from Ex. 1a

+


+

(

+

// Similar to Ex. 1b, but the midinote pattern will cause the stream to generate arrays of two elements,

+

// they will be distributed to the indicated synths

+


+

r = { |add = 0, int = 7| Pstutter(5, Pwhite(60, 65) + [0, int] + add) };

+


+

// each setting streams affects two synths: those of corresponding indices of \synths

+


+

p = PpolyPar([[

+

\synths, [0, 1],

+

\dur, 1/6,

+

\midinote, r.(0, 5),

+

\amp, 0.04

+

],[

+

\synths, [0, 2],

+

\dur, 1/4,

+

\midinote, r.(10, 6),

+

\amp, 0.03

+

],[

+

\synths, [1, 2],

+

\dur, 1/3,

+

\midinote, r.(20, 7),

+

\amp, 0.05

+

]],

+

[\saw, \pulse, \sine]

+

).play

+

)

+


+

p.stop;

+


+


+

Ex. 2a: PpolyPar with one effect synth

+


+

(

+

// With t_gate a percussive envelope will be triggered,

+

// so articulation can be achieved within a monophonic stream.

+

// This is similar to Pbind, though only if envelopes are shorter than entry time differences. 

+


+

SynthDef(\test, { |out = 0, freq = 440, att = 0.01, rel = 0.1, amp = 0.1, t_gate = 1|

+

var sig = Saw.ar(freq, amp), delayedSig;

+

sig = sig!2 * EnvGen.ar(Env.perc(att, rel), t_gate);

+

// add some spatial variance by LFO on delaytime

+

delayedSig = DelayL.ar(sig, delaytime: { LFDNoise3.kr(0.5).range(0.005, 0.02) } ! 2);

+

Out.ar(out, delayedSig * EnvGate())

+

}).add;

+


+

// Effect synthdefs with in and out bus,

+

// both get an EnvGate which introduces a gate arg for proper release,

+

// one could also add a gate arg and an EnvGen using it.

+


+

// wet/dry-relation is fixed, considering the example with fx chain a bypass arg is introduced

+


+

SynthDef(\echo, { |out = 0, in, maxdtime = 0.2, dtime = 0.2, decay = 3, amp = 0.5, bypass = 0|

+

var sig, insig;

+

insig = In.ar(in, 2);

+

sig = CombL.ar(insig, maxdtime, dtime, decay, amp, add: insig) * EnvGate();

+

Out.ar(out, bypass * insig + ((1 - bypass) * sig));

+

}).add;

+


+


+

SynthDef(\wah, { |out = 0, in, freqLo = 200, freqHi = 5000, modFreq = 10, amp = 0.7, bypass = 0|

+

var sig, insig;

+

insig = In.ar(in, 2);

+

sig = RLPF.ar(

+

insig,

+

LinExp.kr(LFDNoise1.kr(modFreq), -1, 1, freqLo, freqHi),

+

0.1,

+

amp,

+

insig * 0.3

+

).softclip * 0.8 * EnvGate();

+

Out.ar(out, bypass * insig + ((1 - bypass) * sig));

+

}).add;

+

)

+


+


+

// Whereas node order is done by PpolyPar, bus handling is the user's responsibility,

+

// it looks more flexible to me to define buses separately.

+


+

// one fx

+


+

b = Bus.audio(s, 2);

+


+

(

+

p = PpolyPar([[

+

\dur, 0.5,

+

\amp, 0.2,

+

\out, b,

+

\t_gate, 1,

+

\midinote, Pwhite(50, 100)

+

],[

+

// dur = inf causes just a running fx synth, none of its args is set by a stream

+

\dur, inf,

+

\in, b,

+

\dtime, 0.1,

+

\decay, 3

+

]],

+

[\test, \echo]

+

).play

+

)

+


+

p.stop;

+


+

b.free;

+


+

+

Ex. 2b: PpolyPar with more effect synths

+


+

// SynthDefs from Ex. 2a

+


+

(

+

b = Bus.audio(s, 2);

+

c = Bus.audio(s, 2);

+

)

+


+


+

// still none of the effects is set by a stream

+


+

(

+

p = PpolyPar([[

+

// values before \dur are not sent to server, so do this work here:

+

// echo (out b) is coupled with short release time

+

// wah (out c) is coupled with longer release time

+

\data, Prand([[b, 0.1], [c, 0.5]], inf),

+

\dur, Prand([1, 1, 2]/5, inf),

+

\amp, 0.3,

+

// data dispatch from above, these values will be sent

+

\rel, Pkey(\data).collect(_[1]),

+

\out, Pkey(\data).collect(_[0]),

+

\t_gate, 1,

+

\midinote, Pwhite(50, 100)

+

],[

+

\dur, inf,

+

\in, b,

+

\out, 0,

+

\dtime, 0.1,

+

\decay, 3

+

],[

+

\dur, inf,

+

\in, c,

+

\amp, 0.3

+

]],

+

[\test, \echo, \wah]

+

).play;

+

)

+


+

p.stop;

+


+

(

+

b.free;

+

c.free;

+

)

+


+


+

Ex. 2c: PpolyPar with more effect synths and streamed setting

+


+

// SynthDefs from Ex. 2a

+


+

(

+

b = Bus.audio(s, 2);

+

c = Bus.audio(s, 2);

+

)

+


+


+

// effects set by streams

+


+

(

+

p = PpolyPar([[

+

// values before \dur are not sent to server, so do this work here:

+

// echo (out b) is coupled with short release time

+

// wah (out c) is coupled with longer release time

+

\data, Prand([[b, 0.1], [c, 0.5]], inf),

+

// will get "bars" of length 4/5

+

\dur, Pn(Pshuf([1, 1, 2]/5)),

+

\amp, 0.3,

+

// data dispatch from above, these values will be sent

+

\rel, Pkey(\data).collect(_[1]),

+

\out, Pkey(\data).collect(_[0]),

+

\t_gate, 1,

+

\midinote, Pwhite(50, 100)

+

],[

+

\dur, 4/5,

+

\in, b,

+

// change delaytime per "bar", random add avoids repeating echo frequencies

+

\dtime, Pshuf([1, 2, 4]/40, inf) + (Pwhite(-0.5, 0.5)/40),

+

\decay, 3

+

],[

+

\dur, 4/5,

+

\in, c,

+

// change modFreq per "bar"

+

\modFreq, Pshuf((1..20), inf),

+

\amp, 0.3

+

]],

+

[\test, \echo, \wah]

+

).play

+

)

+


+

p.stop;

+


+

(

+

b.free;

+

c.free;

+

)

+


+


+

Ex. 2d: PpolyPar with more effect synths, streamed setting and more than one setting stream per synth

+


+

// SynthDefs from Ex. 2a

+


+

(

+

b = Bus.audio(s, 2);

+

c = Bus.audio(s, 2);

+

)

+


+


+

// Now we have more setting streams than synths,

+

// so we need to define which synth is to be set by which stream,

+

// this done via the \synths key, which must be contained in every collection of pairs.

+


+

(

+

p = PpolyPar([[

+

\synths, 0,

+

// values before \dur are not sent to server, so do this work here:

+

// echo (out b) is coupled with short release time

+

// wah (out c) is coupled with longer release time

+

\data, Prand([[b, 0.1], [c, 0.5]], inf),

+

// will get "bars" of length 4/5

+

\dur, Pn(Pshuf([1, 1, 2]/5)),

+

\amp, 0.3,

+

// data dispatch from above, these values will be sent

+

\rel, Pkey(\data).collect(_[1]),

+

\out, Pkey(\data).collect(_[0]),

+

\t_gate, 1

+

],[

+

// stream setting frequency 

+

\synths, 0,

+

\dur, 1/20,

+

\midinote, Pshuf((45..90), inf)

+

],[

+

\synths, 1,

+

\dur, 4/5,

+

\in, b,

+

// change delaytime per "bar", random add avoids repeating echo frequencies

+

\dtime, Pshuf([1, 2, 4]/40, inf) + (Pwhite(-0.5, 0.5)/40),

+

\decay, 3

+

],[

+

\synths, 2,

+

\dur, 4/5,

+

\in, c,

+

// change modFreq per "bar"

+

\modFreq, Pshuf((1..20), inf),

+

\amp, 0.3

+

]],

+

[\test, \echo, \wah]

+

).play

+

)

+


+

p.stop;

+


+

(

+

b.free;

+

c.free;

+

)

+


+


+

Ex. 2e: PpolyPar with effect chain and streamed fx repatching

+


+

// SynthDefs from Ex. 2a

+


+

(

+

b = Bus.audio(s, 2);

+

c = Bus.audio(s, 2);

+

)

+


+

// Here effects are chained in order - see buses passed to \in and \out

+

// Again more setting streams than synths, so the \synths key is needed.

+


+

(

+

p = PpolyPar([[

+

\synths, 0, // source synth

+

// will get "bars" of length 4/5

+

\dur, Pn(Pshuf([1, 1, 2]/5)),

+

\amp, 0.3,

+

\rel, 0.2,

+

\out, b,

+

\t_gate, 1

+

],[

+

// freq rhythm might differ from envelope rhythm (stream 0)

+

\synths, 0,

+

\dur, Pn(Pshuf([1, 1, 2]/5)),

+

\midinote, Pshuf((45..90), inf)

+

],[

+

\synths, 1, // echo

+

\dur, 4/5,

+

\in, b, // gets from source and sends to wah

+

\out, c,

+

// change delaytime per "bar", random add avoids repeating echo frequencies

+

\dtime, Pshuf([1, 2, 4]/20, inf) + (Pwhite(-0.5, 0.5)/20),

+

\decay, 3

+

],[

+

\synths, 2, // wah

+

\dur, 4/5,

+

\in, c, // gets from echo, sends to out 0 by default

+

// change modFreq per "bar"

+

\modFreq, Pshuf((1..20), inf),

+

\amp, 0.3

+

],[

+

// this stream determines effects in action by setting bypass args of both fx synths

+

\synths, [1, 2],

+

\dur, 1/5, 

+

// alternate bypassing of wah synth

+

\bypass, Pseq([[0, 0], [0, 1]], inf)

+

]],

+

[\test, \echo, \wah]

+

).play

+

)

+


+

p.stop;

+


+

(

+

b.free;

+

c.free;

+

)

+


+


+


+


+ + diff --git a/Help/Pshufn.html b/Help/Pshufn.html new file mode 100755 index 0000000..7e2e89d --- /dev/null +++ b/Help/Pshufn.html @@ -0,0 +1,76 @@ + + + + + + + + + + + +

Pshufn Pshuf with continuing permutations 

+


+

Part of: miSCellaneous

+


+

Inherits from: Pn

+


+

Variation of Pshuf which scrambles the list with every repeat. 

+


+

See also: Pshuf, PLshuf, PLshufn, PLx suite

+


+


+

Creation / Class Methods

+


+

*new (list, repeats)

+

+

Creates a new Pshufn object.

+

+

list - list to be scrambled.

+

repeats - number of permutations. Defaults to 1.

+


+

+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

(

+

p = Pbind(

+

\midinote, Pshufn((70..73), inf),

+

\dur, 0.2

+

);

+

)

+


+

x = p.play;

+


+

x.stop;

+


+


+ + diff --git a/Help/Psieve.html b/Help/Psieve.html new file mode 100755 index 0000000..1d5cbbb --- /dev/null +++ b/Help/Psieve.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +

Psieve Abstract superclass of sieve patterns

+


+

Part of: miSCellaneous

+


+

Inherits from: Pattern

+


+

PSVx sieve patterns inherit from Psieve. Normally you don't have to use Psieve directly.

+


+

See also: Sieves and Psieve patterns, Sieve, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+

Instance Methods

+


+

embedInStream

+

+ + diff --git a/Help/PsymNilSafe.html b/Help/PsymNilSafe.html new file mode 100755 index 0000000..ec1feca --- /dev/null +++ b/Help/PsymNilSafe.html @@ -0,0 +1,88 @@ + + + + + + + + + + + +

PsymNilSafe Psym variant that avoids hangs if all referenced patterns return nil

+


+

Part of: miSCellaneous

+


+

Inherits from: Psym

+


+

This adapts an idea of James Harkins' PnNilSafe (ddwPatterns quark) for Psym. If all patterns in the Dictinonary return nil, then Psym's embedInStream can produce an infinite loop, as it never yields. PnNilSafe can't be wrapped around Psym, but the check with logical time can be built into Psym itself. The wrapping into PsymNilSafe can shortly be written by applying 'symplay' instead of 'play'.

+


+

See also: Psym, PLx and live coding with Strings

+


+


+

Creation / Class Methods

+


+

*new (pattern, dict, maxNull)

+

+

Creates a new PsymNilSafe object. pattern expects a pattern of Symbols, dict the lookup dictionary and defaults to the current Environment. maxNull is the number of events with delta = 0 after which PsymNilSafe's method 'embedInStream' yields and thus stops a potentially endless loop. maxNull defaults to 128.

+

+


+

Instance Method for Class Pattern

+

 

+

aPattern.symplay(dict, clock, protoEvent, quant, maxNull = 128)

+

+

Play a pattern wrapped into a PsymNilSafe. Arguments clock, protoEvent and quant work as with playing a Pattern, arguments dict and maxNull work as with PsymNilSafe.new.

+

+

+

+

Example

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+

(

+

x = Pbind(\midinote, Pseries(60, 1, 10), \dur, 0.5).asStream;

+

y = Pbind(\midinote, Pseries(90, -1, 10), \dur, 0.5).asStream;

+


+

z = Pseq([2, 3, 1, 2], 1).asStream;

+


+

~a = Pfuncn({ x.next(()) }, { z.next });

+

~b = Pfuncn({ y.next(()) }, { z.next });

+


+

PsymNilSafe(Pseq("ab", inf)).trace.play;

+


+

// shorter with method 'symplay':

+

// trace indicates all events with delta = 0 (maxNull = 128) in the post window

+

// Pseq("ab", inf).trace.symplay

+


+


+

// ATTENTION: with Psym the example leads to a SC hang !

+

// Psym(Pseq("ab", inf), currentEnvironment).trace.play

+

)

+ + diff --git a/Help/Sieve.html b/Help/Sieve.html new file mode 100755 index 0000000..70ef5d5 --- /dev/null +++ b/Help/Sieve.html @@ -0,0 +1,720 @@ + + + + + + + + + + + +

Sieve Container class for sieve lists 

+


+

Part of: miSCellaneous

+


+

Container for a list of ascending integers as 'points' or 'intervals' .

+


+

See also: Sieves and Psieve patterns, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+


+

Creation / Class Methods

+


+

*newEmpty

+

+

Creates a new empty Sieve object.

+


+

*new (...data)

+

+

Creates a new Sieve object in mode 'points'.

+

+

data - A generator and an optional integer limit, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*new_i (...data)

+

+

Creates a new Sieve object in mode 'intervals'.

+

+

data - A generator and an optional integer limit, which is included when reached.

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*new_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offset.

+

+

data - A generator, an integer offset and an optional integer limit, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*new_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offset.

+

+

data - A generator, an integer offset and an optional integer limit, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*union (...data)

+

+

Creates a new Sieve object in mode 'points', generated by the union of sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*union_i (...data)

+

+

Creates a new Sieve object in mode 'intervals', generated by the union of sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*union_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offsets, generated by the union of sets of integers.

+

+

data - Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*union_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offsets, generated by the union of sets of integers.

+

+

data - Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*sect (...data)

+

+

Creates a new Sieve object in mode 'points', generated by the intersection of sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*sect_i (...data)

+

+

Creates a new Sieve object in mode 'intervals', generated by the intersection of sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*sect_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offsets, generated by the intersection of sets of integers.

+

+

data - Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*sect_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offsets, generated by the intersection of sets of integers.

+

+

data - Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*symdif (...data)

+

+

Creates a new Sieve object in mode 'points', generated by the symmetrical difference of sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*symdif_i (...data)

+

+

Creates a new Sieve object in mode 'intervals', generated by the symmetrical difference of sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*symdif_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offsets, generated by the symmetrical difference of sets integers.

+

+

data - Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*symdif_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offsets, generated by the symmetrical difference of sets of integers.

+

+

data - Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*dif (...data)

+

+

Creates a new Sieve object in mode 'points', generated by the difference of sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*dif_i (...data)

+

+

Creates a new Sieve object in mode 'intervals', generated by the difference of sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

*dif_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offsets, generated by the difference of sets of integers.

+

+

data - Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

*dif_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offsets, generated by the difference of sets of integers.

+

+

data - Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+


+

Instance Methods

+


+

union (...data)

+

+

Creates a new Sieve object in mode 'points', generated by the union of the receiver and sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

| (...data)

+

+

Binary operator for instance method union

+

+

union_i (...data)

+

+

Creates a new Sieve object in mode 'intervals', generated by the union of the receiver and sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

|* (...data)

+

+

Binary operator for instance method union_i

+


+

union_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offsets, generated by the union of the receiver and sets of integers.

+

+

data - Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

union_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offsets, generated by the union of the receiver and sets of integers.

+

+

data - Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

sect (...data)

+

+

Creates a new Sieve object in mode 'points', generated by the intersection of the receiver and sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

& (...data)

+

+

Binary operator for instance method sect

+

+

sect_i (...data)

+

+

Creates a new Sieve object in mode 'intervals', generated by the intersection of the receiver and sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

&* (...data)

+

+

Binary operator for instance method sect_i

+

+

sect_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offsets, generated by the intersection of the receiver and sets of integers.

+

+

data - Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

sect_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offsets, generated by the intersection of the receiver and sets of integers.

+

+

data - Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

symdif (...data)

+

+

Creates a new Sieve object in mode 'points', generated by the symmetrical difference of the receiver and sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

-- (...data)

+

+

Binary operator for instance method symdif

+

+

symdif_i (...data)

+

+

Creates a new Sieve object in mode 'intervals', generated by the symmetrical difference of the receiver and sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

--* (...data)

+

+

Binary operator for instance method symdif_i

+

+

symdif_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offsets, generated by the symmetrical difference of the receiver and sets of integers.

+

+

data - Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

symdif_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offsets, generated by the symmetrical difference of the receiver and sets of integers.

+

+

data - Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

dif (...data)

+

+

Creates a new Sieve object in mode 'points', generated by the difference of the receiver and sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

- (...data)

+

+

Binary operator for instance method dif

+

+

dif_i (...data)

+

+

Creates a new Sieve object in mode 'intervals', generated by the difference of the receiver and sets of integers.

+

+

data - Generators and an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+

-* (...data)

+

+

Binary operator for instance method dif

+

+

dif_o (...data)

+

+

Creates a new Sieve object in mode 'points' with offsets, generated by the difference of the receiver and sets of integers.

+

+

data - Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce zero and its positive multiples.

+

If no limit is passed, returned integers might go up to default limit 65536.

+


+

dif_oi (...data)

+

+

Creates a new Sieve object in mode 'intervals' with offsets, generated by the difference of the receiver and sets of integers.

+

+

data - Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. 

+

Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself.

+

Integers and Stream/Pattern output must be strictly positive. 

+

Integers as generators produce constant intervals.

+

If no limit is passed, integer intervals might be collected up to default summation limit of 65536.

+


+


+

plot 

+

+

Plot the Sieve's list.

+


+

size 

+

+

The Sieve's list size.

+


+

toPoints 

+

+

Convert the Sieve to mode 'points' and set first point to offset.

+

+

toIntervals 

+

+

Convert the Sieve to mode 'intervals' and set offset to first point.

+

+

shift (addOffset) 

+

+

Shift the Sieve's list by integer addOffset.

+

+

>> (addOffset) 

+

+

Binary operator for instance method shift.

+

+

shiftTo (targetOffset) 

+

+

Shift the Sieve's list to integer targetOffset.

+

+

>>! (targetOffset) 

+

+

Binary operator for instance method shiftTo.

+

+

shiftToZero 

+

+

Shift the Sieve's list to offset zero.

+

+

weakCopy 

+

+

Returns a new Sieve with same list object.

+

+

copy 

+

+

Returns a new Sieve with copied list object.

+


+

copyWith (seqCollection, withCheck)

+

+

Takes over mode and offset from receiver and passes an appropriate SequenceableCollection.

+

withCheck determines if seqCollection is checked according to mode.

+

withCheck defaults to true.

+


+

copyApplyTo (operator, withCheck)

+

+

Apply operator (Symbol of method or Function) to the Sieve's list and pass the result to a new 

+

Sieve with copied mode and offset.

+

withCheck determines if result of the operation is checked according to mode.

+

withCheck default to true.

+


+

== (that)

+

+

Equality check. Sieves of different mode are equal iff the lists resulting from conversion are equal.

+


+

segmentGreaterEqual (lo)

+

+

Returns a new Sieve of mode 'points' with Integers greater than or equal lo. 

+


+

>=! (lo)

+

+

Binary operator for segmentGreaterEqual

+


+

segmentGreater (lo)

+

+

Returns a new Sieve of mode 'points' with Integers greater than lo. 

+


+

>! (lo)

+

+

Binary operator for segmentGreater

+


+

segmentLessEqual (hi)

+

+

Returns a new Sieve of mode 'points' with Integers less than or equal hi. 

+


+

<=! (hi)

+

+

Binary operator for segmentLessEqual

+


+

segmentLess (hi)

+

+

Returns a new Sieve of mode 'points' with Integers less than hi. 

+


+

<! (hi)

+

+

Binary operator for segmentLess

+


+

segmentBetweenEqual (lo, hi)

+

+

Returns a new Sieve of mode 'points' with Integers greater than or equal lo and less than or equal hi. 

+

This method is more efficient than applying segmentGreaterEqual plus segmentLessEqual.

+


+

<>=! (bounds)

+

+

Binary operator for segmentBetweenEqual.

+

bounds must be an array with lo and hi.

+


+

segmentBetween (lo, hi)

+

+

Returns a new Sieve of mode 'points' with Integers greater than lo and less than hi. 

+

This method is more efficient than applying segmentGreater plus segmentLess.

+


+

<>! (bounds)

+

+

Binary operator for segmentBetween.

+

bounds must be an array with lo and hi.

+


+

checkSymmetricPeriods 

+

+

Checks the list of a Sieve for symmetric periods and returns an array [periods, symmetries, completions],

+

where periods is the clumped list of periods, symmetries a corresponding array of Symbols and

+

completions a corresponding array of Booleans, indicating if periods are complete.

+

periods can contains Symbols 'sym', 'asym', 'quasisym' and 'identic'. 

+

See Sieves and Psieve patterns for a characterisation of these types.

+

It is assumed that the receiver has a periodic list, changes between periodic and

+

aperiodic segments are not detected, so also aperiodic prefixes of periodic lists.

+

+

checkCharacteristicPeriod

+

+

Checks the first complete period based on the data returned by checkSymmetricPeriods.

+

It returns an array [characteristicPeriod, offset, oddEven, symmetry], where

+

characteristicPeriod denotes the first complete period, offset the index at which it starts

+

in the receiver's list, oddEven one of the Symbols 'odd' and 'even' and symmetry the 

+

symmetry type as in checkSymmetricPeriods.

+

+

plotCharacteristicPeriod

+

+

Plots the characteristic period returned by checkCharacteristicPeriod.

+

+

+

+

Examples

+


+


+

// instantiation with 'new'

+


+

Sieve.new(1000)

+


+

Sieve.new(4, 20)

+


+

Sieve.new_i(4, 20)

+


+

Sieve.new_o(4, 1, 20)

+


+

Sieve.new_oi(4, 1, 20)

+


+


+


+

// instantiation with union

+

// collecting multiples resp. shifted multiples

+

// limit must be wrapped into a Ref object

+


+

Sieve.union(4, 7, `20)

+


+

Sieve.union_i(4, 7, `20)

+


+

Sieve.union_o(4, 1, 7, -1, `20)

+


+

Sieve.union_oi(4, 1, 7, -1, `20)

+


+


+


+

// instantiation with intersection

+

// collecting least common multiples (lcms) resp. shifted lcms

+


+

Sieve.sect(4, 3, `30)

+


+

Sieve.sect_i(4, 3, `30)

+


+

Sieve.sect_o(4, -1, 3, 0, `30)

+


+

Sieve.sect_oi(4, -1, 3, 0, `30)

+


+


+


+

// instantiation with symmetrical difference

+

// equals union without intersection, thus multiples without lcms

+


+

Sieve.symdif(4, 3, `30)

+


+

Sieve.symdif_i(4, 3, `30)

+


+

Sieve.symdif_o(4, -1, 3, 0, `30)

+


+

Sieve.symdif_oi(4, -1, 3, 0, `30)

+


+


+

// all take more arguments

+


+

Sieve.union(4, 5, 7, `30)

+


+

Sieve.symdif(4, 5, 7, `30)

+


+


+


+

// difference is not symmetrical, but subtrahends can be swapped

+


+

Sieve.dif(4, 3, 7, `30)

+


+

Sieve.dif(3, 4, 7, `30)

+


+

Sieve.dif(3, 7, 4, `30)

+


+


+


+

// corresponding instance methods and its binary operators

+


+

a = Sieve(4, 30);

+


+

b = Sieve(3, 30);

+


+

a.union(b)

+


+

a|b

+


+

a.union_i(b)

+


+

a|*b

+


+


+

// here first arg is offset of receiver

+


+

a.union_o(0, b, 100)

+


+

a.union_oi(0, b, 100)

+


+


+

a.sect(8)

+


+

a & 8

+


+

a.sect_i(8)

+


+

a &* 8

+


+


+

a.sect_o(1, b, 5)

+


+

a.sect_oi(1, b, 5)

+


+


+

a.symdif(b)

+


+

a -- b

+


+

a.symdif_i(b)

+


+

a --* b

+


+


+

a.dif(b)

+


+

a - b

+


+

a.dif_i(b)

+


+

a -* b

+


+


+

// For conversion, segmentation and transformation examples see Sieves and Psieve patterns

+


+


+ + diff --git a/Help/Sieves and Psieve patterns.html b/Help/Sieves and Psieve patterns.html new file mode 100755 index 0000000..49173e6 --- /dev/null +++ b/Help/Sieves and Psieve patterns.html @@ -0,0 +1,1343 @@ + + + + + + + + + + + +

Sieves and Psieve patterns list building and sequencing based on Xenakis' sieves

+


+

Part of: miSCellaneous

+


+

See also: Sieve, PSPdiv, PSVunion, PSVunion_i, PSVunion_o, PSVunion_oi, PSVsect, PSVsect_i, PSVsect_o, PSVsect_oi, PSVsymdif, PSVsymdif_i, PSVsymdif_o, PSVsymdif_oi, PSVdif, PSVdif_i, PSVdif_o, PSVdif_oi, PSVop, PSVop_i, PSVop_o, PSVop_oi 

+


+

Iannis Xenakis proposed sieves as integer-based generators for rhythms, pitches and other musical parameters. For an overview of history and implementations, including his own development in Python, see Christopher Ariza's article [3].

+


+

This SC implementation comes in two variants, with the class Sieve and Psieve patterns. Both variants include the usual sieve operations, which are based on set theory and applied to integers: union, intersection, symmetric difference and difference (complement can be defined by difference). These operations are defined for an arbitrary number of arguments as well as binary operators (Sieve). Psieve as an abstract superclass of all sieve patterns integrates sieve sequences into the pattern framework whereas Sieve is defined for calculating sieves as lists, in other words Psieve patterns are the "lazy evaluation" variant of "eager evaluation" Sieve operations. Of course you can produce sieves as lists also with Psieve patterns but for calculating very large sieves beforehand you might want to prefer Sieve, as its operations are slightly faster. For using sieves in a realtime situation the overhead of Psieve patterns will mostly be irrelevant.

+

Why sieves + patterns? Not only can the ouput of sieve calculations be used in enclosing patterns – e.g. for scaling or arbitrary mapping into the continous domain –, Sieve and Psieve patterns also accept Patterns (which must be defined to produce integers) themselves as input for sieve operations, which opens a wide field for experimentation – in the case of Psieve patterns this even allows realtime control of sieve parameters and/or sieve stream output, also the logical operations can be exchanged on the fly. In one regard this is a contradiction to Xenakis' idea of sieves as an "outside-time" structure, on the other hand Xenakis, as Roads pointed out ([4], p.168), always tended to use generative procedures very freely and this also becomes explicit, when he describes "hyperbolae" (transformations) of sieves and suggests "... transformations of the logical operations in some fashion, using the laws of logic and mathematics, or arbitrarily." ([1], p.66). Patterns involve a wide range of such possibilities and provide a comfortable interface to be applied dynamically. 

+

Psieve patterns as well as Sieves can work in two modes, regarding sieves as sequences (resp. lists) of 'intervals' with an offset, or as 'points', meaning the ascending numbers itself (the wording of 'sums' and 'differences' would also be nearby, but here 'difference' is already used for set operations, so I leaned on Xenakis' terms). For efficiency reasons only one representation of a Sieve is current at a time, the default result mode is 'points'. However all operations exist in alternative result mode variants and of course Sieves can also be converted anytime. For calculus of Sieves there exist corresponding binary operators as shortcuts.

+

Characteristics of sieves are closely bound to relations of numbers by prime factors, roughly said: more complexity and longer periodicity is following from merging moduls that have fewer prime factors in common. However for the sake of keeping classes light-weight, dealing with period lengths etc. is not implemented within the sieve classes itself. See [1] and [3] for some number-theoretical considerations, you might also want to check Xenakis' original examples and hints. Useful integer operations (prime numbers, factoring) are contained in SC main and can help you to easily carry through your own experiments, some extensions of built-in lcm-algorithm are contained (4b). A thorough investigation of the symmetric structures generated is out of the scope of this package, however some observations on symmetry types and basic analysis tools are included (3). Last but not least: plotting the intervals can give a good impression of sieve characteristics.

+


+


+

References

+


+

[1] Xenakis, Iannis (1990). “Sieves” Perspectives of New Music 28(1): 58-78.

+

[2] Xenakis, Iannis (1992). Formalized Music. Hillsdale, NY: Pendragon Press, 2nd Revised edition.

+

[3] Ariza, Christopher (2005). "The Xenakis Sieve as Object: A New Model and a Complete Implementation" Computer Music Journal 29(2): 40-60.

+

[4] Roads, Curtis (2015). Composing Electronic Music. A New Aesthetic. Oxford University Press.

+


+


+

1) The Sieve class

+


+

1a) Basic generation from Integers, modes 'intervals' and 'points'

+

+

// define a simple sieve with multiples of 3, 5 and 7, 0 is included

+

// as no limit is given, the result goes up to the default limit of 65536

+

// per default result is given in mode 'points'

+


+

a = Sieve.union(3, 5, 7)

+


+


+

// a limit is passed as Ref object as last arg

+


+

a = Sieve.union(3, 5, 7, `30)

+


+


+

// convert to 'intervals': same Sieve object and new List that has one item less,

+

// the slot 'offset' is set accordingly (here it equals 0)

+

// the offset in mode 'points' is always nil

+


+

a.toIntervals

+


+


+

// convert back

+


+

a.toPoints

+


+


+

// access for arbitrary further use

+


+

a.list

+


+


+

// generate a Sieve from an Integer, it contains one point 

+


+

a = 5.toSieve.dump;

+


+

// it's interval representation is an empty list

+


+

a.toIntervals.dump

+


+


+

// calculate with 'intervals' from the beginning

+


+

Sieve.union_i(3, 5, 7, `30)

+


+


+


+

1b) Generation from Patterns, Streams and Sieves

+

+

// Instead of Integers producing their multiples you can pass Patterns or Streams.

+

// It's assumed that Patterns/Streams produce Integers interpreted as intervals

+

// (if it's defined to produce 'points' it can e.g. be wrapped into a Pdiff).

+


+

Sieve.union(Pseq([1, 10], inf), 5, 7, `30)

+


+

Sieve.union({ loop { rrand(1,10).yield } }.r, 5, 7, `30)

+


+


+

// compare with result from the pattern alone

+

// a union with one argument just returns its resulting elements.

+

// As pattern arguments are interpreted as intervals, 0 is included

+


+

Sieve.union(Pseq([1, 10], inf), `30)

+


+


+

// a Sieve can itself be passed to generate a new one,

+

// the mode of the passed sieve is taken into account

+


+

a = Sieve.union(5, 7, `30);

+


+

Sieve.union(a, 3, `30)

+


+


+


+

1c) Elementary sieve operations

+

+

// beneath union: intersection, symmetric difference, difference

+

// note that order of arguments only plays a role with difference

+


+


+

// intersection: only integers produced by all generators

+

// symmetric difference: only integers produced by one of the generators

+

// difference: only integers produced by the first generator, but by none of the others

+


+

// 'generator' here refers to allowed sieve operator args 

+

// (Integers as modul generators, Patterns/Streams or Sieves itself)

+


+


+

// proof of concept, evaluate in order

+


+

a = Sieve.union(3, 5, 6, `100)

+


+

// collect all pairwise intersections

+


+

(

+

b = Sieve.sect(3, 5, `100); // intersection with moduls: multiples of smallest common multiple

+

c = Sieve.sect(5, 6, `100);

+

d = Sieve.sect(3, 6, `100); // multiples of 6 itself

+

)

+


+

// calculate the symmetric difference ...

+


+

e = Sieve.symdif(3, 5, 6, `100)

+


+


+

// ... it equals the difference of a, b, c and d ...

+


+

f = Sieve.dif(a, b, c, d, `100)

+


+

e == f

+


+

// ... which equals the difference of a and the union of b, c and d

+


+

g = Sieve.dif(a, Sieve.union(b, c, d, `100))

+


+

e == g

+


+


+

1d) Offset methods

+

+

// for passing individual offsets there exist dedicated methods with suffixes '_o' and '_oi'

+

// offsets args are passed after the generating items

+


+


+

// one generating number with offset

+

// producing, mathematically spoken, a part of the residual class 2 modulo 3

+


+

Sieve.union_o(3, 2, `30)

+


+


+

// several generating numbers with offsets, passed pairwise

+


+

Sieve.union_o(3, 2, 5, 1, 7, 4, `30)

+


+

Sieve.union_oi(3, 2, 5, 1, 7, 4, `30)

+


+


+


+

// the shift operation adds an offset, it changes the receiver

+


+

a = Sieve.union(5, 7, `30);

+


+

a.shift(100)

+


+

a.shiftTo(0)

+


+

// as operators

+


+

a >> 100

+


+

a >>! 10

+


+


+


+

1e) Sieve operations defined as instance methods

+


+

// all operations and shortcuts defined above can be applied to instances

+


+

(

+

a = Sieve.union(3, 5, `1000);

+

b = Sieve.union(4, 7, `1000);

+

c = Sieve.union(6, 8, `1000);

+

)

+


+

// Note that large periods are already resulting from simple combinations of

+

// elementary operations and few prime factors.

+


+


+

// by default plot shows intervals

+


+

symdif(a, b, c).plot

+


+

dif(a, b, c).plot

+


+


+


+


+

1f) Sieve operations defined as binary operators

+


+

// instantiation with 'new': second arg limit doesn't need to be a Ref

+

(

+

a = Sieve(6, 60);

+

b = Sieve(8, 60);

+

)

+


+

// union

+


+

union(a,b)

+

a|b

+


+

union_i(a,b)

+

a|*b

+


+


+

// intersection

+


+

sect(a,b)

+

a&b

+


+

sect_i(a,b)

+

a&*b

+


+


+

// symmetric difference

+


+

symdif(a,b)

+

a--b

+


+

symdif_i(a,b)

+

a--*b

+


+


+

// difference, only elementary operation where order plays a role

+


+

dif(a,b)

+

a-b

+


+

dif_i(a,b)

+

a-*b

+


+

dif(b,a)

+

b-a

+


+

dif_i(b,a)

+

b-*a

+


+


+

// Efficiency hint: for an operation with a number of args, especially with large Sieves,

+

// it is more efficient to use the core class or instance methods than to concatenate binary operators:

+

// doing the latter means stepping through the list/range with each binary operation

+


+


+

1g) Segments of Sieves

+


+

// These operations result in new Sieve objects of mode 'points'

+


+

a = Sieve.union(3, 5, 7, `30)

+


+


+

// lo bound

+


+

a.segmentGreaterEqual(7)

+


+

a >=! 7

+


+

a.segmentGreater(7)

+


+

a >! 7

+


+


+

// hi bound

+


+

a.segmentLessEqual(10)

+


+

a <=! 10

+


+

a.segmentLess(10)

+


+

a <! 10

+


+


+

// lo & hi bound

+


+

a.segmentBetweenEqual(10, 20)

+


+

a <>=! [10, 20]

+


+

a.segmentBetween(10, 20)

+


+

a <>! [10, 20]

+


+


+


+

1h) Conversion to Sieves from Arrays 

+


+

// Conversion from arbitrary SequenceableCollection to Sieve:

+

// per default it's assumed that receiver and result are thought to be in mode 'points'

+

// but source and target mode can be passed as args 'fromMode' and 'toMode'

+


+

a = [1, 5, 17, 33, 37, 43, 57, 60, 61, 62, 63, 75, 89, 92, 97];

+


+

a.toSieve

+


+


+


+

// define result mode, add offset

+


+

a.toSieve(toMode: \intervals, addOffset: 100)

+


+


+

// define interval meaning of receiver

+

// default offset zero

+


+

a.toSieve(\intervals)

+


+


+

// same with offset 1, abbreviations for mode selection

+


+

a.toSieve(\i, \p, 1)

+


+


+


+

// if Integers are regarded as points, they must be ascending ...

+


+

a.reverse.toSieve

+


+


+

// ... but intervals can be descending ...

+


+

a.reverse.toSieve(\i)

+


+


+

// ... however they must be positive

+


+

(a.reverse ++ -1).toSieve(\i)

+


+


+

// It's possible to disable the checks preformed with conversion (flag 'withCheck'),

+

// but this only makes sense in a context where a large number of

+

// speed-critical conversions on well-prepared data has to be done.

+

// Otherwise it's always useful to perform those checks as

+

// sieve operations on wrong data (e.g. unordered lists) will fail or hang.

+


+


+


+

1i) Copying and transformation of Sieves by arbitrary array operations 

+


+

// It would be possible to define Sieve as subclass of List but there exist many

+

// methods for List which don't make any sense for sieves, even worse: they can consequently

+

// result in disfunctionality of standard operations defined for Sieves as List subclasses.

+

// This could be overcome with additional checks for these standard operations, a bloating which

+

// can be avoided if we try to keep only "proper sieves" as Sieve objects,

+

// thus by default dedicated wrappers for arbitrary transformations include checks.

+


+


+

a = Sieve.union_o(3, 1, 7, 2, `30)

+


+

// simple deep copy ...

+


+

b = a.copy

+


+


+

// ... lists are equal but not identical

+


+

a.list == b.list

+

a.list === b.list

+


+

// as expected sieves are equal

+


+

a == b

+


+

// but also a converted Sieve is equal

+


+

a == b.toPoints

+


+


+


+

// mode (intervals) and offset are taken over from original Sieve

+

// new array is inserted and checked if it contains proper (ascending) integers

+


+

a.copyWith([2, 17, 29, 31, 35, 53])

+


+

a.copyWith([2, 2, 3, 5, 1, 10])

+


+


+


+


+

// if the receiver is of mode 'intervals', the array of above can be passed

+


+

b = a.copy.toIntervals

+


+

b.copyWith([2, 2, 3, 5, 1, 10])

+


+


+


+

// main workhorse for transformations, note that offset is kept while intervals reversed

+


+

c = b.copyApplyTo(\reverse)

+


+

c.toPoints

+


+


+

b.copyApplyTo(\mirror).plot

+


+


+

// as with method 'applyTo' arbitrary Functions can be passed

+


+

b.copyApplyTo { |x| x * x * x ++ (1..10).mirror  }

+


+


+

// partial application

+


+

b.copyApplyTo(_ ++ [7, 5, 1])

+


+


+


+

2) Psieve patterns

+


+

2a) Basic generation from Integers, output modes 'intervals' and 'points'

+

+

// Psieve patterns use the prefix PSV followed by the name of elementary

+

// sieve operations, as used with the Sieve class, and optional suffixes.

+

// In comparison with Sieve more arguments are taken, so

+

// the generating arguments and offsets are to be passed within an array.

+


+


+

PSVunion([3, 5, 7]).asStream.nextN(20)

+


+

// intervals

+


+

PSVunion_i([3, 5, 7]).asStream.nextN(20)

+


+


+

// other operations

+


+

PSVsect([3, 5, 7]).asStream.nextN(20)

+


+

PSVsymdif([3, 5, 7]).asStream.nextN(20)

+


+

PSVdif([3, 5, 7]).asStream.nextN(20)

+


+


+


+

// offsets, offsets + interval output

+


+

PSVunion_o([3, 2, 5, 4]).asStream.nextN(20)

+


+

PSVunion_oi([3, 2, 5, 4]).asStream.nextN(20)

+


+


+

PSVdif_o([3, 2, 5, 4]).asStream.nextN(20)

+


+

PSVdif_oi([3, 2, 5, 4]).asStream.nextN(20)

+


+

...

+


+

// maxLength defines the maximum number of items -

+

// in case of a randomly generating item or a low summation limit

+

// the overall stream might have to end earlier.

+


+

b = PSVunion([7, 17, 29], 30).asStream

+


+

b.all

+


+


+

// if the summation limit is set, maxLength might not be reached

+


+

c = PSVunion([7, 17, 29], 30, 100).asStream

+


+

d = c.all

+


+

d.size

+


+


+


+

2b) Generation from Patterns, Streams and Sieves

+

+

// distorted periodicity by union with random sieve

+


+

a = ({ rrand(0, 1000) } ! 50).asSet.asArray.sort.toSieve

+


+

PSVunion_i([4, 7, a]).asStream.nextN(100).plot

+


+

// compare

+


+

PSVunion_i([4, 7]).asStream.nextN(100).plot

+


+


+


+

// distorted periodicity by union with random patterns

+


+

PSVunion_i([4, 7, Pn(Pshuf([2, 5, 9])) ]).asStream.nextN(100).plot

+


+

PSVunion_i([4, 7, Pwhite(3, 5) ]).asStream.nextN(100).plot

+


+


+


+

2c) Sequencing logical operations

+

+

// This is done by PSVop patterns, which take a Symbol or a pattern of Symbols,

+

// refering to the elementary logical operators.

+

// The stream of operations is forwarded with every integer point, 

+

// which has to be stepped through.

+


+

// helper function for plotting

+


+

p = { |x, n = 200| x.asStream.nextN(n).plot };

+


+


+

// plain standard operations, all elementary PSV patterns can be written with PSVop

+


+

PSVop([5, 9], \u).asStream.nextN(20) // union

+

PSVop([5, 9], \d).asStream.nextN(20) // difference

+

PSVop([5, 9], \sd).asStream.nextN(20) // symmetric difference

+


+


+

// some operator loops

+


+

p.(PSVop_i([5, 9], Pseq([\u, \sd], inf)))

+

p.(PSVop_i([5, 9], Pseq([\u, \s], inf)))

+

p.(PSVop_i([5, 9], Pseq([\u, \d], inf)))

+

p.(PSVop_i([5, 9], Pseq([\u, \d, \sd], inf)))

+


+


+

// random operator changes

+


+

p.(PSVop_i([5, 9], Pn(Pshuf([\u, \d]))))

+

p.(PSVop_i([5, 9], Prand([\u, \d], inf)))

+


+


+


+

// change of difIndex:

+


+

p.(PSVop_i([5, 2, 3], \d))

+

p.(PSVop_i([5, 9], Prand([\u, \d], inf)))

+


+


+

// "subtract" from index 0: multiples of 5, not divided by 7, 9, and 12

+


+

PSVdif([5, 7, 9, 12]).asStream.nextN(30)

+


+


+

// same as intervals, plotted

+


+

p.(PSVdif_i([5, 7, 9, 12]))

+


+

// written with PSVop

+


+

p.(PSVop_i([5, 7, 9, 12], \d, 0))

+


+


+

// also changing the positions other than the first is equivalent

+


+

p.(PSVdif_i([5, 7, 9, 12]))

+


+

p.(PSVop_i([5, 12, 7, 9], \d, 0))

+


+


+


+

// but "subtracting" from another number is different

+


+

p.(PSVdif_i([12, 9, 7, 5]))

+


+


+

// you can write the same with PSVop and difIndex without swapping the elements

+


+

p.(PSVop_i([5, 12, 7, 9], \d, 1))

+


+


+


+

// you can generate more complicated periods by more refined series of difIndices ...

+


+


+

p.(PSVop_i([4, 7], \d, 0))

+


+


+

p.(PSVop_i([4, 7], \d, PLseq([0, 0, 1])))

+


+

p.(PSVop_i([4, 7], \d, PLseq([0, 0, 1, 0, 0, 0, 1])))

+


+


+

// ... or combination of dynamic operator changes and difIndex changes.

+

// difIndices are only forwarded when operator 'difference' is current

+


+

p.(PSVop_i([4, 7], PLseq([\d, \d, \u]), PLseq([0, 0, 1])))

+


+


+

2d) Using Sieves in other than Psieve patterns

+


+

// Period lengths of intervals of basic Sieves are related to prime factors and 

+

// least common multiples (see Ref. [1] and [3] for a more detailled description)

+


+

// So when using intervals of those basic structures it is not necessary to 

+

// sum up to large numbers, which is what Sieve methods with suffix '_i' and

+

// corresponding Psieve patterns internally do, as they are not checking for periodicity.

+

// Instead we can calculate the list of intervals beforehand and use it at will.

+

// Also 'beforehand' doesn't exclude realtime use: it's easy to write a Function that

+

// generates Sieves and derives Patterns from it, which, with placeholder patterns,

+

// can be exchanged on the fly

+


+


+

// choose factors and calculate lcm (see 3b),

+

// as a summation limit it determines one period of intervals

+


+

a = [5, 6, 8];

+


+

m = a.lcmByGcd;

+


+

// calculate one period of intervals, plot the symmetric structure

+


+

x = Sieve.union_i(5, 6, 8, `m);

+


+

x.plot;

+


+


+

// sieve loop without counting high

+


+

Pseq(x.list, 5).asStream.all.plot

+


+


+

// second Sieve

+


+

(

+

b = [20, 17];

+

n = b.lcmByGcd;

+

v = b ++ `n;

+

y = Sieve.union_i(*v);

+

y.plot;

+

)

+


+

// make list for use with arbitrary Patterns

+


+

z = x.list ++ y.list;

+


+

z.plot;

+


+


+

// alternating sieves

+


+

Pseq(z, 3).asStream.all.plot;

+


+


+

// sequencing random segments of random length

+

// this is done with Pindex, an ascending index list of random length and a random offset

+


+

//  Function for Plazy

+


+

q = { Pindex(z, Pseq((0..rrand(10, 20))) + z.size.rand) }

+


+

Pn(Plazy(q), 30).asStream.all.plot;

+


+


+

// sequencing randomly repeated random segments of random length

+


+

q = { Pindex(z, Pseq(((0..rrand(10, 20)) ! rrand(1, 5)).flat) + z.size.rand) }

+


+

Pn(Plazy(q), 20).asStream.all.plot;

+


+


+

3) Periodicity of intervals with elementary operations

+


+

3a) Periodicity of intervals with elementary operations

+


+

// For 'union' and no offsets one period of intervals is given with summation limit

+

// equal to the least common multiple (lcm) of the generating numbers.

+

// (if one number divides another, the larger one can be dropped)

+


+

// The period length (number of intervals per period) is thus lesser than the lcm.

+

// A symmetric structure is produced, for 'union' the period is equal to its mirror, 

+

// in other words the produced sequence is a concatenation of symmetric segments

+


+

// With 3, 7 and 8 lcm equals 168

+


+

a = Sieve.union_i(3, 7, 8, `168)

+


+

a.plot

+


+

// number of intervals per period

+


+

a.size

+


+

// For symmetric difference and difference lcm also plays a role,

+

// but it's a bit different.

+

// As (offset 0) 0 is not included, the interval sequence doesn't really start from there,

+

// so  with limit = lcm the period is incomplete (although the segment is already symmetric)

+


+

Sieve.symdif_i(3, 7, 8, `168).plot

+


+


+

// To get the full picture take twice lcm as limit:

+

// the interval in the middle was not included before !

+


+

Sieve.symdif_i(3, 7, 8, `(168 * 2)).plot

+


+


+


+

// you can get the continuation to symmetry (begin = end)

+

// with a real start point as offset:

+


+

Sieve.symdif_oi(3, -3, 7, 0, 8, 0, `(168 + 3)).plot;

+


+

Sieve.dif_oi(3, -3, 7, 0, 8, 0, `(168 + 3)).plot;

+

Sieve.dif_oi(7, -7, 3, 0, 8, 0, `(168 + 7)).plot;

+

Sieve.dif_oi(8, -8, 3, 0, 7, 0, `(168 + 8)).plot;

+


+


+

// In other words in the latter cases the produced sequence is a 

+

// concatenation of symmetric segments, in which begin/end points are merged.

+


+


+

3b) Types of symmetry

+


+

// Symmetric structures in periodic series occur as different types,

+

// depending if the period length is even or odd.

+


+

// Definition: 

+

// Lets's call a period 'symmetric' iff it's equaling its reverse.

+

// Lets's call a period 'quasi-symmetric' iff the continuation with its first element is symmetric.

+


+

// If a sequence contains a symmetric or quasisymmetric period, there exists a

+

// symmetric or quasisymmetric period starting in its middle (or just right from it when odd), 

+

// let denote it its 'coperiod'.

+


+

// Statements (formal proof omitted, but rather straightforward): 

+


+

// (1) If the period length is even, a symmetric

+

// period corresponds to a symmetric coperiod and a quasisymmteric period

+

// corresponds to a quasisymmteric coperiod.

+

// (2) If the period length is odd, a symmetric

+

// period corresponds to a quasisymmteric coperiod.

+

// (3) Only a period of identic elements can be symmetric and quasi-symmetric at the same time.

+


+


+

3c) Analysis tools

+


+

// Method 'checkSymmetricPeriods' applies to Arrays resp. intervals of Sieves

+

// and checks for periods and possible symmetries.

+

// It returns an array with 4 items:

+

// (1) the sequence clumped in (quasi-)symmetric (or asymmetric) chunks

+

// (2) the index offset of the first (quasi-)symmetric period

+

// (3) a symbol indicating if the period length is even or odd

+

// (4) an array of Booleans indicating the completeness of the clumped chunks (incomplete periods can be of any type)

+


+

// Some important points here:

+

// (1) 'checkSymmetricPeriods' searches for smallest periods and its (possible) symmetry

+

// (2) 'checkSymmetricPeriods' is supposing that no prefix items are introducing

+

// a periodicity, thus a sequence like  7, 8, 9, 1, 2, 3, 2, 1, 2, 3, 2, 1 ...

+

// will be regarded as non-periodic (this e.g. happens when offsets are far apart!).

+

// (3) In the case of odd quasi-symmetric periods the symmetric coperiod is searched for

+

// and preferred (if it's in the range) 

+

// (4) To get meaningful results for sieves and its elementary operations –

+

// due to (1) and (3) – it is recommended to check sufficiently large sieves,

+

// e.g. set the limit to three times the lcm of the generators.

+


+

// make a Sieve with generating prime Integers 3, 7, 8

+

// regard a section of three time the expected period length

+


+

(

+

a = Sieve.symdif_i(3, 7, 10, `(210 * 3));

+

a.plot;

+

)

+


+

// store analysis data

+

b = a.checkSymmetricPeriods

+


+

// clumped sequence (long)

+

b[0]

+


+

// symmetry types of chunks and completions, the first relevant period is at position 1 and quasisymmetric

+

b[1..2]

+


+

// 'checkCharacteristicPeriod' returns an array with first characteristic period, 

+

// index offset, length type (even or odd) and symmetry type

+


+

a.checkCharacteristicPeriod

+


+

// you can plot it directly, compare with the sieve plot, where period starts at index 41

+


+

a.plotCharacteristicPeriod

+


+


+

3d) Symmetry types of elementary operations without offset

+


+

// Some observations of types occuring, no proof.

+

// Connections between types and used numbers are not obvious here:

+

// all symmetry types of an operator occur with tuples of coprime and not-coprime numbers

+


+

// union:

+


+

// symmetric odd period

+

(

+

a = Sieve.union_i(3, 7, `42);

+

a.plot;

+

a.plotCharacteristicPeriod;

+

)

+


+

// symmetric even period

+

(

+

a = Sieve.union_i(4, 9, `72);

+

a.plot;

+

a.plotCharacteristicPeriod;

+

)

+


+


+

// symdif:

+


+

// symmetric odd period

+

(

+

a = Sieve.symdif_i(8, 9, `216);

+

a.plot;

+

a.plotCharacteristicPeriod;

+

)

+


+

// quasi-symmetric even period

+

(

+

a = Sieve.symdif_i(7, 9, `189);

+

a.plot;

+

a.plotCharacteristicPeriod;

+

)

+


+

// dif:

+


+

// symmetric odd period

+

(

+

a = Sieve.dif_i(5, 6, `90);

+

a.plot;

+

a.plotCharacteristicPeriod;

+

)

+


+


+

// quasi-symmetric even period

+

(

+

a = Sieve.dif_i(6, 10, `90);

+

a.plot;

+

a.plotCharacteristicPeriod;

+

)

+


+


+

3e) Symmetry types of elementary operations with offset

+


+

// If generators are coprime all is quite straight: offsets will not change the

+

// sum of the period equal to the least common multiple but will only cause a shift.

+

// This was elaborated by Xenakis in [1]

+


+


+

// Things become more complicated when generators have prime factors in common:

+

// still period sums are preserved, but symmetry types can change;

+

// asymmetric periods occur. One and the same tuple of generators can cause

+

// different combinations of period types with different offsets.

+


+


+

// Here's a little helper Function to analyze characteristics of different offsets

+

// for a given choice of generating integers

+


+

(

+

f = { |operator = \union_i ...generators|

+

var sieve, types, allGens, lcm, data, input, offsets;

+

//collect all offset combinations

+

offsets = (generators.collect { |i| (0..i-1) }).allTuples;

+


+

offsets.collect { |offset|

+

lcm = lcmByGcd(*generators);

+

input = [operator] ++ [generators, offset].flop.flat ++ Ref(lcm * 4);

+

sieve = Sieve.perform(*input);

+

data = sieve.checkCharacteristicPeriod;

+

([operator, offset] ++ (data.drop(1)) ++

+

["lcm " ++ lcm.asInteger] ++ ["periodSum " ++ data.first.sum]).postln;

+

};

+

""

+

}

+

)

+


+

// this pair of generators gives three different types: odd asym, odd sym, even sym

+

f.(\union_oi, 8, 12)

+


+

// check:

+

// odd asymmetric

+

Sieve.union_oi(8, 0, 12, 1, `48).plot;

+


+

// odd symmetric

+

Sieve.union_oi(8, 0, 12, 2, `48).plot;

+


+

// even symmetric

+

Sieve.union_oi(8, 0, 12, 4, `48).plot;

+


+


+

// odd sym, even sym, even asym

+

f.(\union_oi, 12, 20)

+


+

// odd asym, even sym

+

f.(\union_oi, 15, 20)

+


+

// odd sym, even asym

+

f.(\union_oi, 9, 15)

+


+


+

// More types result from more fixed generators with varying offsets

+

// here: odd sym, odd asym, even sym, even asym

+


+

f.(\union_oi, 8, 10, 12)

+


+


+


+

// Also note that trivial genrator combinations for union without offset (when dividing each other),

+

// bring different results with offsets

+


+

// sequence of equal intervals (2)

+


+

Sieve.union_i(2, 6, 12, `48)

+


+


+

// asymmetric periods with same generators and offsets

+


+

Sieve.union_oi(2, 0, 6, 5, 12, 1, `48).plot;

+


+

// Under same assumption of generators with prime factors in common, a

+

// similar enrichment of symmetry types occurs with operators 'dif' and 'symdif'

+

// when offsets are used. In contrast to 'union' but as with 'dif' and 'symdif'

+

// without offsets, quasisymmteric periods occur.

+


+


+

// changing of symmetry types can also be done by looped sequencing of logical operations

+


+

// symmetric period produced by operator 'union'

+


+

a = PSVop_i([6, 5, 7], \u).iter.nextN(500).toSieve(\i, \i);

+


+

a.plotCharacteristicPeriod;

+


+


+

// altered, here still symmetric with logical sequence

+


+

a = PSVop_i([6, 5, 7], Pseq([\u, \d, \sd, \d], inf)).iter.nextN(500).toSieve(\i, \i);

+


+

a.plotCharacteristicPeriod;

+


+


+


+

4) Troubleshooting

+


+

4a) Critical inputs, limits

+


+

// Due to the definition of sieves there are input combinations which

+

// might result in massive looping without any result.

+


+

// Default settings are chosen in a way that this shouldn't result in hangs immediately,

+

// nevertheless it's the users's responsibility to choose meaningful input values.

+


+


+

// E.g. here we demand multiples of 3 which, at the same time, shouldn't be multiples of 3 ...

+

// The result nil is given not before the limit of 65536 is reached by summation,

+

// benchmarking indicates that.

+


+

PSVdif([3, 3], 10).asStream.next

+


+

{ PSVdif([3, 3], 10).asStream.next }.bench

+


+

Sieve.dif(3, 3)

+


+

{ Sieve.dif(3, 3) }.bench

+


+


+


+

// similar here, no intersection

+


+

PSVsect_o([3, 0, 3, 1], 10).asStream.next

+


+

{ PSVsect_o([3, 0, 3, 1], 10).asStream.next }.bench

+


+

Sieve.sect_o(3, 3)

+


+

{ Sieve.sect_o(3, 3) }.bench

+


+


+

// Another critical operation is intersection with larger coprime numbers

+


+

nthPrime(70)

+

-> 353

+


+

nthPrime(71)

+

-> 359

+


+


+

a = PSVsect([353, 359]).asStream;

+


+

// first intersection at 0, but no further one (below global limit 65536)

+


+

a.nextN(2)

+


+


+

// you can set the limit, but it's a rather inefficient way to 

+

// generate just a series of equal intervals ... 

+


+

a = PSVsect([353, 359], limit: 2 ** 30).asStream;

+


+

a.nextN(20)

+


+

{ a.nextN(20) }.bench

+


+


+

// The largest Integer with 32 bit is 2 ** 31 - 1.

+

// You can set 'limit' with Sieves and Psieve patterns (for instances and globally)

+

// up to 2 ** 31 - 1 - maxGeneratingInteger.

+

// This ensures that the threshold check doesn't exceed the Integer range.

+


+


+

// So if you're sure about useful inputs

+

// you can set a high global limit for calculus with large numbers and/or long streams

+


+

Psieve.limit = 2 ** 31 - 536892

+


+

{ a = PSVunion([253630, 536891]).asStream.nextN(10000) }.bench

+


+


+

// reset global limit

+


+

Psieve.limit = 65536

+


+


+

// Note that Psieve is a bit more flexible as Sieve in this regard

+

// it allows to set summation limit and maxLength.

+


+

Sieve.limit = 2 ** 31 - 536892

+


+

{ a = Sieve.union(253630, 536891) }.bench

+


+

a.list.size

+


+


+

// reset global limit

+


+

Sieve.limit = 65536

+


+


+


+

4b) Calculating least common multiples

+


+

// For calculating period lengths the related operations of greatest common divisor

+

// and least common multiple are relevant (e.g. see Ref. [1])

+


+

// Up to SC 3.7.2 built-in method 'lcm' fails for large Integers,

+

// though this has been fixed in 3.8:

+


+

lcm(248214, 1027542)

+


+

-> 6696095

+


+


+

// A prime factor analysis shows:

+


+


+

a = [248214, 1027542].collect(_.factors)

+


+

-> [ [ 2, 3, 41, 1009 ], [ 2, 3, 41, 4177 ] ]

+


+


+

// Thus the result would have to be

+


+

2.0 * 3 * 41 * 1009 * 4177

+


+

-> 1036789878

+


+


+

// Why is 2.0 needed above ? 

+

// The result of an Integer multiplication is an Integer, 

+

// thus crossing the int32 limit is silent and can easily be overlooked

+


+

3768562 * 876876

+


+

-> 1731721688

+


+


+

3768562.0 * 876876

+


+

-> 3304561572312

+


+


+

// The methods lcmByFactors and lcmByGcd contain the relevant threshold checkes,

+

// they are much slower than 'lcm' but reliable also with large Integers.

+

// 'lcmByFactors' returns an array with lcm as first item, an array with prime factors

+

// of lcm as second item and an array of receiver's and all arguments' prime factors. 

+

// Alternatively the least common multiple can be calculated 

+

// via the greatest common divisor, this is done by method 'lcmByGcd'

+


+

lcmByFactors(248214, 1027542)

+

lcmByGcd(248214, 1027542)

+


+


+

// if calculation exceeds the int32 limit a warning is given, the result is a float

+


+

lcmByFactors(135630546, 429496729)

+

lcmByGcd(135630546, 429496729)

+


+


+

// also more args can be passed (all are integers < 2 * 31),

+

// as lcmByGcd uses gcd internally, this might fail with more than 2

+

// large numbers, whereas lcmByFactors still finds the result

+


+

lcmByGcd(135630546, 429496729, 610337457)

+

lcmByFactors(135630546, 429496729, 610337457)

+


+


+

5) Audio examples

+


+

// synthdefs to play with

+


+

(

+

SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1|

+

var sig = { WhiteNoise.ar } ! 2;

+

sig = BPF.ar(sig, freq, rq) *

+

EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

+

(rq ** -1) * (250 / (freq ** 0.8));

+

OffsetOut.ar(out, sig);

+

}).add;

+


+

SynthDef(\sin_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2;

+

sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

OffsetOut.ar(out, sig);

+

}).add;

+


+


+

SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

+

var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2;

+

sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

+

OffsetOut.ar(out, sig);

+

}).add;

+

)

+


+

5a) Applying sieve intervals to (micro) rhythms

+


+

(

+

// rhythm by sieve intervals

+


+

~delta = 0.05;

+

~rhy = PSVunion_i([4, 6, 7]);

+


+

p = Pbind(

+

    \instrument, \noise_grain,

+

    \dur, PL(\rhy) * PL(\delta),

+

    \att, 0.01,

+

    \rel, 0.05,

+

    \amp, 0.1,

+

    \midinote, Pwhite(50, 90),

+

    \rq, 0.1

+

).play

+

)

+


+


+

// change to micro rhythms

+


+

(

+

~delta = 0.01;

+

~rhy = PSVsymdif_i([4, 6]);

+

)

+


+

// test with different data

+


+

~rhy = PSVsymdif_i([4, 6, 9])

+


+

~rhy = PSVsymdif_i([6, 9, 2])

+


+

~rhy = PSVsymdif_i([9, 2])

+


+

~rhy = PSVsymdif_i([3, 4])

+


+

~rhy = PSVsymdif_i([3, 4, 7])

+


+


+

p.stop

+


+


+

5b) Sequentially generating new sieve patterns for rhythm and pitch

+


+

(

+

// rhythm by sieve intervals

+


+

~delta = 0.1;

+

~rhy = PSVunion_i([4, 6, 7]);

+


+

// some more params for live change

+


+

~rel = 0.05;

+

~midi = Pwhite(50, 90);

+

~rq = 0.1;

+


+

q = Pbind(

+

    \instrument, Prand([\noise_grain, \sin_grain], inf),

+

    \dur, PL(\rhy) * PL(\delta),

+

    \att, 0.005,

+

    \rel, PL(\rel),

+

    \amp, 0.1,

+

    \midinote, PL(\midi),

+

    \rq, PL(\rq)

+

).play

+

)

+


+


+

// turn to micro rhythm

+

(

+

~delta = 0.01;

+


+

// instead of Pn + Plazy also Pspawner with method .seq could be used

+

~rhy = Pn(Plazy {

+

    var r = { rrand(2, 30) } ! 2;

+

    "rhythm generators: ".post; r.sort.postln;

+

    PSVsymdif_i(r, rrand(20, 30))

+

});

+


+

// numbers generated by PSVsymdif_i are used above and below a central pitch

+

~midi = Pn(Plazy {

+

    var r = { rrand(2, 10) } ! 2;

+

    "pitch generators: ".post; r.sort.postln;

+

    PSVsymdif_i(r, rrand(10, 20)) * PLseq([1, -1]) + rrand(50, 95)

+

});

+


+


+

// sequencing rq and release time

+

~rq = Pstutter(Pwhite(2, 5), PLseq([0.005, 0.01, 0.1]));

+


+

~rel = Pstutter(Pwhite(2, 5), Pn(Pshuf([0.05, 0.1, 0.2])));

+

)

+


+

q.stop;

+


+


+

5c) Sequencing instrumental variation with sieves

+


+

// start with sin grains

+

(

+

~delta = 0.15;

+

~rhy = 1;

+

~rel = 0.04;

+

~midi = 70;

+

~instrument = \sin_grain;

+

~rq = 0.01;

+

~amp = 0.1;

+

~type = \note;

+


+

r = Pbind(

+

    \instrument, PL(\instrument),

+

    \dur, PL(\rhy) * PL(\delta),

+

    \att, 0.015 * Pwhite(0.8, 1.2),

+

    \rel, PL(\rel),

+

    \amp, PL(\amp),

+

    \midinote, PL(\midi),

+

    \rq, PL(\rq),

+

    \type, PL(\type)

+

).play

+

)

+


+

// define variation with sieve

+

(

+

~instrument = Pstutter(

+

    PSVunion_i([5, 7, 13]),

+

    PLseq([\saw_grain, \noise_grain, \sin_grain, \noise_grain])

+

)

+

)

+


+

// pitch sequence based on sieve

+

(

+

~midi = Pstutter(Pwhite(2, 5), PSVunion_i([10, 8, 17])) * PLseq([1, -1]) + 80;

+

~rel = 0.45;

+

~amp = 0.06;

+

)

+


+

// transpositions

+

(

+

~midi = Pstutter(Pwhite(2, 5), PSVunion_i([10, 8, 17])) * PLseq([1, -1]) +

+

    80 + Pstutter(Pwhite(10, 20), Pwhite(-5, 5));

+

)

+


+

// microtonal transpositions and added fifths

+

(

+

~midi = Pstutter(Pwhite(2, 5), PSVunion_i([10, 8, 17])) * PLseq([1, -1]) + 80 +

+

    Pstutter(Pwhite(10, 20), Pwhite(-10.0, 5)) +

+

    Prand([0, [0, 7]], inf);

+

)

+


+

// interfering curves generated by 'union'

+

(

+

~midi = [55, 62] + PSVunion_oi([55, 0, 57, 1, 58, 2, 59, 3]);

+


+

~rel = Pstutter(Pwhite(2, 5), Pn(Pshuf([0.05, 0.05, 0.1, 0.5])));

+

)

+


+

r.stop

+


+


+ + diff --git a/Help/Smooth Clipping and Folding.html b/Help/Smooth Clipping and Folding.html new file mode 100755 index 0000000..8ba6788 --- /dev/null +++ b/Help/Smooth Clipping and Folding.html @@ -0,0 +1,229 @@ + + + + + + + + + + + +

Smooth Clipping and Folding a suite of pseudo ugens for smooth clipping and folding

+


+

Part of: miSCellaneous

+


+

See also: SmoothClipS, SmoothClipQ, SmoothFoldS, SmoothFoldQ, SmoothFoldS2, SmoothFoldQ2 

+


+


+

Wave folding is a synthesis technique from analog days, going back to Donald Buchla and the tradition of west coast synthesis. Smooth clipping and folding pseudo ugens from miSCellaneous lib come in variants which include quadratic and sinusoidal waveshaping and allow clipping and folding without aliasing. This can also be used for buffer scratching – a synthesis technique which I have been experimenting with recently with great fun.

+


+


+

Ex. 1: Different types of folding

+

+

// A typical usage is preamplifying a signal, here we start with a sine wave, compare plots

+


+

{

+

[

+

// just smooth clipping

+

SmoothClipS.ar(SinOsc.ar(50) * 10), 

+

+

// folding with main lib's Fold ugen

+

Fold.ar(SinOsc.ar(50) * 10, -1, 1), 

+

+

// folding with rather low smoothing 

+

// wave shaper is partiallly a sine wave

+

SmoothFoldS.ar(SinOsc.ar(50) * 10, smoothAmount: 0.3), 

+

+

// folding with maximum smoothing 

+

// wave shaper is full sine wave

+

SmoothFoldS.ar(SinOsc.ar(50) * 10, smoothAmount: 1), 

+

+

// wave is folded back only to border ranges

+

SmoothFoldS.ar(SinOsc.ar(50) * 10, foldRange: 0.3),

+


+

// folding with different sizes of border ranges

+

SmoothFoldS2.ar(SinOsc.ar(50) * 10, foldRangeLo: 0.5, foldRangeHi: 0.2)

+

]

+

}.plot(1/50)

+


+


+

attachments/Smooth Clipping and Folding/fold_examples.png

+


+


+

Ex. 2: Generating rich spectra by folding sine waves

+

+

// Folding ugens do multichannel expansion, let two anticyclic sines control the fold range,

+

// control smoothing amount with MouseX

+


+

(

+

x = {

+

var source = SinOsc.ar(50);

+

SmoothFoldS.ar(source, -0.1, 0.1, SinOsc.kr(0.05, [0, pi]).range(0.1, 1), MouseX.kr(0, 1))

+

}.scope

+

)

+


+

x.release

+


+


+

// Compare with the parabolic smoothing variant, the difference isn't great in this case

+


+

(

+

x = {

+

var source = SinOsc.ar(50);

+

SmoothFoldQ.ar(source, -0.1, 0.1, SinOsc.kr(0.05, [0, pi]).range(0.1, 1), MouseX.kr(0, 1))

+

}.scope

+

)

+


+

x.release

+


+


+

// slow modulations of source frequency with independant LFOs 

+


+

(

+

x = {

+

var source = SinOsc.ar(50 * { LFDNoise3.kr(0.1).range(0.98, 1.02) } ! 2);

+

SmoothFoldS.ar(source, -0.1, 0.1, SinOsc.kr(0.05, [0, pi]).range(0.1, 1))

+

}.scope

+

)

+


+

x.release

+


+


+

// Adding more complexity by applying preamplification (causes more folding) and adding an offset,

+

// these operations are also L/R-independant 

+


+

(

+

x = {

+

var source = SinOsc.ar(

+

50 * { LFDNoise3.kr(0.1).range(0.98, 1.02) } ! 2,

+

0,

+

{ LFDNoise3.kr(0.15).range(0.5, 3) } ! 2,

+

{ LFDNoise3.kr(0.2).range(-2, 2) } ! 2

+

);

+

SmoothFoldS.ar(source, -0.1, 0.1, SinOsc.kr(0.05, [0, pi]).range(0.1, 1))

+

}.scope

+

)

+


+

x.release

+


+


+


+

Ex. 3: Applying modulated folding to LFO sources

+

+

// the other way round, take a lfo source and modulate folding parameters, here the relative folding range

+


+

(

+

x = {

+

var source = LFDNoise3.ar(0.3!2).range(0.5, 1);

+

SmoothFoldS.ar(source, -0.1, 0.1, SinOsc.ar([50, 50.1]).range(0.1, 1) )

+

}.scope

+

)

+


+

x.release

+


+


+

// modulating fold bounds

+


+

(

+

x = {

+

var source = LFDNoise3.ar(0.3!2).range(0.5, 1);

+

var bounds = SinOsc.ar([50, 50.1]).range(0.02, 0.1);

+

SmoothFoldS.ar(source, bounds.neg, bounds)

+

}.scope

+

)

+


+

x.release

+


+


+

// modulating bounds and range

+


+

(

+

x = {

+

var source = LFDNoise3.ar(0.3!2).range(0.5, 1);

+

var range = SinOsc.ar([50, 50.1]).range(0.02, 0.1);

+

SmoothFoldS.ar(source, range.neg, range, SinOsc.ar([200, 200.1]).range(0.5, 1))

+

}.scope

+

)

+


+

x.release

+


+


+


+

Ex. 4: Buffer scratching with folded signal as position control

+

+

// Interesting micro textures can be generated that way.

+

// Technically this is waveshaping with an audio buffer as transfer function and the folded signal as source.

+


+

// compare with granulation, sound file from buffer granulation tutorial

+


+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+


+

(

+

SynthDef(\bufScratchFold, { |bufnum = 0, globalFreq = 0.7, localOscSize = 0.01, foldRange = 0.28,

+

localFreq = 0.87, preAmp = 1.4, smoothAmount = 0.36|

+

var sig = BufRd.ar(

+

1,

+

bufnum,

+

(

+

// define global and local movement

+

LFDNoise3.ar(globalFreq).range(0.2, 0.7) +

+

SmoothFoldS.ar(

+

// adding space by decorrelating the local scratching / oscillation

+

LFTri.ar(localFreq * ({ LFDNoise3.ar(0.2).range(0.999, 1.001) } ! 2)) * preAmp,

+

foldRange: foldRange,

+

smoothAmount: smoothAmount

+

) * localOscSize

+

) * BufFrames.ir(bufnum)

+

);

+

// as local oscillation can stick with positive or negative values, a dc leaker is recommended 

+

Out.ar(0, LeakDC.ar(sig) * EnvGate.new)

+

}).add

+

)

+


+

x = Synth(\bufScratchFold, [bufnum: b])

+


+

x.set(\preAmp, 5.4)

+

x.set(\foldRange, 0.08)

+

x.set(\localFreq, 0.5)

+

x.set(\localOscSize, 0.05)

+

x.set(\foldRange, 0.02)

+

x.set(\localFreq, 0.1)

+


+

x.release

+


+


+


+


+


+ + diff --git a/Help/SmoothClipQ.html b/Help/SmoothClipQ.html new file mode 100755 index 0000000..b7de51b --- /dev/null +++ b/Help/SmoothClipQ.html @@ -0,0 +1,73 @@ + + + + + + + + + + + +

SmoothClipQ wave shaping / clipping pseudo ugen using parabolic segments

+


+

Part of: miSCellaneous

+


+

Wave shaping with parabolic segments at borders. The amount of smoothness can be controlled: 0 means a linear transfer function, 1 a full parabolic segment, values inbetween a transfer function which consists of a line and a parabolic segment. For amount > 0 the slope of the transfer function equals 0 at the borders.

+


+


+

See also: Smooth Clipping and Folding, SmoothClipS, SmoothFoldS, SmoothFoldQ, SmoothFoldS2, SmoothFoldQ2 

+


+


+

Class Methods

+


+

*ar (in, lo, hi, amount, delta)

+

+

*kr (in, lo, hi, amount, delta)

+

+

in - input signal.

+

lo - lower limit, defaults to -1.

+

hi - upper limit, defaults to 1.

+

amount - amount of smoothness, must be >= 0 and <= 1, defaults to 0.5.

+

delta - threshold for avoiding zero divisions (which would happen if lo = hi and the border case of amount = 1).

+

Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// control smoothness

+


+

x = { SmoothClipQ.ar(LFTri.ar(300, 0, 0.25), -0.2, 0.2, MouseX.kr(0, 1)) }.scope  

+


+

x.release

+


+


+ + diff --git a/Help/SmoothClipS.html b/Help/SmoothClipS.html new file mode 100755 index 0000000..8b20917 --- /dev/null +++ b/Help/SmoothClipS.html @@ -0,0 +1,72 @@ + + + + + + + + + + + +

SmoothClipS wave shaping / clipping pseudo ugen using sine segments

+


+

Part of: miSCellaneous

+


+

Wave shaping with sine segments at borders. The amount of smoothness can be controlled: 0 means a linear transfer function, 1 a full sine segment, values inbetween a transfer function which consists of a line and a sine segment. For amount > 0 the slope of the transfer function equals 0 at the borders.

+


+

See also: Smooth Clipping and Folding, SmoothClipQ, SmoothFoldS, SmoothFoldQ, SmoothFoldS2, SmoothFoldQ2 

+


+


+

Class Methods

+


+

*ar (in, lo, hi, amount, delta)

+

+

*kr (in, lo, hi, amount, delta)

+

+

in - input signal.

+

lo - lower limit, defaults to -1.

+

hi - upper limit, defaults to 1.

+

amount - amount of smoothness, must be >= 0 and <= 1, defaults to 0.5.

+

delta - threshold for avoiding zero divisions (which would happen if lo = hi and the border case of amount = 1).

+

Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// control smoothness

+


+

x = { SmoothClipS.ar(LFTri.ar(300, 0, 0.25), -0.2, 0.2, MouseX.kr(0, 1)) }.scope  

+


+

x.release

+


+


+ + diff --git a/Help/SmoothFoldQ.html b/Help/SmoothFoldQ.html new file mode 100755 index 0000000..4fcfb40 --- /dev/null +++ b/Help/SmoothFoldQ.html @@ -0,0 +1,75 @@ + + + + + + + + + + + +

SmoothFoldQ wave folding pseudo ugen using parabolic segments

+


+

Part of: miSCellaneous

+


+

Wave folding using SmoothClipQ. Values outside the range lo-hi are folded back, if foldRange > 0. A larger foldRange means less folding whereas a smaller foldRange causes more folding. The spectral result depends on the source wave form also but in general a smaller foldRange causes more energy in the higher spectrum whereas near foldRange == 0 the higher part of the spectrum again decreases. Note that, in contrast to classical wave folding, the number of foldings isn't limited here, possibly causing aliasing when heavy folding is forced.

+


+

See also: Smooth Clipping and Folding, SmoothClipS, SmoothClipQ, SmoothFoldS, SmoothFoldS2, SmoothFoldQ2 

+


+


+

Class Methods

+


+

*ar (in, lo, hi, foldRange, smoothAmount, delta)

+

+

*kr (in, lo, hi, foldRange, smoothAmount, delta)

+

+

in - input signal.

+

lo - lower limit, defaults to -1.

+

hi - upper limit, defaults to 1.

+

foldRange - the relative amount of the range (defined by lo and hi) used for folding, should be >= 0 and <= 1. 

+

0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine 

+

the ranges near both borders, which are used for folding, defaults to 1.

+

smoothAmount - amount of smoothness, must be >= 0 and <= 1, defaults to 0.5.

+

delta - threshold for avoiding zero divisions (which would happen if lo = hi and the border case of amount = 1).

+

Normally not to be set by the user, except for very small folding ranges, defaults to 0.00001.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// see scope, MouseX controls foldRange, MouseY smoothAmount

+


+

x = { SmoothFoldQ.ar(SinOsc.ar(90), -0.1, 0.1, MouseX.kr(0.2, 0.8), MouseY.kr(0, 1)) }.scope  

+


+

x.release

+


+


+ + diff --git a/Help/SmoothFoldQ2.html b/Help/SmoothFoldQ2.html new file mode 100755 index 0000000..21d2daa --- /dev/null +++ b/Help/SmoothFoldQ2.html @@ -0,0 +1,79 @@ + + + + + + + + + + + +

SmoothFoldQ2 wave folding pseudo ugen using parabolic segments, two fold ranges

+


+

Part of: miSCellaneous

+


+

Wave folding using SmoothClipQ. Values outside the range lo-hi are folded back, if the concerned foldRange > 0. A larger foldRange means less folding whereas a smaller foldRange causes more folding. The spectral result depends on the source wave form also but in general a smaller foldRange causes more energy in the higher spectrum whereas near foldRange == 0 the higher part of the spectrum again decreases. Note that, in contrast to classical wave folding, the number of foldings isn't limited here, possibly causing aliasing when heavy folding is forced.

+


+

See also: Smooth Clipping and Folding, SmoothClipS, SmoothClipQ, SmoothFoldS, SmoothFoldQ, SmoothFoldS2 

+


+


+

Class Methods

+


+

*ar (in, lo, hi, foldRangeLo, foldRangeHi, smoothAmount, delta)

+

+

*kr (in, lo, hi, foldRangeLo, foldRangeHi, smoothAmount, delta)

+

+

in - input signal.

+

lo - lower limit, defaults to -1.

+

hi - upper limit, defaults to 1.

+

foldRangeLo - the relative amount of the range (defined by lo and hi) used for folding back values below lo, should be >= 0 and <= 1. 

+

0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine 

+

the range near lo, which is used for folding, defaults to 1.

+

foldRangeHi - the relative amount of the range (defined by lo and hi) used for folding back values above hi, should be >= 0 and <= 1. 

+

0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine 

+

the range near hi, which is used for folding, defaults to 1.

+

smoothAmount - amount of smoothness, must be >= 0 and <= 1, defaults to 0.5.

+

delta - threshold for avoiding zero divisions (which would happen if lo = hi and the border case of amount = 1).

+

Normally not to be set by the user, except for very small folding ranges, defaults to 0.00001.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// see scope, MouseX controls foldRangeLo, MouseY foldRangeHi

+


+

x = { SmoothFoldQ2.ar(SinOsc.ar(90), -0.1, 0.1, MouseX.kr(0, 1), MouseY.kr(0, 1)) }.scope  

+


+

x.release

+


+


+


+ + diff --git a/Help/SmoothFoldS.html b/Help/SmoothFoldS.html new file mode 100755 index 0000000..7253c93 --- /dev/null +++ b/Help/SmoothFoldS.html @@ -0,0 +1,77 @@ + + + + + + + + + + + +

SmoothFoldS wave folding pseudo ugen using sine segments

+


+

Part of: miSCellaneous

+


+

Wave folding using SmoothClipS. Values outside the range lo-hi are folded back, if foldRange > 0. A larger foldRange means less folding whereas a smaller foldRange causes more folding. The spectral result depends on the source wave form also but in general a smaller foldRange causes more energy in the higher spectrum whereas near foldRange == 0 the higher part of the spectrum again decreases. Note that, in contrast to classical wave folding, the number of foldings isn't limited here, possibly causing aliasing when heavy folding is forced.

+


+


+

See also: Smooth Clipping and Folding, SmoothClipS, SmoothClipQ, SmoothFoldQ, SmoothFoldS2, SmoothFoldQ2 

+


+


+

Class Methods

+


+

*ar (in, lo, hi, foldRange, smoothAmount, delta)

+

+

*kr (in, lo, hi, foldRange, smoothAmount, delta)

+

+

in - input signal.

+

lo - lower limit, defaults to -1.

+

hi - upper limit, defaults to 1.

+

foldRange - the relative amount of the range (defined by lo and hi) used for folding, should be >= 0 and <= 1. 

+

0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine 

+

the ranges near both borders, which are used for folding, defaults to 1.

+

smoothAmount - amount of smoothness, must be >= 0 and <= 1, defaults to 0.5.

+

delta - threshold for avoiding zero divisions (which would happen if lo = hi and the border case of amount = 1).

+

Normally not to be set by the user, except for very small folding ranges, defaults to 0.00001.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// see scope, MouseX controls foldRange, MouseY smoothAmount

+


+

x = { SmoothFoldS.ar(SinOsc.ar(90), -0.1, 0.1, MouseX.kr(0.2, 0.8), MouseY.kr(0, 1)) }.scope  

+


+

x.release

+


+


+


+ + diff --git a/Help/SmoothFoldS2.html b/Help/SmoothFoldS2.html new file mode 100755 index 0000000..811033f --- /dev/null +++ b/Help/SmoothFoldS2.html @@ -0,0 +1,80 @@ + + + + + + + + + + + +

SmoothFoldS2 wave folding pseudo ugen using sine segments, two fold ranges

+


+

Part of: miSCellaneous

+


+

Wave folding using SmoothClipS. Values outside the range lo-hi are folded back, if the concerned foldRange > 0. A larger foldRange means less folding whereas a smaller foldRange causes more folding. The spectral result depends on the source wave form also but in general a smaller foldRange causes more energy in the higher spectrum whereas near foldRange == 0 the higher part of the spectrum again decreases. Note that, in contrast to classical wave folding, the number of foldings isn't limited here, possibly causing aliasing when heavy folding is forced.

+


+


+

See also: Smooth Clipping and Folding, SmoothClipS, SmoothClipQ, SmoothFoldS, SmoothFoldQ, SmoothFoldQ2 

+


+


+

Class Methods

+


+

*ar (in, lo, hi, foldRangeLo, foldRangeHi, smoothAmount, delta)

+

+

*kr (in, lo, hi, foldRangeLo, foldRangeHi, smoothAmount, delta)

+

+

in - input signal.

+

lo - lower limit, defaults to -1.

+

hi - upper limit, defaults to 1.

+

foldRangeLo - the relative amount of the range (defined by lo and hi) used for folding back values below lo, should be >= 0 and <= 1. 

+

0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine 

+

the range near lo, which is used for folding, defaults to 1.

+

foldRangeHi - the relative amount of the range (defined by lo and hi) used for folding back values above hi, should be >= 0 and <= 1. 

+

0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine 

+

the range near hi, which is used for folding, defaults to 1.

+

smoothAmount - amount of smoothness, must be >= 0 and <= 1, defaults to 0.5.

+

delta - threshold for avoiding zero divisions (which would happen if lo = hi and the border case of amount = 1).

+

Normally not to be set by the user, except for very small folding ranges, defaults to 0.00001.

+

+


+

Examples

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

// see scope, MouseX controls foldRangeLo, MouseY foldRangeHi

+


+

x = { SmoothFoldS2.ar(SinOsc.ar(90), -0.1, 0.1, MouseX.kr(0, 1), MouseY.kr(0, 1)) }.scope  

+


+

x.release

+


+


+


+ + diff --git a/Help/TZeroXBufRd.html b/Help/TZeroXBufRd.html new file mode 100755 index 0000000..4300ef0 --- /dev/null +++ b/Help/TZeroXBufRd.html @@ -0,0 +1,1230 @@ + + + + + + + + + + + +

TZeroXBufRd triggers sequences of segments between zero crossings from one or more buffers with demand-rate control

+


+

+

Part of: miSCellaneous

+


+

+

Inherits from: UGen

+


+

+

TZeroXBufRd is for triggering possibly overlapped segments between zero crossings (half wavesets) from one or more buffers, whereby several reading and processing parameters can be sequenced with demand rate ugens. Full waveset sequences can so be generated as a special case. It needs analysis data prepared with ZeroXBufWr. For consecutive reading of segments between zero crossings see ZeroXBufRd. ZeroXBufRd / TZeroXBufRd can be used for a number of synthesis / processing techniques in a field between wavesets [1, 4, 5], pulsar synthesis [1, 3], buffer modulation and rectification (which are both a kind of waveshaping) and stochastic concatenation methods [2, 6]. There are already existing SC waveset implementations like Alberto de Campo's Wavesets quark (https://github.com/supercollider-quarks/quarks) and Olaf Hochherz's SPList (https://github.com/olafklingt/SPList), which do language-side analysis and Fabian Seidl's RTWaveSets plugin (https://github.com/tai-studio/RTWaveSets). My focus has been server-side analysis and demand rate ugen control of half waveset parameters as well as multichannel and buffer switch options. Realtime control while analysis is possible, as long as reading is only refering to already analysed sections, but clearly most flexibility is given with a fully analysed buffer, which can also be done in quasi realtime.

+


+

+

NOTE: Depending on the multichannel sizes and the options used (rate and dir sequencing) it might be necessary to increase server resources, i.e. the number of interconnect buffers and / or memory size (e.g. s.options.numWireBufs = 256; s.options.memSize = 8192 * 32; s.reboot). Because of overlappings this is more relevant with TZeroXBufRd than with ZeroXBufRd.

+


+

+

NOTE: Often it pays to adjust zero crossings in the sound buffer effectively to 0, that way sawtooth-like interpolation artefacts can be avoided. See Ex.1 below.

+


+

+

NOTE: Demand rate UGens in ZeroXBufRd / TZeroXBufRd must always use inf as repeats arg, this is of course not necessary for nested ones. You might pass a length arg though (Ex. 5).

+


+

+

NOTE: The distance between triggers must remain above control duration, otherwise the synthesis fails. For faster trigerring you'd have to use the server with a lower blocksize.

+


+

+

NOTE: As triggered half wavesets can overlap you'd have to care for a sufficiently large 'overlapSize' arg. See Ex.3 for possible estimations.

+


+

+

NOTE: For avoiding too long half wavesets it might be useful to apply LeakDC resp. a high pass filter before analysis.

+


+

+

NOTE: In rare cases I noticed corrupted buffers in multi buffer examples for no obvious reason.

+


+

+


+

+

CREDITS: Thanks to Tommaso Settimi for an inspiring discussion, which gave me a nudge to tackle these classes. 

+


+

+


+

+

REFERENCES:

+


+

+

[1] de Campo, Alberto. "Microsound" In: Wilson, S., Cottle, D. and Collins, N. (eds). 2011. 

+

The SuperCollider Book. Cambridge, MA: MIT Press, 463-504.

+

+

[2] Luque, Sergio (2006). Stochastic Synthesis, Origins and Extensions. Institute of Sonology, Royal Conservatory, The Netherlands. 

+

http://sergioluque.com

+


+

+

[3] Roads, Curtis (2001). Microsound. Cambridge, MA: MIT Press.

+

+

[4] Seidl, Fabian (2016). Granularsynthese mit Wavesets für Live-Anwendungen. Master Thesis, TU Berlin.

+

https://www2.ak.tu-berlin.de/~akgroup/ak_pub/abschlussarbeiten/2016/Seidl_MasA.pdf

+


+

+

[5] Wishart, Trevor (1994). Audible Design. York: Orpheus The Pantomime Ltd. 

+


+

+

[6] Xenakis, Iannis (1992). Formalized Music. Hillsdale, NY: Pendragon Press, 2nd Revised edition.

+


+

+


+

+


+

+

See also: ZeroXBufRd, ZeroXBufWr, DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFanOut, Buffer Granulation, Live Granulation, PbindFx, kitchen studies

+


+

+


+

+

Creation / Class Methods

+


+

+

*ar (sndBuf, zeroXBuf, bufMix, trig, zeroX = 0, xNum = 1, xRep = 1,  power = 1, mul = 1, add = 0, rate = 1, dir = 1, 

+

interpl = 4, overlapSize = 10, length = inf, maxTime = inf, att = 0, rel = 1, curve = -4, doneAction = 0)

+

+

sndBuf - Buffer or SequenceableCollection of Buffers to read the data from, data must correspond to zeroXBuf.

+

zeroXBuf - Analysis Buffer resp. SequenceableCollection of such, prepared with ZeroXBufWr. 

+

Must refer to data passed to sndBuf.

+

bufMix - A Number indicating the sndBuf index, a demand rate or other ugens returning sndBuf indices or 

+

a SequenceableCollection of such.

+

If bufMix equals nil (default) the size of the returned signal equals the size of sndBuf,

+

otherwise it equals its own size.

+

trig - A trigger signal for starting new half waveset groups.

+

zeroX - A Number indicating the index in zeroXBuf, a demand rate or other ugens returning zeroXBuf indices or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of zeroX

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 0.

+

xNum - Determining the number of half wavesets starting at zeroX, a demand rate or other ugens returning these numbers or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of xNum

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 1.

+

xRep - Determining the number of repetitions of half wavesets resp. half waveset groups (given by xNum) starting at zeroX, 

+

a demand rate or other ugens returning these numbers or a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of xNum

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 1.

+

power - Used for processing the buffer signal according to the formula: sig ** power * mul + add per half waveset group. 

+

Must be a positive Number, a demand rate or other ugens returning power values or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of power

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 1.

+

mul - Used for processing the buffer signal according to the formula: sig ** power * mul + add per half waveset group. 

+

Must be a Number, a demand rate or other ugens returning mul values or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of mul

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 1. 

+

add - Used for processing the buffer signal according to the formula: sig ** power * mul + add per half waveset group. 

+

Must be a Number, a demand rate or other ugens returning add values or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of add

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 0. 

+

rate - Determines the playback rate per half waveset group. 

+

Must be a positive Number, a demand rate or other ugens returning rate values or a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of rate

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 1.

+

dir - Determines the playback direction of half waveset groups. 

+

Must be +1 or -1, a demand rate or other ugens returning dir values or a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of dir

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 1. 

+

interpl - Determines the interpolation type for the BufRd ugens. 

+

Must equal 1 (no), 2 (linear) or 4 (cubic) or a SequenceableCollection of these numbers.

+

Defaults to 4. 

+

overlapSize - Determines the maximum overlap of half waveset groups. 

+

This is a fixed number or a SequenceableCollection thereof determining the size of internally used

+

multichannel signals for overlappings. If this number is too low the synthesis fails.

+

See Ex. 3 for estimating a sufficiently large value.

+

Defaults to 10. 

+

length - Determines the number of triggers before release of the overall asr envelope. 

+

Can be a Sequenceable Collection too. Overruled by maxTime if this is reached before.

+

Defaults to inf. 

+

maxTime - Determines the time before release of the overall asr envelope. 

+

Can be a Sequenceable Collection too. Overruled by length if this is reached before.

+

Defaults to inf. 

+

att - Attack time of overall asr envelope or SequenceableCollection thereof. 

+

Defaults to 0. 

+

rel - Release time of overall asr envelope or SequenceableCollection thereof.  

+

Defaults to 0. 

+

curve - Curve of overall asr envelope or SequenceableCollection thereof.  

+

Defaults to -4. 

+

doneAction - Done action of overall asr envelope or SequenceableCollection thereof.  

+

Defaults to 0. 

+

+

+


+

+

Examples

+


+

+

// See also the examples of ZeroXBufRd help. Most features work in the same way,

+

// this help file focusses rather on the differences.

+


+

+


+

+

Ex.1) Basic usage

+

+

// prepare two short buffers for audio and zero crossing data

+

// the size of needed zeroX data space can be roughly estimated

+


+

+

(

+

b = Buffer.alloc(s, 256);

+

z = Buffer.alloc(s, 32);

+

)

+


+

+

// analyse a short snippet of modulation

+


+

+

(

+

x = {

+

var src = SinOsc.ar(SinOsc.ar(1200, 0, 1000, 100)) * SinOsc.ar(700) - 0.1;

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+

+


+

+

// check the waveform

+


+

+

b.plot

+


+

+


+

+


+

+

// It's important to note that the maximum trigger rate should be below control rate,

+

// otherwise sequencing with demand rate ugens is messed up.

+

// This limitation can be circumvented by rebooting the server with a lower blockSize.

+


+

+

// demand rate ugens in TZeroXBufRd should always use inf as repeats arg

+


+

+

(

+

~maxTrigRate = s.sampleRate / s.options.blockSize;

+


+

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(MouseX.kr(10, ~maxTrigRate)),

+

zeroX: Dseq([2, 3], inf),

+

rate: 0.2

+

) * 0.1

+

}.play

+

)

+


+

+

s.scope

+


+

+

x.release

+


+

+

// In this example waveforms are slightly crossing the x axis.

+

// This comes from the fact that buffer values at zero crossing positions are not equal zero,

+

// but just of a different sign than the sample before.

+

// The effect is also explained in ZeroXBufRd's help Ex.7.

+


+

+

(

+

{

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(150),

+

zeroX: Dseq([2, 3], inf),

+

rate: 0.2

+

) * 0.2

+

}.plot(0.03)

+

)

+


+

+


+

+

// To get cleaner waveforms it's therefore recommended to adjust zero crossings in the buffer.

+

// This can also be done while analysis by using the flag 'adjustZeroXs' in ZeroXBufWr.

+


+

+

b.adjustZeroXs(z)

+


+

+


+

+

// smoother result

+


+

+

(

+

{

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(150),

+

zeroX: Dseq([2, 3], inf),

+

rate: 0.2

+

) * 0.2

+

}.plot(0.03)

+

)

+


+

+


+

+

// mul and dir can be used in the same way as with ZeroXBufRd

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(MouseX.kr(30, ~maxTrigRate)),

+

zeroX: Dseq([2, 3], inf) + LFDNoise0.ar(10).range(0, 5),

+

mul: Dseq([0.05, 0.1, 0.35], inf),

+

rate: 0.5,

+

dir: Dseq([1, -1], inf),

+

)

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// unlike with ZeroXBufRd with rate there's no restriction for using 

+

// combinations of demand rate and normal ugens

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(MouseX.kr(30, ~maxTrigRate)),

+

zeroX: Dseq([2, 3], inf),

+

mul: Dseq([0.05, 0.1, 0.35], inf),

+

rate: Dseq([0.2, 0.5, 1], inf) * SinOsc.ar(5).range(0.8, 1.2),

+

dir: Dseq([1, -1], inf),

+

)

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// power arg

+

// be careful with this arg moving away from 1 ! 

+

// high power values can result in loud signals if the source has values outside [-1, 1] and

+

// small power values can also become loud with source values near zero

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(MouseX.kr(30, ~maxTrigRate)),

+

zeroX: Dseq([2, 3], inf),

+

power: MouseY.kr(0.3, 2),

+

rate: 0.2,

+

dir: Dseq([1, 1, 1, -1], inf),

+

).tanh * 0.3

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

Ex.2) The 'xNum' and 'xRep' args

+


+

+

// needs Buffers from Ex.1

+


+

+

// With ZeroXBufRd repetitions of (groups of) half wavesets can easily be defined

+

// with passing appropriate demand rate ugens to the zeroX arg.

+

// With TZeroXBufRd the situation is different as the use of an external trigger

+

// opens the freedom to specify the sequences of such groups independently.

+


+

+

// The following plots show the options in the case of non-overlapping groups.

+


+

+


+

+

// Half waveset and full waveset at indices 2 and 3

+


+

+

(

+

{

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(100),

+

xNum: Dseq([1, 2], inf),

+

zeroX: Dseq([2, 3], inf),

+

rate: 0.2

+

);

+

}.plot(0.03)

+

)

+


+

+

// Repeated half wavesets

+


+

+

(

+

{

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(50),

+

xRep: 3,

+

zeroX: Dseq([2, 3], inf),

+

rate: 0.2

+

);

+

}.plot(0.05)

+

)

+


+

+


+

+

// Sequencing half waveset number and repetitions

+


+

+

(

+

{

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(50),

+

xNum: Dseq([1, 2, 3], inf),

+

xRep: Dseq([3, 2, 1], inf),

+

zeroX: Dseq([2, 3], inf),

+

rate: 0.2

+

);

+

}.plot(0.14)

+

)

+


+

+


+

+


+

+


+

+

Ex.3) The 'overlapSize' arg

+


+

+


+

+

// TZeroXBufRd enables overlappings of half wavesets, the maximum number of

+

// overlaps is given with the non-modulatable arg 'overlapSize' which defaults to 10.

+

// So if waveset groups are too long and/or the trigger rate is too high

+

// groups will be cut and the synthesis, according to the other passed args, fails.

+


+

+

// Supposed constant values, the minimum necessary overlapSize can be calculated

+

// by the formula:

+


+

+

// ceiling((wavesetGroupSampleNum * trigRate) / (sampleRate * rate))

+


+

+

// where wavesetGroupSampleNum means the number of samples of a group,

+

// which consists of xNum * xRep half wavesets.

+


+

+


+

+

// It's the user's responsibility to care for a sufficiently large overlapSize,

+

// resp. sufficiently low trigger rates, xNum and xRep args as well as not too low playback rates.

+

// With recorded sounds it might be necessary to estimate the largest half wavesets

+

// by analysis in the language.

+


+

+

// An easy way to reduce the maximum and average half waveset length is 

+

// repeating the zeroX analysis with a LeakDC or high pass filter applied.

+


+

+


+

+

// Buffers from Ex.1

+

// We suppose a sample rate of 44100

+


+

+

b.loadToFloatArray(action: { |b| v = b })

+


+

+

z.loadToFloatArray(action: { |b| w = b })

+


+

+

// zero crossing analysis data

+


+

+

w

+


+

+

// with zeroX = 1 and xNum = 7 we have a chunk of 194 samples

+


+

+

w[8] - w[1]

+


+

+


+

+

// according to the above formula, with a trigger rate of 500 and

+

// a playback rate of 0.5 the minimal necessary overlapSize is 5

+


+

+

194 * 500 / (44100 * 0.5)

+


+

+

-> 4.3990929705215

+


+

+

// check with overlapSizes of 5 (or higher),

+

// it's the same and sounding smooth,

+

// with overlapSize = 4 the result is erroneous, resp. distorted

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(500),

+

zeroX: 1,

+

xNum: 7,

+

rate: 0.5,

+

overlapSize: 5,

+

) * 0.1

+

}.play

+

)

+


+

+

s.scope

+


+

+

x.release

+


+

+


+

+


+

+

Ex.4) Multichannel usage and the 'bufMix' arg

+


+

+

// This is very simliar to the conventions of ZeroXBufRd,

+

// buffers can be switched per trigger.

+


+

+

// Without passing a bufMix arg the size of the returned signal is determined by the buffer input. 

+

// It may be a single channel buffer or an array of single channel buffers, 

+

// in correspondence with the analysis buffer(s) - multichannel buffers are not allowed. 

+

// If bufMix is passed, it determines the size of the returned signal, 

+

// its components can be demand rate or other ugens to control switching between buffers per half waveset groups.

+


+

+

// Note: buffer switching can become CPU-demanding with a lot of Buffers 

+

// as for fast switching it is necessary to play all in parallel

+


+

+

(

+

// boot with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.options.memSize = 8192 * 32; 

+

s.reboot;

+

)

+


+

+


+

+

// prepare 3 buffers

+


+

+

(

+

b = { Buffer.alloc(s, 1000, 1) } ! 3;

+

z = { Buffer.alloc(s, 100, 1) } ! 3;

+

)

+


+

+

// fill with basic waveforms

+

(

+

{

+

var src = [

+

SinOsc.ar(400),

+

LFTri.ar(400),

+

SinOsc.ar(400) ** 10

+

];

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+

+


+

+

s.scope

+


+

+

// play 2 channels with sine and triangle, where

+

// triangle is alternating between half and full waveset

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(100),

+

xNum: [2, Dseq([1, 2], inf)],

+

zeroX: 0,

+

bufMix: [0, 1]

+

) * 0.1

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// sequencing repetitions

+

// if both channels should get the same sequence wrap demand rate ugen into a Function

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(100),

+

xNum: 1,

+

xRep: { Dseq((1..5), inf) },

+

zeroX: 0,

+

bufMix: [0, 1],

+

rate: 0.7

+

) * 0.1

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// overlapping groups

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(100),

+

xNum: 1,

+

xRep: [Dseq((1..15), inf), Dseq((15..1), inf)],

+

zeroX: 0,

+

bufMix: [0, 1],

+

rate: MouseX.kr(0.2, 3),

+

overlapSize: 10

+

) * 0.1

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// estimate the maximum xRep for the given overlapSize,

+

// in the above example (suppose samplerate 44100):

+

// as the buffers have been generated with freq = 400,

+

// we can roughly estimate the half wavesets with 55 samples.

+


+

+

// According to the formula of Ex. 2

+


+

+

55 * xRep * 100 / (44100 * 0.2) = 10

+


+

+

xRep = (44100 * 0.2) * 10 / (55 * 100)

+


+

+

-> 16.036363636364

+


+

+

// so with xRep values up to 17 we get a bit of distortion besides the aliasing (hardly audible),

+

// it gets stronger with lower overlapSize and disappears with higher values

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(100),

+

xNum: 1,

+

xRep: [Dseq((1..17), inf), Dseq((17..1), inf)],

+

zeroX: 1,

+

bufMix: [0, 1],

+

rate: 0.2,

+

overlapSize: 10   // check with higher and lower values

+

) * 0.1

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+


+

+

// alternating buffers in one channel

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(100),

+

xNum: 2,

+

xRep: 1,

+

zeroX: 1,

+

bufMix: Dseq([0, 1], inf),

+

rate: 0.6,

+

) * 0.1

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// various sequences in both channels with switched waveforms

+


+

+

(

+

x = {

+

TZeroXBufRd.ar(

+

b, z,

+

trig: Impulse.ar(200),

+

xNum: [Dseq([1, 2], inf), Dseq([2, 1], inf)],

+

xRep: [Dseq([1, 2], inf), Dseq([2, 1], inf)],

+

zeroX: 0,

+

bufMix: [Dseq([0, 1, 2], inf), Dseq([1, 2, 0], inf)],

+

rate: MouseX.kr(0.2, 2),

+

) * 0.1

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

Ex.5) The overall envelope

+


+

+

// This works also like for ZeroXBufRd, 'length' refers to the maximum number of triggers for half waveset groups.

+

 

+

// The finishing of a TZeroXBufRd is not detemined by finite demand rate ugens but by an overall envelope, 

+

// its release section is triggered by a maximum number of half wavesets ('length') or a maximum time. 

+


+

+

// Buffers from Ex.4

+


+

+

{ TZeroXBufRd.ar(b[0], z[0], trig: Impulse.ar(500), rate: 1, length: 10, rel: 0.01) }.plot(0.05)

+


+

+

{ TZeroXBufRd.ar(b[0], z[0], trig: Impulse.ar(500),  rate: 1, maxTime: 0.02, rel: 0.01) }.plot(0.05)

+


+

+


+

+

// envelopes can be differentiated

+


+

+

{ TZeroXBufRd.ar(b[0..1], z[0..1], trig: Impulse.ar(500), rate: 1, maxTime: [0.01, 0.005], rel: [0.005, 0.02]) }.plot(0.03)

+


+

+

{ TZeroXBufRd.ar(b[0..1], z[0..1], trig: Impulse.ar(500), rate: 1, length: [7, 2], rel: [0.005, 0.02]) }.plot(0.03)

+


+

+


+

+

// there should be only one doneAction 2 in this case

+


+

+

{ TZeroXBufRd.ar(b[0..1], z[0..1], trig: Impulse.ar(500), rate: 1, maxTime: [0.01, 0.005], rel: [0.05, 0.5], doneAction: [0, 2]) }.play

+


+

+


+

+

(

+

b.do(_.free);

+

z.do(_.free);

+

)

+


+

+


+

+

Ex.6) Simultaneous writing and reading

+


+

+


+

+

// The reading of half wavesets can start before analysis is finished,

+

// if TZeroXBufRd is carefully used in with a bit of delay.

+

// A bit more delicate than with ZeroXBufRd because of the independent trigger.

+


+

+


+

+

// prepare buffers

+


+

+

(

+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

b = Buffer.read(s, p);

+

)

+


+

+

(

+

z = Buffer.alloc(s, b.duration * 44100 / 5, 1);

+

s.scope;

+

)

+


+

+

// Simultaneous writing and reading is easier with ZeroXBufRd 

+

// as the trigger deltas are given by the source then.

+


+

+

(

+

{

+

var src = PlayBuf.ar(1, b, BufRateScale.ir(b));

+

// write zero crossings, but no need to overwrite sound buffer

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, writeSndBuf: 0);

+

DelayL.ar(

+

TZeroXBufRd.ar(

+

b, z,

+

// indicating stereo

+

bufMix: [0, 0],

+

// one trigger for both channels

+

// sufficiently slow progress for the given source

+

// (can not be guaranteed for an arbitrary signal)

+

trig: TDuty.ar(Dstutter(5, Dseq([1, 5, 10], inf)) * ControlDur.ir),

+

xNum: 1,

+

xRep: 2,

+

// stereo, drate ugens must be wrapped

+

zeroX: { Dseries() },

+

rate: { Dwhite(0.5, 0.7) }, 

+

maxTime: 7,

+

rel: 1,

+

doneAction: 2

+

),

+

0.1,

+

0.1

+

);

+

}.play

+

)

+


+

+

(

+

b.free;

+

z.free;

+

)

+


+

+

// It's of course unproblematic – and still quasi realtime – to fully a analyse 

+

// a snippet of sound with ZeroXBufWr before freely using TZeroXBufRd in the same synth

+


+

+


+

+

Ex.7) Granulation with movement through a buffer

+


+

+

// See Buffer Granulation tutorial, Ex. 1h

+


+

+


+

+

Ex.8) Pulsar synthesis with envelopes, variable number of source and envelope half wavesets

+


+

+

// Thanks to Marcin Pietruszewski for discussions on pulsar synthesis with envelopes

+


+

+

// Durations of envelopes can be adapted to source waveset lengths, regardless of

+

// half waveset numbers (xNum) and numbers of repetition (xRep) for source and envelope.

+

// This needs a bit of analysis.

+


+

+


+

+

// prepare buffers

+


+

+

(

+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

~srcBuf = Buffer.read(s, p);

+

~srcBufZeros = Buffer.alloc(s, 100000);

+


+

+

~envBuf = Buffer.alloc(s, 1000, 1);

+

~envBufZeros = Buffer.alloc(s, 100, 1);

+

)

+


+

+

// analyse source buffer and sine envelope

+


+

+

(

+

{

+

    var src = PlayBuf.ar(1, ~srcBuf, BufRateScale.ir(~srcBuf), doneAction: 2);

+

    ZeroXBufWr.ar(

+

src, ~srcBuf, ~srcBufZeros,

+

startWithZeroX: 0, adjustZeroXs: 1, doneAction: 2

+

);

+

}.play;

+


+

+

{

+

    var src = SinOsc.ar(50);

+

    ZeroXBufWr.ar(

+

src, ~envBuf, ~envBufZeros,

+

startWithZeroX: 1, adjustZeroXs: 0, doneAction: 2

+

);

+

    Silent.ar

+

}.play

+

)

+


+

+


+

+

// load source and envelope data into language

+


+

+

(

+

~srcBufZeros.loadToFloatArray(action: { |x| ~srcBufZeroXArr = x.reject(_==0) });

+

~srcBuf.loadToFloatArray(action: { |x| ~srcBufArr = x });

+


+

+

~envBufZeros.loadToFloatArray(action: { |x| ~envBufZeroXArr = x.as(Array) });

+

~envBuf.loadToFloatArray(action: { |x| ~envBufArr = x });

+

)

+


+

+


+

+

// choose source and envelope waveset and

+

// maximum number of half wavesets

+


+

+

// finally we need the lengths of the wavesets (~srcZeroXDiffs, ~envZeroXDiffs) for

+

// adjusting the envelope playback rate in the SynthDef dynamically

+


+

+

(

+

~srcZeroXOffset = 1019;  // check with other offset

+

~srcMaxHalfWavesetNum = 2;

+


+

+

~srcZeroXIndices = ~srcBufZeroXArr[~srcZeroXOffset..~srcZeroXOffset + ~srcMaxHalfWavesetNum];

+

~srcZeroXDiffs = ~srcZeroXIndices.differentiate.drop(1);

+


+

+

~envZeroXOffset = 0;

+

~envMaxHalfWavesetNum = 2;

+


+

+

~envZeroXIndices = ~envBufZeroXArr[~envZeroXOffset..~envZeroXOffset + ~envMaxHalfWavesetNum];

+

~envZeroXDiffs = ~envZeroXIndices.differentiate.drop(1);

+


+

+

[

+

~srcBufArr[(~srcZeroXIndices[0]..~srcZeroXIndices[~srcMaxHalfWavesetNum])],

+

~envBufArr[(~envZeroXIndices[0]..~envZeroXIndices[~envMaxHalfWavesetNum])]

+

].plot;

+

)

+


+

+


+

+

(

+

SynthDef(\pulsar_1, { |out, freq = 50, freqDev = 0.1, xNumSrc = 1, xRepSrc = 1,

+

rateSrc = 1, xNumEnv = 1, xRepEnv = 1, power = 1, amp = 1|

+

var sig, env, trig, xNumSrcIndicator, xNumEnvIndicator,

+

rawSrcSampleNum, rawEnvSampleNum, rateEnv;

+


+

+

// need 1-0 arrays for calculating the number of samples dynamically

+

xNumSrcIndicator = { |i| xNumSrc > DC.ar(i) } ! ~srcMaxHalfWavesetNum;

+

xNumEnvIndicator = { |i| xNumEnv > DC.ar(i) } ! ~envMaxHalfWavesetNum;

+


+

+

rawSrcSampleNum = (xNumSrcIndicator * ~srcZeroXDiffs).sum * xRepSrc;

+

rawEnvSampleNum = (xNumEnvIndicator * ~envZeroXDiffs).sum * xRepEnv;

+


+

+

// pulsar source and envelope must have same length

+

// so we must have:

+

// rawSrcSampleNum / rateSrc == rawEnvSampleNum / rateEnv

+

// hence rateEnv can be calculated:

+


+

+

rateEnv = rawEnvSampleNum * rateSrc / rawSrcSampleNum;

+


+

+

// a bit of frequency decorrelation

+

trig = Impulse.ar([1, freqDev / 100 + 1] * freq);

+


+

+

// stereo source

+

sig = TZeroXBufRd.ar(

+

~srcBuf,

+

~srcBufZeros,

+

[~srcBuf, ~srcBuf],

+

xNum: xNumSrc,

+

xRep: xRepSrc,

+

trig: trig,

+

zeroX: 1001,

+

rate: rateSrc

+

);

+


+

+

// stereo envelope

+

env = TZeroXBufRd.ar(

+

~envBuf,

+

~envBufZeros,

+

[~envBuf, ~envBuf],

+

xNum: xNumEnv,

+

xRep: xRepEnv,

+

trig: trig,

+

zeroX: 0,

+

rate: rateEnv

+

);

+

Out.ar(out, Limiter.ar(env ** power * sig) * amp)

+

},

+

// make lags, but not for discrete args

+

(1!3) ++ (0!2) ++ 1 ++ (0!2) ++ [1, 0.1],

+

metadata: (

+

specs: (

+

freq: [20, 150, \exp, 0, 50],

+

freqDev: [0, 2, 3, 0, 0.3],

+

xNumSrc: [1, 2, \lin, 1, 1],

+

xRepSrc: [1, 2, \lin, 1, 1],

+

rateSrc: [0.3, 3, \lin, 0, 1],

+


+

+

xNumEnv: [1, 2, \lin, 1, 1],

+

xRepEnv: [1, 2, \lin, 1, 1],

+


+

+

power: [0.5, 5, \exp, 0, 1],

+

amp: [0, 2, \lin, 0, 1]

+

)

+

)

+

).add

+

)

+


+

+


+

+

s.scope;

+

s.freqscope;

+


+

+

\pulsar_1.sVarGui.gui(synthColorGroups: (0..8).clumps([5, 4]))

+


+

+


+

+

Ex.9) Layers of pulsar streams

+


+

+


+

+

// Thanks to Jan Ferreira for discussions on pulsar masking

+


+

+

(

+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

~srcBuf = Buffer.read(s, p);

+

~srcBufZeros = Buffer.alloc(s, 100000);

+

)

+


+

+


+

+

// analyse source buffer

+


+

+

(

+

{

+

var src = PlayBuf.ar(1, ~srcBuf, BufRateScale.ir(~srcBuf), doneAction: 2);

+

ZeroXBufWr.ar(

+

src, ~srcBuf, ~srcBufZeros,

+

startWithZeroX: 0, adjustZeroXs: 1, doneAction: 2

+

);

+

}.play;

+

)

+


+

+

// for low rates you might have to take a larger overlap value (see Ex. 3)
+

+

(

+

SynthDef(\pulsar_2, { |out, freq = 50, maxFreqDev = 2, freqDevFreq = 0.3,

+

xNumSrc = 1, xRepSrc = 1, rateSrc = 1, amp = 1|

+

var sig, result, env, trig, seq, gates, gatesL, gatesR, pulsarNum;

+


+

+

// a bit of frequency decorrelation

+


+

+

pulsarNum = 4;

+

seq = [1, 1, 0, 0];

+


+

+

// 2 * 4 impulse streams with slightly modulated frequencies

+

trig = { Impulse.ar({ LFDNoise3.ar(freqDevFreq).range(0, maxFreqDev) } ! pulsarNum / 100 + 1 * freq) } ! 2;

+


+

+

// 2 * 4 pulsar streams

+

sig = { |i|

+

TZeroXBufRd.ar(

+

~srcBuf,

+

~srcBufZeros,

+

~srcBuf ! pulsarNum,

+

xNum: xNumSrc,

+

xRep: xRepSrc,

+

trig: trig[i],

+

zeroX: 1005,

+

        rate: rateSrc,

+

overlapSize: 5

+

)

+

} ! 2;

+


+

+

// masking of pulsars with gates

+

gatesL = sig[0].collect { |s| Demand.ar(s, 0, Dseq(seq.scramble, inf)) };

+

gatesR = sig[1].collect { |s| Demand.ar(s, 0, Dseq(seq.scramble, inf)) };

+


+

+

result = [(sig[0] * gatesL).sum, (sig[1] * gatesR).sum];

+


+

+

// more condensed writing of the previous 3 lines:

+

// gates = sig.collect { |x| x.collect { |y| Demand.ar(y, 0, Dseq(seq.scramble, inf)) } };

+

// result = (sig * gates).collect(_.sum);

+


+

+

Out.ar(out, Limiter.ar(result) * amp)

+

},

+

// make lags, but not for discrete args

+

(1!4) ++ (0!2) ++ (0!2),

+

metadata: (

+

specs: (

+

freq: [20, 250, \exp, 0, 100],

+

maxFreqDev: [0, 5, 3, 0, 1.5],

+

freqDevFreq: [0, 5, 3, 0, 0.2],

+

xNumSrc: [1, 2, \lin, 1, 1],

+

xRepSrc: [1, 2, \lin, 1, 1],

+

rateSrc: [0.4, 6, \lin, 0, 1],

+


+

+

xNumEnv: [1, 2, \lin, 1, 1],

+

xRepEnv: [1, 2, \lin, 1, 1],

+


+

+

seqL: [0, 1, \lin, 1, 1],

+

seqR: [0, 1, \lin, 1, 1],

+

power: [0.5, 5, \exp, 0, 1],

+

amp: [0, 1, \lin, 0, 0.5]

+

)

+

)

+

).add

+

)

+


+

+


+

+

s.scope;

+

s.freqscope;

+


+

+

\pulsar_2.sVarGui.gui(synthColorGroups: (0..6).clumps([3, 2, 1, 1]))

+


+

+


+

+ + diff --git a/Help/VarGui shortcut builds.html b/Help/VarGui shortcut builds.html new file mode 100644 index 0000000..d302c78 --- /dev/null +++ b/Help/VarGui shortcut builds.html @@ -0,0 +1,1099 @@ + + + + + + + + + + + +

VarGui shortcut builds quick building of slider / player guis for synths and sequencing

+


+

Part of: miSCellaneous

+


+

See also: VarGui, HS with VarGui, PLx suite, Buffer Granulation

+


+


+

VarGui allows combined control of Synths, Pbinds / EventStreamPlayers and Tasks in one GUI. Many of its options though may not be relevant for most usages, also control specs were to be given directly (changed with v0.5). Shortcut build methods take advantage of SynthDef controlspec metadata, if defined (or global ControlSpecs), and autogenerate Pbinds for sequencing. Controls and Pbinds can be customized with options. 

+

Main tools are the methods sVarGui (synth), pVarGui (pattern) and compound builders spVarGui / psVarGui. Methods sVarGui / pVarGui, applied to a single Symbol / String, produce a VarGui for one or more Synth(s) or Pbind(s) based on the referred single SynthDef (Exs. 1a, 1b, 2a, 2b). Both methods also apply to collections of SynthDef names (3a, 3b), whereas spVarGui and psVarGui, builders of mixed Synth / Pbind VarGuis, expect collections of two items which may be Symbols / Strings or collections of Symbols / Strings (3c).

+

Examples are using Patterns from dynamic scope PLx suite for conveniently refering to environmental variables.

+


+


+

1.) Synth GUIs

+


+

Example 1a:   default instrument 

+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// No metadata is defined with default instrument, per default global ControlSpecs are used.

+

// Here its definition from Event.sc:

+


+

(

+

SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1;

+

var z;

+

z = LPF.ar(

+

Mix.new(VarSaw.ar(freq + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3)),

+

XLine.kr(Rand(4000,5000), Rand(2500,3200), 1)

+

) * Linen.kr(gate, 0.01, 0.7, 0.3, 2);

+

OffsetOut.ar(out, Pan2.ar(z, pan, amp));

+

}, [\ir]).storeOnce;

+

)

+


+


+

// With the next line you should get a VarGui with controls for freq, amp, pan and gate.

+

// In SC 3.5.0 gate isn't stored anymore in global ControlSpecs, so it won't be there.

+

// If you encounter other differences, global ControlSpecs and / or the 

+

// default instrument have probably been changed,

+

// see below how to display and change global Specs.

+

// In global ControlSpecs amp (and, if there, gate) default to 0, 

+

// you have to raise both in order to hear sound after pressing the player button.

+


+

\default.sVarGui.gui;

+


+


+

// automatic use of global ControlSpecs turned off, Synths with default args

+


+

\default.sVarGui(useGlobalSpecs: false).gui;

+


+


+

// global specs, not all of them ControlSpecs, are stored in Spec.specs ...

+


+

Spec.specs.associationsDo(_.postln);

+


+


+

// ... they can be added or replaced with .add

+

// for default instrument examples 

+

// we replace \amp spec with a new ControlSpec with default 0.1 

+


+

Spec.add(\amp, [0, 1, \lin, 0, 0.1]);

+


+


+

// generating a number of Synths, controls can be excluded,

+

// parallel slider dragging with Alt, parallel button action with Shift-click

+


+

\default.sVarGui(exclude: [\gate, \pan],  num: 3).gui;

+


+


+


+

// controls can be replaced ...

+


+

\default.sVarGui(ctrReplace: [\freq, [400, 440, \exp, 0, 420]], exclude: \gate).gui;

+


+


+

// ... and added, before or after the main block of controls from metadata or global ControlSpecs,

+

// this collection is empty here, so ctrBefore and ctrAfter are equivalent

+


+

\default.sVarGui(useGlobalSpecs: false, ctrBefore: [\freq, [400, 440, \exp, 0, 420]]).gui;

+


+


+

// bad definition, \freq is already used as global ControlSpec 

+


+

\default.sVarGui(ctrBefore: [\freq, [300, 1000, \exp, 0, 440]]).gui; // WRONG

+


+


+


+

Example 1b:   SynthDefs including metadata 

+


+


+

SynthDef with defined controlspec metadata. The specs can be stored as ControlSpecs or Arrays, may also be Symbols / Strings refering to the global dictionary of Specs (Spec.specs). It's only convention to store specs under the key \specs, it can be anything else, and nothing prevents you from storing more than one dictionary of ControlSpecs per SynthDef - the VarGui shortcut methods have a metaKey arg (default: \specs) which determines where to lookup in the metadata dictionary.

+


+


+

(

+

SynthDef(\buzz, { arg freq = 400, out, gate = 1, freqDev = 0.01, att = 0.01, dec = 0.01, susLevel = 0.5, rel = 1, 

+

cutoff = #[1000, 1000, 1000], rq = 0.25, amp = 0.2, preAmp = 1, maxDelay = 0.03;

+

    var src, srcFreq, sig;

+

    sig = Mix.fill(3, { |i|

+

    // chorus spread over three registers 

+

    srcFreq = (2 ** i) * freq * (1 + Rand(freqDev.neg, freqDev)) / 2;

+

    src = RLPF.ar(Saw.ar(srcFreq, preAmp), cutoff[i], rq).softclip * amp * 

+

    EnvGen.kr(Env.adsr(att, dec, susLevel, rel), gate, doneAction: 2);

+

    // random spatialization (for sequencing) by L/R delay

+

    [DelayL.ar(src, maxDelay, Rand(0, maxDelay)), DelayL.ar(src, maxDelay, Rand(0, maxDelay))]

+

}) ;

+

    // ensure sample accurate output for events of short durations    

+

    OffsetOut.ar(out, sig);

+

    

+

    }, metadata: (

+

    specs: (

+

    freq: [20, 5000, \exp, 0, 80],

+

    gate: [0, 1.0, \lin, 0, 1],

+

     freqDev: [0.0, 0.02, \lin, 0, 0.002],

+

  att: [0.01, 0.2, \lin, 0, 0.01],

+

    dec: [0.01, 0.2, \lin, 0, 0.01],

+

    susLevel: [0.01, 1, \lin, 0, 0.5],

+

    rel: [0.01, 2, \exp, 0, 1],

+

    cutoff: [200, 5000, \exp, 0, 1000],

+

    rq: [0.1, 0.9,\lin, 0, 0.2],

+

    amp: [0.0, 1, \lin, 0, 0.2],

+

    preAmp: [0.9, 5, \lin, 0, 1],

+

    maxDelay: [0, 0.01, \lin, 0, 0.002]

+

    ),

+

    // doesn't matter for VarGui if spec defined as Array or ControlSpec,

+

    // an arbitrary number of arbitrarily named alternative Spec Events may be passed,

+

    // for non-sequencing use adsr params may be dropped

+

    basicSpecs: (

+

    freq: [20, 5000, \exp, 0, 80],

+

    gate: [0, 1.0, \lin, 0, 1],

+

    cutoff: [200, 5000, \exp, 0, 1000],

+

    rq: [0.1, 0.9,\lin, 0, 0.2],

+

    amp: [0.0, 1, \lin, 0, 0.2],

+

    preAmp: [0.9, 5, \lin, 0, 1]

+

    )

+

    ) 

+

).add;

+

)

+


+


+

// here metadata includes a gate spec with default 1

+

// metadata has priority over global ControlSpecs

+

// metaKey \specs used per default

+


+

\buzz.sVarGui.gui;

+


+


+

// use gui options for better arrangement 

+


+

\buzz.sVarGui(num: 4).gui(tryColumnNum: 2, sliderHeight: 16);

+


+


+

// alternative metadata, metaKey to be passed,

+

// mostly sounds different from last version 

+

// because of fixed max chorus-width param freqDev

+


+

\buzz.sVarGui(num: 4, metaKey: \basicSpecs).gui(tryColumnNum: 2);

+


+


+


+

2.) Sequencing GUIs

+


+


+

Controlling Pbinds / EventStreamPlayers with VarGui is based on (a) the setting of environmental variables and (b) the proper definition of Pbinds with Patterns of PLx suite or Patterns with functional code (Event patterns and Functions) so that EventStreamPlayers get the updated variable values in the following events.

+


+


+

Basic example of a Pbind to get envir values:

+


+


+

p = Pbind(

+

\dur, Pfunc { ~dur } * Pseq([2,1,1], inf), 

+

\legato, Pfunc { ~legato }, 

+

\amp, Pfunc { ~amp } 

+

)

+


+

// or

+


+

p = Pbind(

+

\dur, PL(\dur) * Pseq([2,1,1], inf), 

+

\legato, PL(\legato), 

+

\amp, PL(\amp) 

+

)

+


+

Especially when based on a SynthDef with a large number of args writing a Pbind can be lengthy, moreover redundant if there are many pbind pairs of the form 

+


+


+

[ aSymbol, Pfunc { ~aSymbol } ] 

+


+


+

The pVarGui method is going the opposite way: Pbind pairs get this most simple form for all args with metadata or global ControlSpecs defined, though it's possible to exclude keys and add respectively replace customized pairs before and after the automatically generated ones. All you need is a SynthDef that is suited for Pbind sequencing (known to SynthDescLib by being added, properly defined Envelopes).

+

WARNING: as a lot of the underlying Pbind structure (by default all of it !) is hidden, it's possible to call pVarGui without knowing anything about Patterns. Nevertheless it's highly recommended to be aware of the mechanisms of Pbind for applying useful sequencing customizations and having a look at the fully written Pbind examples in VarGui. Clearly: adding, replacing and excluding Pbind pairs without seeing the resulting code is a possible source of error, so if using VarGui this way better start slowly and go on stepwise. With the post option you can print out the ordered list of pairs.

+


+


+

Example 2a:   default instrument 

+


+


+

// for default instrument examples 

+

// we replace \amp spec with a new ControlSpec with default 0.1 

+


+

Spec.add(\amp, [0, 1, \lin, 0, 0.1]);

+


+


+

// Most simple example to get a VarGui controlling a Pbind / EventStreamPlayer with 

+

// default instrument, control of duration and legato is automatically added.

+

// As with the sVarGui method synth controls are added when found in metadata definition 

+

// or global ControlSpecs, different from sVarGui \gate is excluded by default.

+


+

\default.pVarGui.gui;

+


+


+

// adding a pattern pair - if it contains one of the keys \note, \midinote or \degree

+

// the freq pair is removed automatically, see posted Pbind pairs 

+


+

\default.pVarGui(pAfter: [\degree, Pwhite(0,5)], post: true).gui;

+


+


+

// pattern pair replacement as in basic example above

+


+

\default.pVarGui(pReplace: [\dur, PL(\dur) * Prand([2,1,1], inf)] ).gui;

+


+


+

// adding a pattern pair after the main block of pairs,

+

// same effect as before

+


+

\default.pVarGui(pAfter: [\dur, Pkey(\dur) * Prand([2,1,1], inf)], post: true).gui;

+


+


+

// Internally a Pbind of this form had been generated (see post window), 

+

// in each Event the second Stream with key \dur gets the value from the first, 

+

// overrides it in the Event. 

+


+

Pbind(

+

// pBefore pairs (optinally passed to pVarGui) 

+

...

+

+

// these are always placed before the main block

+

\instrument, \default,

+

\dur, Pfunc { ~dur },

+

\legato, Pfunc { ~legato },

+

+

// main block of pairs corresponding to args for which 

+

// spec definition was found (if not excluded or replaced), 

+

// ordered like in SynthDef

+

+

\freq, Pfunc { ~freq },

+

\amp, Pfunc { ~amp },

+

\pan, Pfunc { ~pan },

+

+

// pAfter pairs (optinally passed to pVarGui)

+

\dur, Pkey(\dur) * Prand([2,1,1], inf)

+

)

+


+

+

// Additional arrayed control for simple step sequencing with random init values. 

+

// The PLseq degree pattern defaults to repeats = inf and envir = \current.

+

// It would take values from the current Environment of streamification.

+


+

// Here playing and setting the variables will 

+

// happen in a new Environment generated by the VarGui.

+


+

(

+

\default.pVarGui(

+

ctrBefore: [\a, { [0, 5, \lin, 1, rrand(0,6)] } ! 8 ],

+

pBefore: [\degree, PLseq(\a) ]

+

).gui;

+

)

+


+

// A number of Pbinds and control sets can be generated with the num arg,

+

// control and pattern customization args take Functions (optionally of indices)

+

// for expansion.

+


+

// This a potentially powerful feature, also suited for producing a real mess !

+

// Especially think of possibly inappropriate ranges ! 

+

// If you are unsure try out evaluating code of the form ...

+


+

{ |i| ... } ! n

+


+

// ... separately before plugging in the shortcut methods.

+


+


+

// Here the Function passed to ctrBefore has no index arg, 

+

// it just produces 4 different tuples of 

+

// init values for the step sequencers,

+


+

// the pBefore Function determines 4 different octave registers

+

// quant arg ensures staying in sync based on the passed rhythmic patterns

+


+

(

+

\default.pVarGui(

+

/* ctrBefore: */ { [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ] },

+

/* ctrReplace: */ [\dur, [0.2, 0.6, \lin, 0.2, 0.2]],

+

pBefore: { |i| [\degree, PLseq(\a), \octave, i+4] },

+

num: 4,

+

quant: 0.2,

+

post: true 

+

).gui(tryColumnNum: 2);

+

)

+


+


+


+

Example 2b:   SynthDefs including metadata 

+


+


+

Automatic Pbind generation in VarGui shortcut builds is especially saving typing if SynthDefs have a lot of args and controlspec metadata defined.

+


+

// most simple, controls are built for all args with metadata defined

+

// arrayed args are detected and controls are established for a corresponding array

+

// SynthDef from above

+


+

\buzz.pVarGui.gui;

+


+


+

// use the exclude option to exclude from automatically generated controls and Pbind pairs

+

// you would want to do that for a static value or sequencing not to be gui-controlled

+

 

+

 

+

// always SynthDef default value 0.01 for attack time 

+


+

\buzz.pVarGui(exclude: [\att], post: true).gui;

+


+


+

// sequencing without control

+

// RLP filter rq excluded from gui control and automatic Pbind pair generation, 

+

// though added to Pbind pairs again as passed to pBefore (could also be pAfter),

+

// here PLseq taken as it defaults to inf

+


+

\buzz.pVarGui(exclude: [\rq], pBefore: [\rq, PLseq([ 0.05, 0.8, 0.8, 0.8]) ], post: true).gui;

+


+


+

// same effect on sound, but we have a useless rq control

+


+

\buzz.pVarGui(pAfter: [\rq, PLseq([ 0.05, 0.8, 0.8, 0.8]) ], post: true).gui;

+

\buzz.pVarGui(pReplace: [\rq, PLseq([ 0.05, 0.8, 0.8, 0.8]) ], post: true).gui;

+


+


+

// here the pBefore arg is useless as the Pfunc that takes controlled values 

+

// comes after in execution and will overwrite the value per Event

+


+

\buzz.pVarGui(pBefore: [\rq, PLseq([ 0.05, 0.8, 0.8, 0.8]) ], post: true).gui;

+


+


+

// establishing a cutoff freq rhythm for all three layers

+

// remember that in Pbinds arrayed args must be distinguished from the syntax for generating 

+

// multiple synths per Event, this can be achieved by wrapping in an additional array

+


+

\buzz.pVarGui(pReplace: [\cutoff, Pfunc { [~cutoff] } * PLseq([3,1,1]) ]).gui; 

+


+


+


+

// step sequencer for quartertone bassline

+


+

(

+

\buzz.pVarGui(

+

ctrAfter: [\a, { var lo = 25, hi = 50; [lo, hi, \lin, 0.5, rrand(lo*2, hi*2) / 2 ] } ! 8 ],

+

pBefore: [\midinote, PLseq(\a)],

+

post: true

+

).gui;

+

)

+


+


+


+

// step sequencer with two voices

+

// by passing functions different control ranges and registers are established

+

// both lines are in a quartertone scale but shifted by an eigthtone

+

// lines itself can be shifted by an offset ~add

+


+

// modifier keys can be used to perform parallel slider or button actions

+

// alt for parallel slider movement and shift for parallel button actions (VarGui)

+

 

+


+

(

+

\buzz.pVarGui(

+

ctrReplace: [\dur, [0.1, 0.2, \lin, 0.1, 0.2], \amp, [0.0, 0.5, \lin, 0, 0.3]],

+

ctrAfter: { |i| var d = 0.25; 

+

[\add, [-12, 12, \lin, 0.5, 0],

+

\a, { [(i*d), 11.5 + (i*d), \lin, 0.25, rrand(0, 23) / 2 + (i*d)] } ! 8

+

] },

+

pBefore: { |i| [

+

\note, PLseq(\a) + PL(\add), 

+

\octave, 3 + (i*4) + Pwhite(0,1) 

+

] },

+

quant: 0.2,

+

post: true,

+

num: 2

+

).gui(tryColumnNum: 2);

+

)

+


+


+

Example 2c:   Combination of parameter control and pattern exchange (JITLib)

+


+


+

(

+

\buzz.pVarGui(

+

ctrReplace: [\freq, [20, 1000, \exp, 0, 80]],

+

pReplace: [

+

\cutoff, Pdefn(\co, Pseq([3, 1, 1, 1, 1], inf)) * Pfunc { [~cutoff] },

+

\freq, Pdefn(\fr, Pseq([1, 1, 1.3, 1.2, 1], inf)) * Pfunc { ~freq }

+

]

+

).gui;

+

)

+


+

// start from gui and eval pattern replacements before playing around with sliders

+


+

Pdefn(\fr, Pseq([1, 1, 1.7, 1.3, 1.2], inf));

+


+

Pdefn(\co, Pseq([2, 1, 1], inf));

+


+

Pdefn(\fr, 1);

+


+

Pdefn(\co, Pseq([1, 2, 1.7, 1.3, 1.2], inf));

+


+


+


+

Example 2d:   Combination of parameter control and pattern exchange (PLx)

+


+


+

// above done with PL

+

// note that PLs are taking envir variables from the current Environment by default,

+

// this is ok for the variables set by VarGui (freq) in its Environment,

+

// but for PLs to take values from elsewhere we must give the Environment explicitely.

+

// Suppose we are in topEnvironment here:

+


+

// As PL defaults to repeats = inf, Pseq can be taken without a repeats arg 

+

// for endless looping

+


+

(

+

~co = Pseq([3, 1, 1, 1, 1]);

+

~fr = Pseq([1, 1, 1.3, 1.2, 1]);

+


+

\buzz.pVarGui(

+

ctrReplace: [\freq, [20, 1000, \exp, 0, 80]],

+

pReplace: [

+

\cutoff, PL(\co, envir: \top) * Pfunc { [~cutoff] },

+

\freq, PL(\fr, envir: \top) * PL(\freq)

+

]

+

).gui;

+

)

+


+

// start from gui and eval pattern replacements before playing around with sliders

+


+

~fr = Pseq([1, 1, 1.7, 1.3, 1.2]);

+


+

~co = Pseq([2, 1, 1]);

+


+

~fr = 1;

+


+

~co = Pseq([1, 2, 1.7, 1.3, 1.2]);

+


+


+

//////////////////////////////////

+


+


+

// alternative version with a PLseq placeholder

+


+

(

+

~co = [3, 1, 1, 1, 1];

+

~fr = [1, 1, 1.3, 1.2, 1];

+


+

\buzz.pVarGui(

+

ctrReplace: [\freq, [20, 1000, \exp, 0, 80]],

+

pReplace: [

+

\cutoff, PLseq(\co, envir: \top) * Pfunc { [~cutoff] },

+

\freq, PLseq(\fr, envir: \top) * PL(\freq)

+

]

+

).gui;

+

)

+


+

// start from gui and eval pattern replacements before playing around with sliders

+


+

~fr = [1, 1, 1.7, 1.3, 1.2];

+


+

~co = [2, 1, 1];

+


+

~fr = [1];

+


+

~co = [1, 2, 1.7, 1.3, 1.2];

+


+


+


+


+

3.) Mixed GUIs

+


+


+

To control Synths or Pbinds / EventStreamPlayers derived from more than one SynthDef sVarGui and pVarGui can be applied to collections.

+


+


+

Example 3a:   Synths from different SynthDefs

+


+


+

// for default instrument examples 

+

// we replace \amp spec with a new ControlSpec with default 0.1 

+


+

Spec.add(\amp, [0, 1, \lin, 0, 0.1]);

+


+


+

// different synths 

+


+

[\buzz, \default].sVarGui.gui

+


+


+

// in basic form this works equivalently ...

+


+

VarGui(synth: [\buzz, \default]).gui;

+


+


+

// ... but for customization the shortcut method sVarGui is more convenient,

+

// otherwise you'd have to pass or generate controls explicitely (5b).

+

// sVarGui and pVarGui, applied to SequenceableCollections, expect Dictionaries

+

// of pairs argName / arg, so you can pass Events.

+

// Their number must equal the collection's size.

+


+

(

+

[\buzz, \default].sVarGui(

+

(ctrReplace: [\freq, [97, 103, \exp, 0, 99]]),

+

(ctrReplace: [\freq, [194, 206, \exp, 0, 201]], exclude: \gate)

+

).gui;

+

)

+


+

// more synths

+


+

(

+

[\default, \buzz].sVarGui(

+

(ctrReplace: { [\freq, [194, 206, \exp, 0, rrand(194.0, 206)]] }, exclude: \gate, num: 4),

+

(ctrReplace: [\freq, [97, 103, \exp, 0, 99]])

+

).gui(tryColumnNum: 2, allowSynthBreak: false);

+

)

+


+


+


+

Example 3b:   Pbinds from different SynthDefs

+


+


+

// overtones, deviations of pitch and pulsation 

+


+

(

+

[\default, \buzz].pVarGui(

+

(ctrReplace: { |i| var f = (2*i + 1) * 100, d = 0.2, lo = 0.99, hi = 1.01; 

+

[\freq, [f*lo, f*hi, 0, 0, f*rrand(lo,hi)], \dur, [d*lo, d*hi, 0, 0, d*rrand(lo,hi)] ] }, 

+

exclude: \gate, 

+

post: true,

+

num: 4),

+

(ctrReplace: [\freq, [97, 103, \exp, 0, 99], \dur, [0.195, 0.205, 0, 0, 0.2] ])

+

).gui(tryColumnNum: 2, allowEnvirBreak: false)

+

)

+


+


+

// step sequencer 

+


+

(

+

[\buzz, \default].pVarGui((

+

ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ],

+

ctrReplace: [\dur, [0.2, 0.4, \lin, 0.2, 0.2]],

+

pBefore: [\degree, PLseq(\a), \octave, 3], 

+

quant: 0.2

+

),(

+

ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 4 ],

+

ctrReplace: [\dur, [0.1, 0.3, \lin, 0.1, 0.2]],

+

pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + PLseq(\a), 

+

\octave, Pwhite(5,7) ], 

+

quant: 0.2

+

)

+

).gui(tryColumnNum: 2, allowEnvirBreak: false);

+

)

+


+


+


+

Example 3c:   Synths and Pbinds 

+


+


+

// Pbind granulation with \buzz, controls and pbind pairs adapted and added, 

+

// psVarGui (pbind input first) and spVarGui (synth input first)

+

// apply to collections of two items.

+


+

// If only one type of SynthDef is to be used for Pbind(s) or Synth(s)

+

// it may be a plain Symbol / String, the corresponding arg tuples may

+

// be given as plain (not collected) Dictionaries

+


+

// Keep in mind that nevertheless one Dictionary may produce 

+

// more than one Synth (Pbind)

+


+

(

+

[\buzz, \default].psVarGui(

+

(

+

ctrReplace: [\dur, r = [0.01, 0.05, \lin, 0, 0.03], 

+

\rel, r,

+

\freq, [100, 1000, \lin, 0, 100],

+

\amp, [0, 0.5, \lin, 0, 0.15],

+

\legato, [0.1, 2, \lin, 0, 0.5],

+

\freqDev, [0, 0.2, \lin, 0, 0.01]],

+

ctrBefore: [\durDev, [0, 1, \lin, 0, 0.01]],

+

pAfter: [\dur, Pkey(\dur) * Pfunc { x = 1 + ~durDev; rrand(1/x,x) }],

+

post: true

+

),(

+

ctrReplace: { [\dur, [0.01, 0.05, \lin, 0, 0.03],

+

\freq, [100, 1000, \lin, 0, rrand(100, 1000)]] }, 

+

exclude: \gate, 

+

num: 4

+

)

+

).gui(tryColumnNum: 2, allowEnvirBreak: false);

+

)

+


+


+

// If more than one type of SynthDef is to be used for Pbind(s) or Synth(s)

+

// Symbols / Strings must be collected, also the corresponding Dictionaries

+


+

// spVarGui just takes input in reversed order (synth first).

+

// This is independant from representation in the GUI

+

// which can be determined by args sliderPriority (\var or \synth)

+

// and playerPriority (\stream or \synth)

+


+

(

+

[\default, [\buzz, \default]].spVarGui(

+

(

+

ctrReplace: { [\dur, [0.01, 0.05, \lin, 0, 0.03],

+

\freq, [100, 1000, \lin, 0, rrand(100, 1000)]] }, 

+

exclude: \gate, 

+

num: 4

+

),[

+

(

+

ctrReplace: [\dur, r = [0.01, 0.05, \lin, 0, 0.03], 

+

\rel, r,

+

\freq, [100, 1000, \lin, 0, 100],

+

\amp, [0, 0.5, \lin, 0, 0.15],

+

\legato, [0.1, 2, \lin, 0, 0.5],

+

\freqDev, [0, 0.2, \lin, 0, 0.01]],

+

ctrBefore: [\durDev, [0, 1, \lin, 0, 0.01]],

+

pAfter: [\dur, Pkey(\dur) * Pfunc { var x = 1 + ~durDev; rrand(1/x,x) }],

+

post: true

+

),(

+

ctrReplace: [\dur, r = [0.01, 0.05, \lin, 0, 0.03], 

+

\freq, [100, 1000, \lin, 0, 100],

+

\amp, [0, 0.5, \lin, 0, 0.15],

+

\legato, [0.1, 2, \lin, 0, 0.5]],

+

ctrBefore: [\durDev, [0, 1, \lin, 0, 0.01],

+

\freqDev, [0, 0.2, \lin, 0, 0.01]],

+

pAfter: [\dur, Pkey(\dur) * Pfunc { var x = 1 + ~durDev; rrand(1/x,x) },

+

\freq, Pkey(\freq) * Pfunc { var x = 1 + ~freqDev; rrand(1/x,x) }],

+

post: true

+

)

+

]

+

).gui(tryColumnNum: 2, allowEnvirBreak: false, sliderPriority: \synth, playerPriority: \synth);

+

)

+


+


+


+

4.) Differences between shortcut builds and direct VarGui instantiation

+


+


+

.) Shortcut methods apply to Symbols / Strings or collections thereof for Synth and Pbind generation and control. Symbols / Strings refer to the corresponding SynthDefs, known to SynthDesLib.global. For passing Synths, nodeIDs, EventStreamPlayers, also Task Functions and Tasks, you would have to call VarGui directly.

+


+

.) Shortcuts and also direct instantiation (not before v0.5) are using SynthDef metadata and / or global ControlSpecs. Though customization options for specs (exclude, add, replace) are a main feature of the shortcut methods. As a compromise spec- and pbindmaker methods with these options may be plugged into direct instantiation (5b).

+


+

.) The pVarGui method invokes automatic Pbind generation with customization options for Pbind pairs (exclude, pBefore, pAfter, pReplace), VarGui expects Pbinds to be passed directly. As a compromise a pbindmaker method with these options may be plugged into direct instantiation (5b).

+


+

.) In shortcut methods the num arg causes coordinated expansion, all other args (except server) may optionally be given as Functions of indices. With VarGui input you'd have to pass lengthy nested Arrays or write something like { |i| ... } ! n for every arg explicitely, as sizes of input must match. As a compromise spec- and pbindmaker methods with num arg may be plugged into direct instantiation (5b).

+


+

.) Reordering / interleaving of controls of different Environments / Synths (VarGui helpfile Examples 5a and 5b) is not possible with shortcut methods. However this seems to be a quite special feature, with shortcut methods ordering of Synths / Pbinds is primarily defined by order of args, control customization options allow further modifications of order. Finally choosing sliderPriority (\synth or \var first) is a pure gui method option and applies to both ways of VarGui building.

+

+

+


+

5.) Shortcut method specifictions

+


+


+

Shortcut methods also take Functions, that should return args in a valid form. This can be used for defining controls in different ranges in combination with num. See examples above.

+


+


+

5a:   core shortcut methods

+


+

aSynthDefName.sVarGui(ctrBefore, ctrReplace, ctrAfter, exclude, metaKey, useGlobalSpecs, num, server)

+

+

Instantiate a VarGui object for control of one or more Synth(s) derived from a SynthDef name (Symbol / String).

+

Per default controlspecs are taken from metadata definition, if defined with the SynthDef, or

+

the global dictionary Spec.specs. Controls can be excluded with exclude, added with 

+

ctrBefore or ctrAfter and replaced with ctrReplace, this is also the order of operations.

+

+

ctrBefore - a spec pair collection of the form [ key, spec, ...  ] where

+

key is the symbol of the control arg to be set and

+

spec can be a collection of the form [ minval, maxval, warp, step, default ] 

+

defining the corresponding ControlSpec, a Symbol / String to look for a ControlSpec in the

+

global dictionary Spec.specs or a collection of specs of this form

+

for an arrayed control. May also be a SimpleNumber for a dummy slider with fixed value. 

+

Defaults to nil. Also takes a Function of indices that returns a valid arg. 

+

ctrReplace - a spec pair collection of a form like ctrBefore. 

+

Also takes a Function of indices that returns a valid arg. Defaults to nil.

+

ctrAfter - a spec pair collection of a form like ctrBefore. 

+

Also takes a Function of indices that returns a valid arg. Defaults to nil.

+

exclude - a collection of control keys (Symbols) to be excluded. 

+

Also takes a Function of indices that returns a valid arg. Defaults to nil.

+

metaKey - Symbol. Key to lookup SynthDef metadata. 

+

Also takes a Function of indices that returns a Symbol. 

+

Defaults to \specs.

+

useGlobalSpecs - Boolean. Defines if to consider global ControlSpecs if 

+

metadata is not given. Also takes a Function of indices that returns a Boolean. 

+

Defaults to true.

+

num - Integer. Number of Synths to be controlled. Defaults to 1.

+

server - Server. The server to send node messages for synth control. 

+

Defaults to the default server.

+


+

+

aSequenceableCollection.sVarGui(argDictionary1, ... , argDictionaryN, server)

+


+

Instantiate a VarGui object for control of Synths derived from a collection of SynthDef names (Symbols / Strings).

+

Options for Synth controls are taken from the corresponding arg Dictionaries.

+

Size of collection must equal N, the number of Dictionaries. 

+

See aSynthDefName.sVarGui for valid arg syntax.

+

+

server - Server. The server to send node messages for synth control. 

+

Defaults to the default server.

+

+


+

aSynthDefName.pVarGui(ctrBefore, ctrReplace, ctrAfter, durCtr, legatoCtr, pBefore, pReplace, pAfter, exclude, excludeGate, clock, quant, metaKey, useGlobalSpecs, post, trace, num)

+

+

Instantiate a VarGui object for control of one or more Pbind(s) / EventStreamPlayer(s), 

+

derived from a SynthDef name (Symbol / String). One or more Pbind(s) with Pfunc pairs and

+

the corresponding environmental variable controls are generated, 

+

controls for duration and legato are added. 

+

Per default controlspecs are taken from metadata definition, if defined with the SynthDef, or

+

the global dictionary Spec.specs. Controls can be excluded with exclude, added with 

+

ctrBefore or ctrAfter and replaced with ctrReplace; Pbind pairs can be excluded with exclude, 

+

added with pBefore or pAfter and replaced with pReplace, this is the order of operations for 

+

Synths and Pbind pairs. Note that exclude applies to Synth controls and Pbind pairs. 

+

If an added Pbind pair with key \degree, \note or \midinote is detected, \freq is excluded 

+

automatically from Pbind and controls. Gate control is excluded per default.

+

+

ctrBefore - a spec pair collection of the form [ key, spec, ...  ] where

+

key is the symbol of the environmental variable to be set and

+

spec can be a collection of the form [ minval, maxval, warp, step, default ] 

+

defining the corresponding ControlSpec, a Symbol / String to look for a ControlSpec in the

+

global dictionary Spec.specs or a collection of specs of this form

+

for an arrayed control if the environmental variable should have the value 

+

of a collection itself. May also be a SimpleNumber for a dummy slider with fixed value.

+

Defaults to nil. Also takes a Function of indices that returns a valid arg. 

+

ctrReplace - a spec pair collection of a form like ctrBefore. 

+

Also takes a Function of indices that returns a valid arg. Defaults to nil.

+

ctrAfter - a spec pair collection of a form like ctrBefore. 

+

Also takes a Function of indices that returns a valid arg. Defaults to nil.

+

durCtr - a controlspec of the form [ minval, maxval, warp, step, default ]

+

or a Symbol to look for a ControlSpec in the global dictionary Spec.specs. 

+

Also takes a Function of indices that returns a valid arg. 

+

Defaults to #[0.05, 3, \exp, 0, 0.2].

+

legatoCtr - a controlspec of the form [ minval, maxval, warp, step, default ]

+

or a Symbol to look for a ControlSpec in the global dictionary Spec.specs. 

+

Also takes a Function of indices that returns a valid arg. 

+

Defaults to #[0.1, 5, \exp, 0, 0.8].

+

pBefore - a Pbind pair collection of the form [ key, pattern, ...  ]. 

+

Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. 

+

pReplace - a Pbind pair collection of the form [ key, pattern, ...  ]. 

+

Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. 

+

pAfter - a Pbind pair collection of the form [ key, pattern, ...  ]. 

+

Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. 

+

exclude - a control key (Symbol) or a collection of control keys to be excluded from 

+

Pbind pairs and controls. Also takes a Function of indices that returns a valid arg. 

+

Defaults to nil.

+

excludeGate - Boolean. Determines if gate control should be excluded. 

+

Also takes a Function of indices that returns a Boolean. Defaults to true.

+

clock -  TempoClock, nil or collection thereof for playing streams.

+

Also takes a Function of indices that returns a valid arg. 

+

Defaults to the default TempoClock.

+

quant -  Quant, Float, nil or collection thereof used as quant data for playing streams.

+

Also takes a Function of indices that returns a valid arg. 

+

Defaults to Quant.default (none).

+

metaKey - Symbol. Key to lookup SynthDef metadata. 

+

Also takes a Function of indices that returns a Symbol. 

+

Defaults to \specs.

+

useGlobalSpecs - Boolean. Defines if to consider global ControlSpecs if 

+

metadata is not given. Also takes a Function of indices that returns a Boolean. 

+

Defaults to true.

+

post - Boolean. Determines if to post order of Pbind pairs. 

+

Also takes a Function of indices that returns a Boolean. Defaults to false.

+

trace - Boolean. Determines if EventStreamPlayer should post Events when playing. 

+

Also takes a Function of indices that returns a Boolean. Defaults to false.

+

num - Integer. Number of Pbinds / EventStreamPlayers to be controlled. Defaults to 1.

+


+


+

aSequenceableCollection.pVarGui(argDictionary1, ... , argDictionaryN)

+


+

Instantiate a VarGui object for control of Pbinds / EventStreamPlayers derived from a 

+

collection of SynthDef names (Symbols / Strings). Options for controls are taken from the corresponding 

+

arg Dictionaries. Size of collection must equal N, the number of Dictionaries. 

+

See aSynthDefName.pVarGui for valid arg syntax.

+


+


+

The following two methods are equivalent, just take opposite arg order. 

+

This is independant of arrangement, which can be determined by gui args

+

sliderPriority (\var or \synth) and playerPriority (\stream or \synth).

+


+


+

aSequenceableCollection.spVarGui(sVarGuiArgDictionaries, pVarGuiArgDictionaries, server)

+


+

Instantiate a VarGui object for control of Synths and Pbinds / EventStreamPlayers 

+

derived from SynthDef names (Symbols / Strings). Thus receiver must be of the form 

+

[ synthNames, pbindSynthNames ], whereby synthNames and pbindSynthNames

+

may be Symbols / Strings or SequenceableCollections of Symbols / Strings.

+


+

sVarGuiArgDictionaries - arg Dictionary or SequenceableCollection thereof, whose size 

+

must be equal to that of synthNames (if a collection). 

+

See aSynthDefName.sVarGui for valid arg syntax. 

+

Defaults to [].

+

pVarGuiArgDictionaries - arg Dictionary or SequenceableCollection thereof, whose size 

+

must be equal to that of pbindSynthNames (if a collection). 

+

See aSynthDefName.pVarGui for valid arg syntax.

+

Defaults to [].

+

server - Server. The server to send node messages for synth control. 

+

Defaults to the default server.

+


+


+

aSequenceableCollection.psVarGui(pVarGuiArgDictionaries, sVarGuiArgDictionaries, server)

+


+

Instantiate a VarGui object for control of Pbinds / EventStreamPlayers and Synths 

+

derived from SynthDef names (Symbols / Strings). Thus receiver must be of the form 

+

[ pbindSynthNames, synthNames ], whereby pbindSynthNames and synthNames

+

may be Symbols / Strings or SequenceableCollections of Symbols / Strings.

+


+

pVarGuiArgDictionaries - arg Dictionary or SequenceableCollection thereof, whose size 

+

must be equal to that of pbindSynthNames (if a collection). 

+

See aSynthDefName.pVarGui for valid arg syntax.

+

Defaults to [].

+

sVarGuiArgDictionaries - arg Dictionary or SequenceableCollection thereof, whose size 

+

must be equal to that of synthNames (if a collection). 

+

See aSynthDefName.sVarGui for valid arg syntax. 

+

Defaults to [].

+

server - Server. The server to send node messages for synth control. 

+

Defaults to the default server.

+


+


+

5b:   spec- and pbindmaker methods

+


+

In most cases you won't need this, except for reloading customized sequencing VarGuis (6). For special arrangements methods for making specdata and Pbinds of appropriate Pfuncs can be used for customizing control with direct VarGui instantiation. These methods take args in the same way as sVarGui and pVarGui.

+


+


+

// sVarGuiSpecs returns a list of spec pair lists of the form [\key, specArray, ...]

+

// derived from SynthDef metadata / global ControlSpecs,

+

// controls can be excluded, added and replaced

+


+

(

+

VarGui(

+

synth: [\default, \buzz], 

+

synthCtr: \default.sVarGuiSpecs(exclude: \gate) ++

+

\buzz.sVarGuiSpecs(ctrReplace: [\amp, [0, 0.5, 0, 0, 0.1]])

+

).gui;

+

)

+


+

// shorter

+


+

(

+

[\default, \buzz].sVarGui(

+

(exclude: \gate), 

+

(ctrReplace: [\amp, [0, 0.5, 0, 0, 0.1]])

+

).gui;

+

)

+


+


+

// as with sVarGui arguments can be functions of indices 

+


+

(

+

VarGui(

+

synth: \default!4 ++ \buzz, 

+

synthCtr: 

+

\default.sVarGuiSpecs(num: 4, exclude: \gate, 

+

ctrReplace: { |i| var x = i*100 + 400; [\freq, [x, x*1.1, 0, 0, x]] }) ++

+

\buzz.sVarGuiSpecs(ctrReplace: [\amp, [0, 0.5, 0, 0, 0.2]])

+

).gui(tryColumnNum: 2, allowSynthBreak: false);

+

)

+


+

// shorter

+


+

(

+

[\default, \buzz].sVarGui(

+

(num: 4, exclude: \gate, ctrReplace: { |i| var x = i*100 + 400; [\freq, [x, x*1.1, 0, 0, x]] }), 

+

(ctrReplace: [\amp, [0, 0.5, 0, 0, 0.2]])

+

).gui(tryColumnNum: 2, allowSynthBreak: false);

+

)

+


+


+


+

// pVarGuiSpecs also returns a list of spec pair lists, 

+

// just adds specs for dur and legato by default,

+

// pfuncPbinds returns appropriate Pbinds of Pfuncs

+

// additional pairs can be added

+


+

// however, compared to pVarGui, more coordination is the user's responsibility:

+

// specmaker and pbindmaker don't know about each other

+

// useless control \freq excluded explicitely here

+


+

(

+

VarGui(

+

varCtr: 

+

\buzz.pVarGuiSpecs(ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ],

+

ctrReplace: [\dur, [0.2, 0.4, \lin, 0.2, 0.2]], exclude: \freq) ++

+

\default.pVarGuiSpecs(ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 4 ],

+

ctrReplace: [\dur, [0.1, 0.3, \lin, 0.1, 0.2]], exclude: \freq),

+

stream: 

+

\buzz.pfuncPbinds(pBefore: [\degree, PLseq(\a), \octave, 3]) ++ 

+

\default.pfuncPbinds(pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + 

+

PLseq(\a), \octave, Pwhite(5,7)],  post: true),

+

quant: 0.2

+

).gui(tryColumnNum: 2, allowEnvirBreak: false);

+

)

+


+

// shorter

+


+

(

+

[\buzz, \default].pVarGui((

+

ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ],

+

ctrReplace: [\dur, [0.2, 0.4, \lin, 0.2, 0.2]],

+

pBefore: [\degree, PLseq(\a), \octave, 3], 

+

quant: 0.2

+

),(

+

ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 4 ],

+

ctrReplace: [\dur, [0.1, 0.3, \lin, 0.1, 0.2]],

+

pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + 

+

PLseq(\a), \octave, Pwhite(5,7) ], 

+

quant: 0.2

+

)

+

).gui(tryColumnNum: 2, allowEnvirBreak: false);

+

)

+


+


+

aSynthDefName.sVarGuiSpecs(ctrBefore, ctrReplace, ctrAfter, exclude, metaKey, useGlobalSpecs, num)

+

+

Generate control data for one or more Synth(s) derived from a SynthDef name (Symbol / String).

+

Returns a grouped collection, also for num = 1. 

+

Data can directly been used as input to VarGui's synthCtr arg. 

+

This method can be used for control of Synths derived from more than one SynthDef in one VarGui

+

or combined control of Synths and Pbinds / EventStreamPlayers.

+

+

args - see aSynthDefName.sVarGui

+


+


+

aSynthDefName.pVarGuiSpecs(ctrBefore, ctrReplace, ctrAfter, durCtr, legatoCtr, exclude, excludeGate, metaKey, useGlobalSpecs, num)

+

+

Generate envir variable control data for one or more Environments derived from 

+

a SynthDef name (Symbol / String). Returns a grouped collection, also for num = 1. 

+

Data can directly been used as input to VarGui's varCtr arg. 

+

This method can be used for control of Pbinds / EventStreamPlayers derived from 

+

more than one SynthDef in one VarGui or combined control of Synths and 

+

Pbinds / EventStreamPlayers.

+

+

args - see aSynthDefName.pVarGui with the exception of Pbind-related args

+

pBefore, pReplace, pAfter, clock, quant, post and trace

+

+


+

aSynthDefName.pfuncPbinds(pBefore, pReplace, pAfter, exclude, excludeGate, excludeDur, excludeLegato, metaKey, useGlobalSpecs, post, trace, num)

+

+

Generate Pbinds with Pfunc pairs derived from a SynthDef name (Symbol / String) and corresponding metadata 

+

resp. global ControlSpecs. Duration and legato pairs are added by default.

+

If an added Pbind pair with key \degree, \note or \midinote is detected, 

+

\freq is excluded automatically. Returns a collection, also for num = 1.

+

+

args - see aSynthDefName.pVarGui with the exception of clock, quant and control-related args

+

ctrBefore, ctrReplace, ctrAfter, durCtr, legatoCtr. 

+

excludeDur - Boolean. Determines if \dur should be excluded from the Pbind. 

+

Also takes a Function of indices that returns a Boolean. Defaults to false.

+

excludeLegato - Boolean. Determines if \legato should be excluded from the Pbind. 

+

Also takes a Function of indices that returns a Boolean. Defaults to false.

+


+


+


+

6.) Save and load

+


+

A VarGui's slider state can be saved via save button and dialog. Loading is straightforward with Synths. You just have to give the correct path and synth arg (which maybe has to be an array).

+


+


+

// change slider positions and save

+


+

\buzz.sVarGui.gui;

+


+

// reload

+


+

VarGui.load("Path_To_XY", "XY", synth: \buzz).gui;

+


+


+

// change slider positions and save

+


+

\buzz.sVarGui(num: 4, metaKey: \basicSpecs).gui(tryColumnNum: 2);

+


+

// reload, synth arg must have corresponding size

+


+

VarGui.load("Path_To_XY", "XY", synth: \buzz ! 4).gui(tryColumnNum: 2);

+


+


+

VarGui only saves slider states, not the Pbind structure (would open a can of worms). So if you had a customized Pbind in the original setup you'd have to pass an appropriate Pbind together with load. This can also be a Pbind other than in the original setup. But if you want to restore you can nearly copy the input of the original cutomization into the pbindmaker method pfuncPbinds (5b).

+


+

// with no customization save and load also works straightforward

+

// change slider positions and save

+


+

\buzz.pVarGui.gui;

+


+

// standard pbind is (re-)autogenerated and fits the variables to be set

+


+

VarGui.load("Path_To_XY", "XY", stream: \buzz).gui;

+


+


+

// only controls are customized, save and load also works straightforward

+

// change slider positions, save and reload

+


+

(

+

[\default, \buzz].pVarGui(

+

(ctrReplace: { |i| var f = (2*i + 1) * 100, d = 0.2, lo = 0.99, hi = 1.01; 

+

[\freq, [f*lo, f*hi, 0, 0, f*rrand(lo,hi)], \dur, [d*lo, d*hi, 0, 0, d*rrand(lo,hi)] ] }, 

+

exclude: \gate, 

+

post: true,

+

num: 4),

+

(ctrReplace: [\freq, [97, 103, \exp, 0, 99], \dur, [0.195, 0.205, 0, 0, 0.2] ])

+

).gui(tryColumnNum: 2, allowEnvirBreak: false)

+

)

+


+

(

+

VarGui.load("Path_To_XY", "XY", stream: \default ! 4 ++ \buzz)

+

.gui(tryColumnNum: 2, allowEnvirBreak: false)

+

)

+


+


+

// change slider positions and save

+


+

(

+

[\buzz, \default].pVarGui((

+

ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ],

+

ctrReplace: [\dur, [0.2, 0.4, \lin, 0.2, 0.2]],

+

pBefore: [\degree, PLseq(\a), \octave, 3], 

+

quant: 0.2

+

),(

+

ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 4 ],

+

ctrReplace: [\dur, [0.1, 0.3, \lin, 0.1, 0.2]],

+

pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + PLseq(\a), 

+

\octave, Pwhite(5,7) ], 

+

quant: 0.2

+

)

+

).gui(tryColumnNum: 2, allowEnvirBreak: false);

+

)

+


+

// restoring pbind structure, note that pfuncPbinds always returns a collection

+

// and quant arg is separate in VarGui.new and VarGui.load

+


+

(

+

VarGui.load("Path_To_XY", "XY", 

+

stream: 

+

\buzz.pfuncPbinds(pBefore: [\degree, PLseq(\a), \octave, 3]) ++

+

\default.pfuncPbinds(pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + PLseq(\a), 

+

\octave, Pwhite(5,7) ]),

+

quant: 0.2

+

).gui(tryColumnNum: 2, allowEnvirBreak: false)

+

)

+


+


+


+


+ + diff --git a/Help/VarGui.html b/Help/VarGui.html new file mode 100755 index 0000000..5acfe5c --- /dev/null +++ b/Help/VarGui.html @@ -0,0 +1,1561 @@ + + + + + + + + + + + +

VarGui slider / player gui to set envir variables and synth controllers and play synths, event patterns and tasks

+


+

Part of: miSCellaneous

+


+

Inherits from: Object

+


+

SC setups may contain discrete and continuous control, e.g. using combinations of Pbinds, Tasks and Synths. There are many possible ways of interaction between such elements, sometimes there is the alternative to control things by server or language (e.g. Pbinds versus Demand Ugens) but often one needs to have both. VarGui is a multi purpose slider and player GUI, originally intended for indentifying parameters to be reused in other setups.

+

Pbinds and Tasks can easily refer to environmental variables - in case of Pbinds e.g. via Pfunc, Plazy, Pcollect, but more conveniently via the dynamic scope PLx suite - so VarGui can control environmental variables and synth controllers. For basic principles of using event patterns with functional code and the related scoping features see Event patterns and Functions. VarGui was not intended for performance, however (since miSCellaneous v0.4) it includes a player section. Players can be used in different (reset) modes, states of synths and streams are reflected by colors, it also works with wslib slider classes (Example 8). For quick GUI generation using SynthDef metadata for controlspecs and / or automatic Pbind builds see VarGui shortcut builds. As both VarGui help files are quite full of details, a more general overview, including some exercises, is given in Introduction to miSCellaneous.

+


+


+

See also: VarGui shortcut builds, Introduction to miSCellaneous, PLx suite, Event patterns and LFOs, Event patterns and Functions, Buffer Granulation, Live Granulation, HS with VarGui

+


+


+

Some Important Issues Regarding VarGui

+


+

VarGui allows to choose SynthDefs or Synths, Events patterns or EventStreamPlayers and Task functions or Tasks as input. It is recommended to take the more general objects (SynthDefs, Event patterns, Task functions) as then derived Synths, EventStreamPlayers and Tasks are newly generated and the latter are run in newly generated Environments. If you follow this it is very unlikely to accidentally break gui representation of data (though it's not strict MVC paradigm).

+


+

On the other hand there are special cases when you might want to pass Synth objects / synth nodes, Tasks or EventStreamPlayers directly (e.g. HS with VarGui). Then a bit more care has to be taken as, although a VarGui instance doesn't allow to poll more than one GUI window from it, nothing prevents you from refering to identical Synth objects / synth nodes, Tasks, EventsStreamPlayers or environmental variables with different VarGui instances. This is not recommended as, obviously, a possible source of confusion.

+


+

If you are passing a Synth directly and moreover its SynthDef is not known to SynthDescLib setting a single component of an arrayed control also causes setting of all components below (no other choice at the moment, however, a very special case). 

+


+

GUI specific:

+


+

With miSCellaneous v0.16 (March 2017) backwards compatibility with Cocoa and SwingOSC is no longer supported, now cross-platform Qt is standard. VarGui was originally written with Cocoa as reference, though restrcitions on Qt are minor. 

+


+

1) With Qt mouseDown is the hardcoded mode of player action, which anyway could be regarded as standard usage.

+


+

2) Moving several sliders in parallel (see Example 1c) works on all platforms and with wslib sliders. 

+

+

3) Combined slider movement with modifier keys differs in the necessary order of key pressing and mouse clicking.

+

In Qt you can (and with the Cmd modifier involved you have to) press thereafter.

+


+

4) With EZSlider you can jump to a certain slider position by clicking at an arbitrary position in the slider field.

+

With wslib sliders you can choose modes \jump and \move (Example 8). 

+


+

5) Combined synth / stream player button actions with Caps-lock are currently not working with Qt.

+

+

+


+

You can list styles by

+


+

GUI.availableStyles

+


+

and set with

+


+

GUI.style = \myFavoriteStyle

+


+


+

Creation / Class Methods

+


+

*new (varCtr, synthCtr, stream, synth, clock, quant, varEnvirGroups, envir, synthCtrGroups, assumePlaying, assumeRunning, assumeEnded, server)

+

+

Create a new VarGui object, which holds control specifications.

+

+

varCtr - specPairs or a collection of specPairs, where

+

specPairs is a collection of the form [ key, spec, ...  ] where

+

key is the symbol of the environmental variable to be set and

+

spec must be one of those:

+

(a) Collection of the form [ minval, maxval, warp, step, default ], 

+

defining the corresponding ControlSpec, or a collection of specs of this form

+

for an arrayed control if the environmental variable should have the value 

+

of a collection itself. step is used for the derivation of slider step size.

+

(b) Symbol refering to a ControlSpec globally stored in Spec.specs

+

or a collection thereof for arrayed control.

+

ControlSpec.step is used for the derivation of slider step size.

+

(c) SimpleNumber for a dummy slider with fixed value.

+

If specPairs or single components of a collection of specPairs are nil and the corresponding

+

stream arg is a Symbol or String pointing to a SynthDef, it will be tried to derive 

+

controls from corresponding SynthDef metadata resp. global ControlSpecs 

+

(see VarGui shortcut builds).

+

+

Variables are set in environments that are either given implicitely (1) or explicitely (2):

+

+

(1) by eventstream players / tasks passed to stream - they are already bound to an

+

environment - or newly generated together with eventstream players or tasks from 

+

patterns or functions passed to stream. 

+

+

(2) by envir. This allows setting of variables in Environments in case of absent stream players.

+

+

From explicit or implicit envir input an ordered collection of non-identical environments is derived,

+

which, without one of the above infos, will consist of currentEnvironment only.

+

+

If varCtr is already grouped as a collection of specPairs then variables from i-th specPairs will be set

+

in the i-th environment of the derived collection. 

+

If varCtr is given as a flat collection and specPairs contains no multiple keys then variables are set

+

in the first resp. only environment in question. 

+

If varCtr is given as a flat collection and specPairs contains multiple keys then varEnvirGroups

+

is needed to map variables to environments (Example 5a).

+

Note that the i-th environment means the i-th non-identical environment, not necessarily

+

(in the case of environments implicitely given by eventstream players / tasks) the environment

+

of the i-th stream.

+

Defaults to nil.

+

synthCtr - specPairs or a collection of specPairs, where

+

specPairs may be nil or a collection of the form [ key, spec, ...  ] where

+

key is the symbol of the control arg to be set and

+

spec must be one of those:

+

(a) Collection of the form [ minval, maxval, warp, step, default ], 

+

defining the corresponding ControlSpec, or a collection of specs of this form

+

for an arrayed control. step is used for the derivation of slider step size.

+

(b) Symbol refering to a ControlSpec globally stored in Spec.specs

+

or a collection thereof for arrayed control.

+

ControlSpec.step is used for the derivation of slider step size.

+

(c) SimpleNumber for a dummy slider with fixed value.

+

If specPairs or single components of a collection of specPairs are nil, it will be tried to

+

derive controls from SynthDef metadata resp. global ControlSpecs (see VarGui shortcut builds).

+

+

If synthCtr is already grouped as a collection of specPairs then controls from i-th specPairs will be used

+

for setting the i-th synth. 

+

If synthCtr is given as a flat collection and specPairs contains no multiple keys then controls will be used

+

for setting the only synth in question.

+

If synthCtr is given as a flat collection and specPairs contains multiple keys then synthCtrGroups

+

is needed to map synth controls to synths (Example 5b).

+

Defaults to nil.

+

stream - Expects Pattern, EventStreamPlayer, Function, Task or a collection thereof,

+

determining the number of stream players.

+

Patterns / Functions are used as templates for EventStreamPlayers / Tasks in 

+

environments generated at init time. If stream is unequal nil variables given by 

+

varCtr will be set in the environments derived from it.

+

A Symbol or String pointing to a SynthDef causes automatic generation of a Pbind. 

+

If varCtr resp. the corresponding varCtr component is nil it will be tried to derive 

+

Pbind controls from corresponding SynthDef metadata resp. global ControlSpecs 

+

(see VarGui shortcut builds).

+

Size of stream must at least equal and may exceed the number of varCtr groups,

+

which (see above) may be given by varCtr itself (if a grouped collection of specPairs)

+

or varCtrGroups. 

+

Correspondence between environments of variables and eventstream / task players 

+

is optionally indicated in the GUI - however the user is responsible for defining 

+

patterns and task functions that really involve the variables to be set !

+

Defaults to nil.

+

synth - Input to which synth controls are mapped. Expects Symbol refering to a SynthDef, 

+

Synth, nodeID or collection thereof. 

+

Size of synth must at least equal and may exceed the number of synthCtr groups,

+

which (see above) may be given by synthCtr itself (if a grouped collection of specPairs)

+

or synthCtrGroups. 

+

It is recommended to refer to "known" SynthDefs (SynthDescLib), best by 

+

passing Symbols or also registered Synths, otherwise the correct synth state must be given

+

explicitely by assumePlaying, assumeRunning resp. assumeEnded, which is a nearby

+

source of error and confusion.

+

Defaults to nil.

+

clock -  TempoClock, nil or collection thereof for playing streams.

+

Passed clocks have precedence over implicitely given clocks (running eventstream players 

+

or tasks as stream input) in case of resuming from pause state.

+

Defaults to the default TempoClock.

+

quant -  Quant, Float, nil or collection thereof used as quant data for playing streams.

+

Will be used in case of resuming from pause state for running 

+

eventstream players or tasks given as stream input.

+

Defaults to Quant.default (none).

+

varEnvirGroups - SequenceableCollection of Collections of Integer.

+

Expects ordered partition of the number N of specPairs given to varCtr as a flat collection, 

+

also counting multiple occurences, thus defining a mapping: variable keys -> environments.

+

Must be a valid partition of N (taking all integers i < N, 0 included, each only once). 

+

Its size must not exceed the number of implicitely or explicitely given environments (see varCtr).

+

E.g. for 3 environments and 6 variables [[2], [0,1,5], [4,3]] would be valid.

+

Defaults to nil.

+

envir - SequenceableCollection of Environments.

+

Environments to be used for variable setting in case of absent stream players, only admissible if stream is nil.

+

Defaults to nil.

+

synthCtrGroups - SequenceableCollection of Collections of Integer.

+

Expects ordered partition of the number N of specPairs given to synthCtr as a flat collection,

+

also counting multiple occurences, thus defining a mapping: synth control keys -> environments.

+

Must be a valid partition of N (taking all integers i < N, 0 included, each only once). 

+

Its size must not exceed the size of synth.

+

Defaults to nil.

+

assumePlaying - Boolean, nil or collection thereof, then size must equal the number of synths given

+

by synth or synthCtr. Player state information for (unregistered) synths or nodeIDs, 

+

booleans are not accepted if corresponding items in synth are instances of Symbol or String.

+

Defaults to nil.

+

assumeRunning - Boolean, nil or collection thereof, then size must equal the number of synths given

+

by synth or synthCtr. Player state information for (unregistered) synths or nodeIDs, 

+

booleans are not accepted if corresponding items in synth are instances of Symbol or String.

+

Defaults to nil.

+

assumeEnded - Boolean, nil or collection thereof, then size must equal the number of synths given

+

by synth or synthCtr. Player state information for (unregistered) synths or nodeIDs, 

+

booleans are not accepted if corresponding items in synth are instances of Symbol or String.

+

Defaults to nil.

+

server - Server. The server to send node messages for synth control. Doesn't affect Pbinds

+

as server can be determined by their definition. Defaults to the default server.

+

+

+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+

+

+

// Pbind defined with variable to be controlled while playing

+

// Pfunc's function is evaluated at playtime of each Event and gets

+

// the value of the variable ~midi in the environment in which the EventStreamPlayer will be playing

+

+

p = Pbind(\midinote, Pfunc { ~midi } + Pseq([0, 3, 5, 7], inf), \dur, 0.2) 

+

+

+

// VarGui to set this variable.

+

// As a pattern is given as stream arg the derived EventStreamPlayer will run in a new environment

+

// which is generated at VarGui init time

+

+

v = VarGui([\midi, [50, 70, \lin, 1, 55]], stream: p).gui;

+

+

+

// Try out basic slider and player functionality

+

+

// Playing: 

+

+

attachments/VarGui/player_1.png

+

+

// Background of player button lightening yellow. Functionality of the reset button depends

+

// on the active mode. The green background of the reset mode button indicates that the reset button

+

// will reset while playing and its background will flash yellow.

+

+

// The text field on the right side indicates whether an eventstream player or a task is playing,

+

// that on the left side shows the index of the environment in which the stream is playing.

+

// This is mainly useful if more stream players and variables of same names in different environments

+

// are involved - below the players there is a toggle button to indicate the envir index in slider name fields.

+

+

// Pausing: 

+

+

attachments/VarGui/player_2.png

+

+

// Now pushing the play button will resume the stream where it has been paused,

+

// pushing the reset button (if the reset mode button has green background) 

+

// will reset and play the stream.

+

+

// Pausing and reset: 

+


+

attachments/VarGui/player_3.png

+

+

// This state can be reached from playing, pausing or ended stream when pushing the reset button under 

+

// reset mode pause (orange background). Now only the play button can play the (reset) stream

+

+

// Stream has ended: 

+


+

attachments/VarGui/player_4.png

+

+

// E.g. if an eventstream player has encountered nil, reset button's background will become red.

+

// Now it needs the reset button to go on and its action - pause or play - depends on the active mode. 

+

+

+

//////////////////////////////////////////////////// 

+


+


+

// synth definition with raising frequency

+

+

(

+

SynthDef(\synth_0, { |devFactor = 0.1, devFreq = 10, amp = 0.1|

+

var freq = XLine.kr(400, 1200, 10); 

+

Out.ar(0, SinOsc.ar(SinOsc.ar(devFreq, 0, devFactor * freq, freq), mul: amp).dup(2) * EnvGate.new) 

+

}).add;

+

)

+

+

// VarGui to play synth of above definition and set synth args, 

+

// Player functionality with different stop / renew modes.

+

// Notification must be on.

+

// Note that pause, stop and resume actions cause sending of run / free messages, thus possibly audible clicks.

+

// To avoid that work with amp = 0 or use a gated envelope and a gate control slider with only values 0 and 1 or

+

// use Synths of limited duration as in Example (2)

+

+

(

+

VarGui(

+

synthCtr: [\devFactor, [0, 0.99, \lin, 0.01, 0.5], 

+

\devFreq, [1, 100, \lin, 0.1, 70],

+

\amp, [0, 0.3, \lin, 0.001, 0.1]], 

+

synth: \synth_0).gui;

+

)

+

+

// Latency options (buttons and boxes below the player section):

+

// bundle latency of synth player actions can be set to nil, a custom value (button abbrieviation c) or 

+

// global server latency (abbr. s), these two values can be set in the boxes below 

+

// settings can be used to sync synth and stream players, if both contained in a GUI

+

+


+

// Synth players work similar as stream players, but there are more options.

+

// There is an addtional stop mode button that determines how and if 

+

// renewing (creating new synth nodes of same definition) is handled.

+

+

// If, as above, a synth is given by a reference to a known SynthDef

+

// VarGui will start with a basicNew Synth object, indicated by 

+

// white background of the synth's number field:

+

+

attachments/VarGui/player_5.png

+

+

// Rate type (audio) is indicated in the field on the right.

+

// From this state a node on the server will be created by pause (newPaused) or play (new):

+

+

attachments/VarGui/player_6.png

+

+

// Now playing and pausing work as usual.

+

// The red and blue background of the stop mode button indicates that the red stop button

+

// will become a blue renew button as soon as the Synth is stopped (node freed).

+

+

// In addition to the renew modes play (green) and pause (orange) there is also a

+

// renew mode basic new (white):

+

+

attachments/VarGui/player_7.png

+

+

+

// If stop mode is set to renew (blue) the renew button will never change its function.

+

// Under renew mode play (green) every renew will free a Synth and immediately start a new 

+

// (background flashing yellow):

+

+

attachments/VarGui/player_8.png

+

+

+

// If stop mode is set to stop (red background), the stop / renew button will change to red:

+

+

attachments/VarGui/player_9.png

+

+

// The renew mode button loses its function (greyed out) and stopping just frees the Synth:

+

+

attachments/VarGui/player_10.png

+

+

// Now stop mode would have to be changed in order to play a new Synth.

+

+

+


+

// General functionality

+

+

+

attachments/VarGui/main_buttons.png

+

+

+

// The Update Button

+

+

// Unless the VarGui window was generated with option updateNow set to false

+

// (which could be desired e.g. for a start with Synth defaults), 

+

// variables and synth controls are set to slider values at gui init time,

+

// normally updating is not required. 

+

// As for most uses VarGui would generate new separate environments for

+

// passed event patterns and task functions, there is no update mechanism implemented

+

// if these variables would be changed otherwise.

+

// Analagously controls of Synths played (and probably just yet generated) by a VarGui 

+

// player could of course be set by means other than a VarGui slider action.

+

+

// Anyway both situations seem to be exceptional (you could hardly do so just by an oversight), 

+

// though you can update to all slider values manually.

+

+

+

// The Save Button

+

+

// Opens a dialog to save the current slider state as an array of 4 items:

+

// varCtr as flattened array of specPairs, synthCtr as flattened array of specPairs, 

+

// varEnvirGroups, synthCtrGroups (which are only stored as groupings in case of multiple key occurences, nil optherwise)

+

+

// Player data and synth information is not stored.

+

// For loading from a file you would have to pass this (see the load method below)

+

+

+

// The Stop Button

+

+

// Free all synths and stop all streams.

+

// You can also use modifier keys for stopping groups of players,

+

// e.g. Shift-click on a stream players's pause button to stop all streams.

+

// Freeing all synths in the same way only works if all synth players can be stopped by their stop buttons at this time.

+

+

+


+

*load (pathname, filename, stream, synth, clock, quant, varEnvirGroups, envir, synthCtrGroups, assumePlaying, assumeRunning, assumeEnded, server)

+

+

Create a VarGui, that loads a stored slider state (varCtr and synthCtr data) from the specified file,

+

which has probably been saved before via dialog. See also  VarGui shortcut builds, Ch. 6.

+

+

pathname - Pathname string.

+

filename - Filename string.

+

other args - see *new

+

+

+

*load_old (pathname, filename, stream, synth, clock, quant, varEnvirGroups, envir, synthCtrGroups, assumePlaying, assumeRunning, assumeEnded, server)

+

+

Create a VarGui, that loads a stored slider state from the specified file.

+

This is for loading slider states that have been saved with miSCellaneous v0.3 or earlier, thus ignoring the synth control slider label.

+

Though old code examples will not necessarily work after just replacing load by load_old as other conventions have changed too.

+

Maybe you'd also have to change synth and stream (former players) or add assumePlaying and assumeRunning args.

+

+

pathname - Pathname string.

+

filename - Filename string.

+

other args - see *new

+


+

+

Creating a gui from the VarGui object

+

+


+

gui (sliderHeight, sliderWidth, labelWidth, numberWidth, sliderType, sliderMode, playerHeight, 

+

precisionSpan, stepEqualZeroDiv, tryColumnNum, allowSynthsBreak, allowVarsBreak, 

+

allowSynthBreak, allowArrayBreak, allowEnvirBreak, minPartitionSize, 

+

sliderPriority, playerPriority, streamPlayerGroups, synthPlayerGroups, comboPlayerGroups, 

+

font, colorsLo, colorsHi, colorDeviation, ctrButtonGreyCenter, ctrButtonGreyDev, greyMode, name, updateNow,

+

sliderFontHeight, playerFontHeight, maxWindowWidth, maxWindowHeight)

+

+

Create a gui window.

+

+

sliderHeight - Gui slider height. Defaults to 18. 

+

sliderWidth - Gui slider width. Depends on number of columns if not given explicitely. 

+

labelWidth - Slider label width. Default depends on platform and gui scheme (70 or 75).  

+

numberWidth - Slider numberbox width. Default depends on platform and gui scheme (40 or 45).  

+

sliderType - One of the Symbols \standard, \smooth, \round. Both latter refer to EZSmoothSlider

+

and  EZRoundSlider from wslib (Quark), which needs to be installed for that choice. 

+

See Example 8. Defaults to \standard. 

+

sliderMode - One of the Symbols \jump, \move. 

+

Only relevant in case of sliderType \smooth or \round. 

+

Determines behaviour if slider field is clicked apart from the knob.  

+

See Example 8. Defaults to \jump. 

+

playerHeight - Gui player height. Defaults to 18. 

+

precisionSpan - Integer. Precision span to be regarded for representation of controlspec step.

+

See Example 9. Defaults to 10. 

+

stepEqualZeroDiv - Integer. Division to be assumed if controlspec step equals 0.

+

See Example 9. Defaults to 100. 

+

tryColumnNum - Integer. VarGui tries to arrange variable and control sliders in this number of columns

+

if possible under the restrictions of maxWindowWidth, sliderWidth, minPartitionSize and the following allow options.

+

Internally a list of possible break indices is derived and then a partition as equal in size as possible is searched for.

+

See Examples 5a, 5b.

+

Defaults to 1.

+

allowSynthsBreak - Boolean. Determines if all synth control sliders should be grouped within one column.

+

Defaults to true. 

+

allowVarsBreak - Boolean. Determines if all variable control sliders should be grouped within one column.

+

Defaults to true. 

+

allowSynthBreak - Boolean. Determines if control sliders of one synth should be grouped within one column.

+

Setting false only has an effect if synth controls are ordered in connected groups.

+

Defaults to true. 

+

allowArrayBreak - Boolean. Determines if control sliders of an arrayed control (variable or synth) should be 

+

grouped within one column. Defaults to true. 

+

allowEnvirBreak - Boolean. Determines if control sliders of one environment should be grouped within one column.

+

Setting false only has an effect if environmental variables are ordered in connected groups.

+

Defaults to true. 

+

minPartitionSize - Integer. Determines the minimum number of sliders of same type that may be grouped 

+

in a different column. Defaults to 0. 

+

sliderPriority - Symbol \var or \synth. Determines if variable or synth control sliders should be listed first.

+

Defaults to \var. 

+

playerPriority - Symbol \stream or \synth. Determines if stream or synth players should be listed first.

+

Defaults to \stream. 

+

streamPlayerGroups - Grouping of number of stream players (E.g. [[0], [1,2]] is a valid grouping of 3 players). 

+

Defines control group for modifier option of stream playing. 

+

Defaults to the grouping of all streamPlayers.

+

synthPlayerGroups - Grouping of number of synth players. 

+

Defines control group for modifier option of synth playing.

+

Defaults to the grouping of all synthPlayers.

+

comboPlayerGroups - ***currently not working*** 

+

Grouping of number of synth and stream players. 

+

Defines control group for modifier option of combined synth and stream playing.

+

Defaults to the grouping of all synth- and streamPlayers.

+

font - GUI font as String. Defaults to "Helvetica".

+

colorsLo - Low RGB value for color space, in which distinct slider colors for logical groups (synth, environment) 

+

and background are chosen by random. Must be between 0 and 1. Defaults to 0.25 in color mode 

+

and 0.4 in grey mode. 

+

colorsHi - Hi RGB value for color space, in which distinct slider colors for logical groups (synth, environment) 

+

and background are chosen by random. Must be between 0 and 1. Defaults to 0.7 in color mode 

+

and 0.6 in grey mode. 

+

colorDeviation - Nonnegative SimpleNumber. Determines the maximum amount of color deviation within a 

+

logical slider group (synth, environment). Within the maximum deviation distinct colors are ramdomly chosen, 

+

but arrayed controls get the same color. Defaults to 0.12 in color mode and 0 in grey mode.

+

ctrButtonGreyCenter - Float between 0 and 1. Determines the grey center from which button colors deviate 

+

by the amount of ctrButtonGreyDev. Defaults to 0.5. 

+

ctrButtonGreyDev - Float between 0 and 1. Determines the intensity of button colors deviating from

+

ctrButtonGreyCenter. Defaults to 0.8 in color mode and 0.65 in grey mode.

+

greyMode - Boolean. Determines if color or grey mode is chosen. Defaults to false.

+

varColorGroups - Grouping of colors of variable control sliders. Overrules automatic color grouping.

+

All indices < n (number of sliders) must occur exactly once. E.g. [[0], [1,2]] would be valid for n = 3.

+

See Example 7.

+

synthColorGroups - Grouping of colors of synth control sliders. Overrules automatic color grouping.

+

All indices < n (number of sliders) must occur exactly once. E.g. [[0], [1,2]] would be valid for n = 3.

+

See Example 7.

+

name - Symbol or String. Name to be displayed in the GUI window title bar. If not given explicitely types of

+

controls and players are indicated.

+

updateNow  - Boolean. Determines if controls and variables should be set to slider values at GUI build time.

+

To keep sliders in sync with control this is done by default - however there may be cases when it is

+

desirable to start with other values, e.g. the synth's defaults. If set to false slider values will not be set

+

before the slider is moved or before all values are set by the update method or the corresponding button.  

+

sliderFontHeight - Float. Adapts to sliderHeight if value nil is given (default). 

+

Normally this doesn't have to be set except with very small sliderHeight and/or certain fonts. 

+

playerFontHeight - Float. Defaults to 12. 

+

maxWindowWidth - Float. Defaults to 1500.

+

maxWindowHeight - Float. Defaults to 700.

+

+

+

// SynthDef from above, number of synth players multiplied by expanding single control

+

// For multiple slider and player button control see the modifier options explained in Example 1c.

+

+

(

+

v = VarGui(

+

synthCtr: [\devFactor, [0, 0.99, \lin, 0.01, 0.5], 

+

\devFreq, [1, 100, \lin, 0.1, 70],

+

\amp, [0, 0.2, \lin, 0.001, 0.02]] ! 6, 

+

synth: \synth_0 ! 6);

+

v.gui;

+

)

+


+

// GUI appearance customization:

+

// close window (only one window per VarGui instance allowed) and try again with same or other color options 

+

+

v.gui;

+

+

+

//

+

+

v.gui(colorDeviation: 0, colorsLo: 0.4, colorsHi: 0.9);

+


+


+

//

+

+

v.gui(greyMode: true);

+


+


+

// force slider arrangement in 2 columns, controls of each synth should only be in one column

+

+

v.gui(colorDeviation: 0, tryColumnNum: 2, allowSynthBreak: false);

+


+


+

+

// you can save a slider state snapshot by button / dialog to the file "XY",

+

// only slider states (flattened spacPair collections) and groupings are stored, 

+

// you have to pass synth data with load 

+


+

VarGui.load("PathToXY", "XY", synth: \synth_0 ! 6).gui;

+

+


+

update

+

+

Update variables and controllers to current slider values. Like update button action.

+

Unless the VarGui window was generated with option updateNow set to false, 

+

variables and controllers are immediately set to slider values and normally updating is not required. 

+

+


+

updateVarSliders(key, varNum, val, indexOffset, updateNow)

+

+

Update variable sliders.

+

+

key - Variable name given as Symbol.  

+

varNum - Integer. Index of variable occurence in given varCtr. Defaults to 0.

+

val - Float. May also be collection, then a sequence of variable sliders is set. 

+

indexOffset - Integer. Updating will start at this index in case key refers to arrayed control. Defaults to 0.

+

updateNow - Boolean. Determines if variables should immediately be updated with sliders. Defaults to true.

+

+


+

updateSynthSliders(key, synthNum, val, indexOffset, updateNow)

+

+

Update synth sliders.

+

+

key - Synth control name given as Symbol.  

+

synthNum - Integer. Index of synth in given synthCtr. Defaults to 0.

+

val - Float. Maybe also be collection, then a sequence of synth sliders is set. 

+

indexOffset - Integer. Updating will start at this index in case key refers to arrayed control. Defaults to 0.

+

updateNow - Boolean. Determines if synth controls should immediately be updated with sliders. Defaults to true.

+


+

+

font(font, sliderFontHeight, playerFontHeight)

+

+

Set GUI font.

+

+

font - String. Defaults to "Helvetica".

+

sliderFontHeight - Integer. Defaults to 12.

+

playerFontHeight - Integer. Defaults to 12.

+


+

+

addSliderAction(function, type, index, envir)

+

+

Add an action to be evaluated as sliderView's mouseUpAction.

+

+

function - Function.

+

type - Slider type, expects \var or \synth. Defaults to \var.

+

index - Integer. Slider index of given type.

+

envir - Environment where function should be evaluated. If not specified and type equals \var 

+

the environment associated with the variable will be taken.

+

+


+

startMIDIlearn

+

+

Start learining mode for receiving MIDI cc messages for slider control, see Ex.10.

+


+


+


+

stopMIDIlearn

+

+

Stop learining mode for receiving MIDI cc messages for slider control, see Ex.10.

+


+


+


+

Example 1a:   step sequencer

+


+


+

(

+

s = Server.local;

+

Server.default = s;

+

s.boot;

+

)

+


+


+

// There are different ways to read a sequence of values from a 

+

// settable array assigned to an envir variable.

+

// PLseq is convenient, for a comparison with Plazy, Pfunc etc. see PLx suite

+


+

// In all cases the EventStreamPlayer can be played in an 

+

// arbitrary environment, from which ~seq will be taken.

+

// This holds true for Patterns with functional code like Pfunc

+

// and also for PLseq, the latter has an envir arg 

+

// which defaults to \current.

+


+

// Note that all PLx Patterns default to repeats = inf,

+

// ListPatterns as Pseq default to repeats = 1.

+


+


+

(

+

// OffsetOut for exact timing

+


+

SynthDef(\synth_1a, { |freq = 400, preAmp = 5, amp = 0.1|

+

var src = SinOsc.ar(freq, 0, mul: preAmp).tanh ! 2;

+

OffsetOut.ar(0, src * amp * EnvGen.ar(Env.perc, doneAction: 2))

+

}).add;

+


+

p = Pbind(

+

\instrument, \synth_1a,

+

\preAmp, PL(\preAmp),  // or Pfunc { ~preAmp }

+

\amp, PL(\amp), // or Pfunc { ~amp }

+

\midinote, PLseq(\seq) + PL(\seqAdd), 

+

\dur, 0.2

+

);

+


+

// ~seq will be set and read in the new Environment, which will be generated at VarGui init time.

+


+

~specPairs = [\seq, [36, 60, \lin, 1, 36] ! 5, 

+

\seqAdd, [0, 12, \lin, 1, 0],

+

\amp, [0, 0.3, \lin, 0.01, 0.1],

+

\preAmp, [5, 50, \lin, 0.01, 20]];

+


+

v = VarGui(~specPairs, stream: p).gui;

+

)

+


+


+


+

Example 1b:   step sequencer with MIDI

+


+


+

// connect a MIDI device, allNotesOff with CmdPeriod useful

+


+

(

+

MIDIClient.init;

+

m = MIDIOut(0);

+

g = { 16.do({|i| m.allNotesOff(i) }) };

+

CmdPeriod.add(g); 

+


+


+

p = Pbind(

+

\type, \midi,

+

\midiout, m, 

+

\chan, 0,

+

+

\amp, PL(\amp),

+

\midinote, PLseq(\seq) + PL(\seqAdd), 

+

\dur, 0.2

+

);

+

)

+


+

(

+

v = VarGui([\seq, [36, 60, \lin, 1, 36] ! 5, 

+

\seqAdd, [0, 12, \lin, 1, 0],

+

\amp, [0, 1, \lin, 0.01, 0.8]],

+

stream: p).gui;

+

)

+


+

// remove if not needed anymore

+


+

CmdPeriod.remove(g); 

+


+


+


+

Example 1c:  more step sequencers, quantization, slider modifiers 

+


+


+

// SynthDef from (1a)

+

// four players from a Pbind, four varCtr specifications with shifted range

+


+

(

+

p = Pbind(

+

\instrument, \synth_1a,

+

\preAmp, PL(\preAmp), 

+

\amp, PL(\amp), 

+

\midinote, PLseq(\seq) + PL(\seqAdd), 

+

\dur, 0.2

+

);

+


+

~spec = 4.collect { |i| [\seq, [12*i + 24, 12*i + 48, \lin, 1, 24*i + 36] ! 5, 

+

\seqAdd, [0, 12, \lin, 1, 0],

+

\amp, [0, 0.15, \lin, 0.01, 0.07 - (0.01 * i)],

+

\preAmp, [5, 50, \lin, 0.01, 20]] };

+

)

+

+

// run all players in sync by passing a quant arg

+


+

+

v = VarGui(~spec, stream: p!4, quant: 0.2).gui(tryColumnNum: 2, streamPlayerGroups: [[0,3], [1,2]]);

+


+

// EventStreamPlayers derived from Pbind are run in four newly generated environments,

+

// variables, as their names are passed four times in spec, are automatically set in these Environments in order.

+

// Envir index display can be toggled with the button on the right.  

+


+

// get envirs:

+


+

v.envirs;

+


+


+

// Try slider actions with modifier keys

+


+


+

// In Qt you can press modifier keys before and thereafter.

+

// Caps-lock is currently replaced by Cmd,

+

// With the Cmd modifier involved you *have* to press thereafter.

+


+

// Modifiers allow to control groups of sliders, values in other sliders are clipped to the according range.

+

// The following rules apply to synth and var control sliders separately,

+

// combined synth and var controls of same name work with Cmd elsewise (Ex.2).

+


+

// Shift: within an arrayed control all sliders below are set to the handled value or clipped.

+

// Shift + Ctrl: within an arrayed control all sliders above are set to the handled value or clipped.

+


+

// Alt: controls of same name (in different envirs or different synths) are set to the handled value or clipped.

+

// Alt + Shift, Alt + Shift + Ctrl: Accordingly for arrayed controls of same name. 

+


+

// Modifier keys also apply to synth and stream player actions,

+

// the following to synth OR stream player combinations either:

+


+

// Shift: button action with all players 

+

// Shift + Ctrl: button action with all players of this state

+

// Shift + Alt: button action with all players of this group (if synthPlayerGroups / streamPlayerGroups was defined)

+

// Shift + Ctrl + Alt: button action with all players of this state and group ( --- " --- )

+


+


+

Example 1d:   step sequencer, stream players in same environment 

+


+


+

// You can try to benefit from eventstream players having variables in common.

+

// SynthDef from (1a), second Pbind derived from p without pitch offset as parallel pattern 

+


+

(

+

p = Pbind(

+

\instrument, \synth_1a,

+

\preAmp, PL(\preAmp), 

+

\amp, PL(\amp), 

+

\midinote, PLseq(\seq) + PL(\seqAdd), 

+

\dur, 0.2

+

);

+


+

q = Pbindf(p, \midinote, PLseq(\seq));

+


+

v = VarGui([\seq, { [48, 84, \lin, 1, rrand(48, 84)] } ! 5, 

+

\seqAdd, [0, 12, \lin, 1, 7],

+

\amp, [0, 0.3, \lin, 0.01, 0.07],

+

\preAmp, [5, 50, \lin, 0.01, 20]],

+

stream: [q,p].collect(_.asESP),

+

// so currentEnvironment at build time is given implicitely 

+

// by both ESPs and used for setting

+

quant: 1

+

// ensures that players, also if started separately 

+

// for the first time, will play the sequence in sync

+

).gui;

+

)

+


+

// stopping and resuming of a single player may break parallelism 

+

// Shift-clicked reset syncs again

+


+


+


+

Example 1e:   step sequencer and replacements with PLx

+


+


+

(

+

// SynthDef from (1a)

+


+

// Pbind with PL as placeholder for pitch patterns

+

// master VarGui with player

+

// start and set seq

+


+


+

p = Pbind(

+

\instrument, \synth_1a,

+

\preAmp, PL(\preAmp), 

+

\amp, PL(\amp), 

+

\midinote, PL(\a) + PL(\seqAdd), 

+

\dur, 0.2

+

);

+


+


+

// ESP as stream arg: variables will be read from (build time) current Environment.

+


+

~basePat = PLseq(\seq);

+

~a = ~basePat;

+


+

v = VarGui([\seq, [36, 60, \lin, 1, 36] ! 5,

+

\seqAdd, [0, 12, \lin, 1, 0],

+

\amp, [0, 0.1, \lin, 0.001, 0.05],

+

\preAmp, [5, 50, \lin, 0.01, 20]],

+

stream: p.asESP).gui(name: \seq); 

+


+

// control spec array builder for midi range 

+


+

~pitchSpec = { |key = \seq, default = 60, step = 1, lo = 24, hi = 96|

+

var spec = default.asArray.collect([lo, hi, \lin, step, _]);

+

[key, (spec.size == 1).if { spec.flatten }{ spec }];

+

};

+

)

+


+


+

// replace while playing:

+


+

// chord sequence 

+


+

( 

+

~chordPat = PLseq(\seq) + [0, 7];

+

~a = ~chordPat;

+

)

+


+


+

// random sequence with new VarGui for lead voice bounds

+


+

(

+

VarGui(~pitchSpec.(\b, [75, 85], 0.01)).gui(name: \random);

+


+

~randPat = Pfunc { rrand(~b[0], ~b[1]) } + [0, -13.3, -17.7];

+

~a = ~randPat;

+

)

+


+

// accumulation sequence with new VarGui 

+


+

(

+

VarGui(~pitchSpec.(\accum, { rrand(40,80) }!7 )).gui(name: \accum);

+


+

~accPat = Ptuple( [ PL(\accum), PLseq((0..6)) ] ).collect { |x| x[0].keep(x[1]) - x[1] };

+

~a = ~accPat;

+

)

+


+


+

// switch between PLs and set values in VarGui windows

+


+


+

~a = ~basePat;

+


+

~a = ~chordPat;

+


+

~a = ~randPat;

+


+

~a = ~accPat;

+


+


+


+

Example 2:   stream players and synths in one VarGui 

+


+


+

(

+

// Pbind with default synth

+

// ~freq can be a collection, then pitch or chord are shifted by ~freqFac,

+

// ascending (~step > 0) or descending (~step < 0) groups of 4 items

+


+

p = Pbind(

+

\dur, 0.4,

+

\amp, PL(\amp),

+

\freq, PL(\step) ** PLseq((0..3)) * PL(\freq) * PL(\freqFac)    

+

);

+

+

// Synth producing an ascending line, it ends as envelope params are defined,

+

// end notifications will be reflected in the VarGui

+

+

SynthDef(\synth_2, { |out = 0, freq = #[600, 700, 900, 1000], freqFac = 1, 

+

amp = 0.1, ampFac = 0.5, preAmp = 5, attTime = 0.2, relTime = 2, susTime = 6|

+

var src = SinOsc.ar(freq * XLine.kr(freqFac/2, freqFac, attTime + susTime), 0, mul: preAmp).tanh ! 2;

+

Out.ar(0, src * amp * ampFac * EnvGen.ar(Env.linen(attTime, susTime, relTime), doneAction: 2))

+

}).add;

+

)

+


+

// Try multiple slider movements with modifier keys as described in (1c).

+

// As synth args and variables are identically named they can be controlled

+

// with linked slider movements using Cmd.

+


+

// Accordingly stream and synth player buttons can also be pressed in parallel 

+


+

(

+

v = VarGui([\freq, [600, 700, 900, 1000].collect([400, 1200, \lin, 0.01, _]),

+

\freqFac, [0.25, 1.25, \lin, 0.01, 1],

+

\amp, [0.0, 0.1, \lin, 0.001, 0.03],

+

\step, [0.7, 1.25, \lin, 0.01, 1.03]] ! 2, 

+

   

+

[\freq, [600, 700, 900, 1000].collect([400, 1200, \lin, 0.01, _]),

+

\freqFac, [0.25, 1.25, \lin, 0.01, 1],

+

\attTime, [0.02, 2, \lin, 0.01, 0.2],

+

\relTime, [0.02, 2, \lin, 0.01, 2],

+

\susTime, [0.1, 10, \lin, 0.01, 5],

+

\amp, [0.0, 0.1, \lin, 0.001, 0.03],

+

\preAmp, [5, 50, \lin, 0.01, 20]] ! 2,

+

   

+

p!2, \synth_2 ! 2, quant: 0.4

+

).gui(tryColumnNum: 2, allowSynthsBreak: false, allowEnvirBreak: false  

+

/*, colorDeviation: 0 */

+

/*, sliderPriority: \synth, playerPriority: \synth */

+

)

+

)

+


+


+

Example 3:   granular synthesis with Tasks

+


+


+

(

+

// Synth definition for single synthesized grains, waveform to be selected by type 

+


+

SynthDef(\synth_3, { arg out = 0, freqLo = 1000, freqHi = 10000, amp = 0.1, type = 0, pan;

+

var env = EnvGen.kr(Env.perc(0.001, 0.003, amp),doneAction:2), freq;

+

freq = Rand(freqLo, freqHi);

+

OffsetOut.ar(out, Pan2.ar(Select.ar(type, [FSinOsc.ar(freq), Saw.ar(freq), Pulse.ar(freq)]), pan) * env) 

+

}).add;

+


+

// Task function definition. Although Tasks can be passed directly to VarGui as stream arg, 

+

// Functions have the advantage that the generated Tasks (as EventStreamPlayers with given Pbinds) 

+

// will be played in different environments automatically.

+

 

+

f = {

+

loop {

+

s.sendBundle(0.2, ["/s_new","synth_3", -1,0,0, 

+

\out, 0, 

+

\type, ~type, 

+

\amp, ~amp,

+

\freqLo, ~freqMid / ~freqIntvl.sqrt,

+

\freqHi, ~freqMid * ~freqIntvl.sqrt,

+

\pan, [1, -1].choose * ~pan]

+

);  

+

[~durShort, ~durLong].choose.wait;

+

}

+

};

+

)

+


+


+

(

+

v = VarGui( 3.collect { |i| [\type, [0, 2, \lin, 1, i],

+

\freqMid, [100, 8000, \lin, 1, 500 + (2000 * i)],

+

\freqIntvl, [1, 2.0, \lin, 0.01, 1.4],

+

\amp, [0.0, 0.2, \lin, 0.01, 0.05 * (i + 1)],

+

\pan, [0.0, 1.0, \lin, 0.01, 0.8],

+

\durShort, [0.005, 0.01, \lin, 0.001, 0.01],

+

\durLong, [0.01, 0.05, \lin, 0.001, 0.02]

+

] },

+

//  f!3 doesn't collect Functions as there is already a short notation for collecting Function values of Integers, e.g. (_*3)!5

+

stream: 3.collect { f }

+

).gui(colorDeviation: 0);

+

)

+


+


+


+

Example 4a:   interaction of synth and stream players, control synth controlling pbind synths

+


+


+

// Pbind producing a sequence of short events, each pbind-driven synth reading frequency from a control bus

+


+

( 

+

// control synth

+


+

SynthDef(\synth_4_kr, {|out = 0, devFreq = 0.5, midiCenter = 60, midiDev = 10|

+

Out.kr(out, LFDNoise3.kr(devFreq, midiDev, midiCenter))

+

}).add;

+


+

// audio synth

+

// mix of sine and pulse, interval, basic frequency read from control bus

+


+

SynthDef(\synth_4_ar, {|out = 0, in = 0, soundMix = 0.5, pulseWidth = 0.5, indexLR = 0,

+

att = 0.005, rel = 0.1, amp = 0.1, midiInt = 3, midiAdd = 0|

+

var mix, freq1, freq2, fr;

+

freq1 = (In.kr(in, 1) + midiAdd).midicps;

+

freq2 = freq1 * midiInt.midicps / 0.midicps;

+

fr = [Select.kr(indexLR, [freq1, freq2]), Select.kr(1-indexLR, [freq1, freq2])];

+

mix = (SinOsc.ar(fr, 0, amp) * (1 - soundMix)) + (Pulse.ar(fr, pulseWidth, amp) * soundMix);

+

Out.ar(out, EnvGen.ar(Env.perc(att, rel), doneAction: 2) * mix)

+

}).add;

+


+


+

x = Pbind(\instrument, \synth_4_ar,

+

\dur, 0.1,

+

\in, PL(\in),

+

\amp, PL(\amp),

+

\midiInt, Pfunc { rrand(~int[0], ~int[1]) }, // interval bounds

+

\rel, Pfunc { ~rel.choose }, // short and long release time

+

\soundMix, Pwhite(0.1, 0.9),

+

\indexLR, PLrand([0, 1])

+

);

+

)

+


+


+

(

+

// in GUI start control synth before EventStreamPlayer

+

// then try playing with sliders and pausing / resuming the control synth

+


+

c = Bus.control(s,1).index;

+


+

VarGui([\in, [c, c, \lin, 1, c], // bus fixed, display only

+

\int, [3,4].collect([0, 19, \lin, 0.1, _]), 

+

\rel, [0.01, 0.15].collect([0.01, 0.4, \lin, 0.005, _]), 

+

\amp, [0.0, 0.3, \lin, 0.01, 0.15]],

+

+

[\midiCenter, [45, 80, \lin, 0.1, 70],

+

\midiDev, [0, 20, \lin, 0.1, 20], 

+

\devFreq, [0.1, 15, \lin, 0.1, 1],

+

\out, c], // bus fixed, display only

+

x, \synth_4_kr

+

).gui(sliderPriority: \synth, playerPriority: \synth)

+

)

+


+


+


+

Example 4b:   interaction of synth and stream players, pbind and control synth controlling audio synth

+


+


+

// audio synth controlled by LFO pitch synth and a Pbind setting synth args (like Pmono)

+

// PLx ListPatterns taken here as they default to repeats = inf

+


+

(

+

y = Pbind(\type, \set,

+

// sequencing of durations and amplitudes quite fixed, just global tempo and amp control

+

\dur, PLshufn([0.15, 0.15, 0.35]) / PL(\tempo),

+

\amp, PLrand([0.05, 0.12, 0.2]) * PL(\amp),

+

\soundMix, PLseq([0.1, 0.5, 0.9]),

+

\indexLR, PLrand([0, 1]),

+

+

\midiInt, Pfunc { rrand(0, ~intMax) },

+

\args, [\amp, \soundMix, \midiInt, \indexLR] // args to be set must be listed

+

);

+


+


+

// In GUI start control synth (#0) first, then audio synth (#1) and EventStreamPlayer,

+

// then try playing with sliders and pausing / resuming the control synth and EventStreamPlayer.

+


+


+

d = Bus.control(s,1).index;

+


+

VarGui([ \tempo, [0.7, 1.2, \lin, 0.01, 1], 

+

\amp, [0.0, 1.5, \lin, 0.01, 0.7], 

+

\intMax, [-12, 12.0, \lin, 0.1, 8]],

+


+

[[\midiCenter, [45, 80, \lin, 0.1, 60],

+

\midiDev, [0, 20, \lin, 0.1, 1.5], 

+

\devFreq, [0.1, 15, \lin, 0.1, 10],

+

\out, d], // just display bus indices

+

[\in, d,

+

\out, 0,

+

\rel, 10000]], // ignore Env.perc definition

+


+

y, [\synth_4_kr,\synth_4_ar] 

+

).gui(sliderPriority: \synth, playerPriority: \synth)

+

)

+


+


+


+

Example 5a:   reordering of var control

+


+


+

// One may want to have controls grouped per name, not per Environment (resp. Pbind, Task, Synth),

+

// this can be achieved by passing a flat (not grouped) array of specPairs and the appropriate grouping,

+

// there are methods implemented for that special case of reordering:

+


+

(

+

// SynthDef from (1a)

+


+

p = Pbind(

+

\instrument, \synth_1a,

+

\preAmp, PL(\preAmp),

+

\amp, PL(\amp),

+

\midinote, Pfunc { ~midi + rrand(~midiDev.neg, ~midiDev) }, 

+

\dur, 0.2

+

);

+


+

// flat specPairs array without multiple keys

+


+

~specPairs = [\midi, [24, 80, \lin, 1, 36],

+

\midiDev, [0, 0.5, \lin, 0.01, 0.25],

+

\amp, [0, 0.3, \lin, 0.01, 0.1],

+

\preAmp, [5, 50, \lin, 0.01, 25]];

+


+

// get flat expansion of specPairs and appropriate groups

+

+

~specPairsDupped = ~specPairs.specPairsDup(5).postln;

+

~specPairsGroups = ~specPairs.specPairsDupGroups(5);

+

)

+


+


+

// parallel slider movement with Alt (not an array here but identically named variables in different envirs)

+

// parallel player action with Shift-click

+


+

(

+

v = VarGui(

+

~specPairsDupped, 

+

stream: p!5, 

+

varEnvirGroups: ~specPairsGroups, 

+

quant: 0.2

+

).gui(colorDeviation: 0);

+

)

+


+

// compare implicit mapping by passing a grouped collection of specPairs as varCtr arg

+


+

(

+

v = VarGui(

+

~specPairs!5, 

+

stream: p!5, 

+

quant: 0.2

+

).gui(colorDeviation: 0);

+

)

+


+

// Note that in the case of not connected varEnvirGroups the column break gui option

+

// allowEnvirBreak is ignored, means always set to true.

+


+


+


+

Example 5b:   reordering of synth control

+


+


+

(

+

SynthDef(\synth_5b, { |devFactor = 0.1, devFreq = 10, amp = 0.1|

+

var freq = XLine.kr(2000, 400, 3); 

+

Out.ar(0, Pan2.ar(SinOsc.ar(SinOsc.ar(devFreq, 0, devFactor * freq, freq), mul: amp), LFDNoise3.ar(2)) * EnvGate.new) 

+

}).add;

+


+


+

~specPairs = [\devFactor, [0, 0.99, \lin, 0.01, 0.5], 

+

\devFreq, [1, 100, \lin, 0.1, 70],

+

\amp, [0, 0.02, \lin, 0.001, 0.01]];

+


+

~num = 10; // ~num = 20;  use gui args sliderHeight and tryColumnNum for larger nums

+


+

v = VarGui(

+

synthCtr: ~specPairs.specPairsDup(~num), 

+

synth: \synth_5b ! ~num,

+

synthCtrGroups: ~specPairs.specPairsDupGroups(~num)

+

).gui(colorDeviation: 0 /* ,sliderHeight: 12, tryColumnNum: 2 */);

+

)

+


+

// compare implicit mapping by passing a grouped collection of specPairs as synthCtr arg

+


+

(

+

~num = 10;

+


+

v = VarGui(

+

synthCtr: ~specPairs ! ~num, 

+

synth: \synth_5b ! ~num

+

).gui(colorDeviation: 0);

+

)

+


+

// Note that in the case of not connected synthCtrGroups the column break gui option

+

// allowSynthBreak is ignored, means always set to true.

+


+


+


+

Example 6a:   non standard usage: passing Synths 

+


+


+

// Passing symbols or strings as synth args, refering to known (added) SynthDefs, is recommended

+

// However also Synths or nodeIDs can be given, but the player would have to know their state,

+

// so Synths should be registered

+


+

(

+

SynthDef(\sine, { |freq = 400, amp = 0.1| Out.ar(0, SinOsc.ar(freq, mul: amp)) }).add;

+

SynthDef(\pulse, { |freq = 400, amp = 0.1| Out.ar(0, Pulse.ar(freq, mul: amp)) }).add;

+

)

+


+

(

+

x = Synth(\sine).register;

+

y = Synth(\pulse).register;

+

)

+


+

// pause pulse 

+


+

y.run(false);

+


+

// VarGui checks state, definition is known, new Synths of same definition can be started

+


+

v = VarGui(synth: [x,y]).gui;

+


+


+

// With nodeIDs or non-registered Synths VarGui demands addtional state info

+


+

(

+

x = { SinOsc.ar(400, mul: 0.1) }.play;

+

y = { Pulse.ar(700, mul: 0.01) }.play;

+

)

+


+

y.run(false);

+


+

// no new Synths from temporary synth definition

+


+

v = VarGui(synth: [x,y], assumePlaying: true, assumeRunning: [true, false]).gui;

+


+


+


+

Example 6b:   non standard usage: setting sliders from outside

+


+


+

/// Synth definition with arrayed control

+

+

(

+

SynthDef(\vibes, { |amp = 0.005, devFactor = 0.1, devFreq = 10, devDisturb = 0.1| 

+

var freq = \midi.kr(20!30).midicps;

+

Out.ar(0, Mix.fill(30, {|i| [i.even.if {0}{1}, i.even.if {1}{0}] * 

+

SinOsc.ar(SinOsc.ar(devFreq * LFDNoise3.ar(0.5, devDisturb), 

+

0, devFactor * freq[i], freq[i]), mul: amp) } )) 

+

}).add;

+

)

+

+


+

// open gui with random midi values first, start playing 

+


+

(

+

v = VarGui( 

+

synthCtr: [\devFactor, [0, 0.01, \lin, 0.001, 0.005],

+

\devFreq, [0, 1, \lin, 0.01, 0.5], 

+

\devDisturb, [0, 0.99, \lin, 0.01, 0.1], 

+

\amp, [0, 0.05, \lin, 0.001, 0.005],

+

\midi, 30.collect { [45.0, 95, \lin, 0.01, rrand(45,95)] }

+

], 

+

synth: \vibes

+

).gui;

+

)

+


+


+

// update whole control array with one random value

+

// second arg synth index

+


+

v.updateSynthSliders(\midi, 0, rrand(50, 80.0)!30);

+


+


+

// update random group, fourth arg start index

+

// evaluate several times

+


+

v.updateSynthSliders(\midi, 0, rrand(50, 80.0)!5, rrand(0,25));

+


+

+

// phase shift gui

+


+

(

+

p = Pbind(

+

\dur, 0.07,

+

\type, \rest,

+

\i, Pseries(),

+

\do, Pfunc { |e| v.updateSynthSliders(\midi, 0, (e.i / 5).sin * 25 + 70, e.i % 30) }

+

).play(AppClock)

+

)

+


+


+

// stop updating

+


+

p.stop;

+


+


+


+

Example 6c:   non standard usage: setting values for plotting

+


+


+

// By setting a slider hook arbitrary actions can be triggered with lifting a slider handle.

+

// A slider hook can e.g. trigger the refreshing of a plot of parametric functions.

+


+

(

+

p = Plotter("move slider to plot", bounds: Rect(200, 200, 700, 500));

+


+

// number of plots in window

+


+

n = 5; 

+


+

// VarGui with random init params

+


+

v = VarGui({ [

+

\a, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2,

+

\b, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2] } ! n,

+

envir: () ! n

+

).gui;

+


+

// definition of parametric function, environment of variables will be defined later on

+


+

f = { |x| (x * ~a[0]).sin * ~b[0] + ((x * ~a[1]).sin * ~b[1]) }; 

+


+

// evaluation points

+


+

x = (0, 0.1..20);

+


+

// add slider hook, index not defined, everything will be refreshed with arbitrary sliderView mouseUp

+


+

v.addSliderAction { p.value_(v.envirs.collect { |e| f.inEnvir(e).(x) }).refresh };

+


+

// slider movements affecting more than one plot can be achieved with modifier combinations including alt

+

)

+


+


+


+

Example 7:   color grouping

+


+


+

// By default color grouping is based on logical separations:

+

// different synths and environments, different variables and controls, arrays.

+


+

// But apart of that there might exist more related controls

+

// and you'd like to overrule default color grouping.

+

// Especially with a large number of sliders a useful color grouping makes

+

// control much more convenient (also see examples in Buffer Granulation). 

+


+


+

// SynthDef where freq and amp controls could be grouped

+


+

(

+

SynthDef(\synth_7, { |freq = 400, freqOsc = 10, freqDev = 0.03, preAmp = 5, amp = 0.1|

+

var src = SinOsc.ar(SinOsc.ar(freqOsc, 0, freqDev * freq, freq), 0, mul: preAmp).tanh ! 2;

+

Out.ar(0, src * amp * EnvGate())

+

}).add;

+

)

+


+


+


+

// The clumps method is fine for grouping

+


+

(

+

~g = (0..4).clumps([3,2]);

+


+

~synthCtr = [

+

\freq, [20, 5000, \exp, 0, 80],

+

\freqOsc, [0, 20.0, \lin, 0, 10],

+

\freqDev, [0, 0.5, \lin, 0, 0.03],

+

\preAmp, [5, 50, \lin, 0.01, 20],

+

\amp, [0, 0.2, \lin, 0.01, 0.03]

+

];

+

)

+


+


+

// GUI with one Synth

+


+

VarGui(synth: \synth_7, synthCtr: ~synthCtr).gui(synthColorGroups: ~g);

+


+


+

// GUI with two Synths and separate colors for the second

+


+

VarGui(synth: \synth_7!2, synthCtr: ~synthCtr!2).gui(synthColorGroups: ~g+5 ++ ~g);

+


+


+

// Duplicating colors

+


+

(

+

~h = [~g, ~g+5].flop.collect(_.flat);  // or ~h = ~g.collect { |x| x + 5 ++ x };

+


+

VarGui(synth: \synth_7!2, synthCtr: ~synthCtr!2).gui(synthColorGroups: ~h);

+

)

+


+


+


+


+

Example 8:   EZSmoothSlider, EZRoundSlider

+


+


+

// EZSlider is used as default slider type.

+

// With wslib installed (Quark) you can choose 

+

// its slider types EZSmoothSlider and EZRoundSlider 

+

// as gui option 

+


+


+

VarGui(synth: \default).gui(sliderType: \smooth);

+


+

VarGui(synth: \default).gui(sliderType: \round);

+


+


+

// you can choose from their modes \jump (\default) or \move as general option

+


+

VarGui(synth: \default).gui(sliderType: \smooth, sliderMode: \move);

+


+


+

// if you want to set individually you can refer to sliders directly

+

// compare different slider behaviour of Synth 0 and 1

+


+

(

+

v = VarGui(synth: \default!2).gui(sliderType: \smooth, sliderMode: \move);

+


+

v.synthCtrSliders.first.sliderView.mode_(\jump)

+

)

+


+

// For multiple slider handling see Ex 1c.

+


+

// Very small numbers are forced to exponential notation (see also Ex. 9).

+


+

+


+

Example 9:   slider step precision

+


+


+

// VarGui makes some adaptions concerning slider step sizes and rounding

+

// depending on passed controlspec step size

+

// in order to unify slightly different behaviour of GUI kits

+

// and set parameters automatically also for very small step sizes.

+


+

// For this reason (and because of multiple slider handling) 

+

// scaling by modifiers is disabled by default.

+

// You can have a finer control by moving the mouse over the NumberBox,

+

// this also works with modifiers for multiple sliders (Ex.1c, Ex.2).

+


+


+

VarGui([\a, [0, 1, \lin, 0.1, 0]]).gui;

+


+

VarGui([\a, [0, 1, \lin, 0.01, 0]]).gui;

+


+

VarGui([\a, [0, 1, \lin, 0.001, 0]]).gui;

+


+


+


+

VarGui([\a, [0, 100, \lin, 1, 0]]).gui;

+


+

VarGui([\a, [0, 10000, \lin, 10, 0]]).gui;

+


+

VarGui([\a, [0, 10000, \lin, 100, 0]]).gui;

+


+


+

// controlspec step = 0 causes division of range by 100

+


+

VarGui([\a, [0, 1, \lin, 0, 0]]).gui;

+


+

VarGui([\a, [0, 0.1, \lin, 0, 0]]).gui;

+


+

VarGui([\a, [0, 1000, \lin, 0, 0]]).gui;

+


+


+

// division parameter can be changed

+


+

VarGui([\a, [0, 1000, \lin, 0, 0]]).gui(stepEqualZeroDiv: 10);

+


+

VarGui([\a, [0, 0.1, \lin, 0, 0]]).gui(stepEqualZeroDiv: 10);

+


+


+


+

// in case of very long numbers it may be necessary to adapt numberWidth

+


+

VarGui([\a, [0, 0.001, \lin, 0, 0]]).gui(numberWidth: 70);

+


+


+

// In general ranges are adapted to step size if necessary - 

+

// this is ControlSpec's standard behaviour

+


+

VarGui([\a, [1000, 5000, \lin, 1000.001, 0]]).gui(numberWidth: 70);

+


+


+

// a rather exotic option: 

+

// precisionSpan defaults to 10 and fails here ...

+


+

VarGui([\a, [10000000, 50000000, \lin, 10000000.001, 0]]).gui(numberWidth: 100);

+


+


+

// ... but resolves this

+


+

VarGui([\a, [10000000, 50000000, \lin, 10000000.001, 0]]).gui(precisionSpan: 12, numberWidth: 100);

+


+


+


+

Example 10:   MIDI learn functionality with sliders

+


+


+

// Connect your midi device, maybe you have to restart SC then.

+

// As gui example e.g. take SynthDefs and Pbind x from (4a), evaluate both code blocks.

+

// It must be possible to refer to the VarGui instance (e.g. v = VarGui(...))

+


+

// start MIDI

+


+

MIDIIn.connectAll

+


+

// on your device define desired knobs, faders etc. to send cc messages

+

// probably you want to control different parameters, so define different cc numbers

+

 

+

// start learning mode

+


+

v.startMIDIlearn

+


+


+

// now assignment can happen by selecting a VarGui slider box

+

// it gets a focus (blue border, sometimes not very well visible)

+

// then send midi cc with the fader/knob of your choice

+

// sending data with different cc number, while the same slider is still or again selected, will overwrite

+


+

// end of learning has to be defined

+


+

v.stopMIDIlearn

+


+


+

// adaption of control can be done by recalling the learning mode

+


+

v.startMIDIlearn

+


+

...

+


+

v.stopMIDIlearn

+


+


+ + diff --git a/Help/Working with HS and HSpar.html b/Help/Working with HS and HSpar.html new file mode 100644 index 0000000..c091dbb --- /dev/null +++ b/Help/Working with HS and HSpar.html @@ -0,0 +1,147 @@ + + + + + + + + + + + +

Working with HS and HSpar objects for use of synth values in the language by event patterns

+


+

Part of: miSCellaneous

+


+

See also: HS, PHS, PHSuse, HSpar, PHSpar, PHSparUse, PHSplayer, PHSparPlayer, PHSusePlayer, Event patterns and LFOs, HS with VarGui

+


+


+

Motivation

+


+

Sometimes it may be desirable to use synth values in a Pbind (actually the derived EventStreamPlayer), I'm especially thinking of LFO-like controls for the generated synth(s). There are different ways to do this or something similar, see Event patterns and LFOs for examples.

+


+

As a general distinction for an event stream you can have new control values per event (1) or continuous control (2).

+

Some possible implementations:

+


+

attachments/Working with HS and HSpar/tab_2b.png

+


+

There also exist elaborated language solutions that can be used in the above sense: see the interpolation methods in Wouter Snoei's wslib quark and (based on that) Splines with gui by crucialfelix.

+


+

For (1) and (2) LFO behaviour can be defined using help synths (1c, 1d, 1e, 2a, 2b). If the Pbind-generated synths read values from control buses (1e, 2a, 2b), you don't have these values in the language (well, maybe you don't need them). With (1c) you have to use the internal server (of course this may also be ok).

+

Having to define audio SynthDefs with respect to possible future control needs (1e, 2a) though is a bit unflexible. So (1d) integrates some nice features, nevertheless this has to be traded off with additional latency inherent in its mechanism: by using HS / PHS (and relates) help synth values are sent back to the client via OSCresponders, which works with local and internal server.

+

The HS / PHS approach would especially be of interest if control behaviour could more easily be defined by server means than in SC lang (e.g. specific and / or nested UGens) but data should also be further manipulated in the language (e.g. for some kind of combinatorial use such as harmonic or polyphonic calculations). As a separate feature HSpar / PHSpar support timed and combinatorial possibilities to refer to more than one control synth (e.g. switching). This type of control, however, would not necessarily have to use the underlying demand-respond implementation.

+


+


+

Working scheme

+


+

1.) Define help synth(s) by HS or HSpar.

+

+

These objects hold synth definitions, HS holds a single synth definition,

+

HSpar can hold more than one and has additional features.

+


+

2.) Define Pbind(s) by PHS (for HS) or PHSpar (for HSpar) to use synth values.

+


+

3.) Play PHS / PHSpar.

+


+

This instantiates a PHSplayer / PHSparPlayer object. Synth values are demanded and received for the use in defined Pbinds, HS / HSpar keep track of OSC traffic. PHSplayer / PHSparPlayer can be stopped and started with options, concerning Pbinds and help synths.

+


+

Option, may be done immediately with (2) and (3) or later to "step in":

+


+

4.) Define further Pbind(s) by PHSuse / PHSparUse which refer to the same HS / HSpar.

+


+

5.) Play PHSuse / PHSparUse.

+


+

A PHSusePlayer object is instantiated, which can also be stopped and started with options. Different from PHSplayer / PHSparPlayer these options are not affecting help synth control, which is defined by PHS / PHSpar or can be done by their players' methods stop and play. Therefore steps (4) and (5) require the preceding definition resp. playing of PHS / PHSpar. Synchronization with other players can be done by the use of Quant objects. 

+

Instead of using PHSuse / PHSparUse objects in order to have separate players it's also possible to define several HS / HSpar objects independently. See PHSparUse for an example.

+

A VarGui interface may be generated before step (3), see HS with VarGui. 

+


+


+

Latency

+


+

Due to the mechanism of demanding and receiving a synth value before sending an actual message defined by the event pattern, there is a latency in addition to and independent from normal server latency (actually there are two, called demandLatency and respondLatency). demandLatency is the latency of the help synth(s) in relation to the synth value demands, driven by the Pbind duration pattern, respondLatency means the time given to the response to be received safely on time by the client.

+

If these additional latencies are too small the communication between client and server may loose track of synth values needed by the event pattern and the mechanism breaks. On the other hand large latencies and heavy OSC traffic could need more CPU for bookkeeping - maybe you have to play around to find right values. Using the default latencies of HS / HSpar (demandLatency = 0.15, respondLatency = 0.15) and the Server (latency = 0.2) there is an overall latency of 0.5 sec (and in most cases you could lower this a lot if you need to). I didn't intend to use the whole thing for live performance, so just be aware.

+


+


+

OSC demand / respond mechanism to use server values in event streams

+


+

Suppose HS defined, this happens when PHS is played:

+


+


+

1.) latency = 0

+

     sequence of duration values generated in language

+


+

attachments/Working with HS and HSpar/latency_1.png

+


+


+

2.) latency = demandLatency 

+

(timing accuracy depends on defined time granulation) 

+

in server: synth started, values taken at scheduled times

+

+

attachments/Working with HS and HSpar/latency_2.png

+

+

3.) latency = demandLatency + respondLatency

+

back in language, synth values can be used 

+

e.g. to generate control data for an instrument

+

+

attachments/Working with HS and HSpar/latency_3.png

+


+

4.) latency = demandLatency + respondLatency + server latency

+

if note event: (audio) synth started

+

+

attachments/Working with HS and HSpar/latency_4.png

+


+


+

Relation to Pbinds and EventStreamPlayers

+


+

PHS / PHSpar are almost "like" Pbind objects in the way, that event stream behaviour is defined. Internally two event patterns are defined, one for demand time and one for receive time. Consequently PHSplayer / PHSparPlayer also consist of more than one EventStreamPlayer (PHSparPlayer contains a third one for switching between help synths). This splitting seemed necessary for stopping and resuming Pbind(s) and help synth(s).

+

You can easily sync PHSplayers / PHSparPlayers / PHSusePlayers themselves or with other EventStreamPlayers via Quant objects. The corresponding play methods take into account the needed time (caused by latency) to step into the quantization as early as possible.

+


+


+

Granularity - internal quantization

+


+

If several Pbinds are played in parallel, demands for values of the same help synth can be very close together, which causes possibly unnecessary OSC traffic: If accuracy of synth values is not very important (and for LFO-like purposes it is probably not) it seems sufficient to identify synth values from a small time region. This is done by the HS / HSpar parameter granularity, which defaults to 200, defining a time region of 0.005 seconds. Synth value demands within such a region are using just one (namely the first) demanded value, scheduling itself isn't affected. Mostly you won't have to think about this quantization - note that it has nothing to do with Quant objects and synchronizing players.

+


+


+

Synth value access

+


+

In the case of a single help synth (HS), values are accessible within the PHS / PHSuse by the local variable ~val, e.g. by Pkey(\val) or a construction with Pfunc. In the case of several help synths (HSpar), there is the option of defining a separately timed pattern to switch between playing help synths, always marking one as "current". Then ~val refers to this current help synth, but reference behaviour can be differentiated by options and other keywords within the PHSpar / PHSparUse to get values of all playing help synths and other values (indices) which may be useful for control definitions. See examples in the help file PHSpar, e.g. the last one.

+


+


+

Order of execution

+


+

If more than one Pbind is defined via PHS / PHSuse / PHSpar / PHSparUse the corresponding players will be started in order of definition with a small amount of time-shift between them, so that they can refer to each other (especially at coinciding points of logical time). See the last example of PHS.

+


+


+

About the examples

+


+

Examples in the doc files use help synths values mainly for pitch - this seemes to make the concept quite clear. I preferred to take the default instrument to emphasize what is happening structurally. See HS examples for other usages. 

+

Be sure to have the right HS / HSpar definition evaluated before defining resp. playing a PHS / PHSpar ! - examples usually begin with a HS / HSpar definition and are thought to be worked through following the comments.

+


+


+ + diff --git a/Help/ZeroXBufRd.html b/Help/ZeroXBufRd.html new file mode 100755 index 0000000..b336dc3 --- /dev/null +++ b/Help/ZeroXBufRd.html @@ -0,0 +1,1472 @@ + + + + + + + + + + + +

ZeroXBufRd reads consecutive sequences of segments between zero crossings from one or more buffers with demand-rate control

+


+

+

Part of: miSCellaneous

+


+

+

Inherits from: UGen

+


+

+

ZeroXBufRd is for consecutive reading of segments between zero crossings (half wavesets) from one or more buffers, whereby several reading and processing parameters can be sequenced with demand rate ugens. Full waveset sequences can so be generated as a special case. It needs analysis data prepared with ZeroXBufWr. For triggering possibly overlapped (half) wavesets see TZeroXBufRd. ZeroXBufRd / TZeroXBufRd can be used for a number of synthesis / processing techniques in a field between wavesets [1, 4, 5], pulsar synthesis [1, 3], buffer modulation and rectification (which are both a kind of waveshaping) and stochastic concatenation methods [2, 6]. There are already existing SC waveset implementations like Alberto de Campo's Wavesets quark (https://github.com/supercollider-quarks/quarks) and Olaf Hochherz's SPList (https://github.com/olafklingt/SPList), which do language-side analysis and Fabian Seidl's RTWaveSets plugin (https://github.com/tai-studio/RTWaveSets). My focus has been server-side analysis and demand rate ugen control of half waveset parameters as well as multichannel and buffer switch options. Realtime control while analysis is possible, as long as reading is only refering to already analysed sections, but clearly most flexibility is given with a fully analysed buffer, which can also be done in quasi realtime.

+


+

+

NOTE: Depending on the multichannel sizes and the options used (rate and dir sequencing) it might be necessary to increase server resources, i.e. the number of interconnect buffers and / or memory size (e.g. s.options.numWireBufs = 256; s.options.memSize = 8192 * 32; s.reboot). Because of overlappings this is more relevant with TZeroXBufRd than with ZeroXBufRd.

+


+

+

NOTE: Often it pays to adjust zero crossings in the sound buffer effectively to 0, that way sawtooth-like interpolation artefacts can be avoided. See Ex. 7 below.

+


+

+

NOTE: The reading of consecutive half wavesets is implemented with Sweep and retriggering. For that reason each played back half waveset has a minimum length of 2 samples. In the case of adjusted zero crossings that immediately follow each other this can lead to flat sections of a few samples length. Normally this is irrelevant, but you might check setting ZeroXBufWr's adjustZeroXs flag to 2, see the example there and below.

+


+

+

NOTE: Demand rate UGens in ZeroXBufRd / TZeroXBufRd must always use inf as repeats arg, this is of course not necessary for nested ones. You might pass a length arg though (Ex. 5).

+


+

+

NOTE: For avoiding too long half wavesets it might be useful to apply LeakDC resp. a high pass filter before analysis.

+


+

+

NOTE: In rare cases I noticed corrupted buffers in multi buffer examples for no obvious reason.

+


+

+

NOTE: For full functionality at least SC 3.7 is recommended (rate sequencing doesn't work in 3.6)

+


+

+


+

+

CREDITS: Thanks to Tommaso Settimi for an inspiring discussion, which gave me a nudge to tackle these classes. 

+


+

+


+

+

REFERENCES:

+


+

+

[1] de Campo, Alberto. "Microsound" In: Wilson, S., Cottle, D. and Collins, N. (eds). 2011. 

+

The SuperCollider Book. Cambridge, MA: MIT Press, 463-504.

+

+

[2] Luque, Sergio (2006). Stochastic Synthesis, Origins and Extensions. Institute of Sonology, Royal Conservatory, The Netherlands. 

+

http://sergioluque.com

+


+

+

[3] Roads, Curtis (2001). Microsound. Cambridge, MA: MIT Press.

+

+

[4] Seidl, Fabian (2016). Granularsynthese mit Wavesets für Live-Anwendungen. Master Thesis, TU Berlin.

+

https://www2.ak.tu-berlin.de/~akgroup/ak_pub/abschlussarbeiten/2016/Seidl_MasA.pdf

+


+

+

[5] Wishart, Trevor (1994). Audible Design. York: Orpheus The Pantomime Ltd. 

+


+

+

[6] Xenakis, Iannis (1992). Formalized Music. Hillsdale, NY: Pendragon Press, 2nd Revised edition.

+


+

+


+

+


+

+

See also: TZeroXBufRd, ZeroXBufWr, DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFanOut, Buffer Granulation, Live Granulation, PbindFx, kitchen studies

+


+

+


+

+

Creation / Class Methods

+


+

+

*ar (sndBuf, zeroXBuf, bufMix, zeroX = 0, power = 1, mul = 1, add = 0, rate = 1, rateMul = 1, dir = 1, interpl = 4, dUniqueBufSize = 1048576,

+

length = inf, maxTime = inf, att = 0, rel = 0, curve = -4, doneAction = 0)

+

+

sndBuf - Buffer or SequenceableCollection of Buffers to read the data from, data must correspond to zeroXBuf.

+

zeroXBuf - Analysis Buffer resp. SequenceableCollection of such, prepared with ZeroXBufWr. 

+

Must refer to data passed to sndBuf.

+

bufMix - A Number indicating the sndBuf index, a demand rate or other ugens returning sndBuf indices or 

+

a SequenceableCollection of such. In contrast to other args combinations of demand rate and normal ugens are not valid. 

+

If bufMix equals nil (default) the size of the returned signal equals the size of sndBuf,

+

otherwise it equals its own size.

+

zeroX - A Number indicating the index in zeroXBuf, a demand rate or other ugens returning zeroXBuf indices or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of zeroX

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 0.

+

power - Used for processing the buffer signal according to the formula: sig ** power * mul + add per half waveset. 

+

Must be a positive Number, a demand rate or other ugens returning power values or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of power

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 1.

+

mul - Used for processing the buffer signal according to the formula: sig ** power * mul + add per half waveset. 

+

Must be a Number, a demand rate or other ugens returning mul values or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of mul

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 1. 

+

add - Used for processing the buffer signal according to the formula: sig ** power * mul + add per half waveset. 

+

Must be a Number, a demand rate or other ugens returning add values or 

+

a SequenceableCollection of such.

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of add

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

Defaults to 0. 

+

rate - Determines the playback rate per half waveset together with rateMul. 

+

Must be a positive Number, a demand rate or other ugens returning rate values or a SequenceableCollection of such. 

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of rate

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

In contrast to other args combinations of demand rate and normal ugens are not valid for implementational reasons. 

+

Though you can pass a demand rate ugen here and a normal ugen to rateMul (or vice versa) which are then multiplied per half waveset.

+

Defaults to 1.

+

rateMul - Determines the playback rate per half waveset together with rate. 

+

Must be a positive Number, a demand rate or other ugens returning rateMul values or a SequenceableCollection of such. 

+

If in this case the overall multichannel size determined by sndBuf or bufMix is larger than the size of rateMul

+

and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once.

+

In contrast to other args combinations of demand rate and normal ugens are not valid for implementational reasons. 

+

Though you can pass a normal ugen here and a demand rate ugen to rate (or vice versa) which are then multiplied per half waveset.

+

Defaults to 1.

+

dir - Determines the playback direction of half wavesets. 

+

Must be +1 or -1, a demand rate or other ugens returning dir values or 

+

a SequenceableCollection of such.  If in this case the overall multichannel size determined by sndBuf or bufMix is 

+

larger than the size of dir and the latter contains demand rate ugens, they must all be wrapped into Functions for 

+

being used more than once. In contrast to other args combinations of demand rate and normal ugens are not valid.

+

Defaults to 1. 

+

interpl - Determines the interpolation type for the BufRd ugens. 

+

Must equal 1 (no), 2 (linear) or 4 (cubic) or a SequenceableCollection of these numbers.

+

Defaults to 4. 

+

dUniqueBufSize - Determines the buffer size for Dunique objects which have to be used in the case

+

of demand rate ugens passed to rate, dir or bufMix. See Ex.2.

+

Must be an Integer or a SequenceableCollection of Integers.

+

Defaults to 1048576. 

+

length - Determines the number of triggers before release of the overall asr envelope. 

+

Can be a Sequenceable Collection too. Overruled by maxTime if this is reached before.

+

Defaults to inf. 

+

maxTime - Determines the time before release of the overall asr envelope. 

+

Can be a Sequenceable Collection too. Overruled by length if this is reached before.

+

Defaults to inf. 

+

att - Attack time of overall asr envelope or SequenceableCollection thereof. 

+

Defaults to 0. 

+

rel - Release time of overall asr envelope or SequenceableCollection thereof.  

+

Defaults to 0. 

+

curve - Curve of overall asr envelope or SequenceableCollection thereof.  

+

Defaults to -4. 

+

doneAction - Done action of overall asr envelope or SequenceableCollection thereof.  

+

Defaults to 0. 

+

+

+


+

+

Examples

+


+

+

(

+

// boot with extended resources, might be needed for some examples

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.options.memSize = 8192 * 32; 

+

s.reboot;

+

)

+


+

+


+

+

Ex.1) Basic usage

+

+

// prepare two short buffers for audio and zero crossing data

+

// the size of needed zeroX data space can be roughly estimated

+


+

+

(

+

b = Buffer.alloc(s, 2000);

+

z = Buffer.alloc(s, 200);

+

)

+


+

+

// analyse a short snippet of ring modulation

+


+

+

(

+

{

+

var src = SinOsc.ar(300) * SinOsc.ar(120) + SinOsc.ar(30) * 0.1;

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+

+


+

+

// check the waveform

+


+

+

b.plot

+


+

+


+

+


+

+

// loop 3rd half waveset

+

// compare plot and scope

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: 2) }.play

+


+

+

s.scope

+


+

+

x.release

+


+

+


+

+

// loop a whole waveset

+

// demand rate ugens in ZeroXBufRd should always use inf as repeats arg

+

 

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf)) }.play

+


+

+

x.release

+


+

+


+

+

// Note that the distance between zero crossings can be very short,

+

// here the half waveset at index 5 (735-740) has a length of only 5 samples and the amplitude is low.

+


+

+

// zero crossing indices

+


+

+

z.loadToFloatArray(action: { |b| b.postln })

+


+

+


+

+

// the signal is hardly audible, but there as freqscope shows

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: 5) }.play

+


+

+

s.freqscope

+


+

+

x.release

+


+

+


+

+


+

+

// loop a group of 3 wavesets

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq((1..6), inf)) }.play

+


+

+

x.release

+


+

+


+

+


+

+

// sequence multiplier for half wavesets

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), mul: Dseq([1, 0.2], inf)) }.play

+


+

+

x.release

+


+

+


+

+

// mul can be used for rectifying effects

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq((1..2), inf), mul: Dseq([0, 1, 1], inf)) }.play

+


+

+

x.release

+


+

+


+

+


+

+

// add an offset sequence, this results in a pulse-like effect

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), add: Dseq([-0.05, 0.05], inf)) * 0.5 }.play

+


+

+

x.release

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), add: Dseq([0.05, -0.05], inf)) * 0.5 }.play

+


+

+

x.release

+


+

+


+

+


+

+

// half wavesets can also get a power,

+

// per waveset the signal is calculated according to

+

// ((sig ** power) * mul) + add

+


+

+

// be careful with this arg moving away from 1 ! 

+

// high power values can result in loud signals if the source has values outside [-1, 1] and

+

// small power values can also become loud with source values near zero

+


+

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), power: 0.7) }.play

+


+

+

x.release

+


+

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), power: Dseq([0.8, 1.7, 1], inf)) }.play

+


+

+

x.release

+


+

+


+

+


+

+

Ex.2) The 'rate' and 'dir' args

+


+

+

// needs Buffers from Ex.1

+


+

+

s.scope

+


+

+


+

+

// playback rates can be defined generally ...

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), rate: 2.5) }.play

+


+

+

x.release

+


+

+


+

+

// ... or as sequence

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), rate: Dseq((1..3), inf)) }.play

+


+

+

x.release

+


+

+


+

+

// slightly different: here the whole waveset gets one rate

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), rate: Dstutter(2, Dseq((1..3), inf))) }.play

+


+

+

x.release

+


+

+


+

+


+

+

// with the dir argument set to -1 the half wave set is reversed

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: 7) }.play

+


+

+

x.release

+


+

+


+

+

// compare scope, no audible difference here

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: 7, dir: -1) }.play

+


+

+

x.release

+


+

+


+

+

// dir can also be sequenced

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: 7, dir: Dseq([1, 1, -1], inf)) }.play

+


+

+

x.release

+


+

+


+

+

// When demand rate ugens are used for 'rate' or 'dir' args ZeroXBufRd employs Dunique objects.

+

// This means that Buffers have to be allocated for counting, and if the Buffer is full

+

// the synthesis fails. By default a value of 1048576 is defined for 'dUniqueBufSize'.

+

// This should be sufficient for at least some minutes auf audio in average use cases.

+


+

+

// However with very fast triggering you might want to pass a higher value.

+

// With multichannel applications it might be necessary then to run the server with higher memSize.

+


+

+


+

+

// With a deliberately bad (low) size, sequencing fails after a second

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: 1, rate: Dseq([1, 2], inf), dUniqueBufSize: 1024) }.play

+


+

+

x.release

+


+

+

// From low values you can roughly estimate the needed dUniqueBufSize to safely run your application

+

// for a given time

+


+

+


+

+


+

+


+

+

Ex.3) Passing ordinary UGens as args

+


+

+


+

+

// needs Buffers from Ex.1

+


+

+

// values are sampled and hold for the duration of the segments

+


+

+

s.scope

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: SinOsc.ar(SinOsc.ar(0.1).range(0.2, 5)).range(1, 15)) }.play

+


+

+

x.release

+


+

+


+

+

// more fun with moving rates

+


+

+

(

+

x = { 

+

ZeroXBufRd.ar(

+

b, z, 

+

zeroX: LFDNoise3.ar(SinOsc.ar(0.1).range(1, 10)).range(1, 15),

+

rate: SinOsc.ar(SinOsc.ar(0.3).range(0.2, 5)).exprange(1, 5)

+

) ! 2

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// warp effect with accelerating rates

+


+

+

(

+

x = { 

+

ZeroXBufRd.ar(

+

b, z, 

+

zeroX: LFDNoise3.ar(SinOsc.ar(0.1).range(1, 10)).range(1, 15),

+

rate: Dseq((1..50) / 20 + 0.5, inf)

+

) ! 2

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// also combinations of demand rate and ordinary ugens are possible

+

// though with the exception of rate, dir and bufMix args

+


+

+

(

+

x = { 

+

ZeroXBufRd.ar(

+

b, z, 

+

zeroX: LFDNoise3.ar(SinOsc.ar(0.1).range(1, 10)).range(1, 15),

+

rate: Dstutter(Dwhite(10, 100), Dwhite(0.5, 2)),

+

mul: Dseq([0.2, 0.2, 1], inf) * LFDNoise3.ar(5).range(1, 5)

+

) ! 2

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// for combinations of normal and demand ugens with the rate arg rateMul can be used

+


+

+

(

+

x = { 

+

ZeroXBufRd.ar(

+

b, z, 

+

zeroX: LFDNoise3.ar(SinOsc.ar(0.1).range(1, 10)).range(1, 15),

+

rate: Dstutter(Dstutter(3, Dwhite(1, 5)), Dseq([0.5, 1, 1.5], inf)),

+

rateMul: LFDNoise3.ar(5).range(1, 5)

+

) ! 2

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// free resources

+


+

+

(

+

b.free;

+

z.free;

+

)

+


+

+


+

+

Ex.4) Multichannel usage and the 'bufMix' arg

+


+

+

// Without passing a bufMix arg the size of the returned signal is determined by the buffer input. 

+

// It may be a single channel buffer or an array of single channel buffers, 

+

// in correspondence with the analysis buffer(s) - multichannel buffers are not allowed. 

+

// If bufMix is passed, it determines the size of the returned signal, 

+

// its components can be demand rate or other ugens to control switching between buffers per half waveset.

+


+

+

// Note: buffer switching can become CPU-demanding with a lot of Buffers 

+

// as for fast switching it is necessary to play all in parallel

+


+

+

(

+

// boot with extended resources

+

s = Server.local;

+

Server.default = s;

+

s.options.numWireBufs = 256; 

+

s.options.memSize = 8192 * 32; 

+

s.reboot;

+

)

+


+

+


+

+

// prepare 3 buffers

+


+

+

(

+

b = { Buffer.alloc(s, 1000, 1) } ! 3;

+

z = { Buffer.alloc(s, 100, 1) } ! 3;

+

)

+


+

+

// fill with basic waveforms

+

(

+

{

+

var src = [

+

SinOsc.ar(400),

+

LFTri.ar(400),

+

SinOsc.ar(400) ** 10

+

];

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+

+


+

+

s.scope(3)

+


+

+

// play 3 channels

+


+

+

x = { ZeroXBufRd.ar(b, z, zeroX: 1) * 0.1 }.play

+


+

+

x.release

+


+

+


+

+

// play from 1st buffer

+


+

+

x = { ZeroXBufRd.ar(b, z, bufMix: 0, zeroX: Dseq([0, 1], inf)) * 0.2 }.play

+


+

+

x.release

+


+

+


+

+


+

+

// play from 3rd and 1st buffer

+

// to use equally defined demand rate ugens for both, wrap them into a Function

+


+

+

x = { ZeroXBufRd.ar(b, z, bufMix: [2, 0], zeroX: { Dseq([0, 1], inf) }) * 0.1 }.play

+


+

+

x.release

+


+

+


+

+

// play 2 channels with different zeroX sequences

+


+

+

(

+

x = {

+

ZeroXBufRd.ar(

+

b, z,

+

bufMix: [1, 2],

+

zeroX: [Dseq([0, 0, 1], inf), Dseq([0, 1], inf)]

+

) * 0.1

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+


+

+


+

+

// buffers can be switched per half waveset

+


+

+


+

+

x = { ZeroXBufRd.ar(b, z, bufMix: Dseq([0, 1, 2], inf), zeroX: 2, mul: 0.3) }.play

+


+

+

x.release

+


+

+


+

+

x = { ZeroXBufRd.ar(b, z, bufMix: Dseq([0, 1], inf), zeroX: Dseq([1, 2], inf), mul: 0.3) }.play

+


+

+

x.release

+


+

+


+

+


+

+

// Gendy-like texture

+


+

+

(

+

x = {

+

ZeroXBufRd.ar(

+

b, z,

+

bufMix: { Dseq([0, 1, 2], inf) } ! 2,

+

zeroX: 2,

+

mul: 0.1,

+

// array of Drands with different offset

+

// the average of the Drand output is 50,

+

// so on average 1/4 is added to x

+

// and the harmonic relation of L:R is 5:7 -> tritone

+

rate: [1, 1.5].collect { |x| Drand((1..100) / 200 + x, inf) }

+

)

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// bufMix determines size

+

// other args are expanded accordingly

+


+

+

(

+

x = {

+

ZeroXBufRd.ar(b, z,

+

bufMix: { Dseq([0, 1, 2], inf) } ! 2,

+

zeroX: { Dseq([1, 2], inf) },

+

mul: { Dstutter(Diwhite(1, 1000), Drand([0.01, 0.07, 0.2], inf)) },

+

rate: { Dstutter(Diwhite(1, 12), Dwhite(0.1, 10)) }

+

)

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+


+

+

Ex.5) The overall envelope

+


+

+

// The finishing of a ZeroXBufRd is not detemined by finite demand rate ugens but by an overall envelope, 

+

// its release section is triggered by a maximum number of half wavesets ('length') or a maximum time. 

+


+

+

// Buffers from Ex.4

+


+

+

{ ZeroXBufRd.ar(b[0], z[0], rate: 1, length: 10, rel: 0.01) }.plot(0.03)

+


+

+

{ ZeroXBufRd.ar(b[0], z[0], rate: 1, maxTime: 0.01, rel: 0.01) }.plot(0.03)

+


+

+


+

+

// envelopes can be differentiated

+


+

+

{ ZeroXBufRd.ar(b[0..1], z[0..1], rate: 1, maxTime: [0.01, 0.005], rel: [0.005, 0.02]) }.plot(0.03)

+


+

+

{ ZeroXBufRd.ar(b[0..1], z[0..1], rate: 1, length: [7, 2], rel: [0.005, 0.02]) }.plot(0.03)

+


+

+


+

+

// there should be only one doneAction 2 in this case

+


+

+

{ ZeroXBufRd.ar(b[0..1], z[0..1], rate: 1, maxTime: [0.01, 0.005], rel: [0.05, 0.5], doneAction: [0, 2]) }.play

+


+

+


+

+

(

+

b.do(_.free);

+

z.do(_.free);

+

)

+


+

+


+

+

Ex.6) Simultaneous writing and reading

+


+

+


+

+

// The reading of half wavesets can start before analysis is finished,

+

// if ZeroXBufRd is carefully used in with a bit of delay.

+


+

+


+

+

// prepare buffers

+


+

+

(

+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

b = Buffer.read(s, p);

+

)

+


+

+

(

+

z = Buffer.alloc(s, b.duration * 44100 / 5, 1);

+

s.scope;

+

)

+


+

+


+

+


+

+

// Here the average playback rate equals 1 (0.8 = 4/5, 1.25 = 5/4),

+

// so playback will not be faster than writing.

+


+

+

(

+

{

+

var src = PlayBuf.ar(1, b, BufRateScale.ir(b));

+

// write zero crossings, but no need to overwrite sound buffer

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, writeSndBuf: false);

+

DelayL.ar(

+

ZeroXBufRd.ar(

+

b, z,

+

// Dseries keeps counting through the half-filled zeroX buffer

+

zeroX: Dseries(),

+

rate: Dstutter(10, Dseq([0.8, 1, 1.25], inf)),

+

// estimate end time

+

maxTime: b.duration + 1,

+

doneAction: 2

+

),

+

0.2,

+

0.1

+

) ! 2;

+

}.play

+

)

+


+

+

(

+

b.free;

+

z.free;

+

)

+


+

+

// The same can be done with a live-generated signal or a mic input,

+

// but ensure that reading comes after writing !

+


+

+


+

+

// FAILURE BY BAD DEFINITION !

+

// rates are fast, so zeroX indices are referred before analysis 

+

// resulting in garbage noise

+


+

+


+

+

(

+

b = { Buffer.alloc(s, 5 * 44100) } ! 2;

+

z = { Buffer.alloc(s, 2 * 44100) } ! 2;

+

)

+


+

+


+

+

(

+

{

+

var src = LFDNoise3.ar(300 ! 2), sig;

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1);

+

sig = DelayL.ar(

+

ZeroXBufRd.ar(

+

b, z,

+

// Dseries keeps counting through the half-filled zeroX buffer

+

zeroX: { Dseries() },

+


+

+

rate: { Dseq((1..10) / LFDNoise3.ar(3).range(5, 10) + 1, inf) },

+

mul: { Dstutter(Dwhite(50, 500), Drand([0.02, 0.1, 0.5], inf)) },

+

// estimate end time

+

maxTime: 10,

+

att: 0.2,

+

rel: 5,

+

doneAction: 2

+

),

+

0.2,

+

0.1

+

);

+

LeakDC.ar(sig)

+

}.play

+

)

+


+

+


+

+


+

+

//  Reasonable realtime usage

+

//  zeroX is deferred by stuttering, rates are sufficiently low

+


+

+

(

+

b.do(_.zero);

+

z.do(_.zero);

+

)

+


+

+


+

+

(

+

{

+

var src = LFDNoise3.ar(3000 ! 2);

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1);

+

DelayL.ar(

+

ZeroXBufRd.ar(

+

b, z,

+

zeroX: { Dstutter(Dwhite(50, 300), Dseries()) * 2 + Dseq((1..4), inf) },

+

rate: { Dseq((1..5) / LFDNoise3.ar(3).range(5, 10) + 0.5, inf) },

+

mul: { Dstutter(Dwhite(50, 500), Drand([0.05, 0.3, 0.7], inf)) },

+

// estimate end time

+

maxTime: 20,

+

att: 0.2,

+

rel: 5,

+

doneAction: 2

+

),

+

0.2,

+

0.1

+

);

+

}.play

+

)

+


+

+

(

+

b.do(_.free);

+

z.do(_.free);

+

)

+


+

+

// It's of course unproblematic – and still quasi realtime – to fully a analyse 

+

// a snippet of sound with ZeroXBufWr before freely using ZeroXBufRd in the same synth

+


+

+


+

+


+

+

Ex.7) Adjusting zero crossings

+


+

+


+

+

// In general a half waveset isn't totally unipolar:

+

// Zero crossing indices indicate the change of the sign of a signal,

+

// so with this convention the last sample of the half waveset itself

+

// has a different sign.

+


+

+

// This can have the consequence that, depending on the playback rate,

+

// sawtooth-like effects might occur. Such artefacts can be circumvented by

+

// adjusting buffer values at zero crossing indices to 0,

+

// so playback of (half) wavesets is smoothened, especially with extreme rate values.

+


+

+


+

+

(

+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

b = Buffer.read(s, p);

+

z = Buffer.alloc(s, 100000);

+

)

+


+

+

// analyse buffer

+

(

+

{

+

var src = PlayBuf.ar(1, b, BufRateScale.ir(b), doneAction: 2);

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, doneAction: 2);

+

}.play

+

)

+


+

+

// this half waveset clearly shows the effect

+

// (tested with samplerate 44100)

+


+

+

s.scope

+


+

+

x = { ZeroXBufRd.ar(b, z, nil, 220, rate: 0.1) * 2 }.play

+


+

+


+

+

// adjust zeros, also works with arrays of Buffers

+

// you can apply it while running, it might take a moment though

+


+

+

b.adjustZeroXs(z)

+


+

+

x.release

+


+

+


+

+

// alternatively adjusting zero crossings can be chosen as option with analysis:

+

// set flag 'adjustZeroXs' to 1

+


+

+


+

+

(

+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

b = Buffer.read(s, p);

+

z = Buffer.alloc(s, 100000);

+

)

+


+

+

// analyse buffer

+


+

+

(

+

{

+

var src = PlayBuf.ar(1, b, BufRateScale.ir(b), doneAction: 2);

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, adjustZeroXs: 1, doneAction: 2);

+

}.play

+

)

+


+

+

s.scope

+


+

+

x = { ZeroXBufRd.ar(b, z, nil, 220, rate: 0.1) * 2 }.play

+


+

+

x.release

+


+

+


+

+

// the flag 'adjustZeroXs' can also be set to 2

+

// in this case zero crossings have a minimum distance of 2 samples

+

// This goes along with ZeroXBufRd's convention to play one half waveset with a minimum length of 2 samples

+

// (otherwise it couldn't act as a trigger)

+


+

+

// the difference can be observed with fast switches between signs like with WhiteNoise

+


+

+

(

+

b = Buffer.alloc(s, 2000);

+

z = Buffer.alloc(s, 2000);

+

)

+


+

+

(

+

{

+

var src = WhiteNoise.ar();

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, adjustZeroXs: 1, doneAction: 2) * 0.2;

+

}.play

+

)

+


+

+

s.scope

+


+

+


+

+

// this generates a more pulsar-like waveform with adjacent zero crossings,

+

// note that transitions to flat sections are smoothened by cubic interpolation

+


+

+

x = { ZeroXBufRd.ar(b, z, nil, Dseq((0..50), inf), rate: 0.03) * 0.5 }.play

+


+

+

x.release

+


+

+


+

+

// there are no flat sections with 'adjustZeroXs' set to 2

+


+

+

(

+

b.zero;

+

z.zero;

+

)

+


+

+

(

+

{

+

var src = WhiteNoise.ar();

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, adjustZeroXs: 2, doneAction: 2) * 0.2;

+

}.play

+

)

+


+

+


+

+

x = { ZeroXBufRd.ar(b, z, nil, Dseq((0..50), inf), rate: 0.03) * 0.5 }.play

+


+

+

x.release

+


+

+


+

+

Ex.8) Granulation with movement through a buffer

+


+

+

// See Buffer Granulation tutorial, Ex. 1g

+


+

+

Ex.9) Smooth concatenation of adjacent wavesets

+


+

+

// If we invert the waveset with every change of direction we smoothly continue its slope at the zero crossing.

+

// This can be done simply by using the sequence of directions as a ZeroXBufRd's multiplier input,

+

// the waveform then consists of antisymmetric segments.

+


+

+

// Dwalk is suited for this usage, but should not get ordinary ugens as input

+

// for stepsPerDir and stepWidth

+


+

+

(

+

// boot with extended resources

+


+

+

s = Server.local;

+

Server.default = s;

+

s.options.memSize = 8192 * 32;

+

s.reboot;

+

s.scope;

+

s.freqscope;

+

)

+


+

+

// load soundfile into buffer

+

// allocate buffer for zero crossings

+


+

+

(

+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

b = Buffer.read(s, p);

+

z = Buffer.alloc(s, 100000);

+

)

+


+

+

// analyse buffer

+


+

+

(

+

{

+

    var src = PlayBuf.ar(1, b, BufRateScale.ir(b), doneAction: 2);

+

    ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, doneAction: 2);

+

}.play

+

)

+


+

+

// check the number of zero crossings

+


+

+

(

+

z.loadToFloatArray(

+

action: { |x|

+

~zeroXs = x.reject(_==0);

+

~zeroNum = ~zeroXs.size;

+

"done".postln;

+

"number of zero crossings: ".post;

+

~zeroNum.postln

+

}

+

)

+

)

+


+

+

// mix short and long walks into one direction

+


+

+

(

+

x = {

+

var sig, zeroX, dir;

+


+

+

#zeroX, dir = Dwalk(

+

Dwrand([1, 3, 50], [50, 10, 1].normalizeSum, inf),

+

start: 1000,

+

lo: 100,

+

hi: 5000,

+

withDirs: 1

+

);

+

// we need dir twice

+

dir = Dunique(dir, 2048 ** 2);

+

sig = ZeroXBufRd.ar(

+

        b, z,

+

        nil,

+

zeroX,

+

        mul: dir, // change sign together with direction -> smooth continuation of slope

+

        rate: 1,

+

dir: dir

+

);

+

// stereo by simple delay

+

DelayL.ar(sig, 0.2, [0, 0.1])

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// decorrelation by using different rate sequences,

+

// as zeroX is used twice it also needs to be dunique-fied.

+


+

+

(

+

x = {

+

var sig, zeroX, dir;

+


+

+

#zeroX, dir = Dwalk(

+

Dwrand([1, 5, 50], [50, 5, 1].normalizeSum, inf),

+

start: 1000,

+

lo: 100,

+

hi: 5000,

+

withDirs: 1

+

);

+

dir = Dunique(dir, 2048 ** 2);

+

zeroX = Dunique(zeroX, 2048 ** 2);

+

{

+

ZeroXBufRd.ar(

+

b, z,

+

nil,

+

zeroX,

+

mul: dir,

+

rate: Dstutter(Dwhite(1, 20), Dwhite(0.6, 1.4)),

+

dir: dir

+

)

+

} ! 2

+

}.play

+

)

+


+

+

x.release
+

+


+

+

Ex.10) Smooth concatenation of adjacent segments restricted by turning points resp. local minima or maxima

+


+

+


+

+

// This is similar to the previous approach, but here we can change direction

+

// where the slope is zero, so we need an analysis of the slope's zero crossings,

+

// the waveform consists of symmetric segments.

+


+

+

// Dwalk is suited for this usage, but should not get ordinary ugens as input

+

// for stepsPerDir and stepWidth

+


+

+


+

+

(

+

// boot with extended resources

+


+

+

s = Server.local;

+

Server.default = s;

+

s.options.memSize = 8192 * 32;

+

s.reboot;

+

s.scope;

+

s.freqscope;

+

)

+


+

+

// load soundfile into buffer

+

// allocate buffer for zero crossings (of slope !)

+


+

+

(

+

p = Platform.resourceDir +/+ "sounds/a11wlk01.wav";

+

b = Buffer.read(s, p);

+

c = Buffer.read(s, p); // not absolutely needed, just in case to record slope

+

z = Buffer.alloc(s, 100000);

+

)

+


+

+

// analyse slope, indices of turning points resp. local minima or maxima are written to z

+

// buffer c isn't overwritten here (adjustZeroXs == -1)

+


+

+

(

+

{

+

var slope = Slope.ar(PlayBuf.ar(1, b, BufRateScale.ir(b), doneAction: 2));

+

var env = EnvGen.ar(Env([0, 1, 1, 0], [0.01, b.duration - 0.02, 0.01]));

+

    ZeroXBufWr.ar(slope, c, z, adjustZeroXs: -1, doneAction: 2);

+

// slope is loud - don't want to play it !

+

Saw.ar(100, 0.05) * env;

+

}.play

+

)

+


+

+

// slope has much more zero crossings than the original recording, check

+


+

+

(

+

z.loadToFloatArray(

+

action: { |x|

+

~zeroXs = x.reject(_==0);

+

~zeroNum = ~zeroXs.size;

+

"done".postln;

+

"number of positions where slope equals zero: ".post;

+

~zeroNum.postln

+

}

+

)

+

)

+


+

+


+

+

// walk between turning points resp. local minima or maxima

+


+

+

(

+

x = {

+

var sig, zeroX, dir;

+

#zeroX, dir = Dwalk(

+

Dstutter(Dwhite(1, 12), Dwrand([1, 3, 17], [10, 1, 1].normalizeSum, inf)),

+

start: 1000,

+

lo: 100,

+

hi: 25000,

+

withDirs: 1

+

);

+

sig = ZeroXBufRd.ar(

+

        b, z,

+

        nil,

+

zeroX,

+

mul: 1,  // signal doesn't have to be inverted with direction changes

+

rate: 0.5,

+

dir: dir

+

);

+

// stereo by simple delay

+

DelayL.ar(Limiter.ar(sig * 10, 0.5), 0.2, [0, 0.1])

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+

// stereo by decorrelated rate sequences

+

// need to duniquefy dir and zeroX

+


+

+

(

+

x = {

+

var sig, zeroX, dir;

+

#zeroX, dir = Dwalk(

+

Dstutter(Dwhite(1, 50), Dwrand([1, 3, 25], [10, 5, 1].normalizeSum, inf)),

+

start: 1000,

+

lo: 100,

+

hi: 25000,

+

withDirs: 1,

+

dUniqueBufSize: 2048 ** 2

+

);

+

dir = Dunique(dir, 2048 ** 2);

+

zeroX = Dunique(zeroX, 2048 ** 2);

+

sig = { ZeroXBufRd.ar(

+

        b, z,

+

        nil,

+

zeroX,

+

mul: 1,  // signal doesn't have to be inverted with direction changes

+

rate: Dstutter(Dwhite(1, 2), Dseq((1..50) / 50 + 0.5, inf)),

+

dir: dir

+

) } ! 2;

+

LeakDC.ar(Limiter.ar(sig * 5, 0.3))

+

}.play

+

)

+


+

+

x.release

+


+

+


+

+


+

+


+

+


+

+


+

+


+

+


+

+ + diff --git a/Help/ZeroXBufWr.html b/Help/ZeroXBufWr.html new file mode 100755 index 0000000..b859cb2 --- /dev/null +++ b/Help/ZeroXBufWr.html @@ -0,0 +1,241 @@ + + + + + + + + + + + +

ZeroXBufWr writes zero crossing analysis from signals to buffers

+


+

Part of: miSCellaneous

+


+

Inherits from: UGen

+


+

ZeroXBufWr analyses zero crossings from an input signal and writes the signal and the zero crossing indices to buffers. It is intended to be used with ZeroXBufRd and TZeroXBufRd, see these help files for more examples.

+


+

NOTE: Often it pays to adjust zero crossings in the sound buffer effectively to 0, that way sawtooth-like interpolation artefacts can be avoided. See Ex. 2 and Ex. 7 in ZeroXBufRd help..

+


+

NOTE: For avoiding too long half wavesets it can be useful to apply LeakDC resp. a high pass filter before analysis.

+


+

NOTE: For full functionality at least SC 3.7 is recommended (adjustZeroXs set to 2 doesn't work in 3.6)

+


+


+

CREDITS: Thanks to Tommaso Settimi for an inspiring discussion, which gave me a nudge to tackle these classes. 

+


+


+

See also: ZeroXBufRd, TZeroXBufWr, DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFanOut, Buffer Granulation, Live Granulation, PbindFx, kitchen studies

+


+


+

Creation / Class Methods

+


+

*ar (in, sndBuf, zeroXBuf, startWithZeroX = 0, adjustZeroXs = 1, doneAction = 0)

+

+

in - Signal to be analysed, size must correspond to sndBuf and zeroXBuf.

+

sndBuf - Buffer or SequenceableCollection of Buffers to write signals to, 

+

size must correspond to in and zeroXBuf, writing can be disabled with writeSndBuf.

+

The length of sndBuf determines the trigger for the doneAction.

+

zeroXBuf - Buffer or SequenceableCollection of Buffers to write anaysis data to, 

+

size must correspond to in and sndBuf.

+

startWithZeroX - Number 0 or 1 or SequenceableCollection of such, 

+

determining whether the first sample should be regarded as zero crossing.

+

Defaults to 0.

+

adjustZeroXs - One of the Numbers -1, 0, 1, 2 or a SequenceableCollection of such.

+

-1:  indicate all zeroXs, dont't write sound buffer

+

0:  indicate all zeroXs, write sound buffer

+

1:  indicate all zeroXs, write sound buffer, set to 0 there

+

2:  indicate zeroXs with a minimum distance of 2, set to 0 there

+

Actions 1 and 2 can lead to smoother half wavesets, see examples.

+

Defaults to 0.

+

doneAction - Done action to be performed after the duration of the longest buffer of sndBuf. 

+

Defaults to 0. 

+

+


+

Examples

+


+

// See the ZeroXBufRd and TZeroXBufRd help files for more examples

+


+


+

Ex.1) Basic usage

+


+

// prepare two short buffers for audio and zero crossing data

+


+

(

+

b = Buffer.alloc(s, 256);

+

z = Buffer.alloc(s, 256);

+

)

+


+


+

// analyse a short snippet of random noise

+


+

(

+

{

+

var src = LFDNoise3.ar(5000);

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+

// plot buffer, most likely it doesn't start with 0

+


+

b.plot

+


+

// zero crossings

+


+

z.loadToFloatArray(action: { |b| b.postln })

+


+


+

// clear buffers

+


+

(

+

b.zero;

+

z.zero;

+

)

+


+


+

// example with SinOsc

+

// other than you might expect SinOsc doesn't start with 0

+

// for analysis you might want to regard the value at index 0 as zero crossing

+

// this can be done with the flag startWithZeroX:

+


+

(

+

{

+

var src = SinOsc.ar(500);

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+

// plot buffer, you see that it doesn't start with 0

+


+

b.plot

+


+

// zero crossings including start

+


+

z.loadToFloatArray(action: { |b| b.postln })

+


+


+

Ex.2) Adjusting zero crossings

+


+


+

(

+

b = Buffer.alloc(s, 128);

+

z = Buffer.alloc(s, 128);

+

)

+


+


+

s.scope

+


+

// fill buffer

+


+

(

+

{

+

var src = LFPar.ar(700);

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+


+

// playing this repeated half waveset at slow rate shows that the x axis is crossed

+

// as the buffer's value at the zero crossing isn't exactly 0

+


+

x = { ZeroXBufRd.ar(b, z, nil, 1, rate: 0.2) * 0.1 }.play

+


+

// this can be circumvented by two strategies:

+


+

// setting to zeros from the language

+

// can be done while running

+


+

b.adjustZeroXs(z)

+


+

x.release

+


+


+

// alternatively writing can be done with the flag 'adjustZeroXs' set to 1:

+


+

(

+

{

+

var src = LFPar.ar(700);

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, adjustZeroXs: 1, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+


+

x = { ZeroXBufRd.ar(b, z, nil, 1, rate: 0.2) * 0.1 }.play

+


+

x.release

+


+


+

// If flag 'adjustZeroXs' is set to 2, this defines the minimum distance of detected zero crossings,

+

// these positions in the buffer are also set to 0.

+

// This option can make sense in the case of sources with many fast sign switchings.

+


+


+

// here the resulting buffer can end up with sequences of zeros ...

+

(

+

{

+

var seq = Drand([1, 1, 2, 3], inf);

+

var src = Duty.ar(SampleDur.ir * seq, 0, Dwhite(0.1, 1) * Dseq([-1, 1], inf));

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, adjustZeroXs: 1, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+

b.plot

+


+


+

// ... whereas here we have a continuous sequence of at least minimal half wavesets

+


+

(

+

{

+

var seq = Drand([1, 1, 2, 3], inf);

+

var src = Duty.ar(SampleDur.ir * seq, 0, Dwhite(0.1, 1) * Dseq([-1, 1], inf));

+

ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, adjustZeroXs: 2, doneAction: 2);

+

Silent.ar

+

}.play

+

)

+


+

b.plot

+


+

// This can especially make a difference with ZeroXBufRd, as the latter works with

+

// a minimum half waveset length of 2 samples

+


+


+


+


+


+ + diff --git a/Help/attachments/DXMix/sliding_ex_1.png b/Help/attachments/DXMix/sliding_ex_1.png new file mode 100644 index 0000000..4093b05 Binary files /dev/null and b/Help/attachments/DXMix/sliding_ex_1.png differ diff --git a/Help/attachments/DXMix/sliding_ex_2.png b/Help/attachments/DXMix/sliding_ex_2.png new file mode 100644 index 0000000..6cc9e34 Binary files /dev/null and b/Help/attachments/DXMix/sliding_ex_2.png differ diff --git a/Help/attachments/DXMix/sliding_ex_3.png b/Help/attachments/DXMix/sliding_ex_3.png new file mode 100644 index 0000000..24cfd8a Binary files /dev/null and b/Help/attachments/DXMix/sliding_ex_3.png differ diff --git a/Help/attachments/DXMix/sliding_ex_4.png b/Help/attachments/DXMix/sliding_ex_4.png new file mode 100644 index 0000000..08bc72b Binary files /dev/null and b/Help/attachments/DXMix/sliding_ex_4.png differ diff --git a/Help/attachments/DXMix/sliding_ex_5.png b/Help/attachments/DXMix/sliding_ex_5.png new file mode 100644 index 0000000..ab4a86b Binary files /dev/null and b/Help/attachments/DXMix/sliding_ex_5.png differ diff --git a/Help/attachments/DXMix/sliding_ex_6.png b/Help/attachments/DXMix/sliding_ex_6.png new file mode 100644 index 0000000..3c15d4f Binary files /dev/null and b/Help/attachments/DXMix/sliding_ex_6.png differ diff --git a/Help/attachments/Idev suite/XIdev_scheme_3.png b/Help/attachments/Idev suite/XIdev_scheme_3.png new file mode 100644 index 0000000..e1a8c42 Binary files /dev/null and b/Help/attachments/Idev suite/XIdev_scheme_3.png differ diff --git a/Help/attachments/PSPdiv/PSPdiv_graph_1.png b/Help/attachments/PSPdiv/PSPdiv_graph_1.png new file mode 100755 index 0000000..69fa581 Binary files /dev/null and b/Help/attachments/PSPdiv/PSPdiv_graph_1.png differ diff --git a/Help/attachments/PbindFx/PbindFx_graph_1.png b/Help/attachments/PbindFx/PbindFx_graph_1.png new file mode 100644 index 0000000..c275001 Binary files /dev/null and b/Help/attachments/PbindFx/PbindFx_graph_1.png differ diff --git a/Help/attachments/PbindFx/PbindFx_graph_2a.png b/Help/attachments/PbindFx/PbindFx_graph_2a.png new file mode 100644 index 0000000..b0ed238 Binary files /dev/null and b/Help/attachments/PbindFx/PbindFx_graph_2a.png differ diff --git a/Help/attachments/PbindFx/PbindFx_graph_2b.png b/Help/attachments/PbindFx/PbindFx_graph_2b.png new file mode 100644 index 0000000..a92e6f2 Binary files /dev/null and b/Help/attachments/PbindFx/PbindFx_graph_2b.png differ diff --git a/Help/attachments/PbindFx/PbindFx_graph_3a.png b/Help/attachments/PbindFx/PbindFx_graph_3a.png new file mode 100644 index 0000000..2ab825e Binary files /dev/null and b/Help/attachments/PbindFx/PbindFx_graph_3a.png differ diff --git a/Help/attachments/PbindFx/PbindFx_graph_3b.png b/Help/attachments/PbindFx/PbindFx_graph_3b.png new file mode 100644 index 0000000..2511a02 Binary files /dev/null and b/Help/attachments/PbindFx/PbindFx_graph_3b.png differ diff --git a/Help/attachments/PbindFx/PbindFx_graph_4a.png b/Help/attachments/PbindFx/PbindFx_graph_4a.png new file mode 100644 index 0000000..3477ab6 Binary files /dev/null and b/Help/attachments/PbindFx/PbindFx_graph_4a.png differ diff --git a/Help/attachments/PbindFx/PbindFx_graph_4b.png b/Help/attachments/PbindFx/PbindFx_graph_4b.png new file mode 100644 index 0000000..fd554c3 Binary files /dev/null and b/Help/attachments/PbindFx/PbindFx_graph_4b.png differ diff --git a/Help/attachments/PbindFx/PbindFx_graph_4c.png b/Help/attachments/PbindFx/PbindFx_graph_4c.png new file mode 100644 index 0000000..ac6e89f Binary files /dev/null and b/Help/attachments/PbindFx/PbindFx_graph_4c.png differ diff --git a/Help/attachments/Smooth Clipping and Folding/fold_examples.png b/Help/attachments/Smooth Clipping and Folding/fold_examples.png new file mode 100644 index 0000000..4de05c4 Binary files /dev/null and b/Help/attachments/Smooth Clipping and Folding/fold_examples.png differ diff --git a/Help/attachments/VarGui/main_buttons.png b/Help/attachments/VarGui/main_buttons.png new file mode 100644 index 0000000..698cb47 Binary files /dev/null and b/Help/attachments/VarGui/main_buttons.png differ diff --git a/Help/attachments/VarGui/player_1.png b/Help/attachments/VarGui/player_1.png new file mode 100644 index 0000000..f068950 Binary files /dev/null and b/Help/attachments/VarGui/player_1.png differ diff --git a/Help/attachments/VarGui/player_10.png b/Help/attachments/VarGui/player_10.png new file mode 100644 index 0000000..65817a8 Binary files /dev/null and b/Help/attachments/VarGui/player_10.png differ diff --git a/Help/attachments/VarGui/player_2.png b/Help/attachments/VarGui/player_2.png new file mode 100644 index 0000000..8b40b16 Binary files /dev/null and b/Help/attachments/VarGui/player_2.png differ diff --git a/Help/attachments/VarGui/player_3.png b/Help/attachments/VarGui/player_3.png new file mode 100644 index 0000000..eb6a55b Binary files /dev/null and b/Help/attachments/VarGui/player_3.png differ diff --git a/Help/attachments/VarGui/player_4.png b/Help/attachments/VarGui/player_4.png new file mode 100644 index 0000000..eca189e Binary files /dev/null and b/Help/attachments/VarGui/player_4.png differ diff --git a/Help/attachments/VarGui/player_5.png b/Help/attachments/VarGui/player_5.png new file mode 100644 index 0000000..ceef9c5 Binary files /dev/null and b/Help/attachments/VarGui/player_5.png differ diff --git a/Help/attachments/VarGui/player_6.png b/Help/attachments/VarGui/player_6.png new file mode 100644 index 0000000..11ce114 Binary files /dev/null and b/Help/attachments/VarGui/player_6.png differ diff --git a/Help/attachments/VarGui/player_7.png b/Help/attachments/VarGui/player_7.png new file mode 100644 index 0000000..d04c5cf Binary files /dev/null and b/Help/attachments/VarGui/player_7.png differ diff --git a/Help/attachments/VarGui/player_8.png b/Help/attachments/VarGui/player_8.png new file mode 100644 index 0000000..f665912 Binary files /dev/null and b/Help/attachments/VarGui/player_8.png differ diff --git a/Help/attachments/VarGui/player_9.png b/Help/attachments/VarGui/player_9.png new file mode 100644 index 0000000..2e41b6d Binary files /dev/null and b/Help/attachments/VarGui/player_9.png differ diff --git a/Help/attachments/Working with HS and HSpar/latency_1.png b/Help/attachments/Working with HS and HSpar/latency_1.png new file mode 100644 index 0000000..1b207c2 Binary files /dev/null and b/Help/attachments/Working with HS and HSpar/latency_1.png differ diff --git a/Help/attachments/Working with HS and HSpar/latency_2.png b/Help/attachments/Working with HS and HSpar/latency_2.png new file mode 100644 index 0000000..e0552b2 Binary files /dev/null and b/Help/attachments/Working with HS and HSpar/latency_2.png differ diff --git a/Help/attachments/Working with HS and HSpar/latency_3.png b/Help/attachments/Working with HS and HSpar/latency_3.png new file mode 100644 index 0000000..ddefebc Binary files /dev/null and b/Help/attachments/Working with HS and HSpar/latency_3.png differ diff --git a/Help/attachments/Working with HS and HSpar/latency_4.png b/Help/attachments/Working with HS and HSpar/latency_4.png new file mode 100644 index 0000000..c9775cd Binary files /dev/null and b/Help/attachments/Working with HS and HSpar/latency_4.png differ diff --git a/Help/attachments/Working with HS and HSpar/tab_2b.png b/Help/attachments/Working with HS and HSpar/tab_2b.png new file mode 100644 index 0000000..95cac96 Binary files /dev/null and b/Help/attachments/Working with HS and HSpar/tab_2b.png differ diff --git a/Help/attachments/enum/graph.png b/Help/attachments/enum/graph.png new file mode 100644 index 0000000..121e8d5 Binary files /dev/null and b/Help/attachments/enum/graph.png differ diff --git a/Help/enum.html b/Help/enum.html new file mode 100644 index 0000000..61d2796 --- /dev/null +++ b/Help/enum.html @@ -0,0 +1,358 @@ + + + + + + + + + + + +

enum general enumeration tool, can be used for a variety of combinatorial problems

+


+

Part of: miSCellaneous

+


+

Method enum implements a basic backtracking search suited for a number of counting and

+

optimization problems. For specification of search criteria a boolean-valued Function 

+

has to be passed.

+


+

Some Important Issues Regarding method enum

+


+

The method applies to Integers indicating the recursion depth.

+

Due to the nature of combinatorial problems with an often rapid growth of solutions

+

and/or enumeration steps with increase of size, it is recommended to start 

+

examples with low numbers to avoid hangs.

+


+

Integer::enum (pool, function, evalAtZero, type, order, maxNum)

+

+

Returns solutions of the problem, which is defined by function,

+

as an Array of SequenceableCollections (size = receiver).

+

+

pool - SequenceableCollection of items to be considered for 

+

possible solutions.

+

If type equals 0 the same pool is taken for all indices of possible solutions,

+

if type equals 1 a SequenceableCollection of pools might be passed.

+

The existence of an additional type arg is necessary as it might also be desirable

+

to consider SequenceableCollections as single items of possible solutions.

+

+

function - Boolean-valued Function to be evaluated at currentIndex. 

+

For many applications it is not necessary to evaluate at index 0

+

(so per default evalAtZero set to false), the Function is not evaluated

+

and the item is supposed to be considered as first element of a possible solution.

+

+

From current state the Function is passed the following args to specify search:

+

item - Current item to be checked

+

currentIndex - Current enumeration level, 

+

between 1 (resp. 0 in case evalAtZero set to true) and receiver - 1

+

currentCol - Contains current collection of items already chosen

+

at indices up to currentIndex - 1, for efficiency reasons 

+

length of this collection equals receiver and items indexed at 

+

current or higher enumeration level might stem from earlier enumeration steps.

+

indexCol - Current collection of indices (of items from pool) already chosen, 

+

for efficiency reasons length of this collection equals receiver and indices at 

+

current or higher enumeration level might stem from earlier enumeration steps.

+


+

evalAtZero - Boolean. Determines if function will be evaluated at index 0.

+

Defaults to false.

+

+

type - Must be 0 or 1. Determines if pool should be taken for all items (0, default)

+

or specified per index (1).

+


+

order - Boolean. Determines if search should follow order of items given in pool

+

or a search order is randomly chosen. Defaults to true.

+

For search of a single random solution one would set order to false and

+

maxNum to 1.

+

+

maxNum - Integer. Maximum number of solutions to be searched for.

+

Defaults to inf.

+


+


+


+

Example 1:   Basic enumerations, Subsets

+


+


+

// Listing all tuples from a given collection.

+

// Note that this kind of complete enumeration 

+

// can be done with method allTuples more efficiently.

+


+


+

3.enum([1,2])

+


+

-> [ [ 1, 1, 1 ], [ 1, 1, 2 ], [ 1, 2, 1 ], [ 1, 2, 2 ], 

+

[ 2, 1, 1 ], [ 2, 1, 2 ], [ 2, 2, 1 ], [ 2, 2, 2 ] ]

+


+


+


+

// type 1 for specified pool(s) 

+

// receiver must equal size of passed pools

+


+

3.enum([[1,2], [-1,-2], [\a,\b]], type: 1)

+


+

-> [ [ 1, -1, a ], [ 1, -1, b ], [ 1, -2, a ], [ 1, -2, b ], 

+

[ 2, -1, a ], [ 2, -1, b ], [ 2, -2, a ], [ 2, -2, b ] ]

+


+


+


+

// strictly monotone tuples 

+

// note that function is evaluated only for i > 0,

+

// so no problem to write i-1 

+


+

3.enum((1..4), { |x,i,col| x > col[i-1] }); 

+


+

-> [ [ 1, 2, 3 ], [ 1, 2, 4 ], [ 1, 3, 4 ], [ 2, 3, 4 ] ]

+


+


+

// Above is equivalent to the task of finding all

+

// k-subsets of a given set of n elements.

+

// The results are lexically ordered.

+

// For an arbitrary pool, not necessarily numbers,

+

// you can use the index collection arg within the Function.

+


+

3.enum([\a, \b, \c, \d], { |x,i,col,icol| icol[i] > icol[i-1] }); 

+


+

-> [ [ a, b, c ], [ a, b, d ], [ a, c, d ], [ b, c, d ] ]

+


+


+

// The number of k-subsets of a set of length n equals  n! / k! / (n-k)!

+

// You might want to check before a complete enumeration:

+


+

~subsetNum = { |n, k| 

+

    var p = 1; 

+

    k.do { |i| p = p * (n - k + i + 1) / (i + 1) }; 

+

    p 

+

}; 

+


+

~subsetNum.(18,5)

+


+

-> 8568

+


+


+

// In principle search for tuples with certain features (not only subsets)

+

// can always be done with using allTuples and filtering out afterwards, 

+

// but this is only feasible for small n.

+

// E.g. n = 18 and k = 5 requires calculating 1889568 (n**k) tuples first. 

+

// Furthermore method allTuples defaults to a maximum number of 16364 (2**14).

+

// So (18**5).log2.ceil (21) gives the exponent of 2 to pass 

+


+

{ 

+

((1..18)!5).allTuples((2**21).asInteger)

+

.select { |y| y.every { |x,i| (i == 0) or: { y[i-1] < y[i] } } }.size.postln; 

+

}.bench

+


+

-> 8568

+

time to run: 6.6191733989999 seconds.

+

6.6191733989999

+


+


+

{ 5.enum((1..18), { |x,i,col| x > col[i-1] }).size.postln; }.bench

+


+

-> 8568

+

time to run: 0.10966498400012 seconds.

+

0.10966498400012

+


+


+

// Tuples without repetitions -

+

// keep in mind that passed collection is of full length in each step, 

+

// so we have to restrict to the indices up to i-1.

+

// Writing col[(0..i-1)] means that a new Array is generated in

+

// every enumeration step. This might be a bottleneck

+

// with a huge number of steps and could be optimized.  

+


+

3.enum((1..4), { |x,i,col| col[(0..i-1)].includes(x).not }); 

+


+

-> [[ 1, 2, 3 ], [ 1, 2, 4 ], [ 1, 3, 2 ], [ 1, 3, 4 ], [ 1, 4, 2 ], [ 1, 4, 3 ], 

+

[ 2, 1, 3 ], [ 2, 1, 4 ], [ 2, 3, 1 ], [ 2, 3, 4 ], [ 2, 4, 1 ], [ 2, 4, 3 ], 

+

[ 3, 1, 2 ], [ 3, 1, 4 ], [ 3, 2, 1 ], [ 3, 2, 4 ], [ 3, 4, 1 ], [ 3, 4, 2 ], 

+

[ 4, 1, 2 ], [ 4, 1, 3 ], [ 4, 2, 1 ], [ 4, 2, 3 ], [ 4, 3, 1 ], [ 4, 3, 2 ]]

+


+


+


+

Example 2:   Melodic Shapes

+


+


+

// This follows an idea by Fabrice Mogini

+

// Given a sequence of pitches, find all melodies of same shape,

+

// here just understood as up-and-down movement,

+

// using the given pitches without repetition.

+


+

// The Function has to check whether

+

// 1.) there are no repetitions

+

// 2.) the difference to the last item is of same signum as in the original pitch sequence

+


+

// keep in mind that, as always, passed collection is of full length in each step, 

+

// so we have to restrict to the indices up to i-1

+


+

( 

+

// assuming no pitches repeated

+

m = [60, 65, 62, 69, 71]; 

+

d = m.differentiate.sign; 

+

f = { |x,i,col| col[(0..i-1)].includes(x).not && ((x - col[i-1]).sign == d[i]) }; 

+

m.size.enum(m, f); 

+

) 

+


+

--> [ [ 60, 65, 62, 69, 71 ], [ 60, 69, 62, 65, 71 ], [ 60, 71, 62, 65, 69 ], 

+

[ 65, 69, 60, 62, 71 ], [ 65, 71, 60, 62, 69 ], [ 62, 65, 60, 69, 71 ], 

+

[ 62, 69, 60, 65, 71 ], [ 62, 71, 60, 65, 69 ], [ 69, 71, 60, 62, 65 ] ]

+


+


+


+

Example 3:   Partitions of Integers, Scales

+


+


+

// list all partitions of a given integer a into n summands

+


+

(

+

a = 10;

+

n = 5;

+


+

// storage of partial sums

+

// ith element will represent sum up to index i-1

+


+

p = 0!(n+1);

+


+

// Function should also consider case i = 0

+


+

f = { |x,i,col| 

+

var order = (i > 0).if { x >= col[i-1] }{ true };

+

p[i+1] = p[i] + x;

+

order and: {

+

(i + 1 < n).if {

+

// check if partial sums are not too large

+

(n - i) * x + p[i] <= a

+

}{

+

// partition check at last index i == n-1

+

p[i+1] == a 

+

}

+

}

+

};

+


+

// true causes check also at index 0

+

5.enum((1..10), f, true); 

+

)

+


+

-> [ [ 1, 1, 1, 1, 6 ], [ 1, 1, 1, 2, 5 ], [ 1, 1, 1, 3, 4 ], [ 1, 1, 2, 2, 4 ], 

+

[ 1, 1, 2, 3, 3 ], [ 1, 2, 2, 2, 3 ], [ 2, 2, 2, 2, 2 ] ]

+


+


+


+

// in above Function the given integer and the number of summands are hardcoded.

+

// For a general purpose tool better make a function constructor,

+

// also build in an arg that determines if solutions should be ascending or not

+


+

(

+

// Function to make boolean value Function depending on sum a and number of summands n

+

g = { |a,n,ascending = true|

+

var p = 0!(n+1);

+

{ |x,i,col| 

+

var order = ((i > 0) && ascending).if { x >= col[i-1] }{ true };

+

p[i+1] = p[i] + x;

+

order and: {

+

(i + 1 < n).if {

+

// check if partial sums are not too large

+

ascending.if { x }{ 1 } * (n - i) + p[i] <= a

+

}{

+

// partition check at last index i == n-1

+

p[i+1] == a 

+

}

+

}

+

}

+

};

+


+

// Function for listing all partitions of number a with n summands 

+


+

h = { |a,n,pool,ascending = true| n.enum(pool, g.(a,n,ascending), true) };

+

)

+


+


+

// partitions of number 10 consisting of 5 summands

+

// monotone tuples are demanded (so reorder of tuples is neglected)

+


+

h.(10, 5, (1..10))

+


+

-> [ [ 1, 1, 1, 1, 6 ], [ 1, 1, 1, 2, 5 ], [ 1, 1, 1, 3, 4 ], [ 1, 1, 2, 2, 4 ], 

+

[ 1, 1, 2, 3, 3 ], [ 1, 2, 2, 2, 3 ], [ 2, 2, 2, 2, 2 ] ]

+


+


+

// partitions of number 12, taking order into account (not ascending),

+

// lists all possible scales of a certain number of pitches,

+

// given as interval arrays

+


+

// this gives all scales of 7 tones with stepwidth from 1 to 3 semitones.

+

// Result contains rotations of interval arrays that are different,

+

// e.g. major [2,2,1,2,2,2,1] and dorian [2,1,2,2,2,1,2]

+


+

x = h.(12, 7, (1..3), false);

+

x.size;

+


+

-> 266

+


+


+

Example 4:   Graphs

+


+


+

// undirected graph with 9 nodes

+


+

attachments/enum/graph.png

+


+

(

+

// graph represented as array of possible successor nodes

+

g = [[1,2], [0,3], [0,3,5], [1,2,4,6], [3,7], [2,6], [3,5,7], [4,6,8], [7]];

+


+

// Function for finding unused nodes to be connected

+

f = { |x,i,col| col[(0..i-1)].includes(x).not and: { g[col[i-1]].includes(x) } };

+

+

// search for all paths using each node exactly once

+

9.enum((0..8), f)

+

)

+


+

-> [ [ 1, 0, 2, 5, 6, 3, 4, 7, 8 ], [ 4, 3, 1, 0, 2, 5, 6, 7, 8 ], [ 6, 5, 2, 0, 1, 3, 4, 7, 8 ], 

+

[ 8, 7, 4, 3, 1, 0, 2, 5, 6 ], [ 8, 7, 4, 3, 6, 5, 2, 0, 1 ], [ 8, 7, 6, 5, 2, 0, 1, 3, 4 ] ]

+


+


+

// give only one random solution - here path of length 8

+


+

8.enum((0..8), f, order: false, maxNum: 1)

+


+

-> [ [ 0, 1, 3, 4, 7, 6, 5, 2 ] ]

+


+


+


+


+ + diff --git a/Help/kitchen studies.html b/Help/kitchen studies.html new file mode 100755 index 0000000..ec498d3 --- /dev/null +++ b/Help/kitchen studies.html @@ -0,0 +1,1090 @@ + + + + + + + + + + + +

kitchen studies source code of the corresponding fixed media piece

+


+

Part of: miSCellaneous

+


+

See also: PbindFx, Buffer Granulation, Live Granulation, VarGui, DX suite, DXMix, DXMixIn, DXEnvFan, DXEnvFanOut, DXFan, DXFanOut, ZeroXBufRd, TZeroXBufRd, ZeroXBufWr

+


+


+

In 2016 my interest in granular synthesis was focussed on the potential of sequencing arbitrary effects and effect graphs on single grains. This is an application of the class PbindFx, which I added with miSCellaneous v0.14. As the possibilities are countless – an arbitrary number of effects can be applied on each grain in an arbitrary way: in sequence, in parallel or in any graph order – I started experiments with combinations of at most two effects and sequencing their parameters. At the same time I wanted to share my experiences and document what I was encountering while composing a piece of music. I thought the best approach for that twofold need would be a sequence of small pieces in once, each of them using different effect processings per grain. A piece of such form would not be totally typical for my usual compositional practice, as I tend to favour longer forms with a few contrasting types of material combined in a developing relation, but nevertheless an interesting challenge. As a sound source I took the kitchen sound of five seconds which is already contained in miSCellaneous lib since version 0.7 and used for examples in the Buffer Granulation tutorial. 

+

For the fixed media piece kitchen studies the audio, resulting from the six sections of code below, has only been cut and slightly mastered with a bit of equalization (as many random components are included, the output will of course vary from one evaluation to the next). Each code section delivers a mixed control by gui (VarGui) and code snippets (Patterns), which reflects my personal experimental preferences with the concerned sounds and might serve as a starting point for the reader's experiments and modifications. Compressed versions of the original piece as a whole and its parts can be found on my website http://daniel-mayer.at, a further documentation of the compositional process will follow as publication in the artistic research database Research Catalogue (https://www.researchcatalogue.net/profile/show-exposition?exposition=324609).

+


+


+

WARNING: 

+


+

1.) Be careful with amplitudes, especially with buffers you haven't granulated before !

+

Also keep in mind that a granular cloud moving through a buffer can suddenly become louder – this is especially the case with the one percussive sound contained in the used kitchen source sound. Moreover other controls than amp (e.g. buffer position, trigger rate, effect parameters) can cause a raise of amplitude too.

+


+

2.) I haven't used below setups for live performances. Although all of them work stable for me as they are, in general hangs can occasionally happen with pattern-driven setups. Often this can be tracked down to sequences of extremely short event durations (and/or long grain durations). Where this can happen as a side-effect, thresholds can be built in (e.g. a parameter maxTrigRate). 

+

Another possible source of hangs is careless deep nesting of Patterns where mistakes can easily occur. Starting with clear Pattern structures is recommended – and if more complications are involved: testing without sound first after saving your patch (generating data with a single Stream derived from a Pattern), might be a good idea.

+


+


+

NOTE: 

+


+

Running the code examples: First run the code from chapter 'Preparations' - i.e. start server with extended ressources and evaluate the following block of code. Then you can run the example code of each part (but not in parallel). See the comments of 'Part 1', many of them explain principles, which are used in all other parts too. Similary, gui conventions are pretty much the same for all six parts, see the note on common VarGui features below.

+

Types of variables: For employing the VarGui interface environmental variables and PLx patterns are used. With VarGui every EventStreamPlayer derived from a passed Pattern is run in a separate newly generated Environment, where variables are being set and Streams from Pfuncs and PLx patterns are reading from. See Event patterns and Functions, PLx suite and VarGui. For certain live replacements though, environmental variables in topEnvironment are chosen and for the sake of clarity some interpreter variables are used too. As a result of these two exceptions the six code blocks, as they are, cannot be run in parallel. As each one is producing dense and rich sound structures it was supposed that combining them in realtime would not be the first option anyway, but it could of course be done by rewriting these variables.

+

Buffers: All examples below expect mono buffers like the delivered kitchen sound. Buffer paths refering to the included sample suppose that you have installed via quarks or moved miSCellaneous lib into the user or system extensions directory directly (not into a subfolder), if not so or you have moved the sample file somewhere else you'd have to change paths accordingly.

+

Versions: For the published version of kitchen studies I worked on OS 10.6 / SC 3.6.5 and OS 10.8.5 / SC 3.8.0, slight differences in sound might occur with other hardware, operating systems and SC versions.

+


+


+


+

Preparations

+


+

// start server with extended ressources

+


+

(

+

s.options.numPrivateAudioBusChannels = 2048;

+

s.options.memSize = 8192 * 32;

+

s.options.maxNodes = 1024 * 4;

+


+

s.reboot;

+

)

+


+

// loading the buffer (maybe you need to use Platform.systemExtensionDir instead)

+


+

// defining SynthDefs:

+

// 'pos' for single grains

+

// 'posPlay' for moving buffer position

+

// fx SynthDefs for PbindFx

+

// 'leakDC' for leaking DC from summed grains and limiting

+


+

(

+

b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav");

+

// This searches the most likely extension places for the miSCellaneous folder.

+

// In case of an extraordinary install situation or a removed sound file, pass the concerned path.

+


+


+

// \posPlay is the synthdef for one grain

+

// args pos and posDev are to be read from a bus, played by a LFO

+


+

// arg pos is relative between 0 and 1

+

// arg posDev is absolute (max absolute deviation in seconds from pos)

+


+

SynthDef(\posPlay, { |out = 0, sndBuf = 0, granDur = 0.1, pos = 0,

+

posDev = 0, rate = 1, att = 0.002, rel = 0.005, pan = 0, amp = 1|

+

var env, src;

+

pos = Rand(posDev.neg, posDev) / BufDur.kr(sndBuf) + pos;

+

src = PlayBuf.ar(1, sndBuf, BufRateScale.kr(sndBuf) * rate,

+

1, round(pos * BufFrames.kr(sndBuf)), 1, 2);

+

env = EnvGen.ar(

+

Env([0, amp, amp, 0], [att, granDur - att - rel, rel], 0),

+

doneAction: 2

+

);

+

OffsetOut.ar(out, Pan2.ar(src, pan) * env);

+

}, \ir!10).add;

+


+

// posRate of 1 (given by posRateE and posRateM) means

+

// pos movement through the buffer with original velocity.

+

// buffer segment is determined by posLo and posHi.

+

// posDev and pos movement do not depend on buffer length and size of buffer segment !

+

// (posRate does, but pos is then mapped to the buffer segment by .linlin)

+


+

// there are four types of movement through the buffer, determined by the arg 'type':

+

// O: forward

+

// 1: backward

+

// 2: forward and backward

+

// 3: randomly with cubic interpolation

+


+

SynthDef(\pos, { |out = 0, sndBuf = 0, posRateE = 0, posRateM = 1, type = 0,

+

posLo = 0, posHi = 1, posDevE = 1, posDevM = 0|

+

var pos, posDev = 10 ** posDevE * posDevM, posRate = 10 ** posRateE * posRateM;

+


+

posRate = posRate / BufDur.kr(sndBuf) / max(0.001, (posHi - posLo));

+

pos = Select.kr(type, [

+

LFSaw.kr(posRate, 1),

+

LFSaw.kr(posRate, 1).neg,

+

LFTri.kr(posRate, 1).neg,

+

LFDNoise3.kr(posRate)

+

]);

+

pos = pos.linlin(-1, 1, posLo, posHi);

+

Out.kr(out, [pos, posDev])

+

}).add;

+


+


+

// leaking DC and limiting summed audio of all grains

+


+

SynthDef(\leakDC, { |out = 0, in|

+

var sig = In.ar(in, 2);

+

Out.ar(out, Limiter.ar(LeakDC.ar(sig)));

+

}).add;

+


+


+

// this Function determines enveloping convention:

+

// absEnv = 1 (true): attack and release times in milliseconds are taken anyway,

+

// 'overlap' is possibly overriden.

+

// absEnv = 0 (false): attack and release times in milliseconds are taken only if

+

// their sum is smaller than grain duration (derived from 'overlap' and 'trigRate'),

+

// otherwise they are shrunk and their relation is kept.

+


+

d = ();

+


+

d.attRel = { |dict, granDur, att, rel, absEnv = 1|

+

// times in secs

+

var sum;

+

att = att * 0.001;

+

rel = rel * 0.001;

+

sum = att + rel;

+

(sum <= granDur).if {

+

[att, rel]

+

}{

+

(absEnv == 1).if {

+

[att, rel]

+

}{

+

[granDur * att, granDur * rel] / sum

+

}

+

}

+

};

+

)

+


+


+


+

NOTE: 

+


+

Common features of dedicated VarGuis 

+


+

The slider section always contains two blocks, the lower one is for parameters of the buffer position synth and the upper one is for parameters of the PbindFx. Within the lower block the relative buffer position is determined by parameters 'posLo' and 'posHi'. As the percussive event within the the loaded kitchen sound appears around position 0.3, most position defaults describe a relatively small section around this value. The deviation of position as well as the rate of movement through the buffer are determined by a mantissa-exponent representation each. Finally four types of movement are defined: 0 = forward, 1 = backward, 2 = forward and backward, 3 = random with cubic interpolation.

+

Within the upper block there are two or three differently colored sections, separating controls of source and effects (one or two). PbindFx parameters can be directly passed to the source grain player synths (such a 'amp'), but they might also be used to process the actual synth args, e.g. 'absEnv' determines if parameters 'att' and 'rel' are to be taken literally or relative with respect to an "absolute" 'overlap' value (see pseudo method 'attRel' above), this kind of processing is defined within the PbindFx. Not all parameters need to occur in the VarGui instance, they might also be controlled by PL pattern proxies, in that case the pattern sources are defined in topEnvironment later on.

+

Amplitude parameters 'amp' occur with grain synths as well as with effect synths, in the latter case they appear with the fx name as suffix in the gui. Their meaning is not the same in all cases, mostly they are understood as amplitudes of the fx-processed signal ("pre-mix"), but they can be applied to the mix too ("post-mix"). In the case of a combined fx/no-fx sequencing a more fine-tuned amplitude control can be achieved with applying a separate gain fx, which is used instead of no effect. For the the sake of clarity this is only done within the last part.

+

The EventStreamPlayer derived from the PbindFx (e0 on the right side) and the buffer position synth can be started separately by pressing the green buttons. Note that patches can also be controlled by setting certain PL proxy pattern sources as shown at the bottom of part 1. Regarding the compositional process of kitchen studies, the final sounding result very much depends on the decision which parameters are controlled by pattern sequencing and which ones are controlled by gui-tunable parameters. Such decisions happened during a long process of experimenting which usually started with gui control for all parameters. However for the final version no live-control with sliders has been recorded, parameters were either fixed or algorithmically controlled by patterns.

+


+


+


+


+

Part 1 – comb delay plus separate delay modulation

+


+

// This granulation combines two effects in sequence.

+

// The same effect chain (fxOrder = [1, 2]) is used for all grains but parameters change.

+


+

(

+

// comb delay

+


+

SynthDef(\comb, { |out = 0, in, mix = 0.5, amp = 1, maxDelayTime = 0.2, delayTime = 0.02,

+

decayTime = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = CombL.ar(inSig, maxDelayTime, delayTime, decayTime, amp);

+

OffsetOut.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+

// modulation of delay

+

// modInd is a provisional measure for delay modulation in analogy to FM's index

+


+

SynthDef(\delayMod, { |out = 0, in, delayTime = 0.1, maxDelayTime = 2,

+

modFreq = 0, modIndM = 0, modIndE = -5, mix = 1, amp = 0.1|

+

var inSig = In.ar(in, 2), sig, modDev;

+

modDev = 10 ** modIndE * modIndM * modFreq;

+

sig = DelayC.ar(inSig, maxDelayTime, SinOsc.ar(modFreq, 0, modDev, delayTime), amp);

+

OffsetOut.ar(out, (1 - mix) * inSig + (sig * mix));

+

}).add;

+


+


+

// topEnvironment needed for pattern proxies

+

// the slider variables are set in dedicated environments by VarGui

+


+

t = topEnvironment.push;

+


+

// bus to DC leaker

+

a = Bus.audio(s, 2);

+


+

// bus for position control

+

c = Bus.control(s, 2);

+


+

// PLs with envir = t will read from topEnvironment Patterns/Streams (see below)

+

// other PLs will read slider values

+


+

// produce three minutes audio

+


+

p = Pfindur(180, PbindFx([

+

\instrument, \posPlay,

+

\sndBuf, b,

+

\out, a,

+


+

\dur, 1 / PL(\trigRate),

+

\granDur, Pkey(\dur) * PL(\overlap, envir: t),

+


+

\pos, c.subBus(0).asMap,

+

\posDev, c.subBus(1).asMap,

+


+

\rate, PL(\rate, envir: t),

+

\amp, PL(\amp),

+


+

\pan, PLseq([-1, 1]) * PL(\panMax),

+

// determining concrete envelope times (see Function attRel above)

+

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },

+


+

\fxOrder, PL(\fxOrder, envir: t),

+

// comb delay time needed for lag in case of no comb

+

\delayTime_comb, PL(\delayTime_comb, envir: t).collect { |x| d.dt = x; x },

+

\lag, Pfunc { |ev|  (ev.fxOrder.asArray.includes(1)).if { 0 }{ ev.delayTime_comb } },

+

\cleanupDelay, 0.3

+

],[

+

\fx, \comb,

+

\decayTime, PL(\decayTime_comb),

+

\delayTime, Pfunc { d.dt },

+

\mix, PL(\mix_comb),

+

\amp, PL(\amp_comb),

+

\cleanupDelay, Pkey(\decayTime)

+

],[

+

\fx, \delayMod,

+

\mix, PL(\mix_delayMod),

+

\amp, PL(\amp_delayMod),

+

\modIndM, PL(\modIndM_delayMod),

+

\modIndE, PL(\modIndE_delayMod),

+

\modFreq, PL(\modFreq_delayMod, envir: t),

+

\cleanupDelay, 0.1

+

    ]

+

));

+


+

// slider and player control

+

v = VarGui([

+

\att, [1, 200, \lin, 0, 6.97],

+

    \rel, [1, 200, \lin, 0, 8.96],

+

     \absEnv, [0, 1, \lin, 1, 0.0],

+

\trigRate, [1, 200, \lin, 0, 66.67],

+

\panMax, [0, 1, \lin, 0, 0.89],

+

    \amp, [0.0, 3, \lin, 0, 1],

+


+

\decayTime_comb, [0.001, 1, \lin, 0, 0.14086],

+

    \mix_comb, [0, 1, \lin, 0, 0.7],

+

    \amp_comb, [0, 3, \lin, 0, 1],

+


+

    \modIndE_delayMod, [-6, -2, \lin, 1, -5.0],

+

    \modIndM_delayMod, [0, 10, \lin, 0, 1.5],

+

    \mix_delayMod, [0, 1, \lin, 0, 0.88],

+

    \amp_delayMod, [0, 3, \lin, 0, 1.0]

+

],[

+

\sndBuf, b.bufnum,

+

    \posLo, [0, 0.99, \lin, 0, 0.2673],

+

   \posHi, [0, 0.99, \lin, 0, 0.3267],

+

    \posDevE, [-6, 0, \lin, 1, -5],

+

    \posDevM, [0.0, 10, \lin, 0, 6.7],

+


+

    \posRateE, [-4, 2, \lin, 1, -1],

+

    \posRateM, [0.1, 10, \lin, 0, 0.694],

+

  \type, [0, 3, \lin, 1, 3],

+

\out, c.index

+

], stream: p, synth: \pos

+

);

+


+

// gui look, color grouping

+

w = (

+

varColorGroups: (0..12).clumps([6, 3, 4]),

+

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

+

tryColumnNum: 1,

+

labelWidth: 130,

+

sliderWidth: 350,

+

sliderHeight: 18,

+

playerHeight: 18

+

);

+

)

+


+


+


+

// running the patch: evaluate this, 

+

// then in gui start position synth and PbindFx stream by pressing both green buttons

+


+

// Psegs are very practical for defining LFO-like behaviour in SC lang

+


+

(

+

x = Synth(\leakDC, [\out, 0, \in, a]);

+


+

~overlap = Pseg(PLwrand([0.05, 0.5, 1], [4, 1, 1].normalizeSum), Pwhite(0.5, 1), \sine);

+

~rate = Pseg(Pwhite(0.05, 1), Pexprand(0.01, 0.5), \sine);

+

~delayTime_comb = Pseg(PLseq([0.02, 0.002]), Pwhite(8, 15));

+

~fxOrder = [1, 2];

+

~modFreq_delayMod = Pseg(Pwhite(10, 150), Pwhite(2, 5), \sine);

+


+

v.performWithEnvir(\gui, w)

+

)

+


+

// while running the patch you can play with sliders and

+

// exchange the control patterns in top envir on the fly:

+


+

~delayTime_comb = 0.002;

+

~rate = 0.5;

+


+

~modFreq_delayMod = 70;

+

~modFreq_delayMod = 50;

+


+

~modFreq_delayMod = Pseg(Pwhite(10, 150), Pwhite(2, 5), \sine);

+


+


+

// experiment with fx sequencing: no - comb - (comb > delay modulation)

+


+

~fxOrder = PLseq([0, 1, [1, 2]]);

+

~fxOrder = Pstutter(Pwhite(1, 10), PLseq([0, 1, [1, 2]]));

+


+


+

// after stopping free leakDC synth

+


+

x.free

+


+


+


+

Part 2 – rectangular comb (FFT)

+


+

(

+

// rectangular comb, FFT processing per grain

+


+

SynthDef(\pv_rectComb, { |out = 0, in, numTeeth = 0, width = 0.5, phase = 0, mix = 1,

+

amp = 0.1|

+

    var chain, inSig = In.ar(in, 2), sig, bufSize = 512, inSigDelayed;

+


+

    chain = FFT({ LocalBuf(bufSize) } ! 2, inSig);

+

    chain = PV_RectComb(chain, numTeeth, phase, width);

+

sig = IFFT(chain) * amp;

+

// for mix we have to take into account FFT delay

+

inSigDelayed = DelayL.ar(

+

inSig,

+

0.1,

+

bufSize / s.sampleRate - (s.options.blockSize / s.sampleRate)

+

);

+

OffsetOut.ar(out, mix * sig + ((1 - mix) * inSigDelayed));

+

}).add;

+


+


+

t = topEnvironment.push;

+


+

a = Bus.audio(s, 2);

+

c = Bus.control(s, 2);

+


+

// play two loops with three "interludes" (see Task below)

+


+

p = Pfindur(178, PbindFx([

+

\instrument, \posPlay,

+

\sndBuf, b,

+

\out, a,

+


+

\dur, 1 / PL(\trigRate),

+

\granDur, Pkey(\dur) * PL(\overlap),

+


+

\pos, c.subBus(0).asMap,

+

\posDev, c.subBus(1).asMap,

+


+

\rate, PL(\rate, envir: t),

+

\amp, PL(\amp),

+


+

\pan, PLseq([-1, 1]) * PL(\panMax),

+

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },

+


+

\fxOrder, PLseq(\fxOrder, envir: t),

+

\cleanupDelay, 0.3

+

],[

+

\fx, \pv_rectComb,

+

\mix, PL(\mix_rectComb, envir: t),

+

\amp, PL(\amp_rectComb),

+


+

\numTeeth, PL(\numTeeth_rectComb, envir: t),

+

\width, PL(\width_rectComb, envir: t),

+

\phase, PL(\phase_rectComb, envir: t),

+


+

\cleanupDelay, 0.2

+

    ]

+

));

+


+

// timed pattern control for interludes, done with a Task

+


+

u = Task({

+

var times_1 = PLseq([10, 10, 20, 30]).iter;

+

var times_2 = PLseq([7, 8, 4, 0]).iter;

+


+

loop {

+

var tm;

+

~numTeeth_rectComb = 10;

+

~width_rectComb = 0.05;

+

~rate = 0.2;

+


+

tm = times_1.next;

+

tm.wait;

+


+

~numTeeth_rectComb = Pstutter(Pwhite(5, 30), Pwhite(3, 15));

+

~rate = Pstutter(Pwhite(50, 100), Pwhite(0.3, 0.5));

+

~width_rectComb = Pbrown(0.2, 0.5, 0.02);

+


+

tm = times_2.next;

+

tm.wait;

+

}

+

}.inEnvir(t));

+


+


+

v = VarGui([

+

// separate Environments for Task and PbindFx player

+

[],[

+

    \att, [1, 200, \lin, 0, 7],

+

    \rel, [1, 200, \lin, 0, 7],

+

     \absEnv, [0, 1, \lin, 1, 0],

+

\overlap, [0.05, 15, \lin, 0, 3.3],

+

\trigRate, [1, 200, \lin, 0, 54.73],

+


+

\panMax, [0, 1, \lin, 0, 0.99],

+

    \amp, [0.0, 3, \lin, 0, 0.15],

+


+

\amp_rectComb, [0, 10, \lin, 0, 5.6]

+

]],[

+

\sndBuf, b.bufnum,

+

    \posLo, [0, 0.99, \lin, 0, 0.2574],

+

   \posHi, [0, 0.99, \lin, 0, 0.3267],

+

    \posDevE, [-6, 0, \lin, 1, -5],

+

    \posDevM, [0.0, 10, \lin, 0, 1.9],

+


+

    \posRateE, [-4, 2, \lin, 1, -1],

+

    \posRateM, [0.1, 10, \lin, 0, 5.05],

+

  \type, [0, 3, \lin, 1, 3],

+

\out, c.index

+

], stream: [u, p], synth: \pos

+

);

+


+

w = (

+

varColorGroups: (0..7).clumps([7, 1]),

+

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

+

tryColumnNum: 1,

+

labelWidth: 130,

+

sliderWidth: 350,

+

sliderHeight: 18,

+

playerHeight: 18

+

);

+

)

+


+


+

// running the patch: evaluate this, 

+

// then in gui start position synth  

+

// start PbindFx / Task players together by pressing their green buttons with shift-click

+


+

// while running the patch check modified slider values and 

+

// consider pattern replacements as shown in part 1

+


+


+

(

+

x = Synth(\leakDC, [\out, 0, \in, a]);

+


+

~mix_rectComb = 0.75;

+

~phase_rectComb = Pstutter(Pwhite(1, 4), Pwhite(0.0, 0.6));

+

~fxOrder = [1];

+


+

v.performWithEnvir(\gui, w)

+

)

+


+


+

// after stopping free leakDC synth

+


+

x.free

+


+


+


+

Part 3 – resampling

+


+

(

+

// fx with "post-mix" amplitude

+


+

SynthDef(\resample, { |out = 0, in, mix = 0.5, amp = 1, resampleRate = 44100|

+

var sig, inSig = In.ar(in, 2);

+

sig = Latch.ar(inSig, Impulse.ar(resampleRate));

+

OffsetOut.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp);

+

}).add;

+


+

t = topEnvironment.push;

+


+

a = Bus.audio(s, 2);

+

c = Bus.control(s, 2);

+


+

p = Pfindur(90, PbindFx([

+

\instrument, \posPlay,

+

\sndBuf, b,

+

\out, a,

+


+

\dur, 1 / PL(\trigRate, envir: t),

+

\granDur, Pkey(\dur) * PL(\overlap),

+


+

\pos, c.subBus(0).asMap,

+

\posDev, c.subBus(1).asMap,

+


+

\rate, PL(\rate, envir: t),

+

\amp, PL(\amp),

+


+

\pan, PLseq([-1, 1]) * PL(\panMax),

+

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },

+


+

\fxOrder, PL(\fxOrder, envir: t),

+

\cleanupDelay, 0.3

+

],[

+

\fx, \resample,

+

\mix, PL(\mix_resample, envir: t),

+

\amp, PL(\amp_resample),

+

\resampleRate, PL(\resampleRate_resample, envir: t),

+

\cleanupDelay, 0.01

+

    ]

+

));

+


+

v = VarGui([

+

\att, [1, 200, \lin, 0, 4.98],

+

    \rel, [1, 200, \lin, 0, 4.98],

+

     \absEnv, [0, 1, \lin, 1, 1.0],

+

\overlap, [0.05, 15, \lin, 0, 3.5],

+


+

\panMax, [0, 1, \lin, 0, 0.93],

+

    \amp, [0.0, 3, \lin, 0, 0.15],

+


+

\amp_resample, [0, 3, \lin, 0, 2.7]

+

],[

+

\sndBuf, b.bufnum,

+

    \posLo, [0, 0.99, \lin, 0, 0.27],

+

   \posHi, [0, 0.99, \lin, 0, 0.34],

+

    \posDevE, [-6, 0, \lin, 1, -5],

+

    \posDevM, [0.0, 10, \lin, 0, 0.3],

+


+

    \posRateE, [-4, 2, \lin, 1, -1],

+

    \posRateM, [0.1, 10, \lin, 0, 3.5],

+

  \type, [0, 3, \lin, 1, 3],

+

\out, c.index

+

], stream: p, synth: \pos

+

);

+


+

w = (

+

varColorGroups: (0..6).clumps([6, 1]),

+

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

+

tryColumnNum: 1,

+

labelWidth: 110,

+

sliderWidth: 350,

+

sliderHeight: 18,

+

playerHeight: 18

+

);

+

)

+


+


+

// running the patch: evaluate this, 

+

// then in gui start position synth and PbindFx stream by pressing both green buttons

+


+

// while running the patch check modified slider values and 

+

// consider pattern replacements as shown in part 1

+


+

(

+

Synth(\leakDC, [\out, 0, \in, a]);

+


+

~resampleRate_resample = Pn(Plazy {

+

var freqs = (1..rrand(8, 15)) * rrand(100, 600);

+

Pseq(freqs, rrand(3, 6))

+

});

+


+

~fxOrder = 1;

+


+

~mix_resample = Pseg(

+

PLseq([0.0, 0.9]),

+

// see PS help, examples for "counted embedding"

+

PLseq([

+

PS(Pwhite(2.0, 3), Pwhite(1, 2)),

+

PS(Pwhite(0.03, 0.05), Pwhite(50, 80))

+

]),

+

\step

+

);

+


+

// random quarter tone row

+

~rate = Pseg(PLshuf((-24..24)/2).midiratio, Pwhite(0.2, 2), \step);

+


+

~trigRate = Pseg(Pwhite(40, 120), Pwhite(0.5, 2), \sine);

+


+

v.performWithEnvir(\gui, w)

+

)

+


+

// after stopping free leakDC synth

+


+

x.free

+


+


+

Part 4 – spectral complements (FFT)

+


+

(

+

// emphasizes / supresses bin range and / or complement

+

// uses pseudo ugens PV_BinRange and PV_BinGap

+


+

// freqLo and freqHi define the range

+

// rangeMul is the multiplier for the selected band

+

// compMul is the multiplier for the rest of the spectrum

+


+

SynthDef(\pv_binRangeBal, { |out = 0, in, freqLo = 80, freqHi = 300, rangeMul = 1, compMul = 1, mix = 1|

+

var chain, chain_range, chain_comp, inSig = In.ar(in, 2), sig, bufSize = 2048,

+

lo, hi, binRange, binNum, inSigDelayed;

+


+

binRange = s.sampleRate / bufSize;

+

binNum = (freqHi - freqLo / binRange).round;

+

lo = (freqLo / binRange).round;

+

hi = (freqHi / binRange).round;

+


+

chain = FFT({ LocalBuf(bufSize) } ! 2, inSig);

+

chain_range = PV_BinRange(chain, lo, hi);

+

chain_comp = PV_BinGap(chain, lo, hi);

+


+

sig = IFFT(chain_range) * rangeMul + (IFFT(chain_comp) * compMul);

+

inSigDelayed = DelayL.ar(

+

inSig,

+

0.1,

+

bufSize / s.sampleRate - (s.options.blockSize / s.sampleRate)

+

);

+

OffsetOut.ar(out, mix * sig + ((1 - mix) * inSigDelayed));

+

}).add;

+


+


+

t = topEnvironment.push;

+


+

a = Bus.audio(s, 2);

+

c = Bus.control(s, 2);

+


+

p = Pfindur(200, PbindFx([

+

\instrument, \posPlay,

+

\sndBuf, b,

+

\out, a,

+


+

\dur, 1 / PL(\trigRate),

+

\granDur, Pkey(\dur) * PL(\overlap),

+


+

\pos, c.subBus(0).asMap,

+

\posDev, c.subBus(1).asMap,

+


+

\rate, PL(\rate, envir: t),

+

\amp, PL(\amp),

+


+

\pan, PLseq([-1, 1]) * PL(\panMax),

+

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },

+


+

\fxOrder, PL(\fxOrder, envir: t),

+

\cleanupDelay, 0.3

+

],[

+

\fx, \pv_binRangeBal,

+

\mix, PL(\mix_pv_binRangeBal),

+


+

\freqLo, PL(\freqLo_pv_binRangeBal, envir: t),

+

\freqHi, Pkey(\freqLo) + PL(\freqRange_pv_binRangeBal, envir: t),

+


+

// multipliers for spectral band and complement are given as tupel 'muls'

+

\muls, PL(\muls_pv_binRangeBal, envir: t),

+

\rangeMul, Pkey(\muls).collect(_[0]),

+

\compMul, Pkey(\muls).collect(_[1]),

+


+

\cleanupDelay, 0.2

+

    ]

+

));

+


+

v = VarGui([

+

\att, [1, 200, \lin, 0, 3],

+

    \rel, [1, 200, \lin, 0, 3],

+

     \absEnv, [0, 1, \lin, 1, 0],

+


+

    \overlap, [0.1, 2, \lin, 0, 1.45],

+

\trigRate, [1, 200, \lin, 0, 118.41],

+


+

\panMax, [0, 1, \lin, 0, 0.88],

+

    \amp, [0.0, 3, \lin, 0, 0.9],

+


+

\mix_pv_binRangeBal, [0, 1, \lin, 0, 1]

+

],[

+

\sndBuf, b.bufnum,

+

    \posLo, [0, 0.99, \lin, 0, 0.21],

+

   \posHi, [0, 0.99, \lin, 0, 0.34],

+

    \posDevE, [-6, 0, \lin, 1, -5],

+

    \posDevM, [0.0, 10, \lin, 0, 1.0],

+


+

    \posRateE, [-4, 2, \lin, 1, -2],

+

    \posRateM, [0.1, 10, \lin, 0, 2.278],

+

  \type, [0, 3, \lin, 1, 3],

+


+

\out, c.index

+

], stream: p, synth: \pos

+

);

+


+

w = (

+

varColorGroups: (0..7).clumps([7, 1]),

+

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

+

tryColumnNum: 1,

+

labelWidth: 130,

+

sliderWidth: 350,

+

sliderHeight: 18,

+

playerHeight: 18

+

);

+

)

+


+


+

// running the patch: evaluate this, 

+

// then in gui start position synth and PbindFx stream by pressing both green buttons

+


+

// while running the patch check modified slider values and 

+

// consider pattern replacements as shown in part 1

+


+

(

+

Synth(\leakDC, [\out, 0, \in, a]);

+


+

~fxOrder = 1;

+


+

// double source grain in octave distance

+

~rate = [0.15, 0.3];

+


+

// pattern for lower bound of spectral band

+

~freqLo_pv_binRangeBal = Pn(Plazy {

+

var x = { exprand(200, 4000)  } ! rrand(2, 4);

+

Pseq(x, rrand(5, 15));

+

});

+


+

~freqRange_pv_binRangeBal = 2500;

+


+

// this is a bit more complicated

+

// polyrhythm for multipliers of band and complement

+

// it becomes more clear when applying trace to the following patterns

+


+

~zeroOneSeqTupleStream = PLshufn([

+

[[0, 1, 1], [0, 0, 1, 1]],

+

[[0, 1, 1], [0, 1, 1, 1]],

+

[[0, 0, 1], [0, 0, 1, 1]],

+

[[0, 0, 1], [0, 1, 1, 1]],

+

]).iter;

+


+

~muls_pv_binRangeBal = Pn(Plazy ({

+

var tuple = ~zeroOneSeqTupleStream.next;

+

if (0.5.coin) { tuple = tuple.reverse };

+

if (0.5.coin) {

+

tuple[0] = tuple[0].reverse;

+

tuple[1] = tuple[1].reverse;

+

};

+

Pfinval([500, 400, 300].choose, Ptuple([

+

PLseq(tuple[0]),

+

PLseq(tuple[1])

+

]))

+

}.inEnvir));

+


+

v.performWithEnvir(\gui, w)

+

)

+


+

// after stopping free leakDC synth

+


+

x.free

+


+


+

Part 5 – frequency shift with feedback

+


+

(

+

// frequency shift with feedback

+

// "post-mix" fx amplitude

+


+

// fx with feedback obviously needs envelope !

+


+

SynthDef(\freqShift, { |out = 0, in, mix = 1, freq = 0, fbAmp = 0,

+

granDur = 0.1, att = 0.01, rel = 0.01, amp = 0.1|

+

var inSig = In.ar(in, 2), sig, fb, env;

+

sig = inSig + (fbAmp * LocalIn.ar(2));

+

sig = Limiter.ar(sig);

+

sig = FreqShift.ar(sig, freq);

+

LocalOut.ar(sig);

+


+

sig = LPF.ar(sig, 10000);

+

// delayed attack is functioning as additional feedback control

+

env = EnvGen.ar(Env([0, 0, 1, 1, 0], [0.01, att, granDur - att - rel, rel]), doneAction: 2);

+

OffsetOut.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp * env);

+

}).add;

+


+

t = topEnvironment.push;

+


+

a = Bus.audio(s, 2);

+

c = Bus.control(s, 2);

+


+

p = PbindFx([

+

\instrument, \posPlay,

+

\sndBuf, b,

+

\out, a,

+


+

\dur, 1 / PL(\trigRate),

+

\granDur, Pkey(\dur) * PL(\overlap),

+


+

\pos, c.subBus(0).asMap,

+

\posDev, c.subBus(1).asMap,

+


+

\rate, PL(\rate, envir: t),

+

\amp, PL(\amp),

+


+

\pan, PLseq([-1, 1]) * PL(\panMax),

+

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },

+


+

// envelope data to be shared with fx

+

\do, Ptuple([Pkey(\granDur), Pkey(\att), Pkey(\rel)])

+

.collect { |x|  t[\share] =  x },

+


+

\fxOrder, PL(\fxOrder, envir: t),

+

+

\cleanupDelay, Pfunc { |ev|  max(0.2, ev[\granDur] - ev[\dur]) }

+

],[

+

\fx, \freqShift,

+

\mix, PL(\mix_freqShift),

+

\amp, PL(\amp_freqShift),

+

[\granDur, \att, \rel], Pfunc { t[\share] },

+

\granDur, Pkey(\granDur) * 0.9,

+

\freq, PL(\freq_freqShift, envir: t),

+

\fbAmp, PL(\fbAmp_freqShift, envir: t),

+


+

\cleanupDelay, 0.5

+

    ]

+

);

+


+


+

v = VarGui([

+

\att, [1, 200, \lin, 0, 3],

+

    \rel, [1, 200, \lin, 0, 7],

+

     \absEnv, [0, 1, \lin, 1, 1.0],

+


+

    \overlap, [0.05, 15, \lin, 0, 6.927],

+

\trigRate, [1, 200, \lin, 0, 24.88],

+


+

\panMax, [0, 1, \lin, 0, 0.84],

+

    \amp, [0.0, 1, \lin, 0, 0.06],

+


+

    \mix_freqShift, [0, 1, \lin, 0, 0.54],

+

    \amp_freqShift, [0.1, 2, \lin, 0, 0.3]

+

],[

+

\sndBuf, b.bufnum,

+

    \posLo, [0, 0.99, \lin, 0, 0.21],

+

   \posHi, [0, 0.99, \lin, 0, 0.34],

+

    \posDevE, [-6, 0, \lin, 1, -5],

+

    \posDevM, [0.0, 10, \lin, 0, 0.5],

+


+

    \posRateE, [-4, 2, \lin, 1, 0],

+

    \posRateM, [0.1, 10, \lin, 0, 1.486],

+

  \type, [0, 3, \lin, 1, 3],

+


+

\out, c.index

+

], stream: p, synth: \pos

+

);

+


+


+

w = (

+

varColorGroups: (0..8).clumps([7, 2]),

+

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

+

tryColumnNum: 1,

+

labelWidth: 130,

+

sliderWidth: 350,

+

sliderHeight: 18,

+

playerHeight: 18

+

);

+

)

+


+


+

// running the patch: evaluate this, 

+

// then in gui start position synth and PbindFx stream by pressing both green buttons

+


+

// while running the patch check modified slider values and 

+

// consider pattern replacements as shown in part 1

+


+

(

+

Synth(\leakDC, [\out, 0, \in, a]);

+


+

~freq_freqShift = Pstutter(Pwhite(10, 50), Pwhite(-200, 50, 1));

+

~fbAmp_freqShift = Pstutter(Pstutter(Pwhite(2, 5), Pwhite(1, 2)), PLseq([1, -2]));

+

~fxOrder = [1];

+


+

~rate = Pn(Plazy {

+

Pseries(exprand(0.3, 0.7), rrand(0.02, 0.1), rrand(5, 20));

+

}) * 0.8;  

+


+

v.performWithEnvir(\gui, w)

+

)

+


+

// after stopping free leakDC synth

+


+

x.free

+


+


+

Part 6 – band pass

+


+

(

+

// band pass

+

// "post-mix" fx amplitude

+


+

SynthDef(\bpf, { |out = 0, in, freq = 440, rq = 0.1, amp = 1, mix = 1|

+

var sig, inSig = In.ar(in, 2);

+

sig = BPF.ar(inSig, freq, rq);

+

OffsetOut.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp);

+

}).add;

+


+

// gain as fx for better control of balancing when sequencing grains with and without band pass

+


+

SynthDef(\gain, { |out = 0, in, amp = 1|

+

var inSig = In.ar(in, 2);

+

OffsetOut.ar(out, inSig * amp);

+

}).add;

+


+


+

t = topEnvironment.push;

+


+

a = Bus.audio(s, 2);

+

c = Bus.control(s, 2);

+


+

// duration defined by 'rate' with Pfinval

+

p = PbindFx([

+

\instrument, \posPlay,

+

\sndBuf, b,

+

\out, a,

+


+

\dur, 1 / PL(\trigRate),

+

\granDur, Pkey(\dur) * PL(\overlap),

+


+

\pos, c.subBus(0).asMap,

+

\posDev, c.subBus(1).asMap,

+


+

\rate, PL(\rate, 1, envir: t),

+

\amp, PL(\amp),

+


+

\pan, PLseq([-1, 1]) * PL(\panMax),

+

[\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) },

+


+

\fxOrder, PL(\fxOrder, envir: t),

+

\cleanupDelay, 0.3

+

],[

+

\fx, \gain,

+

\amp, PL(\amp_gain),

+


+

\cleanupDelay, 0.01

+

],[

+

\fx, \bpf,

+

\freq, PL(\freq_bpf, envir: t),

+

\rq, PL(\rq_bpf),

+

\mix, PL(\mix_bpf),

+

\amp, PL(\amp_bpf),

+


+

\cleanupDelay, 0.01

+

]

+

);

+


+

v = VarGui([

+

    \att, [1, 200, \lin, 0, 4.98],

+

    \rel, [1, 200, \lin, 0, 4.98],

+

     \absEnv, [0, 1, \lin, 1, 1.0],

+

     \overlap, [0.05, 15, \lin, 0, 6.7775],

+


+

\trigRate, [1, 200, \lin, 0, 176.12],

+

\panMax, [0, 1, \lin, 0, 0.83],

+

    \amp, [0.0, 3, \lin, 0, 0.75],

+


+

    \amp_gain, [0.0, 3, \lin, 0, 0.36],

+


+

    \rq_bpf, [0.01, 1, \lin, 0, 0.0397],

+

    \mix_bpf, [0, 1, \lin, 0, 0.91],

+

    \amp_bpf, [0, 1, \lin, 0, 2.7],

+

],[

+

\sndBuf, b.bufnum,

+

    \posLo, [0, 0.99, \lin, 0, 0.2574],

+

   \posHi, [0, 0.99, \lin, 0, 0.4059],

+

    \posDevE, [-6, 0, \lin, 1, -4],

+

    \posDevM, [0.0, 10, \lin, 0, 1.0],

+


+

    \posRateE, [-4, 2, \lin, 1, -1],

+

    \posRateM, [0.1, 10, \lin, 0, 0.892],

+

  \type, [0, 3, \lin, 1, 3],

+

\out, c.index

+

], stream: p, synth: \pos

+

);

+


+

w = (

+

varColorGroups: (0..10).clumps([8, 3]),

+

synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]),

+

tryColumnNum: 1,

+

labelWidth: 130,

+

sliderWidth: 350,

+

sliderHeight: 18,

+

playerHeight: 18

+

);

+

)

+


+


+

// running the patch: evaluate this, 

+

// then in gui start position synth and PbindFx stream by pressing both green buttons

+


+

// while running the patch check modified slider values and 

+

// consider pattern replacements as shown in part 1

+


+

(

+

Synth(\leakDC, [\out, 0, \in, a]);

+


+

// "octave arpeggio" bandpass frequencies, interleaved (PS with repeats = 1)

+

// check other relations, repetition numbers and fxOrder sequences

+


+

~freq_bpf = PLseq([

+

PS(Pstutter(3, Pseg(Pwhite(200, 7000), Pwhite(0.5, 2))) * PLseq([0.5, 1, 2]), 1),

+

PS(Pstutter(2, Pseg(Pwhite(200, 7000), Pwhite(0.5, 2))) * PLseq([0.5, 1, 2]), 1)

+

]);

+


+

// start with 1 ensures that we don't have filter only in single channel

+

~fxOrder = PLseq([1, 2, 2, 1]);

+


+

// development by ascending rate "chords"

+


+

~rate = Pseq([

+

Pfinval(5582,

+

Pstutter(

+

PLseq([120, 80, 80]),

+

Pexprand(0.3, 1.5).clump(PLshufn((1..3)) * 2 + 1)

+

).flatten

+

),

+

Pfinval(5582,

+

Pstutter(

+

PLseq([120, 80, 80]),

+

Pexprand(0.3, 0.6).clump(PLshufn((1..3)) * 2 + 1)

+

).flatten

+

),

+

Pfinval(15000,

+

Pstutter(

+

PLseq([120, 80, 80]),

+

Pexprand(0.1, 0.2).clump(PLshufn((1..3)) * 2 + 1)

+

).flatten

+

)

+

]);

+


+

v.performWithEnvir(\gui, w)

+

)

+


+

// after stopping free leakDC synth

+


+

x.free

+


+


+


+


+ + diff --git a/Help/miSCellaneous.html b/Help/miSCellaneous.html new file mode 100644 index 0000000..145f54c --- /dev/null +++ b/Help/miSCellaneous.html @@ -0,0 +1,609 @@ + + + + + + + + + + + +

miSCellaneous a library of SuperCollider extensions (c) 2009-2020 Daniel Mayer 

+


+

+


+

+

Version 0.24 contains these class and help files (SCDoc and old HTML system):

+


+

+


+

+

1.) Guide Introduction to miSCellaneous: recommended starting point.

+


+

+

2.) VarGui:  a slider / player gui to set envir variables and synth controllers and play synths, 

+

event patterns and tasks, see also VarGui shortcut builds.

+

HS with VarGui, a specific tutorial about the combination of HS family and VarGui.

+


+

+

3.) General tutorials: Event patterns and LFOs contains an overview of

+

related LFO-control setups with event patterns. Event patterns and Functions 

+

is treating requirements of the control of EventStreamPlayers with VarGui.

+

Event patterns and array args focusses on passing arrays to synths with patterns.

+

enum is a general enumeration method suited for many combinatorial problems such as 

+

listing subsets, partitions of integers, searching for paths within graphs etc.

+


+

+

4.) PLx suite, dynamic scope variants of common Pattern classes for convenient replacement. 

+

Can be used for shorter writing of Pbinds to be played with VarGui and / or live coding, 

+

see PLx and live coding with Strings. PLbindef and PLbindefPar as subclasses of Pdef 

+

allow replacement of key streams in shortcut pseudomethod syntax.

+

+

5.) PSx stream patterns, Pattern variants that have a state and can remember their last values. 

+

Can be used for recording streams and event streams (PS),

+

data sharing between event streams (PSdup), comfortable defining of subpatterns

+

with counted embedding, defining of recursive (event) sequences (PSrecur) and building

+

loops on given Patterns/Streams with a variety of options, also for live interaction (PSloop).

+


+

+

6.) Event pattern classes for use with effects: PbindFx can handle arbitrary effect graphs per event, 

+

PmonoPar and PpolyPar follow the Pmono paradigm. The tutorial kitchen studies contains 

+

the documented source code of a fixed media piece using PbindFx for granulation.

+

+

7.) Further Pattern classes: PlaceAll, Pshufn, PsymNilSafe.

+

PSPdiv is a dynamic multi-layer pulse divider based on Pspawner.

+


+

+

8.) Buffer Granulation, a tutorial covering different approaches of implementing

+

this synthesis method in SC (server versus language control, mixed forms),

+

examples with VarGui, language-driven control with PLx suite patterns.

+

The tutorial Live Granulation summarizes direct options without explicit use of a buffer.

+

+

9.) A family of classes for the use of synth values in Pbind-like objects.

+

Take Working with HS and HSpar as a starting point, see also

+

HS, PHS, PHSuse, HSpar, PHSpar, PHSparUse, PHSplayer, PHSparPlayer, PHSusePlayer.

+

+

10.) EventShortcuts, a class for user-defined keywords for events and event patterns.

+

The tutorial Other event and pattern shortcuts collects some further abbreviations,

+

e.g. functional reference within events and event patterns, similar to Pkey.

+

+

11.) An implementation of Xenakis' Sieves as class and pattern family, see

+

Sieves and Psieve patterns for an overview and examples.

+

+

12.) FFT pseudo ugens for defining ranges by bin index: PV_BinRange and PV_BinGap.

+

+

13.) Smooth Clipping and Folding, a suite of pseudo ugens.

+


+

+

14.) DX suite, pseudo ugens for crossfaded mixing and fanning according to demand-rate control.

+

+

15.) Idev suite, patterns and drate ugen searching for numbers with integer distance from a 

+

source pattern / signal.

+

+

16.) Nonlinear dynamics: Fb1 for single sample feedback / feedforward and GFIS for 

+

generalized functional iteration synthesis. Fb1_ODE for ordinary differential equation integration.

+

+

17.) ZeroXBufWr, ZeroXBufRd, TZeroXBufRd: pseudo ugens for analysis of zero crossings and 

+

playing sequences of segments between them with demand rate control. Dwalk, a 

+

pseudo demand rate ugen that supports specific synthesis options with ZeroXBufRd.

+

+


+

+

Many of the examples here are using patterns, resp. event patterns but do not cover their basic concepts. 

+

For a detailled description of SC's sequencing capabilities see James Harkins' Practical Guide to Patterns 

+

(PG_01_Introduction), the tutorial Streams-Patterns-Events (1-7) and the Pattern help files 

+

(Pattern, Pbind and the type-specific ones).

+


+

+

VarGui handles namespace separation by using different Environments.

+

So a gui for control of parametrized families of different types of objects can be built on the fly

+

(e.g. a number of EventStreamPlayers from a single Pbind definition with snippets of functional code,

+

a number of Function plots from a single parametric function definition etc.).

+

See Environment and Event helpfiles for the underlying concepts and Event patterns and Functions and 

+

PLx suite for their application to event patterns resp. EventStreamPlayers.

+


+

+


+

+

Requirements

+


+

+

At least SuperCollider version 3.6 but newer versions are recommended, 

+

with 3.6 you'd have to use Qt GUI kit, which is the only option anyway from 3.7 onwards. 

+

Unable to test on 3.5 anymore, code might work, anyway old help is still supported.

+


+

+

If you still use Cocoa or SwingOSC with these old SC versions,

+

you can take miSCellaneous 0.15b and add classes and help files from a 

+

newer version of miSCellaneous.

+


+

+

For using VarGui with EZSmoothSlider and EZRoundSlider you would need to 

+

install Wouter Snoei's wslib Quark, one buffer granulation

+

example using Wavesets depends on Alberto de Campo's Wavesets Quark. 

+


+

+

I tested examples on SC versions 3.6 - 3.11, 

+

on OS 10.8 - 10.13, Ubuntu 12.04 and Windows 7, 10; 

+

though not every platform / OS version / SC version combination.

+


+

+


+

+

SCDoc issues (SC 3.10)

+


+

+

With SC 3.10.0 - SC 3.10.2 there are issues with evaluating code in the 

+

help file examples (double evaluation).

+

For these versions you'd rather copy the help file code into scd files and run it there. 

+

Double evaluation has been fixed with 3.10.3 (August 2019).

+

With SC updates to 3.10 it might happen that help file examples are invisible.

+

In that case delete the Help folder which resides in one of these places, depending

+

where you have installed:

+


+

+

Platform.userAppSupportDir;

+

Platform.systemAppSupportDir;

+


+

+

Then restart SC.

+


+

+


+

+

Installation

+


+

+

By version 0.17 you can install either via the quarks extension management system or a 

+

downloaded zip from GitHub or my website. 

+

Both methods shouldn't be combined, e.g. if you have already done a manual install 

+

before by placing a miSCellaneous folder in the Extensions folder, then remove 

+

it from there before the quarks install.

+


+

+

1.) Installation via Quarks

+


+

+

This requires a SC version >= 3.7, see the recommended ways to install here:

+


+

+

http://doc.sccode.org/Guides/UsingQuarks.html

+

https://github.com/supercollider-quarks/quarks

+


+

+

After a install of a newer version of miSCellaneous do a SCDoc update by

+


+

+

SCDoc.indexAllDocuments(true);

+


+

+


+

+

2.) Manual installation from a downloaded zip

+


+

+

You can download the newest version from 

+


+

+

https://github.com/dkmayer/miSCellaneous_lib

+


+

+

and the newest and all previous versions from here:

+


+

+

http://daniel-mayer.at

+


+

+


+

+

Copy the miSCellaneous folder into the Extensions folder and recompile 

+

the class library or (re-)start SC. If the Extensions folder doesn't exist you'd probably 

+

have to create it yourself. Check SC help for platform-specific conventions 

+

(or changes) of extension places.

+


+

+

Typical user-specific extension directories:

+


+

+

OSX: ~/Library/Application Support/SuperCollider/Extensions/

+

Linux: ~/.local/share/SuperCollider/Extensions/

+


+

+

Typical system-wide extension directories:

+


+

+

OSX: /Library/Application Support/SuperCollider/Extensions/

+

Linux: /usr/share/SuperCollider/Extensions/

+


+

+

You can check Extension directories with

+


+

+

Platform.userExtensionDir;

+

Platform.systemExtensionDir;

+


+

+

On Windows see the README file of SC for the recommended extensions path.

+

On Windows and OSX you might have to make the concerned folders visible,

+

if they aren't already. The miSCellaneous folder should be placed directly

+

into the Extensions folder, not into a subfolder. 

+


+

+

After a install of a newer version of miSCellaneous do a SCDoc update by

+


+

+

SCDoc.indexAllDocuments(true);

+


+

+

License

+


+

+

miSCellaneous is distributed under the GNU Public License in accordance with SuperCollider.

+

You should have received a copy of the GNU General Public License along

+

with this program; if not, write to the Free Software Foundation, Inc.,

+

51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

+


+

+


+

+

Contact

+


+

+

Email: daniel-mayer@email.de

+

URL:  http://daniel-mayer.at

+


+

+


+

+

Credits

+


+

+

Many thanks to James McCartney for developing SuperCollider, 

+

Alberto de Campo for showing me its capabilities,

+

Wouter Snoei for his nice slider classes in wslib, 

+

Nathaniel Virgo for his suggestions for feedback, 

+

David Pirrò for his hints concerning ODE integration,

+

James Harkins for his remarks on many things and 

+

the whole community for contributions and hints !

+


+

+


+

+

History

+


+

+

v0.24 2020-07-08

+


+

+

.) Dwalk, demand rate ugen that supports specific synthesis options with ZeroXBufRd

+

.) New examples #9, #10 in ZeroXBufRd help with applications of Dwalk

+

.) New examples #8, #9 in TZeroXBufRd help (pulsar synthesis)

+

.) Bugfix related to PV_BinRange and PV_BinGap, usage in FFT chains enabled

+

+


+

+

v0.23 2020-04-19

+


+

+

.) ZeroXBufWr, ZeroXBufRd, TZeroXBufRd: playing half wavesets with demand rate control

+

.) Remove doubled method lincurve_3_9, which caused a warning without harm

+

.) AddEventTypes_PbindFx: use store instead of writeDefFile in private methods

+

.) Minor fixes in help files

+

+


+

+

v0.22 2019-08-14

+


+

+

.) Fb1_ODE and related: ordinary differential equation integration

+

.) Fb1: now also runs at control rate, Fb1.new is equivalent to Fb1.ar

+

.) Fb1: minor change of forced graph ordering

+

.) PbindFx: accept instruments/fxs given as Strings in pbindData key/value pairs

+

.) Minor fixes in help files

+

+


+

+

v0.21 2018-07-25

+


+

+

.) Fb1: single sample feedback / feedforward

+

.) GFIS: generalized functional iteration synthesis

+

.) Redefined variables in Sieve, PSrecur and PSPdiv to enable inlining

+

.) Minor fixes in help files

+

+


+

+

v0.20 2018-05-21

+


+

+

.) Idev suite, search for numbers with integer distance from a source

+

.) Bug fix in Integer method lcmByFactors

+

.) Minor fixes in help files

+

+


+

+

v0.19 2017-11-22

+


+

+

.) DX, a suite of pseudo ugens for crossfaded mixing and fanning 

+

.) Minor adaption of PbindFx event type

+

.) Minor fixes in help files

+

+


+

+

v0.18 2017-09-26

+


+

+

.) PLbindef / PLbindefPar: new features and implementation

+

.) Minor fixes in PV_BinGap and PV_BinRange

+

.) Minor fixes in help files

+


+

+


+

+

v0.17 2017-08-21

+


+

+

.) Smooth Clipping and Folding, a suite of pseudo ugens

+

.) Added MIDI learning feature for VarGui slider control

+

.) Platform.miSCellaneousDirs, search for miSCellaneous folder

+

.) Adapted PV_BinRange and PV_BinGap to do multichannel expansion

+

.) Exchanged graphic examples in VarGui help (Qt now)

+

.) Minor fixes in help files

+

.) First version released via GitHub and as Quark

+


+

+


+

+

v0.16 2017-03-03

+


+

+

.) Added PSPdiv, a dynamic multi-layer pulse divider based on Pspawner

+

.) Added tutorial: Live Granulation

+

.) Dropped miSCellaneous' b-branch: Cocoa and SwingOSC no longer supported

+

.) Replaced OSCpathResponder by OSCFunc in classes VarGuiPlayerSection, 

+

  HelpSynth and HelpSynthPar

+

.) Minor fixes in help files

+


+

+


+

+

v0.15 2017-01-04

+


+

+

.) Guide "Introduction to miSCellaneous"

+

.) kitchen studies: source code of the corresponding fixed media piece

+

.) PV_BinRange, PV_BinGap: FFT pseudo ugens for defining bin ranges

+

.) PbindFx help: new example 4a for defining implicit fx parallelism 

+

.) Minor fixes (help files, typos)

+

.) Complemeting history information (tutorials)

+


+

+


+

+

v0.14 2016-08-25

+


+

+

.) Sieves and Psieve patterns, an implementation of Xenakis' sieves

+

.) PLbindef / PLbindefPar: replacement of key streams in pseudomethod syntax

+

.) Tutorial: PLx and live coding with Strings

+

.) PsymNilSafe, method symplay: avoid hangs with nil input

+

.) EventShortcuts: reworking of replacement, new method eventShortcuts

+

.) PbindFx: reworking of bus match check

+

.) Minor fixes (help files, typos)

+


+

+


+

+

v0.13 2015-10-28

+


+

+

.) PbindFx: Event pattern class for effect handling on per-event base

+

.) Minor fixes (help files, typos)

+


+

+


+

+

v0.12 2015-05-03

+


+

+

.) PmonoPar, PpolyPar: 

+

  Event pattern classes for parallel setting streams and effect handling

+

.) PSloop: Pattern to derive loops from a given Pattern

+

.) Reworked replacing options of PL list patterns: cutItems arg

+

.) PLn: PLx Pattern for replacing with protecting periods

+

.) Added PLxrand, PLx version of Pxrand 

+

.) Changed implementations of PStream (PS) and PSrecur, 

+

  now the MemoRoutine isn't instantiated before embedding,

+

  this enables use of PSx patterns with VarGui

+

.) Fixed a bug in PL list patterns (PL_PproxyNth) that caused too early

+

  replacements in certain cases

+

.) Added method bufSeq for PStream

+

.) Added missing help file of PLtuple

+

.) Minor fixes (help files, typos)

+


+

+


+

+

v0.11 2015-02-02

+


+

+

.) Tutorial: Event patterns and array args

+

+


+

+

v0.10 2014-10-06

+


+

+

.) Split into branches 0.10a (3.7 onwards) and 0.10b (from 3.4 up to 3.6.x),

+

  with SC 3.7 SwingOSC and Cocoa aren't supported anymore

+

.) Class EventShortcuts and tutorial "Other event and pattern shortcuts"

+


+

+


+

+

v0.9 2014-02-18

+


+

+

.) PSx stream patterns (classes and tutorial), based on class MemoRoutine

+

.) Buffer Granulation Tutorial: new examples (1c - 1e, 3d)

+

.) VarGui save dialog fix (was broken with 3.6)

+

.) Minor fixes (help files, typos)

+


+

+


+

+

v0.8 2013-05-05

+


+

+

    .) Enumeration tutorial: method enum

+


+

+


+

+

v0.7.1 2013-04-28

+


+

+

    .) Fix in PLseries and PLgeom

+


+

+


+

+

v0.7 2012-08-31

+


+

+

.) Buffer Granulation tutorial file

+

.) VarGui

+

.) Support of wslib slider classes EZSmoothSlider and EZRoundSlider

+

.) Color grouping options for synth and envir variable control

+

.) Adapting EZSlider to ControlSpec step size (fixes certain rounding and jitter issues)

+

.) Multiple slider handling with modifier keys: fixes and cleanup

+


+

+


+

+

v0.6 2012-05-19

+


+

+

.) PLx dynamic scope pattern suite

+

.) Implementation of a number of common Patterns as PLx classes

+

.) Tutorial PLx suite

+

.) Changing examples in some previous help files accordingly

+

.) Pstream, PlaceAll, Pshufn

+


+

+


+

+

v0.5 2012-03-18

+


+

+

.) VarGui shortcut build methods

+

.) Use of SynthDef metadata and global ControlSpecs

+

.) Tutorial VarGui shortcut builds

+

.) Automatic Pbind generation

+

.) Minor fixes in VarGui init procedure

+


+

+


+

+

v0.4 2011-12-27

+


+

+

.) VarGui support for HS family classes

+

.) Tutorial HS with VarGui

+

.) VarGui takes addAction as slider hook

+

.) Linux check (tested on Ubuntu):

+

.) Fixed system time issue in HS family

+

.) Specified VarGui appearance default parameters for platform and gui kit

+

.) Supports SCDoc, the new SC help system

+

.) Tutorial Event Patterns and Functions

+

.) Tutorial Event patterns and LFOs

+

.) Play methods of PHSx and PHSxPlayer now also take numbers as quant arg

+

.) Minor fixes in HS family

+


+

+


+

+

v0.4_beta 2011-08-18

+


+

+

.) VarGui relaunch:

+

.) Player section for Synths, EventStreamPlayers and Tasks 

+

.) Different player modes

+

.) Button colors and background colors reflecting playing states 

+

.) Handling groups of players and sliders with modifier keys

+

.) Player action by mouse down or up

+

.) Variables can be set in different environments

+

.) Latency setting, global and for synth player message bundling

+

.) GUI appearance customization in size, arrangement and color

+

.) Many other changes, e.g. arg conventions 

+

.) Private extension methods get prefix miSC_

+


+

+


+

+

v0.3 2010-10-21

+


+

+

.) VarGui:

+

.) Arrayed synth control supported

+

.) Slider update methods added

+

.) Save dialog now uses unified gui class Dialog

+

.) Again compiling with SC 3.3 and 3.3.1

+

+


+

+

v0.2 2010-09-18

+


+

+

.) Minor adaptions to SC 3.4

+

.) Fixed time shifting issue

+


+

+


+

+

v0.1 2009-11-24

+


+

+

.) VarGui, interface for envir variable and synth arg control

+

.) HS / HSpar etc.: classes for the use of synth values in Pbind-like objects

+

.) Tutorial Working with HS and HSpar

+


+

+


+

+ + diff --git a/HelpSource/Classes/Boolean.ext.schelp b/HelpSource/Classes/Boolean.ext.schelp new file mode 100644 index 0000000..75b9a9b --- /dev/null +++ b/HelpSource/Classes/Boolean.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_dispatchCollect + + diff --git a/HelpSource/Classes/Buffer.ext.schelp b/HelpSource/Classes/Buffer.ext.schelp new file mode 100755 index 0000000..f1b5eb4 --- /dev/null +++ b/HelpSource/Classes/Buffer.ext.schelp @@ -0,0 +1,16 @@ + +INSTANCEMETHODS:: + + +method:: adjustZeroXs +Sets all zero crossing positions of the receiver (the sound Buffer) to 0. In most cases this ensures smoother waveforms when using ZeroXBufRd / TZeroXBufRd. See link::Classes/ZeroXBufRd#Éx. 7:: and link::Classes/ZeroXBufWr#Éx. 2::, it works in analogy to ZeroXBufWr's flag adjustZeroXs set to 1. Asynchronous. + +argument::zeroXBuf +Buffer of zero crossings, assumed to be analysed with ZeroXBufWr. + +argument::action +Action function to be performed after the setting messages have been sent. Gets array of zero crossings as argument. + + + + diff --git a/HelpSource/Classes/Collection.ext.schelp b/HelpSource/Classes/Collection.ext.schelp new file mode 100644 index 0000000..5e91668 --- /dev/null +++ b/HelpSource/Classes/Collection.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_enumerateIndices, miSC_isGrouping, miSC_collectCopy, miSC_asSet + + diff --git a/HelpSource/Classes/Color.ext.schelp b/HelpSource/Classes/Color.ext.schelp new file mode 100644 index 0000000..7f69fba --- /dev/null +++ b/HelpSource/Classes/Color.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_exp, miSC_iplToGrey, miSC_dim + + diff --git a/HelpSource/Classes/ControlSpec.ext.schelp b/HelpSource/Classes/ControlSpec.ext.schelp new file mode 100644 index 0000000..d7f3efa --- /dev/null +++ b/HelpSource/Classes/ControlSpec.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_specAsArray + + diff --git a/HelpSource/Classes/DIdev.schelp b/HelpSource/Classes/DIdev.schelp new file mode 100755 index 0000000..41c48a9 --- /dev/null +++ b/HelpSource/Classes/DIdev.schelp @@ -0,0 +1,457 @@ +CLASS:: DIdev +summary:: pseudo drate ugen, searches for numbers with integer distance from a source signal, optionally avoiding repetitions within a span +categories:: Libraries>miSCellaneous>Idev suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/Idev_suite, Classes/PIdev, Classes/PLIdev + + +DESCRIPTION:: + + +DIdev / PIdef / PLIdev search for numbers with integer distance from a source signal / pattern up to a given deviation. Repetitions within a lookback span are avoided, DIdev / PIdef / PLIdev randomly choose from possible solutions. Intended for search within integer grids (pitches, indices etc.), however applications with non-integer sources are possible, see examples. + + +note:: +DIdev needs at least a SC version >= 3.7.0 for proper working. +:: + +note:: +It's the user's responsibility to pass a combination of deviation and lookback values that allows a possible choice, see examples. +:: + +note:: +In contrast to PIdev and PLIdev, DIdev needs to know maximum deviations (strong::minLoDev::, strong::maxHiDev::) beforehand. Together with strong::maxLookBack:: they determine multichannel sizes, so a relatively high number of ugens might be involved. +:: + + + +CLASSMETHODS:: + + +method::new + +Creates a new DIdev object. + + +argument::in +The source signal, integer distances are calculated from the value of this signal with each trigger. Can be demand rate or other ugen. + +argument::maxLookBack +Integer, the maximum lookback span. Fixed, defaults to 3. + +argument::minLoDev +Integer, the minimum low deviation (must not be exceeded by any strong::loDev:: value). Should be negative, cannot be modulated, defaults to -3. + +argument::maxHiDev +Integer, the maximum high deviation (must not be exceeded by any strong::hiDev:: value). Should be positive, cannot be modulated, defaults to 3. + +argument::lookBack +Determines the current lookback span for avoiding repetitions. Can be modulated (demand rate or other ugen, no ar) but must not exceed strong::maxLookBack::. If no value is passed, then strong::maxLookBack:: is taken. + +argument::loDev +Determines the current low deviation for the search. Can be modulated (demand rate or other ugen) but must not exceed strong::minLoDev::. If not specified, them strong::minLoDev:: is taken. + +argument::hiDev +Determines the current high deviation for the search. Can be modulated (demand rate or other ugen) but must not exceed strong::maxHiDev::. If not specified, then strong::maxHiDev:: is taken. + +argument::thr +Threshold for equality comparison. Can be modulated (demand rate or other ugen). Defaults to 1e-3. + +argument::length +Number of repeats. Defaults to inf. + + + +section::Examples + + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + + +anchor::Ex.1:: +subsection::Ex. 1: Basic usage: random choice within region without repetitions + +code:: +// constant source (72), max deviation +/- 3 +// no repetition within 5 pitches + +( +x = { + var trig = Impulse.ar(5); + var midi = Demand.ar(trig, 0, DIdev(72, 2, -1, 2)); + midi.poll(5, label: \midi); + SinOsc.ar(midi.midicps.lag(0.007)) ! 2 * 0.1 +}.play; +) + +x.release +:: + + + + +anchor::Ex.2:: +subsection::Ex. 2: Variable deviations and lookBack + +code:: +( +x = { + |loDev = -6, hiDev = 5, lookBack = 2| + var trig = Impulse.ar(5); + // define maxLookBack, minLoDev and maxHiDev + var midi = Demand.ar(trig, 0, DIdev(72, 11, -6, 5, lookBack, loDev, hiDev)); + midi.poll(5, label: \midi); + SinOsc.ar(midi.midicps.lag(0.007)) ! 2 * 0.1 +}.play; +) + + +// as lookBack equals 2, this defines a fixed sequence (up or down anyway) + +( +x.set(\loDev, -1); +x.set(\hiDev, 1) +) + + +// widen range + +( +x.set(\loDev, -6); +x.set(\hiDev, 5); +) + + +// force a twelve-tone row + +x.set(\lookBack, 11) + + +// contradictory input, lookBack 11 not possible within range, causes repetitions + +( +x.set(\loDev, -3); +x.set(\hiDev, 2) +) + +x.release +:: + + + + +anchor::Ex.3:: +subsection::Ex. 3: Moving source signal + +code:: +( +x = { + |loDev = -1, hiDev = 1, lookBack = 2| + var trig = Impulse.ar(7); + // define maxLookBack, minLoDev and maxHiDev + // source is rounded to integers here + var midi = Demand.ar(trig, 0, DIdev(SinOsc.ar(0.2, 0, 15, 82).round, 11, -6, 5, lookBack, loDev, hiDev)); + midi.poll(7, label: \midi); + SinOsc.ar(midi.midicps.lag(0.007)) ! 2 * 0.1 +}.play; +) + + +// widen range and increase lookBack + +( +x.set(\loDev, -6); +x.set(\hiDev, 5); +x.set(\lookBack, 10); +) + +x.release +:: + + + +anchor::Ex.4:: +subsection::Ex. 4: Dynamic deviation range and lookBack + +code:: +// lookBack and deviations coupled here +// maxLookBack, minLoDev and maxHiDev must be large enough + +( +x = { + var trig = Impulse.ar(7); + var dev = SinOsc.ar(0.1, -pi/2).range(1, 5); + var midi = Demand.ar(trig, 0, + DIdev(78, 10, -5, 5, + SinOsc.kr(0.1, -pi/2).range(1, 5).round.poll(label: \lookBack), + dev.neg.poll(label: \loDev), + dev.poll(label: \hiDev) + ) + ); + SinOsc.ar(midi.lag(0.007).midicps, 0, 0.1) ! 2 +}.play; +) + +x.release + + +// loDev and hiDev can be demand rate + +( +x = { + var trig = Impulse.ar(5); + var hiDev = Dseq([1, 10], inf); + var midi = Demand.ar(trig, 0, + DIdev(78, 10, -10, 10, + 1, + Dseq([-10, 5], inf), + Dseq([-5, 10], inf) + ) + ); + SinOsc.ar(midi.lag(0.007).midicps, 0, 0.1) ! 2 +}.play; +) + +x.release + + +// lookBack can also be demand rate + +( +x = { + var trig = Impulse.ar(5); + var hiDev = Dseq([1, 10], inf); + var midi = Demand.ar(trig, 0, + DIdev(70, 10, -15, 15, + Dstutter(4, Dseq([1, 3], inf)), + Dstutter(4, Dseq([-9, 7], inf)), + Dstutter(4, Dseq([-8, 10], inf)) + ) + ); + midi.poll(trig); + SinOsc.ar(midi.lag(0.007).midicps, 0, 0.1) ! 2 * EnvGen.ar(Env.asr(0.15)) +}.play; +) + +x.release +:: + + + + +anchor::Ex.5:: +subsection::Ex. 5: Non-integer source + +code:: +( +x = { + |lookBack = 3, thr = 1| + var trig = Impulse.ar(7); + // for a non-integer source it makes sense to take a sufficiently large threshold thr + var midi = Demand.ar(trig, 0, DIdev(SinOsc.ar(0.2, 0, 15, 82), 5, -6, 5, lookBack, thr: thr)); + midi.poll(7, label: \midi); + SinOsc.ar(midi.midicps.lag(0.007)) ! 2 * 0.1 +}.play; +) + +// close floats can occur here +x.set(\thr, 0.01) + +// not here +x.set(\thr, 2) + +x.release +:: + + + + +anchor::Ex.6:: +subsection::Ex. 6: Multichannel expansion + +code:: +// nothing especially implemented + +( +x = { + var trig = Impulse.ar(7); + var in = [0, 8.5]; + var maxLookBack = [1, 3]; + var loDev = [-1, -5]; + var hiDev = [1, 5]; + var midi = { |i| Demand.ar(trig, 0, + DIdev( + in[i] + SinOsc.ar(0.1, -pi/2).range(65, 85).round, + maxLookBack: maxLookBack[i], + loDev: loDev[i], + hiDev: hiDev[i] + ).dpoll(label: "midi_" ++ (i == 0).if { "lo" }{ "hi"}) + ) } ! 2; + SinOsc.ar(midi.lag(0.007).midicps, 0, 0.1) +}.play; +) + +x.release +:: + + + +anchor::Ex.7:: +subsection::Ex. 7: Application to other params: rhythm + +code:: +// if we have indexed data for whatever, we can slide over it +// prepare some rhythms in order +// use them for SynthDef + +( +~rhythmBase = [ + [1, 1], + [2, 1, 1], + [1, 1, 2], +].collect(_.normalizeSum); + +~rhythms = ~rhythmBase *.x [1, 2]; +~rhythmNum = ~rhythms.size; +~rhythms = ~rhythms.scramble; + +"rhythm types: ".postln; +~rhythms.do { |r, i| i.post; ": ".post; r.postln }; + +SynthDef(\sine_rhythm, { |out = 0, freq = 400, att = 0.01, rel = 0.1, gate = 1, amp = 0.2| + var trig, loDev = -1, hiDev = 1, sig, src, + rhy = ~rhythmNum.collect { |i| Dseq(~rhythms[i], 1) }; + + trig = TDuty.ar( + Dswitch(rhy, + // be careful not to exceed range of rhythm indices, which can result in server quit + Dpoll( + DIdev(SinOsc.ar(0.1).range(loDev.abs, ~rhythmNum - hiDev - 1).round, 2, loDev, hiDev), + 'rhythm type' + ) + ) * 0.3 + ); + src = SinOsc.ar(Demand.ar(trig, 0, LFDNoise3.ar([0.5, 3], [10, 15], [60, 85])).midicps); + sig = Decay2.ar(trig, att, rel) * src; + Out.ar(out, sig * EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2) * amp); +}).add; +) + +x = Synth(\sine_rhythm) + +x.release +:: + + +anchor::Ex.8:: +subsection::Ex. 8: Proof of concept + +code:: +( +// Function to check an array for repetitions within a maximum test span + +~repetitionCheck = { |array, maxTestSpan| + maxTestSpan.do { |i| + var result = (array.drop(i+1) - array).drop((i+1).neg).includes(0).not; + ("no repetitions within a span of " ++ (i+2).asString ++ " items: ").post; + result.postln; + } +}; + +// prepare buffer to store DIdev values + +n = 10000; // buffer size +r = 10000; // trigger rate +b = Buffer.alloc(s, n); +) + + +// run to store, wait until finished (a bit more than 1 second) +( +{ + var trig = Impulse.ar(r); + Demand.ar(trig, 0, Dbufwr(DIdev(Dbrown(0, 20, 0.3).round, 5, -7, 7), b, Dseries(0, 1, n), 0)); + Line.ar(dur: n / r + 0.1, doneAction: 2); + 0 +}.play; +) + +// move data to language + +b.loadToFloatArray(action: { |x| a = x.asInteger; "array filled \n".postln; }) + + +// no repetitions within a maximum span of 6 (maxLookBack was 5) + +~repetitionCheck.(a, 10); +:: + + +anchor::Ex.9:: +subsection::Ex. 9: Switching signals with DXMix + +code:: +// source of 10 stereo granulations: +// they differ in position movement, trigrate and rate + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + +( +x = { + var pos = { |i| SinOsc.ar(0.02, pi/5 * i).range(0.1, 0.8) } ! 10; + var sig; + + sig = pos.collect { |p, i| + TGrains.ar( + 2, + trigger: Impulse.ar(i * 10 + 30), + bufnum: b, + rate: LFDNoise3.ar(0.1).range(0.3, 1.5), + centerPos: p * BufDur.ir(b), + dur: 0.1, + pan: Dseq([-1, 1], inf) + ) + }; + // switch between stereo sources with DXMix + DXMix.ar( + Dpoll(DIdev(SinOsc.ar(0.1).range(2, 7).round, 2, -2, 1)), + `sig, + fadeMode: 1, + stepTime: 0.03, + fadeTime: 0.002 + ) * 5 +}.play +) + +x.release + + +// sine stereo sources + +( +x = { + var sig = (1..20).scramble.collect { |i| + SinOsc.ar( + [i, 20-i] * 100 * LFDNoise3.ar(0.1).range(0.97, 1.03), + 0, + 0.05 * LFDNoise3.ar(1 ! 2).range(0.1, 1) + ) + }; + DXMix.ar( + Dpoll(DIdev(SinOsc.ar(0.05).range(2, 17).round, 2, -2, 2)), + `sig, + fadeMode: 1, + stepTime: 0.03, + fadeTime: 0.002 + ) ; +}.play +) + +x.release +:: + diff --git a/HelpSource/Classes/DXEnvFan.schelp b/HelpSource/Classes/DXEnvFan.schelp new file mode 100644 index 0000000..050ebcd --- /dev/null +++ b/HelpSource/Classes/DXEnvFan.schelp @@ -0,0 +1,727 @@ +CLASS:: DXEnvFan +summary:: returns crossfade envelopes according to demand-rate control +categories:: Libraries>miSCellaneous>DX suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +DXEnvFan returns the multichannel envelope signal, which is used by DXMix / DXMixIn / DXFan / DXFanOut implicitely. It can be used as envelope and trigger at the same time, which leads to applications such as crossfading PlayBufs and different kinds of granulation, using a buffer or not. + +note:: +As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the link::Tutorials/DX_suite:: overview and go through the help file examples in this order: link::Classes/DXMix#Ex.1#DXMix:: - link::Classes/DXMixIn#Ex.1#DXMixIn:: - link::Classes/DXEnvFan#Ex.1#DXEnvFan:: - link::Classes/DXEnvFanOut#Ex.1#DXEnvFanOut:: - link::Classes/DXFan#Ex.1#DXFan:: - link::Classes/DXFanOut#Ex.1#DXFanOut::. Some general conventions are treated in detail in the following examples: fades and steps in link::Classes/DXMix#Ex.2#DXMix, Ex.2:: – width and offset arguments in link::Classes/DXMix#Ex.3#DXMix, Ex.3:: – multichannel expansion in link::Classes/DXMix#Ex.6#DXMix, Ex.6:: – crossfade types in link::Classes/DXEnvFan#Ex.1#Ex.1::. +:: + +note:: +PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice. +:: + +note:: +Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See link::Classes/DXMix#Ex.8#DXMix, Ex.8:: and link::Classes/DXEnvFan#Ex.2#Ex.2::, link::Classes/DXEnvFan#Ex.4#Ex.4:: for aspects of CPU demand. +:: + +note:: +In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See link::Classes/DXFan#Ex.4#DXFan, Ex.4:: for aspects of granulation with high trigger rates / short grain durations. +:: + +note:: +The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours. +:: + +subsection::Credits +Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz. + + + + +CLASSMETHODS:: + +method::ar + +argument::out +Determines the sequence of channels between which the envelopes should be crossfaded. A channel index, a demand rate or other ugen returning channel indices or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::out:: and the latter contains demand rate ugens, they must all be wrapped into Functions. + +argument::fadeTime +A fade time, a demand rate or other ugens returning fade times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::fadeTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::fadeTime:: depends on strong::fadeMode::. strong::fadeTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::stepTime +A step time, a demand rate or other ugens returning step times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::stepTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::stepTime:: depends on strong::fadeMode::. strong::stepTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::fadeMode +Integers between 0 and 4 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. +list:: +## fadeMode = 0: only fadeTimes are used, no steps +## fadeMode = 1: alternate steps and fades, begin with step; strong::stepTime:: means time without fade +## fadeMode = 2: alternate fades and steps, begin with fade; strong::stepTime:: means time without fade +## fadeMode = 3: alternate steps and fades, begin with step; strong::stepTime:: means sum of step and fade, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +## fadeMode = 4: alternate fades and steps, begin with fade; strong::stepTime:: means sum of fade and step, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +:: +Defaults to 0. + +argument::sine +Determines the crossfade type: sine-based or not. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::sine:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::sine:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::equalPower +Determines if crossfading of equal power type (square root) should be applied. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::equalPower:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::equalPower:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::power +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude. If power and curve are passed, power applies before. A positive Number or a demand rate or other ugen returning positive strong::power:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::power:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::curve +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude according to the lincurve mapping. If power and curve are passed, power applies before. A Number or a demand rate or other ugen returning strong::curve:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::curve:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Calculation of curvature is not giving reliable results when strong::width:: and / or strong::dynOutOffset:: are being modulated at the same time. Defaults to 0. + +argument::allowTypeSeq +Enables sequencing of strong::sine::, strong::equalPower::, strong::power:: and strong::curve:: with demand rate ugens and modulating of strong::sine:: and strong::equalPower:: with other ugens. A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0. + +argument::fadeRate +One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. Defaults to \ar. + +argument::maxFadeNum +Integer determining the maximum number of fades, after which strong::doneAction:: applies. A SequenceableCollection causes multichannel expansion. Not modulatable. Defaults to inf. + +argument::maxWidth +An Integer determining the maximum strong::width:: or a SequenceableCollection of such, causing multichannel expansion, strong::width:: goes into PanAz's width arg. strong::maxWidth:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 2. + +argument::width +Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, causing multichannel expansion. Not modulatable in versions earlier than SC 3.9. It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed strong::maxWidth::. Defaults to 2. + +argument::initOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines an initial offset for PanAz's pos arg. This can be useful for a start with full or reduced width. Not modulatable. Defaults to 0. + +argument::maxDynOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines the maximum strong::dynOutOffset:: to be expected. strong::maxDynOutOffset:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 1. + +argument::dynOutOffset +UGen, Integer or Float or a SequenceableCollection of such, causing multichannel expansion. By passing a ugen the movement between buses can be modulated. Note that a ugen's output must not exceed strong::maxDynOutOffset::. Defaults to 0. + +argument::allowFadeEnd +Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion. Determines if a demand rate input to in with finite length will be monitored, which needs a quite complicated trigger logic and more running ugens. If set to 0, the behaviour after the end of strong::in:: is undefined. Defaults to 1. + +argument::size +Integer or a SequenceableCollection of such, causing multichannel expansion. Determines the size of the returned multichannel envelope signal. Not modulatable. Defaults to 2. + +argument::bus +Bus, bus index or a SequenceableCollection of such, causing multichannel expansion. Determines whether a private multichannel bus should be used for channel switching. This is recommended for larger width sizes (> 10 or so) as otherwise the number of ugens might result in an overflow error. Not modulatable. Defaults to nil. + +argument::zeroThr +A Number or a ugen returning strong::zeroThr:: numbers or a SequenceableCollection of such, causing multichannel expansion. Determines if output values below this threshold are replaced by 0. This makes sense if the output signal is used as trigger (e.g. with DXEnvFan). In the case of low power numbers small inaccuracies are amplified, this is avoided with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power. As this requires more ugens running in parallel it is disabled by default = nil. + +argument::doneAction +Integer or a SequenceableCollection of such, causing multichannel expansion. Determines the doneAction after strong::maxFadeNum:: is exceeded. Defaults to 0. + +method::kr + + +SECTION::Examples + +anchor::Ex.1:: +code:: +( +// load with extended resources +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.reboot; +) +:: + + + +subsection::Ex.1: Basic types of crossfades + +code:: +// For normal crossfades the default values - equal power panning of sine type - should be fine, +// other types of crossfade envelopes can be taken for other purposes such as granular synthesis. + + +// default width 2, default equal power crossfading + +// employs crossfading from PanAz, which results in envelopes of sine type, from which +// the square root is taken + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// when dropping equalPower we get the pure sine envelope +// higher width + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + maxWidth: 8, + width: 5.5, + equalPower: 0, + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// type sine = 0 switches to a linear curve base, +// with default equalPower = 1 this leads to a square root envelope + + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + sine: 0, + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// a linear curve base without equalPower means a linear crossfade + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + sine: 0, + equalPower: 0, + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// if equalPower is set to 0 you can choose a power +// check alternative values for sine and power also + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + equalPower: 0, + sine: 0, // 1 + power: 3, // 0.3 + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// power can be a ugen too + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + equalPower: 0, + sine: 1, // 1 + power: SinOsc.ar(10, -pi/2).range(0.5, 10), + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// if equalPower is set to 0 you can also choose a curve arg +// which is passed as curvature to lincurve + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + equalPower: 0, + curve: 3, // -3 + sine: 0, + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// curve can be a ugen too + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + curve: Line.ar(5, -5, 0.1), // -3 + sine: 0, + equalPower: 0, + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// Passing a dynOutOffset arg - wobbled sliding + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + dynOutOffset: SinOsc.ar(150).range(0, 1), + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// This is not recommended: +// With dynOutOffset or modulated width a curve arg leads to irregularities + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + dynOutOffset: SinOsc.ar(150).range(0, 1), + equalPower: 0, + curve: 1, + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// out can also be a ugen, but note that with equalPower (default) +// blending the same channel leads to amplitude pikes, +// to avoid that take a linear crossfade + +( +{ + DXEnvFan.ar( + LFDNoise3.ar(15).range(0, 7), + size: 8, + fadeTime: 0.01, + stepTime: 0.02, + fadeMode: 1, + sine: 0, + equalPower: 1 // compare 0 + ) +}.plot(1) +) +:: + + +anchor::Ex.2:: +subsection::Ex.2: Sequencing crossfade types and parameters + +code:: +// sequencing crossfade types and parameters must be allowed explicitely as +// it is more CPU-costly + +// alternate linear and pure sine +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + allowTypeSeq: 1, + sine: Dseq([0, 1], inf), + equalPower: 0, // Dseq([0, 1], inf) + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + + +// curvature sequencing + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + allowTypeSeq: 1, + sine: 0, + equalPower: 0, + curve: Dseq([-3, -3, 3, 3], inf), + // fadeMode: 1, + // stepTime: 0.015 + ) +}.plot(0.1) +) + +:: + + +anchor::Ex.3:: +subsection::Ex.3: The 'zeroThr' arg + +code:: +// There might occur small inaccuracies in the calculation of crossfade values. +// This is irrelevant in most cases, but if you use the output signal as trigger +// it is definitely unwanted. +// You can supress such if you set an appropriate zero threshold. + + +// The effect of unwanted envelope segments especially becomes apparent +// with small power values that blow up values near 0. +// Values below the zeroThr are set to 0 before applying power or curvature. + +// Compare version without passing zeroThr. + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + equalPower: 0, + sine: 0, + power: 0.3, + fadeMode: 1, + stepTime: 0.015, + zeroThr: 0.002 + ) +}.plot(0.3) +) +:: + + +anchor::Ex.4:: +subsection::Ex.4: The 'bus' arg + +code:: +// As channel switching with DXEnvFan / DXFan is implemented by "watcher ugens", +// it becomes costly if the number of output channels increases (growth of quadratic order). +// The effort is lowered significantly if you pass a reserved bus for this or use DXEnvFanOut, +// this also concerns DXFan / DXFanOut. + +( +// load with extended resources +s = Server.local; +Server.default = s; +s.options.numPrivateAudioBusChannels = 256; +s.options.memSize = 8192 * 4; +s.options.numWireBufs = 512; +s.reboot; +) + + +// on my machine this example needs ca. 2.5 % CPU (818 ugens) ... + +( +x = { +    DXEnvFan.ar( +        Dshuf((0..29), inf), +        size: 30, +        fadeTime: 0.005 +    ) * 0.25 +}.play +) + +x.release + +// ... whereas this needs ca. 0.6 % CPU (167 ugens) + +( +a = Bus.audio(s, 30); + +x = { +    DXEnvFan.ar( +        Dshuf((0..29), inf), +        size: 30, +        fadeTime: 0.005, +        bus: a +    ) * 0.25 +}.play +) + +x.release + +( +a.free; +b.free; +) + + + +// care has to be taken with buses and multichannel expansion: + +// here two buses have to passed as otherwise we get a wrong result, +// the same bus would be taken for different calculations at the same time + +( +a = Bus.audio(s, 8); +b = Bus.audio(s, 8); + +{ + Mix(DXEnvFan.ar( + [Dseq((0..7), inf), Dseq((7..0), inf)], + size: 8, + fadeTime: 0.01, + equalPower: 0, + bus: [a, b] + )) +}.plot(0.1) +) + +( +a.free; +b.free; +) +:: + + +anchor::Ex.5:: +subsection::Ex.5: PlayBuf crossfading + +code:: +// Here a two channel envelope signal is used to crossfade between +// two channels of a PlayBuf, as rates are close we get a trill effect. + +// Note that the envelope signal is used three times: + +// as a trigger to the startPos within PlayBuf +// as a trigger for the demand ugen that determines the startPos within PlayBuf +// as an envelope for the PlayBuf + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + +( +x = { + var sig, env = DXEnvFan.ar( + Dseq([0, 1], inf), + fadeMode: 3, + stepTime: 0.05, + fadeTime: 0.01, + // to ensure right triggering set zeroThr + zeroThr: 0.002 + ); + + sig = PlayBuf.ar( + 1, + b, + [1, 1.1] * BufRateScale.kr(b), + env, + Demand.ar( + env, + 0, + Dstutter( + 5, + Dwhite(0.1, 0.9) + ) * BufFrames.ir(b) + ) + ) * 1.2 * env; + // do a bit correlation + Splay.ar(sig, 0.8) +}.play +) + +x.release + + +// L and R get different mixes, +// both take the same envelope trigger which switches +// between PlayBufs of different rates, the difference between L and R +// comes from different random start positions. + +( +x = { + var buf, env = DXEnvFan.ar( + Dseq([Dshuf((0..4)), Dshuf((5..7))], inf), + size: 8, + fadeMode: 3, + stepTime: 0.2, + fadeTime: 0.02, + zeroThr: 0.002 + ); + + { + Mix(PlayBuf.ar( + 1, + b, + { |j| j / 4 + 0.3 } ! 8 * BufRateScale.kr(b), + env, + Demand.ar(env, 0, Drand((2..5)/10, inf) * BufFrames.ir(b)), + loop: 0, + ) * env) + } ! 2 ; +}.play +) + + +x.release + +// variant with multichannel expansion +// two 8 ch trigger envelopes are used for L and R mixes +( +x = { + var buf, env = DXEnvFan.ar( + [Dseq((0..7), inf), Diwhite(0, 7)], + size: 8, + fadeMode: 3, + stepTime: 0.2, + fadeTime: 0.02, + zeroThr: 0.002 + ); + + { |i| Mix(PlayBuf.ar( + 1, + b, + { |j| j / 4 + 0.3 } ! 8 * BufRateScale.kr(b), + env[i], + // same positions go parallel + Demand.ar(env[i], 0, Dstutter(8, Dseq((2..5)/10, inf)) * BufFrames.ir(b)), + loop: 0, + ) * env[i]) } ! 2 ; +}.play +) + +x.release +:: + + +anchor::Ex.6:: +subsection::Ex.6: Granulation, sequencing of fxs and fx parameters per grain + +code:: +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + +// sequencing of band pass frequency +// see also Buffer Granulation tutorial, Ex. 1f + +( +a = Bus.audio(s, 8); + +x = { + var sig, env = DXEnvFan.ar( + Dseq((0..7), inf), + size: 8, + maxWidth: 8, + width: 5, + fadeTime: 0.01, + // to ensure right triggering set zeroThr + zeroThr: 0.002, + bus: a + ); + + sig = PlayBuf.ar( + 1, + b, + BufRateScale.kr(b), + env, + Demand.ar(env, 0, Dbrown(0.2, 0.5, 0.0025) * BufFrames.ir(b)), + loop: 0, + ) * env; + + // multichannel trigger polls from single sequencing source + // ensure non-zero filter freq for later triggers with max + sig = BPF.ar(sig, Demand.ar(env, 0, Dbrown(200, 2000, 100)).max(200), 0.1); + + // do a bit correlation + Splay.ar(sig, 0.6) * 7 +}.play +) + +x.release + +a.free + + + +// sequencing of different fxs + +( +a = Bus.audio(s, 8); + +x = { + var sig, env = DXEnvFan.ar( + Drand((0..7), inf), + size: 8, + maxWidth: 8, + // oscillation of overlap + width: LFDNoise3.ar(1).range(2, 6), + fadeTime: 0.015, + // to ensure right triggering set zeroThr + zeroThr: 0.002, + bus: a + ); + + sig = PlayBuf.ar( + 1, + b, + BufRateScale.kr(b), + env, + Demand.ar(env, 0, Dbrown(0.2, 0.5, 0.005) * BufFrames.ir(b)), + loop: 0, + ) * env; + + // selection of fxs as well as fx params triggered with envelope + // ring modulation, bit crusher and band pass + sig = Select.ar(Demand.ar(env, 0, Dstutter(Diwhite(5, 30), Dxrand([0, 1, 2], inf))), [ + sig * SinOsc.ar(Demand.ar(env, 0, Dbrown(250, 1000, 200))) * 1.5, + sig.round(2 ** Demand.ar(env, 0, Dbrown(-1, -4, 1))).lag(0.005) * 1.5, + // ensure non-zero filter freq for later triggers with max + BPF.ar(sig, Demand.ar(env, 0, Dbrown(200, 2000, 200)).max(200), 0.2) * 5 + ]); + + // do a bit correlation + Splay.ar(sig, 0.6) +}.play +) + +x.release + +a.free +:: + + +anchor::Ex.7:: +subsection::Ex.7: Multichannel expansion + +code:: + +// Not as complicated options as with DXMix +// we get an array of multichannel envelopes, here a mix of them + +( +{ + Mix(DXEnvFan.ar( + (0..3).collect { |i| Dseq((0..3).rotate(i), inf) }, + fadeTime: [0.01, 0.05], + size: 4, + width: 1 + )) +}.plot(0.2) +) + +// see Ex.4 for a delicate case with bus args +:: + + diff --git a/HelpSource/Classes/DXEnvFanOut.schelp b/HelpSource/Classes/DXEnvFanOut.schelp new file mode 100644 index 0000000..83858e6 --- /dev/null +++ b/HelpSource/Classes/DXEnvFanOut.schelp @@ -0,0 +1,222 @@ +CLASS:: DXEnvFanOut +summary:: sends crossfade envelopes to out buses according to demand-rate control +categories:: Libraries>miSCellaneous>DX suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXFan, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +DXEnvFanOut sends multichannel envelopes, which are used by DXMix / DXMixIn / DXFan / DXFanOut implicitely, to a sequence of out buses, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. It can be used as envelope and trigger at the same time, which leads to applications such as crossfading PlayBufs and different kinds of granulation, using a buffer or not. + +note:: +As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the link::Tutorials/DX_suite:: overview and go through the help file examples in this order: link::Classes/DXMix#Ex.1#DXMix:: - link::Classes/DXMixIn#Ex.1#DXMixIn:: - link::Classes/DXEnvFan#Ex.1#DXEnvFan:: - link::Classes/DXEnvFanOut#Ex.1#DXEnvFanOut:: - link::Classes/DXFan#Ex.1#DXFan:: - link::Classes/DXFanOut#Ex.1#DXFanOut::. Some general conventions are treated in detail in the following examples: fades and steps in link::Classes/DXMix#Ex.2#DXMix, Ex.2:: – width and offset arguments in link::Classes/DXMix#Ex.3#DXMix, Ex.3:: – multichannel expansion in link::Classes/DXMix#Ex.6#DXMix, Ex.6:: – crossfade types in link::Classes/DXEnvFan#Ex.1#DXEnvFan, Ex.1::. +:: + +note:: +PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice. +:: + +note:: +Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See link::Classes/DXMix#Ex.8#DXMix, Ex.8:: and link::Classes/DXEnvFan#Ex.2#DXEnvFan, Ex.2::, link::Classes/DXEnvFan#Ex.4#DXEnvFan, Ex.4:: for aspects of CPU demand. +:: + +note:: +In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See link::Classes/DXFan#Ex.4#DXFan, Ex.4:: for aspects of granulation with high trigger rates / short grain durations. +:: + +note:: +The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours. +:: + +subsection::Credits +Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz. + + + + +CLASSMETHODS:: + +method::ar + +argument::out +Determines the sequence of buses between which the envelope should be crossfaded. A bus index, a demand rate or other ugen returning bus indices or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::out:: and the latter contains demand rate ugens, they must all be wrapped into Functions. + +argument::fadeTime +A fade time, a demand rate or other ugens returning fade times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::fadeTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::fadeTime:: depends on strong::fadeMode::. strong::fadeTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::stepTime +A step time, a demand rate or other ugens returning step times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::stepTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::stepTime:: depends on strong::fadeMode::. strong::stepTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::fadeMode +Integers between 0 and 4 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. +list:: +## fadeMode = 0: only fadeTimes are used, no steps +## fadeMode = 1: alternate steps and fades, begin with step; strong::stepTime:: means time without fade +## fadeMode = 2: alternate fades and steps, begin with fade; strong::stepTime:: means time without fade +## fadeMode = 3: alternate steps and fades, begin with step; strong::stepTime:: means sum of step and fade, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +## fadeMode = 4: alternate fades and steps, begin with fade; strong::stepTime:: means sum of fade and step, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +:: +Defaults to 0. + +argument::sine +Determines the crossfade type: sine-based or not. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::sine:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::sine:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::equalPower +Determines if crossfading of equal power type (square root) should be applied. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::equalPower:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::equalPower:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::power +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude. If power and curve are passed, power applies before. A positive Number or a demand rate or other ugen returning positive strong::power:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::power:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::curve +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude according to the lincurve mapping. If power and curve are passed, power applies before. A Number or a demand rate or other ugen returning strong::curve:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::curve:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Calculation of curvature is not giving reliable results when strong::width:: and / or strong::dynOutOffset:: are being modulated at the same time. Defaults to 0. + +argument::allowTypeSeq +Enables sequencing of strong::sine::, strong::equalPower::, strong::power:: and strong::curve:: with demand rate ugens and modulating of strong::sine:: and strong::equalPower:: with other ugens. A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0. + +argument::fadeRate +One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. Defaults to \ar. + +argument::maxFadeNum +Integer determining the maximum number of fades, after which strong::doneAction:: applies. A SequenceableCollection causes multichannel expansion. Not modulatable. Defaults to inf. + +argument::maxWidth +An Integer determining the maximum strong::width:: or a SequenceableCollection of such, causing multichannel expansion, strong::width:: goes into PanAz's width arg. strong::maxWidth:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 2. + +argument::width +Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, causing multichannel expansion. Not modulatable in versions earlier than SC 3.9. It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed strong::maxWidth::. Defaults to 2. + +argument::initOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines an initial offset for PanAz's pos arg. This can be useful for a start with full or reduced width. Not modulatable. Defaults to 0. + +argument::maxDynOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines the maximum strong::dynOutOffset:: to be expected. strong::maxDynOutOffset:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 1. + +argument::dynOutOffset +UGen, Integer or Float or a SequenceableCollection of such, causing multichannel expansion. By passing a ugen the movement between buses can be modulated. Note that a ugen's output must not exceed strong::maxDynOutOffset::. Defaults to 0. + +argument::allowFadeEnd +Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion. Determines if a demand rate input to in with finite length will be monitored, which needs a quite complicated trigger logic and more running ugens. If set to 0, the behaviour after the end of strong::in:: is undefined. Defaults to 1. + +argument::zeroThr +A Number or a ugen returning strong::zeroThr:: numbers or a SequenceableCollection of such, causing multichannel expansion. Determines if output values below this threshold are replaced by 0. This makes sense if the output signal is used as trigger (e.g. with DXEnvFan). In the case of low power numbers small inaccuracies are amplified, this is avoided with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power. As this requires more ugens running in parallel it is disabled by default = nil. + +argument::doneAction +Integer or a SequenceableCollection of such, causing multichannel expansion. Determines the doneAction after strong::maxFadeNum:: is exceeded. Defaults to 0. + +method::kr + + +SECTION::Examples + +note:: +As with DXFan / DXFanOut examples from DXEnvFan help also can be written with DXEnvFanOut, but the latter doesn't need size and bus args as out buses are addressed directly. There is a small difference also with multichannel expansion, see last example below. +:: + + +anchor::Ex.1:: +code:: +( +// load with extended resources +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.reboot; +) +:: + + +subsection::Ex.1: Basic usage + +code:: +// play SinOscs silently and wait for granulation envelopes + +( +a = Bus.audio(s, 10); +x = { Splay.ar(In.ar(a, 10) * SinOsc.ar((3..12) * 100, 0, 0.05)) }.play +) + + +// send envelopes to buses + +( +z = { + DXEnvFanOut.ar( + Dseq((0..5) + a.index, inf) + Diwhite(1, 4), + + // with newer SC versions you can also write: + // Dseq((0..5), inf) + Diwhite(1, 4) + a.index, + + fadeTime: 0.05, + width: 1, + initOutOffset: -0.5 + ) +}.play +) + +x.release; + +// cleanup + +( +z.free; +a.free; +) +:: + + + +anchor::Ex.2:: +subsection::Ex.2: Multichannel expansion + +code:: +// play SinOscs silently and wait for granulation envelopes + +( +a = Bus.audio(s, 10); +x = { Splay.ar(In.ar(a, 10) * SinOsc.ar((3..12) * 100, 0, 0.05)) }.play +) + +// send envelope to buses in parallel + +( +z = { + DXEnvFanOut.ar( + [0, 3] + Dseq((0..3) + a.index, inf) + Diwhite(0, 3), + + // with newer SC versions you can also write: + // [0, 3] + Dseq((0..3), inf) + Diwhite(0, 3) + a.index, + + fadeTime: 0.05, + width: 1, + initOutOffset: -0.5 + ) +}.play +) + +x.release; + +// cleanup + +( +z.free; +a.free; +) + + +// Ex.7 from DXEnvFan help looks a bit different, +// as DXEnvFanOut is an out ugen we don't need to mix + +( +a = Bus.audio(s, 4); +{ + DXEnvFanOut.ar( + (0..3).collect { |i| Dseq((0..3).rotate(i), inf) } + a.index, + fadeTime: [0.01, 0.05], + width: 1 + ); + In.ar(a, 4) +}.plot(0.2) +) + +a.free +:: + diff --git a/HelpSource/Classes/DXFan.schelp b/HelpSource/Classes/DXFan.schelp new file mode 100644 index 0000000..6d9e7ed --- /dev/null +++ b/HelpSource/Classes/DXFan.schelp @@ -0,0 +1,327 @@ +CLASS:: DXFan +summary:: crossfades signals within a multichannel array according to demand-rate control +categories:: Libraries>miSCellaneous>DX suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +DXFan crossfades signals to a sequence of channels, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. + +note:: +As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the link::Tutorials/DX_suite:: overview and go through the help file examples in this order: link::Classes/DXMix#Ex.1#DXMix:: - link::Classes/DXMixIn#Ex.1#DXMixIn:: - link::Classes/DXEnvFan#Ex.1#DXEnvFan:: - link::Classes/DXEnvFanOut#Ex.1#DXEnvFanOut:: - link::Classes/DXFan#Ex.1#DXFan:: - link::Classes/DXFanOut#Ex.1#DXFanOut::. Some general conventions are treated in detail in the following examples: fades and steps in link::Classes/DXMix#Ex.2#DXMix, Ex.2:: – width and offset arguments in link::Classes/DXMix#Ex.3#DXMix, Ex.3:: – multichannel expansion in link::Classes/DXMix#Ex.6#DXMix, Ex.6:: – crossfade types in link::Classes/DXEnvFan#Ex.1#DXEnvFan, Ex.1::. +:: + +note:: +PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice. +:: + +note:: +Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See link::Classes/DXMix#Ex.8#DXMix, Ex.8:: and link::Classes/DXEnvFan#Ex.2#DXEnvFan, Ex.2::, link::Classes/DXEnvFan#Ex.4#DXEnvFan, Ex.4:: for aspects of CPU demand. +:: + +note:: +In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See link::Classes/DXFan#Ex.4#Ex.4:: for aspects of granulation with high trigger rates / short grain durations. +:: + +note:: +The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours. +:: + +subsection::Credits +Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz. + + + + +CLASSMETHODS:: + +method::ar + +argument::out +Determines the sequence of channels between which the signal should be crossfaded. A channel index, a demand rate or other ugen returning channel indices or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::out:: and the latter contains demand rate ugens, they must all be wrapped into Functions. + +argument::channelsArrayRef +The signal to be crossfaded. A single channel can be passed as such, an array must be wrapped into a Ref object to avoid multichannel expansion. In this case the multichannel signal is crossfaded from one block of adjacent channels to the next, whereby the lowest channel index follows the base sequence defined by out. A SequenceableCollection causes multichannel expansion, whereby single items of the collection can itself be Ref objects containing multichannel signals. + +argument::fadeTime +A fade time, a demand rate or other ugens returning fade times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::fadeTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::fadeTime:: depends on strong::fadeMode::. strong::fadeTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::stepTime +A step time, a demand rate or other ugens returning step times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::stepTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::stepTime:: depends on strong::fadeMode::. strong::stepTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::fadeMode +Integers between 0 and 4 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. +list:: +## fadeMode = 0: only fadeTimes are used, no steps +## fadeMode = 1: alternate steps and fades, begin with step; strong::stepTime:: means time without fade +## fadeMode = 2: alternate fades and steps, begin with fade; strong::stepTime:: means time without fade +## fadeMode = 3: alternate steps and fades, begin with step; strong::stepTime:: means sum of step and fade, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +## fadeMode = 4: alternate fades and steps, begin with fade; strong::stepTime:: means sum of fade and step, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +:: +Defaults to 0. + +argument::sine +Determines the crossfade type: sine-based or not. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::sine:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::sine:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::equalPower +Determines if crossfading of equal power type (square root) should be applied. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::equalPower:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::equalPower:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::power +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude. If power and curve are passed, power applies before. A positive Number or a demand rate or other ugen returning positive strong::power:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::power:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::curve +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude according to the lincurve mapping. If power and curve are passed, power applies before. A Number or a demand rate or other ugen returning strong::curve:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::curve:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Calculation of curvature is not giving reliable results when strong::width:: and / or strong::dynOutOffset:: are being modulated at the same time. Defaults to 0. + +argument::allowTypeSeq +Enables sequencing of strong::sine::, strong::equalPower::, strong::power:: and strong::curve:: with demand rate ugens and modulating of strong::sine:: and strong::equalPower:: with other ugens. A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0. + +argument::fadeRate +One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. Defaults to \ar. + +argument::maxFadeNum +Integer determining the maximum number of fades, after which strong::doneAction:: applies. A SequenceableCollection causes multichannel expansion. Not modulatable. Defaults to inf. + +argument::maxWidth +An Integer determining the maximum strong::width:: or a SequenceableCollection of such, causing multichannel expansion, strong::width:: goes into PanAz's width arg. strong::maxWidth:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 2. + +argument::width +Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, causing multichannel expansion. Not modulatable in versions earlier than SC 3.9. It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed strong::maxWidth::. Defaults to 2. + +argument::initOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines an initial offset for PanAz's pos arg. This can be useful for a start with full or reduced width. Not modulatable. Defaults to 0. + +argument::maxDynOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines the maximum strong::dynOutOffset:: to be expected. strong::maxDynOutOffset:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 1. + +argument::dynOutOffset +UGen, Integer or Float or a SequenceableCollection of such, causing multichannel expansion. By passing a ugen the movement between buses can be modulated. Note that a ugen's output must not exceed strong::maxDynOutOffset::. Defaults to 0. + +argument::allowFadeEnd +Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion. Determines if a demand rate input to in with finite length will be monitored, which needs a quite complicated trigger logic and more running ugens. If set to 0, the behaviour after the end of strong::in:: is undefined. Defaults to 1. + +argument::size +Integer or a SequenceableCollection of such, causing multichannel expansion. Determines the size of the returned multichannel signal. Not modulatable. Defaults to 2. + +argument::bus +Bus, bus index or a SequenceableCollection of such, causing multichannel expansion. Determines whether a private multichannel bus should be used for channel switching. This is recommended for larger width sizes (> 10 or so) as otherwise the number of ugens might result in an overflow error. Not modulatable. Defaults to nil. + +argument::zeroThr +A Number or a ugen returning strong::zeroThr:: numbers or a SequenceableCollection of such, causing multichannel expansion. Determines if output values below this threshold are replaced by 0. This makes sense if the output signal is used as trigger (e.g. with DXEnvFan). In the case of low power numbers small inaccuracies are amplified, this is avoided with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power. As this requires more ugens running in parallel it is disabled by default = nil. + +argument::doneAction +Integer or a SequenceableCollection of such, causing multichannel expansion. Determines the doneAction after strong::maxFadeNum:: is exceeded. Defaults to 0. + +method::kr + + +SECTION::Examples + +note:: +Note that, as with DXEnvFan, higher values passed to 'size' and 'maxWidth' cause a significant growth of the number of used ugens. In this case consider passing a bus arg or the use of link::Classes/DXFanOut::, see link::Classes/DXEnvFan#Ex.4#DXEnvFan, Ex.4::. +:: + + +anchor::Ex.1:: +code:: +( +// load with extended resources +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.reboot; +) +:: + + +subsection::Ex.1: Basic usage: simple crossfade + +code:: +// crossfading a mono source between two outs + +( +x = { + DXFan.ar( + Dseq([0, 1], inf), + PinkNoise.ar(0.05), + fadeTime: 1 + ) +}.play +) + +x.release + + +// Crossfading a stereo source between two outs, +// for more than two channels size has to be passed. + +( +{ + DXFan.ar( + Dseq([0, 2], inf), + `(Saw.ar(50 * [1, 5], 0.03)), + size: 4, + fadeTime: 0.1 + ) +}.plot(0.2) +) + + +// crossfading a mono source between several outs + +( +{ + DXFan.ar( + Dseq([0, 3, 1, 2], inf), + BPF.ar(PinkNoise.ar(), LFDNoise3.ar(1).range(100, 2000), 0.1, 0.7), + size: 4, + fadeTime: 0.05 + ) +}.plot(0.2) +) + + +// sliding over several channels, increased width + +( +{ + DXFan.ar( + Dseq([0, 3, 1, 2], inf), + BPF.ar(PinkNoise.ar(), LFDNoise3.ar(1).range(100, 2000), 0.1, 0.7), + size: 4, + fadeTime: 0.02, + maxWidth: 4, + width: 3 + ) +}.plot(0.2) +) +:: + + + + +anchor::Ex.2:: +subsection::Ex.2: Multichannel expansion + +code:: +// crossfading mono sources to alternate channels +// note that the result of DXFan, due to multichannel expansion, +// is an array of two stereo arrays which is then mixed to one stereo array. + +( +x = { + var lfo = { LFDNoise3.kr(0.2) }; + Mix(DXFan.ar( + [Dseq([0, 1], inf), Dseq([1, 0], inf)], + [ + LFTri.ar(150 * lfo.().range(1, 2), 0, 0.05), + BPF.ar(PinkNoise.ar(), lfo.().range(1000, 3000), 0.1, 0.7) + ], + fadeTime: 0.07, + width: 1 + )) +}.play +) + +x.release + + +// crossfading stereo sources to channel pairs (4ch), polyrhythm of fadeTimes. +// Similar to the above example the result of DXFan, due to multichannel expansion, +// is an array of two 4-channel arrays which is then mixed to one 4-channel array. + +( +x = { + var lfo = { LFDNoise3.kr(0.2) }; + Mix(DXFan.ar( + [Dseq([0, 2], inf), Dseq([2, 0], inf)], + [ + `({ |i| LFTri.ar(150 * lfo.().range(0.5, 2), 0, 0.05) } ! 2) , + `({ |i| BPF.ar(PinkNoise.ar(), lfo.().range(500, 1500) * (i + 1), 0.1, 0.7) } ! 2) + ], + size: 4, + fadeTime: [Dseq([0.06, 0.06, 0.03], inf), Dseq([0.05, 0.05, 0.17], inf)], + width: 1 + )) +}.play; +) + +x.release +:: + + + +anchor::Ex.3:: +subsection::Ex.3: Granulation by crossfading to different channels + +code:: +// mono source crossfaded to 8 channels + +( +{ + var lfo = LFDNoise3.kr(0.2); + DXFan.ar( + Dshuf((0..7), inf), + SinOsc.ar(lfo.range(800, 1500), 0, 0.03), + size: 8, + fadeTime: 0.01 + ) +}.plot(0.2) +) + + +// crossfading stereo pairs to every second out + +( +{ + var lfo = LFDNoise3.kr(0.2); + DXFan.ar( + Dseq((0, 2..6), inf), + `(SinOsc.ar(lfo.range(800, 1500) * [1, 1.02], 0, 0.03)), + size: 8, + fadeTime: 0.01 + ) ; +}.plot(0.2) +) +:: + + +anchor::Ex.4:: +subsection::Ex.4: Granulation with high trigger rates and / or short grain durations + +code:: +// fadeTimes should be above blockSize +// for shorter grains you can use smaller blocksizes + +( +s.options.blockSize = 2; +s.reboot; +) + +// as the effect is AM on single outs we get side bands + +( +s.doWhenBooted { + x = { + var lfo = LFDNoise3.ar(0.2); + DXFan.ar( + Dseq((0..7), inf), + SinOsc.ar(lfo.range(800, 1500), 0, 0.03), + size: 8, + fadeTime: 0.0002 + ) + }.play +}; +) + +x.release + + +// go back to default + +( +s.options.blockSize = 64; +s.reboot; +) +:: + + diff --git a/HelpSource/Classes/DXFanOut.schelp b/HelpSource/Classes/DXFanOut.schelp new file mode 100644 index 0000000..d910575 --- /dev/null +++ b/HelpSource/Classes/DXFanOut.schelp @@ -0,0 +1,273 @@ +CLASS:: DXFanOut +summary:: crossfades signals between out buses according to demand-rate control +categories:: Libraries>miSCellaneous>DX suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +DXFanOut crossfades signals to a sequence of out buses, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. + +note:: +As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the link::Tutorials/DX_suite:: overview and go through the help file examples in this order: link::Classes/DXMix#Ex.1#DXMix:: - link::Classes/DXMixIn#Ex.1#DXMixIn:: - link::Classes/DXEnvFan#Ex.1#DXEnvFan:: - link::Classes/DXEnvFanOut#Ex.1#DXEnvFanOut:: - link::Classes/DXFan#Ex.1#DXFan:: - link::Classes/DXFanOut#Ex.1#DXFanOut::. Some general conventions are treated in detail in the following examples: fades and steps in link::Classes/DXMix#Ex.2#DXMix, Ex.2:: – width and offset arguments in link::Classes/DXMix#Ex.3#DXMix, Ex.3:: – multichannel expansion in link::Classes/DXMix#Ex.6#DXMix, Ex.6:: – crossfade types in link::Classes/DXEnvFan#Ex.1#DXEnvFan, Ex.1::. +:: + +note:: +PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice. +:: + +note:: +Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See link::Classes/DXMix#Ex.8#DXMix, Ex.8:: and link::Classes/DXEnvFan#Ex.2#DXEnvFan, Ex.2::, link::Classes/DXEnvFan#Ex.4#DXEnvFan, Ex.4:: for aspects of CPU demand. +:: + +note:: +In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See link::Classes/DXFan#Ex.4#DXFan, Ex.4:: for aspects of granulation with high trigger rates / short grain durations. +:: + +note:: +The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours. +:: + +subsection::Credits +Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz. + + + + +CLASSMETHODS:: + +method::ar + +argument::out +Determines the sequence of buses between which the envelope should be crossfaded. A bus index, a demand rate or other ugen returning bus indices or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::out:: and the latter contains demand rate ugens, they must all be wrapped into Functions. + +argument::channelsArrayRef +The signals to be crossfaded. A single channel doesn't can be passed as such, an array must be wrapped into a Ref object to avoid multichannel expansion. In this case the multichannel signal is crossfaded from one block of adjacent buses to the next, whereby the lowest bus index follows the base sequence defined by out. A SequenceableCollection causes multichannel expansion, whereby single items of the collection can itself be Ref objects containing multichannel signals. + + +argument::fadeTime +A fade time, a demand rate or other ugens returning fade times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::fadeTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::fadeTime:: depends on strong::fadeMode::. strong::fadeTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::stepTime +A step time, a demand rate or other ugens returning step times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::stepTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::stepTime:: depends on strong::fadeMode::. strong::stepTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::fadeMode +Integers between 0 and 4 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. +list:: +## fadeMode = 0: only fadeTimes are used, no steps +## fadeMode = 1: alternate steps and fades, begin with step; strong::stepTime:: means time without fade +## fadeMode = 2: alternate fades and steps, begin with fade; strong::stepTime:: means time without fade +## fadeMode = 3: alternate steps and fades, begin with step; strong::stepTime:: means sum of step and fade, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +## fadeMode = 4: alternate fades and steps, begin with fade; strong::stepTime:: means sum of fade and step, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +:: +Defaults to 0. + +argument::sine +Determines the crossfade type: sine-based or not. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::sine:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::sine:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::equalPower +Determines if crossfading of equal power type (square root) should be applied. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::equalPower:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::equalPower:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::power +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude. If power and curve are passed, power applies before. A positive Number or a demand rate or other ugen returning positive strong::power:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::power:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::curve +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude according to the lincurve mapping. If power and curve are passed, power applies before. A Number or a demand rate or other ugen returning strong::curve:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::curve:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Calculation of curvature is not giving reliable results when strong::width:: and / or strong::dynOutOffset:: are being modulated at the same time. Defaults to 0. + +argument::allowTypeSeq +Enables sequencing of strong::sine::, strong::equalPower::, strong::power:: and strong::curve:: with demand rate ugens and modulating of strong::sine:: and strong::equalPower:: with other ugens. A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0. + +argument::fadeRate +One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. Defaults to \ar. + +argument::maxFadeNum +Integer determining the maximum number of fades, after which strong::doneAction:: applies. A SequenceableCollection causes multichannel expansion. Not modulatable. Defaults to inf. + +argument::maxWidth +An Integer determining the maximum strong::width:: or a SequenceableCollection of such, causing multichannel expansion, strong::width:: goes into PanAz's width arg. strong::maxWidth:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 2. + +argument::width +Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, causing multichannel expansion. Not modulatable in versions earlier than SC 3.9. It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed strong::maxWidth::. Defaults to 2. + +argument::initOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines an initial offset for PanAz's pos arg. This can be useful for a start with full or reduced width. Not modulatable. Defaults to 0. + +argument::maxDynOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines the maximum strong::dynOutOffset:: to be expected. strong::maxDynOutOffset:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 1. + +argument::dynOutOffset +UGen, Integer or Float or a SequenceableCollection of such, causing multichannel expansion. By passing a ugen the movement between buses can be modulated. Note that a ugen's output must not exceed strong::maxDynOutOffset::. Defaults to 0. + +argument::allowFadeEnd +Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion. Determines if a demand rate input to in with finite length will be monitored, which needs a quite complicated trigger logic and more running ugens. If set to 0, the behaviour after the end of strong::in:: is undefined. Defaults to 1. + +argument::zeroThr +A Number or a ugen returning strong::zeroThr:: numbers or a SequenceableCollection of such, causing multichannel expansion. Determines if output values below this threshold are replaced by 0. This makes sense if the output signal is used as trigger (e.g. with DXEnvFan). In the case of low power numbers small inaccuracies are amplified, this is avoided with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power. As this requires more ugens running in parallel it is disabled by default = nil. + +argument::doneAction +Integer or a SequenceableCollection of such, causing multichannel expansion. Determines the doneAction after strong::maxFadeNum:: is exceeded. Defaults to 0. + +method::kr + + +SECTION::Examples + +note:: +Examples from DXFan help also can be written with DXFanOut, but the latter doesn't need a size arg as the out bus is addressed directly. There is a small difference also with multichannel expansion, see example below. +:: + + +anchor::Ex.1:: +code:: +( +// load with extended resources +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.reboot; +) +:: + + +subsection::Ex.1: Multichannel expansion + +code:: +// Compare with Ex. 2 from DXFan help. +// As DXFanOut, other than DXFan, doesn't return an array, a mix is not necessary, +// by internal use of Out ugens the signal is mixed to the referred buses anyway. + +// For the same reason a normal release doesn't work, to get this option we can add an EnvGate. + +( +x = { + var lfo = { LFDNoise3.kr(0.2) }; + DXFanOut.ar( + [Dseq([0, 1], inf), Dseq([1, 0], inf)], + [ + LFTri.ar(150 * lfo.().range(1, 2), 0, 0.05), + BPF.ar(PinkNoise.ar(), lfo.().range(1000, 3000), 0.1, 0.7) + ] * EnvGate(), + fadeTime: 0.07, + width: 1 + ) +}.play +) + +x.release +:: + + + +anchor::Ex.2:: +subsection::Ex.2: Fast crossfading between fx processings ("fx granulation") + +code:: +// To be distinguished by fx processing of grains! +// define fxs to read from buses + +( +a = Bus.audio(s, 6); + +SynthDef(\resample, { |out = 0, in, lfoFreq = 0.2, resampleLo = 500, resampleHi = 2000, + lag = 0.001, mix = 1, amp = 0.1| + var sig, inSig = In.ar(in, 2), lfo; + lfo = LFDNoise3.kr(lfoFreq).range(resampleLo, resampleHi); + sig = Latch.ar(inSig, Impulse.ar(lfo)).lag(lag); + Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp); +}).add; + +SynthDef(\ring, { |out = 0, in, lfoFreq = 0.2, modFreqLo = 50, modFreqHi = 2000, + mix = 1, amp = 0.1| + var sig, mod, lfo, src, inSig = In.ar(in, 2); + lfo = LFDNoise3.kr(lfoFreq).range(modFreqLo, modFreqHi); + mod = SinOsc.ar(lfo); + sig = inSig * mod; + Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp); +}).add; + +SynthDef(\rectifier, { |out = 0, in, amount = 0, mix = 1, amp = 0.1| + var sig, inSig = In.ar(in, 2); + // need collect as if ugen doesn't take arrays + sig = inSig.collect { |x,i| x.ceil.if(x, x.abs * amount) }; + Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp); +}).add; +) + +// start fxs first +( +u = Synth(\resample, [in: a.subBus(0, 2)]); +v = Synth(\ring, [in: a.subBus(2, 2)]); +w = Synth(\rectifier, [in: a.subBus(4, 2)]); +) + + +// start source, crossfade between fx buses +// play with MouseX, fx and source period are in integer relation + +( +x = { + var seq = Demand.kr( + Impulse.kr(5), + 0, + Dxrand([60, 62, 65.5, 66, 68.5], inf) + Drand([-12, 0, 12], inf) + ).midicps.lag(0.015); + + DXFanOut.ar( + Dseq([0, 1, 2], inf) * 2 + a.index, + `(SinOsc.ar( + seq * [1, 1.01], + 0, + 1 + ) * EnvGate()), + fadeTime: 0.2 / (MouseX.kr(5, 25).round.poll), + width: 1.2 + ) +}.play +) + + +// cleanup + +x.release; + +[u, v, w].do(_.free); + + + +// granulated source + fast fx crossfades + +// load sound file + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + + +// start fxs first +( +u = Synth(\resample, [in: a.subBus(0, 2), lag: 0.01, mix: 0.6]); +v = Synth(\ring, [in: a.subBus(2, 2), mix: 1]); +) + +// feed DXFanOut with granulated signal +// control two trigger rates with MouseX and MouseY +( +x = { + var trig = Impulse.ar(MouseY.kr(20, 100).poll(2, label: 'source trigrate')); + var pos = BufDur.kr(b) * LFDNoise3.ar(0.6).range(0.1, 0.5); + var sig = TGrains.ar(2, trig, b, 1, pos, 0.5, Dseq([-1, 1], inf) * 0.8, 3); + DXFanOut.ar( + Dseq([0, 2], inf) + a.index, + `(sig * EnvGate()), + fadeTime: 0.2 / (MouseX.kr(5, 100).round.poll(2, label: 'fx trigrate')), + width: 1.5 + ) +}.play +) + +// cleanup + +x.release; + +[u, v].do(_.free); + +a.free; +:: + diff --git a/HelpSource/Classes/DXMix.schelp b/HelpSource/Classes/DXMix.schelp new file mode 100644 index 0000000..30bafdf --- /dev/null +++ b/HelpSource/Classes/DXMix.schelp @@ -0,0 +1,988 @@ +CLASS:: DXMix +summary:: crossfades between signals according to demand-rate control +categories:: Libraries>miSCellaneous>DX suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/DX_suite, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +DXMix crossfades between signals according to a sequence of indices, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. + + +note:: +As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the link::Tutorials/DX_suite:: overview and go through the help file examples in this order: link::Classes/DXMix#Ex.1#DXMix:: - link::Classes/DXMixIn#Ex.1#DXMixIn:: - link::Classes/DXEnvFan#Ex.1#DXEnvFan:: - link::Classes/DXEnvFanOut#Ex.1#DXEnvFanOut:: - link::Classes/DXFan#Ex.1#DXFan:: - link::Classes/DXFanOut#Ex.1#DXFanOut::. Some general conventions are treated in detail in the following examples: fades and steps in link::#Ex.2#Ex.2:: – width and offset arguments in link::#Ex.3#Ex.3:: – multichannel expansion in link::#Ex.6#Ex.6:: – crossfade types in link::Classes/DXEnvFan#Ex.1#DXEnvFan, Ex.1::. +:: + +note:: +PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice. +:: + +note:: +Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See link::#Ex.8#Ex.8:: and link::Classes/DXEnvFan#Ex.2#DXEnvFan, Ex.2::, link::Classes/DXEnvFan#Ex.4#DXEnvFan, Ex.4:: for aspects of CPU demand. +:: + +note:: +In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See link::Classes/DXFan#Ex.4#DXFan, Ex.4:: for aspects of granulation with high trigger rates / short grain durations. +:: + +note:: +The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours. +:: + + +subsection::Credits +Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz. + + + + + +CLASSMETHODS:: + +method::ar + +argument::in +Determines the sequence of signals to be crossfaded. A demand rate or other ugen returning channel array indices, a single channel array index or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::in:: and the latter contains demand rate ugens, they must all be wrapped into Functions. + +argument::channelsArrayRef +The signals to be crossfaded. To avoid multichannel expansion of this arg, the signals to be crossfaded must be wrapped into a Ref object. If the Ref object contains an array of signal arrays, these arrays are crossfaded. A SequenceableCollection causes multichannel expansion, whereby single items of the collection can itself be Ref objects containing multichannel signals. See multichannel examples below. + +argument::fadeTime +A fade time, a demand rate or other ugens returning fade times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::fadeTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::fadeTime:: depends on strong::fadeMode::. strong::fadeTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::stepTime +A step time, a demand rate or other ugens returning step times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::stepTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::stepTime:: depends on strong::fadeMode::. strong::stepTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::fadeMode +Integers between 0 and 4 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. +list:: +## fadeMode = 0: only fadeTimes are used, no steps +## fadeMode = 1: alternate steps and fades, begin with step; strong::stepTime:: means time without fade +## fadeMode = 2: alternate fades and steps, begin with fade; strong::stepTime:: means time without fade +## fadeMode = 3: alternate steps and fades, begin with step; strong::stepTime:: means sum of step and fade, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +## fadeMode = 4: alternate fades and steps, begin with fade; strong::stepTime:: means sum of fade and step, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +:: +Defaults to 0. + +argument::sine +Determines the crossfade type: sine-based or not. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::sine:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::sine:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::equalPower +Determines if crossfading of equal power type (square root) should be applied. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::equalPower:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::equalPower:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::power +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude. If power and curve are passed, power applies before. A positive Number or a demand rate or other ugen returning positive strong::power:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::power:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::curve +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude according to the lincurve mapping. If power and curve are passed, power applies before. A Number or a demand rate or other ugen returning strong::curve:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::curve:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Calculation of curvature is not giving reliable results when strong::width:: and / or strong::dynOutOffset:: are being modulated at the same time. Defaults to 0. + +argument::allowTypeSeq +Enables sequencing of strong::sine::, strong::equalPower::, strong::power:: and strong::curve:: with demand rate ugens and modulating of strong::sine:: and strong::equalPower:: with other ugens. A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0. + +argument::fadeRate +One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. Defaults to \ar. + +argument::maxFadeNum +Integer determining the maximum number of fades, after which strong::doneAction:: applies. A SequenceableCollection causes multichannel expansion. Not modulatable. Defaults to inf. + +argument::maxWidth +An Integer determining the maximum strong::width:: or a SequenceableCollection of such, causing multichannel expansion, strong::width:: goes into PanAz's width arg. strong::maxWidth:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 2. + +argument::width +Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, causing multichannel expansion. Not modulatable in versions earlier than SC 3.9. It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed strong::maxWidth::. Defaults to 2. + +argument::initOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines an initial offset for PanAz's pos arg. This can be useful for a start with full or reduced width. Not modulatable. Defaults to 0. + +argument::maxDynOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines the maximum strong::dynOutOffset:: to be expected. strong::maxDynOutOffset:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 1. + +argument::dynOutOffset +UGen, Integer or Float or a SequenceableCollection of such, causing multichannel expansion. By passing a ugen the movement between buses can be modulated. Note that a ugen's output must not exceed strong::maxDynOutOffset::. Defaults to 0. + +argument::allowFadeEnd +Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion. Determines if a demand rate input to in with finite length will be monitored, which needs a quite complicated trigger logic and more running ugens. If set to 0, the behaviour after the end of strong::in:: is undefined. Defaults to 1. + +argument::zeroThr +A Number or a ugen returning strong::zeroThr:: numbers or a SequenceableCollection of such, causing multichannel expansion. Determines if output values below this threshold are replaced by 0. This makes sense if the output signal is used as trigger (e.g. with DXEnvFan). In the case of low power numbers small inaccuracies are amplified, this is avoided with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power. As this requires more ugens running in parallel it is disabled by default = nil. + +argument::doneAction +Integer or a SequenceableCollection of such, causing multichannel expansion. Determines the doneAction after strong::maxFadeNum:: is exceeded. Defaults to 0. + +method::kr + + +SECTION::Examples + +anchor::Ex.1:: +code:: +( +// load with extended resources +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.reboot; +) +:: + + +subsection::Ex.1: Basic usage: simple crossfade + +code:: +// crossfading between 2 mono sources +// the array of channels to crossfade must be put into a Ref + +( +x = { + DXMix.ar( + Dseq([0, 1], inf), + `[ + Saw.ar(LFDNoise3.kr(0.1).range(40, 90), 0.03), + PinkNoise.ar(0.05) + ], + fadeTime: 3 + ) ! 2 +}.play +) + +x.release + + +// sources can itself be multichannel arrays +// fadeTimes passed as demand rate ugen + +( +x = { + var lfo = LFDNoise3.kr(0.2); + DXMix.ar( + Dseq([0, 1], inf), + `[ + Saw.ar(lfo.range(40, 90) * [1, 1.02], 0.03), + { BPF.ar(PinkNoise.ar(0.05), lfo.range(200, 5000), 0.1) } ! 2 + ], + fadeTime: Dwhite(3, 7) + ) +}.play +) + +x.release + + + +// So far all examples default to width = 2, PanAz's default width. +// It means that at most two channels overlap during crossfade. + +// If higher values of width should be taken, maxWidth must be increased, +// in order to ensure a sufficiently large number of channels used for overlapping internally. +// See Ex.3 for a detailled explanation of the width arg. + +// sliding over a sequence of channel indices + +( +x = { + var lfo = LFDNoise3.kr(0.2); + DXMix.ar( + Dseq([0, 2, 1, 3], inf), + `[ + Saw.ar(lfo.range(40, 70) * [1, 1.02], 0.02), + { BPF.ar(PinkNoise.ar(0.05), lfo.range(200, 5000), 0.1) } ! 2, + Pulse.ar(lfo.range(40, 70) * [1, 1.02], 0.5, 0.02), + { BPF.ar(Dust.ar(100), lfo.range(200, 5000), 0.1) } ! 2 + ], + fadeTime: Dwhite(3, 7), + maxWidth: 5, + width: 4 + ) +}.play +) + +x.release +:: + + +anchor::Ex.2:: +subsection::Ex.2: Basic usage: fades and steps + + +code:: +// in Ex.1 we always used fadeMode's default 0, where only fades are polled +// You might instead want to alternate steps (sections of no fade) with fades: + +// fadeMode = 0: only fadeTimes are used, no steps +// fadeMode = 1: alternate steps and fades, begin with step; stepTime means time without fade +// fadeMode = 2: alternate fades and steps, begin with fade; stepTime means time without fade +// fadeMode = 3: alternate steps and fades, begin with step; stepTime means sum of step and fade, +// thus stepTime must be larger than fadeTime +// fadeMode = 4: alternate fades and steps, begin with fade; stepTime means sum of fade and step, +// thus stepTime must be larger than fadeTime + + +// Check different fadeModes here, different effects with same times: + +// With fadeMode = 2 or 4 we start with a short fade, which gives the impression of a pickup. +// With fadeMode = 3 or 4 the tempo is faster, as fadeTime is subtracted from stepTime. +// With these modes it's the user's responsibility to limit fadeTimes. + + +// NOTE: fadeTimes and stepTimes must be larger than the duration of a control cycle +// with default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. +// If you go below, the step mechanism is messed up you get jumps and clicks. + +// Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, +// which is calculated by stepTime - fadeTime, is larger than this threshold. + + +( +x = { + DXMix.ar( + Dxrand((0..19), inf), + `(Saw.ar((1..10) * 100, 0.1) ++ SinOsc.ar((1..10) * 200, 0, 0.15)), + fadeTime: 0.05, + stepTime: 0.15, + fadeMode: 1 + + // check other fadeModes + + // fadeMode: 2 + // fadeMode: 3 + // fadeMode: 4 + ) + // short fade-in with Function.play to make initial DXMix fade (fadeModes 2 and 4) audible +}.play(fadeTime: 0.005) +) + +x.release + + +// some overtone fun with large width, independent DXMixs for L and R + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + { + DXMix.ar( + Dxrand((0..15), inf), + `(Saw.ar((1..8) * 70 * lfo, 0.05) ++ SinOsc.ar((1..8) * 68.5 * lfo, 0, 0.075)), + fadeTime: 0.02, + stepTime: 0.18, + fadeMode: 1, + width: 7, + maxWidth: 8 + ) + } ! 2 +}.play +) + +x.release + + +// ugen arguments can also be passed to 'in' +// the 'initOutOffset' arg is explained along the next examples + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + { + DXMix.ar( + LFDNoise3.ar(2).range(0, 15), + `(LFTri.ar((1..8) * 70 * lfo, 0, 0.05) ++ SinOsc.ar((1..8) * 68.5 * lfo, 0, 0.075)), + fadeTime: 0.01, + stepTime: 0.15, + fadeMode: 1, + width: 7, + maxWidth: 8, + equalPower: 0, // better here with sines, see curvature options in DXEnvFan help, Ex.1 + initOutOffset: -4 + ) + } ! 2 +}.play +) + +x.release + + + +// stepTime controlled by ugen + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + { + DXMix.ar( + LFDNoise3.ar(2).range(0, 15), + `(LFTri.ar((1..8) * 70 * lfo, 0, 0.05) ++ SinOsc.ar((1..8) * 68.5 * lfo, 0, 0.075)), + fadeTime: 0.01, + stepTime: SinOsc.ar(0.2).range(0.1, 0.5), + fadeMode: 1, + width: 3, + maxWidth: 8, + initOutOffset: -3 + ) + } ! 2 +}.play +) + +x.release +:: + + +anchor::Ex.3:: +subsection::Ex.3: Width and offset arguments for channel span + + +code:: + +// The width parameter determines the number of channels, which are +// maximally affected by the crossfade, it uses the convention of PanAz's width arg. +// The graphics below explain the overlap scheme of DXMix and cousins, +// for DXMix the indices denote the channels to be mixed, +// for DXMixIn the indices denote the buses to be mixed, +// for DXFanOut the buses to which the signal will be spread, +// for DXFan the channels to which the signal will be spread, +// for DXEnvFanOut the buses to which the envelope will be spread. +// for DXEnvFan the channels to which the envelope will be spread. + +// Let's look at a DXMix example with different width values: + + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + DXMix.ar( + Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf), + `(Saw.ar((1..8) * 70 * lfo, 0.075)), + fadeTime: 0.12, + stepTime: 0.38, + fadeMode: 1, + width: 2, + // check other width values below maxWidth + + maxWidth: 8 + ) ! 2 +}.play +) + +x.release + +// Width = 2 means that the crossfade only affects adjacent channels / signals / buses. +// In the graphic the center position of the movement is marked with bold letters, +// a diamond sign denotes, that the center lies in the middle of two channels, +// which are then equally weighted. Squared brackets enclose the active channel numbers. +// So the succession of two rows describes one fade. + +// width = 2: + +:: + +image::attachments/DXMix/sliding_ex_1.png:: + +code:: + +// For an arbitrary integer width n the number of active channels lies between n-1 and n, +// here a scheme of the number of affected channels: + + +width number of channels affected with + center position directly at channel + +2 1 +3 3 +4 3 +5 5 +6 5 +7 7 +8 7 +... + +width number of channels affected with + center position exactly between two channels + +2 2 +3 2 +4 4 +5 4 +6 6 +7 6 +8 8 +... + + +// DXMix and cousins take over the logic of PanAz. However there's a little difference at +// start with width > 2. As we are sliding over a index sequence which has a beginning, +// it seems natural that the start index should be the middle of the span, which is covered according to width. +// So from width > 2 onwards there's a "left side" of the channel span, which hasn't been generated so far, +// thus the full width is not reached before an entrance phase which increases with the width value. + +// width = 3: + +:: + +image::attachments/DXMix/sliding_ex_2.png:: + +code:: +// width = 4: +:: + +image::attachments/DXMix/sliding_ex_3.png:: + +code:: +// If one wants to start width full width there's the possibility to do this by +// passing an initOutOffset argument. + +// Here we start with initOutOffset = 1, fadeMode = 1 (step first), +// index = 4 (5th partial) is the middle of the channel span with most weight, +// which can be clearly perceived. + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + DXMix.ar( + Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf), + `(Saw.ar((1..8) * 70 * lfo, 0.075)), + fadeTime: 0.1, + stepTime: 2, + fadeMode: 1, + width: 3, + maxWidth: 8, + initOutOffset: 1 + ) ! 2 +}.play +) + +x.release + + +// overlap scheme with width = 3 and initOutOffset = 1: +:: + +image::attachments/DXMix/sliding_ex_4.png:: + +code:: +// Here we start with initOutOffset = 0.5, fadeMode = 1 (step first), +// channels with indices 0 and 4 (base tone and 5th partial) are equally weighted. + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + DXMix.ar( + Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf), + `(Saw.ar((1..8) * 70 * lfo, 0.075)), + fadeTime: 0.1, + stepTime: 2, + fadeMode: 1, + width: 3, + maxWidth: 8, + initOutOffset: 0.5 + ) ! 2 +}.play +) + +x.release + + +// overlap scheme with width = 3 and initOutOffset = 0.5: +:: + +image::attachments/DXMix/sliding_ex_5.png:: + + +code:: +// It might also be desirable to start with a fade from silence. +// This can be done with a negative initOutOffset: + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + DXMix.ar( + Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf), + `(Saw.ar((1..8) * 70 * lfo, 0.075)), + fadeTime: 2, + width: 2, + initOutOffset: -1 + ) ! 2 +}.play +) + +x.release + + +// overlap scheme with width = 2 and initOutOffset = -1: +:: + +image::attachments/DXMix/sliding_ex_6.png:: + +code:: + +// width can also be modulated (suited rather for lfos, only enabled from SC 3.9 onwards) + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + DXMix.ar( + Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf), + `(Saw.ar((1..8) * 70 * lfo, 0.05)), + fadeTime: 2, + width: SinOsc.ar(LFDNoise3.ar(1).range(0.2, 10)).range(2, 5), + maxWidth: 5 + ) ! 2 +}.play +) + +x.release + + +// for faster modulations take dynOutOffset, +// maxDynOutOffset must be set properly + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + { + DXMix.ar( + Dseq([0, 4, 1, 5, 2, 6, 3, 7], inf), + `(SinOsc.ar((1..8) * 140 * lfo, 0, 0.05)), + fadeTime: 2, + width: 2, + maxDynOutOffset: 2, + dynOutOffset: SinOsc.ar(LFDNoise3.ar(1).range(0.2, 25)).range(0, 2) + ) + } ! 2 +}.play +) + +x.release + + +// You can also control the movement of the channel span entirely by +// passing a ugen to dynOutOffset, therefore set fadeTime to inf. + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + { + DXMix.ar( + Dseq([0, 4, 1, 5, 2, 6, 3, 7, 0], inf), + `(SinOsc.ar((1..8) * 140 * lfo, 0, 0.05)), + fadeTime: inf, + width: 2, + maxDynOutOffset: 7, + dynOutOffset: SinOsc.ar(0.2).range(0, 7) + ) + } ! 2 +}.play +) + +x.release + +:: + +anchor::Ex.4:: +subsection::Ex.4: Switching between PlayBufs + +code:: +// This can go towards granulation + +// load sound file + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + + +// switch between looping PlayBufs + +( +x = { + var sig = { + DXMix.ar( + Dxrand([0, 1, 2], inf), + `(PlayBuf.ar(1, b, BufRateScale.kr(b) * [0.4, 1, 2.2], loop: 1)), + stepTime: 0.2, + fadeTime: 0.01, + fadeMode: 1, + width: 2 + ) } ! 2; + // do a bit correlation + Splay.ar(sig, 0.8) +}.play +) + +x.release +:: + +anchor::Ex.5:: +subsection::Ex.5: Granulation + +code:: +// granulation by fast fading between channels +// here single channels contain PlayBufs with ordered rates + +// load sound file + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + + +// widths (default 2) means overlap, thus grain length = 0.02 +// large-scale polyrhythm by differing rates + +( +x = { + var durs, trig, sig, thr = 0.5; + sig = { |i| + DXMix.ar( + Dseq((49..0), inf), + `(PlayBuf.ar( + 1, + b, + BufRateScale.kr(b) * ({ |j| j / 500 + 0.7 + (i * 0.02) } ! 50), + loop: 1 + )), + fadeTime: 0.01 + ) + } ! 2; + // do a bit correlation + Splay.ar(sig, 0.8) +}.play +) + +x.release + + + +// microsound with synthesized sources +// fadeTime change between very fast and medium length + +// compare version with dynOutOffset + +( +// universal lfo, several instances to be used, thus put into a Function + +l = { LFDNoise3.ar(LFDNoise3.ar(1).exprange(0.1, 30)).range(0.5, 3) }; + +// pool of sources and arguments for channel array + +e = ( + GrayNoise: [0.1], + Dust: [500], + BrownNoise: [0.1], + Pulse: [exprand(50, 500) * l], + SinOsc: [exprand(50, 500) * l], + Saw: [exprand(50, 500) * l] +); + +x = { + var durs, trig, sig, thr = 0.5; + sig = { |i| + DXMix.ar( + Dseq((0..19), inf), + `( + // generate array of oscillators to slide over + { + var sym = e.keys.choose; + sym.asClass.performList(\ar, e[sym]); + } ! 20 + ), + fadeTime: Dstutter( + Diwhite(2, 5), + Dwhite(0.01, 1).linexp(0.01, 1, 0.01, 1) + ), + width: 2, + maxWidth: 2, + // add oscillation between adjacent channels of sequence + // dynOutOffset: SinOsc.ar(LFDNoise3.ar(1).exprange(1, 100)).range(0, 1) + ) * 0.1 + } ! 2; + // do a bit correlation + Splay.ar(sig, 0.8) * 2 +}.play +) + +x.release +:: + +anchor::Ex.6:: +subsection::Ex.6: Multichannel expansion + +code:: +// If a multiple demand rate ugen is implicitely needed, it must be wrapped into a Function + +// Because of the array passed to fadeTime, we need two Dseqs to poll from. +// As only one demand rate ugen is passed as 'in' arg, it must be wrapped into a Function. + +( +x = { + DXMix.ar( + { Dseq([0, 1, 2], inf) }, + // equivalent: + // [Dseq([0, 1, 2], inf), Dseq([0, 1, 2], inf)], + `[ + Saw.ar(LFDNoise3.kr(0.1).range(40, 90), 0.03), + Pulse.ar(LFDNoise3.kr(0.1).range(80, 180), 0.5, 0.03), + PinkNoise.ar(0.05) + ], + fadeTime: [0.03, 3] + ) +}.play +) + +x.release + + +// L and R switching between same sources + +( +x = { + var lfo = { LFDNoise3.kr(0.2) }; + DXMix.ar( + [Dseq([0, 1, 2], inf), Dseq([2, 3, 1], inf)], + `[ + Saw.ar(lfo.().range(40, 90), 0.03), + BPF.ar(PinkNoise.ar(0.05), lfo.().range(200, 5000), 0.1), + BPF.ar(Crackle.ar(1.95, 0.5), lfo.().range(200, 5000), 0.1), + Pulse.ar(lfo.().range(40, 90), 0.5, 0.03) + ], + fadeTime: { Dwhite(3, 7) } + ) +}.play +) + +x.release + + +// L and R switching between different sources + +( +x = { + var lfo = { LFDNoise3.kr(0.2) }; + DXMix.ar( + [Dseq([0, 1], inf), Dseq([1, 0], inf)], + [ + `[ + Saw.ar(lfo.().range(40, 90), 0.03), + BPF.ar(PinkNoise.ar(0.05), lfo.().range(200, 5000), 0.1) + ], + `[ + Saw.ar(lfo.().range(40, 90), 0.03), + BPF.ar(PinkNoise.ar(0.05), lfo.().range(200, 5000), 0.1) + ] + ], + fadeTime: { Dwhite(3, 7) } + ) +}.play +) + +x.release + + +// last example, written in a more condensed way + +( +x = { + var lfo = { LFDNoise3.kr(0.2) }; + DXMix.ar( + [Dseq([0, 1], inf), Dseq([1, 0], inf)], + [ + { Saw.ar(lfo.().range(40, 90), 0.03) } ! 2, + { BPF.ar(PinkNoise.ar(0.05), lfo.().range(200, 5000), 0.1) } ! 2 + ].flop.collect(`_), + fadeTime: { Dwhite(3, 7) } + ) +}.play +) + +x.release + + +// overtone series - L up, R down +// decreasing fundamental + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + DXMix.ar( + [Dseq((0..15), inf), Dseq((15..0), inf)], + `(SinOsc.ar((1..16) * 120 * lfo, 0, 0.05)), + fadeTime: 0.1, + equalPower: 0 + ) +}.play +) + +x.release + + + +// more complicated expansions: +// DXMix produces a nested array of two stereo fades, +// it is mixed to a flat stereo array + +( +x = { + var lfo = { LFDNoise3.kr(0.2) }; + Mix(DXMix.ar( + [Dseq([0, 1], inf), Dseq([2, 3], inf)], + `[ + Saw.ar(lfo.().range(40, 90) * [1, 3], 0.03), + { BPF.ar(BrownNoise.ar(0.05), lfo.().range(100, 500), 0.3) } ! 2, + SinOsc.ar(lfo.().range(200, 500) * [2, 3], 0, 0.03), + { BPF.ar(Dust.ar(300), lfo.().range(200, 5000), 0.1) } ! 2 + ], + fadeTime: [Dwhite(3, 7), Dwhite(2, 5)] + )) +}.play +) + +x.release + + +// similar situation as above, but the two stereo fades are referring to the same channels +// the result is a kind of accentuation + +( +x = { + var lfo = LFDNoise3.kr(0.2); + Mix(DXMix.ar( + [Dseq([0, 1], inf), Dseq([1, 1, 1, 0], inf)], + `[ + Saw.ar(lfo.range(40, 90) * [1, 3], 0.025), + { BPF.ar(PinkNoise.ar(0.05), lfo.range(500, 5000), 0.1, 5) } ! 2 + ], + fadeTime: [Dwhite(3, 7), 0.1], + // width < 2 produces fade gaps + width: [2, 0.7] + )) +}.play +) + +x.release + + +// interesting multichannel expansions are possible with drate ugens based on nested arrays, +// e.g. coupling of streams + +( +x = { + var lfo = { LFDNoise3.kr(0.2) }; + Mix(DXMix.ar( + Dxrand([[0, 1], [1, 0], [2, 3], [3, 2]], inf), + `[ + Saw.ar(lfo.().range(40, 90) * [1, 3], 0.02), + { BPF.ar(PinkNoise.ar(1), lfo.().range(500, 5000), 0.02, 1) } ! 2, + SinOsc.ar(lfo.().range(400, 900) * [1, 1.2], 0, 0.01), + { BPF.ar(ClipNoise.ar(0.2), lfo.().range(500, 5000), 0.02, 1) } ! 2 + ], + fadeTime: 0.1, + // width < 2 produces fade gaps + width: 1 + )) +}.play +) + +x.release +:: + + +anchor::Ex.7:: +subsection::Ex.7: Stopping and doneAction + +code:: +// The done action is invoked after maxFadeNum. + +( +x = { + var lfo = LFDNoise3.kr(0.2); + DXMix.ar( + Dseq([0, 1], inf), + `[ + Saw.ar(lfo.range(40, 90) * [1, 1.02], 0.03), + { BPF.ar(PinkNoise.ar(0.05), lfo.range(200, 5000), 0.1) } ! 2 + ], + fadeTime: 0.5, + stepTime: 1, + fadeMode: 0, // check with other modes too + maxFadeNum: 5, + doneAction: 2 + ) +}.play +) + +x.release +:: + + + +anchor::Ex.8:: +subsection::Ex.8: Saving CPU + +code:: +// This might be a topic if you're running many fades in parallel and +// fadeTimes are not extremely short (and hence audio rate doesn't make a difference). + +// Here my machine needs ca. 2 % CPU for fadeRate = \kr +// and ca. 6 % CPU for fadeRate = \ar. + +// 10 parallel sequences of overtone fading + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + var sig = DXMix.ar( + { Drand((0..15), inf) }, + `(SinOsc.ar((1..16) * 70 * lfo, 0, 0.2 / (1..16).sqrt)), + fadeTime: 1 / (1..10), + fadeRate: \kr, // check CPU difference to \ar + maxWidth: 4, + width: 3 + ); + Splay.ar(sig) +}.play +) + +x.release + + +// monitoring demand rate ugens needs a quite complicated trigger logic in this context +// per default you can pass finite drate ugens for 'in' - +// observe that layers will stop after different times as fadeTimes are different + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + var sig = DXMix.ar( + { Drand((0..15), 20) }, + `(SinOsc.ar((1..16) * 70 * lfo, 0, 0.2 / (1..16).sqrt)), + fadeTime: 1 / (1..10), + fadeRate: \kr, // also check CPU difference to \ar + maxWidth: 4, + width: 3 + ); + Splay.ar(sig) +}.play +) + +x.release + + +// if you don't need this option you can save a number of ugens with allowFadeEnd = 0, compare + +( +x = { + var lfo = XLine.ar(1, 0.5, 60); + var sig = DXMix.ar( + { Drand((0..15), 20) }, + `(SinOsc.ar((1..16) * 70 * lfo, 0, 0.2 / (1..16).sqrt)), + fadeTime: 1 / (1..10), + fadeRate: \kr, // also check CPU difference to \ar + maxWidth: 4, + width: 3, + allowFadeEnd: 0 + ); + Splay.ar(sig) +}.play +) + +x.release +:: + +note:: +Also see link::Classes/DXEnvFan#Ex.2#DXEnvFan, Ex.2:: and link::Classes/DXEnvFan#Ex.4#DXEnvFan, Ex.4:: for CPU aspects. Note that the use of the options allowTypeSeq and zeroThr also needs more CPU. Rescaling, which is necessary for SC versions before 3.9 with audio rate, is also more CPU-costly. +:: + + diff --git a/HelpSource/Classes/DXMixIn.schelp b/HelpSource/Classes/DXMixIn.schelp new file mode 100644 index 0000000..50a46a0 --- /dev/null +++ b/HelpSource/Classes/DXMixIn.schelp @@ -0,0 +1,219 @@ +CLASS:: DXMixIn +summary:: crossfades between signals from buses according to demand-rate control +categories:: Libraries>miSCellaneous>DX suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/DX_suite, Classes/DXMix, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +DXMixIn crossfades between signals from buses according to a sequence of indices, which, together with fadeTimes and stepTimes, can be passed as demand rate ugens. + +note:: +As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the link::Tutorials/DX_suite:: overview and go through the help file examples in this order: link::Classes/DXMix#Ex.1#DXMix:: - link::Classes/DXMixIn#Ex.1#DXMixIn:: - link::Classes/DXEnvFan#Ex.1#DXEnvFan:: - link::Classes/DXEnvFanOut#Ex.1#DXEnvFanOut:: - link::Classes/DXFan#Ex.1#DXFan:: - link::Classes/DXFanOut#Ex.1#DXFanOut::. Some general conventions are treated in detail in the following examples: fades and steps in link::Classes/DXMix#Ex.2#DXMix, Ex.2:: – width and offset arguments in link::Classes/DXMix#Ex.3#DXMix, Ex.3:: – multichannel expansion in link::Classes/DXMix#Ex.6#DXMix, Ex.6:: – crossfade types in link::Classes/DXEnvFan#Ex.1#DXEnvFan, Ex.1::. +:: + +note:: +PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice. +:: + +note:: +Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See link::Classes/DXMix#Ex.8#DXMix, Ex.8:: and link::Classes/DXEnvFan#Ex.2#DXEnvFan, Ex.2::, link::Classes/DXEnvFan#Ex.4#DXEnvFan, Ex.4:: for aspects of CPU demand. +:: + +note:: +In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See link::Classes/DXFan#Ex.4#DXFan, Ex.4:: for aspects of granulation with high trigger rates / short grain durations. +:: + +note:: +The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours. +:: + + +subsection::Credits +Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz. + + + + + +CLASSMETHODS:: + +method::ar + +argument::in +Determines the sequence of signals to be crossfaded. A demand rate or other ugen returning bus indices, a single bus index or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::in:: and the latter contains demand rate ugens, they must all be wrapped into Functions. + +argument::fadeTime +A fade time, a demand rate or other ugens returning fade times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::fadeTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::fadeTime:: depends on strong::fadeMode::. strong::fadeTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::stepTime +A step time, a demand rate or other ugens returning step times or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::stepTime:: and the latter contains demand rate ugens, they must all be wrapped into Functions. The interpretation of strong::stepTime:: depends on strong::fadeMode::. strong::stepTime:: must be larger than the duration of a control cycle. Defaults to 1. + +argument::fadeMode +Integers between 0 and 4 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. +list:: +## fadeMode = 0: only fadeTimes are used, no steps +## fadeMode = 1: alternate steps and fades, begin with step; strong::stepTime:: means time without fade +## fadeMode = 2: alternate fades and steps, begin with fade; strong::stepTime:: means time without fade +## fadeMode = 3: alternate steps and fades, begin with step; strong::stepTime:: means sum of step and fade, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +## fadeMode = 4: alternate fades and steps, begin with fade; strong::stepTime:: means sum of fade and step, thus strong::stepTime:: must be larger than strong::fadeTime::, the difference must be larger than the duration of a control cycle +:: +Defaults to 0. + +argument::sine +Determines the crossfade type: sine-based or not. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::sine:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::sine:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::equalPower +Determines if crossfading of equal power type (square root) should be applied. A Boolean, 0 or 1 or a demand rate or other ugen returning strong::equalPower:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::equalPower:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Modulating this arg is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::power +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude. If power and curve are passed, power applies before. A positive Number or a demand rate or other ugen returning positive strong::power:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::power:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Defaults to 1. + +argument::curve +This only comes into play if strong::equalPower:: equals 0, then it's applied to the crossfade amplitude according to the lincurve mapping. If power and curve are passed, power applies before. A Number or a demand rate or other ugen returning strong::curve:: numbers or a SequenceableCollection of such, causing multichannel expansion. If in this case the overall multichannel size is larger than the size of strong::curve:: and the latter contains demand rate ugens, they must all be wrapped into Functions. Sequencing this arg with demand rate ugens is only possible if strong::allowTypeSeq:: equals 1. Calculation of curvature is not giving reliable results when strong::width:: and / or strong::dynOutOffset:: are being modulated at the same time. Defaults to 0. + +argument::allowTypeSeq +Enables sequencing of strong::sine::, strong::equalPower::, strong::power:: and strong::curve:: with demand rate ugens and modulating of strong::sine:: and strong::equalPower:: with other ugens. A Boolean, 0 or 1 or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. As this requires more ugens running in parallel it is disabled by default = 0. + +argument::fadeRate +One of the Symbols \ar and \kr, determining the crossfade rate used by PanAz or a SequenceableCollection of such, causing multichannel expansion. Not modulatable. Defaults to \ar. + +argument::maxFadeNum +Integer determining the maximum number of fades, after which strong::doneAction:: applies. A SequenceableCollection causes multichannel expansion. Not modulatable. Defaults to inf. + +argument::maxWidth +An Integer determining the maximum strong::width:: or a SequenceableCollection of such, causing multichannel expansion, strong::width:: goes into PanAz's width arg. strong::maxWidth:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 2. + +argument::width +Integer, Float, UGen (only from SC 3.9 onwards) or a SequenceableCollection of such, causing multichannel expansion. Not modulatable in versions earlier than SC 3.9. It determines the width according to PanAz's width arg. Note that a ugen's output must not exceed strong::maxWidth::. Defaults to 2. + +argument::initOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines an initial offset for PanAz's pos arg. This can be useful for a start with full or reduced width. Not modulatable. Defaults to 0. + +argument::maxDynOutOffset +An Integer or Float or a SequenceableCollection of such, causing multichannel expansion. Determines the maximum strong::dynOutOffset:: to be expected. strong::maxDynOutOffset:: increases the internally used and potentially needed number of parallel channels. Not modulatable. Defaults to 1. + +argument::dynOutOffset +UGen, Integer or Float or a SequenceableCollection of such, causing multichannel expansion. By passing a ugen the movement between buses can be modulated. Note that a ugen's output must not exceed strong::maxDynOutOffset::. Defaults to 0. + +argument::allowFadeEnd +Integer, Boolean or a SequenceableCollection of such, causing multichannel expansion. Determines if a demand rate input to in with finite length will be monitored, which needs a quite complicated trigger logic and more running ugens. If set to 0, the behaviour after the end of strong::in:: is undefined. Defaults to 1. + +argument::zeroThr +A Number or a ugen returning strong::zeroThr:: numbers or a SequenceableCollection of such, causing multichannel expansion. Determines if output values below this threshold are replaced by 0. This makes sense if the output signal is used as trigger (e.g. with DXEnvFan). In the case of low power numbers small inaccuracies are amplified, this is avoided with an appropriate zeroThr (e.g. = 0.001), as the operation is applied before taking the power. As this requires more ugens running in parallel it is disabled by default = nil. + +argument::doneAction +Integer or a SequenceableCollection of such, causing multichannel expansion. Determines the doneAction after strong::maxFadeNum:: is exceeded. Defaults to 0. + +method::kr + + +SECTION::Examples + +anchor::Ex.1:: +code:: +( +// load with extended resources +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.reboot; +) +:: + + + +subsection::Ex.1: Crossfaded mixing from buses + +code:: +// play to buses silently + +( +a = Bus.audio(s, 5); + +x = { + Out.ar(a.index, [ + BrownNoise.ar(0.1), + PinkNoise.ar(0.2), + WhiteNoise.ar(0.05), + ClipNoise.ar(0.03), + GrayNoise.ar(0.05) + ]) +}.play +) + +// mix from buses, mind node order + +( +y = { + DXMixIn.ar( + Dxrand((0..4), inf) + a.index, + fadeTime: 0.5, + ) +}.play(addAction: \addToTail) +) + +y.release + + +// cleanup + +( +x.free; +a.free; +) +:: + + +anchor::Ex.2:: +subsection::Ex.2: Multichannel expansion + + +code:: +// play to buses silently + +( +a = Bus.audio(s, 5); +x = { Out.ar(a.index, SinOsc.ar((3..7) * 100, 0, 0.05)) }.play +) + + +// mix the source +// a Drate ugen with arrays causes multichannel expansion +// thus the drate ugen for fadeTime needs to be wrapped into a Function + +( +z = { + DXMixIn.ar( + Drand([[0, 1], [2, 3], [4, 0], [1, 2], [3, 4]], inf) + a.index, + fadeTime: { Dwhite(0.5, 5) } + ) +}.play(addAction: \addToTail) +) + +// stop DXMixIn, source still running + +z.release + + +// get source with other rhythm + +( +z = { + DXMixIn.ar( + Dseq([[0, 1], [2, 3], [4, 0], [1, 2], [3, 4]], inf) + a.index, + fadeTime: [0.05, 2] + ) +}.play(addAction: \addToTail) +) + +z.release + +// cleanup + +( +x.free; +a.free; +) +:: + + diff --git a/HelpSource/Classes/Dictionary.ext.schelp b/HelpSource/Classes/Dictionary.ext.schelp new file mode 100644 index 0000000..9d91b38 --- /dev/null +++ b/HelpSource/Classes/Dictionary.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_maybePutPairs, miSC_getEnvir + + diff --git a/HelpSource/Classes/Dwalk.schelp b/HelpSource/Classes/Dwalk.schelp new file mode 100644 index 0000000..bb60f07 --- /dev/null +++ b/HelpSource/Classes/Dwalk.schelp @@ -0,0 +1,301 @@ +CLASS:: Dwalk +summary:: demand rate ugen for (random) walks +categories:: Libraries>miSCellaneous +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/ZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +Dwalk can be used for general purposes in a way similar to Pwalk, but can also return data in a form, which is especially suited for usage with link::Classes/ZeroXBufRd::. A sequence of Integers (stepsPerDir) determines the number of consequential additions of numbers (stepWidth) before directional changes. Dwalk returns the current sum and, optionally, the current direction (plus or minus 1). + +For buffer modulation with link::Classes/ZeroXBufRd:: a stepWidth of 1 allows stepping through adjacent segments. There are two ways to achieve smooth directional changes of the read pointer: (1) at zero crossings of the original waveform together with inverting it or (2) at the positions where the waveform changes direction (up/down) or in other words: its slope crosses zero. + + +note:: +For implementational reasons Dwalk doesn't multichannel expand, use duplication of Functions instead. +:: + +note:: +If withDirs is set to 1 and many values are polled, extended memory size is recommended: a high bufSize is needed for Dunique (as – and especially – with ZeroXBufRd). +:: + +note:: +Ordinary ugens for stepWidth and steps per direction (via stepsPerDirMulUGen and stepsPerDirAddUGen) should be avoided in combination with ZeroXBufRd. +:: + + + +CLASSMETHODS:: + +method::new + +argument::stepsPerDir +Integer or demand rate ugen determining the integer number of steps into one direction. If withDirs equals 0 also an ordinary ugens might be passed. If withDirs equals 1 a Dunique is used internally, in this case for passing an ordinary ugen alone or in combination with a demand rate ugen use strong::stepsPerDirMulUGen:: or strong::stepsPerDirAddUGen::. Defaults to 1. + +argument::stepWidth +Number, demand rate or other ugen determining the step width. Defaults to 1. How strong::stepWidth:: is interpreted in case of a demand rate or other ugen depends on the value of strong::stepMode::. An ordinary ugens should not be chosen when using Dwalk for ZeroXBufRd. + + +argument::stepMode +Determines how strong::stepWidth:: is interpreted in case of a demand rate or other ugen: +list:: +## 0 – one step width is taken for all steps into one direction +## 1 – step width changes per step. +:: +Defaults to 0. + +argument::start +The start value, which is returned anyway. Defaults to 0. + +argument::lo +Low boundary value, causes clipping. Directional changes apply after clipping. Might also be demand rate or other ugen, defaults to -inf. + +argument::hi +High boundary value, causes clipping. Directional changes apply after clipping. Might also be demand rate or other ugen, defaults to inf. + +argument::startDir +The start direction, which is returned anyway. Defaults to 1. + +argument::dirChangeMode +Determines if directional changes should apply immediately (1) or after a repetition of the current value (0, default). The latter corresponds to the convention of ZeroXBufRd: if a segment should be played in reverse direction, the index (the zeroX arg) would stay the same whereas the direction (the dir arg) is multiplied with -1. + +argument::withDirs +0, 1, false or true. Defaults to 0. Determines if direction values (plus or minus 1) should be returned. In this case the result is an array of two demand rate ugens returning the summed numbers and the direction values. If set to 1 a Dunique is used internally and strong::stepsPerDir:: must not get an ordinary ugen. + +argument::stepsPerDirMulUGen +Ordinary ugen to be multiplied with strong::stepsPerDir::. This option is only necessary in the case that strong::withDirs:: equals 1 and should not be used in combination with ZeroXBufRd. The number of steps per direction is calculated by strong::stepsPerDir:: * strong::stepsPerDirMulUGen:: + strong::stepsPerDirAddUGen::. Defaults to 1. + +argument::stepsPerDirAddUGen +Ordinary ugen to be multiplied with strong::stepsPerDir::. This option is only necessary in the case that strong::withDirs:: equals 1 and should not be used in combination with ZeroXBufRd. The number of steps per direction is calculated by strong::stepsPerDir:: * strong::stepsPerDirMulUGen:: + strong::stepsPerDirAddUGen::. Defaults to 0. + +argument::dUniqueBufSize +Determines the buffer size for a Dunique object which has to be used in case that strong::withDirs:: is set to 1. Defaults to 1048576. + + + +SECTION::Examples + + +anchor::Ex.1:: +code:: +( +// boot with extended resources + +s = Server.local; +Server.default = s; +s.options.memSize = 8192 * 32; +s.reboot; +) +:: + + +subsection::Ex.1: Basic usage + +code:: +// dirChangeMode == 1 (immediate direction change) +// note that here we get two resulting sequences (withDirs == 1): +// the actual number sequence and the sequence of directions (+-1) + + +// stepsPerDir sequence: +// 1, 2, 3, 1, 2, 3, 1... + + +// step sequence with default stepWidth == 1: +// 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, ... + +// resulting number sequence: +// 0, 1, 0, -1, 0, 1, 2, 1, 2, 3, 2, 1, 0, 1, ... + +( +{ + var x = Dwalk(Dseq([1, 2, 3], inf), dirChangeMode: 1, withDirs: 1); + Duty.ar(SampleDur.ir, 0, x) +}.plot(0.002) +) + + + +// For usage with ZeroXBufRd the dirChangeMode flag needs to be set to 0 (default) +// in order to avoid jumps in the resulting waveform + +// Let's regard how such a sequence looks like + +( +{ + var x = Dwalk(Dseq([1, 2, 3], inf), withDirs: 1); + Duty.ar(SampleDur.ir, 0, x) +}.plot(0.002) +) + + +// It might be counterintuitive that the number sequence starts with two 0s, +// but it makes sense if we regard the input and the definition: + + +// stepsPerDir sequence: +// 1, 2, 3, 1, 2, 3, 1... + + +// step sequence with default stepWidth == 1: +// 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, ... + +// The start value (which is always returned) defaults to 0, +// for the next value to return, a directional change in the step sequence is encountered, +// because of dirChangeMode 0 again 0 is returned. +// The next step is -1, which is no change in direction, thus -1 is effectively added and returned. +// Then the direction is changed to 1, so -1 is returned once more – and so on. + + +// resulting number sequence: +// 0, 0, -1, -1, 0, 1, 1, 1, 2, 2, 1, 0, 0, 0, -1, -1, ... + + + +////////////////////////////////////////////////////////// + + + +// changing stepWidth with default stepMode 0: +// slopes vary, but stay the same within one direction + +( +{ + var x = Dwalk(Dseq([1, 2, 3], inf), Dwhite(0.1, 0.5), dirChangeMode: 1); + Duty.ar(SampleDur.ir, 0, x) +}.plot(0.002) +) + + +// changing stepWidth with default stepMode 1: +// slopes can vary also within one direction + +( +{ + var x = Dwalk(Dseq([1, 2, 3], inf), Dwhite(0.1, 0.5), stepMode: 1, dirChangeMode: 1); + Duty.ar(SampleDur.ir, 0, x) +}.plot(0.002) +) + + +// also ordinary ugens might be passed to stepWidth ... + +( +{ + var x = Dwalk(Dseq([1, 2, 3], inf), SinOsc.ar(1000).range(0.1, 1), dirChangeMode: 1); + Duty.ar(SampleDur.ir, 0, x) +}.plot(0.003) +) + + +// ... or to stepsPerDir, but note that it must be an integer (or will be rounded to one), +// quantisation can lead to surprising patterns + +( +{ + var x = Dwalk(SinOsc.ar(1000).range(1, 5), dirChangeMode: 1); + Duty.ar(SampleDur.ir, 0, x) +}.plot(0.1) +) + +( +{ + var x = Dwalk(SinOsc.ar(1001).range(1, 5), dirChangeMode: 1); + Duty.ar(SampleDur.ir, 0, x) +}.plot(0.1) +) + + +// special case of an ordinary ugen involved in determining the steps per direction plus withDirs set to 1: +// in this case internally a Dunique is used which must not get an ordinary ugen input, +// hence the arguments stepsPerDirMulUGen and stepsPerDirAddUGen must be used. + +( +{ + var x = Dwalk(stepsPerDirMulUGen: SinOsc.ar(1000).range(1, 5).round, dirChangeMode: 1, withDirs: 1); + Duty.ar(SampleDur.ir, 0, x) +}.plot(0.1) +) +:: + + +anchor::Ex.2:: +subsection::Ex.2: Stochastic synthesis + +code:: +// similar to DemandEnvGen Dwalk can be used for stochastic synthesis textures +// LeakDC recommended, though it obfuscates the original waveform + +( +y = { + var x = { + Dwalk( + Dstutter(Dwhite(1, 10), Dwhite(5, 50)), + 0.01, + lo: -1, + hi: 1 + ) } ! 2; + LeakDC.ar(Duty.ar(SampleDur.ir, 0, x)) * 0.5 +}.play +) + +y.release + + +( +y = { + var x = { + Dwalk( + Dstutter(Dwhite(3, 10), Drand((1..200), inf)), + Dstutter(Dstutter(5, Dwhite(1, 30)), Dseq((1..70) ** 2, inf) / 30000), + lo: -1, + hi: 1 + ) } ! 2; + LeakDC.ar(Duty.ar(SampleDur.ir, 0, x)) * 0.3 +}.play +) + +y.release + + +// glitchy + +( +y = { + var x = { + Dwalk( + Dstutter(Dwhite(3, 10), Drand((1..200), inf)), + Dstutter(Dstutter(5, Dwhite(1, 30)), Dseq((1..70) ** 2, inf) / 30000), + lo: SinOsc.ar(0.1).range(-1, 0), + hi: Dstutter( + Drand([ + Dstutter(Dwhite(10, 15), Drand([Dseq((200..1)), Dseq((150..1))], inf)), + Dwhite(5000, 40000, 1) + ], inf), + Dseq([0, 1], inf) + ) + ) } ! 2; + LeakDC.ar(Duty.ar(SampleDur.ir, 0, x)) * 0.3 +}.play +) + +y.release +:: + + + +anchor::Ex.3:: +subsection::Ex.3: Smooth concatenation of adjacent wavesets + +See link::Classes/ZeroXBufRd#Ex. 9:: + +anchor::Ex.4:: +subsection::Ex.4: Smooth concatenation of adjacent segments restricted by turning points resp. local minima or maxima + +See link::Classes/ZeroXBufRd#Ex. 10:: + + + + + + + diff --git a/HelpSource/Classes/EZSlider.ext.schelp b/HelpSource/Classes/EZSlider.ext.schelp new file mode 100644 index 0000000..9013a3e --- /dev/null +++ b/HelpSource/Classes/EZSlider.ext.schelp @@ -0,0 +1,7 @@ + +INSTANCEMETHODS:: + +private:: miSC_colorize + +private:: miSC_adaptToControlStep, miSC_mode, miSC_mode_ + diff --git a/HelpSource/Classes/Event.ext.schelp b/HelpSource/Classes/Event.ext.schelp new file mode 100644 index 0000000..0bc00e0 --- /dev/null +++ b/HelpSource/Classes/Event.ext.schelp @@ -0,0 +1,22 @@ + +INSTANCEMETHODS:: + +method:: asESP +Abbreviation for asEventStreamPlayer + + +method::on +See link::Tutorials/Other_event_and_pattern_shortcuts::. Plays an Event. If strong::dur:: isn't specified it is set to inf. + +method::off +See link::Tutorials/Other_event_and_pattern_shortcuts::. Releases the Event's node. + +argument::releaseTime +SimpleNumber for release time. Defaults to nil (default release time of event mechanism). + + +method::eventShortcuts + +Wraps the receiver into a Pcollect with a Function that replaces according to EventShortcuts if it's turned on. This can be useful in cases where a pattern's method embedInStream changes keys of Events before applying the event type function, that employs the normal replacement mechanism of EventShortcuts. Then the mapping can be done inside/before. Normally you wouldn't need to call that method explicitely. See also link::Classes/EventShortcuts::, link::Classes/Event#-eventShortcuts::, link::Classes/SequenceableCollection#-eventShortcuts:: and link::Tutorials/PLx_and_live_coding_with_Strings:: + + diff --git a/HelpSource/Classes/EventShortcuts.schelp b/HelpSource/Classes/EventShortcuts.schelp new file mode 100644 index 0000000..c6cfd31 --- /dev/null +++ b/HelpSource/Classes/EventShortcuts.schelp @@ -0,0 +1,306 @@ +CLASS:: EventShortcuts +summary:: holds default and user-defined dictionaries of shortcuts for events and event patterns +categories::Libraries>miSCellaneous>Event and pattern shortcuts +related:: Overviews/miSCellaneous, Tutorials/PLx_and_live_coding_with_Strings, Tutorials/Other_event_and_pattern_shortcuts + + +DESCRIPTION:: + +Container for dictionaries of shortcuts for the event framework, which can be defined by the user. Shortcuts might be for event keywords or any other (e.g. synth args). At every time one shortcut dictionary is current, but it's only active if EventShortcuts is turned on. Dictionaries are encapsulated and can only be accessed via copies and posting to prevent unintended changes. For event keywords see James Harkins' Practical Guide to Patterns (especially link::Tutorials/A-Practical-Guide/PG_07_Value_Conversions:: and link::Tutorials/A-Practical-Guide/PG_08_Event_Types_and_Parameters::). + + +note:: +Implementation of shortcuts works like this: if you don't turn EventShortcuts on at all, nothing is changed in the event framework – if you turn it on a mapping function is prepended to every event type function, if this has not been done before in this session and the event type function hasn't been newly defined since last prepending – if you turn it off the prepended function is still there, but does no mapping. + +Therfore shortcuts won't work automatically after new definitions of event types. You'd have to turn EventShortcuts on again or apply the method link::Classes/EventShortcuts#*prefixEventTypes::. Quite obviously, switching between different shortcut dictionaries might cause a mess while playing (or pausing and resuming) patterns with these shortcuts. But these are exceptional cases, a typical usage would be defining your personal shortcut dictionary (e.g. in the startup file), turning EventShortcuts on and playing therewith, then maybe turning it off and on again on occasion. + +Some pattern classes (e.g. Ppar) don't work correctly with EventShortcuts, but this can be circumvented by applying shortcuts to source event patterns before, see method link::Classes/Pattern#-eventShortcuts::. See also link::Tutorials/PLx_and_live_coding_with_Strings:: + +:: + +CLASSMETHODS:: + +private::miSC_replaceShortcuts + +method::add + +Adds a new named IdentityDictionary of shortcuts. + +argument::name +Symbol or String. + +argument::dict +IdentityDictionary of abbreviations (keys given as Symbols) and original names (values given as Symbols). + +argument::overwrite +Boolean. Determines if a shortcut dictionary of that name – if existing at all – is overwritten. Defaults to false. + + + +method::addOnBase + +Adds a new named IdentityDictionary of shortcuts based on the copy of an existing one. + +argument::baseName +Symbol or String. Name of the shortcut dictionary to build upon. + +argument::newName +Symbol or String. Name of the new shortcut set. + +argument::dict +IdentityDictionary of new or/and additional abbreviations (keys given as Symbols) and original names (values given as Symbols). + +argument::overwrite +Boolean. Determines if a shortcut dictionary of that name – if existing at all – is overwritten. Defaults to false. +note:: +strong::overwrite:: only determines overwriting of an old dictionary of the same name. It doesn't influence the overwriting in the copy of the base dictionary itself, as exactly this is a main aim of the method. (you might want to replace the association 's'-> 'strum' by 's' -> 'server') +:: + + +method::remove + +Removes a named IdentityDictionary of that name. + +argument::name +Symbol or String. + + +method::removeAll + +Removes all IdentityDictionaries except \default. + + +method::copyDict + +Returns a copy of a shortcut dictionary of that name (if stored). + +argument::name +Symbol or String. + + + +method::copyCurrentDict + +Returns a copy of the current shortcut dictionary of that name. + +argument::name +Symbol or String. + + +method::copyAllDicts + +Returns an IdentityDictionary of copies of all stored shortcut dictionaries. + + + +method::post + +Posts the shortcut dictionary of that name (if stored). + +argument::name +Symbol or String. + + +method::postCurrent + +Posts the current shortcut dictionary. + + + +method::postAll + +Posts all shortcut dictionaries. + + + +method::makeCurrent + +Makes the shortcut dictionary of that name current (if stored). + +argument::name +Symbol or String. + + + +method::on + +Turns on the shortcut mechanism, making it ready for events / patterns to be played. Also invokes link::Classes/EventShortcuts#*prefixEventTypes::. + + +method::off + +Turns the shortcut mechanism off. + + +method::prefixEventTypes + +Puts the remapping function before all event type functions. Therefore a newly defined event type won't work with shortcuts before this has been called ( directly or via link::Classes/EventShortcuts#*on:: ). + + +method::current + +Returns the Symbol of the current shortcut dictionary. + + +method::dictNames + +Returns the Symbols of all shortcut dictionaries. + + +method::state + +Returns the current state (\on or \off). + + + + + +EXAMPLES:: + + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// turn shortcuts on + +EventShortcuts.on + + +// post all, right now only \default exists + +EventShortcuts.postAll + + +// play an Event with midinote 70 + +(m: 70).play + + +// play a Pbind + +( +Pbind( + \m, Pwhite(60, 90, 20) + [0, -7], // midinote + \p, Pwhite(-1.0, 1), // pan + \l, 3, // legato + \s, 0.1, // strum + \d, 0.5 // dur +).play +) + + + +// define new dictionary based on \default with two different shortcuts + +EventShortcuts.addOnBase(\default, \mine, (s: \scale, t: \ctranspose)) + + +// it isn't current yet, make it + +EventShortcuts.makeCurrent(\mine) + + +// play Pbind with new shortcuts, using key/value notation here + +( +p = Pbind(*[ + de: Prand([[0, 1, 26, 27], [-6, 4, 10], [0, 8, 14]], inf), // degree + t: Pwhite(0, 1) + Pwrand([0, -10, 10], [0.9, 0.05, 0.05], 200), // ctranspose + d: 0.2, // dur + s: Scale.chromatic24 // scale +]).play +) + + +// define a SynthDef + +( +SynthDef(\test, { |out = 0, freq = 440, width = 0.5, amp = 0.05, gate = 1, + att = 0.05, rel = 1, pan = 0| + Out.ar(out, Pan2.ar(Pulse.ar(freq, width, amp), pan) * + EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction:2) + ) +}).add +) + + +// you can also define shortcuts for synthdef args + +( +EventShortcuts.addOnBase(\mine, \mine, (w: \width, r: \rel), true); +EventShortcuts.makeCurrent(\mine); +) + + +( +Pbind(*[ + i: \test, // instrument + n: Prand([0, 4, 7], inf), // note + o: Pwhite(5, 6), // octave + dt: Pwhite(0, 20), // detune (in cent) + l: 0.2, // legato + d: 0.2, // dur + w: Pseq((5, 10..40)/100, 4), // width (synth arg) + r: Pseq([0.1, 0.5], inf), // rel (synth arg) +]).play +) + + +// works also with other event patterns: Pmono, PmonoArtic, Pbindef + +( +Pmono(\default, *[ + d: 0.03, // dur + a: 0.3, // amp + dt: Pseq([0, 10], inf), // detune in Hz + p: Pseq((-100, -95..100)/100, 1) // pan +]).play +) + +// further shortcuts for playing events and patterns are collected in "Other event and pattern shortcuts" +// e.g. you can directly play a Pbind derived from an Array: + +( +[ + n: Pshuf((1..12)), // note + d: 0.5, // dur + l: 3, // legato + o: Pwhite(4, 7) // octave +].pp +) + +// also possible with Pbindef, though you shouldn't change current shortcut +// if you still want to refer later on to a Pbindef defined before the change + + +( +Pbindef(\x, *[ + d: Prand([1,1,2]/5, inf), // dur + m: Pwhite(50, 80), // midinote + ct: Prand([[0, 4], [0, 5]], inf) // ctranspose +]).play +) + +Pbindef(\x, \d, Prand([1,1,1,2,3]/7, inf)) + +Pbindef(\x, \ct, [0, 5, 8, 13, 16]) + +Pbindef(\x).stop; + + +// turns off and ensures that default shortcuts are active when EventShortcuts is +// turned on next time in this session + +( +EventShortcuts.off; +EventShortcuts.makeCurrent(\default); +) + +:: + diff --git a/HelpSource/Classes/EventStreamPlayer.ext.schelp b/HelpSource/Classes/EventStreamPlayer.ext.schelp new file mode 100644 index 0000000..d077174 --- /dev/null +++ b/HelpSource/Classes/EventStreamPlayer.ext.schelp @@ -0,0 +1,5 @@ + +INSTANCEMETHODS:: + +method:: asESP +Abbreviation for asEventStreamPlayer diff --git a/HelpSource/Classes/FFT.ext.schelp b/HelpSource/Classes/FFT.ext.schelp new file mode 100644 index 0000000..89174a5 --- /dev/null +++ b/HelpSource/Classes/FFT.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_getFFTbufSize + + diff --git a/HelpSource/Classes/Fb1.schelp b/HelpSource/Classes/Fb1.schelp new file mode 100755 index 0000000..8db9428 --- /dev/null +++ b/HelpSource/Classes/Fb1.schelp @@ -0,0 +1,1652 @@ +CLASS:: Fb1 +summary:: single sample feedback / feedforward pseudo ugen +categories:: Libraries>miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +Fb1 provides an interface for single sample feedback and feedforward at audio and control rate, the defining relation with (formal) access to previous samples is passed as a Function, which might involve additional UGens. Fb1.ar works with arbitrary blockSizes and also allows to refer to samples earlier than one blockSize before. This includes linear filter definitions of arbitrary length with dynamic coefficients as well as all kinds of nonlinear calculations of feedback and feedforward data (FOS and SOS UGens cover the linear case with lengths 1 and 2, LTI the general linear case). + +Fb1 at control rate exists since miSCellaneous v0.22, for compatibility reasons I left the convention that Fb1.new generates an ar UGen, so Fb1.new is now equivalent to Fb1.ar and Fb1.kr is possible in addition, see link::#Ex. 5#Ex.5::. Fb1 is the base of an ordinary differential equation integrator framework for initial value problems, that also came with v0.22, see link::Classes/Fb1_ODE:: for an introduction. + +strong::HISTORY AND CREDITS: :: There have been long discussions on single-sample feedback in SC. The most simple, but CPU-intense strategy is setting the server's blockSize to 1. Julian Rohrhuber gave a number of examples with Dbufrd / Dbufwr. SC's folder 'Examples' contains the files single_sample_feedback.scd and single_sample_feedback_02.scd. Special solutions are also possible with Delay1, Delay2 and other UGens. This particular implementation is based on Nathaniel Virgo's suggestion of iteratively writing to and reading from Buffers of blockSize – big credit for this! See also Nathaniel Virgo's Feedback quark for his feedback classes Fb and FbNode. Thanks also to James Harkins for his remarks on graph order. See link::#Ex. 1a#Ex.1a:: for the basic feedback implementation principle. I implemented the ar feedforward option by temporary buffers for ar writing and kr reading, the feedback / feedforward relation can now be passed via a Function with 'in' and 'out' args. That way the syntax looks very similar to the common notations used for filter descriptions and also applies directly to the multichannel case. Most other options of Fb1 are for special multichannel handling and differentiated lookback definitions, which can help to save a lot of UGens. + + +warning:: +Be careful with amplitudes, feedback can become loud! It is highly recommended to take measures to avoid blowup, e.g. by limiting operators (tanh, softclip, distort) and/or using MasterFX from the JITLibExtensions quark. Also consider that short iteration cycles can produce loud high pitches, wrapping lopass filters is useful! +:: + +note:: +The convenience of direct definition of the feedback / feedforward relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. You might also want to experiment with blockSizes smaller than 64 and larger than 1 (e.g. 8, 16 or 32). Check the strong::graphOrderType:: arg, other values might cause considerable CPU saving and/or shortening of synthdef compile time. +:: + +CLASSMETHODS:: + +private:: checkInits + + +method::new + +Creates a new Fb1 ar object. + + +argument::func +The Function to define the feedback / feedforward relation. The Function should take the two arguments 'in' and 'out', both understood as nested multichannel signals, additionally a block index is passed (link::#Ex. 3e#Ex.3e::). Each 'in' / 'out' item of the arrays represents current or previous samples, for all points in time the samples are passed in specific array shapes, which are determined by the shapes of strong::in:: and strong::outSize::. Allowed are pure signals (size = 0) and nested SequenceableCollections at maximum: e.g. strong::outSize:: can be 0, 3, or [0, 2, 5], accordingly in signals can be of sizes 0, i or [i1, ... , in] with i, ij >= 0. Note that 'in' and 'out' only formally represent ar feedback and feedforward signals, technically kr UGens (BufRd.kr) are passed, the ar signals are reconstructed at the end by reading (arrays of) Buffers. + +The Function should return the multichannel UGen to be referred to with 'out', the shapes of the returned UGens and strong::outSize:: must be the same. Furthermore the meaning of 'in' and 'out' depends on the strong::inDepth:: and strong::outDepth:: arguments. If an Integer is passed to them (default), the indices of 'in' resp. 'out' correspond to the lookback indices: E.g. out[1] refers to the last output sample(s) (of shape strong::outSize::), out[2] to the output sample(s) before the last output sample(s) etc. This is compliant with the convention of writing out[i-1], out[i-2] etc., out[0] refers to out[i-blockSize]. For a multichannel 'in' / 'out' signal, depth can be differentiated, which saves UGens in the case of "gaps" in the recursion: E.g. for a three-channel out signal strong::outDepth:: can look like [3, [7, 18], [2, 5, 6]]. Then out[1] is a three-channel signal, whereby out[1][0] corresponds to out[i-1] of the first, out[1][1] to out[i-18] of the second and out[1][2] to out[i-5] of the third component. If the size of strong::inDepth:: / strong::outDepth:: is smaller than outSize, wrapping is applied. As a result double-bracketing can be used to define specific lookback indices for all components of the multichannel signal: E.g. if strong::outDepth:: equals [[7, 18]] for a three-channel signal then out[0] means the three-channel signal out[i-7] and out[1] means out[i-18]. See link::#Ex. 3a#Ex.3a:: for multichannel feedback / feedforward. + + +argument::in +A single ar input signal or a SequenceableCollection of ar input signals to be referred to with strong::func:: (feedforward data). See link::#Ex. 3a#Ex.3a:: for multichannel feedback / feedforward. + + +argument::outSize +Integer or SequenceableCollection thereof, the size(s) defined by the UGen(s) returned by strong::func::. It's the user's responsibilty to pass the correct size(s)! Defaults to 0. + + +argument::inDepth +Integer or SequenceableCollection of Integers or SequenceableCollections thereof, this determines the behaviour of func (see there). If an Integer is passed, it means the maximum storage size for feedforward data. If a SequenceableCollection is passed, lookback indices for feedforward data can be differentiated, its items can again be Integers or SequenceableCollections (see strong::func::). Usually the inner SequenceableCollections should be ordered, but this is not compulsory. Defaults to 1 (no lookback). See link::#Ex. 3c#Ex.3c::. + + +argument::outDepth +Integer or SequenceableCollection of Integers or SequenceableCollections thereof, this determines the behaviour of func (see there). If an Integer is passed, it means the maximum storage size for feedback data. If a SequenceableCollection is passed, lookback indices for feedback data can be differentiated, its items can again be Integers or SequenceableCollections (see strong::func::). Usually the inner SequenceableCollections should be ordered, but this is not compulsory. Defaults to 2 (look back to last sample at maximum). See link::#Ex. 3c#Ex.3c::. + + +argument::inInit +Number or SequenceableCollection, feedforward init data. If a Number is passed, it means the previous init value for the calculation of the first sample(s), if the size of in is larger than 1, this init value is taken for all components of the multichannel signal. If a SequenceableCollection is passed, this differentiates the init values for a multichannel signal 'in' used by strong::func::. Then the components must be Numbers (again defining one init value) or SequenceableCollections, which define a lookback collection: first Number is the previous value, second the value before and so on. If the size of strong::inInit:: is smaller than the size of strong::in::, wrapping is applied, that way a double-bracket array, e.g. [[3, 0, 1]], defines the same init sequence for all components of a multichannel strong::in::. See link::#Ex. 3b#Ex.3b::. + + + +argument::outInit +Number or SequenceableCollection, feedback init data. If a Number is passed, it means the previous init value for the calculation of the first sample(s), if strong::outSize:: is larger than 1, this init value is taken for all components of the multichannel signal 'out' used by strong::func::. If a SequenceableCollection is passed, this differentiates the init values for this multichannel signal. Then the components must be Numbers (again defining one init value) or SequenceableCollections, which define a lookback collection: first Number is the previous value, second the value before and so on. If the size of strong::outInit:: is smaller than strong::outSize::, wrapping is applied, that way a double-bracket array, e.g. [[3, 0, 1]], defines the same init sequence for all components of the multichannel signal 'out' used by strong::func::. See link::#Ex. 3b#Ex.3b::. + + +argument::blockSize +Integer, this should be the server blockSize. It's the user's responsibility to pass the correct number. However it might be interesting to experiment with other values. Defaults to 64. See link::#Ex. 3d#Ex.3d::. + + +argument::blockFactor +Integer. For a value > 1 this allows for lookback indices larger than strong::blockSize::, up to strong::blockSize:: * strong::blockFactor:: - 1. It's the user's responsibility to pass correct Integers in this case. Defaults to 1. See link::#Ex. 3d#Ex.3d::. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and = 0. Note that 'in' and 'out' only formally represent feedback and feedforward signals, technically kr UGens (BufRd.kr) are passed. + +The Function should return the multichannel UGen to be referred to with 'out', the shapes of the returned UGens and strong::outSize:: must be the same. Furthermore the meaning of 'in' and 'out' depends on the strong::inDepth:: and strong::outDepth:: arguments. If an Integer is passed to them (default), the indices of 'in' resp. 'out' correspond to the lookback indices: E.g. out[1] refers to the last control output sample(s) (of shape strong::outSize::), out[2] to the control output sample(s) before the last control sample(s) etc. This is compliant with the convention of writing out[i-1], out[i-2] etc. For a multichannel 'in' / 'out' signal, depth can be differentiated, which saves UGens in the case of "gaps" in the recursion: E.g. for a three-channel out signal strong::outDepth:: can look like [3, [7, 18], [2, 5, 6]]. Then out[1] is a three-channel signal, whereby out[1][0] corresponds to out[i-1] of the first, out[1][1] to out[i-18] of the second and out[1][2] to out[i-5] of the third component. If the size of strong::inDepth:: / strong::outDepth:: is smaller than outSize, wrapping is applied. As a result double-bracketing can be used to define specific lookback indices for all components of the multichannel signal: E.g. if strong::outDepth:: equals [[7, 18]] for a three-channel signal then out[0] means the three-channel signal out[i-7] and out[1] means out[i-18]. See link::#Ex. 3a#Ex.3a:: for multichannel feedback / feedforward. + + +argument::in +A single kr input signal or a SequenceableCollection of kr input signals to be referred to with strong::func:: (feedforward data). See link::#Ex. 3a#Ex.3a:: for multichannel feedback / feedforward. + + +argument::outSize +Integer or SequenceableCollection thereof, the size(s) defined by the UGen(s) returned by strong::func::. It's the user's responsibilty to pass the correct size(s)! Defaults to 0. + + +argument::inDepth +Integer or SequenceableCollection of Integers or SequenceableCollections thereof, this determines the behaviour of func (see there). If an Integer is passed, it means the maximum storage size for feedforward data. If a SequenceableCollection is passed, lookback indices for feedforward data can be differentiated, its items can again be Integers or SequenceableCollections (see strong::func::). Usually the inner SequenceableCollections should be ordered, but this is not compulsory. Defaults to 1 (no lookback). See link::#Ex. 3c#Ex.3c::. + + +argument::outDepth +Integer or SequenceableCollection of Integers or SequenceableCollections thereof, this determines the behaviour of func (see there). If an Integer is passed, it means the maximum storage size for feedback data. If a SequenceableCollection is passed, lookback indices for feedback data can be differentiated, its items can again be Integers or SequenceableCollections (see strong::func::). Usually the inner SequenceableCollections should be ordered, but this is not compulsory. Defaults to 2 (look back to last control sample at maximum). See link::#Ex. 3c#Ex.3c::. + + +argument::inInit +Number or SequenceableCollection, feedforward init data. If a Number is passed, it means the previous init value for the calculation of the first control sample(s), if the size of in is larger than 1, this init value is taken for all components of the multichannel signal. If a SequenceableCollection is passed, this differentiates the init values for a multichannel signal 'in' used by strong::func::. Then the components must be Numbers (again defining one init value) or SequenceableCollections, which define a lookback collection: first Number is the previous value, second the value before and so on. If the size of strong::inInit:: is smaller than the size of strong::in::, wrapping is applied, that way a double-bracket array, e.g. [[3, 0, 1]], defines the same init sequence for all components of a multichannel strong::in::. See link::#Ex. 3b#Ex.3b::. + + +argument::outInit +Number or SequenceableCollection, feedback init data. If a Number is passed, it means the previous init value for the calculation of the first control sample(s), if strong::outSize:: is larger than 1, this init value is taken for all components of the multichannel signal 'out' used by strong::func::. If a SequenceableCollection is passed, this differentiates the init values for this multichannel signal. Then the components must be Numbers (again defining one init value) or SequenceableCollections, which define a lookback collection: first Number is the previous value, second the value before and so on. If the size of strong::outInit:: is smaller than strong::outSize::, wrapping is applied, that way a double-bracket array, e.g. [[3, 0, 1]], defines the same init sequence for all components of the multichannel signal 'out' used by strong::func::. See link::#Ex. 3b#Ex.3b::. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and 1 blow up ! + // only the next line is limited with softclip + + // in[0][0] is the current stereo signal from the bus + // in[1][0] is the previous + // in[0][1] is the current mono lfo, passed with the 'in' arg + + // func returns a stereo signal, so outSize must be passed 2 + // inDepth [2, 1] because we use inSig[0], inSig[1] and lfo[0] + // outDepth 3 because we use out[2] + // note that it would be cheaper to use out[0] and outDepth: [[2]], + // but a bit more difficult to read + + ((in[0][0] - in[1][0]) % 0.01 * in[0][1]).softclip + }, [inSig, lfo], 2, [2, 1], 3 + ), 15000) * 0.1 +}.play +) + +// start source + +x = { Out.ar(~bus, SinOsc.ar([60, 60.1]) * EnvGate.new) }.play + +// stop source and start new one + +x.release + +x = { Out.ar(~bus, Saw.ar(SinOsc.ar(0.07).linlin(-1, 1, [100, 100.01], 100.2) * EnvGate.new)) }.play + + +x.release + +( +y.free; +~bus.free; +) + +:: + + +anchor::Ex. 2b:: +subsection::Ex. 2b: sin + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// sin can work as a limiter as well as a nonlinear dynamics engine, +// here it causes a wavefolding-like effect + +s.scope + +// start fb fx Synth + +( +~bus = Bus.audio(s, 1); + +y = { + var inSig = In.ar(~bus); + var lfo = SinOsc.ar(0.1, -pi/2).linexp(-1, 1, [5, 10], 100) * 100; + LPF.ar( + Fb1({ |in, out| + // here out[0] refers to out[i-2] because of outDepth: [[2]] + (out[0] * 0.7) + // factors > 1 blow up ! + // here in[0][0] and in[1][0] are current and previous mono inSig, + // but in[0][1] is stereo, so again a stereo signal is returned by func, + // which must be indicated with the outSize arg + ((in[0][0] - in[1][0]) * in[0][1]).sin + }, [inSig, lfo], 2, [2, 1], [[2]] + ) * 0.05, 15000) +}.play +) + +// start source + +x = { Out.ar(~bus, SinOsc.ar(60) * EnvGate.new) }.play; + +x.release; + +// stop source and start new one + +x = { Out.ar(~bus, Saw.ar(SinOsc.ar(0.05).linlin(-1, 1, 100, 100.02) * EnvGate.new)) }.play + +x.release + +( +y.free; +~bus.free; +) +:: + + + +anchor::Ex. 2c:: +subsection::Ex. 2c: * + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// rather irrational concatenation of simple operations +// depth changes cause frequency changes + +s.scope + +// start fb fx Synth + +( +~bus = Bus.audio(s, 1); + +y = { + var inSig = In.ar(~bus); + var lfo = { LFDNoise3.ar(0.07).linexp(-1, 1, 1, 5) } ! 2; + var i = Demand.kr(Dust.kr(0.5), 0, Dxrand((0..2), inf)); + var sig = Fb1({ |in, out| + ( + in[0][1] * ( + in[0][0] * 0.12 + ( + // changes between out[i-23], out[i-41] and out[i-60] cause frequency changes, + // Select depends on a signal from outside, not previous samples as in Ex. 2f + (in[1][0].squared - Select.kr(i, out).squared).sqrt + ) + ) + ).tanh + }, [inSig, lfo], 2, [2, 1], [[23, 41, 60]]) * 0.1; + // add frequency modulation by delay modulation + // lopass filtering with lag + DelayC.ar(sig, 0.2, LFDNoise3.ar(1).range(0.01, 0.1)).lag(0.0005) +}.play +) + +// start source + +x = { Out.ar(~bus, LFTri.ar(LFDNoise0.ar(5).exprange(1, 100)) * EnvGate.new) }.play; + +x.release + +( +y.free; +~bus.free; +) +:: + + + +anchor::Ex. 2d:: +subsection::Ex. 2d: / + + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// With divisions we must avoid division by zero resp. blowup, here it's max in the divisor. +// The example also establishes a cross-feedback of the two channels by using reverse. + +s.scope + +// start fb fx Synth + +( +~bus = Bus.audio(s, 2); + +y = { + var inSig = In.ar(~bus, 2); + var lfo = LFDNoise3.ar(1).linexp(-1, 1, 0.2, 10); + LPF.ar( + Fb1({ |in, out| + (in[1][0] * in[0][1] / max(0.001, (in[1][0] - out[1].reverse).abs)).tanh + }, [inSig, lfo], 2, [2, 1], 2 + ), 15000) * 0.1 +}.play +) + +// start source + +x = { Out.ar(~bus, SinOsc.ar(LFDNoise3.ar(0.1!2).range(100, 101)) * EnvGate.new) }.play + +x.release + +( +y.free; +~bus.free; +) +:: + + +anchor::Ex. 2e:: +subsection::Ex. 2e: ** + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + + +// exponentiation can also be interesting +// here an area of instability is crossed by a stereo lfo + +s.scope + +// start fb fx Synth + +( +~bus = Bus.audio(s, 1); + +y = { + var inSig = In.ar(~bus); + var lfo = { LFDNoise3.ar(0.5).linexp(-1, 1, 0.1, 150) } ! 2; + + Fb1({ |in, out| + in[1][0] * 0.07 + + // out[0] refers to out[i-2] because of outDepth: [[2]] + (2 ** (in[1][0] - out[0] * in[0][1]).abs).tanh + }, [inSig, lfo], 2, [2, 1], [[2]]) * + // avoid bump at start + EnvGen.ar(Env.asr(2)) +}.play +) + + +// start source + +x = { Out.ar(~bus, LFTri.ar(60) * EnvGate.new) }.play; + + +x.release + +( +y.free; +~bus.free; +) +:: + + +anchor::Ex. 2f:: +subsection::Ex. 2f: Conditional feedback + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// defining the next sample depending on some characteristics of the previous one(s) +// This can be done with the if UGen and Select. +// 'if' doesn't support multichannel expansion, so take Select here + +s.scope + +// noisy texture with beeps + +( +x = { + var src = LFDNoise3.ar(1, 0.1); + // ar modulators to be passed (avoid annoying steady tone caused by kr) + var mod1 = LFDNoise3.ar(1).range(0.01, 0.2); + + // already slight difference results in quite strong stereo decorrelation + var mod2 = LFDNoise3.ar(1).range([0.0001, 0.0002], 0.0049); + + Fb1({ |in, out| + // give same names as above for better readability + var src = in[0][0]; + var mod1 = in[0][1]; + var mod2 = in[0][2]; + softclip( + Select.kr( + // as mod2 is stereo we get stereo expansion + // and in turn different selections + + // outDepth = [[1, 6]] + // so out[0] refers to out[i-1], out[1] to out[i-6] + + out[0] % 0.005 < mod2, + [out[1].neg * mod1, out[0] * 0.1] + ) + src + out[0] + ) + // lopass filtering with lag + }, [src, mod1, mod2], 2, 1, [[1, 6]]).lag(0.001) * 0.5 +}.play +) + +x.release +:: + + + +section::Examples 3: Conventions and args + + + + +anchor::Ex. 3a:: +subsection::Ex. 3a: Multichannel feedback / feedforward + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// in and out can be multichannel signals of arbitrary size or collections thereof, +// however arbitrary nesting is not supported, +// outSize arg has to be passed explicitely, +// size of in arg is taken over automatically. + +// also mind the difference between size 0 and 1: +// with outSize of 1 or [0] Fb1 returns an array + +// here out is of size 3, in of sizes [3, 0] + +( +x = { + var inSig = SinOsc.ar(LFDNoise3.ar(0.01 ! 3).range(100, 101)); // 3 channel in signal + var lfo = LFDNoise3.ar(0.1).linexp(-1, 1, 0.2, 10); + var sig; + sig = LPF.ar( + Fb1({ |in, out| + // in[0][0] and in[1][0] represent current and previous 3 channel samples from inSig + // in[0][1] represents current sample from lfo + // rotate causes cross-feedback of 3 channels + // with reverse only first and last would cross + (in[1][0] * in[0][1] / max(0.001, (in[1][0] - out[1].rotate(1)).abs)).tanh + // outSize 3 has to be passed + }, [inSig, lfo], 3, [2, 1], 2 + ), 15000) * 0.1; + Splay.ar(sig) +}.play +) + + +x.release + + +// again out is of size 3, in of sizes [3, 3] + +( +x = { + var inSig = SinOsc.ar(LFDNoise3.ar(0.01 ! 3).range(100, 101)); // 3 channel in signal + var mod = SinOsc.ar([1, 2.001, 3.999] * 120).linexp(-1, 1, 0.2, 10); + var sig; + sig = LPF.ar( + Fb1({ |in, out| + // in[0][0] and in[1][0] represent current and previous 3 channel samples from inSig + // in[0][1] represents current 3 samples from mod + // rotate causes cross-feedback of 3 channels + // with reverse only first and last would cross + (in[1][0] * in[0][1] / max(0.001, (in[1][0] - out[1].rotate(1)).abs)).tanh + // outSize has to be passed + }, [inSig, mod], 3, [2, 1], 2 + ), 15000) * 0.05; + Splay.ar(sig) +}.play +) + +x.release + + +// it's also possible to let func return an array of (multichannel) signals, +// outSize must be set accordingly, here [2, 0], +// whereby the mono within the array is a "helper feedback" + +( +x = { + var inSig = SinOsc.ar(LFDNoise3.ar(1.5 ! 2).exprange(50, 100)); + var lfo = LFDNoise3.ar(5).linexp(-1, 1, 0.5, 100); + var sig; + sig = LPF.ar( + Fb1({ |in, out| + // in[1][0] represents previous 2 channel samples from inSig + // in[0][1] represents current sample from lfo + + // reverse causes cross-feedback of 2 main channels + // main feedback crosses with helper feedback + [ + // for main feedback use helper feedback + (out[1][1] / max(0.001, (in[1][0] - out[1][0].reverse).abs)).tanh, + // for helper feedback use first channel of main feedback + (out[1][0][0] + 0.1 / max(0.01, ((in[0][1].abs)))).tanh + ] + // outSize [2, 0] has to be passed + }, [inSig, lfo], [2, 0], [2, 1], 2 + ), 12000) * 0.2; + // return main feedback + sig[0] +}.play +) + +x.release + + + +// here 2 x stereo, outSize == [2, 2] + +( +x = { + // two stereo sources + var in_1 = SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(100, 101)); + var in_2 = SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(150, 151)); + var lfo = LFDNoise3.ar(0.1).linexp(-1, 1, 0.2, 10); + var sig; + sig = LPF.ar( + Fb1({ |in, out| + // rename for better readability + + // previous ins are stereo + var prevIn_1 = in[1][0]; + var prevIn_2 = in[1][1]; + + // mono lfo + var lfo = in[0][2]; + + // out is 2 x 2 (see below) + var prevOut_1 = out[1][0]; // stereo + var prevOut_2 = out[1][1]; // stereo + + // we return an array of two stereo signals + [ + (prevIn_1 * lfo / max(0.001, (prevIn_1 - prevOut_1.reverse).abs)), + (prevIn_2 * lfo / max(0.001, (prevIn_2 - prevOut_2.reverse).abs)) + ].tanh + + // outSize [2, 2] has to be passed + }, [in_1, in_2, lfo], [2, 2], 2, 2 + ), 15000) * 0.05; + sig[0] + sig[1]; // mix together + // sig[0]; + // sig[1]; +}.play +) + +x.release + + +// variant: cross feedback within first stereo out (a) plus +// cross feedback the stereo signals with each other (b) + +// at the end take only first stereo out +// because of (b) the 150 Hz of in_2 are contained in the resulting signal + +( +x = { + var in_1 = SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(100, 101)); + var in_2 = SinOsc.ar(LFDNoise3.ar(0.1 ! 2).range(150, 151)); + var lfo = LFDNoise3.ar(0.1).linexp(-1, 1, 0.2, 10); + var sig; + sig = LPF.ar( + Fb1({ |in, out| + // rename for better readability + + // previous ins are stereo + var prevIn_1 = in[1][0]; + var prevIn_2 = in[1][1]; + + // mono lfo + var lfo = in[0][2]; + + // out is 2 x 2 (see below) + var prevOut_1 = out[1][0]; // stereo + var prevOut_2 = out[1][1]; // stereo + + // we return an array of two stereo signals + + [ + (prevIn_1 * lfo / max(0.001, (prevIn_1 - prevOut_2.reverse).abs)), + (prevIn_2 * lfo / max(0.001, (prevIn_2 - prevOut_1).abs)) + ].tanh + + // outSize has to be passed + }, [in_1, in_2, lfo], [2, 2], 2, 2 + ), 15000) * 0.05; + sig[0] +}.play +) + +x.release + + +:: + + + + +anchor::Ex. 3b:: +subsection::Ex. 3b: inInit / outInit + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// linear congruential generator + +// this is not a strict linear congruential generator +// as the server doesn't know integers, it's done with floats, +// all is blurred by floating point inaccuracy +// however interesting results can be obtained + +// different start values can produce different orbits + +// WARNING: can produce loud high pitches with certain init values and factors +// as a result of short iteration cycles, take LPF ! + + +// same init value for both channels + +( +x = { + var sig = Fb1({ |in, out| + out[1] * 5.239 % 1 + }, + outSize: 2, + outInit: 1 // [1] is equivalent + ).tanh * 0.2; + LPF.ar(sig, 2000) +}.play +) + +x.release + + +// other iteration sequence by different init value + +( +x = { + var sig = Fb1({ |in, out| + out[1] * 5.239 % 1 + }, + outSize: 2, + outInit: 2 + ).tanh * 0.2; + LPF.ar(sig, 2000) +}.play +) + +x.release + + +// defining different init values per channel + +( +x = { + var sig = Fb1({ |in, out| + out[1] * 5.239 % 1 + }, + outSize: 2, + outInit: [1, 2] + ).tanh * 0.2; + LPF.ar(sig, 2000) +}.play +) + +x.release + + + +// mono, two init values for one channel (previous and previous of previous) +// this needs double brackets for outInit + +( +x = { + var sig = Fb1({ |in, out| + out[1] * 2 + (out[2] * 3) % 1.01 + }, + outSize: 1, + outInit: [[2, 6]], + outDepth: 3 // needed as we look back for 2 values + ).tanh * 0.2; + LPF.ar(sig, 2000) +}.play +) + +x.release + + +// stereo, different arrays of init values per channel + +( +x = { + var sig = Fb1({ |in, out| + out[1] * 2 + (out[2] * 3) % 1.01 + }, + outSize: 2, + outInit: [[3, 1], [2, 5]], + outDepth: 3 + ).tanh * 0.2; + LPF.ar(sig, 2000) +}.play +) + +x.release + + +// inInit values can be defined in the same way as outInit + +// want a trigger at start in connection with Dust + +( +x = { + var trig = Dust.ar(0.3); + var src = SinOsc.ar(1000); + var sig = Fb1({ |in, out| + var tr = in[1][0]; + var mod = in[0][1]; + (out[1] + tr * (mod * 0.02 + 0.999999)) + }, + in: [trig, src], + inDepth: 2, + // here both in buffers get init values, only the first is relevant + inInit: 1, // [1, 0] doesn't make a difference + ).lag(0.005).tanh * 0.5; + sig +}.play +) + +x.release + +// inInit and outInit cannot be differentiated for a multichannel component of a multichannel in / out. +// If you want to do such, you'd have to split the inner +// multichannel component and differentiate the outer one. +:: + + + +anchor::Ex. 3c:: +subsection::Ex. 3c: inDepth / outDepth + + + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// Normally, and like in most previous examples, +// out[j] and in[j] in func refer to samples out[i-j] and in[i-j], +// in[0] to the current input samples. +// The lookback size can be determined by passing Integers to inDepth / outDepth, e.g. +// with inDepth = 2 you can refer to in[0] and in[i-1] +// with outDepth = 3 you can refer to out[i-1] and out[i-2], +// out[0] refers to out[i-blockSize]. + +// When refering not to previous but to earlier samples, +// passing specified inDepth / outDepth indices is saving UGens. + +// Here in[0] refers to in[0], the current sample(s), and +// in[1] refers to in[i-56]. + +( +x = { + var src = SinOsc.ar(500 * LFDNoise3.ar(5)); + var sig = Fb1({ |in, out| + (out[1] / max(0.01, (in[1] - in[0]))).tanh + }, + in: src, + inDepth: [[0, 56]], + outInit: 1 + ) ! 2 * 0.1 ; + sig +}.play +) + +x.release + + +// looking back less far changes the sound colour + +( +x = { + var src = SinOsc.ar(500 * LFDNoise3.ar(5)); + var sig = Fb1({ |in, out| + (out[1] / max(0.01, (in[1] - in[0]))).tanh + }, + in: src, + inDepth: [[0, 29]], + outInit: 1 + ) ! 2 * 0.1; + sig +}.play +) + +x.release + + +// differentiate inDepth per channel + +( +x = { + // stereo in + var src = SinOsc.ar(500 * LFDNoise3.ar(5!2)); + var sig = Fb1({ |in, out| + (out[1] / max(0.01, (in[1] - in[0]))).tanh + }, + outSize: 2, + in: src, + inDepth: [[0, 29], [0, 56]], + outInit: 1 + ) * [0.2, 0.1]; + sig +}.play +) + +x.release + +// inDepth and outDepth cannot be differentiated for a multichannel component of a multichannel in / out. +// If you want to do such, you'd have to split the inner +// multichannel component and differentiate the outer one. +:: + + + + +anchor::Ex. 3d:: +subsection::Ex. 3d: blockSize / blockFactor + + + +code:: + +// Normally Fb1's blockSize should equal the server's current blockSize, +// which can be set as a server option, per default it's 64. + +// If you are using a different blockSize, you can either +// reset it for the examples in this helpfile ... + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// ... or run the examples with passing a different blockSize, e.g. with: + +Fb1(..., blockSize: s.options.blockSize) + + +// You can however try to creativily use a "wrong" blockSize and play with artefacts, +// variant of Ex. 3c + +( +x = { + // stereo in + var src = SinOsc.ar(300 * LFDNoise3.ar(1!2)); + var sig = Fb1({ |in, out| + (out[1] / max(0.01, (in[1] - in[0]))).tanh + }, + outSize: 2, + in: src, + inDepth: [[0, 15], [0, 19]], + outInit: 1, + blockSize: 33 + ) * 0.2; + LPF.ar(sig, 3000) +}.play +) + +x.release + +// variant of Ex. 3c +// suppose a blockSize of 64, to look back to in[i-150] set blockFactor to 3. + +( +x = { + // stereo in + var src = SinOsc.ar(500 * LFDNoise3.ar([0.3, 7])); + var sig = Fb1({ |in, out| + (out[1] / max(0.001, (in[1] - in[0]))).distort + }, + outSize: 2, + in: src, + inDepth: [[0, 150], [0, 29]], + outInit: 1, + blockFactor: 3 + ) * [0.07, 0.15]; + sig +}.play +) + +x.release +:: + + + + +anchor::Ex. 3e:: +subsection::Ex. 3e: func index + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// func can take an index as third arg, it runs from 0 to blockSize - 1 +// this can be used to define the feedback relation depending on it + +// note that inDepth is set to [2, 1] as we look back to inSig[i-1] (in[1][0]), +// but not to lfo[i-1] (in[0][1] == lfo[0]), this saves 128 UGens ! + + +( +x = { + var inSig = SinOsc.ar([50, 50.1]); + var lfo = SinOsc.ar(LFDNoise3.ar(0.1).range(0, 500)).range(0, [100, 105]); + LPF.ar( + Fb1({ |in, out, i| + ( + // establish alternating feedback relations in the synthdef graph + i.odd.if { + in[0][0] * in[0][1] + out[1] + }{ + (in[0][0] - in[1][0]) * out[1] + } + ).tanh + }, [inSig, lfo], 2, [2, 1], 2 + ) * 0.1, 12000) +}.play +) + +x.release + +:: + + + + +anchor::Ex. 4:: +section::Ex. 4: Saving CPU + + +code:: +// check blockSize before (or reset blockSize in examples) + +( +if (s.options.blockSize != 64) { + s.options.blockSize = 64; + s.quit.reboot; +} +) + +// UGens written in func are generated as often as blockSize. +// Therefore, if possible, references to kr UGens outside save resources. +// In addition look for hidden unnecessary operations, which can add hundreds of UGens + + +// 2440 UGens (with blockSize == 64) +// deliberately bad, deterministic lfo is generated blockSize times + + +( +x = { + var sig, src; + src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25); + sig = Fb1({ |in, out| + var a = in[0]; + var b = out[1]; + var lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01); + softclip((a * a * a) + (a * a) + a + (a * a * b) / max(lfo, a + b)); + }, src, 2) * 0.1; + LPF.ar(sig, 3000) +}.play +) + +x.release + +// 2188 UGens as (blockSize - 1) * 4 = 63 * 4 = 252 UGens are saved + +( +x = { + var sig, src, lfo; + src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25); + lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01); + sig = Fb1({ |in, out| + var a = in[0]; + var b = out[1]; + softclip((a * a * a) + (a * a) + a + (a * a * b) / max(lfo, a + b)); + }, src, 2) * 0.1; + LPF.ar(sig, 3000) +}.play +) + +x.release + + +// still bad though when looking at the the algebraic identity +// a^3 + a^2 + a + (a * a * b) = ((a + b) * a + a) * a + a +// these are 8 vs 5 operations, so with stereo we can save further +// ((8 - 5) * 2) * 64 = 384 UGens + +// 1804 UGens + +( +x = { + var sig, src, lfo; + src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25); + lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01); + sig = Fb1({ |in, out| + var a = in[0]; + var b = out[1]; + // with SC's L/R-precendence we can write without brackets + softclip(a + b * a + a * a + a / max(lfo, a + b)); + }, src, 2) * 0.1; + LPF.ar(sig, 3000) +}.play +) + + +x.release + + +// without forcing the topological order of BufRds and BufWrs further UGens are saved, +// this might or might not be the same result, might be distorted in worst case, +// however in all my tests I didn't encounter a single example where it was different + +// 1713 UGens + + +( +x = { + var sig, src, lfo; + src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25); + lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01); + sig = Fb1({ |in, out| + var a = in[0]; + var b = out[1]; + // with SC's L/R-precendence we can write without brackets + softclip(a + b * a + a * a + a / max(lfo, a + b)); + }, src, 2, graphOrderType: 0) * 0.1; + LPF.ar(sig, 3000) +}.play +) + +x.release + + +// check if it's the same - run silently + +( +x = { + var sig, src, lfo; + src = SinOsc.ar(90 * LFDNoise3.ar(0.3!2).range(0.98, 1.02)) * SinOsc.ar(45.25); + lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01); + sig = Fb1({ |in, out| + var a = in[0]; + var b = out[1]; + // with SC's L/R-precendence we can write without brackets + softclip(a + b * a + a * a + a / max(lfo, a + b)); + }, src, 2, graphOrderType: 0) - + Fb1({ |in, out| + var a = in[0]; + var b = out[1]; + softclip(a + b * a + a * a + a / max(lfo, a + b)); + }, src, 2) * 0.1; + LPF.ar(sig, 3000) +}.play +) + +x.release + +// see also Ex.5 for saving CPU with kr +:: + + + +anchor::Ex. 5:: +section::Ex. 5: Control rate + + +code:: +// Ex.4 with kr and K2A + +( +x = { + var sig, src, lfo; + src = SinOsc.kr(90 * LFDNoise3.kr(0.3!2).range(0.98, 1.02)) * SinOsc.kr(45.25); + lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01); + sig = Fb1.kr({ |in, out| + var a = in[0]; + var b = out[1]; + // with SC's L/R-precendence we can write without brackets + softclip(a + b * a + a * a + a / max(lfo, a + b)); + }, src, 2, graphOrderType: 0) * 0.1; + LPF.ar(K2A.ar(sig), 3000) +}.play +) + +x.release + + +// FM with same signal + +( +x = { + var sig, src, lfo; + src = SinOsc.kr(90 * LFDNoise3.kr(0.3!2).range(0.98, 1.02)) * SinOsc.kr(45.25); + lfo = SinOsc.kr(SinOsc.kr(0.1).range(0.03, 1)).range(0.001, 0.01); + sig = Fb1.kr({ |in, out| + var a = in[0]; + var b = out[1]; + // with SC's L/R-precendence we can write without brackets + softclip(a + b * a + a * a + a / max(lfo, a + b)); + }, src, 2, graphOrderType: 0) * 0.1; + SinOsc.ar(2000 * sig, 0, 0.1) +}.play +) + +x.release +:: + diff --git a/HelpSource/Classes/Fb1_Duffing.schelp b/HelpSource/Classes/Fb1_Duffing.schelp new file mode 100755 index 0000000..2c7b6be --- /dev/null +++ b/HelpSource/Classes/Fb1_Duffing.schelp @@ -0,0 +1,320 @@ +CLASS:: Fb1_Duffing +summary:: Duffing pseudo ugen +categories:: Libraries>miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol + + +DESCRIPTION:: + +Fb1_ODE wrapper for the Duffing ODE system with external f: + +y'(t) = w(t) + +w'(t) = f(t) + (gamma * cos(omega * t)) - (delta * w(t)) - (beta * y(t)^3) - (alpha * y(t)) + +coming from the 2nd order equation + +y''(t) + (delta * y'(t)) + (alpha * y(t)) + (beta * y(t)^3) = (gamma * cos(omega * t)) + f(t) + + +It returns a 2-channel signal. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens. + + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[2]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[3]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: + + +subsection::References + +numberedList:: +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +method::new + +Creates a new Fb1_Duffing ar object. + + +argument::f +Defaults to 0. + + +argument::alpha +Defaults to 0. + + +argument::beta +Defaults to 1. + + +argument::gamma +Defaults to 1. + + +argument::delta +Defaults to 1. + + +argument::omega +Defaults to 1. + + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. Defaults to 0. + + +argument::y0 +Initial value of the ODE. Expects array of size 2. Defaults to #[1, 0]. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +Fb1_ODE wrapper for the Hopf ODE system with external f: + +v'(t) = (mu - (v(t)^2 + w(t)^2)) * v(t) - (theta * w(t)) + f(t) + +w'(t) = (mu - (v(t)^2 + w(t)^2)) * w(t) + (theta * v(t)) + +It returns a 2-channel signal. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens. + + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[4]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[5]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: +anchor::[4]:: +anchor::[5]:: + +subsection::References + +numberedList:: +## Righetti, Ludovic; Buchli, Jonas; Ijspeert, Auke Jan (2009): "Adaptive Frequency Oscillators and Applications". The Open Cybernetics and Systemics Journal, 3, 64-69. https://www.researchgate.net/publication/41666931_Adaptive_Frequency_Oscillators_and_Applications_Open_Access Summary: https://biorob.epfl.ch/research/research-dynamical/page-36365-en-html +## Nachstedt, Timo; Tetzlaff, Christian; Manoonpong, Poramate (2017): "Fast Dynamical Coupling Enhances Frequency Adaptation of Oscillators for Robotic Locomotion Control". Frontiers in Neurorobotics. Published online 2017 Mar 21 https://www.frontiersin.org/articles/10.3389/fnbot.2017.00014/full +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +method::new + +Creates a new Fb1_Hopf ar object. + + +argument::f +Defaults to 0. + + +argument::mu +Defaults to 1. + + +argument::theta +Defaults to 1. + + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. Defaults to 0. + + +argument::y0 +Initial value of the ODE. Expects array of size 2. Defaults to #[1, 1]. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withOutScale +Boolean. Determines if the Fb1_ODEdef's default scaling values for integration and differential signals should be applied to the output. Defaults to true. +warning:: strong::withOutScale:: does not implement a general safety net functionality, strong::withOutScale::'s default value true does by no means say that output is limited. The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, under different circumstances, always lead to high out levels. +:: + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +This is an adaptive variant of Hopf, i.e. it can hold the frequency of an oscillating input f after it vanishes. The model refers to link::#[1]::, see also link::#[2]:: (4.1.) for an enhancement implemented with link::Classes/Fb1_HopfAFDC::. The parameter naming follows the convention of link::#[2]::. + +v'(t) = (mu - (v(t)^2 + w(t)^2)) * v(t) - (theta(t) * w(t)) + f(t) + +w'(t) = (mu - (v(t)^2 + w(t)^2)) * w(t) + (theta(t) * v(t)) + +theta'(t) = -eta * f(t) * w(t) / sqrt(v(t)^2 + w(t)^2) + +It returns a 3-channel signal. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens. + + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[4]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[5]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: +anchor::[4]:: +anchor::[5]:: + +subsection::References + +numberedList:: +## Righetti, Ludovic; Buchli, Jonas; Ijspeert, Auke Jan (2009): "Adaptive Frequency Oscillators and Applications". The Open Cybernetics and Systemics Journal, 3, 64-69. https://www.researchgate.net/publication/41666931_Adaptive_Frequency_Oscillators_and_Applications_Open_Access Summary: https://biorob.epfl.ch/research/research-dynamical/page-36365-en-html +## Nachstedt, Timo; Tetzlaff, Christian; Manoonpong, Poramate (2017): "Fast Dynamical Coupling Enhances Frequency Adaptation of Oscillators for Robotic Locomotion Control". Frontiers in Neurorobotics. Published online 2017 Mar 21 https://www.frontiersin.org/articles/10.3389/fnbot.2017.00014/full +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +method::new + +Creates a new Fb1_HopfA ar object. + + +argument::f +Defaults to 0. + + +argument::mu +Defaults to 1. + + +argument::eta +Defaults to 1. + + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. Defaults to 0. + + +argument::y0 +Initial value of the ODE. Expects array of size 3. Defaults to #[1, 1, 1]. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withOutScale +Boolean. Determines if the Fb1_ODEdef's default scaling values for integration and differential signals should be applied to the output. Defaults to true. +warning:: strong::withOutScale:: does not implement a general safety net functionality, strong::withOutScale::'s default value true does by no means say that output is limited. The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, under different circumstances, always lead to high out levels. +:: + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and 0.1).poll; + var src = SinOsc.ar(ratio * 200); + var sig = Fb1_HopfA.ar(src * hold.lag(0.5), 0.15, 2, 800); + sig[0] ! 2 * 0.5 +}.play +) + +x.release + + +// with Saw the result is less exact, sometimes Hopf jumps to higher partials + +( +x = { + var ratio = Demand.ar( + Impulse.ar(3), 0, + Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf) + ).midiratio; + var hold = (MouseX.kr(0, 1) > 0.1).poll; + var src = Saw.ar(ratio * 200); + var sig = Fb1_HopfA.ar(src * hold.lag(0.5), 0.15, 2, 800); + sig[0] ! 2 * 0.5 +}.play +) + +x.release + + +// an inexact adaption can be used though to generate interesting irregular variations +// listen for a while + +( +x = { + var ratio = Demand.ar( + Impulse.ar(3), 0, + Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf) + ).midiratio; + var src = Saw.ar(ratio * 200, 0.3); + var sig = Fb1_HopfA.ar(src, 8, 10.5, 600); + sig[0] ! 2 * 0.3 +}.play +) + +x.release +:: + diff --git a/HelpSource/Classes/Fb1_HopfAFDC.schelp b/HelpSource/Classes/Fb1_HopfAFDC.schelp new file mode 100755 index 0000000..a1ef027 --- /dev/null +++ b/HelpSource/Classes/Fb1_HopfAFDC.schelp @@ -0,0 +1,396 @@ +CLASS:: Fb1_HopfAFDC +summary:: Adaptive Hopf pseudo ugen with fast dynamical coupling +categories:: Libraries>miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +This is an adaptive variant of Hopf, which enhances the adaption mechanism of link::#[1]::, see link::#[2]:: (4.1.) for a description. The parameter naming follows the convention of link::#[2]::. + +v'(t) = (mu - (v(t)^2 + w(t)^2)) * v(t) - (theta(t) * w(t)) + p(t) + +w'(t) = (mu - (v(t)^2 + w(t)^2)) * w(t) + (theta(t) * v(t)) + +tau * beta'(t) = beta0 - beta(t) + (kappa * p(t) * v(t)) + +tau * epsilon'(t) = epsilon0 - epsilon(t) + (kappa * f(t) * p(t)) + +theta'(t) = -eta * p(t) * w(t) / sqrt(v(t)^2 + w(t)^2) + +with + +p(t) = (epsilon(t) * f(t)) - (beta(t) * v(t)) + + +It returns a 5-channel signal. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens. + + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[4]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[5]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: +anchor::[4]:: +anchor::[5]:: + +subsection::References + +numberedList:: +## Righetti, Ludovic; Buchli, Jonas; Ijspeert, Auke Jan (2009): "Adaptive Frequency Oscillators and Applications". The Open Cybernetics and Systemics Journal, 3, 64-69. https://www.researchgate.net/publication/41666931_Adaptive_Frequency_Oscillators_and_Applications_Open_Access Summary: https://biorob.epfl.ch/research/research-dynamical/page-36365-en-html +## Nachstedt, Timo; Tetzlaff, Christian; Manoonpong, Poramate (2017): "Fast Dynamical Coupling Enhances Frequency Adaptation of Oscillators for Robotic Locomotion Control". Frontiers in Neurorobotics. Published online 2017 Mar 21 https://www.frontiersin.org/articles/10.3389/fnbot.2017.00014/full +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +method::new + +Creates a new Fb1_HopfAFDC ar object. + + +argument::f +Defaults to 0. + + +argument::mu +Defaults to 1. + + +argument::eta +Defaults to 1. + + +argument::tau +Defaults to 1. + + +argument::kappa +Defaults to 1. + + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. Defaults to 0. + + +argument::y0 +Initial value of the ODE. Expects array of size 5. Defaults to #[1, 1, 0.01, 0.01, 1]. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withOutScale +Boolean. Determines if the Fb1_ODEdef's default scaling values for integration and differential signals should be applied to the output. Defaults to true. +warning:: strong::withOutScale:: does not implement a general safety net functionality, strong::withOutScale::'s default value true does by no means say that output is limited. The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, under different circumstances, always lead to high out levels. +:: + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and 0.1).poll; + var src = SinOsc.ar(ratio * 200); + var sig = Fb1_HopfAFDC.ar(src * hold.lag(0.1), 0.2, 0.6, 1, 50, 150) * + EnvGen.ar(Env.asr(0.05)); + [sig[0] * 0.7, src * 0.1] +}.play +) + +x.release + + +// in contrast to Fb1_HopfA adaption to the Saw works quite well + +( +x = { + var ratio = Demand.ar( + Impulse.ar(3), 0, + Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf) + ).midiratio; + var hold = (MouseX.kr(0, 1) > 0.1).poll; + var src = Saw.ar(ratio * 200); + var sig = Fb1_HopfAFDC.ar(src * hold.lag(0.1), 0.2, 0.6, 1, 50, 150) * + EnvGen.ar(Env.asr(0.05)); + [sig[0] * 0.5, src * 0.04] +}.play +) + +x.release + + +// it works still with faster tempo and bigger jumps +// however big jumps and/or the hold gate make the system instable, +// compose with clip helps + +( +x = { + var ratio = Demand.ar( + Impulse.ar(7), 0, + Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf) + Drand([0, 12, 24], inf) + ).midiratio; + var hold = MouseX.kr(0, 1) > 0.1; + var src = Saw.ar(ratio * 200); + var tMul = 150; + var sig = Fb1_HopfAFDC.ar(src * hold.lag(0.1), 0.2, 0.6, 1, 50, tMul, + compose: { |x| x.clip2(100) } + ) * EnvGen.ar(Env.asr(0.05)); + [sig[0] * 0.5, src * 0.04] +}.play +) + +x.release + + +// confused adaption by feeding with phase modulated signal + +( +x = { + var ratio = Demand.ar( + Impulse.ar(3), 0, + Dseq([0, 2, 4, 5, 7, 9, 11, 12], inf) + ).midiratio; + var src = SinOsc.ar(ratio * 200, SinOsc.ar(200).range(0.2, 1)); + var sig = Fb1_HopfAFDC.ar(src, 1.5, 1, 1, 100, 200) * + EnvGen.ar(Env.asr(0.05)); + sig[0] ! 2 * 0.3 +}.play +) + +x.release +:: + diff --git a/HelpSource/Classes/Fb1_Lorenz.schelp b/HelpSource/Classes/Fb1_Lorenz.schelp new file mode 100755 index 0000000..2167488 --- /dev/null +++ b/HelpSource/Classes/Fb1_Lorenz.schelp @@ -0,0 +1,335 @@ +CLASS:: Fb1_Lorenz +summary:: Lorenz pseudo ugen +categories:: Libraries>miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +Fb1_ODE wrapper for the Lorenz ODE system: + +u'(t) = s * (v(t) - u(t)) + +v'(t) = (u(t) * (r - w(t))) - v(t) + +w'(t) = (u(t) * v(t)) - (b * w(t)) + +It returns a 3-channel signal. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens. + + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[2]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[3]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: + + +subsection::References + +numberedList:: +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +method::new + +Creates a new Fb1_Lorenz ar object. + + +argument::s +Defaults to 10. + + +argument::r +Defaults to 30. + + +argument::b +Defaults to 2. + + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. Defaults to 0. + + +argument::y0 +Initial value of the ODE. Expects array of size 3. Defaults to #[1, 1, 1]. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withOutScale +Boolean. Determines if the Fb1_ODEdef's default scaling values for integration and differential signals should be applied to the output. Defaults to true. +warning:: strong::withOutScale:: does not implement a general safety net functionality, strong::withOutScale::'s default value true does by no means say that output is limited. The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, under different circumstances, always lead to high out levels. +:: + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +Fb1_ODE wrapper for the mass spring damper equation of motion: + +y''(t) * mass = f(t) - (dampen * y'(t)) - (spring * y(t)) + +It returns a 2-channel signal with position and velocity. link::Classes/Fb1_SD:: with unified mass is slightly more efficient than Fb1_MSD and you can always rewrite. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens and further MSD examples. + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[2]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[3]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: + + +subsection::References + +numberedList:: +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +method::new + +Creates a new Fb1_MSD ar object. + + +argument::f +External force. Defaults to 0. + + +argument::mass +Mass. Defaults to 1. + + +argument::spring +Spring stiffness. Defaults to 1. + + +argument::dampen +Dampening factor. Defaults to 0. + + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. Defaults to 0. + + +argument::y0 +Initial value of the ODE. Expects array of size 2. Defaults to #[0, 0]. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +Pseudo ugen for integrating / audifying ordinary (systems of) differential equations with initial values in realtime, based on the Fb1 single sample feedback class. It makes use of Fb1_ODEdef and Fb1_ODEintdef, containers for ODEs and numerical solution methods, which provide an interface for adding new ODE systems or integration methods interactively. There exists a huge number of ODE models in different areas, e.g. have a look at the SIAM publication Exploring ODEs link::#[1]::, it approaches the topic from an experimental point of view, which is quite related to the demands of practical audification / synthesis / processing. And of course it's fun to define your own ODEs, see this help file and link::Classes/Fb1_ODEdef::. + + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[2]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[3]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::#Examples 5#Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +Fb1_ODE in its plain form (without strong::tMul:: modulation, use of strong::compose:: etc.) produces audio data as a numerical integration of an ODE initial value problem, defined by Fb1_ODEdef and a numerical procedure, defined by Fb1_ODEintdef. This of course supposes well-defined ODE systems and it should be kept in mind that the Fb1_ODE framework doesn't perform any mathematical checks regarding the principal existence and uniqueness of a solution of a given Fb1_ODEdef. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: + + +subsection::References + +numberedList:: +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +private:: basicNew + + +method::new + +Creates a new Fb1_ODE ar object. + + +argument::name +The name of the ODE defined and stored via Fb1_ODEdef. Can be one of the predefined ODEs, for which wrappers exist, or a self-defined one. Expects a Symbol. + + +argument::argList +The argList to be passed to the ODE. Can contain ar or kr UGens and SequenceableCollections thereof. + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. If no value is passed, the default value of the referred Fb1_ODEdef is taken, usually 0. + + +argument::y0 +Initial value of the ODE. Expects number or array of correct size, which is determined by the Fb1_ODEdef. If no value is passed, the default value of the referred Fb1_ODEdef is taken. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withOutScale +Boolean. Determines if the Fb1_ODEdef's default scaling values for integration and differential signals should be applied to the output. Defaults to true. +warning:: strong::withOutScale:: does not implement a general safety net functionality, strong::withOutScale::'s default value true does by no means say that output is limited. The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, under different circumstances, always lead to high out levels. +:: + + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +To be used to define ODE systems that can then be audified with Fb1_ODE. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens. + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[2]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[3]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +Fb1_ODE in its plain form (without strong::tMul:: modulation, use of strong::compose:: etc.) produces audio data as a numerical integration of an ODE initial value problem, defined by Fb1_ODEdef and a numerical procedure, defined by Fb1_ODEintdef. This of course supposes well-defined ODE systems and it should be kept in mind that the Fb1_ODE framework doesn't perform any mathematical checks regarding the principal existence and uniqueness of a solution of a given Fb1_ODEdef. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: + + +subsection::References + +numberedList:: +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +private:: initClass + + + +method::new + +Creates a new Fb1_ODEdef object. + + +argument::name +The name of the ODE, expects a Symbol. Default Fb1_ODEdefs cannot be overwritten. + + +argument::function +The implementation of the function F which describes the ODE given as Y'(t) = F(t, Y(t)) where Y, F can be a single-valued or vector-valued. It must take time as first and a system state (possibly an array) as second parameter and optional further args that can also be arrays. The system state is expected to have the size of the system (given by strong::y0::'s size), the function's return value must also equal this size. If the system size is greater than 1 the components of the output array should be rather written as Functions, as this optimizes the compile process with symplectic integration procedures, it isn't compulsory though. See examples below and in the source file Fb1_ODEdef.sc. + + +argument::t0 +Number. Default initial time value. + + +argument::y0 +Default initial value of the ODE. Expects number or array and determines the size of the system, strong::function::'s second arg and return value must be of same size. + + +argument::outScale +Number that determines the default multiplier for the integration signal produced by Fb1_ODE when the latter's strong::withOutScale:: flag is set to true (default). Defaults to 1. Especially thought for systems like Lorenz which produce high levels with standard parameters, then it makes sense to set strong::outScale:: to a value smaller than 1. +warning:: strong::outScale:: / strong::diffOutScale:: / strong::withOutScale:: do not implement a general safety net functionality, strong::withOutScale:: 's default value true does by no means say that output is limited. The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, under different circumstances, always lead to high out levels. +:: + + +argument::diffOutScale +Number that determines the default multiplier for the differential signal produced by Fb1_ODE when the latter's strong::withOutScale:: flag is set to true (default). Defaults to 1. Especially thought for systems like Lorenz which produce high levels with standard parameters, then it makes sense to set strong::outScale:: to a value smaller than 1. +warning:: strong::outScale:: / strong::diffOutScale:: / strong::withOutScale:: do not implement a general safety net functionality, strong::withOutScale::'s default value true does by no means say that output is limited. The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, under different circumstances, always lead to high out levels. +:: + + + + +method::at + +Returns the Fb1_ODEdef instance of the Symbol strong::key:: if it exists. + +argument::key +Symbol. + + +method::keys + +Returns an array of all keys of currently stored Fb1_ODEdefs. + + +method::postAll + +Posts all keys of currently stored Fb1_ODEdefs. + + +method::remove + +Removes the Fb1_ODEdef of the Symbol strong::key:: from the Dictionary. + +argument::key +Symbol. + + +method::reset + +Removes all Fb1_ODEdefs other than the predefined ones from the Dictionary. + + + + +INSTANCEMETHODS:: + + +private:: initFb1_ODEdef, value, compose, makeFirstOutInit, makeOutInit, getIntSize, dispatchIntFunc + + + +method::next + +Method for language-side integration, gives next value based on previous data. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym', like 'sym2') is highly recommended. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Multi-step procedures are not implemented, remaining: + +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +:: + +argument::t +Previous time, expects Number. + + +argument::y +Previous state, expects Number or Array of system size. + + +argument::dt +Number. Time delta. + + +argument::args +List of additional args to be passed to Fb1_ODEdef's function. + + + +method::nextN + +Method for language-side integration, gives an array of next strong::n:: values (arrays) based on previous data. Last values (arrays) are stored at last position. For intTypes with sizeFactor 2 the differential is included. See Examples. + + +argument::n +Integer. Number of next values (resp. value arrays) to be calculated. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym', like 'sym2') is highly recommended. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Multi-step procedures are not implemented, remaining: + +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +:: + +argument::t +Previous time, expects Number. + + +argument::y +Previous state, expects Number or Array of system size. + + +argument::dt +Number. Time delta. + + +argument::args +List of additional args to be passed to Fb1_ODEdef's function. + + +argument::withTime +Boolean. Determines if integrated time should be included. Defaults to false. + + +argument::includeStart +Boolean. Determines if start value(s) should be included. Defaults to true. + + +method::name +Getter for the Fb1_ODEdef's name. + + +method::function +Getter for the Fb1_ODEdef's function. + + +method::name +Getter for the Fb1_ODEdef's name. + + +method::t0 +Getter for the Fb1_ODEdef's t0. + + +method::y0 +Getter for the Fb1_ODEdef's y0. + + +method::outScale +Getter for the Fb1_ODEdef's outScale. + + +method::diffOutScale +Getter for the Fb1_ODEdef's diffOutScale. + + +method::size +Getter for the Fb1_ODEdef's system size. + + + + + + +section::Examples 1: Defining new ODEs + +code:: +// reboot with reduced blockSize + +( +s = Server.local; +Server.default = s; +s.options.blockSize = 8; +s.reboot; +) +:: + + + +anchor::Ex. 1a:: +subsection::Ex. 1a: Extending the harmonic oscillator + + +code:: +// This seems to be an interesting strategy. +// We can e.g. try to multiply one of its equations with a term near 1, +// so start with rather small k and investigate + +// y'(t) = w(t) +// w'(t) = -y(t) * (1 + (k * w(t))) + + +( +Fb1_ODEdef(\harmonic_ext_1, { |t, y, k| + [ + y[1], + y[0].neg * (1 + (k * y[1])) + ] +}, 0, [0, 1], 1, 1); +) + + +// brassy sound + +( +x = { + var sig = Fb1_ODE.ar(\harmonic_ext_1, + [1], 1500, 0, [0, 1], + ) * 0.1; + sig +}.play +) + +x.release + + +// with oscillating k the system tends to become unstable +// but with softclip per sample it can be kept +// (see chapter 'compose' option in Fb1_ODE help) + +( +x = { + var sig = Fb1_ODE.ar(\harmonic_ext_1, + // k oscillates between 1 and 3 + [SinOsc.ar(50).lincurve(-1, 1, 1, 3, 2)], + 1500, 0, [0, 1], + compose: \softclip + ) * 0.1; + sig +}.play +) + +x.release + + +// an oscillation between 1 and higher values totally changes the spectrum + +( +x = { + var sig = Fb1_ODE.ar(\harmonic_ext_1, + // k oscillates between 1 and 10 + [SinOsc.ar(50).lincurve(-1, 1, 1, 10, 2)], + 1500, 0, [0, 1], + compose: \softclip + ) * 0.1; + sig +}.play +) + +x.release + + +// play with the two states + +( +x = { + var sig = Fb1_ODE.ar(\harmonic_ext_1, + // upper oscillation bound for k oscillates itself between 2 and 15 + [SinOsc.ar(50).lincurve(-1, 1, 1, SinOsc.ar(SinOsc.ar(0.2).exprange(0.2, 15)).range(2, 15), 2)], + 1500, 0, [0, 1], + compose: \softclip + ) * 0.1; + sig +}.play +) + +x.release +:: + + + + +anchor::Ex. 1b:: +subsection::Ex. 1b: Extending exponential decay + + +code:: +// exponential decay is described by the equation +// y'(t) = -y(t) + +// an oscillating decay can e.g. be got by +// y'(t) = -y(t) * sin(t) +// the analytic solution includes a log of the sine, +// so we get more partials + + +( +Fb1_ODEdef(\exp_decay_raw, { |t, y| + y.neg * sin(t) +}, 0, 1, 1, 1); +) + + +( +x = { + var sig = Fb1_ODE.ar(\exp_decay_raw, + tMul: 100 * 2pi, + compose: \softclip + ) ! 2; + Line.kr(dur: 10, doneAction: 2); + sig +}.play +) + +x.release + + +// multiplication with a second sine with multiplied time leads to strange and interesting results + +( +Fb1_ODEdef(\exp_decay_extended, { |t, y, k| + y.neg * (sin(t) * sin(k * t)) +}, 0, 1, 1, 1); +) + + + +// ATTENTION: danger of blowup, can be reduced with softclip composition per sample +// constant values lead to ring modulation-like effects ... + +( +x = { + var sig = Fb1_ODE.ar(\exp_decay_extended, + [2.7], 100 * 2pi, 0, 1, + compose: \softclip + ); + Line.kr(dur: 10, doneAction: 2); + sig ! 2 +}.play +) + +x.release + + +// ... whereas modulations produce more complex changing spectra +( +x = { + var sig = Fb1_ODE.ar(\exp_decay_extended, + [SinOsc.ar(120).range(3, 3.01)], 100 * 2pi, 0, 1, + compose: \softclip + ); + Line.kr(dur: 10, doneAction: 2); + sig ! 2 +}.play +) + +x.release + + +// for decorrelated stereo we can expand to two independent equations +// k should be of size 2 + +( +Fb1_ODEdef(\exp_decay_extended_2, { |t, y, k| + [ + y[0].neg * (sin(t) * sin(k[0] * t)), + y[1].neg * (sin(t) * sin(k[1] * t)) + ] +}, 0, [1, 1], 1, 1); +) + +( +x = { + var sig = Fb1_ODE.ar(\exp_decay_extended_2, + [SinOsc.ar(120).range([3, 3.01], [3.01, 3.02])], 100 * 2pi, 0, [1, 1], + compose: \softclip + ); + Line.kr(dur: 10, doneAction: 2); + sig +}.play +) + +x.release +:: + + + +anchor::Ex. 2:: +section::Ex. 2: Language-side integration + + +code:: +// Possible though not optimized. +// For longer sections it's probably better to employ Fb1 ODE solvers, +// store audio in a buffer and load it into a float array, +// for quick tests it might be useful though. + +// integrate begin of a Lorenz system, last array is last state, so flop for plot + +( +a = Fb1_ODEdef.at(\Lorenz).nextN( + n: 1000, + intType: \sym2, + t: 0, + y: [1, 1, 1], + dt: 1/100, + args: [30, 12, 2] +); + +a.flop.plot +) +:: + + + + diff --git a/HelpSource/Classes/Fb1_ODEintdef.schelp b/HelpSource/Classes/Fb1_ODEintdef.schelp new file mode 100755 index 0000000..3c23144 --- /dev/null +++ b/HelpSource/Classes/Fb1_ODEintdef.schelp @@ -0,0 +1,186 @@ +CLASS:: Fb1_ODEintdef +summary:: container for ordinary differential equation numeric integrators +categories:: Libraries>miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + +For the optional definition of ODE integration procedures that can then be used with Fb1_ODE. This is an advanced feature, the built-in symplectic procedures should suffice for most use cases. See the source code in Fb1_ODEintdef.sc for the way integrators are defined and link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens. + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[2]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[3]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +Fb1_ODE in its plain form (without strong::tMul:: modulation, use of strong::compose:: etc.) produces audio data as a numerical integration of an ODE initial value problem, defined by Fb1_ODEdef and a numerical procedure, defined by Fb1_ODEintdef. This of course supposes well-defined ODE systems and it should be kept in mind that the Fb1_ODE framework doesn't perform any mathematical checks regarding the principal existence and uniqueness of a solution of a given Fb1_ODEdef. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: + + +subsection::References + +numberedList:: +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +private:: initClass + + + +method::new + +Creates a new Fb1_ODEintdef object and stores it in a Dictionary for further usage with Fb1_ODE. + + +argument::name +The name of the integration procedure, expects a Symbol. Default Fb1_ODEintdefs cannot be overwritten. + + +argument::function +The implementation of the Function ("stepper") which describes the numeric procedure. It must take the arguments odeDef, t, y, dt and ... args for further ODE args and must return the new integration value(s). For evaluating the ODE function the method Fb1_ODEdef.value is used which performs the evaluation depending on its first argument. + + +argument::stepDepth +Integer. Values greater than one are for multi-step procedures. Defaults to 1. + + +argument::sizeFactor +Integer. 1 means that the procedure buffers only integration values, 2 means that the differential is buffered too. Defaults to 1. + + + + +method::at + +Returns the Fb1_ODEintdef instance of the Symbol strong::key:: if it exists. + +argument::key +Symbol. + + +method::keys + +Returns an array of all keys of currently stored Fb1_ODEintdefs. + + +method::postAll + +Posts all keys of currently stored Fb1_ODEintdefs. + + +method::remove + +Removes the Fb1_ODEintdef of the Symbol strong::key:: from the Dictionary. + +argument::key +Symbol. + + +method::reset + +Removes all Fb1_ODEintdefs other than the predefined ones from the Dictionary. + + + + +INSTANCEMETHODS:: + + +private:: initFb1_ODEintdef + + +method::name + +Getter for the Fb1_ODEintdef's name. + + +method::function + +Getter for the Fb1_ODEintdef's function. + + +method::stepDepth + +Getter for the Fb1_ODEintdef's stepDepth. + + +method::sizeFactor + +Getter for the Fb1_ODEintdef's sizeFactor. + + + + + + +section::Examples + + +code:: +// This shows how the classical 4-step Runge-Kutta is implemented, +// quite straight it follows its mathematical definition. + +// t: old time +// y: old state (array) +// dt: integration step +// size: size of the ODE (not used here, but needs to be there) +// ... args is for further args of the ODE + +// The odeDef argument expects the Fb1_ODEdef object. +// The evaluation of the ODE function is implemented by odeDef::value. +// This method takes a first argument i which determines the kind of evaluation: +// For i == nil it returns the evaluation value(s) as Array, +// for a number it only returns the ith component. +// The distinction is necessary as the symplectic procedures refer to +// the single components of the ODE system. + +// There exist also symplectic Runge-Kutta procedures which I didn't test so far. +// they would probably also need that option. + +Fb1_ODEintdef(\rk4, + { |odeDef, t, y, dt, size ... args| + var k1 = odeDef.(nil, t, y, *args); + var k2 = odeDef.(nil, dt * 0.5 + t, k1 * dt * 0.5 + y, *args); + var k3 = odeDef.(nil, dt * 0.5 + t, k2 * dt * 0.5 + y, *args); + var k4 = odeDef.(nil, dt + t, k3 * dt + y, *args); + ((k2 + k3) * 2 + k1 + k4) * dt / 6 + y + }, 1, 1); + + +// The same procedure as '_d' variant which stores the differential +// The argument y is now passed an array of doubled size with the +// last differential values in the second half of it. +// Finally the new differential value is calculated and appended to the integration. + +Fb1_ODEintdef(\rk4_d, + { |odeDef, t, y, dt, size ... args| + var k1, k2, k3, k4, yy, ff, yNew, fNew; + yy = y[0..size-1]; + ff = y[size..2*size-1]; + k1 = ff; + k2 = odeDef.(nil, dt * 0.5 + t, k1 * dt * 0.5 + yy, *args); + k3 = odeDef.(nil, dt * 0.5 + t, k2 * dt * 0.5 + yy, *args); + k4 = odeDef.(nil, dt + t, k3 * dt + yy, *args); + yNew = ((k2 + k3) * 2 + k1 + k4) * dt / 6 + yy; + fNew = odeDef.(nil, t + dt, yNew, *args); + yNew ++ fNew + }, 1, 2); +:: + diff --git a/HelpSource/Classes/Fb1_SD.schelp b/HelpSource/Classes/Fb1_SD.schelp new file mode 100755 index 0000000..43b0173 --- /dev/null +++ b/HelpSource/Classes/Fb1_SD.schelp @@ -0,0 +1,304 @@ +CLASS:: Fb1_SD +summary:: spring damper model pseudo ugen +categories:: Libraries>miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_VanDerPol, Classes/Fb1_Duffing + + +DESCRIPTION:: + + +Fb1_ODE wrapper for the spring damper equation of motion with unit mass: + +y''(t) = f(t) - (dampen * y'(t)) - (spring * y(t)) + +It returns a 2-channel signal with position and velocity. Fb1_SD is slightly more efficient than Fb1_MSD and you can always rewrite. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens and further MSD examples. + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[2]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[3]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: + + +subsection::References + +numberedList:: +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +method::new + +Creates a new Fb1_SD ar object. + + +argument::f +External force. Defaults to 0. + + +argument::spring +Spring stiffness. Defaults to 1. + + +argument::dampen +Dampening factor. Defaults to 0. + + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. Defaults to 0. + + +argument::y0 +Initial value of the ODE. Expects array of size 2. Defaults to #[0, 0]. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1, Classes/GFIS, Classes/Fb1_ODE, Classes/Fb1_ODEdef, Classes/Fb1_ODEintdef, Classes/Fb1_MSD, Classes/Fb1_SD, Classes/Fb1_Lorenz, Classes/Fb1_Hopf, Classes/Fb1_HopfA, Classes/Fb1_HopfAFDC, Classes/Fb1_Duffing + + +DESCRIPTION:: + +Fb1_ODE wrapper for the Van der Pol ODE system with external f: + +y'(t) = w(t) + +w'(t) = f(t) + (mu * (1 - y(t)^2) * w(t)) - (theta^2 * y(t)) + +coming from the 2nd order equation + +y''(t) = f(t) + (mu * (1 - y(t)^2) * y'(t)) - (theta^2 * y(t)) + + +It returns a 2-channel signal. The parameter naming follows the convention of link::#[2]::. As described in link::#[1]:: and link::#[2]:: adaptive variants can be made in the same way as for Hopf and other oscillators. See link::Classes/Fb1_ODE:: for general information about Fb1 ODE integrator UGens. + + + +strong::HISTORY AND CREDITS: :: Big credit to David Pirrò from IEM Graz for pointing me to the symplectic integration methods, which are essential for audifying ODEs, as they help to ensure numeric stability in the long run (e.g. to avoid drifts of oscillations that are mathematically expected to be regular). See the chapter on integration in his dissertation link::#[4]::, pp 135-146. You might also want check David Pirròs optimized ODE compiler named Henri link::#[5]::. Big credit also to Nathaniel Virgo who brought up the buffering strategy used in Fb1, which is Fb1_ODE's working horse. + + +warning:: +Especially with self-defined ODEs the usage of this class is – inherently – highly experimental. Be careful with amplitudes, as always with feedback it can become loud! Sudden blowups might result form the mathematical characteristics of the ODE systems or they might come from parameter changes on which ODEs can react extremely sensitive to, they can also stem from numerical accumulation effects. It is highly recommended to take precautionary measures, e.g. by limiting/distorting operators (tanh, clip, softclip, distort) with the compose option (See link::Classes/Fb1_ODE#Examples 5#Fb1_ODE Examples 5::) and/or external limiting and/or using MasterFX from the JITLibExtensions quark. +:: + +note:: +The convenience of direct definition of the ODE relation comes with the price of a large number of UGens involved. You might want to allow a higher number of UGens with the server option numWireBufs. For a nice workflow I'd recommended to take reduced blockSizes (e.g. 1, 2, 4, 8, 16) while experimenting as compile time is shorter, but once you have finished the design of a SynthDef it might pay going back to blocksize 32 or 64 for runtime efficiency, especially if many kr UGens are involved. +:: + + +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: +anchor::[4]:: +anchor::[5]:: + +subsection::References + +numberedList:: +## Righetti, Ludovic; Buchli, Jonas; Ijspeert, Auke Jan (2009): "Adaptive Frequency Oscillators and Applications". The Open Cybernetics and Systemics Journal, 3, 64-69. https://www.researchgate.net/publication/41666931_Adaptive_Frequency_Oscillators_and_Applications_Open_Access Summary: https://biorob.epfl.ch/research/research-dynamical/page-36365-en-html +## Nachstedt, Timo; Tetzlaff, Christian; Manoonpong, Poramate (2017): "Fast Dynamical Coupling Enhances Frequency Adaptation of Oscillators for Robotic Locomotion Control". Frontiers in Neurorobotics. Published online 2017 Mar 21 https://www.frontiersin.org/articles/10.3389/fnbot.2017.00014/full +## Trefethen, Lloyd N.; Birkisson Ásgeir; Driscoll, Tobin A. (2017): Exploring ODEs. SIAM - Society for Industrial and Applied Mathematics. Free download from: https://people.maths.ox.ac.uk/trefethen/Exploring.pdf +## Pirrò, David (2017). Composing Interactions. Dissertation. Institute of Electronic Music and Acoustics, University of Music and Performing Arts Graz. Free download from: https://pirro.mur.at/media/pirro_david_composing_interactions_print.pdf +## https://git.iem.at/davidpirro/henri +:: + + +CLASSMETHODS:: + +method::new + +Creates a new Fb1_VanDerPol ar object. + + +argument::f +Defaults to 0. + + +argument::mu +Defaults to 1. + + +argument::theta +Defaults to 1. + + + +argument::tMul +Time multiplier, which determines velocity of proceeding in the dynamic system. The default value 1 means that the step delta of time equals 1 / sample duration. For getting audible oscillations you might, depending on the ODE definition, have to pass much higher values. Can also be a kr / ar UGen and might also be negative. + + +argument::t0 +Initial time. Expects Number. Defaults to 0. + + +argument::y0 +Initial value of the ODE. Expects array of size 2. Defaults to #[1, 1]. + + +argument::intType +Integration type. Expects one of the Symbols, for which procedures are stored with Fb1_ODEintdef. The use of symplectic procedures (with prefix 'sym') is highly recommended. Defaults to \sym2. For more accurate integration you can try symplectic procedures of higher order like \sym4, \sym8 etc. Families of integration procedures: +list:: +## Symplectic: \sym2, \sym2_d, \sym4, \sym4_d, \sym6, \sym6_d, \sym8, \sym8_d, \sym12, \sym12_d, \sym16, \sym16_d, \sym32, \sym32_d, \sym64, \sym64_d +## Euler: \eu, \eu_d, \eum, \eum_d, \eui, \eui_d +## Prediction-Evaluation-Correction: \pec, \pece, \pecec, \pecece +## Runge-Kutta: \rk3, \rk3_d, \rk3h, \rk3h_d, \rk4, \rk4_d +## Adams-Bashforth: \ab2, \ab3, \ab4, \ab5, \ab6 +## Adams-Bashforth-Moulton: \abm21, \abm22, \abm32, \abm33, \abm43, \abm44, \abm54, \abm55, \abm65, \abm66 +:: + + +argument::compose +Operator(s) / Function(s) to be applied to the system value on a per-sample base. This of course blurs the numeric procedure but can be used for containing or in a creative way. Can be an operator Symbol, a Function or an arbitrarily mixed SequenceableCollection thereof. The Functions are in any case expected to take an array (the system state) as first argument. If only one Function is given it must output an array of same (system) size. Within a SequenceableCollection a Function must output a value of size 0. Within a SequenceableCollection an operator Symbol is applied only to the corresponding component. A Function can optionally take a second argument which is for ar UGens passed via strong::composeArIn::. + + +argument::composeArIn +ar UGen or SequenceableCollection thereof or a SequenceableCollection that can contain both. This is the way to use ar UGens within a Function passed to compose. UGens are passed to the Function's second argument. + + +argument::dt0 +First time delta in seconds to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. In case of a multi-step procedure a dt0 value will be derived from the default server's properties (sample duration * strong::tMul::). + + +argument::argList0 +Initial argList value(s) to be used for the language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. If no UGens are passed to strong::argList::, values will be assumed from passed strong::argList:: Numbers. + + +argument::init_intType +Integration type for language-side calculation of first values of a multi-step strong::intType:: procedure. This will mostly be irrelevant as (single-step) symplectic procedures are to be preferred. Defaults to \sym8. + + +argument::withOutScale +Boolean. Determines if the Fb1_ODEdef's default scaling values for integration and differential signals should be applied to the output. Defaults to true. +warning:: strong::withOutScale:: does not implement a general safety net functionality, strong::withOutScale::'s default value true does by no means say that output is limited. The default scaling values of predefined Fb1_ODEdefs are average assumptions and can, under different circumstances, always lead to high out levels. +:: + +argument::withDiffChannels +Boolean. Determines if channel(s) with differential value(s) should be returned. This is only applicable with integration types with a sizeFactor == 2, which means that for every sample the values of the differential are buffered. As by default this is not the case for all predefined numeric procedures, there exist variants with appendix '_d', e.g. \sym2_d. Defaults to false. + + +argument::withTimeChannel +Boolean. Determines if accumulated time is output in an additional channel. +warning:: with constant strong::tMul:: it produces an ascending DC which is not affected by strong::leakDC:: if this is set to true. Might especially be of interest if time is modulated. Defaults to false. +:: + +argument::blockSize +Integer, this should be the server blockSize. If no Integer is passed, the current default server's blockSize is assumed (in contrast to Fb1). So explicitely passing a blockSize should only be necessary in special cases, e.g. when compiling before booting. However for a pleasant workflow for ar usage it's recommended to use Fb1_ODE with a reduced server blockSize in order to reduce SynthDef compile time. + + +argument::graphOrderType +0, 1 or 2. Determines if topological order of generated BufRd and BufWr instances in the SynthDef graph is forced by additional UGens. +list:: +##Type 0: forced graph order is turned off. +##Type 1 (default): graph order is forced by summation and miSCellaneous>Nonlinear +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Fb1 + + +DESCRIPTION:: + + +The GFIS class implements functional iteration synthesis as pseudo ugen loosely based on the description by Agostino Di Scipio ([1], [2]), who used the abbreviation FIS and pointed to its rich potential. Yari Marimoto has written a plugin implementation of the main sine-map iteration model, which is included in the trnsnd quark under the same name. The GFIS pseudo ugen implementation allows settings which go beyond functional iteration in a strict sense. + +Principle idea of synthesis: given a parametrized nonlinear function, time-variance of init values and/or parameter sets with fixed iteration depth n can produce interesting waveforms. Due to the highly nonlinear dynamics involved, a great amount of unpredictability invites to experiment and exploration – depending on the characteristics of the time-varying signal, results span from brittle noisy textures to drones with rich spectral movements. The cited papers mainly describe strict iteration with sine and mention iterated waveshaping, but as the GFIS class implementation just takes an arbitrary Function it's easy to blur the concept, e.g. by altering the Function and/or the parametrization depending on the iteration level and/or applying iteration on multichannel signals, crossing their data etc. Also interesting – and probably not widely explored – is the use of functional iteration as controller / modulator / engine for other synthesis methods. + + +warning:: +Be careful with amplitudes, in general higher numbers of iteration produce signals with more energy and due to the nonlinear dynamics signals can suddenly become loud! Also go sure that your function doesn't allow blowup with iteration (this at least doesn't happen with the standard examples of the sine map model). +:: + +subsection::References + +numberedList:: +## Di Scipio, Agostino (1999). "Synthesis Of Environmental Sound Textures by Iterated Nonlinear Functions" in: Proceedings of the 2nd COST G-6 Workshop on Digital Audio Effects (DAFx99), NTNU, Trondheim, December 9-11, 1999. +## Di Scipio, Agostino (2001). "Iterated Nonlinear Functions as a Sound-Generating Engine" Leonardo, Vol. 34, No. 3 (2001), pp. 249-254, MIT Press. +:: + + +CLASSMETHODS:: + +private:: checkInits + +method::ar + +argument::func +Function used to establish the iteration by applying it strong::n:: times at build time of the synthdef graph. The Function should take two arguments: the signal and an optional index. It should return the signal used for iteration. The signal can be multichannel, then the Function might take that into account and refer to single channels of the signal arg – but the Function might also ignore it and rely on multichannel expansion, see examples. + +Note that UGens written within strong::func:: are instantiated strong::n:: times, this is usually not what you want for iterating the same parametrical function, with determined signals it's a waste of CPU and for random UGens the result is different. For the strict interpretation of FIS define the parameter signal outside and refer to it from inside strong::func::. + + +argument::init +Init value for the iteration, can also be a SequenceableCollection. + +argument::n +Integer, the maximum iteration number, it determines how often the Function is used for building the synthdef graph, hence this value is not modulatable. + + +argument::nOut +Integer or SequenceableCollection of Integers. An Integer determines the iteration level of the returned signal. That way you can define a maximum iteration number strong::n:: and switch between lower ones, however strong::n:: iterations are permanently calculated. In general switching will cause clicks, so this is an option for testing primarily. A SequenceableCollection of level indices will produce a multichannel signal, which in turn allows defining smooth transitions between signals of different iteration levels. + + +argument::leakDC +Boolean. Determines if a LeakDC is applied to the output. If the parameter signal doesn't change (which can e.g. happen with a LFDNoise UGen) the result will in general be a DC offset, hence DC leaking is recommended. Defaults to true. + + +argument::leakCoef +Number, the strong::leakDC:: coefficient. Defaults to 0.995. + +method::kr + + +section::Examples 1: The sine map model + +These examples use an iterated sine map as described by Agostino Di Scipio. For the sine map sin(r * x) values of r varying between 2 and 4 are interesting. Driven by LFNoise parametrizations as in the first examples we get noise textures. + + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + + +anchor::Ex. 1a:: +subsection::Ex. 1a: Time-varying the factor r + + + +code:: +( +y = { + var r = LFDNoise3.ar(10).range(3.5, 4); + GFIS.ar({ |x| sin(r * x) }, 0.3, 9) * 0.1 ! 2 +}.play +) + +y.release + + +// this is not "classical" FIS: +// for each iteration a different parametrization is taken ! +// As LFDNoise UGens aren't coupled, pulsations are less unique + +( +y = { + GFIS.ar({ |x| sin(LFDNoise3.ar(10).range(3.5, 4) * x) }, 0.3, 9) * 0.1 ! 2 +}.play +) + +y.release + + + +// less iterations + +( +y = { + var r = LFDNoise3.ar(10).range(3.5, 4); + GFIS.ar({ |x| sin(r * x) }, 0.3, 7) * 0.1 ! 2 +}.play +) + +y.release + + + +// different init value + +( +y = { + var r = LFDNoise3.ar(10).range(3.5, 4); + GFIS.ar({ |x| sin(r * x) }, 0.85, 7) * 0.1 ! 2 +}.play +) + +y.release + + + +// higher iteration gives sections with more high frequencies in the spectrum +// even higher numbers soon lead to (interrupted) white noise + +( +y = { + var r = LFDNoise3.ar(3).range(3.5, 4); + GFIS.ar({ |x| sin(r * x) }, 0.3, 12) * 0.03 ! 2 +}.play +) + +y.release + + +// higher iteration numbers can partially be "equilibrated" with lower r +// this leads to different sounds, here a more "airy" noise + +( +y = { + var r = LFDNoise3.ar(7).range(2.9, 3.1); + GFIS.ar({ |x| sin(r * x) }, 0.3, 15) * 0.1 ! 2 +}.play +) + +y.release + + +// granular-like noise burst textures + +( +y = { + var r = LFDNoise3.ar(50).range(2.5, 3); + GFIS.ar({ |x| sin(r * x) }, 0.3, 15) * 0.1 ! 2 +}.play +) + +y.release +:: + + +anchor::Ex. 1b:: +subsection::Ex. 1b: Time-varying init values + + +code:: +// mono + +( +y = { + var i = LFDNoise3.ar(7).range(0.2, 0.9); + GFIS.ar({ |x| sin(3.2 * x) }, i, 10) * 0.1 ! 2 +}.play +) + +y.release + + +// stereo init is propagated to a stereo signal + +( +y = { + var i = LFDNoise3.ar(7).range(0.2, 0.9) * [1, 1.01]; + GFIS.ar({ |x| sin(3.2 * x) }, i, 10) * 0.1 +}.play +) + +y.release + +:: + + + + +anchor::Ex. 1c:: +subsection::Ex. 1c: Time-varying init values and factor r + +code:: +( +y = { + var i = LFDNoise3.ar(7).range(0.2, 0.9) * [1, 1.01]; + var r = LFDNoise3.ar(2).range(3.2, 3.6) * [1, 1.01]; + GFIS.ar({ |x| sin(r * x) }, i, 9) * 0.1 +}.play +) + +y.release +:: + + +anchor::Ex. 1d:: +subsection::Ex. 1d: Producing pitch by periodically oscillating parameters + +code:: +// variants of phase glitter +// note that here again the "lazy" FIS with different oscillators per iteration is used + +( +y = { + var osc = SinOsc.ar(30); + GFIS.ar({ |x| sin((osc + LFDNoise3.ar(0.15)).linlin(-2, 2, 3.2, 4) * x) }, [0.5, 0.505], 9) * 0.1 +}.play +) + +y.release + +( +y = { + var osc = SinOsc.ar([30, 30.5]); + GFIS.ar({ |x| sin((osc + LFDNoise3.ar(0.15)).linlin(-2, 2, 3.2, 4) * x) }, 0.5, 9) * 0.1 +}.play +) + +y.release + + +( +y = { + var osc = SinOsc.ar([30, 30.5]); + GFIS.ar({ |x| sin((osc + LFDNoise3.ar(0.15)).linlin(-2, 2, 3.2, 4) * x) }, [0.5, 0.505], 9) * 0.1 +}.play +) + +y.release + + +// harmonics as different oscillation frequencies per iteration + +( +y = { + var oscMod = SinOsc.ar(0.1).range(30.01, 30.3); + GFIS.ar({ |x, i| + sin((SinOsc.ar([30, oscMod] * (i+1)) + LFDNoise3.ar(0.15)).linlin(-2, 2, 3, 4) * x) + }, [0.5, 0.502], 7) * 0.1 +}.play +) + +y.release + + + +// Pulse as oscillator + +( +y = { + var factors = Demand.ar( + TDuty.ar(Dxrand([4, 5, 7], inf)), + 0, + Dseq([Dseq([1.3, 1.4], 2), 0.5], inf) + ).lag(0.7); + var osc = Pulse.ar([70, 70 * factors]).lag(0.001); + LPF.ar( + GFIS.ar({ |x| + sin((osc + LFDNoise3.ar(0.15)).linlin(-2, 2, 3.2, 4) * x) + }, [0.5, 0.505], 9) * 0.1, + 9000 + ) +}.play +) + + +y.release + +:: + + +anchor::Ex. 2:: +section::Examples 2: The waveshaping model - iteration via buffered data + +code:: +// a Buffer can be filled with an arbitrary mathematical function or audio data + +// load audio + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + + +// load to array + +b.loadToFloatArray(action: { |array| a = array; "done".postln }); + + +// take short snippet, normalize between 0 and 1 +// that's most practical when we map to buffer index later on + +// the sound of the snippet is quite irrelevant +// more oscillations in general produce more noise with iteration +// start trying with sine-like forms + +d = a[3150..3300].normalize; + +d.plot; + + +// fill new buffer for iteration + +c = Buffer.loadCollection(s, d) +:: + + + + +anchor::Ex. 2a:: +subsection::Ex. 2a: Time-varying init values + +code:: +// result of BufRd is used as index for the next BufRd +// needs Buffer c prepared above + +( +y = { + GFIS.ar( + { |x| BufRd.ar(1, c, c.numFrames * x) }, + (LFDNoise3.ar(3) * [1, 1.1]).range(0.5, 0.51), 7 + ) * 0.2 +}.play +) + +y.release +:: + + +anchor::Ex. 2b:: +subsection::Ex. 2b: Time-varying index deviation + + +code:: +// needs Buffer c prepared above + +( +y = { + var add = LFDNoise3.ar(0.3) * [0.05, 0.0505]; + GFIS.ar( + { |x| BufRd.ar(1, c, c.numFrames * (x + add)) }, + 0.5, 7 + ) * 0.2 +}.play +) + +y.release +:: + + + +anchor::Ex. 2c:: +subsection::Ex. 2c: Time-varying init values and index deviation + + +code:: +// needs Buffer c prepared above + +( +y = { + var add = LFDNoise3.ar(0.3) * [0.05, 0.0505]; + GFIS.ar( + { |x| BufRd.ar(1, c, c.numFrames * (x + add)) }, + (LFDNoise3.ar(0.3) * [1, 1.01]).range(0.5, 0.505), 7 + ) * 0.2 +}.play +) + +y.release +:: + + +anchor::Ex. 3:: +section::Examples 3: GFIS as controller / modulator / engine for other synthesis + +This can transfer the instable characteristics to other sound worlds. + + +anchor::Ex. 3a:: +subsection::Ex. 3a: FM + + + +code:: +( +y = { + var mod = GFIS.ar({ |x| sin(LFDNoise3.ar(1).range(2.9, 4) * x) }, 0.3, 7); + SinOsc.ar(mod * [50, 51] * 70 + 300) * LFDNoise3.ar(2).range(0.2, 0.05) +}.play +) + +y.release +:: + + + +anchor::Ex. 3b:: +subsection::Ex. 3b: Buffer modulation phase controlled by GFIS + + +code:: +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +b = Buffer.read(s, p); + +( +y = { + var pos = 0.5; // other pos offset will give totally different sounds + var osc = GFIS.ar({ |x| sin(LFDNoise3.ar(10).range(3.2, 4) * x) }, 0.3, 6) * 0.1; + // GFIS involves a LeakDC, but not BufRd + LeakDC.ar(BufRd.ar(1, b, b.numFrames * (osc * [0.0120, 0.0125] * 5 + pos), interpolation: 4)) * 0.1 +}.play +) + +y.release +:: + + +anchor::Ex. 3c:: +subsection::Ex. 3c: Iterated GFIS + + +code:: +// augmentation of nonlinearity: +// GFIS itself used as time-varying control of another GFIS + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + +// load to array and fill new buffer for iteration + +b.loadToFloatArray(action: { |array| a = array; "done".postln }); + +d = a[3150..3300].normalize; + +c = Buffer.loadCollection(s, d); + + +( +y = { + var r = LFDNoise3.ar(3).range(2.7, 3.4); + var add = GFIS.ar({ |x| sin(r * x) }, 0.5, 3) * [0.05, 0.055]; + GFIS.ar( + { |x| BufRd.ar(1, c, c.numFrames * (x + add)) }, + 0.5, 7 + ) * 0.2 +}.play +) + +y.release +:: + + +anchor::Ex. 4:: +section::Ex. 4: The nOut arg + + + +code:: +// nOut allows for switching between iteration levels up to maximum n + +( +y = { + |nOut = 10| + GFIS.ar({ |x| sin(LFDNoise1.ar(20).range(3, 3.5) * x) }, [0.2, 0.3], 10, nOut) * 0.1 +}.play +) + + +y.set(\nOut, 9) + +y.set(\nOut, 8) + +y.release + + +// it can be passed an array of levels, +// the resulting multichannel signal can e.g. be used for crossfaded switching with DXMix + +// switch between 3 mono signals and double them + +( +y = { + var src = GFIS.ar({ |x| sin(LFDNoise3.ar(30).range(3, 4) * x) }, 0.2, 9, [5, 7, 9]) * 0.1; + DXMix.ar(Dseq([0, 1, 2], inf), `src, fadeTime: 0.01, stepTime: 0.02, fadeMode: 1) ! 2 +}.play +) + +y.release; + +// switch between 3 stereo signals + +( +y = { + var src = GFIS.ar({ |x| sin(LFDNoise3.ar(30).range(3, 4) * x) }, [0.2, 0.21], 9, [5, 7, 9]) * 0.1; + DXMix.ar(Dseq([0, 1, 2], inf), `src, fadeTime: 0.01, stepTime: 0.02, fadeMode: 1) +}.play +) + +y.release; +:: + + + +anchor::Ex. 5:: +section::Ex. 5: Comparison FIS / GFIS + +
code:: +// FIS is contained in the trnsnd quark +// first example of its helpfile + +{ FIS.ar(LinExp.ar(LFTri.ar(0.1), -1, 1, 1, 4), LFNoise2.ar(300).range(0, 1), 3, 0.1) }.play; + +// the same with GFIS requires definition of varying params outside func +
( +y = { + var r = LinExp.ar(LFTri.ar(0.1), -1, 1, 1, 4); + var i = LFNoise2.ar(300).range(0, 1); + GFIS.ar({ |x| sin(r * x) }, i, 3, 3, false) * 0.1 +}.play +) + +y.release + +// proof of concept, difference is silent as it should + +( +z = { + var r = LinExp.ar(LFTri.ar(0.1), -1, 1, 1, 4); + var i = LFNoise2.ar(300).range(0, 1); + GFIS.ar({ |x| sin(r * x) }, i, 3, 3, false) * 0.1 - FIS.ar(r, i, 3, 0.1) +}.play +) + +z.release +:: + + diff --git a/HelpSource/Classes/HS.schelp b/HelpSource/Classes/HS.schelp new file mode 100644 index 0000000..6cb7c85 --- /dev/null +++ b/HelpSource/Classes/HS.schelp @@ -0,0 +1,248 @@ +CLASS:: HS +summary:: object for use of synth values in the language by event patterns +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/PHS, Classes/PHSuse, Classes/PHSplayer, Classes/PHSusePlayer + + +DESCRIPTION:: + + +To be used in connection with link::Classes/PHS:: / link::Classes/PHSuse:: to play event patterns using synth values. Holds a singular synth definition, keeps track of OSC traffic when PHS / PHSuse are played. For using several help synths in parallel or setting controllers of a singular help synth see link::Classes/HSpar:: and related. + + +CLASSMETHODS:: + +method::new + +Creates a new HS object. + +argument::server +Must be running server. + +argument::ugenFunc +Function that defines the synth. + +argument::demandLatency +Latency of help synth in seconds. Default value 0.15. + +argument::respondLatency +Time in seconds, given to the response to be received by the client on time. +Default value 0.15. + +argument::postOSC +Boolean for posting of (server to client) OSC messages. Defaults to false. + +argument::granularity +Time grains per second, quantization for bookkeeping. Defaults to 200. + +argument::inputCheck +Boolean for checking input data. Defaults to true. + + + +discussion:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + +// make a new HS +// This compiles the SynthDef and prepares the HS to be played with a PHS, +// no Synth object is yet instantiated. + +h = HS(s, { LFDNoise3.kr(0.7, 15, 75) }); + +:: + +A PHS, like a Pbind, defines event stream behaviour, only durations are given separately. +Here values are demanded just by one stream with constant duration, +but more than one duration pattern per HS and more than one Pbind per duration pattern +may be defined - see the link::Classes/PHS:: help file for examples. + +The play method of a PHS creates a PHSplayer which plays a synth from HS's synth definition, +synth values are accessible within the PHS definition by the variable code::~val::, e.g. by code::Pkey(\val)::. +If HS's synthdef has no args, this must be specified in the PHS by code::nil:: or code::[]::. + +code:: + +p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) ]).play; + +// also: +// p = PHS(h, [], 0.15, [ \midinote, Pfunc { |e| e.use { ~val } } ]).play; + +// stop allows resuming, cleanup with free or Cmd-. + +p.stop; // HS synth still playing + +p.play; // resume the PHSplayer + +p.stop(true); // HS synth also paused + +p.play; // resume the PHSplayer together with HS synth + +p.free; // also stop HS synth and cleanup - PHSplayer and HS object are freed + +:: + +INSTANCEMETHODS:: + +method::demandLatency +Set or get latency of help synth in seconds. Defaults to 0.15. + +method::respondLatency +Set or get time (in seconds) given to the response to be received by the client on time. Defaults to 0.15. + +method::postOSC +Set or get the flag that defines whether OSC messages should be posted or not. Boolean. Defaults to false. + +method::granularity +Set or get the (Integer) number of time grains per second, denoting the quantization for bookkeeping. Defaults to 200. + +method::inputCheck +Set or get the flag that defines whether input data should be checked. Boolean. Defaults to true. + + +method::listen + +Make HS ready to poll values from a synth, demanded by possibly more than one stream. +Therefore strong::num:: trigger synths are run, ready to receive triggers for sending help synth values back to client, +but a synth from HS's ugenFunc definition is not yet playing after that. +If PHS / PHSuse are played there is no need to call strong::listen:: explicitely. + +argument::num +Integer. Number of trigger synths. + +argument::latency +Float. Latency in seconds. + + + +method::play + +Play a synth derived from HS's ugenFunc definition. HS must already be listening. +If PHS / PHSuse are played there is no need to call strong::play:: explicitely. + +argument::args +Synth args. + +argument::latency +Float. Latency in seconds. + +method::clearBookkeeping +Cleanup OSC bookkeeping. + +method::stop +Free the playing help synth and cleanup OSC bookkeeping. + +method::sleep +Free the listening trigger synths. + +method::busFree +Deallocate the bus. + +method::free +Free all related PHSplayers / PHSusePlayers, then strong::stop::, strong::sleep:: and strong::busFree::. + +Note:: strong::stop:: and strong::free:: will normally be called by the respective player methods, see link::Classes/PHSplayer::. +:: + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// define a tendency for chord density + +h = HS(s, { LFDNoise3.kr(0.5, 4, 5) }); + + +// language operations used for building chords from synth values + +( +p = PHS(h, [], 0.18, + [\midinote, Pkey(\val).collect {|x| + Array.series(x.round, 60 - (x * 1.2), 5) + Array.rand(x.round, -0.8, 0.8) }, + \legato, 0.6, + \amp, 0.05] +).play; +) + +// cleanup (freeing buses and nodes) by pressing Cmd-. or explicitely + +p.free; + + +///////////////////////////////////////////////////////////////////// + +// example with other than default instrument +// define synth to move between two sounds + +( +SynthDef(\simpleMorph, {|out = 0, freq = 440, amp = 0.1, morphQ = 0| + var src, normalMorphQ = morphQ.mod(1); + src = Saw.ar(freq, mul: amp) * (1 - normalMorphQ) + (SinOsc.ar(freq, mul: amp) * normalMorphQ); + Out.ar(out, src ! 2 * EnvGen.ar(Env.perc, doneAction: 2)); +}).add; +) + +// HelpSynth definition, values between 0 and 1 +// Clock and quant for syncing + +( +h = HS(s, { LFTri.kr(0.5, mul: 0.5, add: 0.5) }); + +c = TempoClock.new; +q = 0.2; +) + +// help synth values for pitch and synth arg \morphQ + +( +p = PHS(h, [], 0.2, + [\instrument, \simpleMorph, + \midinote, Pkey(\val) * 30 + 60 + [0, 9] + Pxrand([0, 1.5, -1.5], inf), + \amp, 0.07, + \morphQ, Pkey(\val) ] +).play(c,q); +) + + +// sync it with a normal pbind (EventStreamPlayer) with default instrument +// no use of HS / PHS + +( +r = Pbind( + \dur, 0.2, + \midinote, Pxrand([58, 60, 61, 63, 64, 67, 69, 70], inf) + + [-7, 0, 5, 9] + Pseq([0.2, -0.2],inf), + \amp, 0.03 +).play(c, quant: q); +) + +// pause and stop are synonym + +p.pause; + + +// resume p in sync + +p.play(c, q); + + +// stop and free + +( +r.stop; +p.free; +) + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/HSpar.schelp b/HelpSource/Classes/HSpar.schelp new file mode 100644 index 0000000..89bdce8 --- /dev/null +++ b/HelpSource/Classes/HSpar.schelp @@ -0,0 +1,97 @@ +CLASS:: HSpar +summary:: object for use of synth values in the language by event patterns +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/PHSpar, Classes/PHSparUse, Classes/PHSparPlayer, Classes/PHSusePlayer + +DESCRIPTION:: +To be used in connection with link::Classes/PHSpar:: / link::Classes/PHSparUse:: to play event patterns using synth values. Holds synth definitions, keeps track of OSC traffic when PHSpar / PHSparUse are played. For using a singular help synth see link::Classes/HS:: and related. + +CLASSMETHODS:: + +method::new + +Creates a new HSpar object. + +argument::server +Must be running server. + +argument::ugenFuncs +Collection of functions that define the synths. + +argument::demandLatency +Latency of help synths in seconds. Default value 0.15. + +argument::respondLatency +Time in seconds, given to the response to be received by the client on time. Default value 0.15. + +argument::postOSC +Boolean for posting of (server to client) OSC messages. Defaults to false. + +argument::granularity +Time grains per second, quantization for bookkeeping. Defaults to 200. + +argument::inputCheck +Boolean for checking input data. Defaults to true. + + +EXAMPLES:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + +// define a HSpar with two help synth definitions + +( +h = HSpar(s, [ { |freq = 1, dev = 10, center = 70| + LFDNoise3.kr(freq, dev, center) }, + { |freq = 1, dev = 5, center = 70, addFreq = 0.1, addDev = 5| + LFTri.kr(freq, 0, dev, center) + SinOsc.kr(addFreq, 0, addDev) } + ]); +) + +// define a PHSpar to switch between these two + +( +p = PHSpar(h, + Pwhite(2.5, 3), // switch duration + Pseq([0,1],inf), // switch indices + // helpSynthArgs: always default values for help synth #0, always set help synth #1 + [nil, [\freq, 1.2, \center, Pwhite(60, 80)] ], + // pbindArgs: different from PHS, they have to be given as collection; ~val refers to current switch + [0.1, [\midinote, Pkey(\val) + [0, 5], \legato, 0.2, \amp, [0.1, 0.07]]] +).play; +) + +// stop and free HSpar by player + +p.free; + + + +///////////////////////////////////////////////// + + +// HSpar with only one help synth + +h = HSpar(s, [ { |freq = 1, dev = 5, center = 65| LFDNoise3.kr(freq, dev, center) } ] ); + + +// to be used for setting synth args + +( +p = PHSpar(h, + Pwhite(0.15, 0.35, inf), // switch durations + 0, // switch index + [[\center, Pwhite(65, 80)]], // set args + [0.1, [\midinote, Pkey(\val) + Pseq([[0,-7], -2],inf), \legato, 0.3, \amp, 0.05 ]] +).play +) + +// stop and free HSpar by player + +p.free; +:: diff --git a/HelpSource/Classes/IdentityDictionary.ext.schelp b/HelpSource/Classes/IdentityDictionary.ext.schelp new file mode 100644 index 0000000..4fc99fe --- /dev/null +++ b/HelpSource/Classes/IdentityDictionary.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private::miSC_checkFxOrder, miSC_fxOrderWarnString, miSC_keysValuesEvery, miSC_keysValuesAny, miSC_putAppend, miSC_removeDrop, miSC_getPredecessors, miSC_getSuccessors, miSC_getTopoOrder, miSC_getFxOrders + +private::miSC_replaceKeys \ No newline at end of file diff --git a/HelpSource/Classes/Integer.ext.schelp b/HelpSource/Classes/Integer.ext.schelp new file mode 100644 index 0000000..523bf7b --- /dev/null +++ b/HelpSource/Classes/Integer.ext.schelp @@ -0,0 +1,36 @@ + +INSTANCEMETHODS:: + +private:: miSC_groupIndices, miSC_cubeDivision, miSC_selectCubeComponent, miSC_distinctCubePoints +private:: miSC_distinctColors, miSC_colorDeviationPairs, miSC_getCtrIndex +private:: miSC_runMsg, miSC_setMsg, miSC_setnMsg, miSC_freeMsg +private:: miSC_partMax, miSC_eqPart + +private:: miSC_isPseudoCaps, miSC_checkColorGroups + +private:: miSC_getClutch, miSC_checkFxOrder, miSC_getTopoOrder, miSC_getPredecessors, miSC_getSuccessors, miSC_getFxOrders, miSC_zeroIdRank, miSC_fxIdRank + + + +private:: miSC_streamifySieveItems, miSC_fxOrderWarnString + + +method:: lcmByGcd + +Calculates the least common multiple by using the greatest common divisor. This can avoid problems with large numbers. +See link::Tutorials/Sieves_and_Psieve_patterns#4b:: + +argument::... args +Integers + + + +method:: lcmByFactors + +Calculates the least common multiple by prime factors. This can avoid problems with large numbers. +Returns an array with lcm as first item, an array with prime factors of lcm as second item and an array of receiver's and all arguments' prime factors. +See link::Tutorials/Sieves_and_Psieve_patterns#4b:: + +argument::... args +Integers + diff --git a/HelpSource/Classes/LocalBuf.ext.schelp b/HelpSource/Classes/LocalBuf.ext.schelp new file mode 100644 index 0000000..89174a5 --- /dev/null +++ b/HelpSource/Classes/LocalBuf.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_getFFTbufSize + + diff --git a/HelpSource/Classes/MemoRoutine.schelp b/HelpSource/Classes/MemoRoutine.schelp new file mode 100644 index 0000000..552b843 --- /dev/null +++ b/HelpSource/Classes/MemoRoutine.schelp @@ -0,0 +1,227 @@ +CLASS:: MemoRoutine +summary:: Routine-like object which stores last values +categories::Libraries>miSCellaneous>PSx stream patterns, Streams-Patterns-Events>PSx stream patterns +related:: Overviews/miSCellaneous, Tutorials/PSx_stream_patterns, Classes/Routine, Classes/PS, Classes/PSdup, Classes/PSrecur + + +DESCRIPTION:: + + +A MemoRoutine behaves like a Routine, though it is not defined as a subclass of it. It stores last values, the maximum buffer size can be defined. MemoRoutine is internally used by PSx and related Pattern classes, which therefore also remember their last value(s). + + +CLASSMETHODS:: + +method::new + +Creates a MemoRoutine instance with the given Function. + +argument::func +Function to instantiate the Routine with. + +argument::stackSize +Call stack depth, defaults to 512. + +argument::bufSize +Number of last values to store, defaults to 1. + +argument::copyItems +Determines if and how to copy items, which are returned by method next +(run, value, resume) and which are either non-Sets or member of Sets, into the buffer. +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). +Other values are interpreted as 0. Defaults to 0. + +0: storage of original item + +1: storage of copied item + +2: storage of deepCopied item + +argument::copySets +Determines if to copy Sets (and hence Events), +which are returned by method next (run, value, resume), into the buffer. +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). +Other values are interpreted as 0. Defaults to 1. + +0: storage of original Set + +1: storage of copied Set + +note:: + +The distinction of copying items and sets makes sense in the case of event streams. Per default Events are copied (strong::copySets:: == 1), not their values (strong::copyItems:: == 0). By playing Events those are used to store additional data (synth ids, msgFuncs …) which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use MemoRoutine - copied Events will not contain this additional data. If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured then copying makes no sense, this is the normal case, so strong::copyItems:: defaults to 0. When going to alter the ouput, you might want to set strong::copyItems:: to 1 for a MemoRoutine returning simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set strong::copySets:: to 1 and strong::copyItems:: to 2 (an option strong::copySets:: == 2 doesn't exist as it would be contradictory in combination with strong::copyItems:: < 2). + +:: + + +INSTANCEMETHODS:: + + +method::next + +argument::inval +Same conventions with method yield as with aRoutine.next. + + +method::value +Same as next. + +method::resume +Same as next. + +method::run +Same as next. + +method::lastValue +Last value. + +method::lastValues +Instance variable getter method for array of stored last values. + +method::at +Returns ith item of array of last values (keep in mind reversed order: last value first). + +method::bufSize +Size of array of last values. + +method::reset +Resets the MemoRoutine by resetting its Routine. + +method::stop +Stops the MemoRoutine by stopping its Routine. + +method::count +Instance variable getter and setter methods. +Counts each call of next / value / resume / run. + +method::copyItems +Instance variable getter and setter methods. +If used directly the Integer copy code must be passed (see above). + +method::copySets +Instance variable getter and setter methods. +If used directly the Integer copy code must be passed (see above). + +method::routine +Instance variable getter and setter methods. + + + + +EXAMPLES:: + +code:: + +( +// a MemoRoutine + +m = MemoRoutine( + { |inval| 1000.do { |i| inval = i.yield } }, + bufSize: 50 +); +) + +m.nextN(5); + + +// get last value + +m.lastValue; + +m[0]; + + +// get last values, order is from last to earlier + +m.lastValues; + + +// get value before last value + +m[1]; + + + +// get more values + +m.nextN(100); + + + +// first values are lost now + +m.lastValues; + + +// as loop in m is restricted you might call .all +// as stop is indicated by the return of nil, +// the array lastValues contains nil as first item + +m.all; + +m.lastValues; + + + +// Per default a MemoRoutine is just storing the last values, +// with structured data types this can cause surprises + +( +// a MemoRoutine + +m = MemoRoutine( + { |inval| var a = [[2,1], [4,3]]; 1000.do { inval = a.choose.yield } }, + bufSize: 50 +); +) + +// generate some values + +m.nextN(10); + +// apply a method on last values + +( +x = m.lastValue; +x.sort; +) + +// as sort is destructive one of MemoRoutine's arrays is altered now ! + +m.nextN(10); + + + +// to avoid such you can simply do .copy.sort which doesn't alter lastValues, +// but you can also store copied values in the buffer by passing a flag with +// instantiation of the MemoRoutine + +( +// a MemoRoutine which stores copied arrays + +m = MemoRoutine( + { |inval| var a = [[2,1], [4,3]]; 1000.do { inval = a.choose.yield } }, + bufSize: 100, + copyItems: 1 // 1, true or \true for copy, 2 or \deep for deepCopy +); +) + +m.nextN(10); + +( +x = m.lastValue; +x.sort; +) + +// order not altered with next items + +m.nextN(10); + +// however the concerned array stored as item in lastValues remains altered + +m[10]; + + + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/Method.ext.schelp b/HelpSource/Classes/Method.ext.schelp new file mode 100644 index 0000000..b6662b1 --- /dev/null +++ b/HelpSource/Classes/Method.ext.schelp @@ -0,0 +1,8 @@ + + + +INSTANCEMETHODS:: + +private:: miSC_errorString + + diff --git a/HelpSource/Classes/Nil.ext.schelp b/HelpSource/Classes/Nil.ext.schelp new file mode 100644 index 0000000..7fe7a10 --- /dev/null +++ b/HelpSource/Classes/Nil.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_asSet + + diff --git a/HelpSource/Classes/Node.ext.schelp b/HelpSource/Classes/Node.ext.schelp new file mode 100644 index 0000000..680a053 --- /dev/null +++ b/HelpSource/Classes/Node.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_runMsg, miSC_setMsg, miSC_setnMsg, miSC_freeMsg + + diff --git a/HelpSource/Classes/Object.ext.schelp b/HelpSource/Classes/Object.ext.schelp new file mode 100644 index 0000000..952a13a --- /dev/null +++ b/HelpSource/Classes/Object.ext.schelp @@ -0,0 +1,32 @@ + +INSTANCEMETHODS:: + +private:: miSC_isKindOfFixedHSindex, miSC_isKindOfFixedHSindices, miSC_isKindOfPossibleHSindices +private:: miSC_isKindOfPossibleHSstartIndices, miSC_normalizeHSstartIndices + + +private:: miSC_isGrouping, miSC_getCtrIndex, miSC_collectCopy, miSC_performWithEnvir + +private:: miSC_defNameAsArray + +private:: miSC_makeLacePat + + +private:: miSC_repTypeFactor, miSC_applyNaryFuncProxy + +private:: miSC_getEnvir + +private:: miSC_copy, miSC_getCopyCode + +private:: miSC_check + +private:: miSC_checkFxOrder, miSC_fxOrderWarnString, miSC_getFxOrderData + + +private:: miSC_isPointSel, miSC_isIntervalSel + +private:: miSC_unifySize + +private:: miSC_Dmultiply + +private:: miSC_Dmultiply2 \ No newline at end of file diff --git a/HelpSource/Classes/PHS.schelp b/HelpSource/Classes/PHS.schelp new file mode 100644 index 0000000..0ab1526 --- /dev/null +++ b/HelpSource/Classes/PHS.schelp @@ -0,0 +1,196 @@ +CLASS:: PHS +summary:: defines Pbind(s) for using synth values of a HS +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/PHS, Classes/PHSuse, Classes/PHSplayer, Classes/PHSusePlayer + + +DESCRIPTION:: +Defines Pbind(s) which, when played, can use values of a synth derived from link::Classes/HS::'s synth definition. + + +CLASSMETHODS:: + +method::new + +Creates a new PHS object. + +argument::helpSynth +A HS object. + +argument::helpSynthArgs +Collection of key / value pairs for the HS synth. + +argument::... pbindArgs +dur1, pbindData1, ... , durN, pbindDataN + +where strong::dur:: is duration value or pattern / stream of durations for corresponding Pbind(s) +and strong::pbindData:: is a collection of Pbind pairs or a collection of Pbind pair collections, +defining possibly several Pbinds with the same event timing. + +INSTANCEMETHODS:: + +method::play + +A link::Classes/PHSplayer:: object is instantiated and started using the TempoClock strong::clock::. +Quant or SimpleNumber strong::quant:: lets the player step into the quantization as soon as possible, +with respect to the necessary latency. The PHSplayer can be stopped and resumed with several options. + + +method::newPaused + +VarGui support, see link::Tutorials/HS_with_VarGui:: for examples. + +Return a new paused Synth derived from HS's ugenFunc definition, which may be passed to a VarGui object. +VarGui will automatically detect that the Synth origins from a HS definition and gui functionality wil be adapted. + +argument::args +Collection of key / value pairs for the HS synth. + +argument::latency +SimpleNumber (seconds). + + + +method::asTask + +VarGui support, see link::Tutorials/HS_with_VarGui:: for examples. + +Returns a wrapper Task, which may be passed to a VarGui object. +Playing and stopping the wrapper Task invokes playing and +stopping behaviour of the underlying PHSplayer, per default also taking control over +playing and stopping the help synth. + +argument::clock +TempoClock. + +argument::quant +Quant or SimpleNumber. + +argument::hsStop +Boolean. Determines if help synth will stop together with PHSplayer. Defaults to false. + +argument::hsPlay +Boolean. Determines if help synth will resume together with PHSplayer. Defaults to true. + +argument::newEnvir +Boolean. Determines if Task will be played in a newly generated environment. Defaults to true. +This option especially becomes important when PHS's strong::pbindData:: contains functional code with +environmental variables. + +argument::removeCtrWithCmdPeriod +Boolean. Defaults to true. +Determines if notification of PHSplayer will be stopped with CmdPeriod. + + + + + + + +EXAMPLES:: + +code:: + + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// define a HS with args + +h = HS(s, { |freq = 0.5, dev = 10, center = 65| LFDNoise3.kr(freq, dev, center) }); + + +// two Pbinds with different event timing reading from one help synth + +( +p = PHS(h, nil, // default help synth args + Prand([0.4, 0.2],inf), [ \midinote, Pkey(\val), \amp, 0.08 ], + 0.1, [ \midinote, Pkey(\val) + 9.5 + Pxrand([0, 2, 5],inf), \amp, 0.06 ] +).play; +) + +// stop player and free HS, ready to be played again with the same or another PHS + +p.free; + + +// now with synth args + +( +p = PHS(h, [\freq, 2, \dev, 20], // more oscillation + Prand([0.4, 0.2],inf), [ \midinote, Pkey(\val) - [0, 5, 10], \amp, 0.06 ], + 0.1, [ \midinote, Pkey(\val) + 9.5 + Pxrand([0, 4, 7],inf), \amp, 0.06 ] +).play; +) + + +// stop player and free HS + +p.free; + + +// let PHS define two Pbinds with same event timing, one mostly pausing + +( +p = PHS(h, [], 0.16, + [ [ \midinote, Pkey(\val), \amp, 0.1], + [ \midinote, Pkey(\val) + [-7, 4, 7], + \amp, Pwrand([0.04, 0.08], [0.8, 0.2], inf), + \type, Pkey(\val).collect { |x| x.asInteger.even.if { \note }{ \rest } } ] + ] +).play; +) + + +// stop player and free HS + +p.free; +:: + +Order of execution: three Pbinds defined, scheduling slightly time-shifted internally, +so references can be established. + +Printout of variables which may be used within a PHS definition, +code::timeGrains:: is just a time ID, depending on granularity, not used for scheduling. +code::demandIndex:: indicates the index of the stream of durations, +posted code::demandIndex:: = 1, as two pbinds are using duration stream #0. + +code:: +( +p = PHS(h, [], + 0.16, + [ [ \midinote, Pkey(\val) + Pseq([0,2], inf), \amp, 0.06], + [ \midinote, Pkey(\val) + [-7, 4, 7], + \amp, Pwrand([0.06,0.1], [0.8,0.2], inf), + // "random" appearance of middle voice chords depending on synth values + \type, Pkey(\val).collect { |x| ~middle = x.asInteger.even.if { \note }{ \rest } } ] + ], + Pseq([0.32, 0.16], inf), + [ [ \midinote, Pkey(\val) + [15, 20, 25], + \legato, 0.2, + \amp, 0.025, + // upper voice is playing in case of middle voice rest + \type, Pkey(\val).collect { |x| (~middle == \rest).if { \note }{ \rest } }, + \post, Pfunc { |e| e.use { + "demandIndex: ".post; ~demandIndex.postln; + "timeGrains: ".post; ~timeGrains.postln; + "dur: ".post; ~dur.postln; + "val: ".post; ~val.postln; + "================================".postln; + } + } + ] ] +).play; +) + + +// stop player and free HS + +p.free; + +:: + diff --git a/HelpSource/Classes/PHSpar.schelp b/HelpSource/Classes/PHSpar.schelp new file mode 100644 index 0000000..6b7ee69 --- /dev/null +++ b/HelpSource/Classes/PHSpar.schelp @@ -0,0 +1,415 @@ +CLASS:: PHSpar +summary:: defines Pbind(s) for using synth values of a HSpar +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/HSpar, Classes/PHSparUse, Classes/PHSparPlayer, Classes/PHSusePlayer + + +DESCRIPTION:: +Defines Pbind(s) which, when played, can use values of synths derived from link::Classes/HSpar::'s synth definitions. + + +CLASSMETHODS:: + +method::new + +Creates a new PHSpar object. + +argument::helpSynthPar +A HSpar object. + +argument::switchDur +Duration value or pattern / stream of durations determining the times of HSpar synth switches. + +argument::switchIndex +Index or pattern / stream of indices determining HSpar's synth definition to switch to. +Defaults to 0. + +argument::helpSynthArgs +Collection of Pbind pair collections (size = number of strong::helpSynthPar::'s help synths), +defining synth args to be set at switch times. + +argument::pbindArgs +Collection of the form strong::[ dur1, pbindData1, ... , durN, pbindDataN:: strong::]::, whereby +strong::dur:: is a duration value or pattern / stream of durations for the corresponding Pbind(s) and +strong::pbindData:: is a collection of Pbind pairs or a collection of Pbind pair collections, +defining possibly several Pbinds with same event timing. + +argument::hsIndices +Per default values are taken from the currently switched help synth. +Explicitely given strong::hsIndices:: allow reference to values of other help synths from the +corresponding Pbind(s). See the examples below. +Expects a collection of valid hsIndex values resp. patterns / streams of valid hsIndex values. +A valid hsIndex value is a valid help synth index or a collection of valid help synth indices. +The collection's size must equal strong::N::, the number of strong::pbindArgs::'s event timings. + +argument::switchOn +Boolean or Pattern / Stream of Booleans, determining if switched help synths should be resumed. +Defaults to false. + +argument::switchOff +Boolean or Pattern / Stream of Booleans, determining if help synths, +which are left at a switch, should be paused. +Defaults to false. + +argument::set +Boolean or Pattern / Stream of Booleans, determining if next synth input values, defined in strong::helpSynthArgs::, should be +taken for setting the synth. The first help synth args are always set. Defaults to true. + +argument::hsStartIndices +A valid help synth index, a collection of valid help synth indices or one of the symbols: \all, \none. +Determines which help synths should be started at the beginning in addition to the one of the first switch index. +Help synths of other than first switch index are played with default args. + + +INSTANCEMETHODS:: + +method::newPaused + +VarGui support, see link::Tutorials/HS_with_VarGui:: for examples. + +Return a collection of new paused Synth(s) derived from HSpar's ugenFunc definition(s), which may be passed to a VarGui object. +VarGui will automatically detect the origin from a HSpar definition and gui functionality wil be adapted. + +argument::args +Collection of collection(s) of key / value pairs for the HSpar synth(s). + +argument::latency +SimpleNumber (seconds). + + + +method::asTask + +VarGui support, see link::Tutorials/HS_with_VarGui:: for examples. + +Returns a wrapper Task, which may be passed to a VarGui object. +Playing and stopping the wrapper Task invokes playing and +stopping behaviour of the underlying PHSparPlayer, per default also taking control over +playing and stopping the help synth(s). + +argument::clock +TempoClock. + +argument::quant +Quant or SimpleNumber. + +argument::hsStop +Boolean, Integer or SequenceableCollection of Integers determining help synth indices. +Determines if help synth(s) will stop together with PHSparPlayer. Defaults to false. + +argument::hsPlay +Boolean, Integer or SequenceableCollection of Integers determining help synth indices. +Determines if help synth(s) will resume together with PHSparPlayer. Defaults to true. + +argument::switchStop +Boolean. Determines if switching will stop together with PHSparPlayer. Defaults to true. + +argument::newEnvir +Boolean. Determines if Task will be played in a newly generated environment. Defaults to true. +This option especially becomes important when PHSpar's strong::pbindData:: contains functional code with +environmental variables. + +argument::removeCtrWithCmdPeriod +Boolean. Defaults to true. +Determines if notification of PHSparPlayer will be stopped with CmdPeriod. + + + +EXAMPLES:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + +// define a HSpar with two help synth definitions + +( +h = HSpar(s, [ + { |freq = 1, dev = 5, center = 65| + LFDNoise3.kr(freq, dev, center) }, + { |freq = 1, dev = 5, center = 75, addFreq = 0.1, addDev = 5| + LFTri.kr(freq, 0, dev, center) + SinOsc.kr(addFreq, 0, addDev) } +]); +) + +// define a PHSpar to switch between these two +// reference to currently switched help synth via ~val +// note that pbindArgs, different from PHS, have to be given as collection + +( +p = PHSpar(h, + 3, // switch duration + Pseq([0,1],inf), // switch indices + // center of help synth #0 is reset at every switch, default values for help synth #1 : + [ [\center, Pseq([65, 90], inf)], nil], + // ~val refers to current switch : + [0.1, [\midinote, Pkey(\val) + Pseq([0,1],inf), \legato, 0.2 ] ] +).play; +) + +// stop and free HSpar + +p.free; + + + +// no need to define a switch pattern +// default switch index = 0, default switch duration = inf +// hsIndices value \all causes that all help synth values at +// corresponding Pbind times are accesible via ~vals + +( +p = PHSpar(h, + pbindArgs: [0.1, [\midinote, Pkey(\vals) /* collection, thus played as interval */ + Pseq([0,1],inf), + \legato, 0.2 ] ], + hsIndices: [ \all ] +).play; +) + +p.free; + + + +// this is a bit wasteful as always both help synth values are demanded via the OSCresponder mechanism: +// see printed (serverToClient) OSC traffic + +( +h.postOSC = true; + +p = PHSpar(h, + pbindArgs: [0.1, [\midinote, Pkey(\vals).collect(_.at(1)) /* take only the second */ + + Pseq([0,1],inf), \legato, 0.2 ] ], + hsIndices: [ \all ] +).play; +) + +p.free; + + + +// the index may be determined by hsIndices +// only the needed help synth value is demanded and can be referenced via ~thisVal: +// compare printed OSC traffic + +( +p = PHSpar(h, + pbindArgs: [0.1, [\midinote, Pkey(\thisVal) /* take value determined by hsIndices */ + + Pseq([0,1],inf), \legato, 0.2 ] ], + hsIndices: [ 1 ] +).play; +) + +( +p.free; +h.postOSC = false; +) + + +// hsIndices may contain patterns +// only the needed help synth value is demanded and can be referenced via ~thisVal (or ~theseVals) : + +( +p = PHSpar(h, + pbindArgs: [0.1, [\midinote, Pkey(\thisVal) + Pseq([0,1],inf), \legato, 0.2 ] ], + hsIndices: [ Prand([0,1], inf) ] +).play; +) + +p.free; + + + +// hsIndices also accepts collections of indices or patterns thereof, +// referenced by ~theseVals (which is always a collection) + +( +p = PHSpar(h, + pbindArgs: [0.1, [\midinote, Pkey(\theseVals) + Pseq([0,1],inf), \legato, 0.2 ] ], + hsIndices: [ Pstutter(Pwhite(5,10), Pxrand([0, 1, [0,1]], inf)) ] +).play; +) + +p.free; + + + +// two Pbinds with different event timing +// hsIndices specified for each + +( +p = PHSpar(h, + // "+" wraps collections, so if ~theseVals is an interval then + // ~midinote consists of the two added sevenths [-5, 5], [0, 10], + // otherwise ~midinote is a chord of fourths + pbindArgs: [0.2, [\midinote, Pkey(\theseVals) + [-5, 0, 5,10] , \legato, 0.2, \amp, 0.05 ], + 0.1, [\midinote, Pkey(\thisVal) + Pseq([0,1],inf), \legato, 0.2, \amp, 0.08 ]], + hsIndices: [ Pstutter(Pwhite(5,10), Pxrand([0, 1, [0,1]], inf)), 0] +).play; +) + +p.free; + + + +// the switched indices can be referenced via the hsIndices arg by symbol \switch +// \all is synonym to [0,1] in the last example + +( +p = PHSpar(h, + Pwhite(0.5, 1.5),// switch duration + Pseq([0,1],inf), // switch indices + [ [], [] ], // default values for help synths + // "+" wraps collections, so if ~theseVals is an interval then + // ~midinote consists of the two added sevenths [-5, 5], [0, 10], + // otherwise ~midinote is a chord of fourths + [ 0.2, [\midinote, Pkey(\theseVals) + [-5, 0, 5,10] , \legato, 0.2, \amp, 0.05 ], + 0.1, [\midinote, Pkey(\thisVal) + Pseq([0,1],inf), \legato, 0.2, \amp, 0.08 ] ], + [ Pstutter(Pwhite(5,10), Pxrand([0, 1, \all], inf)), \switch ] + // single line switches between trill and arpeggio +).play; +) + +p.free; + + +////////////////////////////////////////////////////////////////////////////////// + + +// HSpar with two help synth definitions + +( +h = HSpar(s, [ + { |start = 90, end = 60, dur = 10, add = 0| + XLine.kr(start, end, dur) + add; }, + { |freq = 0.3, dev = 15, center = 60, phase = 0, add = 0| + LFTri.kr(freq, phase, dev, center + add); } +]); +) + + +// switch between two help synths +// both help synths are run from the start (default hsStartIndices = true) +// help synths of switch index are paused when index is left (switchOff: true) and +// resumed when index is given (switchOn: true) +// movement downwards needs more than 10 seconds defined by XLine + +( +p = PHSpar(h, + Pwhite(0.3,1.0), // switch duration + Pseq([0,1], inf), // switch indices, + // distinguish help synth values by reference to ~switchIndex + pbindArgs: [0.12, [\midinote, Pkey(\val) + + Pkey(\switchIndex).collect(switch(_, 0, [0,2.5], 1, [0,7])), \legato, 0.2 ] ], + switchOn: true, + switchOff: true +).play; +) + +p.free; + + + +// never pause help synth #0, always pause #1 +// end value of help synth #0 reached after 10 seconds + +( +p = PHSpar(h, + Pwhite(0.3,1.0), // switch duration + Pseq([0,1], inf), // switch indices, + // distinguish help synth values by reference to ~switchIndex + pbindArgs: [0.12, [\midinote, Pkey(\val) + + Pkey(\switchIndex).collect(switch(_, 0, [0,2.5], 1, [0,7])), \legato, 0.2 ] ], + switchOn: true, + switchOff: Pseq([false, true], inf) // next pause value with next switch index +).play; +) + +p.free; + + + +// hsStartIndices: define which help synths should be run from the beginning (only #1) +// help synth #0 is run when switched first (switchOn: true) +// movement downwards starts after 5 seconds + +( +p = PHSpar(h, + Pseq([5, Pwhite(0.3,1.0, inf)]), // switch duration + Pseq([1,0], inf), // switch indices, + // distinguish help synth values by reference to ~switchIndex + pbindArgs: [0.12, [\midinote, Pkey(\val) + + Pkey(\switchIndex).collect(switch(_, 0, [0,2.5], 1, [0,7])), \legato, 0.2 ] ], + switchOn: true, + hsStartIndices: 1 // \none (except the switched) or [1] would also do +).play; +) + +p.free; + + + +// set: determines whether new values should be polled from +// streams (defined in helpSynthArgs) at switch time. +// Here only at every second switch a new add value (register change) is set. + +( +p = PHSpar(h, + Pwhite(0.3, 0.5, inf), // switch duration + Pseq([0,1], inf), // switch indices, + [ [\add, Pseq([0, 10.5, 21], inf) ], [\add, Pseq([0, 10.5, 21], inf), \dev, 5] ], // helpSynthArgs + // distinguish help synth values by reference to ~switchIndex + pbindArgs: [0.12, [\midinote, Pkey(\val) + + Pkey(\switchIndex).collect(switch(_, 0, [0,2.5], 1, [0,7])), \legato, 0.2 ] ], + set: Pstutter(2, Pseq([true, false], inf)) // next set value with next switch index +).play; +) + +p.free; + + + +// now things quite mixed up + +// basic pitches chosen by index sequence defined in hsIndices, reference by Pkey(\theseVals) +// switchIndex only determines the added interval +// if theseVals contains only one value the determined interval is based on this value +// ([a] + [x, y] = [a + x, a + y]), +// otherwise it is just added to the interval of theseVals +// ([a, b] + [x, y] = [a + x, b + y]) + +// printout of variables which may be used within a PHSpar definition +// timeGrains is just a time ID, depending on granularity, not used for scheduling + +( +p = PHSpar(h, + Pwhite(0.3,1.0), // switch duration + Pseq([0,1], inf), // switch indices, + // distinguish added interval by reference to ~switchIndex + pbindArgs: [0.12, [ + \midinote, Pkey(\theseVals) + Pkey(\switchIndex).collect(switch(_, 0, [0, 2.5], 1, [0, 7])), + \legato, 0.2, + \post, Pfunc {|e| e.use { + "demandIndex: ".post; ~demandIndex.postln; + "timeGrains: ".post; ~timeGrains.postln; + "dur: ".post; ~dur.postln; + "switchIndex: ".post; ~switchIndex.postln; + "vals: ".post; ~vals.postln; + "val: ".post; ~val.postln; + "theseIndices: ".post; ~theseIndices.postln; + "theseVals: ".post; ~theseVals.postln; + "thisVal: ".post; ~thisVal.postln; + "================================".postln; + } + } + ]], + hsIndices: [ Pstutter(Pwhite(5,10), Pxrand([ 0, 1, \all ], inf)) ] +).play; +) + +p.free; + + +:: diff --git a/HelpSource/Classes/PHSparPlayer.schelp b/HelpSource/Classes/PHSparPlayer.schelp new file mode 100755 index 0000000..b948acd --- /dev/null +++ b/HelpSource/Classes/PHSparPlayer.schelp @@ -0,0 +1,291 @@ +CLASS:: PHSparPlayer +summary:: PHSpar player object +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/HSpar, Classes/PHSpar, Classes/PHSparUse, Classes/PHSusePlayer + +DESCRIPTION:: +Implicitely instantiated when link::Classes/PHSpar::'s play method is called, allows stopping and resuming with options also concerning the help synth. + + +CLASSMETHODS:: + +method::new + +Creates a new PHSparPlayer object. + +argument::pHelpSynthPar +A PHSpar object. + + +INSTANCEMETHODS:: + +method::play + +argument::clock +A TempoClock object. If not assigned, takes the default TempoClock. + +argument::quant +A Quant object. Makes the player start at the next grid that gives enough time for latency. + +argument::hsPlay +Boolean. Determines if help synth should start. Defaults to true. + +argument::switchPlay +Boolean. Determines if switch pattern should play. Defaults to true. + +argument::pbindPlay +Boolean. Determines if Pbind(s) should play. Defaults to true. + +argument::quantBufferTime +SimpleNumber (seconds). Calculated time to include latency for "stepping in" +is lengthened by this value. Defaults to 0.2. + + +method::stop + +argument::hsStop +Boolean. Determines if help synth should pause. Defaults to false. + +argument::switchStop +Boolean. Determines if switch pattern player should pause. Defaults to false. + +argument::pbindStop +Boolean. Determines if Pbind player(s) should pause. Defaults to true. + +argument::addAction +Function to be evaluated at receive time. + +method::pause + += strong::stop:: + + +method::free + +Stop the PHSparPlayer and all PHSusePlayers that are using the same HSpar, also free the HSpar. + +Note:: strong::stop:: (= strong::pause::) allows resuming the player - strong::free:: resets, player can be started again. +:: + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// define PHSpar and several users + +( +h = HSpar(s, [ + { |freq = 2, dev = 7, center = 60| + LFDNoise3.kr(freq, dev, center) }, + { |freq = 5, dev = 10, center = 80| + LFDNoise3.kr(freq, dev, center) } +]); + +~p1 = PHSpar(h, + pbindArgs: [0.1, [\midinote, Pkey(\thisVal), \legato, 0.2, \amp, 0.06 ] ], + hsIndices: [0] ); + +~p2 = PHSparUse(h, + pbindArgs: [0.2, [\midinote, Pkey(\thisVal) - 3,\legato, 0.2, \amp, 0.06 ]], + hsIndices: [1] ); + +~p3 = PHSparUse(h, + pbindArgs: [Pn(Pshuf([0.2, 0.1, 0.1], 1), inf), [\midinote, Pkey(\theseVals) + 5, \legato, 0.2, \amp, 0.06 ]], + hsIndices: [[0, 1]] ); + +~p4 = PHSparUse(h, + pbindArgs: [Pn(Pshuf([0.2, 0.1, 0.1], 1), inf), [\midinote, Pkey(\theseVals) + 8, \legato, 0.2, \amp, 0.06 ]], + hsIndices: [[0, 1]] ); + +c = TempoClock.new; +q = Quant(0.8, 0); +) + + +// start PHSparPlayer with help synth #0 + +~x1 = ~p1.play(c,q); + + +// add a PHSusePlayer using help synth #1 + +~x2 = ~p2.play(c,q); + + +// add a PHSusePlayer using both help synths + +~x3 = ~p3.play(c,q); + + +// and another one + +~x4 = ~p4.play(c,q); + + +// stop PHSparPlayer, help synths keep playing + +~x1.stop; + + +// stop a PHSusePlayer + +~x2.stop; + + +// and another one, only player ~x4 left + +~x3.stop; + + +// resume ~x1 + +~x1.play(c,q); + + +// stop only help synth #1 + +~x1.stop(hsStop: 1, pbindStop: false); + + +// stop also help synth #0 and player ~x1 + +~x1.stop(hsStop: 0); + + +// now only one pbind engaged via PHSusePlayer ~x4, but help synths may be controlled via PHSparPlayer ~x1 ! +// resume both help synths + + +~x1.play(hsPlay: [0,1], pbindPlay: false); + + +// stop them again, using keyword + +~x1.stop(hsStop: \all, pbindStop: false); + + +// stop last remaining PHSusePlayer via the "leading" PHSparPlayer and free HSpar + +~x1.free; + + + + + +//////////////////////////////////////////////////// + + +( +// define HSpar: two sine waves with opposite phases + +h = HSpar(s, [ + { |freq = 0.25, dev = 5, center = 70| + SinOsc.kr(freq, 3pi/2, dev, center) }, + { |freq = 0.25, dev = 5, center = 70| + SinOsc.kr(freq, pi/2, dev, center) } +]); + +// one stream shared by both switch indices +// goal: repetition of sine wave segment, alternate register, small random add for pitch + +~pitchBaseStream = (Pseq([60,80],inf) + Pwhite(0.0,5.0)).asStream; + +p = PHSpar(h, 2, // switchDur = half phase of sine wave + Pseq([0,1], inf), // switchIndex + [\center, ~pitchBaseStream] ! 2, + [0.25, [\midinote, Pkey(\val) + [-5.25, 0, 4, 7.25] , \legato, 0.2, \amp, 0.06 ]] +); + +) + + +// stopping and resuming with options +// if stopped together, help synths, pbinds and switchIndex player are kept in sync for resuming +// therefore methods stop and play are blocking the player for some moments, but freeing is always possible + +// switchStop = true is blocking for the whole current switch duration - this could be reduced + +x = p.play; + + +// stop everything, keep sync: try stopping and resuming several times, leave some moments between + +x.stop(switchStop: true, hsStop: true, pbindStop: true); + +x.play; + + +// free everything + +x.free; + + + +////////////////// + +x = p.play; + + +// stop only the pbind +// try stopping and resuming several times - always ascending segments of the sine wave, pbind is "stepping in" +// as points of stepping in may differ, low slope in ascending sequences may occur at the beginning or at the end + +x.stop; // equivalent to x.stop(switchStop: false, hsStop: false, pbindStop: true) + +x.play; + + +// + +x.free; + + + +////////////////// + +x = p.play; + + +// stop everything BUT the pbind +// try stopping and resuming several times - again always ascending segments of the sine wave as +// switchIndex player and help synths are stopped and started together + +x.stop(switchStop: true, hsStop: true, pbindStop: false); + +x.play; + + +// + +x.free; + + + +////////////////// + +x = p.play; + + +// let only help synths run +// try stopping and resuming several times +// switchIndex player and help synths are relinked, other sine wave segments establish + +x.stop(switchStop: true, hsStop: false, pbindStop: true); + +x.play; + + +// + +x.free; + + + +:: diff --git a/HelpSource/Classes/PHSparUse.schelp b/HelpSource/Classes/PHSparUse.schelp new file mode 100644 index 0000000..847fab7 --- /dev/null +++ b/HelpSource/Classes/PHSparUse.schelp @@ -0,0 +1,178 @@ +CLASS:: PHSparUse +summary::defines Pbind(s) for using synth values of a HSpar +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/HSpar, Classes/PHSpar, Classes/PHSparPlayer, Classes/PHSusePlayer + +DESCRIPTION:: +Defines Pbind(s) which, when played, can use values of synths derived from link::Classes/HSpar::'s synth definitions. +Note:: Playing a PHSparUse is just an option for using an already playing help synth of a HSpar, which requires link::Classes/PHSpar:: first. +See link::Guides/Guide_to_HS_and_HSpar::, "Working Scheme". +:: + +CLASSMETHODS:: + +method::new + +Creates a new PHSparUse object. + +argument::helpSynth +A HSpar object. + +argument::pbindArgs +Collection of the form strong::[ dur1, pbindData1, ... , durN, pbindDataN:: strong::]::, whereby +strong::dur:: is a duration value or pattern / stream of durations for the corresponding Pbind(s) and +strong::pbindData:: is a collection of Pbind pairs or a collection of Pbind pair collections, +defining possibly several Pbinds with same event timing. + +argument::hsIndices +Per default values are taken from the currently switched help synth. +Explicitely given strong::hsIndices:: allow reference to values of other help synths from the corresponding Pbind(s). +Expects a collection of valid hsIndex values resp. patterns / streams of valid hsIndex values. +A valid hsIndex value is a valid help synth index or a collection of valid help synth indices. + + +INSTANCEMETHODS:: + +method::play + +A link::Classes/PHSusePlayer:: object is instantiated and started using the TempoClock strong::clock::. +Quant or SimpleNumber strong::quant:: lets the player step into the quantization as soon as possible, +with respect to the necessary latency. The PHSusePlayer can be stopped and resumed with options. + + +EXAMPLES:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// HSpar with two synth definitions + +( +h = HSpar(s, [ + { |freq = 1, dev = 10, center = 80| + LFDNoise3.kr(freq, dev, center) }, + { |freq = 0.5, dev = 5, center = 60, addFreq = 0.1, addDev = 5| + LFDNoise3.kr(freq, dev, center) + SinOsc.kr(addFreq, 0, addDev) } +]); + +c = TempoClock(1); +q = 0.12; +) + + +// Play Pbind (via PHSpar) to poll values from one or both synths, +// according to the pattern given to hsIndices. +// Random add with adverb "+.x" adds an interval also to each of two simultaneous synth values. + +( +x = PHSpar(h, + pbindArgs: [0.12, [\midinote, Pkey(\theseVals).collect(_ +.x [7, 8, [0,7], [-1,8]].choose), + \legato, 0.2, \amp, 0.05 ]], + hsIndices: [ Prand([0,1,\all], inf)] +).play(c,q); +) + + +// "stepping in" with second player at (with respect to latency) next possible time on grid + +( +y = PHSparUse(h, + pbindArgs: [ Prand([0.36, 0.48],inf), + [\midinote, Pkey(\theseVals) + [1, 1.75, 2.5, 3.25, 4, 4.75, 5.5, 6.25], + \legato, 0.2, \amp, Prand([0.03, 0.05, 0.07], inf) ]], + hsIndices: [ Pn(Pshuf([0,0,1,1],1), inf) ] +).play(c,q); +) + + +// stop x, help synths still playing, producing values for y + +x.stop; + + +// resume x + +x.play(c,q); + + +// freeing the PHSparPlayer also stops all "related" PHSusePlayers and frees the HSpar + +x.free; + + + +///////////////////////////////////////////////////////////////////////////////// + +// Instead of using PHSuse / PHSparUse objects in order to have seperate players +// it's, of course, also possible to define several HS / HSpar objects independently. +// As before players can be synced by quantization. + +( +h = HSpar(s, [ + { |freq = 0.5, dev = 5, center = 70| + LFDNoise3.kr(freq, dev, center) }, + { |freq = 0.2, dev = 5, center = 62, addFreq = 0.1, addDev = 2| + LFTri.kr(freq, 0, dev, center) + SinOsc.kr(addFreq, 0, addDev) } +]); +c = TempoClock.new; +q = 0.24; +) + + +// first player using a HSpar + +( +x = PHSpar(h, + pbindArgs: [ + Pstutter(Pwhite(5,10), Pseq([0.12, 0.16], inf)), + [\midinote, Pkey(\thisVal) + Pseq([[2, 5], [1, 6], [0, 7]], inf), \legato, 0.2, \amp, 0.07], + 0.24, [\midinote, Pkey(\thisVal) + [-6, -1, 3], \legato, 0.2, \amp, 0.07]], + hsIndices: [0, 1] +).play(c,q); +) + + +// define an additional HS + +( +k = HS(s, {|start = 90, end = 50, dur = 10| XLine.kr(start, end, dur); }); +) + + +// start synced player + +( +r = PHS(k, + [\start, { rrand(85, 100) }], + 0.12, [\midinote, Pkey(\val) + [0, 3.5], \legato, 0.2, \amp, 0.07] +); +y = r.play(c,q); +) + + +// stop and free (reset) second player and HS + +y.free; + + +// play again - a new help synth is started + +y.play(c,q); + + +// stop and free first player and HSpar + +x.free; + + +// stop and free second one and HS + +y.free; + + +:: diff --git a/HelpSource/Classes/PHSplayer.schelp b/HelpSource/Classes/PHSplayer.schelp new file mode 100755 index 0000000..bc3ce1c --- /dev/null +++ b/HelpSource/Classes/PHSplayer.schelp @@ -0,0 +1,104 @@ +CLASS:: PHSplayer +summary:: PHS player object +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/HS, Classes/PHS, Classes/PHSuse, Classes/PHSusePlayer + +DESCRIPTION:: +Implicitely instantiated when link::Classes/PHS::'s play method is called, allows stopping and resuming with options also concerning the help synth. + + +CLASSMETHODS:: + +method::new + +Creates a new PHSplayer object. + +argument::pHelpSynth +A PHS object. + + +INSTANCEMETHODS:: + +method::play + +argument::clock +A TempoClock object. If not assigned, takes the default TempoClock. + +argument::quant +Quant or SimpleNumber. Makes the player start at the next grid that gives enough time for latency. + +argument::hsPlay +Boolean. Determines if help synth should also start. Defaults to true. + +argument::pbindPlay +Boolean. Determines if Pbind(s) should play. Defaults to true. + +argument::quantBufferTime +SimpleNumber (seconds). Calculated time to include latency for "stepping in" +is lengthened by this value. Defaults to 0.2. + +method::stop + +argument::hsStop +Boolean. Determines if help synth should stop. Defaults to false. + +argument::pbindStop +Boolean. Determines if Pbind player(s) should stop. Defaults to true. + +argument::addAction +Function to be evaluated at receive time. + +method::pause + += strong::stop:: + +method::free + +Stop the PHSplayer and all PHSusePlayers that are using the same HS, also free the HS. + +Note:: strong::stop:: (= strong::pause::) allows resuming the player - strong::free:: resets, player can be started again. +:: + +EXAMPLES:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// define HS, PHS - play + +( +h = HS(s, { |freq = 0.5, dev = 10, center = 65| LFDNoise3.kr(freq, dev, center) }); + +p = PHS(h, nil, // default help synth args + Prand([0.4, 0.2],inf), [ \midinote, Pkey(\val), \amp, 0.1 ], + 0.1, [ \midinote, Pkey(\val) + 9.5 + Pxrand([0, 2, 5],inf), \amp, 0.08 ] +).play; +) + + +// stop only help synth + +p.stop(hsStop: true, pbindStop: false); + + +// stop pbind + +p.stop; + + +// resume help synth and eventstream player + +p.play(hsPlay: true, pbindPlay: true); + + +// stop player and free HS + +p.free; + + +:: diff --git a/HelpSource/Classes/PHSuse.schelp b/HelpSource/Classes/PHSuse.schelp new file mode 100644 index 0000000..1281f52 --- /dev/null +++ b/HelpSource/Classes/PHSuse.schelp @@ -0,0 +1,127 @@ +CLASS:: PHSuse +summary:: defines Pbind(s) for using synth values of a HS +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/HS, Classes/PHS, Classes/PHSplayer, Classes/PHSusePlayer + +DESCRIPTION:: +Defines Pbind(s) which, when played, can use values of a synth derived from link::Classes/HS::'s synth definition. Note::Playing a PHSuse is just an option for using an already playing help synth of a HS, which requires link::Classes/PHS:: first. See link::Guides/Guide_to_HS_and_HSpar::, "Working Scheme". +:: + +CLASSMETHODS:: + +method::new + +argument::helpSynth +A HS object. + +argument::... pbindArgs +dur1, pbindData1, ... , durN, pbindDataN + +where strong::dur:: is duration value or pattern / stream of durations for corresponding Pbind(s) +and strong::pbindData:: is a collection of Pbind pairs or a collection of Pbind pair collections, +defining possibly several Pbinds with the same event timing. + +INSTANCEMETHODS:: + +method::play + +A link::Classes/PHSusePlayer:: object is instantiated and started using the TempoClock strong::clock::. +Quant or SimpleNumber strong::quant:: lets the player step into the quantization as soon as possible, +with respect to the necessary latency. The PHSusePlayer can be stopped and resumed with options. + + +method::asTask + +VarGui support, see link::Tutorials/HS_with_VarGui:: for examples. + +Returns a wrapper Task, which may be passed to a VarGui object. +Playing and stopping the wrapper Task invokes playing and +stopping behaviour of the underlying PHSusePlayer. + +argument::clock +TempoClock. + +argument::quant +Quant or SimpleNumber. + +argument::newEnvir +Boolean. Determines if Task will be played in a newly generated environment. Defaults to true. +This option especially becomes important when PHSuse's strong::pbindData:: contains functional code with +environmental variables. + +argument::removeCtrWithCmdPeriod +Boolean. Defaults to true. +Determines if notification of PHSusePlayer will be stopped with CmdPeriod. + + + + + +EXAMPLES:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// two Pbinds getting values for pitches from a single HS + +h = HS(s, { |freq = 0.5, dev = 10, center = 65| LFDNoise3.kr(freq, dev, center) }); + + +// define Pbinds via PHS and PHSuse separately, +// choose quant that allows synchronization + +( +u = PHS(h, nil, // default help synth args + 0.15, [ \midinote, Pkey(\val) + [-5, 0], \amp, 0.065 ]); + +v = PHSuse(h, 0.15, + [ \midinote, 130 - Pkey(\val) + [0, 5], // mirror at center frequency + \amp, Pwrand([0.03, 0.07], [0.7, 0.3], inf) ]); + +c = TempoClock(1); +q =0.3; +) + + +// start first player + +x = u.play(c,q); + + +// "stepping in" with second player at (with respect to latency) next possible time on grid + +y = v.play(c,q); + + +// stop x, help synth still playing, producing values for y + +x.stop; + + +// define and play another PHSuse + +( +w = PHSuse(h, 0.15, + [ \midinote, 130 - Pkey(\val) + [0, 5] + Pwhite(7.0, 10.0), // mirror at center frequency + random add + \amp, Pwrand([0.03, 0.07], [0.7, 0.3], inf) ]); + +z = w.play(c,q); +) + + +// resume x + +x.play(c,q); + + +// freeing the PHSplayer also stops all "related" PHSusePlayers and frees the HS + +x.free; + + +:: diff --git a/HelpSource/Classes/PHSusePlayer.schelp b/HelpSource/Classes/PHSusePlayer.schelp new file mode 100755 index 0000000..59007cb --- /dev/null +++ b/HelpSource/Classes/PHSusePlayer.schelp @@ -0,0 +1,104 @@ +CLASS:: PHSusePlayer +summary:: player object for PHSuse and PHSparUse +categories::Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Guide_to_HS_and_HSpar, Tutorials/HS_with_VarGui, Classes/HS, Classes/PHS, Classes/PHSuse, Classes/PHSplayer + +DESCRIPTION:: +Implicitely instantiated when link::Classes/PHSuse::'s or link::Classes/PHSparUse::'s play method is called. + + +CLASSMETHODS:: + +method::new + +Creates a new PHSusePlayer object. + +argument::pHelpSynthUse +A PHSuse or PHSparUse object. + +INSTANCEMETHODS:: + +method::play + +argument::clock +A TempoClock object. If not assigned, takes the default TempoClock. + +argument::quant +Quant or SimpleNumber. Makes the player start at the next grid that gives enough time for latency. + +argument::quantBufferTime +SimpleNumber (seconds). Calculated time to include latency for "stepping in" +is lengthened by this value. Defaults to 0.2. + +method::stop + +argument::addAction +Function to be evaluated at receive time. + +method::pause += strong::stop:: + + +method::free + +Only free this PHSusePlayer - the PHSplayer / PHSparPlayer, which is using the same HS / HSpar, is not affected. + +Note:: strong::stop:: (= strong::pause::) allows resuming the player - strong::free:: resets, player can be started again. +:: + +EXAMPLES:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// define HS, PHS, PHSuse + +( +h = HS(s, { |freq = 0.5, dev = 10, center = 65| LFDNoise3.kr(freq, dev, center) }); + +u = PHS(h, nil, // default help synth args + // two pbinds with different timing + Prand([0.4, 0.2],inf), [ \midinote, Pkey(\val) + 4, \amp, 0.07 ], + 0.1, [ \midinote, Pkey(\val) + 15 + Pxrand([0, 2, 5],inf), \amp, 0.04, \legato, 0.5 ] +); + +v = PHSuse(h, // two pbinds with different timing + Prand([0.4, 0.2],inf) , [ \midinote, Pkey(\val), \amp, 0.07 ], + 0.1, [ \midinote, Pkey(\val) + 6 + Pxrand([0, 2, 5],inf), \amp, 0.06, \legato, 0.5 ] +); + +c = TempoClock(1); +q = 0.2; +) + + +// play PHS + +x = u.play(c,q); + + +// play PHSuse + +y = v.play(c,q); + + +// stop PHSplayer + +x.stop; + + +// PHSusePlayer doesn't control HS - HS synth still running, see server window + +y.free; + + +// PHSplayer controls HS - this also stops HS synth + +x.free; + +:: diff --git a/HelpSource/Classes/PIdev.schelp b/HelpSource/Classes/PIdev.schelp new file mode 100755 index 0000000..9be0a0b --- /dev/null +++ b/HelpSource/Classes/PIdev.schelp @@ -0,0 +1,346 @@ +CLASS:: PIdev +summary:: pattern searching for numbers with integer distance from a source pattern, optionally avoiding repetitions within a span +categories:: Libraries>miSCellaneous>Other patterns, Libraries>miSCellaneous>Idev suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/Idev_suite, Classes/PLIdev, Classes/DIdev + + +DESCRIPTION:: + + +DIdev / PIdef / PLIdev search for numbers with integer distance from a source signal / pattern up to a given deviation. Repetitions within a lookback span are avoided, DIdev / PIdef / PLIdev randomly choose from possible solutions. Intended for search within integer grids (pitches, indices etc.), however applications with non-integer sources are possible, see examples. + +note:: +It's the user's responsibility to pass a combination of deviation and lookback values that allows a possible choice, see examples. +:: + +note:: +In contrast to DIdev, PIdev and PLIdev do *not* need to know maximum deviations (strong::minLoDev::, strong::maxHiDev::) beforehand. Thus the order of arguments is different (here strong::loDev:: and strong::hiDev:: before lookBack). +:: + + + +CLASSMETHODS:: + + +method::new + +Creates a new PIdev object. + + +argument::pattern +The source value pattern to start search from. + +argument::maxLookBack +Integer, the maximum lookback span. Fixed, defaults to 3. + +argument::loDev +Determines the current low deviation for the search. Defaults to -3. + +argument::hiDev +Determines the current high deviation for the search. Defaults to 3. + +argument::lookBack +Determines the current lookback span for avoiding repetitions. Can be modulated but must not exceed strong::maxLookBack::. If no value is passed, then strong::maxLookBack:: is taken. + +argument::thr +Threshold for equality comparison. Can be modulated, defaults to 1e-3. + +argument::length +Number of repeats. Defaults to inf. + + + +section::Examples + + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + + +anchor::Ex.1:: +subsection::Ex. 1: Basic usage: random choice within region without repetitions + +code:: +// constant source (72), max deviation +/- 3 +// no repetition within 5 pitches + +( +p = Pbind( + \dur, 0.2, + \midinote, PIdev(72, 5, -3, 3).trace(prefix: "midi "), +).play; +) + +p.stop +:: + + + + +anchor::Ex.2:: +subsection::Ex. 2: Variable deviations and lookBack + +code:: +( +~loDev = -6; +~hiDev = 5; +~lookBack = 2; + +p = Pbind( + \dur, 0.2, + \midinote, PIdev(72, 11, Pfunc { ~loDev }, Pfunc { ~hiDev }, Pfunc { ~lookBack }).trace(prefix: "midi "), +).play; +) + +// change on the fly +// as lookBack equals 2, this defines a fixed sequence (up or down anyway) + +( +~loDev = -1; +~hiDev = 1; +) + +// widen range + +( +~loDev = -6; +~hiDev = 5; +) + +// force a twelve-tone row + +~lookBack = 11; + + +// contradictory input, lookBack 11 not possible within range, causes repetitions + +( +~loDev = -3; +~hiDev = 2; +) + +p.stop +:: + + + + +anchor::Ex.3:: +subsection::Ex. 3: Moving source signal + +code:: +( +~loDev = -1; +~hiDev = 1; +~lookBack = 2; + +p = Pbind( + \dur, 1/7, + \midinote, PIdev( + Pseg(Pseq([65, 90], inf), 5, \sin).round, + 11, + Pfunc { ~loDev }, + Pfunc { ~hiDev }, + Pfunc { ~lookBack } + ).trace(prefix: "midi "), +).play; +) + +// widen range and increase lookBack + +( +~loDev = -6; +~hiDev = 5; +~lookBack = 10; +) + +p.stop +:: + + + +anchor::Ex.4:: +subsection::Ex. 4: Dynamic deviation range and lookBack + +code:: +// lookBack and deviations coupled here +// maxLookBack must be large enough + +( +~loDev = -1; +~hiDev = 1; +~lookBack = 2; + + +p = Pbind( +    \dur, 1/7, +    \midinote, PIdev( +        78, +        10, +        Pn(Plazy { ~loDev }), +        Pn(Plazy { ~hiDev }).trace(prefix: "absolute deviation "), +        Pn(Plazy { ~lookBack }) +    ).trace(prefix: "midi "); +).play +) + +// start parameter movement on the fly + +( +~loDev = Pseg(Pseq([2, 5].neg, inf), 5, \sin); +~hiDev = Pseg(Pseq([2, 5], inf), 5, \sin).trace(prefix: "absolute deviation and lookBack "); +~lookBack = Pseg(Pseq([2, 5], inf), 5, \sin); +) + +p.stop +:: + + + + +anchor::Ex.5:: +subsection::Ex. 5: Non-integer source + +code:: +( +~loDev = -6; +~hiDev = 5; +~lookBack = 3; +~thr = 1; + +p = Pbind( + \dur, 1/7, + \midinote, PIdev( + Pseg(Pseq([65, 90], inf), 5, \sin), + 5, + Pfunc { ~loDev }, + Pfunc { ~hiDev }, + Pfunc { ~lookBack }, + Pfunc { ~thr } + ).trace(prefix: "midi "), +).play; +) + +// close floats can occur here +~thr = 0.01 + +// not here +~thr = 2 + +p.stop +:: + + + + +anchor::Ex.6:: +subsection::Ex. 6: Multichannel expansion + +code:: +// larger pitch range and lookBack in upper voice, +// as with most patterns, no automatic array expansion of pattern arguments + +( +p = Pbind( + \dur, 1/7, + \src, Pseg(Pseq([65, 85], inf), 5, \sin).round, + \midinote, Ptuple([ + PIdev(Pkey(\src), 1, -1, 1), + PIdev(Pkey(\src) + 8.5, 3, -5, 5) + ]).trace(prefix: "midi "), +).play; +) + +p.stop +:: + + + + +anchor::Ex.7:: +subsection::Ex. 7: Application to other params: rhythm + +code:: +// if we have indexed data of whatever, we can slide over it, +// groups of durations as items to be streamed by PIdev + +( +~rhythmBase = [ + [1, 1], + [2, 1, 1], + [1, 1, 2] +].collect(_.normalizeSum); + +~rhythms = ~rhythmBase *.x [1, 2]; +~rhythmNum = ~rhythms.size; +~rhythms = ~rhythms.scramble; + +SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, + rq = 0.05, pan = 0, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, Pan2.ar(sig, pan)); +}).add; +) + +( +// rhythmic variation and partial pitch repetition +// play for a while to note slow sliding caused by Pseg + +~loDev = -1; +~hiDev = 1; + +p = Pbind( + \instrument, \noise_grain, + \rel, Pexprand(0.05, 0.15), + \dur, PIdev( + // be careful not to exceed index bounds + Pseg(Pseq([~loDev.abs, ~rhythmNum - ~hiDev - 1], inf), 10, \sin, inf).round, + 2, // lookBack span, no repetition within 3 items + ~loDev, + ~hiDev + ).trace(prefix: "rhythm type: ").collect(~rhythms[_]).flatten * 0.3, + \midinote, Pstutter(Pwhite(1, 2), Pclump(3, Pxrand((50..100), inf))).flatten + [0, 12], + \pan, Pstutter(Pwhite(1, 2), Pclump(3, Pwhite(-1.0, 1))).flatten +).play; +) + +p.stop +:: + + +anchor::Ex.8:: +subsection::Ex. 8: Proof of concept + +code:: +( +// Function to check an array for repetitions within a maximum test span + +~repetitionCheck = { |array, maxTestSpan| + maxTestSpan.do { |i| + var result = (array.drop(i+1) - array).drop((i+1).neg).includes(0).not; + ("no repetitions within a span of " ++ (i+2).asString ++ " items: ").post; + result.postln; + } +} +) + +// test case +// no repetitions within a maximum span of 6 (lookBack == 5) + +( +p = PIdev(Pbrown(0, 20, 0.3).round.asInteger, 5, -7, 7).iter; +a = p.nextN(10000); +a.plot; +~repetitionCheck.(a, 10); +) + +:: + diff --git a/HelpSource/Classes/PL.schelp b/HelpSource/Classes/PL.schelp new file mode 100644 index 0000000..2ac45d0 --- /dev/null +++ b/HelpSource/Classes/PL.schelp @@ -0,0 +1,144 @@ +CLASS:: PL +summary:: dynamic scope placeholder pattern +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds, Classes/PLn + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. +note:: PL follows a paradigm of immediate replacement. There are cases though where you might prefer to finish streams or substreams before replacement, especially when syncing comes into play, for these options consider link::Classes/PLn:: and the strong::cutItems:: arg of PLx list patterns. +:: + +CLASSMETHODS:: + +method::new + +Creates a new PL object. + +argument::item +Symbol or other Object. +If a Symbol is passed, item can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::type +Expects 1 or inf. +Defines how items other than Patterns or Streams should be embedded. +Defaults to 1. Rather to be used by other PLx Patterns than by the user. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// definition for future reference in arbitrary Environments + +p = Pbind(\midinote, PL(\a), \dur, 0.2); + + +// prepare current Environment + +( +~a = 60; +x = p.play; +) + + +// replace with items or patterns + +~a = 58; + + +// PL had repeats = inf, so Pseq is embedded endlessly + +~a = Pseq([60, 60, 58, 60, 53, 54.5, 56, 58]); + +x.stop; + + +////////////////////// + + +// placeholder may also get event patterns + +( +p = PL(\a, 1); + +~a = Pbind( + \midinote, Pwhite(80, 85), + \dur, 0.2 +); + +x = p.play; +) + + + +// replace, PL had repeats = 1, so ... + +( +~a = Pbind( + \midinote, Pseq((70..65)), + \dur, 0.05 +); +) + + +////////////////////// + + +// PL may be used in cases where there is no PLx implementation + +// Pseg used for pitch curve, linear interpolation + +( +p = Pbind( + \midinote, Pseg(PL(\p), PL(\d), \lin, inf), + \dur, 0.1 +); + +~p = Pshuf((50..75)); + +~d = 0.2; + +x = p.play; +) + + +// play with segment length + +~d = 0.4; + +~d = 1; + + +// can also be replaced by pattern + +~d = Pseq([0.2, 0.4, 1]); + + +// only two points left for interpolation +// there may be repetitions + +~p = Pshufn([50, 100]).trace; + +x.stop; + + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLIdev.schelp b/HelpSource/Classes/PLIdev.schelp new file mode 100755 index 0000000..0e76b2f --- /dev/null +++ b/HelpSource/Classes/PLIdev.schelp @@ -0,0 +1,378 @@ +CLASS:: PLIdev +summary:: dynamic scope pattern searching for numbers with integer distance from a source pattern, optionally avoiding repetitions within a span +categories:: Libraries>miSCellaneous>Other patterns, Libraries>miSCellaneous>Idev suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/Idev_suite, Tutorials/PLx_suite, Classes/PIdev, Classes/DIdev + + +DESCRIPTION:: + + +DIdev / PIdef / PLIdev search for numbers with integer distance from a source signal / pattern up to a given deviation. Repetitions within a lookback span are avoided, DIdev / PIdef / PLIdev randomly choose from possible solutions. Intended for search within integer grids (pitches, indices etc.), however applications with non-integer sources are possible, see examples. + +note:: +It's the user's responsibility to pass a combination of deviation and lookback values that allows a possible choice, see examples. +:: + +note:: +In contrast to DIdev, PIdev and PLIdev do *not* need to know maximum deviations (strong::minLoDev::, strong::maxHiDev::) beforehand. Thus the order of arguments is different (here strong::loDev:: and strong::hiDev:: before lookBack). +:: + + + +CLASSMETHODS:: + + +method::new + +Creates a new PLIdev object. + + +argument::pattern +Symbol or PIdev pattern arg. The source value pattern to start search from. If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. + +argument::maxLookBack +Integer, the maximum lookback span. Fixed, defaults to 3. + +argument::loDev +Symbol or PIdev loDev arg. Determines the current low deviation for the search. If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to -3. + +argument::hiDev +Symbol or PIdev hiDev arg. Determines the current high deviation for the search. If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to 3. + +argument::lookBack +Symbol or PIdev lookBack arg. Determines the current lookback span for avoiding repetitions. Can be modulated but must not exceed strong::maxLookBack::. If no value is passed, then strong::maxLookBack:: is taken. If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to nil. + +argument::thr +Symbol or PIdev thr arg. Threshold for equality comparison. If a Symbol is passed, a pattern/value can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to 1e-3. + +argument::length +Symbol or PIdev length arg. Number of repeats. If a Symbol is passed, a value can be assigned to an envir variable later on. Defaults to inf. + +argument::envir +Dictionary or one of the Symbols \top, \t (topEnvironment), \current, \c (currentEnvironment). Dictionary to be taken for variable reference. Defaults to \current. + + + +section::Examples + + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + + +anchor::Ex.1:: +subsection::Ex. 1: Basic usage: random choice within region without repetitions + +code:: +// constant source, max deviation +/- 3 +// no repetition within 5 pitches + +( +~in = 72; + +p = Pbind( + \dur, 0.2, + \midinote, PLIdev(\in, 5, -3, 3).trace(prefix: "midi "), +).play; +) + +// change source + +~in = 65 + +p.stop +:: + + + + +anchor::Ex.2:: +subsection::Ex. 2: Variable deviations and lookBack + +code:: +( +~loDev = -6; +~hiDev = 5; +~lookBack = 2; + +p = Pbind( + \dur, 0.2, + \midinote, PLIdev(72, 11, \loDev, \hiDev, \lookBack).trace(prefix: "midi "), +).play; +) + + +// change on the fly +// as lookBack equals 2, this defines a fixed sequence (up or down anyway) + +( +~loDev = -1; +~hiDev = 1; +) + + +// widen range + +( +~loDev = -6; +~hiDev = 5; +) + + +// force a twelve-tone row + +~lookBack = 11; + + +// contradictory input, lookBack 11 not possible within range, causes repetitions + +( +~loDev = -3; +~hiDev = 2; +) + +p.stop +:: + + + + +anchor::Ex.3:: +subsection::Ex. 3: Moving source signal + +code:: +( +~loDev = -1; +~hiDev = 1; +~lookBack = 2; + +p = Pbind( + \dur, 1/7, + \midinote, PLIdev( + Pseg(Pseq([65, 90], inf), 5, \sin).round, + 11, + \loDev, + \hiDev, + \lookBack + ).trace(prefix: "midi "), +).play; +) + + +// widen range and increase lookBack + +( +~loDev = -6; +~hiDev = 5; +~lookBack = 10; +) + +p.stop +:: + + + +anchor::Ex.4:: +subsection::Ex. 4: Dynamic deviation range and lookBack + +code:: +// lookBack and deviations coupled here +// maxLookBack must be large enough + +( +~loDev = -1; +~hiDev = 1; +~lookBack = 2; + +p = Pbind( + \dur, 1/7, + \midinote, PLIdev(78, 10, \loDev, \hiDev, \lookBack).trace(prefix: "midi "), +).play; +) + +// start parameter movement on the fly + +( +~loDev = Pseg(Pseq([2, 5].neg, inf), 5, \sin); +~hiDev = Pseg(Pseq([2, 5], inf), 5, \sin).trace(prefix: "absolute deviation and lookBack "); +~lookBack = Pseg(Pseq([2, 5], inf), 5, \sin); +) + +p.stop +:: + + + + +anchor::Ex.5:: +subsection::Ex. 5: Non-integer source + +code:: +( +~loDev = -6; +~hiDev = 5; +~lookBack = 3; +~thr = 1; + +p = Pbind( + \dur, 1/7, + \midinote, PLIdev( + Pseg(Pseq([65, 90], inf), 5, \sin), + 5, + \loDev, + \hiDev, + \lookBack, + \thr + ).trace(prefix: "midi "), +).play; +) + + +// close floats can occur here +~thr = 0.01 + + +// not here +~thr = 2 + +p.stop +:: + + + + +anchor::Ex.6:: +subsection::Ex. 6: Multichannel expansion + +code:: +// larger pitch range and lookBack in upper voice +// as with most patterns, no automatic array expansion of pattern arguments + +( +~src = Pseg(Pseq([65, 85], inf), 5, \sin).round; + +p = Pbind( + \dur, 1/7, + \midinote, Ptuple([ + PLIdev(\src, 1, -1, 1), + PLIdev(\src, 3, -5, 5) + 8.5 + ]).trace(prefix: "midi "), +).play; +) + +p.stop +:: + + + + +anchor::Ex.7:: +subsection::Ex. 7: Application to other params: rhythm + +code:: +// if we have indexed data of whatever, we can slide over it, +// groups of durations as items to be streamed by PLIdev + +( +~rhythmBase = [ + [1, 1], + [2, 1, 1], + [1, 1, 2] +].collect(_.normalizeSum); + +~rhythms = ~rhythmBase *.x [1, 2]; +~rhythmNum = ~rhythms.size; +~rhythms = ~rhythms.scramble; + +SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, + rq = 0.05, pan = 0, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, Pan2.ar(sig, pan)); +}).add; +) + +( +// rhythmic variation and partial pitch repetition +// play for a while to note slow sliding, caused by Pseg + +~loDev = -1; +~hiDev = 1; +~maxLookBack = 5; // fixed ! +~lookBack = 2; + +// be careful not to exceed index bounds +~src = Pseg(Pseq([~loDev.abs, ~rhythmNum - ~hiDev - 1], inf), 10, \sin, inf).round; + +p = Pbind( + \instrument, \noise_grain, + \rel, Pexprand(0.05, 0.15), + \dur, PLIdev(\src, ~maxLookBack, \loDev, \hiDev, \lookBack).trace(prefix: "rhythm type: ") + .collect(~rhythms[_]).flatten * 0.3, + \midinote, Pstutter(Pwhite(1, 2), Pclump(3, Pxrand((50..100), inf))).flatten + [0, 12], + \pan, Pstutter(Pwhite(1, 2), Pclump(3, Pwhite(-1.0, 1))).flatten +).play; +) + + +// more variation by larger deviation from fixed source and larger lookBack + +( +~src = 3; +~loDev = -3; +~hiDev = 2; +~lookBack = 4; +) + + +// fixed cycle with these bounds and lookBack == 1 + +( +~loDev = -1; +~hiDev = 0; +~lookBack = 1; +) + +// other fixed cycle + +~src = 2; + +p.stop +:: + + +anchor::Ex.8:: +subsection::Ex. 8: Proof of concept + +code:: +( +// Function to check an array for repetitions within a maximum test span + +~repetitionCheck = { |array, maxTestSpan| + maxTestSpan.do { |i| + var result = (array.drop(i+1) - array).drop((i+1).neg).includes(0).not; + ("no repetitions within a span of " ++ (i+2).asString ++ " items: ").post; + result.postln; + } +} +) + +// test case +// no repetitions within a maximum span of 6 (maxLookBack == 5) + +( +~src = Pbrown(0, 20, 0.3).round.asInteger; +p = PLIdev(\src, 5, -7, 7).iter; +a = p.nextN(10000); +a.plot; +~repetitionCheck.(a, 10); +) +:: + diff --git a/HelpSource/Classes/PLbeta.schelp b/HelpSource/Classes/PLbeta.schelp new file mode 100644 index 0000000..c6b4012 --- /dev/null +++ b/HelpSource/Classes/PLbeta.schelp @@ -0,0 +1,114 @@ +CLASS:: PLbeta +summary:: dynamic scope Pbeta variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pbeta, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLbeta object. + +argument::lo +Symbol or Pbeta lo arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::hi +Symbol or Pbeta hi arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::prob1 +Symbol or Pbeta prob1 arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::prob2 +Symbol or Pbeta prob2 arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Pbeta length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +( +p = Pbind( + \midinote, PLbeta(\lo, \hi, \p1, \p2), + \dur, 0.1 +); +) + +// prepare current Environment +// prob values for equal distribution + +( +~lo = 60; +~hi = 90; +~p1 = 1; +~p2 = 1; +) + + +// run + +x = p.play; + + +// replace probabilities, get values close to the bounds + +( +~p1 = 0.02; +~p2 = 0.02; +) + + +// change between close-to-bounds and equally-distributed +// PLseq defaults to repeats = inf + +( +~p1 = Pstutter(10, PLseq([0.01, 1])); +~p2 = Pstutter(10, PLseq([0.01, 1])); +) + + +// moving bounds + +( +~lo = PLseq((60..70)); +~hi = PLseq((80..90)); +) + +x.stop; + +:: + diff --git a/HelpSource/Classes/PLbindef.schelp b/HelpSource/Classes/PLbindef.schelp new file mode 100755 index 0000000..2a2c71c --- /dev/null +++ b/HelpSource/Classes/PLbindef.schelp @@ -0,0 +1,423 @@ +CLASS::PLbindef +summary::wrapper for Pbindef which allows replacement in object prototyping style +categories:: Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Tutorials/PLx_suite, Classes/PLbindefPar, Classes/PLbindefEnvironment, Classes/PLbindefParEnvironment, Classes/EventShortcuts, Tutorials/PLx_and_live_coding_with_Strings + + +DESCRIPTION:: +PLbindef works like a normal Pbindef and, like the latter, uses Pdef's global bookkeeping, but replacement of key streams can be done in object prototyping style with a dedicated PLbindefEnvironment, which also holds player methods. This hybrid Environment is itself assigned to the PLbindef's name in an Environment of choice, by default the current Environment. Setting can thus be done in very condensed syntax, also in combination with link::Classes/EventShortcuts::. For more info on object prototyping see link::Classes/Environment#Using Environments as object prototypes#::. + + +note:: +PLbindefs are registered globally in the same Dictionary as Pdefs, Pbindefs and PLbindefPars. It's recommended to do cleanup with code::remove:: or code::Pdef.removeAll:: after using PLbindef / PLbindefPar as in examples below. Otherwise unwanted or strange behaviour might be caused by leftover sources when playing a new PLbindef / PLbindefPar example with the same key. With SC >= 3.7 occasional posts of the default parent event occur with Pbindef, so also with PLbindef, this doesn't cause problems though. +:: + +CLASSMETHODS:: + +method::new + +Creates a new PLbindef object or sets sources as with Pbindef. + +argument::... args +First arg should be the name, followed by key/value pairs for the Pbindef. The last arg can be an optional environment which determines where the corresponding PLbindefEnvironment should be stored, by default this is the current Environment at instantiation time. + +INSTANCEMETHODS:: + +private::miSC_setRefEnvir +private::miSC_updateSourceEnvir + + +method::clear + +As with link::Classes/Pdef#-clear::, but in addition remove name entry from strong::refEnvir::. + + +method::sourceEnvir + +Getter for PLbindef's PLbindefEnvironment. + + +method::refEnvir + +Getter for the Environment where PLbindef's PLbindefEnvironment is associated with PLbindef's key. + + + +anchor::above:: +SECTION::Examples + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + +// synthdefs to play with + +( +SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\sin_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; +) +:: + + +SUBSECTION::Ex.1) Setting key streams + +code:: +// start with a fresh global Dictionary + +Pdef.removeAll + +// store PLbindef under key \x + +( +PLbindef(\x, + \instrument, \sin_grain, + \dur, 0.2, + \midinote, Pwhite(60, 90) +) +) + +// PLbindefEnvironment has been made and assigned to the variable ~x in currentEnvironment, check + +~x + + +// now the PLbindefEnvironment can also be treated as a player + +~x.play + + +// set params while playing + +~x.att = Pwhite(0.01, 0.2) + +~x.midinote = 75 + +~x.att = 0.01 + +~x.dur = 0.02 + +~x.midinote = Pbrown(60, 90, 1.5) + + +// prototype underscore syntax + +~x.midinote_(Pbrown(70, 80, 3)) + + + +// pause + +~x.stop + +// resume + +~x.play + + + +// use method 'value' for parallel setting + +~x.(\dur, 0.05, \midinote, Pwhite(70.0, 72)) + + +// sequential underscore setting + +~x.dur_(0.07).midinote_(Pwhite(72.0, 74)) + + +// Pbindef syntax of setting also still possible + +PLbindef(\x, \dur, 0.1, \midinote, Pwhite(80, 81.0)) + + +~x.stop + +// cleanup + +~x.remove +:: + + + +SUBSECTION::Ex.2) PLbindef with EventShortcuts + +Use SynthDefs from link::Classes/PLbindef#above#above::. + +code:: +// syntax can be more condensed with EventShortcuts, turn it on, post defaults + +EventShortcuts.on + +EventShortcuts.postCurrent + + +( +PLbindef(\u, + \i, \saw_grain, + \d, 0.2, + \m, Pwhite(60, 90) +) +) + +~u.play + +~u.d = 0.1 + + +~u.m = Pn(Plazy { Pseries(rrand(50, 70), 1.5, 20) }) + +~u.m = ~u.m + [0, 4, 7] + +~u.i = PLseq([\sin_grain, \saw_grain]) + + +~u.a = Pn(Pseries(0.05, 0.01, 20)) + +~u.m = 70 + + +// stop + cleanup in one + +~u.remove +:: + + + +SUBSECTION::Ex.3) PLbindef with VarGui + +Use SynthDef from link::Classes/PLbindef#above#above::. + +code:: +// start with gui and define pitch sequence with sliders + +( +p = PLbindef(\z, + \i, \saw_grain, + \d, 0.2, + \m, PLseq(\midi) +); + +VarGui([\midi, [50, 90, \lin, 1, 60] ! 7], stream: p).gui; +) + + +~z.m = PLseq(\midi) + [0, 5, 8] + +~z.m = ~z.m + PLseq([-1, 0, 1] * 12) + +// stop with gui, then do cleanup + +~z.remove +:: + + +SUBSECTION::Ex.4) PLbindef with PbindFx + +See also link::Classes/PbindFx#Ex. 7c#Ex.7c: Replacement with Pbindef:: + +code:: +// boot server with extended resources + +( +s.options.numPrivateAudioBusChannels = 1024; +s.options.memSize = 8192 * 16; +s.reboot; +) + +( +SynthDef(\echo, { |out, in, maxEchoDelta = 0.2, echoDelta = 0.1, + decayTime = 1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = DelayL.ar( + CombL.ar(inSig, maxEchoDelta, echoDelta, decayTime, amp), + maxEchoDelta, + maxEchoDelta - echoDelta + ); + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + +SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000, + cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = RLPF.ar( + inSig, + LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi), + rq, + amp + ).softclip; + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + +SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\sin_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; +) + +// prepare EventShortcuts for additional keys + +( +EventShortcuts.addOnBase(\default, \fxs, ( + dec: \decayTime, + cd: \cleanupDelay, + cf: \cutOffMoveFreq, + fxo: \fxOrder, + dta: \echoDelta +), true); + +EventShortcuts.makeCurrent(\fxs); + +EventShortcuts.on; +) + + +( +// source and fxs passed as PLbindefs + +k = [\q, \echo, \wah]; + +PLbindef(\q, + \i, PLrand([\noise_grain, \saw_grain]), + \d, 0.25, + \m, PLseq([60, 60, 60, 62]), + \fxo, PLseq([0, 0, 1, 2]), + // echo introduces delay, so do delay if no echo + \lag, Pfunc { |e| e.fxo.asArray.includes(1).if { 0 }{ 0.2 } }, + \a, 0.1, + \att, 0.01, + \rel, 0.1, + + \cd, Pkey(\att)+ Pkey(\rel) + 0.001 +); + +PLbindef(\echo, + \fx, \echo, + \dta, 0.06, + \a, 0.5, + \dec, Pwhite(0.3, 1.8), + \cd, Pkey(\dec) +); + +PLbindef(\wah, + \fx, \wah, + \cf, Pwhite(1, 3), + \a, 0.5, + \cd, 0.01 +); + + +p = PbindFx(*k.collect(PLbindef(_))); // PbindFx(*(k.collect { |x| PLbindef(x) })); + +q = p.play; +) + + + +// manipulate midinotes + +~q.m = ~q.m + PLseq([0, 12, -12]) + +~q.m = ~q.m + PLrand([0, [0, -4]]) + +~q.m = ~q.m + PLrand([0, [0, -7]]) + + +// rhythm + +~q.d = PLshufn([1, 1, 2, 1] / 8) + + +// echo param and fx order sequence + +~echo.dta = Pwhite(0.03, 0.08) + +~q.fxo = PLseq([0, 0, 1, 2, 1, [1, 2]]) + + + +~q.m = 48 + +// produce overtone series with echodelta + +~echo.dta = 1 / 24.midicps / PLseq((1..16)) + + +// this also works in chords ("fx expansion") +// source is processed twice per event + +~echo.dta = 1 / [24, 26.7].midicps / PLseq((1..16)) + + + +// cleanup + +q.stop + +Pdef.removeAll +:: + + + + + +SUBSECTION::Ex.5) Passing a refEnvir + +code:: +// on occasion it might be desirable to control from a dedicated Environment + +Pdef.removeAll + +( +PLbindef(\a, + \instrument, \sin_grain, + \dur, 0.2, + \midinote, Pwhite(60, 90), + e = () +) +) + +e.use { ~a.play } + +e[\a].stop + +e.a.play + +e.a.stop + +e.a.remove +:: diff --git a/HelpSource/Classes/PLbindefEnvironment.schelp b/HelpSource/Classes/PLbindefEnvironment.schelp new file mode 100644 index 0000000..e97c5b3 --- /dev/null +++ b/HelpSource/Classes/PLbindefEnvironment.schelp @@ -0,0 +1,82 @@ +CLASS::PLbindefEnvironment +summary::Environment made by PLbindef to play and set its sources +categories:: Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Tutorials/PLx_suite, Classes/PLbindef, Classes/PLbindefPar, Classes/PLbindefParEnvironment, Classes/EventShortcuts, Tutorials/PLx_and_live_coding_with_Strings + + +DESCRIPTION:: +Instances of this class are made as side effect of PLbindef creation, assigned to PLbindef's name in an Environment of choice (by default the current one) and used as player interface as well as for setting PLbindef's sources in condensed prototyping syntax. They are not thought to be created explicitely though. See link::Classes/PLbindef:: for examples. + + + +CLASSMETHODS:: + +method::new + +Creates a new PLbindefEnvironment object with arguments of IdentityDictionary. In contrast to the latter strong::know:: defaults to true, which allows setting sources of the PLbindef in object prototyping style. strong::name:: is used for the corresponding key of the PLbindef. Normally not to be used explicitely. + + +INSTANCEMETHODS:: + +private::miSC_setName +private::miSC_envirClear +private::miSC_setPLbindef + +method::put + +Associates strong::obj:: with Symbol strong::key:: and updates PLbindef's source. + + +method::superPut + +Associates strong::obj:: with Symbol strong::key:: mimicing link::Classes/IdentityDictionary#-put::. + + +method::value + +Expects key/value pairs and applies strong::put::. + + +method::name + +Getter for PLbindef's key. + + +method::plbindef + +Getter for the corrresponding PLbindef. + + +method::play + +Plays all corresponding PLbindefs with passed arguments, which might be arrays. In this case wrapped indexing is applied. + + +method::isPlaying + +Indicates if the corresponding PLbindef is playing. + + +method::reset + +Resets the corresponding PLbindef. + + +method::stop + +Stops the corresponding PLbindef. + + +method::clear + +Clears the corresponding PLbindef. + + +method::remove + +Removes the corresponding PLbindef. + + + + + diff --git a/HelpSource/Classes/PLbindefPar.schelp b/HelpSource/Classes/PLbindefPar.schelp new file mode 100755 index 0000000..8072697 --- /dev/null +++ b/HelpSource/Classes/PLbindefPar.schelp @@ -0,0 +1,672 @@ +CLASS::PLbindefPar +summary::container for parallel PLbindefs which allows replacement in object prototyping style +categories:: Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite + +related:: Overviews/miSCellaneous, Tutorials/PLx_suite, Classes/PLbindef, Classes/PLbindefEnvironment, Classes/PLbindefParEnvironment, Classes/EventShortcuts, Tutorials/PLx_and_live_coding_with_Strings + + +DESCRIPTION:: +PLbindefPar employs a number of parallel PLbindefs, replacement of key streams can be done in object prototyping style with a dedicated PLbindefParEnvironment, which also holds player methods. This hybrid Environment is itself assigned to the PLbindefPar's name in an Environment of choice, by default the current Environment. Setting can thus be done in very condensed syntax, also in combination with link::Classes/EventShortcuts::. For more info on object prototyping see link::Classes/Environment#Using Environments as object prototypes#::. + +note:: +PLbindefs are registered globally in the same Dictionary as Pdefs, Pbindefs and PLbindefPars. In addition for size = n PLbindefs of the same name with indices i = 0, ... n-1 appended are stored globally. It's recommended to do cleanup with code::remove:: or code::Pdef.removeAll:: after using PLbindef / PLbindefPar as in examples below. Otherwise unwanted or strange behaviour might be caused by leftover sources when playing a new PLbindef / PLbindefPar example with the same key. With SC >= 3.7 occasional posts of the default parent event occur with Pbindef, so also with PLbindef, this doesn't cause problems though. +:: + +note:: +For setting, getting, playing, stopping and resetting subsets of PLbindefs in prototyping syntax the class TempPLbindefParEnvironment is involved (see link::Classes/PLbindefPar#Ex. 1#Ex.1::). The user doesn't need to care about this class, instances are generated implicitely and might occur in the post window. +:: + + + +CLASSMETHODS:: + +method::new + +Creates a new PLbindefPar object or sets sources of an existing one. + + +argument::... args +First arg should be the strong::name::, followed by strong::num::, the number of parallel PLbindefs, and the key/value pairs. Values are assigned to single PLbindefs according to the convention of link::Classes/PLbindefParEnvironment#-put::, see there and examples below. The last arg can be an optional environment which determines where the corresponding PLbindefParEnvironment should be stored, by default this is the current Environment at instantiation time. + +INSTANCEMETHODS:: + +private::miSC_setRefEnvir +private::miSC_updateSourceEnvir +private::eventShortcuts + + +method::sourceEnvir + +Getter for PLbindefPar's PLbindefParEnvironment. + + +method::refEnvir + +Getter for the Environment where PLbindefPar's PLbindefParEnvironment is associated with PLbindefPar's key. + + +method::num + +Getter for PLbindefPar's number of parallel PLbindefs. + + +method::play + +Plays all corresponding PLbindefs with passed arguments, which might be arrays. In this case wrapped indexing is applied. + + +method::reset + +Resets all corresponding PLbindefs. + + +method::stop + +Stops all corresponding PLbindefs. + + + +method::clear + +Clears all corresponding PLbindefs. + + +method::remove + +Removes not only the PLbindefPar but also associated PLbindefs from global entry. + + +method::subPLbindefs + +Returns corresponding PLbindefs. + + +method::subEnvirs + +Returns corresponding PLbindefEnvironments. + + + +anchor::above:: +SECTION::Examples + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + +// synthdefs to play with + +( +SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\sin_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +EventShortcuts.on; +) + +:: + +anchor::Ex. 1:: +SUBSECTION::Ex.1) Setting key streams + +code:: +// start with a fresh global Dictionary + +Pdef.removeAll + +// use with EventShortcuts + +EventShortcuts.on + +( +PLbindefPar(\v, 3, + \i, [\saw_grain, \sin_grain, \noise_grain], + \d, 0.8, + \m, [65, 70, 80], + \rq, 0.002, + \a, 0.03, + \att, Pwhite(0.05, 0.01) +) +) + +// now we have a PLbindefEnvironment as player which can use indices + +~v.play + + +// set all streams to one val or pattern + +~v.d = PLseq([0.2, 0.1, 0.1]) + + +// set all streams to different vals or patterns + +~v.d = [0.1, 0.3, PLseq([0.2, 0.1])] + + +// set single streams + +~v[2].d = PLseq([0.2, 0.2, 0.1]) + +~v[2].m = Pwhite(90, 100) + + + +// set only some streams + +~v[[0, 1]].m = [67, 77] + +~v[[1, 2]].m = [Pwhite(75, 80.0), 92] + + + +// parallel intervals and chords for single streams + +~v[2].m = Pwhite(80, 90) + [0, 9] + +~v[0].m = Pwhite(50, 65) + [0, 5] + +~v[1].m = Pwhite(70, 75.5) + [0, 3, 7] + + +~v.m = [72, 76, 79] + +~v.m = Pwhite(0.0, 1) ! 3 + [72, 76, 79] + + +// this is different: +// all PLbindefs get the same pattern with chord +// WARNING: with large arrays this can become loud! + +~v.m = Pwhite(0.0, 2) + [72, 76, 79] + + +// reference to last sources + +~v.m = [72, 76, 79 + PLseq([1, 2])] + +~v.m = ~v.m + 1.5 + +~v.m = ~v.m + [0, -12, 12] + +~v[2].m = ~v[2].m - 5 + + +// subarray reference to last sources + +~v[[1, 2]].m = ~v[[1, 2]].m - 7 + + +// equivalent, here getter looks different as not from temporary envir: + +// ~v[[1, 2]].m = ~v.m[[1, 2]] - 7 + + + +// use adverbs to build chords on each voice + +~v.m = ~v.m +.t [0, 4, 7] + + +// pause single streams + +~v[1].stop + +~v[2].stop + + +// play all again + +~v.play + + +// can also be done this way + +PLbindefPar(\v).stop + +PLbindefPar(\v).play + + +
// stop, start and reset substreams + +~v[[0, 1]].stop + +~v[[0, 1]].play + + + +// set more than one key, all streams + +~v.(\d, 0.1, \m, [70, 75, 80 + Pwhite(0.0, 5)]) + + +// set more than one key, but only chosen streams + +~v.([0, 2], \d, [0.35, 0.5], \m, [90, 97]) + + +// this can still be done similar to Pbindef-style + +PLbindefPar(\v, [0, 2], \d, [0.3, 0.55], \m, [83, 86]) + + +// prototyping's underscore syntax, applied successively + +~v.d_([0.4, 0.5, 0.6]).m_([70, 81, 92]) + +~v[1].d_(0.15).m_(94) + +~v[[0, 2]].d_(0.25).m_([73, 77.5]) + + + +// set all streams, they might be time-shifted + +~v.d = 0.2 + + +// hard sync with reset + +~v.reset + +~v.stop + + +// with method 'play' arrays can be passed to clock, protoType, quants and doReset + +~v.play([1, 1.05, 1.1].collect(TempoClock(_))) + +~v.stop + + +// wrapped indexing applies, same clock for players 0 and 2 + +~v.play([1, 1.5].collect(TempoClock(_))) + + +// stop + cleanup + +~v.remove +:: + + + +anchor::Ex. 2:: +SUBSECTION::Ex.2) More parallel streams, from granular to additive + +Use SynthDefs from link::Classes/PLbindefPar#above#above::. + +code:: +// use EventShortcuts + +EventShortcuts.on + +( +PLbindefPar(\y, 12, + // wrapped indexing: collection is used for all 12 streams + \i, [\saw_grain, \sin_grain, \noise_grain], + \d, (1..12)/100, + \m, { rrand(60, 90) } ! 12, + \a, 0.02, + \rq, 0.002 +) +) + +~y.play + +// evaluate more than once + +~y.m = { rrand(60, 90) } ! 12 + + +// refer to current midinotes + +~y.m = ~y.m - 1 + + +~y.d = [1, 2, 4] / 8 + +~y.d = ~y.d / 4 + +~y.stop + +~y.remove + + + +// replacement introducing additive structures + +( +PLbindefPar(\add, 12, + \i, \sin_grain, + \d, 1, + \m, { Pn(Pseries(rrand(40, 60), Pwhite(1.0, 3), 20)) } ! 12, + \att, 5, + \rel, 5, + \a, 0.005 +).play +) + +~add.f = { PLseq((1..16)) * (100 + Pwhite(0.0, 3)) } ! 12 + +~add.stop + +~add.remove +:: + + +anchor::Ex. 3:: +SUBSECTION::Ex.3) PLbindefPar with VarGui + +Use SynthDefs from link::Classes/PLbindefPar#above#above::. + +code:: +// PLbindefPar's single streams can be prepared for VarGui +// start with gui and define pitch sequences with sliders + +( +EventShortcuts.on; + +p = PLbindefPar(\w, 3, + \i, [\saw_grain, \sin_grain, \noise_grain], + \d, [1, 2, 4] / 8, + \n, PLseq(\midi), + \m, 70 + Pkey(\n), + \a, 0.05, + \rq, 0.001 +); + +VarGui([\midi, [0, 12, \lin, 1, 0] ! 4] ! 3, stream: p.subPLbindefs, quant: 1).gui +) + + +~w.d = [4, 2, 1] / 8 + +~w.d = [1, 2, PLrand([2, 2, 1])] / 8 + + +// lines or chords per layer + +~w.m = [70, 75, 80].collect(Pkey(\n) + _) // ~w.m = [70, 75, 80].collect { |i| Pkey(\n) + i } + +~w.m = Pkey(\n) + [70, 75, 80] + + +// exchange instruments + +~w.i = ~w.i.reverse + + +// exchange some durations + +~w[[0, 1]].d = [0.5, 0.8] + + +// stop with gui + +// cleanup after stopping with gui + +~w.remove +:: + + + +anchor::Ex. 4:: +SUBSECTION::Ex.4) PLbindefPar with PbindFx + +See also link::Classes/PbindFx#Ex. 7c#Ex.7c: Replacement with Pbindef:: + +code:: +// boot server with extended resources + +( +s.options.numPrivateAudioBusChannels = 1024; +s.options.memSize = 8192 * 16; +s.reboot; + +// fx synths + +SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\sin_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\echo, { |out, in, maxEchoDelta = 0.2, echoDelta = 0.1, + decayTime = 1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = DelayL.ar( + CombL.ar(inSig, maxEchoDelta, echoDelta, decayTime, amp), + maxEchoDelta, + maxEchoDelta - echoDelta + ); + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + +SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000, + cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = RLPF.ar( + inSig, + LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi), + rq, + amp + ).softclip; + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + + +// prepare EventShortcuts for additional keys + +EventShortcuts.addOnBase(\default, \fxs, ( + dec: \decayTime, + cd: \cleanupDelay, + cf: \cutOffMoveFreq, + fxo: \fxOrder, + dta: \echoDelta +), true); + +EventShortcuts.makeCurrent(\fxs); + +EventShortcuts.on; +) + + +( +// source and fxs passed as PLbindefPar / PLbindefs + +PLbindefPar(\r, 12, + \i, [\noise_grain, \saw_grain], + \d, 0.25, + \m, PLseq([60, 60, 60, 62]), + \fxo, PLseq([0, 0, 1, 2]), + // echo introduces delay, so do delay if no echo + \lag, Pfunc { |e| e.fxo.asArray.includes(1).if { 0 }{ 0.2 } }, + \a, 0.03, + \att, 0.01, + \rel, 0.1, + + \cd, Pkey(\att) + Pkey(\rel) + 0.001 +); + +PLbindef(\echo, + \fx, \echo, + \dta, 0.06, + \a, 1, + \dec, Pwhite(0.3, 1.8), + \cd, Pkey(\dec) +); + +PLbindef(\wah, + \fx, \wah, + \cf, Pwhite(1, 3), + \a, 1, + \cd, 0.01 +); + + +// we rather take a Ppar of PbindFxs than a PbindFx with a Ppar source + +q = Ppar( + PLbindefPar(\r).subPLbindefs.collect { |plbindef| + PbindFx(plbindef, PLbindef(\echo), PLbindef(\wah)) + } +).play +) + +
// still single source streams can be controlled by the PLbindefPar interface +// differentiate rhythm + +~r.d = (1..12) / 8 + + + +// manipulate midinotes + +~r.m = (48, 50..70) + + +// echo param and fx order sequence + +~echo.dta = Pwhite(0.03, 0.08) + +~r.fxo = PLseq([0, 0, 1, 2, 1, [1, 2]]) + + + +// produce overtone series with echodelta + +~echo.dta = 1 / 24.midicps / PLseq((1..16)) + + +// this also works in chords ("fx expansion") +// source is processed twice per event + +~echo.dta = 1 / [24, 26.7].midicps / PLseq((1..32).mirror) + + +// cleanup + +q.stop + +Pdef.removeAll +:: + + +anchor::Ex. 5:: +SUBSECTION::Ex.5) PLbindefPar with array args + +code:: +// specific bracketing needed here in analogy to array args with Pbinds + +( +SynthDef(\noise_grain_chord, { |out = 0, freq = #[400, 500], att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\sin_grain_chord, { |out = 0, freq = #[400, 500], att = 0.005, rel = 0.1, amp = 0.1| + var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\saw_grain_chord, { |out = 0, freq = #[400, 500], att = 0.005, rel = 0.1, amp = 0.1| + var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +EventShortcuts.on +) + +( +PLbindefPar(\c, 3, + \i, [\saw_grain_chord, \noise_grain_chord, \sin_grain_chord], + \d, 0.5, + \rq, 0.02, + \m, [[70, 79]] ! 3, + // or: [[[70, 79]]] + + // this looks a bit weird, but it's necessary: + // with Pbind and array args we need double bracketing, + // here we are refering to an array of Pbind data + \a, 0.03, + \att, Pwhite(0.02, 0.01), + \rel, 0.2 +); + +~c.play +) + + +~c.m = [[[75, 78]]] + + +// set single streams + +~c[1].d = 0.3; + +~c[2].d = 0.4; + + + +~c[0].m = [[65, 68]] + +~c[2].m = [[87, 90]] + + +// set substreams + +~c[[0, 1]].m = [[[65, 70]] + Pseq([0, 1.5], inf), [[75, 80]]] + +( +~c.([1, 2], + \d, [0.6, 0.35], + \m, [ + [[75, 80]] + Pseq([0, 1], inf), + [[85, 90]] + Pseq([0, 1.5, 2.5], inf) + ] +) +) + +~c.remove +:: + + diff --git a/HelpSource/Classes/PLbindefParEnvironment.schelp b/HelpSource/Classes/PLbindefParEnvironment.schelp new file mode 100644 index 0000000..f09a964 --- /dev/null +++ b/HelpSource/Classes/PLbindefParEnvironment.schelp @@ -0,0 +1,98 @@ +CLASS::PLbindefParEnvironment +summary::Environment made by PLbindefPar to play and set its sources +categories:: Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Tutorials/PLx_suite, Classes/PLbindef, Classes/PLbindefPar, Classes/PLbindefEnvironment, Classes/EventShortcuts, Tutorials/PLx_and_live_coding_with_Strings + + +DESCRIPTION:: +Instances of this class are made as side effect of PLbindefPar creation, assigned to PLbindefPar's name in an Environment of choice (by default the current one) and used as player interface and to set PLbindef's sources in condensed syntax. They are not thought to be created explicitely though. +A PLbindefParEnvironment contains the references to its PLbindefs as Associations of indices to PLbindefEnvironments. See link::Classes/PLbindefPar:: for examples. + +note:: +For setting, getting, playing, stopping and resetting subsets of PLbindefs in prototyping syntax the class TempPLbindefParEnvironment is involved (see link::Classes/PLbindefPar#Ex. 1#Ex.1:: in PLbindefPar's help). The user doesn't need to care about this class, instances are generated implicitely and might occur in the post window. +:: + +CLASSMETHODS:: + +method::new + +Creates a new PLbindefParEnvironment object with arguments of IdentityDictionary. In contrast to the latter strong::know:: defaults to true, which allows setting sources of the PLbindefPar in object prototyping style. strong::name:: is used for the corresponding key of the PLbindefPar. strong::num:: is the number of parallel patterns in the PLbindefPar. strong::plbindefPar:: is the corresponding PLbindefPar, strong::plbindefs:: might be passed. Normally not be used explicitely. + + +INSTANCEMETHODS:: + +private::miSC_setNum +private::miSC_setName +private::miSC_setPLbindef +private::miSC_plbindefParInit + +method::put + +Associates strong::obj:: with Symbol strong::key:: and updates the stored PLbindefEnvironments depending on the class of strong::obj::. For a SequenceableCollection items are assigned in the PLbindefEnvironments of corresponding indices (method 'wrapAt' is used for handling cases of smaller collections). Other types of passed objects are assigned in all PLbindefEnvironments. + + +method::superPut + +Associates strong::obj:: with Symbol strong::key:: mimicing link::Classes/IdentityDictionary#-put::. + + +method::value + +Expects key/value pairs and applies strong::put::. Optionally the first arg can be an Integer or a collection of Integers, specifying the PLbindefs to be set. + + +method::num + +Getter for PLbindefPar's number of parallel PLbindefs. + + +method::name + +Getter for PLbindefPar's key. + + +method::plbindefPar + +Getter for the corrresponding PLbindefPar. + + +method::at + +For Integers it works as link::Classes/IdentityDictionary#-at::, for SequenceableCollections of Integers it enables setting and getting as well as playing, stopping, resetting and clearing of sources of corresponding PLbindefs by using a TempPLbindefParEnvironment (see link::Classes/PLbindefPar#Ex. 1#Ex.1:: in PLbindefPar's help). + + +method::play + +Plays all PLbindefs of the corresponding PLbindefPar with passed arguments. + + +method::reset + +Resets all PLbindefs of the corresponding PLbindefPar. + + +method::stop + +Stops all PLbindefs of the corresponding PLbindefPar. + + +method::clear + +Clears all PLbindefs of the corresponding PLbindefPar and delete references to sourceEnvir and refEnvir. + + +method::remove + +Removes the corresponding PLbindefPar and all related PLbindefs from the global entry, delete references to sourceEnvir and refEnvir. + + +method::subPLbindefs + +Returns corresponding PLbindefs. + + +method::subEnvirs + +Returns corresponding PLbindefEnvironments. + + diff --git a/HelpSource/Classes/PLbrown.schelp b/HelpSource/Classes/PLbrown.schelp new file mode 100644 index 0000000..22ce25b --- /dev/null +++ b/HelpSource/Classes/PLbrown.schelp @@ -0,0 +1,88 @@ +CLASS:: PLbrown +summary:: dynamic scope Pbrown variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pbrown, Classes/PLgbrown, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLbrown object. + +argument::lo +Symbol or Pbrown lo arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::hi +Symbol or Pbrown hi arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::step +Symbol or Pbrown step arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0.125. + +argument::length +Symbol or Pbrown length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +p = PLbrown(\lo, \hi, \step); + + +// prepare current Environment +// PLseq repeats arg defaults to inf + +( +~lo = 55; +~hi = 80; +~step = Pstutter(10, PLseq([0.05, 3.5])); +) + + +// run + +( +x = Pbind( + \midinote, Ptuple(p!2), + \dur, 0.1 +).play; +) + +// replace + +( +~lo = Pseq((50..90)); +~hi = Pseq((50..90) + 15); +) + +:: + diff --git a/HelpSource/Classes/PLcauchy.schelp b/HelpSource/Classes/PLcauchy.schelp new file mode 100644 index 0000000..9e00be6 --- /dev/null +++ b/HelpSource/Classes/PLcauchy.schelp @@ -0,0 +1,86 @@ +CLASS:: PLcauchy +summary:: dynamic scope Pcauchy variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pcauchy, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLcauchy object. + +argument::mean +Symbol or Pcauchy mean arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::spread +Symbol or Pcauchy spread arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Pcauchy length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments, +// Cauchy distribution is not bounded, so do clip ! + +( +p = Pbind( + \midinote, PLcauchy(\mean, \spread).clip(60, 90), + \dur, 0.1 +); +) + +// prepare current Environment + +( +~mean = 75; +~spread = 0.1; +) + + +// run + +x = p.play; + + +// move mean value and distribution +// PLseq defaults to repeats = inf + +( +~mean = PLseq((80, 79.7..70)); +~spread = PLseq([0, 0, 0, 1]); +) + +x.stop; + +:: + diff --git a/HelpSource/Classes/PLexprand.schelp b/HelpSource/Classes/PLexprand.schelp new file mode 100644 index 0000000..5e435a3 --- /dev/null +++ b/HelpSource/Classes/PLexprand.schelp @@ -0,0 +1,85 @@ +CLASS:: PLexprand +summary:: dynamic scope Pexprand variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pexprand, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLexprand object. + +argument::lo +Symbol or Pexprand lo arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0.0001. + +argument::hi +Symbol or Pexprand hi arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Pexprand length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments, +// intervals grow together with base tone + +( +q = PLexprand(\lo, \hi); + +p = Pbind( + \midinote, 60 + q + PLtuple([0, q]), + \dur, 0.1 +); +) + + +// prepare current Environment + +( +~lo = 0.1; +~hi = 10; +) + + +// run + +x = p.play; + + +// replace + +~hi = 20; + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLgauss.schelp b/HelpSource/Classes/PLgauss.schelp new file mode 100644 index 0000000..0257219 --- /dev/null +++ b/HelpSource/Classes/PLgauss.schelp @@ -0,0 +1,86 @@ +CLASS:: PLgauss +summary:: dynamic scope Pgauss variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pgauss, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLgauss object. + +argument::mean +Symbol or Pgauss mean arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::dev +Symbol or Pgauss dev arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Pgauss length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments, +// Gauss distribution is not bounded, so do clip ! + +( +p = Pbind( + \midinote, PLgauss(\mean, \dev).clip(60, 90), + \dur, 0.1 +); +) + +// prepare current Environment + +( +~mean = 75; +~dev = 0.3; +) + + +// run + +x = p.play; + + +// move mean value and distribution +// PLseq defaults to repeats = inf + +( +~mean = PLseq((80, 79.75..70.25)); +~dev = Pstutter(8, PLseq([4, 0])); +) + +x.stop; + +:: + diff --git a/HelpSource/Classes/PLgbrown.schelp b/HelpSource/Classes/PLgbrown.schelp new file mode 100644 index 0000000..ac3b346 --- /dev/null +++ b/HelpSource/Classes/PLgbrown.schelp @@ -0,0 +1,97 @@ +CLASS:: PLgbrown +summary:: dynamic scope Pgbrown variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pgbrown, Classes/PLbrown, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLgbrown object. + +argument::lo +Symbol or Pgbrown lo arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::hi +Symbol or Pgbrown hi arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::step +Symbol or Pgbrown step arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0.125. + +argument::length +Symbol or Pgbrown length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +( +p = Pbind( + \midinote, PLgbrown(\lo, \hi, \step) + [0, -7.4, -12.7], + \dur, 0.1, + \amp, 0.05 +); +) + +// prepare Environments + +( +e = (lo: 65, hi: 90, step: 0.01); +f = e.copy.put(\step, 0.05); +) + + +// run + +( +e.use { x = p.play(quant: 0.2) }; +f.use { y = p.play(quant: 0.2) }; +) + +// replace + +( +e.lo = 65; +f.lo = 65; +e.hi = Pwhite(65, 67); +f.hi = Pwhite(65, 95); +f.step = 0.3 +) + +y.stop; + +x.stop; + +:: + diff --git a/HelpSource/Classes/PLgeom.schelp b/HelpSource/Classes/PLgeom.schelp new file mode 100644 index 0000000..8661f7e --- /dev/null +++ b/HelpSource/Classes/PLgeom.schelp @@ -0,0 +1,85 @@ +CLASS:: PLgeom +summary:: dynamic scope Pgeom variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pgeom, Classes/PLseries, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLgeom object. + +argument::start +Symbol or Pgeom start arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to 0. + +argument::grow +Symbol or Pgeom grow arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Pgeom length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +( +p = Pbind( + \freq, Pn(PLgeom(\start, \grow, 50)) % 2000 + 400, + \dur, 0.1 +); +) + +// prepare current Environment + +( +~start = 100; +~grow = 1.1; +) + + +// from ascending to random + +x = p.play; + + +// replace + +~grow = 0.99; + +~grow = 1.4; + + +x.stop; + + +:: + diff --git a/HelpSource/Classes/PLhprand.schelp b/HelpSource/Classes/PLhprand.schelp new file mode 100644 index 0000000..b22a9d3 --- /dev/null +++ b/HelpSource/Classes/PLhprand.schelp @@ -0,0 +1,90 @@ +CLASS:: PLhprand +summary:: dynamic scope Phprand variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Phprand, Classes/PLlprand, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLhprand object. + +argument::lo +Symbol or Phprand lo arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::hi +Symbol or Phprand hi arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Phprand length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +q = PLhprand(\lo, \hi); + + +// prepare current Environment +// PLseq repeats arg defaults to inf + +( +~lo = 60; +~hi = PLseq((92..62)); +) + + + +// run + +( +y = Pbind( + \midinote, Ptuple(q!5), + \dur, 0.1, + \amp, 0.05 +).play; +) + + +// replace, converging bounds + +( +~lo = PLseq((40..70) ++ (70!5)); +~hi = 70.5; +) + +y.stop; + + +:: + +Compare this example with lo-weighted link::Classes/PLlprand:: diff --git a/HelpSource/Classes/PLlprand.schelp b/HelpSource/Classes/PLlprand.schelp new file mode 100644 index 0000000..f4a1d73 --- /dev/null +++ b/HelpSource/Classes/PLlprand.schelp @@ -0,0 +1,91 @@ +CLASS::PLlprand +summary::dynamic scope Plprand variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Plprand, Classes/PLhprand, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLlprand object. + +argument::lo +Symbol or Plprand lo arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::hi +Symbol or Plprand hi arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Plprand length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +p = PLlprand(\lo, \hi); + + +// prepare current Environment +// PLseq repeats arg defaults to inf + +( +~lo = 60; +~hi = PLseq((92..62)); +) + + +// run + +( +x = Pbind( + \midinote, Ptuple(p!5), + \dur, 0.1, + \amp, 0.05 +).play; +) + + +// replace, converging bounds + +( +~lo = PLseq((40..70) ++ (70!5)); +~hi = 70.5; +) + +x.stop; + +:: + +Compare this example with hi-weighted link::Classes/PLhprand:: + + + \ No newline at end of file diff --git a/HelpSource/Classes/PLmeanrand.schelp b/HelpSource/Classes/PLmeanrand.schelp new file mode 100644 index 0000000..b88ba92 --- /dev/null +++ b/HelpSource/Classes/PLmeanrand.schelp @@ -0,0 +1,87 @@ +CLASS:: PLmeanrand +summary:: dynamic scope Pmeanrand variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pmeanrand, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLmeanrand object. + +argument::lo +Symbol or Pmeanrand lo arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::hi +Symbol or Pmeanrand hi arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Pmeanrand length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +p = PLmeanrand(\lo, \hi); + + +// prepare current Environment +// PLseq repeats arg defaults to inf + +( +~lo = PLseq((60, 60.25..70)); +~hi = PLseq((70, 69.75..60)); +) + + +// run + +( +x = Pbind( + \midinote, Ptuple([p, 65]), + \amp, [0.1, 0.06], + \dur, 0.1 +).play; +) + + +// replace + +( +~lo = 60.5; +~hi = 61; +) + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLn.schelp b/HelpSource/Classes/PLn.schelp new file mode 100644 index 0000000..9b27ceb --- /dev/null +++ b/HelpSource/Classes/PLn.schelp @@ -0,0 +1,219 @@ +CLASS:: PLn +summary:: dynamic scope placeholder pattern whose streams will be finished before replacements +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds, Classes/PL + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. In contrast to link::Classes/PL:: a replacement doesn't take effect immediately, but with the next embedding. This behaviour might be preferred with syncing. +note:: +As sources are finished before next replacements are possible, infinite source streams, other than with other PLx patterns, inhibit further replacements at all. For the option of finishing substreams with PLx list patterns see their cutItems arg and link::#Ex.3::. +:: + +CLASSMETHODS:: + +method::new + +Creates a new PL object. + +argument::item +Symbol or other Object. +If a Symbol is passed, item can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + +anchor::Ex.1:: +subsection::Ex.1: Protection of periods + +code:: + +// compare PL and PLn, start with polling two items from each Stream +// Note that Pseq defaults to repeats = 1 and PL/PLn default to repeats = inf + +( +~a = Pseq([1, 2, 3]); +x = PLn(\a).asStream; +y = PL(\a).asStream; + +x.nextN(2); +y.nextN(2); +) + + +// replace proxy source + +~a = ~a * 100; + + +// Stream from PLn finishes before replacement + +x.nextN(2) + +// With PL substream is replaced immediately + +y.nextN(2) +:: + + +anchor::Ex.2:: +subsection::Ex.2: Protection of periods for syncing + + +code:: + +// playing two interval lines in parallel + +( +~a = Pseq([4, 2, 0]) + [0, -5]; +~b = Pseq([7, 5, 4]) + [0, 5]; + +p = Pbind( + \dur, Pseq([0.4, 0.2, 0.2], inf), + \note, PLn(\a) +); + +q = Pbindf(p, \note, PLn(\b)); + +x = Ppar([p, q]).play; +) + + + +// replace the lower event stream, new notes start after end of embedding Pseq + +~a = Pseq([3, 2, 0]) + [0, -5]; + + +// replace second line + +~b = Pseq([7, 5, 4]) + [5, 12]; + + +// source nil stops event stream after end of embedding Pseq + +~a = nil; +:: + + +anchor::Ex.3:: +subsection::Ex.3: Protection of periods and subperiods with event streams + +code:: + +// define two Pbinds of same length, play first + +( +x = Pbind( + \dur, Pseq([0.4, 0.2, 0.2]), + \note, Pseq([4, 2, 0]) +); + +y = Pbind( + \dur, 0.2, + \note, Pseq([7, 7, 5, 4]) +); + +~a = x; + +PLn(\a).play; +) + +// switch between them several times, periods are protected + +~a = y; + +~a = x; + + +// define a sequence of parts, we want to exchange parts later on + +( +~trill_1 = Pbind( + \dur, 0.4/3, + \note, Pseq([7, 9, 7]) +); + +~trill_2 = Pbind( + \dur, 0.4/5, + \note, Pseq([7, 8, 7, 8, 7]) +); + +~fin = Pbind( + \dur, 0.2, + \note, Pseq([5, 4]) +); + +~b = [~trill_1, ~fin]; +) + +// replace with a PLseq +// note that source must have repeats = 1, +// in order to protect substreams with PLseq and change the behaviour later on +// we pass to cutItmes a Function that, for now, returns false + +( +~cutItems = false; +~a = PLseq(\b, 1, cutItems: \cutItems); + +~b[0] = ~trill_2; +) + +// helper function to generate trill patterns + +~trill = { |lo, hi, dur, reps| Pbind(\dur, dur / reps, \note, Pser([lo, hi], reps)) }; + + +// replace with a long slow trill + +~b[0] = ~trill_3 = ~trill.(7, 9, 0.9, 5); + + + +// replace back while long trill, +// due to the current value ~cutItems = false it will be completed and +// replacement takes effect in next loop + +~b[0] = ~trill_2; + +// replace back + +~b[0] = ~trill_3; + + +// change replacing behaviour + +~cutItems = true; + + +// now replacing while long trill will take immediate effect + +~b[0] = ~trill_2; + + +// stop + +~a = nil + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLnaryFunc.schelp b/HelpSource/Classes/PLnaryFunc.schelp new file mode 100755 index 0000000..ac61674 --- /dev/null +++ b/HelpSource/Classes/PLnaryFunc.schelp @@ -0,0 +1,90 @@ +CLASS:: PLnaryFunc +summary:: dynamic scope PnaryFunc variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pnaryop, Classes/PLnaryop, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. For replacing operators dynamically take link::Classes/PLnaryFunc:: with the operator wrapped into a Function. + + +CLASSMETHODS:: + +method::new + +Creates a new PLnaryFunc object. + +argument::func +Symbol or func arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by a Pattern or Stream. + +argument::pat +Symbol or pattern arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. + +argument::arglist +They are starting with input for the Function's second arg, +as items from pat will be passed to its first. +If Symbols are passed, arglist args can be assigned to envir variables later on. +Can be dynamically replaced by Patterns or Streams. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +( +p = Pbind( + \midinote, PLnaryFunc(\f, \src, [\dev]), + \dur, 0.1 +); +) + + +// define Environment and play + +( +e = (f: { |x,y| x + y }, src: PLseq([61, 62]), dev: Pbrown(-5, 5, 0.2) ); + +e.use { x = p.play }; +) + + +// replace Function input Patterns + +e.dev = PLseq((0, 0.5..7)); + +e.src = PLseq([61,64,65]); + + +// replace Function + +e.f = { |x,y| x - y }; + +e.f = { |x,y| y * 4 + x }; + + +x.stop; + +:: + +See also link::Tutorials/PLx_suite#Ex. 5b#Ex.5b::. \ No newline at end of file diff --git a/HelpSource/Classes/PLnaryop.schelp b/HelpSource/Classes/PLnaryop.schelp new file mode 100755 index 0000000..685ee41 --- /dev/null +++ b/HelpSource/Classes/PLnaryop.schelp @@ -0,0 +1,82 @@ +CLASS:: PLnaryop +summary:: dynamic scope Pnaryop variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pnaryop, Classes/PLnaryFunc, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. For replacing operators dynamically take link::Classes/PLnaryFunc:: with the operator wrapped into a Function. + + +CLASSMETHODS:: + +method::new + +Creates a new PLnaryop object. + +argument::operator +Symbol or Pnaryop operator arg. +If a Symbol is passed, list can be assigned to an envir variable later on. + +argument::pat +Symbol or Pnaryop pattern arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. + +argument::arglist +Symbols or Pnaryop arglist arg. +If Symbols are passed, arglist args can be assigned to envir variables later on. +Can be dynamically replaced by Patterns or Streams. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +( +p = Pbind( + \midinote, PLnaryop('+', \src, [\dev]), + \dur, 0.1 +); +) + + +// define Environment and play + +( +e = (src: PLseq([61,62]), dev: Pbrown(-5, 5, 0.2) ); + +e.use { x = p.play }; +) + + +// replace + +e.dev = PLseq((0, 0.5..7)); + +e.dev = Pbrown(-5, 5, 0.2); + +e.src = PLseq([61,64,65]); + + +x.stop; +:: + +link::Tutorials/PLx_suite#Ex. 5a#Ex.5a::. \ No newline at end of file diff --git a/HelpSource/Classes/PLpoisson.schelp b/HelpSource/Classes/PLpoisson.schelp new file mode 100644 index 0000000..d56be5b --- /dev/null +++ b/HelpSource/Classes/PLpoisson.schelp @@ -0,0 +1,75 @@ +CLASS:: PLpoisson +summary:: dynamic scope Ppoisson variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Ppoisson, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLpoisson object. + +argument::mean +Symbol or Ppoisson mean arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Ppoisson length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments, +// Poisson distribution gives positive integer values + +( +p = Pbind( + \midinote, (PLpoisson(\mean) + 50).clip(60, 90), + \dur, 0.1 +); +) + + +// prepare current Environment + +~mean = 10; + + +// run + +x = p.play; + + +// changing mean value + +~mean = Pstutter(5, PLseq((0, 5..30))); + +x.stop; + +:: + diff --git a/HelpSource/Classes/PLrand.schelp b/HelpSource/Classes/PLrand.schelp new file mode 100644 index 0000000..b71e094 --- /dev/null +++ b/HelpSource/Classes/PLrand.schelp @@ -0,0 +1,139 @@ +CLASS:: PLrand +summary:: dynamic scope Prand variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Prand, Classes/PLxrand, Classes/PLwrand, Classes/PLshuf, Classes/PLshufn, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLrand object. + +argument::list +Symbol or Prand list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Prand repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// define Pattern and prepare Environments + +( +p = Pbind( + \midinote, PLrand(\a), + \dur, 0.2 +); + +e = (a: (67..72)); +f = (a: (77..82)); +) + +// start together or not, but sync anyway + +e.use { x = p.play(quant: 0.2) }; +f.use { y = p.play(quant: 0.2) }; + + +// replace array elements ... + +f.a[0] = 97; + +e.a[0] = 96; + + +// ... or arrays + +f.a = (72, 72.5..77); + +e.a = [60]; + +e.a = [Pseq([60, 47.5]) + [0, 5], [61, 65.5]]; + +x.stop; +y.stop; + + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLrand(\a); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(80, 85, 3), + \dur, 0.2 + ) +]; + +x = p.play; +) + + +// replace array element + +( +~a[0] = Pbind( + \midinote, Pwhite(70, 75, 3) + [0, 5], + \dur, 0.15 +); +) + + +// replace whole array + +( +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.05 + ), + Pbind( + \midinote, Pwhite(70, 90, 2), + \dur, 0.25 + ) +]; +) + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLseq.schelp b/HelpSource/Classes/PLseq.schelp new file mode 100755 index 0000000..f58816c --- /dev/null +++ b/HelpSource/Classes/PLseq.schelp @@ -0,0 +1,137 @@ +CLASS:: PLseq +summary:: dynamic scope Pseq variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pseq, Classes/PLser, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLseq object. + +argument::list +Symbol or Pseq list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Pseq repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::offset +Symbol or Pseq offset arg. Defaults to 0. +If a Symbol is passed, offset can be assigned to an envir variable later on. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// definition for future reference in arbitrary Environments + +( +p = Pbind( + \midinote, PLseq(\a), + \dur, 0.1 +); +) + + +// assign a value to variable ~a in current Environment, +// play there. +// PLseq defaults to repeats = inf, Pseq defaults to repeats = 1 + +( +~a = (67..70) ++ Pseq((99..70)); + +x = p.play; +) + + +// try replacing Pseq stream while chromatic line down, +// works immediately with default value cutItems = true + +~a[4] = 73; + + +// replace whole list + +~a = (60..65); + +x.stop; + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLseq(\a); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(80, 85, 3), + \dur, 0.2 + ) +]; + +x = p.play; +) + + +// replace array element + +( +~a[0] = Pbind( + \midinote, Pwhite(70, 75, 3) + [0, 5], + \dur, 0.15 +); +) + +// replace whole array + +( +~a = [ Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.05 + ), + Pbind( + \midinote, Pwhite(70, 90, 2), + \dur, 0.25 + ) +]; +) + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLser.schelp b/HelpSource/Classes/PLser.schelp new file mode 100644 index 0000000..62b220d --- /dev/null +++ b/HelpSource/Classes/PLser.schelp @@ -0,0 +1,115 @@ +CLASS:: PLser +summary:: dynamic scope Pser variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pser, Classes/PLseq, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLser object. + +argument::list +Symbol or Pser list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Pser repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::offset +Symbol or Pser offset arg. Defaults to 0. +If a Symbol is passed, offset can be assigned to an envir variable later on. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +// PLseq not used as proxy, only taken as it defaults to repeats = inf + +p = Pbind( + \midinote, PLseq([PLser(\a, \r), 60, 61]), + \dur, 0.2 +); + +// prepare current Environment + +~a = (67..70); +~r = 6; +) + +x = p.play; + + +// replace repeats + +~r = 3; + + +// replace array elements + +~a[0] = [77, 93.5]; + +~a[2] = Pseq((94..96)); + + +// replace whole array + +~a = [55, 52]; + +x.stop; + + + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLser(\a, 3); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.1 + ), + Pbind( + \midinote, Pwhite(80, 85, 3), + \dur, 0.1 + ) +]; + +x = p.play; +) + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLseries.schelp b/HelpSource/Classes/PLseries.schelp new file mode 100644 index 0000000..b33f1ea --- /dev/null +++ b/HelpSource/Classes/PLseries.schelp @@ -0,0 +1,92 @@ +CLASS:: PLseries +summary:: dynamic scope Pseries variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pseries, Classes/PLgeom, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLseries object. + +argument::start +Symbol or Pseries start arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to 0. + +argument::step +Symbol or Pseries step arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Pseries length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +( +p = Pbind( + \midinote, PLseries(0, \step) % 24 + 60, + \dur, 0.1 +); +) + +// prepare Environments + +( +e = (step: 1); +f = e.copy; +) + + +// run + + +e.use { x = p.play(quant: 0.1) }; + +f.use { y = p.play(quant: 0.1) }; + + +// replace + +f.step = 2; + +e.step = Pwhite(0.5, 1.5); + +f.step = Pwhite(1, -3); + + + +y.stop; + +x.stop; + +:: + diff --git a/HelpSource/Classes/PLshuf.schelp b/HelpSource/Classes/PLshuf.schelp new file mode 100644 index 0000000..21cd648 --- /dev/null +++ b/HelpSource/Classes/PLshuf.schelp @@ -0,0 +1,138 @@ +CLASS:: PLshuf +summary:: dynamic scope Pshuf variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pshuf, Classes/Pshufn, Classes/PLshufn, Classes/PLrand, Classes/PLxrand, Classes/PLwrand, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. A PLshuf stream keeps a permutation until it ends or there is a list replacement. See link::Classes/PLshufn:: for ongoing choice of new permutations. + + +CLASSMETHODS:: + +method::new + +Creates a new PLshuf object. + +argument::list +Symbol or Pshuf list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Pshuf repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +p = Pbind( + \midinote, PLshuf(\a), + \dur, 0.2 +); + +// prepare Environment + +~a = (67..72); +) + +x = p.play; + + +// replace array elements ... + +~a[0] = 63; + + +// ... or whole arrays +// evaluating more than once gives a newly chosen permutation + +~a = (60..65); + +x.stop; + + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLshuf(\a); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(80, 85, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(80, 85, 6), + \dur, 0.1 + ) +]; + +x = p.play; +) + + +// replace array element + +( +~a[2] = Pbind( + \midinote, Pwhite(70, 75, 3) + [0, 5], + \dur, 0.15 +); +) + + +// replace whole array + +( +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3) + [0, 5], + \dur, 0.15 + ), + Pbind( + \midinote, Pwhite(70, 80, 2) + [0, 4], + \dur, 0.25 + ), + Pbind( + \midinote, Pwhite(95, 100, 6) + [0, -9], + \dur, 0.05 + ) +]; +) + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLshufn.schelp b/HelpSource/Classes/PLshufn.schelp new file mode 100644 index 0000000..1a20176 --- /dev/null +++ b/HelpSource/Classes/PLshufn.schelp @@ -0,0 +1,145 @@ +CLASS:: PLshufn +summary:: dynamic scope Pshufn variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pshufn, Classes/Pshuf, Classes/PLshuf, Classes/PLrand, Classes/PLxrand, Classes/PLwrand, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. A PLshufn stream keeps on choosing permutations for the current list. See link::Classes/PLshuf:: for keeping one permutation. + + +CLASSMETHODS:: + +method::new + +Creates a new PLshufn object. + +argument::list +Symbol or Pshufn list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Pshufn repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +p = Pbind( + \midinote, PLshufn(\a), + \dur, 0.2 +); + +// prepare Environments + +e = (a: (67..72)); +f = (a: (77..82)); +) + +e.use { x = p.play(quant: 0.2) }; +f.use { y = p.play(quant: 0.2) }; + + +// replace array elements ... + +e.a[0] = 97; +f.a[0] = 98; + + +// ... or arrays + +( +e.a = [80, 81]; +f.a = (70..73); +) + +e.a[0] = Pwhite(60, 65, 1) + [0, -5, 29.5]; + +y.stop; +x.stop; + + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLshufn(\a); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(80, 85, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(80, 85, 6), + \dur, 0.1 + ) +]; + +x = p.play; +) + + +// replace array element + +( +~a[2] = Pbind( + \midinote, Pwhite(70, 75, 3) + [0, 5], + \dur, 0.15 +); +) + +// replace whole array + +( +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3) + [0, 5], + \dur, 0.15 + ), + Pbind( + \midinote, Pwhite(70, 80, 2) + [0, 4], + \dur, 0.25 + ), + Pbind( + \midinote, Pwhite(95, 100, 6) + [0, -9], + \dur, 0.05 + ) +]; +) + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLslide.schelp b/HelpSource/Classes/PLslide.schelp new file mode 100644 index 0000000..5249ccf --- /dev/null +++ b/HelpSource/Classes/PLslide.schelp @@ -0,0 +1,158 @@ +CLASS:: PLslide +summary:: dynamic scope Pslide variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pslide, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLslide object. + +argument::list +Symbol or Pslide list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Pslide repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::len +Symbol or Pslide len arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to 3. + +argument::step +Symbol or Pslide step arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::start +Symbol or Pslide start arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to 0. + +argument::wrapAtEnd +Symbol or Pslide wrapAtEnd arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to true. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// definition for future reference in arbitrary Environments + +p = PLslide(\a, inf, \len, \step, \start); + + +// prepare current Environment + +( +~len = 3; +~step = 1; +~start = 0; +~a = [60, 61, 62, 70, 71, 72, 80, 81, 82]; +) + + +// run + +( +x = Pbind( + \midinote, p, + \dur, 0.2 +).play; +) + + +// replace list elements + +~a[0] = 60.5; + +~a[8] = Pseq([92, 80.5], 1); + + + +// len and step can be replaced by Patterns +// PLseq defaults to repeats = inf + +~len = Pstutter(5, PLseq([1, 2, 3])); + +( +~len = 1; +~step = Pstutter(2, PLseq([0, 4])); +) + +x.stop; + + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLslide(\a, inf, \len, \step, \start); + +~len = 3; +~step = 1; +~start = 0; +~add = 0; + +~a = (60, 62..70).collect { |x| + Pbind( + \midinote, x + Pseq((0, 0.2..0.8)) + PL(\add), + \dur, 0.1 + ); +}; + +x = p.play; +) + + +// replace slide params an ~add + +~add = [4, 9]; + +~step = 2; + +~len = 4; + +~add = [4, 9, 13]; + + + +// replace array element + +( +~a[0] = Pbind( + \midinote, 80 + Pseq((0.8, 0.6..0)) + PL(\add), + \dur, 0.05 +); +) + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLswitch.schelp b/HelpSource/Classes/PLswitch.schelp new file mode 100644 index 0000000..dab83b4 --- /dev/null +++ b/HelpSource/Classes/PLswitch.schelp @@ -0,0 +1,162 @@ +CLASS:: PLswitch +summary:: dynamic scope Pswitch variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pswitch, Classes/PLswitch1, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLswitch object. + +argument::list +Symbol or Pswitch list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::which +Symbol or Pswitch which arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// definition for future reference in arbitrary Environments + +p = PLswitch(\a, \w); + + +// define array +// PLseq defaults to repeats = inf + + +( +~a = (70..75) ++ Pshuf((85..80), 2) ++ Pseq((90..94)); + +~w = PLseq((0..7)); + +x = Pbind(\midinote, p, \dur, 0.1).play; +) + +// update array element + +~a[2] = Pseq([86, 85], 2) + [0,3]; + + +// reverse index pattern + +~w = PLseq((7..0)); + + +// keep in mind that indices are wrapped, no surprise here ... + +~a = (70,72..84); + + +// ... but with shorter array indices are grouped in 5 + 3 + +~a = (70,72..78); + +x.stop; + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLswitch(\a, \w); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(70, 75, 3), + \dur, 0.15 + ), + Pbind( + \midinote, Pwhite(80, 85, 3), + \dur, 0.1 + ), + Pbind( + \midinote, Pwhite(90, 95, 6), + \dur, 0.05 + ) +]; + +~w = PLseq((0..3)); + +x = p.play; +) + + +// replace index sequence + +~w = PLseq([3,0]); + +~w = PLseq([1,2]); + + +// replace array element + +( +~a[2] = Pbind( + \midinote, Pwhite(70, 75, 3) + [0, 5], + \dur, 0.15 +); +) + + +// replace whole array + +( +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3) + [0, 5], + \dur, 0.15 + ), + Pbind( + \midinote, Pwhite(70, 80, 2) + [0, 4], + \dur, 0.25 + ), + Pbind( + \midinote, Pwhite(95, 100, 6) + [0, -9], + \dur, 0.05 + ) +]; +) + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLswitch1.schelp b/HelpSource/Classes/PLswitch1.schelp new file mode 100644 index 0000000..33ed873 --- /dev/null +++ b/HelpSource/Classes/PLswitch1.schelp @@ -0,0 +1,163 @@ +CLASS:: PLswitch1 +summary:: dynamic scope Pswitch1 variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pswitch1, Classes/PLswitch, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLswitch1 object. + +argument::list +Symbol or Pswitch1 list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::which +Symbol or Pswitch1 which arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// definition for future reference in arbitrary Environments + +p = PLswitch1(\a, \w); + + +// run Pbind in current Environment +// take PLseq as they already default to repeats = inf + +( +~a = [PLseq((85..80)), PLseq((65..70))]; +~w = PLseq([0, 1]); + +x = Pbind(\midinote, p, \dur, 0.1).play; +) + +// update array element + +~a[1] = PLseq([96, 95]); + + +// update array + +~a = [59, 85]; + + +// update index pattern, used to stop also + +~w = Pseq([0, 0, 1], 5); + + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLswitch1(\a, \w); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(70, 75), + \dur, 0.15 + ), + Pbind( + \midinote, Pwhite(80, 85), + \dur, 0.1 + ), + Pbind( + \midinote, Pwhite(90, 95), + \dur, 0.05 + ) +]; + +~w = PLseq((0..3)); + +x = p.play; +) + + +// replace index sequence + +~w = PLseq([3, 0]); + +~w = PLseq([1, 2]); + + +// replace array element + +( +~a[2] = Pbind( + \midinote, Pwhite(70, 75) + [0, 5], + \dur, 0.15 +); +) + + +// replace whole array + +( +~a = [ + Pbind( + \midinote, Pwhite(60, 65) + [0, 5], + \dur, 0.15 + ), + Pbind( + \midinote, Pwhite(70, 80) + [0, 4], + \dur, 0.25 + ), + Pbind( + \midinote, Pwhite(95, 100) + [0, -9], + \dur, 0.05 + ), + Pbind( + \midinote, Pwhite(95, 100) + [0, -14], + \dur, 0.02 + ) +]; +) + +~w = PLshuf((0..3)); + +~w = PLshufn((0..3)); + + +x.stop; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLtuple.schelp b/HelpSource/Classes/PLtuple.schelp new file mode 100644 index 0000000..aedb930 --- /dev/null +++ b/HelpSource/Classes/PLtuple.schelp @@ -0,0 +1,94 @@ +CLASS:: PLtuple +summary:: dynamic scope Ptuple variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Ptuple, Classes/PLser, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLtuple object. + +argument::list +Symbol or Ptuple list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Ptuple repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// definition for future reference in arbitrary Environments + +p = PLtuple(\a); + + +// prepare current Environment +// PLtuple defaults to repeats = inf, +// so inner Patterns are repeatedly embedded + +~a = [ Pshuf((60..65)), 70, Pshuf((75..80)) ]; + + +// run + +x = Pbind(\midinote, p, \dur, 0.2).trace.play; + + +// replace elements + +~a[0] = 72.5; + +~a[1] = 74.5; + +~a[2] = Prand([85, 86, 87]); + + +// replace array + +// Ptuple and PLtuple start with new embedding of ALL patterns +// if one ends, so here default repeats = inf of PLshuf has no effect: +// new permutation with every loop as Pshuf has repeats = 1 + +~a = [ Pshuf((60..65)), 70, PLshuf((75..80)) ]; + + +// both Patterns have repeats = inf, +// permutation is kept + +~a = [ PLshuf((60..65)), 70, PLshuf((75..80)) ]; + + +x.stop; +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLwalk.schelp b/HelpSource/Classes/PLwalk.schelp new file mode 100644 index 0000000..f4e213c --- /dev/null +++ b/HelpSource/Classes/PLwalk.schelp @@ -0,0 +1,186 @@ +CLASS:: PLwalk +summary:: dynamic scope Pwalk variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pwalk, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLwalk object. + +argument::list +Symbol or Pwalk list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::step +Symbol or Pwalk stepPattern arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to Prand([1, -1], inf). + +argument::direction +Symbol or Pwalk directionPattern arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::start +Symbol or Pwalk startPos arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// definition for future reference in arbitrary Environments + +p = PLwalk(\a, \step, \dir, \start); + + +// prepare current Environment + +( +~a = (60, 64..102); +~step = 1; +~start = 70; +~dir = -1; +) + + +// run + +( +x = Pbind( + \midinote, p + [0, 3], + \dur, 0.1, + \legato, 1.2 +).play; +) + + +// replace with Patterns +// PLx ListPatterns default to repeats = inf + +~step = PLrand([1, 2]); + +~dir = PLseq([-1, 1]); + + +// replace list + +~a = ~a - 1.5; + + +x.stop; + + + + +////////////////////// + + +// placeholder may also get lists of event patterns + + +( +p = PLwalk(\a, \step, \dir); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 1) + [0, -8.7], + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(70, 75, 2) + [0, -3.3], + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(80, 90, 3) + [0, -4.3], + \dur, 0.1 + ), + Pbind( + \midinote, Pwhite(90, 100, 2) + [0, 5.7], + \dur, 0.05 + ) +]; + +~step = 1; + +~dir = 1; + +x = p.play; +) + + +// replace step pattern, pending between two source patterns +// try evaluating several times + +~step = PLseq([1, -1]); + +~step = 1; + + +// replace array element + +( +~step = 1; + +~a[0] = Pbind( + \midinote, Pwhite(60,75,1) + [0, 5, 14, 19, 22], + \dur, 0.15 +); + +~a[3] = Pbind( + \midinote, Pseq((90..95)), + \dur, 0.05 +); +) + +// replace whole array + +( +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.05 + ), + Pbind( + \midinote, Pwhite(70, 90, 2), + \dur, 0.25 + ), + Pbind( + \midinote, Pwhite(70, 75, 3), + \dur, 0.05 + ), + Pbind( + \midinote, Pwhite(80, 95, 3) + [0, -5.3], + \dur, 0.3 + ) +]; +) + +x.stop; +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLwhite.schelp b/HelpSource/Classes/PLwhite.schelp new file mode 100644 index 0000000..2b2b9c1 --- /dev/null +++ b/HelpSource/Classes/PLwhite.schelp @@ -0,0 +1,84 @@ +CLASS:: PLwhite +summary:: dynamic scope Pwhite variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pwhite, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLwhite object. + +argument::lo +Symbol or Pwhite lo arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 0. + +argument::hi +Symbol or Pwhite hi arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. Defaults to 1. + +argument::length +Symbol or Pwhite length arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +Defaults to inf. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// definition for future reference in arbitrary Environments + +p = PLwhite(\lo, \hi); + + +// prepare current Environment +// PLseq repeats defaults to inf + +( +~lo = PLseq((60..70)); +~hi = PLseq((62..72)); +) + + +// run + +( +x = Pbind( + \midinote, p, + \dur, 0.1 +).play; +) + + +// replace, converging bounds, stops when ~lo ends (Pseq default repeats = 1) + +( +~lo = Pseq((60..80) ++ (80!5)); +~hi = 80; +) + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLwrand.schelp b/HelpSource/Classes/PLwrand.schelp new file mode 100644 index 0000000..43a0952 --- /dev/null +++ b/HelpSource/Classes/PLwrand.schelp @@ -0,0 +1,103 @@ +CLASS:: PLwrand +summary:: dynamic scope Pwrand variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pwrand, Classes/PLrand, Classes/PLxrand, Classes/PLshuf, Classes/PLshufn, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLwrand object. + +argument::list +Symbol or Pwrand list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::weights +Symbol or Pwrand weights arg. +If a Symbol is passed, weights can be assigned to an envir variable later on. +Can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Pwrand repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +p = Pbind( + \freq, 50 * PLwrand(\a, \w), + \dur, 0.01, + \amp, 0.02 +); + +// prepare (current) Environment +// give low overtones more weight + +~a = (1..8); +~w = (8..1).cubed.normalizeSum; +) + +x = p.play; + + +// reverse overtone weights + +~w = (1..8).cubed.normalizeSum; + + +// replace with Pattern for weights, +// PLseq taken as repeats defaults to inf + +~w = Pstutter(50, PLseq([(8..1), (1..8)].collect { |x| x.cubed.normalizeSum })); + + +// replace arrays (must be sufficiently long) + +~a = (3..10); +~a = (5..12); + +~a = (1,3..15); +~a = (1,4..22); +~a = (1,5..29); +~a = (1,6..36); +~a = (2,7..37); +~a = (3,8..38); +~a = (4,9..39); +~a = (5,10..40); + +x.stop; + + + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PLxrand.schelp b/HelpSource/Classes/PLxrand.schelp new file mode 100644 index 0000000..7b573a7 --- /dev/null +++ b/HelpSource/Classes/PLxrand.schelp @@ -0,0 +1,150 @@ +CLASS:: PLxrand +summary:: dynamic scope Pxrand variant +categories::Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events>PLx suite +related:: Overviews/miSCellaneous, Classes/Pxrand, Classes/PLrand, Classes/PLwrand, Classes/PLshuf, Classes/PLshufn, Tutorials/PLx_suite, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds + + +DESCRIPTION:: + + +Takes Symbol args for later reference by the Streams, which will read from variables in the Environments of their instantiation. See link::Tutorials/PLx_suite::. + + +CLASSMETHODS:: + +method::new + +Creates a new PLxrand object. + +argument::list +Symbol or Pxrand list arg. +If a Symbol is passed, list can be assigned to an envir variable later on. +This lists's elements can be dynamically replaced by Patterns or Streams. + +argument::repeats +Symbol or Prand repeats arg. If a Symbol is passed, repeats can be assigned to an envir variable later on. Defaults to inf. + +argument::cutItems +Symbol or Boolean or Integer (0 or 1) or a Function returning Boolean or Integer. +If a Symbol is passed, cutItems can be assigned to an envir variable later on. +Determines if list items, which are Patterns or Streams themselves, +will be finished if a replacement occurs during their embedding, or if they will be replaced immediately. +The latter is the default behaviour (default value true). +For protecting whole lists from immediate replacements see link::Classes/PLn::. + +argument::envir +Dictionary or one of the Symbols +\top, \t (topEnvironment), \current, \c (currentEnvironment). +Dictionary to be taken for variable reference. Defaults to \current. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// define Pattern and prepare Environments + +( +p = Pbind( + \midinote, PLxrand(\a), + \dur, 0.2 +); + +e = (a: (67..72)); +f = (a: (73..78)); +) + +// start together or not, but sync anyway + +e.use { x = p.play(quant: 0.2) }; +f.use { y = p.play(quant: 0.2) }; + + +// replace array elements ... + +f.a[0] = Pseq((0, 0.5..3) + 75); + +e.a[0] = Pseq(60 - (0, 0.5..3)); + + +// ... or arrays + +f.a = (72, 72.5..77); + +e.a = [60]; + +e.a = [Pseq([60, 47.5]) + [0, 5], [61, 65.5], [80, 83.5]]; + +x.stop; +y.stop; + + + +////////////////////// + + +// placeholder may also get lists of event patterns + +( +p = PLxrand(\a); + +~a = [ + Pbind( + \midinote, Pwhite(60, 65, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(70, 75, 3), + \dur, 0.2 + ), + Pbind( + \midinote, Pwhite(80, 85, 3), + \dur, 0.2 + ) +]; + +x = p.play; +) + + +// replace array element + +( +~a[0] = Pbind( + \midinote, Pwhite(70, 75, 3) + [0, 5], + \dur, 0.15 +); +) + + +// replace whole array + +( +~a = [ + Pbind( + \midinote, Pxrand((60..65), 3), + \dur, 0.15 + ), + Pbind( + \midinote, Pxrand((85..95), 3), + \dur, 0.05 + ), + Pbind( + \midinote, Pxrand((70..80), 2), + \dur, 0.25 + ) +]; +) + +x.stop; + +:: + + + \ No newline at end of file diff --git a/HelpSource/Classes/PS.schelp b/HelpSource/Classes/PS.schelp new file mode 100644 index 0000000..1ca3170 --- /dev/null +++ b/HelpSource/Classes/PS.schelp @@ -0,0 +1,251 @@ +CLASS:: PS +summary:: Pattern that behaves like a Stream +categories::Libraries>miSCellaneous>PSx stream patterns, Streams-Patterns-Events>PSx stream patterns +related:: Overviews/miSCellaneous, Tutorials/PSx_stream_patterns, Classes/MemoRoutine, Classes/PSdup, Classes/PSrecur, Classes/PSloop + + +DESCRIPTION:: + + +In general Patterns are stateless. But e.g. for counted embedding in other Patterns the exception of stream-like behaviour is practical. PS might also be used in cases where Streams must not be passed to certain Patterns. + +note:: +Name and implementation of former Pstream has changed with miSCellaneous_v0.9, in compliance with other PSx patterns it's been renamed to PS / PStream, however for backwards compatibility Pstream will still work by subclassing. + +:: + + +CLASSMETHODS:: + +method::new + +Creates a new PS object. + +argument::srcPat +Source pattern, might also be event pattern. + +argument::length +Number of output items, may be pattern or stream, defaults to inf. + +argument::bufSize +Size of buffer to store last values, defaults to 1. + +argument::copyItems +Determines if and how to copy items, which are which are either non-Sets or member of Sets. +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). +Other values are interpreted as 0. Defaults to 0. + +0: original item + +1: copy item + +2: deepCopy item + +argument::copySets +Determines if to copy Sets (and hence Events). +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). +Other values are interpreted as 0. Defaults to 1. + +0: original Set + +1: copy Set + +note:: +The distinction of copying items and sets makes sense in the case of event streams. Per default Events are copied (strong::copySets:: == 1), not their values (strong::copyItems:: == 0). By playing Events those are used to store additional data (synth ids, msgFuncs …) which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use MemoRoutine - copied Events will not contain this additional data. If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured then copying makes no sense, this is the normal case, so copyItems defaults to 0. When going to alter the ouput, you might want to set strong::copyItems:: to 1 for a PSx returning simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set strong::copySets:: to 1 and strong::copyItems:: to 2 (an option strong::copySets:: == 2 doesn't exist as it would be contradictory in combination with strong::copyItems:: < 2). +:: + + +note:: +Copy options concern copying into PS's buffer as well as the output of a Stream derived from the PS. When such a Stream is outputting copies this prevents unintended altering of items from strong::srcPat::. On the other hand storing copies in PS's buffer prevents these from being altered unintendedly. +:: + + +INSTANCEMETHODS:: + +method::lastValue +Last value stored in strong::memoRoutine:: + +method::lastValues +Array of last values stored in strong::memoRoutine::, latest value is first in array. + +method::at +Returns ith item of array of last values stored in strong::memoRoutine:: (keep in mind reversed order: last value first) + +method::bufSize +Size of array of last values. + +method::srcPat +Instance variable getter and setter methods. + +method::length +Instance variable getter and setter methods. + +method::lengthStream +Instance variable getter and setter methods. + +method::memoRoutine +Instance variable getter and setter methods. + +method::count +Instance variable getter and setter methods. +Counts each call of next / value / resume / run on the strong::memoRoutine::. +If several Streams are derived from one PS each call of next on a derived Stream +will be counted by the strong::memoRoutine:: and thus by the PS. + +method::bufSeq +Returns items of the array strong::lastValues::, but in the order in which they appeared, i.e. +latest value is first in array. + +argument::dropNils +Boolean. If strong::dropNils:: is true (default), nils will be rejected from the array. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// PS used to store a sequence of 6 events + +( +p = Pbind( + \midinote, Pwhite(60, 90, 6), + \dur, Prand([0.2, 0.4], inf) +); + +// As the sequence ends with nil, nil is also stored in the buffer of PStream's MemoRoutine. +// Hence to store the whole sequence we must increase the buffer size by 1. + +q = PS(p, bufSize: 7); + +q.play; +) + + +// values are shifted, so latest are first + +q.lastValues + + +// method bufSeq gives items in order in which they appeared, dropping nils by default + +q.bufSeq + + +// repeat original sequence + +Pseq(q.bufSeq).play + + +// play in reverse order, as Stream has been finished there's also a nil to be dropped + +Pseq(q.lastValues.drop(1)).play + + +// counted embedding of value patterns +// PLx variants default to repeats = inf + +( +p = PLseq([ + PS(PLseq((60..65)), 3), + PS(PLseq((80..90)), Pwhite(2,5)) +]); + +x = Pbind( + \midinote, p, + \dur, 0.1 +).play; +) + +x.stop; + + +// counted embedding of event patterns + +( +p = Pbind( + \midinote, PLseq((55..70)) + Pfunc { [0, [4,5].choose] }, + \dur, 0.2 +); + +q = Pbind( + \midinote, PLseq((80..100)), + \dur, 0.05 +); + +x = PLseq([ + PS(p, Pwhite(2,6)), + PS(q, Pwhite(2,6)) +]).play; +) + +x.stop; + +:: + +note:: +Repeated streamifying of a PS is just like resuming a Stream (yes, PS behaves like ... a Stream). For getting a Stream to start at the beginning as defined by the Pattern enclosed by the PS, you'd have to generate a new PS, e.g. by reevaluating its definition or wrapping it into a Function. +:: + + +code:: + +p = PS(Pseries(), 5); + +// evaluate more than once + +p.asStream.all; + + + +// compare + +q = { PS(Pseries(), 5) }; + +// evaluate more than once + +q.value.asStream.all; + + + +// For recursively generating data see PSrecur. +// Referring to buffered last values of a PS can +// easily be done with method .at. + + +// canonical brown movement +// define 3 voices refering to a PS +// use separate PS to collect data +// plot + +( +p = PS(Pbrown(65, 90, 2.1), inf, 16); +p.iter.nextN(16); + +q = Pfunc { p[5] - 7 }; +r = Pfunc { p[10] - 14 }; +t = Pfunc { p[15] - 21 }; + +u = PS(Ptuple([p,q,r,t]), bufSize: 100); + +a = Plotter().superpose_(true).plotMode_(\plines); +a.value = u.iter.nextN(100).flop +) + +// playback stored pitches + +( +v = Pbind( + \midinote, Pseq(u.bufSeq), + \dur, 0.2 +).trace.play +) + +:: + diff --git a/HelpSource/Classes/PSPdiv.ext.schelp b/HelpSource/Classes/PSPdiv.ext.schelp new file mode 100644 index 0000000..fdd6485 --- /dev/null +++ b/HelpSource/Classes/PSPdiv.ext.schelp @@ -0,0 +1,4 @@ + +INSTANCEMETHODS:: + +private:: miSC_calcDivs diff --git a/HelpSource/Classes/PSPdiv.schelp b/HelpSource/Classes/PSPdiv.schelp new file mode 100755 index 0000000..fee2448 --- /dev/null +++ b/HelpSource/Classes/PSPdiv.schelp @@ -0,0 +1,524 @@ +CLASS:: PSPdiv +summary:: dynamic multi-layer pulse divider +categories:: Libraries>miSCellaneous>Other patterns +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Pspawner, Tutorials/Sieves_and_Psieve_patterns, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation + + +DESCRIPTION:: + + +PSPdiv controls the timing of one or several layers of event patterns by a single pulse pattern. In every layer single pulse durations or integer multiples ('division bases') of pulse durations can be divided by Integers or proportionally. For every layer the event pattern data can be given as event pattern or a function, which is generating an event pattern for every divisional operation. Division bases and divisions as well as the pulse itself can be controlled by patterns. + +PSPdiv is built on Pspawner and therefore allows sequential or parallel spawning: the type of embedding can be sequenced by a pattern for every divisional operation. So a single layer alone can produce a number of overlapping sequences. + +Time division in space notation scheme with two layers and embedding of sequential type: + +image::attachments/PSPdiv/PSPdiv_graph_1.png:: + +A sequence of regular or irregular strong::pulses:: is given as pattern of floats, these durations are marked as proportional spaces in the graphic. The sequence of strong::divBases:: collects groups of strong::pulses:: and divides them according to the sequence of strong::divs::. The resulting durations of each layer are again marked by proportional spacing, common entry points of layers are marked by emphasized vertical lines. + +Special thanks to Ron Kuivila for developing Pspawner, it's such a versatile class ! + + + +CLASSMETHODS:: + + +private::shiftDelta + + + +method::new + +Creates a new PSPdiv object. + + +argument::pulse +Duration or pattern of durations, given as beats. Defaults to 1. + +argument::evPat +An event pattern, a Function generating event patterns or: a SequenceableCollection thereof. If an event pattern is given, the durations of the current division, calculated from current values of strong::pulse::, strong::div:: and strong::divBase::, are inserted with the 'dur' key. If a Function is given, it will be passed 4 arguments: + +list:: +## an array of durations of the current division, based on the following arguments +## the current division number strong::div:: +## the current division base number strong::divBase:: +## the current division type strong::divType:: ('seq' or 'par') +:: + +The Function should return the event pattern to be scheduled for this division. The typical application would be using the array of durations in a Pseq with repeats = 1 as 'dur' value, though you are free to let it return any kind of event pattern. Layers are slightly shifted, so that the event from a pattern of lower index is calculated before an event from a pattern of higher index, if events are scheduled to happen at the same time. + +argument::div +Integer or array of array of numbers, determining a division of strong::divBase:: * strong::pulse:: beats or a pattern returning such or: a SequenceableCollection thereof. Defaults to 1. A single array of numbers is interpreted as indicator for several layers (see note below), hence the doubled array is necessary for a proportional division. + +argument::divBase +Integer determining the number of multiples of strong::pulse:: to be used as base of division or a pattern returning such or: a SequenceableCollection thereof. Defaults to 1. + + +argument::divType +Symbol 'seq' or 'par' or or a pattern returning such or: a SequenceableCollection thereof. Defaults to 'seq'. Determines the type of embedding according to Pspawner's convention. + +note:: +If one of the arguments strong::evPat::, strong::div::, strong::divBase:: and strong::divType:: is passed as SequenceableCollection of size > 1, a multitude of layers is assumed and the other args are interpreted accordingly. To avoid ambiguities only one size > 1 is allowed at maximum amongst these args (but more than one of them can be a SequenceableCollection of this size). Consequently a proportional strong::div:: arg for one layer must be passed in double brackets. +:: + + +INSTANCEMETHODS:: + +private::div, divBase, divType, evPat, pspDivInit, pulse + + + +section::Examples + + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + + +anchor::Ex.1:: +subsection::Ex. 1: Basic functionality with one layer + +code:: + +// test SynthDef + +( +SynthDef(\varKlank, { |freq = 500, att = 0.01, rel = 0.1, sourceType = 0, + ringtime = 1, pan = 0, amp = 0.1| + var sig, source = Select.ar(sourceType, [ + BrownNoise.ar(0.05), + Impulse.ar(0) + ]); + sig = DynKlank.ar(`[freq * (1..8), amp * (8..1)/8, ringtime * (1!8)], source); + sig = Splay.ar(sig); + OffsetOut.ar(0, Balance2.ar(sig[0], sig[1], pan) * + EnvGen.ar(Env.perc(att, rel), doneAction: 2)) +}).add; +) + + +( +// base pattern +p = Pbind( + \instrument, \varKlank, + \midinote, Pwhite(65, 90), + \amp, 0.1, + \att, Pwhite(0.005, 0.02), + \rel, 2, + \sourceType, 1, + \ringtime, 2, + \pan, Pwhite(-0.3, 0.3) +); +) + + + +// div = 1, pulse not divided + +x = PSPdiv(0.8, p, 1).play; + +x.stop + + +// div as array of two items leads to expansion into two layers + +x = PSPdiv(0.8, p, [2, 1]).play; + +x.stop + + +// here div is interpreted as proportion for one layer + +x = PSPdiv(0.8, p, [[3, 1]] ).play; + +x.stop + + +// specifying per layer + +x = PSPdiv(0.8, [p, Pbindf(p, \rel, 0.1)], [1, [3, 2, 1]] ).play; + +x.stop + + + +// use with PL proxies + +( +// defaults, want to replace later on +~pulse = 0.8; +~div = 1; +~divBase = 1; +~divType = \seq; + +q = PSPdiv(PL(\pulse), p, PL(\div), PL(\divBase), PL(\divType)); +) + + + +// start playing + +x = q.play + + +// replace on the fly: +// alternating tuplets + +~div = PLseq([2, 3]); + + +// "dotted" notes, now we don't need double brackets as above as it's the source of the PL, +// not the PSPdiv arg + +~div = [3, 1]; + + +// acceleration and deceleration by pulse pattern control + +~pulse = Pseg(PLseq([0.8, 0.3]), 10) + + +( +// use divBase for base length control of tuplets +// here this could be written with div sequencing only also + +~pulse = 1; + +~div = PLseq([2, 6]); + +~divBase = PLseq([1, 1, 2]); +) + + +x.stop +:: + + + +anchor::Ex.2:: +subsection::Ex. 2: Parallel embedding with one layer + +SynthDef from link::#Ex.1:: + +code:: +( +// default values for proxies + +~pulse = 0.5; +~div = PLseq([6, 4]); +~divBase = 2; +~divType = \par; + + +// evPat argument now given as Function +// this especially makes sense in combination with parallel embedding +// otherwise parallel patterns would poll event data from a single event stream + + +p = { |durs| + // use durs calculated from pulse, div and divBase + var size = durs.size; + Pbind( + \dur, Pseq(durs), + \instrument, \varKlank, + \midinote, Pseq(~baseStream.next + rrand(0, 7) + (0..size)), + \amp, 0.1, + \att, Pwhite(0.005, 0.02), + \rel, ~releaseStream.next, + \ringtime, 2, + \sourceType, 1, + \pan, ~panStream.next + ) +}; + +// these streams deliver items for each embedding + +~baseStream = PLseq([60, 70, 80]).iter; +~releaseStream = Pn(Pshuf([2, 0.6, 0.2])).iter; +~panStream = Pwhite(-0.8, 0.8).iter; + + +// we still have only one layer, but it will overlap + +q = PSPdiv(PL(\pulse), p, PL(\div), PL(\divBase), PL(\divType)); +) + + +// start + +x = q.play + + +// change to sequential embedding + +~divType = \seq; + + +// type of embedding can be sequenced too + +~divType = PLrand([\seq, \par, \par]); + + +// default divBase was 2, allow shorter division bases + +~divBase = PLrand([1, 2]); + + +x.stop +:: + + + + +anchor::Ex.3:: +subsection::Ex. 3: Ornamenting a line with a second layer + +SynthDef from link::#Ex.1:: + +code:: +( +p = Pbind( + \instrument, \varKlank, + // basic melodic line, data stored in variable for use by ornamentation + \midinote, (Pn(Pshuf([72, 74, 76, 79, 81])).collect { |x| ~m = x; }) + + // random broadening of line + PLrand([[-14, 0], [0, 14]]), + \amp, 0.15, + \att, Pwhite(0.01, 0.02), + \rel, 3, + \sourceType, 1, + \ringtime, 3, + \pan, Pwhite(-0.3, 0.3) +); + +~releaseStream = Pn(Pshuf([2, 0.5, 0.2])).iter; +~panStream = Pwhite(-0.8, 0.8).iter; +~dirStream = PLseq([1, -1]).iter; + +// defines trill pattern for every event from melodic line + +q = { |durs| + // use durs calculated from pulse, div and divBase + var size = durs.size, midibase = ~m; + Pbind( + \dur, Pseq(durs), + \instrument, \varKlank, + // define trill alternating above and below basic melodic line + \midinote, Pn(rrand(4, 9) * ~dirStream.next, size) + PLseq([0, 1]) + midibase, + \amp, 0.1, + \att, Pwhite(0.005, 0.02), + \rel, ~releaseStream.next, + \ringtime, 2, + \sourceType, 1, + \pan, ~panStream.next + ) +}; + +// default values for PL proxies, want to replace later on + +~pulse = PLshufn([1, 1, 2]/2); +~div = PLrand([4, 6, 8]); +~divBase = 1; +~divType = \seq; + +r = PSPdiv( + PL(\pulse), + [p, q], + [1, PL(\div)], + [1, PL(\divBase)], + [1, PL(\divType)] +); +) + + +// start with sequential embedding: trill on every note, variuos divisions + +x = r.play + + +// change to parallel embedding of trills, overlapping + +( +~divBase = 2; +~div = PLrand([8, 12, 16]); +~divType = \par; +) + +x.stop +:: + + + + + + +anchor::Ex.4:: +subsection::Ex. 4: Polyrhythmics of several layers + +SynthDef from link::#Ex.1:: + +code:: +// three layers using varying pentatonic scales shifted by sixth tones + +( +// staccato layer +p = Pbind( + \instrument, \varKlank, + \midinote, PLshufn([0, 2, 4, 7, 9]) + Pn(Pstutter(15, Pwhite(80, 100, 1))) + + 0.6 + [0, -14], + // use some rests + \amp, 0.06, + \att, Pwhite(0.01, 0.02), + \rel, 0.15, + \sourceType, 1, + \ringtime, 3, + \pan, Pwhite(-0.3, 0.3) +); + +// long release layer +q = Pbind( + \instrument, \varKlank, + \midinote, PLshufn([0, 2, 4, 7, 9]) + Pn(Pstutter(20, Pwhite(60, 80, 1))) + + PLrand([[-14, 0]]) + PLrand([0, 12]), + \amp, 0.08, + \att, Pwhite(0.01, 0.02), + \rel, 2, + \sourceType, 1, + \ringtime, 2, + \pan, Pwhite(-0.3, 0.3) +); + +// "drum" layer +r = Pbind( + \instrument, \varKlank, + \midinote, PLshufn([0, 2, 4, 7, 9]) + Pn(Pstutter(25, Pwhite(35, 50, 1))) + + 0.3 + [-12, 0], + \amp, 0.02, + \att, Pwhite(0.01, 0.02), + \rel, 0.35, + \sourceType, 0, + \ringtime, 1, + \pan, Pwhite(-0.3, 0.3) +); + +// default values for PL proxies, want to replace later on + +~pulse = PLshufn([1, 1, 2] * 5/9); + +~div0 = PLshufn([4, 8]); +~div1 = PLshufn([2, 3, 4]); +~div2 = PLshufn([2, 4]); +~divBase = 1; +~divType = \seq; + +u = PSPdiv( + PL(\pulse), + [p, q, r], + [PL(\div0), PL(\div1), PL(\div2)], + // will be exapanded to array of 3 PLs with same symbol reference + PL(\divBase), + PL(\divType) +); +) + + +// start + +x = u.play + + +// vary pulse + +~pulse = PLshufn([1, 1.5, 2.5, 3] * 5/9); + +~pulse = 1; + +~div0 = PLshufn([4, 6, 8]); + +~div2 = PLshufn([2, 4, 6]); + +~pulse = PLshufn([1, 1, 9/8]); + +x.stop; + +:: + + + + +anchor::Ex.5:: +subsection::Ex. 5: Granulation, control of many layers + +SynthDef from link::#Ex.1:: + +code:: + +( +~amp = 0.05; +~att = 0.005; +~rel = 0.05; +~sourceType = 0; +~ringtime = 1; + +~pan = PLseq([0.8, -0.8]); + +// Pbind generator, patterns will stick to midinotes +p = { |midinote| Pbind( + \instrument, \varKlank, + \midinote, midinote, + \amp, PL(\amp), + \att, PL(\att), + \rel, PL(\rel), + \sourceType, PL(\sourceType), + \ringtime, PL(\ringtime), + \pan, PL(\pan) +) }; + +// produce an array of Pbinds, covering a whole tone cluster +q = ((1, 3..51) + 40).collect(p.(_)); + +~size = q.size; + +// same pattern source for each PL, +// but for each midinote divisions per pulse can vary + +~div = Pfunc { (2 ** (1..4)).choose.asInteger }; +~divBase = 1; + +~pulse = 1; +~rel = 0.015; +~sourceType = 1; + +// as q is an array the args 'div' and 'divBase' will be expanded to arrays +u = PSPdiv(PL(\pulse), q, PL(\div), PL(\divBase)); +) + + + +x = u.play + +// this setting exchanges the source of all PL patterns expanded as 'div' arg + +~div = Pfunc { (2 ** (1..6)).choose.asInteger } + + +// also with divBase, layers are decorrelated as each stream from this pattern acts differently + + +~divBase = PLrand([1, 2]) + +~divBase = PLrand([1, 2, 3]) + + +~pulse = 1/4 + +x.stop +:: + + diff --git a/HelpSource/Classes/PSVdif.schelp b/HelpSource/Classes/PSVdif.schelp new file mode 100644 index 0000000..7881e56 --- /dev/null +++ b/HelpSource/Classes/PSVdif.schelp @@ -0,0 +1,52 @@ + +CLASS::PSVdif +summary::Sieve pattern for difference of integer generators with point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for difference of integer generators with point output. Corresponds to Sieve's methods link::Classes/Sieve#*dif:: and link::Classes/Sieve#-dif::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVdif object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +p = PSVdif([3, 2], 10) + +p.asStream.nextN(15) + + +a = Sieve(7, 30) + +q = PSVdif([1, a], 20) + +q.asStream.all + + +r = PSVdif([1, a], limit: 15) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVdif_i.schelp b/HelpSource/Classes/PSVdif_i.schelp new file mode 100644 index 0000000..1362ba3 --- /dev/null +++ b/HelpSource/Classes/PSVdif_i.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVdif_i +summary::Sieve pattern for difference of integer generators with interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for difference of integer generators with interval output. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVdif_i object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned until default summation limit of 65536. + + +SECTION::Examples + +code:: +p = PSVdif_i([3, 2], 10) + +p.asStream.nextN(15) + + +a = Sieve(7, 30) + +q = PSVdif_i([1, a], 20) + +q.asStream.all + + +r = PSVdif_i([1, a], limit: 15) + +r.asStream.all + +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVdif_o.schelp b/HelpSource/Classes/PSVdif_o.schelp new file mode 100644 index 0000000..88687cc --- /dev/null +++ b/HelpSource/Classes/PSVdif_o.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVdif_o +summary::Sieve pattern for difference of integer generators with offsets and point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for difference of integer generators with offsets and point output. Corresponds to Sieve's methods link::Classes/Sieve#*dif_o:: and link::Classes/Sieve#-dif_o::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVdif_o object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Offsets must be integers. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +p = PSVdif_o([3, 1, 5, -2], 10) + +p.asStream.nextN(15) + + +a = Sieve(4, 20) + +q = PSVdif_o([a, 90, 3, 80], 20) + +q.asStream.all + + +r = PSVdif_o([a, 90, 3, 80], limit: 100) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVdif_oi.schelp b/HelpSource/Classes/PSVdif_oi.schelp new file mode 100644 index 0000000..8c345ab --- /dev/null +++ b/HelpSource/Classes/PSVdif_oi.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVdif_oi +summary::Sieve pattern for difference of integer generators with offsets and interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for difference of integer generators with offsets and interval output. Corresponds to Sieve's methods link::Classes/Sieve#*dif_oi:: and link::Classes/Sieve#-dif_oi::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVdif_oi object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +Offsets must be integers. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned up to default summation limit of 65536. + + +SECTION::Examples + +code:: +p = PSVdif_oi([5, 0, 10, -20], 20) + +p.asStream.nextN(15) + + +a = Sieve(4, 20) + +q = PSVdif_oi([1, 0, a, -2], 20) + +q.asStream.all + + +r = PSVdif_oi([1, 0, 10, -2], limit: 20) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVop.schelp b/HelpSource/Classes/PSVop.schelp new file mode 100644 index 0000000..41a7103 --- /dev/null +++ b/HelpSource/Classes/PSVop.schelp @@ -0,0 +1,73 @@ + +CLASS::PSVop +summary::Sieve pattern for arbitrary set operations of integer generators with point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for arbitrary set operations of integer generators with point output. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVop object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. + +argument::op +One of the Symbols 'u', 's', 'sd', 'd' as abbreviations for set operations 'union', +'sect', 'symdif', 'dif' or a Pattern/Stream to produce such. Defaults to 'u'. + +argument::difIndex +Integer or a Pattern/Stream to produce such. +Determines the generator from which will be subtracted in case of operation 'dif'. +Defaults to 0. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +// equivalent + +x = PSVop([3, 5], \sd) +y = PSVsymdif([3, 5]) + +x.asStream.nextN(15) +y.asStream.nextN(15) + + +// sequencing of logical operations + +p = PSVop([3, 3], Pseq([\u, \sd], inf)) + +p.asStream.nextN(15) + + +// specify difference + +q = PSVop([2, 5], \d, 1) + +q.asStream.nextN(100) + + +r = PSVop([2, 5], \d, 0) + +r.asStream.nextN(100) +:: + diff --git a/HelpSource/Classes/PSVop_i.schelp b/HelpSource/Classes/PSVop_i.schelp new file mode 100644 index 0000000..acc5ad6 --- /dev/null +++ b/HelpSource/Classes/PSVop_i.schelp @@ -0,0 +1,72 @@ + +CLASS::PSVop_i +summary::Sieve pattern for arbitrary set operations of integer generators with interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for arbitrary set operations of integer generators with interval output. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVop_i object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. + +argument::op +One of the Symbols 'u', 's', 'sd', 'd' as abbreviations for set operations 'union', +'sect', 'symdif', 'dif' or a Pattern/Stream to produce such. Defaults to 'u'. + +argument::difIndex +Integer or a Pattern/Stream to produce such. +Determines the generator from which will be subtracted in case of operation 'dif'. +Defaults to 0. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned until default summation limit of 65536. + + +SECTION::Examples + +code:: +// equivalent + +x = PSVop_i([3, 5], \sd) +y = PSVsymdif_i([3, 5]) + +x.asStream.nextN(15) +y.asStream.nextN(15) + + +// sequencing of logical operations + +p = PSVop_i([3, 3], Pseq([\u, \sd], inf)) + +p.asStream.nextN(15) + + +// specify difference + +q = PSVop_i([2, 5], \d, 1) + +q.asStream.nextN(100) + + +r = PSVop_i([2, 5], \d, 0) + +r.asStream.nextN(100) +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVop_o.schelp b/HelpSource/Classes/PSVop_o.schelp new file mode 100644 index 0000000..37cb8b8 --- /dev/null +++ b/HelpSource/Classes/PSVop_o.schelp @@ -0,0 +1,75 @@ + +CLASS::PSVop_o +summary::Sieve pattern for arbitrary set operations of integer generators with offsets and point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for arbitrary set operations of integer generators with offsets and point output. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVop_o object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Offsets must be integers. + +argument::op +One of the Symbols 'u', 's', 'sd', 'd' as abbreviations for set operations 'union', +'sect', 'symdif', 'dif' or a Pattern/Stream to produce such. Defaults to 'u'. + +argument::difIndex +Integer or a Pattern/Stream to produce such. +Determines the generator from which will be subtracted in case of operation 'dif'. +Defaults to 0. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +// equivalent + +x = PSVop_o([3, 1, 5, 0], \sd) +y = PSVsymdif_o([3, 1, 5, 0]) + +x.asStream.nextN(15) +y.asStream.nextN(15) + + +// sequencing of logical operations + +p = PSVop_o([3, 1, 2, 0], Pseq([\s, \u, \u], inf)) + +p.asStream.nextN(15) + + +// specify difference + +q = PSVop_o([3, 1, 5, 0], \d, 1) + +q.asStream.nextN(10) + + +r = PSVop_o([3, 1, 5, 0], \d, 0) + +r.asStream.nextN(10) +:: + + diff --git a/HelpSource/Classes/PSVop_oi.schelp b/HelpSource/Classes/PSVop_oi.schelp new file mode 100644 index 0000000..1b29134 --- /dev/null +++ b/HelpSource/Classes/PSVop_oi.schelp @@ -0,0 +1,74 @@ + +CLASS::PSVop_oi +summary::Sieve pattern for arbitrary set operations of integer generators with offsets and interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o + +DESCRIPTION:: +Pattern for arbitrary set operations of integer generators with offsets and interval output. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVop_oi object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +Offsets must be integers. + +argument::op +One of the Symbols 'u', 's', 'sd', 'd' as abbreviations for set operations 'union', +'sect', 'symdif', 'dif' or a Pattern/Stream to produce such. Defaults to 'u'. + +argument::difIndex +Integer or a Pattern/Stream to produce such. +Determines the generator from which will be subtracted in case of operation 'dif'. +Defaults to 0. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned up to default summation limit of 65536. + + +SECTION::Examples + +code:: +// equivalent + +x = PSVop_oi([3, 1, 5, 0], \sd) +y = PSVsymdif_oi([3, 1, 5, 0]) + +x.asStream.nextN(15) +y.asStream.nextN(15) + + +// sequencing of logical operations + +p = PSVop_oi([3, 1, 2, 0], Pseq([\s, \u, \u], inf)) + +p.asStream.nextN(15) + + +// specify difference + +q = PSVop_oi([3, 1, 5, 0], \d, 1) + +q.asStream.nextN(10) + + +r = PSVop_oi([3, 1, 5, 0], \d, 0) + +r.asStream.nextN(10) +:: + diff --git a/HelpSource/Classes/PSVsect.schelp b/HelpSource/Classes/PSVsect.schelp new file mode 100644 index 0000000..3e52194 --- /dev/null +++ b/HelpSource/Classes/PSVsect.schelp @@ -0,0 +1,52 @@ + +CLASS::PSVsect +summary::Sieve pattern for intersection of integer generators with point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for intersection of integer generators with point output. Corresponds to Sieve's methods link::Classes/Sieve#*sect:: and link::Classes/Sieve#-sect::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVsect object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +p = PSVsect([3, 5], 10) + +p.asStream.nextN(15) + + +a = Sieve(7, 1000) + +q = PSVsect([a, 100], 10) + +q.asStream.all + + +r = PSVsect([a, 100], limit: 500) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVsect_i.schelp b/HelpSource/Classes/PSVsect_i.schelp new file mode 100644 index 0000000..fe220f1 --- /dev/null +++ b/HelpSource/Classes/PSVsect_i.schelp @@ -0,0 +1,52 @@ + +CLASS::PSVsect_i +summary::Sieve pattern for intersection of integer generators with interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for intersection of integer generators with interval output. Corresponds to Sieve's methods link::Classes/Sieve#*sect_i:: and link::Classes/Sieve#-sect_i::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVsect_i object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned until default summation limit of 65536. + + +SECTION::Examples + +code:: +p = PSVsect_i([3, 5], 10) + +p.asStream.nextN(15) + + +a = Sieve(7, 1000) + +q = PSVsect_i([a, 100], 10) + +q.asStream.all + + +r = PSVsect_i([a, 100], limit: 500) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVsect_o.schelp b/HelpSource/Classes/PSVsect_o.schelp new file mode 100644 index 0000000..f2b4e4d --- /dev/null +++ b/HelpSource/Classes/PSVsect_o.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVsect_o +summary::Sieve pattern for intersection of integer generators with offsets and point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for intersection of integer generators with offsets and point output. Corresponds to Sieve's methods link::Classes/Sieve#*sect_o:: and link::Classes/Sieve#-sect_o::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVsect_o object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Offsets must be integers. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +p = PSVsect_o([3, 1, 5, -2], 10) + +p.asStream.nextN(15) + + +a = Sieve(4, 200) + +q = PSVsect_o([a, 90, 10, 100], 20) + +q.asStream.all + + +r = PSVunion_o([a, 50, 10, -100], limit: 100) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVsect_oi.schelp b/HelpSource/Classes/PSVsect_oi.schelp new file mode 100644 index 0000000..3baa8e3 --- /dev/null +++ b/HelpSource/Classes/PSVsect_oi.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVsect_oi +summary::Sieve pattern for intersection of integer generators with offsets and interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for intersection of integer generators with offsets and interval output. Corresponds to Sieve's methods link::Classes/Sieve#*sect_oi:: and link::Classes/Sieve#-sect_oi::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVsect_oi object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +Offsets must be integers. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned up to default summation limit of 65536. + + +SECTION::Examples + +code:: +p = PSVsect_oi([10, 31, 50, 1], 20) + +p.asStream.nextN(15) + + +a = Sieve(4, 20) + +q = PSVsect_oi([a, 20, 10, 0], 5) + +q.asStream.all + + +r = PSVsect_oi([a, 20, 10, 0], limit: 25) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVsymdif.schelp b/HelpSource/Classes/PSVsymdif.schelp new file mode 100644 index 0000000..de1bff9 --- /dev/null +++ b/HelpSource/Classes/PSVsymdif.schelp @@ -0,0 +1,52 @@ + +CLASS::PSVsymdif +summary::Sieve pattern for symmetric difference of integer generators with point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for symmetric difference of integer generators with point output. Corresponds to Sieve's methods link::Classes/Sieve#*symdif:: and link::Classes/Sieve#-symdif::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVsymdif object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +p = PSVsymdif([3, 5], 10) + +p.asStream.nextN(15) + + +a = Sieve(7, 50) + +q = PSVsymdif([a, 2], 15) + +q.asStream.all + + +r = PSVsymdif([a, 2], limit: 20) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVsymdif_i.schelp b/HelpSource/Classes/PSVsymdif_i.schelp new file mode 100644 index 0000000..256d299 --- /dev/null +++ b/HelpSource/Classes/PSVsymdif_i.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVsymdif_i +summary::Sieve pattern for symmetric difference of integer generators with interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for symmetric difference of integer generators with interval output. Corresponds to Sieve's methods link::Classes/Sieve#*symdif_i:: and link::Classes/Sieve#-symdif_i::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVsymdif_i object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned until default summation limit of 65536. + + +SECTION::Examples + +code:: +p = PSVsymdif_i([3, 5], 10) + +p.asStream.nextN(15) + + +a = Sieve(7, 30) + +q = PSVsymdif_i([a, 100], 10) + +q.asStream.all + + +r = PSVsymdif_i([a, 100], limit: 1000) + +r.asStream.all + +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVsymdif_o.schelp b/HelpSource/Classes/PSVsymdif_o.schelp new file mode 100644 index 0000000..3e05054 --- /dev/null +++ b/HelpSource/Classes/PSVsymdif_o.schelp @@ -0,0 +1,54 @@ + +CLASS::PSVsymdif_o +summary::Sieve pattern for symmetric difference of integer generators with offsets and point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for symmetric difference of integer generators with offsets and point output. Corresponds to Sieve's methods link::Classes/Sieve#*symdif_o:: and link::Classes/Sieve#-symdif_o::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVsymdif_o object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Offsets must be integers. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +p = PSVunion_o([3, 1, 5, -2], 10) + +p.asStream.nextN(15) + + +a = Sieve(4, 20) + +q = PSVunion_o([a, 90, 10, 100], 20) + +q.asStream.all + + +r = PSVsymdif_o([a, 90, 10, 80], limit: 100) + +r.asStream.all + +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVsymdif_oi.schelp b/HelpSource/Classes/PSVsymdif_oi.schelp new file mode 100644 index 0000000..8d1ba99 --- /dev/null +++ b/HelpSource/Classes/PSVsymdif_oi.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVsymdif_oi +summary::Sieve pattern for symmetric difference of integer generators with offsets and interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for symmetric difference of integer generators with offsets and interval output. Corresponds to Sieve's methods link::Classes/Sieve#*symdif_oi:: and link::Classes/Sieve#-symdif_oi::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVsymdif_oi object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +Offsets must be integers. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned up to default summation limit of 65536. + + +SECTION::Examples + +code:: +p = PSVsymdif_oi([10, 0, 50, 1], 20) + +p.asStream.nextN(15) + + +a = Sieve(4, 20) + +q = PSVsymdif_oi([a, 0, 10, 100], 20) + +q.asStream.all + + +r = PSVsymdif_oi([a, 0, 10, 100], limit: 120) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVunion.schelp b/HelpSource/Classes/PSVunion.schelp new file mode 100644 index 0000000..3434319 --- /dev/null +++ b/HelpSource/Classes/PSVunion.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVunion +summary::Sieve pattern for union of integer generators with point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for union of integer generators with point output. Corresponds to Sieve's methods link::Classes/Sieve#*union:: and link::Classes/Sieve#-union::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVunion object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +p = PSVunion([3, 5], 10) + +p.asStream.nextN(15) + + +a = Sieve(7, 30) + +q = PSVunion([a, 100], 10) + +q.asStream.all + + +r = PSVunion([a, 100], limit: 1000) + +r.asStream.all + +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVunion_i.schelp b/HelpSource/Classes/PSVunion_i.schelp new file mode 100644 index 0000000..ffc1050 --- /dev/null +++ b/HelpSource/Classes/PSVunion_i.schelp @@ -0,0 +1,52 @@ + +CLASS::PSVunion_i +summary::Sieve pattern for union of integer generators with interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for union of integer generators with interval output. Corresponds to Sieve's methods link::Classes/Sieve#*union_i:: and link::Classes/Sieve#-union_i::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVunion_i object. + +argument::genList +An array of generators. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned until default summation limit of 65536. + + +SECTION::Examples + +code:: +p = PSVunion_i([3, 5], 10) + +p.asStream.nextN(15) + + +a = Sieve(7, 30) + +q = PSVunion_i([a, 100], 10) + +q.asStream.all + + +r = PSVunion_i([a, 100], limit: 1000) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVunion_o.schelp b/HelpSource/Classes/PSVunion_o.schelp new file mode 100644 index 0000000..6897cb2 --- /dev/null +++ b/HelpSource/Classes/PSVunion_o.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVunion_o +summary::Sieve pattern for union of integer generators with offsets and point output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for union of integer generators with offsets and point output. Corresponds to Sieve's methods link::Classes/Sieve#*union_o:: and link::Classes/Sieve#-union_o::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVunion_o object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Offsets must be integers. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which integers can be returned by the stream. +If no limit is passed, returned integers might go up to default limit 65536. + + +SECTION::Examples + +code:: +p = PSVunion_o([3, 1, 5, -2], 10) + +p.asStream.nextN(15) + + +a = Sieve(4, 20) + +q = PSVunion_o([a, 90, 10, 100], 20) + +q.asStream.all + + +r = PSVunion_o([a, 10, 3, 7], limit: 20) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSVunion_oi.schelp b/HelpSource/Classes/PSVunion_oi.schelp new file mode 100644 index 0000000..040abe6 --- /dev/null +++ b/HelpSource/Classes/PSVunion_oi.schelp @@ -0,0 +1,53 @@ + +CLASS::PSVunion_oi +summary::Sieve pattern for union of integer generators with offsets and interval output +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Pattern for union of integer generators with offsets and interval output. Corresponds to Sieve's methods link::Classes/Sieve#*union_oi:: and link::Classes/Sieve#-union_oi::. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +method::new + +Creates a new PSVunion_oi object. + +argument::genList +An array of generators and corresponding offsets. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +Offsets must be integers. + +argument::maxLength +Integer. Maximum number of items, which the stream will return. +Defaults to inf. + +argument::limit +Integer. Limit up to which intervals can be returned by the stream. +If no limit is passed, integer intervals might be returned up to default summation limit of 65536. + + +SECTION::Examples + +code:: +p = PSVunion_oi([10, 0, 50, 1], 20) + +p.asStream.nextN(15) + + +a = Sieve(4, 20) + +q = PSVunion_oi([a, 0, 10, 100], 20) + +q.asStream.all + + +r = PSVunion_oi([a, 0, 10, 100], limit: 120) + +r.asStream.all +:: \ No newline at end of file diff --git a/HelpSource/Classes/PSdup.schelp b/HelpSource/Classes/PSdup.schelp new file mode 100644 index 0000000..391aba8 --- /dev/null +++ b/HelpSource/Classes/PSdup.schelp @@ -0,0 +1,172 @@ +CLASS:: PSdup +summary:: Pattern which returns last values from other patterns via PSx +categories::Libraries>miSCellaneous>PSx stream patterns, Streams-Patterns-Events>PSx stream patterns +related:: Overviews/miSCellaneous, Tutorials/PSx_stream_patterns, Classes/MemoRoutine, Classes/PS, Classes/PSrecur, Classes/PSloop + + +DESCRIPTION:: + + +PSdup uses the storage functionality of other PSx patterns. To track a value stream or event stream from a pattern you'd have to wrap the pattern into a PSx. + + + +CLASSMETHODS:: + +method::new + +Creates a new PSdup object. + +argument::psxPat +Source pattern, must be a PSx pattern. + +argument::length +Number of output items, may be pattern or stream, defaults to inf. + +argument::bufSize +Size of buffer to store last values, defaults to 1. + +argument::copyItems +Determines if and how to copy the last items from strong::psxPat::'s buffer +which are either non-Sets or member of Sets. +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). +Other values are interpreted as 0. Defaults to 0. + +0: original item + +1: copy item + +2: deepCopy item + +argument::copySets +Determines if and how to copy the last items from strong::psxPat::'s buffer +which are Sets. +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). +Other values are interpreted as 0. Defaults to 1. + +0: original Set + +1: copy Set + +note:: +The distinction of copying items and sets makes sense in the case of event streams. Per default Events are copied (strong::copySets:: == 1), not their values (strong::copyItems:: == 0). By playing Events those are used to store additional data (synth ids, msgFuncs …) which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use MemoRoutine - copied Events will not contain this additional data. If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured then copying makes no sense, this is the normal case, so copyItems defaults to 0. When going to alter the ouput, you might want to set strong::copyItems:: to 1 for a PSx returning simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set strong::copySets:: to 1 and strong::copyItems:: to 2 (an option strong::copySets:: == 2 doesn't exist as it would be contradictory in combination with strong::copyItems:: < 2). +:: + + +note:: +Copy options concern copying into PSdup's buffer as well as the output of a Stream derived from the PSdup. When such a Stream is outputting copies this prevents unintended altering of items stored in the buffer of strong::psxPat::. On the other hand storing copies in PSdup's buffer prevents these from being altered unintendedly. +:: + + +INSTANCEMETHODS:: + +method::psxPat +Instance variable getter and setter methods. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// copying of value patterns +// PSdup needs a PSx as input, as its value storage is used, +// so it's easiest to wrap the original pattern into a PS + +( +p = PS(Pseries()); +q = PSdup(p); + +// PSx patterns have a state and, in this regard, behave like Streams. +// However, like other Patterns, for generating they must be made to Streams +// which are the objects actually returning items. + +x = p.asStream; +y = q.asStream; +) + +// y follows x (as in the array left is evaluated before right) + +( +a = []; +10.do { a = a.add([x.next, y.next]) }; +a; +) + + +// get values from original Pattern / Stream + +x.nextN(10) + +// now y copies the last value of x resp. p + +y.nextN(10) + + + +// data sharing with event patterns + +( +p = Pbind( + \midinote, Pwhite(60, 90) + PLrand([0, 0, [0, -3]]), + \dur, PLrand([0.2, 0.4]), + \amp, PLrand([0.07, 0.12, 0.2]), + \type, Pfunc { 0.1.coin.if { \rest }{ \note } }, + \pan, 1 +); + +q = PS(p); + +// use PSdup as base for alterings +// it takes the last event stored in PS(p) + +r = Pbindf(Padd(\midinote, PLrand([1,2,4,5]), PSdup(q)), \pan, -1); + +// as in general with event stream data sharing it's recommended to invent a small delta +// between streams, here this is extended to a clear echo + +t = Ptpar([0, q, 0.1, r]).trace.play; +) + +t.stop; + + +// data sharing with event patterns, more voices + + +( +p = Pbind( + \midinote, Pwhite(50.0, 65), + \dur, PLrand([0.7, 0.9, 1.2]), + \amp, PLrand([0.1, 0.15, 0.2]), + \legato, 0.1 +); + +q = PS(p); + +// make pattern maker Function for parametrized alterings + +f = { |div, add| Padd(\midinote, add, Pmul(\dur, 1 / div, PSdup(q))) }; +d = 0.001; + +t = Ptpar([ + 0, q, + d, f.(2, 5 + PLrand([0, [0, 12.05]])), + d*2, f.(4, 7 + PLrand([0, [0, 12.1]])), + d*3, f.(8, 10 + PLrand([0, [0, 12.2]])) +]).play +) + + +t.stop; + + +:: + + \ No newline at end of file diff --git a/HelpSource/Classes/PSloop.schelp b/HelpSource/Classes/PSloop.schelp new file mode 100644 index 0000000..5ef711c --- /dev/null +++ b/HelpSource/Classes/PSloop.schelp @@ -0,0 +1,681 @@ +CLASS:: PSloop +summary:: Pattern to derive loops from a given Pattern +categories::Libraries>miSCellaneous>PSx stream patterns, Streams-Patterns-Events>PSx stream patterns +related:: Overviews/miSCellaneous, Tutorials/PSx_stream_patterns, Classes/MemoRoutine, Classes/PS, Classes/PSdup, Classes/PSrecur + +DESCRIPTION:: + +Although PSloop is not a subclass of PStream, instances of PSloop have a state by storing a PStream of the source pattern in an instance variable. So the specific characteristics of PSx patterns are indirectly inherited by PSloop. This especially concerns the way to generate new, fresh PSloop streams, see examples in link::Tutorials/PSx_stream_patterns::. + +Note that impossible lookBack and indices values will be clipped, see descriptions of args strong::bufSize::, strong::lookBack:: and strong::indices::. + + + +CLASSMETHODS:: + +method::new + +Creates a new PSloop object. + +argument::srcPat +Source pattern for looping, can be value or event pattern. + +argument::length +Number of output items, may be Pattern or Stream, defaults to inf. + +argument::bufSize +Integer size of buffer to store last values. Determines and clips the maximum strong::lookBack:: index. Defaults to 1. + + +argument::lookBack +Non-negative Integer determining the depth of looking backwards when looping, may be Pattern or Function also. +Default 0 means going on with the Stream of the source pattern strong::srcPat::. +If no strong::indices:: is given, a positive Integer strong::lookBack:: = n causes straight looping from the nth item +in the past up to the last item of the Stream. +If strong::indices:: is given, an index array is produced by this function applied to strong::lookBack:: = n. +The array refers to the nth item in the past by array index 0. +If strong::lookBack:: values are derived from a Pattern or Function, they might be polled at the end of the loops +or with every item, this is determined by the current value of strong::doSkip::. +strong::lookBack:: will be clipped by strong::bufSize:: if it is greater than the latter. + + +argument::doSkip +Integer or Boolean or: Function or Stream returning such. +Determines if strong::lookBack:: values are polled at the end of loops (0, false) or within loops (1, true) also. +In the latter case a new strong::lookBack:: value will stop the current loop and start a new one. Defaults to 1. + +argument::loopFunc +Function or Pattern returning Functions, to be applied to items of the loops. +If Functions are streamed by passing a Function-generating Pattern strong::loopFuncPerItem:: determines if +a Function will be applied per item (1, true) or to all items of a loop (0, false). +When loopFunc is nil or the strong::loopFunc:: stream returns nil, no Function is applied. + +argument::loopFuncPerItem +Integer or Boolean or: Function or Stream returning such. +If strong::loopFunc:: is given, strong::loopFuncPerItem:: determines if a Function will be applied per item (1, true) or +to all items of a loop (0, false). + +argument::loopFuncAsGoFunc +Integer or Boolean or: Function or Stream returning such. +Determines if strong::loopFunc:: will be used for new items outside loops too (1, true). +In this case strong::goFunc:: is ignored (resp. nothing is polled from a goFunc stream) and nil values +from the loopFunc stream are also taken over (no Function is applied outside loops then). + +argument::goFunc +Function or Pattern returning Functions, to be applied to new items outside loops. +When strong::goFunc:: is nil or the strong::goFunc:: stream returns nil, no Function is applied. +Note that goFunc has no effect when strong::loopFuncAsGoFunc:: determines to use strong::loopFunc:: generally. + +argument::indices +SequenceableCollection, Function, or Pattern returning SequenceableCollections or Functions. +If no strong::indices:: is passed (default nil) a positive Integer strong::lookBack:: = n causes straight looping from the nth item +in the past up to the last item of the Stream. +A SequenceableCollection passed via strong::indices:: is taken as array of indices and determines the items of the buffer, +identifying the nth item in the past with array index 0. +A Function passed via strong::indices:: takes strong::lookBack:: = n as argument and must return an index array which also +determines the items of the buffer, identifying the nth item in the past with array index 0. +A Pattern passed via strong::indices:: should be defined to generate Functions or SequenceableCollections, +which are interpreted in the same way as above. +In case of a Pattern a new strong::indices:: value is polled within a loop if current strong::doSkip:: equals 1 or true. +strong::indices:: will be clipped by strong::lookBack:: - 1, strong::lookBack:: itself might be clipped by strong::bufSize::. + + +argument::copyItems +Argument passed to the PS wrapper of strong::srcPat::. See link::Classes/PS::. +Determines if and how to copy items which are either non-Sets or member of Sets. +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). +Other values are interpreted as 0. Defaults to 0. + +0: original item + +1: copy item + +2: deepCopy item + +argument::copySets +Argument passed to the PS wrapper of strong::srcPat::. See link::Classes/PS::. +Determines if and how to copy Sets (and hence Events). +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). +Other values are interpreted as 0. Defaults to 1. + +0: original Set + +1: copy Set + +note:: +The distinction of copying items and sets makes sense in the case of event streams. Per default Events are copied (strong::copySets:: == 1), not their values (strong::copyItems:: == 0). By playing Events those are used to store additional data (synth ids, msgFuncs …) which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use MemoRoutine - copied Events will not contain this additional data. If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured then copying makes no sense, this is the normal case, so copyItems defaults to 0. When going to alter the ouput, you might want to set strong::copyItems:: to 1 for a PSx returning simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set strong::copySets:: to 1 and strong::copyItems:: to 2 (an option strong::copySets:: == 2 doesn't exist as it would be contradictory in combination with strong::copyItems:: < 2). +:: + + +note:: +Copy options concern copying into PSrecur's buffer as well as the output of a Stream derived from the PSrecur. When such a Stream is outputting copies this prevents unintended altering of items stored in the buffer of PSrecur. On the other hand storing copies in PSrecur's buffer prevents these from being altered unintendedly. +:: + + +INSTANCEMETHODS:: + +method::psPat +Instance variable getter and setter methods. +strong::psPat:: holds a Pstream with args strong::srcPat::, strong::length::, strong::bufSize::, strong::copyItems::, strong::copySets::. + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + +anchor::Ex.1a:: +subsection::Ex.1a: PSloop used as value pattern + +code:: +// straight usage as value pattern for midinotes + +( +// lookBack determines number of items to loop, must start with 0 (no loop), +// doSkip determines if loops should be completed or not with new lookBack values. + +// Passing both via Functions enables to set on the fly, +// enable immediate loop change by setting doSkip to 1 + +~lookBack = 0; +~doSkip = true; + +p = Pbind( + \dur, 1/5, + \midinote, PSloop( + Prand((60..80), inf), + bufSize: 10, + lookBack: { ~lookBack }, + doSkip: { ~doSkip } + ) +).play +) + +// wait a bit to fill buffer +// keep running, set loop lengths immediately + +~lookBack = 4 + +~lookBack = 2 + +~lookBack = 7 + + +// change to skip per loop (default) + +~doSkip = false + +~lookBack = 3 + +~lookBack = 5 + +~lookBack = 9 + + +// go on + +~lookBack = 0 + +p.stop; + +:: + + +anchor::Ex.1b:: +subsection::Ex.1b: PSloop used as event pattern + +code:: +// rhythms are looped too + +( +~lookBack = 0; +~doSkip= 1; + +p = PSloop( + Pbind( + \dur, Pn(Pshuf([1, 1, 2, Pseq(2!3/3)]/5)), + \midinote, Prand((60..80), inf) + ), + bufSize: 10, + lookBack: { ~lookBack }, + doSkip: { ~doSkip} +).play +) + +// wait a bit to fill buffer +// keep running, set loop lengths immediately + +~lookBack = 6 + +~lookBack = 5 + + +// go on and loop again + +~lookBack = 0 + +~lookBack = 7 + + +p.stop; +:: + + + +anchor::Ex.2a:: +subsection::Ex.2a: PSloop used as value pattern, lookBack given as pattern + +code:: +// lookBack passed as pattern, +// lookBack pattern must start with zeros, +// otherwise it will immediately stop with buffer values = nil + +// PSloop as value pattern: in general rhythms aren't looped + +( +p = Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, PSloop( + Prand((60..80), inf), + bufSize: 10, + lookBack: Pseq([0, 0, 0, 2, 2, 3, 3], inf) + ) +).play +) + +p.stop +:: + + + + +anchor::Ex.2b:: +subsection::Ex.2b: PSloop used as event pattern, lookBack given as pattern + +code:: +// rhythms looped too + +( +p = PSloop( + Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, Prand((60..80), inf) + ), + bufSize: 10, + lookBack: Pseq([0, 0, 0, 2, 2, 3, 3], inf) +).play +) + +p.stop + +:: + + +anchor::Ex.3a:: +subsection::Ex.3a: lookBack indices given as array + +code:: +// With the indices arg arbitrary series from the buffer can be played. +// Passing an array we define an index sequence which doesn't depend on lookBack + +( +~lookBack = 0; + +p = PSloop( + Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, Pseq((60..80), inf) + ), + bufSize: 10, + lookBack: { ~lookBack }, + indices: [0, 1, 0, 1, 2, 0, 1, 2, 3] +).trace.play +) + +// wait a bit to fill buffer +// start loop with useful lookBack (here > 3) + +~lookBack = 4 + +~lookBack = 6 + + +// go on and loop again + +~lookBack = 0; + +~lookBack = 5 + + +p.stop +:: + + +anchor::Ex.3b:: +subsection::Ex.3b: lookBack indices given as Function + +code:: +// If a Function is passed to indices it takes the current lookBack arg as argument. + +// mirroring the full lookBack size +// start without loop + +( +~lookBack = 0; + +p = PSloop( + Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, Prand((60..80), inf) + ), + bufSize: 10, + lookBack: { ~lookBack }, + indices: { |l| (0..l-1).mirror } +).trace.play +) + +// wait a bit to fill buffer +// mirrored loop i.e. loop with added retrograde + +~lookBack = 3 + + +// go on and loop again + +~lookBack = 0 + +~lookBack = 3 + +p.stop +:: + + +anchor::Ex.3c:: +subsection::Ex.3c: lookBack indices given as Pattern + +code:: +// The indices arg might get a pattern which can generate Functions as well as arrays. +// Start with sufficient number of zeros as lookBack to fill buffer. + +( +p = PSloop( + Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, Prand((60..80), inf) + ), + bufSize: 10, + lookBack: Pseq([Pn(0, 7), Pstutter(4, Pwhite(3, 5, 1))], inf).trace, + indices: Pseq([1!2, { |l| (l-1..0).postln }], inf).trace +).trace.play +) + +p.stop +:: + + +anchor::Ex.4a:: +subsection::Ex.4a: loopFunc given as Function + +code:: +// a Function passed to loopFunc is applied to all items of the looping + +( +~lookBack = 0; + +p = Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, PSloop( + Prand((60..80), inf), + bufSize: 10, + lookBack: { ~lookBack }, + loopFunc: { |x| x + [0, 5] } + ) +).play +) + +// wait a bit to fill buffer +// loop with Function applied + +~lookBack = 3 + +~lookBack = 6 + + +// go on and stop + +~lookBack = 0 + +p.stop +:: + + + +anchor::Ex.4b:: +subsection::Ex.4b: loopFunc given as Pattern + +code:: +// loopFunc also takes a pattern of Functions, in this case +// the arg loopFuncPerItem decides if next Functions are polled per item or per loop. + +( +~lookBack = 0; +~loopFuncPerItem = true; // or 1 + +p = Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, PSloop( + Prand((60..80), inf), + bufSize: 10, + lookBack: { ~lookBack }, + // every number gets a Function that adds an interval of that size + loopFunc: Pxrand((3..11), inf).collect { |x| { |y| y - [0, x].postln } }, + loopFuncPerItem: { ~loopFuncPerItem } + ) +).trace.play +) + +// wait a bit to fill buffer +// start looping with Functions polled per item + +~lookBack = 3 + +~lookBack = 6 + + +// one interval per loop + +~loopFuncPerItem = false // or 0 + +~lookBack = 9 + + +// switch back to new interval per item + +~loopFuncPerItem = 1 + + +// go on and stop + +~lookBack = 0 + +p.stop +:: + + +anchor::Ex.4c:: +subsection::Ex.4c: loopFunc vs. goFunc + +code:: +// Besides loopFunc a separate Function (or Pattern of Functions) can be defined for +// application outside loops via the goFunc arg. +// The arg loopFuncAsGoFunc decides if loopFunc should be taken for that task +// (and goFunc should be ignored in that case). + +( +~lookBack = 0; +~loopFuncPerItem = false; // one func per loop +~loopFuncAsGoFunc = false; // start with dedicated goFunc + +p = Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, PSloop( + Prand((60..80), inf), + bufSize: 10, + lookBack: { ~lookBack }, + // every number gets a Function that adds an interval of that size + loopFunc: Pxrand((3..11), inf).collect { |x| { |y| y - [0, x].postln } }, + loopFuncPerItem: { ~loopFuncPerItem }, + goFunc: { |x| [x, x + 2] }, + loopFuncAsGoFunc: { ~loopFuncAsGoFunc } + ) +).trace.play +) + +// wait a bit to fill buffer +// start looping with Functions polled per loop + +~lookBack = 3 + +~lookBack = 6 + + +// loop per item + +~loopFuncPerItem = true + + +// prepare to use loopFunc when going on + +~loopFuncAsGoFunc = true + + +// go on + +~lookBack = 0 + + +// switch to dedicated goFunc and stop + +~loopFuncAsGoFunc = false + +p.stop +:: + + + +anchor::Ex.4d:: +subsection::Ex.4d: Turning loopFunc and goFunc on and off + +code:: +// There are no separate PSloop flags needed for that: +// If loopFunc or goFunc return nil, no Function is applied, +// so suited Patterns using flags can be passed as loopFunc / goFunc args. + +( +~lookBack = 0; +~loopFuncPerItem = false; // one func per loop +~loopFuncAsGoFunc = false; // start with dedicated goFunc + +~turnOnLoopFunc = true; +~turnOnGoFunc = true; + +p = Pbind( + \dur, Pn(Pshuf([1, 1, 2]/6)), + \midinote, PSloop( + Prand((60..80), inf), + bufSize: 10, + lookBack: { ~lookBack }, + // every number gets a Function that adds an interval of that size, + // if ~turnOnLoopFunc evaluates to false the stream returns nil instead of a Function + loopFunc: Pxrand((3..11), inf).collect { |x| ~turnOnLoopFunc.if { { |y| y - [0, x].postln } } }, + loopFuncPerItem: { ~loopFuncPerItem }, + goFunc: Pfunc { ~turnOnGoFunc.if { { |x| [x, x + 2] } } }, + loopFuncAsGoFunc: { ~loopFuncAsGoFunc } + ) +).trace.play +) + +// wait a bit to fill buffer +// start looping with Functions polled per loop + +~lookBack = 3 + +~lookBack = 6 + + +// when Functions are polled per loop, turning loopFunc off also works per loop + +~turnOnLoopFunc = false + + +// use loopFunc again, namely per item + +~turnOnLoopFunc = true + +~loopFuncPerItem = true + + +// now turning it off works immediately + +~turnOnLoopFunc = false + + + +// go on + +~lookBack = 0 + + +// as loopFunc is now turned off, this applies also when used for new values + +~loopFuncAsGoFunc = true + + +// switch to goFunc again + +~loopFuncAsGoFunc = false + + +// silently turn loopFunc on + +~turnOnLoopFunc = true + + +// turn goFunc off and again use loopFunc as goFunc + +~turnOnGoFunc = false + +~loopFuncAsGoFunc = true + + + +// loop, again with loopFunc per item + +~lookBack = 5 + + +p.stop + +:: + + +anchor::Ex.5:: +subsection::Ex.5: PSloops controlling different sound params + +code:: +// Overlapping of loops on different sound params can give interesting polyrhythmic structures + +( +SynthDef(\psloop_1, { |out = 0, freq = 440, centDif = 5, rq = 0.1, cutoff = 1000, amp = 0.1 + att = 0.01, sus = 0.0, rel = 0.01| + var sig = Saw.ar(freq * [1, (centDif * 0.01).midiratio], amp); + Out.ar(0, BPF.ar(sig, cutoff.clip(30, 8000)) * EnvGen.ar(Env.linen(att, sus, rel), doneAction: 2)); +}, 0.01!6).add +) + +( +// Function to generate a standard PSloop with lookBack arg to by controlled by env variable with suffix LB +// Pfunc could also be written as: PL((name ++ \LB).asSymbol) + +~psLoop = { |src, name| PSloop(src, bufSize: 10, lookBack: Pfunc { currentEnvironment[(name ++ \LB).asSymbol] }) }; + +p = Pbind( + \instrument, \psloop_1, + \att, 0.01, + \amp, Pfunc { ~amp }, + \sus, ~psLoop.(Prand([0.1, 0.2], inf), \sus), + \rel, ~psLoop.(Prand([0.01, 0.1, 1], inf), \rel), + \dur, ~psLoop.(Pn(Pshuf([1, 1, 2]/5)), \dur), + \centDif, Pshuf([5, -20, 40], inf), + \midinote, ~psLoop.(Pxrand((30..50), inf), \midinote), + \cutoff, ~psLoop.(Pshuf([500, 1500, 5000], inf), \cutoff) +); + +q = Pbindf(p, \midinote, ~psLoop.(Pxrand((50..70), inf), \midinote)); +r = Pbindf(p, \midinote, ~psLoop.(Pxrand((75..95), inf), \midinote)); + + +// VarGui for control of lookBack params of 3 parallel streams +// zero values for args with suffix LB mean: no looping + +// play loops by setting several params to non-zero values, +// you can grab params of different voices by using the alt key while +// moving a slider + +VarGui({ |i| [ + \susLB, [0, 7, \lin, 1, 0], + \relLB, [0, 7, \lin, 1, 0], + \durLB, [0, 7, \lin, 1, 0], + \midinoteLB, [0, 7, \lin, 1, 0], + \cutoffLB, [0, 7, \lin, 1, 0], + \amp, [0, 1, \lin, 0, 0.2 + (i * 0.1)] +] }!3, stream: [r, q, p], quant: 1/5 +).gui(labelWidth: 80) +) +:: + diff --git a/HelpSource/Classes/PSrecur.schelp b/HelpSource/Classes/PSrecur.schelp new file mode 100644 index 0000000..689e132 --- /dev/null +++ b/HelpSource/Classes/PSrecur.schelp @@ -0,0 +1,117 @@ +CLASS:: PSrecur +summary:: Pattern to generate new values with a recursive Function +categories::Libraries>miSCellaneous>PSx stream patterns, Streams-Patterns-Events>PSx stream patterns +related:: Overviews/miSCellaneous, Tutorials/PSx_stream_patterns, Classes/MemoRoutine, Classes/PS, Classes/PSdup, Classes/PSloop + + + +CLASSMETHODS:: + +method::new + +Creates a new PSrecur object. + +argument::recurFunc +Recursive Function, the the array of last values will be passed as first arg, count as second arg. + +argument::length +Number of output items, may be pattern or stream, defaults to inf. + +argument::bufSize +Size of buffer to store last values. +If strong::bufSize:: is not defined and strong::start:: is not given or a single item, strong::bufSize:: gets value 1. +If strong::bufSize:: is not defined and strong::start:: is an array strong::bufSize:: gets its length. + +argument::start +Single start item or array of items used for recursion. + +argument::copyItems +Determines if and how to copy the last items generated by strong::recurFunc:: which are either non-Sets or member of Sets. +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). +Other values are interpreted as 0. Defaults to 0. + +0: original item + +1: copy item + +2: deepCopy item + +argument::copySets +Determines if and how to copy Sets generated by strong::recurFunc::. +Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). +Other values are interpreted as 0. Defaults to 1. + +0: original Set + +1: copy Set + +note:: +The distinction of copying items and sets makes sense in the case of event streams. Per default Events are copied (strong::copySets:: == 1), not their values (strong::copyItems:: == 0). By playing Events those are used to store additional data (synth ids, msgFuncs …) which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use MemoRoutine - copied Events will not contain this additional data. If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured then copying makes no sense, this is the normal case, so copyItems defaults to 0. When going to alter the ouput, you might want to set strong::copyItems:: to 1 for a PSx returning simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set strong::copySets:: to 1 and strong::copyItems:: to 2 (an option strong::copySets:: == 2 doesn't exist as it would be contradictory in combination with strong::copyItems:: < 2). +:: + + +note:: +Copy options concern copying into PSrecur's buffer as well as the output of a Stream derived from the PSrecur. When such a Stream is outputting copies this prevents unintended altering of items stored in the buffer of PSrecur. On the other hand storing copies in PSrecur's buffer prevents these from being altered unintendedly. +:: + + +INSTANCEMETHODS:: + +method::recurFuncStream +Instance variable getter and setter methods. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// another way to generate the fibonacci sequence +// the first arg passed to the Function is the PSrecur itself, +// x[0] refers to its last value, x[1] to the value before the last value + +( +a = PSrecur({ |x| x[0] + x[1] }, start: [0, 1]); +b = a.iter; +b.nextN(10); +) + +// shorter with partial application + +PSrecur(_[0]+_[1], start: [0, 1]).iter.nextN(10); + + +// recursion with event patterns +// function calculates an iterated average of past event values for all keys, +// events at the end of the array are taken into account most. + +( +u = { |e,f| e.copy.keysValuesChange { |k| e[k]+f[k]/2 } }; +v = { |a| a.reduce(u) }; +) + +( +// three basic Events + +e = (midinote: 65, dur: 0.75, amp: 0.1, pan: 0); +f = (midinote: 58, dur: 0.05, amp: 0.3, pan: -1); +g = (midinote: 92, dur: 0.05, amp: 0.4, pan: 1); + +// mostly build "blurred" average of last five events, +// by chance disturbance by choosing a basic event + +p = PSrecur({ |x| 0.8.coin.if { v.(x[..4]) }{ [e,f,f,g].choose } }, start: [e,f,e,g,e]); + +q = p.trace.play; +) + +q.stop; + + +:: + diff --git a/HelpSource/Classes/PV_BinGap.schelp b/HelpSource/Classes/PV_BinGap.schelp new file mode 100755 index 0000000..3afc6a4 --- /dev/null +++ b/HelpSource/Classes/PV_BinGap.schelp @@ -0,0 +1,103 @@ +CLASS:: PV_BinGap +summary:: pseudo ugen keeping the complement of a spectral range +categories::Libraries>miSCellaneous>PV pseudo ugens +related:: Overviews/miSCellaneous, Classes/PV_BinRange, Guides/FFT-Overview + + +DESCRIPTION:: + + +Based on link::Classes/PV_BrickWall::, but instead of wipe parameters it takes two bin numbers. + + +CLASSMETHODS:: + +method::new + +Creates a new PV_BinGap object. + +argument::buffer +FFT buffer. + +argument::loBin +low bin index of excluded range. + +argument::hiBin +high bin index of excluded range. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// frequencies are rounded to nearest bins + +( +f = { |loFreq = 800, hiFreq = 1500, fundFreq = 50, amp = 0.1| + var bufSize = 1024, binRange, loBin, hiBin, sig, chain; + + sig = Saw.ar(fundFreq, amp); + + binRange = s.sampleRate / bufSize; + loBin = (loFreq / binRange).round; + hiBin = (hiFreq / binRange).round; + + chain = FFT(LocalBuf(bufSize), sig); + chain = PV_BinGap(chain, loBin, hiBin); + IFFT(chain) ! 2; +}; + +x = f.play; + +s.freqscope; +) + +x.set(\loFreq, 300); + +x.set(\hiFreq, 3000); + +x.release; + + + +// for multichannel expansion an array of mono buffers must be provided + +( +g = { |loFreq = 800, hiFreq = #[1500, 1500] fundFreq = 50, amp = 0.1| + var bufSize = 1024, binRange, loBin, hiBin, sig, chain; + + sig = Saw.ar(fundFreq, amp); + + binRange = s.sampleRate / bufSize; + loBin = (loFreq / binRange).round; + hiBin = (hiFreq / binRange).round; + + chain = FFT({ LocalBuf(bufSize) } ! 2, sig); + chain = PV_BinGap(chain, loBin, hiBin); + IFFT(chain); +}; + +x = g.play; + +s.freqscope; +) + +x.set(\loFreq, 300); + +x.set(\hiFreq, [1200, 1200]); + +x.set(\hiFreq, [800, 2000]); + +x.set(\hiFreq, [2000, 800]); + +x.release; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PV_BinRange.schelp b/HelpSource/Classes/PV_BinRange.schelp new file mode 100755 index 0000000..a2a4390 --- /dev/null +++ b/HelpSource/Classes/PV_BinRange.schelp @@ -0,0 +1,103 @@ +CLASS:: PV_BinRange +summary:: pseudo ugen keeping a spectral range +categories::Libraries>miSCellaneous>PV pseudo ugens +related:: Overviews/miSCellaneous, Classes/PV_BinGap, Guides/FFT-Overview + + +DESCRIPTION:: + + +Based on link::Classes/PV_BrickWall::, but instead of wipe parameters it takes two bin numbers. + + +CLASSMETHODS:: + +method::new + +Creates a new PV_BinRange object. + +argument::buffer +FFT buffer. + +argument::loBin +low bin index of resulting range. + +argument::hiBin +high bin index of resulting range. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// frequencies are rounded to nearest bins + +( +f = { |loFreq = 800, hiFreq = 1500, fundFreq = 50, amp = 0.1| + var bufSize = 1024, binRange, loBin, hiBin, sig, chain; + + sig = Saw.ar(fundFreq, amp); + + binRange = s.sampleRate / bufSize; + loBin = (loFreq / binRange).round; + hiBin = (hiFreq / binRange).round; + + chain = FFT(LocalBuf(bufSize), sig); + chain = PV_BinRange(chain, loBin, hiBin); + IFFT(chain) ! 2; +}; + +x = f.play; + +s.freqscope; +) + +x.set(\loFreq, 300); + +x.set(\hiFreq, 1000); + +x.release; + + + +// for multichannel expansion an array of mono buffers must be provided + +( +g = { |loFreq = #[500, 500], hiFreq = 1500, fundFreq = 50, amp = 0.1| + var bufSize = 1024, binRange, loBin, hiBin, sig, chain; + + sig = Saw.ar(fundFreq, amp); + + binRange = s.sampleRate / bufSize; + loBin = (loFreq / binRange).round; + hiBin = (hiFreq / binRange).round; + + chain = FFT({ LocalBuf(bufSize) } ! 2, sig); + chain = PV_BinRange(chain, loBin, hiBin); + IFFT(chain); +}; + +x = g.play; + +s.freqscope; +) + +x.set(\loFreq, [200, 200]); + +x.set(\loFreq, [300, 1200]); + +x.set(\loFreq, [1200, 300]); + +x.set(\hiFreq, 2000); + +x.release; + +:: + \ No newline at end of file diff --git a/HelpSource/Classes/PV_ChainUGen.ext.schelp b/HelpSource/Classes/PV_ChainUGen.ext.schelp new file mode 100644 index 0000000..89174a5 --- /dev/null +++ b/HelpSource/Classes/PV_ChainUGen.ext.schelp @@ -0,0 +1,6 @@ + +INSTANCEMETHODS:: + +private:: miSC_getFFTbufSize + + diff --git a/HelpSource/Classes/Pattern.ext.schelp b/HelpSource/Classes/Pattern.ext.schelp new file mode 100644 index 0000000..d4023b7 --- /dev/null +++ b/HelpSource/Classes/Pattern.ext.schelp @@ -0,0 +1,24 @@ + + + +INSTANCEMETHODS:: + + + +private:: miSC_repTypeFactor + +private:: miSC_streamifySieveItems + + +method:: asESP +Abbreviation for asEventStreamPlayer + +method::symplay + +Plays a pattern wrapped into a link::Classes/PsymNilSafe::. Args strong::clock::, strong::protoEvent:: and strong::quant:: are passed to link::Classes/Pattern#-play::, strong::dict:: and strong::maxNull:: work like with PsymNilSafe, strong::dict:: defaults to the current Environment and strong::maxNull:: to 128. See link::Tutorials/PLx_and_live_coding_with_Strings:: + + +method::eventShortcuts + +Wraps the receiver into a Pcollect with a Function that replaces according to EventShortcuts if it's turned on. This can be useful in cases where a pattern's method embedInStream changes keys of Events before applying the event type function, that employs the normal replacement mechanism of EventShortcuts. Then the mapping can be done inside/before. Normally you wouldn't need to call that method explicitely. See also link::Classes/EventShortcuts::, link::Classes/Event#-eventShortcuts::, link::Classes/SequenceableCollection#-eventShortcuts:: and link::Tutorials/PLx_and_live_coding_with_Strings:: + diff --git a/HelpSource/Classes/PbindFx.schelp b/HelpSource/Classes/PbindFx.schelp new file mode 100755 index 0000000..0cbaf9d --- /dev/null +++ b/HelpSource/Classes/PbindFx.schelp @@ -0,0 +1,2096 @@ +CLASS:: PbindFx +summary:: event pattern for effect handling on per-event base +categories:: Libraries>miSCellaneous>Other patterns +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/kitchen_studies, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PmonoPar, Classes/PpolyPar, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + + +DESCRIPTION:: + + +PbindFx works like a normal Pbind of event type 'note' in most regards, but with the additional option to define a number of effects. Their order and parameters can also be defined with patterns which allows a great flexibility: for each event an arbitrary multichannel effect graph can be applied, mixing sequential and parallel arrangement ad libitum. This requires a relatively high amount of resource management: bus allocation, routing and node ordering as well as delayed cleanup have to be done for each event. All necessary bookkeeping is done automatically, for some critical parameters though it's the user's responsibility to pass meaningful values (e.g. cleanupDelay for reverb has to be defined sufficiently high, otherwise reverb synth and audio bus might be freed before reverb has ended). There is always a tradeoff between flexibility and processing effort, if you won't change fx parameters on a per-event base or you won't reorder your effect arrangement, then you might prefer playing effects from and to predefined buses and control them otherwise, e.g. per LFOs (additionally possible also with PbindFx) or dedicated setting streams, see link::Classes/PmonoPar:: and link::Classes/PpolyPar::. However with the strategy of effects bound to buses you have the same effect arrangements and parameters concerning all source signals sent to the same bus, more variation with such setups needs more (pre-)definition of buses, whereas with PbindFx overlapping events can be processed with different effect arrangements and parameters with no explicit effort. +One possible application of PbindFx is applying effects per grain, for the project link::Tutorials/kitchen_studies:: I documented the source code of a fixed media piece in six parts, using this technique. + +warning:: + +As bus allocation is done dynamically per event, there is a circumvented, but still potential danger of creating feedback loops. To prevent this, additional "zero synths" are started with bus-reading fx synths, playing a zero signal with ReplaceOut to the buses in question, they are placed before those fx / source synth(s), which will play there too. For all test examples, even with deliberately bad values, zero synths turned out to be an effective way to block unwanted input signals and feedback, as they last as long as fx / source synths (they even overlap them a bit). However, with extraordinary parameter values for timing, improperly defined fx / source synths, sloppy audio bus mapping and / or parallel actions that affect resource management globally, feedback, as in any situation of heavy bus repatching, can not be totally excluded. Be aware of that, avoid high levels and be careful with headphones ! +:: + + + +CLASSMETHODS:: + +method::new + +Creates a new PbindFx object. + + +argument::pbindData +SequenceableCollection of Pbind's key/value pairs or event pattern. +Passing a list saves explicitely typing Pbind, but passing an event pattern is more flexible, +as it allows replacement (link::#Ex. 7#Ex.7::), event pattern filtering (link::#Ex. 3a#Ex.3a::) and similar operations. + +specific keys: + +list:: +## strong::\fxOrder:: – For each event it can be given in three ways, for single effects and +sequential ordering it may be + +numberedlist:: +## strong::an Integer::, or +## strong::a SequenceableCollection of Integers::, +indicating the effect order. Effect counting starts with 1, 0 means no effect (default). +E.g. if three effects are passed to strong::fxData::, [1], [2], [3], [1, 2], [1, 3], [2, 3], +[2, 1], [3, 1], [3, 2], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1], +would represent all possible effect orders with each effect applied once. +Passing single Integers is equivalent to passing them in an array. +Data concerning one event can also be given as +## strong::a Ref object containing an IdentityDictionary::, representing the effect graph. +Keys of the dictionary – single Integers – are identified with effects of strong::fxData::, values +of the dictionary might be single Integers or SequenceableCollections of +Integers, indicating a branching of effects (whenever you want a branching of effects +or routing two different effects to a third one, the input must be given as a ref'd IdentityDictionary). +Within the effect graph an explicit direct out can be denoted with the Symbol \o, +0 represents the source. The graph must be acyclic, a check is performed. +strong::fxOrder:: can also be a Pattern, sequencing effect orders of the forms (1) - (3). +See link::#Principle of operation#Principle of operation:: and link::#Ex. 10a#Ex.10a::, link::#Ex. 10b#Ex.10b::, link::#Ex. 10c#Ex.10c::. +:: + +## strong::\cleanupDelay:: – Number or Pattern to generate Numbers, taken as seconds. +Its meaning differs, depending if source synth (passed by \instrument) has +a gated or fixed length envelope. If a gate control is encountered in the SynthDef +description, a gated envelope is assumed. +In the latter case this arg determines the earliest time after release when +cleanup may start. With a fixed-length envelope it determines the earliest time +after synth start when cleanup may start. +With effects defined, cleanupDelays of source and effects are summarized. +Defaults to the class variable strong::defaultSourceCleanupDelay:: which defaults to 0.3. +Typically you would take the releaseTime of the source synth's / synthdef's +gated envelope, or the maximum overall length of the fixed-length envelope, +each plus a small delta. + +## strong::\cleanupClock:: – The clock on which freeing of audio buses is scheduled with SkipJack objects. +As the clock should survive CmdPeriod its permanent flag must be set to true. +Per default the SystemClock is used, but for certain cases, e.g. granulation +you might want to pass a TempoClock with higher queueSize, see examples below. + +## strong::\cleanupDt:: – Number or Pattern to generate Numbers, taken as seconds. +Determines the delta time for cleanup with SkipJack objects. In those intervals +it is checked whether event-specific cleanup delaytimes have been reached +and cleanup (freeing buses) should be performed. Defaults to the class variable +strong::defaultCleanupDt:: which defaults to 0.2. With higher values more SkipJack objects +have to be scheduled at the same time, although with fewer activity. + +## strong::\freePerGroup:: – Boolean, defaults to false. +Determines if effects and zero synths of the chain should be freed separately or +removed at the end of the chain by freeing the enclosing group. +With more effects and longer cleanupDelays the latter leads to a larger number +of parallel synths and might be wasteful thus. On the other hand it can be +a useful option with short events and cleanupDelays, e.g. in case of granulation. +See below timing scheme for a more detailled description. + +## strong::\otherBusArgs:: – Takes SequenceableCollection of Symbols, defaults to nil. +For all passed symbols the source synth is enabled to read from and write to external buses, +which are passed to an arg of that name, LocalIn and LocalOut are allowed anyway. +Per default a restrictive check of synth I/O ugens and matching is performed in order to prevent +unintended reading from resp. writing to buses. With this option you can allow reading +and writing in a controlled way, see link::#Ex. 6a#Ex.6a:: and link::#Ex. 6b#Ex.6b::. + +:: + +argument::... fxData +SequenceableCollection of Pbind's key/value pairs defining effect sequencing or event pattern. +Passing a list saves explicitely typing Pbind, but passing an event pattern is more flexible, +as it allows replacement (link::#Ex. 7#Ex.7::), event pattern filtering (link::#Ex. 3a#Ex.3a::) and similar operations. + +specific keys: + +list:: +## strong::\fx:: – Symbol or Pattern to generate Symbols. +Determines the effect synthdef. + +## strong::\cleanupDelay:: – Number or Pattern to generate Numbers, taken as seconds. +Determines the earliest time after effect node delay when cleanup, +including freeing the effect synth, may start. +With effects defined, cleanupDelays of source and effects are summarized. +Defaults to the class variable strong::defaultFxCleanupDelay:: which defaults to 0.05. +Typically you would take the maximum delay of the effect synth plus a small delta. + +## strong::\otherBusArgs:: – Takes SequenceableCollection of Symbols, defaults to nil. +For all passed symbols the fx synth is enabled to read from and write to external buses, +which are passed to an arg of that name, LocalIn and LocalOut are allowed anyway. +Per default a restrictive check of synth I/O ugens and matching is performed in order to prevent +unintended reading from resp. writing to buses. With this option you can allow reading +in a controlled way, see link::#Ex. 6a#Ex.6a:: and link::#Ex. 6b#Ex.6b::. +:: + + +method::defaultSourceCleanupDelay + +Get and set the value of this class variable. Defaults to 0.3. + + +method::defaultCleanupDt + +Get and set the value of this class variable. Defaults to 0.2. + + +method::defaultFxCleanupDelay + +Get and set the value of this class variable. Defaults to 0.05. + + +anchor::Principle of operation:: +section::Principle of operation + +subsection::Per-event effect routing with PbindFx, example scheme of two effects applied sequentially + +This can typically be achieved by passing an array [i, j] to \fxOrder, where i and j denote arbitrary unequal positive effect numbers (numbers smaller or equal than strong::fxData::'s size). + +Fig. 1 + +image::attachments/PbindFx/PbindFx_graph_1.png:: + + +Effects FX#1 and FX#2 read from buses BUS#1 and BUS#2 which are reserved for the duration of the event plus a cleanup time. BUS#1 and BUS#2 get their data from source synth SRC resp. FX#1. "Zero synths" Z#1 and Z#2 are placed before them in node order, they are lasting as long as (in fact a bit longer than) FX#1 and FX#2, playing zero signals to BUS#1 and BUS#2 with ReplaceOut and cancelling out possible residual signals on those buses, thus blocking feedback. BUS#1 and BUS#2 might be multichannel buses, in that case a number of mono zero synths is established for each Z#i. +Two dedicated groups are generated for each event: FXGRP is the container for all event-generated synths and SRCGRP is placed at its head. Z#1 is placed at the head of SRCGRP, other zero synths and effects are sequentially placed upwards from the tail of FXGRP, interlaced in the way described below. Finally source synth(s) are placed at tail of SRCGRP (the group passed further to the note event). + + +node ordering for n = 2 + +Fig. 2 + +list:: +##FXGRP: +list:: + ##SRCGRP: +list:: + ##Z#1 + ##SRC +:: + + ##Z#2 + ##FX#1 + ##FX#2 +:: +:: + +You can also pass a group (or a pattern of groups) to PbindFx, in that case FXGRP is related to the group depending on \addAction (default \addToHead): + + + +general node ordering with n sequentially applied effects for n > 2 +group GRP passed to PbindFx with key \group, +default addAction \addToHead + + +Fig. 3 + +list:: +##GRP: +list:: + ##FXGRP: +list:: + ##SRCGRP: +list:: + ##Z#1 + ##SRC +:: + ##Z#2 + ##FX#1 + + ... + + ... + + ##Z#n + ##FX#n-1 + ##FX#n +:: +:: +:: + + +Note that effect order is arbitrary per event, determined by key strong::\fxOrder::, in these examples FX#i denote the order in the regarded event, not the order in which the effects are passed to PbindFx. The whole process is implemented via dedicated event type 'pbindFx', an extension of event type 'note', which is chosen with PbindFx by default. The following schemes refer to server-side timing, additional lang-side offset might be passed with lag (in seconds) to ensure proper timing for sequences combining delayed and non-delayed effects e.g. including occasional echo as in several of the examples below. + + +subsection:: Timing scheme of PbindFx with two effects applied sequentially, source synth with gated envelope + +Fig. 4 + +image::attachments/PbindFx/PbindFx_graph_2a.png:: + + +list:: +##t#0: start SRC, Z#i, FX#i and groups with server-side ordering as described in routing scheme, + in fact Z#i are started slightly earlier. +##t#1: begin of SRC envelope release period, caused by a release message (set gate arg to 0) +##t#2: end of SRC envelope release period, probably end of source synth (doneAction = 2) +##t#3: supposed latest end of source synth due to given cleanupDelay of source, added to t#1 + (cleanupDelay might be passed as a constant maximum release time for all events) +##t#4: list:: + ##~freePerGroup == false (default): freeing of FX#1 and Z#1, the latter a bit later + ##~freePerGroup == true: do nothing + :: +##t#5: free FXGRP, thus also FX#2 and Z#2. +:: + +For longer effect chains with n effects the case distinction of t#4 applies to all pairs Z#i / FX#i for 1 < i < n: +with ~freePerGroup == true they all are freed at last by freeing the group. + + +subsection:: Timing scheme of PbindFx with two effects applied sequentially, source synth with fixed-length envelope + +Fig. 5 + +image::attachments/PbindFx/PbindFx_graph_2b.png:: + + +list:: +##t#0: start SRC, Z#i, FX#i and groups with server-side ordering as described in routing scheme, + in fact Z#i are started slightly earlier. +##t#1: begin of SRC envelope release period, caused by the synth itself (no setting gate to 0) +##t#2: end of SRC envelope release period, probably end of source synth (doneAction = 2) +##t#3: supposed latest end of source synth due to given cleanupDelay of source, added to t#0 + (cleanupDelay might be passed as a constant maximum envelope length for all events) +##t#4 - t#5: as with gated envelope +:: + + +subsection:: Parallel effect processing, arbitrary effect graphs + +The most simple case of applying two effects in parallel can be triggered by passing `(0: [1, 2]) to fxOrder. Effect numbers occurring in arrays of dictionary values cause a routing from these effects to out. Symbol \o (for destination out) can also be passed explicitely. +Branching like this requires the use of a split synth which routes the output to the demanded multitude of fx buses. A suitable predefined split synthdef is chosen according to the number of branches and the channel number of the signal to be splitted – currently both is limited by 8, however synths, which are only playing to out directly, might have an arbitrary number of output channels. +Different to sequential fx processing, both fx bus zero synths have to be added before the source, additional zero synth(s) for the split bus are prepended. + +Fig. 6 + +image::attachments/PbindFx/PbindFx_graph_3a.png:: + +The following effect graph can be established by fxOrder `(0: [1, 2, 3], 2: [4, \o], 3: 2), split synths, zero synths (for fx buses and split buses) are omitted in this scheme. For each (acyclic) fx graph a topological order is calculated (here it could e.g. be [0,1,3,2,4]) and the node ordering, including FXGRP and SRCGRP, similar to the sequential case, is derived accordingly. Some small differences, especially concerning the cleanup delay times, have to be taken into account, these details are omitted here. + +Fig. 7 + +image::attachments/PbindFx/PbindFx_graph_3b.png:: + + +section::Conventions + +numberedlist:: + +##Source instrument and effect SynthDefs must be known to SynthDescLib.global (e.g. by creating SynthDefs with methods 'add' or 'store') + +##Fx SynthDefs must be defined with arg 'out' for the outbus index, arg 'in' for inbus index and use In.ar(in, ...) within the SynthDef.It's up to the user whether to define the effect with dry/wet mix option. Effect synths don't need to have an envelope, their freeing is handled by the event type function. + +##Effect chains must be defined properly in terms of in/out channel number. For each event the bus matching of the effect chain is checked, following these conventions: + +list:: +##For source and fx SynthDefs there must be only one out ugen using 'out' as bus arg, LocalOut is allowed, other out ugens can be admitted by strong::\otherBusArgs::. +##Source SynthDefs must not have in ugens, except LocalIn or they are admitted by strong::\otherBusArgs::. +##Fxs must read from buses with ugen In.ar(in, ...) refering to the bus arg 'in', there must not be other in ugens within the fx SynthDef except LocalIn or they are admitted by strong::\otherBusArgs::, see link::#Ex. 6a#Ex.6a:: and link::#Ex. 6b#Ex.6b::. +##Number of out channels of preceeding source / fx synth must not be greater than the number of the following fx synth's in channels, this is checked for all pairs of an fx graph. +##Mismatches of above points lead to errors. +:: + +##Checks of (3) are also based on info in SynthDescLib.global. It is possible to replace SynthDefs on the fly (link::#Ex. 7a#Ex.7a::, link::#Ex. 7b#Ex.7b::), in that case I'd recommend to use also methods 'add' or 'store' – e.g. in case of using 'send' for redefinition no bus matching check is performed and a possibly wrong routing would be undetected. + +##As shown in above graphics the source synth's cleanupDelay is interpretated differentely with gated and non-gated (fixed-length) envelopes. This is done automatically by looking for a 'gate' arg in the SynthDef's SynthDesc. It's the users responsibility to use the conventional arg 'gate' for release in the SynthDef and omit a 'gate' arg with SynthDefs employing fixed-length envelopes. + +##As with normal event patterns the source Pbind / Pbind pairs may be defined with arrays to produce multiple synths per event, then the whole source signal is routed to the fx graph (see link::#Ex. 2b#Ex.2b::, link::#Ex. 2c#Ex.2c:: and others). fxData pairs can also be defined with arrays, which causes parallel processing per node ("implicit parallelism"), see link::#Ex. 4#Ex.4a::. For applying different fxs to parallel events (resp. chords) use parallel PbindFxs (link::#Ex. 3a#Ex.3a::, link::#Ex. 3b#Ex.3b::). It is of course also possible that fx synths have array args and fx patterns are passing them with the double-bracketing convention used for these cases, see link::Tutorials/Event_patterns_and_array_args::. + +##Source instrument and effect SynthDefs are allowed to have args of same name. There is no problem as key/value pairs are stored in separate lists/events. + +##PbindFx is implemented per automatically chosen effect type 'pbindFx', which employs event type 'note', thus an event type must neither be passed to pbindData nor to fxData. + +##The value conversion framework can be used for fxData, e.g. by passing \midinote instead of \freq or \db instead of \amp as with Pbind, see Ex.9. Other than with Pbind, freq and amp event defaults are not passed to fx synths, if no such values are passed via fxData. In that case default values of fx synths are taken. + +##While playing PbindFx you should not play with allocation affecting private buses. Freeing buses is done by SkipJack objects, so you should not forcefully stop all SkipJack objects while playing PbindFx. + +##Keys \group and \addAction may be passed, they determine how the group enclosing event-generated synths is related to the passed group, see scheme link::#Principle of operation#Principle of operation::. + +##Zero synths are using the SynthDefs 'pbindFx_zero' and 'pbindFx_splitZero' which are written to disk at startup time, as well as split synths pbindFx_split_axb for a = 2,...,8 and b = 1,...,8. Of course these SynthDefs shouldn't be deleted or exchanged. + +:: + + +section::Resources, troubleshooting + +numberedlist:: + +##You might encounter the error message "Meta_Bus:audio: failed to get an audio bus allocated." As audio buses for effect chains are allocated and freed per event a higher number of private audio buses is likely to be required (more than default 128). You might also encounter the error message "exception in real time: alloc failed, increase server's memory allocation (e.g. via ServerOptions)". Hence it's recommended to set the concerned server options before (re-)booting and working with PbindFx, e.g.: + +code:: + +( +s.options.numPrivateAudioBusChannels = 1024; +s.options.memSize = 8192 * 16; +s.reboot; +) +:: + +##You might encounter the warning "Scheduler queue is full." Per default delayed freeing of buses is scheduled on SystemClock, which defaults to queueSize 1024. In case of granulation and/or large cleanup delays it's recommended to pass a cleanup clock with sufficiently large queueSize via strong::pbindData::, its permanent flag must be set to true, e.g.: + +code:: +... +\cleanupClock, t = TempoClock(queueSize: 8192).permanent_(true) +... +:: + +Note that this clock keeps on running and survives CmdPeriod, you should explicitely stop it if you don't need it anymore, hence it should be stored in a variable. In case you haven't done that you can still stop all TempoClocks and remove them from CmdPeriod with + +code:: +TempoClock.all.copy.do(_.stop) +:: + +##Cleanup delays for source and effects (or, if not passed, their default values) should be sufficiently large (see description of strong::\cleanupDelay:: ), otherwise effects and audio buses might be freed too early and signals cut. + +##Some effects, like echo, introduce a delay. If sequencing mixes delayed events with non-delayed events, entries of the latter have to be delayed accordingly to preserve correct timing, this can be done by setting an offset in seconds with \lag. See examples with echo below. +:: + +section::Ex. 1: Straight usage with unchanged effect order + +anchor::Ex. 1a:: +subsection::Ex. 1a: Source synth with sustained envelope + + +code:: +// boot server with extended resources + +( +s.options.numPrivateAudioBusChannels = 1024; +s.options.memSize = 8192 * 16; +s.reboot; +) + +// basic source and effect synthdefs for this help file + +( +// All ins and outs use two channels + +// source synthdef +// take releaseTime = decayTime + +SynthDef(\source, { |out = 0, freq = 400, decayTime = 0.5, + attackTime = 0.005, amp = 0.1, gate = 1| + var env, sig = Decay.ar(Impulse.ar(0), decayTime, Saw.ar(freq)); + env = EnvGen.ar(Env.asr(attackTime, amp, decayTime, \lin), gate, doneAction: 2); + Out.ar(out, sig ! 2 * env) +}).add; + + +// spat fx +// This effect introduces a very small delay, +// in examples balancing by lag (as it obviously has to be done with echo) is neglected. + +SynthDef(\spat, { |out, in, freq = 1, maxDelayTime = 0.005, + amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = DelayC.ar( + inSig, + maxDelayTime, + { LFDNoise3.ar(freq, maxDelayTime, maxDelayTime/2) } ! 2, + amp + ); + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + + +// echo fx, always unified delay maxEchoDelta + +SynthDef(\echo, { |out, in, maxEchoDelta = 0.2, echoDelta = 0.1, + decayTime = 1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = DelayL.ar( + CombL.ar(inSig, maxEchoDelta, echoDelta, decayTime, amp), + maxEchoDelta, + maxEchoDelta - echoDelta + ); + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + + +// wah-wah fx + +SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000, + cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = RLPF.ar( + inSig, + LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi), + rq, + amp + ).softclip; + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + + +// reverb fx +// rough estimation: freeVerb's room arg = decayTime / 10 + +SynthDef(\reverb, { |out, in, damp = 0.5, + decayTime = 10, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + Out.ar(out, FreeVerb.ar(inSig, mix, min(decayTime, 10) / 10, damp, amp)); +}).add; +) + + +// Fx params are sequenced on a per-event base. + +// see server window: number of groups (divided by 2) indicates +// the number of parallel event chains in action +// check while running with s.queryAllNodes + +( +p = PbindFx([ + \instrument, \source, + \dur, 0.25, + \amp, 0.2, + \midinote, Prand([ + Pwhite(80, 90, 1), + Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf), + ], inf), + + \fxOrder, [1, 2], + // With a sustained envelope \cleanupDelay refers to the maximum release time, + // in SynthDef \source releaseTime = decayTime, so take cleanupDelay = decayTime + \decayTime, Pwhite(0.2, 2), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + // oscillation of delay -> frequency modulation of source signal + \freq, Prand([1, 2, 3], inf), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + // variation by sequencing of params + \fx, \wah, + \mix, Pseq([0.2, 0.5, 0.7], inf), + \cutOffMoveFreq, Pseq([1, 2, 5, 10], inf), + \cleanupDelay, 0.01 + ] +); + +q = p.play; +) + +// see server window and compare "regular" stop +// (descending number of groups, synths and ugens reflects delayed cleanup) +// with stopping the same example by Cmd-Period + +q.stop; + +:: + + + +anchor::Ex. 1b:: +subsection::Ex. 1b: Source synth with fixed-length envelope + + +SynthDefs from link::#Ex. 1a:: plus SynthDef variation with adsr args + +code:: +( +SynthDef(\source_adsrFixed, { |out = 0, freq = 400, decayTime = 0.5, + att = 0.005, dec = 0.01, sus = 0.2, rel = 0.3, susLevel = 0.5, amp = 0.1| + var env, sig = Saw.ar(freq); + env = EnvGen.kr(Env([0, 1, susLevel, susLevel, 0], [att, dec, sus, rel]), doneAction: 2); + Out.ar(out, sig ! 2 * env * amp) +}).add; +) + +// adsr values passed, \cleanupDelay estimated as max of sum + +( +p = PbindFx([ + \instrument, \source_adsrFixed, + \dur, 0.25, + \att, Pwhite(0.005, 0.01), + \dec, Pwhite(0.01, 0.02), + \sus, Pwhite(0.02, 0.3), + \rel, Pwhite(0.2, 1.5), + + \susLevel, 0.4, + \amp, 0.2, + + \midinote, Prand([ + Pwhite(80, 90, 1), + Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf), + ], inf), + + \fxOrder, [1, 2], + + // cleanupDelay must be larger than max env length, otherwise events might be cut ! + // here we do an estimation (att + dec + sus + rel < 2), + // but it could be summed from those event values too, e.g. with + // \cleanupDelay, Pfunc { |e| e.att + e.dec + e.sus + e.rel } + \cleanupDelay, 2 + ],[ + \fx, \spat, + \freq, Prand([1, 2, 3], inf), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \wah, + \mix, Pseq([0.2, 0.5, 0.7], inf), + \cutOffMoveFreq, Pseq([1, 2, 5, 10], inf), + \cleanupDelay, 0.01 + ] +); + +q = p.play; +) + + +// check node order while running + +s.queryAllNodes; + +q.stop; +:: + + +section::Ex. 2: Sequencing of different effect chains + +anchor::Ex. 2a:: +subsection::Ex. 2a: Determined fx sequence + +PbindFx using spat and echo effects. Especially relevant keys: \fxOrder which determines fx sequencing and \cleanupDelay for proper releaseTimes of source and effects. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +p = PbindFx([ + \instrument, \source, + \dur, 0.5, + \amp, 0.3, + \midinote, Pwhite(50, 90), + + // fx sequence \spat, \spat + \echo, etc. + \fxOrder, Pseq([1, [1,2]], inf), + + // echo is delayed (maxEchoDelta = 0.2), + // compensate here by shift when no echo + // we need \lag rather than \timingOffset as the whole delaytime calculation refers to seconds + \lag, Pseq([0.2, 0], inf), + + // in SynthDef \source releaseTime = decayTime, so take cleanupDelay = decayTime + \decayTime, Pseq([1, 0.1], inf), + \cleanupDelay, Pkey(\decayTime) + ],[ + // define effect with index 1 + \fx, \spat, + \maxDelayTime, 0.005, + + // oscillation of delay -> frequence modulation of source signal + \freq, Pseq([1, 1, 10], inf), + + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + // define effect with index 2 + \fx, \echo, + \echoDelta, 0.08, + \decayTime, Pwhite(0.8, 3), + + \cleanupDelay, Pkey(\decayTime) + ] +); + +q = p.play; +) + +q.stop; +:: + +anchor::Ex. 2b:: +subsection::Ex. 2b: Random fx sequence + +If more than one synth per event is produced (here by key 'midinote'), the effect chain is applied to all of them, see link::#Ex. 3#Ex.3:: for applying different effects to parallel synths. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +p = PbindFx([ + \instrument, \source, + \dur, 0.2, + \amp, 0.3, + + // downwards tendency + chord sequence + \midinote, Pseq((90, 80..50), inf) + + Pn(Pshuf([[0, 5], 0, [0, 2.5], [-2.5, 12.5], [-3, 0]])), + + \fxOrder, Prand([1, [1,2], [1,2]], inf), + + // lag must be adapted to maxEchoDelta + \lag, Pfunc { |e| e.fxOrder.isArray.if { 0 }{ 0.2 } }, + + // echo -> shorter source decay + \decayTime, Pfunc { |e| e.fxOrder.isArray.if { 0.1 }{ 0.7 } }, + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \echo, + \echoDelta, Pseq((1..5)/50, inf), + \decayTime, 1, + \cleanupDelay, Pkey(\decayTime) + ] +); + +q = p.play; +) + +q.stop; +:: + + +anchor::Ex. 2c:: +subsection::Ex. 2c: Some extensions + +Additional use of rests, reverb added. Here the reverb usage is deliberately wasteful, see link::#Ex. 2d#Ex.2d:: for an alternative. The use of Pn + Pshuf (or equivalently Pshufn) gives balanced random variation for several key streams. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +p = PbindFx([ + \instrument, \source, + \dur, Pn(Pshuf(0.2!5 ++ Rest(0.2))), + + \midinote, Pseq((90, 80..40), inf) + + Pn(Pshuf([[0, 5], 0, [0, 2.5], [-2.5, 12.5], [-3, 0]])), + + \fxOrder, Pn(Pshuf([[1,2], [1,2,3], [3,1], 1])), + + \lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }, + \amp, Pfunc { |e| (e.fxOrder != [1,2]).if { 0.3 }{ 0.6 } }, + + \decayTime, Pfunc { |e| + rrand(0.3, 0.8) / (e.fxOrder.asArray.includes(2).if { 10 }{ 1 }) + }, + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, Pn(Pshuf([1, 1, 1, 5, 20, 50])), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \echo, + \echoDelta, Pseq((1..5)/50, inf), + \decayTime, Pwhite(0.3, 1.8), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \reverb, + \mix, 0.3, + \damp, 0.1, + \decayTime, Pwhite(3.0, 10), + \cleanupDelay, Pkey(\decayTime) + ] +); + +q = p.play; +) + +q.stop; +:: + +anchor::Ex. 2d:: +subsection::Ex. 2d: Saving resources + +If effects have a long cleanup delay, you will get a possibly large number of overlapping effect chains. E.g. in link::#Ex. 2c#Ex.2c:: many reverb synths can be there in parallel, the decayTime is controlled by Pwhite(3.0, 10), so it might well be that reverbs with decayTimes 7.95, 8, and 8.1 are instantiated in parallel, which doesn't make much difference and is quite wasteful. Reverb is often placed at the last position of the effect chain, so a more efficient approach would be the following: do all effect sequencing without reverb with PbindFx and pipe the overall out to a permanently running reverb. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + + +code:: + +// start two reverbs with different parameters, read from dedicated buses + +( +a = Bus.audio(s, 2); +b = Bus.audio(s, 2); + +x = Synth(\reverb, [mix: 0.3, damp: 0.1, decayTime: 3, in: a]); +y = Synth(\reverb, [mix: 0.2, damp: 0.1, decayTime: 10, in: b]); +) + +// play PbindFx +// compare CPU usage with Ex. 2c + +( +p = PbindFx([ + \instrument, \source, + \dur, Pn(Pshuf(0.2!5 ++ Rest(0.2))), + + \midinote, Pseq((90, 80..40), inf) + + Pn(Pshuf([[0, 5], 0, [0, 2.5], 0, [-2.5, 12.5], [-3, 0]])), + + \fxOrder, Pn(Pshuf([1, 2, [1,2]])), + + \lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }, + \amp, Pfunc { |e| (e.fxOrder != [1,2]).if { 0.3 }{ 0.6 } }, + + \decayTime, Pfunc { |e| + rrand(0.3, 0.8) / (e.fxOrder.asArray.includes(2).if { 10 }{ 1 }) + }, + \cleanupDelay, Pkey(\decayTime), + + // pipe out to different reverbs resp. 0 (no reverb) + \out, Pn(Pshuf([0, 0, a, a, b])) + ],[ + \fx, \spat, + \freq, Pn(Pshuf([1, 1, 1, 5, 20, 50])), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \echo, + \echoDelta, Pseq((1..5)/50, inf), + \decayTime, Pwhite(0.3, 1.8), + \cleanupDelay, Pkey(\decayTime) + ] +); + +q = p.play; +) + +// stop + +q.stop; + +// free extra resources + +[x, y, a, b].do(_.free); + +:: + + +anchor::Ex. 3:: +section::Ex. 3: Different effects for parallel synths + + +This can be done with parallel PbindFxs. + + +anchor::Ex. 3a:: +subsection::Ex. 3a: Using a template Pbind + + +Here the option of passing a source Pbind instead of a list of Pbind pairs can be used. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// master pattern, fxOrders defines fxOrder for voices of chord +f = Pbind( + \dur, 0.3, + \type, \rest, + \fxOrders, Pn(Pshuf([ + [[1, 3], 2, 1], [1, [1, 3], 2], [2, 1, [1, 3]], + [0, 1, 1], [1, 0, 1], [1, 1, 0] + ]).collect { |o| ~o = o }) +); + +// core source Pbind +a = Pbind( + \instrument, \source, + \dur, 0.3, + + // reference to fxOrder will be got from Pchains below + \lag, Pfunc { |e| e.fxOrder.includes(2).if { 0 }{ 0.2 } }, + \amp, Pfunc { |e| e.fxOrder.any([2,3].includes(_)).if { 0.3 }{ 0.1 } }, + + \decayTime, Pfunc { |e| rrand(0.5, 0.7) / (e.fxOrder.includes(2).if { 10 }{ 1 }) }, + \cleanupDelay, Pkey(\decayTime) +); + +// lists of fx pairs +b = [[ + \fx, \spat, + \freq, Pn(Pshuf([1, 2, 3, 10])), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) +],[ + \fx, \echo, + \echoDelta, 3/50, + \decayTime, Pwhite(0.3, 1.8), + \cleanupDelay, Pkey(\decayTime) +],[ + \fx, \wah, + \mix, 0.5, + \cutOffMoveFreq, Pseq([5, 10], inf), + \cleanupDelay, 0.01 +]]; + +// derive three Pchains from core Pbind, +// voice-specific fxOrder will be read from master pattern +u = a <> Pbind( + \fxOrder, Pfunc { ~o[0].asArray }, + \midinote, 72 +); + +v = a <> Pbind( + \fxOrder, Pfunc { ~o[1].asArray }, + \midinote, Pstutter(Pwhite(5, 10), Prand((60..70), inf)) +); + +w = a <> Pbind( + \fxOrder, Pfunc { ~o[2].asArray }, + \midinote, Pstutter(Pwhite(5, 10), Prand((75..85), inf)) +); + + +// play in parallel, master must be before + +d = 0.001; + +q = Ptpar([ + 0, f, + d, PbindFx(u, *b), + d, PbindFx(v, *b), + d, PbindFx(w, *b) +]).play; +) + +q.stop; + +:: + + +anchor::Ex. 3b:: +subsection::Ex. 3b: Using a PbindFx generator Function + +Equivalent to link::#Ex. 3a#Ex.3a::, but might look more straight, as fxOrder pair already generated with PbindFx Function. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// master pattern for fxOrder +f = Pbind( + \dur, 0.3, + \type, \rest, + \fxOrders, Pn(Pshuf([ + [[1, 3], 2, 1], [1, [1, 3], 2], [2, 1, [1, 3]], + [0, 1, 1], [1, 0, 1], [1, 1, 0] + ]).collect { |o| ~o = o }) +); + +// PbindFx generator + +g = { |i| PbindFx([ + \instrument, \source, + \dur, 0.3, + + \fxOrder, Pfunc { ~o[i].asArray }, + \lag, Pfunc { |e| e.fxOrder.includes(2).if { 0 }{ 0.2 } }, + \amp, Pfunc { |e| e.fxOrder.any([2,3].includes(_)).if { 0.3 }{ 0.1 } }, + + \decayTime, Pfunc { |e| rrand(0.5, 0.7) / (e.fxOrder.includes(2).if { 10 }{ 1 }) }, + \cleanupDelay, Pkey(\decayTime), + ],[ + \fx, \spat, + \freq, Pn(Pshuf([1, 2, 3, 10])), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \echo, + \echoDelta, 3/50, + \decayTime, Pwhite(0.3, 1.8), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \wah, + \mix, 0.5, + \cutOffMoveFreq, Pseq([5, 10], inf), + \cleanupDelay, 0.01 + ] +); +}; + +// derive three Pbindfs from PbindFx generator, +// as PbindFx is a subclass of Pbind, you can apply Pbindf as to Pbind + +u = Pbindf(g.(0), \midinote, 72); +v = Pbindf(g.(1), \midinote, Pstutter(Pwhite(5, 10), Prand((60..70), inf))); +w = Pbindf(g.(2), \midinote, Pstutter(Pwhite(5, 10), Prand((75..85), inf))); + +d = 0.001; +q = Ptpar([0, f, d, u, d, v, d, w]).play; +) + +q.stop; +:: + + +anchor::Ex. 4:: +section::Ex. 4: Applying the same fx SynthDef more than once in a chain + +subsection::Ex. 4a: Implicit duplication (= implicit parallelism) + +code:: +// Within a graph node parallelism can be established in analogy to +// the generation of multiple synths with Pbind: +// by passing an array as value within fxData. + +( +// pitchshift fx, result can easily be controlled by ear + +SynthDef(\pitchShift, { |out, in, windowSize = 0.2, midiShift = 1, + pitchDispersion = 0, timeDispersion = 0, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + + sig = PitchShift.ar( + inSig, + windowSize, + midiShift.midiratio, + pitchDispersion, + timeDispersion + ); + + Out.ar(out, (1 - mix) * inSig + (sig * mix) * amp); +}).add; + + + +p = PbindFx([ + \instrument, \source, + \dur, 0.5, + \amp, 0.2, + \midinote, Pwhite(50, 80), + + \fxOrder, 1, + // With a sustained envelope \cleanupDelay refers to the maximum release time, + // in SynthDef \source releaseTime = decayTime, so take cleanupDelay = decayTime + \decayTime, 1.5, + \cleanupDelay, Pkey(\decayTime) + ],[ + // variation by sequencing of params + \fx, \pitchShift, + \mix, 0.8, + // implicitely generate parallel processing with different fx params + // the two fxs applied in parallel together with the source result in a sequence of major triads + \midiShift, [4, 7], + \windowSize, 0.04, + \timeDispersion, 0.005, + \cleanupDelay, 0.01 + ] +); + +q = p.play; +) + +q.stop; +:: + + +subsection::Ex. 4b: Explicit duplication + +code:: +// By passing the same fx in different fxData args you have all options +// by defining the fx graph: in sequence, in parallel or however. +// Here is an example for explicit duplication in sequence, +// for defining explicit parallelism and arbitrary fx graphs see Ex. 10 + +// SynthDefs from Ex. 1a, see also extended server resources defined there + +( +p = PbindFx([ + \instrument, \source, + \dur, 1/3, + \amp, Pseq([0.2, 0.4, 0.3], inf), + \midinote, Pseq([ + Pwhite(35, 48, 1) + [-14, 0, 14], + Pshuf([60, 67, 70, 73], 1) + Pshuf([0, 12], 1) + [0, 4, 12], + ], inf), + + \fxOrder, Pseq([1, [1, 2], [1, 2, 3]], inf), + \lag, Pseq([0.17, 0.03, 0.00], inf), + + \decayTime, Pseq([2.5, 0.1, 0.1], inf), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, Prand([1, 2, 3], inf), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \echo, + \echoDelta, Pwhite(0.06, 0.1), + \decayTime, 1, + + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \echo, + // short delta results in additional frequency + \echoDelta, Pwhite(0.005, 0.01), + \decayTime, 0.2, + + \cleanupDelay, Pkey(\decayTime) + ] +); + +q = p.play; +) + +q.stop; +:: + + + +anchor::Ex. 5:: +section::Ex. 5: Tempo control + +Tempo control works as with Pbind and can be influenced by a number of parameters. As cleanup parameters of PbindFx are passed in seconds, tempo control can be done independent from making cleanup time changes (though you might do so as well). The use of a dedicated TempoClock as master tempo control is a good idea, setting tempo for individual streams can be done with 'stretch'. The following example employs a PbindFx generator Function, the tempo of streams can be controlled individually and generally. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// PbindFx generator Function, +// streams will get different midinote offsets, +// tempo will be read from a passed array + +g = { |midiOffset, tempoArray, index| + + PbindFx([ + \instrument, \source, + \dur, 0.25, + \stretch, Pfunc { 1 / tempoArray[index] }, + \midinote, Prand([ + Pwhite(55, 75, 1), + Pn(Pshuf([60, 67, 70, 73])), + ], inf) + midiOffset, + + \fxOrder, Pn(Pshuf([1, 1, [1, 2], [1, 3]])), + \lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }, + \amp, 0.2, + + \decayTime, Pfunc { |e| rrand(0.3, 0.8) / (e.fxOrder.asArray.includes(2).if { 10 }{ 1 }) }, + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, 2, + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \echo, + \echoDelta, Pseq((1..5)/50, inf), + \decayTime, Pwhite(0.3, 1.8), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \wah, + \mix, 0.5, + \resLo, 400, + \reHi, 2000, + \cutOffMoveFreq, 10, + \cleanupDelay, 0.01 + ] +) }; + +// master TempoClock + +t = TempoClock.new; + + +// array for individual tempo factors + +~tempo = [1, 1, 2]/2; + +// start first player on quant grid, +// it refers to the tempo factor ~tempo[0] +// note that quant refers to the tempo of the TempoClock + +x = g.(-24, ~tempo, 0).play(t, quant: 1); +) + +// start other players on quant grid + +y = g.(0, ~tempo, 1).play(t, quant: 1); + +z = g.(12, ~tempo, 2).play(t, quant: 1); + + +// change player's individual tempos, +// note that tempo changes do not necessarily happen on quant grid, +// so a rhythmic "phase shift" (which can be nice) might happen + +~tempo[2] = 2/3 + +~tempo[1] = 1/3 + +~tempo[0] = 1 + + +// set general tempo + +t.tempo = 3/4 + + +// stop players individually + +x.stop; + +y.stop; + +z.stop; + + +// stop clock + +t.stop; +:: + + +anchor::Ex. 6:: +section::Ex. 6: Further external routing + + +This means the use of buses, which are not internally used by PbindFx's event Function. As shown in link::#Ex. 2d#Ex.2d:: it can be useful to route PbindFx's out to an external reverb. Vice versa data can be read from external buses, from source synths as well as from fx synths. + +Audio routing benefits from the possibility to pass groups to PbindFx. Then all temporary groups, generated during playing the PbindFx, +are enclosed by it and node order can be clearly defined. For audio bus routing better use In.ar than mapping with asMap, that way matching of ins and outs of fx chains is checked and it avoids issues with using asMap and stopping the external source (occuring at least in SC 3.6.6). + +Spat SynthDef from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +anchor::Ex. 6a:: +subsection::Ex. 6a: Source synth reading audio modulation signals from external buses + +code:: +// source synth based on SinOsc and additional ring modulation, +// while reading from an audio bus with no signal, it outputs only the sine + +( +SynthDef(\source_mod, { |out = 0, freq = 400, decayTime = 0.5, + attackTime = 0.005, amp = 0.1, modIn, gate = 1| + var env, sig; + sig = Decay.ar(Impulse.ar(0), decayTime, SinOsc.ar(freq) * (1 + In.ar(modIn))); + env = EnvGen.ar(Env.asr(attackTime, amp, decayTime, \lin), gate, doneAction: 2); + Out.ar(out, sig ! 2 * env) +}).add; +) + + +( +// new group and bus for the modulation signal +g = Group.new; +a = Bus.audio(s, 1); + +// play source with spat effect only +p = PbindFx([ + \instrument, \source_mod, + \dur, 0.25, + \amp, 0.1, + \midinote, Prand([ + Pwhite(80, 90, 1), + Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf), + ], inf), + + \fxOrder, 1, + + \modIn, a, + // to enable this reading the fx chain check must know + \otherBusArgs, [\modIn], + \group, g, + + \decayTime, Pwhite(0.2, 2), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, Prand([1, 2, 10], inf), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ] +); + +q = p.play; +) + + +// while playing PbindFx, play modulation synth to dedicated bus, +// placing before group ensures that all generated synths can read from the bus + +x = { Out.ar(a, SinOsc.ar(500, 0, 0.5)) }.play(target: g, addAction: \addBefore); + +// no modulation again + +x.free; + + +// modulation with other signal + +y = { Out.ar(a, Pulse.ar(700, 0.5, 0.5)) }.play(target: g, addAction: \addBefore); + + +// add a further modulation signal + +z = { Out.ar(a, Formant.ar(700, 1200, mul: 0.5)) }.play(target: g, addAction: \addBefore); + + +// formant modulation only + +y.free; + +// stop it also + +z.free; + + +// stop and PbindFx cleanup + +q.stop; + + +// cleanup of other resources, free bus and group + +( +g.free; +a.free; +) +:: + +anchor::Ex. 6b:: +subsection::Ex. 6b: Fx synths reading audio modulation signals from external buses + +Spat SynthDef from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// simple sine source synth +SynthDef(\source_sine, { |out = 0, freq = 400, decayTime = 0.5, + attackTime = 0.005, amp = 0.1, gate = 1| + var env, sig; + sig = Decay.ar(Impulse.ar(0), decayTime, SinOsc.ar(freq)); + env = EnvGen.ar(Env.asr(attackTime, amp, decayTime, \lin), gate, doneAction: 2); + Out.ar(out, sig ! 2 * env) +}).add; + +// ring modulation fx synth, expecting to read the modulation signal from a bus +SynthDef(\ring, { |out, in, modIn, amp = 2, mix = 0.5| + var sig, inSig = In.ar(in, 2); + sig = inSig * In.ar(modIn, 1); + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; +) + +( +// new group and buses for the modulation signal +g = Group.new; + +a = Bus.audio(s, 1); +b = Bus.audio(s, 1); + +// two ring modulation fxs +p = PbindFx([ + \instrument, \source_sine, + \dur, 0.25, + \amp, 0.12, + \midinote, Prand([ + Pwhite(80, 90, 1), + Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf), + ], inf), + + \group, g, + + \fxOrder, Pn(Pshuf([1, [1, 2], [1, 3]])), + \decayTime, Pwhite(0.2, 2), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, Prand([1, 2, 10], inf), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \ring, + // need the index explicitely here + \modIn, a.index, + // to enable this reading the fx chain check must know + \otherBusArgs, [\modIn], + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \ring, + \modIn, b.index, + // to enable this reading the fx chain check must know + \otherBusArgs, [\modIn], + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ] +); + +q = p.play; +) + + +// now live change of modulation as in Ex. 6a, but this applies only to one of three events + +x = { Out.ar(a, SinOsc.ar(500, 0, 0.5)) }.play(target: g, addAction: \addBefore); + + +// also modulate events where fx reads from b + +y = { Out.ar(b, Pulse.ar(700, 0.5, 0.5)) }.play(target: g, addAction: \addBefore); + + +// add a further modulation signal + +z = { Out.ar(a, Formant.ar(700, 1200, mul: 0.5)) }.play(target: g, addAction: \addBefore); + + +// remove two others + +x.free; + +y.free; + + +// stop it also + +z.free; + + +// stop and PbindFx cleanup + +q.stop; + + +// cleanup of other resources, free buses and group + +( +g.free; +a.free; +b.free; +) + + +:: + + +anchor::Ex. 6c:: +subsection::Ex. 6c: Controlling effects with LFOs + +Control the amount / mix of an effect continously. + +Spat SynthDef from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: + +( +// bus for LFO control +c = Bus.control(s, 1); + +// play source with spat and wah-wah effect, +// as initially bus value is zero there is no wah at start + +p = PbindFx([ + \instrument, \source, + \dur, 0.25, + \amp, 0.15, + \midinote, Prand([ + Pwhite(80, 90, 1), + Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf), + ], inf), + \fxOrder, [1, 2], + \decayTime, Pwhite(0.2, 2), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, 2, + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \wah, + // maps each fx synth's mix input to the control bus + \mix, c.asMap, + // other params might still be controlled per event + \cutOffMoveFreq, Pseq([5, 20], inf), + \cleanupDelay, 0.01 + ] +); + +q = p.play; +) + +// chime in with wah from 0 (start phase == -pi/2) + +x = { Out.kr(c, SinOsc.kr(0.15, -pi/2).range(0, 0.8)) }.play + + +// stop and free resources + +( +q.stop; +x.free; +c.free; +) +:: + +anchor::Ex. 7:: +section::Ex. 7: Replacement + +Replacement can affect certain key streams only or whole source resp. fx patterns, the latter can be done with using the option of passing source / fx patterns instead of lists. + +anchor::Ex. 7a:: +subsection::Ex. 7a: Replacement restricted to key streams + +This can be done with Pbind + Pdefn or Pbind + PL, a specific possibility with PbindFx is the replacement of \fxOrder. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +~midi = PLseq([60, 60, 60, 62]); +~fxs = PLseq([0, 0, 1, 3]); + +// Pdefn(\midi, Pseq([60, 60, 60, 62], inf)); +// Pdefn(\fxs, Pseq([0, 0, 1, 3], inf)); + +p = PbindFx([ + \instrument, \source, + \dur, 0.25, + \midinote, PL(\midi), // Pdefn(\midi), + \fxOrder, PL(\fxs), // Pdefn(\fxs), + + \lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }, + \amp, 0.15, + + \decayTime, 0.2, + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, 2, + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \echo, + \echoDelta, 0.06, + \decayTime, Pwhite(0.3, 1.8), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \wah, + \cutOffMoveFreq, Pseq([5, 20], inf), + \cleanupDelay, 0.01 + ] +); + +q = p.play; +) + + +// exchange midinote and effect sequencing on the fly + +~midi = PLseq([60, 62, 63]); + +~fxs = PLseq([1, [1, 2], [1, 3], [1, 2, 3]]); + +( +~midi = PLshufn([60, 62, 63]) + + PLshufn([-24, -12, 0]) + + PLshufn([0, [0, 7], [0, 7, 12]]); +) + + +// stop and free resources + +q.stop; + + +:: + + +anchor::Ex. 7b:: +subsection::Ex. 7b: Replacement of source and fx patterns + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// define source and effect patterns + +~midi = PLseq([60, 60, 62, 63]); +~fxs = PLseq([1, [1, 2], [1, 3], [1, 2, 3]]); + +~src = Pbind( + \instrument, \source, + \dur, 0.25, + \midinote, PL(\midi), + \fxOrder, PL(\fxs), + + \lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }, + \amp, 0.15, + + \decayTime, 0.2, + \cleanupDelay, Pkey(\decayTime) +); + + +~fx1 = Pbind( + \fx, \spat, + \freq, 2, + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) +); + +~fx2 = Pbind( + \fx, \echo, + \echoDelta, 0.06, + \decayTime, Pwhite(0.3, 1.8), + \cleanupDelay, Pkey(\decayTime) +); + +~fx3 = Pbind( + \fx, \wah, + \cutOffMoveFreq, Pseq([5, 20], inf), + \cleanupDelay, 0.01 +); + +// pass PLx or Pdef placeholder patterns to PbindFx +p = PbindFx(PL(\src), PL(\fx1), PL(\fx2), PL(\fx3)); + +q = p.play; +) + + + +( +// chorus fx + +SynthDef(\chorus, { |out, in, amp = 1, loDelay = 0.001, hiDelay = 0.005, + maxDelayTime = 0.1, mix = 1| + var sig, inSig = In.ar(in, 2); + inSig = Mix.fill(10, { |i| + DelayL.ar(inSig, maxDelayTime, LFDNoise3.ar(2).range(loDelay, hiDelay)) + }); + sig = inSig * amp / 2; + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; +) + +// replace on the fly, fxOrder sequencing stays the same, but effect changes + +( +~fx3 = Pbind( + \fx, \chorus, + \maxDelayTime, 0.01, + \loDelay, 0.001, + \hiDelay, 0.01, + \cleanupDelay, Pkey(\maxDelayTime) +); +) + +( +// new source SynthDef + +SynthDef(\source_pulse, { |out = 0, freq = 400, decayTime = 0.5, + attackTime = 0.005, amp = 0.1, gate = 1| + var env, sig; + sig = Decay.ar(Impulse.ar(0), decayTime, Pulse.ar(freq)); + env = EnvGen.ar(Env.asr(attackTime, amp, decayTime, \lin), gate, doneAction: 2); + Out.ar(out, sig ! 2 * env) +}).add; +) + +// replace source, building of phrases with rests + +( +~src = Pbind( + \instrument, \source_pulse, + \dur, Pn(Pshuf([1, 1, 2, 2, 2, 2, 2, 4, Rest(5)], inf)) / 8, + \midinote, PL(\midi) + Pn(Pshuf([-24, -19, 0, 19, 24], inf)), + \fxOrder, PL(\fxs), + + \lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }, + \amp, 0.1, + + \decayTime, 0.1, + \cleanupDelay, Pkey(\decayTime) +); +) + +q.stop; +:: + + +anchor::Ex. 7c:: +subsection::Ex. 7c: Replacement with Pbindef + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// source and fxs passed as Pbindefs + +// list of instrument symbols +i = [\src, \fx1, \fx2, \fx3]; + +Pbindef(\src, + \instrument, \source, + \dur, 0.25, + \midinote, Pseq([60, 60, 60, 62], inf), + \fxOrder, Pseq([0, 0, 1, 3], inf), + + \lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }, + \amp, 0.15, + + \decayTime, 0.2, + \cleanupDelay, Pkey(\decayTime) +); + +Pbindef(\fx1, + \fx, \spat, + \freq, 2, + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) +); + +Pbindef(\fx2, + \fx, \echo, + \echoDelta, 0.06, + \decayTime, Pwhite(0.3, 1.8), + \cleanupDelay, Pkey(\decayTime) +); + +Pbindef(\fx3, + \fx, \wah, + \cutOffMoveFreq, Pseq([5, 20], inf), + \cleanupDelay, 0.01 +); + + +p = PbindFx(*i.collect { |x| Pbindef(x) }); + +q = p.play; +) + + +// replace some of source's key streams + +( +Pbindef(\src, + \midinote, Pn(Pshuf([60, 62, 63])) + + Pn(Pshuf([-24, -12, 0])) + + Pn(Pshuf([0, [0, 7], [0, 7, 12]])), + \fxOrder, Pseq([[1, 3], [1, 2, 3]], inf) +) +) + +// replace some of an effect's key streams +// note: this kind of multiple key replacement in Pbindef doesn't work with SC 3.5 + +( +Pbindef(\fx3, + \fx, \wah, + [\resLo, \resHi], Pwrand([[200, 300], [1500, 2000]], [0.8, 0.2], inf), + \cutOffMoveFreq, Pseq([1, 5, 20], inf), + \cleanupDelay, 0.01 +) +) + +q.stop; + + +// before playing again do Pbindef cleanup + +i.do { |x| Pbindef(x).clear }; + + +:: + +anchor::Ex. 8:: +section::Ex. 8: GUI control + +Control of fx params with VarGui, control of fx sequencing by code. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// ensure we are in top envir +currentEnvironment = topEnvironment; + +// pattern for fxOrder sequencing +// we want to exchange this on the fly later on + +~fxs = PLshufn([1, 2, 3, [2, 3], [1, 2, 3]]); + +p = PbindFx([ + \instrument, \source, + \dur, PL(\dur), + \degree, PLshufn(\degree), + // Spec returns a float, so write like this + \octave, Pfunc { rrand(~octaveLo.asInteger, ~octaveHi.asInteger) }, + + // we want to read from code in top envir + \fxOrder, PL(\fxs, envir: topEnvironment), + + \lag, Pfunc { |e| e.fxOrder.asArray.includes(2).if { 0 }{ 0.2 } }, + \amp, PL(\amp), + + \attackTime, PLwhite(\attackTimeLo, \attackTimeHi), + \decayTime, PLwhite(\decayTimeLo, \decayTimeHi), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, PLwhite(\spatFreqLo, \spatFreqHi), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \echo, + \echoDelta, PL(\echoDelta), + \decayTime, PLwhite(\echoDecayLo, \echoDecayHi), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \wah, + \resLo, PLwhite(\wahResLo, \wahResHi), + \cutOffMoveFreq, PL(\wahCutOffMoveFreq), + \mix, PL(\wahMix), + \cleanupDelay, 0.01 + ] +); + +v = VarGui([ + \degree, { |i| [0, 6, \lin, 1, i+2] }!4, + \octaveLo, [3, 6, \lin, 1, 4], + \octaveHi, [3, 6, \lin, 1, 6], + \dur, [0.2, 0.3, \lin, 0, 0.2], + \attackTimeLo, [0.01, 0.1, \lin, 0, 0.02], + \attackTimeHi, [0.01, 0.1, \lin, 0, 0.05], + \amp, [0.0, 0.5, \lin, 0, 0.2], + + \decayTimeLo, [0.1, 0.5, \lin, 0, 0.2], + \decayTimeHi, [0.1, 0.5, \lin, 0, 0.4], + + \spatFreqLo, [0.1, 10, \lin, 0, 0.5], + \spatFreqHi, [0.1, 10, \lin, 0, 2], + + \echoDelta, [0.03, 0.1, \lin, 0, 0.05], + \echoDecayLo, [0.1, 2, \lin, 0, 0.3], + \echoDecayHi, [0.1, 2, \lin, 0, 1.8], + + \wahCutOffMoveFreq, [0, 10, \lin, 0, 5], + \wahResLo, [100, 3000, \exp, 0, 200], + \wahResHi, [100, 3000, \exp, 0, 2000], + \wahMix, [0, 1, \lin, 0, 1] + ], stream: p +).gui( + sliderWidth: 350, + labelWidth: 120, + varColorGroups: (0..20).clumps([12, 2, 3, 4]); +); +) + +// start playing with gui and test params +// change of echoDelta necessarily causes a delay as +// delays for events without echo have to be adapted + + +// change effect order sequencing + +~fxs = PLseq([1, 1, 2, 2, 3, 3]); + +// only spat + wah + +~fxs = [1, 3]; + + +// no effects + +~fxs = 0; + + +// kind of polyrhythm with effects and pitches +// all 4 pitch classes are permanently reordered by PLshufn +// fixed effect sequencing + +~fxs = PLseq([1, [1, 2], [1, 2, 3]]); + + +// stop by gui or explicitely + +v.streams[0].stop; +:: + + +anchor::Ex. 9:: +section::Ex. 9: Using value conversions with fx data + +Effects can produce their own characteristic frequencies. For this it can be practical to use Event's value conversion framework. + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// filter bank effect, level of signal very much depends on input frequencies +SynthDef(\klank, { |out, in, freq = 400, add = 7, amp = 1, ringTime = 0.1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = DynKlank.ar(`[freq * [1, add.midiratio], nil, ringTime ! 2], inSig) * amp / 100; + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; +) + +( +p = PbindFx([ + \instrument, \source, + \dur, 0.2, + \amp, Pseq([0.15, 0.1, 0.1], inf), + \midinote, Pn(Pshuf([36, 36, 48, 48, 60, 65, 67])) + + Pseq([Pn(0, 40), Pn(7, 10), Pn(-5, 10)], inf) + + Pn(Pshuf([0, 0, 0, [0, 7], [0, 9], [0, 14]])), + \decayTime, Pwhite(0.8, 1.5), + \fxOrder, Pn(Pshuf([1, [1, 2], [1, 2]])), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, Prand([1, 2, 3] / 5, inf), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \klank, + \octave, Pwhite(5, 8), + // passing notes instead of frequencies is more pleasant here + // resulting signal is louder if pitches are near overtones of source + \note, Pn(Pshuf([0, 4, 5, 7])), + \add, Prand([7, 12], inf), + \decayTime, 0.1, + \ringTime, Pwhite(0.1, 0.3), + \mix, 0.7, + \cleanupDelay, Pkey(\ringTime) + ] +); + +q = p.play; +) + +q.stop; + +:: + + +section::Ex. 10: Parallel effects and arbitrary effect graphs + +anchor::Ex. 10a:: +subsection::Ex. 10a: Parallel effects + +Here source is routed to echo #1 and echo #2 in parallel, echo #1 (fx index 2) is a "classical" echo whereas echo #2 (fx index 3), due to short echoDelta, results in an additional frequency. The output of echo #2 is routed to a wah-wah, echo #1 directly to out. + + +image::attachments/PbindFx/PbindFx_graph_4c.png:: + +SynthDefs from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +p = PbindFx([ + \instrument, \source, + \dur, Pseq([Pn(0.2, { rrand(8, 12) }), Pwhite(2.0, 4.0, 1)], inf), + \amp, 0.3, + \midinote, Prand([ + Pwhite(80, 90, 1), + Prand([60, 67, 70, 73]) + Prand([0, -12.3, -23.7], inf), + ], inf), + + \fxOrder, `(0: 1, 1: [2, 3], 3: 4), + // compare with this version, where echo #1 is less present, as it also goes to wah + // \fxOrder, `(0: 1, 1: [2, 3], 3: 4, 2: 4), + + \decayTime, 0.1, + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \spat, + \freq, Prand([0.1, 0.8], inf), + \maxDelayTime, 0.001, + \cleanupDelay, 0.1 + ],[ + \fx, \echo, + \echoDelta, 0.1, + \decayTime, 3, + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \echo, + \echoDelta, Pwhite(0.01, 0.05), + \decayTime, 5, + \amp, 0.5, + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \wah, + \mix, 0.7, + \cutOffMoveFreq, Pseq([1, 2, 5, 10], inf), + \cleanupDelay, 0.05 + ] +); + +q = p.play; +) + +q.stop; +:: + + +anchor::Ex. 10b:: +subsection::Ex. 10b: Modulation graphs + +A generalized modulating effect node has two ins: carrier and modulator. Fx convention of PbindFx demands one single In ugen per fx synth, but two ins can simply be handled by a 2-channel In ugen and hard-panned input signals. + +Spat SynthDef from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + +code:: +( +// sine source +SynthDef(\sine_adsrFixed, { |out = 0, freq = 400, decayTime = 0.5, + att = 0.005, dec = 0.01, sus = 0.2, rel = 0.3, susLevel = 0.5, amp = 0.1| + var env, sig = SinOsc.ar(freq); + env = EnvGen.kr(Env([0, 1, susLevel, susLevel, 0], [att, dec, sus, rel]), doneAction: 2); + Out.ar(out, sig ! 2 * env * amp) +}).add; + +// amplitude modulation synth +SynthDef(\ampMod, { |out, in, dev = 1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = inSig[0] * (inSig[1] * dev + DC.ar(1)) * amp; + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + +// phase modulation synth +SynthDef(\phaseMod, { |out, in, maxDelay = 0.1, dev = 1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = DelayC.ar(inSig[0], maxDelay, maxDelay * dev * inSig[1], amp); + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + +// modulator synths, no Ins + +SynthDef(\sineM, { |out, in, freq = 100| + Out.ar(out, [0, SinOsc.ar(freq)]); +}).add; + +SynthDef(\pulseM, { |out, in, freq = 100, width = 0.5| + Out.ar(out, [0, Pulse.ar(freq, width, 2)]); +}).add; + +SynthDef(\sawM, { |out, in, freq = 100| + Out.ar(out, [0, Saw.ar(freq)]); +}).add; +) + +// blend of AM events + +( +p = PbindFx([ + \instrument, \sine_adsrFixed, + \dur, 1, + \susLevel, 1, + \att, 5, + \sus, 0, + \rel, 5, + \amp, 0.03, + \midinote, Pwhite(40, 80), + \fxOrder, `(0: 1, 3: 1, 1: 2), + + \decayTime, 1, + \cleanupDelay, 12 + ],[ + \fx, \ampMod, + \dev, Pwhite(0.1, 0.6) + ],[ + \fx, \spat, + \freq, Pwhite(0.2, 2), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ],[ + \fx, \pulseM, + \freq, Pwhite(200, 1000) + ] +); + +q = p.play; +) + +q.stop; + +:: + +anchor::Ex. 10c:: +subsection::Ex. 10c: Modulation graphs, changed per event + +SynthDefs from Ex. 10b, spat SynthDef from link::#Ex. 1a#Ex.1a::, see also extended server resources defined there. + + +code:: +// fx graphs corresponding to fxOrder `(0:1, 4:1, 1:6) and `(0:1, 5:1, 1:6), src = \sine_adsrFixed: +:: + +image::attachments/PbindFx/PbindFx_graph_4a.png:: + +code:: +// fx graph corresponding to fxOrder `(0:2, 3:2, 2:6), src = \sine_adsrFixed: +:: + +image::attachments/PbindFx/PbindFx_graph_4b.png:: + +code:: + +( +p = PbindFx([ + \instrument, \sine_adsrFixed, + \dur, 0.3, + \susLevel, 1, + \att, 0.01, + \sus, 0.15, + \rel, Pwhite(0.3, 1.2), + \amp, 0.05, + + \midinote, Pwhite(30, 60) + Prand([0, [0, -12.5]], 200), + + // changes between amplitude (pulse and saw) and phase modulation (sine) + + \fxOrder, Pn(Pshuf([ + `(0:1, 4:1, 1:6), + `(0:1, 5:1, 1:6), + `(0:2, 3:2, 2:6) + ])), + + // equivalent: + // the source stream returns pairs, where the first number indicates + // the modulation type and the second number the modulator, + // the collect function packs the data into the right format of a ref'd Event. + + // \fxOrder, Pn(Pshuf([ [1, 4], [1, 5], [2, 3] ])) + // .collect { |x| ().putPairs([0, x[0], x[1], x[0], x[0], 6]).asRef }, + + \decayTime, 2, + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \ampMod, + \dev, Pwhite(0.1, 0.5) + ],[ + \fx, \phaseMod, + \dev, Pwhite(0.03, 0.05) + ],[ + \fx, \sineM, + \freq, Pwhite(150, 700) + ],[ + \fx, \sawM, + \freq, Pwhite(150, 700) + ],[ + \fx, \pulseM, + \freq, Pwhite(150, 700) + ],[ + \fx, \spat, + \freq, Pwhite(0.1, 1), + \maxDelayTime, 0.005, + \cleanupDelay, Pkey(\maxDelayTime) + ] +); + +q = p.play; +) + +q.stop; + +:: + + + diff --git a/HelpSource/Classes/PlaceAll.schelp b/HelpSource/Classes/PlaceAll.schelp new file mode 100644 index 0000000..c518284 --- /dev/null +++ b/HelpSource/Classes/PlaceAll.schelp @@ -0,0 +1,85 @@ +CLASS::PlaceAll +summary::Arbitrarily nested embedding of subarrays +categories::Libraries>miSCellaneous>Other patterns +related:: Overviews/miSCellaneous, Classes/Place, Classes/Ppatlace + + +DESCRIPTION:: + + +PlaceAll is integrating Ppatlace and Place (taking items as well as Patterns) and allows an arbitrary depth of nesting arrays. + + +CLASSMETHODS:: + +method::new + +Creates a new PlaceAll object. + +argument::list +Array which may contain subarrays. Leaves of the array tree may be Patterns, Streams or other Items to be embedded. + +argument::repeats +Number of list loops. Defaults to 1. + +argument::offset +List index offset. Defaults to 0. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +p = Pbind( + \midinote, PlaceAll([[60, 61], 70, [80, [81, 81.5], 82]], inf), + \dur, 0.2 +); + +x = p.play; +) + +x.stop; + + +// to distinguish subarrays from arrays to be taken as output use Refs, +// as wrapping in another array wouldn't do + +( +p = Pbind( + \midinote, PlaceAll([[60, 61], 70, [80, `[81, 81.5], 82]], inf), + \dur, 0.2 +); + +x = p.play; +) + + +x.stop; + + +// Items may also be Patterns or Streams + +( +p = Pbind( + \midinote, PlaceAll([[60, 61], 70, [80, Pwhite(84.0, 89), 82]], inf), + \dur, 0.2 +); + +x = p.play; +) + + +x.stop; + + + +:: + + \ No newline at end of file diff --git a/HelpSource/Classes/Platform.ext.schelp b/HelpSource/Classes/Platform.ext.schelp new file mode 100755 index 0000000..a7d773f --- /dev/null +++ b/HelpSource/Classes/Platform.ext.schelp @@ -0,0 +1,12 @@ + +CLASSMETHODS:: + +method:: miSCellaneousDirs + +Returns an Array of Strings, representing found directories of miSCellaneous lib. The method is searching at places of the most likely installation scenarios: +user and system extension as well as quark extension directories. + +argument::withWarnings +Determines if warnings should be posted in case of no or more than one matches. + + diff --git a/HelpSource/Classes/PmonoPar.schelp b/HelpSource/Classes/PmonoPar.schelp new file mode 100644 index 0000000..84973ec --- /dev/null +++ b/HelpSource/Classes/PmonoPar.schelp @@ -0,0 +1,156 @@ +CLASS:: PmonoPar +summary:: monophonic event pattern for an arbitrary number of timed setting streams +categories::Libraries>miSCellaneous>Other patterns +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Pmono, Classes/PpolyPar, Classes/PbindFx + + +DESCRIPTION:: + +This is similar to Pmono, but allows an arbitrary number of differently timed setting streams in parallel. + +strong::History::: PmonoPar and PpolyPar grew out of discussions on sc-users list, based on an example by Jonatan Liljedahl. Thanks to him, Ron Kuivila, user Monsieur and others for their comments on this – I then suggested classes PsetGroup and PsetFxGroup, which internally used Pgroup. Meanwhile I reworked the implementation, but it's still based on groups. I renamed PsetGroup to PmonoPar – as this makes the functionality more clear – and PsetFxGroup to PpolyPar, as it can be used with or without effect synths, the crucial point is the setting of parallel streams. + +CLASSMETHODS:: + +method::new + +Creates a new PmonoPar object. + +argument::setPatternPairs +SequenceableCollection of SequenceableCollections containing key/value pairs. +Each of the inner collections represents the data of one synth setting stream. +Per convention key/value pairs written after a pair with \dur will cause setting, pairs before will not. +If keys \midinote, \note or \degree are occuring after \dur, they will be converted to a frequency value, +which will be used for setting the arg 'freq'. + +argument::defname +Symbol or String. Name of the SynthDef to be used for the synth being set. +Defaults to \default. + +argument::offset +Number. Offset to be taken for time-shifting synth init and streams. Defaults to 1e-6. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + +anchor::Ex.1a:: +subsection::Ex.1a: PmonoPar with differently timed streams + +code:: + +// per convention keys after \dur are the ones to be set +// playing ends after end of last stream + +( +p = PmonoPar([ + [ + \dur, 1.0, + \pan, Pser([-0.9, 0, 0.9], 8) + ],[ + \dur, 0.4, + \freq, Pexprand(300, 1000, 24) + ],[ + \dur, 0.2, + \amp, Pseq([0.05, 0.3], 30) + ] +]).trace.play; +) +:: + + +anchor::Ex.1b:: +subsection::Ex.1b: Passing values by using the value conversion framework + + +code:: + +// if \degree, \note or \midinote are occuring after \dur, +// a frequency value will be calculated according to Event's usual conversion framework +// and used for setting the arg 'freq'. + +( +p = PmonoPar([ + [ + \dur, 1.0, + \pan, Pser([-0.9, 0, 0.9], 8) + ],[ + \dur, 0.4, + \midinote, Pseq((70..75), 7) + ],[ + \dur, 0.2, + \amp, Pseq([0.05, 0.3], 30) + ] +]).trace.play; +) + +:: + + +anchor::Ex.2:: +subsection::Ex.2: Data sharing between streams of PmonoPar + +code:: + +// define two Pbinds of same length, play first + +// data sharing with rhythms of coinciding entry points is sure as streams are time-shifted + +( +p = PmonoPar([ + [ + \dur, Prand([1, 1, 2]/3, inf).collect(~dur = _).trace, // or: .collect { |x| ~dur = x } .trace + \amp, Pseq([0.2, 0.05], 15).trace + ],[ + \dur, Pfunc { ~dur / 2 }, + \midinote, Pshuf((60..85)) + ] +]).trace.play; +) + +:: + + +anchor::Ex.3:: +subsection::Ex.3: Data sharing between streams of parallel PmonoPars + +code:: + +// data sharing between streams of same PmonoPar and streams of second PmonoPar, +// use of Ptpar ensures that first stream of second PmonoPar comes after first stream of first PmonoPar, +// consider also PpolyPar for such type of usage + +( +p = PmonoPar([ + [ + \dur, Prand([1, 1, 2]/3, 40).collect(~dur = _), + \amp, Pseq([0.3, 0.1], 15) + ],[ + \dur, Pfunc { ~dur / 2 }, + \midinote, Pshuf((50..70)) + ] +]); + +q = PmonoPar([ + [ + \dur, Pfunc { ~dur }, + \amp, Pseq([0.3, 0.1], 15) + ],[ + \dur, Pfunc { ~dur / 3 }, + \midinote, Pshuf((75..95), 2) + ] +]); + +r = Ptpar([0, p, 1e-5, q]).trace.play +) +:: + + diff --git a/HelpSource/Classes/PpolyPar.schelp b/HelpSource/Classes/PpolyPar.schelp new file mode 100644 index 0000000..8ec525e --- /dev/null +++ b/HelpSource/Classes/PpolyPar.schelp @@ -0,0 +1,500 @@ +CLASS:: PpolyPar +summary:: polyphonic event pattern for an arbitrary number of timed setting streams +categories::Libraries>miSCellaneous>Other patterns +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Pmono, Classes/PmonoPar, Classes/PbindFx + + +DESCRIPTION:: + +This is similar to PmonoPar and allows an arbitrary number of monophonic streams as well as an arbitrary number of differently timed setting streams applied to them in parallel. Each setting action can affect an arbitrary combination of running synths. PpolyPar can be used for polyphonic sources alone as well as in combination with effects. + +strong::History::: PmonoPar and PpolyPar grew out of discussions on sc-users list, based on an example by Jonatan Liljedahl. Thanks to him, Ron Kuivila, user Monsieur and others for their comments on this – I then suggested classes PsetGroup and PsetFxGroup, which internally used Pgroup. Meanwhile I reworked the implementation, but it's still based on groups. I renamed PsetGroup to PmonoPar – as this makes the functionality more clear – and PsetFxGroup to PpolyPar, as it can be used with or without effect synths, the crucial point is the setting of parallel streams. + +CLASSMETHODS:: + +method::new + +Creates a new PpolyPar object. + +argument::setPatternPairs +SequenceableCollection of SequenceableCollections containing key/value pairs. +Each of the inner collections represents the data of one synth setting stream. +Per convention key/value pairs written after a pair with \dur will cause setting, pairs before will not. +If keys \midinote, \note or \degree are occuring after \dur, they will be converted to a frequency value, which will be used for setting the arg 'freq'. + +Also per convention, if the number of setting streams (the size of strong::setPatternPairs::) equals the number +of synths (the size of strong::defNames::), the settings of stream i will automatically affect synth i. +If sizes are unequal, each collection of key/value pairs needs a pair with key strong::\synths:: and a value. +This value can be an Integer, meaning synth i, or a SequenceableCollection of Integers, in which case all of those +synths will be affected by the setting. The value can also be a Pattern, so that synths to be set +might change from event to event (see examples). + +argument::defNames +SequenceableCollection of Symbols or Strings. Names of the SynthDefs to be used for the synths being set. +Defaults to #[\default]. + +argument::order +SequenceableCollection of Integers indicating the order of nodes passed to strong::defNames::. +Defaults to nil, in that case an order 0, ... , (defNames.size - 1) is assumed, i.e. synth i comes before synth i+1. + +argument::offset +Number. Offset to be taken for time-shifting synth inits and streams. Defaults to 1e-6. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + +anchor::Ex.1a:: +subsection::Ex.1a: PpolyPar with different instruments + +code:: + +// basic SynthDefs, EnvGate invents a gate arg which is necessary for release + +( +SynthDef(\saw, { |freq = 400, freqlag = 0.0, amp = 0.1, amplag = 0.01| + Out.ar(0, Saw.ar(Lag.kr(freq, freqlag), VarLag.kr(amp, amplag, warp: 1)) ! 2 * EnvGate()); +}).add; + +SynthDef(\pulse, { |freq = 400, freqlag = 0.0, amp = 0.1, amplag = 0.05| + Out.ar(0, Pulse.ar(Lag.kr(freq, freqlag), mul: VarLag.kr(amp, amplag, warp: 1)) ! 2 * EnvGate()); +}).add; + +SynthDef(\sine, { |freq = 400, freqlag = 0.0, amp = 0.1, amplag = 0.05| + Out.ar(0, SinOsc.ar(Lag.kr(freq, freqlag), 0, mul: VarLag.kr(amp, amplag, warp: 1)) ! 2 * EnvGate()); +}).add; +) + +// simple usage, each stream is setting the corresponding instrument + +// per convention keys after \dur are the ones to be set +// note that you'd need \freq (the SynthDef arg) + +( +p = PpolyPar([[ + \dur, 1/6, + \amp, Pwrand([0, 0.07], [1, 7]/8, inf), + \midinote, Pshuf((30..68), inf) + ],[ + \dur, Prand([1, 1, 2]/4, inf), + \amp, Pwrand([0, 0.07, 0.1], [0.3, 0.3, 0.4], inf), + \freqlag, 0.2, + \amplag, Prand([0.2, 0.4], inf), + \midinote, Pbrown(70, 90, 5) + ]], + [\saw, \pulse] +).play; +) + +p.stop; +:: + + +anchor::Ex.1b:: +subsection::Ex.1b: PpolyPar with different instruments and overlapping settings + + +code:: + +// SynthDefs from Ex. 1a + +( +// Function to create midinote patterns in different ranges + +r = { |add = 0| Pstutter(5, Pwhite(60, 70) + add) }; + +// each setting stream affects two synths: those of corresponding indices of \synths, +// but only with one value: the Integer polled from the Pstutter stream + +p = PpolyPar([[ + \synths, [0, 1], + \dur, 1/6, + \midinote, r.(), + \amp, 0.04 + ],[ + \synths, [0, 2], + \dur, 1/4, + \midinote, r.(10), + \amp, 0.03 + ],[ + \synths, [1, 2], + \dur, 1/3, + \midinote, r.(20), + \amp, 0.05 + ]], + [\saw, \pulse, \sine] +).play +) + +p.stop; +:: + + +anchor::Ex.1c:: +subsection::Ex.1c: PpolyPar with different instruments, overlapping settings with array dispatch + + +code:: + +// SynthDefs from Ex. 1a + +( +// Similar to Ex. 1b, but the midinote pattern will cause the stream to generate arrays of two elements, +// they will be distributed to the indicated synths + +r = { |add = 0, int = 7| Pstutter(5, Pwhite(60, 65) + [0, int] + add) }; + +// each setting streams affects two synths: those of corresponding indices of \synths + +p = PpolyPar([[ + \synths, [0, 1], + \dur, 1/6, + \midinote, r.(0, 5), + \amp, 0.04 + ],[ + \synths, [0, 2], + \dur, 1/4, + \midinote, r.(10, 6), + \amp, 0.03 + ],[ + \synths, [1, 2], + \dur, 1/3, + \midinote, r.(20, 7), + \amp, 0.05 + ]], + [\saw, \pulse, \sine] +).play +) + +p.stop; + +:: + + +anchor::Ex.2a:: +subsection::Ex.2a: PpolyPar with one effect synth + +code:: + +( +// With t_gate a percussive envelope will be triggered, +// so articulation can be achieved within a monophonic stream. +// This is similar to Pbind, though only if envelopes are shorter than entry time differences. + +SynthDef(\test, { |out = 0, freq = 440, att = 0.01, rel = 0.1, amp = 0.1, t_gate = 1| + var sig = Saw.ar(freq, amp), delayedSig; + sig = sig!2 * EnvGen.ar(Env.perc(att, rel), t_gate); + // add some spatial variance by LFO on delaytime + delayedSig = DelayL.ar(sig, delaytime: { LFDNoise3.kr(0.5).range(0.005, 0.02) } ! 2); + Out.ar(out, delayedSig * EnvGate()) +}).add; + +// Effect synthdefs with in and out bus, +// both get an EnvGate which introduces a gate arg for proper release, +// one could also add a gate arg and an EnvGen using it. + +// wet/dry-relation is fixed, considering the example with fx chain a bypass arg is introduced + +SynthDef(\echo, { |out = 0, in, maxdtime = 0.2, dtime = 0.2, decay = 3, amp = 0.5, bypass = 0| + var sig, insig; + insig = In.ar(in, 2); + sig = CombL.ar(insig, maxdtime, dtime, decay, amp, add: insig) * EnvGate(); + Out.ar(out, bypass * insig + ((1 - bypass) * sig)); +}).add; + + +SynthDef(\wah, { |out = 0, in, freqLo = 200, freqHi = 5000, modFreq = 10, amp = 0.7, bypass = 0| + var sig, insig; + insig = In.ar(in, 2); + sig = RLPF.ar( + insig, + LinExp.kr(LFDNoise1.kr(modFreq), -1, 1, freqLo, freqHi), + 0.1, + amp, + insig * 0.3 + ).softclip * 0.8 * EnvGate(); + Out.ar(out, bypass * insig + ((1 - bypass) * sig)); +}).add; +) + + +// Whereas node order is done by PpolyPar, bus handling is the user's responsibility, +// it looks more flexible to me to define buses separately. + +// one fx + +b = Bus.audio(s, 2); + +( +p = PpolyPar([[ + \dur, 0.5, + \amp, 0.2, + \out, b, + \t_gate, 1, + \midinote, Pwhite(50, 100) + ],[ + // dur = inf causes just a running fx synth, none of its args is set by a stream + \dur, inf, + \in, b, + \dtime, 0.1, + \decay, 3 + ]], + [\test, \echo] +).play +) + +p.stop; + +b.free; + +:: + + +anchor::Ex.2b:: +subsection::Ex.2b: PpolyPar with more effect synths + +code:: + +// SynthDefs from Ex. 2a + +( +b = Bus.audio(s, 2); +c = Bus.audio(s, 2); +) + + +// still none of the effects is set by a stream + +( +p = PpolyPar([[ + // values before \dur are not sent to server, so do this work here: + // echo (out b) is coupled with short release time + // wah (out c) is coupled with longer release time + \data, Prand([[b, 0.1], [c, 0.5]], inf), + \dur, Prand([1, 1, 2]/5, inf), + \amp, 0.3, + // data dispatch from above, these values will be sent + \rel, Pkey(\data).collect(_[1]), + \out, Pkey(\data).collect(_[0]), + \t_gate, 1, + \midinote, Pwhite(50, 100) + ],[ + \dur, inf, + \in, b, + \out, 0, + \dtime, 0.1, + \decay, 3 + ],[ + \dur, inf, + \in, c, + \amp, 0.3 + ]], + [\test, \echo, \wah] +).play; +) + +p.stop; + +( +b.free; +c.free; +) + + +:: + + + +anchor::Ex.2c:: +subsection::Ex.2c: PpolyPar with more effect synths and streamed setting + +code:: + +// SynthDefs from Ex. 2a + +( +b = Bus.audio(s, 2); +c = Bus.audio(s, 2); +) + + +// effects set by streams + +( +p = PpolyPar([[ + // values before \dur are not sent to server, so do this work here: + // echo (out b) is coupled with short release time + // wah (out c) is coupled with longer release time + \data, Prand([[b, 0.1], [c, 0.5]], inf), + // will get "bars" of length 4/5 + \dur, Pn(Pshuf([1, 1, 2]/5)), + \amp, 0.3, + // data dispatch from above, these values will be sent + \rel, Pkey(\data).collect(_[1]), + \out, Pkey(\data).collect(_[0]), + \t_gate, 1, + \midinote, Pwhite(50, 100) + ],[ + \dur, 4/5, + \in, b, + // change delaytime per "bar", random add avoids repeating echo frequencies + \dtime, Pshuf([1, 2, 4]/40, inf) + (Pwhite(-0.5, 0.5)/40), + \decay, 3 + ],[ + \dur, 4/5, + \in, c, + // change modFreq per "bar" + \modFreq, Pshuf((1..20), inf), + \amp, 0.3 + ]], + [\test, \echo, \wah] +).play +) + +p.stop; + +( +b.free; +c.free; +) + +:: + + +anchor::Ex.2d:: +subsection::Ex.2d: PpolyPar with more effect synths, streamed setting and more than one setting stream per synth + +code:: + +// SynthDefs from Ex. 2a + +( +b = Bus.audio(s, 2); +c = Bus.audio(s, 2); +) + + +// Now we have more setting streams than synths, +// so we need to define which synth is to be set by which stream, +// this done via the \synths key, which must be contained in every collection of pairs. + +( +p = PpolyPar([[ + \synths, 0, + // values before \dur are not sent to server, so do this work here: + // echo (out b) is coupled with short release time + // wah (out c) is coupled with longer release time + \data, Prand([[b, 0.1], [c, 0.5]], inf), + // will get "bars" of length 4/5 + \dur, Pn(Pshuf([1, 1, 2]/5)), + \amp, 0.3, + // data dispatch from above, these values will be sent + \rel, Pkey(\data).collect(_[1]), + \out, Pkey(\data).collect(_[0]), + \t_gate, 1 + ],[ + // stream setting frequency + \synths, 0, + \dur, 1/20, + \midinote, Pshuf((45..90), inf) + ],[ + \synths, 1, + \dur, 4/5, + \in, b, + // change delaytime per "bar", random add avoids repeating echo frequencies + \dtime, Pshuf([1, 2, 4]/40, inf) + (Pwhite(-0.5, 0.5)/40), + \decay, 3 + ],[ + \synths, 2, + \dur, 4/5, + \in, c, + // change modFreq per "bar" + \modFreq, Pshuf((1..20), inf), + \amp, 0.3 + ]], + [\test, \echo, \wah] +).play +) + +p.stop; + +( +b.free; +c.free; +) + +:: + + +anchor::Ex.2e:: +subsection::Ex.2e: PpolyPar with effect chain and streamed fx repatching + +code:: +// SynthDefs from Ex. 2a + +( +b = Bus.audio(s, 2); +c = Bus.audio(s, 2); +) + +// Here effects are chained in order - see buses passed to \in and \out +// Again more setting streams than synths, so the \synths key is needed. + +( +p = PpolyPar([[ + \synths, 0, // source synth + // will get "bars" of length 4/5 + \dur, Pn(Pshuf([1, 1, 2]/5)), + \amp, 0.3, + \rel, 0.2, + \out, b, + \t_gate, 1 + ],[ + // freq rhythm might differ from envelope rhythm (stream 0) + \synths, 0, + \dur, Pn(Pshuf([1, 1, 2]/5)), + \midinote, Pshuf((45..90), inf) + ],[ + \synths, 1, // echo + \dur, 4/5, + \in, b, // gets from source and sends to wah + \out, c, + // change delaytime per "bar", random add avoids repeating echo frequencies + \dtime, Pshuf([1, 2, 4]/20, inf) + (Pwhite(-0.5, 0.5)/20), + \decay, 3 + ],[ + \synths, 2, // wah + \dur, 4/5, + \in, c, // gets from echo, sends to out 0 by default + // change modFreq per "bar" + \modFreq, Pshuf((1..20), inf), + \amp, 0.3 + ],[ + // this stream determines effects in action by setting bypass args of both fx synths + \synths, [1, 2], + \dur, 1/5, + // alternate bypassing of wah synth + \bypass, Pseq([[0, 0], [0, 1]], inf) + ]], + [\test, \echo, \wah] +).play +) + +p.stop; + +( +b.free; +c.free; +) + + +:: + diff --git a/HelpSource/Classes/Pshufn.schelp b/HelpSource/Classes/Pshufn.schelp new file mode 100644 index 0000000..cbb92f1 --- /dev/null +++ b/HelpSource/Classes/Pshufn.schelp @@ -0,0 +1,51 @@ +CLASS::Pshufn +summary::Pshuf with continuing permutations +categories::Libraries>miSCellaneous>Other patterns +related:: Overviews/miSCellaneous, Classes/Pshuf + + +DESCRIPTION:: + + +Variation of Pshuf which scrambles the list with every repeat. + + + +CLASSMETHODS:: + +method::new + +Creates a new Pshufn object. + +argument::list +List to be scrambled. + +argument::repeats +Number of permutations. Defaults to 1. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +p = Pbind( + \midinote, Pshufn((70..73), inf), + \dur, 0.2 +); +) + +x = p.play; + +x.stop; + +:: + + \ No newline at end of file diff --git a/HelpSource/Classes/Psieve.schelp b/HelpSource/Classes/Psieve.schelp new file mode 100644 index 0000000..ae98b7e --- /dev/null +++ b/HelpSource/Classes/Psieve.schelp @@ -0,0 +1,19 @@ + +CLASS::Psieve +summary::Abstract superclass of sieve patterns +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Sieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +PSVx sieve patterns inherit from Psieve. Normally you don't have to use Psieve directly. For an introduction and examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + +CLASSMETHODS:: + + +private:: miSC_checkGenList, miSC_checkOffsetGenList + +method::limit + +Getter and setter for Integer limit of all Psieve patterns. You might want to set it globally when working with large numbers. Default to 65536. \ No newline at end of file diff --git a/HelpSource/Classes/PsymNilSafe.schelp b/HelpSource/Classes/PsymNilSafe.schelp new file mode 100644 index 0000000..595fc5d --- /dev/null +++ b/HelpSource/Classes/PsymNilSafe.schelp @@ -0,0 +1,55 @@ +CLASS::PsymNilSafe +summary::Psym variant that avoids hangs if all referenced patterns return nil +categories:: Libraries>miSCellaneous>Other patterns +related:: Overviews/miSCellaneous, Classes/Psym, Tutorials/PLx_and_live_coding_with_Strings + + +DESCRIPTION:: +This adapts an idea of James Harkins' PnNilSafe (ddwPatterns quark) for Psym. If all patterns in the Dictinonary return nil, then Psym's embedInStream can produce an infinite loop, as it never yields. PnNilSafe can't be wrapped around Psym, but the check with logical time can be built into Psym itself. The wrapping into PsymNilSafe can shortly be written by applying link::Classes/Pattern#-symplay:: instead of link::Classes/Pattern#-play::. See link::Tutorials/PLx_and_live_coding_with_Strings:: for more examples. + +CLASSMETHODS:: + +method::new + +Creates a new PsymNilSafe object. strong::pattern:: expects a pattern of Symbols, strong::dict:: the lookup dictionary and strong::defaults:: to the current Environment. strong::maxNull:: is the number of events with delta = 0 after which PsymNilSafe's method 'embedInStream' yields and thus stops a potentially endless loop. strong::maxNull:: defaults to 128. + + +INSTANCEMETHODS:: + +method::maxNull + +Getter and setter for PsymNilSafe's variable maxNull. + + + + +SECTION::Example + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +x = Pbind(\midinote, Pseries(60, 1, 10), \dur, 0.5).asStream; +y = Pbind(\midinote, Pseries(90, -1, 10), \dur, 0.5).asStream; + +z = Pseq([2, 3, 1, 2], 1).asStream; + +~a = Pfuncn({ x.next(()) }, { z.next }); +~b = Pfuncn({ y.next(()) }, { z.next }); + +PsymNilSafe(Pseq("ab", inf)).trace.play; + +// shorter with method 'symplay': +// trace indicates all events with delta = 0 (maxNull = 128) in the post window +// Pseq("ab", inf).trace.symplay + + +// ATTENTION: with Psym the example leads to a SC hang ! +// Psym(Pseq("ab", inf), currentEnvironment).trace.play +) +:: + diff --git a/HelpSource/Classes/SequenceableCollection.ext.schelp b/HelpSource/Classes/SequenceableCollection.ext.schelp new file mode 100755 index 0000000..0cdd524 --- /dev/null +++ b/HelpSource/Classes/SequenceableCollection.ext.schelp @@ -0,0 +1,195 @@ + +INSTANCEMETHODS:: + + +private:: miSC_isGrouping, miSC_isConnected, miSC_groupingFromIndices, miSC_isKeysAndValues, miSC_isPossiblePbindData +private:: miSC_isPossiblePbindArgs, miSC_hasNoReservedKeys, miSC_hasNoReservedKeysInPbindData +private:: miSC_hasNoReservedKeysInPbindArgs, miSC_isPossibleHelpSynthArgs, miSC_isPossibleHelpSynthParArgs, miSC_areControlRateSynthDefs +private:: miSC_isIndexSubset + +private:: miSC_isGrouping, miSC_isConnected, miSC_groupingFromIndices + +private:: miSC_pfuncPbindsFromTuples, miSC_specAsArray, miSC_oddSpecAsArray, miSC_sVarGuiData, miSC_pVarGuiData + +private:: miSC_makeLacePat, miSC_makeLaceList + +private:: miSC_partitionIndex + +private:: miSC_getClutch, miSC_atKey, miSC_makeStartFxBundle, miSC_makeStartZeroBundle, miSC_checkFxOrder, miSC_fxOrderWarnString, miSC_getTopoOrder, miSC_getPredecessors, miSC_getSuccessors, miSC_getFxOrders, miSC_getFxBusData, miSC_collectEvalWithoutKeyPairs + + +private:: miSC_lowestIndexForWhichGreaterEqual, miSC_highestIndexForWhichLessEqual, miSC_checkSymmetricPeriods, miSC_checkCharacteristicPeriod, miSC_streamifySieveItems, miSC_streamifySieveItems, miSC_isQuasiSymmetricRange, miSC_isSymmetricRange, miSC_smallestPeriodLength, miSC_symmetryType + +private:: miSC_getFFTbufSize + +private:: miSC_unifySize + +private:: miSC_Dmultiply + +private:: miSC_Dmultiply2 + + +method:: specPairsDup +Duplicates pairs strong::num:: times. See link::Classes/VarGui#Ex. 5a#Ex.5a:: + +method:: specPairsDupGroups +Returns an index grouping for rearranging a collection that has been duplicated strong::num:: times. See link::Classes/VarGui#Ex. 5a#Ex.5a:: + + + +method:: sVarGui +See link::Tutorials/VarGui_shortcut_builds::. Instantiate a VarGui object for control of Synths derived from a collection of SynthDef names (Symbols / Strings). Options for Synth controls are taken from the corresponding arg Dictionaries. Size of receiver must equal strong::N::, the number of Dictionaries. + +argument::... args +arg Dictionaries, see link::Classes/Symbol#-sVarGui:: for valid arg syntax, with exception of server. Optionally the last item of args may be a Server to send node messages for synth control. Defaults to the default server. + + + + + +method:: pVarGui +See link::Tutorials/VarGui_shortcut_builds::. Instantiate a VarGui object for control of Pbinds / EventStreamPlayers derived from a collection of SynthDef names (Symbols / Strings). Options for controls are taken from the corresponding arg Dictionaries. Size of collection must equal strong::N::, the number of Dictionaries. + +argument::... args +arg Dictionaries, see link::Classes/Symbol#-pVarGui:: for valid arg syntax. + + + + + + +method:: spVarGui +See link::Tutorials/VarGui_shortcut_builds::. Instantiate a VarGui object for control of Pbinds / EventStreamPlayers and Synths derived from SynthDef names (Symbols / Strings). Thus receiver must be of the form strong::[ synthNames, pbindSynthNames ]::, whereby strong::synthNames:: and strong::pbindSynthNames:: may be Symbols / Strings or SequenceableCollections of Symbols / Strings. + +argument::sVarGuiArgDictionaries +arg Dictionary or SequenceableCollection thereof, whose size must be equal to that of strong::synthNames:: (if a collection). See link::Classes/Symbol#-sVarGui:: for valid arg syntax. Defaults to []. + +argument::pVarGuiArgDictionaries +arg Dictionary or SequenceableCollection thereof, whose size must be equal to that of strong::pbindSynthNames:: (if a collection). See link::Classes/Symbol#-pVarGui:: for valid arg syntax. Defaults to []. + +argument::server +Server. The server to send node messages for synth control. Defaults to the default server. + + + + +method:: psVarGui +See link::Tutorials/VarGui_shortcut_builds::. Instantiate a VarGui object for control of Pbinds / EventStreamPlayers and Synths derived from SynthDef names (Symbols / Strings). Thus receiver must be of the form strong::[ pbindSynthNames, synthNames ]::, whereby strong::pbindSynthNames:: and strong::synthNames:: may be Symbols / Strings or SequenceableCollections of Symbols / Strings. + +argument::pVarGuiArgDictionaries +arg Dictionary or SequenceableCollection thereof, whose size must be equal to that of strong::pbindSynthNames:: (if a collection). See link::Classes/Symbol#-pVarGui:: for valid arg syntax. Defaults to []. + +argument::sVarGuiArgDictionaries +arg Dictionary or SequenceableCollection thereof, whose size must be equal to that of strong::synthNames:: (if a collection). See link::Classes/Symbol#-sVarGui:: for valid arg syntax. Defaults to []. + +argument::server +Server. The server to send node messages for synth control. Defaults to the default server. + + + + + +method::ev +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives an Event from an array of key/value-pairs. Values might be functions that refer to prior keys of the array. + +method::on +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives an Event from an array with method link::Classes/SequenceableCollection#-ev:: and plays it using link::Classes/Event#-on::. + +method::pa +See link::Tutorials/Other_event_and_pattern_shortcuts::. Expects basically an array of key/value-pairs ready for an event pattern. Exception: values might be functions that refer to prior keys of the array, thus delivering an shortcut functionality of the common Pfunc { |e| ... } construct (and similar to Pkey). + +method::p +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives a Pbind from an array involving link::Classes/SequenceableCollection#-pa::. + + +method::pm +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives a Pmono with instrument passed as Symbol from an array involving link::Classes/SequenceableCollection#-pa::. + +argument::sym +Symbol. + + +method::pma +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives a PmonoArtic with instrument passed as Symbol from an array involving link::Classes/SequenceableCollection#-pa::. + +argument::sym +Symbol. + + +method::pbf +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives a Pbindef with reference passed as Symbol from an array involving link::Classes/SequenceableCollection#-pa::. + +argument::sym +Symbol. + + + +method::pp +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives a Pbind from an array involving link::Classes/SequenceableCollection#-pa::. Arguments strong::clock::, strong::protoEvent:: and strong::quant:: are used by method link::Classes/Pattern#-play::. + +method::ppm +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives a Pmono with instrument strong::sym:: (a Symbol) from an array involving link::Classes/SequenceableCollection#-pa::. Arguments strong::clock::, strong::protoEvent:: and strong::quant:: are used by method link::Classes/Pattern#-play::. + +method::ppma +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives a PmonoArtic with instrument strong::sym:: (a Symbol) from an array involving link::Classes/SequenceableCollection#-pa::. Arguments strong::clock::, strong::protoEvent:: and strong::quant:: are used by method link::Classes/Pattern#-play::. + + +method::ppma +See link::Tutorials/Other_event_and_pattern_shortcuts::. Derives a Pbindef with reference strong::sym:: (a Symbol) from an array involving link::Classes/SequenceableCollection#-pa::. Arguments strong::clock::, strong::protoEvent:: and strong::quant:: are used by method link::Classes/Pattern#-play::. + + + +method::eventShortcuts + +Applies method to all items of the collection. See also link::Classes/EventShortcuts::, link::Classes/Pattern#-eventShortcuts::, link::Classes/Event#-eventShortcuts:: and link::Tutorials/PLx_and_live_coding_with_Strings:: + + + + +method:: toSieve + +Expects a strictly ascending collection of Integers and converts to a new Sieve. +See link::Tutorials/Sieves_and_Psieve_patterns:: + +argument::fromMode +Determines how to interpret the receiver. Must be Symbol 'points' (or 'p'), or Symbol 'intervals' (or 'i'). +Defaults to 'points'. + +argument::toMode +Determines resulting mode. Must be Symbol 'points' (or 'p'), or Symbol 'intervals' (or 'i'). +Defaults to 'points'. + +argument::addOffset +Integer offset added to the result. Defaults to 0. + +argument::withCheck +Boolean. Determines if receiver should be checked. Defaults to true. +In normal use it's recommended not to change this, as Sieve methods +assume properly ordered lists. It only makes sense in a case where +large collection data, that is known to be ordered, has to be converted. + + + +method:: lcmByGcd + +Expects a SequenceableCollection of Integers and calculates the least common multiple by using the greatest common divisor. +This can avoid problems with large numbers. See link::Tutorials/Sieves_and_Psieve_patterns#4b:: + + +method:: lcmByFactors + +Expects a SequenceableCollection of Integers and calculates the least common multiple by prime factors. +This can avoid problems with large numbers. Returns an array with lcm as first item, an array with prime factors of +lcm as second item and an array of receiver's and all arguments' prime factors. +See link::Tutorials/Sieves_and_Psieve_patterns#4b:: + + +method:: adjustZeroXs +Sets all zero crossing positions of (sound) Buffers in a SequenceableCollection to 0. In most cases this ensures smoother waveforms when using ZeroXBufRd / TZeroXBufRd. See link::Classes/ZeroXBufRd#Éx. 7:: and link::Classes/ZeroXBufWr#Éx. 2::, it works in analogy to ZeroXBufWr's flag adjustZeroXs set to 1. Asynchronous. + +argument::zeroXBuf +SequenceableColletion of Buffers of zero crossings, analysed with ZeroXBufWr. + +argument::action +Action function or SequenceableCollection of such to be performed after the setting messages have been sent. Gets array of zero crossings as argument. + diff --git a/HelpSource/Classes/Set.ext.schelp b/HelpSource/Classes/Set.ext.schelp new file mode 100755 index 0000000..bad73e7 --- /dev/null +++ b/HelpSource/Classes/Set.ext.schelp @@ -0,0 +1,5 @@ + +INSTANCEMETHODS:: + + +private::miSC_copy diff --git a/HelpSource/Classes/Sieve.schelp b/HelpSource/Classes/Sieve.schelp new file mode 100644 index 0000000..bf64909 --- /dev/null +++ b/HelpSource/Classes/Sieve.schelp @@ -0,0 +1,949 @@ +CLASS::Sieve +summary::Container class for sieve lists +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Tutorials/Sieves_and_Psieve_patterns, Classes/Psieve, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + +DESCRIPTION:: +Container for a list of ascending integers as 'points' or 'intervals'. For an introduction and more examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + +CLASSMETHODS:: + +method::newEmpty + +Creates a new empty Sieve object. + + +method::new + +Creates a new Sieve object in mode 'points'. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. + +argument::limit +An integer limit, which is included when reached. If no limit is passed, returned integers might go up to default limit 65536. + + +method::new_i + +Creates a new Sieve object in mode 'intervals'. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. + +argument::limit +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::new_o + +Creates a new Sieve object in mode 'points' with offset. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. + +argument::offset +An Integer. + +argument::limit +An integer limit, which is included when reached. If no limit is passed, returned integers might go up to default limit 65536. + + +method::new_oi + +Creates a new Sieve object in mode 'intervals' with offset. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. + +argument::offset +An Integer. + +argument::limit +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + + +method::union + +Creates a new Sieve object in mode 'points', generated by the union of sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + + +method::union_i + +Creates a new Sieve object in mode 'intervals', generated by the union of sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::union_o + +Creates a new Sieve object in mode 'points' with offsets, generated by the union of sets of integers. + +argument::... data +Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::union_oi + +Creates a new Sieve object in mode 'intervals' with offsets, generated by the union of sets of integers. + +argument::... data +Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::sect + +Creates a new Sieve object in mode 'points', generated by the intersection of sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::sect_i + +Creates a new Sieve object in mode 'intervals', generated by the intersection of sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::sect_o + +Creates a new Sieve object in mode 'points' with offsets, generated by the intersection of sets of integers. + +argument::... data +Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::sect_oi + +Creates a new Sieve object in mode 'intervals' with offsets, generated by the intersection of sets of integers. + +argument::... data +Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::symdif + +Creates a new Sieve object in mode 'points', generated by the symmetrical difference of sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::symdif_i + +Creates a new Sieve object in mode 'intervals', generated by the symmetrical difference of sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::symdif_o + +Creates a new Sieve object in mode 'points' with offsets, generated by the symmetrical difference of sets integers. + +argument::... data +Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::symdif_oi + +Creates a new Sieve object in mode 'intervals' with offsets, generated by the symmetrical difference of sets of integers. + +argument::... data +Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::dif + +Creates a new Sieve object in mode 'points', generated by the difference of sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::dif_i + +Creates a new Sieve object in mode 'intervals', generated by the difference of sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::dif_o + +Creates a new Sieve object in mode 'points' with offsets, generated by the difference of sets of integers. + +argument::... data +Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::dif_oi + +Creates a new Sieve object in mode 'intervals' with offsets, generated by the difference of sets of integers. + +argument::... data +Alternating generators and integer offsets plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + +method::limit + +Get the global limit of Class Sieve. +Set the global limit of Class Sieve to an integer value. + + + +INSTANCEMETHODS:: + +method::union + +Creates a new Sieve object in mode 'points', generated by the union of the receiver and sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::| + +Binary operator for instance method strong::union::. Creates a new Sieve object in mode 'points', generated by the union of the receiver and a generator. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Returned integers might go up to default limit 65536. + + +method::union_i + +Creates a new Sieve object in mode 'intervals', generated by the union of the receiver and sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::|* + +Binary operator for instance method strong::union_i::. Creates a new Sieve object in mode 'intervals', generated by the union of the receiver and a generator. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Integer intervals might be collected up to default summation limit of 65536. + + + +method::union_o + +Creates a new Sieve object in mode 'points' with offsets, generated by the union of the receiver and sets of integers. + +argument::... data +Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + + +method::union_oi + +Creates a new Sieve object in mode 'intervals' with offsets, generated by the union of the receiver and sets of integers. + +argument::... data +Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::sect + +Creates a new Sieve object in mode 'points', generated by the intersection of the receiver and sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::& + +Binary operator for instance method strong::sect::. Creates a new Sieve object in mode 'points', generated by the intersection of the receiver and a generator. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Returned integers might go up to default limit 65536. + + + +method::sect_i + +Creates a new Sieve object in mode 'intervals', generated by the intersection of the receiver and sets of integers. + + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + + +method::&* + +Binary operator for instance method strong::sect_i::. Creates a new Sieve object in mode 'intervals', generated by the intersection of the receiver and a generator. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Integer intervals might be collected up to default summation limit of 65536. + + +method::sect_o + +Creates a new Sieve object in mode 'points' with offsets, generated by the intersection of the receiver and sets of integers. + +argument::... data +Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::sect_oi + +Creates a new Sieve object in mode 'intervals' with offsets, generated by the intersection of the receiver and sets of integers. + +argument::... data +Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::symdif + +Creates a new Sieve object in mode 'points', generated by the symmetrical difference of the receiver and sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::-- + +Binary operator for instance method strong::symdif::. Creates a new Sieve object in mode 'points', generated by the symmetric difference of the receiver and a generator. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Returned integers might go up to default limit 65536. + + +method::symdif_i + +Creates a new Sieve object in mode 'intervals', generated by the symmetrical difference of the receiver and sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + +method::--* + +Binary operator for instance method strong:: symdif_i::. Creates a new Sieve object in mode 'intervals', generated by the symmetric difference of the receiver and a generator. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Integer intervals might be collected up to default summation limit of 65536. + + +method::symdif_o + +Creates a new Sieve object in mode 'points' with offsets, generated by the symmetrical difference of the receiver and sets of integers. + +argument::... data +Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, returned integers might go up to default limit 65536. + + +method::symdif_oi + +Creates a new Sieve object in mode 'intervals' with offsets, generated by the symmetrical difference of the receiver and sets of integers. + +argument::... data +Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals are collected up to default summation limit of 65536. + + + +method::dif + +Creates a new Sieve object in mode 'points', generated by the difference of the receiver and sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + +method::- + +Binary operator for instance method strong::dif::. Creates a new Sieve object in mode 'points', generated by the difference of the receiver and a generator. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Returned integers might go up to default limit 65536. + + + +method::dif_i + +Creates a new Sieve object in mode 'intervals', generated by the difference of the receiver and sets of integers. + +argument::... data +Generators and an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + +method::-* + +Binary operator for instance method strong:: dif_i::. Creates a new Sieve object in mode 'intervals', generated by the difference of the receiver and a generator. + +argument::gen +A generator. Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +Integer intervals might be collected up to default summation limit of 65536. + + +method::dif_o + +Creates a new Sieve object in mode 'points' with offsets, generated by the difference of the receiver and sets of integers. + +argument::... data +Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce zero and its positive multiples. +If no limit is passed, returned integers might go up to default limit 65536. + +method::dif_oi + +Creates a new Sieve object in mode 'intervals' with offsets, generated by the difference of the receiver and sets of integers. + +argument::... data +Alternating integer offsets and generators plus an optional integer limit, wrapped into a Ref, which is included when reached. +Allowed generators: Integers, Streams or Patterns producing intervals or Sieves itself. +Integers and Stream/Pattern output must be strictly positive. +Integers as generators produce constant intervals. +If no limit is passed, integer intervals might be collected up to default summation limit of 65536. + + + +method::plot + +Plot the Sieve's list. + + + +method::size + +The Sieve's list size. + + +method::toPoints + +Convert the Sieve to mode 'points' and set first point to offset. + + +method::toIntervals + +Convert the Sieve to mode 'intervals' and set offset to first point. + + +method::shift + +Shift the Sieve's list by strong::addOffset::. + +argument::addOffset +An Integer. + + +method::>> + +Binary operator for instance method strong::shift::. Shift the Sieve's list by strong::addOffset::. + +argument::addOffset +An Integer. + + +method::shiftTo + +Shift the Sieve's list to strong::targetOffset::. + +argument::targetOffset +An Integer. + + +method::>>! + +Binary operator for instance method strong::shiftTo::. Shift the Sieve's list to strong::targetOffset::. + +argument::targetOffset +An Integer. + + +method::shiftToZero + +Shift the Sieve's list to offset zero. + + +method::weakCopy + +Returns a new Sieve with same list object. + + +method::copy + +Returns a new Sieve with copied list object. + + +method::copyWith + +Takes over mode and offset from receiver and passes an appropriate SequenceableCollection. + +argument::seqCollection +SequenceableCollection. + +argument::withCheck +Boolean. Determines if strong::seqCollection:: is checked according to mode. +Defaults to true. + + +method::copyApplyTo + +Apply operator (Symbol of method or Function) to the Sieve's list and pass the result to a new Sieve with copied mode and offset. + +argument::operator +Symbol of method or Function. + +argument::withCheck +Boolean. Determines if result of the operation is checked according to mode. +Defaults to true. + + +method::== + +Equality check. Sieves of different mode are equal iff the lists resulting from conversion are equal. + +argument::that +Object to compare. + + +method::segmentGreaterEqual + +Returns a new Sieve of mode 'points' with Integers greater than or equal strong::lo::. + +argument::lo +Integer. + + +method::>=! + +Binary operator for strong::segmentGreaterEqual::. + +argument::lo +Integer. + + + +method::segmentGreater + +Returns a new Sieve of mode 'points' with Integers greater than strong::lo::. + +argument::lo +Integer. + + +method::>! + +Binary operator for strong::segmentGreater::. + +argument::lo +Integer. + + +method::segmentLessEqual + +Returns a new Sieve of mode 'points' with Integers less than or equal strong::hi::. + +argument::hi +Integer. + + +method::<=! + +Binary operator for strong::segmentLessEqual::. + +argument::hi +Integer. + + +method::segmentLess + +Returns a new Sieve of mode 'points' with Integers less than strong::hi::. + +argument::hi +Integer. + + +method::=! + +Binary operator for strong::segmentBetweenEqual::. + +argument::bounds +Must be an array with integers lo and hi. + + + +method::segmentBetween + +Returns a new Sieve of mode 'points' with Integers greater than strong::lo:: and less than strong::hi::. +This method is more efficient than applying strong::segmentGreater:: plus strong::segmentLess::. + +argument::lo +Integer. + +argument::hi +Integer. + + + +method::<>! + +Binary operator for strong::segmentBetween::. + +argument::bounds +Must be an array with integers lo and hi. + + + +method::checkSymmetricPeriods + +Checks the list of a Sieve for symmetric periods and returns an array strong::[periods, symmetries, completions]::, +where periods is the clumped list of strong::periods::, strong::symmetries:: a corresponding array of Symbols and +strong::completions:: a corresponding array of Booleans, indicating if periods are complete. +strong::periods:: can contains Symbols strong::'sym'::, strong::'asym'::, strong::'quasisym':: and strong::'identic'::. +See link::Tutorials/Sieves_and_Psieve_patterns#3b:: for a characterisation of these types. +It is assumed that the receiver has a periodic list, changes between periodic and +aperiodic segments are not detected, so also aperiodic prefixes of periodic lists. + + +method::checkCharacteristicPeriod + +Checks the first complete period based on the data returned by strong::checkSymmetricPeriods::. +It returns an array strong::[characteristicPeriod, offset, oddEven, symmetry]::, where +strong::characteristicPeriod:: denotes the first complete period, strong::offset:: the index at which it starts +in the receiver's list, strong::oddEven:: one of the Symbols 'odd' and 'even' and strong::symmetry:: the +symmetry type as in strong::checkSymmetricPeriods::. + + +method::plotCharacteristicPeriod + +Plots the characteristic period returned by strong::checkCharacteristicPeriod::. + + +method::list + +Get the Sieve's list. + + +method::offset + +Get the Sieve's offset. + + +method:: mode + +Get the Sieve's mode. + + + +private:: miSC_setMode, miSC_setOffset, miSC_setList, miSC_sieveOp, miSC_checkOffsetArgs, miSC_checkArgs, miSC_streamifySieveItems + + + +SECTION::Examples + +code:: +// instantiation with 'new' + +Sieve.new(1000) + +Sieve.new(4, 20) + +Sieve.new_i(4, 20) + +Sieve.new_o(4, 1, 20) + +Sieve.new_oi(4, 1, 20) + + + +// instantiation with union +// collecting multiples resp. shifted multiples +// limit must be wrapped into a Ref object + +Sieve.union(4, 7, `20) + +Sieve.union_i(4, 7, `20) + +Sieve.union_o(4, 1, 7, -1, `20) + +Sieve.union_oi(4, 1, 7, -1, `20) + + + +// instantiation with intersection +// collecting least common multiples (lcms) resp. shifted lcms + +Sieve.sect(4, 3, `30) + +Sieve.sect_i(4, 3, `30) + +Sieve.sect_o(4, -1, 3, 0, `30) + +Sieve.sect_oi(4, -1, 3, 0, `30) + + + +// instantiation with symmetrical difference +// equals union without intersection, thus multiples without lcms + +Sieve.symdif(4, 3, `30) + +Sieve.symdif_i(4, 3, `30) + +Sieve.symdif_o(4, -1, 3, 0, `30) + +Sieve.symdif_oi(4, -1, 3, 0, `30) + + +// all take more arguments + +Sieve.union(4, 5, 7, `30) + +Sieve.symdif(4, 5, 7, `30) + + + +// difference is not symmetrical, but subtrahends can be swapped + +Sieve.dif(4, 3, 7, `30) + +Sieve.dif(3, 4, 7, `30) + +Sieve.dif(3, 7, 4, `30) + + + +// corresponding instance methods and its binary operators + +a = Sieve(4, 30); + +b = Sieve(3, 30); + +a.union(b) + +a|b + +a.union_i(b) + +a|*b + + +// here first arg is offset of receiver + +a.union_o(0, b, 100) + +a.union_oi(0, b, 100) + + +a.sect(8) + +a & 8 + +a.sect_i(8) + +a &* 8 + + +a.sect_o(1, b, 5) + +a.sect_oi(1, b, 5) + + +a.symdif(b) + +a -- b + +a.symdif_i(b) + +a --* b + + +a.dif(b) + +a - b + +a.dif_i(b) + +a -* b + +:: + +For conversion, segmentation and transformation examples see link::Tutorials/Sieves_and_Psieve_patterns::. + + + + diff --git a/HelpSource/Classes/SimpleNumber.ext.schelp b/HelpSource/Classes/SimpleNumber.ext.schelp new file mode 100644 index 0000000..8205654 --- /dev/null +++ b/HelpSource/Classes/SimpleNumber.ext.schelp @@ -0,0 +1,5 @@ + +INSTANCEMETHODS:: + + +private:: miSC_specAsArray, miSC_decimalStrings \ No newline at end of file diff --git a/HelpSource/Classes/SkipJack.ext.schelp b/HelpSource/Classes/SkipJack.ext.schelp new file mode 100644 index 0000000..a57bb7f --- /dev/null +++ b/HelpSource/Classes/SkipJack.ext.schelp @@ -0,0 +1,5 @@ + +INSTANCEMETHODS:: + +private:: miSC_cleanup + diff --git a/HelpSource/Classes/SmoothClipQ.schelp b/HelpSource/Classes/SmoothClipQ.schelp new file mode 100644 index 0000000..4939c05 --- /dev/null +++ b/HelpSource/Classes/SmoothClipQ.schelp @@ -0,0 +1,65 @@ +CLASS:: SmoothClipQ +summary:: wave shaping / clipping pseudo ugen using parabolic segments +categories::Libraries>miSCellaneous>WaveFolding +related:: Overviews/miSCellaneous, Tutorials/Smooth_Clipping_and_Folding, Classes/SmoothClipS, Classes/SmoothFoldS, Classes/SmoothFoldQ, Classes/SmoothFoldS2, Classes/SmoothFoldQ2 + + +DESCRIPTION:: + +Wave shaping with parabolic segments at borders. The strong::amount:: of smoothness can be controlled: 0 means a linear transfer function, 1 a full parabolic segment, values inbetween a transfer function which consists of a line and a parabolic segment. For strong::amount:: > 0 the slope of the transfer function equals 0 at the borders. + +CLASSMETHODS:: + +method::ar + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + +argument::amount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if lo = hi and the border case of strong::amount = 1::). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + +method::kr + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + +argument::amount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if lo = hi and the border case of strong::amount = 1::). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// control smoothness + +x = { SmoothClipQ.ar(LFTri.ar(300, 0, 0.25), -0.2, 0.2, MouseX.kr(0, 1)) }.scope + +x.release +:: + diff --git a/HelpSource/Classes/SmoothClipS.schelp b/HelpSource/Classes/SmoothClipS.schelp new file mode 100644 index 0000000..f75b545 --- /dev/null +++ b/HelpSource/Classes/SmoothClipS.schelp @@ -0,0 +1,65 @@ +CLASS:: SmoothClipS +summary:: wave shaping / clipping pseudo ugen using sine segments +categories::Libraries>miSCellaneous>WaveFolding +related:: Overviews/miSCellaneous, Tutorials/Smooth_Clipping_and_Folding, Classes/SmoothClipQ, Classes/SmoothFoldS, Classes/SmoothFoldQ, Classes/SmoothFoldS2, Classes/SmoothFoldQ2 + + +DESCRIPTION:: + +Wave shaping with sine segments at borders. The strong::amount:: of smoothness can be controlled: 0 means a linear transfer function, 1 a full sine segment, values inbetween a transfer function which consists of a line and a sine segment. For strong::amount:: > 0 the slope of the transfer function equals 0 at the borders. + +CLASSMETHODS:: + +method::ar + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + +argument::amount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if lo = hi and the border case of strong::amount = 1::). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + +method::kr + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + +argument::amount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if lo = hi and the border case of strong::amount = 1::). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// control smoothness + +x = { SmoothClipS.ar(LFTri.ar(300, 0, 0.25), -0.2, 0.2, MouseX.kr(0, 1)) }.scope + +x.release +:: + diff --git a/HelpSource/Classes/SmoothFoldQ.schelp b/HelpSource/Classes/SmoothFoldQ.schelp new file mode 100644 index 0000000..436008b --- /dev/null +++ b/HelpSource/Classes/SmoothFoldQ.schelp @@ -0,0 +1,72 @@ +CLASS:: SmoothFoldQ +summary:: wave folding pseudo ugen using parabolic segments +categories::Libraries>miSCellaneous>WaveFolding +related:: Overviews/miSCellaneous, Tutorials/Smooth_Clipping_and_Folding, Classes/SmoothClipS, Classes/SmoothClipQ, Classes/SmoothFoldS, Classes/SmoothFoldS2, Classes/SmoothFoldQ2 + + +DESCRIPTION:: + +Wave folding using SmoothClipQ. Values outside the range strong::lo::-strong::hi:: are folded back, if foldRange > 0. A larger strong::foldRange:: means less folding whereas a smaller strong::foldRange:: causes more folding. The spectral result depends on the source wave form also but in general a smaller strong::foldRange:: causes more energy in the higher spectrum whereas near strong::foldRange:: == 0 the higher part of the spectrum again decreases. Note that, in contrast to classical wave folding, the number of foldings isn't limited here, possibly causing aliasing when heavy folding is forced. + +CLASSMETHODS:: + +method::ar + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + +argument::foldRange +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the ranges near both borders, which are used for folding, defaults to 1. + +argument::smoothAmount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if strong::lo:: = strong::hi:: and the border case of strong::amount:: = 1). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + +method::kr + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + +argument::foldRange +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the ranges near both borders, which are used for folding, defaults to 1. + +argument::smoothAmount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if strong::lo:: = strong::hi:: and the border case of strong::amount:: = 1). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// see scope, MouseX controls foldRange, MouseY smoothAmount + +x = { SmoothFoldQ.ar(SinOsc.ar(90), -0.1, 0.1, MouseX.kr(0.2, 0.8), MouseY.kr(0, 1)) }.scope + +x.release +:: + diff --git a/HelpSource/Classes/SmoothFoldQ2.schelp b/HelpSource/Classes/SmoothFoldQ2.schelp new file mode 100644 index 0000000..a22f43c --- /dev/null +++ b/HelpSource/Classes/SmoothFoldQ2.schelp @@ -0,0 +1,82 @@ +CLASS:: SmoothFoldQ2 +summary:: wave folding pseudo ugen using parabolic segments, two fold ranges +categories::Libraries>miSCellaneous>WaveFolding +related:: Overviews/miSCellaneous, Tutorials/Smooth_Clipping_and_Folding, Classes/SmoothClipS, Classes/SmoothClipQ, Classes/SmoothFoldS, Classes/SmoothFoldQ, Classes/SmoothFoldS2 + + +DESCRIPTION:: + +Wave folding using SmoothClipQ. Values outside the range strong::lo::-strong::hi:: are folded back, if the concerned foldRange > 0. A larger foldRange means less folding whereas a smaller foldRange causes more folding. The spectral result depends on the source wave form also but in general a smaller foldRange causes more energy in the higher spectrum whereas near foldRange == 0 the higher part of the spectrum again decreases. Note that, in contrast to classical wave folding, the number of foldings isn't limited here, possibly causing aliasing when heavy folding is forced. + +CLASSMETHODS:: + +method::ar + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + + +argument::foldRangeLo +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding back values below strong::lo::, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the range near strong::lo::, which is used for folding, defaults to 1. + +argument::foldRangeHi +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding back values above strong::hi::, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the range near strong::hi::, which is used for folding, defaults to 1. + +argument::smoothAmount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if strong::lo:: = strong::hi:: and the border case of strong::amount:: = 1). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + +method::kr + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + + +argument::foldRangeLo +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding back values below strong::lo::, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the range near strong::lo::, which is used for folding, defaults to 1. + +argument::foldRangeHi +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding back values above strong::hi::, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the range near strong::hi::, which is used for folding, defaults to 1. + +argument::smoothAmount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if strong::lo:: = strong::hi:: and the border case of strong::amount:: = 1). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// see scope, MouseX controls foldRangeLo, MouseY foldRangeHi + +x = { SmoothFoldQ2.ar(SinOsc.ar(90), -0.1, 0.1, MouseX.kr(0, 1), MouseY.kr(0, 1)) }.scope + +x.release + +:: + diff --git a/HelpSource/Classes/SmoothFoldS.schelp b/HelpSource/Classes/SmoothFoldS.schelp new file mode 100644 index 0000000..0bf91a4 --- /dev/null +++ b/HelpSource/Classes/SmoothFoldS.schelp @@ -0,0 +1,72 @@ +CLASS:: SmoothFoldS +summary:: wave folding pseudo ugen using sine segments +categories::Libraries>miSCellaneous>WaveFolding +related:: Overviews/miSCellaneous, Tutorials/Smooth_Clipping_and_Folding, Classes/SmoothClipS, Classes/SmoothClipQ, Classes/SmoothFoldQ, Classes/SmoothFoldS2, Classes/SmoothFoldQ2 + + +DESCRIPTION:: + +Wave folding using SmoothClipS. Values outside the range strong::lo::-strong::hi:: are folded back, if foldRange > 0. A larger strong::foldRange:: means less folding whereas a smaller strong::foldRange:: causes more folding. The spectral result depends on the source wave form also but in general a smaller strong::foldRange:: causes more energy in the higher spectrum whereas near strong::foldRange:: == 0 the higher part of the spectrum again decreases. Note that, in contrast to classical wave folding, the number of foldings isn't limited here, possibly causing aliasing when heavy folding is forced. + +CLASSMETHODS:: + +method::ar + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + +argument::foldRange +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the ranges near both borders, which are used for folding, defaults to 1. + +argument::smoothAmount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if strong::lo:: = strong::hi:: and the border case of strong::amount:: = 1). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + +method::kr + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + +argument::foldRange +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the ranges near both borders, which are used for folding, defaults to 1. + +argument::smoothAmount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if strong::lo:: = strong::hi:: and the border case of strong::amount:: = 1). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// see scope, MouseX controls foldRange, MouseY smoothAmount + +x = { SmoothFoldS.ar(SinOsc.ar(90), -0.1, 0.1, MouseX.kr(0.2, 0.8), MouseY.kr(0, 1)) }.scope + +x.release +:: + diff --git a/HelpSource/Classes/SmoothFoldS2.schelp b/HelpSource/Classes/SmoothFoldS2.schelp new file mode 100644 index 0000000..edc0efd --- /dev/null +++ b/HelpSource/Classes/SmoothFoldS2.schelp @@ -0,0 +1,81 @@ +CLASS:: SmoothFoldS2 +summary:: wave folding pseudo ugen using sine segments, two fold ranges +categories::Libraries>miSCellaneous>WaveFolding +related:: Overviews/miSCellaneous, Tutorials/Smooth_Clipping_and_Folding, Classes/SmoothClipS, Classes/SmoothClipQ, Classes/SmoothFoldS, Classes/SmoothFoldQ, Classes/SmoothFoldQ2 + + +DESCRIPTION:: + +Wave folding using SmoothClipS. Values outside the range strong::lo::-strong::hi:: are folded back, if the concerned foldRange > 0. A larger foldRange means less folding whereas a smaller foldRange causes more folding. The spectral result depends on the source wave form also but in general a smaller foldRange causes more energy in the higher spectrum whereas near foldRange == 0 the higher part of the spectrum again decreases. Note that, in contrast to classical wave folding, the number of foldings isn't limited here, possibly causing aliasing when heavy folding is forced. + +CLASSMETHODS:: + +method::ar + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + + +argument::foldRangeLo +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding back values below strong::lo::, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the range near strong::lo::, which is used for folding, defaults to 1. + +argument::foldRangeHi +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding back values above strong::hi::, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the range near strong::hi::, which is used for folding, defaults to 1. + +argument::smoothAmount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if strong::lo:: = strong::hi:: and the border case of strong::amount:: = 1). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + +method::kr + +argument::in +input signal. + +argument::lo +lower limit, defaults to -1. + +argument::hi +upper limit, defaults to 1. + + +argument::foldRangeLo +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding back values below strong::lo::, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the range near strong::lo::, which is used for folding, defaults to 1. + +argument::foldRangeHi +the relative amount of the range (defined by strong::lo:: and strong::hi::) used for folding back values above strong::hi::, should be >= 0 and <= 1. 0 means no folding (just smooth clipping), 1 means that the full range is used for folding, values inbetween determine the range near strong::hi::, which is used for folding, defaults to 1. + +argument::smoothAmount +amount of smoothness, must be >= 0 and <= 1, defaults to 0.5. + +argument::delta +Threshold for avoiding zero divisions (which would happen if strong::lo:: = strong::hi:: and the border case of strong::amount:: = 1). Normally not to be set by the user, except for very small clipping ranges, defaults to 0.00001. + + + +EXAMPLES:: + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// see scope, MouseX controls foldRangeLo, MouseY foldRangeHi + +x = { SmoothFoldS2.ar(SinOsc.ar(90), -0.1, 0.1, MouseX.kr(0, 1), MouseY.kr(0, 1)) }.scope + +x.release + +:: + diff --git a/HelpSource/Classes/Stream.ext.schelp b/HelpSource/Classes/Stream.ext.schelp new file mode 100644 index 0000000..bd3e786 --- /dev/null +++ b/HelpSource/Classes/Stream.ext.schelp @@ -0,0 +1,10 @@ + +INSTANCEMETHODS:: + +method:: asESP +Abbreviation for asEventStreamPlayer + +private:: miSC_repTypeFactor + +private:: miSC_streamifySieveItems + diff --git a/HelpSource/Classes/String.ext.schelp b/HelpSource/Classes/String.ext.schelp new file mode 100644 index 0000000..2531398 --- /dev/null +++ b/HelpSource/Classes/String.ext.schelp @@ -0,0 +1,26 @@ + +INSTANCEMETHODS:: + +private:: miSC_specAsArray, miSC_defNameAsArray + + +method:: pfuncPbinds +See link::Classes/Symbol#-pfuncPbinds::, link::Tutorials/VarGui_shortcut_builds:: + +method:: sVarGuiSpecs +See link::Classes/Symbol#-sVarGuiSpecs::, link::Tutorials/VarGui_shortcut_builds:: + + +method:: pVarGuiSpecs +See link::Classes/Symbol#-pVarGuiSpecs::, link::Tutorials/VarGui_shortcut_builds:: + + + +method:: sVarGui +See link::Classes/Symbol#-sVarGui::, link::Tutorials/VarGui_shortcut_builds:: + + +method:: pVarGui +See link::Classes/Symbol#-pVarGui::, link::Tutorials/VarGui_shortcut_builds:: + + diff --git a/HelpSource/Classes/Symbol.ext.schelp b/HelpSource/Classes/Symbol.ext.schelp new file mode 100644 index 0000000..9600bed --- /dev/null +++ b/HelpSource/Classes/Symbol.ext.schelp @@ -0,0 +1,212 @@ + +INSTANCEMETHODS:: + +private:: miSC_getCtrIndex, miSC_getEnvir + +private:: miSC_specAsArray, miSC_expandCtrs, miSC_expandCtrPairs, miSC_findGlobalControlSpec, miSC_getControlTuples, miSC_controlsToExclude, miSC_sVarGuiSpecsFromTuples, miSC_pVarGuiData + +private:: miSC_checkPbindFxBusMatch + + +method:: sVarGui +See link::Tutorials/VarGui_shortcut_builds::. Instantiate a VarGui object for control of one or more Synth(s) derived from a SynthDef name which may also be given as String. Per default controlspecs are taken from metadata definition, if defined with the SynthDef, or the global dictionary Spec.specs. Controls can be excluded with strong::exclude::, added with strong::ctrBefore:: or strong::ctrAfter:: and replaced with strong::ctrReplace::, this is also the order of operations. + +argument::ctrBefore +a spec pair collection of the form strong::[ key, spec, ...:: strong::]:: where strong::key:: is the symbol of the control arg to be set and strong::spec:: can be a collection of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]:: defining the corresponding ControlSpec, a Symbol / String to look for a ControlSpec in the global dictionary Spec.specs or a collection of specs of this form for an arrayed control. May also be a SimpleNumber for a dummy slider with fixed value. Defaults to nil. Also takes a Function of indices that returns a valid arg. + +argument::ctrReplace +a spec pair collection of a form like strong::ctrBefore::. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::ctrAfter +a spec pair collection of a form like strong::ctrBefore::. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::exclude +a collection of control keys (Symbols) to be excluded. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::metaKey +Symbol. Key to lookup SynthDef metadata. Also takes a Function of indices that returns a Symbol. Defaults to \specs. + +argument::useGlobalSpecs +Boolean. Defines if to consider global ControlSpecs if metadata is not given. Also takes a Function of indices that returns a Boolean. Defaults to true. + +argument::num +Boolean. Integer. Number of Synths to be controlled. Defaults to 1. + +argument::server +Server. Defaults to the default server. + + + + + +method:: pVarGui +See link::Tutorials/VarGui_shortcut_builds::. Instantiate a VarGui object for control of one or more Pbind(s) / EventStreamPlayer(s), derived from a SynthDef name which may also be given as String. One or more Pbind(s) with Pfunc pairs and the corresponding environmental variable controls are generated, controls for duration and legato are added. Per default controlspecs are taken from metadata definition, if defined with the SynthDef, or the global dictionary Spec.specs. Controls can be excluded with strong::exclude::, added with strong::ctrBefore:: or strong::ctrAfter:: and replaced with strong::ctrReplace::; Pbind pairs can be excluded with strong::exclude::, added with strong::pBefore:: or strong::pAfter::: and replaced with strong::pReplace::, this is the order of operations for Synths and Pbind pairs. Note that strong::exclude:: applies to Synth controls and Pbind pairs. If an added Pbind pair with key \degree, \note or \midinote is detected, \freq is excluded automatically from Pbind and controls. Gate control is excluded per default. + +argument::ctrBefore +a spec pair collection of the form strong::[ key, spec, ...:: strong::]:: where strong::key:: is the symbol of the environmental variable to be set and strong::spec:: can be a collection of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]:: defining the corresponding ControlSpec, a Symbol / String to look for a ControlSpec in the global dictionary Spec.specs or a collection of specs of this form for an arrayed control if the environmental variable should have the value of a collection itself. May also be a SimpleNumber for a dummy slider with fixed value. Defaults to nil. Also takes a Function of indices that returns a valid arg. + +argument::ctrReplace +a spec pair collection of a form like strong::ctrBefore::. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::ctrAfter +a spec pair collection of a form like strong::ctrBefore::. Also takes a Function of indices that returns a valid arg. Defaults to nil. + + +argument::durCtr +a controlspec of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]:: or a Symbol / String to look for a ControlSpec in the global dictionary Spec.specs. Also takes a Function of indices that returns a valid arg. Defaults to #[0.05, 3, \exp, 0, 0.2]. + + +argument::legatoCtr +a controlspec of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]:: or a Symbol / String to look for a ControlSpec in the global dictionary Spec.specs. Also takes a Function of indices that returns a valid arg. Defaults to #[0.1, 5, \exp, 0, 0.8]. + +argument::pBefore +a Pbind pair collection of the form strong::[ key, patterns, ...:: strong::]::. Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. + +argument::pReplace +a Pbind pair collection of the form strong::[ key, patterns, ...:: strong::]::. Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. + +argument::pAfter +a Pbind pair collection of the form strong::[ key, patterns, ...:: strong::]::. Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. + +argument::exclude +a control key (Symbol) or a collection of control keys to be excluded from Pbind pairs and controls. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::excludeGate +Boolean. Determines if gate control should be excluded. Also takes a Function of indices that returns a Boolean. Defaults to true. + +argument::clock +TempoClock, nil or collection thereof for playing streams. Also takes a Function of indices that returns a valid arg. Defaults to the default TempoClock. + +argument::quant +Quant, Float, nil or collection thereof used as quant data for playing streams. Also takes a Function of indices that returns a valid arg. Defaults to Quant.default (none). + +argument::metaKey +Symbol. Key to lookup SynthDef metadata. Also takes a Function of indices that returns a Symbol. Defaults to \specs. + +argument::useGlobalSpecs +Boolean. Defines if to consider global ControlSpecs if metadata is not given. Also takes a Function of indices that returns a Boolean. Defaults to true. + +argument::post +Boolean. Determines if to post order of Pbind pairs. Also takes a Function of indices that returns a Boolean. Defaults to false. + +argument::trace +Boolean. Determines if EventStreamPlayer should post Events when playing. Also takes a Function of indices that returns a Boolean. Defaults to false. + +argument::num +Integer. Number of Pbinds / EventStreamPlayers to be controlled. Defaults to 1. + + + + +method:: sVarGuiSpecs +See link::Tutorials/VarGui_shortcut_builds::. Generate control data for one or more Synth(s) derived from a SynthDef name which may also be given as String. Returns a grouped collection, also for num = 1. Data can directly been used as input to VarGui's synthCtr arg. This method can be used for control of Synths derived from more than one SynthDef in one VarGui or combined control of Synths and Pbinds / EventStreamPlayers. + +argument::ctrBefore +a spec pair collection of the form strong::[ key, spec, ...:: strong::]:: where strong::key:: is the symbol of the control arg to be set and strong::spec:: can be a collection of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]:: defining the corresponding ControlSpec, a Symbol / String to look for a ControlSpec in the global dictionary Spec.specs or a collection of specs of this form for an arrayed control. May also be a SimpleNumber for a dummy slider with fixed value. Defaults to nil. Also takes a Function of indices that returns a valid arg. + +argument::ctrReplace +a spec pair collection of a form like strong::ctrBefore::. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::ctrAfter +a spec pair collection of a form like strong::ctrBefore::. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::exclude +a collection of control keys (Symbols) to be excluded. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::metaKey +Symbol. Key to lookup SynthDef metadata. Also takes a Function of indices that returns a Symbol. Defaults to \specs. + +argument::useGlobalSpecs +Boolean. Defines if to consider global ControlSpecs if metadata is not given. Also takes a Function of indices that returns a Boolean. Defaults to true. + +argument::num +Boolean. Integer. Number of Synths to be controlled. Defaults to 1. + + + + + +method:: pVarGuiSpecs +See link::Tutorials/VarGui_shortcut_builds::. Generate envir variable control data for one or more Environments derived from a SynthDef name which may also be given as String. Returns a grouped collection, also for num = 1. Data can directly been used as input to VarGui's varCtr arg. This method can be used for control of Pbinds / EventStreamPlayers derived from more than one SynthDef in one VarGui or combined control of Synths and Pbinds / EventStreamPlayers. + + +argument::ctrBefore +a spec pair collection of the form strong::[ key, spec, ...:: strong::]:: where strong::key:: is the symbol of the environmental variable to be set and strong::spec:: can be a collection of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]:: defining the corresponding ControlSpec, a Symbol / String to look for a ControlSpec in the global dictionary Spec.specs or a collection of specs of this form for an arrayed control if the environmental variable should have the value of a collection itself. May also be a SimpleNumber for a dummy slider with fixed value. Defaults to nil. Also takes a Function of indices that returns a valid arg. + +argument::ctrReplace +a spec pair collection of a form like strong::ctrBefore::. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::ctrAfter +a spec pair collection of a form like strong::ctrBefore::. Also takes a Function of indices that returns a valid arg. Defaults to nil. + + +argument::durCtr +a controlspec of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]:: or a Symbol to look for a ControlSpec in the global dictionary Spec.specs. Also takes a Function of indices that returns a valid arg. Defaults to #[0.05, 3, \exp, 0, 0.2]. + + +argument::legatoCtr +a controlspec of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]:: or a Symbol to look for a ControlSpec in the global dictionary Spec.specs. Also takes a Function of indices that returns a valid arg. Defaults to #[0.1, 5, \exp, 0, 0.8]. + +argument::exclude +a control key (Symbol) or a collection of control keys to be excluded from Pbind pairs and controls. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::excludeGate +Boolean. Determines if gate control should be excluded. Also takes a Function of indices that returns a Boolean. Defaults to true. + +argument::metaKey +Symbol. Key to lookup SynthDef metadata. Also takes a Function of indices that returns a Symbol. Defaults to \specs. + +argument::useGlobalSpecs +Boolean. Defines if to consider global ControlSpecs if metadata is not given. Also takes a Function of indices that returns a Boolean. Defaults to true. + +argument::num +Integer. Number of Pbinds / EventStreamPlayers to be controlled. Defaults to 1. + + + + + +method:: pfuncPbinds +See link::Tutorials/VarGui_shortcut_builds::. Generate Pbinds with Pfunc pairs derived from corresponding SynthDef metadata resp. global ControlSpecs. Duration and legato pairs are added by default. If an added Pbind pair with key \degree, \note or \midinote is detected, \freq is excluded automatically. Returns a collection, also for num = 1. + + +argument::pBefore +a Pbind pair collection of the form strong::[ key, patterns, ...:: strong::]::. Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. + +argument::pReplace +a Pbind pair collection of the form strong::[ key, patterns, ...:: strong::]::. Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. + +argument::pAfter +a Pbind pair collection of the form strong::[ key, patterns, ...:: strong::]::. Also takes a Function of indices that returns a Pbind pair collection. Defaults to nil. + +argument::exclude +a control key (Symbol) or a collection of control keys to be excluded from Pbind pairs and controls. Also takes a Function of indices that returns a valid arg. Defaults to nil. + +argument::excludeGate +Boolean. Determines if gate control should be excluded. Also takes a Function of indices that returns a Boolean. Defaults to true. + +argument:: excludeDur +Boolean. Determines if \dur should be excluded from the Pbind. Also takes a Function of indices that returns a Boolean. Defaults to false. + +argument:: excludeLegato +Boolean. Determines if \legato should be excluded from the Pbind. Also takes a Function of indices that returns a Boolean. Defaults to false. + +argument::metaKey +Symbol. Key to lookup SynthDef metadata. Also takes a Function of indices that returns a Symbol. Defaults to \specs. + +argument::useGlobalSpecs +Boolean. Defines if to consider global ControlSpecs if metadata is not given. Also takes a Function of indices that returns a Boolean. Defaults to true. + +argument::post +Boolean. Determines if to post order of Pbind pairs. Also takes a Function of indices that returns a Boolean. Defaults to false. + +argument::trace +Boolean. Determines if EventStreamPlayer should post Events when playing. Also takes a Function of indices that returns a Boolean. Defaults to false. + +argument::num +Integer. Number of Pbinds / EventStreamPlayers to be controlled. Defaults to 1. + + + + + diff --git a/HelpSource/Classes/Synth.ext.schelp b/HelpSource/Classes/Synth.ext.schelp new file mode 100644 index 0000000..a52f187 --- /dev/null +++ b/HelpSource/Classes/Synth.ext.schelp @@ -0,0 +1,7 @@ + +INSTANCEMETHODS:: + +private:: miSC_getCtrIndex + +private:: miSC_getParentHelpSynth, miSC_isGeneratedByHelpSynth + diff --git a/HelpSource/Classes/TZeroXBufRd.schelp b/HelpSource/Classes/TZeroXBufRd.schelp new file mode 100755 index 0000000..a6e0946 --- /dev/null +++ b/HelpSource/Classes/TZeroXBufRd.schelp @@ -0,0 +1,972 @@ +CLASS:: TZeroXBufRd +summary:: triggers sequences of segments between zero crossings from one or more buffers with demand-rate control +categories:: Libraries>miSCellaneous>ZeroX ugens +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/ZeroXBufRd, Classes/ZeroXBufWr, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies + +DESCRIPTION:: + +TZeroXBufRd is for triggering possibly overlapped segments between zero crossings (half wavesets) from one or more buffers, whereby several reading and processing parameters can be sequenced with demand rate ugens. Full waveset sequences can so be generated as a special case. It needs analysis data prepared with link::Classes/ZeroXBufWr::. For consecutive reading of segments between zero crossings see link::Classes/ZeroXBufRd::. ZeroXBufRd / TZeroXBufRd can be used for a number of synthesis / processing techniques in a field between wavesets [1, 4, 5], pulsar synthesis [1, 3], buffer modulation and rectification (which are both a kind of waveshaping) and stochastic concatenation methods [2, 6]. There are already existing SC waveset implementations like Alberto de Campo's Wavesets quark (https://github.com/supercollider-quarks/quarks) and Olaf Hochherz's SPList (https://github.com/olafklingt/SPList), which do language-side analysis and Fabian Seidl's RTWaveSets plugin (https://github.com/tai-studio/RTWaveSets). My focus has been server-side analysis and demand rate ugen control of half waveset parameters as well as multichannel and buffer switch options. Realtime control while analysis is possible, as long as reading is only refering to already analysed sections, but clearly most flexibility is given with a fully analysed buffer, which can also be done in quasi realtime. + +note:: +Depending on the multichannel sizes and the options used (rate and dir sequencing) it might be necessary to increase server resources, i.e. the number of interconnect buffers and / or memory size (e.g. s.options.numWireBufs = 256; s.options.memSize = 8192 * 32; s.reboot). Because of overlappings this is more relevant with TZeroXBufRd than with ZeroXBufRd. +:: + +note:: +Often it pays to adjust zero crossings in the sound buffer effectively to 0, that way sawtooth-like interpolation artefacts can be avoided. See link::#Ex. 1#Ex. 1:: and link::Classes/ZeroXBufRd#Ex. 7::. +:: + +note:: +Demand rate UGens in ZeroXBufRd / TZeroXBufRd must always use inf as repeats arg, this is of course not necessary for nested ones. You might pass a length arg though (link::#Ex. 5#Ex. 5::). +:: + +note:: +The distance between triggers must remain above control duration, otherwise the synthesis fails. For faster trigerring you'd have to use the server with a lower blocksize. +:: + +note:: +As triggered half wavesets can overlap you'd have to care for a sufficiently large 'overlapSIze' arg. See link::#Ex. 3#Ex. 3:: for possible estimations. +:: + +note:: +For avoiding too long half wavesets it might be useful to apply LeakDC resp. a high pass filter before analysis. +:: + +note:: +In rare cases I noticed corrupted buffers in multi buffer examples for no obvious reason. +:: + +subsection::Credits +Thanks to Tommaso Settimi for an inspiring discussion, which gave me a nudge to tackle these classes. + + +subsection::References + +numberedList:: + +## de Campo, Alberto. "Microsound" In: Wilson, S., Cottle, D. and Collins, N. (eds). 2011. The SuperCollider Book. Cambridge, MA: MIT Press, 463-504. + +## Luque, Sergio (2006). Stochastic Synthesis, Origins and Extensions. Institute of Sonology, Royal Conservatory, The Netherlands. https://sergioluque.com + +## Roads, Curtis (2001). Microsound. Cambridge, MA: MIT Press. + +## Seidl, Fabian (2016). Granularsynthese mit Wavesets für Live-Anwendungen. Master Thesis, TU Berlin. https://www2.ak.tu-berlin.de/~akgroup/ak_pub/abschlussarbeiten/2016/Seidl_MasA.pdf + +## Wishart, Trevor (1994). Audible Design. York: Orpheus The Pantomime Ltd. + +## Xenakis, Iannis (1992). Formalized Music. Hillsdale, NY: Pendragon Press, 2nd Revised edition. + +:: + + + +CLASSMETHODS:: + + +method::ar + +Creates a new TZeroXBufRd ar UGen. + + +argument::sndBuf +Buffer or SequenceableCollection of Buffers to read the data from, data must correspond to strong::zeroXBuf::. + +argument::zeroXBuf +Analysis Buffer resp. SequenceableCollection of such, prepared with link::Classes/ZeroXBufWr::. Must refer to data passed to strong::sndBuf::. + +argument::bufMix +A Number indicating the strong::sndBuf:: index, a demand rate or other ugens returning strong::sndBuf:: indices or a SequenceableCollection of such. If strong::bufMix:: equals nil (default) the size of the returned signal equals the size of strong::sndBuf::, otherwise it equals its own size. + +argument::trig +A trigger signal for starting new half waveset groups. + + +argument::zeroX +A Number indicating the index in strong::zeroXBuf::, a demand rate or other ugens returning strong::zeroXBuf:: indices or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::zeroX:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 0. + +argument::xNum +Determining the number of half wavesets starting at strong::zeroX::, a demand rate or other ugens returning these numbers or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::xNum:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 1. + +argument::xRep +Determining the number of repetitions of half wavesets resp. half waveset groups (given by strong::xNum::) starting at strong::zeroX::, a demand rate or other ugens returning these numbers or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::xRep:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 1. + + +argument::power +Used for processing the buffer signal according to the formula: sig ** strong::power:: * strong::mul:: + strong::add:: per half waveset. Must be a positive Number, a demand rate or other ugens returning strong::power:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::power:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 1. + +argument::mul +Used for processing the buffer signal according to the formula: sig ** strong::power:: * strong::mul:: + strong::add:: per half waveset. Must be a Number, a demand rate or other ugens returning strong::mul:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::mul:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 1. + +argument::add +Used for processing the buffer signal according to the formula: sig ** strong::power:: * strong::mul:: + strong::add:: per half waveset. Must be a Number, a demand rate or other ugens returning strong::add:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::add:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 0. + + +argument::rate +Determines the playback rate per half waveset together with strong::rateMul::. Must be a positive Number, a demand rate or other ugens returning strong::rate:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::rate:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 1. + +argument::dir +Determines the playback direction of half wavesets. Must be +1 or -1, a demand rate or other ugens returning strong::dir:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::dir:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 1. + + +argument::interpl +Determines the interpolation type for the BufRd ugens. Must equal 1 (no), 2 (linear) or 4 (cubic) or a SequenceableCollection of these numbers. Defaults to 4. + +argument::overlapSize +Determines the maximum overlap of half waveset groups. This is a fixed number or a SequenceableCollection thereof determining the size of internally used multichannel signals for overlappings. If this number is too low the synthesis fails. See link::#Ex. 3#Ex.3:: for estimating a sufficiently large value. Defaults to 10. + + +argument::length +Determines the number of triggers before release of the overall asr envelope. Can be a Sequenceable Collection too. Overruled by strong::maxTime:: if this is reached before. Defaults to inf. + +argument::maxTime +Determines the time before release of the overall asr envelope. Can be a Sequenceable Collection too. Overruled by strong::length:: if this is reached before. Defaults to inf. + +argument::att +Attack time of overall asr envelope or SequenceableCollection thereof. Defaults to 0. + +argument::rel +Release time of overall asr envelope or SequenceableCollection thereof. Defaults to 0. + +argument::curve +Curve of overall asr envelope or SequenceableCollection thereof. Defaults to -4. + +argument::doneAction +Done action of overall asr envelope or SequenceableCollection thereof. Defaults to 0. + + + + +section::Examples + +See also the examples of link::Classes/ZeroXBufRd:: help. Most features work in the same way, this help file focusses rather on the differences. + + + +anchor::Ex. 1:: +subsection::Ex. 1: Basic usage + + +code:: +// prepare two short buffers for audio and zero crossing data +// the size of needed zeroX data space can be roughly estimated + +( +b = Buffer.alloc(s, 256); +z = Buffer.alloc(s, 32); +) + +// analyse a short snippet of modulation + +( +x = { + var src = SinOsc.ar(SinOsc.ar(1200, 0, 1000, 100)) * SinOsc.ar(700) - 0.1; + ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, doneAction: 2); + Silent.ar +}.play +) + + +// check the waveform + +b.plot + + + +// It's important to note that the maximum trigger rate should be below control rate, +// otherwise sequencing with demand rate ugens is messed up. +// This limitation can be circumvented by rebooting the server with a lower blockSize. + +// demand rate ugens in TZeroXBufRd should always use inf as repeats arg + +( +~maxTrigRate = s.sampleRate / s.options.blockSize; + +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(MouseX.kr(10, ~maxTrigRate)), + zeroX: Dseq([2, 3], inf), + rate: 0.2 + ) * 0.1 +}.play +) + +s.scope + +x.release + +// In this example waveforms are slightly crossing the x axis. +// This comes from the fact that buffer values at zero crossing positions are not equal zero, +// but just of a different sign than the sample before. +// The effect is also explained in ZeroXBufRd's help Ex.7. + +( +{ + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(150), + zeroX: Dseq([2, 3], inf), + rate: 0.2 + ) * 0.2 +}.plot(0.03) +) + + +// To get cleaner waveforms it's therefore recommended to adjust zero crossings in the buffer. +// This can also be done while analysis by using the flag 'adjustZeroXs' in ZeroXBufWr. + +b.adjustZeroXs(z) + + +// smoother result + +( +{ + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(150), + zeroX: Dseq([2, 3], inf), + rate: 0.2 + ) * 0.2 +}.plot(0.03) +) + + +// mul and dir can be used in the same way as with ZeroXBufRd + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(MouseX.kr(30, ~maxTrigRate)), + zeroX: Dseq([2, 3], inf) + LFDNoise0.ar(10).range(0, 5), + mul: Dseq([0.05, 0.1, 0.35], inf), + rate: 0.5, + dir: Dseq([1, -1], inf), + ) +}.play +) + +x.release + + +// unlike with ZeroXBufRd with rate there's no restriction for using +// combinations of demand rate and normal ugens + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(MouseX.kr(30, ~maxTrigRate)), + zeroX: Dseq([2, 3], inf), + mul: Dseq([0.05, 0.1, 0.35], inf), + rate: Dseq([0.2, 0.5, 1], inf) * SinOsc.ar(5).range(0.8, 1.2), + dir: Dseq([1, -1], inf), + ) +}.play +) + +x.release + + +// power arg +// be careful with this arg moving away from 1 ! +// high power values can result in loud signals if the source has values outside [-1, 1] and +// small power values can also become loud with source values near zero + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(MouseX.kr(30, ~maxTrigRate)), + zeroX: Dseq([2, 3], inf), + power: MouseY.kr(0.3, 2), + rate: 0.2, + dir: Dseq([1, 1, 1, -1], inf), + ).tanh * 0.3 +}.play +) + +x.release +:: + + + +anchor::Ex. 2:: +subsection::Ex. 2: The 'xNum' and 'xRep' args + + +code:: +// needs Buffers from Ex.1 + +// With ZeroXBufRd repetitions of (groups of) half wavesets can easily be defined +// with passing appropriate demand rate ugens to the zeroX arg. +// With TZeroXBufRd the situation is different as the use of an external trigger +// opens the freedom to specify the sequences of such groups independently. + +// The following plots show the options in the case of non-overlapping groups. + + +// Half waveset and full waveset at indices 2 and 3 + +( +{ + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(100), + xNum: Dseq([1, 2], inf), + zeroX: Dseq([2, 3], inf), + rate: 0.2 + ); +}.plot(0.03) +) + +// Repeated half wavesets + +( +{ + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(50), + xRep: 3, + zeroX: Dseq([2, 3], inf), + rate: 0.2 + ); +}.plot(0.05) +) + + +// Sequencing half waveset number and repetitions + +( +{ + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(50), + xNum: Dseq([1, 2, 3], inf), + xRep: Dseq([3, 2, 1], inf), + zeroX: Dseq([2, 3], inf), + rate: 0.2 + ); +}.plot(0.14) +) +:: + + + + +anchor::Ex. 3:: +subsection::Ex. 3: The 'overlapSize' arg + +code:: +// TZeroXBufRd enables overlappings of half wavesets, the maximum number of +// overlaps is given with the non-modulatable arg 'overlapSize' which defaults to 10. +// So if waveset groups are too long and/or the trigger rate is too high +// groups will be cut and the synthesis, according to the other passed args, fails. + +// Supposed constant values, the minimum necessary overlapSize can be calculated +// by the formula: + +// ceiling((wavesetGroupSampleNum * trigRate) / (sampleRate * rate)) + +// where wavesetGroupSampleNum means the number of samples of a group, +// which consists of xNum * xRep half wavesets. + + +// It's the user's responsibility to care for a sufficiently large overlapSize, +// resp. sufficiently low trigger rates, xNum and xRep args as well as not too low playback rates. +// With recorded sounds it might be necessary to estimate the largest half wavesets +// by analysis in the language. + +// An easy way to reduce the maximum and average half waveset length is +// repeating the zeroX analysis with a LeakDC or high pass filter applied. + + +// Buffers from Ex.1 +// We suppose a sample rate of 44100 + +b.loadToFloatArray(action: { |b| v = b }) + +z.loadToFloatArray(action: { |b| w = b }) + +// zero crossing analysis data + +w + +// with zeroX = 1 and xNum = 7 we have a chunk of 194 samples + +w[8] - w[1] + + +// according to the above formula, with a trigger rate of 500 and +// a playback rate of 0.5 the minimal necessary overlapSize is 5 + +194 * 500 / (44100 * 0.5) + +-> 4.3990929705215 + +// check with overlapSizes of 5 (or higher), +// it's the same and sounding smooth, +// with overlapSize = 4 the result is erroneous, resp. distorted + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(500), + zeroX: 1, + xNum: 7, + rate: 0.5, + overlapSize: 5, + ) * 0.1 +}.play +) + +s.scope + +x.release +:: + + +anchor::Ex. 4:: +subsection::Ex. 4: Multichannel usage and the 'bufMix' arg + +code:: +// This is very simliar to the conventions of ZeroXBufRd, +// buffers can be switched per trigger. + +// Without passing a bufMix arg the size of the returned signal is determined by the buffer input. +// It may be a single channel buffer or an array of single channel buffers, +// in correspondence with the analysis buffer(s) - multichannel buffers are not allowed. +// If bufMix is passed, it determines the size of the returned signal, +// its components can be demand rate or other ugens to control switching between buffers per half waveset groups. + +// Note: buffer switching can become CPU-demanding with a lot of Buffers +// as for fast switching it is necessary to play all in parallel + +( +// boot with extended resources +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.options.memSize = 8192 * 32; +s.reboot; +) + + +// prepare 3 buffers + +( +b = { Buffer.alloc(s, 1000, 1) } ! 3; +z = { Buffer.alloc(s, 100, 1) } ! 3; +) + +// fill with basic waveforms +( +{ + var src = [ + SinOsc.ar(400), + LFTri.ar(400), + SinOsc.ar(400) ** 10 + ]; + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2); + Silent.ar +}.play +) + + +s.scope + +// play 2 channels with sine and triangle, where +// triangle is alternating between half and full waveset + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(100), + xNum: [2, Dseq([1, 2], inf)], + zeroX: 0, + bufMix: [0, 1] + ) * 0.1 +}.play +) + +x.release + + +// sequencing repetitions +// if both channels should get the same sequence wrap demand rate ugen into a Function + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(100), + xNum: 1, + xRep: { Dseq((1..5), inf) }, + zeroX: 0, + bufMix: [0, 1], + rate: 0.7 + ) * 0.1 +}.play +) + +x.release + + +// overlapping groups + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(100), + xNum: 1, + xRep: [Dseq((1..15), inf), Dseq((15..1), inf)], + zeroX: 0, + bufMix: [0, 1], + rate: MouseX.kr(0.2, 3), + overlapSize: 10 + ) * 0.1 +}.play +) + +x.release + + +// estimate the maximum xRep for the given overlapSize, +// in the above example (suppose samplerate 44100): +// as the buffers have been generated with freq = 400, +// we can roughly estimate the half wavesets with 55 samples. + +// According to the formula of Ex. 2 + +55 * xRep * 100 / (44100 * 0.2) = 10 + +xRep = (44100 * 0.2) * 10 / (55 * 100) + +-> 16.036363636364 + +// so with xRep values up to 17 we get a bit of distortion besides the aliasing (hardly audible), +// it gets stronger with lower overlapSize and disappears with higher values + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(100), + xNum: 1, + xRep: [Dseq((1..17), inf), Dseq((17..1), inf)], + zeroX: 1, + bufMix: [0, 1], + rate: 0.2, + overlapSize: 10 // check with higher and lower values + ) * 0.1 +}.play +) + +x.release + + + +// alternating buffers in one channel +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(100), + xNum: 2, + xRep: 1, + zeroX: 1, + bufMix: Dseq([0, 1], inf), + rate: 0.6, + ) * 0.1 +}.play +) + +x.release + + +// various sequences in both channels with switched waveforms + +( +x = { + TZeroXBufRd.ar( + b, z, + trig: Impulse.ar(200), + xNum: [Dseq([1, 2], inf), Dseq([2, 1], inf)], + xRep: [Dseq([1, 2], inf), Dseq([2, 1], inf)], + zeroX: 0, + bufMix: [Dseq([0, 1, 2], inf), Dseq([1, 2, 0], inf)], + rate: MouseX.kr(0.2, 2), + ) * 0.1 +}.play +) + +x.release +:: + + +anchor::Ex. 5:: +subsection::Ex. 5: The overall envelope + + +code:: +// This works also like for ZeroXBufRd, 'length' refers to the maximum number of triggers for half waveset groups. + +// The finishing of a TZeroXBufRd is not detemined by finite demand rate ugens but by an overall envelope, +// its release section is triggered by a maximum number of half wavesets ('length') or a maximum time. + +// Buffers from Ex.4 + +{ TZeroXBufRd.ar(b[0], z[0], trig: Impulse.ar(500), rate: 1, length: 10, rel: 0.01) }.plot(0.05) + +{ TZeroXBufRd.ar(b[0], z[0], trig: Impulse.ar(500), rate: 1, maxTime: 0.02, rel: 0.01) }.plot(0.05) + + +// envelopes can be differentiated + +{ TZeroXBufRd.ar(b[0..1], z[0..1], trig: Impulse.ar(500), rate: 1, maxTime: [0.01, 0.005], rel: [0.005, 0.02]) }.plot(0.03) + +{ TZeroXBufRd.ar(b[0..1], z[0..1], trig: Impulse.ar(500), rate: 1, length: [7, 2], rel: [0.005, 0.02]) }.plot(0.03) + + +// there should be only one doneAction 2 in this case + +{ TZeroXBufRd.ar(b[0..1], z[0..1], trig: Impulse.ar(500), rate: 1, maxTime: [0.01, 0.005], rel: [0.05, 0.5], doneAction: [0, 2]) }.play + + +( +b.do(_.free); +z.do(_.free); +) +:: + + +anchor::Ex. 6:: +subsection::Ex. 6: Simultaneous writing and reading + + +code:: +// The reading of half wavesets can start before analysis is finished, +// if TZeroXBufRd is carefully used in with a bit of delay. +// A bit more delicate than with ZeroXBufRd because of the independent trigger. + + +// prepare buffers + +( +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +b = Buffer.read(s, p); +) + +( +z = Buffer.alloc(s, b.duration * 44100 / 5, 1); +s.scope; +) + + +// Simultaneous writing and reading is easier with ZeroXBufRd +// as the trigger deltas are given by the source then. + +( +{ + var src = PlayBuf.ar(1, b, BufRateScale.ir(b)); + // write zero crossings, but no need to overwrite sound buffer + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, writeSndBuf: 0); + DelayL.ar( + TZeroXBufRd.ar( + b, z, + // indicating stereo + bufMix: [0, 0], + // one trigger for both channels + // sufficiently slow progress for the given source + // (can not be guaranteed for an arbitrary signal) + trig: TDuty.ar(Dstutter(5, Dseq([1, 5, 10], inf)) * ControlDur.ir), + xNum: 1, + xRep: 2, + // stereo, drate ugens must be wrapped + zeroX: { Dseries() }, + rate: { Dwhite(0.5, 0.7) }, + maxTime: 7, + rel: 1, + doneAction: 2 + ), + 0.1, + 0.1 + ); +}.play +) + +( +b.free; +z.free; +) + +// It's of course unproblematic – and still quasi realtime – to fully a analyse +// a snippet of sound with ZeroXBufWr before freely using TZeroXBufRd in the same synth +:: + + + +anchor::Ex. 7:: +subsection::Ex. 7: Granulation with movement through a buffer + + +See link::Tutorials/Buffer_Granulation#Ex.1h:: + + +anchor::Ex. 8:: +subsection::Ex. 8: Pulsar synthesis with envelopes, variable number of source and envelope half wavesets + +code:: +// Thanks to Marcin Pietruszewski for discussions on pulsar synthesis with envelopes + +// Durations of envelopes can be adapted to source waveset lengths, regardless of +// half waveset numbers (xNum) and numbers of repetition (xRep) for source and envelope. +// This needs a bit of analysis. + + +// prepare buffers + +( +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +~srcBuf = Buffer.read(s, p); +~srcBufZeros = Buffer.alloc(s, 100000); + +~envBuf = Buffer.alloc(s, 1000, 1); +~envBufZeros = Buffer.alloc(s, 100, 1); +) + +// analyse source buffer and sine envelope + +( +{ + var src = PlayBuf.ar(1, ~srcBuf, BufRateScale.ir(~srcBuf), doneAction: 2); + ZeroXBufWr.ar( + src, ~srcBuf, ~srcBufZeros, + startWithZeroX: 0, adjustZeroXs: 1, doneAction: 2 + ); +}.play; + +{ + var src = SinOsc.ar(50); + ZeroXBufWr.ar( + src, ~envBuf, ~envBufZeros, + startWithZeroX: 1, adjustZeroXs: 0, doneAction: 2 + ); + Silent.ar +}.play +) + + +// load source and envelope data into language + +( +~srcBufZeros.loadToFloatArray(action: { |x| ~srcBufZeroXArr = x.reject(_==0) }); +~srcBuf.loadToFloatArray(action: { |x| ~srcBufArr = x }); + +~envBufZeros.loadToFloatArray(action: { |x| ~envBufZeroXArr = x.as(Array) }); +~envBuf.loadToFloatArray(action: { |x| ~envBufArr = x }); +) + + +// choose source and envelope waveset and +// maximum number of half wavesets + +// finally we need the lengths of the wavesets (~srcZeroXDiffs, ~envZeroXDiffs) for +// adjusting the envelope playback rate in the SynthDef dynamically + +( +~srcZeroXOffset = 1019; // check with other offset +~srcMaxHalfWavesetNum = 2; + +~srcZeroXIndices = ~srcBufZeroXArr[~srcZeroXOffset..~srcZeroXOffset + ~srcMaxHalfWavesetNum]; +~srcZeroXDiffs = ~srcZeroXIndices.differentiate.drop(1); + +~envZeroXOffset = 0; +~envMaxHalfWavesetNum = 2; + +~envZeroXIndices = ~envBufZeroXArr[~envZeroXOffset..~envZeroXOffset + ~envMaxHalfWavesetNum]; +~envZeroXDiffs = ~envZeroXIndices.differentiate.drop(1); + +[ + ~srcBufArr[(~srcZeroXIndices[0]..~srcZeroXIndices[~srcMaxHalfWavesetNum])], + ~envBufArr[(~envZeroXIndices[0]..~envZeroXIndices[~envMaxHalfWavesetNum])] +].plot; +) + + +( +SynthDef(\pulsar_1, { |out, freq = 50, freqDev = 0.1, xNumSrc = 1, xRepSrc = 1, + rateSrc = 1, xNumEnv = 1, xRepEnv = 1, power = 1, amp = 1| + var sig, env, trig, xNumSrcIndicator, xNumEnvIndicator, + rawSrcSampleNum, rawEnvSampleNum, rateEnv; + + // need 1-0 arrays for calculating the number of samples dynamically + xNumSrcIndicator = { |i| xNumSrc > DC.ar(i) } ! ~srcMaxHalfWavesetNum; + xNumEnvIndicator = { |i| xNumEnv > DC.ar(i) } ! ~envMaxHalfWavesetNum; + + rawSrcSampleNum = (xNumSrcIndicator * ~srcZeroXDiffs).sum * xRepSrc; + rawEnvSampleNum = (xNumEnvIndicator * ~envZeroXDiffs).sum * xRepEnv; + + // pulsar source and envelope must have same length + // so we must have: + // rawSrcSampleNum / rateSrc == rawEnvSampleNum / rateEnv + // hence rateEnv can be calculated: + + rateEnv = rawEnvSampleNum * rateSrc / rawSrcSampleNum; + + // a bit of frequency decorrelation + trig = Impulse.ar([1, freqDev / 100 + 1] * freq); + + // stereo source + sig = TZeroXBufRd.ar( + ~srcBuf, + ~srcBufZeros, + [~srcBuf, ~srcBuf], + xNum: xNumSrc, + xRep: xRepSrc, + trig: trig, + zeroX: 1001, + rate: rateSrc + ); + + // stereo envelope + env = TZeroXBufRd.ar( + ~envBuf, + ~envBufZeros, + [~envBuf, ~envBuf], + xNum: xNumEnv, + xRep: xRepEnv, + trig: trig, + zeroX: 0, + rate: rateEnv + ); + Out.ar(out, Limiter.ar(env ** power * sig) * amp) +}, +// make lags, but not for discrete args +(1!3) ++ (0!2) ++ 1 ++ (0!2) ++ [1, 0.1], +metadata: ( + specs: ( + freq: [20, 150, \exp, 0, 50], + freqDev: [0, 2, 3, 0, 0.3], + xNumSrc: [1, 2, \lin, 1, 1], + xRepSrc: [1, 2, \lin, 1, 1], + rateSrc: [0.3, 3, \lin, 0, 1], + + xNumEnv: [1, 2, \lin, 1, 1], + xRepEnv: [1, 2, \lin, 1, 1], + + power: [0.5, 5, \exp, 0, 1], + amp: [0, 2, \lin, 0, 1] + ) +) +).add +) + + +s.scope; +s.freqscope; + +\pulsar_1.sVarGui.gui(synthColorGroups: (0..8).clumps([5, 4])) +:: + +anchor::Ex. 9:: +subsection::Ex. 9: Layers of pulsar streams + +code:: +// Thanks to Jan Ferreira for discussions on pulsar masking + +( +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +~srcBuf = Buffer.read(s, p); +~srcBufZeros = Buffer.alloc(s, 100000); +) + + +// analyse source buffer + +( +{ + var src = PlayBuf.ar(1, ~srcBuf, BufRateScale.ir(~srcBuf), doneAction: 2); + ZeroXBufWr.ar( + src, ~srcBuf, ~srcBufZeros, + startWithZeroX: 0, adjustZeroXs: 1, doneAction: 2 + ); +}.play; +) + + +// for low rates you might have to take a larger overlap value (see Ex. 3)
 +( +SynthDef(\pulsar_2, { |out, freq = 50, maxFreqDev = 2, freqDevFreq = 0.3, + xNumSrc = 1, xRepSrc = 1, rateSrc = 1, amp = 1| + var sig, result, env, trig, seq, gates, gatesL, gatesR, pulsarNum; + + // a bit of frequency decorrelation + + pulsarNum = 4; + seq = [1, 1, 0, 0]; + + // 2 * 4 impulse streams with slightly modulated frequencies + trig = { Impulse.ar({ LFDNoise3.ar(freqDevFreq).range(0, maxFreqDev) } ! pulsarNum / 100 + 1 * freq) } ! 2; + + // 2 * 4 pulsar streams + sig = { |i| + TZeroXBufRd.ar( + ~srcBuf, + ~srcBufZeros, + ~srcBuf ! pulsarNum, + xNum: xNumSrc, + xRep: xRepSrc, + trig: trig[i], + zeroX: 1005, + rate: rateSrc, + overlapSize: 5 + ) + } ! 2; + + // masking of pulsars with gates + gatesL = sig[0].collect { |s| Demand.ar(s, 0, Dseq(seq.scramble, inf)) }; + gatesR = sig[1].collect { |s| Demand.ar(s, 0, Dseq(seq.scramble, inf)) }; + + result = [(sig[0] * gatesL).sum, (sig[1] * gatesR).sum]; + + // more condensed writing of the previous 3 lines: + // gates = sig.collect { |x| x.collect { |y| Demand.ar(y, 0, Dseq(seq.scramble, inf)) } }; + // result = (sig * gates).collect(_.sum); + + Out.ar(out, Limiter.ar(result) * amp) +}, +// make lags, but not for discrete args +(1!4) ++ (0!2) ++ (0!2), +metadata: ( + specs: ( + freq: [20, 250, \exp, 0, 100], + maxFreqDev: [0, 5, 3, 0, 1.5], + freqDevFreq: [0, 5, 3, 0, 0.2], + xNumSrc: [1, 2, \lin, 1, 1], + xRepSrc: [1, 2, \lin, 1, 1], + rateSrc: [0.4, 6, \lin, 0, 1], + + xNumEnv: [1, 2, \lin, 1, 1], + xRepEnv: [1, 2, \lin, 1, 1], + + seqL: [0, 1, \lin, 1, 1], + seqR: [0, 1, \lin, 1, 1], + power: [0.5, 5, \exp, 0, 1], + amp: [0, 1, \lin, 0, 0.5] + ) +) +).add +) + + +s.scope; +s.freqscope; + +\pulsar_2.sVarGui.gui(synthColorGroups: (0..6).clumps([3, 2, 1, 1])) +:: + + diff --git a/HelpSource/Classes/TempoClock.ext.schelp b/HelpSource/Classes/TempoClock.ext.schelp new file mode 100644 index 0000000..5edb332 --- /dev/null +++ b/HelpSource/Classes/TempoClock.ext.schelp @@ -0,0 +1,4 @@ + +INSTANCEMETHODS:: + +private:: miSC_getPhaseFromNextTimeOnGrid, miSC_getPhaseFromTimeToNextBeat diff --git a/HelpSource/Classes/Thread.ext.schelp b/HelpSource/Classes/Thread.ext.schelp new file mode 100644 index 0000000..4446d3e --- /dev/null +++ b/HelpSource/Classes/Thread.ext.schelp @@ -0,0 +1,4 @@ + +INSTANCEMETHODS:: + +private:: miSC_getReceiver, miSC_getParent, miSC_getFunc, miSC_getEnvironment diff --git a/HelpSource/Classes/UGen.ext.schelp b/HelpSource/Classes/UGen.ext.schelp new file mode 100755 index 0000000..756a0d2 --- /dev/null +++ b/HelpSource/Classes/UGen.ext.schelp @@ -0,0 +1,5 @@ + +INSTANCEMETHODS:: + + +private::miSC_methodSelectorForRate diff --git a/HelpSource/Classes/VarGui.schelp b/HelpSource/Classes/VarGui.schelp new file mode 100755 index 0000000..e8ea218 --- /dev/null +++ b/HelpSource/Classes/VarGui.schelp @@ -0,0 +1,1677 @@ +CLASS:: VarGui +summary:: slider / player gui to set envir variables and synth controllers and play synths, event patterns and tasks +categories:: Libraries>miSCellaneous>GUI, GUI +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/VarGui_shortcut_builds, Tutorials/HS_with_VarGui, Tutorials/Event_patterns_and_LFOs, Tutorials/Event_patterns_and_Functions, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation + + +DESCRIPTION:: + + +SC setups may contain discrete and continuous control, e.g. using combinations of Pbinds, Tasks and Synths. There are many possible ways of interaction between such elements, sometimes there is the alternative to control things by server or language (e.g. Pbinds versus Demand Ugens) but often one needs to have both. VarGui is a multi purpose slider and player GUI, originally intended for indentifying parameters to be reused in other setups. +Pbinds and Tasks can easily refer to environmental variables - in case of Pbinds e.g. via Pfunc, Plazy, Pcollect, but more conveniently via the dynamic scope link::Tutorials/PLx_suite:: - so VarGui can control environmental variables and synth controllers. For using event patterns with functional code and the related scoping features see link::Tutorials/Event_patterns_and_Functions::. VarGui was not intended for performance, however (since miSCellaneous v0.4) it includes a player section. Players can be used in different (reset) modes, states of synths and streams are reflected by colors, it also works with wslib slider classes (link::#Ex. 8#Ex.8::). For quick GUI generation using SynthDef metadata for controlspecs and / or automatic Pbind builds see link::Tutorials/VarGui_shortcut_builds::. As both VarGui help files are quite full of details, a more general overview, including some exercises, is given in link::Guides/Introduction_to_miSCellaneous::. + + +subsection:: Some Important Issues Regarding VarGui + +VarGui allows to choose SynthDefs or Synths, Events patterns or EventStreamPlayers and Task functions or Tasks as input. It is recommended to take the more general objects (SynthDefs, Event patterns, Task functions) as then derived Synths, EventStreamPlayers and Tasks are newly generated and the latter are run in newly generated Environments. If you follow this it is very unlikely to accidentally break gui representation of data (though it's not strict MVC paradigm). + +On the other hand there are special cases when you might want to pass Synth objects / synth nodes, Tasks or EventStreamPlayers directly (e.g. link::Tutorials/HS_with_VarGui::). Then a bit more care has to be taken as, although a VarGui instance doesn't allow to poll more than one GUI window from it, nothing prevents you from refering to identical Synth objects / synth nodes, Tasks, EventsStreamPlayers or environmental variables with different VarGui instances. This is not recommended as, obviously, a possible source of confusion. + +If you are passing a Synth directly and moreover its SynthDef is not known to SynthDescLib setting a single component of an arrayed control also causes setting of all components below (no other choice at the moment, however, a very special case). + + +subsection:: GUI specific + +With miSCellaneous v0.16 (March 2017) backwards compatibility with Cocoa and SwingOSC is no longer supported, now cross-platform Qt is standard. VarGui was originally written with Cocoa as reference, though restrcitions on Qt are minor. + + +numberedList:: +## With Qt mouseDown is the hardcoded mode of player action, which anyway could be regarded as standard usage. + +## Moving several sliders in parallel (link::#Ex. 1c#Ex.1c::) works on all platforms and with wslib sliders. + +## Combined slider movement with modifier keys can differ in the necessary order of key pressing and mouse clicking. In Qt you can (and with the Cmd modifier involved you have to) press thereafter. + +## With EZSlider you can jump to a certain slider position by clicking at an arbitrary position in the slider field. With wslib sliders you can choose modes \jump and \move (link::#Ex. 8#Ex.8::). + +## Combined synth / stream player button actions with Caps-lock are currently not working with Qt. +:: + +You can list styles by code::GUI.availableStyles:: and set with code::GUI.style = \myFavoriteStyle:: + + +CLASSMETHODS:: + +method::new + +Create a new VarGui object, which holds control specifications. + + +argument::varCtr +strong::specPairs:: or a collection of strong::specPairs::, where +strong::specPairs:: is a collection of the form strong::[ key, spec, ...:: strong::]:: where +strong::key:: is the symbol of the environmental variable to be set and +strong::spec:: must be one of those: + +list:: +## Collection of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]::, defining the corresponding ControlSpec, or a collection of specs of this form for an arrayed control if the environmental variable should have the value of a collection itself. strong::step:: is used for the derivation of slider step size. + +## Symbol refering to a ControlSpec globally stored in Spec.specs or a collection thereof for arrayed control. ControlSpec.step is used for the derivation of slider step size. + +## SimpleNumber for a dummy slider with fixed value. +:: + + +If strong::specPairs:: or single components of a collection of strong::specPairs:: are nil and the corresponding strong::stream:: arg is a Symbol or String pointing to a SynthDef, it will be tried to derive controls from corresponding SynthDef metadata resp. global ControlSpecs (see link::Tutorials/VarGui_shortcut_builds::). + + +Variables are set in environments that are either given implicitely (1) or explicitely (2): + +numberedList:: +## by eventstream players / tasks passed to strong::stream:: - they are already bound to an +environment - or newly generated together with eventstream players or tasks from +patterns or functions passed to strong::stream::. + +## by strong::envir::. This allows setting of variables in Environments in case of absent stream players. +:: + +From explicit or implicit strong::envir:: input an ordered collection of non-identical environments is derived, +which, without one of the above infos, will consist of currentEnvironment only. + +list:: +## If strong::varCtr:: is already grouped as a collection of strong::specPairs:: then variables from i-th strong::specPairs:: will be set +in the i-th environment of the derived collection. +## If strong::varCtr:: is given as a flat collection and strong::specPairs:: contains no multiple keys then variables are set +in the first resp. only environment in question. +## If strong::varCtr:: is given as a flat collection and strong::specPairs:: contains multiple keys then strong::varEnvirGroups:: +is needed to map variables to environments (link::#Ex. 5a#Ex.5a::). +:: +Note that the i-th environment means the i-th non-identical environment, not necessarily +(in the case of environments implicitely given by eventstream players / tasks) the environment +of the i-th stream. +Defaults to nil. + + +argument::synthCtr +strong::specPairs:: or a collection of strong::specPairs::, where +strong::specPairs:: is a collection of the form strong::[ key, spec, ...:: strong::]:: where +strong::key:: is the symbol of the control arg to be set and +strong::spec:: must be one of those: + + +list:: +## Collection of the form strong::[:: strong::minval, maxval, warp, step, default:: strong::]::, defining the corresponding ControlSpec or a collection of specs of this form for an arrayed control. strong::step:: is used for the derivation of slider step size. + +## Symbol refering to a ControlSpec globally stored in Spec.specs or a collection thereof for arrayed control. ControlSpec.step is used for the derivation of slider step size. + +## SimpleNumber for a dummy slider with fixed value. + +:: + + +If strong::specPairs:: or single components of a collection of strong::specPairs:: are nil, it will be tried to derive controls from SynthDef metadata resp. global ControlSpecs (see link::Tutorials/VarGui_shortcut_builds::). + +list:: +## If strong::synthCtr:: is already grouped as a collection of strong::specPairs:: then controls from i-th strong::specPairs:: will be used +for setting the i-th synth. +## If strong::synthCtr:: is given as a flat collection and strong::specPairs:: contains no multiple keys then controls will be used +for setting the only synth in question. +## If strong::synthCtr:: is given as a flat collection and strong::specPairs:: contains multiple keys then strong::synthCtrGroups:: +is needed to map synth controls to synths (link::#Ex. 5b#Ex.5b::). +:: +Defaults to nil. + +argument::stream +Expects Pattern, EventStreamPlayer, Function, Task or a collection thereof, +determining the number of stream players. +Patterns / Functions are used as templates for EventStreamPlayers / Tasks in +environments generated at init time. If strong::stream:: is unequal nil variables given by +strong::varCtr:: will be set in the environments derived from it. +Size of strong::stream:: must at least equal and may exceed the number of strong::varCtr:: groups, +which (see above) may be given by strong::varCtr:: itself (if a grouped collection of strong::specPairs::) +or strong::varCtrGroups::. +Correspondence between environments of variables and eventstream / task players +is optionally indicated in the GUI - however the user is responsible for defining +patterns and task functions that really involve the variables to be set ! +Defaults to nil. + + +argument::synth +Input to which synth controls are mapped. Expects Symbol refering to a SynthDef, +Synth, nodeID or collection thereof. +Size of strong::synth:: must at least equal and may exceed the number of synthCtr groups, +which (see above) may be given by strong::synthCtr:: itself (if a grouped collection of strong::specPairs::) +or strong::synthCtrGroups::. +It is recommended to refer to "known" SynthDefs (SynthDescLib), best by +passing Symbols or also registered Synths, otherwise the correct synth state must be given +explicitely by strong::assumePlaying::, strong::assumeRunning:: resp. strong::assumeEnded::, which is a nearby +source of error and confusion. +Defaults to nil. + + +argument::clock +TempoClock, nil or collection thereof for playing streams. +Passed clocks have precedence over implicitely given clocks (running eventstream players +or tasks as strong::stream:: input) in case of resuming from pause state. +Defaults to the default TempoClock. +quant - Quant, Float, nil or collection thereof used as quant data for playing streams. +Will be used in case of resuming from pause state for running +eventstream players or tasks given as strong::stream:: input. +Defaults to Quant.default (none). + + +argument::quant +Quant, Float, nil or collection thereof used as quant data for playing streams. +Will be used in case of resuming from pause state for running +eventstream players or tasks given as strong::stream:: input. +Defaults to Quant.default (none). + + +argument::varEnvirGroups +SequenceableCollection of Collections of Integer. +Expects ordered partition of the number N of strong::specPairs:: given to strong::varCtr:: as a flat collection, +also counting multiple occurences, thus defining a mapping: variable keys -> environments. +Must be a valid partition of N (taking all integers i < N, 0 included, each only once). +Its size must not exceed the number of implicitely or explicitely given environments (see strong::varCtr::). +E.g. for 3 environments and 6 variables [[2], [0,1,5], [4,3]] would be valid. +Defaults to nil. + + +argument::envir +SequenceableCollection of Environments. +Environments to be used for variable setting in case of absent stream players, only admissible if strong::stream:: is nil. +Defaults to nil. + +argument::synthCtrGroups +SequenceableCollection of Collections of Integer. +Expects ordered partition of the number N of strong::specPairs:: given to strong::synthCtr:: as a flat collection, +also counting multiple occurences, thus defining a mapping: synth control keys -> environments. +Must be a valid partition of N (taking all integers i < N, 0 included, each only once). +Its size must not exceed the size of strong::synth::. +Defaults to nil. + + +argument::assumePlaying +Boolean, nil or collection thereof, then size must equal the number of synths given +by strong::synth:: or strong::synthCtr::. Player state information for (unregistered) synths or nodeIDs, +booleans are not accepted if corresponding items in strong::synth:: are instances of Symbol or String. +Defaults to nil. + + +argument::assumeRunning +Boolean, nil or collection thereof, then size must equal the number of synths given +by strong::synth:: or strong::synthCtr::. Player state information for (unregistered) synths or nodeIDs, +booleans are not accepted if corresponding items in strong::synth:: are instances of Symbol or String. +Defaults to nil. + +argument::assumeEnded +Boolean, nil or collection thereof, then size must equal the number of synths given +by strong::synth:: or strong::synthCtr::. Player state information for (unregistered) synths or nodeIDs, +booleans are not accepted if corresponding items in strong::synth:: are instances of Symbol or String. +Defaults to nil. + +argument::server +The server to send node messages to. +Defaults to the local server. + +discussion:: + +anchor::gt_1:: + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + +Pbind defined with variable to be controlled while playing. Pfunc's function is evaluated at playtime of each Event and gets the value of the variable ~midi in the environment in which the EventStreamPlayer will be playing. + +code:: +p = Pbind(\midinote, Pfunc { ~midi } + Pseq([0, 3, 5, 7], inf), \dur, 0.2) +:: + +VarGui to set this variable. As a pattern is given as stream arg the derived EventStreamPlayer will run in a new environment which is generated at VarGui init time. + +code:: +v = VarGui([\midi, [50, 70, \lin, 1, 55]], stream: p).gui; +:: + +Try out basic slider and player functionality. + + +strong::Playing: :: + + +image::attachments/VarGui/player_1.png:: + + +Background of player button lightening yellow. Functionality of the reset button depends on the active mode. The green background of the reset mode button indicates that the reset button will reset while playing and its background will flash yellow. + +The text field on the right side indicates whether an eventstream player or a task is playing, that on the left side shows the index of the environment in which the stream is playing. This is mainly useful if more stream players and variables of same names in different environments are involved - below the players there is a toggle button to indicate the envir index in slider name fields. + +strong::Pausing: :: + + +image::attachments/VarGui/player_2.png:: + + +Now pushing the play button will resume the stream where it has been paused, pushing the reset button (if the reset mode button has green background) will reset and play the stream. + +strong::Pausing and reset: :: + + +image::attachments/VarGui/player_3.png:: + + +This state can be reached from playing, pausing or ended stream when pushing the reset button under reset mode pause (orange background). Now only the play button can play the (reset) stream. + +strong::Stream has ended: :: + + +image::attachments/VarGui/player_4.png:: + + +E.g. if an eventstream player has encountered nil, reset button's background will become red. Now it needs the reset button to go on and its action - pause or play - depends on the active mode. + + +anchor::synth_0:: + +code:: +// synth definition with raising frequency + +( +SynthDef(\synth_0, { |devFactor = 0.1, devFreq = 10, amp = 0.1| + var freq = XLine.kr(400, 1200, 10); + Out.ar(0, SinOsc.ar(SinOsc.ar(devFreq, 0, devFactor * freq, freq), mul: amp).dup(2) * EnvGate.new) +}).add; +) +:: + +VarGui to play synth of above definition and set synth args, player functionality with different stop / renew modes. +Notification must be on. Note that pause, stop and resume actions cause sending of run / free messages, thus possibly audible clicks. To avoid that work with amp = 0 or use a gated envelope and a gate control slider with only values 0 and 1 or use Synths of limited duration as in Ex. 2. + +code:: +( +VarGui( + synthCtr: [\devFactor, [0, 0.99, \lin, 0.01, 0.5], + \devFreq, [1, 100, \lin, 0.1, 70], + \amp, [0, 0.3, \lin, 0.001, 0.1]], + synth: \synth_0 +).gui; +) +:: + + +Latency options (buttons and boxes below the player section): bundle latency of synth player actions can be set to nil, a custom value (button abbrieviation c) or global server latency (abbr. s), these two values can be set in the boxes below. Settings can be used to sync synth and stream players, if both contained in a GUI. + + +Synth players work similar as stream players, but there are more options. There is an addtional stop mode button that determines how and if renewing (creating new synth nodes of same definition) is handled. + +If, as above, a synth is given by a reference to a known SynthDef VarGui will start with a basicNew Synth object, indicated by white background of the synth's number field: + +image::attachments/VarGui/player_5.png:: + +Rate type (audio) is indicated in the field on the right. From this state a node on the server will be created by pause (newPaused) or play (new): + +image::attachments/VarGui/player_6.png:: + +Now playing and pausing work as usual. The red and blue background of the stop mode button indicates that the red stop button will become a blue renew button as soon as the Synth is stopped (node freed). In addition to the renew modes play (green) and pause (orange) there is also a renew mode basic new (white): + +image::attachments/VarGui/player_7.png:: + +If stop mode is set to renew (blue) the renew button will never change its function. Under renew mode play (green) every renew will free a Synth and immediately start a new (background flashing yellow): + +image::attachments/VarGui/player_8.png:: + +If stop mode is set to stop (red background), the stop / renew button will change to red: + +image::attachments/VarGui/player_9.png:: + +The renew mode button loses its function (greyed out) and stopping just frees the Synth: + +image::attachments/VarGui/player_10.png:: + +Now stop mode would have to be changed in order to play a new Synth. + + +General functionality buttons: + +image::attachments/VarGui/main_buttons.png:: + + +strong::Update:: + +Unless the VarGui window was generated with option updateNow set to false (which could be desired e.g. for a start with Synth defaults), variables and synth controls are set to slider values at gui init time, normally updating is not required. As for most uses VarGui would generate new separate environments for passed event patterns and task functions, there is no update mechanism implemented if these variables would be changed otherwise. Analagously controls of Synths played (and probably just yet generated) by a VarGui player could of course be set by means other than a VarGui slider action. Anyway both situations seem to be exceptional (you could hardly do so just by an oversight), though you can update to all slider values manually. + + +strong::Save:: + +Opens a dialog to save the current slider state as an array of four items: varCtr as flattened array of specPairs, synthCtr as flattened array of specPairs, varEnvirGroups, synthCtrGroups (which are only stored as groupings in case of multiple key occurences, nil optherwise). Player data and synth information is not stored. For loading from a file you would have to pass this (see the load method below). + + +strong::Stop:: + +Free all synths and stop all streams. You can also use modifier keys for stopping groups of players, e.g. Shift-click on a stream players's pause button to stop all streams. Freeing all synths in the same way only works if all synth players can be stopped by their stop buttons at this time. + + + +method::load + +Create a VarGui, that loads a stored slider state (strong::varCtr:: and strong::synthCtr:: data) from the specified file, which has probably been saved before via dialog. See also link::Tutorials/VarGui_shortcut_builds#6::. + +argument::pathname +Pathname string. + +argument::filename +Filename string. + +argument::stream +see link::Classes/VarGui#*new::. + +argument::synth +see link::Classes/VarGui#*new::. + +argument::clock +see link::Classes/VarGui#*new::. + +argument::quant +see link::Classes/VarGui#*new::. + +argument::varEnvirGroups +see link::Classes/VarGui#*new::. + +argument::envir +see link::Classes/VarGui#*new::. + +argument::synthCtrGroups +see link::Classes/VarGui#*new::. + +argument::assumePlaying +see link::Classes/VarGui#*new::. + +argument::assumeRunning +see link::Classes/VarGui#*new::. + +argument::assumeEnded +see link::Classes/VarGui#*new::. + +argument::server +see link::Classes/VarGui#*new::. + + +method::load_old + +Create a VarGui, that loads a stored slider state from the specified file. This is for loading slider states that have been saved with miSCellaneous v0.3 or earlier, thus ignoring the synth control slider label. Though old code examples will not necessarily work after just replacing load by load_old as other conventions have changed too. Maybe you'd also have to change strong::synth:: and strong::stream:: (former strong::players::) or add strong::assumePlaying:: and strong::assumeRunning:: args. + +argument::pathname +Pathname string. + +argument::filename +Filename string. + +argument::stream +see link::Classes/VarGui#*new::. + +argument::synth +see link::Classes/VarGui#*new::. + +argument::clock +see link::Classes/VarGui#*new::. + +argument::quant +see link::Classes/VarGui#*new::. + +argument::varEnvirGroups +see link::Classes/VarGui#*new::. + +argument::envir +see link::Classes/VarGui#*new::. + +argument::synthCtrGroups +see link::Classes/VarGui#*new::. + +argument::assumePlaying +see link::Classes/VarGui#*new::. + +argument::assumeRunning +see link::Classes/VarGui#*new::. + +argument::assumeEnded +see link::Classes/VarGui#*new::. + +argument::server +see link::Classes/VarGui#*new::. + + + +INSTANCEMETHODS:: + +private:: varCtr, synthCtr, synthIDs, server, streams, synthCtrIndices, varCtrNum, varNum, varColorNum, synthCtrNum, synthPlayerNum, streamPlayerNum, synthColorNum, varStrings, synthCtrStrings, saveData, envirs, varEnvirIndices, synthCtrSynthIndices, varEnvirGroups, synths, assumePlaying, assumeRunning, assumeEnded, streamEnvirIndices, varEnvirIndices, clocks, quants, window, playerSection, colors, varCtrColorPairs, synthCtrColorPairs, synthCtrSliders, varCtrSliders, synthNameBoxes, init, windowName, initVarCtrColorGroups, initSynthCtrColorGroups, effectiveSliderWidth, placeSliders, refreshVarCtrSliderLabels, possibleColumnBreakIndices, performVarArrayActions, performSynthArrayActions, performSliderActions, makeMsgList, updateVarSliders, updateSynthSliders, makeSaveValueMsgList, checkSingleSynthInput, checkArgs, envirFromSliderIndex, midiBindVarSlider, midiBindSynthSlider, initSynthSliderDict, initVarSliderDict, synthSliderDict, varSliderDict + +method::gui +Create a gui window. + +argument::sliderHeight +Gui slider height. Defaults to 18. + +argument::sliderWidth +Gui slider width. Depends on number of columns if not given explicitely. + +argument::labelWidth +Slider label width. Default depends on platform and gui scheme (70 or 75). + +argument::numberWidth +Slider numberbox width. Default depends on platform and gui scheme (40 or 45). + +argument::sliderType +One of the Symbols \standard, \smooth, \round. Both latter refer to EZSmoothSlider and EZRoundSlider from wslib (Quark), which needs to be installed for that choice. See link::#Ex. 8#Ex.8:: . Defaults to \standard. + +argument::sliderMode +One of the Symbols \jump, \move. Only relevant in case of sliderType \smooth or \round. Determines behaviour if slider field is clicked apart from the knob. See link::#Ex. 8#Ex.8::. Defaults to \jump. + +argument::playerHeight +Gui player height. Defaults to 18. + +argument::precisionSpan +Integer. Precision span to be regarded for representation of controlspec step. See link::#Ex. 9#Ex.9::. Defaults to 10. + +argument::stepEqualZeroDiv +Integer. Division to be assumed if controlspec step equals 0. See link::#Ex. 9#Ex.9::. Defaults to 100. + +argument::tryColumnNum +Integer. VarGui tries to arrange variable and control sliders in this number of columns if possible under the restrictions of strong::maxWindowWidth::, strong::sliderWidth::, strong::minPartitionSize:: and the following allow options. Internally a list of possible break indices is derived and then a partition as equal in size as possible is searched for. See link::#Ex. 5a#Ex.5a::, link::#Ex. 5b#Ex.5b::. Defaults to 1. + +argument::allowSynthsBreak +Boolean. Determines if all synth control sliders should be grouped within one column. +Defaults to true. + +argument::allowVarsBreak +Boolean. Determines if all variable control sliders should be grouped within one column. +Defaults to true. + +argument::allowSynthBreak +Boolean. Determines if control sliders of one synth should be grouped within one column. +Setting false only has an effect if synth controls are ordered in connected groups. +Defaults to true. + +argument::allowArrayBreak +Boolean. Determines if control sliders of an arrayed control (variable or synth) should be +grouped within one column. Defaults to true. + +argument::allowEnvirBreak +Boolean. Determines if control sliders of one environment should be grouped within one column. +Setting false only has an effect if environmental variables are ordered in connected groups. +Defaults to true. + +argument::minPartitionSize +Integer. Determines the minimum number of sliders of same type that may be grouped +in a different column. Defaults to 0. + +argument::sliderPriority +Symbol \var or \synth. Determines if variable or synth control sliders should be listed first. +Defaults to \var. + +argument::playerPriority +Symbol \stream or \synth. Determines if stream or synth players should be listed first. +Defaults to \stream. + +argument::streamPlayerGroups +Grouping of number of stream players (E.g. [[0], [1,2]] is a valid grouping of 3 players). +Defines control group for modifier option of stream playing. +Defaults to the grouping of all streamPlayers. + +argument::synthPlayerGroups +Grouping of number of synth players. +Defines control group for modifier option of synth playing. +Defaults to the grouping of all synthPlayers. + +argument::comboPlayerGroups +strong::*** currently not working *** :: + Grouping of number of synth and stream players. +Defines control group for modifier option of combined synth and stream playing. +Defaults to the grouping of all synth- and streamPlayers. + +argument::font +GUI font as String. Defaults to "Helvetica". + +argument::colorsLo +Low RGB value for color space, in which distinct slider colors for logical groups (synth, environment) +and background are chosen by random. Must be between 0 and 1. Defaults to 0.25 in color mode +and 0.4 in grey mode. + +argument::colorsHi +Hi RGB value for color space, in which distinct slider colors for logical groups (synth, environment) +and background are chosen by random. Must be between 0 and 1. Defaults to 0.7 in color mode +and 0.6 in grey mode. + +argument::colorDeviation +Nonnegative SimpleNumber. Determines the maximum amount of color deviation within a +logical slider group (synth, environment). Within the maximum deviation distinct colors are ramdomly chosen, +but arrayed controls get the same color. Defaults to 0.12 in color mode and 0 in grey mode. +ctrButtonGreyCenter - Float between 0 and 1. Determines the grey center from which button colors deviate +by the amount of strong::ctrButtonGreyDev::. Defaults to 0.5. + +argument::ctrButtonGreyCenter +Float between 0 and 1. Determines the grey center from which button colors deviate by the amount of strong::ctrButtonGreyDev::. Defaults to 0.5. + +argument::ctrButtonGreyDev +Float between 0 and 1. Determines the intensity of button colors deviating from +ctrButtonGreyCenter. Defaults to 0.8 in color mode and 0.65 in grey mode. + +argument::greyMode +Boolean. Determines if color or grey mode is chosen. Defaults to false. + +argument::varColorGroups +Grouping of colors of variable control sliders. Overrules automatic color grouping. +All indices < n (number of sliders) must occur exactly once. E.g. [[0], [1,2]] would be valid for n = 3. +See link::#Ex. 7#Ex.7::. + +argument::synthColorGroups +Grouping of colors of synth control sliders. Overrules automatic color grouping. +All indices < n (number of sliders) must occur exactly once. E.g. [[0], [1,2]] would be valid for n = 3. +See link::#Ex. 7#Ex.7::. + +argument::name +Symbol or String. Name to be displayed in the GUI window title bar. If not given explicitely types of +controls and players are indicated. + +argument::updateNow +Boolean. Determines if controls and variables should be set to slider values at GUI build time. +To keep sliders in sync with control this is done by default - however there may be cases when it is +desirable to start with other values, e.g. the synth's defaults. If set to false slider values will not be set +before the slider is moved or before all values are set by the update method or the corresponding button. + +argument::sliderFontHeight +Float. Adapts to sliderHeight if value nil is given (default). +Normally this doesn't have to be set except with very small strong::sliderHeight:: and/or certain fonts. + +argument::playerFontHeight +Float. Defaults to 12. + +argument::maxWindowWidth +Float. Defaults to 1500. + +argument::maxWindowHeight +Float. Defaults to 700. + + +anchor::gt_0:: + +discussion:: +SynthDef link::#synth_0:: from above, number of synth players multiplied by expanding single control. +For multiple slider and player button control see the modifier options explained in link::#Ex. 1c#Ex.1c::. + +code:: +( +v = VarGui( + synthCtr: [\devFactor, [0, 0.99, \lin, 0.01, 0.5], + \devFreq, [1, 100, \lin, 0.1, 70], + \amp, [0, 0.2, \lin, 0.001, 0.02]] ! 6, + synth: \synth_0 ! 6 +).gui; +) +:: + +GUI appearance customization: +close window (only one window per VarGui instance allowed) and try again with same or other color options. + +code:: +v.gui; + +v.gui(colorDeviation: 0, colorsLo: 0.4, colorsHi: 0.9); + +v.gui(greyMode: true); +:: + +Force slider arrangement in 2 columns, controls of each synth should only be in one column. + +code:: +v.gui(colorDeviation: 0, tryColumnNum: 2, allowSynthBreak: false); +:: + + +You can save a slider state snapshot by button / dialog to the file "XY", +only slider states (flattened spacPair collections) and groupings are stored, +you have to pass synth data with load. + +code:: +VarGui.load("PathToXY", "XY", synth: \synth_0 ! 6).gui; +:: + +method::update + +Update variables and controllers to current slider values. Like update button action. +Unless the VarGui window was generated with option strong::updateNow:: set to false, +variables and controllers are immediately set to slider values and normally updating is not required. + + +method::updateVarSliders + +Update variable sliders. + +argument::key +Variable name given as Symbol. + +argument::varNum +Integer. Index of variable occurence in given strong::varCtr::. Defaults to 0. + +argument::val +Float. May also be collection, then a sequence of variable sliders is set. + +argument::indexOffset +Integer. Updating will start at this index in case strong::key:: refers to arrayed control. Defaults to 0. + +argument::updateNow +Boolean. Determines if variables should immediately be updated with sliders. Defaults to true. + + +method::updateSynthSliders + +Update synth sliders. + +argument::key +Synth control name given as Symbol. + +argument::synthIndex +Integer. Index of synth in given strong::synthCtr::. Defaults to 0. + +argument::val +Float. Maybe also be collection, then a sequence of synth sliders is set. + +argument::indexOffset +Integer. Updating will start at this index in case strong::key:: refers to arrayed control. Defaults to 0. + +argument::updateNow +Boolean. Determines if synth controls should immediately be updated with sliders. Defaults to true. + + +method::font + +Set GUI font. + +argument::font +String. Defaults to "Helvetica". + +argument::sliderFontHeight +Integer. Defaults to 12. + +argument::playerFontHeight +Integer. Defaults to 12. + + +method::addSliderAction + +Add an action to be evaluated as sliderView's mouseUpAction. + +argument::function +Function. + +argument::type +Slider type, expects \var or \synth. Defaults to \var. + +argument::index +Integer. Slider index of given type. + +argument::envir +Environment where function should be evaluated. If not specified and strong::type:: equals \var the environment associated with the variable will be taken. + + +method::startMIDIlearn +Start learining mode for receiving MIDI cc messages for slider control, see link::#Ex. 10#Ex.10::. + +method::stopMIDIlearn +Stop learining mode for receiving MIDI cc messages for slider control, see link::#Ex. 10#Ex.10::. + + + + + +EXAMPLES:: + +anchor::Ex. 1a:: +subsection::Ex. 1a: step sequencer + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + +There are different ways to read a sequence of values from a settable array assigned to an envir variable. PLseq is convenient, for a comparison with Plazy, Pfunc etc. see link::Tutorials/PLx_suite::. +In all cases the EventStreamPlayer can be played in an arbitrary environment, from which ~seq will be taken. This holds true for Patterns with functional code like Pfunc and also for PLseq, the latter has an envir arg which defaults to \current. +Note that all PLx Patterns default to repeats = inf, ListPatterns as Pseq default to repeats = 1. + + +code:: +( +SynthDef(\synth_1a, { |freq = 400, preAmp = 5, amp = 0.1| + var src = SinOsc.ar(freq, 0, mul: preAmp).tanh ! 2; + OffsetOut.ar(0, src * amp * EnvGen.ar(Env.perc, doneAction: 2)) +}).add; +) + +( +p = Pbind( + \instrument, \synth_1a, + \preAmp, PL(\preAmp), // or Pfunc { ~preAmp } + \amp, PL(\amp), // or Pfunc { ~amp } + \midinote, PLseq(\seq) + PL(\seqAdd), + \dur, 0.2 +); +) +:: + +code::~seq:: will be set and read in the new Environment, which will be generated at VarGui init time. + +code:: +( +~specPairs = [\seq, [36, 60, \lin, 1, 36] ! 5, + \seqAdd, [0, 12, \lin, 1, 0], + \amp, [0, 0.3, \lin, 0.01, 0.1], + \preAmp, [5, 50, \lin, 0.01, 20]]; + +v = VarGui(~specPairs, stream: p).gui; +) +:: + + +anchor::Ex. 1b:: +subsection::Ex. 1b: step sequencer with MIDI + +code:: + +( +// connect a MIDI device, allNotesOff with CmdPeriod useful + +MIDIClient.init; +m = MIDIOut(0); +g = { 16.do({ |i| m.allNotesOff(i) }) }; +CmdPeriod.add(g); + + +p = Pbind( + \type, \midi, + \midiout, m, + \chan, 0, + + \amp, PL(\amp), + \midinote, PLseq(\seq) + PL(\seqAdd), + \dur, 0.2 +); +) + +( +v = VarGui([\seq, [36, 60, \lin, 1, 36] ! 5, + \seqAdd, [0, 12, \lin, 1, 0], + \amp, [0, 1, \lin, 0.01, 0.8]], + stream: p +).gui; +) + +// remove if not needed anymore + +CmdPeriod.remove(g); + +:: + +anchor::Ex. 1c:: +subsection::Ex. 1c: more step sequencers, quantization, slider modifiers + + +SynthDef from link::#Ex. 1a#Ex.1a:: , four players from a Pbind, four varCtr specifications with shifted range. + +code:: +( +p = Pbind( + \instrument, \synth_1a, + \preAmp, PL(\preAmp), + \amp, PL(\amp), + \midinote, PLseq(\seq) + PL(\seqAdd), + \dur, 0.2 +); + +~spec = 4.collect { |i| [\seq, [12*i + 24, 12*i + 48, \lin, 1, 24*i + 36] ! 5, + \seqAdd, [0, 12, \lin, 1, 0], + \amp, [0, 0.15, \lin, 0.01, 0.07 - (0.01 * i)], + \preAmp, [5, 50, \lin, 0.01, 20]] }; +) + +// run all players in sync by passing a quant arg + +v = VarGui(~spec, stream: p!4, quant: 0.2).gui(tryColumnNum: 2, streamPlayerGroups: [[0,3], [1,2]]); + + +// EventStreamPlayers derived from Pbind are run in +// four newly generated environments, +// variables, as their names are passed four times in spec, +// are automatically set in these Environments in order. +// Envir index display can be toggled with the button on the right. + + +v.envirs; // get envirs + +// try slider action with modifier keys + +:: + +note:: + +In Qt you can press modifier keys before and thereafter. Caps-lock is currently replaced by Cmd, with Cmd modifier involved you *have* to press thereafter. + +Modifiers allow to control groups of sliders, values in other sliders are clipped to the according range. The following rules apply to synth and var control sliders separately, combined synth and var controls of same name work with Caps on cocoa and Cmd elsewise (link::#Ex. 2#Ex.2::). + + +list:: +## Shift: within an arrayed control all sliders below are set to the handled value or clipped. +## Shift + Ctrl: within an arrayed control all sliders above are set to the handled value or clipped. + +## Alt: controls of same name (in different envirs or different synths) are set to the handled value or clipped. +## Alt + Shift, Alt + Shift + Ctrl: Accordingly for arrayed controls of same name. +:: + +Modifier keys also apply to synth and stream player actions, the following to synth OR stream player combinations either: + +list:: +## Shift: button action with all players +## Shift + Ctrl: button action with all players of this state +## Shift + Alt: button action with all players of this group (if synthPlayerGroups / streamPlayerGroups was defined) +## Shift + Ctrl + Alt: button action with all players of this state and group ( --- " --- ) +:: + +:: + + +anchor::Ex. 1d:: +subsection::Ex. 1d: step sequencer, stream players in same environment + + +You can try to benefit from eventstream players having variables in common. +SynthDef from link::#Ex. 1a#Ex.1a:: , second Pbind derived from p without pitch offset as parallel pattern. + +code:: +( +p = Pbind( + \instrument, \synth_1a, + \preAmp, PL(\preAmp), + \amp, PL(\amp), + \midinote, PLseq(\seq) + PL(\seqAdd), + \dur, 0.2 +); + +q = Pbindf(p, \midinote, PLseq(\seq)); + +v = VarGui([\seq, { [48, 84, \lin, 1, rrand(48, 84)] } ! 5, + \seqAdd, [0, 12, \lin, 1, 7], + \amp, [0, 0.3, \lin, 0.01, 0.07], + \preAmp, [5, 50, \lin, 0.01, 20]], + stream: [q,p].collect(_.asESP), + // so currentEnvironment is given implicitely by both ESPs and used for setting + quant: 1 + // ensures that players, also if started separately for the first time, + // will play the sequence in sync +).gui; +) + +// stopping and resuming of a single player may break parallelism +// Shift-clicked reset syncs again + +:: + +anchor::Ex. 1e:: +subsection::Ex. 1e: step sequencer and replacements with PLx + +SynthDef from link::#Ex. 1a#Ex.1a:: + +code:: + +( +// SynthDef from (1a) + +// Pbind with PL as placeholder for pitch patterns +// master VarGui with player +// start and set seq + + +p = Pbind( + \instrument, \synth_1a, + \preAmp, PL(\preAmp), + \amp, PL(\amp), + \midinote, PL(\a) + PL(\seqAdd), + \dur, 0.2 +); + + +// ESP as stream arg: variables will be read from (build time) current Environment. + +~basePat = PLseq(\seq); +~a = ~basePat; + +v = VarGui([\seq, [36, 60, \lin, 1, 36] ! 5, + \seqAdd, [0, 12, \lin, 1, 0], + \amp, [0, 0.1, \lin, 0.001, 0.05], + \preAmp, [5, 50, \lin, 0.01, 20]], + stream: p.asESP).gui(name: \seq); + +// control spec array builder for midi range + +~pitchSpec = { |key = \seq, default = 60, step = 1, lo = 24, hi = 96| + var spec = default.asArray.collect([lo, hi, \lin, step, _]); + [key, (spec.size == 1).if { spec.flatten }{ spec }]; +}; +) + + +// replace while playing: + +// chord sequence + +( +~chordPat = PLseq(\seq) + [0, 7]; +~a = ~chordPat; +) + + +// random sequence with new VarGui for lead voice bounds + +( +VarGui(~pitchSpec.(\b, [75, 85], 0.01)).gui(name: \random); + +~randPat = Pfunc { rrand(~b[0], ~b[1]) } + [0, -13.3, -17.7]; +~a = ~randPat; +) + +// accumulation sequence with new VarGui + +( +VarGui(~pitchSpec.(\accum, { rrand(40,80) }!7 )).gui(name: \accum); + +~accPat = Ptuple( [ PL(\accum), PLseq((0..6)) ] ).collect { |x| x[0].keep(x[1]) - x[1] }; +~a = ~accPat; +) + + +// switch between PLs and set values in VarGui windows + + +~a = ~basePat; + +~a = ~chordPat; + +~a = ~randPat; + +~a = ~accPat; + +:: + +anchor::Ex. 2:: +subsection::Ex. 2: stream players and synths in one VarGui + +code:: +( +// Pbind with default synth +// ~freq can be a collection, then pitch or chord are shifted by ~freqFac, +// ascending (~step > 0) or descending (~step < 0) groups of 4 items + +p = Pbind( + \dur, 0.4, + \amp, PL(\amp), + \freq, PL(\step) ** PLseq((0..3)) * PL(\freq) * PL(\freqFac) +); + +// Synth producing an ascending line, it ends as envelope params are defined, +// end notifications will be reflected in the VarGui + +SynthDef(\synth_2, { |out = 0, freq = #[600, 700, 900, 1000], freqFac = 1, + amp = 0.1, ampFac = 0.5, preAmp = 5, attTime = 0.2, relTime = 2, susTime = 6| + var src = SinOsc.ar(freq * XLine.kr(freqFac/2, freqFac, attTime + susTime), 0, mul: preAmp).tanh ! 2; + Out.ar(0, src * amp * ampFac * EnvGen.ar(Env.linen(attTime, susTime, relTime), doneAction: 2)) +}).add; +) +:: + +Try multiple slider movements with modifier keys as described in link::#Ex. 1c#Ex.1c::. As synth args and variables are identically named they can be controlled with linked slider movements using Cmd. Accordingly stream and synth player buttons can also be pressed in parallel. + +code:: +( +v = VarGui([\freq, [600, 700, 900, 1000].collect([400, 1200, \lin, 0.01, _]), + \freqFac, [0.25, 1.25, \lin, 0.01, 1], + \amp, [0.0, 0.1, \lin, 0.001, 0.03], + \step, [0.7, 1.25, \lin, 0.01, 1.03]] ! 2, + + [\freq, [600, 700, 900, 1000].collect([400, 1200, \lin, 0.01, _]), + \freqFac, [0.25, 1.25, \lin, 0.01, 1], + \attTime, [0.02, 2, \lin, 0.01, 0.2], + \relTime, [0.02, 2, \lin, 0.01, 2], + \susTime, [0.1, 10, \lin, 0.01, 5], + \amp, [0.0, 0.1, \lin, 0.001, 0.03], + \preAmp, [5, 50, \lin, 0.01, 20]] ! 2, + + p!2, + \synth_2 ! 2, + quant: 0.4 +).gui(tryColumnNum: 2, allowSynthsBreak: false, allowEnvirBreak: false +/*, colorDeviation: 0 */ +/*, sliderPriority: \synth, playerPriority: \synth */ +) +) +:: + +anchor::Ex. 3:: +subsection::Ex. 3: granular synthesis with Tasks + +code:: +( +// Synth definition for single synthesized grains, waveform to be selected by type + +SynthDef(\synth_3, { arg out = 0, freqLo = 1000, freqHi = 10000, amp = 0.1, type = 0, pan; + var env = EnvGen.kr(Env.perc(0.001, 0.003, amp),doneAction:2), freq; + freq = Rand(freqLo, freqHi); + OffsetOut.ar(out, Pan2.ar(Select.ar(type, [FSinOsc.ar(freq), Saw.ar(freq), Pulse.ar(freq)]), pan) * env) +}).add; + +// Task function definition. Although Tasks can be passed directly to VarGui as stream arg, +// Functions have the advantage that the generated Tasks (as EventStreamPlayers with given Pbinds) +// will be played in different environments automatically. + +f = { + loop { + s.sendBundle(0.2, ["/s_new","synth_3", -1,0,0, + \out, 0, + \type, ~type, + \amp, ~amp, + \freqLo, ~freqMid / ~freqIntvl.sqrt, + \freqHi, ~freqMid * ~freqIntvl.sqrt, + \pan, [1, -1].choose * ~pan] + ); + [~durShort, ~durLong].choose.wait; + } +}; +) + + +( +v = VarGui( 3.collect { |i| [\type, [0, 2, \lin, 1, i], + \freqMid, [100, 8000, \lin, 1, 500 + (2000 * i)], + \freqIntvl, [1, 2.0, \lin, 0.01, 1.4], + \amp, [0.0, 0.2, \lin, 0.01, 0.05 * (i + 1)], + \pan, [0.0, 1.0, \lin, 0.01, 0.8], + \durShort, [0.005, 0.01, \lin, 0.001, 0.01], + \durLong, [0.01, 0.05, \lin, 0.001, 0.02] + ] }, + // f!3 doesn't collect Functions as there is already a short notation for + // collecting Function values of Integers, e.g. (_*3)!5 + stream: 3.collect { f } +).gui(colorDeviation: 0); +) +:: + + +anchor::Ex. 4a:: +subsection::Ex. 4a: interaction of synth and stream players, control synth controlling pbind synths + + +Pbind producing a sequence of short events, each pbind-driven synth reading frequency from a control bus + +code:: +( +// control synth + +SynthDef(\synth_4_kr, {|out = 0, devFreq = 0.5, midiCenter = 60, midiDev = 10| + Out.kr(out, LFDNoise3.kr(devFreq, midiDev, midiCenter)) +}).add; + +// audio synth +// mix of sine and pulse, interval, basic frequency read from control bus + +SynthDef(\synth_4_ar, {|out = 0, in = 0, soundMix = 0.5, pulseWidth = 0.5, indexLR = 0, + att = 0.005, rel = 0.1, amp = 0.1, midiInt = 3, midiAdd = 0| + var mix, freq1, freq2, fr; + freq1 = (In.kr(in, 1) + midiAdd).midicps; + freq2 = freq1 * midiInt.midicps / 0.midicps; + fr = [Select.kr(indexLR, [freq1, freq2]), Select.kr(1-indexLR, [freq1, freq2])]; + mix = (SinOsc.ar(fr, 0, amp) * (1 - soundMix)) + (Pulse.ar(fr, pulseWidth, amp) * soundMix); + Out.ar(out, EnvGen.ar(Env.perc(att, rel), doneAction: 2) * mix) +}).add; + + +x = Pbind(\instrument, \synth_4_ar, + \dur, 0.1, + \in, PL(\in), + \amp, PL(\amp), + \midiInt, Pfunc { rrand(~int[0], ~int[1]) }, // interval bounds + \rel, Pfunc { ~rel.choose }, // short and long release time + \soundMix, Pwhite(0.1, 0.9), + \indexLR, PLrand([0, 1]) +); +) + + +( +// in GUI start control synth before EventStreamPlayer +// then try playing with sliders and pausing / resuming the control synth + +c = Bus.control(s,1).index; + +VarGui([\in, [c, c, \lin, 1, c], // bus fixed, display only + \int, [3,4].collect([0, 19, \lin, 0.1, _]), + \rel, [0.01, 0.15].collect([0.01, 0.4, \lin, 0.005, _]), + \amp, [0.0, 0.3, \lin, 0.01, 0.15]], + + [\midiCenter, [45, 80, \lin, 0.1, 70], + \midiDev, [0, 20, \lin, 0.1, 20], + \devFreq, [0.1, 15, \lin, 0.1, 1], + \out, c], // bus fixed, display only + x, \synth_4_kr +).gui(sliderPriority: \synth, playerPriority: \synth) +) +:: + + +anchor::Ex. 4b:: +subsection::Ex. 4b: interaction of synth and stream players, pbind and control synth controlling audio synth + + +Audio synth controlled by LFO pitch synth and a Pbind setting synth args (like Pmono). PLx ListPatterns taken here as they default to repeats = inf. + +code:: +( +y = Pbind(\type, \set, + // sequencing of durations and amplitudes quite fixed, just global tempo and amp control + \dur, PLshufn([0.15, 0.15, 0.35]) / PL(\tempo), + \amp, PLrand([0.05, 0.12, 0.2]) * PL(\amp), + \soundMix, PLseq([0.1, 0.5, 0.9]), + \indexLR, PLrand([0, 1]), + + \midiInt, Pfunc { rrand(0, ~intMax) }, + \args, [\amp, \soundMix, \midiInt, \indexLR] // args to be set must be listed +); + + +// In GUI start control synth (#0) first, then audio synth (#1) and EventStreamPlayer, +// then try playing with sliders and pausing / resuming the control synth and EventStreamPlayer. + + +d = Bus.control(s,1).index; + +VarGui([ \tempo, [0.7, 1.2, \lin, 0.01, 1], + \amp, [0.0, 1.5, \lin, 0.01, 0.7], + \intMax, [-12, 12.0, \lin, 0.1, 8]], + + [[\midiCenter, [45, 80, \lin, 0.1, 60], + \midiDev, [0, 20, \lin, 0.1, 1.5], + \devFreq, [0.1, 15, \lin, 0.1, 10], + \out, d], // just display bus indices + [\in, d, + \out, 0, + \rel, 10000]], // ignore Env.perc definition + + y, [\synth_4_kr,\synth_4_ar] +).gui(sliderPriority: \synth, playerPriority: \synth) +) +:: + +anchor::Ex. 5a:: +subsection::Ex. 5a: reordering of var control + + +One may want to have controls grouped per name, not per Environment (resp. Pbind, Task, Synth), +this can be achieved by passing a flat (not grouped) array of specPairs and the appropriate grouping, +there are methods implemented for that special case of reordering: + +SynthDef from link::#Ex. 1a#Ex.1a:: + +code:: +( +p = Pbind( + \instrument, \synth_1a, + \preAmp, PL(\preAmp), + \amp, PL(\amp), + \midinote, Pfunc { ~midi + rrand(~midiDev.neg, ~midiDev) }, + \dur, 0.2 +); + +// flat specPairs array without multiple keys + +~specPairs = [\midi, [24, 80, \lin, 1, 36], + \midiDev, [0, 0.5, \lin, 0.01, 0.25], + \amp, [0, 0.3, \lin, 0.01, 0.1], + \preAmp, [5, 50, \lin, 0.01, 25]]; + +// get flat expansion of specPairs and appropriate groups + +~specPairsDupped = ~specPairs.specPairsDup(5).postln; +~specPairsGroups = ~specPairs.specPairsDupGroups(5); +) + + +// parallel slider movement with Alt (not an array here but identically named variables in different envirs) +// parallel player action with Shift-click + +( +v = VarGui( + ~specPairsDupped, + stream: p!5, + varEnvirGroups: ~specPairsGroups, + quant: 0.2 +).gui(colorDeviation: 0); +) + +// compare implicit mapping by passing a grouped collection of specPairs as varCtr arg + +( +v = VarGui( + ~specPairs!5, + stream: p!5, + quant: 0.2 +).gui(colorDeviation: 0); +) + +// Note that in the case of not connected varEnvirGroups the column break gui option +// allowEnvirBreak is ignored, means always set to true. + +:: + +anchor::Ex. 5b:: +subsection::Ex. 5b: reordering of synth control + +code:: +( +SynthDef(\synth_5b, { |devFactor = 0.1, devFreq = 10, amp = 0.1| + var freq = XLine.kr(2000, 400, 3); + Out.ar(0, Pan2.ar(SinOsc.ar(SinOsc.ar(devFreq, 0, devFactor * freq, freq), mul: amp), LFDNoise3.ar(2)) * EnvGate.new) +}).add; + + +~specPairs = [\devFactor, [0, 0.99, \lin, 0.01, 0.5], + \devFreq, [1, 100, \lin, 0.1, 70], + \amp, [0, 0.02, \lin, 0.001, 0.01]]; + +~num = 10; // ~num = 20; use gui args sliderHeight and tryColumnNum for larger nums + +v = VarGui( + synthCtr: ~specPairs.specPairsDup(~num), + synth: \synth_5b ! ~num, + synthCtrGroups: ~specPairs.specPairsDupGroups(~num) +).gui(colorDeviation: 0 /* ,sliderHeight: 12, tryColumnNum: 2 */); +) + +// compare implicit mapping by passing a grouped collection of specPairs as synthCtr arg + +( +~num = 10; + +v = VarGui( + synthCtr: ~specPairs ! ~num, + synth: \synth_5b ! ~num +).gui(colorDeviation: 0); +) + +// Note that in the case of not connected synthCtrGroups the column break gui option +// allowSynthBreak is ignored, means always set to true. +:: + + +anchor::Ex. 6a:: +subsection::Ex. 6a: non standard usage: passing Synths + +Passing symbols or strings as synth args, refering to known (added) SynthDefs, is recommended. +However also Synths or nodeIDs can be given, but the player would have to know their state, +so Synths should be registered. + +code:: +( +SynthDef(\sine, { |freq = 400, amp = 0.1| Out.ar(0, SinOsc.ar(freq, mul: amp)) }).add; +SynthDef(\pulse, { |freq = 400, amp = 0.1| Out.ar(0, Pulse.ar(freq, mul: amp)) }).add; +) + +( +x = Synth(\sine).register; +y = Synth(\pulse).register; +) + +// pause pulse + +y.run(false); + +// VarGui checks state, definition is known, new Synths of same definition can be started + +v = VarGui(synth: [x,y]).gui; + + +// With nodeIDs or non-registered Synths VarGui demands addtional state info + +( +x = { SinOsc.ar(400, mul: 0.1) }.play; +y = { Pulse.ar(700, mul: 0.01) }.play; +) + +y.run(false); + +// no new Synths from temporary synth definition + +v = VarGui(synth: [x,y], assumePlaying: true, assumeRunning: [true, false]).gui; + +:: + + +anchor::Ex. 6b:: +subsection::Ex. 6b: non standard usage: setting sliders from outside + +code:: +( +// synth definition with arrayed control + +SynthDef(\vibes, { |amp = 0.005, devFactor = 0.1, devFreq = 10, devDisturb = 0.1| + var freq = \midi.kr(20!30).midicps; + Out.ar(0, Mix.fill(30, {|i| [i.even.if {0}{1}, i.even.if {1}{0}] * + SinOsc.ar(SinOsc.ar(devFreq * LFDNoise3.ar(0.5, devDisturb), + 0, devFactor * freq[i], freq[i]), mul: amp) } )) +}).add; +) + + +// open gui with random midi values first, start playing + +( +v = VarGui( + synthCtr: [\devFactor, [0, 0.01, \lin, 0.001, 0.005], + \devFreq, [0, 1, \lin, 0.01, 0.5], + \devDisturb, [0, 0.99, \lin, 0.01, 0.1], + \amp, [0, 0.05, \lin, 0.001, 0.005], + \midi, 30.collect { [45.0, 95, \lin, 0.01, rrand(45,95)] } + ], + synth: \vibes +).gui; +) + + +// update whole control array with one random value +// second arg synth index + +v.updateSynthSliders(\midi, 0, rrand(50, 80.0)!30); + + +// update random group, fourth arg start index +// evaluate several times + +v.updateSynthSliders(\midi, 0, rrand(50, 80.0)!5, rrand(0,25)); + + +// phase shift gui + +( +p = Pbind( + \dur, 0.07, + \type, \rest, + \i, Pseries(), + \do, Pfunc { |e| v.updateSynthSliders(\midi, 0, (e.i / 5).sin * 25 + 70, e.i % 30) } +).play(AppClock) +) + + +// stop updating + +p.stop; + +:: + + +anchor::Ex. 6c:: +subsection::Ex. 6c: non standard usage: setting values for plotting + +code:: + +// By setting a slider hook arbitrary actions can be triggered with lifting a slider handle. +// A slider hook can e.g. trigger the refreshing of a plot of parametric functions. + +( +p = Plotter("move slider to plot", bounds: Rect(200, 200, 700, 500)); + +// number of plots in window + +n = 5; + +// VarGui with random init params + +v = VarGui({ [ + \a, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2, + \b, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2] } ! n, + envir: () ! n +).gui; + +// definition of parametric function, environment of variables will be defined later on + +f = { |x| (x * ~a[0]).sin * ~b[0] + ((x * ~a[1]).sin * ~b[1]) }; + +// evaluation points + +x = (0, 0.1..20); + +// add slider hook, index not defined, everything will be refreshed with arbitrary sliderView mouseUp + +v.addSliderAction { p.value_(v.envirs.collect { |e| f.inEnvir(e).(x) }).refresh }; + +// slider movements affecting more than one plot can be achieved with modifier combinations including alt +) + +:: + +anchor::Ex. 7:: +subsection::Ex. 7: color grouping + +code:: + +// By default color grouping is based on logical separations: +// different synths and environments, different variables and controls, arrays. + +// But apart of that there might exist more related controls +// and you'd like to overrule default color grouping. +// Especially with a large number of sliders a useful color grouping makes +// control much more convenient (also see Buffer Granulation examples). + + +// SynthDef where freq and amp controls could be grouped + +( +SynthDef(\synth_7, { |freq = 400, freqOsc = 10, freqDev = 0.03, preAmp = 5, amp = 0.1| + var src = SinOsc.ar(SinOsc.ar(freqOsc, 0, freqDev * freq, freq), 0, mul: preAmp).tanh ! 2; + Out.ar(0, src * amp * EnvGate()) +}).add; +) + + + +// The clumps method is fine for grouping + +( +~g = (0..4).clumps([3,2]); + +~synthCtr = [ + \freq, [20, 5000, \exp, 0, 80], + \freqOsc, [0, 20.0, \lin, 0, 10], + \freqDev, [0, 0.5, \lin, 0, 0.03], + \preAmp, [5, 50, \lin, 0.01, 20], + \amp, [0, 0.2, \lin, 0.01, 0.03] +]; +) + + +// GUI with one Synth + +VarGui(synth: \synth_7, synthCtr: ~synthCtr).gui(synthColorGroups: ~g); + + +// GUI with two Synths and separate colors for the second + +VarGui(synth: \synth_7!2, synthCtr: ~synthCtr!2).gui(synthColorGroups: ~g+5 ++ ~g); + + +// Duplicating colors + +( +~h = [~g, ~g+5].flop.collect(_.flat); // or ~h = ~g.collect { |x| x + 5 ++ x }; + +VarGui(synth: \synth_7!2, synthCtr: ~synthCtr!2).gui(synthColorGroups: ~h); +) + +:: + + +anchor::Ex. 8:: +subsection::Ex. 8: EZSmoothSlider, EZRoundSlider + +code:: + +// EZSlider is used as default slider type. +// With wslib installed (Quark) you can choose +// its slider types EZSmoothSlider and EZRoundSlider +// as gui option + + +VarGui(synth: \default).gui(sliderType: \smooth); + +VarGui(synth: \default).gui(sliderType: \round); + + +// you can choose from their modes \jump (\default) or \move as general option + +VarGui(synth: \default).gui(sliderType: \smooth, sliderMode: \move); + + +// if you want to set individually you can refer to sliders directly +// compare different slider behaviour of Synth 0 and 1 + +( +v = VarGui(synth: \default!2).gui(sliderType: \smooth, sliderMode: \move); + +v.synthCtrSliders.first.sliderView.mode_(\jump) +) + +// For multiple slider handling see Ex 1c. + +// Very small numbers are forced to exponential notation (see also Ex. 9). + +:: + + +anchor::Ex. 9:: +subsection::Ex. 9: slider step precision + +code:: + +// VarGui makes some adaptions concerning slider step sizes and rounding +// depending on passed controlspec step size +// in order to unify slightly different behaviour of GUI kits +// and set parameters automatically also for very small step sizes. + +// For this reason (and because of multiple slider handling) +// scaling by modifiers is disabled by default. +// You can have a finer control by moving the mouse over the NumberBox, +// this also works with modifiers for multiple sliders (Ex.1c, Ex.2). + + +VarGui([\a, [0, 1, \lin, 0.1, 0]]).gui; + +VarGui([\a, [0, 1, \lin, 0.01, 0]]).gui; + +VarGui([\a, [0, 1, \lin, 0.001, 0]]).gui; + + + +VarGui([\a, [0, 100, \lin, 1, 0]]).gui; + +VarGui([\a, [0, 10000, \lin, 10, 0]]).gui; + +VarGui([\a, [0, 10000, \lin, 100, 0]]).gui; + + +// controlspec step = 0 causes division of range by 100 + +VarGui([\a, [0, 1, \lin, 0, 0]]).gui; + +VarGui([\a, [0, 0.1, \lin, 0, 0]]).gui; + +VarGui([\a, [0, 1000, \lin, 0, 0]]).gui; + + +// division parameter can be changed + +VarGui([\a, [0, 1000, \lin, 0, 0]]).gui(stepEqualZeroDiv: 10); + +VarGui([\a, [0, 0.1, \lin, 0, 0]]).gui(stepEqualZeroDiv: 10); + + + +// in case of very long numbers it may be necessary to adapt numberWidth + +VarGui([\a, [0, 0.001, \lin, 0, 0]]).gui(numberWidth: 70); + + +// In general ranges are adapted to step size if necessary - +// this is ControlSpec's standard behaviour + +VarGui([\a, [1000, 5000, \lin, 1000.001, 0]]).gui(numberWidth: 70); + + +// a rather exotic option: +// precisionSpan defaults to 10 and fails here ... + +VarGui([\a, [10000000, 50000000, \lin, 10000000.001, 0]]).gui(numberWidth: 100); + + +// ... but resolves this + +VarGui([\a, [10000000, 50000000, \lin, 10000000.001, 0]]).gui(precisionSpan: 12, numberWidth: 100); + +:: + + + +anchor::Ex. 10:: +subsection::Ex. 10: MIDI learn functionality with sliders + + +Connect your midi device, maybe you have to restart SC then. As gui example e.g. take SynthDefs and Pbind from link::#Ex. 4a#Ex.4a::, evaluate both code blocks. It must be possible to refer to the VarGui instance (e.g. v = VarGui(...)) + + +code:: + +// start MIDI + +MIDIIn.connectAll + +// on your device define desired knobs, faders etc. to send cc messages +// probably you want to control different parameters, so define different cc numbers + +// start learning mode + +v.startMIDIlearn + + +// now assignment can happen by selecting a slider box +// it gets a focus (blue border, sometimes not very well visible) +// then send midi cc with the fader/knob of your choice +// sending data with different cc number, while the same slider is still or again selected, will overwrite + +// end of learning has to be defined + +v.stopMIDIlearn + + +// adaption of control can be done by recalling the learning mode + +v.startMIDIlearn + +... + +v.stopMIDIlearn + +:: + + + diff --git a/HelpSource/Classes/ZeroXBufRd.schelp b/HelpSource/Classes/ZeroXBufRd.schelp new file mode 100755 index 0000000..6ae18f9 --- /dev/null +++ b/HelpSource/Classes/ZeroXBufRd.schelp @@ -0,0 +1,1110 @@ +CLASS:: ZeroXBufRd +summary:: reads consecutive sequences of segments between zero crossings from one or more buffers with demand-rate control +categories:: Libraries>miSCellaneous>ZeroX ugens +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/TZeroXBufRd, Classes/ZeroXBufWr, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies + +DESCRIPTION:: + +ZeroXBufRd is for consecutive reading of segments between zero crossings (half wavesets) from one or more buffers, whereby several reading and processing parameters can be sequenced with demand rate ugens. Full waveset sequences can so be generated as a special case. It needs analysis data prepared with link::Classes/ZeroXBufWr::. For triggering possibly overlapped (half) wavesets see link::Classes/TZeroXBufRd::. ZeroXBufRd / TZeroXBufRd can be used for a number of synthesis / processing techniques in a field between wavesets [1, 4, 5], pulsar synthesis [1, 3], buffer modulation and rectification (which are both a kind of waveshaping) and stochastic concatenation methods [2, 6]. There are already existing SC waveset implementations like Alberto de Campo's Wavesets quark (https://github.com/supercollider-quarks/quarks) and Olaf Hochherz's SPList (https://github.com/olafklingt/SPList), which do language-side analysis and Fabian Seidl's RTWaveSets plugin (https://github.com/tai-studio/RTWaveSets). My focus has been server-side analysis and demand rate ugen control of half waveset parameters as well as multichannel and buffer switch options. Realtime control while analysis is possible, as long as reading is only refering to already analysed sections, but clearly most flexibility is given with a fully analysed buffer, which can also be done in quasi realtime. + +note:: +Depending on the multichannel sizes and the options used (rate and dir sequencing) it might be necessary to increase server resources, i.e. the number of interconnect buffers and / or memory size (e.g. s.options.numWireBufs = 256; s.options.memSize = 8192 * 32; s.reboot). Because of overlappings this is more relevant with TZeroXBufRd than with ZeroXBufRd. +:: + +note:: +Often it pays to adjust zero crossings in the sound buffer effectively to 0, that way sawtooth-like interpolation artefacts can be avoided. See link::#Ex. 7:: and link::Classes/TZeroXBufRd#Ex. 1:: and link::Classes/ZeroXBufWr#Ex. 2::. +:: + +note:: +The reading of consecutive half wavesets is implemented with Sweep and retriggering. For that reason each played back half waveset has a minimum length of 2 samples. In the case of adjusted zero crossings that immediately follow each other this can lead to flat sections of a few samples length. Normally this is irrelevant, but you might check setting ZeroXBufWr's flag 'adjustZeroXs' to 2, see link::#Ex. 7:: and link::Classes/ZeroXBufWr#Ex. 2::. +:: + +note:: +Demand rate UGens in ZeroXBufRd / TZeroXBufRd should always use inf as repeats arg, this is of course not necessary for nested ones. You might pass a length arg though (link::#Ex. 5::). +:: + +note:: +For avoiding too long half wavesets it might be useful to apply LeakDC resp. a high pass filter before analysis. +:: + +note:: +In rare cases I noticed corrupted buffers in multi buffer examples for no obvious reason. +:: + +note:: +For full functionality at least SC 3.7 is recommended (rate sequencing doesn't work in 3.6) +:: + + +subsection::Credits +Thanks to Tommaso Settimi for an inspiring discussion, which gave me a nudge to tackle these classes. + + +subsection::References + +numberedList:: + +## de Campo, Alberto. "Microsound" In: Wilson, S., Cottle, D. and Collins, N. (eds). 2011. The SuperCollider Book. Cambridge, MA: MIT Press, 463-504. + +## Luque, Sergio (2006). Stochastic Synthesis, Origins and Extensions. Institute of Sonology, Royal Conservatory, The Netherlands. https://sergioluque.com + +## Roads, Curtis (2001). Microsound. Cambridge, MA: MIT Press. + +## Seidl, Fabian (2016). Granularsynthese mit Wavesets für Live-Anwendungen. Master Thesis, TU Berlin. https://www2.ak.tu-berlin.de/~akgroup/ak_pub/abschlussarbeiten/2016/Seidl_MasA.pdf + +## Wishart, Trevor (1994). Audible Design. York: Orpheus The Pantomime Ltd. + +## Xenakis, Iannis (1992). Formalized Music. Hillsdale, NY: Pendragon Press, 2nd Revised edition. + +:: + + +CLASSMETHODS:: + + +method::ar + +Creates a new ZeroXBufRd ar UGen. + + +argument::sndBuf +Buffer or SequenceableCollection of Buffers to read the data from, data must correspond to strong::zeroXBuf::. + +argument::zeroXBuf +Analysis Buffer resp. SequenceableCollection of such, prepared with link::Classes/ZeroXBufWr::. Must refer to data passed to strong::sndBuf::. + +argument::bufMix +A Number indicating the strong::sndBuf:: index, a demand rate or other ugens returning strong::sndBuf:: indices or a SequenceableCollection of such. In contrast to other args combinations of demand rate and normal ugens are not valid. If strong::bufMix:: equals nil (default) the size of the returned signal equals the size of strong::sndBuf::, otherwise it equals its own size. + +argument::zeroX +A Number indicating the index in strong::zeroXBuf::, a demand rate or other ugens returning strong::zeroXBuf:: indices or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::zeroX:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 0. + + +argument::power +Used for processing the buffer signal according to the formula: sig ** strong::power:: * strong::mul:: + strong::add:: per half waveset. Must be a positive Number, a demand rate or other ugens returning strong::power:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::power:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 1. + +argument::mul +Used for processing the buffer signal according to the formula: sig ** strong::power:: * strong::mul:: + strong::add:: per half waveset. Must be a Number, a demand rate or other ugens returning strong::mul:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::mul:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 1. + +argument::add +Used for processing the buffer signal according to the formula: sig ** strong::power:: * strong::mul:: + strong::add:: per half waveset. Must be a Number, a demand rate or other ugens returning strong::add:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::add:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. Defaults to 0. + + +argument::rate +Determines the playback rate per half waveset together with strong::rateMul::. Must be a positive Number, a demand rate or other ugens returning strong::rate:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::rate:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. In contrast to other args combinations of demand rate and normal ugens are not valid for implementational reasons. Though you can pass a demand rate ugen here and a normal ugen to strong::rateMul:: (or vice versa) which are then multiplied per half waveset. Defaults to 1. + +argument::rateMul +Determines the playback rate per half waveset together with strong::rate::. Must be a positive Number, a demand rate or other ugens returning strong::rateMul:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::rateMul:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. In contrast to other args combinations of demand rate and normal ugens are not valid for implementational reasons. Though you can pass a normal ugen here and a demand rate ugen to strong::rate:: (or vice versa) which are then multiplied per half waveset. Defaults to 1. + +argument::dir +Determines the playback direction of half wavesets. Must be +1 or -1, a demand rate or other ugens returning strong::dir:: values or a SequenceableCollection of such. If in this case the overall multichannel size determined by strong::sndBuf:: or strong::bufMix:: is larger than the size of strong::dir:: and the latter contains demand rate ugens, they must all be wrapped into Functions for being used more than once. In contrast to other args combinations of demand rate and normal ugens are not valid. Defaults to 1. + + +argument::interpl +Determines the interpolation type for the BufRd ugens. Must equal 1 (no), 2 (linear) or 4 (cubic) or a SequenceableCollection of these numbers. Defaults to 4. + +argument::dUniqueBufSize +Determines the buffer size for Dunique objects which have to be used in the case of demand rate ugens passed to strong::rate::, strong::dir:: or strong::bufMix::. See link::#Ex. 2::. Must be an Integer or a SequenceableCollection of Integers. Defaults to 1048576. + +argument::length +Determines the number of triggers before release of the overall asr envelope. Can be a Sequenceable Collection too. Overruled by strong::maxTime:: if this is reached before. Defaults to inf. + +argument::maxTime +Determines the time before release of the overall asr envelope. Can be a Sequenceable Collection too. Overruled by strong::length:: if this is reached before. Defaults to inf. + + +argument::att +Attack time of overall asr envelope or SequenceableCollection thereof. Defaults to 0. + +argument::rel +Release time of overall asr envelope or SequenceableCollection thereof. Defaults to 0. + +argument::curve +Curve of overall asr envelope or SequenceableCollection thereof. Defaults to -4. + +argument::doneAction +Done action of overall asr envelope or SequenceableCollection thereof. Defaults to 0. + + + + +section::Examples + + +code:: +( +// boot with extended resources, might be needed for some examples +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.options.memSize = 8192 * 32; +s.reboot; +) +:: + + +anchor::Ex. 1:: +subsection::Ex. 1: Basic usage + + +code:: +( +b = Buffer.alloc(s, 2000); +z = Buffer.alloc(s, 200); +) + +// analyse a short snippet of ring modulation + +( +{ + var src = SinOsc.ar(300) * SinOsc.ar(120) + SinOsc.ar(30) * 0.1; + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2); + Silent.ar +}.play +) + + +// check the waveform + +b.plot + + + +// loop 3rd half waveset +// compare plot and scope + +x = { ZeroXBufRd.ar(b, z, zeroX: 2) }.play + +s.scope + +x.release + + +// loop a whole waveset +// demand rate ugens in ZeroXBufRd should always use inf as repeats arg + + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf)) }.play + +x.release + + +// Note that the distance between zero crossings can be very short, +// here the half waveset at index 5 (735-740) has a length of only 5 samples and the amplitude is low. + +// zero crossing indices + +z.loadToFloatArray(action: { |b| b.postln }) + + +// the signal is hardly audible, but there as freqscope shows + +x = { ZeroXBufRd.ar(b, z, zeroX: 5) }.play + +s.freqscope + +x.release + + + +// loop a group of 3 wavesets + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq((1..6), inf)) }.play + +x.release + + + +// sequence multiplier for half wavesets + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), mul: Dseq([1, 0.2], inf)) }.play + +x.release + + +// mul can be used for rectifying effects + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq((1..2), inf), mul: Dseq([0, 1, 1], inf)) }.play + +x.release + + + +// add an offset sequence, this results in a pulse-like effect + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), add: Dseq([-0.05, 0.05], inf)) * 0.5 }.play + +x.release + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), add: Dseq([0.05, -0.05], inf)) * 0.5 }.play + +x.release + + + +// half wavesets can also get a power, +// per waveset the signal is calculated according to +// sig ** power * mul + add + +// be careful with this arg moving away from 1 ! +// high power values can result in loud signals if the source has values outside [-1, 1] and +// small power values can also become loud with source values near zero + + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), power: 0.7) }.play + +x.release + + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), power: Dseq([0.8, 1.7, 1], inf)) }.play + +x.release +:: + + + +anchor::Ex. 2:: +subsection::Ex. 2: The 'rate' and 'dir' args + + +code:: +// needs Buffers from Ex.1 + +s.scope + + +// playback rates can be defined generally ... + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), rate: 2.5) }.play + +x.release + + +// ... or as sequence + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), rate: Dseq((1..3), inf)) }.play + +x.release + + +// slightly different: here the whole waveset gets one rate + +x = { ZeroXBufRd.ar(b, z, zeroX: Dseq([1, 2], inf), rate: Dstutter(2, Dseq((1..3), inf))) }.play + +x.release + + + +// with the dir argument set to -1 the half wave set is reversed + +x = { ZeroXBufRd.ar(b, z, zeroX: 7) }.play + +x.release + + +// compare scope, no audible difference here + +x = { ZeroXBufRd.ar(b, z, zeroX: 7, dir: -1) }.play + +x.release + + +// dir can also be sequenced + +x = { ZeroXBufRd.ar(b, z, zeroX: 7, dir: Dseq([1, 1, -1], inf)) }.play + +x.release + + +// When demand rate ugens are used for 'rate' or 'dir' args ZeroXBufRd employs Dunique objects. +// This means that Buffers have to be allocated for counting, and if the Buffer is full +// the synthesis fails. By default a value of 1048576 is defined for 'dUniqueBufSize'. +// This should be sufficient for at least some minutes auf audio in average use cases. + +// However with very fast triggering you might want to pass a higher value. +// With multichannel applications it might be necessary then to run the server with higher memSize. + + +// With a deliberately bad (low) size, sequencing fails after a second + +x = { ZeroXBufRd.ar(b, z, zeroX: 1, rate: Dseq([1, 2], inf), dUniqueBufSize: 1024) }.play + +x.release + +// From low values you can roughly estimate the needed dUniqueBufSize to safely run your application +// for a given time +:: + + + + +anchor::Ex. 3:: +subsection::Ex. 3: Passing ordinary UGens as args + +code:: +// needs Buffers from Ex.1 + +// values are sampled and hold for the duration of the segments + +s.scope + +x = { ZeroXBufRd.ar(b, z, zeroX: SinOsc.ar(SinOsc.ar(0.1).range(0.2, 5)).range(1, 15)) }.play + +x.release + + +// more fun with moving rates + +( +x = { + ZeroXBufRd.ar( + b, z, + zeroX: LFDNoise3.ar(SinOsc.ar(0.1).range(1, 10)).range(1, 15), + rate: SinOsc.ar(SinOsc.ar(0.3).range(0.2, 5)).exprange(1, 5) + ) ! 2 +}.play +) + +x.release + + +// warp effect with accelerating rates + +( +x = { + ZeroXBufRd.ar( + b, z, + zeroX: LFDNoise3.ar(SinOsc.ar(0.1).range(1, 10)).range(1, 15), + rate: Dseq((1..50) / 20 + 0.5, inf) + ) ! 2 +}.play +) + +x.release + + +// also combinations of demand rate and ordinary ugens are possible +// though with the exception of rate, dir and bufMix args + +( +x = { + ZeroXBufRd.ar( + b, z, + zeroX: LFDNoise3.ar(SinOsc.ar(0.1).range(1, 10)).range(1, 15), + rate: Dstutter(Dwhite(10, 100), Dwhite(0.5, 2)), + mul: Dseq([0.2, 0.2, 1], inf) * LFDNoise3.ar(5).range(1, 5) + ) ! 2 +}.play +) + +x.release + + +// for combinations of normal and demand ugens with the rate arg rateMul can be used + +( +x = { + ZeroXBufRd.ar( + b, z, + zeroX: LFDNoise3.ar(SinOsc.ar(0.1).range(1, 10)).range(1, 15), + rate: Dstutter(Dstutter(3, Dwhite(1, 5)), Dseq([0.5, 1, 1.5], inf)), + rateMul: LFDNoise3.ar(5).range(1, 5) + ) ! 2 +}.play +) + +x.release + + +// free resources + +( +b.free; +z.free; +) +:: + + +anchor::Ex. 4:: +subsection::Ex. 4: Multichannel usage and the 'bufMix' arg + +code:: +// Without passing a bufMix arg the size of the returned signal is determined by the buffer input. +// It may be a single channel buffer or an array of single channel buffers, +// in correspondence with the analysis buffer(s) - multichannel buffers are not allowed. +// If bufMix is passed, it determines the size of the returned signal, +// its components can be demand rate or other ugens to control switching between buffers per half waveset. + +// Note: buffer switching can become CPU-demanding with a lot of Buffers +// as for fast switching it is necessary to play all in parallel + +( +// boot with extended resources +s = Server.local; +Server.default = s; +s.options.numWireBufs = 256; +s.options.memSize = 8192 * 32; +s.reboot; +) + + +// prepare 3 buffers + +( +b = { Buffer.alloc(s, 1000, 1) } ! 3; +z = { Buffer.alloc(s, 100, 1) } ! 3; +) + +// fill with basic waveforms +( +{ + var src = [ + SinOsc.ar(400), + LFTri.ar(400), + SinOsc.ar(400) ** 10 + ]; + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2); + Silent.ar +}.play +) + + + +s.scope(3) + +// play 3 channels + +x = { ZeroXBufRd.ar(b, z, zeroX: 1) * 0.1 }.play + +x.release + + +// play from 1st buffer + +x = { ZeroXBufRd.ar(b, z, bufMix: 0, zeroX: Dseq([0, 1], inf)) * 0.2 }.play + +x.release + + + +// play from 3rd and 1st buffer +// to use equally defined demand rate ugens for both, wrap them into a Function + +x = { ZeroXBufRd.ar(b, z, bufMix: [2, 0], zeroX: { Dseq([0, 1], inf) }) * 0.1 }.play + +x.release + + +// play 2 channels with different zeroX sequences + +( +x = { + ZeroXBufRd.ar( + b, z, + bufMix: [1, 2], + zeroX: [Dseq([0, 0, 1], inf), Dseq([0, 1], inf)] + ) * 0.1 +}.play +) + +x.release + + + + +// buffers can be switched per half waveset + + +x = { ZeroXBufRd.ar(b, z, bufMix: Dseq([0, 1, 2], inf), zeroX: 2, mul: 0.3) }.play + +x.release + + +x = { ZeroXBufRd.ar(b, z, bufMix: Dseq([0, 1], inf), zeroX: Dseq([1, 2], inf), mul: 0.3) }.play + +x.release + + + +// Gendy-like texture + +( +x = { + ZeroXBufRd.ar( + b, z, + bufMix: { Dseq([0, 1, 2], inf) } ! 2, + zeroX: 2, + mul: 0.1, + // array of Drands with different offset + // the average of the Drand output is 50, + // so on average 1/4 is added to x + // and the harmonic relation of L:R is 5:7 -> tritone + rate: [1, 1.5].collect { |x| Drand((1..100) / 200 + x, inf) } + ) +}.play +) + +x.release + + +// bufMix determines size +// other args are expanded accordingly + +( +x = { + ZeroXBufRd.ar(b, z, + bufMix: { Dseq([0, 1, 2], inf) } ! 2, + zeroX: { Dseq([1, 2], inf) }, + mul: { Dstutter(Diwhite(1, 1000), Drand([0.01, 0.07, 0.2], inf)) }, + rate: { Dstutter(Diwhite(1, 12), Dwhite(0.1, 10)) } + ) +}.play +) + +x.release +:: + + +anchor::Ex. 5:: +subsection::Ex. 5: The overall envelope + + +code:: +// The finishing of a ZeroXBufRd is not detemined by finite demand rate ugens but by an overall envelope, +// its release section is triggered by a maximum number of half wavesets ('length') or a maximum time. + +// Buffers from Ex.4 + +{ ZeroXBufRd.ar(b[0], z[0], rate: 1, length: 10, rel: 0.01) }.plot(0.03) + +{ ZeroXBufRd.ar(b[0], z[0], rate: 1, maxTime: 0.01, rel: 0.01) }.plot(0.03) + + +// envelopes can be differentiated + +{ ZeroXBufRd.ar(b[0..1], z[0..1], rate: 1, maxTime: [0.01, 0.005], rel: [0.005, 0.02]) }.plot(0.03) + +{ ZeroXBufRd.ar(b[0..1], z[0..1], rate: 1, length: [7, 2], rel: [0.005, 0.02]) }.plot(0.03) + + +// there should be only one doneAction 2 in this case + +{ ZeroXBufRd.ar(b[0..1], z[0..1], rate: 1, maxTime: [0.01, 0.005], rel: [0.05, 0.5], doneAction: [0, 2]) }.play + + +( +b.do(_.free); +z.do(_.free); +) +:: + + +anchor::Ex. 6:: +subsection::Ex. 6: Simultaneous writing and reading + + +code:: +// The reading of half wavesets can start before analysis is finished, +// if ZeroXBufRd is carefully used in with a bit of delay. + + +// prepare buffers + +( +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +b = Buffer.read(s, p); +) + +( +z = Buffer.alloc(s, b.duration * 44100 / 5, 1); +s.scope; +) + + + +// Here the average playback rate equals 1 (0.8 = 4/5, 1.25 = 5/4), +// so playback will not be faster than writing. + +( +{ + var src = PlayBuf.ar(1, b, BufRateScale.ir(b)); + // write zero crossings, but no need to overwrite sound buffer + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, writeSndBuf: false); + DelayL.ar( + ZeroXBufRd.ar( + b, z, + // Dseries keeps counting through the half-filled zeroX buffer + zeroX: Dseries(), + rate: Dstutter(10, Dseq([0.8, 1, 1.25], inf)), + // estimate end time + maxTime: b.duration + 1, + doneAction: 2 + ), + 0.2, + 0.1 + ) ! 2; +}.play +) + +( +b.free; +z.free; +) + +// The same can be done with a live-generated signal or a mic input, +// but ensure that reading comes after writing ! + + +// FAILURE BY BAD DEFINITION ! +// rates are fast, so zeroX indices are referred before analysis +// resulting in garbage noise + + +( +b = { Buffer.alloc(s, 5 * 44100) } ! 2; +z = { Buffer.alloc(s, 2 * 44100) } ! 2; +) + + +( +{ + var src = LFDNoise3.ar(300 ! 2), sig; + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1); + sig = DelayL.ar( + ZeroXBufRd.ar( + b, z, + // Dseries keeps counting through the half-filled zeroX buffer + zeroX: { Dseries() }, + + rate: { Dseq((1..10) / LFDNoise3.ar(3).range(5, 10) + 1, inf) }, + mul: { Dstutter(Dwhite(50, 500), Drand([0.02, 0.1, 0.5], inf)) }, + // estimate end time + maxTime: 10, + att: 0.2, + rel: 5, + doneAction: 2 + ), + 0.2, + 0.1 + ); + LeakDC.ar(sig) +}.play +) + + + +// Reasonable realtime usage +// zeroX is deferred by stuttering, rates are sufficiently low + +( +b.do(_.zero); +z.do(_.zero); +) + + +( +{ + var src = LFDNoise3.ar(3000 ! 2); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1); + DelayL.ar( + ZeroXBufRd.ar( + b, z, + zeroX: { Dstutter(Dwhite(50, 300), Dseries()) * 2 + Dseq((1..4), inf) }, + rate: { Dseq((1..5) / LFDNoise3.ar(3).range(5, 10) + 0.5, inf) }, + mul: { Dstutter(Dwhite(50, 500), Drand([0.05, 0.3, 0.7], inf)) }, + // estimate end time + maxTime: 20, + att: 0.2, + rel: 5, + doneAction: 2 + ), + 0.2, + 0.1 + ); +}.play +) + +( +b.do(_.free); +z.do(_.free); +) +:: + + + +anchor::Ex. 7:: +subsection::Ex. 7: Adjusting zero crossings + + +code:: +// In general a half waveset isn't totally unipolar: +// Zero crossing indices indicate the change of the sign of a signal, +// so with this convention the last sample of the half waveset itself +// has a different sign. + +// This can have the consequence that, depending on the playback rate, +// sawtooth-like effects might occur. Such artefacts can be circumvented by +// adjusting buffer values at zero crossing indices to 0, +// so playback of (half) wavesets is smoothened, especially with extreme rate values. + + +( +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +b = Buffer.read(s, p); +z = Buffer.alloc(s, 100000); +) + +// analyse buffer +( +{ + var src = PlayBuf.ar(1, b, BufRateScale.ir(b), doneAction: 2); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, doneAction: 2); +}.play +) + +// this half waveset clearly shows the effect +// (tested with samplerate 44100) + +s.scope + +x = { ZeroXBufRd.ar(b, z, nil, 220, rate: 0.1) * 2 }.play + + +// adjust zeros, also works with arrays of Buffers +// you can apply it while running, it might take a moment though + +b.adjustZeroXs(z) + +x.release + + +// alternatively adjusting zero crossings can be chosen as option with analysis: +// set flag 'adjustZeroXs' to 1 + + +( +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +b = Buffer.read(s, p); +z = Buffer.alloc(s, 100000); +) + +( +{ + var src = PlayBuf.ar(1, b, BufRateScale.ir(b), doneAction: 2); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, adjustZeroXs: 1, doneAction: 2); +}.play +) + +s.scope + +x = { ZeroXBufRd.ar(b, z, nil, 220, rate: 0.1) * 2 }.play + +x.release + + +// the flag 'adjustZeroXs' can also be set to 2 +// in this case zero crossings have a minimum distance of 2 samples +// This goes along with ZeroXBufRd's convention to play one half waveset with a minimum length of 2 samples +// (otherwise it couldn't act as a trigger) + +// the difference can be observed with fast switches between signs like with WhiteNoise + +( +b = Buffer.alloc(s, 2000); +z = Buffer.alloc(s, 2000); +) + +( +{ + var src = WhiteNoise.ar(); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, adjustZeroXs: 1, doneAction: 2) * 0.2; +}.play +) + +s.scope + + +// this generates a more pulsar-like waveform with adjacent zero crossings, +// note that transitions to flat sections are smoothened by cubic interpolation + +x = { ZeroXBufRd.ar(b, z, nil, Dseq((0..50), inf), rate: 0.03) * 0.5 }.play + +x.release + + +// there are no flat sections with 'adjustZeroXs' set to 2 + +( +b.zero; +z.zero; +) + +( +{ + var src = WhiteNoise.ar(); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, adjustZeroXs: 2, doneAction: 2) * 0.2; +}.play +) + + +x = { ZeroXBufRd.ar(b, z, nil, Dseq((0..50), inf), rate: 0.03) * 0.5 }.play + +x.release +:: + + + +anchor::Ex. 8:: +subsection::Ex. 8: Granulation with movement through a buffer + +See link::Tutorials/Buffer_Granulation#Ex.1g:: + + +anchor::Ex. 9:: +subsection::Ex. 9: Smooth concatenation of adjacent wavesets + +code:: +// If we invert the waveset with every change of direction +// we smoothly continue its slope at the zero crossing. +// This can be done simply by using the sequence of directions as a +// ZeroXBufRd's multiplier input, the waveform then consists of +// antisymmetric segments. + +// Dwalk is suited for this usage, but should not get +// ordinary ugens as input for stepsPerDir and stepWidth + +( +// boot with extended resources + +s = Server.local; +Server.default = s; +s.options.memSize = 8192 * 32; +s.reboot; +s.scope; +s.freqscope; +) + +// load soundfile into buffer +// allocate buffer for zero crossings + +( +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +b = Buffer.read(s, p); +z = Buffer.alloc(s, 100000); +) + +// analyse buffer + +( +{ + var src = PlayBuf.ar(1, b, BufRateScale.ir(b), doneAction: 2); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, doneAction: 2); +}.play +) + +// check the number of zero crossings + +( +z.loadToFloatArray( + action: { |x| + ~zeroXs = x.reject(_==0); + ~zeroNum = ~zeroXs.size; + "done".postln; + "number of zero crossings: ".post; + ~zeroNum.postln + } +) +) + +// mix short and long walks into one direction + +( +x = { + var sig, zeroX, dir; + + #zeroX, dir = Dwalk( + Dwrand([1, 3, 50], [50, 10, 1].normalizeSum, inf), + start: 1000, + lo: 100, + hi: 5000, + withDirs: 1 + ); + // we need dir twice + dir = Dunique(dir, 2048 ** 2); + sig = ZeroXBufRd.ar( + b, z, + nil, + zeroX, + // change sign together with direction -> + // smooth continuation of slope + mul: dir, + rate: 1, + dir: dir + ); + // stereo by simple delay + DelayL.ar(sig, 0.2, [0, 0.1]) +}.play +) + +x.release + + +// decorrelation by using different rate sequences, +// as zeroX is used twice it also needs to be duniquefied. + +( +x = { + var sig, zeroX, dir; + + #zeroX, dir = Dwalk( + Dwrand([1, 5, 50], [50, 5, 1].normalizeSum, inf), + start: 1000, + lo: 100, + hi: 5000, + withDirs: 1 + ); + dir = Dunique(dir, 2048 ** 2); + zeroX = Dunique(zeroX, 2048 ** 2); + { + ZeroXBufRd.ar( + b, z, + nil, + zeroX, + mul: dir, + rate: Dstutter(Dwhite(1, 20), Dwhite(0.6, 1.4)), + dir: dir + ) + } ! 2 +}.play +) + +x.release +:: + + +anchor::Ex. 10:: +subsection::Ex. 10: Smooth concatenation of adjacent segments restricted by turning points resp. local minima or maxima + +code:: +// This is similar to the previous approach, +// but here we can change direction where slope is zero, +// so we need an analysis of the slope's zero crossings, +// the waveform consists of symmetric segments. + +// Dwalk is suited for this usage, but should not get +// ordinary ugens as input for stepsPerDir and stepWidth + + +( +// boot with extended resources + +s = Server.local; +Server.default = s; +s.options.memSize = 8192 * 32; +s.reboot; +s.scope; +s.freqscope; +) + +// load soundfile into buffer +// allocate buffer for zero crossings (of slope !) + +( +p = Platform.resourceDir +/+ "sounds/a11wlk01.wav"; +b = Buffer.read(s, p); +c = Buffer.read(s, p); // not absolutely needed, just in case to record slope +z = Buffer.alloc(s, 100000); +) + +// analyse slope, indices of turning points resp. +// local minima or maxima are written to z +// buffer c isn't overwritten here (adjustZeroXs == -1) + +( +{ + var slope = Slope.ar(PlayBuf.ar(1, b, BufRateScale.ir(b), doneAction: 2)); + var env = EnvGen.ar(Env([0, 1, 1, 0], [0.01, b.duration - 0.02, 0.01])); + ZeroXBufWr.ar(slope, c, z, adjustZeroXs: -1, doneAction: 2); + // slope is loud - don't want to play it ! + Saw.ar(100, 0.05) * env; +}.play +) + +// slope has much more zero crossings than the original recording, check + +( +z.loadToFloatArray( + action: { |x| + ~zeroXs = x.reject(_==0); + ~zeroNum = ~zeroXs.size; + "done".postln; + "number of positions where slope equals zero: ".post; + ~zeroNum.postln + } +) +) + + +// walk between turning points resp. local minima or maxima + +( +x = { + var sig, zeroX, dir; + #zeroX, dir = Dwalk( + Dstutter(Dwhite(1, 12), Dwrand([1, 3, 17], [10, 1, 1].normalizeSum, inf)), + start: 1000, + lo: 100, + hi: 25000, + withDirs: 1 + ); + sig = ZeroXBufRd.ar( + b, z, + nil, + zeroX, + mul: 1, // signal doesn't have to be inverted with direction changes + rate: 0.5, + dir: dir + ); + // stereo by simple delay + DelayL.ar(Limiter.ar(sig * 10, 0.5), 0.2, [0, 0.1]) +}.play +) + +x.release + + +// stereo by decorrelated rate sequences +// need to duniquefy dir and zeroX + +( +x = { + var sig, zeroX, dir; + #zeroX, dir = Dwalk( + Dstutter(Dwhite(1, 50), Dwrand([1, 3, 25], [10, 5, 1].normalizeSum, inf)), + start: 1000, + lo: 100, + hi: 25000, + withDirs: 1, + dUniqueBufSize: 2048 ** 2 + ); + dir = Dunique(dir, 2048 ** 2); + zeroX = Dunique(zeroX, 2048 ** 2); + sig = { ZeroXBufRd.ar( + b, z, + nil, + zeroX, + mul: 1, // signal doesn't have to be inverted with direction changes + rate: Dstutter(Dwhite(1, 2), Dseq((1..50) / 50 + 0.5, inf)), + dir: dir + ) } ! 2; + LeakDC.ar(Limiter.ar(sig * 5, 0.3)) +}.play +) + +x.release +:: + + + + + diff --git a/HelpSource/Classes/ZeroXBufWr.schelp b/HelpSource/Classes/ZeroXBufWr.schelp new file mode 100755 index 0000000..a9cd762 --- /dev/null +++ b/HelpSource/Classes/ZeroXBufWr.schelp @@ -0,0 +1,218 @@ +CLASS:: ZeroXBufWr +summary:: writes zero crossing analysis from signals to buffers +categories:: Libraries>miSCellaneous>ZeroX ugens +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies + +DESCRIPTION:: + +ZeroXBufWr analyses zero crossings from an input signal and writes the signal and the zero crossing indices to buffers. It is intended to be used with link::Classes/ZeroXBufRd:: and link::Classes/TZeroXBufRd::, see these help files for more examples. + + +note:: +Often it pays to adjust zero crossings in the sound buffer effectively to 0, that way sawtooth-like interpolation artefacts can be avoided. See link::#Ex. 2::, link::Classes/ZeroXBufRd#Ex. 7:: and link::Classes/TZeroXBufRd#Ex. 1::. +:: + +note:: +For avoiding too long half wavesets it might be useful to apply LeakDC resp. a high pass filter before analysis. +:: + +note:: +For full functionality at least SC 3.7 is recommended (adjustZeroXs set to 2 doesn't work in 3.6) +:: + +subsection::Credits +Thanks to Tommaso Settimi for an inspiring discussion, which gave me a nudge to tackle these classes. + + +CLASSMETHODS:: + + +method::ar + +Creates a new ZeroXBufWr ar UGen. + + +argument::in +Signal to be analysed, size must correspond to strong::sndBuf:: and strong::zeroXBuf::. + +argument::sndBuf +Buffer or SequenceableCollection of Buffers to write signals to, size must correspond to strong::in:: and strong::zeroXBuf::, writing can be disabled with strong::writeSndBuf::. The length of strong::sndBuf:: determines the trigger for the doneAction. + +argument::zeroXBuf +Buffer or SequenceableCollection of Buffers to write anaysis data to, size must correspond to strong::in:: and strong::sndBuf::. + +argument::startWithZeroX +Number 0 or 1 or SequenceableCollection of such, determining whether the first sample should be regarded as zero crossing. Defaults to 0. + +argument::adjustZeroXs +One of the Numbers -1, 0, 1, 2 or a SequenceableCollection of such. +list:: +##-1: indicate all zeroXs, dont't write sound buffer +##0: indicate all zeroXs, write sound buffer +##1: indicate all zeroXs, write sound buffer, set to 0 there +##2: indicate zeroXs with a minimum distance of 2, set to 0 there +:: +Actions 1 and 2 can lead to smoother half wavesets, see examples. Defaults to 0. + + +argument::doneAction +Done action be performed after the duration of the longest buffer of strong::sndBuf::. Defaults to 0. + + +section::Examples + +See the link::Classes/ZeroXBufRd:: and link::Classes/TZeroXBufRd:: help files for more examples + + +anchor::Ex. 1:: +subsection::Ex. 1: Basic usage + +code:: +// prepare two short buffers for audio and zero crossing data + +( +b = Buffer.alloc(s, 256); +z = Buffer.alloc(s, 256); +) + + +// analyse a short snippet of random noise + +( +{ + var src = LFDNoise3.ar(5000); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 0, doneAction: 2); + Silent.ar +}.play +) + +// plot buffer, most likely it doesn't start with 0 + +b.plot + +// zero crossings + +z.loadToFloatArray(action: { |b| b.postln }) + + + +// clear buffers + +( +b.zero; +z.zero; +) + + +// example with SinOsc +// other than you might expect SinOsc doesn't start with 0 +// for analysis you might want to regard the value at index 0 as zero crossing +// this can be done with the flag startWithZeroX: + +( +{ + var src = SinOsc.ar(500); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2); + Silent.ar +}.play +) + +// plot buffer, you see that it doesn't start with 0 + +b.plot + +// zero crossings including start + +z.loadToFloatArray(action: { |b| b.postln }) +:: + +anchor::Ex. 2:: +subsection::Ex. 2: Adjusting zero crossings + +code:: +( +b = Buffer.alloc(s, 128); +z = Buffer.alloc(s, 128); +) + + +s.scope + +// fill buffer + +( +{ + var src = LFPar.ar(700); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, doneAction: 2); + Silent.ar +}.play +) + + +// playing this repeated half waveset at slow rate shows that the x axis is crossed +// as the buffer's value at the zero crossing isn't exactly 0 + +x = { ZeroXBufRd.ar(b, z, nil, 1, rate: 0.2) * 0.1 }.play + +// this can be circumvented by two strategies: + +// setting to zeros from the language +// can be done while running + +b.adjustZeroXs(z) + +x.release + + +// alternatively writing can be done with the flag 'adjustZeroXs' set to 1: + +( +{ + var src = LFPar.ar(700); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, adjustZeroXs: 1, doneAction: 2); + Silent.ar +}.play +) + + +x = { ZeroXBufRd.ar(b, z, nil, 1, rate: 0.2) * 0.1 }.play + +x.release + + +// If flag 'adjustZeroXs' is set to 2, this defines the minimum distance of detected zero crossings, +// these positions in the buffer are also set to 0. +// This option can make sense in the case of sources with many fast sign switchings. + + +// here the resulting buffer can end up with sequences of zeros ... + +( +{ + var seq = Drand([1, 1, 2, 3], inf); + var src = Duty.ar(SampleDur.ir * seq, 0, Dwhite(0.1, 1) * Dseq([-1, 1], inf)); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, adjustZeroXs: 1, doneAction: 2); + Silent.ar +}.play +) + +b.plot + + +// ... whereas here we have a continuous sequence of at least minimal half wavesets + +( +{ + var seq = Drand([1, 1, 2, 3], inf); + var src = Duty.ar(SampleDur.ir * seq, 0, Dwhite(0.1, 1) * Dseq([-1, 1], inf)); + ZeroXBufWr.ar(src, b, z, startWithZeroX: 1, adjustZeroXs: 2, doneAction: 2); + Silent.ar +}.play +) + +b.plot + +// This can especially make a difference with ZeroXBufRd, as the latter works with +// a minimum half waveset length of 2 samples +:: + diff --git a/HelpSource/Classes/attachments/DXMix/sliding_ex_1.png b/HelpSource/Classes/attachments/DXMix/sliding_ex_1.png new file mode 100644 index 0000000..4093b05 Binary files /dev/null and b/HelpSource/Classes/attachments/DXMix/sliding_ex_1.png differ diff --git a/HelpSource/Classes/attachments/DXMix/sliding_ex_2.png b/HelpSource/Classes/attachments/DXMix/sliding_ex_2.png new file mode 100644 index 0000000..6cc9e34 Binary files /dev/null and b/HelpSource/Classes/attachments/DXMix/sliding_ex_2.png differ diff --git a/HelpSource/Classes/attachments/DXMix/sliding_ex_3.png b/HelpSource/Classes/attachments/DXMix/sliding_ex_3.png new file mode 100644 index 0000000..24cfd8a Binary files /dev/null and b/HelpSource/Classes/attachments/DXMix/sliding_ex_3.png differ diff --git a/HelpSource/Classes/attachments/DXMix/sliding_ex_4.png b/HelpSource/Classes/attachments/DXMix/sliding_ex_4.png new file mode 100644 index 0000000..08bc72b Binary files /dev/null and b/HelpSource/Classes/attachments/DXMix/sliding_ex_4.png differ diff --git a/HelpSource/Classes/attachments/DXMix/sliding_ex_5.png b/HelpSource/Classes/attachments/DXMix/sliding_ex_5.png new file mode 100644 index 0000000..ab4a86b Binary files /dev/null and b/HelpSource/Classes/attachments/DXMix/sliding_ex_5.png differ diff --git a/HelpSource/Classes/attachments/DXMix/sliding_ex_6.png b/HelpSource/Classes/attachments/DXMix/sliding_ex_6.png new file mode 100644 index 0000000..3c15d4f Binary files /dev/null and b/HelpSource/Classes/attachments/DXMix/sliding_ex_6.png differ diff --git a/HelpSource/Classes/attachments/PSPdiv/PSPdiv_graph_1.png b/HelpSource/Classes/attachments/PSPdiv/PSPdiv_graph_1.png new file mode 100755 index 0000000..bcd1218 Binary files /dev/null and b/HelpSource/Classes/attachments/PSPdiv/PSPdiv_graph_1.png differ diff --git a/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_1.png b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_1.png new file mode 100644 index 0000000..418f3cd Binary files /dev/null and b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_1.png differ diff --git a/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_2a.png b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_2a.png new file mode 100644 index 0000000..e337f5c Binary files /dev/null and b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_2a.png differ diff --git a/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_2b.png b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_2b.png new file mode 100644 index 0000000..9f9c715 Binary files /dev/null and b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_2b.png differ diff --git a/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_3a.png b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_3a.png new file mode 100644 index 0000000..45bc9c3 Binary files /dev/null and b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_3a.png differ diff --git a/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_3b.png b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_3b.png new file mode 100644 index 0000000..80db03a Binary files /dev/null and b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_3b.png differ diff --git a/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4a.png b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4a.png new file mode 100644 index 0000000..ad0d381 Binary files /dev/null and b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4a.png differ diff --git a/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4b.png b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4b.png new file mode 100644 index 0000000..1e4cd2f Binary files /dev/null and b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4b.png differ diff --git a/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4c.png b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4c.png new file mode 100644 index 0000000..ac6e89f Binary files /dev/null and b/HelpSource/Classes/attachments/PbindFx/PbindFx_graph_4c.png differ diff --git a/HelpSource/Classes/attachments/VarGui/main_buttons.png b/HelpSource/Classes/attachments/VarGui/main_buttons.png new file mode 100644 index 0000000..698cb47 Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/main_buttons.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_1.png b/HelpSource/Classes/attachments/VarGui/player_1.png new file mode 100644 index 0000000..f068950 Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_1.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_10.png b/HelpSource/Classes/attachments/VarGui/player_10.png new file mode 100644 index 0000000..65817a8 Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_10.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_2.png b/HelpSource/Classes/attachments/VarGui/player_2.png new file mode 100644 index 0000000..8b40b16 Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_2.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_3.png b/HelpSource/Classes/attachments/VarGui/player_3.png new file mode 100644 index 0000000..eb6a55b Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_3.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_4.png b/HelpSource/Classes/attachments/VarGui/player_4.png new file mode 100644 index 0000000..eca189e Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_4.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_5.png b/HelpSource/Classes/attachments/VarGui/player_5.png new file mode 100644 index 0000000..ceef9c5 Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_5.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_6.png b/HelpSource/Classes/attachments/VarGui/player_6.png new file mode 100644 index 0000000..11ce114 Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_6.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_7.png b/HelpSource/Classes/attachments/VarGui/player_7.png new file mode 100644 index 0000000..d04c5cf Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_7.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_8.png b/HelpSource/Classes/attachments/VarGui/player_8.png new file mode 100644 index 0000000..f665912 Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_8.png differ diff --git a/HelpSource/Classes/attachments/VarGui/player_9.png b/HelpSource/Classes/attachments/VarGui/player_9.png new file mode 100644 index 0000000..2e41b6d Binary files /dev/null and b/HelpSource/Classes/attachments/VarGui/player_9.png differ diff --git a/HelpSource/Guides/Guide_to_HS_and_HSpar.schelp b/HelpSource/Guides/Guide_to_HS_and_HSpar.schelp new file mode 100644 index 0000000..4fccb80 --- /dev/null +++ b/HelpSource/Guides/Guide_to_HS_and_HSpar.schelp @@ -0,0 +1,120 @@ + + + +TITLE::Guide to HS and HSpar +summary::objects for use of synth values in the language by event patterns +categories:: Libraries>miSCellaneous>HS and HSpar, Streams-Patterns-Events>HS and HSpar, OpenSoundControl, Server>Architecture +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/HS, Classes/PHS, Classes/PHSuse, Classes/HSpar, Classes/PHSpar, Classes/PHSparUse, Classes/PHSplayer, Classes/PHSparPlayer, Classes/PHSusePlayer, Tutorials/Event_patterns_and_LFOs, Tutorials/HS_with_VarGui + + +SECTION::Motivation + +Sometimes it may be desirable to use synth values in a Pbind (actually the derived EventStreamPlayer), I'm especially thinking of LFO-like controls for the generated synth(s). There are different ways to do this or something similar, see link::Tutorials/Event_patterns_and_LFOs:: for examples. + +As a general distinction for an event stream you can have new control values per event (1) or continuous control (2). +Some possible implementations: + +image::attachments/Guide_to_HS_and_HSpar/tab_2b.png:: + +There also exist elaborated language solutions that can be used in the above sense: see the interpolation methods in Wouter Snoei's wslib quark and (based on that) Splines with gui by crucialfelix. + +For (1) and (2) LFO behaviour can be defined using help synths (1c, 1d, 1e, 2a, 2b). If the Pbind-generated synths read values from control buses (1e, 2a, 2b), you don't have these values in the language (well, maybe you don't need them). With (1c) you have to use the internal server (of course this may also be ok). Having to define audio SynthDefs with respect to possible future control needs (1e, 2a) though is a bit unflexible. So (1d) integrates some nice features, nevertheless this has to be traded off with additional latency inherent in its mechanism: by using HS / PHS (and relates) help synth values are sent back to the client via OSCresponders, which works with local and internal server. + +The HS / PHS approach would especially be of interest if control behaviour could more easily be defined by server means than in SC lang (e.g. specific and / or nested UGens) but data should also be further manipulated in the language (e.g. for some kind of combinatorial use such as harmonic or polyphonic calculations). As a separate feature HSpar / PHSpar support timed and combinatorial possibilities to refer to more than one control synth (e.g. switching). This type of control, however, would not necessarily have to use the underlying demand-respond implementation. + +SECTION::Working scheme + +numberedList:: +## strong::Define help synth(s) by HS or HSpar.:: +These objects hold synth definitions, HS holds a single synth definition, +HSpar can hold more than one and has additional features. + + + +## strong::Define Pbind(s) by PHS (for HS) or PHSpar (for HSpar) to use synth values.:: + +## strong::Play PHS / PHSpar.:: +This instantiates a PHSplayer / PHSparPlayer object. Synth values are demanded and received for the use in defined Pbinds, HS / HSpar keep track of OSC traffic. PHSplayer / PHSparPlayer can be stopped and started with options, concerning Pbinds and help synths. + +Option, may be done immediately with (2) and (3) or later to "step in": + +## strong::Define further Pbind(s) by PHSuse / PHSparUse which refer to the same HS / HSpar.:: + +## strong::Play PHSuse / PHSparUse.:: +A PHSusePlayer object is instantiated, which can also be stopped and started with options. Different from PHSplayer / PHSparPlayer these options are only concerning the Pbind(s). Help synth control is defined by PHS / PHSpar or can be done by their players' methods strong::stop:: and strong::play::. Therefore steps (4) and (5) require the preceding definition resp. playing of PHS / PHSpar. Synchronization with other players can be done by the use of Quant objects. + +Instead of using PHSuse / PHSparUse objects in order to have separate players it's also possible to define several HS / HSpar objects independently. See link::Classes/PHSparUse:: for an example. + +A VarGui interface may be generated before step (3), see link::Tutorials/HS_with_VarGui::. +:: + +SECTION::Latency + +Due to the mechanism of demanding and receiving a synth value strong::before:: sending an actual message defined by the event pattern, there is a latency in addition to and independent from normal server latency (actually there are two, called strong::demandLatency:: and strong::respondLatency::). strong::demandLatency:: is the latency of the help synth(s) in relation to the synth value demands, driven by the Pbind duration pattern, respondLatency means the time given to the response to be received safely on time by the client. + +If these additional latencies are too small the communication between client and server may loose track of synth values needed by the event pattern and the mechanism breaks. On the other hand large latencies and heavy OSC traffic could need more CPU for bookkeeping - maybe you have to play around to find right values. Using the default latencies of HS / HSpar (demandLatency = 0.15, respondLatency = 0.15) and the Server (latency = 0.2) there is an overall latency of 0.5 sec (and in most cases you could lower this a lot if you need to). I didn't intend to use the whole thing for live performance, so just be aware. + + +subsection::OSC demand / respond mechanism to use server values in event streams + +Suppose HS defined, this happens when PHS is played: + +numberedList:: +## latency = 0 + +sequence of duration values generated in language +image::attachments/Guide_to_HS_and_HSpar/latencyCorr_1b.png:: + + +## latency = demandLatency + +(timing accuracy depends on defined time granulation) +in server: synth started, values taken at scheduled times +image::attachments/Guide_to_HS_and_HSpar/latencyCorr_2b.png:: + + + +## latency = demandLatency + respondLatency + +back in language, synth values can be used +e.g. to generate control data for an instrument +image::attachments/Guide_to_HS_and_HSpar/latencyCorr_3b.png:: + + +## latency = demandLatency + respondLatency + server latency + +if note event: (audio) synth started +image::attachments/Guide_to_HS_and_HSpar/latencyCorr_4b.png:: + +:: + + +SECTION::Relation to Pbinds and EventStreamPlayers + +PHS / PHSpar are almost "like" Pbind objects in the way, that event stream behaviour is defined. Internally two event patterns are defined, one for demand time and one for receive time. Consequently PHSplayer / PHSparPlayer also consist of more than one EventStreamPlayer (PHSparPlayer contains a third one for switching between help synths). This splitting seemed necessary for stopping and resuming Pbind(s) and help synth(s). + +You can easily sync PHSplayers / PHSparPlayers / PHSusePlayers themselves or with other EventStreamPlayers via Quant objects. The corresponding play methods take into account the needed time (caused by latency) to step into the quantization as early as possible. + + +SECTION::Granularity - internal quantization + +If several Pbinds are played in parallel, demands for values of the same help synth can be very close together, which causes possibly unnecessary OSC traffic: If accuracy of synth values is not very important (and for LFO-like purposes it is probably not) it seems sufficient to identify synth values from a small time region. This is done by the HS / HSpar parameter strong::granularity::, which defaults to 200, defining a time region of 0.005 seconds. Synth value demands within such a region are using just one (namely the first) demanded value, scheduling itself isn't affected. Mostly you won't have to think about this quantization - note that it has nothing to do with Quant objects and synchronizing players. + + +SECTION::Synth value access + +In the case of a single help synth (HS), values are accessible within the PHS / PHSuse by the local variable code::~val::, e.g. by code::Pkey(\val):: or a construction with code::Pfunc::. In the case of several help synths (HSpar), there is the option of defining a separately timed pattern to switch between playing help synths, always marking one as "current". Then code::~val:: refers to this current help synth, but reference behaviour can be differentiated by options and other keywords within the PHSpar / PHSparUse to get values of all playing help synths and other values (indices) which may be useful for control definitions. See examples in the help file link::Classes/PHSpar::, e.g. the last one. + + +SECTION::Order of execution + +If more than one Pbind is defined via PHS / PHSuse / PHSpar / PHSparUse the corresponding players will be started in order of definition with a small amount of time-shift between them, so that they can refer to each other (especially at coinciding points of logical time). See the last example of link::Classes/PHS::. + + +SECTION::About the examples + +Examples in the doc files use help synths values mainly for pitch - this seemes to make the concept quite clear. I preferred to take the default instrument to emphasize what is happening structurally. See link::Classes/HS:: examples for other usages. + +Be sure to have the right HS / HSpar definition evaluated before defining resp. playing a PHS / PHSpar ! - examples usually begin with a HS / HSpar definition and are thought to be worked through following the comments. + + diff --git a/HelpSource/Guides/Introduction_to_miSCellaneous.schelp b/HelpSource/Guides/Introduction_to_miSCellaneous.schelp new file mode 100755 index 0000000..20e71d4 --- /dev/null +++ b/HelpSource/Guides/Introduction_to_miSCellaneous.schelp @@ -0,0 +1,582 @@ +TITLE::Introduction to miSCellaneous +summary::overview, references and examples +categories:: Libraries>miSCellaneous, Guides +related:: Overviews/miSCellaneous + + +DESCRIPTION:: + +At the beginning of 2017, it was almost 10 years ago when I did my first steps in systematically ordering and extending personal SC tools. At that time I already had some experience in SC, but project-oriented composition was my main focus. Meanwhile miSCellaneous lib has grown and when I look at its readme file, although I always tried to keep stuff documented properly, I doubt that the connections between the different classes would be easy to get at a first glance. Topics accumulated, some help files have become huge (VarGui, PbindFx) and a lot of examples and documented (extra-)features might hide the basic motivations. As I was asked by colleagues at the Institute of Electronic Music and Acoustic Graz (IEM) we organized a one-day lecture/workshop on miSCellaneous lib on November 5th, 2016, and this tutorial summarizes the overview given then. At that point I'd like to say that I'm very grateful to IEM Graz and the interested SC community from here and abroad for support and feedback, we are going to have further SC meetings at the same place – Hanns Holger Rutz already continued in December. There's also a focus on artistic research here at IEM, and research projects, events and discussions are lighting an ongoing discourse in the field between art and science, creating an inspiring environment. + +Concerning the structure of this file: after a brief history and a grouping of content the tour will guide through selected topics in an order which, hopefully, will outline the central ideas. It will start with VarGui, PLx, then go over PbindFx, EventShortcuts to live coding aspects of PLx, continue with independent classes, class families, methods and SC tutorials and end with the Buffer Granulation tutorial, which again integrates some of the previous topics. PbindFx gets more space, as it's in my recent focus and you can use it for a number of things which are not, or at least not easily doable in plain SC. To a large extent the tour consists of references to selected already existing examples, which are spread over various help files. Conitinuative, but less central topics are marked as such, as well as some legacy code, which is still working, but not very relevant, as other possibilities have been invented meanwhile. A few new examples have been added, the reader is invited to check the exercises and to have fun with his/her favourite instrument resp. effect SynthDefs. + + + +SECTION::History + +In 2007 / 08 I was working at ZKM Karlsruhe, preparing a piece for flute and multichannel electronics (emphasis::Lokale Orbits / Solo 3::). Then I thought it would be useful to order and document my tools, by doing so my programming would – hopefully – not only be useful for me but for others too and I would be able to give something back to the open source community, from which I have been getting so much valuable input. Also I noticed that a better structuring of code together with the need of documentation stimulated reflection and development and significantly improved the functionality of my tools also just for personal use. + +emphasis::Lokale Orbits:: is a series of works for small instrumentations and multichannel tape where recordings with involved musicians were the base for granular processings. From the beginning miSCellaneous lib was developed in parallel to the needs that came from that artistic motivation. The first particular motivation was related to the fundamental architecture of SC – the division between language and server – and its consequences for granular synthesis. I never wanted to spend much time in gui programming, but I wanted to have a multi-purpose gui that would easily let me experiment with granular synthesis driven by language (patterns in particular) and server (granulation ugens). It should also be easy to combine these different controls in a single patch, e.g. fine-tune the parameters of a LFO and those of a Pattern resp. the derived running EventStreamPlayer at the same time. This need led to the development of the VarGui interface. It is the the oldest part and, so to speak, the kernel of miSCellaneous, already contained in the first public release of 2009. A player section and a number of features was added in 2011. Thus VarGui doesn't use SC's extended gui features which came up with the invention of Qt and if I had to build something from scratch I'd certainly look for a revised code structure, nevertheless VarGui reliably served my needs quite well over a long period of time. The twofold control option (environmental variables and synth args) turned out to be useful in many contexts, also the handling of arrays (environmental variables as well as synth args) is quite practical and allows a quick instantiation of huge slider+player guis. + +Between 2009 and 2016 a number of pattern families was added, some of them with granular synthesis in mind. The one I'm using most is the PLx proxy pattern family which takes advantage of environmental variables and dynamic scoping and goes well together with the VarGui interface concerning control of running Patterns/EventStreamPlayers. PLx also opens nice opportunities for live-coding with very condensed syntax. I'm not doing this on stage but I see live-coding as valuable part of a dynamic compositional process. As a side remark, PLx patterns mirror most of SC's main lib patterns and can also be used as non-proxies. In contrast to main lib's list patterns they default to repeats = inf, which probably saved myself the typing of many thousands of 'infs' over the years. + +Another part of miSCellaneous is a number of tutorials, which I added from time to time. Some refer to general SC topics, independent from miSCellaneous (e.g. Event patterns and array args), others to topics specific to miSCellaneous (e.g. PLx and live coding with Strings) and in some I tried a general overview of principal SC strategies, but also used examples with features of miSCellaneous (Buffer Granulation). Finally there's other stuff that doesn't fall into the above categories, e.g. the class EventShortcuts for customized shortcuts with Events and event patterns. Classes related to nonlinear dynamics (single sample feedback with Fb1, generailzed functional iteration synthesis with GFIS) have been added in 2018, Fb1_ODE and related, a framework for general ordinary differential equation integration of initial value problems in 2019. + +SECTION::Groups of content + +list:: + +## VarGui (2009), multi-purpose slider / player gui. Can have sliders for control of synth parameters and environmental variables as well as a player section for control of Synths, Tasks or EventStreamPlayers derived from Patterns. + +## Pattern classes/class families: + +list:: + +## PHSx (2009): pattern-like objects using synth values in language, still working but a bit outdated as we have synchronous buses now + +## PLx (2012): dynamic scope proxy patterns, especially suited for VarGui control, also live coding + +## PSx (2014): patterns acting like streams and remembering last values, good for certain types of nested pattern use and recursion + +## PmonoPar, PpolyPar (2015): differently timed setting of event streams + +## PbindFx (2015): sequencing arbitrary effects and effect graphs + +## PLbindef, PLbindefPar (2016): proxies based on Pbindef allowing shortcut replacement syntax + +## PSVx (2016): a pattern implementation of Xenakis sieves related to the class Sieve, Psieve patterns enable an unusual "realtime sieve modification". Interesting for many applications, e.g. granular rhythms, though I didn't have the time to experiment a lot yet. + +## PSPdiv (2017): a dynamic multi-layer pulse divider based on Pspawner + +## Other pattern classes +:: + +## Tutorials: + +list:: + +## Event patterns and Functions (2011) + +## Event patterns and LFOs (2011) + +## Buffer Granulation Tutorial (2012): different strategies of buffer granulation and control + +## Event patterns and array args (2015) + +## PLx and live coding with Strings (2016) + +## Sieves and Psieve patterns (2016) + +## kitchen studies (2016): commented source code of six short pieces from a kitchen sound using PbindFx + +## Live Granulation Tutorial (2017): different strategies of live granulation and control + +## Other tutorials +:: + + +## Other topics: + +list:: + +## enum (2013): general enumeration tool + +## EventShortcuts (2014): user-defined keywords for events and event patterns + +## Smooth Clipping and Folding (2017) + +## DX suite (2017): pseudo ugens for crossfaded mixing and fanning with drate control + +## Idev suite (2018): patterns and drate ugen searching for numbers with integer distance from a source pattern / signal + +## Fb1, GFIS (2018): single sample feedback and generalized functional iteration synthesis + +## Fb1_ODE (2019): general ordinary differential equation integration + +## ZeroXBufWr / ZeroXBufRd / TZeroXBufRd (2020): playing sequences of segments between zero crossings with demand rate control + +## Other +:: + +:: + +SECTION::Tour 1: VarGui + +The link::Classes/VarGui:: class help file as well link::Tutorials/VarGui_shortcut_builds:: are both bloated with information, so I'd like to give just a few examples and references here in order to show its basic features. + + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + +subsection::Tour 1a: synth control + +code:: +// quick control of default synth, specs are globally known +// note that amp is set to 0 by its default spec +// start by pressing the green button + +VarGui(synth: \default).gui + + +// alternative (shortcut build) writing + +\default.sVarGui.gui +:: + +For basic control of a self-defined Synth/SynthDef by passing control specs see link::Classes/VarGui#synth_0#this example:: in VarGui help. + +For control of multiple Synths/SynthDefs see link::Classes/VarGui#gt_0#this example:: in the same file below. + +It's often more practical to pass control specs as SynthDef metadata as done in link::Tutorials/Buffer_Granulation#1#Ex.1a:: of the Buffer Granulation tutorial. + + + +table:: + +## strong::Exercise::: Control your own (sustained) SynthDef with VarGui, either use SynthDef metadata or VarGui's argument 'synthCtr' for passing specs. + +:: + + +subsection::Tour 1b: environmental variables and pattern control + + +For basic control of an environmental variable in combination with a Pbind run link::Classes/VarGui#gt_1#this example:: in VarGui help. + +code:: +// after having evaluated p, consider the subtle differences of these variants: +// play with slider values and start and pause ad libitum. +// The quant argument ensures synchronization when starting players separately. + +// one array variable in one environment, one player +v = VarGui([\midi, [50, 70, \lin, 1, 55] ! 3], stream: p, quant: 0.2).gui; + +// three single variables in three environments, three players +v = VarGui([\midi, [50, 70, \lin, 1, 55]] ! 3, stream: p!3, quant: 0.2).gui; + +// three array variables in three environments, three players +v = VarGui([\midi, [50, 70, \lin, 1, 55] ! 3] ! 3, stream: p!3, quant: 0.2).gui; + +:: + +These examples show the application of dynamic scoping. A Function has been defined with an "unconnected" free variable and the players evaluate the Function in different Environments provided by the VarGui. Setting the sliders affects the variable of the same name in different Environments of different players. + +This example used a Pfunc, but it works the same with PLx patterns, we take a look at them and leave VarGui for a moment. + + + +SECTION::Tour 2: PLx suite + +Pdef and Pdefn are main lib's proxies for replacement of event patterns and non-event patterns. PL is the most general PLx proxy, taking over some combined functionality of Pdef and Pdefn on the base of dynamic scoping, go through the examples of the help file link::Classes/PL:: to see how it works with value and event patterns. + +Though what neither PL nor Pdef/Pdefn can provide is the replacement of pure lists or list items in the case of list patterns. There's a main lib workaround with a combination of Pn and Plazy (link::Tutorials/PLx_suite#Ex. 1a#PLx suite, Ex.1a::), but it isn't satisfying for several reasons, so I suppose that PLx list patterns like PLseq are amongst the most useful ones of the whole PLx family, e.g. see link::Tutorials/PLx_suite#Ex. 1b#PLx suite, Ex.1b::. Other PLx list patterns like PLrand, PLwrand, PLser, PLshuf, PLshufn, PLswitch etc. work similar. + +There exists also a number of non-list PLx patterns, have a look at link::Classes/PLwhite:: as a typical example. + + +table:: + +## strong::Exercise::: Play your own enveloped SynthDef (or take default SynthDef with params: freq, amp, pan) with some PLx patterns and perform live replacements as in the examples above. +:: + + + +SECTION::Tour 3: PLx patterns used with VarGui + +See link::Classes/VarGui#Ex. 1a#VarGui, Ex.1a::, for basic step sequencing with PLseq, the array variable is implicitely defined in VarGui's first arg. + +See link::Classes/VarGui#Ex. 1c#VarGui, Ex.1c::, for multiple players and array variables and control of multiple sliders and buttons with modifier keys (note that not all functionality might be available on all platforms). + +See link::Classes/VarGui#Ex. 4a#VarGui, Ex.4a::, for a sequencing setup with some PLs used. But more interesting here is that each synth reads is base frequency from a control bus, which gets its data from a separate synth. Synth and EventStreamPlayer are both controlled from the gui. The two slider blocks on the left side concern Synth settings (above) and variable setting for the Pbind / EventStreamPlayer (below). Accordingly we have two players on the right side. Try running the player and starting and stopping the control synth. + +link::Tutorials/Buffer_Granulation#2#Buffer Granulation, Ex.2a::, shows basic language-driven granulation, gui values are taken over by PL patterns and Pfunc. + + +table:: + +## strong::Exercise::: Write a combination of an event pattern and PLx patterns as above with your own SynthDef and play it with a VarGui. Note that it's not necessary to control all args by the gui, nor is it necessary to control parameters directly: you can e.g. control bounds for midinotes in the gui and define the calculation for the actual midinote (e.g. random selection) in the event pattern: + +code:: +\midinote, PLwhite(\midiLo, \midiHi), +:: + +:: + + +SECTION::Tour 4: PbindFx + +PbindFx is an event pattern for effect handling on per-event base. There are other ways for working with event patterns and effects, already possible with main lib, but they have disadvantages: with Pfx and Pfxb there is no built-in way to sequence effect types or effect parameters, you could also route the event's audio to effect buses, but for overlapping events with different fx graphs/params you'd have to define additional buses beforehand as in Ex. 4a. + + +subsection::Ex. 4a: sequencing fx params by using different fx buses + +First go to link::Classes/PbindFx#Ex. 1a#PbindFx, Ex. 1a::, reboot server with extended ressources and evaluate source and fx synths. + + +code:: +// start fxs +( +a = Bus.audio(s, 2); +b = Bus.audio(s, 2); + +x = Synth(\echo, [decayTime: 1.5, echoDelta: 0.15, in: a]); +y = Synth(\echo, [decayTime: 5, echoDelta: 0.1, in: b]); +) + +// play pattern, the two effects allow switching between them +( +p = Pbind( + \dur, 0.5, + \instrument, \source, + \note, Pshuf((0..11), inf) + Pseq([[0.2, 14.2], [0, 4], [0, 4]], inf), + \octave, Pwhite(3, 6), + \out, Pseq([b, a, a], inf) // here we do fx sequencing +).play +) + +p.stop + +// we need to do cleanup manually here + +[x, y, a, b].do(_.free); + +:: + +subsection::Ex. 4b: sequencing fx params by using PbindFx + +code:: +// Ex. 4a translated to PbindFx syntax – +// though it's not exactly identical as fxs are processed in not only two but +// more parallel fx synths + +( +p = PbindFx([ + \dur, 0.5, + \instrument, \source, + \note, PLshuf((0..11)) + PLseq([[0.2, 14.2], [0, 4], [0, 4]]), + \octave, Pwhite(3, 6), + \fxOrder, 1 // always fx #1 (echo) + ],[ + \fx, \echo, + \decayTime, PLseq([5, 1.5, 1.5]), + \echoDelta, PLseq([0.1, 0.15, 0.15]), + \cleanupDelay, 5.2, // with short default echo would be cut + + // this would save a bit CPU + // \cleanupDelay, PLseq([5, 1.5, 1.5]) + 0.2 + ] +).play +) + + +// cleanup (delayed freeing of synths and buses) is done automatically +// watch server window: + +p.stop + + +// now run the same example with the following variant of 'echoDelta', +// obviously the result cannot be achieved with an approach like in Ex. 4a + + \echoDelta, Pwhite(0.05, 0.2) +:: + + +subsection::Ex. 4c: alternation of fx / non-fx by defining a fxOrder sequence + +code:: +( +p = PbindFx([ + \dur, 0.2, + \instrument, \source, + \note, PLshuf((0..11)) + PLseq([[0.4, 14.4], [0, 4], [0, 4]]), + \octave, Pwhite(3, 6), + + // fxOrder = 0 means no fx + \fxOrder, PLseq([0, 0, 1]), + + // if no fx, we need to compensate the unified echo delay of 0.2 + \lag, Pif(Pkey(\fxOrder) > 0, 0, 0.2) + ],[ + \fx, \echo, + \decayTime, PLseq([5, 1.5, 1.5]), + \echoDelta, Pwhite(0.04, 0.12), + \cleanupDelay, PLseq([5, 1.5, 1.5]) + 0.2 + ] +).play +) + +p.stop + +:: + + +table:: +##strong::Exercise::: Play your own (percussive + stereo) SynthDef (or instrument 'source') with an arbitrary sequencing of no fx and echos (by defining 'fxOrder') and echo params as in Ex. 4c. Note that in fx 'echo' the max echoDelta defaults to 0.2. +:: + +subsection::Tour 4d: principles of operation + +For each event several issues have to be internally considered by PbindFx: building and checking of fx graphs (no cycles !), cleaning buses from possibly remaining residual audio (adding "zero synths"), splitting (in case of parallel parts in the fx graph), grouping of all event-related synths and checking accumulated cleanup delay times. You might skip that for the moment, but for a more detailled overview see link::Classes/PbindFx#Principle of operation#Principle of operation::, as well as for a listing of conventions. + + +subsection::Tour 4e: sequential application of fxs (fixed fx chains) + +See PbindFx, examples link::Classes/PbindFx#Ex. 1a#1a:: and link::Classes/PbindFx#Ex. 1b#1b::. + + +subsection::Tour 4f: sequencing different fx chains + +See PbindFx, examples link::Classes/PbindFx#Ex. 2a#2a::, link::Classes/PbindFx#Ex. 2b#2b::, link::Classes/PbindFx#Ex. 2c#2c::, link::Classes/PbindFx#Ex. 2d#2d::. + +table:: +##strong::Exercise::: Play your own (percussive + stereo) SynthDef (or instrument 'source') with an arbitrary sequencing of fx chains (your fxs and/or fxs from PbindFx help). Maybe just extend your last own example. +:: + +subsection::Tour 4g: parallel effects and arbitrary effect graphs + +It seems to be a still underestimated option to design effect graphs different from simple chains. In a classical DAW interface a pile of slots for effects in chain is common, although more differentiated possibilities are also there. Considering event sequencing with PbindFx we can select fx graphs per event / grain, in addition to the sequencing of fx parameters itself. See link::Classes/PbindFx#Ex. 10a#PbindFx, Ex.10a::, for a parallel application of echo, note the syntax of the event graph, passed as Event to 'fxOrder'. + +table:: +##strong::Exercise::: Compare the sound of the two fx graphs given in link::Classes/PbindFx#Ex. 10a#Ex.10a::, also try the following or similar, how do the corresponding fx graphs look like ? + +code:: + \fxOrder, `(0: 1, 1: [2, 3], 2: 4), + + \fxOrder, `(0: 1, 1: [2, 3, \o], 2: 4), + + \fxOrder, `(0: 1, 1: [2, 3], 3: 4, 2: 3), + + \fxOrder, `(0: 1, 1: [2, 3], 3: 2, 2: 4), + + \fxOrder, `(0: 1, 1: [2, 3, \o], 3: 4, 2: 3), +:: +:: + +See PbindFx, Ex. link::Classes/PbindFx#Ex. 10b#10b:: and link::Classes/PbindFx#Ex. 10c#10c:: for modulation graphs and their sequencing. + +table:: +##strong::Exercise::: Play your own (percussive + stereo) SynthDef (or instrument 'source') with an arbitrary sequencing of fx *graphs* (your fxs and/or fxs from PbindFx help). Maybe just extend your last own example. +:: + +subsection::Tour 4h: implicit parallelism of single effects + +This can simple be done by passing arrays within fxData, see link::Classes/PbindFx#Ex. 4#PbindFx, Ex.4a::. + + + +subsection::Tour 4i: different effects with parallel PbindFxs + +A further option for parallelism, a typical case would be the application of different effects to the notes of a chord, see link::Classes/PbindFx#Ex. 3b#PbindFx, Ex.3b:: (rather straight than 3a). + + +subsection::Tour 4j: PbindFx and replacements + +See link::Classes/PbindFx#Ex. 7a#PbindFx, Ex.7a:: for replacement of key streams. + +Up to now PbindFx got lists as args in all examples. As args can be event patterns too, they can also be proxy patterns which opens the door for various unusual kinds of source and fx replacements, see PbindFx Ex. link::Classes/PbindFx#Ex. 7b#7b:: and link::Classes/PbindFx#Ex. 7c#7c::. + +table:: +##strong::Exercise::: Take one of your previous working PbindFx examples and rewrite its args (arrays of key/value pairs) as PL or Pbindef proxies. Run the example and replace source and/or fx patterns on the fly. +:: + + +subsection::Tour 4k: continuative topics + +For use with VarGui see link::Classes/PbindFx#Ex. 8#PbindFx, Ex.8::. + +For using one fx SynthDef in more than one fxData see link::Classes/PbindFx#Ex. 4#PbindFx, Ex.4::. + +For source and fxs reading from external buses (ar or kr) see link::Classes/PbindFx#Ex. 6a#PbindFx, Ex.6a-c::. + +For using value conversions with fxData see PbindFx, link::Classes/PbindFx#Ex. 9#PbindFx, Ex.9::. + + +subsection::Tour 4l: kitchen studies, a granular synthesis application + +One motivation for the development of PbindFx was the idea to explore granular synthesis variants with differentiated effect processings. The fixed media composition emphasis::kitchen studies:: collects six short pieces with different fxs and handlings of fx sequencing, each derived from the kitchen sound of five seconds, which is already contained in miSCellaneous lib. At the same time emphasis::kitchen studies:: is an ongoing artistic research project: the commented source code is published in the tutorial link::Tutorials/kitchen_studies::, compressed versions of the original piece as a whole and its parts can be found on my website link::http://daniel-mayer.at:: (use a separate browser, players won't work within this window), a further documentation of the compositional process will follow as publication in the artistic research database emphasis::Research Catalogue:: (link::http://researchcatalogue.net::). + + +SECTION::Tour 5: EventShortcuts: less typing with Events and event patterns + +EventShortcuts is an interface for defining your own shortcut keys for often used keyword in Events and event patterns. For example you might want to write 'inst' or just 'i' instead of 'instrument' etc., then define your collection of shortcuts – e.g. in your startup or a certain load file – and activate it on occasion. There is also a default shortcuts dictionary, see its content and play a simple example: + +code:: + +EventShortcuts.on; + +EventShortcuts.postAll; + +x = Pbind(\m, Pwhite(60, 90), \d, 0.2).play; + +:: + +For examples of (re-)defining or extending shortcut dictionaries see link::Classes/EventShortcuts::. + +table:: +##strong::Exercise::: Take one of your favourite SynthDefs with non-standard arguments (other than freq, amp, pan, etc.) and write an event pattern example, using these arguments. Then choose useful abbreviations, define a new shortcut dictionary with these (either by extending the default or by defining a new one), make it current and run the event pattern example with abbreviated keys. +:: + + +SECTION::Tour 6: live coding + +This was not my main focus from the beginning, but it turned out that PLx patterns, in combination with EventShortcuts and/or Character sequencing (tour 6b) open up live coding possibilities with very condensed syntax. + + +subsection::Tour 6a: PLbindef and PLbindefPar + +PLbindef is a wrapper class for Pbindef, which allows replacements in pseudo-method syntax in a newly created Environment. + +code:: +EventShortcuts.on; + +PLbindef(\x, \d, 0.2, \m, Pwhite(60, 90)).play; + +// Now 'x' is also the name of a new PLbindefEnvironment in the current Environment, +// its pseudo-methods can be used for setting, moreover it is a player also. + +~x.play + +~x.m = PLseq((60..70)) + +~x.stop + +:: + +For further examples see link::Classes/PLbindef::. + +PLbindefPar is also based on Pbindef, but unlike PLbindef it's not a plain wrapper: it employs a number of Pbindefs in parallel, which allows control of polyphonic, additive or granular structures, see link::Classes/PLbindefPar::. + +warning:: +If you're piling up a lot of layers, be careful with amplitudes, the amplitude values are taken for the single layers, so you'd have to reduce them accordingly ! +:: + +subsection::Tour 6b: PLx and live coding with Strings + +Already in plain SC Strings as Arrays of Characters can be used for sequencing. PLx patterns fit and continuate this concept, see link::Tutorials/PLx_and_live_coding_with_Strings:: for some options, also in connection with PLbindef, PLbindefPar and PbindFx. + + +SECTION::Tour 7: independent classes, class families and methods + +subsection::Tour 7a: PSx stream patterns + +These are a bit paradoxical classes: Patterns which behave as if they were Streams, thus have an internal state and remember its last value(s), which can e.g. be used for recursion or certain demands of repeated embedding as in the following example: + +code:: + +( +// PS gets source and length patterns as args + +p = PLseq([ + PS(PLseq((1..5)), PLseq([1, 2])), + PS(PLseq((1..5) * 100), PLrand((3..5))) +]); + +p.asStream.nextN(100) +) + +// this can also be written with a combination of Streams and Patterns, +// but it needs more typing (same with Pswitch1 variants) + +( +a = Pseq((1..5), inf).asStream; +b = Pseq((1..5) * 100, inf).asStream; + +p = Pseq([ + Pfuncn({ a.next }, Pseq([1, 2], inf).asStream), + Pfuncn({ b.next }, Prand((3..5), inf).asStream), +], inf); + +p.asStream.nextN(100) +) +:: + +See link::Tutorials/PSx_stream_patterns:: for an overview. + + +subsection::Tour 7b: PSVx sieve patterns and Sieves + +Sieves, recommended by Iannis Xenakis as generative principles for arbitrary musical parameters, are implemented twice: with the class Sieve and Psieve patterns, which adapt the sieve calculus for realtime interactions. The tutorial link::Tutorials/Sieves_and_Psieve_patterns:: starts from scratch, thus can be studied completely independent from other miSCellaneous stuff and most other SC requirements. The last chapter gives some audio examples, granular rhythms might be an especially interesting application. + + +subsection::Tour 7c: PmonoPar and PpolyPar + +link::Classes/PmonoPar:: follows the Pmono convention of a single synth, being set but extends it to an arbitrary number of differently timed setting streams. With link::Classes/PpolyPar:: the number of continously running synths is arbitrary as well and setting streams can switch the synths to set. That way complicated fx variations can be achieved by a paradigm different from link::Classes/PbindFx::. + + +subsection::Tour 7d: enum + +A general tool, which can be used for many enumeration and optimization problems (sets, partitions, graphs etc.), melodic shapes and scales are possible musical applications, see link::Tutorials/enum::. + + +subsection::Tour 7e: HS / HSpar / PHS / PHSpar + +A framework for using server-generated values in Pbind-like objects in the language. A bit outdated now, as synchronous bus gives easy access to server values, however the double-latency mechanism provides a good accuracy of values – if this is needed – by passing a high granularity parameter. A lower value minimizes OSC traffic if this is more important. See link::Guides/Guide_to_HS_and_HSpar::. + + +subsection::Tour 7f: PSPdiv + +A dynamic multi-layer pulse divider based on Pspawner, as the latter it supports parallel and sequential sprouting of sub-patterns. Might be used for line ornamentation, polyrhythmical structures, granulation etc., see link::Classes/PSPdiv::. + + +subsection::Tour 7g: Smooth Clipping and Folding + +A suite of pseudo ugens, see link::Tutorials/Smooth_Clipping_and_Folding::. + + +subsection::Tour 7h: DX suite + +pseudo ugens for crossfaded mixing and fanning according to demand-rate control, see link::Tutorials/DX_suite::. + + +subsection::Tour 7i: Idev suite + +patterns and drate ugen searching for numbers with integer distance from a source pattern / signal, see link::Tutorials/Idev_suite::. + + +subsection::Tour 7j: Nonlinear dynamics + +pseudo ugens for single sample feedback and generalized functional iteration synthesis, see link::Classes/Fb1:: and link::Classes/GFIS::. General ordinary differential equation integration, see link::Classes/Fb1_ODE:: and related classes. + +subsection::Tour 7k: ZeroXBufWr / ZeroXBufRd / TZeroXBufRd + +pseudo ugens for zero crossing analysis and playing sequences of segments between them with demand rate control, see link::Classes/ZeroXBufWr::, link::Classes/ZeroXBufRd::, link::Classes/TZeroXBufRd::. + + + + + +SECTION::Tour 8: (Rather) independent SC tutorials + + +subsection::Tour 8a: Event patterns and Functions + +This tutorial is about dynamic scope, comparing the behaviour of Functions, Streams and EventStreamPlayers in Environments. It's thus treating some preconditions which are relevant for link::Tutorials/PLx_suite:: and link::Classes/VarGui::, see link::Tutorials/Event_patterns_and_Functions::. + + +subsection::Tour 8b: Event patterns and LFOs + +Continuous (with LFO) and discrete ("LFO-like") control strategies for event patterns are compared in link::Tutorials/Event_patterns_and_LFOs::. + + +subsection::Tour 8c: Event patterns and array args + +SynthDef array args seem to be a sometimes confusing topic, especially when it's about (pseudo-)variable array lengths and Envelopes, this is even more the case when it comes to the sequencing of such synths. For this reason, and not at last to remind myself to the subtle syntax differences, I wrote this tutorial: link::Tutorials/Event_patterns_and_array_args::. + + +SECTION::Tour 9: Buffer Granulation and Live Granulation – tutorials + +Actually scheduled as a general overview of granulation possibilities in SC, also collecting various ideas from the sc-users mailing list discussions, I have to admit that in its current form many examples, especially in the Buffer granulation tutorial, require one or more features of miSCellaneous and thus might not always be easy to follow. Then again it would be hard to write such (gui) examples without presuppositions and at the same time with a clearly representable amount of code. However I hope that based on this guided tour it might be easier to step through for people who haven't used this lib before, see link::Tutorials/Buffer_Granulation:: and link::Tutorials/Live_Granulation::. + + + + + + + + diff --git a/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_1b.png b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_1b.png new file mode 100644 index 0000000..8a9d6b4 Binary files /dev/null and b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_1b.png differ diff --git a/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_2b.png b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_2b.png new file mode 100644 index 0000000..004cec8 Binary files /dev/null and b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_2b.png differ diff --git a/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_3b.png b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_3b.png new file mode 100644 index 0000000..104dc47 Binary files /dev/null and b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_3b.png differ diff --git a/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_4b.png b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_4b.png new file mode 100644 index 0000000..f205c45 Binary files /dev/null and b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/latencyCorr_4b.png differ diff --git a/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/tab_2b.png b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/tab_2b.png new file mode 100644 index 0000000..95cac96 Binary files /dev/null and b/HelpSource/Guides/attachments/Guide_to_HS_and_HSpar/tab_2b.png differ diff --git a/HelpSource/Overviews/miSCellaneous.schelp b/HelpSource/Overviews/miSCellaneous.schelp new file mode 100644 index 0000000..38246f5 --- /dev/null +++ b/HelpSource/Overviews/miSCellaneous.schelp @@ -0,0 +1,503 @@ +TITLE::miSCellaneous +summary::a library of SuperCollider extensions (c) 2009-2020 Daniel Mayer +categories:: Libraries>miSCellaneous +related:: Guides/Introduction_to_miSCellaneous + +DESCRIPTION:: + +Version 0.24 contains these class and help files (SCDoc and old HTML system): + +numberedList:: + +## link::Guides/Introduction_to_miSCellaneous::: recommended starting point. + +## link::Classes/VarGui:: : a slider / player gui to set envir variables and synth controllers and play synths, event patterns and tasks, see also link::Tutorials/VarGui_shortcut_builds::. link::Tutorials/HS_with_VarGui::, a specific tutorial about the combination of HS family and VarGui. + + +## General tutorials: link::Tutorials/Event_patterns_and_LFOs:: contains an overview of related LFO-control setups with event patterns. +link::Tutorials/Event_patterns_and_Functions:: is treating requirements of the control of EventStreamPlayers with VarGui. link::Tutorials/Event_patterns_and_array_args:: focusses on passing arrays to synths with patterns. +link::Tutorials/enum:: is a general enumeration method suited for many combinatorial problems such as listing subsets, partitions of integers, +searching for paths within graphs etc. + +## link::Tutorials/PLx_suite::, dynamic scope variants of common Pattern classes for convenient replacement. Can be used for shorter writing of Pbinds to be played with link::Classes/VarGui:: and / or live coding, see link::Tutorials/PLx_and_live_coding_with_Strings::. link::Classes/PLbindef:: and link::Classes/PLbindefPar:: as subclasses of link::Classes/Pdef:: allow replacement of key streams in shortcut pseudomethod syntax. + +## link::Tutorials/PSx_stream_patterns::, Pattern variants that have a state and can remember their last values. Can be used for recording streams and event streams (link::Classes/PS::), data sharing between event streams (link::Classes/PSdup::), comfortable defining of subpatterns with counted embedding, defining of recursive (event) sequences (link::Classes/PSrecur::) and building loops on given Patterns/Streams with a variety of options, also for live interaction (link::Classes/PSloop::). + +## Event pattern classes for use with effects: link::Classes/PbindFx:: can handle arbitrary effect graphs per event, link::Classes/PmonoPar:: and link::Classes/PpolyPar:: follow the Pmono paradigm. The tutorial link::Tutorials/kitchen_studies:: contains the documented source code of a fixed media piece using PbindFx for granulation. + +## Further Pattern classes: link::Classes/PlaceAll::, link::Classes/Pshufn::, link::Classes/PsymNilSafe::. link::Classes/PSPdiv:: is a dynamic multi-layer pulse divider based on Pspawner. + +## link::Tutorials/Buffer_Granulation::, a tutorial covering different approaches of implementing this synthesis method in SC (server versus language control, mixed forms), examples with link::Classes/VarGui::, language-driven control with link::Tutorials/PLx_suite:: patterns. The tutorial link::Tutorials/Live_Granulation:: summarizes direct options without explicit use of a buffer. + +## A family of classes for the use of synth values in Pbind-like objects. +Take link::Guides/Guide_to_HS_and_HSpar:: as a starting point, see also link::Classes/HS::, link::Classes/PHS::, +link::Classes/PHSuse::, link::Classes/HSpar::, link::Classes/PHSpar::, link::Classes/PHSparUse::, +link::Classes/PHSplayer::, link::Classes/PHSparPlayer::, link::Classes/PHSusePlayer:: + +## link::Classes/EventShortcuts::, a class for user-defined keywords for events and event patterns. The tutorial link::Tutorials/Other_event_and_pattern_shortcuts:: collects some further abbreviations, e.g. functional reference within events and event patterns, similar to Pkey. + +## An implementation of Xenakis' Sieves as class and pattern family, see link::Tutorials/Sieves_and_Psieve_patterns:: for an overview and examples. + +## FFT pseudo ugens for defining ranges by bin index: link::Classes/PV_BinRange:: and link::Classes/PV_BinGap::. + +## link::Tutorials/Smooth_Clipping_and_Folding::, a suite of pseudo ugens. + +## link::Tutorials/DX_suite::, pseudo ugens for crossfaded mixing and fanning according to demand-rate control. + +## link::Tutorials/Idev_suite::, patterns and drate ugen searching for numbers with integer distance from a source pattern / signal. + +## Nonlinear dynamics: link::Classes/Fb1:: for single sample feedback / feedforward and link::Classes/GFIS:: for generalized functional iteration synthesis. link::Classes/Fb1_ODE:: for ordinary differential equation integration. + +## link::Classes/ZeroXBufWr::, link::Classes/ZeroXBufRd::, link::Classes/TZeroXBufRd::, pseudo ugens for analysis of zero crossings and playing sequences of segments between them with demand rate control. link::Classes/Dwalk::, a pseudo demand rate ugen that supports specific synthesis options with link::Classes/ZeroXBufRd::. +:: + + + +Many of the examples here are using patterns, resp. event patterns but do not cover their basic concepts. +For a detailled description of SC's sequencing capabilities see James Harkins' Practical Guide to Patterns +(link::Tutorials/A-Practical-Guide/PG_01_Introduction::), the tutorial link::Tutorials/Streams-Patterns-Events1::, and the Pattern help files +(link::Classes/Pattern::, link::Classes/Pbind:: and the type-specific ones). + +VarGui handles namespace separation by using different Environments. +So a gui for control of parametrized families of different types of objects can be built on the fly +(e.g. a number of EventStreamPlayers from a single Pbind definition with snippets of functional code, +a number of Function plots from a single parametric function definition etc.). +See link::Classes/Environment:: and link::Classes/Event:: helpfiles for the underlying concepts and +link::Tutorials/Event_patterns_and_Functions:: and link::Tutorials/PLx_suite:: +for their application to Event patterns resp. EventStreamPlayers. + + + +SECTION::Requirements + +At least SuperCollider version 3.6 but newer versions are recommended, +with 3.6 you'd have to use Qt GUI kit, which is the only option anyway from 3.7 onwards. +Unable to test on 3.5 anymore, code might work, anyway old help is still supported. + +If you still use Cocoa or SwingOSC with these old SC versions, +you can take miSCellaneous 0.15b and add classes and help files from a +newer version of miSCellaneous. + +For using VarGui with EZSmoothSlider and EZRoundSlider you would need to +install Wouter Snoei's wslib Quark, one buffer granulation +example using Wavesets depends on Alberto de Campo's Wavesets Quark. + +I tested examples on SC versions 3.6 - 3.11, +on OS 10.8 - 10.13, Ubuntu 12.04 and Windows 7, 10; +though not every platform / OS version / SC version combination. + + +SECTION::SCDoc issues (SC 3.10) + +With SC 3.10.0 - SC 3.10.2 there are issues with evaluating code in the +help file examples (double evaluation). +For these versions you'd rather copy the help file code into scd files and run it there. +Double evaluation has been fixed with 3.10.3 (August 2019). +With SC updates to 3.10 it might happen that help file examples are invisible. +In that case delete the Help folder which resides in one of these places, depending +where you have installed: + +code:: +Platform.userAppSupportDir; +Platform.systemAppSupportDir; +:: + +Then restart SC. + + +SECTION::Installation + +By version 0.17 you can install either via the quarks extension management system or a +downloaded zip from GitHub or my website. Both methods shouldn't be combined, +e.g. if you have already done a manual install +before by placing a miSCellaneous folder in the Extensions folder, then remove +it from there before the quarks install. + + +SUBSECTION::1.) Installation via Quarks + +This requires a SC version >= 3.7, see the recommended ways to install here: + +link::http://doc.sccode.org/Guides/UsingQuarks.html:: + +link::https://github.com/supercollider-quarks/quarks:: + +After a install of a newer version of miSCellaneous do a SCDoc update by + +code:: +SCDoc.indexAllDocuments(true) +:: + + +SUBSECTION::2.) Manual installation from a downloaded zip + +You can download the newest version from + +link::https://github.com/dkmayer/miSCellaneous_lib:: + +and the newest and all previous versions from here: + +link::http://daniel-mayer.at:: + +Copy the miSCellaneous folder into the Extensions folder and recompile the class library +or (re-)start SC. If the Extensions folder doesn't exist you'd probably have to create it yourself. +Check SC help for platform-specific conventions (or changes) of extension places. + +list:: +## Typical user-specific extension directories: + +list:: +## OSX: ~/Library/Application Support/SuperCollider/Extensions/ +## Linux: ~/.local/share/SuperCollider/Extensions/ +:: + +## Typical system-wide extension directories: + +list:: +## OSX: /Library/Application Support/SuperCollider/Extensions/ +## Linux: /usr/share/SuperCollider/Extensions/ +:: + +:: + +You can check Extension directories with + +code:: +Platform.userExtensionDir; +Platform.systemExtensionDir; +:: + +On Windows see the README file of SC for the recommended extensions path. +On Windows and OSX you might have to make the concerned folders visible, +if they aren't already. The miSCellaneous folder should be placed directly +into the Extensions folder, not into a subfolder. + +After a install of a newer version of miSCellaneous do a SCDoc update by + +code:: +SCDoc.indexAllDocuments(true) +:: + + +SECTION::License + +miSCellaneous is distributed under the GNU Public License in accordance with SuperCollider. +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +SECTION::Contact + +Email: daniel-mayer@email.de + +URL: link::http://daniel-mayer.at:: + + +SECTION::Credits + +Many thanks to James McCartney for developing SuperCollider, +Alberto de Campo for showing me its capabilities, +Wouter Snoei for his nice slider classes in wslib, +Nathaniel Virgo for his suggestions for feedback, +David Pirrò for his hints concerning ODE integration, +James Harkins for his remarks on many things and +the whole community for contributions and hints ! + + +SECTION::History + + +v0.24 2020-07-08 + +list:: +## Dwalk, demand rate ugen that supports specific synthesis options with ZeroXBufRd +## New examples #9, #10 in ZeroXBufRd help with applications of Dwalk +## New examples #8, #9 in TZeroXBufRd help (pulsar synthesis) +## Bugfix related to PV_BinRange and PV_BinGap, usage in FFT chains enabled +:: + +v0.23 2020-04-19 + +list:: +## ZeroXBufWr, ZeroXBufRd, TZeroXBufRd: playing half wavesets with demand rate control +## Remove doubled method lincurve_3_9, which caused a warning without harm +## AddEventTypes_PbindFx: use store instead of writeDefFile in private methods +## Minor fixes in help files +:: + +v0.22 2019-08-14 + +list:: +## Fb1_ODE and related: ordinary differential equation integration +## Fb1: now also runs at control rate, Fb1.new is equivalent to Fb1.ar +## Fb1: minor change of forced graph ordering +## PbindFx: accept instruments/fxs given as Strings in pbindData key/value pairs +## Minor fixes in help files +:: + +v0.21 2018-07-25 + +list:: +## Fb1: single sample feedback / feedforward +## GFIS: generalized functional iteration synthesis +## Redefined variables in Sieve, PSrecur and PSPdiv to enable inlining +## Minor fixes in help files +:: + +v0.20 2018-05-21 + +list:: +## Idev suite, search for numbers with integer distance from a source +## Bug fix in Integer method lcmByFactors +## Minor fixes in help files +:: + +v0.19 2017-11-22 + +list:: +## DX, a suite of pseudo ugens for crossfaded mixing and fanning +## Minor adaption of PbindFx event type +## Minor fixes in help files +:: + +v0.18 2017-09-26 + +list:: +## PLbindef / PLbindefPar: new features and implementation +## Minor fixes in PV_BinGap and PV_BinRange +## Minor fixes in help files +:: + + +v0.17 2017-08-21 + +list:: +## Smooth Clipping and Folding, a suite of pseudo ugens +## Added MIDI learning feature for VarGui slider control +## Platform.miSCellaneousDirs, search for miSCellaneous folder +## Adapted PV_BinRange and PV_BinGap to do multichannel expansion +## Exchanged graphic examples in VarGui help (Qt now) +## Minor fixes in help files +## First version released via GitHub and as Quark +:: + + +v0.16 2017-03-03 + +list:: +## Added PSPdiv, a dynamic multi-layer pulse divider based on Pspawner +## Added tutorial: Live Granulation +## Dropped miSCellaneous' b-branch: Cocoa and SwingOSC no longer supported +## Replaced OSCpathResponder by OSCFunc in classes VarGuiPlayerSection, HelpSynth and HelpSynthPar +## Minor fixes in help files +:: + + +v0.15 2017-01-04 + +list:: +## Guide "Introduction to miSCellaneous" +## kitchen studies: source code of the corresponding fixed media piece +## PV_BinRange, PV_BinGap: FFT pseudo ugens for defining bin ranges +## PbindFx help: new example 4a for defining implicit fx parallelism +## Minor fixes (help files, typos) +## Complemeting history information (tutorials) +:: + + +v0.14 2016-08-25 + +list:: +## Sieves and Psieve patterns, an implementation of Xenakis' sieves +## PLbindef / PLbindefPar: replacement of key streams in pseudomethod syntax +## Tutorial: PLx and live coding with Strings +## PsymNilSafe, method symplay: avoid hangs with nil input +## EventShortcuts: reworking of replacement, new method eventShortcuts +## PbindFx: reworking of bus match check +## Minor fixes (help files, typos) +:: + + +v0.13 2015-10-28 + +list:: +## PbindFx: Event pattern class for effect handling on per-event base +## Minor fixes (help files, typos) +:: + + +v0.12 2015-05-03 + +list:: +## PmonoPar, PpolyPar: Event pattern classes for parallel setting streams and effect handling +## PSloop: Pattern to derive loops from a given Pattern +## Reworked replacing options of PL list patterns: cutItems arg +## PLn: PLx Pattern for replacing with protecting periods +## Added PLxrand, PLx version of Pxrand +## Changed implementations of PStream (PS) and PSrecur, +now the MemoRoutine isn't instantiated before embedding, this enables use of PSx patterns with VarGui +## Fixed a bug in PL list patterns (PL_PproxyNth) that caused too early +replacements in certain cases +## Added method bufSeq for PStream +## Added missing help file of PLtuple +## Minor fixes (help files, typos) +:: + + +v0.11 2015-02-02 + +list:: +## Tutorial: link::Tutorials/Event_patterns_and_array_args:: +:: + + +v0.10 2014-10-06 + +list:: +## Split into branches 0.10a (3.7 onwards) and 0.10b (from 3.4 up to 3.6.x), with SC 3.7 SwingOSC and Cocoa aren't supported anymore. +## Class link::Classes/EventShortcuts:: and tutorial link::Tutorials/Other_event_and_pattern_shortcuts::. +:: + + +v0.9 2014-02-18 + +list:: +## PSx stream patterns (classes and tutorial), based on class MemoRoutine +## Buffer Granulation Tutorial: new examples (1c - 1e, 3d) +## VarGui save dialog fix (was broken with 3.6) +## Minor fixes (help files, typos) + +:: + +v0.8 2013-05-05 + +list:: +## Enumeration tutorial: method enum + +:: + +v0.7.1 2013-04-28 + +list:: +## Fix in PLseries and PLgeom + +:: + + +v0.7 2012-08-31 + +list:: +## Buffer Granulation tutorial file +## VarGui +list:: +## Support of wslib slider classes EZSmoothSlider and EZRoundSlider +## Color grouping options for synth and envir variable control +## Adapting EZSlider to ControlSpec step size (fixes certain rounding and jitter issues) +## Multiple slider handling with modifier keys: fixes and cleanup +:: + +:: + +v0.6 2012-05-19 + +list:: +## PLx dynamic scope pattern suite + +list:: +## Implementation of a number of common Patterns as PLx classes +## Tutorial PLx suite +## Changing examples in some previous help files accordingly + +:: + + +## Pstream, PlaceAll, Pshufn +:: + + +v0.5 2012-03-18 + +list:: +## VarGui shortcut build methods + +list:: +## Use of SynthDef metadata and global ControlSpecs +## Tutorial VarGui shortcut builds +## Automatic Pbind generation +:: + + +## Minor fixes in VarGui init procedure +:: + + +v0.4 2011-12-27 + +list:: +## VarGui support for HS family classes +## Tutorial HS with VarGui +## VarGui takes addAction as slider hook + +## Linux check (tested on Ubuntu): + +list:: +## Fixed system time issue in HS family +## Specified VarGui appearance default parameters for platform and gui kit +:: + +## Supports SCDoc, the new SC help system +## Tutorial Event Patterns and Functions +## Tutorial Event patterns and LFOs +## Play methods of PHSx and PHSxPlayer now also take numbers as quant arg +## Minor fixes in HS family +:: + + +v0.4_beta 2011-08-18 + +list:: +## VarGui relaunch: + +list:: +## Player section for Synths, EventStreamPlayers and Tasks +## Different player modes +## Button colors and background colors reflecting playing states +## Handling groups of players and sliders with modifier keys +## Player action by mouse down or up +## Variables can be set in different environments +## Latency setting, global and for synth player message bundling +## GUI appearance customization in size, arrangement and color +## Many other changes, e.g. arg conventions +:: + +## Private extension methods get prefix miSC_ +:: + +v0.3 2010-10-21 + +list:: +## VarGui: +list:: +## Arrayed synth control supported +## Slider update methods added +## Save dialog now uses unified gui class Dialog +:: +## Again compiling with SC 3.3 and 3.3.1 +:: + +v0.2 2010-09-18 + +list:: +## Minor adaptions to SC 3.4 +## Fixed time shifting issue +:: + +v0.1 2009-11-24 + +list:: +## VarGui, interface for envir variable and synth arg control +## HS / HSpar etc.: classes for the use of synth values in Pbind-like objects +## Tutorial Working with HS and HSpar +:: + diff --git a/HelpSource/Tutorials/Buffer_Granulation.schelp b/HelpSource/Tutorials/Buffer_Granulation.schelp new file mode 100755 index 0000000..16e4705 --- /dev/null +++ b/HelpSource/Tutorials/Buffer_Granulation.schelp @@ -0,0 +1,1695 @@ + + +TITLE::Buffer Granulation +summary::different approaches to buffer granulation with gui examples +categories:: Libraries>miSCellaneous>General Tutorials, Streams-Patterns-Events +related:: Tutorials/Live_Granulation, Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/VarGui, Tutorials/VarGui_shortcut_builds, Tutorials/PLx_suite, Classes/PbindFx, Tutorials/kitchen_studies, Tutorials/Sieves_and_Psieve_patterns, Classes/PSPdiv, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +As with many things in SC work can be done by language or server. Speaking about granulation in general this mainly concerns the control of single grains, their rate, timing, length and other parameters. Regarding buffer granulation specifically this task can e.g. be taken over by ugens like TGrains and GrainBuf, the latter additionally accepting a grain envelope arg. The SC plugin distribution contains further variations (see BhobUGens, JoshUGens). By using granulation ugens in a SynthDef granular textures can be produced with a single Synth, including grain parameter sequencing with demand rate ugens, the link::Tutorials/DX_suite:: opens additional genuine options in this regard. Alternatively SynthDefs for single grains can be defined with PlayBuf or BufRd in order to control the whole buffer granulation process from language side, using Patterns, Tasks or Routines. What is the best way? To a large extent this is a question of personal preference. Regarding CPU performance granulation ugens have an advantage, whereas e.g. pattern-driven granulation allows concise control over the sequencing of granulation parameters in a clear syntax. If pattern-driven granulation is becoming CPU-critical you might want to consider the event type \grain, a lightweight variant of default type \note (see comment in the source file Event.sc). For granulation with Tasks see link::Classes/VarGui#Ex. 3#VarGui, Ex.3::. + +Also hybrid strategies are possible, e.g. language controlled setting of a single granulation Synth or involving extra control synths in a language-driven granulation process, further options are Pspawner / Pspawn and Wavesets. VarGui can be used to integrate these setups in single GUIs. Together with this file (miSCellaneous v0.7) I reinvented a color grouping option that overrides automatic color grouping (link::Classes/VarGui#Ex. 7#VarGui, Ex.7::). The latter is based on the logical structure of ordinary and array controls, synths and environments. This is useful for VarGuis up to a medium number of controls per Synth / Pattern / Task and also for a large number of such items, but it doesn't handle cases well where there are many controls per item. Not barely an aesthetic detail, it is much more convenient for experimenting to group all sliders of a Synth that, say, have to do with a bandpass filter, within one color. + +subsection::Types of variables + +In general interpreter variables are prefered in examples below, wherever possible. If evaluated with example code only their values are passed and further changing of used variables doesn't affect already generated gui instances. On the contrary repeated evaluation of an interpreter variable, e.g. from a Pfunc, is unsafe - variable's value could change while gui has not yet been closed - so in concerned examples values are passed to the event definition, in link::#Ex.2c:: a variable is declared for the same reason. +The use of environmental variables is following the way VarGui is handling them. Per default every EventStreamPlayer derived from a passed Pattern is run in a separate newly generated Environment, where variables are being set and Streams from Pfuncs and PLx patterns are reading from. See link::Tutorials/Event_patterns_and_Functions::, link::Tutorials/PLx_suite:: and link::Classes/VarGui::. + + + +warning:: + +numberedList:: + +## Be careful with amplitudes, especially with buffers you haven't granulated before! Also keep in mind that a granular cloud moving through a buffer can suddenly become louder and other controls than amp (e.g. buffer position, trigger rate, bandpass parameters) can cause a raise of amplitude too. + + +## I haven't used below setups for live performances. Although all of them work stable for me as they are, in general hangs can occasionally happen with pattern-driven setups. Often this can be tracked down to sequences of extremely short event durations (and/or long grain durations). Where this can happen as a side-effect, thresholds can be built in, e.g. link::#Ex.2d:: (Wavesets) has a parameter maxTrigRate. +Another possible source of hangs is careless deep nesting of Patterns where mistakes can easily occur. Starting with clear Pattern structures is recommended - and if more complications are involved: testing without sound first, after saving your patch, might be a good idea. +:: +:: + +note:: +All variants from this tutorial can be applied to a buffer, which is occasionally (or continuously) filled with live input. Vice versa variants from link::Tutorials/Live_Granulation:: can of course be applied to any signal, thus also to any playback of a buffer. + +All examples below expect mono buffers. Buffer paths refering to the included sample suppose that you have installed via quarks or moved miSCellaneous lib into the user or system extensions directory directly (not into a subfolder), if not so or you have moved the sample file somewhere else you'd have to change paths accordingly. + +Due to a bug in SC 3.7 / 3.8 TGrains didn't response to amp changes, I changed the examples accordingly, the bug is fixed in 3.9. + +While other parts of miSCellaneous lib were running fine I was unable to allocate buffers with the SC 3.5.4 Windows binary in August 2012. Meanwhile this issue has been solved (SC 3.6.6, February 2014). There also seemed to be accuracy issues with OffsetOut on Windows with SC 3.6 still, but not with newer versions. +:: + + + +subsection::Credits + +Thanks for contributions and inspirations by SCers Alberto de Campo (Wavesets), James Harkins (Patterns), Ron Kuivila (Pspawner), Sergio Luque (stochastic distributions), Josh Parmenter (granulation plugins), Bhob Rainey (granulation plugins). + +subsection::References + +numberedList:: + +## de Campo, Alberto. "Microsound" In: Wilson, S., Cottle, D. and Collins, N. (eds). 2011. The SuperCollider Book. Cambridge, MA: MIT Press, 463-504. + +## Luque, Sergio (2006). Stochastic Synthesis, Origins and Extensions. Institute of Sonology, Royal Conservatory, The Netherlands. http://sergioluque.com + +## Roads, Curtis (2001). Microsound. Cambridge, MA: MIT Press. + +## Seidl, Fabian (2016). Granularsynthese mit Wavesets für Live-Anwendungen. Master Thesis, TU Berlin. http://www2.ak.tu-berlin.de/~akgroup/ak_pub/abschlussarbeiten/2016/Seidl_MasA.pdf + +## Wishart, Trevor (1994). Audible Design. York: Orpheus The Pantomime Ltd. + +## Xenakis, Iannis (1992). Formalized Music. Hillsdale, NY: Pendragon Press, 2nd Revised edition. + +:: + + + +anchor::1:: +SECTION::1. Granulation with Ugens +subsection::Ex.1a: Basic buffer granulation Synth + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + +// basic SynthDef suited for pitch-shift and time-stretch +// buffer position given relatively (posLo and posHi between 0 and 1) +// posDev: maximum amount of deviation from (moving) grain center position +// posDev = 0 can lead to comb filter effects (which may be nice sometimes) + +// passing control specs as metadata allows for VarGui shortcut build method sVarGui +// metadata specs can be overwritten by arg ctrReplace +// alternatively control specs may be passed as synthCtr arg to a build with VarGui( ... ) + +( +SynthDef(\gran_1a, { arg out = 0, bufNum = 0, posLo = 0.0, posHi = 1.0, + posRate = 1, posDev = 0.01, trigRate = 100, granDur = 0.1, rate = 1.0, + panMax = 1, amp = 0.1, interp = 4; + + var trig, pan, pos, bufDur, bufDurSection, posDif; + + posDif = posHi - posLo; + bufDur = BufDur.kr(bufNum); + bufDurSection = bufDur * posDif; + trig = Impulse.kr(trigRate); + pos = posLo * bufDur + + (Phasor.ar(0, BufRateScale.kr(bufNum) * posRate / SampleRate.ir, posLo * bufDur, posHi * bufDur) + + (TRand.kr(-0.5 * posDev, 0.5 * posDev, trig) * bufDur)).mod(bufDurSection); + pan = Demand.kr(trig, 0, Dseq([panMax, panMax.neg],inf) * 0.999); + Out.ar(out, TGrains.ar(2, trig, bufNum, rate, pos, granDur, pan, 1, interp) * amp); + }, metadata: ( + specs: ( + posLo: [0.01, 0.99, \lin, 0.01, 0], + posHi: [0.01, 0.99, \lin, 0.01, 1], + posRate: [0.1, 2, \lin, 0.01, 1], + posDev: [0, 0.2, 5, 0, 0.01], + granDur: [0.01, 0.3, \lin, 0.01, 0.1], + trigRate: [1, 200, \lin, 0.01, 100], + rate: [0.1, 2, \lin, 0.01, 1], + panMax: [0.0, 1, \lin, 0.005, 0.8], + amp: [0.0, 0.5, \lin, 0.005, 0.25] + ) + ) +).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +) + + +// start from GUI + +\gran_1a.sVarGui([\bufNum, b.bufnum]).gui; + + +:: + +anchor::Ex.1b:: +subsection::Ex.1b: More deviations + +code:: + +// In example 1a only a deviation from the grain center position was implemented. +// With additional deviation controls a greater plasticity of sound can be achieved, +// here deviations are added for trigRate (LFO with oscillation freq and deviation max), +// grain duration and rate (TRand, equally weighted random deviation with given max). +// Deviations intervals could be defined alternatively, +// e.g. (1/(1+maxDev), 1+maxDev) with 0 < maxDev +// instead of (1-maxDev, 1+mexDev) with 0 < maxDev < 1 + +// posRate control range is widened by inventing two controls for +// mantissa and exponent, so posRate = 1 for init param pair +// posRateE = 0 and posRateM = 1 + +( +SynthDef(\gran_1b, { arg out = 0, bufNum = 0, posLo = 0.0, posHi = 1.0, + posRateE = 0, posRateM = 1, posDev = 0.01, trigRate = 100, trigRateDev = 0, + trigRateOsc = 1, granDur = 0.1, granDurDev = 0, rate = 1.0, rateDev = 0, + panMax = 1, amp = 0.1, interp = 4; + + var trig, pan, pos, bufDur, bufDurSection, posDif, posRate; + + posDif = posHi - posLo; + bufDur = BufDur.kr(bufNum); + bufDurSection = bufDur * posDif; + trig = Impulse.kr(LFDNoise3.kr(trigRateOsc, trigRate * trigRateDev, trigRate)); + posRate = 10 ** posRateE * posRateM; + pos = posLo * bufDur + + (Phasor.ar(0, BufRateScale.kr(bufNum) * posRate / SampleRate.ir, posLo * bufDur, posHi * bufDur) + + (TRand.kr(-0.5, 0.5, trig) * posDev * bufDur)).mod(bufDurSection); + pan = Demand.kr(trig, 0, Dseq([panMax, panMax.neg],inf) * 0.999); + Out.ar(out, TGrains.ar(2, trig, bufNum, rate * (TRand.kr(-1, 1.0, trig) * rateDev + 1), pos, + granDur * (TRand.kr(-1, 1.0, trig) * granDurDev + 1), pan, 1, interp) * amp); + }, metadata: ( + specs: ( + posLo: [0.01, 0.99, \lin, 0.01, 0], + posHi: [0.01, 0.99, \lin, 0.01, 1], + posRateE: [-3, 4, \lin, 1, 0], + posRateM: [0.1, 10, \exp, 0.01, 1], + posDev: [0, 0.2, 5, 0, 0.05], + trigRate: [1, 200, \lin, 0.01, 100], + trigRateDev: [0.0, 1, \lin, 0.01, 0], + trigRateOsc: [0.1, 2, \lin, 0.01, 3], + granDur: [0.01, 0.3, \lin, 0.01, 0.1], + granDurDev: [0.0, 0.95, \lin, 0.01, 0], + + rate: [0.1, 2, \lin, 0.01, 1], + rateDev: [0.0, 0.99, \linear, 0.01, 0.05], + panMax: [0.0, 1, \lin, 0.005, 0.8], + amp: [0.0, 0.5, \lin, 0.005, 0.25] + ) + ) +).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. +) + + +// start from GUI +// use color grouping for better overview + +\gran_1b.sVarGui([\bufNum, b.bufnum]).gui(synthColorGroups: (0..14).clumps([1,5,3,2,2,1,1]) ) + +:: + + +subsection::Ex.1c: Buffer granulation Synth with external control synths + +code:: + +// This is a more modular approach, once having a basic granulation synth +// it can be linked with arbitrary control synths, here +// also moving through the buffer is controlled externally. +// Depending on LFOs it might be worth defining +// audio buses for control to get higher precision. + + +( +SynthDef(\gran_1c, { arg out = 0, bufNum = 0, amp = 0.1, pos = 0.5, posDev = 0.01, + trigRate = 100, granDur = 0.1, rate = 1, panMax = 1, interp = 4; + var trig, pan; + trig = Impulse.kr(trigRate); + pan = Demand.kr(trig, 0, Dseq([panMax, panMax.neg],inf) * 0.999); + pos = (pos * BufDur.kr(bufNum) * WhiteNoise.kr(posDev, 1)); + Out.ar(out, TGrains.ar(2, trig, bufNum, rate, pos, granDur, pan, 1, interp) * amp); +}).add; + + +// LFO synthdef for switching between 3 types: +// 0: LFDNoise3 (smooth random movement) +// 1: SinOsc +// 2: LFSaw (works as phasor for position) + +SynthDef(\lfo, { |out = 0, lfoType = 0, freq = 1, lo = 0, hi = 1| + var ctrl; + ctrl = Select.kr(lfoType, [ + LFDNoise3, + SinOsc, + LFSaw + ].collect { |x| x.kr(freq, mul: hi-lo/2, add: hi+lo/2) }); + Out.kr(out, ctrl); +}).add; + +// multichannel control bus +c = Bus.control(s, 5); + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. +) + + +// In contrast to Ex. 1a and 1b VarGui is called explicitely +// as control specs should differ. + +// Also here the granulation Synth is generated explicitely and not by the interface +// as its controls have to be mapped to buses + +( +// start granulation synth paused and register +// to let VarGui know its state + +x = Synth.newPaused(\gran_1c, [\bufNum, b]).register; + +// map args to be controlled to consecutive subbuses +x.map(*[[\pos, \trigRate, \granDur, \rate, \panMax], (0..4).collect(c.subBus(_))].flop.flat) +) + +( +// Open VarGui interface, granulation synth (#5) is paused +// (orange button on yellow background). +// Shift-clicking one of the green buttons runs +// granulation synth and control synths (#0 - #4). +// All synths can be paused (orange button) and resumed. + +// NOTE: When stopping the granulation synth +// the linkage with control synths is lost! +// A new synth generated by the interface +// (by pressing the blue button of #5) is not +// automatically mapped to control buses. +// On the other hand control synths might be stopped and +// newly generated. + + +VarGui(synthCtr: [[ + \pos, 0, // dummy spec for labelling + \out, c.index, + \lfoType, [0, 2, \lin, 1, 0], + // as LFOs are unified, original movement tempo is + // indicated not by 1 but by 1 / buffer duration + \freq, [0.01, 20, \exp, 0.0, 1/b.duration], + \lo, [0.0, 1, \lin, 0, 0.1], + \hi, [0.0, 1, \lin, 0, 0.9] + ],[ + \trigRate, 1, + \out, c.index + 1, + \lfoType, [0, 2, \lin, 1, 0], + \freq, [0.001, 0.5, \exp, 0.0, 0.2], + \lo, [1, 100, \lin, 0, 7], + \hi, [1, 100, \lin, 0, 40] + ],[ + \granDur, 2, + \out, c.index + 2, + \lfoType, [0, 2, \lin, 1, 1], + \freq, [0.001, 0.5, \exp, 0.0, 0.4], + \lo, [0.01, 0.2, \lin, 0, 0.1], + \hi, [0.01, 0.2, \lin, 0, 0.15] + ],[ + \rate, 3, + \out, c.index + 3, + \lfoType, [0, 2, \lin, 1, 0], + \freq, [0.001, 0.1, \exp, 0.0, 0.03], + \lo, [0.1, 3, \lin, 0, 0.6], + \hi, [0.1, 3, \lin, 0, 1.1] + ],[ + \panMax, 4, + \out, c.index + 4, + \lfoType, [0, 2, \lin, 1, 0], + \freq, [0.001, 0.5, \exp, 0.0, 0.5], + \lo, [0.0, 1, \lin, 0, 0.06], + \hi, [0.0, 1, \lin, 0, 0.9] + ],[ + \out, 0, + \bufNum, b.bufnum, + \posDev, [0.001, 0.2, \exp, 0, 0.02], + \amp, [0.0, 2, \lin, 0, 0.5] + ]], + // 5 lfo synths are generated by the interface (synthdef name passed) + // but granulation Synth is passed directly as object + synth: \lfo!5 ++ x +).gui(sliderPriority: \synth, playerPriority: \synth); +) + +:: + +subsection::Ex.1d: Buffer granulation Synth with demand rate ugens + +code:: + +// Repeated grain triggering within a synth can be defined by demand rate ugens, +// which is comfortable in connection with granular ugens. + +// E.g. with TGrains you can trigger parameters like grain duration, playback rate, +// position, panning and amp, single grains will keep their params if they overlap. +// (Note that in the below implementation changes of demand rate array fields will apply +// also not before the next call of those fields in the synth) + +// More refined per-grain control, e.g. per-grain filtering with filter parameter streams, +// can be done by using a multichannel trick, see Ex. 1e. + +( +// length of demand rate sequence, you might want to check larger sizes +// in connection with gui arg tryColumnNum > 1 +~n = 5; + +// posRate control range is widened by inventing two controls for +// mantissa and exponent, so posRate = 1 for init param pair +// posRateE = 0 and posRateM = 1 + +SynthDef(\gran_1d, { |out = 0, soundBuf, posLo = 0.1, posHi = 0.3, + posRateE = 0, posRateM = 1, granDurMul = 1, rateMul = 1, panMul = 1, amp = 0.5, interp = 2| + + var signal, bufDur, granDur, granGate, relGranDurs, pos, overlap, overlaps, overlapSeq, + pan, rate, relRates, rateSeq, posRate, granDurSeq; + + // array args for demand rate sequencing, short form of NamedControl + relGranDurs = \relGranDurs.kr(0.1!~n); + relRates = \relRates.kr(1!~n); + overlaps = \overlaps.kr(1!~n); + + // Dstutter (or Dunique) necessary as granDurSeq is polled twice: granGate and granDur + granDurSeq = Dstutter(2, Dseq(relGranDurs, inf)); + rateSeq = Dseq(relRates, inf); + overlapSeq = Dseq(overlaps, inf); + + granGate = TDuty.ar(granDurSeq * granDurMul); + granDur = Demand.ar(granGate, 0, granDurSeq * granDurMul); + rate = Demand.ar(granGate, 0, rateSeq) * rateMul; + pan = Demand.ar(granGate, 0, Dseq([1, -1], inf)) * 0.999 * panMul; + overlap = Demand.ar(granGate, 0, overlapSeq); + + bufDur = BufDur.kr(soundBuf); + posRate = 10 ** posRateE * posRateM; + + pos = Phasor.ar(0, BufRateScale.kr(soundBuf) * posRate / (SampleRate.ir * bufDur), posLo, posHi); + signal = TGrains.ar(2, granGate, soundBuf, rate, pos * bufDur, granDur * overlap, pan, 1, interp); + + Out.ar(out, signal * amp); +} +).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. +) + +( +// relGranDurs are multiplied with granDurMul, analogously relRates with rateMul. +// Changes of these params apply immediately in contrast to the array args +// used by demand rate ugens which are used with next demand. + +// check out moving a number of sliders of one array by using +// Shift, Alt + Shift and Ctrl + Shift, see VarGui help Ex. 1c. + + +VarGui(synthCtr: [ + soundBuf: b.bufnum, + + posLo: [0, 1, \lin, 0, 0.1], + posHi: [0, 1, \lin, 0, 0.9], + posRateE: [-3, 4, \lin, -1, 0], + posRateM: [0.1, 10, \exp, 0.01, 0.5], + + // generating control specifications depending on index + relGranDurs: { |i| [0.01, 0.1, \lin, 0, i * 0.005 + 0.02] } ! ~n, + granDurMul: [0.03, 2, \lin, 0, 0.25], + overlaps: [0.1, 5, \lin, 0, 1.5] ! ~n, + + relRates: { |i| [0.1, 1.5, \lin, 0, (5-i) * 0.1 + 0.5] } ! ~n, + rateMul: [0.1, 2, \lin, 0, 0.5], + + panMul: [0, 1, \lin, 0, 0.6], + amp: [0.0, 3, \lin, 0, 2] +], + synth: \gran_1d +).gui( + tryColumnNum: 1, + // color grouping for better overview + synthColorGroups: (0..(~n*3+8)).clumps([1,4,~n+1,~n,~n+1,1,1]), + labelWidth: 90, + sliderWidth: 280 +); +) +:: + +anchor::Ex.1e:: +subsection::Ex.1e: Buffer granulation Synth with per-grain effect processing (TGrains) + +code:: + +// TGrains and other granular ugens allow per-grain processing +// for a limited number of parameters (pos, rate etc.) +// Nevertheless it is possible to apply arbitrary effects with +// per-grain parameter changes, even if grains overlap. +// This can be achieved by defining the granulation output +// as a multichannel signal and appropriate triggering of fx parameters. +// The example is adapted from a recommendation of Julian Rohrhuber. + +// The method is elegant but also a bit tricky in terms of +// multichannel triggering and channel routing. +// See Ex.1f for achieving the same with DX ugens + +( +// the multichannel size and equivalently: +// the maximum number of overlapping grains that might get +// different fx parameters have to be fixed. +// For convenience of later L/R-spatialization we take an +// even number ~n = 2 * ~m + +~m = 5; +~n = 2 * ~m; + +SynthDef(\gran_1e, { |out = 0, soundBuf, posLo = 0.1, posHi = 0.9, + posRateE = 0, posRateM = 1, rate = 1, panMax = 0.8, bpRQ = 0.1, bpLo = 50, bpHi = 5000, + amp = 1, bpFund = 100, overlap = 2, trigRate = 1, interp = 2| + var sig, sigL, sigR, bpFreq, chan, bpFreqSeqs, dUgen, + trig, trigs, bufDur, pos, posRate; + + trig = Impulse.ar(trigRate); + // we need a multichannel trigger that steps through all consecutive channels + trigs = { |i| PulseDivider.ar(trig, ~n, ~n-1-i) } ! ~n; + + chan = Demand.ar(trig, 0, Dseq((0..~n-1), inf)); + + posRate = 10 ** posRateE * posRateM; + bufDur = BufDur.kr(soundBuf); + pos = Phasor.ar(0, BufRateScale.kr(soundBuf) * posRate * SampleDur.ir / bufDur, posLo, posHi); + + sig = TGrains.ar(~n, trig, soundBuf, rate, pos * bufDur, overlap/trigRate, + // Panning convention is that from PanAz, + // speakers should be from 0 to 2, but (orientation) + // 1/n has to be substracted for n speakers. + // If this isn't done correctly grains are spread onto more than one channel + // and per-grain application of fxs fails. + chan.linlin(0, ~n-1, -1/~n, (2*~n - 3)/~n), 1, interp); + + dUgen = Dwhite(0.0, 1); + sig = sig.collect { |ch, i| + // this is the place to define fxs per channel/grain + // multichannel trigger is polling from a single demand ugen + bpFreq = Demand.ar(trigs[i], 0, dUgen).linlin(0, 1, bpLo, bpHi); + + // amplitude compensation for lower rq of bandpass filter + BPF.ar(ch, bpFreq, bpRQ, (bpRQ ** -1) * (400 / bpFreq ** 0.5)); + }; + + // routing to two channels ... + sigL = Mix(((0..(~m-1)) * 2).collect(sig[_])); + sigR = Mix(((0..(~m-1)) * 2 + 1).collect(sig[_])); + + // ... in order to have L/R-spreading with panMax as in other examples + Out.ar(0, Pan2.ar(sigL, panMax.neg) + Pan2.ar(sigR, panMax) * amp) +}).add; + + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. +) + + +( +VarGui(synthCtr: [ + soundBuf: b.bufnum, + posLo: [0, 1, \lin, 0, 0.2], + posHi: [0, 1, \lin, 0, 0.5], + posRateE: [-3, 4, \lin, -1, -1], + posRateM: [0.1, 10, \exp, 0.01, 0.8], + overlap: [0.1, ~n, \lin, 0, 12], + trigRate: [1, 100, \lin, 0, 45], + rate: [0.1, 2, \lin, 0, 1], + + bpRQ: [0.05, 1, \lin, 0, 0.25], + bpLo: [50.0, 5000, \exp, 0, 50], + bpHi: [50.0, 5000, \exp, 0, 5000], + panMax: [0, 1, \lin, 0, 0.85], + amp: [0.0, 3, \lin, 0, 1] +], + synth: \gran_1e +).gui( + tryColumnNum: 1, + synthColorGroups: (0..12).clumps([1,4,2,1,3,1,1]) +); +) +:: + +anchor::Ex.1f:: +subsection::Ex.1f: Buffer granulation Synth with per-grain effect processing (DXEnvFan) + +code:: +// DXEnvFan generates a multichannel envelope which can be used as trigger for granulation and fxs on grains. +// It encapsulates the trigger logic, which has been used explicitely in Ex. 1e. +// DX ugens can be used for a variety of microsound techniques, see their help files. + +( +~maxOverlap = 12; +a = Bus.audio(s, ~maxOverlap); + +// overlap only settable in SC versions >= 3.9 + +SynthDef(\gran_1f, { |out = 0, soundBuf, bus = 0, posLo = 0.1, posHi = 0.9, + posRateE = 0, posRateM = 1, overlap = 2, trigRate = 1, rate = 1, + bpRQ = 0.1, bpLo = 50, bpHi = 5000, panMax = 0.8, amp = 1| + var sig, bpFreq, dUgen, bufDur, pos, posRate, playbuf, env, maxOverlap = ~maxOverlap; + + posRate = 10 ** posRateE * posRateM; + bufDur = BufDur.kr(soundBuf); + pos = Phasor.ar(0, BufRateScale.kr(soundBuf) * posRate * SampleDur.ir / bufDur, posLo, posHi); + + // multichannel trigger + env = DXEnvFan.ar( + Dseq((0..maxOverlap-1), inf), + trigRate.reciprocal, + size: maxOverlap, + maxWidth: maxOverlap, + width: (Main.versionAtLeast(3, 9)).if { overlap }{ 2 }, + // option to avoid unwanted triggers + zeroThr: 0.002, + // take equalPower = 0 for non-squared sine envelopes + // more efficient with helper bus + equalPower: 0, + bus: a + ); + // multichannel playback, pos is triggered for each grain + playbuf = PlayBuf.ar(1, soundBuf, rate, env, pos * BufFrames.ir(soundBuf), 1); + + dUgen = Dwhite(0, 1); + // multichannel trigger used to poll values from drate ugen + bpFreq = Demand.ar(env, 0, dUgen).linlin(0, 1, bpLo, bpHi); + + // generate grains by multiplying with envelope + sig = playbuf * env; + + // different frequency on each grain channel + sig = BPF.ar(sig, bpFreq, bpRQ, (bpRQ ** -1) * (400 / bpFreq ** 0.5)); + + // generate array of 5 stereo signals + sig = Pan2.ar(sig, Demand.ar(env, 0, Dseq([-1, 1], inf) * panMax)); + + // mix to out + Out.ar(0, Mix(sig) * amp) +}, metadata: ( + specs: ( + posLo: [0.01, 0.99, \lin, 0.01, 0], + posHi: [0.01, 0.99, \lin, 0.01, 0.5], + posRateE: [-3, 4, \lin, 1, -1], + posRateM: [0.1, 10, \exp, 0.01, 1.35], + trigRate: [1, 200, \lin, 0.01, 90], + overlap: [0.2, 12, \lin, 0.01, 7], + rate: [0.1, 2, \lin, 0.01, 0.75], + panMax: [0.0, 1, \lin, 0.005, 0.75], + bpLo: [100, 5000, \lin, 0, 300], + bpHi: [100, 5000, \lin, 0, 3000], + bpRQ: [0.05, 1, \lin, 0, 0.18], + amp: [0.0, 3, \lin, 0.005, 1] + ) +)).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. +) + +( +\gran_1f.sVarGui([\soundBuf, b.bufnum]).gui( + tryColumnNum: 1, + synthColorGroups: (0..12).clumps([1,4,2,1,3,1,1]) +) +) +:: + + +anchor::Ex.1g:: +subsection::Ex.1g: Buffer granulation with (half) wavesets: ZeroXBufRd + +code:: +// movement through the buffer of zero crossings +// with a slow pos rate we get repetitions of half wavesets + +// load sound buffer + +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + + +// allocate zeroX buffer + +( +z = Buffer.alloc(s, b.duration * 44100 / 5, 1); +s.scope; +) + + +// write zero crossings, but no need to overwrite sound buffer +// this is caused by setting adjustZeroXs to -1 + +( +{ + // use LeakDC to avoid extremely long half wavesets + var src = LeakDC.ar(PlayBuf.ar(1, b, BufRateScale.ir(b))); + ZeroXBufWr.ar(src, b, z, adjustZeroXs: -1, doneAction: 2); +}.play +) + + +// instead adjust from lang and get zeroXs + +b.adjustZeroXs(z, { |z| ~zeroXs = z.reject(_==0) }) + +// get number of zeroXs + +~zeroXNum = ~zeroXs.size + + +( +SynthDef(\gran_1g, { |out = 0, soundBuf, zeroXBuf, zeroXNum, posLo = 0.1, posHi = 0.9, + posRateE = 0, posRateM = 1, rate = 1, amp = 1| + var sig, bufDur, pos, posRate; + + posRate = 10 ** posRateE * posRateM; + bufDur = BufDur.kr(soundBuf); + + // move through the buffer of zero crossings + // the tempo (rate) refers to the sound buffer + pos = Phasor.ar( + 0, + BufRateScale.kr(soundBuf) * posRate * SampleDur.ir / bufDur, + posLo, + posHi + ) * zeroXNum; + + sig = ZeroXBufRd.ar( + soundBuf, + zeroXBuf, + 0!2, + pos, + mul: amp, + rate: rate * [1, 1.01] + ); + Out.ar(0, sig * amp) +}, metadata: ( + specs: ( + posLo: [0.01, 0.99, \lin, 0.01, 0.1], + posHi: [0.01, 0.99, \lin, 0.01, 0.5], + posRateE: [-2, 2, \lin, 1, -1], + posRateM: [0.1, 10, \exp, 0.01, 3], + rate: [0.3, 3, \lin, 0.01, 1], + amp: [0.0, 2, \lin, 0.005, 1] + ) +) +).add; + +\gran_1g.sVarGui([\soundBuf, b.bufnum, \zeroXBuf, z.bufnum, \zeroXNum, ~zeroXNum]).gui +) +:: + + +anchor::Ex.1h:: +subsection::Ex.1h: Buffer granulation with (half) wavesets: TZeroXBufRd + +code:: +// buffer preparations from Ex. 1g + +// again a movement through the buffer of zero crossings is implemented +// the repetition of wavesets though is defined by xNum and xRep + +// with high trigger rates, xNum and xRep values and low rates you might +// have to increase overlapSize, see TZeroXBufRd help for a discussion of this topic + +( +SynthDef(\gran_1h, { |out = 0, soundBuf, zeroXBuf, zeroXNum, posLo = 0.1, posHi = 0.9, + posRateE = 0, posRateM = 1, xNum = 1, xRep = 1, rate = 1, trigRate = 100, amp = 1| + var sig, bufDur, pos, posRate; + + posRate = 10 ** posRateE * posRateM; + + bufDur = BufDur.kr(soundBuf); + pos = Phasor.ar( + 0, + BufRateScale.kr(soundBuf) * posRate * SampleDur.ir / bufDur, + posLo, + posHi + ) * zeroXNum; + + // move through the buffer of zero crossings + // the tempo (rate) refers to the sound buffer + sig = TZeroXBufRd.ar( + soundBuf, + zeroXBuf, + 0!2, // bufMix arg triggers stereo + trig: Impulse.ar(trigRate), + zeroX: pos, + xNum: xNum, + xRep: xRep, + mul: amp, + rate: rate * [1, 1.01], // a bit of decorrelation + overlapSize: 20 // larger overlapSize + ); + // by overlapping a DC can easily accumulate, so do leak + Out.ar(0, LeakDC.ar(sig) * amp) +}, metadata: ( + specs: ( + posLo: [0.01, 0.99, \lin, 0.01, 0.1], + posHi: [0.01, 0.99, \lin, 0.01, 0.5], + posRateE: [-2, 2, \lin, 1, -1], + posRateM: [0.1, 10, \exp, 0.01, 1], + rate: [0.3, 2, \lin, 0.01, 1], + trigRate: [5, 120, \lin, 0, 50], + xNum: [1, 5, \lin, 1, 2], + xRep: [1, 5, \lin, 1, 3], + amp: [0.0, 2, \lin, 0, 1] + ) +) +).add; + +\gran_1h.sVarGui([\soundBuf, b.bufnum, \zeroXBuf, z.bufnum, \zeroXNum, ~zeroXNum]).gui +) +:: + + +anchor::2:: +SECTION::2. Granulation driven by language + +subsection::Ex.2a: Basic buffer granulation Pbind + +note:: +Language-driven sequencing is not sample-exact in realtime (with NRT synthesis it is). This is related to hardware control and cannot be overcome currently. However for most practical purposes this might not be relevant. It is anyway a far less strong effect than the inaccuracies related to Out.ar and the combination of OffsetOut.ar and In.ar (see examples 2a-d in link::Tutorials/Live_Granulation::) +:: + + + +code:: + +// Control parameters like in Ex.1a, but implemented with a SynthDef for +// playing single grains and an appropriate Pbind, +// OffsetOut used for exact timing. + +( +SynthDef(\gran_2a, { |out = 0, pos = 0, sndBuf = 0, windowBuf = 1, granDur = 0.1, + rate = 1, loop = 1, panMax = 0, amp = 1| + var window, src; + src = PlayBuf.ar(1, sndBuf, BufRateScale.kr(sndBuf) * rate, + 1, round(pos * BufFrames.kr(sndBuf)), loop, 2); + window = BufRd.ar(1, windowBuf, + EnvGen.ar(Env([0, BufFrames.kr(windowBuf)], [granDur]), + doneAction: 2), loop, 4); + OffsetOut.ar(out, Pan2.ar(src, panMax, amp) * window); +}).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +w = Buffer.sendCollection(s, Signal.hanningWindow(1024)); +) + + +// Determining the correct buffer position depending on +// posRate, posLo and posHi needs a little calculation. +// In 1a this was done inside the ugen by a Phasor. +// See Ex.3b, 3c for doing position movement with a separate Synth. + +// PL placeholder patterns used, could also be Pfunc { ~ ... } + +( +p = Pbind( + \instrument, \gran_2a, + \sndBuf, b, + \windowBuf, w, + + \dur, 1 / PL(\trigRate), + \granDur, PL(\granDur), + \time, Ptime(), + \pos, Pfunc { |e| + var relTime = ~posRate * e.time / e.sndBuf.duration, relDif; + relDif = ~posHi - ~posLo; + relTime + rand2(~posDev) % relDif + ~posLo; + }, + \rate, PL(\rate), + \amp, PL(\amp), + \panMax, PLseq([-1,1]) * PL(\panMax), + \out, 0 +); + +VarGui([ + \posLo, [0.0, 0.99, \lin, 0.01, 0], + \posHi, [0.0, 0.99, \lin, 0.01, 1], + \posRate, [0.1, 2, \lin, 0.01, 1], + \posDev, [0, 0.2, 5, 0, 0.01], + \trigRate, [1, 200, \lin, 0.01, 120], + \granDur, [0.01, 0.3, \lin, 0.005, 0.06], + \rate, [0.1, 3, \lin, 0.01, 1], + \panMax, [0.0, 1, \lin, 0.0, 0.8], + \amp, [0.0, 1, \lin, 0.01, 0.25] + ], stream: p +).gui(varColorGroups: (0..8).clumps([4,1,1,1,1,1])) +) + +:: + +anchor::Ex.2b:: +subsection::Ex.2b: Switching between stochastic distributions + +code:: + +// Extended basic SynthDef from Ex.2a with bandpass filter + +( +SynthDef(\gran_2b, { |out = 0, pos = 0, sndBuf = 0, windowBuf = 1, granDur = 0.1, + rate = 1, loop = 1, panMax = 0, amp = 1, bpFreq = 500, bpRQ = 0.5, bpWet = 1| + var window, granSrc, src; + granSrc = PlayBuf.ar(1, sndBuf, BufRateScale.kr(sndBuf) * rate, + 1, round(pos * BufFrames.kr(sndBuf)), loop, 2); + window = BufRd.ar(1, windowBuf, + EnvGen.ar(Env([0, BufFrames.kr(windowBuf)], [granDur]), + doneAction: 2), loop, 4); + // do amplitude compensation, estimation like in Wavesets example by Alberto de Campo + src = (BPF.ar(granSrc, bpFreq, bpRQ, mul: (bpRQ ** -1) * (400 / bpFreq ** 0.5)) * + bpWet + (granSrc * (1 - bpWet))); + + OffsetOut.ar(out, Pan2.ar(src, panMax, amp) * window); +}).add; + + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +w = Buffer.sendCollection(s, Signal.hanningWindow(1024)); +) + + +// random types + +// 0: low value +// 1: low or high value, evenly distributed +// 2: evenly distributed +// 3: linear decrease from mean value +// 4: exponential distribution +// 5: beta distribution, default parameter 0.3 centers value at the borders +// 6: brownian movement (of first order) +// 7: brownian movement of second order (stepsize itself generated by brownian movement) + +// A second order brownian movement much more tends to get stuck at the +// borders than a normal (first order) brownian movement +// E.g. see Sergio Luque's presentation of Xenakis's stochastic synthesis: +// "Stochastic Synthesis, Origins and Extensions", pp 25-28 +// http://sergioluque.com + + +// Function that generates an array of PLx patterns +// of different random types (see PLx suite). +// These placeholders can refer to environmental variables +// to be set by the VarGui interface later on. + +( +d = { |keyLo, keyHi, betaProb = 0.3, brownStepFac = 0.01, + brown2Ratio = 1, brown2StepFac = 0.01| + // keyLo and keyHi must be Symbols, + // other args may be Symbols + var patLo, patHi, patDif; + + // avoid lo-hi reversing with Pbrown + patLo = min(PL(keyLo), PL(keyHi)); + patHi = max(PL(keyLo), PL(keyHi)); + patDif = patHi - patLo; + [ + PL(keyLo), + Pfunc { currentEnvironment[[keyLo, keyHi].choose] }, + PLwhite(keyLo, keyHi), + PLmeanrand(keyLo, keyHi), + PLexprand(keyLo, keyHi), + PLbeta(keyLo, keyHi, betaProb, betaProb), + PLbrown(patLo, patHi, patDif * PL(brownStepFac)), + PLbrown(patLo, patHi, + PLbrown( + patDif.neg * PL(brown2Ratio) / 2, + patDif * PL(brown2Ratio) / 2, + patDif * PL(brown2Ratio) * PL(brown2StepFac) + ) + ) + ] +}; +) + +// trigrate, granDur, rate and bpFreq are +// chosen between bounds according to the +// random distribution type notated with suffix D + +// single grains are filtered with a bandpass +// amount of effect controlled with bpWet + +( +p = Pbind( + \instrument, \gran_2b, + \sndBuf, b, + \windowBuf, w, + + \dur, 1 / PLswitch1(d.(\trigRateLo, \trigRateHi), \trigRateD), + \granDur, PLswitch1(d.(\granDurLo, \granDurHi), \granDurD), + \time, Ptime(), + \posRate, PL(\posRate), + \pos, Pfunc { |e| + var relTime = ~posRate * e.time / e.sndBuf.duration, relDif; + relDif = ~posHi - ~posLo; + relTime + rand2(~posDev) % relDif + ~posLo; + }, + \rate, PLswitch1(d.(\rateLo, \rateHi), \rateD), + \bpFreq, PLswitch1(d.(\bpFreqLo, \bpFreqHi), \bpFreqD), + \bpRQ, PL(\bpRQ), + \bpWet, PL(\bpWet), + + \amp, PL(\amp), + \panMax, PLseq([-1,1]) * PL(\panMax), + \out, 0 +); + +VarGui([ + \posLo, [0.0, 0.99, \lin, 0.01, 0.21], + \posHi, [0.0, 0.99, \lin, 0.01, 0.47], + \posRate, [0.1, 2, \lin, 0.01, 0.2], + \posDev, [0, 0.2, 5, 0, 0.002], + + \trigRateLo, [1, 200, \lin, 0.01, 21], + \trigRateHi, [1, 200, \lin, 0.01, 155], + \trigRateD, [0, 7, \lin, 1, 6], + + \granDurLo, [0.01, 0.6, \exp, 0.0, 0.037], + \granDurHi, [0.01, 0.6, \exp, 0.0, 0.4], + \granDurD, [0, 7, \lin, 1, 6], + + \rateLo, [0.1, 3, \lin, 0.01, 1.09], + \rateHi, [0.1, 3, \lin, 0.01, 1.63], + \rateD, [0, 7, \lin, 1, 1], + + \bpFreqLo, [50, 10000, \exp, 0.1, 54], + \bpFreqHi, [50, 10000, \exp, 0.1,8275], + \bpFreqD, [0, 7, \lin, 1, 1], + \bpRQ, [0.01, 0.99, \lin, 0.0, 0.07], + \bpWet, [0.0, 1, \linear, 0.0, 0.23], + + \panMax, [0.0, 1, \lin, 0.0, 0.85], + \amp, [0.0, 1, \lin, 0.01, 0.25] + ], stream: p +).gui(varColorGroups: (0..19).clumps([4,3,3,3,5,1,1])) +) + +:: + +anchor::Ex.2c:: +subsection::Ex.2c: Generating granular phrases with Pspawner + +code:: + +// This example needs SynthDef \gran_2b and Function d to be taken from Ex. 2b + +( +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +w = Buffer.sendCollection(s, Signal.hanningWindow(1024)); +) + +// A simple form of Pspawner is used to generate phrases. +// Phrase length params are taken a bit roughly as sustain and +// rest times also depend on randomly varying grain lengths. +// spSustain controls medium sustain time (without grain length overhead) +// spLegato controls medium legato factor (disregarding reduction by grain length overhead) +// spDev is causing separate random deviation of spSustain and spLegato +// between 1/(1+spDev) and 1+spDev + +// random distribution switching is restricted here to +// types 6 and 7 (random walks of first and second order) +// to force individual sound qualities of phrases. + +( +// declare var here as pattern is repeatedly evaluated from within the Pspawner, +// interpreter variable would be unsafe if running examples in parallel + +var p = Pbind( + \instrument, \gran_2b, + \sndBuf, b, + \windowBuf, w, + + \dur, 1 / PLswitch1(d.(\trigRateLo, \trigRateHi), \trigRateD), + \granDur, PLswitch1(d.(\granDurLo, \granDurHi), \granDurD), + \time, Ptime(), + \posRate, PL(\posRate), + + // random timeOffset added with each spawning + \pos, Pfunc { |e| + var relTime = ~posRate * e.time / e.sndBuf.duration + e.timeOffset, relDif; + relDif = ~posHi - ~posLo; + relTime + rand2(~posDev) % relDif + ~posLo; + }, + \rate, PLswitch1(d.(\rateLo, \rateHi), \rateD), + \bpFreq, PLswitch1(d.(\bpFreqLo, \bpFreqHi), \bpFreqD), + \bpRQ, PL(\bpRQ), + \bpWet, PL(\bpWet), + + \amp, PL(\amp), + \panMax, PLseq([-1,1]) * PL(\panMax), + \out, 0 +); + +q = Pspawner({ |sp| + var randomizer = { |x| var y = rand(x); 0.5.coin.if { 1 + y }{ 1 / (1 + y) } }, + sus, legato, delta; + + loop { + sus = ~spSustain * randomizer.(~spDev); + legato = ~spLegato * randomizer.(~spDev); + + // take random offset for each phrase + sp.par(Pfindur(sus, Psetpre(\timeOffset, 5.0.rand, p))); + delta = sus / (~spLegato * randomizer.(~spDev)); + sp.wait(delta) + } +}); + +VarGui([ + \posLo, [0.0, 0.99, \lin, 0.01, 0.16], + \posHi, [0.0, 0.99, \lin, 0.01, 0.41], + \posRate, [0.1, 2, \lin, 0.01, 1.4], + \posDev, [0, 0.2, 5, 0, 0.0017], + + \trigRateLo, [1, 200, \lin, 0.01, 70], + \trigRateHi, [1, 200, \lin, 0.01, 150], + \trigRateD, [6, 7, \lin, 1, 7], + + \granDurLo, [0.01, 0.6, \exp, 0.0, 0.02], + \granDurHi, [0.01, 0.6, \exp, 0.0, 0.11], + \granDurD, [6, 7, \lin, 1, 6], + + \rateLo, [0.1, 3, \lin, 0.01, 0.2], + \rateHi, [0.1, 3, \lin, 0.01, 1.86], + \rateD, [6, 7, \lin, 1, 7], + + \bpFreqLo, [50, 10000, \exp, 0.1, 67], + \bpFreqHi, [50, 10000, \exp, 0.1, 5885], + \bpFreqD, [6, 7, \lin, 1, 6], + \bpRQ, [0.01, 0.99, \lin, 0.0, 0.17], + \bpWet, [0.0, 1, \linear, 0.0, 0.38], + + \spSustain, [0.2, 2, \linear, 0.0, 0.884], + \spLegato, [0.6, 1.2, \linear, 0.0, 0.996], + \spDev, [0.0, 1, \linear, 0.0, 0.41], + + \panMax, [0.0, 1, \lin, 0.0, 0.85], + \amp, [0.0, 1, \lin, 0.01, 0.35] + ], stream: q +).gui(varColorGroups: (0..22).clumps([4,3,3,3,5,3,1,1]) ) +) + +:: +anchor::Ex.2d:: +subsection::Ex.2d: Wave sets + +code:: + +// For this example you need Alberto de Campo's Wavesets class +// (Quark extension, implementation following definitions of Trevor Wishart) +// and the Function d from Ex.2b. + +// the wave set player synth optionally adds a BPF applied to the signal, +// amount can be controlled with bpWet +// attack and release time > 0 for smoothening + +( +SynthDef(\wsPlayer, { arg out = 0, buf = 0, start = 0, length = 441, + rate = 1, att = 0.03, rel = 0.03, wvDur = 1, panMax = 0, amp = 0.2, + delayL = 0.0, delayR = 0.0, bpFreq = 500, bpRQ = 0.5, bpWet = 1; + var phasor, env, granSrc, src, attEff, relEff, sus; + + phasor = Phasor.ar(0, BufRateScale.ir(buf) * rate, 0, length) + start; + attEff = min(att, wvDur/2); + relEff = min(rel, wvDur/2); + sus = wvDur - attEff - relEff; + + env = EnvGen.ar(Env([0, 1, 1, 0], [attEff, sus, relEff], \sine), doneAction: 2); + granSrc = BufRd.ar(1, buf, phasor); + src = (BPF.ar(granSrc, bpFreq, bpRQ, mul: (bpRQ ** -1) * (400 / bpFreq ** 0.5)) * + bpWet + (granSrc * (1 - bpWet))); + OffsetOut.ar(out, Pan2.ar(src, panMax, amp) * env); +}).add; + +a = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +b = Buffer.read(s, a); +w = Wavesets.from(a); + +// relative positions of zero crossings +x = w.fracXings.drop(-1) / w.numFrames; +) + + +// Note that trigRate / event duration is not controlled directly in this setup, +// it's derived from wave set length (may vary) and legato factor. +// For short wave sets and a low legato value durs could become so short that +// a sudden mass of grains could cause hangs. +// To avoid this a parameter maxTrigRate is invented. + +// As wavesets start at distinguished positions in the buffer and +// - together with legato - durations are determined, +// an additionally given position rate in general cannot result in a "correct" +// looping through the buffer, there will be a deviation. +// Here two types of approximation can be choosen with posType: +// Type 0 takes the waveset nearest to the calculated exact position. +// Type 1 linearly maps positions to wave set indices. +// As wave sets are of different length the latter is a rough heuristic +// accelerating buffer parts with relatively low frequencies. + +( +p = Pbind( + \instrument, \wsPlayer, + + // refering to interpreter variables within Pfuncs + // when running several examples in parallel is unsafe, + // so pass them here, then access from within the event + + \b, b, + \w, w, + \x, x, + + \time, Ptime(), + \posLo, PL(\posLo), + \posHi, PL(\posHi), + \posRate, PL(\posRate), + + \pos, Pfunc { |e| (e.time * e.posRate / e.b.duration) % + (e.posHi - e.posLo) + e.posLo }, + + // Estimation of ws index from relative position, see explanation above, + // indexIn might be a bottleneck with large buffers, + // also a more rough estimation of ws index could be used: + // \startWs, Pfunc { |e| e.pos.linlin(0, 1, 0, e.w.xings.size - 2).round.asInteger } + + \startWs, Pfunc { |e| + (~posType == 0).if { + e.x.indexIn(e.pos) + }{ + e.pos.linlin(e.posLo, e.posHi, e.x.indexIn(e.posLo), e.x.indexIn(e.posHi)) + .round.asInteger + } + }, + + \numWs, PLswitch1(d.(\numWsLo, \numWsHi), \numWsD), + \repeats, PLswitch1(d.(\repeatsLo, \repeatsHi), \repeatsD), + + \bpFreq, PLswitch1(d.(\bpFreqLo, \bpFreqHi), \bpFreqD), + \bpRQ, PL(\bpRQ), + \bpWet, PL(\bpWet), + + \rate, PLswitch1(d.(\rateLo, \rateHi), \rateD), + \data, Pfunc { |e| e.w.frameFor(e.startWs, e.numWs) }, + + \buf, b.bufnum, + \start, Pkey(\data).collect(_[0]), // startFrame + \length, Pkey(\data).collect(_[1]), // length (frameNum) + \wvDur, Pkey(\data).collect(_[2]) * Pkey(\repeats), // sustain time + + \calculatedDur, Pkey(\wvDur) / PLswitch1(d.(\legatoLo, \legatoHi), \legatoD), + \dur, Pfunc { |e| max(e.calculatedDur, 1 / ~maxTrigRate) }, + + \panMax, PLseq([-1,1]) * PL(\panMax), + \amp, PL(\amp), + \out, 0 +); + + +VarGui([ + \posLo, [0, 1, \lin, 0.0, 0.15], + \posHi, [0, 1, \lin, 0.0, 0.45], + \posRate, [0.0, 2, \lin, 0.01, 0.25], + \posType, [0, 1, \lin, 1, 0], + + \numWsLo, [1, 100, \lin, 1, 5], + \numWsHi, [1, 100, \lin, 1, 23], + \numWsD, [0, 7, \lin, 1, 6], + + \repeatsLo, [1, 4, \lin, 1, 1], + \repeatsHi, [1, 4, \lin, 1, 2], + \repeatsD, [0, 7, \lin, 1, 6], + + \maxTrigRate, [1, 250, \lin, 1, 200], + + \bpFreqLo, [50, 10000, \exp, 0.1, 67], + \bpFreqHi, [50, 10000, \exp, 0.1, 9600], + \bpFreqD, [0, 7, \lin, 1, 7], + \bpRQ, [0.01, 0.99, \lin, 0.005, 0.22], + \bpWet, [0.0, 1, \lin, 0.005, 0.0], + + \rateLo, [0.05, 2, \lin, 0.0, 0.32], + \rateHi, [0.05, 2, \lin, 0.0, 1.3], + \rateD, [0, 7, \lin, 1, 7], + + \att, [0.0, 0.05, \lin, 0.001, 0.001], + \rel, [0.0, 0.05, \lin, 0.001, 0.001], + + \panMax, [0.0, 1, \lin, 0, 0.8], + \legatoLo, [0.3, 25, \exp, 0, 0.4], + \legatoHi, [0.3, 25, \exp, 0, 5.5], + \legatoD, [0, 7, \lin, 1, 7], + \amp, [0.0, 2.0, \lin, 0.0, 0.7] + ], + stream: p +).gui(varColorGroups: (0..25).clumps([4,3,3,1,5,3,2,1,3,1])) +) + + +:: + + +anchor::3:: +SECTION::3. Hybrid Implementations + +subsection::Ex.3a: Granulation with ugen plus step sequencing + +code:: + +// Here the trigger for the TGrains ugen comes from a Pbind +// which also generates rates like a step sequencer + + +( +SynthDef(\gran_3a, { arg out = 0, posLo = 0.0, posHi = 1.0, + posRate = 1, posDev = 0.01, bufNum = 0, t_trig = 0, + granDur = 0.1, t_rate = 1.0, rateDev = 0, + panMax = 1, amp = 0.1, interp = 4; + + var pan, pos, bufDur, bufDurSection, posDif; + + posDif = posHi - posLo; + bufDur = BufDur.kr(bufNum); + bufDurSection = bufDur * posDif; + pos = posLo * bufDur + + (Phasor.ar(0, BufRateScale.kr(bufNum) * posRate / SampleRate.ir, posLo * bufDur, posHi * bufDur) + + (TRand.kr(-0.5, 0.5, t_trig) * posDev * bufDur)).mod(bufDurSection); + pan = Demand.kr(t_trig, 0, Dseq([panMax, panMax.neg], inf) * 0.999); + Out.ar(out, TGrains.ar(2, t_trig, bufNum, t_rate, pos, granDur, pan, 1, interp) * amp); + }, metadata: ( + specs: ( + posLo: [0.01, 0.99, \lin, 0.01, 0], + posHi: [0.01, 0.99, \lin, 0.01, 1], + posRate: [0.1, 2, \lin, 0.01, 1], + posDev: [0, 0.2, 5, 0, 0.01], + panMax: [0.0, 1, \lin, 0.005, 0.8], + amp: [0.0, 1, \lin, 0.005, 0.5] + ) + ) +).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. +) + + +// As the setting Pbind needs to know the Synth's nodeID +// the Synth has to be started explicitely and passed to the VarGui later on +// (VarGui takes Synths as well as SynthDefs, passing a SynthDef is recommended in general). +// The Synth starts silently as t_trig defaults to 0. + + +( +x = Synth(\gran_3a, [\bufNum, b]).register; + +p = Pbind( + \type, \set, + \id, x, + \args, [\t_trig, \t_rate, \granDur], + + \dur, PL(\dur), + \granDur, Pkey(\dur) * PL(\legato), + \t_trig, 1, + \t_rate, PLseq(\midi).midiratio +); +) + +// Do start and pause with the Pbind (EventStreamPlayer) player. +// If you stop the Synth you cannot resume audio with a new Synth +// as the EventStreamPlayer has lost the correct nodeID +// (however the Synth can be paused and resumed). + +( +VarGui(varCtr: [ + \dur, [0.01, 0.1, \lin, 0, 0.05], + \legato, [0.3, 3, \lin, 0, 1], + \midi, [-12, 12, \lin, 1, 1] ! 8 + ], synth: x, stream: p +).gui; +) + +:: + + +subsection::Ex.3b: Using external control synths + +code:: + +// Example needs SynthDef from Ex.2a + +// Also with language-driven granulation +// controls can be delegated to separate Synths, +// which output to control buses. +// Control inputs of single synths can read from +// these buses (comfortably use aBus.asMap in the Pbind) +// or synths can read from buses with In.kr (needs extra definition). + +// A nearby parameter to determine with a separate synth is grain position + +( +SynthDef(\bufPhasor, { |out = 0, sndBuf = 0, posRate = 1, posLo = 0, posHi = 1, posDev = 0.01| + var pos, posDif; + posDif = posHi - posLo; + pos = Phasor.ar(1, posRate * BufRateScale.kr(sndBuf) / BufFrames.kr(sndBuf), 0, posDif) + + WhiteNoise.kr(posDev / 2) % posDif + posLo; + Out.kr(out, A2K.kr(pos)); + } +).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +w = Buffer.sendCollection(s, Signal.hanningWindow(1024)); +c = Bus.control(s,1); +) + +// in GUI start EventStreamPlayer and bufPhasor Synth + +( +p = Pbind( + \instrument, \gran_2a, + \sndBuf, b, + \windowBuf, w, + + \dur, 1 / PL(\trigRate), + \granDur, PL(\granDur), + + \pos, c.asMap, + \rate, PL(\rate), + \amp, PL(\amp), + \panMax, PLseq([-1,1]) * PL(\panMax), + \out, 0 +); + +VarGui([ + \trigRate, [1, 200, \lin, 0.01, 50], + \granDur, [0.01, 0.3, \lin, 0.005, 0.12], + \rate, [0.1, 3, \lin, 0.01, 1], + \panMax, [0.0, 1, \lin, 0.0, 0.8], + \amp, [0.0, 1, \lin, 0.01, 0.3] + ],[ + \out, c.index, + \sndBuf, b.bufnum, + \posLo, [0, 1, \linear, 0.005, 0], + \posHi, [0, 1, \linear, 0.005, 1], + \posRate, [0.1, 2, \linear, 0.01, 1], + \posDev, [0, 0.2, 5, 0, 0.01] + ], + p, \bufPhasor +).gui(sliderPriority: \synth, playerPriority: \synth); +) + + +:: + + +subsection::Ex.3c: Switching between ugens of external control synths + +code:: + +// Example needs SynthDef from Ex.2a + +// external control synth for position as in Ex.3a, but with +// different types of movement to select + +( +SynthDef(\bufPosLFO, { |out = 0, lfoType = 0, freq = 1, posLo = 0, posHi = 1, posDev = 0.01| + var pos, posDif; + posDif = posHi - posLo; + pos = WhiteNoise.kr(posDev / 2) + Select.kr(lfoType, + [LFDNoise0, LFDNoise1, LFDNoise3].collect(_.kr(freq, posDif))) % posDif + posLo; + Out.kr(out, pos); +}).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +w = Buffer.sendCollection(s, Signal.hanningWindow(1024)); +c = Bus.control(s,1); +) + + +// added parallel grains +// in GUI start bufPhasor Synth before or together with EventStreamPlayer + +// buffer position movement can be forward and backward +// lfoType 0: LFDNoise0 (jumps) +// lfoType 1: LFDNoise1 (linear interpolation) +// lfoType 2: LFDNoise3 (cubic interpolation, smooth, useful in many control contexts) + +( +p = Pbind( + \instrument, \gran_2a, + \sndBuf, b, + \windowBuf, w, + + \dur, 1 / PL(\trigRate), + \granDur, PL(\granDur), + + \pos, c.asMap, + \rate, PL(\rate) * PL(\midiAdd).midiratio, + \amp, PL(\amp), + \panMax, PLseq([-1,1]) * PL(\panMax), + \out, 0 +); + +VarGui([ + \trigRate, [1, 200, \lin, 0.01, 80], + \granDur, [0.01, 0.3, \lin, 0.005, 0.195], + \rate, [0.1, 1.5, \lin, 0.01, 0.6], + \midiAdd, [-5, 0].collect([-12, 12, \lin, 1, _]), + \panMax, [0.0, 1, \lin, 0.0, 0.95], + \amp, [0.0, 1, \lin, 0.01, 0.15] + ],[ + \out, c.index, + \posLo, [0, 1, \linear, 0.005, 0.15], + \posHi, [0, 1, \linear, 0.005, 0.43], + \posDev, [0, 0.2, 5, 0, 0.0], + \freq, [0.01, 2, \lin, 0, 0.96], + \lfoType, [0, 2, \lin, 1, 2] + ], + p, \bufPosLFO +).gui(sliderPriority: \synth, playerPriority: \synth); +) + +:: + + +subsection::Ex.3d: Pattern-driven sequencing of granular events using demand rate ugens + +code:: + +// This is basically the same as SynthDef \gran_1d with an additional envelope, +// grain position phasor is left out, position is determined by a phasor synth via bus and mapping. + +( +// length of demand rate sequence, you might want to check larger sizes +// in connection with gui arg tryColumnNum > 1 +~n = 5; + + +SynthDef(\gran_3d, { |out = 0, soundBuf, envBuf, att = 0.1, sus = 1, rel = 1, pos = 0.5, + granDurMul = 1, rateMul = 1, overlapMul = 1, panMul = 1, amp = 0.5, interp = 2| + + var signal, bufDur, granDur, granGate, relGranDurs, overlap, relOverlaps, overlapSeq, + pan, rate, relRates, rateSeq, granDurSeq; + + // array args for demand rate sequencing, short form of NamedControl + relGranDurs = \relGranDurs.kr(0.1!~n); + relRates = \relRates.kr(1!~n); + relOverlaps = \relOverlaps.kr(1!~n); + + // Dstutter (or Dunique) necessary as granDurSeq is polled twice: granGate and granDur + granDurSeq = Dstutter(2, Dseq(relGranDurs, inf)); + rateSeq = Dseq(relRates, inf); + overlapSeq = Dseq(relOverlaps, inf); + + granGate = TDuty.ar(granDurSeq * granDurMul); + granDur = Demand.ar(granGate, 0, granDurSeq * granDurMul); + rate = Demand.ar(granGate, 0, rateSeq) * rateMul; + pan = Demand.ar(granGate, 0, Dseq([1, -1], inf)) * 0.999 * panMul; + overlap = Demand.ar(granGate, 0, overlapSeq) * overlapMul; + + bufDur = BufDur.kr(soundBuf); + signal = TGrains.ar(2, granGate, soundBuf, rate, pos * bufDur, granDur * overlap, pan, 1, interp) * + EnvGen.kr(Env([0, 1, 1, 0], [att, sus, rel]), doneAction: 2); + + Out.ar(out, signal * amp); +} +).add; + +SynthDef(\bufPhasor_2, { |out = 0, sndBuf = 0, posRateE = 0, posRateM = 1, + posLo = 0, posHi = 1, posDev = 0.01| + + var pos, posDif, posRate; + posDif = posHi - posLo; + posRate = 10 ** posRateE * posRateM; + pos = Phasor.ar(1, posRate * BufRateScale.kr(sndBuf) / BufFrames.kr(sndBuf), 0, posDif) + + WhiteNoise.kr(posDev / 2) % posDif + posLo; + Out.kr(out, A2K.kr(pos)); +} +).add; + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + +c = Bus.control(s,1); +) + + +// Pattern control of granular events + +// Every granular event gets a duration between durLo and durHi (exp distribution) +// and an envelope according to att, sus and rel. +// One of four events is randomly defined as rest. + +// arg arrays relGranDurs, relRates and relOverlaps determine +// demand rate sequencing for granulation as in Ex. 1d. +// Per event they are multiplied with corresponding factors +// limited by granDurMulLo/Hi, rateMulLo/Hi and overlapMulLo/Hi + +// start phasor synth (first row in player console) before event stream player +// event stream player might start with rest + + +( +// Passing arrayed args with an event pattern requires +// wrapping them into an array or Ref object. +// This is necessary to distinguish from triggering +// several synths per event. +// .collect(`_) is short for .collect { |x| Ref(x) } +// .collect([_]) or .collect { |x| [x] } would also be possible + +p = Pbind( + \instrument, \gran_3d, + \soundBuf, b, + \pos, c.asMap, + + \dur, PLexprand(\durLo, \durHi), + \type, PLshufn(\note!3 ++ \rest), + \att, PL(\att), + \sus, PL(\sus), + \rel, PL(\rel), + + + \relGranDurs, PL(\relGranDurs).collect(`_), + \granDurMul, PLwhite(\granDurMulLo, \granDurMulHi), + + \relOverlaps, PL(\relOverlaps).collect(`_), + \overlapMul, PLwhite(\overlapMulLo, \overlapMulHi), + + \relRates, PL(\relRates).collect(`_), + \rateMul, PLwhite(\rateMulLo, \rateMulHi), + + \panMul, PL(\panMul), + \amp, PL(\amp) +); + +VarGui([ + \durLo, [0.05, 1.5, \lin, 0, 0.16], + \durHi, [0.05, 1.5, \lin, 0, 1.2], + \att, [0.01, 0.6, \lin, 0, 0.4], + \sus, [0.01, 0.6, \lin, 0, 0.05], + \rel, [0.01, 0.6, \lin, 0, 0.5], + \relGranDurs, { |i| [0.01, 0.1, \lin, 0, i * 0.005 + 0.02] } ! ~n, + \granDurMulLo, [0.03, 2, \lin, 0, 0.05], + \granDurMulHi, [0.03, 2, \lin, 0, 1.8], + + \relRates, { |i| [0.1, 1.5, \lin, 0, (5-i) * 0.1 + 0.5] } ! ~n, + \rateMulLo, [0.1, 2, \lin, 0, 0.5], + \rateMulHi, [0.1, 2, \lin, 0, 1.5], + + \relOverlaps, [0.5, 3, \lin, 0, 2] ! ~n, + \overlapMulLo, [0.1, 2, \lin, 0, 0.25], + \overlapMulHi, [0.1, 2, \lin, 0, 1.8], + + \panMul, [0.0, 1, \lin, 0.0, 0.8], + \amp, [0.0, 3, \lin, 0.01, 1.8] + ],[ + \out, c.index, + \sndBuf, b.bufnum, + \posLo, [0, 1, \lin, 0.005, 0.1], + \posHi, [0, 1, \lin, 0.005, 0.9], + \posRateE, [-3, 4, \lin, -1, 0], + \posRateM, [0.1, 10, \exp, 0.01, 1], + \posDev, [0, 0.2, 5, 0, 0.01] + ], + p, \bufPhasor_2 +).gui( + tryColumnNum: 2, + sliderPriority: \synth, + playerPriority: \synth, + varColorGroups: (0..(~n*3+12)).clumps([2,3,~n+2,~n+2,~n+2,1,1]), + synthColorGroups: (0..6).clumps([1,1,2,2,1]), + labelWidth: 90, + sliderWidth: 300 +); +) + +:: + + +anchor::4. Extensions of Setups:: +SECTION::4. Extensions of Setups + + +list:: +## strong::Changing ranges and scaling:: + +This concerns all control parameters in question. E.g. the layering of long grains (> 200 ms) in connection with small rates of position changes (posRate) often has interesting effects. One may want to drop the term granulation in that case, though it's the same structure of synthesis. For such parameters with a very large coefficient boundHi / boundLo one may take exponential scaling, and if this is not fine enough you can invent a control pair of mantissa and exponent (as for posRate in link::#Ex.1b::). +:: + +list:: +## strong::Parameter linkage:: + +On the one hand a logical restriction, on the other hand it can make sense from a musical / perceptional point of view. E.g. shortening of grains could be linked with a raise of rate (as low frequencies might fail to unfold in short grains). Anyway parameter linkage is reducing complexity - it's a trade-off between simplicity of the interface and exclusion of certain constellations which should be considered from case to case. +:: + +list:: +## strong::Inventing and extending controls and LFO changes dependant on specific buffers and parameter sets:: + +Say one has started playing around with a certain buffer and a general granulation patch. A parameter set that gives an interesting sound may react in a very interesting way on a change of a single parameter e.g., trigger rate. Then it may be an option to build in a control or a LFO specifically designed for that parameter - LFO is meant here in a general sense, it could be a LFO in a Synth, a dedicated LFO synth or defined by a rapidly sequencing Pattern. +:: + +list:: +## strong::Iterated granulation:: + +Granulation of buffers themselves resulting from buffer granulation can give interesting effects. +:: + +list:: +## strong::Spatialization:: + +For the sake of ease and comparison a L/R-switch per grain with one panning parameter was used in the examples above. Needless to say that spatial scattering of grains remains a large field of experimentation. Generally spoken spatialization can be part of the synthesis process or carried through independently afterwards, but also a combination of both approaches is feasible. +:: + +list:: +## strong::Effects:: + +Effect processing can be applied to all or single grains in a language-driven granulation setup. There can be one or more effects, serial or in parallel, defined outside or inside the Synth playing the buffer, as with the BPF in link::#Ex.2b::, link::#Ex.2c::, link::#Ex.2d::. In these examples the bandpass is applied in general, but also a sequencing of effects can be done. And even in the case of just one effect (e.g. reverb) a decent sequencing of Fx- / noFx-events can sound very interesting. This can be done with PbindFx (as in the project link::Tutorials/kitchen_studies::), sequencing not more than one effect per grain can be done straightly: continously running effect synths read from different buses and events with different out values cause the player synths to output to these buses. +:: + +list:: +## strong::Micro rhythm:: + +See Xenakis' suggestion of Sieves with rhythm generation as a special application, a granulation example is contained in the example section of: link::Tutorials/Sieves_and_Psieve_patterns::. PSPdiv, a dynamic multi-layer pulse divider, which is based on Pspawner, can also be used in this context, see the last example of link::Classes/PSPdiv::. +:: diff --git a/HelpSource/Tutorials/DX_suite.schelp b/HelpSource/Tutorials/DX_suite.schelp new file mode 100644 index 0000000..b5d442c --- /dev/null +++ b/HelpSource/Tutorials/DX_suite.schelp @@ -0,0 +1,156 @@ +TITLE::DX suite +summary::pseudo ugens for crossfaded mixing and fanning according to demand-rate control +categories:: Libraries>miSCellaneous>DX suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/PbindFx, Tutorials/kitchen_studies + + +DESCRIPTION:: + +DX (Demand XFade) ugens are built upon DemandEnvGen and PanAz and can hence use their dynamic control options. Their user interface and underlying ugen structure is almost identical, due to multichannel expansion they are capable of triggering complex signal flows. As demand rate sequencing can be performed fast, DX ugens can, beneath their functionality as signal distribution controllers in the medium or large time-scale, be used as genuin synthesis tools in the area of microsound, e.g. for fast switching between sources as well as different processings of them. DXEnvFan and DXEnvFanOut give specific options as they can be used as multichannel envelopes and triggers at the same time. This e.g. enables server-side granulation techniques for arbitrary sound sources that are difficult to handle with granulation ugens alone, such es effect sequencing per grain and others. A related application, not necessarily in a granular time-scale, is crossfaded playback from different buffer positions. Finally DX fanning ugens allow server-side definition of spatial movements by crossfading between non-adjacent channels respectively buses. + + +note:: +As interface and conventions of DX ugens are nearly identical, I didn't double examples for all features. It's recommended to start with the DX suite overview and go through the help file examples in this order: link::Classes/DXMix#Ex.1#DXMix:: - link::Classes/DXMixIn#Ex.1#DXMixIn:: - link::Classes/DXEnvFan#Ex.1#DXEnvFan:: - link::Classes/DXEnvFanOut#Ex.1#DXEnvFanOut:: - link::Classes/DXFan#Ex.1#DXFan:: - link::Classes/DXFanOut#Ex.1#DXFanOut::. Some general conventions are treated in detail in the following examples: fades and steps in link::Classes/DXMix#Ex.2#DXMix, Ex.2:: – width and offset arguments in link::Classes/DXMix#Ex.3#DXMix, Ex.3:: – multichannel expansion in link::Classes/DXMix#Ex.6#DXMix, Ex.6:: – crossfade types in link::Classes/DXEnvFan#Ex.1#DXEnvFan, Ex.1::. +:: + +note:: +PanAz.ar's args pos and orientation were scaled wrongly in SC versions up to 3.8. DX ugens neutralize this bug by inverse scaling, so it should actually work the same with SC versions before 3.9 with the exception of examples with modulatable width (disabled in earlier versions). I didn't encounter differences in any other test examples, however I'd rather recommend a SC version from 3.9 onwards, if you have the choice. +:: + +note:: +Depending on the multichannel sizes it might be necessary to increase server resources, i.e. the number of interconnect buffers (e.g. s.options.numWireBufs = 256; s.reboot). See link::Classes/DXMix#Ex.8#DXMix, Ex.8:: and link::Classes/DXEnvFan#Ex.2#DXEnvFan, Ex.2::, link::Classes/DXEnvFan#Ex.4#DXEnvFan, Ex.4:: for aspects of CPU demand. +:: + +note:: +In my tests timing was exact up to one sample. So when used for granulation DX ugens avoid the inevitable inccuracies of language-based triggering in realtime. However care has to be taken: fade and step times must be larger than the duration of a control cycle. With default values sampleRate = 44100 Hz and blockSize = 64, this equals ca. 0.00145 sec. If you go below, the fade mechanism is messed up and you get jumps and clicks. Accordingly with fadeModes 3 and 4 you have to ensure that the remaining 'real' stepTime, which is calculated by stepTime minus fadeTime, is larger than this threshold. But as a workaround you can always lower the blocksize. See link::Classes/DXFan#Ex.4#DXFan, Ex.4:: for aspects of granulation with high trigger rates / short grain durations. +:: + +note:: +The current implementation is bound to counting with Dseries and – inherent to 32 bit floats – the integer accuracy limit of 2 ** 24 - 1 = 16777215. This can be an issue with setups that are using extreme short durations for hours. +:: + + +subsection::Credits +Thanks to Wouter Snoei for his PlayBufCF class. It gave me a lot of inspiration for DX ugens – although in the end the implementation with PanAz and DemandEnvGen is quite different. Thanks also to Till Bovermann for ironing out a longstanding bug in PanAz. + + + + +SECTION::Examples + +anchor::Ex.1:: +subsection::Ex.1 DXMix, crossfade sequencing + +code:: +// Crossfaded switching between sources, there exists a number of options, +// e.g. for fade mode (inclusion of steps = plateau phases), curve type and width. +// The syntax of passing the sources within a Ref object is necessary +// to distinguish from multichannel expansion. + +( +{ + DXMix.ar( + Dseq([1, 0, 1, 2], inf), + `([SinOsc.ar(100), WhiteNoise.ar(), LFTri.ar(100)]), + stepTime: 0.015, + fadeTime: 0.015, + fadeMode: 1, // alternate steps and fades + sine: 0, // sine type or not + equalPower: 0 // square-rooted (equal power) or not + ) +}.plot(0.12) +) +:: + + +anchor::Ex.2:: +subsection::Ex.2 Comparison of fanning DX ugens + +code:: +// Crossfading source signals with DXFan, +// result is a multichannel signal of size that has to be passed + +( +{ + DXFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + SinOsc.ar(500), + size: 8, + fadeTime: 0.01 + ) +}.plot(0.1) +) + +( +{ + DXFan.ar( + Dseq([2, 0, 4, 6], inf), + `(SinOsc.ar([300, 700])), + size: 8, + fadeTime: 0.01 + ) +}.plot(0.1) +) + + +// DXEnvFan returns a multichannel envelope, +// the size has to be passed. +// Without any options it defaults to the square-rooted (equal power) sine type. + +( +{ + DXEnvFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + size: 8, + fadeTime: 0.01, + ) +}.plot(0.1) +) + +// proof of concept with other fanning DX ugens: + +// for getting the same result with DXFan pass a DC as source +( +{ + DXFan.ar( + Dseq([3,2,1,4,5,6,7,0], inf), + DC.ar(1), + size: 8, + fadeTime: 0.01 + ) +}.plot(0.1) +) + +// envelopes can also be sent to buses, for plotting we can get them back with In, +// here the size is considered via the bus. + +( +a = Bus.audio(s, 8); +{ + DXEnvFanOut.ar( + Dseq([3,2,1,4,5,6,7,0], inf) + a.index, + fadeTime: 0.01 + ); + In.ar(a, 8) +}.plot(0.1) +) + +// analogously with DXFanOut and DC input + +( +b = Bus.audio(s, 8); +{ + DXFanOut.ar( + Dseq([3,2,1,4,5,6,7,0], inf) + b.index, + DC.ar(1), + fadeTime: 0.01 + ); + In.ar(b, 8) +}.plot(0.1) +) + +( +a.free; +b.free; +) +:: diff --git a/HelpSource/Tutorials/Event_patterns_and_Functions.schelp b/HelpSource/Tutorials/Event_patterns_and_Functions.schelp new file mode 100644 index 0000000..d256ede --- /dev/null +++ b/HelpSource/Tutorials/Event_patterns_and_Functions.schelp @@ -0,0 +1,320 @@ + + +TITLE::Event patterns and Functions +summary::using event patterns with functional elements +categories:: Libraries>miSCellaneous>General Tutorials, Streams-Patterns-Events +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/PLx_suite, Classes/VarGui, Tutorials/VarGui_shortcut_builds + +DESCRIPTION:: + +This is a tutorial file about event patterns with functional code. Features concerning scope are regarded along a line from Functions to Streams, Streams of Events and EventStreamPlayers. These general characteristics allow to generate a parametrized family of EventStreamPlayers from a single event pattern definition and are used as a base for controlling EventStreamPlayers with link::Classes/VarGui:: . For a convenience way to generate Patterns with environment-dependent reference see link::Tutorials/PLx_suite:: . + + +SECTION::Functions in Environments + +Environments are a standard way to separate namespaces in SuperCollider. Functions may include strong::environmental variables:: and then strong::dynamic scope:: comes into play: variables get the value from the context in which the Function is strong::evaluated::. Anyway a specific environment can be linked with the Function, the latter will hold for Streams polled from Patterns with certain functional code - it's just the environment in which the stream has been generated from the pattern. + +Take a simple Function with one argument and assign it to the strong::interpreter variable:: f: + +code:: + +f = { |x| x*2 + ~a }; + +// equivalent: + +f = { |x| x*2 + currentEnvironment[\a] }; + +// now set the variable in the Environment (context) in which to evaluate the Function later on + +~a = 1; + +// equivalent: +// currentEnvironment[\a] = 1; + +// not surprising the result of the following operation: + +f.value(1); + +// or shorter: +// f.(1) + +-> 3 + +// now evaluation in new environment, value taken from there: + +( +e = Environment[\a -> 5]; +e.use { f.value(1) }; +) + +-> 7 + +// with inEnvir a new Function is bound to a specific Environment + +( +g = f.inEnvir(e); +g.(1); +) + +-> 7 + + +f.(1); + +-> 3 + +:: + +So either by evaluating the original Function in different Environments or, equivalently, by deriving new Functions from the original attached to different Environments we create a parametrized family of Functions. The Function's basic behaviour is pretty simple (x*2), in dependance of the context it is just varied (a linear offset is added). With more environmental variables a much greater complexity could be introduced, though possibly obscuring the basic behaviour. + +This priciple can now easily be extended to Streams, Streams of Events and EventStreamPlayers. We define the basic behaviour of an event stream with an event pattern (Pbind), then enrich the event pattern with functional code and can have a huge variety of EventStreamPlayers in different Environments with different parameter sets - all playable and modifiable in realtime (in parallel or independentely) and all polled from one single event pattern definition! Step by step: + +SECTION::Streams in Environments + +What happens in the case of a Stream that will repeatedly evaluate a Function? + +code:: + +// the Pfunc just describes what a Stream, polled from it, should do (evaluate a Function by the method next) +// the Pfunc itself is not bound to a specific Environment + +p = Pfunc { ~a }; + +// by making a Stream an Environment (the current) is attached + +( +q = p.asStream; +~a = 1; +q.next; +) + +-> 1 + +// next value of (Func)Stream demanded in new Environment will be taken from old defining context + +( +~a = 2; +e = Environment[\a -> 5]; +e.use { q.next }; +) + +-> 2 + + +// vice versa Stream defined in new Environment takes next value from there +// also if evaluated in other Environment + +( +r = e.use { p.asStream }; +r.next; +) + + +-> 5 + +:: + +Indeed, Streams derived from the simple Pfunc don't do much here, they just reflect values in the Environment in which they are defined. As with Functions in the first example Pfuncs can be combined with other Patterns to modify their behaviour. So a family of related Streams (common attributes) can be polled from a single pattern. + + +code:: + +// try above with new pattern +// here the common attributes are: period length = 2, magnitude relation of items = 1:10) + +p = Pseq([1,10], inf) * Pfunc { ~a }; + +:: + +An even more generalized operation is the use of Pcollect, which defines the following Stream behaviour: Output of the source stream will be taken as input by the collecting stream, as with Pfunc the Function defined in Pcollect will live in the Environment in which the Stream has been polled from the Pattern. The above could also be written like this: + +code:: + +p = Pcollect({ |x| x * ~a }, Pseq([1,10], inf)); + +p = Pseq([1,10], inf).collect({ |x| x * ~a }); + +p = Pseq([1,10], inf).collect(_*~a); // short form (partial application), don't overrely on it in more complicated cases + +:: + +There is another Pattern engaging Functions, thus allowing to benefit from environment-dependance: Plazy. it evaluates a Function that returns a Pattern to be embedded in a stream. For repeated embedding the Plazy can be wrapped into a Pn. If a generated Pattern repeats infinitely, there will not be any further evaluation. So when using Plazy have a close look at the inner Pattern's repeat arg. + +code:: + +// with each evaluation of Plazy's Function a new random sequence of length = 2 will be generated and repeated 3 times +// maximum range of the whole sequence is controlled by the environmental variable ~a + +( +p = Pn(Plazy { Pseq({ rand2(~a) } ! 2, 3) }); +q = p.asStream; +~a = 3; +q.nextN(60); +) + +// instead of using an Environment we can use an instance of its subclass Event which has handy syntax. +// new empty Event: + +(); + +// new Event with var ~a set, range enlarged: + +( +e = (a: 10); +r = e.use { p.asStream }; +r.nextN(60); +) + +// compare, both streams are demanded values in same Environment, but they are attached to different ones + +q.nextN(60); + +:: + +This example was still similar to the ones before, basically a loop modified by a factor. But imagine that a Plazy could give out arbitrary new Patterns, the choice itself could depend on an environment-dependant parameter. + + +SECTION::Event streams in Environments + + +One step closer to the sound: let's define an event pattern with single patterns that contain functional code. Again the pattern itself is neutral concerning the relation to Environments ... + + +code:: + +( +p = Pbind( + \dur, Pfunc { ~a }, + \midinote, Pfunc { ~b } +); +) + +// ... but the stream is not neutral. It's bound to the current environment. + +( +q = p.asStream; + +~a = 0.5; +~b = 60; +) + +// define a new Environment - an Event, as syntax is handy. + +e = (a: 1, b: 70); + +// So we have two surrounding Events as variable spaces, in which Streams of Events can live. +// Don't be confused by that, the stream-generated Events are a different story. + +// now check a next element of the event stream in the new environment (event). +// The method next must itself be passed an event - +// the result stems from the former current event, in which the event stream was generated + +e.use { q.next(()) }; + +-> ( 'dur': 0.5, 'midinote': 60 ) + +// double check - also the second event stream belongs to its defining context + +( +e.use { r = p.asStream; }; +r.next(()); +) + +-> ( 'dur': 1, 'midinote': 70 ) + +:: + +SECTION::EventStreamPlayers in Environments + +Not much will change by the last transition, EventStreamPlayers are also attached to environments. + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +p = Pbind( + \dur, Pfunc { ~a }, + \midinote, Pfunc { ~b } +); +) + +// define two Environments (Events) with EventStreamPlayers attached to it + +( +e = (a: 0.2, b: 60); +f = (a: 0.4, b: 67); + +e.use { q = p.asEventStreamPlayer }; +f.use { r = p.asEventStreamPlayer }; +) + + +// play the players with different init data in sync + +( +q.play(quant: 0.2); +r.play(quant: 0.2); +) + + +// now change envir variables while playing +// the stream will take new values at the next evaluation + +f.b = 65; + +e.b = 62; + +( +q.stop; +r.stop; +) + +:: + +The VarGui interface can be used to control running EventStreamPlayers in that way. They may be passed directly, then their attached Environments will be taken for variable setting: + + +code:: + +// start playing from gui + +( +v = VarGui([[ + \a, [0.2, 0.8, \lin, 0.2, 0.2], + \b, [48, 80, \lin, 1, 58] + ],[ + \a, [0.2, 0.8, \lin, 0.2, 0.4], + \b, [48, 80, \lin, 1, 67] + ]], stream: [q, r], quant: 0.2 +).gui; +) + +// so you could still set from outside + +f.b = 65; + +:: + +But also the event pattern can be passed directly (recommended). Then new separate Environments will be generated automatically, +changing envir variables by accident is very unlikely. + +code:: + +( +v = VarGui({ [ + \a, [0.2, 0.8, \lin, 0.2, 0.2 * (4.rand + 1) ], + \b, [48, 80, \lin, 1, 48.rrand(80) ] + ] } ! 10, stream: p ! 10, quant: 0.2 +).gui(tryColumnNum: 2); +) + +// however, environments are accessible if necessary + +v.envirs; + +:: diff --git a/HelpSource/Tutorials/Event_patterns_and_LFOs.schelp b/HelpSource/Tutorials/Event_patterns_and_LFOs.schelp new file mode 100644 index 0000000..52f84db --- /dev/null +++ b/HelpSource/Tutorials/Event_patterns_and_LFOs.schelp @@ -0,0 +1,572 @@ + + +TITLE::Event patterns and LFOs +summary::summary of some LFO control strategies for event patterns +categories:: Libraries>miSCellaneous>General Tutorials, Streams-Patterns-Events +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Guides/Guide_to_HS_and_HSpar, Classes/VarGui, Tutorials/VarGui_shortcut_builds, Tutorials/HS_with_VarGui + + +DESCRIPTION:: + +Basic distinction: synths generated by an event pattern can be controlled directly by a LFO (synths wired or mapped to control buses) or on a per-event base. The latter can be achieved by language-only strategies or with help of control synths. For the sake of clarity most examples use the default instrument and pitch as control parameter. + + + +SECTION::1. Control by new values per event + +anchor::Ex.1a:: +subsection::Ex.1a: Functions of time + +code:: + + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +( +// LFO defined as Function + +f = { |x| (x * 2).sin * 3 + (x * 0.3).sin }; + +p = Pbind( + \dev, Ptime().collect(f), // current time passed to function + \midinote, Pkey(\dev) + 60 + [0, 4], + \amp, 0.05, + \dur, 0.15 +).play; +) + +p.stop; + + +////////////////////////// + +// example with GUI + + +( +// parametric control function + +f = { |x| (x * ~a[0]).sin * ~b[0] + ((x * ~a[1]).sin * ~b[1]) }; + +p = Pbind( + \dev, Ptime().collect(f), + \midinote, Pkey(\dev) + 60 + [0, 4], + \amp, 0.05, + \dur, 0.15 +); + +v = VarGui([ + \a, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2, + \b, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2], + stream: p +).gui +) + +// add a hook that re-plots control function after every slider change + +( +u = Plotter(bounds: Rect(200, 200, 700, 500)); + +// evaluation points + +x = (0, 0.1..20); + +// EventStreamPlayer is run in separate Environment, +// function values have to be polled from there, +// evaluate function initially and add it as mouseUp slider action + +g = { u.value_(f.inEnvir(v.envirs.first).(x)).refresh }; + +g.(); + +v.addSliderAction(g); +) + + + +////////////////////////// + +// GUI example with two LFOs + + +( +// parametric control function + +f = { |x| (x * ~a[0]).sin * ~b[0] + ((x * ~a[1]).sin * ~b[1]) }; + +p = Pbind( + \dev, Ptime().collect(f), + \midinote, Pkey(\dev) + Pfunc { ~add }, + \amp, 0.05, + \dur, 0.15 +); + +// add some harmonies + +q = Padd(\midinote, [-12, -7, 0, 5], p); +r = Padd(\midinote, [0, 2.5], p); + +v = VarGui([70, 55].collect { |x| [ + \a, { [0, 5, \lin, 0.01, rrand(0.0, 5)] } ! 2, + \b, { [0, 5, \lin, 0.01, rrand(0.0, 5)] } ! 2, + \add, [x-2, x+2, \lin, 0.01, x] + ] }, stream: [r,q], quant: 0.15 +).gui +) + +// add a hook that re-plots control function after every slider change + +( +u = Plotter(bounds: Rect(200, 200, 700, 500)); + +// evaluation points + +x = (0, 0.1..20); + +// EventStreamPlayer is run in separate Environment, +// function values have to be polled from there, +// evaluate function initially and add it as mouseUp slider action + +g = { u.value_(v.envirs.collect { |e| f.inEnvir(e).(x) }).refresh }; + + +g.(); + +v.addSliderAction(g); +) + +:: + + +anchor::Ex.1b:: +subsection::Ex.1b: Envelopes + +code:: + +( +e = Env([0, 10, 0], [2,1], \sin); +e.plot; +) + +// currently (SC 3.4.4) this will play the envelope once +// and then continue with the end value + +( +p = Pbind( + \dev, e, + \midinote, Pkey(\dev) + 60 + [0, 4], + \amp, 0.05, + \dur, 0.15 +).play; +) + +p.stop; + + +// you can use modulo calculus for looping + +( +p = Pbind( + \dev, Ptime().collect { |t| e[t % (e.times.sum)] }, + \midinote, Pkey(\dev) + 60 + [0, 4], + \amp, 0.05, + \dur, 0.15 +).play; +) + +p.stop; + + +////////////////////////// + +// example with GUI + +( +n = 5; + +p = Pbind( + \dev, Ptime().collect { |t| e[t % (e.times.sum)] }, + \midinote, Pkey(\dev) + 60 + [0, 4], + \amp, 0.05, + \dur, 0.15 +); + +v = VarGui([ + \levels, { [-15, 15, \lin, 0.01, rrand(-15.0, 15)] } ! n, + \times, { [0.5, 3, \lin, 0.01, rrand(0.5, 3)] } ! (n-1), + \curves, { [0, 7, \lin, 1, rrand(0, 7)] } ! (n-1), + \stretch, [0.1, 10, \lin, 0.01, 1]], + stream: p +).gui; +) + +// add a hook that plots envelope after every slider change + +( +u = Plotter(bounds: Rect(200, 200, 700, 500)); + +g = { + v.envirs.first.use { e = Env(~levels, ~times * ~stretch, ~curves) }; + u.value_(e.asSignal).refresh; +}; + +g.(); + +v.addSliderAction(g); +) + +:: + + + +anchor::Ex.1c:: +subsection::Ex.1c: SharedOut / shared memory + +SharedOut will be deprecated in future releases of SC and replaced by a shared memory mechanism (Tim Blechmann). +SharedOut is at least included in version 3.4.4. + +code:: + +// internal server needed for all examples with SharedOut, +// shared memory also works with local server + +( +s = Server.internal; +Server.default = s; +s.boot; +) + +// start LFO + +x = { SharedOut.kr(0, LFDNoise3.kr(0.3, 20, 70)) }.play; + + +// play event pattern + +( +p = Pbind( + \dur, 0.15, + \midinote, Pfunc { s.getSharedControl(0) } +).play; +) + +( +p.stop; +x.free; +) + +////////////////////////// + +// example with GUI + +// ATTENTION: this version of the example works with SC versions > 3.4.4 +// in which SharedOut is still supported and which already include +// the fix of a minor bug which blocked adding of SynthDefs. +// See below for an equivalent example with shared memory. + +// If you're using a version <= 3.4.4 you can fix it by yourself, +// adding this method to SharedOut and recompile: +// *numFixedArgs { ^1 } + +// ... or take the example version below the following version + +( +s = Server.internal; +Server.default = s; +s.boot; +) + +( +SynthDef(\control, { |midiCenter, dev, devFreq| SharedOut.kr(0, LFDNoise3.kr(devFreq, dev, midiCenter)) }).add; + +p = Pbind( + \dur, 0.15, + \midinote, Pfunc { s.getSharedControl(0) } + [0, 4] +); + +// start control synth before stream + +v = VarGui(synthCtr: [ + \midiCenter, [50, 80, \lin, 0.01, 70], + \dev, [0, 20, \lin, 0.01, 20], + \devFreq, [0, 3, \lin, 0.01, 0.5]], + synth: \control, stream: p +).gui(playerPriority: \synth); +) + + +// this version of the example works also with SC versions <= 3.4.4 + +( +SynthDef(\control, { |midiCenter, dev, devFreq| SharedOut.kr(0, LFDNoise3.kr(devFreq, dev, midiCenter)) }).send(s); +) + +( +x = Synth(\control).register; + +// or start paused: +// x = Synth.newPaused(\control).register; + +p = Pbind( + \dur, 0.15, + \midinote, Pfunc { s.getSharedControl(0) } + [0, 4] +); +) + +( +v = VarGui(synthCtr: [ + \midiCenter, [50, 80, \lin, 0.01, 70], + \dev, [0, 20, \lin, 0.01, 20], + \devFreq, [0, 3, \lin, 0.01, 0.5]], + synth: x, stream: p +).gui(playerPriority: \synth); +) + + +// shared memory example, SC version >= 3.5 +// start LFO + +c = Bus.control(s, 1); + +x = { Out.kr(c, LFDNoise3.kr(0.3, 20, 70)) }.play; + + +// play event pattern + +( +p = Pbind( + \dur, 0.15, + \midinote, Pfunc { c.getSynchronous } +).play; +) + +( +p.stop; +x.free; +) + +:: + + +anchor::Ex.1d:: +subsection::Ex.1d: HS / PHS and related + +With link::Classes/HS:: server values can be used in link::Classes/PHS:: objects which mimic event patterns. This is achieved by an OSC demand and respond mechanism which introduces a small amount of additional latency. It works with local and internal server, see link::Guides/Guide_to_HS_and_HSpar:: for further details. Using the HS family with VarGui is discussed in link::Tutorials/HS_with_VarGui::. + +The HS / PHS approach would especially be of interest if control behaviour could more easily be defined by server means than in SC lang (e.g. specific and / or nested UGens) but data should also be further manipulated in the language (e.g. for some kind of combinatorial use such as harmonic or polyphonic calculations). + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// a HS contains the control synth definition but will also hold playing Synth instances + +h = HS(s, { |midiCenter = 70, dev = 20, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }); + + +// a PHS refers to a HS and, when played, takes control over the control synth + +p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) + [0, 4] ]).play; + + +// stop player and control synth + +p.free; + +:: + + +Methods of linked playing / stopping and resuming (stream + help synth) are supported as well as reference to an already playing link::Classes/HS:: by link::Classes/PHSuse::. Various kinds of control synth control and synth value reference are possible with two or more help synths (see link::Classes/HSpar::, link::Classes/PHSpar:: and link::Classes/PHSparUse::). + + +anchor::Ex.1e:: +subsection::Ex.1e: audio synths reading from a control bus, discretized + +Derived from (2a), disadvantage: SynthDef must be adapted to control needs beforehand. + +code:: + +( +c = Bus.control(s,1); +d = Bus.control(s,1); + +SynthDef(\perc_1e, {|amp = 0.1, bus, att = 0.01, rel = 1| + var in = In.kr(bus, 1); + Out.ar(0, (SinOsc.ar(Latch.kr(in, in).midicps, 0, amp) * + EnvGen.ar(Env.perc(att, rel), doneAction: 2))!2) +}).add; +) + +( +x = { Out.kr(c, LFDNoise3.kr(1, 5, 75)) }.play; +y = { Out.kr(d, LFDNoise3.kr(1, 5, 65)) }.play; + +p = Pbind( + \instrument, \perc_1e, + \dur, 0.3, + \amp, 0.07, + \bus, [c, d] +).play; +) + +( +p.stop; +x.free; +y.free; +) + +:: + + +anchor::Ex.1f:: +subsection::Ex.1f: Pseg + +Pseg can work like a kind of meta pattern for LFO-like control, patterns are used to pass envelope data. + +code:: + +( +p = Pbind( +    \note, Pseg(Pseq([0, Pwhite(3, 10, 1)], inf), Pseq([1, 3],inf), 'lin'), +    \dur, 0.2 +).play; +) + +p.stop; + +:: + + + +SECTION::2. Continuous LFO control + +anchor::Ex.2a:: +subsection::Ex.2a: audio synths reading from a control bus + +The disadvantage of this strategy (compared to 2b) is that synth definitions have to be written especially for control purposes. It must be known in advance which parameters should be controlled by another synth. + +code:: + +( +c = Bus.control(s,1); + +SynthDef(\perc_2a, {|amp = 0.1, bus = 0, att = 0.01, rel = 0.25| + Out.ar(0, (SinOsc.ar(In.kr(bus, 1).midicps, 0, amp) * + EnvGen.ar(Env.perc(att, rel), doneAction: 2))!2) +}).add; +) + +( +x = { Out.kr(c, LFDNoise3.kr(3, 10, 65)) }.play; + +p = Pbind( + \instrument, \perc_2a, + \dur, 0.3, + \bus, c +).play; +) + +( +p.stop; +x.free; +) + +:: + + + +anchor::Ex.2b:: +subsection::Ex.2b: audio synths mapped to a control bus + +In general more practical than (2a), though by SC vs 3.4.4 reserved keys (e.g. \freq) can't be mapped to a bus, under these circumstances args would have to be renamed. + +code:: + +( +c = Bus.control(s,1); +d = Bus.control(s,1); + +SynthDef(\perc_2b, {|amp = 0.1, midi = 60, att = 0.01, rel = 0.25| + Out.ar(0, (SinOsc.ar(midi.midicps, 0, amp) * + EnvGen.ar(Env.perc(att, rel), doneAction: 2))!2) +}).add; +) + +( +x = { Out.kr(c, LFDNoise3.kr(1, 5, 75)) }.play; +y = { Out.kr(d, LFDNoise3.kr(1, 5, 65)) }.play; + +p = Pbind( + \instrument, \perc_2b, + \dur, 0.3, + \midi, [c, d].collect(_.asMap) +).play; +) + +( +p.stop; +x.free; +y.free; +) + + +////////////////////////// + +// example with GUI + + +( +c = Bus.control(s,1); +d = Bus.control(s,1); + +SynthDef(\perc_2b, {|amp = 0.1, midi = 60, att = 0.01, rel = 0.25| + Out.ar(0, (SinOsc.ar(midi.midicps, 0, amp) * + EnvGen.ar(Env.perc(att, rel), doneAction: 2))!2) +}).add; + +SynthDef(\control_2b, { |midiCenter = 70, dev = 20, devFreq = 1, out = 0| + Out.kr(out, LFDNoise3.kr(devFreq, dev, midiCenter)) +}).add; +) + +( +p = Pbind( + \instrument, \perc_2b, + \dur, Pfunc { ~dur }, + // following values will be collections of two elements + \amp, Pfunc { ~amp }, + \att, Pfunc { ~att }, + \rel, Pfunc { ~rel }, + \midi, [c, d].collect(_.asMap) +); + +// in gui start control synths before stream player ! + +v = VarGui([ + \dur, [0.05, 0.5, \lin, 0.005, 0.2], + // setting envir variables to collections of two elements + \amp, [0, 0.1, \lin, 0.005, 0.07] ! 2, + \att, [0.005, 0.1, \lin, 0.005, 0.01] ! 2, + \rel, [0.005, 0.5, \lin, 0.005, 0.1] ! 2 + ], + 2.collect { |i| + var bus = [c,d][i].index; + [\midiCenter, [60, 80, \lin, 0.01, [65, 75][i] ], + \dev, [0, 10, \lin, 0.01, 10], + \devFreq, [0, 3, \lin, 0.01, 0.5], + \out, [bus, bus, \lin, 1, bus]] + }, p, \control_2b ! 2 +).gui(sliderPriority: \synth, playerPriority: \synth); +) + +:: + diff --git a/HelpSource/Tutorials/Event_patterns_and_array_args.schelp b/HelpSource/Tutorials/Event_patterns_and_array_args.schelp new file mode 100644 index 0000000..e6f47ac --- /dev/null +++ b/HelpSource/Tutorials/Event_patterns_and_array_args.schelp @@ -0,0 +1,678 @@ + + +TITLE::Event patterns and array args +summary::setting and passing arrays and envelopes via patterns +categories:: Libraries>miSCellaneous>General Tutorials, Streams-Patterns-Events +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous + + +DESCRIPTION:: + +This tutorial covers some use cases of array args, especially passing arrays to synths with patterns. + + +anchor::Ex.1:: +SECTION::Ex.1: Alternative writing of arrayed args in SynthDefs + + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// SynthDef with array of overtone weights, +// weights can additionally be influenced with a dampExp arg unequal to 0, +// see examples below. + +( +// The standard way to define array args: +// when appearing within the list of args, a literal Array must be used, +// shortcut (1..8) generates an Array with Integers from 1 to 8 + +SynthDef(\array_1a, { |out = 0, freq = 440, otAmps = #[1,1,1,1,1,1,1,1], dampExp = 0, + att = 0.01, rel = 0.6, amp = 0.1, gate = 1, freqLag = 0.02, otLag = 0.02| + var sig, env, freqs, amps; + freqs = (freq * (1..8)).clip(20, 20000).lag(freqLag); + amps = ((otAmps / ((1..8) ** dampExp)).normalizeSum * amp).lag(otLag); + sig = SinOsc.ar(freqs, 0, amps); + env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2); + Out.ar(out, Splay.ar(sig) * env) +}).add +) + +// Note that SynthDef \array_1a uses the array arg's size (8) at two further places (arrays (1..8)). +// As a literal Array, by its nature, requires the explicit writing of its items, +// this becomes arkward with larger arrays and/or rewriting the SynthDef with other sizes. +// For those reasons the use of NamedControl turns out to be very convenient with array args, +// it also gives an easy way of lagging. + + +( +// Alternative to SynthDef \array_1a using NamedControl: +// Although a NamedControl can be written at any position within the SynthDef, +// it's a useful convention to invent it directly after the args list +// by assigning it to a variable of the same name (so code with literal Array args can be reused easily). +// With NamedControl the array size can now also be written as a variable, +// which allows to define SynthDefs with different array sizes on the fly. +// Also note NamedControl's shortcuts aSymbol.kr / aSymbol.kr +// which make its use even more comfortable. + +// define size for SynthDef +n = 8; + +// define SynthDef +// shortcut 1!n (short for 1.dup(n)) generates an Array of size n filled with 1. + +SynthDef(\array_1b, { |out = 0, freq = 440, dampExp = 0, + att = 0.01, rel = 0.6, amp = 0.1, gate = 1, freqLag = 0.02, otLag = 0.02| + var otAmps = NamedControl.kr(\otAmps, 1!n); // shortcut: otAmps = \otAmps.kr(1!n); + var sig, env, freqs, amps; + freqs = (freq * (1..n)).clip(20, 20000).lag(freqLag); + amps = ((otAmps / ((1..n) ** dampExp)).normalizeSum * amp).lag(otLag); + sig = SinOsc.ar(freqs, 0, amps); + env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2); + Out.ar(out, Splay.ar(sig) * env) +}).add +) + + +// As array sizes of SynthDefs are fixed you might want to define for a number +// of Integers and name the SynthDefs appropriately. +// Here's such a "SynthDef factory": +// we get SynthDefs of names \array_1b_1, ..., \array_1b_16 with corresponding array sizes, +// see Ex.3b for a use case. + +( +(1..16).do { |n| + var name = \array_1b ++ \_ ++ n; + SynthDef(name, { |out = 0, freq = 440, dampExp = 0, + att = 0.01, rel = 0.6, amp = 0.1, gate = 1, freqLag = 0.02, otLag = 0.02| + var otAmps = NamedControl.kr(\otAmps, 1!n); // shortcut: otAmps = \otAmps.kr(1!n); + var sig, env, freqs, amps; + freqs = (freq * (1..n)).clip(20, 20000).lag(freqLag); + amps = ((otAmps / ((1..n) ** dampExp)).normalizeSum * amp).lag(otLag); + sig = SinOsc.ar(freqs, 0, amps); + env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2); + Out.ar(out, Splay.ar(sig) * env) + }).add +} +) + +:: + + +SECTION::2. Setting array args and array fields of a running synth + + +anchor::Ex.2a:: +subsection::Ex.2a: Setting array args of a running synth + +code:: + +// start Synth, SynthDefs \array_1a, \array_1b and \array_1b_8 are equivalent + +x = Synth(\array_1a, [freq: 300, amp: 0.2]) + + +// only odd partials (clarinet-like) + +x.set(\otAmps, [1, 0, 1, 0, 1, 0, 1, 0]) + + +// emphasize one partial + +x.set(\otAmps, [1, 0, 1, 0, 1, 5, 1, 0]) + + +// only lower ones + +x.set(\otAmps, [1, 1, 1, 1, 0, 0, 0, 0]) + + +// default again + +x.set(\otAmps, [1, 1, 1, 1, 1, 1, 1, 1]) + + + +// force lower partials with dampExp > 0 + +x.set(\dampExp, 1.5) + + +// force higher partials with dampExp < 0 + +x.set(\dampExp, -1.5) + + +// default again + +x.set(\dampExp, 0) + + +// release + +x.release + +:: + + + +anchor::Ex.2b:: +subsection::Ex.2b: Setting array args of a running synth with Pbind of event type \set + + +For many use cases Pmono (see link::#Ex.2c::) is the most practical solution as it doesn't require explicit starting of a synth. However sometimes it is necessary to access the running synth itself, then a Pbind with event type \set is a good choice. + + +code:: + + +// start Synth silently + +x = Synth(\array_1a, [freq: 200, amp: 0]) + + +// Pbind of event type set: +// args to be set must be listed. +// Note that the array arg requires double brackets (syntactic distinction from setting multiple nodes) + +( +p = Pbind( + \dur, 0.2, + \type, \set, + \id, x, + \args, #[freq, amp, dampExp, otAmps], // must be given explicitely + \midinote, Pwhite(45.0, 70), // for midinote freq must be set + \amp, 0.3, + \dampExp, Pwhite(0, 3), + \otAmps, [[2, 1, 3, 2, 1, 2, 2, 1]] // double brackets for array arg ! +).play +) + +( +// stop EventStreamPlayer and release Synth + +p.stop; +x.release; +) + + + +// start Synth silently + +x = Synth(\array_1a, [freq: 200, amp: 0]); + + +// Sequencing the array arg: +// rising spectral shape, falling fundamental + +( +// Array for Pseq, we need an array of doubly bracketed arrays or +// ref'd arrays as in Ex. 2c. + +o = [ + [[1, 1, 0, 0, 0, 0, 0, 0]], + [[1, 0, 1, 0, 0, 0, 0, 0]], + [[1, 1, 0, 1, 0, 0, 0, 0]], + [[1, 0, 1, 0, 1, 0, 0, 0]], + [[1, 1, 0, 1, 0, 1, 0, 0]], + [[1, 0, 1, 0, 1, 0, 1, 0]], + [[1, 0, 0, 1, 0, 1, 0, 1]] +]; + +p = Pbind( + \dur, 0.2, + \type, \set, + \id, x, + \args, #[freq, amp, otAmps], // must be given explicitely + \midinote, Pn(Plazy { Pseq((70..50)) / rrand(1, 1.2) }), + \amp, 0.3, + \otAmps, Pseq(o, 30) +).play +) + +( +// stop EventStreamPlayer and release Synth + +p.stop; +x.release; +) + + + + +:: + + +anchor::Ex.2c:: +subsection::Ex.2c: Setting array args of a running synth with Pmono + + +Rewriting second example of 2b, shorter with Pmono. Instead of doubly bracketed arrays you can choose Refs of Arrays also. Note that this alternative is not valid for a single nested Array as in the first example of link::#Ex.2b:: . + +code:: + +( +o = [ + `[1, 1, 0, 0, 0, 0, 0, 0], + `[1, 0, 1, 0, 0, 0, 0, 0], + `[1, 1, 0, 1, 0, 0, 0, 0], + `[1, 0, 1, 0, 1, 0, 0, 0], + `[1, 1, 0, 1, 0, 1, 0, 0], + `[1, 0, 1, 0, 1, 0, 1, 0], + `[1, 0, 0, 1, 0, 1, 0, 1] +]; + +p = Pmono(\array_1a, + \dur, 0.2, + \midinote, Pn(Plazy { Pseq((70..50)) / rrand(1, 1.2) }), + \amp, 0.3, + \otAmps, Pseq(o, 30) +).play +) + + +// stop EventStreamPlayer from Pmono (synth is released also) + +p.stop; + +:: + + +anchor::Ex.2d:: +subsection::Ex.2d: Setting i-th fields of a running synth + + +code:: + +// start Synth + +x = Synth(\array_1a, [freq: 200, amp: 0.3]) + + +// by default otAmps equals [1, 1, 1, 1, 1, 1, 1, 1] + +// Since SC 3.6.x there exists method seti for setting single elements of arrays: +// turn off high overtones + +x.seti(\otAmps, 7, 0) + +x.seti(\otAmps, 6, 0) + +x.seti(\otAmps, 5, 0) + + +x.release; + + +// With older SC versions you can use the following helper functions to achieve the same. +// This requires that the SynthDef has been added in order to have control arg info in SynthDescLib. + +( +~getCtlIndex = { |defName, argName, index| +    var x = SynthDescLib.global.at(defName.asSymbol).controls +        .collect({|x| x.name.asSymbol }).indexOf(argName.asSymbol); +    x !? { x + index }; +}; + +~setiID = { |server, defname, nodeID, key, index, value| +    server.sendMsg(15, nodeID, ~getCtlIndex.(defname, key, index), value); +}; + + +~seti = { |node, key, index, value| +    ~setiID.(node.server, node.defName, node.nodeID, key, index, value); +}; +) + + +// start Synth + +x = Synth(\array_1a, [freq: 200, amp: 0.5]) + + +// turn off high overtones + +~seti.(x, \otAmps, 7, 0) + +~seti.(x, \otAmps, 6, 0) + +~seti.(x, \otAmps, 5, 0) + +x.release; + + +:: + + + +anchor::Ex.2e:: +subsection::Ex.2e: Setting i-th fields of a running synth with patterns + + +For SC versions before invention of method seti use helper functions from link::#Ex.2d:: in Pbind. + +code:: + +// start Synth + +x = Synth(\array_1a, [freq: 200, amp: 0.3]) + + +// sequence setting of single fields +// As this is currently not integrated with Pmono or event type \set +// it must be done explicitely. Method 'makeBundle' with server latency arg +// ensures that array field setting is done in parallel to +// other settings triggered by the event. + +( +// continously subtract and add 7 overtones + +p = Pbind( + \type, \rest, + \dur, 0.2, + \otAmp, Pstutter(7, Pseq([0,1], inf)), + \amp, 0.3, + \i, Pn(Pshuf((1..7))), +// use Function ~seti (2d) for SC versions without method seti: + \do, Pfunc { |e| s.makeBundle(s.latency, { ~seti.(x, \otAmps, e.i, e.otAmp) }) } +// for newer SC versions with method seti you can use this line instead: +// \do, Pfunc { |e| s.makeBundle(s.latency, { x.seti(\otAmps, e.i, e.otAmp) }) } + ).trace.play +) + +// stop player and synth + +( +p.stop; +x.release; +) + + +// This approach can be extended by setting more than one field per event. +// In some cases this might be a reasonable alternative to setting whole arrays +// of running synths (2b, 2c), which requires more OSC traffic. + +// start Synth from Ex. 1 with 16 overtones + +x = Synth(\array_1b_16, [freq: 100, amp: 0.3]) + +( +// lists for bookkeeping of substracted and added overtones +a = (1..16).asList; +b = List[]; + +// Function to shovel indices from list to list + +f = { |list1, list2| + var x = list1.choose; + list1.remove(x); + list2.add(x); + x +}; + +// continously subtract and add 5 x 3 overtones + +p = Pbind( + \type, \rest, + \dur, 0.2, + \otAmp, Pstutter(5, Pseq([0,1], inf)), + \amp, 0.3, + // shoveling indices from a to b and back, outputting sorted index tripels + \i, Pseq([ + Pfinval(5, Pclump(3, Pfunc { f.(a, b) } )), + Pfinval(5, Pclump(3, Pfunc { f.(b, a) } )) + ], inf).collect(_.sort), +// use Function ~seti (2d) for SC versions without method seti: + \do, Pfunc { |e| s.makeBundle(s.latency, { e.i.do { |j| ~seti.(x, \otAmps, j, e.otAmp) } }) } +// for newer SC versions with method seti you can use this line instead: +// \do, Pfunc { |e| s.makeBundle(s.latency, { e.i.do { |j| x.seti(\otAmps, j, e.otAmp) } }) } +).trace.play +) + +// stop player and synth + +( +p.stop; +x.release; +) + +:: + + +anchor::Ex.2f:: +subsection::Ex.2f: Alternatives with demand ugens + + + +Besides from passing arrays, array sequencing can be done within synths by demand ugens. This is saving OSC bandwidth, especially with large arrays and short durations, for more complicated sequencing tasks coding might be harder than with patterns. + + +code:: + +( +// array sequencing within SynthDef + +q = [ + [1, 1, 0, 0, 0, 0, 0, 0], + [1, 0, 1, 0, 0, 0, 0, 0], + [1, 1, 0, 1, 0, 0, 0, 0], + [1, 0, 1, 0, 1, 0, 0, 0], + [1, 1, 0, 1, 0, 1, 0, 0], + [1, 0, 1, 0, 1, 0, 1, 0], + [1, 0, 0, 1, 0, 1, 0, 1] +]; + +// duration will have to be passed with a key unequal to reserved keyword \dur + +SynthDef(\array_1c, { |out = 0, freq = 440, dampExp = 0, duration = 0.2, + att = 0.01, rel = 0.6, amp = 0.1, gate = 1, freqLag = 0.02, otLag = 0.02| + var otAmps, sig, env, freqs, amps, arr; + otAmps = Demand.kr(Impulse.kr(1 / duration), 0, Dseq(q, inf)); + freqs = (freq * (1..8)).clip(20, 20000).lag(freqLag); + amps = ((otAmps / ((1..8) ** dampExp)).normalizeSum * amp).lag(otLag); + sig = SinOsc.ar(freqs, 0, amps); + env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2); + Out.ar(out, Splay.ar(sig) * env) +}).add; + +// SynthDef for complete server-side sequencing +// takes midiseq as array arg + +SynthDef(\array_1d, { |out = 0, dampExp = 0, duration = 0.2, divLo = 1, divHi = 1.2, + att = 0.01, rel = 0.6, amp = 0.3, gate = 1, freqLag = 0.02, otLag = 0.02| + var midiseq = \midiseq.kr((70..50)); + var trig, otAmps, sig, env, freq, freqs, amps, arr; + trig = Impulse.kr(1 / duration); + otAmps = Demand.kr(trig, 0, Dseq(q, inf)); + freq = Demand.kr(trig, 0, Dseq(midiseq, inf) / + Dstutter(midiseq.size, Dwhite(divLo, divHi))).midicps; + freqs = (freq * (1..8)).clip(20, 20000).lag(freqLag); + amps = ((otAmps / ((1..8) ** dampExp)).normalizeSum * amp).lag(otLag); + sig = SinOsc.ar(freqs, 0, amps); + env = EnvGen.ar(Env.asr(att, 1, rel), gate, doneAction: 2); + Out.ar(out, Splay.ar(sig) * env) +}).add +) + +( +// equivalent to Ex 2c +// still using a pattern for non-array sequencing + +p = Pmono(\array_1c, + \dur, 0.2, + \duration, Pkey(\dur), + \midinote, Pn(Plazy { Pseq((70..50)) / rrand(1, 1.2) }), + \amp, 0.3 +).play +) + +p.stop; + + +// equivalent, now all done by synth + +x = Synth(\array_1d) + + +// set midi sequence + +x.set(\midiseq, (50..70)) + +x.set(\midiseq, (70..50)) + +x.set(\midiseq, (70..50).scramble) + +x.release + + +:: + + +SECTION::3. Sequencing synths with array args by Pbind + + +anchor::Ex.3a:: +subsection::Ex.3a: Sequencing synths of same definition with array args by Pbind + + +code:: + +// Using a Pbind with array args is straightforward, +// it's just important to remember that arrays must be in +// double brackets (or wrapped into Refs). +// Written explicitely it looks a bit odd as you end up with +// three brackets at begin and end: +// Pseq([[[1, 1, 0, 0, 0, 0, 0, 0]], ... , [[1, 0, 0, 1, 0, 1, 0, 1]]], 100) + + +( +// Array o from definitions in Ex. 2b / 2c. + +o = [ + [[1, 1, 0, 0, 0, 0, 0, 0]], + [[1, 0, 1, 0, 0, 0, 0, 0]], + [[1, 1, 0, 1, 0, 0, 0, 0]], + [[1, 0, 1, 0, 1, 0, 0, 0]], + [[1, 1, 0, 1, 0, 1, 0, 0]], + [[1, 0, 1, 0, 1, 0, 1, 0]], + [[1, 0, 0, 1, 0, 1, 0, 1]] +]; + +// generating multiple nodes per event is also no problem, +// arrays from o are sent to both nodes here: + +p = Pbind( + \instrument, \array_1a, + \dur, 0.2, + \amp, 0.3, + \stepsPerOctave, 5, + \octave, 4, + \note, Pstutter(7, Pn(Pshuf((0..4)))) + [0, -4], + \otAmps, Pseq(o, 100) +).trace.play +) + +p.stop; + +:: + + +anchor::Ex.3b:: +subsection::Ex.3b: Sequencing synths with different array arg sizes by Pbind + + +By using the Array o in the last examples we did a kind of zeropadding: setting unused elements to zero. When altering one running synth (as with event type \set or Pmono) one can only save OSC messages if less than all fields are changed, see link::#Ex. 2e:: . When continuously generating new synths – as with 'normal' Pbind of type \note – there is another alternative: using SynthDefs of dedicated array arg sizes per event. This is the use case of a "SynthDef factory" as described in link::#Ex.1:: . + + + +code:: + + +( +// needs SynthDefs from "factory" in Ex. 1 + +q = [ + [[1, 1, 1]], + [[1, 1, 1, 1, 1]], + [[1, 0, 1, 0, 1, 0, 1, 0, 1]], + [[1, 1, 0, 1, 0, 1, 0, 1, 0, 1]] +]; + +p = Pbind( + \otAmps, Pn(Pshuf(q), 100), + \size, Pkey(\otAmps).collect { |x| x[0].size.asSymbol }, + \instrument, Pkey(\size).collect { |x| \array_1b_ ++ x }, // SynthDef depends on chosen otAmp array + \dur, 0.2, + \amp, 0.3, + \stepsPerOctave, 7, + \octave, 4, + \note, Pstutter(3, Pn(Pshuf((0..4)))) + Pn(Pshuf([[0, 2], [0, 4, 8], [0, -3, -5, -8]])) +).trace.play +) + +p.stop; + +:: + +anchor::Ex.4:: +SECTION::Ex.4 Sequencing synths with envelope array args by Pbind + +Env objects have a representation in a special Array format – which you can get with anEnv.asArray – the task of passing Env data to synths can thus be reduced to the task of passing Arrays in this special format. Nevertheless direct passing of Envs is possible also. + + +code:: + +( +// NamedControl is recommended in that case as a literal Array of special format would be impractical. +// Define a SynthDef with maximum envelope size you expect to pass + +SynthDef(\envArray_1, { |out = 0, freq = 440, amp = 0.1, timeScale = 1, gate = 1| + var sig, env, envArray, envSig; + envArray = Env([0, 1, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0]).asArray; // works also without '.asArray' + env = \env.kr(envArray); // shortcut for NamedControl.kr(\env, envArray) + envSig = EnvGen.kr(env, gate, timeScale: timeScale, doneAction: 2); + sig = Saw.ar([freq, freq * 2.01], amp); + Out.ar(out, sig * envSig) +}).add +) + +// Pbind uses event type \on to avoid setting gate to 0 and receiving messages "node not found", +// synths are ended by envelopes anyway. + + +( +p = Pbind( + \type, Pshuf([\on, \on, \rest], inf), + \instrument, \envArray_1, + \dur, Pn(Pshuf([1, 1/2, 1/2]), 30), + \midinote, Pn(Pshuf((40..80))) + Pn(Pshuf([[0.5, 11.5], [0, 4], [0, -7], [-0.5, -9.5]])), + + // envData contains env types, determined by levels and times. + // Times are only relations, within the synthdef they are scaled by the timeScale arg. + \envData, Pn(Pshuf([ + [[0, 1, 0], [1, 1]], + [[0, 1, 1/4, 1, 0], [1, 1, 1, 1]], + [[0, 1, 1/4, 1, 1/4, 1, 0], [1, 1, 1, 1, 1, 1]] + ])), + + // *x splits the envData array into levels and times, Env is converted in to an Array automatically + \env, Pkey(\envData).collect { |x| Env(*x) }, + + // when wanting to pass the Array explicitely it would require + // wrapping into an additional Array which is necessary for passing: + // \env, Pkey(\envData).collect { |x| [Env(*x).asArray] }, + + \timeScale, Pfunc { |e| e.dur / e.envData[1].sum } +).trace.play; +) + +p.stop; + +:: + diff --git a/HelpSource/Tutorials/HS_with_VarGui.schelp b/HelpSource/Tutorials/HS_with_VarGui.schelp new file mode 100644 index 0000000..a222a1f --- /dev/null +++ b/HelpSource/Tutorials/HS_with_VarGui.schelp @@ -0,0 +1,360 @@ +TITLE::HS with VarGui +summary::using HS / HSpar with VarGui +categories:: Libraries>miSCellaneous>HS and HSpar, Libraries>miSCellaneous>GUI, Streams-Patterns-Events>HS and HSpar +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Guides/Guide_to_HS_and_HSpar, Classes/VarGui, Tutorials/VarGui_shortcut_builds, Tutorials/Event_patterns_and_LFOs + +DESCRIPTION:: + +It is recommended to get familiar with the concepts of the link::Classes/HS:: / link::Classes/PHS:: and link::Classes/HSpar:: / link::Classes/PHSpar:: classes before combining them with VarGui, see link::Guides/Guide_to_HS_and_HSpar:: for an overview. + +Alternative control setups with event patterns and VarGui examples are discussed in link::Tutorials/Event_patterns_and_LFOs::. + +subsection::Differences to other control setups with event patterns and VarGui + +Instances of HS and PHS are hybrid objects in the sense that their functionality is not interchangeable with that of some (one might think at a first glance) related objects (Synths and Pbinds). A HS contains a SynthDef but will also hold Synths derived thereof. A PHS holds Pbind pairs but, when played, instantiates two EventStreamPlayers to be synchronized, also taking control over the used HS. A played PHSpar even controls a third player switching between running control synths. + +On the other hand VarGui is already prepared to accept synths / synth definitions and pbinds / eventstream players / tasks functions / tasks for generating synth and stream players. For plugging these concepts together it seems practical to use objects (resp. introduce adapting ones) that fit into what's already there. This can be achieved by two methods: + +numberedList:: + +## code::.newPaused::, applicable to PHS / PHSpar makes the used HS / HSpar generate a new paused Synth (or possibly several in case of PHSpar). Synths are returned by the method and can be passed to VarGui, which automatically detects their derivation from HS / HSpar and adapts gui functionality. + +## code::.asTask::, applicable to PHS / PHSpar and PHSuse / PHSparUse returns a wrapper Task for compound players of the PHS family. Playing and stopping the wrapper Task invokes playing and stopping behaviour of the underlying compound players, per default also taking control over playing and stopping the help synth(s). + +:: + +An alternative approach to link HS and HSpar with VarGui is shown in link::#Ex.3::. + + +anchor::Ex.1a:: +subsection::Ex.1a: HS / PHS + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +h = HS(s, { |midiCenter = 70, dev = 20, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }); + +p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) ]); + +// Instantiation and return of a new paused Synth from HS's synth definition + +x = p.newPaused; + +// second intermediate object: +// PHS's asTask method generates a wrapping task +// which will invoke expected player actions by notification + +// Note asTask's flag newEnvir, it determines if +// the player should run in a newly generated environment, defaults to true. +// This allows to take one PHS definition with varibles to be set in +// different environments (as with Pbind). + +t = p.asTask; + +// also compare play / stop behaviour with these options (default was hsStop: false, hsPlay: true): +// hsPlay = false will run Synth together with stream only for the first time + +// t = p.asTask(hsStop: true, hsPlay: true); +// t = p.asTask(hsStop: true, hsPlay: false); +// t = p.asTask(hsStop: false, hsPlay: false); +) + +( +// VarGui detects that a Synth derived from a HS has been passed: +// synth player stop / reset button and mode buttons are greyed out, +// pause button background color is blue as Synth is new and paused. +// Playing and stopping the stream player will also cause the synth's running +// (and stopping with asTask's option hsStop = true), +// however playing and stopping the help synth separately is still possible. + +// For Synths derived from HS / HSpar pushing the general stop button will +// only cause pausing - other synths will be freed - whereas Cmd. will free all synths, +// but ends HS gui control. Then a new VarGui would have to polled from reevaluated x and t. + +v = VarGui([], + [[\midiCenter, [50, 65, \lin, 0.01, 60], + \dev, [0, 10, \lin, 0.01, 10], + \devFreq, [0, 3, \lin, 0.01, 0.5] ]], + t, x +).gui; +) + +// free resources, PHSplayer is wrapped, so use Cmd-. or this + +h.free; + +:: + + +anchor::Ex.1b:: +subsection::Ex.1b: HS / PHS and PHSuse + +code:: + +( +h = HS(s, { |midiCenter = 70, dev = 20, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }); + +p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) ]); +q = PHSuse(h, 0.3, [ \midinote, Pkey(\val) + 5 ]); +r = PHSuse(h, 0.4, [ \midinote, Pkey(\val) + 10 ]); + +x = p.newPaused; + +// quant arg for syncing + +t = [p,q,r].collect(_.asTask(quant: 0.3)); +) + +( +// The PHSplayer has control over the help synth, +// PHSusePlayers can't be played before, +// though it's possible to start all players together with shift-click. + +// For the same reason hsStop and hsPlay options are only available for the PHS-wrapping Task. + +// After PHSplayer has been started (and maybe paused) PHSusePlayers can be +// played and stopped separately. + + +v = VarGui([], + [[ \midiCenter, [50, 65, \lin, 0.01, 60], + \dev, [0, 10, \lin, 0.01, 10], + \devFreq, [0, 3, \lin, 0.01, 0.5] ]], + t, x +).gui; +) + +h.free; + +:: + + + +anchor::Ex.2a:: +subsection::Ex.2a: HSpar / PHSpar + +code:: + +( +h = HSpar(s, [ + { |midiCenter = 65, dev = 10, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }, + { |midiCenter = 90, dev = 10, devFreq = 1| LFSaw.kr(devFreq, 0, dev, midiCenter) } +]); + +// switching behaviour defined by hsIndices + +p = PHSpar(h, + pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [-4, 5] ]], + hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) +]); + +// for PHSpar this returns a collection of new paused help synths + +x = p.newPaused; + +t = p.asTask(quant: 0.3, hsStop: true); + +// You can specify start and stop options for selected Synths of HSpar +// with indices or collections of indices, e.g.: +// t = p.asTask(quant: 0.3, hsStop: 0); +) + +( +// Playing and stopping the PHSparPlayer's wrapper Task affects both help synths in parallel. + +v = VarGui([], + [[54.1, 3.85, 1.5], [60, 7.8, 2.7]].collect { |y| [ + \midiCenter, [50, 65, \lin, 0.01, y[0]], + \dev, [0, 10, \lin, 0.01, y[1]], + \devFreq, [0, 3, \lin, 0.01, y[2]]] + }, t, x +).gui; +) + +h.free; + +:: + + + +anchor::Ex.2b:: +subsection::Ex.2b: HSpar / PHSpar and PHSparUse + +code:: + +( +h = HSpar(s, [ + { |midiCenter = 65, dev = 10, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }, + { |midiCenter = 90, dev = 10, devFreq = 1| LFSaw.kr(devFreq, 0, dev, midiCenter) } +]); + +p = PHSpar(h, + pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [-4, 5] ]], + hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) +]); + +x = p.newPaused; + +q = PHSparUse(h, + pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [0, 9] ]], + hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) ] +); + +r = PHSparUse(h, + pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [2.5, 7] ]], + hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) ] +); + +t = [p,q,r].collect(_.asTask(quant: 0.3)); +) + +( +v = VarGui([], + [60, 75].collect { |y| [ + \midiCenter, [55, 85, \lin, 0.01, y], + \dev, [0, 10, \lin, 0.01, 10], + \devFreq, [0, 3, \lin, 0.01, 0.5]] + }, t, x +).gui; +) + +h.free; + +:: + + + +anchor::Ex.2c:: +subsection::Ex.2c: HSpar / PHSpar and PHSparUse with switch pattern + +code:: + +( +h = HSpar(s, [ + { |midiCenter = 65, dev = 10, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) }, + { |midiCenter = 90, dev = 10, devFreq = 1| LFSaw.kr(devFreq, 0, dev, midiCenter) } +]); + + +// define event pattern to switch between the two help synths, +// switch times (average and random deviation) to be controlled from gui as well as +// chord structure determined by a single interval parameter + +p = PHSpar(h, + switchDur: Pfunc { ~swTmMid * (1 + rand2(~swTmDev)) }, + switchIndex: Pseq([0,1], inf), + pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + Pfunc { [~int.neg, 0, ~int] } ]], + switchOn: true, // help synths to be resumed when active and ... + switchOff: true // ... to be paused when left - will be reflected by VarGui's button background colors +); + +x = p.newPaused; + +// chord structure of other pbinds will be taken from same variable ~int, so ... + +q = PHSparUse(h, [ 0.15, [ \midinote, Pkey(\val) + Pfunc { ~int.neg / 3 + [~int.neg, 0, ~int] } ]]); +r = PHSparUse(h, [ 0.15, [ \midinote, Pkey(\val) + Pfunc { ~int * 3 / 5 + [~int.neg, 0, ~int] } ]]); + +// ... players must be in the same environment, this is ensured by setting .asTask's flag newEnvir to false, +// all will be played and set in currentEnvironment. + +t = [p,q,r].collect(_.asTask(quant: 0.3, newEnvir: false)); +) + +( +v = VarGui([ + \swTmMid, [0.3, 1.5, \lin, 0.01, 1], + \swTmDev, [0, 0.5, \lin, 0.01, 0.5], + \int, [0, 12, \lin, 0.01, 8] + ], [60, 75].collect { |y| [ + \midiCenter, [55, 85, \lin, 0.01, y], + \dev, [0, 10, \lin, 0.01, 10], + \devFreq, [0, 3, \lin, 0.01, 0.5]] + }, t, x +).gui; +) + +// As can be seen in gui stopping with red stop button won't stop switching. +// Player had been started with asTask's default option switchStop = false +// and, as wrapped in a task, is not directly accessible. +// Anyway you can, as always, stop and free resources with Cmd-. or + +h.free; + +:: + + +anchor::Ex.3:: +subsection::Ex.3: Help Synths not passed explicitely + +This is an alternative approach if syncing control synths and streams is not important - whereas resetting control synths becomes an option. + +code:: + +( +// Actual help synths can be defined separately and fed into +// formal help synths via control busses + +// control synths hard-wired to allocated busses + +c = Bus.control(s, 2).index; + +SynthDef(\control_1, { |midiStart = 65, midiDiff = 15, duration = 10| + Out.kr(c, XLine.kr(midiStart, midiStart + midiDiff, duration) ) }).add; +SynthDef(\control_2, { |midiStart = 90, midiDiff = 15, duration = 10| + Out.kr(c+1, XLine.kr(midiStart, midiStart - midiDiff, duration) ) }).add; + +h = HSpar(s, [{ In.kr(c) }, { In.kr(c+1) }] ); + +// PHSpar and PHSparUse defined in same way as above but ... +// ... PHSparPlayer wrapper Task defaults to hsStop = false, +// so reading values from bus c is not stopped with pausing and +// PHSparUsePlayers can still get new values from "external" control synths + +p = PHSpar(h, + pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [-4, 5] ]], + hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) +]).asTask(quant: 0.3); + + +q = PHSparUse(h, + pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [0, 9] ]], + hsIndices: [ Pstutter(Prand([2, 3, 5, 11], inf), Pseq([0,1], inf)) ] +).asTask(quant: 0.3); + +r = PHSparUse(h, + pbindArgs: [ 0.15, [ \midinote, Pkey(\thisVal) + [2.5, 7] ]], + hsIndices: [ Pstutter(Prand([2,3,5, 11], inf), Pseq([0,1], inf)) ] +).asTask(quant: 0.3); +) + + +( +// Now control synths have to be started before streams ! +// Caps + shift click for parallel play action will work on cocoa, +// you can choose custom bundle latency (c) and set it slightly below server latency + +v = VarGui([], + [60, 80].collect { |x| [ + \midiStart, [50, 95, \lin, 0.01, x], + \midiDiff, [0, 30, \lin, 0.01, 15], + \duration, [1, 20, \lin, 0.01, 15]] + }, [p,q,r], [\control_1, \control_2] +).gui(playerPriority: \synth, sliderPriority: \synth); + +// modes of "external" control synths can be set +// resetting (starting new synths of same definition) is also possible +) + +// additional synths engaged here, so cleanup with Cmd-. or stopping in gui plus ... + +h.free; + +:: + diff --git a/HelpSource/Tutorials/Idev_suite.schelp b/HelpSource/Tutorials/Idev_suite.schelp new file mode 100644 index 0000000..441e3e4 --- /dev/null +++ b/HelpSource/Tutorials/Idev_suite.schelp @@ -0,0 +1,26 @@ +TITLE::Idev suite +summary::classes for number search with integer distance from a source, optionally avoiding repetitions within a span +categories:: Libraries>miSCellaneous>Idev suite, Streams-Patterns-Events +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/DIdev, Classes/PIdev, Classes/PLIdev + + +DESCRIPTION:: + +DIdev / PIdef / PLIdev search for numbers with integer distance from a source signal / pattern up to a given deviation. Repetitions within a lookback span are avoided, DIdev / PIdef / PLIdev randomly choose from possible solutions. Intended for search within integer grids (pitches, indices etc.), however applications with non-integer sources are possible, see examples. + +The main musical idea of these classes is, that it's often unwanted to have certain characteristics repeated within a perceptional time window. This might apply to melodic lines as well as to rhythmic patterns or sequences of timbre and can be continued in the domain of microsound. The principle can easily be understood with pitches, therefore most examples are of this kind. + +In the following example integers are searched within the neighbourhood of a rounded sine source. The hi and lo deviation is constant (+/-5) and a lookBack value of 3 garantuees that there are no repetitions of integer values within a group of 4 (e.g. find a closest repetition within the last 5 points). In general lookback and deviations can be dynamic, the source doesn't need to be rounded and the comparison threshold for the repetition check can also be passed as a dynamic argument. + + +image::attachments/Idev_suite/Idev_scheme_3.png:: + +note:: +It's the user's responsibility to pass a combination of deviation and lookback values that allows a possible choice, see examples. +:: + +note:: +In contrast to PIdev and PLIdev, DIdev needs to know maximum deviations (strong::minLoDev::, strong::maxHiDev::) beforehand. Together with strong::maxLookBack:: they determine multichannel sizes, which might be CPU-consuming. +:: + + diff --git a/HelpSource/Tutorials/Live_Granulation.schelp b/HelpSource/Tutorials/Live_Granulation.schelp new file mode 100755 index 0000000..ae7a700 --- /dev/null +++ b/HelpSource/Tutorials/Live_Granulation.schelp @@ -0,0 +1,900 @@ + + +TITLE::Live Granulation +summary::different approaches to live granulation with gui examples +categories:: Libraries>miSCellaneous>General Tutorials, Streams-Patterns-Events +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/Buffer_Granulation, Classes/VarGui, Tutorials/VarGui_shortcut_builds, Tutorials/PLx_suite, Classes/PbindFx, Tutorials/kitchen_studies, Tutorials/Sieves_and_Psieve_patterns, Classes/PSPdiv, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + +DESCRIPTION:: + +This file is a complement to the link::Tutorials/Buffer_Granulation:: tutorial. It contains variants of granulation that are applied to an audio signal directly, without the explicit use of a buffer. As with buffer granulation there exist language-driven and server-driven as well as hybrid variants. Especially pattern-based live granulation raises certain accuracy issues, which are summarized in this tutorial. + +warning:: + +numberedList:: + +## Be careful with amplitudes! To reduce feedback I've built in tanh, LPF and delay into examples with SoundIn, though better use headphones to avoid feedback at all. For tips on reducing feedback or creative use of feedback see Nathaniel Virgo's Feedback quark. + +## Avoid too early freeing of audio buses: + +numberedList:: +## When there are still running synths, unintendedly sound might be routed to a processing resp. feedback chain. +## For the same reason don't free buses if they are hard-wired to SynthDefs, which you still want to use. +:: +You can free buses on occasion if you are sure that nothing is running and you won't need them again. Keep in mind that you can also (re-)start the server with a higher number of audio buses available (link::#Ex.2c::) + +## I haven't used below setups for live performances. Although all of them work stable for me as they are, in general hangs can occasionally happen with pattern-driven setups. Often this can be tracked down to sequences of extremely short event durations (and/or long grain durations). Where this can happen as a side-effect, thresholds can be built in. + +Another possible source of hangs is careless deep nesting of Patterns where mistakes can easily occur. Starting with clear Pattern structures is recommended - and if more complications are involved: testing without sound first, after saving your patch, might be a good idea. +:: +:: + +note:: +With the exception of link::#Ex.2d#(2d):: all examples use SoundIn, supposing to use input from your computer resp. soundcard. Depending on your setup you might have to change the standard input (bus = 0) passed to VarGui resp. the SoundIn ugen. Use headphones to avoid feedback! +:: + +note:: +All live granulation variants of this file can of course be applied to any signal, thus also to any playback of a buffer. Vice versa all variants from link::Tutorials/Buffer_Granulation:: can be applied to a buffer, which is occasionally (or continuously) filled with live input. +:: + + +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) +:: + +anchor::1:: +SECTION::1. Live granulation scheduled by server +subsection::Ex. 1a: Basic live granulation with GrainIn + +code:: +( +~audioBus_ex_1a = Bus.audio(s, 1); + +// pass envelopes with buffer, hanning is GrainIn's default anyway +h = Signal.hanningWindow(1000); +e = Buffer.loadCollection(s, h); + +SynthDef(\soundIn, { |out, in, ampIn = 1| + Out.ar(out, SoundIn.ar(in) * ampIn) +}).add; + +SynthDef(\live_gran_1a, { |out, in, envBuf, trigRate = 50, overlap = 0.5, panMax = 0.5, + panType = 0, amp = 1, minGrainDur = 0.001| + var inSig, sig, trig, grainDur, pan; + + inSig = In.ar(in, 1); + // reduce feedback + inSig = DelayC.ar(LPF.ar(inSig.tanh, 2000), 0.1, 0.01); + + trig = Impulse.ar(trigRate); + grainDur = max(trigRate.reciprocal * overlap, minGrainDur); + + // select L/R or random sequencing + pan = Demand.ar( + trig, + 0, + Dswitch1([ + Dseq([1, -1], inf), + Dwhite(-1, 1) + ], panType) + ) * min(panMax, 0.999); + + sig = GrainIn.ar( + 2, + trig, + grainDur, + inSig, + pan, + envBuf + ); + Out.ar(out, sig * EnvGate.new * amp); +}).add; +) + + +// to avoid feedback use with headphones + +( +// start granulation (first row) before to ensure right order +VarGui( + synthCtr: [ + [ + \envBuf, e.bufnum, + \in, ~audioBus_ex_1a.index, + \trigRate, [5, 500, \lin, 0, 50], + \overlap, [0.05, 3, \lin, 0, 0.5], + \panType, [0, 1, \lin, 1, 0], + \panMax, [0, 1, \lin, 0, 0.5], + \amp, [0, 1, \lin, 0, 0.1] + ],[ + \out, ~audioBus_ex_1a.index, + // you might have to pass a different 'in' bus for SoundIn + // depending on your setup + \in, 0, + \ampIn, [0, 1, \lin, 0, 1] + ] + ], + synth: [\live_gran_1a, \soundIn] +).gui +) + + +:: + +Even with overlapping grains, sequenced effect processing per grain is possible with GrainIn, but requires more effort, see link::Tutorials/Buffer_Granulation#Ex.1e in the Buffer Granulation tutorial::. + + +anchor::Ex.1b:: +subsection::Ex. 1b: Live granulation with server-driven enveloping + +In this example only non-overlapping grains are regarded, for overlapping, as with GrainIn, a multichannel approach as in link::Tutorials/Buffer_Granulation#Ex.1e#Ex.1e in the Buffer Granulation tutorial:: can be applied. In comparison to link::#1#live granulation example 1a:: there is hardly an advantage in this basic variant. However intermediate processings can be built into this SynthDef, which aren't possible in the above example, where granulation is encapsulated in a ugen, manipulation of the envelope signal could be one. + +code:: +( +~audioBus_ex_1b = Bus.audio(s, 1); + +// pass envelopes with buffer +h = Signal.hanningWindow(1000); +e = Buffer.loadCollection(s, h); + +SynthDef(\soundIn, { |out, in, ampIn = 1| + Out.ar(out, SoundIn.ar(in) * ampIn) +}).add; + +// suppose overlap < 1 + +SynthDef(\live_gran_1b, { |out, in, envBuf, trigRate = 50, overlap = 0.5, panMax = 0.5, + panType = 0, amp = 1, minGrainDur = 0.001, interpolation = 4| + var inSig, env, numFrames = BufFrames.kr(envBuf), startTrig, grainDur, + latchedTrigRate, latchedStartTrig, latchedOverlap, pan; + + inSig = In.ar(in, 1); + // reduce feedback + inSig = DelayC.ar(LPF.ar(inSig.tanh, 2000), 0.1, 0.01); + + startTrig = Impulse.ar(trigRate); + // why this ? - shape of envelope shouldn't be changed while application + latchedTrigRate = Latch.ar(K2A.ar(trigRate), startTrig); + latchedStartTrig = Impulse.ar(latchedTrigRate); + latchedOverlap = Latch.ar(K2A.ar(overlap), startTrig); + + latchedOverlap = max(latchedOverlap, latchedTrigRate * minGrainDur); + grainDur = (latchedOverlap / latchedTrigRate); + + env = BufRd.ar( + 1, + envBuf, + Sweep.ar( + latchedStartTrig, + latchedOverlap.reciprocal * latchedTrigRate * numFrames, + ).clip(0, numFrames - 1), + interpolation: interpolation + ); + + pan = Demand.ar( + latchedStartTrig, + 0, + Dswitch1([ + Dseq([1, -1], inf), + Dwhite(-1, 1) + ], panType) + ) * panMax * 0.999; + + Out.ar(out, Pan2.ar(inSig * env * amp, pan) * EnvGate.new); +}).add +) + + +// to avoid feedback use with headphones + +( +// start granulation (first row) before to ensure right order +VarGui( + synthCtr: [ + [ + \envBuf, e.bufnum, + \in, ~audioBus_ex_1b.index, + \trigRate, [5, 500, \lin, 0, 50], + \overlap, [0.05, 0.99, \lin, 0, 0.5], + \panType, [0, 1, \lin, 1, 0], + \panMax, [0, 1, \lin, 0, 0.5], + \amp, [0, 1, \lin, 0, 0.1] + ],[ + \out, ~audioBus_ex_1b.index, + // you might have to pass a different 'in' bus for SoundIn + // depending on your setup + \in, 0, + \ampIn, [0, 1, \lin, 0, 1] + ] + ], + synth: [\live_gran_1b, \soundIn] +).gui +) + + +:: + + +anchor::2:: +SECTION::2. Live granulation driven by language + +The crucial point of this strategy is a kind of incompatibility of In.ar and OffsetOut. Normally we use OffsetOut for exact buffer granulation with patterns, as the start of the synth (the grain) is corrected by a shift (standard Out.ar is only able to start at control block boundaries). However, when In.ar and OffsetOut.ar are used in the same synth the read input signal is also shifted, which results in a correct grain distance but an fluctuating input delay. This can be overcome by a little trick: we can use OffsetOut to send a trigger to a bus, which indicates its correct delay. Then the trigger can be read in the same synth and trigger again a gated envelope for the input signal, the resulting grain can be output with normal Out.ar. So grain distances are correct and input is not fluctuating (see link::#Ex.2d:: for an accuracy comparison). Nevertheless language-driven sequencing is not sample-exact in realtime, no matter if In.ar is used or not, this is related to hardware control and cannot be overcome. This might be an issue with a strictly periodic input signal and very short grain distances. You can e.g. check out with granulation of a fixed-pitch triangular wave or similar, every few seconds or so there will happen an audible jump due to an irregular time interval, which is necessary for clock calibration; at that point there will, in general, also be a phase shift of the input signal (see last example of link::#Ex.2d::). This might not be noticed with an input of spoken voice e.g. Control block length is also a boundary for gated envelopes defined with EnvGen – here envelopes are established with an env buffer used by BufRd and Sweep ugens, a flexible method as Envelopes shape can be defined arbitrarily in language. + + +anchor::Ex.2a:: +subsection::Ex. 2a: Basic Pbind live granulation + +At that point I haven't seen a solution to pass the trigger bus as argument, in the example it is hard-wired. As the SynthDef uses a SetResetFF ugen, which relies on two triggers within control block length, grainDelta shouldn't be shorter than that. If shorter grainDeltas are required this would be possible with alternating SynthDefs and buses. For the same reason this SynthDef should only be used once at the same time by a Pbind/EventStreamPlayer. Alternatively – as in link::#Ex.2b:: – a SynthDef factory can produce a number of structurally equal SynthDefs bound to a number of buses. + + +code:: +( +// input bus and trigger bus +// trigger bus must be hard-wired in SynthDef though + +~audioBus_ex_2a = Bus.audio(s, 1); +~trigBus_ex_2a = Bus.audio(s, 1); + +// pass envelopes with buffer +h = Signal.hanningWindow(1000); +e = Buffer.loadCollection(s, h); + +SynthDef(\soundIn, { |out, in, ampIn = 1| + Out.ar(out, SoundIn.ar(in) * ampIn) +}).add; + +SynthDef(\live_gran_2a, { |out, inBus, envBuf, grainDelta, pan = 0, + overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4| + + var inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate, + defaultOffset = ControlRate.ir.reciprocal; + inSig = In.ar(inBus, 1); + + // reduce feedback + inSig = LPF.ar(inSig.tanh, 2000); + + // low overlap bound given by minimal grain duration + overlap = max(overlap, minGrainDur / grainDelta); + + // impulse for offset check + OffsetOut.ar(~trigBus_ex_2a, Impulse.ar(0)); + + // additional gate to start with 0 in delayed case + // this is necessary as OffsetOut outputs impulse twice + gate = SetResetFF.ar(In.ar(~trigBus_ex_2a)); + + env = BufRd.ar( + 1, + envBuf, + Sweep.ar( + In.ar(~trigBus_ex_2a), + (overlap * grainDelta).reciprocal * numFrames, + ).clip(0, numFrames - 1), + interpolation: interpolation + ); + // to finish synth + Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2); + + // shifted accurate output + Out.ar(out, Pan2.ar(env * gate * inSig * amp, pan)); +}).add +) + + +// to avoid feedback use with headphones + +// start synth and event stream player + +( +p = Pbind( + \instrument, \live_gran_2a, + \inBus, ~audioBus_ex_2a, + \envBuf, e, + \dur, Pfunc { ~trigRate.reciprocal }, + \grainDelta, Pkey(\dur), + \overlap, Pfunc { ~overlap }, + \amp, Pfunc { ~amp }, + \pan, Pfunc { ~panMax } * PLseq([1, -1]), + \addAction, \addToTail +); + +VarGui([ + \trigRate, [20, 500, \lin, 0, 100], + \overlap, [0.1, 2, \lin, 0, 0.5], + \panMax, [0, 1, \lin, 0, 0.5], + \amp, [0, 1, \lin, 0, 0.1] + ],[ + \out, ~audioBus_ex_2a.index, + // you might have to pass a different 'in' bus for SoundIn + // depending on your setup + \in, 0, + \ampIn, [0, 1, \lin, 0, 1] + ], p, \soundIn +).gui +) + + +:: + + +anchor::Ex.2b:: +subsection::Ex. 2b: Parallel Pbind live granulation + +As trigger buses have to be hard-wired, a SynthDef factory produces a number of structurally equal SynthDefs bound to a number of buses. In this variant the granulation is combined with a bandpass filter. + +code:: +( +// input bus and trigger bus +// trigger bus must be hard-wired in SynthDef though + +~audioBus_ex_2b = Bus.audio(s, 1); + +// pass envelopes with buffer +h = Signal.hanningWindow(1000); +e = Buffer.loadCollection(s, h); + +SynthDef(\soundIn, { |out, in, ampIn = 1| + Out.ar(out, SoundIn.ar(in) * ampIn) +}).add; + +// "SynthDef factory", as each SynthDef neads hard-wired bus, make 3 of each + +~trigBus_ex_2b = { Bus.audio(s, 1) } ! 3; + +{ |i| + var name = \live_gran_2b ++ "_" ++ i.asString; + SynthDef(name, { |out, inBus, envBuf, grainDelta, pan = 0, rq = 0.1, center = 500, + overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4| + + var sig, inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate, + defaultOffset = ControlRate.ir.reciprocal; + inSig = In.ar(inBus, 1); + + // reduce feedback + inSig = LPF.ar(inSig.tanh, 2000); + + // amplitude compensation for lower rq of bandpass filter + inSig = BPF.ar(inSig, center, rq, (rq ** -1) * (400 / center ** 0.5)); + + // low overlap bound given by minimal grain duration + overlap = max(overlap, minGrainDur / grainDelta); + + // impulse for offset check + OffsetOut.ar(~trigBus_ex_2b[i], Impulse.ar(0)); + + // additional gate to start with 0 in delayed case + // this is necessary as OffsetOut outputs impulse twice + gate = SetResetFF.ar(In.ar(~trigBus_ex_2b[i])); + + env = BufRd.ar( + 1, + envBuf, + Sweep.ar( + In.ar(~trigBus_ex_2b[i]), + (overlap * grainDelta).reciprocal * numFrames, + ).clip(0, numFrames - 1), + interpolation: interpolation + ); + // to finish synth + Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2); + + sig = env * gate * inSig * amp; + + // shifted accurate output + Out.ar(out, Pan2.ar(sig, pan)); + }).add +} ! 3 +) + + +// to avoid feedback use with headphones + +// start synth and event stream players + +( +// pattern maker, we need the three different instruments +p = { |i| Pbind( + \instrument, \live_gran_2b ++ "_" ++ i.asString, + \inBus, ~audioBus_ex_2b, + \envBuf, e, + \dur, Pfunc { ~trigRate.reciprocal }, + \grainDelta, Pkey(\dur), + \overlap, Pfunc { ~overlap }, + \rq, Pfunc { ~rq }, + \center, Pfunc { ~center }, + \amp, Pfunc { ~amp }, + \pan, Pfunc { ~panMax } * PLseq([1, -1]), + \addAction, \addToTail +) }; + +VarGui([ + \trigRate, [20, 500, \lin, 0, 100], + \overlap, [0.1, 2, \lin, 0, 0.5], + \panMax, [0, 1, \lin, 0, 0.5], + \center, [100, 3000, \exp, 0, 800], + \rq, [0.1, 1, \lin, 0, 0.1], + \amp, [0, 1, \lin, 0, 0.1] + ] ! 3,[ + \out, ~audioBus_ex_2b.index, + // you might have to pass a different 'in' bus for SoundIn + // depending on your setup + \in, 0, + \ampIn, [0, 1, \lin, 0, 1] + ], p ! 3, \soundIn +).gui +) + + +:: + + + +anchor::Ex.2c:: +subsection::Ex. 2c: Live granulation with PbindFx + +code:: +// extended ressources needed + +( +s.options.numPrivateAudioBusChannels = 1024; +s.options.memSize = 8192 * 16; +s.reboot; +) + + +( +// input bus and trigger bus +// trigger bus must be hard-wired in SynthDef though + +~audioBus_ex_2c = Bus.audio(s, 1); +~trigBus_ex_2c = Bus.audio(s, 1); + +// pass envelopes with buffer +h = Signal.hanningWindow(1000); +e = Buffer.loadCollection(s, h); + +SynthDef(\soundIn, { |out, in, ampIn = 1| + Out.ar(out, SoundIn.ar(in) * ampIn) +}).add; + + +// two fx SynthDefs + +SynthDef(\resample, { |out = 0, in, mix = 0.5, amp = 1, resampleRate = 44100| + var sig, inSig = In.ar(in, 2); + sig = Latch.ar(inSig, Impulse.ar(resampleRate)); + Out.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp); +}).add; + +SynthDef(\bpf, { |out = 0, in, freq = 440, rq = 0.1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = BPF.ar(inSig, freq, rq, (rq ** -1) * (400 / freq ** 0.5)); + Out.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp); +}).add; + + +SynthDef(\live_gran_2c, { |out, inBus, envBuf, grainDelta, pan = 0, + overlap = 1, amp = 1, minGrainDur = 0.001, interpolation = 4| + + var inSig, offset, env, numFrames = BufFrames.ir(envBuf), gate, + defaultOffset = ControlRate.ir.reciprocal; + inSig = In.ar(inBus, 1); + + // reduce feedback + inSig = LPF.ar(inSig.tanh, 2000); + + // low overlap bound given by minimal grain duration + overlap = max(overlap, minGrainDur / grainDelta); + + // impulse for offset check + OffsetOut.ar(~trigBus_ex_2c, Impulse.ar(0)); + + // additional gate to start with 0 in delayed case + // this is necessary as OffsetOut outputs impulse twice + gate = SetResetFF.ar(In.ar(~trigBus_ex_2c)); + + env = BufRd.ar( + 1, + envBuf, + Sweep.ar( + In.ar(~trigBus_ex_2c), + (overlap * grainDelta).reciprocal * numFrames, + ).clip(0, numFrames - 1), + interpolation: interpolation + ); + // to finish synth + Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2); + + // shifted accurate output + Out.ar(out, Pan2.ar(env * gate * inSig * amp, pan)); +}).add +) + + +( +// start without fx + +~fxOrder = 0; + +// playing the PbindFx in a new group ensures that soundIn synth is placed before when started later + +g = Group.new; + +p = PbindFx([ + \instrument, \live_gran_2c, + \inBus, ~audioBus_ex_2c, + // allow hard-wired bus (bus connections check) + \otherBusArgs, [\inBus, ~trigBus_ex_2c.index.asFloat], + \envBuf, e, + \dur, 1 / PL(\trigRate), + \grainDelta, Pkey(\dur), + \overlap, PL(\overlap), + \amp, PL(\amp), + \pan, PL(\panMax) * PLseq([1, -1]), + \fxOrder, PL(\fxOrder, envir: 't'), + \cleanupDelay, 0.1, + + \group, g + ],[ + \fx, \resample, + \mix, 1, + \amp, PL(\amp_resample), + \resampleRate, PL(\resampleRate_resample), + \cleanupDelay, 0.01 + ],[ + \fx, \bpf, + \freq, PL(\freq_bpf), + \rq, PL(\rq_bpf), + \mix, 1, + \amp, PL(\amp_bpf), + \cleanupDelay, 0.01 + ] +); + +VarGui([ + \trigRate, [20, 500, \lin, 0, 100], + \overlap, [0.1, 2, \lin, 0, 0.5], + \panMax, [0, 1, \lin, 0, 0.5], + \amp, [0, 1, \lin, 0, 0.1], + + \resampleRate_resample, [200, 3000, \exp, 0, 500], + \amp_resample, [0, 1, \lin, 0, 1], + + \freq_bpf, [50, 3000, \exp, 0, 200], + \rq_bpf, [0.1, 1, \lin, 0, 0.3], + \amp_bpf, [0, 1, \lin, 0, 1] + ],[ + \out, ~audioBus_ex_2c.index, + // you might have to pass a different 'in' bus for SoundIn + // depending on your setup + \in, 0, + \ampIn, [0, 1, \lin, 0, 1] + ], p, \soundIn +).gui( + varColorGroups: (0..8).clumps([4, 2, 3]), + tryColumnNum: 1, + labelWidth: 140, + sliderWidth: 350 +) +) + + +// check fxs and their params, mix is fixed to 1 + +// resample + +~fxOrder = 1 + + +// band pass + +~fxOrder = 2 + + +// resample -> band pass + +~fxOrder = [1, 2] + + +// band pass -> resample + +~fxOrder = [2, 1] + + +// fx alternations +// Pstutter with repeats equal 2 ensures L/R balance + +~fxOrder = Pstutter(2, PLseq([1, 2])) + +~fxOrder = Pstutter(2, PLseq([[1, 2], 1])) + +~fxOrder = Pstutter(2, PLseq([[1, 2], 2])) + + +~fxOrder = Pstutter(2, PLseq([[2, 1], 1])) + +~fxOrder = Pstutter(2, PLseq([[2, 1], 2])) + + +~fxOrder = Pstutter(2, PLseq([[1, 2], [2, 1]])) + + + +:: + + +anchor::Ex.2d:: +subsection::Ex. 2d: Accuracy comparison + +This example compares the inaccuracies occuring with "normal" usage of Out.ar and the usage of In.ar + OffsetOut.ar with the strategy recommended in link::#Ex.2a::. Run the three examples, the audio output is played and recorded into three files in the recordings directory (you get the path with thisProcess.platform.recordingsDir). + +code:: +// test with source of added sines, inaccuracies with straight use of Out.ar + +( +~audioBus_ex_2d1 = Bus.audio(s, 1); + +// pass envelopes with buffer +// envelope with sharp attack to make effect more clear +h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000); +e = Buffer.loadCollection(s, h); + +// sine as test signal +SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in| + freq = freq * (1..8); + Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new); + Out.ar(granOut, SinOsc.ar(freq).sum * amp); +}).add; + +// envelopes in left channel, granulation in right +SynthDef(\live_gran_shaky_1, { |out, inBus, envBuf, grainDelta, + overlap = 1, amp = 1, interpolation = 4| + + var inSig, offset, env, numFrames = BufFrames.ir(envBuf), + defaultOffset = ControlRate.ir.reciprocal; + inSig = In.ar(inBus, 1); + + // reduce feedback with sound in source + inSig = LPF.ar(inSig.tanh, 2000); + + env = BufRd.ar( + 1, + envBuf, + Sweep.ar( + 1, + (overlap * grainDelta).reciprocal * numFrames, + ).clip(0, numFrames - 1), + interpolation: interpolation + ); + // to finish synth + Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2); + + Out.ar(out + 1, inSig * env); +}).add; + + +p = Pfindur(1, Pbind( + \instrument, \live_gran_shaky_1, + \inBus, ~audioBus_ex_2d1, + \envBuf, e, + \dur, 0.005, + \grainDelta, Pkey(\dur), + \overlap, 0.5, + \amp, 0.1, + \addAction, \addToTail +)); +) + + +( +// record live input and granulation (which comes with delay due to normal pbind latency) +// see the file in an editor then: +// distances are irregular due to Out ugen's accuracy limit control block boundary, +// though source signal is not delayed + +{ + ~date = Date.getDate.stamp; + ~fileName = thisProcess.platform.recordingsDir +/+ + ("live_gran_shaky_1" ++ "_" ++ ~date ++ ".aiff"); + s.record(~fileName); + s.sync; + + x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d1]); + p.play; + + 1.5.wait; + x.release; + s.stopRecording; +}.fork +) + + + +///////////////////////// + +// test with source of added sines, inaccuracies with use of In.ar + OffsetOut.ar + +( +~audioBus_ex_2d2 = Bus.audio(s, 1); + +// pass envelopes with buffer +// envelope with sharp attack to make effect more clear +h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000); +e = Buffer.loadCollection(s, h); + +// sine as test signal +SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in| + freq = freq * (1..8); + Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new); + Out.ar(granOut, SinOsc.ar(freq).sum * amp); +}).add; + +// envelopes in left channel, granulation in right +SynthDef(\live_gran_shaky_2, { |out, inBus, envBuf, grainDelta, + overlap = 1, amp = 1, interpolation = 4| + + var inSig, offset, env, numFrames = BufFrames.ir(envBuf), + defaultOffset = ControlRate.ir.reciprocal; + inSig = In.ar(inBus, 1); + + // reduce feedback with sound in source + inSig = LPF.ar(inSig.tanh, 2000); + + env = BufRd.ar( + 1, + envBuf, + Sweep.ar( + 1, + (overlap * grainDelta).reciprocal * numFrames, + ).clip(0, numFrames - 1), + interpolation: interpolation + ); + // to finish synth + Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2); + + OffsetOut.ar(out + 1, inSig * env); +}).add; + + +p = Pfindur(1, Pbind( + \instrument, \live_gran_shaky_2, + \inBus, ~audioBus_ex_2d2, + \envBuf, e, + \dur, 0.005, + \grainDelta, Pkey(\dur), + \overlap, 0.5, + \amp, 0.1, + \addAction, \addToTail +)); +) + + +// record live input and granulation (which comes with delay due to normal pbind latency) +// see the file in an editor then: +// distances are regular due to OffsetOut, but source signal is delayed +// between 0 and control block length + +( +{ + ~date = Date.getDate.stamp; + ~fileName = thisProcess.platform.recordingsDir +/+ + ("live_gran_shaky_2" ++ "_" ++ ~date ++ ".aiff"); + s.record(~fileName); + s.sync; + + x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d2]); + p.play; + + 1.5.wait; + x.release; + s.stopRecording; +}.fork +) + + + +///////////////////////// + +// test with source of added sines, granulation done as in Ex.2a + +( +// input bus and trigger bus +// trigger bus must be hard-wired in SynthDef though + +~audioBus_ex_2d3 = Bus.audio(s, 1); +~trigBus_ex_2d3 = Bus.audio(s, 1); + + +// pass envelopes with buffer +// envelope with sharp attack to make effect more clear +h = Env([0, 1, 1, 0], [1, 10, 1].normalizeSum, curve: \sine).discretize(1000); +e = Buffer.loadCollection(s, h); + +// sine as test signal +SynthDef(\sineIn, { |directOut, granOut, freq = 200, amp = 0.03, in| + freq = freq * (1..8); + Out.ar(directOut, SinOsc.ar(freq).sum * amp * EnvGate.new); + Out.ar(granOut, SinOsc.ar(freq).sum * amp); +}).add; + +// envelopes in left channel, granulation in right +SynthDef(\live_gran_ok, { |out, inBus, envBuf, grainDelta, gate, + overlap = 1, amp = 1, interpolation = 4| + + var inSig, offset, env, numFrames = BufFrames.ir(envBuf), + defaultOffset = ControlRate.ir.reciprocal; + inSig = In.ar(inBus, 1); + + // reduce feedback with sound in source + inSig = LPF.ar(inSig.tanh, 2000); + + // impulse for offset check + OffsetOut.ar(~trigBus_ex_2d3, Impulse.ar(0)); + + // necessary additional gate to start with 0 in delayed case + gate = SetResetFF.ar(In.ar(~trigBus_ex_2d3)); + + env = BufRd.ar( + 1, + envBuf, + Sweep.ar( + In.ar(~trigBus_ex_2d3), + (overlap * grainDelta).reciprocal * numFrames, + ).clip(0, numFrames - 1), + interpolation: interpolation + ); + // to finish synth + Line.ar(dur: overlap * grainDelta + defaultOffset, doneAction: 2); + + // shifted accurate output + Out.ar(out + 1, inSig * env * gate); +}).add; + + +p = Pfindur(1, Pbind( + \instrument, \live_gran_ok, + \inBus, ~audioBus_ex_2d3, + \envBuf, e, + \dur, 0.005, + \grainDelta, Pkey(\dur), + \overlap, 0.5, + \amp, 0.1, + \addAction, \addToTail +)); +) + + +( +// record live input and granulation (which comes with delay due to normal pbind latency) +// see the file in an editor then: +// distances are regular due to OffsetOut, and source signal is not delayed + +// However also in this case, because of hardware output calibration, it happens that phase shifts +// occur in granulation. +// This is currently unavoidable in SC's realtime mode, but probably irrelevant in most cases of +// live granulation, where there is no strictly periodic input signal + +{ + ~date = Date.getDate.stamp; + ~fileName = thisProcess.platform.recordingsDir +/+ + ("\live_gran_ok" ++ "_" ++ ~date ++ ".aiff"); + s.record(~fileName); + s.sync; + + x = Synth(\sineIn, args: [directOut: 0, granOut: ~audioBus_ex_2d3]); + p.play; + + 1.5.wait; + x.release; + s.stopRecording; +}.fork +) + + +:: + + diff --git a/HelpSource/Tutorials/Other_event_and_pattern_shortcuts.schelp b/HelpSource/Tutorials/Other_event_and_pattern_shortcuts.schelp new file mode 100644 index 0000000..7384a3d --- /dev/null +++ b/HelpSource/Tutorials/Other_event_and_pattern_shortcuts.schelp @@ -0,0 +1,221 @@ + + +TITLE::Other event and patterns shortcuts +summary::various shortcut writings for events and patterns +categories:: Libraries>miSCellaneous>Event and pattern shortcuts +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/EventShortcuts, Tutorials/PLx_and_live_coding_with_Strings + +DESCRIPTION:: + +Apart from the class EventShortcuts itself, which handles bookkeeping of shortcut dictionaries, miSCellaneous lib includes some additional shortcut methods for generation and playing of events and different types of event patterns from SequenceableCollections. These methods support functional conventions for reference. See also link::Tutorials/PLx_and_live_coding_with_Strings::. + + +SECTION::1.) Event shortcuts + +subsection::1a: Event methods + +link::Classes/Event#-on:: + +link::Classes/Event#-off:: + + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// start default synth + +x = ().on + + +// release with 3 seconds releaseTime + +x.off(3) + + +// with EventShortcuts turned on this gives a quick way to run synths, +// here default synth with replacements 'n' -> 'note' and 'd' -> 'dur'. + + +EventShortcuts.makeCurrent(\default).on + +(n: [0, 4, 7, 12], d: 3).on + + +// define SynthDef + +SynthDef(\x, { |out = 0, freq = 440, amp = 0.05| Out.ar(0, SinOsc.ar(freq, 0, amp) * EnvGate.new) }).add + + +// run synth, audible beats with equal temperature, pitches given as notes ('n') + +(i: \x, n: [0, 4, 7], d: 3).on + + +// not with just intonation, pitches given as frequencies ('f') + +(i: \x, f: (4..6) * 100, d: 3).on + +:: + + + +subsection::1b: Event-related methods defined for SequenceableCollections + +link::Classes/SequenceableCollection#-ev:: + +link::Classes/SequenceableCollection#-on:: + +code:: + +// define an array as event template +// references to prior keys can be written as environmental variables in Functions +// here: higher midinotes get shorter durations + +( +a = [ + m: { rrand(60, 100) }, // midinote + d: { ~m.linlin(60, 90, 3, 0.1) } // dur +] +) + +// suppose EventShortCuts are turned on +// make an Event and see its data, then play, try several times + +x = a.ev + +x.on + + +// same can be done in one, try several times + +a.on + + +:: + + + +SECTION::2.) Event-pattern-related methods defined for SequenceableCollections + +link::Classes/SequenceableCollection#-pa:: + +link::Classes/SequenceableCollection#-p:: + +link::Classes/SequenceableCollection#-pm:: + +link::Classes/SequenceableCollection#-pma:: + +link::Classes/SequenceableCollection#-pbf:: + + +link::Classes/SequenceableCollection#-pp:: + +link::Classes/SequenceableCollection#-ppm:: + +link::Classes/SequenceableCollection#-ppma:: + +link::Classes/SequenceableCollection#-ppbf:: + + +code:: + +// use reference convention with Functions +// make an array of patternpairs to be used in different event pattern types + +( +a = [ + m: Pwhite(60, 80, 30), // midinote + d: { (~m > 70).if { 2 }{ 1 }/8 } // dur +].pa +) + +Pbind(*a).play + +Pmono(\default, *a).play + + + +// define Pbind and play on different channels + +( +p = [ + m: Pwhite(60, 80, 100), + d: { (~m > 70).if { 2 }{ 1 }/8 } +].p +) + +Pbindf(p, \p, -1).play(quant: 1/4) // \p for pan + +Pbindf(p, \p, 1).play(quant: 1/4) + + + +// same with Pmono ... + +( +[ + m: Pwhite(60, 80, 30), + d: { (~m > 70).if { 2 }{ 1 }/8 } +].pm(\default) +) + +Pbindf(p, \p, -1).play(quant: 1/4) + +Pbindf(p, \p, 1).play(quant: 1/4) + + + +// ... and PmonoArtic + +( +p = [ + m: Pwhite(60, 80, 100), + d: { (~m > 70).if { 2 }{ 1 }/8 }, + l: Pwhite(0.1, 1.5) // legato +].pma(\default) +) + +Pbindf(p, \p, -1).play(quant: 1/4) + +Pbindf(p, \p, 1).play(quant: 1/4) + + + +// analogous definition with Pbindef +// example different as Pbindef is pattern and player in one + +( +p = [ + m: Pwhite(60, 80, 100), + d: { (~m > 70).if { 2 }{ 1 }/8 } +].pbf(\x) +) + +Pbindef(\x, \p, -1).play + +Pbindef(\x, \p, 1) + +Pbindef(\x).clear + + + +// immediately play Pbind +// also play Pmono, PmonoArtic and Pbindef directly with methods ppm, ppma and ppbdf + +( +[ + m: Pwhite(60, 80, 30), + d: { (~m > 70).if { 2 }{ 1 }/8 } +].pp +) + +:: + + + + diff --git a/HelpSource/Tutorials/PLx_and_live_coding_with_Strings.schelp b/HelpSource/Tutorials/PLx_and_live_coding_with_Strings.schelp new file mode 100644 index 0000000..4c310cf --- /dev/null +++ b/HelpSource/Tutorials/PLx_and_live_coding_with_Strings.schelp @@ -0,0 +1,773 @@ +TITLE::PLx and live coding with Strings +summary::PLx patterns as placeholders for sequencing with letters +categories:: Libraries>miSCellaneous>PLx suite +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/PLx_suite, Classes/PsymNilSafe, Classes/PLbindef, Classes/PLbindefPar, Classes/EventShortcuts + + +DESCRIPTION:: + +Strings and Chars as high-level representations for musical objects can be used for sequencing with very condensed syntax. This is already possible with standard patterns like Pseq etc. – PLx list patterns fit this concept as their referenced Arrays/Strings can be replaced on the fly. Examples below also use EventShortcuts to minimize typing. + +warning:: +Sequencing with infinite Patterns/Streams has always the potential of hangs. E.g. Psym hangs if all referenced pattern return nil (SC 3.7.2). Here convenience method link::Classes/Pattern#-symplay:: is suggested: it employs PsymNilSafe, its method strong::embedInStream:: performs a check like in James Harkins' PnNilSafe from ddwPatterns quark (which can't be used directly this case). strong::symplay:: thus avoids hangs of that type, see link::#Ex.2b#Ex.2b) Embedding with continuation::. +:: + + +anchor::above:: +code:: +( +s = Server.local; +Server.default = s; +s.boot; +) + +( +// synthdefs to play with, use of EventShortcuts + +SynthDef(\noise, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\sin, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + + +SynthDef(\saw, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + +EventShortcuts.on; +) +:: + + +SECTION::Ex.1) Straight usage with finite Patterns and Events + +code:: +( +// use EventShortcuts + +EventShortcuts.on; +EventShortcuts.postAll; + +// base Pbind + +~x = Pbind(\d, 0.1, \i, \sin); + +// chars for the string, event patterns or events + +~a = Pbind(\m, Pseries(60, 2, 4)) <> ~x; +~b = Pbind(\m, Pseries(80, -2, 8)) <> ~x; +~c = (i: \saw, m: [95, 96, 97], d: 0.8); + +// define sequence + +~p = "aab" +) + +// symplay wraps into a PsymNilSafe +// PLseq defaults to repeats = inf and refers to 'p' in current Environment, +// thus the String "aab", the EventStreamPlayer should also get a name for start/stop, +// in this case we take the same letter (p) as interpreter variable + +p = PLseq(\p).symplay + + +// replace String of PLseq + +~p = "aacb" + + +// use of basic String operations + +~p = ~p ++ "cc" + +~p = ~p.reverse + + +( +// new char and sequence + +~d = Pbind(\m, Pwhite(60, 90, 2), \i, \noise) <> ~x; +~p = "adadcb"; +) + +// replace String + +~p = "bbcbcad" + + +// new chars +// the Function as first arg of Pseries is evaluated with every embedding, +// thus movements up and down start on different pitches + +( +~a = Pbind(\m, Pseries({ rrand(60, 75) }, 7, 4)) <> ~x; +~b = Pbind(\m, Pseries({ rrand(85, 95) }, -5, 8)) <> ~x; +~c = Pbind(\i, \noise, \m, Pn((95..100), 2)) <> ~x +) + + +// modify ~b + +~b = Pbind(\i, PLrand([\sin, \saw])) <> ~b; + + +// modify list, loop goes on +// evaluate several times, compare sound and posted String + +~p = ~p.scramble + +p.stop +:: + + + + +SECTION::Ex.2) Repeated embedding + +Control of embedding resp. the number of Events, that one Patterns should produce when played, is a subtle topic. A basic distinction is whether an embedded sequence should be produced with desired behaviour from begin to end (2a) or it should be paused and resumed (2b). The latter is the classical behaviour of Streams, but it can be mimiced with PSx stream patterns. In any case embedding can be done with varying length, e.g. by defined sequences or by interaction. + + +subsection::2a) Embedding without continuation + +Use SynthDefs from link::Tutorials/PLx_and_live_coding_with_Strings#above#above::. + +code:: +( +// base Pbinds +~x = Pbind(\d, 0.15, \i, \sin, \rel, 1.5, \a, 0.02); +~y = Pbind(\d, Pn(0.9, 1), \i, \saw, \att, 0.8, \rel, 4, \a, 0.006); + +// variable for repeats arg +~ar = 4; + +// descending sequence, note the repeats arg: as with the start arg +// the Function is evaluated with every embedding, +// ~ar can be a Stream or a value +~a = Pbind(\m, Pseries({ rrand(75.0, 95) }, -7, { ~ar.next })) <> ~x; + +// chords without octave doubling +~b = Pbind(\m, Pfunc { { [48, 60, 72].choose } ! 9 + (0..12).scramble.drop(3) }) <> ~y; + +// define sequence +~p = "ba" +) + + +p = PLseq(\p).symplay + +// change to other repeats number + +~ar = 6 + +// make it a sequence with a Stream + +~ar = PLseq([2, 2, 4]).asStream // or shorter: PLseq([2, 2, 4]).iter + + +// go back to num and change String + +~ar = 3 + +~p = "baaa" + +~p = "baabaaaa" + + +p.stop +:: + + +anchor::Ex.2b:: +subsection::Ex.2b) Embedding with continuation + +Use SynthDefs from link::Tutorials/PLx_and_live_coding_with_Strings#above#above::. + +code:: +// This can be done by feeding a stream into a pattern, either directly or with PSx + +( +// base Pbind +~x = Pbind(\d, 0.2, \rel, 0.5, \a, 0.03); + +// Streams to be continued + +~as = (Pbind(\m, Pn(Pseries(60, 1, 16)), \i, \saw) <> ~x).asStream; +~bs = (Pbind(\m, Pn(Pseries(90, -1, 16)), \i, \sin) <> ~x).asStream; + +// variable for repeats args +~ar = 4; +~br = 4; + +// for getting next values of an event stream we must pass an empty event as arg '.next(())' +~a = Pfuncn({ ~as.next(()) }, { ~ar.next }); +~b = Pfuncn({ ~bs.next(()) }, { ~br.next }); + +// define sequence +~p = "ab" +) + + +p = PLseq(\p).symplay + + +// change repeats + +~ar = 2; +~br = 1; + + +// this causes ~a to produce nils, only ~b still returns events + +~ar = nil; + +// This stops the player. +// Note that this kind of nil-detection works because 'symplay' employs PsymNilSafe. +// A construct like Psym(Pseq("ab", inf), ...).play would hang in that case + +~br = nil; + +// Note that this is different from the case, when the dictionary's key itself is nil. +// Then we get silent events, that also would't cause a hang with Psym(Pseq("ab", inf), ...).play + + +// evaluate above code starting from ~x = ... again and run + +p = PLseq(\p).trace.symplay + +// now we get a rest event + +~a = nil + +// player keeps running silently, stop explicitely + +~b = nil + +p.stop + + +// same as above written with PSx stream patterns + +( +// base Pbind +~x = Pbind(\d, 0.2, \rel, 0.5, \a, 0.03); + +// variable for repeats args +~ar = 4; +~br = 4; + +~a = PS(Pbind(\m, Pn(Pseries(60, 1, 16)), \i, \saw) <> ~x, { ~ar.next }); +~b = PS(Pbind(\m, Pn(Pseries(90, -1, 16)), \i, \sin) <> ~x, { ~br.next }); + +// define sequence +~p = "ab" +) + + +p = PLseq(\p).symplay + +// change repeats + +~ar = 2; +~br = 1; + +p.stop; +:: + + + + + + + +SECTION::Ex.3) Parallel embedding + +Use SynthDefs from link::Tutorials/PLx_and_live_coding_with_Strings#above#above::. + +code:: +// This can e.g. be done with Ppar, Ptuple or Pspawner, which is most flexible. +// There's a tiny isssue here in combination with EventShortcuts, duration keys +// should be in full length (You could apply method 'eventShortcuts' inside Ppar, +// but that's even more typing in that case, so we just write 'dur' instead of 'd'). + +( +// base Pbind + +~x = Pbind(\dur, 0.1, \i, \sin); +~y = Pbind(\dur, 0.05, \i, \sin); + +// chars for the String, event patterns or events + +~a = Pbind(\m, Pseries({ rrand(60.0, 65) }, 1, 8)) <> ~x; +~b = Pbind(\m, Pseries({ rrand(85.0, 95) }, -1, 8)) <> ~y; + +~c = Ppar([~a, ~b]); + +~d = (i: \saw, m: [95, 96, 97], d: 0.8); + +// define sequence + +~p = "accd" +) + + +p = PLseq(\p).symplay + +// equivalent with Pspawner + +~c = Pspawner { |sp| sp.par(~a); sp.par(~b) }; + + +// with Pspawner you have precise control over embedding of subsequences + +~c = Pspawner { |sp| sp.par(~a); 4.do { sp.par(~b) } }; + + +~e = Pspawner { |sp| sp.par(~a); 3.do { sp.seq(~b) } }; + +~p = "acde" + +p.stop +:: + + + +SECTION::Ex.4) Use of other PLx list patterns + +Use SynthDefs from link::Tutorials/PLx_and_live_coding_with_Strings#above#above::. + +code:: +// We can keep the String constant and switch to different PLx list patterns. + +( +// base Pbind + +~x = Pbind(\d, 0.1, \i, \saw); +~y = Pbind(\d, 0.2, \i, \sin); + +// chars for the String, event patterns or events + +~a = Pbind(\m, Pseries(60, Pwhite(1.0, 7.0), 4)) <> ~x; +~b = Pbind(\m, Pseries(80, Pwhite(1.0, 7.0), 4)) <> ~y; + +~c = ~x <> ~b; +~d = ~y <> ~a; + +~e = Pbind(\i, \noise, \m, Pn(70, 2)) <> ~x; + +// define sequence + +~p = "abcde" +) + + +// we need another proxy in that case, take general PL + +~l = PLseq(\p); + +p = PL(\l).symplay; + + +// scramble sequence and keep + +~l = PLshuf(\p); + + +// scramble with every loop + +~l = PLshufn(\p); + + + +// weighted random + +~l = PLwrand(\p, [4, 1, 3, 1, 1]/10); + +p.stop; +:: + + + + + +SECTION::Ex.5) PLbindef and PLbindefPar + + +High-level control of Strings can be combined with replacing key streams with PLbindef/PLbindefPar. + +note:: +You should always do cleanup with link::Classes/PLbindef#-remove:: / link::Classes/PLbindefPar#-remove:: or link::Classes/Pdef#-removeAll:: after finishing the examples (see notes in link::Classes/PLbindef:: / link::Classes/PLbindefPar::). +:: + + +subsection::Ex.5a) PLbindef + +Use SynthDefs from link::Tutorials/PLx_and_live_coding_with_Strings#above#above::. + +code:: +( +// base PLbindef, continued embedding with PS as in Ex. 2b + +~x = PS(PLbindef(\y, \dur, 0.1, \i, \noise, \rq, 0.5, \att, 0.05, \rel, 0.1, \a, 0.05)); + +// chars for the String, event patterns or events + +~a = Pbind(\m, Pn(75, 1)) <> ~x; +~b = Pbind(\m, Pn(80, 1)) <> ~x; + +~c = Ppar([~a, ~b]); + +// define sequence + +~p = "ab" +) + +p = PLseq(\p).symplay + + +// update PLbindef's rq and amplitude with patterns + +~y.rq = PLseq((100..1).mirror / 1000) + +~y.a = PLseq((20..80).mirror / 1000) + + +// update String + +~p = "c" + +~p = "ababccc" + + +// stop and cleanup + +p.stop + +~y.remove +:: + + + +subsection::Ex.5b) PLbindefPar + +Use SynthDefs from link::Tutorials/PLx_and_live_coding_with_Strings#above#above::. + +code:: +( +// data base for two PLbindefPars, continued embedding with PS as in Ex. 2b + +~w = [\dur, 0.1, \i, \noise, \rq, 0.5, \att, 0.05, \rel, 0.1, \a, 0.02]; + +~chord = (46, 53..95); + +~a = PS(PLbindefPar(\u, 7, \m, ~chord, *~w), 1); +~b = PS(PLbindefPar(\v, 7, \m, ~chord + 2, *~w), 1); + +// define sequence + +~p = "ab" +) + +p = PLseq(\p).symplay + + +~u.rq = 0.005 + +~v.i = \saw + + +// evolving changes + +~u.rq = PLseq((100, 95..5).mirror / 1000) + +~v.a = Pseg(PLseq([0.01, 0.04]), Pwhite(4, 7)) + + +// change sequence per String + +~p = "aabb" + +~p = "aabbabab" + +~p = "aabbcababc" + +~p = "aaaab" + + +// fade out + +~v.a = Pseg(Pseq([0.02, 0]), 20) + +~u.a = Pseg(Pseq([0.02, 0]), 20) + + +// stop and cleanup + +( +p.stop; +~u.remove; +~v.remove; +) +:: + + +SECTION::Ex.6) String sequencing with PbindFx + +Control with Strings can be thought in many ways. With effects one can e.g. use different Strings for src and fx sequencing. + +anchor::last:: +subsection::Ex.6a) PbindFx + +code:: +// boot server with extended resources for PbindFx + +( +s.options.numPrivateAudioBusChannels = 1024; +s.options.memSize = 8192 * 16; +s.reboot; + +// fx synths + +SynthDef(\resample, { |out = 0, in, mix = 1, amp = 1, + resampleRate = 22050, lagTime = 1| + var sig, inSig = In.ar(in, 2); + sig = Latch.ar(inSig, Impulse.ar(resampleRate)); // resampling + // lag in milliseconds for smoothing + sig = sig.lag(lagTime * 0.001); + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + +SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000, + cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = RLPF.ar( + inSig, + LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi), + rq, + amp + ).softclip; + Out.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + + +// src synths + +SynthDef(\noise, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\saw, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + + +// prepare EventShortcuts for additional keys + +EventShortcuts.addOnBase(\default, \fxExs, ( + dec: \decayTime, + cd: \cleanupDelay, + cf: \cutOffMoveFreq, + fxo: \fxOrder, + rs: \resampleRate +), true); + +EventShortcuts.makeCurrent(\fxExs); + +EventShortcuts.on; +) + + +( +// base Pbind +// PbindFx's fxOrder (short: fxo) syntax employed by Symbol mapping: +// u: no fx, x: resample, y: wah, z: resample and wah in sequence + +~r = Pbind( + \fxo, Psym(PLseq(\fx), (u:0, x:1, y:2, z:[1, 2])), + \a, 0.07, + \att, 0.01, + \rel, 0.3, + \cd, Pkey(\att)+ Pkey(\rel) + 0.1 +); + +// PS to embed + +~a = PS(Pbind(\i, \saw, \d, 0.1, \m, PLseq([60, 60, 60, 62])) <> ~r, 1); + +~b = PS(Pbind( + \i, \noise, + \d, 0.2, + \m, PLseq([Pwhite(41.0, 50),Pwhite(71.0, 80)]), + \rq, 0.01 +) <> ~r, 1); + +// no cleanupDelay defaults for fxs as they don't delay +q = PbindFx(PsymNilSafe(PLseq(\p)), [ + \fx, \resample, + \mix, 0.3, + \rs, Pwhite(200, 500), + \a, 1 + ],[ + \fx, \wah, + \mix, 0.8, + \cf, Pwhite(0.5, 5), + \a, 0.7 +]); +) + +// start instrument sequence with no fx + +( +~fx = "u"; +~p = "aab"; +p = q.play; +) + +// fx sequences + +~fx = "uuxy" + +~fx = "uyuxzzzz" + + +~p = "b" + +p.stop +:: + + +subsection::Ex.6b) PbindFx and PLbindef + +Use SynthDefs from link::Tutorials/PLx_and_live_coding_with_Strings#last#last example::. + +code:: +// There's more fine-tuned control if we can replace key streams also + +( +// base pairs for PLbindef + +~r = [ + \fxo, Psym(PLseq(\fx), (u:0, x:1, y:2, z:[1, 2])), + \a, 0.07, + \att, 0.01, + \rel, 0.3, + \cd, Pkey(\att) + Pkey(\rel) + 0.001 +]; + +// PS to embed + +~a = PS(PLbindef(\aa, \i, \saw, \d, 0.1, \m, PLseq([60, 60, 60, 62]), *~r), 1); + +~b = PS(PLbindef(\bb, + \i, \noise, + \d, 0.2, + \m, PLseq([Pwhite(41.0, 50),Pwhite(71.0, 80)]), + \rq, 0.1, + *~r +), 1); + +// as we have defined fx chars 'x' and 'y' above, +// choose related names 'xx' and'yy' for PLbindefs + +q = PbindFx(PsymNilSafe(PLseq(\p)), + PLbindef(\xx, + \fx, \resample, + \mix, 0.3, + \rs, Pwhite(200, 500), + \a, 1 + ), + PLbindef(\yy, + \fx, \wah, + \mix, 0.8, + \cf, Pwhite(0.5, 5), + \a, 0.7 + ) +); +) + +// start with no fxs + +( +~fx = "u"; +~p = "aab"; +p = q.play; +) + +// fx sequence + +~fx = "uuxy" + + +// midinote for ~a and ~b + +~aa.m = [50, 52] + +~bb.m = Pwhite(80, 96) + + + +// switch to single fx resample for testing changes of its control streams +// resample, test rate + +~fx = "x" + +~xx.rs = Pwhite(1000, 3000) + + +// same with wah + +~fx = "y" + +~yy.cf = 3 + + +// src changes + +~aa.rel = 0.1 + +~bb.rel = 0.5 + + +// further playing + +~fx = "xxy" + +~p = "aabaabaaaabb" + + +~aa.m = [38, 40] + +~bb.rq = 0.7 + +~fx = "z" + + +// fade out + +~aa.a = Pseg(Pseq([0.04, 0]), 20) + +~bb.a = Pseg(Pseq([0.04, 0]), 20) + + +// cleanup + +p.stop + +Pdef.removeAll +:: diff --git a/HelpSource/Tutorials/PLx_suite.schelp b/HelpSource/Tutorials/PLx_suite.schelp new file mode 100644 index 0000000..04576b3 --- /dev/null +++ b/HelpSource/Tutorials/PLx_suite.schelp @@ -0,0 +1,577 @@ +TITLE::PLx suite +summary::dynamic scope Pattern variants +categories:: Libraries>miSCellaneous>PLx suite, Streams-Patterns-Events +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Tutorials/Event_patterns_and_Functions, Classes/VarGui, Tutorials/VarGui_shortcut_builds, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Tutorials/PLx_and_live_coding_with_Strings + + +DESCRIPTION:: + +Environmental variables within functions can act as placeholders for values, but also Patterns itself. So Patterns including functional code (e.g. Pfunc, Plazy, Pcollect) can, thanks to dynamic scoping, turn into different Streams, depending on the environment where streamifying happens ( link::Tutorials/Event_patterns_and_Functions:: ). This can be used for getting a whole parametrized family of Streams / EventStreamPlayers from a single pattern definition. Other applications are on-the-fly replacements and gui control of parameters of Pbinds / EventStreamPlayers with link::Classes/VarGui:: . Nevertheless constructs with Plazy, Pfunc etc. require some redundant typing which is saved by PLx Patterns (lazy evaluation). They are either plain wrapper classes or include variant implementations for the most common pattern types and deliver a more or less unified way for the described kind of placeholding. + +Unification however can only be approximated as Patterns, even those of one type (e.g. ListPatterns), are defining different behaviour: not all inputs of a source pattern x can be dynamically updated (e.g. the start of a Pseries), not all of them are allowed to be Patterns itself. Implementation and usage may differ a bit from class to class. If there is no PLx implementation of a source Pattern class you can use PL as a general pattern placeholder input, see link::Classes/PL:: help file for an example. link::Classes/PLbindef:: and link::Classes/PLbindefPar:: allow key stream replacements in shortcut object prototyping syntax. + + + +note:: +PL follows a paradigm of immediate replacement. There are cases though where you might prefer to finish streams or substreams before replacement, especially when syncing comes into play, for these options consider link::Classes/PLn:: and the strong::cutItems:: arg of PLx list patterns. +:: +warning:: +Feeding a looped process with an invalid input has always the potential to lead to hangs. See link::Classes/PsymNilSafe:: and link::Tutorials/PLx_and_live_coding_with_Strings:: for some remarks on that. +:: + +SECTION::PLx pattern classes + +subsection::value and event patterns + +link::Classes/PL::, link::Classes/PLn::, link::Classes/PLseq::, link::Classes/PLser::, link::Classes/PLrand::, link::Classes/PLwrand::, link::Classes/PLshuf::, link::Classes/PLshufn::, link::Classes/PLslide::, link::Classes/PLtuple::, link::Classes/PLwalk::, link::Classes/PLswitch::, link::Classes/PLswitch1:: + +subsection::value patterns + +link::Classes/PLwhite::, link::Classes/PLlprand::, link::Classes/PLhprand::, link::Classes/PLmeanrand::, link::Classes/PLbrown::, link::Classes/PLgbrown::, link::Classes/PLseries::, link::Classes/PLgeom::, link::Classes/PLbeta::, link::Classes/PLcauchy::, link::Classes/PLgauss::, link::Classes/PLpoisson::, link::Classes/PLexprand:: + +subsection::filter patterns + +link::Classes/PLnaryop::, link::Classes/PLnaryFunc::, link::Classes/PLIdev:: + +subsection::subclasses of Pdef + +link::Classes/PLbindef::, link::Classes/PLbindefPar:: + + + +EXAMPLES:: + +anchor::Ex. 1a:: +subsection::Ex. 1a: ListPatterns placeholder constructs with Plazy + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + +// This is how dynamic scope placeholding of a ListPattern +// could be done with Plazy, +// Pn defaults to repeats = inf. + +( +p = Pn(Plazy { Pseq(~a, 1) }); + +~a = (60..70); + +x = Pbind( + \midinote, p, + \dur, 0.2 +).play; +) + + +// First drawback: replacing of a new list doesn't have an immediate effect +// as the old list is looped through before. + +// Try evaluating this before the end of the original loop. + +~a = (75..84) ++ Pseq([85,86], 10); + + +// Second drawback: replacing of a single pattern element, +// which corresponds to a stream just being embedded, +// doesn't have an immediate effect +// as this embedding is finished before. + +// Try evaluating this during the trill, it doesn't have an +// effect before the next loop. + +~a[10] = 91; + +x.stop; + +:: + +Similar placeholder constructs with Pcollect and Pfunc have similar drawbacks concerning replacement. However, this type of "delayed replacement" might be wanted in some cases and is also possible with PLx patterns, see link::Classes/PLn:: and the strong::cutItems:: arg of PLx list patterns. + + + +anchor::Ex. 1b:: +subsection::Ex. 1b: PLx implementation of ListPatterns + +PLx Patterns take symbols as input. Derived Streams get the values of the Environment of streamification. + + +code:: + +( +p = Pbind( + \midinote, PLseq(\a), + \dur, 0.2 +); + +~a = (60..70); + +y = p.play; +) + + +// replacement of the whole list has an immediate effect now, +// with Pseq the loop starts with the new list + +~a = (75..84) ++ Pseq([85,86], 10); + + +// replacing a single element also has an immediate effect, +// (as PLseq's cutItems arg defaults to true), +// try evaluating during the trill + +~a[10] = 91; + +y.stop; + + +// PLx ListPattern implementations can also act as +// ordinary ListPatterns if args are not Symbols. +// Difference: repeats arg defaults to inf, +// so you save typing in this case, +// but don't apply .all to Streams derived from such Patterns ! + +( +x = Pbind( + \midinote, PLseq((60..70)), + \dur, 0.2 +).play; +) + +x.stop; + +:: + +anchor::Ex. 1c:: +subsection::Ex. 1c: PLx implementation of Non-ListPatterns + + +code:: + +// an explicit definition with Plazy + +p = Pwhite(Pn(Plazy { ~lo }), Pn(Plazy { ~hi }), { ~r }); + + +// similarily done implicitely by PLwhite + +q = PLwhite(\lo, \hi, \r); + + +// streamify in envir + +e = (lo: 60, hi: 90, r: 30); + +e.use { q.asStream.all }; + + +// reset repeats to inf and streamify again + +e.use { ~r = inf; x = Pbind(\midinote, q, \dur, 0.2).play }; + + +// replace lower bound by pattern + +e.lo = PLseq([60, 90]); + +x.stop; + +:: + + +anchor::Ex. 1d:: +subsection::Ex. 1d: Plain PL + +A general placeholder that can be updated after instantiation. +Its repeats arg defaults to inf. + +code:: + +PL(\a, \r); + +// roughly equivalent to + +Pn(Plazy { ~a }, { ~r }); + + +e = (a: Pseq((60, 62.5..80))); + +e.use { x = Pbind(\midinote, PL(\a), \dur, 0.1).play } + + +// note that with this replacement new scrambles are chosen +// repeatedly because Pshuf's repeats arg defaults to 1. + +e.a = Pshuf((80, 78.5..65)); + + +// fixed reordering + +e.a = Pshuf((80, 78.5..65), inf); + +e.a = PLshuf((80, 78.5..65)); + + +x.stop; + +:: + +PL can also be used with Patterns which don't have a PLx implementation. +See link::Classes/PL:: for an example. + + + +anchor::Ex. 2:: +subsection::Ex. 2: Playing in different Environments + +code:: + +// Pbind to be streamified differently in different environments + +( +p = Pbind( + \midinote, PLseq(\a), + \dur, PL(\d) +); + +e = (a: (67..72), d: 0.1); +f = (a: (85..90), d: 0.2); +) + + +// start in sync or individually + +x = e.use { p.play(quant: 0.2) }; +y = f.use { p.play(quant: 0.2) }; + + +// replacement of array elements ... + +e.a[0] = 95; +f.a[0] = [75, 79]; + + +// ... which may also be Patterns + +f.a[0] = Pseq([75, 79], 3); + + +// replacing the whole array + +f.a = (83..80); + +f.a = (79..75) +.t [0, 3.5]; + +e.a = [Pseq([63, 65.5], 4), 85, 87]; + +x.stop; +y.stop; + +:: + + + +anchor::Ex. 3:: +subsection::Ex. 3: Use with VarGui + +code:: + +// basic form of a step sequencer (amp defaults to 0 in associated global ControlSpec) + +( +\default.pVarGui( + ctrBefore: [\a, [0, 6, \lin, 1, 3] ! 8], + pBefore: [\degree, PLseq(\a) ] +).gui; +) + +:: + + +See link::Classes/VarGui:: and link::Tutorials/VarGui_shortcut_builds:: for further examples. + + + + +anchor::Ex. 4:: +subsection::Ex. 4: The repeats arg + +PLx Patterns' repeats arg defaults to inf. This makes sense in situations where you want to go on replacing items on the fly. If a PLx Patterns is itself enclosed you may want to set it to a different value. Anyway the resulting number of repeats is the product of outer and inner repeats. + +code:: + +// PL(\a) defaults to repeats = inf +// Pshuf defaults to repeats = 1, is embedded repeatedly and +// so it continues producing new permutations (like Pshufn) + +( +p = Pbind( + \midinote, PL(\a), + \dur, 0.15 +); + +~a = Pshuf((60..63)); +) + + +x = p.play; + + +// same effect, but this is normal Pshufn behaviour + +~a = Pshufn((60..63)); +~a = Pshufn((60..63), inf); + +x.stop; + + +// PL(\a, 2) demands inner repeats = inf for endless running + +( +p = Pbind( + \midinote, PL(\a, 2), + \dur, 0.15 +); + +~a = Pshuf((60..63), inf); +) + +// now normal Pshuf behaviour + +x = p.play; + + +// same achieved with PLshuf((60..63)) as it defaults to repeats = inf + +~a = PLshuf((60..63)); + + +// stop with a Pseq which defaults to repeats = 1, played twice because of PL(\a, 2) + +~a = Pseq((70..65)); + + +:: + + +anchor::Ex. 5a:: +subsection::Ex. 5a: Updating input of N-ary operators + +One may want to have the choice to update inputs of N-ary operators applied to Patterns too. A common case is clipping. Say you have a Pcauchy pattern (distribution with a relatively high number of outliers) and want to dynamically change its mean value and clip bounds. + + +code:: + +( +p = Pbind( + \midinote, PLcauchy(\m, \s).collect(_.clip(~lo, ~hi)), + \dur, 0.1 +); +) + +// define the environment + +e = (m: 75, s: 1, lo: 60, hi: 90); + +e.use { x = p.play }; + +// update upper bound to mean value + +e.hi = 75; + +x.stop; + + +// above pitch pattern could be written explicitely too with Pnaryop + +Pnaryop(\clip, PLcauchy(\m, \s), [Pfunc { ~lo }, Pfunc { ~hi }]) + +// more powerful: PL allows updating with patterns + +Pnaryop(\clip, PLcauchy(\m, \s), [PL(\lo), PL(\hi)]) + +// even shorter: the PLnaryop class expands to the above + +PLnaryop(\clip, PLcauchy(\m, \s), [\lo, \hi]) + + +// In the simple case of updating clip bounds with values +// maybe one would rather use the version with collect. + +// But with Pnaryop you can pass a list of arbitrary patterns as arglist +// and with PLnaryop you can dynamically update with arbitrary patterns - +// both can be used for more differentiated control of clip bounds +// (or args of any other N-ary operator or Function). +// Also the source pattern can be replaced. + +( +p = Pbind( + \midinote, PLnaryop(\clip, \pat, [\lo, \hi]), + \dur, 0.1 +); +) + +// define the environment and play + +e = (pat: PLcauchy(\m, \s), m: 75, s: 1, lo: 60, hi: 90); + +e.use { x = p.play }; + + +// compare distributions + +e.pat = PLgauss(\m, \s); + + +// switch back to Cauchy with more outliers + +e.pat = PLcauchy(\m, \s); + + +// update bounds, lo bound 85 is mostly gone below, +// so nearly every second event has this midinote +// vice versa with hi bound 65 + +e.lo = PLseq([60, 85]); + +e.use { ~lo = 60; ~hi = PLseq([65, 90]) }; + + +// clipping to a window that loops through the distribution: +// values are taking more or less the wandering clip bounds if lo or hi, +// but are rather randomly distributed between clip bounds around the mean value + +e.use { ~lo = PLseq((50..95)); ~hi = ~lo + 10 }; + +x.stop; + +:: + +For replacing operators dynamically take PLnaryFunc with the operator wrapped into a Function. + + +anchor::Ex. 5b:: +subsection::Ex. 5b: Updating input of N-ary Functions + +Self-defined functions can be used as with PLnaryop. + + +code:: + +( +p = Pbind( + \midinote, PLnaryFunc(\f, \src, [\b, \c]), + \dur, 0.1 +); +) + +// define Environment + +( +e = (); + +e.src = Pstutter(3, PLseq((60, 70..90))); +e.b = PLseq((0..2)); +e.c = 0; + +e.f = { |x,y,z| x + y + z }; +) + + +// run + +e.use { x = p.play }; + + +// replace function input + +e.b = PLseq((0..1)); + +e.c = [-5, 0]; + +e.c = PLseq([[-5, 0], [0, 3]]); + +e.b = PLshuf((0..3)); + + +// replace function + +e.f = { |x,y,z| x + (y * 1.2) + z }; + +e.f = { |x,y,z| x + y + (z * 1.7) }; + + +x.stop; + +:: + + +subsection::Ex. 6: PLbind / PLbindef + +code:: + +// start PLbindef, an special Environment of that name is created for setting + +p = PLbindef(\a, \midinote, 70).play + +// set in it + +~a.midinote = Pwhite(60, 80) + +~a.midinote = ~a.midinote + [0, 4] + +~a.dur = 0.25 + +// cleanup + +( +p.stop; +p.remove; +) + + +// PLbindef sprouts parallel processes +// start 4 in unisono + +p = PLbindefPar(\b, 4, \midinote, 60, \dur, 2, \amp, 0.03).play + +// set single streams + +~b[3].midinote = Pwhite(70, 70.5) + +~b[3].dur = 0.05 + + +~b[2].midinote = Pwhite(65.0, 67) + +~b[2].dur = 0.1 + + +// use method value for setting + +~b.([0, 1], \midinote, Pwhite(50, 60), \dur, 0.1) + + +// general setting, now we probably aren't in sync + +~b.dur = 2 + + +// reset syncs + +p.reset + + +// stop and cleanup + +( +p.stop; +p.remove; +) +:: + + diff --git a/HelpSource/Tutorials/PSx_stream_patterns.schelp b/HelpSource/Tutorials/PSx_stream_patterns.schelp new file mode 100644 index 0000000..89d964a --- /dev/null +++ b/HelpSource/Tutorials/PSx_stream_patterns.schelp @@ -0,0 +1,69 @@ + +TITLE::PSx stream patterns +summary::Pattern variants that have a state and can remember their last values +categories:: Libraries>miSCellaneous>PSx stream patterns, Streams-Patterns-Events +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/MemoRoutine, Classes/PS, Classes/PSdup, Classes/PSrecur, Classes/PSloop + + +DESCRIPTION:: + +In general Patterns are thought to have no state. They are defining models for the really generative objects, Streams, which respond to the method next that causes them to return a next value. So, in general, an arbitrary number of Streams might be derived from one Pattern, all behaving as defined by the latter. +However there are cases where it is comfortable to have objects that behave like Streams, e.g. resume from their last state if embedded, and at the same time still benefit from everything which is already implemented for Patterns. PSx stream patterns are an attempt to accomplish this. PSx patterns behave like Streams - they resume from last state when repeatedly embedded -, they remember their last value (or a number of last values) and they are real Patterns by subclassing, i.e. operators defined for Patterns can be applied. Internally they use Stream's subclass link::Classes/MemoRoutine:: which performs value buffering. + + +SECTION::PSx value and event pattern classes + +link::Classes/PS::, link::Classes/PSdup::, link::Classes/PSrecur::, link::Classes/PSloop:: + + +EXAMPLES:: + + +code:: + +( +p = Pseq([ + PS(Pseq((1..9), inf), 3), + PS((Pseries() + 1) * 100, Pseq([1,2,3], inf)) +], inf); + +q = p.asStream; + +q.nextN(50); +) + +// ATTENTION: + +// It is important to remember that, differing from normal +// Pattern convention, repeatedly applying .asStream to a +// PSx pattern or a Pattern that encloses a PSx doesn't cause a Stream to begin at the start. +// Every new Stream refers to the internally used and previously left off MemoRoutine. + + +p.asStream.nextN(5); + +p.asStream.nextN(10); + + +// For getting a totally new Stream you can reevaluate the Pattern definition or +// define the Pattern with a wrapping Function: + +( +a = { + Pseq([ + PS(Pseq((1..9), inf), 3), + PS((Pseries() + 1) * 100, Pseq([1,2,3], inf)) + ], inf); +}; + +b = a.value.asStream; + +b.nextN(50); +) + +a.value.asStream.nextN(5); + +a.value.asStream.nextN(10); + +:: + diff --git a/HelpSource/Tutorials/Sieves_and_Psieve_patterns.schelp b/HelpSource/Tutorials/Sieves_and_Psieve_patterns.schelp new file mode 100644 index 0000000..1959237 --- /dev/null +++ b/HelpSource/Tutorials/Sieves_and_Psieve_patterns.schelp @@ -0,0 +1,1347 @@ +TITLE::Sieves and Psieve patterns +summary::list building and sequencing based on Xenakis' sieves +categories:: Libraries>miSCellaneous>Sieves and Psieve Patterns, Streams-Patterns-Events>Sieves and Psieve Patterns +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/Sieve, Classes/Psieve, Classes/PSPdiv, Classes/PSVunion, Classes/PSVunion_i, Classes/PSVunion_o, Classes/PSVunion_oi, Classes/PSVsect, Classes/PSVsect_i, Classes/PSVsect_o, Classes/PSVsect_oi, Classes/PSVsymdif, Classes/PSVsymdif_i, Classes/PSVsymdif_o, Classes/PSVsymdif_oi, Classes/PSVdif, Classes/PSVdif_i, Classes/PSVdif_o, Classes/PSVdif_oi, Classes/PSVop, Classes/PSVop_i, Classes/PSVop_o, Classes/PSVop_oi + + +DESCRIPTION:: + +Iannis Xenakis proposed sieves as integer-based generators for rhythms, pitches and other musical parameters. For an overview of history and implementations, including his own development in Python, see Christopher Ariza's article link::#[3]::. + +This SC implementation comes in two variants, with the class Sieve and Psieve patterns. Both variants include the usual sieve operations, which are based on set theory and applied to integers: union, intersection, symmetric difference and difference (complement can be defined by difference). These operations are defined for an arbitrary number of arguments as well as binary operators (Sieve). Psieve as an abstract superclass of all sieve patterns integrates sieve sequences into the pattern framework whereas Sieve is defined for calculating sieves as lists, in other words Psieve patterns are the "lazy evaluation" variant of "eager evaluation" Sieve operations. Of course you can produce sieves as lists also with Psieve patterns but for calculating very large sieves beforehand you might want to prefer Sieve, as its operations are slightly faster. For using sieves in a realtime situation the overhead of Psieve patterns will mostly be irrelevant. + +Why sieves + patterns? Not only can the ouput of sieve calculations be used in enclosing patterns – e.g. for scaling or arbitrary mapping into the continous domain –, Sieve and Psieve patterns also accept Patterns (which must be defined to produce integers) themselves as input for sieve operations, which opens a wide field for experimentation – in the case of Psieve patterns this even allows realtime control of sieve parameters and/or sieve stream output, also the logical operations can be exchanged on the fly. In one regard this is a contradiction to Xenakis' idea of sieves as an "outside-time" structure, on the other hand Xenakis, as Roads pointed out (link::#[1]::, p.168), always tended to use generative procedures very freely and this also becomes explicit, when he describes "hyperbolae" (transformations) of sieves and suggests "... transformations of the logical operations in some fashion, using the laws of logic and mathematics, or arbitrarily." (link::#[3]::, p.66). Patterns involve a wide range of such possibilities and provide a comfortable interface to be applied dynamically. + +Psieve patterns as well as Sieves can work in two modes, regarding sieves as sequences (resp. lists) of 'intervals' with an offset, or as 'points', meaning the ascending numbers itself (the wording of 'sums' and 'differences' would also be nearby, but here 'difference' is already used for set operations, so I leaned on Xenakis' terms). For efficiency reasons only one representation of a Sieve is current at a time, the default result mode is 'points'. However all operations exist in alternative result mode variants and of course Sieves can also be converted anytime. For calculus of Sieves there exist corresponding binary operators as shortcuts. + +Characteristics of sieves are closely bound to relations of numbers by prime factors, roughly said: more complexity and longer periodicity is following from merging moduls that have fewer prime factors in common. However for the sake of keeping classes light-weight, dealing with period lengths etc. is not implemented within the sieve classes itself. See link::#[1]:: and link::#[3]:: for some number-theoretical considerations, you might also want to check Xenakis' original examples and hints. Useful integer operations (prime numbers, factoring) are contained in SC main and can help you to easily carry through your own experiments, some extensions of built-in lcm-algorithm are contained (link::#4b::). A thorough investigation of the symmetric structures generated is out of the scope of this package, however some observations on symmetry types (link::#3b::) and basic analysis tools are included (link::#3c::). Last but not least: plotting the intervals can give a good impression of sieve characteristics. +anchor::[1]:: +anchor::[2]:: +anchor::[3]:: +anchor::[4]:: + +subsection::References + +numberedList:: +## Xenakis, Iannis (1990). "Sieves" Perspectives of New Music 28(1): 58-78. +## Xenakis, Iannis (1992). Formalized Music. Hillsdale, NY: Pendragon Press, 2nd Revised edition. +## Ariza, Christopher (2005). "The Xenakis Sieve as Object: A New Model and a Complete Implementation" Computer Music Journal 29(2): 40-60. +## Roads, Curtis (2015). Composing Electronic Music. A New Aesthetic. Oxford University Press. + +:: + + +SECTION::1) The Sieve class + +subsection::1a) Basic generation from Integers, modes 'intervals' and 'points' + +code:: +// define a simple sieve with multiples of 3, 5 and 7, 0 is included +// as no limit is given, the result goes up to the default limit of 65536 +// per default result is given in mode 'points' + +a = Sieve.union(3, 5, 7) + + +// a limit is passed as Ref object as last arg + +a = Sieve.union(3, 5, 7, `30) + + +// convert to 'intervals': same Sieve object and new List that has one item less, +// the slot 'offset' is set accordingly (here it equals 0) +// the offset in mode 'points' is always nil + +a.toIntervals + + +// convert back + +a.toPoints + + +// access for arbitrary further use + +a.list + + +// generate a Sieve from an Integer, it contains one point + +a = 5.toSieve.dump; + +// it's interval representation is an empty list + +a.toIntervals.dump + + +// calculate with 'intervals' from the beginning + +Sieve.union_i(3, 5, 7, `30) +:: + + +subsection::1b) Generation from Patterns, Streams and Sieves + +code:: +// Instead of Integers producing their multiples you can pass Patterns or Streams. +// It's assumed that Patterns/Streams produce Integers interpreted as intervals +// (if it's defined to produce 'points' it can e.g. be wrapped into a Pdiff). + +Sieve.union(Pseq([1, 10], inf), 5, 7, `30) + +Sieve.union({ loop { rrand(1,10).yield } }.r, 5, 7, `30) + + +// compare with result from the pattern alone +// a union with one argument just returns its resulting elements. +// As pattern arguments are interpreted as intervals, 0 is included + +Sieve.union(Pseq([1, 10], inf), `30) + + +// a Sieve can itself be passed to generate a new one, +// the mode of the passed sieve is taken into account + +a = Sieve.union(5, 7, `30); + +Sieve.union(a, 3, `30) +:: + + +subsection::1c) Elementary sieve operations + +code:: +// beneath union: intersection, symmetric difference, difference +// note that order of arguments only plays a role with difference + + +// intersection: only integers produced by all generators +// symmetric difference: only integers produced by one of the generators +// difference: only integers produced by the first generator, but by none of the others + +// 'generator' here refers to allowed sieve operator args +// (Integers as modul generators, Patterns/Streams or Sieves itself) + + +// proof of concept, evaluate in order + +a = Sieve.union(3, 5, 6, `100) + +// collect all pairwise intersections + +( +b = Sieve.sect(3, 5, `100); // intersection with moduls: multiples of smallest common multiple +c = Sieve.sect(5, 6, `100); +d = Sieve.sect(3, 6, `100); // multiples of 6 itself +) + +// calculate the symmetric difference ... + +e = Sieve.symdif(3, 5, 6, `100) + + +// ... it equals the difference of a, b, c and d ... + +f = Sieve.dif(a, b, c, d, `100) + +e == f + +// ... which equals the difference of a and the union of b, c and d + +g = Sieve.dif(a, Sieve.union(b, c, d, `100)) + +e == g +:: + + +subsection::1d) Offset methods + +code:: +// for passing individual offsets there exist dedicated methods with suffixes '_o' and '_oi' +// offsets args are passed after the generating items + + +// one generating number with offset +// producing, mathematically spoken, a part of the residual class 2 modulo 3 + +Sieve.union_o(3, 2, `30) + + +// several generating numbers with offsets, passed pairwise + +Sieve.union_o(3, 2, 5, 1, 7, 4, `30) + +Sieve.union_oi(3, 2, 5, 1, 7, 4, `30) + + + +// the shift operation adds an offset, it changes the receiver + +a = Sieve.union(5, 7, `30); + +a.shift(100) + +a.shiftTo(0) + +// as operators + +a >> 100 + +a >>! 10 +:: + + +subsection::1e) Sieve operations defined as instance methods + +code:: +// all operations and shortcuts defined above can be applied to instances + +( +a = Sieve.union(3, 5, `1000); +b = Sieve.union(4, 7, `1000); +c = Sieve.union(6, 8, `1000); +) + +// Note that large periods are already resulting from simple combinations of +// elementary operations and few prime factors. + + +// by default plot shows intervals + +symdif(a, b, c).plot + +dif(a, b, c).plot +:: + + + +subsection::1f) Sieve operations defined as binary operators + +code:: +// instantiation with 'new': second arg limit doesn't need to be a Ref +( +a = Sieve(6, 60); +b = Sieve(8, 60); +) + +// union + +union(a,b) +a|b + +union_i(a,b) +a|*b + + +// intersection + +sect(a,b) +a&b + +sect_i(a,b) +a&*b + + +// symmetric difference + +symdif(a,b) +a--b + +symdif_i(a,b) +a--*b + + +// difference, only elementary operation where order plays a role + +dif(a,b) +a-b + +dif_i(a,b) +a-*b + +dif(b,a) +b-a + +dif_i(b,a) +b-*a + + +// Efficiency hint: for an operation with a number of args, especially with large Sieves, +// it is more efficient to use the core class or instance methods than to concatenate binary operators: +// doing the latter means stepping through the list/range with each binary operation +:: + +subsection::1g) Segments of Sieves + +code:: +// These operations result in new Sieve objects of mode 'points' + +a = Sieve.union(3, 5, 7, `30) + + +// lo bound + +a.segmentGreaterEqual(7) + +a >=! 7 + +a.segmentGreater(7) + +a >! 7 + + +// hi bound + +a.segmentLessEqual(10) + +a <=! 10 + +a.segmentLess(10) + +a =! [10, 20] + +a.segmentBetween(10, 20) + +a <>! [10, 20] +:: + + +subsection::1h) Conversion to Sieves from Arrays + +code:: +// Conversion from arbitrary SequenceableCollection to Sieve: +// per default it's assumed that receiver and result are thought to be in mode 'points' +// but source and target mode can be passed as args 'fromMode' and 'toMode' + +a = [1, 5, 17, 33, 37, 43, 57, 60, 61, 62, 63, 75, 89, 92, 97]; + +a.toSieve + + + +// define result mode, add offset + +a.toSieve(toMode: \intervals, addOffset: 100) + + +// define interval meaning of receiver +// default offset zero + +a.toSieve(\intervals) + + +// same with offset 1, abbreviations for mode selection + +a.toSieve(\i, \p, 1) + + + +// if Integers are regarded as points, they must be ascending ... + +a.reverse.toSieve + + +// ... but intervals can be descending ... + +a.reverse.toSieve(\i) + + +// ... however they must be positive + +(a.reverse ++ -1).toSieve(\i) + + +// It's possible to disable the checks preformed with conversion (flag 'withCheck'), +// but this only makes sense in a context where a large number of +// speed-critical conversions on well-prepared data has to be done. +// Otherwise it's always useful to perform those checks as +// sieve operations on wrong data (e.g. unordered lists) will fail or hang. +:: + + +subsection::1i) Copying and transformation of Sieves by arbitrary array operations + +code:: +// It would be possible to define Sieve as subclass of List but there exist many +// methods for List which don't make any sense for sieves, even worse: they can consequently +// result in disfunctionality of standard operations defined for Sieves as List subclasses. +// This could be overcome with additional checks for these standard operations, a bloating which +// can be avoided if we try to keep only "proper sieves" as Sieve objects, +// thus by default dedicated wrappers for arbitrary transformations include checks. + + +a = Sieve.union_o(3, 1, 7, 2, `30) + +// simple deep copy ... + +b = a.copy + + +// ... lists are equal but not identical + +a.list == b.list +a.list === b.list + +// as expected sieves are equal + +a == b + +// but also a converted Sieve is equal + +a == b.toPoints + + + +// mode (intervals) and offset are taken over from original Sieve +// new array is inserted and checked if it contains proper (ascending) integers + +a.copyWith([2, 17, 29, 31, 35, 53]) + +a.copyWith([2, 2, 3, 5, 1, 10]) + + + + +// if the receiver is of mode 'intervals', the array of above can be passed + +b = a.copy.toIntervals + +b.copyWith([2, 2, 3, 5, 1, 10]) + + + +// main workhorse for transformations, note that offset is kept while intervals reversed + +c = b.copyApplyTo(\reverse) + +c.toPoints + + +b.copyApplyTo(\mirror).plot + + +// as with method 'applyTo' arbitrary Functions can be passed + +b.copyApplyTo { |x| x * x * x ++ (1..10).mirror } + + +// partial application + +b.copyApplyTo(_ ++ [7, 5, 1]) +:: + + +SECTION::2) Psieve patterns + +subsection::2a) Basic generation from Integers, modes 'intervals' and 'points' + + +code:: +// Psieve patterns use the prefix PSV followed by the name of elementary +// sieve operations, as used with the Sieve class, and optional suffixes. +// In comparison with Sieve more arguments are taken, so +// the generating arguments and offsets are to be passed within an array. + + +PSVunion([3, 5, 7]).asStream.nextN(20) + +// intervals + +PSVunion_i([3, 5, 7]).asStream.nextN(20) + + +// other operations + +PSVsect([3, 5, 7]).asStream.nextN(20) + +PSVsymdif([3, 5, 7]).asStream.nextN(20) + +PSVdif([3, 5, 7]).asStream.nextN(20) + + + +// offsets, offsets + interval output + +PSVunion_o([3, 2, 5, 4]).asStream.nextN(20) + +PSVunion_oi([3, 2, 5, 4]).asStream.nextN(20) + + +PSVdif_o([3, 2, 5, 4]).asStream.nextN(20) + +PSVdif_oi([3, 2, 5, 4]).asStream.nextN(20) + +... + +// maxLength defines the maximum number of items - +// in case of a randomly generating item or a low summation limit +// the overall stream might have to end earlier. + +b = PSVunion([7, 17, 29], 30).asStream + +b.all + + +// if the summation limit is set, maxLength might not be reached + +c = PSVunion([7, 17, 29], 30, 100).asStream + +d = c.all + +d.size + +:: + + +subsection::2b) Generation from Patterns, Streams and Sieves + + +code:: +// distorted periodicity by union with random sieve + +a = ({ rrand(0, 1000) } ! 50).asSet.asArray.sort.toSieve + +PSVunion_i([4, 7, a]).asStream.nextN(100).plot + +// compare + +PSVunion_i([4, 7]).asStream.nextN(100).plot + + + +// distorted periodicity by union with random patterns + +PSVunion_i([4, 7, Pn(Pshuf([2, 5, 9])) ]).asStream.nextN(100).plot + +PSVunion_i([4, 7, Pwhite(3, 5) ]).asStream.nextN(100).plot +:: + + +subsection::2c) Sequencing logical operations + + +code:: +// This is done by PSVop patterns, which take a Symbol or a pattern of Symbols, +// refering to the elementary logical operators. +// The stream of operations is forwarded with every integer point, +// which has to be stepped through. + +// helper function for plotting + +p = { |x, n = 200| x.asStream.nextN(n).plot }; + + +// plain standard operations, all elementary PSV patterns can be written with PSVop + +PSVop([5, 9], \u).asStream.nextN(20) // union +PSVop([5, 9], \d).asStream.nextN(20) // difference +PSVop([5, 9], \sd).asStream.nextN(20) // symmetric difference + + +// some operator loops + +p.(PSVop_i([5, 9], Pseq([\u, \sd], inf))) +p.(PSVop_i([5, 9], Pseq([\u, \s], inf))) +p.(PSVop_i([5, 9], Pseq([\u, \d], inf))) +p.(PSVop_i([5, 9], Pseq([\u, \d, \sd], inf))) + + +// random operator changes + +p.(PSVop_i([5, 9], Pn(Pshuf([\u, \d])))) +p.(PSVop_i([5, 9], Prand([\u, \d], inf))) + + + +// change of difIndex: + +p.(PSVop_i([5, 2, 3], \d)) +p.(PSVop_i([5, 9], Prand([\u, \d], inf))) + + +// "subtract" from index 0: multiples of 5, not divided by 7, 9, and 12 + +PSVdif([5, 7, 9, 12]).asStream.nextN(30) + + +// same as intervals, plotted + +p.(PSVdif_i([5, 7, 9, 12])) + +// written with PSVop + +p.(PSVop_i([5, 7, 9, 12], \d, 0)) + + +// also changing the positions other than the first is equivalent + +p.(PSVdif_i([5, 7, 9, 12])) + +p.(PSVop_i([5, 12, 7, 9], \d, 0)) + + + +// but "subtracting" from another number is different + +p.(PSVdif_i([12, 9, 7, 5])) + + +// you can write the same with PSVop and difIndex without swapping the elements + +p.(PSVop_i([5, 12, 7, 9], \d, 1)) + + + +// you can generate more complicated periods by more refined series of difIndices ... + + +p.(PSVop_i([4, 7], \d, 0)) + + +p.(PSVop_i([4, 7], \d, PLseq([0, 0, 1]))) + +p.(PSVop_i([4, 7], \d, PLseq([0, 0, 1, 0, 0, 0, 1]))) + + +// ... or combination of dynamic operator changes and difIndex changes. +// difIndices are only forwarded when operator 'difference' is current + +p.(PSVop_i([4, 7], PLseq([\d, \d, \u]), PLseq([0, 0, 1]))) +:: + + + +subsection::2d) Using Sieves in other than Psieve patterns + +code:: +// Period lengths of intervals of basic Sieves are related to prime factors and +// least common multiples ([1] and [3] for a more detailled description) + +// So when using intervals of those basic structures it is not necessary to +// sum up to large numbers, which is what Sieve methods with suffix '_i' and +// corresponding Psieve patterns internally do, as they are not checking for periodicity. +// Instead we can calculate the list of intervals beforehand and use it at will. +// Also 'beforehand' doesn't exclude realtime use: it's easy to write a Function that +// generates Sieves and derives Patterns from it, which, with placeholder patterns, +// can be exchanged on the fly + + +// choose factors and calculate lcm (see 3b), +// as a summation limit it determines one period of intervals + +a = [5, 6, 8]; + +m = a.lcmByGcd; + +// calculate one period of intervals, plot the symmetric structure + +x = Sieve.union_i(5, 6, 8, `m); + +x.plot; + + +// sieve loop without counting high + +Pseq(x.list, 5).asStream.all.plot + + +// second Sieve + +( +b = [20, 17]; +n = b.lcmByGcd; +v = b ++ `n; +y = Sieve.union_i(*v); +y.plot; +) + +// make list for use with arbitrary Patterns + +z = x.list ++ y.list; + +z.plot; + + +// alternating sieves + +Pseq(z, 3).asStream.all.plot; + + +// sequencing random segments of random length +// this is done with Pindex, an ascending index list of random length and a random offset + +// Function for Plazy + +q = { Pindex(z, Pseq((0..rrand(10, 20))) + z.size.rand) } + +Pn(Plazy(q), 30).asStream.all.plot; + + +// sequencing randomly repeated random segments of random length + +q = { Pindex(z, Pseq(((0..rrand(10, 20)) ! rrand(1, 5)).flat) + z.size.rand) } + +Pn(Plazy(q), 20).asStream.all.plot; +:: + + + +SECTION::3) Periodicity of intervals with elementary operations + +subsection::3a) Periodicity of intervals with elementary operations + +code:: +// For 'union' and no offsets one period of intervals is given with summation limit +// equal to the least common multiple (lcm) of the generating numbers. +// (if one number divides another, the larger one can be dropped) + +// The period length (number of intervals per period) is thus lesser than the lcm. +// A symmetric structure is produced, for 'union' the period is equal to its mirror, +// in other words the produced sequence is a concatenation of symmetric segments + +// With 3, 7 and 8 lcm equals 168 + +a = Sieve.union_i(3, 7, 8, `168) + +a.plot + +// number of intervals per period + +a.size + +// For symmetric difference and difference lcm also plays a role, +// but it's a bit different. +// As (offset 0) 0 is not included, the interval sequence doesn't really start from there, +// so with limit = lcm the period is incomplete (although the segment is already symmetric) + +Sieve.symdif_i(3, 7, 8, `168).plot + + +// To get the full picture take twice lcm as limit: +// the interval in the middle was not included before ! + +Sieve.symdif_i(3, 7, 8, `(168 * 2)).plot + + + +// you can get the continuation to symmetry (begin = end) +// with a real start point as offset: + +Sieve.symdif_oi(3, -3, 7, 0, 8, 0, `(168 + 3)).plot; + +Sieve.dif_oi(3, -3, 7, 0, 8, 0, `(168 + 3)).plot; +Sieve.dif_oi(7, -7, 3, 0, 8, 0, `(168 + 7)).plot; +Sieve.dif_oi(8, -8, 3, 0, 7, 0, `(168 + 8)).plot; + + +// In other words in the latter cases the produced sequence is a +// concatenation of symmetric segments, in which begin/end points are merged. +:: + + +subsection::3b) Types of symmetry +anchor::3b:: + +Symmetric structures in periodic series occur as different types, depending if the period length is even or odd. + +DEFINITION: + +numberedList:: +##Lets's call a period 'symmetric' iff it's equaling its reverse. +##Lets's call a period 'quasi-symmetric' iff the continuation with its first element is symmetric. +##If a sequence contains a symmetric or quasisymmetric period, there exists a symmetric or quasisymmetric period starting in its middle (or just right from it when odd), +let denote it its 'coperiod'. +:: + + +STATEMENTS (formal proof omitted, but rather straightforward): + +numberedList:: +##If the period length is even, a symmetric period corresponds to a symmetric coperiod and a quasisymmteric period corresponds to a quasisymmteric coperiod. +##If the period length is odd, a symmetric period corresponds to a quasisymmteric coperiod. +##Only a period of identic elements can be symmetric and quasi-symmetric at the same time. +:: + +subsection::3c) Analysis tools +anchor::3c:: +code:: +// Method 'checkSymmetricPeriods' applies to Arrays resp. intervals of Sieves +// and checks for periods and possible symmetries. +// It returns an array with 4 items: +// (1) the sequence clumped in (quasi-)symmetric (or asymmetric) chunks +// (2) the index offset of the first (quasi-)symmetric period +// (3) a symbol indicating if the period length is even or odd +// (4) an array of Booleans indicating the completeness of the clumped chunks (incomplete periods can be of any type) + +// Some important points here: +// (1) 'checkSymmetricPeriods' searches for smallest periods and its (possible) symmetry +// (2) 'checkSymmetricPeriods' is supposing that no prefix items are introducing +// a periodicity, thus a sequence like 7, 8, 9, 1, 2, 3, 2, 1, 2, 3, 2, 1 ... +// will be regarded as non-periodic (this e.g. happens when offsets are far apart!). +// (3) In the case of odd quasi-symmetric periods the symmetric coperiod is searched for +// and preferred (if it's in the range) +// (4) To get meaningful results for sieves and its elementary operations – +// due to (1) and (3) – it is recommended to check sufficiently large sieves, +// e.g. set the limit to three times the lcm of the generators. + +// make a Sieve with generating prime Integers 3, 7, 8 +// regard a section of three time the expected period length + +( +a = Sieve.symdif_i(3, 7, 10, `(210 * 3)); +a.plot; +) + +// store analysis data +b = a.checkSymmetricPeriods + +// clumped sequence (long) +b[0] + +// symmetry types of chunks and completions, the first relevant period is at position 1 and quasisymmetric +b[1..2] + +// 'checkCharacteristicPeriod' returns an array with first characteristic period, +// index offset, length type (even or odd) and symmetry type + +a.checkCharacteristicPeriod + +// you can plot it directly, compare with the sieve plot, where period starts at index 41 + +a.plotCharacteristicPeriod +:: + + +subsection::3d) Symmetry types of elementary operations without offset + +code:: +// Some observations of types occuring, no proof. +// Connections between types and used numbers are not obvious here: +// all symmetry types of an operator occur with tuples of coprime and not-coprime numbers + +// union: + +// symmetric odd period +( +a = Sieve.union_i(3, 7, `42); +a.plot; +a.plotCharacteristicPeriod; +) + +// symmetric even period +( +a = Sieve.union_i(4, 9, `72); +a.plot; +a.plotCharacteristicPeriod; +) + + +// symdif: + +// symmetric odd period +( +a = Sieve.symdif_i(8, 9, `216); +a.plot; +a.plotCharacteristicPeriod; +) + +// quasi-symmetric even period +( +a = Sieve.symdif_i(7, 9, `189); +a.plot; +a.plotCharacteristicPeriod; +) + +// dif: + +// symmetric odd period +( +a = Sieve.dif_i(5, 6, `90); +a.plot; +a.plotCharacteristicPeriod; +) + + +// quasi-symmetric even period +( +a = Sieve.dif_i(6, 10, `90); +a.plot; +a.plotCharacteristicPeriod; +) +:: + + +subsection::3e) Symmetry types of elementary operations with offset + +code:: +// If generators are coprime all is quite straight: offsets will not change the +// sum of the period equal to the least common multiple but will only cause a shift. +// This was elaborated by Xenakis in [1] + + +// Things become more complicated when generators have prime factors in common: +// still period sums are preserved, but symmetry types can change; +// asymmetric periods occur. One and the same tuple of generators can cause +// different combinations of period types with different offsets. + + +// Here's a little helper Function to analyze characteristics of different offsets +// for a given choice of generating integers + +( +f = { |operator = \union_i ...generators| + var sieve, types, allGens, lcm, data, input, offsets; + //collect all offset combinations + offsets = (generators.collect { |i| (0..i-1) }).allTuples; + + offsets.collect { |offset| + lcm = lcmByGcd(*generators); + input = [operator] ++ [generators, offset].flop.flat ++ Ref(lcm * 4); + sieve = Sieve.perform(*input); + data = sieve.checkCharacteristicPeriod; + ([operator, offset] ++ (data.drop(1)) ++ + ["lcm " ++ lcm.asInteger] ++ ["periodSum " ++ data.first.sum]).postln; + }; + "" +} +) + +// this pair of generators gives three different types: odd asym, odd sym, even sym +f.(\union_oi, 8, 12) + +// check: +// odd asymmetric +Sieve.union_oi(8, 0, 12, 1, `48).plot; + +// odd symmetric +Sieve.union_oi(8, 0, 12, 2, `48).plot; + +// even symmetric +Sieve.union_oi(8, 0, 12, 4, `48).plot; + + +// odd sym, even sym, even asym +f.(\union_oi, 12, 20) + +// odd asym, even sym +f.(\union_oi, 15, 20) + +// odd sym, even asym +f.(\union_oi, 9, 15) + + +// More types result from more fixed generators with varying offsets +// here: odd sym, odd asym, even sym, even asym + +f.(\union_oi, 8, 10, 12) + + + +// Also note that trivial genrator combinations for union without offset (when dividing each other), +// bring different results with offsets + +// sequence of equal intervals (2) + +Sieve.union_i(2, 6, 12, `48) + + +// asymmetric periods with same generators and offsets + +Sieve.union_oi(2, 0, 6, 5, 12, 1, `48).plot; + +// Under same assumption of generators with prime factors in common, a +// similar enrichment of symmetry types occurs with operators 'dif' and 'symdif' +// when offsets are used. In contrast to 'union' but as with 'dif' and 'symdif' +// without offsets, quasisymmteric periods occur. + + +// changing of symmetry types can also be done by looped sequencing of logical operations + +// symmetric period produced by operator 'union' + +a = PSVop_i([6, 5, 7], \u).iter.nextN(500).toSieve(\i, \i); + +a.plotCharacteristicPeriod; + + +// altered, here still symmetric with logical sequence + +a = PSVop_i([6, 5, 7], Pseq([\u, \d, \sd, \d], inf)).iter.nextN(500).toSieve(\i, \i); + +a.plotCharacteristicPeriod; +:: + + +SECTION::4) Troubleshooting + +subsection::4a) Critical inputs, limits + +code:: +// Due to the definition of sieves there are input combinations which +// might result in massive looping without any result. + +// Default settings are chosen in a way that this shouldn't result in hangs immediately, +// nevertheless it's the users's responsibility to choose meaningful input values. + + +// E.g. here we demand multiples of 3 which, at the same time, shouldn't be multiples of 3 ... +// The result nil is given not before the limit of 65536 is reached by summation, +// benchmarking indicates that. + +PSVdif([3, 3], 10).asStream.next + +{ PSVdif([3, 3], 10).asStream.next }.bench + +Sieve.dif(3, 3) + +{ Sieve.dif(3, 3) }.bench + + + +// similar here, no intersection + +PSVsect_o([3, 0, 3, 1], 10).asStream.next + +{ PSVsect_o([3, 0, 3, 1], 10).asStream.next }.bench + +Sieve.sect_o(3, 3) + +{ Sieve.sect_o(3, 3) }.bench + + +// Another critical operation is intersection with larger coprime numbers + +nthPrime(70) +-> 353 + +nthPrime(71) +-> 359 + + +a = PSVsect([353, 359]).asStream; + +// first intersection at 0, but no further one (below global limit 65536) + +a.nextN(2) + + +// you can set the limit, but it's a rather inefficient way to +// generate just a series of equal intervals ... + +a = PSVsect([353, 359], limit: 2 ** 30).asStream; + +a.nextN(20) + +{ a.nextN(20) }.bench + + +// The largest Integer with 32 bit is 2 ** 31 - 1. +// You can set 'limit' with Sieves and Psieve patterns (for instances and globally) +// up to 2 ** 31 - 1 - maxGeneratingInteger. +// This ensures that the threshold check doesn't exceed the Integer range. + + +// So if you're sure about useful inputs +// you can set a high global limit for calculus with large numbers and/or long streams + +Psieve.limit = 2 ** 31 - 536892 + +{ a = PSVunion([253630, 536891]).asStream.nextN(10000) }.bench + + +// reset global limit + +Psieve.limit = 65536 + + +// Note that Psieve is a bit more flexible as Sieve in this regard +// it allows to set summation limit and maxLength. + +Sieve.limit = 2 ** 31 - 536892 + +{ a = Sieve.union(253630, 536891) }.bench + +a.list.size + + +// reset global limit + +Sieve.limit = 65536 +:: + + +subsection::4b) Calculating least common multiples +anchor::4b:: +code:: +// For calculating period lengths the related operations of greatest common divisor +// and least common multiple are relevant (e.g. see [1]) + +// Up to SC 3.7.2 built-in method 'lcm' fails for large Integers, +// though this has been fixed in 3.8: + +lcm(248214, 1027542) + +-> 6696095 + + +// A prime factor analysis shows: + + +a = [248214, 1027542].collect(_.factors) + +-> [ [ 2, 3, 41, 1009 ], [ 2, 3, 41, 4177 ] ] + + +// Thus the result has to be + +2.0 * 3 * 41 * 1009 * 4177 + +-> 1036789878 + + +// Why is 2.0 needed above ? +// The result of an Integer multiplication is an Integer, +// thus crossing the int32 limit is silent and can easily be overlooked + +3768562 * 876876 + +-> 1731721688 + + +3768562.0 * 876876 + +-> 3304561572312 + + +// The methods lcmByFactors and lcmByGcd contain the relevant threshold checkes, +// they are much slower than 'lcm' but reliable also with large Integers. +// 'lcmByFactors' returns an array with lcm as first item, an array with prime factors +// of lcm as second item and an array of receiver's and all arguments' prime factors. +// Alternatively the least common multiple can be calculated +// via the greatest common divisor, this is done by method 'lcmByGcd' + +lcmByFactors(248214, 1027542) +lcmByGcd(248214, 1027542) + + +// if calculation exceeds the int32 limit a warning is given, the result is a float + +lcmByFactors(135630546, 429496729) +lcmByGcd(135630546, 429496729) + + +// also more args can be passed (all are integers < 2 * 31), +// as lcmByGcd uses gcd internally, this might fail with more than 2 +// large numbers, whereas lcmByFactors still finds the result + +lcmByGcd(135630546, 429496729, 610337457) +lcmByFactors(135630546, 429496729, 610337457) + +:: + +anchor::5:: +SECTION::5) Audio examples + +code:: + +// synthdefs to play with +( +SynthDef(\noise_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1| + var sig = { WhiteNoise.ar } ! 2; + sig = BPF.ar(sig, freq, rq) * + EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) * + (rq ** -1) * (250 / (freq ** 0.8)); + OffsetOut.ar(out, sig); +}).add; + +SynthDef(\sin_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; + + +SynthDef(\saw_grain, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1| + var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2; + sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2); + OffsetOut.ar(out, sig); +}).add; +) +:: + + +subsection::5a) Applying sieve intervals to (micro) rhythms + +code:: +( +// rhythm by sieve intervals + +~delta = 0.05; +~rhy = PSVunion_i([4, 6, 7]); + +p = Pbind( + \instrument, \noise_grain, + \dur, PL(\rhy) * PL(\delta), + \att, 0.01, + \rel, 0.05, + \amp, 0.1, + \midinote, Pwhite(50, 90), + \rq, 0.1 +).play +) + + +// change to micro rhythms + +( +~delta = 0.01; +~rhy = PSVsymdif_i([4, 6]); +) + +// test with different data + +~rhy = PSVsymdif_i([4, 6, 9]) + +~rhy = PSVsymdif_i([6, 9, 2]) + +~rhy = PSVsymdif_i([9, 2]) + +~rhy = PSVsymdif_i([3, 4]) + +~rhy = PSVsymdif_i([3, 4, 7]) + + +p.stop +:: + + +anchor::5b:: +subsection::5b) Sequentially generating new sieve patterns for rhythm and pitch + +code:: +( +// rhythm by sieve intervals + +~delta = 0.1; +~rhy = PSVunion_i([4, 6, 7]); + +// some more params for live change + +~rel = 0.05; +~midi = Pwhite(50, 90); +~rq = 0.1; + +q = Pbind( + \instrument, Prand([\noise_grain, \sin_grain], inf), + \dur, PL(\rhy) * PL(\delta), + \att, 0.005, + \rel, PL(\rel), + \amp, 0.1, + \midinote, PL(\midi), + \rq, PL(\rq) +).play +) + + +// turn to micro rhythm +( +~delta = 0.01; + +// instead of Pn + Plazy also Pspawner with method .seq could be used +~rhy = Pn(Plazy { + var r = { rrand(2, 30) } ! 2; + "rhythm generators: ".post; r.sort.postln; + PSVsymdif_i(r, rrand(20, 30)) +}); + +// numbers generated by PSVsymdif_i are used above and below a central pitch +~midi = Pn(Plazy { + var r = { rrand(2, 10) } ! 2; + "pitch generators: ".post; r.sort.postln; + PSVsymdif_i(r, rrand(10, 20)) * PLseq([1, -1]) + rrand(50, 95) +}); + + +// sequencing rq and release time +~rq = Pstutter(Pwhite(2, 5), PLseq([0.005, 0.01, 0.1])); + +~rel = Pstutter(Pwhite(2, 5), Pn(Pshuf([0.05, 0.1, 0.2]))); +) + +q.stop; +:: + + + +anchor::5c:: +subsection::5c) Sequencing instrumental variation with sieves + +code:: +( +~delta = 0.15; +~rhy = 1; +~rel = 0.04; +~midi = 70; +~instrument = \sin_grain; +~rq = 0.01; +~amp = 0.1; +~type = \note; + +r = Pbind( + \instrument, PL(\instrument), + \dur, PL(\rhy) * PL(\delta), + \att, 0.015 * Pwhite(0.8, 1.2), + \rel, PL(\rel), + \amp, PL(\amp), + \midinote, PL(\midi), + \rq, PL(\rq), + \type, PL(\type) +).play +) + +// define variation with sieve +( +~instrument = Pstutter( + PSVunion_i([5, 7, 13]), + PLseq([\saw_grain, \noise_grain, \sin_grain, \noise_grain]) +) +) + +// pitch sequence based on sieve +( +~midi = Pstutter(Pwhite(2, 5), PSVunion_i([10, 8, 17])) * PLseq([1, -1]) + 80; +~rel = 0.45; +~amp = 0.06; +) + +// transpositions +( +~midi = Pstutter(Pwhite(2, 5), PSVunion_i([10, 8, 17])) * PLseq([1, -1]) + + 80 + Pstutter(Pwhite(10, 20), Pwhite(-5, 5)); +) + +// microtonal transpositions and added fifths +( +~midi = Pstutter(Pwhite(2, 5), PSVunion_i([10, 8, 17])) * PLseq([1, -1]) + 80 + + Pstutter(Pwhite(10, 20), Pwhite(-10.0, 5)) + + Prand([0, [0, 7]], inf); +) + +// interfering curves generated by 'union' +( +~midi = [55, 62] + PSVunion_oi([55, 0, 57, 1, 58, 2, 59, 3]); + +~rel = Pstutter(Pwhite(2, 5), Pn(Pshuf([0.05, 0.05, 0.1, 0.5]))); +) + +r.stop +:: + + diff --git a/HelpSource/Tutorials/Smooth_Clipping_and_Folding.schelp b/HelpSource/Tutorials/Smooth_Clipping_and_Folding.schelp new file mode 100644 index 0000000..a8d38a2 --- /dev/null +++ b/HelpSource/Tutorials/Smooth_Clipping_and_Folding.schelp @@ -0,0 +1,207 @@ +TITLE::Smooth Clipping and Folding +summary::a suite of pseudo ugens for smooth clipping and folding +categories::Libraries>miSCellaneous>WaveFolding +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/SmoothClipS, Classes/SmoothClipQ, Classes/SmoothFoldS, Classes/SmoothFoldQ, Classes/SmoothFoldS2, Classes/SmoothFoldQ2 + +DESCRIPTION:: + +Wave folding is a synthesis technique from analog days, going back to Donald Buchla and the tradition of west coast synthesis. Smooth clipping and folding pseudo ugens from miSCellaneous lib come in variants which include quadratic and sinusoidal waveshaping and allow clipping and folding without aliasing. This can also be used for buffer scratching, a synthesis technique which I have been experimenting with recently with great fun. + + +EXAMPLES:: + +anchor::Ex. 1:: +subsection::Ex. 1: Different types of folding + +code:: + +// A typical usage is preamplifying a signal, here we start with a sine wave, compare plots + +{ + [ + // just smooth clipping + SmoothClipS.ar(SinOsc.ar(50) * 10), + + // folding with main lib's Fold ugen + Fold.ar(SinOsc.ar(50) * 10, -1, 1), + + // folding with rather low smoothing + // wave shaper is partiallly a sine wave + SmoothFoldS.ar(SinOsc.ar(50) * 10, smoothAmount: 0.3), + + // folding with maximum smoothing + // wave shaper is full sine wave + SmoothFoldS.ar(SinOsc.ar(50) * 10, smoothAmount: 1), + + // wave is folded back only to border ranges + SmoothFoldS.ar(SinOsc.ar(50) * 10, foldRange: 0.3), + + // folding with different sizes of border ranges + SmoothFoldS2.ar(SinOsc.ar(50) * 10, foldRangeLo: 0.5, foldRangeHi: 0.2) + ] +}.plot(1/50) + +:: + +image::attachments/Smooth_Clipping_and_Folding/fold_examples.png:: + + + +anchor::Ex. 2:: +subsection::Ex. 2: Generating rich spectra by folding sine waves + +code:: + +// Folding ugens do multichannel expansion, let two anticyclic sines control the fold range, +// control smoothing amount with MouseX + +( +x = { + var source = SinOsc.ar(50); + SmoothFoldS.ar(source, -0.1, 0.1, SinOsc.kr(0.05, [0, pi]).range(0.1, 1), MouseX.kr(0, 1)) +}.scope +) + +x.release + + +// Compare with the parabolic smoothing variant, the difference isn't great in this case + +( +x = { + var source = SinOsc.ar(50); + SmoothFoldQ.ar(source, -0.1, 0.1, SinOsc.kr(0.05, [0, pi]).range(0.1, 1), MouseX.kr(0, 1)) +}.scope +) + +x.release + + +// slow modulations of source frequency with independent LFOs + +( +x = { + var source = SinOsc.ar(50 * { LFDNoise3.kr(0.1).range(0.98, 1.02) } ! 2); + SmoothFoldS.ar(source, -0.1, 0.1, SinOsc.kr(0.05, [0, pi]).range(0.1, 1)) +}.scope +) + +x.release + + +// Adding more complexity by applying preamplification (causes more folding) and adding an offset, +// these operations are also L/R-independent + +( +x = { + var source = SinOsc.ar( + 50 * { LFDNoise3.kr(0.1).range(0.98, 1.02) } ! 2, + 0, + { LFDNoise3.kr(0.15).range(0.5, 3) } ! 2, + { LFDNoise3.kr(0.2).range(-2, 2) } ! 2 + ); + SmoothFoldS.ar(source, -0.1, 0.1, SinOsc.kr(0.05, [0, pi]).range(0.1, 1)) +}.scope +) + +x.release +:: + + + +anchor::Ex. 3:: +subsection::Ex. 3: Applying modulated folding to LFO sources + +code:: + +// the other way round, take a lfo source and modulate folding parameters, here the relative folding range + +( +x = { + var source = LFDNoise3.ar(0.3!2).range(0.5, 1); + SmoothFoldS.ar(source, -0.1, 0.1, SinOsc.ar([50, 50.1]).range(0.1, 1) ) +}.scope +) + +x.release + + +// modulating fold bounds + +( +x = { + var source = LFDNoise3.ar(0.3!2).range(0.5, 1); + var bounds = SinOsc.ar([50, 50.1]).range(0.02, 0.1); + SmoothFoldS.ar(source, bounds.neg, bounds) +}.scope +) + +x.release + + +// modulating bounds and range + +( +x = { + var source = LFDNoise3.ar(0.3!2).range(0.5, 1); + var range = SinOsc.ar([50, 50.1]).range(0.02, 0.1); + SmoothFoldS.ar(source, range.neg, range, SinOsc.ar([200, 200.1]).range(0.5, 1)) +}.scope +) + +x.release +:: + + + +anchor::Ex. 4:: +subsection::Ex. 4: Buffer scratching with folded signal as position control + +code:: + +// Interesting micro textures can be generated that way. +// Technically this is waveshaping with an audio buffer as transfer function and the folded signal as source. + +// compare with granulation, sound file from buffer granulation tutorial + +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); +// This searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or a removed sound file, pass the concerned path. + + +( +SynthDef(\bufScratchFold, { |bufnum = 0, globalFreq = 0.7, localOscSize = 0.01, foldRange = 0.28, + localFreq = 0.87, preAmp = 1.4, smoothAmount = 0.36| + var sig = BufRd.ar( + 1, + bufnum, + ( + // define global and local movement + LFDNoise3.ar(globalFreq).range(0.2, 0.7) + + SmoothFoldS.ar( + // adding space by decorrelating the local scratching / oscillation + LFTri.ar(localFreq * ({ LFDNoise3.ar(0.2).range(0.999, 1.001) } ! 2)) * preAmp, + foldRange: foldRange, + smoothAmount: smoothAmount + ) * localOscSize + ) * BufFrames.ir(bufnum) + ); + // as local oscillation can stick with positive or negative values, a dc leaker is recommended + Out.ar(0, LeakDC.ar(sig) * EnvGate.new) +}).add +) + +x = Synth(\bufScratchFold, [bufnum: b]) + +x.set(\preAmp, 5.4) +x.set(\foldRange, 0.08) +x.set(\localFreq, 0.5) +x.set(\localOscSize, 0.05) +x.set(\foldRange, 0.02) +x.set(\localFreq, 0.1) + +x.release + +:: + + diff --git a/HelpSource/Tutorials/VarGui_shortcut_builds.schelp b/HelpSource/Tutorials/VarGui_shortcut_builds.schelp new file mode 100644 index 0000000..f73be71 --- /dev/null +++ b/HelpSource/Tutorials/VarGui_shortcut_builds.schelp @@ -0,0 +1,907 @@ + + +TITLE::VarGui shortcut builds +summary::quick building of slider / player guis for synths and sequencing +categories:: Libraries>miSCellaneous>GUI +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/VarGui, Tutorials/HS_with_VarGui, Tutorials/PLx_suite, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation + +DESCRIPTION:: + +VarGui allows combined control of Synths, Pbinds / EventStreamPlayers and Tasks in one GUI. Many of its options though may not be relevant for most usages, also control specs were to be given directly (changed with v0.5). Shortcut build methods take advantage of SynthDef controlspec metadata, if defined (or global ControlSpecs), and autogenerate Pbinds for sequencing. Controls and Pbinds can be customized with options. +Main tools are the methods strong::sVarGui:: ( strong::s::ynth ), strong::pVarGui:: ( strong::p::attern ) and compound builders strong::spVarGui:: / strong::psVarGui::. Methods strong::sVarGui:: / strong::pVarGui::, applied to a single Symbol / String, produce a VarGui for one or more Synth(s) or Pbind(s) based on the referred single SynthDef ( link::#Ex.1a::, link::#Ex.1b::, link::#Ex.2a::, link::#Ex.2b:: ). Both methods also apply to collections of SynthDef Symbols / Strings ( link::#Ex.3a::, link::#Ex.3b:: ), whereas strong::spVarGui:: and strong::psVarGui::, builders of mixed Synth / Pbind VarGuis, expect collections of two items which may be Symbols / Strings or collections of Symbols / Strings ( link::#Ex.3c:: ). +Examples are using Patterns from dynamic scope link::Tutorials/PLx_suite:: for conveniently refering to environmental variables. + + +SECTION::1. Synth GUIs + +anchor::Ex.1a:: +subsection::Ex.1a: default instrument + +code:: + +( +s = Server.local; +Server.default = s; +s.boot; +) + + +// No metadata is defined with default instrument, per default global ControlSpecs are used. +// Here its definition from Event.sc: + +( +SynthDef(\default, { arg out=0, freq=440, amp=0.1, pan=0, gate=1; + var z; + z = LPF.ar( + Mix.new(VarSaw.ar(freq + [0, Rand(-0.4,0.0), Rand(0.0,0.4)], 0, 0.3)), + XLine.kr(Rand(4000,5000), Rand(2500,3200), 1) + ) * Linen.kr(gate, 0.01, 0.7, 0.3, 2); + OffsetOut.ar(out, Pan2.ar(z, pan, amp)); +}, [\ir]).storeOnce; +) + + +// With the next line you should get a VarGui with controls for freq, amp and pan. +// If not, global ControlSpecs and / or the default instrument have probably been changed, +// see below how to display and change global Specs. +// In global ControlSpecs amp defaults to 0, you have to raise in order +// to hear sound after pressing the player button + +\default.sVarGui.gui; + + +// automatic use of global ControlSpecs turned off, Synths with default args + +\default.sVarGui(useGlobalSpecs: false).gui; + + +// global specs, not all of them ControlSpecs, are stored in Spec.specs ... + +Spec.specs.associationsDo(_.postln); + + +// ... they can be added or replaced with .add +// for default instrument examples +// we replace \amp spec with a new ControlSpec with default 0.1 + +Spec.add(\amp, [0, 1, \lin, 0, 0.1]); + + +// generating a number of Synths, controls can be excluded, +// parallel slider dragging with Alt, parallel button action with Shift-click + +\default.sVarGui(exclude: \pan, num: 3).gui; + + + +// controls can be replaced ... + +\default.sVarGui(ctrReplace: [\freq, [400, 440, \exp, 0, 420]]).gui; + + +// ... and added, before or after the main block of controls from metadata or global ControlSpecs, +// this collection is empty here, so ctrBefore and ctrAfter are equivalent + +\default.sVarGui(useGlobalSpecs: false, ctrBefore: [\freq, [400, 440, \exp, 0, 420]]).gui; + + +// bad definition, \freq is already used as global ControlSpec + +\default.sVarGui(ctrBefore: [\freq, [300, 1000, \exp, 0, 440]]).gui; // WRONG + + +:: + +anchor::Ex.1b:: +subsection::Ex.1b: SynthDefs including metadata + + +SynthDef with defined controlspec metadata. The specs can be stored as ControlSpecs or Arrays, may also be Symbols / Strings refering to the global dictionary of Specs (Spec.specs). It's only convention to store specs under the key \specs, it can be anything else, and nothing prevents you from storing more than one dictionary of ControlSpecs per SynthDef - the VarGui shortcut methods have a metaKey arg (default: \specs) which determines where to lookup in the metadata dictionary. + + +code:: + +( +SynthDef(\buzz, { arg freq = 400, out, gate = 1, freqDev = 0.01, att = 0.01, dec = 0.01, susLevel = 0.5, rel = 1, + cutoff = #[1000, 1000, 1000], rq = 0.25, amp = 0.2, preAmp = 1, maxDelay = 0.03; + var src, srcFreq, sig; + sig = Mix.fill(3, { |i| + // chorus spread over three registers + srcFreq = (2 ** i) * freq * (1 + Rand(freqDev.neg, freqDev)) / 2; + src = RLPF.ar(Saw.ar(srcFreq, preAmp), cutoff[i], rq).softclip * amp * + EnvGen.kr(Env.adsr(att, dec, susLevel, rel), gate, doneAction: 2); + // random spatialization (for sequencing) by L/R delay + [DelayL.ar(src, maxDelay, Rand(0, maxDelay)), DelayL.ar(src, maxDelay, Rand(0, maxDelay))] + }) ; + // ensure sample accurate output for events of short durations + OffsetOut.ar(out, sig); + + }, metadata: ( + specs: ( + freq: [20, 5000, \exp, 0, 80], + gate: [0, 1.0, \lin, 0, 1], + freqDev: [0.0, 0.02, \lin, 0, 0.002], + att: [0.01, 0.2, \lin, 0, 0.01], + dec: [0.01, 0.2, \lin, 0, 0.01], + susLevel: [0.01, 1, \lin, 0, 0.5], + rel: [0.01, 2, \exp, 0, 1], + cutoff: [200, 5000, \exp, 0, 1000], + rq: [0.1, 0.9,\lin, 0, 0.2], + amp: [0.0, 1, \lin, 0, 0.2], + preAmp: [0.9, 5, \lin, 0, 1], + maxDelay: [0, 0.01, \lin, 0, 0.002] + ), + // doesn't matter for VarGui if spec defined as Array or ControlSpec, + // an arbitrary number of arbitrarily named alternative Spec Events may be passed, + // for non-sequencing use adsr params may be dropped + basicSpecs: ( + freq: [20, 5000, \exp, 0, 80], + gate: [0, 1.0, \lin, 0, 1], + cutoff: [200, 5000, \exp, 0, 1000], + rq: [0.1, 0.9,\lin, 0, 0.2], + amp: [0.0, 1, \lin, 0, 0.2], + preAmp: [0.9, 5, \lin, 0, 1] + ) + ) +).add; +) + + +// here metadata includes a gate spec with default 1 +// metadata has priority over global ControlSpecs +// metaKey \specs used per default + +\buzz.sVarGui.gui; + + +// use gui options for better arrangement + +\buzz.sVarGui(num: 4).gui(tryColumnNum: 2, sliderHeight: 16); + + +// alternative metadata, metaKey to be passed, +// mostly sounds different from last version +// because of fixed max chorus-width param freqDev + +\buzz.sVarGui(num: 4, metaKey: \basicSpecs).gui(tryColumnNum: 2); + +:: + + + +SECTION::2. Sequencing GUIs + +Controlling Pbinds / EventStreamPlayers with VarGui is based on (a) the setting of environmental variables and (b) the proper definition of Pbinds with Patterns of link::Tutorials/PLx_suite:: or Patterns with functional code ( link::Tutorials/Event_patterns_and_Functions:: ) so that EventStreamPlayers get the updated variable values in the following events. + +Basic example of a Pbind to get envir values: + +code:: +p = Pbind( + \dur, Pfunc { ~dur } * Pseq([2,1,1], inf), + \legato, Pfunc { ~legato }, + \amp, Pfunc { ~amp } +) + +// or + +p = Pbind( + \dur, PL(\dur) * Pseq([2,1,1], inf), + \legato, PL(\legato), + \amp, PL(\amp) +) + +:: + +Especially when based on a SynthDef with a large number of args writing a Pbind can be lengthy, moreover redundant if there are many pbind pairs of the form + +code:: + +[ aSymbol, Pfunc { ~aSymbol } ] + +:: + +The pVarGui method is going the opposite way: Pbind pairs get this most simple form for all args with metadata or global ControlSpecs defined, though it's possible to exclude keys and add respectively replace customized pairs before and after the automatically generated ones. All you need is a SynthDef that is suited for Pbind sequencing (known to SynthDescLib by being strong::add::ed, properly defined Envelopes). +warning:: As a lot of the underlying Pbind structure (by default all of it !) is hidden, it's possible to call pVarGui without knowing anything about Patterns. Nevertheless it's highly recommended to be aware of the mechanisms of Pbind for applying useful sequencing customizations and having a look at the fully written Pbind examples in link::Classes/VarGui::. Clearly: adding, replacing and excluding Pbind pairs without seeing the resulting code is a possible source of error, so if using VarGui this way better start slowly and go on stepwise. With the post option you can print out the ordered list of pairs. +:: + +anchor::Ex.2a:: +subsection::Ex.2a: default instrument + +code:: + +// for default instrument examples +// we replace \amp spec with a new ControlSpec with default 0.1 + +Spec.add(\amp, [0, 1, \lin, 0, 0.1]); + + +// Most simple example to get a VarGui controlling a Pbind / EventStreamPlayer with +// default instrument, control of duration and legato is automatically added. +// As with the sVarGui method synth controls are added when found in metadata definition +// or global ControlSpecs, different from sVarGui \gate is excluded by default. + +\default.pVarGui.gui; + + +// adding a pattern pair - if it contains one of the keys \note, \midinote or \degree +// the freq pair is removed automatically, see posted Pbind pairs + +\default.pVarGui(pAfter: [\degree, Pwhite(0,5)], post: true).gui; + + +// pattern pair replacement as in basic example above + +\default.pVarGui(pReplace: [\dur, PL(\dur) * Prand([2,1,1], inf)] ).gui; + + +// adding a pattern pair after the main block of pairs, +// same effect as before + +\default.pVarGui(pAfter: [\dur, Pkey(\dur) * Prand([2,1,1], inf)], post: true).gui; + + +// Internally a Pbind of this form had been generated (see post window), +// in each Event the second Stream with key \dur gets the value from the first, +// overrides it in the Event. + +Pbind( + // pBefore pairs (optinally passed to pVarGui) + ... + + // these are always placed before the main block + \instrument, \default, + \dur, Pfunc { ~dur }, + \legato, Pfunc { ~legato }, + + // main block of pairs corresponding to args for which + // spec definition was found (if not excluded or replaced), + // ordered like in SynthDef + + \freq, Pfunc { ~freq }, + \amp, Pfunc { ~amp }, + \pan, Pfunc { ~pan }, + + // pAfter pairs (optinally passed to pVarGui) + \dur, Pkey(\dur) * Prand([2,1,1], inf) +) + + +// Additional arrayed control for simple step sequencing with random init values. +// The PLseq degree pattern defaults to repeats = inf and envir = \current. +// It would take values from the current Environment of streamification. + +// Here playing and setting the variables will +// happen in a new Environment generated by the VarGui. + +( +\default.pVarGui( + ctrBefore: [\a, { [0, 5, \lin, 1, rrand(0,6)] } ! 8 ], + pBefore: [\degree, PLseq(\a) ] +).gui; +) + +// A number of Pbinds and control sets can be generated with the num arg, +// control and pattern customization args take Functions (optionally of indices) +// for expansion. + +// This a potentially powerful feature, also suited for producing a real mess ! +// Especially think of possibly inappropriate ranges ! +// If you are unsure try out evaluating code of the form ... + +{ |i| ... } ! n + +// ... separately before plugging in the shortcut methods. + + +// Here the Function passed to ctrBefore has no index arg, +// it just produces 4 different tuples of +// init values for the step sequencers, + +// the pBefore Function determines 4 different octave registers +// quant arg ensures staying in sync based on the passed rhythmic patterns + +( +\default.pVarGui( + /* ctrBefore: */ { [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ] }, + /* ctrReplace: */ [\dur, [0.2, 0.6, \lin, 0.2, 0.2]], + pBefore: { |i| [\degree, PLseq(\a), \octave, i+4] }, + num: 4, + quant: 0.2, + post: true +).gui(tryColumnNum: 2); +) + +:: + +anchor::Ex.2b:: +subsection::Ex.2b: SynthDefs including metadata + + +Automatic Pbind generation in VarGui shortcut builds is especially saving typing if SynthDefs have a lot of args and controlspec metadata defined. + +code:: + +// most simple, controls are built for all args with metadata defined +// arrayed args are detected and controls are established for a corresponding array +// SynthDef from above + +\buzz.pVarGui.gui; + + +// use the exclude option to exclude from automatically generated controls and Pbind pairs +// you would want to do that for a static value or sequencing not to be gui-controlled + + +// always SynthDef default value 0.01 for attack time + +\buzz.pVarGui(exclude: [\att], post: true).gui; + + +// sequencing without control +// RLP filter rq excluded from gui control and automatic Pbind pair generation, +// though added to Pbind pairs again as passed to pBefore (could also be pAfter), +// here PLseq taken as it defaults to inf + +\buzz.pVarGui(exclude: [\rq], pBefore: [\rq, PLseq([ 0.05, 0.8, 0.8, 0.8]) ], post: true).gui; + + +// same effect on sound, but we have a useless rq control + +\buzz.pVarGui(pAfter: [\rq, PLseq([ 0.05, 0.8, 0.8, 0.8]) ], post: true).gui; +\buzz.pVarGui(pReplace: [\rq, PLseq([ 0.05, 0.8, 0.8, 0.8]) ], post: true).gui; + + +// here the pBefore arg is useless as the Pfunc that takes controlled values +// comes after in execution and will overwrite the value per Event + +\buzz.pVarGui(pBefore: [\rq, PLseq([ 0.05, 0.8, 0.8, 0.8]) ], post: true).gui; + + +// establishing a cutoff freq rhythm for all three layers +// remember that in Pbinds arrayed args must be distinguished from the syntax for generating +// multiple synths per Event, this can be achieved by wrapping in an additional array + +\buzz.pVarGui(pReplace: [\cutoff, Pfunc { [~cutoff] } * PLseq([3,1,1]) ]).gui; + + +// step sequencer for quartertone bassline + +( +\buzz.pVarGui( + ctrAfter: [\a, { var lo = 25, hi = 50; [lo, hi, \lin, 0.5, rrand(lo*2, hi*2) / 2 ] } ! 8 ], + pBefore: [\midinote, PLseq(\a)], + post: true +).gui; +) + + + +// step sequencer with two voices +// by passing functions different control ranges and registers are established +// both lines are in a quartertone scale but shifted by an eigthtone +// lines itself can be shifted by an offset ~add + +// modifier keys can be used to perform parallel slider or button actions +// alt for parallel slider movement and shift for parallel button actions (VarGui) + + +( +\buzz.pVarGui( + ctrReplace: [\dur, [0.1, 0.2, \lin, 0.1, 0.2], \amp, [0.0, 0.5, \lin, 0, 0.3]], + ctrAfter: { |i| var d = 0.25; + [\add, [-12, 12, \lin, 0.5, 0], + \a, { [(i*d), 11.5 + (i*d), \lin, 0.25, rrand(0, 23) / 2 + (i*d)] } ! 8 + ] }, + pBefore: { |i| [ + \note, PLseq(\a) + PL(\add), + \octave, 3 + (i*4) + Pwhite(0,1) + ] }, + quant: 0.2, + post: true, + num: 2 +).gui(tryColumnNum: 2); +) + + +:: + + +anchor::Ex.2c:: +subsection::Ex.2c: Combination of parameter control and pattern exchange (JITLib) + +code:: + +( +\buzz.pVarGui( + ctrReplace: [\freq, [20, 1000, \exp, 0, 80]], + pReplace: [ + \cutoff, Pdefn(\cutoff, Pseq([3, 1, 1, 1, 1], inf)) * Pfunc { [~cutoff] }, + \freq, Pdefn(\freq, Pseq([1, 1, 1.3, 1.2, 1], inf)) * Pfunc { ~freq } + ] +).gui; +) + +// start from gui and eval pattern replacements before playing around with sliders + +Pdefn(\freq, Pseq([1, 1, 1.7, 1.3, 1.2], inf)); + +Pdefn(\cutoff, Pseq([2, 1, 1], inf)); + +Pdefn(\freq, 1); + +Pdefn(\cutoff, Pseq([1, 1, 1.7, 1.3, 1.2], inf)); + +:: + +anchor::Ex.2d:: +subsection::Ex.2d: Combination of parameter control and pattern exchange (PLx) + +code:: + +// above done with PL +// note that PLs are taking envir variables from the current Environment by default, +// this is ok for the variables set by VarGui (freq) in its Environment, +// but for PLs to take values from elsewhere we must give the Environment explicitely. +// Suppose we are in topEnvironment here: + +// As PL defaults to repeats = inf, Pseq can be taken without a repeats arg +// for endless looping + +( +~co = Pseq([3, 1, 1, 1, 1]); +~fr = Pseq([1, 1, 1.3, 1.2, 1]); + +\buzz.pVarGui( + ctrReplace: [\freq, [20, 1000, \exp, 0, 80]], + pReplace: [ + \cutoff, PL(\co, envir: \top) * Pfunc { [~cutoff] }, + \freq, PL(\fr, envir: \top) * PL(\freq) + ] +).gui; +) + +// start from gui and eval pattern replacements before playing around with sliders + +~fr = Pseq([1, 1, 1.7, 1.3, 1.2]); + +~co = Pseq([2, 1, 1]); + +~fr = 1; + +~co = Pseq([1, 2, 1.7, 1.3, 1.2]); + + +////////////////////////////////// + + +// alternative version with a PLseq placeholder + +( +~co = [3, 1, 1, 1, 1]; +~fr = [1, 1, 1.3, 1.2, 1]; + +\buzz.pVarGui( + ctrReplace: [\freq, [20, 1000, \exp, 0, 80]], + pReplace: [ + \cutoff, PLseq(\co, envir: \top) * Pfunc { [~cutoff] }, + \freq, PLseq(\fr, envir: \top) * PL(\freq) + ] +).gui; +) + +// start from gui and eval pattern replacements before playing around with sliders + +~fr = [1, 1, 1.7, 1.3, 1.2]; + +~co = [2, 1, 1]; + +~fr = [1]; + +~co = [1, 2, 1.7, 1.3, 1.2]; + +:: + + +SECTION::3. Mixed GUIs + +To control Synths or Pbinds / EventStreamPlayers derived from more than one SynthDef sVarGui and pVarGui can be applied to collections. + +anchor::Ex.3a:: +subsection::Ex.3a: Synths from different SynthDefs + +code:: + +// for default instrument examples +// we replace \amp spec with a new ControlSpec with default 0.1 + +Spec.add(\amp, [0, 1, \lin, 0, 0.1]); + + +// different synths + +[\buzz, \default].sVarGui.gui + + +// in basic form this works equivalently ... + +VarGui(synth: [\buzz, \default]).gui; + + +// ... but for customization the shortcut method sVarGui is more convenient, +// otherwise you'd have to pass or generate controls explicitely (5b). +// sVarGui and pVarGui, applied to SequenceableCollections, expect Dictionaries +// of pairs argName / arg, so you can pass Events. +// Their number must equal the collection's size. + +( +[\buzz, \default].sVarGui( + (ctrReplace: [\freq, [97, 103, \exp, 0, 99]]), + (ctrReplace: [\freq, [194, 206, \exp, 0, 201]]) +).gui; +) + +// more synths + +( +[\default, \buzz].sVarGui( + (ctrReplace: { [\freq, [194, 206, \exp, 0, rrand(194.0, 206)]] }, num: 4), + (ctrReplace: [\freq, [97, 103, \exp, 0, 99]]) +).gui(tryColumnNum: 2, allowSynthBreak: false); +) + +:: + + +anchor::Ex.3b:: +subsection::Ex.3b: Pbinds from different SynthDefs + +code:: + +// overtones, deviations of pitch and pulsation + +( +[\default, \buzz].pVarGui( + (ctrReplace: { |i| var f = (2*i + 1) * 100, d = 0.2, lo = 0.99, hi = 1.01; + [\freq, [f*lo, f*hi, 0, 0, f*rrand(lo,hi)], \dur, [d*lo, d*hi, 0, 0, d*rrand(lo,hi)] ] }, + post: true, + num: 4), + (ctrReplace: [\freq, [97, 103, \exp, 0, 99], \dur, [0.195, 0.205, 0, 0, 0.2] ]) +).gui(tryColumnNum: 2, allowEnvirBreak: false) +) + + +// step sequencer + +( +[\buzz, \default].pVarGui(( + ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ], + ctrReplace: [\dur, [0.2, 0.4, \lin, 0.2, 0.2]], + pBefore: [\degree, PLseq(\a), \octave, 3], + quant: 0.2 + ),( + ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 4 ], + ctrReplace: [\dur, [0.1, 0.3, \lin, 0.1, 0.2]], + pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + PLseq(\a), + \octave, Pwhite(5,7) ], + quant: 0.2 + ) +).gui(tryColumnNum: 2, allowEnvirBreak: false); +) + +:: + +anchor::Ex.3c:: +subsection::Ex.3c: Synths and Pbinds + + +code:: +// Pbind granulation with \buzz, controls and pbind pairs adapted and added, +// psVarGui (pbind input first) and spVarGui (synth input first) +// apply to collections of two items. + +// If only one type of SynthDef is to be used for Pbind(s) or Synth(s) +// it may be a plain Symbol / String, the corresponding arg tuples may +// be given as plain (not collected) Dictionaries + +// Keep in mind that nevertheless one Dictionary may produce +// more than one Synth (Pbind) + +( +[\buzz, \default].psVarGui( + ( + ctrReplace: [\dur, r = [0.01, 0.05, \lin, 0, 0.03], + \rel, r, + \freq, [100, 1000, \lin, 0, 100], + \amp, [0, 0.5, \lin, 0, 0.15], + \legato, [0.1, 2, \lin, 0, 0.5], + \freqDev, [0, 0.2, \lin, 0, 0.01]], + ctrBefore: [\durDev, [0, 1, \lin, 0, 0.01]], + pAfter: [\dur, Pkey(\dur) * Pfunc { x = 1 + ~durDev; rrand(1/x,x) }], + post: true + ),( + ctrReplace: { [\dur, [0.01, 0.05, \lin, 0, 0.03], + \freq, [100, 1000, \lin, 0, rrand(100, 1000)]] }, + num: 4 + ) +).gui(tryColumnNum: 2, allowEnvirBreak: false); +) + + +// If more than one type of SynthDef is to be used for Pbind(s) or Synth(s) +// Symbols / Strings must be collected, also the corresponding Dictionaries + +// spVarGui just takes input in reversed order (synth first). +// This is independent from representation in the GUI +// which can be determined by args sliderPriority (\var or \synth) +// and playerPriority (\stream or \synth) + +( +[\default, [\buzz, \default]].spVarGui( + ( + ctrReplace: { [\dur, [0.01, 0.05, \lin, 0, 0.03], + \freq, [100, 1000, \lin, 0, rrand(100, 1000)]] }, + num: 4 + ),[ + ( + ctrReplace: [\dur, r = [0.01, 0.05, \lin, 0, 0.03], + \rel, r, + \freq, [100, 1000, \lin, 0, 100], + \amp, [0, 0.5, \lin, 0, 0.15], + \legato, [0.1, 2, \lin, 0, 0.5], + \freqDev, [0, 0.2, \lin, 0, 0.01]], + ctrBefore: [\durDev, [0, 1, \lin, 0, 0.01]], + pAfter: [\dur, Pkey(\dur) * Pfunc { var x = 1 + ~durDev; rrand(1/x,x) }], + post: true + ),( + ctrReplace: [\dur, r = [0.01, 0.05, \lin, 0, 0.03], + \freq, [100, 1000, \lin, 0, 100], + \amp, [0, 0.5, \lin, 0, 0.15], + \legato, [0.1, 2, \lin, 0, 0.5]], + ctrBefore: [\durDev, [0, 1, \lin, 0, 0.01], + \freqDev, [0, 0.2, \lin, 0, 0.01]], + pAfter: [\dur, Pkey(\dur) * Pfunc { var x = 1 + ~durDev; rrand(1/x,x) }, + \freq, Pkey(\freq) * Pfunc { var x = 1 + ~freqDev; rrand(1/x,x) }], + post: true + ) + ] +).gui(tryColumnNum: 2, allowEnvirBreak: false, sliderPriority: \synth, playerPriority: \synth); +) + +:: + +SECTION::4. Differences between shortcut builds and direct VarGui instantiation + + +list:: +## Shortcut methods apply to Symbols / Strings or collections thereof for Synth and Pbind generation and control. Symbols / Strings refer to the corresponding SynthDefs, known to SynthDesLib.global. For passing Synths, nodeIDs, EventStreamPlayers, also Task Functions and Tasks, you would have to call VarGui directly. +## Shortcuts and also direct instantiation (not before v0.5) are using SynthDef metadata and / or global ControlSpecs. Though customization options for specs (exclude, add, replace) are a main feature of the shortcut methods. As a compromise spec- and pbindmaker methods with these options may be plugged into direct instantiation ( link::#5b:: ). +## The pVarGui method invokes automatic Pbind generation with customization options for Pbind pairs (exclude, pBefore, pAfter, pReplace), VarGui expects Pbinds to be passed directly. As a compromise a pbindmaker method with these options may be plugged into direct instantiation ( link::#5b:: ). +## In shortcut methods the strong::num:: arg causes coordinated expansion, all other args (except strong::server::) may optionally be given as Functions of indices. With VarGui input you'd have to pass lengthy nested Arrays or write something like { |i| ... } ! n for every arg explicitely, as sizes of input must match. As a compromise spec- and pbindmaker methods with strong::num:: arg may be plugged into direct instantiation ( link::#5b:: ). +## Reordering / interleaving of controls of different Environments / Synths ( link::Classes/VarGui#Ex. 5a:: and link::Classes/VarGui#Ex. 5b:: ) is not possible with shortcut methods. However this seems to be a quite special feature, with shortcut methods ordering of Synths / Pbinds is primarily defined by order of args, control customization options allow further modifications of order. Finally choosing sliderPriority (\synth or \var first) is a pure gui method option and applies to both ways of VarGui building. +:: + +SECTION::5. Shortcut method specifictions + +Shortcut methods also take Functions, that should return args in a valid form. This can be used for defining controls in different ranges in combination with strong::num::. See examples above. + + +anchor::5a:: +subsection::5a: core shortcut methods + +link::Classes/Symbol#-sVarGui:: + +link::Classes/SequenceableCollection#-sVarGui:: + +link::Classes/Symbol#-pVarGui:: + +link::Classes/SequenceableCollection#-pVarGui:: + + +note:: +The following two methods are equivalent, just take opposite arg order. This is independent of arrangement, which can be determined by gui args sliderPriority (\var or \synth) and playerPriority (\stream or \synth). +:: + +link::Classes/SequenceableCollection#-spVarGui:: + +link::Classes/SequenceableCollection#-psVarGui:: + +anchor::5b:: +subsection::5b: spec- and pbindmaker methods + +In most cases you won't need this, except for reloading customized sequencing VarGuis ( link::#6:: ). For special arrangements methods for making specdata and Pbinds of appropriate Pfuncs can be used for customizing control with direct VarGui instantiation. These methods take args in the same way as sVarGui and pVarGui. + + +link::Classes/Symbol#-sVarGuiSpecs:: + +link::Classes/Symbol#-pVarGuiSpecs:: + +link::Classes/Symbol#-pfuncPbinds:: + +code:: + + +// sVarGuiSpecs returns a list of spec pair lists of the form [\key, specArray, ...] +// derived from SynthDef metadata / global ControlSpecs, +// controls can be excluded, added and replaced + +( +VarGui( + synth: [\default, \buzz], + synthCtr: \default.sVarGuiSpecs ++ + \buzz.sVarGuiSpecs(ctrReplace: [\amp, [0, 0.5, 0, 0, 0.1]]) +).gui; +) + +// shorter + +( +[\default, \buzz].sVarGui( + (), + (ctrReplace: [\amp, [0, 0.5, 0, 0, 0.1]]) +).gui; +) + + +// as with sVarGui arguments can be functions of indices + +( +VarGui( + synth: \default!4 ++ \buzz, + synthCtr: + \default.sVarGuiSpecs(num: 4, + ctrReplace: { |i| var x = i*100 + 400; [\freq, [x, x*1.1, 0, 0, x]] }) ++ + \buzz.sVarGuiSpecs(ctrReplace: [\amp, [0, 0.5, 0, 0, 0.2]]) +).gui(tryColumnNum: 2, allowSynthBreak: false); +) + +// shorter + +( +[\default, \buzz].sVarGui( + (num: 4, ctrReplace: { |i| var x = i*100 + 400; [\freq, [x, x*1.1, 0, 0, x]] }), + (ctrReplace: [\amp, [0, 0.5, 0, 0, 0.2]]) +).gui(tryColumnNum: 2, allowSynthBreak: false); +) + + + +// pVarGuiSpecs also returns a list of spec pair lists, +// just adds specs for dur and legato by default, +// pfuncPbinds returns appropriate Pbinds of Pfuncs +// additional pairs can be added + +// however, compared to pVarGui, more coordination is the user's responsibility: +// specmaker and pbindmaker don't know about each other +// useless control \freq excluded explicitely here + +( +VarGui( + varCtr: + \buzz.pVarGuiSpecs(ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ], + ctrReplace: [\dur, [0.2, 0.4, \lin, 0.2, 0.2]], exclude: \freq) ++ + \default.pVarGuiSpecs(ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 4 ], + ctrReplace: [\dur, [0.1, 0.3, \lin, 0.1, 0.2]], exclude: \freq), + stream: + \buzz.pfuncPbinds(pBefore: [\degree, PLseq(\a), \octave, 3]) ++ + \default.pfuncPbinds(pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + PLseq(\a), + \octave, Pwhite(5,7)], post: true), + quant: 0.2 +).gui(tryColumnNum: 2, allowEnvirBreak: false); +) + +// shorter + +( +[\buzz, \default].pVarGui(( + ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ], + ctrReplace: [\dur, [0.2, 0.4, \lin, 0.2, 0.2]], + pBefore: [\degree, PLseq(\a), \octave, 3], + quant: 0.2 + ),( + ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 4 ], + ctrReplace: [\dur, [0.1, 0.3, \lin, 0.1, 0.2]], + pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + PLseq(\a), + \octave, Pwhite(5,7) ], + quant: 0.2 + ) +).gui(tryColumnNum: 2, allowEnvirBreak: false); +) + +:: + + +anchor::6:: +SECTION::6. Save and load + +A VarGui's slider state can be saved via save button and dialog. Loading is straightforward with Synths. You just have to give the correct path and synth arg (which maybe has to be an array). + +code:: + +// change slider positions and save + +\buzz.sVarGui.gui; + +// reload + +VarGui.load("Path_To_XY", "XY", synth: \buzz).gui; + + +// change slider positions and save + +\buzz.sVarGui(num: 4, metaKey: \basicSpecs).gui(tryColumnNum: 2); + +// reload, synth arg must have corresponding size + +VarGui.load("Path_To_XY", "XY", synth: \buzz ! 4).gui(tryColumnNum: 2); + +:: + + +VarGui only saves slider states, not the Pbind structure (would open a can of worms). So if you had a customized Pbind in the original setup you'd have to pass an appropriate Pbind together with load. This can also be a Pbind other than in the original setup. But if you want to restore you can nearly copy the input of the original cutomization into the pbindmaker method pfuncPbinds ( link::#5b:: ). + + +code:: + +// with no customization save and load also works straightforward +// change slider positions and save + +\buzz.pVarGui.gui; + +// standard pbind is (re-)autogenerated and fits the variables to be set + +VarGui.load("Path_To_XY", "XY", stream: \buzz).gui; + + +// only controls are customized, save and load also works straightforward +// change slider positions, save and reload + +( +[\default, \buzz].pVarGui( + (ctrReplace: { |i| var f = (2*i + 1) * 100, d = 0.2, lo = 0.99, hi = 1.01; + [\freq, [f*lo, f*hi, 0, 0, f*rrand(lo,hi)], \dur, [d*lo, d*hi, 0, 0, d*rrand(lo,hi)] ] }, + post: true, + num: 4), + (ctrReplace: [\freq, [97, 103, \exp, 0, 99], \dur, [0.195, 0.205, 0, 0, 0.2] ]) +).gui(tryColumnNum: 2, allowEnvirBreak: false) +) + +( +VarGui.load("Path_To_XY", "XY", stream: \default ! 4 ++ \buzz) + .gui(tryColumnNum: 2, allowEnvirBreak: false) +) + + +// change slider positions and save + +( +[\buzz, \default].pVarGui(( + ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 8 ], + ctrReplace: [\dur, [0.2, 0.4, \lin, 0.2, 0.2]], + pBefore: [\degree, PLseq(\a), \octave, 3], + quant: 0.2 + ),( + ctrBefore: [\a, { [0, 6, \lin, 1, rrand(0,6) ] } ! 4 ], + ctrReplace: [\dur, [0.1, 0.3, \lin, 0.1, 0.2]], + pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + PLseq(\a), + \octave, Pwhite(5,7) ], + quant: 0.2 + ) +).gui(tryColumnNum: 2, allowEnvirBreak: false); +) + +// restoring pbind structure, note that pfuncPbinds always returns a collection +// and quant arg is separate in VarGui.new and VarGui.load + +( +VarGui.load("Path_To_XY", "XY", + stream: + \buzz.pfuncPbinds(pBefore: [\degree, PLseq(\a), \octave, 3]) ++ + \default.pfuncPbinds(pBefore: [\degree, Pfunc { [0, [0,4], [0,5]].choose } + + PLseq(\a), \octave, Pwhite(5,7) ]), + quant: 0.2 +).gui(tryColumnNum: 2, allowEnvirBreak: false) +) + +:: + + diff --git a/HelpSource/Tutorials/attachments/Idev_suite/Idev_scheme_3.png b/HelpSource/Tutorials/attachments/Idev_suite/Idev_scheme_3.png new file mode 100644 index 0000000..2fb4ed6 Binary files /dev/null and b/HelpSource/Tutorials/attachments/Idev_suite/Idev_scheme_3.png differ diff --git a/HelpSource/Tutorials/attachments/Smooth_Clipping_and_Folding/fold_examples.png b/HelpSource/Tutorials/attachments/Smooth_Clipping_and_Folding/fold_examples.png new file mode 100644 index 0000000..4de05c4 Binary files /dev/null and b/HelpSource/Tutorials/attachments/Smooth_Clipping_and_Folding/fold_examples.png differ diff --git a/HelpSource/Tutorials/attachments/enum/graph.png b/HelpSource/Tutorials/attachments/enum/graph.png new file mode 100644 index 0000000..121e8d5 Binary files /dev/null and b/HelpSource/Tutorials/attachments/enum/graph.png differ diff --git a/HelpSource/Tutorials/enum.schelp b/HelpSource/Tutorials/enum.schelp new file mode 100644 index 0000000..faeeaad --- /dev/null +++ b/HelpSource/Tutorials/enum.schelp @@ -0,0 +1,323 @@ + + +TITLE::enum +summary::general enumeration tool, can be used for a variety of combinatorial problems +categories:: Libraries>miSCellaneous>General Tutorials +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous + +DESCRIPTION:: + +Method enum implements a basic backtracking search suited for a number of counting and optimization problems. For specification of search criteria a boolean-valued Function has to be passed. + +note::The method applies to Integers indicating the recursion depth. +Due to the nature of combinatorial problems with an often rapid growth of solutions +and/or enumeration steps with increase of size, it is recommended to start +examples with low numbers to avoid hangs. +:: + +INSTANCEMETHODS:: + + +method::enum +Returns solutions of the problem, which is defined by strong::function::, +as an Array of SequenceableCollections (size = receiver). + +argument::pool + +SequenceableCollection of items to be considered for possible solutions. If type equals 0 the same pool is taken for all indices of possible solutions, if type equals 1 a SequenceableCollection of pools might be passed. The existence of an additional strong::type:: arg is necessary as it might also be desirable to consider SequenceableCollections as single items of possible solutions. + +argument::function + +Boolean-valued Function to be evaluated at strong::currentIndex::. For many applications it is not necessary to evaluate at index 0 (so per default strong::evalAtZero:: set to false), the Function is not evaluated and the item is supposed to be considered as first element of a possible solution. + +From current state the Function is passed the following args to specify search: + +list:: +## strong::item:: - Current item to be checked. + +## strong::currentIndex:: - Current enumeration level, +between 1 (resp. 0 in case strong::evalAtZero:: set to true) and receiver - 1. + +## strong::currentCol:: - Contains current collection of items already chosen +at indices up to strong::currentIndex:: - 1, for efficiency reasons +length of this collection equals receiver and items indexed at +current or higher enumeration level might stem from earlier enumeration steps. + +## strong::indexCol:: - Current collection of indices (of items from strong::pool::) already chosen, +for efficiency reasons length of this collection equals receiver and indices at +current or higher enumeration level might stem from earlier enumeration steps. +:: + +argument::evalAtZero + +Boolean. Determines if strong::function:: will be evaluated at index 0. Defaults to false. + + +argument::type + +Must be 0 or 1. Determines if strong::pool:: should be taken for all items (0, default) or specified per index (1). + +argument::order + +Boolean. Determines if search should follow order of items given in strong::pool:: or a search order is randomly chosen. Defaults to true. For search of a single random solution one would set strong::order:: to false and strong::maxNum:: to 1. + +argument::maxNum + +Integer. Maximum number of solutions to be searched for. Defaults to inf. + + + +SECTION::Example 1: Basic enumerations, Subsets + +code:: +// Listing all tuples from a given collection. +// Note that this kind of complete enumeration +// can be done with method allTuples more efficiently. + + +3.enum([1,2]) + +-> [ [ 1, 1, 1 ], [ 1, 1, 2 ], [ 1, 2, 1 ], [ 1, 2, 2 ], + [ 2, 1, 1 ], [ 2, 1, 2 ], [ 2, 2, 1 ], [ 2, 2, 2 ] ] + + + +// type 1 for specified pool(s) +// receiver must equal size of passed pools + +3.enum([[1,2], [-1,-2], [\a,\b]], type: 1) + +-> [ [ 1, -1, a ], [ 1, -1, b ], [ 1, -2, a ], [ 1, -2, b ], + [ 2, -1, a ], [ 2, -1, b ], [ 2, -2, a ], [ 2, -2, b ] ] + + + +// strictly monotone tuples +// note that function is evaluated only for i > 0, +// so no problem to write i-1 + +3.enum((1..4), { |x,i,col| x > col[i-1] }); + +-> [ [ 1, 2, 3 ], [ 1, 2, 4 ], [ 1, 3, 4 ], [ 2, 3, 4 ] ] + + +// Above is equivalent to the task of finding all +// k-subsets of a given set of n elements. +// The results are lexically ordered. +// For an arbitrary pool, not necessarily numbers, +// you can use the index collection arg within the Function. + +3.enum([\a, \b, \c, \d], { |x,i,col,icol| icol[i] > icol[i-1] }); + +-> [ [ a, b, c ], [ a, b, d ], [ a, c, d ], [ b, c, d ] ] + + +// The number of k-subsets of a set of length n equals n! / k! / (n-k)! +// You might want to check before a complete enumeration: + +~subsetNum = { |n, k| +    var p = 1; +    k.do { |i| p = p * (n - k + i + 1) / (i + 1) }; +    p +}; + +~subsetNum.(18,5) + +-> 8568 + + +// In principle search for tuples with certain features (not only subsets) +// can always be done with using allTuples and filtering out afterwards, +// but this is only feasible for small n. +// E.g. n = 18 and k = 5 requires calculating 1889568 (n**k) tuples first. +// Furthermore method allTuples defaults to a maximum number of 16364 (2**14). +// So (18**5).log2.ceil (21) gives the exponent of 2 to pass + +{ + ((1..18)!5).allTuples((2**21).asInteger) + .select { |y| y.every { |x,i| (i == 0) or: { y[i-1] < y[i] } } }.size.postln; +}.bench + +-> 8568 +time to run: 6.6191733989999 seconds. +6.6191733989999 + + +{ 5.enum((1..18), { |x,i,col| x > col[i-1] }).size.postln; }.bench + +-> 8568 +time to run: 0.10966498400012 seconds. +0.10966498400012 + + +// Tuples without repetitions - +// keep in mind that passed collection is of full length in each step, +// so we have to restrict to the indices up to i-1. +// Writing col[(0..i-1)] means that a new Array is generated in +// every enumeration step. This might be a bottleneck +// with a huge number of steps and could be optimized. + +3.enum((1..4), { |x,i,col| col[(0..i-1)].includes(x).not }); + +-> [[ 1, 2, 3 ], [ 1, 2, 4 ], [ 1, 3, 2 ], [ 1, 3, 4 ], [ 1, 4, 2 ], [ 1, 4, 3 ], + [ 2, 1, 3 ], [ 2, 1, 4 ], [ 2, 3, 1 ], [ 2, 3, 4 ], [ 2, 4, 1 ], [ 2, 4, 3 ], + [ 3, 1, 2 ], [ 3, 1, 4 ], [ 3, 2, 1 ], [ 3, 2, 4 ], [ 3, 4, 1 ], [ 3, 4, 2 ], + [ 4, 1, 2 ], [ 4, 1, 3 ], [ 4, 2, 1 ], [ 4, 2, 3 ], [ 4, 3, 1 ], [ 4, 3, 2 ]] + +:: + + +SECTION::Example 2: Melodic Shapes + +code:: +// This follows an idea by Fabrice Mogini +// Given a sequence of pitches, find all melodies of same shape, +// here just understood as up-and-down movement, +// using the given pitches without repetition. + +// The Function has to check whether +// 1.) there are no repetitions +// 2.) the difference to the last item is of same signum as in the original pitch sequence + +// keep in mind that, as always, passed collection is of full length in each step, +// so we have to restrict to the indices up to i-1 + +( +// assuming no pitches repeated +m = [60, 65, 62, 69, 71]; +d = m.differentiate.sign; +f = { |x,i,col| col[(0..i-1)].includes(x).not && ((x - col[i-1]).sign == d[i]) }; +m.size.enum(m, f); +) + +--> [ [ 60, 65, 62, 69, 71 ], [ 60, 69, 62, 65, 71 ], [ 60, 71, 62, 65, 69 ], + [ 65, 69, 60, 62, 71 ], [ 65, 71, 60, 62, 69 ], [ 62, 65, 60, 69, 71 ], + [ 62, 69, 60, 65, 71 ], [ 62, 71, 60, 65, 69 ], [ 69, 71, 60, 62, 65 ] ] + +:: + + +SECTION::Example 3: Partitions of Integers, Scales + +code:: +// list all partitions of a given integer a into n summands + +( +a = 10; +n = 5; + +// storage of partial sums +// ith element will represent sum up to index i-1 + +p = 0!(n+1); + +// Function should also consider case i = 0 + +f = { |x,i,col| + var order = (i > 0).if { x >= col[i-1] }{ true }; + p[i+1] = p[i] + x; + order and: { + (i + 1 < n).if { + // check if partial sums are not too large + (n - i) * x + p[i] <= a + }{ + // partition check at last index i == n-1 + p[i+1] == a + } + } +}; + +// true causes check also at index 0 +5.enum((1..10), f, true); +) + +-> [ [ 1, 1, 1, 1, 6 ], [ 1, 1, 1, 2, 5 ], [ 1, 1, 1, 3, 4 ], [ 1, 1, 2, 2, 4 ], + [ 1, 1, 2, 3, 3 ], [ 1, 2, 2, 2, 3 ], [ 2, 2, 2, 2, 2 ] ] + + + +// in above Function the given integer and the number of summands are hardcoded. +// For a general purpose tool better make a function constructor, +// also build in an arg that determines if solutions should be ascending or not + +( +// Function to make boolean value Function depending on sum a and number of summands n +g = { |a,n,ascending = true| + var p = 0!(n+1); + { |x,i,col| + var order = ((i > 0) && ascending).if { x >= col[i-1] }{ true }; + p[i+1] = p[i] + x; + order and: { + (i + 1 < n).if { + // check if partial sums are not too large + ascending.if { x }{ 1 } * (n - i) + p[i] <= a + }{ + // partition check at last index i == n-1 + p[i+1] == a + } + } + } +}; + +// Function for listing all partitions of number a with n summands + +h = { |a,n,pool,ascending = true| n.enum(pool, g.(a,n,ascending), true) }; +) + + +// partitions of number 10 consisting of 5 summands +// monotone tuples are demanded (so reorder of tuples is neglected) + +h.(10, 5, (1..10)) + +-> [ [ 1, 1, 1, 1, 6 ], [ 1, 1, 1, 2, 5 ], [ 1, 1, 1, 3, 4 ], [ 1, 1, 2, 2, 4 ], + [ 1, 1, 2, 3, 3 ], [ 1, 2, 2, 2, 3 ], [ 2, 2, 2, 2, 2 ] ] + + +// partitions of number 12, taking order into account (not ascending), +// lists all possible scales of a certain number of pitches, +// given as interval arrays + +// this gives all scales of 7 tones with stepwidth from 1 to 3 semitones. +// Result contains rotations of interval arrays that are different, +// e.g. major [2,2,1,2,2,2,1] and dorian [2,1,2,2,2,1,2] + +x = h.(12, 7, (1..3), false); +x.size; + +-> 266 + +:: + + +SECTION::Example 4: Graphs + +code:: +// undirected graph with 9 nodes +:: + +image::attachments/enum/graph.png:: + +code:: +( +// graph represented as array of possible successor nodes +g = [[1,2], [0,3], [0,3,5], [1,2,4,6], [3,7], [2,6], [3,5,7], [4,6,8], [7]]; + +// Function for finding unused nodes to be connected +f = { |x,i,col| col[(0..i-1)].includes(x).not and: { g[col[i-1]].includes(x) } }; + +// search for all paths using each node exactly once +9.enum((0..8), f) +) + +-> [ [ 1, 0, 2, 5, 6, 3, 4, 7, 8 ], [ 4, 3, 1, 0, 2, 5, 6, 7, 8 ], [ 6, 5, 2, 0, 1, 3, 4, 7, 8 ], + [ 8, 7, 4, 3, 1, 0, 2, 5, 6 ], [ 8, 7, 4, 3, 6, 5, 2, 0, 1 ], [ 8, 7, 6, 5, 2, 0, 1, 3, 4 ] ] + + +// give only one random solution - here path of length 8 + +8.enum((0..8), f, order: false, maxNum: 1) + +-> [ [ 0, 1, 3, 4, 7, 6, 5, 2 ] ] + +:: \ No newline at end of file diff --git a/HelpSource/Tutorials/kitchen_studies.schelp b/HelpSource/Tutorials/kitchen_studies.schelp new file mode 100755 index 0000000..d7f172a --- /dev/null +++ b/HelpSource/Tutorials/kitchen_studies.schelp @@ -0,0 +1,1078 @@ +TITLE::kitchen studies +summary::source code of the corresponding fixed media piece +categories:: Libraries>miSCellaneous +related:: Overviews/miSCellaneous, Guides/Introduction_to_miSCellaneous, Classes/PbindFx, Tutorials/Buffer_Granulation, Tutorials/Live_Granulation, Classes/VarGui, Tutorials/DX_suite, Classes/DXMix, Classes/DXMixIn, Classes/DXEnvFan, Classes/DXEnvFanOut, Classes/DXFan, Classes/DXFanOut, Classes/ZeroXBufRd, Classes/TZeroXBufRd, Classes/ZeroXBufWr + + +DESCRIPTION:: + +In 2016 my interest in granular synthesis was focussed on the potential of sequencing arbitrary effects and effect graphs on single grains. This is an application of the class link::Classes/PbindFx::, which I added with miSCellaneous v0.14. As the possibilities are countless – an arbitrary number of effects can be applied on each grain in an arbitrary way: in sequence, in parallel or in any graph order – I started experiments with combinations of at most two effects and sequencing their parameters. At the same time I wanted to share my experiences and document what I was encountering while composing a piece of music. I thought the best approach for that twofold need would be a sequence of small pieces in once, each of them using different effect processings per grain. A piece of such form would not be totally typical for my usual compositional practice, as I tend to favour longer forms with a few contrasting types of material combined in a developing relation, but nevertheless an interesting challenge. As a sound source I took the kitchen sound of five seconds which is already contained in miSCellaneous lib since version 0.7 and used for examples in the link::Tutorials/Buffer_Granulation:: tutorial. + +For the fixed media piece emphasis::kitchen studies:: the audio, resulting from the six sections of code below, has only been cut and slightly mastered with a bit of equalization (as many random components are included, the output will of course vary from one evaluation to the next). Each code section delivers a mixed control by gui (VarGui) and code snippets (Patterns), which reflects my personal experimental preferences with the concerned sounds and might serve as a starting point for the reader's experiments and modifications. Compressed versions of the original piece as a whole and its parts can be found on my website link::http://daniel-mayer.at:: (use a separate browser, players won't work within this window), a further documentation of the compositional process will follow as publication in the artistic research database emphasis::Research Catalogue:: (link::https://www.researchcatalogue.net/profile/show-exposition?exposition=324609::). + +warning:: +numberedList:: + +## Be careful with amplitudes, especially with buffers you haven't granulated before! +Also keep in mind that a granular cloud moving through a buffer can suddenly become louder – this is especially the case with the one percussive sound contained in the used kitchen source sound. Moreover other controls than amp (e.g. buffer position, trigger rate, effect parameters) can cause a raise of amplitude too. + +## I haven't used below setups for live performances. Although all of them work stable for me as they are, in general hangs can occasionally happen with pattern-driven setups. Often this can be tracked down to sequences of extremely short event durations (and/or long grain durations). Where this can happen as a side-effect, thresholds can be built in (e.g. a parameter maxTrigRate). +Another possible source of hangs is careless deep nesting of Patterns where mistakes can easily occur. Starting with clear Pattern structures is recommended – and if more complications are involved: testing without sound first after saving your patch (generating data with a single Stream derived from a Pattern), might be a good idea. +:: +:: + + +note:: +list:: + +## strong::Running the code examples: :: First run the code from chapter link::#Preparations:: - i.e. start server with extended ressources and evaluate the following block of code. Then you can run the example code of each part (but not in parallel). See the comments of link::#1#Part 1::, many of them explain principles, which are used in all other parts too. Similary, gui conventions are pretty much the same for all six parts, see the note on link::#0#common VarGui features:: below. + +## strong::Types of variables: :: For employing the VarGui interface environmental variables and PLx patterns are used. With VarGui every EventStreamPlayer derived from a passed Pattern is run in a separate newly generated Environment, where variables are being set and Streams from Pfuncs and PLx patterns are reading from. See link::Tutorials/Event_patterns_and_Functions::, link::Tutorials/PLx_suite:: and link::Classes/VarGui::. For certain live replacements though, environmental variables in topEnvironment are chosen and for the sake of clarity some interpreter variables are used too. As a result of these two exceptions the six code blocks, as they are, cannot be run in parallel. As each one is producing dense and rich sound structures it was supposed that combining them in realtime would not be the first option anyway (but it could of course be done by rewriting these variables). + +## strong::Buffers: :: All examples below expect mono buffers like the delivered kitchen sound. Buffer paths refering to the included sample suppose that you have moved miSCellaneous lib into the user extensions directory directly (not into a subfolder), if not so or you have moved the sample file somewhere else you'd have to change paths accordingly. + +## strong::Versions: :: For the published version of emphasis::kitchen studies:: I worked on OS 10.6 / SC 3.6.5 and OS 10.8.5 / SC 3.8.0, slight differences in sound might occur with other hardware, operating systems and SC versions. + +:: +:: + + + +SECTION::Preparations + +code:: +// start server with extended ressources + +( +s.options.numPrivateAudioBusChannels = 2048; +s.options.memSize = 8192 * 32; +s.options.maxNodes = 1024 * 4; + +s.reboot; +) + +// Loading the buffer with 'miSCellaneousDirs', +// this searches the most likely extension places for the miSCellaneous folder. +// In case of an extraordinary install situation or if you have removed +// the sound file, pass the concerned path + +// defining SynthDefs: +// 'pos' for single grains +// 'posPlay' for moving buffer position +// fx SynthDefs for PbindFx +// 'leakDC' for leaking DC from summed grains and limiting + +( +b = Buffer.read(s, Platform.miSCellaneousDirs[0] +/+ "Sounds" +/+ "kitchen_sounds_1.wav"); + +// \posPlay is the synthdef for one grain +// args pos and posDev are to be read from a bus, played by a LFO + +// arg pos is relative between 0 and 1 +// arg posDev is absolute (max absolute deviation in seconds from pos) + +SynthDef(\posPlay, { |out = 0, sndBuf = 0, granDur = 0.1, pos = 0, + posDev = 0, rate = 1, att = 0.002, rel = 0.005, pan = 0, amp = 1| + var env, src; + pos = Rand(posDev.neg, posDev) / BufDur.kr(sndBuf) + pos; + src = PlayBuf.ar(1, sndBuf, BufRateScale.kr(sndBuf) * rate, + 1, round(pos * BufFrames.kr(sndBuf)), 1, 2); + env = EnvGen.ar( + Env([0, amp, amp, 0], [att, granDur - att - rel, rel], 0), + doneAction: 2 + ); + OffsetOut.ar(out, Pan2.ar(src, pan) * env); +}, \ir!10).add; + +// posRate of 1 (given by posRateE and posRateM) means +// pos movement through the buffer with original velocity. +// buffer segment is determined by posLo and posHi. +// posDev and pos movement do not depend on buffer length and size of buffer segment ! +// (posRate does, but pos is then mapped to the buffer segment by .linlin) + +// there are four types of movement through the buffer, determined by the arg 'type': +// O: forward +// 1: backward +// 2: forward and backward +// 3: randomly with cubic interpolation + +SynthDef(\pos, { |out = 0, sndBuf = 0, posRateE = 0, posRateM = 1, type = 0, + posLo = 0, posHi = 1, posDevE = 1, posDevM = 0| + var pos, posDev = 10 ** posDevE * posDevM, posRate = 10 ** posRateE * posRateM; + + posRate = posRate / BufDur.kr(sndBuf) / max(0.001, (posHi - posLo)); + pos = Select.kr(type, [ + LFSaw.kr(posRate, 1), + LFSaw.kr(posRate, 1).neg, + LFTri.kr(posRate, 1).neg, + LFDNoise3.kr(posRate) + ]); + pos = pos.linlin(-1, 1, posLo, posHi); + Out.kr(out, [pos, posDev]) +}).add; + + +// leaking DC and limiting summed audio of all grains + +SynthDef(\leakDC, { |out = 0, in| + var sig = In.ar(in, 2); + Out.ar(out, Limiter.ar(LeakDC.ar(sig))); +}).add; + + +// this Function determines enveloping convention: +// absEnv = 1 (true): attack and release times in milliseconds are taken anyway, +// 'overlap' is possibly overriden. +// absEnv = 0 (false): attack and release times in milliseconds are taken only if +// their sum is smaller than grain duration (derived from 'overlap' and 'trigRate'), +// otherwise they are shrunk and their relation is kept. + +d = (); + +d.attRel = { |dict, granDur, att, rel, absEnv = 1| + // times in secs + var sum; + att = att * 0.001; + rel = rel * 0.001; + sum = att + rel; + (sum <= granDur).if { + [att, rel] + }{ + (absEnv == 1).if { + [att, rel] + }{ + [granDur * att, granDur * rel] / sum + } + } +}; +) + +:: + +anchor::0:: +note:: + +strong::Common features of dedicated VarGuis:: + +The slider section always contains two blocks, the lower one is for parameters of the buffer position synth and the upper one is for parameters of the PbindFx. Within the lower block the relative buffer position is determined by parameters 'posLo' and 'posHi'. As the percussive event within the the loaded kitchen sound appears around position 0.3, most position defaults describe a relatively small section around this value. The deviation of position as well as the rate of movement through the buffer are determined by a mantissa-exponent representation each. Finally four types of movement are defined: 0 = forward, 1 = backward, 2 = forward and backward, 3 = random with cubic interpolation. + +Within the upper block there are two or three differently colored sections, separating controls of source and effects (one or two). PbindFx parameters can be directly passed to the source grain player synths (such a 'amp'), but they might also be used to process the actual synth args, e.g. 'absEnv' determines if parameters 'att' and 'rel' are to be taken literally or relative with respect to an "absolute" 'overlap' value (see pseudo method 'attRel' above), this kind of processing is defined within the PbindFx. Not all parameters need to occur in the VarGui instance, they might also be controlled by PL pattern proxies, in that case the pattern sources are defined in topEnvironment later on. + +Amplitude parameters 'amp' occur with grain synths as well as with effect synths, in the latter case they appear with the fx name as suffix in the gui. Their meaning is not the same in all cases, mostly they are understood as amplitudes of the fx-processed signal ("pre-mix"), but they can be applied to the mix too ("post-mix"). In the case of a combined fx/no-fx sequencing a more fine-tuned amplitude control can be achieved with applying a separate gain fx, which is used instead of no effect. For the the sake of clarity this is only done within the last part. + +The EventStreamPlayer derived from the PbindFx (e0 on the right side) and the buffer position synth can be started separately by pressing the green buttons. Note that patches can also be controlled by setting certain PL proxy pattern sources as shown at the bottom of part 1. Regarding the compositional process of emphasis::kitchen studies::, the final sounding result very much depends on the decision which parameters are controlled by pattern sequencing and which ones are controlled by gui-tunable parameters. Such decisions happened during a long process of experimenting which usually started with gui control for all parameters. However for the final version no live-control with sliders has been recorded, parameters were either fixed or algorithmically controlled by patterns. + +:: + +anchor::1:: +SECTION::Part 1 – comb delay plus separate delay modulation + +code:: +// This granulation combines two effects in sequence. +// The same effect chain (fxOrder = [1, 2]) is used for all grains but parameters change. + +( +// comb delay + +SynthDef(\comb, { |out = 0, in, mix = 0.5, amp = 1, maxDelayTime = 0.2, delayTime = 0.02, + decayTime = 1| + var sig, inSig = In.ar(in, 2); + sig = CombL.ar(inSig, maxDelayTime, delayTime, decayTime, amp); + OffsetOut.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + +// modulation of delay +// modInd is a provisional measure for delay modulation in analogy to FM's index + +SynthDef(\delayMod, { |out = 0, in, delayTime = 0.1, maxDelayTime = 2, + modFreq = 0, modIndM = 0, modIndE = -5, mix = 1, amp = 0.1| + var inSig = In.ar(in, 2), sig, modDev; + modDev = 10 ** modIndE * modIndM * modFreq; + sig = DelayC.ar(inSig, maxDelayTime, SinOsc.ar(modFreq, 0, modDev, delayTime), amp); + OffsetOut.ar(out, (1 - mix) * inSig + (sig * mix)); +}).add; + + +// topEnvironment needed for pattern proxies +// the slider variables are set in dedicated environments by VarGui + +t = topEnvironment.push; + +// bus to DC leaker +a = Bus.audio(s, 2); + +// bus for position control +c = Bus.control(s, 2); + +// PLs with envir = t will read from topEnvironment Patterns/Streams (see below) +// other PLs will read slider values + +// produce three minutes audio + +p = Pfindur(180, PbindFx([ + \instrument, \posPlay, + \sndBuf, b, + \out, a, + + \dur, 1 / PL(\trigRate), + \granDur, Pkey(\dur) * PL(\overlap, envir: t), + + \pos, c.subBus(0).asMap, + \posDev, c.subBus(1).asMap, + + \rate, PL(\rate, envir: t), + \amp, PL(\amp), + + \pan, PLseq([-1, 1]) * PL(\panMax), + // determining concrete envelope times (see Function attRel above) + [\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) }, + + \fxOrder, PL(\fxOrder, envir: t), + // comb delay time needed for lag in case of no comb + \delayTime_comb, PL(\delayTime_comb, envir: t).collect { |x| d.dt = x; x }, + \lag, Pfunc { |ev| (ev.fxOrder.asArray.includes(1)).if { 0 }{ ev.delayTime_comb } }, + \cleanupDelay, 0.3 + ],[ + \fx, \comb, + \decayTime, PL(\decayTime_comb), + \delayTime, Pfunc { d.dt }, + \mix, PL(\mix_comb), + \amp, PL(\amp_comb), + \cleanupDelay, Pkey(\decayTime) + ],[ + \fx, \delayMod, + \mix, PL(\mix_delayMod), + \amp, PL(\amp_delayMod), + \modIndM, PL(\modIndM_delayMod), + \modIndE, PL(\modIndE_delayMod), + \modFreq, PL(\modFreq_delayMod, envir: t), + \cleanupDelay, 0.1 + ] +)); + +// slider and player control +v = VarGui([ + \att, [1, 200, \lin, 0, 6.97], + \rel, [1, 200, \lin, 0, 8.96], + \absEnv, [0, 1, \lin, 1, 0.0], + \trigRate, [1, 200, \lin, 0, 66.67], + \panMax, [0, 1, \lin, 0, 0.89], + \amp, [0.0, 3, \lin, 0, 1], + + \decayTime_comb, [0.001, 1, \lin, 0, 0.14086], + \mix_comb, [0, 1, \lin, 0, 0.7], + \amp_comb, [0, 3, \lin, 0, 1], + + \modIndE_delayMod, [-6, -2, \lin, 1, -5.0], + \modIndM_delayMod, [0, 10, \lin, 0, 1.5], + \mix_delayMod, [0, 1, \lin, 0, 0.88], + \amp_delayMod, [0, 3, \lin, 0, 1.0] + ],[ + \sndBuf, b.bufnum, + \posLo, [0, 0.99, \lin, 0, 0.2673], + \posHi, [0, 0.99, \lin, 0, 0.3267], + \posDevE, [-6, 0, \lin, 1, -5], + \posDevM, [0.0, 10, \lin, 0, 6.7], + + \posRateE, [-4, 2, \lin, 1, -1], + \posRateM, [0.1, 10, \lin, 0, 0.694], + \type, [0, 3, \lin, 1, 3], + \out, c.index + ], stream: p, synth: \pos +); + +// gui look, color grouping +w = ( + varColorGroups: (0..12).clumps([6, 3, 4]), + synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]), + tryColumnNum: 1, + labelWidth: 130, + sliderWidth: 350, + sliderHeight: 18, + playerHeight: 18 +); +) + + + +// running the patch: evaluate this, +// then in gui start position synth and PbindFx stream by pressing both green buttons + +// Psegs are very practical for defining LFO-like behaviour in SC lang + +( +x = Synth(\leakDC, [\out, 0, \in, a]); + +~overlap = Pseg(PLwrand([0.05, 0.5, 1], [4, 1, 1].normalizeSum), Pwhite(0.5, 1), \sine); +~rate = Pseg(Pwhite(0.05, 1), Pexprand(0.01, 0.5), \sine); +~delayTime_comb = Pseg(PLseq([0.02, 0.002]), Pwhite(8, 15)); +~fxOrder = [1, 2]; +~modFreq_delayMod = Pseg(Pwhite(10, 150), Pwhite(2, 5), \sine); + +v.performWithEnvir(\gui, w) +) + +// while running the patch you can play with sliders and +// exchange the control patterns in top envir on the fly: + +~delayTime_comb = 0.002; +~rate = 0.5; + +~modFreq_delayMod = 70; +~modFreq_delayMod = 50; + +~modFreq_delayMod = Pseg(Pwhite(10, 150), Pwhite(2, 5), \sine); + + +// experiment with fx sequencing: no - comb - (comb > delay modulation) + +~fxOrder = PLseq([0, 1, [1, 2]]); +~fxOrder = Pstutter(Pwhite(1, 10), PLseq([0, 1, [1, 2]])); + + +// after stopping free leakDC synth + +x.free + +:: + + +SECTION::Part 2 – rectangular comb (FFT) + +code:: + +( +// rectangular comb, FFT processing per grain + +SynthDef(\pv_rectComb, { |out = 0, in, numTeeth = 0, width = 0.5, phase = 0, mix = 1, + amp = 0.1| + var chain, inSig = In.ar(in, 2), sig, bufSize = 512, inSigDelayed; + + chain = FFT({ LocalBuf(bufSize) } ! 2, inSig); + chain = PV_RectComb(chain, numTeeth, phase, width); + sig = IFFT(chain) * amp; + // for mix we have to take into account FFT delay + inSigDelayed = DelayL.ar( + inSig, + 0.1, + bufSize / s.sampleRate - (s.options.blockSize / s.sampleRate) + ); + OffsetOut.ar(out, mix * sig + ((1 - mix) * inSigDelayed)); +}).add; + + +t = topEnvironment.push; + +a = Bus.audio(s, 2); +c = Bus.control(s, 2); + +// play two loops with three "interludes" (see Task below) + +p = Pfindur(178, PbindFx([ + \instrument, \posPlay, + \sndBuf, b, + \out, a, + + \dur, 1 / PL(\trigRate), + \granDur, Pkey(\dur) * PL(\overlap), + + \pos, c.subBus(0).asMap, + \posDev, c.subBus(1).asMap, + + \rate, PL(\rate, envir: t), + \amp, PL(\amp), + + \pan, PLseq([-1, 1]) * PL(\panMax), + [\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) }, + + \fxOrder, PLseq(\fxOrder, envir: t), + \cleanupDelay, 0.3 + ],[ + \fx, \pv_rectComb, + \mix, PL(\mix_rectComb, envir: t), + \amp, PL(\amp_rectComb), + + \numTeeth, PL(\numTeeth_rectComb, envir: t), + \width, PL(\width_rectComb, envir: t), + \phase, PL(\phase_rectComb, envir: t), + + \cleanupDelay, 0.2 + ] +)); + +// timed pattern control for interludes, done with a Task + +u = Task({ + var times_1 = PLseq([10, 10, 20, 30]).iter; + var times_2 = PLseq([7, 8, 4, 0]).iter; + + loop { + var tm; + ~numTeeth_rectComb = 10; + ~width_rectComb = 0.05; + ~rate = 0.2; + + tm = times_1.next; + tm.wait; + + ~numTeeth_rectComb = Pstutter(Pwhite(5, 30), Pwhite(3, 15)); + ~rate = Pstutter(Pwhite(50, 100), Pwhite(0.3, 0.5)); + ~width_rectComb = Pbrown(0.2, 0.5, 0.02); + + tm = times_2.next; + tm.wait; + } +}.inEnvir(t)); + + +v = VarGui([ + // separate Environments for Task and PbindFx player + [],[ + \att, [1, 200, \lin, 0, 7], + \rel, [1, 200, \lin, 0, 7], + \absEnv, [0, 1, \lin, 1, 0], + \overlap, [0.05, 15, \lin, 0, 3.3], + \trigRate, [1, 200, \lin, 0, 54.73], + + \panMax, [0, 1, \lin, 0, 0.99], + \amp, [0.0, 3, \lin, 0, 0.15], + + \amp_rectComb, [0, 10, \lin, 0, 5.6] + ]],[ + \sndBuf, b.bufnum, + \posLo, [0, 0.99, \lin, 0, 0.2574], + \posHi, [0, 0.99, \lin, 0, 0.3267], + \posDevE, [-6, 0, \lin, 1, -5], + \posDevM, [0.0, 10, \lin, 0, 1.9], + + \posRateE, [-4, 2, \lin, 1, -1], + \posRateM, [0.1, 10, \lin, 0, 5.05], + \type, [0, 3, \lin, 1, 3], + \out, c.index + ], stream: [u, p], synth: \pos +); + +w = ( + varColorGroups: (0..7).clumps([7, 1]), + synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]), + tryColumnNum: 1, + labelWidth: 130, + sliderWidth: 350, + sliderHeight: 18, + playerHeight: 18 +); +) + + +// running the patch: evaluate this, +// then in gui start position synth +// start PbindFx / Task players together by pressing their green buttons with shift-click + +// while running the patch check modified slider values and +// consider pattern replacements as shown in part 1 + + +( +x = Synth(\leakDC, [\out, 0, \in, a]); + +~mix_rectComb = 0.75; +~phase_rectComb = Pstutter(Pwhite(1, 4), Pwhite(0.0, 0.6)); +~fxOrder = [1]; + +v.performWithEnvir(\gui, w) +) + + +// after stopping free leakDC synth + +x.free + +:: + + +SECTION::Part 3 – resampling + +code:: + +( +// fx with "post-mix" amplitude + +SynthDef(\resample, { |out = 0, in, mix = 0.5, amp = 1, resampleRate = 44100| + var sig, inSig = In.ar(in, 2); + sig = Latch.ar(inSig, Impulse.ar(resampleRate)); + OffsetOut.ar(out, ((1 - mix) * inSig + (sig * mix)) * amp); +}).add; + +t = topEnvironment.push; + +a = Bus.audio(s, 2); +c = Bus.control(s, 2); + +p = Pfindur(90, PbindFx([ + \instrument, \posPlay, + \sndBuf, b, + \out, a, + + \dur, 1 / PL(\trigRate, envir: t), + \granDur, Pkey(\dur) * PL(\overlap), + + \pos, c.subBus(0).asMap, + \posDev, c.subBus(1).asMap, + + \rate, PL(\rate, envir: t), + \amp, PL(\amp), + + \pan, PLseq([-1, 1]) * PL(\panMax), + [\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) }, + + \fxOrder, PL(\fxOrder, envir: t), + \cleanupDelay, 0.3 + ],[ + \fx, \resample, + \mix, PL(\mix_resample, envir: t), + \amp, PL(\amp_resample), + \resampleRate, PL(\resampleRate_resample, envir: t), + \cleanupDelay, 0.01 + ] +)); + +v = VarGui([ + \att, [1, 200, \lin, 0, 4.98], + \rel, [1, 200, \lin, 0, 4.98], + \absEnv, [0, 1, \lin, 1, 1.0], + \overlap, [0.05, 15, \lin, 0, 3.5], + + \panMax, [0, 1, \lin, 0, 0.93], + \amp, [0.0, 3, \lin, 0, 0.15], + + \amp_resample, [0, 3, \lin, 0, 2.7] + ],[ + \sndBuf, b.bufnum, + \posLo, [0, 0.99, \lin, 0, 0.27], + \posHi, [0, 0.99, \lin, 0, 0.34], + \posDevE, [-6, 0, \lin, 1, -5], + \posDevM, [0.0, 10, \lin, 0, 0.3], + + \posRateE, [-4, 2, \lin, 1, -1], + \posRateM, [0.1, 10, \lin, 0, 3.5], + \type, [0, 3, \lin, 1, 3], + \out, c.index + ], stream: p, synth: \pos +); + +w = ( + varColorGroups: (0..6).clumps([6, 1]), + synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]), + tryColumnNum: 1, + labelWidth: 110, + sliderWidth: 350, + sliderHeight: 18, + playerHeight: 18 +); +) + + +// running the patch: evaluate this, +// then in gui start position synth and PbindFx stream by pressing both green buttons + +// while running the patch check modified slider values and +// consider pattern replacements as shown in part 1 + +( +Synth(\leakDC, [\out, 0, \in, a]); + +~resampleRate_resample = Pn(Plazy { + var freqs = (1..rrand(8, 15)) * rrand(100, 600); + Pseq(freqs, rrand(3, 6)) +}); + +~fxOrder = 1; + +~mix_resample = Pseg( + PLseq([0.0, 0.9]), + // see PS help, examples for "counted embedding" + PLseq([ + PS(Pwhite(2.0, 3), Pwhite(1, 2)), + PS(Pwhite(0.03, 0.05), Pwhite(50, 80)) + ]), + \step +); + +// random quarter tone row +~rate = Pseg(PLshuf((-24..24)/2).midiratio, Pwhite(0.2, 2), \step); + +~trigRate = Pseg(Pwhite(40, 120), Pwhite(0.5, 2), \sine); + +v.performWithEnvir(\gui, w) +) + +// after stopping free leakDC synth + +x.free + +:: + + + +SECTION::Part 4 – spectral complements (FFT) + +code:: +( +// emphasizes / supresses bin range and / or complement +// uses pseudo ugens PV_BinRange and PV_BinGap + +// freqLo and freqHi define the range +// rangeMul is the multiplier for the selected band +// compMul is the multiplier for the rest of the spectrum + +SynthDef(\pv_binRangeBal, { |out = 0, in, freqLo = 80, freqHi = 300, rangeMul = 1, compMul = 1, mix = 1| + var chain, chain_range, chain_comp, inSig = In.ar(in, 2), sig, bufSize = 2048, + lo, hi, binRange, binNum, inSigDelayed; + + binRange = s.sampleRate / bufSize; + binNum = (freqHi - freqLo / binRange).round; + lo = (freqLo / binRange).round; + hi = (freqHi / binRange).round; + + chain = FFT({ LocalBuf(bufSize) } ! 2, inSig); + chain_range = PV_BinRange(chain, lo, hi); + chain_comp = PV_BinGap(chain, lo, hi); + + sig = IFFT(chain_range) * rangeMul + (IFFT(chain_comp) * compMul); + inSigDelayed = DelayL.ar( + inSig, + 0.1, + bufSize / s.sampleRate - (s.options.blockSize / s.sampleRate) + ); + OffsetOut.ar(out, mix * sig + ((1 - mix) * inSigDelayed)); +}).add; + + +t = topEnvironment.push; + +a = Bus.audio(s, 2); +c = Bus.control(s, 2); + +p = Pfindur(200, PbindFx([ + \instrument, \posPlay, + \sndBuf, b, + \out, a, + + \dur, 1 / PL(\trigRate), + \granDur, Pkey(\dur) * PL(\overlap), + + \pos, c.subBus(0).asMap, + \posDev, c.subBus(1).asMap, + + \rate, PL(\rate, envir: t), + \amp, PL(\amp), + + \pan, PLseq([-1, 1]) * PL(\panMax), + [\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) }, + + \fxOrder, PL(\fxOrder, envir: t), + \cleanupDelay, 0.3 + ],[ + \fx, \pv_binRangeBal, + \mix, PL(\mix_pv_binRangeBal), + + \freqLo, PL(\freqLo_pv_binRangeBal, envir: t), + \freqHi, Pkey(\freqLo) + PL(\freqRange_pv_binRangeBal, envir: t), + + // multipliers for spectral band and complement are given as tupel 'muls' + \muls, PL(\muls_pv_binRangeBal, envir: t), + \rangeMul, Pkey(\muls).collect(_[0]), + \compMul, Pkey(\muls).collect(_[1]), + + \cleanupDelay, 0.2 + ] +)); + +v = VarGui([ + \att, [1, 200, \lin, 0, 3], + \rel, [1, 200, \lin, 0, 3], + \absEnv, [0, 1, \lin, 1, 0], + + \overlap, [0.1, 2, \lin, 0, 1.45], + \trigRate, [1, 200, \lin, 0, 118.41], + + \panMax, [0, 1, \lin, 0, 0.88], + \amp, [0.0, 3, \lin, 0, 0.9], + + \mix_pv_binRangeBal, [0, 1, \lin, 0, 1] +],[ + \sndBuf, b.bufnum, + \posLo, [0, 0.99, \lin, 0, 0.21], + \posHi, [0, 0.99, \lin, 0, 0.34], + \posDevE, [-6, 0, \lin, 1, -5], + \posDevM, [0.0, 10, \lin, 0, 1.0], + + \posRateE, [-4, 2, \lin, 1, -2], + \posRateM, [0.1, 10, \lin, 0, 2.278], + \type, [0, 3, \lin, 1, 3], + + \out, c.index + ], stream: p, synth: \pos +); + +w = ( + varColorGroups: (0..7).clumps([7, 1]), + synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]), + tryColumnNum: 1, + labelWidth: 130, + sliderWidth: 350, + sliderHeight: 18, + playerHeight: 18 +); +) + + +// running the patch: evaluate this, +// then in gui start position synth and PbindFx stream by pressing both green buttons + +// while running the patch check modified slider values and +// consider pattern replacements as shown in part 1 + +( +Synth(\leakDC, [\out, 0, \in, a]); + +~fxOrder = 1; + +// double source grain in octave distance +~rate = [0.15, 0.3]; + +// pattern for lower bound of spectral band +~freqLo_pv_binRangeBal = Pn(Plazy { + var x = { exprand(200, 4000) } ! rrand(2, 4); + Pseq(x, rrand(5, 15)); +}); + +~freqRange_pv_binRangeBal = 2500; + +// this is a bit more complicated +// polyrhythm for multipliers of band and complement +// it becomes more clear when applying trace to the following patterns + +~zeroOneSeqTupleStream = PLshufn([ + [[0, 1, 1], [0, 0, 1, 1]], + [[0, 1, 1], [0, 1, 1, 1]], + [[0, 0, 1], [0, 0, 1, 1]], + [[0, 0, 1], [0, 1, 1, 1]], +]).iter; + +~muls_pv_binRangeBal = Pn(Plazy ({ + var tuple = ~zeroOneSeqTupleStream.next; + if (0.5.coin) { tuple = tuple.reverse }; + if (0.5.coin) { + tuple[0] = tuple[0].reverse; + tuple[1] = tuple[1].reverse; + }; + Pfinval([500, 400, 300].choose, Ptuple([ + PLseq(tuple[0]), + PLseq(tuple[1]) + ])) +}.inEnvir)); + +v.performWithEnvir(\gui, w) +) + +// after stopping free leakDC synth + +x.free + +:: + + + +SECTION::Part 5 – frequency shift with feedback + +code:: + +( +// frequency shift with feedback +// "post-mix" fx amplitude + +// fx with feedback obviously needs envelope ! + +SynthDef(\freqShift, { |out = 0, in, mix = 1, freq = 0, fbAmp = 0, + granDur = 0.1, att = 0.01, rel = 0.01, amp = 0.1| + var inSig = In.ar(in, 2), sig, fb, env; + sig = inSig + (fbAmp * LocalIn.ar(2)); + sig = Limiter.ar(sig); + sig = FreqShift.ar(sig, freq); + LocalOut.ar(sig); + + sig = LPF.ar(sig, 10000); + // delayed attack is functioning as additional feedback control + env = EnvGen.ar(Env([0, 0, 1, 1, 0], [0.01, att, granDur - att - rel, rel]), doneAction: 2); + OffsetOut.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp * env); +}).add; + +t = topEnvironment.push; + +a = Bus.audio(s, 2); +c = Bus.control(s, 2); + +p = PbindFx([ + \instrument, \posPlay, + \sndBuf, b, + \out, a, + + \dur, 1 / PL(\trigRate), + \granDur, Pkey(\dur) * PL(\overlap), + + \pos, c.subBus(0).asMap, + \posDev, c.subBus(1).asMap, + + \rate, PL(\rate, envir: t), + \amp, PL(\amp), + + \pan, PLseq([-1, 1]) * PL(\panMax), + [\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) }, + + // envelope data to be shared with fx + \do, Ptuple([Pkey(\granDur), Pkey(\att), Pkey(\rel)]) + .collect { |x| t[\share] = x }, + + \fxOrder, PL(\fxOrder, envir: t), + + \cleanupDelay, Pfunc { |ev| max(0.2, ev[\granDur] - ev[\dur]) } + ],[ + \fx, \freqShift, + \mix, PL(\mix_freqShift), + \amp, PL(\amp_freqShift), + [\granDur, \att, \rel], Pfunc { t[\share] }, + \granDur, Pkey(\granDur) * 0.9, + \freq, PL(\freq_freqShift, envir: t), + \fbAmp, PL(\fbAmp_freqShift, envir: t), + + \cleanupDelay, 0.5 + ] +); + + +v = VarGui([ + \att, [1, 200, \lin, 0, 3], + \rel, [1, 200, \lin, 0, 7], + \absEnv, [0, 1, \lin, 1, 1.0], + + \overlap, [0.05, 15, \lin, 0, 6.927], + \trigRate, [1, 200, \lin, 0, 24.88], + + \panMax, [0, 1, \lin, 0, 0.84], + \amp, [0.0, 1, \lin, 0, 0.06], + + \mix_freqShift, [0, 1, \lin, 0, 0.54], + \amp_freqShift, [0.1, 2, \lin, 0, 0.3] +],[ + \sndBuf, b.bufnum, + \posLo, [0, 0.99, \lin, 0, 0.21], + \posHi, [0, 0.99, \lin, 0, 0.34], + \posDevE, [-6, 0, \lin, 1, -5], + \posDevM, [0.0, 10, \lin, 0, 0.5], + + \posRateE, [-4, 2, \lin, 1, 0], + \posRateM, [0.1, 10, \lin, 0, 1.486], + \type, [0, 3, \lin, 1, 3], + + \out, c.index + ], stream: p, synth: \pos +); + + +w = ( + varColorGroups: (0..8).clumps([7, 2]), + synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]), + tryColumnNum: 1, + labelWidth: 130, + sliderWidth: 350, + sliderHeight: 18, + playerHeight: 18 +); +) + + +// running the patch: evaluate this, +// then in gui start position synth and PbindFx stream by pressing both green buttons + +// while running the patch check modified slider values and +// consider pattern replacements as shown in part 1 + +( +Synth(\leakDC, [\out, 0, \in, a]); + +~freq_freqShift = Pstutter(Pwhite(10, 50), Pwhite(-200, 50, 1)); +~fbAmp_freqShift = Pstutter(Pstutter(Pwhite(2, 5), Pwhite(1, 2)), PLseq([1, -2])); +~fxOrder = [1]; + +~rate = Pn(Plazy { + Pseries(exprand(0.3, 0.7), rrand(0.02, 0.1), rrand(5, 20)); +}) * 0.8; + +v.performWithEnvir(\gui, w) +) + +// after stopping free leakDC synth + +x.free + + +:: + +SECTION::Part 6 – band pass + +code:: + +( +// band pass +// "post-mix" fx amplitude + +SynthDef(\bpf, { |out = 0, in, freq = 440, rq = 0.1, amp = 1, mix = 1| + var sig, inSig = In.ar(in, 2); + sig = BPF.ar(inSig, freq, rq); + OffsetOut.ar(out, (mix * sig + ((1 - mix) * inSig)) * amp); +}).add; + +// gain as fx for better control of balancing when sequencing grains with and without band pass + +SynthDef(\gain, { |out = 0, in, amp = 1| + var inSig = In.ar(in, 2); + OffsetOut.ar(out, inSig * amp); +}).add; + + +t = topEnvironment.push; + +a = Bus.audio(s, 2); +c = Bus.control(s, 2); + +// duration defined by 'rate' with Pfinval +p = PbindFx([ + \instrument, \posPlay, + \sndBuf, b, + \out, a, + + \dur, 1 / PL(\trigRate), + \granDur, Pkey(\dur) * PL(\overlap), + + \pos, c.subBus(0).asMap, + \posDev, c.subBus(1).asMap, + + \rate, PL(\rate, 1, envir: t), + \amp, PL(\amp), + + \pan, PLseq([-1, 1]) * PL(\panMax), + [\att, \rel], Pfunc { |ev| d.attRel(ev[\granDur], ~att, ~rel, ~absEnv) }, + + \fxOrder, PL(\fxOrder, envir: t), + \cleanupDelay, 0.3 + ],[ + \fx, \gain, + \amp, PL(\amp_gain), + + \cleanupDelay, 0.01 + ],[ + \fx, \bpf, + \freq, PL(\freq_bpf, envir: t), + \rq, PL(\rq_bpf), + \mix, PL(\mix_bpf), + \amp, PL(\amp_bpf), + + \cleanupDelay, 0.01 + ] +); + +v = VarGui([ + \att, [1, 200, \lin, 0, 4.98], + \rel, [1, 200, \lin, 0, 4.98], + \absEnv, [0, 1, \lin, 1, 1.0], + \overlap, [0.05, 15, \lin, 0, 6.7775], + + \trigRate, [1, 200, \lin, 0, 176.12], + \panMax, [0, 1, \lin, 0, 0.83], + \amp, [0.0, 3, \lin, 0, 0.75], + + \amp_gain, [0.0, 3, \lin, 0, 0.36], + + \rq_bpf, [0.01, 1, \lin, 0, 0.0397], + \mix_bpf, [0, 1, \lin, 0, 0.91], + \amp_bpf, [0, 1, \lin, 0, 2.7], + ],[ + \sndBuf, b.bufnum, + \posLo, [0, 0.99, \lin, 0, 0.2574], + \posHi, [0, 0.99, \lin, 0, 0.4059], + \posDevE, [-6, 0, \lin, 1, -4], + \posDevM, [0.0, 10, \lin, 0, 1.0], + + \posRateE, [-4, 2, \lin, 1, -1], + \posRateM, [0.1, 10, \lin, 0, 0.892], + \type, [0, 3, \lin, 1, 3], + \out, c.index + ], stream: p, synth: \pos +); + +w = ( + varColorGroups: (0..10).clumps([8, 3]), + synthColorGroups: (0..8).clumps([1, 2, 2, 2, 1, 1]), + tryColumnNum: 1, + labelWidth: 130, + sliderWidth: 350, + sliderHeight: 18, + playerHeight: 18 +); +) + + +// running the patch: evaluate this, +// then in gui start position synth and PbindFx stream by pressing both green buttons + +// while running the patch check modified slider values and +// consider pattern replacements as shown in part 1 + +( +Synth(\leakDC, [\out, 0, \in, a]); + +// "octave arpeggio" bandpass frequencies, interleaved (PS with repeats = 1) +// check other relations, repetition numbers and fxOrder sequences + +~freq_bpf = PLseq([ + PS(Pstutter(3, Pseg(Pwhite(200, 7000), Pwhite(0.5, 2))) * PLseq([0.5, 1, 2]), 1), + PS(Pstutter(2, Pseg(Pwhite(200, 7000), Pwhite(0.5, 2))) * PLseq([0.5, 1, 2]), 1) +]); + +// start with 1 ensures that we don't have filter only in single channel +~fxOrder = PLseq([1, 2, 2, 1]); + +// development by descending rate "chords" + +~rate = Pseq([ + Pfinval(5582, + Pstutter( + PLseq([120, 80, 80]), + Pexprand(0.3, 1.5).clump(PLshufn((1..3)) * 2 + 1) + ).flatten + ), + Pfinval(5582, + Pstutter( + PLseq([120, 80, 80]), + Pexprand(0.3, 0.6).clump(PLshufn((1..3)) * 2 + 1) + ).flatten + ), + Pfinval(15000, + Pstutter( + PLseq([120, 80, 80]), + Pexprand(0.1, 0.2).clump(PLshufn((1..3)) * 2 + 1) + ).flatten + ) +]); + +v.performWithEnvir(\gui, w) +) + +// after stopping free leakDC synth + +x.free + +:: + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..82fa1da --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.txt b/README.txt new file mode 100755 index 0000000..5814490 --- /dev/null +++ b/README.txt @@ -0,0 +1,457 @@ + +====================================================================================== +miSCellaneous - a library of SuperCollider extensions (c) 2009-2020 Daniel Mayer +====================================================================================== + +Version 0.24 contains these class and help files (SCDoc and old HTML system): + + +1. Guide "Introduction to miSCellaneous": recommended starting point. + +2. VarGui: a slider / player gui to set envir variables and synth controllers and + play synths, event patterns and tasks, see also "VarGui shortcut builds". + "HS with VarGui", a specific tutorial about the combination of HS family and VarGui. + +3. General tutorials: "Event patterns and LFOs" contains an overview of + related LFO-control setups with event patterns. "Event patterns and Functions" + is treating requirements of the control of EventStreamPlayers with VarGui. + "Event patterns and array args" focusses on passing arrays to synths with patterns. + "enum" is a general enumeration method suited for many combinatorial problems such as + listing subsets, partitions of integers, searching for paths within graphs etc. + +4. "PLx suite", dynamic scope variants of common Pattern classes for convenient + replacement. Can be used for shorter writing of Pbinds to be played with VarGui + and / or live coding, see "PLx and live coding with Strings". + PLbindef and PLbindefPar as subclasses of Pdef allow replacement of + key streams in shortcut pseudomethod syntax. + +5. PSx stream patterns, Pattern variants that have a state and can remember their + last values. Can be used for recording streams and event streams (PS), + data sharing between event streams (PSdup), comfortable defining of subpatterns + with counted embedding, defining of recursive (event) sequences (PSrecur) and + building loops on given Patterns/Streams with a variety of options, also for + live interaction (PSrecur). + +6. Event pattern classes for use with effects: PbindFx can handle arbitrary effect + graphs per event, PmonoPar and PpolyPar follow the Pmono paradigm. + The tutorial "kitchen studies" contains the documented source code of a + fixed media piece using PbindFx for granulation. + +7. Further Pattern classes: PlaceAll, Pshufn, PsymNilSafe. + PSPdiv is a dynamic multi-layer pulse divider based on Pspawner. + +8. "Buffer Granulation", a tutorial covering different approaches of implementing + this synthesis method in SC (server versus language control, mixed forms), + examples with VarGui, language-driven control with PLx suite patterns. + The tutorial "Live Granulation" summarizes direct options without + explicit use of a buffer. + +9. A family of classes for the use of synth values in Pbind-like objects. + Take Working with HS and HSpar as a starting point, see also + HS, PHS, PHSuse, HSpar, PHSpar, PHSparUse, PHSplayer, PHSparPlayer, PHSusePlayer. + +10. EventShortcuts, a class for user-defined keywords for events and event patterns. + The tutorial "Other event and pattern shortcuts" collects some further abbreviations, + e.g. functional reference within events and event patterns, similar to Pkey. + +11. An implementation of Xenakis' Sieves as class and pattern family, see + "Sieves and Psieve patterns" for an overview and examples. + +12. FFT pseudo ugens for defining ranges by bin index: PV_BinRange and PV_BinGap. + +13. Smooth Clipping and Folding, a suite of pseudo ugens. + +14. DX, a suite of pseudo ugens for crossfaded mixing and fanning according to + demand-rate control. + +15. Idev suite, patterns and drate ugen searching for numbers with integer distance from a + source pattern / signal. + +16. Nonlinear dynamics: Fb1 for single sample feedback / feedforward and GFIS for + generalized functional iteration synthesis. + Fb1_ODE for ordinary differential equation integration. + +17. ZeroXBufWr, ZeroXBufRd, TZeroXBufRd: pseudo ugens for analysis of zero crossings and + playing sequences of segments between them with demand rate control. Dwalk, a + pseudo demand rate ugen that supports specific synthesis options with ZeroXBufRd. + + +Many of the examples here are using patterns, resp. event patterns but do not cover +their basic concepts. For a detailled description of SC's sequencing capabilities see +James Harkins' Practical Guide to Patterns (PG_01_Introduction), the tutorial +Streams-Patterns-Events (1-7) and the Pattern help files +(Pattern, Pbind and the type-specific ones). + +VarGui handles namespace separation by using different Environments. +So a gui for control of parametrized families of different types of objects can be +built on the fly (e.g. a number of EventStreamPlayers from a single Pbind definition +with snippets of functional code, a number of Function plots from a single +parametric function definition etc.). +See Environment and Event helpfiles for the underlying concepts and +"Event patterns and Functions" and "PLx suite" for their application to +event patterns resp. EventStreamPlayers. + + +Requirements + +At least SuperCollider version 3.6 but newer versions are recommended, +with 3.6 you'd have to use Qt GUI kit, which is the only option anyway from 3.7 onwards. +Unable to test on 3.5 anymore, code might work, anyway old help is still supported. + +If you still use Cocoa or SwingOSC with these old SC versions, +you can take miSCellaneous 0.15b and add classes and help files from a +newer version of miSCellaneous. + +For using VarGui with EZSmoothSlider and EZRoundSlider you would need to +install Wouter Snoei's wslib Quark, one buffer granulation +example using Wavesets depends on Alberto de Campo's Wavesets Quark. + +I tested examples on SC versions 3.6 - 3.11, +on OS 10.8 - 10.13, Ubuntu 12.04 and Windows 7, 10; +though not every platform / OS version / SC version combination. + + +SCDoc issues (SC 3.10) + +With SC 3.10.0 - SC 3.10.2 there are issues with evaluating code in the +help file examples (double evaluation). +For these versions you'd rather copy the help file code +into scd files and run it there. +Double evaluation has been fixed with 3.10.3 (August 2019). +With SC updates to 3.10 it might happen that help file examples are invisible. +In that case delete the Help folder which resides in one of these places, depending +where you have installed: + +Platform.userAppSupportDir; +Platform.systemAppSupportDir; + +Then restart SC. + + +Installation + +By version 0.17 you can install either via the quarks extension management system or a +downloaded zip from GitHub or my website. +Both methods shouldn't be combined, e.g. if you have already done a manual install +before by placing a miSCellaneous folder in the Extensions folder, then remove +it from there before the quarks install. + +1.) Installation via Quarks + +This requires a SC version >= 3.7, see the recommended ways to install here: + +http://doc.sccode.org/Guides/UsingQuarks.html +https://github.com/supercollider-quarks/quarks + +After a install of a newer version of miSCellaneous do a SCDoc update by + +SCDoc.indexAllDocuments(true) + + +2.) Manual installation from a downloaded zip + +You can download the newest version from + +https://github.com/dkmayer/miSCellaneous_lib + +and the newest and all previous versions from here: + +http://daniel-mayer.at + +Copy the miSCellaneous folder into the Extensions folder and recompile +the class library or (re-)start SC. If the Extensions folder doesn't exist +you'd probably have to create it yourself. Check SC help for platform-specific +conventions (or changes) of extension places. + +Typical user-specific extension directories: + +OSX: ~/Library/Application Support/SuperCollider/Extensions/ +Linux: ~/.local/share/SuperCollider/Extensions/ + +Typical system-wide extension directories: + +OSX: /Library/Application Support/SuperCollider/Extensions/ +Linux: /usr/share/SuperCollider/Extensions/ + +You can check Extension directories with + +Platform.userExtensionDir; +Platform.systemExtensionDir; + +On Windows see the README file of SC for the recommended extensions path. +On Windows and OSX you might have to make the concerned folders visible, +if they aren't already. The miSCellaneous folder should be placed directly +into the Extensions folder, not into a subfolder. + +After a install of a newer version of miSCellaneous do a SCDoc update by + +SCDoc.indexAllDocuments(true) + + +License + +miSCellaneous is distributed under the GNU Public License in accordance +with SuperCollider. You should have received a copy of the +GNU General Public License along with this program; +if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +Contact + +Email: daniel-mayer@email.de +URL: http://daniel-mayer.at + + +Credits + +Many thanks to James McCartney for developing SuperCollider, +Alberto de Campo for showing me its capabilities, +Wouter Snoei for his nice slider classes in wslib, +Nathaniel Virgo for his suggestions for feedback, +David Pirrò for his hints concerning ODE integration, +James Harkins for his remarks on many things and +the whole community for contributions and hints ! + + +===================================================================================== + + +History + + +v0.24 2020-07-08 + + .) Dwalk, demand rate ugen that supports specific synthesis options with ZeroXBufRd + .) New examples #9, #10 in ZeroXBufRd help with applications of Dwalk + .) New examples #8, #9 in TZeroXBufRd help (pulsar synthesis) + .) Bugfix related to PV_BinRange and PV_BinGap, usage in FFT chains enabled + + +v0.23 2020-04-19 + + .) ZeroXBufWr, ZeroXBufRd, TZeroXBufRd: playing half wavesets with demand rate control + .) Remove doubled method lincurve_3_9, which caused a warning without harm + .) AddEventTypes_PbindFx: use store instead of writeDefFile in private methods + .) Minor fixes in help files + + +v0.22 2019-08-14 + + .) Fb1_ODE and related: ordinary differential equation integration + .) Fb1: now also runs at control rate, Fb1.new is equivalent to Fb1.ar + .) Fb1: minor change of forced graph ordering + .) PbindFx: accept instruments/fxs given as Strings in pbindData key/value pairs + .) Minor fixes in help files + + +v0.21 2018-07-25 + + .) Fb1: single sample feedback / feedforward + .) GFIS: generalized functional iteration synthesis + .) Redefined variables in Sieve, PSrecur and PSPdiv to enable inlining + .) Minor fixes in help files + + +v0.20 2018-05-21 + + .) Idev suite, search for numbers with integer distance from a source + .) Bug fix in Integer method lcmByFactors + .) Minor fixes in help files + + +v0.19 2017-11-22 + + .) DX, a suite of pseudo ugens for crossfaded mixing and fanning + .) Minor adaption of PbindFx event type + .) Minor fixes in help files + + +v0.18 2017-09-26 + + .) PLbindef / PLbindefPar: new features and implementation + .) Minor fixes in PV_BinGap and PV_BinRange + .) Minor fixes in help files + + +v0.17 2017-08-21 + + .) Smooth Clipping and Folding, a suite of pseudo ugens + .) Added MIDI learning feature for VarGui slider control + .) Platform.miSCellaneousDirs, search for miSCellaneous folder + .) Adapted PV_BinRange and PV_BinGap to do multichannel expansion + .) Exchanged graphic examples in VarGui help (Qt now) + .) Minor fixes in help files + .) First version released via GitHub and as Quark + + +v0.16 2017-03-03 + + .) Added PSPdiv, a dynamic multi-layer pulse divider based on Pspawner + .) Added tutorial: Live Granulation + .) Dropped miSCellaneous' b-branch: Cocoa and SwingOSC no longer supported + .) Replaced OSCpathResponder by OSCFunc in classes VarGuiPlayerSection, + HelpSynth and HelpSynthPar + .) Minor fixes in help files + + +v0.15 2017-01-04 + + .) Guide "Introduction to miSCellaneous" + .) kitchen studies: source code of the corresponding fixed media piece + .) PV_BinRange, PV_BinGap: FFT pseudo ugens for defining bin ranges + .) PbindFx help: new example 4a for defining implicit fx parallelism + .) Minor fixes (help files, typos) + .) Complemeting history information (tutorials) + + +v0.14 2016-08-25 + + .) Sieves and Psieve patterns, an implementation of Xenakis' sieves + .) PLbindef / PLbindefPar: replacement of key streams in pseudomethod syntax + .) Tutorial: PLx and live coding with Strings + .) PsymNilSafe, method symplay: avoid hangs with nil input + .) EventShortcuts: reworking of replacement, new method eventShortcuts + .) PbindFx: reworking of bus match check + .) Minor fixes (help files, typos) + + +v0.13 2015-10-28 + + .) PbindFx: Event pattern class for effect handling on per-event base + .) Minor fixes (help files, typos) + + +v0.12 2015-05-03 + + .) PmonoPar, PpolyPar: + Event pattern classes for parallel setting streams and effect handling + .) PSloop: Pattern to derive loops from a given Pattern + .) Reworked replacing options of PL list patterns: cutItems arg. + .) PLn: PLx Pattern for replacing with protecting periods + .) Added PLxrand, PLx version of Pxrand + .) Changed implementations of PStream (PS) and PSrecur, + now the MemoRoutine isn't instantiated before embedding, + this enables use of PSx patterns with VarGui + .) Fixed a bug in PL list patterns (PL_PproxyNth) that caused too early + replacements in certain cases + .) Added method bufSeq for PStream + .) Added missing help file of PLtuple + .) Minor fixes (help files, typos) + + +v0.11 2015-02-02 + + .) Tutorial: Event patterns and array args + + +v0.10 2014-10-06 + + .) Split into branches 0.10a (3.7 onwards) and 0.10b (from 3.4 up to 3.6.x), + with SC 3.7 SwingOSC and Cocoa aren't supported anymore. + .) Class EventShortcuts and tutorial "Other event and pattern shortcuts" + + +v0.9 2014-02-18 + + .) PSx stream patterns (classes and tutorial), based on class MemoRoutine + .) Buffer Granulation Tutorial: new examples (1c - 1e, 3d) + .) VarGui save dialog fix (was broken with 3.6) + .) Minor fixes (help files, typos) + + +v0.8 2013-05-05 + + .) Enumeration tutorial: method enum + + +v0.7.1 2013-04-28 + + .) Fix in PLseries and PLgeom + + +v0.7 2012-08-31 + + .) Buffer Granulation tutorial file + .) VarGui + .) Support of wslib slider classes EZSmoothSlider and EZRoundSlider + .) Color grouping options for synth and envir variable control + .) Adapting EZSlider to ControlSpec step size + (fixes certain rounding and jitter issues) + .) Multiple slider handling with modifier keys: fixes and cleanup + + +v0.6 2012-05-19 + + .) PLx dynamic scope pattern suite + .) Implementation of a number of common Patterns as PLx classes + .) Tutorial PLx suite + .) Changing examples in some previous help files accordingly + .) Pstream, PlaceAll, Pshufn + + +v0.5 2012-03-18 + + .) VarGui shortcut build methods + .) Use of SynthDef metadata and global ControlSpecs + .) Tutorial VarGui shortcut builds + .) Automatic Pbind generation + .) Minor fixes in VarGui init procedure + + +v0.4 2011-12-27 + + .) VarGui support for HS family classes + .) Tutorial HS with VarGui + .) VarGui takes addAction as slider hook + .) Linux check (tested on Ubuntu): + .) Fixed system time issue in HS family + .) Specified VarGui appearance default parameters for platform and gui kit + .) Supports SCDoc, the new SC help system + .) Tutorial Event Patterns and Functions + .) Tutorial Event patterns and LFOs + .) Play methods of PHSx and PHSxPlayer now also take numbers as quant arg + .) Minor fixes in HS family + + +v0.4beta 2011-08-18 + + .) VarGui relaunch: + .) Player section for Synths, EventStreamPlayers and Tasks + .) Different player modes + .) Button colors and background colors reflecting playing states + .) Handling groups of players and sliders with modifier keys + .) Player action by mouse down or up (currently cocoa only) + .) Variables can be set in different environments + .) Latency setting, global and for synth player message bundling + .) GUI appearance customization in size, arrangement and color + .) Many other changes, e.g. arg conventions + .) Private extension methods get prefix miSC_ + + +v0.3 2010-10-21 + + .) VarGui: + .) Arrayed synth control supported + .) Slider update methods added + .) Save dialog now uses unified gui class Dialog + .) Again compiling with SC 3.3 and 3.3.1 + + +v0.2 2010-09-18 + + .) Minor adaptions to SC 3.4 + .) Fixed time shifting issue + + +v0.1 2009-11-24 + + .) VarGui, interface for envir variable and synth arg control + .) HS / HSpar etc.: classes for the use of synth values in Pbind-like objects + .) Tutorial Working with HS and HSpar + + +===================================================================================== + + + \ No newline at end of file diff --git a/Sounds/kitchen_sounds_1.wav b/Sounds/kitchen_sounds_1.wav new file mode 100644 index 0000000..800b69c Binary files /dev/null and b/Sounds/kitchen_sounds_1.wav differ diff --git a/miSCellaneous_lib.quark b/miSCellaneous_lib.quark new file mode 100644 index 0000000..cb9a499 --- /dev/null +++ b/miSCellaneous_lib.quark @@ -0,0 +1,13 @@ +( + name: "miSCellaneous_lib", + author: "Daniel Mayer", + summary: "Extensions and tutorials: patterns, fx sequencing, granulation, (half) wavesets with demand rate control, wave folding, sieves, combined lang and server gui control, live coding, single sample feedback, ordinary differential equation audification, generalized functional iteration synthesis", + version: "0.24.0", + since: "2009", + schelp: "miSCellaneous.schelp", + helpdoc: "miSCellaneous.html", + isCompatible: { Main.versionAtMost(3, 5).not }, + organisation: "Institute of Electronic Music and Acoustics Graz (IEM)", + country: "Austria", + url: "http://daniel-mayer.at/software_en.htm" +)