mirror of
https://github.com/ssb22/mwr2ly.git
synced 2023-05-26 07:35:17 +00:00
723 lines
28 KiB
Python
723 lines
28 KiB
Python
#!/usr/bin/env python
|
|
# (should work in either Python 2 or Python 3)
|
|
|
|
# Add depth to MIDI file v1.2, Silas S. Brown.
|
|
# Used some code from an old version of
|
|
# Python Midi Package by Max M.
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# Where to find history:
|
|
# on GitHub at https://github.com/ssb22/mwr2ly
|
|
# and on GitLab at https://gitlab.com/ssb22/mwr2ly
|
|
# and on BitBucket https://bitbucket.org/ssb22/mwr2ly
|
|
# and at https://gitlab.developers.cam.ac.uk/ssb22/mwr2ly
|
|
# and in China: https://gitee.com/ssb22/mwr2ly
|
|
|
|
import sys
|
|
patch_map = list(range(128))
|
|
if "--cdp-130" in sys.argv:
|
|
# Patch changes for Casio CDP-130
|
|
patch_map = [0,1,2,3,4,3,5,5,6,6,6,6,6,6,6,6,9,9,9,8,9,9,9,9,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,7,7,7,7,7,5,6,0,7,7,7,7,7,7,7,7,9,9,9,9,9,9,9,9,9,9,9,9,7,7,7,7,7,7,7,7,7,7,7,7,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,5,5,5,5,6,9,7,7,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
|
# and on channel 10: available keynums are 33 and 34 (Metronome Click and Metronome Bell), 75, 78 and 80 (claves, mute cuica, mute triangle: these are sounded by the user interface); other keys are silent
|
|
sys.argv.remove("--cdp-130")
|
|
|
|
reverb_by_patch = [
|
|
0x70,0x70,0x70,0x70, 0x70,0x70,0x70,0x70,
|
|
0x70,0x70,0x70,0x70, 0x70,0x70,0x70,0x70,
|
|
0x00,0x00,0x7D,0x00, 0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
0x44,0x44,0x44,0x64, 0x44,0x44,0x44,0x70,
|
|
0x00,0x00,0x00,0x00, 0x70,0x70,0x70,0x70,
|
|
0x64,0x70,0x70,0x70, 0x64,0x64,0x64,0x64,
|
|
0x64,0x64,0x64,0x64, 0x64,0x64,0x64,0x64,
|
|
0x64,0x64,0x64,0x64, 0x64,0x64,0x64,0x64,
|
|
0x64,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
0x50,0x50,0x50,0x50, 0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x7D,0x00, 0x00,0x7D,0x60,0x7D]
|
|
|
|
pan_by_patch = [
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x40,0x30,0x40,0x32, 0x34,0x36,0x38,0x40,
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x3F,0x48,0x50,0x56, 0x40,0x40,0x4D,0x3C,
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x44,0x4C,0x4D,0x44, 0x3B,0x48,0x48,0x48,
|
|
0x40,0x41,0x42,0x43, 0x48,0x3B,0x40,0x39,
|
|
0x35,0x37,0x40,0x40, 0x40,0x38,0x40,0x40,
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x4A,0x4B,0x4C,0x4D, 0x40,0x40,0x40,0x40,
|
|
0x40,0x40,0x40,0x40, 0x40,0x40,0x40,0x40,
|
|
0x40,0x40,0x48,0x40, 0x40,0x40,0x40,0x40]
|
|
|
|
def B(s):
|
|
if type("")==type(u""): return s.encode("utf-8") # Python 3
|
|
else: return s # Python 2
|
|
|
|
try: from cStringIO import StringIO
|
|
except: # Python 3
|
|
from io import BytesIO as StringIO
|
|
unichr = chr
|
|
def chr(x): return unichr(x).encode('latin1')
|
|
from struct import pack, unpack
|
|
def getNibbles(byte): return (byte >> 4 & 0xF, byte & 0xF)
|
|
def setNibbles(hiNibble, loNibble):
|
|
return (hiNibble << 4) + loNibble
|
|
def readBew(value):
|
|
return unpack('>%s' % {1:'B', 2:'H', 4:'L'}[len(value)], value)[0]
|
|
def writeBew(value, length):
|
|
return pack('>%s' % {1:'B', 2:'H', 4:'L'}[length], value)
|
|
def readVar(value):
|
|
sum = 0
|
|
for byte in unpack('%sB' % len(value), value):
|
|
sum = (sum << 7) + (byte & 0x7F)
|
|
if not 0x80 & byte: break
|
|
return sum
|
|
def varLen(value):
|
|
if value <= 127:
|
|
return 1
|
|
elif value <= 16383:
|
|
return 2
|
|
elif value <= 2097151:
|
|
return 3
|
|
else:
|
|
return 4
|
|
def writeVar(value):
|
|
sevens = to_n_bits(value, varLen(value))
|
|
for i in range(len(sevens)-1):
|
|
sevens[i] = sevens[i] | 0x80
|
|
return fromBytes(sevens)
|
|
def to_n_bits(value, length=1, nbits=7):
|
|
bytes = [(value >> (i*nbits)) & 0x7F for i in range(length)]
|
|
bytes.reverse()
|
|
return bytes
|
|
def toBytes(value):
|
|
return unpack('%sB' % len(value), value)
|
|
def fromBytes(value):
|
|
if not value:
|
|
return B('')
|
|
return pack('%sB' % len(value), *value)
|
|
class RawOutstreamFile:
|
|
def __init__(self, outfile=None):
|
|
if not outfile: outfile = B('')
|
|
self.buffer = StringIO()
|
|
self.outfile = outfile
|
|
def writeSlice(self, str_slice):
|
|
self.buffer.write(str_slice)
|
|
def writeBew(self, value, length=1):
|
|
self.writeSlice(writeBew(value, length))
|
|
def writeVarLen(self, value):
|
|
var = self.writeSlice(writeVar(value))
|
|
def write(self):
|
|
if self.outfile:
|
|
if type(self.outfile)==str:
|
|
outfile = open(self.outfile, 'wb')
|
|
outfile.write(self.getvalue())
|
|
outfile.close()
|
|
else:
|
|
self.outfile.write(self.getvalue())
|
|
def getvalue(self):
|
|
return self.buffer.getvalue()
|
|
NOTE_OFF = 0x80
|
|
NOTE_ON = 0x90
|
|
AFTERTOUCH = 0xA0
|
|
CONTINUOUS_CONTROLLER = 0xB0
|
|
PATCH_CHANGE = 0xC0
|
|
CHANNEL_PRESSURE = 0xD0
|
|
PITCH_BEND = 0xE0
|
|
BANK_SELECT = 0x00
|
|
MODULATION_WHEEL = 0x01
|
|
BREATH_CONTROLLER = 0x02
|
|
FOOT_CONTROLLER = 0x04
|
|
PORTAMENTO_TIME = 0x05
|
|
DATA_ENTRY = 0x06
|
|
CHANNEL_VOLUME = 0x07
|
|
BALANCE = 0x08
|
|
PAN = 0x0A
|
|
EXPRESSION_CONTROLLER = 0x0B
|
|
EFFECT_CONTROL_1 = 0x0C
|
|
EFFECT_CONTROL_2 = 0x0D
|
|
GEN_PURPOSE_CONTROLLER_1 = 0x10
|
|
GEN_PURPOSE_CONTROLLER_2 = 0x11
|
|
GEN_PURPOSE_CONTROLLER_3 = 0x12
|
|
GEN_PURPOSE_CONTROLLER_4 = 0x13
|
|
BANK_SELECT = 0x20
|
|
MODULATION_WHEEL = 0x21
|
|
BREATH_CONTROLLER = 0x22
|
|
FOOT_CONTROLLER = 0x24
|
|
PORTAMENTO_TIME = 0x25
|
|
DATA_ENTRY = 0x26
|
|
CHANNEL_VOLUME = 0x27
|
|
BALANCE = 0x28
|
|
PAN = 0x2A
|
|
EXPRESSION_CONTROLLER = 0x2B
|
|
EFFECT_CONTROL_1 = 0x2C
|
|
EFFECT_CONTROL_2 = 0x2D
|
|
GENERAL_PURPOSE_CONTROLLER_1 = 0x30
|
|
GENERAL_PURPOSE_CONTROLLER_2 = 0x31
|
|
GENERAL_PURPOSE_CONTROLLER_3 = 0x32
|
|
GENERAL_PURPOSE_CONTROLLER_4 = 0x33
|
|
SUSTAIN_ONOFF = 0x40
|
|
PORTAMENTO_ONOFF = 0x41
|
|
SOSTENUTO_ONOFF = 0x42
|
|
SOFT_PEDAL_ONOFF = 0x43
|
|
LEGATO_ONOFF = 0x44
|
|
HOLD_2_ONOFF = 0x45
|
|
SOUND_CONTROLLER_1 = 0x46
|
|
SOUND_CONTROLLER_2 = 0x47
|
|
SOUND_CONTROLLER_3 = 0x48
|
|
SOUND_CONTROLLER_4 = 0x49
|
|
SOUND_CONTROLLER_5 = 0x4A
|
|
SOUND_CONTROLLER_7 = 0x4C
|
|
SOUND_CONTROLLER_8 = 0x4D
|
|
SOUND_CONTROLLER_9 = 0x4E
|
|
SOUND_CONTROLLER_10 = 0x4F
|
|
GENERAL_PURPOSE_CONTROLLER_5 = 0x50
|
|
GENERAL_PURPOSE_CONTROLLER_6 = 0x51
|
|
GENERAL_PURPOSE_CONTROLLER_7 = 0x52
|
|
GENERAL_PURPOSE_CONTROLLER_8 = 0x53
|
|
PORTAMENTO_CONTROL = 0x54
|
|
EFFECTS_1 = 0x5B
|
|
EFFECTS_2 = 0x5C
|
|
EFFECTS_3 = 0x5D
|
|
EFFECTS_4 = 0x5E
|
|
EFFECTS_5 = 0x5F
|
|
DATA_INCREMENT = 0x60
|
|
DATA_DECREMENT = 0x61
|
|
NON_REGISTERED_PARAMETER_NUMBER = 0x62
|
|
NON_REGISTERED_PARAMETER_NUMBER = 0x63
|
|
REGISTERED_PARAMETER_NUMBER = 0x64
|
|
REGISTERED_PARAMETER_NUMBER = 0x65
|
|
ALL_SOUND_OFF = 0x78
|
|
RESET_ALL_CONTROLLERS = 0x79
|
|
LOCAL_CONTROL_ONOFF = 0x7A
|
|
ALL_NOTES_OFF = 0x7B
|
|
OMNI_MODE_OFF = 0x7C
|
|
OMNI_MODE_ON = 0x7D
|
|
MONO_MODE_ON = 0x7E
|
|
POLY_MODE_ON = 0x7F
|
|
SYSTEM_EXCLUSIVE = 0xF0
|
|
MTC = 0xF1
|
|
SONG_POSITION_POINTER = 0xF2
|
|
SONG_SELECT = 0xF3
|
|
TUNING_REQUEST = 0xF6
|
|
END_OFF_EXCLUSIVE = 0xF7
|
|
SEQUENCE_NUMBER = 0x00
|
|
TEXT = 0x01
|
|
COPYRIGHT = 0x02
|
|
SEQUENCE_NAME = 0x03
|
|
INSTRUMENT_NAME = 0x04
|
|
LYRIC = 0x05
|
|
MARKER = 0x06
|
|
CUEPOINT = 0x07
|
|
PROGRAM_NAME = 0x08
|
|
DEVICE_NAME = 0x09
|
|
MIDI_CH_PREFIX = 0x20
|
|
MIDI_PORT = 0x21
|
|
END_OF_TRACK = 0x2F
|
|
TEMPO = 0x51
|
|
SMTP_OFFSET = 0x54
|
|
TIME_SIGNATURE = 0x58
|
|
KEY_SIGNATURE = 0x59
|
|
SPECIFIC = 0x7F
|
|
FILE_HEADER = B('MThd')
|
|
TRACK_HEADER = B('MTrk')
|
|
TIMING_CLOCK = 0xF8
|
|
SONG_START = 0xFA
|
|
SONG_CONTINUE = 0xFB
|
|
SONG_STOP = 0xFC
|
|
ACTIVE_SENSING = 0xFE
|
|
SYSTEM_RESET = 0xFF
|
|
META_EVENT = 0xFF
|
|
def is_status(byte):
|
|
return (byte & 0x80) == 0x80
|
|
class MidiOutFile:
|
|
def update_time(self, new_time=0, relative=1):
|
|
if relative:
|
|
self._relative_time = new_time
|
|
self._absolute_time += new_time
|
|
else:
|
|
self._relative_time = new_time - self._absolute_time
|
|
self._absolute_time = new_time
|
|
def reset_time(self):
|
|
self._relative_time = 0
|
|
self._absolute_time = 0
|
|
def rel_time(self):
|
|
return self._relative_time
|
|
def abs_time(self):
|
|
return self._absolute_time
|
|
def reset_run_stat(self):
|
|
self._running_status = None
|
|
def set_run_stat(self, new_status):
|
|
self._running_status = new_status
|
|
def get_run_stat(self):
|
|
return self._running_status
|
|
def set_current_track(self, new_track):
|
|
self._current_track = new_track
|
|
def get_current_track(self):
|
|
return self._current_track
|
|
def __init__(self, raw_out=None):
|
|
if not raw_out: raw_out = B('')
|
|
self.raw_out = RawOutstreamFile(raw_out)
|
|
self._absolute_time = 0
|
|
self._relative_time = 0
|
|
self._current_track = 0
|
|
self._running_status = None
|
|
def write(self):
|
|
self.raw_out.write()
|
|
def event_slice(self, slc):
|
|
trk = self._current_track_buffer
|
|
trk.writeVarLen(self.rel_time())
|
|
trk.writeSlice(slc)
|
|
def note_on(self, channel=0, note=0x40, velocity=0x40):
|
|
slc = fromBytes([NOTE_ON + channel, note, velocity])
|
|
self.event_slice(slc)
|
|
def note_off(self, channel=0, note=0x40, velocity=0x40):
|
|
slc = fromBytes([NOTE_OFF + channel, note, velocity])
|
|
self.event_slice(slc)
|
|
def aftertouch(self, channel=0, note=0x40, velocity=0x40):
|
|
slc = fromBytes([AFTERTOUCH + channel, note, velocity])
|
|
self.event_slice(slc)
|
|
def continuous_controller(self, channel, controller, value):
|
|
slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value])
|
|
self.event_slice(slc)
|
|
def patch_change(self, channel, patch):
|
|
slc = fromBytes([PATCH_CHANGE + channel, patch])
|
|
self.event_slice(slc)
|
|
def channel_pressure(self, channel, pressure):
|
|
slc = fromBytes([CHANNEL_PRESSURE + channel, pressure])
|
|
self.event_slice(slc)
|
|
def pitch_bend(self, channel, value):
|
|
msb = (value>>7) & 0xFF
|
|
lsb = value & 0xFF
|
|
slc = fromBytes([PITCH_BEND + channel, msb, lsb])
|
|
self.event_slice(slc)
|
|
def sysex_event(self, data):
|
|
sysex_len = writeVar(len(data)+1)
|
|
self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE))
|
|
def midi_time_code(self, msg_type, values):
|
|
value = (msg_type<<4) + values
|
|
self.event_slice(fromBytes([MIDI_TIME_CODE, value]))
|
|
def song_position_pointer(self, value):
|
|
lsb = (value & 0x7F)
|
|
msb = (value >> 7) & 0x7F
|
|
self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb]))
|
|
def song_select(self, songNumber):
|
|
self.event_slice(fromBytes([SONG_SELECT, songNumber]))
|
|
def tuning_request(self):
|
|
self.event_slice(chr(TUNING_REQUEST))
|
|
def header(self, format=0, nTracks=1, division=96):
|
|
raw = self.raw_out
|
|
raw.writeSlice(FILE_HEADER)
|
|
bew = raw.writeBew
|
|
bew(6, 4)
|
|
bew(format, 2)
|
|
bew(nTracks, 2)
|
|
bew(division, 2)
|
|
def eof(self):
|
|
self.write()
|
|
def meta_slice(self, meta_type, data_slice):
|
|
"Writes a meta event"
|
|
slc = fromBytes([META_EVENT, meta_type]) + \
|
|
writeVar(len(data_slice)) + data_slice
|
|
self.event_slice(slc)
|
|
def meta_event(self, meta_type, data):
|
|
self.meta_slice(meta_type, fromBytes(data))
|
|
def start_of_track(self, n_track=0):
|
|
self._current_track_buffer = RawOutstreamFile()
|
|
self.reset_time()
|
|
self._current_track += 1
|
|
def end_of_track(self):
|
|
raw = self.raw_out
|
|
raw.writeSlice(TRACK_HEADER)
|
|
track_data = self._current_track_buffer.getvalue()
|
|
eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0])
|
|
raw.writeBew(len(track_data)+len(eot_slice), 4)
|
|
raw.writeSlice(track_data)
|
|
raw.writeSlice(eot_slice)
|
|
def sequence_number(self, value):
|
|
self.meta_slice(meta_type, writeBew(value, 2))
|
|
def text(self, text):
|
|
self.meta_slice(TEXT, text)
|
|
def copyright(self, text):
|
|
self.meta_slice(COPYRIGHT, text)
|
|
def sequence_name(self, text):
|
|
self.meta_slice(SEQUENCE_NAME, text)
|
|
def instrument_name(self, text):
|
|
self.meta_slice(INSTRUMENT_NAME, text)
|
|
def lyric(self, text):
|
|
self.meta_slice(LYRIC, text)
|
|
def marker(self, text):
|
|
self.meta_slice(MARKER, text)
|
|
def cuepoint(self, text):
|
|
self.meta_slice(CUEPOINT, text)
|
|
def program_name(self,progname): pass
|
|
def device_name(self,devicename): pass
|
|
def midi_ch_prefix(self, channel):
|
|
self.meta_slice(MIDI_CH_PREFIX, chr(channel))
|
|
def midi_port(self, value):
|
|
self.meta_slice(MIDI_CH_PREFIX, chr(value))
|
|
def tempo(self, value):
|
|
hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff)
|
|
self.meta_slice(TEMPO, fromBytes([hb, mb, lb]))
|
|
def smtp_offset(self, hour, minute, second, frame, framePart):
|
|
self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart]))
|
|
def time_signature(self, nn, dd, cc, bb):
|
|
self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb]))
|
|
def key_signature(self, sf, mi):
|
|
self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi]))
|
|
def sequencer_specific(self, data):
|
|
self.meta_slice(SPECIFIC, fromBytes(data))
|
|
class RawInstreamFile:
|
|
def __init__(self, infile=None):
|
|
if infile:
|
|
if type(infile)==str:
|
|
infile = open(infile, 'rb')
|
|
self.data = infile.read()
|
|
infile.close()
|
|
else:
|
|
self.data = infile.read()
|
|
else: self.data = B('')
|
|
self.cursor = 0
|
|
def setData(self, data=None):
|
|
if not data: data = B('')
|
|
self.data = data
|
|
def setCursor(self, position=0):
|
|
self.cursor = position
|
|
def getCursor(self):
|
|
return self.cursor
|
|
def moveCursor(self, relative_position=0):
|
|
self.cursor += relative_position
|
|
def nextSlice(self, length, move_cursor=1):
|
|
c = self.cursor
|
|
slc = self.data[c:c+length]
|
|
if move_cursor:
|
|
self.moveCursor(length)
|
|
return slc
|
|
def readBew(self, n_bytes=1, move_cursor=1):
|
|
return readBew(self.nextSlice(n_bytes, move_cursor))
|
|
def readVarLen(self):
|
|
MAX_VARLEN = 4
|
|
var = readVar(self.nextSlice(MAX_VARLEN, 0))
|
|
self.moveCursor(varLen(var))
|
|
return var
|
|
class EventDispatcher:
|
|
def __init__(self, outstream):
|
|
self.outstream = outstream
|
|
self.convert_zero_velocity = 1
|
|
self.dispatch_continuos_controllers = 1
|
|
self.dispatch_meta_events = 1
|
|
def header(self, format, nTracks, division):
|
|
self.outstream.header(format, nTracks, division)
|
|
def start_of_track(self, current_track):
|
|
self.outstream.set_current_track(current_track)
|
|
self.outstream.start_of_track(current_track)
|
|
def sysex_event(self, data):
|
|
self.outstream.sysex_event(data)
|
|
def eof(self):
|
|
self.outstream.eof()
|
|
def update_time(self, new_time=0, relative=1):
|
|
self.outstream.update_time(new_time, relative)
|
|
def reset_time(self):
|
|
self.outstream.reset_time()
|
|
def channel_messages(self, hi_nible, channel, data):
|
|
stream = self.outstream
|
|
data = toBytes(data)
|
|
if (NOTE_ON & 0xF0) == hi_nible:
|
|
note, velocity = data
|
|
if velocity==0 and self.convert_zero_velocity:
|
|
stream.note_off(channel, note, 0x40)
|
|
else:
|
|
stream.note_on(channel, note, velocity)
|
|
elif (NOTE_OFF & 0xF0) == hi_nible:
|
|
note, velocity = data
|
|
stream.note_off(channel, note, velocity)
|
|
elif (AFTERTOUCH & 0xF0) == hi_nible:
|
|
note, velocity = data
|
|
stream.aftertouch(channel, note, velocity)
|
|
elif (CONTINUOUS_CONTROLLER & 0xF0) == hi_nible:
|
|
controller, value = data
|
|
if self.dispatch_continuos_controllers:
|
|
self.continuous_controllers(channel, controller, value)
|
|
else:
|
|
stream.continuous_controller(channel, controller, value)
|
|
elif (PATCH_CHANGE & 0xF0) == hi_nible:
|
|
program = data[0]
|
|
stream.patch_change(channel, program)
|
|
elif (CHANNEL_PRESSURE & 0xF0) == hi_nible:
|
|
pressure = data[0]
|
|
stream.channel_pressure(channel, pressure)
|
|
elif (PITCH_BEND & 0xF0) == hi_nible:
|
|
hibyte, lobyte = data
|
|
value = (hibyte<<7) + lobyte
|
|
stream.pitch_bend(channel, value)
|
|
else: raise Exception('Illegal channel message')
|
|
def continuous_controllers(self, channel, controller, value):
|
|
stream = self.outstream
|
|
stream.continuous_controller(channel, controller, value)
|
|
def system_commons(self, common_type, common_data):
|
|
stream = self.outstream
|
|
if common_type == MTC:
|
|
data = readBew(common_data)
|
|
msg_type = (data & 0x07) >> 4
|
|
values = (data & 0x0F)
|
|
stream.midi_time_code(msg_type, values)
|
|
elif common_type == SONG_POSITION_POINTER:
|
|
hibyte, lobyte = toBytes(common_data)
|
|
value = (hibyte<<7) + lobyte
|
|
stream.song_position_pointer(value)
|
|
elif common_type == SONG_SELECT:
|
|
data = readBew(common_data)
|
|
stream.song_select(data)
|
|
elif common_type == TUNING_REQUEST:
|
|
stream.tuning_request(time=None)
|
|
def meta_events(self, meta_type, data):
|
|
stream = self.outstream
|
|
if meta_type == SEQUENCE_NUMBER:
|
|
number = readBew(data)
|
|
stream.sequence_number(number)
|
|
elif meta_type == TEXT:
|
|
stream.text(data)
|
|
elif meta_type == COPYRIGHT:
|
|
stream.copyright(data)
|
|
elif meta_type == SEQUENCE_NAME:
|
|
stream.sequence_name(data)
|
|
elif meta_type == INSTRUMENT_NAME:
|
|
stream.instrument_name(data)
|
|
elif meta_type == LYRIC:
|
|
stream.lyric(data)
|
|
elif meta_type == MARKER:
|
|
stream.marker(data)
|
|
elif meta_type == CUEPOINT:
|
|
stream.cuepoint(data)
|
|
elif meta_type == PROGRAM_NAME:
|
|
stream.program_name(data)
|
|
elif meta_type == DEVICE_NAME:
|
|
stream.device_name(data)
|
|
elif meta_type == MIDI_CH_PREFIX:
|
|
channel = readBew(data)
|
|
stream.midi_ch_prefix(channel)
|
|
elif meta_type == MIDI_PORT:
|
|
port = readBew(data)
|
|
stream.midi_port(port)
|
|
elif meta_type == END_OF_TRACK:
|
|
stream.end_of_track()
|
|
elif meta_type == TEMPO:
|
|
b1, b2, b3 = toBytes(data)
|
|
stream.tempo((b1<<16) + (b2<<8) + b3)
|
|
elif meta_type == SMTP_OFFSET:
|
|
hour, minute, second, frame, framePart = toBytes(data)
|
|
stream.smtp_offset(
|
|
hour, minute, second, frame, framePart)
|
|
elif meta_type == TIME_SIGNATURE:
|
|
nn, dd, cc, bb = toBytes(data)
|
|
stream.time_signature(nn, dd, cc, bb)
|
|
elif meta_type == KEY_SIGNATURE:
|
|
sf, mi = toBytes(data)
|
|
stream.key_signature(sf, mi)
|
|
elif meta_type == SPECIFIC:
|
|
meta_data = toBytes(data)
|
|
stream.sequencer_specific(meta_data)
|
|
else:
|
|
meta_data = toBytes(data)
|
|
stream.meta_event(meta_type, meta_data)
|
|
class MidiFileParser:
|
|
def __init__(self, raw_in, outstream):
|
|
self.raw_in = raw_in
|
|
self.dispatch = EventDispatcher(outstream)
|
|
self._running_status = None
|
|
def parseMThdChunk(self):
|
|
raw_in = self.raw_in
|
|
header_chunk_type = raw_in.nextSlice(4)
|
|
header_chunk_zise = raw_in.readBew(4)
|
|
if header_chunk_type != FILE_HEADER:
|
|
raise Exception("Invalid MIDI file")
|
|
self.format = raw_in.readBew(2)
|
|
self.nTracks = raw_in.readBew(2)
|
|
self.division = raw_in.readBew(2)
|
|
if header_chunk_zise > 6:
|
|
raw_in.moveCursor(header_chunk_zise-6)
|
|
self.dispatch.header(self.format, self.nTracks, self.division)
|
|
def parseMTrkChunk(self):
|
|
self.dispatch.reset_time()
|
|
dispatch = self.dispatch
|
|
raw_in = self.raw_in
|
|
dispatch.start_of_track(self._current_track)
|
|
raw_in.moveCursor(4)
|
|
tracklength = raw_in.readBew(4)
|
|
track_endposition = raw_in.getCursor() + tracklength
|
|
while raw_in.getCursor() < track_endposition:
|
|
time = raw_in.readVarLen()
|
|
dispatch.update_time(time)
|
|
peak_ahead = raw_in.readBew(move_cursor=0)
|
|
if (peak_ahead & 0x80):
|
|
status = self._running_status = raw_in.readBew()
|
|
else:
|
|
status = self._running_status
|
|
hi_nible, lo_nible = status & 0xF0, status & 0x0F
|
|
if status == META_EVENT:
|
|
meta_type = raw_in.readBew()
|
|
meta_length = raw_in.readVarLen()
|
|
meta_data = raw_in.nextSlice(meta_length)
|
|
dispatch.meta_events(meta_type, meta_data)
|
|
elif status == SYSTEM_EXCLUSIVE:
|
|
sysex_length = raw_in.readVarLen()
|
|
sysex_data = raw_in.nextSlice(sysex_length-1)
|
|
if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE:
|
|
eo_sysex = raw_in.readBew()
|
|
dispatch.sysex_event(sysex_data)
|
|
elif hi_nible == 0xF0:
|
|
data_sizes = {
|
|
MTC:1,
|
|
SONG_POSITION_POINTER:2,
|
|
SONG_SELECT:1,
|
|
}
|
|
data_size = data_sizes.get(hi_nible, 0)
|
|
common_data = raw_in.nextSlice(data_size)
|
|
common_type = lo_nible
|
|
dispatch.system_common(common_type, common_data)
|
|
else:
|
|
data_sizes = {
|
|
PATCH_CHANGE:1,
|
|
CHANNEL_PRESSURE:1,
|
|
NOTE_OFF:2,
|
|
NOTE_ON:2,
|
|
AFTERTOUCH:2,
|
|
CONTINUOUS_CONTROLLER:2,
|
|
PITCH_BEND:2,
|
|
}
|
|
data_size = data_sizes.get(hi_nible, 0)
|
|
channel_data = raw_in.nextSlice(data_size)
|
|
event_type, channel = hi_nible, lo_nible
|
|
dispatch.channel_messages(event_type, channel, channel_data)
|
|
def parseMTrkChunks(self):
|
|
for t in range(self.nTracks):
|
|
self._current_track = t
|
|
self.parseMTrkChunk()
|
|
self.dispatch.eof()
|
|
class MidiInFile:
|
|
def __init__(self, outStream, infile=None):
|
|
if not infile: infile = B('')
|
|
self.raw_in = RawInstreamFile(infile)
|
|
self.parser = MidiFileParser(self.raw_in, outStream)
|
|
def read(self):
|
|
p = self.parser
|
|
p.parseMThdChunk()
|
|
p.parseMTrkChunks()
|
|
def setData(self, data=None):
|
|
if not data: data = B('')
|
|
self.raw_in.setData(data)
|
|
|
|
class MidiToMidi:
|
|
def update_time(self, new_time=0, relative=1):
|
|
self.midi.update_time(new_time,relative)
|
|
def reset_time(self): self.midi.reset_time()
|
|
def rel_time(self): return self.midi.rel_time()
|
|
def abs_time(self): return self.midi.abs_time()
|
|
def __init__(self,outfile):
|
|
self.midi = MidiOutFile(outfile)
|
|
def set_current_track(self, new_track):
|
|
self.midi.set_current_track(new_track)
|
|
def get_current_track(self):
|
|
return self.midi.get_current_track()
|
|
def channel_message(self, message_type, channel, data):
|
|
self.midi.channel_message(message_type,channel,data)
|
|
def note_on(self, channel=0, note=0x40, velocity=0x40):
|
|
self.midi.note_on(channel,note,velocity)
|
|
def note_off(self, channel=0, note=0x40, velocity=0x40):
|
|
self.midi.note_off(channel, note, velocity)
|
|
def aftertouch(self, channel=0, note=0x40, velocity=0x40):
|
|
self.midi.aftertouch(channel, note, velocity)
|
|
def continuous_controller(self, channel, controller, value):
|
|
self.midi.continuous_controller(channel, controller, value)
|
|
def patch_change(self, channel, patch):
|
|
self.midi.patch_change(channel, patch_map[patch])
|
|
# Introduce pan/reverb settings
|
|
self.midi.update_time(0)
|
|
self.midi.continuous_controller(channel,10,pan_by_patch[patch])
|
|
self.midi.update_time(0)
|
|
self.midi.continuous_controller(channel,91,reverb_by_patch[patch])
|
|
# controller 93 if want chorus
|
|
# e.g. for just flutes, clarinets and shamisen to be chorus:
|
|
# self.midi.update_time(0)
|
|
# if patch in [71,73,106]: self.midi.continuous_controller(channel,93,0x40)
|
|
# else: self.midi.continuous_controller(channel,93,0)
|
|
def channel_pressure(self, channel, pressure):
|
|
self.midi.channel_pressure(channel, pressure)
|
|
def pitch_bend(self, channel, value):
|
|
self.midi.pitch_bend(channel, value)
|
|
def sysex_event(self, data):
|
|
self.midi.sysex_event(data)
|
|
def song_position_pointer(self, value):
|
|
self.midi.song_position_pointer(value)
|
|
def song_select(self, songNumber):
|
|
self.midi.song_select(songNumber)
|
|
def tuning_request(self): self.midi.tuning_request()
|
|
def midi_time_code(self, msg_type, values):
|
|
self.midi.midi_time_code(msg_type,values)
|
|
def header(self, format=0, nTracks=1, division=96):
|
|
self.midi.header(format,nTracks,division)
|
|
def eof(self): self.midi.eof()
|
|
def start_of_track(self, n_track=0):
|
|
self.midi.start_of_track(n_track)
|
|
def end_of_track(self):
|
|
self.midi.end_of_track()
|
|
def meta_event(self, meta_type, data):
|
|
self.midi.meta_event(meta_type,data)
|
|
def sequence_number(self, value):
|
|
self.midi.sequence_number(value)
|
|
def text(self, text): self.midi.text(text)
|
|
def copyright(self, text): self.midi.copyright(text)
|
|
def sequence_name(self, text):
|
|
self.midi.sequence_name(text)
|
|
def instrument_name(self, text):
|
|
self.midi.instrument_name(text)
|
|
def lyric(self, text): self.midi.lyric(text)
|
|
def marker(self, text): self.midi.marker(text)
|
|
def cuepoint(self, text): self.midi.cuepoint(text)
|
|
def midi_ch_prefix(self, channel):
|
|
self.midi.midi_ch_prefix(channel)
|
|
def midi_port(self, value):
|
|
self.midi.midi_port(value)
|
|
def tempo(self, value): self.midi.tempo(value)
|
|
def smtp_offset(self, hour, minute, second, frame, framePart):
|
|
self.midi.smtp_offset(hour, minute, second, frame, framePart)
|
|
def time_signature(self, nn, dd, cc, bb):
|
|
self.midi.time_signature(nn, dd, cc, bb)
|
|
def key_signature(self, sf, mi):
|
|
self.midi.key_signature(sf, mi)
|
|
def sequencer_specific(self, data):
|
|
self.midi.sequencer_specific(data)
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv)<3:
|
|
sys.stderr.write("Syntax: midi-add-depth [--cdp-130] infile|- outfile|-\n")
|
|
sys.exit(1)
|
|
if sys.argv[1]=='-':
|
|
try: inF = sys.stdin.buffer
|
|
except: inF = sys.stdin
|
|
else: inF = open(sys.argv[1],"rb")
|
|
if sys.argv[2]=='-':
|
|
try: outF = sys.stdout.buffer
|
|
except: outF = sys.stdout
|
|
else: outF = open(sys.argv[2],"wb")
|
|
MidiInFile(MidiToMidi(outF), inF).read()
|