#include <windows.h>
#include <zlib.h>

#include "in_ym.h"

int isbitset(void *buffer, int bit) { /* returns nonzero if bit is set, else zero */
    return (*(unsigned int *)buffer) & (0x1 << bit);
}

static void setbit(void *buffer, int bit) {
    (*(unsigned int *)buffer) |= 0x1 << bit;
    return;
}

static void clrbit(void *buffer, int bit) {
    if(isbitset(buffer, bit))
        (*(unsigned int *)buffer) ^= 0x1 << bit;
    return;
}

#ifdef __LCC__
BOOL WINAPI LibMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    return TRUE;
}
#endif

/*
** Shows a message box when
** a system function fails.
*/
static void ShowErrorMessage(int WinError, char *BoxTitle) {
    LPVOID lpMsgBuf;

    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, WinError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
    MessageBox(mod.hMainWindow, lpMsgBuf, BoxTitle, MB_ICONSTOP);
    LocalFree(lpMsgBuf);
    return;
}

/*
** This is usually used to detect
** internet URLs.
*/
int isourfile(char *filename) {
    return 0;
}

int AddGYMTag(const char *filename) {
    HANDLE file;
    int filesize;
    unsigned long bytesread;
    unsigned char *data;

    file = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(file == INVALID_HANDLE_VALUE) {
        ShowErrorMessage(GetLastError(), "Error Opening File");
        return 0;
    }
    filesize = GetFileSize(file, NULL);
    data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, filesize + 428);
    if(!data) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        CloseHandle(file);
        return 0;
    }
    ReadFile(file, &data[428], filesize, &bytesread, NULL);
	memset(data, 0, 428);
    *(int *)&data[0] = 0x584d5947;
    lstrcpyn((char *)&data[4], "Unknown Song", 32);
    lstrcpyn((char *)&data[36], "Unknown Game", 32);
    lstrcpyn((char *)&data[68], "Unknown Publisher", 32);
    lstrcpyn((char *)&data[100], "Unknown Emulator", 32);
    lstrcpyn((char *)&data[132], "Unknown Person", 32);
    lstrcpyn((char *)&data[164], "Header added by YMAMP", 256);
    *(int *)&data[420] = 0;
    *(int *)&data[424] = 0;
    SetFilePointer(file, 0, NULL, FILE_BEGIN);
    WriteFile(file, data, filesize + 428, &bytesread, NULL);
    SetEndOfFile(file);
    SetFilePointer(file, 0, NULL, FILE_BEGIN);
    ReadFile(file, &gymtag, 428, &bytesread, NULL);
    CloseHandle(file);
    HeapFree(GetProcessHeap(), 0, data);
    return 1;
}

/*
** FillDialogInfo initializes
** the config dialog controls
** with data.
*/
static void FillDialogInfo(HANDLE dlgWnd) {
    char string[5];

    switch(samplerate) {
        case(22050):
	    SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_22050, BM_SETCHECK, BST_CHECKED, 0);
            break;
        case(24000):
	    SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_24000, BM_SETCHECK, BST_CHECKED, 0);
	    break;
        case(48000):
	    SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_48000, BM_SETCHECK, BST_CHECKED, 0);
	    break;
        default:
	    SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_44100, BM_SETCHECK, BST_CHECKED, 0);
	    break;
    }

    switch(prio) {
        case(THREAD_PRIORITY_LOWEST):
	    SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_LOWEST, BM_SETCHECK, BST_CHECKED, 0);
	    break;
        case(THREAD_PRIORITY_BELOW_NORMAL):
	    SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_LOWER, BM_SETCHECK, BST_CHECKED, 0);
	    break;
        case(THREAD_PRIORITY_ABOVE_NORMAL):
	    SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_HIGHER, BM_SETCHECK, BST_CHECKED, 0);
	    break;
        case(THREAD_PRIORITY_HIGHEST):
            SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_HIGHEST, BM_SETCHECK, BST_CHECKED, 0);
            break;
        default:
            SendDlgItemMessage(dlgWnd, DLG_CONFIG_RDO_NORMAL, BM_SETCHECK, BST_CHECKED, 0);
            break;
    }

    SendDlgItemMessage(dlgWnd, DLG_CONFIG_HSCR_2151, SBM_SETRANGE, 50, 150);
    SendDlgItemMessage(dlgWnd, DLG_CONFIG_HSCR_2151, SBM_SETPOS, ym2151_clock, TRUE);
    wsprintf(string, "%i%%", ym2151_clock);
    SendDlgItemMessage(dlgWnd, DLG_CONFIG_TXT_2151, WM_SETTEXT, 0, (LPARAM)string);
    SendDlgItemMessage(dlgWnd, DLG_CONFIG_HSCR_2612, SBM_SETRANGE, 50, 150);
    SendDlgItemMessage(dlgWnd, DLG_CONFIG_HSCR_2612, SBM_SETPOS, ym2612_clock, TRUE);
    wsprintf(string, "%i%%", ym2612_clock);
    SendDlgItemMessage(dlgWnd, DLG_CONFIG_TXT_2612, WM_SETTEXT, 0, (LPARAM)string);
    SendDlgItemMessage(dlgWnd, DLG_CONFIG_HSCR_76496, SBM_SETRANGE, 50, 150);
    SendDlgItemMessage(dlgWnd, DLG_CONFIG_HSCR_76496, SBM_SETPOS, sn76496_clock, TRUE);
    wsprintf(string, "%i%%", sn76496_clock);
    SendDlgItemMessage(dlgWnd, DLG_CONFIG_TXT_76496, WM_SETTEXT, 0, (LPARAM)string);

    if(isbitset(&channels, 0))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2151_1, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 1))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2151_2, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 2))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2151_3, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 3))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2151_4, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 4))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2151_5, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 5))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2151_6, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 6))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2151_7, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 7))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2151_8, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 8))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2612_1, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 9))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2612_2, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 10))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2612_3, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 11))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2612_4, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 12))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2612_5, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 13))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_2612_6, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 14))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_76496_1, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 15))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_76496_2, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 16))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_76496_3, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
    if(isbitset(&channels, 17))
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_CHK_76496_4, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
     
    return;
}

static void ProcessScrolling(HWND dlgWnd, WPARAM wParam, HWND hSbar) {
    char string[5];
    int scrollpos = GetScrollPos(hSbar, SB_CTL);    

    switch(LOWORD(wParam)) {
        case(SB_THUMBTRACK):
	    case(SB_THUMBPOSITION):
		    scrollpos = HIWORD(wParam);
			SendMessage(hSbar, SBM_SETPOS, (WPARAM)scrollpos, TRUE);
			break;
        case(SB_LINERIGHT):
	    scrollpos = GetScrollPos(hSbar, SB_CTL) + 1;
	    if(scrollpos > 150)
	        scrollpos = 150;
            SendMessage(hSbar, SBM_SETPOS, scrollpos, TRUE);
	    break;
        case(SB_LINELEFT):
	    scrollpos = GetScrollPos(hSbar, SB_CTL) - 1;
	    if(scrollpos < 50)
	        scrollpos = 50;
            SendMessage(hSbar, SBM_SETPOS, scrollpos, TRUE);
	    break;
        case(SB_PAGERIGHT):
            scrollpos = GetScrollPos(hSbar, SB_CTL) + 10;
			if(scrollpos > 150)
			    scrollpos = 150;
			SendMessage(hSbar, SBM_SETPOS, scrollpos, TRUE);
			break;
        case(SB_PAGELEFT):
            scrollpos = GetScrollPos(hSbar, SB_CTL) - 10;
			if(scrollpos < 50)
			    scrollpos = 50;
            SendMessage(hSbar, SBM_SETPOS, scrollpos, TRUE);
			break;
    }
    wsprintf(string, "%i%%", scrollpos);
    if(GetDlgItem(dlgWnd, DLG_CONFIG_HSCR_2151) == hSbar) {
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_TXT_2151, WM_SETTEXT, 0, (LPARAM)string);
        ym2151_clock = scrollpos;
    }
    else if(GetDlgItem(dlgWnd, DLG_CONFIG_HSCR_2612) == hSbar) {
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_TXT_2612, WM_SETTEXT, 0, (LPARAM)string);
        ym2612_clock = scrollpos;
    }
    else {
        SendDlgItemMessage(dlgWnd, DLG_CONFIG_TXT_76496, WM_SETTEXT, 0, (LPARAM)string);
        sn76496_clock = scrollpos;
    }
    return;
}

static void ProcessCheckBoxes(int Lo_wParam) {
    switch(Lo_wParam) {
        case(DLG_CONFIG_CHK_2151_1):
            isbitset(&channels, 0) ? clrbit(&channels, 0) : setbit(&channels, 0);
            break;
        case(DLG_CONFIG_CHK_2151_2):
            isbitset(&channels, 1) ? clrbit(&channels, 1) : setbit(&channels, 1);
            break;
        case(DLG_CONFIG_CHK_2151_3):
            isbitset(&channels, 2) ? clrbit(&channels, 2) : setbit(&channels, 2);
            break;
        case(DLG_CONFIG_CHK_2151_4):
            isbitset(&channels, 3) ? clrbit(&channels, 3) : setbit(&channels, 3);
            break;
        case(DLG_CONFIG_CHK_2151_5):
            isbitset(&channels, 4) ? clrbit(&channels, 4) : setbit(&channels, 4);
            break;
        case(DLG_CONFIG_CHK_2151_6):
            isbitset(&channels, 5) ? clrbit(&channels, 5) : setbit(&channels, 5);
            break;
        case(DLG_CONFIG_CHK_2151_7):
            isbitset(&channels, 6) ? clrbit(&channels, 6) : setbit(&channels, 6);
            break;
        case(DLG_CONFIG_CHK_2151_8):
            isbitset(&channels, 7) ? clrbit(&channels, 7) : setbit(&channels, 7);
            break;
        case(DLG_CONFIG_CHK_2612_1):
            isbitset(&channels, 8) ? clrbit(&channels, 8) : setbit(&channels, 8);
            break;
        case(DLG_CONFIG_CHK_2612_2):
            isbitset(&channels, 9) ? clrbit(&channels, 9) : setbit(&channels, 9);
            break;
        case(DLG_CONFIG_CHK_2612_3):
            isbitset(&channels, 10) ? clrbit(&channels, 10) : setbit(&channels, 10);
            break;
        case(DLG_CONFIG_CHK_2612_4):
            isbitset(&channels, 11) ? clrbit(&channels, 11) : setbit(&channels, 11);
            break;
        case(DLG_CONFIG_CHK_2612_5):
            isbitset(&channels, 12) ? clrbit(&channels, 12) : setbit(&channels, 12);
            break;
        case(DLG_CONFIG_CHK_2612_6):
            isbitset(&channels, 13) ? clrbit(&channels, 13) : setbit(&channels, 13);
            break;
        case(DLG_CONFIG_CHK_76496_1):
            isbitset(&channels, 14) ? clrbit(&channels, 14) : setbit(&channels, 14);
            break;
        case(DLG_CONFIG_CHK_76496_2):
            isbitset(&channels, 15) ? clrbit(&channels, 15) : setbit(&channels, 15);
            break;
        case(DLG_CONFIG_CHK_76496_3):
            isbitset(&channels, 16) ? clrbit(&channels, 16) : setbit(&channels, 16);
            break;
        case(DLG_CONFIG_CHK_76496_4):
            isbitset(&channels, 17) ? clrbit(&channels, 17) : setbit(&channels, 17);
            break;
        case(DLG_CONFIG_RDO_22050):
            samplerate = 22050;
            break;
        case(DLG_CONFIG_RDO_24000):
            samplerate = 24000;
            break;
        case(DLG_CONFIG_RDO_44100):
            samplerate = 44100;
            break;
        case(DLG_CONFIG_RDO_48000):
            samplerate = 48000;
            break;
        case(DLG_CONFIG_RDO_LOWEST):
            prio = THREAD_PRIORITY_LOWEST;
            break;
        case(DLG_CONFIG_RDO_LOWER):
            prio = THREAD_PRIORITY_BELOW_NORMAL;
            break;
        case(DLG_CONFIG_RDO_NORMAL):
            prio = THREAD_PRIORITY_NORMAL;
            break;
        case(DLG_CONFIG_RDO_HIGHER):
            prio = THREAD_PRIORITY_ABOVE_NORMAL;
            break;
        case(DLG_CONFIG_RDO_HIGHEST):
            prio = THREAD_PRIORITY_HIGHEST;
            break;
        }
        return;
}

/*
** This is the config dialog box
** procedure. Note that this function
** is a DialogProc, not an ordinary
** WindowProc.
*/
BOOL CALLBACK ConfigProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    int retval = 0;
    
    switch(uMsg) {
        case(WM_INITDIALOG):
            retval = 1;
            FillDialogInfo(hwndDlg);
            break;
        case(WM_COMMAND):
            if(HIWORD(wParam) == BN_CLICKED) {
                retval = 1;
                ProcessCheckBoxes(LOWORD(wParam));
            }
            break;
        case(WM_HSCROLL):
            retval = 1;
            ProcessScrolling(hwndDlg, wParam, (HWND)lParam);
            break;
        case(WM_CLOSE):
            EndDialog(hwndDlg, 0);
            break;
    }
    return retval;
}

/*
** Gets configuration settings
** from the user.
*/
void config(HWND hwndParent) {
    DialogBox(mod.hDllInstance, MAKEINTRESOURCE(DLG_CONFIG), hwndParent, ConfigProc);
    return;
}

BOOL CALLBACK AboutProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    int retval = 0;

    switch(uMsg) {
        case(WM_INITDIALOG):
            retval = 1;
            SendDlgItemMessage(hWnd, DLG_ABOUT_TXT_DATE, WM_SETTEXT, 0, (LPARAM)__DATE__);
            break;
        case(WM_CLOSE):
            retval = 1;
            EndDialog(hWnd, 0);
            break;
    }
    return retval;
}

/*
** Shows information about
** YMAMP
*/
void about(HWND hwndParent) {
    DialogBox(mod.hDllInstance, MAKEINTRESOURCE(DLG_ABOUT), hwndParent, AboutProc);
    return;
}

/*
** Loads configuration from a
** file on disk at startup.
*/
void init(void) {
    LPTSTR fnoff;
    char cfgname[MAX_PATH];

    GetModuleFileName(mod.hDllInstance, cfgname, MAX_PATH);
    GetFullPathName(cfgname, MAX_PATH, cfgname, &fnoff);
    cfgname[(fnoff - cfgname)] = '\0';
    lstrcat(cfgname, "plugin.ini");
    samplerate = GetPrivateProfileInt("YMAMP", "Sample_Rate", 44100, cfgname);
    if((samplerate != 22050) && (samplerate != 24000) && (samplerate != 44100) && (samplerate != 48000))
        samplerate = 44100;
    ym2151_clock = GetPrivateProfileInt("YMAMP", "YM2151_Clock", 100, cfgname);
    if((ym2151_clock < 50) || (ym2151_clock > 150))
        ym2151_clock = 100;
    ym2612_clock = GetPrivateProfileInt("YMAMP", "YM2612_Clock", 100, cfgname);
    if((ym2612_clock < 50) || (ym2612_clock > 150))
        ym2612_clock = 100;
    sn76496_clock = GetPrivateProfileInt("YMAMP", "SN76496_Clock", 100, cfgname);
    if((sn76496_clock < 50) || (sn76496_clock > 150))
        sn76496_clock = 100;
    prio = GetPrivateProfileInt("YMAMP", "Priority", THREAD_PRIORITY_NORMAL, cfgname);
    if((prio != THREAD_PRIORITY_NORMAL) && (prio != THREAD_PRIORITY_ABOVE_NORMAL) && (prio != THREAD_PRIORITY_HIGHEST) && (prio != THREAD_PRIORITY_BELOW_NORMAL) && (prio != THREAD_PRIORITY_LOWEST))
        prio = THREAD_PRIORITY_NORMAL;
    channels = GetPrivateProfileInt("YMAMP", "Channels", 0x3ffff, cfgname);
    return;
}

/*
** Saves configuration information
** to a file on disk on shutdown
*/
void quit(void) {
    LPTSTR fnoff;
    char cfgname[MAX_PATH], string[11];

    GetModuleFileName(mod.hDllInstance, cfgname, MAX_PATH);
    GetFullPathName(cfgname, MAX_PATH, cfgname, &fnoff);
    cfgname[(fnoff - cfgname)] = '\0';
    lstrcat(cfgname, "plugin.ini");
    wsprintf(string, "%i", ym2151_clock);
    WritePrivateProfileString("YMAMP", "YM2151_Clock", string, cfgname);
    wsprintf(string, "%i", ym2612_clock);
    WritePrivateProfileString("YMAMP", "YM2612_Clock", string, cfgname);
    wsprintf(string, "%i", sn76496_clock);
    WritePrivateProfileString("YMAMP", "SN76496_Clock", string, cfgname);
    wsprintf(string, "%i", prio);
    WritePrivateProfileString("YMAMP", "Priority", string, cfgname);
    wsprintf(string, "%i", samplerate);
    WritePrivateProfileString("YMAMP", "Sample_Rate", string, cfgname);
    wsprintf(string, "%i", channels);
    WritePrivateProfileString("YMAMP", "Channels", string, cfgname);

    return;
}

/*
** Determines the length in
** milliseconds of a CYM file.
*/
static int calc_cym_time_length(unsigned char *data, int size) {
    int loop, num_zeros = 0;

    for(loop = 0; loop < size; loop++) {
        if(!data[loop]) num_zeros++;
        else loop++;
    }
    return (int)((num_zeros / 60.0) * 1000.0);
}

/*
** Determines the length in
** milliseconds of a GYM file.
*/
static int calc_gym_time_length(unsigned char *data, int size) {
    int loop, num_zeros = 0;

    for(loop = 0; loop < size; loop++) {
        switch(data[loop]) {
            case(0x00):
                num_zeros++;
                continue;
            case(0x01):
                loop += 2;
                continue;
            case(0x02):
                loop += 2;
                continue;
            case(0x03):
                loop += 1;
                continue;
        }
    }
    return (int)((num_zeros / 60.0) * 1000.0);
}

/*
** Starts the playback process
** when the user clicks the play
** button.
*/
int play(char *fn) {
    int thread_id, maxlatency, ym_type;
    HANDLE ym_file;
    unsigned char *tmpbuf;
    unsigned long srcsize, destsize;

    lstrcpy(lastfn, fn);
    paused = 0;
    ym_file = CreateFile(lastfn, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(ym_file == INVALID_HANDLE_VALUE) {
        ShowErrorMessage(GetLastError(), "Error Opening File");
        return -1;
    }
    ReadFile(ym_file, &gymtag, 428, (LPDWORD)&file_size, NULL);
    if(gymtag.id == 0x584d5947)
        file_size = GetFileSize(ym_file, NULL) - 428;
    else {
        SetFilePointer(ym_file, 0, NULL, FILE_BEGIN);
        file_size = GetFileSize(ym_file, NULL);
    }
    tmpbuf = (unsigned char *)HeapAlloc(GetProcessHeap(), 0, file_size);
    if(!tmpbuf) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        CloseHandle(ym_file);
        return 1;
    }
    ReadFile(ym_file, tmpbuf, file_size, (LPDWORD)&file_size, NULL);
    CloseHandle(ym_file);
    if(!lstrcmpi(".cym", lastfn + lstrlen(lastfn) - 4)) {
        ym_type = 1;
        ym_data = tmpbuf;
        time_length = calc_cym_time_length(ym_data, file_size);
    }
    else if(!lstrcmpi(".gym", lastfn + lstrlen(lastfn) - 4)) {
        ym_type = 2;
        if(gymtag.id != 0x584d5947 && !AddGYMTag(lastfn)) {
            HeapFree(GetProcessHeap(), 0, tmpbuf);
            return 1;
        }
        if(gymtag.compressed) {
            int zerr;
            destsize = gymtag.compressed;
            srcsize = file_size;
            ym_data = HeapAlloc(GetProcessHeap(), 0, destsize);
            if(!ym_data) {
                ShowErrorMessage(GetLastError(), "Error Allocating Memory");
                HeapFree(GetProcessHeap(), 0, tmpbuf);
                return 1;
            }            
            zerr = uncompress(ym_data, &destsize, tmpbuf, srcsize);
            HeapFree(GetProcessHeap(), 0, tmpbuf);
            if(zerr != Z_OK) {
                HeapFree(GetProcessHeap(), 0, ym_data);
                return 1;
            }   
            file_size = destsize;
            time_length = calc_gym_time_length(ym_data, file_size);            
        }
        else {
            ym_data = tmpbuf;
            time_length = calc_gym_time_length(ym_data, file_size);
        }
    }
    else {
        HeapFree(GetProcessHeap(), 0, tmpbuf);
        return 1;
    }
    maxlatency = mod.outMod->Open(samplerate, 2, 16, 0, 0);
    if(maxlatency < 0)
        return 1;
    mod.SetInfo(0, samplerate / 1000, 2, 0);
    mod.SAVSAInit(samplerate, maxlatency);
    mod.VSASetInfo(2, samplerate);
    mod.outMod->SetVolume(-666);
    killDecodeThread = 0;
    if(thread_handle)
        CloseHandle(thread_handle);
    if(ym_type == 1)
        thread_handle = CreateThread(NULL, 0, PlayCYMThread, (void *)&killDecodeThread, 0, (LPDWORD)&thread_id);
    else
        thread_handle = CreateThread(NULL, 0, PlayGYMThread, (void *)&killDecodeThread, 0, (LPDWORD)&thread_id);
    if(!thread_handle) {
        int err = GetLastError();
        mod.outMod->Close();
        mod.SAVSADeInit();
        ShowErrorMessage(err, "Error Creating PlayThread");
        HeapFree(GetProcessHeap(), 0, tmpbuf);
        return 1;
    }
    SetThreadPriority(thread_handle, prio);
    return 0;
}

/*
** Pauses playback when the
** user clicks the pause button.
*/
void pause(void) {
    paused = 1;
    mod.outMod->Pause(1);
    return;
}

/*
** Resumes playback when the
** user clicks the pause
** button again.
*/
void unpause(void) {
    paused = 0;
    mod.outMod->Pause(0);
    return;
}

/* Returns pause status */
int ispaused(void) {
    return paused;
}

/*
** Stops the playback when the
** user clicks the stop button.
*/
void stop(void) {
    if(thread_handle) {
        killDecodeThread = 1;
        WaitForSingleObject(thread_handle, INFINITE);
        CloseHandle(thread_handle);
    }
    mod.outMod->Close();
    mod.SAVSADeInit();
    return;
}

int getlength(void) {
    return time_length;
}

int getoutputtime(void) {
    return time_pos + (mod.outMod->GetOutputTime() - mod.outMod->GetWrittenTime());
}

void setoutputtime(int time_in_ms) {
    seek_needed = time_in_ms;
    return;
}

void setvolume(int volume) {
    mod.outMod->SetVolume(volume);
    return;
}

void setpan(int pan) {
    mod.outMod->SetPan(pan);
    return;
}

/*
** Returns information about
** the specified file.
*/
void getfileinfo(char *filename, char *title, int *length_in_ms) {
    if(!filename || !*filename)
        filename = lastfn;

    if(title) {
        if((filename == lastfn) && (gymtag.id == 0x584d5947))
            lstrcpyn(title, gymtag.songtitle, 32);
        else if(GetFileTitle(filename, title, MAX_PATH))
            *title = '\0';
    }
        
    if(length_in_ms) {
        HANDLE ym_file_info;
        int ym_info_size;
        unsigned char *ym_data_info;

        if(!lstrcmp(filename, lastfn)) {
            *length_in_ms = time_length;
            return;
        }
        ym_file_info = CreateFile(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if(ym_file_info == INVALID_HANDLE_VALUE) {
            *length_in_ms = -1000;
            return;
        }
        ym_info_size = GetFileSize(ym_file_info, NULL);
        ym_data_info = (unsigned char *)HeapAlloc(GetProcessHeap(), 0, ym_info_size);
        if(!ym_data_info) {
            *length_in_ms = -1000;
            CloseHandle(ym_file_info);
            return;
        }
        ReadFile(ym_file_info, ym_data_info, ym_info_size, (LPDWORD)&ym_info_size, NULL);
        CloseHandle(ym_file_info);
        if(!lstrcmpi(".cym", lastfn + lstrlen(lastfn) - 4))
            *length_in_ms = calc_cym_time_length(ym_data_info, ym_info_size);
        else if(!lstrcmpi(".gym", lastfn + lstrlen(lastfn) - 4)) {
            if(*(int *)&ym_data_info[0] == 0x584d5947)
                *length_in_ms = calc_gym_time_length(&ym_data_info[428], ym_info_size - 428);
            else
                *length_in_ms = calc_gym_time_length(ym_data_info, ym_info_size);
        }
        else
            *length_in_ms = -1000;
        HeapFree(GetProcessHeap(), 0, ym_data_info);
    }
    return;
}

void DoInfoDlgInit(HWND hWnd) {
    char text[257];

    lstrcpyn(text, gymtag.songtitle, 33);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_SONGTITLE, EM_LIMITTEXT, 32, 0);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_SONGTITLE, WM_SETTEXT, 0, (LPARAM)text);
    lstrcpyn(text, gymtag.gametitle, 33);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_GAMETITLE, EM_LIMITTEXT, 32, 0);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_GAMETITLE, WM_SETTEXT, 0, (LPARAM)text);
    lstrcpyn(text, gymtag.gamepub, 33);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_GAMEPUB, EM_LIMITTEXT, 32, 0);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_GAMEPUB, WM_SETTEXT, 0, (LPARAM)text);
    lstrcpyn(text, gymtag.dumpemu, 33);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_DUMPEMU, EM_LIMITTEXT, 32, 0);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_DUMPEMU, WM_SETTEXT, 0, (LPARAM)text);
    lstrcpyn(text, gymtag.dumper, 33);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_DUMPER, EM_LIMITTEXT, 32, 0);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_DUMPER, WM_SETTEXT, 0, (LPARAM)text);
    lstrcpyn(text, gymtag.comments, 257);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_COMMENTS, EM_LIMITTEXT, 256, 0);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_COMMENTS, WM_SETTEXT, 0, (LPARAM)text);
    if(gymtag.compressed)
        SendDlgItemMessage(hWnd, DLG_INFO_BTN_COMPRESS, WM_SETTEXT, 0, (LPARAM)"Decompress");
    else
        SendDlgItemMessage(hWnd, DLG_INFO_BTN_COMPRESS, WM_SETTEXT, 0, (LPARAM)"Compress");
    return;
}

void DoInfoDlgClose(HWND hWnd, const char *filename) {
    char text[257];
    HANDLE hfile;
    DWORD byteswritten;

    memset(text, 0, 257);
    gymtag.id = 0x584d5947;
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_SONGTITLE, WM_GETTEXT, 33, (LPARAM)text);
    memcpy(gymtag.songtitle, text, 32);
    memset(text, 0, 257);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_GAMETITLE, WM_GETTEXT, 33, (LPARAM)text);
    memcpy(gymtag.gametitle, text, 32);
    memset(text, 0, 257);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_GAMEPUB, WM_GETTEXT, 33, (LPARAM)text);
    memcpy(gymtag.gamepub, text, 32);
    memset(text, 0, 257);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_DUMPEMU, WM_GETTEXT, 33, (LPARAM)text);
    memcpy(gymtag.dumpemu, text, 32);
    memset(text, 0, 257);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_DUMPER, WM_GETTEXT, 33, (LPARAM)text);
    memcpy(gymtag.dumper, text, 32);
    memset(text, 0, 257);
    SendDlgItemMessage(hWnd, DLG_INFO_EDIT_COMMENTS, WM_GETTEXT, 257, (LPARAM)text);
    memcpy(gymtag.comments, text, 256);
    hfile = CreateFile(filename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hfile == INVALID_HANDLE_VALUE)
        return;
    WriteFile(hfile, &gymtag, 428, &byteswritten, NULL);
    CloseHandle(hfile);
    return;
}

void DoGYMCompression(HWND hWnd, const char *filename) {
    HANDLE gymfile;
    unsigned long bytesin, bytesout;
    unsigned char *inbuf, *outbuf;

    gymfile = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(!gymfile)
        return;
    bytesin = GetFileSize(gymfile, NULL) - 428;
    inbuf = HeapAlloc(GetProcessHeap(), 0, bytesin);
    if(!inbuf) {
        CloseHandle(gymfile);
        return;
    }
    SetFilePointer(gymfile, 428, NULL, FILE_BEGIN);
    ReadFile(gymfile, inbuf, bytesin, (LPDWORD)&bytesin, NULL);
    if(!gymtag.compressed) {
        bytesout = (int)(bytesin * 1.1);
        outbuf = HeapAlloc(GetProcessHeap(), 0, bytesout);        
        if(compress2(outbuf, &bytesout, inbuf, bytesin, Z_BEST_COMPRESSION) != Z_OK) {
            CloseHandle(gymfile);
            HeapFree(GetProcessHeap(), 0, inbuf);
            HeapFree(GetProcessHeap(), 0, outbuf);
            return;
        }
        SetFilePointer(gymfile, 428, NULL, FILE_BEGIN);
        WriteFile(gymfile, outbuf, bytesout, &bytesout, NULL);
		SetEndOfFile(gymfile);
        gymtag.compressed = bytesin;
		SetFilePointer(gymfile, 0, NULL, FILE_BEGIN);
		WriteFile(gymfile, &gymtag, 428, &bytesout, NULL);
        SendDlgItemMessage(hWnd, DLG_INFO_BTN_COMPRESS, WM_SETTEXT, 0, (LPARAM)"Decompress");
    }
	else {
	    bytesout = gymtag.compressed;
		outbuf = HeapAlloc(GetProcessHeap(), 0, bytesout);        
        if(uncompress(outbuf, &bytesout, inbuf, bytesin) != Z_OK) {
            CloseHandle(gymfile);
            HeapFree(GetProcessHeap(), 0, inbuf);
            HeapFree(GetProcessHeap(), 0, outbuf);
            return;
        }
        SetFilePointer(gymfile, 428, NULL, FILE_BEGIN);
        WriteFile(gymfile, outbuf, bytesout, &bytesout, NULL);
		SetEndOfFile(gymfile);
		gymtag.compressed = 0;
		SetFilePointer(gymfile, 0, NULL, FILE_BEGIN);
		WriteFile(gymfile, &gymtag, 428, &bytesout, NULL);
        SendDlgItemMessage(hWnd, DLG_INFO_BTN_COMPRESS, WM_SETTEXT, 0, (LPARAM)"Compress");
	}
	HeapFree(GetProcessHeap(), 0, inbuf);
	HeapFree(GetProcessHeap(), 0, outbuf);
    CloseHandle(gymfile);
	return;
}

BOOL CALLBACK InfoDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    static char *fn = NULL;
    int retval = 0;

    switch(uMsg) {
        case(WM_INITDIALOG):
            retval = 1;
            fn = (char *)lParam;
            DoInfoDlgInit(hWnd);
            break;
        case(WM_COMMAND):
            if(HIWORD(wParam) == BN_CLICKED) {
                retval = 1;
                if(LOWORD(wParam) == DLG_INFO_BTN_COMPRESS) {
                    DoGYMCompression(hWnd, fn);
                    break;
                }
                else if(LOWORD(wParam) == IDOK)
                    DoInfoDlgClose(hWnd, fn);
                EndDialog(hWnd, 0);
            }
            break;
        case(WM_CLOSE):
            retval = 1;
            EndDialog(hWnd, 0);
            break;
    }
    return retval;
}

/*
** Shows information about the current
** file when the user clicks on the
** info button.
*/
int infoDlg(char *fn, HWND hwnd) {
    int time_len;
    char *title, *outstring;

    if(!lstrcmpi(fn + lstrlen(fn) - 4, ".gym")) {
        DialogBoxParam(mod.hDllInstance, MAKEINTRESOURCE(DLG_INFO), hwnd, InfoDlgProc, (LPARAM)fn);
        return 0;
    }
    else {
        title = HeapAlloc(GetProcessHeap(), 0, 256);
        if(!title) {
            ShowErrorMessage(GetLastError(), "Error Allocating Memory");
            return 0;
        }
        outstring = HeapAlloc(GetProcessHeap(), 0, 1000);
        if(!outstring) {
            ShowErrorMessage(GetLastError(), "Error Allocating Memory");
            HeapFree(GetProcessHeap(), 0, title);
            return 0;
        }
        getfileinfo(fn, title, &time_len);
        wsprintf(outstring, "File name: %s\n\nLength in seconds: %i", title, time_len / 1000);
        MessageBox(hwnd, outstring, "File Info", MB_OK);
        HeapFree(GetProcessHeap(), 0, title);
        HeapFree(GetProcessHeap(), 0, outstring);
    }
    return 0;
}

In_Module mod = {
    0x100,
    "YMAMP v2.0 by Marp (x86)",
    0,      /* hMainWindow */
    0,      /* hDllInstance */
    "GYM;CYM\0Callus and Genecyst sound files (*.CYM;*.GYM)\0",
    1,      /* is_seekable */
    1,      /* uses output */
    config,
    about,
    init,
    quit,
    getfileinfo,
    infoDlg,
    isourfile,
    play,
    pause,
    unpause,
    ispaused,
    stop,

    getlength,
    getoutputtime,
    setoutputtime,
    setvolume,
    setpan,

    /* pointers filled in by winamp */
    0,0,0,0,0,0,0,0,0,      /* vis stuff */
    0,0,    /* dsp */

    NULL,   /* EQ, not used */
    NULL,   /* setinfo */
    0       /* out_mod */
};

#if defined(_MSC_VER) || defined(__LCC__) || defined(__MINGW32__) || defined(__CYGWIN__)
__declspec(dllexport)
#else
#warning Make sure winampGetInModule2() gets exported!
#endif
In_Module *winampGetInModule2(void) {
    return &mod;
}

static int get_cym_data_pos(int time_position) {
    unsigned loop, num_zeros = 0, num_zeros_target;

    num_zeros_target = (int)((time_position / 1000.0) * 60.0);
    for(loop = 0; loop < file_size; loop++) {
        if(num_zeros == num_zeros_target)
            break;
        if(!ym_data[loop])
            num_zeros++;
        else
            loop++;
    }
    return loop;
}

static int get_gym_data_pos(int time_position) {
    unsigned loop, num_zeros = 0, num_zeros_target;

    num_zeros_target = (int)((time_position / 1000.0) * 60.0);
    for(loop = 0; loop < file_size; loop++) {
        if(num_zeros == num_zeros_target)
            break;
        switch(ym_data[loop]) {
            case(0x00):
                num_zeros++;
                continue;
            case(0x01):
                loop += 2;
                continue;
            case(0x02):
                loop += 2;
                continue;
            case(0x03):
                loop += 1;
                continue;
        }
    }
    return loop;
}

short mix(int a, int b) {
    int c = (a + b) * 2;
    if(c > 32767)
        c = 32767;
    else if( c < -32768)
        c = -32768;
    return (short)c;
}

DWORD WINAPI PlayGYMThread(void *want_stop) {
    int loop, loop2 = 0, update = 0, adjtime = 0,
        srate, srate152, srate15, srate30, numsamples;
    short *samp_buf, *ym_buf[2], *snbuf;
    unsigned char *dac_data;
    unsigned long data_pos = 0;
    int dac_max = 0;

    srate = samplerate;
    srate152 = (srate / 15) << 1;
    srate15 = srate / 15;
    srate30 = srate / 30;
    
    dac_data = HeapAlloc(GetProcessHeap(), 0, srate30);
    if(!dac_data) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
        HeapFree(GetProcessHeap(), 0, ym_data);
        *(int *)want_stop = 0;
        return 0;
    }
    ym_buf[0] = (short *)HeapAlloc(GetProcessHeap(), 0, srate15);
    if(!ym_buf[0]) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
        HeapFree(GetProcessHeap(), 0, ym_data);
        HeapFree(GetProcessHeap(), 0, dac_data);
        *(int *)want_stop = 0;
        return 0;
    }
    ym_buf[1] = (short *)HeapAlloc(GetProcessHeap(), 0, srate15);
    if(!ym_buf[1]) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
        HeapFree(GetProcessHeap(), 0, ym_data);
        HeapFree(GetProcessHeap(), 0, ym_buf[0]);
        *(int *)want_stop = 0;
        return 0;
    }
    snbuf = (short *)HeapAlloc(GetProcessHeap(), 0, srate15);
    if(!snbuf) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
        HeapFree(GetProcessHeap(), 0, ym_data);
        HeapFree(GetProcessHeap(), 0, ym_buf[0]);
        HeapFree(GetProcessHeap(), 0, ym_buf[1]);
        *(int *)want_stop = 0;
        return 0;
    }
    samp_buf = (short *)HeapAlloc(GetProcessHeap(), 0, srate152 << 1);
    if(!samp_buf) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
        HeapFree(GetProcessHeap(), 0, ym_data);
        HeapFree(GetProcessHeap(), 0, ym_buf[0]);
        HeapFree(GetProcessHeap(), 0, ym_buf[1]);
        HeapFree(GetProcessHeap(), 0, snbuf);
        *(int *)want_stop = 0;
        return 0;
    }
    do {
        loop = YM2612Init(1, ym2612_clock * 7670442 / 100, srate, NULL, NULL);
        if(loop == DUP_INIT)
            YM2612Shutdown();
        else if(loop) {
            ShowErrorMessage(loop, "Error Initializing YM2612 Emulation");
            YM2612Shutdown();
            HeapFree(GetProcessHeap(), 0, ym_data);
            HeapFree(GetProcessHeap(), 0, ym_buf[0]);
            HeapFree(GetProcessHeap(), 0, ym_buf[1]);
            HeapFree(GetProcessHeap(), 0, snbuf);
            HeapFree(GetProcessHeap(), 0, samp_buf);
            return 0;
        }
    } while(loop);
    if(SN76496_init(0, sn76496_clock * 3579580 / 100, srate)) {
        MessageBox(mod.hMainWindow, "Error initializing SN76496 emulation.", "ERROR", MB_ICONSTOP);
        YM2612Shutdown();
        HeapFree(GetProcessHeap(), 0, ym_data);
        HeapFree(GetProcessHeap(), 0, ym_buf[0]);
        HeapFree(GetProcessHeap(), 0, ym_buf[1]);
        HeapFree(GetProcessHeap(), 0, snbuf);
        HeapFree(GetProcessHeap(), 0, samp_buf);
        return 0;
    }
    mod.SetInfo(0, srate / 1000, 2, 1);
    while(!*(int *)want_stop && (data_pos < file_size)) {
        if(seek_needed != -1) {
            mod.SetInfo(0, srate / 1000, 2, 0);
            time_pos = seek_needed - (seek_needed % 60);
            data_pos = get_gym_data_pos(time_pos);
            mod.outMod->Flush(time_pos);
            mod.SetInfo(0, srate / 1000, 2, 1);
            adjtime = 0;
            dac_max = 0;
            seek_needed = -1;
            continue;
        }
        switch(ym_data[data_pos]) {
            case(0x00):
                data_pos++;
                if(update) {
                    update = 0;
                    if(dac_max && isbitset(&channels, 13)) {
                        short *dac_buf[2];
                        double update_cycle = (double)srate30 / dac_max; /* dac port write cycle */
                        double stream_cnt = 0;
                        for(loop = 0; loop < dac_max; loop++) {
                            int old_cnt = (int)stream_cnt;
                            int step;
                            /* update DAC port */
                            YM2612Write(0, 0, 0x2a);
                            YM2612Write(0, 1, dac_data[loop]);
                            if(loop == dac_max - 1)
                                step = srate30 - old_cnt;
                            else {
                                stream_cnt += update_cycle;
                                step = (int)stream_cnt - old_cnt;
                            }
                            /* update stream */
                            dac_buf[0] = &ym_buf[0][old_cnt];
                            dac_buf[1] = &ym_buf[1][old_cnt];
                            YM2612UpdateOne(0, (void **)dac_buf, step);
                        }
                        dac_max = 0;
                    }
                    else
                        YM2612UpdateOne(0, (void **)ym_buf, srate30);
                    SN76496Update_16(0, snbuf, srate30);
                    loop2 = 0;
                    for(loop = 0; loop < srate30; loop++) {
                        samp_buf[loop2] = mix(ym_buf[0][loop], snbuf[loop] >> 1);
                        loop2++;
                        samp_buf[loop2] = mix(ym_buf[1][loop], snbuf[loop] >> 1);
                        loop2++;
                    }
                    mod.SAAddPCMData(samp_buf, 2, 16, time_pos);
                    mod.VSAAddPCMData(samp_buf, 2, 16, time_pos);
                    if(mod.dsp_isactive())
                        numsamples = mod.dsp_dosamples(samp_buf, srate30, 16, 2, srate);
                    else
                        numsamples = srate30;
                    if(adjtime == 2) {
                        time_pos += 34;
                        adjtime = 0;
                    }
                    else {
                        time_pos += 33;
                        adjtime++;
                    }
                    if(numsamples > srate30) {
                        while(mod.outMod->CanWrite() < srate152 && !*(int *)want_stop)
                            Sleep(20);
                        mod.outMod->Write(samp_buf, srate152);
                        numsamples -= srate30;
                        while(mod.outMod->CanWrite() < (numsamples << 2) && !*(int *)want_stop)
                            Sleep(20);
                        mod.outMod->Write(&samp_buf[srate15], numsamples << 2);
                    }
                    else {
                        while(mod.outMod->CanWrite() < (numsamples << 2) && !*(int *)want_stop)
                            Sleep(20);
                        mod.outMod->Write(samp_buf, numsamples << 2);
                    }
                }
                else
                    update = 1;
                break;
            case(0x01):
                data_pos++;
                if(ym_data[data_pos] == 0x2a) {
                    data_pos++;
                    if(dac_max < srate30)
                        dac_data[dac_max++] = ym_data[data_pos];
                    data_pos++;
                }
                else {
                    YM2612Write(0, 0, ym_data[data_pos++]);
                    YM2612Write(0, 1, ym_data[data_pos++]);
                }
                break;
            case(0x02):
                data_pos++;
                YM2612Write(0, 2, ym_data[data_pos++]);
                YM2612Write(0, 3, ym_data[data_pos++]);
                break;
            case(0x03):
                data_pos++;
                SN76496Write(0, ym_data[data_pos++]);
                break;
            default:
                data_pos++;
                break;
        }
    }
    YM2612Shutdown();
    HeapFree(GetProcessHeap(), 0, ym_data);
    HeapFree(GetProcessHeap(), 0, ym_buf[0]);
    HeapFree(GetProcessHeap(), 0, ym_buf[1]);
    HeapFree(GetProcessHeap(), 0, snbuf);
    HeapFree(GetProcessHeap(), 0, samp_buf);
    if(data_pos >= file_size) {
        mod.SetInfo(0, srate / 1000, 2, 0);
        while(mod.outMod->IsPlaying() && !*(int *)want_stop)
            Sleep(20);
        if(!*(int *)want_stop)
            PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
    }
    time_pos = 0;
    *(int *)want_stop = 0;
    return 0;
}

DWORD WINAPI PlayCYMThread(void *want_stop) {
    int loop, loop2 = 0, update = 0, adjtime = 0,
        srate, srate152, srate15, srate30, numsamples;
    short *samp_buf, *ym_buf[2];
    unsigned long data_pos = 0;

    srate = samplerate;
    srate152 = (srate / 15) << 1;
    srate15 = srate / 15;
    srate30 = srate / 30;

    ym_buf[0] = (short *)HeapAlloc(GetProcessHeap(), 0, srate15);
    if(!ym_buf[0]) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        HeapFree(GetProcessHeap(), 0, ym_data);
        PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
        *(int *)want_stop = 0;
        return 0;
    }
    ym_buf[1] = (short *)HeapAlloc(GetProcessHeap(), 0, srate15);
    if(!ym_buf[1]) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        HeapFree(GetProcessHeap(), 0, ym_data);
        HeapFree(GetProcessHeap(), 0, ym_buf[0]);
        PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
        *(int *)want_stop = 0;
        return 0;
    }
    samp_buf = (short *)HeapAlloc(GetProcessHeap(), 0, srate152 << 1);
    if(!samp_buf) {
        ShowErrorMessage(GetLastError(), "Error Allocating Memory");
        HeapFree(GetProcessHeap(), 0, ym_data);
        HeapFree(GetProcessHeap(), 0, ym_buf[0]);
        HeapFree(GetProcessHeap(), 0, ym_buf[1]);
        PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
        *(int *)want_stop = 0;
        return 0;
    }
    do {
        loop = YM2151Init(1, ym2151_clock * 3579545 / 100, srate);
        if(loop == DUP_INIT)
            YM2151Shutdown();
        else if(loop) {
            ShowErrorMessage(loop, "Error Initializing YM2151 Emulation");
            YM2151Shutdown();
            HeapFree(GetProcessHeap(), 0, ym_data);
            HeapFree(GetProcessHeap(), 0, ym_buf[0]);
            HeapFree(GetProcessHeap(), 0, ym_buf[1]);
            HeapFree(GetProcessHeap(), 0, samp_buf);
            return 0;
        }
    } while(loop);
    mod.SetInfo(0, srate / 1000, 2, 1);
    while(!*(int *)want_stop && (data_pos < file_size)) {
        if(seek_needed != -1) {
            mod.SetInfo(0, srate / 1000, 2, 0);
            time_pos = seek_needed - (seek_needed % 60);
            data_pos = get_cym_data_pos(time_pos);
            mod.outMod->Flush(time_pos);
            mod.SetInfo(0, srate / 1000, 2, 1);
            adjtime = 0;
            seek_needed = -1;
            continue;
        }
        if(!ym_data[data_pos]) {
            data_pos++;
            if(update) {
                YM2151UpdateOne(0, (void **)ym_buf, srate30);
                loop2 = 0;
                for(loop = 0; loop < srate30; loop++) {
                    samp_buf[loop2] = (short)(ym_buf[0][loop]);
                    loop2++;
                    samp_buf[loop2] = (short)(ym_buf[1][loop]);
                    loop2++;
                }
                mod.SAAddPCMData(samp_buf, 2, 16, time_pos);
                mod.VSAAddPCMData(samp_buf, 2, 16, time_pos);
                if(mod.dsp_isactive())
                    numsamples = mod.dsp_dosamples(samp_buf, srate30, 16, 2, srate);
                else
                    numsamples = srate30;
                if(adjtime == 2) {
                    time_pos += 34;
                    adjtime = 0;
                }
                else {
                    time_pos += 33;
                    adjtime++;
                }
                if(numsamples > srate30) {
                    while(mod.outMod->CanWrite() < srate152 && !*(int *)want_stop)
                        Sleep(20);
                    mod.outMod->Write(samp_buf, srate152);
                    numsamples -= srate30;
                    while(mod.outMod->CanWrite() < (numsamples << 2) && !*(int *)want_stop)
                        Sleep(20);
                    mod.outMod->Write(&samp_buf[srate15], numsamples << 2);
                }
                else {
                    while(mod.outMod->CanWrite() < (numsamples << 2) && !*(int *)want_stop)
                        Sleep(20);
                    mod.outMod->Write(samp_buf, numsamples << 2);
                }
                update = 0;
            }
            else
                update = 1;
        }
        else {
            YM2151WriteReg(0, ym_data[data_pos], ym_data[data_pos + 1]);
            data_pos += 2;
        }
    }
    YM2151Shutdown();
    HeapFree(GetProcessHeap(), 0, ym_data);
    HeapFree(GetProcessHeap(), 0, ym_buf[0]);
    HeapFree(GetProcessHeap(), 0, ym_buf[1]);
    HeapFree(GetProcessHeap(), 0, samp_buf);
    if(data_pos >= file_size) {
        mod.SetInfo(0, srate / 1000, 2, 0);
        while(mod.outMod->IsPlaying() && !*(int *)want_stop)
            Sleep(20);
        if(!*(int *)want_stop)
            PostMessage(mod.hMainWindow, WM_USER + 2, 0, 0);
    }
    time_pos = 0;
    *(int *)want_stop = 0;
    return 0;
}
