Skip to content

Commit

Permalink
nss: fix playback of ADPCM-B and add support for looping
Browse files Browse the repository at this point in the history
  • Loading branch information
dciabrin committed Jun 24, 2024
1 parent 48c3fb3 commit a9c69e0
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 9 deletions.
17 changes: 16 additions & 1 deletion nullsound/nss-adpcm.s
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
state_adpcm_a_channel::
.db 0

;;; current ADPCM-B instrumment play command (with loop)
state_adpcm_b_start_cmd::
.db 0


.area CODE
Expand All @@ -51,6 +54,8 @@ state_adpcm_a_channel::
init_nss_adpcm_state_tracker::
ld a, #0
ld (state_adpcm_a_channel), a
ld a, #0x80 ; start flag
ld (state_adpcm_b_start_cmd), a
ret

;;; Reset ADPCM-A playback state.
Expand Down Expand Up @@ -354,6 +359,14 @@ _adpcm_b_loop:
dec d
jp nz, _adpcm_b_loop

;; play command, with/without loop bit
ld a, #0x80
bit 0, (hl)
jr z, _adpcm_b_post_loop_chk
set 4, a
_adpcm_b_post_loop_chk:
ld (state_adpcm_b_start_cmd), a

pop de
pop hl
pop bc
Expand Down Expand Up @@ -463,7 +476,9 @@ _no_delta_shift:

;; start the ADPCM-B channel
ld b, #REG_ADPCM_B_START_STOP
ld c, #0x80 ; start flag
;; start command (with loop when configured)
ld a, (state_adpcm_b_start_cmd)
ld c, a
call ym2610_write_port_a

pop hl
Expand Down
29 changes: 22 additions & 7 deletions tools/furtool.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ def uf4(self):
self.pos += 4
return res

def s4(self):
res = unpack_from("<i", self.data, self.pos)[0]
self.pos += 4
return res

def ustr(self):
res = []
b = self.u1()
Expand Down Expand Up @@ -208,6 +213,7 @@ class adpcm_b_sample:
class pcm_sample:
name: str = ""
data: bytearray = field(default=b"", repr=False)
loop: bool = False


@dataclass
Expand Down Expand Up @@ -239,6 +245,7 @@ class adpcm_a_instrument:
class adpcm_b_instrument:
name: str = ""
sample: adpcm_b_sample = None
loop: bool = False


def read_fm_instrument(bs):
Expand Down Expand Up @@ -418,6 +425,9 @@ def asm_ident(x):
if itype in [37, 38]:
ins = {37: adpcm_a_instrument,
38: adpcm_b_instrument}[itype]()
# ADPCM-B loop information
if itype == 38:
ins.loop = smp[sample].loop
if isinstance(smp[sample],pcm_sample):
# the sample is encoded in PCM, so it has to be converted
# to be played back on the hardware.
Expand Down Expand Up @@ -469,16 +479,17 @@ def read_sample(bs):
else:
error("sample '%s' is of unsupported type: %d"%(str(name), stype))
# assert c4_freq == {5: 18500, 6: 44100}[stype]
bs.u1() # unused play direction
bs.u1() # unused loop direction
bs.u2() # unused flags
bs.read(8) # unused looping info
loop_start, loop_end = bs.s4(), bs.s4()
bs.read(16) # unused rom allocation
data = bs.read(data_bytes) + bytearray(data_padding)
# generate a ASM name for the instrument
insname = re.sub(r"\W|^(?=\d)", "_", name).lower()
ins = {5: adpcm_a_sample,
6: adpcm_b_sample,
16: pcm_sample}[stype](insname, data)
ins.loop = loop_start != -1 and loop_end != -1
return ins


Expand All @@ -501,6 +512,12 @@ def read_samples(ptrs, bs):
smp.append(read_sample(bs))
return smp

def check_for_unused_samples(smp, bs):
# module might have unused samples, leave them in the output
# if these are pcm_samples, convert them to adpcm_a to avoid errors
for i,s in enumerate(smp):
if isinstance(s, pcm_sample):
smp[i] = convert_sample(s, 37)

def asm_fm_instrument(ins, fd):
dtmul = tuple(ebit(ins.ops[i].detune, 6, 4) | ebit(ins.ops[i].multiply, 3, 0) for i in range(4))
Expand Down Expand Up @@ -597,6 +614,8 @@ def asm_adpcm_instrument(ins, fd):
print("%s:" % ins.name, file=fd)
print(" .db %s_START_LSB, %s_START_MSB ; start >> 8" % (name, name), file=fd)
print(" .db %s_STOP_LSB, %s_STOP_MSB ; stop >> 8" % (name, name), file=fd)
if isinstance(ins, adpcm_b_instrument):
print(" .db 0x%02x ; loop" % (ins.loop,), file=fd)
print("", file=fd)


Expand Down Expand Up @@ -683,11 +702,7 @@ def main():
m = read_module(bs)
smp = read_samples(m.samples, bs)
ins = read_instruments(m.instruments, smp, bs)
# module might have unused samples, leave them in the output
# if these are pcm_samples, convert them to adpcm_a to avoid errors
for i,s in enumerate(smp):
if isinstance(s, pcm_sample):
smp[i] = convert_sample(s, 37)
check_for_unused_samples(smp, bs)

if arguments.output:
outfd = open(arguments.output, "w")
Expand Down
8 changes: 7 additions & 1 deletion tools/nsstool.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ def to_nss_note(furnace_note):
nss_note = (octave << 4) + note
return nss_note

def to_nss_b_note(furnace_note):
octave = (furnace_note // 12) - 6
note = furnace_note % 12
nss_note = (octave << 4) + note
return nss_note

def make_ssg_note(furnace_note):
octave = (furnace_note // 12) - 5
note = furnace_note % 12
Expand Down Expand Up @@ -413,7 +419,7 @@ def convert_b_row(row, channel, opcodes):
if row.note == 180:
opcodes.append(b_stop())
else:
opcodes.append(b_note(to_nss_note(row.note)))
opcodes.append(b_note(to_nss_b_note(row.note)))
return jmp_to_order


Expand Down

0 comments on commit a9c69e0

Please sign in to comment.