aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fmdriver/fmdriver.h6
-rw-r--r--fmdriver/fmdriver_common.c71
-rw-r--r--fmdriver/fmdriver_common.h5
-rw-r--r--fmdriver/fmdriver_fmp.c83
-rw-r--r--fmdriver/fmdriver_pmd.c4674
-rw-r--r--fmdriver/fmdriver_pmd.h462
-rw-r--r--fmdriver/pmd_ssgeff.h281
-rw-r--r--fmdsp/fmdsp.c19
-rw-r--r--gtk/Makefile.am2
-rw-r--r--gtk/main.c82
-rw-r--r--gtk/toneview.c15
-rw-r--r--gtk/toneview.h4
-rw-r--r--win32/fmplayer.mak4
-rw-r--r--win32/main.c85
14 files changed, 5644 insertions, 149 deletions
diff --git a/fmdriver/fmdriver.h b/fmdriver/fmdriver.h
index a35f9b8..93d2f26 100644
--- a/fmdriver/fmdriver.h
+++ b/fmdriver/fmdriver.h
@@ -35,11 +35,11 @@ enum fmdriver_track_type {
enum fmdriver_track_info {
FMDRIVER_TRACK_INFO_NORMAL,
- FMDRIVER_TRACK_INFO_SSG_NOISE_ONLY,
- FMDRIVER_TRACK_INFO_SSG_NOISE_MIX,
+ FMDRIVER_TRACK_INFO_SSG,
FMDRIVER_TRACK_INFO_FM3EX,
FMDRIVER_TRACK_INFO_PPZ8,
FMDRIVER_TRACK_INFO_PDZF,
+ FMDRIVER_TRACK_INFO_SSGEFF,
};
struct fmdriver_track_status {
@@ -60,6 +60,8 @@ struct fmdriver_track_status {
// for FMP, ppz8 channel+1 or 0
// use for track mask or display
uint8_t ppz8_ch;
+ bool ssg_tone;
+ bool ssg_noise;
};
struct fmdriver_work {
diff --git a/fmdriver/fmdriver_common.c b/fmdriver/fmdriver_common.c
new file mode 100644
index 0000000..26e6def
--- /dev/null
+++ b/fmdriver/fmdriver_common.c
@@ -0,0 +1,71 @@
+#include "fmdriver/fmdriver_common.h"
+
+uint8_t fmdriver_fm_freq2key(uint16_t freq) {
+ int block = freq >> (8+3);
+ int f_num = freq & ((1<<(8+3))-1);
+ if (!f_num) return 0x00;
+ while (!(f_num & (1<<(8+3-1)))) {
+ f_num <<= 1;
+ block--;
+ }
+ static const uint16_t freqtab[0xc] = {
+ 0x042e, // < 9 (a)
+ 0x046e,
+ 0x04b1,
+ 0x04f9,
+ 0x0544,
+ 0x0595,
+ 0x05ea,
+ 0x0644,
+ 0x06a3,
+ 0x0708,
+ 0x0773,
+ 0x07e4,
+ };
+ int note = 0;
+ for (; note < 12; note++) {
+ if (f_num < freqtab[note]) break;
+ }
+ note += 9;
+ block += (note/12);
+ note %= 12;
+
+ if (block < 0) return 0x00;
+ if (block > 8) return 0x8b;
+ return (block << 4) | note;
+}
+
+uint8_t fmdriver_ssg_freq2key(uint16_t freq) {
+ if (!freq) return 0x00;
+ int octave = -5;
+ while (!(freq & 0x8000)) {
+ freq <<= 1;
+ octave++;
+ }
+ // 7987200.0 / (64*440*(2**((i+2+0.5)/12))/(1<<8))
+ static const uint16_t freqtab[0xc] = {
+ 0xf57f, // > 0 (c)
+ 0xe7b8,
+ 0xdab7,
+ 0xce70,
+ 0xc2da,
+ 0xb7ea,
+ 0xad98,
+ 0xa3da,
+ 0x9aa7,
+ 0x91f9,
+ 0x89c8,
+ 0x820c,
+ };
+ int note = 0;
+ for (; note < 12; note++) {
+ if (freq > freqtab[note]) break;
+ }
+ note += 11;
+ octave += (note/12);
+ note %= 12;
+
+ if (octave < 0) return 0x00;
+ if (octave > 8) return 0x8b;
+ return (octave << 4) | note;
+}
diff --git a/fmdriver/fmdriver_common.h b/fmdriver/fmdriver_common.h
index 706dc95..2700336 100644
--- a/fmdriver/fmdriver_common.h
+++ b/fmdriver/fmdriver_common.h
@@ -1,6 +1,8 @@
#ifndef MYON_FMDRIVER_COMMON_H_INCLUDED
#define MYON_FMDRIVER_COMMON_H_INCLUDED
+#include <stdint.h>
+
static inline uint16_t read16le(const uint8_t *ptr) {
return (unsigned)ptr[0] | (((unsigned)ptr[1])<<8);
}
@@ -13,6 +15,9 @@ static inline int16_t u16s16(uint16_t v) {
return (v & 0x8000u) ? ((int32_t)v)-0x10000l : v;
}
+uint8_t fmdriver_fm_freq2key(uint16_t freq);
+uint8_t fmdriver_ssg_freq2key(uint16_t freq);
+
#if 0
#include <stdio.h>
#define FMDRIVER_DEBUG(...) fprintf(stderr, __VA_ARGS__)
diff --git a/fmdriver/fmdriver_fmp.c b/fmdriver/fmdriver_fmp.c
index 5643f24..90a157c 100644
--- a/fmdriver/fmdriver_fmp.c
+++ b/fmdriver/fmdriver_fmp.c
@@ -73,41 +73,6 @@ static uint16_t fmp_fm_freq(uint8_t note) {
return freqtab[note%0xc] + ((note/0xc)<<(3+8));
}
-static uint8_t fmp_fm_freq2key(uint16_t freq) {
- int block = freq >> (8+3);
- int f_num = freq & ((1<<(8+3))-1);
- if (!f_num) return 0x00;
- while (!(f_num & (1<<(8+3-1)))) {
- f_num <<= 1;
- block--;
- }
- static const uint16_t freqtab[0xc] = {
- 0x042e, // < 9 (a)
- 0x046e,
- 0x04b1,
- 0x04f9,
- 0x0544,
- 0x0595,
- 0x05ea,
- 0x0644,
- 0x06a3,
- 0x0708,
- 0x0773,
- 0x07e4,
- };
- int note = 0;
- for (; note < 12; note++) {
- if (f_num < freqtab[note]) break;
- }
- note += 9;
- block += (note/12);
- note %= 12;
-
- if (block < 0) return 0x00;
- if (block > 8) return 0x8b;
- return (block << 4) | note;
-}
-
static uint8_t fmp_ssg_octave(uint8_t note) {
return note/0xc;
}
@@ -151,41 +116,6 @@ static uint16_t fmp_part_ssg_freq(struct fmp_part *part, uint8_t note) {
return part->detune + freq;
}
-static uint8_t fmp_ssg_freq2key(uint16_t freq) {
- if (!freq) return 0x00;
- int octave = -5;
- while (!(freq & 0x8000)) {
- freq <<= 1;
- octave++;
- }
- // 7987200.0 / (64*440*(2**((i+2+0.5)/12))/(1<<8))
- static const uint16_t freqtab[0xc] = {
- 0xf57f, // > 0 (c)
- 0xe7b8,
- 0xdab7,
- 0xce70,
- 0xc2da,
- 0xb7ea,
- 0xad98,
- 0xa3da,
- 0x9aa7,
- 0x91f9,
- 0x89c8,
- 0x820c,
- };
- int note = 0;
- for (; note < 12; note++) {
- if (freq > freqtab[note]) break;
- }
- note += 11;
- octave += (note/12);
- note %= 12;
-
- if (octave < 0) return 0x00;
- if (octave > 8) return 0x8b;
- return (octave << 4) | note;
-}
-
// 3172
static uint16_t fmp_adpcm_freq(uint8_t note) {
static const uint16_t freqtab[4][0xc] = {
@@ -2851,13 +2781,10 @@ static void fmp_work_status_update(struct fmdriver_work *work,
track->info = FMDRIVER_TRACK_INFO_PPZ8;
track->actual_key = 0xff;
} else {
- track->actual_key = part->status.rest ? 0xff : fmp_ssg_freq2key(part->prev_freq);
- bool tone = !((fmp->ssg_mix >> part->opna_keyon_out) & 1);
- bool noise = !((fmp->ssg_mix >> part->opna_keyon_out) & 8);
- if (noise) {
- if (tone) track->info = FMDRIVER_TRACK_INFO_SSG_NOISE_MIX;
- else track->info = FMDRIVER_TRACK_INFO_SSG_NOISE_ONLY;
- }
+ track->info = FMDRIVER_TRACK_INFO_SSG;
+ track->actual_key = part->status.rest ? 0xff : fmdriver_ssg_freq2key(part->prev_freq);
+ track->ssg_tone = !((fmp->ssg_mix >> part->opna_keyon_out) & 1);
+ track->ssg_noise = !((fmp->ssg_mix >> part->opna_keyon_out) & 8);
}
track->volume = part->current_vol - 1;
} else {
@@ -2871,7 +2798,7 @@ static void fmp_work_status_update(struct fmdriver_work *work,
track->fmslotmask[s] = part->u.fm.slot_mask & (1<<(4+s));
}
}
- track->actual_key = part->status.rest ? 0xff : fmp_fm_freq2key(part->prev_freq);
+ track->actual_key = part->status.rest ? 0xff : fmdriver_fm_freq2key(part->prev_freq);
}
track->volume = 0x7f - part->actual_vol;
}
diff --git a/fmdriver/fmdriver_pmd.c b/fmdriver/fmdriver_pmd.c
new file mode 100644
index 0000000..76a97ba
--- /dev/null
+++ b/fmdriver/fmdriver_pmd.c
@@ -0,0 +1,4674 @@
+#include "fmdriver_pmd.h"
+#include "fmdriver_common.h"
+
+enum {
+ SSG_ENV_STATE_OLD_AL,
+ SSG_ENV_STATE_OLD_SR,
+ SSG_ENV_STATE_OLD_RR,
+ SSG_ENV_STATE_OLD_OFF,
+ SSG_ENV_STATE_OLD_NEW = 0xff
+};
+
+enum {
+ SSG_ENV_STATE_NEW_OFF,
+ SSG_ENV_STATE_NEW_AR,
+ SSG_ENV_STATE_NEW_DR,
+ SSG_ENV_STATE_NEW_SR,
+ SSG_ENV_STATE_NEW_RR
+};
+
+enum {
+ SSG_ENV_PARAM_NEW_AR,
+ SSG_ENV_PARAM_NEW_DR,
+ SSG_ENV_PARAM_NEW_SR,
+ SSG_ENV_PARAM_NEW_RR
+};
+
+enum {
+ SSG_ENV_PARAM_OLD_AL,
+ SSG_ENV_PARAM_OLD_AD,
+ SSG_ENV_PARAM_OLD_SR,
+ SSG_ENV_PARAM_OLD_RR
+};
+
+enum {
+ LFO_WF_TRIANGLE1,
+ LFO_WF_SAWTOOTH,
+ LFO_WF_SQUARE,
+ LFO_WF_RANDOM,
+ LFO_WF_TRIANGLE2,
+ LFO_WF_TRIANGLE3,
+ LFO_WF_ONESHOT
+};
+
+// 3447
+static void pmd_reg_write(struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ uint8_t addr, uint8_t data
+ ) {
+ work->opna_writereg(work, addr | (pmd->opna_a1 ? 0x100:0), data);
+}
+
+// 132b
+static void pmd_opna_a1_on(struct driver_pmd *pmd) {
+ pmd->opna_a1 = true;
+}
+
+// 133d
+static void pmd_opna_a1_off(struct driver_pmd *pmd) {
+ pmd->opna_a1 = false;
+}
+
+// 346f
+static uint8_t pmd_opna_read_ssgout(struct fmdriver_work *work) {
+ return work->opna_readreg(work, 0x07);
+}
+
+// 331f
+static void pmd_stop_sound(struct fmdriver_work *work,
+ struct driver_pmd *pmd) {
+ // stop all FM sound except fm effect (CH6)
+
+ // set release rate to 0xff
+ bool fmeff = pmd->fmeff_playing;
+ for (int a1 = 0; a1 < 2; a1++) {
+ a1 ? pmd_opna_a1_on(pmd) : pmd_opna_a1_off(pmd);
+ for (int s = 0; s < 4; s++) {
+ for (int c = 0; c < 3; c++) {
+ if (a1 && fmeff && (c == 2)) continue;
+ pmd_reg_write(work, pmd, 0x80|c|(s<<2), 0xff);
+ }
+ }
+ }
+ // 3353
+ // keyoff
+ for (int c = 0; c < 3; c++) {
+ work->opna_writereg(work, 0x28, 0x00|c);
+ }
+ for (int c = 0; c < 3; c++) {
+ if (fmeff && (c == 2)) continue;
+ work->opna_writereg(work, 0x28, 0x04|c);
+ }
+ // 3375
+ // stop SSG sound
+ if (!pmd->ssgeff_num) {
+ if (pmd->ppsdrv_enabled) {
+ // TODO: PPSDRV
+ }
+ work->opna_writereg(work, 0x07, 0xbf);
+ } else {
+ // 338f
+ // don't stop ssg effect when playing
+ uint8_t ssgout = pmd_opna_read_ssgout(work);
+ ssgout &= 0x3f;
+ ssgout |= 0x9b;
+ work->opna_writereg(work, 0x07, ssgout);
+ }
+ // 33a2
+ // stop ADPCM
+ if (!pmd->adpcmeff_playing && !pmd->adpcm_disabled) {
+ work->opna_writereg(work, 0x101, 0x02);
+ work->opna_writereg(work, 0x100, 0x01);
+ work->opna_writereg(work, 0x110, 0x80);
+ work->opna_writereg(work, 0x110, 0x18);
+ }
+ // 33c8
+ // stop PPZ8
+ if (work->ppz8_functbl) {
+ for (int c = 0; c < 8; c++) {
+ work->ppz8_functbl->channel_stop(work->ppz8, c);
+ }
+ }
+}
+
+// 11df
+static void pmd_mstop(struct fmdriver_work *work,
+ struct driver_pmd *pmd) {
+ pmd->playing = false;
+ pmd->paused = false;
+ pmd->fadeout_speed = 0;
+ pmd->status2 = 0xff;
+ pmd->fadeout_vol = 0xff;
+ pmd_stop_sound(work, pmd);
+}
+
+// 1058
+static void pmd_reset_state(struct driver_pmd *pmd) {
+ pmd->fadeout_vol = 0;
+ pmd->fadeout_speed = 0;
+ pmd->faded_out = false;
+ for (struct pmd_part *p = pmd->parts; p < (&pmd->parts[PMD_PART_NUM]); p++) {
+ /*
+ bool ext = p->mask.ext;
+ bool effect = p->mask.effect;
+ bool mml = p->mask.mml;
+ bool ff = p->mask.ff;
+ uint8_t note_proc = p->note_proc;
+ zero reset part structure
+ */
+ p->actual_note = 0xff;
+ p->curr_note = 0xff;
+ }
+ // 1090
+ pmd->no_keyoff = false;
+ pmd->status1 = 0;
+ pmd->status2 = 0;
+ pmd->meas_cnt = 0;
+ pmd->tick_cnt = 0;
+ pmd->timera_cnt = 0;
+ pmd->timera_cnt_b = 0;
+ for (int i = 0; i < 6; i++) pmd->fm_slotkey[i] = 0;
+ pmd->fm3ex_fb_alg = 0;
+ pmd->tonemask_fb_alg = false;
+ pmd->adpcm_start = 0;
+ pmd->adpcm_stop = 0;
+ // TODO: pmd->42c1 = 0x8000;
+ pmd->ssgrhythm = 0;
+ pmd->opnarhythm = 0;
+ pmd->rand = 0;
+ pmd->fm3ex_force = false;
+ for (int s = 0; s < 4; s++) {
+ pmd->fm3ex_det[s] = 0;
+ }
+ pmd->fm3ex_needed = 0;
+ pmd->fm3ex_mode = 0x3f;
+ pmd->opna_a1 = false;
+ pmd->meas_len = 96;
+ pmd->fm_voldown = pmd->fm_voldown_orig;
+ pmd->ssg_voldown = pmd->ssg_voldown_orig;
+ pmd->adpcm_voldown = pmd->adpcm_voldown_orig;
+ pmd->ppz8_voldown = pmd->ppz8_voldown_orig;
+ pmd->opnarhythm_voldown = pmd->opnarhythm_voldown_orig;
+ pmd->pcm86_vol_spb = pmd->pcm86_vol_spb_orig;
+}
+
+// 0fa9
+static bool pmd_data_init(struct driver_pmd *pmd) {
+ if (pmd->data[-1] > 1) return false;
+ pmd->opm_flag = pmd->data[-1];
+ if (pmd->datalen < 0x18) return false;
+ if (pmd->data[0] != 0x18) {
+ if (pmd->datalen < (0x18+2)) return false;
+ pmd->tone_ptr = read16le(&pmd->data[0x18]);
+ pmd->tone_included = true;
+ } else {
+ pmd->tone_included = false;
+ }
+ // 0fcd
+ for (int pi = 0; pi <= PMD_PART_RHYTHM; pi++) {
+ struct pmd_part *p = &pmd->parts[pi];
+ p->ptr = read16le(&pmd->data[pi*2]);
+ if (pmd->datalen < (p->ptr + 1)) return false;
+ p->len_cnt = 1;
+ p->keystatus.off = true;
+ p->keystatus.off_mask = true;
+ p->md_cnt = 0xff;
+ p->md_cnt_set = 0xff;
+ p->md_cnt_b = 0xff;
+ p->md_cnt_set_b = 0xff;
+ p->actual_note = 0xff;
+ p->curr_note = 0xff;
+ if (pi <= PMD_PART_FM_6) {
+ p->vol = 108;
+ p->fm_pan_ams_pms = 0xc0;
+ p->fm_slotmask = 0xf0;
+ p->fm_tone_slotmask = 0xff;
+ } else if (pi <= PMD_PART_SSG_3) {
+ p->vol = 8;
+ p->ssg_mix = 0x07;
+ p->ssg_env_state_old = SSG_ENV_STATE_OLD_OFF;
+ } else if (pi == PMD_PART_ADPCM) {
+ p->vol = 128;
+ p->fm_pan_ams_pms = 0xc0;
+ } else if (pi == PMD_PART_RHYTHM) {
+ p->vol = 15;
+ }
+ }
+ // 1049
+ // part R
+ pmd->r_offset = read16le(&pmd->data[0x16]);
+ pmd->r_ptr = 0;
+ return true;
+}
+
+// 111c
+static void pmd_reset_opna(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ // enable FM 4-6ch and set IRQ mask
+ work->opna_writereg(work, 0x29, 0x83);
+ // set ssg noise freq to 0 and write
+ pmd->ssg_noise_freq = 0;
+ if (!pmd->ssgeff_priority) {
+ work->opna_writereg(work, 0x06, 0x00);
+ pmd->ssg_noise_freq_wrote = 0;
+ }
+ // 1139
+ // reset pan/ams/pms
+ for (int a = 0; a < 2; a++) {
+ a ? pmd_opna_a1_on(pmd) : pmd_opna_a1_off(pmd);
+ for (int c = 0; c < 3; c++) {
+ pmd_reg_write(work, pmd, 0xb4+c, 0xc0);
+ }
+ }
+ pmd_opna_a1_off(pmd);
+ // 1166
+ // reset HLFO
+ pmd->opna_last22 = 0x00;
+ work->opna_writereg(work, 0x22, 0x00);
+ // 1170
+ // reset opnarhythm
+ for (int i = 0; i < 6; i++) {
+ pmd->opnarhythm_ilpan[i] = 0xcf;
+ }
+ work->opna_writereg(work, 0x10, 0xff);
+ if (pmd->opnarhythm_voldown) {
+ pmd->opnarhythm_tl = (0xc0 * (0x100-pmd->opnarhythm_voldown)) >> 10;
+ } else {
+ pmd->opnarhythm_tl = 0x30;
+ }
+ work->opna_writereg(work, 0x11, pmd->opnarhythm_tl);
+ // 11a0
+ // adpcm: set limit address to 0xffff
+ if (!pmd->adpcm_disabled) {
+ work->opna_writereg(work, 0x10c, 0xff);
+ work->opna_writereg(work, 0x10d, 0xff);
+ }
+ // 11b3
+ // reset ppz8 pan
+ // dx=5 ah=0x13 al=i-1 call 0790
+ if (work->ppz8_functbl) {
+ for (int c = 0; c < 8; c++) {
+ work->ppz8_functbl->channel_pan(work->ppz8, c, 5);
+ }
+ }
+}
+
+// 2329
+static void pmd_calc_tempo(
+ struct driver_pmd *pmd
+) {
+ uint8_t timerb = 0x100 - pmd->timerb;
+ int tempo = 0xff;
+ if (timerb) {
+ tempo = 0x112c / timerb;
+ if ((0x112c % timerb) & 0x80) tempo++;
+ if (tempo > 0xff) tempo = 0xff;
+ }
+ pmd->tempo = tempo;
+ pmd->tempo_bak = tempo;
+}
+
+// 2348
+static void pmd_calc_tempo_rev(
+ struct driver_pmd *pmd
+) {
+ uint8_t tempo = pmd->tempo;
+ int timerb = 0;
+ if (tempo) {
+ timerb = 0x112c / tempo;
+ if (0x112c % tempo) timerb++;
+ timerb = 0x100 - timerb;
+ if (timerb < 0) timerb = 0;
+ }
+ pmd->timerb = timerb;
+ pmd->timerb_bak = timerb;
+}
+
+// 3e43
+static void pmd_timerb_write(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ // fast forward not implemented
+ pmd->timerb_wrote = pmd->timerb;
+ work->opna_writereg(work, 0x26, pmd->timerb);
+}
+
+// 32eb
+static void pmd_reset_timer(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ pmd->timerb = 200;
+ pmd->timerb_bak = pmd->timerb;
+ pmd_calc_tempo(pmd);
+ pmd_timerb_write(work, pmd);
+ // set timera to 0x000
+ // enable both timers
+ work->opna_writereg(work, 0x25, 0x00);
+ work->opna_writereg(work, 0x24, 0x00);
+ work->opna_writereg(work, 0x27, 0x3f);
+ pmd->tick_cnt = 0;
+ pmd->meas_cnt = 0;
+ pmd->meas_len = 96;
+}
+
+// 2cf6
+static void pmd_part_off_ssg(
+ struct pmd_part *part
+) {
+ if (part->actual_note == 0xff) return;
+ if (part->ssg_env_state_old == SSG_ENV_STATE_OLD_NEW) {
+ part->ssg_env_state_new = SSG_ENV_STATE_NEW_RR;
+ } else {
+ part->ssg_env_state_old = SSG_ENV_STATE_OLD_RR;
+ }
+}
+
+// 3102
+static void pmd_lfo_reset(
+ struct pmd_part *part
+) {
+ part->lfo_diff = 0;
+ part->lfo = part->lfo_set;
+ part->md_cnt = part->md_cnt_set;
+ if (part->lfo_waveform == LFO_WF_SQUARE ||
+ part->lfo_waveform == LFO_WF_RANDOM) {
+ part->lfo.speed = 1;
+ } else {
+ part->lfo.speed++;
+ }
+}
+
+// 26ec
+static void pmd_opnarhythm_tl_write(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ uint8_t vol
+) {
+ uint8_t outvol = pmd->fadeout_vol;
+ if (outvol) {
+ outvol = ((uint16_t)vol * (0x100-outvol)) >> 8;
+ }
+ work->opna_writereg(work, 0x11, outvol);
+}
+
+static uint8_t pmd_part_cmdload(
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (!part->ptr || (pmd->datalen < (part->ptr + 1))) {
+ part->ptr = 0;
+ return 0x80;
+ }
+ return pmd->data[part->ptr++];
+}
+
+// 2f5a
+static void pmd_part_md_tick(
+ struct pmd_part *part
+) {
+ if (--part->md_speed) return;
+ part->md_speed = part->md_speed_set;
+ if (!part->md_cnt) return;
+ if (!(part->md_cnt & 0x80)) part->md_cnt--;
+ // 2f73
+ uint8_t step = part->lfo.step;
+ if (step & 0x80) {
+ // 2f7a
+ step = -step;
+ step += part->md_depth;
+ if (step & 0x80) {
+ // 2f87
+ step = 0;
+ if (part->md_depth & 0x80) step = 0x81;
+ } else {
+ // 2f81
+ step = -step;
+ }
+ } else {
+ // 2f93
+ step += part->md_depth;
+ if (step & 0x80) {
+ step = 0;
+ if (part->md_depth & 0x80) step = 0x7f;
+ }
+ }
+ part->lfo.step = step;
+}
+
+// 2fa8
+static uint16_t pmd_rand(
+ struct driver_pmd *pmd,
+ uint16_t range
+) {
+ uint16_t rand = pmd->rand * 0x103u;
+ rand += 3;
+ rand &= 0x7fffu;
+ pmd->rand = rand;
+ uint32_t mul = (uint32_t)rand * range;
+ return mul / 0x7fffu;
+}
+
+// 2e7f
+static void pmd_lfo_tick_waveform(
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->lfo.speed != 1) {
+ if (part->lfo.speed != 0xff) {
+ part->lfo.speed--;
+ }
+ return;
+ }
+ part->lfo.speed = part->lfo_set.speed;
+ switch (part->lfo_waveform) {
+ case LFO_WF_TRIANGLE1:
+ case LFO_WF_TRIANGLE2:
+ case LFO_WF_TRIANGLE3:
+ {
+ int16_t deptha = u8s8(part->lfo.step);
+ if (part->lfo_waveform == LFO_WF_TRIANGLE3) {
+ deptha *= (deptha > 0) ? deptha : -deptha;
+ }
+ // 2ec6
+ part->lfo_diff = u16s16((uint16_t)part->lfo_diff + (uint16_t)deptha);
+ if (!part->lfo_diff) pmd_part_md_tick(part);
+ if (part->lfo.times != 0xff) {
+ if (!--part->lfo.times) {
+ uint8_t times = part->lfo_set.times;
+ if (part->lfo_waveform != LFO_WF_TRIANGLE2) times *= 2;
+ part->lfo.times = times;
+ part->lfo.step = -part->lfo.step;
+ }
+ }
+ }
+ break;
+ case LFO_WF_SAWTOOTH:
+ {
+ int16_t deptha = u8s8(part->lfo.step);
+ part->lfo_diff = u16s16((uint16_t)part->lfo_diff + (uint16_t)deptha);
+ if (part->lfo.times != 0xff) {
+ if (!--part->lfo.times) {
+ // 2f09
+ part->lfo_diff = -part->lfo_diff;
+ pmd_part_md_tick(part);
+ part->lfo.times = part->lfo_set.times * 2;
+ }
+ }
+ }
+ break;
+ case LFO_WF_SQUARE:
+ part->lfo_diff = u8s8(part->lfo.step) * u8s8(part->lfo.times);
+ pmd_part_md_tick(part);
+ part->lfo.step = -part->lfo.step;
+ break;
+ case LFO_WF_RANDOM:
+ {
+ // 2f40
+ int deptha = u8s8(part->lfo.step);
+ if (deptha < 0) deptha = -deptha;
+ uint16_t range = (unsigned)deptha * part->lfo.times;
+ uint16_t rand = pmd_rand(pmd, range*2);
+ rand -= range;
+ part->lfo_diff = rand;
+ pmd_part_md_tick(part);
+ }
+ break;
+ case LFO_WF_ONESHOT:
+ if (part->lfo.times) {
+ if (part->lfo.times != 0xff) {
+ part->lfo.times--;
+ }
+ part->lfo_diff = u16s16((uint16_t)part->lfo_diff + (uint16_t)u8s8(part->lfo.step));
+ }
+ break;
+ }
+}
+
+// 2e48
+// returns true if lfo_diff updated
+static bool pmd_lfo_tick(
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->lfo.delay) {
+ part->lfo.delay--;
+ return false;
+ }
+ int16_t lfo_diff_prev = part->lfo_diff;
+ if (part->flagext.lfo) {
+ // 2e58
+ uint8_t cnt = pmd->timera_cnt - pmd->timera_cnt_b;
+ for (int i = 0; i < cnt; i++) {
+ pmd_lfo_tick_waveform(pmd, part);
+ }
+ } else {
+ // 2e6f
+ pmd_lfo_tick_waveform(pmd, part);
+ }
+ // 2e76
+ return part->lfo_diff != lfo_diff_prev;
+}
+
+// 2493
+static void pmd_part_lfo_flip(struct pmd_part *part) {
+ int16_t tmpi16;
+ uint8_t tmpu8;
+ struct pmd_lfo tmplfo;
+ struct pmd_part_lfo_flags tmplfof;
+ struct pmd_part_flagext tmpflagext;
+
+ tmpi16 = part->lfo_diff_b;
+ part->lfo_diff_b = part->lfo_diff;
+ part->lfo_diff = tmpi16;
+ tmplfof = part->lfof_b;
+ part->lfof_b = part->lfof;
+ part->lfof = tmplfof;
+ tmpflagext = part->flagext_b;
+ part->flagext_b = part->flagext;
+ part->flagext = tmpflagext;
+ tmplfo = part->lfo_b;
+ part->lfo_b = part->lfo;
+ part->lfo = tmplfo;
+ tmplfo = part->lfo_set_b;
+ part->lfo_set_b = part->lfo_set;
+ part->lfo_set = tmplfo;
+ tmpu8 = part->md_depth_b;
+ part->md_depth_b = part->md_depth;
+ part->md_depth = tmpu8;
+ tmpu8 = part->md_speed_b;
+ part->md_speed_b = part->md_speed;
+ part->md_speed = tmpu8;
+ tmpu8 = part->md_speed_set_b;
+ part->md_speed_set_b = part->md_speed_set;
+ part->md_speed_set = tmpu8;
+ tmpu8 = part->lfo_waveform_b;
+ part->lfo_waveform_b = part->lfo_waveform;
+ part->lfo_waveform = tmpu8;
+ tmpu8 = part->md_cnt_b;
+ part->md_cnt_b = part->md_cnt;
+ part->md_cnt = tmpu8;
+ tmpu8 = part->md_cnt_set_b;
+ part->md_cnt_set_b = part->md_cnt_set;
+ part->md_cnt_set = tmpu8;
+}
+
+// 30a6
+static void pmd_lfo_tick_if_needed_hlfo(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ part->hlfo_delay = part->hlfo_delay_set;
+ if (part->hlfo_delay) {
+ pmd_reg_write(work, pmd, 0xb3+pmd->proc_ch, part->fm_pan_ams_pms & 0xc0);
+ }
+ // 30c0
+ part->slot_delay_cnt = part->slot_delay;
+ if (part->lfof.freq || part->lfof.vol) {
+ if (!part->lfof.sync) {
+ pmd_lfo_reset(part);
+ }
+ pmd_lfo_tick(pmd, part);
+ }
+ // 30db
+ if (part->lfof_b.freq || part->lfof_b.vol) {
+ if (!part->lfof_b.sync) {
+ pmd_part_lfo_flip(part);
+ pmd_lfo_reset(part);
+ pmd_part_lfo_flip(part);
+ }
+ pmd_part_lfo_flip(part);
+ pmd_lfo_tick(pmd, part);
+ pmd_part_lfo_flip(part);
+ }
+}
+
+// 3086
+static void pmd_lfo_tick_if_needed(
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->lfof.freq || part->lfof.vol) {
+ pmd_lfo_tick(pmd, part);
+ }
+ // 3091
+ if (part->lfof_b.freq || part->lfof_b.vol) {
+ pmd_part_lfo_flip(part);
+ pmd_lfo_tick(pmd, part);
+ pmd_part_lfo_flip(part);
+ }
+}
+
+// 31ce
+static void pmd_ssg_env_tick_new(
+ struct pmd_part *part
+) {
+ switch (part->ssg_env_state_new) {
+ case SSG_ENV_STATE_NEW_AR:
+ // 31d2
+ {
+ uint8_t ar = part->ssg_env_param[SSG_ENV_PARAM_NEW_AR];
+ if (((unsigned)(ar-1)) & 0x80) {
+ // 31ff
+ if (part->ssg_env_param_set[SSG_ENV_PARAM_NEW_AR]) {
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_AR]++;
+ }
+ } else {
+ // 31d9
+ part->ssg_env_vol = u8s8(part->ssg_env_vol + ar);
+ if (part->ssg_env_vol <= 0xf) {
+ // 31e4
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_AR] =
+ part->ssg_env_param_set[SSG_ENV_PARAM_NEW_AR] - 0x10;
+ } else {
+ // 31ee
+ part->ssg_env_vol = 0xf;
+ part->ssg_env_state_new = SSG_ENV_STATE_NEW_DR;
+ if (part->ssg_env_param_sl == 0xf) {
+ part->ssg_env_state_new = SSG_ENV_STATE_NEW_SR;
+ }
+ }
+ }
+ }
+ break;
+ case SSG_ENV_STATE_NEW_DR:
+ // 320d
+ {
+ uint8_t dr = part->ssg_env_param[SSG_ENV_PARAM_NEW_DR];
+ if (((unsigned)(dr-1)) & 0x80) {
+ // 3238
+ if (part->ssg_env_param_set[SSG_ENV_PARAM_NEW_DR]) {
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_DR]++;
+ }
+ } else {
+ // 3214
+ part->ssg_env_vol = u8s8(part->ssg_env_vol - dr);
+ uint8_t sl = part->ssg_env_param_sl;
+ if ((part->ssg_env_vol >= 0) && (part->ssg_env_vol >= sl)) {
+ // 3223
+ uint8_t newdr = part->ssg_env_param_set[SSG_ENV_PARAM_NEW_DR] - 0x10;
+ if (newdr & 0x80) newdr += newdr;
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_DR] = newdr;
+ } else {
+ // 3231
+ part->ssg_env_vol = sl;
+ part->ssg_env_state_new = SSG_ENV_STATE_NEW_SR;
+ }
+ }
+ }
+ break;
+ case SSG_ENV_STATE_NEW_SR:
+ // 3246
+ {
+ uint8_t sr = part->ssg_env_param[SSG_ENV_PARAM_NEW_SR];
+ if (((unsigned)(sr-1)) & 0x80) {
+ // 3266
+ if (part->ssg_env_param_set[SSG_ENV_PARAM_NEW_SR]) {
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_SR]++;
+ }
+ } else {
+ // 324d
+ part->ssg_env_vol = u8s8(part->ssg_env_vol - sr);
+ if (part->ssg_env_vol < 0) part->ssg_env_vol = 0;
+ uint8_t newsr = part->ssg_env_param_set[SSG_ENV_PARAM_NEW_SR] - 0x10;
+ if (newsr & 0x80) newsr += newsr;
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_SR] = newsr;
+ }
+ }
+ break;
+ default: // SSG_ENV_STATE_NEW_RR
+ // 3273
+ {
+ uint8_t rr = part->ssg_env_param[SSG_ENV_PARAM_NEW_RR];
+ if (((unsigned)(rr-1)) & 0x80) {
+ // 3291
+ if (part->ssg_env_param_set[SSG_ENV_PARAM_NEW_RR]) {
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_RR]++;
+ }
+ } else {
+ // 327a
+ part->ssg_env_vol = u8s8((unsigned)part->ssg_env_vol - rr);
+ if (part->ssg_env_vol < 0) part->ssg_env_vol = 0;
+ uint8_t newrr = part->ssg_env_param_set[SSG_ENV_PARAM_NEW_RR];
+ newrr += newrr;
+ newrr -= 0x10;
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_RR] = newrr;
+ }
+ }
+ break;
+ }
+}
+
+// 31b9
+// returns true if volume update needed
+static bool pmd_ssg_env_tick_new_check(
+ struct pmd_part *part
+) {
+ if (part->ssg_env_state_new == SSG_ENV_STATE_NEW_OFF) return false;
+ int8_t prev_vol = part->ssg_env_vol;
+ pmd_ssg_env_tick_new(part);
+ return part->ssg_env_vol != prev_vol;
+}
+
+// 3060
+static void pmd_part_lfo_init_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t note
+) {
+ uint8_t n = note & 0xf;
+ if (n == 0xc) {
+ note = part->curr_note;
+ n = note & 0xf;
+ }
+ // 3072
+ part->curr_note = note;
+
+ if (n == 0xf) {
+ pmd_lfo_tick_if_needed(pmd, part);
+ return;
+ }
+ part->portamento_diff = 0;
+
+ if (pmd->no_keyoff) {
+ pmd_lfo_tick_if_needed(pmd, part);
+ } else {
+ pmd_lfo_tick_if_needed_hlfo(work, pmd, part);
+ }
+}
+
+// 2fc4
+static void pmd_part_lfo_init_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t note
+) {
+ uint8_t n = note & 0xf;
+ if (n == 0xc) {
+ note = part->curr_note;
+ n = note & 0xf;
+ }
+ // 2fd6
+ part->curr_note = note;
+ if (n == 0xf) {
+ pmd_lfo_tick_if_needed(pmd, part);
+ return;
+ }
+ // 2fe1
+ part->portamento_diff = 0;
+ if (pmd->no_keyoff) {
+ pmd_lfo_tick_if_needed(pmd, part);
+ return;
+ }
+ if (part->ssg_env_state_old != SSG_ENV_STATE_OLD_NEW) {
+ // 2ff6
+ part->ssg_env_state_old = SSG_ENV_STATE_OLD_AL;
+ part->ssg_env_vol = 0;
+ // isn't this reversed?
+ uint8_t al = part->ssg_env_param_set[SSG_ENV_PARAM_OLD_AL] =
+ part->ssg_env_param[SSG_ENV_PARAM_OLD_AL];
+ if (!al) {
+ // 3008
+ part->ssg_env_state_old = SSG_ENV_STATE_OLD_SR;
+ part->ssg_env_vol = part->ssg_env_param[SSG_ENV_PARAM_OLD_AD];
+ }
+ // 3012
+ part->ssg_env_param_set[SSG_ENV_PARAM_OLD_SR] =
+ part->ssg_env_param[SSG_ENV_PARAM_OLD_SR];
+ part->ssg_env_param_set[SSG_ENV_PARAM_OLD_RR] =
+ part->ssg_env_param[SSG_ENV_PARAM_OLD_RR];
+ pmd_lfo_tick_if_needed_hlfo(work, pmd, part);
+ return;
+ } else {
+ // 3021
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_AR] =
+ part->ssg_env_param_set[SSG_ENV_PARAM_NEW_AR] - 0x10;
+ int dr = part->ssg_env_param_set[SSG_ENV_PARAM_NEW_DR] - 0x10;
+ if (dr < 0) dr += dr;
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_DR] = dr;
+ int sr = part->ssg_env_param_set[SSG_ENV_PARAM_NEW_SR] - 0x10;
+ if (sr < 0) sr += sr;
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_SR] = sr;
+ uint8_t rr = part->ssg_env_param_set[SSG_ENV_PARAM_NEW_RR];
+ rr += rr;
+ rr -= 0x10;
+ part->ssg_env_param[SSG_ENV_PARAM_NEW_RR] = rr;
+ // 304f
+ part->ssg_env_vol = part->ssg_env_param_al;
+ part->ssg_env_state_new = SSG_ENV_STATE_NEW_AR;
+ // 31b9
+ pmd_ssg_env_tick_new_check(part);
+ pmd_lfo_tick_if_needed_hlfo(work, pmd, part);
+ return;
+ }
+}
+
+// 0e5e
+static void pmd_ssgeff_stop(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ // TODO: PPSDRV
+ work->opna_writereg(work, 0x0a, 0x00);
+ uint8_t ssgout = pmd_opna_read_ssgout(work);
+ ssgout |= (1<<2) | (1<<5);
+ work->opna_writereg(work, 0x07, ssgout);
+ pmd->ssgeff_priority = 0;
+ pmd->ssgeff_num = 0xff;
+ pmd->ssgeff_on = false;
+}
+
+// 0def
+static void pmd_ssgeff_advance(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ const struct pmd_ssgeff_data *ssgeff = pmd->ssgeff_ptr;
+ if (ssgeff->wait == 0xff) {
+ pmd_ssgeff_stop(work, pmd);
+ return;
+ }
+ pmd->ssgeff_wait = ssgeff->wait;
+ pmd->ssgeff_tonefreq = ssgeff->tone_freq;
+ work->opna_writereg(work, 0x04, pmd->ssgeff_tonefreq);
+ work->opna_writereg(work, 0x05, pmd->ssgeff_tonefreq>>8);
+ pmd->ssgeff_noisefreq = ssgeff->noise_freq;
+ work->opna_writereg(work, 0x06, pmd->ssgeff_noisefreq);
+ pmd->ssg_noise_freq_wrote = pmd->ssgeff_noisefreq;
+ uint8_t ssgout = pmd_opna_read_ssgout(work);
+ ssgout &= ~((1<<2) | (1<<5));
+ ssgout |= ((!ssgeff->tone_mix)<<2) | ((!ssgeff->noise_mix)<<5);
+ pmd->ssgeff_tone_mix = ssgeff->tone_mix;
+ pmd->ssgeff_noise_mix = ssgeff->noise_mix;
+ work->opna_writereg(work, 0x07, ssgout);
+ work->opna_writereg(work, 0x0a, ssgeff->env_level);
+ work->opna_writereg(work, 0x0b, ssgeff->env_freq);
+ work->opna_writereg(work, 0x0c, ssgeff->env_freq>>8);
+ work->opna_writereg(work, 0x0d, ssgeff->env_waveform);
+ pmd->ssgeff_tonefreq_add = ssgeff->tonefreq_add;
+ pmd->ssgeff_noisefreq_add = ssgeff->noisefreq_add;
+ pmd->ssgeff_noisefreq_cnt_set = ssgeff->noisefreq_wait;
+ pmd->ssgeff_noisefreq_cnt = pmd->ssgeff_noisefreq_cnt_set;
+ pmd->ssgeff_ptr++;
+}
+
+// 0e8d
+static void pmd_ssgeff_tick_idle(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ pmd->ssgeff_tonefreq += pmd->ssgeff_tonefreq_add;
+ work->opna_writereg(work, 0x04, pmd->ssgeff_tonefreq);
+ work->opna_writereg(work, 0x05, pmd->ssgeff_tonefreq>>8);
+ // pmd_opna_read_ssgout(work);
+ if (!pmd->ssgeff_noisefreq_add && !pmd->ssgeff_noisefreq_cnt_set) return;
+ if (--pmd->ssgeff_noisefreq_cnt) return;
+ pmd->ssgeff_noisefreq_cnt = pmd->ssgeff_noisefreq_cnt_set;
+ pmd->ssgeff_noisefreq += pmd->ssgeff_noisefreq_add;
+ work->opna_writereg(work, 0x06, pmd->ssgeff_noisefreq);
+ pmd->ssg_noise_freq_wrote = pmd->ssgeff_noisefreq;
+}
+
+// 0dde
+static void pmd_ssgeff_tick(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ if (--pmd->ssgeff_wait) {
+ pmd_ssgeff_tick_idle(work, pmd);
+ } else {
+ pmd_ssgeff_advance(work, pmd);
+ }
+}
+
+// 2dda
+static uint8_t *pmd_get_toneptr(
+ struct driver_pmd *pmd,
+ uint8_t tonenum
+) {
+ if (pmd->tone_included) {
+ for (uint16_t toneptr = pmd->tone_ptr; ; toneptr += 0x1a) {
+ if (pmd->datalen < (toneptr+0x1a)) return 0;
+ if (pmd->data[toneptr] == tonenum) {
+ return &pmd->data[toneptr+1];
+ }
+ }
+ }
+ // TODO: external tone
+ return 0;
+}
+
+// 2cbc
+static void pmd_part_do_keyoff_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ uint8_t out = pmd->proc_ch-1;
+ int ch_ind = pmd->proc_ch-1;
+ if (pmd->opna_a1) {
+ ch_ind += 3;
+ out |= 4;
+ }
+ pmd->fm_slotkey[ch_ind] &= ~part->fm_slotmask;
+ out |= pmd->fm_slotkey[ch_ind];
+ work->opna_writereg(work, 0x28, out);
+}
+
+// 2e15
+static bool pmd_part_tone_slotmask_stop(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ uint8_t mask = part->fm_tone_slotmask;
+ if (!mask) return false;
+ uint8_t addr = 0x3f + pmd->proc_ch;
+ for (int s = 0; s < 4; s++, addr += 4) {
+ if (!(mask & (1<<(7-s)))) continue;
+ // TL
+ pmd_reg_write(work, pmd, addr, 0x7f);
+ // SL/RR (why 0x7f instead of 0xff?)
+ pmd_reg_write(work, pmd, addr+0x40, 0x7f);
+ }
+ pmd_part_do_keyoff_fm(work, pmd, part);
+ return true;
+}
+
+// 3ee7
+static const uint8_t pmd_alg_output_slot_table[8] = {
+ 0x80, 0x80, 0x80, 0x80, 0xa0, 0xe0, 0xe0, 0xf0
+};
+// 3eef
+static const uint8_t pmd_alg_tone_slot_table[8] = {
+ 0xee, 0xee, 0xee, 0xee, 0xcc, 0x88, 0x88, 0x00
+};
+
+// 2dce
+static void pmd_part_tone_tl_update(
+ struct pmd_part *part,
+ uint8_t const *toneptr
+) {
+ for (int s = 0; s < 4; s++) {
+ part->fm_tone_tl[s] = toneptr[0x04+s];
+ }
+}
+
+// 2d0d
+static void pmd_part_tone_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ uint8_t const * const toneptr = pmd_get_toneptr(pmd, part->tonenum);
+ if (!toneptr) return;
+ if (!pmd_part_tone_slotmask_stop(work, pmd, part)) {
+ // 2d15
+ //toneptr += 4;
+ } else {
+ // 2d1b
+ uint8_t fb_alg = toneptr[0x18];
+ if (pmd->tonemask_fb_alg) fb_alg = part->fb_alg;
+ if (!pmd->opna_a1 && pmd->proc_ch == 3) {
+ if (pmd->tonemask_fb_alg) {
+ // 2d43
+ fb_alg = pmd->fm3ex_fb_alg;
+ } else {
+ // 2d49
+ if (!(part->fm_slotmask & 0x10)) {
+ fb_alg &= 0x7;
+ fb_alg |= pmd->fm3ex_fb_alg & 0x38;
+ }
+ // 2d59
+ pmd->fm3ex_fb_alg = fb_alg;
+ }
+ }
+ // 2d5d
+ pmd_reg_write(work, pmd, 0xaf+pmd->proc_ch, fb_alg);
+ part->fb_alg = fb_alg;
+ uint8_t outslot = pmd_alg_output_slot_table[fb_alg&7];
+ if (!(part->vol_lfo_slotmask & 0xf)) {
+ part->vol_lfo_slotmask = outslot;
+ }
+ if (!(part->vol_lfo_slotmask_b & 0xf)) {
+ part->vol_lfo_slotmask_b = outslot;
+ }
+ // 2d83
+ part->fm_slotout = outslot;
+ uint8_t tone_slotmask = part->fm_tone_slotmask;
+ uint8_t tl_slotmask = pmd_alg_tone_slot_table[fb_alg&7] & tone_slotmask;
+ for (int s = 0; s < 4; s++) {
+ if (tone_slotmask & (1<<(7-s))) {
+ pmd_reg_write(work, pmd, 0x2f+pmd->proc_ch+s*4, toneptr[0x00+s]);
+ }
+ }
+ for (int s = 0; s < 4; s++) {
+ if (tl_slotmask & (1<<(7-s))) {
+ pmd_reg_write(work, pmd, 0x3f+pmd->proc_ch+s*4, toneptr[0x04+s]);
+ }
+ }
+ for (int s = 0; s < 4; s++) {
+ if (tone_slotmask & (1<<(3-s))) {
+ pmd_reg_write(work, pmd, 0x4f+pmd->proc_ch+s*4, toneptr[0x08+s]);
+ pmd_reg_write(work, pmd, 0x6f+pmd->proc_ch+s*4, toneptr[0x10+s]);
+ }
+ if (tone_slotmask & (1<<(7-s))) {
+ pmd_reg_write(work, pmd, 0x5f+pmd->proc_ch+s*4, toneptr[0x0c+s]);
+ pmd_reg_write(work, pmd, 0x7f+pmd->proc_ch+s*4, toneptr[0x14+s]);
+ }
+ }
+ }
+ // 2dce
+ pmd_part_tone_tl_update(part, toneptr);
+}
+
+// 0d28, 0d09
+static void pmd_ssg_effect(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ uint8_t effnum
+) {
+ if (effnum > PMD_SSGEFF_CNT) return;
+ pmd->ssgeff_internal = true;
+ if (pmd->ssgeff_disabled) return;
+ // TODO: PPSDRV
+ // 0da4
+ pmd->ssgeff_num = effnum;
+ // 3ef7
+ if (pmd->ssgeff_priority > pmd_ssgeff_table[effnum].priority) return;
+ // TODO: PPSDRV
+ // 0dc5
+ pmd->ssgeff_ptr = pmd_ssgeff_table[effnum].data;
+ pmd->parts[PMD_PART_SSG_3].mask.effect = true;
+ pmd_ssgeff_advance(work, pmd);
+ pmd->ssgeff_priority = pmd_ssgeff_table[effnum].priority;
+ pmd->ssgeff_on = true;
+}
+
+// 1a58
+static void pmd_cmd_null_16(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ for (int i = 0; i < 16; i++) {
+ pmd_part_cmdload(pmd, part);
+ }
+}
+
+// 1a5b
+static void pmd_cmd_null_6(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ for (int i = 0; i < 6; i++) {
+ pmd_part_cmdload(pmd, part);
+ }
+}
+
+// 1a5c
+static void pmd_cmd_null_5(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ for (int i = 0; i < 5; i++) {
+ pmd_part_cmdload(pmd, part);
+ }
+}
+
+// 1a5d
+static void pmd_cmd_null_4(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ for (int i = 0; i < 4; i++) {
+ pmd_part_cmdload(pmd, part);
+ }
+}
+
+// 1a5e
+static void pmd_cmd_null_3(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ for (int i = 0; i < 3; i++) {
+ pmd_part_cmdload(pmd, part);
+ }
+}
+
+// 1a5f
+static void pmd_cmd_null_2(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ for (int i = 0; i < 2; i++) {
+ pmd_part_cmdload(pmd, part);
+ }
+}
+
+// 1a60
+static void pmd_cmd_null_1(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ pmd_part_cmdload(pmd, part);
+}
+
+// 1a61
+static void pmd_cmd_null_0(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+}
+
+// 271a
+static uint8_t pmd_part_note_transpose(
+ struct pmd_part *part,
+ uint8_t note
+) {
+ if (note == 0x0f) return note;
+ int8_t transpose = u8s8((uint8_t)(part->transpose + part->transpose_master));
+ if (!transpose) return note;
+ uint8_t octave = note >> 4;
+ note &= 0x0f;
+ if (transpose < 0) {
+ int newnote = note + transpose;
+ while (newnote < 0) {
+ octave--;
+ newnote += 0xc;
+ }
+ note = newnote;
+ } else {
+ int newnote = note + transpose;
+ while (newnote >= 0xc) {
+ octave++;
+ newnote -= 0xc;
+ }
+ note = newnote;
+ }
+ return octave<<4 | note;
+}
+
+// 2771
+static void pmd_note_freq_fm(
+ struct pmd_part *part,
+ uint8_t note
+) {
+ if ((note & 0xf) == 0xf) {
+ // 2798
+ part->actual_note = 0xff;
+ if (!part->lfof.freq && !part->lfof_b.freq) {
+ part->actual_freq = 0;
+ }
+ return;
+ }
+ // 277b
+ part->actual_note = note;
+ static const uint16_t fm_tonetable[0x10] = {
+ // 3eb7
+ // round(144*440*(2**((-9+i)/12))*(1<<17)/7987200)
+ 0x026a,
+ 0x028f,
+ 0x02b6,
+ 0x02df,
+ 0x030b,
+ 0x0339,
+ 0x036a,
+ 0x039e,
+ 0x03d5,
+ 0x0410,
+ 0x044e,
+ 0x048f,
+ 0, 0, 0, 0
+ };
+ uint16_t freq = fm_tonetable[note & 0x0f];
+ freq |= (note & 0x70) << 7;
+ part->actual_freq = freq;
+}
+
+// 27a8
+static void pmd_note_freq_ssg(
+ struct pmd_part *part,
+ uint8_t note
+) {
+ if ((note & 0xf) == 0xf) {
+ part->actual_note = 0xff;
+ if (!part->lfof.freq && !part->lfof_b.freq) {
+ part->actual_freq = 0;
+ }
+ return;
+ }
+ part->actual_note = note;
+ static const uint16_t ssg_tonetable[0x10] = {
+ // 3ecf
+ // round(7987200/(64*440*(2**-4)*(2.0**((3+i)/12))))
+ 0x0ee8,
+ 0x0e12,
+ 0x0d48,
+ 0x0c89,
+ 0x0bd5,
+ 0x0b2b,
+ 0x0a8a,
+ 0x09f3,
+ 0x0964,
+ 0x08dd,
+ 0x085e,
+ 0x07e6,
+ 0, 0, 0, 0
+ };
+ uint8_t octave = note >> 4;
+ uint16_t tonefreq = ssg_tonetable[note & 0x0f];
+ if (octave) {
+ tonefreq >>= (octave-1);
+ bool inc = tonefreq & 1;
+ tonefreq >>= 1;
+ tonefreq += inc;
+ }
+ part->actual_freq = tonefreq;
+}
+
+// 14a0
+static void pmd_part_calc_gate(
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (pmd->datalen < (part->ptr + 1)) {
+ if (pmd->data[part->ptr] == 0xc1) {
+ part->ptr++;
+ part->gate = 0;
+ return;
+ }
+ }
+ uint8_t gate = part->gate_abs;
+ if (part->gate_rel) {
+ // 14ae
+ gate += ((uint16_t)part->len_cnt * part->gate_rel) >> 8;
+ }
+ // 14b8
+ if (part->gate_rand_range) {
+ // 14be
+ uint16_t range = part->gate_rand_range & 0x7f;
+ range++;
+ uint16_t rand = pmd_rand(pmd, range);
+ if (part->gate_rand_range & 0x80) {
+ gate -= rand;
+ } else {
+ gate += rand;
+ }
+ }
+ // 14de
+ if (part->gate_min) {
+ int len = part->len_cnt - part->gate_min;
+ if (len < 0) {
+ part->gate = 0;
+ return;
+ }
+ if (gate >= len) {
+ gate = len;
+ }
+ }
+ // 14f2
+ part->gate = gate;
+}
+
+// 2b4c
+// bl = slotmask
+// al = vol
+static void pmd_fm_vol_out_sub(
+ uint8_t slotmask,
+ uint8_t *table,
+ uint8_t vol
+) {
+ for (int s = 0; s < 4; s++) {
+ if (!(slotmask & (1<<(7-s)))) continue;
+ if (!(vol & 0x80)) {
+ int newvol = table[s] - vol;
+ if (newvol < 0) newvol = 0;
+ table[s] = newvol;
+ } else {
+ int newvol = table[s] + (uint8_t)(-vol);
+ if (newvol > 0xff) newvol = 0xff;
+ table[s] = newvol;
+ }
+ }
+}
+
+// 2a38
+static void pmd_fm_vol_out(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ uint8_t slotmask = part->fm_slotmask;
+ if (!slotmask) return;
+ uint8_t vol = part->volume_save;
+ if (vol) {
+ vol--;
+ } else {
+ vol = part->vol;
+ }
+ // 2a4e
+ if (part != &pmd->parts[PMD_PART_FM_EFF]) {
+ // 2a56
+ if (pmd->fm_voldown) {
+ uint8_t voldown = -pmd->fm_voldown;
+ vol = (vol * voldown) >> 8;
+ }
+ if (pmd->fadeout_vol) {
+ uint8_t fadeout = -pmd->fadeout_vol;
+ vol = (vol * fadeout) >> 8;
+ }
+ }
+ // 2a72
+ // 4, 3, 2, 1
+ uint8_t table[4] = {
+ 0x80, 0x80, 0x80, 0x80
+ };
+ vol ^= 0xff;
+ uint8_t oslotmask = slotmask & part->fm_slotout;
+ uint8_t update_slots = oslotmask;
+ for (int s = 0; s < 4; s++) {
+ if (oslotmask & (1<<(7-s))) table[s] = vol;
+ }
+ // 2aa8
+ if (vol != 0xff) {
+ if (part->lfof.vol) {
+ uint8_t lslotmask = slotmask & part->vol_lfo_slotmask;
+ update_slots |= lslotmask;
+ pmd_fm_vol_out_sub(lslotmask, table, part->lfo_diff);
+ }
+ if (part->lfof_b.vol) {
+ uint8_t lslotmask = slotmask & part->vol_lfo_slotmask_b;
+ update_slots |= lslotmask;
+ pmd_fm_vol_out_sub(lslotmask, table, part->lfo_diff_b);
+ }
+ }
+ // 2ad3
+ for (int s = 0; s < 4; s++) {
+ if (!(update_slots & (1<<(7-s)))) continue;
+ static const uint8_t tladdr[4] = {
+ 0x4b, 0x43, 0x47, 0x3f
+ };
+ static const uint8_t toneind[4] = {
+ 3, 1, 2, 0
+ };
+ int sout = table[s] + part->fm_tone_tl[toneind[s]];
+ if (sout > 0xff) sout = 0xff;
+ sout -= 0x80;
+ if (sout < 0) sout = 0;
+ pmd_reg_write(work, pmd, tladdr[s]+pmd->proc_ch, sout);
+ }
+}
+
+// 2b70
+static void pmd_ssg_vol_out(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->ssg_env_state_old == SSG_ENV_STATE_OLD_OFF ||
+ (part->ssg_env_state_old == SSG_ENV_STATE_OLD_NEW &&
+ part->ssg_env_state_new == SSG_ENV_STATE_NEW_OFF)) {
+ return;
+ }
+ uint8_t vol = part->volume_save;
+ if (vol) {
+ vol--;
+ } else {
+ vol = part->vol;
+ }
+ // 2b91
+ if (pmd->ssg_voldown) {
+ uint8_t voldown = -pmd->ssg_voldown;
+ vol = vol * voldown >> 8;
+ }
+ if (pmd->fadeout_vol) {
+ uint8_t fadeout = -pmd->fadeout_vol;
+ vol = vol * fadeout >> 8;
+ }
+ // 2bad
+ if (vol) {
+ if (part->ssg_env_state_old == SSG_ENV_STATE_OLD_NEW) {
+ // 2bb7
+ uint8_t envvol = part->ssg_env_vol;
+ if (!envvol) {
+ // 2bd9
+ vol = 0;
+ // -> 2c0f
+ } else {
+ vol = (vol * (envvol+1)) >> 3;
+ if (vol & 1) {
+ vol >>= 1;
+ vol++;
+ } else {
+ vol >>= 1;
+ }
+ }
+ } else {
+ // 2bd4
+ vol += part->ssg_env_vol;
+ if (vol & 0x80) vol = 0; // -> 2c0f
+ if (vol > 0xf) vol = 0xf;
+ }
+ if (vol) {
+ // 2be4
+ if (part->lfof.vol || part->lfof_b.vol) {
+ int32_t lfovol = 0;
+ // TODO:
+ if (part->lfof.vol) vol += part->lfo_diff;
+ if (part->lfof_b.vol) vol += part->lfo_diff_b;
+ lfovol += vol;
+ vol = u8s8(lfovol);
+ if (lfovol < 0) {
+ vol = 0;
+ } else if (lfovol > 0xf) {
+ vol = 0xf;
+ }
+ }
+ }
+ }
+ // 2c0f
+ work->opna_writereg(work, 0x07+pmd->proc_ch, vol);
+}
+
+// 2985
+static void pmd_ssg_freq_out(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ uint16_t freq = part->actual_freq;
+ if (!freq) return;
+ freq += (unsigned)part->portamento_diff;
+ if (!part->flagext.detune) {
+ // 2996
+ freq += (unsigned)(-part->detune);
+ if (part->lfof.freq) {
+ freq += (unsigned)(-part->lfo_diff);
+ }
+ if (part->lfof_b.freq) {
+ freq += (unsigned)(-part->lfo_diff_b);
+ }
+ } else {
+ // 29ad
+ if (part->detune) {
+ int32_t det = (((int32_t)freq) * ((int32_t)part->detune)) >> 12;
+ if (det >= 0) {
+ det++;
+ } else {
+ det--;
+ }
+ freq += (uint16_t)(-det);
+ }
+ if (part->lfof.freq || part->lfof_b.freq) {
+ // 29db
+ int32_t lfo = 0;
+ if (part->lfof.freq) lfo += part->lfo_diff;
+ if (part->lfof_b.freq) lfo += part->lfo_diff_b;
+ if (lfo) {
+ lfo = (((int32_t)freq) * lfo) >> 12;
+ if (lfo >= 0) {
+ lfo++;
+ } else {
+ lfo--;
+ }
+ freq += (uint16_t)(-lfo);
+ }
+ }
+ // 2a0d
+ }
+ // 2a10
+ if (freq >= 0x8000) freq = 0;
+ if (freq >= 0x1000) freq = 0x0fff;
+ // 2a20
+ part->output_freq = freq;
+ work->opna_writereg(work, (pmd->proc_ch-1)*2, freq);
+ work->opna_writereg(work, (pmd->proc_ch-1)*2+1, freq>>8);
+}
+
+// 2c9f
+static uint16_t pmd_part_ssg_readout(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ uint16_t ret = 0x0900<<(pmd->proc_ch-1);
+ ret |= pmd_opna_read_ssgout(work);
+ return ret;
+}
+
+// 2c67
+static void pmd_ssg_note_noise_out(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->actual_note == 0xff) return;
+ uint16_t noteout = pmd_part_ssg_readout(work, pmd);
+ uint8_t ssgout = noteout | (noteout >> 8);
+ uint8_t pmdout = (noteout >> 8) & part->ssg_mix;
+ ssgout &= ~pmdout;
+ work->opna_writereg(work, 0x07, ssgout);
+ if (pmd->ssg_noise_freq != pmd->ssg_noise_freq_wrote) {
+ if (pmd->ssgeff_num & 0x80) {
+ work->opna_writereg(work, 0x06, pmd->ssg_noise_freq);
+ pmd->ssg_noise_freq_wrote = pmd->ssg_noise_freq;
+ }
+ }
+}
+
+// 148b
+static void pmd_part_loop_check(
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ pmd->loop.looped &= part->loop.looped;
+ pmd->loop.ended &= part->loop.ended;
+ pmd->loop.env &= part->loop.env;
+}
+
+// 2c19
+static void pmd_part_fm_keyon(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->actual_note == 0xff) return;
+ uint8_t *slotkey = &pmd->fm_slotkey[pmd->proc_ch-1+(3*pmd->opna_a1)];
+ *slotkey |= part->fm_slotmask;
+ if (part->slot_delay_cnt) {
+ *slotkey &= part->slot_delay_mask;
+ }
+ work->opna_writereg(work, 0x28, *slotkey | (pmd->proc_ch-1) | (pmd->opna_a1<<2));
+}
+
+// 2942
+static uint32_t pmd_blkfnum_normalize(
+ uint16_t blk,
+ uint16_t fnum
+) {
+ for (;;) {
+ if ((fnum & 0x8000) || (fnum < 0x26a)) {
+ int32_t iblk = (int32_t)blk - 0x0800;
+ if (iblk < 0) {
+ // 2976
+ blk = 0;
+ if (fnum & 0x8000) fnum = 0x0008;
+ if (fnum < 0x0008) fnum = 0x0008;
+ break;
+ }
+ blk = iblk;
+ fnum += 0x26a;
+ continue;
+ }
+ if (fnum < 0x4d4) break;
+ // 2950
+ blk += 0x0800;
+ if (blk != 0x4000) {
+ fnum -= 0x26a;
+ continue;
+ }
+ blk = 0x3800;
+ if (fnum > 0x07ff) fnum = 0x7ff;
+ break;
+ }
+ return (((uint32_t)blk) << 16) | fnum;
+}
+
+// 27d8
+static void pmd_fm_freq_out(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ uint16_t fnum = part->actual_freq;
+ if (!fnum) return;
+ if (!part->fm_slotmask) return;
+ uint16_t blk = fnum & 0x3800;
+ fnum &= 0x7ff;
+ fnum += (uint16_t)part->portamento_diff;
+ fnum += (uint16_t)part->detune;
+ if (pmd->proc_ch != 3 || pmd->opna_a1 || pmd->fm3ex_mode == 0x3f) {
+ // 280c
+ if (part->lfof.freq) fnum += (uint16_t)part->lfo_diff;
+ if (part->lfof_b.freq) fnum += (uint16_t)part->lfo_diff_b;
+ uint32_t blkfnum = pmd_blkfnum_normalize(blk, fnum);
+ blkfnum |= blkfnum >> 16;
+ blkfnum &= 0xffff;
+ part->output_freq = blkfnum;
+ pmd_reg_write(work, pmd, pmd->proc_ch + 0xa3, blkfnum >> 8);
+ pmd_reg_write(work, pmd, pmd->proc_ch + 0x9f, blkfnum);
+
+ } else {
+ // 2837
+ uint8_t fm_slotmask = part->fm_slotmask;
+ uint8_t lfo_slotmask = part->vol_lfo_slotmask;
+ if (!(lfo_slotmask & 0x0f)) {
+ lfo_slotmask = 0xf0;
+ }
+ uint8_t lfo_slotmask_b = part->vol_lfo_slotmask_b;
+ if (!(lfo_slotmask_b & 0x0f)) {
+ lfo_slotmask_b = 0xf0;
+ }
+ static const uint8_t slot_addr[2][4] = {
+ {0xad, 0xae, 0xac, 0xa6}, {0xa9, 0xaa, 0xa8, 0xa2}
+ };
+ for (int s = 3; s >= 0; s--) {
+ const uint8_t slot_bit = (1 << (4+s));
+ if (fm_slotmask & slot_bit) {
+ // 2858
+ uint16_t fnums = fnum + (uint16_t)pmd->fm3ex_det[s];
+ if ((lfo_slotmask & slot_bit) && part->lfof.freq) fnums += (uint16_t)part->lfo_diff;
+ if ((lfo_slotmask_b & slot_bit) && part->lfof_b.freq) fnums += (uint16_t)part->lfo_diff_b;
+ uint32_t blkfnum = pmd_blkfnum_normalize(blk, fnums);
+ blkfnum |= blkfnum >> 16;
+ blkfnum &= 0xffff;
+ part->output_freq = blkfnum;
+ pmd_reg_write(work, pmd, slot_addr[0][s], blkfnum >> 8);
+ pmd_reg_write(work, pmd, slot_addr[1][s], blkfnum);
+ }
+ }
+ }
+}
+
+// 13b5
+static void pmd_part_fm_out(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->volume_save && part->actual_note != 0xff) {
+ if (!pmd->volume_saved) {
+ part->volume_save = 0;
+ }
+ pmd->volume_saved = false;
+ }
+ pmd_fm_vol_out(work, pmd, part);
+ pmd_fm_freq_out(work, pmd, part);
+ pmd_part_fm_keyon(work, pmd, part);
+ part->note_proc++;
+ pmd->no_keyoff = false;
+ pmd->volume_saved = false;
+ part->keystatus.off = false;
+ part->keystatus.off_mask = false;
+ if (pmd->datalen > (part->ptr + 1)) {
+ if (pmd->data[part->ptr] == 0xfb) {
+ part->keystatus.off_mask = true;
+ }
+ }
+ pmd_part_loop_check(pmd, part);
+}
+
+// 15d4
+static void pmd_part_ssg_out(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->volume_save && part->actual_note != 0xff) {
+ if (!pmd->volume_saved) {
+ part->volume_save = 0;
+ }
+ pmd->volume_saved = false;
+ }
+ // 15ef
+ pmd_ssg_vol_out(work, pmd, part);
+ pmd_ssg_freq_out(work, pmd, part);
+ pmd_ssg_note_noise_out(work, pmd, part);
+ part->note_proc++;
+ pmd->no_keyoff = false;
+ pmd->volume_saved = 0;
+ part->keystatus.off = false;
+ part->keystatus.off_mask = false;
+ if (pmd->datalen > (part->ptr + 1)) {
+ if (pmd->data[part->ptr] == 0xfb) {
+ part->keystatus.off_mask = true;
+ }
+ }
+ pmd_part_loop_check(pmd, part);
+}
+
+// none
+static bool pmd_part_masked(
+ const struct pmd_part *part
+) {
+ return part->mask.ext || part->mask.effect || part->mask.disabled
+ || part->mask.slot || part->mask.mml || part->mask.ff;
+}
+
+// 1f7e
+static void pmd_fm_freq_out_mask(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (!pmd_part_masked(part)) {
+ pmd_fm_freq_out(work, pmd, part);
+ }
+}
+
+// 1ee3
+static void pmd_fm3ex_mode_update(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ int partbit;
+ if (part == &pmd->parts[PMD_PART_FM_3]) partbit = 1;
+ else if (part == &pmd->parts[PMD_PART_FM_3B]) partbit = 2;
+ else if (part == &pmd->parts[PMD_PART_FM_3C]) partbit = 4;
+ else partbit = 8;
+
+ bool fm3ex;
+ if (!(part->fm_slotmask & 0xf0)) fm3ex = false;
+ else if (part->fm_slotmask != 0xf0) fm3ex = true;
+ else if (!(part->vol_lfo_slotmask & 0x0f)) fm3ex = false;
+ else if (part->lfof.freq) fm3ex = true;
+ else if (!(part->vol_lfo_slotmask_b & 0x0f)) fm3ex = false;
+ else if (part->lfof_b.freq) fm3ex = true;
+ else fm3ex = false;
+ if (!fm3ex) {
+ pmd->fm3ex_needed &= ~partbit;
+ } else {
+ pmd->fm3ex_needed |= partbit;
+ }
+ uint8_t fm3ex_mode;
+ if (pmd->fm3ex_needed || pmd->fm3ex_force) {
+ fm3ex_mode = 0x7f;
+ } else {
+ fm3ex_mode = 0x3f;
+ }
+ if (fm3ex_mode == pmd->fm3ex_mode) return;
+ pmd->fm3ex_mode = fm3ex_mode;
+ // output to 0x27 to update fm3exmode
+ // but mask with 0xcf to prevent timer resetting
+ work->opna_writereg(work, 0x27, fm3ex_mode & 0xcf);
+ if (fm3ex_mode == 0x3f) return;
+ if (part == &pmd->parts[PMD_PART_FM_3]) return;
+ pmd_fm_freq_out_mask(work, pmd, &pmd->parts[PMD_PART_FM_3]);
+ if (part == &pmd->parts[PMD_PART_FM_3B]) return;
+ pmd_fm_freq_out_mask(work, pmd, &pmd->parts[PMD_PART_FM_3B]);
+ if (part == &pmd->parts[PMD_PART_FM_3C]) return;
+ pmd_fm_freq_out_mask(work, pmd, &pmd->parts[PMD_PART_FM_3C]);
+}
+
+// 1d7b
+static bool pmd_fm3ex_mode_update_check(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (pmd->proc_ch == 3 && !pmd->opna_a1) {
+ pmd_fm3ex_mode_update(work, pmd, part);
+ return true;
+ }
+ return false;
+}
+
+// 2268
+static void pmd_cmdff_tonenum(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t tonenum = pmd_part_cmdload(pmd, part);
+ part->tonenum = tonenum;
+ if (!pmd_part_masked(part)) {
+ pmd_part_tone_fm(work, pmd, part);
+ return;
+ }
+ // 2277
+ uint8_t const * const toneptr = pmd_get_toneptr(pmd, tonenum);
+ if (!toneptr) return;
+ part->fb_alg = toneptr[0x18];
+ pmd_part_tone_tl_update(part, toneptr);
+ if (pmd->proc_ch == 3 && part->fm_tone_slotmask && !pmd->opna_a1) {
+ uint8_t fb_alg = part->fb_alg;
+ if (!(part->fm_slotmask & 0x10)) {
+ fb_alg &= 0x07;
+ fb_alg |= pmd->fm3ex_fb_alg & 0x38;
+ }
+ pmd->fm3ex_fb_alg = fb_alg;
+ part->fb_alg = fb_alg;
+ }
+}
+
+// 22b3
+static void pmd_cmdfe_gate_abs(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ (void)work;
+ part->gate_abs = pmd_part_cmdload(pmd, part);
+ part->gate_rand_range = 0;
+}
+
+// 22cb
+static void pmd_cmdfd_vol(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ (void)work;
+ part->vol = pmd_part_cmdload(pmd, part);
+}
+
+// 22d0
+static void pmd_cmdfc_tempo(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ (void)work;
+ uint8_t val = pmd_part_cmdload(pmd, part);
+ if (val < 0xfb) {
+ pmd->timerb = val;
+ pmd->timerb_bak = val;
+ pmd_calc_tempo(pmd);
+ } else if (val == 0xff) {
+ // 22e1
+ uint8_t tempo = pmd_part_cmdload(pmd, part);
+ if (tempo < 0x12) tempo = 0x12;
+ pmd->tempo = tempo;
+ pmd->tempo_bak = tempo;
+ pmd_calc_tempo_rev(pmd);
+ } else if (val == 0xfe) {
+ // 22f0
+ int timerbdiff = u8s8(pmd_part_cmdload(pmd, part));
+ int timerb = pmd->timerb_bak + timerbdiff;
+ if (timerb > 0xfa) timerb = 0xfa;
+ if (timerb < 0) timerb = 0;
+ pmd->timerb = timerb;
+ pmd->timerb_bak = timerb;
+ pmd_calc_tempo(pmd);
+ } else {
+ // 2313
+ int tempodiff = u8s8(pmd_part_cmdload(pmd, part));
+ int tempo = pmd->tempo_bak + tempodiff;
+ if (tempo > 0xff) tempo = 0xff;
+ if (tempo < 0x12) tempo = 0x12;
+ pmd->tempo = tempo;
+ pmd->tempo_bak = tempo;
+ pmd_calc_tempo_rev(pmd);
+ }
+}
+
+// 236b
+static void pmd_cmdfb_tie(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->no_keyoff = true;
+}
+
+// 2371, 2376
+static void pmd_cmdfa_d5_det(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ (void)work;
+ uint16_t val = pmd_part_cmdload(pmd, part);
+ val |= ((uint16_t)(pmd_part_cmdload(pmd, part)) << 8);
+ part->detune = u16s16(val);
+}
+
+// 237b
+static void pmd_cmdf9_repeat_reset(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint16_t ptr = pmd_part_cmdload(pmd, part);
+ ptr |= ((uint16_t)(pmd_part_cmdload(pmd, part)) << 8);
+ ptr++;
+ if (pmd->datalen < (ptr+1)) {
+ // TODO: better error handling
+ part->ptr = 0;
+ } else {
+ pmd->data[ptr] = 0;
+ }
+}
+
+// 2391
+static void pmd_cmdf8_repeat(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t repeat = pmd_part_cmdload(pmd, part);
+ if (repeat) {
+ // 0xf8 repeat cnt ptr
+ if (pmd->datalen < (part->ptr+1)) {
+ part->ptr = 0;
+ return;
+ }
+ pmd->data[part->ptr]++;
+ uint8_t repeatcnt = pmd_part_cmdload(pmd, part);
+ if (repeat == repeatcnt) {
+ part->ptr += 2;
+ return;
+ }
+ } else {
+ // 0xf8 0x00 0x00 ptr
+ part->ptr++;
+ part->loop.looped = true;
+ part->loop.ended = false;
+ }
+ // 23a7
+ uint16_t ptr = pmd_part_cmdload(pmd, part);
+ ptr |= ((uint16_t)(pmd_part_cmdload(pmd, part)) << 8);
+ ptr += 2;
+ part->ptr = ptr;
+}
+
+// 23bd
+static void pmd_cmdf7_repeat_exit(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint16_t ptr = pmd_part_cmdload(pmd, part);
+ ptr |= ((uint16_t)(pmd_part_cmdload(pmd, part)) << 8);
+ if (pmd->datalen < (ptr+2)) {
+ part->ptr = 0;
+ return;
+ }
+ uint8_t repeat = pmd->data[ptr];
+ uint8_t repeatcnt = pmd->data[ptr+1];
+ if (repeatcnt == (repeat-1)) {
+ part->ptr = ptr+4;
+ }
+}
+
+// 23de
+static void pmd_cmdf6_set_loop(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->loop_ptr = part->ptr;
+}
+
+// 23e2
+static void pmd_cmdf5_transpose(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->transpose = pmd_part_cmdload(pmd, part);
+}
+
+// 23f4
+static void pmd_cmdf4_volinc_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->vol += 4;
+ if (part->vol > 0x7f) part->vol = 0x7f;
+}
+
+// 2409
+static void pmd_cmdf4_volinc_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ if (part->vol < 0xf) part->vol++;
+}
+
+// 241c
+static void pmd_cmdf3_voldec_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ int vol = part->vol - 4;
+ if (vol < 0) vol = 0;
+ part->vol = vol;
+}
+
+// 2435
+static void pmd_cmdf3_voldec_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ if (part->vol) part->vol--;
+}
+
+// 24ea
+static void pmd_cmdf2_lfo(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->lfo_set.delay = pmd_part_cmdload(pmd, part);
+ part->lfo_set.speed = pmd_part_cmdload(pmd, part);
+ part->lfo_set.step = pmd_part_cmdload(pmd, part);
+ part->lfo_set.times = pmd_part_cmdload(pmd, part);
+ part->lfo = part->lfo_set;
+ pmd_lfo_reset(part);
+}
+
+// 2513
+static void pmd_cmdf1_lfo_switch(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t val = pmd_part_cmdload(pmd, part);
+ if (val & 0xf8) val = 1;
+ part->lfof.freq = val & (1<<0);
+ part->lfof.vol = val & (1<<1);
+ part->lfof.sync = val & (1<<2);
+ pmd_lfo_reset(part);
+}
+
+// 2526
+static void pmd_cmdf1_lfo_switch_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd_cmdf1_lfo_switch(work, pmd, part);
+ pmd_fm3ex_mode_update_check(work, pmd, part);
+}
+
+// 1d31
+static void pmd_cmdf1_ppsdrv(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ // TODO: PPSDRV
+}
+
+// 252c
+static void pmd_cmdf0_env_old(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t val;
+ val = pmd_part_cmdload(pmd, part);
+ part->ssg_env_param_set[SSG_ENV_PARAM_OLD_AL] = val;
+ part->ssg_env_param[SSG_ENV_PARAM_OLD_AL] = val;
+ val = pmd_part_cmdload(pmd, part);
+ part->ssg_env_param_set[SSG_ENV_PARAM_OLD_AD] = val;
+ val = pmd_part_cmdload(pmd, part);
+ part->ssg_env_param_set[SSG_ENV_PARAM_OLD_SR] = val;
+ part->ssg_env_param[SSG_ENV_PARAM_OLD_SR] = val;
+ val = pmd_part_cmdload(pmd, part);
+ part->ssg_env_param_set[SSG_ENV_PARAM_OLD_RR] = val;
+ part->ssg_env_param[SSG_ENV_PARAM_OLD_RR] = val;
+ if (part->ssg_env_state_old == SSG_ENV_STATE_OLD_NEW) {
+ part->ssg_env_state_old = SSG_ENV_STATE_OLD_RR;
+ part->ssg_env_vol = -15;
+ }
+}
+
+// 2554
+static void pmd_cmdef_poke(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t reg = pmd_part_cmdload(pmd, part);
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ pmd_reg_write(work, pmd, reg, data);
+}
+
+// 255c
+static void pmd_cmdee_noise_freq(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->ssg_noise_freq = pmd_part_cmdload(pmd, part);
+}
+
+// 2576
+static void pmd_cmded_ssgmix(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->ssg_mix = pmd_part_cmdload(pmd, part);
+}
+
+// 25cc
+static uint8_t pmd_hlfo_delay_check(
+ struct pmd_part *part,
+ uint8_t pan_ams_pms
+) {
+ if (part->hlfo_delay) pan_ams_pms &= 0xc0;
+ return pan_ams_pms;
+}
+
+// 257c
+static void pmd_part_set_pan(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t pan
+){
+ uint8_t pan_ams_pms = (pan & 3) << 6;
+ pan_ams_pms |= part->fm_pan_ams_pms & 0x3f;
+ part->fm_pan_ams_pms = pan_ams_pms;
+ // 258e
+ if (pmd->proc_ch == 3 && !pmd->opna_a1) {
+ pmd->parts[PMD_PART_FM_3].fm_pan_ams_pms = pan_ams_pms;
+ pmd->parts[PMD_PART_FM_3B].fm_pan_ams_pms = pan_ams_pms;
+ pmd->parts[PMD_PART_FM_3C].fm_pan_ams_pms = pan_ams_pms;
+ pmd->parts[PMD_PART_FM_3D].fm_pan_ams_pms = pan_ams_pms;
+ }
+ // 25b6
+ if (pmd_part_masked(part)) return;
+ pan_ams_pms = pmd_hlfo_delay_check(part, pan_ams_pms);
+ pmd_reg_write(work, pmd, 0xb3+pmd->proc_ch, pan_ams_pms);
+}
+
+// 257b
+static void pmd_cmdec_pan(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd_part_set_pan(work, pmd, part, pmd_part_cmdload(pmd, part));
+}
+
+// 265d
+static void pmd_opnarhythm_inc(uint8_t *incdata, uint8_t val) {
+ for (int i = 0; i < 6; i++) {
+ if (val & (1<<i)) {
+ incdata[i]++;
+ }
+ }
+}
+
+// 25ed
+static void pmd_cmdeb_opnarhythm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t val = pmd_part_cmdload(pmd, part);
+ val &= pmd->opnarhythm_mask;
+ if (!val) return;
+ if (pmd->fadeout_vol) {
+ pmd_opnarhythm_tl_write(work, pmd, pmd->opnarhythm_tl);
+ }
+ if (!(val & 0x80)) {
+ // 260c
+ for (int i = 0; i < 6; i++) {
+ if (val & (1<<i)) {
+ work->opna_writereg(work, 0x18+i, pmd->opnarhythm_ilpan[i]);
+ }
+ }
+ }
+ // 2626
+ work->opna_writereg(work, 0x10, val);
+ if (val & 0x80) {
+ // 262d
+ pmd_opnarhythm_inc(pmd->opnarhythm_dump_inc, val);
+ pmd->opnarhythm &= val ^ 0xff;
+ } else {
+ // 263b
+ pmd_opnarhythm_inc(pmd->opnarhythm_shot_inc, val);
+ pmd->opnarhythm |= val;
+ }
+}
+
+// 266c
+static void pmd_cmdea_opnarhythm_il(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t val = pmd_part_cmdload(pmd, part);
+ uint8_t i = (val >> 5) - 1;
+ if (i >= 6) return;
+ uint8_t ilpan = pmd->opnarhythm_ilpan[i];
+ ilpan &= ~0x1fu;
+ ilpan |= val & 0x1f;
+ pmd->opnarhythm_ilpan[i] = ilpan;
+ work->opna_writereg(work, 0x18+i, ilpan);
+}
+
+// 26c8
+static void pmd_cmde9_opnarhythm_pan(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t val = pmd_part_cmdload(pmd, part);
+ uint8_t i = (val >> 5) - 1;
+ if (i >= 6) return;
+ uint8_t ilpan = pmd->opnarhythm_ilpan[i];
+ ilpan &= ~0xc0u;
+ ilpan |= (val & 0x3) << 6;
+ pmd->opnarhythm_ilpan[i] = ilpan;
+ work->opna_writereg(work, 0x18+i, ilpan);
+}
+
+// 26d8
+static void pmd_cmde8_opnarhythm_tl(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t tl = pmd_part_cmdload(pmd, part);
+ if (pmd->opnarhythm_voldown) {
+ tl = ((uint16_t)tl * (0x100-pmd->opnarhythm_voldown)) >> 8;
+ }
+ pmd->opnarhythm_tl = tl;
+ if (pmd->fadeout_vol) {
+ tl = ((uint16_t)tl * (0x100-pmd->fadeout_vol)) >> 8;
+ }
+ work->opna_writereg(work, 0x11, tl);
+}
+
+// 23e7
+static void pmd_cmde7_transpose_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->transpose += pmd_part_cmdload(pmd, part);
+}
+
+// 26fe
+static void pmd_cmde6_opnarhythm_tl_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ int tl = pmd->opnarhythm_tl + u8s8(pmd_part_cmdload(pmd, part));
+ if (tl > 0x3f) tl = 0x3f;
+ if (tl < 0) tl = 0;
+ pmd->opnarhythm_tl = tl;
+ if (pmd->fadeout_vol) {
+ tl = ((uint16_t)tl * (0x100-pmd->fadeout_vol)) >> 8;
+ }
+ work->opna_writereg(work, 0x11, tl);
+}
+
+// 2695
+static void pmd_cmde5_opnarhythm_il_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t i = pmd_part_cmdload(pmd, part) - 1;
+ if (i >= 6) {
+ part->ptr++;
+ return;
+ }
+ int il = pmd->opnarhythm_ilpan[i] & 0x1f;
+ il += u8s8(pmd_part_cmdload(pmd, part));
+ if (il > 0x1f) il = 0x1f;
+ if (il < 0) il = 0;
+ pmd->opnarhythm_ilpan[i] &= ~0x1fu;
+ pmd->opnarhythm_ilpan[i] |= il;
+ work->opna_writereg(work, 0x18+i, pmd->opnarhythm_ilpan[i]);
+}
+
+// 225e
+static void pmd_cmde4_hlfo_delay(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->hlfo_delay_set = pmd_part_cmdload(pmd, part);
+}
+
+// 2403
+static void pmd_cmde3_vol_add_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = part->vol + pmd_part_cmdload(pmd, part);
+ if (vol > 0x7f) vol = 0x7f;
+ part->vol = vol;
+}
+
+// 2416
+static void pmd_cmde3_vol_add_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = part->vol + pmd_part_cmdload(pmd, part);
+ if (vol > 0xf) vol = 0xf;
+ part->vol = vol;
+}
+
+// FM: 2427
+// SSG: 2440
+static void pmd_cmde2_vol_sub(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ int vol = part->vol - pmd_part_cmdload(pmd, part);
+ if (vol < 0) vol = 0;
+ part->vol = vol;
+}
+
+// 2209
+static void pmd_cmde1_ams_pms(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t pan_ams_pms = pmd_part_cmdload(pmd, part);
+ pan_ams_pms |= part->fm_pan_ams_pms & 0xc0;
+ part->fm_pan_ams_pms = pan_ams_pms;
+ if (pmd->proc_ch == 3 && !pmd->opna_a1) {
+ pmd->parts[PMD_PART_FM_3].fm_pan_ams_pms = pan_ams_pms;
+ pmd->parts[PMD_PART_FM_3B].fm_pan_ams_pms = pan_ams_pms;
+ pmd->parts[PMD_PART_FM_3C].fm_pan_ams_pms = pan_ams_pms;
+ pmd->parts[PMD_PART_FM_3D].fm_pan_ams_pms = pan_ams_pms;
+ }
+ if (pmd_part_masked(part)) return;
+ pan_ams_pms = pmd_hlfo_delay_check(part, pan_ams_pms);
+ pmd_reg_write(work, pmd, 0xb3+pmd->proc_ch, pan_ams_pms);
+}
+
+// 2252
+static void pmd_cmde0_hlfo(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t hlfo = pmd_part_cmdload(pmd, part);
+ pmd->opna_last22 = hlfo;
+ work->opna_writereg(work, 0x22, hlfo);
+}
+
+// 2263
+static void pmd_cmddf_meas_len(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->meas_len = pmd_part_cmdload(pmd, part);
+}
+
+// 21cc
+static void pmd_cmdde_echo_init_add_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = part->vol + pmd_part_cmdload(pmd, part);
+ if (vol > 0x7f) vol = 0x7f;
+ part->volume_save = vol+1;
+ pmd->volume_saved = true;
+}
+
+// 21e1
+static void pmd_cmdde_echo_init_add_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = part->vol + pmd_part_cmdload(pmd, part);
+ if (vol > 0xf) vol = 0xf;
+ part->volume_save = vol+1;
+ pmd->volume_saved = true;
+}
+
+// 21fb
+static void pmd_cmddd_echo_init_sub(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ int vol = part->vol - pmd_part_cmdload(pmd, part);
+ if (vol < 0) vol = 0;
+ part->volume_save = vol+1;
+ pmd->volume_saved = true;
+}
+
+// 21be
+static void pmd_cmddc_status1(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->status1 = pmd_part_cmdload(pmd, part);
+}
+
+// 21c3
+static void pmd_cmddb_status1_add(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->status1 += pmd_part_cmdload(pmd, part);
+}
+
+// 2104
+static void pmd_cmdda_portamento_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t note = pmd_part_cmdload(pmd, part);
+ if (pmd_part_masked(part)) {
+ return;
+ }
+ pmd_part_lfo_init_fm(work, pmd, part, note);
+ note = pmd_part_note_transpose(part, note);
+ pmd_note_freq_fm(part, note);
+ uint16_t f1 = part->actual_freq;
+ uint8_t n1 = part->actual_note;
+ note = pmd_part_cmdload(pmd, part);
+ note = pmd_part_note_transpose(part, note);
+ pmd_note_freq_fm(part, note);
+ uint16_t f2 = part->actual_freq;
+ part->actual_freq = f1;
+ part->actual_note = n1;
+ uint8_t f2blk = f2 >> 11;
+ uint8_t f1blk = f1 >> 11;
+ f1 &= 0x7ff;
+ f2 &= 0x7ff;
+ int32_t blkdiff = (int32_t)(f2blk - f1blk) * 0x26a;
+ int32_t freqdiff = f2 - f1 + blkdiff;
+ uint8_t clocks = pmd_part_cmdload(pmd, part);
+ part->len = part->len_cnt = clocks;
+ pmd_part_calc_gate(pmd, part);
+ int16_t p_add = 0;
+ int16_t p_rem = 0;
+ if (clocks) {
+ p_add = freqdiff / clocks;
+ p_rem = freqdiff % clocks;
+ }
+ part->portamento_add = p_add;
+ part->portamento_rem = p_rem;
+ part->lfof.portamento = true;
+ pmd_part_fm_out(work, pmd, part);
+}
+
+// 2176
+static void pmd_cmdda_portamento_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t note = pmd_part_cmdload(pmd, part);
+ if (pmd_part_masked(part)) {
+ return;
+ }
+ pmd_part_lfo_init_ssg(work, pmd, part, note);
+ note = pmd_part_note_transpose(part, note);
+ pmd_note_freq_ssg(part, note);
+ uint16_t f1 = part->actual_freq;
+ uint8_t n1 = part->actual_note;
+ note = pmd_part_cmdload(pmd, part);
+ note = pmd_part_note_transpose(part, note);
+ pmd_note_freq_ssg(part, note);
+ uint16_t f2 = part->actual_freq;
+ part->actual_freq = f1;
+ part->actual_note = n1;
+ uint8_t clocks = pmd_part_cmdload(pmd, part);
+ part->len = part->len_cnt = clocks;
+ int freqdiff = f2 - f1;
+ pmd_part_calc_gate(pmd, part);
+ int16_t p_add = 0;
+ int16_t p_rem = 0;
+ if (clocks) {
+ p_add = freqdiff / clocks;
+ p_rem = freqdiff % clocks;
+ }
+ part->portamento_add = p_add;
+ part->portamento_rem = p_rem;
+ part->lfof.portamento = true;
+ pmd_part_ssg_out(work, pmd, part);
+}
+
+// 20bf
+static void pmd_cmdd6_md(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->md_speed = pmd_part_cmdload(pmd, part);
+ part->md_speed_set = part->md_speed;
+ part->md_depth = pmd_part_cmdload(pmd, part);
+}
+
+// 2054
+static void pmd_cmdd4_ssgeff(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t cmd = pmd_part_cmdload(pmd, part);
+ if (pmd_part_masked(part)) return;
+ if (cmd) {
+ pmd_ssg_effect(work, pmd, cmd);
+ } else {
+ pmd_ssgeff_stop(work, pmd);
+ }
+}
+
+// 206f
+static void pmd_cmdd3_fmeff(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ // TODO: 42ae
+ uint8_t val = pmd_part_cmdload(pmd, part);
+ if (pmd_part_masked(part)) return;
+ bool a1 = pmd->opna_a1;
+ if (val) {
+ //3be1();
+ } else {
+ //3c43();
+ }
+ pmd->opna_a1 = a1;
+}
+
+// 20b6
+static void pmd_cmdd2_fadeout(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ // TODO
+ pmd_part_cmdload(pmd, part);
+}
+
+// 2561
+static void pmd_cmdd0_ssgnoisefreqadd(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ int nf = pmd->ssg_noise_freq + u8s8(pmd_part_cmdload(pmd, part));
+ if (nf < 0) nf = 0;
+ if (nf > 0x1f) nf = 0x1f;
+ pmd->ssg_noise_freq = nf;
+}
+
+// 2044
+static void pmd_part_fm_keyon_if_not_masked(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (!pmd_part_masked(part) && !part->keystatus.off_mask)
+ pmd_part_fm_keyon(work, pmd, part);
+}
+
+// 1f88
+static void pmd_cmdcf_slotmask(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ if (data & 0x0f) {
+ part->fm_slotout = data << 4;
+ } else {
+ // 1f9c
+ uint8_t alg = 0;
+ if (pmd->proc_ch == 3 && !pmd->opna_a1) {
+ alg = pmd->fm3ex_fb_alg;
+ } else {
+ // 2dda
+ const uint8_t *toneptr = pmd_get_toneptr(pmd, part->tonenum);
+ if (toneptr) {
+ alg = toneptr[0x18];
+ }
+ }
+ alg &= 7;
+ part->fm_slotout = pmd_alg_output_slot_table[alg];
+ }
+ // 1fc9
+ data &= 0xf0;
+ if (data == part->fm_slotmask) return;
+ // 1fd1
+ part->fm_slotmask = data;
+ part->mask.slot = !data;
+ // 1fe3
+ if (pmd_fm3ex_mode_update_check(work, pmd, part)
+ && part != &pmd->parts[PMD_PART_FM_3]) {
+ pmd_part_fm_keyon_if_not_masked(work, pmd, &pmd->parts[PMD_PART_FM_3]);
+ if (part != &pmd->parts[PMD_PART_FM_3B]) {
+ pmd_part_fm_keyon_if_not_masked(work, pmd, &pmd->parts[PMD_PART_FM_3B]);
+ if (part != &pmd->parts[PMD_PART_FM_3C]) {
+ pmd_part_fm_keyon_if_not_masked(work, pmd, &pmd->parts[PMD_PART_FM_3C]);
+ }
+ }
+ }
+ // 2012
+ uint8_t tonemask = 0;
+ if (part->fm_slotmask & 0x80) tonemask |= 0x11;
+ if (part->fm_slotmask & 0x40) tonemask |= 0x44;
+ if (part->fm_slotmask & 0x20) tonemask |= 0x22;
+ if (part->fm_slotmask & 0x10) tonemask |= 0x88;
+ part->fm_tone_slotmask = tonemask;
+ part->proc_masked = pmd_part_masked(part);
+}
+
+// 1e1b
+static void pmd_cmdcd_env_new(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->ssg_env_param_set[SSG_ENV_PARAM_NEW_AR] = pmd_part_cmdload(pmd, part) & 0x1f;
+ part->ssg_env_param_set[SSG_ENV_PARAM_NEW_DR] = pmd_part_cmdload(pmd, part) & 0x1f;
+ part->ssg_env_param_set[SSG_ENV_PARAM_NEW_SR] = pmd_part_cmdload(pmd, part) & 0x1f;
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ part->ssg_env_param_set[SSG_ENV_PARAM_NEW_RR] = data & 0xf;
+ part->ssg_env_param_sl = (data >> 4) ^ 0xf;
+ part->ssg_env_param_al = pmd_part_cmdload(pmd, part) & 0xf;
+ if (part->ssg_env_state_old != SSG_ENV_STATE_OLD_NEW) {
+ part->ssg_env_state_old = SSG_ENV_STATE_OLD_NEW;
+ part->ssg_env_state_new = SSG_ENV_STATE_NEW_RR;
+ part->ssg_env_vol = 0;
+ }
+}
+
+// 1def
+static void pmd_cmdcc_det_ext(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->flagext.detune = pmd_part_cmdload(pmd, part) & 1;
+}
+
+// 1e16
+static void pmd_cmdcb_lfo_waveform(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->lfo_waveform = pmd_part_cmdload(pmd, part);
+}
+
+// 1dfa
+static void pmd_cmdca_lfo_ext(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->flagext.lfo = pmd_part_cmdload(pmd, part) & 1;
+}
+
+// 1e07
+static void pmd_cmdc9_env_ext(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->flagext.env = pmd_part_cmdload(pmd, part) & 1;
+}
+
+// 1e99
+static void pmd_cmdc8_fm3ex_det(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ if (pmd->proc_ch != 3 || pmd->opna_a1) {
+ pmd_cmd_null_3(work, pmd, part);
+ return;
+ }
+ // 1ead
+ uint8_t mask = pmd_part_cmdload(pmd, part);
+ uint16_t val16 = pmd_part_cmdload(pmd, part);
+ val16 |= ((uint16_t)pmd_part_cmdload(pmd, part)) << 8;
+ int16_t det = u16s16(val16);
+ for (int s = 0; s < 4; s++) {
+ if (mask & (1<<s)) {
+ pmd->fm3ex_det[s] = det;
+ }
+ }
+ // 1ecd
+ pmd->fm3ex_force = false;
+ for (int s = 0; s < 4; s++) {
+ if (pmd->fm3ex_det[s]) pmd->fm3ex_force = true;
+ }
+ pmd_fm3ex_mode_update(work, pmd, part);
+}
+
+// 1e5f
+static void pmd_cmdc7_fm3ex_det_add(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ if (pmd->proc_ch != 3 || pmd->opna_a1) {
+ pmd_cmd_null_3(work, pmd, part);
+ return;
+ }
+ // 1e73
+ uint8_t mask = pmd_part_cmdload(pmd, part);
+ uint16_t det = pmd_part_cmdload(pmd, part);
+ det |= ((uint16_t)pmd_part_cmdload(pmd, part)) << 8;
+ for (int s = 0; s < 4; s++) {
+ if (mask & (1<<s)) {
+ pmd->fm3ex_det[s] = u16s16((uint16_t)pmd->fm3ex_det[s] + det);
+ }
+ }
+ pmd->fm3ex_force = false;
+ for (int s = 0; s < 4; s++) {
+ if (pmd->fm3ex_det[s]) pmd->fm3ex_force = true;
+ }
+ pmd_fm3ex_mode_update(work, pmd, part);
+}
+
+// 1dc0
+static void pmd_fm3ex_init(
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint16_t ptr
+) {
+ part->ptr = ptr;
+ part->len = part->len_cnt = 1;
+ part->keystatus.off = true;
+ part->keystatus.off_mask = true;
+ part->md_cnt = 0xff;
+ part->md_cnt_set = 0xff;
+ part->md_cnt_b = 0xff;
+ part->md_cnt_set_b = 0xff;
+ part->actual_note = 0xff;
+ part->curr_note = 0xff;
+ part->vol = 0x6c;
+ part->fm_pan_ams_pms = pmd->parts[PMD_PART_FM_3].fm_pan_ams_pms;
+ part->mask.slot = true;
+}
+
+// 1d90
+static void pmd_cmdc6_fm3ex_init(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ for (int i = 0; i < 3; i++) {
+ uint16_t ptr = pmd_part_cmdload(pmd, part);
+ ptr |= ((uint16_t)pmd_part_cmdload(pmd, part)) << 8;
+ if (!ptr) continue;
+ pmd_fm3ex_init(pmd, &pmd->parts[PMD_PART_FM_3B+i], ptr);
+ }
+}
+
+// 1d45
+static void pmd_cmdc5_lfo_slotmask(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t mask = pmd_part_cmdload(pmd, part);
+ mask &= 0x0f;
+ if (mask) {
+ // 1d4a
+ mask <<= 4;
+ mask |= 0x0f;
+ part->vol_lfo_slotmask = mask;
+ } else {
+ // 1d59
+ part->vol_lfo_slotmask = part->fm_slotout;
+ }
+ // 1d7b
+ pmd_fm3ex_mode_update_check(work, pmd, part);
+}
+
+// 1d61
+static void pmd_cmdba_lfo2_slotmask(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t mask = pmd_part_cmdload(pmd, part);
+ mask &= 0x0f;
+ if (mask) {
+ // 1d4a
+ mask <<= 4;
+ mask |= 0x0f;
+ part->vol_lfo_slotmask_b = mask;
+ } else {
+ // 1d59
+ part->vol_lfo_slotmask_b = part->fm_slotout;
+ }
+ // 1d7b
+ pmd_fm3ex_mode_update_check(work, pmd, part);
+}
+
+// 22c6
+static void pmd_cmdc4_gate_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->gate_rel = pmd_part_cmdload(pmd, part);
+}
+
+// 25d9
+static void pmd_cmdc3_pan_ex(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ pmd_part_cmdload(pmd, part);
+ uint8_t pan;
+ if (data == 0) pan = 3;
+ else if (data & 0x80) pan = 1;
+ else pan = 2;
+ pmd_part_set_pan(work, pmd, part, pan);
+}
+
+// 2509
+static void pmd_cmdc2_lfo_delay(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t d = pmd_part_cmdload(pmd, part);
+ part->lfo.delay = d;
+ part->lfo_set.delay = d;
+ pmd_lfo_reset(part);
+}
+
+// 1a95
+// DF
+static void pmd_cmdc0_ff_fm_voldown(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->fm_voldown = pmd_part_cmdload(pmd, part);
+}
+
+// 1ab0
+// DF +/-
+static void pmd_cmdc0_fe_fm_voldown_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = pmd_part_cmdload(pmd, part);
+ if (!vol) {
+ pmd->fm_voldown = pmd->fm_voldown_orig;
+ } else {
+ int newvol = pmd->fm_voldown + u8s8(vol);
+ if (newvol > 0xff) newvol = 0xff;
+ if (newvol < 0) newvol = 0;
+ pmd->fm_voldown = newvol;
+ }
+}
+
+// 1a9c
+// DS
+static void pmd_cmdc0_fd_ssg_voldown(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->ssg_voldown = pmd_part_cmdload(pmd, part);
+}
+
+// 1ad6
+// DS +/-
+static void pmd_cmdc0_fc_ssg_voldown_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = pmd_part_cmdload(pmd, part);
+ if (!vol) {
+ pmd->ssg_voldown = pmd->ssg_voldown_orig;
+ } else {
+ int newvol = pmd->ssg_voldown + u8s8(vol);
+ if (newvol > 0xff) newvol = 0xff;
+ if (newvol < 0) newvol = 0;
+ pmd->ssg_voldown = newvol;
+ }
+}
+
+// 1aa1
+// DP
+static void pmd_cmdc0_fb_adpcm_voldown(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->adpcm_voldown = pmd_part_cmdload(pmd, part);
+}
+
+// 1ade
+// DP +/-
+static void pmd_cmdc0_fa_adpcm_voldown_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = pmd_part_cmdload(pmd, part);
+ if (!vol) {
+ pmd->adpcm_voldown = pmd->adpcm_voldown_orig;
+ } else {
+ int newvol = pmd->adpcm_voldown + u8s8(vol);
+ if (newvol > 0xff) newvol = 0xff;
+ if (newvol < 0) newvol = 0;
+ pmd->adpcm_voldown = newvol;
+ }
+}
+
+// 1aa6
+// DR
+static void pmd_cmdc0_f9_opnarhythm_voldown(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->opnarhythm_voldown = pmd_part_cmdload(pmd, part);
+}
+
+// 1ae6
+// DR +/-
+static void pmd_cmdc0_f8_opnarhythm_voldown_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = pmd_part_cmdload(pmd, part);
+ if (!vol) {
+ pmd->opnarhythm_voldown = pmd->opnarhythm_voldown_orig;
+ } else {
+ int newvol = pmd->opnarhythm_voldown + u8s8(vol);
+ if (newvol > 0xff) newvol = 0xff;
+ if (newvol < 0) newvol = 0;
+ pmd->opnarhythm_voldown = newvol;
+ }
+}
+
+// 1a8e
+// probably A
+static void pmd_cmdc0_f7_pcm86_vol(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->pcm86_vol_spb = pmd_part_cmdload(pmd, part) & 1;
+}
+
+
+// 1aab
+// unimplemented (disabled in PMDPPZ.COM)
+static void pmd_cmdc0_f6_ppz8_voldown(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd->ppz8_voldown = pmd_part_cmdload(pmd, part);
+}
+
+// 1aee
+// unimplemented (disabled in PMDPPZ.COM)
+static void pmd_cmdc0_f5_ppz8_voldown_rel(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t vol = pmd_part_cmdload(pmd, part);
+ if (!vol) {
+ pmd->ppz8_voldown = pmd->ppz8_voldown_orig;
+ } else {
+ int newvol = pmd->ppz8_voldown + u8s8(vol);
+ if (newvol > 0xff) newvol = 0xff;
+ if (newvol < 0) newvol = 0;
+ pmd->ppz8_voldown = newvol;
+ }
+}
+
+typedef void (*pmd_cmd_func) (
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+);
+
+static const pmd_cmd_func pmd_cmd_table_c0[] = {
+ pmd_cmdc0_ff_fm_voldown,
+ pmd_cmdc0_fe_fm_voldown_rel,
+ pmd_cmdc0_fd_ssg_voldown,
+ pmd_cmdc0_fc_ssg_voldown_rel,
+ pmd_cmdc0_fb_adpcm_voldown,
+ pmd_cmdc0_fa_adpcm_voldown_rel,
+ pmd_cmdc0_f9_opnarhythm_voldown,
+ pmd_cmdc0_f8_opnarhythm_voldown_rel,
+ pmd_cmdc0_f7_pcm86_vol,
+ // f6, f5 is disabled
+ pmd_cmdc0_f6_ppz8_voldown,
+ pmd_cmdc0_f5_ppz8_voldown_rel,
+};
+
+// 1a62
+static void pmd_cmdc0_extended(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t data
+) {
+ if (data < 0xf7) {
+ part->ptr = 0;
+ return;
+ }
+ pmd_cmd_table_c0[data^0xff](work, pmd, part);
+}
+
+// 1cc0
+static void pmd_part_fm_unmask(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (!part->fm_tone_slotmask) return;
+ //uint8_t tonenum = part->tonenum;
+ uint8_t fm_tone_tl[4];
+ for (int i = 0; i < 4; i++) {
+ fm_tone_tl[i] = part->fm_tone_tl[i];
+ }
+ pmd->tonemask_fb_alg = true;
+ pmd_part_tone_fm(work, pmd, part);
+ pmd->tonemask_fb_alg = false;
+ for (int i = 0; i < 4; i++) {
+ part->fm_tone_tl[i] = fm_tone_tl[i];
+ }
+ uint8_t slotmask = (part->fm_slotout ^ 0xff) & part->fm_slotmask;
+ if (slotmask) {
+ for (int s = 0; s < 4; s++) {
+ if (!(slotmask & (1<<(7-s)))) continue;
+ static const uint8_t regoff[4] = {3, 1, 2, 0};
+ pmd_reg_write(work, pmd, 0x3f+regoff[s]*4+pmd->proc_ch, fm_tone_tl[regoff[s]]);
+ }
+ }
+ // 1d23
+ pmd_reg_write(work, pmd, 0xb3+pmd->proc_ch, pmd_hlfo_delay_check(part, part->fm_pan_ams_pms));
+}
+
+// 1c50
+static void pmd_cmdc0_mml_mask_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ if (data >= 2) {
+ pmd_cmdc0_extended(work, pmd, part, data);
+ return;
+ } else if (data == 1) {
+ // 1c5c
+ part->mask.mml = false;
+ bool masked = pmd_part_masked(part);
+ part->mask.mml = true;
+ if (!masked) {
+ pmd_part_tone_slotmask_stop(work, pmd, part);
+ }
+ // 1c9c
+ part->proc_masked = true;
+ } else {
+ // 1ca0
+ part->mask.mml = false;
+ if (pmd_part_masked(part)) {
+ part->proc_masked = true;
+ } else {
+ pmd_part_fm_unmask(work, pmd, part);
+ part->proc_masked = false;
+ }
+ }
+}
+
+// 1c7a
+static void pmd_cmdc0_mml_mask_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ if (data >= 2) {
+ pmd_cmdc0_extended(work, pmd, part, data);
+ return;
+ } else if (data == 1) {
+ // 1c86
+ part->mask.mml = false;
+ bool masked = pmd_part_masked(part);
+ part->mask.mml = true;
+ if (!masked) {
+ // 1c90
+ uint16_t ssgout = pmd_part_ssg_readout(work, pmd);
+ ssgout |= ssgout>>8;
+ work->opna_writereg(work, 0x07, ssgout);
+ }
+ // 1c9c
+ part->proc_masked = true;
+ } else {
+ // 1ca0
+ part->mask.mml = false;
+ part->proc_masked = pmd_part_masked(part);
+ }
+}
+
+// 1caa
+static void pmd_cmdc0_mml_mask_rhythm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ if (data >= 2) {
+ pmd_cmdc0_extended(work, pmd, part, data);
+ return;
+ } else if (data == 1) {
+ part->mask.mml = true;
+ } else {
+ part->mask.mml = false;
+ }
+}
+
+// 244e
+static void pmd_cmdbf_lfo2(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd_part_lfo_flip(part);
+ pmd_cmdf2_lfo(work, pmd, part);
+ pmd_part_lfo_flip(part);
+}
+
+// 2473
+static void pmd_cmdbe_lfo2_switch(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t val = pmd_part_cmdload(pmd, part);
+ part->lfof_b.freq = val & (1<<0);
+ part->lfof_b.vol = val & (1<<1);
+ part->lfof_b.sync = val & (1<<2);
+ pmd_part_lfo_flip(part);
+ pmd_lfo_reset(part);
+ pmd_part_lfo_flip(part);
+}
+
+// 248d
+static void pmd_cmdbe_lfo2_switch_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd_cmdbe_lfo2_switch(work, pmd, part);
+ pmd_fm3ex_mode_update_check(work, pmd, part);
+}
+
+// 245f
+static void pmd_cmdbd_lfo2_md(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd_part_lfo_flip(part);
+ pmd_cmdd6_md(work, pmd, part);
+ pmd_part_lfo_flip(part);
+}
+
+// 2464
+static void pmd_cmdbc_lfo2_waveform(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd_part_lfo_flip(part);
+ pmd_cmdcb_lfo_waveform(work, pmd, part);
+ pmd_part_lfo_flip(part);
+}
+
+// 2469
+static void pmd_cmdbb_lfo2_ext(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd_part_lfo_flip(part);
+ pmd_cmdca_lfo_ext(work, pmd, part);
+ pmd_part_lfo_flip(part);
+}
+
+// 246e
+static void pmd_cmdb9_lfo2_delay(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ pmd_part_lfo_flip(part);
+ pmd_cmdc2_lfo_delay(work, pmd, part);
+ pmd_part_lfo_flip(part);
+}
+
+// 1b87
+// command O
+static void pmd_cmdb8_slottl(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ uint8_t slotmask = data & 0xf & (part->fm_slotmask>>4);
+ uint8_t val = pmd_part_cmdload(pmd, part);
+ bool masked = pmd_part_masked(part);
+ bool relative = data & 0x80;
+ static const uint8_t slottable[4] = {0, 2, 1, 3};
+ for (int s = 0; s < 4; s++) {
+ if (!(slotmask & (1<<s))) continue;
+ if (!relative) {
+ part->fm_tone_tl[slottable[s]] = val & 0x7f;
+ } else {
+ uint8_t vol = part->fm_tone_tl[slottable[s]] + val;
+ if (vol & 0x80) {
+ if (val & 0x80) vol = 0;
+ else vol = 0x7f;
+ }
+ part->fm_tone_tl[slottable[s]] = vol;
+ }
+ if (!masked) {
+ pmd_reg_write(work, pmd, 0x3f+pmd->proc_ch+8*s, part->fm_tone_tl[slottable[s]]);
+ }
+ }
+}
+
+// 20cb
+static void pmd_cmdb7_lfo_md_cnt(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ bool b = data & 0x80;
+ data &= 0x7f;
+ if (!data) data = 0xff;
+ if (!b) {
+ part->md_cnt = part->md_cnt_set = data;
+ } else {
+ part->md_cnt_b = part->md_cnt_set_b = data;
+ }
+}
+
+// 1b19
+static void pmd_part_write_fb(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t data
+) {
+ uint8_t newfb = (data & 7) << 3;
+ uint8_t fbalg;
+ if ((pmd->proc_ch == 3) && (!pmd->opna_a1)) {
+ pmd->fm3ex_fb_alg &= 7;
+ pmd->fm3ex_fb_alg |= newfb;
+ fbalg = pmd->fm3ex_fb_alg;
+ } else {
+ part->fb_alg &= 7;
+ part->fb_alg |= newfb;
+ fbalg = part->fb_alg;
+ }
+ if ((pmd->proc_ch == 3) && (!pmd->opna_a1) && (!(part->fm_slotmask & 0x10))) return;
+ pmd_reg_write(work, pmd, 0xaf+pmd->proc_ch, fbalg);
+ part->fb_alg = fbalg;
+}
+
+// 1b0e
+static void pmd_cmdb6_fb(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ uint8_t fbalg;
+ if (!(data & 0x80)) {
+ pmd_part_write_fb(work, pmd, part, data);
+ } else {
+ // 1b51
+ if (!(data & 0x40)) data &= 7;
+ if ((pmd->proc_ch == 3) && (!pmd->opna_a1)) {
+ fbalg = pmd->fm3ex_fb_alg;
+ } else {
+ fbalg = part->fb_alg;
+ }
+ fbalg >>= 3;
+ fbalg &= 7;
+ fbalg += data;
+ if (fbalg & 0x80) {
+ pmd_part_write_fb(work, pmd, part, 0);
+ } else if (fbalg > 7) {
+ pmd_part_write_fb(work, pmd, part, 7);
+ } else {
+ pmd_part_write_fb(work, pmd, part, fbalg);
+ }
+ }
+}
+
+// 1af6
+static void pmd_cmdb5_slot_delay(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ uint8_t data = pmd_part_cmdload(pmd, part);
+ data = (~data) & 0xf;
+ data <<= 4;
+ part->slot_delay_mask = data;
+ part->slot_delay = pmd_part_cmdload(pmd, part);
+ part->slot_delay_cnt = part->slot_delay;
+}
+
+// 22bc
+static void pmd_cmdb3_gate_min(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->gate_min = pmd_part_cmdload(pmd, part);
+}
+
+// 23ef
+static void pmd_cmdb2_transpose_master(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->transpose_master = pmd_part_cmdload(pmd, part);
+}
+
+// 22c1
+static void pmd_cmdb1_gate_rand_range(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+){
+ part->gate_rand_range = pmd_part_cmdload(pmd, part);
+}
+
+enum {
+ PMD_CMD_CNT = 0x100-0xb1
+};
+
+static const pmd_cmd_func pmd_cmd_table_fm[PMD_CMD_CNT] = {
+ pmd_cmdff_tonenum,
+ pmd_cmdfe_gate_abs,
+ pmd_cmdfd_vol,
+ pmd_cmdfc_tempo,
+ pmd_cmdfb_tie,
+ pmd_cmdfa_d5_det,
+ pmd_cmdf9_repeat_reset,
+ pmd_cmdf8_repeat,
+ pmd_cmdf7_repeat_exit,
+ pmd_cmdf6_set_loop,
+ pmd_cmdf5_transpose,
+ pmd_cmdf4_volinc_fm,
+ pmd_cmdf3_voldec_fm,
+ pmd_cmdf2_lfo,
+ pmd_cmdf1_lfo_switch_fm,
+ pmd_cmd_null_4,
+ pmd_cmdef_poke,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmdec_pan,
+ pmd_cmdeb_opnarhythm,
+ pmd_cmdea_opnarhythm_il,
+ pmd_cmde9_opnarhythm_pan,
+ pmd_cmde8_opnarhythm_tl,
+ pmd_cmde7_transpose_rel,
+ pmd_cmde6_opnarhythm_tl_rel,
+ pmd_cmde5_opnarhythm_il_rel,
+ pmd_cmde4_hlfo_delay,
+ pmd_cmde3_vol_add_fm,
+ pmd_cmde2_vol_sub,
+ pmd_cmde1_ams_pms,
+ pmd_cmde0_hlfo,
+ pmd_cmddf_meas_len,
+ pmd_cmdde_echo_init_add_fm,
+ pmd_cmddd_echo_init_sub,
+ pmd_cmddc_status1,
+ pmd_cmddb_status1_add,
+ pmd_cmdda_portamento_fm,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmdd6_md,
+ pmd_cmdfa_d5_det,
+ pmd_cmdd4_ssgeff,
+ pmd_cmdd3_fmeff,
+ pmd_cmdd2_fadeout,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmdcf_slotmask,
+ pmd_cmd_null_6,
+ pmd_cmd_null_5,
+ pmd_cmd_null_1,
+ pmd_cmdcb_lfo_waveform,
+ pmd_cmdca_lfo_ext,
+ pmd_cmd_null_1,
+ pmd_cmdc8_fm3ex_det,
+ pmd_cmdc7_fm3ex_det_add,
+ pmd_cmdc6_fm3ex_init,
+ pmd_cmdc5_lfo_slotmask,
+ pmd_cmdc4_gate_rel,
+ pmd_cmdc3_pan_ex,
+ pmd_cmdc2_lfo_delay,
+ pmd_cmd_null_0,
+ pmd_cmdc0_mml_mask_fm,
+ pmd_cmdbf_lfo2,
+ pmd_cmdbe_lfo2_switch_fm,
+ pmd_cmdbd_lfo2_md,
+ pmd_cmdbc_lfo2_waveform,
+ pmd_cmdbb_lfo2_ext,
+ pmd_cmdba_lfo2_slotmask,
+ pmd_cmdb9_lfo2_delay,
+ pmd_cmdb8_slottl,
+ pmd_cmdb7_lfo_md_cnt,
+ pmd_cmdb6_fb,
+ pmd_cmdb5_slot_delay,
+ pmd_cmd_null_16,
+ pmd_cmdb3_gate_min,
+ pmd_cmdb2_transpose_master,
+ pmd_cmdb1_gate_rand_range
+};
+
+static const pmd_cmd_func pmd_cmd_table_ssg[PMD_CMD_CNT] = {
+ pmd_cmd_null_1,
+ pmd_cmdfe_gate_abs,
+ pmd_cmdfd_vol,
+ pmd_cmdfc_tempo,
+ pmd_cmdfb_tie,
+ pmd_cmdfa_d5_det,
+ pmd_cmdf9_repeat_reset,
+ pmd_cmdf8_repeat,
+ pmd_cmdf7_repeat_exit,
+ pmd_cmdf6_set_loop,
+ pmd_cmdf5_transpose,
+ pmd_cmdf4_volinc_ssg,
+ pmd_cmdf3_voldec_ssg,
+ pmd_cmdf2_lfo,
+ pmd_cmdf1_lfo_switch,
+ pmd_cmdf0_env_old,
+ pmd_cmdef_poke,
+ pmd_cmdee_noise_freq,
+ pmd_cmded_ssgmix,
+ pmd_cmd_null_1,
+ pmd_cmdeb_opnarhythm,
+ pmd_cmdea_opnarhythm_il,
+ pmd_cmde9_opnarhythm_pan,
+ pmd_cmde8_opnarhythm_tl,
+ pmd_cmde7_transpose_rel,
+ pmd_cmde6_opnarhythm_tl_rel,
+ pmd_cmde5_opnarhythm_il_rel,
+ pmd_cmd_null_1,
+ pmd_cmde3_vol_add_ssg,
+ pmd_cmde2_vol_sub,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmddf_meas_len,
+ pmd_cmdde_echo_init_add_ssg,
+ pmd_cmddd_echo_init_sub,
+ pmd_cmddc_status1,
+ pmd_cmddb_status1_add,
+ pmd_cmdda_portamento_ssg,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmdd6_md,
+ pmd_cmdfa_d5_det,
+ pmd_cmdd4_ssgeff,
+ pmd_cmdd3_fmeff,
+ pmd_cmdd2_fadeout,
+ pmd_cmd_null_1,
+ pmd_cmdd0_ssgnoisefreqadd,
+ pmd_cmd_null_1,
+ pmd_cmd_null_6,
+ pmd_cmdcd_env_new,
+ pmd_cmdcc_det_ext,
+ pmd_cmdcb_lfo_waveform,
+ pmd_cmdca_lfo_ext,
+ pmd_cmdc9_env_ext,
+ pmd_cmd_null_3,
+ pmd_cmd_null_3,
+ pmd_cmd_null_6,
+ pmd_cmd_null_1,
+ pmd_cmdc4_gate_rel,
+ pmd_cmd_null_2,
+ pmd_cmdc2_lfo_delay,
+ pmd_cmd_null_0,
+ pmd_cmdc0_mml_mask_ssg,
+ pmd_cmdbf_lfo2,
+ pmd_cmdbe_lfo2_switch,
+ pmd_cmdbd_lfo2_md,
+ pmd_cmdbc_lfo2_waveform,
+ pmd_cmdbb_lfo2_ext,
+ pmd_cmd_null_1,
+ pmd_cmdb9_lfo2_delay,
+ pmd_cmd_null_2,
+ pmd_cmdb7_lfo_md_cnt,
+ pmd_cmd_null_1,
+ pmd_cmd_null_2,
+ pmd_cmd_null_16,
+ pmd_cmdb3_gate_min,
+ pmd_cmdb2_transpose_master,
+ pmd_cmdb1_gate_rand_range
+};
+
+static const pmd_cmd_func pmd_cmd_table_rhythm[PMD_CMD_CNT] = {
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmdfd_vol,
+ pmd_cmdfc_tempo,
+ pmd_cmdfb_tie,
+ pmd_cmdfa_d5_det,
+ pmd_cmdf9_repeat_reset,
+ pmd_cmdf8_repeat,
+ pmd_cmdf7_repeat_exit,
+ pmd_cmdf6_set_loop,
+ pmd_cmd_null_1,
+ pmd_cmdf4_volinc_ssg,
+ pmd_cmdf3_voldec_ssg,
+ pmd_cmd_null_4,
+ pmd_cmdf1_ppsdrv,
+ pmd_cmd_null_4,
+ pmd_cmdef_poke,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmdeb_opnarhythm,
+ pmd_cmdea_opnarhythm_il,
+ pmd_cmde9_opnarhythm_pan,
+ pmd_cmde8_opnarhythm_tl,
+ pmd_cmd_null_1,
+ pmd_cmde6_opnarhythm_tl_rel,
+ pmd_cmde5_opnarhythm_il_rel,
+ pmd_cmd_null_1,
+ pmd_cmde3_vol_add_ssg,
+ pmd_cmde2_vol_sub,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmddf_meas_len,
+ pmd_cmdde_echo_init_add_ssg,
+ pmd_cmddd_echo_init_sub,
+ pmd_cmddc_status1,
+ pmd_cmddb_status1_add,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_2,
+ pmd_cmdfa_d5_det,
+ pmd_cmdd4_ssgeff,
+ pmd_cmdd3_fmeff,
+ pmd_cmdd2_fadeout,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1, // d0
+ pmd_cmd_null_1,
+ pmd_cmd_null_6,
+ pmd_cmd_null_5,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_3, // c8
+ pmd_cmd_null_3,
+ pmd_cmd_null_6,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_2,
+ pmd_cmd_null_1,
+ pmd_cmd_null_0,
+ pmd_cmdc0_mml_mask_rhythm, // c0
+ pmd_cmd_null_4,
+ pmd_cmd_null_1,
+ pmd_cmd_null_2,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_2, // b8
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_2,
+ pmd_cmd_null_16,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1,
+ pmd_cmd_null_1
+};
+
+// 1857
+static void pmd_part_cmd_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t cmd
+) {
+ if (cmd < 0xb1) {
+ part->ptr = 0;
+ return;
+ }
+ pmd_cmd_table_ssg[cmd^0xff](work, pmd, part);
+}
+
+// 184d
+static void pmd_part_cmd_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t cmd
+) {
+ if (cmd < 0xb1) {
+ part->ptr = 0;
+ return;
+ }
+ pmd_cmd_table_fm[cmd^0xff](work, pmd, part);
+}
+
+// 1852
+static void pmd_part_cmd_rhythm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t cmd
+) {
+ if (cmd < 0xb1) {
+ part->ptr = 0;
+ return;
+ }
+ pmd_cmd_table_rhythm[cmd^0xff](work, pmd, part);
+}
+
+// 20e8
+static void pmd_portamento_tick(
+ struct pmd_part *part
+) {
+ part->portamento_diff = u16s16(((uint16_t)part->portamento_diff) + ((uint16_t)part->portamento_add));
+ if (part->portamento_rem > 0) {
+ part->portamento_rem--;
+ part->portamento_diff = u16s16(((uint16_t)part->portamento_diff) + 1);
+ } else if (part->portamento_rem < 0) {
+ part->portamento_rem++;
+ part->portamento_diff = u16s16(((uint16_t)part->portamento_diff) - 1);
+ }
+}
+
+// 3161
+static void pmd_ssg_env_tick_old(
+ struct pmd_part *part
+) {
+ switch (part->ssg_env_state_old) {
+ case SSG_ENV_STATE_OLD_AL:
+ // 3167
+ if (--part->ssg_env_param_set[SSG_ENV_PARAM_OLD_AL]) return;
+ part->ssg_env_state_old = SSG_ENV_STATE_OLD_SR;
+ part->ssg_env_vol = u8s8(part->ssg_env_param_set[SSG_ENV_PARAM_OLD_AD]);
+ return;
+ case SSG_ENV_STATE_OLD_SR:
+ // 317c
+ if (!part->ssg_env_param_set[SSG_ENV_PARAM_OLD_SR]) return;
+ if (--part->ssg_env_param_set[SSG_ENV_PARAM_OLD_SR]) return;
+ part->ssg_env_param_set[SSG_ENV_PARAM_OLD_SR] = part->ssg_env_param[SSG_ENV_PARAM_OLD_SR];
+ part->ssg_env_vol = u8s8(((uint8_t)part->ssg_env_vol) - 1);
+ if ((-15 <= part->ssg_env_vol) && (part->ssg_env_vol < 15)) return;
+ part->ssg_env_vol = -15;
+ return;
+ case SSG_ENV_STATE_OLD_RR:
+ // 31a3
+ if (!part->ssg_env_param_set[SSG_ENV_PARAM_OLD_RR]) {
+ part->ssg_env_vol = -15;
+ return;
+ }
+ if (--part->ssg_env_param_set[SSG_ENV_PARAM_OLD_RR]) return;
+ part->ssg_env_param_set[SSG_ENV_PARAM_OLD_RR] = part->ssg_env_param[SSG_ENV_PARAM_OLD_RR];
+ part->ssg_env_vol = u8s8(((uint8_t)part->ssg_env_vol) - 1);
+ if ((-15 <= part->ssg_env_vol) && (part->ssg_env_vol < 15)) return;
+ part->ssg_env_vol = -15;
+ return;
+ }
+}
+
+// 314e
+// returns true if volume update needed
+static bool pmd_ssg_env_tick_check(
+ struct pmd_part *part
+) {
+ if (part->ssg_env_state_old == SSG_ENV_STATE_OLD_NEW) {
+ return pmd_ssg_env_tick_new_check(part);
+ }
+ int8_t old_env_vol = part->ssg_env_vol;
+ pmd_ssg_env_tick_old(part);
+ return old_env_vol != part->ssg_env_vol;
+}
+
+// 312e
+// returns true if volume update needed
+static bool pmd_ssg_env_proc(
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->flagext.env) {
+ // 3134
+ uint8_t timera_lapsed = pmd->timera_cnt - pmd->timera_cnt_b;
+ bool update_needed = false;
+ for (int i = 0; i < timera_lapsed; i++) {
+ update_needed |= pmd_ssg_env_tick_check(part);
+ }
+ return update_needed;
+ } else {
+ return pmd_ssg_env_tick_check(part);
+ }
+}
+
+// 1617
+static void pmd_part_proc_ssg_lfoenv(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ static const struct pmd_part_lfo_flags lfof_z;
+ pmd->lfoprocf = lfof_z;
+ pmd->lfoprocf_b = lfof_z;
+ pmd->lfoprocf.portamento = part->lfof.portamento;
+ if (part->lfof.freq || part->lfof.vol || part->lfof.sync || part->lfof.portamento ||
+ part->lfof_b.freq || part->lfof_b.vol || part->lfof_b.sync || part->lfof_b.portamento) {
+ // 1625
+ if (part->lfof.freq || part->lfof.vol) {
+ if (pmd_lfo_tick(pmd, part)) {
+ pmd->lfoprocf.freq = part->lfof.freq;
+ pmd->lfoprocf.vol = part->lfof.vol;
+ }
+ }
+ // 1637
+ if (part->lfof_b.freq || part->lfof_b.vol) {
+ pmd_part_lfo_flip(part);
+ if (pmd_lfo_tick(pmd, part)) {
+ pmd->lfoprocf_b.freq = part->lfof_b.freq;
+ pmd->lfoprocf_b.vol = part->lfof_b.vol;
+ }
+ pmd_part_lfo_flip(part);
+ }
+ // 1659
+ if (pmd->lfoprocf.freq || pmd->lfoprocf.portamento || pmd->lfoprocf_b.freq) {
+ if (pmd->lfoprocf.portamento) {
+ pmd_portamento_tick(part);
+ }
+ pmd_ssg_freq_out(work, pmd, part);
+ }
+ }
+ // 166d
+ if (pmd_ssg_env_proc(pmd, part) || pmd->lfoprocf.vol || pmd->lfoprocf_b.vol || pmd->fadeout_speed) {
+ pmd_ssg_vol_out(work, pmd, part);
+ }
+ pmd_part_loop_check(pmd, part);
+}
+
+// 16d9
+static bool pmd_part_ssg_next_masked(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part,
+ uint8_t note
+) {
+ if (part->mask.ext) return true;
+ if (!part->mask.effect) return true;
+ if (pmd->ssgeff_priority >= 2) return true;
+ if ((note & 0xf) == 0xf) return true;
+ if (pmd->ssgeff_priority == 1) {
+ pmd_ssgeff_stop(work, pmd);
+ }
+ part->mask.effect = false;
+ return pmd_part_masked(part);
+}
+
+// 1561
+static void pmd_part_loop_check_masked(
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ pmd->no_keyoff = false;
+ pmd->volume_saved = false;
+ pmd_part_loop_check(pmd, part);
+}
+
+// 1541
+static void pmd_part_proc_note_masked(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ part->actual_freq = 0;
+ part->actual_note = 0xff;
+ part->curr_note = 0xff;
+ part->len = part->len_cnt = pmd_part_cmdload(pmd, part);
+ part->note_proc++;
+
+ if (!pmd->volume_saved) {
+ part->volume_save = 0;
+ }
+ pmd->no_keyoff = false;
+ pmd->volume_saved = false;
+ pmd_part_loop_check(pmd, part);
+}
+
+// 2cb5
+static void pmd_part_off_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->keystatus.off && part->keystatus.off_mask) return;
+ pmd_part_do_keyoff_fm(work, pmd, part);
+}
+
+// 13f8
+static void pmd_part_proc_fm_lfo(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (part->hlfo_delay) {
+ if (!--part->hlfo_delay) {
+ pmd_reg_write(work, pmd, 0xb3+pmd->proc_ch, part->fm_pan_ams_pms);
+ }
+ }
+ // 1410
+ if (part->slot_delay_cnt) {
+ if (!--part->slot_delay_cnt) {
+ if (!part->keystatus.off) {
+ pmd_part_fm_keyon(work, pmd, part);
+ }
+ }
+ }
+ static const struct pmd_part_lfo_flags lfof_z;
+ pmd->lfoprocf = lfof_z;
+ pmd->lfoprocf_b = lfof_z;
+ pmd->lfoprocf.portamento = part->lfof.portamento;
+ // 1424
+ if (part->lfof.freq || part->lfof.vol || part->lfof.sync || part->lfof.portamento ||
+ part->lfof_b.freq || part->lfof_b.vol || part->lfof_b.sync || part->lfof_b.portamento) {
+ if (part->lfof.freq || part->lfof.vol) {
+ if (pmd_lfo_tick(pmd, part)) {
+ pmd->lfoprocf.freq = part->lfof.freq;
+ pmd->lfoprocf.vol = part->lfof.vol;
+ }
+ }
+ if (part->lfof_b.freq || part->lfof_b.vol) {
+ pmd_part_lfo_flip(part);
+ if (pmd_lfo_tick(pmd, part)) {
+ pmd->lfoprocf_b.freq = part->lfof.freq;
+ pmd->lfoprocf_b.vol = part->lfof.vol;
+ }
+ pmd_part_lfo_flip(part);
+ }
+ // 1466
+ if (pmd->lfoprocf.freq || pmd->lfoprocf.portamento || pmd->lfoprocf_b.freq) {
+ if (pmd->lfoprocf.portamento) {
+ pmd_portamento_tick(part);
+ }
+ pmd_fm_freq_out(work, pmd, part);
+ }
+ // 147a
+ }
+ // 1481
+ if (pmd->lfoprocf.vol || pmd->lfoprocf_b.vol || pmd->fadeout_speed) {
+ pmd_fm_vol_out(work, pmd, part);
+ }
+ pmd_part_loop_check(pmd, part);
+}
+
+// 1350 / 14fc
+static void pmd_part_proc_fm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (!part->ptr) return;
+ part->proc_masked = pmd_part_masked(part);
+ part->len_cnt--;
+ if (part->proc_masked) {
+ part->keystatus.off = true;
+ part->keystatus.off_mask = true;
+ } else if (!part->keystatus.off && !part->keystatus.off_mask) {
+ if (part->len_cnt <= part->gate) {
+ pmd_part_off_fm(work, pmd, part);
+ part->keystatus.off = true;
+ part->keystatus.off_mask = true;
+ }
+ }
+ // 1377
+ if (part->len_cnt) {
+ if (part->proc_masked) {
+ pmd_part_loop_check(pmd, part);
+ } else {
+ pmd_part_proc_fm_lfo(work, pmd, part);
+ }
+ return;
+ }
+ if (part->proc_masked) {
+ // 1505
+ if (part->mask.effect && !pmd->fmeff_playing) {
+ part->mask.effect = false;
+ part->proc_masked = pmd_part_masked(part);
+ }
+ }
+ // 137b
+ if (!part->proc_masked) {
+ part->lfof.portamento = false;
+ }
+ for (;;) {
+ // 137f / 151b
+ uint8_t cmd = pmd_part_cmdload(pmd, part);
+ if (cmd & 0x80) {
+ if (cmd != 0x80) {
+ // 1386 / 1522
+ pmd_part_cmd_fm(work, pmd, part, cmd);
+ if (cmd == 0xda && !pmd_part_masked(part)) return;
+ } else {
+ // 138b / 1527
+ part->ptr = 0;
+ part->loop.looped = true;
+ part->loop.ended = true;
+ part->actual_note = 0xff;
+ if (!part->loop_ptr) {
+ if (part->proc_masked) {
+ pmd_part_proc_fm_lfo(work, pmd, part);
+ } else {
+ pmd_part_loop_check_masked(pmd, part);
+ }
+ return;
+ }
+ part->ptr = part->loop_ptr;
+ part->loop.ended = false;
+ }
+ } else {
+ // 13a5 / 1541
+ if (part->proc_masked) {
+ pmd_part_proc_note_masked(work, pmd, part);
+ return;
+ } else {
+ pmd_part_lfo_init_fm(work, pmd, part, cmd);
+ cmd = pmd_part_note_transpose(part, cmd);
+ pmd_note_freq_fm(part, cmd);
+ part->len = part->len_cnt = pmd_part_cmdload(pmd, part);
+ pmd_part_calc_gate(pmd, part);
+ // 13b5
+ pmd_part_fm_out(work, pmd, part);
+ return;
+ }
+ }
+ }
+}
+
+// 156f / 1689
+static void pmd_part_proc_ssg(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (!part->ptr) return;
+ part->proc_masked = pmd_part_masked(part);
+
+ part->len_cnt--;
+ if (part->proc_masked) {
+ part->keystatus.off = true;
+ part->keystatus.off_mask = true;
+ } else if (!part->keystatus.off && !part->keystatus.off_mask) {
+ if (part->len_cnt <= part->gate) {
+ pmd_part_off_ssg(part);
+ part->keystatus.off = true;
+ part->keystatus.off_mask = true;
+ }
+ }
+ // 1596
+ if (part->len_cnt) {
+ if (part->proc_masked) {
+ pmd_part_loop_check(pmd, part);
+ } else {
+ pmd_part_proc_ssg_lfoenv(work, pmd, part);
+ }
+ return;
+ }
+ part->lfof.portamento = false;
+ for (;;) {
+ // 159e / 1699
+ uint8_t cmd = pmd_part_cmdload(pmd, part);
+ if (cmd & 0x80) {
+ if (cmd != 0x80) {
+ if (part->proc_masked && (cmd == 0xda)) {
+ // 16a0
+ part->proc_masked = pmd_part_ssg_next_masked(work, pmd, part, cmd);
+ }
+ // 15a5
+ pmd_part_cmd_ssg(work, pmd, part, cmd);
+ if (cmd == 0xda && !pmd_part_masked(part)) return;
+ } else {
+ // 15aa
+ part->ptr = 0;
+ part->loop.looped = true;
+ part->loop.ended = true;
+ part->actual_note = 0xff;
+ if (!part->loop_ptr) {
+ if (part->proc_masked) {
+ pmd_part_proc_ssg_lfoenv(work, pmd, part);
+ } else {
+ pmd_part_loop_check_masked(pmd, part);
+ }
+ return;
+ }
+ part->ptr = part->loop_ptr;
+ part->loop.ended = false;
+ }
+ } else {
+ if (part->proc_masked && pmd_part_ssg_next_masked(work, pmd, part, cmd)) {
+ // 16ce
+ pmd_part_proc_note_masked(work, pmd, part);
+ return;
+ } else {
+ // 15c4
+ pmd_part_lfo_init_ssg(work, pmd, part, cmd);
+ cmd = pmd_part_note_transpose(part, cmd);
+ pmd_note_freq_ssg(part, cmd);
+ part->len = part->len_cnt = pmd_part_cmdload(pmd, part);
+ pmd_part_calc_gate(pmd, part);
+ // 15d4
+ if (part->volume_save && (part->actual_note != 0xff)) {
+ if (!pmd->volume_saved) {
+ part->volume_save = 0;
+ }
+ pmd->volume_saved = false;
+ }
+ // 15ef
+ pmd_ssg_vol_out(work, pmd, part);
+ pmd_ssg_freq_out(work, pmd, part);
+ pmd_ssg_note_noise_out(work, pmd, part);
+ part->note_proc++;
+ pmd->no_keyoff = false;
+ pmd->volume_saved = false;
+ part->keystatus.off = false;
+ part->keystatus.off_mask = false;
+ // fb
+ if (pmd->datalen > (part->ptr + 1)) {
+ if (pmd->data[part->ptr] == 0xfb) {
+ part->keystatus.off_mask = true;
+ }
+ }
+ pmd_part_loop_check(pmd, part);
+ return;
+ }
+ }
+ }
+ //pmd_part_proc_ssg_lfoenv(work, pmd, part);
+}
+
+// 42d6
+static const struct pmd_ssgrhythm_opna_table_entry {
+ uint8_t reg;
+ uint8_t data;
+ uint8_t kon;
+} pmd_ssgrhythm_opna_table[11] = {
+ {0x18, 0xdf, 0x01},
+ {0x19, 0xdf, 0x02},
+ {0x1c, 0x5f, 0x10},
+ {0x1c, 0xdf, 0x10},
+ {0x1c, 0x9f, 0x10},
+ {0x1d, 0xd3, 0x20},
+ {0x19, 0xdf, 0x02},
+ {0x1b, 0x9c, 0x88},
+ {0x1a, 0x9d, 0x04},
+ {0x1a, 0xdf, 0x04},
+ {0x1a, 0x5e, 0x04},
+};
+
+// 1813
+static void pmd_ssgrhythm_opna_out(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ const struct pmd_ssgrhythm_opna_table_entry *r
+) {
+ work->opna_writereg(work, r->reg, r->data);
+ uint8_t kon = r->kon & pmd->opnarhythm_mask;
+ if (!kon) return;
+ if (kon & 0x80) {
+ // 1828
+ work->opna_writereg(work, 0x10, 0x84);
+ kon = 0x08 & pmd->opnarhythm_mask;
+ if (!kon) return;
+ }
+ work->opna_writereg(work, 0x10, kon);
+}
+
+// 170d
+static void pmd_part_proc_opnarhythm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+ if (!part->ptr) return;
+
+ if (--part->len_cnt) {
+ pmd_part_loop_check(pmd, part);
+ return;
+ }
+ // 171b
+ for (;;) {
+ while (pmd->r_ptr && (pmd->datalen >= (pmd->r_ptr+1)) && (pmd->data[pmd->r_ptr] != 0xff)) {
+ // 1726
+ uint8_t rcmd = pmd->data[pmd->r_ptr++];
+ if (rcmd & 0x80) {
+ // 1785
+ if (rcmd & 0x40) {
+ uint16_t tmp = part->ptr;
+ part->ptr = pmd->r_ptr;
+ pmd->r_ptr = tmp;
+ pmd_part_cmd_rhythm(work, pmd, part, rcmd);
+ tmp = part->ptr;
+ part->ptr = pmd->r_ptr;
+ pmd->r_ptr = tmp;
+ continue;
+ }
+ // 1794
+ if (pmd_part_masked(part)) {
+ pmd->ssgrhythm = 0;
+ pmd->r_ptr++;
+ part->len = part->len_cnt = pmd->data[pmd->r_ptr++];
+ pmd_part_loop_check_masked(pmd, part);
+ part->note_proc++;
+ return;
+ }
+ pmd->ssgrhythm = ((rcmd << 8) | pmd->data[pmd->r_ptr++]) & 0x3fff;
+ if (pmd->ssgrhythm) {
+ // 17b0
+ if (pmd->ssgrhythm_opna) {
+ for (int i = 0; i < 11; i++) {
+ if (pmd->ssgrhythm & (1<<i)) {
+ pmd_ssgrhythm_opna_out(work, pmd, &pmd_ssgrhythm_opna_table[i]);
+ }
+ }
+ }
+ // 17cc
+ if (pmd->fadeout_vol) {
+ if (pmd->ssgrhythm_opna) {
+ pmd_opnarhythm_tl_write(work, pmd, pmd->opnarhythm_tl);
+ }
+ }
+ // 17ea
+ if (!pmd->fadeout_vol || pmd->ppsdrv_enabled) {
+ // TODO: PPSDRV
+ }
+ // 180c
+ uint8_t eff;
+ for (eff = 0;; eff++) {
+ if (pmd->ssgrhythm & (1<<eff)) break;
+ }
+ pmd_ssg_effect(work, pmd, eff);
+ }
+ } else {
+ pmd->ssgrhythm = 0;
+ }
+ part->len = part->len_cnt = pmd->data[pmd->r_ptr++];
+ pmd_part_loop_check_masked(pmd, part);
+ part->note_proc++;
+ return;
+ }
+ // 1740
+ for (;;) {
+ uint8_t cmd = pmd_part_cmdload(pmd, part);
+ if (cmd & 0x80) {
+ if (cmd != 0x80) {
+ pmd_part_cmd_rhythm(work, pmd, part, cmd);
+ } else {
+ // 1765
+ part->ptr = 0;
+ part->loop.looped = true;
+ part->loop.ended = true;
+ if (!part->loop_ptr) {
+ part->ptr = 0;
+ pmd_part_loop_check_masked(pmd, part);
+ return;
+ }
+ part->ptr = part->loop_ptr;
+ part->loop.ended = false;
+ }
+ } else {
+ // 174c
+ uint16_t ptr = pmd->r_offset + cmd*2;
+ if (pmd->datalen < (ptr + 2)) {
+ part->ptr = 0;
+ return;
+ }
+ pmd->r_ptr = pmd->data[ptr] | (((uint16_t)pmd->data[ptr+1]) << 8);
+ break;
+ }
+ }
+ }
+}
+
+// 0149
+static void pmd_part_proc_adpcm(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+}
+
+// 079b
+static void pmd_part_proc_ppz8(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd,
+ struct pmd_part *part
+) {
+}
+
+// 11fd
+static void pmd_proc_parts(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ if (!pmd->opm_flag) {
+ for (int c = 0; c < 3; c++) {
+ pmd->proc_ch = c+1;
+ struct pmd_part *part = &pmd->parts[PMD_PART_SSG_1+c];
+ pmd_part_proc_ssg(work, pmd, part);
+ }
+ }
+ // 122a
+ pmd_opna_a1_on(pmd);
+ for (int c = 0; c < 3; c++) {
+ pmd->proc_ch = c+1;
+ struct pmd_part *part = &pmd->parts[PMD_PART_FM_4+c];
+ pmd_part_proc_fm(work, pmd, part);
+ }
+ pmd_opna_a1_off(pmd);
+ for (int c = 0; c < 3; c++) {
+ pmd->proc_ch = c+1;
+ struct pmd_part *part = &pmd->parts[PMD_PART_FM_1+c];
+ pmd_part_proc_fm(work, pmd, part);
+ }
+ // 1272
+ for (int c = 0; c < 3; c++) {
+ struct pmd_part *part = &pmd->parts[PMD_PART_FM_3B+c];
+ pmd_part_proc_fm(work, pmd, part);
+ }
+ // 1284
+ if (!pmd->opm_flag) {
+ pmd_part_proc_opnarhythm(work, pmd, &pmd->parts[PMD_PART_RHYTHM]);
+ pmd_part_proc_adpcm(work, pmd, &pmd->parts[PMD_PART_ADPCM]);
+ for (int c = 0; c < 8; c++) {
+ pmd->proc_ch = c;
+ struct pmd_part *part = &pmd->parts[PMD_PART_PPZ_1+c];
+ pmd_part_proc_ppz8(work, pmd, part);
+ }
+ }
+ // 12ef
+ if (!pmd->loop.looped && !pmd->loop.ended && !pmd->loop.env) return;
+ // 12f7
+ for (int i = 0; i < 12; i++) {
+ struct pmd_part *part = &pmd->parts[PMD_PART_FM_1+i];
+ if (!part->loop.looped || !part->loop.ended || part->loop.env) {
+ part->loop.looped = false;
+ part->loop.ended = false;
+ part->loop.env = false;
+ }
+ }
+ // 130d
+ if (!pmd->loop.looped || !pmd->loop.ended || pmd->loop.env) {
+ if (++pmd->status2 == 0xff) pmd->status2 = 1;
+ } else {
+ pmd->status2 = 0xff;
+ }
+}
+
+// 3e2e
+static void pmd_update_note_meas(
+ struct driver_pmd *pmd
+) {
+ uint8_t tick = ++pmd->tick_cnt;
+ if (tick == pmd->meas_len) {
+ pmd->tick_cnt = 0;
+ pmd->meas_cnt++;
+ }
+}
+
+// 3d68
+static void pmd_timerb(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ if (pmd->playing) {
+ // 3d8f
+ pmd_proc_parts(work, pmd);
+ pmd_timerb_write(work, pmd);
+ pmd_update_note_meas(pmd);
+ pmd->timera_cnt_b = pmd->timera_cnt;
+ }
+ // 3d9e
+ // user interrupt
+}
+
+// 329e
+static void pmd_fadeout_tick(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ if (pmd->paused) return;
+ if (!pmd->fadeout_speed) return;
+ int newvol = pmd->fadeout_vol + u8s8(pmd->fadeout_speed);
+ if (newvol < 0) {
+ newvol = 0;
+ pmd->fadeout_vol = 0;
+ pmd->fadeout_speed = 0;
+ pmd_opnarhythm_tl_write(work, pmd, pmd->opnarhythm_tl);
+ } else if (newvol > 0xff) {
+ newvol = 0xff;
+ pmd->fadeout_speed = 0;
+ //if (pmd_fadeout_mstop) pmd->
+ }
+}
+
+// 3daf
+static void pmd_timera(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ if (!(++pmd->timera_cnt & 7)) {
+ // 3dbf
+ pmd_fadeout_tick(work, pmd);
+ // 3e66: fast-forward
+ }
+ if (pmd->ssgeff_priority) {
+ // TODO: PPSDRV
+ if (!pmd->ppsdrv_enabled || (!(pmd->ssgeff_num & 0x80))) {
+ pmd_ssgeff_tick(work, pmd);
+ }
+ }
+ // 3ddd
+ if (pmd->fmeff_playing) {
+ // TODO: FMEFF
+ // call 0x0f03
+ }
+ // esc/grph key check??
+}
+
+// 3d1b
+static void pmd_timer(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ uint8_t status = work->opna_status(work, 0);
+ work->opna_writereg(work, 0x27, pmd->fm3ex_mode);
+ if (status & 1) {
+ pmd_timera(work, pmd);
+ }
+ if (status & 2) {
+ pmd_timerb(work, pmd);
+ }
+}
+
+enum pmd_part_type {
+ PART_TYPE_FM,
+ PART_TYPE_SSG,
+ PART_TYPE_ADPCM,
+};
+
+static const struct {
+ uint8_t ind;
+ enum pmd_part_type type;
+ bool fm3ex;
+} pmd_track_map[FMDRIVER_TRACK_NUM] = {
+ {PMD_PART_FM_1, PART_TYPE_FM, false},
+ {PMD_PART_FM_2, PART_TYPE_FM, false},
+ {PMD_PART_FM_3, PART_TYPE_FM, true},
+ {PMD_PART_FM_3B, PART_TYPE_FM, true},
+ {PMD_PART_FM_3C, PART_TYPE_FM, true},
+ {PMD_PART_FM_3D, PART_TYPE_FM, true},
+ {PMD_PART_FM_4, PART_TYPE_FM, false},
+ {PMD_PART_FM_5, PART_TYPE_FM, false},
+ {PMD_PART_FM_6, PART_TYPE_FM, false},
+ {PMD_PART_SSG_1, PART_TYPE_SSG, false},
+ {PMD_PART_SSG_2, PART_TYPE_SSG, false},
+ {PMD_PART_SSG_3, PART_TYPE_SSG, false},
+ {PMD_PART_ADPCM, PART_TYPE_ADPCM, false},
+};
+
+static void pmd_work_status_update(
+ struct fmdriver_work *work,
+ const struct driver_pmd *pmd
+) {
+ for (int t = 0; t < FMDRIVER_TRACK_NUM; t++) {
+ struct fmdriver_track_status *track = &work->track_status[t];
+ const struct pmd_part *part = &pmd->parts[pmd_track_map[t].ind];
+ track->playing = !part->loop.ended;
+ track->masked = pmd_part_masked(part);
+ track->info = FMDRIVER_TRACK_INFO_NORMAL;
+ track->ticks = part->len;
+ track->ticks_left = part->len_cnt;
+ track->key = part->actual_note;
+ track->fmslotmask[0] = !(part->fm_slotmask & (1<<4));
+ track->fmslotmask[1] = !(part->fm_slotmask & (1<<5));
+ track->fmslotmask[2] = !(part->fm_slotmask & (1<<6));
+ track->fmslotmask[3] = !(part->fm_slotmask & (1<<7));
+ track->tonenum = part->tonenum;
+ track->volume = part->vol;
+ int detune = part->detune;
+ if (detune > INT8_MAX) detune = INT8_MAX;
+ if (detune < INT8_MIN) detune = INT8_MIN;
+ track->detune = detune;
+ track->status[0] = part->lfof.freq ? 'P' : '-';
+ track->status[1] = part->lfof.vol ? 'A' : '-';
+ track->status[2] = part->lfof.sync ? 'S' : '-';
+ track->status[3] = part->lfof_b.freq ? 'P' : '-';
+ track->status[4] = part->lfof_b.vol ? 'A' : '-';
+ track->status[5] = part->lfof_b.sync ? 'S' : '-';
+ track->status[6] = '-';
+ track->status[7] = part->lfof.portamento ? 'P' : '-';
+ switch (pmd_track_map[t].type) {
+ case PART_TYPE_FM:
+ track->actual_key = ((track->key & 0xf) == 0xf) ?
+ 0xff : fmdriver_fm_freq2key(part->output_freq);
+ if (pmd_track_map[t].fm3ex) {
+ if (part->fm_slotmask != 0xf0) track->info = FMDRIVER_TRACK_INFO_FM3EX;
+ }
+ break;
+ case PART_TYPE_SSG:
+ track->info = FMDRIVER_TRACK_INFO_SSG;
+ track->actual_key = ((track->key & 0xf) == 0xf) ?
+ 0xff : fmdriver_ssg_freq2key(part->output_freq);
+ track->ssg_tone = part->ssg_mix & (1<<0);
+ track->ssg_noise = part->ssg_mix & (1<<3);
+ if (part == &pmd->parts[PMD_PART_SSG_3]) {
+ if (pmd->ssgeff_on) {
+ track->info = FMDRIVER_TRACK_INFO_SSGEFF;
+ track->tonenum = pmd->ssgeff_num;
+ track->ssg_tone = pmd->ssgeff_tone_mix;
+ track->ssg_noise = pmd->ssgeff_noise_mix;
+ track->actual_key = pmd->ssgeff_tone_mix ?
+ fmdriver_ssg_freq2key(pmd->ssgeff_tonefreq) : 0xff;
+ }
+ }
+ break;
+ case PART_TYPE_ADPCM:
+ track->playing = false;
+ }
+ }
+ work->ssg_noise_freq = pmd->ssg_noise_freq_wrote;
+}
+
+// 3c76
+static void pmd_opna_interrupt(struct fmdriver_work *work) {
+ struct driver_pmd *pmd = (struct driver_pmd *)work->driver;
+ // 3d1b
+ for (;;) {
+ uint8_t status = work->opna_status(work, 0);
+ if (status & 3) {
+ pmd_timer(work, pmd);
+ } else {
+ break;
+ }
+ }
+ // 3cc0
+ pmd_work_status_update(work, pmd);
+}
+
+// 0f4d
+static void pmd_mstart(
+ struct fmdriver_work *work,
+ struct driver_pmd *pmd
+) {
+ pmd_mstop(work, pmd);
+ pmd_reset_state(pmd);
+ pmd_data_init(pmd);
+ pmd->fadeout_vol = 0;
+ if (pmd->adpcm_disabled) {
+ pmd->parts[PMD_PART_ADPCM].mask.disabled = true;
+ }
+ // reset PPZ8
+
+ // 0f99
+ pmd_reset_opna(work, pmd);
+ pmd_reset_timer(work, pmd);
+ pmd->playing = true;
+ // TODO
+ // 4375??
+}
+
+bool pmd_load(struct driver_pmd *pmd,
+ uint8_t *data, uint16_t datalen) {
+ if (!datalen) return false;
+ pmd->data = data + 1;
+ pmd->datalen = datalen - 1;
+ pmd_reset_state(pmd);
+ pmd->ssgeff_num = 0xff;
+ if (!pmd_data_init(pmd)) return false;
+ pmd->fadeout_vol = 0;
+ if (pmd->adpcm_disabled) {
+ pmd->parts[PMD_PART_ADPCM].mask.disabled = true;
+ }
+ pmd->opnarhythm_mask = 0xff;
+ pmd->ssgrhythm_opna = true;
+ return true;
+}
+
+// check if null-terminated string is valid
+static const char *pmd_check_str(
+ const struct driver_pmd *pmd,
+ uint16_t ptr
+) {
+ uint16_t c = ptr;
+ while (pmd->datalen >= (c+1)) {
+ if (!pmd->data[c++]) return &pmd->data[ptr];
+ }
+ return 0;
+}
+
+// 383e
+const char *pmd_get_memo(
+ const struct driver_pmd *pmd,
+ int index
+) {
+ if (index < -2) return 0;
+ if (pmd->datalen < 2) return 0;
+ if (read16le(&pmd->data[0]) == 0x18) return 0;
+ if (pmd->datalen < (0x18+2)) return 0;
+ uint16_t toneptr = read16le(&pmd->data[0x18]);
+ if (toneptr < 4) return 0;
+ if (pmd->datalen < toneptr) return 0;
+ uint8_t flaglow = pmd->data[toneptr-2];
+ uint8_t flaghigh = pmd->data[toneptr-1];
+ if (flaglow != 0x40) {
+ if (flaghigh != 0xfe) return 0;
+ if (flaglow < 0x41) return 0;
+ }
+ if (flaglow >= 0x42) index++;
+ if (flaglow >= 0x48) index++;
+ if (index < 0) return 0;
+ // 3873
+ uint16_t memoptr = read16le(&pmd->data[toneptr-4]);
+ while ((pmd->datalen >= (memoptr+2)) && read16le(&pmd->data[memoptr])) {
+ if (index == 0) {
+ if (pmd->datalen < (memoptr+2)) return 0;
+ return pmd_check_str(pmd, read16le(&pmd->data[memoptr]));
+ }
+ memoptr += 2;
+ index--;
+ }
+ return 0;
+}
+
+void pmd_filenamecopy(char *dest, const char *src) {
+ int i;
+ for (i = 0; i < (PMD_FILENAMELEN+1); i++) {
+ dest[i] = src[i];
+ if (!src[i]) return;
+ }
+ dest[0] = 0;
+}
+
+void pmd_init(struct fmdriver_work *work,
+ struct driver_pmd *pmd) {
+ // TODO: reset ppz8
+
+ // 0f99
+ work->driver = pmd;
+ pmd_reset_opna(work, pmd);
+ pmd_reset_timer(work, pmd);
+ pmd->playing = true;
+ work->driver_opna_interrupt = pmd_opna_interrupt;
+ static const int memotable[3] = {1, 4, 5};
+ for (int i = 0; i < 3; i++) {
+ const char *title = pmd_get_memo(pmd, memotable[i]);
+ int c = 0;
+ if (title) {
+ while (title[c] && (c < (FMDRIVER_TITLE_BUFLEN-1))) {
+ work->comment[i][c] = title[c];
+ c++;
+ }
+ }
+ work->comment[i][c] = 0;
+ }
+ const char *pcmfile = pmd_get_memo(pmd, -2);
+ if (pcmfile) {
+ pmd_filenamecopy(&pmd->ppzfile, pcmfile);
+ }
+ pcmfile = pmd_get_memo(pmd, -1);
+ if (pcmfile) {
+ pmd_filenamecopy(&pmd->ppsfile, pcmfile);
+ }
+ pcmfile = pmd_get_memo(pmd, 0);
+ if (pcmfile) {
+ pmd_filenamecopy(&pmd->ppcfile, pcmfile);
+ }
+}
+
+enum {
+ PPC_HEADER_SIZE = 30+2+4*256
+};
+
+bool pmd_ppc_load(
+ struct fmdriver_work *work,
+ uint8_t *data, size_t datalen
+) {
+ if (datalen < PPC_HEADER_SIZE) return false;
+ const char *header = "ADPCM DATA for PMD ver.4.4- ";
+ for (int i = 0; i < 30; i++) {
+ if (data[i] != (uint8_t)header[i]) return false;
+ }
+
+}
diff --git a/fmdriver/fmdriver_pmd.h b/fmdriver/fmdriver_pmd.h
new file mode 100644
index 0000000..356099e
--- /dev/null
+++ b/fmdriver/fmdriver_pmd.h
@@ -0,0 +1,462 @@
+#ifndef MYON_FMDRIVER_PMD_H_INCLUDED
+#define MYON_FMDRIVER_PMD_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "fmdriver.h"
+enum {
+ PMD_FILENAMELEN = 8+1+3
+};
+
+enum {
+ PMD_PART_FM_1,
+ PMD_PART_FM_2,
+ PMD_PART_FM_3,
+ PMD_PART_FM_4,
+ PMD_PART_FM_5,
+ PMD_PART_FM_6,
+ PMD_PART_SSG_1,
+ PMD_PART_SSG_2,
+ PMD_PART_SSG_3,
+ PMD_PART_ADPCM,
+ PMD_PART_RHYTHM,
+ PMD_PART_FM_3B,
+ PMD_PART_FM_3C,
+ PMD_PART_FM_3D,
+ PMD_PART_PPZ_1,
+ PMD_PART_PPZ_2,
+ PMD_PART_PPZ_3,
+ PMD_PART_PPZ_4,
+ PMD_PART_PPZ_5,
+ PMD_PART_PPZ_6,
+ PMD_PART_PPZ_7,
+ PMD_PART_PPZ_8,
+ PMD_PART_FM_EFF,
+ PMD_PART_NUM,
+};
+
+struct pmd_lfo {
+ uint8_t delay;
+ uint8_t speed;
+ uint8_t step;
+ uint8_t times;
+};
+
+struct pmd_part {
+ // 0000
+ uint16_t ptr;
+ // 0002
+ uint16_t loop_ptr;
+ // 0004
+ uint8_t len_cnt;
+ // 0005
+ uint8_t gate;
+ // 0006
+ uint16_t actual_freq;
+ // 0008
+ int16_t detune;
+ // 000a
+ int16_t lfo_diff;
+ // 000c
+ int16_t portamento_diff;
+ // 000e
+ int16_t portamento_add;
+ // 0010
+ int16_t portamento_rem;
+ // 0012
+ uint8_t vol;
+ // 0013
+ uint8_t transpose;
+ // 0014
+ struct pmd_lfo lfo;
+ // 0018
+ struct pmd_lfo lfo_set;
+ // 001c
+ struct pmd_part_lfo_flags {
+ // bit 0
+ bool freq;
+ // bit 1
+ bool vol;
+ // bit 2
+ bool sync;
+ // bit 3
+ bool portamento;
+ } lfof, lfof_b;
+ // 001d
+ uint8_t volume_save;
+ // 001e
+ uint8_t md_depth;
+ // 001f
+ uint8_t md_speed;
+ // 0020
+ uint8_t md_speed_set;
+ // 0021
+ uint8_t ssg_env_state_old;
+ // 0022
+ uint8_t ssg_env_state_new;
+ // 0023
+ uint8_t ssg_env_param_set[4];
+ // 0027
+ uint8_t ssg_env_param_sl;
+ // 0028
+ uint8_t ssg_env_param_al;
+ // 0029
+ uint8_t ssg_env_param[4];
+ // 002d
+ int8_t ssg_env_vol;
+ // 002e
+ struct pmd_part_flagext {
+ // bit 0
+ bool detune;
+ // bit 1
+ bool lfo;
+ // bit 2
+ bool env;
+ } flagext, flagext_b;
+ // 002f
+ uint8_t fm_pan_ams_pms;
+ // 0030
+ uint8_t ssg_mix;
+ // 0031
+ uint8_t tonenum;
+ // 0032
+ struct {
+ // bit 0
+ bool looped;
+ // bit 1
+ bool ended;
+ // bit 2
+ bool env;
+ } loop;
+ // 0033
+ // bit 7-4: slot 4,3,2,1
+ uint8_t fm_slotout;
+ // 0034
+ // 1,3,2,4
+ uint8_t fm_tone_tl[4];
+ // 0038
+ // 4,3,2,1,0,0,0,0
+ // when 1, enabled
+ uint8_t fm_slotmask;
+ // 0039
+ // 1,3,2,4,1,3,2,4
+ uint8_t fm_tone_slotmask;
+ // 003a
+ uint8_t lfo_waveform;
+ // 003b
+ struct {
+ // bit 0
+ bool ext;
+ // bit 1
+ bool effect;
+ // bit 2
+ bool disabled;
+ // bit 5
+ // true when all slots masked (part->fm_slotmask == 0)
+ bool slot;
+ // bit 6
+ bool mml;
+ // bit 7
+ bool ff;
+ } mask;
+ // 003c
+ struct {
+ // bit 0, 2-7
+ bool off;
+ // bit 1
+ bool off_mask;
+ } keystatus;
+ // 003d
+ // default: same as part->fm_slotout
+ // bit 7-4: mask slot 4,3,2,1
+ // bit 3-0: if 1, overridden by user
+ uint8_t vol_lfo_slotmask;
+ // 003e
+ // q
+ uint8_t gate_abs;
+ // 003f
+ // Q
+ uint8_t gate_rel;
+ // 0040
+ uint8_t hlfo_delay_set;
+ // 0041
+ uint8_t hlfo_delay;
+ // 0042
+ int16_t lfo_diff_b;
+ // 0044
+ struct pmd_lfo lfo_b;
+ // 0048
+ struct pmd_lfo lfo_set_b;
+ // 004c
+ uint8_t md_depth_b;
+ // 004d
+ uint8_t md_speed_b;
+ // 004e
+ uint8_t md_speed_set_b;
+ // 004f
+ uint8_t lfo_waveform_b;
+ // 0050
+ uint8_t vol_lfo_slotmask_b;
+ // 0051
+ uint8_t md_cnt;
+ // 0052
+ uint8_t md_cnt_set;
+ // 0053
+ uint8_t md_cnt_b;
+ // 0054
+ uint8_t md_cnt_set_b;
+ // 0055
+ uint8_t actual_note;
+ // 0056
+ uint8_t slot_delay;
+ // 0057
+ uint8_t slot_delay_cnt;
+ // 0058
+ uint8_t slot_delay_mask;
+ // 0059
+ uint8_t fb_alg;
+ // 005a
+ // increment when new note/rest processed
+ uint8_t note_proc;
+ // 005b
+ uint8_t gate_min;
+ // 005e
+ uint8_t curr_note;
+ // 005f
+ uint8_t transpose_master;
+ // 0060
+ uint8_t gate_rand_range;
+
+ // original
+ // used from pmd_part_proc_ssg, pmd_cmdc0, pmd_cmdcf_slotmask
+ bool proc_masked;
+ // for display
+ uint8_t len;
+ uint16_t output_freq;
+};
+
+struct pmd_ssgeff_data {
+ // ticks to wait, or 0xff to end
+ uint8_t wait;
+ uint16_t tone_freq;
+ uint8_t noise_freq;
+ bool tone_mix;
+ bool noise_mix;
+ uint8_t env_level;
+ uint16_t env_freq;
+ uint8_t env_waveform;
+ int8_t tonefreq_add;
+ int8_t noisefreq_add;
+ uint8_t noisefreq_wait;
+};
+
+#include "pmd_ssgeff.h"
+
+struct driver_pmd {
+ // pmd->data = data+1;
+ // pmd->datalen = datalen-1;
+ // data[-1] is valid (OPM flag)
+ uint8_t *data;
+ uint16_t datalen;
+ // 0ee1
+ const struct pmd_ssgeff_data *ssgeff_ptr;
+ // 0ee3
+ uint16_t ssgeff_tonefreq;
+ // 0ee5
+ int16_t ssgeff_tonefreq_add;
+ // 0ee7
+ uint8_t ssgeff_wait;
+ // 0ee8
+ uint8_t ssgeff_noisefreq;
+ // 0ee9
+ int8_t ssgeff_noisefreq_add;
+ uint8_t ssgeff_noisefreq_cnt_set;
+ // 0eea
+ uint8_t ssgeff_noisefreq_cnt;
+ // 0eeb
+ uint8_t ssgeff_priority;
+ // 0eec
+ uint8_t ssgeff_num;
+ // 0eed
+ bool ssgeff_internal;
+ // 0eee
+ // 2fc2
+ uint16_t rand;
+ // 42ae
+ uint8_t proc_ch;
+ // 42af
+ // set with 0xfb
+ // when next command is 0xfb, disable gate (q/Q)
+ // used to implement tie
+ bool no_keyoff;
+ // 42b0
+ // set when volume saved for soft echo (W)
+ bool volume_saved;
+ // 42b2
+ bool opna_a1;
+ // 42b3
+ // same format as opna 0x28
+ // 0b4321____
+ uint8_t fm_slotkey[6];
+ // 42b9
+ // compare with part->loop
+ struct {
+ // bit 0
+ bool looped;
+ // bit 1
+ bool ended;
+ // bit 2
+ bool env;
+ } loop;
+ // 42ba
+ bool ppsdrv_enabled;
+ // 42c1
+ // 42c3
+ // timera_cnt sampled at timerb
+ uint8_t timera_cnt_b;
+ // 42c5
+ // needed when fm3ex_det valid
+ bool fm3ex_force;
+ // 42c6
+ // 1: ch3
+ // 2: ch3a
+ // 4: ch3b
+ // 8: ch3c
+ // when 0, not needed to extend fm3
+ uint8_t fm3ex_needed;
+ // 42d2
+ uint8_t fm3ex_fb_alg;
+ // 42d3
+ bool tonemask_fb_alg;
+ // 42d5
+ struct pmd_part_lfo_flags lfoprocf, lfoprocf_b;
+ // 42d6
+ // 430e
+ uint16_t tone_ptr;
+ // 4310
+ uint16_t r_offset;
+ // 4312
+ uint16_t r_ptr;
+ // 4314
+ uint8_t opnarhythm_mask;
+ // 4317
+ uint8_t fm_voldown;
+ // 4318
+ uint8_t ssg_voldown;
+ // 4319
+ uint8_t adpcm_voldown;
+ // 431a
+ uint8_t opnarhythm_voldown;
+ // 431b
+ bool tone_included;
+ // 431c
+ bool opm_flag;
+ // 431d
+ uint8_t status1;
+ // 431e
+ uint8_t status2;
+ // 431f
+ uint8_t timerb;
+ // 4320
+ uint8_t fadeout_speed;
+ // 4321
+ uint8_t fadeout_vol;
+ // 4322
+ uint8_t timerb_bak;
+ // 4323
+ uint8_t meas_len;
+ // 4324
+ uint8_t tick_cnt;
+ // 4325
+ uint8_t timera_cnt;
+ // 4326
+ bool ssgeff_disabled;
+ // 4327
+ uint8_t ssg_noise_freq;
+ // 4328
+ uint8_t ssg_noise_freq_wrote;
+ // 432a
+ bool fmeff_playing;
+ // 432c
+ bool adpcmeff_playing;
+ // 432d
+ uint16_t adpcm_start;
+ // 432f
+ uint16_t adpcm_stop;
+ // 433a
+ uint8_t opnarhythm;
+ // 433b
+ uint8_t opnarhythm_ilpan[6];
+ // 4341
+ uint8_t opnarhythm_tl;
+ // 4342
+ uint16_t ssgrhythm;
+ // 4348
+ bool playing;
+ // 4349
+ bool paused;
+ // 434a
+ //bool fadeout_mstop;
+ // 434b
+ bool ssgrhythm_opna;
+ // 4350
+ bool adpcm_disabled;
+ // 4354
+ int16_t fm3ex_det[4];
+ // 4362
+ uint8_t timerb_wrote;
+ // 4363
+ bool faded_out;
+ // 4366
+ bool pcm86_vol_spb;
+ // 4367
+ uint16_t meas_cnt;
+ // 436a
+ uint8_t opna_last22;
+ // 436b
+ uint8_t tempo;
+ // 436c
+ uint8_t tempo_bak;
+ // 4370
+ uint8_t fm_voldown_orig;
+ // 4371
+ uint8_t ssg_voldown_orig;
+ // 4372
+ uint8_t adpcm_voldown_orig;
+ // 4373
+ uint8_t opnarhythm_voldown_orig;
+ // 4374
+ bool pcm86_vol_spb_orig;
+ // 4386
+ uint8_t opnarhythm_shot_inc[6];
+ // 438c
+ uint8_t opnarhythm_dump_inc[6];
+ // 4392
+ // 0b0e111111
+ // output to 0x27
+ uint8_t fm3ex_mode;
+ // 4394
+ uint8_t ppz8_voldown;
+ // 4395
+ uint8_t ppz8_voldown_orig;
+ // 43ce
+ struct pmd_part parts[PMD_PART_NUM];
+
+ bool ssgeff_tone_mix;
+ bool ssgeff_noise_mix;
+ bool ssgeff_on;
+ char ppzfile[PMD_FILENAMELEN+1];
+ char ppsfile[PMD_FILENAMELEN+1];
+ char ppcfile[PMD_FILENAMELEN+1];
+};
+
+bool pmd_load(struct driver_pmd *pmd, uint8_t *data, uint16_t datalen);
+void pmd_init(struct fmdriver_work *work, struct driver_pmd *pmd);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MYON_FMDRIVER_PMD_H_INCLUDED
diff --git a/fmdriver/pmd_ssgeff.h b/fmdriver/pmd_ssgeff.h
new file mode 100644
index 0000000..f2087ad
--- /dev/null
+++ b/fmdriver/pmd_ssgeff.h
@@ -0,0 +1,281 @@
+enum {
+ PMD_SSGEFF_CNT = 0x28
+};
+
+// 0x00: 0x01: 0x0078
+static const struct pmd_ssgeff_data pmd_ssgeff_00[] = {
+ {0x01, 0x05dc, 0x1f, true, true, 0x0f, 0x0000, 0x00, 127, 0, 0},
+ {0x08, 0x06a4, 0x00, true, false, 0x10, 0x04b0, 0x00, 127, 0, 0},
+ {0xff}
+};
+// 0x01: 0x01: 0x008f
+static const struct pmd_ssgeff_data pmd_ssgeff_01[] = {
+ {0x0e, 0x0190, 0x07, true, true, 0x10, 0x0bb8, 0x00, 93, -1, 2},
+ {0xff}
+};
+// 0x02: 0x01: 0x009b
+static const struct pmd_ssgeff_data pmd_ssgeff_02[] = {
+ {0x02, 0x02bc, 0x00, true, true, 0x0f, 0x0000, 0x00, 100, 0, 0},
+ {0x0e, 0x0384, 0x00, true, true, 0x10, 0x09c4, 0x00, 100, 0, 0},
+ {0xff}
+};
+// 0x03: 0x01: 0x00b2
+static const struct pmd_ssgeff_data pmd_ssgeff_03[] = {
+ {0x02, 0x01f4, 0x05, true, true, 0x0f, 0x0000, 0x00, 60, 0, 0},
+ {0x0e, 0x026c, 0x00, true, true, 0x10, 0x09c4, 0x00, 60, 0, 0},
+ {0xff}
+};
+// 0x04: 0x01: 0x00c9
+static const struct pmd_ssgeff_data pmd_ssgeff_04[] = {
+ {0x02, 0x012c, 0x00, true, true, 0x0f, 0x0000, 0x00, 50, 0, 0},
+ {0x0e, 0x0190, 0x00, true, true, 0x10, 0x09c4, 0x00, 50, 0, 0},
+ {0xff}
+};
+// 0x05: 0x01: 0x00e0
+static const struct pmd_ssgeff_data pmd_ssgeff_05[] = {
+ {0x02, 0x0037, 0x00, true, false, 0x10, 0x012c, 0x00, 100, 0, 0},
+ {0xff}
+};
+// 0x06: 0x01: 0x00ec
+static const struct pmd_ssgeff_data pmd_ssgeff_06[] = {
+ {0x10, 0x0000, 0x0f, false, true, 0x10, 0x0bb8, 0x00, 0, -1, 1},
+ {0xff}
+};
+// 0x07: 0x01: 0x00f8
+static const struct pmd_ssgeff_data pmd_ssgeff_07[] = {
+ {0x06, 0x0027, 0x00, true, true, 0x10, 0x01f4, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x08: 0x01: 0x0104
+static const struct pmd_ssgeff_data pmd_ssgeff_08[] = {
+ {0x20, 0x0027, 0x00, true, true, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x09: 0x01: 0x0110
+static const struct pmd_ssgeff_data pmd_ssgeff_09[] = {
+ {0x1f, 0x0028, 0x1f, true, true, 0x10, 0x1388, 0x00, 0, -1, 1},
+ {0xff}
+};
+// 0x0a: 0x01: 0x011c
+static const struct pmd_ssgeff_data pmd_ssgeff_0a[] = {
+ {0x1f, 0x001e, 0x00, true, true, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x0b: 0x02: 0x0128
+static const struct pmd_ssgeff_data pmd_ssgeff_0b[] = {
+ {0x03, 0x01dd, 0x0f, false, true, 0x10, 0x03e8, 0x00, 0, 7, 1},
+ {0x02, 0x01dd, 0x00, false, true, 0x10, 0x03e8, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x0c: 0x02: 0x013f
+static const struct pmd_ssgeff_data pmd_ssgeff_0c[] = {
+ {0x01, 0x012c, 0x00, true, false, 0x10, 0x012c, 0x0d, 0, 0, 0},
+ {0x06, 0x012c, 0x00, true, false, 0x10, 0x2710, 0x00, 80, 0, 0},
+ {0xff}
+};
+// 0x0d: 0x02: 0x0156
+static const struct pmd_ssgeff_data pmd_ssgeff_0d[] = {
+ {0x04, 0x01dd, 0x00, false, true, 0x0e, 0x2710, 0x00, 0, 5, 1},
+ {0x04, 0x01dd, 0x0a, false, true, 0x10, 0x07d0, 0x00, 0, -1, 1},
+ {0xff}
+};
+// 0x0e: 0x02: 0x016d
+static const struct pmd_ssgeff_data pmd_ssgeff_0e[] = {
+ {0x03, 0x01dd, 0x00, false, true, 0x10, 0x01f4, 0x0d, 0, 0, 0},
+ {0x08, 0x01dd, 0x0f, false, true, 0x10, 0x07d0, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x0f: 0x02: 0x0184
+static const struct pmd_ssgeff_data pmd_ssgeff_0f[] = {
+ {0x03, 0x01dd, 0x0a, false, true, 0x10, 0x0064, 0x0d, 0, 0, 0},
+ {0x10, 0x01dd, 0x05, false, true, 0x10, 0x2710, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x10: 0x02: 0x019b
+static const struct pmd_ssgeff_data pmd_ssgeff_10[] = {
+ {0x02, 0x0190, 0x00, true, false, 0x10, 0x01f4, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x11: 0x02: 0x01a7
+static const struct pmd_ssgeff_data pmd_ssgeff_11[] = {
+ {0x04, 0x01dd, 0x0f, false, true, 0x10, 0x03e8, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x12: 0x02: 0x01b3
+static const struct pmd_ssgeff_data pmd_ssgeff_12[] = {
+ {0x02, 0x01dd, 0x1f, false, true, 0x0f, 0x2710, 0x00, 0, 0, 0},
+ {0x0c, 0x01dd, 0x00, false, true, 0x10, 0x1388, 0x00, 0, 1, 1},
+ {0xff}
+};
+// 0x13: 0x02: 0x01ca
+static const struct pmd_ssgeff_data pmd_ssgeff_13[] = {
+ {0x02, 0x0190, 0x00, true, false, 0x10, 0x03e8, 0x00, 0, 0, 0},
+ {0x02, 0x00c8, 0x00, true, false, 0x10, 0x03e8, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x14: 0x02: 0x01e1
+static const struct pmd_ssgeff_data pmd_ssgeff_14[] = {
+ {0x04, 0x0190, 0x00, true, false, 0x10, 0x07d0, 0x00, 0, 0, 0},
+ {0x08, 0x00c8, 0x00, true, false, 0x10, 0x0bb8, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x15: 0x02: 0x01f8
+static const struct pmd_ssgeff_data pmd_ssgeff_15[] = {
+ {0x03, 0x0190, 0x00, true, false, 0x10, 0x07d0, 0x00, 0, 0, 0},
+ {0x03, 0x0064, 0x00, true, false, 0x10, 0x07d0, 0x00, 0, 0, 0},
+ {0x03, 0x00c8, 0x00, true, false, 0x10, 0x07d0, 0x00, 0, 0, 0},
+ {0x03, 0x0190, 0x00, true, false, 0x10, 0x07d0, 0x00, 0, 0, 0},
+ {0x08, 0x0064, 0x00, true, false, 0x10, 0x0bb8, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x16: 0x02: 0x0230
+static const struct pmd_ssgeff_data pmd_ssgeff_16[] = {
+ {0x10, 0x07d0, 0x00, true, false, 0x0f, 0x2710, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x17: 0x02: 0x023c
+static const struct pmd_ssgeff_data pmd_ssgeff_17[] = {
+ {0x04, 0x01dd, 0x1f, false, true, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0x08, 0x01dd, 0x1f, true, true, 0x10, 0x0bb8, 0x00, 127, -1, 1},
+ {0xff}
+};
+// 0x18: 0x02: 0x0253
+static const struct pmd_ssgeff_data pmd_ssgeff_18[] = {
+ {0x04, 0x01dd, 0x19, false, true, 0x10, 0x07d0, 0x00, 0, 0, 0},
+ {0x20, 0x01dd, 0x14, false, true, 0x10, 0x1770, 0x00, 0, 1, 3},
+ {0xff}
+};
+// 0x19: 0x02: 0x026a
+static const struct pmd_ssgeff_data pmd_ssgeff_19[] = {
+ {0x06, 0x00c8, 0x00, true, true, 0x10, 0x1388, 0x00, 20, 0, 0},
+ {0xff}
+};
+// 0x1a: 0x02: 0x0276
+static const struct pmd_ssgeff_data pmd_ssgeff_1a[] = {
+ {0x04, 0x0028, 0x14, true, true, 0x10, 0x2710, 0x00, 20, 0, 0},
+ {0x10, 0x0014, 0x05, true, true, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x1b: 0x02: 0x028d
+static const struct pmd_ssgeff_data pmd_ssgeff_1b[] = {
+ {0x06, 0x0258, 0x00, true, false, 0x10, 0x03e8, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x1c: 0x02: 0x0299
+static const struct pmd_ssgeff_data pmd_ssgeff_1c[] = {
+ {0x04, 0x03e8, 0x00, true, false, 0x10, 0x2710, 0x00, 127, 0, 0},
+ {0x10, 0x01dd, 0x00, true, true, 0x10, 0x2710, 0x00, 64, 0, 0},
+ {0xff}
+};
+// 0x1d: 0x02: 0x02b0
+static const struct pmd_ssgeff_data pmd_ssgeff_1d[] = {
+ {0x04, 0x03e8, 0x1f, true, true, 0x0f, 0x2710, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x1e: 0x02: 0x02bc
+static const struct pmd_ssgeff_data pmd_ssgeff_1e[] = {
+ {0x04, 0x0fff, 0x1f, true, true, 0x0f, 0x2710, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x1f: 0x02: 0x02c8
+static const struct pmd_ssgeff_data pmd_ssgeff_1f[] = {
+ {0x04, 0x01dd, 0x00, true, false, 0x10, 0x03e8, 0x00, -50, 0, 0},
+ {0x10, 0x00f2, 0x00, true, false, 0x10, 0x1770, 0x00, -8, 0, 0},
+ {0xff}
+};
+// 0x20: 0x02: 0x02df
+static const struct pmd_ssgeff_data pmd_ssgeff_20[] = {
+ {0x04, 0x0064, 0x00, true, false, 0x10, 0x01f4, 0x00, 0, 0, 0},
+ {0x04, 0x000a, 0x00, true, true, 0x10, 0x03e8, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x21: 0x02: 0x02f6
+static const struct pmd_ssgeff_data pmd_ssgeff_21[] = {
+ {0x08, 0x01dd, 0x05, false, true, 0x10, 0x01f4, 0x0d, 0, 0, 0},
+ {0x18, 0x001e, 0x00, true, true, 0x10, 0x2710, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x22: 0x02: 0x030d
+static const struct pmd_ssgeff_data pmd_ssgeff_22[] = {
+ {0x04, 0x012c, 0x00, true, false, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0x04, 0x00b4, 0x00, true, false, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0x04, 0x00c8, 0x00, true, false, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0x18, 0x0096, 0x00, true, false, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x23: 0x02: 0x033a
+static const struct pmd_ssgeff_data pmd_ssgeff_23[] = {
+ {0x03, 0x00ee, 0x00, true, false, 0x0e, 0x07d0, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x24: 0x02: 0x0346
+static const struct pmd_ssgeff_data pmd_ssgeff_24[] = {
+ {0x04, 0x00c8, 0x00, true, false, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0x10, 0x0064, 0x00, true, false, 0x10, 0x1388, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x25: 0x02: 0x035d
+static const struct pmd_ssgeff_data pmd_ssgeff_25[] = {
+ {0x10, 0x0000, 0x00, true, true, 0x10, 0x01f4, 0x0d, 1, 1, 1},
+ {0x10, 0x0010, 0x10, true, true, 0x10, 0x157c, 0x00, 1, 1, 1},
+ {0xff}
+};
+// 0x26: 0x02: 0x0374
+static const struct pmd_ssgeff_data pmd_ssgeff_26[] = {
+ {0x01, 0x00c8, 0x00, true, false, 0x0e, 0x03e8, 0x00, 0, 0, 0},
+ {0xff}
+};
+// 0x27: 0x02: 0x0380
+static const struct pmd_ssgeff_data pmd_ssgeff_27[] = {
+ {0x02, 0x00c8, 0x00, true, false, 0x10, 0x0320, 0x00, 0, 0, 0},
+ {0x02, 0x0064, 0x00, true, false, 0x10, 0x0320, 0x00, 0, 0, 0},
+ {0x02, 0x0032, 0x00, true, false, 0x10, 0x0320, 0x00, 0, 0, 0},
+ {0x02, 0x0019, 0x00, true, false, 0x10, 0x0320, 0x00, 0, 0, 0},
+ {0xff}
+};
+
+static const struct {
+ uint8_t priority;
+ const struct pmd_ssgeff_data *data;
+} pmd_ssgeff_table[PMD_SSGEFF_CNT] = {
+ {1, pmd_ssgeff_00},
+ {1, pmd_ssgeff_01},
+ {1, pmd_ssgeff_02},
+ {1, pmd_ssgeff_03},
+ {1, pmd_ssgeff_04},
+ {1, pmd_ssgeff_05},
+ {1, pmd_ssgeff_06},
+ {1, pmd_ssgeff_07},
+ {1, pmd_ssgeff_08},
+ {1, pmd_ssgeff_09},
+ {1, pmd_ssgeff_0a},
+ {2, pmd_ssgeff_0b},
+ {2, pmd_ssgeff_0c},
+ {2, pmd_ssgeff_0d},
+ {2, pmd_ssgeff_0e},
+ {2, pmd_ssgeff_0f},
+ {2, pmd_ssgeff_10},
+ {2, pmd_ssgeff_11},
+ {2, pmd_ssgeff_12},
+ {2, pmd_ssgeff_13},
+ {2, pmd_ssgeff_14},
+ {2, pmd_ssgeff_15},
+ {2, pmd_ssgeff_16},
+ {2, pmd_ssgeff_17},
+ {2, pmd_ssgeff_18},
+ {2, pmd_ssgeff_19},
+ {2, pmd_ssgeff_1a},
+ {2, pmd_ssgeff_1b},
+ {2, pmd_ssgeff_1c},
+ {2, pmd_ssgeff_1d},
+ {2, pmd_ssgeff_1e},
+ {2, pmd_ssgeff_1f},
+ {2, pmd_ssgeff_20},
+ {2, pmd_ssgeff_21},
+ {2, pmd_ssgeff_22},
+ {2, pmd_ssgeff_23},
+ {2, pmd_ssgeff_24},
+ {2, pmd_ssgeff_25},
+ {2, pmd_ssgeff_26},
+ {2, pmd_ssgeff_27},
+};
diff --git a/fmdsp/fmdsp.c b/fmdsp/fmdsp.c
index 6c880b7..abf60a0 100644
--- a/fmdsp/fmdsp.c
+++ b/fmdsp/fmdsp.c
@@ -452,7 +452,7 @@ static void fmdsp_track_without_key(
) {
const char *track_info1 = " ";
char track_info2[5] = " ";
- if (track->playing) {
+ if (track->playing || track->info == FMDRIVER_TRACK_INFO_SSGEFF) {
switch (track->info) {
case FMDRIVER_TRACK_INFO_PPZ8:
snprintf(track_info2, sizeof(track_info2), "PPZ8");
@@ -460,11 +460,14 @@ static void fmdsp_track_without_key(
case FMDRIVER_TRACK_INFO_PDZF:
snprintf(track_info2, sizeof(track_info2), "PDZF");
break;
- case FMDRIVER_TRACK_INFO_SSG_NOISE_ONLY:
- snprintf(track_info2, sizeof(track_info2), "N%02X ", work->ssg_noise_freq);
- break;
- case FMDRIVER_TRACK_INFO_SSG_NOISE_MIX:
- snprintf(track_info2, sizeof(track_info2), "M%02X ", work->ssg_noise_freq);
+ case FMDRIVER_TRACK_INFO_SSGEFF:
+ track_info1 = "EFF ";
+ case FMDRIVER_TRACK_INFO_SSG:
+ if (track->ssg_noise) {
+ snprintf(track_info2, sizeof(track_info2), "%c%02X ",
+ track->ssg_tone ? 'M' : 'N',
+ work->ssg_noise_freq);
+ }
break;
case FMDRIVER_TRACK_INFO_FM3EX:
track_info1 = "EX ";
@@ -564,7 +567,7 @@ static void fmdsp_update_10(struct fmdsp *fmdsp,
for (int i = 0; i < KEY_OCTAVES; i++) {
vramblit(vram, KEY_X+KEY_W*i, TRACK_H*it+KEY_Y,
s_key_bg, KEY_W, KEY_H);
- if (track->playing) {
+ if (track->playing || track->info == FMDRIVER_TRACK_INFO_SSGEFF) {
if (track->actual_key >> 4 == i) {
vramblit_key(vram, KEY_X+KEY_W*i, TRACK_H*it+KEY_Y,
s_key_mask, KEY_W, KEY_H,
@@ -618,7 +621,7 @@ static void fmdsp_update_13(struct fmdsp *fmdsp,
for (int i = 0; i < KEY_OCTAVES; i++) {
vramblit(vram, KEY_X+KEY_W*i, TRACK_H_S*it+KEY_Y,
s_key_bg + KEY_S_OFFSET, KEY_W, KEY_H_S);
- if (track->playing) {
+ if (track->playing || track->info == FMDRIVER_TRACK_INFO_SSGEFF) {
if (track->actual_key >> 4 == i) {
vramblit_key(vram, KEY_X+KEY_W*i, TRACK_H_S*it+KEY_Y,
s_key_mask + KEY_S_OFFSET, KEY_W, KEY_H_S,
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 665f3ca..970bf73 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -8,6 +8,8 @@ LIBOPNA_SRC=../libopna/opnaadpcm.c \
../libopna/opna.c
FMDRIVER_SRC=../fmdriver/fmdriver_fmp.c \
+ ../fmdriver/fmdriver_pmd.c \
+ ../fmdriver/fmdriver_common.c \
../fmdriver/ppz8.c
FMDSP_SRC=../fmdsp/fmdsp.c \
diff --git a/gtk/main.c b/gtk/main.c
index cd3ddb2..6fd7ec4 100644
--- a/gtk/main.c
+++ b/gtk/main.c
@@ -8,6 +8,7 @@
#include <stdatomic.h>
#include "fmdriver/fmdriver_fmp.h"
+#include "fmdriver/fmdriver_pmd.h"
#include "fmdriver/ppz8.h"
#include "libopna/opna.h"
#include "libopna/opnatimer.h"
@@ -23,6 +24,11 @@ enum {
AUDIOBUFLEN = 0,
};
+union drivers {
+ struct driver_pmd pmd;
+ struct driver_fmp fmp;
+};
+
static struct {
GtkWidget *mainwin;
bool pa_initialized;
@@ -36,8 +42,8 @@ static struct {
char drum_rom[OPNA_ROM_SIZE];
bool drum_rom_loaded;
char adpcm_ram[OPNA_ADPCM_RAM_SIZE];
- struct driver_fmp *fmp;
- void *fmpdata;
+ union drivers *driver;
+ void *data;
void *ppzbuf;
uint8_t vram[PC98_W*PC98_H];
struct fmdsp_font font98;
@@ -52,8 +58,8 @@ static void quit(void) {
Pa_CloseStream(g.pastream);
}
if (g.pa_initialized) Pa_Terminate();
- free(g.fmp);
- free(g.fmpdata);
+ free(g.driver);
+ free(g.data);
free(g.ppzbuf);
gtk_main_quit();
}
@@ -88,11 +94,10 @@ static int pastream_cb(const void *inptr, void *outptr, unsigned long frames,
memset(outptr, 0, sizeof(int16_t)*frames*2);
opna_timer_mix(timer, buf, frames);
- bool xchg = false;
- if (atomic_compare_exchange_weak_explicit(&toneview_g.flag, &xchg, true,
- memory_order_acquire, memory_order_relaxed)) {
+ if (!atomic_flag_test_and_set_explicit(
+ &toneview_g.flag, memory_order_acquire)) {
tonedata_from_opna(&toneview_g.tonedata, &g.opna);
- atomic_store_explicit(&toneview_g.flag, false, memory_order_release);
+ atomic_flag_clear_explicit(&toneview_g.flag, memory_order_release);
}
return paContinue;
}
@@ -112,6 +117,11 @@ static void opna_writereg_libopna(struct fmdriver_work *work, unsigned addr, uns
opna_timer_writereg(timer, addr, data);
}
+static unsigned opna_readreg_libopna(struct fmdriver_work *work, unsigned addr) {
+ struct opna_timer *timer = (struct opna_timer *)work->opna;
+ return opna_readreg(&g.opna, addr);
+}
+
static uint8_t opna_status_libopna(struct fmdriver_work *work, bool a1) {
struct opna_timer *timer = (struct opna_timer *)work->opna;
uint8_t status = opna_timer_status(timer);
@@ -278,6 +288,10 @@ err:
}
static bool openfile(const char *uri) {
+ enum {
+ DRIVER_PMD,
+ DRIVER_FMP
+ } driver_type;
if (!g.pa_initialized) {
msgbox_err("Could not initialize Portaudio");
goto err;
@@ -316,33 +330,40 @@ static bool openfile(const char *uri) {
msgbox_err("cannot read file");
goto err_buf;
}
- struct driver_fmp *fmp = calloc(1, sizeof(struct driver_fmp));
- if (!fmp) {
+ union drivers *driver = calloc(1, sizeof(*driver));
+ if (!driver) {
msgbox_err("cannot allocate memory for fmp");
goto err_buf;
}
- if (!fmp_load(fmp, fmbuf, filelen)) {
- msgbox_err("invalid FMP file");
- goto err_fmp;
+ if (fmp_load(&driver->fmp, fmbuf, filelen)) {
+ driver_type = DRIVER_FMP;
+ } else {
+ memset(driver, 0, sizeof(*driver));
+ if (pmd_load(&driver->pmd, fmbuf, filelen)) {
+ driver_type = DRIVER_PMD;
+ } else {
+ msgbox_err("invalid file");
+ goto err_driver;
+ }
}
if (!g.pastream) {
PaError pe = Pa_OpenDefaultStream(&g.pastream, 0, 2, paInt16, SRATE, AUDIOBUFLEN,
pastream_cb, &g.opna_timer);
if (pe != paNoError) {
msgbox_err("cannot open portaudio stream");
- goto err_fmp;
+ goto err_driver;
}
} else if (!g.pa_paused) {
PaError pe = Pa_StopStream(g.pastream);
if (pe != paNoError) {
msgbox_err("Portaudio Error");
- goto err_fmp;
+ goto err_driver;
}
}
- free(g.fmp);
- g.fmp = fmp;
- free(g.fmpdata);
- g.fmpdata = fmbuf;
+ free(g.driver);
+ g.driver = driver;
+ free(g.data);
+ g.data = fmbuf;
opna_reset(&g.opna);
if (!g.drum_rom_loaded) {
load_drumrom();
@@ -355,20 +376,25 @@ static bool openfile(const char *uri) {
opna_timer_reset(&g.opna_timer, &g.opna);
memset(&g.work, 0, sizeof(g.work));
g.work.opna_writereg = opna_writereg_libopna;
+ g.work.opna_readreg = opna_readreg_libopna;
g.work.opna_status = opna_status_libopna;
g.work.opna = &g.opna_timer;
g.work.ppz8 = &g.ppz8;
g.work.ppz8_functbl = &ppz8_functbl;
opna_timer_set_int_callback(&g.opna_timer, opna_int_cb, &g.work);
opna_timer_set_mix_callback(&g.opna_timer, opna_mix_cb, &g.ppz8);
- fmp_init(&g.work, g.fmp);
- fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);
- GFile *dir = g_file_get_parent(fmfile);
- if (dir) {
- loadpvi(&g.work, g.fmp, dir);
- loadppzpvi(&g.work, g.fmp, dir);
- g_object_unref(G_OBJECT(dir));
+ if (driver_type == DRIVER_FMP) {
+ fmp_init(&g.work, &g.driver->fmp);
+ GFile *dir = g_file_get_parent(fmfile);
+ if (dir) {
+ loadpvi(&g.work, &g.driver->fmp, dir);
+ loadppzpvi(&g.work, &g.driver->fmp, dir);
+ g_object_unref(G_OBJECT(dir));
+ }
+ } else {
+ pmd_init(&g.work, &g.driver->pmd);
}
+ fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);
g_object_unref(G_OBJECT(fmstream));
g_object_unref(G_OBJECT(fminfo));
g_object_unref(G_OBJECT(fmfile));
@@ -380,8 +406,8 @@ static bool openfile(const char *uri) {
g.current_uri = turi;
}
return true;
-err_fmp:
- free(fmp);
+err_driver:
+ free(driver);
err_buf:
free(fmbuf);
err_stream:
diff --git a/gtk/toneview.c b/gtk/toneview.c
index 79be94f..47c8f86 100644
--- a/gtk/toneview.c
+++ b/gtk/toneview.c
@@ -3,7 +3,9 @@
#include <stdatomic.h>
#include <stdbool.h>
-struct toneview_g toneview_g;
+struct toneview_g toneview_g = {
+ .flag = ATOMIC_FLAG_INIT
+};
static struct {
GtkWidget *tonewin;
@@ -14,7 +16,9 @@ static struct {
enum fmplayer_tonedata_format format;
bool normalize;
GtkClipboard *clipboard;
-} g;
+} g = {
+ .normalize = true
+};
static void on_destroy(GtkWidget *w, gpointer ptr) {
(void)w;
@@ -26,11 +30,10 @@ gboolean tick_cb(GtkWidget *widget, GdkFrameClock *clock, gpointer ptr) {
(void)widget;
(void)clock;
(void)ptr;
- bool xchg = false;
- if (atomic_compare_exchange_weak_explicit(&toneview_g.flag, &xchg, true,
- memory_order_acquire, memory_order_relaxed)) {
+ if (!atomic_flag_test_and_set_explicit(
+ &toneview_g.flag, memory_order_acquire)) {
g.tonedata = toneview_g.tonedata;
- atomic_store_explicit(&toneview_g.flag, false, memory_order_release);
+ atomic_flag_clear_explicit(&toneview_g.flag, memory_order_release);
}
g.tonedata_n = g.tonedata;
for (int c = 0; c < 6; c++) {
diff --git a/gtk/toneview.h b/gtk/toneview.h
index 51c1769..376787e 100644
--- a/gtk/toneview.h
+++ b/gtk/toneview.h
@@ -6,9 +6,9 @@
extern struct toneview_g {
struct fmplayer_tonedata tonedata;
- atomic_bool flag;
+ atomic_flag flag;
} toneview_g;
-void show_toneview();
+void show_toneview(void);
#endif // MYON_FMPLAYER_GTK_TONEVIEW_H_INCLUDED
diff --git a/win32/fmplayer.mak b/win32/fmplayer.mak
index 4eb3919..11f98f2 100644
--- a/win32/fmplayer.mak
+++ b/win32/fmplayer.mak
@@ -7,7 +7,9 @@ DEFINES=UNICODE _UNICODE \
WINVER=0x0500 _WIN32_WINNT=0x0500 \
DIRECTSOUND_VERSION=0x0800
-FMDRIVER_OBJS=fmdriver_fmp \
+FMDRIVER_OBJS=fmdriver_pmd \
+ fmdriver_fmp \
+ fmdriver_common \
ppz8
LIBOPNA_OBJS=opna \
opnatimer \
diff --git a/win32/main.c b/win32/main.c
index 6269359..16d2f3e 100644
--- a/win32/main.c
+++ b/win32/main.c
@@ -6,6 +6,7 @@
#include <commctrl.h>
#include "fmdriver/fmdriver_fmp.h"
+#include "fmdriver/fmdriver_pmd.h"
#include "libopna/opna.h"
#include "libopna/opnatimer.h"
#include "fmdsp/fmdsp.h"
@@ -30,6 +31,11 @@ enum {
#define ENABLE_WM_DROPFILES
// #define ENABLE_IDROPTARGET
+union drivers {
+ struct driver_pmd pmd;
+ struct driver_fmp fmp;
+};
+
static struct {
HINSTANCE hinst;
HANDLE heap;
@@ -38,7 +44,8 @@ static struct {
struct opna_timer opna_timer;
struct ppz8 ppz8;
struct fmdriver_work work;
- struct driver_fmp *fmp;
+ union drivers *driver;
+ uint8_t *data;
struct fmdsp fmdsp;
uint8_t vram[PC98_W*PC98_H];
struct fmdsp_font font;
@@ -68,6 +75,11 @@ static void opna_writereg_libopna(struct fmdriver_work *work, unsigned addr, uns
opna_timer_writereg(timer, addr, data);
}
+static unsigned opna_readreg_libopna(struct fmdriver_work *work, unsigned addr) {
+ struct opna_timer *timer = (struct opna_timer *)work->opna;
+ return opna_readreg(&g.opna, addr);
+}
+
static uint8_t opna_status_libopna(struct fmdriver_work *work, bool a1) {
struct opna_timer *timer = (struct opna_timer *)work->opna;
uint8_t status = opna_timer_status(timer);
@@ -238,6 +250,10 @@ err:
}
static void openfile(HWND hwnd, const wchar_t *path) {
+ enum {
+ DRIVER_PMD,
+ DRIVER_FMP
+ } driver_type;
HANDLE file = CreateFile(path, GENERIC_READ,
0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (file == INVALID_HANDLE_VALUE) {
@@ -253,30 +269,39 @@ static void openfile(HWND hwnd, const wchar_t *path) {
MessageBox(hwnd, L"Invalid File (Filesize too large)", L"Error", MB_ICONSTOP);
goto err_file;
}
- void *fmpdata = HeapAlloc(g.heap, 0, li.QuadPart);
- if (!fmpdata) {
+ void *data = HeapAlloc(g.heap, 0, li.QuadPart);
+ if (!data) {
MessageBox(hwnd, L"Cannot allocate memory for file", L"Error", MB_ICONSTOP);
goto err_file;
}
DWORD readbytes;
- if (!ReadFile(file, fmpdata, li.QuadPart, &readbytes, 0) || readbytes != li.QuadPart) {
+ if (!ReadFile(file, data, li.QuadPart, &readbytes, 0) || readbytes != li.QuadPart) {
MessageBox(hwnd, L"Cannot read file", L"Error", MB_ICONSTOP);
- goto err_fmpdata;
+ goto err_data;
}
- struct driver_fmp *fmp = HeapAlloc(g.heap, HEAP_ZERO_MEMORY, sizeof(struct driver_fmp));
- if (!fmp) {
+ union drivers *driver = HeapAlloc(g.heap, HEAP_ZERO_MEMORY, sizeof(*driver));
+ if (!driver) {
MessageBox(hwnd, L"Cannot allocate memory for fmp", L"Error", MB_ICONSTOP);
- goto err_fmpdata;
+ goto err_data;
}
- if (!fmp_load(fmp, fmpdata, li.QuadPart)) {
- MessageBox(hwnd, L"Invalid File (not FMP data)", L"Error", MB_ICONSTOP);
- goto err_fmp;
+ if (fmp_load(&driver->fmp, data, li.QuadPart)) {
+ driver_type = DRIVER_FMP;
+ } else {
+ ZeroMemory(driver, sizeof(*driver));
+ if (pmd_load(&driver->pmd, data, li.QuadPart)) {
+ driver_type = DRIVER_PMD;
+ } else {
+ MessageBox(hwnd, L"Invalid File (not FMP or PMD)", L"Error", MB_ICONSTOP);
+ goto err_driver;
+ }
}
if (g.sound) {
g.sound->pause(g.sound, 1);
}
- if (g.fmp) HeapFree(g.heap, 0, g.fmp);
- g.fmp = fmp;
+ if (g.driver) HeapFree(g.heap, 0, g.driver);
+ g.driver = driver;
+ if (g.data) HeapFree(g.heap, 0, g.data);
+ g.data = data;
opna_reset(&g.opna);
if (g.drum_rom) opna_drum_set_rom(&g.opna.drum, g.drum_rom);
opna_adpcm_set_ram_256k(&g.opna.adpcm, g.opna_adpcm_ram);
@@ -284,22 +309,27 @@ static void openfile(HWND hwnd, const wchar_t *path) {
ppz8_init(&g.ppz8, SRATE, PPZ8MIX);
ZeroMemory(&g.work, sizeof(g.work));
g.work.opna_writereg = opna_writereg_libopna;
+ g.work.opna_readreg = opna_readreg_libopna;
g.work.opna_status = opna_status_libopna;
g.work.opna = &g.opna_timer;
g.work.ppz8 = &g.ppz8;
g.work.ppz8_functbl = &ppz8_functbl;
opna_timer_set_int_callback(&g.opna_timer, opna_int_cb, &g.work);
opna_timer_set_mix_callback(&g.opna_timer, opna_mix_cb, &g.ppz8);
- fmp_init(&g.work, g.fmp);
- loadpvi(&g.work, g.fmp, path);
- loadppzpvi(&g.work, g.fmp, path);
+ if (driver_type == DRIVER_PMD) {
+ pmd_init(&g.work, &g.driver->pmd);
+ } else {
+ fmp_init(&g.work, &g.driver->fmp);
+ loadpvi(&g.work, &g.driver->fmp, path);
+ loadppzpvi(&g.work, &g.driver->fmp, path);
+ }
if (!g.sound) {
g.sound = sound_init(hwnd, SRATE, SECTLEN,
sound_cb, &g.opna_timer);
SetWindowText(g.driverinfo, g.sound->apiname);
}
fmdsp_vram_init(&g.fmdsp, &g.work, g.vram);
- if (!g.sound) goto err_fmp;
+ if (!g.sound) goto err_driver;
g.sound->pause(g.sound, 0);
g.paused = false;
CloseHandle(file);
@@ -310,10 +340,10 @@ static void openfile(HWND hwnd, const wchar_t *path) {
if (g.lastopenpath) HeapFree(g.heap, 0, (void *)g.lastopenpath);
g.lastopenpath = pathcpy;
return;
-err_fmp:
- HeapFree(g.heap, 0, fmp);
-err_fmpdata:
- HeapFree(g.heap, 0, fmpdata);
+err_driver:
+ HeapFree(g.heap, 0, driver);
+err_data:
+ HeapFree(g.heap, 0, data);
err_file:
CloseHandle(file);
}
@@ -324,8 +354,14 @@ static void openfiledialog(HWND hwnd) {
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.hInstance = g.hinst;
- ofn.lpstrFilter = L"FMP files (*.opi;*.ovi;*.ozi;*.m26;*.m86)\0"
- "*.opi;*.ovi;*.ozi;*.m26;*.m86\0"
+ ofn.lpstrFilter = L"All supported files (*.m;*.m2;*.mz;*.opi;*.ovi;*.ozi;*.m26;*.m86)\0"
+ "*.m;*.m2;*.mz;*.opi;*.ovi;*.ozi;*.m26;*.m86\0"
+ "PMD files (*.m;*.m2;*.mz)\0"
+ "*.m;*.m2;*.mz\0"
+ "FMP files (*.opi;*.ovi;*.ozi)\0"
+ "*.opi;*.ovi;*.ozi\0"
+ "PLAY6 files (*.m26;*.m86)\0"
+ "*.m26;*.m86\0"
"All Files (*.*)\0"
"*.*\0\0";
ofn.lpstrFile = path;
@@ -459,7 +495,8 @@ static void on_command(HWND hwnd, int id, HWND hwnd_c, UINT code) {
static void on_destroy(HWND hwnd) {
(void)hwnd;
if (g.sound) g.sound->free(g.sound);
- if (g.fmp) HeapFree(g.heap, 0, g.fmp);
+ if (g.driver) HeapFree(g.heap, 0, g.driver);
+ if (g.data) HeapFree(g.heap, 0, g.data);
if (g.drum_rom) HeapFree(g.heap, 0, g.drum_rom);
if (g.ppz8_buf) HeapFree(g.heap, 0, g.ppz8_buf);
PostQuitMessage(0);