Complex ratios in polymetric expressions

This page is for devel­op­ers of Bol Processor BP3 (read instal­la­tion). It is not a for­mal descrip­tion of 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 that may be help­ful for check­ing or extend­ing algorithms.

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

Syntax of silences

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

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

For instance, “C4 C5 3/2 D5 E5″ yields 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 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 total dura­tion 3 1/2 (3.5) beats. This silence is the first field of the poly­met­ric expres­sion (explained below). This yields the fol­low­ing piano roll:

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

Syntax of tempo

Any sequence of sym­bols com­pli­ant with Bol Processor syn­tax is processed as a poly­met­ric expres­sion. Typical forms are:

  • field 1, field2 indi­cat­ing that field1 and field2 should be super­posed and the total dura­tion should be that of field1;
  • field1.field2 indi­cat­ing that field1 and field2 should be in sequence where the dura­tion of each field should be that of field1;
  • {expres­sion} is equiv­a­lent to expres­sion.

Brackets ‘{‘ and ’}’ are used to pro­duce mul­ti­level expressions.

A set of exam­ples of poly­met­ric expres­sions may be found on tuto­r­i­al Polymetric struc­tures.

For instance, {{C4 D4, E4 F4 G4}, E5} yields the fol­low­ing structure:

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

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

*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 irre­spec­tive of 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 on the graph.

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 named PolyExpand() in file “Polymetric.c”.

At this stage it is impor­tant not to con­fuse 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­tions of units to 2/3 of the metronome peri­od. This is an absolute tem­po mark­er. Equivalently, “*4″ mul­ti­plies dura­tions by 4, and “*1/5″ or “/5″ divides them by 5 — where­as “1/5″ is a silence last­ing 1/5 beat.

The third syn­tax is the one used by Bol Processor’s time-setting algo­rithms. Despite its syn­tac­ti­cal­ly valid­i­ty, we do not rec­om­mend using it in gram­mars and data because it may cre­ate con­flict­ing dura­tions in poly­met­ric struc­tures. For exam­ple, {*2/1 A4 B4, *3/1 A5 B5} does not make sense because it attempts to force the first field to dura­tion 2 x 2 = 4 beats and the sec­ond field to 3 x 2 = 6 beats. The prop­er (nev­er con­flict­ing) man­ner of chang­ing a tem­po in data or gram­mars is the “_tempo(x)” per­for­mance tool.

Expanding a polymetric expression

In the pre­ced­ing para­graph, we saw {{C4 D4, E4 F4 G4}, E5} rep­re­sent­ed inter­nal­ly 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 one con­tain­ing explic­it tem­po mark­ers. Therefore it the one main­tained along all 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 obtained click­ing the EXPAND but­ton on a Data page. Underline sym­bols ‘_’ rep­re­sent exten­sions of the dura­tion of the pre­ced­ing unit. These should not be con­fused with ‘-’ (silences). To make things clear, let us replace a ‘_’ with ‘-’:

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

This yields the fol­low­ing struc­ture in which “F4” is not extended:

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

The expand­ed poly­met­ric expres­sion may grow larg­er than com­pre­hen­sive to a human observ­er. In this case, only the com­pact ver­sion is returned.

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

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

Notes “C4”, “D4” etc. bear iden­ti­fiers kobj = 2, 3 etc. Identifier “0” is reserved for exten­sions ‘_’ and “1” for silences “-”, none of which is shown on the graph. An excep­tion is object #8 labelled «-» which is an out-time (null-duration) “silence” mark­ing the end of the struc­ture to facil­i­tate its syn­chro­niza­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 every col­umn is assigned a date (in phys­i­cal time). Cells of this phase dia­gram con­tain the iden­ti­fiers of sound-objects, includ­ing “0” and “1”. It is cre­at­ed by pro­ce­dure FillPhaseDiagram() in file “FillPhaseDiagram.c”.

It is easy to guess that the table would grow very large if com­pres­sion pro­ce­dures were not applied. For instance, Listz’s 14th Rhapsody would require no less than 9 x 1021 cells! The rea­son is that Bol Processor com­putes sym­bol­ic dura­tions as inte­ger ratios. A sym­bol­ic dura­tion of 5/3 will nev­er be replaced with “1.666” for two rea­sons: (1) round­ings would cumu­late as notice­able errors, and (2) we don’t know in advance how many dec­i­mals need to be kept. The phys­i­cal dura­tion of 5/3 beats depends on the metronome and the suc­ces­sion of “_tempo(x)” con­trols mod­i­fy­ing the tempo.

Let us first exam­ine a non-problematic 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. When the dia­gram grows larg­er, this phys­i­cal dura­tion may decrease beyond lim­it. This is where quan­ti­za­tion is invoked. It is set to 10 mil­lisec­onds by default, which means that two events occur­ring with­in than 10 ms may be writ­ten into the same col­umn. To this effect, the com­pact poly­met­ric struc­ture is rewrit­ten using a com­pres­sion rate (Kpress) that makes it fit a phase dia­gram of suit­able size.

If the musi­cal piece lasts for 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 instance more than 5 x 1012 for Beethoven’s Fugue in B-flat major.

Adding to the dif­fi­cul­ty, the algo­rithm must take care of sequences of events falling into the same col­umn. This sit­u­a­tion is sig­naled by vari­able toofast 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 toofast case, each event is writ­ten on a new line of the table in such a way that the sequen­tial order of the stream will be respected.

Thus, 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 per­formed with­in a sin­gle frame of 10 ms actu­al­ly include events ‘_’, name­ly 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” when either ‘p’ or ‘q’ goes beyond a lim­it which 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 Bol Processor’s con­sole code, ‘p’ and ‘q’ are actu­al­ly encod­ed as dou­ble floating-point num­bers whose man­tis­sa may con­tain as many dig­its as unsigned long inte­gers. Arithmetic oper­a­tions are per­formed on frac­tions. Each result­ing frac­tion is checked for com­plex­i­ty by a pro­ce­dure named Simplify() in file “Arithmetic.c”:

  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 divider (GCD).

Part (1) of the Simplify() pro­ce­dure gen­er­ates round­ing errors, yet 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

Ratio 36001/24000 can­not be sim­pli­fied. Nonetheless, 1/24000 beat would last 0.04 ms which is much less than the 10 ms quan­ti­za­tion. Therefore, 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 giv­en that 35542/24783 (1.43) is close to 1.5. However, the com­pu­ta­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 unique ‘-’ (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 com­plete expla­na­tion requires the poly­met­ric algo­rithm exposed here.

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

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

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

The case is dif­fer­ent with a silence last­ing 143/100 because the toofast sit­u­a­tion impos­es that less than 142 ‘_’ should be insert­ed after ‘-’. To this effect, a (floating-point) vari­able part_of_ip is ini­tial­ized to 0 and gets incre­ment­ed by a cer­tain val­ue until it reach­es beyond 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. Therefore the incre­ment is 1 and part_of_ip will reach the val­ue of Kpress after 2 cycles. This amounts to say­ing that one in two ‘_’ will be skipped.

Incrementing ip requires a more com­pli­cat­ed process. The algo­rithm keeps track of col­umn num­bers in the table as it would be cre­at­ed with Kpress = 1. These num­bers are gen­er­al­ly much larg­er than the ones of the actu­al phase dia­gram. The large num­ber i is mapped to ip via 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);
}

Thus, 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

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

Let us use 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 of 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 few redun­dant ratios delet­ed — is:

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

yield­ing 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 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 speed *13/12800. The dura­tion is there­fore 667 * 13/12800 = 0.67 beats.

Following the algo­rithm step by step would be tricky 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 in 10 objects ‘_’ have been skipped.

Conclusion

These exam­ples and expla­na­tions pro­vide insights for an eas­i­er com­pre­hen­sion of code in file “FillPhaseDiagram.c” of the con­sole’s code. We hope that it will serve for future devel­op­ment or migra­tion of algorithms.

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

Leave a Reply

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