From 6fd10cdacb5cbe47a4fc339c20a733d4a9a384a1 Mon Sep 17 00:00:00 2001 From: Takamichi Horikawa Date: Sat, 26 Nov 2016 20:57:57 +0900 Subject: initial --- .gitignore | 3 + LICENSE | 25 + README.md | 29 + curses/.gitignore | 12 + curses/Makefile.am | 18 + curses/configure.ac | 9 + curses/main.c | 541 ++++++++ fmdriver/fmdriver.h | 30 + fmdriver/fmdriver_common.h | 19 + fmdriver/fmdriver_fmp.c | 3257 ++++++++++++++++++++++++++++++++++++++++++++ fmdriver/fmdriver_fmp.h | 529 +++++++ fmdriver/ppz8.c | 293 ++++ fmdriver/ppz8.h | 76 ++ libopna/opna.c | 24 + libopna/opna.h | 30 + libopna/opnaadpcm.c | 208 +++ libopna/opnaadpcm.h | 43 + libopna/opnadrum.c | 141 ++ libopna/opnadrum.h | 57 + libopna/opnafm.c | 511 +++++++ libopna/opnafm.h | 111 ++ libopna/opnassg.c | 233 ++++ libopna/opnassg.h | 60 + libopna/opnatables.h | 242 ++++ libopna/opnatimer.c | 79 ++ libopna/opnatimer.h | 42 + libopna/s98gen.c | 141 ++ libopna/s98gen.h | 34 + screenshot.png | Bin 0 -> 22486 bytes 29 files changed, 6797 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 curses/.gitignore create mode 100644 curses/Makefile.am create mode 100644 curses/configure.ac create mode 100644 curses/main.c create mode 100644 fmdriver/fmdriver.h create mode 100644 fmdriver/fmdriver_common.h create mode 100644 fmdriver/fmdriver_fmp.c create mode 100644 fmdriver/fmdriver_fmp.h create mode 100644 fmdriver/ppz8.c create mode 100644 fmdriver/ppz8.h create mode 100644 libopna/opna.c create mode 100644 libopna/opna.h create mode 100644 libopna/opnaadpcm.c create mode 100644 libopna/opnaadpcm.h create mode 100644 libopna/opnadrum.c create mode 100644 libopna/opnadrum.h create mode 100644 libopna/opnafm.c create mode 100644 libopna/opnafm.h create mode 100644 libopna/opnassg.c create mode 100644 libopna/opnassg.h create mode 100644 libopna/opnatables.h create mode 100644 libopna/opnatimer.c create mode 100644 libopna/opnatimer.h create mode 100644 libopna/s98gen.c create mode 100644 libopna/s98gen.h create mode 100644 screenshot.png 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 +#include +#include +#include "libopna/opna.h" +#include "libopna/opnatimer.h" +#include "fmdriver/fmdriver.h" +#include "fmdriver/fmdriver_fmp.h" +#include +#include +#include +#include +#include + +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 +#include +#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 +#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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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 + +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 +#include + +#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 +#include + +#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<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 +#include + +#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<phase >> 8); + pind_hires += modulation << 1; + minus = pind_hires & (1<<(LOGSINTABLEHIRESBIT+1)); + reverse = pind_hires & (1<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; + 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<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 +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 +#include + +#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<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 +#include + +#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<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_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_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 +#include + +#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 +#include +#include +#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 Binary files /dev/null and b/screenshot.png differ -- cgit v1.2.3