Complex ratios in polymetric expressions

This page is intend­ed for devel­op­ers of the Bol Processor BP3 (read instal­la­tion). It is not a for­mal descrip­tion of the algo­rithms car­ried by the con­sole’s C code, but rather an illus­tra­tion of their man­age­ment of musi­cal process­es, which may be use­ful for check­ing or extend­ing algorithms.

All exam­ples are con­tained in the file “-da.checkPoly” in the “ctests” fold­er in the distribution.

Syntax of silences

In the Bol Processor’s data/grammar syn­tax, silences (rests in con­ven­tion­al musi­cal ter­mi­nol­o­gy) are rep­re­sent­ed either by a hyphen ‘-’ for a sin­gle unit dura­tion, or by inte­ger ratios to spec­i­fy a more com­plex duration:

  • 4″ is a rest of 4 units duration
  • 5/3″ is a rest of (approx­i­mate­ly) 1.666-unit duration
  • 3 1/2″ is a rest of 3.5 units duration

For exam­ple, “C4 C5 3/2 D5 E5″ results in the fol­low­ing piano roll with a rest of 3/2 (1.5) units start­ing on beat 2 and end­ing on beat 3.5:

Piano roll of item “C4 C5 3/2 D5 E5”

In this tuto­r­i­al we will use the default metronome val­ue = 60 beats per minute.

Another sim­ple exam­ple is {3 1/2, C3 D3 B2}, which is the sequence of notes “C3 D3 B2″ con­strained to a total dura­tion of 3 1/2 (3.5) beats. This silence is the first field of the poly­met­ric expres­sion (explained below). This results in the fol­low­ing piano roll:

or equiv­a­lent­ly the sound-object graph:

Syntax of tempo

Any sequence of sym­bols con­form­ing to the syn­tax of Bol Processor is processed as a poly­met­ric expres­sion. Typical forms are:

  • field 1, field2 indi­cates that field1 and field2 should be super­im­posed and the total dura­tion should be that of field1;
  • field1.field2 indi­cates that field1 and field2 should be sequen­tial, with the dura­tion of each field being that of field1;
  • {expres­sion} is equiv­a­lent to expres­sion.

Brackets ‘{‘ and ’}’ are used to cre­ate multi-level expressions.

A num­ber of exam­ples of poly­met­ric expres­sions can be found in the Polymetric struc­tures tutorial.

For exam­ple, {{C4 D4, E4 F4 G4}, E5} gives the fol­low­ing structure:

Item {{C4 D4, E4 F4 G4}, E5} on a sound-object graph

In order to inter­pret this struc­ture, the Bol Processor needs to insert explic­it tem­po val­ues into the expres­sion. In fact, in this case, the most com­pact rep­re­sen­ta­tion would be with explic­it tem­po values:

*1/1 {{C4 D4,*2/3 E4 F4 G4} ‚*2/1 E5}

Expressions such as “*2/3″ indi­cate that the dura­tion of each note (or sound-object) should be mul­ti­plied by 2/3, regard­less of the pre­ced­ing state­ments. This means that the dura­tions of notes “E4”, “F4” and “G4” should be 2/3 sec­onds as shown in the diagram.

Creating the com­pact rep­re­sen­ta­tion with its explic­it tem­po mark­ers may require recur­sive calls of a sophis­ti­cat­ed pro­ce­dure called PolyExpand() in the “Polymetric.c” file.

At this stage it is impor­tant not to con­fuse the notations:

  1. 2/3″ is a silence of dura­tion 2/3 beats;
  2. _tempo(2/3)” mul­ti­plies the cur­rent tem­po by 2/3. This is a rel­a­tive tem­po marker;
  3. *2/3″ sets the cur­rent dura­tion of the units to 2/3 of the metronome peri­od. This is an absolute tem­po mark­er. Similarly, “*4″ mul­ti­plies dura­tions by 4, and “*1/5″ or “/5″ divides them by 5 — where­as “1/5″ is a 1/5 beat silence.

The third syn­tax is the one used by the Bol Processor’s time-setting algo­rithms. Despite its syn­tac­tic valid­i­ty, we do not rec­om­mend using it in gram­mars and data, as it can pro­duce con­flict­ing dura­tions in poly­met­ric struc­tures. For exam­ple, {*2/1 A4 B4, *3/1 A5 B5} makes no sense because it tries to force the first field to have a dura­tion of 2 x 2 = 4 beats and the sec­ond field to have a dura­tion of 3 x 2 = 6 beats. The cor­rect (nev­er con­tra­dic­to­ry) way to change a tem­po in data or gram­mars is to use the “_tempo(x)” per­for­mance tool.

Expanding a polymetric expression

In the pre­vi­ous para­graph we saw that {{C4 D4, E4 F4 G4}, E5} is inter­nal­ly rep­re­sent­ed as *1/1 {{C4 D4,*2/3 E4 F4 G4} ‚*2/1 E5}. This inter­nal rep­re­sen­ta­tion is the most com­pact with explic­it tem­po mark­ings. Therefore, it is the one that is main­tained through all the steps of time setting.

Humans may pre­fer to see a more com­pre­hen­sive rep­re­sen­ta­tion called the expand­ed poly­met­ric expres­sion:

/3 {{C4_ _ D4_ _, E4_ F4_ G4_} , E5_ _ _ _ _}

This is done by click­ing the EXPAND but­ton on a data page. Underscores’_’ rep­re­sent exten­sions of the dura­tion of the pre­vi­ous unit. These should not be con­fused with ‘-’ (silence). To make things clear­er, let us replace a ‘_’ with a ‘-’:

/3 {{C4_ _ D4_ _, E4_ F4 - G4_}, E5_ _ _ _ _}

This results in the fol­low­ing struc­ture, where “F4” is not extended:

Item /3 {{C4_ _ D4_ _, E4_ F4 - G4_}, E5_ _ _ _ _}

The expand­ed poly­met­ric expres­sion may become too large for a human observ­er. In this case only the com­pact ver­sion will be returned.

In the code of the Bol proces­sor con­sole, sound objects (of all kinds) are iden­ti­fied by num­bers. The vari­able used to iden­ti­fy them in algo­rithms is always ‘k’ or ‘kobj’. There is an option (in the code) to dis­play object iden­ti­fiers on a graph, which is set by the SHOWEVERYTHING con­stant. If set to true, this would be the pre­vi­ous sound object graph:

Item /3 {{C4_ _ D4_ _, E4_ F4 - G4_}, E5_ _ _ _ _}
in SHOWEVERYTHING mode

Notes “C4”, “D4”, etc. have iden­ti­fiers kobj = 2, 3, etc. The iden­ti­fi­er “0” is reserved for exten­sions ‘_’ and “1” for silences “-”, none of which are shown in the graph. An excep­tion is object #8, labelled «-», which is an out-time (zero dura­tion) “silence” mark­ing the end of the struc­ture to facil­i­tate its syn­chro­ni­sa­tion with the next item.

The phase diagram

Given a com­pact poly­met­ric struc­ture, time-setting algo­rithms require a table in which each col­umn is assigned a date (in phys­i­cal time). The cells of this phase dia­gram con­tain the iden­ti­fiers of the sound-objects, includ­ing “0” and “1”. It is cre­at­ed by the pro­ce­dure FillPhaseDiagram() in the file “FillPhaseDiagram.c”.

It is easy to imag­ine that the table would become very large if no com­pres­sion tech­niques were used. For exam­ple, Liszt’s 14th Rhapsody would require no less than 9 x 1021 cells! The rea­son is that the Bol Processor cal­cu­lates sym­bol­ic dura­tions as inte­ger ratios. A sym­bol­ic dura­tion of 5/3 will nev­er be replaced by “1.666” for two rea­sons: (1) round­ings would accu­mu­late as notice­able errors, and (2) we don’t know in advance how many dec­i­mals we need to keep. The phys­i­cal dura­tion of 5/3 beats depends on the metronome and the sequence of “_tempo(x)” con­trols that change the tempo.

Let us first con­sid­er an unprob­lem­at­ic case. The poly­met­ric expres­sion /3 {{C4_ _ D4_ _, E4_ F4 - G4_}, E5_ _ _ _ _} cre­ates the fol­low­ing phase diagram:

Phase dia­gram of
/3 {{C4_ _ D4_ _, E4_ F4 - G4_}, E5_ _ _ _ _}

In this exam­ple, if the metronome is set to 60 beats per minute, the phys­i­cal dura­tion assigned to each col­umn is 1/3 sec­ond = 333 ms. As the graph becomes larg­er, this phys­i­cal dura­tion may decrease beyond the lim­it. This is where quan­ti­za­tion comes in. It is set to 10 mil­lisec­onds by default, which means that two events occur­ring with­in 10 ms of each oth­er can be writ­ten into the same col­umn. To do this, the com­pact poly­met­ric struc­ture is rewrit­ten with a com­pres­sion rate (Kpress) that makes it fit into a phase dia­gram of suit­able size.

If the piece of music lasts 10 min­utes, we’ll still get 10 x 60000 / 10 = 60000 columns in the table. Filling the phase dia­gram requires a very high com­pres­sion rate, for exam­ple more than 5 x 1012 for Beethoven’s Fugue in B-flat major.

To make mat­ters worse, the algo­rithm has to deal with sequences of events that fall into the same col­umn. This sit­u­a­tion is sig­nalled by the vari­able toofast, which is obtained by com­par­ing the cur­rent tem­po with the max­i­mum tem­po accept­ed in the struc­ture. In the case of toofast, each event is writ­ten to a new row of the table in such a way as to respect the sequen­tial order of the stream.

So we end up with 12132 lines for the phase table of Beethoven’s fugue, in which the longest toofast stream con­tains 625 events — notes or sound-objects. These 625 events, which occur with­in a sin­gle frame of 10 ms, actu­al­ly include ‘_’ events which are exten­sions of notes belong­ing to the stream.

Dealing with complex ratios

In Bol Processor ter­mi­nol­o­gy, an inte­ger ratio p/q is “com­plex” if either ‘p’ or ‘q’ exceeds a lim­it that depends on the source code. The lim­it is ULONG_MAX, the max­i­mum val­ue of an unsigned long type, cur­rent­ly 18446744073709551616.

In the code of the Bol Processor con­sole, ‘p’ and ‘q’ are actu­al­ly cod­ed as dou­ble float­ing point num­bers whose man­tis­sa can con­tain as many dig­its as unsigned long inte­gers. Arithmetic oper­a­tions are per­formed on the frac­tions. Each result­ing frac­tion is checked for com­plex­i­ty by a pro­ce­dure called Simplify() in the “Arithmetic.c” file:

  1. While ‘p’ or ‘q’ is greater than ULONG_MAX, divide ‘p’ and ‘q’ by 10;
  2. Divide ‘p’ and ‘q’ by their great­est com­mon divi­sor (GCD).

Part (1) of the Simplify() pro­ce­dure gen­er­ates round­ing errors, but these rep­re­sent a few units of very large num­bers. In this way, the accu­ra­cy of sym­bol­ic dura­tions is main­tained through­out the com­pu­ta­tion of com­pli­cat­ed poly­met­ric structures.

Complex ratios in silences

Let us check the effect on quan­ti­za­tion by playing:

C4 C5 36001/24000 D5 E5

The ratio 36001/24000 can­not be sim­pli­fied. However, 1/24000 beat would take 0.04 ms which is much less than the 10 ms quan­ti­za­tion. So, the ratio can be approx­i­mat­ed to 36000/24000 and sim­pli­fied to 3/2. The final result is there­fore “C4 C5 3/2 D5 E5″:

Item “C4 C5 36001/24000 D5 E5” sim­pli­fied as “C4 C5 3/2 D5 E5”

Let us now con­sid­er “C4 C5 35542/24783 D5 E5″ which looks sim­i­lar, as 35542/24783 (1.43) is close to 1.5. However, the cal­cu­la­tion is more com­plex… Using the 10 ms quan­ti­za­tion, the ratio is reduced to 143/100 and the com­pact poly­met­ric expres­sion is:

/1 C4 C5 /100 - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /1 D5 E5

The 143/100 silence is now rep­re­sent­ed as a sin­gle ‘-’ (kobj = 1) fol­lowed by 142 ‘_’ (kobj = 0). This sequence is toofast because tem­po­max, the max­i­mum tem­po accept­ed here, would be ‘50’ instead of ‘100’. The com­pres­sion rate is Kpress = 2. A full expla­na­tion requires the poly­met­ric algo­rithm explained here.

The process of fill­ing the phase table can be found in “FillPhaseDiagram.c”. We call ‘ip’ the index of the col­umn into which the next event is to be plot­ted. In the most com­mon sit­u­a­tion, e.g. writ­ing “C4 _ _” (object #2), two pro­ce­dures are called:

  • Plot() writes ‘2’ (kobj) into col­umn ip
  • PutZeros() writes two zeros into the columns ip + 1 and ip +2.

So, “C4 _ _” will have a sym­bol­ic dura­tion of 3 units, as expected.

The case is dif­fer­ent with a silence of 143/100, because the toofast sit­u­a­tion requires that less than 142 ‘_’ should be insert­ed after ‘-’. To this end, a (floating-point) vari­able part_of_ip is ini­tialised to 0 and gets incre­ment­ed by a cer­tain val­ue until it exceeds Kpress. Then Plot() and PutZeros() are called, part_of_ip is reset and a new cycle starts… until all 142 ‘_’ of the com­pact poly­met­ric expres­sion have been read.

The incre­ment of part_of_ip in each cycle is:

part_of_ip += Kpress * tem­po­max / tempo;

In this sim­ple exam­ple, tem­po = 100, tem­po­max = 50 and Kpress =2. So the incre­ment is 1 and part_of_ip will reach the val­ue of Kpress after 2 cycles. This means that every oth­er ‘_’ will be skipped.

Incrementing ip requires a more com­pli­cat­ed process. The algo­rithm keeps track of the col­umn num­bers in the table as it would be cre­at­ed with Kpress = 1. These num­bers are usu­al­ly much larg­er than those of the actu­al phase dia­gram. The large num­ber i is mapped to ip using the Class() function:

unsigned long Class(double i) {
unsigned long result;
if(Kpress < 2.) return((unsigned long)i);
result = 1L + ((unsigned long)(floor(i) / Kpress));
return(result);
}

So, each cycle of read­ing ‘_’ in the toofast sit­u­a­tion ends up incre­ment­ing i and then updat­ing ip via the Class(i) func­tion. The incre­ment of i is:

prodtem­po - 1

in which:

prodtem­po = Prod / tempo

The vari­ables Prod and Kpress are cal­cu­lat­ed after the com­pact poly­met­ric expres­sion has been cre­at­ed. Prod is the low­est com­mon mul­ti­ple (LCM) of all tem­po val­ues, i.e. ‘100’ in this example.

Let us use the inte­gers Pclock and Qclock to define the metronome val­ue as Qclock * 60 / Pclock. If the metronome is set to its default val­ue of 60 bpm, then Pclock = Qclock = 1.

The fol­low­ing (sim­pli­fied) code cal­cu­lates Kpress and updates Prod accordingly:

Kpress = 1. + (Quantization * Qclock * Prod) / Pclock / 1000.;
if(Kpress > 1.) {
s = LCM(Prod, Kpress) / Prod;
if(s > 1. && s < 10. && Prod < 1000000.) Prod = Round(s * Prod);
s = Round(Prod / Kpress);
if(s > 10.) Prod = Kpress * s;
}

As expect­ed, we get the fol­low­ing sound-object graph:

Sound-object graph of C4 35542/24783 D5.
The silence dura­tion is 35542/24783 = 1.43 beats.

A more complex structure

This is a phrase from Liszt’s 14th Hungarian Rhapsody:

_tempo(80/39) {F1, C2} {2, F2} 667/480 {53/480, G1, G2} {1/2, Ab1, Ab2} {1/2, B1, B2}

The com­pact poly­met­ric expres­sion — with a some redun­dant ratios removed — is:

*39/80 {F1, C2} {F2 _} *13/12800 - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ *689/12800 {G1, G2} *39/160 {Ab1, Ab2} {B1, B2}

which gives the fol­low­ing sound-object graph:

_tempo(80/39){F1,C2}_tempo(80/39){2,F2}667/480 {53/480,G1,G2}{1/2,Ab1,Ab2}{1/2,B1,B2}
Metronome is set to 60 beats per minute.

Let us cal­cu­late the dura­tion of the silence between “F2” and “G1” in two ways:

  1. In the source poly­met­ric expres­sion, this silence is notat­ed as 667/480. Since the tem­po is 80/39, its dura­tion should be 667/480 * 39/80 = 0.67 beats (con­firmed by the graph).
  2. In the com­pact poly­met­ric expres­sion, we find one ‘-’ object fol­lowed by 666 ‘_’ pro­lon­ga­tions at a speed of *13/12800. The dura­tion is there­fore 667 * 13/12800 = 0.67 beats.

It would be dif­fi­cult to fol­low the algo­rithm step by step because Prod = 2496100 , Kpress = 24961 and tem­po­max = Prod / Kpress = 100. Within the silence, tem­po = 985 and the incre­ment of part_of_ip is 24961 * 100 / 985 = 2 534.11167… The num­ber of cycles before part_of_ip reach­es the val­ue of Kpress is ceil(9.85) = 10. This means that 9 out of 10 objects ‘_’ have been skipped.

Conclusion

These exam­ples and expla­na­tions pro­vide insight into the code in the “FillPhaseDiagram.c” file of the con­sole code. We hope that it will be use­ful for future devel­op­ment or migra­tion of algorithms.

This is also a demon­stra­tion of the com­plex­i­ty of time cal­cu­la­tions when deal­ing with poly­met­ric struc­tures capa­ble of car­ry­ing all the details of real musi­cal works — see Importing MusicXML scores for “real life” examples.

Leave a Reply

Your email address will not be published. Required fields are marked *