mirror of
https://github.com/freedoom/freedoom.git
synced 2025-09-02 07:25:45 -04:00
genmidi: Add Python code for reading OPL instruments.
These files load OPL instrument data from SBI (SoundBlaster Instrument) or A2I (AdlibTracker2 Instrument) format files.
This commit is contained in:
parent
1a7fa5a92b
commit
5ce7690612
2 changed files with 253 additions and 0 deletions
210
lumps/genmidi/a2i_file.py
Normal file
210
lumps/genmidi/a2i_file.py
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
|
||||||
|
HEADER_STRING = "_A2ins_"
|
||||||
|
|
||||||
|
class BitReader:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.data = data
|
||||||
|
self.index = 0
|
||||||
|
self.byte = 0
|
||||||
|
self.byte_bit = 0
|
||||||
|
|
||||||
|
def read_byte(self):
|
||||||
|
if self.index >= len(self.data):
|
||||||
|
raise IndexError("Reached end of decompress stream " +
|
||||||
|
"(%i bytes)" % len(self.data))
|
||||||
|
result, = struct.unpack("B", self.data[self.index:self.index+1])
|
||||||
|
self.index += 1
|
||||||
|
return result
|
||||||
|
|
||||||
|
def read_bit(self):
|
||||||
|
if self.byte_bit <= 0:
|
||||||
|
self.byte = self.read_byte()
|
||||||
|
self.byte_bit = 7
|
||||||
|
else:
|
||||||
|
self.byte_bit -= 1
|
||||||
|
|
||||||
|
if (self.byte & (1 << self.byte_bit)) != 0:
|
||||||
|
result = 1
|
||||||
|
else:
|
||||||
|
result = 0
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def read_bits(self, n):
|
||||||
|
result = 0
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
result = (result << 1) + self.read_bit()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def read_gamma(reader):
|
||||||
|
result = 1
|
||||||
|
|
||||||
|
while True:
|
||||||
|
result = (result << 1) | reader.read_bit()
|
||||||
|
|
||||||
|
if reader.read_bit() == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def decompress(data, data_len):
|
||||||
|
reader = BitReader(data)
|
||||||
|
result = []
|
||||||
|
lwm = 0
|
||||||
|
last_offset = 0
|
||||||
|
|
||||||
|
# First byte is an implied straight copy.
|
||||||
|
result.append(reader.read_byte())
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if reader.read_bit():
|
||||||
|
if reader.read_bit():
|
||||||
|
if reader.read_bit():
|
||||||
|
# 111 = Copy byte from history,
|
||||||
|
# up to 15 bytes back.
|
||||||
|
#print "111 copy"
|
||||||
|
|
||||||
|
offset = reader.read_bits(4)
|
||||||
|
|
||||||
|
if offset == 0:
|
||||||
|
result.append(0)
|
||||||
|
else:
|
||||||
|
b = result[len(result) - offset]
|
||||||
|
result.append(b)
|
||||||
|
|
||||||
|
lwm = 0
|
||||||
|
else:
|
||||||
|
#print "110 copy"
|
||||||
|
# 110 = Copy 2-3 bytes from
|
||||||
|
# further back in history
|
||||||
|
|
||||||
|
offset = reader.read_byte()
|
||||||
|
count = 2 + (offset & 0x01)
|
||||||
|
offset = offset >> 1
|
||||||
|
|
||||||
|
if offset == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
index = len(result) - offset
|
||||||
|
for i in range(count):
|
||||||
|
result += result[index:index+1]
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
last_offset = offset
|
||||||
|
lwm = 1
|
||||||
|
else:
|
||||||
|
# 10 = Copy from further away...
|
||||||
|
|
||||||
|
offset = read_gamma(reader)
|
||||||
|
|
||||||
|
if lwm == 0 and offset == 2:
|
||||||
|
#print "10 copy type 1"
|
||||||
|
count = read_gamma(reader)
|
||||||
|
index = len(result) - last_offset
|
||||||
|
for i in range(count):
|
||||||
|
result += result[index:index+1]
|
||||||
|
index += 1
|
||||||
|
else:
|
||||||
|
#print "10 copy type 2"
|
||||||
|
if lwm == 0:
|
||||||
|
offset -= 3
|
||||||
|
else:
|
||||||
|
offset -= 2
|
||||||
|
|
||||||
|
offset = offset * 256 \
|
||||||
|
+ reader.read_byte()
|
||||||
|
|
||||||
|
count = read_gamma(reader)
|
||||||
|
|
||||||
|
if offset >= 32000:
|
||||||
|
count += 1
|
||||||
|
if offset >= 1280:
|
||||||
|
count += 1
|
||||||
|
if offset < 128:
|
||||||
|
count += 2
|
||||||
|
|
||||||
|
index = len(result) - offset
|
||||||
|
for i in range(count):
|
||||||
|
result += result[index:index+1]
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
last_offset = offset
|
||||||
|
|
||||||
|
lwm = 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
#print "Single byte output"
|
||||||
|
|
||||||
|
# 0 = Straight-through byte copy.
|
||||||
|
result.append(reader.read_byte())
|
||||||
|
lwm = 0
|
||||||
|
|
||||||
|
#print "len: %i" % len(result)
|
||||||
|
|
||||||
|
return struct.pack("%iB" % len(result), *result)
|
||||||
|
|
||||||
|
FIELDS = [
|
||||||
|
"m_am_vibrato_eg",
|
||||||
|
"c_am_vibrato_eg",
|
||||||
|
"m_ksl_volume",
|
||||||
|
"c_ksl_volume",
|
||||||
|
"m_attack_decay",
|
||||||
|
"c_attack_decay",
|
||||||
|
"m_sustain_release",
|
||||||
|
"c_sustain_release",
|
||||||
|
"m_waveform",
|
||||||
|
"c_waveform",
|
||||||
|
"feedback_fm",
|
||||||
|
"panning",
|
||||||
|
"finetune",
|
||||||
|
"voice_type",
|
||||||
|
]
|
||||||
|
|
||||||
|
def decode_type_9(data):
|
||||||
|
compressed_len, = struct.unpack("<H", data[0:2])
|
||||||
|
compressed_data = data[2:2+compressed_len]
|
||||||
|
decompressed_data = decompress(compressed_data, compressed_len)
|
||||||
|
|
||||||
|
instr_data = {}
|
||||||
|
|
||||||
|
# Decode main fields:
|
||||||
|
|
||||||
|
for i in range(len(FIELDS)):
|
||||||
|
instr_data[FIELDS[i]], = \
|
||||||
|
struct.unpack("B", decompressed_data[i:i+1])
|
||||||
|
|
||||||
|
# Decode instrument name
|
||||||
|
|
||||||
|
ps = decompressed_data[14:]
|
||||||
|
instr_data["name"], = struct.unpack("%ip" % len(ps), ps)
|
||||||
|
|
||||||
|
return instr_data
|
||||||
|
|
||||||
|
def read(filename):
|
||||||
|
f = open(filename)
|
||||||
|
data = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
hdrstr, crc, filever = struct.unpack("<7sHB", data[0:10])
|
||||||
|
|
||||||
|
if hdrstr.lower() != HEADER_STRING.lower():
|
||||||
|
raise Exception("Wrong file header ID string")
|
||||||
|
|
||||||
|
# TODO: CRC?
|
||||||
|
|
||||||
|
if filever == 9:
|
||||||
|
return decode_type_9(data[10:])
|
||||||
|
else:
|
||||||
|
raise Exception("Unsupported file version: %i" % filever)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
for filename in sys.argv[1:]:
|
||||||
|
print filename
|
||||||
|
print read(filename)
|
||||||
|
|
43
lumps/genmidi/sbi_file.py
Normal file
43
lumps/genmidi/sbi_file.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
HEADER_VALUE = "SBI\x1a"
|
||||||
|
|
||||||
|
FIELDS = [
|
||||||
|
"m_am_vibrato_eg",
|
||||||
|
"c_am_vibrato_eg",
|
||||||
|
"m_ksl_volume",
|
||||||
|
"c_ksl_volume",
|
||||||
|
"m_attack_decay",
|
||||||
|
"c_attack_decay",
|
||||||
|
"m_sustain_release",
|
||||||
|
"c_sustain_release",
|
||||||
|
"m_waveform",
|
||||||
|
"c_waveform",
|
||||||
|
"feedback_fm"
|
||||||
|
]
|
||||||
|
|
||||||
|
def read(filename):
|
||||||
|
f = open(filename)
|
||||||
|
data = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
header, name = struct.unpack("4s32s", data[0:36])
|
||||||
|
|
||||||
|
if header != HEADER_VALUE:
|
||||||
|
raise Exception("Invalid header for SBI file!")
|
||||||
|
|
||||||
|
instr_data = data[36:]
|
||||||
|
result = { "name": name.rstrip("\0") }
|
||||||
|
|
||||||
|
for i in range(len(FIELDS)):
|
||||||
|
result[FIELDS[i]], = struct.unpack("B", instr_data[i:i+1])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
for filename in sys.argv[1:]:
|
||||||
|
print filename
|
||||||
|
print read(filename)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue