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