aboutsummaryrefslogtreecommitdiff
path: root/win32
diff options
context:
space:
mode:
Diffstat (limited to 'win32')
-rw-r--r--win32/amd64/Makefile42
-rw-r--r--win32/dsoundout.c180
-rw-r--r--win32/dsoundout.h13
-rw-r--r--win32/fmplayer.icobin0 -> 318 bytes
-rw-r--r--win32/lnf.manifest10
-rw-r--r--win32/lnf.rc5
-rw-r--r--win32/main.c484
-rw-r--r--win32/soundout.c44
-rw-r--r--win32/soundout.h18
-rw-r--r--win32/uc.c36
-rw-r--r--win32/waveout.c100
-rw-r--r--win32/waveout.h13
-rw-r--r--win32/x86/Makefile43
13 files changed, 988 insertions, 0 deletions
diff --git a/win32/amd64/Makefile b/win32/amd64/Makefile
new file mode 100644
index 0000000..5c959c5
--- /dev/null
+++ b/win32/amd64/Makefile
@@ -0,0 +1,42 @@
+vpath %.c ../
+vpath %.c ../../fmdriver
+vpath %.c ../../libopna
+vpath %.c ../../fmdsp
+vpath %.rc ..
+TARGET=fmplayer.exe
+FMDRIVER_OBJS=fmdriver_fmp.o \
+ ppz8.o
+LIBOPNA_OBJS=opna.o \
+ opnatimer.o \
+ opnafm.o \
+ opnassg.o \
+ opnadrum.o \
+ opnaadpcm.o
+FMDSP_OBJS=fmdsp.o
+OBJS=main.o dsoundout.o soundout.o waveout.o uc.o lnf.o \
+ $(FMDRIVER_OBJS) \
+ $(LIBOPNA_OBJS) \
+ $(FMDSP_OBJS)
+ARCH=x86_64
+PREFIX=$(ARCH)-w64-mingw32-
+CC=$(PREFIX)gcc
+WINDRES=$(PREFIX)windres
+STRIP=$(PREFIX)strip
+CFLAGS=-std=c99 -Os -Wall -Wextra -pedantic -I../.. \
+ -DUNICODE -D_UNICODE \
+ -DWINVER=0x0500 -D_WIN32_WINNT=0x0500
+LIBS=-nostdlib -s -Wl,-eentry \
+ -Wl,--subsystem,windows \
+ -lgcc -lntdll \
+ -luser32 -lkernel32 -lole32 -ldxguid -luuid -lcomdlg32 \
+ -lgdi32 -lshlwapi -lwinmm -lshell32
+
+$(TARGET): $(OBJS)
+ $(CC) -o $@ $(OBJS) $(LIBS)
+ $(STRIP) $@
+
+%.o: %.rc
+ $(WINDRES) -o $@ -i $<
+
+clean:
+ rm -f $(TARGET) $(OBJS)
diff --git a/win32/dsoundout.c b/win32/dsoundout.c
new file mode 100644
index 0000000..6fda977
--- /dev/null
+++ b/win32/dsoundout.c
@@ -0,0 +1,180 @@
+#include "dsoundout.h"
+#include <windows.h>
+#include <dsound.h>
+
+struct dsound_state {
+ IDirectSound8 *ds;
+ DWORD sectlen;
+ IDirectSoundBuffer *dsbuf;
+ HANDLE e_posnotf;
+ HANDLE t_update;
+ DWORD terminate;
+ DWORD currsect;
+ DWORD playing;
+ HANDLE mtx_cbproc;
+ sound_callback cbfunc;
+ void *userptr;
+};
+
+static DWORD WINAPI bufupdatethread(LPVOID p) {
+ DWORD playcur, writecur;
+ struct dsound_state *dsound = (struct dsound_state *)p;
+ while (1) {
+ WaitForSingleObject(dsound->e_posnotf, INFINITE);
+ if (dsound->terminate) ExitThread(0);
+ dsound->dsbuf->lpVtbl->GetCurrentPosition(dsound->dsbuf, &playcur, &writecur);
+ DWORD playsect = playcur / dsound->sectlen;
+ if (playsect < dsound->currsect) {
+ LPVOID ptr;
+ DWORD len;
+ DWORD startptr = dsound->currsect * dsound->sectlen;
+ DWORD writelen = (dsound->sectlen * (4 - dsound->currsect));
+ dsound->dsbuf->lpVtbl->Lock(dsound->dsbuf, startptr, writelen, &ptr, &len, NULL, NULL, 0);
+ int16_t *bufptr = (int16_t *)ptr;
+
+ if (WaitForSingleObject(dsound->mtx_cbproc, 0) == WAIT_OBJECT_0) {
+ if (dsound->playing) {
+ (*dsound->cbfunc)(dsound->userptr, bufptr, writelen/4);
+ } else {
+ ZeroMemory(bufptr, writelen);
+ }
+ ReleaseMutex(dsound->mtx_cbproc);
+ } else {
+ ZeroMemory(bufptr, writelen);
+ }
+
+ dsound->dsbuf->lpVtbl->Unlock(dsound->dsbuf, ptr, writelen, NULL, 0);
+ dsound->currsect = 0;
+ } else if (playsect == dsound->currsect) continue;
+ LPVOID ptr;
+ DWORD len;
+ DWORD startptr = dsound->currsect * dsound->sectlen;
+ uint32_t writelen = (dsound->sectlen * (playsect - dsound->currsect));
+ dsound->dsbuf->lpVtbl->Lock(dsound->dsbuf, startptr, writelen, &ptr, &len, NULL, NULL, 0);
+ int16_t *bufptr = (int16_t *)ptr;
+
+ if (WaitForSingleObject(dsound->mtx_cbproc, 0) == WAIT_OBJECT_0) {
+ if (dsound->playing) {
+ (*dsound->cbfunc)(dsound->userptr, bufptr, writelen/4);
+ } else {
+ ZeroMemory(bufptr, writelen);
+ }
+ ReleaseMutex(dsound->mtx_cbproc);
+ } else {
+ ZeroMemory(bufptr, writelen);
+ }
+
+ dsound->dsbuf->lpVtbl->Unlock(dsound->dsbuf, ptr, writelen, NULL, 0);
+ dsound->currsect = playsect;
+ }
+}
+
+struct dsound_state *dsound_init(HWND hwnd, unsigned srate, unsigned sectlen,
+ sound_callback cbfunc, void *userptr) {
+ HANDLE heap = GetProcessHeap();
+ struct dsound_state *dsound = HeapAlloc(heap, HEAP_ZERO_MEMORY,
+ sizeof(struct dsound_state));
+ if (!dsound) {
+ MessageBoxW(hwnd, L"cannot allocate memory for DirectSound", L"Error", MB_ICONSTOP);
+ goto err;
+ }
+ CoInitializeEx(0, COINIT_MULTITHREADED);
+ HRESULT hr;
+ hr = CoCreateInstance(
+ &CLSID_DirectSound8,
+ 0,
+ CLSCTX_INPROC_SERVER,
+ &IID_IDirectSound8,
+ (void **)&dsound->ds);
+ if (hr != S_OK) {
+ MessageBoxW(hwnd, L"cannot create instance of DirectSound8", L"Error", MB_ICONSTOP);
+ goto err_dsound;
+ }
+ hr = dsound->ds->lpVtbl->Initialize(dsound->ds, 0);
+ if (hr != S_OK) {
+ MessageBoxW(hwnd, L"cannot initialize DirectSound8", L"Error", MB_ICONSTOP);
+ goto err_dsound;
+ }
+ dsound->ds->lpVtbl->SetCooperativeLevel(dsound->ds, hwnd, DSSCL_NORMAL);
+
+ DSBUFFERDESC bufdesc = {0};
+ bufdesc.dwSize = sizeof(bufdesc);
+ bufdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 |
+ DSBCAPS_GLOBALFOCUS |
+ DSBCAPS_CTRLPOSITIONNOTIFY;
+ bufdesc.dwBufferBytes = sectlen * 4;
+ WAVEFORMATEX format;
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ format.nChannels = 2;
+ format.nSamplesPerSec = srate;
+ format.wBitsPerSample = 16;
+ format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
+ format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+ format.cbSize = 0;
+ bufdesc.lpwfxFormat = &format;
+ bufdesc.guid3DAlgorithm = DS3DALG_DEFAULT;
+ bufdesc.dwReserved = 0;
+
+ if (dsound->ds->lpVtbl->CreateSoundBuffer(dsound->ds, &bufdesc, &dsound->dsbuf, 0) != S_OK) {
+ MessageBoxW(hwnd, L"cannot initialize DirectSoundBuffer", L"Error", MB_ICONSTOP);
+ goto err_ds;
+ }
+
+ IDirectSoundNotify *dsnotf;
+ if (dsound->dsbuf->lpVtbl->QueryInterface(dsound->dsbuf,
+ &IID_IDirectSoundNotify,
+ (void **)&dsnotf) != S_OK) {
+ goto err_dsbuf;
+ }
+
+ dsound->e_posnotf = CreateEventW(NULL, FALSE, FALSE, L"SNDNOTF");
+ dsound->t_update = CreateThread(NULL, 0, bufupdatethread, dsound, 0, NULL);
+ dsound->sectlen = sectlen;
+ dsound->playing = 0;
+ dsound->terminate = 0;
+ dsound->cbfunc = cbfunc;
+ dsound->userptr = userptr;
+ dsound->mtx_cbproc = CreateMutexW(0, FALSE, 0);
+
+
+ DSBPOSITIONNOTIFY posnotf[4] = {
+ {(DWORD)dsound->sectlen*0, dsound->e_posnotf},
+ {(DWORD)dsound->sectlen*1, dsound->e_posnotf},
+ {(DWORD)dsound->sectlen*2, dsound->e_posnotf},
+ {(DWORD)dsound->sectlen*3, dsound->e_posnotf},
+ };
+ dsnotf->lpVtbl->SetNotificationPositions(dsnotf, 4, posnotf);
+ dsnotf->lpVtbl->Release(dsnotf);
+ return dsound;
+err_dsbuf:
+ dsound->dsbuf->lpVtbl->Release(dsound->dsbuf);
+err_ds:
+ dsound->ds->lpVtbl->Release(dsound->ds);
+err_dsound:
+ HeapFree(heap, 0, dsound);
+err:
+ return 0;
+}
+
+void dsound_pause(struct dsound_state *dsound, int pause) {
+ if (pause) {
+ dsound->playing = 0;
+ WaitForSingleObject(dsound->mtx_cbproc, INFINITE);
+ ReleaseMutex(dsound->mtx_cbproc);
+ } else {
+ dsound->playing = 1;
+ dsound->dsbuf->lpVtbl->Play(dsound->dsbuf, 0, 0, DSBPLAY_LOOPING);
+ }
+}
+
+void dsound_delete(struct dsound_state *dsound) {
+ if (!dsound) return;
+ dsound_pause(dsound, 1);
+ dsound->terminate = 1;
+ SetEvent(dsound->e_posnotf);
+ WaitForSingleObject(dsound->t_update, INFINITE);
+ CloseHandle(dsound->mtx_cbproc);
+ dsound->dsbuf->lpVtbl->Release(dsound->dsbuf);
+ dsound->ds->lpVtbl->Release(dsound->ds);
+ HeapFree(GetProcessHeap(), 0, dsound);
+}
diff --git a/win32/dsoundout.h b/win32/dsoundout.h
new file mode 100644
index 0000000..10482e6
--- /dev/null
+++ b/win32/dsoundout.h
@@ -0,0 +1,13 @@
+#ifndef MYON_DSOUNDOUT_H_INCLUDED
+#define MYON_DSOUNDOUT_H_INCLUDED
+
+#include "soundout.h"
+
+struct dsound_state;
+
+struct dsound_state *dsound_init(HWND hwnd, unsigned srate, unsigned sectlen,
+ sound_callback cbfunc, void *userptr);
+void dsound_delete(struct dsound_state *dsound);
+void dsound_pause(struct dsound_state *dsound, int pause);
+
+#endif // MYON_DSOUNDOUT_H_INCLUDED
diff --git a/win32/fmplayer.ico b/win32/fmplayer.ico
new file mode 100644
index 0000000..801126c
--- /dev/null
+++ b/win32/fmplayer.ico
Binary files differ
diff --git a/win32/lnf.manifest b/win32/lnf.manifest
new file mode 100644
index 0000000..ef951b7
--- /dev/null
+++ b/win32/lnf.manifest
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="CompanyName.ProductName.YourApplication" type="win32" />
+ <description>Your application description here.</description>
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
+ </dependentAssembly>
+ </dependency>
+</assembly>
diff --git a/win32/lnf.rc b/win32/lnf.rc
new file mode 100644
index 0000000..500cb8c
--- /dev/null
+++ b/win32/lnf.rc
@@ -0,0 +1,5 @@
+#include <winuser.h>
+
+1 ICON "fmplayer.ico"
+CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "lnf.manifest"
+
diff --git a/win32/main.c b/win32/main.c
new file mode 100644
index 0000000..dd60702
--- /dev/null
+++ b/win32/main.c
@@ -0,0 +1,484 @@
+#include <stddef.h>
+#include <stdbool.h>
+#include <windows.h>
+#include <shlwapi.h>
+#include <windowsx.h>
+#include <commctrl.h>
+
+#include "fmdriver/fmdriver_fmp.h"
+#include "libopna/opna.h"
+#include "libopna/opnatimer.h"
+#include "fmdsp/fmdsp.h"
+#include "soundout.h"
+
+enum {
+ ID_OPENFILE = 0x10,
+ TIMER_FMDSP = 1,
+};
+
+enum {
+ SRATE = 55467,
+ SECTLEN = 4096,
+ PPZ8MIX = 0xa000,
+ FONT_ROM_SIZE = 0x84000,
+ FONT_ROM_FILESIZE = 0x46800,
+};
+
+#define ENABLE_WM_DROPFILES
+// #define ENABLE_IDROPTARGET
+
+static struct {
+ HINSTANCE hinst;
+ HANDLE heap;
+ struct sound_state *sound;
+ struct opna opna;
+ struct opna_timer opna_timer;
+ struct ppz8 ppz8;
+ struct fmdriver_work work;
+ struct driver_fmp *fmp;
+ struct fmdsp fmdsp;
+ uint8_t vram[PC98_W*PC98_H];
+ uint8_t font[FONT_ROM_SIZE];
+ void *drum_rom;
+ uint8_t opna_adpcm_ram[OPNA_ADPCM_RAM_SIZE];
+ void *ppz8_buf;
+} g;
+
+
+static void opna_int_cb(void *userptr) {
+ struct fmdriver_work *work = (struct fmdriver_work *)userptr;
+ work->driver_opna_interrupt(work);
+}
+
+static void opna_mix_cb(void *userptr, int16_t *buf, unsigned samples) {
+ struct ppz8 *ppz8 = (struct ppz8 *)userptr;
+ ppz8_mix(ppz8, buf, samples);
+}
+
+static void opna_writereg_libopna(struct fmdriver_work *work, unsigned addr, unsigned data) {
+ struct opna_timer *timer = (struct opna_timer *)work->opna;
+ opna_timer_writereg(timer, addr, data);
+}
+
+static uint8_t opna_status_libopna(struct fmdriver_work *work, bool a1) {
+ struct opna_timer *timer = (struct opna_timer *)work->opna;
+ uint8_t status = opna_timer_status(timer);
+ if (!a1) {
+ status &= 0x83;
+ }
+ return status;
+}
+
+static void sound_cb(void *p, int16_t *buf, unsigned frames) {
+ struct opna_timer *timer = (struct opna_timer *)p;
+ ZeroMemory(buf, sizeof(int16_t)*frames*2);
+ opna_timer_mix(timer, buf, frames);
+}
+
+static void on_timer(HWND hwnd, UINT id) {
+ if (id == TIMER_FMDSP) {
+ InvalidateRect(hwnd, 0, FALSE);
+ }
+}
+
+static HANDLE pvisearch(const wchar_t *filename, const char *pviname_a) {
+ enum {
+ WPVINAMELEN = 8*2+1+3+1,
+ };
+ wchar_t pviname[WPVINAMELEN];
+ wchar_t pvipath[PATH_MAX];
+ if (MultiByteToWideChar(932, MB_ERR_INVALID_CHARS,
+ pviname_a, -1, pviname, WPVINAMELEN) == 0) {
+ return INVALID_HANDLE_VALUE;
+ }
+ lstrcat(pviname, L".PVI");
+ if (lstrlen(filename) >= PATH_MAX) return INVALID_HANDLE_VALUE;
+ lstrcpy(pvipath, filename);
+ PathRemoveFileSpec(pvipath);
+ if (lstrlen(pvipath) + lstrlen(pviname) + 1 >= PATH_MAX) {
+ return INVALID_HANDLE_VALUE;
+ }
+ lstrcat(pvipath, L"\\");
+ lstrcat(pvipath, pviname);
+ return CreateFile(pvipath, GENERIC_READ,
+ 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+}
+
+static void loadrom(void) {
+ const wchar_t *path = L"ym2608_adpcm_rom.bin";
+ wchar_t exepath[MAX_PATH];
+ if (GetModuleFileName(0, exepath, MAX_PATH)) {
+ PathRemoveFileSpec(exepath);
+ if ((lstrlen(exepath) + lstrlen(path) + 1) < MAX_PATH) {
+ lstrcat(exepath, L"\\");
+ lstrcat(exepath, path);
+ path = exepath;
+ }
+ }
+ HANDLE file = CreateFile(path, GENERIC_READ,
+ 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+ if (file == INVALID_HANDLE_VALUE) goto err;
+ DWORD filesize = GetFileSize(file, 0);
+ if (filesize != OPNA_ROM_SIZE) goto err_file;
+ void *buf = HeapAlloc(g.heap, 0, OPNA_ROM_SIZE);
+ if (!buf) goto err_file;
+ DWORD readbytes;
+ if (!ReadFile(file, buf, OPNA_ROM_SIZE, &readbytes, 0)
+ || readbytes != OPNA_ROM_SIZE) goto err_buf;
+ CloseHandle(file);
+ g.drum_rom = buf;
+ return;
+err_buf:
+ HeapFree(g.heap, 0, buf);
+err_file:
+ CloseHandle(file);
+err:
+ return;
+}
+
+static bool loadpvi(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ const wchar_t *filename) {
+ if (!fmp->pvi_name[0]) return true;
+ HANDLE pvifile = pvisearch(filename, fmp->pvi_name);
+ if (pvifile == INVALID_HANDLE_VALUE) goto err;
+ DWORD filesize = GetFileSize(pvifile, 0);
+ if (filesize == INVALID_FILE_SIZE) goto err_file;
+ void *data = HeapAlloc(g.heap, 0, filesize);
+ if (!data) goto err_file;
+ DWORD readbytes;
+ if (!ReadFile(pvifile, data, filesize, &readbytes, 0)
+ || readbytes != filesize) goto err_data;
+ if (!fmp_adpcm_load(work, data, filesize)) goto err_data;
+ HeapFree(g.heap, 0, data);
+ CloseHandle(pvifile);
+ return true;
+err_data:
+ HeapFree(g.heap, 0, data);
+err_file:
+ CloseHandle(pvifile);
+err:
+ return false;
+}
+
+static bool loadppzpvi(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ const wchar_t *filename) {
+ if (!fmp->ppz_name[0]) return true;
+ HANDLE pvifile = pvisearch(filename, fmp->ppz_name);
+ if (pvifile == INVALID_HANDLE_VALUE) goto err;
+ DWORD filesize = GetFileSize(pvifile, 0);
+ if (filesize == INVALID_FILE_SIZE) goto err_file;
+ void *data = HeapAlloc(g.heap, 0, filesize);
+ if (!data) goto err_file;
+ void *buf = HeapAlloc(g.heap, 0, ppz8_pvi_decodebuf_samples(filesize) * sizeof(int16_t));
+ if (!buf) goto err_data;
+ DWORD readbytes;
+ if (!ReadFile(pvifile, data, filesize, &readbytes, 0)
+ || readbytes != filesize) goto err_buf;
+ if (!ppz8_pvi_load(work->ppz8, 0, data, filesize, buf)) goto err_buf;
+ if (g.ppz8_buf) HeapFree(g.heap, 0, g.ppz8_buf);
+ g.ppz8_buf = buf;
+ HeapFree(g.heap, 0, data);
+ CloseHandle(pvifile);
+ return true;
+err_buf:
+ HeapFree(g.heap, 0, buf);
+err_data:
+ HeapFree(g.heap, 0, data);
+err_file:
+ CloseHandle(pvifile);
+err:
+ return false;
+}
+
+static void openfile(HWND hwnd, const wchar_t *path) {
+ HANDLE file = CreateFile(path, GENERIC_READ,
+ 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+ if (file == INVALID_HANDLE_VALUE) {
+ MessageBox(hwnd, L"Cannot open file", L"Error", MB_ICONSTOP);
+ return;
+ }
+ LARGE_INTEGER li;
+ if (!GetFileSizeEx(file, &li)) {
+ MessageBox(hwnd, L"Cannot open file", L"Error", MB_ICONSTOP);
+ goto err_file;
+ }
+ if (li.QuadPart > 0xffff) {
+ MessageBox(hwnd, L"Invalid File (Filesize too large)", L"Error", MB_ICONSTOP);
+ goto err_file;
+ }
+ void *fmpdata = HeapAlloc(g.heap, 0, li.QuadPart);
+ if (!fmpdata) {
+ MessageBox(hwnd, L"Cannot allocate memory for file", L"Error", MB_ICONSTOP);
+ goto err_file;
+ }
+ DWORD readbytes;
+ if (!ReadFile(file, fmpdata, li.QuadPart, &readbytes, 0) || readbytes != li.QuadPart) {
+ MessageBox(hwnd, L"Cannot read file", L"Error", MB_ICONSTOP);
+ goto err_fmpdata;
+ }
+ struct driver_fmp *fmp = HeapAlloc(g.heap, HEAP_ZERO_MEMORY, sizeof(struct driver_fmp));
+ if (!fmp) {
+ MessageBox(hwnd, L"Cannot allocate memory for fmp", L"Error", MB_ICONSTOP);
+ goto err_fmpdata;
+ }
+ if (!fmp_load(fmp, fmpdata, li.QuadPart)) {
+ MessageBox(hwnd, L"Invalid File (not FMP data)", L"Error", MB_ICONSTOP);
+ goto err_fmp;
+ }
+ if (g.sound) {
+ g.sound->pause(g.sound, 1);
+ }
+ if (g.fmp) HeapFree(g.heap, 0, g.fmp);
+ g.fmp = fmp;
+ opna_reset(&g.opna);
+ if (g.drum_rom) opna_drum_set_rom(&g.opna.drum, g.drum_rom);
+ opna_adpcm_set_ram_256k(&g.opna.adpcm, g.opna_adpcm_ram);
+ opna_timer_reset(&g.opna_timer, &g.opna);
+ ppz8_init(&g.ppz8, SRATE, PPZ8MIX);
+ ZeroMemory(&g.work, sizeof(g.work));
+ g.work.opna_writereg = opna_writereg_libopna;
+ g.work.opna_status = opna_status_libopna;
+ g.work.opna = &g.opna_timer;
+ g.work.ppz8 = &g.ppz8;
+ g.work.ppz8_functbl = &ppz8_functbl;
+ opna_timer_set_int_callback(&g.opna_timer, opna_int_cb, &g.work);
+ opna_timer_set_mix_callback(&g.opna_timer, opna_mix_cb, &g.ppz8);
+ fmp_init(&g.work, g.fmp);
+ loadpvi(&g.work, g.fmp, path);
+ loadppzpvi(&g.work, g.fmp, path);
+ if (!g.sound) {
+ g.sound = sound_init(hwnd, SRATE, SECTLEN,
+ sound_cb, &g.opna_timer);
+ }
+ fmdsp_vram_init(&g.fmdsp, &g.work, g.font, g.vram);
+ if (!g.sound) goto err_fmp;
+ g.sound->pause(g.sound, 0);
+ CloseHandle(file);
+ return;
+err_fmp:
+ HeapFree(g.heap, 0, fmp);
+err_fmpdata:
+ HeapFree(g.heap, 0, fmpdata);
+err_file:
+ CloseHandle(file);
+}
+
+static void openfiledialog(HWND hwnd) {
+ wchar_t path[MAX_PATH] = {0};
+ OPENFILENAME ofn = {0};
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = hwnd;
+ ofn.hInstance = g.hinst;
+ ofn.lpstrFilter = L"FMP files (*.opi;*.ovi;*.ozi;*.m26;*.m86)\0"
+ "*.opi;*.ovi;*.ozi;*.m26;*.m86\0"
+ "All Files (*.*)\0"
+ "*.*\0\0";
+ ofn.lpstrFile = path;
+ ofn.nMaxFile = sizeof(path)/sizeof(path[0]);
+ ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
+ if (!GetOpenFileName(&ofn)) return;
+ openfile(hwnd, path);
+}
+
+#ifdef ENABLE_IDROPTARGET
+struct fmplayer_droptarget {
+ IDropTarget idt;
+ ULONG refcnt;
+};
+
+static HRESULT fmplayer_droptarget_addref(
+ IDropTarget *ptr) {
+ struct fmplayer_droptarget *this_ = (struct fmplayer_droptarget *)ptr;
+ return ++this_->refcnt;
+}
+
+static HRESULT fmplayer_droptarget_release(
+ IDropTarget *ptr) {
+ struct fmplayer_droptarget *this_ = (struct fmplayer_droptarget *)ptr;
+ ULONG refcnt = --this_->refcnt;
+ if (!refcnt) HeapFree(g.heap, 0, this_);
+ return refcnt;
+}
+
+static HRESULT fmplayer_droptarget_queryinterface(
+ IDropTarget *ptr, GUID *iid, void **obj) {
+ struct fmplayer_droptarget *this_ = (struct fmplayer_droptarget *)ptr;
+ if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IDropTarget)) {
+ *obj = ptr;
+ fmplayer_droptarget_addref(ptr);
+ return S_OK;
+ } else {
+ *obj = 0;
+ return E_NOINTERFACE;
+ }
+}
+
+struct fmplayer_droptarget *fmplayer_droptarget(void) {
+ struct fmplayer_droptarget *fdt = HeapAlloc(g.heap, HEAP_ZERO_MEMORY, sizeof(*fdt));
+ if (!fdt) return 0;
+ static bool vtblinit = false;
+ static IDropTargetVtbl vtbl;
+ if (!vtblinit) {
+ vtbl.QueryInterface = fmplayer_droptarget_queryinterface;
+ vtbl.AddRef = fmplayer_droptarget_addref;
+ vtbl.Release = fmplayer_droptarget_release;
+ vtblinit = true;
+ }
+ fdt->idt.lpVtbl = &vtbl;
+ fdt->refcnt = 1;
+}
+#endif // ENABLE_IDROPTARGET
+
+#ifdef ENABLE_WM_DROPFILES
+static void on_dropfiles(HWND hwnd, HDROP hdrop) {
+ wchar_t path[MAX_PATH] = {0};
+ if (DragQueryFile(hdrop, 0, path, sizeof(path)/sizeof(path[0]))) {
+ openfile(hwnd, path);
+ }
+ DragFinish(hdrop);
+}
+#endif // ENABLE_WM_DROPFILES
+
+static bool on_create(HWND hwnd, CREATESTRUCT *cs) {
+ (void)cs;
+ HWND button = CreateWindowEx(
+ 0,
+ L"BUTTON",
+ L"&Open",
+ WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
+ 10, 10,
+ 40, 25,
+ hwnd, (HMENU)ID_OPENFILE, g.hinst, 0
+ );
+ NONCLIENTMETRICS ncm;
+ ncm.cbSize = sizeof(ncm);
+ SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
+ HFONT font = CreateFontIndirect(&ncm.lfMessageFont);
+ SetWindowFont(button, font, TRUE);
+ loadrom();
+ fmdsp_init(&g.fmdsp);
+ fmdsp_vram_init(&g.fmdsp, &g.work, g.font, g.vram);
+ SetTimer(hwnd, TIMER_FMDSP, 50, 0);
+#ifdef ENABLE_WM_DROPFILES
+ DragAcceptFiles(hwnd, TRUE);
+#endif
+ return true;
+}
+
+static void on_command(HWND hwnd, int id, HWND hwnd_c, UINT code) {
+ (void)code;
+ (void)hwnd_c;
+ switch (id) {
+ case ID_OPENFILE:
+ openfiledialog(hwnd);
+ break;
+ }
+}
+
+static void on_destroy(HWND hwnd) {
+ (void)hwnd;
+ if (g.sound) g.sound->delete(g.sound);
+ if (g.fmp) HeapFree(g.heap, 0, g.fmp);
+ if (g.drum_rom) HeapFree(g.heap, 0, g.drum_rom);
+ if (g.ppz8_buf) HeapFree(g.heap, 0, g.ppz8_buf);
+ PostQuitMessage(0);
+}
+
+static void on_paint(HWND hwnd) {
+ fmdsp_update(&g.fmdsp, &g.work, g.vram);
+ PAINTSTRUCT ps;
+ static BITMAPINFO *bi = 0;
+ if (!bi) {
+ bi = HeapAlloc(g.heap, HEAP_ZERO_MEMORY,
+ sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*FMDSP_PALETTE_COLORS);
+ if (!bi) return;
+ bi->bmiHeader.biSize = sizeof(bi->bmiHeader);
+ bi->bmiHeader.biWidth = PC98_W;
+ bi->bmiHeader.biHeight = -PC98_H;
+ bi->bmiHeader.biPlanes = 1;
+ bi->bmiHeader.biBitCount = 8;
+ bi->bmiHeader.biCompression = BI_RGB;
+ bi->bmiHeader.biClrUsed = FMDSP_PALETTE_COLORS;
+ }
+ for (int p = 0; p < FMDSP_PALETTE_COLORS; p++) {
+ bi->bmiColors[p].rgbRed = g.fmdsp.palette[p*3+0];
+ bi->bmiColors[p].rgbGreen = g.fmdsp.palette[p*3+1];
+ bi->bmiColors[p].rgbBlue = g.fmdsp.palette[p*3+2];
+ }
+ HDC dc = BeginPaint(hwnd, &ps);
+ HDC mdc = CreateCompatibleDC(dc);
+ HBITMAP bitmap = CreateDIBitmap(
+ dc,
+ &bi->bmiHeader, CBM_INIT,
+ g.vram,
+ bi, DIB_RGB_COLORS);
+ SelectObject(mdc, bitmap);
+ BitBlt(dc, 0, 80, 640, 400, mdc, 0, 0, SRCCOPY);
+ DeleteDC(mdc);
+ DeleteObject(bitmap);
+ EndPaint(hwnd, &ps);
+}
+
+static LRESULT CALLBACK wndproc(
+ HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam
+) {
+ switch (msg) {
+ HANDLE_MSG(hwnd, WM_DESTROY, on_destroy);
+ HANDLE_MSG(hwnd, WM_CREATE, on_create);
+ HANDLE_MSG(hwnd, WM_COMMAND, on_command);
+ HANDLE_MSG(hwnd, WM_PAINT, on_paint);
+ HANDLE_MSG(hwnd, WM_TIMER, on_timer);
+#ifdef ENABLE_WM_DROPFILES
+ HANDLE_MSG(hwnd, WM_DROPFILES, on_dropfiles);
+#endif // ENABLE_WM_DROPFILES
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+static ATOM register_class(HINSTANCE hinst) {
+ WNDCLASS wc = {0};
+ wc.style = CS_HREDRAW | CS_VREDRAW;
+ wc.lpfnWndProc = wndproc;
+ wc.hInstance = hinst;
+ wc.hIcon = LoadIcon(g.hinst, MAKEINTRESOURCE(1));
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
+ wc.lpszClassName = L"TWinc";
+ return RegisterClass(&wc);
+}
+
+int CALLBACK wWinMain(HINSTANCE hinst, HINSTANCE hpinst,
+ wchar_t *cmdline, int cmdshow) {
+ (void)hpinst;
+ (void)cmdline;
+ g.hinst = hinst;
+ g.heap = GetProcessHeap();
+ ATOM wcatom = register_class(g.hinst);
+ DWORD style = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
+ DWORD exStyle = 0;
+ RECT wr;
+ wr.left = 0;
+ wr.right = 640;
+ wr.top = 0;
+ wr.bottom = 480;
+ AdjustWindowRectEx(&wr, style, 0, exStyle);
+ HWND hwnd = CreateWindowEx(
+ exStyle,
+ (wchar_t*)((uintptr_t)wcatom), L"FMPlayer/Win32",
+ style,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ wr.right-wr.left, wr.bottom-wr.top,
+ 0, 0, g.hinst, 0
+ );
+ ShowWindow(hwnd, cmdshow);
+
+ MSG msg = {0};
+ while (GetMessage(&msg, 0, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ return msg.wParam;
+}
+
diff --git a/win32/soundout.c b/win32/soundout.c
new file mode 100644
index 0000000..bb57bc1
--- /dev/null
+++ b/win32/soundout.c
@@ -0,0 +1,44 @@
+#include "soundout.h"
+#include "dsoundout.h"
+#include "waveout.h"
+
+static void soundout_dsound_pause(struct sound_state *state, int pause) {
+ dsound_pause((struct dsound_state *)state->driver_state, pause);
+}
+
+static void soundout_dsound_delete(struct sound_state *state) {
+ dsound_delete((struct dsound_state *)state->driver_state);
+ HeapFree(GetProcessHeap(), 0, state);
+}
+
+static void soundout_waveout_pause(struct sound_state *state, int pause) {
+ waveout_pause((struct waveout_state *)state->driver_state, pause);
+}
+
+static void soundout_waveout_delete(struct sound_state *state) {
+ waveout_delete((struct waveout_state *)state->driver_state);
+ HeapFree(GetProcessHeap(), 0, state);
+}
+
+struct sound_state *sound_init(HWND hwnd, unsigned srate, unsigned sectlen,
+ sound_callback cbfunc, void *userptr) {
+ HANDLE heap = GetProcessHeap();
+ struct sound_state *sound = HeapAlloc(heap, 0, sizeof(struct sound_state));
+ if (!sound) return 0;
+ struct dsound_state *dsound = dsound_init(hwnd, srate, sectlen, cbfunc, userptr);
+ if (dsound) {
+ sound->driver_state = dsound;
+ sound->pause = soundout_dsound_pause;
+ sound->delete = soundout_dsound_delete;
+ return sound;
+ }
+ struct waveout_state *waveout = waveout_init(hwnd, srate, sectlen, cbfunc, userptr);
+ if (waveout) {
+ sound->driver_state = waveout;
+ sound->pause = soundout_waveout_pause;
+ sound->delete = soundout_waveout_delete;
+ return sound;
+ }
+ HeapFree(heap, 0, sound);
+ return 0;
+}
diff --git a/win32/soundout.h b/win32/soundout.h
new file mode 100644
index 0000000..edfa501
--- /dev/null
+++ b/win32/soundout.h
@@ -0,0 +1,18 @@
+#ifndef MYON_SOUNDOUT_H_INCLUDED
+#define MYON_SOUNDOUT_H_INCLUDED
+
+#include <stdint.h>
+#include <windows.h>
+
+typedef void (*sound_callback)(void *userdata, int16_t *buf, unsigned frames);
+struct sound_state {
+ void *driver_state;
+ void (*pause)(struct sound_state *state, int pause);
+ void (*delete)(struct sound_state *state);
+ void *userptr;
+};
+
+struct sound_state *sound_init(HWND hwnd, unsigned srate, unsigned sectlen,
+ sound_callback cbfunc, void *userptr);
+
+#endif // MYON_SOUNDOUT_H_INCLUDED
diff --git a/win32/uc.c b/win32/uc.c
new file mode 100644
index 0000000..70d2e85
--- /dev/null
+++ b/win32/uc.c
@@ -0,0 +1,36 @@
+#include <string.h>
+#include <windows.h>
+
+int memcmp(const void *s1, const void *s2, size_t n) {
+ size_t i = RtlCompareMemory(s1, s2, n);
+ if (i == n) return 0;
+ return ((const unsigned char *)s1)[i] - ((const unsigned char *)s2)[i];
+}
+
+void *memset(void *s, int c, size_t n) {
+ RtlFillMemory(s, n, c);
+ return s;
+}
+
+void *memcpy(void *dest, const void *src, size_t n) {
+ RtlCopyMemory(dest, src, n);
+ return dest;
+}
+
+void *memmove(void *dest, const void *src, size_t n) {
+ RtlMoveMemory(dest, src, n);
+ return dest;
+}
+
+int CALLBACK wWinMain(HINSTANCE hinst, HINSTANCE hpinst,
+ wchar_t *cmdline, int cmdshow);
+
+DWORD CALLBACK entry(void *ptr) {
+ (void)ptr;
+ STARTUPINFO si;
+ GetStartupInfo(&si);
+ int cmdshow = si.wShowWindow;
+ if (si.dwFlags & STARTF_USESHOWWINDOW) cmdshow = SW_SHOWNORMAL;
+ DWORD ret = wWinMain(GetModuleHandle(0), 0, 0, cmdshow);
+ ExitProcess(ret);
+}
diff --git a/win32/waveout.c b/win32/waveout.c
new file mode 100644
index 0000000..9c96483
--- /dev/null
+++ b/win32/waveout.c
@@ -0,0 +1,100 @@
+#include "waveout.h"
+
+struct waveout_state {
+ HWAVEOUT wo;
+ DWORD firstout;
+ sound_callback cbfunc;
+ void *userptr;
+ WAVEHDR waveheaders[4];
+ int16_t *wavebuf;
+};
+
+static void CALLBACK waveout_cbproc(HWAVEOUT wo, UINT msg,
+ DWORD_PTR instance,
+ DWORD_PTR p1, DWORD_PTR p2) {
+ (void)wo;
+ (void)p2;
+ if (msg != WOM_DONE) return;
+ struct waveout_state *waveout = (struct waveout_state *)instance;
+ WAVEHDR *wh = (WAVEHDR *)p1;
+ waveout->cbfunc(waveout->userptr, (int16_t *)wh->lpData, wh->dwBufferLength/4);
+ waveOutWrite(waveout->wo, wh, sizeof(*wh));
+}
+
+struct waveout_state *waveout_init(HWND hwnd, unsigned srate, unsigned sectlen,
+ sound_callback cbfunc, void *userptr) {
+ HANDLE heap = GetProcessHeap();
+ struct waveout_state *waveout = HeapAlloc(heap, HEAP_ZERO_MEMORY,
+ sizeof(struct waveout_state));
+ if (!waveout) {
+ MessageBoxW(hwnd, L"cannot allocate memory for WaveOut", L"Error", MB_ICONSTOP);
+ goto err;
+ }
+ waveout->wavebuf = HeapAlloc(heap, HEAP_ZERO_MEMORY, sectlen*4);
+ if (!waveout->wavebuf) {
+ MessageBoxW(hwnd, L"cannot allocate buffer memory for WaveOut", L"Error", MB_ICONSTOP);
+ goto err_waveout;
+ }
+ WAVEFORMATEX format;
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ format.nChannels = 2;
+ format.nSamplesPerSec = srate;
+ format.wBitsPerSample = 16;
+ format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
+ format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+ format.cbSize = 0;
+ HRESULT hr;
+ hr = waveOutOpen(&waveout->wo, WAVE_MAPPER, &format,
+ (DWORD_PTR)waveout_cbproc, (DWORD_PTR)waveout, CALLBACK_FUNCTION);
+ if (hr != MMSYSERR_NOERROR) {
+ MessageBoxW(hwnd, L"cannot WaveOutOpen", L"Error", MB_ICONSTOP);
+ goto err_wavebuf;
+ }
+ for (int i = 0; i < 4; i++) {
+ WAVEHDR *wh = &waveout->waveheaders[i];
+ wh->lpData = ((char *)waveout->wavebuf) + sectlen*i;
+ wh->dwBufferLength = sectlen;
+ wh->dwFlags = 0;
+ waveOutPrepareHeader(waveout->wo, wh, sizeof(*wh));
+ }
+ waveout->firstout = 1;
+ waveout->cbfunc = cbfunc;
+ waveout->userptr = userptr;
+ return waveout;
+
+err_wavebuf:
+ HeapFree(heap, 0, waveout->wavebuf);
+err_waveout:
+ HeapFree(heap, 0, waveout);
+err:
+ return 0;
+}
+
+void waveout_pause(struct waveout_state *waveout, int pause) {
+ if (pause) {
+ waveOutPause(waveout->wo);
+ } else {
+ if (waveout->firstout) {
+ waveout->firstout = 0;
+ for (int i = 0; i < 4; i++) {
+ WAVEHDR *wh = &waveout->waveheaders[i];
+ waveout->cbfunc(waveout->userptr, (int16_t *)wh->lpData, wh->dwBufferLength/4);
+ waveOutWrite(waveout->wo, wh, sizeof(*wh));
+ }
+ } else {
+ waveOutRestart(waveout->wo);
+ }
+ }
+}
+
+void waveout_delete(struct waveout_state *waveout) {
+ if (!waveout) return;
+ waveout_pause(waveout, 1);
+ for (int i = 0; i < 4; i++) {
+ WAVEHDR *wh = &waveout->waveheaders[i];
+ waveOutUnprepareHeader(waveout->wo, wh, sizeof(*wh));
+ }
+ HANDLE heap = GetProcessHeap();
+ HeapFree(heap, 0, waveout->wavebuf);
+ HeapFree(heap, 0, waveout);
+}
diff --git a/win32/waveout.h b/win32/waveout.h
new file mode 100644
index 0000000..ff33b01
--- /dev/null
+++ b/win32/waveout.h
@@ -0,0 +1,13 @@
+#ifndef MYON_WAVEOUT_H_INCLUDED
+#define MYON_WAVEOUT_H_INCLUDED
+
+#include "soundout.h"
+
+struct waveout_state;
+
+struct waveout_state *waveout_init(HWND hwnd, unsigned srate, unsigned sectlen,
+ sound_callback cbfunc, void *userptr);
+void waveout_delete(struct waveout_state *waveout);
+void waveout_pause(struct waveout_state *waveout, int pause);
+
+#endif // MYON_WAVEOUT_H_INCLUDED
diff --git a/win32/x86/Makefile b/win32/x86/Makefile
new file mode 100644
index 0000000..e273569
--- /dev/null
+++ b/win32/x86/Makefile
@@ -0,0 +1,43 @@
+vpath %.c ../
+vpath %.c ../../fmdriver
+vpath %.c ../../libopna
+vpath %.c ../../fmdsp
+vpath %.rc ..
+TARGET=fmplayer.exe
+FMDRIVER_OBJS=fmdriver_fmp.o \
+ ppz8.o
+LIBOPNA_OBJS=opna.o \
+ opnatimer.o \
+ opnafm.o \
+ opnassg.o \
+ opnadrum.o \
+ opnaadpcm.o
+FMDSP_OBJS=fmdsp.o
+OBJS=main.o dsoundout.o soundout.o waveout.o uc.o lnf.o \
+ $(FMDRIVER_OBJS) \
+ $(LIBOPNA_OBJS) \
+ $(FMDSP_OBJS)
+ARCH=i686
+PREFIX=$(ARCH)-w64-mingw32-
+CC=$(PREFIX)gcc
+WINDRES=$(PREFIX)windres
+STRIP=$(PREFIX)strip
+CFLAGS=-std=c99 -Os -Wall -Wextra -pedantic -I../.. \
+ -DUNICODE -D_UNICODE \
+ -DWINVER=0x0500 -D_WIN32_WINNT=0x0500 \
+ -march=i586
+LIBS=-nostdlib -s -Wl,-e_entry@4 \
+ -Wl,--subsystem,windows \
+ -lgcc -lntdll \
+ -luser32 -lkernel32 -lole32 -ldxguid -luuid -lcomdlg32 \
+ -lgdi32 -lshlwapi -lwinmm -lshell32
+
+$(TARGET): $(OBJS)
+ $(CC) -o $@ $(OBJS) $(LIBS)
+ $(STRIP) $@
+
+%.o: %.rc
+ $(WINDRES) -o $@ -i $<
+
+clean:
+ rm -f $(TARGET) $(OBJS)