Ok, I’m now at my deal breaker – filtering. In real-world hardware analog circuits, filtering is relatively easy. Just add a tunable r-c (resistor-capacitor) circuit, or an op-amp. In software, it becomes a bit less straightforward.
The French mathematician Joseph Fourier developed a way to convert real-time-based measurements into the frequency domain, and back. Now known as the Fourier Transform, this bit of math lets us take time slices, determine their frequency components, zero out the higher frequencies as desired, and then invert the transform to get the time slice back for sending it to the speakers.
Easier said than done. Java doesn’t have a built-in Fourier transform class. There is a downloadable pack from netlib.org, but I don’t have an extractor for .tgz files yet. Fortunately, the FT is a very common exercise project for university students, and there are some public domain Java class implementations on the net. (None that are fully optimized, though)
There’s no point to my explaining the FT here. The important things to know are that the general version of the FT designed for computers is called the DFT (discrete Fourier Transform), an optimized version is the FFT (Fast FT), that even the FFT is slow, and the FFT requires time samples to be a power of 2 (e,g. – 1,024 or 2,024 samples).
I obtained my copy of the FFT class implementation from the Princeton site. It uses a separate class file called Complex.java that I had to hunt for. Save both files in the same directory as the software synth app, making sure that the file names match the class names used within the file. (I.e. – FFT.java and Complex.java). Also, if you’re using Netbeans, make sure to add a line at the beginning of each file for the package (I used “package my.adsr;”. Using the new code is just a matter of creating objects of the FFT and Complex classes, and making Complex arrays for holding the original time slice, the FFT frequency results and the time-domain results from the inverse FFT (IFFT).
Note that I had problems with Netbeans suddenly complaining that the FFT file needs “this” or “super” for referring to objects, but the errors mysteriously went away when I saved the file (or something. All I did was change the formatting a little and saved the file and that was that. I don’t know if I’ll get the error again when I open Netbeans in the future.)
I’ll add the actual code later, after I’m done fixing it.
For the moment, I’ll just talk about concepts.
I’m using a sampling rate of 16000 for the sound engine. The Java timer function only resolves to 1 ms intervals. I want to take time slices between 10 and 20 ms for various reasons (partly to keep array sizes small, and to allow for timely responses to changes to the MIDI keyboard controls when the user makes them). The FFT needs array sizes to be a power of 2. The FFT arrays need to be large enough to precisely resolve my 16,000 samples/second, but small enough to not affect the sound playback at 10 – 20ms intervals. I started out by choosing 1,024 for the array sizes.
My largest sound slices are 320 samples. So, for the first 3 calls from the timer, I have to keep track of where in the fftBuffer array I am, and just do a System arrayCopy (I know this is slow. I don’t know how to do this with a Component object yet). Run the FFT, process the frequency data, then run the IFFT and send the results to the sound engine. Cut the first 320 elements from fftBuffer (System.arraycopy(fftBuffer, 320, fftBuffer, 0, fftBuffer.length – 320);) and repeat. Call 4 is more tricky. For all subsequent calls, just copy the 320 samples to the end of fftBuffer. (For call 4, I need to adapt to the fact that the buffer array is 1024 elements, and the 320 samples would fall from 960 to 1240. This is accomplished simply by cutting the first 64 elements and copying the samples to the end of fftBuffer. One of the advantages (in my opinion) of having fftBuffer larger than the incoming sampled size of 320 is that this gives me some smoothing of the FFT results just in case the incoming sound changes abruptly at a sample boundary.
In the FFT output array, each element will represent step sizes of 16,000 / 1024 = 15.6 hz.
In my test program, I used a sample tone of MIDI note number 89, which is 1,396 hz. Printing the real components of the FFT and then plugging them into Excel, I can find the peak in “bin” 88, which corresponds to 1,390.625 hz. In fact, I’m running the oscillator through the ADSR first, so the energy levels are spread out across several bins. But the main peak is at 1,390.625 hz.
Another note is that the FFT “folds” the calculated results around fftBuffer size/2. What this means to us is that the contents of array elements 511 and 512 are the same frequency, 510 and 513, 509 and 514, etc. If we write 0.0 from element 512 to 1023, we lose half our sound volume from the IFFT output. My work-around is to do something like the following:
int filter = 100; // The bin I want to start filtering from
for(int i = filter; i < 1024 – filter; i++) y[i] = new Complex(0, 0);
I’ll get into the code details next time.