annotate Syncopation models/readmidi.py @ 42:121d0e1f1748

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