@onjmin/dtm 0.1.29 → 0.1.31

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/dist/index.d.mts CHANGED
@@ -309,17 +309,6 @@ type StreamPlaybackOptions = {
309
309
  * 引数には遅れたノート情報と遅延秒数が渡されます。
310
310
  */
311
311
  onLateSkip?: (note: StreamVoiceNote, delay: number) => void;
312
- /**
313
- * 合成が間に合わず、代替メロディ(楽器)を鳴らす際のコールバック。
314
- * 引数には対象トラック、ノート情報、および発音すべき絶対時刻(AudioContextクロック秒)が渡されます。
315
- */
316
- onFallbackPlayNote?: (track: StreamVoiceTrack, note: StreamVoiceNote, t0: number) => void;
317
- /**
318
- * 遅延判定の閾値(秒)。発音予定時刻よりこの秒数前に合成完了していない場合、
319
- * 遅延するとみなし代替メロディに切り替えます。
320
- * 省略時は 0.15 秒(150ms)です。
321
- */
322
- fallbackThresholdSec?: number;
323
312
  };
324
313
  /**
325
314
  * 歌唱モデルをまとめて管理し、koeデモ式の「先読みストリーミング合成」で歌わせる高レベルヘルパ。
@@ -1129,7 +1118,7 @@ declare const parseMmlMeta: (mml: string) => MmlMeta;
1129
1118
  /** MMLからトップレベル宣言を取り除く(ノート解析が誤解釈しないように) */
1130
1119
  declare const stripMmlMeta: (mml: string) => string;
1131
1120
  /** メタ情報を `#inst=… #drum=… #volume=… #mode=…` のMML宣言文字列へ直列化する(空なら空文字) */
1132
- declare const formatMmlMeta: (meta: MmlMeta) => string;
1121
+ declare const formatMmlMeta: (meta: MmlMeta, space?: string) => string;
1133
1122
  type MMLNotePlacement = {
1134
1123
  /** 0:melody 1:submelody 2:bass 3:chord */
1135
1124
  trackIndex: number;
@@ -1237,6 +1226,8 @@ type MmlPlayerInstance = {
1237
1226
  isPlaying: () => boolean;
1238
1227
  destroy: () => void;
1239
1228
  };
1229
+ declare const encodeMml: (mml: string) => Promise<string>;
1230
+ declare const decodeMml: (payload: string) => Promise<string>;
1240
1231
  /**
1241
1232
  * MML文字列を再生専用ビューとして target にマウントする。
1242
1233
  */
@@ -1623,4 +1614,6 @@ declare const DAW_CSS = "\n@font-face {\n font-family: 'k8x12';\n src: url('ht
1623
1614
  */
1624
1615
  declare const injectStyles: (doc?: Document) => void;
1625
1616
 
1626
- 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 DawMode, type DawOptions, type DawViewState, type DrumPattern, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LoopConfig, type LoopPoint, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, MML_END_MARKER, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayback, type MmlPlayerInstance, type MmlPlayerOptions, type ModeSwitchInstance, type ModeSwitchOptions, type MountEditorOptions, type MountPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayMmlOptions, type PlayNoteEvent, type PlaySingingMmlOptions, type PlaybackCue, type PlaybackState, type PresetSelectInstance, type PresetSelectOptions, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamPlaybackOptions, type StreamVoiceNote, type StreamVoiceTrack, type Synth, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createSynth, createVoiceRegistry, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, freqFromPitch, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, playMML, playSingingMML, setDrawOffset, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
1617
+ declare const VOICE_IMAGES: Record<string, string>;
1618
+
1619
+ 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 DawMode, type DawOptions, type DawViewState, type DrumPattern, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LoopConfig, type LoopPoint, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, MML_END_MARKER, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayback, type MmlPlayerInstance, type MmlPlayerOptions, type ModeSwitchInstance, type ModeSwitchOptions, type MountEditorOptions, type MountPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayMmlOptions, type PlayNoteEvent, type PlaySingingMmlOptions, type PlaybackCue, type PlaybackState, type PresetSelectInstance, type PresetSelectOptions, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamPlaybackOptions, type StreamVoiceNote, type StreamVoiceTrack, type Synth, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGES, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createSynth, createVoiceRegistry, decodeMml, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, encodeMml, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, freqFromPitch, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, playMML, playSingingMML, setDrawOffset, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
package/dist/index.d.ts CHANGED
@@ -309,17 +309,6 @@ type StreamPlaybackOptions = {
309
309
  * 引数には遅れたノート情報と遅延秒数が渡されます。
310
310
  */
311
311
  onLateSkip?: (note: StreamVoiceNote, delay: number) => void;
312
- /**
313
- * 合成が間に合わず、代替メロディ(楽器)を鳴らす際のコールバック。
314
- * 引数には対象トラック、ノート情報、および発音すべき絶対時刻(AudioContextクロック秒)が渡されます。
315
- */
316
- onFallbackPlayNote?: (track: StreamVoiceTrack, note: StreamVoiceNote, t0: number) => void;
317
- /**
318
- * 遅延判定の閾値(秒)。発音予定時刻よりこの秒数前に合成完了していない場合、
319
- * 遅延するとみなし代替メロディに切り替えます。
320
- * 省略時は 0.15 秒(150ms)です。
321
- */
322
- fallbackThresholdSec?: number;
323
312
  };
324
313
  /**
325
314
  * 歌唱モデルをまとめて管理し、koeデモ式の「先読みストリーミング合成」で歌わせる高レベルヘルパ。
@@ -1129,7 +1118,7 @@ declare const parseMmlMeta: (mml: string) => MmlMeta;
1129
1118
  /** MMLからトップレベル宣言を取り除く(ノート解析が誤解釈しないように) */
1130
1119
  declare const stripMmlMeta: (mml: string) => string;
1131
1120
  /** メタ情報を `#inst=… #drum=… #volume=… #mode=…` のMML宣言文字列へ直列化する(空なら空文字) */
1132
- declare const formatMmlMeta: (meta: MmlMeta) => string;
1121
+ declare const formatMmlMeta: (meta: MmlMeta, space?: string) => string;
1133
1122
  type MMLNotePlacement = {
1134
1123
  /** 0:melody 1:submelody 2:bass 3:chord */
1135
1124
  trackIndex: number;
@@ -1237,6 +1226,8 @@ type MmlPlayerInstance = {
1237
1226
  isPlaying: () => boolean;
1238
1227
  destroy: () => void;
1239
1228
  };
1229
+ declare const encodeMml: (mml: string) => Promise<string>;
1230
+ declare const decodeMml: (payload: string) => Promise<string>;
1240
1231
  /**
1241
1232
  * MML文字列を再生専用ビューとして target にマウントする。
1242
1233
  */
@@ -1623,4 +1614,6 @@ declare const DAW_CSS = "\n@font-face {\n font-family: 'k8x12';\n src: url('ht
1623
1614
  */
1624
1615
  declare const injectStyles: (doc?: Document) => void;
1625
1616
 
1626
- 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 DawMode, type DawOptions, type DawViewState, type DrumPattern, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LoopConfig, type LoopPoint, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, MML_END_MARKER, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayback, type MmlPlayerInstance, type MmlPlayerOptions, type ModeSwitchInstance, type ModeSwitchOptions, type MountEditorOptions, type MountPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayMmlOptions, type PlayNoteEvent, type PlaySingingMmlOptions, type PlaybackCue, type PlaybackState, type PresetSelectInstance, type PresetSelectOptions, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamPlaybackOptions, type StreamVoiceNote, type StreamVoiceTrack, type Synth, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createSynth, createVoiceRegistry, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, freqFromPitch, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, playMML, playSingingMML, setDrawOffset, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
1617
+ declare const VOICE_IMAGES: Record<string, string>;
1618
+
1619
+ 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 DawMode, type DawOptions, type DawViewState, type DrumPattern, type DtmStudio, type DtmStudioEngines, type DtmStudioOptions, type ExportMidiOptions, INSTRUMENT_PRESETS, type InstrumentPreset, KOE_BASE_URL, KOE_VOICEBANKS, KOE_VOICEBANK_LABELS, KOE_VOICEBANK_TERMS, type KoeVoiceOptions, LinkedList, type LoopConfig, type LoopPoint, type LyricSyllable, type LyricTrack, type LyricsConductor, MAX_VOCAL_VOLUME, MMLCore, type MMLDisplayToken, type MMLNotePlacement, MML_END_MARKER, type MidiExtraction, type MidiNotePlacement, type MidiTrackAnalysis, type MmlMeta, type MmlPlayback, type MmlPlayerInstance, type MmlPlayerOptions, type ModeSwitchInstance, type ModeSwitchOptions, type MountEditorOptions, type MountPlayerOptions, type Note, PITCH_MAP, PREWARM_NOTES, type ParseMMLOptions, type ParseMidiFn, type ParsedMML, type PianoRollInstance, type PianoRollOptions, type PitchToken, type PlayDrumEvent, type PlayMmlOptions, type PlayNoteEvent, type PlaySingingMmlOptions, type PlaybackCue, type PlaybackState, type PresetSelectInstance, type PresetSelectOptions, type PreviewSoundCallback, type RenderConfig, type Sequencer, type SequencerOptions, type SequencerTrack, type SingingVoices, type SingingVoicesOptions, type StreamPlaybackOptions, type StreamVoiceNote, type StreamVoiceTrack, type Synth, TRACKS_ADVANCED, TRACKS_SIMPLE, type ToolMode, type TrackConfig, VOICE_IMAGES, VOICE_IMAGE_KEY, type VoiceModel, type VoiceRegistry, analyzeMidiTracks, applyHarmonicFilter, applyMonophonic, buildChordPlacements, buildNameToKeyMapping, collectPitchTokens, createAudioContext, createDtmStudio, createKlattVoice, createKoeVoice, createLyricsConductor, createPianoRoll, createSequencer, createSingingVoices, createSynth, createVoiceRegistry, decodeMml, decomposeToMonophonic, drawGrid, drawHeader, drawKeyboard, drawNotes, drawSelectedNotes, drawSelectionRect, encodeMml, exportMIDI, extractMidiPlacements, extractMidiPlacementsByTrack, fetchSoundFontList, formatMmlMeta, freqFromPitch, generateRandomPattern, getDrawOffset, getGridCanvas, getGridContext, getGridPosition, getHeaderCanvas, getMidiBPM, getRenderConfig, getXY, icon, init, injectStyles, isChordHeavyTrack, koeUrl, mountDAW, mountMmlPlayer, normalizeLyrics, onClick, panToStereo, parseLyrics, parseMML, parseMmlMeta, playMML, playSingingMML, setDrawOffset, shiftNotes, stripLyrics, stripMmlMeta, vocalVolumeToGain };
package/dist/index.js CHANGED
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  PREWARM_NOTES: () => PREWARM_NOTES,
45
45
  TRACKS_ADVANCED: () => TRACKS_ADVANCED,
46
46
  TRACKS_SIMPLE: () => TRACKS_SIMPLE,
47
+ VOICE_IMAGES: () => VOICE_IMAGES,
47
48
  VOICE_IMAGE_KEY: () => VOICE_IMAGE_KEY,
48
49
  analyzeMidiTracks: () => analyzeMidiTracks,
49
50
  applyHarmonicFilter: () => applyHarmonicFilter,
@@ -61,6 +62,7 @@ __export(index_exports, {
61
62
  createSingingVoices: () => createSingingVoices,
62
63
  createSynth: () => createSynth,
63
64
  createVoiceRegistry: () => createVoiceRegistry,
65
+ decodeMml: () => decodeMml,
64
66
  decomposeToMonophonic: () => decomposeToMonophonic,
65
67
  drawGrid: () => drawGrid,
66
68
  drawHeader: () => drawHeader,
@@ -68,6 +70,7 @@ __export(index_exports, {
68
70
  drawNotes: () => drawNotes,
69
71
  drawSelectedNotes: () => drawSelectedNotes,
70
72
  drawSelectionRect: () => drawSelectionRect,
73
+ encodeMml: () => encodeMml,
71
74
  exportMIDI: () => exportMIDI,
72
75
  extractMidiPlacements: () => extractMidiPlacements,
73
76
  extractMidiPlacementsByTrack: () => extractMidiPlacementsByTrack,
@@ -2163,7 +2166,7 @@ var normalizeLyricLines = (lines) => {
2163
2166
  }
2164
2167
  return { syllables, lineBreaks };
2165
2168
  };
2166
- var LYRIC_LINE = /^@@(\d+)\s+(.*)$/;
2169
+ var LYRIC_LINE = /^@@(\d+)\s*(.*)$/;
2167
2170
  var isLyricContinuation = (seg) => !/^[@#]/.test(seg);
2168
2171
  var splitSegments = (mml) => mml.split(/[;\n\r]+/).map((s) => s.trim()).filter((s) => s.length > 0);
2169
2172
  var clamp = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
@@ -2181,38 +2184,56 @@ var parseLyrics = (mml) => {
2181
2184
  const m = segments[i].match(LYRIC_LINE);
2182
2185
  if (!m) continue;
2183
2186
  const trackId = Number.parseInt(m[1], 10);
2184
- const tokens = m[2].trim().split(/\s+/);
2185
- const modelToken = tokens.shift() ?? "";
2187
+ let rest = m[2].trim();
2186
2188
  let volume = DEFAULT_VOCAL_VOLUME;
2187
2189
  let gate = 100;
2188
2190
  let pan = 64;
2189
2191
  let octave = 0;
2190
- const colon = modelToken.indexOf(":");
2191
- const model = (colon === -1 ? modelToken : modelToken.slice(0, colon)).toLowerCase();
2192
- if (colon !== -1) {
2193
- const v = Number.parseInt(modelToken.slice(colon + 1), 10);
2194
- if (Number.isFinite(v)) volume = clamp(v, 0, MAX_VOCAL_VOLUME);
2195
- }
2196
- const metaTokens = [modelToken];
2197
- while (tokens.length > 0) {
2198
- const v = /^v(\d+)$/.exec(tokens[0]);
2199
- const q2 = /^q(\d+)$/.exec(tokens[0]);
2200
- const p = /^p(\d+)$/.exec(tokens[0]);
2201
- const o = /^o(-?\d+)$/.exec(tokens[0]);
2202
- if (v) {
2203
- volume = clamp(Number.parseInt(v[1], 10), 0, MAX_VOCAL_VOLUME);
2204
- } else if (q2) {
2205
- gate = clamp(Number.parseInt(q2[1], 10), 0, 100);
2206
- } else if (p) {
2207
- pan = clamp(Number.parseInt(p[1], 10), 0, 127);
2208
- } else if (o) {
2209
- octave = clamp(Number.parseInt(o[1], 10), -2, 2);
2210
- } else {
2211
- break;
2192
+ const modelMatch = rest.match(
2193
+ /^([a-z_]+?)(?=(?:[vqpo]-?\d)|[^a-z_]|$)(?::(\d+))?/i
2194
+ );
2195
+ let model = "";
2196
+ const metaTokens = [];
2197
+ if (modelMatch) {
2198
+ model = modelMatch[1].toLowerCase();
2199
+ if (modelMatch[2]) {
2200
+ volume = clamp(Number.parseInt(modelMatch[2], 10), 0, MAX_VOCAL_VOLUME);
2212
2201
  }
2213
- metaTokens.push(tokens.shift());
2202
+ metaTokens.push(modelMatch[0]);
2203
+ rest = rest.substring(modelMatch[0].length).trim();
2214
2204
  }
2215
- const lyricLines = [tokens.join(" ")];
2205
+ while (true) {
2206
+ const vMatch = rest.match(/^v(\d+)/i);
2207
+ if (vMatch) {
2208
+ volume = clamp(Number.parseInt(vMatch[1], 10), 0, MAX_VOCAL_VOLUME);
2209
+ metaTokens.push(vMatch[0]);
2210
+ rest = rest.substring(vMatch[0].length).trim();
2211
+ continue;
2212
+ }
2213
+ const qMatch = rest.match(/^q(\d+)/i);
2214
+ if (qMatch) {
2215
+ gate = clamp(Number.parseInt(qMatch[1], 10), 0, 100);
2216
+ metaTokens.push(qMatch[0]);
2217
+ rest = rest.substring(qMatch[0].length).trim();
2218
+ continue;
2219
+ }
2220
+ const pMatch = rest.match(/^p(\d+)/i);
2221
+ if (pMatch) {
2222
+ pan = clamp(Number.parseInt(pMatch[1], 10), 0, 127);
2223
+ metaTokens.push(pMatch[0]);
2224
+ rest = rest.substring(pMatch[0].length).trim();
2225
+ continue;
2226
+ }
2227
+ const oMatch = rest.match(/^o(-?\d+)/i);
2228
+ if (oMatch) {
2229
+ octave = clamp(Number.parseInt(oMatch[1], 10), -2, 2);
2230
+ metaTokens.push(oMatch[0]);
2231
+ rest = rest.substring(oMatch[0].length).trim();
2232
+ continue;
2233
+ }
2234
+ break;
2235
+ }
2236
+ const lyricLines = [rest];
2216
2237
  while (i + 1 < segments.length && isLyricContinuation(segments[i + 1])) {
2217
2238
  lyricLines.push(segments[++i]);
2218
2239
  }
@@ -2826,44 +2847,13 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2826
2847
  if (model.renderToCache && model.scheduleCached) {
2827
2848
  const renderToCache = model.renderToCache;
2828
2849
  const scheduleCached = model.scheduleCached;
2829
- const threshold = opts?.fallbackThresholdSec ?? 0.15;
2830
2850
  void (async () => {
2831
- let isTimeout = false;
2832
- let timeoutId;
2833
- const renderPromise = renderToCache(
2851
+ const key = await renderToCache(
2834
2852
  note.syllable,
2835
2853
  prevVowel,
2836
2854
  note.pitch,
2837
2855
  note.durationSec * 1e3
2838
- ).then((key2) => {
2839
- if (timeoutId) clearTimeout(timeoutId);
2840
- return { key: key2, type: "success" };
2841
- });
2842
- const timeoutLimitTime = t0 - threshold;
2843
- const timeoutDelayMs = (timeoutLimitTime - ctx.currentTime) * 1e3;
2844
- const timeoutPromise = new Promise(
2845
- (resolve) => {
2846
- if (timeoutDelayMs <= 0) {
2847
- isTimeout = true;
2848
- resolve({ key: null, type: "timeout" });
2849
- } else {
2850
- timeoutId = setTimeout(() => {
2851
- isTimeout = true;
2852
- resolve({ key: null, type: "timeout" });
2853
- }, timeoutDelayMs);
2854
- }
2855
- }
2856
2856
  );
2857
- const result = await Promise.race([renderPromise, timeoutPromise]);
2858
- if (session !== streamSession) return;
2859
- if (result.type === "timeout") {
2860
- console.warn(
2861
- `[dtm] Synthesizer expected late skip: ${note.syllable.kana} at ${note.startSec}s. Fallback to instrument.`
2862
- );
2863
- opts?.onFallbackPlayNote?.(track, note, t0);
2864
- return;
2865
- }
2866
- const key = result.key;
2867
2857
  if (session !== streamSession) return;
2868
2858
  if (key) {
2869
2859
  const delay = ctx.currentTime - t0;
@@ -2871,10 +2861,9 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2871
2861
  scheduleCached(key, t0, peak, track.pan);
2872
2862
  } else {
2873
2863
  console.warn(
2874
- `[dtm] Synthesizer late skip (safety): ${note.syllable.kana} at ${note.startSec}s (delayed by ${delay.toFixed(3)}s)`
2864
+ `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${note.startSec}s (delayed by ${delay.toFixed(3)}s)`
2875
2865
  );
2876
2866
  opts?.onLateSkip?.(note, delay);
2877
- opts?.onFallbackPlayNote?.(track, note, t0);
2878
2867
  }
2879
2868
  }
2880
2869
  })();
@@ -4308,7 +4297,7 @@ var parseMmlMeta = (mml) => {
4308
4297
  return meta;
4309
4298
  };
4310
4299
  var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "");
4311
- var formatMmlMeta = (meta) => {
4300
+ var formatMmlMeta = (meta, space = "") => {
4312
4301
  const parts = [];
4313
4302
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
4314
4303
  if (meta.drum) parts.push(`#drum=${meta.drum}`);
@@ -4316,7 +4305,7 @@ var formatMmlMeta = (meta) => {
4316
4305
  if (meta.drumVolume !== void 0)
4317
4306
  parts.push(`#drumvolume=${meta.drumVolume}`);
4318
4307
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
4319
- return parts.join(" ");
4308
+ return parts.join(space);
4320
4309
  };
4321
4310
  var parseMML = (mml, options = {}) => {
4322
4311
  const stepsPerBar = options.stepsPerBar ?? DEFAULT_STEPS_PER_BAR;
@@ -6268,6 +6257,9 @@ var customEncode = (str) => {
6268
6257
  const bytes = [];
6269
6258
  for (let i = 0; i < str.length; i++) {
6270
6259
  const code = str.charCodeAt(i);
6260
+ if (code === 32) {
6261
+ continue;
6262
+ }
6271
6263
  if (code <= 127) {
6272
6264
  bytes.push(code);
6273
6265
  } else if (code === PROLONGED_MARK) {
@@ -6299,6 +6291,82 @@ var encodeMml = async (mml) => {
6299
6291
  }
6300
6292
  return `u.${encodeURIComponent(mml)}`;
6301
6293
  };
6294
+ var fromBase64Url = (s) => {
6295
+ let normalized = s.replace(/-/g, "+").replace(/_/g, "/");
6296
+ while (normalized.length % 4 !== 0) {
6297
+ normalized += "=";
6298
+ }
6299
+ const bin = atob(normalized);
6300
+ const bytes = new Uint8Array(bin.length);
6301
+ for (let i = 0; i < bin.length; i++) {
6302
+ bytes[i] = bin.charCodeAt(i);
6303
+ }
6304
+ return bytes;
6305
+ };
6306
+ var customDecode = (bytes) => {
6307
+ let str = "";
6308
+ let i = 0;
6309
+ while (i < bytes.length) {
6310
+ const byte = bytes[i];
6311
+ if (byte <= 127) {
6312
+ str += String.fromCharCode(byte);
6313
+ i++;
6314
+ } else if (byte === VALUE_PROLONGED) {
6315
+ str += String.fromCharCode(PROLONGED_MARK);
6316
+ i++;
6317
+ } else if (byte >= 128 && byte <= 222) {
6318
+ str += String.fromCharCode(HIRAGANA_START + (byte - 128));
6319
+ i++;
6320
+ } else if (byte === SHIFT_KATAKANA) {
6321
+ if (i + 1 < bytes.length) {
6322
+ const nextByte = bytes[i + 1];
6323
+ if (nextByte >= 128 && nextByte <= 222) {
6324
+ str += String.fromCharCode(HIRAGANA_START + 96 + (nextByte - 128));
6325
+ }
6326
+ i += 2;
6327
+ } else {
6328
+ i++;
6329
+ }
6330
+ } else {
6331
+ i++;
6332
+ }
6333
+ }
6334
+ return str;
6335
+ };
6336
+ var gunzipCustom = async (bytes) => {
6337
+ const ds = new DecompressionStream("gzip");
6338
+ const writer = ds.writable.getWriter();
6339
+ writer.write(bytes);
6340
+ writer.close();
6341
+ const buf = await new Response(ds.readable).arrayBuffer();
6342
+ return customDecode(new Uint8Array(buf));
6343
+ };
6344
+ var gunzip = async (bytes) => {
6345
+ const ds = new DecompressionStream("gzip");
6346
+ const writer = ds.writable.getWriter();
6347
+ writer.write(bytes);
6348
+ writer.close();
6349
+ const buf = await new Response(ds.readable).arrayBuffer();
6350
+ return new TextDecoder().decode(buf);
6351
+ };
6352
+ var decodeMml = async (payload) => {
6353
+ if (!payload) return "";
6354
+ try {
6355
+ if (payload.startsWith("z.")) {
6356
+ return await gunzipCustom(fromBase64Url(payload.slice(2)));
6357
+ }
6358
+ if (payload.startsWith("g.")) {
6359
+ return await gunzip(fromBase64Url(payload.slice(2)));
6360
+ }
6361
+ if (payload.startsWith("u.")) {
6362
+ return decodeURIComponent(payload.slice(2));
6363
+ }
6364
+ return decodeURIComponent(payload);
6365
+ } catch (e) {
6366
+ console.error("[dtm] failed to decode MML payload", e);
6367
+ return "";
6368
+ }
6369
+ };
6302
6370
  var mountMmlPlayer = (target, mml, options = {}) => {
6303
6371
  injectStyles(target.ownerDocument ?? document);
6304
6372
  const {
@@ -7162,31 +7230,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
7162
7230
  if (streaming && !skipSinging) {
7163
7231
  ensureVoices().startStream(tracks, seq.getStartTime(), {
7164
7232
  isAudible: (t) => !mutedTracks.has(Number(t.id)),
7165
- onFallbackPlayNote: (track, note, t0) => {
7233
+ onLateSkip: () => {
7166
7234
  showPlayerMessage(
7167
7235
  "\u97F3\u58F0\u5408\u6210\u304C\u9593\u306B\u5408\u308F\u306A\u3044\u305F\u3081\u3001\u4E00\u90E8\u306E\u767A\u97F3\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F"
7168
7236
  );
7169
- const trackIdx = Number(track.id);
7170
- if (mutedTracks.has(trackIdx)) return;
7171
- const lt = lyricTracks.get(trackIdx);
7172
- if (!lt) return;
7173
- const relativeWhen = t0 - ensureCtx().currentTime;
7174
- if (relativeWhen > 0) {
7175
- const vol = (lt.volume ?? DEFAULT_VOCAL_VOLUME) / 100 * (trackVolume / 100);
7176
- const e = {
7177
- trackId: track.id ?? "",
7178
- pitch: note.pitch,
7179
- velocity: 100,
7180
- volume: vol,
7181
- when: relativeWhen,
7182
- duration: note.durationSec,
7183
- pan: track.pan
7184
- };
7185
- options.onPlayNote?.(e);
7186
- if (useSynth) ensureSynth().playNote(e);
7187
- }
7188
- },
7189
- onLateSkip: () => {
7190
7237
  }
7191
7238
  });
7192
7239
  }
@@ -8196,27 +8243,7 @@ var mountDAW = (target, options = {}) => {
8196
8243
  sequencer.start(fromStep);
8197
8244
  if (streaming && voices) {
8198
8245
  voices.startStream(streamTracks, sequencer.getStartTime(), {
8199
- isAudible: (t) => !isSolo || t.id === activeTrackId,
8200
- onFallbackPlayNote: (track, note, t0) => {
8201
- if (isSolo && track.id !== activeTrackId) return;
8202
- const relativeWhen = t0 - getAudioTime();
8203
- if (relativeWhen > 0) {
8204
- const trackState = trackStates.find(
8205
- (t) => t.config.id === track.id
8206
- );
8207
- const trackVol = trackState ? trackState.volume : 80;
8208
- const volume = trackVol / 100 * (masterVolume / 100);
8209
- options.onPlayNote?.({
8210
- trackId: track.id ?? "",
8211
- pitch: note.pitch,
8212
- velocity: 100,
8213
- volume,
8214
- when: relativeWhen,
8215
- duration: note.durationSec,
8216
- pan: track.pan
8217
- });
8218
- }
8219
- }
8246
+ isAudible: (t) => !isSolo || t.id === activeTrackId
8220
8247
  });
8221
8248
  }
8222
8249
  updateTransport();
@@ -8507,13 +8534,26 @@ var mountDAW = (target, options = {}) => {
8507
8534
  const barLimitBars = Number(refs.barLimitSelect.value);
8508
8535
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
8509
8536
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
8510
- const metaLine = formatMmlMeta({
8511
- instrument: currentInstrument || void 0,
8512
- drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8513
- volume: masterVolume,
8514
- drumVolume,
8515
- mode
8516
- });
8537
+ const metaLineFull = formatMmlMeta(
8538
+ {
8539
+ instrument: currentInstrument || void 0,
8540
+ drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8541
+ volume: masterVolume,
8542
+ drumVolume,
8543
+ mode
8544
+ },
8545
+ " "
8546
+ );
8547
+ const metaLineMini = formatMmlMeta(
8548
+ {
8549
+ instrument: currentInstrument || void 0,
8550
+ drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8551
+ volume: masterVolume,
8552
+ drumVolume,
8553
+ mode
8554
+ },
8555
+ ""
8556
+ );
8517
8557
  if (refs.decomposeChordToggle.checked) {
8518
8558
  const ignoreHeavy = refs.ignoreChordHeavyToggle.checked;
8519
8559
  const targetStates = ignoreHeavy ? trackStates.filter((t) => !isChordHeavyTrack(t.core.getNotes())) : trackStates;
@@ -8529,8 +8569,8 @@ var mountDAW = (target, options = {}) => {
8529
8569
  const decomposedMini = monoTracks.map(
8530
8570
  (notes, i) => `@${i}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
8531
8571
  );
8532
- const full2 = [metaLine, ...decomposedFull, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
8533
- const minified2 = [metaLine, ...decomposedMini, MML_END_MARKER].filter((s) => s.length > 0).join(";");
8572
+ const full2 = [metaLineFull, ...decomposedFull, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
8573
+ const minified2 = [metaLineMini, ...decomposedMini, MML_END_MARKER].filter((s) => s.length > 0).join(";");
8534
8574
  return {
8535
8575
  full: full2,
8536
8576
  minified: minified2,
@@ -8570,9 +8610,9 @@ var mountDAW = (target, options = {}) => {
8570
8610
  const head = params ? `${x.model} ${params}` : x.model;
8571
8611
  return `@@${x.i} ${head} ${x.text}`;
8572
8612
  });
8573
- const full = [metaLine, ...trackLines, ...lyricLines, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
8613
+ const full = [metaLineFull, ...trackLines, ...lyricLines, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
8574
8614
  const minified = [
8575
- metaLine,
8615
+ metaLineMini,
8576
8616
  ...trackLinesMini,
8577
8617
  ...lyricLines,
8578
8618
  MML_END_MARKER
@@ -10358,6 +10398,7 @@ var createDtmStudio = async (options = {}) => {
10358
10398
  PREWARM_NOTES,
10359
10399
  TRACKS_ADVANCED,
10360
10400
  TRACKS_SIMPLE,
10401
+ VOICE_IMAGES,
10361
10402
  VOICE_IMAGE_KEY,
10362
10403
  analyzeMidiTracks,
10363
10404
  applyHarmonicFilter,
@@ -10375,6 +10416,7 @@ var createDtmStudio = async (options = {}) => {
10375
10416
  createSingingVoices,
10376
10417
  createSynth,
10377
10418
  createVoiceRegistry,
10419
+ decodeMml,
10378
10420
  decomposeToMonophonic,
10379
10421
  drawGrid,
10380
10422
  drawHeader,
@@ -10382,6 +10424,7 @@ var createDtmStudio = async (options = {}) => {
10382
10424
  drawNotes,
10383
10425
  drawSelectedNotes,
10384
10426
  drawSelectionRect,
10427
+ encodeMml,
10385
10428
  exportMIDI,
10386
10429
  extractMidiPlacements,
10387
10430
  extractMidiPlacementsByTrack,
package/dist/index.mjs CHANGED
@@ -2055,7 +2055,7 @@ var normalizeLyricLines = (lines) => {
2055
2055
  }
2056
2056
  return { syllables, lineBreaks };
2057
2057
  };
2058
- var LYRIC_LINE = /^@@(\d+)\s+(.*)$/;
2058
+ var LYRIC_LINE = /^@@(\d+)\s*(.*)$/;
2059
2059
  var isLyricContinuation = (seg) => !/^[@#]/.test(seg);
2060
2060
  var splitSegments = (mml) => mml.split(/[;\n\r]+/).map((s) => s.trim()).filter((s) => s.length > 0);
2061
2061
  var clamp = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
@@ -2073,38 +2073,56 @@ var parseLyrics = (mml) => {
2073
2073
  const m = segments[i].match(LYRIC_LINE);
2074
2074
  if (!m) continue;
2075
2075
  const trackId = Number.parseInt(m[1], 10);
2076
- const tokens = m[2].trim().split(/\s+/);
2077
- const modelToken = tokens.shift() ?? "";
2076
+ let rest = m[2].trim();
2078
2077
  let volume = DEFAULT_VOCAL_VOLUME;
2079
2078
  let gate = 100;
2080
2079
  let pan = 64;
2081
2080
  let octave = 0;
2082
- const colon = modelToken.indexOf(":");
2083
- const model = (colon === -1 ? modelToken : modelToken.slice(0, colon)).toLowerCase();
2084
- if (colon !== -1) {
2085
- const v = Number.parseInt(modelToken.slice(colon + 1), 10);
2086
- if (Number.isFinite(v)) volume = clamp(v, 0, MAX_VOCAL_VOLUME);
2087
- }
2088
- const metaTokens = [modelToken];
2089
- while (tokens.length > 0) {
2090
- const v = /^v(\d+)$/.exec(tokens[0]);
2091
- const q2 = /^q(\d+)$/.exec(tokens[0]);
2092
- const p = /^p(\d+)$/.exec(tokens[0]);
2093
- const o = /^o(-?\d+)$/.exec(tokens[0]);
2094
- if (v) {
2095
- volume = clamp(Number.parseInt(v[1], 10), 0, MAX_VOCAL_VOLUME);
2096
- } else if (q2) {
2097
- gate = clamp(Number.parseInt(q2[1], 10), 0, 100);
2098
- } else if (p) {
2099
- pan = clamp(Number.parseInt(p[1], 10), 0, 127);
2100
- } else if (o) {
2101
- octave = clamp(Number.parseInt(o[1], 10), -2, 2);
2102
- } else {
2103
- break;
2081
+ const modelMatch = rest.match(
2082
+ /^([a-z_]+?)(?=(?:[vqpo]-?\d)|[^a-z_]|$)(?::(\d+))?/i
2083
+ );
2084
+ let model = "";
2085
+ const metaTokens = [];
2086
+ if (modelMatch) {
2087
+ model = modelMatch[1].toLowerCase();
2088
+ if (modelMatch[2]) {
2089
+ volume = clamp(Number.parseInt(modelMatch[2], 10), 0, MAX_VOCAL_VOLUME);
2104
2090
  }
2105
- metaTokens.push(tokens.shift());
2091
+ metaTokens.push(modelMatch[0]);
2092
+ rest = rest.substring(modelMatch[0].length).trim();
2106
2093
  }
2107
- const lyricLines = [tokens.join(" ")];
2094
+ while (true) {
2095
+ const vMatch = rest.match(/^v(\d+)/i);
2096
+ if (vMatch) {
2097
+ volume = clamp(Number.parseInt(vMatch[1], 10), 0, MAX_VOCAL_VOLUME);
2098
+ metaTokens.push(vMatch[0]);
2099
+ rest = rest.substring(vMatch[0].length).trim();
2100
+ continue;
2101
+ }
2102
+ const qMatch = rest.match(/^q(\d+)/i);
2103
+ if (qMatch) {
2104
+ gate = clamp(Number.parseInt(qMatch[1], 10), 0, 100);
2105
+ metaTokens.push(qMatch[0]);
2106
+ rest = rest.substring(qMatch[0].length).trim();
2107
+ continue;
2108
+ }
2109
+ const pMatch = rest.match(/^p(\d+)/i);
2110
+ if (pMatch) {
2111
+ pan = clamp(Number.parseInt(pMatch[1], 10), 0, 127);
2112
+ metaTokens.push(pMatch[0]);
2113
+ rest = rest.substring(pMatch[0].length).trim();
2114
+ continue;
2115
+ }
2116
+ const oMatch = rest.match(/^o(-?\d+)/i);
2117
+ if (oMatch) {
2118
+ octave = clamp(Number.parseInt(oMatch[1], 10), -2, 2);
2119
+ metaTokens.push(oMatch[0]);
2120
+ rest = rest.substring(oMatch[0].length).trim();
2121
+ continue;
2122
+ }
2123
+ break;
2124
+ }
2125
+ const lyricLines = [rest];
2108
2126
  while (i + 1 < segments.length && isLyricContinuation(segments[i + 1])) {
2109
2127
  lyricLines.push(segments[++i]);
2110
2128
  }
@@ -2718,44 +2736,13 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2718
2736
  if (model.renderToCache && model.scheduleCached) {
2719
2737
  const renderToCache = model.renderToCache;
2720
2738
  const scheduleCached = model.scheduleCached;
2721
- const threshold = opts?.fallbackThresholdSec ?? 0.15;
2722
2739
  void (async () => {
2723
- let isTimeout = false;
2724
- let timeoutId;
2725
- const renderPromise = renderToCache(
2740
+ const key = await renderToCache(
2726
2741
  note.syllable,
2727
2742
  prevVowel,
2728
2743
  note.pitch,
2729
2744
  note.durationSec * 1e3
2730
- ).then((key2) => {
2731
- if (timeoutId) clearTimeout(timeoutId);
2732
- return { key: key2, type: "success" };
2733
- });
2734
- const timeoutLimitTime = t0 - threshold;
2735
- const timeoutDelayMs = (timeoutLimitTime - ctx.currentTime) * 1e3;
2736
- const timeoutPromise = new Promise(
2737
- (resolve) => {
2738
- if (timeoutDelayMs <= 0) {
2739
- isTimeout = true;
2740
- resolve({ key: null, type: "timeout" });
2741
- } else {
2742
- timeoutId = setTimeout(() => {
2743
- isTimeout = true;
2744
- resolve({ key: null, type: "timeout" });
2745
- }, timeoutDelayMs);
2746
- }
2747
- }
2748
2745
  );
2749
- const result = await Promise.race([renderPromise, timeoutPromise]);
2750
- if (session !== streamSession) return;
2751
- if (result.type === "timeout") {
2752
- console.warn(
2753
- `[dtm] Synthesizer expected late skip: ${note.syllable.kana} at ${note.startSec}s. Fallback to instrument.`
2754
- );
2755
- opts?.onFallbackPlayNote?.(track, note, t0);
2756
- return;
2757
- }
2758
- const key = result.key;
2759
2746
  if (session !== streamSession) return;
2760
2747
  if (key) {
2761
2748
  const delay = ctx.currentTime - t0;
@@ -2763,10 +2750,9 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2763
2750
  scheduleCached(key, t0, peak, track.pan);
2764
2751
  } else {
2765
2752
  console.warn(
2766
- `[dtm] Synthesizer late skip (safety): ${note.syllable.kana} at ${note.startSec}s (delayed by ${delay.toFixed(3)}s)`
2753
+ `[dtm] Synthesizer late skip: ${note.syllable.kana} at ${note.startSec}s (delayed by ${delay.toFixed(3)}s)`
2767
2754
  );
2768
2755
  opts?.onLateSkip?.(note, delay);
2769
- opts?.onFallbackPlayNote?.(track, note, t0);
2770
2756
  }
2771
2757
  }
2772
2758
  })();
@@ -4200,7 +4186,7 @@ var parseMmlMeta = (mml) => {
4200
4186
  return meta;
4201
4187
  };
4202
4188
  var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "");
4203
- var formatMmlMeta = (meta) => {
4189
+ var formatMmlMeta = (meta, space = "") => {
4204
4190
  const parts = [];
4205
4191
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
4206
4192
  if (meta.drum) parts.push(`#drum=${meta.drum}`);
@@ -4208,7 +4194,7 @@ var formatMmlMeta = (meta) => {
4208
4194
  if (meta.drumVolume !== void 0)
4209
4195
  parts.push(`#drumvolume=${meta.drumVolume}`);
4210
4196
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
4211
- return parts.join(" ");
4197
+ return parts.join(space);
4212
4198
  };
4213
4199
  var parseMML = (mml, options = {}) => {
4214
4200
  const stepsPerBar = options.stepsPerBar ?? DEFAULT_STEPS_PER_BAR;
@@ -6160,6 +6146,9 @@ var customEncode = (str) => {
6160
6146
  const bytes = [];
6161
6147
  for (let i = 0; i < str.length; i++) {
6162
6148
  const code = str.charCodeAt(i);
6149
+ if (code === 32) {
6150
+ continue;
6151
+ }
6163
6152
  if (code <= 127) {
6164
6153
  bytes.push(code);
6165
6154
  } else if (code === PROLONGED_MARK) {
@@ -6191,6 +6180,82 @@ var encodeMml = async (mml) => {
6191
6180
  }
6192
6181
  return `u.${encodeURIComponent(mml)}`;
6193
6182
  };
6183
+ var fromBase64Url = (s) => {
6184
+ let normalized = s.replace(/-/g, "+").replace(/_/g, "/");
6185
+ while (normalized.length % 4 !== 0) {
6186
+ normalized += "=";
6187
+ }
6188
+ const bin = atob(normalized);
6189
+ const bytes = new Uint8Array(bin.length);
6190
+ for (let i = 0; i < bin.length; i++) {
6191
+ bytes[i] = bin.charCodeAt(i);
6192
+ }
6193
+ return bytes;
6194
+ };
6195
+ var customDecode = (bytes) => {
6196
+ let str = "";
6197
+ let i = 0;
6198
+ while (i < bytes.length) {
6199
+ const byte = bytes[i];
6200
+ if (byte <= 127) {
6201
+ str += String.fromCharCode(byte);
6202
+ i++;
6203
+ } else if (byte === VALUE_PROLONGED) {
6204
+ str += String.fromCharCode(PROLONGED_MARK);
6205
+ i++;
6206
+ } else if (byte >= 128 && byte <= 222) {
6207
+ str += String.fromCharCode(HIRAGANA_START + (byte - 128));
6208
+ i++;
6209
+ } else if (byte === SHIFT_KATAKANA) {
6210
+ if (i + 1 < bytes.length) {
6211
+ const nextByte = bytes[i + 1];
6212
+ if (nextByte >= 128 && nextByte <= 222) {
6213
+ str += String.fromCharCode(HIRAGANA_START + 96 + (nextByte - 128));
6214
+ }
6215
+ i += 2;
6216
+ } else {
6217
+ i++;
6218
+ }
6219
+ } else {
6220
+ i++;
6221
+ }
6222
+ }
6223
+ return str;
6224
+ };
6225
+ var gunzipCustom = async (bytes) => {
6226
+ const ds = new DecompressionStream("gzip");
6227
+ const writer = ds.writable.getWriter();
6228
+ writer.write(bytes);
6229
+ writer.close();
6230
+ const buf = await new Response(ds.readable).arrayBuffer();
6231
+ return customDecode(new Uint8Array(buf));
6232
+ };
6233
+ var gunzip = async (bytes) => {
6234
+ const ds = new DecompressionStream("gzip");
6235
+ const writer = ds.writable.getWriter();
6236
+ writer.write(bytes);
6237
+ writer.close();
6238
+ const buf = await new Response(ds.readable).arrayBuffer();
6239
+ return new TextDecoder().decode(buf);
6240
+ };
6241
+ var decodeMml = async (payload) => {
6242
+ if (!payload) return "";
6243
+ try {
6244
+ if (payload.startsWith("z.")) {
6245
+ return await gunzipCustom(fromBase64Url(payload.slice(2)));
6246
+ }
6247
+ if (payload.startsWith("g.")) {
6248
+ return await gunzip(fromBase64Url(payload.slice(2)));
6249
+ }
6250
+ if (payload.startsWith("u.")) {
6251
+ return decodeURIComponent(payload.slice(2));
6252
+ }
6253
+ return decodeURIComponent(payload);
6254
+ } catch (e) {
6255
+ console.error("[dtm] failed to decode MML payload", e);
6256
+ return "";
6257
+ }
6258
+ };
6194
6259
  var mountMmlPlayer = (target, mml, options = {}) => {
6195
6260
  injectStyles(target.ownerDocument ?? document);
6196
6261
  const {
@@ -7054,31 +7119,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
7054
7119
  if (streaming && !skipSinging) {
7055
7120
  ensureVoices().startStream(tracks, seq.getStartTime(), {
7056
7121
  isAudible: (t) => !mutedTracks.has(Number(t.id)),
7057
- onFallbackPlayNote: (track, note, t0) => {
7122
+ onLateSkip: () => {
7058
7123
  showPlayerMessage(
7059
7124
  "\u97F3\u58F0\u5408\u6210\u304C\u9593\u306B\u5408\u308F\u306A\u3044\u305F\u3081\u3001\u4E00\u90E8\u306E\u767A\u97F3\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F"
7060
7125
  );
7061
- const trackIdx = Number(track.id);
7062
- if (mutedTracks.has(trackIdx)) return;
7063
- const lt = lyricTracks.get(trackIdx);
7064
- if (!lt) return;
7065
- const relativeWhen = t0 - ensureCtx().currentTime;
7066
- if (relativeWhen > 0) {
7067
- const vol = (lt.volume ?? DEFAULT_VOCAL_VOLUME) / 100 * (trackVolume / 100);
7068
- const e = {
7069
- trackId: track.id ?? "",
7070
- pitch: note.pitch,
7071
- velocity: 100,
7072
- volume: vol,
7073
- when: relativeWhen,
7074
- duration: note.durationSec,
7075
- pan: track.pan
7076
- };
7077
- options.onPlayNote?.(e);
7078
- if (useSynth) ensureSynth().playNote(e);
7079
- }
7080
- },
7081
- onLateSkip: () => {
7082
7126
  }
7083
7127
  });
7084
7128
  }
@@ -8088,27 +8132,7 @@ var mountDAW = (target, options = {}) => {
8088
8132
  sequencer.start(fromStep);
8089
8133
  if (streaming && voices) {
8090
8134
  voices.startStream(streamTracks, sequencer.getStartTime(), {
8091
- isAudible: (t) => !isSolo || t.id === activeTrackId,
8092
- onFallbackPlayNote: (track, note, t0) => {
8093
- if (isSolo && track.id !== activeTrackId) return;
8094
- const relativeWhen = t0 - getAudioTime();
8095
- if (relativeWhen > 0) {
8096
- const trackState = trackStates.find(
8097
- (t) => t.config.id === track.id
8098
- );
8099
- const trackVol = trackState ? trackState.volume : 80;
8100
- const volume = trackVol / 100 * (masterVolume / 100);
8101
- options.onPlayNote?.({
8102
- trackId: track.id ?? "",
8103
- pitch: note.pitch,
8104
- velocity: 100,
8105
- volume,
8106
- when: relativeWhen,
8107
- duration: note.durationSec,
8108
- pan: track.pan
8109
- });
8110
- }
8111
- }
8135
+ isAudible: (t) => !isSolo || t.id === activeTrackId
8112
8136
  });
8113
8137
  }
8114
8138
  updateTransport();
@@ -8399,13 +8423,26 @@ var mountDAW = (target, options = {}) => {
8399
8423
  const barLimitBars = Number(refs.barLimitSelect.value);
8400
8424
  const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
8401
8425
  const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
8402
- const metaLine = formatMmlMeta({
8403
- instrument: currentInstrument || void 0,
8404
- drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8405
- volume: masterVolume,
8406
- drumVolume,
8407
- mode
8408
- });
8426
+ const metaLineFull = formatMmlMeta(
8427
+ {
8428
+ instrument: currentInstrument || void 0,
8429
+ drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8430
+ volume: masterVolume,
8431
+ drumVolume,
8432
+ mode
8433
+ },
8434
+ " "
8435
+ );
8436
+ const metaLineMini = formatMmlMeta(
8437
+ {
8438
+ instrument: currentInstrument || void 0,
8439
+ drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8440
+ volume: masterVolume,
8441
+ drumVolume,
8442
+ mode
8443
+ },
8444
+ ""
8445
+ );
8409
8446
  if (refs.decomposeChordToggle.checked) {
8410
8447
  const ignoreHeavy = refs.ignoreChordHeavyToggle.checked;
8411
8448
  const targetStates = ignoreHeavy ? trackStates.filter((t) => !isChordHeavyTrack(t.core.getNotes())) : trackStates;
@@ -8421,8 +8458,8 @@ var mountDAW = (target, options = {}) => {
8421
8458
  const decomposedMini = monoTracks.map(
8422
8459
  (notes, i) => `@${i}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
8423
8460
  );
8424
- const full2 = [metaLine, ...decomposedFull, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
8425
- const minified2 = [metaLine, ...decomposedMini, MML_END_MARKER].filter((s) => s.length > 0).join(";");
8461
+ const full2 = [metaLineFull, ...decomposedFull, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
8462
+ const minified2 = [metaLineMini, ...decomposedMini, MML_END_MARKER].filter((s) => s.length > 0).join(";");
8426
8463
  return {
8427
8464
  full: full2,
8428
8465
  minified: minified2,
@@ -8462,9 +8499,9 @@ var mountDAW = (target, options = {}) => {
8462
8499
  const head = params ? `${x.model} ${params}` : x.model;
8463
8500
  return `@@${x.i} ${head} ${x.text}`;
8464
8501
  });
8465
- const full = [metaLine, ...trackLines, ...lyricLines, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
8502
+ const full = [metaLineFull, ...trackLines, ...lyricLines, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
8466
8503
  const minified = [
8467
- metaLine,
8504
+ metaLineMini,
8468
8505
  ...trackLinesMini,
8469
8506
  ...lyricLines,
8470
8507
  MML_END_MARKER
@@ -10248,6 +10285,7 @@ export {
10248
10285
  PREWARM_NOTES,
10249
10286
  TRACKS_ADVANCED,
10250
10287
  TRACKS_SIMPLE,
10288
+ VOICE_IMAGES,
10251
10289
  VOICE_IMAGE_KEY,
10252
10290
  analyzeMidiTracks,
10253
10291
  applyHarmonicFilter,
@@ -10265,6 +10303,7 @@ export {
10265
10303
  createSingingVoices,
10266
10304
  createSynth,
10267
10305
  createVoiceRegistry,
10306
+ decodeMml,
10268
10307
  decomposeToMonophonic,
10269
10308
  drawGrid,
10270
10309
  drawHeader,
@@ -10272,6 +10311,7 @@ export {
10272
10311
  drawNotes,
10273
10312
  drawSelectedNotes,
10274
10313
  drawSelectionRect,
10314
+ encodeMml,
10275
10315
  exportMIDI,
10276
10316
  extractMidiPlacements,
10277
10317
  extractMidiPlacementsByTrack,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "onjmin",
3
3
  "license": "MIT",
4
4
  "name": "@onjmin/dtm",
5
- "version": "0.1.29",
5
+ "version": "0.1.31",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {