@onjmin/dtm 2.1.11 → 2.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -0
- package/dist/index.d.mts +847 -45
- package/dist/index.d.ts +847 -45
- package/dist/index.js +9681 -612
- package/dist/index.mjs +9652 -612
- package/dist/voice-worker.js +40 -26
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -237,6 +237,86 @@ recorder.start();
|
|
|
237
237
|
|
|
238
238
|
---
|
|
239
239
|
|
|
240
|
+
## ヘルプとガイドツアー
|
|
241
|
+
|
|
242
|
+
編集 UI には**ヘルプ(`?`)ボタン**と**目的別のガイドツアー**が同梱されています。どちらも埋め込み先でそのまま動きます。
|
|
243
|
+
|
|
244
|
+
### 何が出るか
|
|
245
|
+
|
|
246
|
+
- **`?` ボタン**(ツールバー右) … 使い方モーダル。画面のあちこちに散っている `ⓘ` 解説を 1 か所から辿れるハブになっています。**既定で表示**(`showHelp: false` で消せます)。
|
|
247
|
+
- **ガイドツアー** … 対象要素をくり抜いて吹き出しで説明するスポットライト型ウォークスルー。冒頭で目的を尋ね、選ばれた枝だけを歩かせます。
|
|
248
|
+
|
|
249
|
+
| 枝 | 案内する内容 |
|
|
250
|
+
| --- | --- |
|
|
251
|
+
| カバー曲を作りたい | オーディオ同時再生へ音源を読み込む → 開始のずれで頭を合わせる → 範囲を切り出す → ミュートで聴き比べる → 重ねて打ち込む |
|
|
252
|
+
| 曲を自動で作りたい | 作曲ボタン → 構成テンプレ → 雰囲気(調) → 再生 → おまかせマスタリング |
|
|
253
|
+
| 自分で打ち込みたい | ピアノロール → ツール/音符の長さ → トラックタブ → 再生 → 楽器・音量 |
|
|
254
|
+
|
|
255
|
+
**自動再生は既定でオフです。** 埋め込み先の第一印象を勝手に上書きしないための既定値で、初回に流したいアプリだけが明示的に有効化します。
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const studio = await createDtmStudio();
|
|
259
|
+
|
|
260
|
+
// 既定(自動再生なし)。? ボタンからはいつでも開始できる
|
|
261
|
+
studio.mountEditor(el, { initialMML });
|
|
262
|
+
|
|
263
|
+
// 初回訪問時だけ自動で流す
|
|
264
|
+
studio.mountEditor(el, { tour: { autoStart: true } });
|
|
265
|
+
|
|
266
|
+
// 独自の「使い方」ボタンから開始する
|
|
267
|
+
myButton.onclick = () => studio.startTour();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 消す・差し替える
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
await createDtmStudio({
|
|
274
|
+
features: { help: false }, // ? ボタンごと出さない
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
studio.mountEditor(el, {
|
|
278
|
+
tour: {
|
|
279
|
+
steps: MY_STEPS, // 既定ステップを丸ごと差し替える
|
|
280
|
+
extraSteps: [myStep], // 既定ステップの後ろに足す
|
|
281
|
+
storageKey: "myapp-tour", // 「もう見た」フラグの保存キー(null で記録しない)
|
|
282
|
+
labels: { next: "Next ▶", prev: "◀ Back", skip: "Skip", done: "Start ▶",
|
|
283
|
+
progress: (i, n) => `${i} / ${n}` },
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**存在しない UI を指すステップは自動的に飛ばされます。** `features.midi: false` や伴奏音源を注入していない構成でも、そのステップだけが黙って抜けます。枝そのものも `when` で出し分けられるので、「選んだ先が全部飛んで空になる」行き止まりは起きません。
|
|
289
|
+
|
|
290
|
+
### 単体で使う
|
|
291
|
+
|
|
292
|
+
ツアーエンジンは `mountDAW` に依存しない素の DOM ユーティリティです。自分のアプリの UI を指すステップを書いて直接呼べます。
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { startTour, hasSeenTour, isTourTargetVisible } from "@onjmin/dtm";
|
|
296
|
+
|
|
297
|
+
startTour({
|
|
298
|
+
root: myAppRoot, // セレクタの検索基点(既定 document)
|
|
299
|
+
steps: [
|
|
300
|
+
{ title: "ようこそ", body: "<p>まずは目的を選んでください。</p>", branches: [
|
|
301
|
+
{ label: "A をしたい", steps: stepsA,
|
|
302
|
+
when: (root) => isTourTargetVisible(root, "#feature-a") },
|
|
303
|
+
{ label: "B をしたい", steps: stepsB },
|
|
304
|
+
] },
|
|
305
|
+
{ target: "#save", title: "保存", body: "<p>ここで保存します。</p>" },
|
|
306
|
+
],
|
|
307
|
+
onEnd: (completed) => console.log(completed ? "完走" : "中断"),
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
| 項目 | 挙動 |
|
|
312
|
+
| --- | --- |
|
|
313
|
+
| 閉じた `<details>` の中の対象 | 自動で開いて採寸し、**ツアー終了時に閉じ直す**(パネルの開閉は localStorage に永続化されるため、勝手に開いた状態を残さない) |
|
|
314
|
+
| キーボード | `←` `→` で移動、`Esc` で中断 |
|
|
315
|
+
| 暗幕のクリック | **進まない**(誤タップで読む前に消えるのを防ぐ) |
|
|
316
|
+
| 画面幅 | 吹き出しは画面幅に合わせて縮み、対象の上下で入る方へ回り込む |
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
240
320
|
## モード(`simple` / `advanced`)
|
|
241
321
|
|
|
242
322
|
トラック構成と MIDI の取り込み方が異なる 2 つのモードがあります。`mode` オプションで切り替え、合わせて `tracks` に対応するトラック構成(`TRACKS_SIMPLE` / `TRACKS_ADVANCED`)を渡します。
|
|
@@ -295,6 +375,7 @@ studio.mountEditor(editorEl, { mode: "advanced", tracks: TRACKS_ADVANCED });
|
|
|
295
375
|
**対象となるケース**
|
|
296
376
|
- 5 トラック以上(ドラムトラック除く)の MML を初心者モードで読み込む
|
|
297
377
|
- 5 トラック以上(ドラムトラック除く)の MIDI を初心者モードで読み込む
|
|
378
|
+
- 選択中のトラックから順に入れると**トラックが足りない**本数の UST を初心者モードで読み込む
|
|
298
379
|
|
|
299
380
|
ダイアログで「はい」を選ぶと上級者モードに切り替わり、コンテンツをそのまま引き継ぎます。
|
|
300
381
|
「いいえ」を選ぶと初心者モードのまま読み込みます(トラックは合算されます)。
|
|
@@ -597,6 +678,168 @@ const cp = mountChordPlayer(document.getElementById("chord-app"), "| C | G | Am
|
|
|
597
678
|
|
|
598
679
|
---
|
|
599
680
|
|
|
681
|
+
## UST(UTAU)の読み込み・書き出し
|
|
682
|
+
|
|
683
|
+
UTAU の曲データ(`.ust`)を、**音符と歌詞をまとめて**取り込めます(MIDI へ書き出す必要はありません)。
|
|
684
|
+
UI は「MIDI / UST / MML 入力」パネルの UST 欄、書き出しは「MIDI / UST / MML 出力」の「UST 出力」です。
|
|
685
|
+
|
|
686
|
+
### 読み込み
|
|
687
|
+
|
|
688
|
+
- **複数ファイルを一度に選べます**。UST は 1 ファイル=1 パートなので、ハモリ等で分かれたファイルを
|
|
689
|
+
**選択中のトラックから順に、隣・その隣…へ 1 ファイルずつ**割り当てます。あぶれたぶんは読み込まず、
|
|
690
|
+
何件落としたかを UI に表示します(初心者モードでトラックが足りないときは上級者モードへの切り替えを提案します)。
|
|
691
|
+
- 並び順は**ファイル名順**です(`01_main.ust` / `02_harmony.ust` のように番号を付けると狙った順に入ります)。
|
|
692
|
+
- 文字コードは Shift_JIS / UTF-8 を自動判別します。BPM は UST の `Tempo` に合わせます。
|
|
693
|
+
- 歌う音源(`lyricModel`)が未選択のトラックには自動で 1 つ割り当てます(選択済みならそのまま)。
|
|
694
|
+
- MIDI 読み込みと違い**全消去はしません**。伴奏を残したままメロディのパートだけ差し替えられます。
|
|
695
|
+
- 「現在のトラックのみ対象とする」が有効なときは、隣へこぼさず先頭の 1 ファイルだけを読み込みます。
|
|
696
|
+
|
|
697
|
+
歌詞は 1 ノート 1 音節へ落とします。
|
|
698
|
+
|
|
699
|
+
| UST の `Lyric` | 取り込み結果 |
|
|
700
|
+
| --- | --- |
|
|
701
|
+
| `か` / `カ` | `か`(カタカナはひらがなへ寄せる) |
|
|
702
|
+
| `a か` / `- か`(連続音) | `か`(空白区切りの最後の語が実体) |
|
|
703
|
+
| `かC4` / `か強`(サフィックス付き) | `か`(先頭のかな列だけ) |
|
|
704
|
+
| `ka` / `kya` / `shi`(ローマ字命名) | `か` / `きゃ` / `し` |
|
|
705
|
+
| `R` | 休符(ノートを作らず位置だけ進む=ピアノロールの隙間) |
|
|
706
|
+
| `+`(前の歌詞を続ける) | 継続記号 `ー` |
|
|
707
|
+
| 読み取れない綴り(CVVC の `a k` など) | 継続記号 `ー`(言い直さず繋ぐ。件数は UI に表示) |
|
|
708
|
+
|
|
709
|
+
### 書き出し
|
|
710
|
+
|
|
711
|
+
「UST 出力」は**選択中のトラック 1 本だけ**を書き出します。UST は単旋律 1 パートのフォーマットなので、
|
|
712
|
+
和音・重なりは先勝ちで 1 本へ潰し、ノートの隙間は `R`(休符)ノートとして書きます。文字コードは
|
|
713
|
+
UTF-8(`Charset=UTF-8` 付き)、改行は CRLF です。31 平均律の微分音は UST に書けないため最寄りの半音へ丸めます。
|
|
714
|
+
|
|
715
|
+
### API
|
|
716
|
+
|
|
717
|
+
```ts
|
|
718
|
+
import { parseUst, buildUst } from "@onjmin/dtm";
|
|
719
|
+
|
|
720
|
+
const part = parseUst(await file.arrayBuffer().then((b) => new Uint8Array(b)), file.name);
|
|
721
|
+
part.bpm; // UST の Tempo(無ければ null)
|
|
722
|
+
part.notes; // { startStep, pitch(MIDIノート番号), durationSteps, velocity }[]
|
|
723
|
+
part.lyrics; // ノート数と同じ音節数のかな歌詞
|
|
724
|
+
|
|
725
|
+
const text = buildUst({ notes, syllables, bpm: 120 }); // .ust テキスト
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
`DawInstance` には `exportUST()`(選択中トラックの Blob)と `applyUstParsed(ustTracks, startIndex?)` があります。
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
## オーディオ同時再生(mp3 / wav / YouTube)
|
|
733
|
+
|
|
734
|
+
音声ファイルやそのURL、YouTubeのURLを、打ち込みと**一緒に鳴らせます**。カラオケ音源に合わせて
|
|
735
|
+
メロディを打ち込む、既存曲に重ねてハモリを作る、といった用途向けです。UIは「オーディオ同時再生」パネル。
|
|
736
|
+
|
|
737
|
+
音を出すのは利用側の責務なので、`createDtmStudio().mountEditor` を使うと自動で配線されます
|
|
738
|
+
(`mountDAW` を直接使う場合は `backingAudio` に `createBackingAudio(...)` の戻り値を渡してください。
|
|
739
|
+
渡さないとパネルごと出ません)。
|
|
740
|
+
|
|
741
|
+
### 同期のしかた
|
|
742
|
+
|
|
743
|
+
打ち込みと**同じアンカー**(`sequencer.getStartTime()`)へ揃えます。音源の種類で精度が変わります。
|
|
744
|
+
|
|
745
|
+
| 読み込み方 | 鳴らし方 | 精度 | WAV書き出し・録音 |
|
|
746
|
+
| --- | --- | --- | --- |
|
|
747
|
+
| ファイル | デコードして `AudioBufferSourceNode` | サンプル単位 | **入る** |
|
|
748
|
+
| URL(CORS可) | 同上 | サンプル単位 | **入る** |
|
|
749
|
+
| URL(CORS不可) | `<audio>` 直接再生+ドリフト補正 | 実測で数ms | 入らない |
|
|
750
|
+
| YouTube | IFrame Player API+ドリフト補正 | 数十ms | 入らない |
|
|
751
|
+
|
|
752
|
+
### 鳴り始めの遅れ(初回再生でズレる問題)
|
|
753
|
+
|
|
754
|
+
再生要求から実際に音が出るまでの遅れは環境依存で事前に読めません(YouTubeのバッファ、
|
|
755
|
+
`<audio>` のデコード、初回再生のウォームアップなど。**mp3/wav でも起きます**)。
|
|
756
|
+
音源の性質で直し方を変えています。
|
|
757
|
+
|
|
758
|
+
| 音源 | 直し方 |
|
|
759
|
+
| --- | --- |
|
|
760
|
+
| デコード済み(ファイル / CORS可URL) | 予約がサンプル単位で正確。何もしない |
|
|
761
|
+
| `<audio>` 直接再生 | **予約してから実測で1回詰める**。測った遅れを足した先へ seek する(seek 自体の立ち上がりを見越す) |
|
|
762
|
+
| YouTube | **先に鳴らして実測し、打ち込みの開始をそこへ合わせる**(`sequencer.start` の pre-roll) |
|
|
763
|
+
|
|
764
|
+
YouTube だけ「待ち合わせ」るのは、再生速度を微調整できず**戻す手段が seek しか無い**ためです。
|
|
765
|
+
`<audio>` は待ち合わせるとかえって精度が落ちます(待った時点で音源が先行してしまう)。
|
|
766
|
+
|
|
767
|
+
実測の肝は、**読み取り値が動いた瞬間**を捕まえること。値が変わった瞬間ならその値はたった今のものだと
|
|
768
|
+
分かるので、更新が粗い(数百ms刻み)YouTubeでも誤差をポーリング間隔(40ms)まで押し込めます。
|
|
769
|
+
|
|
770
|
+
鳴り出してからの追従は:
|
|
771
|
+
|
|
772
|
+
- 進みすぎ(音源が先行)… `<audio>` では **seek しない**。再生速度を最大±5%変えて寄せる
|
|
773
|
+
(`preservesPitch` が効くので音程は動きません)。seek は1回あたり100ms前後の立ち上がりを伴うため、
|
|
774
|
+
それより小さいズレを seek で詰めると、詰めた量より大きく遅れ直して振動します。
|
|
775
|
+
- 大きくズレた(0.3秒超)… seek で直す。
|
|
776
|
+
- YouTube … 速度を変えられないので seek だけが頼り。0.06秒を超えたら直します。
|
|
777
|
+
|
|
778
|
+
**seek には「飛んだ先を読み直すぶん、必ず遅れて鳴り出す」性質があります**(YouTubeで実測 約70ms)。
|
|
779
|
+
見越さずに撃つと毎回そのぶん後ろへ着地し、YouTubeでは速度で寄せ直せないためそこで固定されます。
|
|
780
|
+
そこで**撃つたびに残差から見越し量を学習**し、次からはそのぶん先を狙います。
|
|
781
|
+
|
|
782
|
+
実測値:
|
|
783
|
+
|
|
784
|
+
| 経路 | 対策前 | 対策後 |
|
|
785
|
+
| --- | --- | --- |
|
|
786
|
+
| `<audio>` 直接再生・同時開始 | +104ms から約5秒かけて収束(途中 -48ms まで振動) | 再生直後から **±15ms以内** |
|
|
787
|
+
| YouTube・同時開始 | **-70ms のまま固定** | 約1秒で収束し、以後 **±5ms** |
|
|
788
|
+
|
|
789
|
+
YouTubeは「音源が先・n秒」指定なら、最初の音符から正確に合います。
|
|
790
|
+
|
|
791
|
+
### 音源の範囲(いらないパートを飛ばす)
|
|
792
|
+
|
|
793
|
+
**開始**〜**終了** で音源の使う範囲を決めます。`0:12.500` のように分:秒.ミリ秒、`12.5` のように
|
|
794
|
+
秒だけでも書けます(終了は空欄で最後まで)。
|
|
795
|
+
|
|
796
|
+
### 開始のずれ(どちらが何秒先に始まるか)
|
|
797
|
+
|
|
798
|
+
「**[音源/打ち込み]が先、[n秒]後にもう一方が始まる**」の2項目で、次の4通りをすべて表します。
|
|
799
|
+
|
|
800
|
+
| 指定 | 鳴り方 |
|
|
801
|
+
| --- | --- |
|
|
802
|
+
| 音源が先・0秒 | 同時に始まる(既定) |
|
|
803
|
+
| 音源が先・n秒 | 音源を先に鳴らし、n秒後に打ち込みが入る(前奏の長い音源に合わせる) |
|
|
804
|
+
| 打ち込みが先・0秒 | 同時に始まる |
|
|
805
|
+
| 打ち込みが先・n秒 | 打ち込みが先に鳴り、n秒後に音源が入る(曲の途中から音源を重ねる) |
|
|
806
|
+
|
|
807
|
+
内部では**符号付きの1つの秒数**(正=音源が先)で持ちます。**音符は動きません**——ずれるのは
|
|
808
|
+
再生の開始時刻だけ(音源が先のぶんは `sequencer.start` の pre-roll として待つ)なので、曲データは
|
|
809
|
+
そのままで、いつでも変えられます。再生中に変えるとその場で合わせ直します。曲の途中から再生した
|
|
810
|
+
ときは待ち時間を挟まず、その位置の音源がすぐ鳴ります。
|
|
811
|
+
|
|
812
|
+
### MML への埋め込み
|
|
813
|
+
|
|
814
|
+
| 宣言 | 意味 |
|
|
815
|
+
| --- | --- |
|
|
816
|
+
| `#audio=<URL>` | 音源のURL(mp3 / wav / YouTube) |
|
|
817
|
+
| `#audiostart=<秒>` | 音源のどこから鳴らすか(省略時0) |
|
|
818
|
+
| `#audioend=<秒>` | 音源のどこで止めるか(省略時は最後まで) |
|
|
819
|
+
| `#audiooffset=<秒>` | 開始のずれ。正=音源が先、負=打ち込みが先(省略時0=同時) |
|
|
820
|
+
| `#audiovol=<0-100>` | 音源の音量(省略時80) |
|
|
821
|
+
|
|
822
|
+
**アップロードしたファイルはMMLに含まれません**(受け取った相手の環境にそのファイルは無く、
|
|
823
|
+
`blob:` URLも他人からは開けないため)。URLが無いときは開始位置・音量ごと出力しません。
|
|
824
|
+
|
|
825
|
+
```
|
|
826
|
+
#audio=https://example.com/karaoke.mp3 #audiooffset=6.207 #audiovol=60;
|
|
827
|
+
@0 t120 o4 c d e f;
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### 再生専用ビュー・埋め込み
|
|
831
|
+
|
|
832
|
+
`mountMmlPlayer`(`studio.mountPlayer` / 埋め込みプレイヤー)も `#audio=` を解釈して一緒に鳴らします。
|
|
833
|
+
URLで読み込んだ音源はMMLに載るので、共有したMMLを受け取った側でも伴奏付きで再生されます。
|
|
834
|
+
YouTubeのときはプレイヤー内に動画の枠が出ます。
|
|
835
|
+
|
|
836
|
+
### 制限
|
|
837
|
+
|
|
838
|
+
- ループ再生をONにしても、伴奏音源はループせずそのまま流れます。
|
|
839
|
+
- 曲の終わりは「打ち込みの終端」と「伴奏音源の終端」の遅いほうです(打ち込みが空でも音源だけ鳴らせます)。
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
600
843
|
## ライセンス
|
|
601
844
|
|
|
602
845
|
[MIT](./LICENSE)
|