audio: fixes and optimizations for SSG-EG
authorkub <derkub@gmail.com>
Wed, 22 Apr 2020 18:48:03 +0000 (20:48 +0200)
committerkub <derkub@gmail.com>
Wed, 22 Apr 2020 19:49:48 +0000 (21:49 +0200)
pico/sound/ym2612.c
pico/sound/ym2612.h
pico/sound/ym2612_arm.S

index af381fb..cb4f8c7 100644 (file)
@@ -128,7 +128,7 @@ extern YM2612 *ym2612_940;
 \r
 #endif\r
 \r
-void memset32(int *dest, int c, int count);\r
+void memset32(void *dest, int c, int count);\r
 \r
 \r
 #ifndef __GNUC__\r
@@ -511,7 +511,7 @@ static INT32 lfo_pm_table[128*8*32]; /* 128 combinations of 7 bits meaningful (o
        but LFO works with one more bit of a precision so we really need 4096 elements */\r
 static UINT32 fn_table[4096];  /* fnumber->increment counter */\r
 \r
-static int g_lfo_ampm = 0;\r
+static int g_lfo_ampm;\r
 \r
 /* register number to channel number , slot offset */\r
 #define OPN_CHAN(N) (N&3)\r
@@ -569,7 +569,7 @@ INLINE void FM_KEYON(int c , int s )
                } else {\r
                        SLOT->volume = MIN_ATT_INDEX;\r
                }\r
-               recalc_volout(SLOT);\r
+//             recalc_volout(SLOT);\r
                ym2612.slot_mask |= (1<<s) << (c*4);\r
        }\r
 }\r
@@ -608,8 +608,8 @@ INLINE void set_det_mul(FM_CH *CH, FM_SLOT *SLOT, int v)
 INLINE void set_tl(FM_SLOT *SLOT, int v)\r
 {\r
        SLOT->tl = (v&0x7f)<<(ENV_BITS-7); /* 7bit TL */\r
-       if (SLOT->state > EG_REL)\r
-               recalc_volout(SLOT);\r
+//     if (SLOT->state > EG_REL)\r
+//             recalc_volout(SLOT);\r
 }\r
 \r
 /* set attack rate & key scale  */\r
@@ -761,7 +761,7 @@ INLINE int advance_lfo(int lfo_ampm, UINT32 lfo_cnt_old, UINT32 lfo_cnt)
        return lfo_ampm;\r
 }\r
 \r
-INLINE void update_eg_phase(FM_SLOT *SLOT, UINT32 eg_cnt)\r
+INLINE void update_eg_phase(FM_SLOT *SLOT, UINT32 eg_cnt, UINT32 ssg_en)\r
 {\r
        INT32 volume = SLOT->volume;\r
        UINT32 pack = SLOT->eg_pack[SLOT->state - 1];\r
@@ -774,7 +774,7 @@ INLINE void update_eg_phase(FM_SLOT *SLOT, UINT32 eg_cnt)
        eg_inc_val = pack >> ((eg_cnt >> shift) & 7) * 3;\r
        eg_inc_val = (1 << (eg_inc_val & 7)) >> 1;\r
 \r
-       if (SLOT->ssg&0x08) {\r
+       if ((SLOT->ssg&0x08) && ssg_en) {\r
                switch (SLOT->state)\r
                {\r
                case EG_ATT:    /* attack phase */\r
@@ -854,7 +854,7 @@ INLINE void update_eg_phase(FM_SLOT *SLOT, UINT32 eg_cnt)
        SLOT->volume = volume;\r
 }\r
 \r
-INLINE void update_ssg_eg_phase(FM_SLOT *SLOT)\r
+INLINE UINT32 update_ssg_eg_phase(FM_SLOT *SLOT, UINT32 phase)\r
 {\r
        if (SLOT->ssg&0x01) {\r
                if (SLOT->ssg&0x02) {\r
@@ -869,7 +869,7 @@ INLINE void update_ssg_eg_phase(FM_SLOT *SLOT)
                        SLOT->ssg ^= 4;\r
                        SLOT->ssgn ^= 4;\r
                } else\r
-                       SLOT->phase = 0;\r
+                       phase = 0;\r
 \r
                if (SLOT->state != EG_ATT) {\r
                        SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC;\r
@@ -880,7 +880,8 @@ INLINE void update_ssg_eg_phase(FM_SLOT *SLOT)
                        }\r
                }\r
        }\r
-       recalc_volout(SLOT);\r
+//     recalc_volout(SLOT);\r
+       return phase;\r
 }\r
 #endif\r
 \r
@@ -927,15 +928,23 @@ static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
                int smp = 0;            /* produced sample */\r
                unsigned int eg_out, eg_out2, eg_out4;\r
                FM_SLOT *SLOT;\r
+               UINT32 cnt = ct->eg_timer_add+(ct->eg_timer & ((1<<EG_SH)-1));\r
 \r
-               SLOT = &ct->CH->SLOT[SLOT1];\r
-               if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200) update_ssg_eg_phase(SLOT);\r
-               SLOT = &ct->CH->SLOT[SLOT2];\r
-               if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200) update_ssg_eg_phase(SLOT);\r
-               SLOT = &ct->CH->SLOT[SLOT3];\r
-               if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200) update_ssg_eg_phase(SLOT);\r
-               SLOT = &ct->CH->SLOT[SLOT4];\r
-               if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200) update_ssg_eg_phase(SLOT);\r
+               if (ct->pack & 2) while (cnt >= 1<<EG_SH) {\r
+                       cnt -= 1<<EG_SH;\r
+                       SLOT = &ct->CH->SLOT[SLOT1];\r
+                       if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200)\r
+                               ct->phase1 = update_ssg_eg_phase(SLOT, ct->phase1);\r
+                       SLOT = &ct->CH->SLOT[SLOT2];\r
+                       if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200)\r
+                               ct->phase2 = update_ssg_eg_phase(SLOT, ct->phase2);\r
+                       SLOT = &ct->CH->SLOT[SLOT3];\r
+                       if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200)\r
+                               ct->phase3 = update_ssg_eg_phase(SLOT, ct->phase3);\r
+                       SLOT = &ct->CH->SLOT[SLOT4];\r
+                       if ((SLOT->ssg&0x08) && SLOT->state > EG_REL && SLOT->volume >= 0x200)\r
+                               ct->phase4 = update_ssg_eg_phase(SLOT, ct->phase4);\r
+               }\r
 \r
                if (ct->pack & 8) { /* LFO enabled ? (test Earthworm Jim in between demo 1 and 2) */\r
                        ct->pack = (ct->pack&0xffff) | (advance_lfo(ct->pack >> 16, ct->lfo_cnt, ct->lfo_cnt + ct->lfo_inc) << 16);\r
@@ -943,7 +952,21 @@ static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
                }\r
 \r
                ct->eg_timer += ct->eg_timer_add;\r
-               while (ct->eg_timer >= EG_TIMER_OVERFLOW)\r
+               if (ct->eg_timer < EG_TIMER_OVERFLOW) {\r
+                       SLOT = &ct->CH->SLOT[SLOT1];\r
+                       SLOT->vol_ipol = SLOT->vol_out;\r
+                       if (SLOT->state > EG_REL) recalc_volout(SLOT);\r
+                       SLOT = &ct->CH->SLOT[SLOT2];\r
+                       SLOT->vol_ipol = SLOT->vol_out;\r
+                       if (SLOT->state > EG_REL) recalc_volout(SLOT);\r
+                       SLOT = &ct->CH->SLOT[SLOT3];\r
+                       SLOT->vol_ipol = SLOT->vol_out;\r
+                       if (SLOT->state > EG_REL) recalc_volout(SLOT);\r
+                       SLOT = &ct->CH->SLOT[SLOT4];\r
+                       SLOT->vol_ipol = SLOT->vol_out;\r
+                       if (SLOT->state > EG_REL) recalc_volout(SLOT);\r
+               }\r
+               else while (ct->eg_timer >= EG_TIMER_OVERFLOW)\r
                {\r
                        ct->eg_timer -= EG_TIMER_OVERFLOW;\r
                        ct->eg_cnt++;\r
@@ -951,17 +974,18 @@ static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
 \r
                        SLOT = &ct->CH->SLOT[SLOT1];\r
                        SLOT->vol_ipol = SLOT->vol_out;\r
-                       if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt);\r
+                       if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt, ct->pack & 2);\r
                        SLOT = &ct->CH->SLOT[SLOT2];\r
                        SLOT->vol_ipol = SLOT->vol_out;\r
-                       if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt);\r
+                       if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt, ct->pack & 2);\r
                        SLOT = &ct->CH->SLOT[SLOT3];\r
                        SLOT->vol_ipol = SLOT->vol_out;\r
-                       if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt);\r
+                       if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt, ct->pack & 2);\r
                        SLOT = &ct->CH->SLOT[SLOT4];\r
                        SLOT->vol_ipol = SLOT->vol_out;\r
-                       if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt);\r
+                       if (SLOT->state != EG_OFF) update_eg_phase(SLOT, ct->eg_cnt, ct->pack & 2);\r
                }\r
+\r
 #if 0\r
                UINT32 ifrac0 = ct->eg_timer / (EG_TIMER_OVERFLOW>>EG_SH);\r
                UINT32 ifrac1 = (1<<EG_SH) - ifrac0;\r
@@ -997,6 +1021,7 @@ static void chan_render_loop(chan_rend_context *ct, int *buffer, int length)
                                        ct->CH->SLOT[SLOT3].vol_out) >> 1;\r
                                ct->vol_out4 =  (ct->CH->SLOT[SLOT4].vol_ipol +\r
                                        ct->CH->SLOT[SLOT4].vol_out) >> 1;\r
+                               break;\r
                }\r
 #elif 0\r
                if (ct->eg_timer >> (EG_SH-1) < EG_TIMER_OVERFLOW >> EG_SH) {\r
@@ -1272,7 +1297,7 @@ static int chan_render(int *buffer, int length, int c, UINT32 flags) // flags: s
        crct.mem = crct.CH->mem_value;          /* one sample delay memory */\r
        crct.lfo_cnt = ym2612.OPN.lfo_cnt;\r
 \r
-       flags &= 0x35;\r
+       flags &= 0x37;\r
 \r
        if (crct.lfo_inc) {\r
                flags |= 8;\r
@@ -1453,6 +1478,7 @@ static void reset_channels(FM_CH *CH)
                CH[c].mem_value = CH[c].op1_out = 0;\r
        }\r
        ym2612.slot_mask = 0;\r
+       ym2612.ssg_mask = 0;\r
 }\r
 \r
 /* initialize generic tables */\r
@@ -1655,8 +1681,10 @@ static int OPNWriteReg(int r, int v)
        case 0x90:      /* SSG-EG */\r
                SLOT->ssg =  v&0x0f;\r
                SLOT->ssg ^= SLOT->ssgn;\r
-               if (SLOT->state > EG_REL)\r
-                       recalc_volout(SLOT);\r
+               if (v&0x08) ym2612.ssg_mask |=   1<<(OPN_SLOT(r) + c*4);\r
+               else        ym2612.ssg_mask &= ~(1<<(OPN_SLOT(r) + c*4));\r
+//             if (SLOT->state > EG_REL)\r
+//                     recalc_volout(SLOT);\r
                break;\r
 \r
        case 0xa0:\r
@@ -1751,6 +1779,7 @@ int YM2612UpdateOne_(int *buffer, int length, int stereo, int is_buf_empty)
 {\r
        int pan;\r
        int active_chs = 0;\r
+       int flags = stereo ? 1:0;\r
 \r
        // if !is_buf_empty, it means it has valid samples to mix with, else it may contain trash\r
        if (is_buf_empty) memset32(buffer, 0, length<<stereo);\r
@@ -1786,17 +1815,24 @@ int YM2612UpdateOne_(int *buffer, int length, int stereo, int is_buf_empty)
        refresh_fc_eg_chan( &ym2612.CH[5] );\r
 \r
        pan = ym2612.OPN.pan;\r
-       if (stereo) stereo = 1;\r
 \r
        /* mix to 32bit dest */\r
-       // flags: stereo, ?, disabled, ?, pan_r, pan_l\r
+       // flags: stereo, ssg_enabled, disabled, _, pan_r, pan_l\r
        chan_render_prep();\r
-       if (ym2612.slot_mask & 0x00000f) active_chs |= chan_render(buffer, length, 0, stereo|((pan&0x003)<<4)) << 0;\r
-       if (ym2612.slot_mask & 0x0000f0) active_chs |= chan_render(buffer, length, 1, stereo|((pan&0x00c)<<2)) << 1;\r
-       if (ym2612.slot_mask & 0x000f00) active_chs |= chan_render(buffer, length, 2, stereo|((pan&0x030)   )) << 2;\r
-       if (ym2612.slot_mask & 0x00f000) active_chs |= chan_render(buffer, length, 3, stereo|((pan&0x0c0)>>2)) << 3;\r
-       if (ym2612.slot_mask & 0x0f0000) active_chs |= chan_render(buffer, length, 4, stereo|((pan&0x300)>>4)) << 4;\r
-       if (ym2612.slot_mask & 0xf00000) active_chs |= chan_render(buffer, length, 5, stereo|((pan&0xc00)>>6)|(ym2612.dacen<<2)) << 5;\r
+#define        BIT_IF(v,b,c)   { v &= ~(1<<(b)); if (c) v |= 1<<(b); }\r
+       BIT_IF(flags, 1, (ym2612.ssg_mask & 0x00000f));\r
+       if (ym2612.slot_mask & 0x00000f) active_chs |= chan_render(buffer, length, 0, flags|((pan&0x003)<<4)) << 0;\r
+       BIT_IF(flags, 1, (ym2612.ssg_mask & 0x0000f0));\r
+       if (ym2612.slot_mask & 0x0000f0) active_chs |= chan_render(buffer, length, 1, flags|((pan&0x00c)<<2)) << 1;\r
+       BIT_IF(flags, 1, (ym2612.ssg_mask & 0x000f00));\r
+       if (ym2612.slot_mask & 0x000f00) active_chs |= chan_render(buffer, length, 2, flags|((pan&0x030)   )) << 2;\r
+       BIT_IF(flags, 1, (ym2612.ssg_mask & 0x00f000));\r
+       if (ym2612.slot_mask & 0x00f000) active_chs |= chan_render(buffer, length, 3, flags|((pan&0x0c0)>>2)) << 3;\r
+       BIT_IF(flags, 1, (ym2612.ssg_mask & 0x0f0000));\r
+       if (ym2612.slot_mask & 0x0f0000) active_chs |= chan_render(buffer, length, 4, flags|((pan&0x300)>>4)) << 4;\r
+       BIT_IF(flags, 1, (ym2612.ssg_mask & 0xf00000));\r
+       if (ym2612.slot_mask & 0xf00000) active_chs |= chan_render(buffer, length, 5, flags|((pan&0xc00)>>6)|(!!ym2612.dacen<<2)) << 5;\r
+#undef BIT_IF\r
        chan_render_finish();\r
 \r
        return active_chs; // 1 if buffer updated\r
index 73e693f..b614790 100644 (file)
@@ -153,6 +153,7 @@ typedef struct
        FM_OPN          OPN;                            /* OPN state            */\r
 \r
        UINT32          slot_mask;                      /* active slot mask (performance hack) */\r
+       UINT32          ssg_mask;                       /* active ssg mask (performance hack) */\r
 } YM2612;\r
 #endif\r
 \r
index 1370e6c..0334d1c 100644 (file)
@@ -17,6 +17,7 @@
 
 @ very simple YM2612 output rate to sample rate adaption (~500k cycles @44100)
 #define INTERPOL
+#define SSG_EG
 
 .equiv SLOT1, 0
 .equiv SLOT2, 2
     and     r3, r3, #7           @ eg_inc_val shift, may be 0
     ldrb    r2, [r5,#0x17]       @ state
 
+#if defined(SSG_EG)
     tst     r0, #0x08            @ ssg enabled?
+    tstne   r12, #0x02
     bne     9f
+#endif
 
     @ non-SSG-EG mode
     cmp     r2, #4               @ EG_ATT
     strgeb  r3, [r5,#0x17]       @ state
 
 10: @ finish
+    ldrh    r3, [r5,#0x18]       @ tl
     strh    r0, [r5,#0x1a]       @ volume
+#if defined(SSG_EG)
     b       11f
 
 9:  @ SSG-EG mode
     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
+    addlt   r0, r0, r3, lsl #2
 
     cmp     r2, #2
     blt     1f                   @ EG_REL
     strh    r0, [r5,#0x1a]       @ volume
     cmp     r2, #0x0c            @ if ( ssg&0x04 && state > EG_REL )
     cmpge   r3, #EG_REL+1
+    ldrh    r3, [r5,#0x18]       @ tl
     rsbge   r0, r0, #0x200       @ volume = (0x200-volume) & MAX_ATT
-    lslge   r0, r0, #10
-    lsrge   r0, r0, #10
+    lslge   r0, r0, #22
+    lsrge   r0, r0, #22
 
 11:
-    ldrh    r3, [r5,#0x18]       @ tl
+#endif
     add     r0, r0, r3           @ volume += tl
     strh    r0, [r5,#0x34]       @ vol_out
 
 0: @ EG_OFF
 .endm
 
+#if defined(SSG_EG)
 @ r5=slot, trashes: r0,r2,r3
 .macro update_ssg_eg
     ldrh    r0, [r5,#0x30]                @ ssg+ssgn
     cmp     r2, #EG_REL+1                 @   state > EG_REL &&
     cmpge   r3, #0x200                    @   volume >= 0x200?
     blt     9f
+    orr     r4, r4, #0x10                 @ ssg_update
 
     tst     r0, #0x01
     beq     1f
 9:
 .endm
 
+@ r5=slot, trashes: r0,r2,r3
+.macro recalc_volout
+#if defined(INTERPOL)
+    ldrh    r0, [r5,#0x34]                @ vol_out
+#endif
+    ldrb    r2, [r5,#0x30]                @ ssg
+    ldrb    r3, [r5,#0x17]                @ state
+#if defined(INTERPOL)
+    strh    r0, [r5,#0x36]                @ vol_ipol
+#endif
+    ldrh    r0, [r5,#0x1a]                @ volume
+
+@    and     r2, r2, #0x0c
+    cmp     r2, #0x0c                     @ if ( ~ssg&0x0c && state > EG_REL )
+    cmpge   r3, #EG_REL+1
+    ldrh    r3, [r5,#0x18]                @ tl
+    rsbge   r0, r0, #0x200                @ volume = (0x200-volume) & MAX_ATT
+    lslge   r0, r0, #22
+    lsrge   r0, r0, #22
+    ldrh    r0, [r5,#0x1a]                @ volume
+    ldrh    r3, [r5,#0x18]                @ tl
+
+    add     r0, r0, r3                    @ volume += tl
+    strh    r0, [r5,#0x34]                @ vol_out
+.endm
+#endif
+
 @ r12=lfo_ampm[31:16], r1=lfo_cnt_old, r2=lfo_cnt, r3=scratch
 .macro advance_lfo_m
     mov     r2, r2, lsr #LFO_SH
 .endm
 
 
-@ lr=context, r12=pack (stereo, lastchan, disabled, lfo_enabled | pan_r, pan_l, ams[2] | AMmasks[4] | FB[4] | lfo_ampm[16])
+@ lr=context, r12=pack (stereo, ssg_enabled, disabled, lfo_enabled | pan_r, pan_l, ams[2] | AMmasks[4] | FB[4] | lfo_ampm[16])
 @ r0-r2=scratch, r3=sin_tab, r5=scratch, r6-r7=vol_out[4], r10=op1_out
 .macro upd_algo0_m
 
 .endm
 
 
-@ 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,
+@ lr=context, r12=pack (stereo, ssg_enabled, 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[3],ssg_update,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
 .global chan_render_loop @ chan_rend_context *ct, int *buffer, int length
 
@@ -683,10 +719,17 @@ crl_loop:
     subs    r4, r4, #0x100
     bmi     crl_loop_end
 
-    @ -- SSG --
     ldr     r5, [lr, #0x40]      @ CH
+#if defined(SSG_EG)
+    tst     r12, #0x02              @ ssg_enabled?
+    beq     ssg_done
+    @ -- SSG --
+    lsl     r7, r8, #EG_SH
+    add     r7, r9, r7, lsr #EG_SH
+    subs    r7, r7, #1<<EG_SH
+    blt     ssg_done
 
-    @ r5=slot, trashes: r0,r2,r3
+ssg_loop:
     mov     r6, #4
 ssg_upd_loop:
     update_ssg_eg
@@ -702,10 +745,15 @@ ssg_upd_loop:
     bne     ssg_upd_loop
     sub     r5, r5, #SLOT_STRUCT_SIZE*3
 
+    subs    r7, r7, #1<<EG_SH
+    bge     ssg_loop
+ssg_done:
+#endif
+
     @ -- EG --
     add     r8, r8, r9
     cmp     r8, #EG_TIMER_OVERFLOW
-    bcc     eg_done
+    bcc     volout_upd
     ldr     r1, [lr, #0x3c]     @ eg_cnt
 eg_loop:
     sub     r8, r8, #EG_TIMER_OVERFLOW
@@ -731,9 +779,31 @@ eg_upd_loop:
     sub     r5, r5, #SLOT_STRUCT_SIZE*3
     bhs     eg_loop
     str     r1, [lr, #0x3c]
+    b       eg_done
 
-eg_done:
+volout_upd:
+#if defined(SSG_EG)
+    tst     r4, #0x10               @ ssg_update?
+    beq     eg_done
+
+    @ recalc vol_out
+    mov     r6, #4
+volout_loop:
+    recalc_volout
+#if 0
+    subs    r6, r6, #1
+    addne   r5, r5, #SLOT_STRUCT_SIZE
+#else
+    add     r5, r5, #SLOT_STRUCT_SIZE*2
+    recalc_volout
+    subs    r6, r6, #2
+    subne   r5, r5, #SLOT_STRUCT_SIZE
+#endif
+    bne     volout_loop
+    sub     r5, r5, #SLOT_STRUCT_SIZE*3
+#endif
 
+eg_done:
     @ -- disabled? --
     and     r0, r12, #0xC
     cmp     r0, #0xC
@@ -789,7 +859,7 @@ eg_done:
     @ -- SLOT1 --
     PIC_LDR(r3, r2, ym_tl_tab)
 
-    @ lr=context, r12=pack (stereo, lastchan, disabled, lfo_enabled | pan_r, pan_l, ams[2] | AMmasks[4] | FB[4] | lfo_ampm[16])
+    @ lr=context, r12=pack (stereo, ssg_enabled, disabled, lfo_enabled | pan_r, pan_l, ams[2] | AMmasks[4] | FB[4] | lfo_ampm[16])
     @ r0-r2=scratch, r3=tl_tab, r5=scratch, r6-r7=vol_out[4], r10=op1_out
     upd_slot1_m