Rhythmical Ties

rhythmical
code

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)

Felix Roos 2023