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@50
|
97 def get_time_signature(timeList,barStartTime, barLength, ticksPerQuarter, 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@50
|
107 if event.time>=barStartTime+barLength:
|
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@50
|
113 barLength = calculate_bar_ticks(timesig.get_numerator(),
|
christopher@50
|
114 timesig.get_denominator(),
|
christopher@50
|
115 ticksPerQuarter)
|
christopher@45
|
116
|
christopher@45
|
117 if timesig==None:
|
christopher@45
|
118 if currentTimeSignature==None:
|
christopher@45
|
119 timesig = TimeSignature("4/4")
|
christopher@45
|
120 else:
|
christopher@45
|
121 timesig = currentTimeSignature
|
christopher@45
|
122
|
christopher@50
|
123 return timesig,barLength
|
christopher@45
|
124
|
christopher@45
|
125 def get_tempo(timeList,barStartTime, barEndTime, currentTempo = None):
|
christopher@45
|
126
|
christopher@45
|
127 tempo = None
|
christopher@45
|
128 i=0
|
christopher@45
|
129 # get first event:
|
christopher@45
|
130 while(i<len(timeList)):
|
christopher@45
|
131 # run through list until we find the most recent time signature
|
christopher@45
|
132 # before the end of the current bar
|
christopher@45
|
133 event = timeList[i]
|
christopher@45
|
134 i = i + 1
|
christopher@45
|
135 if event.time>=barEndTime:
|
christopher@45
|
136 break
|
christopher@45
|
137
|
christopher@45
|
138 # run through list until we find the most recent tempo
|
christopher@45
|
139 # before the end of the current bar
|
christopher@45
|
140 if event.type=="SET_TEMPO" and event.time>=barStartTime:
|
christopher@45
|
141 tempo = midi_event_to_qpm_tempo(event)
|
christopher@45
|
142 event.type = "USED_TEMPO"
|
christopher@45
|
143
|
christopher@45
|
144 if tempo==None:
|
christopher@45
|
145 if currentTempo==None:
|
christopher@45
|
146 tempo = 120
|
christopher@45
|
147 else:
|
christopher@45
|
148 tempo = currentTempo
|
christopher@45
|
149
|
christopher@45
|
150 return tempo
|
christopher@45
|
151
|
christopher@45
|
152
|
christopher@45
|
153
|
christopher@45
|
154 # get initial time sig and tempo or use defaults
|
christopher@45
|
155 timeList = get_time_events(midiFile)
|
christopher@45
|
156
|
christopher@45
|
157 # get notes from the midi file (absolute start times from start of file)
|
christopher@45
|
158 notesList = get_notes_from_event_list(get_note_events(midiFile))
|
christopher@45
|
159
|
christopher@45
|
160
|
christopher@45
|
161 # get initial tempo and time signature from time list
|
christopher@45
|
162 timesig = get_initial_time_signature(timeList)
|
christopher@45
|
163 tempo = get_initial_tempo(timeList)
|
christopher@45
|
164
|
christopher@45
|
165
|
christopher@45
|
166
|
christopher@45
|
167
|
christopher@45
|
168 # ticks per quarter note:
|
christopher@45
|
169 ticksPerQuarter = midiFile.ticksPerQuarterNote
|
christopher@45
|
170 #calculate the initial length of a bar in ticks
|
christopher@45
|
171 barlength = calculate_bar_ticks(timesig.get_numerator(), timesig.get_denominator(), ticksPerQuarter)
|
christopher@45
|
172 # initialise time for start and end of current bar
|
christopher@45
|
173 barStartTime = 0
|
christopher@50
|
174 barEndTime = 0# barlength
|
christopher@45
|
175
|
christopher@45
|
176
|
christopher@45
|
177 # initialise bars list
|
christopher@45
|
178 bars = BarList()
|
christopher@45
|
179 noteIndex = 0
|
christopher@45
|
180
|
christopher@45
|
181 note = notesList[0]
|
christopher@45
|
182 # run through the notes list, chopping it into bars
|
christopher@45
|
183 while noteIndex<len(notesList):
|
christopher@45
|
184 #create a local note sequence to build a bar
|
christopher@45
|
185 currentNotes = NoteSequence()
|
christopher@45
|
186
|
christopher@50
|
187 [timesig,barlength] = get_time_signature(timeList,barStartTime, barlength, ticksPerQuarter, timesig)
|
christopher@50
|
188
|
christopher@50
|
189 barEndTime = barEndTime + barlength
|
christopher@50
|
190
|
christopher@45
|
191 tempo = get_tempo(timeList,barStartTime, barEndTime, tempo)
|
christopher@45
|
192
|
christopher@45
|
193
|
christopher@45
|
194 #find all the notes in the current bar
|
christopher@45
|
195 while(note.startTime<barEndTime):
|
christopher@45
|
196 #make note start time relative to current bar
|
christopher@45
|
197 note.startTime = note.startTime - barStartTime
|
christopher@45
|
198 #add note to current bar note sequence
|
christopher@45
|
199 currentNotes.append(note)
|
christopher@45
|
200 noteIndex = noteIndex + 1
|
christopher@45
|
201 if noteIndex<len(notesList):
|
christopher@45
|
202 note = notesList[noteIndex]
|
christopher@45
|
203 else:
|
christopher@45
|
204 break
|
christopher@45
|
205
|
christopher@45
|
206 # create a new bar from the current notes and add it to the list of bars
|
christopher@45
|
207 bars.append(Bar(currentNotes, timesig, ticksPerQuarter, tempo))
|
christopher@45
|
208
|
christopher@45
|
209 barStartTime = barEndTime
|
christopher@45
|
210
|
christopher@45
|
211 return bars
|
christopher@45
|
212
|
christopher@45
|
213
|
christopher@45
|
214 #get note objects from a list of note midi events
|
christopher@45
|
215 def get_notes_from_event_list(noteEventList):
|
christopher@45
|
216 noteslist = NoteSequence()
|
christopher@45
|
217
|
christopher@45
|
218 index = 0
|
christopher@45
|
219
|
christopher@45
|
220 #while not at the end of the list of note events
|
christopher@45
|
221 while index<len(noteEventList):
|
christopher@45
|
222 #get next event from list
|
christopher@45
|
223 event = noteEventList[index]
|
christopher@45
|
224 index = index + 1
|
christopher@45
|
225 #if we've found the start of a note, search for the corresponding end event
|
christopher@45
|
226 if event.type=="NOTE_ON" and event.velocity!=0:
|
christopher@45
|
227 localindex = index
|
christopher@45
|
228
|
christopher@45
|
229 #find corresponding end event
|
christopher@45
|
230 while localindex<len(noteEventList):
|
christopher@45
|
231 endEvent = noteEventList[localindex]
|
christopher@45
|
232 #if its the same note and it's an end event
|
christopher@45
|
233 if endEvent.pitch==event.pitch and (endEvent.type=="NOTE_OFF" or (endEvent.type=="NOTE_ON" and endEvent.velocity==0)):
|
christopher@45
|
234 #make a note
|
christopher@45
|
235 note = Note(event.time,endEvent.time-event.time,event.velocity)
|
christopher@45
|
236 #alter the type of this end event so it can't be linked to another note on
|
christopher@45
|
237 endEvent.type = "DUMMY"
|
christopher@50
|
238
|
christopher@50
|
239 #if this note starts at the same time as the previous one
|
christopher@50
|
240 # replace the previous one if this has longer duration
|
christopher@50
|
241 if len(noteslist)>0 and note.startTime==noteslist[-1].startTime:
|
christopher@50
|
242 if note.duration>noteslist[-1].duration:
|
christopher@50
|
243 noteslist[-1]=note
|
christopher@50
|
244 # otherwise add the note to the list
|
christopher@50
|
245 else:
|
christopher@50
|
246 noteslist.append(note)
|
christopher@45
|
247 #found the end of the note so break out of the local loop
|
christopher@45
|
248 break
|
christopher@50
|
249 localindex = localindex+1
|
christopher@45
|
250
|
christopher@45
|
251 return noteslist
|
christopher@45
|
252
|
christopher@45
|
253
|
christopher@45
|
254
|
christopher@45
|
255
|
christopher@45
|
256
|
christopher@45
|
257
|
christopher@45
|
258
|
christopher@45
|
259
|
christopher@45
|
260
|
christopher@45
|
261
|
christopher@45
|
262 def get_note_events(midiFile, trackNumber = None):
|
christopher@45
|
263 """
|
christopher@45
|
264 Gets all note on and note off events from a midifile.
|
christopher@45
|
265
|
christopher@45
|
266 If trackNumber is not specified, the function will check the file format
|
christopher@45
|
267 and pick either track 0 for a type 0 (single track format) or track 1
|
christopher@45
|
268 for a type 1 or 2 (multi-track) midi file.
|
christopher@45
|
269
|
christopher@45
|
270 """
|
christopher@45
|
271
|
christopher@45
|
272 if trackNumber==None:
|
christopher@45
|
273 if midiFile.format==0:
|
christopher@45
|
274 trackNumber=0
|
christopher@45
|
275 else:
|
christopher@45
|
276 trackNumber=1
|
christopher@45
|
277
|
christopher@45
|
278 return get_events_of_type(midiFile, trackNumber, lambda e: (e.type == 'NOTE_ON') | (e.type == "NOTE_OFF") )
|
christopher@45
|
279
|
christopher@45
|
280
|
christopher@45
|
281
|
christopher@45
|
282 def get_time_events(midiFile):
|
christopher@45
|
283 """
|
christopher@45
|
284 Gets time signature and tempo events from a MIDI file (MIDI format 0
|
christopher@45
|
285 or format 1) and returns a list of those events and their associated
|
christopher@45
|
286 absolute start times. If no time signature or tempo are specified then
|
christopher@45
|
287 defaults of 4/4 and 120QPM are assumed.
|
christopher@45
|
288
|
christopher@45
|
289 From the MIDI file specification:
|
christopher@45
|
290
|
christopher@45
|
291 "All MIDI Files should specify tempo and time signature. If they don't,
|
christopher@45
|
292 the time signature is assumed to be 4/4, and the tempo 120 beats per
|
christopher@45
|
293 minute. In format 0, these meta-events should occur at least at the
|
christopher@45
|
294 beginning of the single multi-channel track. In format 1, these meta-events
|
christopher@45
|
295 should be contained in the first track. In format 2, each of the temporally
|
christopher@45
|
296 independent patterns should contain at least initial time signature and
|
christopher@45
|
297 tempo information."
|
christopher@45
|
298
|
christopher@45
|
299 """
|
christopher@45
|
300 return get_events_of_type(midiFile, 0, lambda e: (e.type == 'SET_TEMPO') | (e.type == "TIME_SIGNATURE") )
|
christopher@45
|
301
|
christopher@45
|
302
|
christopher@45
|
303 def get_events_of_type(midiFile, trackIndex, lambdaEventType):
|
christopher@45
|
304 """
|
christopher@45
|
305 Filters the events in a midi track that are selected by the
|
christopher@45
|
306 function object lambdaEventType e.g. lambda e: (e.type == 'NOTE_ON')
|
christopher@45
|
307 Return a list containing the relevant events with appropriate
|
christopher@45
|
308 delta times between them
|
christopher@45
|
309 """
|
christopher@45
|
310
|
christopher@45
|
311 track = midiFile.tracks[trackIndex]
|
christopher@45
|
312 eventIndex = 0
|
christopher@45
|
313 # numTimeEvents = 0
|
christopher@45
|
314
|
christopher@45
|
315 localEventList = []
|
christopher@45
|
316 localEventFound = True
|
christopher@45
|
317 #accumulatedTime = 0
|
christopher@45
|
318
|
christopher@45
|
319 while localEventFound==True:
|
christopher@45
|
320 #find the next time event from the track:
|
christopher@45
|
321 (localEventIndex, localEventDelta, localEventFound) = find_event(track, eventIndex, lambdaEventType)
|
christopher@45
|
322
|
christopher@45
|
323 if localEventFound==True:
|
christopher@45
|
324 #get the time event object out of the track
|
christopher@45
|
325 localEvent = track.events[localEventIndex]
|
christopher@45
|
326
|
christopher@45
|
327 #update the start event to search from
|
christopher@45
|
328 eventIndex = localEventIndex + 1
|
christopher@45
|
329
|
christopher@45
|
330 #calculate the absolute start time of the time event
|
christopher@45
|
331 #accumulatedTime = accumulatedTime + localEventDelta
|
christopher@45
|
332
|
christopher@45
|
333 localEventList.append(localEvent)
|
christopher@45
|
334
|
christopher@45
|
335 return localEventList
|
christopher@45
|
336
|
christopher@45
|
337
|
christopher@45
|
338 def midi_event_to_time_signature(midiTimeSignatureEvent):
|
christopher@45
|
339 """
|
christopher@45
|
340 Extract the numerator and denominator from a midi time signature
|
christopher@45
|
341 event and return a TimeSignature music object. Ignore clocks per
|
christopher@45
|
342 quarternote and 32nds per quarternote elements since these are
|
christopher@45
|
343 only for sequencer metronome settings which we won't use here.
|
christopher@45
|
344 """
|
christopher@45
|
345 if midiTimeSignatureEvent.type!="TIME_SIGNATURE":
|
christopher@45
|
346 print "Error in midi_event_to_time_signature(), event must be a midi time signature type"
|
christopher@45
|
347 return None
|
christopher@45
|
348 else:
|
christopher@45
|
349 num = ord(midiTimeSignatureEvent.data[0])
|
christopher@45
|
350 denom = 2**ord(midiTimeSignatureEvent.data[1])
|
christopher@45
|
351 return TimeSignature("%d/%d" % (num, denom))
|
christopher@45
|
352
|
christopher@45
|
353
|
christopher@45
|
354 def midi_event_to_qpm_tempo(midiTempoEvent):
|
christopher@45
|
355 """
|
christopher@45
|
356 Extract the tempo in QPM from a midi SET_TEMPO event
|
christopher@45
|
357 """
|
christopher@45
|
358 if midiTempoEvent.type!="SET_TEMPO":
|
christopher@45
|
359 print "Error in midi_event_to_qpm_tempo(), event must be a midi tempo event"
|
christopher@45
|
360 return None
|
christopher@45
|
361 else:
|
christopher@45
|
362 # tempo is stored as microseconds per quarter note
|
christopher@45
|
363 # in three bytes which we can extract as three ints:
|
christopher@45
|
364 values = map(ord, midiTempoEvent.data)
|
christopher@45
|
365 # first byte multiplied by 2^16, second 2^8 and third is normal units
|
christopher@45
|
366 # giving microseconds per quarter
|
christopher@45
|
367 microsecondsPerQuarter = values[0]*2**16 + values[1]*2**8 + values[2]
|
christopher@45
|
368
|
christopher@45
|
369 # to calculate QPM, 1 minute = 60million microseconds
|
christopher@45
|
370 # so divide 60million by micros per quarter:
|
christopher@45
|
371 return 60000000/microsecondsPerQuarter
|
christopher@45
|
372
|
christopher@45
|
373 def find_event(track, eventStartIndex, lambdaExpr):
|
christopher@45
|
374 '''
|
christopher@45
|
375 From code by Csaba Sulyok:
|
christopher@45
|
376 Finds MIDI event based on lambda expression, starting from a given index.
|
christopher@45
|
377 Returns a tuple of the following 3 elements:
|
christopher@45
|
378 1. event index where the lambda expression is true
|
christopher@45
|
379 2. aggregate delta time from event start index until the found event
|
christopher@45
|
380 3. flag whether or not any value was found, or we've reached the end of the event queue
|
christopher@45
|
381 '''
|
christopher@45
|
382
|
christopher@45
|
383 eventIndex = eventStartIndex
|
christopher@45
|
384 deltaTime = 0
|
christopher@45
|
385 while eventIndex < len(track.events) and not lambdaExpr(track.events[eventIndex]):
|
christopher@45
|
386 if track.events[eventIndex].type == 'DeltaTime':
|
christopher@45
|
387 deltaTime += track.events[eventIndex].time
|
christopher@45
|
388 eventIndex += 1
|
christopher@45
|
389
|
christopher@45
|
390 success = eventIndex < len(track.events)
|
christopher@45
|
391 return (eventIndex, deltaTime, success)
|
christopher@45
|
392
|
christopher@45
|
393
|