// ----------------------- PLAYVTAL.C --------------------------
// VTAL Interface for games.
// (C) Copyright 1994-95 by JCAB of Iguana-VangeliSTeam.

#if 0
#include "playvtal.h"
#include <dpmi.h>

#include <stdlib.h>     // Need NULL defined
#include <conio.h>      // outp/inp inline functions.
#include <i86.h>        // _enable
#include <string.h>

#define PRIVATE static




// ********************************************************
// --------------------------------------------------------
// Local data.

typedef void (__interrupt __far *TIRQHandler)(void);

PRIVATE TIRQHandler  OldIrq0Handler;   // Original IRQ0 handler.
PRIVATE PLYV_PRec    PollRec  = NULL;  // PLYV structure used for timer mode.
PRIVATE volatile int IntCount = 0;     // Counter and increment used for
PRIVATE volatile int IntIncr  = 65536; // maintaining the system IRQ0 18.2 Hz rate.
PRIVATE XTRN_TStack  PLYV_Stack = {0,0};// Stack for polling from IRQs.

#define SYSTEMTICK (*(volatile uint32 LP)(0x46C))




// ********************************************************
// --------------------------------------------------------
// Interrupt handler related functions.

PRIVATE void Handler(void)
{
    PLYV_Poll(PollRec, 20);    // Poll sound up to 20 ticks.
}

PRIVATE void __far __interrupt __loadds Irq0Handler(void)
{
    // First, call the original handler when appropriate.

    IntCount += IntIncr;
    if(IntCount > 65536) {
        OldIrq0Handler();
        IntCount -= 65536;
    } else {
        outp(0x20, 0x20);
    }


    // Then, call the sound polling function.

    _enable();                              // Enable interrupts.
    XTRN_StackExec(&PLYV_Stack, Handler);

}

// --------------------------
// Obtains the address of an IRQ handler.

PRIVATE TIRQHandler GetIRQVector(int n)
{
    struct SREGS sregs;
    union REGS inregs, outregs;

    inregs.x.eax = 0x3500 + n + 8;   // DOS4GW redefines the DOS get vector call.
    sregs.ds = sregs.es = 0;
    int386x (0x21, &inregs, &outregs, &sregs);
    return (TIRQHandler)(MK_FP((uint16)sregs.es, outregs.x.ebx));

}

// --------------------------
// Sets the address of an IRQ handler.

PRIVATE void SetIRQVector(int n, TIRQHandler vec)
{
    struct SREGS sregs;
    union REGS inregs, outregs;

    inregs.x.eax = 0x2500 + n + 8;   // DOS set vector call.
    inregs.x.edx = FP_OFF (vec);
    sregs.ds     = FP_SEG (vec);     // Handler pointer.
    sregs.es     = 0;
    int386x (0x21, &inregs, &outregs, &sregs);
}

// --------------------------
// Function to initialize the PLAYVTAL handler.

PRIVATE void InitHandler(void)
{
    if (GetIRQVector(0) != Irq0Handler) {   // If not already installed.

        XTRN_InitStack(&PLYV_Stack, 8192);

        IntIncr = 1193180 / 50;

        outp(0x43, 54);
        outp(0x40, (uint8) IntIncr);
        outp(0x40, (uint8)(IntIncr>>8));    // Set the timer to 100 Hz.

        OldIrq0Handler = GetIRQVector(0);   // Get old handler.
        SetIRQVector(0, Irq0Handler);       // Set our handler.
    }
}

// --------------------------
// Function to uninitialize the PLAYVTAL handler.

PRIVATE void DoneHandler(void)
{
    if (GetIRQVector(0) == Irq0Handler) {   // If it was installed.
        outp(0x43, 54);     // Put the timer to 18.2 Hz.
        outp(0x40, 0);
        outp(0x40, 0);
        SetIRQVector(0, OldIrq0Handler);    // Uninstall it.
        XTRN_DoneStack(&PLYV_Stack);
    }
}




// ********************************************************
// --------------------------------------------------------
// Public functions.

// --------------------------
// Initializes an instance of PLAYVTAL.
// 'setup' is the desired values for it.

PLYV_PRec PUBLICFUNC PLYV_Init(PLYV_PSetup setup)
{
    PLYV_PRec rec;

    // Initialize and perform value checks...

    if (setup == NULL)
        return NULL;

    if (!VTAL_Init())
        return NULL;    // Table initialization failed.

    XTRN_GetMem(&rec, sizeof(*rec));    // Allocate structure memory.
    if (rec == NULL)
        return NULL;

    PollRec = NULL;     // Turn off POLL mode.


    // Setup channels for music and effects devices.

    rec->MusChannels = setup->MusicConfig.maxchans;


    // Load music device.

    if (setup->MusicDevice[0] != 0)
        rec->MusicDev = SDEV_Load(setup->MusicDevice, &setup->MusicConfig);
    else
        rec->MusicDev = NULL;


    // If there was an error, abort.

    if (rec->MusicDev == NULL) {
        XTRN_FreeMem(&rec, sizeof(*rec));
        return NULL;
    }


    // Init the rest of values.

    XTRN_GetMem(&rec->ChannelMute, rec->MusChannels*sizeof(rec->ChannelMute[0]));
    if (rec->ChannelMute == NULL) {
        SDEV_Unload(rec->MusicDev);
        XTRN_FreeMem(&rec, sizeof(*rec));
        return NULL;
    }
    memset(rec->ChannelMute,   FALSE, sizeof(rec->ChannelMute[0])*rec->MusChannels);
    memset(&rec->MusParamsChg, 0,     sizeof(rec->MusParamsChg));
    rec->MusParams    = setup->MusicParams;
    rec->MusicVolume  = rec->MusParams.Volume;
    rec->MusFade      = PLYV_FADEIN;
    rec->MusTickCount = 0;

    rec->WatchProc         = NULL;
    rec->ChanWatchPreProc  = NULL;
    rec->ChanWatchPostProc = NULL;
    rec->PollMode          = TRUE;
    rec->SongPlaying       = FALSE;
    rec->Song              = NULL;


    // Finish init and return.

    rec->MusInfo    = SDEV_GetInfo(rec->MusicDev);
    rec->SoundTypes = rec->MusInfo->SoundTypes;

    return rec;
}


// --------------------------
// Uninitialization.

void PUBLICFUNC PLYV_Done(PLYV_PRec rec)
{
    if (rec == NULL)
        return;

    // Just unload and free all resourced that might be allocated.

    DoneHandler();      // In case the handler was runing.

    PLYV_MUS_Stop(rec);
    PLYV_SetMode(rec, FALSE);
    PLYV_MUS_Unload(rec);
    SDEV_Unload(rec->MusicDev);
    XTRN_FreeMem(&rec->ChannelMute, rec->MusChannels*sizeof(rec->ChannelMute[0]));
    XTRN_FreeMem(&rec, sizeof(*rec));
    VTAL_Done();
}


// --------------------------
// Set the mode of operation. A value of 'timer = TRUE' means timer
// mode is requested. 'FALSE' means polling mode is requested.
// Returns the previous state of 'timer'.

uint PUBLICFUNC PLYV_SetMode(PLYV_PRec rec, uint mode)
{
    uint r;

    if (rec == NULL)
        return PLYV_POLL;

    r = (PollRec != NULL)? PLYV_TIMER: PLYV_POLL;  // Previous state.

    if (mode == PLYV_TIMER) {
        if (PollRec == NULL) {  // Init timer.
            PollRec = rec;
            InitHandler();
        }
    } else {
        if (PollRec != NULL)    // Uninit timer.
            DoneHandler();
        PollRec = NULL;
    }
    
    return r;       // Return previous state.
}


PRIVATE void PUBLICFUNC PLYV_MUS_Poll(PLYV_PRec rec)
{
    GSND_PData raw;
    GSND_TData draw;
    int        i;

    if (rec == NULL)
        return;

    rec->MusTickCount++;      // Increment counter.
        
    GSND_GenPlay(&rec->MusParams, &rec->MusParamsChg);

    // If the tick was processed by the device,
    // we can get the info for the next tick.

    if (rec->SongPlaying && rec->Player != NULL_handle) {

        // Advance the partiture interpreter.

        PLAY_DoPartiture(rec->Player);


        // Pass through the WatchProc.

        raw = rec->PlayInfo->RawData;   // Channel data pointer.
        if (rec->WatchProc != NULL)
            rec->WatchProc(rec, raw);


        // Send sound to device.

        for (i = 0; i < rec->PlayInfo->Song->NumChannels && i < rec->MusChannels; i++) {

            if (raw->Instrument != NULL && !rec->ChannelMute[i]) {

                if (rec->ChanWatchPreProc != NULL)
                    rec->ChanWatchPreProc(rec, i, raw);

                GSND_SndPlay(raw, &rec->MusInfo->SoundChg[i]);

                draw = *raw;

                if (draw.Instrument != NULL)
                    SINS_Play(draw.Instrument, &draw);
                GSND_SndGlob(&draw, &rec->MusParams);

                if (rec->ChanWatchPostProc != NULL)
                    rec->ChanWatchPostProc(rec, i, &draw);

                SDEV_DoRawChannel(rec->MusicDev, i, &draw);
                raw->Trigger = FALSE;
            } else {
                memset(&draw, 0, sizeof(draw));
                draw.Trigger = TRUE;
                if (rec->ChanWatchPreProc != NULL)
                    rec->ChanWatchPreProc(rec, i, raw);
                if (rec->ChanWatchPostProc != NULL)
                    rec->ChanWatchPostProc(rec, i, &draw);
                SDEV_DoRawChannel(rec->MusicDev, i, &draw);
            }
            raw++;
        }
    } else {

        // Send silence to device.

        memset(&draw, 0, sizeof(draw));
        draw.Trigger = TRUE;
        
        for (i = 0; i < rec->MusChannels; i++)
            SDEV_DoRawChannel(rec->MusicDev, i, &draw);
    }
}


// --------------------------
// Polling functions. This is the function that must be called to
// keep the sound running. In fact, if it's not called often enough,
// the sound does some funny things (try it!).

uint PUBLICFUNC PLYV_Poll(PLYV_PRec rec, int TimeOut)
{
    uint   j;            // Counters.
    uint   margin;
    static PollSema = 0; // Semaphore to avoid reentrancy.


    if (rec == NULL)
        return 0;

    if (PollSema != 0)      // Avoid reentry to this function.
        return 0;
    PollSema++;

    j = 0;

    margin = SDEV_GetSetMixMargin(rec->MusicDev, 0);
    SDEV_GetSetMixMargin(rec->MusicDev, margin);   // The first time, we
                                                   // use the default margin.
    while (j < TimeOut && SDEV_DoMix(rec->MusicDev)) {  // Process one tick
        j++;
        SDEV_GetSetMixMargin(rec->MusicDev, margin+1);  // The rest of the times,
                                                        // we use a bigger margin.
        PLYV_MUS_Poll(rec);
    }
    SDEV_GetSetMixMargin(rec->MusicDev, margin);

    PollSema--;         // Reset semaphore.
    return j;
}


void PUBLICFUNC PLYV_FillDevice(PLYV_PRec rec)
{
    uint   full = 0;
    uint32 time = SYSTEMTICK;

    if (rec == NULL || rec->MusInfo == NULL)
        return;

    while (full <= rec->MusInfo->DumpBufferLength && SYSTEMTICK-time < 4) {
        full += PLYV_Poll(rec, 20);
    }
}




// ********************************************************
// --------------------------------------------------------
// Music handling public functions.

// --------------------------
// Load a music file. Returns TRUE if successful.

bool PUBLICFUNC PLYV_MUS_Load(PLYV_PRec rec, LPconststr fname)
{
    if (rec == NULL)
        return FALSE;


    // If a song was previously loaded, unload it.

    if (rec->Song != NULL)
        PLYV_MUS_Unload(rec);


    // Load the song file.

    rec->Song     = SONG_Load(fname, 0xFFFF|rec->SoundTypes);
    rec->SongInfo = SONG_GetInfo(rec->Song);
    rec->Player   = NULL_handle;

    return rec->Song != NULL;
}


// --------------------------
// Unloads the previously loaded music.

void PUBLICFUNC PLYV_MUS_Unload(PLYV_PRec rec)
{
    if (rec == NULL)
        return;

    PLYV_MUS_Stop(rec);     // Stop in case it was running.

    if (rec->Song != NULL) {
        SONG_Unload(rec->Song); // Unload it.
        rec->Song = NULL;
    }
}


// --------------------------
// Start playing the music. 'id' contains the values
// desired for the music.

void PUBLICFUNC PLYV_MUS_Start(PLYV_PRec rec, PLAY_PInitData id)
{
    if (rec == NULL || rec->Song == NULL || rec->Player != NULL
     || rec->SongPlaying || id == NULL)
        return;


    // Init with the tick rate calculated by the device.

    id->RealTimerVal = rec->MusInfo->RealTickRate;


    // Init the player.

    SONG_BindToDevice(rec->Song, rec->MusicDev);
    rec->Player   = PLAY_InitSong(rec->Song, id);
    rec->PlayInfo = PLAY_GetInfo(rec->Player);
    rec->SongPlaying = TRUE;
}


// --------------------------
// Stop playing the music. This totally stops it. If you want
// to pause the music, you should set 'rec->SongPlaying = FALSE'

void PUBLICFUNC PLYV_MUS_Stop(PLYV_PRec rec)
{
    if (rec == NULL)
        return;

    if (rec->Player == NULL_handle || rec->Song == NULL)
        return;
         
    rec->SongPlaying = FALSE;
    PLAY_DoneSong(rec->Player);
    rec->Player = NULL_handle;
    SONG_UnbindToDevice(rec->Song, rec->MusicDev);
}


// --------------------------
// Change the volume of the music.

void PUBLICFUNC PLYV_MUS_ChangeVolume(PLYV_PRec rec, uint vol)
{
    if (rec == NULL)
        return;

    rec->MusicVolume = vol;
    rec->MusParamsChg.Flags |= GSND_GC_TOVOLUME;
    if (rec->MusFade == PLYV_FADEIN)
        rec->MusParamsChg.Volume = vol;
}


// --------------------------
// Change the fading of the music.

void PUBLICFUNC PLYV_MUS_SetFading(PLYV_PRec rec, uint in, uint time)
{
    if (rec == NULL)
        return;                        

    if (in == PLYV_FADEIN) {
        rec->MusParamsChg.Volume  = rec->MusicVolume;
        rec->MusParamsChg.VolTime = time;
        rec->MusParamsChg.Flags  |= GSND_GC_TOVOLUME;
        rec->MusFade = PLYV_FADEIN;
    } else {
        rec->MusParamsChg.Volume  = 0;
        rec->MusParamsChg.VolTime = time;
        rec->MusParamsChg.Flags  |= GSND_GC_TOVOLUME;
        rec->MusFade = PLYV_FADEOUT;
    }
}


// --------------------------
// Jump to another relative sequence position.

PUBLIC void PUBLICFUNC PLYV_MUS_JumpSeqRel(PLYV_PRec rec, sint offs)
{
    if (rec->PlayInfo->NextSeq + offs < rec->SongInfo->SequenceLength) {
        rec->PlayInfo->NextNote = 1;
        rec->PlayInfo->NextSeq += offs;
    }
}

#endif 
