/*      PLAYGM.C
 *
 * PLAYMM v1.01 .GM player
 *
 * Copyright 1996 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>

#if defined (__NT__)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include "midas.h"

char *title =
"PLAYMM v1.01 .GM player, MIDAS Sound System " MVERSTR "\n"
"Copyright 1996 Petteri Kangaslampi and Jarno Paananen\n";

char *usage =
"Usage:\tPLAYGM\t<filename> [MIDAS options]";




/****************************************************************************\
*
* Function:     void Error(char *msg)
*
* Description:  Prints an error message to stderr and exits to DOS
*
* Input:        char *msg               error message
*
\****************************************************************************/

void Error(char *msg)
{
    fprintf(stderr, "Error: %s\n", msg);
    midasClose();
    exit(EXIT_FAILURE);
}




/****************************************************************************\
*
* Function:     gmpModule midasPrepareGM(uchar *gm);
*
* Description:  Prepares a .GM file for playing with GMPlayer.
*
* Input:        uchar *gm               Pointer to the .GM file
*
* Returns:      Pointer to the prepared module structure, which can be
*               played with midasPlayModule().
*
* Note:         This function should be used with the simple MIDAS
*               programming interface, MIDAS.C.
*               This function modifies the start of the .GM module in
*               memory, and may thus be called ONLY ONCE for each module.
*
\****************************************************************************/

gmpModule * CALLING midasPrepareGM(uchar *gm)
{
    gmpModule   *module;
    gmpInstrument *inst;
    gmpSample   *smp;
    int         i, s, error;
    unsigned    a;
    uchar       b;
    uchar       *sample;
    U8          *gmptr = gm;
    static sdSample sdSmp;

    /* Point module to module header: */
    module = (gmpModule*) gm;
    gmptr += sizeof(gmpModule);

    /* Point module->panning to channel initial panning positions: */
    module->panning = (int*) gmptr;
    gmptr += sizeof(int) * module->numChannels;

    /* Point module->songData to song data: */
    module->songData = (ushort*) gmptr;
    gmptr += module->songLength * sizeof(ushort);

    /* Allocate memory for instrument pointers: */
    if ( (error = memAlloc(module->numInsts * sizeof(gmpInstrument*),
        &module->instruments)) != OK )
        midasError(error);

    /* Build all instrument structures and pointers: */
    for ( i = 0; i < module->numInsts; i++ )
    {
        /* Set current instrument pointer: */
        inst = module->instruments[i] = (gmpInstrument*) gmptr;
        gmptr += sizeof(gmpInstrument) + inst->numSamples *
            sizeof(gmpSample);

        /* Point inst->noteSample to note sample numbers if used: */
        if ( inst->noteSamples != NULL )
        {
            inst->noteSamples = gmptr;
            gmptr += 96;
        }

        /* Point inst->volEnvelope to volume envelope information if used: */
        if ( inst->volEnvelope != NULL )
        {
            inst->volEnvelope = (gmpEnvelope*) gmptr;
            gmptr += sizeof(gmpEnvelope);
        }

        /* Point inst->panEnvelope to panning envelope info if used: */
        if ( inst->panEnvelope != NULL )
        {
            inst->panEnvelope = (gmpEnvelope*) gmptr;
            gmptr += sizeof(gmpEnvelope);
        }
    }

    /* Allocate memory for pattern pointers: */
    if ( (error = memAlloc(module->numPatts * sizeof(gmpPattern*),
        &module->patterns)) != OK )
        midasError(error);

    /* Build pattern pointers for all patterns: */
    for ( i = 0; i < module->numPatts; i++ )
    {
        module->patterns[i] = (gmpPattern*) gmptr;
        gmptr += module->patterns[i]->length;
    }

    /* Point all samples to correct sample data, convert samples to raw
       format and add instruments to Sound Device: */
    for ( i = 0; i < module->numInsts; i++ )
    {
        inst = module->instruments[i];

        /* Convert all samples for this instrument: */
        for ( s = 0; s < inst->numSamples; s++ )
        {
            smp = &inst->samples[s];

            /* Convert sample data from delta to raw format if there is any:
               (FIXME - 16-bit samples) */
            if ( (smp->sampleLength > 0) && (smp->sampleType == smp8bit) )
            {
                sample = smp->sample = (uchar*) gmptr;
                gmptr += smp->sampleLength;

                b = 0;
                for ( a = 0; a < smp->sampleLength; a++ )
                {
                    b = b + sample[a];
                    sample[a] = b;
                }
            }

            /* Add sample to Sound Device if instrument is used: */
            if ( inst->used )
            {
                /* Build Sound Device sample structure for sample: */
                sdSmp.sample = smp->sample;
                sdSmp.sampleType = smp->sampleType;
                sdSmp.sampleLength = smp->sampleLength;
                sdSmp.loopMode = smp->loopMode;
                sdSmp.loop1Type = smp->loop1Type;
                sdSmp.loop1Start = smp->loop1Start;
                sdSmp.loop1End = smp->loop1End;
                sdSmp.loop2Type = smp->loop2Type;
                sdSmp.loop2Start = smp->loop2Start;
                sdSmp.loop2End = smp->loop2End;
                if ( sdSmp.sampleType == smpNone )
                    sdSmp.samplePos = sdSmpNone;
                else
                    sdSmp.samplePos = sdSmpConv;

                /* Add the sample to Sound Device and store the Sound Device
                   sample handle in sample->sdHandle: */
                if ( (error = gmpSD->AddSample(&sdSmp, 0, &smp->sdHandle))
                    != OK )
                    midasError(error);
            }
            else
                smp->sdHandle = 0;
        }
    }

    return module;
}




/****************************************************************************\
*
* Function:     void midasFreeGM(gmpModule *module);
*
* Description:  Deallocates a module that has been prepared for playing with
*               midasPrepareGM(). Deallocates allocated structures and
*               removes the instruments from the Sound Device. Note that
*               the .GM file in memory will NOT be deallocated.
*
* Input:        gmpModule *module       module to be deallocated.
*
\****************************************************************************/

void CALLING midasFreeGM(gmpModule *module)
{
    int         error, i, s;
    gmpSample   *smp;

    /* Deallocate pattern pointers: */
    if ( (error = memFree(module->patterns)) != OK )
        midasError(error);

    /* Remove samples from Sound Device: */
    for ( i = 0; i < module->numInsts; i++ )
    {
        for ( s = 0; s < module->instruments[i]->numSamples; s++ )
        {
            smp = &module->instruments[i]->samples[s];

            /* Remove sample from Sound Device if added: */
            if ( smp->sdHandle != 0 )
            {
                if ( (error = gmpSD->RemoveSample(smp->sdHandle)) != OK )
                    midasError(error);
            }
        }
    }

    /* Deallocate instrument pointers: */
    if ( (error = memFree(module->instruments)) != OK )
        midasError(error);
}




int main(int argc, char *argv[])
{
    int         error;
    static gmpInformation *info;
    static long        gmSize;
    static uchar       *gm;
    gmpModule   *module;
    static fileHandle  f;

    /* Disable buffering from stdout: */
    setbuf(stdout, NULL);

    puts(title);
    if ( argc < 2 )
    {
        puts(usage);
        exit(EXIT_SUCCESS);
    }

    /* Normal MIDAS initialization (could run setup here, see demo.c) */

    midasSetDefaults();

    /* EMS MUST be disabled with GMs: */
    midasDisableEMS = 1;

    midasInit();

    printf("Using %s\n%s, using port %X, IRQ %i and DMA %i\n",
        midasSD->name, midasSD->cardNames[midasSD->cardType-1],
        midasSD->port, midasSD->IRQ, midasSD->DMA);

    /* Check .GM file length: */
    if ( (error = fileOpen(argv[1], fileOpenRead, &f)) != OK )
        Error(errorMsg[error]);
    if ( (error = fileGetSize(f, &gmSize)) != OK )
        Error(errorMsg[error]);

    /* Allocate memory for .GM file: */
    if ( (error = memAlloc(gmSize, &gm)) != OK )
        Error(errorMsg[error]);

    /* Read the .GM file to the allocated memory: (here we could decompress
       it from a super compressed format or whatever..) */
    if ( (error = fileRead(f, gm, gmSize)) != OK )
        Error(errorMsg[error]);
    if ( (error = fileClose(f)) != OK )
        Error(errorMsg[error]);

    /* Prepare the .GM file for playing: */
    module = midasPrepareGM(gm);

    /* Play the prepared module normally: */
    midasPlayModule(module, 0);

#ifdef __WIN32__
    /* Start playing in a thread: */
    StartPlayThread(50);
#endif

    /* Show something while playing: */
    puts("Playing - Press any key to stop");
    printf("Song \"%s\"\n", module->name);
    while ( !kbhit() )
    {
        if ( (error = gmpGetInformation(midasPlayHandle, &info)) != OK )
            midasError(error);
        printf("%02X %02X %02X\r", info->position, info->pattern,
            info->row);
#ifdef __WIN32__
        Sleep(50);
#endif
    }

    getch();

#ifdef __WIN32__
    /* Stop playing in a thread: */
    StopPlayThread();
#endif

    /* Stop playing and free the module: */
    midasStopModule(module);
    midasFreeGM(module);

    /* Free also the module memory area (midasFreeGM() does NOT do this!) */
    if ( (error = memFree(gm)) != OK )
        midasError(error);

    /* Close everything and we are done: */
    midasClose();

    return 0;
}