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 | 
