diff options
| author | Takamichi Horikawa <takamichiho@gmail.com> | 2016-11-26 20:57:57 +0900 | 
|---|---|---|
| committer | Takamichi Horikawa <takamichiho@gmail.com> | 2016-11-26 20:57:57 +0900 | 
| commit | 6fd10cdacb5cbe47a4fc339c20a733d4a9a384a1 (patch) | |
| tree | c7f479a70c350dca0b73d76078e46db41d9c4133 | |
initial
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | LICENSE | 25 | ||||
| -rw-r--r-- | README.md | 29 | ||||
| -rw-r--r-- | curses/.gitignore | 12 | ||||
| -rw-r--r-- | curses/Makefile.am | 18 | ||||
| -rw-r--r-- | curses/configure.ac | 9 | ||||
| -rw-r--r-- | curses/main.c | 541 | ||||
| -rw-r--r-- | fmdriver/fmdriver.h | 30 | ||||
| -rw-r--r-- | fmdriver/fmdriver_common.h | 19 | ||||
| -rw-r--r-- | fmdriver/fmdriver_fmp.c | 3257 | ||||
| -rw-r--r-- | fmdriver/fmdriver_fmp.h | 529 | ||||
| -rw-r--r-- | fmdriver/ppz8.c | 293 | ||||
| -rw-r--r-- | fmdriver/ppz8.h | 76 | ||||
| -rw-r--r-- | libopna/opna.c | 24 | ||||
| -rw-r--r-- | libopna/opna.h | 30 | ||||
| -rw-r--r-- | libopna/opnaadpcm.c | 208 | ||||
| -rw-r--r-- | libopna/opnaadpcm.h | 43 | ||||
| -rw-r--r-- | libopna/opnadrum.c | 141 | ||||
| -rw-r--r-- | libopna/opnadrum.h | 57 | ||||
| -rw-r--r-- | libopna/opnafm.c | 511 | ||||
| -rw-r--r-- | libopna/opnafm.h | 111 | ||||
| -rw-r--r-- | libopna/opnassg.c | 233 | ||||
| -rw-r--r-- | libopna/opnassg.h | 60 | ||||
| -rw-r--r-- | libopna/opnatables.h | 242 | ||||
| -rw-r--r-- | libopna/opnatimer.c | 79 | ||||
| -rw-r--r-- | libopna/opnatimer.h | 42 | ||||
| -rw-r--r-- | libopna/s98gen.c | 141 | ||||
| -rw-r--r-- | libopna/s98gen.h | 34 | ||||
| -rw-r--r-- | screenshot.png | bin | 0 -> 22486 bytes | 
29 files changed, 6797 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..729dc37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +.deps +.dirstamp @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2016, Takamichi Horikawa +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this +  list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, +  this list of conditions and the following disclaimer in the documentation +  and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c22563 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Fmplayer (仮) +PC-98用のFM音源ドライバエミュレーション(予定) + +## 現在の状況: +* FMP (PLAY6含む) のみ対応 +* UI がデバッグ用に ncurses で作った仮のもの + +* PDZF部分が不完全 (LFO, ピッチベンドなど) +* PDZF判定も未実装 (using PDZF, 4行コメントとか関係なくエンハンスドモード) +* FM は 55467Hz で合成, SSG は 249600Hz で合成した後 sinc でフィルタして混合 +* PPZ8 は線形補間のみ(オリジナルの無補完よりは…) +* libopna, fmdriver 部分は freestanding な c99 (のはず) + +## 今後の予定: +* まともなUIを作る +* PDZF の完全な対応 +* PMD, MDRV2, PLAY5などの対応 + +## (まだ使えるような状況じゃないけど) 使い方 +現在の仮UIは ncurses, SDL2 を使用します。 +``` +$ cd curses +$ autoreconf -i +$ ./configure +$ ./fmpc foo.ozi +``` +下の方にコメントを適当に iconv で変換したものを出力しているので端末が80行以上あると見えます。(エスケープコードは解釈してません, FMP外字も見えません) +PCMファイルが必要な場合、そのファイルのディレクトリから大文字、小文字の順に読み込みます。(PCMファイル名の文字コードは今のところ考慮していません) +`$HOME/.local/share/libopna/ym2608_adpcm_rom.bin`からMAME互換のドラムサンプルを読み込みます。 diff --git a/curses/.gitignore b/curses/.gitignore new file mode 100644 index 0000000..bab26a8 --- /dev/null +++ b/curses/.gitignore @@ -0,0 +1,12 @@ +Makefile.in +aclocal.m4 +autom4te.cache +compile +configure +depcomp +install-sh +missing +Makefile +config.log +config.status +fmpc diff --git a/curses/Makefile.am b/curses/Makefile.am new file mode 100644 index 0000000..340f926 --- /dev/null +++ b/curses/Makefile.am @@ -0,0 +1,18 @@ +bin_PROGRAMS=fmpc + +LIBOPNA_SOURCES=../libopna/opnaadpcm.c \ +                ../libopna/opnadrum.c \ +                ../libopna/opnafm.c \ +                ../libopna/opnassg.c \ +                ../libopna/opnatimer.c \ +                ../libopna/opna.c + +FMDRIVER_SOURCES=../fmdriver/fmdriver_fmp.c \ +                 ../fmdriver/ppz8.c +fmpc_SOURCES=main.c \ +             $(LIBOPNA_SOURCES) \ +             $(FMDRIVER_SOURCES) + +fmpc_CFLAGS=-Wall -Wextra -pedantic \ +            -I.. $(SDL_CFLAGS) $(NCURSES_CFLAGS) +fmpc_LDADD=$(SDL_LIBS) $(NCURSES_LIBS) diff --git a/curses/configure.ac b/curses/configure.ac new file mode 100644 index 0000000..704d32e --- /dev/null +++ b/curses/configure.ac @@ -0,0 +1,9 @@ +AC_INIT([fmplayer], [0.1.0]) +AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) +AC_PROG_CC_C99 + +AM_PATH_SDL2([2.0.5]) +PKG_CHECK_MODULES([NCURSES], [ncursesw >= 6]) + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/curses/main.c b/curses/main.c new file mode 100644 index 0000000..bfcaa51 --- /dev/null +++ b/curses/main.c @@ -0,0 +1,541 @@ +#include <curses.h> +#include <stdio.h> +#include <stdint.h> +#include "libopna/opna.h" +#include "libopna/opnatimer.h" +#include "fmdriver/fmdriver.h" +#include "fmdriver/fmdriver_fmp.h" +#include <SDL.h> +#include <stdlib.h> +#include <locale.h> +#include <iconv.h> +#include <errno.h> + +static uint8_t g_data[0x10000]; + +enum { +  SRATE = 55467, +}; + +static void sdl_callback(void *userdata, Uint8 *stream, int len) { +  SDL_memset(stream, 0, len); +  struct opna_timer *timer = (struct opna_timer *)userdata; +  int16_t *buf = (int16_t *)stream; +  unsigned samples = len/2/2; +  opna_timer_mix(timer, buf, samples); +} + +static void opna_writereg_libopna(struct fmdriver_work *work, unsigned addr, unsigned data) { +  struct opna_timer *timer = (struct opna_timer *)work->opna; +  opna_timer_writereg(timer, addr, data); +} + +static uint8_t opna_status_libopna(struct fmdriver_work *work, bool a1) { +  struct opna_timer *timer = (struct opna_timer *)work->opna; +  uint8_t status = opna_timer_status(timer); +  if (!a1) { +    status &= 0x83; +  } +  return status; +} + +static void opna_interrupt_callback(void *userptr) { +  struct fmdriver_work *work = (struct fmdriver_work *)userptr; +  work->driver_opna_interrupt(work); +} + +static void opna_mix_callback(void *userptr, int16_t *buf, unsigned samples) { +  struct ppz8 *ppz8 = (struct ppz8 *)userptr; +  ppz8_mix(ppz8, buf, samples); +} + +static const char *notestr[12] = { +  "c ", +  "c+", +  "d ", +  "d+", +  "e ", +  "f ", +  "f+", +  "g ", +  "g+", +  "a ", +  "a+", +  "b ", +}; + +static const char *pdzf_mode_str[3] = { +  "  ", +  "PS", +  "PE" +}; + +static void printnote(int y, const struct fmp_part *part) { +  if (part->status.rest) { +    mvprintw(y, 24, "    "); +  } else { +    mvprintw(y, 24, "o%c%s", +             (part->prev_note/0xc)+'0', +             notestr[(part->prev_note%0xc)] +    ); +  } +} + +static void printpart_ssg(int y, const char *name, const struct driver_fmp *fmp, const struct fmp_part *part) { + +  mvprintw(y, 0, "%s %04X @%03d %3d %3d      %+4d %04X %c%c%c%c%c%c %c%c%c %c%c%c%c %c%c%c %02X %02X %c%c       %s %d", +    name, +    part->current_ptr, +    part->tone, +    part->tonelen_cnt, +    part->current_vol-1, +    part->detune - ((part->detune & 0x8000) ? 0x10000 : 0), +    part->actual_freq, +    part->lfo_f.p ? 'P' : '_', +    part->lfo_f.q ? 'Q' : '_', +    part->lfo_f.r ? 'R' : '_', +    part->lfo_f.a ? 'A' : '_', +    part->lfo_f.w ? 'W' : '_', +    part->lfo_f.e ? 'E' : '_', +    part->status.tie ? 'T' : '_', +    part->status.tie_cont ? 't' : '_', +    part->status.slur ? 'S' : '_', +    part->u.ssg.env_f.attack ? 'A' : '_', +    part->u.ssg.env_f.decay ? 'D' : '_', +    part->u.ssg.env_f.sustain ? 'S' : '_', +    part->u.ssg.env_f.release ? 'R' : '_', +    part->u.ssg.env_f.portamento ? 'P' : '_', +    part->u.ssg.env_f.noise ? 'N' : '_', +    part->u.ssg.env_f.ppz ? 'Z' : '_', +    part->actual_vol, +    part->u.ssg.vol, +    ((fmp->ssg_mix >> part->opna_keyon_out)&1) ? '_' : 'T', +    ((fmp->ssg_mix >> part->opna_keyon_out)&8) ? '_' : 'N', +    pdzf_mode_str[part->pdzf.mode], +    part->pdzf.env_state.status +  ); +  printnote(y, part); +} + +static void printpart_fm(int y, const char *name, const struct fmp_part *part) { +  mvprintw(y, 0, "%s %04X @%03d %3d %3d      %+4d %04X %c%c%c%c%c%c %c%c%c %c%c %02X %02X %02X %02X %c%c%c%c%c%c%c%c %s %3d", +    name, +    part->current_ptr, +    part->tone, +    part->tonelen_cnt, +    0x7f-part->actual_vol, +    part->detune - ((part->detune & 0x8000) ? 0x10000 : 0), +    part->actual_freq, +    part->lfo_f.p ? 'P' : '_', +    part->lfo_f.q ? 'Q' : '_', +    part->lfo_f.r ? 'R' : '_', +    part->lfo_f.a ? 'A' : '_', +    part->lfo_f.w ? 'W' : '_', +    part->lfo_f.e ? 'E' : '_', +    part->status.tie ? 'T' : '_', +    part->status.tie_cont ? 't' : '_', +    part->status.slur ? 'S' : '_', +    (part->pan_ams_pms & 0x80) ? 'L' : '_', +    (part->pan_ams_pms & 0x40) ? 'R' : '_', +    part->u.fm.tone_tl[0], +    part->u.fm.tone_tl[1], +    part->u.fm.tone_tl[2], +    part->u.fm.tone_tl[3], +    (part->u.fm.slot_mask & (1<<0)) ? '_' : '1', +    (part->u.fm.slot_mask & (1<<2)) ? '_' : '2', +    (part->u.fm.slot_mask & (1<<1)) ? '_' : '3', +    (part->u.fm.slot_mask & (1<<3)) ? '_' : '4', +    (part->u.fm.slot_mask & (1<<4)) ? '_' : '1', +    (part->u.fm.slot_mask & (1<<5)) ? '_' : '2', +    (part->u.fm.slot_mask & (1<<6)) ? '_' : '3', +    (part->u.fm.slot_mask & (1<<7)) ? '_' : '4', +    pdzf_mode_str[part->pdzf.mode], +    part->pdzf.vol +  ); +  printnote(y, part); +} + +static void update(const struct ppz8 *ppz8, +                   const struct opna *opna, +                   const struct driver_fmp *fmp) { +  static const char *partname_fm[] = { +    "FM1  ", +    "FM2  ", +    "FM3  ", +    "FM4  ", +    "FM5  ", +    "FM6  ", +    "FMEX1", +    "FMEX2", +    "FMEX3", +  }; +  static const char *partname_ssg[] = { +    "SSG1 ", +    "SSG2 ", +    "SSG3 ", +  }; +  static const char *partname_drum[] = { +    "BD ", +    "SD ", +    "TOP", +    "HH ", +    "TOM", +    "RIM", +  }; +  for (int i = 0; i < 9; i++) { +    const struct fmp_part *part = &fmp->parts[FMP_PART_FM_1+i]; +    printpart_fm(i+1, partname_fm[i], part); +    /* +    if (part->type.fm_3) { +      mvprintw(i+1, 60, "%c%c%c%c", +        (part->u.fm.slot_mask & 0x10) ? ' ' : '1', +        (part->u.fm.slot_mask & 0x20) ? ' ' : '2', +        (part->u.fm.slot_mask & 0x40) ? ' ' : '3', +        (part->u.fm.slot_mask & 0x80) ? ' ' : '4' +      ); +    } +    */ +  } +  for (int i = 0; i < 3; i++) { +    const struct fmp_part *part = &fmp->parts[FMP_PART_SSG_1+i]; +    int y = i+10; +    printpart_ssg(y, partname_ssg[i], fmp, part); +  } +  mvprintw(13, 0, "RHY   %04X      %3d", +           fmp->rhythm.part.current_ptr, +           fmp->rhythm.len_cnt); +  { +    const struct fmp_part *part = &fmp->parts[FMP_PART_ADPCM]; +    mvprintw(14, 0, "ADPCM %04X @%03d %3d %3d      %+4d %04X        %c%c %c%c", +             part->current_ptr, +             part->tone, +             part->tonelen_cnt, +             part->actual_vol, +             part->detune - ((part->detune & 0x8000) ? 0x10000 : 0), +             part->actual_freq, +             part->status.tie ? 'T' : '_', +             part->status.tie_cont ? 't' : '_', +             (part->pan_ams_pms & 0x80) ? 'L' : '_', +             (part->pan_ams_pms & 0x40) ? 'R' : '_' +    ); +    printnote(14, part); +  } +  for (int c = 0; c < 6; c++) { +    for (int s = 0; s < 4; s++) { +      mvprintw(18+c, 7*s, "%02X %03X", +               opna->fm.channel[c].slot[s].tl, +               opna->fm.channel[c].slot[s].env); +    } +  } +  for (int i = 0; i < 3; i++) { +    mvprintw(18+i, 49, "%02X", opna->ssg.regs[8+i]); +  } +  mvprintw(22, 49, "%02X", opna->ssg.regs[6]&0x1f); +  mvprintw(17, 33, "%02X", opna->drum.total_level); +  for (int i = 0; i < 6; i++) { +    mvprintw(18+i, 30, "%s %c %04X %02X %c%c", +             partname_drum[i], +             opna->drum.drums[i].playing ? '@' : '_', +             opna->drum.drums[i].index, +             opna->drum.drums[i].level, +             opna->drum.drums[i].left ? 'L' : '_', +             opna->drum.drums[i].right ? 'R' : '_' +            ); +  } +  mvprintw(18, 54, "%02X %c%c", +           opna->adpcm.vol, +           (opna->adpcm.control2 & 0x80) ? 'L' : '_', +           (opna->adpcm.control2 & 0x40) ? 'R' : '_' +  ); +  mvprintw(20, 54, "%06X", opna->adpcm.start<<5); +  mvprintw(21, 54, "%06X", ((opna->adpcm.end+1)<<5)-1); +  mvprintw(22, 54, "%06X", opna->adpcm.ramptr>>1); +   +  for (int i = 0; i < 8; i++) { +    mvprintw(16+i, 62, "@%03d %1X %1d %08X", +             ppz8->channel[i].voice, +             ppz8->channel[i].vol, +             ppz8->channel[i].pan, +             (unsigned)(ppz8->channel[i].ptr>>16)); +  } +} + +static bool readrom(struct opna *opna) { +  const char *path = "ym2608_adpcm_rom.bin"; +  const char *home = getenv("HOME"); +  char *dpath = 0; +  if (home) { +    const char *datadir = "/.local/share/libopna/"; +    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 != 0x2000) goto err_file; +  if (fseek(rhythm, 0, SEEK_SET) != 0) goto err_file; +  uint8_t data[0x2000]; +  if (fread(data, 1, 0x2000, rhythm) != 0x2000) goto err_file; +  opna_drum_set_rom(&opna->drum, data); +  fclose(rhythm); +  return true; +err_file: +  fclose(rhythm); +err: +  return false; +} + +static FILE *pvisearch(const char *filename, const char *pvibase) { +  // TODO: not SJIS aware +  char pviname[8+3+2] = {0}; +  char pviname_l[8+3+2] = {0}; +  strcpy(pviname, pvibase); +  strcat(pviname, ".PVI"); +  strcpy(pviname_l, pviname); +  for (char *c = pviname_l; *c; c++) { +    if (('A' <= *c) && (*c <= 'Z')) { +      *c += ('a' - 'A'); +    } +  } +  FILE *pvifile = fopen(pviname, "r"); +  if (pvifile) return pvifile; +  pvifile = fopen(pviname_l, "r"); +  if (pvifile) return pvifile; +  char *slash = strrchr(filename, '/'); +  if (!slash) return 0; +  char *pvipath = malloc((slash-filename)+1+sizeof(pviname)); +  if (!pvipath) return 0; +  memcpy(pvipath, filename, slash-filename+1); +  pvipath[slash-filename+1] = 0; +  strcat(pvipath, pviname); +  pvifile = fopen(pvipath, "r"); +  if (pvifile) { +    free(pvipath); +    return pvifile; +  } +  pvipath[slash-filename+1] = 0; +  strcat(pvipath, pviname_l); +  pvifile = fopen(pvipath, "r"); +  if (pvifile) { +    free(pvipath); +    return pvifile; +  } +  free(pvipath); +  return 0; +} + +static bool loadpvi(struct fmdriver_work *work, +                    struct driver_fmp *fmp, +                    const char *filename) { +  // no need to load, always success +  if(strlen(fmp->pvi_name) == 0) return true; +  FILE *pvifile = pvisearch(filename, fmp->pvi_name); +  if (!pvifile) goto err; +  if (fseek(pvifile, 0, SEEK_END) != 0) goto err_file; +  size_t fsize; +  { +    long size = ftell(pvifile); +    if (size < 0) goto err_file; +    fsize = size; +  } +  if (fseek(pvifile, 0, SEEK_SET) != 0) goto err_file; +  void *data = malloc(fsize); +  if (!data) goto err_file; +  if (fread(data, 1, fsize, pvifile) != fsize) goto err_memory; +  if (!fmp_adpcm_load(work, data, fsize)) goto err_memory; +  free(data); +  fclose(pvifile); +  return true; +err_memory: +  free(data); +err_file: +  fclose(pvifile); +err: +  return false; +} + +static bool loadppzpvi(struct fmdriver_work *work, +                    struct driver_fmp *fmp, +                    const char *filename) { +  // no need to load, always success +  if(strlen(fmp->ppz_name) == 0) return true; +  FILE *pvifile = pvisearch(filename, fmp->ppz_name); +  if (!pvifile) goto err; +  if (fseek(pvifile, 0, SEEK_END) != 0) goto err_file; +  size_t fsize; +  { +    long size = ftell(pvifile); +    if (size < 0) goto err_file; +    fsize = size; +  } +  if (fseek(pvifile, 0, SEEK_SET) != 0) goto err_file; +  void *data = malloc(fsize); +  if (!data) goto err_file; +  if (fread(data, 1, fsize, pvifile) != fsize) goto err_memory; +  int16_t *decbuf = calloc(ppz8_pvi_decodebuf_samples(fsize), sizeof(int16_t)); +  if (!decbuf) goto err_memory; +  if (!ppz8_pvi_load(work->ppz8, 0, data, fsize, decbuf)) goto err_memory; +  free(data); +  fclose(pvifile); +  return true; +err_memory: +  free(data); +err_file: +  fclose(pvifile); +err: +  return false; +} + +int main(int argc, char **argv) { +  if (SDL_Init(SDL_INIT_AUDIO) != 0) { +    fprintf(stderr, "cannot initialize SDL\n"); +    return 1; +  } +  if (argc != 2) { +    fprintf(stderr, "invalid arguments\n"); +    return 1; +  } +  FILE *file = fopen(argv[1], "rb"); +  if (!file) { +    fprintf(stderr, "cannot open file\n"); +    return 1; +  } +  if (fseek(file, 0, SEEK_END) != 0) { +    fprintf(stderr, "cannot seek to end\n"); +    return 1; +  } +  long filelen = ftell(file); +  if (fseek(file, 0, SEEK_SET) != 0) { +    fprintf(stderr, "cannot seek to beginning\n"); +    return 1; +  } +  if ((filelen < 0) || (filelen > 0xffff)) { +    fprintf(stderr, "invalid file length: %ld\n", filelen); +    return 1; +  } +  if (fread(g_data, 1, filelen, file) != filelen) { +    fprintf(stderr, "cannot read file\n"); +    return 1; +  } +  fclose(file); +  static struct fmdriver_work work = {0}; +  static struct driver_fmp fmp = {0}; +  static struct opna opna; +  static struct opna_timer timer; +  static struct ppz8 ppz8; +  ppz8_init(&ppz8, SRATE, 0xa000); +  work.ppz8 = &ppz8; +  work.ppz8_functbl = &ppz8_functbl; +  opna_reset(&opna); +  opna_timer_reset(&timer, &opna); +  readrom(&opna); +  opna_adpcm_set_ram_256k(&opna.adpcm, malloc(OPNA_ADPCM_RAM_SIZE)); +   +  opna_timer_set_int_callback(&timer, opna_interrupt_callback, &work); +  opna_timer_set_mix_callback(&timer, opna_mix_callback, &ppz8); +  work.opna_writereg = opna_writereg_libopna; +  work.opna_status = opna_status_libopna; +  work.opna = &timer; +  if (!fmp_init(&work, &fmp, g_data, filelen)) { +    fprintf(stderr, "not fmp\n"); +    return 1; +  } +  bool pvi_loaded = loadpvi(&work, &fmp, argv[1]); +  bool ppz_loaded = loadppzpvi(&work, &fmp, argv[1]); + +  SDL_AudioSpec as = {0}; +  as.freq = SRATE; +  as.format = AUDIO_S16SYS; +  as.channels = 2; +  as.callback = sdl_callback; +  as.userdata = &timer; +  as.samples = 2048; + +  SDL_AudioDeviceID ad = SDL_OpenAudioDevice(0, 0, &as, 0, 0); +  if (!ad) { +    fprintf(stderr, "cannot open audio device\n"); +    return 1; +  } +  SDL_PauseAudioDevice(ad, 0); + +   +  setlocale(LC_CTYPE, ""); +  enum { +    //TBUFLEN = 80*3*2, +    TBUFLEN = 0x10000 +  }; +  char titlebuf[TBUFLEN+1] = {0}; +  if (work.title) { +    iconv_t cd = iconv_open("//IGNORE", "CP932"); +    if (cd != (iconv_t)-1) { +      char titlebufcrlf[TBUFLEN+1] = {0}; +      const char *in = work.title; +      size_t inleft = strlen(in)+1; +      char *out = titlebufcrlf; +      size_t outleft = TBUFLEN; +      for (;;) { +        if (iconv(cd, &in, &inleft, &out, &outleft) == (size_t)-1) { +          *out = 0; +          break; +        } +        if (!inleft) break; +      } +      iconv_close(cd); +      int o = 0; +      for (int i = 0; ; i++) { +        if (titlebufcrlf[i] == 0x0d) continue; +        titlebuf[o++] = titlebufcrlf[i]; +        if (!titlebufcrlf[i]) break; +      } +    } +  } +   +  initscr(); +  cbreak(); +  noecho(); +  clear(); +  refresh(); + +  timeout(20); + +  mvprintw(0, 0, "PART  PTR  TONE LEN VOL NOTE  DET FREQ PQRAWE"); +  mvprintw(14, 61, "PPZ8"); +  mvprintw(16, 0, "FM                           RHYTHM             SSG  ADPCM"); +  mvprintw(17, 0, "TL ENV                        TL    PTR  LV     LV"); +  mvprintw(21, 48, "NZ"); +  mvprintw(24, 0, "PPZ: %c%8s PVI: %c%8s", +           ppz_loaded ? ' ' : '!', +           fmp.ppz_name, +           pvi_loaded ? ' ' : '!', +           fmp.pvi_name); +  mvprintw(25, 0, "%s", titlebuf); + +  int cont = 1; +  int pause = 0; +  while (cont) { +    switch (getch()) { +    case 'q': +      cont = 0; +      break; +    case 'p': +      pause = !pause; +      SDL_PauseAudioDevice(ad, pause); +      break; +    case ERR: +      update(&ppz8, &opna, &fmp); +      break; +    } +  } + +  endwin(); +  SDL_Quit(); +  return 0; +} + diff --git a/fmdriver/fmdriver.h b/fmdriver/fmdriver.h new file mode 100644 index 0000000..67557cc --- /dev/null +++ b/fmdriver/fmdriver.h @@ -0,0 +1,30 @@ +#ifndef MYON_FMDRIVER_H_INCLUDED +#define MYON_FMDRIVER_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> +#include "ppz8.h" + +struct fmdriver_work { +  // set by driver, called by opna +  void (*driver_opna_interrupt)(struct fmdriver_work *work); +  void (*driver_deinit)(struct fmdriver_work *work); +  // driver internal +  void *driver; + + +  // set by opna, called by driver in the interrupt functions +  unsigned (*opna_readreg)(struct fmdriver_work *work, unsigned addr); +  void (*opna_writereg)(struct fmdriver_work *work, unsigned addr, unsigned data); +  uint8_t (*opna_status)(struct fmdriver_work *work, bool a1); +  void *opna; + +  const struct ppz8_functbl *ppz8_functbl; +  struct ppz8 *ppz8; + +  const char *title; +  // driver status +  // fm3ex part map +}; + +#endif // MYON_FMDRIVER_H_INCLUDED diff --git a/fmdriver/fmdriver_common.h b/fmdriver/fmdriver_common.h new file mode 100644 index 0000000..cd804b8 --- /dev/null +++ b/fmdriver/fmdriver_common.h @@ -0,0 +1,19 @@ +#ifndef MYON_FMDRIVER_COMMON_H_INCLUDED +#define MYON_FMDRIVER_COMMON_H_INCLUDED + +static inline uint16_t read16le(const uint8_t *ptr) { +  return (unsigned)ptr[0] | (((unsigned)ptr[1])<<8); +} + +static inline int16_t u8s16(uint8_t v) { +  return (v & 0x80) ? ((int16_t)v)-0x100 : v; +} + +static inline int16_t u16s16(uint16_t v) { +  return (v & 0x8000) ? ((int32_t)v)-0x10000 : v; +} + +#include <stdio.h> +#define FMDRIVER_DEBUG(...) fprintf(stderr, __VA_ARGS__) + +#endif // MYON_FMDRIVER_COMMON_H_INCLUDED diff --git a/fmdriver/fmdriver_fmp.c b/fmdriver/fmdriver_fmp.c new file mode 100644 index 0000000..76e3798 --- /dev/null +++ b/fmdriver/fmdriver_fmp.c @@ -0,0 +1,3257 @@ +#include "fmdriver_fmp.h"
 +#include "fmdriver_common.h"
 +
 +static uint8_t fmp_rand71(struct driver_fmp *fmp) {
 +  // on real PC-98, read from I/O port 0x71 (8253 Timer)
 +  uint8_t val = fmp->rand71;
 +  fmp->rand71 = (val>>1) | ((((val>>7)^(val>>5)^(val>>4)^(val>>3)^1)&1)<<7);
 +  return fmp->rand71;
 +}
 +
 +static uint8_t fmp_part_cmdload(struct driver_fmp *fmp, struct fmp_part *part) {
 +  if (part->current_ptr >= fmp->datalen) {
 +    //exit(2);
 +    part->current_ptr = 0xffff;
 +    return 0x74;
 +  }
 +  return fmp->data[part->current_ptr++];
 +}
 +static uint8_t fmp_part_cmdload_rhythm(struct driver_fmp *fmp, struct fmp_part *part) {
 +  if (part->current_ptr >= fmp->datalen) {
 +    part->current_ptr = 0xffff;
 +    return 0x93;
 +  }
 +  return fmp->data[part->current_ptr++];
 +}
 +
 +static uint16_t fmp_part_cmdload16(struct driver_fmp *fmp, struct fmp_part *part) {
 +  uint16_t val = fmp_part_cmdload(fmp, part);
 +  val |= fmp_part_cmdload(fmp, part) << 8;
 +  return val;
 +}
 +
 +enum {
 +  OPNA_DTMUL = 0x30,
 +  OPNA_TL = 0x40,
 +  OPNA_KSAR = 0x50,
 +  OPNA_AMDR = 0x60,
 +  OPNA_SR = 0x70,
 +  OPNA_SLRR = 0x80,
 +  OPNA_SSGEG = 0x90,
 +  OPNA_FNUM1 = 0xa0,
 +  OPNA_BLKFNUM2 = 0xa4,
 +  OPNA_FBALG = 0xb0,
 +  OPNA_LRAMSPMS = 0xb4,
 +};
 +
 +static void fmp_part_fm_reg_write(struct fmdriver_work *work,
 +                                  struct fmp_part *part,
 +                                  uint8_t addr, uint8_t data) {
 +  uint16_t outaddr = part->opna_keyon_out & 0x3;
 +  if (part->opna_keyon_out & 0x4) outaddr += 0x100;
 +  outaddr += addr;
 +  work->opna_writereg(work, outaddr, data);
 +}
 +
 +// 30ec
 +static uint16_t fmp_fm_freq(uint8_t note) {
 +  // 3106
 +  static const uint16_t freqtab[0xc] = {
 +    0x026a,
 +    0x028f,
 +    0x02b6,
 +    0x02df,
 +    0x030b,
 +    0x0339,
 +    0x036a,
 +    0x039e,
 +    0x03d5,
 +    0x0410,
 +    0x044e,
 +    0x048f,
 +  };
 +  return freqtab[note%0xc] + ((note/0xc)<<(3+8));
 +}
 +
 +static uint8_t fmp_ssg_octave(uint8_t note) {
 +  return note/0xc;
 +}
 +
 +static uint16_t fmp_ppz_freq(uint8_t note) {
 +  static const uint16_t freqtab[0xc] = {
 +    0x8000,
 +    0x87a6,
 +    0x8fb3,
 +    0x9838,
 +    0xa146,
 +    0xaade,
 +    0xb4ff,
 +    0xbfcc,
 +    0xcb34,
 +    0xd747,
 +    0xe418,
 +    0xf1a5,
 +  };
 +  return freqtab[note%0xc];
 +}
 +
 +// 311e
 +static uint16_t fmp_part_ssg_freq(struct fmp_part *part, uint8_t note) {
 +  // 315a
 +  static const uint16_t freqtab[0xc] = {
 +    0x0ee8,
 +    0x0e12,
 +    0x0d48,
 +    0x0c89,
 +    0x0bd5,
 +    0x0b2b,
 +    0x0a8a,
 +    0x09f3,
 +    0x0964,
 +    0x08dd,
 +    0x085e,
 +    0x07e6,
 +  };
 +  uint16_t freq = part->u.ssg.env_f.ppz ? fmp_ppz_freq(note) : freqtab[note%0xc];
 +  return part->detune + freq;
 +}
 +
 +// 3172
 +static uint16_t fmp_adpcm_freq(uint8_t note) {
 +  static const uint16_t freqtab[4][0xc] = {
 +    {
 +      0xdb22,
 +      0xdd53,
 +      0xdfa6,
 +      0xe21c,
 +      0xe4b7,
 +      0xe777,
 +      0xea65,
 +      0xed7f,
 +      0xf0c8,
 +      0xf443,
 +      0xf7f4,
 +      0xfbdc
 +    },
 +    {
 +      0x0000,
 +      0x0463,
 +      0x0909,
 +      0x0df6,
 +      0x132d,
 +      0x1864,
 +      0x1e8a,
 +      0x24bd,
 +      0x2b4e,
 +      0x3244,
 +      0x39a3,
 +      0x4173
 +    },
 +    {
 +      0x49ba,
 +      0x527e,
 +      0x5bc8,
 +      0x65a0,
 +      0x700d,
 +      0x7b19,
 +      0x86cc,
 +      0x9336,
 +      0xa057,
 +      0xae42,
 +      0xbd01,
 +      0xcca2
 +    },
 +    {
 +      0xc8b4,
 +      0xc9cc,
 +      0xcaf5,
 +      0xcc30,
 +      0xcd7e,
 +      0xcedf,
 +      0xd056,
 +      0xd1e3,
 +      0xd387,
 +      0xd544,
 +      0xd71c,
 +      0xd911
 +    }
 +  };
 +  uint16_t octave = note / 0xc;
 +  octave = (octave - 3) & 3;
 +  return freqtab[octave][note%0xc];
 +}
 +
 +static uint8_t fmp_pdzf_vol_clamp(uint8_t v, uint8_t ev) {
 +  int16_t ret = v + u8s16(ev);
 +  if (ret < 0) ret = 0;
 +  if (ret > 0xf) ret = 0xf;
 +  return ret;
 +}
 +
 +static void fmp_part_pdzf_vol_update(struct fmdriver_work *work,
 +                                     struct fmp_part *part) {
 +  part->pdzf.vol = part->type.fm ? (0x7f-part->current_vol)&0xf : part->current_vol - 1;
 +  if (part->pdzf.mode) {
 +    uint8_t envvol = part->lfo_f.q ? part->pdzf.env_state.vol : 0;
 +    if (work->ppz8_functbl) {
 +      work->ppz8_functbl->channel_volume(
 +        work->ppz8,
 +        part->pdzf.ppz8_channel,
 +        fmp_pdzf_vol_clamp(part->pdzf.vol, envvol)
 +      );
 +    }
 +  }
 +}
 +
 +// 29e7
 +static void fmp_part_fm_vol(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  fmp_part_pdzf_vol_update(work, part);
 +  uint8_t pvol = part->actual_vol + fmp->fm_vol;
 +  if (pvol & 0x80) {
 +    if (fmp->fm_vol & 0x80) {
 +      pvol = 0;
 +    } else {
 +      pvol = 0x7f;
 +    }
 +  }
 +  for (int s = 0; s < 4; s++) {
 +    // 2a16
 +    if (!(part->slot_vol_mask & (1<<s))) continue;
 +    uint8_t svol = pvol - part->u.fm.slot_rel_vol[s];
 +    if (svol & 0x80) {
 +      if (part->u.fm.slot_rel_vol[s] & 0x80) {
 +        svol = 0x7f;
 +      } else {
 +        svol = 0;
 +      }
 +    }
 +    // 2a31
 +
 +    if (fmp->data_version >= 6) {
 +      svol += part->u.fm.tone_tl[s];
 +      if (svol & 0x80) svol = 0x7f;
 +    }
 +    
 +    if (!(part->u.fm.slot_mask & (1<<s))) {
 +      fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, svol);
 +    }
 +  }
 +}
 +
 +// 3273
 +static void fmp_set_timer_ch3(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp) {
 +  work->opna_writereg(work, 0x27, fmp->timer_ch3 | 0x2a);
 +}
 +
 +// 326a
 +static void fmp_set_tempo(struct fmdriver_work *work, struct driver_fmp *fmp) {
 +  work->opna_writereg(work, 0x26, fmp->timerb);
 +  fmp_set_timer_ch3(work, fmp);
 +}
 +
 +// 2979
 +static void fmp_part_keyoff_fm(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part) {
 +  // 2979
 +  uint8_t out = part->opna_keyon_out;
 +  if (part->type.fm_3) {
 +    fmp->fm3_slot_keyon &= (part->u.fm.slot_mask | 0x0f);
 +    out |= fmp->fm3_slot_keyon;
 +  }
 +  work->opna_writereg(work, 0x28, out);
 +}
 +
 +// 2961
 +static void fmp_part_keyoff(struct fmdriver_work *work, struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  part->ext_keyon = 0;
 +  part->status.tie_cont = false;
 +  if (part->type.adpcm) return;
 +  if (!part->type.ssg) {
 +    fmp_part_keyoff_fm(work, fmp, part);
 +  } else {
 +    // 29a9
 +    part->u.ssg.curr_vol = 0xff;
 +    if (!part->u.ssg.env.release_rate) part->actual_vol = 0;
 +    part->u.ssg.env_f.portamento = true;
 +    part->u.ssg.env_f.attack = false;
 +  }
 +  if (part->pdzf.mode) {
 +    part->pdzf.keyon = false;
 +    if (part->lfo_f.q && part->pdzf.env_param.rr) {
 +      part->pdzf.env_state.status = PDZF_ENV_REL;
 +      part->pdzf.env_state.cnt = part->pdzf.env_param.rr;
 +    } else {
 +      part->pdzf.env_state.status = PDZF_ENV_OFF;
 +      if (work->ppz8_functbl) {
 +        work->ppz8_functbl->channel_stop(
 +          work->ppz8,
 +          part->pdzf.ppz8_channel
 +        );
 +      }
 +    }
 +  }
 +}
 +
 +// 1fc8
 +static bool fmp_cmd62_tempo(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  uint8_t tempo = fmp_part_cmdload(fmp, part);
 +  fmp->timerb_bak = tempo;
 +  fmp->timerb = tempo;
 +  fmp_set_tempo(work, fmp);
 +  return true;
 +}
 +
 +// 2067
 +static bool fmp_cmd63_vol_d_fm(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part) {
 +  uint8_t vol = fmp_part_cmdload(fmp, part);
 +  part->current_vol = vol;
 +  part->actual_vol = vol;
 +  fmp_part_fm_vol(work, fmp, part);
 +  return true;
 +}
 +
 +static void fmp_ssg_ppz8_pdzf_mode_update(struct fmdriver_work *work,
 +                                          struct driver_fmp *fmp,
 +                                          struct fmp_part *part) {
 +  uint8_t prev_mode = part->pdzf.mode;
 +  uint8_t mask = 9 << part->opna_keyon_out;
 +  if ((fmp->ssg_mix & mask) == mask) {
 +    part->u.ssg.env_f.ppz = true;
 +    part->pdzf.mode = 0;
 +  } else {
 +    part->u.ssg.env_f.ppz = false;
 +    if (fmp->pdzf.mode == 2) {
 +      part->pdzf.mode =
 +        (part->u.ssg.envbak.startvol || part->u.ssg.envbak.attack_rate) 
 +        ? 0 : 2;
 +    }
 +  }
 +  if (prev_mode && !part->pdzf.mode) {
 +    part->pdzf.keyon = false;
 +    if (work->ppz8_functbl) {
 +      work->ppz8_functbl->channel_stop(
 +        work->ppz8,
 +        part->pdzf.ppz8_channel
 +      );
 +    }
 +  }
 +}
 +
 +// 23a2
 +static bool fmp_cmd63_mix_ssg(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part) {
 +  (void)work;
 +  uint8_t val = fmp_part_cmdload(fmp, part);
 +  if (val & 0x80) {
 +    // 23a6
 +    fmp->ssg_mix &= val;
 +    //fmp->ssg_mix_se &= val;
 +    val = fmp_part_cmdload(fmp, part);
 +    fmp->ssg_mix |= val;
 +    //fmp->ssg_mix_se |= val;
 +    
 +    fmp_ssg_ppz8_pdzf_mode_update(work, fmp, part);
 +    // 23e4
 +    work->opna_writereg(work, 0x07, fmp->ssg_mix);
 +    // 23ec
 +  } else {
 +    // 23f7
 +    fmp_part_cmdload(fmp, part);
 +    fmp_part_cmdload16(fmp, part);
 +    // TODO: PPZ
 +  }
 +  return true;
 +}
 +
 +// 2574
 +static bool fmp_cmd63_vol_d_adpcm(struct fmdriver_work *work,
 +                                  struct driver_fmp *fmp,
 +                                  struct fmp_part *part) {
 +  uint8_t vol = fmp_part_cmdload(fmp, part);
 +  part->current_vol = vol;
 +  part->actual_vol = vol;
 +  work->opna_writereg(work, 0x10b, vol);
 +  return true;
 +}
 +
 +// 1fd9
 +static bool fmp_cmd64_loop(struct fmdriver_work *work,
 +                           struct driver_fmp *fmp,
 +                           struct fmp_part *part) {
 +  (void)work;
 +  uint8_t loop = fmp_part_cmdload(fmp, part);
 +  if (--loop) {
 +    ((uint8_t *)fmp->data)[part->current_ptr-1] = loop;
 +    uint16_t ptr_diff = fmp_part_cmdload16(fmp, part);
 +    part->current_ptr -= ptr_diff;
 +  } else {
 +    part->current_ptr += 3;
 +    if (part->current_ptr < fmp->datalen) {
 +      ((uint8_t *)fmp->data)[part->current_ptr-4] = fmp->data[part->current_ptr-1];
 +    }
 +  }
 +  return true;
 +}
 +
 +// 1fee
 +static bool fmp_cmd65_loopend(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part) {
 +  (void)work;
 +  uint16_t ptr_diff = fmp_part_cmdload16(fmp, part);
 +  if ((part->current_ptr+ptr_diff-4) >= fmp->datalen) {
 +    part->current_ptr = 0xffff;
 +  } else if (fmp->data[part->current_ptr+ptr_diff-4] == 1) {
 +    part->current_ptr += ptr_diff;
 +    ((uint8_t *)fmp->data)[part->current_ptr-4] = fmp->data[part->current_ptr-1];
 +  }
 +  return true;
 +}
 +
 +// 1fff
 +static bool fmp_cmd66_tie(struct fmdriver_work *work,
 +                          struct driver_fmp *fmp,
 +                          struct fmp_part *part) {
 +  (void)work;
 +  (void)fmp;
 +  part->status.tie = true;
 +  part->status.slur = false;
 +  return true;
 +}
 +
 +// 200f
 +static bool fmp_cmd67_q(struct fmdriver_work *work,
 +                        struct driver_fmp *fmp,
 +                        struct fmp_part *part) {
 +  (void)work;
 +  //TODO
 +  if (fmp->datainfo.flags.q) {
 +    part->gate_cnt = fmp_part_cmdload(fmp, part);
 +  } else {
 +    part->gate_cmp = fmp_part_cmdload(fmp, part);
 +  }
 +  return true;
 +}
 +
 +// 201e
 +static bool fmp_cmd68_pitchbend(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp,
 +                                struct fmp_part *part) {
 +  (void)work;
 +  uint8_t note = fmp_part_cmdload(fmp, part);
 +  note += part->note_diff;
 +  if (note > 0x60) note = 0;
 +  part->pit.target_note = note;
 +  uint16_t freq;
 +  if (!part->type.ssg) {
 +    freq = fmp_fm_freq(note);
 +  } else {
 +    freq = fmp_part_ssg_freq(part, note) >> fmp_ssg_octave(note);
 +  }
 +  part->pit.target_freq = freq;
 +  part->pit.delay = fmp_part_cmdload(fmp, part);
 +  part->pit.speed = fmp_part_cmdload(fmp, part);
 +  part->pit.speed_cnt = part->pit.speed;
 +  uint8_t rate = fmp_part_cmdload(fmp, part);
 +  if (!part->type.fm) rate = -rate;
 +  part->pit.rate = rate;
 +  part->status.pitchbend = true;
 +  return true;
 +}
 +
 +// 25e3
 +static bool fmp_cmd68_adpcm(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  (void)work;
 +  fmp_part_cmdload(fmp, part);
 +  return true;
 +}
 +
 +// 205d
 +static bool fmp_cmd69_vol_fm(struct fmdriver_work *work,
 +                             struct driver_fmp *fmp,
 +                             struct fmp_part *part) {
 +  static const uint8_t voltbl[16] = {
 +    64, 59, 56, 52, 48, 42, 40, 37,
 +    34, 32, 29, 26, 24, 21, 18, 16,
 +  };
 +  uint8_t index = fmp_part_cmdload(fmp, part);
 +  uint8_t vol = (index < 16) ? voltbl[index] : 0x7f;
 +  part->current_vol = vol;
 +  part->actual_vol = vol;
 +  fmp_part_fm_vol(work, fmp, part);
 +  return true;
 +}
 +
 +// 2362
 +static void fmp_part_ppz8_vol(struct fmdriver_work *work,
 +                              struct fmp_part *part) {
 +  uint8_t vol = part->current_vol - 1;
 +  if (vol > 0xf) vol = 0xf;
 +  if (work->ppz8_functbl) {
 +    work->ppz8_functbl->channel_volume(work->ppz8,
 +                                    part->opna_keyon_out,
 +                                    vol);
 +  }
 +}
 +
 +// 234d
 +static void fmp_part_ssg_vol(struct fmdriver_work *work,
 +                             struct fmp_part *part) {
 +  fmp_part_pdzf_vol_update(work, part);
 +  if (!part->u.ssg.env_f.ppz) {
 +    // 2353
 +    uint8_t vol = part->current_vol;
 +    part->u.ssg.vol = ((unsigned)(vol-1) << 8)/vol;
 +  } else {
 +    fmp_part_ppz8_vol(work, part);
 +  }
 +}
 +
 +// 2348
 +static bool fmp_cmd69_vol_ssg(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part) {
 +  (void)work;
 +  part->current_vol = fmp_part_cmdload(fmp, part) + 1;
 +  // added to prevent zero division
 +  if (!part->current_vol) part->current_vol = 1;
 +  fmp_part_ssg_vol(work, part);
 +  return true;
 +}
 +
 +static bool fmp_cmd69_adpcm(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  // 205d: invalid command for adpcm part
 +  (void)work;
 +  fmp_part_cmdload(fmp, part);
 +  return true;
 +}
 +
 +static void fmp_part_fm_relvol(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part,
 +                               uint8_t vol) {
 +  uint8_t newvol = part->current_vol - vol;
 +  if (newvol & 0x80) {
 +    newvol = (vol & 0x80) ? 0x7f : 0;
 +  }
 +  part->current_vol = newvol;
 +  part->actual_vol = newvol;
 +  fmp_part_fm_vol(work, fmp, part);
 +}
 +
 +// 2080
 +static bool fmp_cmd6a_voldec_fm(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp,
 +                                struct fmp_part *part) {
 +  fmp_part_fm_relvol(work, fmp, part, -3);
 +  return true;
 +}
 +
 +// 2086
 +static bool fmp_cmd6b_volinc_fm(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp,
 +                                struct fmp_part *part) {
 +  fmp_part_fm_relvol(work, fmp, part, 3);
 +  return true;
 +}
 +
 +// 238a
 +static bool fmp_cmd6a_voldec_ssg(struct fmdriver_work *work,
 +                                 struct driver_fmp *fmp,
 +                                 struct fmp_part *part) {
 +  (void)fmp;
 +  (void)work;
 +  if (part->current_vol > 1) part->current_vol--;
 +  if (!part->current_vol) part->current_vol = 1;
 +  fmp_part_ssg_vol(work, part);
 +  return true;
 +}
 +// 2396
 +static bool fmp_cmd6b_volinc_ssg(struct fmdriver_work *work,
 +                                 struct driver_fmp *fmp,
 +                                 struct fmp_part *part) {
 +  (void)fmp;
 +  (void)work;
 +  if (part->current_vol < 0x10) part->current_vol++;
 +  fmp_part_ssg_vol(work, part);
 +  return true;
 +}
 +
 +// 25f5
 +static void fmp_part_adpcm_relvol(struct fmdriver_work *work,
 +                                  struct fmp_part *part,
 +                                  uint8_t vol) {
 +  uint16_t relvol = vol;
 +  if (vol & 0x80) relvol |= 0xff00;
 +  uint16_t newvol = part->current_vol + relvol;
 +  if (newvol & 0x100) {
 +    newvol = (vol & 0x80) ? 0 : 0xff;
 +  }
 +  part->current_vol = newvol;
 +  part->actual_vol = newvol;
 +  work->opna_writereg(work, 0x10b, newvol);
 +}
 +
 +// 259e
 +static bool fmp_cmd6a_voldec_adpcm(struct fmdriver_work *work,
 +                                   struct driver_fmp *fmp,
 +                                   struct fmp_part *part) {
 +  (void)fmp;
 +  fmp_part_adpcm_relvol(work, part, -3);
 +  return true;
 +}
 +
 +// 259e
 +static bool fmp_cmd6b_volinc_adpcm(struct fmdriver_work *work,
 +                                   struct driver_fmp *fmp,
 +                                   struct fmp_part *part) {
 +  (void)fmp;
 +  fmp_part_adpcm_relvol(work, part, 3);
 +  return true;
 +}
 +
 +// 208c
 +static bool fmp_cmd6c_kondelay(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part) {
 +  (void)work;
 +  part->keyon_delay = fmp_part_cmdload(fmp, part);
 +  return true;
 +}
 +
 +// 2090
 +static bool fmp_cmd6d_detune(struct fmdriver_work *work,
 +                             struct driver_fmp *fmp,
 +                             struct fmp_part *part) {
 +  (void)work;
 +  uint16_t detune = fmp_part_cmdload(fmp, part);
 +  if (detune & 0x80) detune |= 0xff00;
 +  part->detune = detune;
 +  return true;
 +}
 +
 +// 2095
 +static bool fmp_cmd6e_poke(struct fmdriver_work *work,
 +                           struct driver_fmp *fmp,
 +                           struct fmp_part *part) {
 +  (void)work;
 +  uint16_t port = fmp_part_cmdload(fmp, part);
 +  uint8_t data = fmp_part_cmdload(fmp, part);
 +  // 3222?
 +  if (part->opna_keyon_out & 0x04) port |= 0x100;
 +  work->opna_writereg(work, port, data);
 +  return true;
 +}
 +
 +// 20b7
 +static void fmp_part_sync(struct fmp_part *part) {
 +  if (part->sync != 1) part->sync = 0;
 +}
 +
 +// 209e
 +static bool fmp_cmd6f_sync(struct fmdriver_work *work,
 +                           struct driver_fmp *fmp,
 +                           struct fmp_part *part) {
 +  (void)work;
 +  // sync
 +  // command S{channels}
 +  uint8_t val = fmp_part_cmdload(fmp, part);
 +  if (!(val & 0x80)) {
 +    for (int i = 0; i < 3; i++) {
 +      if (val & (1<<i)) fmp_part_sync(&fmp->parts[FMP_PART_FM_1+i]);
 +    }
 +    for (int i = 0; i < 3; i++) {
 +      if (val & (1<<(i+3))) fmp_part_sync(&fmp->parts[FMP_PART_SSG_1+i]);
 +    }
 +  } else {
 +    for (int i = 0; i < 3; i++) {
 +      if (val & (1<<i)) fmp_part_sync(&fmp->parts[FMP_PART_FM_4+i]);
 +    }
 +    if (val & (1<<3)) fmp_part_sync(&fmp->parts[FMP_PART_ADPCM]);
 +    if (val & (1<<4)) {
 +      if (fmp->rhythm.sync != 1) fmp->rhythm.sync = 0;
 +    }
 +  }
 +  return true;
 +}
 +
 +// 20eb
 +static bool fmp_cmd70_wait(struct fmdriver_work *work,
 +                           struct driver_fmp *fmp,
 +                           struct fmp_part *part) {
 +  (void)work;
 +  (void)fmp;
 +  // command W
 +  part->sync = 2;
 +  return false;
 +}
 +
 +// 20f6
 +static bool fmp_cmd71_tone_fm(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part) {
 +  uint8_t tone = fmp_part_cmdload(fmp, part);
 +  part->tone = tone;
 +  for (int i = 0; i < 4; i++) {
 +    if (part->u.fm.slot_mask & (1<<i)) continue;
 +    fmp_part_fm_reg_write(work, part, OPNA_SLRR+4*i, 0x0f);
 +  }
 +  for (int i = 0; i < 4; i++) {
 +    part->u.fm.slot_rel_vol[i] = 0;
 +  }
 +  // 211b
 +  uint16_t fmtoneptr = fmp->datainfo.fmtoneptr + (tone*25);
 +  for (int i = 0; i < 4; i++) {
 +    part->u.fm.tone_tl[i] = fmp->data[fmtoneptr+4+i];
 +  }
 +  for (int p = 0; p < 6; p++) {
 +    for (int s = 0; s < 4; s++) {
 +      if (part->u.fm.slot_mask & (1<<s)) continue;
 +      fmp_part_fm_reg_write(work, part,
 +                            OPNA_DTMUL+(p*0x10)+4*s,
 +                            fmp->data[fmtoneptr+(p*4)+s]);
 +    }
 +  }
 +  uint8_t fbalg = fmp->data[fmtoneptr+0x18];
 +  // 2160
 +  if (part->type.fm_3) {
 +    if (part->u.fm.slot_mask & 1) {
 +      fbalg = (fbalg & 0x38) | (fmp->fm3_alg & 0x07);
 +    }
 +  }
 +  fmp_part_fm_reg_write(work, part, OPNA_FBALG, fbalg);
 +  if (part->type.fm_3) {
 +    fmp->fm3_alg = fbalg & 0x07;
 +  }
 +  uint8_t alg = fbalg & 0x7;
 +  static const uint8_t alg_vol_tbl[8] = {
 +    0x8, 0x8, 0x8, 0x8, 0xc, 0xe, 0xe, 0xf,
 +  };
 +  part->slot_vol_mask = alg_vol_tbl[alg];
 +  fmp_part_fm_vol(work, fmp, part);
 +  return true;
 +}
 +
 +// 2458
 +static void fmp_envreset_ssg(struct fmp_part *part) {
 +  part->u.ssg.env.startvol = part->u.ssg.envbak.startvol;
 +  part->u.ssg.env.attack_rate = part->u.ssg.envbak.attack_rate;
 +  part->u.ssg.env.decay_rate = part->u.ssg.envbak.decay_rate;
 +  part->u.ssg.env.sustain_lv = part->u.ssg.envbak.sustain_lv;
 +  part->u.ssg.env.sustain_rate = part->u.ssg.envbak.sustain_rate;
 +  part->u.ssg.env.release_rate = part->u.ssg.envbak.release_rate;
 +}
 +
 +// 2458
 +static bool fmp_cmd71_envreset_ssg(struct fmdriver_work *work,
 +                                    struct driver_fmp *fmp,
 +                                    struct fmp_part *part) {
 +  (void)work;
 +  fmp_part_cmdload(fmp, part);
 +  fmp_envreset_ssg(part);
 +  return true;
 +}
 +
 +// 25a8
 +static bool fmp_cmd71_tone_adpcm(struct fmdriver_work *work,
 +                                 struct driver_fmp *fmp,
 +                                 struct fmp_part *part) {
 +  (void)work;
 +  uint8_t tone = fmp_part_cmdload(fmp, part);
 +  part->tone = tone;
 +  if (!(tone & 0x80)) {
 +    // 25b4
 +    uint16_t adpcmptr = fmp->datainfo.adpcmptr+6*tone;
 +    if (adpcmptr+6 <= fmp->datalen) {
 +      part->u.adpcm.startaddr = read16le(&fmp->data[adpcmptr+0]);
 +      part->u.adpcm.endaddr = read16le(&fmp->data[adpcmptr+2]);
 +      part->u.adpcm.deltat = read16le(&fmp->data[adpcmptr+4]);
 +    }
 +  } else {
 +    // 25cc
 +    tone &= 0x7f;
 +    part->u.adpcm.startaddr = fmp->adpcm_startaddr[tone];
 +    part->u.adpcm.endaddr = fmp->adpcm_endaddr[tone];
 +    part->u.adpcm.deltat = fmp->adpcm_deltat;
 +  }
 +  return true;
 +}
 +
 +// 20f2
 +static bool fmp_cmd72_deflen(struct fmdriver_work *work,
 +                             struct driver_fmp *fmp,
 +                             struct fmp_part *part) {
 +  (void)work;
 +  part->default_len = fmp_part_cmdload(fmp, part);
 +  return true;
 +}
 +
 +// 1fac
 +static bool fmp_cmd73_relvol_fm(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp,
 +                                struct fmp_part *part) {
 +  fmp_part_fm_relvol(work, fmp, part, fmp_part_cmdload(fmp, part));
 +  return true;
 +}
 +
 +// 2424
 +static bool fmp_cmd73_noise_ssg(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp,
 +                                struct fmp_part *part) {
 +  // TODO: ignoring se flag
 +  uint8_t val = fmp_part_cmdload(fmp, part);
 +  part->u.ssg.env_f.noise = true;
 +  if (val & 0x80) {
 +    part->u.ssg.env_f.noise = false;
 +  } else {
 +    fmp->ssg_noise_freq = val;
 +    work->opna_writereg(work, 0x06, val);
 +  }
 +  return true;
 +}
 +
 +// 25f5
 +static bool fmp_cmd73_relvol_adpcm(struct fmdriver_work *work,
 +                                   struct driver_fmp *fmp,
 +                                   struct fmp_part *part) {
 +  fmp_part_adpcm_relvol(work, part, fmp_part_cmdload(fmp, part));
 +  return true;
 +}
 +
 +/*
 +// ????
 +// 1b64
 +static void fmp() {
 +  
 +}
 +*/
 +
 +// 2466
 +static bool fmp_cmd74_loop(struct fmdriver_work *work,
 +                           struct driver_fmp *fmp,
 +                           struct fmp_part *part) {
 +  (void)work;
 +  // TODO: se flag
 +  fmp->part_loop_bit &= ~part->part_bit;
 +  if (!fmp->part_loop_bit) {
 +    if (!--fmp->loop_dec) {
 +      fmp->status.looped = true;
 +      // al=2; 1b64();
 +    }
 +    // 248c
 +    fmp->loop_cnt++;
 +    fmp->part_loop_bit = fmp->part_playing_bit;
 +    // al=2; 1b64();
 +  }
 +  // 24a2
 +  uint16_t ptr = part->loop_ptr;
 +  if (ptr != 0xffff) {
 +    part->current_ptr = ptr;
 +    return true;
 +  } else {
 +    fmp->part_loop_bit &= ~part->part_bit;
 +    fmp->part_playing_bit &= ~part->part_bit;
 +    if (!fmp->part_playing_bit) {
 +      // 24bc
 +      // 3e16();
 +      fmp->status.stopped = true;
 +      fmp->status.looped = true;
 +    }
 +    // 24f0
 +    if (!part->type.rhythm) {
 +      part->prev_note = 0x61; // rest
 +      part->status.off = true;
 +      if (part->type.ssg) {
 +        part->actual_vol = 0;
 +        // 0x6a?
 +      }
 +      fmp_part_keyoff(work, fmp, part);
 +    } else {
 +      // 2512
 +      fmp->rhythm.status = 1;
 +    }
 +    return false;
 +  }
 +}
 +
 +// 22e0
 +static bool fmp_cmd75_lfo(struct fmdriver_work *work,
 +                          struct driver_fmp *fmp,
 +                          struct fmp_part *part) {
 +  (void)work;
 +  uint8_t val = fmp_part_cmdload(fmp, part);
 +  if (val & 0x80) {
 +    // LFO on/off
 +    // MD/S command
 +    // 2313
 +    bool set = val & 0x40;
 +    if (val & (1<<0)) part->lfo_f.e = set;
 +    if (val & (1<<1)) part->lfo_f.w = set;
 +    if (val & (1<<2)) part->lfo_f.a = set;
 +    if (val & (1<<3)) part->lfo_f.r = set;
 +    if (val & (1<<4)) part->lfo_f.q = set;
 +    if (val & (1<<5)) part->lfo_f.p = set;
 +    if (!set) {
 +      if (!part->type.fm) {
 +        part->actual_freq = fmp_part_ssg_freq(part, part->prev_note);
 +        part->prev_freq = 0;
 +      } else {
 +        part->actual_freq = fmp_fm_freq(part->prev_note);
 +        part->prev_freq = 0;
 +      }
 +    } else {
 +      if (part->lfo_f.e) part->lfo_f.a = false;
 +    }
 +  } else {
 +    // 22e4
 +    // EON/EOFF (FM3)
 +    if (val & 0x40) {
 +      // 22f1
 +      fmp->timer_ch3 |= 0x40;
 +      if (val & 0x20) {
 +        fmp->ch3_se_freqdiff[0] = u8s16(fmp_part_cmdload(fmp, part));
 +        fmp->ch3_se_freqdiff[1] = u8s16(fmp_part_cmdload(fmp, part));
 +        fmp->ch3_se_freqdiff[2] = u8s16(fmp_part_cmdload(fmp, part));
 +        fmp->ch3_se_freqdiff[3] = u8s16(fmp_part_cmdload(fmp, part));
 +      }
 +    } else {
 +      // 22e9
 +      fmp->timer_ch3 &= 0x3f;
 +    }
 +    fmp_set_timer_ch3(work, fmp);
 +  }
 +  return true;
 +}
 +
 +// 2fa2
 +static void fmp_init_lfo(struct fmp_lfo *lfo) {
 +  lfo->delay_cnt = lfo->delay;
 +  lfo->speed_cnt = 1;
 +  lfo->depth_cnt = lfo->depth >> 1;
 +  int16_t rate2 = u16s16(lfo->rate);
 +  if (lfo->waveform != FMP_LFOWF_TRIANGLE) {
 +    rate2 *= lfo->depth;
 +    // 2fc4
 +    if (lfo->waveform == FMP_LFOWF_STAIRCASE) {
 +      rate2 >>= 1;
 +      lfo->depth_cnt = 1;
 +    }
 +  }
 +  // 2fd0
 +  if (lfo->waveform == FMP_LFOWF_RANDOM) {
 +    rate2 = 0;
 +  }
 +  // 2fd8
 +  lfo->rate2 = rate2;
 +}
 +
 +// 2f84
 +static void fmp_part_init_lfo_pqr(struct fmp_part *part) {
 +  if (part->lfo_f.p) fmp_init_lfo(&part->lfo[FMP_LFO_P]);
 +  if (part->lfo_f.q) fmp_init_lfo(&part->lfo[FMP_LFO_Q]);
 +  if (part->lfo_f.r) fmp_init_lfo(&part->lfo[FMP_LFO_R]);
 +}
 +
 +// 21a6
 +static void fmp_cmd_lfo(struct driver_fmp *fmp,
 +                        struct fmp_part *part,
 +                        struct fmp_lfo *lfo) {
 +  lfo->delay = fmp_part_cmdload(fmp, part);
 +  lfo->speed = fmp_part_cmdload(fmp, part);
 +  uint16_t rate = fmp_part_cmdload(fmp, part);
 +  if (rate & 0x80) rate |= 0xff00;
 +  lfo->rate = rate;
 +  lfo->rate2 = rate;
 +  lfo->depth = fmp_part_cmdload(fmp, part);
 +  lfo->waveform = fmp_part_cmdload(fmp, part);
 +  if (lfo->waveform == 6) {
 +    lfo->rate = (lfo->rate&0xff)*lfo->depth;
 +  }
 +  part->lfo_f.lfo = true;
 +  fmp_part_init_lfo_pqr(part);
 +}
 +
 +// 21a1
 +static bool fmp_cmd76_lfo_p(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  (void)work;
 +  part->lfo_f.p = true;
 +  fmp_cmd_lfo(fmp, part, &part->lfo[FMP_LFO_P]);
 +  return true;
 +}
 +// 21d0
 +static bool fmp_cmd77_lfo_q(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  (void)work;
 +  part->lfo_f.q = true;
 +  struct fmp_lfo *lfo = &part->lfo[FMP_LFO_Q];
 +  fmp_cmd_lfo(fmp, part, lfo);
 +  part->pdzf.env_param.rr = lfo->delay - 2;
 +  part->pdzf.env_param.sr = lfo->speed;
 +  part->pdzf.env_param.dd = u16s16(lfo->rate);
 +  part->pdzf.env_param.al = lfo->depth;
 +  return true;
 +}
 +// 21da
 +static bool fmp_cmd78_lfo_r(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  (void)work;
 +  part->lfo_f.r = true;
 +  struct fmp_lfo *lfo = &part->lfo[FMP_LFO_R];
 +  fmp_cmd_lfo(fmp, part, lfo);
 +  if (part->pdzf.mode != 2 || lfo->depth == 0) {
 +    part->pdzf.voice = lfo->speed;
 +    if (part->pdzf.mode == 2 && (lfo->delay != 2)) {
 +      uint8_t upan = lfo->delay - 2;
 +      if (upan < 10) {
 +        part->pdzf.pan = upan - 5;
 +      }
 +    } else {
 +      int8_t pan = u8s16(lfo->rate);
 +      if (pan < -4) pan = -4;
 +      if (pan > 4) pan = 4;
 +      part->pdzf.pan = pan;
 +    }
 +  }
 +  return true;
 +}
 +
 +static void fmp_pdzf_loop_freq(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part,
 +                               uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) {
 +  if (!part->pdzf.mode) return;
 +  if (part->pdzf.mode != 2 || part->lfo[2].depth == 0) {
 +    uint32_t start = (d0 << 8) | d1;
 +    uint32_t end = (d2 << 8) | d3;
 +    if (start == 0xffff) start = (uint32_t)-1;
 +    if (end == 0xffff) end = (uint32_t)-1;
 +    if ((start != (uint32_t)-1) && (end != (uint32_t)-1)) {
 +      if (start >= end) {
 +        start = (uint32_t)-1;
 +        end = (uint32_t)-1;
 +      }
 +    }
 +    if (work->ppz8_functbl) {
 +      work->ppz8_functbl->channel_loopoffset(
 +        work->ppz8,
 +        part->pdzf.ppz8_channel,
 +        start, end
 +      );
 +    }
 +  }
 +  if (part->pdzf.mode == 2) {
 +    if (part->lfo[2].depth == 1 || part->lfo[2].depth == 2) {
 +      uint32_t addr = ((uint32_t)d0) << 24;
 +      addr |= ((uint32_t)d1) << 16;
 +      addr |= ((uint32_t)d2) << 8;
 +      addr |= d3;
 +      if (part->lfo[2].depth == 1) {
 +        part->pdzf.loopstart32 = addr;
 +      } else {
 +        part->pdzf.loopend32 = addr;
 +      }
 +      if (work->ppz8_functbl) {
 +        work->ppz8_functbl->channel_loopoffset(
 +          work->ppz8,
 +          part->pdzf.ppz8_channel,
 +          part->pdzf.loopstart32, part->pdzf.loopend32
 +        );
 +      }
 +    }
 +  }
 +}
 +
 +static bool fmp_cmd79_lfo_a_fm(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp,
 +                            struct fmp_part *part) {
 +  (void)work;
 +  part->lfo_f.a = true;
 +  part->lfo_f.e = false;
 +  uint8_t v = fmp_part_cmdload(fmp, part);
 +  part->u.fm.alfo.delay = v;
 +  part->u.fm.alfo.delay_cnt = v;
 +  v = fmp_part_cmdload(fmp, part);
 +  part->u.fm.alfo.speed = v;
 +  part->u.fm.alfo.speed_cnt = v;
 +  v = fmp_part_cmdload(fmp, part);
 +  part->u.fm.alfo.rate = v;
 +  part->u.fm.alfo.rate_orig = v;
 +  v = fmp_part_cmdload(fmp, part);
 +  part->u.fm.alfo.depth = v;
 +  part->u.fm.alfo.depth_cnt = v;
 +  fmp_pdzf_loop_freq(work, fmp, part,
 +                     part->u.fm.alfo.delay, part->u.fm.alfo.speed,
 +                     part->u.fm.alfo.rate, part->u.fm.alfo.depth
 +  );
 +  return true;
 +}
 +
 +// 244f
 +static bool fmp_cmd79_env_ssg(struct fmdriver_work *work,
 +                          struct driver_fmp *fmp,
 +                          struct fmp_part *part) {
 +  (void)work;
 +  part->u.ssg.envbak.startvol = fmp_part_cmdload(fmp, part);
 +  part->u.ssg.envbak.attack_rate = fmp_part_cmdload(fmp, part);
 +  part->u.ssg.envbak.decay_rate = fmp_part_cmdload(fmp, part);
 +  part->u.ssg.envbak.sustain_lv = fmp_part_cmdload(fmp, part);
 +  part->u.ssg.envbak.sustain_rate = fmp_part_cmdload(fmp, part);
 +  part->u.ssg.envbak.release_rate = fmp_part_cmdload(fmp, part);
 +  fmp_envreset_ssg(part);
 +  fmp_ssg_ppz8_pdzf_mode_update(work, fmp, part);
 +  fmp_pdzf_loop_freq(work, fmp, part,
 +                     part->u.ssg.envbak.decay_rate,
 +                     part->u.ssg.envbak.sustain_lv,
 +                     part->u.ssg.envbak.sustain_rate,
 +                     part->u.ssg.envbak.release_rate
 +  );
 +  return true;
 +}
 +
 +// 2009
 +static bool fmp_cmd7a_tie(struct fmdriver_work *work,
 +                          struct driver_fmp *fmp,
 +                          struct fmp_part *part) {
 +  (void)work;
 +  (void)fmp;
 +  part->status.tie = true;
 +  part->status.slur = true;
 +  return true;
 +}
 +
 +// 1f9e
 +static bool fmp_cmd7b_transpose(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp,
 +                                struct fmp_part *part) {
 +  (void)work;
 +  uint8_t val = fmp_part_cmdload(fmp, part);
 +  if (val) part->note_diff += val;
 +  else part->note_diff = 0;
 +  return true;
 +}
 +
 +// 2229
 +static void fmp_part_write_pan(struct fmdriver_work *work,
 +                               struct fmp_part *part) {
 +  fmp_part_fm_reg_write(work, part, OPNA_LRAMSPMS, part->pan_ams_pms);
 +}
 +
 +static void fmp_fm_pdzf_mode_update(struct fmdriver_work *work,
 +                                    struct driver_fmp *fmp,
 +                                    struct fmp_part *part) {
 +  uint8_t prev_mode = part->pdzf.mode;
 +  part->pdzf.mode = (part->u.fm.slot_mask == 0xff) ? fmp->pdzf.mode : 0;
 +  if (prev_mode && !part->pdzf.mode) {
 +    part->pdzf.keyon = false;
 +    if (work->ppz8_functbl) {
 +      work->ppz8_functbl->channel_stop(
 +        work->ppz8,
 +        part->pdzf.ppz8_channel
 +      );
 +    }
 +  }
 +}
 +
 +// 2206
 +static bool fmp_cmd7c_lfo_pan_fm(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part) {
 +  uint8_t val1 = fmp_part_cmdload(fmp, part);
 +  if (val1 & 0x01) {
 +    uint8_t val2 = fmp_part_cmdload(fmp, part);
 +    uint8_t val3 = fmp_part_cmdload(fmp, part);
 +    if (val3 & 0x80) {
 +      if (val3 & 0x20) {
 +        // 229b
 +        // HLFO delay
 +        // 7c 01 val a0: DH
 +        part->u.fm.hlfo_delay = val2;
 +      } else {
 +        // 227c
 +        // HLFO on/off
 +        // 7c 01 00 c0: SH1/MDH1: HLFO ON
 +        // 7c 01 00 80: SH0/MDH0: HLFO OFF
 +        val3 &= 0x7f;
 +        val3 >>= 3;
 +        part->u.fm.hlfo_freq &= 0xf7;
 +        part->u.fm.hlfo_freq |= val3;
 +        work->opna_writereg(work, 0x22, part->u.fm.hlfo_freq);
 +        if (!val3) {
 +          part->pan_ams_pms &= 0xc0;
 +          fmp_part_write_pan(work, part);
 +        }
 +      }
 +    } else {
 +      // 226b
 +      // HLFO parameters
 +      // 7c 01 val2 val3 val4: MH
 +      part->u.fm.hlfo_delay = val2;
 +      part->u.fm.hlfo_freq = val3 | 0x08;
 +      part->u.fm.hlfo_apms = fmp_part_cmdload(fmp, part);
 +    }
 +  } else {
 +    // 220a
 +    if (val1 & 0x02) {
 +      // 229f
 +      // LFO W settings
 +      // 7c 02 xx xx xx xx xx: MW
 +      part->lfo_f.w = true;
 +      uint8_t val = fmp_part_cmdload(fmp, part);
 +      part->u.fm.wlfo.delay = val;
 +      part->u.fm.wlfo.delay_cnt = val;
 +      val = fmp_part_cmdload(fmp, part);
 +      part->u.fm.wlfo.speed = val;
 +      part->u.fm.wlfo.speed_cnt = val;
 +      val = fmp_part_cmdload(fmp, part);
 +      part->u.fm.wlfo.rate = val;
 +      part->u.fm.wlfo.rate_orig = val;
 +      part->u.fm.wlfo.rate_curr = 0;
 +      val = fmp_part_cmdload(fmp, part);
 +      part->u.fm.wlfo.depth = val;
 +      part->u.fm.wlfo.depth_cnt = val;
 +      val = fmp_part_cmdload(fmp, part);
 +      part->u.fm.wlfo.sync = val;
 +      
 +      int pdzf_i = (part - &fmp->parts[FMP_PART_FM_EX1]);
 +      if ((pdzf_i == 1) || (pdzf_i == 2)) {
 +        struct pdzf_rhythm *pr = &fmp->pdzf.rhythm[pdzf_i-1];
 +        pr->voice[0] = part->u.fm.wlfo.delay;
 +        pr->voice[1] = part->u.fm.wlfo.speed;
 +        int16_t panpot = u8s16(part->u.fm.wlfo.rate);
 +        if (panpot < -4) panpot = -4;
 +        if (panpot > 4) panpot = 4;
 +        pr->pan = panpot;
 +        pr->note = part->u.fm.wlfo.depth;
 +        pr->enabled = true;
 +      }
 +    } else {
 +      // 2211
 +      if (val1 & 0x04) {
 +        // 22c7
 +        // mask slot
 +        // 7c 04 val: EX
 +        uint8_t val = fmp_part_cmdload(fmp, part);
 +        fmp->timer_ch3 |= 0x40;
 +        if (val == 0xff) {
 +          fmp_part_keyoff(work, fmp, part);
 +          fmp->timer_ch3 &= 0x3f;
 +        }
 +        part->u.fm.slot_mask = val;
 +        fmp_fm_pdzf_mode_update(work, fmp, part);
 +      } else {
 +        // 2218
 +        // PAN
 +        // 7c val1: P
 +        part->pan_ams_pms &= 0x3f;
 +        part->pan_ams_pms |= val1;
 +        if (part->type.fm_3) {
 +          // 2236
 +          fmp->parts[FMP_PART_FM_3].pan_ams_pms &= 0x3f;
 +          fmp->parts[FMP_PART_FM_3].pan_ams_pms |= val1;
 +          fmp->parts[FMP_PART_FM_EX1].pan_ams_pms &= 0x3f;
 +          fmp->parts[FMP_PART_FM_EX1].pan_ams_pms |= val1;
 +          fmp->parts[FMP_PART_FM_EX2].pan_ams_pms &= 0x3f;
 +          fmp->parts[FMP_PART_FM_EX2].pan_ams_pms |= val1;
 +          fmp->parts[FMP_PART_FM_EX3].pan_ams_pms &= 0x3f;
 +          fmp->parts[FMP_PART_FM_EX3].pan_ams_pms |= val1;
 +        }
 +        fmp_part_write_pan(work, part);
 +      }
 +    }
 +  }
 +  return true;
 +}
 +
 +// 1f78
 +static bool fmp_cmd7c_tone_ssg(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part) {
 +  (void)work;
 +  part->tone = fmp_part_cmdload(fmp, part);
 +  uint16_t toneptr = fmp->datainfo.ssgtoneptr + (part->tone*6);
 +  if ((fmp->datainfo.ssgtoneptr != 0xffff) && (fmp->datalen >= (toneptr+6))) {
 +    part->u.ssg.envbak.startvol = fmp->data[toneptr+0];
 +    part->u.ssg.envbak.attack_rate = fmp->data[toneptr+1];
 +    part->u.ssg.envbak.decay_rate = fmp->data[toneptr+2];
 +    part->u.ssg.envbak.sustain_lv = fmp->data[toneptr+3];
 +    part->u.ssg.envbak.sustain_rate = fmp->data[toneptr+4];
 +    part->u.ssg.envbak.release_rate = fmp->data[toneptr+5];
 +  }
 +  fmp_ssg_ppz8_pdzf_mode_update(work, fmp, part);
 +  fmp_pdzf_loop_freq(work, fmp, part,
 +                    part->u.ssg.envbak.decay_rate,
 +                    part->u.ssg.envbak.sustain_lv,
 +                    part->u.ssg.envbak.sustain_rate,
 +                    part->u.ssg.envbak.release_rate
 +  );
 +  fmp_envreset_ssg(part);
 +  return true;
 +}
 +
 +// 25e4
 +static void fmp_adpcm_pan(struct fmdriver_work *work,
 +                          struct driver_fmp *fmp,
 +                          struct fmp_part *part,
 +                          uint8_t val) {
 +  val &= 0xc0;
 +  part->pan_ams_pms = val;
 +  work->opna_writereg(work, 0x101, val | fmp->adpcm_c1);
 +}
 +
 +// 25e4
 +static bool fmp_cmd7c_pan_adpcm(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp,
 +                                struct fmp_part *part) {
 +  fmp_adpcm_pan(work, fmp, part, fmp_part_cmdload(fmp, part));
 +  return true;
 +}
 +
 +// 1f70
 +static bool fmp_cmd7d_sync(struct fmdriver_work *work,
 +                           struct driver_fmp *fmp,
 +                           struct fmp_part *part) {
 +  (void)work;
 +  // unknown MML
 +  fmp->sync.data = fmp_part_cmdload(fmp, part);
 +  fmp->sync.cnt++;
 +  return true;
 +}
 +
 +// 1f46
 +static bool fmp_cmd7e_loop_det(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part) {
 +  (void)work;
 +  uint8_t val1 = fmp_part_cmdload(fmp, part);
 +  if (val1) {
 +    fmp->loop_dec = val1;
 +    fmp->loop_times = val1;
 +    // Fadeout not implemented
 +  } else {
 +    // detune relative
 +    // DX
 +    int16_t det = u8s16(fmp_part_cmdload(fmp, part));
 +    part->detune += det;
 +  }
 +  return true;
 +}
 +// 1f2c
 +static bool fmp_cmd7f_lfo_delay(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp,
 +                                struct fmp_part *part) {
 +  (void)work;
 +  uint8_t val1 = fmp_part_cmdload(fmp, part);
 +  uint8_t delay = fmp_part_cmdload(fmp, part);
 +  if (val1 < 3) {
 +    part->lfo[val1].delay = delay;
 +  } else if (val1 == 3) {
 +    // 1f40
 +    part->u.fm.alfo.delay = delay;
 +  }
 +  return true;
 +}
 +
 +// 1eaf
 +static void fmp_update_slot_rel_vol(struct fmdriver_work *work,
 +                                    struct driver_fmp *fmp,
 +                                    struct fmp_part *part) {
 +  uint8_t pvol = part->actual_vol+fmp->fm_vol;
 +  if (pvol & 0x80) {
 +    pvol = (fmp->fm_vol & 0x80) ? 0 : 0x7f;
 +  }
 +  // 1ed4
 +  for (int s = 0; s < 4; s++) {
 +    uint8_t svol;
 +    if (!(part->slot_vol_mask & (1<<s))) {
 +      // 1ee2
 +      svol = part->u.fm.tone_tl[s] - part->u.fm.slot_rel_vol[s];
 +      if (svol & 0x80) {
 +        svol = (part->u.fm.slot_rel_vol[s] & 0x80) ? 0x7f : 0;
 +      }
 +    } else {
 +      // 1f00
 +      svol = pvol - part->u.fm.slot_rel_vol[s];
 +      if (svol & 0x80) {
 +        svol = (part->u.fm.slot_rel_vol[s] & 0x80) ? 0x7f : 0;
 +      }
 +      svol += part->u.fm.tone_tl[s];
 +      if (svol & 0x80) svol = 0x7f;
 +    }
 +    if (!(part->u.fm.slot_mask & (1<<s))) {
 +      fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, svol);
 +    }
 +    // 1f20
 +  }
 +}
 +
 +// 1e68
 +static bool fmp_cmde2_slotrelvol_set_fm(struct fmdriver_work *work,
 +                                        struct driver_fmp *fmp,
 +                                        struct fmp_part *part) {
 +  uint8_t val = fmp_part_cmdload(fmp, part);
 +  uint8_t mask = fmp_part_cmdload(fmp, part);
 +  for (int i = 0; i < 4; i++) {
 +    if (mask & (1<<i)) {
 +      part->u.fm.slot_rel_vol[i] = val;
 +    }
 +  }
 +  fmp_update_slot_rel_vol(work, fmp, part);
 +  return true;
 +}
 +
 +// 1e7d
 +static bool fmp_cmde3_slotrelvol_add_fm(struct fmdriver_work *work,
 +                                        struct driver_fmp *fmp,
 +                                        struct fmp_part *part) {
 +  uint8_t val = fmp_part_cmdload(fmp, part);
 +  uint8_t mask = fmp_part_cmdload(fmp, part);
 +  for (int i = 0; i < 4; i++) {
 +    if (!(mask & (1<<i))) continue;
 +    int svol = part->u.fm.slot_rel_vol[i] + val;
 +    if (svol > 0x7f) {
 +      svol = (val & 0x80) ? 0 : 0x7f;
 +    }
 +    part->u.fm.slot_rel_vol[i] = svol;
 +  }
 +  fmp_update_slot_rel_vol(work, fmp, part);
 +  return true;
 +}
 +
 +// 2c5b
 +static void fmp_part_lfo_calc(struct driver_fmp *fmp,
 +                              struct fmp_part *part, int num) {
 +  struct fmp_lfo *lfo = &part->lfo[num];
 +  uint8_t waveform = lfo->waveform;
 +  if (waveform > 6) waveform = 0;
 +  switch (waveform) {
 +  case FMP_LFOWF_TRIANGLE:
 +  case FMP_LFOWF_TRIANGLE2:
 +    // 2c7f
 +    if (!lfo->delay) return;
 +    if (--lfo->delay_cnt) return;
 +    lfo->delay_cnt = 1;
 +    if (--lfo->speed_cnt) return;
 +    lfo->speed_cnt = lfo->speed;
 +    part->actual_freq += lfo->rate2;
 +    if (--lfo->depth_cnt) return;
 +    lfo->depth_cnt = lfo->depth;
 +    lfo->rate2 = -lfo->rate2;
 +    return;
 +  case FMP_LFOWF_SAWTOOTH:
 +    // 2cae
 +    if (!lfo->delay) return;
 +    if (--lfo->delay_cnt) return;
 +    lfo->delay_cnt = 1;
 +    if (--lfo->speed_cnt) return;
 +    lfo->speed_cnt = lfo->speed;
 +    part->actual_freq += lfo->rate;
 +    if (--lfo->depth_cnt) return;
 +    lfo->depth_cnt = lfo->depth;
 +    part->actual_freq -= lfo->rate2;
 +    return;
 +  case FMP_LFOWF_SQUARE:
 +    // 2ce0
 +    if (!lfo->delay) return;
 +    if (--lfo->delay_cnt) return;
 +    lfo->delay_cnt = 1;
 +    if (--lfo->speed_cnt) return;
 +    lfo->speed_cnt = lfo->speed;
 +    part->actual_freq += (lfo->rate2 >> (lfo->depth_cnt != 0));
 +    if (lfo->depth_cnt) lfo->depth_cnt = 0;
 +    lfo->rate2 = -lfo->rate2;
 +    return;
 +  case FMP_LFOWF_LINEAR:
 +    // 2d10
 +    if (!lfo->delay) return;
 +    if (!lfo->depth_cnt) return;
 +    if (--lfo->delay_cnt) return;
 +    lfo->delay_cnt = 1;
 +    if (--lfo->speed_cnt) return;
 +    lfo->speed_cnt = lfo->speed;
 +    part->actual_freq += lfo->rate;
 +    lfo->depth_cnt--;
 +    return;
 +  case FMP_LFOWF_STAIRCASE:
 +    // 2d3a
 +    if (!lfo->delay) return;
 +    if (--lfo->delay_cnt) return;
 +    lfo->delay_cnt = 1;
 +    if (--lfo->speed_cnt) return;
 +    lfo->speed_cnt = lfo->speed;
 +    part->actual_freq += lfo->rate2;
 +    if (--lfo->depth_cnt) return;
 +    lfo->depth_cnt = 2;
 +    lfo->rate2 = -lfo->rate2;
 +    return;
 +  case FMP_LFOWF_RANDOM:
 +    if (!lfo->delay) return;
 +    if (--lfo->delay_cnt) return;
 +    lfo->delay_cnt = 1;
 +    if (--lfo->speed_cnt) return;
 +    lfo->speed_cnt = lfo->speed;
 +    {
 +      uint8_t rand = fmp_rand71(fmp);
 +      uint16_t a = (lfo->rate>>1) - (rand*lfo->depth_cnt)%lfo->rate;
 +      lfo->depth_cnt = rand;
 +      part->actual_freq -= lfo->rate2;
 +      part->actual_freq += a;
 +      lfo->rate2 = a;
 +      return;
 +    }
 +  }
 +}
 +
 +// 27c9
 +static void fmp_part_keyon_fm(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part) {
 +  if (part->lfo_f.portamento) return;
 +  // 27df
 +  uint8_t out = part->opna_keyon_out;
 +  if (part->type.fm_3) {
 +    fmp->fm3_slot_keyon |= ((~part->u.fm.slot_mask)&0xf0);
 +    out |= fmp->fm3_slot_keyon;
 +  } else {
 +    // 27fb
 +    out |= 0xf0;
 +  }
 +  work->opna_writereg(work, 0x28, out);
 +}
 +
 +// 2b1c-2b79
 +static void fmp_part_wlfo(struct fmdriver_work *work,
 +                          struct fmp_part *part) {
 +  struct fmp_wlfo *wlfo = &part->u.fm.wlfo;
 +  // 2b1c
 +  if (!wlfo->delay) return;
 +  if (--wlfo->delay_cnt) return;
 +  wlfo->delay_cnt = 1;
 +  if (--wlfo->speed_cnt) return;
 +  wlfo->speed_cnt = wlfo->speed;
 +  // 2b36
 +  wlfo->rate_curr += wlfo->rate;
 +  for (int i = 0; i < 4; i++) {
 +    // 2b4e
 +    if (!(wlfo->sync & (1<<i))) continue;
 +    uint8_t tl = part->u.fm.tone_tl[i] + wlfo->rate_curr;
 +    if (tl & 0x80) {
 +      tl = (wlfo->rate_curr & 0x80) ? 0 : 0x7f;
 +    }
 +    fmp_part_fm_reg_write(work, part, OPNA_TL+4*i, tl);
 +  }
 +  if (!--wlfo->depth_cnt) {
 +    wlfo->depth_cnt = wlfo->depth;
 +    wlfo->rate = -wlfo->rate;
 +  }
 +  // 2b79
 +}
 +
 +// 2e0c
 +static void fmp_part_freq_ppz8(struct fmdriver_work *work,
 +                               struct fmp_part *part) {
 +  uint32_t freq = part->actual_freq;
 +  uint8_t octave = part->u.ssg.octave;
 +  if (!octave) return;
 +  if (octave != 4) {
 +    if (octave > 4) {
 +      // 2e22
 +      freq <<= (octave-4);
 +    } else {
 +      freq >>= (4-octave);
 +    }
 +  }
 +  // 2e3b
 +  int32_t detune = u16s16(part->detune);
 +  freq += detune << 6;
 +  if (work->ppz8_functbl) {
 +    work->ppz8_functbl->channel_freq(work->ppz8, part->opna_keyon_out, freq);
 +  }
 +}
 +
 +static uint32_t fmp_ppz8_note_freq(uint8_t note) {
 +  uint32_t freq = fmp_ppz_freq(note);
 +  uint8_t octave = note/0xc;
 +  if (octave > 4) {
 +    freq <<= (octave - 4);
 +  } else {
 +    freq >>= (4 - octave);
 +  }
 +  return freq;
 +}
 +
 +static void fmp_part_keyon_pdzf(struct fmdriver_work *work,
 +                                struct fmp_part *part) {
 +  uint8_t voice = part->pdzf.voice;
 +  uint8_t pan = part->pdzf.pan + 5;
 +  uint32_t freq = fmp_ppz8_note_freq(part->prev_note);
 +  freq += (u16s16(part->detune) << 3);
 +
 +  if (!part->pdzf.keyon) {
 +    if (work->ppz8_functbl) {
 +      work->ppz8_functbl->channel_play(
 +        work->ppz8,
 +        part->pdzf.ppz8_channel,
 +        voice
 +      );
 +    }
 +    part->pdzf.env_state.status = PDZF_ENV_ATT;
 +    part->pdzf.env_state.vol = 0;
 +    part->pdzf.env_state.cnt = part->pdzf.env_param.al;
 +    part->pdzf.keyon = true;
 +  }
 +
 +  if (work->ppz8_functbl) {
 +    work->ppz8_functbl->channel_pan(
 +      work->ppz8,
 +      part->pdzf.ppz8_channel,
 +      pan
 +    );
 +    work->ppz8_functbl->channel_freq(
 +      work->ppz8,
 +      part->pdzf.ppz8_channel,
 +      freq
 +    );
 +    work->ppz8_functbl->channel_volume(
 +      work->ppz8,
 +      part->pdzf.ppz8_channel,
 +      fmp_pdzf_vol_clamp(part->pdzf.vol, part->pdzf.env_state.vol)
 +    );
 +  }
 +}
 +
 +// 27a3
 +static void fmp_part_keyon(struct fmdriver_work *work,
 +              struct driver_fmp *fmp,
 +              struct fmp_part *part) {
 +  part->ext_keyon = 0xffff;
 +  part->status.keyon = false;
 +  if (part->pdzf.mode) {
 +    fmp_part_keyon_pdzf(work, part);
 +  }
 +  if (!part->type.ssg) {
 +    if (part->lfo_f.lfo) {
 +      part->actual_freq = fmp_fm_freq(part->prev_note);
 +      part->prev_freq = 0;
 +      part->lfo_f.lfo = false;
 +    }
 +    fmp_part_keyon_fm(work, fmp, part);
 +  } else {
 +    // 2805
 +    if (part->lfo_f.lfo) {
 +      uint8_t note = part->prev_note;
 +      part->u.ssg.octave = fmp_ssg_octave(note);
 +      part->actual_freq = fmp_part_ssg_freq(part, note);
 +      part->prev_freq = 0;
 +      part->lfo_f.lfo = false;
 +    }
 +    // 281e
 +    if (part->lfo_f.portamento) {
 +      part->u.ssg.env_f.attack = false;
 +      part->u.ssg.env_f.portamento = true;
 +      return;
 +    }
 +    // 2823
 +    if (part->u.ssg.env_f.ppz) {
 +      if (work->ppz8_functbl) {
 +        work->ppz8_functbl->channel_play(
 +          work->ppz8, part->opna_keyon_out, part->tone
 +        );
 +      }
 +      fmp_part_ppz8_vol(work, part);
 +      fmp_part_freq_ppz8(work, part);
 +      return;
 +    }
 +    part->u.ssg.env_f.attack = true;
 +  }
 +}
 +
 +// 30b1
 +static void fmp_part_init_wlfo(struct fmdriver_work *work,
 +                               struct fmp_part *part) {
 +  if (!(part->u.fm.wlfo.sync & 0x80)) return;
 +  for (int s = 0; s < 4; s++) {
 +    if (!(part->u.fm.wlfo.sync & (1<<s))) continue;
 +    fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, part->u.fm.tone_tl[s]);
 +  }
 +  // 30d5
 +  part->u.fm.wlfo.delay_cnt = part->u.fm.wlfo.delay;
 +  part->u.fm.wlfo.depth_cnt = part->u.fm.wlfo.depth;
 +  part->u.fm.wlfo.rate_curr = 0;
 +  part->u.fm.wlfo.rate = part->u.fm.wlfo.rate_orig;
 +}
 +
 +// 309b
 +static void fmp_part_hlfo(struct fmdriver_work *work,
 +                          struct fmp_part *part) {
 +  work->opna_writereg(work, 0x22, part->u.fm.hlfo_freq);
 +  part->pan_ams_pms &= 0xc0;
 +  part->pan_ams_pms |= part->u.fm.hlfo_apms;
 +  fmp_part_write_pan(work, part);
 +}
 +
 +// 2fdc
 +static void fmp_part_init_lfo_awe(struct fmdriver_work *work,
 +                                  struct driver_fmp *fmp,
 +                                  struct fmp_part *part) {
 +  if (part->lfo_f.w) {
 +    fmp_part_init_wlfo(work, part);
 +  }
 +  if (part->lfo_f.a || part->lfo_f.e || (part->current_vol != part->actual_vol)) {
 +    part->actual_vol = part->current_vol;
 +    uint8_t pvol = part->actual_vol + fmp->fm_vol;
 +    if (pvol & 0x80) {
 +      pvol = (fmp->fm_vol & 0x80) ? 0 : 0x7f;
 +    }
 +    for (int s = 0; s < 4; s++) {
 +      if (!(part->slot_vol_mask & (1<<s))) {
 +        // 3024
 +        uint8_t vol = part->u.fm.tone_tl[s];
 +        vol -= part->u.fm.slot_rel_vol[s];
 +        if (vol & 0x80) {
 +          vol = (part->u.fm.slot_rel_vol[s] & 0x80) ? 0x7f : 0x00;
 +        }
 +        // 3039
 +        if (!(part->u.fm.slot_mask & (1<<s))) {
 +          fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, vol);
 +        }
 +      } else {
 +        // 3042
 +        uint8_t svol = pvol;
 +        if (fmp->data_version >= 0x06) {
 +          svol -= part->u.fm.slot_rel_vol[s];
 +          if (svol & 0x80) {
 +            svol = (part->u.fm.slot_rel_vol[s] & 0x80) ? 0x7f : 0x00;
 +          }
 +          // 305c
 +          svol += part->u.fm.tone_tl[s];
 +          if (svol & 0x80) svol = 0x7f;
 +        }
 +        // 3062
 +        if (!(part->u.fm.slot_mask & (1<<s))) {
 +          fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, svol);
 +        }
 +      }
 +      // 3069
 +    }
 +  }
 +  // 3073
 +  part->u.fm.alfo.delay_cnt = part->u.fm.alfo.delay;
 +  part->u.fm.alfo.depth_cnt = part->u.fm.alfo.depth;
 +  part->u.fm.alfo.rate = part->u.fm.alfo.rate_orig;
 +  if (part->u.fm.hlfo_freq & 0x80) {
 +    work->opna_writereg(work, 0x22, 0x00);
 +    part->u.fm.hlfo_delay_cnt = part->u.fm.hlfo_delay;
 +    if (!part->u.fm.hlfo_delay_cnt) {
 +      // 309b
 +      work->opna_writereg(work, 0x22, part->u.fm.hlfo_freq);
 +      part->pan_ams_pms &= 0xc0;
 +      part->pan_ams_pms |= part->u.fm.hlfo_apms;
 +      fmp_part_write_pan(work, part);
 +    }
 +  }
 +  // 30b0
 +}
 +
 +static void fmp_part_pdzf_env(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part) {
 +  switch (part->pdzf.env_state.status) {
 +  case PDZF_ENV_ATT:
 +    if (!part->pdzf.env_state.cnt--) {
 +      part->pdzf.env_state.vol = part->pdzf.env_param.dd;
 +      part->pdzf.env_state.status = PDZF_ENV_DEC;
 +      if (part->pdzf.env_state.vol > PDZF_ENV_VOL_MIN) {
 +        part->pdzf.env_state.cnt = part->pdzf.env_param.sr;
 +        if (work->ppz8_functbl) {
 +          work->ppz8_functbl->channel_volume(
 +            work->ppz8,
 +            part->pdzf.ppz8_channel,
 +            fmp_pdzf_vol_clamp(part->pdzf.vol, part->pdzf.env_state.vol)
 +          );
 +        }
 +      } else {
 +        part->pdzf.env_state.vol = PDZF_ENV_VOL_MIN;
 +        part->pdzf.env_state.status = PDZF_ENV_OFF;
 +        if (work->ppz8_functbl) {
 +          work->ppz8_functbl->channel_stop(
 +            work->ppz8,
 +            part->pdzf.ppz8_channel
 +          );
 +        }
 +      }
 +    }
 +    break;
 +  case PDZF_ENV_DEC:
 +    if (!part->pdzf.env_param.sr) {
 +      //part->pdzf.env_state.vol = PDZF_ENV_VOL_MIN;
 +    } else if (!--part->pdzf.env_state.cnt) {
 +      part->pdzf.env_state.vol--;
 +      if (part->pdzf.env_state.vol > PDZF_ENV_VOL_MIN) {
 +        part->pdzf.env_state.cnt = part->pdzf.env_param.sr;
 +        if (work->ppz8_functbl) {
 +          work->ppz8_functbl->channel_volume(
 +            work->ppz8,
 +            part->pdzf.ppz8_channel,
 +            fmp_pdzf_vol_clamp(part->pdzf.vol, part->pdzf.env_state.vol)
 +          );
 +        }
 +      }
 +    }
 +    if (part->pdzf.env_state.vol <= PDZF_ENV_VOL_MIN) {
 +        part->pdzf.env_state.vol = PDZF_ENV_VOL_MIN;
 +        part->pdzf.env_state.status = PDZF_ENV_OFF;
 +        if (work->ppz8_functbl) {
 +          work->ppz8_functbl->channel_stop(
 +            work->ppz8,
 +            part->pdzf.ppz8_channel
 +          );
 +        }
 +      }
 +    break;
 +  case PDZF_ENV_REL:
 +    if (!--part->pdzf.env_state.cnt) {
 +      part->pdzf.env_state.vol--;
 +      if (part->pdzf.env_state.vol > PDZF_ENV_VOL_MIN) {
 +        part->pdzf.env_state.cnt = part->pdzf.env_param.rr;
 +        if (work->ppz8_functbl) {
 +          work->ppz8_functbl->channel_volume(
 +            work->ppz8,
 +            part->pdzf.ppz8_channel,
 +            fmp_pdzf_vol_clamp(part->pdzf.vol, part->pdzf.env_state.vol)
 +          );
 +        }
 +      } else {
 +        part->pdzf.env_state.vol = PDZF_ENV_VOL_MIN;
 +        part->pdzf.env_state.status = PDZF_ENV_OFF;
 +        if (work->ppz8_functbl) {
 +          work->ppz8_functbl->channel_stop(
 +            work->ppz8,
 +            part->pdzf.ppz8_channel
 +          );
 +        }
 +      }
 +    }
 +    break;
 +  }
 +}
 +
 +// 2ad9
 +static void fmp_part_lfo(struct fmdriver_work *work,
 +                         struct driver_fmp *fmp,
 +                         struct fmp_part *part) {
 +  // 2ad9
 +  if (part->lfo_f.p) fmp_part_lfo_calc(fmp, part, 0);
 +  if (part->lfo_f.q) fmp_part_lfo_calc(fmp, part, 1);
 +  if (part->lfo_f.r) fmp_part_lfo_calc(fmp, part, 2);
 +  if (part->type.fm) {
 +    // 2b03
 +    if ((part->u.fm.hlfo_freq & 0x04) && part->u.fm.hlfo_delay_cnt) {
 +      if (!--part->u.fm.hlfo_delay_cnt) {
 +        fmp_part_hlfo(work, part);
 +      }
 +    }
 +    // 2b17
 +    if (part->lfo_f.w) {
 +      fmp_part_wlfo(work, part);
 +    }
 +    // 2b79
 +    if (part->lfo_f.a || part->lfo_f.e) {
 +      // 2b7e
 +      if (!part->u.fm.alfo.delay) return;
 +      if (--part->u.fm.alfo.delay_cnt) return;
 +      part->u.fm.alfo.delay_cnt = 1;
 +      if (--part->u.fm.alfo.speed_cnt) return;
 +      part->u.fm.alfo.speed_cnt = part->u.fm.alfo.speed;
 +      uint8_t vol = part->actual_vol + part->u.fm.alfo.rate;
 +      if (vol & 0x80) {
 +        vol = (part->u.fm.alfo.rate & 0x80) ? 0x00 : 0x7f;
 +      }
 +      part->actual_vol = vol;
 +      if (!part->lfo_f.e) {
 +        // 2bb3
 +        fmp_part_fm_vol(work, fmp, part);
 +        if (--part->u.fm.alfo.depth_cnt) return;
 +        part->u.fm.alfo.depth_cnt = part->u.fm.alfo.depth;
 +        part->u.fm.alfo.rate = -part->u.fm.alfo.rate;
 +      } else {
 +        // 2bc5
 +        fmp_part_keyoff_fm(work, fmp, part);
 +        fmp_part_fm_vol(work, fmp, part);
 +        fmp_part_keyon_fm(work, fmp, part);
 +        part->u.fm.alfo.speed_cnt = part->u.fm.alfo.depth_cnt;
 +        if (part->u.fm.alfo.speed == part->u.fm.alfo.speed_cnt) {
 +          part->u.fm.alfo.depth_cnt = part->u.fm.alfo.depth;
 +        } else {
 +          part->u.fm.alfo.depth_cnt = part->u.fm.alfo.speed;
 +        }
 +      }
 +    } 
 +  }
 +}
 +
 +// 2a51
 +static void fmp_part_fm(struct fmdriver_work *work, struct driver_fmp *fmp,
 +                        struct fmp_part *part) {
 +  if (part->status.lfo_sync) {
 +    fmp_part_init_lfo_pqr(part);
 +    fmp_part_init_lfo_awe(work, fmp, part);
 +    part->actual_freq = fmp_fm_freq(part->prev_note);
 +    part->status.lfo_sync = false;
 +  }
 +  // 2a6a
 +  if (part->status.pitchbend || fmp->datainfo.flags.lfo_octave_fix) {
 +    uint16_t fnum = part->actual_freq & 0x7ff;
 +    if (fnum > 0x4d3u) {
 +      part->actual_freq += 0x800;
 +      part->actual_freq -= 0x269;
 +    } else if (fnum < 0x26au) {
 +      part->actual_freq -= 0x800;
 +      part->actual_freq += 0x269;
 +      if (part->actual_freq & 0x8000) {
 +        part->actual_freq = 0x200;
 +      }
 +    }
 +  }
 +  // 2aa3
 +  if ((part->actual_freq + part->detune) != part->prev_freq) {
 +    part->prev_freq = part->actual_freq + part->detune;
 +    if (part->type.fm_3 && (fmp->timer_ch3 & 0x40)) {
 +      // 2be7
 +      static const uint8_t ch3_fnum_addr[] = {
 +        0xad, 0xac, 0xae, 0xa6
 +      };
 +      static const uint8_t ch3_index[] = {
 +        0, 2, 1, 3
 +      };
 +      for (int i = 0; i < 4; i++) {
 +        if (part->u.fm.slot_mask & (1<<i)) continue;
 +        uint16_t freq = part->prev_freq + fmp->ch3_se_freqdiff[ch3_index[i]];
 +        work->opna_writereg(work, ch3_fnum_addr[i], freq>>8);
 +        work->opna_writereg(work, ch3_fnum_addr[i]-4, freq&0xff);
 +      }
 +    } else {
 +      fmp_part_fm_reg_write(work, part, OPNA_BLKFNUM2, part->prev_freq>>8);
 +      fmp_part_fm_reg_write(work, part, OPNA_FNUM1, part->prev_freq&0xff);
 +    }
 +  }
 +  // 2ad0
 +  if (part->status.keyon) {
 +    fmp_part_keyon(work, fmp, part);
 +  }
 +  fmp_part_lfo(work, fmp, part);
 +}
 +
 +// 2e80-2ed6
 +static void fmp_part_ssg_env_adsr(struct fmp_part *part) {
 +  // 2e80
 +  if (!part->u.ssg.env_f.decay) {
 +    unsigned newvol = part->actual_vol + part->u.ssg.env.attack_rate;
 +    if (!(newvol & 0x100) && (part->u.ssg.vol > newvol)) {
 +      part->actual_vol = newvol;
 +      return;
 +    }
 +    // 2e96
 +    part->actual_vol = part->u.ssg.vol;
 +    part->u.ssg.env_f.decay = true;
 +  }
 +  // 2e9d
 +  if (!part->u.ssg.env_f.sustain) {
 +    unsigned newvol = part->actual_vol - part->u.ssg.env.decay_rate;
 +    if (!(newvol & 0x100) && (part->u.ssg.env.sustain_lv < newvol)) {
 +      part->actual_vol = newvol;
 +      return;
 +    }
 +    // 2eb3
 +    part->actual_vol = part->u.ssg.env.sustain_lv;
 +    part->u.ssg.env_f.sustain = true;
 +  }
 +  // 2eba
 +  if (part->u.ssg.env_f.release) return;
 +  unsigned newvol = part->actual_vol - part->u.ssg.env.sustain_rate;
 +  if (!(newvol & 0x100)) {
 +    part->actual_vol = newvol;
 +    return;
 +  }
 +  // 2ec8
 +  part->actual_vol = 0;
 +  part->u.ssg.env_f.release = true;
 +  part->u.ssg.env_f.attack = false;
 +}
 +
 +// 2e7a
 +static void fmp_part_ssg_env(struct fmdriver_work *work,
 +                             struct fmp_part *part) {
 +  if (part->u.ssg.env_f.attack) {
 +    fmp_part_ssg_env_adsr(part);
 +  } else {
 +    // 2ed6
 +    if (!part->u.ssg.env_f.portamento) {
 +      //part->u.ssg.curr_vol = dh
 +      return;
 +    } else {
 +      // 2edc
 +      if (!part->u.ssg.env_f.release) {
 +        // 2ee2
 +        unsigned newvol = part->actual_vol - part->u.ssg.env.release_rate;
 +        if (!(newvol & 0x100)) {
 +          part->actual_vol = newvol;
 +        } else {
 +          part->actual_vol = 0;
 +          part->u.ssg.env_f.release = true;
 +          part->u.ssg.env_f.portamento = false;
 +        }
 +      }
 +    }
 +  }
 +  // 2ef6
 +  uint8_t outvol = ((part->actual_vol * part->current_vol) >> 8);
 +  if (outvol == part->u.ssg.curr_vol) {
 +    //part->u.ssg.curr_vol = dh
 +    return;
 +  }
 +  // 2f03
 +  work->opna_writereg(work, 0x8+part->opna_keyon_out, outvol);
 +}
 +
 +// 2db0
 +static void fmp_part_ssg(struct fmdriver_work *work, struct driver_fmp *fmp,
 +                         struct fmp_part *part) {
 +  if (part->status.lfo_sync) {
 +    fmp_part_init_lfo_pqr(part);
 +    part->u.ssg.octave = fmp_ssg_octave(part->prev_note);
 +    part->actual_freq = fmp_part_ssg_freq(part, part->prev_note);
 +    part->status.lfo_sync = false;
 +  }
 +  // 2dc9
 +  if (part->status.keyon) {
 +    fmp_part_keyon(work, fmp, part);
 +  }
 +  // 2dd2
 +  if (part->u.ssg.env_f.ppz) return;
 +  fmp_part_ssg_env(work, part);
 +  uint16_t freq = part->actual_freq >> part->u.ssg.octave;
 +  if (freq == part->prev_freq) {
 +    // 2ad0
 +    if (part->status.keyon) {
 +      fmp_part_keyon(work, fmp, part);
 +    }
 +  } else {
 +    part->prev_freq = freq;
 +    if (!part->eff_chan) {
 +      // 2df8
 +      unsigned reg = part->opna_keyon_out*2;
 +      work->opna_writereg(work, reg, freq&0xff);
 +      work->opna_writereg(work, reg|1, freq>>8);
 +    }
 +  }
 +  fmp_part_lfo(work, fmp, part);
 +}
 +
 +// 2f61
 +static void fmp_part_ssg_env_reset(struct fmp_part *part) {
 +  part->u.ssg.env_f.decay = false;
 +  part->u.ssg.env_f.sustain = false;
 +  part->u.ssg.env_f.release = false;
 +  uint8_t vol = part->u.ssg.env.startvol;
 +  if (vol >= part->u.ssg.vol) {
 +    part->u.ssg.env_f.decay = true;
 +    vol = part->u.ssg.vol;
 +  }
 +  part->actual_vol = vol;
 +  if (part->u.ssg.env.sustain_lv >= part->u.ssg.vol) {
 +    part->u.ssg.env_f.sustain = true;
 +  }
 +}
 +
 +// 28b3
 +static void fmp_adpcm_addr_set(struct fmdriver_work *work,
 +                               struct driver_fmp *fmp,
 +                               struct fmp_part *part) {
 +  if (part->lfo_f.portamento) return;
 +  work->opna_writereg(work, 0x100, 0x00);
 +  work->opna_writereg(work, 0x100, 0x01);
 +  //work->opna_writereg(work, 0x110, 0x08);
 +  //work->opna_writereg(work, 0x110, 0x80);
 +  work->opna_writereg(work, 0x101, part->pan_ams_pms | fmp->adpcm_c1);
 +  work->opna_writereg(work, 0x102, part->u.adpcm.startaddr & 0xff);
 +  work->opna_writereg(work, 0x103, part->u.adpcm.startaddr >> 8);
 +  work->opna_writereg(work, 0x104, part->u.adpcm.endaddr & 0xff);
 +  work->opna_writereg(work, 0x105, part->u.adpcm.endaddr >> 8);
 +}
 +
 +// 2853
 +static void fmp_part_adpcm(struct fmdriver_work *work,
 +                           struct driver_fmp *fmp,
 +                           struct fmp_part *part) {
 +  if (!part->status.keyon) return;
 +  part->ext_keyon = 0xffff;
 +  part->status.keyon = false;
 +  if (part->status.tie_cont) return;
 +  fmp_adpcm_addr_set(work, fmp, part);
 +  part->actual_freq = fmp_adpcm_freq(part->prev_note);
 +  uint16_t freq = part->actual_freq;
 +  // why does the detune byte has to be swapped??
 +  freq += (part->detune >> 8) | ((part->detune << 8) & 0xff00);
 +  freq += part->u.adpcm.deltat;
 +  work->opna_writereg(work, 0x109, freq & 0xff);
 +  work->opna_writereg(work, 0x10a, freq >> 8);
 +  work->opna_writereg(work, 0x100, 0xa0);
 +}
 +
 +// 2750
 +static void fmp_part_keyon_pre(struct driver_fmp *fmp, struct fmp_part *part) {
 +  // TODO: seproc
 +  if (fmp->datainfo.flags.q) {
 +    // 275e
 +    uint8_t tonelen = part->tonelen_cnt;
 +    unsigned shift = 0;
 +    if (tonelen < 0x10) shift = 3;
 +    tonelen <<= shift;
 +    tonelen >>= 3;
 +    tonelen *= part->gate_cnt;
 +    tonelen >>= shift;
 +    part->gate_cmp = part->tonelen_cnt - tonelen;
 +  }
 +  // 277f
 +  part->status.keyon = true;
 +  if (part->type.ssg && !part->status.tie_cont && !part->u.ssg.env_f.ppz) {
 +    part->u.ssg.env_f.portamento = false;
 +    if (!part->lfo_f.portamento) {
 +      fmp_part_ssg_env_reset(part);
 +    }
 +  }
 +}
 +
 +// 1cde
 +static void fmp_part_pit_end_ssg(struct fmp_part *part) {
 +  part->u.ssg.octave = fmp_ssg_octave(part->pit.target_note);
 +  part->actual_freq = fmp_part_ssg_freq(part, part->pit.target_note);
 +  part->pit.rate = 0;
 +  part->status.pitchbend = false;
 +  part->prev_note = part->pit.target_note;
 +}
 +
 +// 1cc4
 +static void fmp_part_pit_end_fm(struct fmp_part *part) {
 +  uint8_t p_delay_cnt = part->lfo[0].delay_cnt;
 +  uint8_t q_delay_cnt = part->lfo[1].delay_cnt;
 +  uint8_t r_delay_cnt = part->lfo[2].delay_cnt;
 +  fmp_part_init_lfo_pqr(part);
 +  part->lfo[0].delay_cnt = p_delay_cnt;
 +  part->lfo[1].delay_cnt = q_delay_cnt;
 +  part->lfo[2].delay_cnt = r_delay_cnt;
 +  // 1cdb
 +  part->actual_freq = part->pit.target_freq;
 +  part->pit.rate = 0;
 +  part->status.pitchbend = false;
 +  part->prev_note = part->pit.target_note;
 +}
 +
 +// 1c4b
 +static void fmp_part_pit(struct fmp_part *part) {
 +  if (part->type.adpcm) return;
 +  if (--part->pit.delay) return;
 +  part->pit.delay = 1;
 +  if (--part->pit.speed_cnt) return;
 +  part->pit.speed_cnt = part->pit.speed;
 +  uint16_t rate = part->pit.rate;
 +  if (rate & 0x80) rate |= 0xff00;
 +  if (!rate) return;
 +  // 1c77
 +  uint16_t freq = part->actual_freq + rate;
 +  if (!(rate & 0x8000)) {
 +    // rate positive
 +    // 1c79
 +    if (!part->type.fm) {
 +      // 1c83
 +      if ((freq >> part->u.ssg.octave) <= part->pit.target_freq) {
 +        part->actual_freq = freq;
 +      } else {
 +        fmp_part_pit_end_ssg(part);
 +      }
 +    } else {
 +      // 1c93
 +      if (freq < part->pit.target_freq) {
 +        part->actual_freq = freq;
 +      } else {
 +        fmp_part_pit_end_fm(part);
 +      }
 +    }
 +  } else {
 +    // rate negative
 +    // 1ca5
 +    if (!part->type.fm) {
 +      if ((freq >> part->u.ssg.octave) >= part->pit.target_freq) {
 +        part->actual_freq = freq;
 +      } else {
 +        fmp_part_pit_end_ssg(part);
 +      }
 +    } else {
 +      // 1cbf
 +      if (freq > part->pit.target_freq) {
 +        part->actual_freq = freq;
 +      } else {
 +        fmp_part_pit_end_fm(part);
 +      }
 +    }
 +  }
 +  // 1cef
 +}
 +
 +// 1d81
 +static bool fmp_part_cmd_exec(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part,
 +                              uint8_t cmd) {
 +  enum {
 +    JMPTBL_LEN = 0x80-0x62
 +  };
 +  typedef bool (*cmdfunc_t)(struct fmdriver_work *work,
 +                          struct driver_fmp *fmp,
 +                          struct fmp_part *part);
 +  static const cmdfunc_t fm_jmptbl[JMPTBL_LEN] = {
 +    fmp_cmd62_tempo,
 +    fmp_cmd63_vol_d_fm,
 +    fmp_cmd64_loop,
 +    fmp_cmd65_loopend,
 +    fmp_cmd66_tie,
 +    fmp_cmd67_q,
 +    fmp_cmd68_pitchbend,
 +    fmp_cmd69_vol_fm,
 +    fmp_cmd6a_voldec_fm,
 +    fmp_cmd6b_volinc_fm,
 +    fmp_cmd6c_kondelay,
 +    fmp_cmd6d_detune,
 +    fmp_cmd6e_poke,
 +    fmp_cmd6f_sync,
 +    fmp_cmd70_wait,
 +    fmp_cmd71_tone_fm,
 +    fmp_cmd72_deflen,
 +    fmp_cmd73_relvol_fm,
 +    fmp_cmd74_loop,
 +    fmp_cmd75_lfo,
 +    fmp_cmd76_lfo_p,
 +    fmp_cmd77_lfo_q,
 +    fmp_cmd78_lfo_r,
 +    fmp_cmd79_lfo_a_fm,
 +    fmp_cmd7a_tie,
 +    fmp_cmd7b_transpose,
 +    fmp_cmd7c_lfo_pan_fm,
 +    fmp_cmd7d_sync,
 +    fmp_cmd7e_loop_det,
 +    fmp_cmd7f_lfo_delay
 +  };
 +  static const cmdfunc_t ssg_jmptbl[JMPTBL_LEN] = {
 +    fmp_cmd62_tempo,
 +    fmp_cmd63_mix_ssg,
 +    fmp_cmd64_loop,
 +    fmp_cmd65_loopend,
 +    fmp_cmd66_tie,
 +    fmp_cmd67_q,
 +    fmp_cmd68_pitchbend,
 +    fmp_cmd69_vol_ssg,
 +    fmp_cmd6a_voldec_ssg,
 +    fmp_cmd6b_volinc_ssg,
 +    fmp_cmd6c_kondelay,
 +    fmp_cmd6d_detune,
 +    fmp_cmd6e_poke,
 +    fmp_cmd6f_sync,
 +    fmp_cmd70_wait,
 +    fmp_cmd71_envreset_ssg,
 +    fmp_cmd72_deflen,
 +    fmp_cmd73_noise_ssg,
 +    fmp_cmd74_loop,
 +    fmp_cmd75_lfo,
 +    fmp_cmd76_lfo_p,
 +    fmp_cmd77_lfo_q,
 +    fmp_cmd78_lfo_r,
 +    fmp_cmd79_env_ssg,
 +    fmp_cmd7a_tie,
 +    fmp_cmd7b_transpose,
 +    fmp_cmd7c_tone_ssg,
 +    fmp_cmd7d_sync,
 +    fmp_cmd7e_loop_det,
 +    fmp_cmd7f_lfo_delay
 +  };
 +  static const cmdfunc_t adpcm_jmptbl[JMPTBL_LEN] = {
 +    fmp_cmd62_tempo,
 +    fmp_cmd63_vol_d_adpcm,
 +    fmp_cmd64_loop,
 +    fmp_cmd65_loopend,
 +    fmp_cmd66_tie,
 +    fmp_cmd67_q,
 +    fmp_cmd68_adpcm,
 +    fmp_cmd69_adpcm,
 +    fmp_cmd6a_voldec_adpcm,
 +    fmp_cmd6b_volinc_adpcm,
 +    fmp_cmd6c_kondelay,
 +    fmp_cmd6d_detune,
 +    fmp_cmd6e_poke,
 +    fmp_cmd6f_sync,
 +    fmp_cmd70_wait,
 +    fmp_cmd71_tone_adpcm,
 +    fmp_cmd72_deflen,
 +    fmp_cmd73_relvol_adpcm,
 +    fmp_cmd74_loop,
 +    fmp_cmd75_lfo,
 +    fmp_cmd76_lfo_p,
 +    fmp_cmd77_lfo_q,
 +    fmp_cmd78_lfo_r,
 +    fmp_cmd79_lfo_a_fm,
 +    fmp_cmd7a_tie,
 +    fmp_cmd7b_transpose,
 +    fmp_cmd7c_pan_adpcm,
 +    fmp_cmd7d_sync,
 +    fmp_cmd7e_loop_det,
 +    fmp_cmd7f_lfo_delay
 +  };
 +  const cmdfunc_t *functable;
 +  if (part->type.adpcm) {
 +    functable = adpcm_jmptbl;
 +  } else {
 +    functable = part->type.ssg ? ssg_jmptbl : fm_jmptbl;
 +  }
 +  return functable[cmd-0x62](work, fmp, part);
 +}
 +
 +static bool fmp_part_cmd_exec2(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part,
 +                              uint8_t cmd) {
 +  if (!part->type.fm || (cmd > 0xe3)) {
 +    part->current_ptr = 0xffff;
 +    return false;
 +  }
 +  
 +  typedef bool (*cmdfunc_t)(struct fmdriver_work *work,
 +                          struct driver_fmp *fmp,
 +                          struct fmp_part *part);
 +  static const cmdfunc_t fm_jmptbl2[2] = {
 +    fmp_cmde2_slotrelvol_set_fm,
 +    fmp_cmde3_slotrelvol_add_fm,
 +  };
 +  return fm_jmptbl2[cmd-0xe2](work, fmp, part);
 +}
 +
 +// 2936
 +static void fmp_part_keyoff_q(struct fmdriver_work *work,
 +                              struct driver_fmp *fmp,
 +                              struct fmp_part *part) {
 +  if (part->type.adpcm) {
 +    part->ext_keyon = 0;
 +    part->status.tie_cont = false;
 +    work->opna_writereg(work, 0x100, 0x00);
 +    work->opna_writereg(work, 0x100, 0x01);
 +    return;
 +  }
 +  if (part->type.fm || !part->u.ssg.env_f.ppz) {
 +    // 2961
 +    part->ext_keyon = 0;
 +    part->status.tie_cont = false;
 +    if (part->type.adpcm) return;
 +    if (!part->type.ssg) {
 +      // 2979
 +      uint8_t keyon = part->opna_keyon_out;
 +      if (part->type.fm_3) {
 +        // 2994
 +        fmp->fm3_slot_keyon &= (part->u.fm.slot_mask | 0x0f);
 +        keyon |= fmp->fm3_slot_keyon;
 +      }
 +      work->opna_writereg(work, 0x28, keyon);
 +      // 29a2
 +    } else {
 +      // 29a9
 +      part->u.ssg.curr_vol = 0xff;
 +      if (part->u.ssg.env.release_rate == 0) {
 +        part->actual_vol = 0;
 +      }
 +      part->u.ssg.env_f.portamento = true;
 +      part->u.ssg.env_f.attack = false;
 +    }
 +  } else {
 +    // 294d
 +    if (work->ppz8_functbl) {
 +      work->ppz8_functbl->channel_volume(
 +        work->ppz8,
 +        part->opna_keyon_out,
 +        0
 +      );
 +    }
 +  }
 +  if (part->pdzf.mode) {
 +    part->pdzf.keyon = false;
 +    if (part->lfo_f.q && part->pdzf.env_param.rr) {
 +      part->pdzf.env_state.status = PDZF_ENV_REL;
 +      part->pdzf.env_state.cnt = part->pdzf.env_param.rr;
 +    } else {
 +      part->pdzf.env_state.status = PDZF_ENV_OFF;
 +      if (work->ppz8_functbl) {
 +        work->ppz8_functbl->channel_stop(
 +          work->ppz8,
 +          part->pdzf.ppz8_channel
 +        );
 +      }
 +    }
 +  }
 +}
 +
 +// 1c0d
 +static void fmp_part_cmd(struct fmdriver_work *work, struct driver_fmp *fmp,
 +                     struct fmp_part *part) {
 +  if (part->sync > 1) return;
 +  if (part->sync == 1) {
 +    // 1c19
 +    if (part->keyon_delay_cnt) {
 +      if (!--part->keyon_delay_cnt) {
 +        fmp_part_keyon_pre(fmp, part);
 +      }
 +    }
 +    // 1c27
 +    if ((!part->status.tie) && (!part->status.rest)) {
 +      if (part->tonelen_cnt && (part->tonelen_cnt == part->gate_cmp)) {
 +        fmp_part_keyoff_q(work, fmp, part);
 +      }
 +    }
 +    // 1c42
 +    if (part->status.pitchbend) {
 +      fmp_part_pit(part);
 +    }
 +    // 1cef
 +    if (--part->tonelen_cnt) return;
 +    // 1cf5
 +    if (!part->status.tie) {
 +      fmp_part_keyoff(work, fmp, part);
 +      part->status.pitchbend = false;
 +    } else {
 +      // 1d04
 +      part->status.slur = false;
 +      part->status.tie = false;
 +      part->status.tie_cont = true;
 +    }
 +  }
 +  // 1d0c
 +  part->sync = 1;
 +  // 1d10
 +  for (;;) {
 +    uint8_t cmd = fmp_part_cmdload(fmp, part);
 +    uint8_t len;
 +    if (!(cmd & 0x80)) {
 +      // 1d1a
 +      if (cmd >= 0x62) {
 +        if (!fmp_part_cmd_exec(work, fmp, part, cmd)) return;
 +        continue;
 +      } else {
 +        // 1d23
 +        len = fmp_part_cmdload(fmp, part);
 +      }
 +    } else {
 +      // 1d26
 +      cmd &= 0x7f;
 +      if (cmd >= 0x62) {
 +        if (!fmp_part_cmd_exec2(work, fmp, part, cmd)) return;
 +        continue;
 +      } else {
 +        // 1d33
 +        len = part->default_len;
 +      }
 +    }
 +    // 1d36
 +    // note
 +    part->tonelen_cnt = len;
 +    if (cmd == 0x61) {
 +      // 1d3e
 +      part->status.tie = false;
 +      part->status.pitchbend = false;
 +      part->status.tie_cont = false;
 +      part->status.rest = true;
 +      fmp_part_keyoff(work, fmp, part);
 +      break;
 +    } else {
 +      // 1d4d
 +      cmd += part->note_diff;
 +      part->status.rest = false;
 +      part->keyon_delay_cnt = part->keyon_delay;
 +      if (!part->keyon_delay) {
 +        fmp_part_keyon_pre(fmp, part);
 +      }
 +      // 1d61
 +      if (!part->status.tie_cont || cmd != part->prev_note) {
 +        // 1d6c
 +        part->status.lfo_sync = true;
 +        part->status.tie_cont = false;
 +        if (part->status.slur) {
 +          fmp_part_keyoff(work, fmp, part);
 +        }
 +      }
 +      // 1d7d
 +      part->prev_note = cmd;
 +      break;
 +    }
 +  }
 +}
 +
 +// 269b
 +static void fmp_part_pan_vol_rhythm(struct fmdriver_work *work,
 +                                    struct fmp_rhythm *rhythm,
 +                                    int part) {
 +  work->opna_writereg(work, 0x18+part,
 +                      rhythm->pans[part] | rhythm->volumes[part]);
 +}
 +
 +// 2619
 +static void fmp_part_cmd_rhythm(struct fmdriver_work *work,
 +                                struct driver_fmp *fmp) {
 +  struct fmp_rhythm *rhythm = &fmp->rhythm;
 +  struct fmp_part *part = &rhythm->part;
 +  if (rhythm->sync > 1) return;
 +  if (rhythm->sync == 1) {
 +    // 2623
 +    if (--rhythm->len_cnt) return;
 +  }
 +  // 262a
 +  for (;;) {
 +    rhythm->sync = 1;
 +    uint8_t cmd = fmp_part_cmdload_rhythm(fmp, part);
 +    if (cmd & 0x80) {
 +      if (cmd & 0x40) {
 +        // 26a5
 +        rhythm->tl_volume = cmd & 0x3f;
 +        work->opna_writereg(work, 0x11, rhythm->tl_volume);
 +      } else if (cmd & 0x20) {
 +        // 26be
 +        int pan = (cmd & 0x18) << 3;
 +        rhythm->pans[cmd&0x07] = pan;
 +        fmp_part_pan_vol_rhythm(work, rhythm, cmd&0x7);
 +      } else if (cmd == 0x90) {
 +        // 26d4
 +        rhythm->default_len = fmp_part_cmdload(fmp, part);
 +      } else if (cmd == 0x91) {
 +        // 26db
 +        fmp_cmd64_loop(work, fmp, part);
 +      } else if (cmd == 0x92) {
 +        // 26e2
 +        fmp_cmd65_loopend(work, fmp, part);
 +      } else if (cmd == 0x93) {
 +        // 26e9
 +        if (!fmp_cmd74_loop(work, fmp, part)) return;
 +      } else if (cmd == 0x94) {
 +        // 26ef
 +        if (rhythm->tl_volume < 0x3f) {
 +          rhythm->tl_volume++;
 +          work->opna_writereg(work, 0x11, rhythm->tl_volume);
 +        }
 +      } else if (cmd == 0x95) {
 +        // 26fb
 +        if (rhythm->tl_volume) {
 +          rhythm->tl_volume--;
 +          work->opna_writereg(work, 0x11, rhythm->tl_volume);
 +        }
 +      } else {
 +        switch (cmd & 0xf8) {
 +        case 0x80:
 +        default:
 +          // 268b
 +          rhythm->volumes[cmd&0x7] = fmp_part_cmdload(fmp, part);
 +          fmp_part_pan_vol_rhythm(work, rhythm, cmd&0x7);
 +          break;
 +        case 0x88:
 +          // 2707
 +          {
 +            uint8_t vol = rhythm->volumes[cmd&0x7] + 1;
 +            if (vol & 0xe0) vol = 0x1f;
 +            rhythm->volumes[cmd&0x7] = vol;
 +          }
 +          fmp_part_pan_vol_rhythm(work, rhythm, cmd&0x7);
 +          break;
 +        case 0x98:
 +          // 2719
 +          {
 +            uint8_t vol = rhythm->volumes[cmd&0x7] - 1;
 +            if (vol & 0xe0) vol = 0;
 +            rhythm->volumes[cmd&0x7] = vol;
 +          }
 +          fmp_part_pan_vol_rhythm(work, rhythm, cmd&0x7);
 +          break;
 +        }
 +      }
 +    } else {
 +      work->opna_writereg(work, 0x10, cmd & rhythm->mask);
 +      if (fmp->pdzf.mode == 2) {
 +        uint8_t pdzf_keyon = (cmd & 0x3f) & rhythm->mask;
 +        bool do_keyon = false;
 +        int p;
 +        for (p = 0; p < 6; p++) {
 +          if ((pdzf_keyon & (1<<p)) && (!rhythm->pans[p]) && fmp->pdzf.rhythm[p&1].enabled) {
 +            do_keyon = true;
 +            break;
 +          }
 +        }
 +        if (do_keyon) {
 +          if (work->ppz8_functbl) {
 +            uint8_t volume = rhythm->volumes[p] & 0x0f;
 +            uint8_t voice = fmp->pdzf.rhythm[p&1].voice[((rhythm->volumes[p] & 0x10) >> 4)];
 +            uint8_t pan = fmp->pdzf.rhythm[p&1].pan + 5;
 +            uint32_t freq = fmp_ppz8_note_freq(fmp->pdzf.rhythm[p&1].note);
 +            uint8_t channel = 6;
 +            work->ppz8_functbl->channel_play(
 +              work->ppz8,
 +              channel,
 +              voice
 +            );
 +            work->ppz8_functbl->channel_volume(
 +              work->ppz8,
 +              channel,
 +              volume
 +            );
 +            work->ppz8_functbl->channel_pan(
 +              work->ppz8,
 +              channel,
 +              pan
 +            );
 +            work->ppz8_functbl->channel_freq(
 +              work->ppz8,
 +              channel,
 +              freq
 +            );
 +          }
 +        }
 +      }
 +      rhythm->len_cnt = (cmd & 0x40) ?
 +                        rhythm->default_len : fmp_part_cmdload(fmp, part);
 +      return;
 +    }
 +  }
 +}
 +
 +// 17f8-1903
 +static void fmp_timerb(struct fmdriver_work *work, struct driver_fmp *fmp) {
 +  // 1805
 +  fmp_set_tempo(work, fmp);
 +
 +  // 1813
 +  if (fmp->status.stopped) {
 +    // TODO: stopped
 +    // jmp 18c7
 +  }
 +  // 1829
 +  if (!--fmp->clock_divider) {
 +    fmp->total_clocks++;
 +    fmp->clock_divider = 10;
 +  }
 +  // 1840
 +  for (int p = 0; p < 6; p++) {
 +    struct fmp_part *part = &fmp->parts[FMP_PART_FM_1+p];
 +    if (part->status.off) continue;
 +    fmp_part_cmd(work, fmp, part);
 +    fmp_part_fm(work, fmp, part);
 +  }
 +  {
 +    struct fmp_part *part = &fmp->parts[FMP_PART_ADPCM];
 +    if (!part->status.off) {
 +      fmp_part_cmd(work, fmp, part);
 +      fmp_part_adpcm(work, fmp, part);
 +    }
 +  }
 +  for (int p = 0; p < 3; p++) {
 +    struct fmp_part *part = &fmp->parts[FMP_PART_SSG_1+p];
 +    if (part->status.off) continue;
 +    fmp_part_cmd(work, fmp, part);
 +    fmp_part_ssg(work, fmp, part);
 +    if (part->pdzf.mode && part->lfo_f.q) {
 +      fmp_part_pdzf_env(work, fmp, part);
 +    }
 +  }
 +  // 187d
 +  for (int p = 0; p < 3; p++) {
 +    struct fmp_part *part = &fmp->parts[FMP_PART_FM_EX1+p];
 +    if (part->status.off) continue;
 +    fmp_part_cmd(work, fmp, part);
 +    if (part->status.off) continue;
 +    fmp_part_fm(work, fmp, part);
 +    if (part->pdzf.mode && part->lfo_f.q) {
 +      fmp_part_pdzf_env(work, fmp, part);
 +    }
 +  }
 +  if (!fmp->rhythm.status) {
 +    fmp_part_cmd_rhythm(work, fmp);
 +  }
 +}
 +
 +static void fmp_init_parts(struct fmdriver_work *work,
 +                           struct driver_fmp *fmp) {
 +  const uint8_t deflen = fmp->datainfo.bar >> 2;
 +  // 3ae7
 +  // FM1, 2, 3, SSG1, 2, 3
 +  for (int i = 0; i < 3; i++) {
 +    struct fmp_part *fpart = &fmp->parts[FMP_PART_FM_1+i];
 +    struct fmp_part *spart = &fmp->parts[FMP_PART_SSG_1+i];
 +    spart->default_len = deflen;
 +    fpart->default_len = deflen;
 +    spart->gate_cmp = 1;
 +    fpart->gate_cmp = 1;
 +    spart->gate_cnt = 8;
 +    fpart->gate_cnt = 8;
 +    spart->u.ssg.env.startvol = 0xff;
 +    spart->u.ssg.env.attack_rate = 0xff;
 +    spart->u.ssg.env.decay_rate = 0xff;
 +    spart->u.ssg.env.sustain_lv = 0xff;
 +    spart->u.ssg.env.release_rate = 0x0a;
 +    fpart->current_vol = 0x1a;
 +    fpart->actual_vol = 0x1a;
 +    spart->current_vol = 0x0e;
 +  }
 +  // FM4, 5, 6, ADPCM
 +  for (int i = 0; i < 4; i++) {
 +    struct fmp_part *part = &fmp->parts[FMP_PART_FM_4+i];
 +    part->default_len = deflen;
 +    part->gate_cmp = 1;
 +    part->gate_cnt = 8;
 +    part->current_vol = 0x1a;
 +    part->actual_vol = 0x1a;
 +  }
 +  {
 +    struct fmp_part *part = &fmp->parts[FMP_PART_ADPCM];
 +    part->default_len = deflen;
 +    part->gate_cmp = 1;
 +    part->gate_cnt = 8;
 +    part->current_vol = 0x1a;
 +    part->actual_vol = 0x1a;
 +  }
 +  // FMEX1, 2, 3
 +  for (int i = 0; i < 3; i++) {
 +    struct fmp_part *part = &fmp->parts[FMP_PART_FM_EX1+i];
 +    part->default_len = deflen;
 +    part->gate_cmp = 1;
 +    part->gate_cnt = 8;
 +    part->current_vol = 0x1a;
 +    part->actual_vol = 0x1a;
 +    part->u.fm.slot_mask = 0xff;
 +    part->pan_ams_pms = 0xc0;
 +  }
 +  // 3b5f
 +  fmp->rhythm.status = false;
 +  fmp->rhythm.len_cnt = 1;
 +  fmp->rhythm.default_len = deflen;
 +  //fmp->rhythm.loop_now
 +  fmp->rhythm.mask = 0x3f;
 +  fmp->rhythm.tl_volume = 0x3c;
 +  for (int i = 0; i < 6; i++) {
 +    fmp->rhythm.volumes[i] = 0x1c;
 +    fmp->rhythm.pans[i] = 0xc0;
 +  }
 +  // couldn't find where this is written,
 +  // but this must be necessary
 +  work->opna_writereg(work, 0x11, fmp->rhythm.tl_volume);
 +  for (int i = 0; i < 6; i++) {
 +    fmp_part_pan_vol_rhythm(work, &fmp->rhythm, i);
 +  }
 +  
 +  {
 +    // 3b86
 +    struct fmp_part *part = &fmp->parts[FMP_PART_ADPCM];
 +    part->current_vol = 0x80;
 +    part->actual_vol = 0x80;
 +    part->gate_cmp = 0;
 +    work->opna_writereg(work, 0x10b, 0x80);
 +    fmp_adpcm_pan(work, fmp, part, 0xc0);
 +  }
 +  // opna flag mask, reset
 +  // work->opna_writereg(work, 0x110, 0x1c);
 +  // work->opna_writereg(work, 0x110, 0x80);
 +  
 +  fmp->timerb = 0xca;
 +  fmp->timerb_bak = 0xca;
 +
 +  // 3c79
 +  for (int i = 0; i < 6; i++) {
 +    fmp->parts[FMP_PART_FM_1+i].current_ptr
 +      = fmp->datainfo.partptr[FMP_DATA_FM_1+i];
 +    fmp->parts[FMP_PART_FM_1+i].loop_ptr
 +      = fmp->datainfo.loopptr[FMP_DATA_FM_1+i];
 +  }
 +  // 3ca3
 +  for (int i = 0; i < 3; i++) {
 +    fmp->parts[FMP_PART_SSG_1+i].current_ptr
 +      = fmp->datainfo.partptr[FMP_DATA_SSG_1+i];
 +    fmp->parts[FMP_PART_SSG_1+i].loop_ptr
 +      = fmp->datainfo.loopptr[FMP_DATA_SSG_1+i];
 +  }
 +  fmp->rhythm.part.current_ptr
 +    = fmp->datainfo.partptr[FMP_DATA_RHYTHM];
 +  fmp->rhythm.part.loop_ptr
 +    = fmp->datainfo.loopptr[FMP_DATA_RHYTHM];
 +    
 +  fmp->parts[FMP_PART_ADPCM].current_ptr
 +    = fmp->datainfo.partptr[FMP_DATA_ADPCM];
 +  fmp->parts[FMP_PART_ADPCM].loop_ptr
 +    = fmp->datainfo.loopptr[FMP_DATA_ADPCM];
 +
 +  // 3d06
 +  for (int i = 0; i < 3; i++) {
 +    fmp->parts[FMP_PART_FM_EX1+i].current_ptr
 +      = fmp->datainfo.partptr[FMP_DATA_FM_EX1+i];
 +    fmp->parts[FMP_PART_FM_EX1+i].loop_ptr
 +      = fmp->datainfo.loopptr[FMP_DATA_FM_EX1+i];
 +  }
 +
 +  // 3d2d
 +  fmp->part_playing_bit = 0x07ff;
 +  fmp->part_loop_bit = 0x07ff;
 +  // TODO: other status bit
 +  fmp->status.looped = false;
 +  fmp->status.stopped = false;
 +  fmp->total_clocks = 0;
 +  fmp->clock_divider = 10;
 +  //  1b64
 +  
 +  // 3c36
 +  /*
 +  if (work->ppz8_functbl) {
 +    work->ppz8_functbl->total_volume(work->ppz8, 
 +  }
 +  */
 +  fmp_fm_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_FM_EX1]);
 +  fmp_fm_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_FM_EX2]);
 +  fmp_fm_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_FM_EX3]);
 +  fmp_ssg_ppz8_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_SSG_1]);
 +  fmp_ssg_ppz8_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_SSG_2]);
 +  fmp_ssg_ppz8_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_SSG_3]);
 +}
 +
 +static void fmp_struct_init(struct fmdriver_work *work,
 +                            struct driver_fmp *fmp) {
 +  // TODO
 +  fmp->pdzf.mode = 2;
 +  // 4e87
 +  fmp->ssg_mix = 0x38;
 +  // 3bb7
 +  work->opna_writereg(work, 0x07, fmp->ssg_mix);
 +
 +  // 5373
 +  // enable OPNA 4-6
 +  work->opna_writereg(work, 0x29, 0x83);
 +  // stop rhythm
 +  work->opna_writereg(work, 0x10, 0xbf);
 +  // reset ADPCM
 +  work->opna_writereg(work, 0x100, 0x21);
 +  work->opna_writereg(work, 0x101, 0x02);
 +  //
 +  fmp->adpcm_c1 = 0x02;
 +
 +  // 5394
 +  // on OPNA, increase volume by 8
 +  // because OPNA's FM output is 6db lower than OPN
 +  fmp->fm_vol = -8;
 +
 +  // 53ca
 +  // TODO: part at 0x1426
 +  // 5408
 +  fmp->parts[FMP_PART_SSG_1].type.ssg = true;
 +  fmp->parts[FMP_PART_SSG_2].type.ssg = true;
 +  fmp->parts[FMP_PART_SSG_3].type.ssg = true;
 +  fmp->parts[FMP_PART_SSG_1].opna_keyon_out = 0;
 +  fmp->parts[FMP_PART_SSG_2].opna_keyon_out = 1;
 +  fmp->parts[FMP_PART_SSG_3].opna_keyon_out = 2;
 +  fmp->parts[FMP_PART_SSG_1].part_bit = 0x0040;
 +  fmp->parts[FMP_PART_SSG_2].part_bit = 0x0080;
 +  fmp->parts[FMP_PART_SSG_3].part_bit = 0x0100;
 +  // 5479
 +  fmp->parts[FMP_PART_FM_1].type.fm = true;
 +  fmp->parts[FMP_PART_FM_2].type.fm = true;
 +  fmp->parts[FMP_PART_FM_3].type.fm = true;
 +  fmp->parts[FMP_PART_FM_3].type.fm_3 = true;
 +  fmp->parts[FMP_PART_FM_4].type.fm = true;
 +  fmp->parts[FMP_PART_FM_5].type.fm = true;
 +  fmp->parts[FMP_PART_FM_6].type.fm = true;
 +  fmp->parts[FMP_PART_FM_1].opna_keyon_out = 0x00;
 +  fmp->parts[FMP_PART_FM_2].opna_keyon_out = 0x01;
 +  fmp->parts[FMP_PART_FM_3].opna_keyon_out = 0x02;
 +  fmp->parts[FMP_PART_FM_4].opna_keyon_out = 0x04;
 +  fmp->parts[FMP_PART_FM_5].opna_keyon_out = 0x05;
 +  fmp->parts[FMP_PART_FM_6].opna_keyon_out = 0x06;
 +  fmp->parts[FMP_PART_FM_1].part_bit = 0x0001;
 +  fmp->parts[FMP_PART_FM_2].part_bit = 0x0002;
 +  fmp->parts[FMP_PART_FM_3].part_bit = 0x0004;
 +  fmp->parts[FMP_PART_FM_4].part_bit = 0x0008;
 +  fmp->parts[FMP_PART_FM_5].part_bit = 0x0010;
 +  fmp->parts[FMP_PART_FM_6].part_bit = 0x0020;
 +  // 5502
 +  fmp->parts[FMP_PART_ADPCM].type.adpcm = true;
 +  fmp->parts[FMP_PART_ADPCM].part_bit = 0x400;
 +  fmp->parts[FMP_PART_FM_EX1].type.fm = true;
 +  fmp->parts[FMP_PART_FM_EX1].type.fm_3 = true;
 +  fmp->parts[FMP_PART_FM_EX2].type.fm = true;
 +  fmp->parts[FMP_PART_FM_EX2].type.fm_3 = true;
 +  fmp->parts[FMP_PART_FM_EX3].type.fm = true;
 +  fmp->parts[FMP_PART_FM_EX3].type.fm_3 = true;
 +  fmp->parts[FMP_PART_FM_EX1].opna_keyon_out = 0x02;
 +  fmp->parts[FMP_PART_FM_EX2].opna_keyon_out = 0x02;
 +  fmp->parts[FMP_PART_FM_EX3].opna_keyon_out = 0x02;
 +  fmp->parts[FMP_PART_FM_EX1].part_bit = 0x0800;
 +  fmp->parts[FMP_PART_FM_EX2].part_bit = 0x1000;
 +  fmp->parts[FMP_PART_FM_EX3].part_bit = 0x2000;
 +  fmp->rhythm.part.type.rhythm = true;
 +  fmp->rhythm.part.part_bit = 0x0200;
 +  // 5625
 +  work->opna_writereg(work, 0x27, 0x30);
 +
 +  // pdzf
 +  fmp->parts[FMP_PART_SSG_1].pdzf.ppz8_channel = 0;
 +  fmp->parts[FMP_PART_SSG_1].pdzf.loopstart32 = -1;
 +  fmp->parts[FMP_PART_SSG_1].pdzf.loopend32 = -1;
 +  fmp->parts[FMP_PART_SSG_2].pdzf.ppz8_channel = 1;
 +  fmp->parts[FMP_PART_SSG_2].pdzf.loopstart32 = -1;
 +  fmp->parts[FMP_PART_SSG_2].pdzf.loopend32 = -1;
 +  fmp->parts[FMP_PART_SSG_3].pdzf.ppz8_channel = 2;
 +  fmp->parts[FMP_PART_SSG_3].pdzf.loopstart32 = -1;
 +  fmp->parts[FMP_PART_SSG_3].pdzf.loopend32 = -1;
 +  fmp->parts[FMP_PART_FM_EX1].pdzf.ppz8_channel = 3;
 +  fmp->parts[FMP_PART_FM_EX1].pdzf.loopstart32 = -1;
 +  fmp->parts[FMP_PART_FM_EX1].pdzf.loopend32 = -1;
 +  fmp->parts[FMP_PART_FM_EX2].pdzf.ppz8_channel = 4;
 +  fmp->parts[FMP_PART_FM_EX2].pdzf.loopstart32 = -1;
 +  fmp->parts[FMP_PART_FM_EX2].pdzf.loopend32 = -1;
 +  fmp->parts[FMP_PART_FM_EX3].pdzf.ppz8_channel = 5;
 +  fmp->parts[FMP_PART_FM_EX3].pdzf.loopstart32 = -1;
 +  fmp->parts[FMP_PART_FM_EX3].pdzf.loopend32 = -1;
 +}
 +
 +// 1774
 +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);
 +  }
 +}
 +
 +static void fmp_title(struct fmdriver_work *work,
 +                      uint8_t *data, uint16_t datalen,
 +                      uint16_t offset) {
 +  for (unsigned i = 0; ; i++) {
 +    int newline = 0;
 +    if ((offset + i) >= datalen) return;
 +    //if (i > 80*3*2) return;
 +    //if (data[offset+i] == 0x0d)
 +    if (data[offset+i] == 0) break;
 +    /*
 +    if ((data[offset+i] == 0x0d) || (data[offset+i] == 0x0a)){
 +      data[offset+i] = 0;
 +      break;
 +    }
 +    */
 +  }
 +  work->title = data+offset;
 +}
 +
 +bool fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp,
 +              uint8_t *data, uint16_t datalen)
 +{
 +  uint16_t offset = read16le(data);
 +  if ((offset+4) > datalen) {
 +    FMDRIVER_DEBUG("invalid offset value\n");
 +    return false;
 +  }
 +  uint8_t dataflags;
 +  bool pviname_valid;
 +  if ((data[offset] == 'F') && (data[offset+1] == 'M') &&
 +      (data[offset+2] == 'C')) {
 +    FMDRIVER_DEBUG("FMP data\n");
 +    uint8_t dataver = data[offset+3];
 +    fmp->data_version = dataver & 0x0f;
 +    if ((dataver & 0xf) >= 0x8) {
 +      FMDRIVER_DEBUG("call word 4152\n");
 +    }
 +    if (dataver <= 0x29) {
 +      FMDRIVER_DEBUG("format:1\n");
 +      if (datalen < 0x1au) {
 +        FMDRIVER_DEBUG("file length shorter than header\n");
 +        return false;
 +      }
 +      fmp->datainfo.partptr[FMP_DATA_FM_1] = read16le(&data[0x02]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_2] = read16le(&data[0x04]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_3] = read16le(&data[0x06]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_4] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_FM_5] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_FM_6] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_SSG_1] = read16le(&data[0x08]);
 +      fmp->datainfo.partptr[FMP_DATA_SSG_2] = read16le(&data[0x0a]);
 +      fmp->datainfo.partptr[FMP_DATA_SSG_3] = read16le(&data[0x0c]);
 +      fmp->datainfo.partptr[FMP_DATA_RHYTHM] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_ADPCM] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX1] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX2] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX3] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_1] = read16le(&data[0x0e]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_2] = read16le(&data[0x10]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_3] = read16le(&data[0x12]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_4] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_5] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_6] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_1] = read16le(&data[0x14]);
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_2] = read16le(&data[0x16]);
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_3] = read16le(&data[0x18]);
 +      fmp->datainfo.loopptr[FMP_DATA_RHYTHM] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_ADPCM] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX1] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX2] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX3] = 0xffff;
 +      fmp->datainfo.bar = data[0x1a];
 +      dataflags = data[0x1b];
 +      fmp->datainfo.adpcmptr = 0xffff;
 +      fmp->datainfo.fmtoneptr = 0x1c;
 +      pviname_valid = false;
 +    } else if (dataver <= 0x49) {
 +      FMDRIVER_DEBUG("format:2\n");
 +      if (datalen < 0x2eu) {
 +        FMDRIVER_DEBUG("file length shorter than header\n");
 +        return false;
 +      }
 +      fmp->datainfo.partptr[FMP_DATA_FM_1] = read16le(&data[0x02]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_2] = read16le(&data[0x04]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_3] = read16le(&data[0x06]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_4] = read16le(&data[0x08]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_5] = read16le(&data[0x0a]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_6] = read16le(&data[0x0c]);
 +      fmp->datainfo.partptr[FMP_DATA_SSG_1] = read16le(&data[0x0e]);
 +      fmp->datainfo.partptr[FMP_DATA_SSG_2] = read16le(&data[0x10]);
 +      fmp->datainfo.partptr[FMP_DATA_SSG_3] = read16le(&data[0x12]);
 +      fmp->datainfo.partptr[FMP_DATA_RHYTHM] = read16le(&data[0x14]);
 +      fmp->datainfo.partptr[FMP_DATA_ADPCM] = read16le(&data[0x16]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX1] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX2] = 0xffff;
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX3] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_1] = read16le(&data[0x18]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_2] = read16le(&data[0x1a]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_3] = read16le(&data[0x1c]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_4] = read16le(&data[0x1e]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_5] = read16le(&data[0x20]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_6] = read16le(&data[0x22]);
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_1] = read16le(&data[0x24]);
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_2] = read16le(&data[0x26]);
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_3] = read16le(&data[0x28]);
 +      fmp->datainfo.loopptr[FMP_DATA_RHYTHM] = read16le(&data[0x2a]);
 +      fmp->datainfo.loopptr[FMP_DATA_ADPCM] = read16le(&data[0x2c]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX1] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX2] = 0xffff;
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX3] = 0xffff;
 +      fmp->datainfo.bar = data[0x2e];
 +      dataflags = data[0x2f];
 +      fmp->datainfo.adpcmptr = read16le(&data[0x30]);
 +      fmp->datainfo.fmtoneptr = 0x32;
 +      pviname_valid = true;
 +    } else if (dataver <= 0x69) {
 +      FMDRIVER_DEBUG("format:3\n");
 +      if (datalen < 0x5eu) {
 +        FMDRIVER_DEBUG("file length shorter than header\n");
 +        return false;
 +      }
 +      fmp->datainfo.partptr[FMP_DATA_FM_1] = read16le(&data[0x02]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_2] = read16le(&data[0x04]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_3] = read16le(&data[0x06]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_4] = read16le(&data[0x08]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_5] = read16le(&data[0x0a]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_6] = read16le(&data[0x0c]);
 +      fmp->datainfo.partptr[FMP_DATA_SSG_1] = read16le(&data[0x0e]);
 +      fmp->datainfo.partptr[FMP_DATA_SSG_2] = read16le(&data[0x10]);
 +      fmp->datainfo.partptr[FMP_DATA_SSG_3] = read16le(&data[0x12]);
 +      fmp->datainfo.partptr[FMP_DATA_RHYTHM] = read16le(&data[0x14]);
 +      fmp->datainfo.partptr[FMP_DATA_ADPCM] = read16le(&data[0x16]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX1] = read16le(&data[0x18]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX2] = read16le(&data[0x1a]);
 +      fmp->datainfo.partptr[FMP_DATA_FM_EX3] = read16le(&data[0x1c]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_1] = read16le(&data[0x30]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_2] = read16le(&data[0x32]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_3] = read16le(&data[0x34]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_4] = read16le(&data[0x36]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_5] = read16le(&data[0x38]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_6] = read16le(&data[0x3a]);
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_1] = read16le(&data[0x3c]);
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_2] = read16le(&data[0x3e]);
 +      fmp->datainfo.loopptr[FMP_DATA_SSG_3] = read16le(&data[0x40]);
 +      fmp->datainfo.loopptr[FMP_DATA_RHYTHM] = read16le(&data[0x42]);
 +      fmp->datainfo.loopptr[FMP_DATA_ADPCM] = read16le(&data[0x44]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX1] = read16le(&data[0x46]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX2] = read16le(&data[0x48]);
 +      fmp->datainfo.loopptr[FMP_DATA_FM_EX3] = read16le(&data[0x4a]);
 +      fmp->datainfo.bar = data[0x5e];
 +      dataflags = data[0x5f];
 +      fmp->datainfo.adpcmptr = read16le(&data[0x60]);
 +      fmp->datainfo.fmtoneptr = 0x66;
 +      pviname_valid = true;
 +    } else {
 +      FMDRIVER_DEBUG("invalid format information\n");
 +      return false;
 +    }
 +  } else if ((data[offset] == 'E') && (data[offset+1] == 'L') &&
 +             (data[offset+2] == 'F')) {
 +    FMDRIVER_DEBUG("PLAY6 data\n");
 +    if (datalen < 0x2au) {
 +      FMDRIVER_DEBUG("file length shorter than header\n");
 +      return false;
 +    }
 +    fmp->data_version = 0x07;
 +    fmp->datainfo.partptr[FMP_DATA_FM_1] = read16le(&data[0x02]);
 +    fmp->datainfo.partptr[FMP_DATA_FM_2] = read16le(&data[0x04]);
 +    fmp->datainfo.partptr[FMP_DATA_FM_3] = read16le(&data[0x06]);
 +    fmp->datainfo.partptr[FMP_DATA_FM_4] = read16le(&data[0x08]);
 +    fmp->datainfo.partptr[FMP_DATA_FM_5] = read16le(&data[0x0a]);
 +    fmp->datainfo.partptr[FMP_DATA_FM_6] = read16le(&data[0x0c]);
 +    fmp->datainfo.partptr[FMP_DATA_SSG_1] = read16le(&data[0x0e]);
 +    fmp->datainfo.partptr[FMP_DATA_SSG_2] = read16le(&data[0x10]);
 +    fmp->datainfo.partptr[FMP_DATA_SSG_3] = read16le(&data[0x12]);
 +    fmp->datainfo.partptr[FMP_DATA_RHYTHM] = read16le(&data[0x14]);
 +    fmp->datainfo.partptr[FMP_DATA_ADPCM] = 0xffff;
 +    fmp->datainfo.partptr[FMP_DATA_FM_EX1] = 0xffff;
 +    fmp->datainfo.partptr[FMP_DATA_FM_EX2] = 0xffff;
 +    fmp->datainfo.partptr[FMP_DATA_FM_EX3] = 0xffff;
 +    fmp->datainfo.loopptr[FMP_DATA_FM_1] = read16le(&data[0x16]);
 +    fmp->datainfo.loopptr[FMP_DATA_FM_2] = read16le(&data[0x18]);
 +    fmp->datainfo.loopptr[FMP_DATA_FM_3] = read16le(&data[0x1a]);
 +    fmp->datainfo.loopptr[FMP_DATA_FM_4] = read16le(&data[0x1c]);
 +    fmp->datainfo.loopptr[FMP_DATA_FM_5] = read16le(&data[0x1e]);
 +    fmp->datainfo.loopptr[FMP_DATA_FM_6] = read16le(&data[0x20]);
 +    fmp->datainfo.loopptr[FMP_DATA_SSG_1] = read16le(&data[0x22]);
 +    fmp->datainfo.loopptr[FMP_DATA_SSG_2] = read16le(&data[0x24]);
 +    fmp->datainfo.loopptr[FMP_DATA_SSG_3] = read16le(&data[0x26]);
 +    fmp->datainfo.loopptr[FMP_DATA_RHYTHM] = read16le(&data[0x28]);
 +    fmp->datainfo.loopptr[FMP_DATA_ADPCM] = 0xffff;
 +    fmp->datainfo.loopptr[FMP_DATA_FM_EX1] = 0xffff;
 +    fmp->datainfo.loopptr[FMP_DATA_FM_EX2] = 0xffff;
 +    fmp->datainfo.loopptr[FMP_DATA_FM_EX3] = 0xffff;
 +    fmp->datainfo.bar = data[0x2a];
 +    dataflags = data[0x2b];
 +    fmp->datainfo.adpcmptr = 0xffff;
 +    fmp->datainfo.fmtoneptr = 0x2e;
 +    pviname_valid = false;
 +  } else {
 +    return false;
 +  }
 +  fmp->datainfo.flags.q = dataflags & 0x01;
 +  fmp->datainfo.flags.ppz = dataflags & 0x02;
 +  fmp->datainfo.flags.lfo_octave_fix = dataflags & 0x04;
 +  {
 +    uint16_t ptr = read16le(&data[0x00]) - 2;
 +    fmp->datainfo.ssgtoneptr = ((ptr+2) > datalen) ? 0xffff : read16le(&data[ptr]);
 +  }
 +  // 3a70
 +  //zero reset part struct
 +  FMDRIVER_DEBUG("bar: %d\n", fmp->datainfo.bar);
 +  for (int i = 0; i < 6; i++) {
 +    FMDRIVER_DEBUG("  FM#%d:   %04X %04X\n",
 +            i+1, fmp->datainfo.partptr[i], fmp->datainfo.loopptr[i]);
 +  }
 +  for (int i = 0; i < 3; i++) {
 +    FMDRIVER_DEBUG("  SSG#%d:  %04X %04X\n",
 +            i+1,
 +            fmp->datainfo.partptr[FMP_DATA_SSG_1+i],
 +            fmp->datainfo.loopptr[FMP_DATA_SSG_1+i]);
 +  }
 +  FMDRIVER_DEBUG("  RHYTHM: %04X %04X\n",
 +          fmp->datainfo.partptr[FMP_DATA_RHYTHM],
 +          fmp->datainfo.loopptr[FMP_DATA_RHYTHM]);
 +  FMDRIVER_DEBUG("  ADPCM:  %04X %04X\n",
 +          fmp->datainfo.partptr[FMP_DATA_ADPCM],
 +          fmp->datainfo.loopptr[FMP_DATA_ADPCM]);
 +  for (int i = 0; i < 3; i++) {
 +    FMDRIVER_DEBUG("  FMEX#%d: %04X %04X\n",
 +            i+1,
 +            fmp->datainfo.partptr[FMP_DATA_FM_EX1+i],
 +            fmp->datainfo.loopptr[FMP_DATA_FM_EX1+i]);
 +  }
 +  FMDRIVER_DEBUG("  FMTONEPTR:  %04X\n", fmp->datainfo.fmtoneptr);
 +  FMDRIVER_DEBUG("  SSGTONEPTR: %04X\n", fmp->datainfo.ssgtoneptr);
 +  FMDRIVER_DEBUG("  data version: 0x%01X\n", fmp->data_version);
 +  fmp->bar_tickcnt = fmp->datainfo.bar;
 +  fmp->data = data;
 +  fmp->datalen = datalen;
 +  fmp_struct_init(work, fmp);
 +  fmp_init_parts(work, fmp);
 +  uint16_t fmtoneptr = fmp->datainfo.fmtoneptr;
 +  FMDRIVER_DEBUG(" 000 %03d %03d\n",
 +                 fmp->data[fmtoneptr+0x18]&0x7,
 +                 (fmp->data[fmtoneptr+0x18]>>3)&0x7
 +  );
 +  for (int i = 0; i < 4; i++) {
 +    static const uint8_t t[4] = {
 +      0, 2, 1, 3,
 +    };
 +    FMDRIVER_DEBUG(" %03d %03d %03d %03d %03d %03d %03d %03d %03d\n",
 +                   fmp->data[fmtoneptr+0x08+t[i]]&0x1f,
 +                   fmp->data[fmtoneptr+0x0c+t[i]]&0x1f,
 +                   fmp->data[fmtoneptr+0x10+t[i]]&0x1f,
 +                   fmp->data[fmtoneptr+0x14+t[i]]&0x0f,
 +                   fmp->data[fmtoneptr+0x14+t[i]]>>4,
 +                   fmp->data[fmtoneptr+0x04+t[i]]&0x7f,
 +                   fmp->data[fmtoneptr+0x08+t[i]]>>6,
 +                   fmp->data[fmtoneptr+0x00+t[i]]&0x0f,
 +                   (fmp->data[fmtoneptr+0x00+t[i]]>>4)&0x7
 +    );
 +  }
 +  fmp_set_tempo(work, fmp);
 +  work->driver = fmp;
 +  work->driver_opna_interrupt = fmp_opna_interrupt;
 +  fmp_title(work, data, datalen, read16le(data)+4);
 +//  uint16_t pcmptr = read16le(data+read16le(data)-2);
 +  uint16_t pcmptr = read16le(data)-0x12;
 +  if (pcmptr <= datalen && (pcmptr+16) < datalen) {
 +    for (int i = 0; i < 8; i++) {
 +      if (pviname_valid) {
 +        fmp->pvi_name[i] = data[pcmptr+8+i];
 +      }
 +      if (pviname_valid && fmp->datainfo.flags.ppz) {
 +        fmp->ppz_name[i] = data[pcmptr+0+i];
 +      }
 +    }
 +  }
 +  return true;
 +}
 +
 +// 4235
 +bool fmp_adpcm_load(struct fmdriver_work *work,
 +                    uint8_t *data, size_t datalen) {
 +  if (datalen < 0x210) return false;
 +  if (datalen > (0x210+(1<<18))) return false;
 +  struct driver_fmp *fmp = (struct driver_fmp *)work->driver;
 +  fmp->adpcm_deltat = read16le(&data[0x8]);
 +  // fmp->adpcm_c1 = data[0x0a];
 +  for (int i = 0; i < 0x80; i++) {
 +    fmp->adpcm_startaddr[i] = read16le(&data[0x10+i*4+0]);
 +    fmp->adpcm_endaddr[i] = read16le(&data[0x10+i*4+2]);
 +  }
 +  work->opna_writereg(work, 0x100, 0x01);
 +  work->opna_writereg(work, 0x110, 0x13);
 +  work->opna_writereg(work, 0x110, 0x80);
 +  work->opna_writereg(work, 0x100, 0x60);
 +  work->opna_writereg(work, 0x101, fmp->adpcm_c1);
 +  work->opna_writereg(work, 0x102, 0x00);
 +  work->opna_writereg(work, 0x103, 0x00);
 +  work->opna_writereg(work, 0x104, 0xff);
 +  work->opna_writereg(work, 0x105, 0xff);
 +  work->opna_writereg(work, 0x10c, 0xff);
 +  work->opna_writereg(work, 0x10d, 0xff);
 +  // 42ca
 +  for (uint32_t i = 0x210; i < datalen; i++) {
 +    work->opna_writereg(work, 0x108, data[i]);
 +  }
 +  work->opna_writereg(work, 0x110, 0x0c);
 +  work->opna_writereg(work, 0x100, 0x01);
 +  return true;
 +}
 diff --git a/fmdriver/fmdriver_fmp.h b/fmdriver/fmdriver_fmp.h new file mode 100644 index 0000000..8ed9451 --- /dev/null +++ b/fmdriver/fmdriver_fmp.h @@ -0,0 +1,529 @@ +#ifndef MYON_FMDRIVER_FMP_H_INCLUDED +#define MYON_FMDRIVER_FMP_H_INCLUDED + +#include "fmdriver.h" +#include <stddef.h> + +enum { +  FMP_PART_FM_1, +  FMP_PART_FM_2, +  FMP_PART_FM_3, +  FMP_PART_FM_4, +  FMP_PART_FM_5, +  FMP_PART_FM_6, +  FMP_PART_FM_EX1, +  FMP_PART_FM_EX2, +  FMP_PART_FM_EX3, +  FMP_PART_PPZ8_1, +  FMP_PART_PPZ8_2, +  FMP_PART_PPZ8_3, +  FMP_PART_PPZ8_4, +  FMP_PART_PPZ8_5, +  FMP_PART_PPZ8_6, +  FMP_PART_PPZ8_7, +  FMP_PART_PPZ8_8, +  FMP_PART_RESERVED, +  FMP_PART_SSG_1, +  FMP_PART_SSG_2, +  FMP_PART_SSG_3, +  FMP_PART_ADPCM, +  FMP_PART_NUM +}; + +enum { +  FMP_RHYTHM_NUM = 6 +}; + +enum { +  FMP_DATA_FM_1, +  FMP_DATA_FM_2, +  FMP_DATA_FM_3, +  FMP_DATA_FM_4, +  FMP_DATA_FM_5, +  FMP_DATA_FM_6, +  FMP_DATA_SSG_1, +  FMP_DATA_SSG_2, +  FMP_DATA_SSG_3, +  FMP_DATA_RHYTHM, +  FMP_DATA_ADPCM, +  FMP_DATA_FM_EX1, +  FMP_DATA_FM_EX2, +  FMP_DATA_FM_EX3, +  FMP_DATA_NUM +}; + +enum { +  FMP_LFO_P = 0, +  FMP_LFO_Q = 1, +  FMP_LFO_R = 2, +}; + +enum { +  FMP_LFOWF_TRIANGLE = 0, +  FMP_LFOWF_SAWTOOTH = 1, +  FMP_LFOWF_SQUARE = 2, +  FMP_LFOWF_LINEAR = 3, +  FMP_LFOWF_STAIRCASE = 4, +  FMP_LFOWF_TRIANGLE2 = 5, +  FMP_LFOWF_RANDOM = 6, +}; + +struct fmp_lfo { +  // 0016 +  uint8_t delay; +  // 0017 +  uint8_t speed; +  // 0018 +  uint8_t delay_cnt; +  // 0019 +  uint8_t speed_cnt; +  // 001a +  uint8_t depth; +  // 001b +  uint8_t depth_cnt; +  // 001c +  uint16_t rate; +  // 001e +  uint16_t rate2; +  // 0020 +  uint8_t waveform; +}; + +struct fmp_ssgenv { +  // 0056 +  uint8_t startvol; +  // 0057 +  uint8_t attack_rate; +  // 0058 +  uint8_t decay_rate; +  // 0059 +  uint8_t sustain_lv; +  // 005a +  uint8_t sustain_rate; +  // 005b +  uint8_t release_rate; +}; + +struct fmp_part { +  // 0000 +  struct { +    // 0x01 +    bool portamento: 1; +    // 0x02 +    bool lfo: 1; // ? +    // 0x04 +    bool e: 1; +    // 0x08 +    bool w: 1; +    // 0x10 +    bool a: 1; +    // 0x20 +    bool r: 1; +    // 0x40 +    bool q: 1; +    // 0x80 +    bool p: 1; +  } lfo_f; +  // 0001 +  uint8_t default_len; +  // 0002 +  uint8_t current_vol; +  // 0003 +  // q command without Q +  // noteoff when equal to gate_cmp +  uint8_t gate_cmp; +  // 0004 +  uint8_t tonelen_cnt; +  // 0005 +  uint8_t gate_cnt; +  // 0006 +  uint8_t actual_vol; +  // 0007 +  // command k +  uint8_t keyon_delay; +  // 0008 +  uint8_t keyon_delay_cnt; +  // 0009 +  uint8_t prev_note; +  // 000a +  struct { +    // 0x01 +    bool keyon: 1; +    // 0x02 +    bool slur: 1; +    // 0x04 +    bool tie_cont: 1; +    // 0x08 +    bool off: 1; +    // 0x10 +    bool lfo_sync: 1; +    // 0x20 +    bool pitchbend: 1; +    // 0x40 +    bool rest: 1; +    // 0x80 +    bool tie: 1; +  } status; +  // 000b +  uint8_t sync; +  // 000c +  uint16_t detune; +  struct { +    // 000e +    uint8_t rate; +    // 000f +    uint8_t delay; +    // 0010 +    uint8_t speed; +    // 0011 +    uint8_t speed_cnt; +    // 0012 +    uint16_t target_freq; +    // 0014 +    uint8_t target_note; +  } pit; +  // 0016 +  struct fmp_lfo lfo[3]; +  // 003a +  uint16_t actual_freq; +  // 003c +  uint16_t prev_freq; +  // 003e +  uint8_t note_diff; +  // 003f +  uint8_t tone; +  // 0040 +  uint16_t ext_keyon; +  // 0042 +  uint8_t pan_ams_pms; +  // 0043 +  uint8_t slot_vol_mask; +  // 0046 +  uint16_t current_ptr; +  // 0048 +  uint16_t loop_ptr; +  // 004a +  struct { +    // 0x01 +    bool fm: 1; +    // 0x02 +    bool fm_3: 1; +    // 0x04 +    bool ssg: 1; +    // 0x08 +    bool rhythm: 1; +    // 0x10 +    bool adpcm: 1; +    // 0x20 +    // SSG backup?? +  } type; +  // 0b00xx x0xx xxxx xxxx +  // || | || |||| |||| +  // || | || |||| |||`- FM#1 +  // || | || |||| ||`-- FM#2 +  // || | || |||| |`--- FM#3 +  // || | || |||| `---- FM#4 +  // || | || |||`------ FM#5 +  // || | || ||`------- FM#6 +  // || | || |`-------- SSG#1 +  // || | || `--------- SSG#2 +  // || | |`----------- SSG#3 +  // || | `------------ rhythm +  // || `-------------- FMEX#1 +  // |`---------------- FMEX#2 +  // `----------------- FMEX#3 +  // 004c +  uint16_t part_bit; +  // 004e +  //   SSG:    1420 +  //   FM:     141e +  //   ADPCM:  1422 +  //   RHYTHM: 1424 +  // 0050 +  uint8_t opna_keyon_out; // output this to 0x28 +  // when SSG, this can be PPZ8 channel num +   +  // 0051 +  uint8_t eff_chan; +  union { +    struct { +      // 0052 (ptr) +      uint8_t tone_tl[4]; +      struct { +        // 0054 +        uint8_t delay; +        // 0055 +        uint8_t speed; +        // 0056 +        uint8_t delay_cnt; +        // 0057 +        uint8_t speed_cnt; +        // 0058 +        uint8_t depth; +        // 0059 +        uint8_t depth_cnt; +        // 005a +        uint8_t rate; +        // 005b +        uint8_t rate_orig; +      } alfo; +      // 005c +      struct fmp_wlfo { +        // 005c +        uint8_t delay; +        // 005d +        uint8_t speed; +        // 005e +        uint8_t delay_cnt; +        // 005f +        uint8_t speed_cnt; +        // 0060 +        uint8_t depth; +        // 0061 +        uint8_t depth_cnt; +        // 0062 +        uint8_t rate; +        // 0063 +        uint8_t rate_orig; +        // 0064 +        uint8_t rate_curr; +        // 0065 +        uint8_t sync; +      } wlfo; +      // 0066 +      uint8_t hlfo_delay; +      // 0067 +      uint8_t hlfo_delay_cnt; +      // 0068 +      // value written to register 0x22 +      uint8_t hlfo_freq; +      // 0069 +      uint8_t hlfo_apms; +      // 006a +      // 0b1234???? +      uint8_t slot_mask; +      // 006b +      uint8_t slot_rel_vol[4]; +    } fm; +    struct { +      // 0052 +      uint8_t curr_vol; +      // 0053 +      struct { +        // 0x01 +        bool ppz: 1; +        // 0x02 +        bool noise: 1; +        // 0x04 +        bool portamento: 1; // ?? +        // 0x10 +        bool release: 1; +        // 0x20 +        bool sustain: 1; +        // 0x40 +        bool decay: 1; +        // 0x80 +        bool attack: 1; +      } env_f; +      /* +      struct { +        // 0x01 +        //bool 1: 1; +      } env_flag; +      */ +      // 0054 +      uint8_t octave; +      // 0055 +      uint8_t vol; +      // 0056 +      struct fmp_ssgenv env; +      // pointer at 005e +      struct fmp_ssgenv envbak; +    } ssg; +    struct { +      // 0052 +      uint16_t startaddr; +      // 0054 +      uint16_t endaddr; +      // 0056 +      uint16_t deltat; +    } adpcm; +  } u; +   +  struct { +    uint32_t loopstart32; +    uint32_t loopend32; +    uint8_t mode; +    uint8_t ppz8_channel; +    uint8_t voice; +    uint8_t vol; +    int8_t pan; +    struct { +      uint8_t al; +      int8_t dd; +      uint8_t sr; +      uint8_t rr; +    } env_param; +    struct { +      enum { +        PDZF_ENV_ATT, +        PDZF_ENV_DEC, +        PDZF_ENV_REL, +        PDZF_ENV_OFF, +      } status; +      uint8_t cnt; +      enum { +        PDZF_ENV_VOL_MIN = -15, +      }; +      int8_t vol; +    } env_state; +    bool keyon; +  } pdzf; +}; + +struct fmp_rhythm { +  // 0acf +  uint8_t mask; +  // 0ad0 +  uint8_t len_cnt; +  // 0ad1 +  uint8_t default_len; +  // 0ad2 +  uint8_t tl_volume; +  // 0ad3 +  uint8_t volumes[FMP_RHYTHM_NUM]; +  // 0ad9 +  uint8_t pans[FMP_RHYTHM_NUM]; +  // 0adf +  //uint8_t loop_now +  // 0ae0 +  uint8_t sync; +  // 0ae1 +  bool status; +  struct fmp_part part; +}; + +struct driver_fmp { +  const uint8_t *data; +  uint16_t datalen; +  // 0103 +  uint8_t timerb; +  struct { +    // 0104 +    uint8_t data; +    // 0105 +    uint16_t cnt; +  } sync; +  // 010d +  struct { +    // 0x4000 +    bool looped: 1; +    // 0x8000 +    bool stopped: 1; +  } status; +  // 010f +  uint16_t total_clocks; +  // 0111 +  uint8_t clock_divider; +  // 0112 +  uint8_t fm_vol; +  // 011a +  uint8_t loop_cnt; +  // 011d +  uint8_t timerb_bak; +  // 011e +  // 0b00FEDFED +  uint8_t ssg_mix; +  // 011f +  // output this to 0x27 to reset timer +  uint8_t timer_ch3; +  // 0120 +  uint8_t ssg_noise_freq; +  // 012f +  uint8_t ssg_mix_se; +  // 0131 +  bool se_proc; +  // 0b54 +  uint8_t loop_dec; +  // 0b55 +  uint8_t loop_times; +  // 0b5e +  uint8_t data_version; +  // 0b6c +  uint8_t bar_tickcnt; +  // 0b92 +  uint8_t fm3_slot_keyon; +  // 0b93 +  uint8_t fm3_alg; +  // 0ba4 +  // see fmp_part::part_bit +  uint16_t part_playing_bit; +  // 0ba6 +  // see fmp_part::part_bit +  uint16_t part_loop_bit; +  // 0b9c +  int16_t ch3_se_freqdiff[4]; + +  // 0cee +  uint16_t adpcm_deltat; +  // 0cf0 +  uint8_t adpcm_c1; +  // 0cf6 +  uint16_t adpcm_startaddr[0x80]; +  uint16_t adpcm_endaddr[0x80]; +  // 0ff6 +  // title +   +  // filename without extension .PVI +  char ppz_name[9]; +  char pvi_name[9]; + +  struct fmp_part parts[FMP_PART_NUM]; +  struct fmp_rhythm rhythm; +  struct { +    uint16_t partptr[FMP_DATA_NUM]; +    uint16_t loopptr[FMP_DATA_NUM]; +    // 4ebe (005e) +    // default len: bar / 4 +    uint8_t bar; +    uint16_t fmtoneptr; +    uint16_t ssgtoneptr; +    uint16_t adpcmptr; +    // *4ebf & 0x01 +    struct { +      // 0x4ebf +      // 0x01 +      bool q: 1; +      // 0x02 +      bool ppz: 1; +      // 0x04 +      bool lfo_octave_fix: 1; +    } flags; +  } datainfo; +  uint8_t rand71; +   +  struct { +    // when 0, no PDZF +    // when 1, PDZF Normal mode +    // when 2, PDZF Enhanced mode +    uint8_t mode; +    struct pdzf_rhythm { +      uint8_t voice[2]; +      int8_t pan; +      uint8_t note; +      bool enabled; +    } rhythm[2]; +  } pdzf; +}; + +// warning: will overwrite data +bool fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp, +              uint8_t *data, uint16_t datalen); +// this function will access opna +bool fmp_adpcm_load(struct fmdriver_work *work, +                    uint8_t *data, size_t datalen); + +// 1da8 +// 6190: fmp external characters + +#endif // MYON_FMDRIVER_FMP_H_INCLUDED diff --git a/fmdriver/ppz8.c b/fmdriver/ppz8.c new file mode 100644 index 0000000..7984575 --- /dev/null +++ b/fmdriver/ppz8.c @@ -0,0 +1,293 @@ +#include "ppz8.h" +#include "fmdriver_common.h" + +void ppz8_init(struct ppz8 *ppz8, uint16_t srate, uint16_t mix_volume) { +  for (int i = 0; i < 2; i++) { +    struct ppz8_pcmbuf *buf = &ppz8->buf[i]; +    buf->data = 0; +    buf->buflen = 0; +    for (int j = 0; j < 128; j++) { +      struct ppz8_pcmvoice *voice = &buf->voice[j]; +      voice->start = 0; +      voice->len = 0; +      voice->loopstart = 0; +      voice->loopend = 0; +      voice->origfreq = 0; +    } +  } +  for (int i = 0; i < 8; i++) { +    struct ppz8_channel *channel = &ppz8->channel[i]; +    channel->ptr = -1; +    channel->loopstartptr = -1; +    channel->loopendptr = -1; +    channel->endptr = 0; +    channel->freq = 0; +    channel->loopstartoff = -1; +    channel->loopendoff = -1; +    channel->prevout[0] = 0; +    channel->prevout[1] = 0; +    channel->vol = 8; +    channel->pan = 5; +    channel->voice = 0; +  } +  ppz8->srate = srate; +  ppz8->totalvol = 12; +  ppz8->mix_volume = mix_volume; +} + +static uint64_t ppz8_loop(const struct ppz8_channel *channel, uint64_t ptr) { +  if (channel->loopstartptr != (uint64_t)-1) { +    if (channel->loopendptr != (uint64_t)-1) { +      if (ptr >= channel->loopendptr) { +        if (channel->loopendptr == channel->loopstartptr) return (uint64_t)-1; +        uint32_t offset = (ptr - channel->loopendptr) >> 16; +        offset %= (uint32_t)((channel->loopendptr - channel->loopstartptr) >> 16); +        offset += (uint32_t)(channel->loopstartptr >> 16); +        return (ptr & ((1<<16)-1)) | (((uint64_t)offset) << 16); +      } +    } else if (ptr >= channel->endptr) { +      if (channel->endptr == channel->loopstartptr) return (uint64_t)-1; +      uint32_t offset = (ptr - channel->endptr) >> 16; +      offset %= (uint32_t)((channel->endptr - channel->loopstartptr) >> 16); +      offset += (uint32_t)(channel->loopstartptr >> 16); +      return (ptr & ((1<<16)-1)) | (((uint64_t)offset) << 16); +    } +  } +  if (ptr >= channel->endptr) { +    return (uint64_t)-1; +  } +  return ptr; +} + +static int32_t ppz8_channel_calc(struct ppz8 *ppz8, struct ppz8_channel *channel) { +  struct ppz8_pcmbuf *buf = &ppz8->buf[channel->voice>>7]; +  struct ppz8_pcmvoice *voice = &buf->voice[channel->voice & 0x7f]; +  int32_t out = 0; +  if (channel->vol) { +    uint16_t coeff = channel->ptr & 0xffffu; +    out += (int32_t)channel->prevout[0] * (0x10000u - coeff); +    out += (int32_t)channel->prevout[1] * coeff; +    out >>= 16; + +    // volume: out * 2**((volume-15)/2) +    out >>= (7 - ((channel->vol&0xf)>>1)); +    if (!(channel->vol&1)) { +      out *= 0xb505; +      out >>= 16; +    } +  } + +  uint64_t ptrdiff = (((uint64_t)channel->freq * voice->origfreq) << 1) / ppz8->srate; +  uint64_t oldptr = channel->ptr; +  channel->ptr += ptrdiff; +  uint32_t bufdiff = (channel->ptr>>16) - (oldptr>>16); +  if (bufdiff) { +    if (/*bufdiff == 1*/0) { +      channel->prevout[0] = channel->prevout[1]; +      channel->prevout[1] = 0; +      channel->ptr = ppz8_loop(channel, channel->ptr); +      if (channel->ptr != (uint64_t)-1) { +        uint32_t bufptr = channel->ptr >> 16; +        if (buf->data && bufptr < buf->buflen) { +          channel->prevout[1] = buf->data[bufptr]; +        } +      } else { +        channel->playing = false; +      } +    } else { +      channel->prevout[0] = 0; +      channel->prevout[1] = 0; +      uint64_t ptr1 = ppz8_loop(channel, channel->ptr - 0x10000); +      if (ptr1 != (uint64_t)-1) { +        uint32_t bufptr = ptr1 >> 16; +        if (buf->data && bufptr < buf->buflen) { +          channel->prevout[0] = buf->data[bufptr]; +        } +        channel->ptr = ppz8_loop(channel, channel->ptr); +        if (channel->ptr != (uint64_t)-1) { +          bufptr = channel->ptr >> 16; +          if (buf->data && bufptr < buf->buflen) { +            channel->prevout[1] = buf->data[bufptr]; +          } +        } else { +          channel->playing = false; +        } +      } else { +        channel->playing = false; +      } +    } +  } +  return out; +} + +void ppz8_mix(struct ppz8 *ppz8, int16_t *buf, unsigned samples) { +  static const uint8_t pan_vol[10][2] = { +    {0, 0}, +    {4, 0}, +    {4, 1}, +    {4, 2}, +    {4, 3}, +    {4, 4}, +    {3, 4}, +    {2, 4}, +    {1, 4}, +    {0, 4} +  }; +  for (unsigned i = 0; i < samples; i++) { +    int32_t lo = buf[i*2+0]; +    int32_t ro = buf[i*2+1]; +    for (int p = 0; p < 8; p++) { +      //if (p < 3) continue; +      //if (p >= 6) continue; +      struct ppz8_channel *channel = &ppz8->channel[p]; +      if (!channel->playing) continue; +      int32_t out = ppz8_channel_calc(ppz8, channel); +      out *= ppz8->mix_volume; +      out >>= 15; +      lo += (out * pan_vol[channel->pan][0]) >> 2; +      ro += (out * pan_vol[channel->pan][1]) >> 2; +    } +    if (lo < INT16_MIN) lo = INT16_MIN; +    if (lo > INT16_MAX) lo = INT16_MAX; +    if (ro < INT16_MIN) ro = INT16_MIN; +    if (ro > INT16_MAX) ro = INT16_MAX; +    buf[i*2+0] = lo; +    buf[i*2+1] = ro; +  } +} + +static int16_t calc_acc(int16_t acc, uint16_t adpcmd, uint8_t data) { +  data &= 0xf; +  int32_t acc_d = (((data&7)<<1)|1); +  if (data&8) acc_d = -acc_d; +  int32_t newacc = acc + (acc_d*adpcmd/8); +  if (newacc < -32768) newacc = -32768; +  if (newacc > 32767) newacc = 32767; +  return newacc; +} + +static uint16_t calc_adpcmd(uint16_t adpcmd, uint8_t data) { +  static const uint8_t adpcm_table[8] = { +    57, 57, 57, 57, 77, 102, 128, 153, +  }; +  uint32_t newadpcmd = adpcmd*adpcm_table[data&7]/64; +  if (newadpcmd < 127) newadpcmd = 127; +  if (newadpcmd > 24576) newadpcmd = 24576; +  return newadpcmd; +} + +bool ppz8_pvi_load(struct ppz8 *ppz8, uint8_t bnum, +                   const uint8_t *pvidata, uint32_t pvidatalen, +                   int16_t *decodebuf) { +  if (bnum >= 2) return false; +  //if (pvidatalen > (0x210+(1<<18))) return false; +  struct ppz8_pcmbuf *buf = &ppz8->buf[bnum]; +  //uint16_t origfreq = ((uint32_t)read16le(&pvidata[0x8]) * 55467) >> 16; +  uint16_t origfreq = (0x49ba*55467)>>16; +  uint32_t lastaddr = 0; +  for (int i = 0; i < 0x80; i++) { +    uint32_t startaddr = read16le(&pvidata[0x10+i*4+0]) << 6; +    uint32_t endaddr = (read16le(&pvidata[0x10+i*4+2])+1) << 6; +    if (startaddr != lastaddr) break; +    if (startaddr >= endaddr) break; +    struct ppz8_pcmvoice *voice = &buf->voice[i]; +    voice->start = startaddr<<1; +    voice->len = (endaddr-startaddr)<<1; +    voice->loopstart = (uint32_t)-1; +    voice->loopend = (uint32_t)-1; +    voice->origfreq = origfreq; + +    int16_t acc = 0; +    uint16_t adpcmd = 127; +    for (uint32_t a = startaddr; a < endaddr; a++) { +      if (pvidatalen <= (0x210+(a>>1))) return false; +      uint8_t data = pvidata[0x210+(a>>1)]; +      if (a&1) { +        data &= 0xf; +      } else { +        data >>= 4; +      } +      acc = calc_acc(acc, adpcmd, data); +      adpcmd = calc_adpcmd(adpcmd, data); +      decodebuf[a] = acc; +    } +    lastaddr = endaddr; +  } +  buf->data = decodebuf; +  buf->buflen = ppz8_pvi_decodebuf_samples(pvidatalen); +  return true; +} + +static void ppz8_channel_play(struct ppz8 *ppz8, uint8_t ch, uint8_t v) { +  if (ch >= 8) return; +  struct ppz8_channel *channel = &ppz8->channel[ch]; +  channel->voice = v; +  struct ppz8_pcmbuf *buf = &ppz8->buf[channel->voice>>7]; +  struct ppz8_pcmvoice *voice = &buf->voice[channel->voice & 0x7f]; +  channel->ptr = ((uint64_t)(voice->start)>>1)<<16; +  channel->endptr = ((uint64_t)(voice->start+voice->len)>>1)<<16; +/*   +  channel->loopstartptr = ((uint64_t)(voice->loopstart)>>1)<<16; +  channel->loopendptr = ((uint64_t)(voice->loopend)>>1)<<16; +*/ +  channel->loopstartptr = (channel->loopstartoff == (uint32_t)-1) ? (uint64_t)-1 +    : channel->ptr + (((uint64_t)(channel->loopstartoff))<<16); +  channel->loopendptr = (channel->loopendoff == (uint32_t)-1) ? (uint64_t)-1 +    : channel->ptr + (((uint64_t)(channel->loopendoff))<<16); +  channel->prevout[0] = 0; +  channel->prevout[1] = 0; +  channel->playing = true; +  uint32_t bufptr = channel->ptr >> 16; +  if (buf->data && bufptr < buf->buflen) { +    channel->prevout[1] = buf->data[bufptr]; +  } +} + +static void ppz8_channel_stop(struct ppz8 *ppz8, uint8_t ch) { +  if (ch >= 8) return; +  struct ppz8_channel *channel = &ppz8->channel[ch]; +  channel->playing = false; +} + +static void ppz8_channel_volume(struct ppz8 *ppz8, uint8_t ch, uint8_t vol) { +  if (ch >= 8) return; +  if (vol >= 16) return; +  struct ppz8_channel *channel = &ppz8->channel[ch]; +  channel->vol = vol; +} + +static void ppz8_channel_freq(struct ppz8 *ppz8, uint8_t ch, uint32_t freq) { +  if (ch >= 8) return; +  struct ppz8_channel *channel = &ppz8->channel[ch]; +  channel->freq = freq; +} + +static void ppz8_channel_loopoffset(struct ppz8 *ppz8, uint8_t ch, +                                    uint32_t startoff, uint32_t endoff) { +  if (ch >= 8) return; +  struct ppz8_channel *channel = &ppz8->channel[ch]; +  channel->loopstartoff = startoff; +  channel->loopendoff = endoff; +} + +static void ppz8_channel_pan(struct ppz8 *ppz8, uint8_t ch, uint8_t pan) { +  if (ch >= 8) return; +  if (pan >= 10) return; +  struct ppz8_channel *channel = &ppz8->channel[ch]; +  channel->pan = pan; +} + +static void ppz8_total_volume(struct ppz8 *ppz8, uint8_t vol) { +  if (vol >= 16) return; +  ppz8->totalvol = vol; +} + +const struct ppz8_functbl ppz8_functbl = { +  ppz8_channel_play, +  ppz8_channel_stop, +  ppz8_channel_volume, +  ppz8_channel_freq, +  ppz8_channel_loopoffset, +  ppz8_channel_pan, +  ppz8_total_volume +}; diff --git a/fmdriver/ppz8.h b/fmdriver/ppz8.h new file mode 100644 index 0000000..6ced669 --- /dev/null +++ b/fmdriver/ppz8.h @@ -0,0 +1,76 @@ +#ifndef MYON_PPZ8_H_INCLUDED +#define MYON_PPZ8_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct ppz8_pcmvoice { +  uint32_t start; +  uint32_t len; +  uint32_t loopstart; +  uint32_t loopend; +  uint16_t origfreq; +}; + +struct ppz8_pcmbuf { +  int16_t *data; +  uint32_t buflen; +  struct ppz8_pcmvoice voice[128]; +}; + +struct ppz8_channel { +  uint64_t ptr; +  uint64_t loopstartptr; +  uint64_t loopendptr; +  uint64_t endptr; +  uint32_t freq; +  uint32_t loopstartoff; +  uint32_t loopendoff; +  int16_t prevout[2]; +  uint8_t vol; +  uint8_t pan; +  uint8_t voice; +  bool playing; +}; + +struct ppz8 { +  struct ppz8_pcmbuf buf[2]; +  struct ppz8_channel channel[8]; +  uint16_t srate; +  uint8_t totalvol; +  uint16_t mix_volume; +}; + +void ppz8_init(struct ppz8 *ppz8, uint16_t srate, uint16_t mix_volume); +void ppz8_mix(struct ppz8 *ppz8, int16_t *buf, unsigned samples); +bool ppz8_pvi_load(struct ppz8 *ppz8, uint8_t buf, +                   const uint8_t *pvidata, uint32_t pvidatalen, +                   int16_t *decodebuf); + +static inline uint32_t ppz8_pvi_decodebuf_samples(uint32_t pvidatalen) { +  if (pvidatalen < 0x210) return 0; +  return (pvidatalen - 0x210) * 2; +} + +struct ppz8_functbl { +  void (*channel_play)(struct ppz8 *ppz8, uint8_t channel, uint8_t voice); +  void (*channel_stop)(struct ppz8 *ppz8, uint8_t channel); +  void (*channel_volume)(struct ppz8 *ppz8, uint8_t channel, uint8_t vol); +  void (*channel_freq)(struct ppz8 *ppz8, uint8_t channel, uint32_t freq); +  void (*channel_loopoffset)(struct ppz8 *ppz8, uint8_t channel, +                             uint32_t startoff, uint32_t endoff); +  void (*channel_pan)(struct ppz8 *ppz8, uint8_t channel, uint8_t pan); +  void (*total_volume)(struct ppz8 *ppz8, uint8_t vol); +}; + +extern const struct ppz8_functbl ppz8_functbl; + +#ifdef __cplusplus +} +#endif + +#endif // MYON_PPZ8_H_INCLUDED diff --git a/libopna/opna.c b/libopna/opna.c new file mode 100644 index 0000000..4e198a0 --- /dev/null +++ b/libopna/opna.c @@ -0,0 +1,24 @@ +#include "opna.h" + +void opna_reset(struct opna *opna) { +  opna_fm_reset(&opna->fm); +  opna_ssg_reset(&opna->ssg); +  opna_ssg_resampler_reset(&opna->resampler); +  opna_drum_reset(&opna->drum); +  opna_adpcm_reset(&opna->adpcm); +} + +void opna_writereg(struct opna *opna, unsigned reg, unsigned val) { +  val &= 0xff; +  opna_fm_writereg(&opna->fm, reg, val); +  opna_ssg_writereg(&opna->ssg, reg, val); +  opna_drum_writereg(&opna->drum, reg, val); +  opna_adpcm_writereg(&opna->adpcm, reg, val); +} + +void opna_mix(struct opna *opna, int16_t *buf, unsigned samples) { +  opna_fm_mix(&opna->fm, buf, samples); +  opna_ssg_mix_55466(&opna->ssg, &opna->resampler, buf, samples); +  opna_drum_mix(&opna->drum, buf, samples); +  opna_adpcm_mix(&opna->adpcm, buf, samples); +} diff --git a/libopna/opna.h b/libopna/opna.h new file mode 100644 index 0000000..a056156 --- /dev/null +++ b/libopna/opna.h @@ -0,0 +1,30 @@ +#ifndef LIBOPNA_OPNA_H_INCLUDED +#define LIBOPNA_OPNA_H_INCLUDED + +#include "opnafm.h" +#include "opnassg.h" +#include "opnadrum.h" +#include "opnaadpcm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct opna { +  struct opna_fm fm; +  struct opna_ssg ssg; +  struct opna_drum drum; +  struct opna_adpcm adpcm; +  struct opna_ssg_resampler resampler; + +}; + +void opna_reset(struct opna *opna); +void opna_writereg(struct opna *opna, unsigned reg, unsigned val); +void opna_mix(struct opna *opna, int16_t *buf, unsigned samples); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNA_H_INCLUDED diff --git a/libopna/opnaadpcm.c b/libopna/opnaadpcm.c new file mode 100644 index 0000000..1157b56 --- /dev/null +++ b/libopna/opnaadpcm.c @@ -0,0 +1,208 @@ +#include "opnaadpcm.h" + +enum { +  C1_START = 0x80, +  C1_REC = 0x40, +  C1_MEMEXT = 0x20, +  C1_REPEAT = 0x10, +  C1_RESET = 0x01, +  C1_MASK = 0xf9, +}; + +enum { +  C2_L = 0x80, +  C2_R = 0x40, +  C2_8BIT = 0x02, +  C2_MASK = 0xcf, +}; + +static const uint8_t adpcm_table[8] = { +  57, 57, 57, 57, 77, 102, 128, 153, +}; + +void opna_adpcm_reset(struct opna_adpcm *adpcm) { +  adpcm->control1 = 0; +  adpcm->control2 = 0; +  adpcm->vol = 0; +  adpcm->delta = 0; +  adpcm->start = 0; +  adpcm->end = 0; +  adpcm->limit = 0; +  adpcm->ramptr = 0; +  adpcm->step = 0; +  adpcm->ram = 0; +  adpcm->acc = 0; +  adpcm->prev_acc = 0; +  adpcm->adpcmd = 127; +  adpcm->out = 0; +} + +static uint32_t addr_conv(const struct opna_adpcm *adpcm, uint16_t a) { +  uint32_t a32 = a; +  return (adpcm->control2 & C2_8BIT) ? (a32<<6) : (a32<<3); +} +static uint32_t addr_conv_e(const struct opna_adpcm *adpcm, uint16_t a) { +  uint32_t a32 = a+1; +  uint32_t ret = (adpcm->control2 & C2_8BIT) ? (a32<<6) : (a32<<3); +  return ret-1; +} + +static void adpcm_calc(struct opna_adpcm *adpcm) { +  uint32_t step = (uint32_t)adpcm->step + (uint32_t)adpcm->delta; +  adpcm->step = step & 0xffff; +  if (step >> 16) { +    if (adpcm->ramptr == addr_conv(adpcm, adpcm->limit)) { +      adpcm->ramptr = 0; +    } +    if (adpcm->ramptr == addr_conv_e(adpcm, adpcm->end)) { +      if (adpcm->control1 & C1_REPEAT) { +        adpcm->ramptr = addr_conv(adpcm, adpcm->start); +        adpcm->acc = 0; +        adpcm->adpcmd = 127; +        adpcm->prev_acc = 0; +      } else { +        // TODO: set EOS +        adpcm->control1 = 0; +        adpcm->out = 0; +        adpcm->prev_acc = 0; +      } +    } +    uint8_t data = 0; +    if (adpcm->ram) { +      data = adpcm->ram[(adpcm->ramptr>>1)&(OPNA_ADPCM_RAM_SIZE-1)]; +    } +    if (adpcm->ramptr&1) { +      data &= 0x0f; +    } else { +      data >>= 4; +    } +    adpcm->ramptr++; +    adpcm->ramptr &= (1<<(24+1))-1; + +    adpcm->prev_acc = adpcm->acc; +    int32_t acc_d = (((data&7)<<1)|1); +    if (data&8) acc_d = -acc_d; +    int32_t acc = adpcm->acc + (acc_d * adpcm->adpcmd / 8); +    if (acc < -32768) acc = -32768; +    if (acc > 32767) acc = 32767; +    adpcm->acc = acc; + +    uint32_t adpcmd = (adpcm->adpcmd * adpcm_table[data&7] / 64); +    if (adpcmd < 127) adpcmd = 127; +    if (adpcmd > 24576) adpcmd = 24576; +    adpcm->adpcmd = adpcmd; +  } +  int32_t out = (int32_t)adpcm->prev_acc * (0x10000-adpcm->step); +  out += (int32_t)adpcm->acc * adpcm->step; +  out >>= 16; +  out *= adpcm->vol; +  out >>= 8; +  if (out < -32768) out = -32768; +  if (out > 32767) out = 32767; +  adpcm->out = out; +} + +void opna_adpcm_writereg(struct opna_adpcm *adpcm, unsigned reg, unsigned val) { +  val &= 0xff; +  if (reg < 0x100) return; +  if (reg >= 0x111) return; +  reg &= 0xff; +  switch (reg) { +  case 0x00: +    adpcm->control1 = val & C1_MASK; +    if (adpcm->control1 & C1_START) { +      adpcm->step = 0; +      adpcm->acc = 0; +      adpcm->prev_acc = 0; +      adpcm->out = 0; +      adpcm->adpcmd = 127; +    } +    if (adpcm->control1 & C1_MEMEXT) { +      adpcm->ramptr = addr_conv(adpcm, adpcm->start); +    } +    if (adpcm->control1 & C1_RESET) { +      adpcm->control1 = 0; +      // TODO: set BRDY +    } +    break; +  case 0x01: +    adpcm->control2 = val & C2_MASK; +    break; +  case 0x02: +    adpcm->start &= 0xff00; +    adpcm->start |= val; +    break; +  case 0x03: +    adpcm->start &= 0x00ff; +    adpcm->start |= (val<<8); +    break; +  case 0x04: +    adpcm->end &= 0xff00; +    adpcm->end |= val; +    break; +  case 0x05: +    adpcm->end &= 0x00ff; +    adpcm->end |= (val<<8); +    break; +  case 0x08: +    // data write +    if ((adpcm->control1 & (C1_START|C1_REC|C1_MEMEXT)) == (C1_REC|C1_MEMEXT)) { +      // external memory write +      if (adpcm->ramptr != addr_conv_e(adpcm, adpcm->end)) { +        if (adpcm->ram) { +          adpcm->ram[(adpcm->ramptr>>1)&(OPNA_ADPCM_RAM_SIZE-1)] = val; +        } +        adpcm->ramptr += 2; +      } else { +        // TODO: set EOS +      } +    } +    break; +  case 0x09: +    adpcm->delta &= 0xff00; +    adpcm->delta |= val; +    break; +  case 0x0a: +    adpcm->delta &= 0x00ff; +    adpcm->delta |= (val<<8); +    break; +  case 0x0b: +    adpcm->vol = val; +    break; +  case 0x0c: +    adpcm->limit &= 0xff00; +    adpcm->limit |= val; +    break; +  case 0x0d: +    adpcm->limit &= 0x00ff; +    adpcm->limit |= (val<<8); +    break; +  } +} + +void opna_adpcm_mix(struct opna_adpcm *adpcm, int16_t *buf, unsigned samples) { +  if (!adpcm->ram) return; +  if (!(adpcm->control1 & C1_START)) return; +  for (unsigned i = 0; i < samples; i++) { +    adpcm_calc(adpcm); +    int32_t lo = buf[i*2+0]; +    int32_t ro = buf[i*2+1]; +    if (adpcm->control2 & C2_L) lo += (adpcm->out>>1); +    if (adpcm->control2 & C2_R) ro += (adpcm->out>>1); +    if (lo < INT16_MIN) lo = INT16_MIN; +    if (lo > INT16_MAX) lo = INT16_MAX; +    if (ro < INT16_MIN) ro = INT16_MIN; +    if (ro > INT16_MAX) ro = INT16_MAX; +    buf[i*2+0] = lo; +    buf[i*2+1] = ro; +    if (!(adpcm->control1 & C1_START)) return; +  } +} + +void opna_adpcm_set_ram_256k(struct opna_adpcm *adpcm, void *ram) { +  adpcm->ram = ram; +} + +void *opna_adpcm_get_ram(struct opna_adpcm *adpcm) { +  return adpcm->ram; +} diff --git a/libopna/opnaadpcm.h b/libopna/opnaadpcm.h new file mode 100644 index 0000000..27a1349 --- /dev/null +++ b/libopna/opnaadpcm.h @@ -0,0 +1,43 @@ +#ifndef LIBOPNA_OPNAADPCM_H_INCLUDED +#define LIBOPNA_OPNAADPCM_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct opna_adpcm { +  uint8_t control1; +  uint8_t control2; +  uint8_t vol; +  uint16_t delta; +  uint16_t start; +  uint16_t end; +  uint16_t limit; +  uint32_t ramptr; +  uint16_t step; +  uint8_t *ram; +  int16_t acc; +  int16_t prev_acc; +  uint16_t adpcmd; +  int16_t out; +}; + +void opna_adpcm_reset(struct opna_adpcm *adpcm); +void opna_adpcm_mix(struct opna_adpcm *adpcm, int16_t *buf, unsigned samples); +void opna_adpcm_writereg(struct opna_adpcm *adpcm, unsigned reg, unsigned val); + +enum { +  OPNA_ADPCM_RAM_SIZE = (1<<18) +}; + +void opna_adpcm_set_ram_256k(struct opna_adpcm *adpcm, void *ram); +void *opna_adpcm_get_ram(struct opna_adpcm *adpcm); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNAADPCM_H_INCLUDED diff --git a/libopna/opnadrum.c b/libopna/opnadrum.c new file mode 100644 index 0000000..2527c1a --- /dev/null +++ b/libopna/opnadrum.c @@ -0,0 +1,141 @@ +#include "opnadrum.h" + +static const uint16_t steps[49] = { +  16,  17,   19,   21,   23,   25,   28, +  31,  34,   37,   41,   45,   50,   55, +  60,  66,   73,   80,   88,   97,  107, +  118, 130,  143,  157,  173,  190,  209, +  230, 253,  279,  307,  337,  371,  408, +  449, 494,  544,  598,  658,  724,  796, +  876, 963, 1060, 1166, 1282, 1411, 1552 +}; + +static const int8_t step_inc[8] = { +  -1, -1, -1, -1, 2, 5, 7, 9 +}; + +void opna_drum_reset(struct opna_drum *drum) { +  for (int d = 0; d < 6; d++) { +    drum->drums[d].data = 0; +    drum->drums[d].playing = false; +    drum->drums[d].index = 0; +    drum->drums[d].len = 0; +    drum->drums[d].level = 0; +    drum->drums[d].left = false; +    drum->drums[d].right = false; +  } +  drum->total_level = 0; +} + +void opna_drum_set_rom(struct opna_drum *drum, void *romptr) { +  uint8_t *rom = (uint8_t *)romptr; +  static const struct { +    unsigned start; +    unsigned end; +    int div; +  } part[6] = { +    {OPNA_ROM_BD_START,  OPNA_ROM_SD_START-1,  3}, +    {OPNA_ROM_SD_START,  OPNA_ROM_TOP_START-1, 3}, +    {OPNA_ROM_TOP_START, OPNA_ROM_HH_START-1,  3}, +    {OPNA_ROM_HH_START,  OPNA_ROM_TOM_START-1, 3}, +    {OPNA_ROM_TOM_START, OPNA_ROM_RIM_START-1, 6}, +    {OPNA_ROM_RIM_START, OPNA_ROM_SIZE-1,      6}, +  }; +  drum->drums[0].data = drum->rom_bd; +  drum->drums[1].data = drum->rom_sd; +  drum->drums[2].data = drum->rom_top; +  drum->drums[3].data = drum->rom_hh; +  drum->drums[4].data = drum->rom_tom; +  drum->drums[5].data = drum->rom_rim; +  for (int p = 0; p < 6; p++) { +    drum->drums[p].playing = false; +    drum->drums[p].index = 0; +    unsigned addr = part[p].start << 1; +    int step = 0; +    unsigned acc = 0; +    int outindex = 0; +    for (;;) { +      if ((addr>>1) == part[p].end) break; +      unsigned data = rom[addr>>1]; +      if (!(addr&1)) data >>= 4; +      data &= ((1<<4)-1); +      int acc_diff = ((((data&7)<<1)|1) * steps[step]) >> 3; +      if (data&8) acc_diff = -acc_diff; +      acc += acc_diff; +      step += step_inc[data&7]; +      if (step < 0) step = 0; +      if (step > 48) step = 48; +      addr++; + +      int out = acc & ((1u<<12)-1); +      if (out >= (1<<11)) out -= (1<<12); +      int16_t out16 = out << 4; +      for (int i = 0; i < part[p].div; i++) { +        drum->drums[p].data[outindex] = out16; +        outindex++; +      } +    } +    drum->drums[p].len = outindex; +  } +} + +void opna_drum_mix(struct opna_drum *drum, int16_t *buf, int samples) { +  for (int i = 0; i < samples; i++) { +    int32_t lo = buf[i*2+0]; +    int32_t ro = buf[i*2+1]; +    for (int d = 0; d < 6; d++) { +      if (drum->drums[d].playing && drum->drums[d].data) { +        int co = drum->drums[d].data[drum->drums[d].index]; +        co >>= 4; +        unsigned level = (drum->drums[d].level^0x1f) + (drum->total_level^0x3f); +        co *= 15 - (level&7); +        co >>= 1+(level>>3); +        if (drum->drums[d].left) lo += co; +        if (drum->drums[d].right) ro += co; +        drum->drums[d].index++; +        if (drum->drums[d].index == drum->drums[d].len) { +          drum->drums[d].index = 0; +          drum->drums[d].playing = false; +        } +      } +    } +    if (lo < INT16_MIN) lo = INT16_MIN; +    if (lo > INT16_MAX) lo = INT16_MAX; +    if (ro < INT16_MIN) ro = INT16_MIN; +    if (ro > INT16_MAX) ro = INT16_MAX; +    buf[i*2+0] = lo; +    buf[i*2+1] = ro; +  } +} + +void opna_drum_writereg(struct opna_drum *drum, unsigned reg, unsigned val) { +  val &= 0xff; +  switch (reg) { +  case 0x10: +    for (int d = 0; d < 6; d++) { +      if (val & (1<<d)) { +        drum->drums[d].playing = !(val & 0x80); +        drum->drums[d].index = 0; +      } +    } +    break; +  case 0x11: +    drum->total_level = val & 0x3f; +    break; +  case 0x18: +  case 0x19: +  case 0x1a: +  case 0x1b: +  case 0x1c: +  case 0x1d: +    { +      int d = reg - 0x18; +      drum->drums[d].left = val & 0x80; +      drum->drums[d].right = val & 0x40; +      drum->drums[d].level = val & 0x1f; +    } +    break; +  default: +    break; +  } +} diff --git a/libopna/opnadrum.h b/libopna/opnadrum.h new file mode 100644 index 0000000..24bcd48 --- /dev/null +++ b/libopna/opnadrum.h @@ -0,0 +1,57 @@ +#ifndef LIBOPNA_OPNADRUM_H_INCLUDED +#define LIBOPNA_OPNADRUM_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define OPNA_ROM_BD_START  0x0000 +#define OPNA_ROM_SD_START  0x01c0 +#define OPNA_ROM_TOP_START 0x0440 +#define OPNA_ROM_HH_START  0x1b80 +#define OPNA_ROM_TOM_START 0x1d00 +#define OPNA_ROM_RIM_START 0x1f80 +#define OPNA_ROM_SIZE       0x2000 + +#define OPNA_ROM_BD_SIZE   ((OPNA_ROM_SD_START-OPNA_ROM_BD_START)*2*3) +#define OPNA_ROM_SD_SIZE   ((OPNA_ROM_TOP_START-OPNA_ROM_SD_START)*2*3) +#define OPNA_ROM_TOP_SIZE   ((OPNA_ROM_HH_START-OPNA_ROM_TOP_START)*2*3) +#define OPNA_ROM_HH_SIZE   ((OPNA_ROM_TOM_START-OPNA_ROM_HH_START)*2*3) +#define OPNA_ROM_TOM_SIZE   ((OPNA_ROM_RIM_START-OPNA_ROM_TOM_START)*2*6) +#define OPNA_ROM_RIM_SIZE   ((OPNA_ROM_SIZE-OPNA_ROM_RIM_START)*2*6) + +struct opna_drum { +  struct { +    int16_t *data; +    bool playing; +    unsigned index; +    unsigned len; +    unsigned level; +    bool left; +    bool right; +  } drums[6]; +  unsigned total_level; +  int16_t rom_bd[OPNA_ROM_BD_SIZE]; +  int16_t rom_sd[OPNA_ROM_SD_SIZE]; +  int16_t rom_top[OPNA_ROM_TOP_SIZE]; +  int16_t rom_hh[OPNA_ROM_HH_SIZE]; +  int16_t rom_tom[OPNA_ROM_TOM_SIZE]; +  int16_t rom_rim[OPNA_ROM_RIM_SIZE]; +}; + +void opna_drum_reset(struct opna_drum *drum); +// set rom data, size: 0x2000 (8192) bytes +void opna_drum_set_rom(struct opna_drum *drum, void *rom); + +void opna_drum_mix(struct opna_drum *drum, int16_t *buf, int samples); + +void opna_drum_writereg(struct opna_drum *drum, unsigned reg, unsigned val); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNADRUM_H_INCLUDED diff --git a/libopna/opnafm.c b/libopna/opnafm.c new file mode 100644 index 0000000..c7512a8 --- /dev/null +++ b/libopna/opnafm.c @@ -0,0 +1,511 @@ +#include "opnafm.h" + +#include "opnatables.h" + +enum { +  CH3_MODE_NORMAL = 0, +  CH3_MODE_CSM    = 1, +  CH3_MODE_SE     = 2 +}; + +static void opna_fm_slot_reset(struct opna_fm_slot *slot) { +  slot->phase = 0; +  slot->env = 1023; +  slot->env_count = 0; +  slot->env_state = ENV_RELEASE; +  slot->rate_shifter = 0; +  slot->rate_selector = 0; +  slot->rate_mul = 0; +  slot->tl = 0; +  slot->sl = 0; +  slot->ar = 0; +  slot->dr = 0; +  slot->sr = 0; +  slot->rr = 0; +  slot->mul = 0; +  slot->det = 0; +  slot->ks = 0; +  slot->keyon = false; +} + + +void opna_fm_chan_reset(struct opna_fm_channel *chan) { +  for (int i = 0; i < 4; i++) { +    opna_fm_slot_reset(&chan->slot[i]); +  } + +  chan->fbmem1 = 0; +  chan->fbmem2 = 0; +  chan->alg_mem = 0; + +  chan->alg = 0; +  chan->fb = 0; +  chan->fnum = 0; +  chan->blk = 0; +} + +void opna_fm_reset(struct opna_fm *fm) { +  for (int i = 0; i < 6; i++) { +    opna_fm_chan_reset(&fm->channel[i]); +    fm->lselect[i] = true; +    fm->rselect[i] = true; +  } +  fm->blkfnum_h = 0; +  fm->env_div3 = 0; +   +  fm->ch3.mode = CH3_MODE_NORMAL; +  for (int i = 0; i < 3; i++) { +    fm->ch3.fnum[i] = 0; +    fm->ch3.blk[i] = 0; +  } +} +#define LIBOPNA_ENABLE_HIRES +// maximum output: 2042<<2 = 8168 +static int16_t opna_fm_slotout(struct opna_fm_slot *slot, int16_t modulation) { +  unsigned pind = (slot->phase >> 10); +  pind += modulation >> 1; +  bool minus = pind & (1<<(LOGSINTABLEBIT+1)); +  bool reverse = pind & (1<<LOGSINTABLEBIT); +  if (reverse) pind = ~pind; +  pind &= (1<<LOGSINTABLEBIT)-1; + +#ifdef LIBOPNA_ENABLE_HIRES +  unsigned pind_hires = (slot->phase >> 8); +  pind_hires += modulation << 1; +  minus = pind_hires & (1<<(LOGSINTABLEHIRESBIT+1)); +  reverse = pind_hires & (1<<LOGSINTABLEHIRESBIT); +  if (reverse) pind_hires = ~pind_hires; +  pind_hires &= (1<<LOGSINTABLEHIRESBIT)-1; + +  int logout = logsintable_hires[pind_hires] + (slot->env << 2) + (slot->tl << 5); +#else +  int logout = logsintable[pind] + (slot->env << 2) + (slot->tl << 5); +#endif // LIBOPNA_ENABLE_HIRES + +  int selector = logout & ((1<<EXPTABLEBIT)-1); +  int shifter = logout >> EXPTABLEBIT; +  if (shifter > 13) shifter = 13;  + +  int16_t out = (exptable[selector] << 2) >> shifter; +  if (minus) out = -out; +  return out; +} + +static unsigned blkfnum2freq(unsigned blk, unsigned fnum) { +  return (fnum << blk) >> 1; +} + +#define F(n) (!!(fnum & (1 << ((n)-1)))) + +static unsigned blkfnum2keycode(unsigned blk, unsigned fnum) { +  unsigned keycode = blk<<2; +  keycode |= F(11) << 1; +  keycode |= (F(11) && (F(10)||F(9)||F(8))) || ((!F(11))&&F(10)&&F(9)&&F(8)); +  return keycode; +} + +#undef F + +static void opna_fm_slot_phase(struct opna_fm_slot *slot, unsigned freq) { +// TODO: detune +//  freq += slot->dt; +  unsigned det = dettable[slot->det & 0x3][slot->keycode]; +  if (slot->det & 0x4) det = -det; +  freq += det; +  freq &= (1U<<17)-1; +  int mul = slot->mul << 1; +  if (!mul) mul = 1; +  slot->phase += ((freq * mul)>>1); +} + +void opna_fm_chan_phase(struct opna_fm_channel *chan) { +  unsigned freq = blkfnum2freq(chan->blk, chan->fnum); +  for (int i = 0; i < 4; i++) { +    opna_fm_slot_phase(&chan->slot[i], freq); +  } +} + +static void opna_fm_chan_phase_se(struct opna_fm_channel *chan, struct opna_fm *fm) { +  unsigned freq; +  freq = blkfnum2freq(fm->ch3.blk[2], fm->ch3.fnum[1]); +  opna_fm_slot_phase(&chan->slot[0], freq); +  freq = blkfnum2freq(fm->ch3.blk[0], fm->ch3.fnum[0]); +  opna_fm_slot_phase(&chan->slot[2], freq); +  freq = blkfnum2freq(fm->ch3.blk[1], fm->ch3.fnum[2]); +  opna_fm_slot_phase(&chan->slot[1], freq); +  freq = blkfnum2freq(chan->blk, chan->fnum); +  opna_fm_slot_phase(&chan->slot[3], freq); +} + +int16_t opna_fm_chanout(struct opna_fm_channel *chan) { +  int16_t fb = chan->fbmem1 + chan->fbmem2; +  int16_t slot0 = chan->fbmem1; +  chan->fbmem1 = chan->fbmem2; +  if (!chan->fb) fb = 0; +  chan->fbmem2 = opna_fm_slotout(&chan->slot[0], fb >> (9 - chan->fb)); + +  int16_t slot2; +  int16_t out = 0; + +  switch (chan->alg) { +  case 0: +    slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); +    chan->alg_mem = opna_fm_slotout(&chan->slot[1], slot0); +    out = opna_fm_slotout(&chan->slot[3], slot2); +    break; +  case 1: +    slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); +    chan->alg_mem = slot0; +    chan->alg_mem += opna_fm_slotout(&chan->slot[1], 0); +    out = opna_fm_slotout(&chan->slot[3], slot2); +    break; +  case 2: +    slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); +    chan->alg_mem = opna_fm_slotout(&chan->slot[1], 0); +    out = opna_fm_slotout(&chan->slot[3], slot0 + slot2); +    break; +  case 3: +    slot2 = opna_fm_slotout(&chan->slot[2], 0); +    out = opna_fm_slotout(&chan->slot[3], slot2 + chan->alg_mem); +    chan->alg_mem = opna_fm_slotout(&chan->slot[1], slot0); +    break; +  case 4: +    out = opna_fm_slotout(&chan->slot[1], slot0); +    slot2 = opna_fm_slotout(&chan->slot[2], 0); +    out += opna_fm_slotout(&chan->slot[3], slot2); +    break; +  case 5: +    out = opna_fm_slotout(&chan->slot[2], chan->alg_mem); +    chan->alg_mem = slot0; +    out += opna_fm_slotout(&chan->slot[1], slot0); +    out += opna_fm_slotout(&chan->slot[3], slot0); +    break; +  case 6: +    out = opna_fm_slotout(&chan->slot[1], slot0); +    out += opna_fm_slotout(&chan->slot[2], 0); +    out += opna_fm_slotout(&chan->slot[3], 0); +    break; +  case 7: +    out = slot0; +    out += opna_fm_slotout(&chan->slot[1], 0); +    out += opna_fm_slotout(&chan->slot[2], 0); +    out += opna_fm_slotout(&chan->slot[3], 0); +    break; +  } +   +  return out; +} + +static void opna_fm_slot_setrate(struct opna_fm_slot *slot, int status) { +  int r; +  switch (status) { +  case ENV_ATTACK: +    r = slot->ar; +    break; +  case ENV_DECAY: +    r = slot->dr; +    break; +  case ENV_SUSTAIN: +    r = slot->sr; +    break; +  case ENV_RELEASE: +    r = (slot->rr*2+1); +    break; +  default: +    return; +  } + +  if (!r) { +    slot->rate_selector = 0; +    slot->rate_mul = 0; +    slot->rate_shifter = 0; +    return; +  } + +  int rate = 2*r + (slot->keycode >> (3 - slot->ks)); + +  if (rate > 63) rate = 63; +  int rate_shifter = 11 - (rate >> 2); +  if (rate_shifter < 0) { +    slot->rate_selector = (rate & ((1<<2)-1)) + 4; +    slot->rate_mul = 1<<(-rate_shifter-1); +    slot->rate_shifter = 0; +  } else { +    slot->rate_selector = rate & ((1<<2)-1); +    slot->rate_mul = 1; +    slot->rate_shifter = rate_shifter; +  } +} + +static void opna_fm_slot_env(struct opna_fm_slot *slot) { +  slot->env_count++; +  if (!(slot->env_count & ((1<<slot->rate_shifter)-1))) { +    int rate_index = (slot->env_count >> slot->rate_shifter) & 7; +    int env_inc = rateinctable[slot->rate_selector][rate_index]; +    env_inc *= slot->rate_mul; + +    switch (slot->env_state) { +    int newenv; +    int sl; +    case ENV_ATTACK: +      newenv = slot->env + (((-slot->env-1) * env_inc) >> 4); +      if (newenv <= 0) { +        slot->env = 0; +        slot->env_state = ENV_DECAY; +        opna_fm_slot_setrate(slot, ENV_DECAY); +      } else { +        slot->env = newenv; +      } +      break; +    case ENV_DECAY: +      slot->env += env_inc; +      sl = slot->sl; +      if (sl == 0xf) sl = 0x1f; +      if (slot->env >= (sl << 5)) { +        slot->env_state = ENV_SUSTAIN; +        opna_fm_slot_setrate(slot, ENV_SUSTAIN); +      } +      break; +    case ENV_SUSTAIN: +      slot->env += env_inc; +      if (slot->env >= 1023) slot->env = 1023; +      break; +    case ENV_RELEASE: +      slot->env += env_inc; +      if (slot->env >= 1023) { +        slot->env = 1023; +        slot->env_state = ENV_OFF; +      } +      break; +    } +  } +} + +void opna_fm_slot_key(struct opna_fm_channel *chan, int slotnum, bool keyon) { +  struct opna_fm_slot *slot = &chan->slot[slotnum]; +  if (keyon) { +    if (!slot->keyon) { +      slot->keyon = true; +      slot->env_state = ENV_ATTACK; +      slot->env_count = 0; +      slot->phase = 0; +      opna_fm_slot_setrate(slot, ENV_ATTACK); +    } +  } else { +    if ((slot->env_state != ENV_OFF) && slot->keyon) { +      slot->keyon = false; +      slot->env_state = ENV_RELEASE; +      opna_fm_slot_setrate(slot, ENV_RELEASE); +    } +  } +} + +void opna_fm_slot_set_det(struct opna_fm_slot *slot, unsigned det) { +  det &= 0x7; +  slot->det = det; +} + +void opna_fm_slot_set_mul(struct opna_fm_slot *slot, unsigned mul) { +  mul &= 0xf; +  slot->mul = mul; +} + +void opna_fm_slot_set_tl(struct opna_fm_slot *slot, unsigned tl) { +  tl &= 0x7f; +  slot->tl = tl; +} + +void opna_fm_slot_set_ks(struct opna_fm_slot *slot, unsigned ks) { +  ks &= 0x3; +  slot->ks = ks; +} + +void opna_fm_slot_set_ar(struct opna_fm_slot *slot, unsigned ar) { +  ar &= 0x1f; +  slot->ar = ar; +  if (slot->env_state == ENV_ATTACK) { +    opna_fm_slot_setrate(slot, ENV_ATTACK); +  } +} + +void opna_fm_slot_set_dr(struct opna_fm_slot *slot, unsigned dr) { +  dr &= 0x1f; +  slot->dr = dr; +  if (slot->env_state == ENV_DECAY) { +    opna_fm_slot_setrate(slot, ENV_DECAY); +  } +} + +void opna_fm_slot_set_sr(struct opna_fm_slot *slot, unsigned sr) { +  sr &= 0x1f; +  slot->sr = sr; +  if (slot->env_state == ENV_SUSTAIN) { +    opna_fm_slot_setrate(slot, ENV_SUSTAIN); +  } +} + +void opna_fm_slot_set_sl(struct opna_fm_slot *slot, unsigned sl) { +  sl &= 0xf; +  slot->sl = sl; +} + +void opna_fm_slot_set_rr(struct opna_fm_slot *slot, unsigned rr) { +  rr &= 0xf; +  slot->rr = rr; +  if (slot->env_state == ENV_RELEASE) { +    opna_fm_slot_setrate(slot, ENV_RELEASE); +  } +} + +void opna_fm_chan_set_blkfnum(struct opna_fm_channel *chan, unsigned blk, unsigned fnum) { +  blk &= 0x7; +  fnum &= 0x7ff; +  chan->blk = blk; +  chan->fnum = fnum; +  for (int i = 0; i < 4; i++) { +    chan->slot[i].keycode = blkfnum2keycode(chan->blk, chan->fnum); +    opna_fm_slot_setrate(&chan->slot[i], chan->slot[i].env_state); +  } +} + +void opna_fm_chan_set_alg(struct opna_fm_channel *chan, unsigned alg) { +  alg &= 0x7; +  chan->alg = alg; +} + +void opna_fm_chan_set_fb(struct opna_fm_channel *chan, unsigned fb) { +  fb &= 0x7; +  chan->fb = fb; +} +//#include <stdio.h> +void opna_fm_writereg(struct opna_fm *fm, unsigned reg, unsigned val) { +  val &= (1<<8)-1; + +  if (reg > 0x1ff) return; + +  switch (reg) { +  case 0x27: +    { +      unsigned mode = val >> 6; +      if (mode != fm->ch3.mode) { +//        printf("0x27\n"); +//        printf("  mode = %d\n", mode); +        fm->ch3.mode = mode; +      } +    } +    return; +  case 0x28: +    { +      int c = val & 0x3; +      if (c == 3) return; +      if (val & 0x4) c += 3; +      for (int i = 0; i < 4; i++) { +        opna_fm_slot_key(&fm->channel[c], i, (val & (1<<(4+i)))); +      } +    } +    return; +  } + +  int c = reg & 0x3; +  if (c == 3) return; +  if (reg & (1<<8)) c += 3; +  int s = ((reg & (1<<3)) >> 3) | ((reg & (1<<2)) >> 1); +  struct opna_fm_channel *chan = &fm->channel[c]; +  struct opna_fm_slot *slot = &chan->slot[s]; +  switch (reg & 0xf0) { +  case 0x30: +    opna_fm_slot_set_det(slot, (val >> 4) & 0x7); +    opna_fm_slot_set_mul(slot, val & 0xf); +    break; +  case 0x40: +    opna_fm_slot_set_tl(slot, val & 0x7f); +    break; +  case 0x50: +    opna_fm_slot_set_ks(slot, (val >> 6) & 0x3); +    opna_fm_slot_set_ar(slot, val & 0x1f); +    break; +  case 0x60: +    opna_fm_slot_set_dr(slot, val & 0x1f); +    break; +  case 0x70: +    opna_fm_slot_set_sr(slot, val & 0x1f); +    break; +  case 0x80: +    opna_fm_slot_set_sl(slot, (val >> 4) & 0xf); +    opna_fm_slot_set_rr(slot, val & 0xf); +    break; +  case 0xa0: +    { +      unsigned blk = (fm->blkfnum_h >> 3) & 0x7; +      unsigned fnum = ((fm->blkfnum_h & 0x7) << 8) | (val & 0xff); +      switch (reg & 0xc) { +      case 0x0: +        opna_fm_chan_set_blkfnum(chan, blk, fnum); +        break; +      case 0x8: +        c %= 3; +        fm->ch3.blk[c] = blk; +        fm->ch3.fnum[c] = fnum; +        break; +      case 0x4: +      case 0xc: +        fm->blkfnum_h = val & 0x3f; +        break; +      } +    } +    break; +  case 0xb0: +    switch (reg & 0xc) { +    case 0x0: +      opna_fm_chan_set_alg(chan, val & 0x7); +      opna_fm_chan_set_fb(chan, (val >> 3) & 0x7); +      break; +    case 0x4: +      fm->lselect[c] = val & 0x80; +      fm->rselect[c] = val & 0x40; +      break; +    } +    break; +  } +} + +void opna_fm_chan_env(struct opna_fm_channel *chan) { +  for (int i = 0; i < 4; i++) { +    opna_fm_slot_env(&chan->slot[i]); +  } +} + +void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples) { +  for (unsigned i = 0; i < samples; i++) { +    if (!fm->env_div3) { +      for (int c = 0; c < 6; c++) { +        opna_fm_chan_env(&fm->channel[c]); +      } +      fm->env_div3 = 3; +    } +    fm->env_div3--; +     +    int32_t lo = buf[i*2+0]; +    int32_t ro = buf[i*2+1]; + +    for (int c = 0; c < 6; c++) { +      int16_t o = opna_fm_chanout(&fm->channel[c]); +      // TODO: CSM +      if (c == 2 && fm->ch3.mode != CH3_MODE_NORMAL) { +        opna_fm_chan_phase_se(&fm->channel[c], fm); +      } else { +        opna_fm_chan_phase(&fm->channel[c]); +      } +      o >>= 1; +      if (fm->lselect[c]) lo += o; +      if (fm->rselect[c]) ro += o; +    } + +    if (lo < INT16_MIN) lo = INT16_MIN; +    if (lo > INT16_MAX) lo = INT16_MAX; +    if (ro < INT16_MIN) ro = INT16_MIN; +    if (ro > INT16_MAX) ro = INT16_MAX; +    buf[i*2+0] = lo; +    buf[i*2+1] = ro; +  } +} diff --git a/libopna/opnafm.h b/libopna/opnafm.h new file mode 100644 index 0000000..acd673f --- /dev/null +++ b/libopna/opnafm.h @@ -0,0 +1,111 @@ +#ifndef LIBOPNA_OPNAFM_H_INCLUDED +#define LIBOPNA_OPNAFM_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +enum { +  ENV_ATTACK, +  ENV_DECAY, +  ENV_SUSTAIN, +  ENV_RELEASE, +  ENV_OFF, +}; + +struct opna_fm_slot { +  // 20bits, upper 10 bits will be the index to sine table +  uint32_t phase; +  // 10 bits +  uint16_t env; +  uint16_t env_count; +  uint8_t env_state; +  uint8_t rate_shifter; +  uint8_t rate_selector; +  uint8_t rate_mul; + +  uint8_t tl; +  uint8_t sl; + +  uint8_t ar; +  uint8_t dr; +  uint8_t sr; +  uint8_t rr; + +  uint8_t mul; +  uint8_t det; +  uint8_t ks; + +  uint8_t keycode; + +  bool keyon; +}; + +struct opna_fm_channel { +  struct opna_fm_slot slot[4]; + +  // save 2 samples for slot 1 feedback +  uint16_t fbmem1; +  uint16_t fbmem2; +  // save sample for long (>2) chain of slots +  uint16_t alg_mem; + +  uint8_t alg; +  uint8_t fb; +  uint16_t fnum; +  uint8_t blk; +}; + +struct opna_fm { +  struct opna_fm_channel channel[6]; + +  // remember here what was written on higher byte, +  // actually write when lower byte written +  uint8_t blkfnum_h; +  // channel 3 blk, fnum +  struct { +    uint16_t fnum[3]; +    uint8_t blk[3]; +    uint8_t mode; +  } ch3; + +  // do envelope once per 3 samples +  uint8_t env_div3; + +  // pan +  bool lselect[6]; +  bool rselect[6]; +}; + +void opna_fm_reset(struct opna_fm *fm); +void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples); +void opna_fm_writereg(struct opna_fm *fm, unsigned reg, unsigned val); + +// +void opna_fm_chan_reset(struct opna_fm_channel *chan); +void opna_fm_chan_phase(struct opna_fm_channel *chan); +void opna_fm_chan_env(struct opna_fm_channel *chan); +void opna_fm_chan_set_blkfnum(struct opna_fm_channel *chan, unsigned blk, unsigned fnum); +int16_t opna_fm_chanout(struct opna_fm_channel *chan); +void opna_fm_slot_key(struct opna_fm_channel *chan, int slotnum, bool keyon); + +void opna_fm_chan_set_alg(struct opna_fm_channel *chan, unsigned alg); +void opna_fm_chan_set_fb(struct opna_fm_channel *chan, unsigned fb); +void opna_fm_slot_set_ar(struct opna_fm_slot *slot, unsigned ar); +void opna_fm_slot_set_dr(struct opna_fm_slot *slot, unsigned dr); +void opna_fm_slot_set_sr(struct opna_fm_slot *slot, unsigned sr); +void opna_fm_slot_set_rr(struct opna_fm_slot *slot, unsigned rr); +void opna_fm_slot_set_sl(struct opna_fm_slot *slot, unsigned sl); +void opna_fm_slot_set_tl(struct opna_fm_slot *slot, unsigned tl); +void opna_fm_slot_set_ks(struct opna_fm_slot *slot, unsigned ks); +void opna_fm_slot_set_mul(struct opna_fm_slot *slot, unsigned mul); +void opna_fm_slot_set_det(struct opna_fm_slot *slot, unsigned det); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBOPNA_OPNAFM_H_INCLUDED */ diff --git a/libopna/opnassg.c b/libopna/opnassg.c new file mode 100644 index 0000000..d87dc91 --- /dev/null +++ b/libopna/opnassg.c @@ -0,0 +1,233 @@ +#include "opnassg.h" +/* +static const float voltable[32] = { +  0.0f,           0.0f,           0x1.ae89f9p-8f, 0x1.000000p-7f, +  0x1.306fe0p-7f, 0x1.6a09e6p-7f, 0x1.ae89f9p-7f, 0x1.000000p-6f, +  0x1.306fe0p-6f, 0x1.6a09e6p-6f, 0x1.ae89f9p-6f, 0x1.000000p-5f, +  0x1.306fe0p-5f, 0x1.6a09e6p-5f, 0x1.ae89f9p-5f, 0x1.000000p-4f, +  0x1.306fe0p-4f, 0x1.6a09e6p-4f, 0x1.ae89f9p-4f, 0x1.000000p-3f, +  0x1.306fe0p-3f, 0x1.6a09e6p-3f, 0x1.ae89f9p-3f, 0x1.000000p-2f, +  0x1.306fe0p-2f, 0x1.6a09e6p-2f, 0x1.ae89f9p-2f, 0x1.000000p-1f, +  0x1.306fe0p-1f, 0x1.6a09e6p-1f, 0x1.ae89f9p-1f, 0x1.000000p-0f +}; +*/ + +// if (i < 2) voltable[i] = 0; +// else       voltable[i] = round((0x7fff / 3.0) * pow(2.0, (i - 31)/4.0)); + +static const int16_t voltable[32] = { +      0,     0,    72,    85, +    101,   121,   144,   171, +    203,   241,   287,   341, +    406,   483,   574,   683, +    812,   965,  1148,  1365, +   1624,  1931,  2296,  2731, +   3247,  3862,  4592,  5461, +   6494,  7723,  9185, 10922 +}; + +#define SINCTABLEBIT 7 +#define SINCTABLELEN (1<<SINCTABLEBIT) + +// GNU Octave +// Fc = 7987200 +// Ff = Fc/144 +// Fs = Fc/32 +// Fe = 20000 +// O = (((Ff/2)-Fe)*2)/(Fs/2) +// B = 128 * O / 2 +// FILTER=sinc(linspace(-127.5,127.5,256)*2/9/2).*rotdim(kaiser(256,B)) +// FILTERI=round(FILTER(1:128).*32768) +static const int16_t sinctable[SINCTABLELEN] = { +      1,     0,    -1,    -2,    -3,    -5,    -6,    -6, +     -6,    -5,    -2,     2,     7,    11,    16,    19, +     20,    18,    13,     5,    -5,   -17,   -29,   -38, +    -44,   -45,   -40,   -29,   -11,    12,    36,    60, +     79,    90,    91,    80,    56,    21,   -22,   -68, +   -112,  -146,  -166,  -166,  -144,  -100,   -37,    39, +    119,   193,   251,   282,   280,   241,   166,    61, +    -64,  -195,  -315,  -406,  -455,  -450,  -385,  -264, +    -96,   101,   306,   491,   632,   705,   694,   593, +    405,   147,  -154,  -464,  -744,  -954, -1062, -1043, +   -889,  -607,  -220,   230,   692,  1108,  1421,  1580, +   1552,  1322,   902,   328,  -343, -1032, -1655, -2125, +  -2369, -2333, -1994, -1365,  -498,   523,  1585,  2557, +   3306,  3714,  3690,  3185,  2206,   815,  -868, -2673, +  -4391, -5798, -6670, -6809, -6067, -4359, -1681,  1886, +   6178, 10957, 15928, 20765, 25133, 28724, 31275, 32600, +}; + +void opna_ssg_reset(struct opna_ssg *ssg) { +  for (int i = 0; i < 3; i++) { +    ssg->ch[i].tone_counter = 0; +    ssg->ch[i].out = false; +  } +  for (int i = 0; i < 0x10; i++) { +    ssg->regs[i] = 0; +  } +  ssg->noise_counter = 0; +  ssg->lfsr = 0; +  ssg->env_counter = 0; +  ssg->env_level = 0; +  ssg->env_att = false; +  ssg->env_alt = false; +  ssg->env_hld = false; +  ssg->env_holding = false; +} + +void opna_ssg_resampler_reset(struct opna_ssg_resampler *resampler) { +  for (int i = 0; i < SINCTABLELEN; i++) { +    resampler->buf[i] = 0; +  } +  resampler->index = 0; +} + +void opna_ssg_writereg(struct opna_ssg *ssg, unsigned reg, unsigned val) { +  if (reg > 0xfu) return; +  val &= 0xff; +  ssg->regs[reg] = val; + +  if (reg == 0xd) { +    ssg->env_att = ssg->regs[0xd] & 0x4; +    if (ssg->regs[0xd] & 0x8) { +      ssg->env_alt = ssg->regs[0xd] & 0x2; +      ssg->env_hld = ssg->regs[0xd] & 0x1; +    } else { +      ssg->env_alt = ssg->env_att; +      ssg->env_hld = true; +    } +    ssg->env_holding = false; +    ssg->env_level = 0; +    ssg->env_counter = 0; +  } +} + +static int opna_ssg_tone_period(const struct opna_ssg *ssg, int chan) { +  return ssg->regs[0+chan*2] | ((ssg->regs[1+chan*2] & 0xf) << 8); +} + +static bool opna_ssg_chan_env(const struct opna_ssg *ssg, int chan) { +  return ssg->regs[0x8+chan] & 0x10; +} +static int opna_ssg_tone_volume(const struct opna_ssg *ssg, int chan) { +  return ssg->regs[0x8+chan] & 0xf; +} + +static bool opna_ssg_tone_out(const struct opna_ssg *ssg, int chan) { +  unsigned reg = ssg->regs[0x7] >> chan; +  return (ssg->ch[chan].out || (reg & 0x1)) && ((ssg->lfsr & 1) || (reg & 0x8)); +} + +static bool opna_ssg_tone_silent(const struct opna_ssg *ssg, int chan) { +  unsigned reg = ssg->regs[0x7] >> chan; +  return (reg & 0x1) && (reg & 0x8); +} + +static int opna_ssg_noise_period(const struct opna_ssg *ssg) { +  return ssg->regs[0x6] & 0x1f; +} + +static int opna_ssg_env_period(const struct opna_ssg *ssg) { +  return (ssg->regs[0xc] << 8) | ssg->regs[0xb]; +} + +static int opna_ssg_env_level(const struct opna_ssg *ssg) { +  return ssg->env_att ? ssg->env_level : 31-ssg->env_level; +} + +int opna_ssg_channel_level(const struct opna_ssg *ssg, int ch) { +  return opna_ssg_chan_env(ssg, ch) +       ? opna_ssg_env_level(ssg) +       : (opna_ssg_tone_volume(ssg, ch) << 1) + 1; +} + +void opna_ssg_generate_raw(struct opna_ssg *ssg, int16_t *buf, int samples) { +  for (int i = 0; i < samples; i++) { +    if (((++ssg->noise_counter) >> 1) >= opna_ssg_noise_period(ssg)) { +      ssg->noise_counter = 0; +      ssg->lfsr |= (!((ssg->lfsr & 1) ^ ((ssg->lfsr >> 3) & 1))) << 17; +      ssg->lfsr >>= 1; +    } +    if (!ssg->env_holding) { +      if (++ssg->env_counter >= opna_ssg_env_period(ssg)) { +        ssg->env_counter = 0; +        ssg->env_level++; +        if (ssg->env_level == 0x20) { +          ssg->env_level = 0; +          if (ssg->env_alt) { +            ssg->env_att = !ssg->env_att; +          } +          if (ssg->env_hld) { +            ssg->env_level = 0x1f; +            ssg->env_holding = true; +          } +        } +      } +    } + +    int16_t out = 0; +    for (int ch = 0; ch < 3; ch++) { +      if (++ssg->ch[ch].tone_counter >= opna_ssg_tone_period(ssg, ch)) { +        ssg->ch[ch].tone_counter = 0; +        ssg->ch[ch].out = !ssg->ch[ch].out; +      } +      /* +      if (opna_ssg_tone_out(ssg, ch)) { +        int level = opna_ssg_chan_env(ssg, ch) +          ? opna_ssg_env_level(ssg) +          : (opna_ssg_tone_volume(ssg, ch) << 1) + 1; +        out += voltable[level]; +      } +      */ +      if (!opna_ssg_tone_silent(ssg, ch)) { +        int level = opna_ssg_channel_level(ssg, ch); +        out += opna_ssg_tone_out(ssg, ch) ? voltable[level] : -voltable[level]; +      } +       +    } +    buf[i] = out / 4; +  } +} + +#define BUFINDEX(n) ((((resampler->index)>>1)+n)&(SINCTABLELEN-1)) + +void opna_ssg_mix_55466( +  struct opna_ssg *ssg, struct opna_ssg_resampler *resampler, +  int16_t *buf, int samples) { +  for (int i = 0; i < samples; i++) { +    { +      int ssg_samples = ((resampler->index + 9)>>1) - ((resampler->index)>>1); +      int16_t ssgbuf[5]; +      opna_ssg_generate_raw(ssg, ssgbuf, ssg_samples); +      for (int j = 0; j < ssg_samples; j++) { +        resampler->buf[BUFINDEX(j)] = ssgbuf[j]; +      } +      resampler->index += 9; +    } +    int32_t sample = 0; +    for (int j = 0; j < SINCTABLELEN; j++) { +      unsigned sincindex = j*2; +      if (!(resampler->index&1)) sincindex++; +      bool sincsign = sincindex & (1<<(SINCTABLEBIT)); +      unsigned sincmask = ((1<<(SINCTABLEBIT))-1); +      sincindex = (sincindex & sincmask) ^ (sincsign ? sincmask : 0); +      sample += (resampler->buf[BUFINDEX(j)] * sinctable[sincindex])>>2; +    } + +    sample >>= 16; +    sample *= 13000; +    sample >>= 14; + +    int32_t lo = buf[i*2+0]; +    int32_t ro = buf[i*2+1]; +    lo += sample; +    ro += sample; +    if (lo < INT16_MIN) lo = INT16_MIN; +    if (lo > INT16_MAX) lo = INT16_MAX; +    if (ro < INT16_MIN) ro = INT16_MIN; +    if (ro > INT16_MAX) ro = INT16_MAX; +    buf[i*2+0] = lo; +    buf[i*2+1] = ro; +  } +} +#undef BUFINDEX diff --git a/libopna/opnassg.h b/libopna/opnassg.h new file mode 100644 index 0000000..5928c49 --- /dev/null +++ b/libopna/opnassg.h @@ -0,0 +1,60 @@ +#ifndef LIBOPNA_OPNASSG_H_INCLUDED +#define LIBOPNA_OPNASSG_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct opna_ssg_ch { +  uint16_t tone_counter; +  bool out; +}; + +struct opna_ssg { +  uint8_t regs[0x10]; +  struct opna_ssg_ch ch[3]; +  uint8_t noise_counter; +  uint32_t lfsr; +  uint16_t env_counter; +  uint8_t env_level; +  bool env_att; +  bool env_alt; +  bool env_hld; +  bool env_holding; +}; + +struct opna_ssg_resampler { +  int16_t buf[(1<<7)]; +  unsigned index; +}; + +void opna_ssg_reset(struct opna_ssg *ssg); +void opna_ssg_resampler_reset(struct opna_ssg_resampler *resampler); +// generate raw data +// Monoral +// Output level: [-32766, 32766] +// Samplerate: clock / 8 +// (on opna: masterclock / 32 +// 7987200 / 32 = 249600) +void opna_ssg_generate_raw(struct opna_ssg *ssg, int16_t *buf, int samples); + +// mix samplerate converted data for mixing with OPNA output +// call to buffer written with OPNA output +// samplerate: 7987200/144 Hz +//            (55466.66..) Hz +void opna_ssg_mix_55466( +  struct opna_ssg *ssg, struct opna_ssg_resampler *resampler, +  int16_t *buf, int samples); +void opna_ssg_writereg(struct opna_ssg *ssg, unsigned reg, unsigned val); + +// channel level (0 - 31) +int opna_ssg_channel_level(const struct opna_ssg *ssg, int ch); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNASSG_H_INCLUDED diff --git a/libopna/opnatables.h b/libopna/opnatables.h new file mode 100644 index 0000000..30140bf --- /dev/null +++ b/libopna/opnatables.h @@ -0,0 +1,242 @@ +#define LOGSINTABLEBIT 8 +#define LOGSINTABLELEN (1<<LOGSINTABLEBIT) +// round(-256.0*log2((sin((2*i+1)*PI/1024.0)))) +static const uint16_t logsintable[LOGSINTABLELEN] = { +  2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137, +  1091, 1050, 1013,  979,  949,  920,  894,  869, +   846,  825,  804,  785,  767,  749,  732,  717, +   701,  687,  672,  659,  646,  633,  621,  609, +   598,  587,  576,  566,  556,  546,  536,  527, +   518,  509,  501,  492,  484,  476,  468,  461, +   453,  446,  439,  432,  425,  418,  411,  405, +   399,  392,  386,  380,  375,  369,  363,  358, +   352,  347,  341,  336,  331,  326,  321,  316, +   311,  307,  302,  297,  293,  289,  284,  280, +   276,  271,  267,  263,  259,  255,  251,  248, +   244,  240,  236,  233,  229,  226,  222,  219, +   215,  212,  209,  205,  202,  199,  196,  193, +   190,  187,  184,  181,  178,  175,  172,  169, +   167,  164,  161,  159,  156,  153,  151,  148, +   146,  143,  141,  138,  136,  134,  131,  129, +   127,  125,  122,  120,  118,  116,  114,  112, +   110,  108,  106,  104,  102,  100,   98,   96, +    94,   92,   91,   89,   87,   85,   83,   82, +    80,   78,   77,   75,   74,   72,   70,   69, +    67,   66,   64,   63,   62,   60,   59,   57, +    56,   55,   53,   52,   51,   49,   48,   47, +    46,   45,   43,   42,   41,   40,   39,   38, +    37,   36,   35,   34,   33,   32,   31,   30, +    29,   28,   27,   26,   25,   24,   23,   23, +    22,   21,   20,   20,   19,   18,   17,   17, +    16,   15,   15,   14,   13,   13,   12,   12, +    11,   10,   10,    9,    9,    8,    8,    7, +     7,    7,    6,    6,    5,    5,    5,    4, +     4,    4,    3,    3,    3,    2,    2,    2, +     2,    1,    1,    1,    1,    1,    1,    1, +     0,    0,    0,    0,    0,    0,    0,    0, +}; +#define LOGSINTABLEHIRESBIT 10 +#define LOGSINTABLEHIRESLEN (1<<LOGSINTABLEHIRESBIT) +static const uint16_t logsintable_hires[LOGSINTABLEHIRESLEN] = { +  2649, 2243, 2055, 1931, 1838, 1764, 1702, 1649, +  1603, 1562, 1525, 1491, 1460, 1432, 1406, 1381, +  1358, 1336, 1316, 1296, 1278, 1260, 1243, 1227, +  1212, 1197, 1183, 1169, 1156, 1143, 1131, 1119, +  1108, 1096, 1086, 1075, 1065, 1055, 1045, 1036, +  1026, 1017, 1009, 1000,  992,  984,  976,  968, +   960,  952,  945,  938,  931,  924,  917,  910, +   904,  897,  891,  885,  879,  872,  867,  861, +   855,  849,  844,  838,  833,  827,  822,  817, +   812,  807,  802,  797,  792,  787,  783,  778, +   773,  769,  764,  760,  756,  751,  747,  743, +   739,  735,  730,  726,  722,  718,  715,  711, +   707,  703,  699,  696,  692,  688,  685,  681, +   678,  674,  671,  667,  664,  661,  657,  654, +   651,  647,  644,  641,  638,  635,  632,  629, +   626,  623,  620,  617,  614,  611,  608,  605, +   602,  599,  597,  594,  591,  588,  586,  583, +   580,  578,  575,  572,  570,  567,  565,  562, +   559,  557,  554,  552,  550,  547,  545,  542, +   540,  538,  535,  533,  531,  528,  526,  524, +   521,  519,  517,  515,  512,  510,  508,  506, +   504,  502,  500,  497,  495,  493,  491,  489, +   487,  485,  483,  481,  479,  477,  475,  473, +   471,  469,  467,  465,  463,  462,  460,  458, +   456,  454,  452,  450,  449,  447,  445,  443, +   441,  440,  438,  436,  434,  433,  431,  429, +   427,  426,  424,  422,  421,  419,  417,  416, +   414,  412,  411,  409,  407,  406,  404,  403, +   401,  399,  398,  396,  395,  393,  392,  390, +   389,  387,  386,  384,  383,  381,  380,  378, +   377,  375,  374,  372,  371,  369,  368,  367, +   365,  364,  362,  361,  360,  358,  357,  355, +   354,  353,  351,  350,  349,  347,  346,  345, +   343,  342,  341,  339,  338,  337,  336,  334, +   333,  332,  330,  329,  328,  327,  325,  324, +   323,  322,  320,  319,  318,  317,  316,  314, +   313,  312,  311,  310,  308,  307,  306,  305, +   304,  303,  301,  300,  299,  298,  297,  296, +   295,  294,  292,  291,  290,  289,  288,  287, +   286,  285,  284,  283,  281,  280,  279,  278, +   277,  276,  275,  274,  273,  272,  271,  270, +   269,  268,  267,  266,  265,  264,  263,  262, +   261,  260,  259,  258,  257,  256,  255,  254, +   253,  252,  251,  250,  249,  248,  247,  246, +   245,  244,  243,  242,  242,  241,  240,  239, +   238,  237,  236,  235,  234,  233,  232,  231, +   231,  230,  229,  228,  227,  226,  225,  224, +   224,  223,  222,  221,  220,  219,  218,  218, +   217,  216,  215,  214,  213,  212,  212,  211, +   210,  209,  208,  208,  207,  206,  205,  204, +   203,  203,  202,  201,  200,  199,  199,  198, +   197,  196,  196,  195,  194,  193,  192,  192, +   191,  190,  189,  189,  188,  187,  186,  186, +   185,  184,  183,  183,  182,  181,  180,  180, +   179,  178,  178,  177,  176,  175,  175,  174, +   173,  173,  172,  171,  171,  170,  169,  168, +   168,  167,  166,  166,  165,  164,  164,  163, +   162,  162,  161,  160,  160,  159,  158,  158, +   157,  156,  156,  155,  154,  154,  153,  152, +   152,  151,  151,  150,  149,  149,  148,  147, +   147,  146,  145,  145,  144,  144,  143,  142, +   142,  141,  141,  140,  139,  139,  138,  138, +   137,  136,  136,  135,  135,  134,  133,  133, +   132,  132,  131,  131,  130,  129,  129,  128, +   128,  127,  127,  126,  125,  125,  124,  124, +   123,  123,  122,  122,  121,  121,  120,  119, +   119,  118,  118,  117,  117,  116,  116,  115, +   115,  114,  114,  113,  113,  112,  112,  111, +   110,  110,  109,  109,  108,  108,  107,  107, +   106,  106,  105,  105,  104,  104,  103,  103, +   102,  102,  101,  101,  101,  100,  100,   99, +    99,   98,   98,   97,   97,   96,   96,   95, +    95,   94,   94,   93,   93,   93,   92,   92, +    91,   91,   90,   90,   89,   89,   88,   88, +    88,   87,   87,   86,   86,   85,   85,   85, +    84,   84,   83,   83,   82,   82,   82,   81, +    81,   80,   80,   79,   79,   79,   78,   78, +    77,   77,   77,   76,   76,   75,   75,   75, +    74,   74,   73,   73,   73,   72,   72,   71, +    71,   71,   70,   70,   69,   69,   69,   68, +    68,   68,   67,   67,   66,   66,   66,   65, +    65,   65,   64,   64,   64,   63,   63,   62, +    62,   62,   61,   61,   61,   60,   60,   60, +    59,   59,   59,   58,   58,   58,   57,   57, +    57,   56,   56,   55,   55,   55,   54,   54, +    54,   54,   53,   53,   53,   52,   52,   52, +    51,   51,   51,   50,   50,   50,   49,   49, +    49,   48,   48,   48,   47,   47,   47,   47, +    46,   46,   46,   45,   45,   45,   44,   44, +    44,   44,   43,   43,   43,   42,   42,   42, +    42,   41,   41,   41,   40,   40,   40,   40, +    39,   39,   39,   38,   38,   38,   38,   37, +    37,   37,   37,   36,   36,   36,   36,   35, +    35,   35,   35,   34,   34,   34,   34,   33, +    33,   33,   33,   32,   32,   32,   32,   31, +    31,   31,   31,   30,   30,   30,   30,   29, +    29,   29,   29,   28,   28,   28,   28,   28, +    27,   27,   27,   27,   26,   26,   26,   26, +    26,   25,   25,   25,   25,   24,   24,   24, +    24,   24,   23,   23,   23,   23,   23,   22, +    22,   22,   22,   22,   21,   21,   21,   21, +    21,   20,   20,   20,   20,   20,   19,   19, +    19,   19,   19,   18,   18,   18,   18,   18, +    18,   17,   17,   17,   17,   17,   17,   16, +    16,   16,   16,   16,   15,   15,   15,   15, +    15,   15,   15,   14,   14,   14,   14,   14, +    14,   13,   13,   13,   13,   13,   13,   12, +    12,   12,   12,   12,   12,   12,   11,   11, +    11,   11,   11,   11,   11,   10,   10,   10, +    10,   10,   10,   10,   10,    9,    9,    9, +     9,    9,    9,    9,    9,    8,    8,    8, +     8,    8,    8,    8,    8,    7,    7,    7, +     7,    7,    7,    7,    7,    7,    6,    6, +     6,    6,    6,    6,    6,    6,    6,    6, +     5,    5,    5,    5,    5,    5,    5,    5, +     5,    5,    4,    4,    4,    4,    4,    4, +     4,    4,    4,    4,    4,    4,    3,    3, +     3,    3,    3,    3,    3,    3,    3,    3, +     3,    3,    3,    3,    2,    2,    2,    2, +     2,    2,    2,    2,    2,    2,    2,    2, +     2,    2,    2,    2,    2,    1,    1,    1, +     1,    1,    1,    1,    1,    1,    1,    1, +     1,    1,    1,    1,    1,    1,    1,    1, +     1,    1,    1,    1,    1,    1,    0,    0, +     0,    0,    0,    0,    0,    0,    0,    0, +     0,    0,    0,    0,    0,    0,    0,    0, +     0,    0,    0,    0,    0,    0,    0,    0, +     0,    0,    0,    0,    0,    0,    0,    0, +}; + +#define EXPTABLEBIT 8 +#define EXPTABLELEN (1<<EXPTABLEBIT) +// round((1<<11) / pow(2.0, (i+1.0)/256.0)) +static const uint16_t exptable[EXPTABLELEN] = { +  2042, 2037, 2031, 2026, 2020, 2015, 2010, 2004, +  1999, 1993, 1988, 1983, 1977, 1972, 1966, 1961, +  1956, 1951, 1945, 1940, 1935, 1930, 1924, 1919, +  1914, 1909, 1904, 1898, 1893, 1888, 1883, 1878, +  1873, 1868, 1863, 1858, 1853, 1848, 1843, 1838, +  1833, 1828, 1823, 1818, 1813, 1808, 1803, 1798, +  1794, 1789, 1784, 1779, 1774, 1769, 1765, 1760, +  1755, 1750, 1746, 1741, 1736, 1732, 1727, 1722, +  1717, 1713, 1708, 1704, 1699, 1694, 1690, 1685, +  1681, 1676, 1672, 1667, 1663, 1658, 1654, 1649, +  1645, 1640, 1636, 1631, 1627, 1623, 1618, 1614, +  1609, 1605, 1601, 1596, 1592, 1588, 1584, 1579, +  1575, 1571, 1566, 1562, 1558, 1554, 1550, 1545, +  1541, 1537, 1533, 1529, 1525, 1520, 1516, 1512, +  1508, 1504, 1500, 1496, 1492, 1488, 1484, 1480, +  1476, 1472, 1468, 1464, 1460, 1456, 1452, 1448, +  1444, 1440, 1436, 1433, 1429, 1425, 1421, 1417, +  1413, 1409, 1406, 1402, 1398, 1394, 1391, 1387, +  1383, 1379, 1376, 1372, 1368, 1364, 1361, 1357, +  1353, 1350, 1346, 1342, 1339, 1335, 1332, 1328, +  1324, 1321, 1317, 1314, 1310, 1307, 1303, 1300, +  1296, 1292, 1289, 1286, 1282, 1279, 1275, 1272, +  1268, 1265, 1261, 1258, 1255, 1251, 1248, 1244, +  1241, 1238, 1234, 1231, 1228, 1224, 1221, 1218, +  1214, 1211, 1208, 1205, 1201, 1198, 1195, 1192, +  1188, 1185, 1182, 1179, 1176, 1172, 1169, 1166, +  1163, 1160, 1157, 1154, 1150, 1147, 1144, 1141, +  1138, 1135, 1132, 1129, 1126, 1123, 1120, 1117, +  1114, 1111, 1108, 1105, 1102, 1099, 1096, 1093, +  1090, 1087, 1084, 1081, 1078, 1075, 1072, 1069, +  1066, 1064, 1061, 1058, 1055, 1052, 1049, 1046, +  1044, 1041, 1038, 1035, 1032, 1030, 1027, 1024, +}; + +static const uint8_t rateinctable[4*2][8] = { +// rates 0 - 47 +  {0, 1, 0, 1, 0, 1, 0, 1}, +  {0, 1, 0, 1, 1, 1, 0, 1}, +  {0, 1, 1, 1, 0, 1, 1, 1}, +  {0, 1, 1, 1, 1, 1, 1, 1}, + +// rates 48 -  +  {1, 1, 1, 1, 1, 1, 1, 1}, +  {1, 1, 1, 2, 1, 1, 1, 2}, +  {1, 2, 1, 2, 1, 2, 1, 2}, +  {1, 2, 2, 2, 1, 2, 2, 2}, +}; + +// datasheet 0.053Hz = 1 +// [FD][keycode] +static const uint8_t dettable[4][32]={ +  { +    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +  }, +  { +    0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, +    2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, +  }, +  { +    1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, +    5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16, +  }, +  { +    2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, +    8, 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22, +  }, +}; diff --git a/libopna/opnatimer.c b/libopna/opnatimer.c new file mode 100644 index 0000000..195e426 --- /dev/null +++ b/libopna/opnatimer.c @@ -0,0 +1,79 @@ +#include "opnatimer.h" +#include "opna.h" + +enum { +  TIMERB_SHIFT = 4, +  TIMERB_BITS = 8 + TIMERB_SHIFT, +}; + +void opna_timer_reset(struct opna_timer *timer, struct opna *opna) { +  timer->opna = opna; +  timer->status = 0; +  timer->interrupt_cb = 0; +  timer->interrupt_userptr = 0; +  timer->mix_cb = 0; +  timer->mix_userptr = 0; +  timer->timerb = 0; +  timer->timerb_load = false; +  timer->timerb_enable = false; +  timer->timerb_cnt = 0; +} + +uint8_t opna_timer_status(const struct opna_timer *timer) { +  return timer->status; +} + +void opna_timer_set_int_callback(struct opna_timer *timer, opna_timer_int_cb_t func, void *userptr) { +  timer->interrupt_cb = func; +  timer->interrupt_userptr = userptr; +} + +void opna_timer_set_mix_callback(struct opna_timer *timer, opna_timer_mix_cb_t func, void *userptr) { +  timer->mix_cb = func; +  timer->mix_userptr = userptr; +} + +void opna_timer_writereg(struct opna_timer *timer, unsigned reg, unsigned val) { +  val &= 0xff; +  opna_writereg(timer->opna, reg, val); +  switch (reg) { +  case 0x26: +    timer->timerb = val; +    timer->timerb_cnt = timer->timerb << TIMERB_SHIFT; +    break; +  case 0x27: +    timer->timerb_load = val & (1<<1); +    timer->timerb_enable = val & (1<<3); +    if (val & (1<<5)) { +      //timer->timerb_cnt = timer->timerb << TIMERB_SHIFT; +      timer->status &= ~(1<<1); +    } +  } +} + +void opna_timer_mix(struct opna_timer *timer, int16_t *buf, unsigned samples) { +  do { +    unsigned generate_samples = samples; +    if (timer->timerb_enable) { +      unsigned timerb_samples = (1<<TIMERB_BITS) - timer->timerb_cnt; +      if (timerb_samples < generate_samples) { +        generate_samples = timerb_samples; +      } +    } +    opna_mix(timer->opna, buf, generate_samples); +    if (timer->mix_cb) { +      timer->mix_cb(timer->mix_userptr, buf, generate_samples); +    } +    buf += generate_samples*2; +    samples -= generate_samples; +    if (timer->timerb_load) { +      timer->timerb_cnt = (timer->timerb_cnt + generate_samples) & ((1<<TIMERB_BITS)-1); +      if (!timer->timerb_cnt && timer->timerb_enable) { +        if (!(timer->status & (1<<1))) { +          timer->status |= (1<<1); +          timer->interrupt_cb(timer->interrupt_userptr); +        } +      } +    } +  } while (samples); +} diff --git a/libopna/opnatimer.h b/libopna/opnatimer.h new file mode 100644 index 0000000..f3094ba --- /dev/null +++ b/libopna/opnatimer.h @@ -0,0 +1,42 @@ +#ifndef LIBOPNA_OPNA_TIMER_H_INCLUDED +#define LIBOPNA_OPNA_TIMER_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*opna_timer_int_cb_t)(void *ptr); +typedef void (*opna_timer_mix_cb_t)(void *ptr, int16_t *buf, unsigned samples); + +struct opna; + +struct opna_timer { +  struct opna *opna; +  uint8_t status; +  opna_timer_int_cb_t interrupt_cb; +  void *interrupt_userptr; +  opna_timer_mix_cb_t mix_cb; +  void *mix_userptr; +  uint8_t timerb; +  bool timerb_load; +  bool timerb_enable; +  uint16_t timerb_cnt; +}; + +void opna_timer_reset(struct opna_timer *timer, struct opna *opna); +uint8_t opna_timer_status(const struct opna_timer *timer); +void opna_timer_set_int_callback(struct opna_timer *timer, +                                 opna_timer_int_cb_t func, void *userptr); +void opna_timer_set_mix_callback(struct opna_timer *timer, +                                 opna_timer_mix_cb_t func, void *userptr); +void opna_timer_writereg(struct opna_timer *timer, unsigned reg, unsigned val); +void opna_timer_mix(struct opna_timer *timer, int16_t *buf, unsigned samples); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNA_TIMER_H_INCLUDED diff --git a/libopna/s98gen.c b/libopna/s98gen.c new file mode 100644 index 0000000..cc2b529 --- /dev/null +++ b/libopna/s98gen.c @@ -0,0 +1,141 @@ +#include "s98gen.h" + +static uint32_t read32le(const void *dataptr, size_t offset) { +  const uint8_t *data = (const uint8_t *)dataptr; +  return ((uint32_t)data[offset+0]) | (((uint32_t)data[offset+1])<<8) | +         (((uint32_t)data[offset+2])<<16) | (((uint32_t)data[offset+3])<<24); +} + +bool s98gen_init(struct s98gen *s98, void *s98dataptr, size_t s98data_size) { +  uint8_t *s98data = (uint8_t *)s98dataptr; +  if (s98data_size < 0x20) { +    // size of s98 header +    return false; +  } +  if (s98data[0] != 'S' || s98data[1] != '9' || +      s98data[2] != '8') { +    // file magic / version check +    return false; +  } +  if ((s98data[3] != '1') && (s98data[3] != '3')) { +    return false; +  } +  if (s98data[0xc] != 0) { +    // COMPRESSING flag not zero +    return false; +  } +  uint32_t clock = 7987200; +  if ((s98data[3] == '3') && s98data[0x1c]) { +    if (s98data_size < 0x30) { +      // size of s98 header + device info +      return false; +    } +    uint32_t devicetype = read32le(s98data, 0x20); +    if (devicetype != 0x04 && devicetype != 0x02) { +      // OPN / OPNA only +      return false; +    } +    clock = read32le(s98data, 0x24); +    // convert OPN clock to OPNA clock +    if (devicetype == 2) clock *= 2; +  } +  s98->s98data = s98data; +  s98->s98data_size = s98data_size; +  s98->current_offset = read32le(s98data, 0x14); +  s98->opnaclock = clock; +  s98->samples_to_generate = 0; +  s98->samples_to_generate_frac = 0; +  s98->timer_numerator = read32le(s98data, 0x04); +  s98->timer_denominator = read32le(s98data, 0x08); +  if (!s98->timer_numerator) s98->timer_numerator = 10; +  if (!s98->timer_denominator) s98->timer_denominator = 1000; +  opna_reset(&s98->opna); +  return true; +} + +// returns 0 when failed +static size_t s98gen_getvv(struct s98gen *s98) { +  unsigned shift = 0; +  size_t value = 0; +  do { +    if (s98->s98data_size < (s98->current_offset+1)) return 0; +    value |= (s98->s98data[s98->current_offset] & 0x7f) << ((shift++)*7); +  } while (s98->s98data[s98->current_offset++] & 0x80); +  return value + 2; +} + +static void s98gen_set_samples_to_generate(struct s98gen *s98, size_t ticks) { +  uint64_t s = s98->opnaclock * s98->timer_numerator * ticks; +  s <<= 16; +  s /= 144 * s98->timer_denominator; +  s += s98->samples_to_generate_frac; +  s98->samples_to_generate = s >> 16; +  s98->samples_to_generate_frac = s & ((((size_t)1)<<16)-1); +} + +static bool s98gen_parse_s98(struct s98gen *s98) { +  for (;;) { +    switch (s98->s98data[s98->current_offset]) { +    case 0x00: +      if (s98->s98data_size < (s98->current_offset + 3)) return false; +      opna_writereg(&s98->opna, +        s98->s98data[s98->current_offset+1], +        s98->s98data[s98->current_offset+2]); +      s98->current_offset += 3; +      break; +    case 0x01: +      if (s98->s98data_size < (s98->current_offset + 3)) return false; +      opna_writereg(&s98->opna, +        s98->s98data[s98->current_offset+1] | 0x100, +        s98->s98data[s98->current_offset+2]); +      s98->current_offset += 3; +      break; +    case 0xfe: +      if (s98->s98data_size < (s98->current_offset+1)) return false; +      s98->current_offset++; +      { +        size_t vv = s98gen_getvv(s98); +        if (!vv) return false; +        s98gen_set_samples_to_generate(s98, vv); +      } +      return true; +    case 0xff: +      if (s98->s98data_size < (s98->current_offset + 1)) return false; +      s98->current_offset++; +      s98gen_set_samples_to_generate(s98, 1); +      return true; +    default: +      return false; +    } +  } +} + +bool s98gen_generate(struct s98gen *s98, int16_t *buf, size_t samples) { +  for (size_t i = 0; i < samples; i++) { +    buf[i*2+0] = 0; +    buf[i*2+1] = 0; +  } +  if (samples <= s98->samples_to_generate) { +    opna_mix(&s98->opna, buf, samples); +    s98->samples_to_generate -= samples; +  } else { +    opna_mix(&s98->opna, buf, s98->samples_to_generate); +    buf += s98->samples_to_generate * 2; +    samples -= s98->samples_to_generate; +    s98->samples_to_generate = 0; +    while (samples) { +      if (!s98gen_parse_s98(s98)) return false; +      if (s98->samples_to_generate >= samples) { +        opna_mix(&s98->opna, buf, samples); +        s98->samples_to_generate -= samples; +        samples = 0; +      } else { +        opna_mix(&s98->opna, buf, s98->samples_to_generate); +        buf += s98->samples_to_generate*2; +        samples -= s98->samples_to_generate; +        s98->samples_to_generate = 0; +      } +    } +  } +  return true; +} diff --git a/libopna/s98gen.h b/libopna/s98gen.h new file mode 100644 index 0000000..8fa458d --- /dev/null +++ b/libopna/s98gen.h @@ -0,0 +1,34 @@ +#ifndef MYON_S98GEN_H_INCLUDED +#define MYON_S98GEN_H_INCLUDED + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include "opna.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct s98gen { +  struct opna opna; +  uint8_t *s98data; +  size_t s98data_size; +  size_t current_offset; +  uint32_t opnaclock; +  uint32_t samples_to_generate; +  uint16_t samples_to_generate_frac; +  uint32_t timer_numerator; +  uint32_t timer_denominator; +}; + +// returns true if initialization succeeded +// returns false without touching *s98 when initialization failed (invalid data) +bool s98gen_init(struct s98gen *s98, void *s98data, size_t s98data_size); +bool s98gen_generate(struct s98gen *s98, int16_t *buf, size_t samples); + +#ifdef __cplusplus +} +#endif + +#endif // MYON_S98GEN_H_INCLUDED diff --git a/screenshot.png b/screenshot.png Binary files differnew file mode 100644 index 0000000..d943ed1 --- /dev/null +++ b/screenshot.png  | 
