annotate synpy/readmidi.py @ 76:90b68f259541 tip

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