123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- /*
-
- Copyright (C) 2011 Id Software, Inc.
-
- 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.
-
- */
-
-
- /*
- ===============================
- iOS implementation of our SDL_Mixer shim for playing MIDI files.
- ===============================
- */
- #include <stddef.h>
- #include "SDL_Mixer.h"
- // Use the Embedded Audio Synthesis library as the backend MIDI renderer.
- #include "embeddedaudiosynthesis/EASGlue.h"
- // Use Core Audio Units for sound output on the device.
- #import <Foundation/Foundation.h>
- #import <AudioToolbox/AudioToolbox.h>
- /*
- ===============================
- "Constants"
- ===============================
- */
- #define ID_GRAPH_SAMPLE_RATE 22050
- /*
- ===============================
- Internal Structures
- ===============================
- */
- // Data structure for mono or stereo sound, to pass to the application's render callback function,
- // which gets invoked by a Mixer unit input bus when it needs more audio to play.
- typedef struct {
- BOOL isStereo; // set to true if there is data in the audioDataRight member
- UInt32 frameCount; // the total number of frames in the audio data
- UInt32 sampleNumber; // the next audio sample to play
- } soundStruct, *soundStructPtr;
- typedef struct MIDIPlayerGraph_tag {
- AUGraph processingGraph;
- AudioUnit ioUnit;
- BOOL playing;
-
- AudioStreamBasicDescription streamFormat;
-
- soundStruct soundStructInst;
- } MIDIPlayerGraph;
- static MIDIPlayerGraph midiPlayer;
- /*
- ===============================
- Internal prototypes
- ===============================
- */
- AudioStreamBasicDescription getStreamFormat( void );
- static void printASBD( AudioStreamBasicDescription asbd );
- static void printErrorMessage( NSString * errorString, OSStatus result );
- static void configureAndInitializeAudioProcessingGraph( MIDIPlayerGraph * player );
- static void startMIDIPlayer( MIDIPlayerGraph * player );
- // AU graph callback.
- static OSStatus inputRenderCallback (
- void *inRefCon, // A pointer to a struct containing the complete audio data
- // to play, as well as state information such as the
- // first sample to play on this invocation of the callback.
- AudioUnitRenderActionFlags *ioActionFlags, // Unused here. When generating audio, use ioActionFlags to indicate silence
- // between sounds; for silence, also memset the ioData buffers to 0.
- const AudioTimeStamp *inTimeStamp, // Unused here.
- UInt32 inBusNumber, // The mixer unit input bus that is requesting some new
- // frames of audio data to play.
- UInt32 inNumberFrames, // The number of frames of audio to provide to the buffer(s)
- // pointed to by the ioData parameter.
- AudioBufferList *ioData // On output, the audio data to play. The callback's primary
- // responsibility is to fill the buffer(s) in the
- // AudioBufferList.
- );
- /* Open the mixer with a certain audio format */
- int Mix_OpenAudio(int frequency, uint16_t format, int channels,
- int chunksize) {
-
- EASGlueInit();
-
- midiPlayer.streamFormat = getStreamFormat();
- midiPlayer.playing = FALSE;
-
- configureAndInitializeAudioProcessingGraph( &midiPlayer );
-
- return 0;
- }
- /* Close the mixer, halting all playing audio */
- void Mix_CloseAudio(void) {
- AUGraphStop( midiPlayer.processingGraph );
- EASGlueShutdown();
- }
- /* Set a function that is called after all mixing is performed.
- This can be used to provide real-time visual display of the audio stream
- or add a custom mixer filter for the stream data.
- */
- void Mix_SetPostMix(void (*mix_func)
- (void *udata, uint8_t *stream, int len), void *arg) {
- }
- /* Fade in music or a channel over "ms" milliseconds, same semantics as the "Play" functions */
- int Mix_FadeInMusic(Mix_Music *music, int loops, int ms) {
-
-
- startMIDIPlayer( &midiPlayer );
-
-
- return 0;
- }
- /* Pause/Resume the music stream */
- void Mix_PauseMusic(void) {
- EASGluePause();
- }
- void Mix_ResumeMusic(void) {
- EASGlueResume();
- }
- /* Halt a channel, fading it out progressively till it's silent
- The ms parameter indicates the number of milliseconds the fading
- will take.
- */
- int Mix_FadeOutMusic(int ms) {
- EASGlueCloseFile();
-
- AUGraphStop( midiPlayer.processingGraph );
- return 1;
- }
- /* Free an audio chunk previously loaded */
- void Mix_FreeMusic(Mix_Music *music) {
- free(music);
- }
- /* Load a wave file or a music (.mod .s3m .it .xm) file */
- Mix_Music * Mix_LoadMUS(const char *file) {
-
- EASGlueOpenFile( file );
-
- Mix_Music * musicStruct = malloc( sizeof(Mix_Music) );
-
- return musicStruct;
- }
- const char * Mix_GetError(void) {
- return "";
- }
- /* Set the volume in the range of 0-128 of a specific channel or chunk.
- If the specified channel is -1, set volume for all channels.
- Returns the original volume.
- If the specified volume is -1, just return the current volume.
- */
- int Mix_VolumeMusic(int volume) {
- return 0;
- }
- /*
- =================================
- Audio Unit helper functions
- =================================
- */
- AudioStreamBasicDescription getStreamFormat( void ) {
- AudioStreamBasicDescription streamFormat = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
- // The AudioUnitSampleType data type is the recommended type for sample data in audio
- // units. This obtains the byte size of the type for use in filling in the ASBD.
- size_t bytesPerSample = sizeof (AudioUnitSampleType);
- // Fill the application audio format struct's fields to define a linear PCM,
- // stereo, noninterleaved stream at the hardware sample rate.
- streamFormat.mFormatID = kAudioFormatLinearPCM;
- streamFormat.mFormatFlags = kAudioFormatFlagsAudioUnitCanonical;
- streamFormat.mBytesPerPacket = bytesPerSample;
- streamFormat.mFramesPerPacket = 1;
- streamFormat.mBytesPerFrame = bytesPerSample;
- streamFormat.mChannelsPerFrame = 2; // 2 indicates stereo
- streamFormat.mBitsPerChannel = 8 * bytesPerSample;
- streamFormat.mSampleRate = ID_GRAPH_SAMPLE_RATE;
- NSLog (@"The stereo stream format for the I/O unit:");
- printASBD( streamFormat );
-
- return streamFormat;
- }
- static void startMIDIPlayer( MIDIPlayerGraph * player ) {
- if ( player == 0 ) {
- NSLog( @"NULL player object, can't start!" );
- return;
- }
-
- NSLog (@"Starting audio processing graph");
- OSStatus result = AUGraphStart (player->processingGraph);
- if (noErr != result) { printErrorMessage( @"AUGraphStart", result ); return;}
- player->playing = YES;
- }
- // You can use this method during development and debugging to look at the
- // fields of an AudioStreamBasicDescription struct.
- static void printASBD( AudioStreamBasicDescription asbd ) {
- char formatIDString[5];
- UInt32 formatID = CFSwapInt32HostToBig (asbd.mFormatID);
- bcopy (&formatID, formatIDString, 4);
- formatIDString[4] = '\0';
-
- NSLog (@" Sample Rate: %10.0f", asbd.mSampleRate);
- NSLog (@" Format ID: %10s", formatIDString);
- NSLog (@" Format Flags: %10lX", asbd.mFormatFlags);
- NSLog (@" Bytes per Packet: %10lu", asbd.mBytesPerPacket);
- NSLog (@" Frames per Packet: %10lu", asbd.mFramesPerPacket);
- NSLog (@" Bytes per Frame: %10lu", asbd.mBytesPerFrame);
- NSLog (@" Channels per Frame: %10lu", asbd.mChannelsPerFrame);
- NSLog (@" Bits per Channel: %10lu", asbd.mBitsPerChannel);
- }
- static void printErrorMessage( NSString * errorString, OSStatus result ) {
- char resultString[5];
- UInt32 swappedResult = CFSwapInt32HostToBig (result);
- bcopy (&swappedResult, resultString, 4);
- resultString[4] = '\0';
- NSLog (
- @"*** %@ error: %s\n",
- errorString,
- (char*) &resultString
- );
- }
- // This method performs all the work needed to set up the audio processing graph:
- // 1. Instantiate and open an audio processing graph
- // 2. Obtain the audio unit nodes for the graph
- // 3. Configure the Multichannel Mixer unit
- // * specify the number of input buses
- // * specify the output sample rate
- // * specify the maximum frames-per-slice
- // 4. Initialize the audio processing graph
- static void configureAndInitializeAudioProcessingGraph( MIDIPlayerGraph * player ) {
- if ( player == 0 ) {
- NSLog( @"NULL player graph object, can't initialize it!" );
- return;
- }
-
- NSLog (@"Configuring and then initializing audio processing graph");
- OSStatus result = noErr;
- //............................................................................
- // Create a new audio processing graph.
- result = NewAUGraph (&player->processingGraph);
- if (noErr != result) { printErrorMessage( @"NewAUGraph", result ); return;}
-
-
- //............................................................................
- // Specify the audio unit component descriptions for the audio units to be
- // added to the graph.
- // I/O unit
- AudioComponentDescription iOUnitDescription;
- iOUnitDescription.componentType = kAudioUnitType_Output;
- iOUnitDescription.componentSubType = kAudioUnitSubType_RemoteIO;
- iOUnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
- iOUnitDescription.componentFlags = 0;
- iOUnitDescription.componentFlagsMask = 0;
-
- //............................................................................
- // Add nodes to the audio processing graph.
- NSLog (@"Adding nodes to audio processing graph");
- AUNode iONode; // node for I/O unit
-
- // Add the nodes to the audio processing graph
- result = AUGraphAddNode (
- player->processingGraph,
- &iOUnitDescription,
- &iONode);
-
- if (noErr != result) { printErrorMessage( @"AUGraphNewNode failed for I/O unit", result ); return;}
- //............................................................................
- // Open the audio processing graph
- // Following this call, the audio units are instantiated but not initialized
- // (no resource allocation occurs and the audio units are not in a state to
- // process audio).
- result = AUGraphOpen (player->processingGraph);
-
- if (noErr != result) { printErrorMessage( @"AUGraphOpen", result ); return;}
-
-
- //............................................................................
- // Obtain the mixer unit instance from its corresponding node.
- result = AUGraphNodeInfo (
- player->processingGraph,
- iONode,
- NULL,
- &player->ioUnit
- );
-
- if (noErr != result) { printErrorMessage( @"AUGraphNodeInfo", result ); return;}
-
- //............................................................................
- // Multichannel Mixer unit Setup
- // Setup the struture that contains the input render callback
- AURenderCallbackStruct inputCallbackStruct;
- inputCallbackStruct.inputProc = &inputRenderCallback;
- inputCallbackStruct.inputProcRefCon = &player->soundStructInst;
-
- NSLog (@"Registering the render callback with the I/O unit" );
- // Set a callback for the specified node's specified input
- result = AUGraphSetNodeInputCallback (
- player->processingGraph,
- iONode,
- 0,
- &inputCallbackStruct
- );
- if (noErr != result) { printErrorMessage( @"AUGraphSetNodeInputCallback", result ); return;}
- NSLog (@"Setting stereo stream format for I/O unit input bus");
- result = AudioUnitSetProperty (
- player->ioUnit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- 0,
- &player->streamFormat,
- sizeof (player->streamFormat)
- );
- if (noErr != result) { printErrorMessage( @"AudioUnitSetProperty (set input bus stream format)", result ); return;}
- //............................................................................
- // Initialize audio processing graph
- // Diagnostic code
- // Call CAShow if you want to look at the state of the audio processing
- // graph.
- NSLog (@"Audio processing graph state immediately before initializing it:");
- CAShow (player->processingGraph);
- NSLog (@"Initializing the audio processing graph");
- // Initialize the audio processing graph, configure audio data stream formats for
- // each input and output, and validate the connections between audio units.
- result = AUGraphInitialize (player->processingGraph);
-
- if (noErr != result) { printErrorMessage( @"AUGraphInitialize", result ); return;}
- }
- #define RAW_EAS_BUFFER_FRAMES 128
- static OSStatus inputRenderCallback (
- void *inRefCon, // A pointer to a struct containing the complete audio data
- // to play, as well as state information such as the
- // first sample to play on this invocation of the callback.
- AudioUnitRenderActionFlags *ioActionFlags, // Unused here. When generating audio, use ioActionFlags to indicate silence
- // between sounds; for silence, also memset the ioData buffers to 0.
- const AudioTimeStamp *inTimeStamp, // Unused here.
- UInt32 inBusNumber, // The mixer unit input bus that is requesting some new
- // frames of audio data to play.
- UInt32 inNumberFrames, // The number of frames of audio to provide to the buffer(s)
- // pointed to by the ioData parameter.
- AudioBufferList *ioData // On output, the audio data to play. The callback's primary
- // responsibility is to fill the buffer(s) in the
- // AudioBufferList.
- ) {
- //printf( "Need %lu samples in %lu buffers!\n", inNumberFrames, ioData->mNumberBuffers );
-
- EAS_I32 generatedThisRender = 0;
- EAS_I32 totalGenerated = 0;
-
- // It looks like EAS interleaves stereo samples, so we have to separate them into the two
- // different buffers that the audio unit provides.
- //const UInt32 totalInterleavedSamplesNeeded = inNumberFrames * 2;
-
- AudioBuffer * audioBufferLeft = &ioData->mBuffers[0];
- AudioBuffer * audioBufferRight = &ioData->mBuffers[1];
-
- /*
- printf( "Need %lu samples in %lu buffers!\n"
- "audioBuffer byte size: %lu channels: %lu\n",
- inNumberFrames, ioData->mNumberBuffers,
- audioBuffer->mDataByteSize, audioBuffer->mNumberChannels );
- */
- AudioUnitSampleType * hardwareBufferLeft = (AudioUnitSampleType *) audioBufferLeft->mData;
- AudioUnitSampleType * hardwareBufferRight = (AudioUnitSampleType *) audioBufferRight->mData;
-
-
- // EAS_Render always produces BUFFER_SIZE_IN_MONO_SAMPLES frames per call. Currently, this
- // is defined to 128. Let's fill up a 128 frame buffer, then do a conversion from EAS_PCM
- // (which is signed 16-bit integer) to AudioUnitSampleType (which is 8.24 fixed-point with
- // a range of -1 to +1).
- //
- // Note that EAS renders interleaved stereo, so we actually a buffer size of
- // 2 * BUFFER_SIZE_IN_MONO_SAMPLES.
-
- EAS_PCM rawEASSamples[RAW_EAS_BUFFER_FRAMES * 2];
-
-
- // EAS generates interleaved stereo samples, but the AudioUnit wants noninterleaved.
- while ( totalGenerated < inNumberFrames ) {
- //EASGlueRender( hardwareBuffer + totalGenerated*2, &generatedThisRender );
- EASGlueRender( rawEASSamples, &generatedThisRender );
-
-
- // Convert from EAS's signed 16-bit format to the AudioUnit's 8.24 fixed-point format.
- // Couldn't find this in the Apple docs, but the 8.24 format should be in the range of
- // -1.0 to 1.0, wasting 6 bits of precision.
- // All we have to do here is left-shift by 9 bits. This will not overflow, because the
- // destination is a 32-bit value.
- // Also take this opportunity to de-interleave the EAS-rendered samples.
- for ( int i = 0; i < RAW_EAS_BUFFER_FRAMES; ++i ) {
- hardwareBufferLeft[totalGenerated + i] = rawEASSamples[i * 2 + 0] << 9;
- hardwareBufferRight[totalGenerated + i] = rawEASSamples[i * 2 + 1] << 9;
- }
-
- totalGenerated += generatedThisRender;
- }
-
- return noErr;
- }
|