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 /fmdriver | |
| parent | 16be4100808e09a94e802adb58bc1c79e5eefd65 (diff) | |
add initial PMD support
Diffstat (limited to 'fmdriver')
| -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 | 
7 files changed, 5502 insertions, 80 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}, +}; | 
