# HG changeset patch # User christopherh # Date 1430124675 -3600 # Node ID e7102885113170a41a498e79464c9d7444c1e221 # Parent af3f32cebf8c06252cbf57515ecad600da812b4c updating latex and bug fixes to main py files diff -r af3f32cebf8c -r e71028851131 SMC2015latex/conclusion.tex --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SMC2015latex/conclusion.tex Mon Apr 27 09:51:15 2015 +0100 @@ -0,0 +1,2 @@ +\section{Conclusion} +\label{sec:conclusion} \ No newline at end of file diff -r af3f32cebf8c -r e71028851131 SMC2015latex/dataset.tex --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SMC2015latex/dataset.tex Mon Apr 27 09:51:15 2015 +0100 @@ -0,0 +1,2 @@ +\section{Data Set} +\label{sec:data} \ No newline at end of file diff -r af3f32cebf8c -r e71028851131 SMC2015latex/framework.tex --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SMC2015latex/framework.tex Mon Apr 27 09:51:15 2015 +0100 @@ -0,0 +1,97 @@ +\section{Framework} + +\begin{figure}[t] +\centering +\includegraphics[width=0.6\columnwidth]{images/framework.pdf} +\caption{Module hierarchy in the synpy toolkit: The top-level module provides a simple interface for the user to test different syncopation models. Musical constructs such as bars, velocity and note sequences, notes and time signatures are defined in the `music objects' module while support for common procedures such as sequence concatenation and subdivision is provided in `basic functions'. Models and file reading components can be interchanged as required by the user.\label{fig:framework}} +\end{figure} + +\begin{figure} +\footnotesize{ +\begin{minted}[frame=single,framesep=10pt]{python} +T{4/4} # time signature +TPQ{4} # ticks per quarternote +# Bar 1 +Y{(0,3,2),(3,1,1),(6,2,2),(10,2,1),(12,4,1)} +# Bar 2 +V{1,0,0,0.5,0,0,1,0,0,0,0.5,0,0.5,0,0,0} +\end{minted} +} +\caption{Example rhythm annotation \code{.rhy} file containing two bars of the Son Clave rhythm. The first is expressed as a note sequence with resolution of four ticks per quarternote; the second is the same rhythm expressed as a velocity sequence (see section~\ref{sec:background}.} +\label{ta:clave} +\end{figure} +\begin{figure} +\footnotesize{ +\begin{minted}[frame=single,framesep=10pt]{python} +>>>from synpy import * +>>>import synpy.PRS as model +>>>calculate_syncopation(model, "clave.rhy") +{'bars_with_valid_output': [0, 1], + 'mean_syncopation_per_bar': 8.625, + 'model_name': 'PRS', + 'number_of_bars': 2, + 'number_of_bars_not_measured': 0, + 'source': 'clave.rhy', + 'summed_syncopation': 17.25, + 'syncopation_by_bar': [8.625, 8.625]} +\end{minted} +} +\caption{To use the library, the top level \code{synpy} module is imported along with a model (in this example Pressing \cite{pressing}); calling \code{calculate\_syncopation()} gives the syncopation results as shown. +\label{ta:example} } +\end{figure} + +The architecture of the toolkit is relatively simple (see Figure~\ref{fig:framework}); syncopation values are calculated for each bar in a given source of rhythm data with the user specifying which model to use and supplying any special parameters that are required. Output can optionally be saved directly to XML or JSON files. Sources of rhythm data can be the name of a file containing rhythm data or a bar or list of bar objects as defined in section~\ref{sec:?}. Where a model is unable to calculate a value for a given rhythm pattern, a “None” value is recorded for that bar and the indices of unmeasured bars reported in the output. + +Musical constructs such as bars, velocity and note sequences, notes and time signatures are defined in the `music objects' module with support for common procedures such as sequence concatenation and subdivision being provided in `basic functions'. Two file reader modules are currently provided; one for reading standard MIDI files and one for reading plain text rhythm annotation \code{.rhy} files. These modules open their respective file types and return a list of bar objects that the syncopation models can process. + +The system architecture has been designed to allow new models to be added easily. Models have a common interface, exposing a single function that will return the syncopation value for a bar of music. Optional parameters may be supplied as a Python dictionary if the user wishes to specify settings different from the those given in the literature for a specific model. + + + +% \section{MIDI Input}\label{sec:midi} + +% \cite{Taylor89MusicTheory} + +% \section{Text Input}\label{sec:textinput} + +% \begin{table*} +% \small{ +% \begin{minted}[frame=single,framesep=10pt]{console} +% ::= [] | + +% ::= "\n" | "\n" + +% ::= [] [] "\n" + +% ::= "#" + +% ::= | [] | + +% ::= [] [] [] + +% ::= "QPM{" "}" + +% ::= "TPQ{" "}" + +% ::= "T{" "/" "}" + +% ::= "V{" "}" | "Y{" "}" + +% ::= | "," + +% ::= "(" "," "," ")" + +% ::= | "," + +% ::= "0" | "1" | "0." + +% ::= | + +% ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" +% \end{minted} +% } +% \caption{Syntax of rhythm text format Backus-Naur Form} +% \label{ta:BNF} +% \end{table*} + + diff -r af3f32cebf8c -r e71028851131 SMC2015latex/syncopation_toolkit.tex --- a/SMC2015latex/syncopation_toolkit.tex Mon Apr 27 09:46:18 2015 +0100 +++ b/SMC2015latex/syncopation_toolkit.tex Mon Apr 27 09:51:15 2015 +0100 @@ -11,6 +11,8 @@ \usepackage{cite} \usepackage{minted} +\newcommand{\code}[1]{{\small\texttt{#1}}} + %%%%%%%%%%%%%%%%%%%%%%%% Some useful packages %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%% See related documentation %%%%%%%%%%%%%%%%%%%%%%%%%% %\usepackage{amsmath} % popular packages from Am. Math. Soc. Please use the @@ -138,95 +140,24 @@ \end{abstract} % -\section{Introduction}\label{sec:introduction} +\input{introduction} + +\input{background} + +\input{framework} + +\input{dataset} + +\input{conclusion} + + % \url{http://www.maynoothuniversity.ie/smc15}. -\input{sections/backgroundLP} -\section{MIDI Input}\label{sec:midi} -\cite{Taylor89MusicTheory} -\section{Text Input}\label{sec:textinput} - -\begin{table*} -\small{ -\begin{minted}[frame=single,framesep=10pt]{console} - ::= [] | - - ::= "\n" | "\n" - - ::= [] [] "\n" - - ::= "#" - - ::= | [] | - - ::= [] [] [] - - ::= "QPM{" "}" - - ::= "TPQ{" "}" - - ::= "T{" "/" "}" - - ::= "V{" "}" | "Y{" "}" - - ::= | "," - - ::= "(" "," "," ")" - - ::= | "," - - ::= "0" | "1" | "0." - - ::= | - - ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" -\end{minted} -} -\caption{Syntax of rhythm text format Backus-Naur Form} -\label{ta:BNF} -\end{table*} - - -\begin{table} -\footnotesize{ -\begin{minted}[frame=single,framesep=10pt]{python} -T{4/4} # time signature -TPQ{4} # ticks per quarternote -# Bar 1 -Y{(0,3,2),(3,1,1),(6,2,2),(10,2,1),(12,4,1)} -# Bar 2 -V{1,0,0,0.5,0,0,1,0,0,0,0.5,0,0.5,0,0,0} -\end{minted} -} -\caption{Example rhythm annotation file containing two bars of the Son Clave rhythm. The first is expressed as a note sequence with resolution of four ticks per quarternote; the second is the same rhythm expressed as a velocity sequence.} -\label{ta:clave} -\end{table} - - -\begin{table} -\footnotesize{ -\begin{minted}[frame=single,framesep=10pt]{python} ->>>from syncopation import * ->>>import PRS as model ->>>calculate_syncopation(model, "clave.rhy") -{'bars_with_valid_output': [0, 1], - 'mean_syncopation_per_bar': 8.625, - 'model_name': 'PRS', - 'number_of_bars': 2, - 'number_of_bars_not_measured': 0, - 'source': 'clave.rhy', - 'summed_syncopation': 17.25, - 'syncopation_by_bar': [8.625, 8.625]} -\end{minted} -} -\caption{Syntax of rhythm text format Backus-Naur Form} -\label{ta:clave} -\end{table} @@ -278,14 +209,6 @@ - - - - -\section{Conclusions} - -some conclusions - % \begin{acknowledgments} You may acknowledge people, projects, diff -r af3f32cebf8c -r e71028851131 Syncopation models/synpy/LHL.py --- a/Syncopation models/synpy/LHL.py Mon Apr 27 09:46:18 2015 +0100 +++ b/Syncopation models/synpy/LHL.py Mon Apr 27 09:51:15 2015 +0100 @@ -6,37 +6,42 @@ from basic_functions import concatenate, repeat, subdivide, ceiling, get_rhythm_category from parameter_setter import are_parameters_valid -terminalNodes = [] # Global variable, storing all the terminal nodes from recursive tree structure in time order -# Each terminnal node contains two properties: its node type (note or rest) and its metrical weight. +# Each terminal node contains two properties: its node type (note or rest) and its metrical weight. class Node: def __init__(self,nodeType,metricalWeight): self.nodeType = nodeType self.metricalWeight = metricalWeight # This function will recurse the tree for a binary sequence and return a sequence containing the terminal nodes in time order. -def recursive_tree(binarySequence, subdivisionSequence, weightSequence, metricalWeight, level): +def recursive_tree(binarySequence, subdivisionSequence, weightSequence, metricalWeight, level, Lmax): # If matching to a Note type, add to terminal nodes + output = list() if binarySequence == concatenate([1],repeat([0],len(binarySequence)-1)): - terminalNodes.append(Node('N',metricalWeight)) + output.append(Node('N',metricalWeight)) # If matching to a Rest type, add to terminal nodes elif binarySequence == repeat([0],len(binarySequence)): - terminalNodes.append(Node('R',metricalWeight)) + output.append(Node('R',metricalWeight)) + + elif level+1 == Lmax: + print "WARNING: LHL tree recursion descended to Lmax, returning a note node but result will not be fully accurate. Check the rhythm pattern under test and/or specify larger Lmax to rectify the problem." + output.append(Node('N',metricalWeight)) # Keep subdividing by the subdivisor of the next level - else: + else: subBinarySequences = subdivide(binarySequence, subdivisionSequence[level+1]) subWeightSequences = concatenate([metricalWeight],repeat([weightSequence[level+1]],subdivisionSequence[level+1]-1)) for a in range(len(subBinarySequences)): - recursive_tree(subBinarySequences[a], subdivisionSequence, weightSequence, subWeightSequences[a], level+1) - + output = output + recursive_tree(subBinarySequences[a], subdivisionSequence, weightSequence, subWeightSequences[a], level+1, Lmax) + + return output def get_syncopation(bar, parameters = None): - del terminalNodes[:] syncopation = None + naughtyglobal = 0 binarySequence = bar.get_binary_sequence() subdivisionSequence = bar.get_subdivision_sequence() @@ -44,9 +49,12 @@ # LHL can only measure monorhythms if get_rhythm_category(binarySequence, subdivisionSequence) == 'poly': print 'Warning: LHL model detects polyrhythms so returning None.' + elif bar.is_empty(): + print 'LHL model detects empty bar so returning -1.' + syncopation = -1 else: # set defaults - Lmax = 5 + Lmax = 10 weightSequence = range(0,-Lmax-1,-1) # if parameters are specified by users, check their validities and update parameters if valid if parameters!= None: @@ -58,25 +66,34 @@ if not are_parameters_valid(Lmax, weightSequence, subdivisionSequence): print 'Error: the given parameters are not valid.' else: - # If there is rhythm in previous bar, process its tree structure + + # For the rhythm in the current bar, process its tree structure and store the terminal nodes + terminalNodes = recursive_tree(ceiling(binarySequence), subdivisionSequence, weightSequence, weightSequence[0],0, Lmax) + + # save the terminal nodes on the current bar so that + # the next bar can access them... + bar.LHLterminalNodes = terminalNodes + + # If there is rhythm in the previous bar and we've already processed it prevbar = bar.get_previous_bar() - if prevbar != None and not prevbar.is_empty(): - prebarBinarySequence = prevbar.get_binary_sequence() - recursive_tree(ceiling(prebarBinarySequence), subdivisionSequence, weightSequence, weightSequence[0],0) - - if len(terminalNodes)>0: + if prevbar != None and prevbar.is_empty() != True: + # get its LHL tree if it has one + try: + prevbarNodes = prevbar.LHLterminalNodes + except AttributeError: + prevbarNodes = [] + + # find the final note node in the previous bar: + if len(prevbarNodes)>0: + i = len(prevbarNodes) - 1 # Only keep the last note-type node - while terminalNodes[-1].nodeType != 'N': - del terminalNodes[-1] - del terminalNodes[0:-1] - - # For the rhythm in the current bar, process its tree structure and store the terminal nodes - recursive_tree(ceiling(binarySequence), subdivisionSequence, weightSequence, weightSequence[0],0) + while prevbarNodes[i].nodeType != 'N' and i>=0: + i = i-1 + # prepend the note to the terminal node list for this bar + terminalNodes = [ prevbarNodes[i] ] + terminalNodes + - # for t in terminalNodes: - # print '<', t.nodeType, t.metricalWeight, '>' - - # Search for the NR pairs that contribute to syncopation,then add the weight-difference to the NRpairSyncopation list + # Search for the NR pairs that contribute to syncopation, then add the weight-difference to the NRpairSyncopation list NRpairSyncopation = [] for i in range(len(terminalNodes)-1,0,-1): if terminalNodes[i].nodeType == 'R': @@ -84,7 +101,6 @@ if (terminalNodes[j].nodeType == 'N') & (terminalNodes[i].metricalWeight >= terminalNodes[j].metricalWeight): NRpairSyncopation.append(terminalNodes[i].metricalWeight - terminalNodes[j].metricalWeight) break - #print NRpairSyncopation # If there are syncopation, sum all the local syncopation values stored in NRpairSyncopation list if len(NRpairSyncopation) != 0: diff -r af3f32cebf8c -r e71028851131 Syncopation models/synpy/PRS.py --- a/Syncopation models/synpy/PRS.py Mon Apr 27 09:46:18 2015 +0100 +++ b/Syncopation models/synpy/PRS.py Mon Apr 27 09:51:15 2015 +0100 @@ -38,12 +38,14 @@ def get_syncopation(bar, parameters = None): syncopation = None - binarySequence = bar.get_binary_sequence() + binarySequence = velocity_sequence_to_min_timespan(bar.get_binary_sequence()) subdivisionSequence = bar.get_subdivision_sequence() # PRS does not handle polyrhythms if get_rhythm_category(binarySequence, subdivisionSequence) == 'poly': print 'Warning: PRS model detects polyrhythms so returning None.' + elif bar.is_empty(): + print 'Warning: PRS model detects empty bar so returning None.' else: syncopation = 0 diff -r af3f32cebf8c -r e71028851131 Syncopation models/synpy/music_objects.py --- a/Syncopation models/synpy/music_objects.py Mon Apr 27 09:46:18 2015 +0100 +++ b/Syncopation models/synpy/music_objects.py Mon Apr 27 09:51:15 2015 +0100 @@ -120,8 +120,10 @@ for note in noteSequence: interOnsetInterval = note.startTime - previousNoteStartTime - velocitySequence += [0]*(interOnsetInterval-1) - velocitySequence += [note.velocity] + #ignore note if it is part of a chord... + if interOnsetInterval!=0: + velocitySequence += [0]*(interOnsetInterval-1) + velocitySequence += [note.velocity] previousNoteStartTime = note.startTime @@ -150,6 +152,21 @@ self.append(localbar) barList.remove(localbar) + def to_string(self, sequenceType="y"): + + output = "" + + for bar in self: + prev = bar.get_previous_bar() + + params = "t"+sequenceType + + if prev!=None and prev.get_time_signature()==bar.get_time_signature(): + params = "-"+params + + output += " " + bar.to_string(params) + + return output class Bar: def __init__(self, rhythmSequence, timeSignature, ticksPerQuarter=None, qpmTempo=None, nextBar=None, prevBar=None): @@ -216,17 +233,22 @@ else: return True - def to_string(self, sequenceType=None): - output = "t{"+self.timeSignature.to_string()+"}" - prev = self.get_previous_bar() - if prev!=None: - if prev.get_time_signature()==self.get_time_signature(): - output="" + def to_string(self, sequenceType="ty"): + + # prev = self.get_previous_bar() + # if prev!=None: + # if prev.get_time_signature()==self.get_time_signature(): + # output="" + output = "" - if sequenceType==None or sequenceType=="v": + if "-t" not in sequenceType: + output = "t{"+self.timeSignature.to_string()+"}" + + if "v" in sequenceType: output += "v{"+self.get_velocity_sequence().to_string()+"}" else: output += "y{"+self.get_note_sequence().to_string()+"}" + return output diff -r af3f32cebf8c -r e71028851131 Syncopation models/synpy/parameter_setter.py --- a/Syncopation models/synpy/parameter_setter.py Mon Apr 27 09:46:18 2015 +0100 +++ b/Syncopation models/synpy/parameter_setter.py Mon Apr 27 09:51:15 2015 +0100 @@ -10,19 +10,19 @@ # {'key': time-signature} : # {'value': [subdivision-sequence, theoretical beat-level represented by index in the subdivision-sequence list]} timeSignatureBase = { - '2/2': [[1,2,2,2,2,2],1], - '3/2': [[1,3,2,2,2,2],1], - '4/2': [[1,2,2,2,2,2],1], - '2/4': [[1,2,2,2,2,2],1], - '3/4': [[1,3,2,2,2,2],1], - '4/4': [[1,2,2,2,2,2],2], - '5/4': [[1,5,2,2,2,2],1], - '7/4': [[1,7,2,2,2,2],1], - '3/8': [[1,3,2,2,2,2],1], - '5/8': [[1,5,2,2,2,2],1], - '6/8': [[1,2,3,2,2,2],1], - '9/8': [[1,3,3,2,2,2],1], - '12/8':[[1,2,2,3,2,2],2], + '2/2': [[1,2,2,2,2,2,2,2,2,2,2,2,2,2],1], + '3/2': [[1,3,2,2,2,2,2,2,2,2,2,2,2,2],1], + '4/2': [[1,2,2,2,2,2,2,2,2,2,2,2,2,2],1], + '2/4': [[1,2,2,2,2,2,2,2,2,2,2,2,2,2],1], + '3/4': [[1,3,2,2,2,2,2,2,2,2,2,2,2,2],1], + '4/4': [[1,2,2,2,2,2,2,2,2,2,2,2,2,2],2], + '5/4': [[1,5,2,2,2,2,2,2,2,2,2,2,2,2],1], + '7/4': [[1,7,2,2,2,2,2,2,2,2,2,2,2,2],1], + '3/8': [[1,3,2,2,2,2,2,2,2,2,2,2,2,2],1], + '5/8': [[1,5,2,2,2,2,2,2,2,2,2,2,2,2],1], + '6/8': [[1,2,3,2,2,2,2,2,2,2,2,2,2,2],1], + '9/8': [[1,3,3,2,2,2,2,2,2,2,2,2,2,2],1], + '12/8':[[1,2,2,3,2,2,2,2,2,2,2,2,2,2],2], } diff -r af3f32cebf8c -r e71028851131 Syncopation models/synpy/readmidi.py --- a/Syncopation models/synpy/readmidi.py Mon Apr 27 09:46:18 2015 +0100 +++ b/Syncopation models/synpy/readmidi.py Mon Apr 27 09:51:15 2015 +0100 @@ -94,7 +94,7 @@ return timesig - def get_time_signature(timeList,barStartTime, barEndTime, currentTimeSignature = None): + def get_time_signature(timeList,barStartTime, barLength, ticksPerQuarter, currentTimeSignature = None): timesig = None i=0 @@ -104,12 +104,15 @@ # before the end of the current bar event = timeList[i] i = i + 1 - if event.time>=barEndTime: + if event.time>=barStartTime+barLength: break if event.type=="TIME_SIGNATURE" and event.time>=barStartTime: timesig = midi_event_to_time_signature(event) event.type = "USED_TIME_SIGNATURE" + barLength = calculate_bar_ticks(timesig.get_numerator(), + timesig.get_denominator(), + ticksPerQuarter) if timesig==None: if currentTimeSignature==None: @@ -117,7 +120,7 @@ else: timesig = currentTimeSignature - return timesig + return timesig,barLength def get_tempo(timeList,barStartTime, barEndTime, currentTempo = None): @@ -168,7 +171,7 @@ barlength = calculate_bar_ticks(timesig.get_numerator(), timesig.get_denominator(), ticksPerQuarter) # initialise time for start and end of current bar barStartTime = 0 - barEndTime = barlength + barEndTime = 0# barlength # initialise bars list @@ -181,7 +184,10 @@ #create a local note sequence to build a bar currentNotes = NoteSequence() - timesig = get_time_signature(timeList,barStartTime, barEndTime, timesig) + [timesig,barlength] = get_time_signature(timeList,barStartTime, barlength, ticksPerQuarter, timesig) + + barEndTime = barEndTime + barlength + tempo = get_tempo(timeList,barStartTime, barEndTime, tempo) @@ -202,8 +208,6 @@ barStartTime = barEndTime - barEndTime = barEndTime + barlength - return bars @@ -231,10 +235,18 @@ 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) + + #if this note starts at the same time as the previous one + # replace the previous one if this has longer duration + if len(noteslist)>0 and note.startTime==noteslist[-1].startTime: + if note.duration>noteslist[-1].duration: + noteslist[-1]=note + # otherwise add the note to the list + else: + noteslist.append(note) #found the end of the note so break out of the local loop break + localindex = localindex+1 return noteslist diff -r af3f32cebf8c -r e71028851131 Syncopation models/synpy/syncopation.py --- a/Syncopation models/synpy/syncopation.py Mon Apr 27 09:46:18 2015 +0100 +++ b/Syncopation models/synpy/syncopation.py Mon Apr 27 09:51:15 2015 +0100 @@ -21,7 +21,9 @@ barlist = source sourceType = "bar list" elif isinstance(source, Bar): - barlist = BarList().append(source) + barlist = BarList() + barlist.append(source) + print barlist sourceType = "single bar" elif isinstance(source, basestring): #treat source as a filename @@ -43,13 +45,19 @@ discardedlist = [] includedlist = [] + if barlist!=None: for bar in barlist: - if not bar.is_empty(): - barSyncopation = sync_perbar_permodel(model, bar, parameters) - else: - barSyncopation = None - print 'Bar %d cannot be measured because it is empty, returning None.' % barlist.index(bar) + print 'processing bar %d' % (barlist.index(bar)+1) + + barSyncopation = sync_perbar_permodel(model, bar, parameters) + + + # if not bar.is_empty(): + # barSyncopation = sync_perbar_permodel(model, bar, parameters) + # else: + # barSyncopation = None + # print 'Bar %d cannot be measured because it is empty, returning None.' % barlist.index(bar) barResults.append(barSyncopation) if barSyncopation != None: @@ -59,13 +67,16 @@ else: barsDiscarded += 1 discardedlist.append(barlist.index(bar)) - print 'Model could not measure bar %d, returning None.' % barlist.index(bar) + print 'Model could not measure bar %d, returning None.' % (barlist.index(bar)+1) import WNBD if model is WNBD: total = total / numberOfNotes - average = total / (len(barResults)-barsDiscarded) + if len(barResults)>barsDiscarded: + average = total / (len(barResults)-barsDiscarded) + else: + average = total output = { "model_name":model.__name__ , @@ -75,6 +86,7 @@ "number_of_bars":len(barResults), "number_of_bars_not_measured":barsDiscarded, "bars_with_valid_output":includedlist, + "bars_without_valid_output":discardedlist, "syncopation_by_bar":barResults }