Rhythmical Ties
July 10, 2020
In modern music, ties across barlines are a common way of adding interest to a melody. For example, take the beginning of the tune "Blue Monk" by Thelonious Monk:
In this post, I want to implement that for rhythmical. Syntax proposal:
[['d4', 'eb4', 'e4', 'f4'], '_'];
The "_" sign will tie the note to the previous.
Render Implementation
If we render the above events:
renderRhythmObject({
duration: 8,
sequential: [['d4', 'eb4', 'e4', 'f4'], '_'],
});
... the result (without path) looks like this:
[
{
"value": "d4",
"time": 0,
"duration": 1
},
{
"value": "eb4",
"time": 1,
"duration": 1
},
{
"value": "e4",
"time": 2,
"duration": 1
},
{
"value": "f4",
"time": 3,
"duration": 1
},
{
"value": "_",
"time": 4,
"duration": 4
}
]
The last event would not be suitable for playback, as "_" is not a valid note name.
To fix that, we can apply a tie by adding its duration to the preceeding event:
const tieReducer = (filter) => (events, event, index, array) => {
if (filter) {
array = array.filter(filter);
}
// check if next event is a tie
if (index + 1 < array.length && array[index + 1].value === '_') {
return events.concat([{ ...event, duration: event.duration + array[index + 1].duration }]); // adds duration of next event to current
}
if (event.value === '_') {
return events; // ignore tie
}
return events.concat([event]); // next event is no tie
};
// to apply the reducer, we use it with reduce on the rendered events:
renderRhythmObject({
duration: 8,
sequential: [['d4', 'eb4', 'e4', 'f4'], '_'],
}).reduce(tieReducer(), []);
Result (again without path):
[
{
"value": "d4",
"time": 0,
"duration": 1
},
{
"value": "eb4",
"time": 1,
"duration": 1
},
{
"value": "e4",
"time": 2,
"duration": 1
},
{
"value": "f4",
"time": 3,
"duration": 5
}
]
Now the last event has a duration of 5, which is 1 (duration of "f4") + 4 (duration of "_")! These events are now suitable for playback:
Forgive me that this example doesn't swing like it should.. you have to wait for that feature to be implemented...
Score implementation
In a score, we have to keep the events seperated, but add the tie flag:
export function rhythmicalScore(rhythm: NestedRhythm<string>) {
return Rhythm.render(rhythm, rhythm.length)
.map((e) => [e.value, Math.floor(e.time), 1 / e.duration])
.reduce((groups: any[][], [note, bar, duration]) => {
// added this part
let tie = false;
if (note === '_') {
let lastNote;
if (groups.length && groups[groups.length - 1].length) {
const lastGroup = groups[groups.length - 1];
lastNote = lastGroup[lastGroup.length - 1];
}
note = lastNote ? lastNote.key : 'r';
tie = lastNote ? true : false;
}
// end of tie logic
if (!groups.length || bar > groups.length - 1) {
groups.push([]);
}
if (duration === 4) {
duration = 'q';
}
if (note === 'r') {
duration = duration + 'r';
note = 'b4';
}
groups[groups.length - 1].push({ key: note, duration, tie }); // changed to object syntax
return groups;
}, []);
}
Now the tie symbol works in the score:
<Score width={600} height={100} staves={rhythmicalScore([[['d4', 'eb4', 'e4', 'f4'], '_']])} />
sandman
Now here's a more sophisticated (non swung) example:
Caution: Only press play if you're on WIFI as it uses ~46MB of samples
Next Steps
In a future post, I want to implement more "post processing" features, which will operate on the rendered events, like:
- swing
- dynamics from pitch
- auto tie (do not attack repeated notes again)
- test tie feature in a polyphonic setting (could need extra fixes)