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.