Skip to content

Commit 1923d8e

Browse files
committed
Initial support for extended FM2 channels
NSS can now encode and playback Furnace modules that make use of YM2610's extended FM2, which allows four independent notes (one note per operator) on channel FM2.
1 parent c391be8 commit 1923d8e

File tree

13 files changed

+2084
-815
lines changed

13 files changed

+2084
-815
lines changed

nullsound/align.inc

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,27 @@
1919
;;; Constants for linking data with desired RAM alignment
2020
;;;
2121

22-
;; Those constants ensure that the entire state of a sound channel
23-
;; never crosses a single 256 bytes boundary, which eases additions
24-
;; and indexing.
25-
;; the .bndry statement is only local to a single file, i.e. it is
26-
;; only enforced by the assembler, not by the linker. So we have
27-
;; to tune the constants below to achieve our desired alignment.
22+
;; These macros ensure that the data between two .align_start
23+
;; and .align_end macro calls stick in a single 256 bytes
24+
;; boundary, to make 16bit arithmetic faster.
2825

29-
.lclequ ALIGN_OFFSET_FM, 0xbc
30-
.lclequ ALIGN_OFFSET_SSG, 0x4b
31-
.lclequ ALIGN_OFFSET_ADPCM_A, 0x52
32-
.lclequ ALIGN_OFFSET_ADPCM_B, 0x10
26+
.macro .align_begin sym
27+
_'sym'_align: .blkb _padding_'sym
28+
_'sym'_begin:
29+
.endm
30+
31+
.macro .align_end sym
32+
_'sym'_end:
33+
.endm
34+
35+
;; Those constants tweak the alignment of symbols in RAM to match
36+
;; our 256bytes boundaries constraints.
37+
38+
.lclequ _padding_state_timer, 0x0
39+
.lclequ _padding_state_fm1, 0xb5
40+
.lclequ _padding_state_fm2, 0xc6
41+
.lclequ _padding_state_fm3, 0x26
42+
.lclequ _padding_state_fm4, 0x0
43+
.lclequ _padding_state_ssg, 0x8c
44+
.lclequ _padding_state_adpcm, 0x48
45+
.lclequ _padding_state_adpcm_b, 0xc0

nullsound/check-bounds.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
ram={}
55
with open(sys.argv[1],"r") as f:
66
for l in f.readlines():
7-
m = re.match(r' *0000(F...) *(_state[^ ]*) *nullsound', l)
7+
m = re.match(r' *0000(F...) *(_state[^ ]*_(begin|end)) *nullsound', l)
88
if m:
99
ram[m.group(2)] = m.group(1)
10-
range_keys=list(set([re.sub(r"_(start|end)", "", x) for x in ram]))
10+
range_keys=list(dict.fromkeys([re.sub(r"_(begin|end)", "", x) for x in ram]))
1111
for k in range_keys:
12-
start, end = ram[k+"_start"], ram[k+"_end"]
12+
start, end = ram[k+"_begin"], ram[k+"_end"]
13+
# print(k, start, end)
1314
if start[0:2] != end[0:2]:
1415
sys.exit("range \"%s\" crosses 256 bytes boundaries: [%s, %s]"%(k, start, end))

nullsound/fx-legato.s

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ quick_legato_down::
132132
;;; hl modified
133133
legato::
134134
set BIT_FX_LEGATO, NOTE_FX(ix)
135+
ld a, #1
135136
ret
136137

137138

@@ -142,4 +143,5 @@ legato::
142143
;;; hl modified
143144
legato_off::
144145
res BIT_FX_LEGATO, NOTE_FX(ix)
146+
ld a, #1
145147
ret

nullsound/nss-adpcm-a.s

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,44 +31,46 @@
3131
.lclequ ADPCM_A_MAX_VOL,0x1f
3232

3333
;; getters for ADPCM-A state
34+
.lclequ INSTR_VOL, (state_a_instr_vol-state_a)
3435
.lclequ OUT_VOL, (state_a_out_vol-state_a)
3536
.lclequ PAN, (state_a_pan-state_a)
3637

3738
.equ NSS_ADPCM_A_INSTRUMENT_PROPS, 4
3839
.equ NSS_ADPCM_A_NEXT_REGISTER, 8
3940

40-
;; specific pipeline state for SSG channel
41+
;; specific pipeline state for ADPCM-A channel
4142
.lclequ STATE_START_NOTE, 0x04
4243
.lclequ BIT_START_NOTE, 2
4344

4445

45-
4646
.area DATA
4747

4848

4949
;;; ADPCM playback state tracker
5050
;;; ------
51-
;; This padding ensures the entire _state_ssg data sticks into
52-
;; a single 256 byte boundary to make 16bit arithmetic faster
53-
.blkb ALIGN_OFFSET_ADPCM_A
5451

55-
_state_adpcm_start:
52+
.align_begin state_adpcm
53+
;;; { ...
5654

5755
;;; ADPCM-A1
5856
state_a1:
59-
;;; state
57+
;;; { ...
6058
state_a_start:
61-
;;; stream pipeline
62-
state_a:
63-
state_a_pipeline: .blkb 1 ; actions to run at every tick (load note, vol, other regs)
64-
state_a_fx: .blkb 1 ; enabled FX for this channel
59+
6560
;;; volume state tracker
6661
state_a_vol_cfg: .blkb 1 ; configured volume
6762
state_a_vol16: .blkb 2 ; current decimal volume
6863
;;; FX state trackers
64+
state_a_fx: .blkb 1 ; enabled FX for this channel
6965
state_a_fx_vol_slide: .blkb SLIDE_SIZE
7066
state_a_fx_trigger: .blkb TRIGGER_SIZE
67+
68+
;;; actions to run at the end of every tick
69+
state_a:
70+
state_a_pipeline: .blkb 1 ; action: load note, load vol, load other regs
71+
7172
;;; ADPCM-A-specific state
73+
state_a_instr_vol: .blkb 1 ; instrument volume
7274
state_a_out_vol: .blkb 1 ; ym2610 volume after the FX pipeline
7375
;;; pan
7476
state_a_pan: .blkb 1 ; configured pan (b7: left, b6: right)
@@ -89,18 +91,21 @@ state_a5:
8991
;;; ADPCM-A6
9092
state_a6:
9193
.blkb ADPCM_A_STATE_SIZE
94+
95+
;;; ... }
9296
state_a6_end:
9397

98+
;;; ... }
99+
.align_end state_adpcm
100+
94101
;;; context: current adpcm channel for opcode actions
95102
state_adpcm_a_channel::
96103
.blkb 1
97104

98-
99105
;;; Global volume attenuation for all ADPCM-A channels
100106
state_adpcm_a_volume_attenuation:: .blkb 1
101107

102108

103-
_state_adpcm_end:
104109

105110

106111

@@ -128,7 +133,7 @@ init_nss_adpcm_state_tracker::
128133
ld bc, #(state_a6_end-1-state_a_start)
129134
ldir
130135
;; init flags
131-
ld iy, #state_a1
136+
ld iy, #state_a_pipeline
132137
ld bc, #ADPCM_A_STATE_SIZE
133138
ld d, #6
134139
_a_init:
@@ -417,9 +422,9 @@ _adpcm_a_loop:
417422
dec d
418423
jp nz, _adpcm_a_loop
419424

420-
;; d: ADPCM-A channel
421-
ld a, (state_adpcm_a_channel)
422-
ld d, a
425+
;; instrument volume
426+
ld a, (hl)
427+
ld INSTR_VOL(ix), a
423428

424429
pop de
425430
pop hl
@@ -583,11 +588,21 @@ _adpcm_a_clamp_level:
583588

584589
;;; Compute the YM2610 output volume from the current channel
585590
;;; ------
591+
;;; IN:
592+
;;; ix: FM pipeline
593+
;;; iy: Volume FX
586594
;;; modified: c
587595
compute_ym2610_a_vol::
588-
;; c: note vol for current channel
589-
ld c, VOL16+1(ix)
590-
596+
;; c: instrument attenuation [-0x1f..0]
597+
ld a, #-0x1f
598+
add INSTR_VOL(ix)
599+
ld c, a
600+
601+
;; c: current volume after instrument attenuation [-0x1f..0x1f]
602+
ld a, VOL16+1(ix)
603+
add c
604+
ld c, a
605+
591606
;; attenuation to match the configured ADPCM-A output level
592607
ld a, (state_adpcm_a_volume_attenuation)
593608
neg

nullsound/nss-adpcm-b.s

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,37 +48,38 @@
4848
.area DATA
4949

5050

51-
;; FIXME: temporary padding to ensures the next data sticks into
52-
;; a single 256 byte boundary to make 16bit arithmetic faster
53-
.blkb ALIGN_OFFSET_ADPCM_B
5451

5552

5653
;;; ADPCM playback state tracker
5754
;;; ------
58-
59-
_state_adpcm_b_start:
55+
.align_begin state_adpcm_b
56+
;;; { ...
6057

6158
;;; ADPCM-B mirrored state
62-
;;; state
59+
;;; { ...
6360
state_b_start:
64-
;;; additional note and FX state tracker
65-
state_b_note_fx: .blkb 1 ; enabled note FX for this channel
61+
62+
;;; note state tracker
6663
state_b_note_cfg: .blkb 1 ; configured note
6764
state_b_note16: .blkb 2 ; current decimal note
65+
;;; note FX state tracker
66+
state_b_note_fx: .blkb 1 ; enabled note FX for this channel
6867
state_b_fx_note_slide: .blkb SLIDE_SIZE
6968
state_b_fx_vibrato: .blkb VIBRATO_SIZE
7069
state_b_fx_arpeggio: .blkb ARPEGGIO_SIZE
7170
state_b_fx_legato: .blkb LEGATO_SIZE
72-
;;; stream pipeline
73-
state_b:
74-
state_b_pipeline: .blkb 1 ; actions to run at every tick (load note, vol, other regs)
75-
state_b_fx: .blkb 1 ; enabled FX for this channel
7671
;;; volume state tracker
7772
state_b_vol_cfg: .blkb 1 ; configured volume
7873
state_b_vol16: .blkb 2 ; current decimal volume
7974
;;; FX state trackers
75+
state_b_fx: .blkb 1 ; enabled FX for this channel
8076
state_b_fx_vol_slide: .blkb SLIDE_SIZE
8177
state_b_trigger: .blkb TRIGGER_SIZE
78+
79+
;;; actions to run at the end of every tick
80+
state_b:
81+
state_b_pipeline: .blkb 1 ; action: load note, load vol, load other regs
82+
8283
;;; ADPCM-B-specific state
8384
;;; Note
8485
state_b_note:
@@ -94,16 +95,17 @@ state_b_out_vol: .blkb 1 ; ym2610 volume after the FX pip
9495
;;; pan
9596
state_b_pan: .blkb 1 ; configured pan (b7: left, b6: right)
9697
;;;
98+
99+
;;; ... }
97100
state_b_end:
98101

102+
;;; ... }
103+
.align_end state_adpcm_b
99104

100105
;;; Global volume attenuation for ADPCM-B channel
101106
state_adpcm_b_volume_attenuation:: .blkb 1
102107

103108

104-
_state_adpcm_b_end:
105-
106-
107109

108110
.area CODE
109111

@@ -388,6 +390,7 @@ adpcm_b_instrument::
388390

389391
;; a: start of ADPCM-B property registers
390392
ld a, #REG_ADPCM_B_ADDR_START_LSB
393+
;; TODO remove useless +0?
391394
add b
392395

393396
_adpcm_b_loop:

0 commit comments

Comments
 (0)