FFT.HMSL version 1.0 September 19, 1988 INTRODUCTION FFT stands for Fast Fourier Transform. There are a variety of ways to describe what a Fourier Transform is and what it does. I'll stick to a purely musical description, specifically: what a Fourier Transform does to a sound sample. The FFT accepts a sequence of sample points as input, and outputs the energies contained in its partials. A single FFT tells you nothing about how a sound changes over time, only that its first partial has "this" much power, its second "this" much, its third "that" much, etc. Timbres are completely describable in terms of how much energy is contained in the various partials. However, most sounds change considerably from start to finish. The strategy, then, is to do a sequence of small FFT's on little chunks of the sample, maybe 32 points at a time. This 32 point spread is called a "window". The window scoots along the sample, taking FFT's at every stop. What you're finally left with is a nice report of how each partial of the original sound changed over time. This is what the routine FFT.H does. An inverse Fourier Transform does the opposite: it recombines all the partials into a single sound sample. This is done by the routine IFFT.H The power of these two routines is dramatic. After doing an FFT.H on a sound, you can go in and change partials around: delete some, invert others, reverse them, draw your own, write routines to generate or alter them in ways that God never intended. Then run the IFFT.H routine to recombine them, and listen to your new sound! Or create sounds from scratch by using IFFT.H alone. ************************** RUNNING THE CODE ****************************** PLEASE DO THE TUTORIAL BEFORE YOU TRY THIS SECTION!!!!!!!! 1) Get HMSL up and running. 2) INCLUDE 4th:FFT/FFT.HMSL ( read file READMEFIRST about renaming disk. etc) If your version of JForth is v1.2, edit the file X*.ASM and remove the HASH.ON and HASH.OFF lines before you do this. 3) Load up a sample into any OB.SAMPLE like this: " MY-SOUND-FILE" LOAD: SAMPLE-IN I instantiated two sample objects: SAMPLE-IN and SAMPLE-OUT in this file. Use them for convenience only; the code doesn't rely on them. 4) Now type INIT.FFT.HMSL (you only have to do this step once, even if you load other samples later) 5) To run the FFT routine, type: SAMPLE-IN FFT.H 6) wait a while (roughly 12 seconds for a sample with 2048 points) 7) get the shape editor up by typing HMSL, and you'll see two objects in there called MAGNITUDES and PHASE INDICES. MAGNITUDES is the shape which contains the amplitudes of the various partials. Flip through the dimensions. PHASE INDICES might look a bit anti-intuitive. Its even numbered dimensions store phase angle information in terms of index values to a trig lookup table instead of in terms of degrees. This is done so for speed, and is extremely necessary. Odd numbered dimensions contain -1's and 0's, which store the signs of some values needed for the IFFT. Hit the close box to get back. 8) To run the inverse FFT routine, instantiate a target sample: let's say OB.SAMPLE SAMPLE.OUT and type: SAMPLE-OUT IFFT.H 9) wait a while. 10) listen to the result START: SAMPLE-OUT IMPORTANT: If you look through the source code, you'll see that the FFT.H and IFFT.H routines don't necessarily expect OB.SAMPLE's. You can pass them OB.ARRAY's if you care to. The data width of the object you pass it is also variable, so any of you who have routines for loading samples from external samplers should bear this in mind. *************************** CHANGING PARAMETERS ***************************** As mentioned above, a "window" creeps over your sample, taking FFT's as it goes. The size of the window defaults to 32, which you can change with SET.WINDOW.SIZE ( size -- ). Just don't try any size that's not a power of 2. Use 2, 4, 8, 16, 32, 64, 128, or 256. I've never tried values that aren't in this list, but I'm not too curious about what would happen to tell you the truth. The FFT presumes that the width of the window = width of the lowest period in the sample. At 10k sample rate, window size of 32 means the lowest frequency you can resolve is about 312 Hz. A window size of 64 means you can do pretty well down to around 156 Hz. The tradeoff: The wider the window, the coarser the detail of spectral change over time. On the other hand, a narrow window will yield few partials. The number of partials generated equals half the window size. You'll get 16 partials with a 32 wide window. The amount of the sample you want to work with can be set with SET.SAMPLE.SIZE ( size -- ). It defaults to 2048. This means you'll be running the FFT on the first 2048 values of your input sample, and that your output sample will be allocated a size of 2048. If your input sample has more than 2048 points, the rest will be ignored, if it has less, the surplus will be padded with zeros. The input sample will be unharmed in any case. *************** A LITTLE MORE ON MAGNITUDES and PHASE-INDICES *********** The number of dimensions in these shapes correspond to the number of partials generated by the FFT. This number depends on the window size. A window 64 wide will generate 32 partials (half). MAGNITUDES would in this case have 33 dimensions; one for each partial and an extra dimension that stores a very important value: the WINDOWSIZE/2+1st "partial". PHASE-INDICES will have 64 dimensions: two for each of 32 partials ( one to hold the index to a sine lookup table, one to hold on to the sign of the real component generated by the FFT). The number of elements (MANY:) in these two shapes will equal the sample size you set divided by the window size. Looking at MAGNITUDES in the shape editor means you can click through the partials and see how the magnitude of that partial changes over the course of the sample. You can and should mess with them. Same with PHASE-INDICES: each even numbered dimension will show you how the phase relationships of the sample's partials change over time, while the odd numbered dimensions hold 0 or -1, used for IFFT. ********************************* TOOLS ************************** To get some nice tools to make your acoustic debauchery faster and easier, INCLUDE FFT.TOOLS Here's a glossary of words contained in this file COMPARE.THEM ( start -- ) testing only. Prints values from SAMPLE-IN and SAMPLE-OUT starting at start, continuing until terminal hit. Used to compare original sample with sample after FFT.H and IFFT.H ENV-TEMPLATE This is not a word, this is an OB.SHAPE used to generate n-stage magnitude envelopes. It has two dimensions. Dimension 0 is an increasing series of values, and should not be altered unless low-to-high ordering is preserved. Dimension 1 contains "magnitude" values. The "shape" of this dimension is used to carve out a nearly identical profile in any dimension of MAGNITUDES. see STAGE.ENVELOPE, FILL.DIM.ENV FILL.DIM.ENV ( dimension -- ) Creates a new set of points in selected MAGNITUDES dimension. Uses INTERP between values in ENV-TEMPLATE. example: 5 FILL.DIM.ENV takes the current n-stage envelope prescribed by ENV-TEMPLATE and leaves the fifth MAGNITUDE dimension filled with values that follow the profile of ENV-TEMPLATE. FILL.DIM.MAG ( value dim -- ) Fills magnitude values of a given partial with given value. FILL.DIM.PHASE ( value dim -- ) Fills phase values of a given partial with given value. INIT.INSTRUMENTS ( -- ) Used for testing. Loads the object SAMPLE-IN into INS-AMIGA-1 and SAMPLE-OUT into INS-AMIGA-2. Puts TUNING-EQUAL into these instruments. PLAY.BOTH ( end-note start-note -- ) Used for testing. Plays SAMPLE-IN and SAMPLE-OUT alternately up selected range of notes by NOTE.ON:ing INS-AMIGA-1 and INS-AMIGA-2 INIT.INSTRUMENTS first. PLAY.OUT ( end-note start-note -- ) Used for testing. Plays SAMPLE-IN up selected range of notes by NOTE.ON:ing INS-AMIGA-2 INIT.INSTRUMENTS first. PLOT.M ( -- ) Plots contents of MAGNITUDES in mountain-style graph. Only plots 50 elements, so if MAGNITUDES has, say, 200 elements, PLOT.M will plot every 4th. Frequency increases left to right, time axis increases "away" from you. Magnitude is vertical. PLOT.FIRST.50 ( -- ) Plots first 50 elements of MAGNITUDES. A nice detailed look at the attack portion of your sound. STAGE.ENVELOPE ( #stages -- ) Sets up how many stages in magnitude envelope. Initializes ENV-TEMPLATE. Initializes all with value 30. example: 10 STAGE.ENVELOPE sets up a 10 point envelope that you can edit and apply to any MAGNITUDE dimension with FILL.DIM.ENV ZERO.DIM.MAG ( dim# -- ) zeros out all magnitude values of a given partial same as 0 dim# FILL.DIM: MAGNITUDES. ZERO.ALL.MAG ( -- ) zeros out all magnitude values of all partials. ZERO.DIM.PHASE ( dim# -- ) zeros out all phase values of a given partial ZERO.ALL.PHASE zeros out all phase values of all partials. ************************** A QUICK TUTORIAL ****************************** We'll run various routines on the sample P2. This sound, by the way, is the second note of the Indonesian Pelog scale, as sampled from an instrument I borrowed from Gamelan Son of Lion (NYC based new music group). Load up HMSL and CD 4th:fft ( read file READMEFIRST about renaming disks, etc) 1) INCLUDE FFT.HMSL If your version of JForth is v1.2, edit the file X*.ASM and delete the HASH.ON and HASH.OFF lines before you do this. 2) INCLUDE FFT.TOOLS 3) " P2" LOAD: SAMPLE-IN 4) INIT.FFT.HMSL 5) SAMPLE-IN FFT.H That should take less than 12 seconds. 6) HMSL Look through the MAGNITUDES dimensions. All have a tendency to slope down because the sound fades out. Notice dimension 11. Large values there. That's the "ching" of the mallet. A high bright partial. Want a better view? Close the HMSL screen and type: 7) PLOT.M This is a "mountain graph" of MAGNITUDE values. There's a long smooth ridge far left. That's the lowest partial of the sound. There's another ridge around the middle of the screen. That's a higher partial (the 6th). See that nice big peak far right? That's the high "ching"! 8) GR.CLOSECURW closes the graphing window. 9) Now let's recombine all the partials to resynthesize a sample. SAMPLE-OUT IFFT.H That should take less than 10 seconds. 10) SAMPLE-OUT now contains the resynthesized data, and you can play it like any other sample. Compare SAMPLE-IN with SAMPLE-OUT: 40 35 PLAY.BOTH You hear that SAMPLE-OUT is noisier and shorter than SAMPLE-IN. 11) Use SET.SAMPLE.SIZE ( size -- ) to change how much of the sample is being used. (The size defaulted to 2048 when you INIT.FFT.HMSL'ed.) type: 6000 SET.SAMPLE.SIZE Now try, just as before: SAMPLE-IN FFT.H SAMPLE-OUT IFFT.H 40 35 PLAY.BOTH You'll note that the resynthesized sample is longer. Try typing: MANY: SAMPLE-OUT . It's not 6000, is it? That's because SET.SAMPLE.SIZE divides your request by the current window size and dumps the remainder. Remember that the window scans your sample end-to-end, and a fraction of a window is a meaningless thing to subject to an FFT. 12) Use SET.WINDOW.SIZE ( size -- ) to change the size of the window. As mentioned above, this will increase or decrease the number of partials generated (#partials=half-of-window-width). type: 64 SET.WINDOW.SIZE Now do your FFT.H and IFFT.H, and use PLAY.BOTH to compare the input sample with the resynthesized sample. Enter the shape-editor by typing HMSL and look through the MAGNITUDE dimensions. Notice that there are 32 partials this time, but that there is a loss of profile detail. The bigger the window, the less it will be able to track fine changes in the source sample. As was mentioned above, a window size that is not a power of 2 is meaningless. Use 2, 4, 8, 16, 32, 64, 128, or 256 as arguments to SET.WINDOW.SIZE. 13) Now to edit partials for resynthesis. First, reset window size to 32 and sample size to 2048. Run the FFT.H on SAMPLE-IN. You now have 16 partials to play with. We'll establish a, say, ten-stage envelope and use this to carve out magnitude profiles of selected partials. Set up the envelope: 10 STAGE.ENVELOPE You'll want to be able to edit it with the mouse, so type: ENV-TEMPLATE ADD: SHAPE-HOLDER 14) Type HMSL to enter the editor, and flip through until you see and select ENV-TEMPLATE. You'll see that dimension 0 is a rising line. Leave it alone. These are index values to MAGNITUDES. Flip to dimension 1. It's named AMP for AMPlitude. Make sure your SET MODE is on REPLACE. Go ahead and chisel out a profile. Don't set values too high. I find that even very small values have a great effect (around 10-20). Exit the editor. 15) Type PLOT.M (not necessary, but a good demo) Apply the envelope to the, say, thirteenth partial by typing: 13 FILL.DIM.ENV PLOT.M again. See what you just did? *** Go back and forth between steps 14 and 15 as often as you like. *** 16) Finally, do a SAMPLE-OUT IFFT.H and listen to it. You should be able to get some extremely interesting sounds this way. **************************** END OF TUTORIAL **************************** General notice to users: Anyone who can make this thing faster and more accurate, please contact me. This code is free and in the public domain. Please pass on the origin of the included sample P2 along to anyone you give it to. If you change the source code, please comment, date, and sign your handiwork before you upload it somewhere or otherwise pass it on. Feel free to call or write for support, but don't feel snubbed if it takes a day or two for me to return your call. Nick Didkovsky 171 east 99th street #2O NYC NY 1OO29 (212) 369-1733 Great thanks to Robert Marsanyi who helped inestimably during the great "HMSL Blowout, Summer '88" This code would not exist without him.