/* coreaudiosound.c: Mac OS X CoreAudio sound I/O Copyright (c) 2006 Fredrick Meunier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "lowlevel.h" #ifdef SOUND_COREAUDIO #include #include #include #include #include "settings.h" #include "sfifo.h" #include "sound.h" #include "ui/ui.h" sfifo_t sound_fifo; /* Number of Spectrum frames audio latency to use */ #define NUM_FRAMES 2 static OSStatus coreaudiowrite( void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData ); /* info about the format used for writing to output unit */ static AudioStreamBasicDescription deviceFormat; /* converts from Fuse format (signed 16 bit ints) to CoreAudio format (floats) */ static AudioUnit gOutputUnit; /* Records sound writer status information */ static int audio_output_started; int sound_lowlevel_init( const char *dev, int *freqptr, int *stereoptr ) { OSStatus err = kAudioHardwareNoError; UInt32 count; AudioDeviceID device = kAudioDeviceUnknown; /* the default device */ UInt32 deviceBufferSize; /* bufferSize returned by kAudioDevicePropertyBufferSize */ int error; float hz; int sound_framesiz; /* get the default output device for the HAL */ count = sizeof( device ); err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &count, (void *)&device ); if ( err != kAudioHardwareNoError ) { ui_error( UI_ERROR_ERROR, "get kAudioHardwarePropertyDefaultOutputDevice error %ld", err ); return 1; } /* get the buffersize that the default device uses for IO */ count = sizeof( deviceBufferSize ); err = AudioDeviceGetProperty( device, 0, false, kAudioDevicePropertyBufferSize, &count, &deviceBufferSize ); if( err != kAudioHardwareNoError ) { ui_error( UI_ERROR_ERROR, "get kAudioDevicePropertyBufferSize error %ld", err ); return 1; } /* get a description of the data format used by the default device */ count = sizeof( deviceFormat ); err = AudioDeviceGetProperty( device, 0, false, kAudioDevicePropertyStreamFormat, &count, &deviceFormat ); if( err != kAudioHardwareNoError ) { ui_error( UI_ERROR_ERROR, "get kAudioDevicePropertyStreamFormat error %ld", err ); return 1; } *freqptr = deviceFormat.mSampleRate; deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger #ifdef WORDS_BIGENDIAN | kLinearPCMFormatFlagIsBigEndian #endif /* #ifdef WORDS_BIGENDIAN */ | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved; deviceFormat.mBytesPerPacket = 2; deviceFormat.mFramesPerPacket = 1; deviceFormat.mBytesPerFrame = 2; deviceFormat.mBitsPerChannel = 16; deviceFormat.mChannelsPerFrame = *stereoptr ? 2 : 1; /* Open the default output unit */ ComponentDescription desc; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; Component comp = FindNextComponent( NULL, &desc ); if( comp == NULL ) { ui_error( UI_ERROR_ERROR, "FindNextComponent" ); return 1; } err = OpenAComponent( comp, &gOutputUnit ); if( comp == NULL ) { ui_error( UI_ERROR_ERROR, "OpenAComponent=%ld", err ); return 1; } /* Set up a callback function to generate output to the output unit */ AURenderCallbackStruct input; input.inputProc = coreaudiowrite; input.inputProcRefCon = NULL; err = AudioUnitSetProperty( gOutputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof( input ) ); if( err ) { ui_error( UI_ERROR_ERROR, "AudioUnitSetProperty-CB=%ld", err ); return 1; } err = AudioUnitSetProperty( gOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &deviceFormat, sizeof( AudioStreamBasicDescription ) ); if( err ) { ui_error( UI_ERROR_ERROR, "AudioUnitSetProperty-SF=%4.4s, %ld", (char*)&err, err ); return 1; } err = AudioUnitInitialize( gOutputUnit ); if( err ) { ui_error( UI_ERROR_ERROR, "AudioUnitInitialize=%ld", err ); return 1; } hz = (float)machine_current->timings.processor_speed / machine_current->timings.tstates_per_frame; sound_framesiz = deviceFormat.mSampleRate / hz; if( ( error = sfifo_init( &sound_fifo, NUM_FRAMES * deviceFormat.mBytesPerFrame * deviceFormat.mChannelsPerFrame * sound_framesiz + 1 ) ) ) { ui_error( UI_ERROR_ERROR, "Problem initialising sound fifo: %s", strerror ( error ) ); return 1; } /* wait to run sound until we have some sound to play */ audio_output_started = 0; return 0; } void sound_lowlevel_end( void ) { OSStatus err; if( audio_output_started ) verify_noerr( AudioOutputUnitStop( gOutputUnit ) ); err = AudioUnitUninitialize( gOutputUnit ); if( err ) { printf( "AudioUnitUninitialize=%ld", err ); } CloseComponent( gOutputUnit ); sfifo_flush( &sound_fifo ); sfifo_close( &sound_fifo ); } /* Copy data to fifo */ void sound_lowlevel_frame( libspectrum_signed_word *data, int len ) { int i = 0; /* Convert to bytes */ libspectrum_signed_byte* bytes = (libspectrum_signed_byte*)data; len <<= 1; while( len ) { if( ( i = sfifo_write( &sound_fifo, bytes, len ) ) < 0 ) { break; } else if( !i ) { usleep( 10000 ); } bytes += i; len -= i; } if( i < 0 ) { ui_error( UI_ERROR_ERROR, "Couldn't write sound fifo: %s", strerror( i ) ); } if( !audio_output_started ) { /* Start the rendering The DefaultOutputUnit will do any format conversions to the format of the default device */ OSStatus err = AudioOutputUnitStart( gOutputUnit ); if( err ) { ui_error( UI_ERROR_ERROR, "AudioOutputUnitStart=%ld", err ); return; } audio_output_started = 1; } } /* This is the audio processing callback. */ OSStatus coreaudiowrite( void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData ) { int f; int len = deviceFormat.mBytesPerFrame * inNumberFrames; uint8_t* out_l = ioData->mBuffers[0].mData; uint8_t* out_r = deviceFormat.mChannelsPerFrame > 1 ? ioData->mBuffers[1].mData : 0; if( out_r ) { /* Deinterleave the left and right stereo channels into their approptiate buffers */ while( sfifo_used( &sound_fifo ) && len ) { f = sfifo_read( &sound_fifo, out_l, deviceFormat.mBytesPerFrame ); out_l += deviceFormat.mBytesPerFrame; f = sfifo_read( &sound_fifo, out_r, deviceFormat.mBytesPerFrame ); out_r += deviceFormat.mBytesPerFrame; len -= deviceFormat.mBytesPerFrame; } /* If we ran out of sound, make do with silence :( */ if( len ) { for( f=0; f 0 ) { out_l += f; len -= f; } /* If we ran out of sound, make do with silence :( */ if( f < 0 ) { for( f=0; f