This is Transport.m in view mode; [Download] [Up]
/* * Transport.m * "Dime store controller" for a DSPRecorder 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) * * 12-Dec-89 R. Dunbar Poor: Added a play button. * 05-Dec-89 R. Dunbar Poor: added support for 22.05KHz and Mono recording. * DSP program is loaded from machO segment rather than external file. * 11-Sep-89 Rob Poor: In willRecord, lseek rather than ftruncate * to make room for the sound file header. * 07-Sep-89 Rob Poor: Created. * * End of edit history */ /* * The Transport object provides a very simple user interface to the * DSPRecorder object. It provides: * stop/pause/start buttons * a window in which to specify the output (sound) file * a window in which to specify the DSP program to load * a running status window, updated every second. * * The Transport object installs itself as the delegate for the * DSPRecorder object. The delegate methods (willRecord, recordData, * and didRecord), open a file, write the sound samples to that file, * and close the file at the end of recording. */ #import <libc.h> #import <appkit/Application.h> #import <appkit/Matrix.h> #import <appkit/TextField.h> #import <appkit/SavePanel.h> #import <soundkit/Sound.h> #import <sound/filesound.h> #import "Transport.h" #import "DSPRecorder.h" #import "errors.h" /* * UpdateStatus is called via a timed entry every second. It simply * dispatches to the updateStatus method for the Transport object. */ void UpdateStatus (DPSTimedEntry te, double timeNow, void *data) { [(id)data updateStatus]; } @implementation Transport:Object + new { self = [super new]; outFileFD = FD_CLOSED; /* Create a timed entry to update the status window every second. */ statusTE = DPSAddTimedEntry(1.0, &UpdateStatus, self, NX_BASETHRESHOLD); /* Allocate a DSP recorder and install self as the delegate. */ dspRecorder = [DSPRecorder new]; [dspRecorder setDelegate: self]; return self; } /* * Standard methods required by the Interface Builder for initializing * the instance variables. */ - setFileNameWindow:anObject { fileNameWindow = anObject; return self; } - setStatusWindow:anObject { statusWindow = anObject; return self; } - setMonoStereoButtons:anObject { monoStereoButtons=anObject; return self; } - setSamplingRateButtons:anObject { samplingRateButtons=anObject; return self; } - (SavePanel *)savePanel { if (savePanel == nil) savePanel = [SavePanel new]; return savePanel; } - openOutput:sender /* * Get the name for the output file. Doesn't actually open it; that * happens in willRecord (qv). */ { [self closeOutput:self]; if ([[self savePanel] runModalForDirectory:"/tmp" file:"temp.snd"]) { outFilename = [savePanel filename]; [fileNameWindow setStringValue:outFilename]; } return self; } - closeOutput :sender { outFilename = NULL; [fileNameWindow setStringValue:""]; return self; } - closeOutputFD :sender { if (outFileFD != FD_CLOSED) { close(outFileFD); outFileFD = FD_CLOSED; } return [self updateStatus]; } - stop:sender { if (playSound) { [playSound stop]; } else { [dspRecorder stop]; } return [self updateStatus]; } - pause:sender { if (playSound) { [playSound pause]; } else { [dspRecorder pause]; } return [self updateStatus]; } - play:sender { [dspRecorder stop]; if (!playSound) { playSound = [Sound newFromSoundfile:(char *)outFilename]; } [playSound play]; return [self updateStatus]; } - record:sender { if (playSound) { [playSound stop]; [playSound free]; playSound = nil; } [dspRecorder run]; return [self updateStatus]; } /* * Update the status display. Called every second via a timed entry. */ - updateStatus { int state = [dspRecorder state]; int nbytes = [dspRecorder bytesRecorded]; char *stateName; char msg[100]; if (state == REC_STOPPED) stateName = "stopped"; else if (state == REC_PAUSED) stateName = "paused"; else if (state == REC_RUNNING) stateName = "running"; else stateName = "unknown"; sprintf(msg,"%s, %d bytes read\n",stateName,nbytes); [statusWindow setStringValue:msg]; return self; } /* * Delegate methods for the DSPRecorder object. */ /* * The delegate method willRecord is called whenever the DSPRecorder * is about to start recording. At this time, we open a file which * will be used to record the sound into. We also read a DSP program * which the DSPRecorder will use to boot the DSP. */ - willRecord :recorder { char *filename; Sound *dspProgram; int s_err, u_err; if (outFilename == NULL) [self openOutput:self]; /* outFileFD should never be open when we get here... */ if (outFileFD != FD_CLOSED) { NXRunAlertPanel(NULL,"Ouput file is already open??!?","OK",NULL,NULL); [dspRecorder stop]; [self closeOutputFD:self]; return nil; } outFileFD = open(outFilename,O_WRONLY|O_TRUNC|O_CREAT,0666); if (outFileFD < 0) { NXRunAlertPanel(NULL,"Could not open output sound file.","OK",NULL,NULL); [dspRecorder stop]; return nil; } monoStereo = [[monoStereoButtons selectedCell] tag]; samplingRate = [[samplingRateButtons selectedCell] tag]; [monoStereoButtons setEnabled:NO]; [samplingRateButtons setEnabled:NO]; /* Leave a hole at the start for a sound file header ... */ u_err = lseek(outFileFD, HEADER_SIZE, L_SET); if (u_err == -1) { NXRunAlertPanel(NULL,"No output sound file is open.","OK",NULL,NULL); [dspRecorder stop]; return nil; } dspProgram = [Sound newFromMachO:"dsprecord.snd"]; if (dspProgram == nil) { NXRunAlertPanel(NULL,"Can't load the DSP program","OK",NULL,NULL); [dspRecorder stop]; return nil; } [dspRecorder setDspProgram:[dspProgram soundStruct]]; return self; } /* * The delegate method didRecord is called whenever the DSPRecorder * finished recording. At this time, we add a sound file header onto * the open sound file and then close the file. */ - didRecord :recorder { int u_err, s_err, bytesRecorded; SNDSoundStruct SNDHeader; bytesRecorded = [dspRecorder bytesRecorded]; if (monoStereo != STEREO_TAG) { /* We've mixed stereo down to mono */ bytesRecorded = bytesRecorded/2; } if (samplingRate == SRATE_22K_TAG) { /* We've tossed every other sample to reduce the srate */ bytesRecorded = bytesRecorded/2; } /* Add a header to the file */ SNDHeader.magic = SND_MAGIC; SNDHeader.dataLocation = HEADER_SIZE; /* right after header */ SNDHeader.dataSize = bytesRecorded; SNDHeader.dataFormat = SND_FORMAT_LINEAR_16; SNDHeader.samplingRate = (samplingRate==SRATE_22K_TAG)?SND_RATE_LOW:SND_RATE_HIGH; SNDHeader.channelCount = (monoStereo==STEREO_TAG)?2:1; u_err = lseek(outFileFD, 0, L_SET); if (u_err == -1) { NXRunAlertPanel(NULL,"Can't seek for sound file header.","OK",NULL,NULL); return nil; } u_err = write(outFileFD, (char *)&SNDHeader, sizeof(SNDHeader)); if (u_err == -1) { NXRunAlertPanel(NULL,"Can't write sound file header.","OK",NULL,NULL); return nil; } /* close the file */ [self closeOutputFD:self]; [monoStereoButtons setEnabled:YES]; [samplingRateButtons setEnabled:YES]; return self; } /* * recordData::: will be called whenever we get a new buffer of data * from the DSP. We modify the data buffer in-place depending on the * mono/stereo and the sample rate settings before writing the buffer * out. */ - recordData :recorder :(char *)data :(int)nbytes { register short *get, *put; register int i, write_samples; get = put = (short *)data; write_samples = nbytes/sizeof(short); switch (monoStereo) { case STEREO_TAG: if (samplingRate==SRATE_22K_TAG) { /* Stereo, 22K: decimate by 2 (discard alternate samples) */ write_samples /= 2; for (i=write_samples/2; i>0; i--) { *put++ = *get++; /* left channel */ *put++ = *get++; /* right channel */ get += 2; /* skip a sample frame */ } } else { /* Stereo, 44K: No decimation or mixing needed. */ } break; case MONO_MIX_TAG: /* mix left and right channels */ if (samplingRate==SRATE_22K_TAG) { /* Mono Mix, 22K: decimate by 2, mix left and right channels */ write_samples /= 4; for (i=0;i<write_samples;i++) { *put++ = (*get++ + *get++)/2; get += 2; } } else { /* Mono Mix, 44K: no decimation, mix left and right channels */ write_samples /= 2; for (i=0;i<write_samples;i++) { *put++ = (*get++ + *get++)/2; } } break; case MONO_L_TAG: if (samplingRate==SRATE_22K_TAG) { /* Mono Left, 22K: decimate by 2, only keep the left channel. */ write_samples /= 4; for (i=0;i<write_samples;i++) { *put++ = *get; get += 4; } } else { /* Mono Left, 44K: no decimation, only keep the left channel. */ write_samples /= 2; for (i=0;i<write_samples;i++) { *put++ = *get; get += 2; } } break; case MONO_R_TAG: /* keep left channel, throw away right */ get += 1; /* start at right channel */ if (samplingRate==SRATE_22K_TAG) { /* Mono Right, 22K: decimate by 2, only keep the Right channel. */ write_samples /= 4; for (i=0;i<write_samples;i++) { *put++ = *get; get += 4; } } else { /* Mono Right, 44K: no decimation, only keep the right channel. */ write_samples /= 2; for (i=0;i<write_samples;i++) { *put++ = *get; get += 2; } } break; } i = write(outFileFD, data, write_samples*sizeof(short)); if (i < 0) { NXRunAlertPanel(NULL,"Failed to write output data","OK",NULL,NULL); [dspRecorder stop]; return nil; } } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.