This algorithm is used by the PHP interface of Bol Processor ‘BP3’ under development. Read:
https://bolprocessor.org/misc/BP3/CheckList.html#timebase
It is implemented in function polymetric_expression() of file _basic_tasks.php (read below).
The function creates a polymetric expression equivalent to the rhythmic structure programmed on a Timebase page. We will take for instance a structure which does not sound musical because of its odd ratios:
The metronome speed is 208.571 beats per minute specified as 73 beats in 21 seconds. Bol Processor always compute durations as integer ratios.
Let us define p_clock = 73 and q_clock = 21.
This structure contains 2 tracks. The following algorithm deals with any number of tracks.
- Track 1 is a cycle of 5 beats [TickCycle] and its speed ratio is 3/2 [Ptick/Qtick]. It produces note C8 (in English notation) at velocity 120 (a MIDI parameter) and duration 10 milliseconds.
- Track 2 is a cycle of 3 beats [TickCycle] and its speed ratio is 4/5 [Ptick/Qtick]. It produces note C7 at velocity 120 and duration 10 milliseconds.
Tick cycles are made explicit by checked/unchecked boxes. Unchecked boxes denote silences ‘-’:
- C8 - C8 - - for the first track
- - C7 C7 for the second track
The rhythmic structure will be represented as a polymetric structure with 2 fields {Track1, Track2} in which fields are forced to the same symbolic duration by the polymetric expansion algorithm (described here). We need to design its content so that the expansion does not alter the tempo declared on each track.
To this effect, consider the symbolic duration of each track:
(q_clock / p_clock) * (TickCycle * Qtick) / Ptick
In order to match symbolic durations, each track should be repeated adequately:
repeat1 * (TickCycle1 * Qtick1) / Ptick1 = repeat2 * (TickCycle2 * Qtick2) / Ptick2 = …
Ratio (q_clock / p_clock) being identical for each track
was eliminated from this equation.
We need to find the set of smallest integers repeat1, repeat2 etc. satisfying this equation.
To this effect, we simplify ratio (TickCycle * Qtick) / Ptick using the greatest common divisor (GCD):
x = GCD((TickCycle * Qtick), Ptick)
y = (TickCycle * Qtick) / x
Then we calculate the least common multiple (LCM) of all y values:
lcm = LCM(y1, y2, …)
This yields for each field:
repeat = (lcm * Ptick) / (TickCycle * Qtick)
In practice,
x1 = GCD((TickCycle1 * Qtick1), Ptick1) = GCD((5 * 2), 3) = 1
y1 = (TickCycle1 * Qtick1) / x1 = (5 * 2) / 1 = 10
x2 = GCD((TickCycle2 * Qtick2), Ptick2) = GCD((3 * 5), 4) = 1
y2 = (TickCycle2 * Qtick2) / x2 = (3 * 5) / 1 = 15
lcm = LCM(10,15) = 30
repeat1 = (lcm * Ptick1) / (TickCycle1 * Qtick1) = (30 * 3) / (5 * 2) = 9
repeat2 = (lcm * Ptick2) / (TickCycle2 * Qtick2) = (30 * 4) / (3 * 5) = 8
The structure will therefore look like:
{Track1 Track1 Track1 Track1 Track1 Track1 Track1 Track1 Track1, Track2 Track2 Track2 Track2 Track2 Track2 Track2 Track2}
We need to include performance parameters setting the duration of ticks. To this effect, we use the _staccato(s) parameter in which s is the percentage of cropped duration at the end of the sound-event. Durations have been set to 10 milliseconds. We calculate the period of each track:
period1 = (((1000 * q_clock) / p_clock) * Qtick1) / Ptick1 = (((1000 * 21) / 73) * 2) / 3 = 192 ms
s1 = 100 * (192 - 10) / 192 = 94%
period2 = (((1000 * q_clock) / p_clock) * Qtick2) / Ptick2 = (((1000 * 21) / 73) * 5) / 4 = 359 ms
s2 = 100 * (359 - 10) / 359 = 97%
Finally, we specify the tempo of each field: 3/2 for the first one and 4/5 for the second one.
The resulting polymetric structure is:
{_tempo(3/2) _chan(1) _vel(120) _staccato(94) C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - , _tempo(4/5) _chan(1) _vel(120) _staccato(97) - C7 C7 - C7 C7 - C7 C7 - C7 C7 - C7 C7 - C7 C7 - C7 C7 - C7 C7}
Note that _tempo(4/5) does not need to be specified because it will be infered by the polymetric expansion algorithm. Therefore, the expression displayed at the bottom of the Timebase page is:
{_tempo(3/2) _chan(1) _vel(120) _staccato(94) C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - C8 - C8 - - , _chan(1) _vel(120) _staccato(97) - C7 C7 - C7 C7 - C7 C7 - C7 C7 - C7 C7 - C7 C7 - C7 C7 - C7 C7}
Implementation in PHP
The following is the code in file _basic_tasks.php.
Table $mute contains flags indicating whether a track is active or mute. Function key_to_note() returns the name of a note in English convention given its key number (MIDI).