This is DACPlayer.m in view mode; [Download] [Up]
/*
* DACPlayer.m
* Implementation of an object to play sound over the soundout device (DACs).
*
* 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
* Edit History (most recent edits first):
*
* Version II created: Sep/92
* Version I created Dec/89
*
* End of Edit History
*/
#import <mach.h>
#import <servers/netname.h>
#import <appkit/Application.h>
#import <appkit/Panel.h>
#import <sound/accesssound.h>
#import <sound/sounddriver.h>
#import <sound/soundstruct.h>
#import "DACPlayer.h"
#import "errors.h"
#import <c.h> /* for MAX */
@interface DACPlayer(DACPlayerPrivate)
- _setState:(Pla_state_t)newState;
- _updateParameters;
- _enqueueRegions;
- _completedRegion;
@end
/*
* Following are the routines that snddriver_reply_handler will
* dispatch to. Note that in each case, the DACPlayer object itself
* will be passed as the first argument.
*/
static void DACCompletedMsg(void *arg, int tag)
{
[(id)arg _completedRegion];
}
/*
* The dispatch table that snddriver_reply_handler uses.
*
* ### rpoor Sep 92: It appears that the sound driver doesn't post
* ### underrun messages. I tried everything and never got notified...
*/
static snddriver_handlers_t dacHandlers = {
(void *)0, /* user-defined arg passed to dispatch functions */
(int) 0, /* timeout */
NULL, /* DACStartedMsg */
DACCompletedMsg,
NULL, /* DACAbortedMsg */
NULL, /* DACPausedMsg */
NULL, /* DACResumedMsg */
NULL, /* DACUnderrunMsg */
NULL,
NULL,
NULL,
NULL
};
/*
* HandleDACMessage is called courtesy of the DPSAddPort mechanism
* whenever a message arrives from the sound driver. userData is bound
* to the DACPlayer object itself.
*/
static void HandleDACMessage(msg_header_t *msg, void *userData)
{
int r;
dacHandlers.arg = userData; /* Install dacPlayer as arg to handlers */
r = snddriver_reply_handler(msg, &dacHandlers);
checkMachError((id)dacHandlers.arg, r, "Can't parse message from DAC");
}
@implementation DACPlayer:Object
- init
{
self = [super init];
playerState = PLA_STOPPED;
regionsQueued = bytesPlayed = bytesQueued = 0;
[self setSamplingRate:SND_RATE_HIGH];
[self setRegionSize:4*vm_page_size andCount:3];
return self;
}
- free
{
return [super free];
}
/***
*** METHODS THAT CONTROL DACPLAYER
***/
- prepare
/*
* grab all the required resources, queue up the initial regions
* (this WILL call the delegate playData::: method), and set state
* to PLA_PAUSED.
*/
{
port_t arbitration_port;
int r, rate, protocol;
[self stop]; /* make sure playing has stopped first. */
/* get the ports that we need */
r = SNDAcquire(SND_ACCESS_OUT, 0, 0, 0, NULL_NEGOTIATION_FUN,
0, &devicePort, &ownerPort);
if (!checkMachError(self,r,"Can't acquire SoundOut resouces"))
return nil;
arbitration_port = ownerPort;
r = snddriver_set_sndout_owner_port(devicePort,ownerPort,&arbitration_port);
if (!checkSnddriverError(self,r,"Cannot become owner of sound-out resources"))
return nil;
r = snddriver_set_ramp(devicePort,0); /* disable ramping */
checkSnddriverError(self,r,"Call to disable ramp failed"); /* not fatal */
/*
* Tell the delegate (if any) that we are about to start playing.
* Call it here in case the delegate wants to configure any of the
* dacPlayer parameters (samplingRate, regionSize, regionCount).
*/
if (flags.willPlay) {
[delegate willPlay :self];
}
[self _updateParameters];
/* set up the DMA read stream */
protocol = 0;
rate = ((samplingRate == SND_RATE_HIGH)?
SNDDRIVER_STREAM_TO_SNDOUT_44:
SNDDRIVER_STREAM_TO_SNDOUT_22);
r = snddriver_stream_setup(devicePort,
ownerPort,
rate,
READ_BUF_SIZE,
sizeof(short),
LOW_WATER,
HIGH_WATER,
&protocol, /* ignored for sndout */
&streamPort);
if (!checkSnddriverError(self,r,"Cannot set up stream to sound-out"))
return nil;
/* allocate a port for the replies */
r = port_allocate(task_self(),&replyPort);
if (!checkMachError(self,r,"Cannot allocate reply port"))
return nil;
/* Start the DMA stream in a paused state */
r = snddriver_stream_control(streamPort,0,SNDDRIVER_PAUSE_STREAM);
if (!checkSnddriverError(self,r,"can't do initial pause"))
return nil;
/*
* Queue up the initial buffers before starting.
*/
bytesPlayed = 0;
bytesQueued = 0;
regionsQueued = 0;
[self _enqueueRegions];
/*
* Set things up to call HandleDACMessage whenever we receive a
* message on the replyPort. (We don't expect ro recieve any message
* until we unpause the stream, see the -run method for that...)
*/
DPSAddPort(replyPort,
HandleDACMessage, /* function to call */
MSG_SIZE_MAX,
self, /* second arg to HandleDACMessage */
NX_MODALRESPTHRESHOLD /* priority */
);
[self _setState:PLA_PAUSED];
return self;
}
- run
{
int r;
if (playerState == PLA_RUNNING) {
return nil; /* already running. Ignore the bum */
} else if (playerState == PLA_STOPPED) {
[self prepare]; /* starting cold. prepare first then run */
} else if (playerState == PLA_STOPPING) {
[[self stop] prepare]; /* stop first, prepare, then run... */
return nil;
} /* else playerState == PLA_PAUSED */
/*
* At this point we know that state = PLA_PAUSED, any initial buffers
* are already queued up, and the stream is paused. We simply resume
* the stream to start things going...
*/
r = snddriver_stream_control(streamPort,0,SNDDRIVER_RESUME_STREAM);
if (!checkSnddriverError(self,r,"Can't resume the DMA stream"))
return nil;
return [self _setState:PLA_RUNNING];
}
- pause
{
int r;
if (playerState == PLA_PAUSED) {
return nil;
} else if (playerState == PLA_STOPPED) {
return [self prepare];
} else if (playerState == PLA_STOPPING) {
return [[self stop] prepare];
} /* else playerState == PLA_RUNNING */
r = snddriver_stream_control(streamPort,WRITE_TAG,SNDDRIVER_PAUSE_STREAM);
checkSnddriverError(self,r,"Call to pause stream failed");
return [self _setState:PLA_PAUSED];
}
- stop
/*
* Terminate all playing right now.
*/
{
int r;
if (playerState == PLA_STOPPED) {
return nil;
}
/* flush any outstanding buffers */
r = snddriver_stream_control(streamPort,
WRITE_TAG,
SNDDRIVER_ABORT_STREAM);
checkSnddriverError(self,r,"Couldn't abort stream");
DPSRemovePort(replyPort);
r = SNDRelease(SND_ACCESS_OUT, devicePort, ownerPort);
if (!checkMachError(self,r,"Can't deallocate the owner port"))
return nil;
r = port_deallocate(task_self(),replyPort);
if (!checkMachError(self,r,"Can't deallocate the reply port"))
return nil;
/*
* Tell the delegate that we stopped playing.
*/
if (flags.didPlay) {
[delegate didPlay :self];
}
bytesPlayed = bytesQueued = 0; /* we've shut everything down */
return [self _setState:PLA_STOPPED];
}
- finish
/*
* The finish method only sets the state to PLA_STOPPING. The routines
* that handle requests from the sound driver will notice this and will
* stop enqueuing new regions. When the last region has been played,
* dacPlayer will call [self stop] to shut things down.
*/
{
if (playerState == PLA_STOPPED) {
return nil; /* already stopped */
} else if (playerState == PLA_STOPPING) {
return nil; /* already stopping */
} /* else (playerState == PLA_RUNNING) || (playerState == PLA_PAUSED) */
return [self _setState:PLA_STOPPING];
}
/***
*** METHODS THAT CONFIGURE DACPLAYER
***/
- delegate { return delegate; }
- setDelegate:anObject
{
delegate = anObject;
/* make note of which methods the delegate responds do */
flags.willPlay = [delegate respondsTo:@selector(willPlay:)];
flags.didPlay = [delegate respondsTo:@selector(didPlay:)];
flags.playData = [delegate respondsTo:@selector(playData:::)];
flags.didChangeState =
[delegate respondsTo:@selector(didChangeState:from:to:)];
return self;
}
- (int)regionSize { return regionSize; }
- (int)regionCount { return regionCount; }
- setRegionSize:(int)size andCount:(int)count
{
int pages;
/* round up to nearest vm_page_size boundary */
pages = (size+vm_page_size-1)/vm_page_size;
newRegionSize = MAX(1,pages)*vm_page_size;
newRegionCount = MAX(1,count);
/* update the new parameters if it's safe... */
return [self _updateParameters];
}
- (int)samplingRate { return samplingRate; }
- setSamplingRate:(int)aRate
{
if (aRate < (SND_RATE_HIGH+SND_RATE_LOW)/2) {
newSamplingRate = SND_RATE_LOW;
} else {
newSamplingRate = SND_RATE_HIGH;
}
/* update the new parameters if it's safe... */
return [self _updateParameters];
}
/***
*** METHODS THAT QUERY DACPLAYER
***/
- (Pla_state_t)playerState { return playerState; }
#define BYTES_PER_SAMPLE (sizeof(short))
#define BYTES_PER_FRAME (BYTES_PER_SAMPLE * 2)
- (int)bytesPlayed { return bytesPlayed; }
- (int)samplesPlayed { return bytesPlayed / BYTES_PER_SAMPLE; }
- (int)framesPlayed { return bytesPlayed / BYTES_PER_FRAME; };
- (double)secondsPlayed
{
return (double)bytesPlayed / (double)(samplingRate * BYTES_PER_FRAME);
}
- (int)bytesQueued { return bytesQueued; }
- (int)samplesQueued { return bytesQueued / BYTES_PER_SAMPLE; }
- (int)framesQueued { return bytesQueued / BYTES_PER_FRAME; }
- (double)secondsQueued
{
return (double)bytesQueued / (double)(samplingRate * BYTES_PER_FRAME);
}
/***
*** Internal methods
***/
- _setState:(Pla_state_t)newState
/*
* Set the state to newState. If there's a delegate that wants to know
* about the change in state, tell 'em. Returns self.
*/
{
Pla_state_t prevState = playerState;
playerState = newState;
if (flags.didChangeState) {
[delegate didChangeState:self from:prevState to:newState];
}
return self;
}
- _updateParameters
/*
* If the state == PLA_STOPPED, update the "pending" new parameters.
* We'll always call this method in -prepare before starting.
*/
{
if (playerState == PLA_STOPPED) {
samplingRate = newSamplingRate;
regionCount = newRegionCount;
regionSize = newRegionSize;
}
return self;
}
- _enqueueRegions
/*
* This is the method that sends buffers to the sound driver stream.
*
* Unless state == STOPPING, it writes regions to the sound driver until
* regionsQueued == regionCount. Each region is passed to the delegate
* in a playData::: method before being handed off to the sound driver.
*/
{
int r;
char *currentRegion;
while ((playerState != PLA_STOPPING) && (regionsQueued < regionCount)) {
/* allocate a region */
r = vm_allocate(task_self(),&(vm_address_t)currentRegion,regionSize,TRUE);
/* let the delegate have its way with the regrion */
if (flags.playData) {
[delegate playData :self :currentRegion :regionSize];
}
/* enqueue the buffer in the DMA stream to the DACs */
r = snddriver_stream_start_writing
(streamPort, /* port */
currentRegion, /* data to be played */
regionSize/sizeof(short), /* number of samples to play */
WRITE_TAG, /* tag for this region */
FALSE, /* preempt */
TRUE, /* deallocate on completion */
FALSE, /* send msg when started */
TRUE, /* send msg when completed */
FALSE, /* send msg when aborted */
FALSE, /* send msg when paused */
FALSE, /* send msg when resumed */
FALSE, /* send msg when underflowed */
replyPort /* port for the above messages */
);
checkSnddriverError(self,r,"Cannot enqueue write request");
regionsQueued += 1;
bytesQueued += regionSize;
}
return self;
}
- _completedRegion
/*
* This method is called whenever we get a "region completed" message from
* the sound driver. (How? The sound driver posts a message on replyPort,
* which is noticed by the DPSAddPort mechanism which calls HandleDACMessage
* which calls snddriver_reply_handler which dispatches to DACCompletedMessage
* which (finally) calls _completedRegion.) Got that?
*
* Oh, what's this method DO? It makes note of the fact that another region
* has been consumed by the DAC stream and calls _enqueueRegions to replenish
* the region queue. If state == PLA_STOPPING and the region queue has hit
* zero, it means that we've just played the last region and we call
* [self stop] to shut things down.
*/
{
/* bump bytesPlayed to reflect the fact that the sound driver played 'em */
bytesPlayed += regionSize;
/* note that one region has been consumed by the sound driver. */
regionsQueued -= 1;
/* replenish the queue of regions */
[self _enqueueRegions];
/* shut down the machinery if we're stopping */
if ((playerState == PLA_STOPPING) && (regionsQueued == 0)) {
[self stop];
}
return self;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.