This is FFTView.m in view mode; [Download] [Up]
/* FFTView.m -- Implementation for FFTView class * * See 'FFTView.h' for details * * jwp@silvertone.Princeton.edu, 12/89 * 2/5/90: Fixed getFrame to handle stereo by summing channels. * 2/21/90: Drawing now done via DPSDoUserPath() to save time. */ #import <stdlib.h> #import <math.h> #import <dpsclient/wraps.h> #import <appkit/Window.h> #import <appkit/Panel.h> #import <appkit/Application.h> #import <soundkit/Sound.h> #import "FFTView.h" #import "fft.h" #import "PWfft.h" /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* Shorthand for soundkit format codes: */ #define SNDSHORT SND_FORMAT_LINEAR_16 #define SNDFLOAT SND_FORMAT_FLOAT /* Shorthand for the PostScript call to draw the cursor */ #define DOCURSOR() PScompositerect(cursorpixel,0,1,bounds.size.height,NX_HIGHLIGHT); /* Handy shorthands for various conversions: */ #define WIDTH bounds.size.width /* Width of view in pixels */ #define freqBW srate/(npoints-1) /* Bandwidth of FFT (in Hz) */ #define pixelBW 2*WIDTH / (npoints-1) /* Bandwidth of FFT (in pixels) */ /* Shorthand for alert panel calls */ #define erralert(title, msg) NXRunAlertPanel(title, msg, "OK", NULL, NULL) /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* Functions in fft_net.c: */ extern void fft(int t, int n, int w, float *sb, int sf, int sc, float *rb, int rf, int rs); extern int power_of_two(int n); extern float *getframe(id sound,int startsamp, int nsamps); extern float scaledata(float *inptr, float *outptr, int nsamps, int scalemask, int *mean); /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ @implementation FFTView /* newFrame: -- Create an instance and initialize. * Default values: npoints = 512, inskip = 0.0 sec. */ + newFrame:(const NXRect *)frameRect { self = [super newFrame:frameRect]; [self setSound:nil]; /* This also resets inskip */ [self setNpoints:512]; PWinit(); /* Initialize PostScript stuff */ return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* setSound: -- Attach a sound to this view * This also resets cursor position and inskip to 0. */ - setSound:aSound { sound = aSound; inskip = cursorpixel = cursorpoint = 0; startsamp = 0; needsFFT = needsScaling = validData = NO; if (sound) { srate = [sound samplingRate]; if (sframe = getframe(sound,startsamp,npoints)) needsFFT = validData = YES; } return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* setNpoints: -- Set size of FFT window * This is where sframe[], coefs[], and scoefs[] arrays are allocated. * This is also where PSdata[] and PSops[] arrays are allocated. */ - setNpoints:(int)n { int i,N; if (!power_of_two(n)) { /* Must be power of two */ erralert("FFTView","Size must be a power of two"); return self; } if ((2*WIDTH/(n-1)) < 0.5) { /* Do we have enough space for this? */ erralert("FFTView","View isn't large enough"); return self; } /* Change the cursorpoint location to reflect the change in FFT resolution. * (pixel location change will be handled by drawSelf::) */ if (npoints) cursorpoint *= n/npoints; npoints = n; if (coefs) free(coefs); coefs = (float *)malloc(npoints * sizeof(float)); if (scoefs) free(scoefs); scoefs = (float *)malloc(npoints * sizeof(float)); /* The PSdata[] and PSops[] arrays are for the graphics -- PSdata will * hold the pixel coordinates of the data, and PSops[] is just an * initial moveto followed by a bunch of linetos. These arrays are * passed to DPSDoUserPath() to draw the series of line segments. * See the 'Lines' demo and /usr/include/dpsclients/dpsNeXT.h for more * info on DPSDoUserPath(). */ if (PSdata) free(PSdata); PSdata = (float *)malloc(npoints * sizeof(float)); if (PSops) free(PSops); PSops = (char *)malloc((N = npoints/2)); PSops[0] = dps_moveto; for (i = 1; i < N; i++) PSops[i] = dps_lineto; needsFFT = needsScaling = validData = NO; if (sframe = getframe(sound,startsamp,npoints)) needsFFT = validData = YES; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* setInskip: -- Set start time of FFT window */ - setInskip:(float)t { inskip = t; startsamp = inskip * srate; needsFFT = needsScaling = validData = NO; if (sframe = getframe(sound,startsamp,npoints)) needsFFT = validData = YES; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* advanceFrame: -- Advance by one frame of data * This can be used to do animations. Also can be invoked by actioncell */ - advanceFrame:sender { startsamp += npoints; if (startsamp > ([sound sampleCount] - npoints)) startsamp = [sound sampleCount] - npoints; inskip = startsamp / srate; needsFFT = needsScaling = validData = NO; if (sframe = getframe(sound,startsamp,npoints)) needsFFT = validData = YES; [self display]; /* This method does own display */ return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* dBdisplay: -- Turn on/off dB display */ - dBdisplay:(BOOL)flag { dBflag = flag; needsScaling = YES; /* Force new scaling of display */ return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* sound -- Returns pointer to attached sound */ - sound { return sound; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* (int)npoints -- Returns size of FFT window */ - (int)npoints { return npoints; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* (float)inskip -- Returns start time of FFT window */ - (float) inskip { return inskip; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* dBdisplay -- returns dBflag */ - (BOOL) dBdisplay { return dBflag; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* getCursorFreq:Amp: -- Retrieves frequency/amplitude data at current * cursor location * Arguments are pointers to floats where data is to go. * This method interpolates between data points. */ - getCursorFreq:(float *)freq Amp:(float *)amp { int i; float fractpoint; if (!sound) { *freq = *amp = 0; return self; } *freq = cursorpoint * freqBW; /* Do a simple linear interpolation between data points in coefs[] array */ i = (int)cursorpoint; /* Integer portion of cursor point */ fractpoint = cursorpoint - i; /* Fraction portion */ *amp = ((scoefs[i+1]-scoefs[i]) * fractpoint) + scoefs[i]; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* setCursorFreq: -- Set the position of the cursor * Argument is a frequency to set cursor to. This method does * all translation necessary. */ - setCursorFreq:(float)freq { if (!sound) return self; /* Interpret frequency as a point (with possible interpolation), then * convert that to a pixel column and draw it. */ cursorpoint = freq / (freqBW); DOCURSOR(); cursorpixel = cursorpoint * pixelBW; DOCURSOR(); return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* setDelegate: -- set the delegate for this object */ - setDelegate:anObject { delegate = anObject; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* delegate -- returns pointer to current delegate */ - delegate { return delegate; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* needsFFT: -- Set/reset needsFFT flag */ - needsFFT:(BOOL)flag { needsFFT = flag; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* needsFFT -- Return value of needsFFT flag */ - (BOOL)needsFFT { return needsFFT; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* drawSelf:: -- Do the FFT (if necessary) and draw it * This method called via 'display' method. */ - drawSelf:(NXRect *)rects :(int)rectCount { static float maxamp; /* Peak amp in coefs[] */ float xincr,yscale; /* Graphics scaling parameters */ int N = npoints/2; /* For display purposes */ int maxfreq; /* Top freq in display (in KHz) */ float KHzincr; /* pixel columns per KHz */ int i; float *inp, *outp; /* Pointer to coefs/scoefs (for speed) */ float bbox[4]; float *fptr; /* First, erase what we have now. If there's no data (i.e., if * validData == NO), that's all there is to it. */ NXEraseRect(&bounds); if (!validData) return self; /* If we need to do a new FFT, then let's do it */ if (needsFFT) { fft(FORWARD, npoints, RECTANGULAR, /* See fft_net.c for info */ sframe, REAL, LINEAR, coefs, MAG, LINEAR); needsFFT = NO; needsScaling = YES; } /* If we need to rescale data, then do that, too */ if (needsScaling) { if (!dBflag) maxamp = scaledata(coefs,scoefs,npoints,0,NULL); else maxamp = scaledata(coefs,scoefs,npoints,dBMASK,NULL); needsScaling = NO; } /* Set scaling factors: * xincr = pixel columns per data point (= pixelBW). * yscale = multiplier to plot maximum amplitude at top of box. * maxfreq and KHzincr are for the KHz ruler. */ xincr = WIDTH/(N-1); yscale = (maxamp > 0) ? (bounds.size.height-20)/maxamp : 1; maxfreq = srate/2000; /* max frequency in KHz */ KHzincr = WIDTH/maxfreq; /* columns between KHz */ /* Fill the PSdata[] array with the coordinates for the plot, * and set the bounding box to the size of our view. The * plot of the data will be done via a DPSDoUserPath() call, * which is the zippiest way to do such things. */ for (i = 0,fptr = PSdata; i < N; i++) { *fptr++ = xincr * i; /* X coord */ *fptr++ = scoefs[i] * yscale; /* Y coord */ } bbox[0] = 0; bbox[1] = 0; bbox[2] = bounds.origin.x + bounds.size.width + 1; bbox[3] = bounds.origin.y + bounds.size.height + 1; PSgsave(); PStranslate(0,20); /* To save room for ruler */ PSsetgray(0.0); DPSDoUserPath(PSdata,npoints,dps_float,PSops,N,bbox,dps_ustroke); PSgrestore(); PWdrawruler(0,maxfreq,1,KHzincr); /* Re-figure the cursor's pixel location here, since this might * be a response to a window resizing. */ cursorpixel = cursorpoint * xincr; DOCURSOR(); return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* mouseDown: -- Handle a mousedown event * This method does cursor management. It locates and draws the cursor * at the point of the mouse down, then follows the mouse along during * any subsequent dragging. This method returns as soon as a mouseup * event is received. * A "cursorMoved" message is sent to the delegate (if any) as * mousedown/mousedragged events are received. */ #define DRAG_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK) - mouseDown:(NXEvent *)event { NXPoint p; /* Where the mouse is */ int oldMask; /* Old event mask */ float pbw = pixelBW; /* Local copy of pixelBW */ if (!sound) return self; /* Ask for mousedragged and mouseup events only for the duration * of this method. */ oldMask = [[self window] addToEventMask:DRAG_MASK]; /* For the initial mousedown event and all subsequent mousedragged events, * update the cursor as necessary and send a cursorMoved: message to * the delegate. */ do { p = event->location; [self convertPoint:&p fromView:nil]; if (p.x < 0.0) p.x = 0.0; if (p.x > WIDTH) p.x = WIDTH; if (cursorpixel != p.x) { /* Draw the cursor: */ [self lockFocus]; DOCURSOR(); /* This unhighlights the old */ cursorpixel = p.x; DOCURSOR(); /* This highlights the new */ [self unlockFocus]; [[self window] flushWindow]; NXPing(); cursorpoint = cursorpixel / pbw; if (delegate && [delegate respondsTo:@selector(cursorMoved:)]) [delegate cursorMoved:self]; } event = [NXApp getNextEvent:DRAG_MASK]; } while (event->type != NX_MOUSEUP); [[self window] setEventMask:oldMask]; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* acceptsFirstResponder -- notify Window Server that we want mouse events */ - (BOOL) acceptsFirstResponder { return YES; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.