//
// Project UnReality: The First Nintendo 64 Emulator
// Copyright (C) 1997-1998, Cold Fusion Entertainment
// All rights reserved
//
// This source code is unpublished propietary information of Cold Fusion Entertainment.  No portion of this software
// may be disclosed to any third party or copied or duplicated in any form, in whole or in part, without the prior
// written consent of Cold Fusion Entertainment.
// 

//
// $Log: AUDIO.CPP $
// Revision 1.7  1998/05/23 15:34:42  BREAKPOINT
// Implemented new FIFO buffering for audio registers rather than using semaphores.
// Revision 1.6  1998/05/14 01:23:16  BREAKPOINT
// Fixed bug where an event was being added to the heap without stopping the R4300i, potentially hanging all interrupts.
// Revision 1.5  1998/03/31 19:41:01  BREAKPOINT
// Removed warnings..
// Revision 1.4  1998/03/22 17:50:08  BREAKPOINT
// Final integration with DeltaPlayer... plays everything including static buffers for audio emulation.
// Revision 1.3  1998/03/10 22:02:12  BREAKPOINT
// Second pass with DeltaPlayer in Project UnReality... now working quite
// well, just need streaming buffer playback and static buffers for emulation.
// Revision 1.2  1998/03/10 01:12:45  BREAKPOINT
// First pass at adding DeltaPlayer to Project UnReality...
// Revision 1.1  1998/03/08 19:07:30  unknown
// Initial revision
//

static char *rcsid = "$Id: AUDIO.CPP 1.7 1998/05/23 15:34:42 BREAKPOINT Exp $";

//---------------------------------------------------------------------------------------------------------------------

#include <stdio.h>
#include <string.h>
#include <dsound.h>
#include <types.h>
#include "resource.h"
#include "mmio.h"
#include "emu.h"
#include "unreal.h"
#include "audio.h"
#include "heap.h"
#include "DeltaPlayer.h"

bool Audio_Disable = FALSE;             // Flag which states whether audio is disabled or not (both emulated and DeltaPlayer)
bool Audio_N64, Audio_DeltaPlayer;      // Flags for both components

Device *audio_dev = NULL;
Loader *audio_loader = NULL;
Replayer *audio_replayer = NULL;
StreamBuf *audio_stream = NULL;
StaticBuf *audio_emubuf = NULL;
StereoStreamBuf *audio_stereo_stream = NULL;
SongStruct audio_song;
ulong audio_bitrate, audio_samplerate;

void Audio_SetMix(int val)
{
    cfg.SoundMixN64 = val;
    Save_Config();

    CheckMenuItem(MainhMenu, ID_DP_MIX_N64,  MF_UNCHECKED);
    CheckMenuItem(MainhMenu, ID_DP_MIX_DP,   MF_UNCHECKED);
    CheckMenuItem(MainhMenu, ID_DP_MIX_BOTH, MF_UNCHECKED);

    switch (val)
    {
        case 0:
            Audio_N64 = TRUE;
            Audio_DeltaPlayer = FALSE; 
            CheckMenuItem(MainhMenu, ID_DP_MIX_N64, MF_CHECKED);
            break;

        case 1:
            Audio_N64 = FALSE;
            Audio_DeltaPlayer = TRUE;
            CheckMenuItem(MainhMenu, ID_DP_MIX_DP, MF_CHECKED);
            break;

        case 2:
            Audio_N64 = TRUE;
            Audio_DeltaPlayer = TRUE;
            CheckMenuItem(MainhMenu, ID_DP_MIX_BOTH, MF_CHECKED);
            break;
    }

    if (Audio_N64 == FALSE)
        audio_emubuf->Stop();

    if (audio_replayer != NULL)
    {
        if (Audio_DeltaPlayer == FALSE)
        {
            Audio_DeltaPlayer = TRUE;
            Audio_StopFile();
            
            if (audio_replayer)
            {
                audio_replayer->Unpause();
                audio_replayer->Stop();
                audio_loader->UnloadSong();

                delete audio_replayer;
                audio_replayer = NULL;
            }

            if (audio_loader)
            {
                delete audio_loader;
                audio_loader = NULL;
            }

            Audio_DeltaPlayer = FALSE;

            EnableMenuItem(MainhMenu, ID_DP_OPEN,  MF_GRAYED);
            EnableMenuItem(MainhMenu, ID_DP_PLAY,  MF_GRAYED);
            EnableMenuItem(MainhMenu, ID_DP_PAUSE, MF_GRAYED);
            EnableMenuItem(MainhMenu, ID_DP_STOP,  MF_GRAYED);
        }
        else
        {
            EnableMenuItem(MainhMenu, ID_DP_OPEN,  MF_ENABLED);
            EnableMenuItem(MainhMenu, ID_DP_PLAY,  MF_GRAYED);
            EnableMenuItem(MainhMenu, ID_DP_PAUSE, MF_ENABLED);
            EnableMenuItem(MainhMenu, ID_DP_STOP,  MF_ENABLED);
        }
    }
    else
    {
        if ((audio_stream != NULL) || (audio_stereo_stream != NULL))
        {
            if (Audio_DeltaPlayer == FALSE)
            {
                if (audio_stream != NULL)
                {
                    audio_stream->Unpause();
                    audio_stream->Stop();
                    audio_loader->FreeStream();
                    audio_stream = NULL;
                }

                if (audio_stereo_stream != NULL)
                {
                    audio_stereo_stream->Unpause();
                    audio_stereo_stream->Stop();
                    audio_loader->FreeStream();
                    audio_stereo_stream = NULL;
                }

                EnableMenuItem(MainhMenu, ID_DP_OPEN,  MF_GRAYED);
                EnableMenuItem(MainhMenu, ID_DP_PLAY,  MF_GRAYED);
                EnableMenuItem(MainhMenu, ID_DP_PAUSE, MF_GRAYED);
                EnableMenuItem(MainhMenu, ID_DP_STOP,  MF_GRAYED);
            }
            else
            {
                EnableMenuItem(MainhMenu, ID_DP_OPEN,  MF_ENABLED);
                EnableMenuItem(MainhMenu, ID_DP_PLAY,  MF_GRAYED);
                EnableMenuItem(MainhMenu, ID_DP_PAUSE, MF_ENABLED);
                EnableMenuItem(MainhMenu, ID_DP_STOP,  MF_ENABLED);
            }
        }
        else
        {
            // No replayer and no stream, so disable everything except open.
            EnableMenuItem(MainhMenu, ID_DP_OPEN,  MF_ENABLED);
            EnableMenuItem(MainhMenu, ID_DP_PLAY,  MF_GRAYED);
            EnableMenuItem(MainhMenu, ID_DP_PAUSE, MF_GRAYED);
            EnableMenuItem(MainhMenu, ID_DP_STOP,  MF_GRAYED);
        }
    }
}

void Audio_Callback(DWORD param)
{
	bool playflag, intrflag;

	// Turn off AI busy flag.
	aireg.status &= ~AI_STATUS_DMA_BUSY;

	// Flag to check for a new buffer and interrupt.
	playflag = FALSE;
	intrflag = FALSE;

	// Handle audio state...
	if (aireg_dram_state)
	{
		aireg_dram_state--;
		if (aireg_dram_state)
		{
			aireg.dram_addr = aireg_dram_hold;
			playflag = TRUE;
		}

		intrflag = TRUE;
	}

	if (aireg_len_state)
	{
		aireg_len_state--;
		if (aireg_len_state)
			aireg.len = aireg_len_hold;
		else
			playflag = FALSE;
	}
	else
	{
		intrflag = FALSE;
	}

	// Check to see if there is another block to process...
	if (playflag && !Audio_Disable)
	{
        // Fire off the new buffer.
        Audio_Play((sshort *)FindMemoryPtr(aireg.dram_addr), aireg.len);
	}

	if (intrflag)
	{
		// FIFO has room for more data now, so clear FIFO flag, zap aireg.len, and signal an AI interrupt.
		aireg.status &= ~AI_STATUS_FIFO_FULL;
		aireg.len = 0;

		if (!(cfg.DisableAudio || Audio_Disable || !Audio_N64))
		{
			if (running)
			{
				// Stop the R4300i so we can add an event to the heap...
				running = FALSE;
				WaitForSingleObject(hDebugEvent, INFINITE);
			}
		}

		// Generate the AI interrupt.
		EventHeapInsert(c0reg.Count, AudioInterfaceInterrupt);

		if (!(cfg.DisableAudio || Audio_Disable || !Audio_N64))
		{
			// Fire the R4300i back up.
			running = TRUE;
			SetEvent(hRunEvent);
		}
	}
}

bool Audio_Init(void)
{
    ulong srate, pri;

    audio_bitrate = 16;
    if (cfg.DisableAudio) return FALSE;

    if (cfg.ForceSoftwareMix)
        audio_dev = new Device(DEVICE_WAVEOUT, DEVMODE_PLAYBACK, MainhWnd, cfg.SoundDev);
    else
        audio_dev = new Device(DEVMODE_PLAYBACK, MainhWnd, cfg.SoundDev);

    if (cfg.SoundHighQ)  audio_dev->UseHighQMixer(TRUE);
    if (cfg.SoundInterp) audio_dev->UseInterpolation(TRUE);
    if (cfg.ThreadMix)   audio_dev->UseThread(TRUE);

    switch (cfg.SoundMixRate)
    {
        case 11: srate = 11025; break;
        case 22: srate = 22050; break;
        case 44: srate = 44100; break;
        case 48: srate = 48110; break;
    }

    if (audio_dev->Init(cfg.SoundBitDepth, srate, cfg.SoundChannels) == FALSE)
        return FALSE;

    switch (cfg.SoundPriority)
    {
        case 0: pri = PRIORITY_NORMAL;   break;
        case 1: pri = PRIORITY_HIGH;     break;
        case 2: pri = PRIORITY_REALTIME; break;
    }

    // Must call this after Init()...
    audio_dev->SetPriority(pri);

    // Create our static buffer for audio emulation.
    audio_emubuf = new StaticBuf(audio_dev, Audio_Callback);
    audio_emubuf->SetVolume(255);
    audio_emubuf->SetFrequency(44010);
    audio_emubuf->SetPosition(-127, +127);

    // Setup mixer.
    Audio_SetMix(cfg.SoundMixN64);
    return TRUE;
}

void Audio_Deinit(void)
{
    Audio_Stop();

    if (audio_stream)
    { 
        audio_stream->Unpause();
        audio_stream->Stop();
        audio_loader->FreeStream();
        audio_stream = NULL;
    }

    if (audio_stereo_stream)
    { 
        audio_stereo_stream->Unpause();
        audio_stereo_stream->Stop();
        audio_loader->FreeStream();
        audio_stereo_stream = NULL;
    }

    if (audio_emubuf)   { delete audio_emubuf;   audio_emubuf   = NULL; }
    if (audio_replayer) { delete audio_replayer; audio_replayer = NULL; }
    if (audio_loader)   { delete audio_loader;   audio_loader   = NULL; }
    if (audio_dev)      { delete audio_dev;      audio_dev      = NULL; }
}

void Audio_Reset(void)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_N64) return;

    if (audio_emubuf)
    {
        if (audio_emubuf->IsPlaying())
            audio_emubuf->Stop();
    }
}

void Audio_Play(sshort *ptr, ulong len)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_N64)
    {
        // Audio is disabled, so we have to fake an interrupt...
        Audio_Callback(0);
        return;
    }

    audio_emubuf->Free();
    audio_emubuf->LoadMEM((uchar)audio_bitrate, STEREO, ptr, len);
    audio_emubuf->SetFrequency(audio_samplerate);
    audio_emubuf->Play();
}

void Audio_Stop(void)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_N64) return;
    audio_emubuf->Stop();
}

void Audio_Unpause(void)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_N64) return;
    // Not implemented yet...
}

void Audio_Pause(void)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_N64) return;
    // Not implemented yet...
}

void Audio_SampleRate(ulong rate)
{
    audio_samplerate = rate;
}

void Audio_BitRate(ulong bits)
{
    audio_bitrate = bits;
}

//----------------------------------------------------------------------------------------------------------
//
//                                      DeltaPlayer Audio Interface
//
//----------------------------------------------------------------------------------------------------------

static int aud_count = 0;

bool Audio_DetectTry(ushort devnum, uchar bits, ulong rate, uchar chan)
{
    bool flag = TRUE;

    audio_dev = new Device(DEVMODE_PLAYBACK, MainhWnd, devnum);
    if (audio_dev->Init(bits, rate, chan) == FALSE) flag = FALSE;
    delete audio_dev;
    audio_dev = NULL;

    return flag;
}

void Audio_ListCallback(uint drv, uchar *name)
{
    aud_count++;
}

BOOL APIENTRY Audio_DetectProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_INITDIALOG:
            return TRUE;
    }

    return FALSE;
}

void Audio_Autodetect(void)
{
    bool flag;
    int t, x;
    FARPROC detectproc;
    HWND detectdlg;

    // Initialize configuration to default values.
    cfg.SoundDev      = 0;
    cfg.SoundBitDepth = 16;
    cfg.SoundMixRate  = 44;
    cfg.SoundChannels = 2;

    // Pop open the detection dialog box.
    detectproc = MakeProcInstance((FARPROC)Audio_DetectProc, MainhInst);
    detectdlg  = CreateDialog(MainhInst, MAKEINTRESOURCE(IDD_SOUND_DETECT), MainhWnd, detectproc);

    // Get a count of audio devices.
    aud_count = 0;
    audio_dev = new Device(DEVMODE_PLAYBACK, MainhWnd, 0);
    audio_dev->ListDrivers(Audio_ListCallback);
    delete audio_dev;
    audio_dev = NULL;

    // Attempt to initialize all buffer types with each device.  Note that 'StandardBufferTypes' is valid now because a
    // device was initialized just above which filled out the structure.

    flag = FALSE;
    for (t = 1; t < aud_count; t++)
    {
        for (x = 0; x < 11; x++)
        {
            if (Audio_DetectTry(t, StandardBufferTypes[x].bits, StandardBufferTypes[x].sample_rate,
                StandardBufferTypes[x].mode) == TRUE)
            {
                cfg.SoundDev      = t;
                cfg.SoundBitDepth = StandardBufferTypes[x].bits;
                cfg.SoundChannels = StandardBufferTypes[x].mode;

                switch (StandardBufferTypes[x].sample_rate)
                {
                    case 11025: cfg.SoundMixRate = 11; break;
                    case 22050: cfg.SoundMixRate = 22; break;
                    case 44100: cfg.SoundMixRate = 44; break;
                }

                x = 11;
                t = aud_count;
                flag = TRUE;
            }
        }
    }

    // If we didn't find a good device, poll detection on the Wave Mapper.
    if (!flag)
    {
        for (x = 0; x < 11; x++)
        {
            if (Audio_DetectTry(0, StandardBufferTypes[x].bits, StandardBufferTypes[x].sample_rate,
                StandardBufferTypes[x].mode) == TRUE)
            {
                cfg.SoundDev      = 0;
                cfg.SoundBitDepth = StandardBufferTypes[x].bits;
                cfg.SoundChannels = StandardBufferTypes[x].mode;

                switch (StandardBufferTypes[x].sample_rate)
                {
                    case 11025: cfg.SoundMixRate = 11; break;
                    case 22050: cfg.SoundMixRate = 22; break;
                    case 44100: cfg.SoundMixRate = 44; break;
                }

                x = 11;
                flag = TRUE;
            }
        }
    }

    // Remove the dialog box.
    DestroyWindow(detectdlg);
    FreeProcInstance(detectproc);
}

void Audio_HandleLoadError(int err)
{
    switch (err)
    {
        case DP_ERR_NOT_FOUND:
            MessageBox(MainhWnd, "File not found!", "DeltaPlayer Error", MB_OK);
            break;

        case DP_ERR_UNKNOWN_TYPE:
            MessageBox(MainhWnd, "Unknown file type!", "DeltaPlayer Error", MB_OK);
            break;

        case DP_ERR_NO_MEM:
            MessageBox(MainhWnd, "Insufficient memory!", "DeltaPlayer Error", MB_OK);
            break;
    }

    delete audio_loader;
    audio_loader = NULL;
}

void Audio_LoadFile(void)
{
    OPENFILENAME ofn;
    BOOL res;
	char *sptr, *dptr;
    char fname[256];
    int err;

	if (cfg.DisableAudio || Audio_Disable || !Audio_DeltaPlayer) return;

    // Load the new song.
    fname[0] = 0;
    memset(&ofn, 0, sizeof(ofn));

    ofn.lStructSize		= sizeof(ofn);
    ofn.hwndOwner		= MainhWnd;
    ofn.lpstrFile		= fname;
    ofn.nMaxFile		= sizeof(fname);
	ofn.lpstrInitialDir = (cfg.LastSoundPath[0] == 0) ? NULL : cfg.LastSoundPath;
    ofn.lpstrFilter		= TEXT("Audio Files *.mod,*.s3m,*.xm,*.mp1,*.mp2,*.mp3,*.wav\0*.mod;*.s3m;*.xm;*.mp1;*.mp2;*.mp3;*.wav\0All Files *.*\0*.*\0\0");
    ofn.nFilterIndex	= 1;
    ofn.Flags			= OFN_FILEMUSTEXIST;

    res = GetOpenFileName(&ofn);
    if (!res) return;

	sptr = strchr(fname, '\\');
	if (sptr != NULL)
	{
		sptr++;
		strcpy(cfg.LastSoundPath, fname);

		sptr = cfg.LastSoundPath;
		do
		{
			dptr = strchr(sptr, '\\');
			if (dptr) sptr = dptr + 1;
		} while (dptr != NULL);

		*sptr = 0;
		Save_Config();				// Save new audio path out to registry...
	}

    // If we already have a song loaded, stop and unload it first.
    if (audio_loader)
    {
        Audio_StopFile();

        if (audio_replayer)
        {
            audio_loader->UnloadSong();
            delete audio_replayer;
            audio_replayer = NULL;
        }

        if (audio_stream)
        {
            audio_loader->FreeStream();
            audio_stream = NULL;
        }

        if (audio_stereo_stream)
        {
            audio_loader->FreeStream();
            audio_stereo_stream = NULL;
        }

        if (audio_loader)
        {
            delete audio_loader;
            audio_loader = NULL;
        }
    }

    audio_loader = new Loader;
    audio_loader->UseDevice(audio_dev);
//    audio_loader->SetCallback(percent_printer);

    if (audio_loader->IsNotModule(fname))
    {
        audio_loader->LoadStream(fname);
        err = audio_loader->GetError();
        if (err != 0)
        {
            Audio_HandleLoadError(err);
            return;
        }

        if (audio_loader->GetStreamMode() == STREAM_MONO)
        {
            audio_stream = audio_loader->GetStreamBufPtr();
            audio_stream->SetVolume(255);
            audio_stream->SetPosition(0);
        }
        else
        {
            audio_stereo_stream = audio_loader->GetStereoStreamBufPtr();
            audio_stereo_stream->SetVolume(255);
            audio_stereo_stream->SetPosition(-127, 127);
        }
    }
    else
    {
        audio_loader->UseSong(&audio_song);
        audio_loader->LoadSong(fname);
        err = audio_loader->GetError();
        if (err != 0)
        {
            Audio_HandleLoadError(err);
            return;
        }

        if (cfg.SoundEmuCubic)   audio_song.EmulationFlags |= EMULATION_CUBIC;
        if (cfg.SoundEmuXTCPlay) audio_song.EmulationFlags |= EMULATION_XTCPLAY;

        // Kick up the replayer.
        audio_replayer = new Replayer(REPLAYMODE_TRACKED);
        audio_replayer->UseDevice(audio_dev);
        audio_replayer->UseSong(&audio_song);
    }

    Audio_PlayFile();
}

void Audio_PlayFile(void)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_DeltaPlayer || ((audio_replayer == NULL) && (audio_stream == NULL) && (audio_stereo_stream == NULL))) return;

    if (audio_replayer != NULL)
        audio_replayer->Play();
    else if (audio_stream != NULL)
        audio_stream->Play();
    else if (audio_stereo_stream != NULL)
        audio_stereo_stream->Play();

    // Enable the DeltaPlayer menus.
    EnableMenuItem(MainhMenu, ID_DP_PLAY,  MF_GRAYED);
    EnableMenuItem(MainhMenu, ID_DP_PAUSE, MF_ENABLED);
    EnableMenuItem(MainhMenu, ID_DP_STOP,  MF_ENABLED);
}

void Audio_UnpauseFile(void)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_DeltaPlayer || ((audio_replayer == NULL) && (audio_stream == NULL) && (audio_stereo_stream == NULL))) return;

    if (audio_replayer != NULL)
        audio_replayer->Unpause();
    else if (audio_stream != NULL)
        audio_stream->Unpause();
    else if (audio_stereo_stream != NULL)
        audio_stereo_stream->Unpause();
}

void Audio_PauseFile(void)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_DeltaPlayer || ((audio_replayer == NULL) && (audio_stream == NULL) && (audio_stereo_stream == NULL))) return;

    if (audio_replayer != NULL)
        audio_replayer->Pause();
    else if (audio_stream != NULL)
        audio_stream->Pause();
    else if (audio_stereo_stream != NULL)
        audio_stereo_stream->Pause();
}

void Audio_StopFile(void)
{
	if (cfg.DisableAudio || Audio_Disable || !Audio_DeltaPlayer || ((audio_replayer == NULL) && (audio_stream == NULL) && (audio_stereo_stream == NULL))) return;

    // Unpause too, just in case.
    if (audio_replayer != NULL)
    {
        audio_replayer->Unpause();
        audio_replayer->Stop();
    } else if (audio_stream != NULL) {
        audio_stream->Unpause();
        audio_stream->Stop();
    } else if (audio_stereo_stream != NULL) {
        audio_stereo_stream->Unpause();
        audio_stereo_stream->Stop();
    }

    // Disable DeltaPlayer pause and stop and enable play.
    EnableMenuItem(MainhMenu, ID_DP_PLAY,  MF_ENABLED);
    CheckMenuItem(MainhMenu,  ID_DP_PAUSE, MF_UNCHECKED);
    EnableMenuItem(MainhMenu, ID_DP_PAUSE, MF_GRAYED);
    EnableMenuItem(MainhMenu, ID_DP_STOP,  MF_GRAYED);
}
