**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.
</ul>
</td></tr></table>
</p>
</details>
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
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.
```cpp
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.
<img width=100% src="/images/strand.svg">
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:
```cpp
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.
```cpp
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:
```cpp
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.
|