瀏覽代碼

audio: added SSG-EG to YM2612, plus some timing changes for SN76496+YM2612

kub 4 年之前
父節點
當前提交
8ac9ab7fcb
共有 13 個文件被更改,包括 572 次插入456 次删除
  1. 1 1
      Makefile
  2. 1 1
      cpu/drc/emit_arm64.c
  3. 4 2
      pico/memory.c
  4. 1 1
      pico/pico.h
  5. 6 23
      pico/pico_cmn.c
  6. 5 2
      pico/pico_int.h
  7. 2 6
      pico/sms.c
  8. 3 4
      pico/sound/mix.c
  9. 2 4
      pico/sound/mix_arm.S
  10. 62 88
      pico/sound/sound.c
  11. 250 117
      pico/sound/ym2612.c
  12. 14 8
      pico/sound/ym2612.h
  13. 221 199
      pico/sound/ym2612_arm.S

+ 1 - 1
Makefile

@@ -36,7 +36,7 @@ endif
 
 ifeq ("$(PLATFORM)",$(filter "$(PLATFORM)","gp2x" "opendingux" "rpi1"))
 # very small caches, avoid optimization options making the binary much bigger
-CFLAGS += -finline-limit=43 -fno-unroll-loops -fno-ipa-cp -ffast-math
+CFLAGS += -finline-limit=42 -fno-unroll-loops -fno-ipa-cp -ffast-math
 # this gets you about 20% better execution speed on 32bit arm/mips
 CFLAGS += -fno-common -fno-stack-protector -fno-guess-branch-probability -fno-caller-saves -fno-tree-loop-if-convert -fno-regmove
 endif

+ 1 - 1
cpu/drc/emit_arm64.c

@@ -1393,7 +1393,7 @@ static void emith_sync_t(int sr)
 	else if (tcond >= 0) {
 		int tmp = rcache_get_tmp();
 		EMIT(A64_CSET(tcond, tmp));
-		EMIT(A64_BFI_IMM(sr, tmp, 0, 1)); // assumes SR.T = bit 0
+		EMIT(A64_BFI_IMM(sr, tmp, __builtin_ffs(T)-1, 1));
 		rcache_free_tmp(tmp);
 	}
 	tcond = -1;

+ 4 - 2
pico/memory.c

@@ -546,7 +546,7 @@ static void PicoWrite8_z80(u32 a, u32 d)
   }
   if ((a & 0x6000) == 0x4000) { // FM Sound
     if (PicoIn.opt & POPT_EN_FM)
-      Pico.m.status |= ym2612_write_local(a & 3, d & 0xff, 0) & 1;
+      ym2612_write_local(a & 3, d & 0xff, 0);
     return;
   }
   // TODO: probably other VDP access too? Maybe more mirrors?
@@ -1059,6 +1059,8 @@ static int ym2612_write_local(u32 a, u32 d, int is_from_z80)
       break;
   }
 
+  int scanline = get_scanline(is_from_z80);
+  PsndDoFM(scanline);
 #ifdef __GP2X__
   if (PicoIn.opt & POPT_EXT_FM)
     return YM2612Write_940(a, d, get_scanline(is_from_z80));
@@ -1224,7 +1226,7 @@ static unsigned char z80_md_bank_read(unsigned short a)
 static void z80_md_ym2612_write(unsigned int a, unsigned char data)
 {
   if (PicoIn.opt & POPT_EN_FM)
-    Pico.m.status |= ym2612_write_local(a, data, 1) & 1;
+    ym2612_write_local(a, data, 1);
 }
 
 static void z80_md_vdp_br_write(unsigned int a, unsigned char data)

+ 1 - 1
pico/pico.h

@@ -70,7 +70,7 @@ extern void *p32x_bios_g, *p32x_bios_m, *p32x_bios_s;
 #define POPT_EN_DRC         (1<<17)
 #define POPT_DIS_SPRITE_LIM (1<<18)
 #define POPT_DIS_IDLE_DET   (1<<19)
-#define POPT_EN_32X         (1<<20)
+#define POPT_EN_32X         (1<<20) // x0 0000
 #define POPT_EN_PWM         (1<<21)
 #define POPT_PWM_IRQ_OPT    (1<<22)
 

+ 6 - 23
pico/pico_cmn.c

@@ -88,7 +88,6 @@ static void do_timing_hacks_vb(void)
 static int PicoFrameHints(void)
 {
   struct PicoVideo *pv = &Pico.video;
-  int line_sample = Pico.m.pal ? 68 : 93;
   int vdp_slots = (Pico.video.reg[12] & 1) ? 18 : 16;
   int lines, y, lines_vis, skip;
   int vcnt_wrap, vcnt_adj;
@@ -150,23 +149,6 @@ static int PicoFrameHints(void)
       }
     }
 
-    // get samples from sound chips
-    if ((y == 224 || y == line_sample) && PicoIn.sndOut)
-    {
-      cycles = SekCyclesDone();
-
-      if (Pico.m.z80Run && !Pico.m.z80_reset && (PicoIn.opt&POPT_EN_Z80))
-        PicoSyncZ80(cycles);
-#ifdef PICO_CD
-      if (PicoIn.AHW & PAHW_MCD)
-        pcd_sync_s68k(cycles, 0);
-#endif
-#ifdef PICO_32X
-      p32x_sync_sh2s(cycles);
-#endif
-      PsndGetSamples(y);
-    }
-
     // Run scanline:
     Pico.t.m68c_line_start = Pico.t.m68c_aim;
     do_timing_hacks_as(pv, vdp_slots);
@@ -238,10 +220,6 @@ static int PicoFrameHints(void)
   p32x_start_blank();
 #endif
 
-  // get samples from sound chips
-  if (y == 224 && PicoIn.sndOut)
-    PsndGetSamples(y);
-
   // Run scanline:
   CPUS_RUN(CYCLES_M68K_LINE - CYCLES_M68K_VINT_LAG);
 
@@ -298,7 +276,7 @@ static int PicoFrameHints(void)
   pv->status |= ((pv->reg[1] >> 3) ^ SR_VB) & SR_VB; // forced blanking
 
   // last scanline
-  Pico.m.scanline = y;
+  Pico.m.scanline = y++;
   pv->v_counter = 0xff;
   pv->lwrite_cnt = 0;
 
@@ -337,6 +315,11 @@ static int PicoFrameHints(void)
 #ifdef PICO_32X
   p32x_sync_sh2s(cycles);
 #endif
+
+  // get samples from sound chips
+  if (PicoIn.sndOut)
+    PsndGetSamples(y);
+
   timers_cycle();
 
   pv->hint_cnt = hint;

+ 5 - 2
pico/pico_int.h

@@ -336,7 +336,7 @@ struct PicoMisc
   unsigned char  eeprom_cycle; // EEPROM cycle number
   unsigned char  eeprom_slave; // EEPROM slave word for X24C02 and better SRAMs
   unsigned char  eeprom_status;
-  unsigned char  status;       // rapid_ym2612, multi_ym_updates
+  unsigned char  pad1;         // was ym2612 status
   unsigned short dma_xfers;    // 18
   unsigned char  eeprom_wb[2]; // EEPROM latch/write buffer
   unsigned int  frame_count;   // 1c for movies and idle det
@@ -433,6 +433,8 @@ struct PicoSound
   int len_e_cnt;
   short dac_line;
   short psg_line;
+  unsigned int fm_mult;                 // samples per line in Q16
+  unsigned int fm_pos;                  // last FM position in Q16
 };
 
 // run tools/mkoffsets pico/pico_int_offs.h if you change these
@@ -872,9 +874,10 @@ PICO_INTERNAL void PsndReset(void);
 PICO_INTERNAL void PsndStartFrame(void);
 PICO_INTERNAL void PsndDoDAC(int line_to);
 PICO_INTERNAL void PsndDoPSG(int line_to);
+PICO_INTERNAL void PsndDoFM(int line_to);
 PICO_INTERNAL void PsndClear(void);
 PICO_INTERNAL void PsndGetSamples(int y);
-PICO_INTERNAL void PsndGetSamplesMS(void);
+PICO_INTERNAL void PsndGetSamplesMS(int y);
 
 // sms.c
 #ifndef NO_SMS

+ 2 - 6
pico/sms.c

@@ -320,16 +320,12 @@ void PicoFrameMS(void)
       }
     }
 
-    // 224 because of how it's done for MD...
-    if (y == 224 && PicoIn.sndOut)
-      PsndGetSamplesMS();
-
     cycles_aim += cycles_line;
     cycles_done += z80_run((cycles_aim - cycles_done) >> 8) << 8;
   }
 
-  if (PicoIn.sndOut && Pico.snd.psg_line < lines)
-    PsndDoPSG(lines - 1);
+  if (PicoIn.sndOut)
+    PsndGetSamplesMS(lines);
 }
 
 void PicoFrameDrawOnlyMS(void)

+ 3 - 4
pico/sound/mix.c

@@ -12,16 +12,15 @@
 #define MINOUT		(-32768)
 
 /* limitter */
-#define Limit16(val) { \
-	val -= (val >> 2); \
-	if ((short)val != val) val = (val < 0 ? MINOUT : MAXOUT); \
-}
+#define Limit16(val) \
+	if ((short)val != val) val = (val < 0 ? MINOUT : MAXOUT)
 
 int mix_32_to_16l_level;
 
 static struct iir2 { // 2-pole IIR
 	int	x[2];		// sample buffer
 	int	y[2];		// filter intermediates
+	int	i;
 } lfi2, rfi2;
 
 // NB ">>" rounds to -infinity, "/" to 0. To compensate the effect possibly use

+ 2 - 4
pico/sound/mix_arm.S

@@ -400,6 +400,8 @@ m32_16l_st_l_no_unal2:
     ldmfd   sp!, {r4-r11,lr}
     bx      lr
 
+#endif /* __GP2X__ */
+
 .global mix_reset @ void
 mix_reset:
     ldr     r0, =filter
@@ -409,11 +411,7 @@ mix_reset:
     bx      lr
 
 .data
-    DCfilt  r4, r10
-    DCfilt  r5, r11
 filter:
     .ds     8
 
-#endif /* __GP2X__ */
-
 @ vim:filetype=armasm

+ 62 - 88
pico/sound/sound.c

@@ -32,52 +32,17 @@ extern int *sn76496_regs;
 static void dac_recalculate(void)
 {
   int lines = Pico.m.pal ? 313 : 262;
-  int mid = Pico.m.pal ? 68 : 93;
-  int i, dac_cnt, pos, len;
+  int i, pos;
 
-  if (Pico.snd.len <= lines)
-  {
-    // shrinking algo
-    dac_cnt = -Pico.snd.len;
-    len=1; pos=0;
-    dac_info[225] = 1;
-
-    for(i=226; i != 225; i++)
-    {
-      if (i >= lines) i = 0;
-      if(dac_cnt < 0) {
-        pos++;
-        dac_cnt += lines;
-      }
-      dac_cnt -= Pico.snd.len;
-      dac_info[i] = pos;
-    }
-  }
-  else
+  pos = 0; // Q16
+
+  for(i = 0; i <= lines; i++)
   {
-    // stretching
-    dac_cnt = Pico.snd.len;
-    pos=0;
-    for(i = 225; i != 224; i++)
-    {
-      if (i >= lines) i = 0;
-      len=0;
-      while(dac_cnt >= 0) {
-        dac_cnt -= lines;
-        len++;
-      }
-      if (i == mid) // midpoint
-        while(pos+len < Pico.snd.len/2) {
-          dac_cnt -= lines;
-          len++;
-        }
-      dac_cnt += Pico.snd.len;
-      pos += len;
-      dac_info[i] = pos;
-    }
+    dac_info[i] = ((pos+(1<<15)) >> 16); // round to nearest
+    pos += Pico.snd.fm_mult;
   }
-  for (i = lines; i < sizeof(dac_info) / sizeof(dac_info[0]); i++)
-    dac_info[i] = dac_info[0];
+  for (i = lines+1; i < sizeof(dac_info) / sizeof(dac_info[0]); i++)
+    dac_info[i] = dac_info[i-1];
 }
 
 
@@ -95,6 +60,7 @@ void PsndRerate(int preserve_state)
 {
   void *state = NULL;
   int target_fps = Pico.m.pal ? 50 : 60;
+  int target_lines = Pico.m.pal ? 313 : 262;
 
   if (preserve_state) {
     state = malloc(0x204);
@@ -121,6 +87,9 @@ void PsndRerate(int preserve_state)
   Pico.snd.len_e_add = ((PicoIn.sndRate - Pico.snd.len * target_fps) << 16) / target_fps;
   Pico.snd.len_e_cnt = 0;
 
+  // samples per line
+  Pico.snd.fm_mult = 65536.0 * PicoIn.sndRate / (target_fps*target_lines);
+
   // recalculate dac info
   dac_recalculate();
 
@@ -149,8 +118,7 @@ PICO_INTERNAL void PsndStartFrame(void)
   }
 
   Pico.snd.dac_line = Pico.snd.psg_line = 0;
-  Pico.m.status &= ~1;
-  dac_info[224] = Pico.snd.len_use;
+  Pico.snd.fm_pos = 0;
 }
 
 PICO_INTERNAL void PsndDoDAC(int line_to)
@@ -159,9 +127,6 @@ PICO_INTERNAL void PsndDoDAC(int line_to)
   int dout = ym2612.dacout;
   int line_from = Pico.snd.dac_line;
 
-  if (line_to >= 313)
-    line_to = 312;
-
   pos  = dac_info[line_from];
   pos1 = dac_info[line_to + 1];
   len = pos1 - pos;
@@ -188,14 +153,9 @@ PICO_INTERNAL void PsndDoPSG(int line_to)
   int pos, pos1, len;
   int stereo = 0;
 
-  if (line_to >= 313)
-    line_to = 312;
-
   pos  = dac_info[line_from];
   pos1 = dac_info[line_to + 1];
   len = pos1 - pos;
-  //elprintf(EL_STATUS, "%3d %3d %3d %3d %3d",
-  //  pos, pos1, len, line_from, line_to);
   if (len <= 0)
     return;
 
@@ -211,6 +171,34 @@ PICO_INTERNAL void PsndDoPSG(int line_to)
   SN76496Update(PicoIn.sndOut + pos, len, stereo);
 }
 
+PICO_INTERNAL void PsndDoFM(int line_to)
+{
+  int pos, len;
+  int stereo = 0;
+
+  // Q16, number of samples to fill in buffer
+  len = ((line_to-1) * Pico.snd.fm_mult) - Pico.snd.fm_pos;
+
+  // don't do this too often (no more than 256 per sec)
+  if (len >> 16 <= PicoIn.sndRate >> 9)
+    return;
+
+  // update position and calculate buffer offset and length
+  pos = Pico.snd.fm_pos >> 16;
+  Pico.snd.fm_pos += len;
+  len = (Pico.snd.fm_pos >> 16) - pos;
+
+  // fill buffer
+  if (PicoIn.opt & POPT_EN_STEREO) {
+    stereo = 1;
+    pos <<= 1;
+  }
+  if (PicoIn.opt & POPT_EN_FM)
+    YM2612UpdateOne(PsndBuffer + pos, len, stereo, 1);
+  else
+    memset32(PsndBuffer + pos, 0, len<<stereo);
+}
+
 // cdda
 static void cdda_raw_update(int *buffer, int length)
 {
@@ -275,11 +263,12 @@ PICO_INTERNAL void PsndClear(void)
 
 static int PsndRender(int offset, int length)
 {
-  int  buf32_updated = 0;
-  int *buf32 = PsndBuffer+offset;
+  int *buf32;
   int stereo = (PicoIn.opt & 8) >> 3;
+  int fmlen = (Pico.snd.fm_pos >> 16) - offset;
 
   offset <<= stereo;
+  buf32 = PsndBuffer+offset;
 
   pprof_start(sound);
 
@@ -288,14 +277,15 @@ static int PsndRender(int offset, int length)
     return length;
   }
 
-  // Add in the stereo FM buffer
-  if (PicoIn.opt & POPT_EN_FM) {
-    buf32_updated = YM2612UpdateOne(buf32, length, stereo, 1);
-  } else
-    memset32(buf32, 0, length<<stereo);
-
-//printf("active_chs: %02x\n", buf32_updated);
-  (void)buf32_updated;
+  // Add in parts of the FM buffer not yet done
+  if (length-fmlen > 0) {
+    int *fmbuf = buf32 + (fmlen << stereo);
+    if (PicoIn.opt & POPT_EN_FM)
+      YM2612UpdateOne(fmbuf, length-fmlen, stereo, 1);
+    else
+      memset32(fmbuf, 0, (length-fmlen)<<stereo);
+    Pico.snd.fm_pos += (length-fmlen)<<16;
+  }
 
   // CD: PCM sound
   if (PicoIn.AHW & PAHW_MCD) {
@@ -327,7 +317,6 @@ static int PsndRender(int offset, int length)
   return length;
 }
 
-// to be called on 224 or line_sample scanlines only
 PICO_INTERNAL void PsndGetSamples(int y)
 {
   static int curr_pos = 0;
@@ -336,33 +325,20 @@ PICO_INTERNAL void PsndGetSamples(int y)
     PsndDoDAC(y - 1);
   PsndDoPSG(y - 1);
 
-  if (y == 224)
-  {
-    if (Pico.m.status & 2)
-         curr_pos += PsndRender(curr_pos, Pico.snd.len-Pico.snd.len/2);
-    else curr_pos  = PsndRender(0, Pico.snd.len_use);
-    if (Pico.m.status & 1)
-         Pico.m.status |=  2;
-    else Pico.m.status &= ~2;
-    if (PicoIn.writeSound)
-      PicoIn.writeSound(curr_pos * ((PicoIn.opt & POPT_EN_STEREO) ? 4 : 2));
-    // clear sound buffer
-    PsndClear();
-    Pico.snd.dac_line = 224;
-    dac_info[224] = 0;
-  }
-  else if (Pico.m.status & 3) {
-    Pico.m.status |=  2;
-    Pico.m.status &= ~1;
-    curr_pos = PsndRender(0, Pico.snd.len/2);
-  }
+  curr_pos  = PsndRender(0, Pico.snd.len_use);
+
+  if (PicoIn.writeSound)
+    PicoIn.writeSound(curr_pos * ((PicoIn.opt & POPT_EN_STEREO) ? 4 : 2));
+  // clear sound buffer
+  PsndClear();
+  Pico.snd.dac_line = y;
 }
 
-PICO_INTERNAL void PsndGetSamplesMS(void)
+PICO_INTERNAL void PsndGetSamplesMS(int y)
 {
   int length = Pico.snd.len_use;
 
-  PsndDoPSG(223);
+  PsndDoPSG(y - 1);
 
   // upmix to "stereo" if needed
   if (PicoIn.opt & POPT_EN_STEREO) {
@@ -374,8 +350,6 @@ PICO_INTERNAL void PsndGetSamplesMS(void)
   if (PicoIn.writeSound != NULL)
     PicoIn.writeSound(length * ((PicoIn.opt & POPT_EN_STEREO) ? 4 : 2));
   PsndClear();
-
-  dac_info[224] = 0;
 }
 
 // vim:shiftwidth=2:ts=2:expandtab

+ 250 - 117
pico/sound/ym2612.c

@@ -7,6 +7,8 @@
 ** document it ("proprietary") and tells to write 0 to SSG-EG control register.
 **
 ** updated with fixes from mame 0.216 (file version 1.5.1) (kub)
+** SSG-EG readded from GenPlus (kub)
+** linear sample interpolation for chip to output rate adaption (kub)
 */
 
 /*
@@ -174,16 +176,6 @@ void memset32(int *dest, int c, int count);
 
 #define EG_TIMER_OVERFLOW (3*(1<<EG_SH)) /* envelope generator timer overflows every 3 samples (on real chip) */
 
-#define MAXOUT		(+32767)
-#define MINOUT		(-32768)
-
-/* limitter */
-#define Limit(val, max,min) { \
-	if ( val > max )      val = max; \
-	else if ( val < min ) val = min; \
-}
-
-
 /*  TL_TAB_LEN is calculated as:
 *   13 - sinus amplitude bits     (Y axis)
 *   2  - sinus sign bit           (Y axis)
@@ -289,8 +281,8 @@ O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18),
 O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18),
 
 /* rates 00-11 */
-O(18),O(18),O( 0),O( 0),
-O( 0),O( 0),O( 2),O( 2),
+O(18),O(18),O( 2),O( 3),
+O( 0),O( 1),O( 2),O( 3),
 O( 0),O( 1),O( 2),O( 3),
 O( 0),O( 1),O( 2),O( 3),
 O( 0),O( 1),O( 2),O( 3),
@@ -554,6 +546,13 @@ INLINE void set_timers( int v )
 		ym2612.OPN.ST.status &= ~1;
 }
 
+INLINE void recalc_volout(FM_SLOT *SLOT)
+{
+	INT16 vol_out = SLOT->volume;
+	if ((SLOT->ssg&0x0c) == 0x0c)
+		vol_out = (0x200 - SLOT->volume) & MAX_ATT_INDEX;
+	SLOT->vol_out = vol_out + SLOT->tl;
+}
 
 INLINE void FM_KEYON(int c , int s )
 {
@@ -562,13 +561,15 @@ INLINE void FM_KEYON(int c , int s )
 	{
 		SLOT->key = 1;
 		SLOT->phase = 0;		/* restart Phase Generator */
+		SLOT->ssg ^= SLOT->ssgn;
+		SLOT->ssgn = 0;
+		SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC;
 		if (SLOT->ar + SLOT->ksr < 32+62) {
-			SLOT->state = (SLOT->volume > MIN_ATT_INDEX) ? EG_ATT :
-				((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC);
+			if (SLOT->volume > MIN_ATT_INDEX) SLOT->state = EG_ATT;
 		} else {
 			SLOT->volume = MIN_ATT_INDEX;
-			SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC;
 		}
+		recalc_volout(SLOT);
 		ym2612.slot_mask |= (1<<s) << (c*4);
 	}
 }
@@ -579,8 +580,18 @@ INLINE void FM_KEYOFF(int c , int s )
 	if( SLOT->key )
 	{
 		SLOT->key = 0;
-		if (SLOT->state>EG_REL)
+		if (SLOT->state>EG_REL) {
 			SLOT->state = EG_REL;/* phase -> Release */
+			if (SLOT->ssg&0x08) {
+				if (SLOT->ssg&0x04)
+					SLOT->volume = (0x200 - SLOT->volume);
+				if (SLOT->volume >= 0x200) {
+					SLOT->volume = MAX_ATT_INDEX;
+					SLOT->state  = EG_OFF;
+				}
+			}
+		}
+		SLOT->vol_out = SLOT->volume + SLOT->tl;
 	}
 }
 
@@ -597,12 +608,15 @@ INLINE void set_det_mul(FM_CH *CH, FM_SLOT *SLOT, int v)
 INLINE void set_tl(FM_SLOT *SLOT, int v)
 {
 	SLOT->tl = (v&0x7f)<<(ENV_BITS-7); /* 7bit TL */
+	if (SLOT->state > EG_REL)
+		recalc_volout(SLOT);
 }
 
 /* set attack rate & key scale  */
 INLINE void set_ar_ksr(FM_CH *CH, FM_SLOT *SLOT, int v)
 {
 	UINT8 old_KSR = SLOT->KSR;
+	int eg_sh_ar, eg_sel_ar;
 
 	SLOT->ar = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0;
 
@@ -611,24 +625,20 @@ INLINE void set_ar_ksr(FM_CH *CH, FM_SLOT *SLOT, int v)
 	{
 		CH->SLOT[SLOT1].Incr=-1;
 	}
+
+	/* refresh Attack rate */
+	if ((SLOT->ar + SLOT->ksr) < 32+62)
+	{
+		eg_sh_ar  = eg_rate_shift [SLOT->ar  + SLOT->ksr ];
+		eg_sel_ar = eg_rate_select[SLOT->ar  + SLOT->ksr ];
+	}
 	else
 	{
-		int eg_sh_ar, eg_sel_ar;
-
-		/* refresh Attack rate */
-		if ((SLOT->ar + SLOT->ksr) < 32+62)
-		{
-			eg_sh_ar  = eg_rate_shift [SLOT->ar  + SLOT->ksr ];
-			eg_sel_ar = eg_rate_select[SLOT->ar  + SLOT->ksr ];
-		}
-		else
-		{
-			eg_sh_ar  = 0;
-			eg_sel_ar = 18;
-		}
-
-		SLOT->eg_pack_ar = eg_inc_pack[eg_sel_ar] | (eg_sh_ar<<24);
+		eg_sh_ar  = 0;
+		eg_sel_ar = 18;
 	}
+
+	SLOT->eg_pack_ar = eg_inc_pack[eg_sel_ar] | (eg_sh_ar<<24);
 }
 
 /* set decay rate */
@@ -750,7 +760,7 @@ INLINE int advance_lfo(int lfo_ampm, UINT32 lfo_cnt_old, UINT32 lfo_cnt)
 	return lfo_ampm;
 }
 
-INLINE void update_eg_phase(UINT16 *vol_out, FM_SLOT *SLOT, UINT32 eg_cnt)
+INLINE void update_eg_phase(FM_SLOT *SLOT, UINT32 eg_cnt)
 {
 	INT32 volume = SLOT->volume;
 	UINT32 pack = SLOT->eg_pack[SLOT->state - 1];
@@ -763,44 +773,113 @@ INLINE void update_eg_phase(UINT16 *vol_out, FM_SLOT *SLOT, UINT32 eg_cnt)
 	eg_inc_val = pack >> ((eg_cnt >> shift) & 7) * 3;
 	eg_inc_val = (1 << (eg_inc_val & 7)) >> 1;
 
-	switch (SLOT->state)
-	{
-	case EG_ATT:		/* attack phase */
-		volume += ( ~volume * eg_inc_val ) >> 4;
-		if ( volume <= MIN_ATT_INDEX )
+	if (SLOT->ssg&0x08) {
+		switch (SLOT->state)
 		{
-			volume = MIN_ATT_INDEX;
-			SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS: EG_DEC;
-		}
-		break;
+		case EG_ATT:	/* attack phase */
+			volume += ( ~volume * eg_inc_val ) >> 4;
+			if ( volume <= MIN_ATT_INDEX )
+			{
+				volume = MIN_ATT_INDEX;
+				SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS: EG_DEC;
+			}
+			break;
 
-	case EG_DEC:	/* decay phase */
-		volume += eg_inc_val;
-		if ( volume >= (INT32) SLOT->sl )
-			SLOT->state = EG_SUS;
-		break;
+		case EG_DEC:	/* decay phase */
+			if (volume < 0x200)
+				volume += 4*eg_inc_val;
+			if ( volume >= (INT32) SLOT->sl )
+				SLOT->state = EG_SUS;
+			break;
 
-	case EG_SUS:	/* sustain phase */
-		volume += eg_inc_val;
-		if ( volume >= MAX_ATT_INDEX )
-		{
-			volume = MAX_ATT_INDEX;
-			/* do not change SLOT->state (verified on real chip) */
+		case EG_SUS:	/* sustain phase */
+			if (volume < 0x200)
+				volume += 4*eg_inc_val;
+			break;
+
+		case EG_REL:	/* release phase */
+			if (volume < 0x200)
+				volume += 4*eg_inc_val;
+			if ( volume >= 0x200 )
+			{
+				volume = MAX_ATT_INDEX;
+				SLOT->state = EG_OFF;
+			}
+			break;
 		}
-		break;
 
-	case EG_REL:	/* release phase */
-		volume += eg_inc_val;
-		if ( volume >= MAX_ATT_INDEX )
+		SLOT->vol_out = volume + SLOT->tl;
+		if ((SLOT->ssg&0x04) && (SLOT->state > EG_REL))
+			SLOT->vol_out = ((0x200 - volume) & MAX_ATT_INDEX) + SLOT->tl;
+	} else {
+		switch (SLOT->state)
 		{
-			volume = MAX_ATT_INDEX;
-			SLOT->state = EG_OFF;
+		case EG_ATT:		/* attack phase */
+			volume += ( ~volume * eg_inc_val ) >> 4;
+			if ( volume <= MIN_ATT_INDEX )
+			{
+				volume = MIN_ATT_INDEX;
+				SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS: EG_DEC;
+			}
+			break;
+
+		case EG_DEC:	/* decay phase */
+			volume += eg_inc_val;
+			if ( volume >= (INT32) SLOT->sl )
+				SLOT->state = EG_SUS;
+			break;
+
+		case EG_SUS:	/* sustain phase */
+			volume += eg_inc_val;
+			if ( volume >= MAX_ATT_INDEX )
+			{
+				volume = MAX_ATT_INDEX;
+				/* do not change SLOT->state (verified on real chip) */
+			}
+			break;
+
+		case EG_REL:	/* release phase */
+			volume += eg_inc_val;
+			if ( volume >= MAX_ATT_INDEX )
+			{
+				volume = MAX_ATT_INDEX;
+				SLOT->state = EG_OFF;
+			}
+			break;
 		}
-		break;
-	}
 
+		SLOT->vol_out = volume + SLOT->tl;
+	}
 	SLOT->volume = volume;
-	*vol_out = SLOT->tl + volume; /* tl is 7bit<<3, volume 0-1023 (0-2039 total) */
+}
+
+INLINE void update_ssg_eg_phase(FM_SLOT *SLOT)
+{
+	if (SLOT->ssg&0x01) {
+		if (SLOT->ssg&0x02) {
+			SLOT->ssg ^= SLOT->ssgn ^ 4;
+			SLOT->ssgn = 4;
+		}
+
+		if (SLOT->state != EG_ATT && !(SLOT->ssg&0x04))
+			SLOT->volume  = MAX_ATT_INDEX;
+	} else {
+		if (SLOT->ssg&0x02) {
+			SLOT->ssg ^= 4;
+			SLOT->ssgn ^= 4;
+		} else
+			SLOT->phase = 0;
+
+		if (SLOT->state != EG_ATT) {
+			SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC;
+			if (SLOT->ar + SLOT->ksr < 32+62) {
+				if (SLOT->volume > MIN_ATT_INDEX) SLOT->state = EG_ATT;
+			} else {
+				SLOT->volume = MIN_ATT_INDEX;
+			}
+		}
+	}
+	recalc_volout(SLOT);
 }
 #endif
 
@@ -846,6 +925,16 @@ static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
 	{
 		int smp = 0;		/* produced sample */
 		unsigned int eg_out, eg_out2, eg_out4;
+		FM_SLOT *SLOT;
+
+		SLOT = &ct->CH->SLOT[SLOT1];
+		if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200) update_ssg_eg_phase(SLOT);
+		SLOT = &ct->CH->SLOT[SLOT2];
+		if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200) update_ssg_eg_phase(SLOT);
+		SLOT = &ct->CH->SLOT[SLOT3];
+		if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200) update_ssg_eg_phase(SLOT);
+		SLOT = &ct->CH->SLOT[SLOT4];
+		if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200) update_ssg_eg_phase(SLOT);
 
 		if (ct->pack & 8) { /* LFO enabled ? (test Earthworm Jim in between demo 1 and 2) */
 			ct->pack = (ct->pack&0xffff) | (advance_lfo(ct->pack >> 16, ct->lfo_cnt, ct->lfo_cnt + ct->lfo_inc) << 16);
@@ -857,12 +946,58 @@ static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
 		{
 			ct->eg_timer -= EG_TIMER_OVERFLOW;
 			ct->eg_cnt++;
-
-			if (ct->CH->SLOT[SLOT1].state != EG_OFF) update_eg_phase(&ct->vol_out1, &ct->CH->SLOT[SLOT1], ct->eg_cnt);
-			if (ct->CH->SLOT[SLOT2].state != EG_OFF) update_eg_phase(&ct->vol_out2, &ct->CH->SLOT[SLOT2], ct->eg_cnt);
-			if (ct->CH->SLOT[SLOT3].state != EG_OFF) update_eg_phase(&ct->vol_out3, &ct->CH->SLOT[SLOT3], ct->eg_cnt);
-			if (ct->CH->SLOT[SLOT4].state != EG_OFF) update_eg_phase(&ct->vol_out4, &ct->CH->SLOT[SLOT4], ct->eg_cnt);
+			if (ct->eg_cnt >= 4096) ct->eg_cnt = 1;
+
+			SLOT = &ct->CH->SLOT[SLOT1];
+			SLOT->vol_ipol = SLOT->vol_out;
+			if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt);
+			SLOT = &ct->CH->SLOT[SLOT2];
+			SLOT->vol_ipol = SLOT->vol_out;
+			if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt);
+			SLOT = &ct->CH->SLOT[SLOT3];
+			SLOT->vol_ipol = SLOT->vol_out;
+			if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt);
+			SLOT = &ct->CH->SLOT[SLOT4];
+			SLOT->vol_ipol = SLOT->vol_out;
+			if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt);
+		}
+#if 0
+		UINT32 ifrac0 = ct->eg_timer / (EG_TIMER_OVERFLOW>>EG_SH);
+		UINT32 ifrac1 = (1<<EG_SH) - ifrac0;
+		SLOT = &ct->CH->SLOT[SLOT1];
+		ct->vol_out1 = (SLOT->vol_ipol*ifrac1 + SLOT->vol_out*ifrac0) >> EG_SH;
+		SLOT = &ct->CH->SLOT[SLOT2];
+		ct->vol_out2 = (SLOT->vol_ipol*ifrac1 + SLOT->vol_out*ifrac0) >> EG_SH;
+		SLOT = &ct->CH->SLOT[SLOT3];
+		ct->vol_out3 = (SLOT->vol_ipol*ifrac1 + SLOT->vol_out*ifrac0) >> EG_SH;
+		SLOT = &ct->CH->SLOT[SLOT4];
+		ct->vol_out4 = (SLOT->vol_ipol*ifrac1 + SLOT->vol_out*ifrac0) >> EG_SH;
+#else
+		switch (ct->eg_timer >> EG_SH)
+		{
+			case 0:
+				ct->vol_out1 =  ct->CH->SLOT[SLOT1].vol_ipol;
+				ct->vol_out2 =  ct->CH->SLOT[SLOT2].vol_ipol;
+				ct->vol_out3 =  ct->CH->SLOT[SLOT3].vol_ipol;
+				ct->vol_out4 =  ct->CH->SLOT[SLOT4].vol_ipol;
+				break;
+			case (EG_TIMER_OVERFLOW>>EG_SH)-1:
+				ct->vol_out1 =  ct->CH->SLOT[SLOT1].vol_out;
+				ct->vol_out2 =  ct->CH->SLOT[SLOT2].vol_out;
+				ct->vol_out3 =  ct->CH->SLOT[SLOT3].vol_out;
+				ct->vol_out4 =  ct->CH->SLOT[SLOT4].vol_out;
+				break;
+			default:
+				ct->vol_out1 =  (ct->CH->SLOT[SLOT1].vol_ipol +
+					ct->CH->SLOT[SLOT1].vol_out) >> 1;
+				ct->vol_out2 =  (ct->CH->SLOT[SLOT2].vol_ipol +
+					ct->CH->SLOT[SLOT2].vol_out) >> 1;
+				ct->vol_out3 =  (ct->CH->SLOT[SLOT3].vol_ipol +
+					ct->CH->SLOT[SLOT3].vol_out) >> 1;
+				ct->vol_out4 =  (ct->CH->SLOT[SLOT4].vol_ipol +
+					ct->CH->SLOT[SLOT4].vol_out) >> 1;
 		}
+#endif
 
 		if (ct->pack & 4) continue; /* output disabled */
 
@@ -892,7 +1027,7 @@ static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
 			if (ct->pack & (1<<(SLOT4+8))) eg_out4 += add;
 		}
 
-		switch( ct->CH->ALGO )
+		switch( ct->algo&0x7 )
 		{
 			case 0:
 			{
@@ -1086,6 +1221,33 @@ static void chan_render_finish(void)
 	ym2612.OPN.lfo_cnt = crct.lfo_cnt;
 }
 
+static UINT32 update_lfo_phase(FM_SLOT *SLOT, UINT32 block_fnum)
+{
+	UINT32 fnum_lfo;
+	INT32  lfo_fn_table_index_offset;
+	UINT8  blk;
+	UINT32 fn;
+	int fc,fdt;
+
+	fnum_lfo   = ((block_fnum & 0x7f0) >> 4) * 32 * 8;
+	lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + crct.CH->pms + ((crct.pack>>16)&0xff) ];
+	if (lfo_fn_table_index_offset)	/* LFO phase modulation active */
+	{
+		block_fnum = block_fnum*2 + lfo_fn_table_index_offset;
+		blk = (block_fnum&0x7000) >> 12;
+		fn  = block_fnum & 0xfff;
+
+		/* phase increment counter */
+		fc = (fn_table[fn]>>(7-blk));
+
+		fdt = fc + SLOT->DT[crct.CH->kcode];
+		if (fdt < 0) fdt += fn_table[0x7ff*2] >> 2;
+
+		return (fdt * SLOT->mul) >> 1;
+	} else
+		return SLOT->Incr;
+}
+
 static int chan_render(int *buffer, int length, int c, UINT32 flags) // flags: stereo, ?, disabled, ?, pan_r, pan_l
 {
 	crct.CH = &ym2612.CH[c];
@@ -1114,58 +1276,22 @@ static int chan_render(int *buffer, int length, int c, UINT32 flags) // flags: s
 	crct.phase3 = crct.CH->SLOT[SLOT3].phase;
 	crct.phase4 = crct.CH->SLOT[SLOT4].phase;
 
-	/* current output from EG circuit (without AM from LFO) */
-	crct.vol_out1 = crct.CH->SLOT[SLOT1].tl + ((UINT32)crct.CH->SLOT[SLOT1].volume);
-	crct.vol_out2 = crct.CH->SLOT[SLOT2].tl + ((UINT32)crct.CH->SLOT[SLOT2].volume);
-	crct.vol_out3 = crct.CH->SLOT[SLOT3].tl + ((UINT32)crct.CH->SLOT[SLOT3].volume);
-	crct.vol_out4 = crct.CH->SLOT[SLOT4].tl + ((UINT32)crct.CH->SLOT[SLOT4].volume);
-
 	crct.op1_out = crct.CH->op1_out;
 	crct.algo = crct.CH->ALGO & 7;
 
-	if(crct.CH->pms)
+	if(crct.CH->pms && (ym2612.OPN.ST.mode & 0xC0) && c == 2) {
+		/* 3 slot mode */
+		crct.incr1 = update_lfo_phase(&crct.CH->SLOT[SLOT1], ym2612.OPN.SL3.block_fnum[1]);
+		crct.incr2 = update_lfo_phase(&crct.CH->SLOT[SLOT2], ym2612.OPN.SL3.block_fnum[2]);
+		crct.incr3 = update_lfo_phase(&crct.CH->SLOT[SLOT3], ym2612.OPN.SL3.block_fnum[0]);
+		crct.incr4 = update_lfo_phase(&crct.CH->SLOT[SLOT4], crct.CH->block_fnum);
+	}
+	else if(crct.CH->pms)
 	{
-		/* add support for 3 slot mode */
-		UINT32 block_fnum = crct.CH->block_fnum;
-
-		UINT32 fnum_lfo   = ((block_fnum & 0x7f0) >> 4) * 32 * 8;
-		INT32  lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + crct.CH->pms + ((crct.pack>>16)&0xff) ];
-
-		if (lfo_fn_table_index_offset)	/* LFO phase modulation active */
-		{
-			UINT8  blk;
-			UINT32 fn;
-			int kc,fc,fdt;
-
-			block_fnum = block_fnum*2 + lfo_fn_table_index_offset;
-			blk = (block_fnum&0x7000) >> 12;
-			fn  = block_fnum & 0xfff;
-
-			/* keyscale code */
-			kc = (blk<<2) | opn_fktable[(fn >> 7) & 0xf];
-			/* phase increment counter */
-			fc = (fn_table[fn]>>(7-blk));
-
-			fdt = fc + crct.CH->SLOT[SLOT1].DT[kc];
-			if (fdt < 0) fdt += fn_table[0x7ff*2] >> 2;
-			crct.incr1 = (fdt*crct.CH->SLOT[SLOT1].mul) >> 1;
-			fdt = fc + crct.CH->SLOT[SLOT2].DT[kc];
-			if (fdt < 0) fdt += fn_table[0x7ff*2] >> 2;
-			crct.incr2 = (fdt*crct.CH->SLOT[SLOT2].mul) >> 1;
-			fdt = fc + crct.CH->SLOT[SLOT3].DT[kc];
-			if (fdt < 0) fdt += fn_table[0x7ff*2] >> 2;
-			crct.incr3 = (fdt*crct.CH->SLOT[SLOT3].mul) >> 1;
-			fdt = fc + crct.CH->SLOT[SLOT4].DT[kc];
-			if (fdt < 0) fdt += fn_table[0x7ff*2] >> 2;
-			crct.incr4 = (fdt*crct.CH->SLOT[SLOT4].mul) >> 1;
-		}
-		else	/* LFO phase modulation  = zero */
-		{
-			crct.incr1 = crct.CH->SLOT[SLOT1].Incr;
-			crct.incr2 = crct.CH->SLOT[SLOT2].Incr;
-			crct.incr3 = crct.CH->SLOT[SLOT3].Incr;
-			crct.incr4 = crct.CH->SLOT[SLOT4].Incr;
-		}
+		crct.incr1 = update_lfo_phase(&crct.CH->SLOT[SLOT1], crct.CH->block_fnum);
+		crct.incr2 = update_lfo_phase(&crct.CH->SLOT[SLOT2], crct.CH->block_fnum);
+		crct.incr3 = update_lfo_phase(&crct.CH->SLOT[SLOT3], crct.CH->block_fnum);
+		crct.incr4 = update_lfo_phase(&crct.CH->SLOT[SLOT4], crct.CH->block_fnum);
 	}
 	else	/* no LFO phase modulation */
 	{
@@ -1297,8 +1423,13 @@ static void reset_channels(FM_CH *CH)
 		CH[c].fc = 0;
 		for(s = 0 ; s < 4 ; s++ )
 		{
+			CH[c].SLOT[s].Incr = -1;
+			CH[c].SLOT[s].key = 0;
+			CH[c].SLOT[s].phase = 0;
+			CH[c].SLOT[s].ssg = CH[c].SLOT[s].ssgn = 0;
 			CH[c].SLOT[s].state= EG_OFF;
 			CH[c].SLOT[s].volume = MAX_ATT_INDEX;
+			CH[c].SLOT[s].vol_out = MAX_ATT_INDEX;
 		}
 		CH[c].mem_value = CH[c].op1_out = 0;
 	}
@@ -1503,8 +1634,10 @@ static int OPNWriteReg(int r, int v)
 		break;
 
 	case 0x90:	/* SSG-EG */
-		// removed.
-		ret = 0;
+		SLOT->ssg =  v&0x0f;
+		SLOT->ssg ^= SLOT->ssgn;
+		if (SLOT->state > EG_REL)
+			recalc_volout(SLOT);
 		break;
 
 	case 0xa0:

+ 14 - 8
pico/sound/ym2612.h

@@ -53,6 +53,11 @@ typedef struct
 		};
 		UINT32 eg_pack[4];
 	};
+
+	UINT8	ssg;		/* 0x30 SSG-EG waveform */
+	UINT8	ssgn;
+	UINT16	vol_out;	/* 0x32 current output from EG (without LFO) */
+	UINT16  vol_ipol;       /* 0x34 interpolator memory */
 } FM_SLOT;
 
 
@@ -176,21 +181,22 @@ int  YM2612PicoStateLoad2(int *tat, int *tbt);
 #else
 /* GP2X specific */
 #include "../../platform/gp2x/940ctl.h"
-#define YM2612Init(baseclock,rate) { \
+#define YM2612Init(baseclock,rate) do { \
 	if (PicoIn.opt&POPT_EXT_FM) YM2612Init_940(baseclock, rate); \
 	else               YM2612Init_(baseclock, rate); \
-}
-#define YM2612ResetChip() { \
+} while (0)
+#define YM2612ResetChip() do { \
 	if (PicoIn.opt&POPT_EXT_FM) YM2612ResetChip_940(); \
 	else               YM2612ResetChip_(); \
-}
-#define YM2612UpdateOne(buffer,length,stereo,is_buf_empty) \
+} while (0)
+#define YM2612UpdateOne(buffer,length,stereo,is_buf_empty) do { \
 	(PicoIn.opt&POPT_EXT_FM) ? YM2612UpdateOne_940(buffer, length, stereo, is_buf_empty) : \
-				YM2612UpdateOne_(buffer, length, stereo, is_buf_empty);
-#define YM2612PicoStateLoad() { \
+				YM2612UpdateOne_(buffer, length, stereo, is_buf_empty); \
+} while (0)
+#define YM2612PicoStateLoad() do { \
 	if (PicoIn.opt&POPT_EXT_FM) YM2612PicoStateLoad_940(); \
 	else               YM2612PicoStateLoad_(); \
-}
+} while (0)
 #endif /* __GP2X__ */
 
 

+ 221 - 199
pico/sound/ym2612_arm.S

@@ -1,6 +1,7 @@
 /*
  * PicoDrive
  * (C) notaz, 2006
+ * (C) kub, 2020	added SSG-EG and simple output rate interpolation
  *
  * This work is licensed under the terms of MAME license.
  * See COPYING file in the top-level directory.
@@ -18,7 +19,7 @@
 .equiv SLOT2, 2
 .equiv SLOT3, 1
 .equiv SLOT4, 3
-.equiv SLOT_STRUCT_SIZE, 0x30
+.equiv SLOT_STRUCT_SIZE, 0x38
 
 .equiv TL_TAB_LEN, 0x1A00
 
@@ -28,11 +29,11 @@
 .equiv EG_REL, 1
 .equiv EG_OFF, 0
 
-.equiv EG_SH,			  16             @ 16.16 fixed point (envelope generator timing)
+.equiv EG_SH,		  16             @ 16.16 fixed point (envelope generator timing)
 .equiv EG_TIMER_OVERFLOW, (3*(1<<EG_SH)) @ envelope generator timer overflows every 3 samples (on real chip)
 .equiv LFO_SH,            24  /*  8.24 fixed point (LFO calculations)       */
 
-.equiv ENV_QUIET,		  (2*13*256/8)
+.equiv ENV_QUIET,	  (2*13*256/8)
 
 .text
 .align 2
@@ -41,8 +42,10 @@
 @ r5=slot, r1=eg_cnt, trashes: r0,r2,r3
 @ writes output to routp, but only if vol_out changes
 .macro update_eg_phase_slot slot
+    ldrh    r0, [r5,#0x32]       @ vol_out
     ldrb    r2, [r5,#0x17]       @ state
     add     r3, r5, #0x1c
+    strh    r0, [r5,#0x34]       @ vol_ipol
     tst     r2, r2
     beq     0f                   @ EG_OFF
 
@@ -60,11 +63,17 @@
     add     r3, r3, r3, lsl #1
     mov     r3, r2, lsr r3
     and     r3, r3, #7           @ eg_inc_val shift, may be 0
+    ldrb    r0, [r5,#0x30]       @ ssg
     ldrb    r2, [r5,#0x17]       @ state
-    ldrh    r0, [r5,#0x1a]       @ volume, unsigned (0-1023)
 
+    tst     r0, #0x08            @ ssg enabled?
+    bne     9f
+
+    @ non-SSG-EG mode
     cmp     r2, #4               @ EG_ATT
+    ldrh    r0, [r5,#0x1a]       @ volume, unsigned (0-1023)
     beq     4f
+
     cmp     r2, #2
     mov     r2, #1
     mov     r2, r2, lsl r3
@@ -82,10 +91,9 @@
 
 4:  @ EG_ATT
     subs    r3, r3, #1           @ eg_inc_val_shift - 1
-    mov     r2, #0
     mvnpl   r2, r0
-    mov     r2, r2, lsl r3
-    add     r0, r0, r2, asr #4
+    movpl   r2, r2, lsl r3
+    addpl   r0, r0, r2, asr #4
     cmp     r0, #0               @ if (volume <= MIN_ATT_INDEX)
     bgt     10f
     ldr     r2, [r5,#0x1c]
@@ -112,24 +120,81 @@
     strgeb  r3, [r5,#0x17]       @ state
 
 10: @ finish
-    ldrh    r3, [r5,#0x18]       @ tl
     strh    r0, [r5,#0x1a]       @ volume
+    b       11f
+
+9:  @ SSG-EG mode
+    cmp     r2, #4               @ EG_ATT
+    ldrh    r0, [r5,#0x1a]       @ volume, unsigned (0-1023)
+    beq     4f
+
+    cmp     r0, #0x200           @ if ( volume < 0x200 )
+    movlt   r0, #1
+    movlt   r3, r0, lsl r3
+    ldrlth  r0, [r5,#0x1a]       @ volume, unsigned (0-1023)
+    movlt   r3, r3, lsr #1       @ eg_inc_val
+    addlt   r0, r0, r3, lsr #2
+
+    cmp     r2, #2
+    blt     1f                   @ EG_REL
+    beq     10f                  @ EG_SUS - nothing more to do
+
+3:  @ EG_DEC
+    ldr     r2, [r5,#0x1c]       @ sl (can be 16bit?)
+    mov     r3, #EG_SUS
+    cmp     r0, r2               @ if ( volume >= (INT32) SLOT->sl )
+    strgeb  r3, [r5,#0x17]       @ state
+    b       10f
+
+4:  @ EG_ATT
+    subs    r3, r3, #1           @ eg_inc_val_shift - 1
+    mvnpl   r2, r0
+    movpl   r2, r2, lsl r3
+    addpl   r0, r0, r2, asr #4
+    cmp     r0, #0               @ if (volume <= MIN_ATT_INDEX)
+    bgt     10f
+    ldr     r2, [r5,#0x1c]
+    mov     r0, #0
+    cmp     r2, #0
+    movne   r3, #EG_DEC
+    moveq   r3, #EG_SUS
+    strb    r3, [r5,#0x17]       @ state
+    b       10f
+
+1:  @ EG_REL
+    mov     r2, #0x200
+    cmp     r0, r2               @ if ( volume >= 0x200 )
+    movge   r0, #1024
+    subge   r0, #1
+    movge   r3, #EG_OFF
+    strgeb  r3, [r5,#0x17]       @ state
+
+10: @ finish
+    strh    r0, [r5,#0x1a]       @ volume
+    ldrb    r2, [r5,#0x30]       @ ssg
+    ldrb    r3, [r5,#0x17]       @ state
+    cmp     r2, #0x0c            @ if ( ssg&0x04 && state > EG_REL )
+    cmpge   r3, #EG_REL+1
+    rsbge   r0, r0, #0x200       @ volume = (0x200-volume) & MAX_ATT
+    lslge   r0, r0, #10
+    lsrge   r0, r0, #10
+
+11:
+    ldrh    r3, [r5,#0x18]       @ tl
+    add     r0, r0, r3           @ volume += tl
+    strh    r0, [r5,#0x32]       @ vol_out
 .if     \slot == SLOT1
     mov     r6, r6, lsr #16
-    add     r0, r0, r3
     orr     r6, r0, r6, lsl #16
 .elseif \slot == SLOT2
     mov     r6, r6, lsl #16
-    add     r0, r0, r3
     mov     r0, r0, lsl #16
     orr     r6, r0, r6, lsr #16
 .elseif \slot == SLOT3
     mov     r7, r7, lsr #16
-    add     r0, r0, r3
     orr     r7, r0, r7, lsl #16
 .elseif \slot == SLOT4
     mov     r7, r7, lsl #16
-    add     r0, r0, r3
     mov     r0, r0, lsl #16
     orr     r7, r0, r7, lsr #16
 .endif
@@ -137,6 +202,63 @@
 0: @ EG_OFF
 .endm
 
+@ r5=slot, trashes: r0,r2,r3
+.macro update_ssg_eg
+    ldrh    r0, [r5,#0x30]                @ ssg+ssgn
+    ldrb    r2, [r5,#0x17]                @ state
+    ldrh    r3, [r5,#0x1a]                @ volume
+    tst     r0, #0x08                     @ ssg enabled?
+    beq     9f
+    cmp     r2, #EG_REL                   @ state > EG_REL?
+    ble     9f
+    cmp     r3, #0x200                    @ volume >= 0x200?
+    blt     9f
+
+    tst     r0, #0x01
+    beq     1f
+
+    tst     r0, #0x02
+    eorne   r0, r0, lsr #8                @ ssg ^= ssgn ^ 4
+    eorne   r0, r0, #0x4
+    orrne   r0, r0, #0x400                @ ssgn = 4
+    strneh  r0, [r5,#0x30]
+
+    eor     r0, r0, #0x4                  @ if ( !(ssg&0x04 )
+    tst     r0, #0x4
+    cmpne   r2, #EG_ATT                   @ if ( state != EG_ATT )
+    movne   r0, #0x400
+    subne   r0, r0, #1
+    strneh  r0, [r5,#0x1a]                @ volume = MAX_ATT
+    b       9f
+
+1:  tst     r0, #0x02
+    eorne   r0, r0, #0x4                  @ ssg ^= 4
+    eorne   r0, r0, #0x400                @ ssgn ^= 4
+    strneh  r0, [r5,#0x30]
+    moveq   r3, #0
+    streq   r3, [r5,#0x0c]                @ phase = 0
+
+    cmp     r2, #EG_ATT                   @ if ( state != EG_ATT )
+    beq     9f
+
+    ldr     r3, [r5,#0x1c]                @ sl
+    mov     r2, #EG_SUS                   @ state = sl==MIN_ATT ? EG_SUS:EG_DEC
+    cmp     r3, #0
+
+    ldr     r0, [r5,#0x04]                @ ar
+    ldr     r3, [r5,#0x14]                @ ksr
+    movne   r2, #EG_DEC
+    add     r0, r0, r3
+    cmp     r0, #32+62                    @ if ( ar+ksr >= 32+62 )
+    ldrlt   r0, [r5,#0x1a]
+    movge   r0, #0
+    strgeh  r0, [r5,#0x1a]                @ volume = MIN_ATT
+
+    cmp     r0, #0
+    movgt   r2, #EG_ATT
+    strb    r2, [r5,#0x17]                @ state
+9:
+.endm
 
 @ r12=lfo_ampm[31:16], r1=lfo_cnt_old, r2=lfo_cnt, r3=scratch
 .macro advance_lfo_m
@@ -532,187 +654,6 @@
 .endm
 
 
-/*
-.global update_eg_phase @ FM_SLOT *SLOT, UINT32 eg_cnt
-
-update_eg_phase:
-    stmfd   sp!, {r5,r6}
-    mov     r5, r0             @ slot
-    ldrh    r3, [r5,#0x18]       @ tl
-    ldrh    r6, [r5,#0x1a]       @ volume
-    add     r6, r6, r3
-    update_eg_phase_slot SLOT1
-    mov     r0, r6
-    ldmfd   sp!, {r5,r6}
-    bx      lr
-.pool
-
-
-.global advance_lfo @ int lfo_ampm, UINT32 lfo_cnt_old, UINT32 lfo_cnt
-
-advance_lfo:
-    mov     r12, r0, lsl #16
-    advance_lfo_m
-    mov     r0, r12, lsr #16
-    bx      lr
-.pool
-
-
-.global upd_algo0 @ chan_rend_context *c
-upd_algo0:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_algo0_m
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-
-
-.global upd_algo1 @ chan_rend_context *c
-upd_algo1:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_algo1_m
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-
-
-.global upd_algo2 @ chan_rend_context *c
-upd_algo2:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_algo2_m
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-
-
-.global upd_algo3 @ chan_rend_context *c
-upd_algo3:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_algo3_m
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-
-
-.global upd_algo4 @ chan_rend_context *c
-upd_algo4:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_algo4_m
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-
-
-.global upd_algo5 @ chan_rend_context *c
-upd_algo5:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_algo5_m
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-
-
-.global upd_algo6 @ chan_rend_context *c
-upd_algo6:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_algo6_m
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-
-
-.global upd_algo7 @ chan_rend_context *c
-upd_algo7:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_algo7_m
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-
-
-.global upd_slot1 @ chan_rend_context *c
-upd_slot1:
-    stmfd   sp!, {r4-r10,lr}
-    mov     lr, r0
-
-    PIC_LDR(r3, ip, ym_sin_tab)
-    PIC_LDR(r5, ip, ym_tl_tab)
-    ldmia   lr, {r6-r7}
-    ldr     r10, [lr, #0x54]
-    ldr     r12, [lr, #0x4c]
-
-    upd_slot1_m
-    str     r10, [lr, #0x38]
-
-    ldmfd   sp!, {r4-r10,pc}
-.pool
-*/
-
-
 @ lr=context, r12=pack (stereo, lastchan, disabled, lfo_enabled | pan_r, pan_l, ams[2] | AMmasks[4] | FB[4] | lfo_ampm[16])
 @ r0-r2=scratch, r3=sin_tab/scratch, r4=(length<<8)|unused[4],was_update,algo[3], r5=tl_tab/slot,
 @ r6-r7=vol_out[4], r8=eg_timer, r9=eg_timer_add[31:16], r10=op1_out, r11=buffer
@@ -730,14 +671,21 @@ chan_render_loop:
     add     r0,  lr, #0x44
     ldmia   r0,  {r8,r9}         @ eg_timer, eg_timer_add
     ldr     r10, [lr, #0x54]     @ op1_out
-    ldmia   lr,  {r6,r7}         @ load volumes
+@   ldmia   lr,  {r6,r7}         @ load volumes
+    ldr     r5, [lr, #0x40]      @ CH
+    ldrh    r6, [r5, #0x32]      @ vol_out values for all slots
+    ldrh    r2, [r5, #0x32+SLOT_STRUCT_SIZE*2]
+    ldrh    r7, [r5, #0x32+SLOT_STRUCT_SIZE]
+    ldrh    r3, [r5, #0x32+SLOT_STRUCT_SIZE*3]
+    orr     r6, r6, r2, lsl #16
+    orr     r7, r7, r3, lsl #16
 
     tst     r12, #8              @ lfo?
     beq     crl_loop
 
 crl_loop_lfo:
     add     r0, lr, #0x30
-    ldmia   r0, {r1,r2}
+    ldmia   r0, {r1,r2}          @ lfo_cnt, lfo_inc
 
     subs    r4, r4, #0x100
     bmi     crl_loop_end
@@ -754,15 +702,29 @@ crl_loop:
     subs    r4, r4, #0x100
     bmi     crl_loop_end
 
+    @ -- SSG --
+    add     r0, lr, #0x3c
+    ldmia   r0, {r1,r5}         @ eg_cnt, CH
+
+    @ r5=slot, trashes: r0,r2,r3
+    update_ssg_eg
+    add     r5, r5, #SLOT_STRUCT_SIZE*2 @ SLOT2 (2)
+    update_ssg_eg
+    sub     r5, r5, #SLOT_STRUCT_SIZE   @ SLOT3 (1)
+    update_ssg_eg
+    add     r5, r5, #SLOT_STRUCT_SIZE*2 @ SLOT4 (3)
+    update_ssg_eg
+    sub     r5, r5, #SLOT_STRUCT_SIZE*3
+
     @ -- EG --
     add     r8, r8, r9
     cmp     r8, #EG_TIMER_OVERFLOW
     bcc     eg_done
-    add     r0, lr, #0x3c
-    ldmia   r0, {r1,r5}         @ eg_cnt, CH
 eg_loop:
     sub     r8, r8, #EG_TIMER_OVERFLOW
     add     r1, r1, #1
+    cmp     r1, #4096
+    movge   r1, #1
                                         @ SLOT1 (0)
     @ r5=slot, r1=eg_cnt, trashes: r0,r2,r3
     update_eg_phase_slot SLOT1
@@ -774,8 +736,8 @@ eg_loop:
     update_eg_phase_slot SLOT4
 
     cmp     r8, #EG_TIMER_OVERFLOW
-    subcs   r5, r5, #SLOT_STRUCT_SIZE*3
-    bcs     eg_loop
+    sub     r5, r5, #SLOT_STRUCT_SIZE*3
+    bhs     eg_loop
     str     r1, [lr, #0x3c]
 
 eg_done:
@@ -787,6 +749,66 @@ eg_done:
     cmp     r0, #0x4
     beq     crl_loop
 
+    @ output interpolation
+#if 0
+    @ basic interpolator, interpolate in middle region, else use closer value
+    mov     r3, r8, lsr #EG_SH      @ eg_timer, [0..3<<EG_SH) after loop
+    cmp     r3, #(EG_TIMER_OVERFLOW>>EG_SH)/2
+    bgt     0f                      @ mix is vol_out
+
+    ldrh    r0, [r5,#0x34]          @ SLOT1 vol_ipol
+    lsleq   r2, r6, #16
+    addeq   r0, r0, r2, lsr #16
+    lsreq   r0, r0, #1
+    mov     r6, r6, lsr #16
+    orr     r6, r0, r6, lsl #16
+
+    ldrh    r0, [r5,#0x34+SLOT_STRUCT_SIZE*2] @ SLOT2 vol_ipol
+    addeq   r0, r0, r6, lsr #16
+    lsreq   r0, r0, #1
+    mov     r6, r6, lsl #16
+    orr     r6, r6, r0
+    ror     r6, r6, #16
+
+    ldrh    r0, [r5,#0x34+SLOT_STRUCT_SIZE]   @ SLOT3 vol_ipol
+    lsleq   r2, r7, #16
+    addeq   r0, r0, r2, lsr #16
+    lsreq   r0, r0, #1
+    mov     r7, r7, lsr #16
+    orr     r7, r0, r7, lsl #16
+
+    ldrh    r0, [r5,#0x34+SLOT_STRUCT_SIZE*3] @ SLOT4 vol_ipol
+    addeq   r0, r0, r7, lsr #16
+    lsreq   r0, r0, #1
+    mov     r7, r7, lsl #16
+    orr     r7, r7, r0
+    ror     r7, r7, #16
+#elif 0
+    @ super-basic... just take value closest to sample point
+    mov     r3, r8, lsr #EG_SH-1    @ eg_timer, [0..3<<EG_SH) after loop
+    cmp     r3, #(EG_TIMER_OVERFLOW>>EG_SH)
+    bgt     0f                      @ mix is vol_out
+
+    ldrh    r0, [r5,#0x34]          @ SLOT1 vol_ipol
+    mov     r6, r6, lsr #16
+    orr     r6, r0, r6, lsl #16
+
+    ldrh    r0, [r5,#0x34+SLOT_STRUCT_SIZE*2] @ SLOT2 vol_ipol
+    mov     r6, r6, lsl #16
+    orr     r6, r6, r0
+    ror     r6, r6, #16
+
+    ldrh    r0, [r5,#0x34+SLOT_STRUCT_SIZE]   @ SLOT3 vol_ipol
+    mov     r7, r7, lsr #16
+    orr     r7, r0, r7, lsl #16
+
+    ldrh    r0, [r5,#0x34+SLOT_STRUCT_SIZE*3] @ SLOT4 vol_ipol
+    mov     r7, r7, lsl #16
+    orr     r7, r7, r0
+    ror     r7, r7, #16
+#endif
+0:
+
     @ -- SLOT1 --
     PIC_LDR(r3, r2, ym_tl_tab)