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