changeset 22:2dbc09ca8013

Refactored KTH, not tested yet. Added conversion functions between note sequence and velocity sequence, and tostring functions. Renamed get_min_timeSpan into velocity_sequence_to_min_timetpan, so need to refactor that.
author Chunyang Song <csong@eecs.qmul.ac.uk>
date Thu, 09 Apr 2015 23:49:16 +0100
parents b6daddeefda9
children df1e7c378ee0
files Syncopation models/KTH.py Syncopation models/basic_functions.py Syncopation models/music_objects.py
diffstat 3 files changed, 176 insertions(+), 72 deletions(-) [+]
line wrap: on
line diff
--- a/Syncopation models/KTH.py	Tue Apr 07 23:16:13 2015 +0100
+++ b/Syncopation models/KTH.py	Thu Apr 09 23:49:16 2015 +0100
@@ -4,7 +4,7 @@
 
 '''
 
-from basic_functions import get_min_timeSpan, get_note_indices, repeat
+from basic_functions import get_note_indices, repeat, note_sequence_to_min_timespan
 
 # To find the nearest power of 2 equal to or less than the given number
 def round_down_power_2(number):
@@ -26,20 +26,20 @@
 	return pow(2,i)
 
 # To examine whether start_time is 'off-beat'
-def start(startTime, c_n):
-	s = 0
+def start_time_offbeat_measure(startTime, c_n):
+	measure = 0
 	if startTime % c_n != 0:
-		s = 2
-	return s
+		measure = 2
+	return measure
 
 # To examine whether end_time is 'off-beat'
-def end(endTime, c_n):
-	s = 0
+def end_time_offbeat_measure(endTime, c_n):
+	measure = 0
 	if endTime % c_n != 0:
-		s = 1
-	return s
+		measure = 1
+	return measure
 
-def get_syncopation(bar, parameters):
+def get_syncopation(bar, parameters = None):
 	syncopation = None
 
 	# KTH only deals with simple-duple meter where the number of beats per bar is a power of two.
@@ -53,8 +53,19 @@
 		if bar.get_next_bar() != None:
 			nextbarNoteSequence = bar.get_next_bar().get_note_sequence()
 
-		# find the delta_t so that the time-span (in ticks) is minimized and the note durations are represented by possible smallest numbers
-		# delta_t is the greatest common divisor of all the note durations
+		# convert note sequence to its minimum time-span representation so that the later calculation can be faster
+		noteSequence = note_sequence_to_min_timespan(noteSequence)
+
+		# calculate syncopation note by note
+		syncopation = 0
+
+		for note in noteSequence:
+			startTime = note[0]
+			duration = note[1]
+			endTime = startTime + duration
+			c_n = round_down_power_2(duration)
+
+			syncopation = syncopation + start_time_offbeat_measure(startTime,c_n) + end_time_offbeat_measure(endTime,c_n)
 
 
 
--- a/Syncopation models/basic_functions.py	Tue Apr 07 23:16:13 2015 +0100
+++ b/Syncopation models/basic_functions.py	Thu Apr 09 23:49:16 2015 +0100
@@ -79,29 +79,51 @@
 				isPrime = False
 	return isPrime
 
-# The min_timeSpan function searches for the shortest possible time-span representation for a sequence.
-def get_min_timeSpan(seq):
-	minTimeSpan = [1]
-	for d in find_divisor(len(seq)):
-		segments = subdivide(seq,d)
+# convert a velocity sequence to its minimum time-span representation
+def velocity_sequence_to_min_timetpan(velocitySequence):
+	minTimeSpanVelocitySeq = [1]
+	for divisors in find_divisor(len(velocitySequence)):
+		segments = subdivide(velocitySequence,divisors)
 		if len(segments)!=0:
-			del minTimeSpan[:]
+			del minTimeSpanSequence[:]
 			for s in segments:
-				minTimeSpan.append(s[0])
-			if sum(minTimeSpan) == sum(seq):
+				minTimeSpanVelocitySeq.append(s[0])
+			if sum(minTimeSpanVelocitySeq) == sum(velocitySequence):
 				break
-	return minTimeSpan
+	return minTimeSpanVelocitySeq
 
-# get_note_indices returns all the indices of all the notes in this sequence
-def get_note_indices(sequence):
+# convert a note sequence to its minimum time-span representation
+def note_sequence_to_min_timespan(noteSequence, barTicks):
+	barBinaryArray = [0]*barTicks
+	for note in noteSequence:
+		# mark note_on event (i.e. startTime) and note_off event (i.e. endTime = startTime + duration) as 1 in the barBinaryArray
+		barBinaryArray[note[0]] = 1
+		barBinaryArray[note[0]+note[1]] = 1
+
+	# convert the barBinaryArray to its minimum time-span representation
+	minBarBinaryArray = velocitySequence_to_min_timeSpan(barBinaryArray)
+	delta_t = len(barBinaryArray)/len(minBarBinaryArray)
+
+	# scale the startTime and duration of each note by delta_t
+	for note in noteSequence:
+		note[0] = note[0]/delta_t
+		note[1] = note[1]/delta_t
+
+	return noteSequence
+
+
+
+# get_note_indices returns all the indices of all the notes in this velocity_sequence
+def get_note_indices(velocitySequence):
 	noteIndices = []
 
-	for index in range(len(sequence)):
-		if sequence[index] != 0:
+	for index in range(len(velocitySequence)):
+		if velocitySequence[index] != 0:
 			noteIndices.append(index)
 
 	return noteIndices
 
+
 # The get_H returns a sequence of metrical weight for a certain metrical level (horizontal),
 # given the sequence of metrical weights in a hierarchy (vertical) and a sequence of subdivisions.
 def get_H(weightSequence,subdivisionSequence, level):
@@ -118,9 +140,29 @@
 		print 'Error: a subdivision factor or metrical weight is not defined for the request metrical level.'
 	return H
 
-def calculate_time_span_ticks(numerator, denominator, ticksPerQuarter):
+
+def calculate_bar_ticks(numerator, denominator, ticksPerQuarter):
 	return (numerator * ticksPerQuarter *4) / denominator
 
+
+def get_rhythm_category(velocitySequence, subdivisionSequence):
+	'''
+	The get_rhythm_category function is used to detect rhythm category: monorhythm or polyrhythm.
+	For monorhythms, all prime factors of the length of minimum time-span representation of this sequence are
+	elements of its subdivision_seq, otherwise it is polyrhythm; 
+	e.g. prime_factors of polyrhythm 100100101010 in 4/4 is [2,3] but subdivision_seq = [1,2,2] for 4/4 
+	'''
+	rhythmCategory = 'mono'
+	for f in find_prime_factors(len(get_min_timeSpan(velocitySequence))):
+		if not (f in subdivisionSequence): 
+			rhythmCategory = 'poly'
+			break
+	return rhythmCategory
+
+
+def string_to_sequence(inputString):
+	return map(int, inputString.split(','))
+
 # # The get_subdivision_seq function returns the subdivision sequence of several common time-signatures defined by GTTM, 
 # # or ask for the top three level of subdivision_seq manually set by the user.
 # def get_subdivision_seq(timesig, L_max):
@@ -156,24 +198,6 @@
 # 	return subdivision_seq
 
 
-def get_rhythm_category(velocitySequence, subdivisionSequence):
-	'''
-	The get_rhythm_category function is used to detect rhythm category: monorhythm or polyrhythm.
-	For monorhythms, all prime factors of the length of minimum time-span representation of this sequence are
-	elements of its subdivision_seq, otherwise it is polyrhythm; 
-	e.g. prime_factors of polyrhythm 100100101010 in 4/4 is [2,3] but subdivision_seq = [1,2,2] for 4/4 
-	'''
-	rhythmCategory = 'mono'
-	for f in find_prime_factors(len(get_min_timeSpan(velocitySequence))):
-		if not (f in subdivisionSequence): 
-			rhythmCategory = 'poly'
-			break
-	return rhythmCategory
-
-def string_to_sequence(inputString):
-	return map(int, inputString.split(','))
-
-
  # The split_by_bar function seperates the score representation of rhythm by bar lines, 
  # resulting in a list representingbar-by-bar rhythm sequence,
  # e.g. rhythm = ['|',[ts1,td1,v1], [ts2,td2,v2], '|',[ts3,td3,v3],'|'...]
--- a/Syncopation models/music_objects.py	Tue Apr 07 23:16:13 2015 +0100
+++ b/Syncopation models/music_objects.py	Thu Apr 09 23:49:16 2015 +0100
@@ -1,22 +1,37 @@
 
-from basic_functions import ceiling, string_to_sequence, calculate_time_span_ticks
-
+from basic_functions import ceiling, string_to_sequence, calculate_bar_ticks
 import parameter_setter 
 import rhythm_parser 
 
 class Note():
-	def __init__(self, argstring):
-		intlist = string_to_sequence(argstring)
-		self.startTime = intlist[0]
-		self.duration = intlist[1]
-		self.velocity = intlist[2]
-	
-	# toString()
+	def __init__(self, firstarg = None, duration = None, velocity = None):
+		self.startTime = 0
+		self.duration = 0
+		self.velocity = 0
+
+		if firstarg != None:
+			if isinstance(firstarg,basestring):
+				intlist = string_to_sequence(firstarg)
+				self.startTime = intlist[0]
+				self.duration = intlist[1]
+				self.velocity = intlist[2]
+			elif isinstance(firstarg,int):
+				self.startTime = firstarg
+
+		if duration != None:
+			self.duration = duration
+		if velocity != None:
+			self.velocity = velocity
+
+
+	def to_string(self):
+		return "(%d,%d,%f)" %(self.startTime, self.duration, self.velocity)
+
 
 # NoteSequence is a list of Note
 class NoteSequence(list):
-	def __init__(self, noteSequenceString=None):
-		if noteSequenceString!=None:
+	def __init__(self, noteSequenceString = None):
+		if noteSequenceString != None:
 			self.string_to_note_sequence(noteSequenceString)
 
 	def string_to_note_sequence(self, noteSequenceString):
@@ -27,17 +42,73 @@
 		for localString in listStrings:
 			self.append(Note(localString))
 
-	# toString()
+	def to_string(self):
+		noteSequenceString = ""
+		for note in self:
+			noteSequenceString += note.to_string() + ","
+		return noteSequenceString[:-1]
 
+# VelocitySequence is a list of float numbers
+class VelocitySequence(list):
+	def __init__(self, velocitySequenceString = None):
+		if velocitySequenceString != None:
+			self.string_to_velocity_sequence(velocitySequenceString)
 
-#print NoteSequence("(1,2,3),(4,5,6),(7,8,9)")
-class VelocitySequence(list):
-	def __init__(self, noteSequenceString=None):
-		if noteSequenceString!=None:
-			self.string_to_note_sequence(noteSequenceString)
+	def string_to_velocity_sequence(self,inputString):
+		self.extend(string_to_sequence(inputString))
 
-	def string_to_note_sequence(self,inputString):
-		self.extend(string_to_sequence(inputString))
+	def to_string(self):
+		return str(self)[1:-1].replace(" ","")
+
+
+def velocity_sequence_to_note_sequence(velocitySequence):
+	
+	noteSequence = NoteSequence()
+
+	for index in range(len(velocitySequence)):
+		if (velocitySequence[index]!= 0): # onset detected
+			startTime = index			
+			velocity = velocitySequence[index]
+
+			# if there are previous notes added
+			if( len(noteSequence) > 0):
+				previousNote = noteSequence[-1]
+				previousNote.duration = startTime - previousNote.startTime
+
+			# add the current note into note sequence
+			noteSequence.append( Note(startTime, 0, velocity) )
+
+	# to set the duration for the last note
+	if( len(noteSequence) > 0):
+		previousNote = noteSequence[-1]
+		previousNote.duration = len(velocitySequence) - previousNote.startTime
+
+	return noteSequence
+
+
+def note_sequence_to_velocity_sequence(noteSequence, timespanTicks = None):
+	
+	velocitySequence = VelocitySequence()
+	
+	previousNoteStartTime = -1
+
+	for note in noteSequence:
+		
+		interOnsetInterval = note.startTime - previousNoteStartTime	
+		velocitySequence += [0]*(interOnsetInterval-1)	
+		velocitySequence += [note.velocity]
+
+		previousNoteStartTime = note.startTime
+
+	if timespanTicks!=None:
+		velocitySequence += [0]*(timespanTicks - len(velocitySequence))
+	else:
+		velocitySequence += [0]*(noteSequence[-1].duration-1)
+
+	# normalising velocity sequence between 0-1
+	velocitySequence = [float(v)/max(velocitySequence) for v in velocitySequence]
+
+	return velocitySequence
 
 
 class BarList(list):
@@ -48,9 +119,6 @@
 		super(BarList, self).append(bar)
 
 
-
-
-
 class Bar:
 
 	def __init__(self, rhythmSequence, timeSignature, ticksPerQuarter=None, qpmTempo=None, nextBar=None, prevBar=None):
@@ -68,13 +136,13 @@
 		self.prevBar = prevBar
 
 	def get_note_sequence(self):
-		if self.noteSequence==None:
-			self.noteSequence = velocity_sequence_to_notes(self.velocitySequence)
+		if self.noteSequence == None:
+			self.noteSequence = velocitySequence_to_noteSequence(self.velocitySequence)
 		return self.noteSequence
 
 	def get_velocity_sequence(self):
-		if self.velocitySequence==None:
-			self.velocitySequence = note_sequence_to_velocities(self.noteSequence)
+		if self.velocitySequence == None:
+			self.velocitySequence = noteSequence_to_velocitySequence(self.noteSequence)
 		return self.velocitySequence
 
 	def get_binary_sequence(self):
@@ -102,8 +170,9 @@
 		return self.timeSignature
 
 	# return the length of a bar in time units (ticks)
-	def get_time_span(self):
-		return calculate_time_span_ticks(self.timeSignature.get_numerator(),self.timeSignature.get_denominator(), self.ticksPerQuarter)
+	def get_bar_ticks(self):
+		return calculate_bar_ticks(self.timeSignature.get_numerator(),self.timeSignature.get_denominator(), self.ticksPerQuarter)
+
 
 class TimeSignature():
 	def __init__(self, inputString):