/*	MikMod sound library
    (c) 2004, Raphael Assenat
	(c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for
	complete list.

	This library is free software; you can redistribute it and/or modify
	it under the terms of the GNU Library 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 Library General Public License for more details.
 
	You should have received a copy of the GNU Library General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id: drv_aiff.c,v 1.4 2004/02/20 22:08:33 raph Exp $

  Driver for output to a file called MUSIC.AIFF [or .AIF on Windows].

==============================================================================*/

/*
        
	Written by Axel "awe" Wefers <awe@fruitz-of-dojo.de>
   
	
	Raphael Assenat: 19 Feb 2004: Command line options documented in the MDRIVER structure,
					 and I added #if 0 's around pragmas, since gcc complaines about them. 
					 Hopefully, the IDE which uses them wont care about that?
*/

/*_______________________________________________________________________________________________iNCLUDES
*/
#if 0
#pragma mark INCLUDES
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "mikmod_internals.h"

#ifdef DRV_AIFF

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <stdio.h>
#include <math.h>			/* required for IEEE extended conversion */
#if 0
#pragma mark -
#endif
/*________________________________________________________________________________________________dEFINES
*/
#if 0
#pragma mark DEFINES
#endif

#define AIFF_BUFFERSIZE			32768
#if defined(WIN32) || defined(DJGPP)
#define AIFF_FILENAME			"music.aif"
#else
#define AIFF_FILENAME			"music.aiff"
#endif /* WIN32 */
#if 0
#pragma mark -
#endif
/*_________________________________________________________________________________________________mACROS
*/
#if 0
#pragma mark MACROS
#endif

#define AIFF_FLOAT_TO_UNSIGNED(f)    	((unsigned long)(((long)(f - 2147483648.0)) + 2147483647L + 1))
#if 0
#pragma mark -
#endif
/*___________________________________________________________________________________________________vARS
*/
#if 0 
#pragma mark VARIABLES
#endif
static	MWRITER	*gAiffOut = NULL;
static	FILE	*gAiffFile = NULL;
static	SBYTE	*gAiffAudioBuffer = NULL;
static	CHAR	*gAiffFileName = NULL;
static	ULONG	gAiffDumpSize = 0;
#if 0
#pragma mark -
#endif
/*____________________________________________________________________________________fUNCTION_pROTOTYPES
*/
#if 0
#pragma mark FUNCTION PROTOTYPES
#endif

#ifdef SUNOS
extern int fclose(FILE *);
#endif

static void	AIFF_ConvertToIeeeExtended (double theValue, char *theBytes);
static void	AIFF_PutHeader (void);
static void	AIFF_CommandLine (CHAR *theCmdLine);
static BOOL	AIFF_IsThere (void);
static BOOL	AIFF_Init (void);
static void	AIFF_Exit (void);
static void	AIFF_Update (void);
#if 0
#pragma mark -
#endif
/*___________________________________________________________________________AIFF_ConvertToIeeeExtended()
*/

void AIFF_ConvertToIeeeExtended (double theValue, char *theBytes)
{
    int			mySign;
    int			myExponent;
    double		myFMant, myFsMant;
    unsigned long	myHiMant, myLoMant;

    if (theValue < 0)
    {
        mySign = 0x8000;
        theValue *= -1;
    } else
    {
        mySign = 0;
    }

    if (theValue == 0)
    {
        myExponent = 0;
        myHiMant = 0;
        myLoMant = 0;
    }
    else
    {
        myFMant = frexp (theValue, &myExponent);
        if ((myExponent > 16384) || !(myFMant < 1))
        {
            myExponent = mySign | 0x7FFF;
            myHiMant = 0;
            myLoMant = 0;
        }
        else
        {
            myExponent += 16382;
            if (myExponent < 0)
            {
                myFMant = ldexp (myFMant, myExponent);
                myExponent = 0;
            }
            myExponent |= mySign;
            myFMant = ldexp (myFMant, 32);          
            myFsMant = floor (myFMant); 
            myHiMant = AIFF_FLOAT_TO_UNSIGNED (myFsMant);
            myFMant = ldexp (myFMant - myFsMant, 32); 
            myFsMant = floor (myFMant); 
            myLoMant = AIFF_FLOAT_TO_UNSIGNED (myFsMant);
        }
    }
    
    theBytes[0] = myExponent >> 8;
    theBytes[1] = myExponent;
    theBytes[2] = myHiMant >> 24;
    theBytes[3] = myHiMant >> 16;
    theBytes[4] = myHiMant >> 8;
    theBytes[5] = myHiMant;
    theBytes[6] = myLoMant >> 24;
    theBytes[7] = myLoMant >> 16;
    theBytes[8] = myLoMant >> 8;
    theBytes[9] = myLoMant;
}

/*_______________________________________________________________________________________AIFF_PutHeader()
*/

static void	AIFF_PutHeader(void)
{
    ULONG	myFrames;
    UBYTE	myIEEE[10];
    
    myFrames = gAiffDumpSize / (((md_mode&DMODE_STEREO) ? 2 : 1) * ((md_mode & DMODE_16BITS) ? 2 : 1));
    AIFF_ConvertToIeeeExtended ((double) md_mixfreq, myIEEE);

    _mm_fseek (gAiffOut, 0, SEEK_SET);
    _mm_write_string  ("FORM", gAiffOut);				/* chunk 'FORM' */
    _mm_write_M_ULONG (gAiffDumpSize + 36, gAiffOut);			/* length of the file */
    _mm_write_string  ("AIFFCOMM", gAiffOut);				/* chunk 'AIFF', 'COMM' */
    _mm_write_M_ULONG (18, gAiffOut);					/* length of this AIFF block */
    _mm_write_M_UWORD ((md_mode & DMODE_STEREO) ? 2 : 1, gAiffOut);	/* channels */
    _mm_write_M_ULONG (myFrames, gAiffOut);				/* frames = freq * secs */
    _mm_write_M_UWORD ((md_mode & DMODE_16BITS) ? 16 : 8, gAiffOut);	/* bits per sample */
    _mm_write_UBYTES  (myIEEE, 10, gAiffOut);				/* frequency [IEEE extended] */
    _mm_write_string  ("SSND", gAiffOut);				/* data chunk 'SSND' */
    _mm_write_M_ULONG (gAiffDumpSize, gAiffOut);			/* data length */
    _mm_write_M_ULONG (0, gAiffOut);					/* data offset, always zero */
    _mm_write_M_ULONG (0, gAiffOut);					/* data blocksize, always zero */
}

/*_____________________________________________________________________________________AIFF_CommandLine()
*/

static void	AIFF_CommandLine (CHAR *theCmdLine)
{
    CHAR	*myFileName = MD_GetAtom ("file", theCmdLine,0);

    if (myFileName != NULL)
    {
        _mm_free (gAiffFileName);
        gAiffFileName = myFileName;
    }
}

/*_________________________________________________________________________________________AIFF_isThere()
*/

static BOOL	AIFF_IsThere (void)
{
    return (1);
}

/*____________________________________________________________________________________________AIFF_Init()
*/

static BOOL	AIFF_Init (void)
{
#if defined unix || (defined __APPLE__ && defined __MACH__)
    if (!MD_Access (gAiffFileName ? gAiffFileName : AIFF_FILENAME))
    {
        _mm_errno=MMERR_OPENING_FILE;
        return (1);
    }
#endif

    if (!(gAiffFile = fopen (gAiffFileName ? gAiffFileName : AIFF_FILENAME, "wb")))
    {
        _mm_errno = MMERR_OPENING_FILE;
        return (1);
    }
    if (!(gAiffOut =_mm_new_file_writer (gAiffFile)))
    {
        fclose (gAiffFile);
        unlink(gAiffFileName ? gAiffFileName : AIFF_FILENAME);
        gAiffFile = NULL;
        return (1);
    }

    if (!(gAiffAudioBuffer = (SBYTE*) _mm_malloc (AIFF_BUFFERSIZE)))
    {
        _mm_delete_file_writer (gAiffOut);
        fclose (gAiffFile);
        unlink (gAiffFileName ? gAiffFileName : AIFF_FILENAME);
        gAiffFile = NULL;
        gAiffOut = NULL;
        return 1;
    }

    md_mode|=DMODE_SOFT_MUSIC|DMODE_SOFT_SNDFX;

    if (VC_Init ())
    {
        _mm_delete_file_writer (gAiffOut);
        fclose (gAiffFile);
        unlink (gAiffFileName ? gAiffFileName : AIFF_FILENAME);
        gAiffFile = NULL;
        gAiffOut = NULL;
        return 1;
    }
    
    gAiffDumpSize = 0;
    AIFF_PutHeader ();

    return (0);
}

/*____________________________________________________________________________________________AIFF_Exit()
*/

static void	AIFF_Exit (void)
{
    VC_Exit ();

    /* write in the actual sizes now */
    if (gAiffOut != NULL)
    {
        AIFF_PutHeader ();
        _mm_delete_file_writer (gAiffOut);
        fclose (gAiffFile);
        gAiffFile = NULL;
        gAiffOut = NULL;
    }
    if (gAiffAudioBuffer != NULL)
    {
        free (gAiffAudioBuffer);
        gAiffAudioBuffer = NULL;
    }
}

/*__________________________________________________________________________________________AIFF_Update()
*/

static void	AIFF_Update (void)
{
    ULONG	myByteCount;

    myByteCount = VC_WriteBytes (gAiffAudioBuffer, AIFF_BUFFERSIZE);
    if (md_mode & DMODE_16BITS)
    {
        _mm_write_M_UWORDS ((UWORD *) gAiffAudioBuffer, myByteCount >> 1, gAiffOut);
    }
    else
    {
        ULONG	i;
        
        for (i = 0; i < myByteCount; i++)
        {
            gAiffAudioBuffer[i] -= 0x80;				/* convert to signed PCM */
        }
        _mm_write_UBYTES (gAiffAudioBuffer, myByteCount, gAiffOut);
    }
    gAiffDumpSize += myByteCount;
}

/*________________________________________________________________________________________________drv_osx
*/

MIKMODAPI MDRIVER drv_aiff = {
    NULL,
    "Disk writer (aiff)",
    "AIFF disk writer (music.aiff) v1.1",
    0,255,
    "aif",
	"file:t:music.aiff:Output file name\n",
    AIFF_CommandLine,
    AIFF_IsThere,
    VC_SampleLoad,
    VC_SampleUnload,
    VC_SampleSpace,
    VC_SampleLength,
    AIFF_Init,
    AIFF_Exit,
    NULL,
    VC_SetNumVoices,
    VC_PlayStart,
    VC_PlayStop,
    AIFF_Update,
    NULL,
    VC_VoiceSetVolume,
    VC_VoiceGetVolume,
    VC_VoiceSetFrequency,
    VC_VoiceGetFrequency,
    VC_VoiceSetPanning,
    VC_VoiceGetPanning,
    VC_VoicePlay,
    VC_VoiceStop,
    VC_VoiceStopped,
    VC_VoiceGetPosition,
    VC_VoiceRealVolume
};

#else

MISSING(drv_aiff);

#endif

/*____________________________________________________________________________________________________eOF
*/