コンテンツにスキップ

ヘルパー:リズム

factory.js のうちリズム生成・パターン操作に使うヘルパーをまとめます。onBeat のコールバックを「飾る」発想で組み合わせると、短いコードで多様なリズムが作れます。

euclid(pulses, steps, rotateBy = 0): boolean[]

Section titled “euclid(pulses, steps, rotateBy = 0): boolean[]”

pulses 個のパルスを steps ステップに均等に配置した真偽配列を返します。rotateBy で開始位置をシフト。

いつ使うか: ステップシーケンサで「N 拍中に M 個鳴らす」を書きたい時。手で並べるより整った間隔になり、ジャンルの定番リズムが一発で出ます。

最小例

euclid(3, 8); // [T,F,F,T,F,F,T,F] tresillo(ラテンの基本)
euclid(5, 8); // [T,F,T,T,F,T,T,F] Cuban cinquillo
euclid(2, 5); // [T,F,T,F,F] Khafif-e-ramal

実用例:4 拍クリップに kick の Tresillo

const pat = euclid(3, 8); // 8 ステップ = 4 拍を 8 分で
onBeat((b) => {
const i = Math.floor(b * 2); // 1/2 拍 = 8 分の通し番号
if (Math.abs(b * 2 - i) < 1e-6 && pat[i % pat.length]) {
noteOn(36, 0.1, 100, b); // C1 キック
}
});
euclid(p, s)用途・呼び方
euclid(2, 5)Khafif-e-ramal(5 拍子の基礎)
euclid(3, 4)4 拍中 3 つ — 三連的
euclid(3, 7)ルーマニアン
euclid(3, 8)Tresillo / Cumbia
euclid(4, 7)サンバ系
euclid(5, 8)Cinquillo(キューバ)
euclid(5, 12)アフリカン
euclid(5, 16)アンビエント・スパース
euclid(7, 12)Pygmy / West African bell

[!TIP] rotateBy を 1 ずつずらすと「裏拍に重心が来る」「逆向きに感じる」パターンが得られます。リズム生成の常套手段。

euclid(3, 8, 0); // [T,F,F,T,F,F,T,F]
euclid(3, 8, 1); // [F,T,F,F,T,F,F,T]
euclid(3, 8, 2); // [F,F,T,F,F,T,F,T] (Bossa nova 系)

コールバックを ラップして パターン操作を追加する関数群。それぞれ独立したクロージャを返すので、onBeat の引数としてそのまま渡せます。

関数シグネチャ内容
every(n, fn)(n, fn) → fn'n 回に 1 度(1 回目, n+1 回目, …)だけ fn を呼ぶ
sometimes(p, fn)(p, fn) → fn'確率 pfn を呼ぶ
degrade(p, fn)(p, fn) → fn'確率 p捨てるsometimes(1-p, fn) 相当)
often(fn)(fn) → fn'sometimes(0.75, fn)
rarely(fn)(fn) → fn'sometimes(0.25, fn)

[!IMPORTANT] HOF の確率系(sometimes / degrade / often / rarely)は Math.random() ベース でシードの影響を受けません。再現性が必要なら seededRandom() を使った独自ラッパを作ってください(レシピ:再現可能な乱数 参照)。

いつ使うか: 「n 回に 1 回だけアクセント」「8 拍ごとにキックを抜く」など、周期的に間引く とき。

// 4 回に 1 回だけアクセント
onBeat(
every(4, (b) => {
setNote({ pitch: 36, velocity: 120, startBeat: b, lengthBeats: 0.1 });
}),
);

注意点: every(n, fn)「呼び出された回数」 をカウントします。onBeat のグリッドは 16 分音符なので、every(4, fn) だと 4 ステップごと(= 1 拍ごと)に発火します。「N 拍ごと」が欲しいなら n = N * stepsPerBeat

いつ使うか: 「ヒューマンな揺らぎ」「ゴーストノートの確率追加」「フィルの確率発生」などランダム性を入れたい時。

// 1/4 の確率でゴーストノートを追加
onBeat(
rarely((b) => {
setNote({ pitch: 38, velocity: 30, startBeat: b, lengthBeats: 0.05 });
}),
);

いつ使うか: 「P 確率で 抜く」と書きたい時。意味的には sometimes(1-p, fn) と同じだが、コードの意図が「減らしたい」場合に読みやすい。

// 50% の確率で抜く(半分くらい鳴る)
onBeat(
degrade(0.5, (b) => {
if (Number.isInteger(b)) noteOn(42, 0.05, 70); // ハイハット
}),
);

HOF はチェインできます。外側から内側へ 評価されるので、組み合わせる順序で挙動が変わります。

// 4 ステップに 1 回 + そのうち 50% で発火 = 期待値 1/8 で発火
onBeat(
every(
4,
sometimes(0.5, (b) => {
setNote({ pitch: 60, startBeat: b, lengthBeats: 0.25 });
}),
),
);
// 「裏拍だけ 30% で抜く」 — 通常 + 抜き分けを 2 つ並べる
onBeat((b) => {
if (Number.isInteger(b)) noteOn(36, 0.1); // 表拍は常に
});
onBeat(
degrade(0.3, (b) => {
if (Math.abs((b % 1) - 0.5) < 1e-6) noteOn(38, 0.1); // 裏拍は 70% だけ
}),
);

onBeat のグリッドは 16 分音符固定 です。三連符など別解像度を使いたい場合は b を見て間引きます。

// 三連符(1 拍を 3 分割)
onBeat((b) => {
if (Math.abs((b * 3) % 1) < 1e-6) {
setNote({ pitch: 67, startBeat: b, lengthBeats: 1 / 3 });
}
});
// 8 分グリッド
onBeat((b) => {
if (Math.abs((b * 2) % 1) < 1e-6) noteOn(60, 0.5);
});
// 全音符(1 拍ごと)
onBeat((b) => {
if (Number.isInteger(b)) noteOn(60, 1);
});

[!TIP] 浮動小数点の比較は < 1e-6 のような誤差トレランスを使ってください。b % 1 === 0b = 0.5 などで偽陽性になることがあります(実装上は問題ありませんが、別解像度を扱う際は注意)。