This is SpectrumView.m in view mode; [Download] [Up]
/* SpectrumView.m -- Implementation for SpectrumView class
*
* See 'SpectrumView.h' for details
*
* smb@datran2.uunet.uu.net
* jwp@silvertone.Princeton.edu
* 2/90
* 03/90: Spectral data plot now done via PSrectfill() to save time
*/
#import <stdlib.h>
#include <limits.h>
#include <sound/sound.h>
#include <dsp/arrayproc.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 "SpectrumView.h"
#import "fft.h"
#import "PWSpectrum.h"
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* Shorthand for the PostScript call to draw the cursor
*/
#define DOVCURSOR() PScompositerect(cursorXpixel,0,1,bounds.size.height,NX_HIGHLIGHT);
#define DOHCURSOR() PScompositerect(0,cursorYpixel,bounds.size.width,1,NX_HIGHLIGHT);
/* Handy shorthands for various conversions:
* WIDTH = width of view in pixels
* freqBW = bandwidth of FFT (in Hz)
* pixelXBW = bandwidth of slice in pixels
* pixelYBW = bandwidth of FFT point in pixels
* SLICESECS = duration of slice in seconds
*/
#define WIDTH (bounds.size.width - 30)
#define freqBW (srate / (npoints - 1))
#define pixelXBW (WIDTH / nslices)
#define pixelYBW (4 *(bounds.size.height - 20) / (npoints - 1))
#define SLICESECS ((slidepoints - 1) / srate)
/* 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);
/* Functions in fft.m:
*/
extern float *getframe(id sound,int startsamp, int nsamps);
extern float scaledata(float *inptr, float *outptr, int nsamps, int scalemask, int *mean);
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
static int *slicemeanindex = NULL; /* slicemeanindex[] stores the
* mean frequency info for the
* slices. It is allocated
* by doSpectrum at display time
*/
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
@implementation SpectrumView
/* dBdisplay: -- Turn on/off dB display
*/
- dBdisplay:(BOOL)flag
{
dBflag = flag;
return self;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* dBdisplay -- returns dBflag
*/
- (BOOL) dBdisplay
{
return dBflag;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* getCursorTime:Freq:Mean: -- Retrieves frequency/amplitude data at current
* cursor location
* Arguments are pointers to floats where data is to go.
*/
- getCursorTime:(float *)time Freq:(float *)freq Mean:(float *)mean
{
if (!sound) {
*freq = *time = *mean = 0.0;
return self;
}
*time = cursorXpoint * SLICESECS;
*freq = cursorYpoint * freqBW;
*mean = slicemeanindex[(int)cursorXpoint] * freqBW;
return self;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* setCursorTime: -- Set the position of the cursor
* Argument is a time to set cursor to. This method does
* all translation necessary.
*/
- setCursorTime:(float)time
{
if (!sound)
return self;
/* Interpret time as a point, then
* convert that to a pixel column and draw it.
*/
cursorXpoint = time / (SLICESECS);
DOVCURSOR();
DOHCURSOR();
cursorXpixel = (cursorXpoint * pixelXBW) + 30;
cursorYpixel = (cursorYpoint * SLICESECS) + 20;
DOHCURSOR();
DOVCURSOR();
return self;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* setDelegate: -- set the delegate for this object
*/
- setDelegate:anObject
{
delegate = anObject;
return self;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* delegate -- returns pointer to current delegate
*/
- delegate
{
return delegate;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* setMeanDisplay: -- Set/reset meanDisplay flag
*/
- setMeanDisplay:(BOOL)flag
{
meanDisplay = flag;
return self;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* setSpectrumDisplay: -- Set/reset spectrumDisplay flag
*/
- setSpectrumDisplay:(BOOL)flag
{
spectrumDisplay = flag;
return self;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* drawSelf:: -- This just clears the view (called by 'display' when
* first bringing up the view, etc.) To draw the spectrum, use
* 'doSpectrum'
*
* This method called via 'display' method.
*/
- drawSelf:(NXRect *)rects :(int)rectCount
{
NXEraseRect(&bounds);
cursorXpixel = cursorYpixel = 0.0;
return self;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* doSpectrum -- Actually draw the spectrum over the specified duration
* This method does all the real work in the SpectrumView
*/
- doSpectrum:aSound Start:(float)start Dur:(float)dur Npoints:(int)n Slidepoints:(int)s;
{
static float *sframe = NULL; /* Current frame of samples */
static float *coefs = NULL; /* Output data */
static float *scoefs = NULL; /* Scaled output data */
static float maxamp; /* Peak amp in coefs[] */
float *inp, *outp; /* Pointers into coefs/scoefs */
int startsamp; /* First sample frame to get */
int nsamps; /* Number of sample frames to get */
int slice = 0; /* Slice counter */
float xincr,yincr; /* Graphics scaling parameters */
int N; /* = npoints/2 */
int maxfreq; /* Top freq in display (in KHz) */
float KHzincr; /* Pixel rows per KHz */
float maxtime; /* Top timing in display (in sec.) */
float timeincr; /* Pixel columns per time quantum */
float x; /* Current pixel column */
int i;
NXEvent foo;
float yval;
int lasti;
/* Initializations:
* -- Initialize PostScript
* -- Set our instance variables (sound,npoints,slidepoints)
* -- Convert start and dur to samples and slices
* -- Set up scaling factors xincr and yincr
*/
PWSpectinit();
sound = aSound;
if (n != npoints) {
if (!power_of_two(n)) {
erralert("SpectrumView","Size must be a power of two");
return self;
}
npoints = n;
/* Allocate new coefs and scoefs arrays, since the FFT size changed.
*/
if (coefs)
free(coefs);
coefs = (float *)malloc(npoints * sizeof(float));
if (scoefs)
free(scoefs);
scoefs = (float *)malloc(npoints * sizeof(float));
}
N = npoints/2;
slidepoints = s;
if (!sound || dur == 0.0)
return self;
srate = [sound samplingRate];
startsamp = start * srate;
nsamps = dur * srate;
nslices = nsamps / slidepoints;
/* Allocate a new array of slicemeans
*/
if (slicemeanindex)
free(slicemeanindex);
slicemeanindex = (int *)malloc(nslices * sizeof(int));
/* Set scaling factors:
* xincr = pixel columns per slice
* yincr = pixel rows per FFT point
* maxfreq and KHzincr are for the KHz ruler.
* timeincr is for the sec. ruler.
*/
xincr = WIDTH / nslices;
if (xincr <= 0.5) {
erralert("Spectrum","View too small for display");
return self;
}
timeincr = xincr * ((srate / (slidepoints - 1)) / 4);
yincr = (bounds.size.height - 20) / ((N / 2) - 1);
if (yincr <= 0.5) {
erralert("Spectrum","View too small for display");
return self;
}
maxfreq = srate / 4000;
KHzincr = (bounds.size.height - 20) / maxfreq;
/* Erase what's there now and draw the rulers
*/
[self lockFocus];
NXEraseRect(&bounds);
PSgsave();
PStranslate(30,0);
PWSpectdrawHruler(0.0,dur,0.25,timeincr);
PSgrestore();
PSgsave();
PStranslate(0,20);
PWSpectdrawVruler(0,maxfreq,1,KHzincr);
PSgrestore();
[self unlockFocus];
[[self window] flushWindow];
cursorXpixel = cursorYpixel = 0.0;
/* Draw the spectrum. 'x' is our current horizontal position.
* We start drawing at (xincr + 1)/2 (i.e., in the middle of the
* first slice), and increment by xincr for subsequent slices.
* The slices are drawn as vertical lines with widths of 'xincr',
* so that they will be centered on 'x'.
*/
[self lockFocus];
for (x = 0.0, slice = 0;
slice < nslices;
x += xincr, slice++, startsamp += slidepoints) {
/* Get a frame of data and apply the FFT
*/
if (!(sframe = getframe(sound,startsamp,npoints)))
break;
fft(FORWARD, npoints, RECTANGULAR,
sframe, REAL, LINEAR,
coefs, MAG, LINEAR);
/* Rescale the FFT results
*/
if (!dBflag)
maxamp = scaledata(coefs,scoefs,npoints,
GRAYSCALEMASK,slicemeanindex+slice);
else
maxamp = scaledata(coefs,scoefs,npoints,
GRAYSCALEMASK|dBMASK,slicemeanindex+slice);
/* And draw this slice
*/
PSgsave();
PStranslate(30+x,20); /* Translate to 'x' */
if (spectrumDisplay) {
/* Go through the grayscaled data and find the largest rectangle
* of uniform color and then draw it. This minimizes the
* Display PostScript overhead.
* yval = current grayscale value
*/
for (i = 0, yval = *scoefs, lasti = 0; i < N/2; i++) {
if (scoefs[i] != yval) {
if (yval < 1.0) {
PSsetgray(yval);
PSrectfill(0,
lasti*yincr,
xincr+1,
(i-lasti)*yincr);
}
yval = scoefs[i];
lasti = i-1;
}
}
if (yval < 1.0) {
PSsetgray(yval);
PSrectfill(0,
lasti*yincr,
xincr+1,
(i-lasti)*yincr);
}
}
if (meanDisplay)
PWSmeanplotdata(slicemeanindex[slice],xincr+1,yincr);
PSgrestore();
[[self window] flushWindow];
// NXPing();
/* Any mouse-down event aborts the plot
*/
if ([NXApp peekNextEvent:NX_MOUSEDOWNMASK into:&foo]) {
[NXApp getNextEvent:NX_MOUSEDOWNMASK];
break;
}
}
[self unlockFocus];
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 pXbw = pixelXBW; /* Local copy of pixelXBW */
float pYbw = pixelYBW; /* Local copy of pixelYBW */
BOOL shift;
int i;
if (!sound)
return self;
shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
/* 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.
*/
if (shift) {
p = event->location;
[self convertPoint:&p fromView:nil];
[self lockFocus];
DOVCURSOR(); /* This unhighlights the old */
DOHCURSOR(); /* This unhighlights the old */
cursorXpixel = p.x;
cursorYpixel = p.y;
DOHCURSOR(); /* This highlights the new */
DOVCURSOR(); /* This highlights the new */
if ((cursorXpoint = (cursorXpixel - 30) / pXbw) < 0)
cursorXpoint = 0;
if ((cursorYpoint = (cursorYpixel - 20) / pYbw) < 0)
cursorYpoint = 0;
if (delegate && [delegate respondsTo:@selector(cursorMoved:)])
[delegate cursorMoved:self];
for (i = 2*cursorXpixel; i < WIDTH; i += cursorXpixel)
PScompositerect(i,0,1,bounds.size.height,NX_HIGHLIGHT);
[self unlockFocus];
[[self window] flushWindow];
NXPing();
do{
event = [NXApp getNextEvent:DRAG_MASK];
} while (event->type != NX_MOUSEUP);
return self;
}
do {
p = event->location;
[self convertPoint:&p fromView:nil];
if (p.x >= 0 && p.x <= bounds.size.width
&& p.y >= 0 && p.y <= bounds.size.height
&& (cursorXpixel != p.x || cursorYpixel != p.y)) { /* Draw the cursor: */
[self lockFocus];
DOVCURSOR(); /* This unhighlights the old */
DOHCURSOR(); /* This unhighlights the old */
cursorXpixel = p.x;
cursorYpixel = p.y;
DOHCURSOR(); /* This highlights the new */
DOVCURSOR(); /* This highlights the new */
[self unlockFocus];
[[self window] flushWindow];
NXPing();
cursorXpoint = (cursorXpixel - 30) / pXbw;
cursorYpoint = (cursorYpixel - 20) / pYbw;
if ((cursorXpoint = (cursorXpixel - 30) / pXbw) < 0)
cursorXpoint = 0;
if ((cursorYpoint = (cursorYpixel - 20) / pYbw) < 0)
cursorYpoint = 0;
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.