Skip to content

Commit cd27a80

Browse files
committed
nullsound: arpeggio FX
1 parent 827a756 commit cd27a80

File tree

11 files changed

+334
-23
lines changed

11 files changed

+334
-23
lines changed

nullsound/Makefile.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ all: nullsound.lib linkcheck.map
1919
-include ../Makefile.config
2020

2121
INCLUDE_FILES=helpers ports ym2610
22-
OBJS=entrypoint memory bios-commands adpcm ym2610 stream timer utils nss-fm nss-ssg nss-adpcm-a nss-adpcm-b fx-vibrato fx-slide fx-vol-slide fx-trigger volume
22+
OBJS=entrypoint memory bios-commands adpcm ym2610 stream timer utils nss-fm nss-ssg nss-adpcm-a nss-adpcm-b fx-vibrato fx-slide fx-vol-slide fx-trigger fx-arpeggio volume
2323
LIB=nullsound.lib
2424

2525
VERSION=@version@

nullsound/doc/arpeggio.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Arpeggio FX semantics
2+
3+
## arpeggio (00xx)
4+
5+
Play a 3-note chord in succession, each note separated by a fixed number of ticks
6+
7+
- The first note of the chord is the currently configured note
8+
- FX specifies the 2nd and 3rd note to play, as a positive semitone offset
9+
- arpeggio stops when explicitely disabled (01..)
10+
- By default, there are two ticks between two notes
11+
- As soon as the FX is applied, it adds to the current note's decimal position
12+
- When changing current note when slide is in progress, the chord resets to the first note
13+
- When an arpeggio in the middle of the chord, the change is effective directly after the configured amount of ticks is reached to play the next note.
14+
15+
```
16+
E-40A..0037
17+
...........
18+
D-40A......
19+
```
20+
21+
## arpeggio speed (e0xx)
22+
23+
Set the number of ticks between two note of the chord
24+
25+
- minimum speed is 1
26+
27+
ex: portamento from C5 to G-5
28+
29+
```
30+
.......E004
31+
G-50A..0037
32+
...........
33+
...........
34+
```

nullsound/fx-arpeggio.s

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
;;;
2+
;;; nullsound - modular sound driver
3+
;;; Copyright (c) 2025 Damien Ciabrini
4+
;;; This file is part of ngdevkit
5+
;;;
6+
;;; ngdevkit is free software: you can redistribute it and/or modify
7+
;;; it under the terms of the GNU Lesser General Public License as
8+
;;; published by the Free Software Foundation, either version 3 of the
9+
;;; License, or (at your option) any later version.
10+
;;;
11+
;;; ngdevkit is distributed in the hope that it will be useful,
12+
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
;;; GNU Lesser General Public License for more details.
15+
;;;
16+
;;; You should have received a copy of the GNU Lesser General Public License
17+
;;; along with ngdevkit. If not, see <http://www.gnu.org/licenses/>.
18+
19+
;;; Trigger effect (delay, cut...), common functions for FM and SSG
20+
;;;
21+
22+
.module nullsound
23+
24+
.include "ym2610.inc"
25+
.include "struct-fx.inc"
26+
.include "pipeline.inc"
27+
28+
29+
.area CODE
30+
31+
32+
;;; ARPEGGIO
33+
;;; Enable arpeggio chord for note playback
34+
;;; ------
35+
;;; ix : state for channel
36+
;;; [ hl ]: semitone:4 - semitone:4
37+
;;; hl modified
38+
arpeggio::
39+
;; check whether this disables FX
40+
ld a, (hl)
41+
cp #0
42+
jr nz, _arpeggio_init
43+
inc hl
44+
res BIT_FX_ARPEGGIO, FX(ix)
45+
;; To fully clear the state after the FX is disabled, we must remember
46+
;; to recompute the note and tune values without shift, so force it here
47+
ld ARPEGGIO_POS8(ix), a
48+
set BIT_LOAD_NOTE, PIPELINE(ix)
49+
ld a, #1
50+
ret
51+
_arpeggio_init:
52+
;; 2nd note in chord
53+
ld a, (hl)
54+
rra
55+
rra
56+
rra
57+
rra
58+
and #0xf
59+
ld ARPEGGIO_2ND(ix), a
60+
61+
;; 3rd note in chord
62+
ld a, (hl)
63+
and #0xf
64+
ld ARPEGGIO_3RD(ix), a
65+
66+
inc hl
67+
68+
;; init arpeggio state if it is not running already
69+
bit BIT_FX_ARPEGGIO, FX(ix)
70+
jr nz, _arpeggio_end
71+
ld a, ARPEGGIO_SPEED(ix)
72+
ld ARPEGGIO_COUNT(ix), a
73+
xor a
74+
ld ARPEGGIO_POS(ix), a
75+
ld ARPEGGIO_POS8(ix), a
76+
77+
set BIT_FX_ARPEGGIO, FX(ix)
78+
79+
_arpeggio_end:
80+
ld a, #1
81+
ret
82+
83+
84+
;;; ARPEGGIO_SPEED
85+
;;; configure the number of ticks between two notes of the chord
86+
;;; ------
87+
;;; ix : state for channel
88+
;;; [ hl ]: speed
89+
;;; hl modified
90+
arpeggio_speed::
91+
ld a, (hl)
92+
inc hl
93+
94+
ld ARPEGGIO_SPEED(ix), a
95+
96+
ld a, #1
97+
98+
ret
99+
100+
101+
;;; Update the arpeggio state for the current channel
102+
;;; ------
103+
;;; ix: mirrored state of the current channel
104+
;;; [TODO modified]
105+
eval_arpeggio_step::
106+
;; assert: speed is always >=1, so count is never 0 here
107+
dec ARPEGGIO_COUNT(ix)
108+
ld a, ARPEGGIO_COUNT(ix)
109+
cp #0
110+
jr z, _arpeggio_update_pos
111+
ret
112+
_arpeggio_update_pos:
113+
;; rearm countdown
114+
ld a, ARPEGGIO_SPEED(ix)
115+
ld ARPEGGIO_COUNT(ix), a
116+
117+
;; update position in the arpeggio
118+
ld a, ARPEGGIO_POS(ix)
119+
dec a
120+
jp p, _arpeggio_post_pos
121+
add #3
122+
_arpeggio_post_pos:
123+
ld ARPEGGIO_POS(ix), a
124+
125+
;; set semitone offset according to position
126+
cp #2
127+
jr nz, _arpeggio_not2
128+
;; pos == 2: 2nd note in chord
129+
ld a, ARPEGGIO_2ND(ix)
130+
ld ARPEGGIO_POS8(ix), a
131+
jr _arpeggio_eval_end
132+
_arpeggio_not2:
133+
cp #1
134+
jr nz, _arpeggio_not1
135+
;; pos == 2: 3rd note in chord
136+
ld a, ARPEGGIO_3RD(ix)
137+
ld ARPEGGIO_POS8(ix), a
138+
jr _arpeggio_eval_end
139+
_arpeggio_not1:
140+
;; 1st note in chord (0 displacement)
141+
ld ARPEGGIO_POS8(ix), #0
142+
143+
_arpeggio_eval_end:
144+
ret

nullsound/nss-adpcm-a.s

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
;;; ------
5757
;; This padding ensures the entire _state_ssg data sticks into
5858
;; a single 256 byte boundary to make 16bit arithmetic faster
59-
.blkb 42
59+
.blkb 82
6060

6161
_state_adpcm_start:
6262

nullsound/nss-adpcm-b.s

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,12 @@
6262

6363
;; FIXME: temporary padding to ensures the next data sticks into
6464
;; a single 256 byte boundary to make 16bit arithmetic faster
65-
.blkb 16
65+
.blkb 26
6666

6767

6868
;;; ADPCM playback state tracker
6969
;;; ------
70+
7071
_state_adpcm_b_start:
7172

7273
;;; ADPCM-B mirrored state
@@ -78,6 +79,7 @@ state_b_trigger: .blkb TRIGGER_SIZE
7879
state_b_fx_vol_slide: .blkb VOL_SLIDE_SIZE
7980
state_b_fx_slide: .blkb SLIDE_SIZE
8081
state_b_fx_vibrato: .blkb VIBRATO_SIZE
82+
state_b_fx_arpeggio: .blkb ARPEGGIO_SIZE
8183
;;; ADPCM-B-specific state
8284
;;; Note
8385
state_b_note:
@@ -128,12 +130,10 @@ init_nss_adpcm_b_state_tracker::
128130
ldir
129131
;; init flags
130132
ld ix, #state_b
131-
ld a, #0x80 ; start flag
132-
ld START_CMD(ix), a
133-
ld a, #0xff ; default volume
134-
ld VOL(ix), a
135-
ld a, #0xff ; default non-existing instrument
136-
ld INSTR(ix), a
133+
ld START_CMD(ix), #0x80 ; default ADPCM-B start flag
134+
ld VOL(ix), #0xff ; default volume
135+
ld INSTR(ix), #0xff ; default non-existing instrument
136+
ld ARPEGGIO_SPEED(ix), #1 ; default arpeggio speed
137137

138138
;; global ADPCM volumes are initialized in the volume state tracker
139139
ret
@@ -170,6 +170,11 @@ _b_post_fx_trigger:
170170
call eval_b_vibrato_step
171171
set BIT_LOAD_NOTE, PIPELINE(ix)
172172
_b_post_fx_vibrato:
173+
bit BIT_FX_ARPEGGIO, FX(ix)
174+
jr z, _b_post_fx_arpeggio
175+
call eval_arpeggio_step
176+
set BIT_LOAD_NOTE, PIPELINE(ix)
177+
_b_post_fx_arpeggio:
173178
bit BIT_FX_SLIDE, FX(ix)
174179
jr z, _b_post_fx_slide
175180
ld hl, #NOTE_SEMITONE
@@ -523,6 +528,11 @@ compute_adpcm_b_fixed_point_note::
523528
ld h, SLIDE_POS16+1(ix)
524529
_b_post_add_slide::
525530

531+
;; hl: arpeggiated semitone
532+
ld c, #0
533+
ld b, ARPEGGIO_POS8(ix)
534+
add hl, bc
535+
526536
;; bc vibrato offset if the vibrato FX is enabled
527537
bit BIT_FX_VIBRATO, FX(ix)
528538
jr z, _b_post_add_vibrato

nullsound/nss-fm.s

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ state_fm_f_num_half_distance::
9494
.blkw 1
9595

9696
;;; context: current fm channel for opcode actions
97+
98+
;; This padding ensures the entire _state_ssg data sticks into
99+
;; a single 256 byte boundary to make 16bit arithmetic faster
100+
.blkb 0xbc
101+
102+
97103
_state_fm_start:
98104
state_fm_channel::
99105
.blkb 1
@@ -111,6 +117,7 @@ state_fm_trigger: .blkb TRIGGER_SIZE
111117
state_fm_fx_vol_slide: .blkb VOL_SLIDE_SIZE
112118
state_fm_fx_slide: .blkb SLIDE_SIZE
113119
state_fm_fx_vibrato: .blkb VIBRATO_SIZE
120+
state_fm_fx_arpeggio: .blkb ARPEGGIO_SIZE
114121
;;; FM-specific state
115122
;;; Note
116123
state_fm_note:
@@ -167,7 +174,7 @@ state_fm_action_funcs:
167174
;;; Reset FM playback state.
168175
;;; Called before playing a stream
169176
;;; ------
170-
;;; [a modified - other registers saved]
177+
;;; bc, de, hl, iy modified
171178
init_nss_fm_state_tracker::
172179
ld hl, #_state_fm_start
173180
ld d, h
@@ -181,17 +188,16 @@ init_nss_fm_state_tracker::
181188
ld (hl), #0xc0
182189
ld bc, #3
183190
ldir
184-
;; init instr to a non-existing instr (0xff)
185-
ld a, #0xff
186-
ld hl, #(state_fm+INSTRUMENT)
191+
;; init non-zero default values
192+
ld d, #4
193+
ld iy, #state_fm
187194
ld bc, #FM_STATE_SIZE
188-
ld (hl), a
189-
add hl, bc
190-
ld (hl), a
191-
add hl, bc
192-
ld (hl), a
193-
add hl, bc
194-
ld (hl), a
195+
_fm_init:
196+
ld INSTRUMENT(iy), #0xff ; non-existing instrument
197+
ld ARPEGGIO_SPEED(iy), #1 ; default arpeggio speed
198+
add iy, bc
199+
dec d
200+
jr nz, _fm_init
195201
;; global FM volume is initialized in the volume state tracker
196202
;; init YM2610 function pointer
197203
ld a, #0xc3 ; jp 0x....
@@ -428,6 +434,11 @@ _fm_post_add_slide::
428434
ld b, DETUNE+1(ix)
429435
add hl, bc
430436

437+
;; hl: arpeggiated semitone
438+
ld c, #0
439+
ld b, ARPEGGIO_POS8(ix)
440+
add hl, bc
441+
431442
;; bc vibrato offset if the vibrato FX is enabled
432443
bit BIT_FX_VIBRATO, FX(ix)
433444
jr z, _fm_post_add_vibrato
@@ -653,6 +664,11 @@ _fm_post_fx_trigger:
653664
call eval_fm_vibrato_step
654665
set BIT_LOAD_NOTE, PIPELINE(ix)
655666
_fm_post_fx_vibrato:
667+
bit BIT_FX_ARPEGGIO, FX(ix)
668+
jr z, _fm_post_fx_arpeggio
669+
call eval_arpeggio_step
670+
set BIT_LOAD_NOTE, PIPELINE(ix)
671+
_fm_post_fx_arpeggio:
656672
bit BIT_FX_SLIDE, FX(ix)
657673
jr z, _fm_post_fx_slide
658674
ld hl, #NOTE_SEMITONE

0 commit comments

Comments
 (0)