@onjmin/dtm 0.1.0 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +1 -1
- package/dist/index.d.mts +511 -3
- package/dist/index.d.ts +511 -3
- package/dist/index.js +2572 -141
- package/dist/index.mjs +2541 -140
- package/dist/voice-worker.js +382 -0
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -21,23 +21,43 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
DAW_CSS: () => DAW_CSS,
|
|
24
|
+
DEFAULT_BPM: () => DEFAULT_BPM,
|
|
25
|
+
DEFAULT_GATE: () => DEFAULT_GATE,
|
|
26
|
+
DEFAULT_PAN: () => DEFAULT_PAN,
|
|
27
|
+
DEFAULT_PLAYBACK_VELOCITY: () => DEFAULT_PLAYBACK_VELOCITY,
|
|
28
|
+
DEFAULT_STEPS_PER_BAR: () => DEFAULT_STEPS_PER_BAR,
|
|
29
|
+
DEFAULT_VELOCITY: () => DEFAULT_VELOCITY,
|
|
30
|
+
DEFAULT_VOCAL_VOLUME: () => DEFAULT_VOCAL_VOLUME,
|
|
24
31
|
DRUM_FONT: () => DRUM_FONT,
|
|
25
32
|
DRUM_KEYS: () => DRUM_KEYS,
|
|
26
33
|
DRUM_PATTERNS: () => DRUM_PATTERNS,
|
|
27
34
|
INSTRUMENT_PRESETS: () => INSTRUMENT_PRESETS,
|
|
35
|
+
KOE_BASE_URL: () => KOE_BASE_URL,
|
|
36
|
+
KOE_VOICEBANKS: () => KOE_VOICEBANKS,
|
|
37
|
+
KOE_VOICEBANK_LABELS: () => KOE_VOICEBANK_LABELS,
|
|
38
|
+
KOE_VOICEBANK_TERMS: () => KOE_VOICEBANK_TERMS,
|
|
28
39
|
LinkedList: () => LinkedList,
|
|
40
|
+
MAX_VOCAL_VOLUME: () => MAX_VOCAL_VOLUME,
|
|
29
41
|
MMLCore: () => MMLCore,
|
|
30
42
|
PITCH_MAP: () => PITCH_MAP,
|
|
43
|
+
PREWARM_NOTES: () => PREWARM_NOTES,
|
|
31
44
|
TRACKS_ADVANCED: () => TRACKS_ADVANCED,
|
|
32
45
|
TRACKS_SIMPLE: () => TRACKS_SIMPLE,
|
|
46
|
+
VOICE_IMAGE_KEY: () => VOICE_IMAGE_KEY,
|
|
33
47
|
analyzeMidiTracks: () => analyzeMidiTracks,
|
|
34
48
|
applyHarmonicFilter: () => applyHarmonicFilter,
|
|
35
49
|
applyMonophonic: () => applyMonophonic,
|
|
36
50
|
buildChordPlacements: () => buildChordPlacements,
|
|
37
51
|
buildNameToKeyMapping: () => buildNameToKeyMapping,
|
|
52
|
+
collectPitchTokens: () => collectPitchTokens,
|
|
38
53
|
createAudioContext: () => createAudioContext,
|
|
54
|
+
createKlattVoice: () => createKlattVoice,
|
|
55
|
+
createKoeVoice: () => createKoeVoice,
|
|
56
|
+
createLyricsConductor: () => createLyricsConductor,
|
|
39
57
|
createPianoRoll: () => createPianoRoll,
|
|
40
58
|
createSequencer: () => createSequencer,
|
|
59
|
+
createSingingVoices: () => createSingingVoices,
|
|
60
|
+
createVoiceRegistry: () => createVoiceRegistry,
|
|
41
61
|
decomposeToMonophonic: () => decomposeToMonophonic,
|
|
42
62
|
drawGrid: () => drawGrid,
|
|
43
63
|
drawHeader: () => drawHeader,
|
|
@@ -49,6 +69,7 @@ __export(index_exports, {
|
|
|
49
69
|
extractMidiPlacements: () => extractMidiPlacements,
|
|
50
70
|
extractMidiPlacementsByTrack: () => extractMidiPlacementsByTrack,
|
|
51
71
|
fetchSoundFontList: () => fetchSoundFontList,
|
|
72
|
+
formatMmlMeta: () => formatMmlMeta,
|
|
52
73
|
generateRandomPattern: () => generateRandomPattern,
|
|
53
74
|
getDrawOffset: () => getDrawOffset,
|
|
54
75
|
getGridCanvas: () => getGridCanvas,
|
|
@@ -62,12 +83,21 @@ __export(index_exports, {
|
|
|
62
83
|
init: () => init,
|
|
63
84
|
injectStyles: () => injectStyles,
|
|
64
85
|
isChordHeavyTrack: () => isChordHeavyTrack,
|
|
86
|
+
koeUrl: () => koeUrl,
|
|
65
87
|
mountDAW: () => mountDAW,
|
|
88
|
+
mountMmlPlayer: () => mountMmlPlayer,
|
|
89
|
+
normalizeLyrics: () => normalizeLyrics,
|
|
66
90
|
onClick: () => onClick,
|
|
91
|
+
panToStereo: () => panToStereo,
|
|
92
|
+
parseLyrics: () => parseLyrics,
|
|
67
93
|
parseMML: () => parseMML,
|
|
94
|
+
parseMmlMeta: () => parseMmlMeta,
|
|
68
95
|
setDrawOffset: () => setDrawOffset,
|
|
69
96
|
setupRecorder: () => setupRecorder,
|
|
70
|
-
shiftNotes: () => shiftNotes
|
|
97
|
+
shiftNotes: () => shiftNotes,
|
|
98
|
+
stripLyrics: () => stripLyrics,
|
|
99
|
+
stripMmlMeta: () => stripMmlMeta,
|
|
100
|
+
vocalVolumeToGain: () => vocalVolumeToGain
|
|
71
101
|
});
|
|
72
102
|
module.exports = __toCommonJS(index_exports);
|
|
73
103
|
|
|
@@ -263,7 +293,7 @@ var buildChordPlacements = (options) => {
|
|
|
263
293
|
if (notes.length === 0) return;
|
|
264
294
|
const startStep = barIndex * chordLength;
|
|
265
295
|
notes.forEach((noteOffset, i) => {
|
|
266
|
-
const stepOffset = i *
|
|
296
|
+
const stepOffset = i * 3;
|
|
267
297
|
placements.push({
|
|
268
298
|
startStep: startStep + stepOffset,
|
|
269
299
|
pitch: C3 + noteOffset + offset,
|
|
@@ -358,7 +388,10 @@ var buildUI = (target, options) => {
|
|
|
358
388
|
<div class="dtm-tracks" data-dtm="track-tabs"></div>
|
|
359
389
|
|
|
360
390
|
<div class="dtm-roll-wrap">
|
|
361
|
-
<div class="dtm-roll" data-dtm="roll"
|
|
391
|
+
<div class="dtm-roll" data-dtm="roll">
|
|
392
|
+
<div data-dtm="wrapper" style="position:absolute;inset:0;"></div>
|
|
393
|
+
<div class="dtm-overlay" data-dtm="overlay" hidden><div class="dtm-spinner"></div></div>
|
|
394
|
+
</div>
|
|
362
395
|
<div class="dtm-vscroll" data-dtm="vscroll"><div class="dtm-vscroll-thumb" data-dtm="vscroll-thumb"></div></div>
|
|
363
396
|
</div>
|
|
364
397
|
<div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
|
|
@@ -491,8 +524,6 @@ var buildUI = (target, options) => {
|
|
|
491
524
|
</div>
|
|
492
525
|
</div>
|
|
493
526
|
</details>
|
|
494
|
-
|
|
495
|
-
<div class="dtm-overlay" data-dtm="overlay" hidden><div class="dtm-spinner"></div></div>
|
|
496
527
|
</div>`;
|
|
497
528
|
const root = q(target, '[data-dtm="root"]');
|
|
498
529
|
const sel = (name) => q(root, `[data-dtm="${name}"]`);
|
|
@@ -681,6 +712,1155 @@ var DRUM_PATTERNS = {
|
|
|
681
712
|
]
|
|
682
713
|
};
|
|
683
714
|
|
|
715
|
+
// node_modules/.pnpm/@onjmin+koe@1.0.3/node_modules/@onjmin/koe/dist/index.js
|
|
716
|
+
var MAGIC = 1263486208;
|
|
717
|
+
function parseKoeHeader(headerBytes) {
|
|
718
|
+
const view = new DataView(headerBytes);
|
|
719
|
+
if (view.byteLength < 8 || view.getUint32(0, false) !== MAGIC) {
|
|
720
|
+
throw new Error("Not a .koe file (bad magic)");
|
|
721
|
+
}
|
|
722
|
+
return { jsonLength: view.getUint32(4, true) };
|
|
723
|
+
}
|
|
724
|
+
var pcmBase = (jsonLength) => 8 + jsonLength;
|
|
725
|
+
var BlobVoiceSource = class {
|
|
726
|
+
constructor(blob, base) {
|
|
727
|
+
this.blob = blob;
|
|
728
|
+
this.base = base;
|
|
729
|
+
}
|
|
730
|
+
blob;
|
|
731
|
+
base;
|
|
732
|
+
readBytes(offset, length) {
|
|
733
|
+
const start = this.base + offset;
|
|
734
|
+
return this.blob.slice(start, start + length).arrayBuffer();
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
var RangeVoiceSource = class {
|
|
738
|
+
constructor(url, base) {
|
|
739
|
+
this.url = url;
|
|
740
|
+
this.base = base;
|
|
741
|
+
}
|
|
742
|
+
url;
|
|
743
|
+
base;
|
|
744
|
+
async readBytes(offset, length) {
|
|
745
|
+
const start = this.base + offset;
|
|
746
|
+
const res = await fetch(this.url, {
|
|
747
|
+
headers: { Range: `bytes=${start}-${start + length - 1}` }
|
|
748
|
+
});
|
|
749
|
+
if (!res.ok && res.status !== 206) {
|
|
750
|
+
throw new Error(`.koe range request failed: ${res.status}`);
|
|
751
|
+
}
|
|
752
|
+
return res.arrayBuffer();
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
async function rangeFetch(url, start, length) {
|
|
756
|
+
const res = await fetch(url, {
|
|
757
|
+
headers: { Range: `bytes=${start}-${start + length - 1}` }
|
|
758
|
+
});
|
|
759
|
+
if (!res.ok && res.status !== 206)
|
|
760
|
+
throw new Error(`.koe fetch failed: ${res.status}`);
|
|
761
|
+
return res.arrayBuffer();
|
|
762
|
+
}
|
|
763
|
+
var VoiceBank = class _VoiceBank {
|
|
764
|
+
constructor(manifest, source) {
|
|
765
|
+
this.manifest = manifest;
|
|
766
|
+
this.source = source;
|
|
767
|
+
}
|
|
768
|
+
manifest;
|
|
769
|
+
source;
|
|
770
|
+
/**
|
|
771
|
+
* Parse a .koe archive header + manifest and bind a lazy PCM source.
|
|
772
|
+
* @param koe a Blob/File of the .koe archive, or a URL (served with Range support)
|
|
773
|
+
*/
|
|
774
|
+
static async load(koe) {
|
|
775
|
+
if (typeof koe === "string") {
|
|
776
|
+
const header2 = await rangeFetch(koe, 0, 8);
|
|
777
|
+
const { jsonLength: jsonLength2 } = parseKoeHeader(header2);
|
|
778
|
+
const json2 = await rangeFetch(koe, 8, jsonLength2);
|
|
779
|
+
const manifest2 = JSON.parse(new TextDecoder().decode(json2));
|
|
780
|
+
return new _VoiceBank(
|
|
781
|
+
manifest2,
|
|
782
|
+
new RangeVoiceSource(koe, pcmBase(jsonLength2))
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
const header = await koe.slice(0, 8).arrayBuffer();
|
|
786
|
+
const { jsonLength } = parseKoeHeader(header);
|
|
787
|
+
const json = await koe.slice(8, 8 + jsonLength).arrayBuffer();
|
|
788
|
+
const manifest = JSON.parse(new TextDecoder().decode(json));
|
|
789
|
+
return new _VoiceBank(
|
|
790
|
+
manifest,
|
|
791
|
+
new BlobVoiceSource(koe, pcmBase(jsonLength))
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
/** True if the bank contains a phoneme under this alias. */
|
|
795
|
+
has(phoneme) {
|
|
796
|
+
return this.manifest.phonemes[phoneme] !== void 0;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Raw Int16 PCM bytes (48 kHz / mono) for a phoneme, or null if unknown.
|
|
800
|
+
* The returned ArrayBuffer is freshly allocated and safe to transfer to a
|
|
801
|
+
* worker / AudioWorklet.
|
|
802
|
+
*/
|
|
803
|
+
async readPcmBytes(phoneme) {
|
|
804
|
+
const entry = this.manifest.phonemes[phoneme];
|
|
805
|
+
if (!entry) return null;
|
|
806
|
+
return this.source.readBytes(entry.offset, entry.length * 2);
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* A phoneme's PCM as a Float64Array normalised to [-1, 1], or null if unknown.
|
|
810
|
+
* Intended for external analysis / resynthesis such as the WORLD vocoder.
|
|
811
|
+
*/
|
|
812
|
+
async getPcm(phoneme) {
|
|
813
|
+
const buf = await this.readPcmBytes(phoneme);
|
|
814
|
+
if (!buf) return null;
|
|
815
|
+
const int16 = new Int16Array(buf);
|
|
816
|
+
const f64 = new Float64Array(int16.length);
|
|
817
|
+
for (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;
|
|
818
|
+
return f64;
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
var WORLDLINE_SAMPLE_RATE = 48e3;
|
|
822
|
+
var MIN_WORLDLINE_SAMPLES = 4096;
|
|
823
|
+
var SYNTH_REQ_SIZE = 120;
|
|
824
|
+
var WL_FRAME_MS = 10;
|
|
825
|
+
var samplesToMs = (samples) => samples / WORLDLINE_SAMPLE_RATE * 1e3;
|
|
826
|
+
function leadInFromEntry(entry) {
|
|
827
|
+
return {
|
|
828
|
+
preMs: samplesToMs(entry.pre || 0),
|
|
829
|
+
consonantMs: samplesToMs(entry.consonant || 0)
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
var moduleCache = /* @__PURE__ */ new Map();
|
|
833
|
+
function injectScript(src) {
|
|
834
|
+
return new Promise((resolve, reject) => {
|
|
835
|
+
const existing = document.querySelector(
|
|
836
|
+
`script[data-koe-worldline="${src}"]`
|
|
837
|
+
);
|
|
838
|
+
if (existing) {
|
|
839
|
+
resolve();
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
const s = document.createElement("script");
|
|
843
|
+
s.src = src;
|
|
844
|
+
s.dataset.koeWorldline = src;
|
|
845
|
+
s.onload = () => resolve();
|
|
846
|
+
s.onerror = () => reject(new Error(`worldline: failed to load ${src}`));
|
|
847
|
+
document.head.appendChild(s);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
function loadWasm(scriptUrl) {
|
|
851
|
+
const cached = moduleCache.get(scriptUrl);
|
|
852
|
+
if (cached) return cached;
|
|
853
|
+
const baseUrl = scriptUrl.slice(0, scriptUrl.lastIndexOf("/") + 1);
|
|
854
|
+
const instantiate = () => {
|
|
855
|
+
const factory = globalThis.WorldlineModule;
|
|
856
|
+
if (!factory)
|
|
857
|
+
throw new Error(
|
|
858
|
+
"worldline: WorldlineModule global was not defined by the script"
|
|
859
|
+
);
|
|
860
|
+
return factory({ locateFile: (f) => baseUrl + f });
|
|
861
|
+
};
|
|
862
|
+
let promise;
|
|
863
|
+
if (typeof document !== "undefined") {
|
|
864
|
+
promise = injectScript(scriptUrl).then(instantiate);
|
|
865
|
+
} else if (typeof globalThis.importScripts === "function") {
|
|
866
|
+
promise = Promise.resolve().then(() => {
|
|
867
|
+
globalThis.importScripts(scriptUrl);
|
|
868
|
+
return instantiate();
|
|
869
|
+
});
|
|
870
|
+
} else {
|
|
871
|
+
return Promise.reject(
|
|
872
|
+
new Error(
|
|
873
|
+
"Worldline.load requires a DOM or a classic Web Worker (importScripts) to load worldline.js"
|
|
874
|
+
)
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
moduleCache.set(scriptUrl, promise);
|
|
878
|
+
return promise;
|
|
879
|
+
}
|
|
880
|
+
var Worldline = class _Worldline {
|
|
881
|
+
constructor(wasm) {
|
|
882
|
+
this.wasm = wasm;
|
|
883
|
+
}
|
|
884
|
+
wasm;
|
|
885
|
+
sampleRate = WORLDLINE_SAMPLE_RATE;
|
|
886
|
+
/**
|
|
887
|
+
* Load + instantiate the worldline WASM module (deduped per scriptUrl).
|
|
888
|
+
*
|
|
889
|
+
* Works on the main thread (loads via `<script>`) and inside a classic Web
|
|
890
|
+
* Worker (loads via `importScripts`), so the heavy synthesis can run
|
|
891
|
+
* off-thread. The matching `worldline.wasm` is fetched next to scriptUrl.
|
|
892
|
+
*/
|
|
893
|
+
static async load(options) {
|
|
894
|
+
return new _Worldline(await loadWasm(options.scriptUrl));
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Render one note to Float32 PCM at 48 kHz.
|
|
898
|
+
*
|
|
899
|
+
* The output buffer is laid out as [lead-in/consonant ≈ preMs][vowel ≈
|
|
900
|
+
* durationMs], rendered from sample offset 0 (no leading silence). The vowel
|
|
901
|
+
* onset (the "beat") sits at ≈ preMs into the buffer, so a sequencer should
|
|
902
|
+
* place the buffer at `beatTime − preMs` and may trim/crossfade the lead-in.
|
|
903
|
+
*
|
|
904
|
+
* No internal crossfade is applied — apply fades externally.
|
|
905
|
+
*
|
|
906
|
+
* @returns Float32 PCM, or null when `pcm` is shorter than
|
|
907
|
+
* {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
|
|
908
|
+
*/
|
|
909
|
+
renderNote(params) {
|
|
910
|
+
const { pcm, pitch, durationMs, preMs, consonantMs, tempo = 120 } = params;
|
|
911
|
+
if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;
|
|
912
|
+
const WL = this.wasm;
|
|
913
|
+
const FS = WORLDLINE_SAMPLE_RATE;
|
|
914
|
+
const midiNote = Math.round(69 + 12 * Math.log2(pitch / 440));
|
|
915
|
+
const posMs = 0;
|
|
916
|
+
const reqLen = preMs + durationMs;
|
|
917
|
+
const cutMs = WL_FRAME_MS * 2;
|
|
918
|
+
const ps = WL._PhraseSynthNew();
|
|
919
|
+
if (!ps) return null;
|
|
920
|
+
const reqPtr = WL._malloc(SYNTH_REQ_SIZE);
|
|
921
|
+
if (!reqPtr) {
|
|
922
|
+
WL._PhraseSynthDelete(ps);
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
const samplePtr = WL._malloc(pcm.length * 8);
|
|
926
|
+
if (!samplePtr) {
|
|
927
|
+
WL._free(reqPtr);
|
|
928
|
+
WL._PhraseSynthDelete(ps);
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
WL.HEAPF64.set(pcm, samplePtr >> 3);
|
|
932
|
+
const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
|
|
933
|
+
sv(0, FS, "i32");
|
|
934
|
+
sv(4, pcm.length, "i32");
|
|
935
|
+
sv(8, samplePtr, "*");
|
|
936
|
+
sv(12, 0, "i32");
|
|
937
|
+
sv(16, 0, "*");
|
|
938
|
+
sv(20, midiNote, "i32");
|
|
939
|
+
sv(24, 100, "double");
|
|
940
|
+
sv(32, 0, "double");
|
|
941
|
+
sv(40, reqLen, "double");
|
|
942
|
+
sv(48, consonantMs, "double");
|
|
943
|
+
sv(56, cutMs, "double");
|
|
944
|
+
sv(64, 100, "double");
|
|
945
|
+
sv(72, 0, "double");
|
|
946
|
+
sv(80, tempo, "double");
|
|
947
|
+
sv(88, 0, "i32");
|
|
948
|
+
sv(92, 0, "*");
|
|
949
|
+
sv(96, 0, "i32");
|
|
950
|
+
sv(100, 0, "i32");
|
|
951
|
+
sv(104, 100, "i32");
|
|
952
|
+
sv(108, 0, "i32");
|
|
953
|
+
sv(112, 0, "i32");
|
|
954
|
+
sv(116, 100, "i32");
|
|
955
|
+
WL._PhraseSynthAddRequest(ps, reqPtr, posMs, 0, reqLen, 0, 0, 0);
|
|
956
|
+
WL._free(samplePtr);
|
|
957
|
+
WL._free(reqPtr);
|
|
958
|
+
const totalMs = posMs + reqLen + WL_FRAME_MS * 2;
|
|
959
|
+
const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
|
|
960
|
+
const f0Arr = new Float64Array(nFrames).fill(pitch);
|
|
961
|
+
const gArr = new Float64Array(nFrames).fill(0.5);
|
|
962
|
+
const tArr = new Float64Array(nFrames).fill(0.5);
|
|
963
|
+
const bArr = new Float64Array(nFrames).fill(0.5);
|
|
964
|
+
const vArr = new Float64Array(nFrames).fill(1);
|
|
965
|
+
const f0Ptr = WL._malloc(nFrames * 8);
|
|
966
|
+
const gPtr = WL._malloc(nFrames * 8);
|
|
967
|
+
const tPtr = WL._malloc(nFrames * 8);
|
|
968
|
+
const bPtr = WL._malloc(nFrames * 8);
|
|
969
|
+
const vPtr = WL._malloc(nFrames * 8);
|
|
970
|
+
if (!f0Ptr || !gPtr || !tPtr || !bPtr || !vPtr) {
|
|
971
|
+
if (f0Ptr) WL._free(f0Ptr);
|
|
972
|
+
if (gPtr) WL._free(gPtr);
|
|
973
|
+
if (tPtr) WL._free(tPtr);
|
|
974
|
+
if (bPtr) WL._free(bPtr);
|
|
975
|
+
if (vPtr) WL._free(vPtr);
|
|
976
|
+
WL._PhraseSynthDelete(ps);
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
WL.HEAPF64.set(f0Arr, f0Ptr >> 3);
|
|
980
|
+
WL.HEAPF64.set(gArr, gPtr >> 3);
|
|
981
|
+
WL.HEAPF64.set(tArr, tPtr >> 3);
|
|
982
|
+
WL.HEAPF64.set(bArr, bPtr >> 3);
|
|
983
|
+
WL.HEAPF64.set(vArr, vPtr >> 3);
|
|
984
|
+
WL._PhraseSynthSetCurves(
|
|
985
|
+
ps,
|
|
986
|
+
f0Ptr,
|
|
987
|
+
gPtr,
|
|
988
|
+
tPtr,
|
|
989
|
+
bPtr,
|
|
990
|
+
vPtr,
|
|
991
|
+
nFrames,
|
|
992
|
+
WL_FRAME_MS
|
|
993
|
+
);
|
|
994
|
+
WL._free(f0Ptr);
|
|
995
|
+
WL._free(gPtr);
|
|
996
|
+
WL._free(tPtr);
|
|
997
|
+
WL._free(bPtr);
|
|
998
|
+
WL._free(vPtr);
|
|
999
|
+
const yPtrPtr = WL._malloc(4);
|
|
1000
|
+
if (!yPtrPtr) {
|
|
1001
|
+
WL._PhraseSynthDelete(ps);
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
const outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);
|
|
1005
|
+
const yPtr = WL.getValue(yPtrPtr, "*");
|
|
1006
|
+
const audio = outLen > 0 ? new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice() : null;
|
|
1007
|
+
WL._free(yPtrPtr);
|
|
1008
|
+
WL._PhraseSynthDelete(ps);
|
|
1009
|
+
return audio;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
// src/types.ts
|
|
1014
|
+
var DEFAULT_VOCAL_VOLUME = 200;
|
|
1015
|
+
var DEFAULT_BPM = 120;
|
|
1016
|
+
var DEFAULT_GATE = 100;
|
|
1017
|
+
var DEFAULT_PAN = 64;
|
|
1018
|
+
var DEFAULT_VELOCITY = 100;
|
|
1019
|
+
var DEFAULT_PLAYBACK_VELOCITY = 127;
|
|
1020
|
+
var DEFAULT_STEPS_PER_BAR = 192;
|
|
1021
|
+
|
|
1022
|
+
// src/lyrics.ts
|
|
1023
|
+
var kanaTable = {
|
|
1024
|
+
\u3042: ["", "a"],
|
|
1025
|
+
\u3044: ["", "i"],
|
|
1026
|
+
\u3046: ["", "u"],
|
|
1027
|
+
\u3048: ["", "e"],
|
|
1028
|
+
\u304A: ["", "o"],
|
|
1029
|
+
\u304B: ["k", "a"],
|
|
1030
|
+
\u304D: ["k", "i"],
|
|
1031
|
+
\u304F: ["k", "u"],
|
|
1032
|
+
\u3051: ["k", "e"],
|
|
1033
|
+
\u3053: ["k", "o"],
|
|
1034
|
+
\u3055: ["s", "a"],
|
|
1035
|
+
\u3057: ["sh", "i"],
|
|
1036
|
+
\u3059: ["s", "u"],
|
|
1037
|
+
\u305B: ["s", "e"],
|
|
1038
|
+
\u305D: ["s", "o"],
|
|
1039
|
+
\u305F: ["t", "a"],
|
|
1040
|
+
\u3061: ["ch", "i"],
|
|
1041
|
+
\u3064: ["ts", "u"],
|
|
1042
|
+
\u3066: ["t", "e"],
|
|
1043
|
+
\u3068: ["t", "o"],
|
|
1044
|
+
\u306A: ["n", "a"],
|
|
1045
|
+
\u306B: ["n", "i"],
|
|
1046
|
+
\u306C: ["n", "u"],
|
|
1047
|
+
\u306D: ["n", "e"],
|
|
1048
|
+
\u306E: ["n", "o"],
|
|
1049
|
+
\u306F: ["h", "a"],
|
|
1050
|
+
\u3072: ["h", "i"],
|
|
1051
|
+
\u3075: ["f", "u"],
|
|
1052
|
+
\u3078: ["h", "e"],
|
|
1053
|
+
\u307B: ["h", "o"],
|
|
1054
|
+
\u307E: ["m", "a"],
|
|
1055
|
+
\u307F: ["m", "i"],
|
|
1056
|
+
\u3080: ["m", "u"],
|
|
1057
|
+
\u3081: ["m", "e"],
|
|
1058
|
+
\u3082: ["m", "o"],
|
|
1059
|
+
\u3084: ["y", "a"],
|
|
1060
|
+
\u3086: ["y", "u"],
|
|
1061
|
+
\u3088: ["y", "o"],
|
|
1062
|
+
\u3089: ["r", "a"],
|
|
1063
|
+
\u308A: ["r", "i"],
|
|
1064
|
+
\u308B: ["r", "u"],
|
|
1065
|
+
\u308C: ["r", "e"],
|
|
1066
|
+
\u308D: ["r", "o"],
|
|
1067
|
+
\u308F: ["w", "a"],
|
|
1068
|
+
\u3092: ["w", "o"],
|
|
1069
|
+
\u304C: ["g", "a"],
|
|
1070
|
+
\u304E: ["g", "i"],
|
|
1071
|
+
\u3050: ["g", "u"],
|
|
1072
|
+
\u3052: ["g", "e"],
|
|
1073
|
+
\u3054: ["g", "o"],
|
|
1074
|
+
\u3056: ["z", "a"],
|
|
1075
|
+
\u3058: ["j", "i"],
|
|
1076
|
+
\u305A: ["z", "u"],
|
|
1077
|
+
\u305C: ["z", "e"],
|
|
1078
|
+
\u305E: ["z", "o"],
|
|
1079
|
+
\u3060: ["d", "a"],
|
|
1080
|
+
\u3062: ["j", "i"],
|
|
1081
|
+
\u3065: ["z", "u"],
|
|
1082
|
+
\u3067: ["d", "e"],
|
|
1083
|
+
\u3069: ["d", "o"],
|
|
1084
|
+
\u3070: ["b", "a"],
|
|
1085
|
+
\u3073: ["b", "i"],
|
|
1086
|
+
\u3076: ["b", "u"],
|
|
1087
|
+
\u3079: ["b", "e"],
|
|
1088
|
+
\u307C: ["b", "o"],
|
|
1089
|
+
\u3071: ["p", "a"],
|
|
1090
|
+
\u3074: ["p", "i"],
|
|
1091
|
+
\u3077: ["p", "u"],
|
|
1092
|
+
\u307A: ["p", "e"],
|
|
1093
|
+
\u307D: ["p", "o"],
|
|
1094
|
+
\u3093: ["N", "N"]
|
|
1095
|
+
};
|
|
1096
|
+
var SMALL_KANA = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087\u3063";
|
|
1097
|
+
var VOWEL_KANA = {
|
|
1098
|
+
a: "\u3042",
|
|
1099
|
+
i: "\u3044",
|
|
1100
|
+
u: "\u3046",
|
|
1101
|
+
e: "\u3048",
|
|
1102
|
+
o: "\u304A"
|
|
1103
|
+
};
|
|
1104
|
+
var sanitizeText = (text) => text.normalize("NFKC").replace(/[ァ-ヶ]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 96)).replace(/[^ぁ-ゖー]/g, "");
|
|
1105
|
+
var splitSyllables = (text) => {
|
|
1106
|
+
const result = [];
|
|
1107
|
+
for (const ch of text) {
|
|
1108
|
+
if (result.length > 0 && SMALL_KANA.includes(ch)) {
|
|
1109
|
+
result[result.length - 1] += ch;
|
|
1110
|
+
} else {
|
|
1111
|
+
result.push(ch);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return result;
|
|
1115
|
+
};
|
|
1116
|
+
var kanaToVowel = (kana) => {
|
|
1117
|
+
if (/[ぁゃ]/.test(kana)) return "a";
|
|
1118
|
+
if (/[ぃ]/.test(kana)) return "i";
|
|
1119
|
+
if (/[ぅゅ]/.test(kana)) return "u";
|
|
1120
|
+
if (/[ぇ]/.test(kana)) return "e";
|
|
1121
|
+
if (/[ぉょ]/.test(kana)) return "o";
|
|
1122
|
+
if (/[あかさたなはまやらわがざだばぱ]/.test(kana)) return "a";
|
|
1123
|
+
if (/[いきしちにひみりぎじぢびぴ]/.test(kana)) return "i";
|
|
1124
|
+
if (/[うくすつぬふむゆるぐずづぶぷ]/.test(kana)) return "u";
|
|
1125
|
+
if (/[えけせてねへめれげぜでべぺ]/.test(kana)) return "e";
|
|
1126
|
+
if (/[おこそとのほもよろごぞどぼぽ]/.test(kana)) return "o";
|
|
1127
|
+
return "";
|
|
1128
|
+
};
|
|
1129
|
+
var analyzeSyllable = (syllable) => {
|
|
1130
|
+
if (syllable === "\u30FC") return { kana: syllable, consonant: "-", vowel: "-" };
|
|
1131
|
+
if (syllable === "\u3063") return { kana: syllable, consonant: "Q", vowel: "" };
|
|
1132
|
+
const head = syllable[0];
|
|
1133
|
+
const row = kanaTable[head];
|
|
1134
|
+
const consonant = row ? row[0] : "";
|
|
1135
|
+
let vowel = row ? row[1] : kanaToVowel(head);
|
|
1136
|
+
if (syllable.length === 2 && syllable[1] !== "\u3063") {
|
|
1137
|
+
const v = kanaToVowel(syllable[1]);
|
|
1138
|
+
if (v) vowel = v;
|
|
1139
|
+
}
|
|
1140
|
+
return { kana: syllable, consonant, vowel };
|
|
1141
|
+
};
|
|
1142
|
+
var resolveLongVowels = (syllables) => {
|
|
1143
|
+
const result = [];
|
|
1144
|
+
let prevVowel = "";
|
|
1145
|
+
for (const syl of syllables) {
|
|
1146
|
+
if (syl.consonant === "-") {
|
|
1147
|
+
if (!prevVowel) continue;
|
|
1148
|
+
result.push({
|
|
1149
|
+
kana: VOWEL_KANA[prevVowel] ?? syl.kana,
|
|
1150
|
+
consonant: "",
|
|
1151
|
+
vowel: prevVowel
|
|
1152
|
+
});
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
if (syl.vowel && syl.vowel !== "N") prevVowel = syl.vowel;
|
|
1156
|
+
result.push(syl);
|
|
1157
|
+
}
|
|
1158
|
+
return result;
|
|
1159
|
+
};
|
|
1160
|
+
var normalizeLyrics = (text) => resolveLongVowels(splitSyllables(sanitizeText(text)).map(analyzeSyllable));
|
|
1161
|
+
var normalizeLyricLines = (lines) => {
|
|
1162
|
+
const syllables = [];
|
|
1163
|
+
const lineBreaks = [];
|
|
1164
|
+
for (const line of lines) {
|
|
1165
|
+
const part = normalizeLyrics(line);
|
|
1166
|
+
if (part.length === 0) continue;
|
|
1167
|
+
if (syllables.length > 0) lineBreaks.push(syllables.length);
|
|
1168
|
+
syllables.push(...part);
|
|
1169
|
+
}
|
|
1170
|
+
return { syllables, lineBreaks };
|
|
1171
|
+
};
|
|
1172
|
+
var LYRIC_LINE = /^@@(\d+)\s+(.*)$/;
|
|
1173
|
+
var isLyricContinuation = (seg) => !/^[@#]/.test(seg);
|
|
1174
|
+
var splitSegments = (mml) => mml.split(/[;\n\r]+/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1175
|
+
var clamp = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
1176
|
+
var MAX_VOCAL_VOLUME = 400;
|
|
1177
|
+
var VOCAL_BOOST_DB_PER_PERCENT = 0.08;
|
|
1178
|
+
var vocalVolumeToGain = (v) => {
|
|
1179
|
+
if (v <= 0) return 0;
|
|
1180
|
+
if (v <= 100) return v / 100;
|
|
1181
|
+
return 10 ** ((v - 100) * VOCAL_BOOST_DB_PER_PERCENT / 20);
|
|
1182
|
+
};
|
|
1183
|
+
var parseLyrics = (mml) => {
|
|
1184
|
+
const tracks = /* @__PURE__ */ new Map();
|
|
1185
|
+
const segments = splitSegments(mml);
|
|
1186
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1187
|
+
const m = segments[i].match(LYRIC_LINE);
|
|
1188
|
+
if (!m) continue;
|
|
1189
|
+
const trackId = Number.parseInt(m[1], 10);
|
|
1190
|
+
const tokens = m[2].trim().split(/\s+/);
|
|
1191
|
+
const modelToken = tokens.shift() ?? "";
|
|
1192
|
+
let volume = DEFAULT_VOCAL_VOLUME;
|
|
1193
|
+
let gate = 100;
|
|
1194
|
+
let pan = 64;
|
|
1195
|
+
let octave = 0;
|
|
1196
|
+
const colon = modelToken.indexOf(":");
|
|
1197
|
+
const model = (colon === -1 ? modelToken : modelToken.slice(0, colon)).toLowerCase();
|
|
1198
|
+
if (colon !== -1) {
|
|
1199
|
+
const v = Number.parseInt(modelToken.slice(colon + 1), 10);
|
|
1200
|
+
if (Number.isFinite(v)) volume = clamp(v, 0, MAX_VOCAL_VOLUME);
|
|
1201
|
+
}
|
|
1202
|
+
const metaTokens = [modelToken];
|
|
1203
|
+
while (tokens.length > 0) {
|
|
1204
|
+
const v = /^v(\d+)$/.exec(tokens[0]);
|
|
1205
|
+
const q2 = /^q(\d+)$/.exec(tokens[0]);
|
|
1206
|
+
const p = /^p(\d+)$/.exec(tokens[0]);
|
|
1207
|
+
const o = /^o(-?\d+)$/.exec(tokens[0]);
|
|
1208
|
+
if (v) {
|
|
1209
|
+
volume = clamp(Number.parseInt(v[1], 10), 0, MAX_VOCAL_VOLUME);
|
|
1210
|
+
} else if (q2) {
|
|
1211
|
+
gate = clamp(Number.parseInt(q2[1], 10), 0, 100);
|
|
1212
|
+
} else if (p) {
|
|
1213
|
+
pan = clamp(Number.parseInt(p[1], 10), 0, 127);
|
|
1214
|
+
} else if (o) {
|
|
1215
|
+
octave = clamp(Number.parseInt(o[1], 10), -2, 2);
|
|
1216
|
+
} else {
|
|
1217
|
+
break;
|
|
1218
|
+
}
|
|
1219
|
+
metaTokens.push(tokens.shift());
|
|
1220
|
+
}
|
|
1221
|
+
const lyricLines = [tokens.join(" ")];
|
|
1222
|
+
while (i + 1 < segments.length && isLyricContinuation(segments[i + 1])) {
|
|
1223
|
+
lyricLines.push(segments[++i]);
|
|
1224
|
+
}
|
|
1225
|
+
const { syllables, lineBreaks } = normalizeLyricLines(lyricLines);
|
|
1226
|
+
tracks.set(trackId, {
|
|
1227
|
+
trackId,
|
|
1228
|
+
model,
|
|
1229
|
+
volume,
|
|
1230
|
+
gate,
|
|
1231
|
+
pan,
|
|
1232
|
+
octave,
|
|
1233
|
+
syllables,
|
|
1234
|
+
metaText: metaTokens.join(" "),
|
|
1235
|
+
...lineBreaks.length > 0 ? { lineBreaks } : {}
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
return tracks;
|
|
1239
|
+
};
|
|
1240
|
+
var stripLyrics = (mml) => {
|
|
1241
|
+
const segments = splitSegments(mml);
|
|
1242
|
+
const kept = [];
|
|
1243
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1244
|
+
if (LYRIC_LINE.test(segments[i])) {
|
|
1245
|
+
while (i + 1 < segments.length && isLyricContinuation(segments[i + 1]))
|
|
1246
|
+
i++;
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
kept.push(segments[i]);
|
|
1250
|
+
}
|
|
1251
|
+
return kept.join("\n");
|
|
1252
|
+
};
|
|
1253
|
+
var panToStereo = (pan) => Math.max(-1, Math.min(1, (pan - 64) / 64));
|
|
1254
|
+
var createLyricsConductor = (lyrics) => {
|
|
1255
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
1256
|
+
const consume = (trackId) => {
|
|
1257
|
+
const track = lyrics.get(trackId);
|
|
1258
|
+
if (!track || track.syllables.length === 0) return null;
|
|
1259
|
+
const ptr = pointers.get(trackId) ?? 0;
|
|
1260
|
+
const syllable = track.syllables[ptr];
|
|
1261
|
+
if (!syllable) return null;
|
|
1262
|
+
pointers.set(trackId, ptr + 1);
|
|
1263
|
+
return {
|
|
1264
|
+
model: track.model,
|
|
1265
|
+
syllable,
|
|
1266
|
+
volume: vocalVolumeToGain(track.volume ?? DEFAULT_VOCAL_VOLUME),
|
|
1267
|
+
gate: (track.gate ?? DEFAULT_GATE) / 100,
|
|
1268
|
+
pan: panToStereo(track.pan ?? DEFAULT_PAN)
|
|
1269
|
+
};
|
|
1270
|
+
};
|
|
1271
|
+
const reset = () => pointers.clear();
|
|
1272
|
+
return { consume, reset };
|
|
1273
|
+
};
|
|
1274
|
+
var FORMANTS = {
|
|
1275
|
+
a: [800, 1200],
|
|
1276
|
+
i: [300, 2300],
|
|
1277
|
+
u: [350, 800],
|
|
1278
|
+
e: [500, 1900],
|
|
1279
|
+
o: [500, 900],
|
|
1280
|
+
// 撥音(ん)は鼻音寄りの低フォルマント
|
|
1281
|
+
N: [250, 1e3]
|
|
1282
|
+
};
|
|
1283
|
+
var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
|
|
1284
|
+
var createKlattVoice = (ctx, destination) => {
|
|
1285
|
+
const active = /* @__PURE__ */ new Set();
|
|
1286
|
+
const voice = (syllable, e) => {
|
|
1287
|
+
const t0 = ctx.currentTime + e.when;
|
|
1288
|
+
const peak = Math.max(1e-4, e.volume);
|
|
1289
|
+
if (syllable.vowel === "" || syllable.consonant === "Q") return;
|
|
1290
|
+
const [f1, f2] = FORMANTS[syllable.vowel] ?? FORMANTS.a;
|
|
1291
|
+
const attack = 0.02;
|
|
1292
|
+
const release = 0.06;
|
|
1293
|
+
const sustainEnd = t0 + Math.max(attack + 0.02, e.duration);
|
|
1294
|
+
let panner = null;
|
|
1295
|
+
let out = destination;
|
|
1296
|
+
if (typeof ctx.createStereoPanner === "function") {
|
|
1297
|
+
panner = ctx.createStereoPanner();
|
|
1298
|
+
panner.pan.value = Math.max(-1, Math.min(1, e.pan ?? 0));
|
|
1299
|
+
panner.connect(destination);
|
|
1300
|
+
out = panner;
|
|
1301
|
+
}
|
|
1302
|
+
const osc = ctx.createOscillator();
|
|
1303
|
+
osc.type = "sawtooth";
|
|
1304
|
+
osc.frequency.value = midiToFreq(e.pitch);
|
|
1305
|
+
const makeFormant = (freq, q2, gainScale) => {
|
|
1306
|
+
const filter = ctx.createBiquadFilter();
|
|
1307
|
+
filter.type = "bandpass";
|
|
1308
|
+
filter.frequency.value = freq;
|
|
1309
|
+
filter.Q.value = q2;
|
|
1310
|
+
const g = ctx.createGain();
|
|
1311
|
+
g.gain.value = gainScale;
|
|
1312
|
+
osc.connect(filter).connect(g);
|
|
1313
|
+
return g;
|
|
1314
|
+
};
|
|
1315
|
+
const env = ctx.createGain();
|
|
1316
|
+
env.gain.setValueAtTime(1e-4, t0);
|
|
1317
|
+
env.gain.exponentialRampToValueAtTime(peak, t0 + attack);
|
|
1318
|
+
env.gain.setValueAtTime(peak, sustainEnd);
|
|
1319
|
+
env.gain.exponentialRampToValueAtTime(1e-4, sustainEnd + release);
|
|
1320
|
+
const MAKEUP = 4;
|
|
1321
|
+
makeFormant(f1, 6, MAKEUP).connect(env);
|
|
1322
|
+
makeFormant(f2, 9, MAKEUP * 0.7).connect(env);
|
|
1323
|
+
env.connect(out);
|
|
1324
|
+
const fricatives = /* @__PURE__ */ new Set(["s", "sh", "ch", "ts", "h", "f"]);
|
|
1325
|
+
if (fricatives.has(syllable.consonant)) {
|
|
1326
|
+
const dur = 0.05;
|
|
1327
|
+
const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
|
|
1328
|
+
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
|
|
1329
|
+
const data = buffer.getChannelData(0);
|
|
1330
|
+
for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
|
|
1331
|
+
const src = ctx.createBufferSource();
|
|
1332
|
+
src.buffer = buffer;
|
|
1333
|
+
const hp = ctx.createBiquadFilter();
|
|
1334
|
+
hp.type = "highpass";
|
|
1335
|
+
hp.frequency.value = syllable.consonant === "sh" ? 3e3 : 4500;
|
|
1336
|
+
const ng = ctx.createGain();
|
|
1337
|
+
ng.gain.setValueAtTime(peak * 0.5, t0);
|
|
1338
|
+
ng.gain.exponentialRampToValueAtTime(1e-4, t0 + dur);
|
|
1339
|
+
src.connect(hp).connect(ng).connect(out);
|
|
1340
|
+
src.start(t0);
|
|
1341
|
+
src.stop(t0 + dur);
|
|
1342
|
+
active.add(src);
|
|
1343
|
+
src.onended = () => {
|
|
1344
|
+
active.delete(src);
|
|
1345
|
+
src.disconnect();
|
|
1346
|
+
hp.disconnect();
|
|
1347
|
+
ng.disconnect();
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
osc.start(t0);
|
|
1351
|
+
osc.stop(sustainEnd + release + 0.02);
|
|
1352
|
+
active.add(osc);
|
|
1353
|
+
osc.onended = () => {
|
|
1354
|
+
active.delete(osc);
|
|
1355
|
+
osc.disconnect();
|
|
1356
|
+
panner?.disconnect();
|
|
1357
|
+
};
|
|
1358
|
+
};
|
|
1359
|
+
voice.stopAll = () => {
|
|
1360
|
+
for (const n of active) {
|
|
1361
|
+
try {
|
|
1362
|
+
n.stop();
|
|
1363
|
+
} catch {
|
|
1364
|
+
}
|
|
1365
|
+
n.disconnect();
|
|
1366
|
+
}
|
|
1367
|
+
active.clear();
|
|
1368
|
+
};
|
|
1369
|
+
return voice;
|
|
1370
|
+
};
|
|
1371
|
+
var KOE_BASE_URL = "https://pub-12482a6b5cbc4c9e906b2e1904cabae5.r2.dev";
|
|
1372
|
+
var KOE_VOICEBANKS = {
|
|
1373
|
+
tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093.koe",
|
|
1374
|
+
rino: "\u6625\u97F3\u30EA\u30CEver0.3.koe",
|
|
1375
|
+
roze: "\u675F\u97F3\u30ED\u30BCver0.\uFF151(\u591A\u97F3\u968E).koe",
|
|
1376
|
+
ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642\u9023\u7D9A\u97F3Ver.1.03.koe",
|
|
1377
|
+
ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640\u6B4C\u9023\u7D9A\u97F3\u666E1.00.koe",
|
|
1378
|
+
teto: "\u91CD\u97F3\u30C6\u30C8\u5358\u72EC\u97F3.koe",
|
|
1379
|
+
shiyo: "\u9769\u547D\u30B7\u30E8.koe"
|
|
1380
|
+
};
|
|
1381
|
+
var KOE_VOICEBANK_LABELS = {
|
|
1382
|
+
tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093",
|
|
1383
|
+
rino: "\u6625\u97F3\u30EA\u30CE",
|
|
1384
|
+
roze: "\u675F\u97F3\u30ED\u30BC",
|
|
1385
|
+
ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642",
|
|
1386
|
+
ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640",
|
|
1387
|
+
teto: "\u91CD\u97F3\u30C6\u30C8",
|
|
1388
|
+
shiyo: "\u9769\u547D\u30B7\u30E8"
|
|
1389
|
+
};
|
|
1390
|
+
var VOICE_IMAGE_KEY = {
|
|
1391
|
+
klatt: "puyuyu",
|
|
1392
|
+
tsukuyomi: "tsukuyomi",
|
|
1393
|
+
rino: "rino",
|
|
1394
|
+
roze: "roze",
|
|
1395
|
+
ruko_male: "ruko",
|
|
1396
|
+
ruko_female: "ruko",
|
|
1397
|
+
teto: "teto",
|
|
1398
|
+
shiyo: "shiyo"
|
|
1399
|
+
};
|
|
1400
|
+
var KOE_VOICEBANK_TERMS = {
|
|
1401
|
+
tsukuyomi: "https://tyc.rei-yumesaki.net/material/utau/terms/",
|
|
1402
|
+
rino: "https://hatenakun1.github.io/halunelino/",
|
|
1403
|
+
roze: "https://tabaneroze.ninja-web.net/terms-of-use.html",
|
|
1404
|
+
ruko_male: "https://long-sleeper.net/index.php?id=22",
|
|
1405
|
+
ruko_female: "https://long-sleeper.net/index.php?id=22",
|
|
1406
|
+
teto: "https://kasaneteto.jp/guidelines/voice.html",
|
|
1407
|
+
shiyo: "https://kakumeisiyo.my.canva.site/dagkuyjwycs"
|
|
1408
|
+
};
|
|
1409
|
+
var koeUrl = (name, base = KOE_BASE_URL) => `${base}/${encodeURIComponent(name)}`;
|
|
1410
|
+
var DEFAULT_WORLDLINE_SCRIPT = "https://onjmin.github.io/koe/demo/world/worldline.js";
|
|
1411
|
+
var KOE_SAMPLE_RATE = 48e3;
|
|
1412
|
+
var expandSeparators = (candidate) => candidate.includes(" ") ? [candidate, candidate.replace(/ /g, "\u3000"), candidate.replace(/ /g, "")] : [candidate];
|
|
1413
|
+
var PITCH_SUFFIX = /_([A-G][#b]?-?\d+)$/;
|
|
1414
|
+
var NAME_SEMITONE = {
|
|
1415
|
+
c: 0,
|
|
1416
|
+
d: 2,
|
|
1417
|
+
e: 4,
|
|
1418
|
+
f: 5,
|
|
1419
|
+
g: 7,
|
|
1420
|
+
a: 9,
|
|
1421
|
+
b: 11
|
|
1422
|
+
};
|
|
1423
|
+
var pitchTokenToMidi = (token) => {
|
|
1424
|
+
const m = /^([A-Ga-g])([#b]?)(-?\d+)$/.exec(token);
|
|
1425
|
+
if (!m) return null;
|
|
1426
|
+
let semi = NAME_SEMITONE[m[1].toLowerCase()];
|
|
1427
|
+
if (m[2] === "#") semi++;
|
|
1428
|
+
else if (m[2] === "b") semi--;
|
|
1429
|
+
return (Number.parseInt(m[3], 10) + 1) * 12 + semi;
|
|
1430
|
+
};
|
|
1431
|
+
var collectPitchTokens = (aliases) => {
|
|
1432
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1433
|
+
for (const a of aliases) {
|
|
1434
|
+
const m = PITCH_SUFFIX.exec(a);
|
|
1435
|
+
if (!m || seen.has(m[1])) continue;
|
|
1436
|
+
const midi = pitchTokenToMidi(m[1]);
|
|
1437
|
+
if (midi != null) seen.set(m[1], midi);
|
|
1438
|
+
}
|
|
1439
|
+
return [...seen].map(([token, midi]) => ({ token, midi }));
|
|
1440
|
+
};
|
|
1441
|
+
var resolveKoeAlias = (hasAlias, pitchTokens, syl, prevVowel, noteNum) => {
|
|
1442
|
+
const kana = syl.kana;
|
|
1443
|
+
const cons = syl.consonant === "N" ? "n" : syl.consonant;
|
|
1444
|
+
const vow = syl.vowel === "N" ? "" : syl.vowel;
|
|
1445
|
+
const romaji = `${cons}${vow}` || vow;
|
|
1446
|
+
const pv = prevVowel || "-";
|
|
1447
|
+
const raw = [
|
|
1448
|
+
// 連続音(VCV): 直前母音つき
|
|
1449
|
+
`${pv} ${kana}`,
|
|
1450
|
+
`${pv} ${romaji}`,
|
|
1451
|
+
// 単独音 / CVVC
|
|
1452
|
+
kana,
|
|
1453
|
+
romaji
|
|
1454
|
+
];
|
|
1455
|
+
const vk = VOWEL_KANA[syl.vowel];
|
|
1456
|
+
if (vk) raw.push(`${pv} ${vk}`, vk, syl.vowel);
|
|
1457
|
+
if (syl.vowel === "N") raw.push("\u3093", "n", "N", `${pv} \u3093`);
|
|
1458
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1459
|
+
const tryAlias = (candidate) => {
|
|
1460
|
+
for (const v of expandSeparators(candidate)) {
|
|
1461
|
+
if (seen.has(v)) continue;
|
|
1462
|
+
seen.add(v);
|
|
1463
|
+
if (hasAlias(v)) return v;
|
|
1464
|
+
}
|
|
1465
|
+
return null;
|
|
1466
|
+
};
|
|
1467
|
+
if (pitchTokens.length) {
|
|
1468
|
+
const nearest = pitchTokens.slice().sort((a, b) => Math.abs(a.midi - noteNum) - Math.abs(b.midi - noteNum));
|
|
1469
|
+
for (const { token } of nearest) {
|
|
1470
|
+
for (const base of raw) {
|
|
1471
|
+
const hit = tryAlias(`${base}_${token}`);
|
|
1472
|
+
if (hit) return hit;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
for (const base of raw) {
|
|
1477
|
+
const hit = tryAlias(base);
|
|
1478
|
+
if (hit) return hit;
|
|
1479
|
+
}
|
|
1480
|
+
return null;
|
|
1481
|
+
};
|
|
1482
|
+
var createLocalBackend = async (options) => {
|
|
1483
|
+
const bank = await VoiceBank.load(options.koe);
|
|
1484
|
+
const worldline = options.lightweight ? null : await Worldline.load({
|
|
1485
|
+
scriptUrl: options.worldlineScriptUrl ?? DEFAULT_WORLDLINE_SCRIPT
|
|
1486
|
+
}).catch(() => null);
|
|
1487
|
+
const pcmCache = /* @__PURE__ */ new Map();
|
|
1488
|
+
const getPcm = (alias) => {
|
|
1489
|
+
let p = pcmCache.get(alias);
|
|
1490
|
+
if (!p) {
|
|
1491
|
+
p = bank.getPcm(alias);
|
|
1492
|
+
pcmCache.set(alias, p);
|
|
1493
|
+
}
|
|
1494
|
+
return p;
|
|
1495
|
+
};
|
|
1496
|
+
const renderAlias = async (alias, pitch, durationMs) => {
|
|
1497
|
+
const pcm = await getPcm(alias);
|
|
1498
|
+
if (!pcm || pcm.length === 0) return null;
|
|
1499
|
+
const entry = bank.manifest.phonemes[alias];
|
|
1500
|
+
const lead = leadInFromEntry(entry);
|
|
1501
|
+
const targetHz = midiToFreq(pitch);
|
|
1502
|
+
if (worldline) {
|
|
1503
|
+
const audio = worldline.renderNote({
|
|
1504
|
+
pcm,
|
|
1505
|
+
pitch: targetHz,
|
|
1506
|
+
durationMs,
|
|
1507
|
+
...lead
|
|
1508
|
+
});
|
|
1509
|
+
if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
|
|
1510
|
+
}
|
|
1511
|
+
const rate = entry.pitch > 0 ? targetHz / entry.pitch : 1;
|
|
1512
|
+
return {
|
|
1513
|
+
pcm: Float32Array.from(pcm),
|
|
1514
|
+
preSec: entry.pre / KOE_SAMPLE_RATE / rate,
|
|
1515
|
+
rate
|
|
1516
|
+
};
|
|
1517
|
+
};
|
|
1518
|
+
return {
|
|
1519
|
+
hasAlias: (a) => bank.has(a),
|
|
1520
|
+
pitchTokens: collectPitchTokens(Object.keys(bank.manifest.phonemes)),
|
|
1521
|
+
renderAlias,
|
|
1522
|
+
dispose: () => {
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
};
|
|
1526
|
+
var spawnVoiceWorker = async (url) => {
|
|
1527
|
+
const sameOrigin = new URL(url, location.href).origin === location.origin;
|
|
1528
|
+
if (sameOrigin) return new Worker(url);
|
|
1529
|
+
const text = await fetch(url).then((r) => r.text());
|
|
1530
|
+
return new Worker(
|
|
1531
|
+
URL.createObjectURL(new Blob([text], { type: "text/javascript" }))
|
|
1532
|
+
);
|
|
1533
|
+
};
|
|
1534
|
+
var createWorkerBackend = async (workerUrl, options) => {
|
|
1535
|
+
const worker = await spawnVoiceWorker(workerUrl);
|
|
1536
|
+
const aliasSet = /* @__PURE__ */ new Set();
|
|
1537
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1538
|
+
let reqId = 0;
|
|
1539
|
+
let onReady = null;
|
|
1540
|
+
let onFail = null;
|
|
1541
|
+
worker.onmessage = (ev) => {
|
|
1542
|
+
const m = ev.data;
|
|
1543
|
+
if (m.type === "ready") {
|
|
1544
|
+
for (const a of m.aliases) aliasSet.add(a);
|
|
1545
|
+
onReady?.();
|
|
1546
|
+
} else if (m.type === "error") {
|
|
1547
|
+
onFail?.(new Error(m.message));
|
|
1548
|
+
} else if (m.type === "rendered") {
|
|
1549
|
+
const cb = pending.get(m.id);
|
|
1550
|
+
if (cb) {
|
|
1551
|
+
pending.delete(m.id);
|
|
1552
|
+
cb(m);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
worker.onerror = (e) => {
|
|
1557
|
+
const ev = e;
|
|
1558
|
+
onFail?.(new Error(ev.message || ev.error || `Event: ${ev.type}`));
|
|
1559
|
+
};
|
|
1560
|
+
await new Promise((resolve, reject) => {
|
|
1561
|
+
onReady = resolve;
|
|
1562
|
+
onFail = reject;
|
|
1563
|
+
worker.postMessage({
|
|
1564
|
+
type: "init",
|
|
1565
|
+
koe: options.koe,
|
|
1566
|
+
worldlineScriptUrl: options.worldlineScriptUrl ?? DEFAULT_WORLDLINE_SCRIPT,
|
|
1567
|
+
lightweight: !!options.lightweight
|
|
1568
|
+
});
|
|
1569
|
+
});
|
|
1570
|
+
onReady = null;
|
|
1571
|
+
onFail = null;
|
|
1572
|
+
const renderAlias = (alias, pitch, durationMs) => new Promise((resolve) => {
|
|
1573
|
+
const id = ++reqId;
|
|
1574
|
+
pending.set(
|
|
1575
|
+
id,
|
|
1576
|
+
(m) => resolve(
|
|
1577
|
+
m.pcm ? { pcm: m.pcm, preSec: m.preSec ?? 0, rate: m.rate ?? 1 } : null
|
|
1578
|
+
)
|
|
1579
|
+
);
|
|
1580
|
+
worker.postMessage({
|
|
1581
|
+
type: "render",
|
|
1582
|
+
id,
|
|
1583
|
+
alias,
|
|
1584
|
+
pitch,
|
|
1585
|
+
durationMs
|
|
1586
|
+
});
|
|
1587
|
+
});
|
|
1588
|
+
return {
|
|
1589
|
+
hasAlias: (a) => aliasSet.has(a),
|
|
1590
|
+
pitchTokens: collectPitchTokens(aliasSet),
|
|
1591
|
+
renderAlias,
|
|
1592
|
+
dispose: () => worker.terminate()
|
|
1593
|
+
};
|
|
1594
|
+
};
|
|
1595
|
+
var createKoeVoice = async (ctx, destination, options) => {
|
|
1596
|
+
const backend = options.voiceWorkerUrl ? await createWorkerBackend(options.voiceWorkerUrl, options) : await createLocalBackend(options);
|
|
1597
|
+
const renderCache = /* @__PURE__ */ new Map();
|
|
1598
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
1599
|
+
const active = /* @__PURE__ */ new Set();
|
|
1600
|
+
let prevVowel = "";
|
|
1601
|
+
const keyOf = (alias, pitch, durationMs) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}`;
|
|
1602
|
+
const renderInto = (alias, pitch, durationMs) => {
|
|
1603
|
+
const key = keyOf(alias, pitch, durationMs);
|
|
1604
|
+
const existing = renderCache.get(key);
|
|
1605
|
+
if (existing !== void 0) return Promise.resolve(existing);
|
|
1606
|
+
const flying = inflight.get(key);
|
|
1607
|
+
if (flying) return flying;
|
|
1608
|
+
const p = (async () => {
|
|
1609
|
+
const out = await backend.renderAlias(alias, pitch, durationMs);
|
|
1610
|
+
let rendered = null;
|
|
1611
|
+
if (out) {
|
|
1612
|
+
const buf = ctx.createBuffer(1, out.pcm.length, KOE_SAMPLE_RATE);
|
|
1613
|
+
buf.copyToChannel(out.pcm, 0);
|
|
1614
|
+
rendered = { audio: buf, preSec: out.preSec, rate: out.rate };
|
|
1615
|
+
}
|
|
1616
|
+
renderCache.set(key, rendered);
|
|
1617
|
+
inflight.delete(key);
|
|
1618
|
+
return rendered;
|
|
1619
|
+
})();
|
|
1620
|
+
inflight.set(key, p);
|
|
1621
|
+
return p;
|
|
1622
|
+
};
|
|
1623
|
+
const LEADCAP_S = 0.09;
|
|
1624
|
+
const schedule = (r, t0, peak, pan) => {
|
|
1625
|
+
let out = destination;
|
|
1626
|
+
let panner = null;
|
|
1627
|
+
if (typeof ctx.createStereoPanner === "function") {
|
|
1628
|
+
panner = ctx.createStereoPanner();
|
|
1629
|
+
panner.pan.value = Math.max(-1, Math.min(1, pan));
|
|
1630
|
+
panner.connect(destination);
|
|
1631
|
+
out = panner;
|
|
1632
|
+
}
|
|
1633
|
+
const src = ctx.createBufferSource();
|
|
1634
|
+
src.buffer = r.audio;
|
|
1635
|
+
src.playbackRate.value = r.rate;
|
|
1636
|
+
const effPre = Math.min(r.preSec, LEADCAP_S);
|
|
1637
|
+
const skipS = r.preSec - effPre;
|
|
1638
|
+
const startAt = Math.max(ctx.currentTime + 1e-3, t0 - effPre);
|
|
1639
|
+
const playDurSec = r.audio.duration / r.rate - skipS;
|
|
1640
|
+
const endAt = startAt + playDurSec;
|
|
1641
|
+
const attack = 0.01;
|
|
1642
|
+
const release = 0.04;
|
|
1643
|
+
const env = ctx.createGain();
|
|
1644
|
+
env.gain.setValueAtTime(1e-4, startAt);
|
|
1645
|
+
env.gain.exponentialRampToValueAtTime(peak, startAt + attack);
|
|
1646
|
+
const fadeStart = Math.max(startAt + attack, endAt - release);
|
|
1647
|
+
env.gain.setValueAtTime(peak, fadeStart);
|
|
1648
|
+
env.gain.exponentialRampToValueAtTime(1e-4, endAt);
|
|
1649
|
+
src.connect(env).connect(out);
|
|
1650
|
+
src.start(startAt, skipS);
|
|
1651
|
+
src.stop(endAt + 0.02);
|
|
1652
|
+
active.add(src);
|
|
1653
|
+
src.onended = () => {
|
|
1654
|
+
active.delete(src);
|
|
1655
|
+
src.disconnect();
|
|
1656
|
+
env.disconnect();
|
|
1657
|
+
panner?.disconnect();
|
|
1658
|
+
};
|
|
1659
|
+
};
|
|
1660
|
+
const model = (syllable, e) => {
|
|
1661
|
+
if (syllable.consonant === "Q" || syllable.vowel === "") return;
|
|
1662
|
+
const alias = resolveKoeAlias(
|
|
1663
|
+
backend.hasAlias,
|
|
1664
|
+
backend.pitchTokens,
|
|
1665
|
+
syllable,
|
|
1666
|
+
prevVowel,
|
|
1667
|
+
e.pitch
|
|
1668
|
+
);
|
|
1669
|
+
if (syllable.vowel && syllable.vowel !== "N") prevVowel = syllable.vowel;
|
|
1670
|
+
if (!alias) return;
|
|
1671
|
+
const t0 = ctx.currentTime + e.when;
|
|
1672
|
+
const peak = Math.max(1e-4, e.volume);
|
|
1673
|
+
const pan = e.pan ?? 0;
|
|
1674
|
+
const durationMs = Math.max(60, e.duration * 1e3);
|
|
1675
|
+
void renderInto(alias, e.pitch, durationMs).then((r) => {
|
|
1676
|
+
if (r) schedule(r, t0, peak, pan);
|
|
1677
|
+
});
|
|
1678
|
+
};
|
|
1679
|
+
model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs) => {
|
|
1680
|
+
if (syllable.consonant === "Q" || syllable.vowel === "") return null;
|
|
1681
|
+
const alias = resolveKoeAlias(
|
|
1682
|
+
backend.hasAlias,
|
|
1683
|
+
backend.pitchTokens,
|
|
1684
|
+
syllable,
|
|
1685
|
+
prevVowelArg,
|
|
1686
|
+
pitch
|
|
1687
|
+
);
|
|
1688
|
+
if (!alias) return null;
|
|
1689
|
+
const dMs = Math.max(60, durationMs);
|
|
1690
|
+
const r = await renderInto(alias, pitch, dMs);
|
|
1691
|
+
return r ? keyOf(alias, pitch, dMs) : null;
|
|
1692
|
+
};
|
|
1693
|
+
model.scheduleCached = (key, t0, peak, pan) => {
|
|
1694
|
+
const r = renderCache.get(key);
|
|
1695
|
+
if (r) schedule(r, t0, peak, pan);
|
|
1696
|
+
};
|
|
1697
|
+
model.stopAll = () => {
|
|
1698
|
+
for (const src of active) {
|
|
1699
|
+
try {
|
|
1700
|
+
src.stop();
|
|
1701
|
+
} catch {
|
|
1702
|
+
}
|
|
1703
|
+
src.disconnect();
|
|
1704
|
+
}
|
|
1705
|
+
active.clear();
|
|
1706
|
+
};
|
|
1707
|
+
model.reset = () => {
|
|
1708
|
+
prevVowel = "";
|
|
1709
|
+
};
|
|
1710
|
+
return model;
|
|
1711
|
+
};
|
|
1712
|
+
var PREWARM_NOTES = 3;
|
|
1713
|
+
var STREAM_LOOKAHEAD_SEC = 1.5;
|
|
1714
|
+
var STREAM_POLL_MS = 100;
|
|
1715
|
+
var FALLBACK_MODEL = "klatt";
|
|
1716
|
+
var createSingingVoices = (ctx, destination, options = {}) => {
|
|
1717
|
+
const catalog = {};
|
|
1718
|
+
for (const [k, file] of Object.entries(KOE_VOICEBANKS))
|
|
1719
|
+
catalog[k] = koeUrl(file);
|
|
1720
|
+
for (const [k, v] of Object.entries(options.voicebanks ?? {}))
|
|
1721
|
+
catalog[k.toLowerCase()] = v;
|
|
1722
|
+
let streamSession = 0;
|
|
1723
|
+
const loaded = /* @__PURE__ */ new Map([
|
|
1724
|
+
[FALLBACK_MODEL, createKlattVoice(ctx, destination)]
|
|
1725
|
+
]);
|
|
1726
|
+
const loading = /* @__PURE__ */ new Map();
|
|
1727
|
+
const load = (model) => {
|
|
1728
|
+
const m = model.toLowerCase();
|
|
1729
|
+
const ready = loaded.get(m);
|
|
1730
|
+
if (ready) return Promise.resolve(ready);
|
|
1731
|
+
const inflight = loading.get(m);
|
|
1732
|
+
if (inflight) return inflight;
|
|
1733
|
+
const koe = catalog[m];
|
|
1734
|
+
if (!koe) return Promise.resolve(null);
|
|
1735
|
+
const p = (async () => (
|
|
1736
|
+
// URL文字列はそのまま渡す。koe側が VoiceBank.load 内で HTTP Range により
|
|
1737
|
+
// マニフェストだけ先読みし、音素PCMは歌う直前にオンデマンド取得する
|
|
1738
|
+
// (= 初回に .koe 全体をDLしない。モバイル初回ロードの待ちを解消)。
|
|
1739
|
+
// Blob/File が直接渡されたケース(ローカル読み込み)はそのまま BlobVoiceSource。
|
|
1740
|
+
createKoeVoice(ctx, destination, {
|
|
1741
|
+
koe,
|
|
1742
|
+
worldlineScriptUrl: options.worldlineScriptUrl,
|
|
1743
|
+
lightweight: options.lightweight,
|
|
1744
|
+
voiceWorkerUrl: options.voiceWorkerUrl
|
|
1745
|
+
})
|
|
1746
|
+
))().then((v) => {
|
|
1747
|
+
loaded.set(m, v);
|
|
1748
|
+
return v;
|
|
1749
|
+
}).catch((err) => {
|
|
1750
|
+
console.warn(`[dtm] koe\u97F3\u6E90 "${m}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F`, err);
|
|
1751
|
+
return null;
|
|
1752
|
+
});
|
|
1753
|
+
loading.set(m, p);
|
|
1754
|
+
return p;
|
|
1755
|
+
};
|
|
1756
|
+
const loadModels = async (models) => {
|
|
1757
|
+
const set = /* @__PURE__ */ new Set();
|
|
1758
|
+
for (const m of models) if (m) set.add(m.toLowerCase());
|
|
1759
|
+
await Promise.all([...set].map((m) => load(m)));
|
|
1760
|
+
};
|
|
1761
|
+
const forEachSungNote = (track, fn) => {
|
|
1762
|
+
let prevVowel = "";
|
|
1763
|
+
for (const note of track.notes) {
|
|
1764
|
+
const syl = note.syllable;
|
|
1765
|
+
if (syl.consonant === "Q" || syl.vowel === "") continue;
|
|
1766
|
+
fn(note, prevVowel);
|
|
1767
|
+
if (syl.vowel && syl.vowel !== "N") prevVowel = syl.vowel;
|
|
1768
|
+
}
|
|
1769
|
+
};
|
|
1770
|
+
const warm = async (tracks, count = PREWARM_NOTES) => {
|
|
1771
|
+
const promises = [];
|
|
1772
|
+
for (const track of tracks) {
|
|
1773
|
+
const m = loaded.get(track.model.toLowerCase());
|
|
1774
|
+
if (!m?.renderToCache) continue;
|
|
1775
|
+
let n = 0;
|
|
1776
|
+
forEachSungNote(track, (note, prevVowel) => {
|
|
1777
|
+
if (n >= count) return;
|
|
1778
|
+
n++;
|
|
1779
|
+
promises.push(
|
|
1780
|
+
m.renderToCache?.(
|
|
1781
|
+
note.syllable,
|
|
1782
|
+
prevVowel,
|
|
1783
|
+
note.pitch,
|
|
1784
|
+
note.durationSec * 1e3
|
|
1785
|
+
) ?? Promise.resolve(null)
|
|
1786
|
+
);
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
await Promise.all(promises);
|
|
1790
|
+
};
|
|
1791
|
+
const startStream = (tracks, anchorTime) => {
|
|
1792
|
+
const session = ++streamSession;
|
|
1793
|
+
const items = [];
|
|
1794
|
+
for (const track of tracks) {
|
|
1795
|
+
const model = loaded.get(track.model.toLowerCase());
|
|
1796
|
+
if (!model) continue;
|
|
1797
|
+
forEachSungNote(track, (note, prevVowel) => {
|
|
1798
|
+
items.push({
|
|
1799
|
+
model,
|
|
1800
|
+
note,
|
|
1801
|
+
prevVowel,
|
|
1802
|
+
volume: track.volume,
|
|
1803
|
+
pan: track.pan
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
items.sort((a, b) => a.note.startSec - b.note.startSec);
|
|
1808
|
+
void (async () => {
|
|
1809
|
+
for (const item of items) {
|
|
1810
|
+
if (session !== streamSession) return;
|
|
1811
|
+
while (item.note.startSec - (ctx.currentTime - anchorTime) > STREAM_LOOKAHEAD_SEC) {
|
|
1812
|
+
await new Promise((resolve) => setTimeout(resolve, STREAM_POLL_MS));
|
|
1813
|
+
if (session !== streamSession) return;
|
|
1814
|
+
}
|
|
1815
|
+
const t0 = anchorTime + item.note.startSec;
|
|
1816
|
+
const peak = Math.max(1e-4, item.volume);
|
|
1817
|
+
const { model, note } = item;
|
|
1818
|
+
if (model.renderToCache && model.scheduleCached) {
|
|
1819
|
+
const key = await model.renderToCache(
|
|
1820
|
+
note.syllable,
|
|
1821
|
+
item.prevVowel,
|
|
1822
|
+
note.pitch,
|
|
1823
|
+
note.durationSec * 1e3
|
|
1824
|
+
);
|
|
1825
|
+
if (session !== streamSession) return;
|
|
1826
|
+
if (key) model.scheduleCached(key, t0, peak, item.pan);
|
|
1827
|
+
} else {
|
|
1828
|
+
const when = t0 - ctx.currentTime;
|
|
1829
|
+
model(note.syllable, {
|
|
1830
|
+
trackId: "",
|
|
1831
|
+
pitch: note.pitch,
|
|
1832
|
+
velocity: 100,
|
|
1833
|
+
volume: peak,
|
|
1834
|
+
when,
|
|
1835
|
+
duration: note.durationSec,
|
|
1836
|
+
pan: item.pan
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1840
|
+
}
|
|
1841
|
+
})();
|
|
1842
|
+
};
|
|
1843
|
+
const stopStream = () => {
|
|
1844
|
+
streamSession++;
|
|
1845
|
+
for (const v of loaded.values()) v.stopAll?.();
|
|
1846
|
+
};
|
|
1847
|
+
const reset = () => {
|
|
1848
|
+
stopStream();
|
|
1849
|
+
for (const v of loaded.values()) v.reset?.();
|
|
1850
|
+
};
|
|
1851
|
+
return { loadModels, warm, startStream, stopStream, reset };
|
|
1852
|
+
};
|
|
1853
|
+
var createVoiceRegistry = (models = {}, fallback = "klatt") => {
|
|
1854
|
+
const sing = (model, syllable, e) => {
|
|
1855
|
+
const fn = models[model] ?? models[fallback];
|
|
1856
|
+
fn?.(syllable, e);
|
|
1857
|
+
};
|
|
1858
|
+
const register = (name, m) => {
|
|
1859
|
+
models[name.toLowerCase()] = m;
|
|
1860
|
+
};
|
|
1861
|
+
return { sing, register };
|
|
1862
|
+
};
|
|
1863
|
+
|
|
684
1864
|
// src/macros.ts
|
|
685
1865
|
var SCALES = [
|
|
686
1866
|
[0, 2, 4, 5, 7, 9, 11],
|
|
@@ -1087,7 +2267,7 @@ var exportMIDI = (options) => {
|
|
|
1087
2267
|
(n.startStep + (n.durationSteps || 1)) * tickPerStep
|
|
1088
2268
|
);
|
|
1089
2269
|
const vel = Math.round(
|
|
1090
|
-
(n.velocity ??
|
|
2270
|
+
(n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
|
|
1091
2271
|
);
|
|
1092
2272
|
events.push({ t: startTick, m: [144 | ch & 15, n.pitch, vel] });
|
|
1093
2273
|
events.push({ t: endTick, m: [144 | ch & 15, n.pitch, 0] });
|
|
@@ -1642,7 +2822,7 @@ var MMLCore = class _MMLCore {
|
|
|
1642
2822
|
startStep: step,
|
|
1643
2823
|
durationSteps: options.noteLengthSteps,
|
|
1644
2824
|
pitch,
|
|
1645
|
-
velocity: options.velocity ??
|
|
2825
|
+
velocity: options.velocity ?? DEFAULT_VELOCITY
|
|
1646
2826
|
};
|
|
1647
2827
|
this.notes.push(newNote);
|
|
1648
2828
|
}
|
|
@@ -1729,6 +2909,8 @@ var MMLCore = class _MMLCore {
|
|
|
1729
2909
|
const config = getRenderConfig();
|
|
1730
2910
|
const total = config.stepsPerBar;
|
|
1731
2911
|
const candidates = [
|
|
2912
|
+
{ dur: "1.", s: total * 1.5 },
|
|
2913
|
+
// 付点全音符(最長。これ以上はタイ未対応のため表現不可)
|
|
1732
2914
|
{ dur: "1", s: total / 1 },
|
|
1733
2915
|
{ dur: "2.", s: total / 2 * 1.5 },
|
|
1734
2916
|
{ dur: "2", s: total / 2 },
|
|
@@ -1789,97 +2971,72 @@ var MMLCore = class _MMLCore {
|
|
|
1789
2971
|
return { text: `o${octave}${name}`, currentOctave: octave };
|
|
1790
2972
|
}
|
|
1791
2973
|
/**
|
|
1792
|
-
* MML
|
|
2974
|
+
* MML生成(単一パス・発音順スキャン)
|
|
2975
|
+
*
|
|
2976
|
+
* 以前は1/2小節ごとのウィンドウで走査していたが、それだと半小節境界をまたぐ音符が
|
|
2977
|
+
* 境界で切り詰められて「ぶつ切り」になり、境界直前に始まる音符は隙間が潰れて欠落し、
|
|
2978
|
+
* 歌詞(@@n)の音節割り当てがずれていた。
|
|
2979
|
+
* 全ノートを発音順に一度で処理し、次の発音までの距離だけを上限として
|
|
2980
|
+
* 各音符の長さを忠実に出力する(次の音符がなければ曲末まで伸ばせる)。
|
|
1793
2981
|
*/
|
|
1794
2982
|
generateMML = (volumeOverride) => {
|
|
1795
2983
|
const config = getRenderConfig();
|
|
1796
2984
|
const vol = volumeOverride ?? this.volume;
|
|
1797
|
-
const
|
|
1798
|
-
const header = `t${this.tempo} q50 v${vol}`;
|
|
2985
|
+
const header = `t${this.tempo} v${vol}`;
|
|
1799
2986
|
const segments = [];
|
|
1800
2987
|
let lastOctave = -1;
|
|
1801
2988
|
let currentCursor = 0;
|
|
1802
2989
|
if (this.notes.length === 0) return header;
|
|
1803
|
-
const
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
const
|
|
1809
|
-
|
|
1810
|
-
);
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
}
|
|
1818
|
-
const { dur, steps } = this.findBestFitDuration(gap);
|
|
1819
|
-
segments.push(`r${dur}`);
|
|
1820
|
-
currentCursor += steps;
|
|
1821
|
-
}
|
|
1822
|
-
continue;
|
|
1823
|
-
}
|
|
1824
|
-
const notesByStep = /* @__PURE__ */ new Map();
|
|
1825
|
-
windowNotes.forEach((n) => {
|
|
1826
|
-
const list = notesByStep.get(n.startStep) || [];
|
|
1827
|
-
list.push(n);
|
|
1828
|
-
notesByStep.set(n.startStep, list);
|
|
1829
|
-
});
|
|
1830
|
-
const sortedSteps = Array.from(notesByStep.keys()).sort((a, b) => a - b);
|
|
1831
|
-
for (let i = 0; i < sortedSteps.length; i++) {
|
|
1832
|
-
const startStep = sortedSteps[i];
|
|
1833
|
-
const notes = notesByStep.get(startStep);
|
|
1834
|
-
if (!notes) continue;
|
|
1835
|
-
while (currentCursor < startStep) {
|
|
1836
|
-
const gap = startStep - currentCursor;
|
|
1837
|
-
if (gap <= 2) {
|
|
1838
|
-
currentCursor = startStep;
|
|
1839
|
-
break;
|
|
1840
|
-
}
|
|
1841
|
-
const { dur, steps } = this.findBestFitDuration(gap);
|
|
1842
|
-
segments.push(`r${dur}`);
|
|
1843
|
-
currentCursor += steps;
|
|
1844
|
-
}
|
|
1845
|
-
const nextStart = sortedSteps[i + 1] ?? windowEnd;
|
|
1846
|
-
const physicsLimit = nextStart - currentCursor;
|
|
1847
|
-
const MIN_STEP = config.stepsPerBar / 64;
|
|
1848
|
-
if (physicsLimit < MIN_STEP) {
|
|
1849
|
-
currentCursor = startStep;
|
|
1850
|
-
continue;
|
|
1851
|
-
}
|
|
1852
|
-
const idealDuration = notes[0].durationSteps;
|
|
1853
|
-
const durStr = this.stepsToMMLDuration(idealDuration, physicsLimit);
|
|
1854
|
-
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
1855
|
-
if (notes.length > 1) {
|
|
1856
|
-
const noteStrs = notes.map((n) => {
|
|
1857
|
-
const oct = Math.floor(n.pitch / 12) - 1;
|
|
1858
|
-
const name = PITCH_MAP[n.pitch % 12];
|
|
1859
|
-
return `o${oct}${name}`;
|
|
1860
|
-
});
|
|
1861
|
-
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
1862
|
-
} else {
|
|
1863
|
-
const { text, currentOctave } = this.getNoteWithOctave(
|
|
1864
|
-
notes[0].pitch,
|
|
1865
|
-
lastOctave
|
|
1866
|
-
);
|
|
1867
|
-
segments.push(`${text}${durStr}`);
|
|
1868
|
-
lastOctave = currentOctave;
|
|
1869
|
-
}
|
|
1870
|
-
currentCursor += actualStepGenerated;
|
|
1871
|
-
}
|
|
1872
|
-
while (currentCursor < windowEnd) {
|
|
1873
|
-
const gap = windowEnd - currentCursor;
|
|
1874
|
-
if (gap <= 2) {
|
|
1875
|
-
currentCursor = windowEnd;
|
|
1876
|
-
break;
|
|
1877
|
-
}
|
|
2990
|
+
const endStep = Math.max(
|
|
2991
|
+
...this.notes.map((n) => n.startStep + n.durationSteps)
|
|
2992
|
+
);
|
|
2993
|
+
const notesByStep = /* @__PURE__ */ new Map();
|
|
2994
|
+
for (const n of this.notes) {
|
|
2995
|
+
const list = notesByStep.get(n.startStep) ?? [];
|
|
2996
|
+
list.push(n);
|
|
2997
|
+
notesByStep.set(n.startStep, list);
|
|
2998
|
+
}
|
|
2999
|
+
const sortedSteps = Array.from(notesByStep.keys()).sort((a, b) => a - b);
|
|
3000
|
+
const MIN_STEP = config.stepsPerBar / 64;
|
|
3001
|
+
const fillRests = (until) => {
|
|
3002
|
+
while (until - currentCursor >= MIN_STEP) {
|
|
3003
|
+
const gap = until - currentCursor;
|
|
1878
3004
|
const { dur, steps } = this.findBestFitDuration(gap);
|
|
1879
3005
|
segments.push(`r${dur}`);
|
|
1880
3006
|
currentCursor += steps;
|
|
1881
3007
|
}
|
|
3008
|
+
};
|
|
3009
|
+
for (let i = 0; i < sortedSteps.length; i++) {
|
|
3010
|
+
const startStep = sortedSteps[i];
|
|
3011
|
+
const notes = notesByStep.get(startStep);
|
|
3012
|
+
if (!notes) continue;
|
|
3013
|
+
fillRests(startStep);
|
|
3014
|
+
const nextStart = sortedSteps[i + 1] ?? endStep;
|
|
3015
|
+
const physicsLimit = nextStart - currentCursor;
|
|
3016
|
+
if (physicsLimit < MIN_STEP) {
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
3019
|
+
const idealDuration = notes[0].durationSteps;
|
|
3020
|
+
const durStr = this.stepsToMMLDuration(idealDuration, physicsLimit);
|
|
3021
|
+
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
3022
|
+
if (notes.length > 1) {
|
|
3023
|
+
const noteStrs = notes.map((n) => {
|
|
3024
|
+
const oct = Math.floor(n.pitch / 12) - 1;
|
|
3025
|
+
const name = PITCH_MAP[n.pitch % 12];
|
|
3026
|
+
return `o${oct}${name}`;
|
|
3027
|
+
});
|
|
3028
|
+
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
3029
|
+
} else {
|
|
3030
|
+
const { text, currentOctave } = this.getNoteWithOctave(
|
|
3031
|
+
notes[0].pitch,
|
|
3032
|
+
lastOctave
|
|
3033
|
+
);
|
|
3034
|
+
segments.push(`${text}${durStr}`);
|
|
3035
|
+
lastOctave = currentOctave;
|
|
3036
|
+
}
|
|
3037
|
+
currentCursor += actualStepGenerated;
|
|
1882
3038
|
}
|
|
3039
|
+
fillRests(endStep);
|
|
1883
3040
|
return `${header} ${segments.join(" ")}`;
|
|
1884
3041
|
};
|
|
1885
3042
|
/**
|
|
@@ -1957,13 +3114,51 @@ var PITCH_MAP2 = {
|
|
|
1957
3114
|
a: 9,
|
|
1958
3115
|
b: 11
|
|
1959
3116
|
};
|
|
1960
|
-
var
|
|
3117
|
+
var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
3118
|
+
var META_DIRECTIVE = /#(inst|drum|volume)=([\w-]+)/gi;
|
|
3119
|
+
var parseMmlMeta = (mml) => {
|
|
3120
|
+
const meta = {};
|
|
3121
|
+
for (const m of mml.matchAll(META_DIRECTIVE)) {
|
|
3122
|
+
const key = m[1].toLowerCase();
|
|
3123
|
+
if (key === "inst") meta.instrument = m[2];
|
|
3124
|
+
else if (key === "drum") meta.drum = m[2];
|
|
3125
|
+
else if (key === "volume") {
|
|
3126
|
+
const v = Number.parseInt(m[2], 10);
|
|
3127
|
+
if (!Number.isNaN(v)) meta.volume = v;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return meta;
|
|
3131
|
+
};
|
|
3132
|
+
var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "");
|
|
3133
|
+
var formatMmlMeta = (meta) => {
|
|
3134
|
+
const parts = [];
|
|
3135
|
+
if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
|
|
3136
|
+
if (meta.drum) parts.push(`#drum=${meta.drum}`);
|
|
3137
|
+
if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
|
|
3138
|
+
return parts.join(" ");
|
|
3139
|
+
};
|
|
1961
3140
|
var parseMML = (mml, options = {}) => {
|
|
1962
|
-
const stepsPerBar = options.stepsPerBar ??
|
|
3141
|
+
const stepsPerBar = options.stepsPerBar ?? DEFAULT_STEPS_PER_BAR;
|
|
3142
|
+
const collectTokens = options.collectTokens ?? false;
|
|
3143
|
+
const collectLyrics = options.collectLyrics ?? false;
|
|
3144
|
+
const clampTrackCount = options.clampTrackCount;
|
|
1963
3145
|
const placements = [];
|
|
3146
|
+
const tokenTracks = /* @__PURE__ */ new Map();
|
|
1964
3147
|
let bpm = null;
|
|
1965
|
-
if (!mml)
|
|
1966
|
-
|
|
3148
|
+
if (!mml) {
|
|
3149
|
+
return {
|
|
3150
|
+
placements,
|
|
3151
|
+
bpm,
|
|
3152
|
+
tokenTracks: collectTokens ? tokenTracks : void 0,
|
|
3153
|
+
lyrics: collectLyrics ? /* @__PURE__ */ new Map() : void 0,
|
|
3154
|
+
meta: {}
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
const noComments = mml.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
3158
|
+
const meta = parseMmlMeta(noComments);
|
|
3159
|
+
const noMeta = stripMmlMeta(noComments);
|
|
3160
|
+
const lyrics = collectLyrics ? parseLyrics(noMeta) : void 0;
|
|
3161
|
+
const fullMML = stripLyrics(noMeta).replace(/[\n\r]+/g, " ").trim();
|
|
1967
3162
|
const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
|
|
1968
3163
|
let trackIndex = 0;
|
|
1969
3164
|
let octave = 4;
|
|
@@ -1973,7 +3168,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
1973
3168
|
const part = rawPart.trim();
|
|
1974
3169
|
if (part.startsWith("@")) {
|
|
1975
3170
|
let idx = Number.parseInt(part.substring(1), 10);
|
|
1976
|
-
if (idx >=
|
|
3171
|
+
if (clampTrackCount !== void 0 && idx >= clampTrackCount) idx = 2;
|
|
1977
3172
|
trackIndex = idx;
|
|
1978
3173
|
octave = 4;
|
|
1979
3174
|
currentStep = 0;
|
|
@@ -1982,13 +3177,27 @@ var parseMML = (mml, options = {}) => {
|
|
|
1982
3177
|
}
|
|
1983
3178
|
const body = part.replace(/\s+/g, "").toLowerCase();
|
|
1984
3179
|
let j = 0;
|
|
3180
|
+
const pushTok = (type, start, dur, from) => {
|
|
3181
|
+
if (!collectTokens) return;
|
|
3182
|
+
let arr = tokenTracks.get(trackIndex);
|
|
3183
|
+
if (!arr) {
|
|
3184
|
+
arr = [];
|
|
3185
|
+
tokenTracks.set(trackIndex, arr);
|
|
3186
|
+
}
|
|
3187
|
+
arr.push({
|
|
3188
|
+
text: body.slice(from, j),
|
|
3189
|
+
startStep: start,
|
|
3190
|
+
durationSteps: dur,
|
|
3191
|
+
type
|
|
3192
|
+
});
|
|
3193
|
+
};
|
|
1985
3194
|
const parseLength = () => {
|
|
1986
3195
|
let numStr = "";
|
|
1987
3196
|
while (j < body.length && /\d/.test(body[j])) {
|
|
1988
3197
|
numStr += body[j];
|
|
1989
3198
|
j++;
|
|
1990
3199
|
}
|
|
1991
|
-
const len = numStr ? Number.parseInt(numStr, 10) : baseLength;
|
|
3200
|
+
const len = numStr ? clamp2(Number.parseInt(numStr, 10), 1, 64) : baseLength;
|
|
1992
3201
|
let steps = Math.round(stepsPerBar / len);
|
|
1993
3202
|
while (j < body.length && body[j] === ".") {
|
|
1994
3203
|
steps = Math.round(steps * 1.5);
|
|
@@ -1998,6 +3207,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
1998
3207
|
};
|
|
1999
3208
|
while (j < body.length) {
|
|
2000
3209
|
const ch = body[j];
|
|
3210
|
+
const tokStart = j;
|
|
2001
3211
|
if (ch === "o") {
|
|
2002
3212
|
j++;
|
|
2003
3213
|
let numStr = "";
|
|
@@ -2005,13 +3215,16 @@ var parseMML = (mml, options = {}) => {
|
|
|
2005
3215
|
numStr += body[j];
|
|
2006
3216
|
j++;
|
|
2007
3217
|
}
|
|
2008
|
-
octave = Number.parseInt(numStr, 10)
|
|
3218
|
+
octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
|
|
3219
|
+
pushTok("octave", currentStep, 0, tokStart);
|
|
2009
3220
|
} else if (ch === ">") {
|
|
2010
|
-
octave
|
|
3221
|
+
octave = Math.min(8, octave + 1);
|
|
2011
3222
|
j++;
|
|
3223
|
+
pushTok("shift", currentStep, 0, tokStart);
|
|
2012
3224
|
} else if (ch === "<") {
|
|
2013
|
-
octave
|
|
3225
|
+
octave = Math.max(0, octave - 1);
|
|
2014
3226
|
j++;
|
|
3227
|
+
pushTok("shift", currentStep, 0, tokStart);
|
|
2015
3228
|
} else if (ch === "l") {
|
|
2016
3229
|
j++;
|
|
2017
3230
|
let numStr = "";
|
|
@@ -2019,11 +3232,15 @@ var parseMML = (mml, options = {}) => {
|
|
|
2019
3232
|
numStr += body[j];
|
|
2020
3233
|
j++;
|
|
2021
3234
|
}
|
|
2022
|
-
baseLength = Number.parseInt(numStr, 10) || 16;
|
|
3235
|
+
baseLength = clamp2(Number.parseInt(numStr, 10) || 16, 1, 64);
|
|
3236
|
+
pushTok("length", currentStep, 0, tokStart);
|
|
2023
3237
|
} else if (ch === "r") {
|
|
2024
3238
|
j++;
|
|
2025
|
-
|
|
2026
|
-
|
|
3239
|
+
const restStart = currentStep;
|
|
3240
|
+
const restSteps = parseLength();
|
|
3241
|
+
pushTok("rest", restStart, restSteps, tokStart);
|
|
3242
|
+
currentStep += restSteps;
|
|
3243
|
+
} else if (ch === "t" || ch === "v" || ch === "q" || ch === "p") {
|
|
2027
3244
|
j++;
|
|
2028
3245
|
let numStr = "";
|
|
2029
3246
|
while (j < body.length && /\d/.test(body[j])) {
|
|
@@ -2031,8 +3248,9 @@ var parseMML = (mml, options = {}) => {
|
|
|
2031
3248
|
j++;
|
|
2032
3249
|
}
|
|
2033
3250
|
if (ch === "t" && trackIndex === 0 && numStr) {
|
|
2034
|
-
bpm = Number.parseInt(numStr, 10);
|
|
3251
|
+
bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
|
|
2035
3252
|
}
|
|
3253
|
+
pushTok("ctrl", currentStep, 0, tokStart);
|
|
2036
3254
|
} else if (ch === "[") {
|
|
2037
3255
|
j++;
|
|
2038
3256
|
const chordNotes = [];
|
|
@@ -2051,10 +3269,10 @@ var parseMML = (mml, options = {}) => {
|
|
|
2051
3269
|
}
|
|
2052
3270
|
chordNotes.push((octave + 1) * 12 + pitch);
|
|
2053
3271
|
} else if (c === ">") {
|
|
2054
|
-
octave
|
|
3272
|
+
octave = Math.min(8, octave + 1);
|
|
2055
3273
|
j++;
|
|
2056
3274
|
} else if (c === "<") {
|
|
2057
|
-
octave
|
|
3275
|
+
octave = Math.max(0, octave - 1);
|
|
2058
3276
|
j++;
|
|
2059
3277
|
} else if (c === "o") {
|
|
2060
3278
|
j++;
|
|
@@ -2063,7 +3281,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
2063
3281
|
numStr += body[j];
|
|
2064
3282
|
j++;
|
|
2065
3283
|
}
|
|
2066
|
-
octave = Number.parseInt(numStr, 10)
|
|
3284
|
+
octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
|
|
2067
3285
|
} else {
|
|
2068
3286
|
j++;
|
|
2069
3287
|
}
|
|
@@ -2078,6 +3296,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
2078
3296
|
durationSteps: Math.max(1, steps)
|
|
2079
3297
|
});
|
|
2080
3298
|
}
|
|
3299
|
+
pushTok("chord", currentStep, Math.max(1, steps), tokStart);
|
|
2081
3300
|
currentStep += steps;
|
|
2082
3301
|
octave = savedOctave;
|
|
2083
3302
|
} else if (Object.hasOwn(PITCH_MAP2, ch)) {
|
|
@@ -2098,18 +3317,25 @@ var parseMML = (mml, options = {}) => {
|
|
|
2098
3317
|
pitch: midiPitch,
|
|
2099
3318
|
durationSteps: Math.max(1, steps)
|
|
2100
3319
|
});
|
|
3320
|
+
pushTok("note", currentStep, Math.max(1, steps), tokStart);
|
|
2101
3321
|
currentStep += steps;
|
|
2102
3322
|
} else {
|
|
2103
3323
|
j++;
|
|
2104
3324
|
}
|
|
2105
3325
|
}
|
|
2106
3326
|
}
|
|
2107
|
-
return {
|
|
3327
|
+
return {
|
|
3328
|
+
placements,
|
|
3329
|
+
bpm,
|
|
3330
|
+
tokenTracks: collectTokens ? tokenTracks : void 0,
|
|
3331
|
+
lyrics,
|
|
3332
|
+
meta
|
|
3333
|
+
};
|
|
2108
3334
|
};
|
|
2109
3335
|
|
|
2110
3336
|
// src/sequencer.ts
|
|
2111
3337
|
var STEPS_PER_BEAT2 = 48;
|
|
2112
|
-
var PLAN_TIME = 0.
|
|
3338
|
+
var PLAN_TIME = 0.5;
|
|
2113
3339
|
var TICK_INTERVAL_MS = 20;
|
|
2114
3340
|
var createSequencer = (options) => {
|
|
2115
3341
|
let timeline = [];
|
|
@@ -2119,15 +3345,18 @@ var createSequencer = (options) => {
|
|
|
2119
3345
|
let animationId = null;
|
|
2120
3346
|
let active = false;
|
|
2121
3347
|
let fromStepValue = 0;
|
|
3348
|
+
let trackVolumeMap = /* @__PURE__ */ new Map();
|
|
2122
3349
|
const secondsPerStep = () => 60 / options.getBpm() / STEPS_PER_BEAT2;
|
|
2123
3350
|
const buildTimeline = (fromStep) => {
|
|
2124
3351
|
timeline = [];
|
|
3352
|
+
trackVolumeMap = /* @__PURE__ */ new Map();
|
|
2125
3353
|
const sps = secondsPerStep();
|
|
2126
3354
|
for (const track of options.getTracks()) {
|
|
3355
|
+
trackVolumeMap.set(track.id, track.volume);
|
|
2127
3356
|
for (const note of track.notes) {
|
|
2128
3357
|
const relativeStart = note.startStep - fromStep;
|
|
2129
3358
|
if (relativeStart < 0) continue;
|
|
2130
|
-
const velocity = note.velocity ??
|
|
3359
|
+
const velocity = note.velocity ?? DEFAULT_PLAYBACK_VELOCITY;
|
|
2131
3360
|
timeline.push({
|
|
2132
3361
|
trackId: track.id,
|
|
2133
3362
|
pitch: note.pitch,
|
|
@@ -2144,6 +3373,9 @@ var createSequencer = (options) => {
|
|
|
2144
3373
|
const sps = secondsPerStep();
|
|
2145
3374
|
const time = options.getAudioTime() - startTime;
|
|
2146
3375
|
const soloId = options.getSoloTrackId();
|
|
3376
|
+
for (const track of options.getTracks()) {
|
|
3377
|
+
trackVolumeMap.set(track.id, track.volume);
|
|
3378
|
+
}
|
|
2147
3379
|
while (nowIndex < timeline.length) {
|
|
2148
3380
|
const ev = timeline[nowIndex];
|
|
2149
3381
|
if (soloId && ev.trackId !== soloId) {
|
|
@@ -2154,11 +3386,12 @@ var createSequencer = (options) => {
|
|
|
2154
3386
|
if (_when > PLAN_TIME) break;
|
|
2155
3387
|
nowIndex++;
|
|
2156
3388
|
const velocityVolume = ev.velocity / 127;
|
|
3389
|
+
const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
|
|
2157
3390
|
options.onPlayNote({
|
|
2158
3391
|
trackId: ev.trackId,
|
|
2159
3392
|
pitch: ev.pitch,
|
|
2160
3393
|
velocity: ev.velocity,
|
|
2161
|
-
volume:
|
|
3394
|
+
volume: currentVolume * velocityVolume,
|
|
2162
3395
|
when: Math.max(0, _when),
|
|
2163
3396
|
duration: ev.duration
|
|
2164
3397
|
});
|
|
@@ -2209,13 +3442,14 @@ var createSequencer = (options) => {
|
|
|
2209
3442
|
}
|
|
2210
3443
|
active = false;
|
|
2211
3444
|
};
|
|
3445
|
+
const START_DELAY = 0.1;
|
|
2212
3446
|
const start = (fromStep) => {
|
|
2213
3447
|
stop();
|
|
2214
3448
|
fromStepValue = fromStep ?? options.getPlayStartStep();
|
|
2215
3449
|
buildTimeline(fromStepValue);
|
|
2216
3450
|
if (timeline.length === 0 && !options.getDrumPattern()?.length) return;
|
|
2217
3451
|
active = true;
|
|
2218
|
-
startTime = options.getAudioTime();
|
|
3452
|
+
startTime = options.getAudioTime() + START_DELAY;
|
|
2219
3453
|
nowIndex = 0;
|
|
2220
3454
|
intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
|
|
2221
3455
|
animationId = requestAnimationFrame(animate);
|
|
@@ -2223,7 +3457,8 @@ var createSequencer = (options) => {
|
|
|
2223
3457
|
return {
|
|
2224
3458
|
start,
|
|
2225
3459
|
stop,
|
|
2226
|
-
isActive: () => active
|
|
3460
|
+
isActive: () => active,
|
|
3461
|
+
getStartTime: () => startTime
|
|
2227
3462
|
};
|
|
2228
3463
|
};
|
|
2229
3464
|
|
|
@@ -2685,10 +3920,12 @@ var DAW_CSS = `
|
|
|
2685
3920
|
|
|
2686
3921
|
/* \u2500\u2500\u2500 \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4 \u2500\u2500\u2500 */
|
|
2687
3922
|
.dtm-overlay {
|
|
2688
|
-
position:
|
|
3923
|
+
position: absolute; inset: 0; z-index: 1000;
|
|
2689
3924
|
background: rgba(0,0,0,.92);
|
|
2690
3925
|
display: flex; align-items: center; justify-content: center;
|
|
2691
3926
|
flex-direction: column; gap: 14px;
|
|
3927
|
+
pointer-events: auto;
|
|
3928
|
+
cursor: wait;
|
|
2692
3929
|
}
|
|
2693
3930
|
.dtm-overlay[hidden] { display: none; }
|
|
2694
3931
|
.dtm-overlay::before {
|
|
@@ -2716,18 +3953,206 @@ var DAW_CSS = `
|
|
|
2716
3953
|
animation: dtm-load 1.6s steps(8) infinite;
|
|
2717
3954
|
}
|
|
2718
3955
|
@keyframes dtm-load { 0%{width:0} 100%{width:100%} }
|
|
3956
|
+
/* \u9032\u6357\u304C\u78BA\u5B9A\u3057\u305F\u3089\u7121\u9650\u30EB\u30FC\u30D7\u6F14\u51FA\u3092\u6B62\u3081\u3001\u5B9F\u6E2C\u5024\u3067\u5857\u308A\u3064\u3076\u3059 */
|
|
3957
|
+
.dtm-spinner--determinate::after { display: none; }
|
|
3958
|
+
.dtm-spinner-fill {
|
|
3959
|
+
position: absolute;
|
|
3960
|
+
left: 0; top: 0; height: 100%;
|
|
3961
|
+
width: 0;
|
|
3962
|
+
background: var(--dtm-primary);
|
|
3963
|
+
transition: width .12s steps(8);
|
|
3964
|
+
}
|
|
3965
|
+
.dtm-loading-label {
|
|
3966
|
+
font-family: var(--dtm-font);
|
|
3967
|
+
font-size: 11px;
|
|
3968
|
+
color: var(--dtm-primary);
|
|
3969
|
+
letter-spacing: .15em;
|
|
3970
|
+
min-height: 1em;
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
@keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
|
3974
|
+
.dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
|
|
3975
|
+
|
|
3976
|
+
.dtm-hidden { display: none !important; }
|
|
3977
|
+
.dtm-grow { flex: 1 1 auto; }
|
|
3978
|
+
.dtm-lyric-icon {
|
|
3979
|
+
flex: 0 0 auto;
|
|
3980
|
+
width: 20px;
|
|
3981
|
+
height: 20px;
|
|
3982
|
+
border-radius: 50%;
|
|
3983
|
+
object-fit: cover;
|
|
3984
|
+
}
|
|
2719
3985
|
|
|
2720
|
-
@keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
|
2721
|
-
.dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
|
|
2722
|
-
|
|
2723
|
-
.dtm-hidden { display: none !important; }
|
|
2724
|
-
.dtm-grow { flex: 1 1 auto; }
|
|
2725
|
-
|
|
2726
3986
|
/* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */
|
|
2727
3987
|
@media (min-width: 768px) {
|
|
2728
3988
|
.dtm-daw { gap: 8px; padding: 10px; }
|
|
2729
3989
|
.dtm-roll { height: 420px; }
|
|
2730
3990
|
}
|
|
3991
|
+
|
|
3992
|
+
/* ====================================================
|
|
3993
|
+
MML PLAYER \u2014 \u518D\u751F\u5C02\u7528\u30D3\u30E5\u30FC\uFF08mountMmlPlayer\uFF09
|
|
3994
|
+
==================================================== */
|
|
3995
|
+
.dtm-player {
|
|
3996
|
+
display: flex;
|
|
3997
|
+
flex-direction: column;
|
|
3998
|
+
gap: var(--dtm-gap);
|
|
3999
|
+
padding: var(--dtm-gap);
|
|
4000
|
+
background: var(--dtm-deep);
|
|
4001
|
+
border: 2px solid var(--dtm-border2);
|
|
4002
|
+
box-shadow: 4px 4px 0 var(--c-black);
|
|
4003
|
+
}
|
|
4004
|
+
.dtm-player-head {
|
|
4005
|
+
display: flex;
|
|
4006
|
+
align-items: center;
|
|
4007
|
+
gap: 8px;
|
|
4008
|
+
flex-wrap: wrap;
|
|
4009
|
+
}
|
|
4010
|
+
.dtm-player-play {
|
|
4011
|
+
flex: 0 0 auto;
|
|
4012
|
+
width: 28px;
|
|
4013
|
+
height: 28px;
|
|
4014
|
+
display: flex;
|
|
4015
|
+
align-items: center;
|
|
4016
|
+
justify-content: center;
|
|
4017
|
+
background: var(--dtm-primary);
|
|
4018
|
+
color: var(--dtm-pfg);
|
|
4019
|
+
border: 2px solid var(--c-black);
|
|
4020
|
+
box-shadow: 2px 2px 0 var(--c-black);
|
|
4021
|
+
cursor: pointer;
|
|
4022
|
+
padding: 0;
|
|
4023
|
+
}
|
|
4024
|
+
.dtm-player-play:active { transform: translate(2px, 2px); box-shadow: none; }
|
|
4025
|
+
.dtm-player-play--stop { background: var(--dtm-danger); }
|
|
4026
|
+
.dtm-player-play:disabled { opacity: 0.4; cursor: default; }
|
|
4027
|
+
.dtm-player-beat-row {
|
|
4028
|
+
display: flex;
|
|
4029
|
+
align-items: center;
|
|
4030
|
+
gap: 4px;
|
|
4031
|
+
}
|
|
4032
|
+
.dtm-player-beat-dot {
|
|
4033
|
+
width: 8px;
|
|
4034
|
+
height: 8px;
|
|
4035
|
+
border-radius: 50%;
|
|
4036
|
+
background: var(--dtm-border2);
|
|
4037
|
+
transition: background 0.06s;
|
|
4038
|
+
}
|
|
4039
|
+
.dtm-player-beat-dot--on { background: var(--dtm-primary); }
|
|
4040
|
+
.dtm-player-bar {
|
|
4041
|
+
font-family: 'k8x12', monospace;
|
|
4042
|
+
font-size: 11px;
|
|
4043
|
+
color: var(--dtm-text);
|
|
4044
|
+
min-width: 2em;
|
|
4045
|
+
margin-left: 4px;
|
|
4046
|
+
}
|
|
4047
|
+
.dtm-player-dots {
|
|
4048
|
+
margin-left: auto;
|
|
4049
|
+
display: flex;
|
|
4050
|
+
align-items: center;
|
|
4051
|
+
gap: 6px;
|
|
4052
|
+
}
|
|
4053
|
+
.dtm-player-dot { width: 8px; height: 8px; display: inline-block; }
|
|
4054
|
+
.dtm-player-mml-header {
|
|
4055
|
+
margin-left: auto;
|
|
4056
|
+
display: flex;
|
|
4057
|
+
align-items: center;
|
|
4058
|
+
gap: 4px;
|
|
4059
|
+
}
|
|
4060
|
+
.dtm-player-mml-link {
|
|
4061
|
+
font-family: 'k8x12', monospace;
|
|
4062
|
+
font-size: 10px;
|
|
4063
|
+
color: var(--dtm-muted);
|
|
4064
|
+
text-decoration: none;
|
|
4065
|
+
white-space: nowrap;
|
|
4066
|
+
}
|
|
4067
|
+
.dtm-player-mml-link:hover { color: var(--dtm-primary); }
|
|
4068
|
+
.dtm-player-emoji {
|
|
4069
|
+
display: inline-flex;
|
|
4070
|
+
align-items: center;
|
|
4071
|
+
justify-content: center;
|
|
4072
|
+
width: 20px;
|
|
4073
|
+
height: 20px;
|
|
4074
|
+
border-radius: 50%;
|
|
4075
|
+
font-size: 18px;
|
|
4076
|
+
line-height: 1;
|
|
4077
|
+
user-select: none;
|
|
4078
|
+
}
|
|
4079
|
+
@keyframes dtm-emoji-jump {
|
|
4080
|
+
0% { transform: translateY(0); }
|
|
4081
|
+
35% { transform: translateY(-5px); }
|
|
4082
|
+
65% { transform: translateY(-5px); }
|
|
4083
|
+
100% { transform: translateY(0); }
|
|
4084
|
+
}
|
|
4085
|
+
.dtm-player-emoji--jump {
|
|
4086
|
+
animation: dtm-emoji-jump 0.18s ease-out forwards;
|
|
4087
|
+
}
|
|
4088
|
+
.dtm-player-chip {
|
|
4089
|
+
font-family: 'k8x12', monospace;
|
|
4090
|
+
font-size: 9px;
|
|
4091
|
+
color: var(--dtm-text);
|
|
4092
|
+
background: var(--dtm-border2);
|
|
4093
|
+
padding: 2px 6px;
|
|
4094
|
+
white-space: nowrap;
|
|
4095
|
+
}
|
|
4096
|
+
.dtm-player-body {
|
|
4097
|
+
position: relative; /* \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4\u306E\u57FA\u6E96\u3002\u30EC\u30FC\u30F3\u7FA4\u3060\u3051\u3092\u8986\u3046 */
|
|
4098
|
+
display: flex;
|
|
4099
|
+
flex-direction: column;
|
|
4100
|
+
gap: var(--dtm-gap);
|
|
4101
|
+
}
|
|
4102
|
+
.dtm-player-lane-row {
|
|
4103
|
+
display: flex;
|
|
4104
|
+
align-items: stretch;
|
|
4105
|
+
gap: 6px;
|
|
4106
|
+
}
|
|
4107
|
+
.dtm-player-lane-label {
|
|
4108
|
+
flex: 0 0 auto;
|
|
4109
|
+
width: 16px;
|
|
4110
|
+
display: flex;
|
|
4111
|
+
flex-direction: column;
|
|
4112
|
+
align-items: center;
|
|
4113
|
+
gap: 2px;
|
|
4114
|
+
padding-top: 4px;
|
|
4115
|
+
}
|
|
4116
|
+
.dtm-player-lane-label--btn {
|
|
4117
|
+
cursor: pointer;
|
|
4118
|
+
user-select: none;
|
|
4119
|
+
}
|
|
4120
|
+
.dtm-player-lane-label--btn:hover { opacity: 0.7; }
|
|
4121
|
+
.dtm-player-lane-label--muted { opacity: 0.3; }
|
|
4122
|
+
.dtm-player-lane-no {
|
|
4123
|
+
font-family: 'k8x12', monospace;
|
|
4124
|
+
font-size: 9px;
|
|
4125
|
+
color: var(--dtm-muted);
|
|
4126
|
+
}
|
|
4127
|
+
.dtm-player-lane {
|
|
4128
|
+
position: relative; /* \u30C8\u30FC\u30AF\u30F3\u306E offsetParent \u3092\u30EC\u30FC\u30F3\u306B\u56FA\u5B9A\u3057\u3001\u4E2D\u592E\u5BC4\u305B\u8A08\u7B97\u3092\u6B63\u3059 */
|
|
4129
|
+
flex: 1 1 auto;
|
|
4130
|
+
overflow-x: auto;
|
|
4131
|
+
white-space: nowrap;
|
|
4132
|
+
background: var(--c-black);
|
|
4133
|
+
border: 2px solid var(--dtm-border2);
|
|
4134
|
+
padding: 6px;
|
|
4135
|
+
scrollbar-width: none;
|
|
4136
|
+
}
|
|
4137
|
+
.dtm-player-lane::-webkit-scrollbar { display: none; }
|
|
4138
|
+
.dtm-tk {
|
|
4139
|
+
font-family: 'k8x12', monospace;
|
|
4140
|
+
font-size: 12px;
|
|
4141
|
+
color: var(--dtm-text);
|
|
4142
|
+
}
|
|
4143
|
+
.dtm-tk--rest { color: var(--dtm-muted); }
|
|
4144
|
+
.dtm-tk--octave,
|
|
4145
|
+
.dtm-tk--shift,
|
|
4146
|
+
.dtm-tk--length,
|
|
4147
|
+
.dtm-tk--ctrl { color: var(--dtm-border2); }
|
|
4148
|
+
.dtm-tk--lyric { color: var(--dtm-text); letter-spacing: 1px; }
|
|
4149
|
+
.dtm-tk--break { color: var(--dtm-muted); opacity: 0.7; margin: 0 2px; }
|
|
4150
|
+
.dtm-tk--meta { color: var(--dtm-border2); margin-right: 4px; }
|
|
4151
|
+
.dtm-tk.is-active {
|
|
4152
|
+
background: var(--tk, var(--dtm-primary));
|
|
4153
|
+
color: var(--c-black);
|
|
4154
|
+
font-weight: bold;
|
|
4155
|
+
}
|
|
2731
4156
|
`;
|
|
2732
4157
|
var injectStyles = (doc = document) => {
|
|
2733
4158
|
if (doc.getElementById(STYLE_ID)) return;
|
|
@@ -2736,6 +4161,77 @@ var injectStyles = (doc = document) => {
|
|
|
2736
4161
|
style.textContent = DAW_CSS;
|
|
2737
4162
|
doc.head.appendChild(style);
|
|
2738
4163
|
};
|
|
4164
|
+
var showLoadingOverlay = (container) => {
|
|
4165
|
+
const origPos = container.style.position;
|
|
4166
|
+
const computed = window.getComputedStyle(container).position;
|
|
4167
|
+
if (computed === "static") {
|
|
4168
|
+
container.style.position = "relative";
|
|
4169
|
+
}
|
|
4170
|
+
const doc = container.ownerDocument ?? document;
|
|
4171
|
+
const overlay = doc.createElement("div");
|
|
4172
|
+
overlay.className = "dtm-overlay";
|
|
4173
|
+
const spinner = doc.createElement("div");
|
|
4174
|
+
spinner.className = "dtm-spinner";
|
|
4175
|
+
const fill = doc.createElement("i");
|
|
4176
|
+
fill.className = "dtm-spinner-fill";
|
|
4177
|
+
spinner.appendChild(fill);
|
|
4178
|
+
overlay.appendChild(spinner);
|
|
4179
|
+
const label = doc.createElement("div");
|
|
4180
|
+
label.className = "dtm-loading-label";
|
|
4181
|
+
overlay.appendChild(label);
|
|
4182
|
+
container.appendChild(overlay);
|
|
4183
|
+
const setProgress = (done, total) => {
|
|
4184
|
+
if (total > 0) {
|
|
4185
|
+
const pct = Math.max(0, Math.min(100, Math.round(done / total * 100)));
|
|
4186
|
+
spinner.classList.add("dtm-spinner--determinate");
|
|
4187
|
+
fill.style.width = `${pct}%`;
|
|
4188
|
+
label.textContent = `${done} / ${total} (${pct}%)`;
|
|
4189
|
+
} else {
|
|
4190
|
+
spinner.classList.remove("dtm-spinner--determinate");
|
|
4191
|
+
fill.style.width = "0";
|
|
4192
|
+
label.textContent = "";
|
|
4193
|
+
}
|
|
4194
|
+
};
|
|
4195
|
+
return {
|
|
4196
|
+
remove: () => {
|
|
4197
|
+
overlay.remove();
|
|
4198
|
+
container.style.position = origPos;
|
|
4199
|
+
},
|
|
4200
|
+
setProgress
|
|
4201
|
+
};
|
|
4202
|
+
};
|
|
4203
|
+
|
|
4204
|
+
// assets/puyuyu.png
|
|
4205
|
+
var puyuyu_default = "data:image/png;base64,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";
|
|
4206
|
+
|
|
4207
|
+
// assets/rino.png
|
|
4208
|
+
var rino_default = "data:image/png;base64,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";
|
|
4209
|
+
|
|
4210
|
+
// assets/roze.png
|
|
4211
|
+
var roze_default = "data:image/png;base64,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";
|
|
4212
|
+
|
|
4213
|
+
// assets/ruko.png
|
|
4214
|
+
var ruko_default = "data:image/png;base64,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";
|
|
4215
|
+
|
|
4216
|
+
// assets/shiyo.png
|
|
4217
|
+
var shiyo_default = "data:image/png;base64,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";
|
|
4218
|
+
|
|
4219
|
+
// assets/teto.png
|
|
4220
|
+
var teto_default = "data:image/png;base64,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";
|
|
4221
|
+
|
|
4222
|
+
// assets/tsukuyomi.png
|
|
4223
|
+
var tsukuyomi_default = "data:image/png;base64,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";
|
|
4224
|
+
|
|
4225
|
+
// src/voice-images.ts
|
|
4226
|
+
var VOICE_IMAGES = {
|
|
4227
|
+
puyuyu: puyuyu_default,
|
|
4228
|
+
rino: rino_default,
|
|
4229
|
+
roze: roze_default,
|
|
4230
|
+
ruko: ruko_default,
|
|
4231
|
+
shiyo: shiyo_default,
|
|
4232
|
+
teto: teto_default,
|
|
4233
|
+
tsukuyomi: tsukuyomi_default
|
|
4234
|
+
};
|
|
2739
4235
|
|
|
2740
4236
|
// src/daw.ts
|
|
2741
4237
|
var BASE_STEP_WIDTH = 0.5;
|
|
@@ -2885,7 +4381,13 @@ var TRACKS_ADVANCED = [
|
|
|
2885
4381
|
}
|
|
2886
4382
|
];
|
|
2887
4383
|
var DEFAULT_TRACKS = TRACKS_SIMPLE;
|
|
2888
|
-
var
|
|
4384
|
+
var LYRIC_MODELS = ["klatt", ...Object.keys(KOE_VOICEBANKS)];
|
|
4385
|
+
var LYRIC_MODEL_LABELS = {
|
|
4386
|
+
klatt: "\u8EFD\u91CF\u30ED\u30DC\u58F0",
|
|
4387
|
+
...KOE_VOICEBANK_LABELS
|
|
4388
|
+
};
|
|
4389
|
+
var lyricModelLabel = (model) => LYRIC_MODEL_LABELS[model] ?? model;
|
|
4390
|
+
var clamp3 = (v, min, max) => Math.min(Math.max(v, min), max);
|
|
2889
4391
|
var mountDAW = (target, options = {}) => {
|
|
2890
4392
|
injectStyles();
|
|
2891
4393
|
const getAudioTime = options.getAudioTime ?? (() => performance.now() / 1e3);
|
|
@@ -2897,7 +4399,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
2897
4399
|
tracks: trackConfigs,
|
|
2898
4400
|
drumPatternNames: Object.keys(drumPatterns),
|
|
2899
4401
|
defaultDrumPattern: drumPatterns.dance ? "dance" : Object.keys(drumPatterns)[0] ?? "none",
|
|
2900
|
-
defaultBpm: options.defaultBpm ??
|
|
4402
|
+
defaultBpm: options.defaultBpm ?? DEFAULT_BPM,
|
|
2901
4403
|
showMidi,
|
|
2902
4404
|
showChord
|
|
2903
4405
|
});
|
|
@@ -2912,10 +4414,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
2912
4414
|
const leftPaddingSteps = renderConfig.stepsPerBar * 16;
|
|
2913
4415
|
let zoomX = 100;
|
|
2914
4416
|
let zoomY = 100;
|
|
2915
|
-
let bpm = options.defaultBpm ??
|
|
4417
|
+
let bpm = options.defaultBpm ?? DEFAULT_BPM;
|
|
2916
4418
|
let masterVolume = 50;
|
|
2917
4419
|
let drumVolume = 80;
|
|
2918
4420
|
let currentDrumPattern = refs.drumSelect.value;
|
|
4421
|
+
let currentInstrument = "";
|
|
2919
4422
|
let activeTrackId = trackConfigs[0].id;
|
|
2920
4423
|
let activeToolMode = "pen";
|
|
2921
4424
|
let currentInsertLength = 48;
|
|
@@ -2925,6 +4428,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
2925
4428
|
let currentOffsetY = (104 - 1 - 60) * renderConfig.keyHeight - 215;
|
|
2926
4429
|
let playStartStep = 0;
|
|
2927
4430
|
let isSolo = false;
|
|
4431
|
+
let lyricTrackIndices = /* @__PURE__ */ new Set();
|
|
2928
4432
|
let playbackState = "stopped";
|
|
2929
4433
|
let pausedPlayStep = 0;
|
|
2930
4434
|
let currentPlayStep = 0;
|
|
@@ -2951,9 +4455,36 @@ var mountDAW = (target, options = {}) => {
|
|
|
2951
4455
|
volume: config.volume,
|
|
2952
4456
|
savedChordInput: "",
|
|
2953
4457
|
savedChordPattern: "block",
|
|
2954
|
-
savedChordRoot: 0
|
|
4458
|
+
savedChordRoot: 0,
|
|
4459
|
+
lyrics: "",
|
|
4460
|
+
lyricModel: "",
|
|
4461
|
+
// 既定は「なし」(歌わない)
|
|
4462
|
+
vocalVolume: DEFAULT_VOCAL_VOLUME,
|
|
4463
|
+
vocalGate: 100,
|
|
4464
|
+
vocalPan: 64,
|
|
4465
|
+
vocalOctave: 0
|
|
2955
4466
|
}));
|
|
2956
4467
|
};
|
|
4468
|
+
const buildLyricsMap = () => {
|
|
4469
|
+
const map = /* @__PURE__ */ new Map();
|
|
4470
|
+
trackStates.forEach((t, i) => {
|
|
4471
|
+
const model = t.lyricModel.trim();
|
|
4472
|
+
const text = t.lyrics.trim();
|
|
4473
|
+
if (!model || !text) return;
|
|
4474
|
+
const syllables = normalizeLyrics(text);
|
|
4475
|
+
if (syllables.length === 0) return;
|
|
4476
|
+
map.set(i, {
|
|
4477
|
+
trackId: i,
|
|
4478
|
+
model: model.toLowerCase(),
|
|
4479
|
+
volume: t.vocalVolume,
|
|
4480
|
+
gate: t.vocalGate,
|
|
4481
|
+
pan: t.vocalPan,
|
|
4482
|
+
octave: t.vocalOctave,
|
|
4483
|
+
syllables
|
|
4484
|
+
});
|
|
4485
|
+
});
|
|
4486
|
+
return map;
|
|
4487
|
+
};
|
|
2957
4488
|
const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
|
|
2958
4489
|
const getMaxNoteStep = () => {
|
|
2959
4490
|
let maxStep = renderConfig.stepsPerBar * 4;
|
|
@@ -3057,7 +4588,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
3057
4588
|
);
|
|
3058
4589
|
const ratio = currentOffsetX / maxOffsetX;
|
|
3059
4590
|
refs.hScrollThumb.style.width = `${thumbW}px`;
|
|
3060
|
-
refs.hScrollThumb.style.left = `${
|
|
4591
|
+
refs.hScrollThumb.style.left = `${clamp3(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
|
|
3061
4592
|
}
|
|
3062
4593
|
const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
|
|
3063
4594
|
const sbH = refs.vScroll.clientHeight;
|
|
@@ -3116,9 +4647,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
3116
4647
|
if (maxOffsetX <= 0) return;
|
|
3117
4648
|
const rect = refs.hScroll.getBoundingClientRect();
|
|
3118
4649
|
const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
|
|
3119
|
-
const x =
|
|
4650
|
+
const x = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
|
|
3120
4651
|
const ratio = x / (rect.width - thumbW);
|
|
3121
|
-
currentOffsetX =
|
|
4652
|
+
currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
|
|
3122
4653
|
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
3123
4654
|
redrawAll();
|
|
3124
4655
|
};
|
|
@@ -3127,9 +4658,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
3127
4658
|
if (maxOffset <= 0) return;
|
|
3128
4659
|
const rect = refs.vScroll.getBoundingClientRect();
|
|
3129
4660
|
const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
|
|
3130
|
-
const y =
|
|
4661
|
+
const y = clamp3(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
|
|
3131
4662
|
const ratio = y / (rect.height - thumbH);
|
|
3132
|
-
currentOffsetY =
|
|
4663
|
+
currentOffsetY = clamp3(ratio * maxOffset, 0, maxOffset);
|
|
3133
4664
|
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
3134
4665
|
redrawAll();
|
|
3135
4666
|
};
|
|
@@ -3411,7 +4942,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
3411
4942
|
"wheel",
|
|
3412
4943
|
(event) => {
|
|
3413
4944
|
event.preventDefault();
|
|
3414
|
-
currentOffsetY =
|
|
4945
|
+
currentOffsetY = clamp3(
|
|
3415
4946
|
currentOffsetY + event.deltaY,
|
|
3416
4947
|
0,
|
|
3417
4948
|
getMaxOffsetY()
|
|
@@ -3459,7 +4990,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
3459
4990
|
const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
|
|
3460
4991
|
renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
|
|
3461
4992
|
refs.zoomYLabel.textContent = `${zoomY}%`;
|
|
3462
|
-
currentOffsetY =
|
|
4993
|
+
currentOffsetY = clamp3(
|
|
3463
4994
|
centerKey * renderConfig.keyHeight - canvas.height / 2,
|
|
3464
4995
|
0,
|
|
3465
4996
|
getMaxOffsetY()
|
|
@@ -3467,6 +4998,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
3467
4998
|
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
3468
4999
|
redrawAll();
|
|
3469
5000
|
};
|
|
5001
|
+
const getViewState = () => ({
|
|
5002
|
+
zoomX,
|
|
5003
|
+
zoomY,
|
|
5004
|
+
decomposeChord: refs.decomposeChordToggle.checked,
|
|
5005
|
+
ignoreChordHeavy: refs.ignoreChordHeavyToggle.checked
|
|
5006
|
+
});
|
|
5007
|
+
const notifyViewState = () => options.onViewStateChange?.(getViewState());
|
|
3470
5008
|
const dispatchNote = (trackId, pitch, trackVol, velocity, when, duration) => {
|
|
3471
5009
|
const volume = trackVol / 100 * (velocity / 127) * (masterVolume / 100);
|
|
3472
5010
|
options.onPlayNote?.({ trackId, pitch, velocity, volume, when, duration });
|
|
@@ -3483,8 +5021,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
3483
5021
|
getSoloTrackId: () => isSolo ? activeTrackId : null,
|
|
3484
5022
|
getAudioTime,
|
|
3485
5023
|
onPlayNote: (e) => {
|
|
3486
|
-
const
|
|
3487
|
-
|
|
5024
|
+
const idx = trackStates.findIndex((t) => t.config.id === e.trackId);
|
|
5025
|
+
if (idx >= 0 && lyricTrackIndices.has(idx) && options.singingVoices) {
|
|
5026
|
+
return;
|
|
5027
|
+
}
|
|
5028
|
+
options.onPlayNote?.({ ...e, volume: e.volume * (masterVolume / 100) });
|
|
3488
5029
|
},
|
|
3489
5030
|
onPlayDrum: (e) => {
|
|
3490
5031
|
const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
|
|
@@ -3510,10 +5051,53 @@ var mountDAW = (target, options = {}) => {
|
|
|
3510
5051
|
},
|
|
3511
5052
|
stepsPerBar: renderConfig.stepsPerBar
|
|
3512
5053
|
});
|
|
3513
|
-
const play = () => {
|
|
5054
|
+
const play = async () => {
|
|
3514
5055
|
options.onResumeAudio?.();
|
|
3515
5056
|
if (playbackState === "playing") return;
|
|
3516
5057
|
const fromStep = playbackState === "paused" ? pausedPlayStep : playStartStep;
|
|
5058
|
+
options.singingVoices?.reset();
|
|
5059
|
+
const lyricMap = buildLyricsMap();
|
|
5060
|
+
lyricTrackIndices = new Set(lyricMap.keys());
|
|
5061
|
+
const secondsPerStep = 60 / bpm / 48;
|
|
5062
|
+
const streamTracks = options.singingVoices ? [...lyricMap.values()].map((lt) => {
|
|
5063
|
+
const trackState = trackStates[lt.trackId];
|
|
5064
|
+
const sorted = [...trackState?.core.getNotes() ?? []].sort(
|
|
5065
|
+
(a, b) => a.startStep - b.startStep
|
|
5066
|
+
);
|
|
5067
|
+
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
5068
|
+
const semis = (lt.octave ?? 0) * 12;
|
|
5069
|
+
const count = Math.min(sorted.length, lt.syllables.length);
|
|
5070
|
+
const notes = [];
|
|
5071
|
+
for (let i = 0; i < count; i++) {
|
|
5072
|
+
const n = sorted[i];
|
|
5073
|
+
if (n.startStep < fromStep) continue;
|
|
5074
|
+
notes.push({
|
|
5075
|
+
syllable: lt.syllables[i],
|
|
5076
|
+
pitch: n.pitch + semis,
|
|
5077
|
+
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
5078
|
+
durationSec: n.durationSteps * secondsPerStep * gate
|
|
5079
|
+
});
|
|
5080
|
+
}
|
|
5081
|
+
return {
|
|
5082
|
+
model: lt.model,
|
|
5083
|
+
volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (masterVolume / 100),
|
|
5084
|
+
pan: panToStereo(lt.pan ?? DEFAULT_PAN),
|
|
5085
|
+
notes
|
|
5086
|
+
};
|
|
5087
|
+
}) : [];
|
|
5088
|
+
const voices = options.singingVoices;
|
|
5089
|
+
const streaming = !!voices && streamTracks.some((t) => t.notes.length > 0);
|
|
5090
|
+
if (streaming && voices) {
|
|
5091
|
+
const overlay = showLoadingOverlay(refs.rollContainer);
|
|
5092
|
+
try {
|
|
5093
|
+
await voices.loadModels(streamTracks.map((t) => t.model));
|
|
5094
|
+
await voices.warm(streamTracks);
|
|
5095
|
+
} catch (err) {
|
|
5096
|
+
console.warn("[dtm] voice preload failed", err);
|
|
5097
|
+
} finally {
|
|
5098
|
+
overlay.remove();
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
3517
5101
|
if (playbackState !== "paused") {
|
|
3518
5102
|
const canvas = getGridCanvas();
|
|
3519
5103
|
currentOffsetX = Math.max(
|
|
@@ -3524,17 +5108,22 @@ var mountDAW = (target, options = {}) => {
|
|
|
3524
5108
|
}
|
|
3525
5109
|
playbackState = "playing";
|
|
3526
5110
|
sequencer.start(fromStep);
|
|
5111
|
+
if (streaming && voices) {
|
|
5112
|
+
voices.startStream(streamTracks, sequencer.getStartTime());
|
|
5113
|
+
}
|
|
3527
5114
|
updateTransport();
|
|
3528
5115
|
};
|
|
3529
5116
|
const pause = () => {
|
|
3530
5117
|
if (playbackState !== "playing") return;
|
|
3531
5118
|
pausedPlayStep = currentPlayStep;
|
|
3532
5119
|
sequencer.stop();
|
|
5120
|
+
options.singingVoices?.stopStream();
|
|
3533
5121
|
playbackState = "paused";
|
|
3534
5122
|
updateTransport();
|
|
3535
5123
|
};
|
|
3536
5124
|
const stop = () => {
|
|
3537
5125
|
sequencer.stop();
|
|
5126
|
+
options.singingVoices?.stopStream();
|
|
3538
5127
|
playbackState = "stopped";
|
|
3539
5128
|
currentPlayStep = 0;
|
|
3540
5129
|
updateTransport();
|
|
@@ -3584,6 +5173,158 @@ var mountDAW = (target, options = {}) => {
|
|
|
3584
5173
|
active.core.setVolume(active.volume);
|
|
3585
5174
|
volLabel.textContent = String(active.volume);
|
|
3586
5175
|
});
|
|
5176
|
+
const lyricDiv = document.createElement("div");
|
|
5177
|
+
lyricDiv.className = "dtm-row";
|
|
5178
|
+
lyricDiv.style.flexDirection = "column";
|
|
5179
|
+
lyricDiv.style.alignItems = "stretch";
|
|
5180
|
+
lyricDiv.innerHTML = `
|
|
5181
|
+
<div class="dtm-row">
|
|
5182
|
+
<span class="dtm-label">\u266A \u6B4C\u8A5E</span>
|
|
5183
|
+
<select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
|
|
5184
|
+
<img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
|
|
5185
|
+
<select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
|
|
5186
|
+
<option value="2">+2 oct</option>
|
|
5187
|
+
<option value="1">+1 oct</option>
|
|
5188
|
+
<option value="0">\xB10 oct</option>
|
|
5189
|
+
<option value="-1">-1 oct</option>
|
|
5190
|
+
<option value="-2">-2 oct</option>
|
|
5191
|
+
</select>
|
|
5192
|
+
<span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
|
|
5193
|
+
</div>
|
|
5194
|
+
<div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
|
|
5195
|
+
<span>\u4F7F\u7528\u6642\u306B\u306F</span>
|
|
5196
|
+
<a data-dtm="lyric-terms-link" target="_blank" rel="noopener" style="color:var(--dtm-primary);text-decoration:underline"></a>
|
|
5197
|
+
<span>\u306E\u5229\u7528\u898F\u7D04\u306B\u5F93\u3063\u3066\u304F\u3060\u3055\u3044</span>
|
|
5198
|
+
</div>
|
|
5199
|
+
<div class="dtm-row" data-dtm="lyric-body" style="flex-direction:column;align-items:stretch">
|
|
5200
|
+
<div class="dtm-row">
|
|
5201
|
+
<span class="dtm-label">\u58F0\u91CF</span>
|
|
5202
|
+
<input type="range" class="dtm-range dtm-grow" data-dtm="lyric-vol" min="0" max="${MAX_VOCAL_VOLUME}" aria-label="\u6B4C\u5531\u306E\u58F0\u91CF\uFF08100=\u7B49\u500D\u3001100\u8D85\u3067\u30D6\u30FC\u30B9\u30C8\u3001\u65E2\u5B9A200\uFF09">
|
|
5203
|
+
<span class="dtm-label" data-dtm="lyric-vol-label"></span>
|
|
5204
|
+
</div>
|
|
5205
|
+
<div class="dtm-row">
|
|
5206
|
+
<span class="dtm-label">\u5B9A\u4F4D</span>
|
|
5207
|
+
<input type="range" class="dtm-range dtm-grow" data-dtm="lyric-pan" min="0" max="127" aria-label="\u6B4C\u5531\u306E\u30B9\u30C6\u30EC\u30AA\u5B9A\u4F4D\uFF08\u5DE6\u53F3\uFF09">
|
|
5208
|
+
<span class="dtm-label" data-dtm="lyric-pan-label"></span>
|
|
5209
|
+
</div>
|
|
5210
|
+
<textarea class="dtm-textarea" data-dtm="lyric-input" rows="2" placeholder="\u3072\u3089\u304C\u306A\u30FB\u30AB\u30BF\u30AB\u30CA\u3067\u6B4C\u8A5E\uFF08\u4F8B: \u3069\u308C\u307F\u3075\u3041\u305D\u3089\u3057\u3069\uFF09"></textarea>
|
|
5211
|
+
</div>`;
|
|
5212
|
+
refs.trackBody.appendChild(lyricDiv);
|
|
5213
|
+
const lyricModelSel = lyricDiv.querySelector(
|
|
5214
|
+
'[data-dtm="lyric-model"]'
|
|
5215
|
+
);
|
|
5216
|
+
const lyricOctaveSel = lyricDiv.querySelector(
|
|
5217
|
+
'[data-dtm="lyric-octave"]'
|
|
5218
|
+
);
|
|
5219
|
+
const lyricIcon = lyricDiv.querySelector(
|
|
5220
|
+
'[data-dtm="lyric-icon"]'
|
|
5221
|
+
);
|
|
5222
|
+
const lyricBody = lyricDiv.querySelector(
|
|
5223
|
+
'[data-dtm="lyric-body"]'
|
|
5224
|
+
);
|
|
5225
|
+
const lyricInput = lyricDiv.querySelector(
|
|
5226
|
+
'[data-dtm="lyric-input"]'
|
|
5227
|
+
);
|
|
5228
|
+
const lyricCount = lyricDiv.querySelector(
|
|
5229
|
+
'[data-dtm="lyric-count"]'
|
|
5230
|
+
);
|
|
5231
|
+
const lyricVol = lyricDiv.querySelector(
|
|
5232
|
+
'[data-dtm="lyric-vol"]'
|
|
5233
|
+
);
|
|
5234
|
+
const lyricVolLabel = lyricDiv.querySelector(
|
|
5235
|
+
'[data-dtm="lyric-vol-label"]'
|
|
5236
|
+
);
|
|
5237
|
+
const lyricPan = lyricDiv.querySelector(
|
|
5238
|
+
'[data-dtm="lyric-pan"]'
|
|
5239
|
+
);
|
|
5240
|
+
const lyricPanLabel = lyricDiv.querySelector(
|
|
5241
|
+
'[data-dtm="lyric-pan-label"]'
|
|
5242
|
+
);
|
|
5243
|
+
const lyricTerms = lyricDiv.querySelector(
|
|
5244
|
+
'[data-dtm="lyric-terms"]'
|
|
5245
|
+
);
|
|
5246
|
+
const lyricTermsLink = lyricDiv.querySelector(
|
|
5247
|
+
'[data-dtm="lyric-terms-link"]'
|
|
5248
|
+
);
|
|
5249
|
+
const fmtPan = (pan) => pan === 64 ? "C" : pan < 64 ? `L${64 - pan}` : `R${pan - 64}`;
|
|
5250
|
+
const addOpt = (value, label) => {
|
|
5251
|
+
const o = document.createElement("option");
|
|
5252
|
+
o.value = value;
|
|
5253
|
+
o.textContent = label;
|
|
5254
|
+
lyricModelSel.appendChild(o);
|
|
5255
|
+
};
|
|
5256
|
+
addOpt("", "\u306A\u3057");
|
|
5257
|
+
for (const m of LYRIC_MODELS) addOpt(m, lyricModelLabel(m));
|
|
5258
|
+
if (active.lyricModel && !LYRIC_MODELS.includes(active.lyricModel)) {
|
|
5259
|
+
addOpt(active.lyricModel, lyricModelLabel(active.lyricModel));
|
|
5260
|
+
}
|
|
5261
|
+
lyricModelSel.value = active.lyricModel;
|
|
5262
|
+
lyricOctaveSel.value = String(active.vocalOctave);
|
|
5263
|
+
lyricInput.value = active.lyrics;
|
|
5264
|
+
lyricVol.value = String(active.vocalVolume);
|
|
5265
|
+
lyricVolLabel.textContent = String(active.vocalVolume);
|
|
5266
|
+
lyricPan.value = String(active.vocalPan);
|
|
5267
|
+
lyricPanLabel.textContent = fmtPan(active.vocalPan);
|
|
5268
|
+
const updateLyricCount = () => {
|
|
5269
|
+
const n = normalizeLyrics(lyricInput.value).length;
|
|
5270
|
+
lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
|
|
5271
|
+
};
|
|
5272
|
+
const syncLyricTerms = () => {
|
|
5273
|
+
const url = active.lyricModel ? KOE_VOICEBANK_TERMS[active.lyricModel] : void 0;
|
|
5274
|
+
if (url) {
|
|
5275
|
+
const label = lyricModelLabel(active.lyricModel);
|
|
5276
|
+
lyricTermsLink.textContent = `${label}UTAU\u97F3\u6E90`;
|
|
5277
|
+
lyricTermsLink.href = url;
|
|
5278
|
+
lyricTerms.classList.remove("dtm-hidden");
|
|
5279
|
+
} else {
|
|
5280
|
+
lyricTerms.classList.add("dtm-hidden");
|
|
5281
|
+
}
|
|
5282
|
+
};
|
|
5283
|
+
const syncLyricIcon = () => {
|
|
5284
|
+
const imgKey = active.lyricModel ? VOICE_IMAGE_KEY[active.lyricModel.toLowerCase()] : void 0;
|
|
5285
|
+
const src = imgKey ? VOICE_IMAGES[imgKey] : void 0;
|
|
5286
|
+
if (src) {
|
|
5287
|
+
lyricIcon.src = src;
|
|
5288
|
+
lyricIcon.classList.remove("dtm-hidden");
|
|
5289
|
+
} else {
|
|
5290
|
+
lyricIcon.removeAttribute("src");
|
|
5291
|
+
lyricIcon.classList.add("dtm-hidden");
|
|
5292
|
+
}
|
|
5293
|
+
};
|
|
5294
|
+
const syncLyricVisibility = () => {
|
|
5295
|
+
lyricBody.style.display = active.lyricModel ? "" : "none";
|
|
5296
|
+
lyricOctaveSel.style.display = active.lyricModel ? "" : "none";
|
|
5297
|
+
updateLyricCount();
|
|
5298
|
+
syncLyricTerms();
|
|
5299
|
+
syncLyricIcon();
|
|
5300
|
+
};
|
|
5301
|
+
syncLyricVisibility();
|
|
5302
|
+
lyricModelSel.addEventListener("change", () => {
|
|
5303
|
+
active.lyricModel = lyricModelSel.value;
|
|
5304
|
+
syncLyricVisibility();
|
|
5305
|
+
});
|
|
5306
|
+
lyricOctaveSel.addEventListener("change", () => {
|
|
5307
|
+
active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
|
|
5308
|
+
});
|
|
5309
|
+
lyricInput.addEventListener("input", () => {
|
|
5310
|
+
active.lyrics = lyricInput.value;
|
|
5311
|
+
updateLyricCount();
|
|
5312
|
+
});
|
|
5313
|
+
lyricVol.addEventListener("input", () => {
|
|
5314
|
+
active.vocalVolume = Number.parseInt(lyricVol.value, 10);
|
|
5315
|
+
lyricVolLabel.textContent = lyricVol.value;
|
|
5316
|
+
});
|
|
5317
|
+
lyricPan.addEventListener("input", () => {
|
|
5318
|
+
active.vocalPan = Number.parseInt(lyricPan.value, 10);
|
|
5319
|
+
lyricPanLabel.textContent = fmtPan(active.vocalPan);
|
|
5320
|
+
});
|
|
5321
|
+
lyricPanLabel.style.cursor = "pointer";
|
|
5322
|
+
lyricPanLabel.title = "\u30BF\u30C3\u30D7\u3067\u4E2D\u592E(C)\u3078";
|
|
5323
|
+
lyricPanLabel.addEventListener("click", () => {
|
|
5324
|
+
active.vocalPan = 64;
|
|
5325
|
+
lyricPan.value = "64";
|
|
5326
|
+
lyricPanLabel.textContent = fmtPan(64);
|
|
5327
|
+
});
|
|
3587
5328
|
if (active.config.id === "chord" && showChord) {
|
|
3588
5329
|
const div = document.createElement("div");
|
|
3589
5330
|
div.className = "dtm-row";
|
|
@@ -3671,6 +5412,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
3671
5412
|
const barLimitBars = Number(refs.barLimitSelect.value);
|
|
3672
5413
|
const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
|
|
3673
5414
|
const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
|
|
5415
|
+
const metaLine = formatMmlMeta({
|
|
5416
|
+
instrument: currentInstrument || void 0,
|
|
5417
|
+
drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
|
|
5418
|
+
volume: masterVolume
|
|
5419
|
+
});
|
|
3674
5420
|
if (refs.decomposeChordToggle.checked) {
|
|
3675
5421
|
const ignoreHeavy = refs.ignoreChordHeavyToggle.checked;
|
|
3676
5422
|
const targetStates = ignoreHeavy ? trackStates.filter((t) => !isChordHeavyTrack(t.core.getNotes())) : trackStates;
|
|
@@ -3680,12 +5426,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
3680
5426
|
);
|
|
3681
5427
|
const monoTracks = decomposeToMonophonic(allNotes);
|
|
3682
5428
|
const refCore = trackStates[0].core;
|
|
3683
|
-
const
|
|
5429
|
+
const decomposedFull = monoTracks.map(
|
|
3684
5430
|
(notes, i) => `@${i} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
|
|
3685
|
-
)
|
|
3686
|
-
const
|
|
5431
|
+
);
|
|
5432
|
+
const decomposedMini = monoTracks.map(
|
|
3687
5433
|
(notes, i) => `@${i}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
|
|
3688
|
-
)
|
|
5434
|
+
);
|
|
5435
|
+
const full2 = [metaLine, ...decomposedFull].filter((s) => s.length > 0).join(";\n");
|
|
5436
|
+
const minified2 = [metaLine, ...decomposedMini].filter((s) => s.length > 0).join(";");
|
|
3689
5437
|
return {
|
|
3690
5438
|
full: full2,
|
|
3691
5439
|
minified: minified2,
|
|
@@ -3694,12 +5442,32 @@ var mountDAW = (target, options = {}) => {
|
|
|
3694
5442
|
barLimit: barLimitBars
|
|
3695
5443
|
};
|
|
3696
5444
|
}
|
|
3697
|
-
const
|
|
5445
|
+
const trackLines = trackStates.map(
|
|
3698
5446
|
(t, i) => `@${i} ${t.core.getMMLFromNotes(clipNotes(t.core.getNotes()), bpm, t.volume).trim()}`
|
|
3699
|
-
)
|
|
3700
|
-
const
|
|
5447
|
+
);
|
|
5448
|
+
const trackLinesMini = trackStates.map(
|
|
3701
5449
|
(t, i) => `@${i}${t.core.getMMLFromNotes(clipNotes(t.core.getNotes()), bpm, t.volume).trim().replace(/\s+/g, "")}`
|
|
3702
|
-
)
|
|
5450
|
+
);
|
|
5451
|
+
const lyricLines = trackStates.map((t, i) => ({
|
|
5452
|
+
i,
|
|
5453
|
+
text: t.lyrics.trim(),
|
|
5454
|
+
model: t.lyricModel.trim(),
|
|
5455
|
+
vol: t.vocalVolume,
|
|
5456
|
+
gate: t.vocalGate,
|
|
5457
|
+
pan: t.vocalPan,
|
|
5458
|
+
oct: t.vocalOctave
|
|
5459
|
+
})).filter((x) => x.model.length > 0 && x.text.length > 0).map((x) => {
|
|
5460
|
+
const params = [
|
|
5461
|
+
x.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x.vol}`,
|
|
5462
|
+
x.gate === 100 ? "" : `q${x.gate}`,
|
|
5463
|
+
x.pan === 64 ? "" : `p${x.pan}`,
|
|
5464
|
+
x.oct === 0 ? "" : `o${x.oct}`
|
|
5465
|
+
].filter((s) => s.length > 0).join(" ");
|
|
5466
|
+
const head = params ? `${x.model} ${params}` : x.model;
|
|
5467
|
+
return `@@${x.i} ${head} ${x.text}`;
|
|
5468
|
+
});
|
|
5469
|
+
const full = [metaLine, ...trackLines, ...lyricLines].filter((s) => s.length > 0).join(";\n");
|
|
5470
|
+
const minified = [metaLine, ...trackLinesMini, ...lyricLines].filter((s) => s.length > 0).join(";");
|
|
3703
5471
|
return {
|
|
3704
5472
|
full,
|
|
3705
5473
|
minified,
|
|
@@ -3720,6 +5488,34 @@ var mountDAW = (target, options = {}) => {
|
|
|
3720
5488
|
refs.outputContainer.classList.remove("dtm-hidden");
|
|
3721
5489
|
updateUndoRedo();
|
|
3722
5490
|
};
|
|
5491
|
+
const getFirstDetectedPitch = () => {
|
|
5492
|
+
let minStep = Number.MAX_SAFE_INTEGER;
|
|
5493
|
+
let candidateNotes = [];
|
|
5494
|
+
for (const t of trackStates) {
|
|
5495
|
+
for (const note of t.core.getNotes()) {
|
|
5496
|
+
if (note.startStep < minStep) {
|
|
5497
|
+
minStep = note.startStep;
|
|
5498
|
+
candidateNotes = [note];
|
|
5499
|
+
} else if (note.startStep === minStep) {
|
|
5500
|
+
candidateNotes.push(note);
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
}
|
|
5504
|
+
if (candidateNotes.length === 0) return null;
|
|
5505
|
+
const sum = candidateNotes.reduce((acc, note) => acc + note.pitch, 0);
|
|
5506
|
+
return Math.round(sum / candidateNotes.length);
|
|
5507
|
+
};
|
|
5508
|
+
const centerPitch = (pitch) => {
|
|
5509
|
+
const canvas = getGridCanvas();
|
|
5510
|
+
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
|
|
5511
|
+
const logicalY = yIndex * renderConfig.keyHeight;
|
|
5512
|
+
currentOffsetY = clamp3(
|
|
5513
|
+
logicalY - (canvas.height - renderConfig.keyHeight) / 2,
|
|
5514
|
+
0,
|
|
5515
|
+
getMaxOffsetY()
|
|
5516
|
+
);
|
|
5517
|
+
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
5518
|
+
};
|
|
3723
5519
|
const clearAll = () => {
|
|
3724
5520
|
for (const t of trackStates) {
|
|
3725
5521
|
t.core.resetHistory();
|
|
@@ -3731,8 +5527,45 @@ var mountDAW = (target, options = {}) => {
|
|
|
3731
5527
|
if (!mml) return;
|
|
3732
5528
|
clearAll();
|
|
3733
5529
|
for (const t of trackStates) t.core.setLoadMode(true);
|
|
3734
|
-
const {
|
|
3735
|
-
|
|
5530
|
+
const {
|
|
5531
|
+
placements,
|
|
5532
|
+
bpm: parsedBpm,
|
|
5533
|
+
lyrics,
|
|
5534
|
+
meta
|
|
5535
|
+
} = parseMML(mml, {
|
|
5536
|
+
stepsPerBar: renderConfig.stepsPerBar,
|
|
5537
|
+
collectLyrics: true,
|
|
5538
|
+
// このDAWのトラック数を超えるチャンネルはベースへ畳み込む(従来挙動)
|
|
5539
|
+
clampTrackCount: trackStates.length
|
|
5540
|
+
});
|
|
5541
|
+
currentInstrument = meta.instrument ?? "";
|
|
5542
|
+
if (meta.drum && drumPatterns[meta.drum]) {
|
|
5543
|
+
currentDrumPattern = meta.drum;
|
|
5544
|
+
refs.drumSelect.value = meta.drum;
|
|
5545
|
+
options.onDrumChange?.(meta.drum);
|
|
5546
|
+
}
|
|
5547
|
+
if (meta.volume !== void 0) {
|
|
5548
|
+
masterVolume = meta.volume;
|
|
5549
|
+
refs.masterVolume.value = String(meta.volume);
|
|
5550
|
+
refs.masterVolumeLabel.textContent = `${meta.volume}%`;
|
|
5551
|
+
}
|
|
5552
|
+
for (const t of trackStates) {
|
|
5553
|
+
t.lyrics = "";
|
|
5554
|
+
t.lyricModel = "";
|
|
5555
|
+
t.vocalVolume = DEFAULT_VOCAL_VOLUME;
|
|
5556
|
+
t.vocalGate = 100;
|
|
5557
|
+
t.vocalPan = 64;
|
|
5558
|
+
t.vocalOctave = 0;
|
|
5559
|
+
}
|
|
5560
|
+
lyrics?.forEach((lt, idx) => {
|
|
5561
|
+
const t = trackStates[idx];
|
|
5562
|
+
if (!t) return;
|
|
5563
|
+
t.lyrics = lt.syllables.map((s) => s.kana).join("");
|
|
5564
|
+
t.lyricModel = lt.model;
|
|
5565
|
+
t.vocalVolume = lt.volume;
|
|
5566
|
+
t.vocalGate = lt.gate;
|
|
5567
|
+
t.vocalPan = lt.pan;
|
|
5568
|
+
t.vocalOctave = lt.octave ?? 0;
|
|
3736
5569
|
});
|
|
3737
5570
|
for (const p of placements) {
|
|
3738
5571
|
const t = trackStates[p.trackIndex];
|
|
@@ -3748,8 +5581,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
3748
5581
|
}
|
|
3749
5582
|
playStartStep = 0;
|
|
3750
5583
|
currentOffsetX = 0;
|
|
3751
|
-
|
|
5584
|
+
const firstPitch = getFirstDetectedPitch();
|
|
5585
|
+
if (firstPitch !== null) {
|
|
5586
|
+
centerPitch(firstPitch);
|
|
5587
|
+
} else {
|
|
5588
|
+
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
5589
|
+
}
|
|
3752
5590
|
redrawAll();
|
|
5591
|
+
updateTrackPanel();
|
|
3753
5592
|
updateUndoRedo();
|
|
3754
5593
|
};
|
|
3755
5594
|
const applyChord = () => {
|
|
@@ -3809,7 +5648,12 @@ var mountDAW = (target, options = {}) => {
|
|
|
3809
5648
|
}
|
|
3810
5649
|
playStartStep = 0;
|
|
3811
5650
|
currentOffsetX = 0;
|
|
3812
|
-
|
|
5651
|
+
const firstPitch = getFirstDetectedPitch();
|
|
5652
|
+
if (firstPitch !== null) {
|
|
5653
|
+
centerPitch(firstPitch);
|
|
5654
|
+
} else {
|
|
5655
|
+
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
5656
|
+
}
|
|
3813
5657
|
redrawAll();
|
|
3814
5658
|
updateUndoRedo();
|
|
3815
5659
|
};
|
|
@@ -3873,25 +5717,32 @@ var mountDAW = (target, options = {}) => {
|
|
|
3873
5717
|
refs.zoomXIn.addEventListener("click", () => {
|
|
3874
5718
|
zoomX = Math.min(200, zoomX + 25);
|
|
3875
5719
|
applyZoomX();
|
|
5720
|
+
notifyViewState();
|
|
3876
5721
|
});
|
|
3877
5722
|
refs.zoomXOut.addEventListener("click", () => {
|
|
3878
5723
|
zoomX = Math.max(25, zoomX - 25);
|
|
3879
5724
|
applyZoomX();
|
|
5725
|
+
notifyViewState();
|
|
3880
5726
|
});
|
|
3881
5727
|
refs.zoomYIn.addEventListener("click", () => {
|
|
3882
5728
|
zoomY = Math.min(200, zoomY + 25);
|
|
3883
5729
|
applyZoomY();
|
|
5730
|
+
notifyViewState();
|
|
3884
5731
|
});
|
|
3885
5732
|
refs.zoomYOut.addEventListener("click", () => {
|
|
3886
5733
|
zoomY = Math.max(50, zoomY - 25);
|
|
3887
5734
|
applyZoomY();
|
|
5735
|
+
notifyViewState();
|
|
3888
5736
|
});
|
|
5737
|
+
refs.decomposeChordToggle.addEventListener("change", notifyViewState);
|
|
5738
|
+
refs.ignoreChordHeavyToggle.addEventListener("change", notifyViewState);
|
|
3889
5739
|
refs.masterVolume.addEventListener("input", () => {
|
|
3890
5740
|
masterVolume = Number.parseInt(refs.masterVolume.value, 10) || 0;
|
|
3891
5741
|
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
3892
5742
|
});
|
|
3893
5743
|
refs.drumSelect.addEventListener("change", () => {
|
|
3894
5744
|
currentDrumPattern = refs.drumSelect.value;
|
|
5745
|
+
options.onDrumChange?.(currentDrumPattern);
|
|
3895
5746
|
});
|
|
3896
5747
|
refs.drumVolume.addEventListener("input", () => {
|
|
3897
5748
|
drumVolume = Number.parseInt(refs.drumVolume.value, 10) || 0;
|
|
@@ -4081,6 +5932,33 @@ var mountDAW = (target, options = {}) => {
|
|
|
4081
5932
|
pause,
|
|
4082
5933
|
stop,
|
|
4083
5934
|
getMML: generateMML,
|
|
5935
|
+
setInstrument: (name) => {
|
|
5936
|
+
currentInstrument = name;
|
|
5937
|
+
},
|
|
5938
|
+
getDrum: () => currentDrumPattern,
|
|
5939
|
+
setDrum: (name) => {
|
|
5940
|
+
if (name !== "none" && !drumPatterns[name]) return;
|
|
5941
|
+
currentDrumPattern = name;
|
|
5942
|
+
refs.drumSelect.value = name;
|
|
5943
|
+
options.onDrumChange?.(name);
|
|
5944
|
+
},
|
|
5945
|
+
getViewState,
|
|
5946
|
+
setViewState: (state) => {
|
|
5947
|
+
if (typeof state.zoomX === "number") {
|
|
5948
|
+
zoomX = clamp3(state.zoomX, 25, 200);
|
|
5949
|
+
applyZoomX();
|
|
5950
|
+
}
|
|
5951
|
+
if (typeof state.zoomY === "number") {
|
|
5952
|
+
zoomY = clamp3(state.zoomY, 50, 200);
|
|
5953
|
+
applyZoomY();
|
|
5954
|
+
}
|
|
5955
|
+
if (typeof state.decomposeChord === "boolean") {
|
|
5956
|
+
refs.decomposeChordToggle.checked = state.decomposeChord;
|
|
5957
|
+
}
|
|
5958
|
+
if (typeof state.ignoreChordHeavy === "boolean") {
|
|
5959
|
+
refs.ignoreChordHeavyToggle.checked = state.ignoreChordHeavy;
|
|
5960
|
+
}
|
|
5961
|
+
},
|
|
4084
5962
|
loadMML,
|
|
4085
5963
|
loadMIDI,
|
|
4086
5964
|
exportMIDI: exportMIDI2,
|
|
@@ -4201,6 +6079,529 @@ var INSTRUMENT_PRESETS = {
|
|
|
4201
6079
|
}
|
|
4202
6080
|
};
|
|
4203
6081
|
|
|
6082
|
+
// src/mml-player.ts
|
|
6083
|
+
var STEPS_PER_BEAT3 = 48;
|
|
6084
|
+
var STEPS_PER_BAR = 192;
|
|
6085
|
+
var DEFAULT_TRACK_COLORS = ["#00e436", "#29adff", "#ff77a8", "#ffec27"];
|
|
6086
|
+
var activePlayer = null;
|
|
6087
|
+
var freqFromPitch = (pitch) => 440 * 2 ** ((pitch - 69) / 12);
|
|
6088
|
+
var mountMmlPlayer = (target, mml, options = {}) => {
|
|
6089
|
+
injectStyles(target.ownerDocument ?? document);
|
|
6090
|
+
const {
|
|
6091
|
+
placements,
|
|
6092
|
+
bpm: parsedBpm,
|
|
6093
|
+
tokenTracks,
|
|
6094
|
+
lyrics,
|
|
6095
|
+
meta
|
|
6096
|
+
} = parseMML(mml, {
|
|
6097
|
+
collectTokens: true,
|
|
6098
|
+
collectLyrics: true
|
|
6099
|
+
});
|
|
6100
|
+
const lyricTracks = lyrics ?? /* @__PURE__ */ new Map();
|
|
6101
|
+
const bpm = parsedBpm ?? options.defaultBpm ?? DEFAULT_BPM;
|
|
6102
|
+
const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
|
|
6103
|
+
const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
|
|
6104
|
+
const trackVolume = meta.volume ?? options.volume ?? 100;
|
|
6105
|
+
const colors = options.trackColors ?? DEFAULT_TRACK_COLORS;
|
|
6106
|
+
const useSynth = options.synth ?? !options.onPlayNote;
|
|
6107
|
+
const secondsPerStep = 60 / bpm / STEPS_PER_BEAT3;
|
|
6108
|
+
const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
|
|
6109
|
+
(a, b) => a - b
|
|
6110
|
+
);
|
|
6111
|
+
const seqTracks = trackIndices.map((index) => {
|
|
6112
|
+
let id = 0;
|
|
6113
|
+
const notes = placements.filter((p) => p.trackIndex === index).map((p) => ({
|
|
6114
|
+
id: id++,
|
|
6115
|
+
startStep: p.startStep,
|
|
6116
|
+
durationSteps: p.durationSteps,
|
|
6117
|
+
pitch: p.pitch,
|
|
6118
|
+
velocity: 100
|
|
6119
|
+
}));
|
|
6120
|
+
return { id: String(index), volume: trackVolume, notes };
|
|
6121
|
+
});
|
|
6122
|
+
const colorOf = (index) => colors[index % colors.length] ?? DEFAULT_TRACK_COLORS[0];
|
|
6123
|
+
let audioCtx = null;
|
|
6124
|
+
const ensureCtx = () => {
|
|
6125
|
+
if (!audioCtx) audioCtx = new AudioContext();
|
|
6126
|
+
return audioCtx;
|
|
6127
|
+
};
|
|
6128
|
+
const synthPlay = (e) => {
|
|
6129
|
+
const ctx = ensureCtx();
|
|
6130
|
+
const osc = ctx.createOscillator();
|
|
6131
|
+
const gain = ctx.createGain();
|
|
6132
|
+
osc.type = "square";
|
|
6133
|
+
osc.frequency.value = freqFromPitch(e.pitch);
|
|
6134
|
+
const t0 = ctx.currentTime + e.when;
|
|
6135
|
+
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5);
|
|
6136
|
+
gain.gain.setValueAtTime(peak, t0);
|
|
6137
|
+
gain.gain.exponentialRampToValueAtTime(1e-3, t0 + e.duration);
|
|
6138
|
+
osc.connect(gain);
|
|
6139
|
+
if (typeof ctx.createStereoPanner === "function" && e.pan) {
|
|
6140
|
+
const panner = ctx.createStereoPanner();
|
|
6141
|
+
panner.pan.value = Math.max(-1, Math.min(1, e.pan));
|
|
6142
|
+
gain.connect(panner);
|
|
6143
|
+
panner.connect(ctx.destination);
|
|
6144
|
+
} else {
|
|
6145
|
+
gain.connect(ctx.destination);
|
|
6146
|
+
}
|
|
6147
|
+
osc.start(t0);
|
|
6148
|
+
osc.stop(t0 + e.duration + 0.02);
|
|
6149
|
+
};
|
|
6150
|
+
const drumSynth = (e) => {
|
|
6151
|
+
const ctx = ensureCtx();
|
|
6152
|
+
const t0 = ctx.currentTime + e.when;
|
|
6153
|
+
const vol = Math.max(1e-4, Math.min(1, e.velocity));
|
|
6154
|
+
const isKick = e.pitch === 35 || e.pitch === 36;
|
|
6155
|
+
const isSnareLike = e.pitch === 38 || e.pitch === 39 || e.pitch === 40;
|
|
6156
|
+
if (isKick) {
|
|
6157
|
+
const osc = ctx.createOscillator();
|
|
6158
|
+
const g2 = ctx.createGain();
|
|
6159
|
+
osc.frequency.setValueAtTime(150, t0);
|
|
6160
|
+
osc.frequency.exponentialRampToValueAtTime(50, t0 + 0.12);
|
|
6161
|
+
g2.gain.setValueAtTime(vol * 0.9, t0);
|
|
6162
|
+
g2.gain.exponentialRampToValueAtTime(1e-3, t0 + 0.18);
|
|
6163
|
+
osc.connect(g2).connect(ctx.destination);
|
|
6164
|
+
osc.start(t0);
|
|
6165
|
+
osc.stop(t0 + 0.2);
|
|
6166
|
+
osc.onended = () => osc.disconnect();
|
|
6167
|
+
return;
|
|
6168
|
+
}
|
|
6169
|
+
const dur = isSnareLike ? 0.18 : 0.05;
|
|
6170
|
+
const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
|
|
6171
|
+
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
|
|
6172
|
+
const data = buffer.getChannelData(0);
|
|
6173
|
+
for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
|
|
6174
|
+
const src = ctx.createBufferSource();
|
|
6175
|
+
src.buffer = buffer;
|
|
6176
|
+
const filter = ctx.createBiquadFilter();
|
|
6177
|
+
filter.type = isSnareLike ? "bandpass" : "highpass";
|
|
6178
|
+
filter.frequency.value = isSnareLike ? 2e3 : 8e3;
|
|
6179
|
+
const g = ctx.createGain();
|
|
6180
|
+
g.gain.setValueAtTime(vol * (isSnareLike ? 0.7 : 0.4), t0);
|
|
6181
|
+
g.gain.exponentialRampToValueAtTime(1e-3, t0 + dur);
|
|
6182
|
+
src.connect(filter).connect(g).connect(ctx.destination);
|
|
6183
|
+
src.start(t0);
|
|
6184
|
+
src.stop(t0 + dur);
|
|
6185
|
+
src.onended = () => {
|
|
6186
|
+
src.disconnect();
|
|
6187
|
+
filter.disconnect();
|
|
6188
|
+
g.disconnect();
|
|
6189
|
+
};
|
|
6190
|
+
};
|
|
6191
|
+
let voices = null;
|
|
6192
|
+
const ensureVoices = () => {
|
|
6193
|
+
if (options.singingVoices) return options.singingVoices;
|
|
6194
|
+
if (!voices) {
|
|
6195
|
+
const ctx = ensureCtx();
|
|
6196
|
+
voices = createSingingVoices(ctx, ctx.destination);
|
|
6197
|
+
}
|
|
6198
|
+
return voices;
|
|
6199
|
+
};
|
|
6200
|
+
const voicesAvailable = useSynth || !!options.singingVoices;
|
|
6201
|
+
const peekVoices = () => options.singingVoices ?? voices;
|
|
6202
|
+
const getAudioTime = () => {
|
|
6203
|
+
if (useSynth) return ensureCtx().currentTime;
|
|
6204
|
+
return options.getAudioTime?.() ?? performance.now() / 1e3;
|
|
6205
|
+
};
|
|
6206
|
+
const doc = target.ownerDocument ?? document;
|
|
6207
|
+
const root = doc.createElement("div");
|
|
6208
|
+
root.className = "dtm-daw dtm-player";
|
|
6209
|
+
const head = doc.createElement("div");
|
|
6210
|
+
head.className = "dtm-player-head";
|
|
6211
|
+
const playBtn = doc.createElement("button");
|
|
6212
|
+
playBtn.type = "button";
|
|
6213
|
+
playBtn.className = "dtm-player-play";
|
|
6214
|
+
playBtn.innerHTML = icon("play", 12);
|
|
6215
|
+
playBtn.disabled = trackIndices.length === 0;
|
|
6216
|
+
const mmlHeader = doc.createElement("div");
|
|
6217
|
+
mmlHeader.className = "dtm-player-mml-header";
|
|
6218
|
+
const emojiEls = [];
|
|
6219
|
+
const emojiByTrack = /* @__PURE__ */ new Map();
|
|
6220
|
+
for (const index of trackIndices) {
|
|
6221
|
+
const em = doc.createElement("span");
|
|
6222
|
+
em.className = "dtm-player-emoji";
|
|
6223
|
+
em.style.backgroundColor = colorOf(index);
|
|
6224
|
+
em.textContent = "\u{1F97A}";
|
|
6225
|
+
mmlHeader.appendChild(em);
|
|
6226
|
+
emojiEls.push(em);
|
|
6227
|
+
emojiByTrack.set(index, em);
|
|
6228
|
+
}
|
|
6229
|
+
const promotedToImage = /* @__PURE__ */ new Set();
|
|
6230
|
+
for (const [index, lt] of lyricTracks) {
|
|
6231
|
+
const em = emojiByTrack.get(index);
|
|
6232
|
+
if (!em) continue;
|
|
6233
|
+
const imgKey = VOICE_IMAGE_KEY[lt.model.toLowerCase()];
|
|
6234
|
+
const src = imgKey ? VOICE_IMAGES[imgKey] : void 0;
|
|
6235
|
+
if (!src) continue;
|
|
6236
|
+
const img = doc.createElement("img");
|
|
6237
|
+
img.src = src;
|
|
6238
|
+
img.width = 20;
|
|
6239
|
+
img.height = 20;
|
|
6240
|
+
img.style.borderRadius = "50%";
|
|
6241
|
+
img.style.objectFit = "cover";
|
|
6242
|
+
img.draggable = false;
|
|
6243
|
+
promotedToImage.add(em);
|
|
6244
|
+
em.textContent = "";
|
|
6245
|
+
em.appendChild(img);
|
|
6246
|
+
}
|
|
6247
|
+
const JUMP_DEDUPE_MS = 50;
|
|
6248
|
+
const lastJumpAt = /* @__PURE__ */ new WeakMap();
|
|
6249
|
+
const jumpEmoji = (em) => {
|
|
6250
|
+
const now = performance.now();
|
|
6251
|
+
const prev = lastJumpAt.get(em);
|
|
6252
|
+
if (prev !== void 0 && now - prev < JUMP_DEDUPE_MS) return;
|
|
6253
|
+
lastJumpAt.set(em, now);
|
|
6254
|
+
em.classList.remove("dtm-player-emoji--jump");
|
|
6255
|
+
void em.offsetWidth;
|
|
6256
|
+
em.classList.add("dtm-player-emoji--jump");
|
|
6257
|
+
};
|
|
6258
|
+
const jumpTimers = [];
|
|
6259
|
+
const jumpEmojiAt = (em, when) => {
|
|
6260
|
+
if (when <= 0) {
|
|
6261
|
+
jumpEmoji(em);
|
|
6262
|
+
return;
|
|
6263
|
+
}
|
|
6264
|
+
jumpTimers.push(setTimeout(() => jumpEmoji(em), when * 1e3));
|
|
6265
|
+
};
|
|
6266
|
+
const clearJumpTimers = () => {
|
|
6267
|
+
for (const t of jumpTimers) clearTimeout(t);
|
|
6268
|
+
jumpTimers.length = 0;
|
|
6269
|
+
};
|
|
6270
|
+
const blinkTimers = [];
|
|
6271
|
+
const scheduleBlink = (em) => {
|
|
6272
|
+
const delay = 2e3 + Math.random() * 5e3;
|
|
6273
|
+
const t = setTimeout(() => {
|
|
6274
|
+
if (promotedToImage.has(em)) return;
|
|
6275
|
+
em.textContent = "\u{1F60C}";
|
|
6276
|
+
const t2 = setTimeout(
|
|
6277
|
+
() => {
|
|
6278
|
+
if (promotedToImage.has(em)) return;
|
|
6279
|
+
em.textContent = "\u{1F97A}";
|
|
6280
|
+
scheduleBlink(em);
|
|
6281
|
+
},
|
|
6282
|
+
200 + Math.random() * 150
|
|
6283
|
+
);
|
|
6284
|
+
blinkTimers.push(t2);
|
|
6285
|
+
}, delay);
|
|
6286
|
+
blinkTimers.push(t);
|
|
6287
|
+
};
|
|
6288
|
+
for (const em of emojiEls) scheduleBlink(em);
|
|
6289
|
+
const dots = doc.createElement("div");
|
|
6290
|
+
dots.className = "dtm-player-dots";
|
|
6291
|
+
dots.style.display = "none";
|
|
6292
|
+
for (const index of trackIndices) {
|
|
6293
|
+
const dot = doc.createElement("span");
|
|
6294
|
+
dot.className = "dtm-player-dot";
|
|
6295
|
+
dot.style.backgroundColor = colorOf(index);
|
|
6296
|
+
dots.appendChild(dot);
|
|
6297
|
+
}
|
|
6298
|
+
const beatRow = doc.createElement("div");
|
|
6299
|
+
beatRow.className = "dtm-player-beat-row";
|
|
6300
|
+
const beatDots = [];
|
|
6301
|
+
for (let i = 0; i < 4; i++) {
|
|
6302
|
+
const d = doc.createElement("span");
|
|
6303
|
+
d.className = "dtm-player-beat-dot";
|
|
6304
|
+
beatRow.appendChild(d);
|
|
6305
|
+
beatDots.push(d);
|
|
6306
|
+
}
|
|
6307
|
+
const barEl = doc.createElement("span");
|
|
6308
|
+
barEl.className = "dtm-player-bar";
|
|
6309
|
+
barEl.textContent = "-";
|
|
6310
|
+
beatRow.appendChild(barEl);
|
|
6311
|
+
const chips = [];
|
|
6312
|
+
const makeChip = (label) => {
|
|
6313
|
+
const chip = doc.createElement("span");
|
|
6314
|
+
chip.className = "dtm-player-chip";
|
|
6315
|
+
chip.textContent = label;
|
|
6316
|
+
chips.push(chip);
|
|
6317
|
+
return chip;
|
|
6318
|
+
};
|
|
6319
|
+
if (meta.instrument) makeChip(`\u266A ${meta.instrument}`);
|
|
6320
|
+
if (meta.drum) makeChip(`\u{1F941} ${meta.drum}${drumPattern ? "" : " (?)"}`);
|
|
6321
|
+
if (meta.volume !== void 0) makeChip(`\u{1F50A} ${meta.volume}%`);
|
|
6322
|
+
head.append(playBtn, beatRow, ...chips, dots, mmlHeader);
|
|
6323
|
+
root.appendChild(head);
|
|
6324
|
+
const body = doc.createElement("div");
|
|
6325
|
+
body.className = "dtm-player-body";
|
|
6326
|
+
root.appendChild(body);
|
|
6327
|
+
const mutedTracks = /* @__PURE__ */ new Set();
|
|
6328
|
+
const laneViews = [];
|
|
6329
|
+
for (const index of trackIndices) {
|
|
6330
|
+
const lyricTrack = lyricTracks.get(index);
|
|
6331
|
+
const isLyricLane = !!lyricTrack && lyricTrack.syllables.length > 0;
|
|
6332
|
+
const row = doc.createElement("div");
|
|
6333
|
+
row.className = "dtm-player-lane-row";
|
|
6334
|
+
const label = doc.createElement("div");
|
|
6335
|
+
label.className = "dtm-player-lane-label dtm-player-lane-label--btn";
|
|
6336
|
+
const swatch = doc.createElement("span");
|
|
6337
|
+
swatch.className = "dtm-player-dot";
|
|
6338
|
+
swatch.style.backgroundColor = colorOf(index);
|
|
6339
|
+
const no = doc.createElement("span");
|
|
6340
|
+
no.className = "dtm-player-lane-no";
|
|
6341
|
+
no.textContent = `@${index}`;
|
|
6342
|
+
label.append(swatch, no);
|
|
6343
|
+
label.addEventListener("click", () => {
|
|
6344
|
+
if (mutedTracks.has(index)) {
|
|
6345
|
+
mutedTracks.delete(index);
|
|
6346
|
+
label.classList.remove("dtm-player-lane-label--muted");
|
|
6347
|
+
} else {
|
|
6348
|
+
mutedTracks.add(index);
|
|
6349
|
+
label.classList.add("dtm-player-lane-label--muted");
|
|
6350
|
+
}
|
|
6351
|
+
});
|
|
6352
|
+
const lane = doc.createElement("div");
|
|
6353
|
+
lane.className = "dtm-player-lane";
|
|
6354
|
+
lane.style.setProperty("--tk", colorOf(index));
|
|
6355
|
+
const laneTokens = [];
|
|
6356
|
+
if (isLyricLane) {
|
|
6357
|
+
const notes = placements.filter((p) => p.trackIndex === index).sort((a, b) => a.startStep - b.startStep);
|
|
6358
|
+
const gateScale = (lyricTrack.gate ?? DEFAULT_GATE) / 100;
|
|
6359
|
+
const breaks = new Set(lyricTrack.lineBreaks ?? []);
|
|
6360
|
+
if (lyricTrack.metaText) {
|
|
6361
|
+
const metaEl = doc.createElement("span");
|
|
6362
|
+
metaEl.className = "dtm-tk dtm-tk--meta";
|
|
6363
|
+
metaEl.textContent = lyricTrack.metaText;
|
|
6364
|
+
lane.appendChild(metaEl);
|
|
6365
|
+
}
|
|
6366
|
+
const count = Math.min(notes.length, lyricTrack.syllables.length);
|
|
6367
|
+
for (let i = 0; i < count; i++) {
|
|
6368
|
+
const note = notes[i];
|
|
6369
|
+
if (breaks.has(i)) {
|
|
6370
|
+
const br = doc.createElement("span");
|
|
6371
|
+
br.className = "dtm-tk dtm-tk--break";
|
|
6372
|
+
br.textContent = "\\n";
|
|
6373
|
+
lane.appendChild(br);
|
|
6374
|
+
}
|
|
6375
|
+
const span = doc.createElement("span");
|
|
6376
|
+
span.className = "dtm-tk dtm-tk--lyric";
|
|
6377
|
+
span.textContent = lyricTrack.syllables[i].kana;
|
|
6378
|
+
lane.appendChild(span);
|
|
6379
|
+
laneTokens.push({
|
|
6380
|
+
el: span,
|
|
6381
|
+
startStep: note.startStep,
|
|
6382
|
+
durationSteps: Math.max(
|
|
6383
|
+
1,
|
|
6384
|
+
Math.round(note.durationSteps * gateScale)
|
|
6385
|
+
)
|
|
6386
|
+
});
|
|
6387
|
+
}
|
|
6388
|
+
} else {
|
|
6389
|
+
const tokens = tokenTracks?.get(index) ?? [];
|
|
6390
|
+
for (const tok of tokens) {
|
|
6391
|
+
const span = doc.createElement("span");
|
|
6392
|
+
span.className = `dtm-tk dtm-tk--${tok.type}`;
|
|
6393
|
+
span.textContent = tok.text;
|
|
6394
|
+
lane.appendChild(span);
|
|
6395
|
+
if (tok.durationSteps > 0) {
|
|
6396
|
+
laneTokens.push({
|
|
6397
|
+
el: span,
|
|
6398
|
+
startStep: tok.startStep,
|
|
6399
|
+
durationSteps: tok.durationSteps
|
|
6400
|
+
});
|
|
6401
|
+
}
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
row.append(label, lane);
|
|
6405
|
+
body.appendChild(row);
|
|
6406
|
+
laneViews.push({ lane, tokens: laneTokens });
|
|
6407
|
+
}
|
|
6408
|
+
const termsModels = [
|
|
6409
|
+
...new Set([...lyricTracks.values()].map((lt) => lt.model))
|
|
6410
|
+
].filter((model) => KOE_VOICEBANK_TERMS[model]);
|
|
6411
|
+
if (termsModels.length > 0) {
|
|
6412
|
+
const termsDiv = doc.createElement("div");
|
|
6413
|
+
termsDiv.className = "dtm-player-terms";
|
|
6414
|
+
termsDiv.style.fontSize = "10px";
|
|
6415
|
+
termsDiv.style.color = "var(--dtm-warn)";
|
|
6416
|
+
termsDiv.style.display = "flex";
|
|
6417
|
+
termsDiv.style.flexDirection = "column";
|
|
6418
|
+
termsDiv.style.gap = "4px";
|
|
6419
|
+
termsDiv.style.marginTop = "4px";
|
|
6420
|
+
termsDiv.style.padding = "0 4px";
|
|
6421
|
+
for (const model of termsModels) {
|
|
6422
|
+
const termsRow = doc.createElement("div");
|
|
6423
|
+
termsRow.style.display = "flex";
|
|
6424
|
+
termsRow.style.alignItems = "center";
|
|
6425
|
+
termsRow.style.gap = "4px";
|
|
6426
|
+
termsRow.style.flexWrap = "wrap";
|
|
6427
|
+
const label = KOE_VOICEBANK_LABELS[model] ?? model;
|
|
6428
|
+
const url = KOE_VOICEBANK_TERMS[model];
|
|
6429
|
+
const span1 = doc.createElement("span");
|
|
6430
|
+
span1.textContent = "\u4F7F\u7528\u6642\u306B\u306F";
|
|
6431
|
+
const a = doc.createElement("a");
|
|
6432
|
+
a.textContent = `${label}UTAU\u97F3\u6E90`;
|
|
6433
|
+
a.href = url;
|
|
6434
|
+
a.target = "_blank";
|
|
6435
|
+
a.rel = "noopener";
|
|
6436
|
+
a.style.color = "var(--dtm-primary)";
|
|
6437
|
+
a.style.textDecoration = "underline";
|
|
6438
|
+
const span2 = doc.createElement("span");
|
|
6439
|
+
span2.textContent = "\u306E\u5229\u7528\u898F\u7D04\u306B\u5F93\u3063\u3066\u304F\u3060\u3055\u3044";
|
|
6440
|
+
termsRow.append(span1, a, span2);
|
|
6441
|
+
termsDiv.appendChild(termsRow);
|
|
6442
|
+
}
|
|
6443
|
+
root.appendChild(termsDiv);
|
|
6444
|
+
}
|
|
6445
|
+
target.appendChild(root);
|
|
6446
|
+
const autoScroll = (lane, el) => {
|
|
6447
|
+
if (el.offsetWidth === 0 || lane.clientWidth === 0) return;
|
|
6448
|
+
const elementCenter = el.offsetLeft + el.offsetWidth / 2;
|
|
6449
|
+
const maxScroll = Math.max(0, lane.scrollWidth - lane.clientWidth);
|
|
6450
|
+
const next = elementCenter - lane.clientWidth / 2;
|
|
6451
|
+
lane.scrollLeft = Math.max(0, Math.min(next, maxScroll));
|
|
6452
|
+
};
|
|
6453
|
+
const renderPlayhead = (step) => {
|
|
6454
|
+
const beatIndex = Math.floor(step / STEPS_PER_BEAT3) % 4;
|
|
6455
|
+
for (let i = 0; i < 4; i++)
|
|
6456
|
+
beatDots[i].classList.toggle("dtm-player-beat-dot--on", i === beatIndex);
|
|
6457
|
+
barEl.textContent = String(Math.floor(step / STEPS_PER_BAR) + 1);
|
|
6458
|
+
for (const view of laneViews) {
|
|
6459
|
+
let active = null;
|
|
6460
|
+
for (const t of view.tokens) {
|
|
6461
|
+
const on = step >= t.startStep && step < t.startStep + t.durationSteps;
|
|
6462
|
+
t.el.classList.toggle("is-active", on);
|
|
6463
|
+
if (on && !active) active = t;
|
|
6464
|
+
}
|
|
6465
|
+
if (active) autoScroll(view.lane, active.el);
|
|
6466
|
+
}
|
|
6467
|
+
};
|
|
6468
|
+
const resetPlayhead = () => {
|
|
6469
|
+
for (const d of beatDots) d.classList.remove("dtm-player-beat-dot--on");
|
|
6470
|
+
barEl.textContent = "-";
|
|
6471
|
+
for (const view of laneViews) {
|
|
6472
|
+
for (const t of view.tokens) t.el.classList.remove("is-active");
|
|
6473
|
+
view.lane.scrollLeft = 0;
|
|
6474
|
+
}
|
|
6475
|
+
};
|
|
6476
|
+
const seq = createSequencer({
|
|
6477
|
+
getTracks: () => seqTracks,
|
|
6478
|
+
getBpm: () => bpm,
|
|
6479
|
+
getPlayStartStep: () => 0,
|
|
6480
|
+
getDrumPattern: () => drumPattern,
|
|
6481
|
+
getSoloTrackId: () => null,
|
|
6482
|
+
getAudioTime,
|
|
6483
|
+
onPlayNote: (e) => {
|
|
6484
|
+
const trackIdx = Number(e.trackId);
|
|
6485
|
+
if (mutedTracks.has(trackIdx)) return;
|
|
6486
|
+
const em = emojiByTrack.get(trackIdx);
|
|
6487
|
+
if (em) jumpEmojiAt(em, e.when);
|
|
6488
|
+
if (lyricTracks.has(trackIdx)) return;
|
|
6489
|
+
options.onPlayNote?.(e);
|
|
6490
|
+
if (useSynth) synthPlay(e);
|
|
6491
|
+
},
|
|
6492
|
+
onPlayDrum: (e) => {
|
|
6493
|
+
const em = emojiEls[0];
|
|
6494
|
+
if (em) jumpEmojiAt(em, e.when);
|
|
6495
|
+
const velocity = e.velocity * (trackVolume / 100);
|
|
6496
|
+
options.onPlayDrum?.({ ...e, velocity });
|
|
6497
|
+
if (useSynth) drumSynth({ ...e, velocity });
|
|
6498
|
+
},
|
|
6499
|
+
onTick: (step) => {
|
|
6500
|
+
renderPlayhead(step);
|
|
6501
|
+
},
|
|
6502
|
+
onEnd: () => finish(),
|
|
6503
|
+
stepsPerBar: STEPS_PER_BAR
|
|
6504
|
+
});
|
|
6505
|
+
let playing = false;
|
|
6506
|
+
const setPlayingUI = (on) => {
|
|
6507
|
+
playing = on;
|
|
6508
|
+
playBtn.innerHTML = icon(on ? "stop" : "play", 12);
|
|
6509
|
+
playBtn.classList.toggle("dtm-player-play--stop", on);
|
|
6510
|
+
};
|
|
6511
|
+
const finish = () => {
|
|
6512
|
+
setPlayingUI(false);
|
|
6513
|
+
clearJumpTimers();
|
|
6514
|
+
resetPlayhead();
|
|
6515
|
+
if (activePlayer === instance) activePlayer = null;
|
|
6516
|
+
};
|
|
6517
|
+
const buildStreamTracks = () => [...lyricTracks.entries()].map(([index, lt]) => {
|
|
6518
|
+
const seqTrack = seqTracks.find((t) => Number(t.id) === index);
|
|
6519
|
+
const sorted = [...seqTrack?.notes ?? []].sort(
|
|
6520
|
+
(a, b) => a.startStep - b.startStep
|
|
6521
|
+
);
|
|
6522
|
+
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
6523
|
+
const semis = (lt.octave ?? 0) * 12;
|
|
6524
|
+
const count = Math.min(sorted.length, lt.syllables.length);
|
|
6525
|
+
const notes = [];
|
|
6526
|
+
for (let i = 0; i < count; i++) {
|
|
6527
|
+
const n = sorted[i];
|
|
6528
|
+
notes.push({
|
|
6529
|
+
syllable: lt.syllables[i],
|
|
6530
|
+
pitch: n.pitch + semis,
|
|
6531
|
+
startSec: n.startStep * secondsPerStep,
|
|
6532
|
+
durationSec: n.durationSteps * secondsPerStep * gate
|
|
6533
|
+
});
|
|
6534
|
+
}
|
|
6535
|
+
return {
|
|
6536
|
+
model: lt.model,
|
|
6537
|
+
volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
|
|
6538
|
+
pan: panToStereo(lt.pan ?? DEFAULT_PAN),
|
|
6539
|
+
notes
|
|
6540
|
+
};
|
|
6541
|
+
});
|
|
6542
|
+
const startWhenReady = async () => {
|
|
6543
|
+
const streaming = voicesAvailable && lyricTracks.size > 0;
|
|
6544
|
+
const tracks = streaming ? buildStreamTracks() : [];
|
|
6545
|
+
if (streaming) {
|
|
6546
|
+
const v = ensureVoices();
|
|
6547
|
+
const overlay = showLoadingOverlay(body);
|
|
6548
|
+
try {
|
|
6549
|
+
await v.loadModels(tracks.map((t) => t.model));
|
|
6550
|
+
await v.warm(tracks);
|
|
6551
|
+
} catch (err) {
|
|
6552
|
+
console.warn("[dtm] voice preload failed", err);
|
|
6553
|
+
} finally {
|
|
6554
|
+
overlay.remove();
|
|
6555
|
+
}
|
|
6556
|
+
if (!playing || activePlayer !== instance) return;
|
|
6557
|
+
}
|
|
6558
|
+
seq.start(0);
|
|
6559
|
+
if (streaming) ensureVoices().startStream(tracks, seq.getStartTime());
|
|
6560
|
+
};
|
|
6561
|
+
const play = () => {
|
|
6562
|
+
if (playing || trackIndices.length === 0) return;
|
|
6563
|
+
if (activePlayer && activePlayer !== instance) activePlayer.stop();
|
|
6564
|
+
activePlayer = instance;
|
|
6565
|
+
setPlayingUI(true);
|
|
6566
|
+
void options.onResumeAudio?.();
|
|
6567
|
+
if (useSynth) {
|
|
6568
|
+
const ctx = ensureCtx();
|
|
6569
|
+
if (ctx.state === "suspended") void ctx.resume();
|
|
6570
|
+
}
|
|
6571
|
+
if (voicesAvailable && lyricTracks.size > 0) ensureVoices().reset();
|
|
6572
|
+
void startWhenReady();
|
|
6573
|
+
};
|
|
6574
|
+
const stop = () => {
|
|
6575
|
+
if (!playing) return;
|
|
6576
|
+
seq.stop();
|
|
6577
|
+
peekVoices()?.stopStream();
|
|
6578
|
+
finish();
|
|
6579
|
+
};
|
|
6580
|
+
playBtn.addEventListener("click", () => {
|
|
6581
|
+
if (playing) stop();
|
|
6582
|
+
else play();
|
|
6583
|
+
});
|
|
6584
|
+
const destroy = () => {
|
|
6585
|
+
seq.stop();
|
|
6586
|
+
peekVoices()?.stopStream();
|
|
6587
|
+
if (activePlayer === instance) activePlayer = null;
|
|
6588
|
+
if (audioCtx) {
|
|
6589
|
+
void audioCtx.close();
|
|
6590
|
+
audioCtx = null;
|
|
6591
|
+
}
|
|
6592
|
+
for (const t of blinkTimers) clearTimeout(t);
|
|
6593
|
+
clearJumpTimers();
|
|
6594
|
+
root.remove();
|
|
6595
|
+
};
|
|
6596
|
+
const instance = {
|
|
6597
|
+
play,
|
|
6598
|
+
stop,
|
|
6599
|
+
isPlaying: () => playing,
|
|
6600
|
+
destroy
|
|
6601
|
+
};
|
|
6602
|
+
return instance;
|
|
6603
|
+
};
|
|
6604
|
+
|
|
4204
6605
|
// src/piano-roll.ts
|
|
4205
6606
|
var createPianoRoll = (options, handlers) => {
|
|
4206
6607
|
const {
|
|
@@ -4486,23 +6887,43 @@ var createPianoRoll = (options, handlers) => {
|
|
|
4486
6887
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4487
6888
|
0 && (module.exports = {
|
|
4488
6889
|
DAW_CSS,
|
|
6890
|
+
DEFAULT_BPM,
|
|
6891
|
+
DEFAULT_GATE,
|
|
6892
|
+
DEFAULT_PAN,
|
|
6893
|
+
DEFAULT_PLAYBACK_VELOCITY,
|
|
6894
|
+
DEFAULT_STEPS_PER_BAR,
|
|
6895
|
+
DEFAULT_VELOCITY,
|
|
6896
|
+
DEFAULT_VOCAL_VOLUME,
|
|
4489
6897
|
DRUM_FONT,
|
|
4490
6898
|
DRUM_KEYS,
|
|
4491
6899
|
DRUM_PATTERNS,
|
|
4492
6900
|
INSTRUMENT_PRESETS,
|
|
6901
|
+
KOE_BASE_URL,
|
|
6902
|
+
KOE_VOICEBANKS,
|
|
6903
|
+
KOE_VOICEBANK_LABELS,
|
|
6904
|
+
KOE_VOICEBANK_TERMS,
|
|
4493
6905
|
LinkedList,
|
|
6906
|
+
MAX_VOCAL_VOLUME,
|
|
4494
6907
|
MMLCore,
|
|
4495
6908
|
PITCH_MAP,
|
|
6909
|
+
PREWARM_NOTES,
|
|
4496
6910
|
TRACKS_ADVANCED,
|
|
4497
6911
|
TRACKS_SIMPLE,
|
|
6912
|
+
VOICE_IMAGE_KEY,
|
|
4498
6913
|
analyzeMidiTracks,
|
|
4499
6914
|
applyHarmonicFilter,
|
|
4500
6915
|
applyMonophonic,
|
|
4501
6916
|
buildChordPlacements,
|
|
4502
6917
|
buildNameToKeyMapping,
|
|
6918
|
+
collectPitchTokens,
|
|
4503
6919
|
createAudioContext,
|
|
6920
|
+
createKlattVoice,
|
|
6921
|
+
createKoeVoice,
|
|
6922
|
+
createLyricsConductor,
|
|
4504
6923
|
createPianoRoll,
|
|
4505
6924
|
createSequencer,
|
|
6925
|
+
createSingingVoices,
|
|
6926
|
+
createVoiceRegistry,
|
|
4506
6927
|
decomposeToMonophonic,
|
|
4507
6928
|
drawGrid,
|
|
4508
6929
|
drawHeader,
|
|
@@ -4514,6 +6935,7 @@ var createPianoRoll = (options, handlers) => {
|
|
|
4514
6935
|
extractMidiPlacements,
|
|
4515
6936
|
extractMidiPlacementsByTrack,
|
|
4516
6937
|
fetchSoundFontList,
|
|
6938
|
+
formatMmlMeta,
|
|
4517
6939
|
generateRandomPattern,
|
|
4518
6940
|
getDrawOffset,
|
|
4519
6941
|
getGridCanvas,
|
|
@@ -4527,10 +6949,19 @@ var createPianoRoll = (options, handlers) => {
|
|
|
4527
6949
|
init,
|
|
4528
6950
|
injectStyles,
|
|
4529
6951
|
isChordHeavyTrack,
|
|
6952
|
+
koeUrl,
|
|
4530
6953
|
mountDAW,
|
|
6954
|
+
mountMmlPlayer,
|
|
6955
|
+
normalizeLyrics,
|
|
4531
6956
|
onClick,
|
|
6957
|
+
panToStereo,
|
|
6958
|
+
parseLyrics,
|
|
4532
6959
|
parseMML,
|
|
6960
|
+
parseMmlMeta,
|
|
4533
6961
|
setDrawOffset,
|
|
4534
6962
|
setupRecorder,
|
|
4535
|
-
shiftNotes
|
|
6963
|
+
shiftNotes,
|
|
6964
|
+
stripLyrics,
|
|
6965
|
+
stripMmlMeta,
|
|
6966
|
+
vocalVolumeToGain
|
|
4536
6967
|
});
|