diff --git a/nullsound/nss-adpcm-a.s b/nullsound/nss-adpcm-a.s index fb14c89..9ffa4c5 100644 --- a/nullsound/nss-adpcm-a.s +++ b/nullsound/nss-adpcm-a.s @@ -30,6 +30,7 @@ ;; getters for ADPCM-A state .lclequ VOL, (state_a_vol-state_a) .lclequ OUT_VOL, (state_a_out_vol-state_a) + .lclequ PAN, (state_a_pan-state_a) .equ NSS_ADPCM_A_INSTRUMENT_PROPS, 4 .equ NSS_ADPCM_A_NEXT_REGISTER, 8 @@ -71,6 +72,8 @@ state_a_trigger: .blkb TRIGGER_SIZE ;;; volume state_a_vol: .blkb 1 ; configured note volume (attenuation) state_a_out_vol: .blkb 1 ; ym2610 volume after the FX pipeline +;;; pan +state_a_pan: .blkb 1 ; configured pan (b7: left, b6: right) ;;; state_a_end: ;;; ADPCM-A2 @@ -128,10 +131,15 @@ init_nss_adpcm_state_tracker:: ld (state_adpcm_a_channel), a ;; set default ld ix, #state_a1 + ld bc, #ADPCM_A_STATE_SIZE ld d, #6 _a_init: ld a, #0x1f ld VOL(ix), a + ld a, #0xc0 + ld PAN(ix), a + + add ix, bc dec d jr nz, _a_init ;; global ADPCM volumes are initialized in the volume state tracker @@ -228,17 +236,17 @@ _post_load_a_vol: ;; Pipeline action: load pan+volume register when it is modified ld a, PIPELINE(ix) - or a, #(STATE_LOAD_VOL|STATE_LOAD_PAN) + and a, #(STATE_LOAD_VOL|STATE_LOAD_PAN) jr z, _post_load_a_pan_vol res BIT_LOAD_VOL, PIPELINE(ix) res BIT_LOAD_PAN, PIPELINE(ix) - ;; c: volume + default pan (L/R) + ;; c: volume + pan ld a, OUT_VOL(ix) - or #0xc0 + add PAN(ix) ld c, a - ;; set pan+volume for channel in the YM2610 + ;; set volume + pan for channel in the YM2610 ;; b: ADPCM-A channel ld a, (state_adpcm_a_channel) add a, #REG_ADPCM_A1_PAN_VOLUME @@ -620,3 +628,17 @@ adpcm_a_retrigger:: ld a, #1 ret + + +;;; ADPCM_A_PAN +;;; Set the pan (l/r) for the current ADPCM-A channel +;;; ------ +;;; [ hl ]: pan (b7: left, b6: right) +adpcm_a_pan:: + ld a, (hl) + inc hl + ld PAN(ix), a + set BIT_LOAD_PAN, PIPELINE(ix) + + ld a, #1 + ret diff --git a/nullsound/nss-adpcm-b.s b/nullsound/nss-adpcm-b.s index 8ab316a..ede2e45 100644 --- a/nullsound/nss-adpcm-b.s +++ b/nullsound/nss-adpcm-b.s @@ -34,6 +34,7 @@ .lclequ DELTA_N, (state_b_note_delta_n-state_b) .lclequ VOL, (state_b_vol-state_b) .lclequ OUT_VOL, (state_b_out_vol-state_b) + .lclequ PAN, (state_b_pan-state_b) .lclequ INSTR, (state_b_instr-state_b) .lclequ START_CMD, (state_b_instr_start_cmd-state_b) @@ -85,6 +86,8 @@ state_b_instr_start_cmd: .blkb 1 ; instrument play command (with ;;; volume state_b_vol: .blkb 1 ; configured note volume (attenuation) state_b_out_vol: .blkb 1 ; ym2610 volume after the FX pipeline +;;; pan +state_b_pan: .blkb 1 ; configured pan (b7: left, b6: right) ;;; state_b_end: @@ -176,9 +179,18 @@ _b_post_check_playing: ld b, #REG_ADPCM_B_VOLUME ld c, OUT_VOL(ix) call ym2610_write_port_a - + res BIT_LOAD_VOL, PIPELINE(ix) _post_load_b_vol: + ;; Pipeline action: load pan if requested + bit BIT_LOAD_PAN, PIPELINE(ix) + jr z, _post_load_b_pan + ld b, #REG_ADPCM_B_PAN + ld c, PAN(ix) + call ym2610_write_port_a + res BIT_LOAD_PAN, PIPELINE(ix) +_post_load_b_pan: + ;; Pipeline action: load note register when the note state is modified bit BIT_LOAD_NOTE, PIPELINE(ix) jr z, _post_load_b_note @@ -779,3 +791,17 @@ adpcm_b_cut:: ld a, #1 ret + + +;;; ADPCM_B_PAN +;;; Set the pan (l/r) for the channel +;;; ------ +;;; [ hl ]: pan (b7: left, b6: right) +adpcm_b_pan:: + ld a, (hl) + inc hl + ld PAN(ix), a + set BIT_LOAD_PAN, PIPELINE(ix) + + ld a, #1 + ret diff --git a/nullsound/stream.s b/nullsound/stream.s index b8d298c..4c551f8 100644 --- a/nullsound/stream.s +++ b/nullsound/stream.s @@ -530,6 +530,8 @@ nss_opcodes: .nss_op adpcm_b_cut .nss_op adpcm_b_delay .nss_op adpcm_a_retrigger + .nss_op adpcm_a_pan + .nss_op adpcm_b_pan diff --git a/tools/nsstool.py b/tools/nsstool.py index 6cc95a1..06351fa 100755 --- a/tools/nsstool.py +++ b/tools/nsstool.py @@ -289,6 +289,8 @@ def register_nss_ops(): ("b_cut", ["delay"]), ("b_delay", ["delay"]), ("a_retrigger", ["delay"]), + ("a_pan", ["pan_mask"]), + ("b_pan", ["pan_mask"]), # reserved opcodes ("nss_label", ["pat"]) ) @@ -301,6 +303,15 @@ def register_nss_ops(): globals()[cname]=make_dataclass(cname, fields) +def convert_pan(fx, fxval): + if fx == 0x08: # pan + pan_l = 0x80 if (fxval & 0xf0) else 0 + pan_r = 0x40 if (fxval & 0x0f) else 0 + elif fx == 0x80: # old pan + pan_l = 0x80 if fxval in [0x00, 0x80] else 0 + pan_r = 0x40 if fxval in [0x80, 0xff] else 0 + return pan_l|pan_r; + # # Furnace module conversion functions @@ -317,15 +328,7 @@ def convert_fm_row(row, channel): opcodes.append(fm_vol(row.vol)) # pre-instrument effects for fx, fxval in row.fx: - if fx == 0x08: # pan - pan_l = 0x80 if (fxval & 0xf0) else 0 - pan_r = 0x40 if (fxval & 0x0f) else 0 - opcodes.append(fm_pan(pan_l|pan_r)) - elif fx == 0x80: # old pan - pan_l = 0x80 if fxval in [0x00, 0x80] else 0 - pan_r = 0x40 if fxval in [0x80, 0xff] else 0 - opcodes.append(fm_pan(pan_l|pan_r)) - elif fx == 0xed: # note delay + if fx == 0xed: # note delay opcodes.append(fm_delay(fxval)) # instrument if row.ins != -1: @@ -382,6 +385,9 @@ def convert_fm_row(row, channel): opcodes.append(fm_porta(fxval)) elif fx == 0xec: # cut opcodes.append(fm_cut(fxval)) + elif fx in [0x08, 0x80]: # panning + pan_mask = convert_pan(fx, fxval) + opcodes.append(fm_pan(pan_mask)) else: add_unknown_fx('FM', fx) @@ -521,6 +527,9 @@ def convert_a_row(row, channel): opcodes.append(groove(fxval)) elif fx == 0xec: # cut opcodes.append(a_cut(fxval)) + elif fx in [0x08, 0x80]: # panning + pan_mask = convert_pan(fx, fxval) + opcodes.append(a_pan(pan_mask)) else: add_unknown_fx('ADPCM-A', fx) @@ -571,6 +580,9 @@ def convert_b_row(row, channel): opcodes.append(b_porta(fxval)) elif fx == 0xec: # cut opcodes.append(b_cut(fxval)) + elif fx in [0x08, 0x80]: # panning + pan_mask = convert_pan(fx, fxval) + opcodes.append(b_pan(pan_mask)) else: add_unknown_fx('ADPCM-B', fx)