@onjmin/dtm 0.1.0 → 0.1.4
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/LICENSE +21 -21
- package/README.md +1 -1
- package/dist/index.d.mts +511 -3
- package/dist/index.d.ts +511 -3
- package/dist/index.js +2572 -141
- package/dist/index.mjs +2541 -140
- package/dist/voice-worker.js +382 -0
- package/package.json +6 -3
package/dist/index.d.mts
CHANGED
|
@@ -58,6 +58,292 @@ type DrumPattern = {
|
|
|
58
58
|
}[];
|
|
59
59
|
declare const DRUM_PATTERNS: Record<string, DrumPattern>;
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* MML歌詞拡張 — 解析・正規化・同期(ヘッドレス)と、任意の歌唱合成ヘルパ。
|
|
63
|
+
*
|
|
64
|
+
* 既存の演奏トラック(@n)とは独立した「歌詞専用行」(@@n) を扱う。
|
|
65
|
+
* @@<トラックID> <モデル名> <歌詞>
|
|
66
|
+
* 例: `@@2 klatt どはどなつのど`
|
|
67
|
+
* `@@3 external_engine きょー`
|
|
68
|
+
*
|
|
69
|
+
* 区切りは半角スペース(引用符不要)。歌詞内のひらがな・カタカナ・長音記号(ー)以外は破棄する。
|
|
70
|
+
*
|
|
71
|
+
* 演奏データ(テンポ・休符・音符長)とは完全に分離されており、
|
|
72
|
+
* Note On のタイミングで音節を1つずつ消費して歌わせる。
|
|
73
|
+
*
|
|
74
|
+
* このライブラリ自体は音を出さない方針のため、解析・同期(createLyricsConductor)と
|
|
75
|
+
* オプトインのフォルマント合成ヘルパ(createKlattVoice / createVoiceRegistry)を分離して提供する。
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/** 歌詞文字列(かな)を正規化済み音節列へ変換する */
|
|
79
|
+
declare const normalizeLyrics: (text: string) => LyricSyllable[];
|
|
80
|
+
/**
|
|
81
|
+
* 歌唱の声量の上限(%)。100=等倍。100超は合成音声をブースト(増幅)する。
|
|
82
|
+
* 100では音量が足りないケース向けに大きめのヘッドルームを確保する。
|
|
83
|
+
* UIスライダー・MML(`v<n>`)パースの双方でこの上限を共有する。
|
|
84
|
+
* {@link vocalVolumeToGain} により v=400 で約 +24dB(≒15.8倍)まで上げられる。
|
|
85
|
+
*/
|
|
86
|
+
declare const MAX_VOCAL_VOLUME = 400;
|
|
87
|
+
/**
|
|
88
|
+
* 声量値(0-{@link MAX_VOCAL_VOLUME})を実際のゲイン係数へ変換する。
|
|
89
|
+
*
|
|
90
|
+
* - 0 → 0(無音), 100 → 1(等倍)。
|
|
91
|
+
* - 0-100 は従来どおりの線形(既存MMLの音量を変えないため)。
|
|
92
|
+
* - 100超は dB 線形(=ゲインは対数)のブースト。スライダーを等間隔で動かすと
|
|
93
|
+
* 等dB=知覚的に均等な音量変化になる。
|
|
94
|
+
*
|
|
95
|
+
* v=100 で両分岐が連続(線形側=1、対数側=10^0=1)するため、つなぎ目で段差は出ない。
|
|
96
|
+
*/
|
|
97
|
+
declare const vocalVolumeToGain: (v: number) => number;
|
|
98
|
+
/**
|
|
99
|
+
* MMLから全歌詞トラックを解析し、トラックIDをキーにした辞書を返す(プリスキャン)。
|
|
100
|
+
* 同一IDが複数あれば後勝ち。
|
|
101
|
+
*
|
|
102
|
+
* 記法: `@@<トラックID> <モデル名> [v<声量>] [q<ゲート>] <歌詞>`
|
|
103
|
+
* 例: `@@4 klatt v100 歌詞`(声量100=等倍。100超でブースト、上限 {@link MAX_VOCAL_VOLUME})
|
|
104
|
+
* 声量は 0-{@link MAX_VOCAL_VOLUME}、ゲートは 0-100。モデル名直後の `v`/`q` トークンとして任意順で付与でき、
|
|
105
|
+
* 最初に現れた歌詞(かな)トークンより前にあるものだけを解釈する。
|
|
106
|
+
* 後方互換として `klatt:80` のコロン区切り声量も受け付ける。
|
|
107
|
+
*/
|
|
108
|
+
declare const parseLyrics: (mml: string) => Map<number, LyricTrack>;
|
|
109
|
+
/**
|
|
110
|
+
* 歌詞専用行を除去し、演奏トラックのみのMMLを返す。
|
|
111
|
+
* parseMML が @@n を演奏ノートとして誤解釈しないよう前処理する。
|
|
112
|
+
*/
|
|
113
|
+
declare const stripLyrics: (mml: string) => string;
|
|
114
|
+
/** 1つの音節を消費した結果。利用側はこれを歌唱合成へ渡す */
|
|
115
|
+
type ConsumedSyllable = {
|
|
116
|
+
/** 歌う合成モデル名 */
|
|
117
|
+
model: string;
|
|
118
|
+
/** 消費した音節 */
|
|
119
|
+
syllable: LyricSyllable;
|
|
120
|
+
/**
|
|
121
|
+
* 歌唱の声量係数(歌詞トラックの volume 0-{@link MAX_VOCAL_VOLUME} を正規化したもの)。
|
|
122
|
+
* 1=等倍で、100超指定時は 1 を超える(ブースト)。ノートのvelocityとは独立。
|
|
123
|
+
* 利用側はこれにマスタ音量を掛けて発音音量とする。
|
|
124
|
+
*/
|
|
125
|
+
volume: number;
|
|
126
|
+
/**
|
|
127
|
+
* 歌唱のゲートタイム係数 0-1(歌詞トラックの gate 0-100 を正規化したもの)。
|
|
128
|
+
* 利用側はノートの発音長(秒)にこれを掛けて実際の歌唱長とする。既定1(レガート)。
|
|
129
|
+
*/
|
|
130
|
+
gate: number;
|
|
131
|
+
/**
|
|
132
|
+
* ステレオ定位 -1(左)〜+1(右)、0が中央(歌詞トラックの pan 0-127 を正規化したもの)。
|
|
133
|
+
* 利用側は StereoPannerNode.pan などへそのまま渡す。
|
|
134
|
+
*/
|
|
135
|
+
pan: number;
|
|
136
|
+
};
|
|
137
|
+
/** MML の pan 値(0-127, 64=中央) を StereoPanner 用の -1〜+1 へ正規化する */
|
|
138
|
+
declare const panToStereo: (pan: number) => number;
|
|
139
|
+
/** 歌詞同期コンダクタ。音節ポインタを保持し、Note On ごとに1音節消費する */
|
|
140
|
+
type LyricsConductor = {
|
|
141
|
+
/**
|
|
142
|
+
* 演奏トラック trackId の Note On に対応する音節を1つ消費して返す。
|
|
143
|
+
* 歌詞が無い/尽きた場合は null(利用側は楽器音として鳴らす)。
|
|
144
|
+
*/
|
|
145
|
+
consume: (trackId: number) => ConsumedSyllable | null;
|
|
146
|
+
/** ポインタを初期化する(再生開始時に呼ぶ) */
|
|
147
|
+
reset: () => void;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* 歌詞トラック辞書から同期コンダクタを生成する。
|
|
151
|
+
*
|
|
152
|
+
* 演奏トラック(@n)で Note On されるたびに consume(n) を呼び、対応する歌詞配列の
|
|
153
|
+
* 現在の音節を消費(ポインタをインクリメント)する。合成方法には依存しない。
|
|
154
|
+
*/
|
|
155
|
+
declare const createLyricsConductor: (lyrics: Map<number, LyricTrack>) => LyricsConductor;
|
|
156
|
+
/** 歌唱合成モデルの実装シグネチャ */
|
|
157
|
+
type VoiceModel = {
|
|
158
|
+
/** 1音節を `ctx.currentTime + e.when` のタイミングで即時発音する(直接呼び出し用)。 */
|
|
159
|
+
(syllable: LyricSyllable, e: PlayNoteEvent): void;
|
|
160
|
+
/** 内部状態(直前母音など)を初期化する。 */
|
|
161
|
+
reset?: () => void;
|
|
162
|
+
/**
|
|
163
|
+
* 1音節を合成してキャッシュへ積み、再生に使うキャッシュキーを返す(重い処理はここ)。
|
|
164
|
+
* ストリーミングスケジューラが「先回り合成」に使う。直前母音は呼び出し側が明示で渡す
|
|
165
|
+
* (モデル内部状態に依存しないので、同一モデルを複数トラックで共有しても干渉しない)。
|
|
166
|
+
* 合成不能(該当音素なし・無声)なら null。klatt 等の軽量モデルは未実装でよい。
|
|
167
|
+
*/
|
|
168
|
+
renderToCache?: (syllable: LyricSyllable, prevVowel: string, pitch: number, durationMs: number) => Promise<string | null>;
|
|
169
|
+
/**
|
|
170
|
+
* {@link renderToCache} 済みのバッファを絶対時刻 t0(AudioContextクロック秒)へスケジュールする。
|
|
171
|
+
* t0 は未来の任意時刻でよく、再生はオーディオスレッドが担うのでメインスレッドのもたつきに影響されない。
|
|
172
|
+
*/
|
|
173
|
+
scheduleCached?: (key: string, t0: number, peak: number, pan: number) => void;
|
|
174
|
+
/** スケジュール済みの発音をすべて即停止する(停止・一時停止・シーク時)。 */
|
|
175
|
+
stopAll?: () => void;
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* klatt風フォルマント合成モデルを生成する。
|
|
179
|
+
*
|
|
180
|
+
* のこぎり波の声門音源を2つのバンドパス(F1,F2)で共鳴させ母音を作る。
|
|
181
|
+
* 子音が摩擦音・破裂音なら短いノイズバーストを頭に付加する。
|
|
182
|
+
*
|
|
183
|
+
* 戻り値の VoiceModel は (音節, PlayNoteEvent) を受け取り、
|
|
184
|
+
* `ctx.currentTime + e.when` のタイミングで destination へ発音する。
|
|
185
|
+
*/
|
|
186
|
+
declare const createKlattVoice: (ctx: AudioContext, destination: AudioNode) => VoiceModel;
|
|
187
|
+
/** koe音源(.koe)が置かれているパブリックバケットのベースURL */
|
|
188
|
+
declare const KOE_BASE_URL = "https://pub-12482a6b5cbc4c9e906b2e1904cabae5.r2.dev";
|
|
189
|
+
/**
|
|
190
|
+
* 内蔵koe音源カタログ: MML中の簡略キーワード → バケット直下の .koe ファイル名。
|
|
191
|
+
* 例: `@@0 roze かな…` で「束音ロゼ」を使う。
|
|
192
|
+
* ファイル名はそのまま encodeURIComponent して URL を組み立てる({@link koeUrl})。
|
|
193
|
+
*/
|
|
194
|
+
declare const KOE_VOICEBANKS: Record<string, string>;
|
|
195
|
+
/**
|
|
196
|
+
* koe音源キーワード → UI表示名(日本語)。歌詞モデルのプルダウン等で使う。
|
|
197
|
+
* MML中の値はキーワード({@link KOE_VOICEBANKS} のキー)のまま、表示だけ和名にする。
|
|
198
|
+
*/
|
|
199
|
+
declare const KOE_VOICEBANK_LABELS: Record<string, string>;
|
|
200
|
+
/**
|
|
201
|
+
* モデルキーワード → 内蔵キャラクター画像キー(voice-images.ts の VOICE_IMAGES キー)。
|
|
202
|
+
* klatt合成は "puyuyu"、koe音源は音源名に対応する画像キーを返す。
|
|
203
|
+
*/
|
|
204
|
+
declare const VOICE_IMAGE_KEY: Record<string, string>;
|
|
205
|
+
/**
|
|
206
|
+
* UTAU音源キーワード → 利用規約URL。
|
|
207
|
+
*/
|
|
208
|
+
declare const KOE_VOICEBANK_TERMS: Record<string, string>;
|
|
209
|
+
/** ファイル名(日本語可)を encodeURIComponent して .koe のフルURLにする */
|
|
210
|
+
declare const koeUrl: (name: string, base?: string) => string;
|
|
211
|
+
/** 多音階バンクのピッチトークン1件(トークン文字列とそのMIDI番号)。 */
|
|
212
|
+
type PitchToken = {
|
|
213
|
+
token: string;
|
|
214
|
+
midi: number;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* 音源マニフェストのエイリアス一覧から、多音階のピッチトークン("_G4" 等)を収集する。
|
|
218
|
+
* 接尾辞を持つエイリアスが1つも無ければ空配列(=単独音/連続音バンク)。
|
|
219
|
+
* 多音階バンクでは全エイリアスが `_ピッチ` 付きで bare エイリアスが存在しないことがあり、
|
|
220
|
+
* その場合は目標ノートに最も近いトークンを base に付与しないと解決できない。
|
|
221
|
+
*/
|
|
222
|
+
declare const collectPitchTokens: (aliases: Iterable<string>) => PitchToken[];
|
|
223
|
+
/** koe音源の生成オプション */
|
|
224
|
+
type KoeVoiceOptions = {
|
|
225
|
+
/** .koe アーカイブのURL、または Blob/File */
|
|
226
|
+
koe: string | Blob;
|
|
227
|
+
/**
|
|
228
|
+
* worldline.js のURL(WORLDボコーダによる高品質再合成)。
|
|
229
|
+
* 省略時は GitHub Pages のホストを使う。`worldline.wasm` は同じ階層から解決される。
|
|
230
|
+
*/
|
|
231
|
+
worldlineScriptUrl?: string;
|
|
232
|
+
/**
|
|
233
|
+
* Worldline(WASM)を使わず、素片を AudioBufferSource の playbackRate で
|
|
234
|
+
* ピッチシフトして鳴らす軽量モード。WASMの読み込みを避けたいときに。
|
|
235
|
+
*/
|
|
236
|
+
lightweight?: boolean;
|
|
237
|
+
/**
|
|
238
|
+
* 歌声合成Worker(`voice-worker.js`)のURL。指定すると重いWORLD再合成を
|
|
239
|
+
* 別スレッドで実行し、メインスレッド(楽器・UI)を一切ブロックしない。
|
|
240
|
+
* 省略時は従来どおりメインスレッドで合成する(後方互換)。
|
|
241
|
+
*/
|
|
242
|
+
voiceWorkerUrl?: string;
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* @onjmin/koe の音源(UTAU由来 .koe)で1音節を歌う {@link VoiceModel} を生成する。
|
|
246
|
+
*
|
|
247
|
+
* VoiceBank で音素PCMをオンデマンド取得し、Worldline(WORLDボコーダ)で目標ピッチ・
|
|
248
|
+
* 音価へ再合成して、共有 AudioContext のタイムライン(`ctx.currentTime + e.when`)へ
|
|
249
|
+
* スケジュールする。Worldlineが使えない/素片が短すぎる場合は素片のピッチシフトへ自動フォールバックする。
|
|
250
|
+
*
|
|
251
|
+
* 音源とWASMの読み込みは非同期のため、戻り値は Promise。`await` してから歌わせること。
|
|
252
|
+
*/
|
|
253
|
+
declare const createKoeVoice: (ctx: AudioContext, destination: AudioNode, options: KoeVoiceOptions) => Promise<VoiceModel>;
|
|
254
|
+
/** ストリーミング再生する歌唱ノート1つ(絶対時刻ベース)。 */
|
|
255
|
+
type StreamVoiceNote = {
|
|
256
|
+
syllable: LyricSyllable;
|
|
257
|
+
/** MIDIノート番号 */
|
|
258
|
+
pitch: number;
|
|
259
|
+
/** アンカー(再生開始時刻)からの相対秒。実発音時刻 = anchorTime + startSec。 */
|
|
260
|
+
startSec: number;
|
|
261
|
+
/** ゲート適用済みの発音長(秒)。 */
|
|
262
|
+
durationSec: number;
|
|
263
|
+
};
|
|
264
|
+
/** ストリーミング再生する歌詞トラック1本。 */
|
|
265
|
+
type StreamVoiceTrack = {
|
|
266
|
+
/** 歌唱モデル名(koe音源キーワード or "klatt")。 */
|
|
267
|
+
model: string;
|
|
268
|
+
/** 最終ゲイン(声量×マスタ等を適用済み。1=等倍)。 */
|
|
269
|
+
volume: number;
|
|
270
|
+
/** ステレオ定位 -1〜+1。 */
|
|
271
|
+
pan: number;
|
|
272
|
+
/** 発音順(startSec昇順)の歌唱ノート列。 */
|
|
273
|
+
notes: StreamVoiceNote[];
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* 歌唱モデルをまとめて管理し、koeデモ式の「先読みストリーミング合成」で歌わせる高レベルヘルパ。
|
|
277
|
+
*
|
|
278
|
+
* 再生開始時に {@link startStream} を呼ぶと、各音を**全力で先回り合成**しながら、
|
|
279
|
+
* 出来た音を AudioContext クロックの**絶対時刻へ即スケジュール**する。再生はオーディオスレッドが
|
|
280
|
+
* 担うため、合成中にメインスレッドがもたついても同期ズレ・音切れが起きない。スロットルは掛けない。
|
|
281
|
+
*
|
|
282
|
+
* 典型的な使い方(呼び出し側=シーケンサ):
|
|
283
|
+
* await voices.loadModels(models); // .koe をfetch(ローディング表示)
|
|
284
|
+
* await voices.warm(tracks); // 先頭数音だけ先に合成(頭出しの貯金)
|
|
285
|
+
* seq.start(fromStep); // 楽器とUIはシーケンサ
|
|
286
|
+
* voices.startStream(tracks, seq.getStartTime()); // 歌声は同じアンカーで先読み合成
|
|
287
|
+
*/
|
|
288
|
+
type SingingVoices = {
|
|
289
|
+
/** 使用する歌唱モデル(.koe)をロードして完了を待つ。 */
|
|
290
|
+
loadModels: (models: Iterable<string>) => Promise<void>;
|
|
291
|
+
/**
|
|
292
|
+
* 各トラック先頭の数音を先に合成してキャッシュへ積む(頭出しの貯金)。
|
|
293
|
+
* これで再生開始直後の密なフレーズでもアンダーランしにくくなる。count 既定 {@link PREWARM_NOTES}。
|
|
294
|
+
*/
|
|
295
|
+
warm: (tracks: StreamVoiceTrack[], count?: number) => Promise<void>;
|
|
296
|
+
/**
|
|
297
|
+
* 歌声のストリーミング再生を開始する。anchorTime は startSec=0 が鳴るべき
|
|
298
|
+
* AudioContextクロック秒(=シーケンサの開始時刻と一致させること)。
|
|
299
|
+
* 即座に return し、合成は裏で先回り進行する。
|
|
300
|
+
*/
|
|
301
|
+
startStream: (tracks: StreamVoiceTrack[], anchorTime: number) => void;
|
|
302
|
+
/** 進行中のストリームを中断し、スケジュール済みの発音をすべて止める(停止・一時停止・シーク)。 */
|
|
303
|
+
stopStream: () => void;
|
|
304
|
+
/** ストリーム停止+各モデルの内部状態を初期化する。 */
|
|
305
|
+
reset: () => void;
|
|
306
|
+
};
|
|
307
|
+
/** {@link SingingVoices.warm} の既定先合成数(各トラック先頭からの音数)。 */
|
|
308
|
+
declare const PREWARM_NOTES = 3;
|
|
309
|
+
type SingingVoicesOptions = {
|
|
310
|
+
/** 追加・上書きするkoe音源カタログ(キーワード → .koe URL または Blob) */
|
|
311
|
+
voicebanks?: Record<string, string | Blob>;
|
|
312
|
+
/** worldline.js のURL({@link createKoeVoice} に渡す) */
|
|
313
|
+
worldlineScriptUrl?: string;
|
|
314
|
+
/** koe音源を軽量モード(素片ピッチシフト)で鳴らす */
|
|
315
|
+
lightweight?: boolean;
|
|
316
|
+
/**
|
|
317
|
+
* 歌声合成Worker(`voice-worker.js`)のURL。指定すると重いWORLD再合成を
|
|
318
|
+
* 別スレッドで実行し、メインスレッド(楽器・UI)を一切ブロックしない(モバイル推奨)。
|
|
319
|
+
* 省略時は従来どおりメインスレッドで合成する。
|
|
320
|
+
*/
|
|
321
|
+
voiceWorkerUrl?: string;
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* klatt と koe音源を一括で扱う {@link SingingVoices} を生成する。
|
|
325
|
+
*/
|
|
326
|
+
declare const createSingingVoices: (ctx: AudioContext, destination: AudioNode, options?: SingingVoicesOptions) => SingingVoices;
|
|
327
|
+
/** 歌唱合成モデルのレジストリ(プラグイン方式) */
|
|
328
|
+
type VoiceRegistry = {
|
|
329
|
+
/** 指定モデルで1音節を歌う。未登録モデルは fallback へ委譲する */
|
|
330
|
+
sing: (model: string, syllable: LyricSyllable, e: PlayNoteEvent) => void;
|
|
331
|
+
/** 歌唱合成モデルを登録する(拡張用) */
|
|
332
|
+
register: (name: string, model: VoiceModel) => void;
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* モデル名 → 合成実装のレジストリを生成する(プラグイン方式)。
|
|
336
|
+
* 未登録のモデル名は fallback(既定 "klatt")へ委譲する。
|
|
337
|
+
*/
|
|
338
|
+
declare const createVoiceRegistry: (models?: Record<string, VoiceModel>, fallback?: string) => VoiceRegistry;
|
|
339
|
+
|
|
340
|
+
declare const DEFAULT_VOCAL_VOLUME = 200;
|
|
341
|
+
declare const DEFAULT_BPM = 120;
|
|
342
|
+
declare const DEFAULT_GATE = 100;
|
|
343
|
+
declare const DEFAULT_PAN = 64;
|
|
344
|
+
declare const DEFAULT_VELOCITY = 100;
|
|
345
|
+
declare const DEFAULT_PLAYBACK_VELOCITY = 127;
|
|
346
|
+
declare const DEFAULT_STEPS_PER_BAR = 192;
|
|
61
347
|
type Note = {
|
|
62
348
|
id: number;
|
|
63
349
|
startStep: number;
|
|
@@ -112,6 +398,76 @@ type PlayNoteEvent = {
|
|
|
112
398
|
when: number;
|
|
113
399
|
/** 秒 */
|
|
114
400
|
duration: number;
|
|
401
|
+
/**
|
|
402
|
+
* ステレオ定位 -1(完全左)〜+1(完全右)、0が中央。未指定は中央扱い。
|
|
403
|
+
* 歌詞トラックに p<n>(0-127, 既定64=中央) があれば正規化した値が載る。
|
|
404
|
+
* 合成側は StereoPannerNode.pan などにそのまま渡せばよい。
|
|
405
|
+
*/
|
|
406
|
+
pan?: number;
|
|
407
|
+
/**
|
|
408
|
+
* 歌詞同期で消費された音節(@@n 歌詞トラックがあるときのみ)。
|
|
409
|
+
* 利用側は voiceModel に応じて歌唱合成へ回す。未指定なら楽器音として鳴らす。
|
|
410
|
+
*
|
|
411
|
+
* この音節が載っているとき、volume はノートのvelocityではなく
|
|
412
|
+
* 歌詞トラック独自の「声量」(model:vol) ×マスタ音量を反映する。
|
|
413
|
+
* 合成音声は velocity を参照せず volume をそのまま音量係数として使えばよい。
|
|
414
|
+
*/
|
|
415
|
+
syllable?: LyricSyllable;
|
|
416
|
+
/** syllable を歌う合成モデル名("klatt" 等)。syllable とセットで届く */
|
|
417
|
+
voiceModel?: string;
|
|
418
|
+
};
|
|
419
|
+
type LyricSyllable = {
|
|
420
|
+
/** 表示用かな("きょ" 等。長音は置換後の母音かな) */
|
|
421
|
+
kana: string;
|
|
422
|
+
/** ローマ字子音("k" "sh" 等。母音始まり・撥音は ""/"N"、促音は "Q") */
|
|
423
|
+
consonant: string;
|
|
424
|
+
/** 母音 "a"|"i"|"u"|"e"|"o"、撥音 "N"、促音 "" */
|
|
425
|
+
vowel: string;
|
|
426
|
+
};
|
|
427
|
+
type LyricTrack = {
|
|
428
|
+
/** 対応する演奏トラックID(@n の n) */
|
|
429
|
+
trackId: number;
|
|
430
|
+
/** 合成モデル名("klatt" 等。小文字化済み) */
|
|
431
|
+
model: string;
|
|
432
|
+
/**
|
|
433
|
+
* 歌唱の声量 0-400。ノートのvelocity(楽器の強弱)とは独立した合成音声専用パラメータ。
|
|
434
|
+
* MMLでは `@@n klatt v80 …` のように v トークンで付与する(`model:80` も後方互換で可)。既定300。
|
|
435
|
+
* 0 で無音、100 で等倍、100超は増幅(ブースト)。実ゲインは vocalVolumeToGain により
|
|
436
|
+
* 0-100は線形・100超はdB線形(対数)で換算され、v=400 で約 +24dB(≒15.8倍)になる。
|
|
437
|
+
*/
|
|
438
|
+
volume: number;
|
|
439
|
+
/**
|
|
440
|
+
* 歌唱のゲートタイム 0-100(音価に対する発音長の割合)。既定100(レガート)。
|
|
441
|
+
* MMLでは `@@n klatt q80 …` のように q トークンで付与する。
|
|
442
|
+
* 小さいほど短く切れた発音(ハイライトも早く消える)、100 で次の音節直前まで持続。
|
|
443
|
+
*/
|
|
444
|
+
gate: number;
|
|
445
|
+
/**
|
|
446
|
+
* ステレオ定位 0-127(0=完全左, 64=中央, 127=右)。既定64。
|
|
447
|
+
* MMLでは `@@n klatt p0 …` のように p トークンで付与する。
|
|
448
|
+
*/
|
|
449
|
+
pan: number;
|
|
450
|
+
/**
|
|
451
|
+
* オクターブシフト -2〜+2(半音換算で octave×12 だけ歌唱ピッチを上下する)。既定0。
|
|
452
|
+
* 音源ごとに得意な音域が異なるため、演奏ノート(@n)のピッチをそのまま使わず
|
|
453
|
+
* オクターブ単位でずらして歌わせるための合成専用パラメータ。
|
|
454
|
+
* MMLでは `@@n klatt o-1 …` のように o トークンで付与する。
|
|
455
|
+
*/
|
|
456
|
+
octave?: number;
|
|
457
|
+
/** 正規化済み音節列 */
|
|
458
|
+
syllables: LyricSyllable[];
|
|
459
|
+
/**
|
|
460
|
+
* 歌詞が複数行(改行)で書かれていたときの改行位置。
|
|
461
|
+
* 値は「この音節インデックスの直前に改行があった」を表す syllables のインデックス列。
|
|
462
|
+
* 表示UIが改行を `\n` として見せるために使う(発音・同期には一切影響しない)。
|
|
463
|
+
*/
|
|
464
|
+
lineBreaks?: number[];
|
|
465
|
+
/**
|
|
466
|
+
* 歌詞行のメタ部分(モデル名+ v/q/p 等のオプション)の原文。
|
|
467
|
+
* 例: `@@3 teto v200 …` なら "teto v200"。
|
|
468
|
+
* 再生専用UIがメタ部分をグレーアウト表示するために使う(発音・同期には影響しない)。
|
|
469
|
+
*/
|
|
470
|
+
metaText?: string;
|
|
115
471
|
};
|
|
116
472
|
type PlayDrumEvent = {
|
|
117
473
|
pitch: number;
|
|
@@ -132,6 +488,20 @@ type ParseChordsFn = (str: string, bpm: number) => Array<{
|
|
|
132
488
|
duration: number;
|
|
133
489
|
}>;
|
|
134
490
|
type ParseMidiFn = (bytes: Uint8Array) => unknown;
|
|
491
|
+
/**
|
|
492
|
+
* 永続化対象の表示・出力設定。利用側がブラウザ再訪時に復元する用途に使う。
|
|
493
|
+
* (ノートやMML本体ではなく、エディタの見え方/出力の挙動の設定)
|
|
494
|
+
*/
|
|
495
|
+
type DawViewState = {
|
|
496
|
+
/** 横方向ズーム(%) */
|
|
497
|
+
zoomX: number;
|
|
498
|
+
/** 縦方向ズーム(%) */
|
|
499
|
+
zoomY: number;
|
|
500
|
+
/** 和音分解モード(getMMLで和音を単音トラックへ分解する) */
|
|
501
|
+
decomposeChord: boolean;
|
|
502
|
+
/** 和音分解時に和音伴奏トラックを分解対象から除外するフラグ */
|
|
503
|
+
ignoreChordHeavy: boolean;
|
|
504
|
+
};
|
|
135
505
|
type DawOptions = {
|
|
136
506
|
/** メロディックトラックのノート発音要求 */
|
|
137
507
|
onPlayNote?: (e: PlayNoteEvent) => void;
|
|
@@ -143,6 +513,16 @@ type DawOptions = {
|
|
|
143
513
|
getAudioTime?: () => number;
|
|
144
514
|
/** 録音ボタン押下(利用側のオーディオグラフに依存するため任意) */
|
|
145
515
|
onToggleRecord?: () => void;
|
|
516
|
+
/**
|
|
517
|
+
* ドラムパターンが変化したときに呼ばれる(ユーザー操作・MML読み込みによる自動入力の両方)。
|
|
518
|
+
* 利用側が選択状態を永続化する用途に使う。
|
|
519
|
+
*/
|
|
520
|
+
onDrumChange?: (name: string) => void;
|
|
521
|
+
/**
|
|
522
|
+
* 表示・出力設定(ズーム / 和音分解モード / 和音伴奏トラック無視)が変化したときに呼ばれる。
|
|
523
|
+
* 利用側が選択状態を永続化する用途に使う。
|
|
524
|
+
*/
|
|
525
|
+
onViewStateChange?: (state: DawViewState) => void;
|
|
146
526
|
parseChord?: ParseChordFn;
|
|
147
527
|
parseChords?: ParseChordsFn;
|
|
148
528
|
parseMidi?: ParseMidiFn;
|
|
@@ -150,6 +530,8 @@ type DawOptions = {
|
|
|
150
530
|
tracks?: TrackConfig[];
|
|
151
531
|
/** ドラムパターン辞書。既定は DRUM_PATTERNS */
|
|
152
532
|
drumPatterns?: Record<string, DrumPattern>;
|
|
533
|
+
/** 歌唱合成の先読みや制御を行うヘルパ(.koe音源の再生前プリロードに使用) */
|
|
534
|
+
singingVoices?: SingingVoices;
|
|
153
535
|
defaultBpm?: number;
|
|
154
536
|
initialMML?: string;
|
|
155
537
|
};
|
|
@@ -162,6 +544,19 @@ type DawInstance = {
|
|
|
162
544
|
full: string;
|
|
163
545
|
minified: string;
|
|
164
546
|
};
|
|
547
|
+
/**
|
|
548
|
+
* MML出力の先頭に埋め込む楽器プリセット名を設定する(トラックとは1対1でないトップレベル宣言)。
|
|
549
|
+
* 空文字で宣言なし。ライブラリ自体は音源を持たないため、名前を運ぶだけ(再生側が解決する)。
|
|
550
|
+
*/
|
|
551
|
+
setInstrument: (name: string) => void;
|
|
552
|
+
/** 現在のドラムパターン名を返す(永続化の保存用)。 */
|
|
553
|
+
getDrum: () => string;
|
|
554
|
+
/** ドラムパターンを設定する(未知のキーは無視)。選択UIにも反映する。 */
|
|
555
|
+
setDrum: (name: string) => void;
|
|
556
|
+
/** 現在の表示・出力設定を返す(永続化の保存用)。 */
|
|
557
|
+
getViewState: () => DawViewState;
|
|
558
|
+
/** 表示・出力設定を復元する(指定したキーのみ反映。UIにも反映する)。 */
|
|
559
|
+
setViewState: (state: Partial<DawViewState>) => void;
|
|
165
560
|
loadMML: (mml: string) => void;
|
|
166
561
|
loadMIDI: (bytes: Uint8Array) => void;
|
|
167
562
|
exportMIDI: () => Blob;
|
|
@@ -342,7 +737,13 @@ declare class MMLCore {
|
|
|
342
737
|
*/
|
|
343
738
|
private getNoteWithOctave;
|
|
344
739
|
/**
|
|
345
|
-
* MML
|
|
740
|
+
* MML生成(単一パス・発音順スキャン)
|
|
741
|
+
*
|
|
742
|
+
* 以前は1/2小節ごとのウィンドウで走査していたが、それだと半小節境界をまたぐ音符が
|
|
743
|
+
* 境界で切り詰められて「ぶつ切り」になり、境界直前に始まる音符は隙間が潰れて欠落し、
|
|
744
|
+
* 歌詞(@@n)の音節割り当てがずれていた。
|
|
745
|
+
* 全ノートを発音順に一度で処理し、次の発音までの距離だけを上限として
|
|
746
|
+
* 各音符の長さを忠実に出力する(次の音符がなければ曲末まで伸ばせる)。
|
|
346
747
|
*/
|
|
347
748
|
private generateMML;
|
|
348
749
|
/**
|
|
@@ -465,7 +866,30 @@ declare const exportMIDI: (options: ExportMidiOptions) => Blob;
|
|
|
465
866
|
*
|
|
466
867
|
* 旧来 demo/index.html の `loadMML` をライブラリへ移植・整理したもの。
|
|
467
868
|
* 実際のノート追加(MMLCore への反映)は呼び出し側で行う。
|
|
869
|
+
*
|
|
870
|
+
* 歌詞専用行(@@n model lyrics)は演奏ノートではないため常に取り除く。
|
|
871
|
+
* `collectLyrics` 指定時は解析済みの歌詞トラック辞書も併せて返す。
|
|
468
872
|
*/
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* 曲全体に効くトップレベル宣言(トラックとは1対1ではない)。
|
|
876
|
+
* MMLの先頭などに `#inst=<プリセット> #drum=<パターン> #volume=<全体音量>` の形で埋め込む。
|
|
877
|
+
* 他のMMLプレイヤーには無害(解析時に除去される)。
|
|
878
|
+
*/
|
|
879
|
+
type MmlMeta = {
|
|
880
|
+
/** 楽器プリセット名(INSTRUMENT_PRESETS のキー等。利用側が音源解決に使う) */
|
|
881
|
+
instrument?: string;
|
|
882
|
+
/** ドラムパターン名(DRUM_PATTERNS のキー) */
|
|
883
|
+
drum?: string;
|
|
884
|
+
/** 全体音量(0-100等) */
|
|
885
|
+
volume?: number;
|
|
886
|
+
};
|
|
887
|
+
/** MMLからトップレベル宣言を抽出する */
|
|
888
|
+
declare const parseMmlMeta: (mml: string) => MmlMeta;
|
|
889
|
+
/** MMLからトップレベル宣言を取り除く(ノート解析が誤解釈しないように) */
|
|
890
|
+
declare const stripMmlMeta: (mml: string) => string;
|
|
891
|
+
/** メタ情報を `#inst=… #drum=… #volume=…` のMML宣言文字列へ直列化する(空なら空文字) */
|
|
892
|
+
declare const formatMmlMeta: (meta: MmlMeta) => string;
|
|
469
893
|
type MMLNotePlacement = {
|
|
470
894
|
/** 0:melody 1:submelody 2:bass 3:chord */
|
|
471
895
|
trackIndex: number;
|
|
@@ -473,20 +897,99 @@ type MMLNotePlacement = {
|
|
|
473
897
|
pitch: number;
|
|
474
898
|
durationSteps: number;
|
|
475
899
|
};
|
|
900
|
+
/** 再生ビュー用の表示トークン(mountMmlPlayer のノート列ハイライトに使う) */
|
|
901
|
+
type MMLDisplayToken = {
|
|
902
|
+
/** 正規化済みのトークン文字列(小文字・空白除去) */
|
|
903
|
+
text: string;
|
|
904
|
+
/** 発音開始ステップ。制御トークン(o/l/</>)は直近の currentStep */
|
|
905
|
+
startStep: number;
|
|
906
|
+
/** 長さステップ。制御トークンは 0(ハイライト対象外) */
|
|
907
|
+
durationSteps: number;
|
|
908
|
+
type: "note" | "chord" | "rest" | "octave" | "shift" | "length" | "ctrl";
|
|
909
|
+
};
|
|
476
910
|
type ParsedMML = {
|
|
477
911
|
placements: MMLNotePlacement[];
|
|
478
912
|
/** メロディトラックの t 指定から検出したBPM(無ければ null) */
|
|
479
913
|
bpm: number | null;
|
|
914
|
+
/**
|
|
915
|
+
* trackIndex → 表示トークン列。`collectTokens` 指定時のみ生成。
|
|
916
|
+
* placements と同一パスで構築するため発音位置と完全に同期する。
|
|
917
|
+
*/
|
|
918
|
+
tokenTracks?: Map<number, MMLDisplayToken[]>;
|
|
919
|
+
/**
|
|
920
|
+
* 歌詞トラックID → 歌詞トラック。`collectLyrics` 指定時のみ生成。
|
|
921
|
+
* 演奏トラック(@n)の n と同じIDで対応づく。
|
|
922
|
+
*/
|
|
923
|
+
lyrics?: Map<number, LyricTrack>;
|
|
924
|
+
/** トップレベル宣言(楽器プリセット・ドラムパターン)。常に返す(無ければ空オブジェクト) */
|
|
925
|
+
meta: MmlMeta;
|
|
480
926
|
};
|
|
481
927
|
type ParseMMLOptions = {
|
|
482
928
|
/** 1小節あたりのステップ数。既定192 */
|
|
483
929
|
stepsPerBar?: number;
|
|
930
|
+
/** 再生ビュー用の表示トークン列も併せて返す。既定 false */
|
|
931
|
+
collectTokens?: boolean;
|
|
932
|
+
/** 歌詞トラック(@@n)の解析結果も併せて返す。既定 false */
|
|
933
|
+
collectLyrics?: boolean;
|
|
934
|
+
/**
|
|
935
|
+
* このトラック数以上のチャンネル(@n)をベース(index 2)へ畳み込む。
|
|
936
|
+
* 4トラックDAWの読込専用の都合。未指定なら畳み込まず実際の @n をそのまま使う
|
|
937
|
+
* (再生専用ビュー等、全トラックを忠実に表示したい用途の既定)。
|
|
938
|
+
*/
|
|
939
|
+
clampTrackCount?: number;
|
|
484
940
|
};
|
|
485
941
|
/**
|
|
486
942
|
* MML文字列を解析してノート配置とBPMを返す。
|
|
487
943
|
*/
|
|
488
944
|
declare const parseMML: (mml: string, options?: ParseMMLOptions) => ParsedMML;
|
|
489
945
|
|
|
946
|
+
/**
|
|
947
|
+
* mountMmlPlayer — MML文字列を「読むだけ」で再生できる軽量ビュー(Layer 2)。
|
|
948
|
+
*
|
|
949
|
+
* 編集UIの mountDAW と対になる存在で、ノート列を横スクロールのトークン帯として表示し、
|
|
950
|
+
* 再生位置をハイライト&オートスクロールする。パースは DTM 正準の parseMML を、
|
|
951
|
+
* 再生スケジューリングは createSequencer を用いるため、コアとモデルが一本化される。
|
|
952
|
+
*
|
|
953
|
+
* 発音は mountDAW と同じく onPlayNote へ注入する設計だが、手軽に鳴らせるよう
|
|
954
|
+
* オプトインの簡易square-wave synth(`synth`)も同梱する。onPlayNote 未指定なら既定で有効。
|
|
955
|
+
*/
|
|
956
|
+
|
|
957
|
+
type MmlPlayerOptions = {
|
|
958
|
+
/** メロディックノートの発音要求。未指定かつ synth 未指定なら内蔵synthが鳴る */
|
|
959
|
+
onPlayNote?: (e: PlayNoteEvent) => void;
|
|
960
|
+
/**
|
|
961
|
+
* ドラムノートの発音要求(MMLのトップレベル宣言 `#drum=…` から解決)。
|
|
962
|
+
* 未指定かつ内蔵synth有効なら、簡易ドラム音で鳴る。
|
|
963
|
+
*/
|
|
964
|
+
onPlayDrum?: (e: PlayDrumEvent) => void;
|
|
965
|
+
/** ドラムパターン辞書。`#drum=<キー>` の解決に使う。既定 DRUM_PATTERNS */
|
|
966
|
+
drumPatterns?: Record<string, DrumPattern>;
|
|
967
|
+
/** 再生クロック秒。既定は内蔵synthの AudioContext.currentTime もしくは performance.now()/1000 */
|
|
968
|
+
getAudioTime?: () => number;
|
|
969
|
+
/** 初回再生時に呼ばれる(AudioContext.resume 等に使う) */
|
|
970
|
+
onResumeAudio?: () => void | Promise<void>;
|
|
971
|
+
/** 内蔵の簡易square-wave synthを使うか。既定は onPlayNote 未指定なら true */
|
|
972
|
+
synth?: boolean;
|
|
973
|
+
/** BPM未検出時のフォールバック。既定120 */
|
|
974
|
+
defaultBpm?: number;
|
|
975
|
+
/** 各トラックの 0-100 ボリューム。既定100 */
|
|
976
|
+
volume?: number;
|
|
977
|
+
/** trackIndex順の表示色。既定はPICO-8パレット4色 */
|
|
978
|
+
trackColors?: string[];
|
|
979
|
+
/** 歌唱合成の先読みや制御を行うヘルパ(.koe音源の再生前プリロードに使用) */
|
|
980
|
+
singingVoices?: SingingVoices;
|
|
981
|
+
};
|
|
982
|
+
type MmlPlayerInstance = {
|
|
983
|
+
play: () => void;
|
|
984
|
+
stop: () => void;
|
|
985
|
+
isPlaying: () => boolean;
|
|
986
|
+
destroy: () => void;
|
|
987
|
+
};
|
|
988
|
+
/**
|
|
989
|
+
* MML文字列を再生専用ビューとして target にマウントする。
|
|
990
|
+
*/
|
|
991
|
+
declare const mountMmlPlayer: (target: HTMLElement, mml: string, options?: MmlPlayerOptions) => MmlPlayerInstance;
|
|
992
|
+
|
|
490
993
|
type PianoRollInstance = {
|
|
491
994
|
core: MMLCore;
|
|
492
995
|
getNotes: () => Note[];
|
|
@@ -610,6 +1113,11 @@ type Sequencer = {
|
|
|
610
1113
|
start: (fromStep?: number) => void;
|
|
611
1114
|
stop: () => void;
|
|
612
1115
|
isActive: () => boolean;
|
|
1116
|
+
/**
|
|
1117
|
+
* 直近の start() が確定した再生開始時刻(getAudioTimeクロック秒、START_DELAY込み)。
|
|
1118
|
+
* 歌声ストリーミング等を同じアンカーで揃えるのに使う。start前は0。
|
|
1119
|
+
*/
|
|
1120
|
+
getStartTime: () => number;
|
|
613
1121
|
};
|
|
614
1122
|
declare const createSequencer: (options: SequencerOptions) => Sequencer;
|
|
615
1123
|
|
|
@@ -619,10 +1127,10 @@ declare const createSequencer: (options: SequencerOptions) => Sequencer;
|
|
|
619
1127
|
* ドット絵・レトロゲーム風ピクセルアートデザイン(美咲フォント・角なし・ハードシャドウ)。
|
|
620
1128
|
* モバイルファースト(狭幅基準)で設計し、min-width メディアクエリで広幅に拡張する。
|
|
621
1129
|
*/
|
|
622
|
-
declare const DAW_CSS = "\n@font-face {\n font-family: 'k8x12';\n src: url('https://db.onlinewebfonts.com/t/777630d46640dc5a928ea833c2fcb875.woff2') format('woff2'),\n url('https://db.onlinewebfonts.com/t/777630d46640dc5a928ea833c2fcb875.woff') format('woff'),\n url('https://db.onlinewebfonts.com/t/777630d46640dc5a928ea833c2fcb875.ttf') format('truetype');\n font-weight: normal;\n font-style: normal;\n}\n\n/* ====================================================\n PIXEL MUSIC STUDIO \u2014 \u30C9\u30C3\u30C8\u7D75UI\u30B7\u30B9\u30C6\u30E0\n PICO-8\u30AB\u30E9\u30FC\u30D1\u30EC\u30C3\u30C8\u30FB\u7F8E\u54B2\u30D5\u30A9\u30F3\u30C8\u30FB\u30B2\u30FC\u30E0\u30A6\u30A3\u30F3\u30C9\u30A6\u67A0\n ==================================================== */\n\n.dtm-daw {\n /* PICO-8 16\u8272\u30D1\u30EC\u30C3\u30C8\u3088\u308A */\n --c-black: #000000;\n --c-navy: #1d2b53;\n --c-purple: #7e2553;\n --c-dkgreen: #008751;\n --c-brown: #ab5236;\n --c-dkgray: #5f574f;\n --c-gray: #c2c3c7;\n --c-white: #fff1e8;\n --c-red: #ff004d;\n --c-orange: #ffa300;\n --c-yellow: #ffec27;\n --c-green: #00e436;\n --c-cyan: #29adff;\n --c-lavend: #83769c;\n --c-pink: #ff77a8;\n --c-peach: #ffccaa;\n\n /* \u30BB\u30DE\u30F3\u30C6\u30A3\u30C3\u30AF\u30C8\u30FC\u30AF\u30F3 */\n --dtm-bg: var(--c-black);\n --dtm-surface: var(--c-navy);\n --dtm-deep: #0a0f1f;\n --dtm-border: var(--c-cyan);\n --dtm-border2: var(--c-dkgray);\n --dtm-text: var(--c-white);\n --dtm-muted: var(--c-lavend);\n --dtm-primary: var(--c-cyan);\n --dtm-pfg: var(--c-black);\n --dtm-danger: var(--c-red);\n --dtm-success: var(--c-green);\n --dtm-accent: var(--c-pink);\n --dtm-gold: var(--c-yellow);\n --dtm-warn: var(--c-orange);\n --dtm-tap: 40px;\n --dtm-gap: 6px;\n --dtm-font: 'k8x12',ui-monospace,monospace;\n\n box-sizing: border-box;\n font-family: var(--dtm-font);\n font-size: 14px;\n line-height: 1.6;\n letter-spacing: .06em;\n color: var(--dtm-text);\n background: var(--dtm-bg);\n width: 100%;\n display: flex;\n flex-direction: column;\n gap: var(--dtm-gap);\n padding: 6px;\n image-rendering: pixelated;\n -webkit-font-smoothing: none;\n -moz-osx-font-smoothing: unset;\n font-smooth: never;\n -webkit-tap-highlight-color: transparent;\n}\n.dtm-daw *,\n.dtm-daw *::before,\n.dtm-daw *::after { box-sizing: border-box; }\n\n/* \u2500\u2500\u2500 \u30B2\u30FC\u30E0\u30A6\u30A3\u30F3\u30C9\u30A6\u5171\u901A\u67A0 \u2500\u2500\u2500 */\n/* \u5916\u67A0(\u9ED22px) \u2192 \u8272\u4ED8\u304D2px border \u2192 \u5185\u67A0(\u9ED2inset2px) \u306E3\u91CD\u69CB\u9020 */\n.dtm-win {\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-primary),\n 4px 4px 0 var(--c-black);\n background: var(--dtm-surface);\n}\n\n/* \u2500\u2500\u2500 \u5171\u901A\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */\n.dtm-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n min-height: var(--dtm-tap);\n min-width: var(--dtm-tap);\n padding: 0 10px;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-surface);\n color: var(--dtm-text);\n font-family: var(--dtm-font);\n font-size: 13px;\n text-transform: uppercase;\n letter-spacing: .12em;\n cursor: pointer;\n user-select: none;\n white-space: nowrap;\n box-shadow: 3px 3px 0 var(--c-black);\n transition: none;\n}\n.dtm-btn:active { transform: translate(3px,3px); box-shadow: none; }\n.dtm-btn:disabled { opacity: .3; cursor: default; box-shadow: none; }\n.dtm-btn--primary { border-color: var(--dtm-primary); background: var(--dtm-primary); color: var(--dtm-pfg); }\n.dtm-btn--success { border-color: var(--dtm-success); background: var(--dtm-success); color: var(--c-black); }\n.dtm-btn--danger { border-color: var(--dtm-danger); background: var(--dtm-danger); color: var(--c-white); }\n.dtm-btn--accent { border-color: var(--dtm-accent); background: var(--dtm-accent); color: var(--c-black); }\n.dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }\n.dtm-btn--icon { padding: 0; }\n\n/* \u2500\u2500\u2500 \u30A2\u30A4\u30B3\u30F3\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */\n.dtm-iconbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dtm-tap);\n height: var(--dtm-tap);\n flex: 0 0 auto;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-surface);\n color: var(--dtm-text);\n font-size: 16px;\n cursor: pointer;\n box-shadow: 3px 3px 0 var(--c-black);\n}\n.dtm-iconbtn:active { transform: translate(3px,3px); box-shadow: none; }\n.dtm-iconbtn:disabled { opacity: .3; cursor: default; box-shadow: none; }\n\n/* \u2500\u2500\u2500 \u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u30D0\u30FC\uFF08HUD\u30B9\u30BF\u30A4\u30EB\uFF09 \u2500\u2500\u2500 */\n.dtm-topbar {\n position: sticky;\n top: 0;\n z-index: 20;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--dtm-gap);\n padding: 6px;\n background: var(--dtm-deep);\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-success),\n 4px 4px 0 var(--c-black);\n}\n\n/* PLAY\u30DC\u30BF\u30F3 \u2014 \u30B2\u30FC\u30E0\u306E\u300C\u6C7A\u5B9A\u30DC\u30BF\u30F3\u300D\u7684\u5B58\u5728\u611F */\n.dtm-play {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 44px;\n padding: 0 20px;\n border: 2px solid var(--c-black);\n background: var(--dtm-success);\n color: var(--c-black);\n font-family: var(--dtm-font);\n font-size: 14px;\n text-transform: uppercase;\n letter-spacing: .2em;\n cursor: pointer;\n box-shadow: 0 0 0 2px var(--dtm-success), 4px 4px 0 var(--c-black);\n}\n.dtm-play:active { transform: translate(4px,4px); box-shadow: none; }\n.dtm-play:disabled { opacity: .35; cursor: default; box-shadow: none; }\n.dtm-play--stop {\n background: var(--dtm-danger);\n box-shadow: 0 0 0 2px var(--dtm-danger), 4px 4px 0 var(--c-black);\n color: var(--c-white);\n}\n.dtm-rec { color: var(--dtm-danger); }\n\n/* BPM \u2014 \u30C7\u30B8\u30BF\u30EB\u30AB\u30A6\u30F3\u30BF\u30FC\u98A8 */\n.dtm-label {\n font-family: var(--dtm-font);\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: .14em;\n color: var(--dtm-muted);\n white-space: nowrap;\n}\n.dtm-checkbox-label {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: var(--dtm-font);\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: .12em;\n color: var(--dtm-muted);\n cursor: pointer;\n user-select: none;\n margin-top: 4px;\n}\n.dtm-checkbox-label:hover { color: var(--dtm-text); }\n.dtm-checkbox-label--sub { margin-left: 20px; font-size: 10px; }\n.dtm-checkbox {\n width: 14px;\n height: 14px;\n accent-color: var(--dtm-success);\n cursor: pointer;\n flex-shrink: 0;\n}\n\n.dtm-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: var(--dtm-font);\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: .1em;\n color: var(--dtm-muted);\n cursor: pointer;\n}\n.dtm-toggle input { width: 16px; height: 16px; accent-color: var(--dtm-accent); }\n\n/* \u2500\u2500\u2500 \u30C4\u30FC\u30EB\u30C9\u30C3\u30AF\uFF08\u88C5\u5099\u30B9\u30ED\u30C3\u30C8\u98A8\uFF09 \u2500\u2500\u2500 */\n.dtm-tooldock {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--dtm-gap);\n padding: 6px;\n background: var(--dtm-deep);\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-border2),\n 4px 4px 0 var(--c-black);\n}\n.dtm-sep {\n width: 2px; align-self: stretch;\n background: var(--dtm-border2); margin: 2px;\n}\n.dtm-row .dtm-label[data-dtm] { min-width: 48px; text-align: center; }\n\n/* \u2500\u2500\u2500 \u30BB\u30B0\u30E1\u30F3\u30C8\uFF08\u30A2\u30A4\u30C6\u30E0\u30B9\u30ED\u30C3\u30C8\uFF09 \u2500\u2500\u2500 */\n.dtm-seg {\n display: inline-flex;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-deep);\n box-shadow: 3px 3px 0 var(--c-black);\n}\n.dtm-segbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dtm-tap);\n height: var(--dtm-tap);\n border: none;\n border-right: 2px solid var(--dtm-border2);\n background: transparent;\n color: var(--dtm-muted);\n cursor: pointer;\n}\n.dtm-segbtn:last-child { border-right: none; }\n.dtm-segbtn--active {\n background: var(--dtm-gold);\n color: var(--c-black);\n}\n.dtm-segbtn:not(.dtm-segbtn--active):active { background: var(--dtm-border2); }\n\n/* \u2500\u2500\u2500 \u30D5\u30A9\u30FC\u30E0\u8981\u7D20 \u2500\u2500\u2500 */\n.dtm-select, .dtm-input, .dtm-textarea {\n min-height: var(--dtm-tap);\n padding: 4px 8px;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-deep);\n color: var(--dtm-text);\n font-family: var(--dtm-font);\n font-size: 13px;\n letter-spacing: .06em;\n box-shadow: inset 2px 2px 0 var(--c-black);\n}\n.dtm-select:focus, .dtm-input:focus, .dtm-textarea:focus {\n outline: none;\n border-color: var(--dtm-primary);\n}\n.dtm-input--num { width: 64px; text-align: center; font-size: 16px; }\n.dtm-textarea { width: 100%; min-height: 56px; resize: vertical; line-height: 1.7; }\n.dtm-range { height: var(--dtm-tap); accent-color: var(--dtm-primary); }\n\n/* \u2500\u2500\u2500 \u30C8\u30E9\u30C3\u30AF\u30D4\u30EB\uFF08\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u9078\u629E\u30DC\u30BF\u30F3\uFF09 \u2500\u2500\u2500 */\n.dtm-tracks {\n display: flex;\n flex-wrap: wrap;\n gap: var(--dtm-gap);\n}\n.dtm-pill {\n --dtm-pill-color: var(--dtm-primary);\n display: inline-flex;\n align-items: center;\n gap: 8px;\n flex: 1 1 auto;\n justify-content: center;\n min-height: 42px;\n padding: 0 12px;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-deep);\n color: var(--dtm-muted);\n font-family: var(--dtm-font);\n font-size: 13px;\n text-transform: uppercase;\n letter-spacing: .1em;\n cursor: pointer;\n box-shadow: 3px 3px 0 var(--c-black);\n}\n.dtm-pill .dtm-dot {\n width: 8px; height: 8px;\n background: var(--dtm-pill-color);\n flex: 0 0 auto;\n box-shadow: 1px 1px 0 var(--c-black);\n}\n/* \u30A2\u30AF\u30C6\u30A3\u30D6\u9078\u629E = \u91D1\u8272\u30CF\u30A4\u30E9\u30A4\u30C8 + \u30AB\u30FC\u30BD\u30EB */\n.dtm-pill--active {\n border-color: var(--dtm-gold);\n color: var(--dtm-gold);\n background: var(--dtm-surface);\n box-shadow: 0 0 0 2px var(--dtm-gold), 3px 3px 0 var(--c-black);\n}\n.dtm-pill--active::before { content: \"\u25BA \"; font-size: 10px; }\n.dtm-pill:not(.dtm-pill--active):active { transform: translate(3px,3px); box-shadow: none; }\n\n/* \u2500\u2500\u2500 \u30D4\u30A2\u30CE\u30ED\u30FC\u30EB\uFF08\u30C8\u30E9\u30C3\u30AB\u30FC\u98A8\uFF09 \u2500\u2500\u2500 */\n.dtm-roll-wrap { display: flex; gap: var(--dtm-gap); }\n.dtm-roll {\n position: relative;\n flex: 1 1 auto;\n height: 56vh;\n min-height: 280px;\n background: var(--dtm-deep);\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-border2),\n 4px 4px 0 var(--c-black);\n overflow: hidden;\n}\n.dtm-vscroll {\n position: relative;\n width: 20px;\n background: var(--dtm-deep);\n border: 2px solid var(--dtm-border2);\n cursor: pointer;\n flex: 0 0 auto;\n touch-action: none;\n}\n.dtm-vscroll-thumb, .dtm-hscroll-thumb {\n position: absolute;\n background: var(--dtm-primary);\n min-width: 20px;\n min-height: 20px;\n}\n.dtm-vscroll-thumb { left: 0; width: 100%; }\n.dtm-hscroll {\n position: relative;\n width: 100%; height: 20px;\n background: var(--dtm-deep);\n border: 2px solid var(--dtm-border2);\n cursor: pointer;\n touch-action: none;\n}\n.dtm-hscroll-thumb { top: 0; height: 100%; }\n\n/* \u2500\u2500\u2500 \u30D1\u30CD\u30EB\uFF08RPG\u30C0\u30A4\u30A2\u30ED\u30B0\u30A6\u30A3\u30F3\u30C9\u30A6\uFF09 \u2500\u2500\u2500 */\n.dtm-panel {\n background: var(--dtm-surface);\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-primary),\n 4px 4px 0 var(--c-black);\n overflow: hidden;\n}\n.dtm-panel > summary {\n list-style: none;\n cursor: pointer;\n padding: 0 12px;\n font-family: var(--dtm-font);\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: .14em;\n display: flex;\n align-items: center;\n min-height: var(--dtm-tap);\n background: var(--dtm-deep);\n border-bottom: 2px solid var(--dtm-border2);\n color: var(--dtm-primary);\n gap: 8px;\n}\n.dtm-panel:not([open]) > summary { border-bottom: none; }\n.dtm-panel > summary::-webkit-details-marker { display: none; }\n/* \u5DE6\u7AEF\u30E9\u30A4\u30F3\uFF08\u30B2\u30FC\u30E0UI\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u8272\u5206\u3051\uFF09 */\n.dtm-panel > summary::before {\n content: '';\n display: block;\n width: 4px;\n height: 20px;\n background: var(--dtm-accent);\n flex: 0 0 auto;\n}\n.dtm-panel[open] > summary::before { background: var(--dtm-primary); }\n/* \u6298\u308A\u305F\u305F\u307F\u77E2\u5370 */\n.dtm-panel > summary::after {\n content: \"\u25B6\";\n margin-left: auto;\n color: var(--dtm-muted);\n font-size: 10px;\n}\n.dtm-panel[open] > summary::after { content: \"\u25BC\"; }\n.dtm-panel-body { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 10px; }\n.dtm-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }\n.dtm-track-body { display: flex; flex-direction: column; gap: 10px; }\n\n/* \u2500\u2500\u2500 MML\u51FA\u529B\uFF08CRT\u30BF\u30FC\u30DF\u30CA\u30EB\uFF09 \u2500\u2500\u2500 */\n.dtm-output {\n background: var(--c-black);\n color: var(--dtm-success);\n border: 2px solid var(--dtm-success);\n padding: 10px;\n box-shadow: 0 0 0 2px var(--c-black), 4px 4px 0 var(--c-black);\n}\n.dtm-output::before {\n content: \"C:\\> MML OUTPUT\";\n display: block;\n font-size: 11px;\n color: var(--dtm-muted);\n letter-spacing: .14em;\n margin-bottom: 6px;\n padding-bottom: 6px;\n border-bottom: 1px solid var(--dtm-border2);\n}\n.dtm-output pre {\n margin: 0;\n background: transparent;\n padding: 0;\n overflow-x: auto;\n font-family: var(--dtm-font);\n font-size: 12px;\n line-height: 1.8;\n color: var(--dtm-success);\n}\n.dtm-output-row { display: flex; gap: 8px; align-items: flex-start; margin-top: 6px; }\n.dtm-output-row pre { flex: 1; }\n\n/* \u2500\u2500\u2500 \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4 \u2500\u2500\u2500 */\n.dtm-overlay {\n position: fixed; inset: 0; z-index: 1000;\n background: rgba(0,0,0,.92);\n display: flex; align-items: center; justify-content: center;\n flex-direction: column; gap: 14px;\n}\n.dtm-overlay[hidden] { display: none; }\n.dtm-overlay::before {\n content: 'NOW LOADING';\n font-family: var(--dtm-font);\n font-size: 13px;\n color: var(--dtm-primary);\n text-transform: uppercase;\n letter-spacing: .25em;\n animation: dtm-blink 1s steps(1) infinite;\n}\n/* 8\u30D6\u30ED\u30C3\u30AF\u523B\u307F\u3067\u57CB\u307E\u308B\u30D4\u30AF\u30BB\u30EB\u30D0\u30FC */\n.dtm-spinner {\n width: 96px; height: 12px;\n position: relative;\n background: var(--c-navy);\n border: 2px solid var(--dtm-primary);\n box-shadow: 0 0 0 2px var(--c-black), 4px 4px 0 var(--c-black);\n}\n.dtm-spinner::after {\n content: '';\n position: absolute;\n left: 0; top: 0; height: 100%;\n background: var(--dtm-primary);\n animation: dtm-load 1.6s steps(8) infinite;\n}\n@keyframes dtm-load { 0%{width:0} 100%{width:100%} }\n\n@keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }\n.dtm-blink { animation: dtm-blink 1s steps(1) infinite; }\n\n.dtm-hidden { display: none !important; }\n.dtm-grow { flex: 1 1 auto; }\n\n/* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */\n@media (min-width: 768px) {\n .dtm-daw { gap: 8px; padding: 10px; }\n .dtm-roll { height: 420px; }\n}\n";
|
|
1130
|
+
declare const DAW_CSS = "\n@font-face {\n font-family: 'k8x12';\n src: url('https://db.onlinewebfonts.com/t/777630d46640dc5a928ea833c2fcb875.woff2') format('woff2'),\n url('https://db.onlinewebfonts.com/t/777630d46640dc5a928ea833c2fcb875.woff') format('woff'),\n url('https://db.onlinewebfonts.com/t/777630d46640dc5a928ea833c2fcb875.ttf') format('truetype');\n font-weight: normal;\n font-style: normal;\n}\n\n/* ====================================================\n PIXEL MUSIC STUDIO \u2014 \u30C9\u30C3\u30C8\u7D75UI\u30B7\u30B9\u30C6\u30E0\n PICO-8\u30AB\u30E9\u30FC\u30D1\u30EC\u30C3\u30C8\u30FB\u7F8E\u54B2\u30D5\u30A9\u30F3\u30C8\u30FB\u30B2\u30FC\u30E0\u30A6\u30A3\u30F3\u30C9\u30A6\u67A0\n ==================================================== */\n\n.dtm-daw {\n /* PICO-8 16\u8272\u30D1\u30EC\u30C3\u30C8\u3088\u308A */\n --c-black: #000000;\n --c-navy: #1d2b53;\n --c-purple: #7e2553;\n --c-dkgreen: #008751;\n --c-brown: #ab5236;\n --c-dkgray: #5f574f;\n --c-gray: #c2c3c7;\n --c-white: #fff1e8;\n --c-red: #ff004d;\n --c-orange: #ffa300;\n --c-yellow: #ffec27;\n --c-green: #00e436;\n --c-cyan: #29adff;\n --c-lavend: #83769c;\n --c-pink: #ff77a8;\n --c-peach: #ffccaa;\n\n /* \u30BB\u30DE\u30F3\u30C6\u30A3\u30C3\u30AF\u30C8\u30FC\u30AF\u30F3 */\n --dtm-bg: var(--c-black);\n --dtm-surface: var(--c-navy);\n --dtm-deep: #0a0f1f;\n --dtm-border: var(--c-cyan);\n --dtm-border2: var(--c-dkgray);\n --dtm-text: var(--c-white);\n --dtm-muted: var(--c-lavend);\n --dtm-primary: var(--c-cyan);\n --dtm-pfg: var(--c-black);\n --dtm-danger: var(--c-red);\n --dtm-success: var(--c-green);\n --dtm-accent: var(--c-pink);\n --dtm-gold: var(--c-yellow);\n --dtm-warn: var(--c-orange);\n --dtm-tap: 40px;\n --dtm-gap: 6px;\n --dtm-font: 'k8x12',ui-monospace,monospace;\n\n box-sizing: border-box;\n font-family: var(--dtm-font);\n font-size: 14px;\n line-height: 1.6;\n letter-spacing: .06em;\n color: var(--dtm-text);\n background: var(--dtm-bg);\n width: 100%;\n display: flex;\n flex-direction: column;\n gap: var(--dtm-gap);\n padding: 6px;\n image-rendering: pixelated;\n -webkit-font-smoothing: none;\n -moz-osx-font-smoothing: unset;\n font-smooth: never;\n -webkit-tap-highlight-color: transparent;\n}\n.dtm-daw *,\n.dtm-daw *::before,\n.dtm-daw *::after { box-sizing: border-box; }\n\n/* \u2500\u2500\u2500 \u30B2\u30FC\u30E0\u30A6\u30A3\u30F3\u30C9\u30A6\u5171\u901A\u67A0 \u2500\u2500\u2500 */\n/* \u5916\u67A0(\u9ED22px) \u2192 \u8272\u4ED8\u304D2px border \u2192 \u5185\u67A0(\u9ED2inset2px) \u306E3\u91CD\u69CB\u9020 */\n.dtm-win {\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-primary),\n 4px 4px 0 var(--c-black);\n background: var(--dtm-surface);\n}\n\n/* \u2500\u2500\u2500 \u5171\u901A\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */\n.dtm-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n min-height: var(--dtm-tap);\n min-width: var(--dtm-tap);\n padding: 0 10px;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-surface);\n color: var(--dtm-text);\n font-family: var(--dtm-font);\n font-size: 13px;\n text-transform: uppercase;\n letter-spacing: .12em;\n cursor: pointer;\n user-select: none;\n white-space: nowrap;\n box-shadow: 3px 3px 0 var(--c-black);\n transition: none;\n}\n.dtm-btn:active { transform: translate(3px,3px); box-shadow: none; }\n.dtm-btn:disabled { opacity: .3; cursor: default; box-shadow: none; }\n.dtm-btn--primary { border-color: var(--dtm-primary); background: var(--dtm-primary); color: var(--dtm-pfg); }\n.dtm-btn--success { border-color: var(--dtm-success); background: var(--dtm-success); color: var(--c-black); }\n.dtm-btn--danger { border-color: var(--dtm-danger); background: var(--dtm-danger); color: var(--c-white); }\n.dtm-btn--accent { border-color: var(--dtm-accent); background: var(--dtm-accent); color: var(--c-black); }\n.dtm-btn--ghost { background: transparent; border-color: var(--dtm-border2); }\n.dtm-btn--icon { padding: 0; }\n\n/* \u2500\u2500\u2500 \u30A2\u30A4\u30B3\u30F3\u30DC\u30BF\u30F3 \u2500\u2500\u2500 */\n.dtm-iconbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dtm-tap);\n height: var(--dtm-tap);\n flex: 0 0 auto;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-surface);\n color: var(--dtm-text);\n font-size: 16px;\n cursor: pointer;\n box-shadow: 3px 3px 0 var(--c-black);\n}\n.dtm-iconbtn:active { transform: translate(3px,3px); box-shadow: none; }\n.dtm-iconbtn:disabled { opacity: .3; cursor: default; box-shadow: none; }\n\n/* \u2500\u2500\u2500 \u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u30D0\u30FC\uFF08HUD\u30B9\u30BF\u30A4\u30EB\uFF09 \u2500\u2500\u2500 */\n.dtm-topbar {\n position: sticky;\n top: 0;\n z-index: 20;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--dtm-gap);\n padding: 6px;\n background: var(--dtm-deep);\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-success),\n 4px 4px 0 var(--c-black);\n}\n\n/* PLAY\u30DC\u30BF\u30F3 \u2014 \u30B2\u30FC\u30E0\u306E\u300C\u6C7A\u5B9A\u30DC\u30BF\u30F3\u300D\u7684\u5B58\u5728\u611F */\n.dtm-play {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 44px;\n padding: 0 20px;\n border: 2px solid var(--c-black);\n background: var(--dtm-success);\n color: var(--c-black);\n font-family: var(--dtm-font);\n font-size: 14px;\n text-transform: uppercase;\n letter-spacing: .2em;\n cursor: pointer;\n box-shadow: 0 0 0 2px var(--dtm-success), 4px 4px 0 var(--c-black);\n}\n.dtm-play:active { transform: translate(4px,4px); box-shadow: none; }\n.dtm-play:disabled { opacity: .35; cursor: default; box-shadow: none; }\n.dtm-play--stop {\n background: var(--dtm-danger);\n box-shadow: 0 0 0 2px var(--dtm-danger), 4px 4px 0 var(--c-black);\n color: var(--c-white);\n}\n.dtm-rec { color: var(--dtm-danger); }\n\n/* BPM \u2014 \u30C7\u30B8\u30BF\u30EB\u30AB\u30A6\u30F3\u30BF\u30FC\u98A8 */\n.dtm-label {\n font-family: var(--dtm-font);\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: .14em;\n color: var(--dtm-muted);\n white-space: nowrap;\n}\n.dtm-checkbox-label {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: var(--dtm-font);\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: .12em;\n color: var(--dtm-muted);\n cursor: pointer;\n user-select: none;\n margin-top: 4px;\n}\n.dtm-checkbox-label:hover { color: var(--dtm-text); }\n.dtm-checkbox-label--sub { margin-left: 20px; font-size: 10px; }\n.dtm-checkbox {\n width: 14px;\n height: 14px;\n accent-color: var(--dtm-success);\n cursor: pointer;\n flex-shrink: 0;\n}\n\n.dtm-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: var(--dtm-font);\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: .1em;\n color: var(--dtm-muted);\n cursor: pointer;\n}\n.dtm-toggle input { width: 16px; height: 16px; accent-color: var(--dtm-accent); }\n\n/* \u2500\u2500\u2500 \u30C4\u30FC\u30EB\u30C9\u30C3\u30AF\uFF08\u88C5\u5099\u30B9\u30ED\u30C3\u30C8\u98A8\uFF09 \u2500\u2500\u2500 */\n.dtm-tooldock {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--dtm-gap);\n padding: 6px;\n background: var(--dtm-deep);\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-border2),\n 4px 4px 0 var(--c-black);\n}\n.dtm-sep {\n width: 2px; align-self: stretch;\n background: var(--dtm-border2); margin: 2px;\n}\n.dtm-row .dtm-label[data-dtm] { min-width: 48px; text-align: center; }\n\n/* \u2500\u2500\u2500 \u30BB\u30B0\u30E1\u30F3\u30C8\uFF08\u30A2\u30A4\u30C6\u30E0\u30B9\u30ED\u30C3\u30C8\uFF09 \u2500\u2500\u2500 */\n.dtm-seg {\n display: inline-flex;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-deep);\n box-shadow: 3px 3px 0 var(--c-black);\n}\n.dtm-segbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dtm-tap);\n height: var(--dtm-tap);\n border: none;\n border-right: 2px solid var(--dtm-border2);\n background: transparent;\n color: var(--dtm-muted);\n cursor: pointer;\n}\n.dtm-segbtn:last-child { border-right: none; }\n.dtm-segbtn--active {\n background: var(--dtm-gold);\n color: var(--c-black);\n}\n.dtm-segbtn:not(.dtm-segbtn--active):active { background: var(--dtm-border2); }\n\n/* \u2500\u2500\u2500 \u30D5\u30A9\u30FC\u30E0\u8981\u7D20 \u2500\u2500\u2500 */\n.dtm-select, .dtm-input, .dtm-textarea {\n min-height: var(--dtm-tap);\n padding: 4px 8px;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-deep);\n color: var(--dtm-text);\n font-family: var(--dtm-font);\n font-size: 13px;\n letter-spacing: .06em;\n box-shadow: inset 2px 2px 0 var(--c-black);\n}\n.dtm-select:focus, .dtm-input:focus, .dtm-textarea:focus {\n outline: none;\n border-color: var(--dtm-primary);\n}\n.dtm-input--num { width: 64px; text-align: center; font-size: 16px; }\n.dtm-textarea { width: 100%; min-height: 56px; resize: vertical; line-height: 1.7; }\n.dtm-range { height: var(--dtm-tap); accent-color: var(--dtm-primary); }\n\n/* \u2500\u2500\u2500 \u30C8\u30E9\u30C3\u30AF\u30D4\u30EB\uFF08\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u9078\u629E\u30DC\u30BF\u30F3\uFF09 \u2500\u2500\u2500 */\n.dtm-tracks {\n display: flex;\n flex-wrap: wrap;\n gap: var(--dtm-gap);\n}\n.dtm-pill {\n --dtm-pill-color: var(--dtm-primary);\n display: inline-flex;\n align-items: center;\n gap: 8px;\n flex: 1 1 auto;\n justify-content: center;\n min-height: 42px;\n padding: 0 12px;\n border: 2px solid var(--dtm-border2);\n background: var(--dtm-deep);\n color: var(--dtm-muted);\n font-family: var(--dtm-font);\n font-size: 13px;\n text-transform: uppercase;\n letter-spacing: .1em;\n cursor: pointer;\n box-shadow: 3px 3px 0 var(--c-black);\n}\n.dtm-pill .dtm-dot {\n width: 8px; height: 8px;\n background: var(--dtm-pill-color);\n flex: 0 0 auto;\n box-shadow: 1px 1px 0 var(--c-black);\n}\n/* \u30A2\u30AF\u30C6\u30A3\u30D6\u9078\u629E = \u91D1\u8272\u30CF\u30A4\u30E9\u30A4\u30C8 + \u30AB\u30FC\u30BD\u30EB */\n.dtm-pill--active {\n border-color: var(--dtm-gold);\n color: var(--dtm-gold);\n background: var(--dtm-surface);\n box-shadow: 0 0 0 2px var(--dtm-gold), 3px 3px 0 var(--c-black);\n}\n.dtm-pill--active::before { content: \"\u25BA \"; font-size: 10px; }\n.dtm-pill:not(.dtm-pill--active):active { transform: translate(3px,3px); box-shadow: none; }\n\n/* \u2500\u2500\u2500 \u30D4\u30A2\u30CE\u30ED\u30FC\u30EB\uFF08\u30C8\u30E9\u30C3\u30AB\u30FC\u98A8\uFF09 \u2500\u2500\u2500 */\n.dtm-roll-wrap { display: flex; gap: var(--dtm-gap); }\n.dtm-roll {\n position: relative;\n flex: 1 1 auto;\n height: 56vh;\n min-height: 280px;\n background: var(--dtm-deep);\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-border2),\n 4px 4px 0 var(--c-black);\n overflow: hidden;\n}\n.dtm-vscroll {\n position: relative;\n width: 20px;\n background: var(--dtm-deep);\n border: 2px solid var(--dtm-border2);\n cursor: pointer;\n flex: 0 0 auto;\n touch-action: none;\n}\n.dtm-vscroll-thumb, .dtm-hscroll-thumb {\n position: absolute;\n background: var(--dtm-primary);\n min-width: 20px;\n min-height: 20px;\n}\n.dtm-vscroll-thumb { left: 0; width: 100%; }\n.dtm-hscroll {\n position: relative;\n width: 100%; height: 20px;\n background: var(--dtm-deep);\n border: 2px solid var(--dtm-border2);\n cursor: pointer;\n touch-action: none;\n}\n.dtm-hscroll-thumb { top: 0; height: 100%; }\n\n/* \u2500\u2500\u2500 \u30D1\u30CD\u30EB\uFF08RPG\u30C0\u30A4\u30A2\u30ED\u30B0\u30A6\u30A3\u30F3\u30C9\u30A6\uFF09 \u2500\u2500\u2500 */\n.dtm-panel {\n background: var(--dtm-surface);\n border: 2px solid var(--c-black);\n box-shadow:\n inset 0 0 0 2px var(--c-black),\n 0 0 0 2px var(--dtm-primary),\n 4px 4px 0 var(--c-black);\n overflow: hidden;\n}\n.dtm-panel > summary {\n list-style: none;\n cursor: pointer;\n padding: 0 12px;\n font-family: var(--dtm-font);\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: .14em;\n display: flex;\n align-items: center;\n min-height: var(--dtm-tap);\n background: var(--dtm-deep);\n border-bottom: 2px solid var(--dtm-border2);\n color: var(--dtm-primary);\n gap: 8px;\n}\n.dtm-panel:not([open]) > summary { border-bottom: none; }\n.dtm-panel > summary::-webkit-details-marker { display: none; }\n/* \u5DE6\u7AEF\u30E9\u30A4\u30F3\uFF08\u30B2\u30FC\u30E0UI\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u8272\u5206\u3051\uFF09 */\n.dtm-panel > summary::before {\n content: '';\n display: block;\n width: 4px;\n height: 20px;\n background: var(--dtm-accent);\n flex: 0 0 auto;\n}\n.dtm-panel[open] > summary::before { background: var(--dtm-primary); }\n/* \u6298\u308A\u305F\u305F\u307F\u77E2\u5370 */\n.dtm-panel > summary::after {\n content: \"\u25B6\";\n margin-left: auto;\n color: var(--dtm-muted);\n font-size: 10px;\n}\n.dtm-panel[open] > summary::after { content: \"\u25BC\"; }\n.dtm-panel-body { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 10px; }\n.dtm-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }\n.dtm-track-body { display: flex; flex-direction: column; gap: 10px; }\n\n/* \u2500\u2500\u2500 MML\u51FA\u529B\uFF08CRT\u30BF\u30FC\u30DF\u30CA\u30EB\uFF09 \u2500\u2500\u2500 */\n.dtm-output {\n background: var(--c-black);\n color: var(--dtm-success);\n border: 2px solid var(--dtm-success);\n padding: 10px;\n box-shadow: 0 0 0 2px var(--c-black), 4px 4px 0 var(--c-black);\n}\n.dtm-output::before {\n content: \"C:\\> MML OUTPUT\";\n display: block;\n font-size: 11px;\n color: var(--dtm-muted);\n letter-spacing: .14em;\n margin-bottom: 6px;\n padding-bottom: 6px;\n border-bottom: 1px solid var(--dtm-border2);\n}\n.dtm-output pre {\n margin: 0;\n background: transparent;\n padding: 0;\n overflow-x: auto;\n font-family: var(--dtm-font);\n font-size: 12px;\n line-height: 1.8;\n color: var(--dtm-success);\n}\n.dtm-output-row { display: flex; gap: 8px; align-items: flex-start; margin-top: 6px; }\n.dtm-output-row pre { flex: 1; }\n\n/* \u2500\u2500\u2500 \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4 \u2500\u2500\u2500 */\n.dtm-overlay {\n position: absolute; inset: 0; z-index: 1000;\n background: rgba(0,0,0,.92);\n display: flex; align-items: center; justify-content: center;\n flex-direction: column; gap: 14px;\n pointer-events: auto;\n cursor: wait;\n}\n.dtm-overlay[hidden] { display: none; }\n.dtm-overlay::before {\n content: 'NOW LOADING';\n font-family: var(--dtm-font);\n font-size: 13px;\n color: var(--dtm-primary);\n text-transform: uppercase;\n letter-spacing: .25em;\n animation: dtm-blink 1s steps(1) infinite;\n}\n/* 8\u30D6\u30ED\u30C3\u30AF\u523B\u307F\u3067\u57CB\u307E\u308B\u30D4\u30AF\u30BB\u30EB\u30D0\u30FC */\n.dtm-spinner {\n width: 96px; height: 12px;\n position: relative;\n background: var(--c-navy);\n border: 2px solid var(--dtm-primary);\n box-shadow: 0 0 0 2px var(--c-black), 4px 4px 0 var(--c-black);\n}\n.dtm-spinner::after {\n content: '';\n position: absolute;\n left: 0; top: 0; height: 100%;\n background: var(--dtm-primary);\n animation: dtm-load 1.6s steps(8) infinite;\n}\n@keyframes dtm-load { 0%{width:0} 100%{width:100%} }\n/* \u9032\u6357\u304C\u78BA\u5B9A\u3057\u305F\u3089\u7121\u9650\u30EB\u30FC\u30D7\u6F14\u51FA\u3092\u6B62\u3081\u3001\u5B9F\u6E2C\u5024\u3067\u5857\u308A\u3064\u3076\u3059 */\n.dtm-spinner--determinate::after { display: none; }\n.dtm-spinner-fill {\n position: absolute;\n left: 0; top: 0; height: 100%;\n width: 0;\n background: var(--dtm-primary);\n transition: width .12s steps(8);\n}\n.dtm-loading-label {\n font-family: var(--dtm-font);\n font-size: 11px;\n color: var(--dtm-primary);\n letter-spacing: .15em;\n min-height: 1em;\n}\n\n@keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }\n.dtm-blink { animation: dtm-blink 1s steps(1) infinite; }\n\n.dtm-hidden { display: none !important; }\n.dtm-grow { flex: 1 1 auto; }\n.dtm-lyric-icon {\n flex: 0 0 auto;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n object-fit: cover;\n}\n\n/* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */\n@media (min-width: 768px) {\n .dtm-daw { gap: 8px; padding: 10px; }\n .dtm-roll { height: 420px; }\n}\n\n/* ====================================================\n MML PLAYER \u2014 \u518D\u751F\u5C02\u7528\u30D3\u30E5\u30FC\uFF08mountMmlPlayer\uFF09\n ==================================================== */\n.dtm-player {\n display: flex;\n flex-direction: column;\n gap: var(--dtm-gap);\n padding: var(--dtm-gap);\n background: var(--dtm-deep);\n border: 2px solid var(--dtm-border2);\n box-shadow: 4px 4px 0 var(--c-black);\n}\n.dtm-player-head {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.dtm-player-play {\n flex: 0 0 auto;\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--dtm-primary);\n color: var(--dtm-pfg);\n border: 2px solid var(--c-black);\n box-shadow: 2px 2px 0 var(--c-black);\n cursor: pointer;\n padding: 0;\n}\n.dtm-player-play:active { transform: translate(2px, 2px); box-shadow: none; }\n.dtm-player-play--stop { background: var(--dtm-danger); }\n.dtm-player-play:disabled { opacity: 0.4; cursor: default; }\n.dtm-player-beat-row {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.dtm-player-beat-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dtm-border2);\n transition: background 0.06s;\n}\n.dtm-player-beat-dot--on { background: var(--dtm-primary); }\n.dtm-player-bar {\n font-family: 'k8x12', monospace;\n font-size: 11px;\n color: var(--dtm-text);\n min-width: 2em;\n margin-left: 4px;\n}\n.dtm-player-dots {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.dtm-player-dot { width: 8px; height: 8px; display: inline-block; }\n.dtm-player-mml-header {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.dtm-player-mml-link {\n font-family: 'k8x12', monospace;\n font-size: 10px;\n color: var(--dtm-muted);\n text-decoration: none;\n white-space: nowrap;\n}\n.dtm-player-mml-link:hover { color: var(--dtm-primary); }\n.dtm-player-emoji {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n font-size: 18px;\n line-height: 1;\n user-select: none;\n}\n@keyframes dtm-emoji-jump {\n 0% { transform: translateY(0); }\n 35% { transform: translateY(-5px); }\n 65% { transform: translateY(-5px); }\n 100% { transform: translateY(0); }\n}\n.dtm-player-emoji--jump {\n animation: dtm-emoji-jump 0.18s ease-out forwards;\n}\n.dtm-player-chip {\n font-family: 'k8x12', monospace;\n font-size: 9px;\n color: var(--dtm-text);\n background: var(--dtm-border2);\n padding: 2px 6px;\n white-space: nowrap;\n}\n.dtm-player-body {\n position: relative; /* \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4\u306E\u57FA\u6E96\u3002\u30EC\u30FC\u30F3\u7FA4\u3060\u3051\u3092\u8986\u3046 */\n display: flex;\n flex-direction: column;\n gap: var(--dtm-gap);\n}\n.dtm-player-lane-row {\n display: flex;\n align-items: stretch;\n gap: 6px;\n}\n.dtm-player-lane-label {\n flex: 0 0 auto;\n width: 16px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n padding-top: 4px;\n}\n.dtm-player-lane-label--btn {\n cursor: pointer;\n user-select: none;\n}\n.dtm-player-lane-label--btn:hover { opacity: 0.7; }\n.dtm-player-lane-label--muted { opacity: 0.3; }\n.dtm-player-lane-no {\n font-family: 'k8x12', monospace;\n font-size: 9px;\n color: var(--dtm-muted);\n}\n.dtm-player-lane {\n position: relative; /* \u30C8\u30FC\u30AF\u30F3\u306E offsetParent \u3092\u30EC\u30FC\u30F3\u306B\u56FA\u5B9A\u3057\u3001\u4E2D\u592E\u5BC4\u305B\u8A08\u7B97\u3092\u6B63\u3059 */\n flex: 1 1 auto;\n overflow-x: auto;\n white-space: nowrap;\n background: var(--c-black);\n border: 2px solid var(--dtm-border2);\n padding: 6px;\n scrollbar-width: none;\n}\n.dtm-player-lane::-webkit-scrollbar { display: none; }\n.dtm-tk {\n font-family: 'k8x12', monospace;\n font-size: 12px;\n color: var(--dtm-text);\n}\n.dtm-tk--rest { color: var(--dtm-muted); }\n.dtm-tk--octave,\n.dtm-tk--shift,\n.dtm-tk--length,\n.dtm-tk--ctrl { color: var(--dtm-border2); }\n.dtm-tk--lyric { color: var(--dtm-text); letter-spacing: 1px; }\n.dtm-tk--break { color: var(--dtm-muted); opacity: 0.7; margin: 0 2px; }\n.dtm-tk--meta { color: var(--dtm-border2); margin-right: 4px; }\n.dtm-tk.is-active {\n background: var(--tk, var(--dtm-primary));\n color: var(--c-black);\n font-weight: bold;\n}\n";
|
|
623
1131
|
/**
|
|
624
1132
|
* スタイルを document.head に一度だけ注入する。
|
|
625
1133
|
*/
|
|
626
1134
|
declare const injectStyles: (doc?: Document) => void;
|
|
627
1135
|
|
|
628
|
-
export { type AddNoteOptions, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type CoreEventHandlers, DAW_CSS, DRUM_FONT, DRUM_KEYS, DRUM_PATTERNS, type DawInstance, type DawOptions, type DrumPattern, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, LinkedList, MMLCore, type MMLNotePlacement, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type Note, PITCH_MAP, type ParseChordFn, type ParseChordsFn, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PlayDrumEvent, type PlayNoteEvent, type PlaybackState, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, createAudioContext, createPianoRoll, createSequencer, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, mountDAW, onClick, parseMML, setDrawOffset, setupRecorder, shiftNotes };
|
|
1136
|
+
export { type AddNoteOptions, type ApplyChordOptions, type ChordPatternType, type ChordPlacement, type ConsumedSyllable, type CoreEventHandlers, DAW_CSS, DEFAULT_BPM, DEFAULT_GATE, DEFAULT_PAN, DEFAULT_PLAYBACK_VELOCITY, DEFAULT_STEPS_PER_BAR, DEFAULT_VELOCITY, DEFAULT_VOCAL_VOLUME, DRUM_FONT, DRUM_KEYS, DRUM_PATTERNS, type DawInstance, type DawOptions, type DawViewState, type DrumPattern, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayerInstance, type MmlPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseChordFn, type ParseChordsFn, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayNoteEvent, type PlaybackState, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamVoiceNote, type StreamVoiceTrack, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createVoiceRegistry, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, setDrawOffset, setupRecorder, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
|