From c5a386c9e2cce061310f3660e4898218dabbec31 Mon Sep 17 00:00:00 2001
From: Takamichi Horikawa <takamichiho@gmail.com>
Date: Mon, 27 Mar 2017 23:28:47 +0900
Subject: PMD: add initial PPZ8 support

---
 fmdriver/fmdriver.h        |  12 +
 fmdriver/fmdriver_common.h |  18 ++
 fmdriver/fmdriver_fmp.c    |  27 +--
 fmdriver/fmdriver_pmd.c    | 535 +++++++++++++++++++++++++++++++++++++++++++--
 fmdriver/fmdriver_pmd.h    |   6 +-
 fmdriver/ppz8.c            |  50 ++++-
 fmdriver/ppz8.h            |   9 +
 7 files changed, 614 insertions(+), 43 deletions(-)

(limited to 'fmdriver')

diff --git a/fmdriver/fmdriver.h b/fmdriver/fmdriver.h
index 84234ee..9f3afee 100644
--- a/fmdriver/fmdriver.h
+++ b/fmdriver/fmdriver.h
@@ -19,6 +19,14 @@ enum {
   FMDRIVER_TRACK_SSG_2,
   FMDRIVER_TRACK_SSG_3,
   FMDRIVER_TRACK_ADPCM,
+  FMDRIVER_TRACK_PPZ8_1,
+  FMDRIVER_TRACK_PPZ8_2,
+  FMDRIVER_TRACK_PPZ8_3,
+  FMDRIVER_TRACK_PPZ8_4,
+  FMDRIVER_TRACK_PPZ8_5,
+  FMDRIVER_TRACK_PPZ8_6,
+  FMDRIVER_TRACK_PPZ8_7,
+  FMDRIVER_TRACK_PPZ8_8,
   FMDRIVER_TRACK_NUM
 };
 enum {
@@ -30,6 +38,7 @@ enum fmdriver_track_type {
   FMDRIVER_TRACKTYPE_FM,
   FMDRIVER_TRACKTYPE_SSG,
   FMDRIVER_TRACKTYPE_ADPCM,
+  FMDRIVER_TRACKTYPE_PPZ8,
   FMDRIVER_TRACKTYPE_CNT,
 };
 
@@ -85,7 +94,10 @@ struct fmdriver_work {
   char comment[3][FMDRIVER_TITLE_BUFLEN];
   // only single-byte uppercase cp932
   char filename[FMDRIVER_TITLE_BUFLEN];
+  // always 8 characters and pad with ' '
+  char pcmname[2][9];
   // driver status (for display)
+  bool pcmerror[2];
   uint8_t ssg_noise_freq;
   struct fmdriver_track_status track_status[FMDRIVER_TRACK_NUM];
   // fm3ex part map
diff --git a/fmdriver/fmdriver_common.h b/fmdriver/fmdriver_common.h
index 2700336..30cf12c 100644
--- a/fmdriver/fmdriver_common.h
+++ b/fmdriver/fmdriver_common.h
@@ -7,6 +7,10 @@ static inline uint16_t read16le(const uint8_t *ptr) {
   return (unsigned)ptr[0] | (((unsigned)ptr[1])<<8);
 }
 
+static inline uint32_t read32le(const uint8_t *ptr) {
+  return (uint32_t)ptr[0] | (((uint32_t)ptr[1])<<8) | (((uint32_t)ptr[2])<<16) | (((uint32_t)ptr[3])<<24);
+}
+
 static inline int8_t u8s8(uint8_t v) {
   return (v & 0x80) ? ((int16_t)v)-0x100 : v;
 }
@@ -15,6 +19,20 @@ static inline int16_t u16s16(uint16_t v) {
   return (v & 0x8000u) ? ((int32_t)v)-0x10000l : v;
 }
 
+static inline void fmdriver_fillpcmname(char *dest, const char *src) {
+  int i;
+  for (i = 0; i < 8; i++) {
+    dest[i] = src[i];
+    if (!src[i]) break;
+  }
+  if (src[i]) i = 0;
+
+  for (; i < 8; i++) {
+    dest[i] = ' ';
+  }
+  dest[i] = 0;
+}
+
 uint8_t fmdriver_fm_freq2key(uint16_t freq);
 uint8_t fmdriver_ssg_freq2key(uint16_t freq);
 
diff --git a/fmdriver/fmdriver_fmp.c b/fmdriver/fmdriver_fmp.c
index 90a157c..cc6f1a6 100644
--- a/fmdriver/fmdriver_fmp.c
+++ b/fmdriver/fmdriver_fmp.c
@@ -2746,6 +2746,14 @@ static const uint8_t fmp_track_map[FMDRIVER_TRACK_NUM] = {
   FMP_PART_SSG_2,
   FMP_PART_SSG_3,
   FMP_PART_ADPCM,
+  FMP_PART_PPZ8_1,
+  FMP_PART_PPZ8_2,
+  FMP_PART_PPZ8_3,
+  FMP_PART_PPZ8_4,
+  FMP_PART_PPZ8_5,
+  FMP_PART_PPZ8_6,
+  FMP_PART_PPZ8_7,
+  FMP_PART_PPZ8_8,
 };
 
 static void fmp_work_status_init(struct fmdriver_work *work,
@@ -3468,27 +3476,12 @@ bool fmp_load(struct driver_fmp *fmp,
   return true;
 }
 
-static const uint8_t fmp_fmdriver_track_map[FMDRIVER_TRACK_NUM] = {
-  FMP_PART_FM_1,
-  FMP_PART_FM_2,
-  FMP_PART_FM_3,
-  FMP_PART_FM_EX1,
-  FMP_PART_FM_EX2,
-  FMP_PART_FM_EX3,
-  FMP_PART_FM_4,
-  FMP_PART_FM_5,
-  FMP_PART_FM_6,
-  FMP_PART_SSG_1,
-  FMP_PART_SSG_2,
-  FMP_PART_SSG_3,
-  FMP_PART_ADPCM
-};
-
 void fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp) {
   fmp_title(work, fmp, read16le(fmp->data)+4);
   fmp_struct_init(work, fmp);
   fmp_init_parts(work, fmp);
   uint16_t fmtoneptr = fmp->datainfo.fmtoneptr;
+  (void)fmtoneptr;
   FMDRIVER_DEBUG(" 000 %03d %03d\n",
                  fmp->data[fmtoneptr+0x18]&0x7,
                  (fmp->data[fmtoneptr+0x18]>>3)&0x7
@@ -3497,6 +3490,8 @@ void fmp_init(struct fmdriver_work *work, struct driver_fmp *fmp) {
   work->driver = fmp;
   work->driver_opna_interrupt = fmp_opna_interrupt;
   fmp_work_status_init(work, fmp);
+  fmdriver_fillpcmname(work->pcmname[0], fmp->pvi_name);
+  fmdriver_fillpcmname(work->pcmname[1], fmp->ppz_name);
 }
 
 // 4235
diff --git a/fmdriver/fmdriver_pmd.c b/fmdriver/fmdriver_pmd.c
index 2a093c2..09630bf 100644
--- a/fmdriver/fmdriver_pmd.c
+++ b/fmdriver/fmdriver_pmd.c
@@ -70,6 +70,7 @@ 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) {
@@ -126,7 +127,9 @@ static void pmd_stop_sound(struct fmdriver_work *work,
     }
   }
 }
+*/
 
+/*
 // 11df
 static void pmd_mstop(struct fmdriver_work *work,
                       struct driver_pmd *pmd) {
@@ -137,6 +140,7 @@ static void pmd_mstop(struct fmdriver_work *work,
   pmd->fadeout_vol = 0xff;
   pmd_stop_sound(work, pmd);
 }
+*/
 
 // 1058
 static void pmd_reset_state(struct driver_pmd *pmd) {
@@ -1310,8 +1314,6 @@ static void pmd_note_freq_adpcm(
     0, 0, 0, 0
   };
   uint8_t octave = note >> 4;
-  int cl = 5 - octave;
-  if (cl < 0) cl = 0;
   uint16_t tonefreq = adpcm_tonetable[note & 0x0f];
   if (octave <= 5) {
     tonefreq >>= (5 - octave);
@@ -1327,6 +1329,50 @@ static void pmd_note_freq_adpcm(
   part->actual_freq = tonefreq;
 }
 
+// 0c97
+static void pmd_note_freq_ppz8(
+  struct pmd_part *part,
+  uint8_t note
+) {
+  if ((note & 0xf) == 0xf) {
+    // 0cd8
+    part->actual_note = 0xff;
+    if (!part->lfof.freq && !part->lfof_b.freq) {
+      part->actual_freq = 0;
+      part->actual_freq_upper = 0;
+    }
+    return;
+  }
+  part->actual_note = note;
+  static const uint16_t ppz8_tonetable[0x10] = {
+    // 0cf1
+    // documented in ppz8.doc
+    // different from round(0x8000*(2**(i/12)))
+    0x8000,
+    0x87a6,
+    0x8fb3,
+    0x9838,
+    0xa146,
+    0xaade,
+    0xb4ff,
+    0xbfcc,
+    0xcb34,
+    0xd747,
+    0xe418,
+    0xf1a5,
+    0, 0, 0, 0
+  };
+  int octave = (note >> 4) - 4;
+  uint32_t freq = ppz8_tonetable[note & 0x0f];
+  if (octave < 0) {
+    freq >>= -octave;
+  } else {
+    freq <<= octave;
+  }
+  part->actual_freq = freq;
+  part->actual_freq_upper = freq >> 16;
+}
+
 // 14a0
 static void pmd_part_calc_gate(
   struct driver_pmd *pmd,
@@ -1594,6 +1640,72 @@ static void pmd_adpcm_vol_out(
   work->opna_writereg(work, 0x10b, vol);
 }
 
+// 0b33
+static void pmd_ppz8_vol_out(
+  struct fmdriver_work *work,
+  struct driver_pmd *pmd,
+  struct pmd_part *part
+) {
+  uint8_t vol = part->volume_save;
+  if (!vol) vol = part->vol;
+  
+  // 0b3f
+  if (pmd->ppz8_voldown) {
+    uint8_t voldown = -pmd->ppz8_voldown;
+    vol = vol * voldown >> 8;
+  }
+  // 0b4c
+  if (pmd->fadeout_vol) {
+    uint8_t fadeout = -pmd->fadeout_vol;
+    vol = vol * fadeout >> 8;
+  }
+  // 0b59
+  if (vol) {
+    if (part->ssg_env_state_old == SSG_ENV_STATE_OLD_NEW) {
+      uint8_t envvol = part->ssg_env_vol;
+      if (!envvol) {
+        vol = 0;
+        // -> 0bd4
+      } else {
+        vol = (vol * (envvol+1)) >> 3;
+        if (vol & 1) {
+          vol >>= 1;
+          vol++;
+        } else {
+          vol >>= 1;
+        }
+      }
+    } else {
+      // 0b7d
+      int newvol = vol + (part->ssg_env_vol << 4);
+      if (newvol > 0xff) newvol = 0xff;
+      if (newvol < 0) newvol = 0;
+      vol = newvol;
+    }
+    // 0ba4
+    if (part->lfof.vol || part->lfof_b.vol) {
+      int32_t lfovol = 0;
+      if (part->lfof.vol) vol += part->lfo_diff;
+      if (part->lfof_b.vol) vol += part->lfo_diff_b;
+      lfovol += vol;
+      vol = lfovol;
+      if (lfovol < 0) {
+        vol = 0;
+      } else if (lfovol > 0xff) {
+        vol = 0xff;
+      }
+    }
+  }
+  // 0bd4
+  if (work->ppz8) {
+    if (vol) {
+      work->ppz8_functbl->channel_volume(work->ppz8, pmd->proc_ch, vol >> 4);
+    } else {
+      work->ppz8_functbl->channel_stop(work->ppz8, pmd->proc_ch);
+    }
+  }
+}
+
 // 2985
 static void pmd_ssg_freq_out(
   struct fmdriver_work *work,
@@ -1675,6 +1787,33 @@ static void pmd_adpcm_freq_out(
   work->opna_writereg(work, 0x10a, freq >> 8);
 }
 
+// 0c27
+static void pmd_ppz8_freq_out(
+  struct fmdriver_work *work,
+  struct driver_pmd *pmd,
+  struct pmd_part *part
+) {
+  uint32_t freq = part->actual_freq | (((uint32_t)part->actual_freq_upper) << 16);
+  if (!freq) return;
+  if (part->portamento_diff) {
+    freq += part->portamento_diff * 4;
+  }
+  int32_t det = 0;
+  if (part->lfof.freq || part->lfof_b.freq) {
+    if (part->lfof.freq) det += part->lfo_diff;
+    if (part->lfof_b.freq) det += part->lfo_diff_b;
+  }
+  // 0c6a
+  det += part->detune;
+  det *= freq >> 8;
+  int64_t outfreq = freq + det;
+  if (outfreq < 0) outfreq = 0;
+  if (outfreq > INT32_MAX) outfreq = INT32_MAX;
+  if (work->ppz8) {
+    work->ppz8_functbl->channel_freq(work->ppz8, pmd->proc_ch, outfreq);
+  }
+}
+
 // 2c9f
 static uint16_t pmd_part_ssg_readout(
   struct fmdriver_work *work,
@@ -1756,6 +1895,18 @@ static void pmd_part_adpcm_keyon(
   }
 }
 
+// 0bf6
+static void pmd_part_ppz8_keyon(
+  struct fmdriver_work *work,
+  struct driver_pmd *pmd,
+  struct pmd_part *part
+) {
+  if (part->actual_note == 0xff) return;
+  if (work->ppz8) {
+    work->ppz8_functbl->channel_play(work->ppz8, pmd->proc_ch, part->tonenum);
+  }
+}
+
 // 2942
 static uint32_t pmd_blkfnum_normalize(
   uint16_t blk,
@@ -1932,6 +2083,37 @@ static void pmd_part_adpcm_out(
   pmd_part_loop_check(pmd, part);
 }
 
+// 0803
+static void pmd_part_ppz8_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_ppz8_vol_out(work, pmd, part);
+  pmd_ppz8_freq_out(work, pmd, part);
+  if (part->keystatus.off) {
+    pmd_part_ppz8_keyon(work, pmd, part);
+  }
+  // 082d
+  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);
+}
+
 // none
 static bool pmd_part_masked(
   const struct pmd_part *part
@@ -2052,11 +2234,6 @@ static void pmd_cmdff_tonenum_adpcm(
   pmd->adpcm_release = 0x8000;
 }
 
-// 0a3c
-static void pmd() {
-  // 0790
-}
-
 // 0afd
 static void pmd_cmdff_tonenum_ppz8(
   struct fmdriver_work *work,
@@ -2065,6 +2242,10 @@ static void pmd_cmdff_tonenum_ppz8(
 ){
   part->tonenum = pmd_part_cmdload(pmd, part);
   // 0a3c
+  if (work->ppz8) {
+    work->ppz8_functbl->channel_loop_voice(work->ppz8, pmd->proc_ch, part->tonenum);
+  }
+  // set origfreq
 }
 
 // 22b3
@@ -2340,7 +2521,7 @@ static void pmd_cmdf1_ppsdrv(
   struct driver_pmd *pmd,
   struct pmd_part *part
 ){
-  uint8_t data = pmd_part_cmdload(pmd, part);
+  /* uint8_t data = */pmd_part_cmdload(pmd, part);
   // TODO: PPSDRV
 }
 
@@ -2447,6 +2628,22 @@ static void pmd_cmdec_pan_adpcm(
   part->pan = pmd_part_cmdload(pmd, part) << 6;
 }
 
+// 0aca
+static void pmd_cmdec_pan_ppz8(
+  struct fmdriver_work *work,
+  struct driver_pmd *pmd,
+  struct pmd_part *part
+) {
+  uint8_t pan = pmd_part_cmdload(pmd, part);
+  // 0ced
+  static const uint8_t pantable[4] = {0, 9, 1, 5};
+  part->pan = 0;
+  if (pan < 4) part->pan = pantable[pan];
+  if (work->ppz8) {
+    work->ppz8_functbl->channel_pan(work->ppz8, pmd->proc_ch, part->pan);
+  }
+}
+
 // 265d
 static void pmd_opnarhythm_inc(uint8_t *incdata, uint8_t val) {
   for (int i = 0; i < 6; i++) {
@@ -2838,7 +3035,7 @@ static void pmd_cmdda_portamento_adpcm(
   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);
+  pmd_note_freq_adpcm(part, note);
   uint16_t f2 = part->actual_freq;
   part->actual_freq = f1;
   part->actual_note = n1;
@@ -2858,6 +3055,42 @@ static void pmd_cmdda_portamento_adpcm(
   pmd_part_adpcm_out(work, pmd, part);
 }
 
+// 0a62
+static void pmd_cmdda_portamento_ppz8(
+  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_ppz8(part, note);
+  uint32_t f1 = (((uint32_t)part->actual_freq_upper) << 16) | 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_ppz8(part, note);
+  uint32_t f2 = (((uint32_t)part->actual_freq_upper) << 16) | part->actual_freq;
+  part->actual_freq = f1;
+  part->actual_freq_upper = f1 >> 16;
+  part->actual_note = n1;
+  int16_t freqdiff = u16s16((f2 - f1)>>4);
+  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_ppz8_out(work, pmd, part);
+}
+
 // 20bf
 static void pmd_cmdd6_md(
   struct fmdriver_work *work,
@@ -2990,10 +3223,10 @@ static void pmd_cmdce_adpcm_loop(
   struct fmdriver_work *work,
   struct driver_pmd *pmd,
   struct pmd_part *part
-){
+) {
   uint16_t diff = pmd_part_cmdload(pmd, part);
   diff |= (uint16_t)pmd_part_cmdload(pmd, part) << 8;
-  if (!(diff & 0x80)) diff += pmd->adpcm_start;
+  if (!(diff & 0x8000)) diff += pmd->adpcm_start;
   else diff += pmd->adpcm_stop;
   pmd->adpcm_start_loop = diff;
   diff = pmd_part_cmdload(pmd, part);
@@ -3010,6 +3243,26 @@ static void pmd_cmdce_adpcm_loop(
   pmd->adpcm_release = diff;
 }
 
+// 0a04
+static void pmd_cmdce_ppz8_loop(
+  struct fmdriver_work *work,
+  struct driver_pmd *pmd,
+  struct pmd_part *part
+) {
+  uint32_t startoff = pmd_part_cmdload(pmd, part);
+  startoff |= (uint16_t)pmd_part_cmdload(pmd, part) << 8;
+  uint32_t endoff = pmd_part_cmdload(pmd, part);
+  endoff |= (uint16_t)pmd_part_cmdload(pmd, part) << 8;
+  if (work->ppz8) {
+    uint32_t len = work->ppz8_functbl->voice_length(work->ppz8, part->tonenum);
+    if (startoff & 0x8000) startoff = len + u16s16(startoff);
+    if (endoff & 0x8000) endoff = len + u16s16(endoff);
+    work->ppz8_functbl->channel_loopoffset(work->ppz8, pmd->proc_ch, startoff, endoff);
+  }
+  pmd_part_cmdload(pmd, part);
+  pmd_part_cmdload(pmd, part);
+}
+
 // 1e1b
 static void pmd_cmdcd_env_new(
   struct fmdriver_work *work,
@@ -3215,6 +3468,27 @@ static void pmd_cmdc3_pan_ex_adpcm(
   part->pan = pan;
 }
 
+// 0ae5
+static void pmd_cmdc3_pan_ex_ppz8(
+  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);
+  if (!(data & 0x80)) {
+    if (data > 4) data = 4;
+  } else {
+    if (data < 0xfc) data = 0xfc;
+  }
+  data += 5;
+  uint8_t pan = data;
+  part->pan = pan;
+  if (work->ppz8) {
+    work->ppz8_functbl->channel_pan(work->ppz8, pmd->proc_ch, part->pan);
+  }
+}
+
 // 2509
 static void pmd_cmdc2_lfo_delay(
   struct fmdriver_work *work,
@@ -3510,7 +3784,7 @@ static void pmd_cmdc0_mml_mask_adpcm(
   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);
@@ -3532,6 +3806,36 @@ static void pmd_cmdc0_mml_mask_adpcm(
   }
 }
 
+// 09d8
+static void pmd_cmdc0_mml_mask_ppz8(
+  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) {
+    // 09e4
+    part->mask.mml = false;
+    bool masked = pmd_part_masked(part);
+    part->mask.mml = true;
+    if (!masked) {
+      if (work->ppz8) {
+        work->ppz8_functbl->channel_stop(work->ppz8, pmd->proc_ch);
+      }
+    }
+    part->proc_masked = true;
+    // -> 08ca
+  } else {
+    // 09fa
+    part->mask.mml = false;
+    part->proc_masked = pmd_part_masked(part);
+    // -> 07cd
+  }
+}
+
 // 1caa
 static void pmd_cmdc0_mml_mask_rhythm(
   struct fmdriver_work *work,
@@ -4099,8 +4403,8 @@ static const pmd_cmd_func pmd_cmd_table_adpcm[PMD_CMD_CNT] = {
   pmd_cmd_null_1,
   pmd_cmde3_vol_add_adpcm,
   pmd_cmde2_vol_sub,
-  pmd_cmde1_ams_pms,
-  pmd_cmde0_hlfo,
+  pmd_cmd_null_1,
+  pmd_cmd_null_1,
   pmd_cmddf_meas_len,
   pmd_cmdde_echo_init_add_adpcm,
   pmd_cmddd_echo_init_sub,
@@ -4151,7 +4455,85 @@ static const pmd_cmd_func pmd_cmd_table_adpcm[PMD_CMD_CNT] = {
 };
 
 static const pmd_cmd_func pmd_cmd_table_ppz8[PMD_CMD_CNT] = {
-  
+  pmd_cmdff_tonenum_ppz8,
+  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_adpcm,
+  pmd_cmdf3_voldec_adpcm,
+  pmd_cmdf2_lfo,
+  pmd_cmdf1_lfo_switch,
+  pmd_cmdf0_env_old,
+  pmd_cmdef_poke,
+  pmd_cmd_null_1,
+  pmd_cmd_null_1,
+  pmd_cmdec_pan_ppz8,
+  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_adpcm,
+  pmd_cmde2_vol_sub,
+  pmd_cmd_null_1,
+  pmd_cmd_null_1,
+  pmd_cmddf_meas_len,
+  pmd_cmdde_echo_init_add_adpcm,
+  pmd_cmddd_echo_init_sub,
+  pmd_cmddc_status1,
+  pmd_cmddb_status1_add,
+  pmd_cmdda_portamento_ppz8,
+  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_cmd_null_1,
+  pmd_cmdce_ppz8_loop,
+  pmd_cmdcd_env_new,
+  pmd_cmd_null_1,
+  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_cmdc3_pan_ex_ppz8,
+  pmd_cmdc2_lfo_delay,
+  pmd_cmd_null_0,
+  pmd_cmdc0_mml_mask_ppz8,
+  pmd_cmdbf_lfo2,
+  pmd_cmdbe_lfo2_switch,
+  pmd_cmdbd_lfo2_md,
+  pmd_cmdbc_lfo2_waveform,
+  pmd_cmdbb_lfo2_ext,
+  pmd_cmdba_lfo2_slotmask, // slotmask on PPZ8??
+  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
 };
 
 // 1857
@@ -4210,6 +4592,20 @@ static void pmd_part_cmd_adpcm(
   pmd_cmd_table_adpcm[cmd^0xff](work, pmd, part);
 }
 
+// 02c6
+static void pmd_part_cmd_ppz8(
+  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_ppz8[cmd^0xff](work, pmd, part);
+}
+
 // 20e8
 static void pmd_portamento_tick(
   struct pmd_part *part
@@ -4375,6 +4771,49 @@ static void pmd_part_proc_adpcm_lfoenv(
   pmd_part_loop_check(pmd, part);
 }
 
+// 084c
+static void pmd_part_proc_ppz8_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) {
+    // 085a
+    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_b.freq;
+        pmd->lfoprocf_b.vol = part->lfof_b.vol;
+      }
+      pmd_part_lfo_flip(part);
+    }
+    // 088e
+    if (pmd->lfoprocf.freq || pmd->lfoprocf.portamento || pmd->lfoprocf_b.freq) {
+      if (pmd->lfoprocf.portamento) {
+        pmd_portamento_tick(part);
+      }
+      pmd_ppz8_freq_out(work, pmd, part);
+    }
+  }
+  // 08a2
+  if (pmd_ssg_env_proc(pmd, part) || pmd->lfoprocf.vol || pmd->lfoprocf_b.vol || pmd->fadeout_speed) {
+    pmd_ppz8_vol_out(work, pmd, part);
+  }
+  pmd_part_loop_check(pmd, part);
+}
+
 // 16d9
 static bool pmd_part_ssg_next_masked(
   struct fmdriver_work *work,
@@ -4938,36 +5377,70 @@ static void pmd_part_proc_ppz8(
   struct driver_pmd *pmd,
   struct pmd_part *part
 ) {
-  /*
   if (!part->ptr) return;
   part->proc_masked = pmd_part_masked(part);
   part->len_cnt--;
-  if (!part->keystatus.off && !part->keystatus.off_mask) {
+  if (part->proc_masked) {
+    part->keystatus.off = true;
+    part->keystatus.off_mask = true;
+  } else if (!part->keystatus.off && !part->keystatus.off_mask) {
     // 07b6
     if (part->len_cnt <= part->gate) {
       part->keystatus.off = true;
       part->keystatus.off_mask = true;
-      pmd_part_off_ppz8(work, pmd, part);
+      pmd_part_off_ppz8(part);
     }
   }
   // 07c2
   if (part->len_cnt) {
-    // 084c
+    if (part->proc_masked) {
+      pmd_part_loop_check(pmd, part);
+    } else {
+      pmd_part_proc_ppz8_lfoenv(work, pmd, part);
+    }
+    return;
   }
   part->lfof.portamento = false;
   for (;;) {
-    // 07cd
+    // 07cd / 08ca
     uint8_t cmd = pmd_part_cmdload(pmd, part);
     if (cmd & 0x80) {
       if (cmd != 0x80) {
-        pmd_part_cmd_adpcm(work, pmd, part, cmd);
+        pmd_part_cmd_ppz8(work, pmd, part, cmd);
         if (cmd == 0xda && !pmd_part_masked(part)) return;
       } else {
-        // 0187
+        // 07d9 / 08d6
+        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_loop_check_masked(pmd, part);
+          } else {
+            pmd_part_proc_ppz8_lfoenv(work, pmd, part);
+          }
+          return;
+        }
+        part->ptr = part->loop_ptr;
+        part->loop.ended = false;
+      }
+    } else {
+      // 07f3 / 08f3
+      if (part->proc_masked) {
+        part->actual_freq_upper = 0;
+        pmd_part_proc_note_masked(work, pmd, part);
+      } else {
+        pmd_part_lfo_init_ssg(work, pmd, part, cmd);
+        cmd = pmd_part_note_transpose(part, cmd);
+        pmd_note_freq_ppz8(part, cmd);
+        part->len = part->len_cnt = pmd_part_cmdload(pmd, part);
+        pmd_part_calc_gate(pmd, part);
+        pmd_part_ppz8_out(work, pmd, part);
       }
+      return;
     }
   }
-  */
 }
 
 // 11fd
@@ -5119,6 +5592,7 @@ enum pmd_part_type {
   PART_TYPE_FM,
   PART_TYPE_SSG,
   PART_TYPE_ADPCM,
+  PART_TYPE_PPZ8
 };
 
 static const struct {
@@ -5139,6 +5613,14 @@ static const struct {
   {PMD_PART_SSG_2, PART_TYPE_SSG,   false},
   {PMD_PART_SSG_3, PART_TYPE_SSG,   false},
   {PMD_PART_ADPCM, PART_TYPE_ADPCM, false},
+  {PMD_PART_PPZ_1, PART_TYPE_PPZ8,   false},
+  {PMD_PART_PPZ_2, PART_TYPE_PPZ8,   false},
+  {PMD_PART_PPZ_3, PART_TYPE_PPZ8,   false},
+  {PMD_PART_PPZ_4, PART_TYPE_PPZ8,   false},
+  {PMD_PART_PPZ_5, PART_TYPE_PPZ8,   false},
+  {PMD_PART_PPZ_6, PART_TYPE_PPZ8,   false},
+  {PMD_PART_PPZ_7, PART_TYPE_PPZ8,   false},
+  {PMD_PART_PPZ_8, PART_TYPE_PPZ8,   false},
 };
 
 static void pmd_work_status_update(
@@ -5200,6 +5682,8 @@ static void pmd_work_status_update(
     case PART_TYPE_ADPCM:
       //track->playing = false;
       break;
+    case PART_TYPE_PPZ8:
+      break;
     }
   }
   work->ssg_noise_freq = pmd->ssg_noise_freq_wrote;
@@ -5221,6 +5705,7 @@ static void pmd_opna_interrupt(struct fmdriver_work *work) {
   pmd_work_status_update(work, pmd);
 }
 
+/*
 // 0f4d
 static void pmd_mstart(
   struct fmdriver_work *work,
@@ -5242,6 +5727,7 @@ static void pmd_mstart(
   // TODO
   // 4375??
 }
+*/
 
 bool pmd_load(struct driver_pmd *pmd,
               uint8_t *data, uint16_t datalen) {
@@ -5353,6 +5839,8 @@ void pmd_init(struct fmdriver_work *work,
   if (pcmfile) {
     pmd_filenamecopy(pmd->ppcfile, pcmfile);
   }
+  fmdriver_fillpcmname(work->pcmname[0], pmd->ppcfile);
+  fmdriver_fillpcmname(work->pcmname[1], pmd->ppzfile);
 }
 
 enum {
@@ -5396,4 +5884,5 @@ bool pmd_ppc_load(
 
   work->opna_writereg(work, 0x110, 0x0c);
   work->opna_writereg(work, 0x100, 0x01);
+  return true;
 }
diff --git a/fmdriver/fmdriver_pmd.h b/fmdriver/fmdriver_pmd.h
index ba03cda..708dd9e 100644
--- a/fmdriver/fmdriver_pmd.h
+++ b/fmdriver/fmdriver_pmd.h
@@ -6,6 +6,7 @@ extern "C" {
 #endif
 
 #include "fmdriver.h"
+#include <stddef.h>
 enum {
   PMD_FILENAMELEN = 8+1+3
 };
@@ -222,6 +223,9 @@ struct pmd_part {
   uint8_t note_proc;
   // 005b
   uint8_t gate_min;
+  // 005c
+  // for ppz8
+  uint16_t actual_freq_upper;
   // 005e
   uint8_t curr_note;
   // 005f
@@ -460,7 +464,7 @@ struct driver_pmd {
 
 bool pmd_load(struct driver_pmd *pmd, uint8_t *data, uint16_t datalen);
 void pmd_init(struct fmdriver_work *work, struct driver_pmd *pmd);
-
+bool pmd_ppc_load(struct fmdriver_work *work, uint8_t *data, size_t datalen);
 #ifdef __cplusplus
 }
 #endif
diff --git a/fmdriver/ppz8.c b/fmdriver/ppz8.c
index a373d84..7540ca0 100644
--- a/fmdriver/ppz8.c
+++ b/fmdriver/ppz8.c
@@ -1,6 +1,7 @@
 #include "ppz8.h"
 #include "fmdriver_common.h"
 //#include <stdio.h>
+#include <string.h>
 
 void ppz8_init(struct ppz8 *ppz8, uint16_t srate, uint16_t mix_volume) {
   for (int i = 0; i < 2; i++) {
@@ -69,7 +70,6 @@ static int32_t ppz8_channel_calc(struct ppz8 *ppz8, struct ppz8_channel *channel
     out += (int32_t)channel->prevout[0] * (0x10000u - coeff);
     out += (int32_t)channel->prevout[1] * coeff;
     out >>= 16;
-
     // volume: out * 2**((volume-15)/2)
     out >>= (7 - ((channel->vol&0xf)>>1));
     if (!(channel->vol&1)) {
@@ -83,7 +83,7 @@ static int32_t ppz8_channel_calc(struct ppz8 *ppz8, struct ppz8_channel *channel
   channel->ptr += ptrdiff;
   uint32_t bufdiff = (channel->ptr>>16) - (oldptr>>16);
   if (bufdiff) {
-    if (/*bufdiff == 1*/0) {
+    if (bufdiff == 1) {
       channel->prevout[0] = channel->prevout[1];
       channel->prevout[1] = 0;
       channel->ptr = ppz8_loop(channel, channel->ptr);
@@ -219,6 +219,33 @@ bool ppz8_pvi_load(struct ppz8 *ppz8, uint8_t bnum,
   return true;
 }
 
+bool ppz8_pzi_load(struct ppz8 *ppz8, uint8_t bnum,
+                   const uint8_t *pzidata, uint32_t pzidatalen,
+                   int16_t *decodebuf) {
+  if (bnum >= 2) return false;
+  if (pzidatalen < (0x20+(18*128))) return false;
+  if (memcmp(pzidata, "PZI0", 4) && memcmp(pzidata, "PZI1", 4)) return false;
+  struct ppz8_pcmbuf *buf = &ppz8->buf[bnum];
+  for (int i = 0; i < 0x80; i++) {
+    struct ppz8_pcmvoice *voice = &buf->voice[i];
+    voice->start = read32le(&pzidata[0x20+18*i+0]) * 2;
+    voice->len = read32le(&pzidata[0x20+18*i+4]) * 2;
+    voice->loopstart = read32le(&pzidata[0x20+18*i+8]);
+    voice->loopend = read32le(&pzidata[0x20+18*i+12]);
+    voice->origfreq = read16le(&pzidata[0x20+18*i+16]);/*
+    if (voice->origfreq) {
+      voice->origfreq = 0x100000000 / voice->origfreq;
+    }*/
+    //voice->origfreq = 15974;
+  }
+  buf->data = decodebuf;
+  buf->buflen = pzidatalen - (0x20+(18*128));
+  for (uint32_t i = 0; i < buf->buflen; i++) {
+    buf->data[i] = (pzidata[0x20+18*128+i] - 0x80) << 8;
+  }
+  return true;
+}
+
 static void ppz8_channel_play(struct ppz8 *ppz8, uint8_t ch, uint8_t v) {
   if (ch >= 8) return;
   struct ppz8_channel *channel = &ppz8->channel[ch];
@@ -285,6 +312,21 @@ static void ppz8_total_volume(struct ppz8 *ppz8, uint8_t vol) {
   ppz8->totalvol = vol;
 }
 
+static void ppz8_channel_loop_voice(struct ppz8 *ppz8, uint8_t ch, uint8_t v) {
+  if (ch >= 8) return;
+  struct ppz8_channel *channel = &ppz8->channel[ch];
+  struct ppz8_pcmbuf *buf = &ppz8->buf[v>>7];
+  struct ppz8_pcmvoice *voice = &buf->voice[v & 0x7f];
+  channel->loopstartptr = ((uint64_t)(voice->loopstart)>>1)<<16;
+  channel->loopendptr = ((uint64_t)(voice->loopend)>>1)<<16;
+}
+
+static uint32_t ppz8_voice_length(struct ppz8 *ppz8, uint8_t v) {
+  struct ppz8_pcmbuf *buf = &ppz8->buf[v>>7];
+  struct ppz8_pcmvoice *voice = &buf->voice[v & 0x7f];
+  return voice->len;
+}
+
 const struct ppz8_functbl ppz8_functbl = {
   ppz8_channel_play,
   ppz8_channel_stop,
@@ -292,5 +334,7 @@ const struct ppz8_functbl ppz8_functbl = {
   ppz8_channel_freq,
   ppz8_channel_loopoffset,
   ppz8_channel_pan,
-  ppz8_total_volume
+  ppz8_total_volume,
+  ppz8_channel_loop_voice,
+  ppz8_voice_length
 };
diff --git a/fmdriver/ppz8.h b/fmdriver/ppz8.h
index 6ced669..cb9a158 100644
--- a/fmdriver/ppz8.h
+++ b/fmdriver/ppz8.h
@@ -50,11 +50,18 @@ void ppz8_mix(struct ppz8 *ppz8, int16_t *buf, unsigned samples);
 bool ppz8_pvi_load(struct ppz8 *ppz8, uint8_t buf,
                    const uint8_t *pvidata, uint32_t pvidatalen,
                    int16_t *decodebuf);
+bool ppz8_pzi_load(struct ppz8 *ppz8, uint8_t bnum,
+                   const uint8_t *pzidata, uint32_t pzidatalen,
+                   int16_t *decodebuf);
 
 static inline uint32_t ppz8_pvi_decodebuf_samples(uint32_t pvidatalen) {
   if (pvidatalen < 0x210) return 0;
   return (pvidatalen - 0x210) * 2;
 }
+static inline uint32_t ppz8_pzi_decodebuf_samples(uint32_t pzidatalen) {
+  if (pzidatalen < 0x920) return 0;
+  return (pzidatalen - 0x920) * 2;
+}
 
 struct ppz8_functbl {
   void (*channel_play)(struct ppz8 *ppz8, uint8_t channel, uint8_t voice);
@@ -65,6 +72,8 @@ struct ppz8_functbl {
                              uint32_t startoff, uint32_t endoff);
   void (*channel_pan)(struct ppz8 *ppz8, uint8_t channel, uint8_t pan);
   void (*total_volume)(struct ppz8 *ppz8, uint8_t vol);
+  void (*channel_loop_voice)(struct ppz8 *ppz8, uint8_t channel, uint8_t voice);
+  uint32_t (*voice_length)(struct ppz8 *ppz8, uint8_t voice);
 };
 
 extern const struct ppz8_functbl ppz8_functbl;
-- 
cgit v1.2.3