home *** CD-ROM | disk | FTP | other *** search
- /* 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