This is SoundDocument.m in view mode; [Download] [Up]
/* SoundDocument.m -- Implementation for SoundDocument object. * * Original code by Lee Boynton * Revision by James Pritchett, 10/89 * Version 1.01, 11/89 * Version 1.2, 1/90 * Version 1.3, 2/90 * -- Added display of sampling rate, data format, channel count * Update for 2.0, 3/91 * -- Changed name of delegate method from "soundChanged:" to * "soundDidChange:" * -- Removed NXWait/NXArrow calls */ #import "EdsndApp.h" #import "SoundDocument.h" #import "ScrollingSound.h" #import <soundkit/soundkit.h> #import <appkit/Application.h> #import <appkit/Panel.h> #import <appkit/OpenPanel.h> #import <appkit/Cursor.h> #import <appkit/Form.h> #import <appkit/ClipView.h> #import <appkit/Button.h> #import <stdlib.h> #import <string.h> /* Macros for getting/putting float values from/to Forms */ #define GETVAL(form) [form floatValueAt:0] #define PUTVAL(form,f) [form setStringValue:dofloat(f) at:0] static int opendocs = 0; /* Number of currently open documents */ /* C FUNCTIONS NEEDED BY SoundDocument METHODS: * * dofloat() -- this function trims all float numbers to 3 decimal places. * It is used by any routine that does a 'setFloatValue:at:' on * a Form, so as to keep the numbers tidier. */ char *dofloat(f) float f; { static char s[32]; sprintf(s,"%8.3f",f); return s; } /* newLocation() -- keeps track of where next window should be opened */ static newLocation(NXPoint *p) { static count = 0; p->x += (20.0 * count); /* 20 pixels right */ p->y -= (25.0 * count); /* 25 pixels down */ count = (count > 10)? 0 : count+1; /* after 10 opens, return to start */ } /* getSavePath() -- Get a pathname for save command */ static BOOL getSavePath(char *buf, char const *defaultPath) { static id savePanel = nil; BOOL ok; char const *fileTypes[2] = {0,0}; char dirName[1024], fileName[256]; /* Make the savePanel if we haven't already done it */ if (!savePanel) savePanel = [SavePanel new]; [NXApp setAutoupdate:NO]; if (defaultPath && *defaultPath) { char *p; strcpy(dirName,defaultPath); if (p = rindex(dirName,'/')) { strcpy(fileName, p+1); *p = '\0'; } else { strcpy(fileName,defaultPath); fileName[0] = '\0'; } ok = [savePanel runModalForDirectory:dirName file:fileName]; } else ok = [savePanel runModal]; [NXApp setAutoupdate:YES]; if (ok) { strcpy(buf,[savePanel filename]); return YES; } else return NO; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ @implementation SoundDocument /* new -- factory method */ + new { opendocs++; self = [super new]; isempty = YES; [NXApp loadNibSection:"snddoc.nib" owner:self]; [self displayChanged:self]; /* Force display in forms */ [NXApp isOpenDocument:YES]; [NXApp isOpenSound:NO]; [NXApp isOpenFile:NO]; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* setFileName: -- set the filename variable to this soundfile's name. */ - setFileName:(char *)aName { if (fileName) /* free old space first */ free(fileName); fileName = (char *)malloc(strlen(aName)+1); strcpy(fileName,aName); [window setTitle:fileName]; /* set the window title */ [NXApp isOpenFile:YES]; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* saveAs: -- Save Sound under an assumed name */ - saveAs:sender { char pathname[1024]; if (getSavePath(pathname,fileName)) { [self setFileName:pathname]; [self save:sender]; [NXApp isOpenFile:YES]; } return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* save: -- Save the sound into a soundfile. */ - save:sender { int err; id theSound = [view sound]; /* pointer to the Sound object */ if (fileName == NULL) [self saveAs:sender]; /* Get a name if needed */ else if (theSound) { if ([theSound needsCompacting]) [theSound compactSamples]; /* Do this if necessary */ err = [theSound writeSoundfile:fileName]; /* If we had any errors, then run an Alert panel; otherwise, mark * this file as untouched. */ if (err) NXRunAlertPanel("Save","Cannot write %s","OK",NULL, NULL,fileName); else [window setDocEdited:NO]; } return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* load: -- Load a soundfile into the EdSoundView */ - load:sender { id newSound; if (fileName) { /* Create a new Sound object from this soundfile and give it to * the ScrollingSound. */ newSound = [Sound newFromSoundfile:fileName]; if (newSound) [scroller setSound:newSound]; [self showDisplayTimes]; [self showSelectionTimes]; [self showFormat:newSound]; if ([scroller duration] != 0) [NXApp isOpenSound:YES]; else [NXApp isOpenSound:NO]; } return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* All play/record/stop/pause/resume messages go straight to the * SoundView. The Document only manages the button displays. */ - play:sender { [playButton setEnabled:NO]; /* Disable play button */ [pauseButton setState:0]; /* Pause off */ [view play:sender]; return self; } - stop:sender { [playButton setState:0]; /* Play button is off and enabled */ [playButton setEnabled:YES]; [pauseButton setState:0]; /* Pause off */ [view stop:sender]; return self; } - pause:sender { if (![playButton state]) { /* If not playing, this is a nop */ [pauseButton setState:0]; return self; } else if ([pauseButton state]) /* If pause is now ON, stop */ [view pause:self]; else [view resume:self]; /* Else, resume */ return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* showDisplayTimes -- Update the start/end/size time displays */ - showDisplayTimes { float start,size; /* Get the times from the ScrollingSound and show * them in the appropriate Forms */ [scroller getStart:&start Size:&size]; PUTVAL(startForm,start); PUTVAL(endForm,start+size); PUTVAL(sizeForm,size); return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* showSelectionTimes -- Display current selection times */ - showSelectionTimes { float start,size; /* Get the times from ScrollingSound and stuff 'em into the Forms */ [scroller getSelStart:&start Size:&size]; PUTVAL(selStartForm,start); PUTVAL(selEndForm,start+size); return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* showFormat: -- Display sound format info in text fields */ - showFormat:sound { float srate = [sound samplingRate]; switch ([sound channelCount]) { case 1: [nchansField setStringValue:"Mono"]; break; case 2: [nchansField setStringValue:"Stereo"]; break; default: [nchansField setStringValue:""]; break; } switch([sound dataFormat]) { case SND_FORMAT_LINEAR_16: [formatField setStringValue:"16-bit"]; break; case SND_FORMAT_FLOAT: [formatField setStringValue:"Float"]; break; case SND_FORMAT_MULAW_8: [formatField setStringValue:"MuLaw"]; break; default: [formatField setStringValue:""]; break; } if (srate == 8012.0) [srateField setStringValue:"8.012 kHz"]; else if (srate == SND_RATE_LOW) [srateField setStringValue:"22.05 kHz"]; else if (srate == SND_RATE_HIGH) [srateField setStringValue:"44.1 kHz"]; else [srateField setStringValue:""]; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* displayChanged: -- Handle a change in the display (scroll or otherwise) * This message is sent by the ScrollingSound */ - displayChanged:sender { [self showDisplayTimes]; [self showSelectionTimes]; [self showFormat:[[scroller view] sound]]; PUTVAL(durationForm,[scroller duration]); return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* newStart: -- Change the display to start at a specified time */ - newStart:sender { float start; /* Get the time from the Form and have the scroller set it */ start = GETVAL(startForm); if (start < 0) { /* ignore negative values */ [self showDisplayTimes]; return self; } [scroller setStart:start]; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* newSize: -- Set the duration of the display. Start time will * remain constant. */ - newSize:sender { float size,start; size = GETVAL(sizeForm); start = GETVAL(startForm); if (size <= 0) { /* erase and ignore bad values */ [self showDisplayTimes]; return self; } [scroller setSize:size]; [scroller setStart:start]; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* setSelStart: -- Set the start time of the selection */ - setSelStart:sender { float start,end,size; /* Start/end/size of selection */ float vstart, vend, vsize ; /* Start/end/size of view */ float dur; /* duration of file */ /* Get the new start and end times for the selection. If the * new start time is > the end time, then make a new endtime == * to the start (i.e., selection size = 0 samples). */ start = GETVAL(selStartForm); end = GETVAL(selEndForm); dur = [scroller duration]; if (start < 0) { /* Ignore negative values */ [self showSelectionTimes]; return self; } else if (start > dur) /* Can't select past EOF */ start = dur; if (start > end) end = start; size = end-start; [scroller setSelStart:start Size:size]; /* If the new start time is outside the current view, scroll the view * so that the new start is in the center. If the whole selection * will fit in the view, center the selection instead. */ vstart = GETVAL(startForm); vend = GETVAL(endForm); if (start < vstart || start > vend) { vsize = GETVAL(sizeForm); if (size <= vsize) vstart = start + size/2 - vsize/2; else vstart = start - vsize/2; if (vstart < 0) vstart = 0; [scroller setStart:vstart]; } return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* setSelEnd: -- Set the end time of the selection */ - setSelEnd:sender { float start,end,size; float vstart, vend, vsize ; /* Start/end/size of view */ float dur; /* duration of file */ /* Grab the new end time. If it's < the current start time, * then set start time = end time (i.e., selection size = 0). */ end = GETVAL(selEndForm); start = GETVAL(selStartForm); dur = [scroller duration]; if (end < 0) { /* Ignore negative values */ [self showSelectionTimes]; return self; } else if (end > dur) /* Can't select past EOF */ end = dur; if (end < start) start = end; size = end-start; [scroller setSelStart:start Size:size]; /* If the new end time is outside the current view, scroll the view * so that the new end is in the center. */ vstart = GETVAL(startForm); vend = GETVAL(endForm); if (end < vstart || end > vend) { vsize = GETVAL(sizeForm); if (size <= vsize) vstart = start + size/2 - vsize/2; else vstart = end - vsize/2; if (vstart < 0) vstart = 0; [scroller setStart:vstart]; } return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* EdSoundView delegate functions start and stop the sound meter and * manage buttons */ - willPlay:sender { [NXApp isPlaying:YES]; /* Tell the App about this */ [meter setSound:[sender soundBeingProcessed]]; [meter run:self]; return self; } - didPlay:sender { [playButton setState:0]; /* Reset the buttons */ [playButton setEnabled:YES]; [pauseButton setState:0]; [meter stop:self]; [NXApp isPlaying:NO]; return self; } - hadError:sender { int err = [[sender soundBeingProcessed] processingError]; NXRunAlertPanel("Play error", SNDSoundError(err),"OK", NULL, NULL); return [self stop:self]; } /* selectionChanged: -- Handle a change in the selection. */ - selectionChanged:sender { [self showSelectionTimes]; return self; } - soundDidChange:sender { [window setDocEdited:YES]; if ([scroller duration] != 0) [NXApp isOpenSound:YES]; else [NXApp isOpenSound:NO]; return self; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* Window delegate messages -- becoming/resigning main status * causes changes in the current Document status */ - windowDidBecomeMain:sender { [window makeFirstResponder:view]; [view showCursor]; if ([scroller duration] != 0) [NXApp isOpenSound:YES]; else [NXApp isOpenSound:NO]; if (fileName) [NXApp isOpenFile:YES]; else [NXApp isOpenFile:NO]; [NXApp setCurrentSound:view]; return self; } - windowDidResignMain:sender { [view stop:sender]; [view hideCursor]; [NXApp setCurrentSound:nil]; return self; } - windowDidMiniaturize:sender /* stop sound if miniaturizing */ { [view stop:sender]; [view hideCursor]; if (--opendocs == 0) [NXApp isOpenDocument:NO]; return self; } - windowDidDeminiaturize:sender { opendocs++; [NXApp isOpenDocument:YES]; return self; } - windowWillClose:sender /* save file if necessary when closing */ { char buf[1024]; int choice; if ([window isDocEdited]) { choice = NXRunAlertPanel("Close", "Sound is modified.\nSave it?", "Yes", "No", "Cancel"); switch (choice) { case NX_ALERTALTERNATE: break; case NX_ALERTDEFAULT: [self save:self]; break; case NX_ALERTOTHER: return nil; } [window setDocEdited:NO]; } if (--opendocs == 0) [NXApp isOpenDocument:NO]; [self windowDidResignMain:self]; return sender; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* Stuff for Interface Builder */ - setWindow:anObject { NXRect theFrame; /* Coordinates of the window */ window = anObject; [window setDelegate:self]; /* Document is delegate for Window */ /* Move this frame to the next location on the screen, as determined * by newLocation(). This keeps documents from hiding each other. */ [window getFrame:&theFrame]; newLocation(&theFrame.origin); [window moveTo:theFrame.origin.x :theFrame.origin.y]; [window makeKeyAndOrderFront:self]; return self; } - setScroller:anObject { scroller = anObject; /* set our view variable so we can get at the EdSoundView, and set us * up as delegate for the EdSoundView and the ScrollingSound. */ view = [scroller view]; [scroller setDelegate:self]; [view setDelegate:self]; [view setContinuous:YES]; /* So we see mouse-drag events */ return self; } - scroller { return scroller; } - setStartForm:anObject { startForm = anObject; return self; } - setEndForm:anObject { endForm = anObject; return self; } - setSelStartForm:anObject { selStartForm = anObject; return self; } - setSelEndForm:anObject { selEndForm = anObject; return self; } - setSizeForm:anObject { sizeForm = anObject; return self; } - setDurationForm:anObject { durationForm = anObject; return self; } - setPlayButton:anObject { playButton = anObject; return self; } - setStopButton:anObject { stopButton = anObject; return self; } - setPauseButton:anObject { pauseButton = anObject; return self; } - setMeter:anObject { meter = anObject; return self; } - setNchansField:anObject { nchansField = anObject; return self; } - setFormatField:anObject { formatField = anObject; return self; } - setSrateField:anObject { srateField = anObject; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.