jevstrudel.git / packages / core / euclid.mjs
1/*
2euclid.mjs - Bjorklund/Euclidean/Diaspora rhythms
3Copyright (C) 2023 Rohan Drape and strudel contributors
4
5See <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/euclid.mjs> for authors of this file.
6
7The Bjorklund algorithm implementation is ported from the Haskell Music Theory Haskell module by Rohan Drape -
8https://rohandrape.net/?t=hmt
9
10This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>.
11*/
12
13import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs';
14import { rotate, flatten, splitAt, zipWith } from './util.mjs';
15import Fraction, { lcm } from './fraction.mjs';
16
17const left = function (n, x) {
18  const [ons, offs] = n;
19  const [xs, ys] = x;
20  const [_xs, __xs] = splitAt(offs, xs);
21  return [
22    [offs, ons - offs],
23    [zipWith((a, b) => a.concat(b), _xs, ys), __xs],
24  ];
25};
26
27const right = function (n, x) {
28  const [ons, offs] = n;
29  const [xs, ys] = x;
30  const [_ys, __ys] = splitAt(ons, ys);
31  const result = [
32    [ons, offs - ons],
33    [zipWith((a, b) => a.concat(b), xs, _ys), __ys],
34  ];
35  return result;
36};
37
38const _bjorklund = function (n, x) {
39  const [ons, offs] = n;
40  return Math.min(ons, offs) <= 1 ? [n, x] : _bjorklund(...(ons > offs ? left(n, x) : right(n, x)));
41};
42
43export const bjorklund = function (ons, steps) {
44  const inverted = ons < 0;
45  const absOns = Math.abs(ons);
46  const offs = steps - absOns;
47  const ones = Array(absOns).fill([1]);
48  const zeros = Array(offs).fill([0]);
49  const result = _bjorklund([absOns, offs], [ones, zeros]);
50  const pattern = flatten(result[1][0]).concat(flatten(result[1][1]));
51  return inverted ? pattern.map((x) => 1 - x) : pattern;
52};
53
54/**
55 * Changes the structure of the pattern to form an Euclidean rhythm.
56 * Euclidean rhythms are rhythms obtained using the greatest common
57 * divisor of two numbers.  They were described in 2004 by Godfried
58 * Toussaint, a Canadian computer scientist.  Euclidean rhythms are
59 * really useful for computer/algorithmic music because they can
60 * describe a large number of rhythms with a couple of numbers.
61 *
62 * @memberof Pattern
63 * @name euclid
64 * @tags temporal
65 * @param {number} pulses the number of onsets/beats
66 * @param {number} steps the number of steps to fill
67 * @returns Pattern
68 * @example
69 * // The Cuban tresillo pattern.
70 * note("c3").euclid(3,8)
71 */
72
73/**
74 * Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
75 * @memberof Pattern
76 * @name euclidRot
77 * @tags temporal
78 * @param {number} pulses the number of onsets/beats
79 * @param {number} steps the number of steps to fill
80 * @param {number} rotation offset in steps
81 * @returns Pattern
82 * @example
83 * // A Samba rhythm necklace from Brazil
84 * note("c3").euclidRot(3,16,14)
85 */
86
87/**
88 * @example // A thirteenth-century Persian rhythm called Khafif-e-ramal.
89 * note("c3").euclid(2,5)
90 * @example // The archetypal pattern of the Cumbia from Colombia, as well as a Calypso rhythm from Trinidad.
91 * note("c3").euclid(3,4)
92 * @example // Another thirteenth century Persian rhythm by the name of Khafif-e-ramal, as well as a Rumanian folk-dance rhythm.
93 * note("c3").euclidRot(3,5,2)
94 * @example // A Ruchenitza rhythm used in a Bulgarian folk dance.
95 * note("c3").euclid(3,7)
96 * @example // The Cuban tresillo pattern.
97 * note("c3").euclid(3,8)
98 * @example // Another Ruchenitza Bulgarian folk-dance rhythm.
99 * note("c3").euclid(4,7)
100 * @example // The Aksak rhythm of Turkey.
101 * note("c3").euclid(4,9)
102 * @example // The metric pattern used by Frank Zappa in his piece titled Outside Now.
103 * note("c3").euclid(4,11)
104 * @example // Yields the York-Samai pattern, a popular Arab rhythm.
105 * note("c3").euclid(5,6)
106 * @example // The Nawakhat pattern, another popular Arab rhythm.
107 * note("c3").euclid(5,7)
108 * @example // The Cuban cinquillo pattern.
109 * note("c3").euclid(5,8)
110 * @example // A popular Arab rhythm called Agsag-Samai.
111 * note("c3").euclid(5,9)
112 * @example // The metric pattern used by Moussorgsky in Pictures at an Exhibition.
113 * note("c3").euclid(5,11)
114 * @example // The Venda clapping pattern of a South African children’s song.
115 * note("c3").euclid(5,12)
116 * @example // The Bossa-Nova rhythm necklace of Brazil.
117 * note("c3").euclid(5,16)
118 * @example // A typical rhythm played on the Bendir (frame drum).
119 * note("c3").euclid(7,8)
120 * @example // A common West African bell pattern.
121 * note("c3").euclid(7,12)
122 * @example // A Samba rhythm necklace from Brazil.
123 * note("c3").euclidRot(7,16,14)
124 * @example // A rhythm necklace used in the Central African Republic.
125 * note("c3").euclid(9,16)
126 * @example // A rhythm necklace of the Aka Pygmies of Central Africa.
127 * note("c3").euclidRot(11,24,14)
128 * @example // Another rhythm necklace of the Aka Pygmies of the upper Sangha.
129 * note("c3").euclidRot(13,24,5)
130 */
131
132const _euclidRot = function (pulses, steps, rotation) {
133  const b = bjorklund(pulses, steps);
134  if (rotation) {
135    return rotate(b, -rotation);
136  }
137  return b;
138};
139
140export const euclid = register('euclid', function (pulses, steps, pat) {
141  return pat.struct(_euclidRot(pulses, steps, 0));
142});
143
144export const bjork = register('bjork', function (euc, pat) {
145  if (!Array.isArray(euc)) {
146    euc = [euc];
147  }
148  const [pulses, steps = pulses, rot = 0] = euc;
149  return pat.struct(_euclidRot(pulses, steps, rot));
150});
151
152export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], function (pulses, steps, rotation, pat) {
153  return pat.struct(_euclidRot(pulses, steps, rotation));
154});
155
156/**
157 * Similar to `euclid`, but each pulse is held until the next pulse,
158 * so there will be no gaps.
159 * @name euclidLegato
160 * @memberof Pattern
161 * @tags temporal
162 * @param {number} pulses the number of onsets/beats
163 * @param {number} steps the number of steps to fill
164 * @param rotation offset in steps
165 * @param pat
166 * @example
167 * note("c3").euclidLegato(3,8)
168 */
169
170const _euclidLegato = function (pulses, steps, rotation, pat) {
171  if (pulses < 1) {
172    return silence;
173  }
174  const bin_pat = _euclidRot(pulses, steps, 0);
175  const gapless = bin_pat
176    .join('')
177    .split('1')
178    .slice(1)
179    .map((s) => [s.length + 1, true]);
180  return pat.struct(timeCat(...gapless)).late(Fraction(rotation).div(steps));
181};
182
183export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) {
184  return _euclidLegato(pulses, steps, 0, pat);
185});
186
187/**
188 * Similar to `euclid`, but each pulse is held until the next pulse,
189 * so there will be no gaps, and has an additional parameter for 'rotating'
190 * the resulting sequence
191 * @name euclidLegatoRot
192 * @memberof Pattern
193 * @tags temporal
194 * @param {number} pulses the number of onsets/beats
195 * @param {number} steps the number of steps to fill
196 * @param {number} rotation offset in steps
197 * @example
198 * note("c3").euclidLegatoRot(3,5,2)
199 */
200export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
201  return _euclidLegato(pulses, steps, rotation, pat);
202});
203
204/**
205 * A 'euclid' variant with an additional parameter that morphs the resulting
206 * rhythm from 0 (no morphing) to 1 (completely 'even'). For example
207 * `sound("bd").euclidish(3,8,0)` would be the same as
208 * `sound("bd").euclid(3,8)`, and `sound("bd").euclidish(3,8,1)` would be the
209 * same as `sound("bd bd bd")`. `sound("bd").euclidish(3,8,0.5)` would have a
210 * groove somewhere between.
211 * Inspired by the work of Malcom Braff.
212 * @name euclidish
213 * @synonyms eish
214 * @memberof Pattern
215 * @tags temporal
216 * @param {number} pulses the number of onsets
217 * @param {number} steps the number of steps to fill
218 * @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse)
219 * @example
220 * sound("hh").euclidish(7,12,sine.slow(8))
221 * .pan(sine.slow(8))
222 */
223export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) {
224  const morphed = _morph(bjorklund(pulses, steps), new Array(pulses).fill(1), perc);
225  return pat.struct(morphed).setSteps(steps);
226});