/*      streamtest.c
 *
 * A small program to test MIDAS digital audio streams and sound effects.
 *
 * Note! This has been written for Windows NT, and has not been tested under
 * MS-DOS. Most of the stuff should work there as well, and all the sound
 * effect things with M32PRE4 too - NT specific things in main() should be
 * marked with (NT) in the comments.
 *
 * Copyright 1996 Petteri Kangaslampi
*/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <process.h>
#include "midas.h"


#define STREAMFILE "e:\\16st.sw"
#define STREAMRATE 44100
#define STREAMTYPE smp16bitStereo
#define STREAMELEMSIZE 4

#define SAMPLEFILE "swoosh.ub"
#define SAMPLERATE 22050
#define SAMPLETYPE smp8bitMono
#define SAMPLELOOP 0

//#define MODULEFILE "f:\\music\\templsun.xm"
#define MODULEFILE "f:\\music\\music.s3m"

#define TOTALCHANNELS 18

/* Stream buffer size in milliseconds: */
#define STREAMBUFFERLEN 500

/* Polling period 25ms: */
#define POLLPERIOD 25



void Error(char *msg)
{
    /* We should use a message box in a windowed application */
    printf("Error: %s\n", msg);
    exit(EXIT_FAILURE);
}


/* Use this kind of error exit function when making windowed applications: */
/*
static void CALLING ErrorExit(char *message)
{
    MessageBox(NULL, message, "MIDAS Error", MB_APPLMODAL | MB_ICONSTOP |
        MB_OK);
    ExitProcess(1);
}
*/


/* A helpful macro: */
#define CALLMIDAS(x) if ( (error = x) != OK ) midasError(error);




static void PollMIDAS(void)
{
    int         error;
    int         callMP;

    if ( (error = midasSD->StartPlay()) != OK )
        midasError(error);
    do
    {
        if ( (error = midasSD->Play(&callMP)) != OK )
            midasError(error);
        if ( callMP )
        {
//            printf("P");
            if ( midasGMPInit )
            {
                if ( (error = gmpPlay()) != OK )
                    midasError(error);
            }
        }
    } while ( callMP && (midasSD->tempoPoll == 0) );
}


static volatile stopPolling = 0;


static unsigned PollerThread(void *dummy)
{
    dummy = dummy;

    /* We'd better make the player thread's priority still above normal: */
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);

    while ( stopPolling == 0 )
    {
        PollMIDAS();
        Sleep(POLLPERIOD);
    }

    stopPolling = 0;

#ifdef __WC32__
    _endthread();
#else
    ExitThread(0);
#endif
    return 0;
}






static unsigned streamFileLeft;
static unsigned streamBufferSize = (STREAMELEMSIZE*STREAMRATE*STREAMBUFFERLEN
                                   / 1000) & (~3);
static uchar    *streamBuffer;
static FILE     *streamFile;
static uchar    streamClear;
static int      streamPlaying = 0;
volatile int    stopStream = 0;
static unsigned streamChannel;

#ifdef __WC32__
static int      streamThread;
#else
static HANDLE   streamThread;
static DWORD    streamThreadID;
#endif




static unsigned StreamPlayerThread(void *dummy)
{
    unsigned    readPos, doNow, writePos;
    int         spaceLeft;

    dummy = dummy;
    writePos = 0;

/*    printf("Olen suuri!\n"); */

    while ( !stopStream )
    {
/*        printf("GetPosition\n"); */
        /* Check where we are playing: */
        midasSD->GetPosition(streamChannel, &readPos);

        /* Calculate how much space we have left in buffer: */
        if ( readPos >= writePos )
            spaceLeft = readPos - writePos;
        else
            spaceLeft = streamBufferSize - writePos + readPos;

        /* Leave some safety margin: (warning, magic constant ahead) */
        spaceLeft -= 16;

        if ( spaceLeft > 0 )
        {
            /* Do not read more data than we have left in the file: */
            if ( spaceLeft > (int) streamFileLeft )
                doNow = streamFileLeft;
            else
                doNow = spaceLeft;
            spaceLeft -= doNow;

/*            printf("Fread(%i)\n", doNow); */
            /* Read from buffer, taking care of wraparound: */
            if ( (writePos + doNow) <= streamBufferSize )
            {
                fread(&streamBuffer[writePos], doNow, 1, streamFile);
                writePos += doNow;
                streamFileLeft -= doNow;
            }
            else
            {
                fread(&streamBuffer[writePos], streamBufferSize-writePos,
                    1, streamFile);
                doNow -= streamBufferSize-writePos;
                streamFileLeft -= streamBufferSize-writePos;
                writePos = 0;
//                printf("poks\n");
                fread(&streamBuffer[writePos], doNow, 1, streamFile);
                writePos += doNow;
                streamFileLeft -= doNow;
            }

            /* If we reached the end of the file, clear the rest of the
               buffer: */
            if ( spaceLeft )
            {
/*                printf("Clear(%i)\n", spaceLeft); */
                /* Clear, taking care of wraparound: */
                if ( (writePos + spaceLeft) <= streamBufferSize )
                {
                    memset(&streamBuffer[writePos], streamClear, spaceLeft);
                    writePos += spaceLeft;
                }
                else
                {
                    memset(&streamBuffer[writePos], streamClear,
                        streamBufferSize-writePos);
                    spaceLeft -= streamBufferSize-writePos;
                    writePos = 0;
                    memset(&streamBuffer[writePos], streamClear, spaceLeft);
                    writePos += spaceLeft;
                }
            }
        }

/*        printf("Sleep\n"); */
        Sleep(STREAMBUFFERLEN / 4);
    }

    stopStream = 0;

#ifdef __WC32__
    _endthread();
#else
    ExitThread(0);
#endif
    return 0;
}


void StopStream(void)
{
    int         error;

    if ( !streamPlaying )
        return;

    /* We'll now stop the thread. This should REALLY be implemented with
       message or something, but I'm too lazy at the moment */
    stopStream = 1;

    while ( stopStream != 0 )
        Sleep(100);

    CALLMIDAS(midasSD->StopStream(streamChannel))

    fclose(streamFile);
    free(streamBuffer);

    streamPlaying = 0;
}




/* Note! This can only play one stream at a time! */

void StartStream(unsigned channel, char *fileName, ulong rate, int sampleType)
{
    int         error;

    if ( streamPlaying )
        StopStream();

    streamChannel = channel;

    if ( (streamFile = fopen(fileName, "rb")) == NULL )
        midasError(errFileOpen);

    /* Give the file a BIG buffer: */
    setvbuf(streamFile, NULL, _IOFBF, 32768);

    /* Get file length: */
    fseek(streamFile, 0, SEEK_END);
    streamFileLeft = (unsigned) ftell(streamFile);
    fseek(streamFile, 0, SEEK_SET);

    streamBuffer = (uchar*) malloc(streamBufferSize);
    if ( (sampleType == smp8bitMono) || (sampleType == smp8bitStereo) )
        streamClear = 128;
    else
        streamClear = 0;

    memset(streamBuffer, streamClear, streamBufferSize);

    if ( streamFileLeft > streamBufferSize )
    {
        fread(streamBuffer, streamBufferSize, 1, streamFile);
        streamFileLeft -= streamBufferSize;
    }

    /* Start playing stream: */
    CALLMIDAS(midasSD->StartStream(streamChannel, streamBuffer,
        streamBufferSize, sampleType, rate))

    /* Start stream player thread: */
#ifdef __WC32__
    streamThread = _beginthread(StreamPlayerThread, NULL, 4096, NULL);

    if ( streamThread == -1 )
        Error("Couldn't start stream thread");
#else
    streamThread = CreateThread(NULL, 4096, (LPTHREAD_START_ROUTINE)
        StreamPlayerThread, NULL, 0, &streamThreadID);

    if ( streamThread == NULL )
        Error("Couldn't start stream thread");
#endif

    streamPlaying = 1;
}



/****************************************************************************\
*
* Function:     unsigned LoadSample(char *fileName, int sampleType, int loop)
*
* Description:  Loads a sample to the current Sound Device
*
* Input:        char *fileName          sample file name
*               int sampleType          sample type - smp8bitMono,
*                                       smp8bitStereo, smp16bitMono or
*                                       smp16bitStereo - note that not all
*                                       SDs support all sample types
*               int loop                1 if the sample should be looped
*
* Returns:      Sample handle for the sample loaded - use with PlaySample();
*
* Note:         The only available sample type for M32PRE4 is smp8bit
*               (note the spelling!) - 8-bit unsigned mono sample.
*
\****************************************************************************/

unsigned LoadSample(char *fileName, int sampleType, int loop)
{
    fileHandle  f;
    int         error;
    long        len;
    uchar       *buf;
    sdSample    smp;
    unsigned    handle;

    /* Get sample file size and load it: */
    CALLMIDAS(fileOpen(fileName, fileOpenRead, &f))
    CALLMIDAS(fileGetSize(f, &len))
    CALLMIDAS(memAlloc(len, (void**) &buf))
    CALLMIDAS(fileRead(f, buf, len))
    CALLMIDAS(fileClose(f))

    /* Build sdSample structure for the sample: */
    smp.sample = buf;
    smp.samplePos = sdSmpConv;
    smp.sampleType = sampleType;
    smp.sampleLength = len;

    if ( loop )
    {
        /* We'll just loop the whole sample */
        smp.loopMode = sdLoop1;
        smp.loop1Start = 0;
        smp.loop1End = len;
        smp.loop1Type = loopUnidir;
    }
    else
    {
        /* No loop */
        smp.loopMode = sdLoopNone;
        smp.loop1Start = smp.loop1End = 0;
        smp.loop1Type = loopNone;
    }
    smp.loop2Start = smp.loop2End = 0;
    smp.loop2Type = loopNone;

    /* Add the sample to Sound Device: */
    CALLMIDAS(midasSD->AddSample(&smp, 1, &handle))

    /* Free buffer and exit: */
    CALLMIDAS(memFree(buf))

    return handle;
}


/****************************************************************************\
*
* Function:     void RemoveSample(unsigned sampleHandle)
*
* Description:  Removes a sample from the Sound Device - deallocates the
*               memory etc.
*
* Input:        unsigned sampleHandle   sample to be removed
*
\****************************************************************************/

void RemoveSample(unsigned sampleHandle)
{
    int         error;

    /* Remove the sample from Sound Device: */
    CALLMIDAS(midasSD->RemoveSample(sampleHandle))
}



/****************************************************************************\
*
* Function:     void PlaySample(unsigned channel, unsigned sampleHandle,
*                   ulong sampleRate, unsigned volume, int panning)
*
* Description:  Plays a sample
*
* Input:        unsigned channel        channel number
*               unsigned sampleHandle   sample to be played
*               ulong sampleRate        sampling rate
*               unsigned volume         volume (0-64)
*               int panning             panning (-64 - 64, 0 is middle)
*
* Notes:        You can use the same functions as here to control the volume
*               and panning when the sound is playing, plus
*               SD->SetRate(channel, rate) to control the playing rate.
*
\****************************************************************************/

void PlaySample(unsigned channel, unsigned sampleHandle, ulong sampleRate,
    unsigned volume, int panning)
{
    int         error;

    /* Stop any previous sound: */
    CALLMIDAS(midasSD->StopSound(channel))

    /* Change sample: */
    CALLMIDAS(midasSD->SetSample(channel, sampleHandle))

    /* Set new volume: */
    CALLMIDAS(midasSD->SetVolume(channel, volume))

    /* Set new panning position: */
    CALLMIDAS(midasSD->SetPanning(channel, panning))

    /* Play sound: */
    CALLMIDAS(midasSD->PlaySound(channel, sampleRate))
}



/****************************************************************************\
*
* Function:     void StopSample(unsigned channel)
*
* Description:  Stops playing a sample
*
* Input:        unsigned channel        channel to be stopped
*
\****************************************************************************/

void StopSample(unsigned channel)
{
    int         error;

    /* Stop the sound: */
    CALLMIDAS(midasSD->StopSound(channel))
}



DWORD           prevTime;

/****************************************************************************\
*
* Function:     void LoopCallback(unsigned channel)
*
* Description:  Our kewl loop callback function
*
* Input:        unsigned channel        channel number
*
\****************************************************************************/

void CALLING LoopCallback(unsigned channel)
{
    DWORD       time = GetTickCount();
    printf("Channel %u looped, diff %u ms\n", channel, time-prevTime);
    prevTime = time;
}


int main(int argc, char *argv[])
{
//    unsigned    readPos, writePos;
//    int         spaceLeft;
//    unsigned    fileLeft;
//    unsigned    doNow;
//    FILE        *f;
    unsigned    mixRate;
    int         error;
    unsigned    sample;
    int         quit = 0;
    gmpModule   *module = NULL;
    char        c;
    gmpInformation *info;
#ifdef __WC32__
    int         pollThread;
#else
    HANDLE      pollThread;
    DWORD       pollThreadID;
#endif

    setbuf(stdout, NULL);

    /* (NT) Let's be a Very Important Process: */
    SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS);

    midasSetDefaults();

/*  (NT) When making windowed applications, remember to change the error
    exit function:
    midasSetErrorExit(&ErrorExit); */

    /* (NT) Let's make our buffer a bit smaller: */
    mBufferLength = 150;                  /* 150 ms */
    mBufferBlocks = 4;
//    midasSDNumber = 1;
//    midasOutputMode = sdMono;
//    midasMixRate = 4000;

    midasInit();

    CALLMIDAS(midasSD->GetMixRate(&mixRate))
    printf("Playing at %uHz, using %s\n", mixRate,
        midasSD->cardNames[midasSD->cardType-1]);

    /* Start polling MIDAS in a thread: */
#ifdef __WC32__
    pollThread = _beginthread(PollerThread, NULL, 4096, NULL);
    if ( pollThread == -1 )
        Error("Problems - couldn't create player thread!");
#else
    pollThread = CreateThread(NULL, 4096, (LPTHREAD_START_ROUTINE)
        PollerThread, NULL, 0, &pollThreadID);
    if ( pollThread == NULL )
        Error("Problems - couldn't create player thread!");
#endif

    /* Open a lot of useless channels: */
    midasOpenChannels(TOTALCHANNELS);

    /* Note! MIDAS will use the _LAST_ N channels (where N is the number of
       channels in module) for music, so you have the first TOTALCHANNELS-N
       channels available for effects */


    /* Load our sample: */
    sample = LoadSample(SAMPLEFILE, SAMPLETYPE, SAMPLELOOP);

    /* We could load zillions of more (assuming not with GUS) */

    /* Set sample loop callback to channel 0 - we could use it to update
       stream data if the buffer was smaller: */
    CALLMIDAS(dsmSetLoopCallback(0, LoopCallback))

    while ( !quit )
    {
        puts("Keys: 1    Play effect\n"
             "      2    Stop effect\n"
             "      q    Play stream\n"
             "      w    Stop stream\n"
             "      a    Play module\n"
             "      s    Stop module\n"
             "      p    Show module playing info\n"
             "      Esc  Exit");
        c = getch();
        switch ( c )
        {
            case '1':
                PlaySample(1, sample, SAMPLERATE, 64, panMiddle);
                break;

            case '2':
                StopSample(1);
                break;

            case 'q':
                StartStream(0, STREAMFILE, STREAMRATE, STREAMTYPE);
                break;

            case 'w':
                StopStream();
                break;

            case 'a':
                if ( module != NULL )
                {
                    midasStopModule(module);
                    CALLMIDAS(gmpFreeModule(module))
                    module = NULL;
                }
                CALLMIDAS(gmpLoadS3M(MODULEFILE, 1, NULL, &module))
                midasPlayModule(module, 0);
                break;

            case 's':
                if ( module != NULL )
                {
                    midasStopModule(module);
                    CALLMIDAS(gmpFreeModule(module))
                    module = NULL;
                }
                break;

            case 'p':
                printf("Press any\n");
                while ( !kbhit() )
                {
                    CALLMIDAS(gmpGetInformation(midasPlayHandle, &info))
                    printf("Pos %02X, Patt %02X, Row %02X\r",
                        info->position, info->pattern, info->row);
                    Sleep(20);
                }
                getch();
                printf("\n");
                break;

            case 27:
                quit = 1;
                break;
        }
    }

    /* Stop polling MIDAS in the thread (ugly or what? naah...) */
    stopPolling = 1;
    while ( stopPolling )
        Sleep(POLLPERIOD/2);

    /* Moved RemoveSample() here - this way we'll be sure nothing will attempt
       to play the sample after it has been removed. In practise we should
       stop all sounds before removing the samples */
    RemoveSample(sample);

    midasClose();

    return 0;
}