diff options
author | Takamichi Horikawa <takamichiho@gmail.com> | 2017-03-14 19:10:24 +0900 |
---|---|---|
committer | Takamichi Horikawa <takamichiho@gmail.com> | 2017-03-14 19:10:24 +0900 |
commit | 46e2455027537dc1ef56b98b712cf92edf27dca5 (patch) | |
tree | e2a09f9b53274a22c0f9a08a2a56bf3f31cf507e | |
parent | 16be4100808e09a94e802adb58bc1c79e5eefd65 (diff) |
add initial PMD support
-rw-r--r-- | fmdriver/fmdriver.h | 6 | ||||
-rw-r--r-- | fmdriver/fmdriver_common.c | 71 | ||||
-rw-r--r-- | fmdriver/fmdriver_common.h | 5 | ||||
-rw-r--r-- | fmdriver/fmdriver_fmp.c | 83 | ||||
-rw-r--r-- | fmdriver/fmdriver_pmd.c | 4674 | ||||
-rw-r--r-- | fmdriver/fmdriver_pmd.h | 462 | ||||
-rw-r--r-- | fmdriver/pmd_ssgeff.h | 281 | ||||
-rw-r--r-- | fmdsp/fmdsp.c | 19 | ||||
-rw-r--r-- | gtk/Makefile.am | 2 | ||||
-rw-r--r-- | gtk/main.c | 82 | ||||
-rw-r--r-- | gtk/toneview.c | 15 | ||||
-rw-r--r-- | gtk/toneview.h | 4 | ||||
-rw-r--r-- | win32/fmplayer.mak | 4 | ||||
-rw-r--r-- | win32/main.c | 85 |
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 \ @@ -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); |