Skip to content

Commit 8c0c5f0

Browse files
committed
Support 8bit wav files in adpcmtool and nsstool
Automatic conversion of wav files into adpcm now supports 8bit sample rate, signed or unsigned.
1 parent 74ddc55 commit 8c0c5f0

File tree

3 files changed

+72
-18
lines changed

3 files changed

+72
-18
lines changed

tools/adpcmtool.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,23 @@ def _decode_sample(self, adpcm4):
141141

142142
return decoded_sample12
143143

144-
def encode(self, pcm16s):
145-
self.reset()
146-
# ADPCM-A encodes 12-bits samples, so downscale the input first
144+
def encode_u8(self, pcm8s):
145+
# ADPCM-A expects 12-bits input samples
146+
pcm12s = [(s-128) << 4 for s in pcm8s]
147+
return self.encode(pcm12s)
148+
149+
def encode_s8(self, pcm8s):
150+
# ADPCM-A expects 12-bits input samples
151+
pcm12s = [s << 4 for s in pcm8s]
152+
return self.encode(pcm12s)
153+
154+
def encode_s16(self, pcm16s):
155+
# ADPCM-A expects 12-bits input samples
147156
pcm12s = [s >> 4 for s in pcm16s]
157+
return self.encode(pcm12s)
158+
159+
def encode(self, pcm12s):
160+
self.reset()
148161
# YM2610 only plays back multiples of 256 bytes
149162
# (512 adpcm samples). If the input is not aligned, add some padding
150163
ceil = ((len(pcm12s)+511)//512)*512;
@@ -241,6 +254,19 @@ def _decode_sample(self, adpcm4):
241254

242255
return decoded_sample16
243256

257+
def encode_u8(self, pcm8s):
258+
# ADPCM-B expects 16-bits input samples
259+
pcm16s = [(s-128) << 8 for s in pcm8s]
260+
return self.encode(pcm16s)
261+
262+
def encode_s8(self, pcm8s):
263+
# ADPCM-B expects 16-bits input samples
264+
pcm16s = [s << 8 for s in pcm8s]
265+
return self.encode(pcm16s)
266+
267+
def encode_s16(self, pcm16s):
268+
return self.encode(pcm16s)
269+
244270
def encode(self, pcm16s):
245271
self.reset()
246272
# YM2610 only plays back multiples of 256 bytes
@@ -264,8 +290,8 @@ def encode(input, output, codec):
264290
# input sanity checks
265291
if w.getnchannels() > 1:
266292
error("Only mono WAV file is supported")
267-
if w.getsampwidth() != 2:
268-
error("Only 16bits per sample is supported")
293+
if w.getsampwidth() not in [1, 2]:
294+
error("Only 8bits or 16bits per sample is supported")
269295
if w.getcomptype() != 'NONE':
270296
error("Only uncompressed WAV file is supported")
271297
wavrate = w.getframerate()
@@ -286,10 +312,14 @@ def encode(input, output, codec):
286312
w.close()
287313

288314
insize = len(rawdata)
289-
samples = struct.unpack('<%dh' % (insize >> 1), rawdata)
290-
dbg("Input is %s bytes long, or %d PCM samples" % (insize, insize >> 1))
291-
# encode input 16-bits samples into ADPCM-A or ADPCM-B 4-bits samples
292-
adpcms = codec.encode(list(samples))
315+
dbg("Input is %s bytes long, or %d PCM samples" % (insize, insize/w.getsampwidth()))
316+
# encode input samples into ADPCM-A or ADPCM-B 4-bits samples
317+
if w.getsampwidth() == 1:
318+
samples = struct.unpack('<%dB' % (insize), rawdata)
319+
adpcms = codec.encode_u8(list(samples))
320+
else:
321+
samples = struct.unpack('<%dh' % (insize>>1), rawdata)
322+
adpcms = codec.encode_s16(list(samples))
293323
# pack the resulting adpcm samples into bytes (2 samples per byte)
294324
adpcms_packed = [(adpcms[i] << 4 | adpcms[i+1]) for i in range(0, len(adpcms), 2)]
295325
outsize = len(adpcms_packed)

tools/furtool.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,14 @@ class adpcm_b_sample:
290290

291291

292292
@dataclass
293-
class pcm_sample:
293+
class pcm8_sample:
294+
name: str = ""
295+
data: bytearray = field(default=b"", repr=False)
296+
frequency: int = 0
297+
loop: bool = False
298+
299+
@dataclass
300+
class pcm16_sample:
294301
name: str = ""
295302
data: bytearray = field(default=b"", repr=False)
296303
frequency: int = 0
@@ -564,7 +571,7 @@ def compile_macro_sequence(keys, blocks, loadbits):
564571

565572

566573
def get_sample_format(smp, sample, itype):
567-
if isinstance(smp[sample],pcm_sample):
574+
if type(smp[sample]) in [pcm8_sample, pcm16_sample]:
568575
# the sample is encoded in PCM, so it has to be converted
569576
# to be played back on the hardware.
570577
warning("sample '%s' is encoded in PCM, converting to ADPCM-%s"%\
@@ -677,6 +684,9 @@ def read_sample(prefix, bs, sample_idx):
677684
elif stype == 16: # PCM16 (requires conversion to ADPCM)
678685
data_bytes = adpcm_samples * 2
679686
data_padding = 0 # adpcmtool codecs automatically adds padding
687+
elif stype == 8: # PCM8 (requires conversion to ADPCM)
688+
data_bytes = adpcm_samples
689+
data_padding = 0 # adpcmtool codecs automatically adds padding
680690
else:
681691
error("sample '%s' is of unsupported type: %d"%(str(name), stype))
682692
bs.u1() # unused loop direction
@@ -688,17 +698,24 @@ def read_sample(prefix, bs, sample_idx):
688698
insname = "%s_%02x_%s"%(prefix, sample_idx, re.sub(r"\W|^(?=\d)", "_", name).lower())
689699
ins = {5: adpcm_a_sample,
690700
6: adpcm_b_sample,
691-
16: pcm_sample}[stype](insname, data)
701+
8: pcm8_sample,
702+
16: pcm16_sample}[stype](insname, data)
692703
ins.loop = loop_start != -1 and loop_end != -1
693704
ins.frequency = c4_freq
705+
694706
return ins
695707

696708

697709
def convert_sample(pcm_sample, totype):
698710
codec = {37: ym2610_adpcma,
699711
38: ym2610_adpcmb}[totype]()
700-
pcm16s = unpack('<%dh' % (len(pcm_sample.data)>>1), pcm_sample.data)
701-
adpcms=codec.encode(pcm16s)
712+
if isinstance(pcm_sample, pcm8_sample):
713+
# NOTE: PCM8 seems to always be signed in Furnace
714+
pcm8s = unpack('<%db' % (len(pcm_sample.data)), pcm_sample.data)
715+
adpcms=codec.encode_s8(pcm8s)
716+
else:
717+
pcm16s = unpack('<%dh' % (len(pcm_sample.data)>>1), pcm_sample.data)
718+
adpcms=codec.encode_s16(pcm16s)
702719
adpcms_packed = [(adpcms[i] << 4 | adpcms[i+1]) for i in range(0, len(adpcms), 2)]
703720
# convert sample to the right class
704721
converted = {37: adpcm_a_sample,
@@ -723,7 +740,7 @@ def check_for_unused_samples(smp, bs):
723740
# module might have unused samples, leave them in the output
724741
# if these are pcm_samples, convert them to adpcm_a to avoid errors
725742
for i,s in enumerate(smp):
726-
if isinstance(s, pcm_sample):
743+
if type(s) in [pcm8_sample, pcm16_sample]:
727744
smp[i] = convert_sample(s, 37)
728745

729746
def asm_fm_instrument(ins, fd):

tools/vromtool.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,21 @@ def convert_to_adpcm(sample, path):
135135
try:
136136
w = wave.open(path, 'rb')
137137
assert w.getnchannels() == 1, "Only mono WAV file is supported"
138-
assert w.getsampwidth() == 2, "Only 16bits per sample is supported"
139138
assert w.getcomptype() == 'NONE', "Only uncompressed WAV file is supported"
140139
nframes = w.getnframes()
141140
data = w.readframes(nframes)
142141
except Exception as e:
143142
error("Could not convert sample '%s' to ADPCM: %s"%(path, e))
144-
pcm16s = unpack('<%dh' % (len(data)>>1), data)
145-
adpcms=codec.encode(pcm16s)
143+
144+
if w.getsampwidth() == 1:
145+
# WAV file format, 8bits is always unsigned
146+
pcm8s = unpack('<%dB' % (len(data)), data)
147+
adpcms=codec.encode_u8(pcm8s)
148+
else:
149+
# WAV file format, 16bits is always signed
150+
pcm16s = unpack('<%dh' % (len(data)>>1), data)
151+
adpcms=codec.encode_s16(pcm16s)
152+
146153
adpcms_packed = [(adpcms[i] << 4 | adpcms[i+1]) for i in range(0, len(adpcms), 2)]
147154
return bytes(adpcms_packed)
148155

0 commit comments

Comments
 (0)