diff options
author | Takamichi Horikawa <takamichiho@gmail.com> | 2016-11-26 20:57:57 +0900 |
---|---|---|
committer | Takamichi Horikawa <takamichiho@gmail.com> | 2016-11-26 20:57:57 +0900 |
commit | 6fd10cdacb5cbe47a4fc339c20a733d4a9a384a1 (patch) | |
tree | c7f479a70c350dca0b73d76078e46db41d9c4133 /fmdriver |
initial
Diffstat (limited to 'fmdriver')
-rw-r--r-- | fmdriver/fmdriver.h | 30 | ||||
-rw-r--r-- | fmdriver/fmdriver_common.h | 19 | ||||
-rw-r--r-- | fmdriver/fmdriver_fmp.c | 3257 | ||||
-rw-r--r-- | fmdriver/fmdriver_fmp.h | 529 | ||||
-rw-r--r-- | fmdriver/ppz8.c | 293 | ||||
-rw-r--r-- | fmdriver/ppz8.h | 76 |
6 files changed, 4204 insertions, 0 deletions
diff --git a/fmdriver/fmdriver.h b/fmdriver/fmdriver.h new file mode 100644 index 0000000..67557cc --- /dev/null +++ b/fmdriver/fmdriver.h @@ -0,0 +1,30 @@ +#ifndef MYON_FMDRIVER_H_INCLUDED +#define MYON_FMDRIVER_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> +#include "ppz8.h" + +struct fmdriver_work { + // set by driver, called by opna + void (*driver_opna_interrupt)(struct fmdriver_work *work); + void (*driver_deinit)(struct fmdriver_work *work); + // driver internal + void *driver; + + + // set by opna, called by driver in the interrupt functions + unsigned (*opna_readreg)(struct fmdriver_work *work, unsigned addr); + void (*opna_writereg)(struct fmdriver_work *work, unsigned addr, unsigned data); + uint8_t (*opna_status)(struct fmdriver_work *work, bool a1); + void *opna; + + const struct ppz8_functbl *ppz8_functbl; + struct ppz8 *ppz8; + + const char *title; + // driver status + // fm3ex part map +}; + +#endif // MYON_FMDRIVER_H_INCLUDED diff --git a/fmdriver/fmdriver_common.h b/fmdriver/fmdriver_common.h new file mode 100644 index 0000000..cd804b8 --- /dev/null +++ b/fmdriver/fmdriver_common.h @@ -0,0 +1,19 @@ +#ifndef MYON_FMDRIVER_COMMON_H_INCLUDED +#define MYON_FMDRIVER_COMMON_H_INCLUDED + +static inline uint16_t read16le(const uint8_t *ptr) { + return (unsigned)ptr[0] | (((unsigned)ptr[1])<<8); +} + +static inline int16_t u8s16(uint8_t v) { + return (v & 0x80) ? ((int16_t)v)-0x100 : v; +} + +static inline int16_t u16s16(uint16_t v) { + return (v & 0x8000) ? ((int32_t)v)-0x10000 : v; +} + +#include <stdio.h> +#define FMDRIVER_DEBUG(...) fprintf(stderr, __VA_ARGS__) + +#endif // MYON_FMDRIVER_COMMON_H_INCLUDED diff --git a/fmdriver/fmdriver_fmp.c b/fmdriver/fmdriver_fmp.c new file mode 100644 index 0000000..76e3798 --- /dev/null +++ b/fmdriver/fmdriver_fmp.c @@ -0,0 +1,3257 @@ +#include "fmdriver_fmp.h"
+#include "fmdriver_common.h"
+
+static uint8_t fmp_rand71(struct driver_fmp *fmp) {
+ // on real PC-98, read from I/O port 0x71 (8253 Timer)
+ uint8_t val = fmp->rand71;
+ fmp->rand71 = (val>>1) | ((((val>>7)^(val>>5)^(val>>4)^(val>>3)^1)&1)<<7);
+ return fmp->rand71;
+}
+
+static uint8_t fmp_part_cmdload(struct driver_fmp *fmp, struct fmp_part *part) {
+ if (part->current_ptr >= fmp->datalen) {
+ //exit(2);
+ part->current_ptr = 0xffff;
+ return 0x74;
+ }
+ return fmp->data[part->current_ptr++];
+}
+static uint8_t fmp_part_cmdload_rhythm(struct driver_fmp *fmp, struct fmp_part *part) {
+ if (part->current_ptr >= fmp->datalen) {
+ part->current_ptr = 0xffff;
+ return 0x93;
+ }
+ return fmp->data[part->current_ptr++];
+}
+
+static uint16_t fmp_part_cmdload16(struct driver_fmp *fmp, struct fmp_part *part) {
+ uint16_t val = fmp_part_cmdload(fmp, part);
+ val |= fmp_part_cmdload(fmp, part) << 8;
+ return val;
+}
+
+enum {
+ OPNA_DTMUL = 0x30,
+ OPNA_TL = 0x40,
+ OPNA_KSAR = 0x50,
+ OPNA_AMDR = 0x60,
+ OPNA_SR = 0x70,
+ OPNA_SLRR = 0x80,
+ OPNA_SSGEG = 0x90,
+ OPNA_FNUM1 = 0xa0,
+ OPNA_BLKFNUM2 = 0xa4,
+ OPNA_FBALG = 0xb0,
+ OPNA_LRAMSPMS = 0xb4,
+};
+
+static void fmp_part_fm_reg_write(struct fmdriver_work *work,
+ struct fmp_part *part,
+ uint8_t addr, uint8_t data) {
+ uint16_t outaddr = part->opna_keyon_out & 0x3;
+ if (part->opna_keyon_out & 0x4) outaddr += 0x100;
+ outaddr += addr;
+ work->opna_writereg(work, outaddr, data);
+}
+
+// 30ec
+static uint16_t fmp_fm_freq(uint8_t note) {
+ // 3106
+ static const uint16_t freqtab[0xc] = {
+ 0x026a,
+ 0x028f,
+ 0x02b6,
+ 0x02df,
+ 0x030b,
+ 0x0339,
+ 0x036a,
+ 0x039e,
+ 0x03d5,
+ 0x0410,
+ 0x044e,
+ 0x048f,
+ };
+ return freqtab[note%0xc] + ((note/0xc)<<(3+8));
+}
+
+static uint8_t fmp_ssg_octave(uint8_t note) {
+ return note/0xc;
+}
+
+static uint16_t fmp_ppz_freq(uint8_t note) {
+ static const uint16_t freqtab[0xc] = {
+ 0x8000,
+ 0x87a6,
+ 0x8fb3,
+ 0x9838,
+ 0xa146,
+ 0xaade,
+ 0xb4ff,
+ 0xbfcc,
+ 0xcb34,
+ 0xd747,
+ 0xe418,
+ 0xf1a5,
+ };
+ return freqtab[note%0xc];
+}
+
+// 311e
+static uint16_t fmp_part_ssg_freq(struct fmp_part *part, uint8_t note) {
+ // 315a
+ static const uint16_t freqtab[0xc] = {
+ 0x0ee8,
+ 0x0e12,
+ 0x0d48,
+ 0x0c89,
+ 0x0bd5,
+ 0x0b2b,
+ 0x0a8a,
+ 0x09f3,
+ 0x0964,
+ 0x08dd,
+ 0x085e,
+ 0x07e6,
+ };
+ uint16_t freq = part->u.ssg.env_f.ppz ? fmp_ppz_freq(note) : freqtab[note%0xc];
+ return part->detune + freq;
+}
+
+// 3172
+static uint16_t fmp_adpcm_freq(uint8_t note) {
+ static const uint16_t freqtab[4][0xc] = {
+ {
+ 0xdb22,
+ 0xdd53,
+ 0xdfa6,
+ 0xe21c,
+ 0xe4b7,
+ 0xe777,
+ 0xea65,
+ 0xed7f,
+ 0xf0c8,
+ 0xf443,
+ 0xf7f4,
+ 0xfbdc
+ },
+ {
+ 0x0000,
+ 0x0463,
+ 0x0909,
+ 0x0df6,
+ 0x132d,
+ 0x1864,
+ 0x1e8a,
+ 0x24bd,
+ 0x2b4e,
+ 0x3244,
+ 0x39a3,
+ 0x4173
+ },
+ {
+ 0x49ba,
+ 0x527e,
+ 0x5bc8,
+ 0x65a0,
+ 0x700d,
+ 0x7b19,
+ 0x86cc,
+ 0x9336,
+ 0xa057,
+ 0xae42,
+ 0xbd01,
+ 0xcca2
+ },
+ {
+ 0xc8b4,
+ 0xc9cc,
+ 0xcaf5,
+ 0xcc30,
+ 0xcd7e,
+ 0xcedf,
+ 0xd056,
+ 0xd1e3,
+ 0xd387,
+ 0xd544,
+ 0xd71c,
+ 0xd911
+ }
+ };
+ uint16_t octave = note / 0xc;
+ octave = (octave - 3) & 3;
+ return freqtab[octave][note%0xc];
+}
+
+static uint8_t fmp_pdzf_vol_clamp(uint8_t v, uint8_t ev) {
+ int16_t ret = v + u8s16(ev);
+ if (ret < 0) ret = 0;
+ if (ret > 0xf) ret = 0xf;
+ return ret;
+}
+
+static void fmp_part_pdzf_vol_update(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ part->pdzf.vol = part->type.fm ? (0x7f-part->current_vol)&0xf : part->current_vol - 1;
+ if (part->pdzf.mode) {
+ uint8_t envvol = part->lfo_f.q ? part->pdzf.env_state.vol : 0;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_volume(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ fmp_pdzf_vol_clamp(part->pdzf.vol, envvol)
+ );
+ }
+ }
+}
+
+// 29e7
+static void fmp_part_fm_vol(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ fmp_part_pdzf_vol_update(work, part);
+ uint8_t pvol = part->actual_vol + fmp->fm_vol;
+ if (pvol & 0x80) {
+ if (fmp->fm_vol & 0x80) {
+ pvol = 0;
+ } else {
+ pvol = 0x7f;
+ }
+ }
+ for (int s = 0; s < 4; s++) {
+ // 2a16
+ if (!(part->slot_vol_mask & (1<<s))) continue;
+ uint8_t svol = pvol - part->u.fm.slot_rel_vol[s];
+ if (svol & 0x80) {
+ if (part->u.fm.slot_rel_vol[s] & 0x80) {
+ svol = 0x7f;
+ } else {
+ svol = 0;
+ }
+ }
+ // 2a31
+
+ if (fmp->data_version >= 6) {
+ svol += part->u.fm.tone_tl[s];
+ if (svol & 0x80) svol = 0x7f;
+ }
+
+ if (!(part->u.fm.slot_mask & (1<<s))) {
+ fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, svol);
+ }
+ }
+}
+
+// 3273
+static void fmp_set_timer_ch3(struct fmdriver_work *work,
+ struct driver_fmp *fmp) {
+ work->opna_writereg(work, 0x27, fmp->timer_ch3 | 0x2a);
+}
+
+// 326a
+static void fmp_set_tempo(struct fmdriver_work *work, struct driver_fmp *fmp) {
+ work->opna_writereg(work, 0x26, fmp->timerb);
+ fmp_set_timer_ch3(work, fmp);
+}
+
+// 2979
+static void fmp_part_keyoff_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ // 2979
+ uint8_t out = part->opna_keyon_out;
+ if (part->type.fm_3) {
+ fmp->fm3_slot_keyon &= (part->u.fm.slot_mask | 0x0f);
+ out |= fmp->fm3_slot_keyon;
+ }
+ work->opna_writereg(work, 0x28, out);
+}
+
+// 2961
+static void fmp_part_keyoff(struct fmdriver_work *work, struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ part->ext_keyon = 0;
+ part->status.tie_cont = false;
+ if (part->type.adpcm) return;
+ if (!part->type.ssg) {
+ fmp_part_keyoff_fm(work, fmp, part);
+ } else {
+ // 29a9
+ part->u.ssg.curr_vol = 0xff;
+ if (!part->u.ssg.env.release_rate) part->actual_vol = 0;
+ part->u.ssg.env_f.portamento = true;
+ part->u.ssg.env_f.attack = false;
+ }
+ if (part->pdzf.mode) {
+ part->pdzf.keyon = false;
+ if (part->lfo_f.q && part->pdzf.env_param.rr) {
+ part->pdzf.env_state.status = PDZF_ENV_REL;
+ part->pdzf.env_state.cnt = part->pdzf.env_param.rr;
+ } else {
+ part->pdzf.env_state.status = PDZF_ENV_OFF;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_stop(
+ work->ppz8,
+ part->pdzf.ppz8_channel
+ );
+ }
+ }
+ }
+}
+
+// 1fc8
+static bool fmp_cmd62_tempo(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t tempo = fmp_part_cmdload(fmp, part);
+ fmp->timerb_bak = tempo;
+ fmp->timerb = tempo;
+ fmp_set_tempo(work, fmp);
+ return true;
+}
+
+// 2067
+static bool fmp_cmd63_vol_d_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t vol = fmp_part_cmdload(fmp, part);
+ part->current_vol = vol;
+ part->actual_vol = vol;
+ fmp_part_fm_vol(work, fmp, part);
+ return true;
+}
+
+static void fmp_ssg_ppz8_pdzf_mode_update(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t prev_mode = part->pdzf.mode;
+ uint8_t mask = 9 << part->opna_keyon_out;
+ if ((fmp->ssg_mix & mask) == mask) {
+ part->u.ssg.env_f.ppz = true;
+ part->pdzf.mode = 0;
+ } else {
+ part->u.ssg.env_f.ppz = false;
+ if (fmp->pdzf.mode == 2) {
+ part->pdzf.mode =
+ (part->u.ssg.envbak.startvol || part->u.ssg.envbak.attack_rate)
+ ? 0 : 2;
+ }
+ }
+ if (prev_mode && !part->pdzf.mode) {
+ part->pdzf.keyon = false;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_stop(
+ work->ppz8,
+ part->pdzf.ppz8_channel
+ );
+ }
+ }
+}
+
+// 23a2
+static bool fmp_cmd63_mix_ssg(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ if (val & 0x80) {
+ // 23a6
+ fmp->ssg_mix &= val;
+ //fmp->ssg_mix_se &= val;
+ val = fmp_part_cmdload(fmp, part);
+ fmp->ssg_mix |= val;
+ //fmp->ssg_mix_se |= val;
+
+ fmp_ssg_ppz8_pdzf_mode_update(work, fmp, part);
+ // 23e4
+ work->opna_writereg(work, 0x07, fmp->ssg_mix);
+ // 23ec
+ } else {
+ // 23f7
+ fmp_part_cmdload(fmp, part);
+ fmp_part_cmdload16(fmp, part);
+ // TODO: PPZ
+ }
+ return true;
+}
+
+// 2574
+static bool fmp_cmd63_vol_d_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t vol = fmp_part_cmdload(fmp, part);
+ part->current_vol = vol;
+ part->actual_vol = vol;
+ work->opna_writereg(work, 0x10b, vol);
+ return true;
+}
+
+// 1fd9
+static bool fmp_cmd64_loop(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint8_t loop = fmp_part_cmdload(fmp, part);
+ if (--loop) {
+ ((uint8_t *)fmp->data)[part->current_ptr-1] = loop;
+ uint16_t ptr_diff = fmp_part_cmdload16(fmp, part);
+ part->current_ptr -= ptr_diff;
+ } else {
+ part->current_ptr += 3;
+ if (part->current_ptr < fmp->datalen) {
+ ((uint8_t *)fmp->data)[part->current_ptr-4] = fmp->data[part->current_ptr-1];
+ }
+ }
+ return true;
+}
+
+// 1fee
+static bool fmp_cmd65_loopend(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint16_t ptr_diff = fmp_part_cmdload16(fmp, part);
+ if ((part->current_ptr+ptr_diff-4) >= fmp->datalen) {
+ part->current_ptr = 0xffff;
+ } else if (fmp->data[part->current_ptr+ptr_diff-4] == 1) {
+ part->current_ptr += ptr_diff;
+ ((uint8_t *)fmp->data)[part->current_ptr-4] = fmp->data[part->current_ptr-1];
+ }
+ return true;
+}
+
+// 1fff
+static bool fmp_cmd66_tie(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ (void)fmp;
+ part->status.tie = true;
+ part->status.slur = false;
+ return true;
+}
+
+// 200f
+static bool fmp_cmd67_q(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ //TODO
+ if (fmp->datainfo.flags.q) {
+ part->gate_cnt = fmp_part_cmdload(fmp, part);
+ } else {
+ part->gate_cmp = fmp_part_cmdload(fmp, part);
+ }
+ return true;
+}
+
+// 201e
+static bool fmp_cmd68_pitchbend(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint8_t note = fmp_part_cmdload(fmp, part);
+ note += part->note_diff;
+ if (note > 0x60) note = 0;
+ part->pit.target_note = note;
+ uint16_t freq;
+ if (!part->type.ssg) {
+ freq = fmp_fm_freq(note);
+ } else {
+ freq = fmp_part_ssg_freq(part, note) >> fmp_ssg_octave(note);
+ }
+ part->pit.target_freq = freq;
+ part->pit.delay = fmp_part_cmdload(fmp, part);
+ part->pit.speed = fmp_part_cmdload(fmp, part);
+ part->pit.speed_cnt = part->pit.speed;
+ uint8_t rate = fmp_part_cmdload(fmp, part);
+ if (!part->type.fm) rate = -rate;
+ part->pit.rate = rate;
+ part->status.pitchbend = true;
+ return true;
+}
+
+// 25e3
+static bool fmp_cmd68_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ fmp_part_cmdload(fmp, part);
+ return true;
+}
+
+// 205d
+static bool fmp_cmd69_vol_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ static const uint8_t voltbl[16] = {
+ 64, 59, 56, 52, 48, 42, 40, 37,
+ 34, 32, 29, 26, 24, 21, 18, 16,
+ };
+ uint8_t index = fmp_part_cmdload(fmp, part);
+ uint8_t vol = (index < 16) ? voltbl[index] : 0x7f;
+ part->current_vol = vol;
+ part->actual_vol = vol;
+ fmp_part_fm_vol(work, fmp, part);
+ return true;
+}
+
+// 2362
+static void fmp_part_ppz8_vol(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ uint8_t vol = part->current_vol - 1;
+ if (vol > 0xf) vol = 0xf;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_volume(work->ppz8,
+ part->opna_keyon_out,
+ vol);
+ }
+}
+
+// 234d
+static void fmp_part_ssg_vol(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ fmp_part_pdzf_vol_update(work, part);
+ if (!part->u.ssg.env_f.ppz) {
+ // 2353
+ uint8_t vol = part->current_vol;
+ part->u.ssg.vol = ((unsigned)(vol-1) << 8)/vol;
+ } else {
+ fmp_part_ppz8_vol(work, part);
+ }
+}
+
+// 2348
+static bool fmp_cmd69_vol_ssg(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->current_vol = fmp_part_cmdload(fmp, part) + 1;
+ // added to prevent zero division
+ if (!part->current_vol) part->current_vol = 1;
+ fmp_part_ssg_vol(work, part);
+ return true;
+}
+
+static bool fmp_cmd69_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ // 205d: invalid command for adpcm part
+ (void)work;
+ fmp_part_cmdload(fmp, part);
+ return true;
+}
+
+static void fmp_part_fm_relvol(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part,
+ uint8_t vol) {
+ uint8_t newvol = part->current_vol - vol;
+ if (newvol & 0x80) {
+ newvol = (vol & 0x80) ? 0x7f : 0;
+ }
+ part->current_vol = newvol;
+ part->actual_vol = newvol;
+ fmp_part_fm_vol(work, fmp, part);
+}
+
+// 2080
+static bool fmp_cmd6a_voldec_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ fmp_part_fm_relvol(work, fmp, part, -3);
+ return true;
+}
+
+// 2086
+static bool fmp_cmd6b_volinc_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ fmp_part_fm_relvol(work, fmp, part, 3);
+ return true;
+}
+
+// 238a
+static bool fmp_cmd6a_voldec_ssg(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)fmp;
+ (void)work;
+ if (part->current_vol > 1) part->current_vol--;
+ if (!part->current_vol) part->current_vol = 1;
+ fmp_part_ssg_vol(work, part);
+ return true;
+}
+// 2396
+static bool fmp_cmd6b_volinc_ssg(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)fmp;
+ (void)work;
+ if (part->current_vol < 0x10) part->current_vol++;
+ fmp_part_ssg_vol(work, part);
+ return true;
+}
+
+// 25f5
+static void fmp_part_adpcm_relvol(struct fmdriver_work *work,
+ struct fmp_part *part,
+ uint8_t vol) {
+ uint16_t relvol = vol;
+ if (vol & 0x80) relvol |= 0xff00;
+ uint16_t newvol = part->current_vol + relvol;
+ if (newvol & 0x100) {
+ newvol = (vol & 0x80) ? 0 : 0xff;
+ }
+ part->current_vol = newvol;
+ part->actual_vol = newvol;
+ work->opna_writereg(work, 0x10b, newvol);
+}
+
+// 259e
+static bool fmp_cmd6a_voldec_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)fmp;
+ fmp_part_adpcm_relvol(work, part, -3);
+ return true;
+}
+
+// 259e
+static bool fmp_cmd6b_volinc_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)fmp;
+ fmp_part_adpcm_relvol(work, part, 3);
+ return true;
+}
+
+// 208c
+static bool fmp_cmd6c_kondelay(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->keyon_delay = fmp_part_cmdload(fmp, part);
+ return true;
+}
+
+// 2090
+static bool fmp_cmd6d_detune(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint16_t detune = fmp_part_cmdload(fmp, part);
+ if (detune & 0x80) detune |= 0xff00;
+ part->detune = detune;
+ return true;
+}
+
+// 2095
+static bool fmp_cmd6e_poke(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint16_t port = fmp_part_cmdload(fmp, part);
+ uint8_t data = fmp_part_cmdload(fmp, part);
+ // 3222?
+ if (part->opna_keyon_out & 0x04) port |= 0x100;
+ work->opna_writereg(work, port, data);
+ return true;
+}
+
+// 20b7
+static void fmp_part_sync(struct fmp_part *part) {
+ if (part->sync != 1) part->sync = 0;
+}
+
+// 209e
+static bool fmp_cmd6f_sync(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ // sync
+ // command S{channels}
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ if (!(val & 0x80)) {
+ for (int i = 0; i < 3; i++) {
+ if (val & (1<<i)) fmp_part_sync(&fmp->parts[FMP_PART_FM_1+i]);
+ }
+ for (int i = 0; i < 3; i++) {
+ if (val & (1<<(i+3))) fmp_part_sync(&fmp->parts[FMP_PART_SSG_1+i]);
+ }
+ } else {
+ for (int i = 0; i < 3; i++) {
+ if (val & (1<<i)) fmp_part_sync(&fmp->parts[FMP_PART_FM_4+i]);
+ }
+ if (val & (1<<3)) fmp_part_sync(&fmp->parts[FMP_PART_ADPCM]);
+ if (val & (1<<4)) {
+ if (fmp->rhythm.sync != 1) fmp->rhythm.sync = 0;
+ }
+ }
+ return true;
+}
+
+// 20eb
+static bool fmp_cmd70_wait(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ (void)fmp;
+ // command W
+ part->sync = 2;
+ return false;
+}
+
+// 20f6
+static bool fmp_cmd71_tone_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t tone = fmp_part_cmdload(fmp, part);
+ part->tone = tone;
+ for (int i = 0; i < 4; i++) {
+ if (part->u.fm.slot_mask & (1<<i)) continue;
+ fmp_part_fm_reg_write(work, part, OPNA_SLRR+4*i, 0x0f);
+ }
+ for (int i = 0; i < 4; i++) {
+ part->u.fm.slot_rel_vol[i] = 0;
+ }
+ // 211b
+ uint16_t fmtoneptr = fmp->datainfo.fmtoneptr + (tone*25);
+ for (int i = 0; i < 4; i++) {
+ part->u.fm.tone_tl[i] = fmp->data[fmtoneptr+4+i];
+ }
+ for (int p = 0; p < 6; p++) {
+ for (int s = 0; s < 4; s++) {
+ if (part->u.fm.slot_mask & (1<<s)) continue;
+ fmp_part_fm_reg_write(work, part,
+ OPNA_DTMUL+(p*0x10)+4*s,
+ fmp->data[fmtoneptr+(p*4)+s]);
+ }
+ }
+ uint8_t fbalg = fmp->data[fmtoneptr+0x18];
+ // 2160
+ if (part->type.fm_3) {
+ if (part->u.fm.slot_mask & 1) {
+ fbalg = (fbalg & 0x38) | (fmp->fm3_alg & 0x07);
+ }
+ }
+ fmp_part_fm_reg_write(work, part, OPNA_FBALG, fbalg);
+ if (part->type.fm_3) {
+ fmp->fm3_alg = fbalg & 0x07;
+ }
+ uint8_t alg = fbalg & 0x7;
+ static const uint8_t alg_vol_tbl[8] = {
+ 0x8, 0x8, 0x8, 0x8, 0xc, 0xe, 0xe, 0xf,
+ };
+ part->slot_vol_mask = alg_vol_tbl[alg];
+ fmp_part_fm_vol(work, fmp, part);
+ return true;
+}
+
+// 2458
+static void fmp_envreset_ssg(struct fmp_part *part) {
+ part->u.ssg.env.startvol = part->u.ssg.envbak.startvol;
+ part->u.ssg.env.attack_rate = part->u.ssg.envbak.attack_rate;
+ part->u.ssg.env.decay_rate = part->u.ssg.envbak.decay_rate;
+ part->u.ssg.env.sustain_lv = part->u.ssg.envbak.sustain_lv;
+ part->u.ssg.env.sustain_rate = part->u.ssg.envbak.sustain_rate;
+ part->u.ssg.env.release_rate = part->u.ssg.envbak.release_rate;
+}
+
+// 2458
+static bool fmp_cmd71_envreset_ssg(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ fmp_part_cmdload(fmp, part);
+ fmp_envreset_ssg(part);
+ return true;
+}
+
+// 25a8
+static bool fmp_cmd71_tone_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint8_t tone = fmp_part_cmdload(fmp, part);
+ part->tone = tone;
+ if (!(tone & 0x80)) {
+ // 25b4
+ uint16_t adpcmptr = fmp->datainfo.adpcmptr+6*tone;
+ if (adpcmptr+6 <= fmp->datalen) {
+ part->u.adpcm.startaddr = read16le(&fmp->data[adpcmptr+0]);
+ part->u.adpcm.endaddr = read16le(&fmp->data[adpcmptr+2]);
+ part->u.adpcm.deltat = read16le(&fmp->data[adpcmptr+4]);
+ }
+ } else {
+ // 25cc
+ tone &= 0x7f;
+ part->u.adpcm.startaddr = fmp->adpcm_startaddr[tone];
+ part->u.adpcm.endaddr = fmp->adpcm_endaddr[tone];
+ part->u.adpcm.deltat = fmp->adpcm_deltat;
+ }
+ return true;
+}
+
+// 20f2
+static bool fmp_cmd72_deflen(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->default_len = fmp_part_cmdload(fmp, part);
+ return true;
+}
+
+// 1fac
+static bool fmp_cmd73_relvol_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ fmp_part_fm_relvol(work, fmp, part, fmp_part_cmdload(fmp, part));
+ return true;
+}
+
+// 2424
+static bool fmp_cmd73_noise_ssg(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ // TODO: ignoring se flag
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ part->u.ssg.env_f.noise = true;
+ if (val & 0x80) {
+ part->u.ssg.env_f.noise = false;
+ } else {
+ fmp->ssg_noise_freq = val;
+ work->opna_writereg(work, 0x06, val);
+ }
+ return true;
+}
+
+// 25f5
+static bool fmp_cmd73_relvol_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ fmp_part_adpcm_relvol(work, part, fmp_part_cmdload(fmp, part));
+ return true;
+}
+
+/*
+// ????
+// 1b64
+static void fmp() {
+
+}
+*/
+
+// 2466
+static bool fmp_cmd74_loop(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ // TODO: se flag
+ fmp->part_loop_bit &= ~part->part_bit;
+ if (!fmp->part_loop_bit) {
+ if (!--fmp->loop_dec) {
+ fmp->status.looped = true;
+ // al=2; 1b64();
+ }
+ // 248c
+ fmp->loop_cnt++;
+ fmp->part_loop_bit = fmp->part_playing_bit;
+ // al=2; 1b64();
+ }
+ // 24a2
+ uint16_t ptr = part->loop_ptr;
+ if (ptr != 0xffff) {
+ part->current_ptr = ptr;
+ return true;
+ } else {
+ fmp->part_loop_bit &= ~part->part_bit;
+ fmp->part_playing_bit &= ~part->part_bit;
+ if (!fmp->part_playing_bit) {
+ // 24bc
+ // 3e16();
+ fmp->status.stopped = true;
+ fmp->status.looped = true;
+ }
+ // 24f0
+ if (!part->type.rhythm) {
+ part->prev_note = 0x61; // rest
+ part->status.off = true;
+ if (part->type.ssg) {
+ part->actual_vol = 0;
+ // 0x6a?
+ }
+ fmp_part_keyoff(work, fmp, part);
+ } else {
+ // 2512
+ fmp->rhythm.status = 1;
+ }
+ return false;
+ }
+}
+
+// 22e0
+static bool fmp_cmd75_lfo(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ if (val & 0x80) {
+ // LFO on/off
+ // MD/S command
+ // 2313
+ bool set = val & 0x40;
+ if (val & (1<<0)) part->lfo_f.e = set;
+ if (val & (1<<1)) part->lfo_f.w = set;
+ if (val & (1<<2)) part->lfo_f.a = set;
+ if (val & (1<<3)) part->lfo_f.r = set;
+ if (val & (1<<4)) part->lfo_f.q = set;
+ if (val & (1<<5)) part->lfo_f.p = set;
+ if (!set) {
+ if (!part->type.fm) {
+ part->actual_freq = fmp_part_ssg_freq(part, part->prev_note);
+ part->prev_freq = 0;
+ } else {
+ part->actual_freq = fmp_fm_freq(part->prev_note);
+ part->prev_freq = 0;
+ }
+ } else {
+ if (part->lfo_f.e) part->lfo_f.a = false;
+ }
+ } else {
+ // 22e4
+ // EON/EOFF (FM3)
+ if (val & 0x40) {
+ // 22f1
+ fmp->timer_ch3 |= 0x40;
+ if (val & 0x20) {
+ fmp->ch3_se_freqdiff[0] = u8s16(fmp_part_cmdload(fmp, part));
+ fmp->ch3_se_freqdiff[1] = u8s16(fmp_part_cmdload(fmp, part));
+ fmp->ch3_se_freqdiff[2] = u8s16(fmp_part_cmdload(fmp, part));
+ fmp->ch3_se_freqdiff[3] = u8s16(fmp_part_cmdload(fmp, part));
+ }
+ } else {
+ // 22e9
+ fmp->timer_ch3 &= 0x3f;
+ }
+ fmp_set_timer_ch3(work, fmp);
+ }
+ return true;
+}
+
+// 2fa2
+static void fmp_init_lfo(struct fmp_lfo *lfo) {
+ lfo->delay_cnt = lfo->delay;
+ lfo->speed_cnt = 1;
+ lfo->depth_cnt = lfo->depth >> 1;
+ int16_t rate2 = u16s16(lfo->rate);
+ if (lfo->waveform != FMP_LFOWF_TRIANGLE) {
+ rate2 *= lfo->depth;
+ // 2fc4
+ if (lfo->waveform == FMP_LFOWF_STAIRCASE) {
+ rate2 >>= 1;
+ lfo->depth_cnt = 1;
+ }
+ }
+ // 2fd0
+ if (lfo->waveform == FMP_LFOWF_RANDOM) {
+ rate2 = 0;
+ }
+ // 2fd8
+ lfo->rate2 = rate2;
+}
+
+// 2f84
+static void fmp_part_init_lfo_pqr(struct fmp_part *part) {
+ if (part->lfo_f.p) fmp_init_lfo(&part->lfo[FMP_LFO_P]);
+ if (part->lfo_f.q) fmp_init_lfo(&part->lfo[FMP_LFO_Q]);
+ if (part->lfo_f.r) fmp_init_lfo(&part->lfo[FMP_LFO_R]);
+}
+
+// 21a6
+static void fmp_cmd_lfo(struct driver_fmp *fmp,
+ struct fmp_part *part,
+ struct fmp_lfo *lfo) {
+ lfo->delay = fmp_part_cmdload(fmp, part);
+ lfo->speed = fmp_part_cmdload(fmp, part);
+ uint16_t rate = fmp_part_cmdload(fmp, part);
+ if (rate & 0x80) rate |= 0xff00;
+ lfo->rate = rate;
+ lfo->rate2 = rate;
+ lfo->depth = fmp_part_cmdload(fmp, part);
+ lfo->waveform = fmp_part_cmdload(fmp, part);
+ if (lfo->waveform == 6) {
+ lfo->rate = (lfo->rate&0xff)*lfo->depth;
+ }
+ part->lfo_f.lfo = true;
+ fmp_part_init_lfo_pqr(part);
+}
+
+// 21a1
+static bool fmp_cmd76_lfo_p(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->lfo_f.p = true;
+ fmp_cmd_lfo(fmp, part, &part->lfo[FMP_LFO_P]);
+ return true;
+}
+// 21d0
+static bool fmp_cmd77_lfo_q(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->lfo_f.q = true;
+ struct fmp_lfo *lfo = &part->lfo[FMP_LFO_Q];
+ fmp_cmd_lfo(fmp, part, lfo);
+ part->pdzf.env_param.rr = lfo->delay - 2;
+ part->pdzf.env_param.sr = lfo->speed;
+ part->pdzf.env_param.dd = u16s16(lfo->rate);
+ part->pdzf.env_param.al = lfo->depth;
+ return true;
+}
+// 21da
+static bool fmp_cmd78_lfo_r(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->lfo_f.r = true;
+ struct fmp_lfo *lfo = &part->lfo[FMP_LFO_R];
+ fmp_cmd_lfo(fmp, part, lfo);
+ if (part->pdzf.mode != 2 || lfo->depth == 0) {
+ part->pdzf.voice = lfo->speed;
+ if (part->pdzf.mode == 2 && (lfo->delay != 2)) {
+ uint8_t upan = lfo->delay - 2;
+ if (upan < 10) {
+ part->pdzf.pan = upan - 5;
+ }
+ } else {
+ int8_t pan = u8s16(lfo->rate);
+ if (pan < -4) pan = -4;
+ if (pan > 4) pan = 4;
+ part->pdzf.pan = pan;
+ }
+ }
+ return true;
+}
+
+static void fmp_pdzf_loop_freq(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part,
+ uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) {
+ if (!part->pdzf.mode) return;
+ if (part->pdzf.mode != 2 || part->lfo[2].depth == 0) {
+ uint32_t start = (d0 << 8) | d1;
+ uint32_t end = (d2 << 8) | d3;
+ if (start == 0xffff) start = (uint32_t)-1;
+ if (end == 0xffff) end = (uint32_t)-1;
+ if ((start != (uint32_t)-1) && (end != (uint32_t)-1)) {
+ if (start >= end) {
+ start = (uint32_t)-1;
+ end = (uint32_t)-1;
+ }
+ }
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_loopoffset(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ start, end
+ );
+ }
+ }
+ if (part->pdzf.mode == 2) {
+ if (part->lfo[2].depth == 1 || part->lfo[2].depth == 2) {
+ uint32_t addr = ((uint32_t)d0) << 24;
+ addr |= ((uint32_t)d1) << 16;
+ addr |= ((uint32_t)d2) << 8;
+ addr |= d3;
+ if (part->lfo[2].depth == 1) {
+ part->pdzf.loopstart32 = addr;
+ } else {
+ part->pdzf.loopend32 = addr;
+ }
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_loopoffset(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ part->pdzf.loopstart32, part->pdzf.loopend32
+ );
+ }
+ }
+ }
+}
+
+static bool fmp_cmd79_lfo_a_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->lfo_f.a = true;
+ part->lfo_f.e = false;
+ uint8_t v = fmp_part_cmdload(fmp, part);
+ part->u.fm.alfo.delay = v;
+ part->u.fm.alfo.delay_cnt = v;
+ v = fmp_part_cmdload(fmp, part);
+ part->u.fm.alfo.speed = v;
+ part->u.fm.alfo.speed_cnt = v;
+ v = fmp_part_cmdload(fmp, part);
+ part->u.fm.alfo.rate = v;
+ part->u.fm.alfo.rate_orig = v;
+ v = fmp_part_cmdload(fmp, part);
+ part->u.fm.alfo.depth = v;
+ part->u.fm.alfo.depth_cnt = v;
+ fmp_pdzf_loop_freq(work, fmp, part,
+ part->u.fm.alfo.delay, part->u.fm.alfo.speed,
+ part->u.fm.alfo.rate, part->u.fm.alfo.depth
+ );
+ return true;
+}
+
+// 244f
+static bool fmp_cmd79_env_ssg(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->u.ssg.envbak.startvol = fmp_part_cmdload(fmp, part);
+ part->u.ssg.envbak.attack_rate = fmp_part_cmdload(fmp, part);
+ part->u.ssg.envbak.decay_rate = fmp_part_cmdload(fmp, part);
+ part->u.ssg.envbak.sustain_lv = fmp_part_cmdload(fmp, part);
+ part->u.ssg.envbak.sustain_rate = fmp_part_cmdload(fmp, part);
+ part->u.ssg.envbak.release_rate = fmp_part_cmdload(fmp, part);
+ fmp_envreset_ssg(part);
+ fmp_ssg_ppz8_pdzf_mode_update(work, fmp, part);
+ fmp_pdzf_loop_freq(work, fmp, part,
+ part->u.ssg.envbak.decay_rate,
+ part->u.ssg.envbak.sustain_lv,
+ part->u.ssg.envbak.sustain_rate,
+ part->u.ssg.envbak.release_rate
+ );
+ return true;
+}
+
+// 2009
+static bool fmp_cmd7a_tie(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ (void)fmp;
+ part->status.tie = true;
+ part->status.slur = true;
+ return true;
+}
+
+// 1f9e
+static bool fmp_cmd7b_transpose(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ if (val) part->note_diff += val;
+ else part->note_diff = 0;
+ return true;
+}
+
+// 2229
+static void fmp_part_write_pan(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ fmp_part_fm_reg_write(work, part, OPNA_LRAMSPMS, part->pan_ams_pms);
+}
+
+static void fmp_fm_pdzf_mode_update(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t prev_mode = part->pdzf.mode;
+ part->pdzf.mode = (part->u.fm.slot_mask == 0xff) ? fmp->pdzf.mode : 0;
+ if (prev_mode && !part->pdzf.mode) {
+ part->pdzf.keyon = false;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_stop(
+ work->ppz8,
+ part->pdzf.ppz8_channel
+ );
+ }
+ }
+}
+
+// 2206
+static bool fmp_cmd7c_lfo_pan_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t val1 = fmp_part_cmdload(fmp, part);
+ if (val1 & 0x01) {
+ uint8_t val2 = fmp_part_cmdload(fmp, part);
+ uint8_t val3 = fmp_part_cmdload(fmp, part);
+ if (val3 & 0x80) {
+ if (val3 & 0x20) {
+ // 229b
+ // HLFO delay
+ // 7c 01 val a0: DH
+ part->u.fm.hlfo_delay = val2;
+ } else {
+ // 227c
+ // HLFO on/off
+ // 7c 01 00 c0: SH1/MDH1: HLFO ON
+ // 7c 01 00 80: SH0/MDH0: HLFO OFF
+ val3 &= 0x7f;
+ val3 >>= 3;
+ part->u.fm.hlfo_freq &= 0xf7;
+ part->u.fm.hlfo_freq |= val3;
+ work->opna_writereg(work, 0x22, part->u.fm.hlfo_freq);
+ if (!val3) {
+ part->pan_ams_pms &= 0xc0;
+ fmp_part_write_pan(work, part);
+ }
+ }
+ } else {
+ // 226b
+ // HLFO parameters
+ // 7c 01 val2 val3 val4: MH
+ part->u.fm.hlfo_delay = val2;
+ part->u.fm.hlfo_freq = val3 | 0x08;
+ part->u.fm.hlfo_apms = fmp_part_cmdload(fmp, part);
+ }
+ } else {
+ // 220a
+ if (val1 & 0x02) {
+ // 229f
+ // LFO W settings
+ // 7c 02 xx xx xx xx xx: MW
+ part->lfo_f.w = true;
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ part->u.fm.wlfo.delay = val;
+ part->u.fm.wlfo.delay_cnt = val;
+ val = fmp_part_cmdload(fmp, part);
+ part->u.fm.wlfo.speed = val;
+ part->u.fm.wlfo.speed_cnt = val;
+ val = fmp_part_cmdload(fmp, part);
+ part->u.fm.wlfo.rate = val;
+ part->u.fm.wlfo.rate_orig = val;
+ part->u.fm.wlfo.rate_curr = 0;
+ val = fmp_part_cmdload(fmp, part);
+ part->u.fm.wlfo.depth = val;
+ part->u.fm.wlfo.depth_cnt = val;
+ val = fmp_part_cmdload(fmp, part);
+ part->u.fm.wlfo.sync = val;
+
+ int pdzf_i = (part - &fmp->parts[FMP_PART_FM_EX1]);
+ if ((pdzf_i == 1) || (pdzf_i == 2)) {
+ struct pdzf_rhythm *pr = &fmp->pdzf.rhythm[pdzf_i-1];
+ pr->voice[0] = part->u.fm.wlfo.delay;
+ pr->voice[1] = part->u.fm.wlfo.speed;
+ int16_t panpot = u8s16(part->u.fm.wlfo.rate);
+ if (panpot < -4) panpot = -4;
+ if (panpot > 4) panpot = 4;
+ pr->pan = panpot;
+ pr->note = part->u.fm.wlfo.depth;
+ pr->enabled = true;
+ }
+ } else {
+ // 2211
+ if (val1 & 0x04) {
+ // 22c7
+ // mask slot
+ // 7c 04 val: EX
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ fmp->timer_ch3 |= 0x40;
+ if (val == 0xff) {
+ fmp_part_keyoff(work, fmp, part);
+ fmp->timer_ch3 &= 0x3f;
+ }
+ part->u.fm.slot_mask = val;
+ fmp_fm_pdzf_mode_update(work, fmp, part);
+ } else {
+ // 2218
+ // PAN
+ // 7c val1: P
+ part->pan_ams_pms &= 0x3f;
+ part->pan_ams_pms |= val1;
+ if (part->type.fm_3) {
+ // 2236
+ fmp->parts[FMP_PART_FM_3].pan_ams_pms &= 0x3f;
+ fmp->parts[FMP_PART_FM_3].pan_ams_pms |= val1;
+ fmp->parts[FMP_PART_FM_EX1].pan_ams_pms &= 0x3f;
+ fmp->parts[FMP_PART_FM_EX1].pan_ams_pms |= val1;
+ fmp->parts[FMP_PART_FM_EX2].pan_ams_pms &= 0x3f;
+ fmp->parts[FMP_PART_FM_EX2].pan_ams_pms |= val1;
+ fmp->parts[FMP_PART_FM_EX3].pan_ams_pms &= 0x3f;
+ fmp->parts[FMP_PART_FM_EX3].pan_ams_pms |= val1;
+ }
+ fmp_part_write_pan(work, part);
+ }
+ }
+ }
+ return true;
+}
+
+// 1f78
+static bool fmp_cmd7c_tone_ssg(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ part->tone = fmp_part_cmdload(fmp, part);
+ uint16_t toneptr = fmp->datainfo.ssgtoneptr + (part->tone*6);
+ if ((fmp->datainfo.ssgtoneptr != 0xffff) && (fmp->datalen >= (toneptr+6))) {
+ part->u.ssg.envbak.startvol = fmp->data[toneptr+0];
+ part->u.ssg.envbak.attack_rate = fmp->data[toneptr+1];
+ part->u.ssg.envbak.decay_rate = fmp->data[toneptr+2];
+ part->u.ssg.envbak.sustain_lv = fmp->data[toneptr+3];
+ part->u.ssg.envbak.sustain_rate = fmp->data[toneptr+4];
+ part->u.ssg.envbak.release_rate = fmp->data[toneptr+5];
+ }
+ fmp_ssg_ppz8_pdzf_mode_update(work, fmp, part);
+ fmp_pdzf_loop_freq(work, fmp, part,
+ part->u.ssg.envbak.decay_rate,
+ part->u.ssg.envbak.sustain_lv,
+ part->u.ssg.envbak.sustain_rate,
+ part->u.ssg.envbak.release_rate
+ );
+ fmp_envreset_ssg(part);
+ return true;
+}
+
+// 25e4
+static void fmp_adpcm_pan(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part,
+ uint8_t val) {
+ val &= 0xc0;
+ part->pan_ams_pms = val;
+ work->opna_writereg(work, 0x101, val | fmp->adpcm_c1);
+}
+
+// 25e4
+static bool fmp_cmd7c_pan_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ fmp_adpcm_pan(work, fmp, part, fmp_part_cmdload(fmp, part));
+ return true;
+}
+
+// 1f70
+static bool fmp_cmd7d_sync(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ // unknown MML
+ fmp->sync.data = fmp_part_cmdload(fmp, part);
+ fmp->sync.cnt++;
+ return true;
+}
+
+// 1f46
+static bool fmp_cmd7e_loop_det(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint8_t val1 = fmp_part_cmdload(fmp, part);
+ if (val1) {
+ fmp->loop_dec = val1;
+ fmp->loop_times = val1;
+ // Fadeout not implemented
+ } else {
+ // detune relative
+ // DX
+ int16_t det = u8s16(fmp_part_cmdload(fmp, part));
+ part->detune += det;
+ }
+ return true;
+}
+// 1f2c
+static bool fmp_cmd7f_lfo_delay(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ (void)work;
+ uint8_t val1 = fmp_part_cmdload(fmp, part);
+ uint8_t delay = fmp_part_cmdload(fmp, part);
+ if (val1 < 3) {
+ part->lfo[val1].delay = delay;
+ } else if (val1 == 3) {
+ // 1f40
+ part->u.fm.alfo.delay = delay;
+ }
+ return true;
+}
+
+// 1eaf
+static void fmp_update_slot_rel_vol(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t pvol = part->actual_vol+fmp->fm_vol;
+ if (pvol & 0x80) {
+ pvol = (fmp->fm_vol & 0x80) ? 0 : 0x7f;
+ }
+ // 1ed4
+ for (int s = 0; s < 4; s++) {
+ uint8_t svol;
+ if (!(part->slot_vol_mask & (1<<s))) {
+ // 1ee2
+ svol = part->u.fm.tone_tl[s] - part->u.fm.slot_rel_vol[s];
+ if (svol & 0x80) {
+ svol = (part->u.fm.slot_rel_vol[s] & 0x80) ? 0x7f : 0;
+ }
+ } else {
+ // 1f00
+ svol = pvol - part->u.fm.slot_rel_vol[s];
+ if (svol & 0x80) {
+ svol = (part->u.fm.slot_rel_vol[s] & 0x80) ? 0x7f : 0;
+ }
+ svol += part->u.fm.tone_tl[s];
+ if (svol & 0x80) svol = 0x7f;
+ }
+ if (!(part->u.fm.slot_mask & (1<<s))) {
+ fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, svol);
+ }
+ // 1f20
+ }
+}
+
+// 1e68
+static bool fmp_cmde2_slotrelvol_set_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ uint8_t mask = fmp_part_cmdload(fmp, part);
+ for (int i = 0; i < 4; i++) {
+ if (mask & (1<<i)) {
+ part->u.fm.slot_rel_vol[i] = val;
+ }
+ }
+ fmp_update_slot_rel_vol(work, fmp, part);
+ return true;
+}
+
+// 1e7d
+static bool fmp_cmde3_slotrelvol_add_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ uint8_t val = fmp_part_cmdload(fmp, part);
+ uint8_t mask = fmp_part_cmdload(fmp, part);
+ for (int i = 0; i < 4; i++) {
+ if (!(mask & (1<<i))) continue;
+ int svol = part->u.fm.slot_rel_vol[i] + val;
+ if (svol > 0x7f) {
+ svol = (val & 0x80) ? 0 : 0x7f;
+ }
+ part->u.fm.slot_rel_vol[i] = svol;
+ }
+ fmp_update_slot_rel_vol(work, fmp, part);
+ return true;
+}
+
+// 2c5b
+static void fmp_part_lfo_calc(struct driver_fmp *fmp,
+ struct fmp_part *part, int num) {
+ struct fmp_lfo *lfo = &part->lfo[num];
+ uint8_t waveform = lfo->waveform;
+ if (waveform > 6) waveform = 0;
+ switch (waveform) {
+ case FMP_LFOWF_TRIANGLE:
+ case FMP_LFOWF_TRIANGLE2:
+ // 2c7f
+ if (!lfo->delay) return;
+ if (--lfo->delay_cnt) return;
+ lfo->delay_cnt = 1;
+ if (--lfo->speed_cnt) return;
+ lfo->speed_cnt = lfo->speed;
+ part->actual_freq += lfo->rate2;
+ if (--lfo->depth_cnt) return;
+ lfo->depth_cnt = lfo->depth;
+ lfo->rate2 = -lfo->rate2;
+ return;
+ case FMP_LFOWF_SAWTOOTH:
+ // 2cae
+ if (!lfo->delay) return;
+ if (--lfo->delay_cnt) return;
+ lfo->delay_cnt = 1;
+ if (--lfo->speed_cnt) return;
+ lfo->speed_cnt = lfo->speed;
+ part->actual_freq += lfo->rate;
+ if (--lfo->depth_cnt) return;
+ lfo->depth_cnt = lfo->depth;
+ part->actual_freq -= lfo->rate2;
+ return;
+ case FMP_LFOWF_SQUARE:
+ // 2ce0
+ if (!lfo->delay) return;
+ if (--lfo->delay_cnt) return;
+ lfo->delay_cnt = 1;
+ if (--lfo->speed_cnt) return;
+ lfo->speed_cnt = lfo->speed;
+ part->actual_freq += (lfo->rate2 >> (lfo->depth_cnt != 0));
+ if (lfo->depth_cnt) lfo->depth_cnt = 0;
+ lfo->rate2 = -lfo->rate2;
+ return;
+ case FMP_LFOWF_LINEAR:
+ // 2d10
+ if (!lfo->delay) return;
+ if (!lfo->depth_cnt) return;
+ if (--lfo->delay_cnt) return;
+ lfo->delay_cnt = 1;
+ if (--lfo->speed_cnt) return;
+ lfo->speed_cnt = lfo->speed;
+ part->actual_freq += lfo->rate;
+ lfo->depth_cnt--;
+ return;
+ case FMP_LFOWF_STAIRCASE:
+ // 2d3a
+ if (!lfo->delay) return;
+ if (--lfo->delay_cnt) return;
+ lfo->delay_cnt = 1;
+ if (--lfo->speed_cnt) return;
+ lfo->speed_cnt = lfo->speed;
+ part->actual_freq += lfo->rate2;
+ if (--lfo->depth_cnt) return;
+ lfo->depth_cnt = 2;
+ lfo->rate2 = -lfo->rate2;
+ return;
+ case FMP_LFOWF_RANDOM:
+ if (!lfo->delay) return;
+ if (--lfo->delay_cnt) return;
+ lfo->delay_cnt = 1;
+ if (--lfo->speed_cnt) return;
+ lfo->speed_cnt = lfo->speed;
+ {
+ uint8_t rand = fmp_rand71(fmp);
+ uint16_t a = (lfo->rate>>1) - (rand*lfo->depth_cnt)%lfo->rate;
+ lfo->depth_cnt = rand;
+ part->actual_freq -= lfo->rate2;
+ part->actual_freq += a;
+ lfo->rate2 = a;
+ return;
+ }
+ }
+}
+
+// 27c9
+static void fmp_part_keyon_fm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ if (part->lfo_f.portamento) return;
+ // 27df
+ uint8_t out = part->opna_keyon_out;
+ if (part->type.fm_3) {
+ fmp->fm3_slot_keyon |= ((~part->u.fm.slot_mask)&0xf0);
+ out |= fmp->fm3_slot_keyon;
+ } else {
+ // 27fb
+ out |= 0xf0;
+ }
+ work->opna_writereg(work, 0x28, out);
+}
+
+// 2b1c-2b79
+static void fmp_part_wlfo(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ struct fmp_wlfo *wlfo = &part->u.fm.wlfo;
+ // 2b1c
+ if (!wlfo->delay) return;
+ if (--wlfo->delay_cnt) return;
+ wlfo->delay_cnt = 1;
+ if (--wlfo->speed_cnt) return;
+ wlfo->speed_cnt = wlfo->speed;
+ // 2b36
+ wlfo->rate_curr += wlfo->rate;
+ for (int i = 0; i < 4; i++) {
+ // 2b4e
+ if (!(wlfo->sync & (1<<i))) continue;
+ uint8_t tl = part->u.fm.tone_tl[i] + wlfo->rate_curr;
+ if (tl & 0x80) {
+ tl = (wlfo->rate_curr & 0x80) ? 0 : 0x7f;
+ }
+ fmp_part_fm_reg_write(work, part, OPNA_TL+4*i, tl);
+ }
+ if (!--wlfo->depth_cnt) {
+ wlfo->depth_cnt = wlfo->depth;
+ wlfo->rate = -wlfo->rate;
+ }
+ // 2b79
+}
+
+// 2e0c
+static void fmp_part_freq_ppz8(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ uint32_t freq = part->actual_freq;
+ uint8_t octave = part->u.ssg.octave;
+ if (!octave) return;
+ if (octave != 4) {
+ if (octave > 4) {
+ // 2e22
+ freq <<= (octave-4);
+ } else {
+ freq >>= (4-octave);
+ }
+ }
+ // 2e3b
+ int32_t detune = u16s16(part->detune);
+ freq += detune << 6;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_freq(work->ppz8, part->opna_keyon_out, freq);
+ }
+}
+
+static uint32_t fmp_ppz8_note_freq(uint8_t note) {
+ uint32_t freq = fmp_ppz_freq(note);
+ uint8_t octave = note/0xc;
+ if (octave > 4) {
+ freq <<= (octave - 4);
+ } else {
+ freq >>= (4 - octave);
+ }
+ return freq;
+}
+
+static void fmp_part_keyon_pdzf(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ uint8_t voice = part->pdzf.voice;
+ uint8_t pan = part->pdzf.pan + 5;
+ uint32_t freq = fmp_ppz8_note_freq(part->prev_note);
+ freq += (u16s16(part->detune) << 3);
+
+ if (!part->pdzf.keyon) {
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_play(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ voice
+ );
+ }
+ part->pdzf.env_state.status = PDZF_ENV_ATT;
+ part->pdzf.env_state.vol = 0;
+ part->pdzf.env_state.cnt = part->pdzf.env_param.al;
+ part->pdzf.keyon = true;
+ }
+
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_pan(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ pan
+ );
+ work->ppz8_functbl->channel_freq(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ freq
+ );
+ work->ppz8_functbl->channel_volume(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ fmp_pdzf_vol_clamp(part->pdzf.vol, part->pdzf.env_state.vol)
+ );
+ }
+}
+
+// 27a3
+static void fmp_part_keyon(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ part->ext_keyon = 0xffff;
+ part->status.keyon = false;
+ if (part->pdzf.mode) {
+ fmp_part_keyon_pdzf(work, part);
+ }
+ if (!part->type.ssg) {
+ if (part->lfo_f.lfo) {
+ part->actual_freq = fmp_fm_freq(part->prev_note);
+ part->prev_freq = 0;
+ part->lfo_f.lfo = false;
+ }
+ fmp_part_keyon_fm(work, fmp, part);
+ } else {
+ // 2805
+ if (part->lfo_f.lfo) {
+ uint8_t note = part->prev_note;
+ part->u.ssg.octave = fmp_ssg_octave(note);
+ part->actual_freq = fmp_part_ssg_freq(part, note);
+ part->prev_freq = 0;
+ part->lfo_f.lfo = false;
+ }
+ // 281e
+ if (part->lfo_f.portamento) {
+ part->u.ssg.env_f.attack = false;
+ part->u.ssg.env_f.portamento = true;
+ return;
+ }
+ // 2823
+ if (part->u.ssg.env_f.ppz) {
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_play(
+ work->ppz8, part->opna_keyon_out, part->tone
+ );
+ }
+ fmp_part_ppz8_vol(work, part);
+ fmp_part_freq_ppz8(work, part);
+ return;
+ }
+ part->u.ssg.env_f.attack = true;
+ }
+}
+
+// 30b1
+static void fmp_part_init_wlfo(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ if (!(part->u.fm.wlfo.sync & 0x80)) return;
+ for (int s = 0; s < 4; s++) {
+ if (!(part->u.fm.wlfo.sync & (1<<s))) continue;
+ fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, part->u.fm.tone_tl[s]);
+ }
+ // 30d5
+ part->u.fm.wlfo.delay_cnt = part->u.fm.wlfo.delay;
+ part->u.fm.wlfo.depth_cnt = part->u.fm.wlfo.depth;
+ part->u.fm.wlfo.rate_curr = 0;
+ part->u.fm.wlfo.rate = part->u.fm.wlfo.rate_orig;
+}
+
+// 309b
+static void fmp_part_hlfo(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ work->opna_writereg(work, 0x22, part->u.fm.hlfo_freq);
+ part->pan_ams_pms &= 0xc0;
+ part->pan_ams_pms |= part->u.fm.hlfo_apms;
+ fmp_part_write_pan(work, part);
+}
+
+// 2fdc
+static void fmp_part_init_lfo_awe(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ if (part->lfo_f.w) {
+ fmp_part_init_wlfo(work, part);
+ }
+ if (part->lfo_f.a || part->lfo_f.e || (part->current_vol != part->actual_vol)) {
+ part->actual_vol = part->current_vol;
+ uint8_t pvol = part->actual_vol + fmp->fm_vol;
+ if (pvol & 0x80) {
+ pvol = (fmp->fm_vol & 0x80) ? 0 : 0x7f;
+ }
+ for (int s = 0; s < 4; s++) {
+ if (!(part->slot_vol_mask & (1<<s))) {
+ // 3024
+ uint8_t vol = part->u.fm.tone_tl[s];
+ vol -= part->u.fm.slot_rel_vol[s];
+ if (vol & 0x80) {
+ vol = (part->u.fm.slot_rel_vol[s] & 0x80) ? 0x7f : 0x00;
+ }
+ // 3039
+ if (!(part->u.fm.slot_mask & (1<<s))) {
+ fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, vol);
+ }
+ } else {
+ // 3042
+ uint8_t svol = pvol;
+ if (fmp->data_version >= 0x06) {
+ svol -= part->u.fm.slot_rel_vol[s];
+ if (svol & 0x80) {
+ svol = (part->u.fm.slot_rel_vol[s] & 0x80) ? 0x7f : 0x00;
+ }
+ // 305c
+ svol += part->u.fm.tone_tl[s];
+ if (svol & 0x80) svol = 0x7f;
+ }
+ // 3062
+ if (!(part->u.fm.slot_mask & (1<<s))) {
+ fmp_part_fm_reg_write(work, part, OPNA_TL+4*s, svol);
+ }
+ }
+ // 3069
+ }
+ }
+ // 3073
+ part->u.fm.alfo.delay_cnt = part->u.fm.alfo.delay;
+ part->u.fm.alfo.depth_cnt = part->u.fm.alfo.depth;
+ part->u.fm.alfo.rate = part->u.fm.alfo.rate_orig;
+ if (part->u.fm.hlfo_freq & 0x80) {
+ work->opna_writereg(work, 0x22, 0x00);
+ part->u.fm.hlfo_delay_cnt = part->u.fm.hlfo_delay;
+ if (!part->u.fm.hlfo_delay_cnt) {
+ // 309b
+ work->opna_writereg(work, 0x22, part->u.fm.hlfo_freq);
+ part->pan_ams_pms &= 0xc0;
+ part->pan_ams_pms |= part->u.fm.hlfo_apms;
+ fmp_part_write_pan(work, part);
+ }
+ }
+ // 30b0
+}
+
+static void fmp_part_pdzf_env(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ switch (part->pdzf.env_state.status) {
+ case PDZF_ENV_ATT:
+ if (!part->pdzf.env_state.cnt--) {
+ part->pdzf.env_state.vol = part->pdzf.env_param.dd;
+ part->pdzf.env_state.status = PDZF_ENV_DEC;
+ if (part->pdzf.env_state.vol > PDZF_ENV_VOL_MIN) {
+ part->pdzf.env_state.cnt = part->pdzf.env_param.sr;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_volume(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ fmp_pdzf_vol_clamp(part->pdzf.vol, part->pdzf.env_state.vol)
+ );
+ }
+ } else {
+ part->pdzf.env_state.vol = PDZF_ENV_VOL_MIN;
+ part->pdzf.env_state.status = PDZF_ENV_OFF;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_stop(
+ work->ppz8,
+ part->pdzf.ppz8_channel
+ );
+ }
+ }
+ }
+ break;
+ case PDZF_ENV_DEC:
+ if (!part->pdzf.env_param.sr) {
+ //part->pdzf.env_state.vol = PDZF_ENV_VOL_MIN;
+ } else if (!--part->pdzf.env_state.cnt) {
+ part->pdzf.env_state.vol--;
+ if (part->pdzf.env_state.vol > PDZF_ENV_VOL_MIN) {
+ part->pdzf.env_state.cnt = part->pdzf.env_param.sr;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_volume(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ fmp_pdzf_vol_clamp(part->pdzf.vol, part->pdzf.env_state.vol)
+ );
+ }
+ }
+ }
+ if (part->pdzf.env_state.vol <= PDZF_ENV_VOL_MIN) {
+ part->pdzf.env_state.vol = PDZF_ENV_VOL_MIN;
+ part->pdzf.env_state.status = PDZF_ENV_OFF;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_stop(
+ work->ppz8,
+ part->pdzf.ppz8_channel
+ );
+ }
+ }
+ break;
+ case PDZF_ENV_REL:
+ if (!--part->pdzf.env_state.cnt) {
+ part->pdzf.env_state.vol--;
+ if (part->pdzf.env_state.vol > PDZF_ENV_VOL_MIN) {
+ part->pdzf.env_state.cnt = part->pdzf.env_param.rr;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_volume(
+ work->ppz8,
+ part->pdzf.ppz8_channel,
+ fmp_pdzf_vol_clamp(part->pdzf.vol, part->pdzf.env_state.vol)
+ );
+ }
+ } else {
+ part->pdzf.env_state.vol = PDZF_ENV_VOL_MIN;
+ part->pdzf.env_state.status = PDZF_ENV_OFF;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_stop(
+ work->ppz8,
+ part->pdzf.ppz8_channel
+ );
+ }
+ }
+ }
+ break;
+ }
+}
+
+// 2ad9
+static void fmp_part_lfo(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ // 2ad9
+ if (part->lfo_f.p) fmp_part_lfo_calc(fmp, part, 0);
+ if (part->lfo_f.q) fmp_part_lfo_calc(fmp, part, 1);
+ if (part->lfo_f.r) fmp_part_lfo_calc(fmp, part, 2);
+ if (part->type.fm) {
+ // 2b03
+ if ((part->u.fm.hlfo_freq & 0x04) && part->u.fm.hlfo_delay_cnt) {
+ if (!--part->u.fm.hlfo_delay_cnt) {
+ fmp_part_hlfo(work, part);
+ }
+ }
+ // 2b17
+ if (part->lfo_f.w) {
+ fmp_part_wlfo(work, part);
+ }
+ // 2b79
+ if (part->lfo_f.a || part->lfo_f.e) {
+ // 2b7e
+ if (!part->u.fm.alfo.delay) return;
+ if (--part->u.fm.alfo.delay_cnt) return;
+ part->u.fm.alfo.delay_cnt = 1;
+ if (--part->u.fm.alfo.speed_cnt) return;
+ part->u.fm.alfo.speed_cnt = part->u.fm.alfo.speed;
+ uint8_t vol = part->actual_vol + part->u.fm.alfo.rate;
+ if (vol & 0x80) {
+ vol = (part->u.fm.alfo.rate & 0x80) ? 0x00 : 0x7f;
+ }
+ part->actual_vol = vol;
+ if (!part->lfo_f.e) {
+ // 2bb3
+ fmp_part_fm_vol(work, fmp, part);
+ if (--part->u.fm.alfo.depth_cnt) return;
+ part->u.fm.alfo.depth_cnt = part->u.fm.alfo.depth;
+ part->u.fm.alfo.rate = -part->u.fm.alfo.rate;
+ } else {
+ // 2bc5
+ fmp_part_keyoff_fm(work, fmp, part);
+ fmp_part_fm_vol(work, fmp, part);
+ fmp_part_keyon_fm(work, fmp, part);
+ part->u.fm.alfo.speed_cnt = part->u.fm.alfo.depth_cnt;
+ if (part->u.fm.alfo.speed == part->u.fm.alfo.speed_cnt) {
+ part->u.fm.alfo.depth_cnt = part->u.fm.alfo.depth;
+ } else {
+ part->u.fm.alfo.depth_cnt = part->u.fm.alfo.speed;
+ }
+ }
+ }
+ }
+}
+
+// 2a51
+static void fmp_part_fm(struct fmdriver_work *work, struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ if (part->status.lfo_sync) {
+ fmp_part_init_lfo_pqr(part);
+ fmp_part_init_lfo_awe(work, fmp, part);
+ part->actual_freq = fmp_fm_freq(part->prev_note);
+ part->status.lfo_sync = false;
+ }
+ // 2a6a
+ if (part->status.pitchbend || fmp->datainfo.flags.lfo_octave_fix) {
+ uint16_t fnum = part->actual_freq & 0x7ff;
+ if (fnum > 0x4d3u) {
+ part->actual_freq += 0x800;
+ part->actual_freq -= 0x269;
+ } else if (fnum < 0x26au) {
+ part->actual_freq -= 0x800;
+ part->actual_freq += 0x269;
+ if (part->actual_freq & 0x8000) {
+ part->actual_freq = 0x200;
+ }
+ }
+ }
+ // 2aa3
+ if ((part->actual_freq + part->detune) != part->prev_freq) {
+ part->prev_freq = part->actual_freq + part->detune;
+ if (part->type.fm_3 && (fmp->timer_ch3 & 0x40)) {
+ // 2be7
+ static const uint8_t ch3_fnum_addr[] = {
+ 0xad, 0xac, 0xae, 0xa6
+ };
+ static const uint8_t ch3_index[] = {
+ 0, 2, 1, 3
+ };
+ for (int i = 0; i < 4; i++) {
+ if (part->u.fm.slot_mask & (1<<i)) continue;
+ uint16_t freq = part->prev_freq + fmp->ch3_se_freqdiff[ch3_index[i]];
+ work->opna_writereg(work, ch3_fnum_addr[i], freq>>8);
+ work->opna_writereg(work, ch3_fnum_addr[i]-4, freq&0xff);
+ }
+ } else {
+ fmp_part_fm_reg_write(work, part, OPNA_BLKFNUM2, part->prev_freq>>8);
+ fmp_part_fm_reg_write(work, part, OPNA_FNUM1, part->prev_freq&0xff);
+ }
+ }
+ // 2ad0
+ if (part->status.keyon) {
+ fmp_part_keyon(work, fmp, part);
+ }
+ fmp_part_lfo(work, fmp, part);
+}
+
+// 2e80-2ed6
+static void fmp_part_ssg_env_adsr(struct fmp_part *part) {
+ // 2e80
+ if (!part->u.ssg.env_f.decay) {
+ unsigned newvol = part->actual_vol + part->u.ssg.env.attack_rate;
+ if (!(newvol & 0x100) && (part->u.ssg.vol > newvol)) {
+ part->actual_vol = newvol;
+ return;
+ }
+ // 2e96
+ part->actual_vol = part->u.ssg.vol;
+ part->u.ssg.env_f.decay = true;
+ }
+ // 2e9d
+ if (!part->u.ssg.env_f.sustain) {
+ unsigned newvol = part->actual_vol - part->u.ssg.env.decay_rate;
+ if (!(newvol & 0x100) && (part->u.ssg.env.sustain_lv < newvol)) {
+ part->actual_vol = newvol;
+ return;
+ }
+ // 2eb3
+ part->actual_vol = part->u.ssg.env.sustain_lv;
+ part->u.ssg.env_f.sustain = true;
+ }
+ // 2eba
+ if (part->u.ssg.env_f.release) return;
+ unsigned newvol = part->actual_vol - part->u.ssg.env.sustain_rate;
+ if (!(newvol & 0x100)) {
+ part->actual_vol = newvol;
+ return;
+ }
+ // 2ec8
+ part->actual_vol = 0;
+ part->u.ssg.env_f.release = true;
+ part->u.ssg.env_f.attack = false;
+}
+
+// 2e7a
+static void fmp_part_ssg_env(struct fmdriver_work *work,
+ struct fmp_part *part) {
+ if (part->u.ssg.env_f.attack) {
+ fmp_part_ssg_env_adsr(part);
+ } else {
+ // 2ed6
+ if (!part->u.ssg.env_f.portamento) {
+ //part->u.ssg.curr_vol = dh
+ return;
+ } else {
+ // 2edc
+ if (!part->u.ssg.env_f.release) {
+ // 2ee2
+ unsigned newvol = part->actual_vol - part->u.ssg.env.release_rate;
+ if (!(newvol & 0x100)) {
+ part->actual_vol = newvol;
+ } else {
+ part->actual_vol = 0;
+ part->u.ssg.env_f.release = true;
+ part->u.ssg.env_f.portamento = false;
+ }
+ }
+ }
+ }
+ // 2ef6
+ uint8_t outvol = ((part->actual_vol * part->current_vol) >> 8);
+ if (outvol == part->u.ssg.curr_vol) {
+ //part->u.ssg.curr_vol = dh
+ return;
+ }
+ // 2f03
+ work->opna_writereg(work, 0x8+part->opna_keyon_out, outvol);
+}
+
+// 2db0
+static void fmp_part_ssg(struct fmdriver_work *work, struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ if (part->status.lfo_sync) {
+ fmp_part_init_lfo_pqr(part);
+ part->u.ssg.octave = fmp_ssg_octave(part->prev_note);
+ part->actual_freq = fmp_part_ssg_freq(part, part->prev_note);
+ part->status.lfo_sync = false;
+ }
+ // 2dc9
+ if (part->status.keyon) {
+ fmp_part_keyon(work, fmp, part);
+ }
+ // 2dd2
+ if (part->u.ssg.env_f.ppz) return;
+ fmp_part_ssg_env(work, part);
+ uint16_t freq = part->actual_freq >> part->u.ssg.octave;
+ if (freq == part->prev_freq) {
+ // 2ad0
+ if (part->status.keyon) {
+ fmp_part_keyon(work, fmp, part);
+ }
+ } else {
+ part->prev_freq = freq;
+ if (!part->eff_chan) {
+ // 2df8
+ unsigned reg = part->opna_keyon_out*2;
+ work->opna_writereg(work, reg, freq&0xff);
+ work->opna_writereg(work, reg|1, freq>>8);
+ }
+ }
+ fmp_part_lfo(work, fmp, part);
+}
+
+// 2f61
+static void fmp_part_ssg_env_reset(struct fmp_part *part) {
+ part->u.ssg.env_f.decay = false;
+ part->u.ssg.env_f.sustain = false;
+ part->u.ssg.env_f.release = false;
+ uint8_t vol = part->u.ssg.env.startvol;
+ if (vol >= part->u.ssg.vol) {
+ part->u.ssg.env_f.decay = true;
+ vol = part->u.ssg.vol;
+ }
+ part->actual_vol = vol;
+ if (part->u.ssg.env.sustain_lv >= part->u.ssg.vol) {
+ part->u.ssg.env_f.sustain = true;
+ }
+}
+
+// 28b3
+static void fmp_adpcm_addr_set(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ if (part->lfo_f.portamento) return;
+ work->opna_writereg(work, 0x100, 0x00);
+ work->opna_writereg(work, 0x100, 0x01);
+ //work->opna_writereg(work, 0x110, 0x08);
+ //work->opna_writereg(work, 0x110, 0x80);
+ work->opna_writereg(work, 0x101, part->pan_ams_pms | fmp->adpcm_c1);
+ work->opna_writereg(work, 0x102, part->u.adpcm.startaddr & 0xff);
+ work->opna_writereg(work, 0x103, part->u.adpcm.startaddr >> 8);
+ work->opna_writereg(work, 0x104, part->u.adpcm.endaddr & 0xff);
+ work->opna_writereg(work, 0x105, part->u.adpcm.endaddr >> 8);
+}
+
+// 2853
+static void fmp_part_adpcm(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ if (!part->status.keyon) return;
+ part->ext_keyon = 0xffff;
+ part->status.keyon = false;
+ if (part->status.tie_cont) return;
+ fmp_adpcm_addr_set(work, fmp, part);
+ part->actual_freq = fmp_adpcm_freq(part->prev_note);
+ uint16_t freq = part->actual_freq;
+ // why does the detune byte has to be swapped??
+ freq += (part->detune >> 8) | ((part->detune << 8) & 0xff00);
+ freq += part->u.adpcm.deltat;
+ work->opna_writereg(work, 0x109, freq & 0xff);
+ work->opna_writereg(work, 0x10a, freq >> 8);
+ work->opna_writereg(work, 0x100, 0xa0);
+}
+
+// 2750
+static void fmp_part_keyon_pre(struct driver_fmp *fmp, struct fmp_part *part) {
+ // TODO: seproc
+ if (fmp->datainfo.flags.q) {
+ // 275e
+ uint8_t tonelen = part->tonelen_cnt;
+ unsigned shift = 0;
+ if (tonelen < 0x10) shift = 3;
+ tonelen <<= shift;
+ tonelen >>= 3;
+ tonelen *= part->gate_cnt;
+ tonelen >>= shift;
+ part->gate_cmp = part->tonelen_cnt - tonelen;
+ }
+ // 277f
+ part->status.keyon = true;
+ if (part->type.ssg && !part->status.tie_cont && !part->u.ssg.env_f.ppz) {
+ part->u.ssg.env_f.portamento = false;
+ if (!part->lfo_f.portamento) {
+ fmp_part_ssg_env_reset(part);
+ }
+ }
+}
+
+// 1cde
+static void fmp_part_pit_end_ssg(struct fmp_part *part) {
+ part->u.ssg.octave = fmp_ssg_octave(part->pit.target_note);
+ part->actual_freq = fmp_part_ssg_freq(part, part->pit.target_note);
+ part->pit.rate = 0;
+ part->status.pitchbend = false;
+ part->prev_note = part->pit.target_note;
+}
+
+// 1cc4
+static void fmp_part_pit_end_fm(struct fmp_part *part) {
+ uint8_t p_delay_cnt = part->lfo[0].delay_cnt;
+ uint8_t q_delay_cnt = part->lfo[1].delay_cnt;
+ uint8_t r_delay_cnt = part->lfo[2].delay_cnt;
+ fmp_part_init_lfo_pqr(part);
+ part->lfo[0].delay_cnt = p_delay_cnt;
+ part->lfo[1].delay_cnt = q_delay_cnt;
+ part->lfo[2].delay_cnt = r_delay_cnt;
+ // 1cdb
+ part->actual_freq = part->pit.target_freq;
+ part->pit.rate = 0;
+ part->status.pitchbend = false;
+ part->prev_note = part->pit.target_note;
+}
+
+// 1c4b
+static void fmp_part_pit(struct fmp_part *part) {
+ if (part->type.adpcm) return;
+ if (--part->pit.delay) return;
+ part->pit.delay = 1;
+ if (--part->pit.speed_cnt) return;
+ part->pit.speed_cnt = part->pit.speed;
+ uint16_t rate = part->pit.rate;
+ if (rate & 0x80) rate |= 0xff00;
+ if (!rate) return;
+ // 1c77
+ uint16_t freq = part->actual_freq + rate;
+ if (!(rate & 0x8000)) {
+ // rate positive
+ // 1c79
+ if (!part->type.fm) {
+ // 1c83
+ if ((freq >> part->u.ssg.octave) <= part->pit.target_freq) {
+ part->actual_freq = freq;
+ } else {
+ fmp_part_pit_end_ssg(part);
+ }
+ } else {
+ // 1c93
+ if (freq < part->pit.target_freq) {
+ part->actual_freq = freq;
+ } else {
+ fmp_part_pit_end_fm(part);
+ }
+ }
+ } else {
+ // rate negative
+ // 1ca5
+ if (!part->type.fm) {
+ if ((freq >> part->u.ssg.octave) >= part->pit.target_freq) {
+ part->actual_freq = freq;
+ } else {
+ fmp_part_pit_end_ssg(part);
+ }
+ } else {
+ // 1cbf
+ if (freq > part->pit.target_freq) {
+ part->actual_freq = freq;
+ } else {
+ fmp_part_pit_end_fm(part);
+ }
+ }
+ }
+ // 1cef
+}
+
+// 1d81
+static bool fmp_part_cmd_exec(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part,
+ uint8_t cmd) {
+ enum {
+ JMPTBL_LEN = 0x80-0x62
+ };
+ typedef bool (*cmdfunc_t)(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part);
+ static const cmdfunc_t fm_jmptbl[JMPTBL_LEN] = {
+ fmp_cmd62_tempo,
+ fmp_cmd63_vol_d_fm,
+ fmp_cmd64_loop,
+ fmp_cmd65_loopend,
+ fmp_cmd66_tie,
+ fmp_cmd67_q,
+ fmp_cmd68_pitchbend,
+ fmp_cmd69_vol_fm,
+ fmp_cmd6a_voldec_fm,
+ fmp_cmd6b_volinc_fm,
+ fmp_cmd6c_kondelay,
+ fmp_cmd6d_detune,
+ fmp_cmd6e_poke,
+ fmp_cmd6f_sync,
+ fmp_cmd70_wait,
+ fmp_cmd71_tone_fm,
+ fmp_cmd72_deflen,
+ fmp_cmd73_relvol_fm,
+ fmp_cmd74_loop,
+ fmp_cmd75_lfo,
+ fmp_cmd76_lfo_p,
+ fmp_cmd77_lfo_q,
+ fmp_cmd78_lfo_r,
+ fmp_cmd79_lfo_a_fm,
+ fmp_cmd7a_tie,
+ fmp_cmd7b_transpose,
+ fmp_cmd7c_lfo_pan_fm,
+ fmp_cmd7d_sync,
+ fmp_cmd7e_loop_det,
+ fmp_cmd7f_lfo_delay
+ };
+ static const cmdfunc_t ssg_jmptbl[JMPTBL_LEN] = {
+ fmp_cmd62_tempo,
+ fmp_cmd63_mix_ssg,
+ fmp_cmd64_loop,
+ fmp_cmd65_loopend,
+ fmp_cmd66_tie,
+ fmp_cmd67_q,
+ fmp_cmd68_pitchbend,
+ fmp_cmd69_vol_ssg,
+ fmp_cmd6a_voldec_ssg,
+ fmp_cmd6b_volinc_ssg,
+ fmp_cmd6c_kondelay,
+ fmp_cmd6d_detune,
+ fmp_cmd6e_poke,
+ fmp_cmd6f_sync,
+ fmp_cmd70_wait,
+ fmp_cmd71_envreset_ssg,
+ fmp_cmd72_deflen,
+ fmp_cmd73_noise_ssg,
+ fmp_cmd74_loop,
+ fmp_cmd75_lfo,
+ fmp_cmd76_lfo_p,
+ fmp_cmd77_lfo_q,
+ fmp_cmd78_lfo_r,
+ fmp_cmd79_env_ssg,
+ fmp_cmd7a_tie,
+ fmp_cmd7b_transpose,
+ fmp_cmd7c_tone_ssg,
+ fmp_cmd7d_sync,
+ fmp_cmd7e_loop_det,
+ fmp_cmd7f_lfo_delay
+ };
+ static const cmdfunc_t adpcm_jmptbl[JMPTBL_LEN] = {
+ fmp_cmd62_tempo,
+ fmp_cmd63_vol_d_adpcm,
+ fmp_cmd64_loop,
+ fmp_cmd65_loopend,
+ fmp_cmd66_tie,
+ fmp_cmd67_q,
+ fmp_cmd68_adpcm,
+ fmp_cmd69_adpcm,
+ fmp_cmd6a_voldec_adpcm,
+ fmp_cmd6b_volinc_adpcm,
+ fmp_cmd6c_kondelay,
+ fmp_cmd6d_detune,
+ fmp_cmd6e_poke,
+ fmp_cmd6f_sync,
+ fmp_cmd70_wait,
+ fmp_cmd71_tone_adpcm,
+ fmp_cmd72_deflen,
+ fmp_cmd73_relvol_adpcm,
+ fmp_cmd74_loop,
+ fmp_cmd75_lfo,
+ fmp_cmd76_lfo_p,
+ fmp_cmd77_lfo_q,
+ fmp_cmd78_lfo_r,
+ fmp_cmd79_lfo_a_fm,
+ fmp_cmd7a_tie,
+ fmp_cmd7b_transpose,
+ fmp_cmd7c_pan_adpcm,
+ fmp_cmd7d_sync,
+ fmp_cmd7e_loop_det,
+ fmp_cmd7f_lfo_delay
+ };
+ const cmdfunc_t *functable;
+ if (part->type.adpcm) {
+ functable = adpcm_jmptbl;
+ } else {
+ functable = part->type.ssg ? ssg_jmptbl : fm_jmptbl;
+ }
+ return functable[cmd-0x62](work, fmp, part);
+}
+
+static bool fmp_part_cmd_exec2(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part,
+ uint8_t cmd) {
+ if (!part->type.fm || (cmd > 0xe3)) {
+ part->current_ptr = 0xffff;
+ return false;
+ }
+
+ typedef bool (*cmdfunc_t)(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part);
+ static const cmdfunc_t fm_jmptbl2[2] = {
+ fmp_cmde2_slotrelvol_set_fm,
+ fmp_cmde3_slotrelvol_add_fm,
+ };
+ return fm_jmptbl2[cmd-0xe2](work, fmp, part);
+}
+
+// 2936
+static void fmp_part_keyoff_q(struct fmdriver_work *work,
+ struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ if (part->type.adpcm) {
+ part->ext_keyon = 0;
+ part->status.tie_cont = false;
+ work->opna_writereg(work, 0x100, 0x00);
+ work->opna_writereg(work, 0x100, 0x01);
+ return;
+ }
+ if (part->type.fm || !part->u.ssg.env_f.ppz) {
+ // 2961
+ part->ext_keyon = 0;
+ part->status.tie_cont = false;
+ if (part->type.adpcm) return;
+ if (!part->type.ssg) {
+ // 2979
+ uint8_t keyon = part->opna_keyon_out;
+ if (part->type.fm_3) {
+ // 2994
+ fmp->fm3_slot_keyon &= (part->u.fm.slot_mask | 0x0f);
+ keyon |= fmp->fm3_slot_keyon;
+ }
+ work->opna_writereg(work, 0x28, keyon);
+ // 29a2
+ } else {
+ // 29a9
+ part->u.ssg.curr_vol = 0xff;
+ if (part->u.ssg.env.release_rate == 0) {
+ part->actual_vol = 0;
+ }
+ part->u.ssg.env_f.portamento = true;
+ part->u.ssg.env_f.attack = false;
+ }
+ } else {
+ // 294d
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_volume(
+ work->ppz8,
+ part->opna_keyon_out,
+ 0
+ );
+ }
+ }
+ if (part->pdzf.mode) {
+ part->pdzf.keyon = false;
+ if (part->lfo_f.q && part->pdzf.env_param.rr) {
+ part->pdzf.env_state.status = PDZF_ENV_REL;
+ part->pdzf.env_state.cnt = part->pdzf.env_param.rr;
+ } else {
+ part->pdzf.env_state.status = PDZF_ENV_OFF;
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->channel_stop(
+ work->ppz8,
+ part->pdzf.ppz8_channel
+ );
+ }
+ }
+ }
+}
+
+// 1c0d
+static void fmp_part_cmd(struct fmdriver_work *work, struct driver_fmp *fmp,
+ struct fmp_part *part) {
+ if (part->sync > 1) return;
+ if (part->sync == 1) {
+ // 1c19
+ if (part->keyon_delay_cnt) {
+ if (!--part->keyon_delay_cnt) {
+ fmp_part_keyon_pre(fmp, part);
+ }
+ }
+ // 1c27
+ if ((!part->status.tie) && (!part->status.rest)) {
+ if (part->tonelen_cnt && (part->tonelen_cnt == part->gate_cmp)) {
+ fmp_part_keyoff_q(work, fmp, part);
+ }
+ }
+ // 1c42
+ if (part->status.pitchbend) {
+ fmp_part_pit(part);
+ }
+ // 1cef
+ if (--part->tonelen_cnt) return;
+ // 1cf5
+ if (!part->status.tie) {
+ fmp_part_keyoff(work, fmp, part);
+ part->status.pitchbend = false;
+ } else {
+ // 1d04
+ part->status.slur = false;
+ part->status.tie = false;
+ part->status.tie_cont = true;
+ }
+ }
+ // 1d0c
+ part->sync = 1;
+ // 1d10
+ for (;;) {
+ uint8_t cmd = fmp_part_cmdload(fmp, part);
+ uint8_t len;
+ if (!(cmd & 0x80)) {
+ // 1d1a
+ if (cmd >= 0x62) {
+ if (!fmp_part_cmd_exec(work, fmp, part, cmd)) return;
+ continue;
+ } else {
+ // 1d23
+ len = fmp_part_cmdload(fmp, part);
+ }
+ } else {
+ // 1d26
+ cmd &= 0x7f;
+ if (cmd >= 0x62) {
+ if (!fmp_part_cmd_exec2(work, fmp, part, cmd)) return;
+ continue;
+ } else {
+ // 1d33
+ len = part->default_len;
+ }
+ }
+ // 1d36
+ // note
+ part->tonelen_cnt = len;
+ if (cmd == 0x61) {
+ // 1d3e
+ part->status.tie = false;
+ part->status.pitchbend = false;
+ part->status.tie_cont = false;
+ part->status.rest = true;
+ fmp_part_keyoff(work, fmp, part);
+ break;
+ } else {
+ // 1d4d
+ cmd += part->note_diff;
+ part->status.rest = false;
+ part->keyon_delay_cnt = part->keyon_delay;
+ if (!part->keyon_delay) {
+ fmp_part_keyon_pre(fmp, part);
+ }
+ // 1d61
+ if (!part->status.tie_cont || cmd != part->prev_note) {
+ // 1d6c
+ part->status.lfo_sync = true;
+ part->status.tie_cont = false;
+ if (part->status.slur) {
+ fmp_part_keyoff(work, fmp, part);
+ }
+ }
+ // 1d7d
+ part->prev_note = cmd;
+ break;
+ }
+ }
+}
+
+// 269b
+static void fmp_part_pan_vol_rhythm(struct fmdriver_work *work,
+ struct fmp_rhythm *rhythm,
+ int part) {
+ work->opna_writereg(work, 0x18+part,
+ rhythm->pans[part] | rhythm->volumes[part]);
+}
+
+// 2619
+static void fmp_part_cmd_rhythm(struct fmdriver_work *work,
+ struct driver_fmp *fmp) {
+ struct fmp_rhythm *rhythm = &fmp->rhythm;
+ struct fmp_part *part = &rhythm->part;
+ if (rhythm->sync > 1) return;
+ if (rhythm->sync == 1) {
+ // 2623
+ if (--rhythm->len_cnt) return;
+ }
+ // 262a
+ for (;;) {
+ rhythm->sync = 1;
+ uint8_t cmd = fmp_part_cmdload_rhythm(fmp, part);
+ if (cmd & 0x80) {
+ if (cmd & 0x40) {
+ // 26a5
+ rhythm->tl_volume = cmd & 0x3f;
+ work->opna_writereg(work, 0x11, rhythm->tl_volume);
+ } else if (cmd & 0x20) {
+ // 26be
+ int pan = (cmd & 0x18) << 3;
+ rhythm->pans[cmd&0x07] = pan;
+ fmp_part_pan_vol_rhythm(work, rhythm, cmd&0x7);
+ } else if (cmd == 0x90) {
+ // 26d4
+ rhythm->default_len = fmp_part_cmdload(fmp, part);
+ } else if (cmd == 0x91) {
+ // 26db
+ fmp_cmd64_loop(work, fmp, part);
+ } else if (cmd == 0x92) {
+ // 26e2
+ fmp_cmd65_loopend(work, fmp, part);
+ } else if (cmd == 0x93) {
+ // 26e9
+ if (!fmp_cmd74_loop(work, fmp, part)) return;
+ } else if (cmd == 0x94) {
+ // 26ef
+ if (rhythm->tl_volume < 0x3f) {
+ rhythm->tl_volume++;
+ work->opna_writereg(work, 0x11, rhythm->tl_volume);
+ }
+ } else if (cmd == 0x95) {
+ // 26fb
+ if (rhythm->tl_volume) {
+ rhythm->tl_volume--;
+ work->opna_writereg(work, 0x11, rhythm->tl_volume);
+ }
+ } else {
+ switch (cmd & 0xf8) {
+ case 0x80:
+ default:
+ // 268b
+ rhythm->volumes[cmd&0x7] = fmp_part_cmdload(fmp, part);
+ fmp_part_pan_vol_rhythm(work, rhythm, cmd&0x7);
+ break;
+ case 0x88:
+ // 2707
+ {
+ uint8_t vol = rhythm->volumes[cmd&0x7] + 1;
+ if (vol & 0xe0) vol = 0x1f;
+ rhythm->volumes[cmd&0x7] = vol;
+ }
+ fmp_part_pan_vol_rhythm(work, rhythm, cmd&0x7);
+ break;
+ case 0x98:
+ // 2719
+ {
+ uint8_t vol = rhythm->volumes[cmd&0x7] - 1;
+ if (vol & 0xe0) vol = 0;
+ rhythm->volumes[cmd&0x7] = vol;
+ }
+ fmp_part_pan_vol_rhythm(work, rhythm, cmd&0x7);
+ break;
+ }
+ }
+ } else {
+ work->opna_writereg(work, 0x10, cmd & rhythm->mask);
+ if (fmp->pdzf.mode == 2) {
+ uint8_t pdzf_keyon = (cmd & 0x3f) & rhythm->mask;
+ bool do_keyon = false;
+ int p;
+ for (p = 0; p < 6; p++) {
+ if ((pdzf_keyon & (1<<p)) && (!rhythm->pans[p]) && fmp->pdzf.rhythm[p&1].enabled) {
+ do_keyon = true;
+ break;
+ }
+ }
+ if (do_keyon) {
+ if (work->ppz8_functbl) {
+ uint8_t volume = rhythm->volumes[p] & 0x0f;
+ uint8_t voice = fmp->pdzf.rhythm[p&1].voice[((rhythm->volumes[p] & 0x10) >> 4)];
+ uint8_t pan = fmp->pdzf.rhythm[p&1].pan + 5;
+ uint32_t freq = fmp_ppz8_note_freq(fmp->pdzf.rhythm[p&1].note);
+ uint8_t channel = 6;
+ work->ppz8_functbl->channel_play(
+ work->ppz8,
+ channel,
+ voice
+ );
+ work->ppz8_functbl->channel_volume(
+ work->ppz8,
+ channel,
+ volume
+ );
+ work->ppz8_functbl->channel_pan(
+ work->ppz8,
+ channel,
+ pan
+ );
+ work->ppz8_functbl->channel_freq(
+ work->ppz8,
+ channel,
+ freq
+ );
+ }
+ }
+ }
+ rhythm->len_cnt = (cmd & 0x40) ?
+ rhythm->default_len : fmp_part_cmdload(fmp, part);
+ return;
+ }
+ }
+}
+
+// 17f8-1903
+static void fmp_timerb(struct fmdriver_work *work, struct driver_fmp *fmp) {
+ // 1805
+ fmp_set_tempo(work, fmp);
+
+ // 1813
+ if (fmp->status.stopped) {
+ // TODO: stopped
+ // jmp 18c7
+ }
+ // 1829
+ if (!--fmp->clock_divider) {
+ fmp->total_clocks++;
+ fmp->clock_divider = 10;
+ }
+ // 1840
+ for (int p = 0; p < 6; p++) {
+ struct fmp_part *part = &fmp->parts[FMP_PART_FM_1+p];
+ if (part->status.off) continue;
+ fmp_part_cmd(work, fmp, part);
+ fmp_part_fm(work, fmp, part);
+ }
+ {
+ struct fmp_part *part = &fmp->parts[FMP_PART_ADPCM];
+ if (!part->status.off) {
+ fmp_part_cmd(work, fmp, part);
+ fmp_part_adpcm(work, fmp, part);
+ }
+ }
+ for (int p = 0; p < 3; p++) {
+ struct fmp_part *part = &fmp->parts[FMP_PART_SSG_1+p];
+ if (part->status.off) continue;
+ fmp_part_cmd(work, fmp, part);
+ fmp_part_ssg(work, fmp, part);
+ if (part->pdzf.mode && part->lfo_f.q) {
+ fmp_part_pdzf_env(work, fmp, part);
+ }
+ }
+ // 187d
+ for (int p = 0; p < 3; p++) {
+ struct fmp_part *part = &fmp->parts[FMP_PART_FM_EX1+p];
+ if (part->status.off) continue;
+ fmp_part_cmd(work, fmp, part);
+ if (part->status.off) continue;
+ fmp_part_fm(work, fmp, part);
+ if (part->pdzf.mode && part->lfo_f.q) {
+ fmp_part_pdzf_env(work, fmp, part);
+ }
+ }
+ if (!fmp->rhythm.status) {
+ fmp_part_cmd_rhythm(work, fmp);
+ }
+}
+
+static void fmp_init_parts(struct fmdriver_work *work,
+ struct driver_fmp *fmp) {
+ const uint8_t deflen = fmp->datainfo.bar >> 2;
+ // 3ae7
+ // FM1, 2, 3, SSG1, 2, 3
+ for (int i = 0; i < 3; i++) {
+ struct fmp_part *fpart = &fmp->parts[FMP_PART_FM_1+i];
+ struct fmp_part *spart = &fmp->parts[FMP_PART_SSG_1+i];
+ spart->default_len = deflen;
+ fpart->default_len = deflen;
+ spart->gate_cmp = 1;
+ fpart->gate_cmp = 1;
+ spart->gate_cnt = 8;
+ fpart->gate_cnt = 8;
+ spart->u.ssg.env.startvol = 0xff;
+ spart->u.ssg.env.attack_rate = 0xff;
+ spart->u.ssg.env.decay_rate = 0xff;
+ spart->u.ssg.env.sustain_lv = 0xff;
+ spart->u.ssg.env.release_rate = 0x0a;
+ fpart->current_vol = 0x1a;
+ fpart->actual_vol = 0x1a;
+ spart->current_vol = 0x0e;
+ }
+ // FM4, 5, 6, ADPCM
+ for (int i = 0; i < 4; i++) {
+ struct fmp_part *part = &fmp->parts[FMP_PART_FM_4+i];
+ part->default_len = deflen;
+ part->gate_cmp = 1;
+ part->gate_cnt = 8;
+ part->current_vol = 0x1a;
+ part->actual_vol = 0x1a;
+ }
+ {
+ struct fmp_part *part = &fmp->parts[FMP_PART_ADPCM];
+ part->default_len = deflen;
+ part->gate_cmp = 1;
+ part->gate_cnt = 8;
+ part->current_vol = 0x1a;
+ part->actual_vol = 0x1a;
+ }
+ // FMEX1, 2, 3
+ for (int i = 0; i < 3; i++) {
+ struct fmp_part *part = &fmp->parts[FMP_PART_FM_EX1+i];
+ part->default_len = deflen;
+ part->gate_cmp = 1;
+ part->gate_cnt = 8;
+ part->current_vol = 0x1a;
+ part->actual_vol = 0x1a;
+ part->u.fm.slot_mask = 0xff;
+ part->pan_ams_pms = 0xc0;
+ }
+ // 3b5f
+ fmp->rhythm.status = false;
+ fmp->rhythm.len_cnt = 1;
+ fmp->rhythm.default_len = deflen;
+ //fmp->rhythm.loop_now
+ fmp->rhythm.mask = 0x3f;
+ fmp->rhythm.tl_volume = 0x3c;
+ for (int i = 0; i < 6; i++) {
+ fmp->rhythm.volumes[i] = 0x1c;
+ fmp->rhythm.pans[i] = 0xc0;
+ }
+ // couldn't find where this is written,
+ // but this must be necessary
+ work->opna_writereg(work, 0x11, fmp->rhythm.tl_volume);
+ for (int i = 0; i < 6; i++) {
+ fmp_part_pan_vol_rhythm(work, &fmp->rhythm, i);
+ }
+
+ {
+ // 3b86
+ struct fmp_part *part = &fmp->parts[FMP_PART_ADPCM];
+ part->current_vol = 0x80;
+ part->actual_vol = 0x80;
+ part->gate_cmp = 0;
+ work->opna_writereg(work, 0x10b, 0x80);
+ fmp_adpcm_pan(work, fmp, part, 0xc0);
+ }
+ // opna flag mask, reset
+ // work->opna_writereg(work, 0x110, 0x1c);
+ // work->opna_writereg(work, 0x110, 0x80);
+
+ fmp->timerb = 0xca;
+ fmp->timerb_bak = 0xca;
+
+ // 3c79
+ for (int i = 0; i < 6; i++) {
+ fmp->parts[FMP_PART_FM_1+i].current_ptr
+ = fmp->datainfo.partptr[FMP_DATA_FM_1+i];
+ fmp->parts[FMP_PART_FM_1+i].loop_ptr
+ = fmp->datainfo.loopptr[FMP_DATA_FM_1+i];
+ }
+ // 3ca3
+ for (int i = 0; i < 3; i++) {
+ fmp->parts[FMP_PART_SSG_1+i].current_ptr
+ = fmp->datainfo.partptr[FMP_DATA_SSG_1+i];
+ fmp->parts[FMP_PART_SSG_1+i].loop_ptr
+ = fmp->datainfo.loopptr[FMP_DATA_SSG_1+i];
+ }
+ fmp->rhythm.part.current_ptr
+ = fmp->datainfo.partptr[FMP_DATA_RHYTHM];
+ fmp->rhythm.part.loop_ptr
+ = fmp->datainfo.loopptr[FMP_DATA_RHYTHM];
+
+ fmp->parts[FMP_PART_ADPCM].current_ptr
+ = fmp->datainfo.partptr[FMP_DATA_ADPCM];
+ fmp->parts[FMP_PART_ADPCM].loop_ptr
+ = fmp->datainfo.loopptr[FMP_DATA_ADPCM];
+
+ // 3d06
+ for (int i = 0; i < 3; i++) {
+ fmp->parts[FMP_PART_FM_EX1+i].current_ptr
+ = fmp->datainfo.partptr[FMP_DATA_FM_EX1+i];
+ fmp->parts[FMP_PART_FM_EX1+i].loop_ptr
+ = fmp->datainfo.loopptr[FMP_DATA_FM_EX1+i];
+ }
+
+ // 3d2d
+ fmp->part_playing_bit = 0x07ff;
+ fmp->part_loop_bit = 0x07ff;
+ // TODO: other status bit
+ fmp->status.looped = false;
+ fmp->status.stopped = false;
+ fmp->total_clocks = 0;
+ fmp->clock_divider = 10;
+ // 1b64
+
+ // 3c36
+ /*
+ if (work->ppz8_functbl) {
+ work->ppz8_functbl->total_volume(work->ppz8,
+ }
+ */
+ fmp_fm_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_FM_EX1]);
+ fmp_fm_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_FM_EX2]);
+ fmp_fm_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_FM_EX3]);
+ fmp_ssg_ppz8_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_SSG_1]);
+ fmp_ssg_ppz8_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_SSG_2]);
+ fmp_ssg_ppz8_pdzf_mode_update(work, fmp, &fmp->parts[FMP_PART_SSG_3]);
+}
+
+static void fmp_struct_init(struct fmdriver_work *work,
+ struct driver_fmp *fmp) {
+ // TODO
+ fmp->pdzf.mode = 2;
+ // 4e87
+ fmp->ssg_mix = 0x38;
+ // 3bb7
+ work->opna_writereg(work, 0x07, fmp->ssg_mix);
+
+ // 5373
+ // enable OPNA 4-6
+ work->opna_writereg(work, 0x29, 0x83);
+ // stop rhythm
+ work->opna_writereg(work, 0x10, 0xbf);
+ // reset ADPCM
+ work->opna_writereg(work, 0x100, 0x21);
+ work->opna_writereg(work, 0x101, 0x02);
+ //
+ fmp->adpcm_c1 = 0x02;
+
+ // 5394
+ // on OPNA, increase volume by 8
+ // because OPNA's FM output is 6db lower than OPN
+ fmp->fm_vol = -8;
+
+ // 53ca
+ // TODO: part at 0x1426
+ // 5408
+ fmp->parts[FMP_PART_SSG_1].type.ssg = true;
+ fmp->parts[FMP_PART_SSG_2].type.ssg = true;
+ fmp->parts[FMP_PART_SSG_3].type.ssg = true;
+ fmp->parts[FMP_PART_SSG_1].opna_keyon_out = 0;
+ fmp->parts[FMP_PART_SSG_2].opna_keyon_out = 1;
+ fmp->parts[FMP_PART_SSG_3].opna_keyon_out = 2;
+ fmp->parts[FMP_PART_SSG_1].part_bit = 0x0040;
+ fmp->parts[FMP_PART_SSG_2].part_bit = 0x0080;
+ fmp->parts[FMP_PART_SSG_3].part_bit = 0x0100;
+ // 5479
+ fmp->parts[FMP_PART_FM_1].type.fm = true;
+ fmp->parts[FMP_PART_FM_2].type.fm = true;
+ fmp->parts[FMP_PART_FM_3].type.fm = true;
+ fmp->parts[FMP_PART_FM_3].type.fm_3 = true;
+ fmp->parts[FMP_PART_FM_4].type.fm = true;
+ fmp->parts[FMP_PART_FM_5].type.fm = true;
+ fmp->parts[FMP_PART_FM_6].type.fm = true;
+ fmp->parts[FMP_PART_FM_1].opna_keyon_out = 0x00;
+ fmp->parts[FMP_PART_FM_2].opna_keyon_out = 0x01;
+ fmp->parts[FMP_PART_FM_3].opna_keyon_out = 0x02;
+ fmp->parts[FMP_PART_FM_4].opna_keyon_out = 0x04;
+ fmp->parts[FMP_PART_FM_5].opna_keyon_out = 0x05;
+ fmp->parts[FMP_PART_FM_6].opna_keyon_out = 0x06;
+ fmp->parts[FMP_PART_FM_1].part_bit = 0x0001;
+ fmp->parts[FMP_PART_FM_2].part_bit = 0x0002;
+ fmp->parts[FMP_PART_FM_3].part_bit = 0x0004;
+ fmp->parts[FMP_PART_FM_4].part_bit = 0x0008;
+ fmp->parts[FMP_PART_FM_5].part_bit = 0x0010;
+ fmp->parts[FMP_PART_FM_6].part_bit = 0x0020;
+ // 5502
+ fmp->parts[FMP_PART_ADPCM].type.adpcm = true;
+ fmp->parts[FMP_PART_ADPCM].part_bit = 0x400;
+ fmp->parts[FMP_PART_FM_EX1].type.fm = true;
+ fmp->parts[FMP_PART_FM_EX1].type.fm_3 = true;
+ fmp->parts[FMP_PART_FM_EX2].type.fm = true;
+ fmp->parts[FMP_PART_FM_EX2].type.fm_3 = true;
+ fmp->parts[FMP_PART_FM_EX3].type.fm = true;
+ fmp->parts[FMP_PART_FM_EX3].type.fm_3 = true;
+ fmp->parts[FMP_PART_FM_EX1].opna_keyon_out = 0x02;
+ fmp->parts[FMP_PART_FM_EX2].opna_keyon_out = 0x02;
+ fmp->parts[FMP_PART_FM_EX3].opna_keyon_out = 0x02;
+ fmp->parts[FMP_PART_FM_EX1].part_bit = 0x0800;
+ fmp->parts[FMP_PART_FM_EX2].part_bit = 0x1000;
+ fmp->parts[FMP_PART_FM_EX3].part_bit = 0x2000;
+ fmp->rhythm.part.type.rhythm = true;
+ fmp->rhythm.part.part_bit = 0x0200;
+ // 5625
+ work->opna_writereg(work, 0x27, 0x30);
+
+ // pdzf
+ fmp->parts[FMP_PART_SSG_1].pdzf.ppz8_channel = 0;
+ fmp->parts[FMP_PART_SSG_1].pdzf.loopstart32 = -1;
+ fmp->parts[FMP_PART_SSG_1].pdzf.loopend32 = -1;
+ fmp->parts[FMP_PART_SSG_2].pdzf.ppz8_channel = 1;
+ fmp->parts[FMP_PART_SSG_2].pdzf.loopstart32 = -1;
+ fmp->parts[FMP_PART_SSG_2].pdzf.loopend32 = -1;
+ fmp->parts[FMP_PART_SSG_3].pdzf.ppz8_channel = 2;
+ fmp->parts[FMP_PART_SSG_3].pdzf.loopstart32 = -1;
+ fmp->parts[FMP_PART_SSG_3].pdzf.loopend32 = -1;
+ fmp->parts[FMP_PART_FM_EX1].pdzf.ppz8_channel = 3;
+ fmp->parts[FMP_PART_FM_EX1].pdzf.loopstart32 = -1;
+ fmp->parts[FMP_PART_FM_EX1].pdzf.loopend32 = -1;
+ fmp->parts[FMP_PART_FM_EX2].pdzf.ppz8_channel = 4;
+ fmp->parts[FMP_PART_FM_EX2].pdzf.loopstart32 = -1;
+ fmp->parts[FMP_PART_FM_EX2].pdzf.loopend32 = -1;
+ fmp->parts[FMP_PART_FM_EX3].pdzf.ppz8_channel = 5;
+ fmp->parts[FMP_PART_FM_EX3].pdzf.loopstart32 = -1;
+ fmp->parts[FMP_PART_FM_EX3].pdzf.loopend32 = -1;
+}
+
+// 1774
+static void fmp_opna_interrupt(struct fmdriver_work *work) {
+ struct driver_fmp *fmp = (struct driver_fmp *)work->driver;
+ if (work->opna_status(work, 0) & 0x02) {
+ fmp_timerb(work, fmp);
+ }
+}
+
+static void fmp_title(struct fmdriver_work *work,
+ uint8_t *data, uint16_t datalen,
+ uint16_t offset) {
+ for (unsigned i = 0; ; i++) {
+ int newline = 0;
+ if ((offset + i) >= datalen) return;
+ //if (i > 80*3*2) return;
+ //if (data[offset+i] == 0x0d)
+ if (data[offset+i] == 0) break;
+ /*
+ if ((data[offset+i] == 0x0d) || (data[offset+i] == 0x0a)){
+ data[offset+i] = 0;
+ break;
+ }
+ */
+ }
+ work->title = data+offset;
+}
+
+bool fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp,
+ uint8_t *data, uint16_t datalen)
+{
+ uint16_t offset = read16le(data);
+ if ((offset+4) > datalen) {
+ FMDRIVER_DEBUG("invalid offset value\n");
+ return false;
+ }
+ uint8_t dataflags;
+ bool pviname_valid;
+ if ((data[offset] == 'F') && (data[offset+1] == 'M') &&
+ (data[offset+2] == 'C')) {
+ FMDRIVER_DEBUG("FMP data\n");
+ uint8_t dataver = data[offset+3];
+ fmp->data_version = dataver & 0x0f;
+ if ((dataver & 0xf) >= 0x8) {
+ FMDRIVER_DEBUG("call word 4152\n");
+ }
+ if (dataver <= 0x29) {
+ FMDRIVER_DEBUG("format:1\n");
+ if (datalen < 0x1au) {
+ FMDRIVER_DEBUG("file length shorter than header\n");
+ return false;
+ }
+ fmp->datainfo.partptr[FMP_DATA_FM_1] = read16le(&data[0x02]);
+ fmp->datainfo.partptr[FMP_DATA_FM_2] = read16le(&data[0x04]);
+ fmp->datainfo.partptr[FMP_DATA_FM_3] = read16le(&data[0x06]);
+ fmp->datainfo.partptr[FMP_DATA_FM_4] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_5] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_6] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_SSG_1] = read16le(&data[0x08]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_2] = read16le(&data[0x0a]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_3] = read16le(&data[0x0c]);
+ fmp->datainfo.partptr[FMP_DATA_RHYTHM] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_ADPCM] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_EX1] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_EX2] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_EX3] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_1] = read16le(&data[0x0e]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_2] = read16le(&data[0x10]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_3] = read16le(&data[0x12]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_4] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_5] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_6] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_SSG_1] = read16le(&data[0x14]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_2] = read16le(&data[0x16]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_3] = read16le(&data[0x18]);
+ fmp->datainfo.loopptr[FMP_DATA_RHYTHM] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_ADPCM] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX1] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX2] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX3] = 0xffff;
+ fmp->datainfo.bar = data[0x1a];
+ dataflags = data[0x1b];
+ fmp->datainfo.adpcmptr = 0xffff;
+ fmp->datainfo.fmtoneptr = 0x1c;
+ pviname_valid = false;
+ } else if (dataver <= 0x49) {
+ FMDRIVER_DEBUG("format:2\n");
+ if (datalen < 0x2eu) {
+ FMDRIVER_DEBUG("file length shorter than header\n");
+ return false;
+ }
+ fmp->datainfo.partptr[FMP_DATA_FM_1] = read16le(&data[0x02]);
+ fmp->datainfo.partptr[FMP_DATA_FM_2] = read16le(&data[0x04]);
+ fmp->datainfo.partptr[FMP_DATA_FM_3] = read16le(&data[0x06]);
+ fmp->datainfo.partptr[FMP_DATA_FM_4] = read16le(&data[0x08]);
+ fmp->datainfo.partptr[FMP_DATA_FM_5] = read16le(&data[0x0a]);
+ fmp->datainfo.partptr[FMP_DATA_FM_6] = read16le(&data[0x0c]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_1] = read16le(&data[0x0e]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_2] = read16le(&data[0x10]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_3] = read16le(&data[0x12]);
+ fmp->datainfo.partptr[FMP_DATA_RHYTHM] = read16le(&data[0x14]);
+ fmp->datainfo.partptr[FMP_DATA_ADPCM] = read16le(&data[0x16]);
+ fmp->datainfo.partptr[FMP_DATA_FM_EX1] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_EX2] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_EX3] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_1] = read16le(&data[0x18]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_2] = read16le(&data[0x1a]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_3] = read16le(&data[0x1c]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_4] = read16le(&data[0x1e]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_5] = read16le(&data[0x20]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_6] = read16le(&data[0x22]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_1] = read16le(&data[0x24]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_2] = read16le(&data[0x26]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_3] = read16le(&data[0x28]);
+ fmp->datainfo.loopptr[FMP_DATA_RHYTHM] = read16le(&data[0x2a]);
+ fmp->datainfo.loopptr[FMP_DATA_ADPCM] = read16le(&data[0x2c]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX1] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX2] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX3] = 0xffff;
+ fmp->datainfo.bar = data[0x2e];
+ dataflags = data[0x2f];
+ fmp->datainfo.adpcmptr = read16le(&data[0x30]);
+ fmp->datainfo.fmtoneptr = 0x32;
+ pviname_valid = true;
+ } else if (dataver <= 0x69) {
+ FMDRIVER_DEBUG("format:3\n");
+ if (datalen < 0x5eu) {
+ FMDRIVER_DEBUG("file length shorter than header\n");
+ return false;
+ }
+ fmp->datainfo.partptr[FMP_DATA_FM_1] = read16le(&data[0x02]);
+ fmp->datainfo.partptr[FMP_DATA_FM_2] = read16le(&data[0x04]);
+ fmp->datainfo.partptr[FMP_DATA_FM_3] = read16le(&data[0x06]);
+ fmp->datainfo.partptr[FMP_DATA_FM_4] = read16le(&data[0x08]);
+ fmp->datainfo.partptr[FMP_DATA_FM_5] = read16le(&data[0x0a]);
+ fmp->datainfo.partptr[FMP_DATA_FM_6] = read16le(&data[0x0c]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_1] = read16le(&data[0x0e]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_2] = read16le(&data[0x10]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_3] = read16le(&data[0x12]);
+ fmp->datainfo.partptr[FMP_DATA_RHYTHM] = read16le(&data[0x14]);
+ fmp->datainfo.partptr[FMP_DATA_ADPCM] = read16le(&data[0x16]);
+ fmp->datainfo.partptr[FMP_DATA_FM_EX1] = read16le(&data[0x18]);
+ fmp->datainfo.partptr[FMP_DATA_FM_EX2] = read16le(&data[0x1a]);
+ fmp->datainfo.partptr[FMP_DATA_FM_EX3] = read16le(&data[0x1c]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_1] = read16le(&data[0x30]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_2] = read16le(&data[0x32]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_3] = read16le(&data[0x34]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_4] = read16le(&data[0x36]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_5] = read16le(&data[0x38]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_6] = read16le(&data[0x3a]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_1] = read16le(&data[0x3c]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_2] = read16le(&data[0x3e]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_3] = read16le(&data[0x40]);
+ fmp->datainfo.loopptr[FMP_DATA_RHYTHM] = read16le(&data[0x42]);
+ fmp->datainfo.loopptr[FMP_DATA_ADPCM] = read16le(&data[0x44]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX1] = read16le(&data[0x46]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX2] = read16le(&data[0x48]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX3] = read16le(&data[0x4a]);
+ fmp->datainfo.bar = data[0x5e];
+ dataflags = data[0x5f];
+ fmp->datainfo.adpcmptr = read16le(&data[0x60]);
+ fmp->datainfo.fmtoneptr = 0x66;
+ pviname_valid = true;
+ } else {
+ FMDRIVER_DEBUG("invalid format information\n");
+ return false;
+ }
+ } else if ((data[offset] == 'E') && (data[offset+1] == 'L') &&
+ (data[offset+2] == 'F')) {
+ FMDRIVER_DEBUG("PLAY6 data\n");
+ if (datalen < 0x2au) {
+ FMDRIVER_DEBUG("file length shorter than header\n");
+ return false;
+ }
+ fmp->data_version = 0x07;
+ fmp->datainfo.partptr[FMP_DATA_FM_1] = read16le(&data[0x02]);
+ fmp->datainfo.partptr[FMP_DATA_FM_2] = read16le(&data[0x04]);
+ fmp->datainfo.partptr[FMP_DATA_FM_3] = read16le(&data[0x06]);
+ fmp->datainfo.partptr[FMP_DATA_FM_4] = read16le(&data[0x08]);
+ fmp->datainfo.partptr[FMP_DATA_FM_5] = read16le(&data[0x0a]);
+ fmp->datainfo.partptr[FMP_DATA_FM_6] = read16le(&data[0x0c]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_1] = read16le(&data[0x0e]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_2] = read16le(&data[0x10]);
+ fmp->datainfo.partptr[FMP_DATA_SSG_3] = read16le(&data[0x12]);
+ fmp->datainfo.partptr[FMP_DATA_RHYTHM] = read16le(&data[0x14]);
+ fmp->datainfo.partptr[FMP_DATA_ADPCM] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_EX1] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_EX2] = 0xffff;
+ fmp->datainfo.partptr[FMP_DATA_FM_EX3] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_1] = read16le(&data[0x16]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_2] = read16le(&data[0x18]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_3] = read16le(&data[0x1a]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_4] = read16le(&data[0x1c]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_5] = read16le(&data[0x1e]);
+ fmp->datainfo.loopptr[FMP_DATA_FM_6] = read16le(&data[0x20]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_1] = read16le(&data[0x22]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_2] = read16le(&data[0x24]);
+ fmp->datainfo.loopptr[FMP_DATA_SSG_3] = read16le(&data[0x26]);
+ fmp->datainfo.loopptr[FMP_DATA_RHYTHM] = read16le(&data[0x28]);
+ fmp->datainfo.loopptr[FMP_DATA_ADPCM] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX1] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX2] = 0xffff;
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX3] = 0xffff;
+ fmp->datainfo.bar = data[0x2a];
+ dataflags = data[0x2b];
+ fmp->datainfo.adpcmptr = 0xffff;
+ fmp->datainfo.fmtoneptr = 0x2e;
+ pviname_valid = false;
+ } else {
+ return false;
+ }
+ fmp->datainfo.flags.q = dataflags & 0x01;
+ fmp->datainfo.flags.ppz = dataflags & 0x02;
+ fmp->datainfo.flags.lfo_octave_fix = dataflags & 0x04;
+ {
+ uint16_t ptr = read16le(&data[0x00]) - 2;
+ fmp->datainfo.ssgtoneptr = ((ptr+2) > datalen) ? 0xffff : read16le(&data[ptr]);
+ }
+ // 3a70
+ //zero reset part struct
+ FMDRIVER_DEBUG("bar: %d\n", fmp->datainfo.bar);
+ for (int i = 0; i < 6; i++) {
+ FMDRIVER_DEBUG(" FM#%d: %04X %04X\n",
+ i+1, fmp->datainfo.partptr[i], fmp->datainfo.loopptr[i]);
+ }
+ for (int i = 0; i < 3; i++) {
+ FMDRIVER_DEBUG(" SSG#%d: %04X %04X\n",
+ i+1,
+ fmp->datainfo.partptr[FMP_DATA_SSG_1+i],
+ fmp->datainfo.loopptr[FMP_DATA_SSG_1+i]);
+ }
+ FMDRIVER_DEBUG(" RHYTHM: %04X %04X\n",
+ fmp->datainfo.partptr[FMP_DATA_RHYTHM],
+ fmp->datainfo.loopptr[FMP_DATA_RHYTHM]);
+ FMDRIVER_DEBUG(" ADPCM: %04X %04X\n",
+ fmp->datainfo.partptr[FMP_DATA_ADPCM],
+ fmp->datainfo.loopptr[FMP_DATA_ADPCM]);
+ for (int i = 0; i < 3; i++) {
+ FMDRIVER_DEBUG(" FMEX#%d: %04X %04X\n",
+ i+1,
+ fmp->datainfo.partptr[FMP_DATA_FM_EX1+i],
+ fmp->datainfo.loopptr[FMP_DATA_FM_EX1+i]);
+ }
+ FMDRIVER_DEBUG(" FMTONEPTR: %04X\n", fmp->datainfo.fmtoneptr);
+ FMDRIVER_DEBUG(" SSGTONEPTR: %04X\n", fmp->datainfo.ssgtoneptr);
+ FMDRIVER_DEBUG(" data version: 0x%01X\n", fmp->data_version);
+ fmp->bar_tickcnt = fmp->datainfo.bar;
+ fmp->data = data;
+ fmp->datalen = datalen;
+ fmp_struct_init(work, fmp);
+ fmp_init_parts(work, fmp);
+ uint16_t fmtoneptr = fmp->datainfo.fmtoneptr;
+ FMDRIVER_DEBUG(" 000 %03d %03d\n",
+ fmp->data[fmtoneptr+0x18]&0x7,
+ (fmp->data[fmtoneptr+0x18]>>3)&0x7
+ );
+ for (int i = 0; i < 4; i++) {
+ static const uint8_t t[4] = {
+ 0, 2, 1, 3,
+ };
+ FMDRIVER_DEBUG(" %03d %03d %03d %03d %03d %03d %03d %03d %03d\n",
+ fmp->data[fmtoneptr+0x08+t[i]]&0x1f,
+ fmp->data[fmtoneptr+0x0c+t[i]]&0x1f,
+ fmp->data[fmtoneptr+0x10+t[i]]&0x1f,
+ fmp->data[fmtoneptr+0x14+t[i]]&0x0f,
+ fmp->data[fmtoneptr+0x14+t[i]]>>4,
+ fmp->data[fmtoneptr+0x04+t[i]]&0x7f,
+ fmp->data[fmtoneptr+0x08+t[i]]>>6,
+ fmp->data[fmtoneptr+0x00+t[i]]&0x0f,
+ (fmp->data[fmtoneptr+0x00+t[i]]>>4)&0x7
+ );
+ }
+ fmp_set_tempo(work, fmp);
+ work->driver = fmp;
+ work->driver_opna_interrupt = fmp_opna_interrupt;
+ fmp_title(work, data, datalen, read16le(data)+4);
+// uint16_t pcmptr = read16le(data+read16le(data)-2);
+ uint16_t pcmptr = read16le(data)-0x12;
+ if (pcmptr <= datalen && (pcmptr+16) < datalen) {
+ for (int i = 0; i < 8; i++) {
+ if (pviname_valid) {
+ fmp->pvi_name[i] = data[pcmptr+8+i];
+ }
+ if (pviname_valid && fmp->datainfo.flags.ppz) {
+ fmp->ppz_name[i] = data[pcmptr+0+i];
+ }
+ }
+ }
+ return true;
+}
+
+// 4235
+bool fmp_adpcm_load(struct fmdriver_work *work,
+ uint8_t *data, size_t datalen) {
+ if (datalen < 0x210) return false;
+ if (datalen > (0x210+(1<<18))) return false;
+ struct driver_fmp *fmp = (struct driver_fmp *)work->driver;
+ fmp->adpcm_deltat = read16le(&data[0x8]);
+ // fmp->adpcm_c1 = data[0x0a];
+ for (int i = 0; i < 0x80; i++) {
+ fmp->adpcm_startaddr[i] = read16le(&data[0x10+i*4+0]);
+ fmp->adpcm_endaddr[i] = read16le(&data[0x10+i*4+2]);
+ }
+ work->opna_writereg(work, 0x100, 0x01);
+ work->opna_writereg(work, 0x110, 0x13);
+ work->opna_writereg(work, 0x110, 0x80);
+ work->opna_writereg(work, 0x100, 0x60);
+ work->opna_writereg(work, 0x101, fmp->adpcm_c1);
+ work->opna_writereg(work, 0x102, 0x00);
+ work->opna_writereg(work, 0x103, 0x00);
+ work->opna_writereg(work, 0x104, 0xff);
+ work->opna_writereg(work, 0x105, 0xff);
+ work->opna_writereg(work, 0x10c, 0xff);
+ work->opna_writereg(work, 0x10d, 0xff);
+ // 42ca
+ for (uint32_t i = 0x210; i < datalen; i++) {
+ work->opna_writereg(work, 0x108, data[i]);
+ }
+ work->opna_writereg(work, 0x110, 0x0c);
+ work->opna_writereg(work, 0x100, 0x01);
+ return true;
+}
diff --git a/fmdriver/fmdriver_fmp.h b/fmdriver/fmdriver_fmp.h new file mode 100644 index 0000000..8ed9451 --- /dev/null +++ b/fmdriver/fmdriver_fmp.h @@ -0,0 +1,529 @@ +#ifndef MYON_FMDRIVER_FMP_H_INCLUDED +#define MYON_FMDRIVER_FMP_H_INCLUDED + +#include "fmdriver.h" +#include <stddef.h> + +enum { + FMP_PART_FM_1, + FMP_PART_FM_2, + FMP_PART_FM_3, + FMP_PART_FM_4, + FMP_PART_FM_5, + FMP_PART_FM_6, + FMP_PART_FM_EX1, + FMP_PART_FM_EX2, + FMP_PART_FM_EX3, + FMP_PART_PPZ8_1, + FMP_PART_PPZ8_2, + FMP_PART_PPZ8_3, + FMP_PART_PPZ8_4, + FMP_PART_PPZ8_5, + FMP_PART_PPZ8_6, + FMP_PART_PPZ8_7, + FMP_PART_PPZ8_8, + FMP_PART_RESERVED, + FMP_PART_SSG_1, + FMP_PART_SSG_2, + FMP_PART_SSG_3, + FMP_PART_ADPCM, + FMP_PART_NUM +}; + +enum { + FMP_RHYTHM_NUM = 6 +}; + +enum { + FMP_DATA_FM_1, + FMP_DATA_FM_2, + FMP_DATA_FM_3, + FMP_DATA_FM_4, + FMP_DATA_FM_5, + FMP_DATA_FM_6, + FMP_DATA_SSG_1, + FMP_DATA_SSG_2, + FMP_DATA_SSG_3, + FMP_DATA_RHYTHM, + FMP_DATA_ADPCM, + FMP_DATA_FM_EX1, + FMP_DATA_FM_EX2, + FMP_DATA_FM_EX3, + FMP_DATA_NUM +}; + +enum { + FMP_LFO_P = 0, + FMP_LFO_Q = 1, + FMP_LFO_R = 2, +}; + +enum { + FMP_LFOWF_TRIANGLE = 0, + FMP_LFOWF_SAWTOOTH = 1, + FMP_LFOWF_SQUARE = 2, + FMP_LFOWF_LINEAR = 3, + FMP_LFOWF_STAIRCASE = 4, + FMP_LFOWF_TRIANGLE2 = 5, + FMP_LFOWF_RANDOM = 6, +}; + +struct fmp_lfo { + // 0016 + uint8_t delay; + // 0017 + uint8_t speed; + // 0018 + uint8_t delay_cnt; + // 0019 + uint8_t speed_cnt; + // 001a + uint8_t depth; + // 001b + uint8_t depth_cnt; + // 001c + uint16_t rate; + // 001e + uint16_t rate2; + // 0020 + uint8_t waveform; +}; + +struct fmp_ssgenv { + // 0056 + uint8_t startvol; + // 0057 + uint8_t attack_rate; + // 0058 + uint8_t decay_rate; + // 0059 + uint8_t sustain_lv; + // 005a + uint8_t sustain_rate; + // 005b + uint8_t release_rate; +}; + +struct fmp_part { + // 0000 + struct { + // 0x01 + bool portamento: 1; + // 0x02 + bool lfo: 1; // ? + // 0x04 + bool e: 1; + // 0x08 + bool w: 1; + // 0x10 + bool a: 1; + // 0x20 + bool r: 1; + // 0x40 + bool q: 1; + // 0x80 + bool p: 1; + } lfo_f; + // 0001 + uint8_t default_len; + // 0002 + uint8_t current_vol; + // 0003 + // q command without Q + // noteoff when equal to gate_cmp + uint8_t gate_cmp; + // 0004 + uint8_t tonelen_cnt; + // 0005 + uint8_t gate_cnt; + // 0006 + uint8_t actual_vol; + // 0007 + // command k + uint8_t keyon_delay; + // 0008 + uint8_t keyon_delay_cnt; + // 0009 + uint8_t prev_note; + // 000a + struct { + // 0x01 + bool keyon: 1; + // 0x02 + bool slur: 1; + // 0x04 + bool tie_cont: 1; + // 0x08 + bool off: 1; + // 0x10 + bool lfo_sync: 1; + // 0x20 + bool pitchbend: 1; + // 0x40 + bool rest: 1; + // 0x80 + bool tie: 1; + } status; + // 000b + uint8_t sync; + // 000c + uint16_t detune; + struct { + // 000e + uint8_t rate; + // 000f + uint8_t delay; + // 0010 + uint8_t speed; + // 0011 + uint8_t speed_cnt; + // 0012 + uint16_t target_freq; + // 0014 + uint8_t target_note; + } pit; + // 0016 + struct fmp_lfo lfo[3]; + // 003a + uint16_t actual_freq; + // 003c + uint16_t prev_freq; + // 003e + uint8_t note_diff; + // 003f + uint8_t tone; + // 0040 + uint16_t ext_keyon; + // 0042 + uint8_t pan_ams_pms; + // 0043 + uint8_t slot_vol_mask; + // 0046 + uint16_t current_ptr; + // 0048 + uint16_t loop_ptr; + // 004a + struct { + // 0x01 + bool fm: 1; + // 0x02 + bool fm_3: 1; + // 0x04 + bool ssg: 1; + // 0x08 + bool rhythm: 1; + // 0x10 + bool adpcm: 1; + // 0x20 + // SSG backup?? + } type; + // 0b00xx x0xx xxxx xxxx + // || | || |||| |||| + // || | || |||| |||`- FM#1 + // || | || |||| ||`-- FM#2 + // || | || |||| |`--- FM#3 + // || | || |||| `---- FM#4 + // || | || |||`------ FM#5 + // || | || ||`------- FM#6 + // || | || |`-------- SSG#1 + // || | || `--------- SSG#2 + // || | |`----------- SSG#3 + // || | `------------ rhythm + // || `-------------- FMEX#1 + // |`---------------- FMEX#2 + // `----------------- FMEX#3 + // 004c + uint16_t part_bit; + // 004e + // SSG: 1420 + // FM: 141e + // ADPCM: 1422 + // RHYTHM: 1424 + // 0050 + uint8_t opna_keyon_out; // output this to 0x28 + // when SSG, this can be PPZ8 channel num + + // 0051 + uint8_t eff_chan; + union { + struct { + // 0052 (ptr) + uint8_t tone_tl[4]; + struct { + // 0054 + uint8_t delay; + // 0055 + uint8_t speed; + // 0056 + uint8_t delay_cnt; + // 0057 + uint8_t speed_cnt; + // 0058 + uint8_t depth; + // 0059 + uint8_t depth_cnt; + // 005a + uint8_t rate; + // 005b + uint8_t rate_orig; + } alfo; + // 005c + struct fmp_wlfo { + // 005c + uint8_t delay; + // 005d + uint8_t speed; + // 005e + uint8_t delay_cnt; + // 005f + uint8_t speed_cnt; + // 0060 + uint8_t depth; + // 0061 + uint8_t depth_cnt; + // 0062 + uint8_t rate; + // 0063 + uint8_t rate_orig; + // 0064 + uint8_t rate_curr; + // 0065 + uint8_t sync; + } wlfo; + // 0066 + uint8_t hlfo_delay; + // 0067 + uint8_t hlfo_delay_cnt; + // 0068 + // value written to register 0x22 + uint8_t hlfo_freq; + // 0069 + uint8_t hlfo_apms; + // 006a + // 0b1234???? + uint8_t slot_mask; + // 006b + uint8_t slot_rel_vol[4]; + } fm; + struct { + // 0052 + uint8_t curr_vol; + // 0053 + struct { + // 0x01 + bool ppz: 1; + // 0x02 + bool noise: 1; + // 0x04 + bool portamento: 1; // ?? + // 0x10 + bool release: 1; + // 0x20 + bool sustain: 1; + // 0x40 + bool decay: 1; + // 0x80 + bool attack: 1; + } env_f; + /* + struct { + // 0x01 + //bool 1: 1; + } env_flag; + */ + // 0054 + uint8_t octave; + // 0055 + uint8_t vol; + // 0056 + struct fmp_ssgenv env; + // pointer at 005e + struct fmp_ssgenv envbak; + } ssg; + struct { + // 0052 + uint16_t startaddr; + // 0054 + uint16_t endaddr; + // 0056 + uint16_t deltat; + } adpcm; + } u; + + struct { + uint32_t loopstart32; + uint32_t loopend32; + uint8_t mode; + uint8_t ppz8_channel; + uint8_t voice; + uint8_t vol; + int8_t pan; + struct { + uint8_t al; + int8_t dd; + uint8_t sr; + uint8_t rr; + } env_param; + struct { + enum { + PDZF_ENV_ATT, + PDZF_ENV_DEC, + PDZF_ENV_REL, + PDZF_ENV_OFF, + } status; + uint8_t cnt; + enum { + PDZF_ENV_VOL_MIN = -15, + }; + int8_t vol; + } env_state; + bool keyon; + } pdzf; +}; + +struct fmp_rhythm { + // 0acf + uint8_t mask; + // 0ad0 + uint8_t len_cnt; + // 0ad1 + uint8_t default_len; + // 0ad2 + uint8_t tl_volume; + // 0ad3 + uint8_t volumes[FMP_RHYTHM_NUM]; + // 0ad9 + uint8_t pans[FMP_RHYTHM_NUM]; + // 0adf + //uint8_t loop_now + // 0ae0 + uint8_t sync; + // 0ae1 + bool status; + struct fmp_part part; +}; + +struct driver_fmp { + const uint8_t *data; + uint16_t datalen; + // 0103 + uint8_t timerb; + struct { + // 0104 + uint8_t data; + // 0105 + uint16_t cnt; + } sync; + // 010d + struct { + // 0x4000 + bool looped: 1; + // 0x8000 + bool stopped: 1; + } status; + // 010f + uint16_t total_clocks; + // 0111 + uint8_t clock_divider; + // 0112 + uint8_t fm_vol; + // 011a + uint8_t loop_cnt; + // 011d + uint8_t timerb_bak; + // 011e + // 0b00FEDFED + uint8_t ssg_mix; + // 011f + // output this to 0x27 to reset timer + uint8_t timer_ch3; + // 0120 + uint8_t ssg_noise_freq; + // 012f + uint8_t ssg_mix_se; + // 0131 + bool se_proc; + // 0b54 + uint8_t loop_dec; + // 0b55 + uint8_t loop_times; + // 0b5e + uint8_t data_version; + // 0b6c + uint8_t bar_tickcnt; + // 0b92 + uint8_t fm3_slot_keyon; + // 0b93 + uint8_t fm3_alg; + // 0ba4 + // see fmp_part::part_bit + uint16_t part_playing_bit; + // 0ba6 + // see fmp_part::part_bit + uint16_t part_loop_bit; + // 0b9c + int16_t ch3_se_freqdiff[4]; + + // 0cee + uint16_t adpcm_deltat; + // 0cf0 + uint8_t adpcm_c1; + // 0cf6 + uint16_t adpcm_startaddr[0x80]; + uint16_t adpcm_endaddr[0x80]; + // 0ff6 + // title + + // filename without extension .PVI + char ppz_name[9]; + char pvi_name[9]; + + struct fmp_part parts[FMP_PART_NUM]; + struct fmp_rhythm rhythm; + struct { + uint16_t partptr[FMP_DATA_NUM]; + uint16_t loopptr[FMP_DATA_NUM]; + // 4ebe (005e) + // default len: bar / 4 + uint8_t bar; + uint16_t fmtoneptr; + uint16_t ssgtoneptr; + uint16_t adpcmptr; + // *4ebf & 0x01 + struct { + // 0x4ebf + // 0x01 + bool q: 1; + // 0x02 + bool ppz: 1; + // 0x04 + bool lfo_octave_fix: 1; + } flags; + } datainfo; + uint8_t rand71; + + struct { + // when 0, no PDZF + // when 1, PDZF Normal mode + // when 2, PDZF Enhanced mode + uint8_t mode; + struct pdzf_rhythm { + uint8_t voice[2]; + int8_t pan; + uint8_t note; + bool enabled; + } rhythm[2]; + } pdzf; +}; + +// warning: will overwrite data +bool fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp, + uint8_t *data, uint16_t datalen); +// this function will access opna +bool fmp_adpcm_load(struct fmdriver_work *work, + uint8_t *data, size_t datalen); + +// 1da8 +// 6190: fmp external characters + +#endif // MYON_FMDRIVER_FMP_H_INCLUDED diff --git a/fmdriver/ppz8.c b/fmdriver/ppz8.c new file mode 100644 index 0000000..7984575 --- /dev/null +++ b/fmdriver/ppz8.c @@ -0,0 +1,293 @@ +#include "ppz8.h" +#include "fmdriver_common.h" + +void ppz8_init(struct ppz8 *ppz8, uint16_t srate, uint16_t mix_volume) { + for (int i = 0; i < 2; i++) { + struct ppz8_pcmbuf *buf = &ppz8->buf[i]; + buf->data = 0; + buf->buflen = 0; + for (int j = 0; j < 128; j++) { + struct ppz8_pcmvoice *voice = &buf->voice[j]; + voice->start = 0; + voice->len = 0; + voice->loopstart = 0; + voice->loopend = 0; + voice->origfreq = 0; + } + } + for (int i = 0; i < 8; i++) { + struct ppz8_channel *channel = &ppz8->channel[i]; + channel->ptr = -1; + channel->loopstartptr = -1; + channel->loopendptr = -1; + channel->endptr = 0; + channel->freq = 0; + channel->loopstartoff = -1; + channel->loopendoff = -1; + channel->prevout[0] = 0; + channel->prevout[1] = 0; + channel->vol = 8; + channel->pan = 5; + channel->voice = 0; + } + ppz8->srate = srate; + ppz8->totalvol = 12; + ppz8->mix_volume = mix_volume; +} + +static uint64_t ppz8_loop(const struct ppz8_channel *channel, uint64_t ptr) { + if (channel->loopstartptr != (uint64_t)-1) { + if (channel->loopendptr != (uint64_t)-1) { + if (ptr >= channel->loopendptr) { + if (channel->loopendptr == channel->loopstartptr) return (uint64_t)-1; + uint32_t offset = (ptr - channel->loopendptr) >> 16; + offset %= (uint32_t)((channel->loopendptr - channel->loopstartptr) >> 16); + offset += (uint32_t)(channel->loopstartptr >> 16); + return (ptr & ((1<<16)-1)) | (((uint64_t)offset) << 16); + } + } else if (ptr >= channel->endptr) { + if (channel->endptr == channel->loopstartptr) return (uint64_t)-1; + uint32_t offset = (ptr - channel->endptr) >> 16; + offset %= (uint32_t)((channel->endptr - channel->loopstartptr) >> 16); + offset += (uint32_t)(channel->loopstartptr >> 16); + return (ptr & ((1<<16)-1)) | (((uint64_t)offset) << 16); + } + } + if (ptr >= channel->endptr) { + return (uint64_t)-1; + } + return ptr; +} + +static int32_t ppz8_channel_calc(struct ppz8 *ppz8, struct ppz8_channel *channel) { + struct ppz8_pcmbuf *buf = &ppz8->buf[channel->voice>>7]; + struct ppz8_pcmvoice *voice = &buf->voice[channel->voice & 0x7f]; + int32_t out = 0; + if (channel->vol) { + uint16_t coeff = channel->ptr & 0xffffu; + out += (int32_t)channel->prevout[0] * (0x10000u - coeff); + out += (int32_t)channel->prevout[1] * coeff; + out >>= 16; + + // volume: out * 2**((volume-15)/2) + out >>= (7 - ((channel->vol&0xf)>>1)); + if (!(channel->vol&1)) { + out *= 0xb505; + out >>= 16; + } + } + + uint64_t ptrdiff = (((uint64_t)channel->freq * voice->origfreq) << 1) / ppz8->srate; + uint64_t oldptr = channel->ptr; + channel->ptr += ptrdiff; + uint32_t bufdiff = (channel->ptr>>16) - (oldptr>>16); + if (bufdiff) { + if (/*bufdiff == 1*/0) { + channel->prevout[0] = channel->prevout[1]; + channel->prevout[1] = 0; + channel->ptr = ppz8_loop(channel, channel->ptr); + if (channel->ptr != (uint64_t)-1) { + uint32_t bufptr = channel->ptr >> 16; + if (buf->data && bufptr < buf->buflen) { + channel->prevout[1] = buf->data[bufptr]; + } + } else { + channel->playing = false; + } + } else { + channel->prevout[0] = 0; + channel->prevout[1] = 0; + uint64_t ptr1 = ppz8_loop(channel, channel->ptr - 0x10000); + if (ptr1 != (uint64_t)-1) { + uint32_t bufptr = ptr1 >> 16; + if (buf->data && bufptr < buf->buflen) { + channel->prevout[0] = buf->data[bufptr]; + } + channel->ptr = ppz8_loop(channel, channel->ptr); + if (channel->ptr != (uint64_t)-1) { + bufptr = channel->ptr >> 16; + if (buf->data && bufptr < buf->buflen) { + channel->prevout[1] = buf->data[bufptr]; + } + } else { + channel->playing = false; + } + } else { + channel->playing = false; + } + } + } + return out; +} + +void ppz8_mix(struct ppz8 *ppz8, int16_t *buf, unsigned samples) { + static const uint8_t pan_vol[10][2] = { + {0, 0}, + {4, 0}, + {4, 1}, + {4, 2}, + {4, 3}, + {4, 4}, + {3, 4}, + {2, 4}, + {1, 4}, + {0, 4} + }; + for (unsigned i = 0; i < samples; i++) { + int32_t lo = buf[i*2+0]; + int32_t ro = buf[i*2+1]; + for (int p = 0; p < 8; p++) { + //if (p < 3) continue; + //if (p >= 6) continue; + struct ppz8_channel *channel = &ppz8->channel[p]; + if (!channel->playing) continue; + int32_t out = ppz8_channel_calc(ppz8, channel); + out *= ppz8->mix_volume; + out >>= 15; + lo += (out * pan_vol[channel->pan][0]) >> 2; + ro += (out * pan_vol[channel->pan][1]) >> 2; + } + if (lo < INT16_MIN) lo = INT16_MIN; + if (lo > INT16_MAX) lo = INT16_MAX; + if (ro < INT16_MIN) ro = INT16_MIN; + if (ro > INT16_MAX) ro = INT16_MAX; + buf[i*2+0] = lo; + buf[i*2+1] = ro; + } +} + +static int16_t calc_acc(int16_t acc, uint16_t adpcmd, uint8_t data) { + data &= 0xf; + int32_t acc_d = (((data&7)<<1)|1); + if (data&8) acc_d = -acc_d; + int32_t newacc = acc + (acc_d*adpcmd/8); + if (newacc < -32768) newacc = -32768; + if (newacc > 32767) newacc = 32767; + return newacc; +} + +static uint16_t calc_adpcmd(uint16_t adpcmd, uint8_t data) { + static const uint8_t adpcm_table[8] = { + 57, 57, 57, 57, 77, 102, 128, 153, + }; + uint32_t newadpcmd = adpcmd*adpcm_table[data&7]/64; + if (newadpcmd < 127) newadpcmd = 127; + if (newadpcmd > 24576) newadpcmd = 24576; + return newadpcmd; +} + +bool ppz8_pvi_load(struct ppz8 *ppz8, uint8_t bnum, + const uint8_t *pvidata, uint32_t pvidatalen, + int16_t *decodebuf) { + if (bnum >= 2) return false; + //if (pvidatalen > (0x210+(1<<18))) return false; + struct ppz8_pcmbuf *buf = &ppz8->buf[bnum]; + //uint16_t origfreq = ((uint32_t)read16le(&pvidata[0x8]) * 55467) >> 16; + uint16_t origfreq = (0x49ba*55467)>>16; + uint32_t lastaddr = 0; + for (int i = 0; i < 0x80; i++) { + uint32_t startaddr = read16le(&pvidata[0x10+i*4+0]) << 6; + uint32_t endaddr = (read16le(&pvidata[0x10+i*4+2])+1) << 6; + if (startaddr != lastaddr) break; + if (startaddr >= endaddr) break; + struct ppz8_pcmvoice *voice = &buf->voice[i]; + voice->start = startaddr<<1; + voice->len = (endaddr-startaddr)<<1; + voice->loopstart = (uint32_t)-1; + voice->loopend = (uint32_t)-1; + voice->origfreq = origfreq; + + int16_t acc = 0; + uint16_t adpcmd = 127; + for (uint32_t a = startaddr; a < endaddr; a++) { + if (pvidatalen <= (0x210+(a>>1))) return false; + uint8_t data = pvidata[0x210+(a>>1)]; + if (a&1) { + data &= 0xf; + } else { + data >>= 4; + } + acc = calc_acc(acc, adpcmd, data); + adpcmd = calc_adpcmd(adpcmd, data); + decodebuf[a] = acc; + } + lastaddr = endaddr; + } + buf->data = decodebuf; + buf->buflen = ppz8_pvi_decodebuf_samples(pvidatalen); + return true; +} + +static void ppz8_channel_play(struct ppz8 *ppz8, uint8_t ch, uint8_t v) { + if (ch >= 8) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->voice = v; + struct ppz8_pcmbuf *buf = &ppz8->buf[channel->voice>>7]; + struct ppz8_pcmvoice *voice = &buf->voice[channel->voice & 0x7f]; + channel->ptr = ((uint64_t)(voice->start)>>1)<<16; + channel->endptr = ((uint64_t)(voice->start+voice->len)>>1)<<16; +/* + channel->loopstartptr = ((uint64_t)(voice->loopstart)>>1)<<16; + channel->loopendptr = ((uint64_t)(voice->loopend)>>1)<<16; +*/ + channel->loopstartptr = (channel->loopstartoff == (uint32_t)-1) ? (uint64_t)-1 + : channel->ptr + (((uint64_t)(channel->loopstartoff))<<16); + channel->loopendptr = (channel->loopendoff == (uint32_t)-1) ? (uint64_t)-1 + : channel->ptr + (((uint64_t)(channel->loopendoff))<<16); + channel->prevout[0] = 0; + channel->prevout[1] = 0; + channel->playing = true; + uint32_t bufptr = channel->ptr >> 16; + if (buf->data && bufptr < buf->buflen) { + channel->prevout[1] = buf->data[bufptr]; + } +} + +static void ppz8_channel_stop(struct ppz8 *ppz8, uint8_t ch) { + if (ch >= 8) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->playing = false; +} + +static void ppz8_channel_volume(struct ppz8 *ppz8, uint8_t ch, uint8_t vol) { + if (ch >= 8) return; + if (vol >= 16) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->vol = vol; +} + +static void ppz8_channel_freq(struct ppz8 *ppz8, uint8_t ch, uint32_t freq) { + if (ch >= 8) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->freq = freq; +} + +static void ppz8_channel_loopoffset(struct ppz8 *ppz8, uint8_t ch, + uint32_t startoff, uint32_t endoff) { + if (ch >= 8) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->loopstartoff = startoff; + channel->loopendoff = endoff; +} + +static void ppz8_channel_pan(struct ppz8 *ppz8, uint8_t ch, uint8_t pan) { + if (ch >= 8) return; + if (pan >= 10) return; + struct ppz8_channel *channel = &ppz8->channel[ch]; + channel->pan = pan; +} + +static void ppz8_total_volume(struct ppz8 *ppz8, uint8_t vol) { + if (vol >= 16) return; + ppz8->totalvol = vol; +} + +const struct ppz8_functbl ppz8_functbl = { + ppz8_channel_play, + ppz8_channel_stop, + ppz8_channel_volume, + ppz8_channel_freq, + ppz8_channel_loopoffset, + ppz8_channel_pan, + ppz8_total_volume +}; diff --git a/fmdriver/ppz8.h b/fmdriver/ppz8.h new file mode 100644 index 0000000..6ced669 --- /dev/null +++ b/fmdriver/ppz8.h @@ -0,0 +1,76 @@ +#ifndef MYON_PPZ8_H_INCLUDED +#define MYON_PPZ8_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct ppz8_pcmvoice { + uint32_t start; + uint32_t len; + uint32_t loopstart; + uint32_t loopend; + uint16_t origfreq; +}; + +struct ppz8_pcmbuf { + int16_t *data; + uint32_t buflen; + struct ppz8_pcmvoice voice[128]; +}; + +struct ppz8_channel { + uint64_t ptr; + uint64_t loopstartptr; + uint64_t loopendptr; + uint64_t endptr; + uint32_t freq; + uint32_t loopstartoff; + uint32_t loopendoff; + int16_t prevout[2]; + uint8_t vol; + uint8_t pan; + uint8_t voice; + bool playing; +}; + +struct ppz8 { + struct ppz8_pcmbuf buf[2]; + struct ppz8_channel channel[8]; + uint16_t srate; + uint8_t totalvol; + uint16_t mix_volume; +}; + +void ppz8_init(struct ppz8 *ppz8, uint16_t srate, uint16_t mix_volume); +void ppz8_mix(struct ppz8 *ppz8, int16_t *buf, unsigned samples); +bool ppz8_pvi_load(struct ppz8 *ppz8, uint8_t buf, + const uint8_t *pvidata, uint32_t pvidatalen, + int16_t *decodebuf); + +static inline uint32_t ppz8_pvi_decodebuf_samples(uint32_t pvidatalen) { + if (pvidatalen < 0x210) return 0; + return (pvidatalen - 0x210) * 2; +} + +struct ppz8_functbl { + void (*channel_play)(struct ppz8 *ppz8, uint8_t channel, uint8_t voice); + void (*channel_stop)(struct ppz8 *ppz8, uint8_t channel); + void (*channel_volume)(struct ppz8 *ppz8, uint8_t channel, uint8_t vol); + void (*channel_freq)(struct ppz8 *ppz8, uint8_t channel, uint32_t freq); + void (*channel_loopoffset)(struct ppz8 *ppz8, uint8_t channel, + uint32_t startoff, uint32_t endoff); + void (*channel_pan)(struct ppz8 *ppz8, uint8_t channel, uint8_t pan); + void (*total_volume)(struct ppz8 *ppz8, uint8_t vol); +}; + +extern const struct ppz8_functbl ppz8_functbl; + +#ifdef __cplusplus +} +#endif + +#endif // MYON_PPZ8_H_INCLUDED |