diff options
Diffstat (limited to 'win32')
-rw-r--r-- | win32/amd64/Makefile | 42 | ||||
-rw-r--r-- | win32/dsoundout.c | 180 | ||||
-rw-r--r-- | win32/dsoundout.h | 13 | ||||
-rw-r--r-- | win32/fmplayer.ico | bin | 0 -> 318 bytes | |||
-rw-r--r-- | win32/lnf.manifest | 10 | ||||
-rw-r--r-- | win32/lnf.rc | 5 | ||||
-rw-r--r-- | win32/main.c | 484 | ||||
-rw-r--r-- | win32/soundout.c | 44 | ||||
-rw-r--r-- | win32/soundout.h | 18 | ||||
-rw-r--r-- | win32/uc.c | 36 | ||||
-rw-r--r-- | win32/waveout.c | 100 | ||||
-rw-r--r-- | win32/waveout.h | 13 | ||||
-rw-r--r-- | win32/x86/Makefile | 43 |
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 Binary files differnew file mode 100644 index 0000000..801126c --- /dev/null +++ b/win32/fmplayer.ico 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) |