[Open all] [Close all]

CSV Import/Export

Standard Humdrum files are encoded as TSV (Tab Separated Values). The minHumdurm library can also read and write data in the CSV (Comma Separated Values) format. This format of the data is useful for text processing which may munge tabs (in particular for copy/pasting Humdrum files into email messages).

Use the HumdrumFile::printCSV funtion to print Humdrum data in the CSV format. The example file in CSV format:

**kern  **kern
*M4/4   *M4/4
8C      12d
.       12e
8B      .
.       12f
*       *^
4A      2g      4d
4G      .       4c
*       *v      *v
=       =
*-      *-
**kern,**kern
*M4/4,*M4/4
8C,12d
.,12e
8B,.
.,12f
*,*^
4A,2g,4d
4G,.,4c
*,*v,*v
=,=
*-,*-

CSV files can be read into a HumdrumFile object by appending “CSV” to the various read functions, such as readCSV() and parseCSV().

XML Export

The humlib library includes a prototype for a Humdrum XML representation; a sample conversion given below. The XML data includes automatic analytical markup done internally within humlib classes.

HumdrumFile objects map to <sequence> elements.

HumdrumLine objects map to <frame> elements if HumdrumLine::hasSpines() returns true; otherwise map to <metaFrame> elements if HumdrumLine is a reference record, global comment, or empty record.

HumdrumToken objects map to <field> elements.

Duration values are given as floating point numbers. When there is a fractional part to a durational value, the fractional part is also expressed as a rational number with the @ratfrac attribute. For example if there is a durational attribute float=”1.66667”, then there will be an additional attribute ratfrac=”2/3” which means that the fractional part of the duration can be precisely expressed as 2/3.

!!!OTL: Test piece
**kern  **kern  **text
!! This is a "global comment"
*M4/4   *M4/4	*
=1-     =1-     =1-
8C      12d     This
.       12e     .
8B      .       .
.       12f     .
*       *^      *
4A      2g      4d      is
4G      .       4c      some
*       *v      *v      *
4B      4b      text
=       =       =
*-      *-      *-
<sequence>
	<sequenceInfo>
		<frameCount>17</frameCount>
		<tpq>6</tpq>
		<sequenceStart float="0"/>
		<sequenceDuration float="4"/>
		<trackInfo>
			<trackCount>3</trackCount>
			<track n="1" dataType="kern" startId="loc1_0"/>
			<track n="2" dataType="kern" startId="loc1_1"/>
			<track n="3" dataType="text" startId="loc1_2" endId ="loc16_0"/>
		<trackInfo>
	</sequenceInfo>
	<frames>
		<metaFrame n="0" text="!!!OTL: Test piece" xml:id="loc0"/>
			<frameInfo>
				<startTime float="0"/>
				<frameType>reference</frameType>
				<referenceKey>OTL</referenceKey>
				<referenceValue>Test piece</referenceValue>
			<frameInfo>
		</metaFrame>
		<frame n="1" xml:id="loc1">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="0"/>
				<frameDuration float="0"/>
				<frameType>interpretation</frameType>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="**kern" xml:id="loc1_0">
					<dataType>kern</dataType>
					<tokenType>manipulator</tokenType>
					<tokenFunction>note</tokenFunction>
				</field>
				<field n="1" track="2" text="**kern" xml:id="loc1_1">
					<dataType>kern</dataType>
					<tokenType>manipulator</tokenType>
					<tokenFunction>note</tokenFunction>
				</field>
				<field n="2" track="3" text="**text" xml:id="loc1_2">
					<dataType>text</dataType>
					<tokenType>manipulator</tokenType>
				</field>
			</fields>
		</frame>
		<metaFrame n="2" text="!! This is a &quot;global comment&quot;" xml:id="loc2"/>
			<frameInfo>
				<startTime float="0"/>
				<frameType>global-comment</frameType>
			<frameInfo>
		</metaFrame>
		<frame n="3" xml:id="loc3">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="0"/>
				<frameDuration float="0"/>
				<frameType>interpretation</frameType>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="*M4/4" xml:id="loc3_0">
					<dataType>kern</dataType>
					<tokenType>interpretation</tokenType>
				</field>
				<field n="1" track="2" text="*M4/4" xml:id="loc3_1">
					<dataType>kern</dataType>
					<tokenType>interpretation</tokenType>
				</field>
				<field n="2" track="3" text="*" xml:id="loc3_2">
					<dataType>text</dataType>
					<tokenType>null</tokenType>
				</field>
			</fields>
		</frame>
		<frame n="4" xml:id="loc4">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="0"/>
				<frameDuration float="0"/>
				<frameType>barline</frameType>
				<barlineDuration float="4"/>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="=1-" xml:id="loc4_0">
					<dataType>kern</dataType>
					<tokenType>barline</tokenType>
				</field>
				<field n="1" track="2" text="=1-" xml:id="loc4_1">
					<dataType>kern</dataType>
					<tokenType>barline</tokenType>
				</field>
				<field n="2" track="3" text="=1-" xml:id="loc4_2">
					<dataType>text</dataType>
					<tokenType>barline</tokenType>
				</field>
			</fields>
		</frame>
		<frame n="5" xml:id="loc5">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="0"/>
				<frameDuration float="0.333333" ratfrac="1/3"/>
				<frameType>data</frameType>
				<kernBoundary start="true" end="false"/>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="8C" xml:id="loc5_0">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="0.5" ratfrac="1/2"/>
					<pitch dpc="C" numacc="0" oct="3" base40="122"/>
				</field>
				<field n="1" track="2" text="12d" xml:id="loc5_1">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="0.333333" ratfrac="1/3"/>
					<pitch dpc="D" numacc="0" oct="4" base40="168"/>
				</field>
				<field n="2" track="3" text="This" xml:id="loc5_2">
					<dataType>text</dataType>
					<tokenType>data</tokenType>
					<duration float="1"/>
				</field>
			</fields>
		</frame>
		<frame n="6" xml:id="loc6">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="0.333333" ratfrac="1/3"/>
				<frameDuration float="0.166667" ratfrac="1/6"/>
				<frameType>data</frameType>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="." xml:id="loc6_0">
					<dataType>kern</dataType>
					<tokenType>null</tokenType>
					<nullResolve text="8C" idref="loc5_0"/>
				</field>
				<field n="1" track="2" text="12e" xml:id="loc6_1">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="0.333333" ratfrac="1/3"/>
					<pitch dpc="E" numacc="0" oct="4" base40="174"/>
				</field>
				<field n="2" track="3" text="." xml:id="loc6_2">
					<dataType>text</dataType>
					<tokenType>null</tokenType>
					<nullResolve text="This" idref="loc5_2"/>
				</field>
			</fields>
		</frame>
		<frame n="7" xml:id="loc7">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="0.5" ratfrac="1/2"/>
				<frameDuration float="0.166667" ratfrac="1/6"/>
				<frameType>data</frameType>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="8B" xml:id="loc7_0">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="0.5" ratfrac="1/2"/>
					<pitch dpc="B" numacc="0" oct="3" base40="157"/>
				</field>
				<field n="1" track="2" text="." xml:id="loc7_1">
					<dataType>kern</dataType>
					<tokenType>null</tokenType>
					<nullResolve text="12e" idref="loc6_1"/>
				</field>
				<field n="2" track="3" text="." xml:id="loc7_2">
					<dataType>text</dataType>
					<tokenType>null</tokenType>
					<nullResolve text="This" idref="loc5_2"/>
				</field>
			</fields>
		</frame>
		<frame n="8" xml:id="loc8">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="0.666667" ratfrac="2/3"/>
				<frameDuration float="0.333333" ratfrac="1/3"/>
				<frameType>data</frameType>
				<kernBoundary start="false" end="true"/>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="." xml:id="loc8_0">
					<dataType>kern</dataType>
					<tokenType>null</tokenType>
					<nullResolve text="8B" idref="loc7_0"/>
				</field>
				<field n="1" track="2" text="12f" xml:id="loc8_1">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="0.333333" ratfrac="1/3"/>
					<pitch dpc="F" numacc="0" oct="4" base40="179"/>
				</field>
				<field n="2" track="3" text="." xml:id="loc8_2">
					<dataType>text</dataType>
					<tokenType>null</tokenType>
					<nullResolve text="This" idref="loc5_2"/>
				</field>
			</fields>
		</frame>
		<frame n="9" xml:id="loc9">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="1"/>
				<frameDuration float="0"/>
				<frameType>interpretation</frameType>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="*" xml:id="loc9_0">
					<dataType>kern</dataType>
					<tokenType>null</tokenType>
				</field>
				<field n="1" track="2" text="*^" xml:id="loc9_1">
					<dataType>kern</dataType>
					<tokenType>manipulator</tokenType>
				</field>
				<field n="2" track="3" text="*" xml:id="loc9_2">
					<dataType>text</dataType>
					<tokenType>null</tokenType>
				</field>
			</fields>
		</frame>
		<frame n="10" xml:id="loc10">
			<frameInfo>
				<fieldCount>4</fieldCount>
				<frameStart float="1"/>
				<frameDuration float="0"/>
				<frameType>local-comment</frameType>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="!LO:TX:Z=18:t=allegro" xml:id="loc10_0">
					<dataType>kern</dataType>
					<tokenType>local-comment</tokenType>
					<tokenFunction>note</tokenFunction>
				</field>
				<field n="1" track="2" subtrack="1" text="!" xml:id="loc10_1">
					<dataType>kern</dataType>
					<tokenType>null</tokenType>
				</field>
				<field n="2" track="2" subtrack="2" text="!" xml:id="loc10_2">
					<dataType>kern</dataType>
					<tokenType>null</tokenType>
				</field>
				<field n="3" track="3" text="!" xml:id="loc10_3">
					<dataType>text</dataType>
					<tokenType>null</tokenType>
				</field>
			</fields>
		</frame>
		<frame n="11" xml:id="loc11">
			<frameInfo>
				<fieldCount>4</fieldCount>
				<frameStart float="1"/>
				<frameDuration float="1"/>
				<frameType>data</frameType>
				<kernBoundary start="true" end="false"/>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="4A" xml:id="loc11_0">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="1"/>
					<pitch dpc="A" numacc="0" oct="3" base40="151"/>
					<parameters>
						<namespace n="1" name="LO">
							<namespace n="2" name="TX">
								<parameter key="Z" value="18" idref="loc10_0"/>
								<parameter key="t" value="allegro" idref="loc10_0"/>
							</namespace>
						</namespace>
					</parameters>
				</field>
				<field n="1" track="2" subtrack="1" text="2g" xml:id="loc11_1">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="2"/>
					<pitch dpc="G" numacc="0" oct="4" base40="185"/>
				</field>
				<field n="2" track="2" subtrack="2" text="4d" xml:id="loc11_2">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="1"/>
					<pitch dpc="D" numacc="0" oct="4" base40="168"/>
				</field>
				<field n="3" track="3" text="is" xml:id="loc11_3">
					<dataType>text</dataType>
					<tokenType>data</tokenType>
					<duration float="1"/>
				</field>
			</fields>
		</frame>
		<frame n="12" xml:id="loc12">
			<frameInfo>
				<fieldCount>4</fieldCount>
				<frameStart float="2"/>
				<frameDuration float="1"/>
				<frameType>data</frameType>
				<kernBoundary start="false" end="true"/>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="4G" xml:id="loc12_0">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="1"/>
					<pitch dpc="G" numacc="0" oct="3" base40="145"/>
				</field>
				<field n="1" track="2" subtrack="1" text="." xml:id="loc12_1">
					<dataType>kern</dataType>
					<tokenType>null</tokenType>
					<nullResolve text="2g" idref="loc11_1"/>
				</field>
				<field n="2" track="2" subtrack="2" text="4c" xml:id="loc12_2">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="1"/>
					<pitch dpc="C" numacc="0" oct="4" base40="162"/>
				</field>
				<field n="3" track="3" text="some" xml:id="loc12_3">
					<dataType>text</dataType>
					<tokenType>data</tokenType>
					<duration float="1"/>
				</field>
			</fields>
		</frame>
		<frame n="13" xml:id="loc13">
			<frameInfo>
				<fieldCount>4</fieldCount>
				<frameStart float="3"/>
				<frameDuration float="0"/>
				<frameType>interpretation</frameType>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="*" xml:id="loc13_0">
					<dataType>kern</dataType>
					<tokenType>null</tokenType>
				</field>
				<field n="1" track="2" subtrack="1" text="*v" xml:id="loc13_1">
					<dataType>kern</dataType>
					<tokenType>manipulator</tokenType>
				</field>
				<field n="2" track="2" subtrack="2" text="*v" xml:id="loc13_2">
					<dataType>kern</dataType>
					<tokenType>manipulator</tokenType>
				</field>
				<field n="3" track="3" text="*" xml:id="loc13_3">
					<dataType>text</dataType>
					<tokenType>null</tokenType>
				</field>
			</fields>
		</frame>
		<frame n="14" xml:id="loc14">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="3"/>
				<frameDuration float="1"/>
				<frameType>data</frameType>
				<kernBoundary start="true" end="true"/>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="4B" xml:id="loc14_0">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="1"/>
					<pitch dpc="B" numacc="0" oct="3" base40="157"/>
				</field>
				<field n="1" track="2" text="4b" xml:id="loc14_1">
					<dataType>kern</dataType>
					<tokenType>data</tokenType>
					<tokenFunction>note</tokenFunction>
					<duration float="1"/>
					<pitch dpc="B" numacc="0" oct="4" base40="197"/>
				</field>
				<field n="2" track="3" text="text" xml:id="loc14_2">
					<dataType>text</dataType>
					<tokenType>data</tokenType>
					<duration float="1"/>
				</field>
			</fields>
		</frame>
		<frame n="15" xml:id="loc15">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="4"/>
				<frameDuration float="0"/>
				<frameType>barline</frameType>
				<barlineDuration float="0"/>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="=" xml:id="loc15_0">
					<dataType>kern</dataType>
					<tokenType>barline</tokenType>
				</field>
				<field n="1" track="2" text="=" xml:id="loc15_1">
					<dataType>kern</dataType>
					<tokenType>barline</tokenType>
				</field>
				<field n="2" track="3" text="=" xml:id="loc15_2">
					<dataType>text</dataType>
					<tokenType>barline</tokenType>
				</field>
			</fields>
		</frame>
		<frame n="16" xml:id="loc16">
			<frameInfo>
				<fieldCount>3</fieldCount>
				<frameStart float="4"/>
				<frameDuration float="0"/>
				<frameType>interpretation</frameType>
			</frameInfo>
			<fields>
				<field n="0" track="1" text="*-" xml:id="loc16_0">
					<dataType>kern</dataType>
					<tokenType>manipulator</tokenType>
				</field>
				<field n="1" track="2" text="*-" xml:id="loc16_1">
					<dataType>kern</dataType>
					<tokenType>manipulator</tokenType>
				</field>
				<field n="2" track="3" text="*-" xml:id="loc16_2">
					<dataType>text</dataType>
					<tokenType>manipulator</tokenType>
				</field>
			</fields>
		</frame>
	</frames>
</sequence>

Parameters

humlib can processes non-data parameters embedded in local or global comments before the data token, interpretation or barline in a spine. The form of a parameter is:

![!][ns1]:[ns2]:key1=[value1][:key2=value[:key3=value3]

The parameter system includes two optional namespace prefixes to the list of parameters. A parameter in the default namespace will start with two colons (:), followed by the parameter list. For example here is a parameter list which does not include any namespace qualifiers:

!::A=a:B=b

With one namespace qualifier, the form would look like this:

!:C:A=a:B=b

And with a full name space description:

!:D:C:A=a:B=b

Namespaces and keys may not contain spaces or colons, and ideally should only contain letters, digits, dashes (-), and underscores (_). Values may contain any character except for colons or newlines. To represent a colon, use &colon; in place of :.

When the humlib parser reads a Humdrum file with parameters, it will automatically parse the parameters and attaches them to the next non-null token in the spine which follows. These parameters can then be accessed from a HumdrumToken with the functions:

  • HumHash::getValue which returns the string for the parameter,
  • HumHash::getValueInt for parsing it as an integer,
  • HumHash::getValueBool for a boolean,
  • HumHash::getValueFloat for a double, or
  • HumHash::getValueFraction as a HumHash fraction.

Here is an example Humdrum file which adds the parameter key/value “Z=z” for a data token:

**A
a1
!::Z=z
a2
a3
*-

The following code would print the value of the Z parameter (“z”):

HumdrumFile infile;
cout << infile.token(3,0).getValue("Z");

The list of parameters for a token are exposed when generating a HumdruXML file for the data will be given as a child of the <field> element that represents the data token. Here is the field element for the “a2” in XML format:

<field n="0" track="1" token="a2" xml:id="loc3_0">
   <dataType>A</dataType>
   <tokenType>data</tokenType>
   <duration float="0"/>
   <parameters>
      <namespace n="1" name="">
         <namespace n="2" name="">
            <parameter key="Z" value="z" idref="loc2_0"/>
         </namespace>
      </namespace>
   </parameters>
</field>

The parameter@idref attribute is an XML id which points to the source line used to set the parameter.

Parameters may be split into separate comments. These two forms are equivalent:

**A
a1
!::Z=z:Y=y
a2
a3
*-
**A
a1
!::Z=z
!::Y=y
a2
a3
*-

This will cause token a2 to have two parameters Z=z and Y=y.

When the same key is used to set the parameter for a token, only the first value of the parameter will be used. In the following example, .getValue("Z") will return z1:

**A
a1
!::Z=z1
!::Z=z2
a2
a3
*-

Example with namespaces:

**A
a1
!ns1:ns2:Z=z1
a2
a3
*-

To access the parameter for a2, the following two cases are equivalent:

infile.token(3, 0).getValue("ns1:ns2:Z");
infile.token(3, 0).getValue("ns1", "ns2", Z");

If a parameter key is not followed by a parameter, it’s value is set to the string “true” which also returns true when accessing the .getValueBool() function.

Layout parameters

Layout parameters can be embedded in **kern data that give graphical printing information which is not otherwise specified in the data. Layout codes have the form:

![!]LO:category:key=value

Local comments, starting with a single ! indicate a local layout parameter which alters the next non-null token in a spine. Global
comments, starting with !!, indicate that the parameter effect all parts.

Slur orientation

Slurs can be forced above notes by placing the local layout code !LO:S:a before the token staring a slur (which is an open parenthesis in **kern data). Use !LO:S:b to force the slur below the group of notes.

<img src=/images/layout/slur.png>

Generic text directions

Any arbitrary text can be placed in the music by using parameters in the !LO:TX: namespace:

**kern**kern
*clefG2*clefG2
*k[f#]*k[f#]
*M4/4*M4/4
=1-=1-
4c/2aa\
4d/.
4e/2gg\
4f/.
!LO:TX:i:rj:t=fine!
=2||=2||
!!LO:TX:bi:z=5:t=ritardando
4g/2ee\
4a/.
4b\2dd\
4cc\.
=3=3
4b\2dd#\
4a/.
!LO:TX:b:X=-15:Z=15:t=ad lib.!
!!LO:TX:Z=15:cj:t=reflectively
4g/2ee\
4f/.
=4=4
4e/2ff\
4d/.
2c/;2gg\;
!LO:TX:i:rj:X=20:t=D.C. al fine!
=:|!=:|!
*-*-

Text parameters:

  • t = text to display.
  • rj = right justify text (boolean).
  • cj = center justify text (boolean).
  • b = bold text.
  • X = horizontal offset of text.
  • Y = placement height below bottom line of staff.
  • Z = placement height above top line of staff.

Rational rhythms

The **recip data type in the standard Humdrum Toolkit cannot represent non-integral subdivisions of whole notes, and has difficulty representing rhythms larger than a whole note. The humlib library understands a rational-number extension to **recip data (i.e., **kern rhythms).

For example, triplet whole notes are equivalent to 2/3rds of a whole note. This is represented as 3%2 in the extended **recip format, where the denominator of the whole-note division is given first, followed by a percent sign, and then the numerator. By extension, a quarter note which is usually represented as 4 can alternately be be represented as 4%1 in the extended format.

<a href=http://www.humdrum.org/humextra/rhythm/#extensions-to-recip-and-kern-rhythms>More info</a>.

Strands

2-D row/column iteration of tokens

You can uniquely iterate through all tokens in a Humdrum file using two methods. The simplest one involves iterating first by line and then by field within each line. Below is a short program that demonstrates this process by echoing the input Humdrum file contents in the same format as a standard TSV Humdrum file

#include "humlib.h"
using namespace std;
using namespace hum;
int main(int argc, char** argv) {
   HumdrumFile infile;
   Options options;
   options.process(argc, argv);
   if (options.getArgCount() > 0) {
      infile.read(options.getArg(1));
   } else {
      infile.read(cin);
   }
   for (int i=0; i<infile.getLineCount(); i++) {
      for (int j=0; j<infile[i].getFieldCount(); j++) {
         cout << infile.token(i, j);
         if (j < infile[i].getFieldCount()) {
            cout << '\t';
         }
      }
      cout << '\n';
   }
   return 0;
}

You can copy this code to cli/humecho.cpp and then in the base directory of humlib type make library && make humecho to compile to bin/humecho.

The lines in the file represent a sequence of data that is sorted in chronological order. This row/column iteration method is suitable for temporal processing of all data tokens.

1-D strand iteration

To iterate through all spines in an different order, the humlib library introduces the concept of strands. A strand is a sequence of tokens in a particular spine that does not include spine splits or merges. The following figure shows example Humdrum data with individual strands highlighted in different colors.

Strand example

Each spine consists of a primary strand which is continuous throughout the total length of the spine (the left-most path in the topology diagram). When a spine splits into sub-spines, a new strand starts at the beginning of the right-hand branch of the split, while the previous strand continues along the left-hand branch.

The strand segments can be used to iterate through all tokens in the file (excluding non-spined lines, which are global comments, reference records and empty lines). A one-dimensional iteration through all strands is illustrated in the following code:

#include "humlib.h"
using namespace std;
using namespace hum;
int main(int argc, char** argv) {
   HumdrumFile infile;
   Options options;
   options.process(argc, argv);
   if (options.getArgCount() > 0) {
      infile.read(options.getArg(1));
   } else {
      infile.read(cin);
   }
   cout << "token\trow/col" << endl;
   for (int i=0; i<infile.getStrandCount(); i++) {
      cout << "=== Strand index " << i << endl;
      HTp current   = infile.getStrandStart(i);
      HTp strandEnd = infile.getStrandEnd(i);
      while (current) {
         cout << current << "\t"
              << current->getLineNumber() << ","
              << current->getFieldNumber() << endl;
         if (current == strandEnd) {
            break;
         }
         current = current->getNextToken();
      }
   }
   return 0;
}

The example Humdrum data contains seven 1-D strands and the example program prints the row/column in the original data that the tokens originate.

Click to view output of the program using the illustrated data
token	row/col
=== Strand index 0
**A	1,1
a	2,1
a	3,1
*	4,1
a	5,1
a	6,1
*^	7,1
a1	8,1
a1	9,1
*v	10,1
a	11,1
a	12,1
*v	13,1
a	14,1
a	15,1
*^	16,1
a1	17,1
a1	18,1
*v	19,1
a	20,1
a	21,1
*-	22,1
=== Strand index 1
a2	8,2
a2	9,2
*v	10,2
=== Strand index 2
a2	17,2
a2	18,2
*v	19,2
=== Strand index 3
**B	1,2
b	2,2
b	3,2
*^	4,2
b1	5,2
b1	6,2
*	7,2
b1	8,3
b1	9,3
*	10,3
b1	11,2
b1	12,2
*	13,2
b1	14,2
b1	15,2
*v	16,2
b	17,3
b	18,3
*	19,3
b	20,2
b	21,2
*-	22,2
=== Strand index 4
b2	5,3
b2	6,3
*^	7,3
b21	8,4
b21	9,4
*	10,4
b21	11,3
b21	12,3
*v	13,3
b2	14,3
b2	15,3
*v	16,3
=== Strand index 5
b22	8,5
b22	9,5
*	10,5
b22	11,4
b22	12,4
*v	13,4
=== Strand index 6
**C	1,3
c	2,3
c	3,3
*	4,3
c	5,4
c	6,4
*	7,4
c	8,6
c	9,6
*	10,6
c	11,5
c	12,5
*	13,5
c	14,4
c	15,4
*	16,4
c	17,4
c	18,4
*	19,4
c	20,3
c	21,3
*-	22,3

2-D strand iteration

A variant of iterating through strands is a 2-D method demonstrated below, where the strand list is limited to a specific spine. The main difference is that you can select one or more spines to process in advance and only iterate through all strands in those spines, ignoring strands in other spines not being processed. In the following example the strands are first iterated through by spine index, and then by strands within each spine.

#include "humlib.h"
using namespace std;
using namespace hum;
int main(int argc, char** argv) {
   HumdrumFile infile;
   Options options;
   options.process(argc, argv);
   if (options.getArgCount() > 0) {
      infile.read(options.getArg(1));
   } else {
      infile.read(cin);
   }
   cout << "token\trow/col" << endl;
   for (int i=0; i<infile.getSpineCount(); i++) {
      for (int j=0; j<infile.getStrandCount(i); j++) {
         cout << "=== Strand index " << i << "," << j << endl;
         HTp current   = infile.getStrandStart(i, j);
         HTp strandEnd = infile.getStrandEnd(i, j);
         while (current) {
            cout << current << "\t"
                 << current->getLineNumber() << ","
                 << current->getFieldNumber() << endl;
            if (current == strandEnd) {
               break;
            }
            current = current->getNextToken();
         }
      }
   }
   return 0;
}

Primary strands always start with an exclusive interpretation (tokens that start with ** followed by the data type) and are ended with a termination manipulator (*-). Secondary strands always start with a token that immediately follows a spine split (*^), and typically end at a merge manipulator (*v), although unmerged sub-strands will end with a termination token.

Note that If a spine is not terminated properly, the HumdrumToken::getNextToken function will return a NULL pointer, so it is useful to terminate strands if either the current token is NULL or if it is the target end token. This is the purpose of the while (current) condition in the strand code examples above.

Efficient iteration through all **kern data

If you want to count all notes in a file, you can use the row/column method described at the start of this section:

#include "humlib.h"
using namespace std;
using namespace hum;

int getNoteCount(HumdrumFile& infile) {
   int count = 0;
   for (int i=0; i<infile.getLineCount(); i++) {
      if (!infile[i].hasSpines() || !infile[i].isData()) {   
         continue;
      }
      for (int j=0; j<infile[i].getFieldCount(); j++) {
         HTp token = infile[i].token(j);
         if (!token->isKern() || token->isNull()) {
            continue;
         }
         vector<string> subtokens = token->getSubtokens();
         HumRegex hre;
         for (int k=0; k<(int)subtokens.size(); k++) {
            if (hre.search(subtokens[k], R"([_r\]])")) {
               continue;
            }
            if (hre.search(subtokens[k], "[a-gA-G]")) {
               count++;
            }
         }
      }
   }
   return count;
}

int main(int argc, char** argv) {
   Options options;
   options.process(argc, argv);
   HumdrumFileStream instream(options);
   HumdrumFile infile;
   int noteCount = 0;
   while (instream.read(infile)) {
      noteCount += getNoteCount(infile);
   }
   cerr << "NOTES: " << noteCount << endl;
   return 0;
}

If there are non-kern spines, all of the tokens in these spines will be processed.

In the following example, only tokens in kern spines will be processed, and non-kern spines will be ignored. In this case strands are used to iterate through the data, and any non-kern strands are skipped over.

#include "humlib.h"
using namespace std;
using namespace hum;

int getNoteCount(HumdrumFile& infile) {
   int count = 0;
   for (int i=0; i<infile.getStrandCount(); i++) {
      HTp current = infile.getStrandStart(i);
      if (!current->isKern()) {
         continue;
      }
      HTp send = infile.getStrandEnd(i);
      while (current && (current != send)) {
         if (!current->isData() || current->isNull()) {
            current = current->getNextToken();
            continue;
         }
         vector<string> subtokens = current->getSubtokens();
         HumRegex hre;
         for (int k=0; k<(int)subtokens.size(); k++) {
            if (hre.search(subtokens[k], R"([_r\]])")) {
               continue;
            }
            if (hre.search(subtokens[k], "[a-gA-G]")) {
               count++;
            }
         }
         current = current->getNextToken();
      }
   }
   return count;
}

int main(int argc, char** argv) {
   Options options;
   options.process(argc, argv);
   HumdrumFileStream instream(options);
   HumdrumFile infile;
   int noteCount = 0;
   while (instream.read(infile)) {
      noteCount += getNoteCount(infile);
   }
   cerr << "NOTES: " << noteCount << endl;
   return 0;
}

Here is an alternate method of using 2-D access of strands. First a list of kern spine starts is extract from the Humdrum data, and then the strands of these spines are iterated over. This method avoids checking strands that occur in non-kern spines.

#include "humlib.h"
using namespace std;
using namespace hum;

int getNoteCount(HumdrumFile& infile) {
   int count = 0;
   vector<HTp> kernStarts = infile.getKernSpineStartList();
   for (int i=0; i<(int)kernStarts.size(); i++) {
      int spine = kernStarts[i]->getSpineIndex();
      for (int j=0; j<infile.getStrandCount(spine); j++) {
         HTp current = infile.getStrandStart(spine, j);
         HTp send = infile.getStrandEnd(spine, j);
         while (current && (current != send)) {
            if (!current->isData() || current->isNull()) {
               current = current->getNextToken();
               continue;
            }
            vector<string> subtokens = current->getSubtokens();
            HumRegex hre;
            for (int k=0; k<(int)subtokens.size(); k++) {
               if (hre.search(subtokens[k], R"([_r\]])")) {
                  continue;
               }
               if (hre.search(subtokens[k], "[a-gA-G]")) {
                  count++;
               }
            }
            current = current->getNextToken();
         }
      }
   }
   return count;
}

int main(int argc, char** argv) {
   Options options;
   options.process(argc, argv);
   HumdrumFileStream instream(options);
   HumdrumFile infile;
   int noteCount = 0;
   while (instream.read(infile)) {
      noteCount += getNoteCount(infile);
   }
   cerr << "NOTES: " << noteCount << endl;
   return 0;
}