npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@onjmin/dtm

v0.1.23

Published

MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント

Downloads

697

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 / ミキサーへ繋ぎたい場合は audioContextdestination を注入します。 注入した 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 / mountMmlPlayersingingVoices に渡してください。

重い WORLD 再合成は専用 Web Worker で実行してメインスレッド(楽器・UI)を塞がず、 複数ボーカルは音源ごとに並列合成されます。

ライセンス

MIT