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