@onjmin/dtm 1.0.8 → 2.0.3

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 CHANGED
@@ -17,6 +17,98 @@ npm i @onjmin/dtm
17
17
 
18
18
  ---
19
19
 
20
+ ## 2.0.0 への移行
21
+
22
+ 31 平均律への対応にあたり、**ノートのピッチの単位を変更**しました。1.x からの更新には修正が必要です。
23
+
24
+ ### ピッチの単位
25
+
26
+ `Note.pitch`(半音)が `Note.pitchUnits`(**1/372 オクターブの整数**)になりました。`NoteData` / `NoteRemove` / `PlayNoteEvent` / `MMLNotePlacement` / `ChordPlacement` も同様です。
27
+
28
+ ```
29
+ 12 平均律 1 半音 = 31 units 31 平均律 1 度 = 12 units
30
+ A4 (MIDI 69) = 2139 units 1 unit ≒ 3.2258 セント
31
+ ```
32
+
33
+ 372 = 12 × 31 で、12 と 31 は互いに素なので最小公倍数がこれになり、**両方の音律が誤差ゼロで同じ数直線に乗ります**。整数のままなので `pitchUnits` の同値判定(重複判定・当たり判定・協調編集の `(startStep, pitchUnits)` キー)がそのまま使えます。
34
+
35
+ 変換ヘルパを公開しています。
36
+
37
+ ```ts
38
+ import { pitchV1ToUnits, unitsToPitchV1, unitsToHz, unitsToMidiDetune } from "@onjmin/dtm";
39
+
40
+ pitchV1ToUnits(60); // 1860 … MIDI ノート番号 → units
41
+ unitsToPitchV1(1860); // 60 … units → MIDI ノート番号(12 平均律でのみ無損失)
42
+ unitsToHz(2139); // 440 … units → 周波数
43
+ unitsToMidiDetune(1968); // { midi: 63, detuneCents: 48.39 } … 31平均律の中立3度
44
+ ```
45
+
46
+ `unitsToMidiDetune` は SoundFont のように整数 MIDI ノートのゾーンしか持たない音源向けです。最寄りのゾーンを鳴らして残差を `detune`(セント)で補正します。
47
+
48
+ > **`PlayDrumEvent.pitch` と `DRUM_KEYS` は変更していません。** これらは GM 打楽器のキー番号であって
49
+ > 音高ではないため、units へ変換すると全ドラムが壊れます。
50
+
51
+ ### 単位はブランド型で区別されます
52
+
53
+ `pitchUnits` の型は素の `number` ではなく **`Units`** です。MIDI ノート番号は **`MidiNote`** で、両者は互いに代入できません。
54
+
55
+ ```ts
56
+ import { units, midiNote, pitchV1ToUnits, type Units } from "@onjmin/dtm";
57
+
58
+ // ノートを手で組むとき
59
+ const note = {
60
+ id: 0,
61
+ startStep: 0,
62
+ durationSteps: 48,
63
+ pitchUnits: pitchV1ToUnits(60), // MIDI 60 (中央ド) から作る
64
+ velocity: 100,
65
+ };
66
+
67
+ // units を直接指定するとき
68
+ const c4: Units = units(1860); // 60 × 31
69
+
70
+ // これはコンパイルエラーになる
71
+ const bad: Units = 1860; // Type 'number' is not assignable to type 'Units'
72
+ ```
73
+
74
+ 素の数値からは `units()` / `midiNote()` を通してください。これは手間ではなく、**「この数値の単位を確認した」という宣言**として機能します。
75
+
76
+ なぜこうしたかというと、`Note.pitch` → `pitchUnits` の改名時に**単位の取り違えを 12 件作り込んだ**からです。すべて型チェックを通過していました。半音のつもりの閾値が units と比較される、units が SoundFont へ MIDI ノート番号として渡されて**楽器音が無音になる**、オクターブユニゾンに半音の 12 が足されて 0.4 半音ずれる、といった不具合が、目視の監査を 3 回重ねても毎回新しく見つかりました。型が同じ `number` である限りコンパイラは単位を一切検証しないためです。
77
+
78
+ ブランド型はこれらを検出します。
79
+
80
+ ```
81
+ units を SoundFont の pitch へ → Units is not assignable to MidiNote
82
+ units に半音の 12 を足す → number is not assignable to Units
83
+ MidiNote を units の関数へ → MidiNote is not assignable to Units
84
+ ```
85
+
86
+ ただし**比較演算だけは防げません**。`pitchUnits < 48` のような式は `number` 同士の比較として通ります。ピッチと数値を比べる箇所は、引き続き単位を目で確認してください。
87
+
88
+ ### その他の破壊的変更
89
+
90
+ | 1.x | 2.0.0 |
91
+ |---|---|
92
+ | `init(target, w, h, config)` | `createRenderer(target, w, h, config)` が描画器インスタンスを返す |
93
+ | `new MMLCore(handlers, volume)` | 第 3 引数に `getConfig: () => RenderConfig` が必要 |
94
+ | `transposeNotes(cores, semitones)` | `transposeNotes(cores, steps)` — 単位は格子 1 ステップ |
95
+
96
+ `renderer` をインスタンス化したのは、モジュール全体がシングルトンで **1 ページに 2 つエディタをマウントすると後からマウントした側が Canvas を奪っていた**ためです(音律以前からのバグ)。曲ごとに音律が違うと格子まで食い違うため、31 平均律対応の前提でもあります。
97
+
98
+ `transposeNotes` の単位を変えたのは、31 平均律では「半音」がクロマチック半音(2 度)とダイアトニック半音(3 度)に分岐して一意に定まらないためです。12 平均律では 1 ステップ=1 半音なので、呼び出し側の値はそのままで動きます。
99
+
100
+ ### 協調編集
101
+
102
+ `onNotesPatch` / `applyPatch` で送受信する `pitchUnits` の意味が 1.x と異なるため、新旧クライアントが混ざると**黙って別の音になります**。dtm 自身は通信路を持たないので、バージョンの突き合わせは利用側アプリのハンドシェイクの責務です。
103
+
104
+ ```ts
105
+ import { PITCH_ENCODING_VERSION } from "@onjmin/dtm"; // 2 (1.x は 1 相当)
106
+ ```
107
+
108
+ v1 → v2 の変換は無損失ですが、**v2 → v1 は 12 平均律の曲でのみ**無損失です(31 平均律を半音へ丸めると最大 48.4 セント動きます)。
109
+
110
+ ---
111
+
20
112
  ## クイックスタート(全部入り `createDtmStudio`)
21
113
 
22
114
  楽器・ドラムの SoundFont、歌声合成、録音までを内包した一番簡単な入口です。SoundFont は実行時に CDN から動的 import し、歌声合成ワーカーは同梱の `dist/voice-worker.js` を使います。
@@ -384,6 +476,66 @@ const cp = mountChordPlayer(document.getElementById("chord-app"), "| C | G | Am
384
476
 
385
477
  ---
386
478
 
479
+ ## 音律(31 平均律)
480
+
481
+ `#edo=31` を宣言すると、その曲を 1 オクターブ 31 分割で扱います。省略時は 12 平均律なので、**既存の MML は 1 文字も解釈が変わりません**。
482
+
483
+ ```
484
+ #edo=31 @0 t120 o4 c c+ c# d- d_ d e_ e;
485
+ ```
486
+
487
+ 音律は**曲単位**です。トラックごと・小節ごとには変えられません。12 平均律と 31 平均律が一致するのはオクターブだけで、途中の音はすべてずれる(長 3 度で 12.9 セント、三全音で 19.4 セント)ため、同時に混ぜても音楽的に成立しないからです。
488
+
489
+ ### 臨時記号
490
+
491
+ 31 平均律では 4 記号を使い分けます。
492
+
493
+ | 記号 | 31 平均律 | 12 平均律 | 意味 |
494
+ |---|---|---|---|
495
+ | `#` | +2 度 | +1 半音 | クロマチック半音上げ(従来のシャープ) |
496
+ | `-` | −2 度 | −1 半音 | クロマチック半音下げ(従来のフラット) |
497
+ | `+` | +1 度 | +1 半音 | 格子 1 ステップ上げ(微分音) |
498
+ | `_` | −1 度 | −1 半音 | 格子 1 ステップ下げ(微分音) |
499
+
500
+ 12 平均律ではクロマチック半音=格子 1 ステップなので 4 記号すべてが従来の意味に潰れ、シャープ 2 つ・フラット 2 つの同義語になります。31 平均律でのみ `#`/`-` と `+`/`_` が分岐します。
501
+
502
+ 記号は累積するので `c##` は +4 度です(12 平均律では D)。幹音は五度連鎖で `c`=0 `d`=5 `e`=10 `f`=13 `g`=18 `a`=23 `b`=28 度。全 31 度の綴りは次の通りで、いずれも 2 文字以内・オクターブを跨ぎません。
503
+
504
+ ```
505
+ 0:c 1:c+ 2:c# 3:d- 4:d_ 5:d 6:d+ 7:d#
506
+ 8:e- 9:e_ 10:e 11:f- 12:e# 13:f 14:f+ 15:f#
507
+ 16:g- 17:g_ 18:g 19:g+ 20:g# 21:a- 22:a_ 23:a
508
+ 24:a+ 25:a# 26:b- 27:b_ 28:b 29:b+ 30:b#
509
+ ```
510
+
511
+ > 記号の選定: `^` は日本の MML 環境(サクラ・PMD・FMP・MUCOM88 等)でタイとして広く定着しており、
512
+ > ピッチ変更へ再定義すると過去の MML 資産が黙って別の音になるため使いません。`v` はベロシティ、
513
+ > `b` は音名 B で塞がっています。`+` を微分音へ転用できるのは `#` と冗長だからで、`-` は唯一の
514
+ > フラット記号なので転用せず、新記号 `_` に新しい概念(微分音)を割り当てています。
515
+ >
516
+ > 31 平均律はミーントーンなので五度連鎖だけで 31 音すべてに届き、ヴィチェンティーノ(1555)以来
517
+ > シャープ・フラット・重複臨時記号で記譜されてきました。`^`/`v` を使う ups-and-downs 記譜は
518
+ > 「五度連鎖が全音に届かない音律」向けの汎用記法で、31 平均律には必要ありません。
519
+
520
+ ### 何が変わるか
521
+
522
+ - **ピアノロール**: 1 オクターブが 31 段になります。音域(MIDI 0–127 相当)は音律に依らず固定なので、
523
+ 段数は自動的に 128 → 328 行へ変わります。鍵盤は幹音(白鍵相当)/微分音(短い中間鍵)/
524
+ クロマチック(黒鍵)の 3 階層で描かれます。縦の伸びは縦ズーム(50〜200%)で吸収できます。
525
+ - **和音**: コード進行入力は五度圏経由で 31 平均律の格子へ写されます。長 3 度が 10 度(純正 5:4 から
526
+ +0.79 セント)になるため、**同じコードが 12 平均律より綺麗に響きます**。増 4 度(15 度)と
527
+ 減 5 度(16 度)も区別されます。
528
+ - **歌声**: koe は Hz を直接受けるので、31 平均律の音もそのまま連続ピッチとして歌います。
529
+ - **MIDI 書き出し**: ピッチベンドの多チャンネル方式で書き出します(ドラム ch を除く 15 チャンネルへ
530
+ ベンド値の種類ぶんを割り当て、曲頭で RPN 0,0 により感度を ±2 半音へ固定)。dtm 同士なら無損失で
531
+ 往復しますが、1 トラック内のマルチチャンネルを潰す DAW では再現されません。
532
+ 15 種類を超えると頻度の低いものから最寄りの半音へ丸めます。
533
+ - **コード名の自動検出**: 12 平均律へ丸めてから判定するため、31 平均律では近似になります。
534
+ 通常のミーントーン和声(三和音・七の和音)は正しく復元されますが、中立 3 度やスーパーメジャーの
535
+ ような 31 平均律固有の音程は別のコードとして誤認されます。
536
+
537
+ ---
538
+
387
539
  ## 歌声合成(歌詞トラック `@@n`)
388
540
 
389
541
  演奏トラック `@n` とは別に歌詞専用行 `@@n` を書くと、そのトラックの Note On に合わせて 1 音節ずつ歌わせられます。