spectral-algs #1

Merged
lambda merged 6 commits from spectral-algs into main 1 month ago

@ -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= в словаре.

@ -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;

@ -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],
),
)

@ -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]
),
)

@ -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;
}
}

@ -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);
});
}
}

@ -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);
);
});
}
}

@ -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);
});
}
}

@ -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,
);
});
}
}
Loading…
Cancel
Save