diff options
Diffstat (limited to 'snd/audiomac.c')
-rw-r--r-- | snd/audiomac.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/snd/audiomac.c b/snd/audiomac.c new file mode 100644 index 0000000..6b86eac --- /dev/null +++ b/snd/audiomac.c @@ -0,0 +1,534 @@ +/* + * audiomac.c + * + * Written by Dominic Mazzoni + * + * sndlib by Roger Dannenberg + * + * I rewrote this code in January 2001 to be compatible with both the old Mac OS + * and the new CarbonLib required to run under Mac OS X. + * + * Brief description of algorithm: we keep two buffers. After the first one is filled + * we pass it to the Sound Manager and ask it to call our callback function as soon as + * it has finished playing. In the meantime we start filling the second buffer. + * Whenever the first buffer completes, we copy the second buffer to the first buffer + * and start playback on that buffer, unless the second buffer is empty, in which case + * we play a small amount of silence, hoping the underlying process will catch up. + * + * Good references: + * + * http://devworld.apple.com/samplecode/Sample_Code/Sound/SndPlayDoubleBuffer.htm + * http://devworld.apple.com/technotes/tn/tn1198.htm + * + */ + +#include <MacTypes.h> +#include <Sound.h> +#include "memory.h" +#include "stdio.h" + +#include "snd.h" +#include "audiomac.h" + +#include <FixMath.h> + +#ifdef __cplusplus +extern "C" { +#endif + +long audio_write(snd_node *n, void *buffer, long length); + + +pascal void playbackCallback(SndChannelPtr channel, SndCommand *theCallBackCmd) +{ + buffer_state *data = (buffer_state *)(theCallBackCmd->param2); + + if (data->busy) { + SndDoCommand(channel, &data->callCmd, true); + return; + } + + data->busy = 1; + + // If there's data in the second buffer, copy it into the playback buffer + // and mark the second buffer as empty again. + + if (data->curBuffer == 1 && data->curSize>0) { + int bytes = data->curSize; + + if (bytes > data->bufferSize) + bytes = data->bufferSize; + + BlockMove((Ptr)data->nextBuffer, (Ptr)data->buffer, bytes); + if (bytes != data->curSize) + BlockMove((Ptr)&data->nextBuffer[bytes], (Ptr)data->nextBuffer, data->curSize - bytes); + + data->header.numFrames = bytes / data->frameSize; + + data->curSize -= bytes; + } + else { + // even when we get here, the sound output is not completed, so we count + // how many empty buffers we've sent. The flush command can return true + // when this number is high enough. + data->empty += 1; + + // Otherwise, either we're finished playing or we're stalling + + if (!data->flushing) { + // Send some silence through the speaker while we wait for + // the program to catch up + + int waittime = 4096; + int i; + + if (waittime > data->bufferSize) + waittime = data->bufferSize; + + for(i=0; i<waittime / 2; i++) + ((short *)data->buffer)[i] = 0; + data->header.numFrames = waittime / data->frameSize; + } + } + + data->busy = 0; + + if (!data->flushing) { + SndDoCommand(channel, &data->playCmd, true); + SndDoCommand(channel, &data->callCmd, true); + } +} + +pascal void recordingCallback(SPBPtr params, Ptr buffer, short peakAmplitude, long numBytes) +{ + buffer_state *data = (buffer_state *)params->userLong; + int bytesAvail; + int bufferBytesUsed; + int i; + + if (data->recqEnd >= data->recqStart) + bufferBytesUsed = data->recqEnd - data->recqStart; + else + bufferBytesUsed = (data->recqEnd + (data->bufferSize - data->recqStart)); + + bytesAvail = data->bufferSize - data->frameSize - bufferBytesUsed; + + if (numBytes > bytesAvail) { + numBytes = bytesAvail; + data->starved++; + } + + for(i=0; i<numBytes; i++) + data->recBuffer[(data->recqEnd+i) % data->bufferSize] = ((char *)buffer)[i]; + + if (numBytes > 0) + data->recqEnd = ((data->recqEnd + numBytes) % data->bufferSize); +} + +int audio_open(snd_node *n, long *f) +{ + OSErr err; + Fixed sampleRateFixed; + int i; + buffer_state *data = (buffer_state *)malloc(sizeof(buffer_state)); + n->u.audio.descriptor = (void *)data; + + data->frameSize = snd_bytes_per_frame(n); + + data->bufferSize = (int) (n->format.srate * (double)data->frameSize); + if (n->u.audio.latency > 0.0) + data->bufferSize = (int)(n->format.srate * n->u.audio.latency) * data->frameSize; + + /* Calculate sample rate as an unsigned fixed-point number */ + if (n->format.srate > 65535.0 || + n->format.srate < 1.0) + sampleRateFixed = 0xAC440000; /* Fixed for 44100 */ + else { + unsigned short numerator = (unsigned short)n->format.srate; + unsigned short denominator = (unsigned short)(65536.0*(n->format.srate - numerator)); + sampleRateFixed = (numerator << 16) | denominator; + } + + /* Open device for recording or playback, depending on mode selected */ + + if (n->write_flag == SND_READ) { + /* recording */ + short gainControl = 0; /* off */ + short numChannels = n->format.channels; + short continuousRecording = 1; /* on */ + short playthroughVolume = 0; /* off */ + OSType quality = 'cd '; + short sampleSize = 16; + short twos = 0; /* i.e. signed */ + OSType compression = 'NONE'; + OSErr err; + + data->recording = 1; + + err = SPBOpenDevice("\p", siWritePermission, &data->refnum); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'qual', &quality); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'agc ', &gainControl); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'srat', &sampleRateFixed); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'ssiz', &sampleSize); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'chan', &numChannels); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'cont', &continuousRecording); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'plth', &playthroughVolume); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'twos', &twos); + if (err) + return !SND_SUCCESS; + + err = SPBSetDeviceInfo(data->refnum, 'comp', &compression); + if (err) + return !SND_SUCCESS; + + data->recBuffer = (char *)malloc(data->bufferSize); + data->recqStart = 0; + data->recqEnd = 0; + data->starved = 0; + + data->params.inRefNum = data->refnum; + data->params.count = 0; /* data->bufferSize; /* bytes to record */ + data->params.milliseconds = 0; /* param will be ignored; use count */ + data->params.bufferLength = 0; /* ignore buffer */ + data->params.bufferPtr = NULL; /* ignore buffer */ + data->params.completionRoutine = NULL; + data->params.userLong = (long)data; + data->params.unused1 = 0; + /* NOTE: This was NewSIInterruptUPP, but this isn't defined in my compiler -RBD */ + data->params.interruptRoutine = NewSIInterruptProc(recordingCallback); + + err = SPBRecord(&data->params, true); + if (err) + return !SND_SUCCESS; + + } + else { + /* playback */ + + data->recording = 0; + data->chan = NULL; + err = SndNewChannel(&data->chan, sampledSynth, 0, NULL); + + if (err) + return !SND_SUCCESS; + + data->buffer = (char *)malloc(data->bufferSize); + + data->nextBufferSize = data->bufferSize * 3; + data->nextBuffer = (char *)malloc(data->nextBufferSize); + + if (!data->buffer || !data->nextBuffer) + return !SND_SUCCESS; + + data->chan->callBack = NewSndCallBackProc(playbackCallback); + + data->header.samplePtr = data->buffer; + data->header.numChannels = n->format.channels; + data->header.sampleRate = sampleRateFixed; + data->header.loopStart = 0; + data->header.loopEnd = 0; + data->header.encode = cmpSH; + data->header.baseFrequency = kMiddleC; + // data->header.AIFFSampleRate = 0; -- this parameter is unused + data->header.markerChunk = NULL; + data->header.format = kSoundNotCompressed; + data->header.futureUse2 = NULL; + data->header.stateVars = NULL; + data->header.leftOverSamples = NULL; + data->header.compressionID = 0; + data->header.packetSize = 0; + data->header.snthID = 0; + data->header.sampleSize = 16; + data->header.sampleArea[0] = 0; + + data->playCmd.cmd = bufferCmd; + data->playCmd.param1 = 0; //unused + data->playCmd.param2 = (long)&data->header; + + data->callCmd.cmd = callBackCmd; + data->callCmd.param1 = 0; + data->callCmd.param2 = (long)data; + + data->curBuffer = 0; + data->curSize = 0; + data->firstTime = 1; + data->finished = 0; + data->busy = 0; + data->flushing = 0; + data->empty = 1; + + // to avoid the glitch at the beginning of playback, write some zeros to prime the pump + // use nextBuffer because it's convenient. We just need a buffer full of zeros. + for (i = 0; i < data->bufferSize; i++) { + data->nextBuffer[i] = 0; + } + audio_write(n, data->nextBuffer, data->bufferSize); + } + + return SND_SUCCESS; +} + + +int audio_close(snd_node *n) +{ + buffer_state *data = (buffer_state *)n->u.audio.descriptor; + OSErr err; + + if (data->recording) { + SPBStopRecording(data->refnum); + SPBCloseDevice(data->refnum); + + if (data->starved) { + data->starved = 0; + } + + #ifndef TARGET_CARBON + DisposeRoutineDescriptor(data->params.interruptRoutine); + #endif + + free((void *)data->recBuffer); + } + else { + SndCallBackUPP callBack; + data->finished = 1; + + callBack = data->chan->callBack; + + err = SndDisposeChannel(data->chan, + true // quiets the channel now + ); + + #ifndef TARGET_CARBON + DisposeRoutineDescriptor(callBack); + #endif + + free((void *)data->buffer); + free((void *)data->nextBuffer); + } + + free((void *)data); + + return SND_SUCCESS; +} + + +int audio_flush(snd_type snd) +// return SND_SUCCESS when sound has been delivered to output +{ + buffer_state *data = (buffer_state *)snd->u.audio.descriptor; + + if (data->recording) { + SPBStopRecording(data->refnum); + } + else { + SCStatus status; + OSErr err; + + //data->flushing = 1; + + /* Start playback if we haven't already */ +/* + if (data->firstTime) { + data->header.numFrames = data->curSize / data->frameSize; + + SndDoCommand(data->chan, &data->playCmd, true); + + data->firstTime = 0; + } + + do { + err = SndChannelStatus(data->chan, sizeof(status), &status); + } while (!err && status.scChannelBusy); +*/ + + //data->flushing = 0; + + } + + // I don't know how to figure out when the last sample has been + // written to the DAC. The following waits until 8 buffers of + // zeros have been written. This seems to work well, giving a + // fraction of a second margin of extra silence at the end. + return (data->empty >= 8 ? SND_SUCCESS : !SND_SUCCESS); +} + + +long audio_read(snd_node *n, void *buffer, long length) +{ + buffer_state *data = (buffer_state *)n->u.audio.descriptor; + + if (data->recording) { + + int bufferBytesUsed; + int i; + + if (data->recqEnd >= data->recqStart) + bufferBytesUsed = data->recqEnd - data->recqStart; + else + bufferBytesUsed = (data->recqEnd + (data->bufferSize - data->recqStart)); + + if (length > bufferBytesUsed) + length = bufferBytesUsed; + + for(i=0; i<length; i++) + ((char *)buffer)[i] = data->recBuffer[(data->recqStart+i) % data->bufferSize]; + + data->recqStart = ((data->recqStart + length) % data->bufferSize); + + return length; + } + else { + /* This shouldn't happen */ + return -1; + } +} + + +long audio_write(snd_node *n, void *buffer, long length) +{ + long written = 0; + long block; + buffer_state *data = (buffer_state *)n->u.audio.descriptor; + + while(data->busy) + ; + + data->busy = 1; + + + if (data->curBuffer==0 && length>0) { + data->empty = 0; + block = min(length, data->bufferSize - data->curSize); + + if (block>0) { + + Ptr dest = (Ptr)&data->buffer[data->curSize]; + BlockMove((Ptr)buffer, dest, block); + + length -= block; + written += block; + data->curSize += block; + buffer = &((char *)buffer)[block]; + + if (data->curSize == data->bufferSize) { + data->curSize = 0; + data->curBuffer = 1; + } + } + } + + // Copy into the second buffer (the one we don't pass to the Sound Manager directly) + + if (data->curBuffer == 1 && length>0) { + data->empty = 0; + block = min(length, data->nextBufferSize - data->curSize); + + if (block > 0) { + + Ptr dest = (Ptr)&data->nextBuffer[data->curSize]; + BlockMove((Ptr)buffer, dest, block); + + length -= block; + written += block; + data->curSize += block; + } + } + + // start playback immediately + + if (data->firstTime) { + data->firstTime = 0; + + if (data->curBuffer==1) { + data->header.numFrames = data->bufferSize / data->frameSize; + } + else { + data->header.numFrames = data->curSize / data->frameSize; + data->curBuffer = 1; + data->curSize = 0; + } + + data->busy = 0; + + SndDoCommand(data->chan, &data->playCmd, true); + SndDoCommand(data->chan, &data->callCmd, true); + } + + data->busy = 0; + + return written; +} + +int audio_reset(snd_node *n) +{ + /* audio reset not implemented */ + return !SND_SUCCESS; +} + +long audio_poll(snd_type snd) +{ + buffer_state *data = (buffer_state *)snd->u.audio.descriptor; + + if (data->recording) { + + int bufferBytesUsed; + + if (data->recqEnd >= data->recqStart) + bufferBytesUsed = data->recqEnd - data->recqStart; + else + bufferBytesUsed = (data->recqEnd + (data->bufferSize - data->recqStart)); + + return (bufferBytesUsed / data->frameSize); + + } + else { + long avail = data->bufferSize - data->curSize; + + if (data->curBuffer == 0) + avail += data->bufferSize; + + /* Is this a bug in snd that I have to return frames here, + and bytes everywhere else? + */ + + return avail / data->frameSize; + } +} + +snd_fns_node mac_dictionary = { audio_poll, audio_read, audio_write, + audio_open, audio_close, audio_reset, audio_flush }; + + +void snd_init() +{ + snd_add_device((char *)"Macintosh", (char *)"default", &mac_dictionary); +} + +#ifdef __cplusplus +} // extern "C" +#endif + |