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