How to perform a FFT on a full sound file?

Hello !

You can skip this : I guess this will be the first of several topics regarding FFT, as I just started using it and understanding it, and it’s difficult to me, as I didn’t study math in high school and topic is really abstract. Excuse me if I’m confused about some concepts. If you’re in the same position as I am, you might find this video useful to get started : 3blue1brown : But what is the Fourier Transform? A visual introduction.

My question is : how do I get the result of a Fourier analysis off a complete sound file ?

Using the documentation, I came up with this code :

(
var filePath = Platform.userHomeDir ++ "/SC/AudioFiles/celloMono.wav";
var buffer = Buffer.read(s, filePath);
var synth;

~fftBuffer = Buffer.alloc(s, 2048, 1);

synth = {
	var audioFile = PlayBuf.ar(1, buffer, BufRateScale.kr(buffer), loop: 1);
	var chain = FFT(~fftBuffer, buffer);
}.play;

CmdPeriod.doOnce({
	buffer.free;
	~fftBuffer.free;
	synth.free;
});
)

(
~fftBuffer.getToFloatArray(action: { |array|
	array.postln;
});
)

Alas, I only get an array full of zeros when posting the content of the buffer…

By looping the sample ( loop: 1 ), I then got a real result (which is different every time…). This made me understand that the FFT() is constantly rewriting the buffer, and I get an array full of zeros because I call it after my sample has stopped playing.

So this is perfect for real time FFT, but the tool seems to be difficult to use for ‘NRT analysis’.

What I understood so far : I should change the FFT() winsize parameter so that it matches my file number of frames. But, as it also has to be a power of 2, I should first calculate the next power of two after the number of frames in my audio file, and then add some zeros at the end of the audio file buffer so that it matches this number. After that, I should loop the sample so every time I access the array, the FFT has been done on the full file duration.

This seems really unappropriate. Does anyone see what I missed ?

There is a quark called JoshMisc - in there is PVAna that will do all you need for you.

Best

Josh(Misc)

Cool ! Thank you very much for this.

I find it surprising that it comes as an extension and not a ‘native’ class somehow.

I will take a look at the AnaUtils.sc file to get comfortable with your work.

My first idea was to get a constant FFT() output so that I can understand it more clearly, but I guess digging into FFT.sc is the best way to get insights about what I can do with the Buffer content.

You can also do it in the language:

(
var size = 512, real, imag, cosTable, complex;

r = Signal.read(Platform.resourceDir +/+ "sounds/a11wlk01.wav");
r = r.addAll(Array.fill(r.size.nextPowerOfTwo-r.size, {0}));

imag = Signal.newClear(r.size.nextPowerOfTwo);
cosTable = Signal.fftCosTable(r.size.nextPowerOfTwo);

complex = fft(r, imag, cosTable);
[r, imag, (complex.magnitude) / 100 ].flop.flat
    .plot("fft", Rect(0, 0, 512 + 8, 500), numChannels: 3);
)

Sam

1 Like

@Sam_Pluta 's is more general use case. The PVAna I posted is for writing out files that can be later used by PV UGens in SC. You can still extract data from those if needed, but there is also header data, etc., that is in there for PV specific purposes.

Thanks for this second answer. I indeed ended up with a .scpv file that was unable to read :slight_smile: . My aim here is to have the bins array off a simple file so I can compare both input and output, and plot the bins until I can fully understand how they are modified by PV Ugens.

Unfortunately, the .read method doesn’t seem to exist inside the Signal class ? I can find a .readNew method (source), but it lacks documentation, and neither the path of the file as a String, as a PathName or, obviously, as a SoundFile worked as an argument.

What black magic are you using ?

yes - the scpv file is basically an array of floats. The first three are:

databufData[0] = buf->samples;
databufData[1] = IN0(5); // hop
databufData[2] = IN0(6); // wintype

This tells PV_PlayBuf and other UGens how to interpret the data. After that, the FFT data is added when a new frame is available:

if((unit->m_frame < numAvailFrames) && (run > 0.f)){
    frameadd = (unit->m_frame * buf->samples) + 3;
    databufData[frameadd] = p->dc;
    databufData[frameadd + 1] = p->nyq;
    for(int i = 1, j = 0; i <= numbins; i++, j++){
	itwo = i * 2;
	databufData[frameadd + itwo] = p->bin[j].phase;
	databufData[frameadd + (itwo + 1)]= p->bin[j].mag;
	}

    unit->m_frame++;
    }

So, dc, nyq then the phase and magnitude of each bin. This is then saved out as a float WAV file. You could open the scpv file as a sound file and interpret it as an array of 32-bit floats that follow the format above.

(and wow - looking at my C++ code from almost 20 years ago… that could be MUCH better now :smiley: )

Haha. Josh, it is still probably better than mine.

@Dindoleon, I apologize, but the Signal.read is found in Jo Anderson’s excellent SignalBox Quark, which incidentally has all kinds of great fft stuff in there as well. I recommend downloading it.

Sam

As @Sam_Pluta has mentioned, Signal.read can be found in SignalBox. Lots of fun stuff here!

Thank you all for the detailed answers and all the different ways to manipulate FFT !

I managed to build my own spectrogram, plotting an FFT Buffer content (so for now, just beginning to understand the basic FFT implementation), and I am really excited about all the possibilities I can see emerging from this practice.

I also got the motivation to go deeper into SC’s underlying concept, and took the time to read the class files, i.e. .sc files, that I was a bit afraid to look at at first.


Off topic, one remark, without any offense intended : Quarks names and contents are difficult to interpret. Without josh explicitly mentioning that there are some FFT tools inside the ‘JoshMisc’ quark, I think I would never had it installed. Same with SignalBox. The only interface I use to discover Quarks is the built-in IDE interface, which only provides names, which in turn are not always directly related to the Quarks content.

But I understand how little time we have to fully document everything, or structure them so that they fit only a particular need. I think the best way to discover Quarks is to go on Github and to begin reading the code, but it might difficult for newcomers or beginners, especially because the class syntax is not the same as the sclang topside syntax. At least it was how I felt about those, only daring using them after about 4 years of sclang usage.

That’s my way of saying that human interaction is the best knowledge transmission vector and that I’m grateful you took time to link the tools you develop and use :slight_smile: .

Regards,
Simon