ヘルパー:リズム
factory.js のうちリズム生成・パターン操作に使うヘルパーをまとめます。onBeat のコールバックを「飾る」発想で組み合わせると、短いコードで多様なリズムが作れます。
ユークリッドリズム
Section titled “ユークリッドリズム”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 cinquilloeuclid(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 キック }});よく使うパターン早見表
Section titled “よく使うパターン早見表”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 系)
高階パターン(HOF)
Section titled “高階パターン(HOF)”コールバックを ラップして パターン操作を追加する関数群。それぞれ独立したクロージャを返すので、onBeat の引数としてそのまま渡せます。
| 関数 | シグネチャ | 内容 |
|---|---|---|
every(n, fn) | (n, fn) → fn' | n 回に 1 度(1 回目, n+1 回目, …)だけ fn を呼ぶ |
sometimes(p, fn) | (p, fn) → fn' | 確率 p で fn を呼ぶ |
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()を使った独自ラッパを作ってください(レシピ:再現可能な乱数 参照)。
every(n, fn)
Section titled “every(n, fn)”いつ使うか: 「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。
sometimes(p, fn) / often(fn) / rarely(fn)
Section titled “sometimes(p, fn) / often(fn) / rarely(fn)”いつ使うか: 「ヒューマンな揺らぎ」「ゴーストノートの確率追加」「フィルの確率発生」などランダム性を入れたい時。
// 1/4 の確率でゴーストノートを追加onBeat( rarely((b) => { setNote({ pitch: 38, velocity: 30, startBeat: b, lengthBeats: 0.05 }); }),);degrade(p, fn)
Section titled “degrade(p, fn)”いつ使うか: 「P 確率で 抜く」と書きたい時。意味的には sometimes(1-p, fn) と同じだが、コードの意図が「減らしたい」場合に読みやすい。
// 50% の確率で抜く(半分くらい鳴る)onBeat( degrade(0.5, (b) => { if (Number.isInteger(b)) noteOn(42, 0.05, 70); // ハイハット }),);組み合わせる
Section titled “組み合わせる”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% だけ }),);解像度を変える
Section titled “解像度を変える”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 === 0はb = 0.5などで偽陽性になることがあります(実装上は問題ありませんが、別解像度を扱う際は注意)。