Extended PV Tools/FFT

Hi all -

I had a specific question - and a more broad question.

The specific question: I’d like to listen to a set collection of bins at one time - is there a good uGen for doing this?

The more broad question: my understanding is that pvcalc and pvcalc2 are the main tools for designing one’s own FFT-based tools in SC - but I’m not seeing a lot in terms of documentations or tutorials. I was wondering if anyone here had some suggestions for things to look at or ways of getting better acquainted with these possibilities.

Thanks for the help.

I think FFT in Supercollider can get really tricky, really quick. In my experience you can do most of the things you need through .pvcalc, .pvcalc2 and also .pvcollect. If you wish, there are also UnpackFFT and Unpack1FFT which can be very useful for certain things (i.e. getting specific values from an FFT chain). However, I find them not that flexible and they can become very confusing sometimes: in my case I had to resort in writing my own UGen, but that really depends on your goal.

For your specific case, I think that .pvcollect might be the method you need, silencing the bins you do not want to listen to and even restricting the range of the bins you process (for CPU power concerns).

All the best,
Stefano

1 Like

I am also interested in such a topic, as recently I have been trying to port some spectral patches from the Plogue Bidule modular environment, and the FFT modules inside operates either on frequency bins, magnitudes or both. This led me to wonder how magnitudes and bins are recombined, and how such a thing could be achieved in SC. (I apologize in advance over my glossing over operating principles of FFT although I do understand bins are represented as imaginary number laid in a chain buffer and that the magnitude is the norm of a frequency bin)

Can one just add the result of a chain operating on magnitudes to another chain working only on bins?

Some of the PV operators are annotated to work with magnitudes, but it is not obvious how the others work on bins and magnitudes unless you look at the UGen C++ code.

Regarding pvcalc2, is there a reason you cannot seem to operate on the bins of each chain as well as magnitudes? It feels like whatever is being done in Bidule could be done in SC with such a change in the API without having to make an UGen.

Hi Herbb -

I’ll do my best to tell you what I know - but keep in mind, it’s pretty limited.

FFT modules work based on collecting “frames” of audio. The frames are comprised of “samples”, in the time domain - but they become “bins” after an fourier transform, in the frequency domain.

The bins are comprised of two values: a magnitude value and a phase value. So, to answer your question - I think you should be able to operate on the bins directly with these tools, and within the bin, you should be able to manipulate the magnitude.

Another solution for custom FFT processing is DynGen.

It’s somewhat limited to roll your own FFT in SC itself. A big reason is simply the size of the resulting SynthDefs. Every bin needs to have its own processing chain, in parallel with every other bin. (Note, The unit generators inside the SynthDef don’t run in parallel, and can’t – I’m referring to the structure of the signal flow.) When you have several hundred, or over a thousand, bins, the SynthDef gets very large, maybe not too speedy. So we’ve usually said for custom FFT processing, it’s usually more efficient to write it in C and expose it as a new UGen.

DynGen is basically like writing a new Ugen, except it isn’t in C and doesn’t require a separate compilation step (and you can hot swap the script at runtime, which will accelerate the development process). It’s not free of challenges – learning eelscript (Reaper script), learning how DynGen uses eelscript, learning the specific API to access FFT bins – but it would be worth it, especially if you expect to do this a lot.

There’s an FFT example in the DynGen help file.

I haven’t used it for FFT, so I don’t have any particular experience to report. Just pointing out that it’s there.

hjh

1 Like

Thanks for the recommendation, @jamshark70

Are there any thoughts about using Faust for similar purposes? It’s daunting to select a new language to learn, since I’m not a great coder.

With regards to DynGen - are there any good resources for learning eelscript for this purpose? DynGen is relatively new in the SC world, is that right?

You can give it a try, there is also a faust integration for supercollider, see GitHub - capital-G/sc_faust: Faust DSP compiler as SuperCollider UGen · GitHub and the FFT docs for faust analyzers - Faust Libraries

The language that DynGen is based upon is called EEL2, and you can look into the source code of Reaper JSFX effects using FFT to get some inspiration since JSFX is also based on EEL2.
The biggest quirk of EEL2 is its memory/array layout, but it is actually really nice once you figure it out IMO - everything should be covered by the docs of DynGen.

The main difference between DynGen and Faust is probably their programming paradigm: Faust is a functional programming language while DynGen is imperative.

what about using two PV_BrickWalls in series? may not be accurate, but it’s quick to try

or if it’s a load of arbitrary bin numbers make a thing for MagMul

[ unfortunately it does seem to get to “just make a UGen” quite often & quickly these days on here afaics ]

For FFT, that’s been the case for years, not “these days” – and for non-FFT operations, it hasn’t really changed: If it was straightforward to do with existing UGens, it’s still straightforward to do. (E.g., dietcv’s very impressive work on polyphonic phaser and trigger control came about because these are things that were never straightforward to do with existing UGens.)

The obstacle in SC for custom FFT, as noted, is that the only way to access multiple bins is through a heavily parallel SynthDef structure, where the calculation happens only once every n control blocks: distributing the work over a large number of UGens that are idle most of the time, instead of running a smaller number of units at audio rate (which is what Max and Pd do). A future FFT design for SC could take the hint from Max and PD, something like:

// assuming you're in a synthdef
var fftPhasor = Phasor.ar(0, 1, 0, fftFrames);

// 2 outputs: real, imag
// FFT and IFFT run when fftPhasor resets to 0
var fft = FFT(fftPhasor, sig), ifft;

// ... do stuff to the complex values...
// e.g., scale all bins according to amps in a buffer
fft = fft * BufRd.ar(1, buf, phase: fftPhasor, loop: 1, interpolation: 1);

ifft = IFFT(fftPhasor, fft[0], fft[1], isPolar: 0);

(… perhaps with some refinement to simplify reflection of FFT bins around the Nyquist bin.)

Then most of the current PV units could be pseudo-UGens.

That could be implemented today without modifying server architecture, if someone had time and wanted to do it.

hjh

1 Like

There’s an undocumented UGen in SC3Plugins called PV_MagScale that will do what you want. Takes two buffers as args - the FFT buffer and another buffer of magnitude multipliers (0-1) for each bin. So, a buffer of the same size as the FFT buffer, filled with 1’s, will give you the original signal. All 0’s will be silent. Etc. So, you can set all the bins you don’t want to 0 if you want an arbitrary “brick wall” or “comb”.

1 Like