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):
 	'''