diff options
-rw-r--r-- | common/fmplayer_common.h | 17 | ||||
-rw-r--r-- | common/fmplayer_drumrom_unix.c | 53 | ||||
-rw-r--r-- | common/fmplayer_drumrom_win.c | 49 | ||||
-rw-r--r-- | common/fmplayer_file.c | 113 | ||||
-rw-r--r-- | common/fmplayer_file.h | 4 | ||||
-rw-r--r-- | common/fmplayer_file_gio.c | 4 | ||||
-rw-r--r-- | common/fmplayer_file_unix.c | 127 | ||||
-rw-r--r-- | common/fmplayer_file_win.c | 2 | ||||
-rw-r--r-- | common/fmplayer_work_opna.c | 63 | ||||
-rw-r--r-- | fmdriver/fmdriver.h | 5 | ||||
-rw-r--r-- | fmdriver/fmdriver_fmp.c | 3 | ||||
-rw-r--r-- | fmdriver/fmdriver_pmd.c | 5 | ||||
-rw-r--r-- | gtk/Makefile.am | 7 | ||||
-rw-r--r-- | gtk/configure.ac | 2 | ||||
-rw-r--r-- | gtk/fmplayer.xpm | 26 | ||||
-rw-r--r-- | gtk/fmplayer32.xpm | 46 | ||||
-rw-r--r-- | gtk/main.c | 112 | ||||
-rw-r--r-- | gtk/toneview.c | 2 | ||||
-rw-r--r-- | gtk/wavesave.c | 256 | ||||
-rw-r--r-- | gtk/wavesave.h | 8 | ||||
-rw-r--r-- | win32/fmplayer.mak | 4 | ||||
-rw-r--r-- | win32/main.c | 118 | ||||
-rw-r--r-- | win32/wavesave.c | 210 | ||||
-rw-r--r-- | win32/wavesave.h | 9 | ||||
-rw-r--r-- | win32/wavewrite.c | 90 | ||||
-rw-r--r-- | win32/wavewrite.h | 15 |
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" +}; @@ -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 |