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