Difference between revisions of "Humdrum Extras"

From CCARH Wiki
Jump to navigation Jump to search
 
(118 intermediate revisions by the same user not shown)
Line 1: Line 1:
[http://extras.humdrum.org Humdrum Extras] is a set of command-line programs and C++ parser library for processing [[Humdrum files]].  The programs can be compiled for Linux, Apple OS X, or Windows (primarily within [[cygwin]], but also in Visual C++).  The Humdrum Extras library can be used to parse Humdrum files independent of the example programs provided with the package.
+
[http://extras.humdrum.org Humdrum Extras] is a set of command-line programs and C++ library for processing [[Humdrum files]].  The programs can be compiled for Linux, Apple OS X, or Windows (primarily within [[cygwin]], but also in Visual C++).  The Humdrum Extras library can be used to parse Humdrum files independent of the example programs provided with the package.
  
  
 
= Example Programs =
 
= Example Programs =
  
The primary intent of the Humdrum Extras package is for user-based processing of Humdrum files as an auxiliary to the [[Humdrum Toolkit]].  Since the programs are compiled from C++, they process data much faster than programs written in interpreted languages, such as [http://en.wikipedia.org/wiki/AWK AWK] which is the main development language for the Humdrum Toolkit.
+
The primary intent of the Humdrum Extras package is for user-based processing of Humdrum files as an auxiliary to the [https://github.com/humdrum-tools/humdrum-tools Humdrum Toolkit].  Since the programs are compiled from C++ code, they process data much faster than programs written in interpreted languages, such as [http://en.wikipedia.org/wiki/AWK AWK] which is the main development language for the Humdrum Toolkit.
  
Documentation for example programs can be found on the web at [http://extras.humdrum.org/man extras.humdrum.org/man].  The source code for these programs is found in the [http://extras.humdrum.org/download download] file, within the <tt>src-programs</tt> directory, or they can be viewed [http://extras.humdrum.org/download/humextra online].
+
Documentation for example programs can be found on the web at [http://extras.humdrum.org/man extras.humdrum.org/man].  The source code for these programs is found in this [http://github.com/craigsapp/humextra/archive/master.zip zip file], within the <tt>src-programs</tt> directory, or they can be viewed [http://github.com/craigsapp/humextra/tree/master/src-programs online].
 +
 
 +
 
 +
== Installing Humdrum Extras (only) ==
 +
 
 +
Humdrum Extras is most conveniently downloaded and updated using the [http://en.wikipedia.org/wiki/Git_(software) git] command (or an interface program which uses git).  Type 'which git' in a terminal to see if it is installed.  In linux it should be easy to install with either 'sudu apt-get install git' or 'sudo yum install git', depending on your flavor of linux.  In OS X, [http://brew.sh Homebrew] is the most convenient unix package manager.  If you install that, then it installs git at the same time.
 +
 
 +
If you have the <tt>git</tt> program installed on your computer, then you can use that system to download [http://github.com/craigsapp/humextra humextra] from GitHub; otherwise, download the [http://github.com/craigsapp/humextra/archive/master.zip zip file] and unzip it.  When using git, type this command in the terminal:
 +
      git clone https://github.com/craigsapp/humextra
 +
To compile the library and example programs:
 +
      cd humextra
 +
      make
 +
Compiled programs are stored in humextra/bin. If you do not want to compile the example programs, instead type:
 +
      make library
 +
 
 +
To periodically receive updates, go into the humextra directory, and type:
 +
    git pull
 +
    make clean # optional, but a good idea
 +
    make
 +
 
 +
This will download the most recent changes in the library source code and recompile the library and example programs.  To update only the library file without recompiling the example programs:
 +
    make libupdate
 +
 
 +
== Installing Humdrum Tools (which includes Humdrum Extras) ==
 +
 
 +
The Humdrum Toolkit package includes both the standard Humdrum Toolkit and Humdrum Extras:
 +
 
 +
  git clone --recursive https://github.com/humdrum-tools/humdrum-tools
 +
 
 +
To setup everything do these commands:
 +
    cd humdrum-tools
 +
    make
 +
 
 +
To periodically update the software:
 +
    cd humdrum-tools
 +
    make update
 +
    make clean # Optional but good to clear out old compiled code.
 +
    make
 +
 
 +
The Humdrum Extras package will be in humdrum-tools/humextra
  
 
<br><br><br>
 
<br><br><br>
 +
 +
== Using the Humextras Library ==
 +
 +
You can place your source code directly into the Humdrum Extras source-code directory, but this will cause problem when updating with git, or possible accidental deletion.  Instead, it may be wise to create a parallel directory structure with symbolic links to the important components of the main humextra installation.
 +
 +
Here is a short bash shell script to create a linked installation of humextra in the current directory, provided that humextra can be found in the command path.  Copy and paste into a terminal to set up a linked copy of humextra:
 +
 +
      HUMDIR=`echo $PATH | tr : '\n' | grep humextra | head -n 1 | sed 's/\/bin$//'`
 +
      if <nowiki>[[</nowiki> -z $HUMDIR <nowiki>]]</nowiki>
 +
      then
 +
        echo "Humdrum Extras not found in PATH variable: not creating linked installation."
 +
      else
 +
        echo "Creating myhum directory, linked to $HUMDIR."
 +
        mkdir myhum
 +
        cd myhum
 +
        ln -s $HUMDIR/include .
 +
        ln -s $HUMDIR/external .
 +
        ln -s $HUMDIR/lib .
 +
        ln -s $HUMDIR/bin .
 +
        ln -s $HUMDIR/Makefile .
 +
        ln -s $HUMDIR/Makefile.programs .
 +
        mkdir src-programs
 +
    fi
 +
 +
You should then be able to place your code in myhum/src-programs, such as myprog.cpp, then compile them within the myhum directory with the following command in the myhum directory:
 +
      make myprog
 +
where "src-programs/myprog.cpp" is the source code.  This will create an executable in the bin directory: "bin/myprog".  You can run it with the command "bin/myprog" or just "myprog" if the Humdrum Extras bin directory is in the $PATH environment variable.
  
 
= Programming Examples =
 
= Programming Examples =
 +
 +
Here are a set of graduated program examples which can be used as a basis for writing your own programs.
  
 
== Basic data access ==
 
== Basic data access ==
Line 20: Line 88:
 
Here is a very simple C++ program called <i>humecho.cpp</i> that uses the Humdrum file parser in the Humdrum Extras library:
 
Here is a very simple C++ program called <i>humecho.cpp</i> that uses the Humdrum file parser in the Humdrum Extras library:
  
<source lang="cpp">
+
<ul>
 +
::<table style="background:white;"><tr><td><source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 
#include <iostream>
 
#include <iostream>
 +
using namespace std;
  
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
   if (argc > 1) hfile.read(argv[1]);
+
   if (argc > 1) { hfile.read(argv[1]); }
   else hfile.read(std::cin);
+
   else { hfile.read(cin); }
   std::cout << hfile;
+
   cout << hfile;
 
   return 0;
 
   return 0;
 
}
 
}
</source>
+
</source></td></tr></table>
 +
</ul>
  
 
This program will take one Humdrum file as an argument (or standard input) and echo the contents of the Humdrum file to standard output.  To compile this program using the Humdrum Extras makefiles, place <tt>humecho.cpp</tt> in the directory <tt>humextra/src-programs</tt>, and then type "<tt>make humecho</tt>" in the <tt>humextra</tt> directory.  The compiled program will be placed in <tt>bin/humecho</tt>.  The <tt>humecho</tt> program can be utilized in several ways, including downloading from the web, or using the humdrum:// URI (or hum:// or h:// abbreviations):
 
This program will take one Humdrum file as an argument (or standard input) and echo the contents of the Humdrum file to standard output.  To compile this program using the Humdrum Extras makefiles, place <tt>humecho.cpp</tt> in the directory <tt>humextra/src-programs</tt>, and then type "<tt>make humecho</tt>" in the <tt>humextra</tt> directory.  The compiled program will be placed in <tt>bin/humecho</tt>.  The <tt>humecho</tt> program can be utilized in several ways, including downloading from the web, or using the humdrum:// URI (or hum:// or h:// abbreviations):
  
    cat file.krn | bin/humecho           | less    # standard input
+
<ul>
    bin/humecho file.krn                 | less    # command-line argument
+
::<table style="background:white;"><tr><td>
    bin/humecho h://wtc/wtc1f01.krn     | less    # humdrum:// URI
+
<source lang="bash">
    bin/humecho <nowiki>http://y.z.com/file.krn</nowiki> | less    # URL
+
cat file.krn | bin/humecho               # standard input
 +
bin/humecho file.krn                     # command-line argument
 +
bin/humecho h://wtc/wtc1f01.krn           # humdrum:// URI
 +
bin/humecho jrp://Jos2721                # jrp:// URI
 +
bin/humecho http://y.z.com/file.krn       # URL
 +
</source>
 +
</td></tr></table>
 +
</ul>
  
 
<br><br><br>
 
<br><br><br>
Line 44: Line 122:
 
=== humecho2.cpp (Accessing individual lines) ===
 
=== humecho2.cpp (Accessing individual lines) ===
  
The <tt>humecho</tt> program shows how to access the datafile in its entirety.  The following source code for <tt>humecho2.cpp</tt> demonstrates how to access lines in the file individually.  A HumdrumFile class essentially consists of an array of HumdrumRecord classes, and HumdrumRecord classes essentially are character strings which print tab-delimited with <tt>cout</tt>:
+
The <tt>humecho</tt> program shows how to access the datafile in its entirety.  The following source code for <tt>humecho2.cpp</tt> demonstrates how to access lines in the file individually.  A HumdrumFile class essentially consists of an array of [http://extras.humdrum.org/download/humextra/include/HumdrumRecord.h HumdrumRecord] classes, and HumdrumRecord classes essentially are character strings which print tab-delimited with <tt>cout</tt>:
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 +
using namespace std;
  
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
   if (argc > 1) hfile.read(argv[1]);
+
   if (argc > 1) { hfile.read(argv[1]); }
   else hfile.read(std::cin);
+
   else hfile.read(cin);
 
   for (int i=0; i<hfile.getNumLines(); i++) {
 
   for (int i=0; i<hfile.getNumLines(); i++) {
       std::cout << hfile[i] << std::endl;
+
       cout << hfile[i] << std::endl;
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table>
 +
</ul>
  
 
<tt>hfile.getNumLines()</tt> returns the number of text lines in the Humdrum file stored in the hfile variable.  So the for loop iterates through each line in the file and prints it to standard output.
 
<tt>hfile.getNumLines()</tt> returns the number of text lines in the Humdrum file stored in the hfile variable.  So the for loop iterates through each line in the file and prints it to standard output.
  
 
<br><br><br>
 
<br><br><br>
 +
 
=== humecho3.cpp (Accessing spine data) ===
 
=== humecho3.cpp (Accessing spine data) ===
  
An even more verbose version of <tt>humecho</tt> is given below.  The <tt>humecho3</tt> program implements the <tt>&lt;&lt;</tt> operator as a second for-loop.  Each HumdrumRecord representing a line of music can be thought of as an array of strings, with each string being one token in the Humdrum File structure.  
+
An even more verbose version of <tt>humecho</tt> is given below.  The <tt>humecho3</tt> program mimics the <tt>&lt;&lt;</tt> operator for HumdrumRecords as a second for-loop.  Each HumdrumRecord representing a line of music can be thought of as an array of strings (const char*), with each string being one token in the Humdrum file structure.  
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 
+
using namespace std;
 +
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
   if (argc > 1) hfile.read(argv[1]);
+
   if (argc > 1) { hfile.read(argv[1]); }
   else hfile.read(std::cin);
+
   else { hfile.read(cin); }
 
   for (int i=0; i<hfile.getNumLines(); i++) {
 
   for (int i=0; i<hfile.getNumLines(); i++) {
       std::cout << "\t" << hfile[i][0];
+
       cout << hfile[i][0];
 
       for (int j=1; j<hfile[i].getFieldCount(); j++) {
 
       for (int j=1; j<hfile[i].getFieldCount(); j++) {
         std::cout << "\t" << hfile[i][j] << std::endl;
+
         cout << "\t" << hfile[i][j];
 
       }
 
       }
       std::cout << std::endl;
+
       cout << endl;
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 
+
</td></tr></table>
 +
</ul>
  
 
HumdrumRecords <em>always</em> contain at least one field, so the code "<tt>cout << hfile[i][0];</tt>" will not cause an invalid array access in any situation.  Both <tt>[]</tt> operators used on the <tt>hfile</tt> variable (first to access a <tt>HumdrumRecord</tt>, and the second for a <tt>const char*</tt>) are checked for a valid range, and the program will exit with an error if an out-of-range value is requested.
 
HumdrumRecords <em>always</em> contain at least one field, so the code "<tt>cout << hfile[i][0];</tt>" will not cause an invalid array access in any situation.  Both <tt>[]</tt> operators used on the <tt>hfile</tt> variable (first to access a <tt>HumdrumRecord</tt>, and the second for a <tt>const char*</tt>) are checked for a valid range, and the program will exit with an error if an out-of-range value is requested.
Line 93: Line 181:
  
 
<br><br><br>
 
<br><br><br>
 +
 
=== HumdrumRecord line types ===
 
=== HumdrumRecord line types ===
  
Each HumdrumRecord is a certain enumerated <i>type</i>
+
Each HumdrumRecord line in a HumdrumFile class possesses an enumerated <i>type</i>:
  
{| cellspacing=0 cellpadding=0
+
<ul>
 +
::{| class="wikitable"  cellspacing=0 cellpadding=2
 +
|-  {{Style|table header}}
 +
! align="left" valign="baseline" width="170" | Enumeration
 +
! align="left" valign="baseline"  width="280" | Description
 
|-
 
|-
|E_humrec_empty || empty line (technically invalid, but allowed in Humdrum Extras parsing)
+
|E_humrec_data || data lines other than measure
 
|-
 
|-
|E_humrec_bibliography || of the form !!!key: value”
+
|E_humrec_data_measure || line starting with =”
 
|-
 
|-
|E_humrec_global_comment&nbsp;&nbsp; || starts with “!!
+
|E_humrec_interpretation || line starting with “*
 
|-
 
|-
|E_humrec_local_comment || local comment (!)
+
|E_humrec_bibliography || reference records of the form “!!!key: value”
 
|-
 
|-
|E_humrec_data_measure || line starting with “=
+
|E_humrec_global_comment&nbsp;&nbsp; || starts with “!!, other than reference records
 
|-
 
|-
|E_humrec_interpretation || line starting with “*”
+
|E_humrec_local_comment || local comment, starting with single "!"
 
|-
 
|-
|E_humrec_data || data lines other than measure
+
|E_humrec_empty || empty line
 
|}
 
|}
 +
</ul>
  
Use the HumdrumRecord::getType() function to access the type of a line.  But for better code readability, the following helper HumdrumRecord functions interface with these enumerations:
+
Use the HumdrumRecord::getType() function to access the type of a line.  These type values can be used for <tt>switch</tt> statements, but for better code readability, the following helper HumdrumRecord functions interface with these enumerations:
  
{| cellspacing=0 cellpadding=0
+
<ul>
 +
::{| class="wikitable"  cellspacing=0 cellpadding=2
 +
|-  {{Style|table header}}
 +
! align="left" valign="baseline" align="center" width="150" | HumdrumRecord<br>method
 +
! align="left" valign="center" width="300" | Description
 
|-
 
|-
 
|.isData() || true if data (other than barline).
 
|.isData() || true if data (other than barline).
Line 132: Line 230:
 
|.isEmpty() || true if nothing on line.
 
|.isEmpty() || true if nothing on line.
 
|}
 
|}
 +
</ul>
  
 
In addition there are a few <i>composite</i> test for line types:
 
In addition there are a few <i>composite</i> test for line types:
  
{| cellspacing=0 cellpadding=0
+
<ul>
|-
+
::{| class="wikitable"  cellspacing=0 cellpadding=2
 +
|-  {{Style|table header}}
 +
! align="left" valign="baseline" align="center" width="150" | HumdrumRecord<br>method
 +
! align="left" valign="center"  width="300" | Description
 +
|- valign="baseline"
 
| .isComment()&nbsp;&nbsp;&nbsp; || isBibliographic() or isGlobalComment() or isLocalComment()
 
| .isComment()&nbsp;&nbsp;&nbsp; || isBibliographic() or isGlobalComment() or isLocalComment()
|-
+
|- valign="baseline"
| .isTandem() || Interpretation lines which contain no spine manipulators (<tt>*+</tt>, <tt>*-</tt>, <tt>*^</tt>, <tt>*v</tt>, <tt>*x</tt>, or exclusive interpretations (starting with <tt>**</tt>).
+
| .isTandem() || Interpretation lines which contain only spine manipulators (<tt>*+</tt>, <tt>*-</tt>, <tt>*^</tt>, <tt>*v</tt>, <tt>*x</tt>, or exclusive interpretations (starting with <tt>**</tt>).
|-
+
|- valign="baseline"
| .isNull() || isData() and all fields are "." (null token).
+
| .isNull() || isData() and all fields are "." (null token), or isInterpretation() and all fields are "*".
 +
|- valign="baseline"
 +
| .hasSpines() || isData() or isMeasure() or isLocalComment() or isInterpretation()
 
|}
 
|}
 +
</ul>
  
 
<br><br><br>
 
<br><br><br>
 +
 
=== "rid -GLI" (Remove all lines except for data lines) ===
 
=== "rid -GLI" (Remove all lines except for data lines) ===
  
 
The Humdrum Tool <tt>rid</tt> with the <tt>-GLI</tt> options can be implemented using the following C++ code:
 
The Humdrum Tool <tt>rid</tt> with the <tt>-GLI</tt> options can be implemented using the following C++ code:
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
Line 160: Line 269:
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table>
 +
</ul>
  
 
The above code will only print lines which are data or barlines.  The official Humdrum file specification does not technically distinguish between barlines and data, but in practice and from a logical point of view they must be separated.  So when using the Humdrum Extras C++ parser for Humdrum files, a line of data should not contain a mixture of data (or null tokens) and barlines.
 
The above code will only print lines which are data or barlines.  The official Humdrum file specification does not technically distinguish between barlines and data, but in practice and from a logical point of view they must be separated.  So when using the Humdrum Extras C++ parser for Humdrum files, a line of data should not contain a mixture of data (or null tokens) and barlines.
  
 
<br><br><br>
 
<br><br><br>
 +
 
=== "rid -GLId" (Remove comments, interpretations and null data) ===
 
=== "rid -GLId" (Remove comments, interpretations and null data) ===
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
Line 178: Line 292:
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table>
 +
</ul>
  
 +
The <tt>HumdrumRecord::isNull()</tt> returns true if all fields in the record are equal to the string "." (called a <i>null token</i> in Humdrum terminology&mdash;not related to a NULL pointer in C).
  
The <tt>HumdrumRecord::isNull()</tt> returns true if all fields in the record are equal to the string "." (called a <i>null record</i> in Humdrum terminology&mdash;not related to a NULL pointer in C).
+
<br><br><br>
  
<br><br><br>
+
== Options class (User-specified options) ==
== User-specified Options ==
 
  
 
=== "myrid -M -C -I" (Handling command-line options) ===
 
=== "myrid -M -C -I" (Handling command-line options) ===
Line 191: Line 307:
 
The Options class can be used to define multiple aliases for the same option, such as a short abbreviation and a long form.  The options are formulated on the command line according to [http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html POSIX] rules for options: single-letter options are preceded by a single dash.  Multiple-letter options are preceeded by two dashes.  When a single-letter option does not require it's own argument, they can be globbed together into a list of options preceded by a single dash.  Here are various program usages for the code below:
 
The Options class can be used to define multiple aliases for the same option, such as a short abbreviation and a long form.  The options are formulated on the command line according to [http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html POSIX] rules for options: single-letter options are preceded by a single dash.  Multiple-letter options are preceeded by two dashes.  When a single-letter option does not require it's own argument, they can be globbed together into a list of options preceded by a single dash.  Here are various program usages for the code below:
  
 
+
<ul>
{| class="wikitable"  cellspacing=0 cellpadding=0  
+
::{| class="wikitable"  cellspacing=0 cellpadding=0  
 
|-  {{Style|table header}}
 
|-  {{Style|table header}}
 
! align="left" valign="baseline" width="170" | Command
 
! align="left" valign="baseline" width="170" | Command
Line 215: Line 331:
 
| myride -M -- -file.krn -C || Process the poorly named file "<tt>-file.krn</tt>" and the even more poorly named file called "<tt>-C</tt>" which is not an option if it comes after the -- marker.
 
| myride -M -- -file.krn -C || Process the poorly named file "<tt>-file.krn</tt>" and the even more poorly named file called "<tt>-C</tt>" which is not an option if it comes after the -- marker.
 
|}
 
|}
 
+
</ul>
  
 
Note in the following source code, an extra include directive for the Options class does not need to be added, since the declaration for the Options class is included in <tt>humdrum.h</tt>.  If you want to use the Options class independent of the HumdrumFile parser, you can instead include the file "<tt>Options.h</tt>".
 
Note in the following source code, an extra include directive for the Options class does not need to be added, since the declaration for the Options class is included in <tt>humdrum.h</tt>.  If you want to use the Options class independent of the HumdrumFile parser, you can instead include the file "<tt>Options.h</tt>".
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
Line 240: Line 358:
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
 
The code "<tt>HumdrumFile hfile(opts.getArg(1));</tt>" reads data from the first argument on the command line.  Note that argument counts are indexed from 1 rather than 0.  Perhaps not a great thing to do, but was intended to allow for similar behavior with command-line string arrays in C, where the name of the command is stored in array element 0, and the first argument (or option) is stored in array element 1.  To access the name of the command, use the Options::getCommand() function.
 
The code "<tt>HumdrumFile hfile(opts.getArg(1));</tt>" reads data from the first argument on the command line.  Note that argument counts are indexed from 1 rather than 0.  Perhaps not a great thing to do, but was intended to allow for similar behavior with command-line string arrays in C, where the name of the command is stored in array element 0, and the first argument (or option) is stored in array element 1.  To access the name of the command, use the Options::getCommand() function.
Line 271: Line 390:
 
There are four Options data types:
 
There are four Options data types:
  
<center>
+
<ul>
{| class="wikitable" cellpadding="3" cellspacing="0" border="1"
+
::{| class="wikitable" cellpadding="3" cellspacing="0" border="0"
 
|- {{Style|table header}}
 
|- {{Style|table header}}
 
! scope="col" align="center" | Option type
 
! scope="col" align="center" | Option type
Line 286: Line 405:
 
| <b>s</b> || string || .getString("OptionName")
 
| <b>s</b> || string || .getString("OptionName")
 
|}
 
|}
</center>
+
</ul>
  
 
In terms of implementation, there are really only two types: booleans (with out parameters) and non-booleans (with parameters).  Within a C++ program you can acess the original string form of the option's parameter, or you can convert it into an <tt>int</tt> or a <tt>double</tt> at runtime.  For example, if an option "number" is defined, you can get the integer version of the number with .getInteger("number"), or the double version of the number with .getDouble("number"), or you can check to see if the option was set from the input arguments to the program with .getBoolean("number").
 
In terms of implementation, there are really only two types: booleans (with out parameters) and non-booleans (with parameters).  Within a C++ program you can acess the original string form of the option's parameter, or you can convert it into an <tt>int</tt> or a <tt>double</tt> at runtime.  For example, if an option "number" is defined, you can get the integer version of the number with .getInteger("number"), or the double version of the number with .getDouble("number"), or you can check to see if the option was set from the input arguments to the program with .getBoolean("number").
Line 292: Line 411:
 
Here are some example option definitions with option names, option aliases, and option types:
 
Here are some example option definitions with option names, option aliases, and option types:
  
<center>
+
<ul>
{| class="wikitable" cellpadding="3" cellspacing="0" border="1"
+
::{| class="wikitable" cellpadding="5" cellspacing="0" border="0"
 
|- {{Style|table header}}
 
|- {{Style|table header}}
 
! scope="col" align="center" | Option definition
 
! scope="col" align="center" | Option definition
 
! scope="col" align="left" | Command-line examples
 
! scope="col" align="left" | Command-line examples
|-
+
|- valign="baseline"
 
| "r=b" || <i>command</i> -r
 
| "r=b" || <i>command</i> -r
|-
+
|- valign="baseline"
 
| "m=i" || <i>command</i> -m 10 or <i>command</i> -m10
 
| "m=i" || <i>command</i> -m 10 or <i>command</i> -m10
|-
+
|- valign="baseline"
 
| "v|value=d" || <i>command</i> -v 5.23 or <i>command</i> -v5.23<br><i>command</i> --value 5.23<br><i>command</i> --value=5.23
 
| "v|value=d" || <i>command</i> -v 5.23 or <i>command</i> -v5.23<br><i>command</i> --value 5.23<br><i>command</i> --value=5.23
|-
+
|- valign="baseline"
 
| "t=s" || <i>command</i> -t string or <i>command</i> -tstring<br><i>command</i> -t "string with spaces"<br><i>command</i> -t 'funny $tring'
 
| "t=s" || <i>command</i> -t string or <i>command</i> -tstring<br><i>command</i> -t "string with spaces"<br><i>command</i> -t 'funny $tring'
 
|}
 
|}
</center>
+
</ul>
  
 
When options names (or option aliases) are a single character, the space between the option name and it parameter is optional, as in "command -m 10" or "command -m10".  When an option has multiple characters, the space is not optional, although an equals sign can be substituted for the space: "command --value 5.23" and "command --value=5.23".  When a string option contains spaces, or other special characters reserved for shell syntax, (such as [;&$|?*\]).  The multi-word option must be enclosed in quotes.  To insert a quote into the string option place a backslash before it: \".  To prevent the command-line parser from looking inside of the string use single quotes: "command -t 'funny $tring'".  In this case the final input will be "funny $tring".  If double quotes were used, $tring would be interpreted as an environmental variable and its value would be substituted, usually resulting in "funny ", since you are not likely to have the shell variable $tring defined.
 
When options names (or option aliases) are a single character, the space between the option name and it parameter is optional, as in "command -m 10" or "command -m10".  When an option has multiple characters, the space is not optional, although an equals sign can be substituted for the space: "command --value 5.23" and "command --value=5.23".  When a string option contains spaces, or other special characters reserved for shell syntax, (such as [;&$|?*\]).  The multi-word option must be enclosed in quotes.  To insert a quote into the string option place a backslash before it: \".  To prevent the command-line parser from looking inside of the string use single quotes: "command -t 'funny $tring'".  In this case the final input will be "funny $tring".  If double quotes were used, $tring would be interpreted as an environmental variable and its value would be substituted, usually resulting in "funny ", since you are not likely to have the shell variable $tring defined.
Line 316: Line 435:
 
The final component of the option definition is a default value to use if no input is given for that option on the command-line.  If no default value is given in the definition, the default value will be zero.  For example, if this option definition is given:
 
The final component of the option definition is a default value to use if no input is given for that option on the command-line.  If no default value is given in the definition, the default value will be zero.  For example, if this option definition is given:
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
   options.define("v|val|value=i:10", "an integer value");
 
   options.define("v|val|value=i:10", "an integer value");
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
 
Then here are different behaviors when accessing that option's value in C++:
 
Then here are different behaviors when accessing that option's value in C++:
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 +
<pre>
 
User-set option:  
 
User-set option:  
 
   program -v 20
 
   program -v 20
Line 333: Line 458:
 
       options.getInteger("val")        &rarr; 10
 
       options.getInteger("val")        &rarr; 10
 
       options.getInteger("v")          &rarr; 10
 
       options.getInteger("v")          &rarr; 10
 +
</pre>
 +
</td></tr></table></ul>
  
 
<br><br><br>
 
<br><br><br>
 +
 
=== Accessing option values ===
 
=== Accessing option values ===
  
As mentioned previously, the .getBoolean, .getInteger, .getDouble and .getString accessor functions are used to extract an option value from the Options database after .process() has been called on the <tt>argc</tt> and <tt>argv</tt> input parameters to <tt>main()</tt>.  All of the <i>get</i> functions can be applied to any option type.  For example, using the option definition:
+
As mentioned previously, the .getBoolean, .getInteger, .getDouble and .getString accessor functions are used to extract an option value from the Options database after .process() has been called on the <tt>argc</tt> and <tt>argv</tt> input parameters to <tt>main()</tt>.  All of the <i>get</i> functions can be applied to any option type.  For example, the option definition:
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
   .define("t|tempature=d:80.6 Farenheit", "temperature setting")
+
   .define("t|temperature=d:80.6 Farenheit", "temperature setting")
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
 
can be used to extract any of the four option types in C++:
 
can be used to extract any of the four option types in C++:
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 +
<pre>
 
   .getBoolean("temperature")          &rarr; 1 (true) if set via the command-line.
 
   .getBoolean("temperature")          &rarr; 1 (true) if set via the command-line.
 
                                         &rarr; 0 (false) if not set via the command-line.
 
                                         &rarr; 0 (false) if not set via the command-line.
Line 350: Line 484:
 
   .getDouble("temperature")            &rarr; 80.6
 
   .getDouble("temperature")            &rarr; 80.6
 
   .getString("temperature")            &rarr; "80.6 Farenheit"
 
   .getString("temperature")            &rarr; "80.6 Farenheit"
 +
</pre>
 +
</td></tr></table></ul>
  
 
<br><br><br>
 
<br><br><br>
 +
 
== Input from piped data or file(s) ==
 
== Input from piped data or file(s) ==
  
Most of the previous program examples expect a single filename as input for processing.  The following program example (<tt>humecho4</tt> is more flexible, allowing for multiple input files.  If no filenames are given, then standard input will be read as the input data:
+
Most of the previous program examples expect a single filename as input for processing.  The following program example (<tt>humecho4</tt>) is more flexible, allowing for multiple input files.  If no filenames are given, then standard input will be read as the input data:
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 +
using namespace std;
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   Options options(argc, argv);
 
   Options options(argc, argv);
Line 365: Line 505:
 
   for (int i=1; i<=numinputs || i==0; i++) {
 
   for (int i=1; i<=numinputs || i==0; i++) {
 
       if (numinputs < 1) {
 
       if (numinputs < 1) {
         hfile.read(std::cin); // read from standard input
+
         hfile.read(cin); // read from standard input
 
       } else {
 
       } else {
 
         hfile.read(options.getArg(i));
 
         hfile.read(options.getArg(i));
 
       }
 
       }
 
       // do something with the Humdrum data here:
 
       // do something with the Humdrum data here:
       std::cout << hfile;
+
       cout << hfile;
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
 
This program has an identical function to <tt>humecho.cpp</tt>, but now multiple files can be read in and processed at the same time.  For example if there are two input files with these contents:
 
This program has an identical function to <tt>humecho.cpp</tt>, but now multiple files can be read in and processed at the same time.  For example if there are two input files with these contents:
  
          file 1                  file 2
 
          ========                =========
 
  
          **kern                  **kern
 
          1c                      2cc
 
          2d                      4b
 
          4e                      2a
 
          *-                      *-
 
  
The final output from the above program will be:
+
<ul>
<source lang="humdrum">
+
::<table style="background:white;"><tr valign=top><td>
 +
<center>file 1</center>
 +
<source lang="text">
 
**kern
 
**kern
 
1c
 
1c
Line 394: Line 530:
 
4e
 
4e
 
*-
 
*-
 +
</source>
 +
<td width=20></td>
 +
<td>
 +
<center>file 2</center>
 +
<source lang="text">
 
**kern
 
**kern
 
2cc
 
2cc
Line 400: Line 541:
 
*-
 
*-
 
</source>
 
</source>
 
+
</td>
Here are some possible command-line realizations for the above program:
+
<td width=40></td>
   
+
<td>
  humecho4 file.krn
+
<center>output</center>
  humecho4 file1.krn file2.krn file3.krn
+
<source lang="text">
  cat file.krn | humecho4
+
**kern
  humecho4
+
1c
 
+
2d
 +
4e
 +
*-
 +
**kern
 +
2cc
 +
4b
 +
2a
 +
*-
 +
</source>
 +
</td>
 +
</tr></table></ul>
 +
 
 +
Here are some possible command-line realizations for the above program:
 +
   
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 +
<source lang="bash">
 +
humecho4 file.krn
 +
humecho4 file1.krn file2.krn file3.krn
 +
cat file.krn | humecho4
 +
humecho4
 +
</source>
 +
</td></tr></table></ul>
 +
 
 
The last command will cause the shell to wait while you type in the input to humecho4, followed by control-D to indicate the end of input data.
 
The last command will cause the shell to wait while you type in the input to humecho4, followed by control-D to indicate the end of input data.
  
Line 416: Line 580:
 
<br><br><br>
 
<br><br><br>
  
== C string comparison functions ==
+
== HumdrumStream class ==
  
Here are three of the string comparison functions available within in the C (or C++) language:
+
Input data from multiple files can be managed by the HumdrumStream class.  This class also manages multiple independent sequences of data, such as movements, in a data file which the HumdrumFile class will not process.
  
<dl>
+
<ul>
<dt> strcmp("string1", "string2")
+
::<table style="background:white;"><tr><td>
<dd> returns 0 if strings are equivalent<br>returns &ndash;1 if string1 is alphabetized before string2<br>returns +1 if string1 is alphabetized after string2.
+
<source lang="cpp">
<dt> strncmp("string1", "string2", n)
+
#include "humdrum.h"
<dd> compare only first <i>n</i> characters of the two strings.
+
using namespace std;
<dt> strchr("string", 'character')
+
int main(int argc, char** argv) {
<dd> returns a pointer to the first occurrence of the character within the string.  If the character is not found in the string, returns a NULL pointer.
+
  Options options(argc, argv);
</dl>
+
  options.process();
 +
  HumdrumStream streamer(options.getArgList());
 +
  HumdrumFile hfile;
 +
  while (streamer.read(hfile)) {
 +
      cout << hfile;
 +
  }
 +
  return 0;
 +
}
 +
</source>
 +
</td></tr></table></ul>
  
Other interesting string processing functions in the C language are <tt>strstr</tt> which is similar to <tt>strchr</tt> but search for a sub-string within the a string; and <tt>strrchr</tt> which is similar to <tt>strchr</tt> but searches for the character in the reverse direction in the string, which returns the last occurrence of the character in the string (or NULL) if the character is not in the string.  For more description about these functions, type "man strrchr" in a terminal for more information about the strrchr function (or any other standard C fuction).
 
  
<br><br><br>
+
== HumdrumFileSet class ==
  
== Third dimension of data access (Note-level access) ==
+
The HumdrumFileSet class functions in a similar manner to the HumdrumStream class.  It reads in multiple segments of Humdrum data from a single physical file, multiple files, or standard input.  The main difference compared to HumdrumStream is that HumdrumFileSet retains the contents of all input segments in memory.
  
Accessing individual notes in **kern data spines requires three dimensions of indexing: (1) the data line of the note, the data field on the line for the note, and then the note number within a chord for the note.  Previous program examples demonstrated how to access lines and line-fields. The following program (<tt>noteloc</tt>) goes one step further to access individual **kern notesThe program takes any sort of Humdrum file, and then outputs a list of all notes found in all kern spines:
+
Here is a basic processing example which demonstrates how to store all input arguments into HumdrumFileSet. In this case the HumdrumFileSet::read() function is used to extract a list of arguments from the options variableIf the options variable does not contain any filenames, standard input will be read:
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 +
using namespace std;
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   Options options(argc, argv);
 
   Options options(argc, argv);
 
   options.process();
 
   options.process();
   HumdrumFile hfile;
+
   HumdrumFileSet infiles;
   hfile.read(options.getArg(1));
+
   infiles.read(options);
  char buffer[1024] = {0};
+
   for (int i=0; i<infiles.getCount(); i++) {
   for (int i=0; i<hfile.getNumLines(); i++) {
+
       cout << infiles[i];
       if (!hfile[i].isData()) continue; // ignore non-data lines
 
      for (int j=0; j<hfile[i].getFieldCount(); j++) {
 
        if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
 
        if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
 
        int count = hfile[i].getTokenCount(j);
 
        for (int k=0; k<count; k++) {
 
            cout << "(" << i+1 <<"," << j+1 << "," << k+1 << ")\t"
 
                << hfile[i].getToken(buffer, j, k) << endl;
 
        }
 
      }
 
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
The line:
+
== C string comparison functions ==
 +
 
 +
Here are three of the string comparison functions available within in the C (or C++) language:
 +
 
 +
<dl>
 +
<dt> strcmp("string1", "string2")
 +
<dd> returns 0 if strings are equivalent<br>returns &ndash;1 if string1 is alphabetized before string2<br>returns +1 if string1 is alphabetized after string2.
 +
<dt> strncmp("string1", "string2", n)
 +
<dd> compare only first <i>n</i> characters of the two strings.
 +
<dt> strchr("string", 'character')
 +
<dd> returns a pointer to the first occurrence of the character within the string.  If the character is not found in the string, returns a NULL pointer.
 +
</dl>
  
<source lang="cpp">
+
Other interesting string processing functions in the C language are <tt>strstr</tt> which is similar to <tt>strchr</tt> but search for a sub-string within the a string; and <tt>strrchr</tt> which is similar to <tt>strchr</tt> but searches for the character in the reverse direction in the string, which returns the last occurrence of the character in the string (or NULL) if the character is not in the string.  For more description about these functions, type "man strrchr" in a terminal for more information about the strrchr function (or any other standard C fuction).
if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
 
</source>
 
  
is used to skip over all spines which do not have **kern data.  The function .getExInterp() returns a const char* string for the name of the exclusive interpretation.  The strcmp() function compares the returns exclusive interpretation name with the string "**kern", and if it does not match, the next data field on the line will be examined.  An equivalent way of identifying the exclusive interpretation can be done with the .isExInterp() function.  The following line of code is equivalent to the one above:
+
<br><br><br>
  
<source lang="cpp">
+
== Third dimension of data access (Note-level access) ==
if (hfile[i].isExInterp(j, "**kern")) continue;
 
</source>
 
  
If the input to the program is the following:
+
Accessing individual notes in **kern data spines requires three dimensions of indexing: (1) the data line of the note, the data field on the line for the note, and then the note number within a chord for the note. Previous program examples demonstrated how to access lines and line-fields.  The following program (<tt>noteloc</tt>) goes one step further to access individual **kern notes.  The program takes any sort of Humdrum file, and then outputs a list of all notes found in all kern spines:
 
 
<source lang="humdrum">
 
**kern **text **kern
 
4C ig- 4c
 
4D 4E -no- .
 
4F -red .
 
. . 4d 4e
 
4r . .
 
4G 4A 4B text .
 
*- *- *-
 
</source>
 
 
 
 
 
Then the output from the <i>noteloc</i> program will be:
 
 
 
 
 
  (2,1,1) 4C
 
  (2,3,1) 4c
 
  (3,1,1) 4D
 
  (3,1,2) 4E
 
  (4,1,1) 4F
 
  (5,3,1) 4d
 
  (5,3,2) 4e
 
  (6,1,1) 4r
 
  (7,1,1) 4G
 
  (7,1,2) 4A
 
  (7,1,3) 4B
 
 
 
Each of the three numbers before the note indicates the address within the file for the note, with the first number being the line on which the note occurs, the second number the field on the line which contains the note, and the last number is the note number within the (possible) chord for the note.
 
 
 
<br><br><br>
 
=== kerninfo.cpp (Count **kern notes in data) ===
 
 
 
Here is an example program which somewhat emulates the "census -k" command from the Humdrum Toolkit.  The program will count the number of note attacks, rests and tied notes in one or more Humdrum files.
 
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
using namespace std;
 
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   Options options(argc, argv);
 
   Options options(argc, argv);
 
   options.process();
 
   options.process();
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
   int restcount  = 0;
+
   hfile.read(options.getArg(1));
  int nullcount  = 0;
+
  char buffer[1024] = {0};
  int attackcount = 0;
+
  for (int i=0; i<hfile.getNumLines(); i++) {
  int tiedcount  = 0;
+
      if (!hfile[i].isData()) continue; // ignore non-data lines
  int chordcount  = 0;
+
      for (int j=0; j<hfile[i].getFieldCount(); j++) {
  for (int arg=1; arg <= options.getArgCount() || arg == 0; arg++) {
+
        if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
      if (options.getArgCount() == 0) {  hfile.read(cin); }
+
        if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
      else { hfile.read(options.getArg(arg)); }
+
        int count = hfile[i].getTokenCount(j);
      char buffer[1024] = {0};
+
        for (int k=0; k<count; k++) {
      for (int i=0; i<hfile.getNumLines(); i++) {
+
            cout << "(" << i+1 <<"," << j+1 << "," << k+1 << ")\t"
        if (!hfile[i].isData()) continue;
+
                << hfile[i].getToken(buffer, j, k) << endl;
        for (int j=0; j<hfile[i].getFieldCount(); j++) {
+
         }  
            if (!hfile[i].isExInterp(j, "**kern")) continue;
+
       }
            int count = hfile[i].getTokenCount(j);
+
   }
            if (count > 1) chordcount++;
+
   return 0;
            for (int k=0; k<count; k++) {
+
}
              hfile[i].getToken(buffer, j, k);
+
</source>
              if (strchr(buffer, 'r') != NULL)  { restcount++; }
+
</td></tr></table></ul>
              else if (strcmp(buffer, ".") == 0) { nullcount++; }
+
 
              else if (strchr(buffer, '_') != NULL) { /* ignore */ }
+
The line:
              else if (strchr(buffer, ']') != NULL) { tiedcount++; }
+
<ul>
              else { attackcount++; }
+
::<table style="background:white;"><tr><td>
            }
+
<source lang="cpp">
         }  
+
if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
       }
 
   }
 
   cout << "Note attacks: " << attackcount << endl; 
 
  cout << "Tied notes  : " << tiedcount  << endl;
 
  cout << "Chords      : " << chordcount  << endl; 
 
  cout << "Rests      : " << restcount  << endl;
 
  cout << "Null Tokens : " << nullcount  << endl; 
 
  return 0;
 
}
 
 
</source>
 
</source>
 +
</td></tr></table>
 +
</ul>
  
Trying out the kerninfo prorgram on this input data:
+
is used to skip over all spines which do not have **kern data.  The function .getExInterp() returns a const char* string for the name of the exclusive interpretation.  The strcmp() function compares the returns exclusive interpretation name with the string "**kern", and if it does not match, the next data field on the line will be examined.  An equivalent way of identifying the exclusive interpretation can be done with the .isExInterp() function.  The following line of code is equivalent to the one above:
  
<source lang="humdrum">
+
<ul>
**kern **text **kern
+
::<table style="background:white;"><tr><td>
 +
<source lang="cpp">
 +
if (hfile[i].isExInterp(j, "**kern")) continue;
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
Input and output to/from the program:
 +
 
 +
<ul>
 +
::<table style="background:white;"><tr valign=top><td>
 +
<source lang="text" tabwidth="10">
 +
**kern **text **kern
 
4C ig- 4c
 
4C ig- 4c
 
4D 4E -no- .
 
4D 4E -no- .
Line 563: Line 707:
 
*- *- *-
 
*- *- *-
 
</source>
 
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
(2,1,1) 4C
 +
(2,3,1) 4c
 +
(3,1,1) 4D
 +
(3,1,2) 4E
 +
(4,1,1) 4F
 +
(5,3,1) 4d
 +
(5,3,2) 4e
 +
(6,1,1) 4r
 +
(7,1,1) 4G
 +
(7,1,2) 4A
 +
(7,1,3) 4B
 +
</source>
 +
</td></tr></table>
 +
</ul>
  
Results in these statistics:
+
Each of the three numbers before the note indicates the address within the file for the note, with the first number being the line on which the note occurs, the second number the field on the line which contains the note, and the last number is the note number within the (possible) chord for the note.
 
 
  Note attacks: 10
 
  Tied notes  : 0
 
  Chords      : 3
 
  Rests      : 1
 
  Null Tokens : 5
 
 
 
Trying out the <tt>kerninfo</tt> program on a real piece of music:
 
 
 
  <b>kerninfo h://wtc/wtc1p04.krn</b>
 
  Note attacks: 675
 
  Tied notes  : 85
 
  Chords      : 14
 
  Rests      : 69
 
  Null Tokens : 967
 
 
 
  
 
<br><br><br>
 
<br><br><br>
 +
=== kerninfo.cpp (Count **kern notes in data) ===
  
== Convert class ==
+
Here is an example program which somewhat emulates the "census -k" command from the Humdrum Toolkit.  The program will count the number of note attacks, rests and tied notes in one or more Humdrum files.
  
In addition to the Options class, and important helper class in HumdrumExtras is the Convert class.  This class handles most conversions between data types.  The HumdrumFile class essentially stores a two-dimensional array of strings.  The **kern notes in a HumdrumFile variable are extracted as strings, but will need to be interpreted further depending on the information about the note which you need.  For example, to convert a **kern note into a MIDI note number, use the following Convert function:
+
<ul>
  Convert::kernToMidiNoteNumber("4d-")          &rarr;  61
+
::<table style="background:white;"><tr><td>
Likewise, the MIDI note 61 can be converted back into a **kern note:
+
<source lang="cpp">
  Convert::midiNoteNumberToKern(buffer, 61)    &rarr;  "c#"
+
#include "humdrum.h"
 
+
using namespace std;
All access to Convert class functions is done statically, so you can shorten the code by using the a typedef for Convert to a shorter name:
+
int main(int argc, char** argv) {
 
+
   Options options(argc, argv);
    typedef Convert C;
+
   options.process();
    C::kernToMidiNoteNumber("4d-");
+
   HumdrumFile hfile;
 
+
  int restcount  = 0;
<br><br><br>
+
  int nullcount  = 0;
 
+
  int attackcount = 0;
=== Convert **kern note name to MIDI ===
+
  int tiedcount  = 0;
 
+
  int chordcount  = 0;
The following program will convert the first note of every chord into a MIDI note number.  As an exercise, adjust the code so that it prints a MIDI note number for every note in the chords.
+
  for (int arg=1; arg <= options.getArgCount() || arg == 0; arg++) {
 
+
      if (options.getArgCount() == 0) {  hfile.read(cin); }
<source lang="cpp">
+
      else { hfile.read(options.getArg(arg)); }
#include "humdrum.h"
+
      char buffer[1024] = {0};
int main(int argc, char** argv) {
+
      for (int i=0; i<hfile.getNumLines(); i++) {
   Options options(argc, argv);
+
        if (!hfile[i].isData()) continue;
   options.process();
+
        for (int j=0; j<hfile[i].getFieldCount(); j++) {
   HumdrumFile hfile(options.getArg(1));
+
            if (!hfile[i].isExInterp(j, "**kern")) continue;
  for (int i=0; i<hfile.getNumLines(); i++) {
+
            int count = hfile[i].getTokenCount(j);
      if (!hfile[i].isData()) continue;
+
            if (count > 1) chordcount++;
      for (int j=0; j<hfile[i].getFieldCount(); j++) {
+
            for (int k=0; k<count; k++) {
      if (hfile[i].isExInterp(j, "**kern")) continue;
+
              hfile[i].getToken(buffer, j, k);
      if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
+
              if (strchr(buffer, 'r') != NULL)  { restcount++; }
      if (strchr(hfile[i][j], 'r') != NULL) continue; // ignore rests
+
              else if (strcmp(buffer, ".") == 0) { nullcount++; }
        cout << hfile[i][j] << "\t" << Convert::kernToMidiNoteNumber(hfile[i][j]) << endl;
+
              else if (strchr(buffer, '_') != NULL) { /* ignore */ }
 +
              else if (strchr(buffer, ']') != NULL) { tiedcount++; }
 +
              else { attackcount++; }
 +
            }
 +
        } 
 
       }
 
       }
 
   }
 
   }
 +
  cout << "Note attacks: " << attackcount << endl; 
 +
  cout << "Tied notes  : " << tiedcount  << endl;
 +
  cout << "Chords      : " << chordcount  << endl; 
 +
  cout << "Rests      : " << restcount  << endl;
 +
  cout << "Null Tokens : " << nullcount  << endl; 
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
Example input and output:
+
<ul>
 
+
::<table style="background:white;"><tr valign=baseline><td>
<source lang="humdrum">
+
Trying out the kerninfo prorgram on this input data:
 +
<source lang="text" tabwidth="10">
 
**kern **text **kern
 
**kern **text **kern
 
4C ig- 4c
 
4C ig- 4c
Line 633: Line 789:
 
*- *- *-
 
*- *- *-
 
</source>
 
</source>
 +
</td><td width=40></td><td>
 +
Results in these statistics:
 +
<pre>
 +
Note attacks: 10
 +
Tied notes  : 0
 +
Chords      : 3
 +
Rests      : 1
 +
Null Tokens : 5
 +
</pre>
 +
</td></tr></table></ul>
 +
 +
Trying out the <tt>kerninfo</tt> program on a real piece of music:
 +
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 +
<tt>kerninfo h://wtc/wtc1p04.krn</tt>
 +
<pre>
 +
Note attacks: 675
 +
Tied notes  : 85
 +
Chords      : 14
 +
Rests      : 69
 +
Null Tokens : 967
 +
</pre>
 +
</td></tr></table></ul>
 +
 +
<br><br><br>
 +
 +
== Convert class ==
 +
 +
In addition to the Options class, and important helper class in Humdrum Extras is the Convert class.  This class handles most conversions between data types.  The HumdrumFile class essentially stores a two-dimensional array of strings.  The **kern notes in a HumdrumFile variable are extracted as strings, but will need to be interpreted further depending on the information about the note which you need.  For example, to convert a **kern note into a MIDI note number, use the following Convert function:
 +
  Convert::kernToMidiNoteNumber("4d-")          &rarr;  61
 +
Likewise, the MIDI note 61 can be converted back into a **kern note:
 +
  Convert::midiNoteNumberToKern(buffer, 61)    &rarr;  "c#"
  
  4C        48
+
All access to Convert class functions is done statically, so you can shorten the code by using the a typedef for Convert to a shorter name:
  4c        60
 
  4D 4E    50
 
  4F        53
 
  4d 4e    62
 
  4G 4A 4B  55
 
  
 +
    typedef Convert C;
 +
    C::kernToMidiNoteNumber("4d-");
  
 
<br><br><br>
 
<br><br><br>
  
== Note Histogram (notehist.cpp) ==  
+
=== Convert **kern note names to MIDI ===
 +
 
 +
The following program will convert the first note of every chord into a MIDI note number. 
  
Here is an example of how to count the number of twelvetone pitch classes in a Humdrum file.  The following program will count tied notes.  As an exercise, have the program skip counting of any middle or end tied notes (middle tied notes have an underscore (_) in their content, and ending tied notes has a closing square bracket (])).
 
  
 +
<ul>
 +
::<table style="background:white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
Line 653: Line 842:
 
   Options options(argc, argv);
 
   Options options(argc, argv);
 
   options.process();
 
   options.process();
   HumdrumFile hfile;
+
   HumdrumFile hfile(options.getArg(1));
  hfile.read(options.getArg(1));
+
   for (int i=0; i<hfile.getNumLines(); i++) {
  double histogram[12] = {0};
+
       if (!hfile[i].isData()) continue;
  char buffer[1024] = {0};
 
  int midikey;
 
  int i;
 
   for (i=0; i<hfile.getNumLines(); i++) {
 
       if (!hfile[i].isData()) continue; // ignore non-data lines
 
 
       for (int j=0; j<hfile[i].getFieldCount(); j++) {
 
       for (int j=0; j<hfile[i].getFieldCount(); j++) {
        if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
+
      if (hfile[i].isExInterp(j, "**kern")) continue;
        if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
+
      if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
        int count = hfile[i].getTokenCount(j);
+
      if (strchr(hfile[i][j], 'r') != NULL) continue; // ignore rests
        for (int k=0; k<count; k++) {
+
        cout << hfile[i][j] << "\t" << Convert::kernToMidiNoteNumber(hfile[i][j]) << endl;
            hfile[i].getToken(buffer, j, k);
 
            if (strchr(buffer, 'r') != NULL) continue; // ignore rests
 
            midikey = Convert::kernToMidiNoteNumber(buffer);
 
            histogram[midikey % 12]++;
 
        }
 
 
       }
 
       }
  }
 
  for (i=0; i<12; i++) {
 
      std::cout << i << "\t" << histogram[i] << std::endl;
 
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
Example output when processing a real piece of music is given below.  The first output line means there are 600 C notes in Beethoven's 32nd sonata, mvmt. 1, 233 C{{music|sharp}}s/D{{music|flat}}s, etc.
+
Example input and output:
  
  notehist h://beethoven/sonatas/sonata32-1.krn
+
<ul>
  0 600
+
::<table style="background:white;"><tr valign=top><td>
  1 233
+
<source lang="text" tabwidth="10">
  2 279
+
**kern **text **kern
  3 476
+
4C ig- 4c
  4 146
+
4D 4E -no- .
  5 513
+
4F -red .
  6 144
+
. . 4d 4e
  7 636
+
4r . .
  8 459
+
4G 4A 4B text .
  9 121
+
*- *- *-
  10 259
+
</source>
  11 230
+
</td><td width=40></td><td>
 
+
<source lang="text">
To sort the pitch classes by how often they occur:
+
4C        48
 +
4c        60
 +
4D 4E    50
 +
4F        53
 +
4d 4e    62
 +
4G 4A 4B  55
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
Note that only the first kern note in the string will be extracted by Convert::kernToMidiNoteNumber().  As an exercise, adjust the code so that it prints a MIDI note number for every note in the chords.
  
  notehist h://beethoven/sonatas/sonata32-1.krn | sort -nrk2
+
<br><br><br>
  7 636
 
  0 600
 
  5 513
 
  3 476
 
  8 459
 
  2 279
 
  10 259
 
  1 233
 
  11 230
 
  4 146
 
  6 144
 
  9 121
 
  
In this case the most common pitch class is G (7), and the least common pitch class is A (9).  Note that the sorting did not have to be implemented in the C++ program, since the command-line program <tt>sort</tt> could be utilized. The options to sort are <tt>-n</tt> (sort numerically), <tt>-r</tt> (sort in reverse order so that the largest number comes first), and <tt>-k2</tt> (sort by the second column of data).
+
== Note Histogram (notehist.cpp) ==
  
Here is a modification of the program so that pitch names rather than pitch-class numbers are displayed:
+
Here is an example of how to count the number of twelvetone pitch classes in a Humdrum file.  The following program will count tied notes.  As an exercise, have the program skip counting of any middle or end tied notes (middle tied notes have an underscore (_) in their content, and ending tied notes has a closing square bracket (])).
  
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
Line 742: Line 918:
 
   }
 
   }
 
   for (i=0; i<12; i++) {
 
   for (i=0; i<12; i++) {
       std::cout << i << "\t"  
+
       std::cout << i << "\t" << histogram[i] << std::endl;
                << Convert::base12ToKern(buffer, histogram[i] + 4 * 12) << std::endl;
 
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
So now the note names will be printed instead of their numeric equivalent:
+
Example output when processing a real piece of music is given below.  The first output line means there are 600 C notes in Beethoven's 32nd sonata, mvmt. 1, 233 C{{music|sharp}}s/D{{music|flat}}s, etc.
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<tt>notehist h://beethoven/sonatas/sonata32-1.krn</tt>
 +
<source lang="text">
 +
0 600
 +
1 233
 +
2 279
 +
3 476
 +
4 146
 +
5 513
 +
6 144
 +
7 636
 +
8 459
 +
9 121
 +
10 259
 +
11 230
 +
</source>
 +
</td></tr></table></ul>
  
  notehist h://beethoven/sonatas/sonata32-1.krn | sort -nrk2
+
To sort the pitch classes by how often they occur:
  G 636
 
  C 600
 
  F 513
 
  E- 476
 
  G# 459
 
  D 279
 
  B- 259
 
  C# 233 
 
  B 230
 
  E 146
 
  F# 144
 
  A 121
 
  
If you want to preserve the accidental spellings, then you can use base-40 instead of base-12 (MIDI note numbers):
+
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<tt>notehist h://beethoven/sonatas/sonata32-1.krn | sort -nrk2</tt>
 +
<source lang="text">
 +
7 636
 +
0 600
 +
5 513
 +
3 476
 +
8 459
 +
2 279
 +
10 259
 +
1 233
 +
11 230
 +
4 146
 +
6 144
 +
9 121
 +
</source>
 +
</td></tr></table></ul>
  
<source lang="cpp">
+
In this case the most common pitch class is G (7), and the least common pitch class is A (9).  Note that the sorting did not have to be implemented in the C++ program, since the command-line program <tt>sort</tt> could be utilized.  The options to sort are <tt>-n</tt> (sort numerically), <tt>-r</tt> (sort in reverse order so that the largest number comes first), and <tt>-k2</tt> (sort by the second column of data).
 +
 
 +
Here is a modification of the program so that pitch names rather than pitch-class numbers are displayed:
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
Line 774: Line 980:
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
 
   hfile.read(options.getArg(1));
 
   hfile.read(options.getArg(1));
   double histogram[40] = {0};
+
   double histogram[12] = {0};
 
   char buffer[1024] = {0};
 
   char buffer[1024] = {0};
   int base40;
+
   int midikey;
 
   int i;
 
   int i;
 
   for (i=0; i<hfile.getNumLines(); i++) {
 
   for (i=0; i<hfile.getNumLines(); i++) {
Line 787: Line 993:
 
             hfile[i].getToken(buffer, j, k);
 
             hfile[i].getToken(buffer, j, k);
 
             if (strchr(buffer, 'r') != NULL) continue; // ignore rests
 
             if (strchr(buffer, 'r') != NULL) continue; // ignore rests
             base40 = Convert::kernToBase40(buffer);
+
             midikey = Convert::kernToMidiNoteNumber(buffer);
             histogram[base40 % 40]++;
+
             histogram[midikey % 12]++;
 
         }
 
         }
 
       }
 
       }
 
   }
 
   }
   for (i=0; i<40; i++) {
+
   for (i=0; i<12; i++) {
      if (histogram[i] == 0) { continue; }
+
       std::cout << i << "\t"  
       std::cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
+
                 << Convert::base12ToKern(buffer, histogram[i] + 4 * 12) << std::endl;
                 << histogram[i] << std::endl;
 
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
With the more verbose pitch information being:
+
So now the note names will be printed instead of their numeric equivalent:
  
notehist h://beethoven/sonatas/sonata32-1.krn
+
<ul>
  C- 32
+
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
  C 600
+
<tt>notehist h://beethoven/sonatas/sonata32-1.krn | sort -nrk2</tt>
  C# 1
+
<source lang="text">
  D- 232
+
G 636
  D 271
+
C 600
  D# 1
+
F 513
  E-- 8
+
E- 476
  E- 475
+
G# 459
  E 134
+
D 279
  E# 2
+
B- 259
  F- 12
+
C# 233 
  F 511
+
B 230
  F# 83
+
E 146
  G- 61
+
F# 144
  G 636
+
A 121
  G# 2
+
</source>
  A- 457
+
</td></tr></table></ul>
  A 121
 
  B- 259
 
  B 198
 
  
 +
If you want to preserve the accidental spellings, then you can use base-40 instead of base-12 (MIDI note numbers):
  
[http://www.cplusplus.com/reference Standard Template Library] classes such as vector can be used instead of the C-like histogram array used in the previous program.  Here is an example using the vector class:
+
<ul>
 
+
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
#include <vector>
 
using namespace std;
 
 
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   Options options(argc, argv);
 
   Options options(argc, argv);
Line 838: Line 1,039:
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
 
   hfile.read(options.getArg(1));
 
   hfile.read(options.getArg(1));
   vector<int> histogram(40);
+
   double histogram[40] = {0};
 
   char buffer[1024] = {0};
 
   char buffer[1024] = {0};
 
   int base40;
 
   int base40;
   unsigned int i;
+
   int i;
   for (i=0; i<(unsigned int)hfile.getNumLines(); i++) {
+
   for (i=0; i<hfile.getNumLines(); i++) {
 
       if (!hfile[i].isData()) continue; // ignore non-data lines
 
       if (!hfile[i].isData()) continue; // ignore non-data lines
 
       for (int j=0; j<hfile[i].getFieldCount(); j++) {
 
       for (int j=0; j<hfile[i].getFieldCount(); j++) {
Line 856: Line 1,057:
 
       }
 
       }
 
   }
 
   }
   for (i=0; i<histogram.size(); i++) {
+
   for (i=0; i<40; i++) {
 
       if (histogram[i] == 0) { continue; }
 
       if (histogram[i] == 0) { continue; }
       cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
+
       std::cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
                 << histogram[i] << endl;
+
                 << histogram[i] << std::endl;
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
In addition, the HumdrumExtras has an Array class which has a similar functionality as vector or C arrays:
+
With the more verbose pitch information being:
  
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<tt>notehist h://beethoven/sonatas/sonata32-1.krn</tt>
 +
<source lang="text">
 +
C- 32
 +
C 600
 +
C# 1
 +
D- 232
 +
D 271
 +
D# 1
 +
E-- 8
 +
E- 475
 +
E 134
 +
E# 2
 +
F- 12
 +
F 511
 +
F# 83
 +
G- 61
 +
G 636
 +
G# 2
 +
A- 457
 +
A 121
 +
B- 259
 +
B 198
 +
</source>
 +
</td></tr></table></ul>
 +
 +
[http://www.cplusplus.com/reference Standard Template Library] classes such as vector can be used instead of the C-like histogram array used in the previous program.  Here is an example using the vector class:
 +
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 +
#include <vector>
 
using namespace std;
 
using namespace std;
  
Line 876: Line 1,110:
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
 
   hfile.read(options.getArg(1));
 
   hfile.read(options.getArg(1));
   Array<int> histogram(40);
+
   vector<int> histogram(40);
  histogram.setAll(0);
 
 
   char buffer[1024] = {0};
 
   char buffer[1024] = {0};
 
   int base40;
 
   int base40;
   int i;
+
   unsigned int i;
   for (i=0; i<hfile.getNumLines(); i++) {
+
   for (i=0; i<(unsigned int)hfile.getNumLines(); i++) {
 
       if (!hfile[i].isData()) continue; // ignore non-data lines
 
       if (!hfile[i].isData()) continue; // ignore non-data lines
 
       for (int j=0; j<hfile[i].getFieldCount(); j++) {
 
       for (int j=0; j<hfile[i].getFieldCount(); j++) {
Line 895: Line 1,128:
 
       }
 
       }
 
   }
 
   }
   for (i=0; i<histogram.getSize(); i++) {
+
   for (i=0; i<histogram.size(); i++) {
 
       if (histogram[i] == 0) { continue; }
 
       if (histogram[i] == 0) { continue; }
 
       cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
 
       cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
Line 903: Line 1,136:
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
== Duration-weighted note histogram ==
+
In addition, Humdrum Extras has an Array class which has a similar functionality as the vector class or C arrays:
 
 
Often it is useful to know how long a certain pitch class sounds in a musical work rather than just how many note attacks for each pitch class.  Here is a program which measures the duration of each pitch class in the music:
 
  
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 +
using namespace std;
 +
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   Options options(argc, argv);
 
   Options options(argc, argv);
Line 915: Line 1,151:
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
 
   hfile.read(options.getArg(1));
 
   hfile.read(options.getArg(1));
   Array<double> histogram(12);
+
   Array<int> histogram(40);
 
   histogram.setAll(0);
 
   histogram.setAll(0);
  histogram.allowGrowth(0);
 
 
   char buffer[1024] = {0};
 
   char buffer[1024] = {0};
  double duration;
+
   int base40;
   int midikey;
 
 
   int i;
 
   int i;
 
   for (i=0; i<hfile.getNumLines(); i++) {
 
   for (i=0; i<hfile.getNumLines(); i++) {
Line 931: Line 1,165:
 
             hfile[i].getToken(buffer, j, k);
 
             hfile[i].getToken(buffer, j, k);
 
             if (strchr(buffer, 'r') != NULL) continue; // ignore rests
 
             if (strchr(buffer, 'r') != NULL) continue; // ignore rests
             midikey = Convert::kernToMidiNoteNumber(buffer);
+
             base40 = Convert::kernToBase40(buffer);
            duration = Convert::kernToDuration(buffer);
+
             histogram[base40 % 40]++;
             histogram[midikey % 12] += duration;
 
 
         }
 
         }
 
       }
 
       }
 
   }
 
   }
 
   for (i=0; i<histogram.getSize(); i++) {
 
   for (i=0; i<histogram.getSize(); i++) {
       std::cout << Convert::base12ToKern(buffer, i+4*12) << "\t"
+
       if (histogram[i] == 0) { continue; }
                 << histogram[i] << std::endl;
+
      cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
 +
                 << histogram[i] << endl;
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
 +
== Duration-weighted note histogram ==
  
Now the program will output the duration in quarter notes for each pitch class in the music:
+
Often it is useful to know how long a certain pitch class sounds in a musical work rather than just how many note attacks for each pitch class.  Here is a program which measures the duration of each pitch class in the music:
 
 
  C 319.556
 
  C# 98.6833
 
  D 137.644
 
  E- 208.4
 
  E 84.1444
 
  F 225.158
 
  F# 75.5278
 
  G 308.772
 
  G# 203.953
 
  A 61.2111
 
  B- 121.267
 
  B 125.767
 
 
 
<br><br><br>
 
 
 
== Line field enumeration by spine ==
 
 
 
The HumdrumRecord::getFieldCount() function returns the number of tokens on each Humdrum line, and each of these fields is accessed by an index number up to this count.  However, if you want to access data by spine number (or to be clear <i>primary</i> spine number), you have to use a different method.  The field number and spine number do not always match because spines can split into subspines which will increase the field count on a line.
 
 
 
To access data by spine, use the HumdrumRecord::getPrimaryTrack() function.  This function returns an integer value for the current spine, enumerated from one (sorry)Multiple fields can have the same primary track number, which is caused by a spine split in the data.
 
 
 
Here is a program which prints the primary track for each data token in a Humdrum file structure:
 
  
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
using namespace std;
 
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   Options options(argc, argv);
 
   Options options(argc, argv);
Line 979: Line 1,193:
 
   HumdrumFile hfile;
 
   HumdrumFile hfile;
 
   hfile.read(options.getArg(1));
 
   hfile.read(options.getArg(1));
   for (int i=0; i<hfile.getNumLines(); i++) {
+
  Array<double> histogram(12);
       if (!hfile[i].isData()) {
+
  histogram.setAll(0);
        cout << hfile[i] << endl;
+
  histogram.allowGrowth(0);
         continue;
+
  char buffer[1024] = {0};
      }
+
  double duration;
      cout << hfile[i].getPrimaryTrack(0);
+
  int midikey;
      for (int j=1; j<hfile[i].getFieldCount(); j++) {
+
  int i;
        cout << '\t' << hfile[i].getPrimaryTrack(j);
+
   for (i=0; i<hfile.getNumLines(); i++) {
 +
       if (!hfile[i].isData()) continue; // ignore non-data lines
 +
      for (int j=0; j<hfile[i].getFieldCount(); j++) {
 +
         if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
 +
        if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
 +
        int count = hfile[i].getTokenCount(j);
 +
        for (int k=0; k<count; k++) {
 +
            hfile[i].getToken(buffer, j, k);
 +
            if (strchr(buffer, 'r') != NULL) continue; // ignore rests
 +
            midikey = Convert::kernToMidiNoteNumber(buffer);
 +
            duration = Convert::kernToDuration(buffer);
 +
            histogram[midikey % 12] += duration;
 +
        }
 
       }
 
       }
       cout << endl;
+
  }
 +
  for (i=0; i<histogram.getSize(); i++) {
 +
       std::cout << Convert::base12ToKern(buffer, i+4*12) << "\t"
 +
                << histogram[i] << std::endl;
 
   }
 
   }
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
And here is example input and output:
 
  
<source lang="humdrum">
+
Now the program will output the duration in quarter notes for each pitch class in the music:
**a **b **c
+
 
. . .
+
<ul>
. . .
+
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
* *^ *^
+
<source lang="text">
. . . . .
+
C 319.556
. . . . .
+
C# 98.6833
* *v *v * *
+
D 137.644
* * *v *v
+
E- 208.4
. . .
+
E 84.1444
. . .
+
F 225.158
*- *- *-
+
F# 75.5278
 +
G 308.772
 +
G# 203.953
 +
A 61.2111
 +
B- 121.267
 +
B 125.767
 
</source>
 
</source>
 +
</td></tr></table></ul>
 +
 +
<br><br><br>
  
<source lang="humdrum">
+
== Rhythm parsing ==
**a **b **c
+
 
1 2 3
+
Humdrum files containing **kern data can be rhythmically parsed using the HumdrumFile::analyzeRhythm() function.  The **kern data is expected to contain rhythmic data which is consistent with the layout of the music on each line in the file.  This function will store rhythmic information for each line in the file, such as the current beat in the measure, the number of beats since the start of the file, and the duration of each line (composite rhythm).
1 2 3
 
* *^ *^
 
1 2 2 3 3
 
1 2 2 3 3
 
* *v *v * *
 
* * *v *v
 
1 2 3
 
1 2 3
 
*- *- *-
 
</source>
 
  
Notice that the input data contains three exclusive interpretations.  This will mean that there are three primary tracks (or spines) in the data.  In the output you can see the numbers for each track: 1, 2, and 3.  The HumdrumRecord::getMaxTrack() function can be called to find out what the maximum track number is (3 in this case).
+
=== Absolute beat ===
  
The primary track value is an integer.  If you also want to see the subtrack information, use .getTrack() instead of .getPrimaryTrack().  The .getTrack() function will have the primary track in the integer portion of a double float, and the enumerated subtrack in the fractional valueThe subtrack value uses the first three digits of the fraction, so you can extract the subtrack by removing the integer part of the number and multiplying by 1000Note that the subtrack enumeration starts at 0, while the primary tracks are enumerated from 1 (sorry again).
+
The HumdrumRecord::getAbsBeat() function returns the number of quarter notes (by default) from the beginning of the file to the current lineBelow is an example program which prints the "absolute beat" data for each line of a Humdrum fileNote that lines of any type (data, comments, interpretations) can be queried for their position in the score. In this example the argument "4" is given to .analyzeRhythm()This means to use the quarter note as the basis for calculating rhythmic values for the score.
  
 +
<ul>
 +
::<table style="background: white;" cellpadding=0 cellspacing=0><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
using namespace std;
+
 
 +
void printRhythm(HumdrumFile& infile) {
 +
  for (int i=0; i<infile.getNumLines(); i++) {
 +
      cout << infile[i].getAbsBeat() << '\t' << infile[i] << '\n';
 +
  }
 +
}
 +
 
 +
void processFile(HumdrumFile& infile) {
 +
  infile.analyzeRhythm("4");
 +
  printRhythm(infile);
 +
}
 +
 
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
 
   Options options(argc, argv);
 
   Options options(argc, argv);
 +
  HumdrumFileSet infiles;
 
   options.process();
 
   options.process();
   HumdrumFile hfile;
+
   infiles.read(options);
  hfile.read(options.getArg(1));
+
 
   for (int i=0; i<hfile.getNumLines(); i++) {
+
   for (int i=0; i<infiles.getCount(); i++) {
       if (!hfile[i].isData()) {
+
       processFile(infiles[i]);
         cout << hfile[i] << endl;
+
      if (i<infiles.getCount() - 1) {
        continue;
+
         cout << '\n';
 
       }
 
       }
      cout << hfile[i].getPrimaryTrack(0);
 
      for (int j=1; j<hfile[i].getFieldCount(); j++) {
 
        cout << '\t' << hfile[i].getTrack(j);
 
      }
 
      cout << endl;
 
 
   }
 
   }
 +
 
   return 0;
 
   return 0;
}
+
};
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
<source lang="humdrum">
+
Here is example input/output from the above program.  Each line in the output starts with the "absolute beat" value for the line, followed by the input line.  Note that the example input contains two measures of 4/4 meter, so the total duration of the score is 8 quarter notes.  This matches to the "absolute beat" position of the last line in the file.
**a **b **c
+
 
1 2 3
+
<ul>
1 2 3
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
* *^ *^
+
<source lang="text">
1 2 2.001 3 3.001
+
**kern
1 2 2.001 3 3.001
+
*M4/4
* *v *v * *
+
=1-
* * *v *v
+
4c
1 2 3
+
4.d
1 2 3
+
8e
*- *- *-
+
8fL
 +
8gJ
 +
=2
 +
12aL
 +
12b
 +
12ccJ
 +
20dd
 +
10ee
 +
20dd
 +
10ee
 +
20dd
 +
10ee
 +
20dd
 +
4cc
 +
==
 +
*-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
0 **kern
 +
0 *M4/4
 +
0 =1-
 +
0 4c
 +
1 4.d
 +
2.5 8e
 +
3 8fL
 +
3.5 8gJ
 +
4 =2
 +
4 12aL
 +
4.33333 12b
 +
4.66667 12ccJ
 +
5 20dd
 +
5.2 10ee
 +
5.6 20dd
 +
5.8 10ee
 +
6.2 20dd
 +
6.4 10ee
 +
6.8 20dd
 +
7 4cc
 +
8 ==
 +
8 *-
 
</source>
 
</source>
 +
</td></tr></table></ul>
 +
  
Even more detail about track information can be accessed with the .getSpineInfo() function.  This function returns the internally stored string which keeps track of how the spine/subspine was manipulated on previous lines of the data.
+
In the above example program, absolute beat positions for each line were printed as floating-point numbers.  Notice that for the triplet eighth notes starting in measure 2, round-off error occurs in 10<sup>-5</sup> decimal place.  If you require rhythmic information with no round-ff error, use the .getAbsBeatR() function rather than .getAbsBeat()The "R" stands for "Rational number".  The return value from "R" versions of rhythm functions return a data type called RationalNumber which can represent fractional values.  A triplet eighth is exactly 1/3 of a quarter note which is approximated by the floating-point value 0.33333.  The RationalNumber class handles such fractions by storing the numerator and denominator as separate integer values to avoid introducing round-off error.
 +
 
 +
Try replacing the printRhythm() function in the above example with this one which substitutes .getAbsBeat() with .getAbsBeatR():
  
 +
<ul>
 +
::<table style="background: white;" cellpadding=0 cellspacing=0><tr><td>
 
<source lang="cpp">
 
<source lang="cpp">
#include "humdrum.h"
+
void printRhythm(HumdrumFile& infile) {
using namespace std;
+
   for (int i=0; i<infile.getNumLines(); i++) {
int main(int argc, char** argv) {
+
       cout << infile[i].getAbsBeatR() << '\t' << infile[i] << '\n';
  Options options(argc, argv);
+
  }
  options.process();
+
}
  HumdrumFile hfile;
+
</source>
  hfile.read(options.getArg(1));
+
</td></tr></table></ul>
   for (int i=0; i<hfile.getNumLines(); i++) {
+
 
       if (!hfile[i].isData()) {
+
Now the output of the program describes the rhythms in terms of fractions rather than floating-point values:
        cout << hfile[i] << endl;
+
 
        continue;
+
<ul>
      }
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
      cout << hfile[i].getPrimaryTrack(0);
+
<source lang="text">
      for (int j=1; j<hfile[i].getFieldCount(); j++) {
+
**kern
        cout << '\t' << hfile[i].getSpineInfo(j);
+
*M4/4
      }
+
=1-
      cout << endl;
+
4c
 +
4.d
 +
8e
 +
8fL
 +
8gJ
 +
=2
 +
12aL
 +
12b
 +
12ccJ
 +
20dd
 +
10ee
 +
20dd
 +
10ee
 +
20dd
 +
10ee
 +
20dd
 +
4cc
 +
==
 +
*-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
0 **kern
 +
0 *M4/4
 +
0 =1-
 +
0 4c
 +
1 4.d
 +
5/2 8e
 +
3 8fL
 +
7/2 8gJ
 +
4 =2
 +
4 12aL
 +
13/3 12b
 +
14/3 12ccJ
 +
5 20dd
 +
26/5 10ee
 +
28/5 20dd
 +
29/5 10ee
 +
31/5 20dd
 +
32/5 10ee
 +
34/5 20dd
 +
7 4cc
 +
8 ==
 +
8 *-
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
This is now quite readable, so printing and integer plus a fractional remainder for each absolute beat position would be more convenient:
 +
 
 +
<ul>
 +
::<table style="background: white;" cellpadding=0 cellspacing=0><tr><td>
 +
<source lang="cpp">
 +
void printRhythm(HumdrumFile& infile) {
 +
  RationalNumber absbeat;
 +
  for (int i=0; i<infile.getNumLines(); i++) {
 +
      absbeat = infile[i].getAbsBeatR();
 +
      absbeat.printTwoPart(cout);
 +
      cout << '\t' << infile[i] << '\n';
 
   }
 
   }
  return 0;
 
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
<source lang="humdrum">
+
Now the output is more readable with numbers such as 31/5 replaced with 6+1/5:
**a **b **c
+
 
1 2 3
+
<ul>
1 2 3
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
* *^ *^
+
<source lang="text">
1 (2)a (2)b (3)a (3)b
+
**kern
1 (2)a (2)b (3)a (3)b
+
*M4/4
* *v *v * *
+
=1-
* * *v *v
+
4c
1 2 3
+
4.d
1 2 3
+
8e
*- *- *-
+
8fL
</source>
+
8gJ
 +
=2
 +
12aL
 +
12b
 +
12ccJ
 +
20dd
 +
10ee
 +
20dd
 +
10ee
 +
20dd
 +
10ee
 +
20dd
 +
4cc
 +
==
 +
*-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
0 **kern
 +
0 *M4/4
 +
0 =1-
 +
0 4c
 +
1 4.d
 +
2+1/2 8e
 +
3 8fL
 +
3+1/2 8gJ
 +
4 =2
 +
4 12aL
 +
4+1/3 12b
 +
4+2/3 12ccJ
 +
5 20dd
 +
5+1/5 10ee
 +
5+3/5 20dd
 +
5+4/5 10ee
 +
6+1/5 20dd
 +
6+2/5 10ee
 +
6+4/5 20dd
 +
7 4cc
 +
8 ==
 +
8 *-
 +
</source>
 +
</td></tr></table></ul>
  
In this case the spine info "<tt>(2)a</tt>" means that that token is in primary spine 2, but the spine was split once, and this subtrack is the left-hand subspine coming out of the split.  The .getPrimaryTrack() function returns the first number in the .getSpineInfo() string.
+
=== Line duration ===
  
Here is a more complex spine manipulation:
+
The line duration, or composite rhythm, of a Humdrum file can be extracted from the rhythmic analysis results by using the HumdrumRecord::getDuration() and getDurationR() functions.  The getDuration() function returns a double float, while getDurationR() returns a RationalNumber type.  The floating point version will contain round-off errors while the RationalNumber type will not, so you can use the different methods depending on your particular application requirements.  Floating point values can also be extracted from RationalNumber types with the RationalNumber::getFloat() function.
  
<source lang="humdrum">
+
Here is an example program which prints the duration of each line in a Humdrum file:
**a **b **c
 
. . .
 
* *^ *^
 
. . . . .
 
* * *^ * *
 
. . . . . .
 
* * *v *v * *
 
* *v *v * *
 
. . . .
 
* * *v *v
 
. . .
 
*- *- *-
 
</source>
 
  
Which has this spine information:
+
<ul>
 +
::<table style="background: white;" cellpadding=0 cellspacing=0><tr><td>
 +
<source lang="cpp">
 +
#include "humdrum.h"
  
<source lang="humdrum">
+
void printRhythm(HumdrumFile& infile) {
**a **b **c
+
  RationalNumber linedur;
1 2 3
+
  for (int i=0; i<infile.getNumLines(); i++) {
* *^ *^
+
      linedur = infile[i].getDurationR();
1 (2)a (2)b (3)a (3)b
+
      linedur.printTwoPart(cout);
* * *^ * *
+
      cout << '\t' << infile[i] << '\n';
1 (2)a ((2)b)a ((2)b)b (3)a (3)b
+
  }
* * *v *v * *
+
}
* *v *v * *
 
1 2 (3)a (3)b
 
* * *v *v
 
1 2 3
 
*- *- *-
 
</source>
 
  
<br><br><br>
+
void processFile(HumdrumFile& infile) {
 +
  infile.analyzeRhythm("4");
 +
  printRhythm(infile);
 +
}
  
=== Spine manipulator examples ===
+
int main(int argc, char** argv) {
 +
  Options options(argc, argv);
 +
  HumdrumFileSet infiles;
 +
  options.process();
 +
  infiles.read(options);
  
Here is an example of spine splits (<tt>*v</tt>) and joins (<tt>*v</tt>):
+
  for (int i=0; i<infiles.getCount(); i++) {
 +
      processFile(infiles[i]);
 +
      if (i<infiles.getCount() - 1) {
 +
        cout << '\n';
 +
      }
 +
  }
  
<source lang="humdrum">
+
  return 0;
**a **b **c
+
}
. . .
 
* *^ *
 
. . . .
 
* * *^ *
 
. . . . .
 
* *v *v *v *
 
. . .
 
*- *- *-
 
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
<source lang="humdrum">
+
This program outputs the duration of each line in quarter-note units:
**a **b **c
+
 
1 2 3
+
<ul>
* *^ *
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
1 (2)a (2)b 3
+
<source lang="text">
* * *^ *
+
**kern **kern
1 (2)a ((2)b)a ((2)b)b 3
+
*M3/4 *M3/4
* *v *v *v *
+
=- =-
1 2 3
+
2c 2cc
*- *- *-
+
8d 12dd
 +
. 12ee
 +
8e .
 +
. 12ff
 +
=2 =2
 +
12f 8gg
 +
12g .
 +
. 8aa
 +
12a .
 +
2b 2bb
 +
== ==
 +
*- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
0 **kern **kern
 +
0 *M3/4 *M3/4
 +
0 =- =-
 +
2 2c 2cc
 +
1/3 8d 12dd
 +
1/6 . 12ee
 +
1/6 8e .
 +
1/3 . 12ff
 +
0 =2 =2
 +
1/3 12f 8gg
 +
1/6 12g .
 +
1/6 . 8aa
 +
1/3 12a .
 +
2 2b 2bb
 +
0 == ==
 +
0 *- *-
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
Here is an example of spine additions (<tt>*+</tt>) and terminations (<tt>*-</tt>):
+
Notice that non-data lines are assigned a duration of 0.  Also note that the line ". 12ee" has a line duration of 1/6th of a quarter note.  This is smaller than the rhythm 12 which is 1/3 of a quarter note due to the 8th note in the first column having its duration split across two lines.
 +
 
 +
If you want the durations to represent **recip Humdrum data (**kern rhythm values), then either divide the RationalValue duration by 4, or use whole notes when analyzing the rhythmic structure of the file with .analyzeRhythm("1") instead of using "4" as its argument.
 +
 
 +
Here is an example program which calculates the composite rhythm of the file and prepends a **recip spine to the original data:
  
<source lang="humdrum">
+
<ul>
**a **b
+
::<table style="background: white;" cellpadding=0 cellspacing=0><tr><td>
. .
+
<source lang="cpp">
*
+
#include "humdrum.h"
*+ **c
 
. . .
 
* *- *
 
. .
 
*- *-
 
</source>
 
  
<source lang="humdrum">
+
void processFile(HumdrumFile& infile) {
**a **b
+
  infile.analyzeRhythm("4");
1 2
+
  RationalNumber linedur;
* *+ **c
+
  for (int i=0; i<infile.getNumLines(); i++) {
1 2 3
+
      if (infile[i].isData()) {
* *- *
+
        linedur = infile[i].getDurationR();
1 3
+
        cout << linedur << '\t';
*- *-
+
        linedur /= 4;
</source>
+
        linedur.printRecip(cout);
 +
        cout << '\t' << infile[i] << '\n';
 +
      } else if (infile[i].isMeasure()) {
 +
        cout << infile[i][0] << '\t' <<infile[i][0];
 +
        cout << '\t' << infile[i] << '\n';
 +
      } else if (strncmp(infile[i][0], "**", 2) == 0) {
 +
        cout << "**dur\t**recip\t" << infile[i] << '\n';
 +
      } else if (strcmp(infile[i][0], "*-") == 0) {
 +
        cout << "*-\t*-\t" << infile[i] << '\n';
 +
      } else if (infile[i].isInterpretation()) {
 +
        cout << "*\t*" << '\t' << infile[i] << '\n';
 +
      } else if (infile[i].isLocalComment()) {
 +
        cout << "!\t!" << '\t' << infile[i] << '\n';
 +
      } else {
 +
        cout << infile[i] << '\n';
 +
      }
 +
  }
 +
}
  
Here is an example of spine exchanges (<tt>*x</tt>):
+
int main(int argc, char** argv) {
 +
  Options options(argc, argv);
 +
  HumdrumFileSet infiles;
 +
  options.process();
 +
  infiles.read(options);
  
<source lang="humdrum">
+
  for (int i=0; i<infiles.getCount(); i++) {
**a **b
+
      processFile(infiles[i]);
. .
+
      if (i<infiles.getCount() - 1) {
. .
+
        cout << '\n';
*x *x
+
      }
. .
+
  }
. .
+
 
*- *-
+
  return 0;
 +
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
 +
 +
Here is the input and output for the composite rhythm program listed above. The HumdrumRecord::getDurationR() information was printed in the first column.  The second column gives the Humdrum **recip representation of the duration as a **kern rhythmic value.  The **recip value is the inverse of the duration, multiplied by 4 in this case to represent inverse divisions of whole notes rather than quarter notes.
  
<source lang="humdrum">
+
<ul>
**a **b
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
1 2
+
<source lang="text">
1 2
+
**kern **kern
*x *x
+
*M3/4 *M3/4
2 1
+
=- =-
2 1
+
2c 2cc
 +
8d 12dd
 +
. 12ee
 +
8e .
 +
. 12ff
 +
=2 =2
 +
12f 8gg
 +
12g .
 +
. 8aa
 +
12a .
 +
2b 2bb
 +
== ==
 
*- *-
 
*- *-
 
</source>
 
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**dur **recip **kern **kern
 +
* * *M3/4 *M3/4
 +
=- =- =- =-
 +
2 2 2c 2cc
 +
1/3 12 8d 12dd
 +
1/6 24 . 12ee
 +
1/6 24 8e .
 +
1/3 12 . 12ff
 +
=2 =2 =2 =2
 +
1/3 12 12f 8gg
 +
1/6 24 12g .
 +
1/6 24 . 8aa
 +
1/3 12 12a .
 +
2 2 2b 2bb
 +
== == == ==
 +
*- *- *- *-
 +
</source>
 +
</td></tr></table></ul>
  
And finally a complex example using all of the spine manipulators:
+
== Line field enumeration by spine ==
 +
 
 +
The HumdrumRecord::getFieldCount() function returns the number of tokens on each Humdrum line, and each of these fields is accessed by an index number up to this count.  However, if you want to access data by spine number (or to be clear <i>primary</i> spine number), you have to use a different method.  The field number and spine number do not always match because spines can split into subspines which will increase the field count on a line.
  
<source lang="humdrum">
+
To access data by spine, use the HumdrumRecord::getPrimaryTrack() function. This function returns an integer value for the current spine, enumerated from one (sorry). Multiple fields can have the same primary track number, which is caused by a spine split in the data.
**a **b
 
. .
 
* *^
 
. . .
 
*+ **c *
 
. . . .
 
* * *x *x
 
. . . .
 
* * *^ *
 
. . . . .
 
* *+ **d * *
 
. . . . . .
 
* * * * *x *x
 
. . . . . .
 
* *- * * * *
 
. . . . .
 
*v *v * * *
 
. . . .
 
* *v *v *
 
. . .
 
* *v *v
 
. .
 
*- *-
 
</source>
 
  
 +
Here is a program which prints the primary track for each data token in a Humdrum file structure:
  
<source lang="humdrum">
+
<ul>
**a    **b
+
::<table style="background: white;" cellpadding=0 cellspacing=0><tr><td>
1      2
+
<source lang="cpp">
*       *^
+
#include "humdrum.h"
1      (2)a   (2)b
+
using namespace std;
*+      **c    *
+
int main(int argc, char** argv) {
1      3      (2)a   (2)b
+
   Options options(argc, argv);
*      *      *x      *x
+
   options.process();
1      3      (2)b    (2)a
+
  HumdrumFile hfile;
*      *      *^      *
+
  hfile.read(options.getArg(1));
1       3      ((2)b)a ((2)b)b (2)a
+
  for (int i=0; i<hfile.getNumLines(); i++) {
*      *+      **d    *      *
+
       if (!hfile[i].isData()) {
1      3      4       ((2)b)a ((2)b)b (2)a
+
        cout << hfile[i] << endl;
*      *      *      *      *x      *x
+
        continue;
1      3      4      ((2)b)a (2)a    ((2)b)b
+
       }
*      *-      *      *      *       *
+
       cout << hfile[i].getPrimaryTrack(0);
1      4       ((2)b)a (2)a    ((2)b)b
+
       for (int j=1; j<hfile[i].getFieldCount(); j++) {
*v      *v      *      *      *
+
        cout << '\t' << hfile[i].getPrimaryTrack(j);
1      4       ((2)b)a (2)a    ((2)b)b
+
       }
*       *v      *v      *
+
       cout << endl;
1       4      ((2)b)a (2)a   ((2)b)b
+
   }
*      *v      *v
+
  return 0;
1      4      2
+
}
*-      *-
 
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
 +
And here is example input and output:
  
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
**a **b **c
 +
. . .
 +
. . .
 +
* *^ *^
 +
. . . . .
 +
. . . . .
 +
* *v *v * *
 +
* * *v *v
 +
. . .
 +
. . .
 +
*- *- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**a **b **c
 +
1 2 3
 +
1 2 3
 +
* *^ *^
 +
1 2 2 3 3
 +
1 2 2 3 3
 +
* *v *v * *
 +
* * *v *v
 +
1 2 3
 +
1 2 3
 +
*- *- *-
 +
</source>
 +
</td></tr></table></ul>
 +
 +
Notice that the input data contains three exclusive interpretations.  This will mean that there are three primary tracks (or spines) in the data.  In the output you can see the numbers for each track: 1, 2, and 3.  The HumdrumRecord::getMaxTrack() function can be called to find out what the maximum track number is (3 in this case).
 +
 +
The primary track value is an integer.  If you also want to see the subtrack information, use .getTrack() instead of .getPrimaryTrack().  The .getTrack() function will have the primary track in the integer portion of a double float, and the enumerated subtrack in the fractional value.  The subtrack value uses the first three digits of the fraction, so you can extract the subtrack by removing the integer part of the number and multiplying by 1000.  Note that the subtrack enumeration starts at 0, while the primary tracks are enumerated from 1 (sorry again).
  
<br><br><br>
+
<ul>
 
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr><td>
=== myextract.cpp ===
 
 
 
Now that you know how to extract information about spines and subspines, you can write your own version of the Humdrum Toolkit's extract program.  It will be even more powerful than the extract program, since the extract program cannot deal with subspines without special processing.  Here is the <tt>myextract</tt> code:
 
 
 
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "humdrum.h"
 
#include "humdrum.h"
 
using namespace std;
 
using namespace std;
 
+
int main(int argc, char** argv) {
void extract(HumdrumFile& hfile, int primarytrack) {
+
   Options options(argc, argv);
   int i, j, fcount, pcount;
+
  options.process();
   for (i=0; i<hfile.getNumLines(); i++) {
+
  HumdrumFile hfile;
       if (!hfile[i].hasSpines()) {
+
  hfile.read(options.getArg(1));
 +
   for (int i=0; i<hfile.getNumLines(); i++) {
 +
       if (!hfile[i].isData()) {
 
         cout << hfile[i] << endl;
 
         cout << hfile[i] << endl;
 
         continue;
 
         continue;
 
       }
 
       }
       fcount = hfile[i].getFieldCount();
+
       cout << hfile[i].getPrimaryTrack(0);
      pcount = 0;
+
       for (int j=1; j<hfile[i].getFieldCount(); j++) {
       for (j=0; j<fcount; j++) {
+
        cout << '\t' << hfile[i].getTrack(j);
        if (primarytrack == hfile[i].getPrimaryTrack(j)) {
 
            if (pcount++ > 0) cout << '\t';
 
            cout << hfile[i][j];
 
        }
 
 
       }
 
       }
       if (pcount > 0) cout << endl;
+
       cout << endl;
 
   }
 
   }
 +
  return 0;
 
}
 
}
 +
</source>
 +
</td></tr></table></ul>
  
int main (int argc, char** argv) {
+
<ul>
  Options opts;
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
  opts.define("t|track=i:1", "extract specified track");
+
<source lang="text">
  opts.process(argc, argv);
+
**a **b **c
  int primarytrack = opts.getInteger("track");
+
. . .
  int numinputs = opts.getArgCount();
+
. . .
  HumdrumFile hfile;
+
* *^ *^
  for (int i=1; i<=numinputs || i==0; i++) {
+
. . . . .
      if (numinputs < 1) { hfile.read(std::cin); }
+
. . . . .
      else { hfile.read(opts.getArg(i)); }
+
* *v *v * *
      extract(hfile, primarytrack);
+
* * *v *v
  }
+
. . .
  return 0;
+
. . .
}
+
*- *- *-
 
</source>
 
</source>
 
+
</td><td width=40></td><td>
Here is input and output to the command:
+
<source lang="text">
    myextract -t 2
 
 
 
<source lang="humdrum">
 
 
**a **b **c
 
**a **b **c
a b c
+
1 2 3
* *^ *
+
1 2 3
a b1 b2 c
+
* *^ *^
* *v *v *
+
1 2 2.001 3 3.001
a b c
+
1 2 2.001 3 3.001
 +
* *v *v * *
 +
* * *v *v
 +
1 2 3
 +
1 2 3
 
*- *- *-
 
*- *- *-
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
<source lang="humdrum">
+
Even more detail about track information can be accessed with the .getSpineInfo() function.  This function returns the internally stored string which keeps track of how the spine/subspine was manipulated on previous lines of the data.
**b
 
b
 
*^
 
b1 b2
 
*v *v
 
b
 
*-
 
</source>
 
  
Notice that all of the "B" information which was in spine 2 was extracted from the input data.
+
<ul>
 
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
== Regular Expressions in C ==
+
<source lang="cpp">
 
+
#include "humdrum.h"
Regular expressions are an important concept to understand when working with Humdrum data, since the data format was designed to take advantage of them.
+
using namespace std;
 
 
=== GNU POSIX regular expressions ===
 
 
 
Different Operating systems have different C implementations of regular expressions.  Here is an example of how to use them on most linux operating systems using the GNU POSIX regular expressions:
 
 
 
<source lang="c">
 
#include <regex.h>
 
#include <stdlib.h>
 
#include <stdio.h>
 
using namespace std;
 
 
int main(int argc, char** argv) {
 
int main(int argc, char** argv) {
   if (argc < 3) exit(1);
+
   Options options(argc, argv);
   const char *searchstring = argv[1];
+
   options.process();
   const char *datastring = argv[2];
+
   HumdrumFile hfile;
   regex_t re;
+
   hfile.read(options.getArg(1));
   int flags = 0 | REG_EXTENDED | REG_ICASE;
+
   for (int i=0; i<hfile.getNumLines(); i++) {
  int status = regcomp(&re, searchstring, flags);
+
      if (!hfile[i].isData()) {
  if (status !=0) {
+
        cout << hfile[i] << endl;
      char errstring[999];
+
        continue;
       regerror(status, &re, errstring, 999);
+
       }
       printf("%s\n", errstring);
+
      cout << hfile[i].getPrimaryTrack(0);
       exit(1);
+
       for (int j=1; j<hfile[i].getFieldCount(); j++) {
 +
        cout << '\t' << hfile[i].getSpineInfo(j);
 +
       }
 +
      cout << endl;
 
   }
 
   }
  status = regexec(&re, datastring, 0, NULL, 0);
 
  if (status == 0) printf("Match Found\n");
 
  else printf("Match Not Found\n");
 
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 +
</td></tr></table></ul>
  
Example behavior of the program:
+
<ul>
 
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
    search cat "cat in the hat"
+
<source lang="text">
    Match Found
+
**a **b **c
 
+
. . .
    search dog "cat in the hat"
+
. . .
    Match Not Found
+
* *^ *^
 
+
. . . . .
For the simple examples above the strstr() C library function could have been used (which would probably also run faster).  But using regular expressions allows for more powerful generalized searching, such as looking for upper and lower case matches:
+
. . . . .
 
+
* *v *v * *
    search cat "Cat in the hat"
+
* * *v *v
    Match Found
+
. . .
 +
. . .
 +
*- *- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**a **b **c
 +
1 2 3
 +
1 2 3
 +
* *^ *^
 +
1 (2)a (2)b (3)a (3)b
 +
1 (2)a (2)b (3)a (3)b
 +
* *v *v * *
 +
* * *v *v
 +
1 2 3
 +
1 2 3
 +
*- *- *-
 +
</source>
 +
</td></tr></table></ul>
  
In this case "cat" was matched to "Cat" since the REG_ICASE flag was used to ignore difference between upper and lower letter cases.  The REG_EXTENDED flag is for using extended regular expressions (regular expressions 2.0). The regexec() function returns 0 if a match was found, otherwise returns a non-zero value to indicate no match was found.
+
In this case the spine info "<tt>(2)a</tt>" means that that token is in primary spine 2, but the spine was split once, and this subtrack is the left-hand subspine coming out of the split.  The .getPrimaryTrack() function returns the first number in the .getSpineInfo() string.
  
 +
Here is a more complex spine manipulation:
  
=== mysed.c (Search and replace) ===
+
<ul>
 
+
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
The following program demonstrates how to do search and replace on strings with GNU POSIX regular expresions.
+
<source lang="text">
 
+
**a **b **c
<source lang="c">
+
. . .
#include <regex.h>
+
* *^ *^
 +
. . . . .
 +
* * *^ * *
 +
. . . . . .
 +
* * *v *v * *
 +
* *v *v * *
 +
. . . .
 +
* * *v *v
 +
. . .
 +
*- *- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**a **b **c
 +
1 2 3
 +
* *^ *^
 +
1 (2)a (2)b (3)a (3)b
 +
* * *^ * *
 +
1 (2)a ((2)b)a ((2)b)b (3)a (3)b
 +
* * *v *v * *
 +
* *v *v * *
 +
1 2 (3)a (3)b
 +
* * *v *v
 +
1 2 3
 +
*- *- *-
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
<br><br><br>
 +
 
 +
=== Spine manipulator examples ===
 +
 
 +
Here is an example of spine splits (<tt>*v</tt>) and joins (<tt>*v</tt>):
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
**a **b **c
 +
. . .
 +
* *^ *
 +
. . . .
 +
* * *^ *
 +
. . . . .
 +
* *v *v *v *
 +
. . .
 +
*- *- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**a **b **c
 +
1 2 3
 +
* *^ *
 +
1 (2)a (2)b 3
 +
* * *^ *
 +
1 (2)a ((2)b)a ((2)b)b 3
 +
* *v *v *v *
 +
1 2 3
 +
*- *- *-
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
Here is an example of spine additions (<tt>*+</tt>) and terminations (<tt>*-</tt>):
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
**a **b
 +
. .
 +
*
 +
*+ **c
 +
. . .
 +
* *- *
 +
. .
 +
*- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**a **b
 +
1 2
 +
* *+ **c
 +
1 2 3
 +
* *- *
 +
1 3
 +
*- *-
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
Here is an example of spine exchanges (<tt>*x</tt>):
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
**a **b
 +
. .
 +
. .
 +
*x *x
 +
. .
 +
. .
 +
*- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**a **b
 +
1 2
 +
1 2
 +
*x *x
 +
2 1
 +
2 1
 +
*- *-
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
And finally a complex example using all of the spine manipulators:
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
**a **b
 +
. .
 +
* *^
 +
. . .
 +
*+ **c *
 +
. . . .
 +
* * *x *x
 +
. . . .
 +
* * *^ *
 +
. . . . .
 +
* *+ **d * *
 +
. . . . . .
 +
* * * * *x *x
 +
. . . . . .
 +
* *- * * * *
 +
. . . . .
 +
*v *v * * *
 +
. . . .
 +
* *v *v *
 +
. . .
 +
* *v *v
 +
. .
 +
*- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**a    **b
 +
1      2
 +
*      *^
 +
1      (2)a    (2)b
 +
*+      **c    *
 +
1      3      (2)a    (2)b
 +
*      *      *x      *x
 +
1      3      (2)b    (2)a
 +
*      *      *^      *
 +
1      3      ((2)b)a ((2)b)b (2)a
 +
*      *+      **d    *      *
 +
1      3      4      ((2)b)a ((2)b)b (2)a
 +
*      *      *      *      *x      *x
 +
1      3      4      ((2)b)a (2)a    ((2)b)b
 +
*      *-      *      *      *      *
 +
1      4      ((2)b)a (2)a    ((2)b)b
 +
*v      *v      *      *      *
 +
1      4      ((2)b)a (2)a    ((2)b)b
 +
*      *v      *v      *
 +
1      4      ((2)b)a (2)a    ((2)b)b
 +
*      *v      *v
 +
1      4      2
 +
*-      *-
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
<br><br><br>
 +
 
 +
=== myextract.cpp ===
 +
 
 +
Now that you know how to extract information about spines and subspines, you can write your own version of the Humdrum Toolkit's extract program.  It will be even more powerful than the extract program, since the extract program cannot deal with subspines without special processing.  Here is the <tt>myextract</tt> code:
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="cpp">
 +
#include "humdrum.h"
 +
using namespace std;
 +
 
 +
void extract(HumdrumFile& hfile, int primarytrack) {
 +
  int i, j, fcount, pcount;
 +
  for (i=0; i<hfile.getNumLines(); i++) {
 +
      if (!hfile[i].hasSpines()) {
 +
        cout << hfile[i] << endl;
 +
        continue;
 +
      }
 +
      fcount = hfile[i].getFieldCount();
 +
      pcount = 0;
 +
      for (j=0; j<fcount; j++) {
 +
        if (primarytrack == hfile[i].getPrimaryTrack(j)) {
 +
            if (pcount++ > 0) cout << '\t';
 +
            cout << hfile[i][j];
 +
        }
 +
      }
 +
      if (pcount > 0) cout << endl;
 +
  }
 +
}
 +
 
 +
int main (int argc, char** argv) {
 +
  Options opts;
 +
  opts.define("t|track=i:1", "extract specified track");
 +
  opts.process(argc, argv);
 +
  int primarytrack = opts.getInteger("track");
 +
  int numinputs = opts.getArgCount();
 +
  HumdrumFile hfile;
 +
  for (int i=1; i<=numinputs || i==0; i++) {
 +
      if (numinputs < 1) { hfile.read(std::cin); }
 +
      else { hfile.read(opts.getArg(i)); }
 +
      extract(hfile, primarytrack);
 +
  }
 +
  return 0;
 +
}
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0>
 +
<tr><td>input</td><td colspan=2><center><tt>myextract -t 2</tt></center></td></tr>
 +
<tr valign=top><td>
 +
<source lang="text">
 +
**a **b **c
 +
a b c
 +
* *^ *
 +
a b1 b2 c
 +
* *v *v *
 +
a b c
 +
*- *- *-
 +
</source>
 +
</td><td width=145></td><td>
 +
<source lang="text">
 +
**b
 +
b
 +
*^
 +
b1 b2
 +
*v *v
 +
b
 +
*-
 +
</source>
 +
</td></tr></table>
 +
</ul>
 +
 
 +
Notice that all of the "B" information which was in spine 2 was extracted from the input data.
 +
 
 +
== Regular Expressions ==
 +
 
 +
Regular expressions are an important concept to understand when working with Humdrum data, since the data format was designed to take advantage of them.
 +
 
 +
=== GNU POSIX regular expressions ===
 +
 
 +
Different Operating systems have different C implementations of regular expressions. Here is an example of how to use them on most linux operating systems using the GNU POSIX regular expressions:
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="c">
 +
#include <regex.h>
 
#include <stdlib.h>
 
#include <stdlib.h>
#include <stdio.h>
+
#include <stdio.h>
#include <string.h>
+
using namespace std;
int main(int argc, char** argv) {
+
int main(int argc, char** argv) {
   if (argc < 4) exit(1);
+
  if (argc < 3) exit(1);
   char buffer[1024] = {0};
+
  const char *searchstring = argv[1];
   const char *searchstring = argv[1];
+
  const char *datastring = argv[2];
   const char *replacestring = argv[2];
+
  regex_t re;
   const char *datastring = argv[3];
+
  int flags = 0 | REG_EXTENDED | REG_ICASE;
   regex_t re;
+
  int status = regcomp(&re, searchstring, flags);
   regmatch_t match;
+
  if (status !=0) {
   int compflags = 0 | REG_EXTENDED | REG_ICASE;
+
      char errstring[999];
   int status = regcomp(&re, searchstring, compflags);
+
      regerror(status, &re, errstring, 999);
   if (status !=0) {
+
      printf("%s\n", errstring);
       regerror(status, &re, buffer, 1024);
+
      exit(1);
       printf("%s\n", buffer);
+
  }
       exit(1);
+
  status = regexec(&re, datastring, 0, NULL, 0);
 +
  if (status == 0) printf("Match Found\n");
 +
  else printf("Match Not Found\n");
 +
  return 0;
 +
}
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
Example behavior of the program:
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
search cat "cat in the hat"
 +
Match Found
 +
 
 +
search dog "cat in the hat"
 +
Match Not Found
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
For the simple examples above the strstr() C library function could have been used (which would probably also run faster).  But using regular expressions allows for more powerful generalized searching, such as looking for upper and lower case matches:
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
search cat "Cat in the hat"
 +
Match Found
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
In this case "cat" was matched to "Cat" since the REG_ICASE flag was used to ignore difference between upper and lower letter cases.  The REG_EXTENDED flag is for using extended regular expressions (regular expressions 2.0).  The regexec() function returns 0 if a match was found, otherwise returns a non-zero value to indicate no match was found.
 +
 
 +
 
 +
==== mysed.c (Search and replace) ====
 +
 
 +
The following program demonstrates how to do search and replace on strings with GNU POSIX regular expressions.
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="c">
 +
#include <regex.h>
 +
#include <stdlib.h>
 +
#include <stdio.h>
 +
#include <string.h>
 +
int main(int argc, char** argv) {
 +
   if (argc < 4) exit(1);
 +
   char buffer[1024] = {0};
 +
   const char *searchstring = argv[1];
 +
   const char *replacestring = argv[2];
 +
   const char *datastring = argv[3];
 +
   regex_t re;
 +
   regmatch_t match;
 +
   int compflags = 0 | REG_EXTENDED | REG_ICASE;
 +
   int status = regcomp(&re, searchstring, compflags);
 +
   if (status !=0) {
 +
       regerror(status, &re, buffer, 1024);
 +
       printf("%s\n", buffer);
 +
       exit(1);
 +
  }
 +
  status = regexec(&re, datastring, 1, &match , 0);
 +
  while (status == 0) {
 +
      strncat(buffer, datastring, match.rm_so);
 +
      strcat(buffer, replacestring);
 +
      datastring += match.rm_eo;
 +
      status = regexec(&re, datastring, 1, &match, REG_NOTBOL);
 +
  }
 +
  printf("%s%s\n", buffer, datastring);
 +
  return 0;
 +
}
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
Example use:
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
mysed klm 000 abcdefghijklmnopqrstuvwxyz
 +
abcdefghij000nopqrstuvwxyz
 +
</source></td></tr></table></ul>
 +
 
 +
=== Perl Compatible Regular Expressions ===
 +
 
 +
The Humdrum Extras library includes the [http://www.pcre.org Perl Compatible Regular Expressions] (PCRE) library which is more portable than GNU POSIX regular expressions, and also more
 +
powerful as it implements extensions to regular expressions which are present in the Perl language.  The Humdrum Extras library also includes a C++ wrapper class for PCRE which allows for a simpler interface.  Below are programs similar to the GNU POSIX regular expressions found above.
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="c">
 +
#include "PerlRegularExpression.h"
 +
#include <iostream>
 +
using namespace std;
 +
 
 +
int main(int argc, char** argv) {
 +
  if (argc < 3) exit(1);
 +
  const char *searchstring = argv[1];
 +
  const char *datastring = argv[2];
 +
  PerlRegularExpression pre;
 +
  if (pre.search(datastring, searchstring, "i")) {
 +
      cout << "Match Found" << endl;
 +
  } else {
 +
      cout << "Match Not Found" << endl;
 +
  }
 +
  return 0;
 +
}
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
The [http://extras.humdrum.org/download/humextra/include/PerlRegularExpression.h PerlRegularExpression] class definition must be included with <tt>PerlRegularExpression.h</tt>  since <tt>humdrum.h</tt> or any of the files it includes does not depend on the PerlRegularExpression class.  The .search() function returns true if a match was found, or false otherwise.  When a match is found, the index location of the string plus one is the return value.
 +
By default, a PerlRegularExpression variable will use extended regular expressions, and the <tt>"i"</tt> used as the third parameter in the .search() function is used to set the ignore-case flag.
 +
 
 +
Example behavior of the program:
 +
 
 +
<ul>
 +
::<table style="background:white;" cellpadding=0 cellspacing=0><tr valign=top><td>
 +
<source lang="text">
 +
search cat "cat in the hat"
 +
Match Found
 +
 
 +
search dog "cat in the hat"
 +
Match Not Found
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
==== mysed.cpp (Search and replace) ====
 +
 
 +
The following program demonstrates how to do search and replace on strings with the PerlRegularExpression class.
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="c">
 +
#include "PerlRegularExpression.h"
 +
#include <stdio.h>
 +
#include <string.h>
 +
 
 +
int main(int argc, char** argv) {
 +
  if (argc < 4) exit(1);
 +
  const char *searchstring = argv[1];
 +
  const char *replacestring = argv[2];
 +
  Array<char> data;
 +
  data = argv[3];
 +
  PerlRegularExpression pre;
 +
  pre.sar(data, searchstring, replacestring, "gi");
 +
  cout << data << endl;
 +
  return 0;
 +
}
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
Example use:
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<pre>
 +
mysed klm 000 abcdefghijklmnopqrstuvwxyz
 +
abcdefghij000nopqrstuvwxyz
 +
</pre>
 +
</td></tr></table></ul>
 +
 
 +
The .sar() function (or .searchAndReplace() as the long form) takes four parameters: (1) the string to perform the replacement, (2) the search string, (3) the replacement string, and (4) the regular expression flags.  In this case <tt>"gi"</tt> represents two flags: "<tt>g</tt>" for a global replacement (not just the first match on the line) and "<tt>i</tt>" as before which means to ignore case.
 +
 
 +
== Editing a HumdrumFile ==
 +
 
 +
When accessing the contents of a HumdrumFile/HumdrumReccords with [][] operators, the value of each spine (or global commend/reference record) is a <tt>const</tt> <tt>char*</tt>.  In order to change a value, use the HumdrumRecord::setToken() fuction.
 +
 
 +
=== erase.cpp (Set all data fields to the null token) ===
 +
 
 +
Here is a program which uses .setToken() to change the contents of all data fields into null tokens.
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="cpp">
 +
#include "humdrum.h"
 +
using namespace std;
 +
 
 +
void setData(HumdrumFile& hfile, const char* replacement) {
 +
  for (int i=0; i<hfile.getNumLines(); i++) {
 +
      if (!hfile[i].isData()) { continue; }
 +
      for (int j=0; j<hfile[i].getFieldCount(); j++) {
 +
        hfile[i].setToken(j, replacement);
 +
      }
 +
  }
 +
}
 +
 
 +
int main(int argc, char** argv) {
 +
  Options opts;
 +
  opts.process(argc, argv);
 +
  HumdrumFile hfile;
 +
  for (int i=1; i<=opts.getArgCount() || i == 0; i++) {
 +
      if (i == 0) { hfile.read(cin); }
 +
      else { hfile.read(opts.getArg(i)); }
 +
      setData(hfile, ".");
 +
      cout << hfile;
 +
  }
 +
  return 0;
 +
}
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
<ul>
 +
::<table style="background:white;"><tr valign=baseline><td>
 +
<source lang="text" tabwidth="10">
 +
**kern **text **kern
 +
4C ig- 4c
 +
4D 4E -no- .
 +
4F -red .
 +
. . 4d 4e
 +
4r . .
 +
4G 4A 4B text .
 +
*- *- *-
 +
</source>
 +
</td><td width=40></td><td>
 +
<source lang="text">
 +
**kern **text **kern
 +
. . .
 +
. . .
 +
. . .
 +
. . .
 +
. . .
 +
. . .
 +
*- *- *-
 +
</source>
 +
 
 +
</td></tr></table></ul>
 +
 
 +
=== transpose.cpp (Transpose music) ===
 +
 
 +
The following example program processes each **kern note by transposing it by a base40 interval.  The transposed note is reinserted into the original HumdrumFile structure which is then printed to standard output.  The main() function is used to handle multiple input files, transposeFile() is used to search through each file and pull out notes to transpose, and transposeNote() is used to replace the original pitch with the transposed version.
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="cpp">
 +
#include "humdrum.h"
 +
#include "PerlRegularExpression.h"
 +
using namespace std;
 +
 
 +
int debugQ = 0;
 +
 
 +
char* transposeNote(Array<char>& subtoken, int transpose) {
 +
  int base40 = Convert::kernToBase40(subtoken.getBase());
 +
  if (base40 <= 0) { return subtoken.getBase(); }
 +
  char newnote[1024] = {0};
 +
  Convert::base40ToKern(newnote, base40 + transpose);
 +
  if (debugQ) { cout << "!! transposing " << subtoken; }
 +
  PerlRegularExpression pre;
 +
  pre.sar(subtoken, "[a-g]+[-#n]*", newnote, "i");
 +
  if (debugQ) { cout << "\tto\t" << subtoken << endl; }
 +
  return subtoken.getBase();
 +
}
 +
 
 +
HumdrumFile& transposeFile(HumdrumFile& hfile, int transpose) {
 +
  Array<char> subtoken;  // for extracting note from chord
 +
  char obuf[1024] = {0};  // output token buffer
 +
  for (int i=0; i<hfile.getNumLines(); i++) {
 +
      if (!hfile[i].isData()) { continue; }
 +
      for (int j=0; j<hfile[i].getFieldCount(); j++) {
 +
        if ((!hfile[i].isExInterp(j, "**kern")) ||
 +
              (strcmp(".", hfile[i][j]) == 0)) {
 +
            continue;
 +
        }
 +
        int tcount = hfile[i].getTokenCount(j);
 +
        strcpy(obuf, "");
 +
        for (int k=0; k<tcount; k++) {
 +
            hfile[i].getToken(subtoken, j, k);
 +
            if (k > 0) { strcat(obuf, " "); }
 +
            strcat(obuf, transposeNote(subtoken, transpose));
 +
        }
 +
        hfile[i].setToken(j, obuf);
 +
      }
 +
  }
 +
  return hfile;
 +
}
 +
 
 +
int main(int argc, char** argv) {
 +
  Options opts;
 +
  opts.define("t|transpose=i:0", "transpose by base-40 interval");
 +
  opts.define("debug=b",        "print debugging statements");
 +
  opts.process(argc, argv);
 +
  debugQ = opts.getBoolean("debug");
 +
  int transpose = opts.getInteger("transpose");
 +
  int numinputs = opts.getArgCount();
 +
  HumdrumFile hfile;
 +
  for (int i=1; i<=numinputs || i==0; i++) {
 +
      if (numinputs < 1) { hfile.read(std::cin);  }
 +
      else { hfile.read(opts.getArg(i)); }
 +
      cout << transposeFile(hfile, transpose);
 +
  }
 +
  return 0;
 +
}
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
== Random melody generator ==
 +
 
 +
The following program will generate a random melody, both in pitch and rhythm:
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="cpp">
 +
#include "humdrum.h"
 +
#include <stdlib.h>    /* for drand48 random numbers */
 +
#include <time.h>      /* for time(NULL) function */
 +
 
 +
void printRandomMelody(int notecount, int seed) {
 +
  cout << "!!!seed:\t" << seed << endl;
 +
  cout << "**kern\n";
 +
  int pitch, rhythm;
 +
  char buffer[1024] = {0};
 +
  for (int i=0; i<notecount; i++) {
 +
      rhythm = int(drand48() * 16 + 1 + 0.5);
 +
      pitch  = int(drand48() * 24 + 12*4.5 + 3);
 +
      cout << rhythm << Convert::base12ToKern(buffer, pitch) << endl;
 +
  }
 +
  cout << "*-\n";
 +
}
 +
 
 +
int main(int argc, char** argv) {
 +
  Options options;
 +
  options.define("c|count=i:20", "number of notes to generate");
 +
  options.define("s|seed=i:-1",  "random number generator seed");
 +
  options.process(argc, argv);
 +
  int seed = options.getInteger("seed");
 +
  if (seed < 0) {
 +
      seed = time(NULL);  // time in seconds since 1 Jan 1970
 +
  }
 +
  srand48(seed);
 +
  printRandomMelody(options.getInteger("count"), seed);
 +
  return 0;
 +
}
 +
</source></td></tr></table></ul>
 +
 
 +
Here is some example output:
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="text">
 +
!!!seed: 1241137496
 +
**kern
 +
2d
 +
9ee-
 +
2a
 +
13g
 +
15B-
 +
16f#
 +
7A
 +
16B
 +
17gg
 +
1d
 +
11cc
 +
14c#
 +
10d
 +
11ee
 +
12f#
 +
13c#
 +
8cc
 +
14b-
 +
3f
 +
17a
 +
*-
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
The <tt>-s</tt> option can be used to generate a fixed random melody; otherwise, the random melody will be based on the current time in seconds since 1 Jan 1970.  The <tt>-c</tt> option can be used to specify the number of notes in the melody.  Notice that if no melody length is given, the default length of 20 note will be used.
 +
 
 +
To display as graphical music notation:
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="bash">
 +
bin/randomel -s 1241137496 \
 +
  | hum2abc -M none | abcm2ps - -O - \
 +
  | convert -quality 100 -density 300 - -trim -resize '33%' output.png
 +
</source>
 +
</td></tr></table></ul>
 +
 
 +
[[File:RandomMelody.png|600px|center]]
 +
 
 +
The Humdrum file data can be stored internally in a stream object.  This stream object can be ready by a HumdrumFile object to be reprocessed within the program, or to be printed with ostream as a HumdrumFile object.  The following example demonstrates this.  Also, since strstream was changed to stringstream in ANSI C99, there are some preprocessor instructions to allow using string streams in both older and newer standards (including differences in Visual C++ 6).
 +
 
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="cpp">
 +
#include "humdrum.h"
 +
#include <stdlib.h>    /* for drand48 random numbers */
 +
#include <time.h>      /* for time(NULL) function */
 +
 
 +
using namespace std;
 +
 
 +
void getRandomMelody(HumdrumFile& datafile, int notecount, int seed) {
 +
  stringstream output;
 +
  output << "!!!seed:\t" << seed << endl;
 +
  output << "**kern\n";
 +
  int pitch, rhythm;
 +
  char buffer[1024] = {0};
 +
  for (int i=0; i<notecount; i++) {
 +
      rhythm = int(drand48() * 16 + 1 + 0.5);
 +
      pitch  = int(drand48() * 24 + 12*4.5 + 3);
 +
      output << rhythm << Convert::base12ToKern(buffer, pitch) << endl;
 +
  }
 +
  output << "*-\n";
 +
  output << ends;
 +
  // It is possible to print to ostream:
 +
  // cout << output.str().c_str() << flush;
 +
  datafile.read(output);
 +
}
 +
 
 +
int main(int argc, char** argv) {
 +
  Options options;
 +
  options.define("c|count=i:20", "number of notes to generate");
 +
  options.define("s|seed=i:-1",  "random number generator seed");
 +
  options.process(argc, argv);
 +
  int seed = options.getInteger("seed");
 +
  if (seed < 0) {
 +
      seed = time(NULL);  // time in seconds since 1 Jan 1970
 +
  }
 +
  srand48(seed);
 +
  HumdrumFile datafile;
 +
  getRandomMelody(datafile, options.getInteger("count"), seed);
 +
  cout << datafile;
 +
  return 0;
 +
}
 +
</source></td></tr></table></ul>
 +
 
 +
== Markov melody analyzer/generator ==
 +
 
 +
Here is a more sophisticated random melody generator.
 +
 
 +
<ul>
 +
::<table cellpadding=0 cellspacing=0 style="background: white;"><tr><td>
 +
<source lang="cpp">
 +
#include "humdrum.h"
 +
#include <regex.h>
 +
#include <stdlib.h>
 +
#include <time.h>
 +
 
 +
void buildTable(HumdrumFile& hfile, Array<Array<double> >& ptable,
 +
      Array<Array<double> >& mtable) {
 +
  int lastmeter = -1; int lastpitch = -1;
 +
  int meter, pitch;
 +
  hfile.analyzeRhythm();
 +
  for (int i=0; i<hfile.getNumLines(); i++) {
 +
      if (!hfile[i].isData()) continue;
 +
      if (strcmp("**kern", hfile[i].getExInterp(0)) != 0) continue;
 +
      if (strcmp(hfile[i][0], ".") == 0) continue;    // ignore null tokens
 +
      if (strchr(hfile[i][0], 'r') != NULL) continue; // ignore rests
 +
      pitch = Convert::kernToBase40(hfile[i][0]) % 40;
 +
      meter = int((hfile[i].getBeat() - 1.0) * 4 + 0.5);
 +
      if (meter < 0) meter = 0;
 +
      if (meter >= 40) meter = 39;
 +
      if (lastmeter < 0) {
 +
        lastpitch = pitch; lastmeter = meter;
 +
        continue;
 +
      }
 +
      mtable[lastmeter][meter]++;  mtable[lastmeter][40]++;
 +
      ptable[lastpitch][pitch]++;  ptable[lastpitch][40]++;
 +
      lastpitch = pitch;            lastmeter = meter;
 +
  }
 +
}
 +
 
 +
void printTables(Array<Array<double> >& ptable,
 +
  Array<Array<double> >& mtable, int style) {
 +
  int i, j;
 +
  double value;
 +
  char buffer[32] = {0};
 +
  for (i=0; i<ptable.getSize(); i++) {
 +
      cout << '\t' << Convert::base40ToKern(buffer, i+4*40);
 +
  }
 +
  cout << endl;
 +
  for (i=0; i<ptable.getSize(); i++) {
 +
      cout << Convert::base40ToKern(buffer, i+4*40);
 +
      for (j=0; j<40; j++) {
 +
        value = style ? ptable[i][j]/ptable[i][40] : ptable[i][j];
 +
        cout << '\t' << value;
 +
      }
 +
      cout << '\t' << ptable[i][40] << endl;
 +
  }
 +
  cout << endl;
 +
  for (i=0; i<mtable.getSize(); i++) cout << "\tb" << i/4.0 + 1.0;
 +
  cout << endl;
 +
  for (i=0; i<mtable.getSize(); i++) {
 +
      cout << "b" << i/4.0 + 1.0;
 +
      for (j=0; j<mtable[i].getSize(); j++) cout << '\t' << mtable[i][j];
 +
      cout << endl;
 +
  }
 +
}
 +
 
 +
int chooseNextTransition(Array<Array<double> >& table, int state) {
 +
  double target = drand48() * table[state][40];
 +
  double sum = 0.0;
 +
  for (int i=0; i<40; i++) {
 +
      sum += table[state][i];
 +
      if (sum > target)  return i;
 +
  }
 +
  return 39;
 +
}
 +
 
 +
void smoothMelody(Array<double>& meldur, Array<int>& melpitch) {
 +
  int beforei, afteri, inta, intb;
 +
  for (int i=2; i<meldur.getSize()-2; i++) {
 +
      if (meldur[i] < 0.0) continue;
 +
      afteri  = i+1;  beforei = i-1;
 +
      if (meldur[afteri] < 0.0) afteri++;
 +
      if (meldur[beforei] < 0.0) beforei--;
 +
      inta = melpitch[i] - melpitch[beforei];
 +
      intb = melpitch[i] - melpitch[afteri];
 +
      if ((inta > 22) && (intb > 22)) {
 +
        melpitch[i] -= 40;
 +
      } else if ((inta < -22) && (intb < -22)) {
 +
        melpitch[i] += 40;
 +
      }
 +
  }
 +
}
 +
 
 +
void generateMelody(Array<Array<double> >& ptable,
 +
      Array<Array<double> >& mtable, int count) {
 +
  int    pitch, pitchclass = 2, meter = 0, oldmeter = 0;
 +
  int    i, measurenumber = 2;
 +
  double duration, barmarker = -1;
 +
  char  buffer[1024] = {0};
 +
  Array<int> melpitch(count*2);  melpitch.setSize(0);
 +
  Array<double> meldur(count*2); meldur.setSize(0);
 +
  for (i=0; i<count; i++) {
 +
      pitchclass  = chooseNextTransition(ptable, pitchclass);
 +
      meter = chooseNextTransition(mtable, meter);
 +
      if (meter > oldmeter) duration = (meter - oldmeter) / 4.0;
 +
      else {
 +
        duration = (4 + meter - oldmeter) / 4.0;
 +
        meldur.append(barmarker);
 +
        pitch = measurenumber++;
 +
        melpitch.append(pitch);
 +
      }
 +
      oldmeter = meter;
 +
      if (duration == 0.0) duration = 4.0;
 +
      if (duration > 4.0)  duration = 4.0;
 +
      if (duration < 0.0)  duration = 1.0;
 +
      pitch = pitchclass + 4 * 40;
 +
      meldur.append(duration);  melpitch.append(pitch);
 +
  }
 +
  smoothMelody(meldur, melpitch);
 +
  cout << "**kern\n*M4/4\n=1-\n";
 +
  for (i=0; i<meldur.getSize(); i++) {
 +
      if (meldur[i] < 0.0) cout << "=" << melpitch[i] << endl;
 +
      else {
 +
        cout << Convert::durationToKernRhythm(buffer, meldur[i]);
 +
        cout << Convert::base40ToKern(buffer, melpitch[i]);
 +
        cout << endl;
 +
      }
 +
  }
 +
  cout << "*-" << endl;
 +
}
 +
 
 +
int main(int argc, char** argv) {
 +
  Options options;
 +
  options.define("t|table=b",      "display table of transitions");
 +
  options.define("f|fraction=b",    "display transitions as fractions");
 +
  options.define("g|generate=i:20", "generate specified number of notes");
 +
  options.process(argc, argv);
 +
  srand48(time(NULL)); HumdrumFile hfile;
 +
  Array<Array<double> > ptable; // pitch transition table
 +
                              // (scale degrees would be musically better)
 +
  Array<Array<double> > mtable; // meter transition table
 +
  ptable.setSize(40); ptable.allowGrowth(0);
 +
  mtable.setSize(40); mtable.allowGrowth(0);
 +
  int i;
 +
  for (i=0; i<ptable.getSize(); i++) {
 +
      ptable[i].setSize(41); ptable[i].allowGrowth(0); ptable[i].setAll(0.0);
 +
      mtable[i].setSize(41); mtable[i].allowGrowth(0); mtable[i].setAll(0.0);
 +
  }
 +
  int numinputs = options.getArgCount();
 +
  for (i=1; i<=numinputs || i==0; i++) {
 +
      if (numinputs < 1) hfile.read(std::cin);
 +
      else hfile.read(options.getArg(i));
 +
      buildTable(hfile, ptable, mtable);
 
   }
 
   }
   status = regexec(&re, datastring, 1, &match , 0);
+
   if (options.getBoolean("table")) {
  while (status == 0) {
+
       printTables(ptable, mtable, options.getBoolean("fraction"));
       strncat(buffer, datastring, match.rm_so);
+
  } else {
      strcat(buffer, replacestring);
+
       generateMelody(ptable, mtable, options.getInteger("generate"));
      datastring += match.rm_eo;
 
       status = regexec(&re, datastring, 1, &match, REG_NOTBOL);
 
 
   }
 
   }
  printf("%s%s\n", buffer, datastring);
 
 
   return 0;
 
   return 0;
 
}
 
}
 
</source>
 
</source>
 
+
</td></tr></table></ul>
Example use:
 
 
 
  mysed klm 000 abcdefghijklmnopqrstuvwxyz
 
  abcdefghij000nopqrstuvwxyz
 

Latest revision as of 20:17, 19 May 2020

Humdrum Extras is a set of command-line programs and C++ library for processing Humdrum files. The programs can be compiled for Linux, Apple OS X, or Windows (primarily within cygwin, but also in Visual C++). The Humdrum Extras library can be used to parse Humdrum files independent of the example programs provided with the package.


Contents

Example Programs

The primary intent of the Humdrum Extras package is for user-based processing of Humdrum files as an auxiliary to the Humdrum Toolkit. Since the programs are compiled from C++ code, they process data much faster than programs written in interpreted languages, such as AWK which is the main development language for the Humdrum Toolkit.

Documentation for example programs can be found on the web at extras.humdrum.org/man. The source code for these programs is found in this zip file, within the src-programs directory, or they can be viewed online.


Installing Humdrum Extras (only)

Humdrum Extras is most conveniently downloaded and updated using the git command (or an interface program which uses git). Type 'which git' in a terminal to see if it is installed. In linux it should be easy to install with either 'sudu apt-get install git' or 'sudo yum install git', depending on your flavor of linux. In OS X, Homebrew is the most convenient unix package manager. If you install that, then it installs git at the same time.

If you have the git program installed on your computer, then you can use that system to download humextra from GitHub; otherwise, download the zip file and unzip it. When using git, type this command in the terminal:

     git clone https://github.com/craigsapp/humextra

To compile the library and example programs:

     cd humextra
     make

Compiled programs are stored in humextra/bin. If you do not want to compile the example programs, instead type:

     make library

To periodically receive updates, go into the humextra directory, and type:

   git pull 
   make clean # optional, but a good idea
   make

This will download the most recent changes in the library source code and recompile the library and example programs. To update only the library file without recompiling the example programs:

   make libupdate

Installing Humdrum Tools (which includes Humdrum Extras)

The Humdrum Toolkit package includes both the standard Humdrum Toolkit and Humdrum Extras:

  git clone --recursive https://github.com/humdrum-tools/humdrum-tools

To setup everything do these commands:

   cd humdrum-tools
   make

To periodically update the software:

   cd humdrum-tools
   make update
   make clean # Optional but good to clear out old compiled code.
   make

The Humdrum Extras package will be in humdrum-tools/humextra




Using the Humextras Library

You can place your source code directly into the Humdrum Extras source-code directory, but this will cause problem when updating with git, or possible accidental deletion. Instead, it may be wise to create a parallel directory structure with symbolic links to the important components of the main humextra installation.

Here is a short bash shell script to create a linked installation of humextra in the current directory, provided that humextra can be found in the command path. Copy and paste into a terminal to set up a linked copy of humextra:

     HUMDIR=`echo $PATH | tr : '\n' | grep humextra | head -n 1 | sed 's/\/bin$//'`
     if [[ -z $HUMDIR ]]
     then
        echo "Humdrum Extras not found in PATH variable: not creating linked installation."
     else 
        echo "Creating myhum directory, linked to $HUMDIR."
        mkdir myhum
        cd myhum
        ln -s $HUMDIR/include .
        ln -s $HUMDIR/external .
        ln -s $HUMDIR/lib .
        ln -s $HUMDIR/bin .
        ln -s $HUMDIR/Makefile .
        ln -s $HUMDIR/Makefile.programs .
        mkdir src-programs
    fi

You should then be able to place your code in myhum/src-programs, such as myprog.cpp, then compile them within the myhum directory with the following command in the myhum directory:

     make myprog

where "src-programs/myprog.cpp" is the source code. This will create an executable in the bin directory: "bin/myprog". You can run it with the command "bin/myprog" or just "myprog" if the Humdrum Extras bin directory is in the $PATH environment variable.

Programming Examples

Here are a set of graduated program examples which can be used as a basis for writing your own programs.

Basic data access

humecho.cpp

Here is a very simple C++ program called humecho.cpp that uses the Humdrum file parser in the Humdrum Extras library:

    #include "humdrum.h"
    #include <iostream>
    using namespace std;
    
    int main(int argc, char** argv) {
       HumdrumFile hfile;
       if (argc > 1) { hfile.read(argv[1]); }
       else { hfile.read(cin); }
       cout << hfile;
       return 0;
    }
    

This program will take one Humdrum file as an argument (or standard input) and echo the contents of the Humdrum file to standard output. To compile this program using the Humdrum Extras makefiles, place humecho.cpp in the directory humextra/src-programs, and then type "make humecho" in the humextra directory. The compiled program will be placed in bin/humecho. The humecho program can be utilized in several ways, including downloading from the web, or using the humdrum:// URI (or hum:// or h:// abbreviations):

    cat file.krn | bin/humecho                # standard input
    bin/humecho file.krn                      # command-line argument
    bin/humecho h://wtc/wtc1f01.krn           # humdrum:// URI
    bin/humecho jrp://Jos2721                 # jrp:// URI
    bin/humecho http://y.z.com/file.krn       # URL
    




humecho2.cpp (Accessing individual lines)

The humecho program shows how to access the datafile in its entirety. The following source code for humecho2.cpp demonstrates how to access lines in the file individually. A HumdrumFile class essentially consists of an array of HumdrumRecord classes, and HumdrumRecord classes essentially are character strings which print tab-delimited with cout:

    #include "humdrum.h"
    using namespace std;
    
    int main(int argc, char** argv) {
       HumdrumFile hfile;
       if (argc > 1) { hfile.read(argv[1]); }
       else hfile.read(cin);
       for (int i=0; i<hfile.getNumLines(); i++) {
          cout << hfile[i] << std::endl;
       }
       return 0;
    }
    

hfile.getNumLines() returns the number of text lines in the Humdrum file stored in the hfile variable. So the for loop iterates through each line in the file and prints it to standard output.




humecho3.cpp (Accessing spine data)

An even more verbose version of humecho is given below. The humecho3 program mimics the << operator for HumdrumRecords as a second for-loop. Each HumdrumRecord representing a line of music can be thought of as an array of strings (const char*), with each string being one token in the Humdrum file structure.

    #include "humdrum.h"
    using namespace std;
     
    int main(int argc, char** argv) {
       HumdrumFile hfile;
       if (argc > 1) { hfile.read(argv[1]); }
       else { hfile.read(cin); }
       for (int i=0; i<hfile.getNumLines(); i++) {
          cout << hfile[i][0];
          for (int j=1; j<hfile[i].getFieldCount(); j++) {
             cout << "\t" << hfile[i][j];
          }
          cout << endl;
       }
       return 0;
    }
    

HumdrumRecords always contain at least one field, so the code "cout << hfile[i][0];" will not cause an invalid array access in any situation. Both [] operators used on the hfile variable (first to access a HumdrumRecord, and the second for a const char*) are checked for a valid range, and the program will exit with an error if an out-of-range value is requested.

The code hfile[i].getFieldCount() returns the number of "fields" on the line. This is a non-standard term for Humdrum files, since "spines" and "tokens" can have somewhat ambiguous meanings. The field count is a count of the spines, but if the spines split the count would include the subspines as well. Global comments and reference records are always element 0 in a HumdrumRecord line. Empty lines, which are technically not allowed in Humdrum files, are also acessed as an empty string at element 0.

Note that hfile[i][j] is a const char* and not a char*. If you want to change the contents of a field, you would have to use hfile[i].changeField(j, "new string").




HumdrumRecord line types

Each HumdrumRecord line in a HumdrumFile class possesses an enumerated type:

    Enumeration Description
    E_humrec_data data lines other than measure
    E_humrec_data_measure line starting with “=”
    E_humrec_interpretation line starting with “*”
    E_humrec_bibliography reference records of the form “!!!key: value”
    E_humrec_global_comment   starts with “!!”, other than reference records
    E_humrec_local_comment local comment, starting with single "!"
    E_humrec_empty empty line

Use the HumdrumRecord::getType() function to access the type of a line. These type values can be used for switch statements, but for better code readability, the following helper HumdrumRecord functions interface with these enumerations:

    HumdrumRecord
    method
    Description
    .isData() true if data (other than barline).
    .isMeasure() true if barline (line starts with “=”).
    .isInterpretation() true if line starts with “*”.
    .isBibliographic() true if in the form of “!!!key: value”.
    .isGlobalComment()    true if line starts with “!!” and not bib.
    .isLocalComment() true if line starts with one “!”.
    .isEmpty() true if nothing on line.

In addition there are a few composite test for line types:

    HumdrumRecord
    method
    Description
    .isComment()    isBibliographic() or isGlobalComment() or isLocalComment()
    .isTandem() Interpretation lines which contain only spine manipulators (*+, *-, *^, *v, *x, or exclusive interpretations (starting with **).
    .isNull() isData() and all fields are "." (null token), or isInterpretation() and all fields are "*".
    .hasSpines() isData() or isMeasure() or isLocalComment() or isInterpretation()




"rid -GLI" (Remove all lines except for data lines)

The Humdrum Tool rid with the -GLI options can be implemented using the following C++ code:

    #include "humdrum.h"
    int main(int argc, char** argv) {
       HumdrumFile hfile(argv[1]);
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!(hfile[i].isData() || hfile[i].isMeasure())) continue;
          std::cout << hfile[i] << std::endl;
       }
       return 0;
    }
    

The above code will only print lines which are data or barlines. The official Humdrum file specification does not technically distinguish between barlines and data, but in practice and from a logical point of view they must be separated. So when using the Humdrum Extras C++ parser for Humdrum files, a line of data should not contain a mixture of data (or null tokens) and barlines.




"rid -GLId" (Remove comments, interpretations and null data)

    #include "humdrum.h"
    int main(int argc, char** argv) {
       HumdrumFile hfile(argv[1]);
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!(hfile[i].isData() || hfile[i].isMeasure())) continue;
          if (hfile[i].isNull()) continue;
          std::cout << hfile[i] << std::endl;
       }
       return 0;
    }
    

The HumdrumRecord::isNull() returns true if all fields in the record are equal to the string "." (called a null token in Humdrum terminology—not related to a NULL pointer in C).




Options class (User-specified options)

"myrid -M -C -I" (Handling command-line options)

The Humdrum Extras library contains a helper class called Options which can be used to manage command-line options. The following example program implements the options -M (suppress measure lines), -C (suppress comments), -I (suppress interpretations) in a C++ implementation of the Humdrum Toolkit rid program.

The Options class can be used to define multiple aliases for the same option, such as a short abbreviation and a long form. The options are formulated on the command line according to POSIX rules for options: single-letter options are preceded by a single dash. Multiple-letter options are preceeded by two dashes. When a single-letter option does not require it's own argument, they can be globbed together into a list of options preceded by a single dash. Here are various program usages for the code below:

    Command Description
    myrid -M file.krn Remove measure lines when echoing file.krn to standard output.
    myrid -M -I -C file.krn Remove measure lines, interpretations and comments (global, local and reference).
    myrid -MIC file.krn Same as above. Shorthand for bundling multiple single-letter boolean options.
    myrid --no-measures file.krn   Long form of "myrid -M".
    myrid --options Secret built-in option for the Option class which will force a list of defined options to be printed to standard output.
    myrid -A file.krn The option list will also be displayed when an undefined or misspelled option is used. Use "--" to disable options processing for unusual cases such as a filename starting with a dash.
    myrid -MM file.krn Duplicate options are ignored, so only the last -M is used. Note that this is not the option "MM" which would be formulated as "myrid --MM".
    myrid -M file.krn -IC Options can occur in any order, and can come before or after any command arguments which are not options.
    myride -M -- -file.krn -C Process the poorly named file "-file.krn" and the even more poorly named file called "-C" which is not an option if it comes after the -- marker.

Note in the following source code, an extra include directive for the Options class does not need to be added, since the declaration for the Options class is included in humdrum.h. If you want to use the Options class independent of the HumdrumFile parser, you can instead include the file "Options.h".

    #include "humdrum.h"
    int main(int argc, char** argv) {
       Options opts;
       opts.define("M|no-measures:b", "remove measures");
       opts.define("C|no-comments:b", "remove comments");
       opts.define("I|no-interpretations:b", "remove interpretations");
       opts.process(argc, argv);
       int measuresQ = !opts.getBoolean("no-measures");
       int commentsQ = !opts.getBoolean("no-comments");
       int interpQ = !opts.getBoolean("no-interpretations");
       HumdrumFile hfile(opts.getArg(1));
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (hfile[i].isMeasure() && !measureQ) continue;
          if (hfile[i].isComment() && !commentQ) continue;
          if (hfile[i].isInterpretation() && !interpQ) continue;
          std::cout << hfile[i] << std::endl;
       }
       return 0;
    }
    

The code "HumdrumFile hfile(opts.getArg(1));" reads data from the first argument on the command line. Note that argument counts are indexed from 1 rather than 0. Perhaps not a great thing to do, but was intended to allow for similar behavior with command-line string arrays in C, where the name of the command is stored in array element 0, and the first argument (or option) is stored in array element 1. To access the name of the command, use the Options::getCommand() function.




Option definitions

Notice the Options::define() function calls in the above program. These are used to define the options that an Options variable will search for when the Options::process() function is called. The .define() function takes two arguments (the second one optional). The first argument is the definition string, and the second is a human-readable description of the option.

The option definition string has the basic format:

"OptionName=OptionType:DefaultValue"

The OptionName can include aliases which are added to the Option name, separated by a pipe (|) character:

"OptionName|OptionAlias1|OptionAlias2=OptionType:DefaultValue"

For example:

"M|no-measures=b"

Is the definition of the option "M" or equivalently "no-measures" which is a boolean type (which means that it sets a true/false switch for the option). For boolean options, there is no default value—they are "false" if not given as an argument to the program, and turned to "true" when given as input to a program.

There are four Options data types:

    Option type Description Options value access
    b boolean (true or false) .getBoolean("OptionName")
    i integer .getInteger("OptionName")
    d double (floating-point number) .getDouble("OptionName")
    s string .getString("OptionName")

In terms of implementation, there are really only two types: booleans (with out parameters) and non-booleans (with parameters). Within a C++ program you can acess the original string form of the option's parameter, or you can convert it into an int or a double at runtime. For example, if an option "number" is defined, you can get the integer version of the number with .getInteger("number"), or the double version of the number with .getDouble("number"), or you can check to see if the option was set from the input arguments to the program with .getBoolean("number").

Here are some example option definitions with option names, option aliases, and option types:

    Option definition Command-line examples
    "r=b" command -r
    "m=i" command -m 10 or command -m10
    value=d" command -v 5.23 or command -v5.23
    command --value 5.23
    command --value=5.23
    "t=s" command -t string or command -tstring
    command -t "string with spaces"
    command -t 'funny $tring'

When options names (or option aliases) are a single character, the space between the option name and it parameter is optional, as in "command -m 10" or "command -m10". When an option has multiple characters, the space is not optional, although an equals sign can be substituted for the space: "command --value 5.23" and "command --value=5.23". When a string option contains spaces, or other special characters reserved for shell syntax, (such as [;&$|?*\]). The multi-word option must be enclosed in quotes. To insert a quote into the string option place a backslash before it: \". To prevent the command-line parser from looking inside of the string use single quotes: "command -t 'funny $tring'". In this case the final input will be "funny $tring". If double quotes were used, $tring would be interpreted as an environmental variable and its value would be substituted, usually resulting in "funny ", since you are not likely to have the shell variable $tring defined.




Default option values

The final component of the option definition is a default value to use if no input is given for that option on the command-line. If no default value is given in the definition, the default value will be zero. For example, if this option definition is given:

       options.define("v|val|value=i:10", "an integer value");
    

Then here are different behaviors when accessing that option's value in C++:

    User-set option: 
       program -v 20
          options.getInteger("value")      → 20
          options.getInteger("val")        → 20
          options.getInteger("v")          → 20
    
    Default option:
       program
          options.getInteger("value")      → 10
          options.getInteger("val")        → 10
          options.getInteger("v")          → 10
    




Accessing option values

As mentioned previously, the .getBoolean, .getInteger, .getDouble and .getString accessor functions are used to extract an option value from the Options database after .process() has been called on the argc and argv input parameters to main(). All of the get functions can be applied to any option type. For example, the option definition:

       .define("t|temperature=d:80.6 Farenheit", "temperature setting")
    

can be used to extract any of the four option types in C++:

       .getBoolean("temperature")           → 1 (true) if set via the command-line.
                                            → 0 (false) if not set via the command-line.
       .getInteger("temperature")           → 80
       .getDouble("temperature")            → 80.6
       .getString("temperature")            → "80.6 Farenheit"
    




Input from piped data or file(s)

Most of the previous program examples expect a single filename as input for processing. The following program example (humecho4) is more flexible, allowing for multiple input files. If no filenames are given, then standard input will be read as the input data:

    #include "humdrum.h"
    using namespace std;
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       int numinputs = options.getArgCount();
       for (int i=1; i<=numinputs || i==0; i++) {
          if (numinputs < 1) {
             hfile.read(cin); // read from standard input
          } else {
             hfile.read(options.getArg(i));
          }
          // do something with the Humdrum data here:
          cout << hfile;
       }
       return 0;
    }
    

This program has an identical function to humecho.cpp, but now multiple files can be read in and processed at the same time. For example if there are two input files with these contents:


    file 1
    **kern
    1c
    2d
    4e
    *-
    
    file 2
    **kern
    2cc
    4b
    2a
    *-
    
    output
    **kern
    1c
    2d
    4e
    *-
    **kern
    2cc
    4b
    2a
    *-
    

Here are some possible command-line realizations for the above program:

    humecho4 file.krn
    humecho4 file1.krn file2.krn file3.krn
    cat file.krn | humecho4
    humecho4
    

The last command will cause the shell to wait while you type in the input to humecho4, followed by control-D to indicate the end of input data.

Note that the number of command-line arguments (other than options) can be queried from an Options variable by using the .getArgCount() function. If there are three filenames as in "echo4 file1.krn file2.krn file3.krn", then .getArgCount() will return 3. The .getArg() function will return a string for the specified argument, starting with argument 1: .getArg(1) == file1.krn, .getArg(2) == file2.krn, .getArg(3) == file3.krn. Note that the first argument is not .getArg(0). If you want to access the command name, then use .getCommand(), which would return "humecho4" in this case.

When reading from standard input use HumdrumFile::read(istream) rather than HumdrumFile::read(const char*). For example, reading from standard input is done with hfile.read(cin) in the above code.




HumdrumStream class

Input data from multiple files can be managed by the HumdrumStream class. This class also manages multiple independent sequences of data, such as movements, in a data file which the HumdrumFile class will not process.

    #include "humdrum.h"
    using namespace std;
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumStream streamer(options.getArgList());
       HumdrumFile hfile;
       while (streamer.read(hfile)) {
          cout << hfile;
       }
       return 0;
    }
    


HumdrumFileSet class

The HumdrumFileSet class functions in a similar manner to the HumdrumStream class. It reads in multiple segments of Humdrum data from a single physical file, multiple files, or standard input. The main difference compared to HumdrumStream is that HumdrumFileSet retains the contents of all input segments in memory.

Here is a basic processing example which demonstrates how to store all input arguments into HumdrumFileSet. In this case the HumdrumFileSet::read() function is used to extract a list of arguments from the options variable. If the options variable does not contain any filenames, standard input will be read:

    #include "humdrum.h"
    using namespace std;
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFileSet infiles;
       infiles.read(options);
       for (int i=0; i<infiles.getCount(); i++) {
          cout << infiles[i];
       }
       return 0;
    }
    

C string comparison functions

Here are three of the string comparison functions available within in the C (or C++) language:

strcmp("string1", "string2")
returns 0 if strings are equivalent
returns –1 if string1 is alphabetized before string2
returns +1 if string1 is alphabetized after string2.
strncmp("string1", "string2", n)
compare only first n characters of the two strings.
strchr("string", 'character')
returns a pointer to the first occurrence of the character within the string. If the character is not found in the string, returns a NULL pointer.

Other interesting string processing functions in the C language are strstr which is similar to strchr but search for a sub-string within the a string; and strrchr which is similar to strchr but searches for the character in the reverse direction in the string, which returns the last occurrence of the character in the string (or NULL) if the character is not in the string. For more description about these functions, type "man strrchr" in a terminal for more information about the strrchr function (or any other standard C fuction).




Third dimension of data access (Note-level access)

Accessing individual notes in **kern data spines requires three dimensions of indexing: (1) the data line of the note, the data field on the line for the note, and then the note number within a chord for the note. Previous program examples demonstrated how to access lines and line-fields. The following program (noteloc) goes one step further to access individual **kern notes. The program takes any sort of Humdrum file, and then outputs a list of all notes found in all kern spines:

    #include "humdrum.h"
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       char buffer[1024] = {0};
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue; // ignore non-data lines
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
             if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
             int count = hfile[i].getTokenCount(j);
             for (int k=0; k<count; k++) {
                cout << "(" << i+1 <<"," << j+1 << "," << k+1 << ")\t"
                     << hfile[i].getToken(buffer, j, k) << endl;
             } 
          }
       }
       return 0;
    }
    

The line:

    if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
    

is used to skip over all spines which do not have **kern data. The function .getExInterp() returns a const char* string for the name of the exclusive interpretation. The strcmp() function compares the returns exclusive interpretation name with the string "**kern", and if it does not match, the next data field on the line will be examined. An equivalent way of identifying the exclusive interpretation can be done with the .isExInterp() function. The following line of code is equivalent to the one above:

    if (hfile[i].isExInterp(j, "**kern")) continue;
    

Input and output to/from the program:

    **kern	**text	**kern
    4C	ig-	4c
    4D 4E	-no-	.
    4F	-red	.
    .	.	4d 4e
    4r	.	.
    4G 4A 4B	text	.
    *-	*-	*-
    
    (2,1,1) 4C
    (2,3,1) 4c
    (3,1,1) 4D
    (3,1,2) 4E
    (4,1,1) 4F
    (5,3,1) 4d
    (5,3,2) 4e
    (6,1,1) 4r
    (7,1,1) 4G
    (7,1,2) 4A
    (7,1,3) 4B
    

Each of the three numbers before the note indicates the address within the file for the note, with the first number being the line on which the note occurs, the second number the field on the line which contains the note, and the last number is the note number within the (possible) chord for the note.




kerninfo.cpp (Count **kern notes in data)

Here is an example program which somewhat emulates the "census -k" command from the Humdrum Toolkit. The program will count the number of note attacks, rests and tied notes in one or more Humdrum files.

    #include "humdrum.h"
    using namespace std;
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       int restcount   = 0;
       int nullcount   = 0;
       int attackcount = 0;
       int tiedcount   = 0;
       int chordcount  = 0;
       for (int arg=1; arg <= options.getArgCount() || arg == 0; arg++) {
          if (options.getArgCount() == 0) {  hfile.read(cin); } 
          else { hfile.read(options.getArg(arg)); }
          char buffer[1024] = {0};
          for (int i=0; i<hfile.getNumLines(); i++) {
             if (!hfile[i].isData()) continue;
             for (int j=0; j<hfile[i].getFieldCount(); j++) {
                if (!hfile[i].isExInterp(j, "**kern")) continue;
                int count = hfile[i].getTokenCount(j);
                if (count > 1) chordcount++;
                for (int k=0; k<count; k++) {
                   hfile[i].getToken(buffer, j, k);
                   if (strchr(buffer, 'r') != NULL)   { restcount++; } 
                   else if (strcmp(buffer, ".") == 0) { nullcount++; } 
                   else if (strchr(buffer, '_') != NULL) { /* ignore */ }
                   else if (strchr(buffer, ']') != NULL) { tiedcount++; } 
                   else { attackcount++; }
                }
             }   
          }
       }
       cout << "Note attacks: " << attackcount << endl;   
       cout << "Tied notes  : " << tiedcount   << endl;
       cout << "Chords      : " << chordcount  << endl;   
       cout << "Rests       : " << restcount   << endl;
       cout << "Null Tokens : " << nullcount   << endl;   
       return 0;
    }
    
    Trying out the kerninfo prorgram on this input data:
    **kern	**text	**kern
    4C	ig-	4c
    4D 4E	-no-	.
    4F	-red	.
    .	.	4d 4e
    4r	.	.
    4G 4A 4B	text	.
    *-	*-	*-
    

    Results in these statistics:

    Note attacks: 10
    Tied notes  : 0
    Chords      : 3
    Rests       : 1
    Null Tokens : 5
    

Trying out the kerninfo program on a real piece of music:

    kerninfo h://wtc/wtc1p04.krn
    Note attacks: 675
    Tied notes  : 85
    Chords      : 14
    Rests       : 69
    Null Tokens : 967
    




Convert class

In addition to the Options class, and important helper class in Humdrum Extras is the Convert class. This class handles most conversions between data types. The HumdrumFile class essentially stores a two-dimensional array of strings. The **kern notes in a HumdrumFile variable are extracted as strings, but will need to be interpreted further depending on the information about the note which you need. For example, to convert a **kern note into a MIDI note number, use the following Convert function:

  Convert::kernToMidiNoteNumber("4d-")          →  61

Likewise, the MIDI note 61 can be converted back into a **kern note:

  Convert::midiNoteNumberToKern(buffer, 61)     →  "c#"

All access to Convert class functions is done statically, so you can shorten the code by using the a typedef for Convert to a shorter name:

   typedef Convert C;
   C::kernToMidiNoteNumber("4d-");




Convert **kern note names to MIDI

The following program will convert the first note of every chord into a MIDI note number.


    #include "humdrum.h"
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile(options.getArg(1));
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue;
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
          if (hfile[i].isExInterp(j, "**kern")) continue;
          if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
          if (strchr(hfile[i][j], 'r') != NULL) continue; // ignore rests
             cout << hfile[i][j] << "\t" << Convert::kernToMidiNoteNumber(hfile[i][j]) << endl;
          }
       }
       return 0;
    }
    

Example input and output:

    **kern	**text	**kern
    4C	ig-	4c
    4D 4E	-no-	.
    4F	-red	.
    .	.	4d 4e
    4r	.	.
    4G 4A 4B	text	.
    *-	*-	*-
    
    4C        48
    4c        60
    4D 4E     50
    4F        53
    4d 4e     62
    4G 4A 4B  55
    

Note that only the first kern note in the string will be extracted by Convert::kernToMidiNoteNumber(). As an exercise, adjust the code so that it prints a MIDI note number for every note in the chords.




Note Histogram (notehist.cpp)

Here is an example of how to count the number of twelvetone pitch classes in a Humdrum file. The following program will count tied notes. As an exercise, have the program skip counting of any middle or end tied notes (middle tied notes have an underscore (_) in their content, and ending tied notes has a closing square bracket (])).

    #include "humdrum.h"
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       double histogram[12] = {0};
       char buffer[1024] = {0};
       int midikey;
       int i;
       for (i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue; // ignore non-data lines
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
             if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
             int count = hfile[i].getTokenCount(j);
             for (int k=0; k<count; k++) {
                hfile[i].getToken(buffer, j, k);
                if (strchr(buffer, 'r') != NULL) continue; // ignore rests
                midikey = Convert::kernToMidiNoteNumber(buffer);
                histogram[midikey % 12]++;
             }
          }
       }
       for (i=0; i<12; i++) {
          std::cout << i << "\t" << histogram[i] << std::endl;
       }
       return 0;
    }
    

Example output when processing a real piece of music is given below. The first output line means there are 600 C notes in Beethoven's 32nd sonata, mvmt. 1, 233 Cs/Ds, etc.

    notehist h://beethoven/sonatas/sonata32-1.krn
    0	600 
    1	233
    2	279
    3	476
    4	146
    5	513
    6	144
    7	636
    8	459
    9	121
    10	259
    11	230
    

To sort the pitch classes by how often they occur:

    notehist h://beethoven/sonatas/sonata32-1.krn | sort -nrk2
    7	636
    0	600
    5	513
    3	476
    8	459
    2	279
    10	259
    1	233
    11	230 
    4	146
    6	144
    9	121
    

In this case the most common pitch class is G (7), and the least common pitch class is A (9). Note that the sorting did not have to be implemented in the C++ program, since the command-line program sort could be utilized. The options to sort are -n (sort numerically), -r (sort in reverse order so that the largest number comes first), and -k2 (sort by the second column of data).

Here is a modification of the program so that pitch names rather than pitch-class numbers are displayed:

    #include "humdrum.h"
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       double histogram[12] = {0};
       char buffer[1024] = {0};
       int midikey;
       int i;
       for (i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue; // ignore non-data lines
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
             if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
             int count = hfile[i].getTokenCount(j);
             for (int k=0; k<count; k++) {
                hfile[i].getToken(buffer, j, k);
                if (strchr(buffer, 'r') != NULL) continue; // ignore rests
                midikey = Convert::kernToMidiNoteNumber(buffer);
                histogram[midikey % 12]++;
             }
          }
       }
       for (i=0; i<12; i++) {
          std::cout << i << "\t" 
                    << Convert::base12ToKern(buffer, histogram[i] + 4 * 12) << std::endl;
       }
       return 0;
    }
    

So now the note names will be printed instead of their numeric equivalent:

    notehist h://beethoven/sonatas/sonata32-1.krn | sort -nrk2
    G	636
    C	600  
    F	513
    E-	476
    G#	459
    D	279
    B-	259
    C#	233  
    B	230 
    E	146
    F#	144 
    A	121
    

If you want to preserve the accidental spellings, then you can use base-40 instead of base-12 (MIDI note numbers):

    #include "humdrum.h"
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       double histogram[40] = {0};
       char buffer[1024] = {0};
       int base40;
       int i;
       for (i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue; // ignore non-data lines
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
             if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
             int count = hfile[i].getTokenCount(j);
             for (int k=0; k<count; k++) {
                hfile[i].getToken(buffer, j, k);
                if (strchr(buffer, 'r') != NULL) continue; // ignore rests
                base40 = Convert::kernToBase40(buffer);
                histogram[base40 % 40]++;
             }
          }
       }
       for (i=0; i<40; i++) {
          if (histogram[i] == 0) { continue; }
          std::cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
                    << histogram[i] << std::endl;
       }
       return 0;
    }
    

With the more verbose pitch information being:

    notehist h://beethoven/sonatas/sonata32-1.krn
    C-	32
    C	600
    C#	1
    D-	232
    D	271
    D#	1
    E--	8
    E-	475
    E	134
    E#	2
    F-	12
    F	511
    F#	83
    G-	61
    G	636
    G#	2
    A-	457
    A	121
    B-	259
    B	198
    

Standard Template Library classes such as vector can be used instead of the C-like histogram array used in the previous program. Here is an example using the vector class:

    #include "humdrum.h"
    #include <vector>
    using namespace std;
    
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       vector<int> histogram(40);
       char buffer[1024] = {0};
       int base40;
       unsigned int i;
       for (i=0; i<(unsigned int)hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue; // ignore non-data lines
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
             if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
             int count = hfile[i].getTokenCount(j);
             for (int k=0; k<count; k++) {
                hfile[i].getToken(buffer, j, k);
                if (strchr(buffer, 'r') != NULL) continue; // ignore rests
                base40 = Convert::kernToBase40(buffer);
                histogram[base40 % 40]++;
             }
          }
       }
       for (i=0; i<histogram.size(); i++) {
          if (histogram[i] == 0) { continue; }
          cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
                    << histogram[i] << endl;
       }
       return 0;
    }
    

In addition, Humdrum Extras has an Array class which has a similar functionality as the vector class or C arrays:

    #include "humdrum.h"
    using namespace std;
    
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       Array<int> histogram(40);
       histogram.setAll(0);
       char buffer[1024] = {0};
       int base40;
       int i;
       for (i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue; // ignore non-data lines
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
             if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
             int count = hfile[i].getTokenCount(j);
             for (int k=0; k<count; k++) {
                hfile[i].getToken(buffer, j, k);
                if (strchr(buffer, 'r') != NULL) continue; // ignore rests
                base40 = Convert::kernToBase40(buffer);
                histogram[base40 % 40]++;
             }
          }
       }
       for (i=0; i<histogram.getSize(); i++) {
          if (histogram[i] == 0) { continue; }
          cout << Convert::base40ToKern(buffer, i + 3*40) << "\t"
                    << histogram[i] << endl;
       }
       return 0;
    }
    

Duration-weighted note histogram

Often it is useful to know how long a certain pitch class sounds in a musical work rather than just how many note attacks for each pitch class. Here is a program which measures the duration of each pitch class in the music:

    #include "humdrum.h"
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       Array<double> histogram(12);
       histogram.setAll(0);
       histogram.allowGrowth(0);
       char buffer[1024] = {0};
       double duration;
       int midikey;
       int i;
       for (i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue; // ignore non-data lines
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             if (strcmp("**kern", hfile[i].getExInterp(j)) != 0) continue;
             if (strcmp(".", hfile[i][j]) == 0) continue; // ignore null tokens
             int count = hfile[i].getTokenCount(j);
             for (int k=0; k<count; k++) {
                hfile[i].getToken(buffer, j, k);
                if (strchr(buffer, 'r') != NULL) continue; // ignore rests
                midikey = Convert::kernToMidiNoteNumber(buffer);
                duration = Convert::kernToDuration(buffer);
                histogram[midikey % 12] += duration;
             }
          }
       }
       for (i=0; i<histogram.getSize(); i++) {
          std::cout << Convert::base12ToKern(buffer, i+4*12) << "\t"
                    << histogram[i] << std::endl;
       }
       return 0;
    }
    


Now the program will output the duration in quarter notes for each pitch class in the music:

    C	319.556
    C#	98.6833 
    D	137.644
    E-	208.4
    E	84.1444
    F	225.158
    F#	75.5278
    G	308.772
    G#	203.953
    A	61.2111
    B-	121.267
    B	125.767
    




Rhythm parsing

Humdrum files containing **kern data can be rhythmically parsed using the HumdrumFile::analyzeRhythm() function. The **kern data is expected to contain rhythmic data which is consistent with the layout of the music on each line in the file. This function will store rhythmic information for each line in the file, such as the current beat in the measure, the number of beats since the start of the file, and the duration of each line (composite rhythm).

Absolute beat

The HumdrumRecord::getAbsBeat() function returns the number of quarter notes (by default) from the beginning of the file to the current line. Below is an example program which prints the "absolute beat" data for each line of a Humdrum file. Note that lines of any type (data, comments, interpretations) can be queried for their position in the score. In this example the argument "4" is given to .analyzeRhythm(). This means to use the quarter note as the basis for calculating rhythmic values for the score.

    #include "humdrum.h"
    
    void printRhythm(HumdrumFile& infile) {
       for (int i=0; i<infile.getNumLines(); i++) {
          cout << infile[i].getAbsBeat() << '\t' << infile[i] << '\n';
       }
    }
    
    void processFile(HumdrumFile& infile) {
       infile.analyzeRhythm("4");
       printRhythm(infile);
    }
    
    int main(int argc, char** argv) {
       Options options(argc, argv);
       HumdrumFileSet infiles;
       options.process();
       infiles.read(options);
    
       for (int i=0; i<infiles.getCount(); i++) {
          processFile(infiles[i]);
          if (i<infiles.getCount() - 1) {
             cout << '\n';
          }
       }
    
       return 0;
    };
    

Here is example input/output from the above program. Each line in the output starts with the "absolute beat" value for the line, followed by the input line. Note that the example input contains two measures of 4/4 meter, so the total duration of the score is 8 quarter notes. This matches to the "absolute beat" position of the last line in the file.

    **kern
    *M4/4
    =1-
    4c
    4.d
    8e
    8fL
    8gJ
    =2
    12aL
    12b
    12ccJ
    20dd
    10ee
    20dd
    10ee
    20dd
    10ee
    20dd
    4cc
    ==
    *-
    
    0	**kern
    0	*M4/4
    0	=1-
    0	4c
    1	4.d
    2.5	8e
    3	8fL
    3.5	8gJ
    4	=2
    4	12aL
    4.33333	12b
    4.66667	12ccJ
    5	20dd
    5.2	10ee
    5.6	20dd
    5.8	10ee
    6.2	20dd
    6.4	10ee
    6.8	20dd
    7	4cc
    8	==
    8	*-
    


In the above example program, absolute beat positions for each line were printed as floating-point numbers. Notice that for the triplet eighth notes starting in measure 2, round-off error occurs in 10-5 decimal place. If you require rhythmic information with no round-ff error, use the .getAbsBeatR() function rather than .getAbsBeat(). The "R" stands for "Rational number". The return value from "R" versions of rhythm functions return a data type called RationalNumber which can represent fractional values. A triplet eighth is exactly 1/3 of a quarter note which is approximated by the floating-point value 0.33333. The RationalNumber class handles such fractions by storing the numerator and denominator as separate integer values to avoid introducing round-off error.

Try replacing the printRhythm() function in the above example with this one which substitutes .getAbsBeat() with .getAbsBeatR():

    void printRhythm(HumdrumFile& infile) {
       for (int i=0; i<infile.getNumLines(); i++) {
          cout << infile[i].getAbsBeatR() << '\t' << infile[i] << '\n';
       }
    }
    

Now the output of the program describes the rhythms in terms of fractions rather than floating-point values:

    **kern
    *M4/4
    =1-
    4c
    4.d
    8e
    8fL
    8gJ
    =2
    12aL
    12b
    12ccJ
    20dd
    10ee
    20dd
    10ee
    20dd
    10ee
    20dd
    4cc
    ==
    *-
    
    0	**kern
    0	*M4/4
    0	=1-
    0	4c
    1	4.d
    5/2	8e
    3	8fL
    7/2	8gJ
    4	=2
    4	12aL
    13/3	12b
    14/3	12ccJ
    5	20dd
    26/5	10ee
    28/5	20dd
    29/5	10ee
    31/5	20dd
    32/5	10ee
    34/5	20dd
    7	4cc
    8	==
    8	*-
    

This is now quite readable, so printing and integer plus a fractional remainder for each absolute beat position would be more convenient:

    void printRhythm(HumdrumFile& infile) {
       RationalNumber absbeat;
       for (int i=0; i<infile.getNumLines(); i++) {
          absbeat = infile[i].getAbsBeatR();
          absbeat.printTwoPart(cout);
          cout << '\t' << infile[i] << '\n';
       }
    }
    

Now the output is more readable with numbers such as 31/5 replaced with 6+1/5:

    **kern
    *M4/4
    =1-
    4c
    4.d
    8e
    8fL
    8gJ
    =2
    12aL
    12b
    12ccJ
    20dd
    10ee
    20dd
    10ee
    20dd
    10ee
    20dd
    4cc
    ==
    *-
    
    0	**kern
    0	*M4/4
    0	=1-
    0	4c
    1	4.d
    2+1/2	8e
    3	8fL
    3+1/2	8gJ
    4	=2
    4	12aL
    4+1/3	12b
    4+2/3	12ccJ
    5	20dd
    5+1/5	10ee
    5+3/5	20dd
    5+4/5	10ee
    6+1/5	20dd
    6+2/5	10ee
    6+4/5	20dd
    7	4cc
    8	==
    8	*-
    

Line duration

The line duration, or composite rhythm, of a Humdrum file can be extracted from the rhythmic analysis results by using the HumdrumRecord::getDuration() and getDurationR() functions. The getDuration() function returns a double float, while getDurationR() returns a RationalNumber type. The floating point version will contain round-off errors while the RationalNumber type will not, so you can use the different methods depending on your particular application requirements. Floating point values can also be extracted from RationalNumber types with the RationalNumber::getFloat() function.

Here is an example program which prints the duration of each line in a Humdrum file:

    #include "humdrum.h"
    
    void printRhythm(HumdrumFile& infile) {
       RationalNumber linedur;
       for (int i=0; i<infile.getNumLines(); i++) {
          linedur = infile[i].getDurationR();
          linedur.printTwoPart(cout);
          cout << '\t' << infile[i] << '\n';
       }
    }
    
    void processFile(HumdrumFile& infile) {
       infile.analyzeRhythm("4");
       printRhythm(infile);
    }
    
    int main(int argc, char** argv) {
       Options options(argc, argv);
       HumdrumFileSet infiles;
       options.process();
       infiles.read(options);
    
       for (int i=0; i<infiles.getCount(); i++) {
          processFile(infiles[i]);
          if (i<infiles.getCount() - 1) {
             cout << '\n';
          }
       }
    
       return 0;
    }
    

This program outputs the duration of each line in quarter-note units:

    **kern	**kern
    *M3/4	*M3/4
    =-	=-
    2c	2cc
    8d	12dd
    .	12ee
    8e	.
    .	12ff
    =2	=2
    12f	8gg
    12g	.
    .	8aa
    12a	.
    2b	2bb
    ==	==
    *-	*-
    
    0	**kern	**kern
    0	*M3/4	*M3/4
    0	=-	=-
    2	2c	2cc
    1/3	8d	12dd
    1/6	.	12ee
    1/6	8e	.
    1/3	.	12ff
    0	=2	=2
    1/3	12f	8gg
    1/6	12g	.
    1/6	.	8aa
    1/3	12a	.
    2	2b	2bb
    0	==	==
    0	*-	*-
    

Notice that non-data lines are assigned a duration of 0. Also note that the line ". 12ee" has a line duration of 1/6th of a quarter note. This is smaller than the rhythm 12 which is 1/3 of a quarter note due to the 8th note in the first column having its duration split across two lines.

If you want the durations to represent **recip Humdrum data (**kern rhythm values), then either divide the RationalValue duration by 4, or use whole notes when analyzing the rhythmic structure of the file with .analyzeRhythm("1") instead of using "4" as its argument.

Here is an example program which calculates the composite rhythm of the file and prepends a **recip spine to the original data:

    #include "humdrum.h"
    
    void processFile(HumdrumFile& infile) {
       infile.analyzeRhythm("4");
       RationalNumber linedur;
       for (int i=0; i<infile.getNumLines(); i++) {
          if (infile[i].isData()) {
             linedur = infile[i].getDurationR();
             cout << linedur << '\t';
             linedur /= 4;
             linedur.printRecip(cout);
             cout << '\t' << infile[i] << '\n';
          } else if (infile[i].isMeasure()) {
             cout << infile[i][0] << '\t' <<infile[i][0];
             cout << '\t' << infile[i] << '\n';
          } else if (strncmp(infile[i][0], "**", 2) == 0) {
             cout << "**dur\t**recip\t" << infile[i] << '\n';
          } else if (strcmp(infile[i][0], "*-") == 0) {
             cout << "*-\t*-\t" << infile[i] << '\n';
          } else if (infile[i].isInterpretation()) {
             cout << "*\t*" << '\t' << infile[i] << '\n';
          } else if (infile[i].isLocalComment()) {
             cout << "!\t!" << '\t' << infile[i] << '\n';
          } else {
             cout << infile[i] << '\n';
          }
       }
    }
    
    int main(int argc, char** argv) {
       Options options(argc, argv);
       HumdrumFileSet infiles;
       options.process();
       infiles.read(options);
    
       for (int i=0; i<infiles.getCount(); i++) {
          processFile(infiles[i]);
          if (i<infiles.getCount() - 1) {
             cout << '\n';
          }
       }
    
       return 0;
    }
    

Here is the input and output for the composite rhythm program listed above. The HumdrumRecord::getDurationR() information was printed in the first column. The second column gives the Humdrum **recip representation of the duration as a **kern rhythmic value. The **recip value is the inverse of the duration, multiplied by 4 in this case to represent inverse divisions of whole notes rather than quarter notes.

    **kern	**kern
    *M3/4	*M3/4
    =-	=-
    2c	2cc
    8d	12dd
    .	12ee
    8e	.
    .	12ff
    =2	=2
    12f	8gg
    12g	.
    .	8aa
    12a	.
    2b	2bb
    ==	==
    *-	*-
    
    **dur	**recip	**kern	**kern
    *	*	*M3/4	*M3/4
    =-	=-	=-	=-
    2	2	2c	2cc
    1/3	12	8d	12dd
    1/6	24	.	12ee
    1/6	24	8e	.
    1/3	12	.	12ff
    =2	=2	=2	=2
    1/3	12	12f	8gg
    1/6	24	12g	.
    1/6	24	.	8aa
    1/3	12	12a	.
    2	2	2b	2bb
    ==	==	==	==
    *-	*-	*-	*-
    

Line field enumeration by spine

The HumdrumRecord::getFieldCount() function returns the number of tokens on each Humdrum line, and each of these fields is accessed by an index number up to this count. However, if you want to access data by spine number (or to be clear primary spine number), you have to use a different method. The field number and spine number do not always match because spines can split into subspines which will increase the field count on a line.

To access data by spine, use the HumdrumRecord::getPrimaryTrack() function. This function returns an integer value for the current spine, enumerated from one (sorry). Multiple fields can have the same primary track number, which is caused by a spine split in the data.

Here is a program which prints the primary track for each data token in a Humdrum file structure:

    #include "humdrum.h"
    using namespace std;
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) {
             cout << hfile[i] << endl;
             continue;
          }
          cout << hfile[i].getPrimaryTrack(0);
          for (int j=1; j<hfile[i].getFieldCount(); j++) {
             cout << '\t' << hfile[i].getPrimaryTrack(j);
          }
          cout << endl;
       }
       return 0;
    }
    

And here is example input and output:

    **a	**b	**c
    .	.	.
    .	.	.
    *	*^	*^
    .	.	.	.	.
    .	.	.	.	.
    *	*v	*v	*	*
    *	*	*v	*v
    .	.	.
    .	.	.
    *-	*-	*-
    
    **a	**b	**c
    1	2	3
    1	2	3
    *	*^	*^
    1	2	2	3	3
    1	2	2	3	3
    *	*v	*v	*	*
    *	*	*v	*v
    1	2	3
    1	2	3
    *-	*-	*-
    

Notice that the input data contains three exclusive interpretations. This will mean that there are three primary tracks (or spines) in the data. In the output you can see the numbers for each track: 1, 2, and 3. The HumdrumRecord::getMaxTrack() function can be called to find out what the maximum track number is (3 in this case).

The primary track value is an integer. If you also want to see the subtrack information, use .getTrack() instead of .getPrimaryTrack(). The .getTrack() function will have the primary track in the integer portion of a double float, and the enumerated subtrack in the fractional value. The subtrack value uses the first three digits of the fraction, so you can extract the subtrack by removing the integer part of the number and multiplying by 1000. Note that the subtrack enumeration starts at 0, while the primary tracks are enumerated from 1 (sorry again).

    #include "humdrum.h"
    using namespace std;
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) {
             cout << hfile[i] << endl;
             continue;
          }
          cout << hfile[i].getPrimaryTrack(0);
          for (int j=1; j<hfile[i].getFieldCount(); j++) {
             cout << '\t' << hfile[i].getTrack(j);
          }
          cout << endl;
       }
       return 0;
    }
    
    **a	**b	**c
    .	.	.
    .	.	.
    *	*^	*^
    .	.	.	.	.
    .	.	.	.	.
    *	*v	*v	*	*
    *	*	*v	*v
    .	.	.
    .	.	.
    *-	*-	*-
    
    **a	**b	**c
    1	2	3
    1	2	3
    *	*^	*^
    1	2	2.001	3	3.001
    1	2	2.001	3	3.001
    *	*v	*v	*	*
    *	*	*v	*v
    1	2	3
    1	2	3
    *-	*-	*-
    

Even more detail about track information can be accessed with the .getSpineInfo() function. This function returns the internally stored string which keeps track of how the spine/subspine was manipulated on previous lines of the data.

    #include "humdrum.h"
    using namespace std;
    int main(int argc, char** argv) {
       Options options(argc, argv);
       options.process();
       HumdrumFile hfile;
       hfile.read(options.getArg(1));
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) {
             cout << hfile[i] << endl;
             continue;
          }
          cout << hfile[i].getPrimaryTrack(0);
          for (int j=1; j<hfile[i].getFieldCount(); j++) {
             cout << '\t' << hfile[i].getSpineInfo(j);
          }
          cout << endl;
       }
       return 0;
    }
    
    **a	**b	**c
    .	.	.
    .	.	.
    *	*^	*^
    .	.	.	.	.
    .	.	.	.	.
    *	*v	*v	*	*
    *	*	*v	*v
    .	.	.
    .	.	.
    *-	*-	*-
    
    **a	**b	**c
    1	2	3
    1	2	3
    *	*^	*^
    1	(2)a	(2)b	(3)a	(3)b
    1	(2)a	(2)b	(3)a	(3)b
    *	*v	*v	*	*
    *	*	*v	*v
    1	2	3
    1	2	3
    *-	*-	*-
    

In this case the spine info "(2)a" means that that token is in primary spine 2, but the spine was split once, and this subtrack is the left-hand subspine coming out of the split. The .getPrimaryTrack() function returns the first number in the .getSpineInfo() string.

Here is a more complex spine manipulation:

    **a	**b	**c
    .	.	.
    *	*^	*^
    .	.	.	.	.
    *	*	*^	*	*
    .	.	.	.	.	.
    *	*	*v	*v	*	*
    *	*v	*v	*	*
    .	.	.	.
    *	*	*v	*v
    .	.	.
    *-	*-	*-
    
    **a	**b	**c
    1	2	3
    *	*^	*^
    1	(2)a	(2)b	(3)a	(3)b
    *	*	*^	*	*
    1	(2)a	((2)b)a	((2)b)b	(3)a	(3)b
    *	*	*v	*v	*	*
    *	*v	*v	*	*
    1	2	(3)a	(3)b
    *	*	*v	*v
    1	2	3
    *-	*-	*-
    




Spine manipulator examples

Here is an example of spine splits (*v) and joins (*v):

    **a	**b	**c
    .	.	.
    *	*^	*
    .	.	.	.
    *	*	*^	*
    .	.	.	.	.
    *	*v	*v	*v	*
    .	.	.
    *-	*-	*-
    
    **a	**b	**c
    1	2	3
    *	*^	*
    1	(2)a	(2)b	3
    *	*	*^	*
    1	(2)a	((2)b)a	((2)b)b	3
    *	*v	*v	*v	*
    1	2	3
    *-	*-	*-
    

Here is an example of spine additions (*+) and terminations (*-):

    **a	**b
    .	.
    *
    *+	**c
    .	.	.
    *	*-	*
    .	.
    *-	*-
    
    **a	**b
    1	2
    *	*+	**c
    1	2	3
    *	*-	*
    1	3
    *-	*-
    

Here is an example of spine exchanges (*x):

    **a	**b
    .	.
    .	.
    *x	*x
    .	.
    .	.
    *-	*-
    
    **a	**b
    1	2
    1	2
    *x	*x
    2	1
    2	1
    *-	*-
    

And finally a complex example using all of the spine manipulators:

    **a	**b
    .	.
    *	*^
    .	.	.
    *+	**c	*
    .	.	.	.
    *	*	*x	*x
    .	.	.	.
    *	*	*^	*
    .	.	.	.	.
    *	*+	**d	*	*
    .	.	.	.	.	.
    *	*	*	*	*x	*x
    .	.	.	.	.	.
    *	*-	*	*	*	*
    .	.	.	.	.
    *v	*v	*	*	*
    .	.	.	.
    *	*v	*v	*
    .	.	.
    *	*v	*v
    .	.
    *-	*-
    
    **a     **b
    1       2
    *       *^
    1       (2)a    (2)b
    *+      **c     *
    1       3       (2)a    (2)b
    *       *       *x      *x
    1       3       (2)b    (2)a
    *       *       *^      *
    1       3       ((2)b)a ((2)b)b (2)a
    *       *+      **d     *       *
    1       3       4       ((2)b)a ((2)b)b (2)a
    *       *       *       *       *x      *x
    1       3       4       ((2)b)a (2)a    ((2)b)b
    *       *-      *       *       *       *
    1       4       ((2)b)a (2)a    ((2)b)b
    *v      *v      *       *       *
    1       4       ((2)b)a (2)a    ((2)b)b
    *       *v      *v      *
    1       4       ((2)b)a (2)a    ((2)b)b
    *       *v      *v
    1       4       2
    *-      *-
    




myextract.cpp

Now that you know how to extract information about spines and subspines, you can write your own version of the Humdrum Toolkit's extract program. It will be even more powerful than the extract program, since the extract program cannot deal with subspines without special processing. Here is the myextract code:

    #include "humdrum.h"
    using namespace std;
    
    void extract(HumdrumFile& hfile, int primarytrack) {
       int i, j, fcount, pcount;
       for (i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].hasSpines()) {
             cout << hfile[i] << endl;
             continue;
          }
          fcount = hfile[i].getFieldCount();
          pcount = 0;
          for (j=0; j<fcount; j++) {
             if (primarytrack == hfile[i].getPrimaryTrack(j)) {
                if (pcount++ > 0) cout << '\t';
                cout << hfile[i][j];
             }
          }
          if (pcount > 0) cout << endl;
       }
    }
    
    int main (int argc, char** argv) {
       Options opts;
       opts.define("t|track=i:1", "extract specified track");
       opts.process(argc, argv);
       int primarytrack = opts.getInteger("track");
       int numinputs = opts.getArgCount();
       HumdrumFile hfile;
       for (int i=1; i<=numinputs || i==0; i++) {
          if (numinputs < 1) { hfile.read(std::cin); }
          else { hfile.read(opts.getArg(i)); }
          extract(hfile, primarytrack);
       }
       return 0;
    }
    
    input
    myextract -t 2
    **a	**b	**c
    a	b	c
    *	*^	*
    a	b1	b2	c
    *	*v	*v	*
    a	b	c
    *-	*-	*-
    
    **b
    b
    *^
    b1	b2
    *v	*v
    b
    *-
    

Notice that all of the "B" information which was in spine 2 was extracted from the input data.

Regular Expressions

Regular expressions are an important concept to understand when working with Humdrum data, since the data format was designed to take advantage of them.

GNU POSIX regular expressions

Different Operating systems have different C implementations of regular expressions. Here is an example of how to use them on most linux operating systems using the GNU POSIX regular expressions:

    #include <regex.h>
    #include <stdlib.h>
    #include <stdio.h>
    using namespace std;
    int main(int argc, char** argv) {
       if (argc < 3) exit(1);
       const char *searchstring = argv[1];
       const char *datastring = argv[2];
       regex_t re;
       int flags = 0 | REG_EXTENDED | REG_ICASE;
       int status = regcomp(&re, searchstring, flags);
       if (status !=0) {
          char errstring[999];
          regerror(status, &re, errstring, 999);
          printf("%s\n", errstring);
          exit(1);
       }
       status = regexec(&re, datastring, 0, NULL, 0);
       if (status == 0) printf("Match Found\n");
       else printf("Match Not Found\n");
       return 0;
    }
    

Example behavior of the program:

    search cat "cat in the hat"
    Match Found
       
    search dog "cat in the hat"
    Match Not Found
    

For the simple examples above the strstr() C library function could have been used (which would probably also run faster). But using regular expressions allows for more powerful generalized searching, such as looking for upper and lower case matches:

    search cat "Cat in the hat"
    Match Found
    

In this case "cat" was matched to "Cat" since the REG_ICASE flag was used to ignore difference between upper and lower letter cases. The REG_EXTENDED flag is for using extended regular expressions (regular expressions 2.0). The regexec() function returns 0 if a match was found, otherwise returns a non-zero value to indicate no match was found.


mysed.c (Search and replace)

The following program demonstrates how to do search and replace on strings with GNU POSIX regular expressions.

    #include <regex.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    int main(int argc, char** argv) {
       if (argc < 4) exit(1);
       char buffer[1024] = {0};
       const char *searchstring = argv[1];
       const char *replacestring = argv[2];
       const char *datastring = argv[3];
       regex_t re;
       regmatch_t match;
       int compflags = 0 | REG_EXTENDED | REG_ICASE;
       int status = regcomp(&re, searchstring, compflags);
       if (status !=0) {
          regerror(status, &re, buffer, 1024);
          printf("%s\n", buffer);
          exit(1);
       }
       status = regexec(&re, datastring, 1, &match , 0);
       while (status == 0) {
          strncat(buffer, datastring, match.rm_so);
          strcat(buffer, replacestring);
          datastring += match.rm_eo;
          status = regexec(&re, datastring, 1, &match, REG_NOTBOL);
       }
       printf("%s%s\n", buffer, datastring);
       return 0;
    }
    

Example use:

    mysed klm 000 abcdefghijklmnopqrstuvwxyz
    abcdefghij000nopqrstuvwxyz
    

Perl Compatible Regular Expressions

The Humdrum Extras library includes the Perl Compatible Regular Expressions (PCRE) library which is more portable than GNU POSIX regular expressions, and also more powerful as it implements extensions to regular expressions which are present in the Perl language. The Humdrum Extras library also includes a C++ wrapper class for PCRE which allows for a simpler interface. Below are programs similar to the GNU POSIX regular expressions found above.

    #include "PerlRegularExpression.h"
    #include <iostream>
    using namespace std;
    
    int main(int argc, char** argv) {
       if (argc < 3) exit(1);
       const char *searchstring = argv[1];
       const char *datastring = argv[2];
       PerlRegularExpression pre;
       if (pre.search(datastring, searchstring, "i")) {
          cout << "Match Found" << endl;
       } else {
          cout << "Match Not Found" << endl;
       }
       return 0;
    }
    

The PerlRegularExpression class definition must be included with PerlRegularExpression.h since humdrum.h or any of the files it includes does not depend on the PerlRegularExpression class. The .search() function returns true if a match was found, or false otherwise. When a match is found, the index location of the string plus one is the return value. By default, a PerlRegularExpression variable will use extended regular expressions, and the "i" used as the third parameter in the .search() function is used to set the ignore-case flag.

Example behavior of the program:

    search cat "cat in the hat"
    Match Found
       
    search dog "cat in the hat"
    Match Not Found
    

mysed.cpp (Search and replace)

The following program demonstrates how to do search and replace on strings with the PerlRegularExpression class.

    #include "PerlRegularExpression.h"
    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char** argv) {
       if (argc < 4) exit(1);
       const char *searchstring = argv[1];
       const char *replacestring = argv[2];
       Array<char> data;
       data = argv[3];
       PerlRegularExpression pre;
       pre.sar(data, searchstring, replacestring, "gi");
       cout << data << endl;
       return 0;
    }
    

Example use:

    mysed klm 000 abcdefghijklmnopqrstuvwxyz
    abcdefghij000nopqrstuvwxyz
    

The .sar() function (or .searchAndReplace() as the long form) takes four parameters: (1) the string to perform the replacement, (2) the search string, (3) the replacement string, and (4) the regular expression flags. In this case "gi" represents two flags: "g" for a global replacement (not just the first match on the line) and "i" as before which means to ignore case.

Editing a HumdrumFile

When accessing the contents of a HumdrumFile/HumdrumReccords with [][] operators, the value of each spine (or global commend/reference record) is a const char*. In order to change a value, use the HumdrumRecord::setToken() fuction.

erase.cpp (Set all data fields to the null token)

Here is a program which uses .setToken() to change the contents of all data fields into null tokens.

    #include "humdrum.h"
    using namespace std;
    
    void setData(HumdrumFile& hfile, const char* replacement) {
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) { continue; }
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             hfile[i].setToken(j, replacement);
          }
       }
    }
    
    int main(int argc, char** argv) {
       Options opts;
       opts.process(argc, argv);
       HumdrumFile hfile;
       for (int i=1; i<=opts.getArgCount() || i == 0; i++) {
          if (i == 0) { hfile.read(cin); }
          else { hfile.read(opts.getArg(i)); }
          setData(hfile, ".");
          cout << hfile;
       }
       return 0;
    }
    
    **kern	**text	**kern
    4C	ig-	4c
    4D 4E	-no-	.
    4F	-red	.
    .	.	4d 4e
    4r	.	.
    4G 4A 4B	text	.
    *-	*-	*-
    
    **kern	**text	**kern
    .	.	.
    .	.	.
    .	.	.
    .	.	.
    .	.	.
    .	.	.
    *-	*-	*-
    

transpose.cpp (Transpose music)

The following example program processes each **kern note by transposing it by a base40 interval. The transposed note is reinserted into the original HumdrumFile structure which is then printed to standard output. The main() function is used to handle multiple input files, transposeFile() is used to search through each file and pull out notes to transpose, and transposeNote() is used to replace the original pitch with the transposed version.

    #include "humdrum.h"
    #include "PerlRegularExpression.h"
    using namespace std;
    
    int debugQ = 0;
    
    char* transposeNote(Array<char>& subtoken, int transpose) {
       int base40 = Convert::kernToBase40(subtoken.getBase());
       if (base40 <= 0) { return subtoken.getBase(); }
       char newnote[1024] = {0};
       Convert::base40ToKern(newnote, base40 + transpose);
       if (debugQ) { cout << "!! transposing " << subtoken; }
       PerlRegularExpression pre;
       pre.sar(subtoken, "[a-g]+[-#n]*", newnote, "i");
       if (debugQ) { cout << "\tto\t" << subtoken << endl; }
       return subtoken.getBase();
    }
    
    HumdrumFile& transposeFile(HumdrumFile& hfile, int transpose) {
       Array<char> subtoken;   // for extracting note from chord
       char obuf[1024] = {0};  // output token buffer
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) { continue; }
          for (int j=0; j<hfile[i].getFieldCount(); j++) {
             if ((!hfile[i].isExInterp(j, "**kern")) ||
                   (strcmp(".", hfile[i][j]) == 0)) {
                continue;
             }
             int tcount = hfile[i].getTokenCount(j);
             strcpy(obuf, "");
             for (int k=0; k<tcount; k++) {
                hfile[i].getToken(subtoken, j, k);
                if (k > 0) { strcat(obuf, " "); }
                strcat(obuf, transposeNote(subtoken, transpose));
             }
             hfile[i].setToken(j, obuf);
          }
       }
       return hfile;
    }
    
    int main(int argc, char** argv) {
       Options opts;
       opts.define("t|transpose=i:0", "transpose by base-40 interval");
       opts.define("debug=b",         "print debugging statements");
       opts.process(argc, argv);
       debugQ = opts.getBoolean("debug");
       int transpose = opts.getInteger("transpose");
       int numinputs = opts.getArgCount();
       HumdrumFile hfile;
       for (int i=1; i<=numinputs || i==0; i++) {
          if (numinputs < 1) { hfile.read(std::cin);  }
          else { hfile.read(opts.getArg(i)); }
          cout << transposeFile(hfile, transpose);
       }
       return 0;
    }
    

Random melody generator

The following program will generate a random melody, both in pitch and rhythm:

    #include "humdrum.h"
    #include <stdlib.h>     /* for drand48 random numbers */
    #include <time.h>       /* for time(NULL) function */
    
    void printRandomMelody(int notecount, int seed) {
       cout << "!!!seed:\t" << seed << endl;
       cout << "**kern\n";
       int pitch, rhythm;
       char buffer[1024] = {0};
       for (int i=0; i<notecount; i++) {
          rhythm = int(drand48() * 16 + 1 + 0.5);
          pitch  = int(drand48() * 24 + 12*4.5 + 3);
          cout << rhythm << Convert::base12ToKern(buffer, pitch) << endl;
       }
       cout << "*-\n";
    }
    
    int main(int argc, char** argv) {
       Options options;
       options.define("c|count=i:20", "number of notes to generate");
       options.define("s|seed=i:-1",  "random number generator seed");
       options.process(argc, argv);
       int seed = options.getInteger("seed");
       if (seed < 0) {
          seed = time(NULL);  // time in seconds since 1 Jan 1970
       }
       srand48(seed);
       printRandomMelody(options.getInteger("count"), seed);
       return 0;
    }
    

Here is some example output:

    !!!seed: 1241137496
    **kern
    2d
    9ee-
    2a
    13g
    15B-
    16f#
    7A
    16B
    17gg
    1d
    11cc
    14c#
    10d
    11ee
    12f#
    13c#
    8cc
    14b-
    3f
    17a
    *-
    

The -s option can be used to generate a fixed random melody; otherwise, the random melody will be based on the current time in seconds since 1 Jan 1970. The -c option can be used to specify the number of notes in the melody. Notice that if no melody length is given, the default length of 20 note will be used.

To display as graphical music notation:

    bin/randomel -s 1241137496 \
       | hum2abc -M none | abcm2ps - -O - \
       | convert -quality 100 -density 300 - -trim -resize '33%' output.png
    
RandomMelody.png

The Humdrum file data can be stored internally in a stream object. This stream object can be ready by a HumdrumFile object to be reprocessed within the program, or to be printed with ostream as a HumdrumFile object. The following example demonstrates this. Also, since strstream was changed to stringstream in ANSI C99, there are some preprocessor instructions to allow using string streams in both older and newer standards (including differences in Visual C++ 6).


    #include "humdrum.h"
    #include <stdlib.h>     /* for drand48 random numbers */
    #include <time.h>       /* for time(NULL) function */
    
    using namespace std;
    
    void getRandomMelody(HumdrumFile& datafile, int notecount, int seed) {
       stringstream output;
       output << "!!!seed:\t" << seed << endl;
       output << "**kern\n";
       int pitch, rhythm;
       char buffer[1024] = {0};
       for (int i=0; i<notecount; i++) {
          rhythm = int(drand48() * 16 + 1 + 0.5);
          pitch  = int(drand48() * 24 + 12*4.5 + 3);
          output << rhythm << Convert::base12ToKern(buffer, pitch) << endl;
       }
       output << "*-\n";
       output << ends;
       // It is possible to print to ostream:
       // cout << output.str().c_str() << flush;
       datafile.read(output);
    }
    
    int main(int argc, char** argv) {
       Options options;
       options.define("c|count=i:20", "number of notes to generate");
       options.define("s|seed=i:-1",  "random number generator seed");
       options.process(argc, argv);
       int seed = options.getInteger("seed");
       if (seed < 0) {
          seed = time(NULL);  // time in seconds since 1 Jan 1970
       }
       srand48(seed);
       HumdrumFile datafile;
       getRandomMelody(datafile, options.getInteger("count"), seed);
       cout << datafile;
       return 0;
    }
    

Markov melody analyzer/generator

Here is a more sophisticated random melody generator.

    #include "humdrum.h"
    #include <regex.h>
    #include <stdlib.h>
    #include <time.h>
    
    void buildTable(HumdrumFile& hfile, Array<Array<double> >& ptable,
          Array<Array<double> >& mtable) {
       int lastmeter = -1; int lastpitch = -1;
       int meter, pitch;
       hfile.analyzeRhythm();
       for (int i=0; i<hfile.getNumLines(); i++) {
          if (!hfile[i].isData()) continue;
          if (strcmp("**kern", hfile[i].getExInterp(0)) != 0) continue;
          if (strcmp(hfile[i][0], ".") == 0) continue;    // ignore null tokens
          if (strchr(hfile[i][0], 'r') != NULL) continue; // ignore rests
          pitch = Convert::kernToBase40(hfile[i][0]) % 40;
          meter = int((hfile[i].getBeat() - 1.0) * 4 + 0.5);
          if (meter < 0) meter = 0;
          if (meter >= 40) meter = 39;
          if (lastmeter < 0) {
             lastpitch = pitch; lastmeter = meter;
             continue;
          }
          mtable[lastmeter][meter]++;   mtable[lastmeter][40]++;
          ptable[lastpitch][pitch]++;   ptable[lastpitch][40]++;
          lastpitch = pitch;            lastmeter = meter;
       }
    }
    
    void printTables(Array<Array<double> >& ptable,
       Array<Array<double> >& mtable, int style) {
       int i, j;
       double value;
       char buffer[32] = {0};
       for (i=0; i<ptable.getSize(); i++) {
          cout << '\t' << Convert::base40ToKern(buffer, i+4*40);
       }
       cout << endl;
       for (i=0; i<ptable.getSize(); i++) {
          cout << Convert::base40ToKern(buffer, i+4*40);
          for (j=0; j<40; j++) {
             value = style ? ptable[i][j]/ptable[i][40] : ptable[i][j];
             cout << '\t' << value;
          }
          cout << '\t' << ptable[i][40] << endl;
       }
       cout << endl;
       for (i=0; i<mtable.getSize(); i++) cout << "\tb" << i/4.0 + 1.0;
       cout << endl;
       for (i=0; i<mtable.getSize(); i++) {
          cout << "b" << i/4.0 + 1.0;
          for (j=0; j<mtable[i].getSize(); j++) cout << '\t' << mtable[i][j];
          cout << endl;
       }
    }
    
    int chooseNextTransition(Array<Array<double> >& table, int state) {
       double target = drand48() * table[state][40];
       double sum = 0.0;
       for (int i=0; i<40; i++) {
          sum += table[state][i];
          if (sum > target)  return i;
       }
       return 39;
    }
    
    void smoothMelody(Array<double>& meldur, Array<int>& melpitch) {
       int beforei, afteri, inta, intb;
       for (int i=2; i<meldur.getSize()-2; i++) {
          if (meldur[i] < 0.0) continue;
          afteri  = i+1;   beforei = i-1;
          if (meldur[afteri] < 0.0) afteri++;
          if (meldur[beforei] < 0.0) beforei--;
          inta = melpitch[i] - melpitch[beforei];
          intb = melpitch[i] - melpitch[afteri];
          if ((inta > 22) && (intb > 22)) {
             melpitch[i] -= 40;
          } else if ((inta < -22) && (intb < -22)) {
             melpitch[i] += 40;
          }
       }
    }
    
    void generateMelody(Array<Array<double> >& ptable,
          Array<Array<double> >& mtable, int count) {
       int    pitch, pitchclass = 2, meter = 0, oldmeter = 0;
       int    i, measurenumber = 2;
       double duration, barmarker = -1;
       char   buffer[1024] = {0};
       Array<int> melpitch(count*2);  melpitch.setSize(0);
       Array<double> meldur(count*2); meldur.setSize(0);
       for (i=0; i<count; i++) {
          pitchclass  = chooseNextTransition(ptable, pitchclass);
          meter = chooseNextTransition(mtable, meter);
          if (meter > oldmeter) duration = (meter - oldmeter) / 4.0;
          else {
             duration = (4 + meter - oldmeter) / 4.0;
             meldur.append(barmarker);
             pitch = measurenumber++;
             melpitch.append(pitch);
          }
          oldmeter = meter;
          if (duration == 0.0) duration = 4.0;
          if (duration > 4.0)  duration = 4.0;
          if (duration < 0.0)  duration = 1.0;
          pitch = pitchclass + 4 * 40;
          meldur.append(duration);  melpitch.append(pitch);
       }
       smoothMelody(meldur, melpitch);
       cout << "**kern\n*M4/4\n=1-\n";
       for (i=0; i<meldur.getSize(); i++) {
          if (meldur[i] < 0.0) cout << "=" << melpitch[i] << endl;
          else {
             cout << Convert::durationToKernRhythm(buffer, meldur[i]);
             cout << Convert::base40ToKern(buffer, melpitch[i]);
             cout << endl;
          }
       }
       cout << "*-" << endl;
    }
    
    int main(int argc, char** argv) {
       Options options;
       options.define("t|table=b",       "display table of transitions");
       options.define("f|fraction=b",    "display transitions as fractions");
       options.define("g|generate=i:20", "generate specified number of notes");
       options.process(argc, argv);
       srand48(time(NULL)); HumdrumFile hfile;
       Array<Array<double> > ptable; // pitch transition table
                                   // (scale degrees would be musically better)
       Array<Array<double> > mtable; // meter transition table
       ptable.setSize(40); ptable.allowGrowth(0);
       mtable.setSize(40); mtable.allowGrowth(0);
       int i;
       for (i=0; i<ptable.getSize(); i++) {
          ptable[i].setSize(41); ptable[i].allowGrowth(0); ptable[i].setAll(0.0);
          mtable[i].setSize(41); mtable[i].allowGrowth(0); mtable[i].setAll(0.0);
       }
       int numinputs = options.getArgCount();
       for (i=1; i<=numinputs || i==0; i++) {
          if (numinputs < 1) hfile.read(std::cin);
          else hfile.read(options.getArg(i));
          buildTable(hfile, ptable, mtable);
       }
       if (options.getBoolean("table")) {
          printTables(ptable, mtable, options.getBoolean("fraction"));
       } else {
          generateMelody(ptable, mtable, options.getInteger("generate"));
       }
       return 0;
    }