Mercurial > hg > syncopation-dataset
view 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 |
line wrap: on
line source
# -*- coding: utf-8 -*- """ Created on Sat Mar 21 22:19:52 2015 @author: christopherh """ from midiparser import MidiFile, MidiTrack, DeltaTime, MidiEvent #from RhythmParser import Bar from music_objects import * from basic_functions import * def read_midi_file(filename): """ open and read a MIDI file, return a MidiFile object """ #create a midifile object, open and read a midi file midiFile = MidiFile() midiFile.open(filename, 'rb') midiFile.read() midiFile.close() return midiFile # def get_bars(midiFile, trackindex=1): # """ returns a list of bar objects from a MidiFile object """ # # select a track to extract (default = 1, ignoring dummy track 0) # track = midiFile.tracks[trackindex] # eventIndex = 0 # numNotes = 0 # noteonlist = [] # noteOnFound==True # while noteOnFound==True: # (noteOnIndex, noteOnDelta, noteOnFound) = self.find_event(track, eventIndex, lambda e: e.type == 'NOTE_ON') # noteEvent = track.events[noteOnIndex] # eventIndex = noteOnIndex + 1 #find_event(x.tracks[0], 0, lambda e: (e.type == 'NOTE_ON') | (e.type == 'KEY_SIGNATURE') | (e.type == "TIME_SIGNATURE")) ''' #read midiFile run through selected track getting notes out to build bars ''' def get_bars_from_midi(midiFile): # couple of inner functions to tidy up getting the initial values of # tempo and time signature def get_initial_tempo(timeList): tempo = None i=0 # Find the initial time and tempo: while(tempo == None and i<len(timeList)): event = timeList[i] i = i + 1 if event.type=="SET_TEMPO": if tempo==None: tempo = midi_event_to_qpm_tempo(event) if tempo==None: tempo = 120 return tempo def get_initial_time_signature(timeList): timesig = None i=0 # Find the initial time and tempo: while(timesig == None and i<len(timeList)): event = timeList[i] i = i + 1 if event.type=="TIME_SIGNATURE": if timesig==None: timesig = midi_event_to_time_signature(event) if timesig==None: timesig = TimeSignature("4/4") return timesig def get_time_signature(timeList,barStartTime, barEndTime, currentTimeSignature = None): timesig = None i=0 while(i<len(timeList)): # run through list until we find the most recent time signature # before the end of the current bar event = timeList[i] i = i + 1 if event.time>=barEndTime: break # print "event %d, start %d, end %d"%(event.time, barStartTime, barEndTime) if event.type=="TIME_SIGNATURE" and event.time>=barStartTime: timesig = midi_event_to_time_signature(event) event.type = "USED_TIME_SIGNATURE" # print timesig.to_string() # this event has been used now so remove it #timeList.remove(event) if timesig==None: if currentTimeSignature==None: timesig = TimeSignature("4/4") else: timesig = currentTimeSignature return timesig def get_tempo(timeList,barStartTime, barEndTime, currentTempo = None): tempo = None i=0 # get first event: while(i<len(timeList)): # run through list until we find the most recent time signature # before the end of the current bar event = timeList[i] i = i + 1 if event.time>=barEndTime: break print "event %d, start %d, end %d"%(event.time, barStartTime, barEndTime) # run through list until we find the most recent tempo # before the end of the current bar if event.type=="SET_TEMPO" and event.time>=barStartTime: tempo = midi_event_to_qpm_tempo(event) event.type = "USED_TEMPO" print tempo # this event has been used now so remove it #timeList.remove(event) if tempo==None: if currentTempo==None: tempo = 120 else: tempo = currentTempo return tempo # get initial time sig and tempo or use defaults timeList = get_time_events(midiFile) print timeList # get notes from the midi file (absolute start times from start of file) notesList = get_notes_from_event_list(get_note_events(midiFile)) # get initial tempo and time signature from time list timesig = get_initial_time_signature(timeList) tempo = get_initial_tempo(timeList) # ticks per quarter note: ticksPerQuarter = midiFile.ticksPerQuarterNote #calculate the initial length of a bar in ticks barlength = calculate_bar_ticks(timesig.get_numerator(), timesig.get_denominator(), ticksPerQuarter) # initialise time for start and end of current bar barStartTime = 0 barEndTime = barlength # initialise bars list bars = BarList() noteIndex = 0 note = notesList[0] # run through the notes list, chopping it into bars while noteIndex<len(notesList): #create a local note sequence to build a bar currentNotes = NoteSequence() timesig = get_time_signature(timeList,barStartTime, barEndTime, timesig) tempo = get_tempo(timeList,barStartTime, barEndTime, tempo) #find all the notes in the current bar while(note.startTime<barEndTime): #make note start time relative to current bar note.startTime = note.startTime - barStartTime #add note to current bar note sequence currentNotes.append(note) noteIndex = noteIndex + 1 if noteIndex<len(notesList): note = notesList[noteIndex] else: break # create a new bar from the current notes and add it to the list of bars bars.append(Bar(currentNotes, timesig, ticksPerQuarter, tempo)) barStartTime = barEndTime barEndTime = barEndTime + barlength return bars #get note objects from a list of note midi events def get_notes_from_event_list(noteEventList): noteslist = NoteSequence() index = 0 #while not at the end of the list of note events while index<len(noteEventList): #get next event from list event = noteEventList[index] index = index + 1 #if we've found the start of a note, search for the corresponding end event if event.type=="NOTE_ON" and event.velocity!=0: localindex = index #find corresponding end event while localindex<len(noteEventList): endEvent = noteEventList[localindex] #if its the same note and it's an end event if endEvent.pitch==event.pitch and (endEvent.type=="NOTE_OFF" or (endEvent.type=="NOTE_ON" and endEvent.velocity==0)): #make a note note = Note(event.time,endEvent.time-event.time,event.velocity) #alter the type of this end event so it can't be linked to another note on endEvent.type = "DUMMY" #add the note to the list noteslist.append(note) #found the end of the note so break out of the local loop break return noteslist def get_note_events(midiFile, trackNumber = None): """ Gets all note on and note off events from a midifile. If trackNumber is not specified, the function will check the file format and pick either track 0 for a type 0 (single track format) or track 1 for a type 1 or 2 (multi-track) midi file. """ if trackNumber==None: if midiFile.format==0: trackNumber=0 else: trackNumber=1 return get_events_of_type(midiFile, trackNumber, lambda e: (e.type == 'NOTE_ON') | (e.type == "NOTE_OFF") ) def get_time_events(midiFile): """ Gets time signature and tempo events from a MIDI file (MIDI format 0 or format 1) and returns a list of those events and their associated absolute start times. If no time signature or tempo are specified then defaults of 4/4 and 120QPM are assumed. From the MIDI file specification: "All MIDI Files should specify tempo and time signature. If they don't, the time signature is assumed to be 4/4, and the tempo 120 beats per minute. In format 0, these meta-events should occur at least at the beginning of the single multi-channel track. In format 1, these meta-events should be contained in the first track. In format 2, each of the temporally independent patterns should contain at least initial time signature and tempo information." """ return get_events_of_type(midiFile, 0, lambda e: (e.type == 'SET_TEMPO') | (e.type == "TIME_SIGNATURE") ) def get_events_of_type(midiFile, trackIndex, lambdaEventType): """ Filters the events in a midi track that are selected by the function object lambdaEventType e.g. lambda e: (e.type == 'NOTE_ON') Return a list containing the relevant events with appropriate delta times between them """ track = midiFile.tracks[trackIndex] eventIndex = 0 # numTimeEvents = 0 localEventList = [] localEventFound = True #accumulatedTime = 0 while localEventFound==True: #find the next time event from the track: (localEventIndex, localEventDelta, localEventFound) = find_event(track, eventIndex, lambdaEventType) if localEventFound==True: #get the time event object out of the track localEvent = track.events[localEventIndex] #update the start event to search from eventIndex = localEventIndex + 1 #calculate the absolute start time of the time event #accumulatedTime = accumulatedTime + localEventDelta localEventList.append(localEvent) return localEventList def midi_event_to_time_signature(midiTimeSignatureEvent): """ Extract the numerator and denominator from a midi time signature event and return a TimeSignature music object. Ignore clocks per quarternote and 32nds per quarternote elements since these are only for sequencer metronome settings which we won't use here. """ if midiTimeSignatureEvent.type!="TIME_SIGNATURE": print "Error in midi_event_to_time_signature(), event must be a midi time signature type" return None else: num = ord(midiTimeSignatureEvent.data[0]) denom = 2**ord(midiTimeSignatureEvent.data[1]) return TimeSignature("%d/%d" % (num, denom)) def midi_event_to_qpm_tempo(midiTempoEvent): """ Extract the tempo in QPM from a midi SET_TEMPO event """ if midiTempoEvent.type!="SET_TEMPO": print "Error in midi_event_to_qpm_tempo(), event must be a midi tempo event" return None else: # tempo is stored as microseconds per quarter note # in three bytes which we can extract as three ints: values = map(ord, midiTempoEvent.data) # first byte multiplied by 2^16, second 2^8 and third is normal units # giving microseconds per quarter microsecondsPerQuarter = values[0]*2**16 + values[1]*2**8 + values[2] # to calculate QPM, 1 minute = 60million microseconds # so divide 60million by micros per quarter: return 60000000/microsecondsPerQuarter def find_event(track, eventStartIndex, lambdaExpr): ''' From code by Csaba Sulyok: Finds MIDI event based on lambda expression, starting from a given index. Returns a tuple of the following 3 elements: 1. event index where the lambda expression is true 2. aggregate delta time from event start index until the found event 3. flag whether or not any value was found, or we've reached the end of the event queue ''' eventIndex = eventStartIndex deltaTime = 0 while eventIndex < len(track.events) and not lambdaExpr(track.events[eventIndex]): if track.events[eventIndex].type == 'DeltaTime': deltaTime += track.events[eventIndex].time eventIndex += 1 success = eventIndex < len(track.events) return (eventIndex, deltaTime, success)