SC vs MAX in FFT Process?

What are the advantages and disadvantages of doing FFT processes in SC in comparison to Max?
Which use-cases are better in one or the other, etc., etc. Looking for quick thoughts and directions.

FFT in Pd and Max is implemented via reblocking. The resulting FFT is just an ordinary audio signal and can be manipulated with all the existing signal objects.

FFT in SC is implemented with buffers and you need a dedicated UGen for every possible operation.

For example, in Pd you can write a complete phase vocoder timestretcher/pitchshifter with standard Pd objects. In SC you have to write a C++ UGen.

IMO SC’s FFT implementation is definitely one of its weaknesses. Needless to say, there are many things that are much easier to do in SC than in Pd. And some things are just a matter of personal preference or habit.

3 Likes

This makes me wonder, with your recent work on reblocking in scsynth, is there potential for a new FFT ecosystem more in line with Pd’s?

4 Likes

Unfortunately not. In Pd you can reblock any section of your patch simply by moving it into a subpatch. SC only offers this tree-like structure on the Node level, but not inside a Synth. My resampling/upsampling implementation works on the Synth level, which means that FFT processing would have to span the entire Synth. This would be pretty awkward and not very practical.

@jordan has already asked if it would be possible to do reblocking/upsampling on only a subsection of a UGen graph. This was my answer:

I actually thought about that. This would be very cool, but it would be pretty complicated to implement. You would not only have to manage resampling and reblocking in the I/O UGens but also between UGen inputs and outputs “at the border”. I guess this could be done by secretely inserting special UGens that do the conversions. Also, you would have to segment the Graph into subgraphs, each with its own context for the rates and tick count. As a consequence you would also have to extend the SynthDef format.

Reblocking/resampling only a section of a graph could be very interesting because it would theoretically enable Pd-style FFT via reblocking and overlap. The question is whether it’s worth the complexity. But someone can give it a try :slight_smile: I certainly won’t have the time and energy to do it.

1 Like

A fun thing to try would be to set the BlockSize and FFT size to the same value, say 1024, then use BufRd to read the FFT buffer to audio, do some normal math on the FFT signal, and write the signal back into a buffer before IFFT. Should work.

I’m trying to twist my brain around this. Can the new reblocking run a synth at a bigger block than scsynth or only smaller? If it can run it at a bigger block, I would say this would be enough. The synth would just run in a big block. Any signal that goes through an FFT is going to be dealing with the FFT delay anyway, so why would it matter if it were running at a block size of 16384 or whatever?

Sam

Only smaller. This is pretty simple to implement because downblocking only means reading/writing a smaller number of samples at a particular offset (based on the subtick count).

Once you reblock to a larger sample rate you have to deal with two issues:

  1. each input I/O UGen would need to buffer the input signal, significantly complicating the code

  2. scsynth’s maintains a fixed number of wirebuffers, each the size of the Server block size.
    If the Synth’s block size is smaller than the Server block size, there is no problem, we just use the first N samples of each buffer.
    But what shall we do if the block size is larger than the Server block size? The wirebuffers are allocated in a single big array, so in theory we can just index in that array. However, this would mean that the number of available wirebuffers depends on the Synth block size. The default number of wirebuffers is 64. With a Server block size of 64 samples this corresponds to a total of 4096 samples. At a block size of 4096, this would only be enough for single wire buffer!
    Side note: Supernova does not have this issue because every Synths manages its own independent set of wire buffers (for thread safety reasons).

Any signal that goes through an FFT is going to be dealing with the FFT delay anyway, so why would it matter if it were running at a block size of 16384 or whatever?

But how would you implement overlap?

Yeah. I just tried this and the overlap won’t work. It will kind of do a hop size of 1. Oh well!

Sam