aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/fmplayer_common.h17
-rw-r--r--common/fmplayer_drumrom_unix.c53
-rw-r--r--common/fmplayer_drumrom_win.c49
-rw-r--r--common/fmplayer_file.c113
-rw-r--r--common/fmplayer_file.h4
-rw-r--r--common/fmplayer_file_gio.c4
-rw-r--r--common/fmplayer_file_unix.c127
-rw-r--r--common/fmplayer_file_win.c2
-rw-r--r--common/fmplayer_work_opna.c63
-rw-r--r--fmdriver/fmdriver.h5
-rw-r--r--fmdriver/fmdriver_fmp.c3
-rw-r--r--fmdriver/fmdriver_pmd.c5
-rw-r--r--gtk/Makefile.am7
-rw-r--r--gtk/configure.ac2
-rw-r--r--gtk/fmplayer.xpm26
-rw-r--r--gtk/fmplayer32.xpm46
-rw-r--r--gtk/main.c112
-rw-r--r--gtk/toneview.c2
-rw-r--r--gtk/wavesave.c256
-rw-r--r--gtk/wavesave.h8
-rw-r--r--win32/fmplayer.mak4
-rw-r--r--win32/main.c118
-rw-r--r--win32/wavesave.c210
-rw-r--r--win32/wavesave.h9
-rw-r--r--win32/wavewrite.c90
-rw-r--r--win32/wavewrite.h15
26 files changed, 1169 insertions, 181 deletions
diff --git a/common/fmplayer_common.h b/common/fmplayer_common.h
index 5394293..c4f58af 100644
--- a/common/fmplayer_common.h
+++ b/common/fmplayer_common.h
@@ -2,7 +2,24 @@
#define MYON_FMPLAYER_COMMON_H_INCLUDED
#include <stddef.h>
+#include <stdbool.h>
void *fmplayer_load_data(const char *name, size_t size);
+struct fmdriver_work;
+struct ppz8;
+struct opna;
+struct opna_timer;
+void fmplayer_init_work_opna(
+ struct fmdriver_work *work,
+ struct ppz8 *ppz8,
+ struct opna *opna,
+ struct opna_timer *timer,
+ void *adpcm_ram
+);
+
+struct opna_drum;
+bool fmplayer_drum_rom_load(struct opna_drum *drum);
+bool fmplayer_drum_loaded(void);
+
#endif // MYON_FMPLAYER_COMMON_H_INCLUDED
diff --git a/common/fmplayer_drumrom_unix.c b/common/fmplayer_drumrom_unix.c
new file mode 100644
index 0000000..791b264
--- /dev/null
+++ b/common/fmplayer_drumrom_unix.c
@@ -0,0 +1,53 @@
+#include "fmplayer_common.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libopna/opnadrum.h"
+
+static struct {
+ uint8_t drum_rom[OPNA_ROM_SIZE];
+ bool loaded;
+} g;
+
+#define DATADIR "/.local/share/fmplayer/"
+
+void loadfile(void) {
+ const char *path = "ym2608_adpcm_rom.bin";
+ const char *home = getenv("HOME");
+ char *dpath = 0;
+ if (home) {
+ const char *datadir = DATADIR;
+ dpath = malloc(strlen(home)+strlen(datadir)+strlen(path) + 1);
+ if (dpath) {
+ strcpy(dpath, home);
+ strcat(dpath, datadir);
+ strcat(dpath, path);
+ path = dpath;
+ }
+ }
+ FILE *rhythm = fopen(path, "r");
+ free(dpath);
+ if (!rhythm) goto err;
+ if (fseek(rhythm, 0, SEEK_END) != 0) goto err_file;
+ long size = ftell(rhythm);
+ if (size != OPNA_ROM_SIZE) goto err_file;
+ if (fseek(rhythm, 0, SEEK_SET) != 0) goto err_file;
+ if (fread(g.drum_rom, 1, OPNA_ROM_SIZE, rhythm) != OPNA_ROM_SIZE) goto err_file;
+ fclose(rhythm);
+ g.loaded = true;
+ return;
+err_file:
+ fclose(rhythm);
+err:
+ return;
+}
+
+bool fmplayer_drum_rom_load(struct opna_drum *drum) {
+ if (!g.loaded) {
+ loadfile();
+ }
+ if (g.loaded) {
+ opna_drum_set_rom(drum, g.drum_rom);
+ }
+ return g.loaded;
+}
diff --git a/common/fmplayer_drumrom_win.c b/common/fmplayer_drumrom_win.c
new file mode 100644
index 0000000..aab4546
--- /dev/null
+++ b/common/fmplayer_drumrom_win.c
@@ -0,0 +1,49 @@
+#include "fmplayer_common.h"
+#include "libopna/opnadrum.h"
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <shlwapi.h>
+static struct {
+ char drum_rom[OPNA_ROM_SIZE];
+ bool loaded;
+} g;
+
+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;
+ DWORD readbytes;
+ if (!ReadFile(file, g.drum_rom, OPNA_ROM_SIZE, &readbytes, 0)
+ || readbytes != OPNA_ROM_SIZE) goto err;
+ CloseHandle(file);
+ g.loaded = true;
+ return;
+err:
+ if (file != INVALID_HANDLE_VALUE) CloseHandle(file);
+ return;
+}
+
+
+bool fmplayer_drum_rom_load(struct opna_drum *drum) {
+ if (!g.loaded) loadrom();
+ if (g.loaded) {
+ opna_drum_set_rom(drum, g.drum_rom);
+ }
+ return g.loaded;
+}
+
+bool fmplayer_drum_loaded(void) {
+ return g.loaded;
+}
diff --git a/common/fmplayer_file.c b/common/fmplayer_file.c
index 1080625..3c05cab 100644
--- a/common/fmplayer_file.c
+++ b/common/fmplayer_file.c
@@ -12,6 +12,92 @@ void fmplayer_file_free(const struct fmplayer_file *fmfileptr) {
free(fmfile);
}
+static void opna_writereg_dummy(struct fmdriver_work *work, unsigned addr, unsigned data) {
+}
+
+static unsigned opna_readreg_dummy(struct fmdriver_work *work, unsigned addr) {
+ return 0xff;
+}
+
+struct dummy_opna {
+ uint32_t timerb_loop;
+ uint8_t loopcnt;
+};
+
+static uint8_t opna_status_dummy(struct fmdriver_work *work, bool a1) {
+ struct dummy_opna *opna = work->opna;
+ if (!opna->timerb_loop) {
+ if (work->loop_cnt >= opna->loopcnt) {
+ opna->timerb_loop = work->timerb_cnt;
+ } else if (work->timerb_cnt > 0xfffff) {
+ opna->timerb_loop = -1;
+ }
+ }
+ return opna->timerb_loop ? 0 : 2;
+}
+
+static void dummy_work_init(struct fmdriver_work *work, struct dummy_opna *dopna) {
+ work->opna_writereg = opna_writereg_dummy;
+ work->opna_readreg = opna_readreg_dummy;
+ work->opna_status = opna_status_dummy;
+ work->opna = dopna;
+}
+
+static struct driver_pmd *pmd_dup(const struct driver_pmd *pmd) {
+ struct driver_pmd *pmddup = malloc(sizeof(*pmddup));
+ if (!pmddup) return 0;
+ memcpy(pmddup, pmd, sizeof(*pmd));
+ size_t datalen = pmddup->datalen+1;
+ const uint8_t *data = pmddup->data-1;
+ uint8_t *datadup = malloc(datalen);
+ if (!datadup) {
+ free(pmddup);
+ return 0;
+ }
+ memcpy(datadup, data, datalen);
+ pmddup->data = datadup+1;
+ pmddup->datalen = datalen-1;
+ return pmddup;
+}
+
+static void pmd_free(struct driver_pmd *pmd) {
+ if (pmd) {
+ free(pmd->data-1);
+ free(pmd);
+ }
+}
+
+static struct driver_fmp *fmp_dup(const struct driver_fmp *fmp) {
+ struct driver_fmp *fmpdup = malloc(sizeof(*fmpdup));
+ if (!fmpdup) return 0;
+ memcpy(fmpdup, fmp, sizeof(*fmp));
+ fmpdup->data = malloc(fmp->datalen);
+ if (!fmpdup->data) {
+ free(fmpdup);
+ return 0;
+ }
+ memcpy((void*)fmpdup->data, fmp->data, fmp->datalen);
+ return fmpdup;
+}
+
+static void fmp_free(struct driver_fmp *fmp) {
+ if (fmp) {
+ free((void*)fmp->data);
+ free(fmp);
+ }
+}
+
+static void calc_loop(struct fmdriver_work *work, int loopcnt) {
+ if ((loopcnt < 1) || (0xff < loopcnt)) {
+ work->loop_timerb_cnt = -1;
+ return;
+ }
+ struct dummy_opna *opna = work->opna;
+ opna->loopcnt = loopcnt;
+ while (!opna->timerb_loop) work->driver_opna_interrupt(work);
+ work->loop_timerb_cnt = opna->timerb_loop;
+}
+
struct fmplayer_file *fmplayer_file_alloc(const void *path, enum fmplayer_file_error *error) {
struct fmplayer_file *fmfile = calloc(1, sizeof(*fmfile));
if (!fmfile) {
@@ -116,9 +202,21 @@ static void loadfmpppz(struct fmdriver_work *work, struct fmplayer_file *fmfile)
fmfile->fmp_ppz_err = !loadppzpvi(work, fmfile, pvifile);
}
-void fmplayer_file_load(struct fmdriver_work *work, struct fmplayer_file *fmfile) {
+void fmplayer_file_load(struct fmdriver_work *work, struct fmplayer_file *fmfile, int loopcnt) {
+ struct dummy_opna dopna = {0};
+ struct fmdriver_work dwork = {0};
switch (fmfile->type) {
case FMPLAYER_FILE_TYPE_PMD:
+ {
+ struct driver_pmd *pmddup = pmd_dup(&fmfile->driver.pmd);
+ if (pmddup) {
+ dummy_work_init(&dwork, &dopna);
+ pmd_init(&dwork, pmddup);
+ calc_loop(&dwork, loopcnt);
+ pmd_free(pmddup);
+ work->loop_timerb_cnt = dwork.loop_timerb_cnt;
+ }
+ }
pmd_init(work, &fmfile->driver.pmd);
loadppc(work, fmfile);
loadpmdppz(work, fmfile);
@@ -126,6 +224,16 @@ void fmplayer_file_load(struct fmdriver_work *work, struct fmplayer_file *fmfile
work->pcmerror[1] = fmfile->pmd_ppz_err;
break;
case FMPLAYER_FILE_TYPE_FMP:
+ {
+ struct driver_fmp *fmpdup = fmp_dup(&fmfile->driver.fmp);
+ if (fmpdup) {
+ dummy_work_init(&dwork, &dopna);
+ fmp_init(&dwork, fmpdup);
+ calc_loop(&dwork, loopcnt);
+ fmp_free(fmpdup);
+ work->loop_timerb_cnt = dwork.loop_timerb_cnt;
+ }
+ }
fmp_init(work, &fmfile->driver.fmp);
loadpvi(work, fmfile);
loadfmpppz(work, fmfile);
@@ -139,6 +247,7 @@ void fmplayer_file_load(struct fmdriver_work *work, struct fmplayer_file *fmfile
#define MSG_FILE_ERR_FILEIO "File I/O error"
#define MSG_FILE_ERR_BADFILE_SIZE "Invalid file size"
#define MSG_FILE_ERR_BADFILE "Invalid file format"
+#define MSG_FILE_ERR_NOTFOUND "File not found"
#define XWIDE(x) L ## x
#define WIDE(x) XWIDE(x)
@@ -151,6 +260,7 @@ const char *fmplayer_file_strerror(enum fmplayer_file_error error) {
MSG_FILE_ERR_FILEIO,
MSG_FILE_ERR_BADFILE_SIZE,
MSG_FILE_ERR_BADFILE,
+ MSG_FILE_ERR_NOTFOUND,
};
return errtable[error];
}
@@ -163,6 +273,7 @@ const wchar_t *fmplayer_file_strerror_w(enum fmplayer_file_error error) {
WIDE(MSG_FILE_ERR_FILEIO),
WIDE(MSG_FILE_ERR_BADFILE_SIZE),
WIDE(MSG_FILE_ERR_BADFILE),
+ WIDE(MSG_FILE_ERR_NOTFOUND),
};
return errtable[error];
}
diff --git a/common/fmplayer_file.h b/common/fmplayer_file.h
index fd7c53e..143c349 100644
--- a/common/fmplayer_file.h
+++ b/common/fmplayer_file.h
@@ -5,6 +5,7 @@
#include "fmdriver/fmdriver.h"
#include "fmdriver/fmdriver_pmd.h"
#include "fmdriver/fmdriver_fmp.h"
+#include "libopna/opnadrum.h"
enum fmplayer_file_type {
FMPLAYER_FILE_TYPE_PMD,
@@ -17,6 +18,7 @@ enum fmplayer_file_error {
FMPLAYER_FILE_ERR_FILEIO,
FMPLAYER_FILE_ERR_BADFILE_SIZE,
FMPLAYER_FILE_ERR_BADFILE,
+ FMPLAYER_FILE_ERR_NOTFOUND,
FMPLAYER_FILE_ERR_COUNT
};
@@ -36,7 +38,7 @@ struct fmplayer_file {
};
struct fmplayer_file *fmplayer_file_alloc(const void *path, enum fmplayer_file_error *error);
void fmplayer_file_free(const struct fmplayer_file *fmfile);
-void fmplayer_file_load(struct fmdriver_work *work, struct fmplayer_file *fmfile);
+void fmplayer_file_load(struct fmdriver_work *work, struct fmplayer_file *fmfile, int loopcnt);
const char *fmplayer_file_strerror(enum fmplayer_file_error error);
const wchar_t *fmplayer_file_strerror_w(enum fmplayer_file_error error);
diff --git a/common/fmplayer_file_gio.c b/common/fmplayer_file_gio.c
index 5e9dd84..a2b52d2 100644
--- a/common/fmplayer_file_gio.c
+++ b/common/fmplayer_file_gio.c
@@ -27,7 +27,7 @@ static void *fileread(GFile *f, size_t maxsize, size_t *filesize, enum fmplayer_
}
fstream = g_file_read(f, 0, 0);
if (!fstream) {
- if (error) *error = FMPLAYER_FILE_ERR_FILEIO;
+ if (error) *error = FMPLAYER_FILE_ERR_NOTFOUND;
goto err;
}
buf = malloc(filelen);
@@ -107,7 +107,7 @@ void *fmplayer_fileread(const void *pathptr, const char *pcmname, const char *ex
g_object_unref(G_OBJECT(pcmfile));
g_object_unref(G_OBJECT(info));
}
- if (error) *error = FMPLAYER_FILE_ERR_FILEIO;
+ if (error) *error = FMPLAYER_FILE_ERR_NOTFOUND;
err:
if (direnum) g_object_unref(G_OBJECT(direnum));
if (dir) g_object_unref(G_OBJECT(dir));
diff --git a/common/fmplayer_file_unix.c b/common/fmplayer_file_unix.c
new file mode 100644
index 0000000..d2855c3
--- /dev/null
+++ b/common/fmplayer_file_unix.c
@@ -0,0 +1,127 @@
+
+#define _POSIX_C_SOURCE 200809l
+#include "common/fmplayer_file.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <string.h>
+#include <strings.h>
+
+static void *fileread(const char *path, size_t maxsize, size_t *filesize, enum fmplayer_file_error *error) {
+ FILE *f = 0;
+ void *buf = 0;
+
+ f = fopen(path, "rb");
+ if (!f) {
+ if (error) *error = FMPLAYER_FILE_ERR_NOTFOUND;
+ goto err;
+ }
+ if (fseek(f, 0, SEEK_END)) {
+ *error = FMPLAYER_FILE_ERR_FILEIO;
+ goto err;
+ }
+ size_t fsize;
+ {
+ long ssize = ftell(f);
+ if (ssize < 0) {
+ *error = FMPLAYER_FILE_ERR_FILEIO;
+ goto err;
+ }
+ if (maxsize && ((size_t)ssize > maxsize)) {
+ *error = FMPLAYER_FILE_ERR_BADFILE_SIZE;
+ goto err;
+ }
+ fsize = ssize;
+ }
+ if (fseek(f, 0, SEEK_SET)) {
+ *error = FMPLAYER_FILE_ERR_FILEIO;
+ goto err;
+ }
+ buf = malloc(fsize);
+ if (!buf) {
+ if (error) *error = FMPLAYER_FILE_ERR_NOMEM;
+ goto err;
+ }
+ if (fread(buf, 1, fsize, f) != fsize) {
+ *error = FMPLAYER_FILE_ERR_FILEIO;
+ goto err;
+ }
+ fclose(f);
+ *filesize = fsize;
+ return buf;
+err:
+ free(buf);
+ if (f) fclose(f);
+ return 0;
+}
+
+void *fmplayer_fileread(const void *pathptr, const char *pcmname, const char *extension,
+ size_t maxsize, size_t *filesize, enum fmplayer_file_error *error) {
+ const char *path = pathptr;
+ if (!pcmname) return fileread(path, maxsize, filesize, error);
+
+ char *namebuf = 0;
+ char *dirbuf = 0;
+ DIR *d = 0;
+
+ if (extension) {
+ size_t namebuflen = strlen(pcmname) + strlen(extension) + 1;
+ namebuf = malloc(namebuflen);
+ if (!namebuf) {
+ if (error) *error = FMPLAYER_FILE_ERR_NOMEM;
+ goto err;
+ }
+ strcpy(namebuf, pcmname);
+ strcat(namebuf, extension);
+ pcmname = namebuf;
+ }
+
+ const char *slash = strrchr(path, '/');
+ const char *dirpath = 0;
+ if (slash) {
+ dirbuf = strdup(path);
+ if (!dirbuf) {
+ if (error) *error = FMPLAYER_FILE_ERR_NOMEM;
+ goto err;
+ }
+ *strrchr(dirbuf, '/') = 0;
+ dirpath = dirbuf;
+ } else {
+ dirpath = ".";
+ }
+ d = opendir(dirpath);
+ if (!d) {
+ *error = FMPLAYER_FILE_ERR_FILEIO;
+ goto err;
+ }
+ const struct dirent *de;
+ while ((de = readdir(d))) {
+ if (!strcasecmp(de->d_name, pcmname)) {
+ size_t pathlen = strlen(dirpath) + 1 + strlen(de->d_name) + 1;
+ char *pcmpath = malloc(pathlen);
+ if (!pcmpath) {
+ if (error) *error = FMPLAYER_FILE_ERR_NOMEM;
+ goto err;
+ }
+ strcpy(pcmpath, dirpath);
+ strcat(pcmpath, "/");
+ strcat(pcmpath, de->d_name);
+ void *buf = fileread(pcmpath, maxsize, filesize, error);
+ free(pcmpath);
+ closedir(d);
+ free(dirbuf);
+ free(namebuf);
+ return buf;
+ }
+ }
+ if (error) *error = FMPLAYER_FILE_ERR_NOTFOUND;
+err:
+ if (d) closedir(d);
+ free(dirbuf);
+ free(namebuf);
+ return 0;
+}
+
+void *fmplayer_path_dup(const void *path) {
+ return strdup(path);
+}
diff --git a/common/fmplayer_file_win.c b/common/fmplayer_file_win.c
index 3397bcc..2910953 100644
--- a/common/fmplayer_file_win.c
+++ b/common/fmplayer_file_win.c
@@ -12,7 +12,7 @@ static void *fileread(const wchar_t *path,
void *buf = 0;
file = CreateFile(path, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (file == INVALID_HANDLE_VALUE) {
- if (error) *error = FMPLAYER_FILE_ERR_FILEIO;
+ if (error) *error = FMPLAYER_FILE_ERR_NOTFOUND;
goto err;
}
LARGE_INTEGER li;
diff --git a/common/fmplayer_work_opna.c b/common/fmplayer_work_opna.c
new file mode 100644
index 0000000..b3b0c69
--- /dev/null
+++ b/common/fmplayer_work_opna.c
@@ -0,0 +1,63 @@
+#include "fmplayer_common.h"
+#include "fmdriver/fmdriver.h"
+#include "fmdriver/ppz8.h"
+#include "libopna/opna.h"
+#include "libopna/opnatimer.h"
+#include <string.h>
+
+enum {
+ SRATE = 55467,
+ PPZ8MIX = 0xa000,
+};
+
+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 unsigned opna_readreg_libopna(struct fmdriver_work *work, unsigned addr) {
+ struct opna_timer *timer = (struct opna_timer *)work->opna;
+ return opna_readreg(timer->opna, addr);
+}
+
+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 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);
+}
+
+void fmplayer_init_work_opna(
+ struct fmdriver_work *work,
+ struct ppz8 *ppz8,
+ struct opna *opna,
+ struct opna_timer *timer,
+ void *adpcm_ram
+) {
+ opna_reset(opna);
+ fmplayer_drum_rom_load(&opna->drum);
+ opna_adpcm_set_ram_256k(&opna->adpcm, adpcm_ram);
+ opna_timer_reset(timer, opna);
+ ppz8_init(ppz8, SRATE, PPZ8MIX);
+ memset(work, 0, sizeof(*work));
+ work->opna_writereg = opna_writereg_libopna;
+ work->opna_readreg = opna_readreg_libopna;
+ work->opna_status = opna_status_libopna;
+ work->opna = timer;
+ work->ppz8 = ppz8;
+ work->ppz8_functbl = &ppz8_functbl;
+ opna_timer_set_int_callback(timer, opna_int_cb, work);
+ opna_timer_set_mix_callback(timer, opna_mix_cb, ppz8);
+}
diff --git a/fmdriver/fmdriver.h b/fmdriver/fmdriver.h
index 9f3afee..aebe27e 100644
--- a/fmdriver/fmdriver.h
+++ b/fmdriver/fmdriver.h
@@ -100,6 +100,11 @@ struct fmdriver_work {
bool pcmerror[2];
uint8_t ssg_noise_freq;
struct fmdriver_track_status track_status[FMDRIVER_TRACK_NUM];
+ uint8_t loop_cnt;
+ // current timerb count
+ uint32_t timerb_cnt;
+ // loop length
+ uint32_t loop_timerb_cnt;
// fm3ex part map
};
diff --git a/fmdriver/fmdriver_fmp.c b/fmdriver/fmdriver_fmp.c
index cc6f1a6..41d0a48 100644
--- a/fmdriver/fmdriver_fmp.c
+++ b/fmdriver/fmdriver_fmp.c
@@ -875,6 +875,7 @@ static bool fmp_cmd74_loop(struct fmdriver_work *work,
}
// 248c
fmp->loop_cnt++;
+ work->loop_cnt = fmp->loop_cnt;
fmp->part_loop_bit = fmp->part_playing_bit;
// al=2; 1b64();
}
@@ -891,6 +892,7 @@ static bool fmp_cmd74_loop(struct fmdriver_work *work,
// 3e16();
fmp->status.stopped = true;
fmp->status.looped = true;
+ work->loop_cnt = -1;
}
// 24f0
if (!part->type.rhythm) {
@@ -3148,6 +3150,7 @@ static void fmp_opna_interrupt(struct fmdriver_work *work) {
struct driver_fmp *fmp = (struct driver_fmp *)work->driver;
if (work->opna_status(work, 0) & 0x02) {
fmp_timerb(work, fmp);
+ work->timerb_cnt++;
}
}
diff --git a/fmdriver/fmdriver_pmd.c b/fmdriver/fmdriver_pmd.c
index 7090fc8..14459a5 100644
--- a/fmdriver/fmdriver_pmd.c
+++ b/fmdriver/fmdriver_pmd.c
@@ -5486,6 +5486,9 @@ static void pmd_proc_parts(
struct fmdriver_work *work,
struct driver_pmd *pmd
) {
+ pmd->loop.looped = true;
+ pmd->loop.ended = true;
+ pmd->loop.env = false;
if (!pmd->opm_flag) {
for (int c = 0; c < 3; c++) {
pmd->proc_ch = c+1;
@@ -5538,6 +5541,7 @@ static void pmd_proc_parts(
} else {
pmd->status2 = 0xff;
}
+ work->loop_cnt = pmd->status2;
}
// 3e2e
@@ -5623,6 +5627,7 @@ static void pmd_timer(
}
if (status & 2) {
pmd_timerb(work, pmd);
+ work->timerb_cnt++;
}
}
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 3524f15..fb06655 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -22,8 +22,8 @@ FMDSP_SRC=../fmdsp/fmdsp.c \
#CFLAGS=
fmplayer_CPPFLAGS=-Wall -Wextra -pedantic \
-I.. \
- $(GTK3_CFLAGS) $(PORTAUDIO_CFLAGS)
-fmplayer_LDADD=$(GTK3_LIBS) $(PORTAUDIO_LIBS)
+ $(GTK3_CFLAGS) $(PORTAUDIO_CFLAGS) $(SNDFILE_CFLAGS)
+fmplayer_LDADD=$(GTK3_LIBS) $(PORTAUDIO_LIBS) $(SNDFILE_LIBS)
if ENABLE_NEON
LIBOPNA_SRC+=../libopna/opnassg-sinc-neon.s
@@ -42,9 +42,12 @@ endif
fmplayer_SOURCES=main.c \
toneview.c \
+ wavesave.c \
../tonedata/tonedata.c \
../common/fmplayer_file.c \
../common/fmplayer_file_gio.c \
+ ../common/fmplayer_work_opna.c \
+ ../common/fmplayer_drumrom_unix.c \
$(LIBOPNA_SRC) \
$(FMDRIVER_SRC) \
$(FMDSP_SRC)
diff --git a/gtk/configure.ac b/gtk/configure.ac
index 79d02c1..cab8553 100644
--- a/gtk/configure.ac
+++ b/gtk/configure.ac
@@ -6,9 +6,9 @@ AC_PROG_RANLIB
AM_PROG_AR
AM_PROG_AS
-dnl AM_PATH_SDL2([2.0.5])
PKG_CHECK_MODULES([PORTAUDIO], [portaudio-2.0])
PKG_CHECK_MODULES([GTK3], [gtk+-3.0 cairo])
+PKG_CHECK_MODULES([SNDFILE], [sndfile])
AC_ARG_ENABLE([neon], AS_HELP_STRING([--enable-neon], [Enable NEON optimized functions for SSG sinc filtering and fmdsp palette lookup. Tested on Cortex-A53 (Raspberry PI 3)]))
AM_CONDITIONAL([ENABLE_NEON], [test "x$enable_neon" = "xyes"])
diff --git a/gtk/fmplayer.xpm b/gtk/fmplayer.xpm
new file mode 100644
index 0000000..d52d9f4
--- /dev/null
+++ b/gtk/fmplayer.xpm
@@ -0,0 +1,26 @@
+/* XPM */
+static char *fmplayer_xpm_16[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 4 1 ",
+" c #40A040",
+". c #C0C0C0",
+"X c gray25",
+"o c white",
+/* pixels */
+" ",
+" .. .. .. .. .. ",
+" .. .. .. .. .. ",
+"XXXXXXXXXXXXXXXX",
+"oXoXoXoXXXXXXXXX",
+"oXoXoooXXXXXXXXX",
+"XoXXoXoXXXXXXXXX",
+"XoXXoXoXXXXXXXXX",
+"XoXXoXoXXXXXXXXX",
+"XXXXXXXXXXXXXXXX",
+"XoXXXooXXoXXXoXX",
+"oXoXoXXXoXoXoXoX",
+"XXoXooXXoXoXXoXX",
+"XoXXoXoXoXoXoXoX",
+"oooXXoXXXoXXXoXX",
+"XXXXXXXXXXXXXXXX"
+};
diff --git a/gtk/fmplayer32.xpm b/gtk/fmplayer32.xpm
new file mode 100644
index 0000000..062d12e
--- /dev/null
+++ b/gtk/fmplayer32.xpm
@@ -0,0 +1,46 @@
+/* XPM */
+static char *fmplayer_xpm_32[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 8 1 ",
+" c gray25",
+". c #606060",
+"X c #40A040",
+"o c #808080",
+"O c #A0A0A0",
+"+ c #C0C0C0",
+"@ c #D8D8D8",
+"# c white",
+/* pixels */
+"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+"OOXOOOXOOOXOOOXOOOXOOOXOOOXOOOXO",
+"##X###X###X###X###X###X###X###X#",
+"@@X@@@X@@@X@@@X@@@X@@@X@@@X@@@X@",
+"++X+++X+++X+++X+++X+++X+++X+++X+",
+"................................",
+" ",
+" ",
+" # # # # ",
+" # # ## ## ",
+" # # # # # ",
+" # # # ",
+" # # # ",
+" # # # ",
+" # # # ",
+" # # # ",
+" ",
+" ### ### ### ### ",
+" # # # # # # # # ",
+" # # # # # # ",
+" # #### # # ### ",
+" # # # # # # # ",
+" # # # # # # # ",
+" # # # # # # # ",
+" ##### ### ### ### ",
+" ",
+" ",
+"++X+++X+++X+++X+++X+++X+++X+++X+",
+"++X+++X+++X+++X+++X+++X+++X+++X+",
+"OOXOOOXOOOXOOOXOOOXOOOXOOOXOOOXO",
+"ooXoooXoooXoooXoooXoooXoooXoooXo",
+"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+};
diff --git a/gtk/main.c b/gtk/main.c
index 0ac2ac5..f0d5633 100644
--- a/gtk/main.c
+++ b/gtk/main.c
@@ -17,6 +17,11 @@
#include "toneview.h"
#include "oscillo/oscillo.h"
#include "oscilloview.h"
+#include "wavesave.h"
+#include "common/fmplayer_common.h"
+
+#include "fmplayer.xpm"
+#include "fmplayer32.xpm"
#define DATADIR "/.local/share/fmplayer/"
//#define FMDSP_2X
@@ -42,8 +47,6 @@ static struct {
struct ppz8 ppz8;
struct fmdriver_work work;
struct fmdsp fmdsp;
- char drum_rom[OPNA_ROM_SIZE];
- bool drum_rom_loaded;
char adpcm_ram[OPNA_ADPCM_RAM_SIZE];
struct fmplayer_file *fmfile;
void *data;
@@ -80,6 +83,16 @@ static void on_menu_quit(GtkMenuItem *menuitem, gpointer ptr) {
quit();
}
+static void on_menu_save(GtkMenuItem *menuitem, gpointer ptr) {
+ (void)menuitem;
+ (void)ptr;
+ if (g.current_uri) {
+ char *uri = g_strdup(g.current_uri);
+ wavesave_dialog(GTK_WINDOW(g.mainwin), uri);
+ g_free(uri);
+ }
+}
+
static void on_tone_view(GtkMenuItem *menuitem, gpointer ptr) {
(void)menuitem;
(void)ptr;
@@ -130,67 +143,6 @@ static int pastream_cb(const void *inptr, void *outptr, unsigned long frames,
return paContinue;
}
-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 unsigned opna_readreg_libopna(struct fmdriver_work *work, unsigned addr) {
- (void)work;
- //struct opna_timer *timer = (struct opna_timer *)work->opna;
- return opna_readreg(&g.opna, addr);
-}
-
-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 load_drumrom(void) {
- const char *path = "ym2608_adpcm_rom.bin";
- const char *home = getenv("HOME");
- char *dpath = 0;
- if (home) {
- const char *datadir = DATADIR;
- dpath = malloc(strlen(home)+strlen(datadir)+strlen(path) + 1);
- if (dpath) {
- strcpy(dpath, home);
- strcat(dpath, datadir);
- strcat(dpath, path);
- path = dpath;
- }
- }
- FILE *rhythm = fopen(path, "r");
- free(dpath);
- if (!rhythm) goto err;
- if (fseek(rhythm, 0, SEEK_END) != 0) goto err_file;
- long size = ftell(rhythm);
- if (size != OPNA_ROM_SIZE) goto err_file;
- if (fseek(rhythm, 0, SEEK_SET) != 0) goto err_file;
- if (fread(g.drum_rom, 1, OPNA_ROM_SIZE, rhythm) != OPNA_ROM_SIZE) goto err_file;
- fclose(rhythm);
- g.drum_rom_loaded = true;
- return;
-err_file:
- fclose(rhythm);
-err:
- return;
-}
-
static void load_fontrom(void) {
const char *path = "font.rom";
const char *home = getenv("HOME");
@@ -261,24 +213,10 @@ static bool openfile(const char *uri) {
fmplayer_file_free(g.fmfile);
g.fmfile = fmfile;
unsigned mask = opna_get_mask(&g.opna);
- opna_reset(&g.opna);
+ g.work = (struct fmdriver_work){0};
+ memset(g.adpcm_ram, 0, sizeof(g.adpcm_ram));
+ fmplayer_init_work_opna(&g.work, &g.ppz8, &g.opna, &g.opna_timer, g.adpcm_ram);
opna_set_mask(&g.opna, mask);
- if (!g.drum_rom_loaded) {
- load_drumrom();
- }
- if (g.drum_rom_loaded) {
- opna_drum_set_rom(&g.opna.drum, g.drum_rom);
- }
- opna_adpcm_set_ram_256k(&g.opna.adpcm, g.adpcm_ram);
- ppz8_init(&g.ppz8, SRATE, PPZ8MIX);
- opna_timer_reset(&g.opna_timer, &g.opna);
- memset(&g.work, 0, sizeof(g.work));
- g.work.opna_writereg = opna_writereg_libopna;
- g.work.opna_readreg = opna_readreg_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;
char *disppath = g_filename_from_uri(uri, 0, 0);
if (disppath) {
strncpy(g.work.filename, disppath, sizeof(g.work.filename)-1);
@@ -286,9 +224,7 @@ static bool openfile(const char *uri) {
} else {
strncpy(g.work.filename, uri, sizeof(g.work.filename)-1);
}
- 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);
- fmplayer_file_load(&g.work, g.fmfile);
+ fmplayer_file_load(&g.work, g.fmfile, 1);
fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);
Pa_StartStream(g.pastream);
g.pa_paused = false;
@@ -321,6 +257,9 @@ static GtkWidget *create_menubar() {
GtkWidget *open = gtk_menu_item_new_with_label("Open");
//g_signal_connect(open, "activate", G_CALLBACK(on_menu_open), 0);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), open);
+ GtkWidget *save = gtk_menu_item_new_with_label("Save wavefile");
+ g_signal_connect(save, "activate", G_CALLBACK(on_menu_save), 0);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), save);
GtkWidget *quit = gtk_menu_item_new_with_label("Quit");
g_signal_connect(quit, "activate", G_CALLBACK(on_menu_quit), 0);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), quit);
@@ -544,6 +483,13 @@ int main(int argc, char **argv) {
#endif
load_fontrom();
gtk_init(&argc, &argv);
+ {
+ GList *iconlist = 0;
+ iconlist = g_list_append(iconlist, gdk_pixbuf_new_from_xpm_data(fmplayer_xpm_16));
+ iconlist = g_list_append(iconlist, gdk_pixbuf_new_from_xpm_data(fmplayer_xpm_32));
+ gtk_window_set_default_icon_list(iconlist);
+ g_list_free(iconlist);
+ }
GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g.mainwin = w;
gtk_window_set_resizable(GTK_WINDOW(w), FALSE);
diff --git a/gtk/toneview.c b/gtk/toneview.c
index 614130b..64c4e98 100644
--- a/gtk/toneview.c
+++ b/gtk/toneview.c
@@ -42,7 +42,7 @@ gboolean tick_cb(GtkWidget *widget, GdkFrameClock *clock, gpointer ptr) {
tonedata_ch_normalize_tl(&g.tonedata_n.ch[c]);
}
if (g.format != g.format_disp ||
- fmplayer_tonedata_channel_isequal(&g.tonedata_n.ch[c], &g.tonedata_n_disp.ch[c])) {
+ !fmplayer_tonedata_channel_isequal(&g.tonedata_n.ch[c], &g.tonedata_n_disp.ch[c])) {
g.tonedata_n_disp.ch[c] = g.tonedata_n.ch[c];
tonedata_ch_string(g.format, g.strbuf, &g.tonedata_n.ch[c], 0);
gtk_label_set_text(GTK_LABEL(g.label[c]), g.strbuf);
diff --git a/gtk/wavesave.c b/gtk/wavesave.c
new file mode 100644
index 0000000..0af656f
--- /dev/null
+++ b/gtk/wavesave.c
@@ -0,0 +1,256 @@
+#include "wavesave.h"
+#include "common/fmplayer_file.h"
+#include <sndfile.h>
+#include <string.h>
+#include "libopna/opna.h"
+#include "libopna/opnatimer.h"
+#include "common/fmplayer_common.h"
+
+enum {
+ SRATE = 55467,
+};
+
+static void msgbox_err(GtkWindow *parent, const char *msg) {
+ GtkWidget *d = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ msg);
+ gtk_dialog_run(GTK_DIALOG(d));
+ gtk_widget_destroy(d);
+}
+
+static sf_count_t gio_get_filelen(void *ptr) {
+ (void)ptr;
+ return -1;
+}
+
+static sf_count_t gio_seek(sf_count_t offset, int whence, void *ptr) {
+ GFileOutputStream *os = ptr;
+ GSeekType st;
+ switch (whence) {
+ default:
+ st = G_SEEK_CUR;
+ break;
+ case SEEK_SET:
+ st = G_SEEK_SET;
+ break;
+ case SEEK_END:
+ st = G_SEEK_END;
+ break;
+ }
+ if (!g_seekable_seek(G_SEEKABLE(os), offset, st, 0, 0)) return -1;
+ return g_seekable_tell(G_SEEKABLE(os));
+}
+
+static sf_count_t gio_read(void *buf, sf_count_t count, void *ptr) {
+ (void)buf;
+ (void)count;
+ (void)ptr;
+ return -1;
+}
+
+static sf_count_t gio_write(const void *buf, sf_count_t count, void *ptr) {
+ GFileOutputStream *os = ptr;
+ return g_output_stream_write(G_OUTPUT_STREAM(os), buf, count, 0, 0);
+}
+
+static sf_count_t gio_tell(void *ptr) {
+ GFileOutputStream *os = ptr;
+ return g_seekable_tell(G_SEEKABLE(os));
+}
+
+static SF_VIRTUAL_IO sf_virt_gio = {
+ .get_filelen = gio_get_filelen,
+ .seek = gio_seek,
+ .read = gio_read,
+ .write = gio_write,
+ .tell = gio_tell,
+};
+
+struct fadeout {
+ struct opna_timer *timer;
+ struct fmdriver_work *work;
+ uint64_t vol;
+ uint8_t loopcnt;
+};
+
+static bool fadeout_mix(
+ struct fadeout *fadeout,
+ int16_t *buf, unsigned frames
+) {
+ opna_timer_mix(fadeout->timer, buf, frames);
+ for (unsigned i = 0; i < frames; i++) {
+ int vol = fadeout->vol >> 16;
+ buf[i*2+0] = (buf[i*2+0] * vol) >> 16;
+ buf[i*2+1] = (buf[i*2+1] * vol) >> 16;
+ if (fadeout->work->loop_cnt >= fadeout->loopcnt) {
+ fadeout->vol = (fadeout->vol * 0xffff0000ull) >> 32;
+ }
+ }
+ return fadeout->vol;
+}
+
+struct thread_write_data {
+ struct opna opna;
+ struct opna_timer timer;
+ struct ppz8 ppz8;
+ struct fmdriver_work work;
+ struct fadeout fadeout;
+ SNDFILE *sndfile;
+ GtkDialog *dialog;
+ GtkProgressBar *pbar;
+ double fraction;
+ uint8_t adpcm_ram[OPNA_ADPCM_RAM_SIZE];
+};
+
+static gboolean idle_close_dialog(gpointer ptr) {
+ struct thread_write_data *data = ptr;
+ gtk_dialog_response(data->dialog, GTK_RESPONSE_ACCEPT);
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean idle_progress_fraction(gpointer ptr) {
+ struct thread_write_data *data = ptr;
+ gtk_progress_bar_set_fraction(data->pbar, data->fraction);
+ return G_SOURCE_REMOVE;
+}
+
+static gpointer thread_write(gpointer ptr) {
+ struct thread_write_data *data = ptr;
+ enum {
+ BUFLEN = 1024,
+ };
+ int16_t buf[BUFLEN*2];
+ for (;;) {
+ memset(buf, 0, sizeof(buf));
+ bool end = !fadeout_mix(&data->fadeout, buf, BUFLEN);
+ if (sf_writef_short(data->sndfile, buf, BUFLEN) != BUFLEN) {
+ return 0;
+ }
+ double newfrac = (double)data->work.timerb_cnt / data->work.loop_timerb_cnt;
+ if ((newfrac - data->fraction) > 0.005) {
+ data->fraction = newfrac;
+ g_idle_add(idle_progress_fraction, ptr);
+ }
+ if (end) break;
+ }
+ g_idle_add(idle_close_dialog, ptr);
+ return 0;
+}
+
+static void wavesave(GtkWindow *parent,
+ struct fmplayer_file *fmfile,
+ const char *saveuri,
+ bool flac,
+ int loopcnt) {
+ if (loopcnt < 1) loopcnt = 1;
+ if (loopcnt > 0xff) loopcnt = 0xff;
+ GFile *savefile = g_file_new_for_uri(saveuri);
+ GFileOutputStream *os = g_file_replace(savefile, 0, TRUE, 0, 0, 0);
+ if (!os) {
+ msgbox_err(parent, "Cannot open output file");
+ g_object_unref(savefile);
+ return;
+ }
+ SF_INFO sfinfo = {
+ .samplerate = SRATE,
+ .channels = 2,
+ .format = (flac ? SF_FORMAT_FLAC : SF_FORMAT_WAV | SF_ENDIAN_CPU) | SF_FORMAT_PCM_16,
+ .seekable = 1,
+ };
+ SNDFILE *sndfile = sf_open_virtual(&sf_virt_gio, SFM_WRITE, &sfinfo, os);
+ if (!sndfile) {
+ char *msg = g_strjoin(0, "SNDFILE Error: ", sf_strerror(sndfile), (char *)0);
+ msgbox_err(parent, msg);
+ g_free(msg);
+ g_object_unref(os);
+ g_object_unref(savefile);
+ return;
+ }
+ struct thread_write_data *tdata = g_new0(struct thread_write_data, 1);
+ fmplayer_init_work_opna(&tdata->work, &tdata->ppz8, &tdata->opna, &tdata->timer, tdata->adpcm_ram);
+ fmplayer_file_load(&tdata->work, fmfile, loopcnt);
+ tdata->fadeout.timer = &tdata->timer;
+ tdata->fadeout.work = &tdata->work;
+ tdata->fadeout.vol = 1ull<<32;
+ tdata->fadeout.loopcnt = loopcnt;
+ GtkWidget *dialog = gtk_dialog_new_with_buttons("Writing...",
+ parent,
+ GTK_DIALOG_MODAL,
+ "Cancel",
+ GTK_RESPONSE_CANCEL,
+ (void*)0);
+ GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ gtk_container_add(GTK_CONTAINER(content), gtk_label_new("Writing..."));
+ GtkWidget *progress = gtk_progress_bar_new();
+ gtk_container_add(GTK_CONTAINER(content), progress);
+ tdata->sndfile = sndfile;
+ tdata->dialog = GTK_DIALOG(dialog);
+ tdata->pbar = GTK_PROGRESS_BAR(progress);
+ tdata->fraction = 0.0;
+ GThread *thread = g_thread_new("Write Worker", thread_write, tdata);
+ gtk_widget_show_all(dialog);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_thread_join(thread);
+ sf_close(sndfile);
+ g_free(tdata);
+ g_object_unref(os);
+ g_object_unref(savefile);
+}
+
+enum {
+ RESPONSE_IGNORE = 1,
+};
+
+void wavesave_dialog(GtkWindow *parent, const char *uri) {
+ GtkWidget *fcd = gtk_file_chooser_dialog_new("Save wave file",
+ parent,
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ "Cancel",
+ GTK_RESPONSE_CANCEL,
+ "Save",
+ GTK_RESPONSE_ACCEPT,
+ (void *)0
+ );
+ GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
+ GtkWidget *typecombo = gtk_combo_box_text_new();
+ gtk_widget_set_halign(typecombo, GTK_ALIGN_END);
+ gtk_box_pack_start(GTK_BOX(hbox), typecombo, FALSE, FALSE, 5);
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(typecombo), 0, "RIFF WAVE (*.wav)");
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(typecombo), 0, "FLAC (*.flac)");
+ gtk_combo_box_set_active(GTK_COMBO_BOX(typecombo), 0);
+ GtkWidget *loopbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
+ gtk_widget_set_halign(loopbox, GTK_ALIGN_END);
+ gtk_box_pack_start(GTK_BOX(hbox), loopbox, FALSE, FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(loopbox), gtk_label_new("Loop:"), FALSE, FALSE, 5);
+ GtkWidget *loopspin = gtk_spin_button_new_with_range(1.0, 255.0, 1.0);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(loopspin), 2.0);
+ gtk_box_pack_start(GTK_BOX(loopbox), loopspin, FALSE, FALSE, 5);
+ gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(fcd))),
+ hbox);
+ gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(fcd), TRUE);
+
+ char *saveuri = 0;
+ gtk_widget_show_all(fcd);
+ gint res;
+ while ((res = gtk_dialog_run(GTK_DIALOG(fcd))) == RESPONSE_IGNORE);
+ if (res == GTK_RESPONSE_ACCEPT) {
+ saveuri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(fcd));
+ }
+ int loopcnt = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(loopspin));
+ bool flac = gtk_combo_box_get_active(GTK_COMBO_BOX(typecombo));
+ gtk_widget_destroy(fcd);
+ if (saveuri) {
+ enum fmplayer_file_error error;
+ struct fmplayer_file *fmfile = fmplayer_file_alloc(uri, &error);
+ if (!fmfile) {
+ char *msg = g_strjoin(0, "Cannot load file: ", fmplayer_file_strerror(error), (char *)0);
+ msgbox_err(parent, msg);
+ g_free(msg);
+ } else {
+ wavesave(parent, fmfile, saveuri, flac, loopcnt);
+ fmplayer_file_free(fmfile);
+ }
+ }
+ g_free(saveuri);
+}
diff --git a/gtk/wavesave.h b/gtk/wavesave.h
new file mode 100644
index 0000000..7dcea1f
--- /dev/null
+++ b/gtk/wavesave.h
@@ -0,0 +1,8 @@
+#ifndef MYON_FMPLAYER_GTK_WAVESAVE_H_INCLUDED
+#define MYON_FMPLAYER_GTK_WAVESAVE_H_INCLUDED
+
+#include <gtk/gtk.h>
+
+void wavesave_dialog(GtkWindow *parent, const char *uri);
+
+#endif // MYON_FMPLAYER_GTK_WAVESAVE_H_INCLUDED
diff --git a/win32/fmplayer.mak b/win32/fmplayer.mak
index 14e3246..9a514b2 100644
--- a/win32/fmplayer.mak
+++ b/win32/fmplayer.mak
@@ -28,6 +28,8 @@ SSEOBJBASE=opnassg-sinc-sse2 \
OBJBASE=main \
toneview \
oscilloview \
+ wavesave \
+ wavewrite \
soundout \
dsoundout \
waveout \
@@ -35,6 +37,8 @@ OBJBASE=main \
guid \
fmplayer_file \
fmplayer_file_win \
+ fmplayer_drumrom_win \
+ fmplayer_work_opna \
about \
$(FMDRIVER_OBJS) \
$(LIBOPNA_OBJS) \
diff --git a/win32/main.c b/win32/main.c
index 76cd883..66a1bf0 100644
--- a/win32/main.c
+++ b/win32/main.c
@@ -20,6 +20,8 @@
#include "oscillo/oscillo.h"
#include "oscilloview.h"
#include "about.h"
+#include "common/fmplayer_common.h"
+#include "wavesave.h"
enum {
ID_OPENFILE = 0x10,
@@ -28,6 +30,7 @@ enum {
ID_TONEVIEW,
ID_OSCILLOVIEW,
ID_ABOUT,
+ ID_WAVESAVE,
};
#define FMPLAYER_CLASSNAME L"myon_fmplayer_ym2608_win32"
@@ -58,12 +61,10 @@ static struct {
struct fmdsp_font font;
uint8_t fontrom[FONT_ROM_FILESIZE];
bool font_loaded;
- void *drum_rom;
uint8_t opna_adpcm_ram[OPNA_ADPCM_RAM_SIZE];
bool paused;
HWND mainwnd;
WNDPROC btn_defproc;
- HWND driverinfo;
HWND button_2x, button_toneview, button_oscilloview, button_about;
bool toneview_on, oscilloview_on, about_on;
const wchar_t *lastopenpath;
@@ -72,39 +73,11 @@ static struct {
UINT mmtimer;
HBITMAP bitmap_vram;
uint8_t *vram32;
+ bool drum_loaded;
} g;
HWND g_currentdlg;
-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 unsigned opna_readreg_libopna(struct fmdriver_work *work, unsigned addr) {
-// struct opna_timer *timer = (struct opna_timer *)work->opna;
- return opna_readreg(&g.opna, addr);
-}
-
-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);
@@ -159,39 +132,6 @@ static void loadfont(void) {
}
}
-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;
- about_set_adpcmrom_loaded(true);
- return;
-err_buf:
- HeapFree(g.heap, 0, buf);
-err_file:
- CloseHandle(file);
-err:
- return;
-}
-
static void openfile(HWND hwnd, const wchar_t *path) {
enum fmplayer_file_error error;
struct fmplayer_file *fmfile = fmplayer_file_alloc(path, &error);
@@ -213,27 +153,17 @@ static void openfile(HWND hwnd, const wchar_t *path) {
fmplayer_file_free(g.fmfile);
g.fmfile = fmfile;
unsigned mask = opna_get_mask(&g.opna);
- opna_reset(&g.opna);
+ fmplayer_init_work_opna(&g.work, &g.ppz8, &g.opna, &g.opna_timer, g.opna_adpcm_ram);
+ if (!g.drum_loaded && fmplayer_drum_loaded()) {
+ g.drum_loaded = true;
+ about_set_adpcmrom_loaded(true);
+ }
opna_set_mask(&g.opna, mask);
- 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_readreg = opna_readreg_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;
WideCharToMultiByte(932, WC_NO_BEST_FIT_CHARS, path, -1, g.work.filename, sizeof(g.work.filename), 0, 0);
- 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);
- fmplayer_file_load(&g.work, g.fmfile);
+ fmplayer_file_load(&g.work, g.fmfile, 1);
if (!g.sound) {
g.sound = sound_init(hwnd, SRATE, SECTLEN,
sound_cb, &g.opna_timer);
- SetWindowText(g.driverinfo, g.sound->apiname);
about_setsoundapiname(g.sound->apiname);
}
fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);
@@ -516,14 +446,14 @@ static bool on_create(HWND hwnd, CREATESTRUCT *cs) {
50, 25,
hwnd, (HMENU)ID_PAUSE, g.hinst, 0
);
- g.driverinfo = CreateWindowEx(
+ HWND wavesavebutton = CreateWindowEx(
0,
- L"STATIC",
- L"",
- WS_VISIBLE | WS_CHILD,
- 110, 15,
+ L"BUTTON",
+ L"&Wave output",
+ WS_TABSTOP | WS_VISIBLE | WS_CHILD,
+ 110, 10,
100, 25,
- hwnd, 0, g.hinst, 0
+ hwnd, (HMENU)ID_WAVESAVE, g.hinst, 0
);
g.button_2x = CreateWindowEx(
0,
@@ -564,6 +494,7 @@ static bool on_create(HWND hwnd, CREATESTRUCT *cs) {
g.btn_defproc = (WNDPROC)GetWindowLongPtr(button, GWLP_WNDPROC);
SetWindowLongPtr(button, GWLP_WNDPROC, (intptr_t)btn_wndproc);
SetWindowLongPtr(pbutton, GWLP_WNDPROC, (intptr_t)btn_wndproc);
+ SetWindowLongPtr(wavesavebutton, GWLP_WNDPROC, (intptr_t)btn_wndproc);
SetWindowLongPtr(g.button_2x, GWLP_WNDPROC, (intptr_t)btn_wndproc);
SetWindowLongPtr(g.button_toneview, GWLP_WNDPROC, (intptr_t)btn_wndproc);
SetWindowLongPtr(g.button_oscilloview, GWLP_WNDPROC, (intptr_t)btn_wndproc);
@@ -574,12 +505,11 @@ static bool on_create(HWND hwnd, CREATESTRUCT *cs) {
HFONT font = CreateFontIndirect(&ncm.lfMessageFont);
SetWindowFont(button, font, TRUE);
SetWindowFont(pbutton, font, TRUE);
- SetWindowFont(g.driverinfo, font, TRUE);
+ SetWindowFont(wavesavebutton, font, TRUE);
SetWindowFont(g.button_2x, font, TRUE);
SetWindowFont(g.button_toneview, font, TRUE);
SetWindowFont(g.button_oscilloview, font, TRUE);
SetWindowFont(g.button_about, font, TRUE);
- loadrom();
loadfont();
fmdsp_init(&g.fmdsp, g.font_loaded ? &g.font : 0);
fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);
@@ -658,6 +588,17 @@ static void on_command(HWND hwnd, int id, HWND hwnd_c, UINT code) {
Button_SetCheck(g.button_about, false);
}
break;
+ case ID_WAVESAVE:
+ {
+ if (g.lastopenpath) {
+ wchar_t *path = wcsdup(g.lastopenpath);
+ if (path) {
+ wavesave_dialog(hwnd, path);
+ free(path);
+ }
+ }
+ }
+ break;
}
}
@@ -666,7 +607,6 @@ static void on_destroy(HWND hwnd) {
timeKillEvent(g.mmtimer);
if (g.sound) g.sound->free(g.sound);
fmplayer_file_free(g.fmfile);
- if (g.drum_rom) HeapFree(g.heap, 0, g.drum_rom);
PostQuitMessage(0);
}
diff --git a/win32/wavesave.c b/win32/wavesave.c
new file mode 100644
index 0000000..cb12f2a
--- /dev/null
+++ b/win32/wavesave.c
@@ -0,0 +1,210 @@
+#include "wavesave.h"
+#include <commdlg.h>
+#include <stdint.h>
+#include "common/fmplayer_file.h"
+#include "common/fmplayer_common.h"
+#include "libopna/opnatimer.h"
+#include "libopna/opna.h"
+#include "wavewrite.h"
+#include <stdlib.h>
+#include <windowsx.h>
+#include <commctrl.h>
+
+enum {
+ SRATE = 55467,
+ LOOPCNT = 2,
+};
+
+static struct {
+ ATOM class;
+} g;
+
+struct fadeout {
+ struct opna_timer *timer;
+ struct fmdriver_work *work;
+ uint64_t vol;
+ uint8_t loopcnt;
+};
+
+static bool fadeout_mix(
+ struct fadeout *fadeout,
+ int16_t *buf, unsigned frames
+) {
+ opna_timer_mix(fadeout->timer, buf, frames);
+ for (unsigned i = 0; i < frames; i++) {
+ int vol = fadeout->vol >> 16;
+ buf[i*2+0] = (buf[i*2+0] * vol) >> 16;
+ buf[i*2+1] = (buf[i*2+1] * vol) >> 16;
+ if (fadeout->work->loop_cnt >= fadeout->loopcnt) {
+ fadeout->vol = (fadeout->vol * 0xffff0000ull) >> 32;
+ }
+ }
+ return fadeout->vol;
+}
+
+struct wavesave_instance {
+ struct opna opna;
+ struct opna_timer timer;
+ struct ppz8 ppz8;
+ struct fmdriver_work work;
+ struct fadeout fadeout;
+ struct fmplayer_file *fmfile;
+ struct wavefile *wavefile;
+ HWND wnd;
+ HWND pbar;
+ int ppos;
+ HANDLE thread;
+ DWORD th_exit;
+ uint8_t adpcm_ram[OPNA_ADPCM_RAM_SIZE];
+};
+
+static DWORD CALLBACK thread_write(void *ptr) {
+ struct wavesave_instance *inst = ptr;
+ enum {
+ BUFLEN = 1024,
+ };
+ int16_t buf[BUFLEN*2];
+ for (;;) {
+ if (inst->th_exit) return 0;
+ memset(buf, 0, sizeof(buf));
+ bool end = !fadeout_mix(&inst->fadeout, buf, BUFLEN);
+ if (wavewrite_write(inst->wavefile, buf, BUFLEN) != BUFLEN) {
+ break;
+ }
+ int newpos = 100 * inst->work.timerb_cnt / inst->work.loop_timerb_cnt;
+ if (newpos != inst->ppos) {
+ inst->ppos = newpos;
+ PostMessage(inst->pbar, PBM_SETPOS, newpos, 0);
+ }
+ /*
+ double newfrac = (double)data->work.timerb_cnt / data->work.loop_timerb_cnt;
+ if ((newfrac - data->fraction) > 0.005) {
+ data->fraction = newfrac;
+ g_idle_add(idle_progress_fraction, ptr);
+ }
+ */
+ if (end) break;
+ }
+ PostMessage(inst->wnd, WM_USER, 0, 0);
+ return 0;
+}
+
+static void wavesave(HWND parent,
+ struct fmplayer_file *fmfile,
+ const wchar_t *savepath) {
+ struct wavefile *wavefile = 0;
+ struct wavesave_instance *inst = 0;
+ wavefile = wavewrite_open_w(savepath, SRATE);
+ if (!wavefile) {
+ MessageBox(parent, L"Cannot open output wave file", L"Error", MB_ICONSTOP);
+ goto err;
+ }
+ inst = malloc(sizeof(*inst));
+ if (!inst) {
+ MessageBox(parent, L"Cannot allocate memory", L"Error", MB_ICONSTOP);
+ goto err;
+ }
+ *inst = (struct wavesave_instance){0};
+ fmplayer_init_work_opna(&inst->work, &inst->ppz8, &inst->opna, &inst->timer, inst->adpcm_ram);
+ fmplayer_file_load(&inst->work, fmfile, LOOPCNT);
+ inst->fadeout.timer = &inst->timer;
+ inst->fadeout.work = &inst->work;
+ inst->fadeout.vol = 1ull<<32;
+ inst->fadeout.loopcnt = LOOPCNT;
+ inst->fmfile = fmfile;
+ inst->wavefile = wavefile;
+ CreateWindow(MAKEINTATOM(g.class),
+ L"Progress",
+ WS_CAPTION | WS_SYSMENU,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ parent, 0, GetModuleHandle(0), inst);
+ return;
+err:
+ if (wavefile) wavewrite_close(wavefile);
+ free(inst);
+}
+
+static bool on_create(HWND hwnd, const CREATESTRUCT *cs) {
+ struct wavesave_instance *inst = cs->lpCreateParams;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (intptr_t)inst);
+ inst->wnd = hwnd;
+ RECT wr = {
+ .left = 0,
+ .top = 0,
+ .right = 200,
+ .bottom = 100,
+ };
+ DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ DWORD exstyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
+ AdjustWindowRectEx(&wr, style, 0, exstyle);
+ SetWindowPos(hwnd, HWND_TOP, 0, 0, wr.right-wr.left, wr.bottom-wr.top,
+ SWP_NOZORDER | SWP_NOMOVE);
+
+ inst->pbar = CreateWindow(PROGRESS_CLASS, 0,
+ WS_CHILD | WS_VISIBLE,
+ 10, 10, 180, 15,
+ hwnd, 0, cs->hInstance, 0);
+
+ ShowWindow(hwnd, SW_SHOW);
+ inst->thread = CreateThread(0, 0, thread_write, inst, 0, 0);
+ return true;
+}
+
+static void on_destroy(HWND hwnd) {
+ struct wavesave_instance *inst = (struct wavesave_instance *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ inst->th_exit = 1;
+ WaitForSingleObject(inst->thread, INFINITE);
+ fmplayer_file_free(inst->fmfile);
+ wavewrite_close(inst->wavefile);
+ free(inst);
+}
+
+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);
+ case WM_USER:
+ DestroyWindow(hwnd);
+ return 0;
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+void wavesave_dialog(HWND parent, const wchar_t *fmpath) {
+ HINSTANCE hinst = GetModuleHandle(0);
+ if (!g.class) {
+ WNDCLASS wc = {
+ .lpfnWndProc = wndproc,
+ .hInstance = hinst,
+ .hIcon = LoadIcon(hinst, MAKEINTRESOURCE(1)),
+ .hCursor = LoadCursor(0, IDC_ARROW),
+ .hbrBackground = (HBRUSH)(COLOR_BTNFACE+1),
+ .lpszClassName = L"myon_fmplayer_ym2608_wavesave_progress",
+ };
+ g.class = RegisterClass(&wc);
+ if (!g.class) {
+ MessageBox(parent, L"Cannot register wavesave window class", L"Error", MB_ICONSTOP);
+ return;
+ }
+ }
+ wchar_t path[MAX_PATH] = {0};
+
+ OPENFILENAME ofn = {
+ .lStructSize = sizeof(ofn),
+ .hwndOwner = parent,
+ .lpstrFilter = L"RIFF WAVE (*.wav)\0"
+ "*.wav\0\0",
+ .lpstrFile = path,
+ .nMaxFile = sizeof(path)/sizeof(path[0]),
+ .Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY,
+ };
+ if (!GetSaveFileName(&ofn)) return;
+ struct fmplayer_file *fmfile = fmplayer_file_alloc(fmpath, 0);
+ if (!fmfile) {
+ MessageBox(parent, L"Cannot open file", L"Error", MB_ICONSTOP);
+ return;
+ }
+ wavesave(parent, fmfile, path);
+}
diff --git a/win32/wavesave.h b/win32/wavesave.h
new file mode 100644
index 0000000..8769c14
--- /dev/null
+++ b/win32/wavesave.h
@@ -0,0 +1,9 @@
+#ifndef MYON_FMPLAYER_WIN32_WAVESAVE_H_INCLUDED
+#define MYON_FMPLAYER_WIN32_WAVESAVE_H_INCLUDED
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+void wavesave_dialog(HWND parent, const wchar_t *fmpath);
+
+#endif // MYON_FMPLAYER_WIN32_WAVESAVE_H_INCLUDED
diff --git a/win32/wavewrite.c b/win32/wavewrite.c
new file mode 100644
index 0000000..7c2d6da
--- /dev/null
+++ b/win32/wavewrite.c
@@ -0,0 +1,90 @@
+#include "wavewrite.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <stdlib.h>
+
+struct wavefile {
+ HANDLE file;
+ uint32_t written_frames;
+};
+
+static void write16le(uint8_t *ptr, uint16_t data) {
+ ptr[0] = data;
+ ptr[1] = data >> 8;
+}
+
+static void write32le(uint8_t *ptr, uint32_t data) {
+ ptr[0] = data;
+ ptr[1] = data >> 8;
+ ptr[2] = data >> 16;
+ ptr[3] = data >> 24;
+}
+
+struct wavefile *wavewrite_open_w(const wchar_t *path, uint32_t samplerate) {
+ struct wavefile *wavefile = 0;
+ wavefile = malloc(sizeof(*wavefile));
+ if (!wavefile) goto err;
+ *wavefile = (struct wavefile){
+ .file = INVALID_HANDLE_VALUE
+ };
+ wavefile->file = CreateFile(path, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
+ if (wavefile->file == INVALID_HANDLE_VALUE) goto err;
+ uint8_t waveheader[44] = {0};
+ memcpy(waveheader, "RIFF", 4);
+ memcpy(waveheader+8, "WAVE", 4);
+ memcpy(waveheader+12, "fmt ", 4);
+ write32le(waveheader+16, 16);
+ write16le(waveheader+20, 1);
+ write16le(waveheader+22, 2);
+ write32le(waveheader+24, samplerate);
+ write32le(waveheader+28, samplerate * 2 * 2);
+ write16le(waveheader+32, 4);
+ write16le(waveheader+34, 16);
+ memcpy(waveheader+36, "data", 4);
+ DWORD written;
+ if (!WriteFile(wavefile->file, waveheader, sizeof(waveheader), &written, 0) || (written != sizeof(waveheader))) {
+ goto err;
+ }
+ return wavefile;
+err:
+ if (wavefile) {
+ if (wavefile->file != INVALID_HANDLE_VALUE) CloseHandle(wavefile->file);
+ free(wavefile);
+ }
+ return 0;
+}
+
+size_t wavewrite_write(struct wavefile *wavefile, const int16_t *buf, size_t frames) {
+ if (frames >= (1ull<<(32-2))) return 0;
+ DWORD written;
+ if (!WriteFile(wavefile->file, buf, frames*4, &written, 0)) {
+ return 0;
+ }
+ uint32_t written_frames = written / 4u;
+ wavefile->written_frames += written_frames;
+ return written_frames;
+}
+
+void wavewrite_close(struct wavefile *wavefile) {
+ LONG fp;
+ uint32_t size;
+ DWORD written;
+ if ((SetFilePointer(wavefile->file, 40, &fp, FILE_BEGIN) == INVALID_SET_FILE_POINTER) || (fp != 40)) {
+ goto cleanup;
+ }
+ size = wavefile->written_frames * 4;
+ if (!WriteFile(wavefile->file, &size, sizeof(size), &written, 0) || (written != sizeof(size))) {
+ goto cleanup;
+ }
+ if ((SetFilePointer(wavefile->file, 4, &fp, FILE_BEGIN) == INVALID_SET_FILE_POINTER) || (fp != 4)) {
+ goto cleanup;
+ }
+ size += 4 + 8 + 16 + 8;
+ if (!WriteFile(wavefile->file, &size, sizeof(size), &written, 0) || (written != sizeof(size))) {
+ goto cleanup;
+ }
+cleanup:
+ CloseHandle(wavefile->file);
+ free(wavefile);
+}
diff --git a/win32/wavewrite.h b/win32/wavewrite.h
new file mode 100644
index 0000000..58db0aa
--- /dev/null
+++ b/win32/wavewrite.h
@@ -0,0 +1,15 @@
+#ifndef MYON_FMPLAYER_WIN32_WAVEWRITE_H_INCLUDED
+#define MYON_FMPLAYER_WIN32_WAVEWRITE_H_INCLUDED
+
+#include <stdint.h>
+#include <stddef.h>
+
+struct wavefile;
+
+struct wavefile *wavewrite_open_w(const wchar_t *path, uint32_t samplerate);
+
+size_t wavewrite_write(struct wavefile *wavefile, const int16_t *buf, size_t frames);
+
+void wavewrite_close(struct wavefile *wavefile);
+
+#endif // MYON_FMPLAYER_WIN32_WAVEWRITE_H_INCLUDED