Mercurial > hg > syncopation-dataset
changeset 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 | df1e7c378ee0 |
children | 59906fa4999c |
files | Syncopation models/music_objects.py Syncopation models/readmidi.py |
diffstat | 2 files changed, 326 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/Syncopation models/music_objects.py Sun Apr 12 13:06:17 2015 +0100 +++ b/Syncopation models/music_objects.py Sun Apr 12 15:40:06 2015 +0100 @@ -136,7 +136,7 @@ self.tpq = ticksPerQuarter self.qpm = qpmTempo - self.timeSignature = TimeSignature(timeSignature) + self.timeSignature = timeSignature self.nextBar = nextBar self.prevBar = prevBar @@ -202,5 +202,7 @@ def get_denominator(self): return int(self.tsString.split('/')[1]) + def to_string(self): + return self.tsString
--- a/Syncopation models/readmidi.py Sun Apr 12 13:06:17 2015 +0100 +++ b/Syncopation models/readmidi.py Sun Apr 12 15:40:06 2015 +0100 @@ -9,7 +9,7 @@ #from RhythmParser import Bar from music_objects import * - +from basic_functions import * @@ -42,9 +42,331 @@ # 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): '''