Created
April 27, 2021 20:48
-
-
Save tattlemuss/8bf99da4c8f33b922b432f0e1f01c4d8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
A very simple and hacky script to convert 16-bit or 24-bit WAV sample files to 4-bit. | |
Files must be mono and uncompressed PCM data. | |
Output is a 4-bits-per-sample output, with an end marker byte, suitable for TTRAK playback. | |
""" | |
import struct, math | |
import os, array | |
# These are the output voltages as set in the Hatari source code, | |
# which we use as "nearest-neighbour" | |
voltages = [ | |
0, 369, 438, 521, 619, 735, 874, 1039, | |
1234, 1467, 1744, 2072, 2463, 2927, 3479, 4135, | |
4914, 5841, 6942, 8250, 9806,11654,13851,16462, | |
19565,23253,27636,32845,39037,46395,55141,65535 | |
] | |
YmVolume4to5 = [ 0,1,5,7,9,11,13,15,17,19,21,23,25,27,29,31 ] | |
def make_tag(a,b,c,d): | |
return (ord(a) << 0) | (ord(b) << 8) | (ord(c) << 16) | (ord(d) << 24) | |
def nearest16(x): | |
""" find the nearest entry to our candidate level """ | |
# This isn't a perfect approach since it doesn't take the logarithmic | |
# factor into account, but it's a reasonable first approximation. | |
best_off = 65536 | |
best_cand = 0 | |
for index, val in enumerate(YmVolume4to5): | |
off = abs(voltages[val] - x) | |
if off < best_off: | |
best_cand = index | |
best_off = off | |
assert(best_cand <= 0xf) | |
return best_cand | |
def read_struct(fh, fmt): | |
size = struct.calcsize(fmt) | |
data = fh.read(size) | |
return struct.unpack(fmt, data) | |
def convert(src, dst): | |
print (src) | |
src_fh = open(src, 'rb') | |
bits = -1 | |
# Read header | |
(chunkid, restsize, wave) = read_struct(src_fh, '<III') | |
if chunkid != make_tag('R', 'I', 'F', 'F'): # "RIFF" in bigendian | |
print("Unknown WAV header") | |
return -1 | |
while True: | |
offset = src_fh.tell() | |
try: | |
(chunk_id, chunk_size) = read_struct(src_fh, '<II') | |
except struct.error: | |
break | |
print("Read WAV chunk with ID 0x%x" % chunk_id) | |
if chunk_id == make_tag('f', 'm', 't', ' '): | |
# Read format | |
(AudioFormat, NumChannels, SampleRate, ByteRate, BlockAlign, BitsPerSample) = read_struct(src_fh, '<HHIIHH') | |
print("AudioFormat:", AudioFormat) | |
print("BPS:", BitsPerSample) | |
print("NumChannels:", NumChannels) | |
print("ByteRate:", ByteRate) | |
if (AudioFormat != 1): | |
print("Compressed format, can't decode") | |
return -1 | |
if (NumChannels != 1): | |
print("Not mono, can't decode") | |
return -1 | |
if BitsPerSample != 16 and BitsPerSample != 24: | |
print("Not 16-bit or 24-bit, can't decode") | |
return -1 | |
bits = BitsPerSample | |
if (chunk_id == make_tag('d', 'a', 't', 'a')): | |
print("Found sample data") | |
vals = array.array('l') | |
# Note: data is little-endian, signed | |
if bits == 16: | |
num_samples = int(chunk_size / 2) | |
for x in range(0, num_samples): | |
(lo, hi) = read_struct(src_fh, '<BB') | |
# Convert to unsigned | |
uns = (((lo) | (hi << 8)) + 0x8000) & 0xffff | |
vals.append(uns) | |
elif bits == 24: | |
num_samples = int(chunk_size / 3) | |
for x in range(0, num_samples): | |
# Read 24 but only use 16 | |
(dummy, lo, hi) = read_struct(src_fh, '<BBB') | |
# Convert to unsigned | |
uns = (((lo) | (hi << 8)) + 0x8000) & 0xffff | |
vals.append(uns) | |
else: | |
print("Unsupported bit count") | |
return -1 | |
# Convert (badly) | |
fourbit_list = [nearest16(val) for val in vals] | |
# end marker | |
fourbit_list[-1] |= 0x80 | |
with open(dst, 'wb') as dst_fh: | |
dst_fh.write(bytearray(fourbit_list)) | |
dst_fh.close() | |
print("Converted to %s OK" % dst) | |
break | |
# Move to next | |
src_fh.seek(offset + chunk_size + 8, os.SEEK_SET) | |
src_fh.close() | |
if __name__ == '__main__': | |
import sys | |
src = sys.argv[1] | |
dst = sys.argv[2] | |
convert(src, dst) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment