[Open all] [Close all]
CVS 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 Separate Values) format. This serves two purposes: (1) for people who cannot wrap their minds around the concept of a tab character, and (2) for text processing which may munge tabs (particularly when 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:
|
|
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 "global comment"" 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 :
in place of :
.
When the minHumdurm 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 a 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.
![]() |
Generic text directions
Any arbitrary text can be placed in the music by using parameters
in the !LO:TX:
namespace:
|
![]() Text parameters:
|
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.
Strands
All tokens in a Humdrum file can be uniquely iterated through with two methods. The most straightforward way is to iterate through each line in the data, and then for each line, iterating through each field on the line. Here is some example code which will print the Humdrum file which will be identical to an input TSV Humdrum file.
HumdrumFile infile;
for (int i=0; i<infile.getLineCount(); i++) {
for (int j=0; j<infile[i].getFieldCount(); j++) {
cout << infile.token(i, j);
// or: cout << infile[i].token(j);
if (j < infile[i].getFieldCount() {
cout << '\t';
}
}
cout << '\n';
}
This format of iterating through the file is suitable for temporal sequence processing of all spine tokens at the same time. The lines in the file represent a sequence where the lines are sorted in time order.
To iterate through spines in an orthogonal manner, the humlib library introduces a concept of strands. A strand is a sequence of tokens in a particular spine which does not include spine splits or merges. The following figure shows an example Humdrum data stream with individual strands given different colors.
Each spine consists of a primary strand which is continuous throughout the total length of the spine. When a spine splits into sub-spines, a new strand starts at the beginning of the right-side branch of the split, while the previous strand continues along the left-side branch.
The strand segments can be used to iterate through all tokens in the file (excluding non-spine lines, which are global comments and reference records). A one dimensional iteration is illustrated in the following code:
Humdrum infile;
HumdrumToken* tok;
for (int i=0; i<infile.getStrandCount(); i++) {
tok = infile.getStrandStart(i);
while (!tok->isStrandEnd()) {
cout << *tok << endl;
tok->getNextToken();
}
}
The above illustration contains seven 1D strands, so the above code will generate this data sequence (removing duplicate adjacent tokens in the sequence):
a a a a a1 a1 a a a a a1 a1 a a a2 a2 a2 a2 b b b1 b1 b1 b1 b1 b1 b1,21 b1,21 b b b b b2 b2 b21 b21 b21 b21 b22 b22 b22 b22 b22 b22 c c c c c c c c c c c c c c
A two-dimensional iteration through the spine tokens can generate the same ordering. In the following example the strands are first iterated through by spine index, and then by strands in the spines, always starting with the primary strand.
Humdrum infile;
HumdrumToken* tok;
for (int i=0; i<infile.getSpineCount(); i++) {
for (int j=0; j<infile.getStrandCount(i); j++) {
tok = infile.getStrandStart(i, j);
while (!tok->isStrandEnd()) {
cout << *tok << endl;
tok->getNextToken();
}
}
When spines do not split or merge, then strands are equivalent to spines. The following code examples will generate the same data ordering:
for (i=0; i<infile.getSpineCount(); i++) {
tok = infile.getSpineStart(i);
while (!tok->isTerminator()) {
cout << *tok << endl;
tok = tok->getNextToken();
}
}
for (int i=0; i<infile.getStrandCount(); i++) {
tok = infile.getStrandStart(i);
while (!tok->isStrandEnd()) {
cout << *tok << endl;
tok->getNextToken();
}
}
Primary strands always start with an exclusive interpretation (interpretation
token which starts with ** followed by the data type), and are ended with the
terminate manipulator (*-
). Secondary strands always start with a
non-exclusive interpretation, and typically end at a merge manipulator
(*v
), although unmerged sub-strands will end with a terminate token.