コンテンツにスキップ

ホスト API

ここに挙げるものはすべて runRenderCode(ホスト)が直接注入 します。libs/codes/*.js より前にロードされ、ユーザコードや lib から上書きすることはできません。

このページの シグネチャ・値域・単位・サンプルコードは、アプリ内の契約定義 (hostApiSpec) と CI で照合 されています。実行時の振る舞いとここに書かれた内容がずれると、ビルドが失敗します。サンプルはすべて lib を 1 つも有効にしていない状態でそのまま貼り付けて Render できます。

euclid() / scale() / lfo() / clamp() などは ホスト API ではなく factory.js (lib) の関数です。ヘルパー を参照してください。

setNote({ pitch?, velocity?, startBeat?, lengthBeats? }): void

ノートを 1 つ Render に出力します。すべてのフィールドは省略可能。

フィールド必須既定値値域 / 単位
pitch任意60(C4)0..127(MIDI ノート番号・整数)
velocity任意1000..127(7bit・整数)
startBeat任意CLIP_BEAT(現在のフェーズの値)クランプなし(beat)
lengthBeats任意10.001 以上(beat)

idcrypto.randomUUID() で自動付与されます。同じピッチ・同じビートで複数回呼んでもそれぞれ別ノートとして記録されます。

最小例

onInit(() => {
setNote({ pitch: 60 });
});

実用例 — 元ノートにオクターブ上のレイヤーを velocity 70 で足す:

onNote((note) => {
setNote({ ...note });
setNote({ ...note, pitch: note.pitch + 12, velocity: 70 });
});

[!CAUTION] pitch / velocity整数に丸められた上でクランプ されます。 lengthBeats の最小値は 0.001 — それ未満を渡しても 0.001 になります(無音ノート防止)。

CC・Pitch Bend・Channel Aftertouch は同じレーンの仕組みに乗ります。画面側の編集については CC / Pitch Bend / Aftertouch オートメーション を参照。

setCc({ ccNumber, beat?, value, curve? }): void

CC イベントを 1 つ Render に出力します。同じ ccNumber への複数回の呼び出しは 1 つのレーンに集約 され、beat 昇順にソートされます。

フィールド必須既定値値域 / 単位
ccNumber必須0..127(整数)
beat任意CLIP_BEATクランプなし(beat)
value必須0..127(7bit・整数)
curve任意"linear"カーブ種別(下表)

最小例

onInit(() => {
setCc({ ccNumber: 11, beat: 0, value: 64 });
});

カーブ指定 (P-2)curveこのポイントで終わるセグメント の形を指定します(前のポイントから「自分」まで補間する際の形):

onInit(() => {
setCc({ ccNumber: 1, beat: 0, value: 0 });
setCc({ ccNumber: 1, beat: 4, value: 100, curve: "scurve" });
});
curve
"linear"直線(既定)
"hold"A の値を保持し、B.beat で B の値に階段状にジャンプ
"exp"ease-in(最初遅く後で急、t**2.7
"expInv"ease-out(最初急で後で緩む、exp の鏡像)
"scurve"smoothstep(両端で接線がフラット、中央で対称)

[!TIP] 同じ ccNumber に複数回 setCc を呼ぶと、1 つのレーン内のイベント列に集約されます。異なる ccNumber ならレーンが別々に作られます。

setCcResolution(stepsPerBeat: number): void

onCc のサンプリンググリッド解像度(1 拍を何分割するか)を設定します。

引数必須値域 / 単位
stepsPerBeat必須1..256(1 拍あたりの呼び出し回数)

呼ばなかった場合の解像度は 16(≈ 1/64 音符 ≈ 31ms @120bpm)です。トップレベル または onInit から呼んだときだけ反映され、onBeat / onNote / onCc 内からの呼び出しは黙って無視されます(グリッドは onCc 掃引の直前にスナップショット)。

onInit(() => setCcResolution(32));
onCc(({ phase }) => {
const value = 64 + 32 * Math.sin(phase * 2 * Math.PI);
setCc({ ccNumber: 1, value });
});

[!CAUTION] 解像度 × クリップ長が 32,768 ticks を超えると Render は Runtime error (onCc): grid sweep would emit ... ticks, exceeding cap を返します。setCcResolution を下げるかクリップを短くしてください。

setPitchBend({ beat?, value, curve? }): void

Pitch Bend のポイントを 1 つ出力します。1 クリップにつき Pitch Bend レーンは 1 本です。

フィールド必須既定値値域 / 単位
beat任意CLIP_BEATクランプなし(beat)
value必須0..16383(14bit・中央 = 8192
curve任意"linear"setCc と同じカーブ種別

curve に指定できる値は "linear" / "hold" / "exp" / "expInv" / "scurve" です。

onInit(() => {
setPitchBend({ beat: 0, value: 16000 });
setPitchBend({ beat: 1, value: 8192, curve: "exp" });
});

[!CAUTION] value: 0 は「ベンドなし」ではなく 全下げ です。中央へ戻すときは 8192 を渡してください。

setChannelAftertouch({ beat?, value, curve? }): void

Channel Aftertouch のポイントを 1 つ出力します。Pitch Bend と同じく 1 クリップに 1 本です。

フィールド必須既定値値域 / 単位
beat任意CLIP_BEATクランプなし(beat)
value必須0..127(7bit・整数)
curve任意"linear"setCc と同じカーブ種別

curve に指定できる値は "linear" / "hold" / "exp" / "expInv" / "scurve" です。

onInit(() => {
setChannelAftertouch({ beat: 0, value: 0 });
setChannelAftertouch({ beat: 0.5, value: 100 });
setChannelAftertouch({ beat: 1, value: 0 });
});

各フェーズに任意個のコールバックを登録します。登録順 に呼ばれ、return 値は無視されます。例外を投げるとそのフェーズが中断 されます(前のフェーズの結果は保持)。詳しくは ライフサイクル:エラーが起きたら何が残るか を参照。

関数コールバック引数呼ばれる回数
onInit(fn)なしレンダー先頭で 1 回
onBeat(fn)(beat: number)クリップ長 × stepsPerBeat(既定 4/拍)
onNote(fn)(note: MidiNote)元ノートの数
onCc(fn)(ctx: { beat, time, phase })クリップ長 × CC 解像度(既定 16/拍。setCcResolution() で変更)

onInit(fn: () => void): void

レンダー開始時に 1 回だけ呼ばれます。クリップ全体を一度に組み立てる用途に向いています。

onInit(() => {
setNote({ pitch: 60 });
});

onBeat(fn: (beat: number) => void): void

stepsPerBeat で決まるステップグリッドを時間順に掃引します。引数の beat はクリップ先頭からのビート位置です。

onBeat((beat) => {
if (beat % 1 === 0) {
setNote({ pitch: 36, startBeat: beat, lengthBeats: 0.25 });
}
});

onNote(fn: (note: MidiNote) => void): void

PianoRoll タブに置かれた元ノートを 1 つずつ、startBeat 昇順で渡します。

onNote((note) => {
setNote({ ...note });
setNote({ ...note, pitch: note.pitch + 12, velocity: 70 });
});

onCc(fn: (ctx: { beat: number; time: number; phase: number }) => void): void

元 CC レーンの有無に関わらず、setCcResolution で決まる固定グリッドで掃引されます。ctx.phasebeat / CLIP_LENGTH0..1 にクランプした値で、1 周期の LFO を書くときに便利です。

onInit(() => setCcResolution(32));
onCc(({ phase }) => {
const value = 64 + 32 * Math.sin(phase * 2 * Math.PI);
setCc({ ccNumber: 1, value });
});

events: MidiNote[]

PianoRoll タブに置かれた 元ノート の配列。レンダー時の clip.notes読み取り専用 で渡されます。

interface MidiNote {
id: string;
pitch: number; // 0..127
velocity: number; // 0..127
startBeat: number;
lengthBeats: number;
}

並びは、legacy な contract v1 では編集時のまま(onNote の引数だけが startBeat 昇順)。contract v2 では正規化された順序 で渡されるため、ID の振り直しや編集順の違いで並びが変わることはありません。

onInit(() => {
console.log("source notes:", events.length);
});

ccLanes: ClipCcLane[]

クリップに保存されている 元チャンネルメッセージレーン の配列(PianoRoll タブのレーン、または MIDI 入力で録音したもの)。

interface ClipCcLane {
id: string;
messageType?: "cc" | "pitchBend" | "channelAftertouch"; // 省略時は "cc"
ccNumber: number; // 0..127(PB / CA では未使用)
events: MidiCcEvent[]; // [{ id, beat, value, curve? }, ...]
}
onInit(() => {
const expr = ccLanes.find((lane) => lane.ccNumber === 11);
console.log(expr ? "CC11 events: " + expr.events.length : "no CC11");
});

sampleCc(ccNumber: number, beat: number): number

入力 ccLanes から ccNumber0..127)のレーンを探し、beat の値を線形補間して返します(float、整数化やクランプは呼び出し側で)。Rust エンジンの再生時補間と同じセマンティクスなので、sampleCc で読んだ値に揺らぎを足して setCc で書き戻すと、聴こえる結果と数値が一致します。

状況戻り値
ccNumber のレーンが存在しない / events が空0
beat が最初の点より前最初の点の value(hold-back)
beat が最後の点より後最後の点の value(hold-forward)
同じ beat に複数点配列順で最後の点(last-wins)
onCc(({ beat, phase }) => {
const base = sampleCc(7, beat);
const wobble = 10 * Math.sin(phase * 8 * Math.PI);
setCc({ ccNumber: 7, value: Math.min(127, Math.max(0, base + wobble)) });
});

[!CAUTION] sampleCc入力 ccLanes からだけ読みます。同じレンダー中に setCc で書いた値は読み返せません(鶏卵問題)。

samplePitchBend(beat: number): number

入力の Pitch Bend レーンを beat で線形補間して返します。レーンが無い / 空のときは 8192(ベンドなし) を返します — 0 は全下げを意味するため、ここだけ既定値が違います。

onCc(({ beat }) => {
const bend = samplePitchBend(beat);
setCc({ ccNumber: 1, value: Math.abs(bend - 8192) / 64 });
});

sampleAftertouch(beat: number): number

入力の Channel Aftertouch レーンを beat で線形補間して返します。レーンが無い / 空のときは 0 を返します。

onCc(({ beat }) => {
setCc({ ccNumber: 74, value: sampleAftertouch(beat) });
});

すべて クリップ先頭を 0 とするローカルな値 です。小節番号・PPQ・トランスポート位置ではありません。更新の仕組みについては ライフサイクル:CLIP_BEAT / CLIP_TIME / CLIP_LENGTH の更新メカニズム を参照。

CLIP_BEAT: number

現在のフェーズに対応するビート(onInit = 0、onBeat = グリッド位置、onNote = そのノートの startBeat、onCc = サンプリンググリッドの位置)。

CLIP_TIME: number

CLIP_BEAT / bpm * 60(秒)。bpm はスカラー値として扱われます(テンポマップ非対応)。

CLIP_LENGTH: number

クリップ長(ビート数)。レンダー中は不変です。

onInit(() => {
console.log("clip length:", CLIP_LENGTH, "beats");
console.log("start:", CLIP_BEAT, "beat /", CLIP_TIME, "sec");
});
onNote((note) => {
const isLast = note.startBeat >= CLIP_LENGTH - 1;
setNote({ ...note, velocity: isLast ? 120 : note.velocity });
});

seededRandom(): number

mulberry32 由来の [0, 1) の擬似乱数。再現可能 です。

デフォルトシードは contract バージョンで決まります。legacy な v1 は「コード本文 + 元ノート + ロード中の lib」のハッシュ、v2 は正規化した musical input から導出 されるため、ID の振り直しや並べ替えではシードが変わりません。

onBeat((beat) => {
const pitch = 60 + Math.floor(seededRandom() * 12);
setNote({ pitch, startBeat: beat, lengthBeats: 0.25 });
});

seed(n: number | string): void

シードを上書きします。文字列は内部でハッシュされて 32 ビット整数シードになります。

onInit(() => {
seed("variation-A");
console.log("first draw:", seededRandom());
});

Math: typeof Math

標準の Math と同じ関数群です。ただし contract v2 では Math.random() が seeded ストリームから引かれる ため、seededRandom() と同じく再現可能になります(v1 では通常の Math.random())。Math.random 以外のメンバーは標準実装のままです。

onBeat((beat) => {
const pitch = 60 + Math.floor(Math.random() * 12);
setNote({ pitch, startBeat: beat, lengthBeats: 0.25 });
});

[!IMPORTANT] factory.jsrandom() / randomInt() / pick() / chance() / weighted() / shuffle() / urn() / drunk()Math.random() ベースの lib 関数です。v1 のクリップでは再現性がないので、確実に再現させたい場面では seededRandom() を直接使ってください。 詳しくは ヘルパー:数値・乱数・モジュレーション > 乱数 を参照。

macros: Readonly<Record<string, number>>

Project Macro のスナップショット(マクロ名 → 値)。読み取り専用 で、レンダー中に値が動くことはありません。ユーザコードから書き換えようとすると strict mode で例外になります。

未定義のマクロ名は undefined になるので、?? で既定値を用意してください。

onInit(() => {
const density = macros.density ?? 4;
for (let i = 0; i < density; i += 1) {
setNote({ pitch: 48, startBeat: i, lengthBeats: 0.25 });
}
});

console.log(...args: unknown[]): void

引数は文字列化されて Code タブのコンソールに表示されます。

  • 文字列はそのまま
  • それ以外は JSON.stringify で文字列化(循環参照・関数値は [object Object] 等になる)
  • console.warn / console.error は提供されません
onNote((note) => {
console.log("note:", note.pitch, "@", note.startBeat);
});

[!TIP] 大量のログを出すと Code タブの描画が重くなります。デバッグが終わったら消しましょう。

これらは普段意識しなくても問題ありませんが、トラブルシュート時に役立ちます。

  • ユーザコードは new Function("__api", ...) でコンパイルされる「スクリプトコンテキスト」で動きます。this はグローバル、arguments は使えますが、モジュールスコープではありません
  • __api とその __sync / __beat / __timeホスト内部の配線 です。公開 API ではないので、補完候補にもこのリファレンスにも出てきません。直接触るコードは将来のバージョンで壊れます
  • CLIP_BEAT / CLIP_TIME はラッパー内の let バインディング。各コールバック直前に __sync() が更新するので、ユーザのクロージャは 呼び出し時点の値 を見ます
  • setNote / setCcidcrypto.randomUUID() で自動付与
  • setCc の同じ ccNumber は内部 Map に bucket されたあと、レーンごとに beat 昇順でソートされて返されます