Mercurial > hg > syncopation-dataset
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):