Skip to content

Commit fda751e

Browse files
committed
nullsound: groove FX
Updated parsing of Furnace module to read speed pattern (more than one speed). Added groove FX support. This commit doesn't implement groove pattern, the groove FX only works for 2-speeds modules.
1 parent 259e50e commit fda751e

File tree

6 files changed

+186
-20
lines changed

6 files changed

+186
-20
lines changed

nullsound/Makefile.in

+1-1
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 bios-commands adpcm ym2610 stream timer nss-fm nss-adpcm-a nss-ssg nss-adpcm-b fx-vibrato fx-slide fx-vol-slide fx-trigger volume
22+
OBJS=entrypoint bios-commands adpcm ym2610 stream timer nss-fm nss-ssg nss-adpcm-a nss-adpcm-b fx-vibrato fx-slide fx-vol-slide fx-trigger volume
2323
LIB=nullsound.lib
2424

2525
VERSION=@version@

nullsound/nss-adpcm-a.s

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@
5353

5454
;;; ADPCM playback state tracker
5555
;;; ------
56+
;; This padding ensures the entire _state_ssg data sticks into
57+
;; a single 256 byte boundary to make 16bit arithmetic faster
58+
.blkb 42
59+
5660
_state_adpcm_start:
5761

5862
;;; ADPCM-A mirrored state

nullsound/stream.s

+5-1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ update_stream_state_tracker::
230230
call process_streams_opcodes
231231
ld a, #0
232232
ld (state_timer_ticks_count), a
233+
call timer_update_ticks_for_next_row
233234
_check_update_stream_pipeline:
234235
ld a, (state_timer_tick_reached)
235236
bit TIMER_CONSUMER_STREAM_BIT, a
@@ -387,9 +388,12 @@ stream_play_multi::
387388
ld (state_ch_bits), bc
388389
call snd_configure_stream_ctx_switches
389390

390-
;; hl: stream data from NSS
391+
;; setup speed and groove
391392
inc iy
392393
inc iy
394+
call timer_init_ticks
395+
396+
;; hl: stream data from NSS
393397
push iy
394398
pop hl
395399

nullsound/timer.s

+120-8
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,21 @@
3333
;;; the stream player as a reliable time source for synchronization.
3434
;;;
3535
.area DATA
36+
_state_timer_start:
3637

37-
state_timer_tick_reached::
38-
.db 0
38+
;;; ticks
39+
state_timer_ticks_per_row:: .blkb 1 ; total number of ticks for the current row
40+
state_timer_ticks_count:: .blkb 1 ; number of ticks reached for the current row
41+
state_timer_tick_reached:: .blkb 1 ; has a new tick been reached
3942

40-
state_timer_ticks_count::
41-
.db 0
43+
;;; speed and groove
44+
;;; This defines how many ticks to wait for each row during playback
45+
;;; up to 16 different ticks can be configured before cycling back to start
46+
state_timer_tick_pos:: .blkb 1 ; position in groove pattern
47+
state_timer_nb_ticks:: .blkb 1 ; length of the the groove pattern
48+
state_timer_ticks:: .blkb 16 ; current groove pattern
4249

43-
state_timer_ticks_per_row::
44-
.db 0
50+
_state_timer_end:
4551

4652

4753
.area CODE
@@ -86,6 +92,81 @@ update_timer_state_tracker::
8692
ret
8793

8894

95+
;;; Initialize speed1 and speed2 from the stream's config
96+
;;; ------
97+
;;; iy : speed1 and speed2 (if use)
98+
timer_init_ticks::
99+
push bc
100+
push hl
101+
push de
102+
103+
;; speed steps
104+
ld a, (iy)
105+
ld (state_timer_nb_ticks), a
106+
;; copy steps
107+
ld b, #0
108+
ld c, a
109+
inc iy
110+
push iy
111+
pop hl
112+
ld de, #state_timer_ticks
113+
ldir
114+
push hl
115+
pop iy
116+
117+
;; initialize the first speed in a way that the first
118+
;; update of the stream tracker will process opcodes immediately
119+
xor a
120+
ld (state_timer_tick_pos), a
121+
call timer_update_ticks_for_next_row
122+
ld a, (state_timer_ticks_per_row)
123+
ld (state_timer_ticks_count), a
124+
125+
pop de
126+
pop hl
127+
pop bc
128+
ret
129+
130+
131+
;;; set the number of ticks for the current row from the
132+
;;; position in the groove pattern
133+
;;; ------
134+
;;; hl modified
135+
timer_set_ticks_per_row::
136+
;; hl: current tick pos (8bit aligned add)
137+
ld hl, #state_timer_ticks
138+
ld a, (state_timer_tick_pos)
139+
add l
140+
ld l, a
141+
142+
;; update ticks per row with current tick
143+
ld a, (hl)
144+
ld (state_timer_ticks_per_row), a
145+
146+
ret
147+
148+
149+
;;; update the position in the groove pattern and set the new
150+
;;; numer of ticks per row
151+
;;; ------
152+
;;; bc modified
153+
timer_update_ticks_for_next_row::
154+
push hl
155+
ld a, (state_timer_nb_ticks)
156+
ld b, a
157+
ld a, (state_timer_tick_pos)
158+
inc a
159+
cp b
160+
jp c, _timer_set_pos
161+
xor a
162+
_timer_set_pos:
163+
ld (state_timer_tick_pos), a
164+
call timer_set_ticks_per_row
165+
pop hl
166+
ret
167+
168+
169+
89170
;;;
90171
;;; NSS opcodes
91172
;;;
@@ -124,23 +205,54 @@ timer_tempo::
124205

125206
;;; ROW_SPEED
126207
;;; number of ticks to wait before processing the next row in the streams
208+
;;; when groove is in use, only speed2 is modified
127209
;;; ------
128210
;;; [hl]: ticks
129211
row_speed::
212+
push de
213+
214+
;; de: tick position to store speed (+0 or +1 if groove is used)
215+
ld de, #state_timer_ticks
216+
ld a, (state_timer_nb_ticks)
217+
dec a
218+
add e
219+
ld e, a
220+
221+
;; update ticks for speed opcode
130222
ld a, (hl)
131223
inc hl
132-
ld (state_timer_ticks_per_row), a
224+
ld (de), a
225+
226+
;; update current ticks per row
227+
push hl
228+
call timer_set_ticks_per_row
229+
pop hl
230+
231+
pop de
133232
ld a, #1
134233
ret
135234

136235

137236
;;; ROW_GROOVE
138237
;;; number of ticks to wait before processing the next row in the streams
238+
;;; this always modified speed1
139239
;;; ------
140240
;;; [hl]: ticks
141241
row_groove::
242+
push de
243+
;; de: tick position to store groove
244+
ld de, #state_timer_ticks
245+
246+
;; update ticks for groove opcode
142247
ld a, (hl)
143248
inc hl
144-
ld (state_timer_ticks_per_row), a
249+
ld (de), a
250+
251+
;; update current ticks per row
252+
push hl
253+
call timer_set_ticks_per_row
254+
pop hl
255+
256+
pop de
145257
ld a, #1
146258
ret

tools/furtool.py

+37-3
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,10 @@ def ebit(data, msb, lsb):
134134
class fur_module:
135135
name: str = ""
136136
author: str = ""
137-
speed: int = 0
137+
speeds: list[int] = field(default_factory=list)
138138
arpeggio: int = 0
139139
frequency: float = 0.0
140+
fxcolumns: list[int] = field(default_factory=list)
140141
instruments: list[int] = field(default_factory=list)
141142
samples: list[int] = field(default_factory=list)
142143

@@ -151,8 +152,8 @@ def read_module(bs):
151152
assert bs.read(4) == b"INFO"
152153
bs.read(4) # skip size
153154
bs.u1() # skip timebase
154-
mod.speed = bs.u1()
155-
bs.u1() # skip speed2
155+
bs.u1() # skip speed 1, use info from speed patterns later
156+
bs.u1() # skip speed 2, use info from speed patterns later
156157
mod.arpeggio = bs.u1()
157158
mod.frequency = bs.uf4()
158159
pattern_len = bs.u2()
@@ -180,6 +181,39 @@ def read_module(bs):
180181
for o in range(nb_orders):
181182
mod.orders[o][i] = bs.u1()
182183
mod.fxcolumns = [bs.u1() for x in range(14)]
184+
bs.read(14) # skip channel hide status (UI)
185+
bs.read(14) # skip channel collapse status (UI)
186+
for i in range(14): bs.ustr() # skip channel names
187+
for i in range(14): bs.ustr() # skip channel short names
188+
mod.comment = bs.ustr()
189+
bs.uf4() # skip master volume
190+
bs.read(28) # skip extended compatibity flags
191+
bs.u2() # skip virtual tempo numerator
192+
bs.u2() # skip virtual tempo denominator
193+
# right now, subsongs are not supported
194+
subsong_name = bs.ustr()
195+
subsong_comment = bs.ustr()
196+
subsongs = bs.u1()
197+
assert subsongs == 0, "subsongs in a single Furnace file is unsupported"
198+
bs.read(3) # skip reserved
199+
# song's additional metadata
200+
system_name = bs.ustr()
201+
game_name = bs.ustr()
202+
song_name_jp = bs.ustr()
203+
song_author_jp = bs.ustr()
204+
system_name_jp = bs.ustr()
205+
game_name_jp = bs.ustr()
206+
bs.read(12) # skip 1 "extra chip output setting"
207+
# patchbay
208+
bs.read(4*bs.u4()) # skip information
209+
bs.u1() # skip auto patchbay
210+
# more compat flags
211+
bs.read(8) # skip compat flags
212+
# speed pattern data
213+
speed_length = bs.u1()
214+
assert 1 <= speed_length <= 16
215+
mod.speeds = [bs.u1() for i in range(speed_length)]
216+
# TODO: grove patterns
183217
return mod
184218

185219

tools/nsstool.py

+19-7
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def register_nss_ops():
216216
# 0x08
217217
("nop" , ),
218218
("speed" , ["ticks"]),
219-
None,
219+
("groove", ["ticks"]),
220220
None,
221221
("b_instr" , ["inst"]),
222222
("b_note" , ["note"]),
@@ -344,6 +344,8 @@ def convert_fm_row(row, channel):
344344
jmp_to_order = 257
345345
elif fx == 0x0f: # Speed
346346
opcodes.append(speed(fxval))
347+
elif fx == 0x09: # Groove
348+
opcodes.append(groove(fxval))
347349
elif fx == 0x04: # vibrato
348350
# fxval == -1 means disable vibrato
349351
fxval = max(fxval, 0)
@@ -440,6 +442,8 @@ def convert_s_row(row, channel):
440442
jmp_to_order = 257
441443
elif fx == 0x0f: # Speed
442444
opcodes.append(speed(fxval))
445+
elif fx == 0x09: # Groove
446+
opcodes.append(groove(fxval))
443447
elif fx == 0x04: # vibrato
444448
# fxval == -1 means disable vibrato
445449
fxval = max(fxval, 0)
@@ -513,6 +517,8 @@ def convert_a_row(row, channel):
513517
opcodes.append(a_retrigger(fxval))
514518
elif fx == 0x0f: # Speed
515519
opcodes.append(speed(fxval))
520+
elif fx == 0x09: # Groove
521+
opcodes.append(groove(fxval))
516522
elif fx == 0xec: # cut
517523
opcodes.append(a_cut(fxval))
518524
else:
@@ -555,6 +561,8 @@ def convert_b_row(row, channel):
555561
jmp_to_order = 257
556562
elif fx == 0x0f: # Speed
557563
opcodes.append(speed(fxval))
564+
elif fx == 0x09: # Groove
565+
opcodes.append(groove(fxval))
558566
elif fx == 0x01: # pitch slide up
559567
# fxval == -1 means disable slide
560568
fxval = max(fxval, 0)
@@ -602,7 +610,7 @@ def row_to_nss(func, pat, pos):
602610
selected_b = [x for x in b_channel if x in channels]
603611

604612
# initialize stream speed from module
605-
tick = m.speed
613+
tick = m.speeds[0]
606614

607615
# -- structures
608616
# a song is composed of a sequence of orders
@@ -1116,12 +1124,14 @@ def stream_name(prefix, channel):
11161124
return prefix+"_%s"%stream_type[channel]
11171125

11181126

1119-
def nss_compact_header(channels, streams, name, fd):
1127+
def nss_compact_header(mod, channels, streams, name, fd):
11201128
bitfield, comment = channels_bitfield(channels)
11211129
if name:
11221130
print("%s::" % name, file=fd)
11231131
print((" .db 0x%02x"%len(streams)).ljust(40)+" ; number of streams", file=fd)
11241132
print((" .dw 0x%04x"%bitfield).ljust(40)+" ; channels: %s"%comment, file=fd)
1133+
speeds=", ".join(["0x%02x"%x for x in mod.speeds])
1134+
print((" .db 0x%02x, %s"%(len(mod.speeds), speeds)).ljust(40)+" ; speeds", file=fd)
11251135
for i, c in enumerate(channels):
11261136
comment = "stream %i: NSS data"%i
11271137
print((" .dw %s"%(stream_name(name,c))).ljust(40)+" ; "+comment, file=fd)
@@ -1163,7 +1173,6 @@ def generate_nss_stream(m, p, bs, ins, channels, stream_idx):
11631173
if stream_idx <= 0:
11641174
tb = round(256 - (4000000 / (1152 * m.frequency)))
11651175
nss.insert(0, tempo(tb))
1166-
nss.insert(0, speed(m.speed))
11671176

11681177
dbg("Transformation passes:")
11691178
dbg(" - remove unreference NSS labels")
@@ -1256,11 +1265,14 @@ def main():
12561265
streams = [generate_nss_stream(m, p, bs, ins, [c], i) for i, c in enumerate(channels)]
12571266
channels, streams = remove_empty_streams(channels, streams)
12581267
# NSS compact header (number of streams, channels bitfield, stream pointers)
1259-
size = 1 + 2 + (2 * len(streams))
1268+
size = (1 + # number of streams
1269+
2 + # channels bitfield
1270+
1 + len(m.speeds) + # speeds
1271+
(2 * len(streams))) # stream pointers
12601272
# all streams sizes
12611273
size += sum([stream_size(s) for s in streams])
12621274
asm_header(streams, m, name, size, outfd)
1263-
nss_compact_header(channels, streams, name, outfd)
1275+
nss_compact_header(m, channels, streams, name, outfd)
12641276
for i, ch, stream in zip(range(len(channels)), channels, streams):
12651277
nss_to_asm(stream, m, stream_name(name, ch), outfd)
12661278
else:
@@ -1273,7 +1285,7 @@ def main():
12731285

12741286
# warn about any unknown FX during the conversion to NSS
12751287
for ch in unknown_fx.keys():
1276-
dbg("unknown FX for %s: %s" % (ch, ", ".join(sorted(unknown_fx[ch]))))
1288+
warn("unknown FX for %s: %s" % (ch, ", ".join(sorted(unknown_fx[ch]))))
12771289

12781290

12791291

0 commit comments

Comments
 (0)