mirror of
https://github.com/freedoom/freedoom.git
synced 2025-09-04 04:25:46 -04:00
genmidi: Split off GENMIDI code into separate file.
Split out code for handling the GENMIDI format into a separate genmidi.py file. Add routine to load the contents of a GENMIDI lump.
This commit is contained in:
parent
85482928d5
commit
3b96d7f1de
3 changed files with 223 additions and 94 deletions
214
lumps/genmidi/genmidi.py
Normal file
214
lumps/genmidi/genmidi.py
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2011, 2012
|
||||||
|
# Contributors to the Freedoom project. All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the freedoom project nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||||
|
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||||
|
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Module for interacting with Doom GENMIDI lumps.
|
||||||
|
#
|
||||||
|
|
||||||
|
from instrument import Instrument, NullInstrument
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
GENMIDI_HEADER = "#OPL_II#"
|
||||||
|
NUM_INSTRUMENTS = 175
|
||||||
|
INSTR_DATA_LEN = 36
|
||||||
|
INSTR_NAME_LEN = 32
|
||||||
|
|
||||||
|
FLAG_FIXED_PITCH = 0x0001
|
||||||
|
FLAG_TWO_VOICE = 0x0004
|
||||||
|
|
||||||
|
KSL_MASK = 0xc0
|
||||||
|
VOLUME_MASK = 0x3f
|
||||||
|
|
||||||
|
# Order of fields in GENMIDI data structures.
|
||||||
|
|
||||||
|
GENMIDI_FIELDS = [
|
||||||
|
"m_am_vibrato_eg",
|
||||||
|
"m_attack_decay",
|
||||||
|
"m_sustain_release",
|
||||||
|
"m_waveform",
|
||||||
|
"m_ksl",
|
||||||
|
"m_volume",
|
||||||
|
"feedback_fm",
|
||||||
|
"c_am_vibrato_eg",
|
||||||
|
"c_attack_decay",
|
||||||
|
"c_sustain_release",
|
||||||
|
"c_waveform",
|
||||||
|
"c_ksl",
|
||||||
|
"c_volume",
|
||||||
|
"null",
|
||||||
|
"note_offset"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Encode a single voice of an instrument to binary.
|
||||||
|
|
||||||
|
def encode_voice(data, offset):
|
||||||
|
result = dict(data)
|
||||||
|
|
||||||
|
result["m_ksl"] = data["m_ksl_volume"] & KSL_MASK
|
||||||
|
result["m_volume"] = data["m_ksl_volume"] & VOLUME_MASK
|
||||||
|
result["c_ksl"] = data["c_ksl_volume"] & KSL_MASK
|
||||||
|
result["c_volume"] = data["c_ksl_volume"] & VOLUME_MASK
|
||||||
|
|
||||||
|
result["null"] = 0
|
||||||
|
result["note_offset"] = offset
|
||||||
|
|
||||||
|
return struct.pack("<BBBBBBBBBBBBBBh",
|
||||||
|
*map(lambda key: result[key], GENMIDI_FIELDS))
|
||||||
|
|
||||||
|
# Encode an instrument to binary.
|
||||||
|
|
||||||
|
def encode_instrument(instrument):
|
||||||
|
flags = 0
|
||||||
|
|
||||||
|
instr1_data = encode_voice(instrument.voice1, instrument.offset1)
|
||||||
|
|
||||||
|
if instrument.voice2 is not None:
|
||||||
|
flags |= FLAG_TWO_VOICE
|
||||||
|
instr2_data = encode_voice(instrument.voice2,
|
||||||
|
instrument.offset2)
|
||||||
|
else:
|
||||||
|
instr2_data = encode_voice(NullInstrument.voice1, 0)
|
||||||
|
|
||||||
|
if instrument.fixed_note is not None:
|
||||||
|
flags |= FLAG_FIXED_PITCH
|
||||||
|
fixed_note = instrument.fixed_note
|
||||||
|
else:
|
||||||
|
fixed_note = 0
|
||||||
|
|
||||||
|
header = struct.pack("<hBB", flags, 128, fixed_note)
|
||||||
|
|
||||||
|
return header + instr1_data + instr2_data
|
||||||
|
|
||||||
|
def encode_instruments(instruments):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for instrument in instruments:
|
||||||
|
result.append(encode_instrument(instrument))
|
||||||
|
|
||||||
|
return b"".join(result)
|
||||||
|
|
||||||
|
def encode_instrument_names(instruments):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for instrument in instruments:
|
||||||
|
result.append(struct.pack("32s", instrument.voice1["name"]))
|
||||||
|
|
||||||
|
return b"".join(result)
|
||||||
|
|
||||||
|
def write(filename, instruments):
|
||||||
|
header = struct.pack("%is" % len(GENMIDI_HEADER), GENMIDI_HEADER)
|
||||||
|
|
||||||
|
f = open(filename, 'w')
|
||||||
|
|
||||||
|
f.write(header)
|
||||||
|
f.write(encode_instruments(instruments))
|
||||||
|
f.write(encode_instrument_names(instruments))
|
||||||
|
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def decode_voice(data):
|
||||||
|
|
||||||
|
fields = struct.unpack("<BBBBBBBBBBBBBBh", data)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
for i in range(len(GENMIDI_FIELDS)):
|
||||||
|
result[GENMIDI_FIELDS[i]] = fields[i]
|
||||||
|
|
||||||
|
result["m_ksl_volume"] = result["m_ksl"] | result["m_volume"]
|
||||||
|
result["c_ksl_volume"] = result["c_ksl"] | result["c_volume"]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def decode_instrument(data, name):
|
||||||
|
flags, finetune, fixed_note = struct.unpack("<hBB", data[0:4])
|
||||||
|
|
||||||
|
voice1 = decode_voice(data[4:20])
|
||||||
|
offset1 = voice1["note_offset"]
|
||||||
|
|
||||||
|
# Second voice?
|
||||||
|
|
||||||
|
if (flags & FLAG_TWO_VOICE) != 0:
|
||||||
|
voice2 = decode_voice(data[20:])
|
||||||
|
offset2 = voice2["note_offset"]
|
||||||
|
else:
|
||||||
|
voice2 = None
|
||||||
|
offset2 = 0
|
||||||
|
|
||||||
|
# Null out fixed_note if the fixed pitch flag isn't set:
|
||||||
|
|
||||||
|
if (flags & FLAG_FIXED_PITCH) == 0:
|
||||||
|
fixed_note = None
|
||||||
|
|
||||||
|
return Instrument(voice1, voice2,
|
||||||
|
off1=offset1, off2=offset2,
|
||||||
|
note=fixed_note)
|
||||||
|
|
||||||
|
def read(filename):
|
||||||
|
f = open(filename)
|
||||||
|
data = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# Check header:
|
||||||
|
|
||||||
|
header = data[0:len(GENMIDI_HEADER)]
|
||||||
|
if header != GENMIDI_HEADER:
|
||||||
|
raise Exception("Incorrect header for GENMIDI lump")
|
||||||
|
|
||||||
|
body = data[len(GENMIDI_HEADER):]
|
||||||
|
instr_data = body[0:NUM_INSTRUMENTS * INSTR_DATA_LEN]
|
||||||
|
instr_names = body[NUM_INSTRUMENTS * INSTR_DATA_LEN:]
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for i in range(NUM_INSTRUMENTS):
|
||||||
|
data = instr_data[i * INSTR_DATA_LEN:(i+1) * INSTR_DATA_LEN]
|
||||||
|
name = instr_names[i * INSTR_NAME_LEN:(i+1) * INSTR_NAME_LEN]
|
||||||
|
|
||||||
|
result.append(decode_instrument(data, name))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
for filename in sys.argv[1:]:
|
||||||
|
instruments = read(filename)
|
||||||
|
for i in range(len(instruments)):
|
||||||
|
instrument = instruments[i]
|
||||||
|
fixed_note = instrument.fixed_note
|
||||||
|
|
||||||
|
if fixed_note is not None:
|
||||||
|
print "%i (fixed note: %i):" % (i, fixed_note)
|
||||||
|
else:
|
||||||
|
print "%i:" % i
|
||||||
|
|
||||||
|
print "\tVoice 1: %s" % instrument.voice1
|
||||||
|
if instrument.voice2 is not None:
|
||||||
|
print "\tVoice 2: %s" % instrument.voice2
|
||||||
|
|
|
@ -58,6 +58,13 @@ def check_opl2(filename, data):
|
||||||
data["c_waveform"])
|
data["c_waveform"])
|
||||||
|
|
||||||
def load_instrument(filename):
|
def load_instrument(filename):
|
||||||
|
|
||||||
|
# As a hack, a literal dictionary of the values can be specified
|
||||||
|
# in place of a filename.
|
||||||
|
|
||||||
|
if isinstance(filename, dict):
|
||||||
|
return filename
|
||||||
|
|
||||||
filename = os.path.join("instruments", filename)
|
filename = os.path.join("instruments", filename)
|
||||||
|
|
||||||
if filename.endswith(".a2i"):
|
if filename.endswith(".a2i"):
|
||||||
|
|
|
@ -29,106 +29,14 @@
|
||||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#
|
#
|
||||||
|
|
||||||
from instrument import NullInstrument
|
import genmidi
|
||||||
import struct
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
GENMIDI_HEADER = "#OPL_II#"
|
|
||||||
|
|
||||||
FLAG_FIXED_PITCH = 0x0001
|
|
||||||
FLAG_TWO_VOICE = 0x0004
|
|
||||||
|
|
||||||
KSL_MASK = 0xc0
|
|
||||||
VOLUME_MASK = 0x3f
|
|
||||||
|
|
||||||
# Order of fields in GENMIDI data structures.
|
|
||||||
|
|
||||||
GENMIDI_FIELDS = [
|
|
||||||
"m_am_vibrato_eg",
|
|
||||||
"m_attack_decay",
|
|
||||||
"m_sustain_release",
|
|
||||||
"m_waveform",
|
|
||||||
"m_ksl",
|
|
||||||
"m_volume",
|
|
||||||
"feedback_fm",
|
|
||||||
"c_am_vibrato_eg",
|
|
||||||
"c_attack_decay",
|
|
||||||
"c_sustain_release",
|
|
||||||
"c_waveform",
|
|
||||||
"c_ksl",
|
|
||||||
"c_volume",
|
|
||||||
"null",
|
|
||||||
"note_offset"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Encode a single voice of an instrument to binary.
|
|
||||||
|
|
||||||
def encode_voice(data, offset):
|
|
||||||
result = dict(data)
|
|
||||||
|
|
||||||
result["m_ksl"] = data["m_ksl_volume"] & KSL_MASK
|
|
||||||
result["m_volume"] = data["m_ksl_volume"] & VOLUME_MASK
|
|
||||||
result["c_ksl"] = data["c_ksl_volume"] & KSL_MASK
|
|
||||||
result["c_volume"] = data["c_ksl_volume"] & VOLUME_MASK
|
|
||||||
|
|
||||||
result["null"] = 0
|
|
||||||
result["note_offset"] = offset
|
|
||||||
|
|
||||||
return struct.pack("<BBBBBBBBBBBBBBh",
|
|
||||||
*map(lambda key: result[key], GENMIDI_FIELDS))
|
|
||||||
|
|
||||||
# Encode an instrument to binary.
|
|
||||||
|
|
||||||
def encode_instrument(instrument):
|
|
||||||
flags = 0
|
|
||||||
|
|
||||||
instr1_data = encode_voice(instrument.voice1, instrument.offset1)
|
|
||||||
|
|
||||||
if instrument.voice2 is not None:
|
|
||||||
flags |= FLAG_TWO_VOICE
|
|
||||||
instr2_data = encode_voice(instrument.voice2,
|
|
||||||
instrument.offset2)
|
|
||||||
else:
|
|
||||||
instr2_data = encode_voice(NullInstrument.voice1, 0)
|
|
||||||
|
|
||||||
if instrument.fixed_note is not None:
|
|
||||||
flags |= FLAG_FIXED_PITCH
|
|
||||||
fixed_note = instrument.fixed_note
|
|
||||||
else:
|
|
||||||
fixed_note = 0
|
|
||||||
|
|
||||||
header = struct.pack("<hBB", flags, 128, fixed_note)
|
|
||||||
|
|
||||||
return header + instr1_data + instr2_data
|
|
||||||
|
|
||||||
def encode_instruments():
|
|
||||||
result = []
|
|
||||||
|
|
||||||
for instrument in INSTRUMENTS + PERCUSSION:
|
|
||||||
result.append(encode_instrument(instrument))
|
|
||||||
|
|
||||||
return b"".join(result)
|
|
||||||
|
|
||||||
def encode_instrument_names():
|
|
||||||
result = []
|
|
||||||
|
|
||||||
for instrument in INSTRUMENTS + PERCUSSION:
|
|
||||||
result.append(struct.pack("32s", instrument.voice1["name"]))
|
|
||||||
|
|
||||||
return b"".join(result)
|
|
||||||
|
|
||||||
def encode_genmidi():
|
|
||||||
header = struct.pack("%is" % len(GENMIDI_HEADER), GENMIDI_HEADER)
|
|
||||||
|
|
||||||
return header + encode_instruments() + encode_instrument_names()
|
|
||||||
|
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print >> sys.stderr, "Usage: %s <filename>" % sys.argv[0]
|
print >> sys.stderr, "Usage: %s <filename>" % sys.argv[0]
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
from config import INSTRUMENTS, PERCUSSION
|
from config import INSTRUMENTS, PERCUSSION
|
||||||
|
|
||||||
f = open(sys.argv[1], "w")
|
genmidi.write(sys.argv[1], INSTRUMENTS + PERCUSSION)
|
||||||
f.write(encode_genmidi())
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue