/*      MODLOAD.C
 *
 * Protracker Module loader
 *
 * Copyright 1995 Petteri Kangaslampi and Jarno Paananen
 *
 * This file is part of the MIDAS Sound System, and may only be
 * used, modified and distributed under the terms of the MIDAS
 * Sound System license, LICENSE.TXT. By continuing to use,
 * modify or distribute this file you indicate that you have
 * read the license and understand and accept it fully.
*/

#include "lang.h"
#include "mtypes.h"
#include "errors.h"
#include "mglobals.h"
#include "mmem.h"
#include "file.h"
#include "sdevice.h"
#include "mplayer.h"
#include "mod.h"
#ifndef NOEMS
#include "ems.h"
#endif
#include "vu.h"
#include "mutils.h"

#ifndef NULL
    #define NULL 0L
#endif



/* Macro for endianness-swap. DANGEROUS - references the argument x
   twice */
#define SWAP16(x) ( ((x << 8) & 0xFF00) | ( (x >> 8) & 0x00FF) )

/* Size of temporary memory area used for avoiding memory fragmentation
   if EMS is used or in protected mode */
#define TEMPSIZE 8192

/* Pass error code in variable "error" on, used in modLoadModule(). */
#define MODLOADPASSERROR { modLoadError(SD); PASSERROR(ID_modLoadModule) }


/****************************************************************************\
*       Module loader buffers and file pointer. These variables are static
*       instead of local so that a separate deallocation can be used which
*       will be called before exiting in error situations
\****************************************************************************/
static fileHandle f;
static int      fileOpened;
static mpModule *mmod;
static ulong    *pattBuf;
static uchar    *smpBuf;
static void     *tempmem;



/****************************************************************************\
*
* Function:     int modFreeModule(mpModule *module, SoundDevice *SD);
*
* Description:  Deallocates a Protracker module
*
* Input:        mpModule *module        module to be deallocated
*               SoundDevice *SD         Sound Device that has stored the
*                                       samples
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING modFreeModule(mpModule *module, SoundDevice *SD)
{
    int         i, error;

    if ( module == NULL )               /* valid module? */
    {
        ERROR(errUndefined, ID_modFreeModule);
        return errUndefined;
    }


    /* deallocate pattern orders if allocated: */
    if ( module->orders != NULL )
        if ( (error = memFree(module->orders)) != OK )
            PASSERROR(ID_modFreeModule)

    /* deallocate instrument used flags is allocated: */
    if ( module->instsUsed != NULL )
        if ( (error = memFree(module->instsUsed)) != OK )
            PASSERROR(ID_modFreeModule)

    if ( module->insts != NULL )        /* instruments? */
    {
        for ( i = 0; i < module->numInsts; i++ )
        {
            /* If the instrument has been added to Sound Device, remove
               it, otherwise just deallocate the sample if allocated */

            if ( (module->insts[i].sdInstHandle != 0) && (SD != NULL) )
            {
                if ( (error = SD->RemInstrument(
                    module->insts[i].sdInstHandle)) != OK )
                    PASSERROR(ID_modFreeModule)
            }
            else
                if ( module->insts[i].sample != NULL )
                    if ( (error = memFree(module->insts[i].sample)) != OK )
                        PASSERROR(ID_modFreeModule)
        }
        /* deallocate instrument structures: */
        if ( (error = memFree(module->insts)) != OK )
            PASSERROR(ID_modFreeModule)
    }

    if ( module->patterns != NULL )
    {
        for ( i = 0; i < module->numPatts; i++ )
        {
            if ( module->patterns[i] != NULL )
            {
                /* if the pattern has been allocate, deallocate it - either
                   from conventional memory or from EMS */

#ifndef NOEMS
                if ( useEMS == 1 )
                {
                    if ( (error = emsFree((emsBlock*) module->patterns[i]))
                        != OK )
                        PASSERROR(ID_modFreeModule)
                }
                else
#endif
                    if ( (error = memFree(module->patterns[i])) != OK )
                        PASSERROR(ID_modFreeModule)
            }
        }
        /* deallocate pattern pointers: */
        if ( (error = memFree(module->patterns)) != OK )
            PASSERROR(ID_modFreeModule)
    }

    /* deallocate the module: */
    if ( (error = memFree(module)) != OK)
        PASSERROR(ID_modFreeModule)

    return OK;
}




/****************************************************************************\
*
* Function:     void modLoadError(SoundDevice *SD)
*
* Description:  Stops loading the module, deallocates all buffers and closes
*               the file.
*
* Input:        SoundDevice *SD         Sound Device that has been used for
*                                       loading.
*
\****************************************************************************/

static void modLoadError(SoundDevice *SD)
{
    /* Close file if opened. Do not process errors. */
    if ( fileOpened )
        if ( fileClose(f) != OK )
            return;

    /* Attempt to deallocate module if allocated. Do not process errors. */
    if ( mmod != NULL )
        if ( modFreeModule(mmod, SD) != OK )
            return;

    /* Deallocate buffers if allocated. Do not process errors. */
    if ( pattBuf != NULL )
        if ( memFree(pattBuf) != OK )
            return;
    if ( smpBuf != NULL )
        if ( memFree(smpBuf) != OK )
            return;
    if ( tempmem != NULL )
        if ( memFree(tempmem) != OK )
            return;
}




/****************************************************************************\
*
* Function:     int modLoadModule(char *fileName, SoundDevice *SD,
*                   int (*SaveSampleInfo)(ushort sdInstHandle, uchar *sample,
*                   ushort slength, ushort loopStart, ushort loopEnd),
*                   mpModule **module);
*
* Description:  Loads a Protracker module into memory
*
* Input:        char *fileName          name of module file to be loaded
*               SoundDevice *SD         Sound Device which will store the
*                                       samples. NULL if the samples should
*                                       not be added to a Sound Device, but
*                                       should be left in conventional memory
*                                       instead.
*               int (*SaveSampleInfo)() Pointer to sample information saving
*                                       function. sdInstHandle = Sound Device
*                                       instrument handle, sample = pointer to
*                                       sample data, slength = sample length,
*                                       loopStart = sample loop start,
*                                       loopEnd = sample loop end. The
*                                       function must return a MIDAS error
*                                       code. NULL if no such function is
*                                       used.
*               mpModule **module       pointer to variable which will store
*                                       the module pointer.
*
* Returns:      MIDAS error code.
*               Pointer to module structure is stored in *module.
*
* Notes:        The only practical use at this point for SaveSampleInfo() are
*               the real VU-meters. To load a module and add the prepare the
*               VU meter information point SaveSampleInfo to vuPrepare().
*
\****************************************************************************/

int CALLING modLoadModule(char *fileName, SoundDevice *SD,
    int CALLING (*SaveSampleInfo)(ushort sdInstHandle, uchar *sample,
    ushort slength, ushort loopStart, ushort loopEnd), mpModule **module)
{
    int             error;              /* MIDAS error code */
    modHeader       modh;
    modInstHdr      *modi;
    mpInstrument    *inst;
    mpPattern       *pattData;

    ushort          trackLen;

    int             i, r;
    ushort          chans;
    ushort          numPatts;
    ulong           foffset;

    ushort          slength;            /* sample length */
    ushort          loopStart;          /* sample loop start */
    ushort          loopLength;         /* sample loop length */

    ulong           maxSmpLength;
    uchar           instx;

    void            *p;


    /* point buffers to NULL and set fileOpened to 0 so that modLoadError()
       can be called at any point: */
    fileOpened = 0;
    mmod = NULL;
    pattBuf = NULL;
    smpBuf = NULL;
    tempmem = NULL;
    ALE = 0;

    /* Open module file: */
    if ( (error = fileOpen(fileName, fileOpenRead, &f)) != OK )
        MODLOADPASSERROR

    /* Allocate memory for the module structure: */
    if ( (error = memAlloc(sizeof(mpModule), (void**) &mmod)) != OK )
        MODLOADPASSERROR

    mmod->orders = NULL;                 /* clear module structure so that */
    mmod->insts = NULL;                  /* it can be deallocated with */
    mmod->patterns = NULL;               /* modFree() at any point */
    mmod->instsUsed = NULL;

    mmod->MP = &mpMOD;                  /* point MP field to module player */

    /* read Protracker module header: */
    if ( (error = fileRead(f, &modh, sizeof(modHeader))) != OK )
        MODLOADPASSERROR

    chans = 0;

    /* Check the module signature to determine number of channels: */
    if ( mMemEqual(&modh.sign[0], "M.K.", 4) ) chans = 4;
    if ( mMemEqual(&modh.sign[0], "M!K!", 4) ) chans = 4;
    if ( mMemEqual(&modh.sign[0], "FLT4", 4) ) chans = 4;
    if ( mMemEqual(&modh.sign[0], "OCTA", 4) ) chans = 8;

    if ( mMemEqual(&modh.sign[1], "CHN", 3) )
    {
        /* xCHN, where x is the number of channels */
        chans = modh.sign[0] - '0';
    }

    if ( mMemEqual(&modh.sign[2], "CH", 2) )
    {
        /* xxCH, where xx is the number of channels */
        chans = (modh.sign[0] - '0') * 10 + (modh.sign[1] - '0');
    }

    if ( mMemEqual(&modh.sign[0], "TDZ", 3) )
    {
        /* TDZx, where x is the number of channels */
        chans = modh.sign[3] - '0';
    }


    /* If number of channels is undetermined, the signature is invalid. */
    if ( chans == 0 )
    {
        ERROR(errInvalidModule, ID_modLoadModule);
        modLoadError(SD);
        return errInvalidModule;
    }

    mmod->numChans = chans;              /* store number of channels */

    mMemCopy(&mmod->songName[0], &modh.songName[0], 20); /* copy song name */
    mmod->songName[20] = 0;                 /* force terminating '\0' */
    mmod->songLength = modh.songLength;     /* copy song length */
    mmod->numInsts = 31;                    /* set number of instruments */
    mmod->flags.requiresALE = 0;
    mmod->flags.extendedOctaves = 0;

    mMemCopy(&mmod->ID, &modh.sign[0], 4);  /* copy module signature */
    mmod->IDnum = idMOD;                    /* Protracker module */


    /* set up panning info in module structure: */
    for ( i = 0; i < mmod->numChans; i++ )
    {
        if ( ((i & 3) == 0) || ((i & 3) == 3) )
            mmod->chanSettings[i] = -64;
        else
            mmod->chanSettings[i] = 64;
    }

    /* find number of patterns in file: */
    numPatts = 0;
    for ( i = 0; i < 128; i++ )         /* search all song data */
        if ( modh.orders[i] >= numPatts )
            numPatts = modh.orders[i] + 1;

    mmod->numPatts = numPatts;      /* store number of patterns */

    /* allocate memory for pattern orders: */
    if ( (error = memAlloc(mmod->songLength, (void**) &mmod->orders)) != OK )
        MODLOADPASSERROR

    /* copy pattern orders */
    mMemCopy(&mmod->orders[0], &modh.orders[0], mmod->songLength);

    /* allocate memory for pattern pointers */
    if ( (error = memAlloc((mmod->numPatts) * sizeof(mpPattern*),
        (void**) &mmod->patterns)) != OK )
        MODLOADPASSERROR

    for ( i = 0; i < mmod->numPatts; i++ ) /* point all unallocated patterns */
        mmod->patterns[i] = NULL;          /* to NULL for safety */

    foffset = sizeof(modHeader);        /* point foffset to first pattern */


    /* allocate memory for instrument used flags: */
    if ( (error = memAlloc(mmod->numInsts, (void**) &mmod->instsUsed)) != OK )
        MODLOADPASSERROR

    /* Mark all instruments unused */
    for ( i = 0; i < mmod->numInsts; i++ )
        mmod->instsUsed[i] = 0;

    /* allocate memory for instrument structures: */
    if ( (error = memAlloc(mmod->numInsts * sizeof(mpInstrument),
        (void**) &mmod->insts)) != OK )
        MODLOADPASSERROR

    /* clear all instruments and find maximum instrument length: */
    maxSmpLength = 0;
    for ( i = 0; i < mmod->numInsts; i++ )
    {
        mmod->insts[i].sample = NULL;
        mmod->insts[i].sdInstHandle = 0;
        if ( maxSmpLength < ( 2 * SWAP16(modh.instruments[i].slength) ) )
            maxSmpLength = 2 * SWAP16(modh.instruments[i].slength);
    }


    /* check that none of the instruments is too long: */

    if ( maxSmpLength > SMPMAX )
        maxSmpLength = SMPMAX;
/*
    {
        ERROR(errInvalidInst, ID_modLoadModule);
        modLoadError(SD);
        return errInvalidInst;
    }
*/

    /* allocate memory for pattern loading and conversion buffer */
    if ( (error = memAlloc(chans * 256, (void**) &pattBuf)) != OK )
        MODLOADPASSERROR

    /* convert all patterns: */

    for ( i = 0; i < numPatts; i++ )
    {
        /* seek to pattern beginning: */
        if ( (error = fileSeek(f, foffset, fileSeekAbsolute)) != OK )
            MODLOADPASSERROR

        /* read pattern data: */
        if ( (error = fileRead(f, pattBuf, 256 * chans)) != OK )
            MODLOADPASSERROR

        /* check used instruments: */
        for ( r = 0; r < (64 * chans); r++)
        {
            instx = ((pattBuf[r] & 0x10) | ((pattBuf[r] >> 20) & 0xF));
            if ((instx > 0) && (instx < 32))
                mmod->instsUsed[instx-1] = 1;
        }

        /* convert pattern to internal format: */
        if ( (error = modConvertPattern(pattBuf, chans, &trackLen,
           (ushort*)&mmod->flags)) != OK )
            MODLOADPASSERROR

#ifndef NOEMS
        if ( useEMS == 1 )          /* is EMS memory used? */
        {
            /* allocate EMS memory for track: */
            if ( (error = emsAlloc(sizeof(mpPattern) + trackLen,
                (emsBlock**) &p)) != OK )
                MODLOADPASSERROR

            /* map EMS block to conventional memory and point pattData
                to the memory area: */
            if ( (error = emsMap((emsBlock*) p, (void**) &pattData))
                != OK )
                MODLOADPASSERROR
        }
        else
        {
#endif
            if ( (error = memAlloc(sizeof(mpPattern) + trackLen, &p)) != OK )
                MODLOADPASSERROR

            pattData = p;
#ifndef NOEMS
        }
#endif

        mmod->patterns[i] = p;
        pattData->length = trackLen;

        /* copy track data from buffer to the correct memory area */
        mMemCopy(&pattData->data[0], pattBuf, trackLen);

        foffset += chans * 256;         /* point foffset to next pattern */
    }

    if ( (error = memFree(pattBuf)) != OK )
        MODLOADPASSERROR
    pattBuf = NULL;


    /* If EMS is used, allocate TEMPSIZE bytes of memory before the sample
       buffer and deallocate it after allocating the sample buffer to
       minimize memory fragmentation. This is not necessary if the samples
       will not be added to a Sound Device. */
#ifndef NOEMS
    if ( useEMS )
    {
#endif
        if ( SD != NULL )
        {
            if ( (error = memAlloc(TEMPSIZE, &tempmem)) != OK )
            {
                MODLOADPASSERROR
            }
        }
#ifndef NOEMS
    }
#endif

    /* allocate memory for sample loading buffer if needed: */
    if ( SD != NULL )
    {
        if ( (error = memAlloc(maxSmpLength, (void**) &smpBuf)) != OK )
        {
            MODLOADPASSERROR
        }
    }

#ifndef NOEMS
    if ( useEMS )
    {
#endif
        if ( SD != NULL )
        {
            if ( (error = memFree(tempmem)) != OK )
            {
                MODLOADPASSERROR
            }
        }
        tempmem = NULL;
#ifndef NOEMS
    }
#endif


    /* point file offset to start of samples */
    foffset = (ulong) (chans * 256) * (ulong) numPatts + sizeof(modHeader);

    for ( i = 0; i < mmod->numInsts; i++ )
    {
        inst = &mmod->insts[i];          /* point inst to current instrument
                                            structure */

        modi = &modh.instruments[i];    /* point modi to current Protracker
                                            module instrument */

        /* Convert sample length, loop start and loop end. They are stored
           as big-endian words, and refer to number of words instead of
           bytes */
        slength = 2 * SWAP16(modi->slength);
        loopStart = 2 * SWAP16(modi->loopStart);
        loopLength = 2 * SWAP16(modi->loopLength);

        mMemCopy(&inst->iname[0], &modi->iname[0], 22);  /* copy inst name */
        inst->iname[22] = 0;            /* force terminating '\0' */
        inst->loopStart = loopStart;    /* copy sample loop start */
        inst->loopEnd = loopStart + loopLength; /* sample loop end */
        if (inst->loopEnd > slength)
            inst->loopEnd = slength;
         if (inst->loopEnd > SMPMAX)
            inst->loopEnd = SMPMAX;

        /* If sample loop end is past byte 2, the sample is looping
           (Protracker uses loop start = 0, length = 2 for no loop,
           Fasttracker start = 0, end = 0 */
        if ( inst->loopEnd > 2)
        {
            inst->looping = 1;
            inst->length = inst->loopEnd;  /* use loop end as sample length */
        }                               /* if looping to avoid loading */
        else                            /* unnecessary sample data */
        {
            inst->looping = 0;
            inst->loopEnd = 0;          /* set loop end to 0 if no loop */
            inst->length = slength;     /* use sample length */
            if (inst->length > SMPMAX)
                inst->length = SMPMAX;

        }

        inst->volume = modi->volume;        /* copy default volume */
        inst->finetune = modi->finetune;    /* copy finetune */

        if (mmod->instsUsed[i] == 1)        /* if not used, don't load */
        {
            if ( inst->length != 0 )        /* is there a sample for this inst? */
            {
                if ( SD == NULL )
                {
                    /* No Sound Device used - allocate memory for the sample
                       and point inst->sample to the memory area: */
                    if ( (error = memAlloc(inst->length, (void**) &smpBuf)) != OK )
                        MODLOADPASSERROR
                    inst->sample = smpBuf;
                }
                else
                {
                    /* The instruments will be added to a Sound Device - load
                       the sample to a buffer (allocated before) and point
                       inst->sample to NULL: */
                    inst->sample = NULL;
                }

                /* seek to sample start position: */
                if ( (error = fileSeek(f, foffset, fileSeekAbsolute)) != OK )
                    MODLOADPASSERROR

                /* read sample to buffer: */
                if ( (error = fileRead(f, smpBuf, inst->length)) != OK )
                    MODLOADPASSERROR
            }
            else
                inst->sample = NULL;

            /* convert sample from signed to unsigned: */
            if ( (error = modConvertSample(smpBuf, inst->length)) != OK )
                MODLOADPASSERROR

            /* add the instrument to Sound Device: */
            if ( SD != NULL )
            {
                error = SD->AddInstrument(smpBuf, smp8bit, inst->length,
                    inst->loopStart, inst->loopEnd, inst->volume, inst->looping,
                    1, &inst->sdInstHandle);
                if ( error != OK )
                    MODLOADPASSERROR
            }

            /* Call SaveSampleInfo() if not NULL: */
            if ( SaveSampleInfo != NULL )
            {
                if ( (error = (*SaveSampleInfo)(inst->sdInstHandle, smpBuf,
                    inst->length, inst->loopStart, inst->loopEnd)) != OK )
                    MODLOADPASSERROR
            }
        }
        else
            inst->sample = NULL;

        foffset += slength;             /* point foffset to next sample */
    }

    /* deallocate sample loading buffer: */
    if ( SD != NULL )
    {
        if ( (error = memFree(smpBuf)) != OK )
        {
            MODLOADPASSERROR
        }
    }
    smpBuf = NULL;


    if ( (error = fileClose(f)) != OK )
        MODLOADPASSERROR
    fileOpened = 0;

    *module = mmod;                     /* return module ptr in *module */
    return OK;
}
