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;
}
@endThese are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.