xref: /aosp_15_r20/external/sonivox/jet_tools/JetCreator/midifile.py (revision f81fb7c475c4b71ff83bdcc517de2a8c174e4e5c)
1*f81fb7c4SAndroid Build Coastguard Worker"""
2*f81fb7c4SAndroid Build Coastguard Worker File:
3*f81fb7c4SAndroid Build Coastguard Worker midifile.py
4*f81fb7c4SAndroid Build Coastguard Worker
5*f81fb7c4SAndroid Build Coastguard Worker Contents and purpose:
6*f81fb7c4SAndroid Build Coastguard Worker Utilities used throughout JetCreator
7*f81fb7c4SAndroid Build Coastguard Worker
8*f81fb7c4SAndroid Build Coastguard Worker Copyright (c) 2008 Android Open Source Project
9*f81fb7c4SAndroid Build Coastguard Worker
10*f81fb7c4SAndroid Build Coastguard Worker Licensed under the Apache License, Version 2.0 (the "License");
11*f81fb7c4SAndroid Build Coastguard Worker you may not use this file except in compliance with the License.
12*f81fb7c4SAndroid Build Coastguard Worker You may obtain a copy of the License at
13*f81fb7c4SAndroid Build Coastguard Worker
14*f81fb7c4SAndroid Build Coastguard Worker      http://www.apache.org/licenses/LICENSE-2.0
15*f81fb7c4SAndroid Build Coastguard Worker
16*f81fb7c4SAndroid Build Coastguard Worker Unless required by applicable law or agreed to in writing, software
17*f81fb7c4SAndroid Build Coastguard Worker distributed under the License is distributed on an "AS IS" BASIS,
18*f81fb7c4SAndroid Build Coastguard Worker WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19*f81fb7c4SAndroid Build Coastguard Worker See the License for the specific language governing permissions and
20*f81fb7c4SAndroid Build Coastguard Worker limitations under the License.
21*f81fb7c4SAndroid Build Coastguard Worker"""
22*f81fb7c4SAndroid Build Coastguard Worker
23*f81fb7c4SAndroid Build Coastguard Workerimport logging
24*f81fb7c4SAndroid Build Coastguard Workerimport struct
25*f81fb7c4SAndroid Build Coastguard Workerimport copy
26*f81fb7c4SAndroid Build Coastguard Workerimport array
27*f81fb7c4SAndroid Build Coastguard Worker
28*f81fb7c4SAndroid Build Coastguard Worker# JET events
29*f81fb7c4SAndroid Build Coastguard WorkerJET_EVENT_MARKER = 102
30*f81fb7c4SAndroid Build Coastguard WorkerJET_MARKER_LOOP_END = 0
31*f81fb7c4SAndroid Build Coastguard WorkerJET_EVENT_TRIGGER_CLIP = 103
32*f81fb7c4SAndroid Build Coastguard Worker
33*f81fb7c4SAndroid Build Coastguard Worker# header definitions
34*f81fb7c4SAndroid Build Coastguard WorkerSMF_HEADER_FMT = '>4slHHH'
35*f81fb7c4SAndroid Build Coastguard WorkerSMF_RIFF_TAG = 'MThd'
36*f81fb7c4SAndroid Build Coastguard Worker
37*f81fb7c4SAndroid Build Coastguard WorkerSMF_TRACK_HEADER_FMT = '>4sl'
38*f81fb7c4SAndroid Build Coastguard WorkerSMF_TRACK_RIFF_TAG = 'MTrk'
39*f81fb7c4SAndroid Build Coastguard Worker
40*f81fb7c4SAndroid Build Coastguard Worker# defaults
41*f81fb7c4SAndroid Build Coastguard WorkerDEFAULT_PPQN = 120
42*f81fb7c4SAndroid Build Coastguard WorkerDEFAULT_BEATS_PER_MEASURE = 4
43*f81fb7c4SAndroid Build Coastguard WorkerDEFAULT_TIME_FORMAT = '%03d:%02d:%03d'
44*f81fb7c4SAndroid Build Coastguard Worker
45*f81fb7c4SAndroid Build Coastguard Worker# force note-offs to end of list
46*f81fb7c4SAndroid Build Coastguard WorkerMAX_SEQ_NUM = 0x7fffffff
47*f81fb7c4SAndroid Build Coastguard Worker
48*f81fb7c4SAndroid Build Coastguard Worker# MIDI messages
49*f81fb7c4SAndroid Build Coastguard WorkerNOTE_OFF = 0x80
50*f81fb7c4SAndroid Build Coastguard WorkerNOTE_ON = 0x90
51*f81fb7c4SAndroid Build Coastguard WorkerPOLY_KEY_PRESSURE = 0xa0
52*f81fb7c4SAndroid Build Coastguard WorkerCONTROL_CHANGE = 0xb0
53*f81fb7c4SAndroid Build Coastguard WorkerPROGRAM_CHANGE = 0xc0
54*f81fb7c4SAndroid Build Coastguard WorkerCHANNEL_PRESSURE = 0xd0
55*f81fb7c4SAndroid Build Coastguard WorkerPITCH_BEND = 0xe0
56*f81fb7c4SAndroid Build Coastguard Worker
57*f81fb7c4SAndroid Build Coastguard Worker# System common messages
58*f81fb7c4SAndroid Build Coastguard WorkerSYSEX = 0xf0
59*f81fb7c4SAndroid Build Coastguard WorkerMIDI_TIME_CODE = 0xf1
60*f81fb7c4SAndroid Build Coastguard WorkerSONG_POSITION_POINTER = 0xf2
61*f81fb7c4SAndroid Build Coastguard WorkerSONG_SELECT = 0xf3
62*f81fb7c4SAndroid Build Coastguard WorkerRESERVED_F4 = 0xf4
63*f81fb7c4SAndroid Build Coastguard WorkerRESERVED_F5 = 0xf5
64*f81fb7c4SAndroid Build Coastguard WorkerTUNE_REQUEST = 0xf6
65*f81fb7c4SAndroid Build Coastguard WorkerEND_SYSEX = 0xf7
66*f81fb7c4SAndroid Build Coastguard Worker
67*f81fb7c4SAndroid Build Coastguard Worker# System real-time messages
68*f81fb7c4SAndroid Build Coastguard WorkerTIMING_CLOCK = 0xf8
69*f81fb7c4SAndroid Build Coastguard WorkerRESERVED_F9 = 0xf9
70*f81fb7c4SAndroid Build Coastguard WorkerSTART = 0xfa
71*f81fb7c4SAndroid Build Coastguard WorkerCONTINUE = 0xfb
72*f81fb7c4SAndroid Build Coastguard WorkerSTOP = 0xfc
73*f81fb7c4SAndroid Build Coastguard WorkerRESERVED_FD = 0xfd
74*f81fb7c4SAndroid Build Coastguard WorkerACTIVE_SENSING = 0xfe
75*f81fb7c4SAndroid Build Coastguard WorkerSYSTEM_RESET = 0xff
76*f81fb7c4SAndroid Build Coastguard Worker
77*f81fb7c4SAndroid Build Coastguard WorkerONE_BYTE_MESSAGES = (
78*f81fb7c4SAndroid Build Coastguard Worker	TUNE_REQUEST,
79*f81fb7c4SAndroid Build Coastguard Worker	TIMING_CLOCK,
80*f81fb7c4SAndroid Build Coastguard Worker	RESERVED_F9,
81*f81fb7c4SAndroid Build Coastguard Worker	START,
82*f81fb7c4SAndroid Build Coastguard Worker	CONTINUE,
83*f81fb7c4SAndroid Build Coastguard Worker	STOP,
84*f81fb7c4SAndroid Build Coastguard Worker	RESERVED_FD,
85*f81fb7c4SAndroid Build Coastguard Worker	ACTIVE_SENSING,
86*f81fb7c4SAndroid Build Coastguard Worker	SYSTEM_RESET)
87*f81fb7c4SAndroid Build Coastguard Worker
88*f81fb7c4SAndroid Build Coastguard WorkerTHREE_BYTE_MESSAGES = (
89*f81fb7c4SAndroid Build Coastguard Worker	NOTE_OFF,
90*f81fb7c4SAndroid Build Coastguard Worker	NOTE_ON,
91*f81fb7c4SAndroid Build Coastguard Worker	POLY_KEY_PRESSURE,
92*f81fb7c4SAndroid Build Coastguard Worker	CONTROL_CHANGE,
93*f81fb7c4SAndroid Build Coastguard Worker	PITCH_BEND)
94*f81fb7c4SAndroid Build Coastguard Worker
95*f81fb7c4SAndroid Build Coastguard WorkerMIDI_MESSAGES = (
96*f81fb7c4SAndroid Build Coastguard Worker	NOTE_OFF,
97*f81fb7c4SAndroid Build Coastguard Worker	NOTE_ON,
98*f81fb7c4SAndroid Build Coastguard Worker	POLY_KEY_PRESSURE,
99*f81fb7c4SAndroid Build Coastguard Worker	CONTROL_CHANGE,
100*f81fb7c4SAndroid Build Coastguard Worker	CHANNEL_PRESSURE,
101*f81fb7c4SAndroid Build Coastguard Worker	PITCH_BEND,
102*f81fb7c4SAndroid Build Coastguard Worker	SYSEX)
103*f81fb7c4SAndroid Build Coastguard Worker
104*f81fb7c4SAndroid Build Coastguard Worker# Meta-events
105*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT = 0xff
106*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_SEQUENCE_NUMBER = 0x00
107*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_TEXT_EVENT = 0x01
108*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_COPYRIGHT_NOTICE = 0x02
109*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_SEQUENCE_TRACK_NAME = 0x03
110*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_INSTRUMENT_NAME = 0x04
111*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_LYRIC = 0x05
112*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_MARKER = 0x06
113*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_CUE_POINT = 0x07
114*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_MIDI_CHANNEL_PREFIX = 0x20
115*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_END_OF_TRACK = 0x2f
116*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_SET_TEMPO = 0x51
117*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_SMPTE_OFFSET = 0x54
118*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_TIME_SIGNATURE = 0x58
119*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_KEY_SIGNATURE = 0x59
120*f81fb7c4SAndroid Build Coastguard WorkerMETA_EVENT_SEQUENCER_SPECIFIC = 0x7f
121*f81fb7c4SAndroid Build Coastguard Worker
122*f81fb7c4SAndroid Build Coastguard Worker# recurring error messages
123*f81fb7c4SAndroid Build Coastguard WorkerMSG_NOT_SMF_FILE = 'Not an SMF file - aborting parse!'
124*f81fb7c4SAndroid Build Coastguard WorkerMSG_INVALID_TRACK_HEADER = 'Track header is invalid'
125*f81fb7c4SAndroid Build Coastguard WorkerMSG_TYPE_MISMATCH = 'msg_type does not match event type'
126*f81fb7c4SAndroid Build Coastguard Worker
127*f81fb7c4SAndroid Build Coastguard WorkerLARGE_TICK_WARNING = 1000
128*f81fb7c4SAndroid Build Coastguard Worker
129*f81fb7c4SAndroid Build Coastguard Worker# default control values
130*f81fb7c4SAndroid Build Coastguard WorkerCTRL_BANK_SELECT_MSB = 0
131*f81fb7c4SAndroid Build Coastguard WorkerCTRL_MOD_WHEEL = 1
132*f81fb7c4SAndroid Build Coastguard WorkerCTRL_RPN_DATA_MSB = 6
133*f81fb7c4SAndroid Build Coastguard WorkerCTRL_VOLUME = 7
134*f81fb7c4SAndroid Build Coastguard WorkerCTRL_PAN = 10
135*f81fb7c4SAndroid Build Coastguard WorkerCTRL_EXPRESSION = 11
136*f81fb7c4SAndroid Build Coastguard WorkerCTRL_BANK_SELECT_LSB = 32
137*f81fb7c4SAndroid Build Coastguard WorkerCTRL_RPN_DATA_LSB = 38
138*f81fb7c4SAndroid Build Coastguard WorkerCTRL_SUSTAIN = 64
139*f81fb7c4SAndroid Build Coastguard WorkerCTRL_RPN_LSB = 100
140*f81fb7c4SAndroid Build Coastguard WorkerCTRL_RPN_MSB = 101
141*f81fb7c4SAndroid Build Coastguard WorkerCTRL_RESET_CONTROLLERS = 121
142*f81fb7c4SAndroid Build Coastguard Worker
143*f81fb7c4SAndroid Build Coastguard WorkerRPN_PITCH_BEND_SENSITIVITY = 0
144*f81fb7c4SAndroid Build Coastguard WorkerRPN_FINE_TUNING = 1
145*f81fb7c4SAndroid Build Coastguard WorkerRPN_COARSE_TUNING = 2
146*f81fb7c4SAndroid Build Coastguard Worker
147*f81fb7c4SAndroid Build Coastguard WorkerMONITOR_CONTROLLERS = (
148*f81fb7c4SAndroid Build Coastguard Worker	CTRL_BANK_SELECT_MSB,
149*f81fb7c4SAndroid Build Coastguard Worker	CTRL_MOD_WHEEL,
150*f81fb7c4SAndroid Build Coastguard Worker	CTRL_RPN_DATA_MSB,
151*f81fb7c4SAndroid Build Coastguard Worker	CTRL_VOLUME,
152*f81fb7c4SAndroid Build Coastguard Worker	CTRL_PAN,
153*f81fb7c4SAndroid Build Coastguard Worker	CTRL_EXPRESSION,
154*f81fb7c4SAndroid Build Coastguard Worker	CTRL_BANK_SELECT_LSB,
155*f81fb7c4SAndroid Build Coastguard Worker	CTRL_RPN_DATA_LSB,
156*f81fb7c4SAndroid Build Coastguard Worker	CTRL_SUSTAIN,
157*f81fb7c4SAndroid Build Coastguard Worker	CTRL_RPN_LSB,
158*f81fb7c4SAndroid Build Coastguard Worker	CTRL_RPN_MSB)
159*f81fb7c4SAndroid Build Coastguard Worker
160*f81fb7c4SAndroid Build Coastguard WorkerMONITOR_RPNS = (
161*f81fb7c4SAndroid Build Coastguard Worker	RPN_PITCH_BEND_SENSITIVITY,
162*f81fb7c4SAndroid Build Coastguard Worker	RPN_FINE_TUNING,
163*f81fb7c4SAndroid Build Coastguard Worker	RPN_COARSE_TUNING)
164*f81fb7c4SAndroid Build Coastguard Worker
165*f81fb7c4SAndroid Build Coastguard WorkerRPN_PITCH_BEND_SENSITIVITY = 0
166*f81fb7c4SAndroid Build Coastguard WorkerRPN_FINE_TUNING = 1
167*f81fb7c4SAndroid Build Coastguard WorkerRPN_COARSE_TUNING = 2
168*f81fb7c4SAndroid Build Coastguard Worker
169*f81fb7c4SAndroid Build Coastguard WorkerDEFAULT_CONTROLLER_VALUES = {
170*f81fb7c4SAndroid Build Coastguard Worker	CTRL_BANK_SELECT_MSB : 121,
171*f81fb7c4SAndroid Build Coastguard Worker	CTRL_MOD_WHEEL : 0,
172*f81fb7c4SAndroid Build Coastguard Worker	CTRL_RPN_DATA_MSB : 0,
173*f81fb7c4SAndroid Build Coastguard Worker	CTRL_VOLUME : 100,
174*f81fb7c4SAndroid Build Coastguard Worker	CTRL_PAN : 64,
175*f81fb7c4SAndroid Build Coastguard Worker	CTRL_EXPRESSION : 127,
176*f81fb7c4SAndroid Build Coastguard Worker	CTRL_RPN_DATA_LSB : 0,
177*f81fb7c4SAndroid Build Coastguard Worker	CTRL_BANK_SELECT_LSB : 0,
178*f81fb7c4SAndroid Build Coastguard Worker	CTRL_SUSTAIN : 0,
179*f81fb7c4SAndroid Build Coastguard Worker	CTRL_RPN_LSB : 0x7f,
180*f81fb7c4SAndroid Build Coastguard Worker	CTRL_RPN_MSB : 0x7f}
181*f81fb7c4SAndroid Build Coastguard Worker
182*f81fb7c4SAndroid Build Coastguard WorkerDEFAULT_RPN_VALUES = {
183*f81fb7c4SAndroid Build Coastguard Worker	RPN_PITCH_BEND_SENSITIVITY : 0x100,
184*f81fb7c4SAndroid Build Coastguard Worker	RPN_FINE_TUNING : 0,
185*f81fb7c4SAndroid Build Coastguard Worker	RPN_COARSE_TUNING : 1}
186*f81fb7c4SAndroid Build Coastguard Worker
187*f81fb7c4SAndroid Build Coastguard Worker# initialize logger
188*f81fb7c4SAndroid Build Coastguard Workermidi_file_logger = logging.getLogger('MIDI_file')
189*f81fb7c4SAndroid Build Coastguard Workermidi_file_logger.setLevel(logging.NOTSET)
190*f81fb7c4SAndroid Build Coastguard Worker
191*f81fb7c4SAndroid Build Coastguard Worker
192*f81fb7c4SAndroid Build Coastguard Workerclass trackGrid(object):
193*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, track, channel, name, empty):
194*f81fb7c4SAndroid Build Coastguard Worker		self.track = track
195*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
196*f81fb7c4SAndroid Build Coastguard Worker		self.name = name
197*f81fb7c4SAndroid Build Coastguard Worker		self.empty = empty
198*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
199*f81fb7c4SAndroid Build Coastguard Worker		return "['%s', '%s', '%s']" % (self.track, self.channel, self.name)
200*f81fb7c4SAndroid Build Coastguard Worker
201*f81fb7c4SAndroid Build Coastguard Worker
202*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
203*f81fb7c4SAndroid Build Coastguard Worker# MIDIFileException
204*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
205*f81fb7c4SAndroid Build Coastguard Workerclass MIDIFileException (Exception):
206*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, stream, msg):
207*f81fb7c4SAndroid Build Coastguard Worker		stream.error_loc = stream.tell()
208*f81fb7c4SAndroid Build Coastguard Worker		self.stream = stream
209*f81fb7c4SAndroid Build Coastguard Worker		self.msg = msg
210*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
211*f81fb7c4SAndroid Build Coastguard Worker		return '[%d]: %s' % (self.stream.error_loc, self.msg)
212*f81fb7c4SAndroid Build Coastguard Worker
213*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
214*f81fb7c4SAndroid Build Coastguard Worker# TimeBase
215*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
216*f81fb7c4SAndroid Build Coastguard Workerclass TimeBase (object):
217*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ppqn=DEFAULT_PPQN, beats_per_measure=DEFAULT_BEATS_PER_MEASURE):
218*f81fb7c4SAndroid Build Coastguard Worker		self.ppqn = ppqn
219*f81fb7c4SAndroid Build Coastguard Worker		self.beats_per_measure = beats_per_measure
220*f81fb7c4SAndroid Build Coastguard Worker
221*f81fb7c4SAndroid Build Coastguard Worker	def ConvertToTicks (self, measures, beats, ticks):
222*f81fb7c4SAndroid Build Coastguard Worker		total_beats = beats + (measures * self.beats_per_measure)
223*f81fb7c4SAndroid Build Coastguard Worker		total_ticks = ticks + (total_beats * self.ppqn)
224*f81fb7c4SAndroid Build Coastguard Worker		return total_ticks
225*f81fb7c4SAndroid Build Coastguard Worker
226*f81fb7c4SAndroid Build Coastguard Worker	def ConvertTicksToMBT (self, ticks):
227*f81fb7c4SAndroid Build Coastguard Worker		beats = ticks / self.ppqn
228*f81fb7c4SAndroid Build Coastguard Worker		ticks -= beats * self.ppqn
229*f81fb7c4SAndroid Build Coastguard Worker		measures = beats / self.beats_per_measure
230*f81fb7c4SAndroid Build Coastguard Worker		beats -= measures * self.beats_per_measure
231*f81fb7c4SAndroid Build Coastguard Worker		return (measures, beats, ticks)
232*f81fb7c4SAndroid Build Coastguard Worker
233*f81fb7c4SAndroid Build Coastguard Worker	def ConvertTicksToStr (self, ticks, format=DEFAULT_TIME_FORMAT):
234*f81fb7c4SAndroid Build Coastguard Worker		measures, beats, ticks = self.ConvertTicksToMBT(ticks)
235*f81fb7c4SAndroid Build Coastguard Worker		return format % (measures, beats, ticks)
236*f81fb7c4SAndroid Build Coastguard Worker
237*f81fb7c4SAndroid Build Coastguard Worker	def ConvertStrTimeToTuple(self, s):
238*f81fb7c4SAndroid Build Coastguard Worker		try:
239*f81fb7c4SAndroid Build Coastguard Worker			measures, beats, ticks = s.split(':',3)
240*f81fb7c4SAndroid Build Coastguard Worker			return (int(measures), int(beats), int(ticks))
241*f81fb7c4SAndroid Build Coastguard Worker		except:
242*f81fb7c4SAndroid Build Coastguard Worker			return (0,0,0)
243*f81fb7c4SAndroid Build Coastguard Worker
244*f81fb7c4SAndroid Build Coastguard Worker	def ConvertStrTimeToTicks(self, s):
245*f81fb7c4SAndroid Build Coastguard Worker		measures, beats, ticks = self.ConvertStrTimeToTuple(s)
246*f81fb7c4SAndroid Build Coastguard Worker		return self.ConvertToTicks(measures, beats, ticks)
247*f81fb7c4SAndroid Build Coastguard Worker
248*f81fb7c4SAndroid Build Coastguard Worker	def MbtDifference(self, mbt1, mbt2):
249*f81fb7c4SAndroid Build Coastguard Worker		t1 = self.ConvertToTicks(mbt1[0], mbt1[1], mbt1[2])
250*f81fb7c4SAndroid Build Coastguard Worker		t2 = self.ConvertToTicks(mbt2[0], mbt2[1], mbt2[2])
251*f81fb7c4SAndroid Build Coastguard Worker		return abs(t1-t2)
252*f81fb7c4SAndroid Build Coastguard Worker
253*f81fb7c4SAndroid Build Coastguard Worker
254*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
255*f81fb7c4SAndroid Build Coastguard Worker# Helper functions
256*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
257*f81fb7c4SAndroid Build Coastguard Workerdef ReadByte (stream):
258*f81fb7c4SAndroid Build Coastguard Worker	try:
259*f81fb7c4SAndroid Build Coastguard Worker		return ord(stream.read(1))
260*f81fb7c4SAndroid Build Coastguard Worker	except TypeError:
261*f81fb7c4SAndroid Build Coastguard Worker		stream.error_loc = stream.tell()
262*f81fb7c4SAndroid Build Coastguard Worker		raise MIDIFileException(stream, 'Unexpected EOF')
263*f81fb7c4SAndroid Build Coastguard Worker
264*f81fb7c4SAndroid Build Coastguard Workerdef ReadBytes (stream, length):
265*f81fb7c4SAndroid Build Coastguard Worker	bytes = []
266*f81fb7c4SAndroid Build Coastguard Worker	for i in range(length):
267*f81fb7c4SAndroid Build Coastguard Worker		bytes.append(ReadByte(stream))
268*f81fb7c4SAndroid Build Coastguard Worker	return bytes
269*f81fb7c4SAndroid Build Coastguard Worker
270*f81fb7c4SAndroid Build Coastguard Workerdef ReadVarLenQty (stream):
271*f81fb7c4SAndroid Build Coastguard Worker	value = 0
272*f81fb7c4SAndroid Build Coastguard Worker	while 1:
273*f81fb7c4SAndroid Build Coastguard Worker		byte = ReadByte(stream)
274*f81fb7c4SAndroid Build Coastguard Worker		value = (value << 7) + (byte & 0x7f)
275*f81fb7c4SAndroid Build Coastguard Worker		if byte & 0x80 == 0:
276*f81fb7c4SAndroid Build Coastguard Worker			return value
277*f81fb7c4SAndroid Build Coastguard Worker
278*f81fb7c4SAndroid Build Coastguard Workerdef WriteByte (stream, value):
279*f81fb7c4SAndroid Build Coastguard Worker	stream.write(chr(value))
280*f81fb7c4SAndroid Build Coastguard Worker
281*f81fb7c4SAndroid Build Coastguard Workerdef WriteBytes (stream, bytes):
282*f81fb7c4SAndroid Build Coastguard Worker	for byte in bytes:
283*f81fb7c4SAndroid Build Coastguard Worker		WriteByte(stream, byte)
284*f81fb7c4SAndroid Build Coastguard Worker
285*f81fb7c4SAndroid Build Coastguard Workerdef WriteVarLenQty (stream, value):
286*f81fb7c4SAndroid Build Coastguard Worker	bytes = [value & 0x7f]
287*f81fb7c4SAndroid Build Coastguard Worker	value = value >> 7
288*f81fb7c4SAndroid Build Coastguard Worker	while value > 0:
289*f81fb7c4SAndroid Build Coastguard Worker		bytes.append((value & 0x7f) | 0x80)
290*f81fb7c4SAndroid Build Coastguard Worker		value = value >> 7
291*f81fb7c4SAndroid Build Coastguard Worker	bytes.reverse()
292*f81fb7c4SAndroid Build Coastguard Worker	WriteBytes(stream, bytes)
293*f81fb7c4SAndroid Build Coastguard Worker
294*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
295*f81fb7c4SAndroid Build Coastguard Worker# EventFilter
296*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
297*f81fb7c4SAndroid Build Coastguard Workerclass EventFilter (object):
298*f81fb7c4SAndroid Build Coastguard Worker	pass
299*f81fb7c4SAndroid Build Coastguard Worker
300*f81fb7c4SAndroid Build Coastguard Workerclass EventTypeFilter (object):
301*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, events, exclude=True):
302*f81fb7c4SAndroid Build Coastguard Worker		self.events = events
303*f81fb7c4SAndroid Build Coastguard Worker		self.exclude = exclude
304*f81fb7c4SAndroid Build Coastguard Worker	def Check (self, event):
305*f81fb7c4SAndroid Build Coastguard Worker		if event.msg_type in self.events:
306*f81fb7c4SAndroid Build Coastguard Worker			return not self.exclude
307*f81fb7c4SAndroid Build Coastguard Worker		return self.exclude
308*f81fb7c4SAndroid Build Coastguard Worker
309*f81fb7c4SAndroid Build Coastguard Workerclass NoteFilter (EventFilter):
310*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, notes, exclude=True):
311*f81fb7c4SAndroid Build Coastguard Worker		self.notes = notes
312*f81fb7c4SAndroid Build Coastguard Worker		self.exclude = exclude
313*f81fb7c4SAndroid Build Coastguard Worker	def Check (self, event):
314*f81fb7c4SAndroid Build Coastguard Worker		if event.msg_type in (NOTE_ON, NOTE_OFF):
315*f81fb7c4SAndroid Build Coastguard Worker			if event.note in self.notes:
316*f81fb7c4SAndroid Build Coastguard Worker				return not self.exclude
317*f81fb7c4SAndroid Build Coastguard Worker		return self.exclude
318*f81fb7c4SAndroid Build Coastguard Worker
319*f81fb7c4SAndroid Build Coastguard Workerclass ChannelFilter (EventFilter):
320*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, channel, exclude=True):
321*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
322*f81fb7c4SAndroid Build Coastguard Worker		self.exclude = exclude
323*f81fb7c4SAndroid Build Coastguard Worker	def Check (self, event):
324*f81fb7c4SAndroid Build Coastguard Worker		if event.msg_type in (NOTE_ON, NOTE_OFF, POLY_KEY_PRESSURE, CONTROL_CHANGE, CHANNEL_PRESSURE, PITCH_BEND):
325*f81fb7c4SAndroid Build Coastguard Worker			if event.channel in self.channel:
326*f81fb7c4SAndroid Build Coastguard Worker				return not self.exclude
327*f81fb7c4SAndroid Build Coastguard Worker		return self.exclude
328*f81fb7c4SAndroid Build Coastguard Worker
329*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
330*f81fb7c4SAndroid Build Coastguard Worker# MIDIEvent
331*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
332*f81fb7c4SAndroid Build Coastguard Workerclass MIDIEvent (object):
333*f81fb7c4SAndroid Build Coastguard Worker	"""Factory for creating MIDI events from a stream."""
334*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
335*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
336*f81fb7c4SAndroid Build Coastguard Worker		if msg_type == SYSEX:
337*f81fb7c4SAndroid Build Coastguard Worker			return SysExEvent.ReadFromStream(stream, seq, ticks, msg_type)
338*f81fb7c4SAndroid Build Coastguard Worker		elif msg_type == END_SYSEX:
339*f81fb7c4SAndroid Build Coastguard Worker			return SysExContEvent.ReadFromStream(stream, seq, ticks, msg_type)
340*f81fb7c4SAndroid Build Coastguard Worker		elif msg_type == META_EVENT:
341*f81fb7c4SAndroid Build Coastguard Worker			return MetaEvent.ReadFromStream(stream, seq, ticks, msg_type)
342*f81fb7c4SAndroid Build Coastguard Worker		else:
343*f81fb7c4SAndroid Build Coastguard Worker			high_nibble = msg_type & 0xf0
344*f81fb7c4SAndroid Build Coastguard Worker			if high_nibble == NOTE_OFF:
345*f81fb7c4SAndroid Build Coastguard Worker				return NoteOffEvent.ReadFromStream(stream, seq, ticks, msg_type)
346*f81fb7c4SAndroid Build Coastguard Worker			elif high_nibble == NOTE_ON:
347*f81fb7c4SAndroid Build Coastguard Worker				return NoteOnEvent.ReadFromStream(stream, seq, ticks, msg_type)
348*f81fb7c4SAndroid Build Coastguard Worker			elif high_nibble == POLY_KEY_PRESSURE:
349*f81fb7c4SAndroid Build Coastguard Worker				return PolyKeyPressureEvent.ReadFromStream(stream, seq, ticks, msg_type)
350*f81fb7c4SAndroid Build Coastguard Worker			elif high_nibble == CONTROL_CHANGE:
351*f81fb7c4SAndroid Build Coastguard Worker				return ControlChangeEvent.ReadFromStream(stream, seq, ticks, msg_type)
352*f81fb7c4SAndroid Build Coastguard Worker			elif high_nibble == PROGRAM_CHANGE:
353*f81fb7c4SAndroid Build Coastguard Worker				return ProgramChangeEvent.ReadFromStream(stream, seq, ticks, msg_type)
354*f81fb7c4SAndroid Build Coastguard Worker			elif high_nibble == CHANNEL_PRESSURE:
355*f81fb7c4SAndroid Build Coastguard Worker				return ChannelPressureEvent.ReadFromStream(stream, seq, ticks, msg_type)
356*f81fb7c4SAndroid Build Coastguard Worker			elif high_nibble == PITCH_BEND:
357*f81fb7c4SAndroid Build Coastguard Worker				return PitchBendEvent.ReadFromStream(stream, seq, ticks, msg_type)
358*f81fb7c4SAndroid Build Coastguard Worker			else:
359*f81fb7c4SAndroid Build Coastguard Worker				stream.Warning('Ignoring unexpected message type 0x%02x' % msg_type)
360*f81fb7c4SAndroid Build Coastguard Worker	def WriteTicks (self, stream, track):
361*f81fb7c4SAndroid Build Coastguard Worker		WriteVarLenQty(stream, self.ticks - track.ticks)
362*f81fb7c4SAndroid Build Coastguard Worker		track.ticks = self.ticks
363*f81fb7c4SAndroid Build Coastguard Worker	def WriteRunningStatus (self, stream, track, filters, msg, data1, data2=None):
364*f81fb7c4SAndroid Build Coastguard Worker		if not self.CheckFilters(filters):
365*f81fb7c4SAndroid Build Coastguard Worker			return
366*f81fb7c4SAndroid Build Coastguard Worker		self.WriteTicks(stream, track)
367*f81fb7c4SAndroid Build Coastguard Worker		status = msg + self.channel
368*f81fb7c4SAndroid Build Coastguard Worker		if track.running_status != status:
369*f81fb7c4SAndroid Build Coastguard Worker			WriteByte(stream, status)
370*f81fb7c4SAndroid Build Coastguard Worker			track.running_status = status
371*f81fb7c4SAndroid Build Coastguard Worker		WriteByte(stream, data1)
372*f81fb7c4SAndroid Build Coastguard Worker		if data2 is not None:
373*f81fb7c4SAndroid Build Coastguard Worker			WriteByte(stream, data2)
374*f81fb7c4SAndroid Build Coastguard Worker	def CheckFilters (self, filters):
375*f81fb7c4SAndroid Build Coastguard Worker		if filters is None or not len(filters):
376*f81fb7c4SAndroid Build Coastguard Worker			return True
377*f81fb7c4SAndroid Build Coastguard Worker
378*f81fb7c4SAndroid Build Coastguard Worker		# never filter meta-events
379*f81fb7c4SAndroid Build Coastguard Worker		if (self.msg_type == META_EVENT) and (self.meta_type == META_EVENT_END_OF_TRACK):
380*f81fb7c4SAndroid Build Coastguard Worker			return True
381*f81fb7c4SAndroid Build Coastguard Worker
382*f81fb7c4SAndroid Build Coastguard Worker		# check all filters
383*f81fb7c4SAndroid Build Coastguard Worker		for f in filters:
384*f81fb7c4SAndroid Build Coastguard Worker			if not f.Check(self):
385*f81fb7c4SAndroid Build Coastguard Worker				return False
386*f81fb7c4SAndroid Build Coastguard Worker		return True
387*f81fb7c4SAndroid Build Coastguard Worker
388*f81fb7c4SAndroid Build Coastguard Worker	def TimeEventStr (self, timebase):
389*f81fb7c4SAndroid Build Coastguard Worker		return '[%s]: %s' % (timebase.ConvertTicksToStr(self.ticks), self.__str__())
390*f81fb7c4SAndroid Build Coastguard Worker
391*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
392*f81fb7c4SAndroid Build Coastguard Worker# NoteOffEvent
393*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
394*f81fb7c4SAndroid Build Coastguard Workerclass NoteOffEvent (MIDIEvent):
395*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, channel, note, velocity):
396*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'NoteOff'
397*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = NOTE_OFF
398*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
399*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
400*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
401*f81fb7c4SAndroid Build Coastguard Worker		self.note = note
402*f81fb7c4SAndroid Build Coastguard Worker		self.velocity = velocity
403*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
404*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
405*f81fb7c4SAndroid Build Coastguard Worker		ticks = ticks
406*f81fb7c4SAndroid Build Coastguard Worker		channel = msg_type & 0x0f
407*f81fb7c4SAndroid Build Coastguard Worker		note = ReadByte(stream)
408*f81fb7c4SAndroid Build Coastguard Worker		velocity = ReadByte(stream)
409*f81fb7c4SAndroid Build Coastguard Worker		if msg_type & 0xf0 != NOTE_OFF:
410*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(-2,1)
411*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
412*f81fb7c4SAndroid Build Coastguard Worker		return NoteOffEvent(ticks, seq, channel, note, velocity)
413*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
414*f81fb7c4SAndroid Build Coastguard Worker		# special case for note-off using zero velocity
415*f81fb7c4SAndroid Build Coastguard Worker		if self.velocity > 0:
416*f81fb7c4SAndroid Build Coastguard Worker			self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
417*f81fb7c4SAndroid Build Coastguard Worker		if track.running_status == (NOTE_OFF + self.channel):
418*f81fb7c4SAndroid Build Coastguard Worker			self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
419*f81fb7c4SAndroid Build Coastguard Worker		else:
420*f81fb7c4SAndroid Build Coastguard Worker			self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, 0)
421*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
422*f81fb7c4SAndroid Build Coastguard Worker		return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.velocity)
423*f81fb7c4SAndroid Build Coastguard Worker
424*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
425*f81fb7c4SAndroid Build Coastguard Worker# NoteOnEvent
426*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
427*f81fb7c4SAndroid Build Coastguard Workerclass NoteOnEvent (MIDIEvent):
428*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, channel, note, velocity, note_length, note_off_velocity):
429*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'NoteOn'
430*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = NOTE_ON
431*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
432*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
433*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
434*f81fb7c4SAndroid Build Coastguard Worker		self.note = note
435*f81fb7c4SAndroid Build Coastguard Worker		self.velocity = velocity
436*f81fb7c4SAndroid Build Coastguard Worker		self.note_length = note_length
437*f81fb7c4SAndroid Build Coastguard Worker		self.note_off_velocity = note_off_velocity
438*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
439*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
440*f81fb7c4SAndroid Build Coastguard Worker		channel = msg_type & 0x0f
441*f81fb7c4SAndroid Build Coastguard Worker		note = ReadByte(stream)
442*f81fb7c4SAndroid Build Coastguard Worker		velocity = ReadByte(stream)
443*f81fb7c4SAndroid Build Coastguard Worker		if msg_type & 0xf0 != NOTE_ON:
444*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(-2,1)
445*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
446*f81fb7c4SAndroid Build Coastguard Worker		if velocity == 0:
447*f81fb7c4SAndroid Build Coastguard Worker			return NoteOffEvent(ticks, seq, channel, note, velocity)
448*f81fb7c4SAndroid Build Coastguard Worker		return NoteOnEvent(ticks, seq, channel, note, velocity, None, None)
449*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
450*f81fb7c4SAndroid Build Coastguard Worker		self.WriteRunningStatus(stream, track, filters, NOTE_ON, self.note, self.velocity)
451*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
452*f81fb7c4SAndroid Build Coastguard Worker		if self.note_length is not None:
453*f81fb7c4SAndroid Build Coastguard Worker			return '%s: ch=%d n=%d v=%d l=%d' % (self.name, self.channel, self.note, self.velocity, self.note_length)
454*f81fb7c4SAndroid Build Coastguard Worker		else:
455*f81fb7c4SAndroid Build Coastguard Worker			return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.velocity)
456*f81fb7c4SAndroid Build Coastguard Worker
457*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
458*f81fb7c4SAndroid Build Coastguard Worker# PolyKeyPressureEvent
459*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
460*f81fb7c4SAndroid Build Coastguard Workerclass PolyKeyPressureEvent (MIDIEvent):
461*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, channel, note, value):
462*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'PolyKeyPressure'
463*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = POLY_KEY_PRESSURE
464*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
465*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
466*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
467*f81fb7c4SAndroid Build Coastguard Worker		self.note = note
468*f81fb7c4SAndroid Build Coastguard Worker		self.value = value
469*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
470*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
471*f81fb7c4SAndroid Build Coastguard Worker		channel = msg_type & 0x0f
472*f81fb7c4SAndroid Build Coastguard Worker		note = ReadByte(stream)
473*f81fb7c4SAndroid Build Coastguard Worker		value = ReadByte(stream)
474*f81fb7c4SAndroid Build Coastguard Worker		if msg_type & 0xf0 != POLY_KEY_PRESSURE:
475*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(-2,1)
476*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
477*f81fb7c4SAndroid Build Coastguard Worker		return PolyKeyPressureEvent(ticks, seq, channel, note, value)
478*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
479*f81fb7c4SAndroid Build Coastguard Worker		self.WriteRunningStatus(stream, track, filters, POLY_KEY_PRESSURE, self.note, self.value)
480*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
481*f81fb7c4SAndroid Build Coastguard Worker		return '%s: ch=%d n=%d v=%d' % (self.name, self.channel, self.note, self.value)
482*f81fb7c4SAndroid Build Coastguard Worker
483*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
484*f81fb7c4SAndroid Build Coastguard Worker# ControlChangeEvent
485*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
486*f81fb7c4SAndroid Build Coastguard Workerclass ControlChangeEvent (MIDIEvent):
487*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, channel, controller, value):
488*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'ControlChange'
489*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = CONTROL_CHANGE
490*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
491*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
492*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
493*f81fb7c4SAndroid Build Coastguard Worker		self.controller = controller
494*f81fb7c4SAndroid Build Coastguard Worker		self.value = value
495*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
496*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
497*f81fb7c4SAndroid Build Coastguard Worker		channel = msg_type & 0x0f
498*f81fb7c4SAndroid Build Coastguard Worker		controller = ReadByte(stream)
499*f81fb7c4SAndroid Build Coastguard Worker		value = ReadByte(stream)
500*f81fb7c4SAndroid Build Coastguard Worker		if msg_type & 0xf0 != CONTROL_CHANGE:
501*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(-2,1)
502*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
503*f81fb7c4SAndroid Build Coastguard Worker		if controller >= 120:
504*f81fb7c4SAndroid Build Coastguard Worker			return ChannelModeEvent(ticks, seq, channel, controller, value)
505*f81fb7c4SAndroid Build Coastguard Worker		return ControlChangeEvent(ticks, seq, channel, controller, value)
506*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
507*f81fb7c4SAndroid Build Coastguard Worker		self.WriteRunningStatus(stream, track, filters, CONTROL_CHANGE, self.controller, self.value)
508*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
509*f81fb7c4SAndroid Build Coastguard Worker		return '%s: ch=%d c=%d v=%d' % (self.name, self.channel, self.controller, self.value)
510*f81fb7c4SAndroid Build Coastguard Worker
511*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
512*f81fb7c4SAndroid Build Coastguard Worker# ChannelModeEvent
513*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
514*f81fb7c4SAndroid Build Coastguard Workerclass ChannelModeEvent (MIDIEvent):
515*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, channel, controller, value):
516*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'ChannelMode'
517*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = CONTROL_CHANGE
518*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
519*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
520*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
521*f81fb7c4SAndroid Build Coastguard Worker		self.controller = controller
522*f81fb7c4SAndroid Build Coastguard Worker		self.value = value
523*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
524*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
525*f81fb7c4SAndroid Build Coastguard Worker		channel = msg_type & 0x0f
526*f81fb7c4SAndroid Build Coastguard Worker		controller = ReadByte(stream)
527*f81fb7c4SAndroid Build Coastguard Worker		value = ReadByte(stream)
528*f81fb7c4SAndroid Build Coastguard Worker		if msg_type & 0xf0 != CONTROL_CHANGE:
529*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(-2,1)
530*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
531*f81fb7c4SAndroid Build Coastguard Worker		if  controller < 120:
532*f81fb7c4SAndroid Build Coastguard Worker			return ControlChangeEvent(ticks, seq, channel, controller, value)
533*f81fb7c4SAndroid Build Coastguard Worker		return ChannelModeEvent(ticks, seq, channel, value)
534*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
535*f81fb7c4SAndroid Build Coastguard Worker		self.WriteRunningStatus(stream, track, filters, CONTROL_CHANGE, self.controller, self.value)
536*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
537*f81fb7c4SAndroid Build Coastguard Worker		return '%s: ch=%d c=%d v=%d' % (self.name, self.channel, self.controller, self.value)
538*f81fb7c4SAndroid Build Coastguard Worker
539*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
540*f81fb7c4SAndroid Build Coastguard Worker# ProgramChangeEvent
541*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
542*f81fb7c4SAndroid Build Coastguard Workerclass ProgramChangeEvent (MIDIEvent):
543*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, channel, program):
544*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'ProgramChange'
545*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = PROGRAM_CHANGE
546*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
547*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
548*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
549*f81fb7c4SAndroid Build Coastguard Worker		self.program = program
550*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
551*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
552*f81fb7c4SAndroid Build Coastguard Worker		channel = msg_type & 0x0f
553*f81fb7c4SAndroid Build Coastguard Worker		program = ReadByte(stream)
554*f81fb7c4SAndroid Build Coastguard Worker		if msg_type & 0xf0 != PROGRAM_CHANGE:
555*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(-1,1)
556*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
557*f81fb7c4SAndroid Build Coastguard Worker		return ProgramChangeEvent(ticks, seq, channel, program)
558*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
559*f81fb7c4SAndroid Build Coastguard Worker		self.WriteRunningStatus(stream, track, filters, PROGRAM_CHANGE, self.program)
560*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
561*f81fb7c4SAndroid Build Coastguard Worker		return '%s: ch=%d p=%d' % (self.name, self.channel, self.program)
562*f81fb7c4SAndroid Build Coastguard Worker
563*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
564*f81fb7c4SAndroid Build Coastguard Worker# ChannelPressureEvent
565*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
566*f81fb7c4SAndroid Build Coastguard Workerclass ChannelPressureEvent (MIDIEvent):
567*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, channel, value):
568*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'ChannelPressure'
569*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = CHANNEL_PRESSURE
570*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
571*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
572*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
573*f81fb7c4SAndroid Build Coastguard Worker		self.value = value
574*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
575*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
576*f81fb7c4SAndroid Build Coastguard Worker		channel = msg_type & 0x0f
577*f81fb7c4SAndroid Build Coastguard Worker		value = ReadByte(stream)
578*f81fb7c4SAndroid Build Coastguard Worker		if msg_type & 0xf0 != CHANNEL_PRESSURE:
579*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(-1,1)
580*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
581*f81fb7c4SAndroid Build Coastguard Worker		return ChannelPressureEvent(ticks, seq, channel, value)
582*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
583*f81fb7c4SAndroid Build Coastguard Worker		self.WriteRunningStatus(stream, track, filters, CHANNEL_PRESSURE, self.value)
584*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
585*f81fb7c4SAndroid Build Coastguard Worker		return '%s: ch=%d v=%d' % (self.name, self.channel, self.value)
586*f81fb7c4SAndroid Build Coastguard Worker
587*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
588*f81fb7c4SAndroid Build Coastguard Worker# PitchBendEvent
589*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
590*f81fb7c4SAndroid Build Coastguard Workerclass PitchBendEvent (MIDIEvent):
591*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, channel, value):
592*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'PitchBend'
593*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = PITCH_BEND
594*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
595*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
596*f81fb7c4SAndroid Build Coastguard Worker		self.channel = channel
597*f81fb7c4SAndroid Build Coastguard Worker		self.value = value
598*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
599*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
600*f81fb7c4SAndroid Build Coastguard Worker		channel = msg_type & 0x0f
601*f81fb7c4SAndroid Build Coastguard Worker		value = (ReadByte(stream) << 7) + ReadByte(stream) - 0x2000
602*f81fb7c4SAndroid Build Coastguard Worker		if msg_type & 0xf0 != PITCH_BEND:
603*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(-2,1)
604*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
605*f81fb7c4SAndroid Build Coastguard Worker		return PitchBendEvent(ticks, seq, channel, value)
606*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
607*f81fb7c4SAndroid Build Coastguard Worker		value = self.value + 0x2000
608*f81fb7c4SAndroid Build Coastguard Worker		if value < 0:
609*f81fb7c4SAndroid Build Coastguard Worker			value = 0
610*f81fb7c4SAndroid Build Coastguard Worker		if value > 0x3fff:
611*f81fb7c4SAndroid Build Coastguard Worker			value = 0x3fff
612*f81fb7c4SAndroid Build Coastguard Worker		self.WriteRunningStatus(stream, track, filters, PITCH_BEND, value >> 7, value & 0x7f)
613*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
614*f81fb7c4SAndroid Build Coastguard Worker		return '%s: ch=%d v=%d' % (self.name, self.channel, self.value)
615*f81fb7c4SAndroid Build Coastguard Worker
616*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
617*f81fb7c4SAndroid Build Coastguard Worker# SysExEvent
618*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
619*f81fb7c4SAndroid Build Coastguard Workerclass SysExEvent (MIDIEvent):
620*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, msg):
621*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'SysEx'
622*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = SYSEX
623*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
624*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
625*f81fb7c4SAndroid Build Coastguard Worker		self.length = len(msg)
626*f81fb7c4SAndroid Build Coastguard Worker		self.msg = msg
627*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
628*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
629*f81fb7c4SAndroid Build Coastguard Worker		pos = stream.tell()
630*f81fb7c4SAndroid Build Coastguard Worker		length = ReadVarLenQty(stream)
631*f81fb7c4SAndroid Build Coastguard Worker		msg = ReadBytes(stream, length)
632*f81fb7c4SAndroid Build Coastguard Worker		if msg_type != SYSEX:
633*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(pos,0)
634*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
635*f81fb7c4SAndroid Build Coastguard Worker		return SysExEvent(ticks, seq, msg)
636*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
637*f81fb7c4SAndroid Build Coastguard Worker		if not self.CheckFilters(filters):
638*f81fb7c4SAndroid Build Coastguard Worker			return
639*f81fb7c4SAndroid Build Coastguard Worker		self.WriteTicks(stream, track)
640*f81fb7c4SAndroid Build Coastguard Worker		WriteByte(stream, SYSEX)
641*f81fb7c4SAndroid Build Coastguard Worker		WriteVarLenQty(stream, self.length)
642*f81fb7c4SAndroid Build Coastguard Worker		WriteBytes(stream, self.msg)
643*f81fb7c4SAndroid Build Coastguard Worker		track.running_status = None
644*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
645*f81fb7c4SAndroid Build Coastguard Worker		fmt_str = '%s: f0' + ' %02x'*self.length
646*f81fb7c4SAndroid Build Coastguard Worker		return fmt_str % ((self.name,) + tuple(self.msg))
647*f81fb7c4SAndroid Build Coastguard Worker
648*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
649*f81fb7c4SAndroid Build Coastguard Worker# SysExContEvent
650*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
651*f81fb7c4SAndroid Build Coastguard Workerclass SysExContEvent (MIDIEvent):
652*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, msg):
653*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'SysEx+'
654*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = END_SYSEX
655*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
656*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
657*f81fb7c4SAndroid Build Coastguard Worker		self.length = len(msg)
658*f81fb7c4SAndroid Build Coastguard Worker		self.msg = msg
659*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
660*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
661*f81fb7c4SAndroid Build Coastguard Worker		pos = stream.tell()
662*f81fb7c4SAndroid Build Coastguard Worker		length = ReadVarLenQty(stream)
663*f81fb7c4SAndroid Build Coastguard Worker		msg = ReadBytes(stream, length)
664*f81fb7c4SAndroid Build Coastguard Worker		if msg_type != END_SYSEX:
665*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(pos,0)
666*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
667*f81fb7c4SAndroid Build Coastguard Worker		return SysExContEvent(ticks, seq, msg)
668*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
669*f81fb7c4SAndroid Build Coastguard Worker		if not self.CheckFilters(filters):
670*f81fb7c4SAndroid Build Coastguard Worker			return
671*f81fb7c4SAndroid Build Coastguard Worker		self.WriteTicks(stream, track)
672*f81fb7c4SAndroid Build Coastguard Worker		WriteByte(stream, END_SYSEX)
673*f81fb7c4SAndroid Build Coastguard Worker		WriteVarLenQty(stream, self.length)
674*f81fb7c4SAndroid Build Coastguard Worker		WriteBytes(stream, self.msg)
675*f81fb7c4SAndroid Build Coastguard Worker		track.running_status = None
676*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
677*f81fb7c4SAndroid Build Coastguard Worker		fmt_str = '%s:' + ' %02x'*self.length
678*f81fb7c4SAndroid Build Coastguard Worker		return fmt_str % ((self.name,) + tuple(self.msg))
679*f81fb7c4SAndroid Build Coastguard Worker
680*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
681*f81fb7c4SAndroid Build Coastguard Worker# MetaEvent
682*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
683*f81fb7c4SAndroid Build Coastguard Workerclass MetaEvent (MIDIEvent):
684*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, ticks, seq, meta_type, msg):
685*f81fb7c4SAndroid Build Coastguard Worker		self.name = 'MetaEvent'
686*f81fb7c4SAndroid Build Coastguard Worker		self.msg_type = META_EVENT
687*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = ticks
688*f81fb7c4SAndroid Build Coastguard Worker		self.seq = seq
689*f81fb7c4SAndroid Build Coastguard Worker		self.meta_type = meta_type
690*f81fb7c4SAndroid Build Coastguard Worker		self.length = len(msg)
691*f81fb7c4SAndroid Build Coastguard Worker		self.msg = msg
692*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
693*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (stream, seq, ticks, msg_type):
694*f81fb7c4SAndroid Build Coastguard Worker		pos = stream.tell()
695*f81fb7c4SAndroid Build Coastguard Worker		meta_type = ReadByte(stream)
696*f81fb7c4SAndroid Build Coastguard Worker		length = ReadVarLenQty(stream)
697*f81fb7c4SAndroid Build Coastguard Worker		msg = ReadBytes(stream, length)
698*f81fb7c4SAndroid Build Coastguard Worker		if msg_type != META_EVENT:
699*f81fb7c4SAndroid Build Coastguard Worker			stream.seek(pos,0)
700*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_TYPE_MISMATCH)
701*f81fb7c4SAndroid Build Coastguard Worker		obj = MetaEvent(ticks, seq, meta_type, msg)
702*f81fb7c4SAndroid Build Coastguard Worker		return obj
703*f81fb7c4SAndroid Build Coastguard Worker	def WriteToStream (self, stream, track, filters=None):
704*f81fb7c4SAndroid Build Coastguard Worker		if not self.CheckFilters(filters):
705*f81fb7c4SAndroid Build Coastguard Worker			return
706*f81fb7c4SAndroid Build Coastguard Worker		self.WriteTicks(stream, track)
707*f81fb7c4SAndroid Build Coastguard Worker		WriteByte(stream, META_EVENT)
708*f81fb7c4SAndroid Build Coastguard Worker		WriteByte(stream, self.meta_type)
709*f81fb7c4SAndroid Build Coastguard Worker		WriteVarLenQty(stream, self.length)
710*f81fb7c4SAndroid Build Coastguard Worker		WriteBytes(stream, self.msg)
711*f81fb7c4SAndroid Build Coastguard Worker		track.running_status = None
712*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
713*f81fb7c4SAndroid Build Coastguard Worker		fmt_str = '%s: %02x' + ' %02x'*self.length
714*f81fb7c4SAndroid Build Coastguard Worker		return fmt_str % ((self.name, self.meta_type) + tuple(self.msg))
715*f81fb7c4SAndroid Build Coastguard Worker
716*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
717*f81fb7c4SAndroid Build Coastguard Worker# MIDIControllers
718*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
719*f81fb7c4SAndroid Build Coastguard Workerclass MIDIControllers (object):
720*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self):
721*f81fb7c4SAndroid Build Coastguard Worker		self.controllers = []
722*f81fb7c4SAndroid Build Coastguard Worker		self.rpns = []
723*f81fb7c4SAndroid Build Coastguard Worker		for channel in range(16):
724*f81fb7c4SAndroid Build Coastguard Worker			self.controllers.append({})
725*f81fb7c4SAndroid Build Coastguard Worker			self.controllers[channel] = copy.deepcopy(DEFAULT_CONTROLLER_VALUES)
726*f81fb7c4SAndroid Build Coastguard Worker			self.rpns.append({})
727*f81fb7c4SAndroid Build Coastguard Worker			self.rpns[channel] = copy.deepcopy(DEFAULT_RPN_VALUES)
728*f81fb7c4SAndroid Build Coastguard Worker		self.pitchbend = [0] * 16
729*f81fb7c4SAndroid Build Coastguard Worker		self.program = [-1] * 16
730*f81fb7c4SAndroid Build Coastguard Worker		self.pressure = [0] * 16
731*f81fb7c4SAndroid Build Coastguard Worker
732*f81fb7c4SAndroid Build Coastguard Worker	def __str__ (self):
733*f81fb7c4SAndroid Build Coastguard Worker		output = []
734*f81fb7c4SAndroid Build Coastguard Worker		for channel in range(16):
735*f81fb7c4SAndroid Build Coastguard Worker			output.append('channel=%d' % channel)
736*f81fb7c4SAndroid Build Coastguard Worker			output.append('  program=%d' % self.program[channel])
737*f81fb7c4SAndroid Build Coastguard Worker			output.append('  pressure=%d' % self.pressure[channel])
738*f81fb7c4SAndroid Build Coastguard Worker
739*f81fb7c4SAndroid Build Coastguard Worker			output.append('  controllers')
740*f81fb7c4SAndroid Build Coastguard Worker			for controller in self.controllers[channel].keys():
741*f81fb7c4SAndroid Build Coastguard Worker				output.append('    %03d: %03d' % (controller, self.controllers[channel][controller]))
742*f81fb7c4SAndroid Build Coastguard Worker
743*f81fb7c4SAndroid Build Coastguard Worker			output.append('  rpns')
744*f81fb7c4SAndroid Build Coastguard Worker			for rpn in self.rpns[channel].keys():
745*f81fb7c4SAndroid Build Coastguard Worker				output.append('    %05d: %05d>' % (controller, self.rpns[channel][rpn]))
746*f81fb7c4SAndroid Build Coastguard Worker		return '\n'.join(output)
747*f81fb7c4SAndroid Build Coastguard Worker
748*f81fb7c4SAndroid Build Coastguard Worker
749*f81fb7c4SAndroid Build Coastguard Worker	def Event (self, event):
750*f81fb7c4SAndroid Build Coastguard Worker		"""Process an event and save any changes in controller values"""
751*f81fb7c4SAndroid Build Coastguard Worker		# process control changes
752*f81fb7c4SAndroid Build Coastguard Worker		if event.msg_type == CONTROL_CHANGE:
753*f81fb7c4SAndroid Build Coastguard Worker			self.ControlChange(event)
754*f81fb7c4SAndroid Build Coastguard Worker		elif event.msg_type == CHANNEL_PRESSURE:
755*f81fb7c4SAndroid Build Coastguard Worker			self.PressureChange(event)
756*f81fb7c4SAndroid Build Coastguard Worker		elif event.msg_type == PROGRAM_CHANGE:
757*f81fb7c4SAndroid Build Coastguard Worker			self.ProgramChange(event)
758*f81fb7c4SAndroid Build Coastguard Worker		elif event.msg_type == PITCH_BEND:
759*f81fb7c4SAndroid Build Coastguard Worker			self.PitchBendChange(event)
760*f81fb7c4SAndroid Build Coastguard Worker
761*f81fb7c4SAndroid Build Coastguard Worker	def PitchBendChange (self, event):
762*f81fb7c4SAndroid Build Coastguard Worker		"""Monitor pitch bend change."""
763*f81fb7c4SAndroid Build Coastguard Worker		self.pitchbend[event.channel] = event.value
764*f81fb7c4SAndroid Build Coastguard Worker
765*f81fb7c4SAndroid Build Coastguard Worker	def ProgramChange (self, event):
766*f81fb7c4SAndroid Build Coastguard Worker		"""Monitor program change."""
767*f81fb7c4SAndroid Build Coastguard Worker		self.program[event.channel] = event.program
768*f81fb7c4SAndroid Build Coastguard Worker
769*f81fb7c4SAndroid Build Coastguard Worker	def ControlChange (self, event):
770*f81fb7c4SAndroid Build Coastguard Worker		"""Monitor control change."""
771*f81fb7c4SAndroid Build Coastguard Worker		controller = event.controller
772*f81fb7c4SAndroid Build Coastguard Worker		if controller in MONITOR_CONTROLLERS:
773*f81fb7c4SAndroid Build Coastguard Worker			channel = event.channel
774*f81fb7c4SAndroid Build Coastguard Worker			self.controllers[channel][controller] = event.value
775*f81fb7c4SAndroid Build Coastguard Worker			if (controller == CTRL_RPN_DATA_MSB) or (controller == CTRL_RPN_DATA_LSB):
776*f81fb7c4SAndroid Build Coastguard Worker				rpn = (self.controllers[channel][CTRL_RPN_MSB] << 7) + self.controllers[channel][CTRL_RPN_LSB]
777*f81fb7c4SAndroid Build Coastguard Worker				if rpn in MONITOR_RPNS:
778*f81fb7c4SAndroid Build Coastguard Worker					value = (self.controllers[channel][CTRL_RPN_DATA_MSB] << 7) + self.controllers[channel][CTRL_RPN_DATA_LSB]
779*f81fb7c4SAndroid Build Coastguard Worker					self.rpns[channel][rpn] = value
780*f81fb7c4SAndroid Build Coastguard Worker
781*f81fb7c4SAndroid Build Coastguard Worker		# reset controllers
782*f81fb7c4SAndroid Build Coastguard Worker		elif event.controller == CTRL_RESET_CONTROLLERS:
783*f81fb7c4SAndroid Build Coastguard Worker			self.ResetControllers[event.channel]
784*f81fb7c4SAndroid Build Coastguard Worker
785*f81fb7c4SAndroid Build Coastguard Worker	def PressureChange (self, event):
786*f81fb7c4SAndroid Build Coastguard Worker		"""Monitor pressure change."""
787*f81fb7c4SAndroid Build Coastguard Worker		self.pressure[event.channel] = event.value
788*f81fb7c4SAndroid Build Coastguard Worker
789*f81fb7c4SAndroid Build Coastguard Worker	def ResetControllers (self, channel):
790*f81fb7c4SAndroid Build Coastguard Worker		"""Reset controllers to default."""
791*f81fb7c4SAndroid Build Coastguard Worker		self.controllers[channel] = DEFAULT_CONTROLLER_VALUES
792*f81fb7c4SAndroid Build Coastguard Worker		self.rpns[channel] = DEFAULT_RPN_VALUES
793*f81fb7c4SAndroid Build Coastguard Worker		self.pressure[channel] = 0
794*f81fb7c4SAndroid Build Coastguard Worker
795*f81fb7c4SAndroid Build Coastguard Worker	def GenerateEventList (self, ticks, ref_values=None):
796*f81fb7c4SAndroid Build Coastguard Worker		"""Generate an event list based on controller differences."""
797*f81fb7c4SAndroid Build Coastguard Worker		events = EventList()
798*f81fb7c4SAndroid Build Coastguard Worker
799*f81fb7c4SAndroid Build Coastguard Worker		# if no reference values, based on default values
800*f81fb7c4SAndroid Build Coastguard Worker		if ref_values is None:
801*f81fb7c4SAndroid Build Coastguard Worker			ref_values = MIDIControllers()
802*f81fb7c4SAndroid Build Coastguard Worker
803*f81fb7c4SAndroid Build Coastguard Worker		# iterate through 16 MIDI channels
804*f81fb7c4SAndroid Build Coastguard Worker		for channel in range(16):
805*f81fb7c4SAndroid Build Coastguard Worker
806*f81fb7c4SAndroid Build Coastguard Worker			# generate RPN changes
807*f81fb7c4SAndroid Build Coastguard Worker			for rpn in self.rpns[channel].keys():
808*f81fb7c4SAndroid Build Coastguard Worker				value = self.rpns[channel][rpn]
809*f81fb7c4SAndroid Build Coastguard Worker				if value != ref_values.rpns[channel][rpn]:
810*f81fb7c4SAndroid Build Coastguard Worker					events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_MSB, rpn >> 7))
811*f81fb7c4SAndroid Build Coastguard Worker					events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_LSB, rpn & 0x7f))
812*f81fb7c4SAndroid Build Coastguard Worker					events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_DATA_MSB, value >> 7))
813*f81fb7c4SAndroid Build Coastguard Worker					events.append(ControlChangeEvent(ticks, -1, channel, CTRL_RPN_DATA_LSB, value & 0x7f))
814*f81fb7c4SAndroid Build Coastguard Worker
815*f81fb7c4SAndroid Build Coastguard Worker			# generate controller changes
816*f81fb7c4SAndroid Build Coastguard Worker			for controller in self.controllers[channel].keys():
817*f81fb7c4SAndroid Build Coastguard Worker				if self.controllers[channel][controller] != ref_values.controllers[channel][controller]:
818*f81fb7c4SAndroid Build Coastguard Worker					events.append(ControlChangeEvent(ticks, -1, channel, controller, self.controllers[channel][controller]))
819*f81fb7c4SAndroid Build Coastguard Worker
820*f81fb7c4SAndroid Build Coastguard Worker			# generate pressure changes
821*f81fb7c4SAndroid Build Coastguard Worker			if self.pressure[channel] != ref_values.pressure[channel]:
822*f81fb7c4SAndroid Build Coastguard Worker				events.append(ChannelPressureEvent(ticks, -1, channel, self.pressure[channel]))
823*f81fb7c4SAndroid Build Coastguard Worker
824*f81fb7c4SAndroid Build Coastguard Worker			# generate program changes
825*f81fb7c4SAndroid Build Coastguard Worker			if self.program[channel] != ref_values.program[channel]:
826*f81fb7c4SAndroid Build Coastguard Worker				if self.program[channel] in range(128):
827*f81fb7c4SAndroid Build Coastguard Worker					events.append(ProgramChangeEvent(ticks, -1, channel, self.program[channel]))
828*f81fb7c4SAndroid Build Coastguard Worker
829*f81fb7c4SAndroid Build Coastguard Worker			# generate pitch bend changes
830*f81fb7c4SAndroid Build Coastguard Worker			if self.pitchbend[channel] != ref_values.pitchbend[channel]:
831*f81fb7c4SAndroid Build Coastguard Worker				if self.pitchbend[channel] in range(-8192,8191):
832*f81fb7c4SAndroid Build Coastguard Worker					events.append(PitchBendEvent(ticks, -1, channel, self.pitchbend[channel]))
833*f81fb7c4SAndroid Build Coastguard Worker
834*f81fb7c4SAndroid Build Coastguard Worker		return events
835*f81fb7c4SAndroid Build Coastguard Worker
836*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
837*f81fb7c4SAndroid Build Coastguard Worker# EventList
838*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
839*f81fb7c4SAndroid Build Coastguard Workerclass EventList (list):
840*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self):
841*f81fb7c4SAndroid Build Coastguard Worker		list.__init__(self)
842*f81fb7c4SAndroid Build Coastguard Worker
843*f81fb7c4SAndroid Build Coastguard Worker	def FixNoteLengths (self):
844*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('Fix note lengths')
845*f81fb7c4SAndroid Build Coastguard Worker
846*f81fb7c4SAndroid Build Coastguard Worker		# search for note-on's in event list
847*f81fb7c4SAndroid Build Coastguard Worker		for index in range(len(self)):
848*f81fb7c4SAndroid Build Coastguard Worker			event = self[index]
849*f81fb7c4SAndroid Build Coastguard Worker			if event.msg_type == NOTE_ON:
850*f81fb7c4SAndroid Build Coastguard Worker				note_off_ticks = event.ticks + event.note_length
851*f81fb7c4SAndroid Build Coastguard Worker
852*f81fb7c4SAndroid Build Coastguard Worker				# check for note-on occuring before end of current note
853*f81fb7c4SAndroid Build Coastguard Worker				for i in range(index + 1, len(self)):
854*f81fb7c4SAndroid Build Coastguard Worker					event_to_check = self[i]
855*f81fb7c4SAndroid Build Coastguard Worker					if event_to_check.ticks >= note_off_ticks:
856*f81fb7c4SAndroid Build Coastguard Worker						break
857*f81fb7c4SAndroid Build Coastguard Worker
858*f81fb7c4SAndroid Build Coastguard Worker					# adjust note length
859*f81fb7c4SAndroid Build Coastguard Worker					if (event_to_check.msg_type == NOTE_ON) and (event_to_check.note == event.note):
860*f81fb7c4SAndroid Build Coastguard Worker						midi_file_logger.debug('Adjusting note length @ %d' % event.ticks)
861*f81fb7c4SAndroid Build Coastguard Worker						event.note_length = event_to_check.ticks - event.ticks
862*f81fb7c4SAndroid Build Coastguard Worker						break
863*f81fb7c4SAndroid Build Coastguard Worker
864*f81fb7c4SAndroid Build Coastguard Worker	def ChaseControllers (self, end_seq, start_seq = 0, values = None):
865*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('ChaseControllers from %d to %d' % (start_seq, end_seq))
866*f81fb7c4SAndroid Build Coastguard Worker
867*f81fb7c4SAndroid Build Coastguard Worker		# initialize controller values
868*f81fb7c4SAndroid Build Coastguard Worker		if values is None:
869*f81fb7c4SAndroid Build Coastguard Worker			values = MIDIControllers()
870*f81fb7c4SAndroid Build Coastguard Worker
871*f81fb7c4SAndroid Build Coastguard Worker		# chase controllers in track
872*f81fb7c4SAndroid Build Coastguard Worker		for i in range(start_seq, min(end_seq, len(self))):
873*f81fb7c4SAndroid Build Coastguard Worker			values.Event(self[i])
874*f81fb7c4SAndroid Build Coastguard Worker
875*f81fb7c4SAndroid Build Coastguard Worker		# return new values
876*f81fb7c4SAndroid Build Coastguard Worker		return values
877*f81fb7c4SAndroid Build Coastguard Worker
878*f81fb7c4SAndroid Build Coastguard Worker	def SelectEvents (self, start, end):
879*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('SelectEvents: %d to %d' % (start, end))
880*f81fb7c4SAndroid Build Coastguard Worker		selected = EventList()
881*f81fb7c4SAndroid Build Coastguard Worker		for event in self:
882*f81fb7c4SAndroid Build Coastguard Worker			if event.ticks >= start:
883*f81fb7c4SAndroid Build Coastguard Worker				if event.ticks >= end:
884*f81fb7c4SAndroid Build Coastguard Worker					break
885*f81fb7c4SAndroid Build Coastguard Worker				midi_file_logger.debug('SelectEvent: %s' % event.__str__())
886*f81fb7c4SAndroid Build Coastguard Worker				selected.append(event)
887*f81fb7c4SAndroid Build Coastguard Worker		return selected
888*f81fb7c4SAndroid Build Coastguard Worker
889*f81fb7c4SAndroid Build Coastguard Worker	def MergeEvents (self, events):
890*f81fb7c4SAndroid Build Coastguard Worker		# copy events and sort them by ticks/sequence#
891*f81fb7c4SAndroid Build Coastguard Worker		self.extend(events)
892*f81fb7c4SAndroid Build Coastguard Worker		self.SortEvents()
893*f81fb7c4SAndroid Build Coastguard Worker
894*f81fb7c4SAndroid Build Coastguard Worker	def InsertEvents (self, events, seq):
895*f81fb7c4SAndroid Build Coastguard Worker		self[seq:seq] = events
896*f81fb7c4SAndroid Build Coastguard Worker		self.RenumberSeq()
897*f81fb7c4SAndroid Build Coastguard Worker
898*f81fb7c4SAndroid Build Coastguard Worker	def DeleteEvents (self, start_index, end_index, move_meta_events=None):
899*f81fb7c4SAndroid Build Coastguard Worker		# default parameters
900*f81fb7c4SAndroid Build Coastguard Worker		if start_index is None:
901*f81fb7c4SAndroid Build Coastguard Worker			start_index = 0
902*f81fb7c4SAndroid Build Coastguard Worker		if end_index is None:
903*f81fb7c4SAndroid Build Coastguard Worker			end_index = len(self)
904*f81fb7c4SAndroid Build Coastguard Worker
905*f81fb7c4SAndroid Build Coastguard Worker		#print("\n")
906*f81fb7c4SAndroid Build Coastguard Worker		#for evt in self[start_index:end_index]:
907*f81fb7c4SAndroid Build Coastguard Worker		#	print("%d %s" % (evt.ticks, evt))
908*f81fb7c4SAndroid Build Coastguard Worker
909*f81fb7c4SAndroid Build Coastguard Worker		# delete events
910*f81fb7c4SAndroid Build Coastguard Worker		delete_count = 0
911*f81fb7c4SAndroid Build Coastguard Worker		move_count = 0
912*f81fb7c4SAndroid Build Coastguard Worker		for event in self[start_index:end_index]:
913*f81fb7c4SAndroid Build Coastguard Worker			#Bth; Added this so we always get clip end events; clips that ended on last measure wouldn't end on repeat
914*f81fb7c4SAndroid Build Coastguard Worker			if (event.msg_type == CONTROL_CHANGE) and \
915*f81fb7c4SAndroid Build Coastguard Worker			        (event.controller == JET_EVENT_TRIGGER_CLIP) and \
916*f81fb7c4SAndroid Build Coastguard Worker			        ((event.value & 0x40) != 0x40):
917*f81fb7c4SAndroid Build Coastguard Worker				pass
918*f81fb7c4SAndroid Build Coastguard Worker			else:
919*f81fb7c4SAndroid Build Coastguard Worker				if (move_meta_events is None) or (event.msg_type != META_EVENT):
920*f81fb7c4SAndroid Build Coastguard Worker					self.remove(event)
921*f81fb7c4SAndroid Build Coastguard Worker					delete_count += 1
922*f81fb7c4SAndroid Build Coastguard Worker
923*f81fb7c4SAndroid Build Coastguard Worker				# move meta-events
924*f81fb7c4SAndroid Build Coastguard Worker				else:
925*f81fb7c4SAndroid Build Coastguard Worker					event.ticks = move_meta_events
926*f81fb7c4SAndroid Build Coastguard Worker					move_count += 1
927*f81fb7c4SAndroid Build Coastguard Worker
928*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('DeleteEvents: deleted %d events in range(%s:%s)' % (delete_count, start_index, end_index))
929*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('DeleteEvents: moved %d events in range(%s:%s)' % (move_count, start_index, end_index))
930*f81fb7c4SAndroid Build Coastguard Worker
931*f81fb7c4SAndroid Build Coastguard Worker
932*f81fb7c4SAndroid Build Coastguard Worker	def SeekEvent (self, pos):
933*f81fb7c4SAndroid Build Coastguard Worker		for i in range(len(self)):
934*f81fb7c4SAndroid Build Coastguard Worker			if self[i].ticks >= pos:
935*f81fb7c4SAndroid Build Coastguard Worker				return i
936*f81fb7c4SAndroid Build Coastguard Worker		return None
937*f81fb7c4SAndroid Build Coastguard Worker
938*f81fb7c4SAndroid Build Coastguard Worker	def RenumberSeq (self):
939*f81fb7c4SAndroid Build Coastguard Worker		seq = 0
940*f81fb7c4SAndroid Build Coastguard Worker		for event in self:
941*f81fb7c4SAndroid Build Coastguard Worker			event.seq = seq
942*f81fb7c4SAndroid Build Coastguard Worker			seq += 1
943*f81fb7c4SAndroid Build Coastguard Worker
944*f81fb7c4SAndroid Build Coastguard Worker	def SortEvents (self):
945*f81fb7c4SAndroid Build Coastguard Worker		self.sort(self.EventSorter)
946*f81fb7c4SAndroid Build Coastguard Worker		self.RenumberSeq()
947*f81fb7c4SAndroid Build Coastguard Worker
948*f81fb7c4SAndroid Build Coastguard Worker	@staticmethod
949*f81fb7c4SAndroid Build Coastguard Worker	def EventSorter (x, y):
950*f81fb7c4SAndroid Build Coastguard Worker		if x.ticks == y.ticks:
951*f81fb7c4SAndroid Build Coastguard Worker			return cmp(x.seq, y.seq)
952*f81fb7c4SAndroid Build Coastguard Worker		else:
953*f81fb7c4SAndroid Build Coastguard Worker			return cmp(x.ticks, y.ticks)
954*f81fb7c4SAndroid Build Coastguard Worker
955*f81fb7c4SAndroid Build Coastguard Worker	def DumpEvents (self, output, timebase):
956*f81fb7c4SAndroid Build Coastguard Worker		if output is not None:
957*f81fb7c4SAndroid Build Coastguard Worker			for event in self:
958*f81fb7c4SAndroid Build Coastguard Worker				output.write('%s\n' % event.TimeEventStr(timebase))
959*f81fb7c4SAndroid Build Coastguard Worker		else:
960*f81fb7c4SAndroid Build Coastguard Worker			for event in self:
961*f81fb7c4SAndroid Build Coastguard Worker				midi_file_logger.debug(event.TimeEventStr(timebase))
962*f81fb7c4SAndroid Build Coastguard Worker
963*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
964*f81fb7c4SAndroid Build Coastguard Worker# MIDITrack
965*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
966*f81fb7c4SAndroid Build Coastguard Workerclass MIDITrack (object):
967*f81fb7c4SAndroid Build Coastguard Worker	"""The MIDITrack class implements methods for reading, parsing,
968*f81fb7c4SAndroid Build Coastguard Worker	modifying, and writing tracks in Standard MIDI Files (SMF).
969*f81fb7c4SAndroid Build Coastguard Worker
970*f81fb7c4SAndroid Build Coastguard Worker	"""
971*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self):
972*f81fb7c4SAndroid Build Coastguard Worker		self.length = 0
973*f81fb7c4SAndroid Build Coastguard Worker		self.events = EventList()
974*f81fb7c4SAndroid Build Coastguard Worker		self.end_of_track = None
975*f81fb7c4SAndroid Build Coastguard Worker		self.channel = None
976*f81fb7c4SAndroid Build Coastguard Worker		self.name = None
977*f81fb7c4SAndroid Build Coastguard Worker
978*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (self, stream, offset, file_size):
979*f81fb7c4SAndroid Build Coastguard Worker		self.stream = stream
980*f81fb7c4SAndroid Build Coastguard Worker		ticks = 0
981*f81fb7c4SAndroid Build Coastguard Worker		seq = 0
982*f81fb7c4SAndroid Build Coastguard Worker		running_status = None
983*f81fb7c4SAndroid Build Coastguard Worker		tick_warning_level = stream.timebase.ppqn * LARGE_TICK_WARNING
984*f81fb7c4SAndroid Build Coastguard Worker
985*f81fb7c4SAndroid Build Coastguard Worker		# read the track header - verify it's an SMF track
986*f81fb7c4SAndroid Build Coastguard Worker		stream.seek(offset)
987*f81fb7c4SAndroid Build Coastguard Worker		bytes = stream.read(struct.calcsize(SMF_TRACK_HEADER_FMT))
988*f81fb7c4SAndroid Build Coastguard Worker		riff_tag, track_len = struct.unpack(SMF_TRACK_HEADER_FMT, bytes)
989*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('SMF track header\n  Tag:      %s\n  TrackLen: %d' % (riff_tag, track_len))
990*f81fb7c4SAndroid Build Coastguard Worker		if (riff_tag != SMF_TRACK_RIFF_TAG):
991*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(stream, MSG_INVALID_TRACK_HEADER)
992*f81fb7c4SAndroid Build Coastguard Worker		self.start = stream.tell()
993*f81fb7c4SAndroid Build Coastguard Worker
994*f81fb7c4SAndroid Build Coastguard Worker		# check for valid track length
995*f81fb7c4SAndroid Build Coastguard Worker		if (self.start + track_len) > file_size:
996*f81fb7c4SAndroid Build Coastguard Worker			stream.Warning('Ignoring illegal track length - %d exceeds length of file' % track_len)
997*f81fb7c4SAndroid Build Coastguard Worker			track_len = None
998*f81fb7c4SAndroid Build Coastguard Worker
999*f81fb7c4SAndroid Build Coastguard Worker		# read the entire track
1000*f81fb7c4SAndroid Build Coastguard Worker		note_on_list = []
1001*f81fb7c4SAndroid Build Coastguard Worker		while 1:
1002*f81fb7c4SAndroid Build Coastguard Worker
1003*f81fb7c4SAndroid Build Coastguard Worker			# save current position
1004*f81fb7c4SAndroid Build Coastguard Worker			pos = stream.tell()
1005*f81fb7c4SAndroid Build Coastguard Worker
1006*f81fb7c4SAndroid Build Coastguard Worker			# check for end of track
1007*f81fb7c4SAndroid Build Coastguard Worker			if track_len is not None:
1008*f81fb7c4SAndroid Build Coastguard Worker				if (pos - self.start) >= track_len:
1009*f81fb7c4SAndroid Build Coastguard Worker					break
1010*f81fb7c4SAndroid Build Coastguard Worker
1011*f81fb7c4SAndroid Build Coastguard Worker			# are we past end of track?
1012*f81fb7c4SAndroid Build Coastguard Worker			if self.end_of_track:
1013*f81fb7c4SAndroid Build Coastguard Worker				stream.Warning('Ignoring data encountered beyond end-of-track meta-event')
1014*f81fb7c4SAndroid Build Coastguard Worker				break;
1015*f81fb7c4SAndroid Build Coastguard Worker
1016*f81fb7c4SAndroid Build Coastguard Worker			# read delta timestamp
1017*f81fb7c4SAndroid Build Coastguard Worker			delta = ReadVarLenQty(stream)
1018*f81fb7c4SAndroid Build Coastguard Worker			if ticks > tick_warning_level:
1019*f81fb7c4SAndroid Build Coastguard Worker				stream.Warning('Tick value is excessive - possibly corrupt data?')
1020*f81fb7c4SAndroid Build Coastguard Worker			ticks += delta
1021*f81fb7c4SAndroid Build Coastguard Worker
1022*f81fb7c4SAndroid Build Coastguard Worker			# get the event type and process it
1023*f81fb7c4SAndroid Build Coastguard Worker			msg_type = ReadByte(stream)
1024*f81fb7c4SAndroid Build Coastguard Worker
1025*f81fb7c4SAndroid Build Coastguard Worker			# if data byte, check for running status
1026*f81fb7c4SAndroid Build Coastguard Worker			if msg_type & 0x80 == 0:
1027*f81fb7c4SAndroid Build Coastguard Worker
1028*f81fb7c4SAndroid Build Coastguard Worker				# use running status
1029*f81fb7c4SAndroid Build Coastguard Worker				msg_type = running_status
1030*f81fb7c4SAndroid Build Coastguard Worker
1031*f81fb7c4SAndroid Build Coastguard Worker				# back up so event can process data
1032*f81fb7c4SAndroid Build Coastguard Worker				stream.seek(-1,1)
1033*f81fb7c4SAndroid Build Coastguard Worker
1034*f81fb7c4SAndroid Build Coastguard Worker				# if no running status, we have a problem
1035*f81fb7c4SAndroid Build Coastguard Worker				if not running_status:
1036*f81fb7c4SAndroid Build Coastguard Worker					stream.Warning('Ignoring data byte received with no running status')
1037*f81fb7c4SAndroid Build Coastguard Worker
1038*f81fb7c4SAndroid Build Coastguard Worker			# create event type from stream
1039*f81fb7c4SAndroid Build Coastguard Worker			event = MIDIEvent.ReadFromStream(stream, seq, ticks, msg_type)
1040*f81fb7c4SAndroid Build Coastguard Worker
1041*f81fb7c4SAndroid Build Coastguard Worker			if self.channel == None:
1042*f81fb7c4SAndroid Build Coastguard Worker				try:
1043*f81fb7c4SAndroid Build Coastguard Worker					self.channel = event.channel
1044*f81fb7c4SAndroid Build Coastguard Worker				except AttributeError:
1045*f81fb7c4SAndroid Build Coastguard Worker					pass
1046*f81fb7c4SAndroid Build Coastguard Worker
1047*f81fb7c4SAndroid Build Coastguard Worker			# track note-ons
1048*f81fb7c4SAndroid Build Coastguard Worker			if event.msg_type == NOTE_ON:
1049*f81fb7c4SAndroid Build Coastguard Worker
1050*f81fb7c4SAndroid Build Coastguard Worker				"""
1051*f81fb7c4SAndroid Build Coastguard Worker				Experimental code to clean up overlapping notes
1052*f81fb7c4SAndroid Build Coastguard Worker				Clean up now occurs during write process
1053*f81fb7c4SAndroid Build Coastguard Worker
1054*f81fb7c4SAndroid Build Coastguard Worker				for note_on in note_on_list:
1055*f81fb7c4SAndroid Build Coastguard Worker					if (event.channel == note_on.channel) and (event.note == note_on.note):
1056*f81fb7c4SAndroid Build Coastguard Worker						stream.Warning('Duplicate note-on\'s encountered without intervening note-off')
1057*f81fb7c4SAndroid Build Coastguard Worker						stream.Warning('  [%s]: %s' % (stream.timebase.ConvertTicksToStr(event.ticks), event.__str__()))
1058*f81fb7c4SAndroid Build Coastguard Worker						note_on.note_length = event.ticks - note_on.ticks - 1
1059*f81fb7c4SAndroid Build Coastguard Worker						if note_on.note_length <= 0:
1060*f81fb7c4SAndroid Build Coastguard Worker							stream.Warning('Eliminating duplicate note-on')
1061*f81fb7c4SAndroid Build Coastguard Worker							event.ticks = note_on.ticks
1062*f81fb7c4SAndroid Build Coastguard Worker							self.events.remove(note_on)
1063*f81fb7c4SAndroid Build Coastguard Worker				"""
1064*f81fb7c4SAndroid Build Coastguard Worker
1065*f81fb7c4SAndroid Build Coastguard Worker				note_on_list.append(event)
1066*f81fb7c4SAndroid Build Coastguard Worker
1067*f81fb7c4SAndroid Build Coastguard Worker			# process note-offs
1068*f81fb7c4SAndroid Build Coastguard Worker			if event.msg_type == NOTE_OFF:
1069*f81fb7c4SAndroid Build Coastguard Worker				for note_on in note_on_list[:]:
1070*f81fb7c4SAndroid Build Coastguard Worker					if (event.channel == note_on.channel) and (event.note == note_on.note):
1071*f81fb7c4SAndroid Build Coastguard Worker						note_on.note_length = event.ticks - note_on.ticks
1072*f81fb7c4SAndroid Build Coastguard Worker						note_on.note_off_velocity = event.velocity
1073*f81fb7c4SAndroid Build Coastguard Worker						note_on_list.remove(note_on)
1074*f81fb7c4SAndroid Build Coastguard Worker						break
1075*f81fb7c4SAndroid Build Coastguard Worker				#else:
1076*f81fb7c4SAndroid Build Coastguard Worker				#	stream.Warning('Note-off encountered without corresponding note-on')
1077*f81fb7c4SAndroid Build Coastguard Worker				#	stream.Warning('  [%s]: %s' % (stream.timebase.ConvertTicksToStr(event.ticks), event.__str__()))
1078*f81fb7c4SAndroid Build Coastguard Worker
1079*f81fb7c4SAndroid Build Coastguard Worker			# check for end of track
1080*f81fb7c4SAndroid Build Coastguard Worker			elif event.msg_type == META_EVENT and event.meta_type == META_EVENT_END_OF_TRACK:
1081*f81fb7c4SAndroid Build Coastguard Worker				self.end_of_track = event.ticks
1082*f81fb7c4SAndroid Build Coastguard Worker
1083*f81fb7c4SAndroid Build Coastguard Worker			# BTH; get track name
1084*f81fb7c4SAndroid Build Coastguard Worker			elif event.msg_type == META_EVENT and event.meta_type == META_EVENT_SEQUENCE_TRACK_NAME:
1085*f81fb7c4SAndroid Build Coastguard Worker				self.name = array.array('B', event.msg).tostring()
1086*f81fb7c4SAndroid Build Coastguard Worker
1087*f81fb7c4SAndroid Build Coastguard Worker			# append event to event list
1088*f81fb7c4SAndroid Build Coastguard Worker			else:
1089*f81fb7c4SAndroid Build Coastguard Worker				self.events.append(event)
1090*f81fb7c4SAndroid Build Coastguard Worker				seq += 1
1091*f81fb7c4SAndroid Build Coastguard Worker
1092*f81fb7c4SAndroid Build Coastguard Worker			# save position for port-mortem
1093*f81fb7c4SAndroid Build Coastguard Worker			stream.last_good_event = pos
1094*f81fb7c4SAndroid Build Coastguard Worker
1095*f81fb7c4SAndroid Build Coastguard Worker			# update running statusc_str(
1096*f81fb7c4SAndroid Build Coastguard Worker			if msg_type < 0xf0:
1097*f81fb7c4SAndroid Build Coastguard Worker				running_status = msg_type
1098*f81fb7c4SAndroid Build Coastguard Worker			elif (msg_type < 0xf8) or (msg_type == 0xff):
1099*f81fb7c4SAndroid Build Coastguard Worker				running_status = None
1100*f81fb7c4SAndroid Build Coastguard Worker
1101*f81fb7c4SAndroid Build Coastguard Worker		# check for stuck notes
1102*f81fb7c4SAndroid Build Coastguard Worker		#if len(note_on_list):
1103*f81fb7c4SAndroid Build Coastguard Worker		#	stream.Warning('Note-ons encountered without corresponding note-offs')
1104*f81fb7c4SAndroid Build Coastguard Worker
1105*f81fb7c4SAndroid Build Coastguard Worker		# check for missing end-of-track meta-event
1106*f81fb7c4SAndroid Build Coastguard Worker		if self.end_of_track is None:
1107*f81fb7c4SAndroid Build Coastguard Worker			self.last_tick = self.events[-1].ticks
1108*f81fb7c4SAndroid Build Coastguard Worker			stream.Warning('End of track encountered with no end-of-track meta-event')
1109*f81fb7c4SAndroid Build Coastguard Worker
1110*f81fb7c4SAndroid Build Coastguard Worker		# if track length was bad, correct it
1111*f81fb7c4SAndroid Build Coastguard Worker		if track_len is None:
1112*f81fb7c4SAndroid Build Coastguard Worker			track_len = stream.tell() - offset - 8
1113*f81fb7c4SAndroid Build Coastguard Worker
1114*f81fb7c4SAndroid Build Coastguard Worker		return track_len
1115*f81fb7c4SAndroid Build Coastguard Worker
1116*f81fb7c4SAndroid Build Coastguard Worker	def Write (self, stream, filters=None):
1117*f81fb7c4SAndroid Build Coastguard Worker		# save current file position so we can write header
1118*f81fb7c4SAndroid Build Coastguard Worker		header_loc = stream.tell()
1119*f81fb7c4SAndroid Build Coastguard Worker		stream.seek(header_loc + struct.calcsize(SMF_TRACK_HEADER_FMT))
1120*f81fb7c4SAndroid Build Coastguard Worker
1121*f81fb7c4SAndroid Build Coastguard Worker		# save a copy of the event list so we can restore it
1122*f81fb7c4SAndroid Build Coastguard Worker		save_events = copy.copy(self.events)
1123*f81fb7c4SAndroid Build Coastguard Worker
1124*f81fb7c4SAndroid Build Coastguard Worker		# create note-off events
1125*f81fb7c4SAndroid Build Coastguard Worker		index = 0
1126*f81fb7c4SAndroid Build Coastguard Worker		while 1:
1127*f81fb7c4SAndroid Build Coastguard Worker			if index >= len(self.events):
1128*f81fb7c4SAndroid Build Coastguard Worker				break
1129*f81fb7c4SAndroid Build Coastguard Worker
1130*f81fb7c4SAndroid Build Coastguard Worker			# if note-on event, create a note-off event
1131*f81fb7c4SAndroid Build Coastguard Worker			event = self.events[index]
1132*f81fb7c4SAndroid Build Coastguard Worker			index += 1
1133*f81fb7c4SAndroid Build Coastguard Worker			if event.msg_type == NOTE_ON:
1134*f81fb7c4SAndroid Build Coastguard Worker				note_off = NoteOffEvent(event.ticks + event.note_length, index, event.channel, event.note, event.note_off_velocity)
1135*f81fb7c4SAndroid Build Coastguard Worker
1136*f81fb7c4SAndroid Build Coastguard Worker				# insert note-off in list
1137*f81fb7c4SAndroid Build Coastguard Worker				for i in range(index, len(self.events)):
1138*f81fb7c4SAndroid Build Coastguard Worker					if self.events[i].ticks >= note_off.ticks:
1139*f81fb7c4SAndroid Build Coastguard Worker						self.events.insert(i, note_off)
1140*f81fb7c4SAndroid Build Coastguard Worker						break
1141*f81fb7c4SAndroid Build Coastguard Worker				else:
1142*f81fb7c4SAndroid Build Coastguard Worker					self.events.append(note_off)
1143*f81fb7c4SAndroid Build Coastguard Worker
1144*f81fb7c4SAndroid Build Coastguard Worker		# renumber list
1145*f81fb7c4SAndroid Build Coastguard Worker		self.events.RenumberSeq()
1146*f81fb7c4SAndroid Build Coastguard Worker
1147*f81fb7c4SAndroid Build Coastguard Worker		# write the events
1148*f81fb7c4SAndroid Build Coastguard Worker		self.running_status = None
1149*f81fb7c4SAndroid Build Coastguard Worker		self.ticks = 0
1150*f81fb7c4SAndroid Build Coastguard Worker		for event in self.events:
1151*f81fb7c4SAndroid Build Coastguard Worker
1152*f81fb7c4SAndroid Build Coastguard Worker			# write event
1153*f81fb7c4SAndroid Build Coastguard Worker			event.WriteToStream(stream, self, filters)
1154*f81fb7c4SAndroid Build Coastguard Worker
1155*f81fb7c4SAndroid Build Coastguard Worker		# restore original list (without note-off events)
1156*f81fb7c4SAndroid Build Coastguard Worker		self.events = save_events
1157*f81fb7c4SAndroid Build Coastguard Worker
1158*f81fb7c4SAndroid Build Coastguard Worker		# write the end-of-track meta-event
1159*f81fb7c4SAndroid Build Coastguard Worker		MetaEvent(self.end_of_track, 0, META_EVENT_END_OF_TRACK,[]).WriteToStream(stream, self, None)
1160*f81fb7c4SAndroid Build Coastguard Worker
1161*f81fb7c4SAndroid Build Coastguard Worker		# write track header
1162*f81fb7c4SAndroid Build Coastguard Worker		end_of_track = stream.tell()
1163*f81fb7c4SAndroid Build Coastguard Worker		track_len = end_of_track - header_loc - struct.calcsize(SMF_TRACK_HEADER_FMT)
1164*f81fb7c4SAndroid Build Coastguard Worker		stream.seek(header_loc)
1165*f81fb7c4SAndroid Build Coastguard Worker		bytes = struct.pack(SMF_TRACK_HEADER_FMT, SMF_TRACK_RIFF_TAG, track_len)
1166*f81fb7c4SAndroid Build Coastguard Worker		stream.write(bytes)
1167*f81fb7c4SAndroid Build Coastguard Worker		stream.seek(end_of_track)
1168*f81fb7c4SAndroid Build Coastguard Worker
1169*f81fb7c4SAndroid Build Coastguard Worker	def Trim (self, start, end, slide=True, chase_controllers=True, delete_meta_events=False, quantize=0):
1170*f81fb7c4SAndroid Build Coastguard Worker		controllers = None
1171*f81fb7c4SAndroid Build Coastguard Worker
1172*f81fb7c4SAndroid Build Coastguard Worker		if quantize:
1173*f81fb7c4SAndroid Build Coastguard Worker			# quantize events just before start
1174*f81fb7c4SAndroid Build Coastguard Worker			for event in self.events.SelectEvents(start - quantize, start):
1175*f81fb7c4SAndroid Build Coastguard Worker				midi_file_logger.debug('Trim: Moving event %s to %d' % (event.__str__(), start))
1176*f81fb7c4SAndroid Build Coastguard Worker				event.ticks = start
1177*f81fb7c4SAndroid Build Coastguard Worker
1178*f81fb7c4SAndroid Build Coastguard Worker			# quantize events just before end
1179*f81fb7c4SAndroid Build Coastguard Worker			for event in self.events.SelectEvents(end - quantize, end):
1180*f81fb7c4SAndroid Build Coastguard Worker				midi_file_logger.debug('Trim: Moving event %s to %d' % (event.__str__(), end))
1181*f81fb7c4SAndroid Build Coastguard Worker				event.ticks = end
1182*f81fb7c4SAndroid Build Coastguard Worker
1183*f81fb7c4SAndroid Build Coastguard Worker		# trim start
1184*f81fb7c4SAndroid Build Coastguard Worker		if start:
1185*f81fb7c4SAndroid Build Coastguard Worker
1186*f81fb7c4SAndroid Build Coastguard Worker			# find first event inside trim
1187*f81fb7c4SAndroid Build Coastguard Worker			start_event = self.events.SeekEvent(start)
1188*f81fb7c4SAndroid Build Coastguard Worker			if start_event is not None:
1189*f81fb7c4SAndroid Build Coastguard Worker
1190*f81fb7c4SAndroid Build Coastguard Worker				# chase controllers to cut point
1191*f81fb7c4SAndroid Build Coastguard Worker				if chase_controllers:
1192*f81fb7c4SAndroid Build Coastguard Worker					controllers = self.events.ChaseControllers(self.events[start_event].seq)
1193*f81fb7c4SAndroid Build Coastguard Worker					controller_events = controllers.GenerateEventList(0)
1194*f81fb7c4SAndroid Build Coastguard Worker					midi_file_logger.debug('Trim: insert new controller events at %d:' % start)
1195*f81fb7c4SAndroid Build Coastguard Worker					controller_events.DumpEvents(None, self.stream.timebase)
1196*f81fb7c4SAndroid Build Coastguard Worker					self.events.InsertEvents(controller_events, start_event)
1197*f81fb7c4SAndroid Build Coastguard Worker
1198*f81fb7c4SAndroid Build Coastguard Worker				# delete events
1199*f81fb7c4SAndroid Build Coastguard Worker				midi_file_logger.debug('Trim: deleting events up to event %d' % start_event)
1200*f81fb7c4SAndroid Build Coastguard Worker				if delete_meta_events:
1201*f81fb7c4SAndroid Build Coastguard Worker					self.events.DeleteEvents(None, start_event, None)
1202*f81fb7c4SAndroid Build Coastguard Worker				else:
1203*f81fb7c4SAndroid Build Coastguard Worker					self.events.DeleteEvents(None, start_event, start)
1204*f81fb7c4SAndroid Build Coastguard Worker
1205*f81fb7c4SAndroid Build Coastguard Worker			# delete everything except metadata
1206*f81fb7c4SAndroid Build Coastguard Worker			else:
1207*f81fb7c4SAndroid Build Coastguard Worker				self.events.DeleteEvents(None, None, start)
1208*f81fb7c4SAndroid Build Coastguard Worker
1209*f81fb7c4SAndroid Build Coastguard Worker		# trim end
1210*f81fb7c4SAndroid Build Coastguard Worker		end_event = self.events.SeekEvent(end)
1211*f81fb7c4SAndroid Build Coastguard Worker		if end_event is not None:
1212*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.debug('Trim: trimming section starting at event %d' % end_event)
1213*f81fb7c4SAndroid Build Coastguard Worker			self.events.DeleteEvents(end_event, None)
1214*f81fb7c4SAndroid Build Coastguard Worker
1215*f81fb7c4SAndroid Build Coastguard Worker		# trim any notes that extend past the end
1216*f81fb7c4SAndroid Build Coastguard Worker		for event in self.events:
1217*f81fb7c4SAndroid Build Coastguard Worker			if event.msg_type == NOTE_ON:
1218*f81fb7c4SAndroid Build Coastguard Worker				if (event.ticks + event.note_length) > end:
1219*f81fb7c4SAndroid Build Coastguard Worker					midi_file_logger.debug('Trim: trimming note that extends past end %s' % event.TimeEventStr(self.stream.timebase))
1220*f81fb7c4SAndroid Build Coastguard Worker					event.note_length = end - event.ticks
1221*f81fb7c4SAndroid Build Coastguard Worker					if event.note_length <= 0:
1222*f81fb7c4SAndroid Build Coastguard Worker						raise 'Error in note length - note should have been deleted'
1223*f81fb7c4SAndroid Build Coastguard Worker
1224*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('Trim: initial end-of-track: %d' % self.end_of_track)
1225*f81fb7c4SAndroid Build Coastguard Worker		self.end_of_track = min(self.end_of_track, end)
1226*f81fb7c4SAndroid Build Coastguard Worker
1227*f81fb7c4SAndroid Build Coastguard Worker		# slide events to start of track to fill hole
1228*f81fb7c4SAndroid Build Coastguard Worker		if slide and start:
1229*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.debug('Trim: sliding events: %d' % start)
1230*f81fb7c4SAndroid Build Coastguard Worker			for event in self.events:
1231*f81fb7c4SAndroid Build Coastguard Worker				if event.ticks > start:
1232*f81fb7c4SAndroid Build Coastguard Worker					event.ticks -= start
1233*f81fb7c4SAndroid Build Coastguard Worker				else:
1234*f81fb7c4SAndroid Build Coastguard Worker					event.ticks = 0
1235*f81fb7c4SAndroid Build Coastguard Worker			self.end_of_track = max(0, self.end_of_track - start)
1236*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('Trim: new end-of-track: %d' % self.end_of_track)
1237*f81fb7c4SAndroid Build Coastguard Worker
1238*f81fb7c4SAndroid Build Coastguard Worker		self.events.RenumberSeq()
1239*f81fb7c4SAndroid Build Coastguard Worker		self.events.FixNoteLengths()
1240*f81fb7c4SAndroid Build Coastguard Worker
1241*f81fb7c4SAndroid Build Coastguard Worker	def DumpEvents (self, output):
1242*f81fb7c4SAndroid Build Coastguard Worker		self.events.DumpEvents(output, self.stream.timebase)
1243*f81fb7c4SAndroid Build Coastguard Worker		if output is not None:
1244*f81fb7c4SAndroid Build Coastguard Worker			output.write('[%s]: end-of-track\n' % self.stream.timebase.ConvertTicksToStr(self.end_of_track))
1245*f81fb7c4SAndroid Build Coastguard Worker		else:
1246*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.debug('[%s]: end-of-track' % self.stream.timebase.ConvertTicksToStr(self.end_of_track))
1247*f81fb7c4SAndroid Build Coastguard Worker
1248*f81fb7c4SAndroid Build Coastguard Worker
1249*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
1250*f81fb7c4SAndroid Build Coastguard Worker# MIDIFile
1251*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
1252*f81fb7c4SAndroid Build Coastguard Workerclass MIDIFile (file):
1253*f81fb7c4SAndroid Build Coastguard Worker	"""The MIDIFile class implements methods for reading, parsing,
1254*f81fb7c4SAndroid Build Coastguard Worker	modifying, and writing Standard MIDI Files (SMF).
1255*f81fb7c4SAndroid Build Coastguard Worker
1256*f81fb7c4SAndroid Build Coastguard Worker	"""
1257*f81fb7c4SAndroid Build Coastguard Worker	def __init__ (self, name, mode):
1258*f81fb7c4SAndroid Build Coastguard Worker		file.__init__(self, name, mode)
1259*f81fb7c4SAndroid Build Coastguard Worker		self.timebase = TimeBase()
1260*f81fb7c4SAndroid Build Coastguard Worker
1261*f81fb7c4SAndroid Build Coastguard Worker	def ReadFromStream (self, start_offset=0, file_size=None):
1262*f81fb7c4SAndroid Build Coastguard Worker		"""Parse the MIDI file creating a list of properties, tracks,
1263*f81fb7c4SAndroid Build Coastguard Worker		and events based on the contents of the file.
1264*f81fb7c4SAndroid Build Coastguard Worker
1265*f81fb7c4SAndroid Build Coastguard Worker		"""
1266*f81fb7c4SAndroid Build Coastguard Worker
1267*f81fb7c4SAndroid Build Coastguard Worker		# determine file size - without using os.stat
1268*f81fb7c4SAndroid Build Coastguard Worker		if file_size == None:
1269*f81fb7c4SAndroid Build Coastguard Worker			self.start_offset = start_offset
1270*f81fb7c4SAndroid Build Coastguard Worker			self.seek(0,2)
1271*f81fb7c4SAndroid Build Coastguard Worker			file_size = self.tell() - self.start_offset
1272*f81fb7c4SAndroid Build Coastguard Worker			self.seek(start_offset,0)
1273*f81fb7c4SAndroid Build Coastguard Worker		else:
1274*f81fb7c4SAndroid Build Coastguard Worker			file_size = file_size
1275*f81fb7c4SAndroid Build Coastguard Worker
1276*f81fb7c4SAndroid Build Coastguard Worker		# for error recovery
1277*f81fb7c4SAndroid Build Coastguard Worker		self.last_good_event = None
1278*f81fb7c4SAndroid Build Coastguard Worker		self.error_loc = None
1279*f81fb7c4SAndroid Build Coastguard Worker
1280*f81fb7c4SAndroid Build Coastguard Worker		# read the file header - verify it's an SMF file
1281*f81fb7c4SAndroid Build Coastguard Worker		bytes = self.read(struct.calcsize(SMF_HEADER_FMT))
1282*f81fb7c4SAndroid Build Coastguard Worker		riff_tag, self.hdr_len, self.format, self.num_tracks, self.timebase.ppqn = struct.unpack(SMF_HEADER_FMT, bytes)
1283*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.debug('SMF header\n  Tag:       %s\n  HeaderLen: %d\n  Format:    %d\n  NumTracks: %d\n  PPQN:      %d\n' % \
1284*f81fb7c4SAndroid Build Coastguard Worker			(riff_tag, self.hdr_len, self.format, self.num_tracks, self.timebase.ppqn))
1285*f81fb7c4SAndroid Build Coastguard Worker
1286*f81fb7c4SAndroid Build Coastguard Worker		# sanity check on header
1287*f81fb7c4SAndroid Build Coastguard Worker		if (riff_tag != SMF_RIFF_TAG) or (self.format not in range(2)):
1288*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(self, MSG_NOT_SMF_FILE)
1289*f81fb7c4SAndroid Build Coastguard Worker
1290*f81fb7c4SAndroid Build Coastguard Worker		# check for odd header size
1291*f81fb7c4SAndroid Build Coastguard Worker		if self.hdr_len + 8 != struct.calcsize(SMF_HEADER_FMT):
1292*f81fb7c4SAndroid Build Coastguard Worker			self.Warning('SMF file has unusual header size: %d bytes' % self.hdr_len)
1293*f81fb7c4SAndroid Build Coastguard Worker
1294*f81fb7c4SAndroid Build Coastguard Worker		# read each of the tracks
1295*f81fb7c4SAndroid Build Coastguard Worker		offset = start_offset + self.hdr_len + 8
1296*f81fb7c4SAndroid Build Coastguard Worker		self.tracks = []
1297*f81fb7c4SAndroid Build Coastguard Worker		self.end_of_file = 0
1298*f81fb7c4SAndroid Build Coastguard Worker		for i in range(self.num_tracks):
1299*f81fb7c4SAndroid Build Coastguard Worker			#print("Track: %d" % i)
1300*f81fb7c4SAndroid Build Coastguard Worker
1301*f81fb7c4SAndroid Build Coastguard Worker			# parse the track
1302*f81fb7c4SAndroid Build Coastguard Worker			track = MIDITrack()
1303*f81fb7c4SAndroid Build Coastguard Worker			length = track.ReadFromStream(self, offset, file_size)
1304*f81fb7c4SAndroid Build Coastguard Worker			track.trackNum = i
1305*f81fb7c4SAndroid Build Coastguard Worker
1306*f81fb7c4SAndroid Build Coastguard Worker			self.tracks.append(track)
1307*f81fb7c4SAndroid Build Coastguard Worker
1308*f81fb7c4SAndroid Build Coastguard Worker			# calculate offset to next track
1309*f81fb7c4SAndroid Build Coastguard Worker			offset += length + 8
1310*f81fb7c4SAndroid Build Coastguard Worker
1311*f81fb7c4SAndroid Build Coastguard Worker			# determine time of last event
1312*f81fb7c4SAndroid Build Coastguard Worker			self.end_of_file = max(self.end_of_file, track.end_of_track)
1313*f81fb7c4SAndroid Build Coastguard Worker
1314*f81fb7c4SAndroid Build Coastguard Worker		# if start_offset is zero, the final offset should match the file length
1315*f81fb7c4SAndroid Build Coastguard Worker		if (offset - start_offset) != file_size:
1316*f81fb7c4SAndroid Build Coastguard Worker			self.Warning('SMF file size is incorrect - should be %d, was %d' % (file_size, offset))
1317*f81fb7c4SAndroid Build Coastguard Worker
1318*f81fb7c4SAndroid Build Coastguard Worker	def Save (self, offset=0, filters=None):
1319*f81fb7c4SAndroid Build Coastguard Worker		"""Save this file back to disk with modifications."""
1320*f81fb7c4SAndroid Build Coastguard Worker		if (not 'w' in self.mode) and (not '+' in self.mode):
1321*f81fb7c4SAndroid Build Coastguard Worker			raise MIDIFileException(self, 'Cannot write to file in read-only mode')
1322*f81fb7c4SAndroid Build Coastguard Worker		self.Write(self, offset, filters)
1323*f81fb7c4SAndroid Build Coastguard Worker
1324*f81fb7c4SAndroid Build Coastguard Worker	def SaveAs (self, filename, offset=0, filters=None):
1325*f81fb7c4SAndroid Build Coastguard Worker		"""Save MIDI data to new file."""
1326*f81fb7c4SAndroid Build Coastguard Worker		output_file = MIDIFile(filename, 'wb')
1327*f81fb7c4SAndroid Build Coastguard Worker		self.Write(output_file, offset, filters)
1328*f81fb7c4SAndroid Build Coastguard Worker		output_file.close()
1329*f81fb7c4SAndroid Build Coastguard Worker
1330*f81fb7c4SAndroid Build Coastguard Worker	def Write (self, output_file, offset=0, filters=None):
1331*f81fb7c4SAndroid Build Coastguard Worker		"""This function does the actual work of writing the file."""
1332*f81fb7c4SAndroid Build Coastguard Worker		# write the file header
1333*f81fb7c4SAndroid Build Coastguard Worker		output_file.seek(offset)
1334*f81fb7c4SAndroid Build Coastguard Worker		bytes = struct.pack(SMF_HEADER_FMT, SMF_RIFF_TAG, struct.calcsize(SMF_HEADER_FMT) - 8, self.format, self.num_tracks, self.timebase.ppqn)
1335*f81fb7c4SAndroid Build Coastguard Worker		output_file.write(bytes)
1336*f81fb7c4SAndroid Build Coastguard Worker
1337*f81fb7c4SAndroid Build Coastguard Worker		# write out the tracks
1338*f81fb7c4SAndroid Build Coastguard Worker		for track in self.tracks:
1339*f81fb7c4SAndroid Build Coastguard Worker			track.Write(output_file, filters)
1340*f81fb7c4SAndroid Build Coastguard Worker
1341*f81fb7c4SAndroid Build Coastguard Worker		# flush the data to disk
1342*f81fb7c4SAndroid Build Coastguard Worker		output_file.flush()
1343*f81fb7c4SAndroid Build Coastguard Worker
1344*f81fb7c4SAndroid Build Coastguard Worker	def ConvertToType0 (self):
1345*f81fb7c4SAndroid Build Coastguard Worker		"""Convert a file to type 0."""
1346*f81fb7c4SAndroid Build Coastguard Worker		if self.format == 0:
1347*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.warning('File is already type 0 - ignoring request to convert')
1348*f81fb7c4SAndroid Build Coastguard Worker			return
1349*f81fb7c4SAndroid Build Coastguard Worker
1350*f81fb7c4SAndroid Build Coastguard Worker		# convert to type 0
1351*f81fb7c4SAndroid Build Coastguard Worker		for track in self.tracks[1:]:
1352*f81fb7c4SAndroid Build Coastguard Worker			self.tracks[0].MergeEvents(track.events)
1353*f81fb7c4SAndroid Build Coastguard Worker		self.tracks = self.tracks[:1]
1354*f81fb7c4SAndroid Build Coastguard Worker		self.num_tracks = 1
1355*f81fb7c4SAndroid Build Coastguard Worker		self.format = 0
1356*f81fb7c4SAndroid Build Coastguard Worker
1357*f81fb7c4SAndroid Build Coastguard Worker	def DeleteEmptyTracks (self):
1358*f81fb7c4SAndroid Build Coastguard Worker		"""Delete any tracks that do not contain MIDI messages"""
1359*f81fb7c4SAndroid Build Coastguard Worker		track_num = 0
1360*f81fb7c4SAndroid Build Coastguard Worker		for track in self.tracks[:]:
1361*f81fb7c4SAndroid Build Coastguard Worker			for event in self.tracks.events:
1362*f81fb7c4SAndroid Build Coastguard Worker				if event.msg_type in MIDI_MESSAGES:
1363*f81fb7c4SAndroid Build Coastguard Worker					break;
1364*f81fb7c4SAndroid Build Coastguard Worker				else:
1365*f81fb7c4SAndroid Build Coastguard Worker					midi_file_logger.debug('Deleting track %d' % track_num)
1366*f81fb7c4SAndroid Build Coastguard Worker					self.tracks.remove(track)
1367*f81fb7c4SAndroid Build Coastguard Worker			track_num += 1
1368*f81fb7c4SAndroid Build Coastguard Worker
1369*f81fb7c4SAndroid Build Coastguard Worker	def ConvertToTicks (self, measures, beats, ticks):
1370*f81fb7c4SAndroid Build Coastguard Worker		return self.timebase.ConvertToTicks(measures, beats, ticks)
1371*f81fb7c4SAndroid Build Coastguard Worker
1372*f81fb7c4SAndroid Build Coastguard Worker	def Trim (self, start, end, quantize=0, chase_controllers=True):
1373*f81fb7c4SAndroid Build Coastguard Worker		track_num = 0
1374*f81fb7c4SAndroid Build Coastguard Worker		for track in self.tracks:
1375*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.debug('Trimming track %d' % track_num)
1376*f81fb7c4SAndroid Build Coastguard Worker			track.Trim(start, end, quantize=quantize, chase_controllers=chase_controllers)
1377*f81fb7c4SAndroid Build Coastguard Worker			track_num += 1
1378*f81fb7c4SAndroid Build Coastguard Worker
1379*f81fb7c4SAndroid Build Coastguard Worker	def DumpTracks (self, output=None):
1380*f81fb7c4SAndroid Build Coastguard Worker		track_num = 0
1381*f81fb7c4SAndroid Build Coastguard Worker		for track in self.tracks:
1382*f81fb7c4SAndroid Build Coastguard Worker			if output is None:
1383*f81fb7c4SAndroid Build Coastguard Worker				midi_file_logger.debug('*** Track %d ***' % track_num)
1384*f81fb7c4SAndroid Build Coastguard Worker			else:
1385*f81fb7c4SAndroid Build Coastguard Worker				output.write('*** Track %d ***' % track_num)
1386*f81fb7c4SAndroid Build Coastguard Worker			track.DumpEvents(output)
1387*f81fb7c4SAndroid Build Coastguard Worker			track_num += 1
1388*f81fb7c4SAndroid Build Coastguard Worker
1389*f81fb7c4SAndroid Build Coastguard Worker	def Warning (self, msg):
1390*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.warning('[%d]: %s' % (self.tell(), msg))
1391*f81fb7c4SAndroid Build Coastguard Worker
1392*f81fb7c4SAndroid Build Coastguard Worker	def Error (self, msg):
1393*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.error('[%d]: %s' % (self.tell(), msg))
1394*f81fb7c4SAndroid Build Coastguard Worker
1395*f81fb7c4SAndroid Build Coastguard Worker	def DumpError (self):
1396*f81fb7c4SAndroid Build Coastguard Worker		if self.last_good_event:
1397*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.error('Dumping from last good event:')
1398*f81fb7c4SAndroid Build Coastguard Worker			pos = self.last_good_event - 16
1399*f81fb7c4SAndroid Build Coastguard Worker			length = self.error_loc - pos + 16
1400*f81fb7c4SAndroid Build Coastguard Worker		elif self.error_loc:
1401*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.error('Dumping from 16 bytes prior to error:')
1402*f81fb7c4SAndroid Build Coastguard Worker			pos = self.error_loc
1403*f81fb7c4SAndroid Build Coastguard Worker			length = 32
1404*f81fb7c4SAndroid Build Coastguard Worker		else:
1405*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.error('No dump information available')
1406*f81fb7c4SAndroid Build Coastguard Worker			return
1407*f81fb7c4SAndroid Build Coastguard Worker
1408*f81fb7c4SAndroid Build Coastguard Worker		self.seek(pos, 0)
1409*f81fb7c4SAndroid Build Coastguard Worker		for i in range(length):
1410*f81fb7c4SAndroid Build Coastguard Worker			if i % 16 == 0:
1411*f81fb7c4SAndroid Build Coastguard Worker				if i:
1412*f81fb7c4SAndroid Build Coastguard Worker					midi_file_logger.error(' '.join(debug_out))
1413*f81fb7c4SAndroid Build Coastguard Worker				debug_out = ['%08x:' % (pos + i)]
1414*f81fb7c4SAndroid Build Coastguard Worker			byte = self.read(1)
1415*f81fb7c4SAndroid Build Coastguard Worker			if len(byte) == 0:
1416*f81fb7c4SAndroid Build Coastguard Worker				break;
1417*f81fb7c4SAndroid Build Coastguard Worker			debug_out.append('%02x' % ord(byte))
1418*f81fb7c4SAndroid Build Coastguard Worker		if i % 16 > 0:
1419*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.error(' '.join(debug_out))
1420*f81fb7c4SAndroid Build Coastguard Worker
1421*f81fb7c4SAndroid Build Coastguard Workerdef GetMidiInfo(midiFile):
1422*f81fb7c4SAndroid Build Coastguard Worker	"""Bth; Get MIDI info"""
1423*f81fb7c4SAndroid Build Coastguard Worker
1424*f81fb7c4SAndroid Build Coastguard Worker	class midiData(object):
1425*f81fb7c4SAndroid Build Coastguard Worker		def __init__ (self):
1426*f81fb7c4SAndroid Build Coastguard Worker			self.err = 1
1427*f81fb7c4SAndroid Build Coastguard Worker			self.endMbt = "0:0:0"
1428*f81fb7c4SAndroid Build Coastguard Worker			self.totalTicks = 0
1429*f81fb7c4SAndroid Build Coastguard Worker			self.maxTracks = 0
1430*f81fb7c4SAndroid Build Coastguard Worker			self.maxMeasures = 0
1431*f81fb7c4SAndroid Build Coastguard Worker			self.maxBeats = 0
1432*f81fb7c4SAndroid Build Coastguard Worker			self.maxTicks = 0
1433*f81fb7c4SAndroid Build Coastguard Worker			self.totalTicks = 0
1434*f81fb7c4SAndroid Build Coastguard Worker			self.timebase = None
1435*f81fb7c4SAndroid Build Coastguard Worker			self.ppqn = 0
1436*f81fb7c4SAndroid Build Coastguard Worker			self.beats_per_measure = 0
1437*f81fb7c4SAndroid Build Coastguard Worker			self.trackList = []
1438*f81fb7c4SAndroid Build Coastguard Worker
1439*f81fb7c4SAndroid Build Coastguard Worker	md = midiData()
1440*f81fb7c4SAndroid Build Coastguard Worker
1441*f81fb7c4SAndroid Build Coastguard Worker	try:
1442*f81fb7c4SAndroid Build Coastguard Worker		m = MIDIFile(midiFile, 'rb')
1443*f81fb7c4SAndroid Build Coastguard Worker		m.ReadFromStream()
1444*f81fb7c4SAndroid Build Coastguard Worker
1445*f81fb7c4SAndroid Build Coastguard Worker		for track in m.tracks:
1446*f81fb7c4SAndroid Build Coastguard Worker			if track.channel is not None:
1447*f81fb7c4SAndroid Build Coastguard Worker				empty = False
1448*f81fb7c4SAndroid Build Coastguard Worker				trk = track.channel + 1
1449*f81fb7c4SAndroid Build Coastguard Worker			else:
1450*f81fb7c4SAndroid Build Coastguard Worker				empty = True
1451*f81fb7c4SAndroid Build Coastguard Worker				trk = ''
1452*f81fb7c4SAndroid Build Coastguard Worker			md.trackList.append(trackGrid(track.trackNum, trk, track.name, empty))
1453*f81fb7c4SAndroid Build Coastguard Worker
1454*f81fb7c4SAndroid Build Coastguard Worker		md.endMbt = m.timebase.ConvertTicksToMBT(m.end_of_file)
1455*f81fb7c4SAndroid Build Coastguard Worker		md.endMbtStr = "%d:%d:%d" % (md.endMbt[0], md.endMbt[1], md.endMbt[2])
1456*f81fb7c4SAndroid Build Coastguard Worker		md.maxMeasures = md.endMbt[0]
1457*f81fb7c4SAndroid Build Coastguard Worker		md.maxBeats = 4
1458*f81fb7c4SAndroid Build Coastguard Worker		md.maxTicks = m.timebase.ppqn
1459*f81fb7c4SAndroid Build Coastguard Worker		md.maxTracks = m.num_tracks
1460*f81fb7c4SAndroid Build Coastguard Worker		md.totalTicks = m.end_of_file
1461*f81fb7c4SAndroid Build Coastguard Worker		md.timebase = m.timebase
1462*f81fb7c4SAndroid Build Coastguard Worker		md.ppqn = m.timebase.ppqn
1463*f81fb7c4SAndroid Build Coastguard Worker		md.beats_per_measure = m.timebase.beats_per_measure
1464*f81fb7c4SAndroid Build Coastguard Worker
1465*f81fb7c4SAndroid Build Coastguard Worker		#add above if more added
1466*f81fb7c4SAndroid Build Coastguard Worker		md.err = 0
1467*f81fb7c4SAndroid Build Coastguard Worker
1468*f81fb7c4SAndroid Build Coastguard Worker		m.close()
1469*f81fb7c4SAndroid Build Coastguard Worker	except:
1470*f81fb7c4SAndroid Build Coastguard Worker		raise
1471*f81fb7c4SAndroid Build Coastguard Worker		pass
1472*f81fb7c4SAndroid Build Coastguard Worker
1473*f81fb7c4SAndroid Build Coastguard Worker	return md
1474*f81fb7c4SAndroid Build Coastguard Worker
1475*f81fb7c4SAndroid Build Coastguard Worker
1476*f81fb7c4SAndroid Build Coastguard Worker
1477*f81fb7c4SAndroid Build Coastguard Worker
1478*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
1479*f81fb7c4SAndroid Build Coastguard Worker# main
1480*f81fb7c4SAndroid Build Coastguard Worker#---------------------------------------------------------------
1481*f81fb7c4SAndroid Build Coastguard Workerif __name__ == '__main__':
1482*f81fb7c4SAndroid Build Coastguard Worker	sys = __import__('sys')
1483*f81fb7c4SAndroid Build Coastguard Worker	os = __import__('os')
1484*f81fb7c4SAndroid Build Coastguard Worker
1485*f81fb7c4SAndroid Build Coastguard Worker	# initialize root logger
1486*f81fb7c4SAndroid Build Coastguard Worker	root_logger = logging.getLogger('')
1487*f81fb7c4SAndroid Build Coastguard Worker	root_logger.setLevel(logging.NOTSET)
1488*f81fb7c4SAndroid Build Coastguard Worker
1489*f81fb7c4SAndroid Build Coastguard Worker	# initialize console handler
1490*f81fb7c4SAndroid Build Coastguard Worker	console_handler = logging.StreamHandler()
1491*f81fb7c4SAndroid Build Coastguard Worker	console_handler.setFormatter(logging.Formatter('%(message)s'))
1492*f81fb7c4SAndroid Build Coastguard Worker	console_handler.setLevel(logging.DEBUG)
1493*f81fb7c4SAndroid Build Coastguard Worker	root_logger.addHandler(console_handler)
1494*f81fb7c4SAndroid Build Coastguard Worker
1495*f81fb7c4SAndroid Build Coastguard Worker	files = []
1496*f81fb7c4SAndroid Build Coastguard Worker	dirs = []
1497*f81fb7c4SAndroid Build Coastguard Worker	last_arg = None
1498*f81fb7c4SAndroid Build Coastguard Worker	sysex_filter = False
1499*f81fb7c4SAndroid Build Coastguard Worker	drum_filter = False
1500*f81fb7c4SAndroid Build Coastguard Worker	convert = False
1501*f81fb7c4SAndroid Build Coastguard Worker
1502*f81fb7c4SAndroid Build Coastguard Worker	# process args
1503*f81fb7c4SAndroid Build Coastguard Worker	for arg in sys.argv[1:]:
1504*f81fb7c4SAndroid Build Coastguard Worker
1505*f81fb7c4SAndroid Build Coastguard Worker		# previous argument implies this argument
1506*f81fb7c4SAndroid Build Coastguard Worker		if last_arg is not None:
1507*f81fb7c4SAndroid Build Coastguard Worker			if last_arg == '-DIR':
1508*f81fb7c4SAndroid Build Coastguard Worker				dirs.append(arg)
1509*f81fb7c4SAndroid Build Coastguard Worker				last_arg = None
1510*f81fb7c4SAndroid Build Coastguard Worker
1511*f81fb7c4SAndroid Build Coastguard Worker		# check for switch
1512*f81fb7c4SAndroid Build Coastguard Worker		elif arg[0] == '-':
1513*f81fb7c4SAndroid Build Coastguard Worker			if arg == '-DIR':
1514*f81fb7c4SAndroid Build Coastguard Worker				last_arg = arg
1515*f81fb7c4SAndroid Build Coastguard Worker			elif arg == '-SYSEX':
1516*f81fb7c4SAndroid Build Coastguard Worker				sysex_filter = True
1517*f81fb7c4SAndroid Build Coastguard Worker			elif arg == '-DRUMS':
1518*f81fb7c4SAndroid Build Coastguard Worker				drum_filter = True
1519*f81fb7c4SAndroid Build Coastguard Worker			elif arg == '-CONVERT':
1520*f81fb7c4SAndroid Build Coastguard Worker				convert = True
1521*f81fb7c4SAndroid Build Coastguard Worker			else:
1522*f81fb7c4SAndroid Build Coastguard Worker				midi_file_logger.error('Bad option %s' % arg)
1523*f81fb7c4SAndroid Build Coastguard Worker
1524*f81fb7c4SAndroid Build Coastguard Worker		# must be a filename
1525*f81fb7c4SAndroid Build Coastguard Worker		else:
1526*f81fb7c4SAndroid Build Coastguard Worker			files.append(arg)
1527*f81fb7c4SAndroid Build Coastguard Worker
1528*f81fb7c4SAndroid Build Coastguard Worker	# setup filters
1529*f81fb7c4SAndroid Build Coastguard Worker	filters = []
1530*f81fb7c4SAndroid Build Coastguard Worker	if sysex_filter:
1531*f81fb7c4SAndroid Build Coastguard Worker		filters.append(EventTypeFilter((SYSEX,)))
1532*f81fb7c4SAndroid Build Coastguard Worker	if drum_filter:
1533*f81fb7c4SAndroid Build Coastguard Worker		filters.append(ChannelFilter((9,),False))
1534*f81fb7c4SAndroid Build Coastguard Worker
1535*f81fb7c4SAndroid Build Coastguard Worker
1536*f81fb7c4SAndroid Build Coastguard Worker	# process dirs
1537*f81fb7c4SAndroid Build Coastguard Worker	for d in dirs:
1538*f81fb7c4SAndroid Build Coastguard Worker		for root, dir_list, file_list in os.walk(d):
1539*f81fb7c4SAndroid Build Coastguard Worker			for f in file_list:
1540*f81fb7c4SAndroid Build Coastguard Worker				if f.endswith('.mid'):
1541*f81fb7c4SAndroid Build Coastguard Worker					files.append(os.path.join(root, f))
1542*f81fb7c4SAndroid Build Coastguard Worker
1543*f81fb7c4SAndroid Build Coastguard Worker	# process files
1544*f81fb7c4SAndroid Build Coastguard Worker	bad_files = []
1545*f81fb7c4SAndroid Build Coastguard Worker	for f in files:
1546*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.info('Processing file %s' % f)
1547*f81fb7c4SAndroid Build Coastguard Worker		midiFile = MIDIFile(f, 'rb')
1548*f81fb7c4SAndroid Build Coastguard Worker		try:
1549*f81fb7c4SAndroid Build Coastguard Worker			midiFile.ReadFromStream()
1550*f81fb7c4SAndroid Build Coastguard Worker
1551*f81fb7c4SAndroid Build Coastguard Worker			#midiFile.DumpTracks()
1552*f81fb7c4SAndroid Build Coastguard Worker			#print('[%s]: end-of-track\n' % midiFile.timebase.ConvertTicksToStr(midiFile.end_of_file))
1553*f81fb7c4SAndroid Build Coastguard Worker
1554*f81fb7c4SAndroid Build Coastguard Worker			# convert to type 0
1555*f81fb7c4SAndroid Build Coastguard Worker			if convert and (midiFile.format == 1):
1556*f81fb7c4SAndroid Build Coastguard Worker				midiFile.Convert(0)
1557*f81fb7c4SAndroid Build Coastguard Worker				converted = True
1558*f81fb7c4SAndroid Build Coastguard Worker			else:
1559*f81fb7c4SAndroid Build Coastguard Worker				converted = False
1560*f81fb7c4SAndroid Build Coastguard Worker
1561*f81fb7c4SAndroid Build Coastguard Worker			# write processed file
1562*f81fb7c4SAndroid Build Coastguard Worker			if converted or len(filters):
1563*f81fb7c4SAndroid Build Coastguard Worker				midiFile.SaveAs(f[:-4] + '-mod.mid', filters)
1564*f81fb7c4SAndroid Build Coastguard Worker
1565*f81fb7c4SAndroid Build Coastguard Worker		except MIDIFileException, X:
1566*f81fb7c4SAndroid Build Coastguard Worker			bad_files.append(f)
1567*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.error('Error in file %s' % f)
1568*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.error(X)
1569*f81fb7c4SAndroid Build Coastguard Worker			midiFile.DumpError()
1570*f81fb7c4SAndroid Build Coastguard Worker		midiFile.close()
1571*f81fb7c4SAndroid Build Coastguard Worker
1572*f81fb7c4SAndroid Build Coastguard Worker	# dump problem files
1573*f81fb7c4SAndroid Build Coastguard Worker	if len(bad_files):
1574*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.info('The following file(s) had errors:')
1575*f81fb7c4SAndroid Build Coastguard Worker		for f in bad_files:
1576*f81fb7c4SAndroid Build Coastguard Worker			midi_file_logger.info(f)
1577*f81fb7c4SAndroid Build Coastguard Worker	else:
1578*f81fb7c4SAndroid Build Coastguard Worker		midi_file_logger.info('All files read successfully')
1579*f81fb7c4SAndroid Build Coastguard Worker
1580