2

我正在研究图像到声音的项目,并尝试在SuperCollider中实现加法合成。我想使用逆 DFFT 对(数百个)正弦波求和,而不是SinOsc为每个正弦波创建一个合成器。

所有 SuperCollider 文档都说,它消耗了由(并由函数转换)IFFT产生的称为“FFT 链”的东西:FFTPV_*

Time-domain signal -> FFT -> [PV_* -> PV_* -> ...] -> IFFT

但是对于我的应用,我不需要FFT 阶段,因为我已经知道我的信号在频域中是如何表示的。我想要的是:

Frequency-domain signal -> Manually constructed FFT chain -> IFFT

“频域信号”是一个 numpy 数组序列,表示我在 Python 应用程序中已经拥有的频域信号。所以,我需要将此信息传递给 SuperCollider。

据我了解, FFT 链意味着某种数据流,但我不明白如何手动将数据写入其中。

我也尝试过使用静音 FFT 链(例如 get FTTof Silence.ar),但我也不知道如何手动设置单个频率箱。

4

1 回答 1

3

这里有几个选项。

  1. 使用PackFFT Ugen。这使您可以使用任意 UGen 数组来表示幅度和相位。这是一个示例,希望比帮助文件中的示例更清楚您的目的:

    s = Server.local;
    s.waitForBoot {
    
    Routine {    
        n = 512;
    
        // massively multichannel control busses
        ~magBus = Bus.control(s, n);
        ~phaseBus = Bus.control(s, n);
    
        s.sync;
    
        ~synth = { var mags, phases, chain, snd;
            mags = n.collect ({ |i| In.kr(~magBus.index + i) });
            phases = n.collect ({ |i| In.kr(~phaseBus.index + i) });
            chain = FFT(LocalBuf(n*2), Silent.ar);
            chain = PackFFT(chain, n, [mags, phases].flop.flatten);
            Out.ar(0, IFFT(chain).dup);
        }.play(s);
    
        s.sync;
    
        // raise each bin magnitude in a random order.
        // eventually results in wide-band noise, so watch your ears...
        Array.series(n).scramble.do({ arg i;
            i.postln;
            ~magBus.setAt(i, -16.dbamp.rand);
            (0.01 + 0.2.rand).wait;
        });    
    }.play;    
    };
    

    请注意,这里的 FFT 缓冲区大小是频段数量的两倍。我相信这是正确的,但不是 100% 肯定。

  2. 使用PV_ChainUGen.pvcollect的和.pvcalc方法。(有关示例代码,请参阅帮助文件。)理论上,您可以使用 Array ref 作为 SynthDef 参数,并使用它来任意设置运行中的合成器的幅度和相位。在实践中,我发现这是一种脆弱的方法:SC 对 FFT 块大小很挑剔(它们受音频设备块大小的限制);非常大的 SynthDef 是有问题的;无论如何,语法最终变得非常可怕。

  3. 实际上,我不会直接使用正弦波,特别是FSinOscUGen,它使用非常有效的正弦近似值,或者DynKlang,它采用 Array 引用。

    这是一个有 1000 个实例的示例FSinOsc,发出安静的隆隆声;它目前在我的 i5 Macbook 上使用 22% 的 CPU(这包括任意平移每个振荡器):

    s = Server.local;
    s.waitForBoot { 
    
    n = 1000;
    
    ~freq = Array.rand(n, 20.0, 60.0).midicps;
    ~amp = Array.rand(n, 1/n * 0.01, 1/n * 0.5);
    ~pan= Array.rand(n, -1.0, 1.0);
    
    ~sines = Array.fill(n, { arg i; 
        { Pan2.ar( FSinOsc.ar(~freq[i], 0, ~amp[i]), ~pan[i]) }.play;
    });
    
    };
    

当然,选项 1 的效率要高得多——看起来大约是 10 倍。但为了简单起见,你不能击败选项 3。

于 2017-10-17T23:59:41.873 回答