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