From cfb11960819c9b6d42b28ca005a923287e750e96 Mon Sep 17 00:00:00 2001 From: lambda Date: Sun, 23 Aug 2026 19:44:29 +0300 Subject: [PATCH 1/6] =?UTF-8?q?WIP:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D0=B8=D0=BD=D1=82=D1=8B=20=D0=B8=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DSP/README.org | 70 ++++++++++++ DSP/examples/spectral_freeze_scatter.scd | 104 ++++++++++++++++++ DSP/examples/using_dictionaries_as_synths.scd | 77 +++++++++++++ DSP/spectral/fft_freeze.scd | 53 +++++++++ DSP/spectral/grain_scatter.scd | 54 +++++++++ NodeDictionary/makeBufferProcessingNdef.sc | 22 ++-- 6 files changed, 371 insertions(+), 9 deletions(-) create mode 100644 DSP/examples/spectral_freeze_scatter.scd create mode 100644 DSP/examples/using_dictionaries_as_synths.scd create mode 100644 DSP/spectral/fft_freeze.scd create mode 100644 DSP/spectral/grain_scatter.scd 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/spectral_freeze_scatter.scd b/DSP/examples/spectral_freeze_scatter.scd new file mode 100644 index 0000000..102d554 --- /dev/null +++ b/DSP/examples/spectral_freeze_scatter.scd @@ -0,0 +1,104 @@ +( +// Пример: spectral freeze + grain scatter. +// Концепция из README.org дуэта 24_09_2026 (концепция №1). +// +// Два независимых словаря: +// - fft_freeze — удержание magnitude, фазовый джиттер (PV_Freeze + PV_PhaseShift). +// - grain_scatter — WarpZ по кольцевому буферу, scatter позиции. +// +// Оба словаря идут через makeBufferProcessingNdef — это стандартный +// путь для Fx/сэмплер-словарей в bmSClib: func получает audio-сигнал +// первым аргументом (| buf |), обёртка делает SynthDef.wrap(func, [buf]) +// и кладёт результат в Ndef. +// +// Входной звук подаётся через Bus — это стандартный паттерн для Fx, +// потому что makeBufferProcessingNdef передаёт \buf как OSC-параметр +// (control-rate число = номер bus), а не как UGen. + +s.options.numBuffers = 1024; // FFT-буферы + ringbuffer для scatter + +s.waitForBoot({ + // ---- Распределение буферов ------------------------------------- + // FFT: явное распределение, как в шаблоне make-scd.el. + ~fftBuf = Buffer.alloc(s, 1024, 2); // stereo FFT, 1024 сэмплов + + // Кольцевой буфер для grain_scatter: 2 секунды, стерео. + ~ringBuf = Buffer.alloc(s, s.sampleRate * 2.0, 2); + + // ---- Входной bus ------------------------------------------------ + // В реальном концерте — звук от Letov или печатной машинки. + // Здесь — SoundIn.ar как stand-in, пишется в ~inBus отдельным + // синтом. Ndef читает этот bus по номеру через \buf.kr. + ~inBus = Bus.audio(s, 2); + { Out.ar(~inBus, SoundIn.ar([0, 1])) }.play; + + // ---- Загрузка словарей ------------------------------------------ + ~freezeDict = BmDspAlgs.getDict("spectral/fft_freeze.scd"); + ~scatterDict = BmDspAlgs.getDict("spectral/grain_scatter.scd"); + + // ---- Режим 1: только freeze -------------------------------------- + // makeBufferProcessingNdef(name, buf) — создаёт Ndef(\name) с + // func, обёрнутой через SynthDef.wrap(func, prependArgs: [buf]). + // buf — это audio-сигнал; здесь передаём номер bus. + ~freezeDict.makeBufferProcessingNdef(\freezeOnly, ~inBus); + Ndef(\freezeOnly).set(\fftBufnum, ~fftBuf.bufnum); + Ndef(\freezeOnly).play; + + // Управление freeze-педалью: gate=1 триггерит freeze, gate=0 + // отпускает. Удобно вешать на MIDI CC или OSC. + Ndef(\freezeOnly).addSpec(\freezeGate, [0, 1, \lin, 1, 0]); + Ndef(\freezeOnly).set(\freezeGate, 0); + + // ---- Режим 2: только scatter ------------------------------------- + ~scatterDict.makeBufferProcessingNdef(\scatterOnly, ~inBus); + Ndef(\scatterOnly).set(\bufnum, ~ringBuf.bufnum); + Ndef(\scatterOnly).play; + + // ---- Режим 3: оба последовательно ------------------------------- + // freeze → scatter: frozen-сигнал проходит через WarpZ. + // Ndef-цепочка: Ndef(\freezeOnly) пишет на свой bus, scatter + // читает с него. + ~freezeBus = Bus.audio(s, 2); + Ndef(\freezeOnly)[0] = { Out.ar(~freezeBus, Ndef(\freezeOnly).bus.asMap) }; + + ~scatterDict.makeBufferProcessingNdef(\freezeAndScatter, ~freezeBus); + Ndef(\freezeAndScatter).set(\bufnum, ~ringBuf.bufnum); + Ndef(\freezeAndScatter).play; + + // ---- Педали ------------------------------------------------------ + // Управление через OSC / MIDI / HID. Ndef.set принимает числа, + // для live-управления — .map(\param, .kr). + Ndef(\freezeOnly).addSpec(\phaseJitter, [0, 1, \lin, 0.01, 0.1]); + Ndef(\freezeOnly).set(\phaseJitter, 0.1); + + Ndef(\scatterOnly).addSpec(\positionJitter, [0, 1, \lin, 0.01, 0.3]); + Ndef(\scatterOnly).set(\positionJitter, 0.3); +}); + +// ---- Использование в перформансе ------------------------------------- +// В живом концерте: +// +// 1. freezeOnly: установить \freezeGate в 1, чтобы "заморозить" +// текущий вход. phaseJitter через педаль добавляет "дыхание" +// frozen-спектра. +// +// 2. На тихой паузе (Letov молчит) — включить freeze, и звук +// "дышит" сам по себе. Снять freeze — вернуться к реальному +// входу. +// +// 3. Когда Letov играет активно — scatterOnly поверх, с +// разбросом позиции через .map(\positionJitter, .kr). +// +// 4. freezeAndScatter: сначала freeze, затем scatter по +// frozen-сигналу. Получается "зависший" звук, который +// разбивается на зёрна — главный спецэффект скетча. +// +// ---- Остановка ------------------------------------------------------- +// Ndef(\freezeOnly).stop; +// Ndef(\scatterOnly).stop; +// Ndef(\freezeAndScatter).stop; +// ~fftBuf.free; +// ~ringBuf.free; +// ~inBus.free; +// ~freezeBus.free; +) 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..1c9de45 --- /dev/null +++ b/DSP/spectral/fft_freeze.scd @@ -0,0 +1,53 @@ +( +name: 'fft_freeze', +func: { + | buf | + + // Входной звук → FFT → freeze magnitude (PV_Freeze) + phase-jitter + // (PV_PhaseShift с шумом) → IFFT. + // + // FFT-буфер передаётся снаружи через \fftBufnum — это позволяет + // распределять буферы в скетче (s.options.numBuffers) и переиспользовать + // между перезапусками синта. + // + // Почему не кастомная пересборка chain из magnitude/phase: готовых + // PV_* UGen для неё нет, а PV_Chain не имеет метода .polar. + // UnpackFFT/PackFFT подходят для поканальной обработки, но не для + // uniform-операций вроде freeze или jitter. PV_Freeze + PV_PhaseShift + // дают ближайший готовый результат. + + var fftBuf = \fftBufnum.kr(0); + + var chain = FFT( + fftBuf, + buf, + \hop.kr(0.25), + \wintype.kr(1) + ); + + // PV_Freeze принимает gate trigger: при freezeGate=1 удерживает + // magnitude, при 0 — пропускает как есть. + var frozen = PV_Freeze(chain, \freezeGate.tr(0)); + + // Phase jitter: случайный сдвиг фазы всех бинов. Субъективно — + // "дыхание" frozen-спектра. + var jittered = PV_PhaseShift(frozen, \phaseJitter.kr(0) * TRand.kr(0, 2pi)); + + var rawSig = IFFT(jittered, \wintype.kr(1)); + + // Duck на громкие моменты — общий паттерн для защиты от клиппинга + var 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: ( + phaseJitter: [0, 1, \lin, 0.01, 0.1], + hop: [0.0625, 1, \exp, 0.01, 0.25], + duckOnLoud: [0, 1, \lin, 0.01, 0.5], + duckThreshold: [0, 1, \lin, 0.01, 0.95] +), +) 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/makeBufferProcessingNdef.sc b/NodeDictionary/makeBufferProcessingNdef.sc index 145e6b2..03e87f3 100644 --- a/NodeDictionary/makeBufferProcessingNdef.sc +++ b/NodeDictionary/makeBufferProcessingNdef.sc @@ -1,14 +1,18 @@ + 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; + + var internalName = if(name.isNil, {this[\name]}, {name}); + + Ndef(internalName, { + var freq, signal, env, gate; + + signal = SynthDef.wrap( + func: this[\func], + prependArgs: [buf] + ); + + signal * \amp.kr(1); + }); } } \ No newline at end of file From 6a6974d762485f74d27ac0043508161a5b63f451 Mon Sep 17 00:00:00 2001 From: Arseniusz Date: Mon, 24 Aug 2026 00:05:03 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D1=83=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BB=20freezer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DSP/spectral/fft_freeze.scd | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/DSP/spectral/fft_freeze.scd b/DSP/spectral/fft_freeze.scd index 1c9de45..6a23444 100644 --- a/DSP/spectral/fft_freeze.scd +++ b/DSP/spectral/fft_freeze.scd @@ -3,46 +3,19 @@ name: 'fft_freeze', func: { | buf | - // Входной звук → FFT → freeze magnitude (PV_Freeze) + phase-jitter - // (PV_PhaseShift с шумом) → IFFT. - // - // FFT-буфер передаётся снаружи через \fftBufnum — это позволяет - // распределять буферы в скетче (s.options.numBuffers) и переиспользовать - // между перезапусками синта. - // - // Почему не кастомная пересборка chain из magnitude/phase: готовых - // PV_* UGen для неё нет, а PV_Chain не имеет метода .polar. - // UnpackFFT/PackFFT подходят для поканальной обработки, но не для - // uniform-операций вроде freeze или jitter. PV_Freeze + PV_PhaseShift - // дают ближайший готовый результат. - - var fftBuf = \fftBufnum.kr(0); - var chain = FFT( - fftBuf, + LocalBuf(2048), buf, - \hop.kr(0.25), - \wintype.kr(1) ); - // PV_Freeze принимает gate trigger: при freezeGate=1 удерживает - // magnitude, при 0 — пропускает как есть. var frozen = PV_Freeze(chain, \freezeGate.tr(0)); - - // Phase jitter: случайный сдвиг фазы всех бинов. Субъективно — - // "дыхание" frozen-спектра. var jittered = PV_PhaseShift(frozen, \phaseJitter.kr(0) * TRand.kr(0, 2pi)); - - var rawSig = IFFT(jittered, \wintype.kr(1)); - - // Duck на громкие моменты — общий паттерн для защиты от клиппинга - var sig = rawSig * (1 - \duckOnLoud.kr(0).clip(0, 1) * - (Amplitude.kr(rawSig) > \duckThreshold.kr(0.95)).lag(0.05)); + var sig = IFFT(jittered, \wintype.kr(1)); sig }, -inChannels: 2, -outChannels: 2, +inChannels: 1, +outChannels: 1, requiresBuffer: true, specs: ( phaseJitter: [0, 1, \lin, 0.01, 0.1], From 8d2c2e887a1715a9044850434688ce72eaa5826f Mon Sep 17 00:00:00 2001 From: Arseniusz Date: Wed, 16 Sep 2026 23:28:46 +0300 Subject: [PATCH 3/6] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DSP/spectral/fft_freeze.scd | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/DSP/spectral/fft_freeze.scd b/DSP/spectral/fft_freeze.scd index 6a23444..703791d 100644 --- a/DSP/spectral/fft_freeze.scd +++ b/DSP/spectral/fft_freeze.scd @@ -2,25 +2,24 @@ name: 'fft_freeze', func: { | buf | - + var in = In.ar(buf, 1); + var isOn = \freezeGate.kr(0); var chain = FFT( LocalBuf(2048), - buf, + in ); - var frozen = PV_Freeze(chain, \freezeGate.tr(0)); - var jittered = PV_PhaseShift(frozen, \phaseJitter.kr(0) * TRand.kr(0, 2pi)); - var sig = IFFT(jittered, \wintype.kr(1)); + var frozen = PV_Freeze(chain, isOn); + var jittered = PV_PhaseShift(frozen, \phaseJitter.kr(0) * TRand.kr(0, 2pi) * isOn); + var sig = IFFT(jittered); - sig + sig; }, inChannels: 1, outChannels: 1, requiresBuffer: true, specs: ( + freezeGate: [0, 1, \step, 1, 0], phaseJitter: [0, 1, \lin, 0.01, 0.1], - hop: [0.0625, 1, \exp, 0.01, 0.25], - duckOnLoud: [0, 1, \lin, 0.01, 0.5], - duckThreshold: [0, 1, \lin, 0.01, 0.95] ), ) From 2fd31d158aeccd248208f0a9d7cdb51081a92107 Mon Sep 17 00:00:00 2001 From: Arseniusz Date: Thu, 17 Sep 2026 00:33:40 +0300 Subject: [PATCH 4/6] =?UTF-8?q?=D1=83=D0=BD=D0=B8=D1=84=D0=B8=D1=86=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NodeDictionary/NodeDictionary.sc | 114 +++++++++++++----- NodeDictionary/makeBufferProcessingNdef.sc | 25 ++-- .../makeBufferProcessingSynthDef.sc | 40 ++---- NodeDictionary/makeSourceNdef.sc | 94 ++++----------- NodeDictionary/makeSourceSynthDef.sc | 104 ++++------------ 5 files changed, 157 insertions(+), 220 deletions(-) diff --git a/NodeDictionary/NodeDictionary.sc b/NodeDictionary/NodeDictionary.sc index e9c4e71..667be12 100644 --- a/NodeDictionary/NodeDictionary.sc +++ b/NodeDictionary/NodeDictionary.sc @@ -1,29 +1,89 @@ 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 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 03e87f3..9cc45d6 100644 --- a/NodeDictionary/makeBufferProcessingNdef.sc +++ b/NodeDictionary/makeBufferProcessingNdef.sc @@ -1,18 +1,19 @@ + NodeDictionary { - makeBufferProcessingNdef { - | name, buf, quant = 1.0 | + makeBufferProcessingNdef { + | name | - var internalName = if(name.isNil, {this[\name]}, {name}); + var internalName = if(name.isNil, {this[\name]}, {name}); - Ndef(internalName, { - var freq, signal, env, gate; + Ndef(internalName, { + var signal, gate; - signal = SynthDef.wrap( - func: this[\func], - prependArgs: [buf] - ); + gate = \gate.tr(0); - signal * \amp.kr(1); - }); - } + 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 From 9029f4c525e58439d1d378a10f26c2054be1b66f Mon Sep 17 00:00:00 2001 From: Arseniusz Date: Sat, 19 Sep 2026 19:34:16 +0300 Subject: [PATCH 5/6] =?UTF-8?q?=D0=BC=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DSP/spectral/fft_freeze.scd | 9 ++++++--- NodeDictionary/NodeDictionary.sc | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/DSP/spectral/fft_freeze.scd b/DSP/spectral/fft_freeze.scd index 703791d..bcbd7a1 100644 --- a/DSP/spectral/fft_freeze.scd +++ b/DSP/spectral/fft_freeze.scd @@ -1,8 +1,7 @@ ( name: 'fft_freeze', func: { - | buf | - var in = In.ar(buf, 1); + var in = In.ar(\in.kr(0), 1); var isOn = \freezeGate.kr(0); var chain = FFT( LocalBuf(2048), @@ -16,8 +15,12 @@ func: { sig; }, inChannels: 1, -outChannels: 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/NodeDictionary/NodeDictionary.sc b/NodeDictionary/NodeDictionary.sc index 667be12..fb5ace8 100644 --- a/NodeDictionary/NodeDictionary.sc +++ b/NodeDictionary/NodeDictionary.sc @@ -11,6 +11,8 @@ NodeDictionary : IdentityDictionary { processSig { | sig, gate | + var signal = sig; + var releaseNode = if ( this[\sustained].asBoolean, { 2 }, From adf52b12455ca377915c353fbbe70b239fd11d6f Mon Sep 17 00:00:00 2001 From: Arseniusz Date: Sat, 19 Sep 2026 19:45:04 +0300 Subject: [PATCH 6/6] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=83=D0=B6=D0=BD=D1=8B=D0=B9=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=BC=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DSP/examples/spectral_freeze_scatter.scd | 104 ----------------------- 1 file changed, 104 deletions(-) delete mode 100644 DSP/examples/spectral_freeze_scatter.scd diff --git a/DSP/examples/spectral_freeze_scatter.scd b/DSP/examples/spectral_freeze_scatter.scd deleted file mode 100644 index 102d554..0000000 --- a/DSP/examples/spectral_freeze_scatter.scd +++ /dev/null @@ -1,104 +0,0 @@ -( -// Пример: spectral freeze + grain scatter. -// Концепция из README.org дуэта 24_09_2026 (концепция №1). -// -// Два независимых словаря: -// - fft_freeze — удержание magnitude, фазовый джиттер (PV_Freeze + PV_PhaseShift). -// - grain_scatter — WarpZ по кольцевому буферу, scatter позиции. -// -// Оба словаря идут через makeBufferProcessingNdef — это стандартный -// путь для Fx/сэмплер-словарей в bmSClib: func получает audio-сигнал -// первым аргументом (| buf |), обёртка делает SynthDef.wrap(func, [buf]) -// и кладёт результат в Ndef. -// -// Входной звук подаётся через Bus — это стандартный паттерн для Fx, -// потому что makeBufferProcessingNdef передаёт \buf как OSC-параметр -// (control-rate число = номер bus), а не как UGen. - -s.options.numBuffers = 1024; // FFT-буферы + ringbuffer для scatter - -s.waitForBoot({ - // ---- Распределение буферов ------------------------------------- - // FFT: явное распределение, как в шаблоне make-scd.el. - ~fftBuf = Buffer.alloc(s, 1024, 2); // stereo FFT, 1024 сэмплов - - // Кольцевой буфер для grain_scatter: 2 секунды, стерео. - ~ringBuf = Buffer.alloc(s, s.sampleRate * 2.0, 2); - - // ---- Входной bus ------------------------------------------------ - // В реальном концерте — звук от Letov или печатной машинки. - // Здесь — SoundIn.ar как stand-in, пишется в ~inBus отдельным - // синтом. Ndef читает этот bus по номеру через \buf.kr. - ~inBus = Bus.audio(s, 2); - { Out.ar(~inBus, SoundIn.ar([0, 1])) }.play; - - // ---- Загрузка словарей ------------------------------------------ - ~freezeDict = BmDspAlgs.getDict("spectral/fft_freeze.scd"); - ~scatterDict = BmDspAlgs.getDict("spectral/grain_scatter.scd"); - - // ---- Режим 1: только freeze -------------------------------------- - // makeBufferProcessingNdef(name, buf) — создаёт Ndef(\name) с - // func, обёрнутой через SynthDef.wrap(func, prependArgs: [buf]). - // buf — это audio-сигнал; здесь передаём номер bus. - ~freezeDict.makeBufferProcessingNdef(\freezeOnly, ~inBus); - Ndef(\freezeOnly).set(\fftBufnum, ~fftBuf.bufnum); - Ndef(\freezeOnly).play; - - // Управление freeze-педалью: gate=1 триггерит freeze, gate=0 - // отпускает. Удобно вешать на MIDI CC или OSC. - Ndef(\freezeOnly).addSpec(\freezeGate, [0, 1, \lin, 1, 0]); - Ndef(\freezeOnly).set(\freezeGate, 0); - - // ---- Режим 2: только scatter ------------------------------------- - ~scatterDict.makeBufferProcessingNdef(\scatterOnly, ~inBus); - Ndef(\scatterOnly).set(\bufnum, ~ringBuf.bufnum); - Ndef(\scatterOnly).play; - - // ---- Режим 3: оба последовательно ------------------------------- - // freeze → scatter: frozen-сигнал проходит через WarpZ. - // Ndef-цепочка: Ndef(\freezeOnly) пишет на свой bus, scatter - // читает с него. - ~freezeBus = Bus.audio(s, 2); - Ndef(\freezeOnly)[0] = { Out.ar(~freezeBus, Ndef(\freezeOnly).bus.asMap) }; - - ~scatterDict.makeBufferProcessingNdef(\freezeAndScatter, ~freezeBus); - Ndef(\freezeAndScatter).set(\bufnum, ~ringBuf.bufnum); - Ndef(\freezeAndScatter).play; - - // ---- Педали ------------------------------------------------------ - // Управление через OSC / MIDI / HID. Ndef.set принимает числа, - // для live-управления — .map(\param, .kr). - Ndef(\freezeOnly).addSpec(\phaseJitter, [0, 1, \lin, 0.01, 0.1]); - Ndef(\freezeOnly).set(\phaseJitter, 0.1); - - Ndef(\scatterOnly).addSpec(\positionJitter, [0, 1, \lin, 0.01, 0.3]); - Ndef(\scatterOnly).set(\positionJitter, 0.3); -}); - -// ---- Использование в перформансе ------------------------------------- -// В живом концерте: -// -// 1. freezeOnly: установить \freezeGate в 1, чтобы "заморозить" -// текущий вход. phaseJitter через педаль добавляет "дыхание" -// frozen-спектра. -// -// 2. На тихой паузе (Letov молчит) — включить freeze, и звук -// "дышит" сам по себе. Снять freeze — вернуться к реальному -// входу. -// -// 3. Когда Letov играет активно — scatterOnly поверх, с -// разбросом позиции через .map(\positionJitter, .kr). -// -// 4. freezeAndScatter: сначала freeze, затем scatter по -// frozen-сигналу. Получается "зависший" звук, который -// разбивается на зёрна — главный спецэффект скетча. -// -// ---- Остановка ------------------------------------------------------- -// Ndef(\freezeOnly).stop; -// Ndef(\scatterOnly).stop; -// Ndef(\freezeAndScatter).stop; -// ~fftBuf.free; -// ~ringBuf.free; -// ~inBus.free; -// ~freezeBus.free; -)