Mercurial > hg > syncopation-dataset
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. |