rhythmical
This lib is able to parse and manipulate musical rhythms by using nested arrays and algebraic fractions.
State
This lib is a work in progress.
Example Showcase
Here are some example usages of the lib, to get you hooked.
Example 1: Rendering for playback
Lets spell the famous bolero rhythm in nested notation:
const bolero = // bar 1 1 1 1 1 // beat 1 1 1 1 1 // beat 2 1 1 // beat 3 // bar 2 1 1 1 1 // beat 1 1 1 1 1 // beat 2 1 1 1 1 1 1 // beat 3 ;
Compared with the standard rhythm notation:
- it is a direct representation in array format
- you could even see the inner brackets as beams
- no need to specify any durations / time signatures
We can now render it and play it back with Tone.js:
; // render eventsconst events = Rhythm; // play back with tonalvar synth = ;const part = synth events // <- the events are used herestart0;partloop = true;partloopEnd = 3;ToneTransportstart'+1';
Example 2: Edit Groupings
Lets take the afrobell in 4/4:
const afrobell4 = 2 0 2 0 1 2 0 2 0 2 0 1;
We can remove the triplet groupings with ungroup:
const afrobell = Rhythm;
...which returns the "raw" afrobell pattern:
2 0 2 0 1 2 0 2 0 2 0 1;
We can now apply another grouping:
const afrobell3 = Rhythm;
2 0 2 0 1 2 0 2 0 2 0 1;
Example 3: Insert
You can insert rhythms into one another. Take the rhythm of a famous rock song riff:
const smoke = 1 1 15 0 1 0 1 0 1 2 0;
As you may know, the riff of the original is 4 bars long. Let's build the next two bars, reusing the first bar:
const smokeOn = Rhythm;
yields:
1 1 15 0 1 // bar 1 0 1 0 1 2 0 // bar 2 1 1 15 0 1 // bar 3 = copied bar 1 0 7 0 0 0 // inserted bar 4;
Note: The first argument is expected to be ungrouped, while the second argument is expected to have groups. This allows you to reuse rhythmic blocks independent of time signatures.
insert at beat
The insert method has a third argument, which specifies the beat of insertion:
Rhythm; // from leftRhythm; // from right
both yield
1 2 3 4;
Design Goals
- Keep it functional: inputs and outputs can be transformed using function chaining
- Keep it immutable: arrays/objects never change
- Keep it static: no class instances => outputs are primitives, objects or arrays
- strictly typed: typescript first
- Influenced by Tonal, Tidal Cycles and Impro-Visor
API
NestedRhythm
A NestedRhythm is an easy way to notate rhythms:
const fourToTheFlour = 'A' 'C' 'E' 'G';const waltz = 'C' 'E' 'E' 'G' 'G';const swingCymbal = 1 2 0 1 1 2 0 1;const swingHihat = 0 1 0 1;
- By using just nested arrays, you can express any musical rhythm
- The notation is very similar to normal musical notation
- Similar concept also used by TidalCycles (or see Tone#Sequence)
- The actual content can be any type
To be able to play a rhythm we need:
- absolute time
- absolute duration
We can use Rhythm.render to calculate that:
Rhythm.render
render(NestedRhythm, duration)
Turns a NestedRhythm to a flat array of TimedEvent:
Rhythm;
yields:
value: 'C' time: 0 duration: 1 path: 0 4 value: 'E' time: 1 duration: 05 path: 1 4 0 2 value: 'G' time: 15 duration: 05 path: 1 4 1 2 value: 'B' time: 2 duration: 1 path: 2 4 value: 'D' time: 3 duration: 1 path: 3 4 ;
- array is now one dimensional
- time and duration are calculated based on the path fractions
Using numbers as duration
Using numbers, we can adjust the duration:
Rhythm;
value: 1 time: 0 duration: 1 path: 0 4 value: 2 time: 1 duration: 1 path: 1 4 0 2 value: 2 time: 2 duration: 1 path: 3 4 value: 1 time: 35 duration: 05 path: 3 4 ;
- duration is now multiplied by value
Rhythm.flat
Converts a NestedRhythm to a one dimensional Array of FlatEvent. This is like render but without the absolute calculations:
const swingCymbal = 1 // one 2 0 1 // two with "swing" off 1 // three 2 0 1 // four with "swing" off;Rhythm;
outputs
value: 1 path: 0 4 value: 2 path: 1 4 0 3 value: 0 path: 1 4 1 3 value: 1 path: 1 4 2 3 value: 1 path: 2 4 value: 2 path: 3 4 0 3 value: 0 path: 3 4 1 3 value: 1 path: 3 4 2 3 ;
FlatEvent
A FlatEvent consists of
- path: path of fractions to keep the nesting information
- value: the original value
Rhythm.time
Calculates time of path fractions:
Rhythm); // yields 0.25 // = (0 + 1/2) / 2 Rhythm); // yields 0.5 // = (1 + 0/2) / 2 Rhythm); // yields 0.75 // = (0 + 3/4) / 1 Rhythm); // yields 0 // = (0 + 0/1) / 1 ; // = 4 * (1 + 1/4) / 4
- the second argument is the time of the whole sequence. If the unit is seconds, we would have a 4s measure, with 4 beats in the bar => 60bpm.
Rhythm.duration
Calculates the duration of a given path:
Rhythm); // 4* 4/3 = 1/3Rhythm); // 4* 4/2 = 1/2
- This method can be used to determine the length of a note
- the first one could be the length of a triplet in 60bpm
- the second one could be the length of an eights in 60bpm
Rhythm.calculate
Calculates time and duration from a FlatEvent. This method is used by render:
const calculated = Rhythm ;
yields
value: 1 time: 0 duration: 1 value: 0 time: 1 duration: 0 value: 3 time: 15 duration: 15 value: 0 time: 2 duration: 0 value: 1 time: 3 duration: 1 ;
- the time value is now the absolute time inside the defined 4s
- the duration is the length of the note inside the subdivision
This format can be used easily to schedule playback, e.g. using Tone.js (see Example 1 at the top)
Playback
The following function could be used to play notes with Tone.js:
{ const events = Rhythm; const part = { synth; } eventsstart0; partloop = true; partloopEnd = cycle; ToneTransportstart'+1';} var pluck = ;;
Polyrythms
Polyrhythm = different pulse, same duration
;;
- The two Rhythms will be played in the same amount of time.
- Having two different pulses (3 and 4), this will create a polyrhythm
Polymeter
Polymeter = same pulse, different length
;;
- The two Rhythms will be played in the same amount of time.
- Having two different pulses (3 and 4), this will create a polyrhythm
Rhythm.spm()
A little helper function to get the seconds per measure:
// polymeter;;// polyrhythm;;
Rhythm.combine
addGroove(string[]): {[chord: string]: number[]}
;
yields
where each chord is mapped to its personal "track". Playing all the tracks at the same time should return a a seamless rhythm.