This is DSPRecorder.m in view mode; [Download] [Up]
/* * DSPRecorder.m * Implementation of a DSP sound recording object. * Author: Robert D. Poor, NeXT Technical Support * Copyright 1989 NeXT, Inc. Next, Inc. is furnishing this software * for example purposes only and assumes no liability for its use. * * Edit history (most recent edits first) * * 4-Feb-90 Rob Poor: cleanup the API. * 21-Dec-89 Rob Poor: Hack to increase the sounddriver timeout from * 1 second to 10 seconds. Guaranteed not to work in new shlibs. * 11-Dec-89 Rob Poor: Don't deallocate the ownerPort, which is the result * of netname_look_up. * 06-Dec-89 Rob Poor: Deleted the unused and useless +newSetup method. * 05-Dec-89 Rob Poor: Changed several instances of check_mach_error to * check_snddriver_error. Put up an alert panel for receive timeouts * in updateStream (rather than simply calling exit). * 11-Sep-89 Rob Poor: In recorderResume, unpause the stream before * sending down the DMA size to avoid losing initial DSP data. * 07-Sep-89 Rob Poor: Created. * * End of edit history */ /* * The DSPRecorder object records data from the DSP chip. It handles * all the setup and sequencing required to receive buffers of data * from the DSP, which will typically be running a program to read * sound samples from the external DSP port. * * With no delegate methods installed, the DSPRecorder won't do * anything useful; it will merely recieve buffers of data from the * DSP and throw them away. However, the following three delegate * methods are implemented: * willRecord :recorder * This method is called after all DSP resources have * been allocated but before any recording starts. You * might use this to open an output file for recording. * didRecord :recorder * This method is called after recording stops. You * might use this method for closing the output file. * recordData :recorder :data :nbytes * This method is called whenever another buffer of data * is available from the DSP. You might use this method * to write the incoming data to a sound file. */ #import <stdio.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 "DSPRecorder.h" #import "errors.h" /* * EL GRANDE HACKOLA: There is a minor bug in the current sounddriver * library: the static int "timeout", defined in the snddriver library, * is demonstrably too short at 1000 (= 1 second). A timeout of 10000 * (= 10 seconds) works without any observed problems. We know the address * of the timeout in the current shared library (found it with gdb), so we * patch it here on the fly. THIS CODE WILL BREAK WHEN THE SHARED LIBRARY * CHANGES. Our only hedge for now is to make sure that the address in question * really contains 1000 before we change it. */ #define TIMEOUT_ADDR ((int *)0x40116ec) #define TIMEOUT_OLDVAL 1000 #define TIMEOUT_NEWVAL 10000 static void fix_snddriver_timeout() { if (*TIMEOUT_ADDR != TIMEOUT_OLDVAL) { NXRunAlertPanel(NULL,"Can't correct timeout bug","OK",NULL,NULL); } else { *TIMEOUT_ADDR = TIMEOUT_NEWVAL; } } /* end of EL GRAND HACKOLA */ /* * HandleDSPMessage is called courtesy of the DPSAddPort mechanism * whenever a message arrives from the DSP driver. userData is bound * to the DSPRecorder object itself. */ static void HandleDSPMessage(msg_header_t *msg, void *userData) { int k_err; snddriver_handlers_t *handlers; handlers = [(DSPRecorder *)userData msgHandlers]; /* handlers = (DSPRecorder *)userData->msgHandlers; */ k_err = snddriver_reply_handler(msg, handlers); check_snddriver_error(k_err,"Cannot parse message from DSP"); } /* * Following are the routines that snddriver_reply_handler will * dispatch to. Note that in each case, the DSPRecorder object itself * will be passed as the first argument. */ static void DSPRecordedDataMsg(void *arg, int tag, char *data, int nbytes) { [(id)arg recordedData:tag:data:nbytes]; } /* * The dispatch table that snddriver_reply_handler uses. */ const static snddriver_handlers_t dspHandlers = { (void *)0, (int) 0, NULL, /* StartedMsg */ NULL, /* CompletedMsg */ NULL, /* AbortedMsg */ NULL, /* PausedMsg */ NULL, /* ResumedMsg */ NULL, /* OverflowMsg */ DSPRecordedDataMsg, NULL, NULL, NULL }; @implementation DSPRecorder:Recorder /* * Create a new DSPRecorder object. */ + new { self = [super new]; msgHandlers = dspHandlers; /* Copy the static structure */ msgHandlers.arg = self; /* Install self as arg to handler functions */ fix_snddriver_timeout(); return self; } - prepare /* * prepare the recorder for recording. It grabs all the requisite resources * and leaves the record stream in a paused state. */ { port_t arbitration_port; int r, protocol; [self stop]; /* make sure that we are stopped first */ bytesRecorded = 0; /* get the device port for the sound/dsp driver on the local machine */ r = netname_look_up(name_server_port,"","sound", &devicePort); check_mach_error(r,"netname lookup failure"); /* try to become owner of the dsp resource */ r = port_allocate(task_self(), &ownerPort); check_mach_error(r,"Cannot allocate owner port"); arbitration_port = ownerPort; r = snddriver_set_dsp_owner_port(devicePort,ownerPort,&arbitration_port); check_snddriver_error(r,"Cannot become owner of dsp resources"); /* get the command port */ r = snddriver_get_dsp_cmd_port(devicePort,ownerPort,&commandPort); check_mach_error(r,"Cannot acquire command port"); /* set up the DMA read stream and set the DSP protocol */ protocol = 0; r = snddriver_stream_setup(devicePort, ownerPort, SNDDRIVER_STREAM_FROM_DSP, READ_BUF_SIZE, BYTES_PER_16BIT, LOW_WATER, HIGH_WATER, &protocol, &streamPort); check_snddriver_error(r,"Cannot set up stream from DSP"); r = snddriver_dsp_protocol(devicePort, ownerPort, protocol); check_snddriver_error(r,"Cannot set up DSP protocol"); /* allocate a port for the replies */ r = port_allocate(task_self(),&replyPort); check_mach_error(r,"Cannot allocate reply port"); /* Start the DMA stream in a paused state */ r = snddriver_stream_control(streamPort,0,SNDDRIVER_PAUSE_STREAM); check_snddriver_error(r,"can't do initial pause"); /* Queue up the initial DMA buffers before starting */ bytesEnqueued = 0; [self updateStream]; DPSAddPort(replyPort, HandleDSPMessage, /* function to call */ MSG_SIZE_MAX, self, /* first arg to HandleDSPMessage */ NX_RUNMODALTHRESHOLD /* priority */ ); /* * Tell the delegate (if any) that we are about to start recording. */ if (delegate && [delegate respondsTo:@selector(willRecord:)]) { [delegate willRecord :self]; } recorderState = REC_PAUSED; /* * Common sense would dictate that we should boot the DSP program * here, but if we do, we will get a little blip of garbage at the * start of the recording. Instead, we boot the DSP when we resume * the stream. */ return self; } /* * recorderResume is only be called from a PAUSED state. It * downloads the DSP program and resumes the DMA stream. */ - run /* * Start (or resume) the recording. If the recorder is already running, this * method has no effect. If the recorder is in a stopped state, it will call * prepare first to set up the recorder. */ { int r; int bufsize = READ_BUF_SIZE; if (recorderState == REC_RUNNING) { return nil; } else if (recorderState == REC_STOPPED) { [self prepare]; } /* else recorderState == REC_PAUSED */ /* boot the dsp from the dspProgram sound structure. */ r = SNDBootDSP(devicePort, ownerPort, dspProgram); check_snd_error(r,"Failed to boot DSP"); /* Resume the DMA stream. */ r = snddriver_stream_control(streamPort,0,SNDDRIVER_RESUME_STREAM); check_snddriver_error(r,"Can't resume the DMA stream"); /* * communicate the read dma buffer size to the DSP (the dsp program * reads this in its reset routine). */ r = snddriver_dsp_write (commandPort, /* port */ &bufsize, /* pointer to the data to write */ 1, /* count of data elements */ sizeof(int), /* bytes per data element */ SNDDRIVER_MED_PRIORITY /* priority of this transactions */ ); check_snddriver_error(r,"Cannot set up DSP"); /* * Post a request to AWAIT_STREAM, which will cause a recordedData * message to be delivered sooner rather than later. */ r = snddriver_stream_control(streamPort, READ_TAG, SNDDRIVER_AWAIT_STREAM); check_snddriver_error(r,"Call to await stream failed"); recorderState = REC_RUNNING; return self; } - pause /* * pause will suspend the recording in such a way that run will resume it. * If called from the stopped state, pause will prepare the stream. If * called from a paused state, this method has no effect. */ { int r; if (recorderState == REC_PAUSED) { return nil; } else if (recorderState == REC_STOPPED) { return [self prepare]; } /* else recorderState == REC_RUNNING */ r = snddriver_stream_control(streamPort, READ_TAG, SNDDRIVER_PAUSE_STREAM); check_snddriver_error(r,"Call to pause stream failed"); recorderState = REC_PAUSED; return self; } - stop /* * Stop recording, shut down the DSP, flush outstanding buffers, frees the * resources acquired in prepare. New state becomes REC_STOPPED, */ { int r; if (recorderState == REC_STOPPED) return nil; recorderState = REC_STOPPED; /* flush any outstanding buffers */ r = snddriver_stream_control(streamPort, READ_TAG, SNDDRIVER_ABORT_STREAM); check_snddriver_error(r,"Couldn't abort stream"); DPSRemovePort(replyPort); r = port_deallocate(task_self(),ownerPort); check_mach_error(r,"Couldn't deallocate ownerPort"); /* * Tell the delegate (if any) that we stopped recording. */ if (delegate && [delegate respondsTo:@selector(didRecord:)]) { [delegate didRecord :self]; } return self; } /*** *** *** Internal methods *** ***/ - setDspProgram :(SNDSoundStruct *)dsp_program { dspProgram = dsp_program; return self; } - (SNDSoundStruct *)dspProgram { return dspProgram; } - (snddriver_handlers_t *)msgHandlers { return &msgHandlers; } /* * recordedData is where most of the recording happens. Whenever we * get a handleRecordedData message, we write what we've got to a file, * free the recorded data buffer, and instantly post a request for * another recorded data message (via the AWAIT_STREAM control message). */ - recordedData :(int)tag :(char *)data :(int)nbytes { int r; bytesRecorded += nbytes; bytesEnqueued -= nbytes; if (delegate && [delegate respondsTo:@selector(recordData:::)]) { [delegate recordData:self:data:nbytes]; } /* Free up the virtual memory used in the data buffer */ r = vm_deallocate(task_self(), (pointer_t)data, nbytes); check_mach_error(r,"Failed to deallocate data buffer"); /* * The delegate of recordData may have stopped the recording, so we * check the state before queueing up new read buffers, etc. */ if (recorderState == REC_RUNNING) { /* Allocate a new read buffer */ [self updateStream]; /* and then instantly request any additional data that's come in. */ r = snddriver_stream_control(streamPort, READ_TAG, SNDDRIVER_AWAIT_STREAM); check_snddriver_error(r,"Call to await stream failed"); } return self; } /* * updateStream enqueues one or more read requests messages. */ - updateStream { int r; while (bytesEnqueued <= REGION_SIZE) { r = snddriver_stream_start_reading (streamPort, /* port */ 0, /* backing store (not yet impl'd) */ REGION_SIZE/BYTES_PER_16BIT, /* count (in samples) to read */ READ_TAG, /* user data */ FALSE, /* send msg when started */ FALSE, /* send msg when completed */ FALSE, /* send msg when aborted */ FALSE, /* send msg when paused */ FALSE, /* send msg when resumed */ FALSE, /* send msg when overflowed */ replyPort /* port for the above messages */ ); check_snddriver_error(r,"Cannot enqueue read request"); bytesEnqueued += REGION_SIZE; } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.