comparison Syncopation models/readmidi.py @ 24:08c298f47917

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