christopher@45
|
1 # -*- coding: utf-8 -*-
|
christopher@45
|
2 """
|
christopher@45
|
3 Created on Sat Mar 21 22:19:52 2015
|
christopher@45
|
4
|
christopher@45
|
5 @author: christopherh
|
christopher@45
|
6 """
|
christopher@45
|
7
|
christopher@45
|
8 from midiparser import MidiFile, MidiTrack, DeltaTime, MidiEvent
|
christopher@45
|
9 #from RhythmParser import Bar
|
christopher@45
|
10
|
christopher@45
|
11 from music_objects import *
|
christopher@45
|
12 from basic_functions import *
|
christopher@45
|
13
|
christopher@45
|
14
|
christopher@45
|
15
|
christopher@45
|
16
|
christopher@45
|
17 def read_midi_file(filename):
|
christopher@45
|
18 """ open and read a MIDI file, return a MidiFile object """
|
christopher@45
|
19
|
christopher@45
|
20 #create a midifile object, open and read a midi file
|
christopher@45
|
21 midiFile = MidiFile()
|
christopher@45
|
22 midiFile.open(filename, 'rb')
|
christopher@45
|
23 midiFile.read()
|
christopher@45
|
24 midiFile.close()
|
christopher@45
|
25
|
christopher@45
|
26 return midiFile
|
christopher@45
|
27
|
christopher@45
|
28 # def get_bars(midiFile, trackindex=1):
|
christopher@45
|
29 # """ returns a list of bar objects from a MidiFile object """
|
christopher@45
|
30
|
christopher@45
|
31 # # select a track to extract (default = 1, ignoring dummy track 0)
|
christopher@45
|
32 # track = midiFile.tracks[trackindex]
|
christopher@45
|
33 # eventIndex = 0
|
christopher@45
|
34 # numNotes = 0
|
christopher@45
|
35
|
christopher@45
|
36 # noteonlist = []
|
christopher@45
|
37 # noteOnFound==True
|
christopher@45
|
38
|
christopher@45
|
39 # while noteOnFound==True:
|
christopher@45
|
40 # (noteOnIndex, noteOnDelta, noteOnFound) = self.find_event(track, eventIndex, lambda e: e.type == 'NOTE_ON')
|
christopher@45
|
41 # noteEvent = track.events[noteOnIndex]
|
christopher@45
|
42 # eventIndex = noteOnIndex + 1
|
christopher@45
|
43
|
christopher@45
|
44
|
christopher@45
|
45 #find_event(x.tracks[0], 0, lambda e: (e.type == 'NOTE_ON') | (e.type == 'KEY_SIGNATURE') | (e.type == "TIME_SIGNATURE"))
|
christopher@45
|
46
|
christopher@45
|
47 '''
|
christopher@45
|
48 #read midiFile
|
christopher@45
|
49
|
christopher@45
|
50
|
christopher@45
|
51
|
christopher@45
|
52 run through selected track getting notes out to build bars
|
christopher@45
|
53
|
christopher@45
|
54 '''
|
christopher@45
|
55
|
christopher@45
|
56
|
christopher@45
|
57
|
christopher@45
|
58 def get_bars_from_midi(midiFile):
|
christopher@45
|
59
|
christopher@45
|
60 # couple of inner functions to tidy up getting the initial values of
|
christopher@45
|
61 # tempo and time signature
|
christopher@45
|
62 def get_initial_tempo(timeList):
|
christopher@45
|
63
|
christopher@45
|
64 tempo = None
|
christopher@45
|
65 i=0
|
christopher@45
|
66 # Find the initial time and tempo:
|
christopher@45
|
67 while(tempo == None and i<len(timeList)):
|
christopher@45
|
68 event = timeList[i]
|
christopher@45
|
69 i = i + 1
|
christopher@45
|
70 if event.type=="SET_TEMPO":
|
christopher@45
|
71 if tempo==None:
|
christopher@45
|
72 tempo = midi_event_to_qpm_tempo(event)
|
christopher@45
|
73
|
christopher@45
|
74 if tempo==None:
|
christopher@45
|
75 tempo = 120
|
christopher@45
|
76
|
christopher@45
|
77 return tempo
|
christopher@45
|
78
|
christopher@45
|
79 def get_initial_time_signature(timeList):
|
christopher@45
|
80
|
christopher@45
|
81 timesig = None
|
christopher@45
|
82 i=0
|
christopher@45
|
83 # Find the initial time and tempo:
|
christopher@45
|
84 while(timesig == None and i<len(timeList)):
|
christopher@45
|
85 event = timeList[i]
|
christopher@45
|
86 i = i + 1
|
christopher@45
|
87 if event.type=="TIME_SIGNATURE":
|
christopher@45
|
88 if timesig==None:
|
christopher@45
|
89 timesig = midi_event_to_time_signature(event)
|
christopher@45
|
90
|
christopher@45
|
91 if timesig==None:
|
christopher@45
|
92 timesig = TimeSignature("4/4")
|
christopher@45
|
93
|
christopher@45
|
94 return timesig
|
christopher@45
|
95
|
christopher@45
|
96
|
christopher@45
|
97 def get_time_signature(timeList,barStartTime, barEndTime, currentTimeSignature = None):
|
christopher@45
|
98
|
christopher@45
|
99 timesig = None
|
christopher@45
|
100 i=0
|
christopher@45
|
101
|
christopher@45
|
102 while(i<len(timeList)):
|
christopher@45
|
103 # run through list until we find the most recent time signature
|
christopher@45
|
104 # before the end of the current bar
|
christopher@45
|
105 event = timeList[i]
|
christopher@45
|
106 i = i + 1
|
christopher@45
|
107 if event.time>=barEndTime:
|
christopher@45
|
108 break
|
christopher@45
|
109
|
christopher@45
|
110 if event.type=="TIME_SIGNATURE" and event.time>=barStartTime:
|
christopher@45
|
111 timesig = midi_event_to_time_signature(event)
|
christopher@45
|
112 event.type = "USED_TIME_SIGNATURE"
|
christopher@45
|
113
|
christopher@45
|
114 if timesig==None:
|
christopher@45
|
115 if currentTimeSignature==None:
|
christopher@45
|
116 timesig = TimeSignature("4/4")
|
christopher@45
|
117 else:
|
christopher@45
|
118 timesig = currentTimeSignature
|
christopher@45
|
119
|
christopher@45
|
120 return timesig
|
christopher@45
|
121
|
christopher@45
|
122 def get_tempo(timeList,barStartTime, barEndTime, currentTempo = None):
|
christopher@45
|
123
|
christopher@45
|
124 tempo = None
|
christopher@45
|
125 i=0
|
christopher@45
|
126 # get first event:
|
christopher@45
|
127 while(i<len(timeList)):
|
christopher@45
|
128 # run through list until we find the most recent time signature
|
christopher@45
|
129 # before the end of the current bar
|
christopher@45
|
130 event = timeList[i]
|
christopher@45
|
131 i = i + 1
|
christopher@45
|
132 if event.time>=barEndTime:
|
christopher@45
|
133 break
|
christopher@45
|
134
|
christopher@45
|
135 # run through list until we find the most recent tempo
|
christopher@45
|
136 # before the end of the current bar
|
christopher@45
|
137 if event.type=="SET_TEMPO" and event.time>=barStartTime:
|
christopher@45
|
138 tempo = midi_event_to_qpm_tempo(event)
|
christopher@45
|
139 event.type = "USED_TEMPO"
|
christopher@45
|
140
|
christopher@45
|
141 if tempo==None:
|
christopher@45
|
142 if currentTempo==None:
|
christopher@45
|
143 tempo = 120
|
christopher@45
|
144 else:
|
christopher@45
|
145 tempo = currentTempo
|
christopher@45
|
146
|
christopher@45
|
147 return tempo
|
christopher@45
|
148
|
christopher@45
|
149
|
christopher@45
|
150
|
christopher@45
|
151 # get initial time sig and tempo or use defaults
|
christopher@45
|
152 timeList = get_time_events(midiFile)
|
christopher@45
|
153
|
christopher@45
|
154 # get notes from the midi file (absolute start times from start of file)
|
christopher@45
|
155 notesList = get_notes_from_event_list(get_note_events(midiFile))
|
christopher@45
|
156
|
christopher@45
|
157
|
christopher@45
|
158 # get initial tempo and time signature from time list
|
christopher@45
|
159 timesig = get_initial_time_signature(timeList)
|
christopher@45
|
160 tempo = get_initial_tempo(timeList)
|
christopher@45
|
161
|
christopher@45
|
162
|
christopher@45
|
163
|
christopher@45
|
164
|
christopher@45
|
165 # ticks per quarter note:
|
christopher@45
|
166 ticksPerQuarter = midiFile.ticksPerQuarterNote
|
christopher@45
|
167 #calculate the initial length of a bar in ticks
|
christopher@45
|
168 barlength = calculate_bar_ticks(timesig.get_numerator(), timesig.get_denominator(), ticksPerQuarter)
|
christopher@45
|
169 # initialise time for start and end of current bar
|
christopher@45
|
170 barStartTime = 0
|
christopher@45
|
171 barEndTime = barlength
|
christopher@45
|
172
|
christopher@45
|
173
|
christopher@45
|
174 # initialise bars list
|
christopher@45
|
175 bars = BarList()
|
christopher@45
|
176 noteIndex = 0
|
christopher@45
|
177
|
christopher@45
|
178 note = notesList[0]
|
christopher@45
|
179 # run through the notes list, chopping it into bars
|
christopher@45
|
180 while noteIndex<len(notesList):
|
christopher@45
|
181 #create a local note sequence to build a bar
|
christopher@45
|
182 currentNotes = NoteSequence()
|
christopher@45
|
183
|
christopher@45
|
184 timesig = get_time_signature(timeList,barStartTime, barEndTime, timesig)
|
christopher@45
|
185 tempo = get_tempo(timeList,barStartTime, barEndTime, tempo)
|
christopher@45
|
186
|
christopher@45
|
187
|
christopher@45
|
188 #find all the notes in the current bar
|
christopher@45
|
189 while(note.startTime<barEndTime):
|
christopher@45
|
190 #make note start time relative to current bar
|
christopher@45
|
191 note.startTime = note.startTime - barStartTime
|
christopher@45
|
192 #add note to current bar note sequence
|
christopher@45
|
193 currentNotes.append(note)
|
christopher@45
|
194 noteIndex = noteIndex + 1
|
christopher@45
|
195 if noteIndex<len(notesList):
|
christopher@45
|
196 note = notesList[noteIndex]
|
christopher@45
|
197 else:
|
christopher@45
|
198 break
|
christopher@45
|
199
|
christopher@45
|
200 # create a new bar from the current notes and add it to the list of bars
|
christopher@45
|
201 bars.append(Bar(currentNotes, timesig, ticksPerQuarter, tempo))
|
christopher@45
|
202
|
christopher@45
|
203 barStartTime = barEndTime
|
christopher@45
|
204
|
christopher@45
|
205 barEndTime = barEndTime + barlength
|
christopher@45
|
206
|
christopher@45
|
207 return bars
|
christopher@45
|
208
|
christopher@45
|
209
|
christopher@45
|
210 #get note objects from a list of note midi events
|
christopher@45
|
211 def get_notes_from_event_list(noteEventList):
|
christopher@45
|
212 noteslist = NoteSequence()
|
christopher@45
|
213
|
christopher@45
|
214 index = 0
|
christopher@45
|
215
|
christopher@45
|
216 #while not at the end of the list of note events
|
christopher@45
|
217 while index<len(noteEventList):
|
christopher@45
|
218 #get next event from list
|
christopher@45
|
219 event = noteEventList[index]
|
christopher@45
|
220 index = index + 1
|
christopher@45
|
221 #if we've found the start of a note, search for the corresponding end event
|
christopher@45
|
222 if event.type=="NOTE_ON" and event.velocity!=0:
|
christopher@45
|
223 localindex = index
|
christopher@45
|
224
|
christopher@45
|
225 #find corresponding end event
|
christopher@45
|
226 while localindex<len(noteEventList):
|
christopher@45
|
227 endEvent = noteEventList[localindex]
|
christopher@45
|
228 #if its the same note and it's an end event
|
christopher@45
|
229 if endEvent.pitch==event.pitch and (endEvent.type=="NOTE_OFF" or (endEvent.type=="NOTE_ON" and endEvent.velocity==0)):
|
christopher@45
|
230 #make a note
|
christopher@45
|
231 note = Note(event.time,endEvent.time-event.time,event.velocity)
|
christopher@45
|
232 #alter the type of this end event so it can't be linked to another note on
|
christopher@45
|
233 endEvent.type = "DUMMY"
|
christopher@45
|
234 #add the note to the list
|
christopher@45
|
235 noteslist.append(note)
|
christopher@45
|
236 #found the end of the note so break out of the local loop
|
christopher@45
|
237 break
|
christopher@45
|
238
|
christopher@45
|
239 return noteslist
|
christopher@45
|
240
|
christopher@45
|
241
|
christopher@45
|
242
|
christopher@45
|
243
|
christopher@45
|
244
|
christopher@45
|
245
|
christopher@45
|
246
|
christopher@45
|
247
|
christopher@45
|
248
|
christopher@45
|
249
|
christopher@45
|
250 def get_note_events(midiFile, trackNumber = None):
|
christopher@45
|
251 """
|
christopher@45
|
252 Gets all note on and note off events from a midifile.
|
christopher@45
|
253
|
christopher@45
|
254 If trackNumber is not specified, the function will check the file format
|
christopher@45
|
255 and pick either track 0 for a type 0 (single track format) or track 1
|
christopher@45
|
256 for a type 1 or 2 (multi-track) midi file.
|
christopher@45
|
257
|
christopher@45
|
258 """
|
christopher@45
|
259
|
christopher@45
|
260 if trackNumber==None:
|
christopher@45
|
261 if midiFile.format==0:
|
christopher@45
|
262 trackNumber=0
|
christopher@45
|
263 else:
|
christopher@45
|
264 trackNumber=1
|
christopher@45
|
265
|
christopher@45
|
266 return get_events_of_type(midiFile, trackNumber, lambda e: (e.type == 'NOTE_ON') | (e.type == "NOTE_OFF") )
|
christopher@45
|
267
|
christopher@45
|
268
|
christopher@45
|
269
|
christopher@45
|
270 def get_time_events(midiFile):
|
christopher@45
|
271 """
|
christopher@45
|
272 Gets time signature and tempo events from a MIDI file (MIDI format 0
|
christopher@45
|
273 or format 1) and returns a list of those events and their associated
|
christopher@45
|
274 absolute start times. If no time signature or tempo are specified then
|
christopher@45
|
275 defaults of 4/4 and 120QPM are assumed.
|
christopher@45
|
276
|
christopher@45
|
277 From the MIDI file specification:
|
christopher@45
|
278
|
christopher@45
|
279 "All MIDI Files should specify tempo and time signature. If they don't,
|
christopher@45
|
280 the time signature is assumed to be 4/4, and the tempo 120 beats per
|
christopher@45
|
281 minute. In format 0, these meta-events should occur at least at the
|
christopher@45
|
282 beginning of the single multi-channel track. In format 1, these meta-events
|
christopher@45
|
283 should be contained in the first track. In format 2, each of the temporally
|
christopher@45
|
284 independent patterns should contain at least initial time signature and
|
christopher@45
|
285 tempo information."
|
christopher@45
|
286
|
christopher@45
|
287 """
|
christopher@45
|
288 return get_events_of_type(midiFile, 0, lambda e: (e.type == 'SET_TEMPO') | (e.type == "TIME_SIGNATURE") )
|
christopher@45
|
289
|
christopher@45
|
290
|
christopher@45
|
291 def get_events_of_type(midiFile, trackIndex, lambdaEventType):
|
christopher@45
|
292 """
|
christopher@45
|
293 Filters the events in a midi track that are selected by the
|
christopher@45
|
294 function object lambdaEventType e.g. lambda e: (e.type == 'NOTE_ON')
|
christopher@45
|
295 Return a list containing the relevant events with appropriate
|
christopher@45
|
296 delta times between them
|
christopher@45
|
297 """
|
christopher@45
|
298
|
christopher@45
|
299 track = midiFile.tracks[trackIndex]
|
christopher@45
|
300 eventIndex = 0
|
christopher@45
|
301 # numTimeEvents = 0
|
christopher@45
|
302
|
christopher@45
|
303 localEventList = []
|
christopher@45
|
304 localEventFound = True
|
christopher@45
|
305 #accumulatedTime = 0
|
christopher@45
|
306
|
christopher@45
|
307 while localEventFound==True:
|
christopher@45
|
308 #find the next time event from the track:
|
christopher@45
|
309 (localEventIndex, localEventDelta, localEventFound) = find_event(track, eventIndex, lambdaEventType)
|
christopher@45
|
310
|
christopher@45
|
311 if localEventFound==True:
|
christopher@45
|
312 #get the time event object out of the track
|
christopher@45
|
313 localEvent = track.events[localEventIndex]
|
christopher@45
|
314
|
christopher@45
|
315 #update the start event to search from
|
christopher@45
|
316 eventIndex = localEventIndex + 1
|
christopher@45
|
317
|
christopher@45
|
318 #calculate the absolute start time of the time event
|
christopher@45
|
319 #accumulatedTime = accumulatedTime + localEventDelta
|
christopher@45
|
320
|
christopher@45
|
321 localEventList.append(localEvent)
|
christopher@45
|
322
|
christopher@45
|
323 return localEventList
|
christopher@45
|
324
|
christopher@45
|
325
|
christopher@45
|
326 def midi_event_to_time_signature(midiTimeSignatureEvent):
|
christopher@45
|
327 """
|
christopher@45
|
328 Extract the numerator and denominator from a midi time signature
|
christopher@45
|
329 event and return a TimeSignature music object. Ignore clocks per
|
christopher@45
|
330 quarternote and 32nds per quarternote elements since these are
|
christopher@45
|
331 only for sequencer metronome settings which we won't use here.
|
christopher@45
|
332 """
|
christopher@45
|
333 if midiTimeSignatureEvent.type!="TIME_SIGNATURE":
|
christopher@45
|
334 print "Error in midi_event_to_time_signature(), event must be a midi time signature type"
|
christopher@45
|
335 return None
|
christopher@45
|
336 else:
|
christopher@45
|
337 num = ord(midiTimeSignatureEvent.data[0])
|
christopher@45
|
338 denom = 2**ord(midiTimeSignatureEvent.data[1])
|
christopher@45
|
339 return TimeSignature("%d/%d" % (num, denom))
|
christopher@45
|
340
|
christopher@45
|
341
|
christopher@45
|
342 def midi_event_to_qpm_tempo(midiTempoEvent):
|
christopher@45
|
343 """
|
christopher@45
|
344 Extract the tempo in QPM from a midi SET_TEMPO event
|
christopher@45
|
345 """
|
christopher@45
|
346 if midiTempoEvent.type!="SET_TEMPO":
|
christopher@45
|
347 print "Error in midi_event_to_qpm_tempo(), event must be a midi tempo event"
|
christopher@45
|
348 return None
|
christopher@45
|
349 else:
|
christopher@45
|
350 # tempo is stored as microseconds per quarter note
|
christopher@45
|
351 # in three bytes which we can extract as three ints:
|
christopher@45
|
352 values = map(ord, midiTempoEvent.data)
|
christopher@45
|
353 # first byte multiplied by 2^16, second 2^8 and third is normal units
|
christopher@45
|
354 # giving microseconds per quarter
|
christopher@45
|
355 microsecondsPerQuarter = values[0]*2**16 + values[1]*2**8 + values[2]
|
christopher@45
|
356
|
christopher@45
|
357 # to calculate QPM, 1 minute = 60million microseconds
|
christopher@45
|
358 # so divide 60million by micros per quarter:
|
christopher@45
|
359 return 60000000/microsecondsPerQuarter
|
christopher@45
|
360
|
christopher@45
|
361 def find_event(track, eventStartIndex, lambdaExpr):
|
christopher@45
|
362 '''
|
christopher@45
|
363 From code by Csaba Sulyok:
|
christopher@45
|
364 Finds MIDI event based on lambda expression, starting from a given index.
|
christopher@45
|
365 Returns a tuple of the following 3 elements:
|
christopher@45
|
366 1. event index where the lambda expression is true
|
christopher@45
|
367 2. aggregate delta time from event start index until the found event
|
christopher@45
|
368 3. flag whether or not any value was found, or we've reached the end of the event queue
|
christopher@45
|
369 '''
|
christopher@45
|
370
|
christopher@45
|
371 eventIndex = eventStartIndex
|
christopher@45
|
372 deltaTime = 0
|
christopher@45
|
373 while eventIndex < len(track.events) and not lambdaExpr(track.events[eventIndex]):
|
christopher@45
|
374 if track.events[eventIndex].type == 'DeltaTime':
|
christopher@45
|
375 deltaTime += track.events[eventIndex].time
|
christopher@45
|
376 eventIndex += 1
|
christopher@45
|
377
|
christopher@45
|
378 success = eventIndex < len(track.events)
|
christopher@45
|
379 return (eventIndex, deltaTime, success)
|
christopher@45
|
380
|
christopher@45
|
381
|