@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 +6 -13
- package/dist/index.d.ts +6 -13
- package/dist/index.js +160 -117
- package/dist/index.mjs +157 -117
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
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(
|
|
2202
|
+
metaTokens.push(modelMatch[0]);
|
|
2203
|
+
rest = rest.substring(modelMatch[0].length).trim();
|
|
2214
2204
|
}
|
|
2215
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
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 = [
|
|
8533
|
-
const minified2 = [
|
|
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 = [
|
|
8613
|
+
const full = [metaLineFull, ...trackLines, ...lyricLines, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
|
|
8574
8614
|
const minified = [
|
|
8575
|
-
|
|
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
|
-
|
|
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
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
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(
|
|
2091
|
+
metaTokens.push(modelMatch[0]);
|
|
2092
|
+
rest = rest.substring(modelMatch[0].length).trim();
|
|
2106
2093
|
}
|
|
2107
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
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 = [
|
|
8425
|
-
const minified2 = [
|
|
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 = [
|
|
8502
|
+
const full = [metaLineFull, ...trackLines, ...lyricLines, MML_END_MARKER].filter((s) => s.length > 0).join(";\n");
|
|
8466
8503
|
const minified = [
|
|
8467
|
-
|
|
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