diff --git a/DSP/README.org b/DSP/README.org index b866ed8..00f738e 100644 --- a/DSP/README.org +++ b/DSP/README.org @@ -18,3 +18,73 @@ Ndef(~synth[\name]).play; ) #+END_SRC +* Словари-обработки (Fx-словари) через makeBufferProcessingNdef +#+BEGIN_SRC sclang +( +// Загрузка словаря как buffer-processing Fx. +~dict = BmDspAlgs.getDict("spectral/fft_freeze.scd"); + +// Вход через Bus — стандартный паттерн: makeBufferProcessingNdef +// передаёт \buf как OSC-параметр (control-rate число = номер bus), +// func получает его первым аргументом. +~inBus = Bus.audio(s, 2); +{ Out.ar(~inBus, SoundIn.ar([0, 1])) }.play; + +// Создаём Ndef: name = имя нового Ndef, buf = audio-сигнал. +~dict.makeBufferProcessingNdef(\freezeOnly, ~inBus); + +// Параметры func (fftBufnum, freezeGate, phaseJitter) теперь — +// control-параметры SynthDef, доступные через .set / .map. +Ndef(\freezeOnly).set(\fftBufnum, someBuffer); +Ndef(\freezeOnly).set(\freezeGate, 1); +Ndef(\freezeOnly).play; +) +#+END_SRC + +** Контракт func для Fx-словарей + +Func в Fx-словаре объявляется с одним обязательным первым аргументом — *buf*: + +#+BEGIN_SRC sclang +func: { + | buf | + // buf — это audio-сигнал, пришедший через OSC-параметр \buf. + // В скетче он = номер Bus.audio, из которого читает Ndef. + // Остальные параметры объявляются через \name.kr(...) / + // \name.tr(...) и становятся control-параметрами SynthDef. + var sig = ...; + sig; +} +#+END_SRC + +Имя первого аргумента (=buf) — это convention, не контракт. Важно, +что func принимает ровно один positional аргумент первым; обёртка +=makeBufferProcessingNdef= делает =SynthDef.wrap(func, prependArgs: [buf])=. + +** Цепочки Fx-словарей + +Чтобы пропустить выход одного Ndef через другой Fx-словарь, +используется промежуточный Bus: + +#+BEGIN_SRC sclang +~midBus = Bus.audio(s, 2); +Ndef(\first)[0] = { Out.ar(~midBus, Ndef(\first).bus.asMap) }; + +~dict2.makeBufferProcessingNdef(\second, ~midBus); +Ndef(\second).play; +#+END_SRC + +Это даёт последовательную обработку: =first → midBus → second=. + +** Отличия от нотных словарей + +Нотные словари (=makeSourceSynthDef= / =makeSourceNdef=) оборачивают func +с prependArgs =[freq, gate]= и добавляют ADSR-огибающую, panning, amp. +Func должна быть вида =| freq, gate |=. + +Fx-словари (=makeBufferProcessingSynthDef= / =makeBufferProcessingNdef=) +оборачивают func с prependArgs =[buf]= и добавляют Pan2 + amp. +Func должна быть вида =| buf |=. Дополнительные обёртки (DetectSilence, +limitDuration через Line) включаются ключами =detectSilence: true= и +=limitDuration: true= в словаре. + diff --git a/DSP/examples/using_dictionaries_as_synths.scd b/DSP/examples/using_dictionaries_as_synths.scd new file mode 100644 index 0000000..4329ecb --- /dev/null +++ b/DSP/examples/using_dictionaries_as_synths.scd @@ -0,0 +1,77 @@ +s.boot; + +~dict = BmDspAlgs.getDict("synths/bass/saw_bass.scd"); +~dict[\postAmp] = true; +~dict[\useEnv] = true; + +~dict.makeSourceSynthDef().add(); + +~sawBass = (); +~sawBass[\name] = \saw; + +Ndef(~sawBass[\name]).clear; +Ndef(~sawBass[\name]).ar(2); +Ndef(~sawBass[\name]).prime(~dict[\name]); +Ndef(~sawBass[\name]).play; + +( +Tdef(\t, { + loop { + [40, 45, 47].do({|n| + Ndef(~sawBass[\name]).put( + n, + Ndef(~sawBass[\name]).source, + 0, + [\freq, n.midicps, \gate, 1, \amp, 0.25, \release, 0.5, \attack, 0.01] + ); + 0.25.wait; + }); + } +}).play; +) + +( +Tdef(\t2, { + loop { + [55, 61].do({|n| + Ndef(~sawBass[\name]).put( + n, + Ndef(~sawBass[\name]).source, + 0, + [\freq, n.midicps, \gate, 1, \amp, 0.25, \release, 0.1, \attack, 0.01] + ); + (4/7).wait; + }); + } +}).play; +) + +( +Ndef(~sawBass[\name]).put( 159, \filter -> { arg in; + d = LPF.ar(in, 1100); + d +}); +) + +( +Ndef(~sawBass[\name]).put( 160, \filter -> { arg in; + var d = in; + var diffusion = \diffusion.kr(1); + var spacesize = \spacesize.kr(0.1); + 8.do { |i| + d = AllpassC.ar( + d, + delaytime: LFNoise1.kr(0.01!2).range(0.01, spacesize), + decaytime: diffusion + ) + }; + + d +}); +) + +Ndef(~sawBass[\name]).set(\diffusion, 0.03); +Ndef(~sawBass[\name]).set(\spacesize, 0.1); + +Tdef(\t2).stop; +Tdef(\t).stop; \ No newline at end of file diff --git a/DSP/spectral/fft_freeze.scd b/DSP/spectral/fft_freeze.scd new file mode 100644 index 0000000..bcbd7a1 --- /dev/null +++ b/DSP/spectral/fft_freeze.scd @@ -0,0 +1,28 @@ +( +name: 'fft_freeze', +func: { + var in = In.ar(\in.kr(0), 1); + var isOn = \freezeGate.kr(0); + var chain = FFT( + LocalBuf(2048), + in + ); + + var frozen = PV_Freeze(chain, isOn); + var jittered = PV_PhaseShift(frozen, \phaseJitter.kr(0) * TRand.kr(0, 2pi) * isOn); + var sig = IFFT(jittered); + + sig; +}, +inChannels: 1, +outChannels: 2, +requiresBuffer: true, +usePan: true, +panning: \pan, +useSaturation: false, +saturationSteps: 4, +specs: ( + freezeGate: [0, 1, \step, 1, 0], + phaseJitter: [0, 1, \lin, 0.01, 0.1], +), +) diff --git a/DSP/spectral/grain_scatter.scd b/DSP/spectral/grain_scatter.scd new file mode 100644 index 0000000..e09b3c6 --- /dev/null +++ b/DSP/spectral/grain_scatter.scd @@ -0,0 +1,54 @@ +( +name: 'grain_scatter', +func: { + | buf | + var bufnum, rawSig, sig; + // Кольцевой буфер (явно создаётся в скетче): RecordBuf пишет в + // живой вход, WarpZ читает с растяжением и рандомизацией позиции. + // Возвращает развёрнутый во времени сигнал, "разбросанный" по буферу. + + bufnum = \bufnum.kr(0); + + // Захват входа в кольцевой буфер. RecordBuf.ar() пишет в буфер + // как побочный эффект; результат не используется. + RecordBuf.ar( + buf, + bufnum, + doneAction: 0 + ); + + // WarpZ: чтение с растяжением и рандомизацией позиции + rawSig = WarpZ.ar( + numChannels: 2, + bufnum: bufnum, + freq: \warpFreq.kr(40), + windowSize: \warpWindowSize.kr(0.15), + position: \positionJitter.kr(0.3), + posBase: \posBase.kr(0), + overlaps: \overlaps.kr(4), + windowRandRatio: \windowRandRatio.kr(0.2), + freqMul: \freqMul.kr(1), + interp: 2 + ); + + // Duck на громкие моменты — общий паттерн для защиты от клиппинга + sig = rawSig * (1 - \duckOnLoud.kr(0).clip(0, 1) * + (Amplitude.kr(rawSig) > \duckThreshold.kr(0.95)).lag(0.05)); + + sig; +}, +inChannels: 2, +outChannels: 2, +requiresBuffer: true, +specs: ( + warpFreq: [1, 200, \exp, 1, 40], + warpWindowSize: [0.01, 0.5, \lin, 0.01, 0.15], + positionJitter: [0, 1, \lin, 0.01, 0.3], + posBase: [0, 1, \lin, 0.01, 0.5], + overlaps: [1, 16, \lin, 1, 4], + windowRandRatio: [0, 1, \lin, 0.01, 0.2], + freqMul: [0.25, 4, \exp, 0.01, 1], + duckOnLoud: [0, 1, \lin, 0.01, 0.5], + duckThreshold: [0, 1, \lin, 0.01, 0.95] +), +) diff --git a/NodeDictionary/NodeDictionary.sc b/NodeDictionary/NodeDictionary.sc index e9c4e71..fb5ace8 100644 --- a/NodeDictionary/NodeDictionary.sc +++ b/NodeDictionary/NodeDictionary.sc @@ -1,29 +1,91 @@ NodeDictionary : IdentityDictionary { - getDefaultOr { - | key, defaultValue | - if(this[\defaults][key].isNil, { - ^defaultValue - }, { - ^this[\defaults][key] - }); - } - - makeAnalysysNdef { - ^Ndef(this[\name], { - var input = SoundIn.ar(\bus.kr(0)); - var signal = SynthDef.wrap( - func: this[\func], - prependArgs: [input] - ); - - if(this[\convertToKr].isNil.not - and: - this[\convertToKr] == true, - { - A2K.kr(signal); - }, { - signal; - }); - }); - } + getDefaultOr { + | key, defaultValue | + if(this[\defaults][key].isNil, { + ^defaultValue + }, { + ^this[\defaults][key] + }); + } + + processSig { + | sig, gate | + + var signal = sig; + + var releaseNode = if ( + this[\sustained].asBoolean, + { 2 }, + { nil } + ); + + var envDoneAction = if ( + this[\sustained].asBoolean, + { 0 }, + { 2 } + ); + + if(this[\useLfo].asBoolean, { + var lfo = switch(this[\lfoWave]) + { \sin } { SinOsc } + { \saw } { Saw } + { \tri } { LFTri } + { \pulse } { Pulse } + ? SinOsc; + + signal = signal * lfo.kr( + \lfoFreq.kr(1) + ).range( + 1 - \lfoGain.kr(0.5), 1 + ); + }); + + if(this[\useSaturation].asBoolean, { + this.getDefaultOr(\saturationSteps, 4).do { + signal = signal.tanh; + } + }); + + if(this[\useEnv].asBoolean, { + var att = this.getDefaultOr(\attack, 0.1); + var rel = this.getDefaultOr(\release, 0.1); + var curve = this.getDefaultOr(\curve, 1); + + var env = Env( + [0, 0, 1, 0], + [0, \attack.kr(att), \release.kr(rel)], + [0, \attackCurve.kr(curve), \releaseCurve.kr(curve)], + releaseNode: releaseNode, + ).kr(envDoneAction, gate: gate); + + signal = signal * env; + }); + + if(this[\usePan].asBoolean, { + switch(this[\panning], + \splay, { signal = Splay.ar(signal, \spread.kr(1), center: \pan.kr(0)); }, + \splayAz, { signal = SplayAz.ar(this[\panningChannels], signal, orientation: \orientation.kr(0.5)); }, + \pan, { signal = Pan2.ar(signal, \pan.kr(0)); } + ); + }); + + if(this[\postAmp].asBoolean, { + signal = signal * \postAmp.kr(1); + }); + + // if(this[\detectSilence].asBoolean, { + // FreeSelf.kr( + // DetectSilence.ar( + // [signal[0], signal[1]] + Impulse.ar(0) + // ).product) + // }); + + if(this[\limitDuration].asBoolean, { + Line.kr(0, 1, \dur.kr(1), doneAction: 2); + }); + + signal = signal * \amp.kr(1); + + ^signal; + } } \ No newline at end of file diff --git a/NodeDictionary/makeBufferProcessingNdef.sc b/NodeDictionary/makeBufferProcessingNdef.sc index 145e6b2..9cc45d6 100644 --- a/NodeDictionary/makeBufferProcessingNdef.sc +++ b/NodeDictionary/makeBufferProcessingNdef.sc @@ -1,14 +1,19 @@ + NodeDictionary { - makeBufferProcessingNdef { - | name, buf, quant = 1.0 | - var ndef = Ndef(name); - ndef.proxyspace.quant = quant; - ndef[0] = SynthDef.wrap( - func: this[\func], - prependArgs: [buf] - ); - - ndef.play; - ^ndef; - } + makeBufferProcessingNdef { + | name | + + var internalName = if(name.isNil, {this[\name]}, {name}); + + Ndef(internalName, { + var signal, gate; + + gate = \gate.tr(0); + + signal = SynthDef.wrap( + func: this[\func], + ); + + this.processSig(signal, gate); + }); + } } \ No newline at end of file diff --git a/NodeDictionary/makeBufferProcessingSynthDef.sc b/NodeDictionary/makeBufferProcessingSynthDef.sc index 26d0659..753b847 100644 --- a/NodeDictionary/makeBufferProcessingSynthDef.sc +++ b/NodeDictionary/makeBufferProcessingSynthDef.sc @@ -1,32 +1,18 @@ + NodeDictionary { - makeBufferProcessingSynthDef { - SynthDef(this[\name], { - var buf = \buf.kr; - var signal = SynthDef.wrap( - func: this[\func], - prependArgs: [buf] - ); + makeBufferProcessingSynthDef { + SynthDef(this[\name], { + var signal, gate; - signal = Pan2.ar( - signal * \amp.kr(1), - \pan.kr(0) - ); + gate = \gate.tr(0); - if(this[\detectSilence].asBoolean, { - FreeSelf.kr( - DetectSilence.ar( - [signal[0], signal[1]] + Impulse.ar(0) - ).product) - }); + signal = SynthDef.wrap( + func: this[\func], + ); - if(this[\limitDuration].asBoolean, { - Line.kr(0, 1, \dur.kr(1), doneAction: 2); - }); - - Out.ar( - \out.kr(0), - signal - ); - }).add; - } + Out.ar( + \out.kr(0), + this.processSig(signal, gate); + ); + }); + } } \ No newline at end of file diff --git a/NodeDictionary/makeSourceNdef.sc b/NodeDictionary/makeSourceNdef.sc index 7015363..354f3ae 100644 --- a/NodeDictionary/makeSourceNdef.sc +++ b/NodeDictionary/makeSourceNdef.sc @@ -1,74 +1,24 @@ + NodeDictionary { - makeSourceNdef { - | name | - var internalName = if(name.isNil, {this[\name]}, {name}); - - Ndef(internalName, { - var freq, signal, env, gate; - - var releaseNode = if ( - this[\sustained].asBoolean, - { 2 }, - { nil } - ); - - gate = \gate.tr(0); - freq = \freq.kr(220); - - if(this[\useFreqLag].asBoolean, { - freq = Lag.kr(freq, \freqLag.kr(0.5)); - }); - - signal = SynthDef.wrap( - func: this[\func], - prependArgs: [freq, gate] - ); - - if(this[\useLfo].asBoolean, { - var lfo = switch(this[\lfoWave]) - { \sin } { SinOsc } - { \saw } { Saw } - { \tri } { LFTri } - { \pulse } { Pulse } - ? SinOsc; - - signal = signal * lfo.kr( - \lfoFreq.kr(1) - ).range( - 1 - \lfoGain.kr(0.5), 1 - ); - }); - - if(this[\useSaturation].asBoolean, { - this[\saturationSteps].do { - signal = signal.tanh; - } - }); - - if(this[\useEnv].asBoolean, { - var att = this.getDefaultOr(\attack, 0.1); - var rel = this.getDefaultOr(\release, 0.1); - var curve = this.getDefaultOr(\curve, 1); - - var env = Env( - [0, 0, 1, 0], - [0, \attack.kr(att), \release.kr(rel)], - [0, \attackCurve.kr(curve), \releaseCurve.kr(curve)], - releaseNode: releaseNode, - ).kr(0, gate: gate); - - signal = signal * env; - }); - - if(this[\usePan].asBoolean, { - switch(this[\panning], - \splay, { signal = Splay.ar(signal, \spread.kr(1), center: \pan.kr(0)); }, - \splayAz, { signal = SplayAz.ar(this[\panningChannels], signal, orientation: \orientation.kr(0.5)); }, - \pan, { signal = Pan2.ar(signal, \pan.kr(0)); } - ); - }); - - signal * \amp.kr(1); - }); - } + makeSourceNdef { + | name | + var internalName = if(name.isNil, {this[\name]}, {name}); + + Ndef(internalName, { + var freq, signal, gate; + + gate = \gate.tr(0); + freq = \freq.kr(220); + + if(this[\useFreqLag].asBoolean, { + freq = Lag.kr(freq, \freqLag.kr(0.5)); + }); + + signal = SynthDef.wrap( + func: this[\func], + prependArgs: [freq, gate] + ); + + this.processSig(signal, gate); + }); + } } \ No newline at end of file diff --git a/NodeDictionary/makeSourceSynthDef.sc b/NodeDictionary/makeSourceSynthDef.sc index db88694..109c9ac 100644 --- a/NodeDictionary/makeSourceSynthDef.sc +++ b/NodeDictionary/makeSourceSynthDef.sc @@ -1,84 +1,24 @@ + NodeDictionary { - makeSourceSynthDef { - ^SynthDef(this[\name], { - var freq, signal, env, gate; - - var releaseNode = if ( - this[\sustained].asBoolean, - { 2 }, - { nil } - ); - - var envDoneAction = if ( - this[\sustained].asBoolean, - { 0 }, - { 2 } - ); - - gate = \gate.tr(0); - freq = \freq.kr(220); - - if(this[\useFreqLag].asBoolean, { - freq = Lag.kr(freq, \freqLag.kr(0.5)); - }); - - signal = SynthDef.wrap( - func: this[\func], - prependArgs: [freq, gate] - ); - - if(this[\useLfo].asBoolean, { - var lfo = switch(this[\lfoWave]) - { \sin } { SinOsc } - { \saw } { Saw } - { \tri } { LFTri } - { \pulse } { Pulse } - ? SinOsc; - - signal = signal * lfo.kr( - \lfoFreq.kr(1) - ).range( - 1 - \lfoGain.kr(0.5), 1 - ); - }); - - if(this[\useSaturation].asBoolean, { - this[\saturationSteps].do { - signal = signal.tanh; - } - }); - - if(this[\useEnv].asBoolean, { - var att = this.getDefaultOr(\attack, 0.1); - var rel = this.getDefaultOr(\release, 0.1); - var curve = this.getDefaultOr(\curve, 1); - - var env = Env( - [0, 0, 1, 0], - [0, \attack.kr(att), \release.kr(rel)], - \envCurve.kr(curve), - releaseNode: releaseNode, - ).kr(envDoneAction, gate: gate); - - signal = signal * env; - }); - - switch(this[\panning], - \splay, { signal = Splay.ar(signal, \spread.kr(1), center: \pan.kr(0)); }, - \splayAz, { signal = SplayAz.ar(this[\panningChannels], signal, orientation: \orientation.kr(0.5)); }, - nil, { signal = Pan2.ar(signal, \pan.kr(0)); } - ); - - signal = signal * \amp.kr(1); - - if(this[\postAmp].asBoolean, { - signal = signal * \postAmp.kr(1); - }); - - Out.ar( - \out.kr(0), - signal, - ); - }); - } + makeSourceSynthDef { + ^SynthDef(this[\name], { + var freq, signal, gate; + + gate = \gate.tr(0); + freq = \freq.kr(220); + + if(this[\useFreqLag].asBoolean, { + freq = Lag.kr(freq, \freqLag.kr(0.5)); + }); + + signal = SynthDef.wrap( + func: this[\func], + prependArgs: [freq, gate] + ); + + Out.ar( + \out.kr(0), + signal, + ); + }); + } } \ No newline at end of file