home *** CD-ROM | disk | FTP | other *** search
- \ FFT.HMSL
-
- \ Nick Didkovsky, Robert Marsanyi August 21, 1988
-
-
-
- \ These routines perform Fast Fourier Transform and inverse FFT
- \ on HMSL array or sample objects. Partials and phase angle data are made
- \ available in the shape editor for manipulation.
-
- \ Usage is sample-in fft.h
- \ or sample-out ifft.h
-
- \ This version runs end-to-end windows across the sample.
- \ Window size defaults to 32, changeable.
-
- INCLUDE? FFT 4TH:FFT/FFT.JF
- INCLUDE? ARCSIN 4TH:FFT/ARCSIN
- EXISTS? TERM.FFT.HMSL .IF TERM.FFT.HMSL .THEN
-
- ANEW TASK-FFT_HMSL
-
- OB.SAMPLE SAMPLE-IN
- OB.SAMPLE SAMPLE-OUT
- OB.BARRAY FFT-IN
- OB.BARRAY IFFT-OUT
- OB.SHAPE MAGNITUDES
- OB.SHAPE PHASE-INDICES
-
- VARIABLE MAX-SAMPLE-SIZE ( 2048 data point dafault)
- VARIABLE WINDOW-SIZE ( 32 default)
- VARIABLE SAMPLE-MAX ( 128 for 8 bit sampling)
- VARIABLE DATA-WIDTH ( 1 for 8 bit sampling)
- VARIABLE FFT-INITED?
- FFT-INITED? OFF
-
- 256 128 XVECTOR WINDOW ( initialize: 256 is alloted to WINDOW.)
- ( DON'T make window bigger than this or )
- ( you'll clobber your dictionary!!)
- : FREE.ALL.FFT
- CLEAR: SHAPE-HOLDER
- FREE: FFT-IN
- FREE: IFFT-OUT
- FREE: SAMPLE-OUT
- FREE: MAGNITUDES
- FREE: PHASE-INDICES
- ;
-
- : SET.LIMITS.PHASE
- WINDOW-SIZE @ 2/ 0 DO
- -999 999 I PUT.DIM.LIMITS: PHASE-INDICES
- LOOP
- ;
-
-
- : INIT.ALL.FFT.OBJECTS
- FREE.ALL.FFT
- MAX-SAMPLE-SIZE @ NEW: SAMPLE-OUT
- MAX-SAMPLE-SIZE @ NEW: IFFT-OUT
- MAX-SAMPLE-SIZE @ NEW: FFT-IN
- MAX-SAMPLE-SIZE @ WINDOW-SIZE @ / DUP DUP DUP ( #els for next 2 shapes)
- WINDOW-SIZE @ 2/ 1+ NEW: MAGNITUDES ( #partials = window-size/2+1 )
- SET.MANY: MAGNITUDES
- " HANDS OFF" WINDOW-SIZE @ 2/ PUT.DIM.NAME: MAGNITUDES
- WINDOW-SIZE @ NEW: PHASE-INDICES ( 2 phase dims per partial, see below)
- \ CLEAR: PHASE-INDICES
- SET.MANY: PHASE-INDICES
- SET.LIMITS.PHASE
- MANY: SAMPLE-IN IF SAMPLE-IN ADD: SHAPE-HOLDER THEN ( anything slicker?)
- SAMPLE-OUT ADD: SHAPE-HOLDER
- PHASE-INDICES ADD: SHAPE-HOLDER
- MAGNITUDES ADD: SHAPE-HOLDER
- ;
-
- \ note that the WINDOWSIZE/2+1st dimension of MAGNITUDES contains the
- \ WINDOWSIZE/2+1st real value resulting from FFT
-
- : SET.WINDOW.SIZE ( width -- )
- DUP 256 > IF ABORT" Window width MUST be 256 or less!" THEN
- DUP WINDOW-SIZE !
- WINDOW ! ( -- , first word in WINDOW is number of entries)
- INIT.ALL.FFT.OBJECTS
- ;
-
- : SET.SAMPLE.SIZE ( size -- )
- WINDOW-SIZE @ / WINDOW-SIZE @ * ( round off points to multiple of window)
- MAX-SAMPLE-SIZE !
- INIT.ALL.FFT.OBJECTS
- ;
-
- : REMIND.ME
- cr cr
- ." SET.WINDOW.SIZE ( size -- ) will change WINDOW width." cr
- ." SET.SAMPLE.SIZE ( size -- ) changes maximum length of sample" cr
- ." <sample> FFT.H runs the Fast Fourier Transform, " CR
- ." <target> IFFT.H is the inverse." CR CR
- ." REMIND.ME to see this message again" cr
- ;
-
- : INIT.FFT.HMSL
- 2048 MAX-SAMPLE-SIZE !
- 128 SAMPLE-MAX !
- 1 DATA-WIDTH !
- cr cr
- ." SAMPLE-MAX defaulting to 128 (8 bit sampling)" cr
- ." DATA-WIDTH defaulting to 1 (8 bit sampling)" cr
- ." DON'T CHANGE ONE WITHOUT THE OTHER!!!" cr cr
- ." WINDOW-SIZE defaulting to 32" cr
- ." MAX-SAMPLE-SIZE defaulting to 2048" cr
- REMIND.ME
- 32 SET.WINDOW.SIZE
- MAX-SAMPLE-SIZE @ NEW: SAMPLE-OUT
- MAX-SAMPLE-SIZE @ NEW: IFFT-OUT
- MAX-SAMPLE-SIZE @ NEW: FFT-IN
- LOADTABLE ( sine lookup table)
- FFT-INITED? ON
- ;
-
- : TERM.FFT.HMSL
- FREE: SAMPLE-IN
- FREE.ALL.FFT
- ;
-
-
-
-
- \ **************************** FFT code starts ****************************
-
-
- \ WINDOW contains WINDOW-SIZE spectrum entries. We only want the first half,
- \ since the second half is just complex conjugate of first half.
-
-
-
-
- : ROUND ( tens-int -- ones-int)
- DUP 0< IF NEGATE 5 + 10 / NEGATE ELSE 5 + 10 / THEN
- ;
-
-
- : |X|.SCALE.UP
- |X|^2 10000 * SQRT ;
-
- : LOAD.WINDOW ( el# -- , load 32 points into WINDOW starting at el#)
- DUP WINDOW-SIZE @ + SWAP DO
- I AT: FFT-IN b->s ( load up stack with signed audio data)
- LOOP
- SAMPLE-MAX @ WINDOW-SIZE @ WINDOW VECTOR! ( interleaved load to WINDOW)
- ;
-
- \ These words load up one time slice. ie, all elements in one dimension.
- : LOAD.MAGNITUDES ( el# --, load magnitudes of partials into MAGNITUDES)
- WINDOW-SIZE @ 2/ 0 DO
- I 1+ WINDOW X@V |X| ( complex fetch, calc magnitude)
- OVER I ED.TO: MAGNITUDES ( el# -- store |X| into MAGNITUDES)
- LOOP
- \ Now for that weird window-size/2+1st value !
- \ Note that (for example) the 17th element of WINDOW goes to
- \ the 16th dimension of MAGNITUDES
- WINDOW-SIZE @ 2/ DUP 1+ ( -- el# window-index window-index+1)
- WINDOW X@V NIP ( -- el# window-index real-value)
- -ROT ED.TO: MAGNITUDES
- ;
-
- \ PHASE-INDICES has 2 dimensions for each partial. Partial 0 maps to
- \ dimension 0 and 1, where 0 holds the sine-table index, and 1 holds
- \ a flag for the real component's sign. Since we are using a sine table,
- \ we calculate phase angle from imaginary component and magnitude. This
- \ way, the sign of the real component can get lost, unless it is flagged
- \ some other way. Here's the way.
-
- VARIABLE SIGN-FLAG
-
- : LOAD.PHASE-INDICES ( el# -- )
- 0 SIGN-FLAG !
- WINDOW-SIZE @ 2/ 0 DO ( loop through partials)
- DUP I ED.AT: MAGNITUDES ( -- el# |X|, borrow magnitude from MAGNITUDES)
- DUP 0= IF DROP 0 OVER I 2* ED.TO: PHASE-INDICES ( no mag? no phase!)
- 0 OVER I 2* 1+ ED.TO: PHASE-INDICES ( -- el#, zero flag)
- ELSE
- I 1+ WINDOW X@V ( -- el# |X| imag real)
- 0< SIGN-FLAG ! ( -- el# |X| imag, save sign of real component)
- ( -- el# |X| imag, only want imaginary part)
- 10000 ROT ( -- el# imag 10000 |X|)
- */ ( scaled divide)
- ARCSIN.INDEX ( -- el# arcsinindex)
- OVER I 2* ED.TO: PHASE-INDICES ( -- el# )
- SIGN-FLAG @ OVER I 2* 1+ ED.TO: PHASE-INDICES ( -- el#)
- THEN
- LOOP
- DROP ( don't need el#)
- ;
-
- \ This word sets data-width (1 for 8 bit sampling)
- \ Don't change data-width without changing SAMPLE-MAX !!!
- \ Note that the data crammed into FFT-IN will be unsigned.
- \ It becomes signed in LOAD.WINDOW
-
- : COPY.TO.FFT.IN ( sample_addr -- )
- DUP DUP ( -- addr addr addr)
- WIDTH: [] DATA-WIDTH ! ( -- addr addr)
- FREE: FFT-IN
- DATA-WIDTH @ SET.WIDTH: FFT-IN
- DATA-WIDTH @ MAX-SAMPLE-SIZE @ * NEW: FFT-IN
- \ CLEAR: FFT-IN
- MANY: [] ( -- addr many-in-input)
- MAX-SAMPLE-SIZE @ MIN ( -- addr min-number-of-data-to-move)
- DATA-WIDTH @ * ( -- addr min-number-scaled)
- SWAP
- data.addr: [] data.addr: fft-in rot move
- ;
-
- \ ******************** THIS IS THE MAIN FFT WINDOW LOOP *******************
-
- \ see if the imaginary components are indeed always 0 in 1st and 17th
-
- : FUNKY WINDOW XVECTOR. ; ( print WINDOW)
-
- \ The window scans through sample end-to-end.
- \ Usage is: sample-name fft.h
-
- : FFT.H ( sample_addr -- )
- cr
- FFT-INITED? @ NOT IF INIT.FFT.HMSL THEN
- DEPTH IF COPY.TO.FFT.IN ELSE ." Need a sample name!" ABORT THEN
- MAX-SAMPLE-SIZE @ WINDOW-SIZE @ / 0 DO ( # windows loop)
- I WINDOW-SIZE @ * ( starting point)
- LOAD.WINDOW
- WINDOW FFT ( fft done in place! WINDOW contains spectrum now)
- I LOAD.MAGNITUDES
- I LOAD.PHASE-INDICES
- ." ." FLUSHEMIT
- LOOP
- CR
- ;
- \ ************************** IFFT code starts **************************
-
- \ real and imaginary components are retrieved using phase angle and
- \ magnitude. The code grabs 1 magnitudes and 1 phase angle
- \ from MAGNITUDES and PHASE-INDICES respectively,
- \ and derives real and complex components for FIRST half of WINDOW.
- \ The SECOND half of WINDOW consists of complex conjugates of the 1st half.
- \ remember window indexes from 1..WINDOW-SIZE, NOT 0..WINDOW-SIZE-1
- \ This routine also takes advantage of the odd/even of the el#, inverting
- \ the data on odd values, leaving even values alone.
-
- VARIABLE SIGN-BACK
-
- : INV.WINDOW.1 ( el# --)
- 0 SIGN-BACK !
- WINDOW-SIZE @ 2/ 0 DO ( loop through dimensions)
- DUP I 2DUP ( -- el# el# i el# i)
- ED.AT: MAGNITUDES ( -- el# el# i |X|)
- DUP 0= IF NIP NIP 0 ( -- el# 0 0, don't bother multiplying by 0)
- ELSE
- -ROT ( -- el# |X| el# i)
- 2DUP ( -- el# |X| el# i el# i)
- 2* ED.AT: PHASE-INDICES ( -- el# |X| el# i index-to-table)
- -ROT ( -- el# |X| index el# i)
- 2* 1+ ED.AT: PHASE-INDICES SIGN-BACK ! ( set flag for cosine)
- 2DUP ( -- el# |X| index |X| index)
- SIN.LOOKUP ( -- el# |X| ind |X| sin)
- 1000 */ ROUND ( -- el# |X| ind imaginary-component)
- -ROT
- COS.LOOKUP
- 1000 */ ROUND ( -- el# imag real)
- SIGN-BACK @ IF NEGATE THEN ( -- el# imag sign-corrected-real)
- THEN
- I 1+ WINDOW X!V ( -- el#, store result to window)
- LOOP
- \ The next two lines retrieve the WINDOW-SIZE/2+1st real value.
- \ Same notice: there is an index difference between where MAGNITUDES
- \ stores the value and index where it goes in WINDOW.
- \ If the window size is 32, the 17th entry into WINDOW is stored in the
- \ 16the dimension of MAGNITUDES.
- WINDOW-SIZE @ 2/ DUP 1+ >R ED.AT: MAGNITUDES 0 SWAP
- R> WINDOW X!V
- ;
-
- \ The remaining complex numbers are conjugates of the first half
- \ example, for 32 wide window: 2->32, 3->31, 4->30, 5->29, etc etc
- \ 17th value was retrieved from 17th dimension of MAGNITUDES.
- : INV.WINDOW.2
- WINDOW-SIZE @ 2/ 1+ 2 DO
- I WINDOW X@V X' ( take conjugate)
- WINDOW-SIZE @ 2+ I - WINDOW X!V ( store symmetrically)
- LOOP
- ;
-
- \ Take the result of one window IFFT and load up a chunk of IFFT-OUT
- : STUFF.FROM.WINDOW ( ifft# -- )
- WINDOW-SIZE @ * ( starting index in IFFT-OUT )
- WINDOW-SIZE @ 1+ 1 DO
- I WINDOW X@V NIP OVER I + 1- TO: IFFT-OUT ( store real part)
- LOOP
- DROP
- ;
-
- \ This word moves the result of IFFT from array IFFT-OUT to target object.
- \ Most likely this will be a sample object, but it could be an array or ...
- \ just in case it's SAMPLE-OUT you're using, I save you the trouble
- \ doing the put.#oneshot: and set.many: on it.
-
- : MOVE.TO.TARGET ( target.addr -- )
- DUP ( -- addr addr )
- FREE: []
- DATA-WIDTH @ OVER SET.WIDTH: []
- MAX-SAMPLE-SIZE @ OVER NEW: []
- DATA.ADDR: [] DATA.ADDR: IFFT-OUT SWAP MAX-SAMPLE-SIZE @ DATA-WIDTH @ * MOVE
- MAX-SAMPLE-SIZE @ SET.MANY: SAMPLE-OUT
- MAX-SAMPLE-SIZE @ PUT.#ONESHOT: SAMPLE-OUT
- ;
-
- : IFFT.H ( target.object -- )
- cr FREE: IFFT-OUT
- DATA-WIDTH @ SET.WIDTH: IFFT-OUT
- MAX-SAMPLE-SIZE @ NEW: IFFT-OUT
- \ CLEAR: IFFT-OUT
- MAX-SAMPLE-SIZE @ WINDOW-SIZE @ / 0 DO
- I INV.WINDOW.1 ( el# -- )
- INV.WINDOW.2
- WINDOW IFFT ( ifft done in place! WINDOW contains data points now)
- I STUFF.FROM.WINDOW
- ." ." FLUSHEMIT
- LOOP
- MOVE.TO.TARGET ( target.object -- )
- ;
-
-
-
- CR CR
- ." ***************************** FFT ************************************"
- CR CR
- ." Be sure to LOAD: a sample into SAMPLE-IN before you INIT.FFT.HMSL!"
- CR CR
- ." **********************************************************************"
- CR CR
-
-
-