/* sqUnixSound.c -- a sound driver for Linux and, theoretically, any system supporting Open Sound System. This version is based on mmap. $Log: sqUnixSound.c,v $ Revision 6.5 2000/11/19 20:42:38 lex added module initialization and shutdown commands * Revision 6.4 1999/03/03 15:56:44 lex * changed fragment size back to 4k. * limits available buffer space to the * actual amount available, even if there * have been massive overflows. There's * no actual *use* to pretending all that * space is there, when in fact the kernel * has looped round and round the buffer. * * Revision 6.3 1999/02/24 00:58:13 lex * fixed some fprintf's. * decreased fragment size from 4k to 1k. * * Revision 6.2 1999/01/25 22:57:43 lex * *** empty log message *** * * Revision 6.1 1998/11/22 18:04:45 lex * insertSamples works. * * Revision 6.0 1998/11/21 21:51:40 lex * initial mmap-based version. seems to work, but doesn't * support insertSamples and recording * * Revision 1.1 1998/11/21 21:50:47 lex * Initial revision * */ #include "sq.h" #ifdef HAVE_OSS #include #include #include #include #include #include #include #include #include #include #include /* put the endianness into a single define */ #ifdef HAS_MSB_FIRST #define IS_BIGENDIAN 1 #else #define IS_BIGENDIAN 0 #endif /* check for _REENTRANT */ #ifndef _REENTRANT #error _REENTRANT must be defined to use OSS sound. Add it to CFLAGS and "make clean interp" #endif /* obvious meanings... */ #define MIN(a,b) (((a)<(b)) ? (a) : (b)) #define MAX(a,b) (((a)>(b)) ? (a) : (b)) /* define TRACE to print progress messages */ /* #define TRACE /* */ /* define TIMINGS to calculate timings */ /* #define TIMINGS /* */ /* XXX some of these names are overly long and similar to each other. shorter, but still representative names, would be nice */ /* lock which must be held for access to any global data */ static pthread_mutex_t globalLock = PTHREAD_MUTEX_INITIALIZER; /* whether the background watcher thread has been started */ static int watcherStarted = 0; /* fd for playing and recording */ /* it's <0 if the device isn't open */ static int audio_fd = -1; /* capabilities of the audio device */ /* only valid if audio_fd >= 0 */ static int audio_caps; /* mmap-ed buffers off of the above fd */ /* each is NULL if the device is not open for the corresponding operation (play or record) */ static unsigned char *playBuffer=NULL, *recordBuffer=NULL; /* semaphores to signal Squeak with. note -- only valid when the device is open for the appropriate mode */ static int playSema, recordSema; /* fragmenting parameters for input and output */ static int playFragSize, playFragsTotal; static int recordFragSize, recordFragsTotal; /* fragments for playing that are open for data */ static int fragBeingFilled; /* index of next fragment being filled */ static int fragPortionFilled; /* bytes in the next fragment that * have already been filled */ static int playFragmentsPlaying; /* number of fragments completely * filled with sound data */ /* max amount to play at once, based on Squeak's requested buffer size */ static int maxFragmentsPlaying; /* description of recording state (assuming recordBuf != NULL) */ static int recordFragsFilled; /* number of fragments that have been * filled by the soundcard but not * returned to Squeak yet */ static int nextFragToRead; /* next fragment we should return to squeak */ static int samplesUsedFromNextFragToRead; /* number of samples that have already been read from the above fragment */ /* description of the mode the sound device is in */ /* note -- same format must be used for recording and playback */ static int fmtStereo; /* whether it is in stereo mode */ static int fmtBytes; /* bytes per sample. 1 or 2. */ static int fmtIsBigEndian; /* whether the output is BigEndian or not */ static int fmtSwapBytes; /* whether the endianness of the device is * different from the integer format of this * system */ static int fmtSigned; /* whether the output format is signed or not */ static int samplingSpeed; /* sampling speed in use */ /* report the time in microseconds; only useful for timing output */ static int utime() { struct timeval tv; gettimeofday(&tv, NULL); return( (tv.tv_sec % 1000) * 1000000 + tv.tv_usec); } /* number of bytes per frame, given the current mode */ /* assumes globalsLock is held */ static int bytesPerPlayFrame() { int bytes; bytes = 1; bytes *= fmtBytes; if(fmtStereo) bytes *= 2; return bytes; } /* obtain the global lock */ static void lockGlobals() { if(pthread_mutex_lock(&globalLock)) { perror("pthread_mutex_lock(&globalLock)"); abort(); } } /* release the global lock */ static void unlockGlobals() { if(pthread_mutex_unlock(&globalLock)) { perror("pthread_mutex_unlock(&globalLock)"); abort(); } } /* trigger the device to start playing and/or recording, depending * on which buffers have been set up */ /* returns 0 on success, nonzero on failure */ static int trigger() { int on; on = 0; if(ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &on)) { perror("SNDCTL_DSP_SETTRIGGER"); return -1; } if(playBuffer) on |= PCM_ENABLE_OUTPUT; if(recordBuffer) on |= PCM_ENABLE_INPUT; if(ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &on)){ perror("SNDCTL_DSP_SETTRIGGER"); return -1; } return 0; } /* open the sound device with the given modes * it is assumed that globalLock is already held. */ static int dsp_open(int recordOnly, int stereo, int sampleRate) { int formats; /* available formats */ int format; /* format actually chosen */ int useDuplex; /* whether to set up for full duplex mode */ int ignored; /* scratch space */ assert(audio_fd < 0); /* open the device */ audio_fd = open("/dev/dsp", O_RDWR); if(audio_fd < 0) { perror("/dev/dsp"); return -1; } /* check the capabilities list */ if(ioctl(audio_fd, SNDCTL_DSP_GETCAPS, &audio_caps)) { perror("SNDCTL_DSP_GETCAPS"); goto errorAfterOpen; } #ifdef TRACE fprintf(stderr, "audio_caps = %x\n", (unsigned) audio_caps); #endif if(! (audio_caps & DSP_CAP_MMAP)) { fprintf(stderr, "sound: mmap not supported! sorry, bailing.\n"); goto errorAfterOpen; } /* set the format from those available. Try formats in decreasing order of preference */ if(ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &formats)) { perror("SNDCTL_DSP_GETFMTS"); goto errorAfterOpen; } #ifdef TRACE fprintf(stderr, "formats = %x\n", formats); #endif if(formats & AFMT_S16_BE) format = AFMT_S16_BE; else if(formats & AFMT_S16_LE) format = AFMT_S16_LE; else if(formats & AFMT_U16_BE) format = AFMT_U16_BE; else if(formats & AFMT_U16_LE) format = AFMT_U16_LE; else if(formats & AFMT_S8) format = AFMT_S8; else if(formats & AFMT_U8) format = AFMT_U8; else { fprintf(stderr, "no supported audio format (%x)\n", formats); goto errorAfterOpen; } if(ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format)) { perror("SNDCTL_DSP_SETFMT"); goto errorAfterOpen; } fprintf(stderr, "soundcard format = %d\n", format); /* record info on the format we ended up with */ switch(format) { case AFMT_U16_LE: fmtBytes = 2; fmtIsBigEndian = 0; fmtSigned = 0; break; case AFMT_U16_BE: fmtBytes = 2; fmtIsBigEndian = 1; fmtSigned = 0; break; case AFMT_S16_LE: fmtBytes = 2; fmtIsBigEndian = 0; fmtSigned = 1; break; case AFMT_S16_BE: fmtBytes = 2; fmtIsBigEndian = 1; fmtSigned = 1; break; case AFMT_U8: fmtBytes = 1; fmtIsBigEndian = 42; fmtSigned = 0; break; case AFMT_S8: fmtBytes = 1; fmtIsBigEndian = 42; fmtSigned = 1; break; default: /* shouldn't be possible */ abort(); } if(fmtBytes == 2) fmtSwapBytes = (fmtIsBigEndian != IS_BIGENDIAN); else fmtSwapBytes = 42; /* set whether we are in stereo or not */ fmtStereo = stereo; if(ioctl(audio_fd, SNDCTL_DSP_STEREO, &fmtStereo)) { perror("SNDCTL_DSP_STEREO"); goto errorAfterOpen; } if(fmtStereo != stereo) { /* XXX we could certainly translate from whatever Squeak * gives to whatever the sound card supports, but * I don't feel like coding it up.... */ fprintf(stderr, "could not set requested stereo mode\n"); goto errorAfterOpen; } #ifdef TRACE fprintf(stderr, "stereo = %d\n", fmtStereo); #endif /* set the sample rate */ if(ioctl(audio_fd, SNDCTL_DSP_SPEED, &sampleRate)) { perror("SNDCTL_DSP_SPEED"); goto errorAfterOpen; } samplingSpeed = sampleRate; #ifdef TRACE fprintf(stderr, "sampling rate = %d\n", samplingSpeed); #endif if(fmtBytes == 1) { fprintf(stderr, "sound: using 8-bit device\n"); } return 0; errorAfterOpen: close(audio_fd); audio_fd = -1; return -1; } /* mmap the play buffer and set the buffering variables */ /* it is assumed that globalLock is held */ int setupPlayBuffer(int maxFramesBuffered) { /* truly temporary temp variables... */ audio_buf_info info; unsigned fragArg; int maxBytesBuffered; /* try for relatively short fragments */ fragArg = 0x7fff0000 + 12; if(ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &fragArg)) { perror("SNDCTL_DSP_SETFRAGMENT"); /* not fatal, so continue */ } /* get the actual fragment size */ if(ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info)) { perror("SNDCTL_DSP_GETOSPACE"); return -1; } playFragSize = info.fragsize; playFragsTotal = info.fragstotal; #ifdef TRACE fprintf(stderr, "fragsize = %d\nfragstotal = %d\n", playFragSize, playFragsTotal); #endif if(playFragsTotal < 2) { fprintf(stderr, "sound: too few (%d) fragments in buffer.\n", playFragsTotal); return -1; } /* do the mmap */ playBuffer = mmap(0, playFragsTotal * playFragSize, PROT_WRITE, MAP_SHARED, audio_fd, 0); if(playBuffer == (unsigned char *) -1) { perror("mmap"); playBuffer = NULL; return -1; } /* zero it out, in case there is static in the buffer */ memset(playBuffer, 0, playFragsTotal * playFragSize); /* set up variables */ fragBeingFilled = 1; fragPortionFilled = 0; playFragmentsPlaying = 1; /* leave the first fragment playing * empty, so Squeak has some time to * fill the next */ /* don't play more than what Squeak wants as a bufferful at a time */ maxBytesBuffered = 2*maxFramesBuffered * bytesPerPlayFrame(); maxFragmentsPlaying = (maxBytesBuffered + (playFragSize-1)) / playFragSize; /* but have at least 2 fragments! */ if(maxFragmentsPlaying < 2) { fprintf(stderr, "increasing fragments to 2 from %d\n", maxFragmentsPlaying); maxFragmentsPlaying = 2; } /* don't allow playing more fragments than we have! */ maxFragmentsPlaying = MIN(maxFragmentsPlaying, playFragsTotal); fprintf(stderr, "maxFramesBuffered: %d bytesPerPlayFrame: %d maxBytesBuffered: %d maxFragmentsPlaying: %d\n", maxFramesBuffered, bytesPerPlayFrame(), maxBytesBuffered, maxFragmentsPlaying); return 0; } /* mmap the record buffer and set the buffering variables */ /* it is assumed that globalLock is held */ int setupRecordBuffer() { /* truly temporary temp variables... */ audio_buf_info info; unsigned fragArg; fprintf(stderr, "setupRecordBuffer\n"); /* get the actual fragment size */ if(ioctl(audio_fd, SNDCTL_DSP_GETISPACE, &info)) { perror("SNDCTL_DSP_GETISPACE"); return -1; } recordFragSize = info.fragsize; recordFragsTotal = info.fragstotal; #if defined(TRACE) || 0 fprintf(stderr, "fragsize = %d\nfragstotal = %d\n", recordFragSize, recordFragsTotal); #endif if(recordFragsTotal < 2) { fprintf(stderr, "sound: too few (%d) fragments in input buffer.\n", recordFragsTotal); return -1; } /* do the mmap */ recordBuffer = mmap(0, recordFragsTotal * recordFragSize, PROT_READ, MAP_SHARED, audio_fd, 0); if(recordBuffer == (unsigned char *) -1) { perror("mmap"); recordBuffer = NULL; return -1; } #if defined(TRACE) || 0 fprintf(stderr, "record buffer at %p, size %d\n", recordBuffer, recordFragsTotal * recordFragSize); #endif /* set up variables */ nextFragToRead = 0; recordFragsFilled = 0; return 0; } /* free all play-related buffers */ /* assumes globalLock is held */ static void destroyPlayBuffer() { if(munmap(playBuffer, playFragsTotal * playFragSize)) perror("munmap"); playBuffer = NULL; } /* free all recording-related buffers */ /* assumes globalLock is held */ static void destroyRecordBuffer() { if(munmap(recordBuffer, recordFragsTotal * recordFragSize)) perror("munmap"); recordBuffer = NULL; } /* close the device, and free all associated memory */ /* it is assumed that globalLock is held */ static void dsp_close(void) { if(audio_fd < 0) { fprintf(stderr, "trying to close the device, but it's already closed!\n"); return; } if(playBuffer) destroyPlayBuffer(); if(recordBuffer) destroyRecordBuffer(); /* actually close the device */ if(close(audio_fd)) perror("close"); audio_fd = -1; } /* do IO on the sound device */ /* it is assumed that lockGlobals() is held */ void snd_poll() { int i; /* check if device is open */ if(audio_fd < 0) { return; } if(playBuffer != NULL) { count_info info; /* find out how many fragments have been processesd */ if(ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &info)) { perror("SNDCTL_DSP_GETOPTR"); fprintf(stderr, "closing the sound device\n"); dsp_close(); return; } if(info.blocks > 0) { #if defined(TRACE) || 0 fprintf(stderr, "(%d)%d fragments have been consumed, ptr=%p\n", utime(), info.blocks, info.ptr); #endif for(i=0; i 0) { recordFragsFilled += info.blocks; #ifdef TRACE fprintf(stderr, "%d fragments have been recorded into\n", info.blocks); #endif signalSemaphoreWithIndex(recordSema); } } } /* transform samples from Squeak's format to the sound card's format. * returns the number of bytes inserted */ static int copyIntoBuffer(unsigned char *src, int numFrames, unsigned char *destBuffer) { unsigned char *dest; /* next byte to write into */ int halfFramesConsumed = 0; /* number of half-frames used up */ int bytesWritten = 0; /* number of bytes written so far */ dest = destBuffer; #if defined(TRACE) || 0 fprintf(stderr, "(%d)converting %d frames from %p to %p\n", utime(), numFrames, src, dest); #endif while(halfFramesConsumed/2 < numFrames) { /* only write odd frames if we are in stereo */ if(fmtStereo || (halfFramesConsumed%2 == 1)) { if(fmtBytes == 1) { /* 8-bit sound device; write the MSB only */ if(IS_BIGENDIAN) *dest = src[0]; else *dest = src[1]; /* convert it to unsigned, if necessary */ if(! fmtSigned) *dest = *dest + 128; dest += 1; bytesWritten += 1; } else { /* 16-bit sound device. more fun */ unsigned char byte0=src[0], byte1=src[1]; /* convert to unsigned if necessary */ if(! fmtSigned) { /* add 16384 by adding 128 to the high * byte */ if(IS_BIGENDIAN) byte0 += 128; else byte1 += 128; } /* copy the bytes into the output buffer, * swapping if necessary */ if(fmtSwapBytes) { dest[0] = byte1; dest[1] = byte0; } else { dest[0] = byte0; dest[1] = byte1; } dest += 2; bytesWritten += 2; } } /* even if we only skiped the sample, we have consumed another half-frame and 2 bytes of source */ halfFramesConsumed += 1; src += 2; } #ifdef TRACE fprintf(stderr, "converted %d halfFrames into %d bytes\n", halfFramesConsumed, bytesWritten); #endif assert(halfFramesConsumed % 2 == 0); return halfFramesConsumed/2; } /* convert and mix samples into an output buffer */ /* XXX you know what? the Squeak buffer could be treated as an array of short's !! */ static int mixIntoBuffer(unsigned char *src, int frames, unsigned char *destBuf) { unsigned char *dest; /* next byte to write into */ int halfFramesConsumed = 0; /* number of half-frames used up */ int bytesWritten = 0; /* number of bytes written so far */ int i; int newVal; /* used to hold intermediate new value, * before capping */ /* this is currently only set up to fill in entire fragments */ dest = destBuf; /* XXX this is a bit silly. same with convertForPlaying */ #ifdef TRACE fprintf(stderr, "mixing %d frames from %p into %p\n", frames, src, dest); #endif while(halfFramesConsumed/2 < frames) { /* only write odd frames if we are in stereo */ if(fmtStereo || (halfFramesConsumed%2 == 1)) { if(fmtBytes == 1) { /* 8-bit sound device */ if(fmtSigned) newVal = * (signed char *)dest; else { newVal = * (unsigned char *) dest; newVal -= 128; } /* add in the MSB */ if(IS_BIGENDIAN) newVal += ((signed char *)src)[0]; else newVal += ((signed char *)src)[1]; /* cap the value */ if(newVal > 127) { fprintf(stderr, "capping\n"); newVal = 127; } if(newVal < -128) { fprintf(stderr, "capping2\n"); newVal = -128; } /* write the new value out */ if(fmtSigned) *dest = newVal; else *dest = newVal + 128; dest += 1; bytesWritten += 1; } else { /* 16-bit sound device. more fun */ int byte0, byte1; /* load the value already in the buffer */ byte0 = (signed char) (dest[0]); byte1 = (signed char) (dest[1]); if((fmtSwapBytes && IS_BIGENDIAN) || (!fmtSwapBytes && !IS_BIGENDIAN)) { /* little-endian output */ newVal = byte0 + 256*byte1; } else { /* big-endian output */ newVal = 256*byte0 + byte1; } if(!fmtSigned) { /* convert to signed for internal calculations */ newVal -= 32768; } /* add in the new value */ if(IS_BIGENDIAN) newVal += 256*(signed char)(src[0]) + (signed char)(src[1]); else newVal += (signed char)(src[0]) + 256*(signed char)(src[1]); /* cap the value */ if(newVal > 32767) newVal = 32767; if(newVal < -32768) newVal = -32768; /* write it out */ if((fmtSwapBytes && IS_BIGENDIAN) || (!fmtSwapBytes && !IS_BIGENDIAN)) { /* little-endian output */ dest[0] = newVal & 0xff; dest[1] = newVal >> 8; } else { /* big-endian output */ dest[0] = newVal >> 8; dest[1] = newVal & 0xff; } dest += 2; bytesWritten += 2; } } /* even if we only skiped the sample, we have consumed another half-frame and 2 bytes of source */ halfFramesConsumed += 1; src += 2; } #if defined(TRACE) || 0 fprintf(stderr, "mixed %d halfFrames (of %d) into %d bytes\n", halfFramesConsumed, frames*2, bytesWritten); #endif assert(halfFramesConsumed % 2 == 0); return halfFramesConsumed/2; } /* convert from the sound card format to squeak's format */ static void convertToSqueak(unsigned char *src, int numSamples, unsigned char *dest) { int samplesConsumed; int sample; #ifdef TRACE fprintf(stderr, "convertToSqueak(%p, %d, %p)\n", src, numSamples, dest); #endif for(samplesConsumed = 0; samplesConsumed 0 && playFragmentsPlaying < maxFragmentsPlaying) { unsigned char *dest; /* pointer to a fragment to play into */ int spaceInFrag; /* number of frames that will fit * into this fragment */ dest = playBuffer + fragBeingFilled * playFragSize; dest += fragPortionFilled; spaceInFrag = (playFragSize - fragPortionFilled) / bytesPerPlayFrame(); /* write some frames into that fragment */ framesConsumed = copyIntoBuffer(src, MIN(framesLeft, spaceInFrag), dest); assert(framesConsumed <= framesLeft); assert(framesConsumed <= spaceInFrag); /* update iteration variables */ src += 4*framesConsumed; /* note: Squeak frames are 4 * bytes regardles of format */ framesLeft -= framesConsumed; fragPortionFilled += framesConsumed * bytesPerPlayFrame(); assert(fragPortionFilled <= playFragSize); if(fragPortionFilled == playFragSize) { /* fragment is full -- move on */ playFragmentsPlaying += 1; fragBeingFilled += 1; fragBeingFilled %= playFragsTotal; fragPortionFilled = 0; } else { #ifdef TRACE fprintf(stderr, "partially filled fragment. (%d < %d)\n", fragPortionFilled, playFragSize); #endif } } /* sanity check, because... */ if(framesLeft != 0) { fprintf(stderr, "ack! %d frames left in snd_play, when it should be 0!\n", framesLeft); } #ifdef TIMINGS gettimeofday(&end, NULL); lapse = (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1000000.0; fprintf(stderr, "played %d samples in %f seconds. (%f samples/sec)\n", frameCount-framesLeft, lapse, (frameCount-framesLeft) / lapse); #endif unlockGlobals(); /* ...our return value is ignored! :( */ return frameCount - framesLeft; } int snd_PlaySilence(void) { fprintf(stderr, "PlaySilence called! Wow! What am I supposed to do about it?!\n"); return 0; } int snd_Start(int frameCount, int samplesPerSec, int stereo, int semaIndex) { #if defined(TRACE) || 0 fprintf(stderr, "snd_Start(%d, %d, %d, %d)\n", frameCount, samplesPerSec, stereo, semaIndex); #endif lockGlobals(); if(audio_fd >= 0) { fprintf(stderr, "snd_Start called, but device is already open!\n"); fprintf(stderr, "closing and starting again\n"); dsp_close(); } if(dsp_open(0, stereo, samplesPerSec)) { unlockGlobals(); return 0; } if(setupPlayBuffer(frameCount)) { dsp_close(); unlockGlobals(); return 0; } if(trigger()) { dsp_close(); unlockGlobals(); return 0; } #if defined(TRACE) || 0 fprintf(stderr, "frameCount = %d, maxFragsPlaying = %d\n", frameCount, maxFragmentsPlaying); #endif playSema = semaIndex; if(! watcherStarted) { pthread_t thread; if(pthread_create(&thread, NULL, watcher, NULL)) { perror("pthread_create"); dsp_close(); unlockGlobals(); return 0; } watcherStarted = 1; } unlockGlobals(); return 1; } /* mix samples from the given buffer into the play buffer directly, so that it will be played as soon as possible */ /* XXX it would be helpful to know if we are being given the entire sound * sample, or just the beginning of a longer sample. In the former case, * we don't need to delay the sound until the end of the buffer */ int snd_InsertSamplesFromLeadTime(int frameCount, int srcBufPtr, int samplesOfLeadTime) { unsigned char *src; /* progresses through the samples array */ int framesLeft; /* number of frames left to write */ int framesConsumed; /* number of frames written to a single frag */ int framesDumped=0; /* total frames dumped on the floor */ int i; int bytesNeeded, fragmentsNeeded, fragsToMix, fragsToSkip; int fragIndex; #ifdef TIMINGS struct timeval start, end; float lapse; #endif int fragsMixed; int framesToMix; #ifdef TIMINGS gettimeofday(&start, NULL); #endif #ifdef TRACE fprintf(stderr, "snd_InsertSamplesFromLeadTime(%d, %d, %d)\n", frameCount, srcBufPtr, samplesOfLeadTime); #endif lockGlobals(); /* sanity checks */ if(audio_fd < 0) { unlockGlobals(); return 0; } if(playBuffer == NULL) { fprintf(stderr, "trying to play, but not open for playing!\n"); unlockGlobals(); return 0; } /* figure out how many complete fragments to fill */ bytesNeeded = frameCount * bytesPerPlayFrame() - fragPortionFilled; if(bytesNeeded <= 0) { fragmentsNeeded = 0; } else { fragmentsNeeded = (bytesNeeded + (playFragSize - 1)) / playFragSize; /* round up the division */ } fragsToMix = MIN(fragmentsNeeded, (playFragmentsPlaying - 1)); assert(maxFragmentsPlaying >= playFragmentsPlaying); assert(fragsToMix >= 0); framesToMix = fragsToMix * playFragSize / bytesPerPlayFrame() + (fragPortionFilled / bytesPerPlayFrame()); fprintf(stderr, "frameCount: %d playFragmentsPlaying: %d fragPortionFilled: %d playFragSize: %d bytesPerPlayFrame: %d\n", frameCount, playFragmentsPlaying, fragPortionFilled, playFragSize, bytesPerPlayFrame()); /* initialize loop variables */ fragsMixed = 0; src = (unsigned char *) srcBufPtr; framesLeft = frameCount; /* skip a few initial frages, if needed. We must consume at least half of the frames that Squeak provides */ if(frameCount/2 > framesToMix) { /* dump some frames. We need to consume at least half of the frames Squeak provides */ framesDumped = frameCount/2 - framesToMix; fprintf(stderr, "insertSamples: dumping %d frames\n", framesDumped); framesLeft -= framesDumped; src += 4*framesDumped; } #if defined(TRACE) || 0 fprintf(stderr, "mixing %d frames (%d full fragments + some spillover)\n", framesToMix); #endif /* mix into one fragment each time through this loop */ fragIndex = fragBeingFilled - fragsToMix; if(fragIndex < 0) fragIndex += playFragsTotal; for(; fragIndex != fragBeingFilled; fragIndex = (fragIndex + 1) % playFragsTotal) { unsigned char *dest; int spaceAvail; assert(framesLeft > 0); spaceAvail = playFragSize / bytesPerPlayFrame(); /* write some frames into that fragment */ dest = playBuffer + fragIndex * playFragSize; framesConsumed = mixIntoBuffer(src, MIN(framesLeft, spaceAvail), dest); /* update the frame count */ framesLeft -= framesConsumed; src += 4*framesConsumed; fragsMixed += 1; } assert(fragsMixed == fragsToMix); /* mix into the beginning of the final, partial fragment */ framesConsumed = mixIntoBuffer(src, (fragPortionFilled / bytesPerPlayFrame()), (playBuffer + fragBeingFilled * playFragSize)); framesLeft -= framesConsumed; #ifdef TRACE fprintf(stderr, "inserted %d samples out of %d\n", frameCount-framesLeft, frameCount); #endif #ifdef TIMINGS gettimeofday(&end, NULL); lapse = (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1000000.0; fprintf(stderr, "inserted %d samples in %f seconds. (%f samples/sec)\n", frameCount-framesLeft, lapse, (frameCount-framesLeft) / lapse); #endif unlockGlobals(); assert(frameCount - framesLeft == framesToMix + framesDumped); return frameCount - framesLeft; } int snd_Stop(void) { #ifdef TRACE fprintf(stderr, "snd_Stop called\n"); #endif lockGlobals(); if(audio_fd < 0) { unlockGlobals(); return 0; } dsp_close(); unlockGlobals(); return 0; } /** recording **/ int snd_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex) { #ifdef TRACE fprintf(stderr, "snd_StartRecording(%d, %d, %d)\n", desiredSamplesPerSec, stereo, semaIndex); #endif lockGlobals(); if(audio_fd >= 0 && recordBuffer != NULL) { fprintf(stderr, "snd_StartRecording: already recording!\n"); snd_StopRecording(); } if(audio_fd >= 0 && recordBuffer == NULL && (audio_caps & DSP_CAP_DUPLEX)) { /* try for full-duplex */ assert(playBuffer != NULL); /* if the device is open, it * should be open for SOMETHING */ if(setupRecordBuffer()) { /* didn't work; fall back on record-only */ fprintf(stderr, "couldn't mmap both buffers at once\n"); dsp_close(); } if(trigger()) { /* ditto -- fall back */ dsp_close(); } } if(audio_fd < 0) { /* open the device for record-only */ if(dsp_open(1, stereo, desiredSamplesPerSec)) { unlockGlobals(); return 0; } if(setupRecordBuffer()) { /* it's just not going to record.... */ dsp_close(); unlockGlobals(); return 0; } } /* at this point, the device should be openned successfully */ assert(audio_fd >= 0); assert(recordBuffer != NULL); recordSema = semaIndex; if(trigger()) { /* doh!! something went wrong at the last moment */ dsp_close(); } unlockGlobals(); return 1; } int snd_SetRecordLevel(int level) { /* XXX can this be implemented on Linux? */ return 0; } int snd_StopRecording(void) { #ifdef TRACE fprintf(stderr, "snd_StopRecording\n"); #endif lockGlobals(); if(audio_fd < 0) { /* device not open */ unlockGlobals(); return; } if(recordBuffer == NULL) { fprintf(stderr, "snd_StopRecording: device not open for recording!\n"); } else { destroyRecordBuffer(); trigger(); } if(playBuffer == NULL) { /* device is not open for anything anymore; close it down */ dsp_close(); } unlockGlobals(); } double snd_GetRecordingSampleRate(void) { int rate; lockGlobals(); if(audio_fd < 0) { unlockGlobals(); return 0; } rate = samplingSpeed; unlockGlobals(); return rate; } int snd_RecordSamplesIntoAtLength(int buf, int startSliceIndex, int bufferSizeInBytes) { unsigned char *dest; int samplesRecorded, samplesRequested, samplesStillWanted; #ifdef TRACE fprintf(stderr, "snd_RecordSamples(%d, %d, %d)\n", buf, startSliceIndex, bufferSizeInBytes); #endif lockGlobals(); if(audio_fd < 0 || recordBuffer == NULL) { fprintf(stderr, "snd_RecordSamples: device not open for recording!\n"); unlockGlobals(); return 0; } dest = (unsigned char*) (buf + 2*startSliceIndex); samplesRecorded = 0; samplesRequested = bufferSizeInBytes / 2 - startSliceIndex; samplesStillWanted = samplesRequested; /* record from one fragment each time through this loop */ while(recordFragsFilled > 0 && samplesStillWanted > 0) { /* record from the next fragment */ int samplesToReport; /* determine number of samples to read from this fragment */ samplesToReport = (recordFragSize / fmtBytes) - samplesUsedFromNextFragToRead; /* don't use more than will fit in the buffer */ samplesToReport = MIN(samplesToReport, samplesStillWanted); /* okay, convert it */ convertToSqueak(recordBuffer + (recordFragSize * nextFragToRead) + samplesUsedFromNextFragToRead * fmtBytes, samplesToReport, dest); /* update tallies */ samplesStillWanted -= samplesToReport; dest += 2*samplesToReport; samplesUsedFromNextFragToRead += samplesToReport; if(samplesUsedFromNextFragToRead == recordFragSize / fmtBytes) { /* finished a fragment */ recordFragsFilled -= 1; nextFragToRead = (nextFragToRead + 1) % recordFragsTotal; samplesUsedFromNextFragToRead = 0; } } unlockGlobals(); return samplesRequested - samplesStillWanted; } #else /* OSS_AUDIO */ int snd_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex) { return 0; } int snd_SetRecordLevel(int level) { return 0; } int snd_StopRecording(void) { return 0; } double snd_GetRecordingSampleRate(void) { return 0; } int snd_RecordSamplesIntoAtLength(int buf, int startSliceIndex, int bufferSizeInBytes) { return 0; } /* No sound support; return values that make the VM think that sound is being played. */ int snd_AvailableSpace(void) { return 8192; } int snd_PlaySamplesFromAtLength(int frameCount, int arrayIndex, int startIndex) { return frameCount; } int snd_PlaySilence(void) { return 8192; } int snd_Start(int frameCount, int samplesPerSec, int stereo, int semaIndex) { return 1; } int snd_InsertSamplesFromLeadTime(int frameCount, int srcBufPtr, int samplesOfLeadTime) { return 0; } int snd_Stop(void) { return 0; } void snd_poll() { } #endif int soundInit(void) { return 1; } int soundShutdown(void) { snd_Stop(); return 1; }