/*
 
 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;
}





