summaryrefslogtreecommitdiff
path: root/snd/audiomac.c
diff options
context:
space:
mode:
Diffstat (limited to 'snd/audiomac.c')
-rw-r--r--snd/audiomac.c534
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
+