@onjmin/dtm
v0.1.23
Published
MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント
Downloads
697
Maintainers
Readme
@onjmin/dtm
MML を中間言語に用いた、モバイルファーストな DAW / ピアノロール打ち込みコンポーネント。 楽器・ドラムに加え、UTAU 音源(@onjmin/koe)による歌声合成にも対応。
インストール
npm i @onjmin/dtmクイックスタート(全部入り createDtmStudio)
楽器・ドラムの SoundFont、歌声合成、録音までを内包した一番簡単な入口。SoundFont は実行時に
CDN から動的 import し、歌声合成ワーカーは同梱の dist/voice-worker.js を使う(差し替え可能)。
import { createDtmStudio } from "@onjmin/dtm";
const studio = await createDtmStudio();
// 編集UI(ピアノロール・音・歌声込み)
const daw = studio.mountEditor(document.getElementById("editor"), {
initialMML: "@0 t120 o5 l8 ccggaag4 ffeeddc4",
});
// 再生専用UI(MML を渡すだけ)
studio.mountPlayer(document.getElementById("player"), daw.getMML().full);モード(simple / advanced)
トラック構成と MIDI の取り込み方が異なる 2 つのモードがあります。mode オプションで切り替え、
合わせて tracks に対応するトラック構成(TRACKS_SIMPLE / TRACKS_ADVANCED)を渡します。
| モード | トラック | MIDI 取り込み | 伴奏(コード進行)UI |
| --- | --- | --- | --- |
| simple | メロディー / サブメロ / ベース / 伴奏 の 4 本 | 各トラックの特徴から役割へ自動分類 | chord トラックに表示(歌詞欄の代わり) |
| advanced | TRACK 01〜15 の 15 本(フラットな連番) | MIDI トラックを1:1 マッピング | なし(全トラックが通常のノート+歌詞トラック) |
import {
createDtmStudio,
TRACKS_SIMPLE,
TRACKS_ADVANCED,
} from "@onjmin/dtm";
const studio = await createDtmStudio();
// シンプルモード(既定)
studio.mountEditor(editorEl, { mode: "simple", tracks: TRACKS_SIMPLE });
// アドバンスモード
studio.mountEditor(editorEl, { mode: "advanced", tracks: TRACKS_ADVANCED });modeを省略するとtracksの本数から推論します(4 本以下→simple/ 5 本以上→advanced)。 4 トラックでも 1:1 で取り込みたい等、意図がトラック数とずれる場合はmodeを明示してください。tracksには任意の独自構成も渡せます(modeと組み合わせて挙動を決めます)。- MIDI のドラム(ch10)はピアノロールで編集できないため、取り込み時のトラック選択 UI には出ません。
mode/tracksは低レベル API のmountDAWでも同じく指定できます。
備考: トラック採番と MIDI チャンネルの対応(暫定仕様)
advanced の @n / タブ名は MML 仕様に合わせたフラットな連番(@0〜@14 / TRACK 01〜15、欠番なし)です。
「ch10 = ドラム」という MIDI の慣習は内部モデルには持ち込まず、MIDI 入出力の変換時にだけ扱います。
- 出力: レーン index → MIDI チャンネルに変換する際、打楽器ch(内部
channel 9= 1始まり ch10)を避けます。 結果、TRACK 01〜09 → ch1〜9 / TRACK 10〜15 → ch11〜16 に書き出されます(タブ番号と MIDI ch は 10 番以降ズレますが、これは出力時の変換詳細です)。15 レーン ⇄ 15 個の非打楽器ch がちょうど 1:1 で、 チャンネル衝突は起きません。 - 入力:
channel 9(ドラム)のノートは取り込まず、ドラムだけのトラックは選択 UI にも出しません。 選択したトラックは選択順に上からレーンへ詰めます(MIDI の実トラック番号やテンポトラックの有無に 左右されません)。 - ドラム自体はノートレーンではなく、別系統のドラム設定で編集します。
経緯: 当初は「タブ番号 = MIDI チャンネル番号」に揃え、ドラムの ch10 をタブの欠番にする案も検討しましたが、 このアプリの正規フォーマットは MML(0 始まり・フラット・ドラムch非依存)であり、MIDI は境界の交換 フォーマットに過ぎません。MIDI の慣習を内部採番に漏らすと MML の一貫性が崩れる(モード間で採番が割れる、 永続化される MML に欠番が残る等)ため、内部は MML 仕様に寄せ、ch10=ドラムの面倒は変換層に閉じ込める 方針に決めました。
低レベル API(mountDAW / 注入式)
本体は音を持たない設計で、onPlayNote / onPlayDrum に自前のシンセを繋ぐ
(createDtmStudio はこの配線を内包したもの)。
import { mountDAW } from "@onjmin/dtm";
const daw = mountDAW(document.getElementById("app"), {
getAudioTime: () => audioCtx.currentTime,
onResumeAudio: () => audioCtx.resume(),
onPlayNote: ({ trackId, pitch, volume, when, duration }) => {
mySynth.play({ pitch, volume, when, duration });
},
onPlayDrum: ({ pitch, velocity, when, duration }) => {
myDrum.play({ pitch, velocity, when, duration });
},
});
daw.play();
daw.getMML(); // { full, minified }
daw.destroy();onPlayNote / onPlayDrum を省略すると無音で編集だけできます。parseMidi を渡すと MIDI 読込 UI が有効になります。また、コード進行入力 UI は標準で有効です(内部的に @onjmin/chord-parser を使用しています)。
再生専用ビューが必要なら mountMmlPlayer を使います。
ヘッドレス再生(ゲーム BGM 向け playMML)
画面を一切持たず、MML 文字列を渡して音だけを鳴らす関数。mountMmlPlayer のような
DOM ビューは作らないので、ゲームの BGM のように「鳴らして・止める」用途に向きます。
import { playMML } from "@onjmin/dtm";
// ユーザー操作(クリック等)のコールスタック内で呼ぶ(自動再生ポリシー対策)
const bgm = playMML("@0 t120 o5 l8 ccggaag4 ffeeddc4 #drum=basic", {
loop: true, // 曲末で止めずシームレスにループ
volume: 70,
});
bgm.setVolume(40); // 再生中も即時反映
bgm.stop(); // 停止
bgm.destroy(); // 停止+内部 AudioContext を解放- 発音はオーディオスレッド上で行われます(未来時刻に予約するため、メインスレッドが 重くても音切れしにくい)。スケジューラ自体はメインスレッドの先読み方式です。
- タブが非アクティブになると自動で一時停止し、復帰で再開します(内部生成 ctx のとき既定 ON)。
- 既存の AudioContext / ミキサーへ繋ぎたい場合は
audioContextとdestinationを注入します。 注入した ctx は SE 等と共有している可能性があるため、非アクティブ時の自動 suspend は 既定 OFF になります(代わりにbgm.suspend()/bgm.resume()を呼び出し側から叩けます)。
const bgm = playMML(mml, {
audioContext: myCtx, // ゲーム側の AudioContext を共有
destination: myMasterGain, // 自前のマスターGain/ミキサーへ
// 自前シンセを使うなら onPlayNote を渡す(内蔵 square synth は自動で無効)
onPlayNote: ({ pitch, volume, when, duration }) => mySynth.play(...),
});高度なループ設定 & 再生キュー(ゲーム同期)
イントロを1回再生したあとに特定区間をループさせたり、曲の特定位置(サビなど)でゲーム内の演出を切り替えるためのイベントを発火させたりできます。
const bgm = playMML(mml, {
// 1. イントロ付きループ(例: 4小節目から曲末までをシームレスループ)
loop: {
start: { bar: 4 }, // または { step: 576 }, { seconds: 12.5 }
// end: { bar: 8 } // ループの終わりを曲末以外に制限したい場合に指定
},
// 2. キュー(イベントトリガー)の登録
cues: [
{ id: "intro_end", time: { bar: 4 } }, // 4小節目に入った瞬間
{ id: "chorus_start", time: { seconds: 45.2 } }, // 45.2秒経過した瞬間
],
// 3. キュー通過時のコールバック
onCue: (cueId) => {
console.log(`BGM cue reached: ${cueId}`);
if (cueId === "chorus_start") {
triggerVisualEffects(); // サビの演出をトリガー
}
}
});歌声合成(
@@n歌詞トラック)はヘッドレス再生では未対応です(楽器・ドラムのみ)。 歌声が必要ならmountMmlPlayer/createDtmStudioを使ってください。
歌声合成(歌詞トラック @@n)
演奏トラック @n とは別に歌詞専用行 @@n を書くと、そのトラックの Note On に合わせて
1 音節ずつ歌わせられます。
@@<トラックID> <モデル> [v<声量>] [q<ゲート>] [p<定位>] [o<オクターブ>] <かな歌詞>
例:
@0 t120 v100 o4g8 g8 e8 e8 f8 e8 d8 c8 g8 g8 e8 e8 d4.;
@@0 tsukuyomi どんぐりころころどんぐりこ;- モデルに
klattを指定すると内蔵フォルマント合成(音源ロード不要)。 - 内蔵 UTAU 音源(@onjmin/koe)キーワード:
tsukuyomi/rino/roze/ruko_male/ruko_female/teto/shiyo。 createDtmStudioを使えば歌声は自動で配線されます。低レベル API で使う場合はcreateSingingVoicesの戻り値をmountDAW/mountMmlPlayerのsingingVoicesに渡してください。
重い WORLD 再合成は専用 Web Worker で実行してメインスレッド(楽器・UI)を塞がず、 複数ボーカルは音源ごとに並列合成されます。
