aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--LICENSE25
-rw-r--r--README.md29
-rw-r--r--curses/.gitignore12
-rw-r--r--curses/Makefile.am18
-rw-r--r--curses/configure.ac9
-rw-r--r--curses/main.c541
-rw-r--r--fmdriver/fmdriver.h30
-rw-r--r--fmdriver/fmdriver_common.h19
-rw-r--r--fmdriver/fmdriver_fmp.c3257
-rw-r--r--fmdriver/fmdriver_fmp.h529
-rw-r--r--fmdriver/ppz8.c293
-rw-r--r--fmdriver/ppz8.h76
-rw-r--r--libopna/opna.c24
-rw-r--r--libopna/opna.h30
-rw-r--r--libopna/opnaadpcm.c208
-rw-r--r--libopna/opnaadpcm.h43
-rw-r--r--libopna/opnadrum.c141
-rw-r--r--libopna/opnadrum.h57
-rw-r--r--libopna/opnafm.c511
-rw-r--r--libopna/opnafm.h111
-rw-r--r--libopna/opnassg.c233
-rw-r--r--libopna/opnassg.h60
-rw-r--r--libopna/opnatables.h242
-rw-r--r--libopna/opnatimer.c79
-rw-r--r--libopna/opnatimer.h42
-rw-r--r--libopna/s98gen.c141
-rw-r--r--libopna/s98gen.h34
-rw-r--r--screenshot.pngbin0 -> 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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ef7f957
--- /dev/null
+++ b/LICENSE
@@ -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 で作った仮のもの
+![screenshot](/screenshot.png?raw=true)
+* 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
new file mode 100644
index 0000000..d943ed1
--- /dev/null
+++ b/screenshot.png
Binary files differ