ヘルパー:数値・乱数・モジュレーション
リズム・音楽理論以外のヘルパーをまとめます。数値ユーティリティ、配列操作、乱数、LFO/envelope、チューニング変換、ステートフルなジェネレータ。
数値ユーティリティ
Section titled “数値ユーティリティ”clamp(v, min, max)
Section titled “clamp(v, min, max)”v を [min, max] の範囲に切り詰めます。
clamp(150, 0, 127); // 127clamp(-5, 0, 127); // 0clamp(60, 0, 127); // 60実用例: ピッチや velocity を MIDI 範囲に収める。
onNote((n) => { setNote({ ...n, pitch: clamp(n.pitch + 7, 0, 127) });});wrap(v, min, max)
Section titled “wrap(v, min, max)”モジュラに [min, max) に折り返します(max は含まない)。clamp と違って 値を保ったまま範囲内に収める。
wrap(7, 0, 5); // 2wrap(-1, 0, 5); // 4wrap(135, 0, 128); // 7実用例: ピッチをオクターブ内に折り畳む。
onNote((n) => { setNote({ ...n, pitch: wrap(n.pitch, 60, 72) }); // C4 オクターブ内に収める});lerp(a, b, t)
Section titled “lerp(a, b, t)”線形補間。t = 0 で a、t = 1 で b。
lerp(0, 100, 0.5); // 50lerp(60, 72, 0.25); // 63mapRange(v, ai, bi, ao, bo)
Section titled “mapRange(v, ai, bi, ao, bo)”v を [ai, bi] から [ao, bo] に線形リマップ。音楽プログラミングの定番。
// 0..1 の正規化値を CC 範囲(0..127)へmapRange(0.5, 0, 1, 0, 127); // 63.5
// velocity 60..120 を表現 0.4..1.0 にmapRange(80, 60, 120, 0.4, 1.0); // 0.6実用例: 元 velocity を CC1(modulation)に。
onNote((n) => { setNote({ ...n }); setCc({ ccNumber: 1, beat: n.startBeat, value: mapRange(n.velocity, 0, 127, 30, 100), });});配列ユーティリティ
Section titled “配列ユーティリティ”| 関数 | シグネチャ | 内容 |
|---|---|---|
range(start, end, step = 1) | 等差数列。step 負も可 | range(0, 4) → [0,1,2,3] |
rotate(arr, n) | 左回転(負で右回転)。新配列 | rotate([1,2,3,4], 1) → [2,3,4,1] |
rev(arr) | 反転コピー | rev([1,2,3]) → [3,2,1] |
interleave(...arrays) | ジッパーマージ | interleave([1,2],[3,4]) → [1,3,2,4] |
shuffle(arr) | Fisher-Yates シャッフル(コピー) | (要:再現性なし) |
unique(arr) | 順序保存で重複除去 | unique([1,2,1,3]) → [1,2,3] |
thin(arr, n) | n 個飛ばしで間引く | thin([1,2,3,4,5], 2) → [1,3,5] |
実用例
// オクターブ違いをジッパーで混ぜるconst lo = [60, 62, 64, 65];const hi = transpose(lo, 12);const mixed = interleave(lo, hi); // [60, 72, 62, 74, 64, 76, 65, 77]
mixed.forEach((p, i) => setNote({ pitch: p, startBeat: i * 0.25 }));// スケールを逆順に並べてからローテーションconst sc = rev(scale("minor", 60));const rot = rotate(sc, 2);乱数(Math.random ベース)
Section titled “乱数(Math.random ベース)”ここの関数群はすべて Math.random() ベース で、シードの影響を受けません。再現性が必要な場合は seededRandom() を直接使ってください。
random() / randomInt(min, max)
Section titled “random() / randomInt(min, max)”random(); // [0, 1) の浮動小数点randomInt(0, 11); // 0..11 の整数(両端含む)pick(arr)
Section titled “pick(arr)”配列から 1 つ選ぶ。空配列は例外。
const sc = scale("major", 60);onBeat((b) => { if (chance(0.3)) setNote({ pitch: pick(sc), startBeat: b, lengthBeats: 0.25 });});chance(p)
Section titled “chance(p)”確率 p(0..1)で true。範囲外は自動でクランプ。
if (chance(0.5)) setNote({ pitch: 60 }); // コインフリップif (chance(0.1)) setNote({ pitch: 84 }); // 10% の確率で高音weighted(items, weights)
Section titled “weighted(items, weights)”重み付きランダムピック。重みの合計で正規化 されるので相対値で OK。
// 50% / 33% / 17%const note = weighted([60, 64, 67], [3, 2, 1]);
// 拍ごとに違う確率分布で音を選ぶonBeat((b) => { const p = weighted([36, 38, 42], [4, 1, 8]); // ハイハットが多め setNote({ pitch: p, startBeat: b, lengthBeats: 0.1 });});[!CAUTION] 重みが全部 0 だと一様分布にフォールバックします。重みの長さが items と違うと例外。
モジュレーション
Section titled “モジュレーション”lfo(phase, shape): number
Section titled “lfo(phase, shape): number”正規化位相 phase(0..1 の小数。範囲外は内部で自動折り返し)に対する LFO 波形値。戻り値は -1..1。
shape | 波形 |
|---|---|
"sin" / "sine" | サイン波 |
"tri" / "triangle" | 三角波 |
"saw" | のこぎり波(-1 → 1) |
"square" / "sq" | 矩形波(-1 か 1) |
すべての波形は phase=0 と phase=0.5 でゼロクロスする設計なので、クロスフェード可能(位相がずれない)。
最小例
lfo(0, "sin"); // 0lfo(0.25, "sin"); // 1lfo(0.5, "sin"); // 0lfo(0.75, "sin"); // -1実用例:クリップ全体で 1 周期の正弦波で velocity を揺らす
onNote((n) => { const phase = n.startBeat / CLIP_LENGTH; // 0..1 setNote({ ...n, velocity: 90 + 20 * lfo(phase, "sin") });});実用例:CC1 を 2 Hz で揺らす(120 BPM 想定)
onBeat((b) => { // b は beat 単位なので、bpm 120 だと b=2 で 1 秒 // 2 Hz = 1 秒に 2 周期 = beat に対して 4 周期/拍 setCc({ ccNumber: 1, beat: b, value: 64 + 32 * lfo(b * 4, "sin") });});envelope(points, t): number
Section titled “envelope(points, t): number”時間-値ペアの配列 [[t0, v0], [t1, v1], ...] を時間でソートし、t における 線形補間値 を返します。範囲外は端点に張り付き。
const env = [ [0, 0], [2, 100], [4, 60],];envelope(env, 0); // 0envelope(env, 1); // 50 (0 → 100 の中間)envelope(env, 3); // 80 (100 → 60 の中間)envelope(env, 5); // 60 (範囲外、最終点に張り付き)実用例:4 拍クリップの ADSR
const adsr = [ [0, 0], [0.5, 100], [1, 80], [3, 80], [4, 0],];onBeat((b) => { setCc({ ccNumber: 11, beat: b, value: envelope(adsr, b) });});[!TIP] 点は時間順でなくても OK — 内部でソートされます。ただし
[t1, v1]のt1同士が同じ値だと、後ろのvに張り付くので注意。
チューニング
Section titled “チューニング”mtof(midi): number
Section titled “mtof(midi): number”MIDI ピッチ → 周波数(Hz)。mtof(69) = 440(A4)。
mtof(69); // 440mtof(60); // 261.625... (C4)mtof(72); // 523.251... (C5)実用例: 周波数情報を console.log でデバッグ表示。
onNote((n) => { console.log(n.pitch, "Hz:", mtof(n.pitch).toFixed(2));});ftom(hz): number
Section titled “ftom(hz): number”周波数 → MIDI ピッチ(小数。整数化したいなら Math.round)。
ftom(440); // 69ftom(442); // 69.078... (チューニング微調整想定)Math.round(ftom(437)); // 69 (A4 にスナップ)[!CAUTION]
ftom(0)以下を渡すと-Infinityを返します。
ステートフルなジェネレータ(Max 系)
Section titled “ステートフルなジェネレータ(Max 系)”これらは呼び出すと クロージャを返します。各クロージャは独立した内部状態を持つので、複数並列で使えます。
urn(n)
Section titled “urn(n)”[0, n) の 非反復ランダム整数。すべての値を 1 度ずつ返したら再シャッフル。Max の urn 相当。
いつ使うか: 「12 音すべてを 1 度ずつ使い切ってから繰り返す」のように、一様性を保ちつつランダムにしたい とき。
const u = urn(7);const sc = scale("minor", 60);onBeat((b) => { if (Number.isInteger(b * 2)) { // 8 分ごと setNote({ pitch: sc[u()], startBeat: b, lengthBeats: 0.5 }); }});drunk(start, step, min, max)
Section titled “drunk(start, step, min, max)”整数の 有界ランダムウォーク。各呼び出しで [-step, step] の整数差分を加算し [min, max] にクランプ。Max の drunk 相当 — 「滑らかなランダム」 の代名詞。
const d = drunk(60, 2, 48, 84); // 中央 60、最大 ±2、48..84 範囲onBeat((b) => { setNote({ pitch: d(), startBeat: b, lengthBeats: 0.25 });});[!TIP]
stepを大きくすると暴れた線、小さくすると緩やかな線になります。step=1だと半音単位で這う。
counter(start, end, stepBy = 1, wrap = true)
Section titled “counter(start, end, stepBy = 1, wrap = true)”モジュラカウンタ。現在値を返してから stepBy だけ進む。end を超えたら wrap=true で start へ巻き戻し、false で範囲内最終値に張り付き。
いつ使うか: 周期的な分散和音、決定的なステップ進行。
const c = counter(0, 4); // 0, 1, 2, 3, 0, 1, 2, 3, ...const ch = chord("maj7", 60);onBeat((b) => { if (Number.isInteger(b * 2)) { setNote({ pitch: ch[c()], startBeat: b, lengthBeats: 0.5 }); }});// stepBy=2 で 2 つ飛ばし、wrap=false で末端張り付きconst c = counter(0, 8, 2, false); // 0, 2, 4, 6, 6, 6, ...- 再現可能な乱数 →
seededRandom/seed - LFO で CC を揺らすパターン → パターン集(レシピ)
- HOF と組み合わせた確率的なリズム → ヘルパー:リズム > 高階パターン