This is Controller.m in view mode; [Download] [Up]
/* Contoller.h * The Controller is provides all of the User Interface as well as the * computational "guts" of the TimeWarp application. * * You may freely copy, distribute, and reuse the code in this example. * NeXT disclaims any warranty of any kind, expressed or implied, as to its * fitness for any particular use. * * Written by: Robert Poor * Created: Sep/92 */ #import "Controller.h" #import "CompletionView.h" #import "errors.h" #import <appkit/Cell.h> #import <appkit/OpenPanel.h> #import <appkit/Window.h> #import <math.h> #import <stdio.h> #import <sys/param.h> #import <appkit/Application.h> // for NX_BASETHRESHOLD #import <appkit/Matrix.h> @interface Controller(ControllerPrivate) - _setSoundFile:(char *)filename; - _updateStatus:sender; - _setSpeed:(float)linValue; @end /* * _updateStatus is called via a timed entry once every second, which in * turn simply calls the _updateStatus: method in Controller. Note that * the "data" argument is bound to the Controller instance. (See the call * to DPSAddTimedEntry in appDidInit: to see how this is managed.) */ void static _updateStatus (DPSTimedEntry te, double timeNow, void *data) { [(id)data _updateStatus:(id)data]; } @implementation Controller - init { [super init]; dacPlayer = [[DACPlayer alloc] init]; [dacPlayer setDelegate:self]; return self; } - free { DPSRemoveTimedEntry(updateTE); [dacPlayer free]; return [super free]; } - appDidInit:sender { [completionView setTextField:completionField]; /* * by passing "self" as the third arg to DPSAddTimedEntry, we get a * handle by which to call back into ourselves from the _updateStatus * function. */ updateTE = DPSAddTimedEntry(UPDATE_RATE, &_updateStatus, self, NX_BASETHRESHOLD); [self _setSpeed:1.0]; return self; } - openSoundFile:sender { const char *const *files; static const char *const fileType[2] = {"snd", NULL}; id openPanel; char fullName[MAXPATHLEN+1]; openPanel = [[OpenPanel new] allowMultipleFiles:NO]; /* run the open panel, filtering for out type of document */ if ([openPanel runModalForTypes:fileType]) { /* open all the files returned by the open panel */ for (files = [openPanel filenames]; files && *files; files++) { /* for the one selected filename... */ sprintf(fullName,"%s/%s",[openPanel directory],*files); [self _setSoundFile:fullName]; } } return self; } - closeSoundFile:sender { return [self _setSoundFile:NULL]; } - _setSoundFile:(char *)aFilename { int r; [self stop:self]; if (aFilename) { SNDSoundStruct *newSound; r = SNDReadSoundfile(aFilename, &newSound); if (!checkSNDError(self, r, "Couldn't read input sound file")) { return nil; } /* aFilename references a valid sound file */ if (srcSound) { SNDFree(srcSound); } srcSound = newSound; [window setTitleAsFilename:aFilename]; srcBase = src = (short *)((void *)srcSound + srcSound->dataLocation); srcEnd = &srcBase[srcSound->dataSize / sizeof(short)]; } else { /* Didn't get a valid sound file */ if (srcSound) { SNDFree(srcSound); srcSound = NULL; } [window setTitleAsFilename:"No Sound File Open"]; /* hack to keep _updateStatus: happy */ srcBase = src = (short *)0; srcEnd = &src[1]; } /* and note the new filename */ filename = aFilename; return self; } - play:sender { if (!filename) { [self openSoundFile:sender]; } if (filename) { // [startButton setEnabled:NO]; [dacPlayer run]; } stoppedManually = NO; return self; } - stop:sender { stoppedManually = YES; [dacPlayer stop]; return self; } - pause:sender { if (!filename) { [self openSoundFile:sender]; } [dacPlayer pause]; return self; } - setSpeedLinear:sender { return [self _setSpeed:[sender floatValue]]; } - setSpeedLogarithmic:sender /* * set the speed logarithmically. The speed will be set to 1/SPEED_RANGE to * SPEED_RANGE as [sender floatValue] ranges from 0 to 1. */ { float logValue, linValue; logValue = [sender floatValue]; linValue = pow((SPEED_RANGE*SPEED_RANGE),logValue)/SPEED_RANGE; /* * A purist might complain that _setSpeed will simply undo all the * hard work in computing linValue from logValue. Tough. */ return [self _setSpeed:linValue]; } - _setSpeed:(float)linValue { float logValue; if (linValue > SPEED_RANGE) linValue = SPEED_RANGE; else if (linValue < 1.0/SPEED_RANGE) linValue = 1.0/SPEED_RANGE; fixRate = linValue * FIXPOINT_UNITY; logValue = log(linValue * SPEED_RANGE)/log(SPEED_RANGE*SPEED_RANGE); [speedField setFloatValue:linValue]; [speedSlider setFloatValue:logValue]; return self; } /*** *** Some non-UI methods ***/ - _updateStatus:sender { char buf[1000]; Pla_state_t state; if (!filename) { sprintf(buf,"No sound file open."); } else { state = [dacPlayer playerState]; switch (state) { case PLA_STOPPED: sprintf(buf,"Stopped"); break; case PLA_PAUSED: sprintf(buf,"Paused"); break; case PLA_RUNNING: sprintf(buf,"Running"); break; case PLA_STOPPING: sprintf(buf,"Stopping..."); break; default: sprintf(buf,"I'm confused!"); break; } } [statusField setStringValue:buf]; [queuedField setIntValue:[dacPlayer framesQueued]]; [playedField setIntValue:[dacPlayer framesPlayed]]; if (srcSound) { double completed; completed = (double)(src - srcBase)/(double)(srcEnd - srcBase); [completionView setDoubleValue:completed]; } else { [completionView setDoubleValue:0.0]; } return self; } /*** *** Delegate Methods called from the DACPlayer object ***/ - willPlay :player /* * Called just before the playing starts. First we set up some * configuration parameters in the DACPlayer (region size, sampling * rate, etc). We then cache some pointers into the sound data. */ { [dacPlayer setSamplingRate:srcSound->samplingRate]; if (srcSound) { src = srcBase; residue = 0; } return self; } - didPlay :player /* * Called after the DAC resources have been freed. */ { /* * The _updateStatus: method looks at the value of src to see how far * through the source sound we've played. Reset it now back to the * start of the sound. (This is really only for cosmetics.) */ src = srcBase; return self; } - playData :(DACPlayer *)player :(char *)region :(int)nbytes /* * This is the delegate method called from the DACPlayer. In this method, * we copy samples from the source sound into the buffer, resampling (ala * linear interpolation) according to the current fixRate parameter. When * all the sound samples have been processed, we call [dacPlayer finish] to * tell it to finish playing any queued samples. */ { short *dst, *dstEnd, *tsrc, *tsrcEnd; int endMargin; fixpoint_t tfixRate, tresidue; if (!srcSound) { [self stop:self]; return nil; } dst = (short *)region; dstEnd = (short *)(®ion[nbytes]); /* cache some instance variable locally (generates better code) */ tsrc = src; tsrcEnd = srcEnd; tfixRate = fixRate; /* rate at which we advance through src */ tresidue = residue; /* current offset between s[0] and s[1] */ /* * endMargin is the number of sample frames we might advance at each step. * We set it to (effectively) CEILING(fixRate) and we use it in calculating * how far we can go in the src buffer. */ endMargin = tfixRate >> LOG2_FIXPOINT_UNITY; if ((endMargin << LOG2_FIXPOINT_UNITY) != tfixRate) { endMargin += 1; } if (srcSound->channelCount == 1) { /* mono src -> stereo dst */ tsrcEnd = tsrcEnd - endMargin - 1; while ((dst < dstEnd) && (tsrc < tsrcEnd)) { short s0, s1, samp; tresidue += tfixRate; tsrc += (tresidue >> LOG2_FIXPOINT_UNITY); tresidue = tresidue & (FIXPOINT_UNITY-1); s0 = tsrc[0]; s1 = tsrc[1]; /* at this point: * s0 is the "low" sample * s1 is the "high" sample * tresidue is between 0 and (fixpoint) 1. * do a linear interpolation between s0 and s1 */ samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY); *dst++ = samp; *dst++ = samp; } } else { /* stereo src -> stereo dst */ tsrcEnd = tsrcEnd - endMargin - 3; while ((dst < dstEnd) && (tsrc < tsrcEnd)) { short s0, s1, samp; tresidue += tfixRate; tsrc += (tresidue >> LOG2_FIXPOINT_UNITY) * 2; tresidue = tresidue & (FIXPOINT_UNITY-1); s0 = tsrc[0]; s1 = tsrc[2]; /* at this point: * s0 is the "low" sample for left channel * s1 is the "high" sample for left channel * tresidue is between 0 and (fixpoint) 1. * do a linear interpolation between s0 and s1 */ samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY); *dst++ = samp; /* left channel */ /* and now for the right channel */ s0 = tsrc[1]; s1 = tsrc[3]; samp = s0 + ((tresidue * (s1 - s0)) >> LOG2_FIXPOINT_UNITY); *dst++ = samp; } } /* decache instance variables that have changed */ src = tsrc; residue = tresidue; /* current offset between s[0] and s[1] */ /* zero out any remaining part of the buffer */ while (dst < dstEnd) { *dst++ = 0; } /* stop the music when we've played the whole file */ if (tsrc >= tsrcEnd) { [dacPlayer finish]; } return self; } - didChangeState:player from:(Pla_state_t)old to:(Pla_state_t)new { [self _updateStatus:self]; /* * If the sound has ended naturally (without us hitting the stop button) * and the repeat button is on, then play the sound again. */ if ((new == PLA_STOPPED) && (stoppedManually == NO) && [repeatButton state]) { [self play:self]; } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.