@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.mjs
CHANGED
|
@@ -190,7 +190,7 @@ var buildChordPlacements = (options) => {
|
|
|
190
190
|
if (notes.length === 0) return;
|
|
191
191
|
const startStep = barIndex * chordLength;
|
|
192
192
|
notes.forEach((noteOffset, i) => {
|
|
193
|
-
const stepOffset = i *
|
|
193
|
+
const stepOffset = i * 3;
|
|
194
194
|
placements.push({
|
|
195
195
|
startStep: startStep + stepOffset,
|
|
196
196
|
pitch: C3 + noteOffset + offset,
|
|
@@ -285,7 +285,10 @@ var buildUI = (target, options) => {
|
|
|
285
285
|
<div class="dtm-tracks" data-dtm="track-tabs"></div>
|
|
286
286
|
|
|
287
287
|
<div class="dtm-roll-wrap">
|
|
288
|
-
<div class="dtm-roll" data-dtm="roll"
|
|
288
|
+
<div class="dtm-roll" data-dtm="roll">
|
|
289
|
+
<div data-dtm="wrapper" style="position:absolute;inset:0;"></div>
|
|
290
|
+
<div class="dtm-overlay" data-dtm="overlay" hidden><div class="dtm-spinner"></div></div>
|
|
291
|
+
</div>
|
|
289
292
|
<div class="dtm-vscroll" data-dtm="vscroll"><div class="dtm-vscroll-thumb" data-dtm="vscroll-thumb"></div></div>
|
|
290
293
|
</div>
|
|
291
294
|
<div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
|
|
@@ -418,8 +421,6 @@ var buildUI = (target, options) => {
|
|
|
418
421
|
</div>
|
|
419
422
|
</div>
|
|
420
423
|
</details>
|
|
421
|
-
|
|
422
|
-
<div class="dtm-overlay" data-dtm="overlay" hidden><div class="dtm-spinner"></div></div>
|
|
423
424
|
</div>`;
|
|
424
425
|
const root = q(target, '[data-dtm="root"]');
|
|
425
426
|
const sel = (name) => q(root, `[data-dtm="${name}"]`);
|
|
@@ -608,6 +609,1155 @@ var DRUM_PATTERNS = {
|
|
|
608
609
|
]
|
|
609
610
|
};
|
|
610
611
|
|
|
612
|
+
// node_modules/.pnpm/@onjmin+koe@1.0.3/node_modules/@onjmin/koe/dist/index.js
|
|
613
|
+
var MAGIC = 1263486208;
|
|
614
|
+
function parseKoeHeader(headerBytes) {
|
|
615
|
+
const view = new DataView(headerBytes);
|
|
616
|
+
if (view.byteLength < 8 || view.getUint32(0, false) !== MAGIC) {
|
|
617
|
+
throw new Error("Not a .koe file (bad magic)");
|
|
618
|
+
}
|
|
619
|
+
return { jsonLength: view.getUint32(4, true) };
|
|
620
|
+
}
|
|
621
|
+
var pcmBase = (jsonLength) => 8 + jsonLength;
|
|
622
|
+
var BlobVoiceSource = class {
|
|
623
|
+
constructor(blob, base) {
|
|
624
|
+
this.blob = blob;
|
|
625
|
+
this.base = base;
|
|
626
|
+
}
|
|
627
|
+
blob;
|
|
628
|
+
base;
|
|
629
|
+
readBytes(offset, length) {
|
|
630
|
+
const start = this.base + offset;
|
|
631
|
+
return this.blob.slice(start, start + length).arrayBuffer();
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
var RangeVoiceSource = class {
|
|
635
|
+
constructor(url, base) {
|
|
636
|
+
this.url = url;
|
|
637
|
+
this.base = base;
|
|
638
|
+
}
|
|
639
|
+
url;
|
|
640
|
+
base;
|
|
641
|
+
async readBytes(offset, length) {
|
|
642
|
+
const start = this.base + offset;
|
|
643
|
+
const res = await fetch(this.url, {
|
|
644
|
+
headers: { Range: `bytes=${start}-${start + length - 1}` }
|
|
645
|
+
});
|
|
646
|
+
if (!res.ok && res.status !== 206) {
|
|
647
|
+
throw new Error(`.koe range request failed: ${res.status}`);
|
|
648
|
+
}
|
|
649
|
+
return res.arrayBuffer();
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
async function rangeFetch(url, start, length) {
|
|
653
|
+
const res = await fetch(url, {
|
|
654
|
+
headers: { Range: `bytes=${start}-${start + length - 1}` }
|
|
655
|
+
});
|
|
656
|
+
if (!res.ok && res.status !== 206)
|
|
657
|
+
throw new Error(`.koe fetch failed: ${res.status}`);
|
|
658
|
+
return res.arrayBuffer();
|
|
659
|
+
}
|
|
660
|
+
var VoiceBank = class _VoiceBank {
|
|
661
|
+
constructor(manifest, source) {
|
|
662
|
+
this.manifest = manifest;
|
|
663
|
+
this.source = source;
|
|
664
|
+
}
|
|
665
|
+
manifest;
|
|
666
|
+
source;
|
|
667
|
+
/**
|
|
668
|
+
* Parse a .koe archive header + manifest and bind a lazy PCM source.
|
|
669
|
+
* @param koe a Blob/File of the .koe archive, or a URL (served with Range support)
|
|
670
|
+
*/
|
|
671
|
+
static async load(koe) {
|
|
672
|
+
if (typeof koe === "string") {
|
|
673
|
+
const header2 = await rangeFetch(koe, 0, 8);
|
|
674
|
+
const { jsonLength: jsonLength2 } = parseKoeHeader(header2);
|
|
675
|
+
const json2 = await rangeFetch(koe, 8, jsonLength2);
|
|
676
|
+
const manifest2 = JSON.parse(new TextDecoder().decode(json2));
|
|
677
|
+
return new _VoiceBank(
|
|
678
|
+
manifest2,
|
|
679
|
+
new RangeVoiceSource(koe, pcmBase(jsonLength2))
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
const header = await koe.slice(0, 8).arrayBuffer();
|
|
683
|
+
const { jsonLength } = parseKoeHeader(header);
|
|
684
|
+
const json = await koe.slice(8, 8 + jsonLength).arrayBuffer();
|
|
685
|
+
const manifest = JSON.parse(new TextDecoder().decode(json));
|
|
686
|
+
return new _VoiceBank(
|
|
687
|
+
manifest,
|
|
688
|
+
new BlobVoiceSource(koe, pcmBase(jsonLength))
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
/** True if the bank contains a phoneme under this alias. */
|
|
692
|
+
has(phoneme) {
|
|
693
|
+
return this.manifest.phonemes[phoneme] !== void 0;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Raw Int16 PCM bytes (48 kHz / mono) for a phoneme, or null if unknown.
|
|
697
|
+
* The returned ArrayBuffer is freshly allocated and safe to transfer to a
|
|
698
|
+
* worker / AudioWorklet.
|
|
699
|
+
*/
|
|
700
|
+
async readPcmBytes(phoneme) {
|
|
701
|
+
const entry = this.manifest.phonemes[phoneme];
|
|
702
|
+
if (!entry) return null;
|
|
703
|
+
return this.source.readBytes(entry.offset, entry.length * 2);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* A phoneme's PCM as a Float64Array normalised to [-1, 1], or null if unknown.
|
|
707
|
+
* Intended for external analysis / resynthesis such as the WORLD vocoder.
|
|
708
|
+
*/
|
|
709
|
+
async getPcm(phoneme) {
|
|
710
|
+
const buf = await this.readPcmBytes(phoneme);
|
|
711
|
+
if (!buf) return null;
|
|
712
|
+
const int16 = new Int16Array(buf);
|
|
713
|
+
const f64 = new Float64Array(int16.length);
|
|
714
|
+
for (let i = 0; i < int16.length; i++) f64[i] = int16[i] / 32768;
|
|
715
|
+
return f64;
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
var WORLDLINE_SAMPLE_RATE = 48e3;
|
|
719
|
+
var MIN_WORLDLINE_SAMPLES = 4096;
|
|
720
|
+
var SYNTH_REQ_SIZE = 120;
|
|
721
|
+
var WL_FRAME_MS = 10;
|
|
722
|
+
var samplesToMs = (samples) => samples / WORLDLINE_SAMPLE_RATE * 1e3;
|
|
723
|
+
function leadInFromEntry(entry) {
|
|
724
|
+
return {
|
|
725
|
+
preMs: samplesToMs(entry.pre || 0),
|
|
726
|
+
consonantMs: samplesToMs(entry.consonant || 0)
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
var moduleCache = /* @__PURE__ */ new Map();
|
|
730
|
+
function injectScript(src) {
|
|
731
|
+
return new Promise((resolve, reject) => {
|
|
732
|
+
const existing = document.querySelector(
|
|
733
|
+
`script[data-koe-worldline="${src}"]`
|
|
734
|
+
);
|
|
735
|
+
if (existing) {
|
|
736
|
+
resolve();
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const s = document.createElement("script");
|
|
740
|
+
s.src = src;
|
|
741
|
+
s.dataset.koeWorldline = src;
|
|
742
|
+
s.onload = () => resolve();
|
|
743
|
+
s.onerror = () => reject(new Error(`worldline: failed to load ${src}`));
|
|
744
|
+
document.head.appendChild(s);
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
function loadWasm(scriptUrl) {
|
|
748
|
+
const cached = moduleCache.get(scriptUrl);
|
|
749
|
+
if (cached) return cached;
|
|
750
|
+
const baseUrl = scriptUrl.slice(0, scriptUrl.lastIndexOf("/") + 1);
|
|
751
|
+
const instantiate = () => {
|
|
752
|
+
const factory = globalThis.WorldlineModule;
|
|
753
|
+
if (!factory)
|
|
754
|
+
throw new Error(
|
|
755
|
+
"worldline: WorldlineModule global was not defined by the script"
|
|
756
|
+
);
|
|
757
|
+
return factory({ locateFile: (f) => baseUrl + f });
|
|
758
|
+
};
|
|
759
|
+
let promise;
|
|
760
|
+
if (typeof document !== "undefined") {
|
|
761
|
+
promise = injectScript(scriptUrl).then(instantiate);
|
|
762
|
+
} else if (typeof globalThis.importScripts === "function") {
|
|
763
|
+
promise = Promise.resolve().then(() => {
|
|
764
|
+
globalThis.importScripts(scriptUrl);
|
|
765
|
+
return instantiate();
|
|
766
|
+
});
|
|
767
|
+
} else {
|
|
768
|
+
return Promise.reject(
|
|
769
|
+
new Error(
|
|
770
|
+
"Worldline.load requires a DOM or a classic Web Worker (importScripts) to load worldline.js"
|
|
771
|
+
)
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
moduleCache.set(scriptUrl, promise);
|
|
775
|
+
return promise;
|
|
776
|
+
}
|
|
777
|
+
var Worldline = class _Worldline {
|
|
778
|
+
constructor(wasm) {
|
|
779
|
+
this.wasm = wasm;
|
|
780
|
+
}
|
|
781
|
+
wasm;
|
|
782
|
+
sampleRate = WORLDLINE_SAMPLE_RATE;
|
|
783
|
+
/**
|
|
784
|
+
* Load + instantiate the worldline WASM module (deduped per scriptUrl).
|
|
785
|
+
*
|
|
786
|
+
* Works on the main thread (loads via `<script>`) and inside a classic Web
|
|
787
|
+
* Worker (loads via `importScripts`), so the heavy synthesis can run
|
|
788
|
+
* off-thread. The matching `worldline.wasm` is fetched next to scriptUrl.
|
|
789
|
+
*/
|
|
790
|
+
static async load(options) {
|
|
791
|
+
return new _Worldline(await loadWasm(options.scriptUrl));
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Render one note to Float32 PCM at 48 kHz.
|
|
795
|
+
*
|
|
796
|
+
* The output buffer is laid out as [lead-in/consonant ≈ preMs][vowel ≈
|
|
797
|
+
* durationMs], rendered from sample offset 0 (no leading silence). The vowel
|
|
798
|
+
* onset (the "beat") sits at ≈ preMs into the buffer, so a sequencer should
|
|
799
|
+
* place the buffer at `beatTime − preMs` and may trim/crossfade the lead-in.
|
|
800
|
+
*
|
|
801
|
+
* No internal crossfade is applied — apply fades externally.
|
|
802
|
+
*
|
|
803
|
+
* @returns Float32 PCM, or null when `pcm` is shorter than
|
|
804
|
+
* {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
|
|
805
|
+
*/
|
|
806
|
+
renderNote(params) {
|
|
807
|
+
const { pcm, pitch, durationMs, preMs, consonantMs, tempo = 120 } = params;
|
|
808
|
+
if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;
|
|
809
|
+
const WL = this.wasm;
|
|
810
|
+
const FS = WORLDLINE_SAMPLE_RATE;
|
|
811
|
+
const midiNote = Math.round(69 + 12 * Math.log2(pitch / 440));
|
|
812
|
+
const posMs = 0;
|
|
813
|
+
const reqLen = preMs + durationMs;
|
|
814
|
+
const cutMs = WL_FRAME_MS * 2;
|
|
815
|
+
const ps = WL._PhraseSynthNew();
|
|
816
|
+
if (!ps) return null;
|
|
817
|
+
const reqPtr = WL._malloc(SYNTH_REQ_SIZE);
|
|
818
|
+
if (!reqPtr) {
|
|
819
|
+
WL._PhraseSynthDelete(ps);
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
const samplePtr = WL._malloc(pcm.length * 8);
|
|
823
|
+
if (!samplePtr) {
|
|
824
|
+
WL._free(reqPtr);
|
|
825
|
+
WL._PhraseSynthDelete(ps);
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
WL.HEAPF64.set(pcm, samplePtr >> 3);
|
|
829
|
+
const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
|
|
830
|
+
sv(0, FS, "i32");
|
|
831
|
+
sv(4, pcm.length, "i32");
|
|
832
|
+
sv(8, samplePtr, "*");
|
|
833
|
+
sv(12, 0, "i32");
|
|
834
|
+
sv(16, 0, "*");
|
|
835
|
+
sv(20, midiNote, "i32");
|
|
836
|
+
sv(24, 100, "double");
|
|
837
|
+
sv(32, 0, "double");
|
|
838
|
+
sv(40, reqLen, "double");
|
|
839
|
+
sv(48, consonantMs, "double");
|
|
840
|
+
sv(56, cutMs, "double");
|
|
841
|
+
sv(64, 100, "double");
|
|
842
|
+
sv(72, 0, "double");
|
|
843
|
+
sv(80, tempo, "double");
|
|
844
|
+
sv(88, 0, "i32");
|
|
845
|
+
sv(92, 0, "*");
|
|
846
|
+
sv(96, 0, "i32");
|
|
847
|
+
sv(100, 0, "i32");
|
|
848
|
+
sv(104, 100, "i32");
|
|
849
|
+
sv(108, 0, "i32");
|
|
850
|
+
sv(112, 0, "i32");
|
|
851
|
+
sv(116, 100, "i32");
|
|
852
|
+
WL._PhraseSynthAddRequest(ps, reqPtr, posMs, 0, reqLen, 0, 0, 0);
|
|
853
|
+
WL._free(samplePtr);
|
|
854
|
+
WL._free(reqPtr);
|
|
855
|
+
const totalMs = posMs + reqLen + WL_FRAME_MS * 2;
|
|
856
|
+
const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
|
|
857
|
+
const f0Arr = new Float64Array(nFrames).fill(pitch);
|
|
858
|
+
const gArr = new Float64Array(nFrames).fill(0.5);
|
|
859
|
+
const tArr = new Float64Array(nFrames).fill(0.5);
|
|
860
|
+
const bArr = new Float64Array(nFrames).fill(0.5);
|
|
861
|
+
const vArr = new Float64Array(nFrames).fill(1);
|
|
862
|
+
const f0Ptr = WL._malloc(nFrames * 8);
|
|
863
|
+
const gPtr = WL._malloc(nFrames * 8);
|
|
864
|
+
const tPtr = WL._malloc(nFrames * 8);
|
|
865
|
+
const bPtr = WL._malloc(nFrames * 8);
|
|
866
|
+
const vPtr = WL._malloc(nFrames * 8);
|
|
867
|
+
if (!f0Ptr || !gPtr || !tPtr || !bPtr || !vPtr) {
|
|
868
|
+
if (f0Ptr) WL._free(f0Ptr);
|
|
869
|
+
if (gPtr) WL._free(gPtr);
|
|
870
|
+
if (tPtr) WL._free(tPtr);
|
|
871
|
+
if (bPtr) WL._free(bPtr);
|
|
872
|
+
if (vPtr) WL._free(vPtr);
|
|
873
|
+
WL._PhraseSynthDelete(ps);
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
WL.HEAPF64.set(f0Arr, f0Ptr >> 3);
|
|
877
|
+
WL.HEAPF64.set(gArr, gPtr >> 3);
|
|
878
|
+
WL.HEAPF64.set(tArr, tPtr >> 3);
|
|
879
|
+
WL.HEAPF64.set(bArr, bPtr >> 3);
|
|
880
|
+
WL.HEAPF64.set(vArr, vPtr >> 3);
|
|
881
|
+
WL._PhraseSynthSetCurves(
|
|
882
|
+
ps,
|
|
883
|
+
f0Ptr,
|
|
884
|
+
gPtr,
|
|
885
|
+
tPtr,
|
|
886
|
+
bPtr,
|
|
887
|
+
vPtr,
|
|
888
|
+
nFrames,
|
|
889
|
+
WL_FRAME_MS
|
|
890
|
+
);
|
|
891
|
+
WL._free(f0Ptr);
|
|
892
|
+
WL._free(gPtr);
|
|
893
|
+
WL._free(tPtr);
|
|
894
|
+
WL._free(bPtr);
|
|
895
|
+
WL._free(vPtr);
|
|
896
|
+
const yPtrPtr = WL._malloc(4);
|
|
897
|
+
if (!yPtrPtr) {
|
|
898
|
+
WL._PhraseSynthDelete(ps);
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
const outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);
|
|
902
|
+
const yPtr = WL.getValue(yPtrPtr, "*");
|
|
903
|
+
const audio = outLen > 0 ? new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice() : null;
|
|
904
|
+
WL._free(yPtrPtr);
|
|
905
|
+
WL._PhraseSynthDelete(ps);
|
|
906
|
+
return audio;
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
// src/types.ts
|
|
911
|
+
var DEFAULT_VOCAL_VOLUME = 200;
|
|
912
|
+
var DEFAULT_BPM = 120;
|
|
913
|
+
var DEFAULT_GATE = 100;
|
|
914
|
+
var DEFAULT_PAN = 64;
|
|
915
|
+
var DEFAULT_VELOCITY = 100;
|
|
916
|
+
var DEFAULT_PLAYBACK_VELOCITY = 127;
|
|
917
|
+
var DEFAULT_STEPS_PER_BAR = 192;
|
|
918
|
+
|
|
919
|
+
// src/lyrics.ts
|
|
920
|
+
var kanaTable = {
|
|
921
|
+
\u3042: ["", "a"],
|
|
922
|
+
\u3044: ["", "i"],
|
|
923
|
+
\u3046: ["", "u"],
|
|
924
|
+
\u3048: ["", "e"],
|
|
925
|
+
\u304A: ["", "o"],
|
|
926
|
+
\u304B: ["k", "a"],
|
|
927
|
+
\u304D: ["k", "i"],
|
|
928
|
+
\u304F: ["k", "u"],
|
|
929
|
+
\u3051: ["k", "e"],
|
|
930
|
+
\u3053: ["k", "o"],
|
|
931
|
+
\u3055: ["s", "a"],
|
|
932
|
+
\u3057: ["sh", "i"],
|
|
933
|
+
\u3059: ["s", "u"],
|
|
934
|
+
\u305B: ["s", "e"],
|
|
935
|
+
\u305D: ["s", "o"],
|
|
936
|
+
\u305F: ["t", "a"],
|
|
937
|
+
\u3061: ["ch", "i"],
|
|
938
|
+
\u3064: ["ts", "u"],
|
|
939
|
+
\u3066: ["t", "e"],
|
|
940
|
+
\u3068: ["t", "o"],
|
|
941
|
+
\u306A: ["n", "a"],
|
|
942
|
+
\u306B: ["n", "i"],
|
|
943
|
+
\u306C: ["n", "u"],
|
|
944
|
+
\u306D: ["n", "e"],
|
|
945
|
+
\u306E: ["n", "o"],
|
|
946
|
+
\u306F: ["h", "a"],
|
|
947
|
+
\u3072: ["h", "i"],
|
|
948
|
+
\u3075: ["f", "u"],
|
|
949
|
+
\u3078: ["h", "e"],
|
|
950
|
+
\u307B: ["h", "o"],
|
|
951
|
+
\u307E: ["m", "a"],
|
|
952
|
+
\u307F: ["m", "i"],
|
|
953
|
+
\u3080: ["m", "u"],
|
|
954
|
+
\u3081: ["m", "e"],
|
|
955
|
+
\u3082: ["m", "o"],
|
|
956
|
+
\u3084: ["y", "a"],
|
|
957
|
+
\u3086: ["y", "u"],
|
|
958
|
+
\u3088: ["y", "o"],
|
|
959
|
+
\u3089: ["r", "a"],
|
|
960
|
+
\u308A: ["r", "i"],
|
|
961
|
+
\u308B: ["r", "u"],
|
|
962
|
+
\u308C: ["r", "e"],
|
|
963
|
+
\u308D: ["r", "o"],
|
|
964
|
+
\u308F: ["w", "a"],
|
|
965
|
+
\u3092: ["w", "o"],
|
|
966
|
+
\u304C: ["g", "a"],
|
|
967
|
+
\u304E: ["g", "i"],
|
|
968
|
+
\u3050: ["g", "u"],
|
|
969
|
+
\u3052: ["g", "e"],
|
|
970
|
+
\u3054: ["g", "o"],
|
|
971
|
+
\u3056: ["z", "a"],
|
|
972
|
+
\u3058: ["j", "i"],
|
|
973
|
+
\u305A: ["z", "u"],
|
|
974
|
+
\u305C: ["z", "e"],
|
|
975
|
+
\u305E: ["z", "o"],
|
|
976
|
+
\u3060: ["d", "a"],
|
|
977
|
+
\u3062: ["j", "i"],
|
|
978
|
+
\u3065: ["z", "u"],
|
|
979
|
+
\u3067: ["d", "e"],
|
|
980
|
+
\u3069: ["d", "o"],
|
|
981
|
+
\u3070: ["b", "a"],
|
|
982
|
+
\u3073: ["b", "i"],
|
|
983
|
+
\u3076: ["b", "u"],
|
|
984
|
+
\u3079: ["b", "e"],
|
|
985
|
+
\u307C: ["b", "o"],
|
|
986
|
+
\u3071: ["p", "a"],
|
|
987
|
+
\u3074: ["p", "i"],
|
|
988
|
+
\u3077: ["p", "u"],
|
|
989
|
+
\u307A: ["p", "e"],
|
|
990
|
+
\u307D: ["p", "o"],
|
|
991
|
+
\u3093: ["N", "N"]
|
|
992
|
+
};
|
|
993
|
+
var SMALL_KANA = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087\u3063";
|
|
994
|
+
var VOWEL_KANA = {
|
|
995
|
+
a: "\u3042",
|
|
996
|
+
i: "\u3044",
|
|
997
|
+
u: "\u3046",
|
|
998
|
+
e: "\u3048",
|
|
999
|
+
o: "\u304A"
|
|
1000
|
+
};
|
|
1001
|
+
var sanitizeText = (text) => text.normalize("NFKC").replace(/[ァ-ヶ]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 96)).replace(/[^ぁ-ゖー]/g, "");
|
|
1002
|
+
var splitSyllables = (text) => {
|
|
1003
|
+
const result = [];
|
|
1004
|
+
for (const ch of text) {
|
|
1005
|
+
if (result.length > 0 && SMALL_KANA.includes(ch)) {
|
|
1006
|
+
result[result.length - 1] += ch;
|
|
1007
|
+
} else {
|
|
1008
|
+
result.push(ch);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return result;
|
|
1012
|
+
};
|
|
1013
|
+
var kanaToVowel = (kana) => {
|
|
1014
|
+
if (/[ぁゃ]/.test(kana)) return "a";
|
|
1015
|
+
if (/[ぃ]/.test(kana)) return "i";
|
|
1016
|
+
if (/[ぅゅ]/.test(kana)) return "u";
|
|
1017
|
+
if (/[ぇ]/.test(kana)) return "e";
|
|
1018
|
+
if (/[ぉょ]/.test(kana)) return "o";
|
|
1019
|
+
if (/[あかさたなはまやらわがざだばぱ]/.test(kana)) return "a";
|
|
1020
|
+
if (/[いきしちにひみりぎじぢびぴ]/.test(kana)) return "i";
|
|
1021
|
+
if (/[うくすつぬふむゆるぐずづぶぷ]/.test(kana)) return "u";
|
|
1022
|
+
if (/[えけせてねへめれげぜでべぺ]/.test(kana)) return "e";
|
|
1023
|
+
if (/[おこそとのほもよろごぞどぼぽ]/.test(kana)) return "o";
|
|
1024
|
+
return "";
|
|
1025
|
+
};
|
|
1026
|
+
var analyzeSyllable = (syllable) => {
|
|
1027
|
+
if (syllable === "\u30FC") return { kana: syllable, consonant: "-", vowel: "-" };
|
|
1028
|
+
if (syllable === "\u3063") return { kana: syllable, consonant: "Q", vowel: "" };
|
|
1029
|
+
const head = syllable[0];
|
|
1030
|
+
const row = kanaTable[head];
|
|
1031
|
+
const consonant = row ? row[0] : "";
|
|
1032
|
+
let vowel = row ? row[1] : kanaToVowel(head);
|
|
1033
|
+
if (syllable.length === 2 && syllable[1] !== "\u3063") {
|
|
1034
|
+
const v = kanaToVowel(syllable[1]);
|
|
1035
|
+
if (v) vowel = v;
|
|
1036
|
+
}
|
|
1037
|
+
return { kana: syllable, consonant, vowel };
|
|
1038
|
+
};
|
|
1039
|
+
var resolveLongVowels = (syllables) => {
|
|
1040
|
+
const result = [];
|
|
1041
|
+
let prevVowel = "";
|
|
1042
|
+
for (const syl of syllables) {
|
|
1043
|
+
if (syl.consonant === "-") {
|
|
1044
|
+
if (!prevVowel) continue;
|
|
1045
|
+
result.push({
|
|
1046
|
+
kana: VOWEL_KANA[prevVowel] ?? syl.kana,
|
|
1047
|
+
consonant: "",
|
|
1048
|
+
vowel: prevVowel
|
|
1049
|
+
});
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
if (syl.vowel && syl.vowel !== "N") prevVowel = syl.vowel;
|
|
1053
|
+
result.push(syl);
|
|
1054
|
+
}
|
|
1055
|
+
return result;
|
|
1056
|
+
};
|
|
1057
|
+
var normalizeLyrics = (text) => resolveLongVowels(splitSyllables(sanitizeText(text)).map(analyzeSyllable));
|
|
1058
|
+
var normalizeLyricLines = (lines) => {
|
|
1059
|
+
const syllables = [];
|
|
1060
|
+
const lineBreaks = [];
|
|
1061
|
+
for (const line of lines) {
|
|
1062
|
+
const part = normalizeLyrics(line);
|
|
1063
|
+
if (part.length === 0) continue;
|
|
1064
|
+
if (syllables.length > 0) lineBreaks.push(syllables.length);
|
|
1065
|
+
syllables.push(...part);
|
|
1066
|
+
}
|
|
1067
|
+
return { syllables, lineBreaks };
|
|
1068
|
+
};
|
|
1069
|
+
var LYRIC_LINE = /^@@(\d+)\s+(.*)$/;
|
|
1070
|
+
var isLyricContinuation = (seg) => !/^[@#]/.test(seg);
|
|
1071
|
+
var splitSegments = (mml) => mml.split(/[;\n\r]+/).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1072
|
+
var clamp = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
1073
|
+
var MAX_VOCAL_VOLUME = 400;
|
|
1074
|
+
var VOCAL_BOOST_DB_PER_PERCENT = 0.08;
|
|
1075
|
+
var vocalVolumeToGain = (v) => {
|
|
1076
|
+
if (v <= 0) return 0;
|
|
1077
|
+
if (v <= 100) return v / 100;
|
|
1078
|
+
return 10 ** ((v - 100) * VOCAL_BOOST_DB_PER_PERCENT / 20);
|
|
1079
|
+
};
|
|
1080
|
+
var parseLyrics = (mml) => {
|
|
1081
|
+
const tracks = /* @__PURE__ */ new Map();
|
|
1082
|
+
const segments = splitSegments(mml);
|
|
1083
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1084
|
+
const m = segments[i].match(LYRIC_LINE);
|
|
1085
|
+
if (!m) continue;
|
|
1086
|
+
const trackId = Number.parseInt(m[1], 10);
|
|
1087
|
+
const tokens = m[2].trim().split(/\s+/);
|
|
1088
|
+
const modelToken = tokens.shift() ?? "";
|
|
1089
|
+
let volume = DEFAULT_VOCAL_VOLUME;
|
|
1090
|
+
let gate = 100;
|
|
1091
|
+
let pan = 64;
|
|
1092
|
+
let octave = 0;
|
|
1093
|
+
const colon = modelToken.indexOf(":");
|
|
1094
|
+
const model = (colon === -1 ? modelToken : modelToken.slice(0, colon)).toLowerCase();
|
|
1095
|
+
if (colon !== -1) {
|
|
1096
|
+
const v = Number.parseInt(modelToken.slice(colon + 1), 10);
|
|
1097
|
+
if (Number.isFinite(v)) volume = clamp(v, 0, MAX_VOCAL_VOLUME);
|
|
1098
|
+
}
|
|
1099
|
+
const metaTokens = [modelToken];
|
|
1100
|
+
while (tokens.length > 0) {
|
|
1101
|
+
const v = /^v(\d+)$/.exec(tokens[0]);
|
|
1102
|
+
const q2 = /^q(\d+)$/.exec(tokens[0]);
|
|
1103
|
+
const p = /^p(\d+)$/.exec(tokens[0]);
|
|
1104
|
+
const o = /^o(-?\d+)$/.exec(tokens[0]);
|
|
1105
|
+
if (v) {
|
|
1106
|
+
volume = clamp(Number.parseInt(v[1], 10), 0, MAX_VOCAL_VOLUME);
|
|
1107
|
+
} else if (q2) {
|
|
1108
|
+
gate = clamp(Number.parseInt(q2[1], 10), 0, 100);
|
|
1109
|
+
} else if (p) {
|
|
1110
|
+
pan = clamp(Number.parseInt(p[1], 10), 0, 127);
|
|
1111
|
+
} else if (o) {
|
|
1112
|
+
octave = clamp(Number.parseInt(o[1], 10), -2, 2);
|
|
1113
|
+
} else {
|
|
1114
|
+
break;
|
|
1115
|
+
}
|
|
1116
|
+
metaTokens.push(tokens.shift());
|
|
1117
|
+
}
|
|
1118
|
+
const lyricLines = [tokens.join(" ")];
|
|
1119
|
+
while (i + 1 < segments.length && isLyricContinuation(segments[i + 1])) {
|
|
1120
|
+
lyricLines.push(segments[++i]);
|
|
1121
|
+
}
|
|
1122
|
+
const { syllables, lineBreaks } = normalizeLyricLines(lyricLines);
|
|
1123
|
+
tracks.set(trackId, {
|
|
1124
|
+
trackId,
|
|
1125
|
+
model,
|
|
1126
|
+
volume,
|
|
1127
|
+
gate,
|
|
1128
|
+
pan,
|
|
1129
|
+
octave,
|
|
1130
|
+
syllables,
|
|
1131
|
+
metaText: metaTokens.join(" "),
|
|
1132
|
+
...lineBreaks.length > 0 ? { lineBreaks } : {}
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
return tracks;
|
|
1136
|
+
};
|
|
1137
|
+
var stripLyrics = (mml) => {
|
|
1138
|
+
const segments = splitSegments(mml);
|
|
1139
|
+
const kept = [];
|
|
1140
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1141
|
+
if (LYRIC_LINE.test(segments[i])) {
|
|
1142
|
+
while (i + 1 < segments.length && isLyricContinuation(segments[i + 1]))
|
|
1143
|
+
i++;
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
kept.push(segments[i]);
|
|
1147
|
+
}
|
|
1148
|
+
return kept.join("\n");
|
|
1149
|
+
};
|
|
1150
|
+
var panToStereo = (pan) => Math.max(-1, Math.min(1, (pan - 64) / 64));
|
|
1151
|
+
var createLyricsConductor = (lyrics) => {
|
|
1152
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
1153
|
+
const consume = (trackId) => {
|
|
1154
|
+
const track = lyrics.get(trackId);
|
|
1155
|
+
if (!track || track.syllables.length === 0) return null;
|
|
1156
|
+
const ptr = pointers.get(trackId) ?? 0;
|
|
1157
|
+
const syllable = track.syllables[ptr];
|
|
1158
|
+
if (!syllable) return null;
|
|
1159
|
+
pointers.set(trackId, ptr + 1);
|
|
1160
|
+
return {
|
|
1161
|
+
model: track.model,
|
|
1162
|
+
syllable,
|
|
1163
|
+
volume: vocalVolumeToGain(track.volume ?? DEFAULT_VOCAL_VOLUME),
|
|
1164
|
+
gate: (track.gate ?? DEFAULT_GATE) / 100,
|
|
1165
|
+
pan: panToStereo(track.pan ?? DEFAULT_PAN)
|
|
1166
|
+
};
|
|
1167
|
+
};
|
|
1168
|
+
const reset = () => pointers.clear();
|
|
1169
|
+
return { consume, reset };
|
|
1170
|
+
};
|
|
1171
|
+
var FORMANTS = {
|
|
1172
|
+
a: [800, 1200],
|
|
1173
|
+
i: [300, 2300],
|
|
1174
|
+
u: [350, 800],
|
|
1175
|
+
e: [500, 1900],
|
|
1176
|
+
o: [500, 900],
|
|
1177
|
+
// 撥音(ん)は鼻音寄りの低フォルマント
|
|
1178
|
+
N: [250, 1e3]
|
|
1179
|
+
};
|
|
1180
|
+
var midiToFreq = (m) => 440 * 2 ** ((m - 69) / 12);
|
|
1181
|
+
var createKlattVoice = (ctx, destination) => {
|
|
1182
|
+
const active = /* @__PURE__ */ new Set();
|
|
1183
|
+
const voice = (syllable, e) => {
|
|
1184
|
+
const t0 = ctx.currentTime + e.when;
|
|
1185
|
+
const peak = Math.max(1e-4, e.volume);
|
|
1186
|
+
if (syllable.vowel === "" || syllable.consonant === "Q") return;
|
|
1187
|
+
const [f1, f2] = FORMANTS[syllable.vowel] ?? FORMANTS.a;
|
|
1188
|
+
const attack = 0.02;
|
|
1189
|
+
const release = 0.06;
|
|
1190
|
+
const sustainEnd = t0 + Math.max(attack + 0.02, e.duration);
|
|
1191
|
+
let panner = null;
|
|
1192
|
+
let out = destination;
|
|
1193
|
+
if (typeof ctx.createStereoPanner === "function") {
|
|
1194
|
+
panner = ctx.createStereoPanner();
|
|
1195
|
+
panner.pan.value = Math.max(-1, Math.min(1, e.pan ?? 0));
|
|
1196
|
+
panner.connect(destination);
|
|
1197
|
+
out = panner;
|
|
1198
|
+
}
|
|
1199
|
+
const osc = ctx.createOscillator();
|
|
1200
|
+
osc.type = "sawtooth";
|
|
1201
|
+
osc.frequency.value = midiToFreq(e.pitch);
|
|
1202
|
+
const makeFormant = (freq, q2, gainScale) => {
|
|
1203
|
+
const filter = ctx.createBiquadFilter();
|
|
1204
|
+
filter.type = "bandpass";
|
|
1205
|
+
filter.frequency.value = freq;
|
|
1206
|
+
filter.Q.value = q2;
|
|
1207
|
+
const g = ctx.createGain();
|
|
1208
|
+
g.gain.value = gainScale;
|
|
1209
|
+
osc.connect(filter).connect(g);
|
|
1210
|
+
return g;
|
|
1211
|
+
};
|
|
1212
|
+
const env = ctx.createGain();
|
|
1213
|
+
env.gain.setValueAtTime(1e-4, t0);
|
|
1214
|
+
env.gain.exponentialRampToValueAtTime(peak, t0 + attack);
|
|
1215
|
+
env.gain.setValueAtTime(peak, sustainEnd);
|
|
1216
|
+
env.gain.exponentialRampToValueAtTime(1e-4, sustainEnd + release);
|
|
1217
|
+
const MAKEUP = 4;
|
|
1218
|
+
makeFormant(f1, 6, MAKEUP).connect(env);
|
|
1219
|
+
makeFormant(f2, 9, MAKEUP * 0.7).connect(env);
|
|
1220
|
+
env.connect(out);
|
|
1221
|
+
const fricatives = /* @__PURE__ */ new Set(["s", "sh", "ch", "ts", "h", "f"]);
|
|
1222
|
+
if (fricatives.has(syllable.consonant)) {
|
|
1223
|
+
const dur = 0.05;
|
|
1224
|
+
const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
|
|
1225
|
+
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
|
|
1226
|
+
const data = buffer.getChannelData(0);
|
|
1227
|
+
for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
|
|
1228
|
+
const src = ctx.createBufferSource();
|
|
1229
|
+
src.buffer = buffer;
|
|
1230
|
+
const hp = ctx.createBiquadFilter();
|
|
1231
|
+
hp.type = "highpass";
|
|
1232
|
+
hp.frequency.value = syllable.consonant === "sh" ? 3e3 : 4500;
|
|
1233
|
+
const ng = ctx.createGain();
|
|
1234
|
+
ng.gain.setValueAtTime(peak * 0.5, t0);
|
|
1235
|
+
ng.gain.exponentialRampToValueAtTime(1e-4, t0 + dur);
|
|
1236
|
+
src.connect(hp).connect(ng).connect(out);
|
|
1237
|
+
src.start(t0);
|
|
1238
|
+
src.stop(t0 + dur);
|
|
1239
|
+
active.add(src);
|
|
1240
|
+
src.onended = () => {
|
|
1241
|
+
active.delete(src);
|
|
1242
|
+
src.disconnect();
|
|
1243
|
+
hp.disconnect();
|
|
1244
|
+
ng.disconnect();
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
osc.start(t0);
|
|
1248
|
+
osc.stop(sustainEnd + release + 0.02);
|
|
1249
|
+
active.add(osc);
|
|
1250
|
+
osc.onended = () => {
|
|
1251
|
+
active.delete(osc);
|
|
1252
|
+
osc.disconnect();
|
|
1253
|
+
panner?.disconnect();
|
|
1254
|
+
};
|
|
1255
|
+
};
|
|
1256
|
+
voice.stopAll = () => {
|
|
1257
|
+
for (const n of active) {
|
|
1258
|
+
try {
|
|
1259
|
+
n.stop();
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
n.disconnect();
|
|
1263
|
+
}
|
|
1264
|
+
active.clear();
|
|
1265
|
+
};
|
|
1266
|
+
return voice;
|
|
1267
|
+
};
|
|
1268
|
+
var KOE_BASE_URL = "https://pub-12482a6b5cbc4c9e906b2e1904cabae5.r2.dev";
|
|
1269
|
+
var KOE_VOICEBANKS = {
|
|
1270
|
+
tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093.koe",
|
|
1271
|
+
rino: "\u6625\u97F3\u30EA\u30CEver0.3.koe",
|
|
1272
|
+
roze: "\u675F\u97F3\u30ED\u30BCver0.\uFF151(\u591A\u97F3\u968E).koe",
|
|
1273
|
+
ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642\u9023\u7D9A\u97F3Ver.1.03.koe",
|
|
1274
|
+
ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640\u6B4C\u9023\u7D9A\u97F3\u666E1.00.koe",
|
|
1275
|
+
teto: "\u91CD\u97F3\u30C6\u30C8\u5358\u72EC\u97F3.koe",
|
|
1276
|
+
shiyo: "\u9769\u547D\u30B7\u30E8.koe"
|
|
1277
|
+
};
|
|
1278
|
+
var KOE_VOICEBANK_LABELS = {
|
|
1279
|
+
tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093",
|
|
1280
|
+
rino: "\u6625\u97F3\u30EA\u30CE",
|
|
1281
|
+
roze: "\u675F\u97F3\u30ED\u30BC",
|
|
1282
|
+
ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642",
|
|
1283
|
+
ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640",
|
|
1284
|
+
teto: "\u91CD\u97F3\u30C6\u30C8",
|
|
1285
|
+
shiyo: "\u9769\u547D\u30B7\u30E8"
|
|
1286
|
+
};
|
|
1287
|
+
var VOICE_IMAGE_KEY = {
|
|
1288
|
+
klatt: "puyuyu",
|
|
1289
|
+
tsukuyomi: "tsukuyomi",
|
|
1290
|
+
rino: "rino",
|
|
1291
|
+
roze: "roze",
|
|
1292
|
+
ruko_male: "ruko",
|
|
1293
|
+
ruko_female: "ruko",
|
|
1294
|
+
teto: "teto",
|
|
1295
|
+
shiyo: "shiyo"
|
|
1296
|
+
};
|
|
1297
|
+
var KOE_VOICEBANK_TERMS = {
|
|
1298
|
+
tsukuyomi: "https://tyc.rei-yumesaki.net/material/utau/terms/",
|
|
1299
|
+
rino: "https://hatenakun1.github.io/halunelino/",
|
|
1300
|
+
roze: "https://tabaneroze.ninja-web.net/terms-of-use.html",
|
|
1301
|
+
ruko_male: "https://long-sleeper.net/index.php?id=22",
|
|
1302
|
+
ruko_female: "https://long-sleeper.net/index.php?id=22",
|
|
1303
|
+
teto: "https://kasaneteto.jp/guidelines/voice.html",
|
|
1304
|
+
shiyo: "https://kakumeisiyo.my.canva.site/dagkuyjwycs"
|
|
1305
|
+
};
|
|
1306
|
+
var koeUrl = (name, base = KOE_BASE_URL) => `${base}/${encodeURIComponent(name)}`;
|
|
1307
|
+
var DEFAULT_WORLDLINE_SCRIPT = "https://onjmin.github.io/koe/demo/world/worldline.js";
|
|
1308
|
+
var KOE_SAMPLE_RATE = 48e3;
|
|
1309
|
+
var expandSeparators = (candidate) => candidate.includes(" ") ? [candidate, candidate.replace(/ /g, "\u3000"), candidate.replace(/ /g, "")] : [candidate];
|
|
1310
|
+
var PITCH_SUFFIX = /_([A-G][#b]?-?\d+)$/;
|
|
1311
|
+
var NAME_SEMITONE = {
|
|
1312
|
+
c: 0,
|
|
1313
|
+
d: 2,
|
|
1314
|
+
e: 4,
|
|
1315
|
+
f: 5,
|
|
1316
|
+
g: 7,
|
|
1317
|
+
a: 9,
|
|
1318
|
+
b: 11
|
|
1319
|
+
};
|
|
1320
|
+
var pitchTokenToMidi = (token) => {
|
|
1321
|
+
const m = /^([A-Ga-g])([#b]?)(-?\d+)$/.exec(token);
|
|
1322
|
+
if (!m) return null;
|
|
1323
|
+
let semi = NAME_SEMITONE[m[1].toLowerCase()];
|
|
1324
|
+
if (m[2] === "#") semi++;
|
|
1325
|
+
else if (m[2] === "b") semi--;
|
|
1326
|
+
return (Number.parseInt(m[3], 10) + 1) * 12 + semi;
|
|
1327
|
+
};
|
|
1328
|
+
var collectPitchTokens = (aliases) => {
|
|
1329
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1330
|
+
for (const a of aliases) {
|
|
1331
|
+
const m = PITCH_SUFFIX.exec(a);
|
|
1332
|
+
if (!m || seen.has(m[1])) continue;
|
|
1333
|
+
const midi = pitchTokenToMidi(m[1]);
|
|
1334
|
+
if (midi != null) seen.set(m[1], midi);
|
|
1335
|
+
}
|
|
1336
|
+
return [...seen].map(([token, midi]) => ({ token, midi }));
|
|
1337
|
+
};
|
|
1338
|
+
var resolveKoeAlias = (hasAlias, pitchTokens, syl, prevVowel, noteNum) => {
|
|
1339
|
+
const kana = syl.kana;
|
|
1340
|
+
const cons = syl.consonant === "N" ? "n" : syl.consonant;
|
|
1341
|
+
const vow = syl.vowel === "N" ? "" : syl.vowel;
|
|
1342
|
+
const romaji = `${cons}${vow}` || vow;
|
|
1343
|
+
const pv = prevVowel || "-";
|
|
1344
|
+
const raw = [
|
|
1345
|
+
// 連続音(VCV): 直前母音つき
|
|
1346
|
+
`${pv} ${kana}`,
|
|
1347
|
+
`${pv} ${romaji}`,
|
|
1348
|
+
// 単独音 / CVVC
|
|
1349
|
+
kana,
|
|
1350
|
+
romaji
|
|
1351
|
+
];
|
|
1352
|
+
const vk = VOWEL_KANA[syl.vowel];
|
|
1353
|
+
if (vk) raw.push(`${pv} ${vk}`, vk, syl.vowel);
|
|
1354
|
+
if (syl.vowel === "N") raw.push("\u3093", "n", "N", `${pv} \u3093`);
|
|
1355
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1356
|
+
const tryAlias = (candidate) => {
|
|
1357
|
+
for (const v of expandSeparators(candidate)) {
|
|
1358
|
+
if (seen.has(v)) continue;
|
|
1359
|
+
seen.add(v);
|
|
1360
|
+
if (hasAlias(v)) return v;
|
|
1361
|
+
}
|
|
1362
|
+
return null;
|
|
1363
|
+
};
|
|
1364
|
+
if (pitchTokens.length) {
|
|
1365
|
+
const nearest = pitchTokens.slice().sort((a, b) => Math.abs(a.midi - noteNum) - Math.abs(b.midi - noteNum));
|
|
1366
|
+
for (const { token } of nearest) {
|
|
1367
|
+
for (const base of raw) {
|
|
1368
|
+
const hit = tryAlias(`${base}_${token}`);
|
|
1369
|
+
if (hit) return hit;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
for (const base of raw) {
|
|
1374
|
+
const hit = tryAlias(base);
|
|
1375
|
+
if (hit) return hit;
|
|
1376
|
+
}
|
|
1377
|
+
return null;
|
|
1378
|
+
};
|
|
1379
|
+
var createLocalBackend = async (options) => {
|
|
1380
|
+
const bank = await VoiceBank.load(options.koe);
|
|
1381
|
+
const worldline = options.lightweight ? null : await Worldline.load({
|
|
1382
|
+
scriptUrl: options.worldlineScriptUrl ?? DEFAULT_WORLDLINE_SCRIPT
|
|
1383
|
+
}).catch(() => null);
|
|
1384
|
+
const pcmCache = /* @__PURE__ */ new Map();
|
|
1385
|
+
const getPcm = (alias) => {
|
|
1386
|
+
let p = pcmCache.get(alias);
|
|
1387
|
+
if (!p) {
|
|
1388
|
+
p = bank.getPcm(alias);
|
|
1389
|
+
pcmCache.set(alias, p);
|
|
1390
|
+
}
|
|
1391
|
+
return p;
|
|
1392
|
+
};
|
|
1393
|
+
const renderAlias = async (alias, pitch, durationMs) => {
|
|
1394
|
+
const pcm = await getPcm(alias);
|
|
1395
|
+
if (!pcm || pcm.length === 0) return null;
|
|
1396
|
+
const entry = bank.manifest.phonemes[alias];
|
|
1397
|
+
const lead = leadInFromEntry(entry);
|
|
1398
|
+
const targetHz = midiToFreq(pitch);
|
|
1399
|
+
if (worldline) {
|
|
1400
|
+
const audio = worldline.renderNote({
|
|
1401
|
+
pcm,
|
|
1402
|
+
pitch: targetHz,
|
|
1403
|
+
durationMs,
|
|
1404
|
+
...lead
|
|
1405
|
+
});
|
|
1406
|
+
if (audio) return { pcm: audio, preSec: lead.preMs / 1e3, rate: 1 };
|
|
1407
|
+
}
|
|
1408
|
+
const rate = entry.pitch > 0 ? targetHz / entry.pitch : 1;
|
|
1409
|
+
return {
|
|
1410
|
+
pcm: Float32Array.from(pcm),
|
|
1411
|
+
preSec: entry.pre / KOE_SAMPLE_RATE / rate,
|
|
1412
|
+
rate
|
|
1413
|
+
};
|
|
1414
|
+
};
|
|
1415
|
+
return {
|
|
1416
|
+
hasAlias: (a) => bank.has(a),
|
|
1417
|
+
pitchTokens: collectPitchTokens(Object.keys(bank.manifest.phonemes)),
|
|
1418
|
+
renderAlias,
|
|
1419
|
+
dispose: () => {
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
};
|
|
1423
|
+
var spawnVoiceWorker = async (url) => {
|
|
1424
|
+
const sameOrigin = new URL(url, location.href).origin === location.origin;
|
|
1425
|
+
if (sameOrigin) return new Worker(url);
|
|
1426
|
+
const text = await fetch(url).then((r) => r.text());
|
|
1427
|
+
return new Worker(
|
|
1428
|
+
URL.createObjectURL(new Blob([text], { type: "text/javascript" }))
|
|
1429
|
+
);
|
|
1430
|
+
};
|
|
1431
|
+
var createWorkerBackend = async (workerUrl, options) => {
|
|
1432
|
+
const worker = await spawnVoiceWorker(workerUrl);
|
|
1433
|
+
const aliasSet = /* @__PURE__ */ new Set();
|
|
1434
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1435
|
+
let reqId = 0;
|
|
1436
|
+
let onReady = null;
|
|
1437
|
+
let onFail = null;
|
|
1438
|
+
worker.onmessage = (ev) => {
|
|
1439
|
+
const m = ev.data;
|
|
1440
|
+
if (m.type === "ready") {
|
|
1441
|
+
for (const a of m.aliases) aliasSet.add(a);
|
|
1442
|
+
onReady?.();
|
|
1443
|
+
} else if (m.type === "error") {
|
|
1444
|
+
onFail?.(new Error(m.message));
|
|
1445
|
+
} else if (m.type === "rendered") {
|
|
1446
|
+
const cb = pending.get(m.id);
|
|
1447
|
+
if (cb) {
|
|
1448
|
+
pending.delete(m.id);
|
|
1449
|
+
cb(m);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
worker.onerror = (e) => {
|
|
1454
|
+
const ev = e;
|
|
1455
|
+
onFail?.(new Error(ev.message || ev.error || `Event: ${ev.type}`));
|
|
1456
|
+
};
|
|
1457
|
+
await new Promise((resolve, reject) => {
|
|
1458
|
+
onReady = resolve;
|
|
1459
|
+
onFail = reject;
|
|
1460
|
+
worker.postMessage({
|
|
1461
|
+
type: "init",
|
|
1462
|
+
koe: options.koe,
|
|
1463
|
+
worldlineScriptUrl: options.worldlineScriptUrl ?? DEFAULT_WORLDLINE_SCRIPT,
|
|
1464
|
+
lightweight: !!options.lightweight
|
|
1465
|
+
});
|
|
1466
|
+
});
|
|
1467
|
+
onReady = null;
|
|
1468
|
+
onFail = null;
|
|
1469
|
+
const renderAlias = (alias, pitch, durationMs) => new Promise((resolve) => {
|
|
1470
|
+
const id = ++reqId;
|
|
1471
|
+
pending.set(
|
|
1472
|
+
id,
|
|
1473
|
+
(m) => resolve(
|
|
1474
|
+
m.pcm ? { pcm: m.pcm, preSec: m.preSec ?? 0, rate: m.rate ?? 1 } : null
|
|
1475
|
+
)
|
|
1476
|
+
);
|
|
1477
|
+
worker.postMessage({
|
|
1478
|
+
type: "render",
|
|
1479
|
+
id,
|
|
1480
|
+
alias,
|
|
1481
|
+
pitch,
|
|
1482
|
+
durationMs
|
|
1483
|
+
});
|
|
1484
|
+
});
|
|
1485
|
+
return {
|
|
1486
|
+
hasAlias: (a) => aliasSet.has(a),
|
|
1487
|
+
pitchTokens: collectPitchTokens(aliasSet),
|
|
1488
|
+
renderAlias,
|
|
1489
|
+
dispose: () => worker.terminate()
|
|
1490
|
+
};
|
|
1491
|
+
};
|
|
1492
|
+
var createKoeVoice = async (ctx, destination, options) => {
|
|
1493
|
+
const backend = options.voiceWorkerUrl ? await createWorkerBackend(options.voiceWorkerUrl, options) : await createLocalBackend(options);
|
|
1494
|
+
const renderCache = /* @__PURE__ */ new Map();
|
|
1495
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
1496
|
+
const active = /* @__PURE__ */ new Set();
|
|
1497
|
+
let prevVowel = "";
|
|
1498
|
+
const keyOf = (alias, pitch, durationMs) => `${alias}|${pitch}|${Math.round(durationMs / 10) * 10}`;
|
|
1499
|
+
const renderInto = (alias, pitch, durationMs) => {
|
|
1500
|
+
const key = keyOf(alias, pitch, durationMs);
|
|
1501
|
+
const existing = renderCache.get(key);
|
|
1502
|
+
if (existing !== void 0) return Promise.resolve(existing);
|
|
1503
|
+
const flying = inflight.get(key);
|
|
1504
|
+
if (flying) return flying;
|
|
1505
|
+
const p = (async () => {
|
|
1506
|
+
const out = await backend.renderAlias(alias, pitch, durationMs);
|
|
1507
|
+
let rendered = null;
|
|
1508
|
+
if (out) {
|
|
1509
|
+
const buf = ctx.createBuffer(1, out.pcm.length, KOE_SAMPLE_RATE);
|
|
1510
|
+
buf.copyToChannel(out.pcm, 0);
|
|
1511
|
+
rendered = { audio: buf, preSec: out.preSec, rate: out.rate };
|
|
1512
|
+
}
|
|
1513
|
+
renderCache.set(key, rendered);
|
|
1514
|
+
inflight.delete(key);
|
|
1515
|
+
return rendered;
|
|
1516
|
+
})();
|
|
1517
|
+
inflight.set(key, p);
|
|
1518
|
+
return p;
|
|
1519
|
+
};
|
|
1520
|
+
const LEADCAP_S = 0.09;
|
|
1521
|
+
const schedule = (r, t0, peak, pan) => {
|
|
1522
|
+
let out = destination;
|
|
1523
|
+
let panner = null;
|
|
1524
|
+
if (typeof ctx.createStereoPanner === "function") {
|
|
1525
|
+
panner = ctx.createStereoPanner();
|
|
1526
|
+
panner.pan.value = Math.max(-1, Math.min(1, pan));
|
|
1527
|
+
panner.connect(destination);
|
|
1528
|
+
out = panner;
|
|
1529
|
+
}
|
|
1530
|
+
const src = ctx.createBufferSource();
|
|
1531
|
+
src.buffer = r.audio;
|
|
1532
|
+
src.playbackRate.value = r.rate;
|
|
1533
|
+
const effPre = Math.min(r.preSec, LEADCAP_S);
|
|
1534
|
+
const skipS = r.preSec - effPre;
|
|
1535
|
+
const startAt = Math.max(ctx.currentTime + 1e-3, t0 - effPre);
|
|
1536
|
+
const playDurSec = r.audio.duration / r.rate - skipS;
|
|
1537
|
+
const endAt = startAt + playDurSec;
|
|
1538
|
+
const attack = 0.01;
|
|
1539
|
+
const release = 0.04;
|
|
1540
|
+
const env = ctx.createGain();
|
|
1541
|
+
env.gain.setValueAtTime(1e-4, startAt);
|
|
1542
|
+
env.gain.exponentialRampToValueAtTime(peak, startAt + attack);
|
|
1543
|
+
const fadeStart = Math.max(startAt + attack, endAt - release);
|
|
1544
|
+
env.gain.setValueAtTime(peak, fadeStart);
|
|
1545
|
+
env.gain.exponentialRampToValueAtTime(1e-4, endAt);
|
|
1546
|
+
src.connect(env).connect(out);
|
|
1547
|
+
src.start(startAt, skipS);
|
|
1548
|
+
src.stop(endAt + 0.02);
|
|
1549
|
+
active.add(src);
|
|
1550
|
+
src.onended = () => {
|
|
1551
|
+
active.delete(src);
|
|
1552
|
+
src.disconnect();
|
|
1553
|
+
env.disconnect();
|
|
1554
|
+
panner?.disconnect();
|
|
1555
|
+
};
|
|
1556
|
+
};
|
|
1557
|
+
const model = (syllable, e) => {
|
|
1558
|
+
if (syllable.consonant === "Q" || syllable.vowel === "") return;
|
|
1559
|
+
const alias = resolveKoeAlias(
|
|
1560
|
+
backend.hasAlias,
|
|
1561
|
+
backend.pitchTokens,
|
|
1562
|
+
syllable,
|
|
1563
|
+
prevVowel,
|
|
1564
|
+
e.pitch
|
|
1565
|
+
);
|
|
1566
|
+
if (syllable.vowel && syllable.vowel !== "N") prevVowel = syllable.vowel;
|
|
1567
|
+
if (!alias) return;
|
|
1568
|
+
const t0 = ctx.currentTime + e.when;
|
|
1569
|
+
const peak = Math.max(1e-4, e.volume);
|
|
1570
|
+
const pan = e.pan ?? 0;
|
|
1571
|
+
const durationMs = Math.max(60, e.duration * 1e3);
|
|
1572
|
+
void renderInto(alias, e.pitch, durationMs).then((r) => {
|
|
1573
|
+
if (r) schedule(r, t0, peak, pan);
|
|
1574
|
+
});
|
|
1575
|
+
};
|
|
1576
|
+
model.renderToCache = async (syllable, prevVowelArg, pitch, durationMs) => {
|
|
1577
|
+
if (syllable.consonant === "Q" || syllable.vowel === "") return null;
|
|
1578
|
+
const alias = resolveKoeAlias(
|
|
1579
|
+
backend.hasAlias,
|
|
1580
|
+
backend.pitchTokens,
|
|
1581
|
+
syllable,
|
|
1582
|
+
prevVowelArg,
|
|
1583
|
+
pitch
|
|
1584
|
+
);
|
|
1585
|
+
if (!alias) return null;
|
|
1586
|
+
const dMs = Math.max(60, durationMs);
|
|
1587
|
+
const r = await renderInto(alias, pitch, dMs);
|
|
1588
|
+
return r ? keyOf(alias, pitch, dMs) : null;
|
|
1589
|
+
};
|
|
1590
|
+
model.scheduleCached = (key, t0, peak, pan) => {
|
|
1591
|
+
const r = renderCache.get(key);
|
|
1592
|
+
if (r) schedule(r, t0, peak, pan);
|
|
1593
|
+
};
|
|
1594
|
+
model.stopAll = () => {
|
|
1595
|
+
for (const src of active) {
|
|
1596
|
+
try {
|
|
1597
|
+
src.stop();
|
|
1598
|
+
} catch {
|
|
1599
|
+
}
|
|
1600
|
+
src.disconnect();
|
|
1601
|
+
}
|
|
1602
|
+
active.clear();
|
|
1603
|
+
};
|
|
1604
|
+
model.reset = () => {
|
|
1605
|
+
prevVowel = "";
|
|
1606
|
+
};
|
|
1607
|
+
return model;
|
|
1608
|
+
};
|
|
1609
|
+
var PREWARM_NOTES = 3;
|
|
1610
|
+
var STREAM_LOOKAHEAD_SEC = 1.5;
|
|
1611
|
+
var STREAM_POLL_MS = 100;
|
|
1612
|
+
var FALLBACK_MODEL = "klatt";
|
|
1613
|
+
var createSingingVoices = (ctx, destination, options = {}) => {
|
|
1614
|
+
const catalog = {};
|
|
1615
|
+
for (const [k, file] of Object.entries(KOE_VOICEBANKS))
|
|
1616
|
+
catalog[k] = koeUrl(file);
|
|
1617
|
+
for (const [k, v] of Object.entries(options.voicebanks ?? {}))
|
|
1618
|
+
catalog[k.toLowerCase()] = v;
|
|
1619
|
+
let streamSession = 0;
|
|
1620
|
+
const loaded = /* @__PURE__ */ new Map([
|
|
1621
|
+
[FALLBACK_MODEL, createKlattVoice(ctx, destination)]
|
|
1622
|
+
]);
|
|
1623
|
+
const loading = /* @__PURE__ */ new Map();
|
|
1624
|
+
const load = (model) => {
|
|
1625
|
+
const m = model.toLowerCase();
|
|
1626
|
+
const ready = loaded.get(m);
|
|
1627
|
+
if (ready) return Promise.resolve(ready);
|
|
1628
|
+
const inflight = loading.get(m);
|
|
1629
|
+
if (inflight) return inflight;
|
|
1630
|
+
const koe = catalog[m];
|
|
1631
|
+
if (!koe) return Promise.resolve(null);
|
|
1632
|
+
const p = (async () => (
|
|
1633
|
+
// URL文字列はそのまま渡す。koe側が VoiceBank.load 内で HTTP Range により
|
|
1634
|
+
// マニフェストだけ先読みし、音素PCMは歌う直前にオンデマンド取得する
|
|
1635
|
+
// (= 初回に .koe 全体をDLしない。モバイル初回ロードの待ちを解消)。
|
|
1636
|
+
// Blob/File が直接渡されたケース(ローカル読み込み)はそのまま BlobVoiceSource。
|
|
1637
|
+
createKoeVoice(ctx, destination, {
|
|
1638
|
+
koe,
|
|
1639
|
+
worldlineScriptUrl: options.worldlineScriptUrl,
|
|
1640
|
+
lightweight: options.lightweight,
|
|
1641
|
+
voiceWorkerUrl: options.voiceWorkerUrl
|
|
1642
|
+
})
|
|
1643
|
+
))().then((v) => {
|
|
1644
|
+
loaded.set(m, v);
|
|
1645
|
+
return v;
|
|
1646
|
+
}).catch((err) => {
|
|
1647
|
+
console.warn(`[dtm] koe\u97F3\u6E90 "${m}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F`, err);
|
|
1648
|
+
return null;
|
|
1649
|
+
});
|
|
1650
|
+
loading.set(m, p);
|
|
1651
|
+
return p;
|
|
1652
|
+
};
|
|
1653
|
+
const loadModels = async (models) => {
|
|
1654
|
+
const set = /* @__PURE__ */ new Set();
|
|
1655
|
+
for (const m of models) if (m) set.add(m.toLowerCase());
|
|
1656
|
+
await Promise.all([...set].map((m) => load(m)));
|
|
1657
|
+
};
|
|
1658
|
+
const forEachSungNote = (track, fn) => {
|
|
1659
|
+
let prevVowel = "";
|
|
1660
|
+
for (const note of track.notes) {
|
|
1661
|
+
const syl = note.syllable;
|
|
1662
|
+
if (syl.consonant === "Q" || syl.vowel === "") continue;
|
|
1663
|
+
fn(note, prevVowel);
|
|
1664
|
+
if (syl.vowel && syl.vowel !== "N") prevVowel = syl.vowel;
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1667
|
+
const warm = async (tracks, count = PREWARM_NOTES) => {
|
|
1668
|
+
const promises = [];
|
|
1669
|
+
for (const track of tracks) {
|
|
1670
|
+
const m = loaded.get(track.model.toLowerCase());
|
|
1671
|
+
if (!m?.renderToCache) continue;
|
|
1672
|
+
let n = 0;
|
|
1673
|
+
forEachSungNote(track, (note, prevVowel) => {
|
|
1674
|
+
if (n >= count) return;
|
|
1675
|
+
n++;
|
|
1676
|
+
promises.push(
|
|
1677
|
+
m.renderToCache?.(
|
|
1678
|
+
note.syllable,
|
|
1679
|
+
prevVowel,
|
|
1680
|
+
note.pitch,
|
|
1681
|
+
note.durationSec * 1e3
|
|
1682
|
+
) ?? Promise.resolve(null)
|
|
1683
|
+
);
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
await Promise.all(promises);
|
|
1687
|
+
};
|
|
1688
|
+
const startStream = (tracks, anchorTime) => {
|
|
1689
|
+
const session = ++streamSession;
|
|
1690
|
+
const items = [];
|
|
1691
|
+
for (const track of tracks) {
|
|
1692
|
+
const model = loaded.get(track.model.toLowerCase());
|
|
1693
|
+
if (!model) continue;
|
|
1694
|
+
forEachSungNote(track, (note, prevVowel) => {
|
|
1695
|
+
items.push({
|
|
1696
|
+
model,
|
|
1697
|
+
note,
|
|
1698
|
+
prevVowel,
|
|
1699
|
+
volume: track.volume,
|
|
1700
|
+
pan: track.pan
|
|
1701
|
+
});
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
items.sort((a, b) => a.note.startSec - b.note.startSec);
|
|
1705
|
+
void (async () => {
|
|
1706
|
+
for (const item of items) {
|
|
1707
|
+
if (session !== streamSession) return;
|
|
1708
|
+
while (item.note.startSec - (ctx.currentTime - anchorTime) > STREAM_LOOKAHEAD_SEC) {
|
|
1709
|
+
await new Promise((resolve) => setTimeout(resolve, STREAM_POLL_MS));
|
|
1710
|
+
if (session !== streamSession) return;
|
|
1711
|
+
}
|
|
1712
|
+
const t0 = anchorTime + item.note.startSec;
|
|
1713
|
+
const peak = Math.max(1e-4, item.volume);
|
|
1714
|
+
const { model, note } = item;
|
|
1715
|
+
if (model.renderToCache && model.scheduleCached) {
|
|
1716
|
+
const key = await model.renderToCache(
|
|
1717
|
+
note.syllable,
|
|
1718
|
+
item.prevVowel,
|
|
1719
|
+
note.pitch,
|
|
1720
|
+
note.durationSec * 1e3
|
|
1721
|
+
);
|
|
1722
|
+
if (session !== streamSession) return;
|
|
1723
|
+
if (key) model.scheduleCached(key, t0, peak, item.pan);
|
|
1724
|
+
} else {
|
|
1725
|
+
const when = t0 - ctx.currentTime;
|
|
1726
|
+
model(note.syllable, {
|
|
1727
|
+
trackId: "",
|
|
1728
|
+
pitch: note.pitch,
|
|
1729
|
+
velocity: 100,
|
|
1730
|
+
volume: peak,
|
|
1731
|
+
when,
|
|
1732
|
+
duration: note.durationSec,
|
|
1733
|
+
pan: item.pan
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1737
|
+
}
|
|
1738
|
+
})();
|
|
1739
|
+
};
|
|
1740
|
+
const stopStream = () => {
|
|
1741
|
+
streamSession++;
|
|
1742
|
+
for (const v of loaded.values()) v.stopAll?.();
|
|
1743
|
+
};
|
|
1744
|
+
const reset = () => {
|
|
1745
|
+
stopStream();
|
|
1746
|
+
for (const v of loaded.values()) v.reset?.();
|
|
1747
|
+
};
|
|
1748
|
+
return { loadModels, warm, startStream, stopStream, reset };
|
|
1749
|
+
};
|
|
1750
|
+
var createVoiceRegistry = (models = {}, fallback = "klatt") => {
|
|
1751
|
+
const sing = (model, syllable, e) => {
|
|
1752
|
+
const fn = models[model] ?? models[fallback];
|
|
1753
|
+
fn?.(syllable, e);
|
|
1754
|
+
};
|
|
1755
|
+
const register = (name, m) => {
|
|
1756
|
+
models[name.toLowerCase()] = m;
|
|
1757
|
+
};
|
|
1758
|
+
return { sing, register };
|
|
1759
|
+
};
|
|
1760
|
+
|
|
611
1761
|
// src/macros.ts
|
|
612
1762
|
var SCALES = [
|
|
613
1763
|
[0, 2, 4, 5, 7, 9, 11],
|
|
@@ -1014,7 +2164,7 @@ var exportMIDI = (options) => {
|
|
|
1014
2164
|
(n.startStep + (n.durationSteps || 1)) * tickPerStep
|
|
1015
2165
|
);
|
|
1016
2166
|
const vel = Math.round(
|
|
1017
|
-
(n.velocity ??
|
|
2167
|
+
(n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
|
|
1018
2168
|
);
|
|
1019
2169
|
events.push({ t: startTick, m: [144 | ch & 15, n.pitch, vel] });
|
|
1020
2170
|
events.push({ t: endTick, m: [144 | ch & 15, n.pitch, 0] });
|
|
@@ -1569,7 +2719,7 @@ var MMLCore = class _MMLCore {
|
|
|
1569
2719
|
startStep: step,
|
|
1570
2720
|
durationSteps: options.noteLengthSteps,
|
|
1571
2721
|
pitch,
|
|
1572
|
-
velocity: options.velocity ??
|
|
2722
|
+
velocity: options.velocity ?? DEFAULT_VELOCITY
|
|
1573
2723
|
};
|
|
1574
2724
|
this.notes.push(newNote);
|
|
1575
2725
|
}
|
|
@@ -1656,6 +2806,8 @@ var MMLCore = class _MMLCore {
|
|
|
1656
2806
|
const config = getRenderConfig();
|
|
1657
2807
|
const total = config.stepsPerBar;
|
|
1658
2808
|
const candidates = [
|
|
2809
|
+
{ dur: "1.", s: total * 1.5 },
|
|
2810
|
+
// 付点全音符(最長。これ以上はタイ未対応のため表現不可)
|
|
1659
2811
|
{ dur: "1", s: total / 1 },
|
|
1660
2812
|
{ dur: "2.", s: total / 2 * 1.5 },
|
|
1661
2813
|
{ dur: "2", s: total / 2 },
|
|
@@ -1716,97 +2868,72 @@ var MMLCore = class _MMLCore {
|
|
|
1716
2868
|
return { text: `o${octave}${name}`, currentOctave: octave };
|
|
1717
2869
|
}
|
|
1718
2870
|
/**
|
|
1719
|
-
* MML
|
|
2871
|
+
* MML生成(単一パス・発音順スキャン)
|
|
2872
|
+
*
|
|
2873
|
+
* 以前は1/2小節ごとのウィンドウで走査していたが、それだと半小節境界をまたぐ音符が
|
|
2874
|
+
* 境界で切り詰められて「ぶつ切り」になり、境界直前に始まる音符は隙間が潰れて欠落し、
|
|
2875
|
+
* 歌詞(@@n)の音節割り当てがずれていた。
|
|
2876
|
+
* 全ノートを発音順に一度で処理し、次の発音までの距離だけを上限として
|
|
2877
|
+
* 各音符の長さを忠実に出力する(次の音符がなければ曲末まで伸ばせる)。
|
|
1720
2878
|
*/
|
|
1721
2879
|
generateMML = (volumeOverride) => {
|
|
1722
2880
|
const config = getRenderConfig();
|
|
1723
2881
|
const vol = volumeOverride ?? this.volume;
|
|
1724
|
-
const
|
|
1725
|
-
const header = `t${this.tempo} q50 v${vol}`;
|
|
2882
|
+
const header = `t${this.tempo} v${vol}`;
|
|
1726
2883
|
const segments = [];
|
|
1727
2884
|
let lastOctave = -1;
|
|
1728
2885
|
let currentCursor = 0;
|
|
1729
2886
|
if (this.notes.length === 0) return header;
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1737
|
-
);
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
}
|
|
1745
|
-
const { dur, steps } = this.findBestFitDuration(gap);
|
|
1746
|
-
segments.push(`r${dur}`);
|
|
1747
|
-
currentCursor += steps;
|
|
1748
|
-
}
|
|
1749
|
-
continue;
|
|
1750
|
-
}
|
|
1751
|
-
const notesByStep = /* @__PURE__ */ new Map();
|
|
1752
|
-
windowNotes.forEach((n) => {
|
|
1753
|
-
const list = notesByStep.get(n.startStep) || [];
|
|
1754
|
-
list.push(n);
|
|
1755
|
-
notesByStep.set(n.startStep, list);
|
|
1756
|
-
});
|
|
1757
|
-
const sortedSteps = Array.from(notesByStep.keys()).sort((a, b) => a - b);
|
|
1758
|
-
for (let i = 0; i < sortedSteps.length; i++) {
|
|
1759
|
-
const startStep = sortedSteps[i];
|
|
1760
|
-
const notes = notesByStep.get(startStep);
|
|
1761
|
-
if (!notes) continue;
|
|
1762
|
-
while (currentCursor < startStep) {
|
|
1763
|
-
const gap = startStep - currentCursor;
|
|
1764
|
-
if (gap <= 2) {
|
|
1765
|
-
currentCursor = startStep;
|
|
1766
|
-
break;
|
|
1767
|
-
}
|
|
1768
|
-
const { dur, steps } = this.findBestFitDuration(gap);
|
|
1769
|
-
segments.push(`r${dur}`);
|
|
1770
|
-
currentCursor += steps;
|
|
1771
|
-
}
|
|
1772
|
-
const nextStart = sortedSteps[i + 1] ?? windowEnd;
|
|
1773
|
-
const physicsLimit = nextStart - currentCursor;
|
|
1774
|
-
const MIN_STEP = config.stepsPerBar / 64;
|
|
1775
|
-
if (physicsLimit < MIN_STEP) {
|
|
1776
|
-
currentCursor = startStep;
|
|
1777
|
-
continue;
|
|
1778
|
-
}
|
|
1779
|
-
const idealDuration = notes[0].durationSteps;
|
|
1780
|
-
const durStr = this.stepsToMMLDuration(idealDuration, physicsLimit);
|
|
1781
|
-
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
1782
|
-
if (notes.length > 1) {
|
|
1783
|
-
const noteStrs = notes.map((n) => {
|
|
1784
|
-
const oct = Math.floor(n.pitch / 12) - 1;
|
|
1785
|
-
const name = PITCH_MAP[n.pitch % 12];
|
|
1786
|
-
return `o${oct}${name}`;
|
|
1787
|
-
});
|
|
1788
|
-
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
1789
|
-
} else {
|
|
1790
|
-
const { text, currentOctave } = this.getNoteWithOctave(
|
|
1791
|
-
notes[0].pitch,
|
|
1792
|
-
lastOctave
|
|
1793
|
-
);
|
|
1794
|
-
segments.push(`${text}${durStr}`);
|
|
1795
|
-
lastOctave = currentOctave;
|
|
1796
|
-
}
|
|
1797
|
-
currentCursor += actualStepGenerated;
|
|
1798
|
-
}
|
|
1799
|
-
while (currentCursor < windowEnd) {
|
|
1800
|
-
const gap = windowEnd - currentCursor;
|
|
1801
|
-
if (gap <= 2) {
|
|
1802
|
-
currentCursor = windowEnd;
|
|
1803
|
-
break;
|
|
1804
|
-
}
|
|
2887
|
+
const endStep = Math.max(
|
|
2888
|
+
...this.notes.map((n) => n.startStep + n.durationSteps)
|
|
2889
|
+
);
|
|
2890
|
+
const notesByStep = /* @__PURE__ */ new Map();
|
|
2891
|
+
for (const n of this.notes) {
|
|
2892
|
+
const list = notesByStep.get(n.startStep) ?? [];
|
|
2893
|
+
list.push(n);
|
|
2894
|
+
notesByStep.set(n.startStep, list);
|
|
2895
|
+
}
|
|
2896
|
+
const sortedSteps = Array.from(notesByStep.keys()).sort((a, b) => a - b);
|
|
2897
|
+
const MIN_STEP = config.stepsPerBar / 64;
|
|
2898
|
+
const fillRests = (until) => {
|
|
2899
|
+
while (until - currentCursor >= MIN_STEP) {
|
|
2900
|
+
const gap = until - currentCursor;
|
|
1805
2901
|
const { dur, steps } = this.findBestFitDuration(gap);
|
|
1806
2902
|
segments.push(`r${dur}`);
|
|
1807
2903
|
currentCursor += steps;
|
|
1808
2904
|
}
|
|
2905
|
+
};
|
|
2906
|
+
for (let i = 0; i < sortedSteps.length; i++) {
|
|
2907
|
+
const startStep = sortedSteps[i];
|
|
2908
|
+
const notes = notesByStep.get(startStep);
|
|
2909
|
+
if (!notes) continue;
|
|
2910
|
+
fillRests(startStep);
|
|
2911
|
+
const nextStart = sortedSteps[i + 1] ?? endStep;
|
|
2912
|
+
const physicsLimit = nextStart - currentCursor;
|
|
2913
|
+
if (physicsLimit < MIN_STEP) {
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2916
|
+
const idealDuration = notes[0].durationSteps;
|
|
2917
|
+
const durStr = this.stepsToMMLDuration(idealDuration, physicsLimit);
|
|
2918
|
+
const actualStepGenerated = this.getStepFromDottedMML(durStr);
|
|
2919
|
+
if (notes.length > 1) {
|
|
2920
|
+
const noteStrs = notes.map((n) => {
|
|
2921
|
+
const oct = Math.floor(n.pitch / 12) - 1;
|
|
2922
|
+
const name = PITCH_MAP[n.pitch % 12];
|
|
2923
|
+
return `o${oct}${name}`;
|
|
2924
|
+
});
|
|
2925
|
+
segments.push(`[${noteStrs.join("")}]${durStr}`);
|
|
2926
|
+
} else {
|
|
2927
|
+
const { text, currentOctave } = this.getNoteWithOctave(
|
|
2928
|
+
notes[0].pitch,
|
|
2929
|
+
lastOctave
|
|
2930
|
+
);
|
|
2931
|
+
segments.push(`${text}${durStr}`);
|
|
2932
|
+
lastOctave = currentOctave;
|
|
2933
|
+
}
|
|
2934
|
+
currentCursor += actualStepGenerated;
|
|
1809
2935
|
}
|
|
2936
|
+
fillRests(endStep);
|
|
1810
2937
|
return `${header} ${segments.join(" ")}`;
|
|
1811
2938
|
};
|
|
1812
2939
|
/**
|
|
@@ -1884,13 +3011,51 @@ var PITCH_MAP2 = {
|
|
|
1884
3011
|
a: 9,
|
|
1885
3012
|
b: 11
|
|
1886
3013
|
};
|
|
1887
|
-
var
|
|
3014
|
+
var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
|
|
3015
|
+
var META_DIRECTIVE = /#(inst|drum|volume)=([\w-]+)/gi;
|
|
3016
|
+
var parseMmlMeta = (mml) => {
|
|
3017
|
+
const meta = {};
|
|
3018
|
+
for (const m of mml.matchAll(META_DIRECTIVE)) {
|
|
3019
|
+
const key = m[1].toLowerCase();
|
|
3020
|
+
if (key === "inst") meta.instrument = m[2];
|
|
3021
|
+
else if (key === "drum") meta.drum = m[2];
|
|
3022
|
+
else if (key === "volume") {
|
|
3023
|
+
const v = Number.parseInt(m[2], 10);
|
|
3024
|
+
if (!Number.isNaN(v)) meta.volume = v;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
return meta;
|
|
3028
|
+
};
|
|
3029
|
+
var stripMmlMeta = (mml) => mml.replace(META_DIRECTIVE, "");
|
|
3030
|
+
var formatMmlMeta = (meta) => {
|
|
3031
|
+
const parts = [];
|
|
3032
|
+
if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
|
|
3033
|
+
if (meta.drum) parts.push(`#drum=${meta.drum}`);
|
|
3034
|
+
if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
|
|
3035
|
+
return parts.join(" ");
|
|
3036
|
+
};
|
|
1888
3037
|
var parseMML = (mml, options = {}) => {
|
|
1889
|
-
const stepsPerBar = options.stepsPerBar ??
|
|
3038
|
+
const stepsPerBar = options.stepsPerBar ?? DEFAULT_STEPS_PER_BAR;
|
|
3039
|
+
const collectTokens = options.collectTokens ?? false;
|
|
3040
|
+
const collectLyrics = options.collectLyrics ?? false;
|
|
3041
|
+
const clampTrackCount = options.clampTrackCount;
|
|
1890
3042
|
const placements = [];
|
|
3043
|
+
const tokenTracks = /* @__PURE__ */ new Map();
|
|
1891
3044
|
let bpm = null;
|
|
1892
|
-
if (!mml)
|
|
1893
|
-
|
|
3045
|
+
if (!mml) {
|
|
3046
|
+
return {
|
|
3047
|
+
placements,
|
|
3048
|
+
bpm,
|
|
3049
|
+
tokenTracks: collectTokens ? tokenTracks : void 0,
|
|
3050
|
+
lyrics: collectLyrics ? /* @__PURE__ */ new Map() : void 0,
|
|
3051
|
+
meta: {}
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
const noComments = mml.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
3055
|
+
const meta = parseMmlMeta(noComments);
|
|
3056
|
+
const noMeta = stripMmlMeta(noComments);
|
|
3057
|
+
const lyrics = collectLyrics ? parseLyrics(noMeta) : void 0;
|
|
3058
|
+
const fullMML = stripLyrics(noMeta).replace(/[\n\r]+/g, " ").trim();
|
|
1894
3059
|
const parts = fullMML.split(/(@\d+)/).filter((p) => p.trim().length > 0);
|
|
1895
3060
|
let trackIndex = 0;
|
|
1896
3061
|
let octave = 4;
|
|
@@ -1900,7 +3065,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
1900
3065
|
const part = rawPart.trim();
|
|
1901
3066
|
if (part.startsWith("@")) {
|
|
1902
3067
|
let idx = Number.parseInt(part.substring(1), 10);
|
|
1903
|
-
if (idx >=
|
|
3068
|
+
if (clampTrackCount !== void 0 && idx >= clampTrackCount) idx = 2;
|
|
1904
3069
|
trackIndex = idx;
|
|
1905
3070
|
octave = 4;
|
|
1906
3071
|
currentStep = 0;
|
|
@@ -1909,13 +3074,27 @@ var parseMML = (mml, options = {}) => {
|
|
|
1909
3074
|
}
|
|
1910
3075
|
const body = part.replace(/\s+/g, "").toLowerCase();
|
|
1911
3076
|
let j = 0;
|
|
3077
|
+
const pushTok = (type, start, dur, from) => {
|
|
3078
|
+
if (!collectTokens) return;
|
|
3079
|
+
let arr = tokenTracks.get(trackIndex);
|
|
3080
|
+
if (!arr) {
|
|
3081
|
+
arr = [];
|
|
3082
|
+
tokenTracks.set(trackIndex, arr);
|
|
3083
|
+
}
|
|
3084
|
+
arr.push({
|
|
3085
|
+
text: body.slice(from, j),
|
|
3086
|
+
startStep: start,
|
|
3087
|
+
durationSteps: dur,
|
|
3088
|
+
type
|
|
3089
|
+
});
|
|
3090
|
+
};
|
|
1912
3091
|
const parseLength = () => {
|
|
1913
3092
|
let numStr = "";
|
|
1914
3093
|
while (j < body.length && /\d/.test(body[j])) {
|
|
1915
3094
|
numStr += body[j];
|
|
1916
3095
|
j++;
|
|
1917
3096
|
}
|
|
1918
|
-
const len = numStr ? Number.parseInt(numStr, 10) : baseLength;
|
|
3097
|
+
const len = numStr ? clamp2(Number.parseInt(numStr, 10), 1, 64) : baseLength;
|
|
1919
3098
|
let steps = Math.round(stepsPerBar / len);
|
|
1920
3099
|
while (j < body.length && body[j] === ".") {
|
|
1921
3100
|
steps = Math.round(steps * 1.5);
|
|
@@ -1925,6 +3104,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
1925
3104
|
};
|
|
1926
3105
|
while (j < body.length) {
|
|
1927
3106
|
const ch = body[j];
|
|
3107
|
+
const tokStart = j;
|
|
1928
3108
|
if (ch === "o") {
|
|
1929
3109
|
j++;
|
|
1930
3110
|
let numStr = "";
|
|
@@ -1932,13 +3112,16 @@ var parseMML = (mml, options = {}) => {
|
|
|
1932
3112
|
numStr += body[j];
|
|
1933
3113
|
j++;
|
|
1934
3114
|
}
|
|
1935
|
-
octave = Number.parseInt(numStr, 10)
|
|
3115
|
+
octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
|
|
3116
|
+
pushTok("octave", currentStep, 0, tokStart);
|
|
1936
3117
|
} else if (ch === ">") {
|
|
1937
|
-
octave
|
|
3118
|
+
octave = Math.min(8, octave + 1);
|
|
1938
3119
|
j++;
|
|
3120
|
+
pushTok("shift", currentStep, 0, tokStart);
|
|
1939
3121
|
} else if (ch === "<") {
|
|
1940
|
-
octave
|
|
3122
|
+
octave = Math.max(0, octave - 1);
|
|
1941
3123
|
j++;
|
|
3124
|
+
pushTok("shift", currentStep, 0, tokStart);
|
|
1942
3125
|
} else if (ch === "l") {
|
|
1943
3126
|
j++;
|
|
1944
3127
|
let numStr = "";
|
|
@@ -1946,11 +3129,15 @@ var parseMML = (mml, options = {}) => {
|
|
|
1946
3129
|
numStr += body[j];
|
|
1947
3130
|
j++;
|
|
1948
3131
|
}
|
|
1949
|
-
baseLength = Number.parseInt(numStr, 10) || 16;
|
|
3132
|
+
baseLength = clamp2(Number.parseInt(numStr, 10) || 16, 1, 64);
|
|
3133
|
+
pushTok("length", currentStep, 0, tokStart);
|
|
1950
3134
|
} else if (ch === "r") {
|
|
1951
3135
|
j++;
|
|
1952
|
-
|
|
1953
|
-
|
|
3136
|
+
const restStart = currentStep;
|
|
3137
|
+
const restSteps = parseLength();
|
|
3138
|
+
pushTok("rest", restStart, restSteps, tokStart);
|
|
3139
|
+
currentStep += restSteps;
|
|
3140
|
+
} else if (ch === "t" || ch === "v" || ch === "q" || ch === "p") {
|
|
1954
3141
|
j++;
|
|
1955
3142
|
let numStr = "";
|
|
1956
3143
|
while (j < body.length && /\d/.test(body[j])) {
|
|
@@ -1958,8 +3145,9 @@ var parseMML = (mml, options = {}) => {
|
|
|
1958
3145
|
j++;
|
|
1959
3146
|
}
|
|
1960
3147
|
if (ch === "t" && trackIndex === 0 && numStr) {
|
|
1961
|
-
bpm = Number.parseInt(numStr, 10);
|
|
3148
|
+
bpm = clamp2(Number.parseInt(numStr, 10), 1, 255);
|
|
1962
3149
|
}
|
|
3150
|
+
pushTok("ctrl", currentStep, 0, tokStart);
|
|
1963
3151
|
} else if (ch === "[") {
|
|
1964
3152
|
j++;
|
|
1965
3153
|
const chordNotes = [];
|
|
@@ -1978,10 +3166,10 @@ var parseMML = (mml, options = {}) => {
|
|
|
1978
3166
|
}
|
|
1979
3167
|
chordNotes.push((octave + 1) * 12 + pitch);
|
|
1980
3168
|
} else if (c === ">") {
|
|
1981
|
-
octave
|
|
3169
|
+
octave = Math.min(8, octave + 1);
|
|
1982
3170
|
j++;
|
|
1983
3171
|
} else if (c === "<") {
|
|
1984
|
-
octave
|
|
3172
|
+
octave = Math.max(0, octave - 1);
|
|
1985
3173
|
j++;
|
|
1986
3174
|
} else if (c === "o") {
|
|
1987
3175
|
j++;
|
|
@@ -1990,7 +3178,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
1990
3178
|
numStr += body[j];
|
|
1991
3179
|
j++;
|
|
1992
3180
|
}
|
|
1993
|
-
octave = Number.parseInt(numStr, 10)
|
|
3181
|
+
octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
|
|
1994
3182
|
} else {
|
|
1995
3183
|
j++;
|
|
1996
3184
|
}
|
|
@@ -2005,6 +3193,7 @@ var parseMML = (mml, options = {}) => {
|
|
|
2005
3193
|
durationSteps: Math.max(1, steps)
|
|
2006
3194
|
});
|
|
2007
3195
|
}
|
|
3196
|
+
pushTok("chord", currentStep, Math.max(1, steps), tokStart);
|
|
2008
3197
|
currentStep += steps;
|
|
2009
3198
|
octave = savedOctave;
|
|
2010
3199
|
} else if (Object.hasOwn(PITCH_MAP2, ch)) {
|
|
@@ -2025,18 +3214,25 @@ var parseMML = (mml, options = {}) => {
|
|
|
2025
3214
|
pitch: midiPitch,
|
|
2026
3215
|
durationSteps: Math.max(1, steps)
|
|
2027
3216
|
});
|
|
3217
|
+
pushTok("note", currentStep, Math.max(1, steps), tokStart);
|
|
2028
3218
|
currentStep += steps;
|
|
2029
3219
|
} else {
|
|
2030
3220
|
j++;
|
|
2031
3221
|
}
|
|
2032
3222
|
}
|
|
2033
3223
|
}
|
|
2034
|
-
return {
|
|
3224
|
+
return {
|
|
3225
|
+
placements,
|
|
3226
|
+
bpm,
|
|
3227
|
+
tokenTracks: collectTokens ? tokenTracks : void 0,
|
|
3228
|
+
lyrics,
|
|
3229
|
+
meta
|
|
3230
|
+
};
|
|
2035
3231
|
};
|
|
2036
3232
|
|
|
2037
3233
|
// src/sequencer.ts
|
|
2038
3234
|
var STEPS_PER_BEAT2 = 48;
|
|
2039
|
-
var PLAN_TIME = 0.
|
|
3235
|
+
var PLAN_TIME = 0.5;
|
|
2040
3236
|
var TICK_INTERVAL_MS = 20;
|
|
2041
3237
|
var createSequencer = (options) => {
|
|
2042
3238
|
let timeline = [];
|
|
@@ -2046,15 +3242,18 @@ var createSequencer = (options) => {
|
|
|
2046
3242
|
let animationId = null;
|
|
2047
3243
|
let active = false;
|
|
2048
3244
|
let fromStepValue = 0;
|
|
3245
|
+
let trackVolumeMap = /* @__PURE__ */ new Map();
|
|
2049
3246
|
const secondsPerStep = () => 60 / options.getBpm() / STEPS_PER_BEAT2;
|
|
2050
3247
|
const buildTimeline = (fromStep) => {
|
|
2051
3248
|
timeline = [];
|
|
3249
|
+
trackVolumeMap = /* @__PURE__ */ new Map();
|
|
2052
3250
|
const sps = secondsPerStep();
|
|
2053
3251
|
for (const track of options.getTracks()) {
|
|
3252
|
+
trackVolumeMap.set(track.id, track.volume);
|
|
2054
3253
|
for (const note of track.notes) {
|
|
2055
3254
|
const relativeStart = note.startStep - fromStep;
|
|
2056
3255
|
if (relativeStart < 0) continue;
|
|
2057
|
-
const velocity = note.velocity ??
|
|
3256
|
+
const velocity = note.velocity ?? DEFAULT_PLAYBACK_VELOCITY;
|
|
2058
3257
|
timeline.push({
|
|
2059
3258
|
trackId: track.id,
|
|
2060
3259
|
pitch: note.pitch,
|
|
@@ -2071,6 +3270,9 @@ var createSequencer = (options) => {
|
|
|
2071
3270
|
const sps = secondsPerStep();
|
|
2072
3271
|
const time = options.getAudioTime() - startTime;
|
|
2073
3272
|
const soloId = options.getSoloTrackId();
|
|
3273
|
+
for (const track of options.getTracks()) {
|
|
3274
|
+
trackVolumeMap.set(track.id, track.volume);
|
|
3275
|
+
}
|
|
2074
3276
|
while (nowIndex < timeline.length) {
|
|
2075
3277
|
const ev = timeline[nowIndex];
|
|
2076
3278
|
if (soloId && ev.trackId !== soloId) {
|
|
@@ -2081,11 +3283,12 @@ var createSequencer = (options) => {
|
|
|
2081
3283
|
if (_when > PLAN_TIME) break;
|
|
2082
3284
|
nowIndex++;
|
|
2083
3285
|
const velocityVolume = ev.velocity / 127;
|
|
3286
|
+
const currentVolume = (trackVolumeMap.get(ev.trackId) ?? ev.volume * 100) / 100;
|
|
2084
3287
|
options.onPlayNote({
|
|
2085
3288
|
trackId: ev.trackId,
|
|
2086
3289
|
pitch: ev.pitch,
|
|
2087
3290
|
velocity: ev.velocity,
|
|
2088
|
-
volume:
|
|
3291
|
+
volume: currentVolume * velocityVolume,
|
|
2089
3292
|
when: Math.max(0, _when),
|
|
2090
3293
|
duration: ev.duration
|
|
2091
3294
|
});
|
|
@@ -2136,13 +3339,14 @@ var createSequencer = (options) => {
|
|
|
2136
3339
|
}
|
|
2137
3340
|
active = false;
|
|
2138
3341
|
};
|
|
3342
|
+
const START_DELAY = 0.1;
|
|
2139
3343
|
const start = (fromStep) => {
|
|
2140
3344
|
stop();
|
|
2141
3345
|
fromStepValue = fromStep ?? options.getPlayStartStep();
|
|
2142
3346
|
buildTimeline(fromStepValue);
|
|
2143
3347
|
if (timeline.length === 0 && !options.getDrumPattern()?.length) return;
|
|
2144
3348
|
active = true;
|
|
2145
|
-
startTime = options.getAudioTime();
|
|
3349
|
+
startTime = options.getAudioTime() + START_DELAY;
|
|
2146
3350
|
nowIndex = 0;
|
|
2147
3351
|
intervalId = setInterval(scheduleTick, TICK_INTERVAL_MS);
|
|
2148
3352
|
animationId = requestAnimationFrame(animate);
|
|
@@ -2150,7 +3354,8 @@ var createSequencer = (options) => {
|
|
|
2150
3354
|
return {
|
|
2151
3355
|
start,
|
|
2152
3356
|
stop,
|
|
2153
|
-
isActive: () => active
|
|
3357
|
+
isActive: () => active,
|
|
3358
|
+
getStartTime: () => startTime
|
|
2154
3359
|
};
|
|
2155
3360
|
};
|
|
2156
3361
|
|
|
@@ -2612,10 +3817,12 @@ var DAW_CSS = `
|
|
|
2612
3817
|
|
|
2613
3818
|
/* \u2500\u2500\u2500 \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4 \u2500\u2500\u2500 */
|
|
2614
3819
|
.dtm-overlay {
|
|
2615
|
-
position:
|
|
3820
|
+
position: absolute; inset: 0; z-index: 1000;
|
|
2616
3821
|
background: rgba(0,0,0,.92);
|
|
2617
3822
|
display: flex; align-items: center; justify-content: center;
|
|
2618
3823
|
flex-direction: column; gap: 14px;
|
|
3824
|
+
pointer-events: auto;
|
|
3825
|
+
cursor: wait;
|
|
2619
3826
|
}
|
|
2620
3827
|
.dtm-overlay[hidden] { display: none; }
|
|
2621
3828
|
.dtm-overlay::before {
|
|
@@ -2643,18 +3850,206 @@ var DAW_CSS = `
|
|
|
2643
3850
|
animation: dtm-load 1.6s steps(8) infinite;
|
|
2644
3851
|
}
|
|
2645
3852
|
@keyframes dtm-load { 0%{width:0} 100%{width:100%} }
|
|
3853
|
+
/* \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 */
|
|
3854
|
+
.dtm-spinner--determinate::after { display: none; }
|
|
3855
|
+
.dtm-spinner-fill {
|
|
3856
|
+
position: absolute;
|
|
3857
|
+
left: 0; top: 0; height: 100%;
|
|
3858
|
+
width: 0;
|
|
3859
|
+
background: var(--dtm-primary);
|
|
3860
|
+
transition: width .12s steps(8);
|
|
3861
|
+
}
|
|
3862
|
+
.dtm-loading-label {
|
|
3863
|
+
font-family: var(--dtm-font);
|
|
3864
|
+
font-size: 11px;
|
|
3865
|
+
color: var(--dtm-primary);
|
|
3866
|
+
letter-spacing: .15em;
|
|
3867
|
+
min-height: 1em;
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
@keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
|
3871
|
+
.dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
|
|
3872
|
+
|
|
3873
|
+
.dtm-hidden { display: none !important; }
|
|
3874
|
+
.dtm-grow { flex: 1 1 auto; }
|
|
3875
|
+
.dtm-lyric-icon {
|
|
3876
|
+
flex: 0 0 auto;
|
|
3877
|
+
width: 20px;
|
|
3878
|
+
height: 20px;
|
|
3879
|
+
border-radius: 50%;
|
|
3880
|
+
object-fit: cover;
|
|
3881
|
+
}
|
|
2646
3882
|
|
|
2647
|
-
@keyframes dtm-blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
|
2648
|
-
.dtm-blink { animation: dtm-blink 1s steps(1) infinite; }
|
|
2649
|
-
|
|
2650
|
-
.dtm-hidden { display: none !important; }
|
|
2651
|
-
.dtm-grow { flex: 1 1 auto; }
|
|
2652
|
-
|
|
2653
3883
|
/* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */
|
|
2654
3884
|
@media (min-width: 768px) {
|
|
2655
3885
|
.dtm-daw { gap: 8px; padding: 10px; }
|
|
2656
3886
|
.dtm-roll { height: 420px; }
|
|
2657
3887
|
}
|
|
3888
|
+
|
|
3889
|
+
/* ====================================================
|
|
3890
|
+
MML PLAYER \u2014 \u518D\u751F\u5C02\u7528\u30D3\u30E5\u30FC\uFF08mountMmlPlayer\uFF09
|
|
3891
|
+
==================================================== */
|
|
3892
|
+
.dtm-player {
|
|
3893
|
+
display: flex;
|
|
3894
|
+
flex-direction: column;
|
|
3895
|
+
gap: var(--dtm-gap);
|
|
3896
|
+
padding: var(--dtm-gap);
|
|
3897
|
+
background: var(--dtm-deep);
|
|
3898
|
+
border: 2px solid var(--dtm-border2);
|
|
3899
|
+
box-shadow: 4px 4px 0 var(--c-black);
|
|
3900
|
+
}
|
|
3901
|
+
.dtm-player-head {
|
|
3902
|
+
display: flex;
|
|
3903
|
+
align-items: center;
|
|
3904
|
+
gap: 8px;
|
|
3905
|
+
flex-wrap: wrap;
|
|
3906
|
+
}
|
|
3907
|
+
.dtm-player-play {
|
|
3908
|
+
flex: 0 0 auto;
|
|
3909
|
+
width: 28px;
|
|
3910
|
+
height: 28px;
|
|
3911
|
+
display: flex;
|
|
3912
|
+
align-items: center;
|
|
3913
|
+
justify-content: center;
|
|
3914
|
+
background: var(--dtm-primary);
|
|
3915
|
+
color: var(--dtm-pfg);
|
|
3916
|
+
border: 2px solid var(--c-black);
|
|
3917
|
+
box-shadow: 2px 2px 0 var(--c-black);
|
|
3918
|
+
cursor: pointer;
|
|
3919
|
+
padding: 0;
|
|
3920
|
+
}
|
|
3921
|
+
.dtm-player-play:active { transform: translate(2px, 2px); box-shadow: none; }
|
|
3922
|
+
.dtm-player-play--stop { background: var(--dtm-danger); }
|
|
3923
|
+
.dtm-player-play:disabled { opacity: 0.4; cursor: default; }
|
|
3924
|
+
.dtm-player-beat-row {
|
|
3925
|
+
display: flex;
|
|
3926
|
+
align-items: center;
|
|
3927
|
+
gap: 4px;
|
|
3928
|
+
}
|
|
3929
|
+
.dtm-player-beat-dot {
|
|
3930
|
+
width: 8px;
|
|
3931
|
+
height: 8px;
|
|
3932
|
+
border-radius: 50%;
|
|
3933
|
+
background: var(--dtm-border2);
|
|
3934
|
+
transition: background 0.06s;
|
|
3935
|
+
}
|
|
3936
|
+
.dtm-player-beat-dot--on { background: var(--dtm-primary); }
|
|
3937
|
+
.dtm-player-bar {
|
|
3938
|
+
font-family: 'k8x12', monospace;
|
|
3939
|
+
font-size: 11px;
|
|
3940
|
+
color: var(--dtm-text);
|
|
3941
|
+
min-width: 2em;
|
|
3942
|
+
margin-left: 4px;
|
|
3943
|
+
}
|
|
3944
|
+
.dtm-player-dots {
|
|
3945
|
+
margin-left: auto;
|
|
3946
|
+
display: flex;
|
|
3947
|
+
align-items: center;
|
|
3948
|
+
gap: 6px;
|
|
3949
|
+
}
|
|
3950
|
+
.dtm-player-dot { width: 8px; height: 8px; display: inline-block; }
|
|
3951
|
+
.dtm-player-mml-header {
|
|
3952
|
+
margin-left: auto;
|
|
3953
|
+
display: flex;
|
|
3954
|
+
align-items: center;
|
|
3955
|
+
gap: 4px;
|
|
3956
|
+
}
|
|
3957
|
+
.dtm-player-mml-link {
|
|
3958
|
+
font-family: 'k8x12', monospace;
|
|
3959
|
+
font-size: 10px;
|
|
3960
|
+
color: var(--dtm-muted);
|
|
3961
|
+
text-decoration: none;
|
|
3962
|
+
white-space: nowrap;
|
|
3963
|
+
}
|
|
3964
|
+
.dtm-player-mml-link:hover { color: var(--dtm-primary); }
|
|
3965
|
+
.dtm-player-emoji {
|
|
3966
|
+
display: inline-flex;
|
|
3967
|
+
align-items: center;
|
|
3968
|
+
justify-content: center;
|
|
3969
|
+
width: 20px;
|
|
3970
|
+
height: 20px;
|
|
3971
|
+
border-radius: 50%;
|
|
3972
|
+
font-size: 18px;
|
|
3973
|
+
line-height: 1;
|
|
3974
|
+
user-select: none;
|
|
3975
|
+
}
|
|
3976
|
+
@keyframes dtm-emoji-jump {
|
|
3977
|
+
0% { transform: translateY(0); }
|
|
3978
|
+
35% { transform: translateY(-5px); }
|
|
3979
|
+
65% { transform: translateY(-5px); }
|
|
3980
|
+
100% { transform: translateY(0); }
|
|
3981
|
+
}
|
|
3982
|
+
.dtm-player-emoji--jump {
|
|
3983
|
+
animation: dtm-emoji-jump 0.18s ease-out forwards;
|
|
3984
|
+
}
|
|
3985
|
+
.dtm-player-chip {
|
|
3986
|
+
font-family: 'k8x12', monospace;
|
|
3987
|
+
font-size: 9px;
|
|
3988
|
+
color: var(--dtm-text);
|
|
3989
|
+
background: var(--dtm-border2);
|
|
3990
|
+
padding: 2px 6px;
|
|
3991
|
+
white-space: nowrap;
|
|
3992
|
+
}
|
|
3993
|
+
.dtm-player-body {
|
|
3994
|
+
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 */
|
|
3995
|
+
display: flex;
|
|
3996
|
+
flex-direction: column;
|
|
3997
|
+
gap: var(--dtm-gap);
|
|
3998
|
+
}
|
|
3999
|
+
.dtm-player-lane-row {
|
|
4000
|
+
display: flex;
|
|
4001
|
+
align-items: stretch;
|
|
4002
|
+
gap: 6px;
|
|
4003
|
+
}
|
|
4004
|
+
.dtm-player-lane-label {
|
|
4005
|
+
flex: 0 0 auto;
|
|
4006
|
+
width: 16px;
|
|
4007
|
+
display: flex;
|
|
4008
|
+
flex-direction: column;
|
|
4009
|
+
align-items: center;
|
|
4010
|
+
gap: 2px;
|
|
4011
|
+
padding-top: 4px;
|
|
4012
|
+
}
|
|
4013
|
+
.dtm-player-lane-label--btn {
|
|
4014
|
+
cursor: pointer;
|
|
4015
|
+
user-select: none;
|
|
4016
|
+
}
|
|
4017
|
+
.dtm-player-lane-label--btn:hover { opacity: 0.7; }
|
|
4018
|
+
.dtm-player-lane-label--muted { opacity: 0.3; }
|
|
4019
|
+
.dtm-player-lane-no {
|
|
4020
|
+
font-family: 'k8x12', monospace;
|
|
4021
|
+
font-size: 9px;
|
|
4022
|
+
color: var(--dtm-muted);
|
|
4023
|
+
}
|
|
4024
|
+
.dtm-player-lane {
|
|
4025
|
+
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 */
|
|
4026
|
+
flex: 1 1 auto;
|
|
4027
|
+
overflow-x: auto;
|
|
4028
|
+
white-space: nowrap;
|
|
4029
|
+
background: var(--c-black);
|
|
4030
|
+
border: 2px solid var(--dtm-border2);
|
|
4031
|
+
padding: 6px;
|
|
4032
|
+
scrollbar-width: none;
|
|
4033
|
+
}
|
|
4034
|
+
.dtm-player-lane::-webkit-scrollbar { display: none; }
|
|
4035
|
+
.dtm-tk {
|
|
4036
|
+
font-family: 'k8x12', monospace;
|
|
4037
|
+
font-size: 12px;
|
|
4038
|
+
color: var(--dtm-text);
|
|
4039
|
+
}
|
|
4040
|
+
.dtm-tk--rest { color: var(--dtm-muted); }
|
|
4041
|
+
.dtm-tk--octave,
|
|
4042
|
+
.dtm-tk--shift,
|
|
4043
|
+
.dtm-tk--length,
|
|
4044
|
+
.dtm-tk--ctrl { color: var(--dtm-border2); }
|
|
4045
|
+
.dtm-tk--lyric { color: var(--dtm-text); letter-spacing: 1px; }
|
|
4046
|
+
.dtm-tk--break { color: var(--dtm-muted); opacity: 0.7; margin: 0 2px; }
|
|
4047
|
+
.dtm-tk--meta { color: var(--dtm-border2); margin-right: 4px; }
|
|
4048
|
+
.dtm-tk.is-active {
|
|
4049
|
+
background: var(--tk, var(--dtm-primary));
|
|
4050
|
+
color: var(--c-black);
|
|
4051
|
+
font-weight: bold;
|
|
4052
|
+
}
|
|
2658
4053
|
`;
|
|
2659
4054
|
var injectStyles = (doc = document) => {
|
|
2660
4055
|
if (doc.getElementById(STYLE_ID)) return;
|
|
@@ -2663,6 +4058,77 @@ var injectStyles = (doc = document) => {
|
|
|
2663
4058
|
style.textContent = DAW_CSS;
|
|
2664
4059
|
doc.head.appendChild(style);
|
|
2665
4060
|
};
|
|
4061
|
+
var showLoadingOverlay = (container) => {
|
|
4062
|
+
const origPos = container.style.position;
|
|
4063
|
+
const computed = window.getComputedStyle(container).position;
|
|
4064
|
+
if (computed === "static") {
|
|
4065
|
+
container.style.position = "relative";
|
|
4066
|
+
}
|
|
4067
|
+
const doc = container.ownerDocument ?? document;
|
|
4068
|
+
const overlay = doc.createElement("div");
|
|
4069
|
+
overlay.className = "dtm-overlay";
|
|
4070
|
+
const spinner = doc.createElement("div");
|
|
4071
|
+
spinner.className = "dtm-spinner";
|
|
4072
|
+
const fill = doc.createElement("i");
|
|
4073
|
+
fill.className = "dtm-spinner-fill";
|
|
4074
|
+
spinner.appendChild(fill);
|
|
4075
|
+
overlay.appendChild(spinner);
|
|
4076
|
+
const label = doc.createElement("div");
|
|
4077
|
+
label.className = "dtm-loading-label";
|
|
4078
|
+
overlay.appendChild(label);
|
|
4079
|
+
container.appendChild(overlay);
|
|
4080
|
+
const setProgress = (done, total) => {
|
|
4081
|
+
if (total > 0) {
|
|
4082
|
+
const pct = Math.max(0, Math.min(100, Math.round(done / total * 100)));
|
|
4083
|
+
spinner.classList.add("dtm-spinner--determinate");
|
|
4084
|
+
fill.style.width = `${pct}%`;
|
|
4085
|
+
label.textContent = `${done} / ${total} (${pct}%)`;
|
|
4086
|
+
} else {
|
|
4087
|
+
spinner.classList.remove("dtm-spinner--determinate");
|
|
4088
|
+
fill.style.width = "0";
|
|
4089
|
+
label.textContent = "";
|
|
4090
|
+
}
|
|
4091
|
+
};
|
|
4092
|
+
return {
|
|
4093
|
+
remove: () => {
|
|
4094
|
+
overlay.remove();
|
|
4095
|
+
container.style.position = origPos;
|
|
4096
|
+
},
|
|
4097
|
+
setProgress
|
|
4098
|
+
};
|
|
4099
|
+
};
|
|
4100
|
+
|
|
4101
|
+
// assets/puyuyu.png
|
|
4102
|
+
var puyuyu_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAQAElEQVR4AezXO6hlVxkH8H0mduo4BFIGER9YmqhTRhAFe8VSDIIvULtpjYjNlIoatRBSWMQ2pdrYjYWt+KhkitzJwJVBYSDh6p5XvHfuOWe/vr3XWt8vsObee87e31rf79vn/MmVzn9NC9y6ce3MYjD1GWj6w6G5TgB4CAgQIJBUQAAkHby2CXQI0gsIgPSPAAACBLIKCICsk9c3AQLpBQRA2kdA4wQIZBcQANmfAP0TIJBWQACkHb3GCRDIKvC4bwHwWMJPAgQIJBMQAMkGrl0CBAg8FhAAjyX8JJBFQJ8EHgkIgEcQfhAgQCCbgADINnH9EiBA4JGAAHgEkeeHTgkQIPBQQAA8dPAvAQIE0gkIgHQj1zABAlkFLvYtAC6K+JsAAQJJBARAkkFrkwABAhcFBMBFEX8TaFVAXwQuCAiACyD+JECAQBYBAZBl0vokQIDABQEBcAGk3T91RoAAgfMCAuC8h78IECCQRkAApBm1RgkQyCqwr28BsE/m0eu3blw7i1xvv/HRs8j16Ve+1UWuF37wRhe5PvWjP3Y1r0ibvnbkbPvakc9mXzvys9XXfvQx9mOPgADYA+NlAgQItC4gAFqfsP4IECCwR0AA7IHxMgECBFoXEACtT1h/BAgQ2CMgAPbAtPOyTggQIHC5gAC43MWrBAgQaF5AADQ/Yg0SIJBV4FjfAuCYkPcJECDQqIAAaHSw2iJAgMAxAQFwTMj7BGoVcG4CRwQEwBEgbxMgQKBVAQHQ6mT1RYAAgSMCAuAIUL1vOzkBAgQOCwiAwz7eJUCAQLMCAqDZ0WqMAIGsAkP7FgBDpVxHgACBxgQEQGMD1Q4BAgSGCgiAoVKuI1CLgHMSGCggAAZCuYwAAQKtCQiA1iaqHwIECAwUEAADoeq5zEkJECAwTEAADHNyFQECBJoTEADNjVRDBAhkFRjbd/UBcOvGtbPI9eJLz3WR65nPfqmLXGfPfKyLXGMfuLHXv/P2v7qa19h+x14fOdu+duSz2deO/Gz1tSO/G/raY+dV2vXVB0BpoM5DgACBWgQEQC2Tck4CxwS8T2CkgAAYCeZyAgQItCIgAFqZpD4IECAwUkAAjAQr93InI0CAwDgBATDOy9UECBBoRkAANDNKjRAgkFVgat8CYKqc+wgQIFC5gACofICOT4AAgakCAmCqnPsIlCLgHAQmCgiAiXBuI0CAQO0CAqD2CTo/AQIEJgoIgIlw5dzmJAQIEJgmIACmubmLAAEC1QsIgOpHqAECBLIKzO1bAMwVdD8BAgQqFRAAlQ7OsQkQIDBXQADMFXQ/ga0E7EtgpoAAmAnodgIECNQqIABqnZxzEyBAYKaAAJgJuN3tdiZAgMA8AQEwz8/dBAgQqFZAAFQ7OgcnQCCrwFJ9hwfArRvXziLXiy8910WuK5/5RBe5lhrkVnWeufK3ztrOYKu5L7Vv5Gerrx353dDXjvxu62sv5byvTngA7NvY6wQIECCwrYAA2Nbf7gTGC7iDwEICAmAhSGUIECBQm4AAqG1izkuAAIGFBATAQpDrlbETAQIElhEQAMs4qkKAAIHqBARAdSNzYAIEsgos3bcAWFpUPQIECFQiIAAqGZRjEiBAYGkBAbC0qHoEogTUJbCwgABYGFQ5AgQI1CIgAGqZlHMSIEBgYQEBsDBoXDmVCRAgsKyAAFjWUzUCBAhUIyAAqhmVgxIgkFUgqm8BECWrLgECBAoXEACFD8jxCBAgECUgAKJk1SWwlIA6BIIEBEAQrLIECBAoXUAAlD4h5yNAgECQgAAIgl2urEoECBCIERAAMa6qEiBAoHgBAVD8iByQAIGsAtF9hwfAJz//wS5y/fvjz3eRK3oAX/3iq13k2r3z185iMPUZiHw2+9rRn6/I74a+9osvPddFrmif8ACIbkB9AgQIEJgmIACmubmLQLyAHQgECwiAYGDlCRAgUKqAACh1Ms5FgACBYAEBEAw8vbw7CRAgECsgAGJ9VSdAgECxAgKg2NE4GAECWQXW6lsArCVtHwIECBQmIAAKG4jjECBAYC0BAbCWtH0IDBVwHYGVBATAStC2IUCAQGkCAqC0iTgPAQIEVhIQACtBD9/GlQQIEFhHQACs42wXAgQIFCcgAIobiQMRIJBVYO2+BcDa4vYjQIBAIQICoJBBOAYBAgTWFhAAa4vbj8A+Aa8TWFlAAKwMbjsCBAiUIiAASpmEcxAgQGBlAQGwMvj+7bxDgACBdQUEwLrediNAgEAxAgKgmFE4CAECWQW26vvKrRvXziLX2f3/dJHru9/5cxe5Xv7y77rI9evXP9dFrrN3/t5ZDKY+A5HPZl878rPV1478buhrR39xR34397X9H0D0BNUnQIBAoQICoNDBOFYiAa0S2EhAAGwEb1sCBAhsLSAAtp6A/QkQILCRgADYCP7dbf1GgACBbQQEwDbudiVAgMDmAgJg8xE4AAECWQW27lsAbD0B+xMgQGAjAQGwEbxtCRAgsLWAANh6AvbPK6BzAhsLCICNB2B7AgQIbCUgALaSty8BAgQ2FhAAmw3AxgQIENhWQABs6293AgQIbCYgADajtzEBAlkFSulbAJQyCecgQIDAygICYGVw2xEgQKAUAQFQyiScI4+ATgkUIiAAChmEYxAgQGBtAQGwtrj9CBAgUIiAAFh9EDYkQIBAGQICoIw5OAUBAgRWFxAAq5PbkACBrAKl9V19APz4Jy90Na/d+17vIteV9/+2i1z33rzbWdsZRD47fe3IZ6evXfNntz97aV/oY89TfQCMbdj1BAgQIPBQQAA8dPAvgXgBOxAoTEAAFDYQxyFAgMBaAgJgLWn7ECBAoDABAbDaQGxEgACBsgQEQFnzcBoCBAisJiAAVqO2EQECWQVK7VsAlDoZ5yJAgECwgAAIBlaeAAECpQoIgFIn41ztCOiEQKECAqDQwTgWAQIEogUEQLSw+gQIEChUQACED8YGBAgQKFNAAJQ5F6ciQIBAuIAACCe2AQECWQVK71sAlD4h5yNAgECQgAAIglWWAAECpQsIgNIn5Hz1Cjg5gcIFBEDhA3I8AgQIRAkIgChZdQkQIFC4gAAIG5DCBAgQKFtAAJQ9H6cjQIBAmIAACKNVmACBrAK19F19ALz3L//sItfVD/++i1y1PCj7zvmBj/yhi1yRs12jduSz09feN5daXo98dvra0TOuxXnfOasPgH2NeZ0AAQIEDgsIgMM+3iUwXsAdBCoREACVDMoxCRAgsLSAAFhaVD0CBAhUIiAAFh+UggQIEKhDQADUMSenJECAwOICAmBxUgUJEMgqUFvfAqC2iTkvAQIEFhIQAAtBKkOAAIHaBARAbRNz3nIFnIxAZQICoLKBOS4BAgSWEhAAS0mqQ4AAgcoEBMBiA1OIAAECdQkIgLrm5bQECBBYTEAALEapEAECWQVq7VsA1Do55yZAgMBMAQEwE9DtBAgQqFVAANQ6OecuR8BJCFQqIAAqHZxjEyBAYK6AAJgr6H4CBAhUKiAAZg9OAQIECNQpIADqnJtTEyBAYLaAAJhNqAABAlkFau87PADu3zvtIlf0AHa7Xbfb1bu+8fVvdpEr2r/2+rtdvc/ObrcLfXb657L2+UZ+t/W1o33CAyC6AfUJECBAYJqAAJjm5i4CXceAQOUCAqDyATo+AQIEpgoIgKly7iNAgEDlAgJg8gDdSIAAgboFBEDd83N6AgQITBYQAJPp3EiAQFaBVvoWAK1MUh8ECBAYKSAARoK5nAABAq0ICIBWJqmP9QTsRKARAQHQyCC1QYAAgbECAmCsmOsJECDQiIAAGD1INxAgQKANAQHQxhx1QYAAgdECAmA0mRsIEMgq0FrfAqC1ieqHAAECAwUEwEAolxEgQKA1AQHQ2kT1EyegMoHGBARAYwPVDgECBIYKCIChUq4jQIBAYwICYPBAXUiAAIG2BARAW/PUDQECBAYLCIDBVC4kQCCrQKt9C4Ajk/3Vz7/fRa4j289++xe/fLWLXJE2fe3ZABsX6HuIXNHtRT47fe1Im752tE/t9QVA7RN0fgIECEwUEAAT4dyWSECrBBoVEACNDlZbBAgQOCYgAI4JeZ8AAQKNCgiAo4N1AQECBNoUEABtzlVXBAgQOCogAI4SuYAAgawCrfctAFqfsP4IECCwR0AA7IHxMgECBFoXEACtT1h/0wXcSaBxAQHQ+IC1R4AAgX0CAmCfjNcJECDQuIAA2DtgbxAgQKBtAQHQ9nx1R4AAgb0CAmAvjTcIEMgqkKVvAZBl0vokQIDABQEBcAHEnwQIEMgiIACyTFqfwwVcSSCJgABIMmhtEiBA4KKAALgo4m8CBAgkERAATw3aCwQIEMghIAByzFmXBAgQeEpAADxF4gUCBLIKZOu7+gC4f++0i1wvP/+bLnKd/OmHXc0r0qavXfsHsu8hctX87PRnj7Tpa0d+N/S1a38+qw+A2gfg/AQIENhKQABsJW/f8gSciEAyAQGQbODaJUCAwGMBAfBYwk8CBAgkExAATwbuFwIECOQSEAC55q1bAgQIPBEQAE8o/EKAQFaBrH0LgKyT1zcBAukFBED6RwAAAQJZBQRA1snr+10BvxFIKiAAkg5e2wQIEBAAngECBAgkFRAAXdLJa5sAgfQCAiD9IwCAAIGsAgIg6+T1TYBAl51AAGR/AvRPgEBaAQGQdvQaJ0Agu4AAyP4EZO5f7wSSCwiA5A+A9gkQyCsgAPLOXucECCQXSBwAySevfQIE0gsIgPSPAAACBLIKCICsk9c3gcQCWn8ocOX6zdNd5Do5udpFrodtxP17/95pF7meffO1ruYVJ6/yEIGan53+7JGfrb72EMM510R+t/W1I7+b+9r+D2DO9N1LgACBigUEQMXDc/SJAm4jQOCBgAB4wOAfAgQI5BMQAPlmrmMCBAg8EEgYAA/69g8BAgTSCwiA9I8AAAIEsgoIgKyT1zeBhAJaPi8gAM57+IsAAQJpBARAmlFrlAABAucFBMB5D3+1LKA3AgTOCQiAcxz+IECAQB4BAZBn1jolQIDAOYFEAXCub38QIEAgvYAASP8IACBAIKuAAMg6eX0TSCSg1csFBMDlLl4lQIBA8wICoPkRa5AAAQKXCwiAy1282pKAXggQuFRAAFzK4kUCBAi0LyAA2p+xDgkQIHCpQIIAuLRvLxIgQCC9gABI/wgAIEAgq4AAyDp5fRNIIKDFwwLhAXDn9t0ucp2cXO0i12G++e/ev3faWfsNvvbTt7qal9nun21vM/8TdLhC5HdDXzvyu62vfbi7+e+GB8D8I6pAgAABAhECAiBCVc0yBJyCAIGDAgLgII83CRAg0K6AAGh3tjojQIDAQYGGA+Bg394kQIBAegEBkP4RAECAQFYBAZB18vom0LCA1oYJCIBhTq4iQIBAcwICoLmRaogAAQLDBATAMCdX1STgrAQIDBIQAIOYXESAAIH2BARAezPVEQECBAYJNBgAg/p2EQECiXlfmwAABDpJREFUBNILCID0jwAAAgSyCgiArJPXN4EGBbQ0TkAAjPNyNQECBJoREADNjFIjBAgQGCcgAMZ5ubpkAWcjQGCUgAAYxeViAgQItCMgANqZpU4IECAwSqChABjVt4sJECCQXkAApH8EABAgkFVAAGSdvL4JNCSglWkC4QFw/ebpLnLduX23i1wnJ1e7yDVtbOXc9e3X3u4i18++8p6u5hVp09cu50mYdpLIz1ZfO/K7oa8d+d3W156mOvyu8AAYfhRXEiBAgMCaAgJgTW17xQioSoDAJAEBMInNTQQIEKhfQADUP0MdECBAYJJAAwEwqW83ESBAIL2AAEj/CAAgQCCrgADIOnl9E2hAQAvzBATAPD93EyBAoFoBAVDt6BycAAEC8wQEwDw/d28pYG8CBGYJCIBZfG4mQIBAvQICoN7ZOTkBAgRmCVQcALP6djMBAgTSCwiA9I8AAAIEsgoIgKyT1zeBigUcfRkBAbCMoyoECBCoTkAAVDcyByZAgMAyAgJgGUdV1hSwFwECiwgIgEUYFSFAgEB9AgKgvpk5MQECBBYRqDAAFulbEQIECKQXEADpHwEABAhkFRAAWSevbwIVCjjysgLVB8D1m6e7yHXn9t0ucp2cXO1qXq984dkuctVs05890qav3e9R84r8bPW1I78b+trLfh2vX636AFifzI4ECBBoQ0AAtDHHHF3okgCBRQUEwKKcihEgQKAeAQFQz6yclAABAosKVBQAi/atGAECBNILCID0jwAAAgSyCgiArJPXN4GKBBw1RkAAxLiqSoAAgeIFBEDxI3JAAgQIxAgIgBhXVZcUUIsAgRABARDCqigBAgTKFxAA5c/ICQkQIBAiUEEAhPStKAECBNILCID0jwAAAgSyCgiArJPXN4EKBBwxVkAAxPqqToAAgWIFBECxo3EwAgQIxAoIgFhf1ecIuJcAgVABARDKqzgBAgTKFRAA5c7GyQgQIBAqUHAAhPatOAECBNILCID0jwAAAgSyCgiArJPXN4GCBRxtHQEBcMT5+s3TXeT60Pf+sYtcd27f7epeb/3v/JGrdp/Y80c+m33tyM9WX/vIxzv92wIg/SMAgACBrAICIOvkS+7b2QgQWEVAAKzCbBMCBAiUJyAAypuJExEgQGAVgQIDYJW+bUKAAIH0AgIg/SMAgACBrAICIOvk9U2gQAFHWldAAKzrbTcCBAgUIyAAihmFgxAgQGBdAQGwrrfdDgl4jwCBVQUEwKrcNiNAgEA5AgKgnFk4CQECBFYVKCgAVu3bZgQIEEgvIADSPwIACBDIKiAAsk5e3wQKEnCUbQQEwDbudiVAgMDmAgJg8xE4AAECBLYREADbuNv1/wX8ToDAJgICYBN2mxIgQGB7AQGw/QycgAABApsIFBAAm/RtUwIECKQXEADpHwEABAhkFRAAWSevbwIFCDjCtgL/BQAA//+pAka0AAAABklEQVQDAMwmGO5zkFekAAAAAElFTkSuQmCC";
|
|
4103
|
+
|
|
4104
|
+
// assets/rino.png
|
|
4105
|
+
var rino_default = "data:image/png;base64,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";
|
|
4106
|
+
|
|
4107
|
+
// assets/roze.png
|
|
4108
|
+
var roze_default = "data:image/png;base64,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";
|
|
4109
|
+
|
|
4110
|
+
// assets/ruko.png
|
|
4111
|
+
var ruko_default = "data:image/png;base64,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";
|
|
4112
|
+
|
|
4113
|
+
// assets/shiyo.png
|
|
4114
|
+
var shiyo_default = "data:image/png;base64,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";
|
|
4115
|
+
|
|
4116
|
+
// assets/teto.png
|
|
4117
|
+
var teto_default = "data:image/png;base64,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";
|
|
4118
|
+
|
|
4119
|
+
// assets/tsukuyomi.png
|
|
4120
|
+
var tsukuyomi_default = "data:image/png;base64,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";
|
|
4121
|
+
|
|
4122
|
+
// src/voice-images.ts
|
|
4123
|
+
var VOICE_IMAGES = {
|
|
4124
|
+
puyuyu: puyuyu_default,
|
|
4125
|
+
rino: rino_default,
|
|
4126
|
+
roze: roze_default,
|
|
4127
|
+
ruko: ruko_default,
|
|
4128
|
+
shiyo: shiyo_default,
|
|
4129
|
+
teto: teto_default,
|
|
4130
|
+
tsukuyomi: tsukuyomi_default
|
|
4131
|
+
};
|
|
2666
4132
|
|
|
2667
4133
|
// src/daw.ts
|
|
2668
4134
|
var BASE_STEP_WIDTH = 0.5;
|
|
@@ -2812,7 +4278,13 @@ var TRACKS_ADVANCED = [
|
|
|
2812
4278
|
}
|
|
2813
4279
|
];
|
|
2814
4280
|
var DEFAULT_TRACKS = TRACKS_SIMPLE;
|
|
2815
|
-
var
|
|
4281
|
+
var LYRIC_MODELS = ["klatt", ...Object.keys(KOE_VOICEBANKS)];
|
|
4282
|
+
var LYRIC_MODEL_LABELS = {
|
|
4283
|
+
klatt: "\u8EFD\u91CF\u30ED\u30DC\u58F0",
|
|
4284
|
+
...KOE_VOICEBANK_LABELS
|
|
4285
|
+
};
|
|
4286
|
+
var lyricModelLabel = (model) => LYRIC_MODEL_LABELS[model] ?? model;
|
|
4287
|
+
var clamp3 = (v, min, max) => Math.min(Math.max(v, min), max);
|
|
2816
4288
|
var mountDAW = (target, options = {}) => {
|
|
2817
4289
|
injectStyles();
|
|
2818
4290
|
const getAudioTime = options.getAudioTime ?? (() => performance.now() / 1e3);
|
|
@@ -2824,7 +4296,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
2824
4296
|
tracks: trackConfigs,
|
|
2825
4297
|
drumPatternNames: Object.keys(drumPatterns),
|
|
2826
4298
|
defaultDrumPattern: drumPatterns.dance ? "dance" : Object.keys(drumPatterns)[0] ?? "none",
|
|
2827
|
-
defaultBpm: options.defaultBpm ??
|
|
4299
|
+
defaultBpm: options.defaultBpm ?? DEFAULT_BPM,
|
|
2828
4300
|
showMidi,
|
|
2829
4301
|
showChord
|
|
2830
4302
|
});
|
|
@@ -2839,10 +4311,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
2839
4311
|
const leftPaddingSteps = renderConfig.stepsPerBar * 16;
|
|
2840
4312
|
let zoomX = 100;
|
|
2841
4313
|
let zoomY = 100;
|
|
2842
|
-
let bpm = options.defaultBpm ??
|
|
4314
|
+
let bpm = options.defaultBpm ?? DEFAULT_BPM;
|
|
2843
4315
|
let masterVolume = 50;
|
|
2844
4316
|
let drumVolume = 80;
|
|
2845
4317
|
let currentDrumPattern = refs.drumSelect.value;
|
|
4318
|
+
let currentInstrument = "";
|
|
2846
4319
|
let activeTrackId = trackConfigs[0].id;
|
|
2847
4320
|
let activeToolMode = "pen";
|
|
2848
4321
|
let currentInsertLength = 48;
|
|
@@ -2852,6 +4325,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
2852
4325
|
let currentOffsetY = (104 - 1 - 60) * renderConfig.keyHeight - 215;
|
|
2853
4326
|
let playStartStep = 0;
|
|
2854
4327
|
let isSolo = false;
|
|
4328
|
+
let lyricTrackIndices = /* @__PURE__ */ new Set();
|
|
2855
4329
|
let playbackState = "stopped";
|
|
2856
4330
|
let pausedPlayStep = 0;
|
|
2857
4331
|
let currentPlayStep = 0;
|
|
@@ -2878,9 +4352,36 @@ var mountDAW = (target, options = {}) => {
|
|
|
2878
4352
|
volume: config.volume,
|
|
2879
4353
|
savedChordInput: "",
|
|
2880
4354
|
savedChordPattern: "block",
|
|
2881
|
-
savedChordRoot: 0
|
|
4355
|
+
savedChordRoot: 0,
|
|
4356
|
+
lyrics: "",
|
|
4357
|
+
lyricModel: "",
|
|
4358
|
+
// 既定は「なし」(歌わない)
|
|
4359
|
+
vocalVolume: DEFAULT_VOCAL_VOLUME,
|
|
4360
|
+
vocalGate: 100,
|
|
4361
|
+
vocalPan: 64,
|
|
4362
|
+
vocalOctave: 0
|
|
2882
4363
|
}));
|
|
2883
4364
|
};
|
|
4365
|
+
const buildLyricsMap = () => {
|
|
4366
|
+
const map = /* @__PURE__ */ new Map();
|
|
4367
|
+
trackStates.forEach((t, i) => {
|
|
4368
|
+
const model = t.lyricModel.trim();
|
|
4369
|
+
const text = t.lyrics.trim();
|
|
4370
|
+
if (!model || !text) return;
|
|
4371
|
+
const syllables = normalizeLyrics(text);
|
|
4372
|
+
if (syllables.length === 0) return;
|
|
4373
|
+
map.set(i, {
|
|
4374
|
+
trackId: i,
|
|
4375
|
+
model: model.toLowerCase(),
|
|
4376
|
+
volume: t.vocalVolume,
|
|
4377
|
+
gate: t.vocalGate,
|
|
4378
|
+
pan: t.vocalPan,
|
|
4379
|
+
octave: t.vocalOctave,
|
|
4380
|
+
syllables
|
|
4381
|
+
});
|
|
4382
|
+
});
|
|
4383
|
+
return map;
|
|
4384
|
+
};
|
|
2884
4385
|
const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
|
|
2885
4386
|
const getMaxNoteStep = () => {
|
|
2886
4387
|
let maxStep = renderConfig.stepsPerBar * 4;
|
|
@@ -2984,7 +4485,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
2984
4485
|
);
|
|
2985
4486
|
const ratio = currentOffsetX / maxOffsetX;
|
|
2986
4487
|
refs.hScrollThumb.style.width = `${thumbW}px`;
|
|
2987
|
-
refs.hScrollThumb.style.left = `${
|
|
4488
|
+
refs.hScrollThumb.style.left = `${clamp3(ratio * (sbW - thumbW), 0, sbW - thumbW)}px`;
|
|
2988
4489
|
}
|
|
2989
4490
|
const totalHeight = renderConfig.keyCount * renderConfig.keyHeight;
|
|
2990
4491
|
const sbH = refs.vScroll.clientHeight;
|
|
@@ -3043,9 +4544,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
3043
4544
|
if (maxOffsetX <= 0) return;
|
|
3044
4545
|
const rect = refs.hScroll.getBoundingClientRect();
|
|
3045
4546
|
const thumbW = Number.parseFloat(refs.hScrollThumb.style.width) || 40;
|
|
3046
|
-
const x =
|
|
4547
|
+
const x = clamp3(clientX - rect.left - thumbW / 2, 0, rect.width - thumbW);
|
|
3047
4548
|
const ratio = x / (rect.width - thumbW);
|
|
3048
|
-
currentOffsetX =
|
|
4549
|
+
currentOffsetX = clamp3(ratio * maxOffsetX, 0, maxOffsetX);
|
|
3049
4550
|
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
3050
4551
|
redrawAll();
|
|
3051
4552
|
};
|
|
@@ -3054,9 +4555,9 @@ var mountDAW = (target, options = {}) => {
|
|
|
3054
4555
|
if (maxOffset <= 0) return;
|
|
3055
4556
|
const rect = refs.vScroll.getBoundingClientRect();
|
|
3056
4557
|
const thumbH = Number.parseFloat(refs.vScrollThumb.style.height) || 40;
|
|
3057
|
-
const y =
|
|
4558
|
+
const y = clamp3(clientY - rect.top - thumbH / 2, 0, rect.height - thumbH);
|
|
3058
4559
|
const ratio = y / (rect.height - thumbH);
|
|
3059
|
-
currentOffsetY =
|
|
4560
|
+
currentOffsetY = clamp3(ratio * maxOffset, 0, maxOffset);
|
|
3060
4561
|
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
3061
4562
|
redrawAll();
|
|
3062
4563
|
};
|
|
@@ -3338,7 +4839,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
3338
4839
|
"wheel",
|
|
3339
4840
|
(event) => {
|
|
3340
4841
|
event.preventDefault();
|
|
3341
|
-
currentOffsetY =
|
|
4842
|
+
currentOffsetY = clamp3(
|
|
3342
4843
|
currentOffsetY + event.deltaY,
|
|
3343
4844
|
0,
|
|
3344
4845
|
getMaxOffsetY()
|
|
@@ -3386,7 +4887,7 @@ var mountDAW = (target, options = {}) => {
|
|
|
3386
4887
|
const centerKey = (currentOffsetY + canvas.height / 2) / renderConfig.keyHeight;
|
|
3387
4888
|
renderConfig.keyHeight = BASE_KEY_HEIGHT * zoomY / 100;
|
|
3388
4889
|
refs.zoomYLabel.textContent = `${zoomY}%`;
|
|
3389
|
-
currentOffsetY =
|
|
4890
|
+
currentOffsetY = clamp3(
|
|
3390
4891
|
centerKey * renderConfig.keyHeight - canvas.height / 2,
|
|
3391
4892
|
0,
|
|
3392
4893
|
getMaxOffsetY()
|
|
@@ -3394,6 +4895,13 @@ var mountDAW = (target, options = {}) => {
|
|
|
3394
4895
|
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
3395
4896
|
redrawAll();
|
|
3396
4897
|
};
|
|
4898
|
+
const getViewState = () => ({
|
|
4899
|
+
zoomX,
|
|
4900
|
+
zoomY,
|
|
4901
|
+
decomposeChord: refs.decomposeChordToggle.checked,
|
|
4902
|
+
ignoreChordHeavy: refs.ignoreChordHeavyToggle.checked
|
|
4903
|
+
});
|
|
4904
|
+
const notifyViewState = () => options.onViewStateChange?.(getViewState());
|
|
3397
4905
|
const dispatchNote = (trackId, pitch, trackVol, velocity, when, duration) => {
|
|
3398
4906
|
const volume = trackVol / 100 * (velocity / 127) * (masterVolume / 100);
|
|
3399
4907
|
options.onPlayNote?.({ trackId, pitch, velocity, volume, when, duration });
|
|
@@ -3410,8 +4918,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
3410
4918
|
getSoloTrackId: () => isSolo ? activeTrackId : null,
|
|
3411
4919
|
getAudioTime,
|
|
3412
4920
|
onPlayNote: (e) => {
|
|
3413
|
-
const
|
|
3414
|
-
|
|
4921
|
+
const idx = trackStates.findIndex((t) => t.config.id === e.trackId);
|
|
4922
|
+
if (idx >= 0 && lyricTrackIndices.has(idx) && options.singingVoices) {
|
|
4923
|
+
return;
|
|
4924
|
+
}
|
|
4925
|
+
options.onPlayNote?.({ ...e, volume: e.volume * (masterVolume / 100) });
|
|
3415
4926
|
},
|
|
3416
4927
|
onPlayDrum: (e) => {
|
|
3417
4928
|
const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
|
|
@@ -3437,10 +4948,53 @@ var mountDAW = (target, options = {}) => {
|
|
|
3437
4948
|
},
|
|
3438
4949
|
stepsPerBar: renderConfig.stepsPerBar
|
|
3439
4950
|
});
|
|
3440
|
-
const play = () => {
|
|
4951
|
+
const play = async () => {
|
|
3441
4952
|
options.onResumeAudio?.();
|
|
3442
4953
|
if (playbackState === "playing") return;
|
|
3443
4954
|
const fromStep = playbackState === "paused" ? pausedPlayStep : playStartStep;
|
|
4955
|
+
options.singingVoices?.reset();
|
|
4956
|
+
const lyricMap = buildLyricsMap();
|
|
4957
|
+
lyricTrackIndices = new Set(lyricMap.keys());
|
|
4958
|
+
const secondsPerStep = 60 / bpm / 48;
|
|
4959
|
+
const streamTracks = options.singingVoices ? [...lyricMap.values()].map((lt) => {
|
|
4960
|
+
const trackState = trackStates[lt.trackId];
|
|
4961
|
+
const sorted = [...trackState?.core.getNotes() ?? []].sort(
|
|
4962
|
+
(a, b) => a.startStep - b.startStep
|
|
4963
|
+
);
|
|
4964
|
+
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
4965
|
+
const semis = (lt.octave ?? 0) * 12;
|
|
4966
|
+
const count = Math.min(sorted.length, lt.syllables.length);
|
|
4967
|
+
const notes = [];
|
|
4968
|
+
for (let i = 0; i < count; i++) {
|
|
4969
|
+
const n = sorted[i];
|
|
4970
|
+
if (n.startStep < fromStep) continue;
|
|
4971
|
+
notes.push({
|
|
4972
|
+
syllable: lt.syllables[i],
|
|
4973
|
+
pitch: n.pitch + semis,
|
|
4974
|
+
startSec: (n.startStep - fromStep) * secondsPerStep,
|
|
4975
|
+
durationSec: n.durationSteps * secondsPerStep * gate
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
return {
|
|
4979
|
+
model: lt.model,
|
|
4980
|
+
volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (masterVolume / 100),
|
|
4981
|
+
pan: panToStereo(lt.pan ?? DEFAULT_PAN),
|
|
4982
|
+
notes
|
|
4983
|
+
};
|
|
4984
|
+
}) : [];
|
|
4985
|
+
const voices = options.singingVoices;
|
|
4986
|
+
const streaming = !!voices && streamTracks.some((t) => t.notes.length > 0);
|
|
4987
|
+
if (streaming && voices) {
|
|
4988
|
+
const overlay = showLoadingOverlay(refs.rollContainer);
|
|
4989
|
+
try {
|
|
4990
|
+
await voices.loadModels(streamTracks.map((t) => t.model));
|
|
4991
|
+
await voices.warm(streamTracks);
|
|
4992
|
+
} catch (err) {
|
|
4993
|
+
console.warn("[dtm] voice preload failed", err);
|
|
4994
|
+
} finally {
|
|
4995
|
+
overlay.remove();
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
3444
4998
|
if (playbackState !== "paused") {
|
|
3445
4999
|
const canvas = getGridCanvas();
|
|
3446
5000
|
currentOffsetX = Math.max(
|
|
@@ -3451,17 +5005,22 @@ var mountDAW = (target, options = {}) => {
|
|
|
3451
5005
|
}
|
|
3452
5006
|
playbackState = "playing";
|
|
3453
5007
|
sequencer.start(fromStep);
|
|
5008
|
+
if (streaming && voices) {
|
|
5009
|
+
voices.startStream(streamTracks, sequencer.getStartTime());
|
|
5010
|
+
}
|
|
3454
5011
|
updateTransport();
|
|
3455
5012
|
};
|
|
3456
5013
|
const pause = () => {
|
|
3457
5014
|
if (playbackState !== "playing") return;
|
|
3458
5015
|
pausedPlayStep = currentPlayStep;
|
|
3459
5016
|
sequencer.stop();
|
|
5017
|
+
options.singingVoices?.stopStream();
|
|
3460
5018
|
playbackState = "paused";
|
|
3461
5019
|
updateTransport();
|
|
3462
5020
|
};
|
|
3463
5021
|
const stop = () => {
|
|
3464
5022
|
sequencer.stop();
|
|
5023
|
+
options.singingVoices?.stopStream();
|
|
3465
5024
|
playbackState = "stopped";
|
|
3466
5025
|
currentPlayStep = 0;
|
|
3467
5026
|
updateTransport();
|
|
@@ -3511,6 +5070,158 @@ var mountDAW = (target, options = {}) => {
|
|
|
3511
5070
|
active.core.setVolume(active.volume);
|
|
3512
5071
|
volLabel.textContent = String(active.volume);
|
|
3513
5072
|
});
|
|
5073
|
+
const lyricDiv = document.createElement("div");
|
|
5074
|
+
lyricDiv.className = "dtm-row";
|
|
5075
|
+
lyricDiv.style.flexDirection = "column";
|
|
5076
|
+
lyricDiv.style.alignItems = "stretch";
|
|
5077
|
+
lyricDiv.innerHTML = `
|
|
5078
|
+
<div class="dtm-row">
|
|
5079
|
+
<span class="dtm-label">\u266A \u6B4C\u8A5E</span>
|
|
5080
|
+
<select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
|
|
5081
|
+
<img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
|
|
5082
|
+
<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">
|
|
5083
|
+
<option value="2">+2 oct</option>
|
|
5084
|
+
<option value="1">+1 oct</option>
|
|
5085
|
+
<option value="0">\xB10 oct</option>
|
|
5086
|
+
<option value="-1">-1 oct</option>
|
|
5087
|
+
<option value="-2">-2 oct</option>
|
|
5088
|
+
</select>
|
|
5089
|
+
<span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
|
|
5090
|
+
</div>
|
|
5091
|
+
<div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
|
|
5092
|
+
<span>\u4F7F\u7528\u6642\u306B\u306F</span>
|
|
5093
|
+
<a data-dtm="lyric-terms-link" target="_blank" rel="noopener" style="color:var(--dtm-primary);text-decoration:underline"></a>
|
|
5094
|
+
<span>\u306E\u5229\u7528\u898F\u7D04\u306B\u5F93\u3063\u3066\u304F\u3060\u3055\u3044</span>
|
|
5095
|
+
</div>
|
|
5096
|
+
<div class="dtm-row" data-dtm="lyric-body" style="flex-direction:column;align-items:stretch">
|
|
5097
|
+
<div class="dtm-row">
|
|
5098
|
+
<span class="dtm-label">\u58F0\u91CF</span>
|
|
5099
|
+
<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">
|
|
5100
|
+
<span class="dtm-label" data-dtm="lyric-vol-label"></span>
|
|
5101
|
+
</div>
|
|
5102
|
+
<div class="dtm-row">
|
|
5103
|
+
<span class="dtm-label">\u5B9A\u4F4D</span>
|
|
5104
|
+
<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">
|
|
5105
|
+
<span class="dtm-label" data-dtm="lyric-pan-label"></span>
|
|
5106
|
+
</div>
|
|
5107
|
+
<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>
|
|
5108
|
+
</div>`;
|
|
5109
|
+
refs.trackBody.appendChild(lyricDiv);
|
|
5110
|
+
const lyricModelSel = lyricDiv.querySelector(
|
|
5111
|
+
'[data-dtm="lyric-model"]'
|
|
5112
|
+
);
|
|
5113
|
+
const lyricOctaveSel = lyricDiv.querySelector(
|
|
5114
|
+
'[data-dtm="lyric-octave"]'
|
|
5115
|
+
);
|
|
5116
|
+
const lyricIcon = lyricDiv.querySelector(
|
|
5117
|
+
'[data-dtm="lyric-icon"]'
|
|
5118
|
+
);
|
|
5119
|
+
const lyricBody = lyricDiv.querySelector(
|
|
5120
|
+
'[data-dtm="lyric-body"]'
|
|
5121
|
+
);
|
|
5122
|
+
const lyricInput = lyricDiv.querySelector(
|
|
5123
|
+
'[data-dtm="lyric-input"]'
|
|
5124
|
+
);
|
|
5125
|
+
const lyricCount = lyricDiv.querySelector(
|
|
5126
|
+
'[data-dtm="lyric-count"]'
|
|
5127
|
+
);
|
|
5128
|
+
const lyricVol = lyricDiv.querySelector(
|
|
5129
|
+
'[data-dtm="lyric-vol"]'
|
|
5130
|
+
);
|
|
5131
|
+
const lyricVolLabel = lyricDiv.querySelector(
|
|
5132
|
+
'[data-dtm="lyric-vol-label"]'
|
|
5133
|
+
);
|
|
5134
|
+
const lyricPan = lyricDiv.querySelector(
|
|
5135
|
+
'[data-dtm="lyric-pan"]'
|
|
5136
|
+
);
|
|
5137
|
+
const lyricPanLabel = lyricDiv.querySelector(
|
|
5138
|
+
'[data-dtm="lyric-pan-label"]'
|
|
5139
|
+
);
|
|
5140
|
+
const lyricTerms = lyricDiv.querySelector(
|
|
5141
|
+
'[data-dtm="lyric-terms"]'
|
|
5142
|
+
);
|
|
5143
|
+
const lyricTermsLink = lyricDiv.querySelector(
|
|
5144
|
+
'[data-dtm="lyric-terms-link"]'
|
|
5145
|
+
);
|
|
5146
|
+
const fmtPan = (pan) => pan === 64 ? "C" : pan < 64 ? `L${64 - pan}` : `R${pan - 64}`;
|
|
5147
|
+
const addOpt = (value, label) => {
|
|
5148
|
+
const o = document.createElement("option");
|
|
5149
|
+
o.value = value;
|
|
5150
|
+
o.textContent = label;
|
|
5151
|
+
lyricModelSel.appendChild(o);
|
|
5152
|
+
};
|
|
5153
|
+
addOpt("", "\u306A\u3057");
|
|
5154
|
+
for (const m of LYRIC_MODELS) addOpt(m, lyricModelLabel(m));
|
|
5155
|
+
if (active.lyricModel && !LYRIC_MODELS.includes(active.lyricModel)) {
|
|
5156
|
+
addOpt(active.lyricModel, lyricModelLabel(active.lyricModel));
|
|
5157
|
+
}
|
|
5158
|
+
lyricModelSel.value = active.lyricModel;
|
|
5159
|
+
lyricOctaveSel.value = String(active.vocalOctave);
|
|
5160
|
+
lyricInput.value = active.lyrics;
|
|
5161
|
+
lyricVol.value = String(active.vocalVolume);
|
|
5162
|
+
lyricVolLabel.textContent = String(active.vocalVolume);
|
|
5163
|
+
lyricPan.value = String(active.vocalPan);
|
|
5164
|
+
lyricPanLabel.textContent = fmtPan(active.vocalPan);
|
|
5165
|
+
const updateLyricCount = () => {
|
|
5166
|
+
const n = normalizeLyrics(lyricInput.value).length;
|
|
5167
|
+
lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
|
|
5168
|
+
};
|
|
5169
|
+
const syncLyricTerms = () => {
|
|
5170
|
+
const url = active.lyricModel ? KOE_VOICEBANK_TERMS[active.lyricModel] : void 0;
|
|
5171
|
+
if (url) {
|
|
5172
|
+
const label = lyricModelLabel(active.lyricModel);
|
|
5173
|
+
lyricTermsLink.textContent = `${label}UTAU\u97F3\u6E90`;
|
|
5174
|
+
lyricTermsLink.href = url;
|
|
5175
|
+
lyricTerms.classList.remove("dtm-hidden");
|
|
5176
|
+
} else {
|
|
5177
|
+
lyricTerms.classList.add("dtm-hidden");
|
|
5178
|
+
}
|
|
5179
|
+
};
|
|
5180
|
+
const syncLyricIcon = () => {
|
|
5181
|
+
const imgKey = active.lyricModel ? VOICE_IMAGE_KEY[active.lyricModel.toLowerCase()] : void 0;
|
|
5182
|
+
const src = imgKey ? VOICE_IMAGES[imgKey] : void 0;
|
|
5183
|
+
if (src) {
|
|
5184
|
+
lyricIcon.src = src;
|
|
5185
|
+
lyricIcon.classList.remove("dtm-hidden");
|
|
5186
|
+
} else {
|
|
5187
|
+
lyricIcon.removeAttribute("src");
|
|
5188
|
+
lyricIcon.classList.add("dtm-hidden");
|
|
5189
|
+
}
|
|
5190
|
+
};
|
|
5191
|
+
const syncLyricVisibility = () => {
|
|
5192
|
+
lyricBody.style.display = active.lyricModel ? "" : "none";
|
|
5193
|
+
lyricOctaveSel.style.display = active.lyricModel ? "" : "none";
|
|
5194
|
+
updateLyricCount();
|
|
5195
|
+
syncLyricTerms();
|
|
5196
|
+
syncLyricIcon();
|
|
5197
|
+
};
|
|
5198
|
+
syncLyricVisibility();
|
|
5199
|
+
lyricModelSel.addEventListener("change", () => {
|
|
5200
|
+
active.lyricModel = lyricModelSel.value;
|
|
5201
|
+
syncLyricVisibility();
|
|
5202
|
+
});
|
|
5203
|
+
lyricOctaveSel.addEventListener("change", () => {
|
|
5204
|
+
active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
|
|
5205
|
+
});
|
|
5206
|
+
lyricInput.addEventListener("input", () => {
|
|
5207
|
+
active.lyrics = lyricInput.value;
|
|
5208
|
+
updateLyricCount();
|
|
5209
|
+
});
|
|
5210
|
+
lyricVol.addEventListener("input", () => {
|
|
5211
|
+
active.vocalVolume = Number.parseInt(lyricVol.value, 10);
|
|
5212
|
+
lyricVolLabel.textContent = lyricVol.value;
|
|
5213
|
+
});
|
|
5214
|
+
lyricPan.addEventListener("input", () => {
|
|
5215
|
+
active.vocalPan = Number.parseInt(lyricPan.value, 10);
|
|
5216
|
+
lyricPanLabel.textContent = fmtPan(active.vocalPan);
|
|
5217
|
+
});
|
|
5218
|
+
lyricPanLabel.style.cursor = "pointer";
|
|
5219
|
+
lyricPanLabel.title = "\u30BF\u30C3\u30D7\u3067\u4E2D\u592E(C)\u3078";
|
|
5220
|
+
lyricPanLabel.addEventListener("click", () => {
|
|
5221
|
+
active.vocalPan = 64;
|
|
5222
|
+
lyricPan.value = "64";
|
|
5223
|
+
lyricPanLabel.textContent = fmtPan(64);
|
|
5224
|
+
});
|
|
3514
5225
|
if (active.config.id === "chord" && showChord) {
|
|
3515
5226
|
const div = document.createElement("div");
|
|
3516
5227
|
div.className = "dtm-row";
|
|
@@ -3598,6 +5309,11 @@ var mountDAW = (target, options = {}) => {
|
|
|
3598
5309
|
const barLimitBars = Number(refs.barLimitSelect.value);
|
|
3599
5310
|
const limitSteps = barLimitBars > 0 ? barLimitBars * renderConfig.stepsPerBar : Infinity;
|
|
3600
5311
|
const clipNotes = (notes) => limitSteps === Infinity ? notes : notes.filter((n) => n.startStep < limitSteps);
|
|
5312
|
+
const metaLine = formatMmlMeta({
|
|
5313
|
+
instrument: currentInstrument || void 0,
|
|
5314
|
+
drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
|
|
5315
|
+
volume: masterVolume
|
|
5316
|
+
});
|
|
3601
5317
|
if (refs.decomposeChordToggle.checked) {
|
|
3602
5318
|
const ignoreHeavy = refs.ignoreChordHeavyToggle.checked;
|
|
3603
5319
|
const targetStates = ignoreHeavy ? trackStates.filter((t) => !isChordHeavyTrack(t.core.getNotes())) : trackStates;
|
|
@@ -3607,12 +5323,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
3607
5323
|
);
|
|
3608
5324
|
const monoTracks = decomposeToMonophonic(allNotes);
|
|
3609
5325
|
const refCore = trackStates[0].core;
|
|
3610
|
-
const
|
|
5326
|
+
const decomposedFull = monoTracks.map(
|
|
3611
5327
|
(notes, i) => `@${i} ${refCore.getMMLFromNotes(notes, bpm, 100).trim()}`
|
|
3612
|
-
)
|
|
3613
|
-
const
|
|
5328
|
+
);
|
|
5329
|
+
const decomposedMini = monoTracks.map(
|
|
3614
5330
|
(notes, i) => `@${i}${refCore.getMMLFromNotes(notes, bpm, 100).trim().replace(/\s+/g, "")}`
|
|
3615
|
-
)
|
|
5331
|
+
);
|
|
5332
|
+
const full2 = [metaLine, ...decomposedFull].filter((s) => s.length > 0).join(";\n");
|
|
5333
|
+
const minified2 = [metaLine, ...decomposedMini].filter((s) => s.length > 0).join(";");
|
|
3616
5334
|
return {
|
|
3617
5335
|
full: full2,
|
|
3618
5336
|
minified: minified2,
|
|
@@ -3621,12 +5339,32 @@ var mountDAW = (target, options = {}) => {
|
|
|
3621
5339
|
barLimit: barLimitBars
|
|
3622
5340
|
};
|
|
3623
5341
|
}
|
|
3624
|
-
const
|
|
5342
|
+
const trackLines = trackStates.map(
|
|
3625
5343
|
(t, i) => `@${i} ${t.core.getMMLFromNotes(clipNotes(t.core.getNotes()), bpm, t.volume).trim()}`
|
|
3626
|
-
)
|
|
3627
|
-
const
|
|
5344
|
+
);
|
|
5345
|
+
const trackLinesMini = trackStates.map(
|
|
3628
5346
|
(t, i) => `@${i}${t.core.getMMLFromNotes(clipNotes(t.core.getNotes()), bpm, t.volume).trim().replace(/\s+/g, "")}`
|
|
3629
|
-
)
|
|
5347
|
+
);
|
|
5348
|
+
const lyricLines = trackStates.map((t, i) => ({
|
|
5349
|
+
i,
|
|
5350
|
+
text: t.lyrics.trim(),
|
|
5351
|
+
model: t.lyricModel.trim(),
|
|
5352
|
+
vol: t.vocalVolume,
|
|
5353
|
+
gate: t.vocalGate,
|
|
5354
|
+
pan: t.vocalPan,
|
|
5355
|
+
oct: t.vocalOctave
|
|
5356
|
+
})).filter((x) => x.model.length > 0 && x.text.length > 0).map((x) => {
|
|
5357
|
+
const params = [
|
|
5358
|
+
x.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x.vol}`,
|
|
5359
|
+
x.gate === 100 ? "" : `q${x.gate}`,
|
|
5360
|
+
x.pan === 64 ? "" : `p${x.pan}`,
|
|
5361
|
+
x.oct === 0 ? "" : `o${x.oct}`
|
|
5362
|
+
].filter((s) => s.length > 0).join(" ");
|
|
5363
|
+
const head = params ? `${x.model} ${params}` : x.model;
|
|
5364
|
+
return `@@${x.i} ${head} ${x.text}`;
|
|
5365
|
+
});
|
|
5366
|
+
const full = [metaLine, ...trackLines, ...lyricLines].filter((s) => s.length > 0).join(";\n");
|
|
5367
|
+
const minified = [metaLine, ...trackLinesMini, ...lyricLines].filter((s) => s.length > 0).join(";");
|
|
3630
5368
|
return {
|
|
3631
5369
|
full,
|
|
3632
5370
|
minified,
|
|
@@ -3647,6 +5385,34 @@ var mountDAW = (target, options = {}) => {
|
|
|
3647
5385
|
refs.outputContainer.classList.remove("dtm-hidden");
|
|
3648
5386
|
updateUndoRedo();
|
|
3649
5387
|
};
|
|
5388
|
+
const getFirstDetectedPitch = () => {
|
|
5389
|
+
let minStep = Number.MAX_SAFE_INTEGER;
|
|
5390
|
+
let candidateNotes = [];
|
|
5391
|
+
for (const t of trackStates) {
|
|
5392
|
+
for (const note of t.core.getNotes()) {
|
|
5393
|
+
if (note.startStep < minStep) {
|
|
5394
|
+
minStep = note.startStep;
|
|
5395
|
+
candidateNotes = [note];
|
|
5396
|
+
} else if (note.startStep === minStep) {
|
|
5397
|
+
candidateNotes.push(note);
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
if (candidateNotes.length === 0) return null;
|
|
5402
|
+
const sum = candidateNotes.reduce((acc, note) => acc + note.pitch, 0);
|
|
5403
|
+
return Math.round(sum / candidateNotes.length);
|
|
5404
|
+
};
|
|
5405
|
+
const centerPitch = (pitch) => {
|
|
5406
|
+
const canvas = getGridCanvas();
|
|
5407
|
+
const yIndex = renderConfig.keyCount - 1 - (pitch - renderConfig.pitchRangeStart);
|
|
5408
|
+
const logicalY = yIndex * renderConfig.keyHeight;
|
|
5409
|
+
currentOffsetY = clamp3(
|
|
5410
|
+
logicalY - (canvas.height - renderConfig.keyHeight) / 2,
|
|
5411
|
+
0,
|
|
5412
|
+
getMaxOffsetY()
|
|
5413
|
+
);
|
|
5414
|
+
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
5415
|
+
};
|
|
3650
5416
|
const clearAll = () => {
|
|
3651
5417
|
for (const t of trackStates) {
|
|
3652
5418
|
t.core.resetHistory();
|
|
@@ -3658,8 +5424,45 @@ var mountDAW = (target, options = {}) => {
|
|
|
3658
5424
|
if (!mml) return;
|
|
3659
5425
|
clearAll();
|
|
3660
5426
|
for (const t of trackStates) t.core.setLoadMode(true);
|
|
3661
|
-
const {
|
|
3662
|
-
|
|
5427
|
+
const {
|
|
5428
|
+
placements,
|
|
5429
|
+
bpm: parsedBpm,
|
|
5430
|
+
lyrics,
|
|
5431
|
+
meta
|
|
5432
|
+
} = parseMML(mml, {
|
|
5433
|
+
stepsPerBar: renderConfig.stepsPerBar,
|
|
5434
|
+
collectLyrics: true,
|
|
5435
|
+
// このDAWのトラック数を超えるチャンネルはベースへ畳み込む(従来挙動)
|
|
5436
|
+
clampTrackCount: trackStates.length
|
|
5437
|
+
});
|
|
5438
|
+
currentInstrument = meta.instrument ?? "";
|
|
5439
|
+
if (meta.drum && drumPatterns[meta.drum]) {
|
|
5440
|
+
currentDrumPattern = meta.drum;
|
|
5441
|
+
refs.drumSelect.value = meta.drum;
|
|
5442
|
+
options.onDrumChange?.(meta.drum);
|
|
5443
|
+
}
|
|
5444
|
+
if (meta.volume !== void 0) {
|
|
5445
|
+
masterVolume = meta.volume;
|
|
5446
|
+
refs.masterVolume.value = String(meta.volume);
|
|
5447
|
+
refs.masterVolumeLabel.textContent = `${meta.volume}%`;
|
|
5448
|
+
}
|
|
5449
|
+
for (const t of trackStates) {
|
|
5450
|
+
t.lyrics = "";
|
|
5451
|
+
t.lyricModel = "";
|
|
5452
|
+
t.vocalVolume = DEFAULT_VOCAL_VOLUME;
|
|
5453
|
+
t.vocalGate = 100;
|
|
5454
|
+
t.vocalPan = 64;
|
|
5455
|
+
t.vocalOctave = 0;
|
|
5456
|
+
}
|
|
5457
|
+
lyrics?.forEach((lt, idx) => {
|
|
5458
|
+
const t = trackStates[idx];
|
|
5459
|
+
if (!t) return;
|
|
5460
|
+
t.lyrics = lt.syllables.map((s) => s.kana).join("");
|
|
5461
|
+
t.lyricModel = lt.model;
|
|
5462
|
+
t.vocalVolume = lt.volume;
|
|
5463
|
+
t.vocalGate = lt.gate;
|
|
5464
|
+
t.vocalPan = lt.pan;
|
|
5465
|
+
t.vocalOctave = lt.octave ?? 0;
|
|
3663
5466
|
});
|
|
3664
5467
|
for (const p of placements) {
|
|
3665
5468
|
const t = trackStates[p.trackIndex];
|
|
@@ -3675,8 +5478,14 @@ var mountDAW = (target, options = {}) => {
|
|
|
3675
5478
|
}
|
|
3676
5479
|
playStartStep = 0;
|
|
3677
5480
|
currentOffsetX = 0;
|
|
3678
|
-
|
|
5481
|
+
const firstPitch = getFirstDetectedPitch();
|
|
5482
|
+
if (firstPitch !== null) {
|
|
5483
|
+
centerPitch(firstPitch);
|
|
5484
|
+
} else {
|
|
5485
|
+
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
5486
|
+
}
|
|
3679
5487
|
redrawAll();
|
|
5488
|
+
updateTrackPanel();
|
|
3680
5489
|
updateUndoRedo();
|
|
3681
5490
|
};
|
|
3682
5491
|
const applyChord = () => {
|
|
@@ -3736,7 +5545,12 @@ var mountDAW = (target, options = {}) => {
|
|
|
3736
5545
|
}
|
|
3737
5546
|
playStartStep = 0;
|
|
3738
5547
|
currentOffsetX = 0;
|
|
3739
|
-
|
|
5548
|
+
const firstPitch = getFirstDetectedPitch();
|
|
5549
|
+
if (firstPitch !== null) {
|
|
5550
|
+
centerPitch(firstPitch);
|
|
5551
|
+
} else {
|
|
5552
|
+
setDrawOffset(currentOffsetX, currentOffsetY);
|
|
5553
|
+
}
|
|
3740
5554
|
redrawAll();
|
|
3741
5555
|
updateUndoRedo();
|
|
3742
5556
|
};
|
|
@@ -3800,25 +5614,32 @@ var mountDAW = (target, options = {}) => {
|
|
|
3800
5614
|
refs.zoomXIn.addEventListener("click", () => {
|
|
3801
5615
|
zoomX = Math.min(200, zoomX + 25);
|
|
3802
5616
|
applyZoomX();
|
|
5617
|
+
notifyViewState();
|
|
3803
5618
|
});
|
|
3804
5619
|
refs.zoomXOut.addEventListener("click", () => {
|
|
3805
5620
|
zoomX = Math.max(25, zoomX - 25);
|
|
3806
5621
|
applyZoomX();
|
|
5622
|
+
notifyViewState();
|
|
3807
5623
|
});
|
|
3808
5624
|
refs.zoomYIn.addEventListener("click", () => {
|
|
3809
5625
|
zoomY = Math.min(200, zoomY + 25);
|
|
3810
5626
|
applyZoomY();
|
|
5627
|
+
notifyViewState();
|
|
3811
5628
|
});
|
|
3812
5629
|
refs.zoomYOut.addEventListener("click", () => {
|
|
3813
5630
|
zoomY = Math.max(50, zoomY - 25);
|
|
3814
5631
|
applyZoomY();
|
|
5632
|
+
notifyViewState();
|
|
3815
5633
|
});
|
|
5634
|
+
refs.decomposeChordToggle.addEventListener("change", notifyViewState);
|
|
5635
|
+
refs.ignoreChordHeavyToggle.addEventListener("change", notifyViewState);
|
|
3816
5636
|
refs.masterVolume.addEventListener("input", () => {
|
|
3817
5637
|
masterVolume = Number.parseInt(refs.masterVolume.value, 10) || 0;
|
|
3818
5638
|
refs.masterVolumeLabel.textContent = `${masterVolume}%`;
|
|
3819
5639
|
});
|
|
3820
5640
|
refs.drumSelect.addEventListener("change", () => {
|
|
3821
5641
|
currentDrumPattern = refs.drumSelect.value;
|
|
5642
|
+
options.onDrumChange?.(currentDrumPattern);
|
|
3822
5643
|
});
|
|
3823
5644
|
refs.drumVolume.addEventListener("input", () => {
|
|
3824
5645
|
drumVolume = Number.parseInt(refs.drumVolume.value, 10) || 0;
|
|
@@ -4008,6 +5829,33 @@ var mountDAW = (target, options = {}) => {
|
|
|
4008
5829
|
pause,
|
|
4009
5830
|
stop,
|
|
4010
5831
|
getMML: generateMML,
|
|
5832
|
+
setInstrument: (name) => {
|
|
5833
|
+
currentInstrument = name;
|
|
5834
|
+
},
|
|
5835
|
+
getDrum: () => currentDrumPattern,
|
|
5836
|
+
setDrum: (name) => {
|
|
5837
|
+
if (name !== "none" && !drumPatterns[name]) return;
|
|
5838
|
+
currentDrumPattern = name;
|
|
5839
|
+
refs.drumSelect.value = name;
|
|
5840
|
+
options.onDrumChange?.(name);
|
|
5841
|
+
},
|
|
5842
|
+
getViewState,
|
|
5843
|
+
setViewState: (state) => {
|
|
5844
|
+
if (typeof state.zoomX === "number") {
|
|
5845
|
+
zoomX = clamp3(state.zoomX, 25, 200);
|
|
5846
|
+
applyZoomX();
|
|
5847
|
+
}
|
|
5848
|
+
if (typeof state.zoomY === "number") {
|
|
5849
|
+
zoomY = clamp3(state.zoomY, 50, 200);
|
|
5850
|
+
applyZoomY();
|
|
5851
|
+
}
|
|
5852
|
+
if (typeof state.decomposeChord === "boolean") {
|
|
5853
|
+
refs.decomposeChordToggle.checked = state.decomposeChord;
|
|
5854
|
+
}
|
|
5855
|
+
if (typeof state.ignoreChordHeavy === "boolean") {
|
|
5856
|
+
refs.ignoreChordHeavyToggle.checked = state.ignoreChordHeavy;
|
|
5857
|
+
}
|
|
5858
|
+
},
|
|
4011
5859
|
loadMML,
|
|
4012
5860
|
loadMIDI,
|
|
4013
5861
|
exportMIDI: exportMIDI2,
|
|
@@ -4128,6 +5976,529 @@ var INSTRUMENT_PRESETS = {
|
|
|
4128
5976
|
}
|
|
4129
5977
|
};
|
|
4130
5978
|
|
|
5979
|
+
// src/mml-player.ts
|
|
5980
|
+
var STEPS_PER_BEAT3 = 48;
|
|
5981
|
+
var STEPS_PER_BAR = 192;
|
|
5982
|
+
var DEFAULT_TRACK_COLORS = ["#00e436", "#29adff", "#ff77a8", "#ffec27"];
|
|
5983
|
+
var activePlayer = null;
|
|
5984
|
+
var freqFromPitch = (pitch) => 440 * 2 ** ((pitch - 69) / 12);
|
|
5985
|
+
var mountMmlPlayer = (target, mml, options = {}) => {
|
|
5986
|
+
injectStyles(target.ownerDocument ?? document);
|
|
5987
|
+
const {
|
|
5988
|
+
placements,
|
|
5989
|
+
bpm: parsedBpm,
|
|
5990
|
+
tokenTracks,
|
|
5991
|
+
lyrics,
|
|
5992
|
+
meta
|
|
5993
|
+
} = parseMML(mml, {
|
|
5994
|
+
collectTokens: true,
|
|
5995
|
+
collectLyrics: true
|
|
5996
|
+
});
|
|
5997
|
+
const lyricTracks = lyrics ?? /* @__PURE__ */ new Map();
|
|
5998
|
+
const bpm = parsedBpm ?? options.defaultBpm ?? DEFAULT_BPM;
|
|
5999
|
+
const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
|
|
6000
|
+
const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
|
|
6001
|
+
const trackVolume = meta.volume ?? options.volume ?? 100;
|
|
6002
|
+
const colors = options.trackColors ?? DEFAULT_TRACK_COLORS;
|
|
6003
|
+
const useSynth = options.synth ?? !options.onPlayNote;
|
|
6004
|
+
const secondsPerStep = 60 / bpm / STEPS_PER_BEAT3;
|
|
6005
|
+
const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
|
|
6006
|
+
(a, b) => a - b
|
|
6007
|
+
);
|
|
6008
|
+
const seqTracks = trackIndices.map((index) => {
|
|
6009
|
+
let id = 0;
|
|
6010
|
+
const notes = placements.filter((p) => p.trackIndex === index).map((p) => ({
|
|
6011
|
+
id: id++,
|
|
6012
|
+
startStep: p.startStep,
|
|
6013
|
+
durationSteps: p.durationSteps,
|
|
6014
|
+
pitch: p.pitch,
|
|
6015
|
+
velocity: 100
|
|
6016
|
+
}));
|
|
6017
|
+
return { id: String(index), volume: trackVolume, notes };
|
|
6018
|
+
});
|
|
6019
|
+
const colorOf = (index) => colors[index % colors.length] ?? DEFAULT_TRACK_COLORS[0];
|
|
6020
|
+
let audioCtx = null;
|
|
6021
|
+
const ensureCtx = () => {
|
|
6022
|
+
if (!audioCtx) audioCtx = new AudioContext();
|
|
6023
|
+
return audioCtx;
|
|
6024
|
+
};
|
|
6025
|
+
const synthPlay = (e) => {
|
|
6026
|
+
const ctx = ensureCtx();
|
|
6027
|
+
const osc = ctx.createOscillator();
|
|
6028
|
+
const gain = ctx.createGain();
|
|
6029
|
+
osc.type = "square";
|
|
6030
|
+
osc.frequency.value = freqFromPitch(e.pitch);
|
|
6031
|
+
const t0 = ctx.currentTime + e.when;
|
|
6032
|
+
const peak = Math.max(1e-4, 0.06 * e.volume * 1.5);
|
|
6033
|
+
gain.gain.setValueAtTime(peak, t0);
|
|
6034
|
+
gain.gain.exponentialRampToValueAtTime(1e-3, t0 + e.duration);
|
|
6035
|
+
osc.connect(gain);
|
|
6036
|
+
if (typeof ctx.createStereoPanner === "function" && e.pan) {
|
|
6037
|
+
const panner = ctx.createStereoPanner();
|
|
6038
|
+
panner.pan.value = Math.max(-1, Math.min(1, e.pan));
|
|
6039
|
+
gain.connect(panner);
|
|
6040
|
+
panner.connect(ctx.destination);
|
|
6041
|
+
} else {
|
|
6042
|
+
gain.connect(ctx.destination);
|
|
6043
|
+
}
|
|
6044
|
+
osc.start(t0);
|
|
6045
|
+
osc.stop(t0 + e.duration + 0.02);
|
|
6046
|
+
};
|
|
6047
|
+
const drumSynth = (e) => {
|
|
6048
|
+
const ctx = ensureCtx();
|
|
6049
|
+
const t0 = ctx.currentTime + e.when;
|
|
6050
|
+
const vol = Math.max(1e-4, Math.min(1, e.velocity));
|
|
6051
|
+
const isKick = e.pitch === 35 || e.pitch === 36;
|
|
6052
|
+
const isSnareLike = e.pitch === 38 || e.pitch === 39 || e.pitch === 40;
|
|
6053
|
+
if (isKick) {
|
|
6054
|
+
const osc = ctx.createOscillator();
|
|
6055
|
+
const g2 = ctx.createGain();
|
|
6056
|
+
osc.frequency.setValueAtTime(150, t0);
|
|
6057
|
+
osc.frequency.exponentialRampToValueAtTime(50, t0 + 0.12);
|
|
6058
|
+
g2.gain.setValueAtTime(vol * 0.9, t0);
|
|
6059
|
+
g2.gain.exponentialRampToValueAtTime(1e-3, t0 + 0.18);
|
|
6060
|
+
osc.connect(g2).connect(ctx.destination);
|
|
6061
|
+
osc.start(t0);
|
|
6062
|
+
osc.stop(t0 + 0.2);
|
|
6063
|
+
osc.onended = () => osc.disconnect();
|
|
6064
|
+
return;
|
|
6065
|
+
}
|
|
6066
|
+
const dur = isSnareLike ? 0.18 : 0.05;
|
|
6067
|
+
const length = Math.max(1, Math.floor(ctx.sampleRate * dur));
|
|
6068
|
+
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
|
|
6069
|
+
const data = buffer.getChannelData(0);
|
|
6070
|
+
for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
|
|
6071
|
+
const src = ctx.createBufferSource();
|
|
6072
|
+
src.buffer = buffer;
|
|
6073
|
+
const filter = ctx.createBiquadFilter();
|
|
6074
|
+
filter.type = isSnareLike ? "bandpass" : "highpass";
|
|
6075
|
+
filter.frequency.value = isSnareLike ? 2e3 : 8e3;
|
|
6076
|
+
const g = ctx.createGain();
|
|
6077
|
+
g.gain.setValueAtTime(vol * (isSnareLike ? 0.7 : 0.4), t0);
|
|
6078
|
+
g.gain.exponentialRampToValueAtTime(1e-3, t0 + dur);
|
|
6079
|
+
src.connect(filter).connect(g).connect(ctx.destination);
|
|
6080
|
+
src.start(t0);
|
|
6081
|
+
src.stop(t0 + dur);
|
|
6082
|
+
src.onended = () => {
|
|
6083
|
+
src.disconnect();
|
|
6084
|
+
filter.disconnect();
|
|
6085
|
+
g.disconnect();
|
|
6086
|
+
};
|
|
6087
|
+
};
|
|
6088
|
+
let voices = null;
|
|
6089
|
+
const ensureVoices = () => {
|
|
6090
|
+
if (options.singingVoices) return options.singingVoices;
|
|
6091
|
+
if (!voices) {
|
|
6092
|
+
const ctx = ensureCtx();
|
|
6093
|
+
voices = createSingingVoices(ctx, ctx.destination);
|
|
6094
|
+
}
|
|
6095
|
+
return voices;
|
|
6096
|
+
};
|
|
6097
|
+
const voicesAvailable = useSynth || !!options.singingVoices;
|
|
6098
|
+
const peekVoices = () => options.singingVoices ?? voices;
|
|
6099
|
+
const getAudioTime = () => {
|
|
6100
|
+
if (useSynth) return ensureCtx().currentTime;
|
|
6101
|
+
return options.getAudioTime?.() ?? performance.now() / 1e3;
|
|
6102
|
+
};
|
|
6103
|
+
const doc = target.ownerDocument ?? document;
|
|
6104
|
+
const root = doc.createElement("div");
|
|
6105
|
+
root.className = "dtm-daw dtm-player";
|
|
6106
|
+
const head = doc.createElement("div");
|
|
6107
|
+
head.className = "dtm-player-head";
|
|
6108
|
+
const playBtn = doc.createElement("button");
|
|
6109
|
+
playBtn.type = "button";
|
|
6110
|
+
playBtn.className = "dtm-player-play";
|
|
6111
|
+
playBtn.innerHTML = icon("play", 12);
|
|
6112
|
+
playBtn.disabled = trackIndices.length === 0;
|
|
6113
|
+
const mmlHeader = doc.createElement("div");
|
|
6114
|
+
mmlHeader.className = "dtm-player-mml-header";
|
|
6115
|
+
const emojiEls = [];
|
|
6116
|
+
const emojiByTrack = /* @__PURE__ */ new Map();
|
|
6117
|
+
for (const index of trackIndices) {
|
|
6118
|
+
const em = doc.createElement("span");
|
|
6119
|
+
em.className = "dtm-player-emoji";
|
|
6120
|
+
em.style.backgroundColor = colorOf(index);
|
|
6121
|
+
em.textContent = "\u{1F97A}";
|
|
6122
|
+
mmlHeader.appendChild(em);
|
|
6123
|
+
emojiEls.push(em);
|
|
6124
|
+
emojiByTrack.set(index, em);
|
|
6125
|
+
}
|
|
6126
|
+
const promotedToImage = /* @__PURE__ */ new Set();
|
|
6127
|
+
for (const [index, lt] of lyricTracks) {
|
|
6128
|
+
const em = emojiByTrack.get(index);
|
|
6129
|
+
if (!em) continue;
|
|
6130
|
+
const imgKey = VOICE_IMAGE_KEY[lt.model.toLowerCase()];
|
|
6131
|
+
const src = imgKey ? VOICE_IMAGES[imgKey] : void 0;
|
|
6132
|
+
if (!src) continue;
|
|
6133
|
+
const img = doc.createElement("img");
|
|
6134
|
+
img.src = src;
|
|
6135
|
+
img.width = 20;
|
|
6136
|
+
img.height = 20;
|
|
6137
|
+
img.style.borderRadius = "50%";
|
|
6138
|
+
img.style.objectFit = "cover";
|
|
6139
|
+
img.draggable = false;
|
|
6140
|
+
promotedToImage.add(em);
|
|
6141
|
+
em.textContent = "";
|
|
6142
|
+
em.appendChild(img);
|
|
6143
|
+
}
|
|
6144
|
+
const JUMP_DEDUPE_MS = 50;
|
|
6145
|
+
const lastJumpAt = /* @__PURE__ */ new WeakMap();
|
|
6146
|
+
const jumpEmoji = (em) => {
|
|
6147
|
+
const now = performance.now();
|
|
6148
|
+
const prev = lastJumpAt.get(em);
|
|
6149
|
+
if (prev !== void 0 && now - prev < JUMP_DEDUPE_MS) return;
|
|
6150
|
+
lastJumpAt.set(em, now);
|
|
6151
|
+
em.classList.remove("dtm-player-emoji--jump");
|
|
6152
|
+
void em.offsetWidth;
|
|
6153
|
+
em.classList.add("dtm-player-emoji--jump");
|
|
6154
|
+
};
|
|
6155
|
+
const jumpTimers = [];
|
|
6156
|
+
const jumpEmojiAt = (em, when) => {
|
|
6157
|
+
if (when <= 0) {
|
|
6158
|
+
jumpEmoji(em);
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
jumpTimers.push(setTimeout(() => jumpEmoji(em), when * 1e3));
|
|
6162
|
+
};
|
|
6163
|
+
const clearJumpTimers = () => {
|
|
6164
|
+
for (const t of jumpTimers) clearTimeout(t);
|
|
6165
|
+
jumpTimers.length = 0;
|
|
6166
|
+
};
|
|
6167
|
+
const blinkTimers = [];
|
|
6168
|
+
const scheduleBlink = (em) => {
|
|
6169
|
+
const delay = 2e3 + Math.random() * 5e3;
|
|
6170
|
+
const t = setTimeout(() => {
|
|
6171
|
+
if (promotedToImage.has(em)) return;
|
|
6172
|
+
em.textContent = "\u{1F60C}";
|
|
6173
|
+
const t2 = setTimeout(
|
|
6174
|
+
() => {
|
|
6175
|
+
if (promotedToImage.has(em)) return;
|
|
6176
|
+
em.textContent = "\u{1F97A}";
|
|
6177
|
+
scheduleBlink(em);
|
|
6178
|
+
},
|
|
6179
|
+
200 + Math.random() * 150
|
|
6180
|
+
);
|
|
6181
|
+
blinkTimers.push(t2);
|
|
6182
|
+
}, delay);
|
|
6183
|
+
blinkTimers.push(t);
|
|
6184
|
+
};
|
|
6185
|
+
for (const em of emojiEls) scheduleBlink(em);
|
|
6186
|
+
const dots = doc.createElement("div");
|
|
6187
|
+
dots.className = "dtm-player-dots";
|
|
6188
|
+
dots.style.display = "none";
|
|
6189
|
+
for (const index of trackIndices) {
|
|
6190
|
+
const dot = doc.createElement("span");
|
|
6191
|
+
dot.className = "dtm-player-dot";
|
|
6192
|
+
dot.style.backgroundColor = colorOf(index);
|
|
6193
|
+
dots.appendChild(dot);
|
|
6194
|
+
}
|
|
6195
|
+
const beatRow = doc.createElement("div");
|
|
6196
|
+
beatRow.className = "dtm-player-beat-row";
|
|
6197
|
+
const beatDots = [];
|
|
6198
|
+
for (let i = 0; i < 4; i++) {
|
|
6199
|
+
const d = doc.createElement("span");
|
|
6200
|
+
d.className = "dtm-player-beat-dot";
|
|
6201
|
+
beatRow.appendChild(d);
|
|
6202
|
+
beatDots.push(d);
|
|
6203
|
+
}
|
|
6204
|
+
const barEl = doc.createElement("span");
|
|
6205
|
+
barEl.className = "dtm-player-bar";
|
|
6206
|
+
barEl.textContent = "-";
|
|
6207
|
+
beatRow.appendChild(barEl);
|
|
6208
|
+
const chips = [];
|
|
6209
|
+
const makeChip = (label) => {
|
|
6210
|
+
const chip = doc.createElement("span");
|
|
6211
|
+
chip.className = "dtm-player-chip";
|
|
6212
|
+
chip.textContent = label;
|
|
6213
|
+
chips.push(chip);
|
|
6214
|
+
return chip;
|
|
6215
|
+
};
|
|
6216
|
+
if (meta.instrument) makeChip(`\u266A ${meta.instrument}`);
|
|
6217
|
+
if (meta.drum) makeChip(`\u{1F941} ${meta.drum}${drumPattern ? "" : " (?)"}`);
|
|
6218
|
+
if (meta.volume !== void 0) makeChip(`\u{1F50A} ${meta.volume}%`);
|
|
6219
|
+
head.append(playBtn, beatRow, ...chips, dots, mmlHeader);
|
|
6220
|
+
root.appendChild(head);
|
|
6221
|
+
const body = doc.createElement("div");
|
|
6222
|
+
body.className = "dtm-player-body";
|
|
6223
|
+
root.appendChild(body);
|
|
6224
|
+
const mutedTracks = /* @__PURE__ */ new Set();
|
|
6225
|
+
const laneViews = [];
|
|
6226
|
+
for (const index of trackIndices) {
|
|
6227
|
+
const lyricTrack = lyricTracks.get(index);
|
|
6228
|
+
const isLyricLane = !!lyricTrack && lyricTrack.syllables.length > 0;
|
|
6229
|
+
const row = doc.createElement("div");
|
|
6230
|
+
row.className = "dtm-player-lane-row";
|
|
6231
|
+
const label = doc.createElement("div");
|
|
6232
|
+
label.className = "dtm-player-lane-label dtm-player-lane-label--btn";
|
|
6233
|
+
const swatch = doc.createElement("span");
|
|
6234
|
+
swatch.className = "dtm-player-dot";
|
|
6235
|
+
swatch.style.backgroundColor = colorOf(index);
|
|
6236
|
+
const no = doc.createElement("span");
|
|
6237
|
+
no.className = "dtm-player-lane-no";
|
|
6238
|
+
no.textContent = `@${index}`;
|
|
6239
|
+
label.append(swatch, no);
|
|
6240
|
+
label.addEventListener("click", () => {
|
|
6241
|
+
if (mutedTracks.has(index)) {
|
|
6242
|
+
mutedTracks.delete(index);
|
|
6243
|
+
label.classList.remove("dtm-player-lane-label--muted");
|
|
6244
|
+
} else {
|
|
6245
|
+
mutedTracks.add(index);
|
|
6246
|
+
label.classList.add("dtm-player-lane-label--muted");
|
|
6247
|
+
}
|
|
6248
|
+
});
|
|
6249
|
+
const lane = doc.createElement("div");
|
|
6250
|
+
lane.className = "dtm-player-lane";
|
|
6251
|
+
lane.style.setProperty("--tk", colorOf(index));
|
|
6252
|
+
const laneTokens = [];
|
|
6253
|
+
if (isLyricLane) {
|
|
6254
|
+
const notes = placements.filter((p) => p.trackIndex === index).sort((a, b) => a.startStep - b.startStep);
|
|
6255
|
+
const gateScale = (lyricTrack.gate ?? DEFAULT_GATE) / 100;
|
|
6256
|
+
const breaks = new Set(lyricTrack.lineBreaks ?? []);
|
|
6257
|
+
if (lyricTrack.metaText) {
|
|
6258
|
+
const metaEl = doc.createElement("span");
|
|
6259
|
+
metaEl.className = "dtm-tk dtm-tk--meta";
|
|
6260
|
+
metaEl.textContent = lyricTrack.metaText;
|
|
6261
|
+
lane.appendChild(metaEl);
|
|
6262
|
+
}
|
|
6263
|
+
const count = Math.min(notes.length, lyricTrack.syllables.length);
|
|
6264
|
+
for (let i = 0; i < count; i++) {
|
|
6265
|
+
const note = notes[i];
|
|
6266
|
+
if (breaks.has(i)) {
|
|
6267
|
+
const br = doc.createElement("span");
|
|
6268
|
+
br.className = "dtm-tk dtm-tk--break";
|
|
6269
|
+
br.textContent = "\\n";
|
|
6270
|
+
lane.appendChild(br);
|
|
6271
|
+
}
|
|
6272
|
+
const span = doc.createElement("span");
|
|
6273
|
+
span.className = "dtm-tk dtm-tk--lyric";
|
|
6274
|
+
span.textContent = lyricTrack.syllables[i].kana;
|
|
6275
|
+
lane.appendChild(span);
|
|
6276
|
+
laneTokens.push({
|
|
6277
|
+
el: span,
|
|
6278
|
+
startStep: note.startStep,
|
|
6279
|
+
durationSteps: Math.max(
|
|
6280
|
+
1,
|
|
6281
|
+
Math.round(note.durationSteps * gateScale)
|
|
6282
|
+
)
|
|
6283
|
+
});
|
|
6284
|
+
}
|
|
6285
|
+
} else {
|
|
6286
|
+
const tokens = tokenTracks?.get(index) ?? [];
|
|
6287
|
+
for (const tok of tokens) {
|
|
6288
|
+
const span = doc.createElement("span");
|
|
6289
|
+
span.className = `dtm-tk dtm-tk--${tok.type}`;
|
|
6290
|
+
span.textContent = tok.text;
|
|
6291
|
+
lane.appendChild(span);
|
|
6292
|
+
if (tok.durationSteps > 0) {
|
|
6293
|
+
laneTokens.push({
|
|
6294
|
+
el: span,
|
|
6295
|
+
startStep: tok.startStep,
|
|
6296
|
+
durationSteps: tok.durationSteps
|
|
6297
|
+
});
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6301
|
+
row.append(label, lane);
|
|
6302
|
+
body.appendChild(row);
|
|
6303
|
+
laneViews.push({ lane, tokens: laneTokens });
|
|
6304
|
+
}
|
|
6305
|
+
const termsModels = [
|
|
6306
|
+
...new Set([...lyricTracks.values()].map((lt) => lt.model))
|
|
6307
|
+
].filter((model) => KOE_VOICEBANK_TERMS[model]);
|
|
6308
|
+
if (termsModels.length > 0) {
|
|
6309
|
+
const termsDiv = doc.createElement("div");
|
|
6310
|
+
termsDiv.className = "dtm-player-terms";
|
|
6311
|
+
termsDiv.style.fontSize = "10px";
|
|
6312
|
+
termsDiv.style.color = "var(--dtm-warn)";
|
|
6313
|
+
termsDiv.style.display = "flex";
|
|
6314
|
+
termsDiv.style.flexDirection = "column";
|
|
6315
|
+
termsDiv.style.gap = "4px";
|
|
6316
|
+
termsDiv.style.marginTop = "4px";
|
|
6317
|
+
termsDiv.style.padding = "0 4px";
|
|
6318
|
+
for (const model of termsModels) {
|
|
6319
|
+
const termsRow = doc.createElement("div");
|
|
6320
|
+
termsRow.style.display = "flex";
|
|
6321
|
+
termsRow.style.alignItems = "center";
|
|
6322
|
+
termsRow.style.gap = "4px";
|
|
6323
|
+
termsRow.style.flexWrap = "wrap";
|
|
6324
|
+
const label = KOE_VOICEBANK_LABELS[model] ?? model;
|
|
6325
|
+
const url = KOE_VOICEBANK_TERMS[model];
|
|
6326
|
+
const span1 = doc.createElement("span");
|
|
6327
|
+
span1.textContent = "\u4F7F\u7528\u6642\u306B\u306F";
|
|
6328
|
+
const a = doc.createElement("a");
|
|
6329
|
+
a.textContent = `${label}UTAU\u97F3\u6E90`;
|
|
6330
|
+
a.href = url;
|
|
6331
|
+
a.target = "_blank";
|
|
6332
|
+
a.rel = "noopener";
|
|
6333
|
+
a.style.color = "var(--dtm-primary)";
|
|
6334
|
+
a.style.textDecoration = "underline";
|
|
6335
|
+
const span2 = doc.createElement("span");
|
|
6336
|
+
span2.textContent = "\u306E\u5229\u7528\u898F\u7D04\u306B\u5F93\u3063\u3066\u304F\u3060\u3055\u3044";
|
|
6337
|
+
termsRow.append(span1, a, span2);
|
|
6338
|
+
termsDiv.appendChild(termsRow);
|
|
6339
|
+
}
|
|
6340
|
+
root.appendChild(termsDiv);
|
|
6341
|
+
}
|
|
6342
|
+
target.appendChild(root);
|
|
6343
|
+
const autoScroll = (lane, el) => {
|
|
6344
|
+
if (el.offsetWidth === 0 || lane.clientWidth === 0) return;
|
|
6345
|
+
const elementCenter = el.offsetLeft + el.offsetWidth / 2;
|
|
6346
|
+
const maxScroll = Math.max(0, lane.scrollWidth - lane.clientWidth);
|
|
6347
|
+
const next = elementCenter - lane.clientWidth / 2;
|
|
6348
|
+
lane.scrollLeft = Math.max(0, Math.min(next, maxScroll));
|
|
6349
|
+
};
|
|
6350
|
+
const renderPlayhead = (step) => {
|
|
6351
|
+
const beatIndex = Math.floor(step / STEPS_PER_BEAT3) % 4;
|
|
6352
|
+
for (let i = 0; i < 4; i++)
|
|
6353
|
+
beatDots[i].classList.toggle("dtm-player-beat-dot--on", i === beatIndex);
|
|
6354
|
+
barEl.textContent = String(Math.floor(step / STEPS_PER_BAR) + 1);
|
|
6355
|
+
for (const view of laneViews) {
|
|
6356
|
+
let active = null;
|
|
6357
|
+
for (const t of view.tokens) {
|
|
6358
|
+
const on = step >= t.startStep && step < t.startStep + t.durationSteps;
|
|
6359
|
+
t.el.classList.toggle("is-active", on);
|
|
6360
|
+
if (on && !active) active = t;
|
|
6361
|
+
}
|
|
6362
|
+
if (active) autoScroll(view.lane, active.el);
|
|
6363
|
+
}
|
|
6364
|
+
};
|
|
6365
|
+
const resetPlayhead = () => {
|
|
6366
|
+
for (const d of beatDots) d.classList.remove("dtm-player-beat-dot--on");
|
|
6367
|
+
barEl.textContent = "-";
|
|
6368
|
+
for (const view of laneViews) {
|
|
6369
|
+
for (const t of view.tokens) t.el.classList.remove("is-active");
|
|
6370
|
+
view.lane.scrollLeft = 0;
|
|
6371
|
+
}
|
|
6372
|
+
};
|
|
6373
|
+
const seq = createSequencer({
|
|
6374
|
+
getTracks: () => seqTracks,
|
|
6375
|
+
getBpm: () => bpm,
|
|
6376
|
+
getPlayStartStep: () => 0,
|
|
6377
|
+
getDrumPattern: () => drumPattern,
|
|
6378
|
+
getSoloTrackId: () => null,
|
|
6379
|
+
getAudioTime,
|
|
6380
|
+
onPlayNote: (e) => {
|
|
6381
|
+
const trackIdx = Number(e.trackId);
|
|
6382
|
+
if (mutedTracks.has(trackIdx)) return;
|
|
6383
|
+
const em = emojiByTrack.get(trackIdx);
|
|
6384
|
+
if (em) jumpEmojiAt(em, e.when);
|
|
6385
|
+
if (lyricTracks.has(trackIdx)) return;
|
|
6386
|
+
options.onPlayNote?.(e);
|
|
6387
|
+
if (useSynth) synthPlay(e);
|
|
6388
|
+
},
|
|
6389
|
+
onPlayDrum: (e) => {
|
|
6390
|
+
const em = emojiEls[0];
|
|
6391
|
+
if (em) jumpEmojiAt(em, e.when);
|
|
6392
|
+
const velocity = e.velocity * (trackVolume / 100);
|
|
6393
|
+
options.onPlayDrum?.({ ...e, velocity });
|
|
6394
|
+
if (useSynth) drumSynth({ ...e, velocity });
|
|
6395
|
+
},
|
|
6396
|
+
onTick: (step) => {
|
|
6397
|
+
renderPlayhead(step);
|
|
6398
|
+
},
|
|
6399
|
+
onEnd: () => finish(),
|
|
6400
|
+
stepsPerBar: STEPS_PER_BAR
|
|
6401
|
+
});
|
|
6402
|
+
let playing = false;
|
|
6403
|
+
const setPlayingUI = (on) => {
|
|
6404
|
+
playing = on;
|
|
6405
|
+
playBtn.innerHTML = icon(on ? "stop" : "play", 12);
|
|
6406
|
+
playBtn.classList.toggle("dtm-player-play--stop", on);
|
|
6407
|
+
};
|
|
6408
|
+
const finish = () => {
|
|
6409
|
+
setPlayingUI(false);
|
|
6410
|
+
clearJumpTimers();
|
|
6411
|
+
resetPlayhead();
|
|
6412
|
+
if (activePlayer === instance) activePlayer = null;
|
|
6413
|
+
};
|
|
6414
|
+
const buildStreamTracks = () => [...lyricTracks.entries()].map(([index, lt]) => {
|
|
6415
|
+
const seqTrack = seqTracks.find((t) => Number(t.id) === index);
|
|
6416
|
+
const sorted = [...seqTrack?.notes ?? []].sort(
|
|
6417
|
+
(a, b) => a.startStep - b.startStep
|
|
6418
|
+
);
|
|
6419
|
+
const gate = (lt.gate ?? DEFAULT_GATE) / 100;
|
|
6420
|
+
const semis = (lt.octave ?? 0) * 12;
|
|
6421
|
+
const count = Math.min(sorted.length, lt.syllables.length);
|
|
6422
|
+
const notes = [];
|
|
6423
|
+
for (let i = 0; i < count; i++) {
|
|
6424
|
+
const n = sorted[i];
|
|
6425
|
+
notes.push({
|
|
6426
|
+
syllable: lt.syllables[i],
|
|
6427
|
+
pitch: n.pitch + semis,
|
|
6428
|
+
startSec: n.startStep * secondsPerStep,
|
|
6429
|
+
durationSec: n.durationSteps * secondsPerStep * gate
|
|
6430
|
+
});
|
|
6431
|
+
}
|
|
6432
|
+
return {
|
|
6433
|
+
model: lt.model,
|
|
6434
|
+
volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
|
|
6435
|
+
pan: panToStereo(lt.pan ?? DEFAULT_PAN),
|
|
6436
|
+
notes
|
|
6437
|
+
};
|
|
6438
|
+
});
|
|
6439
|
+
const startWhenReady = async () => {
|
|
6440
|
+
const streaming = voicesAvailable && lyricTracks.size > 0;
|
|
6441
|
+
const tracks = streaming ? buildStreamTracks() : [];
|
|
6442
|
+
if (streaming) {
|
|
6443
|
+
const v = ensureVoices();
|
|
6444
|
+
const overlay = showLoadingOverlay(body);
|
|
6445
|
+
try {
|
|
6446
|
+
await v.loadModels(tracks.map((t) => t.model));
|
|
6447
|
+
await v.warm(tracks);
|
|
6448
|
+
} catch (err) {
|
|
6449
|
+
console.warn("[dtm] voice preload failed", err);
|
|
6450
|
+
} finally {
|
|
6451
|
+
overlay.remove();
|
|
6452
|
+
}
|
|
6453
|
+
if (!playing || activePlayer !== instance) return;
|
|
6454
|
+
}
|
|
6455
|
+
seq.start(0);
|
|
6456
|
+
if (streaming) ensureVoices().startStream(tracks, seq.getStartTime());
|
|
6457
|
+
};
|
|
6458
|
+
const play = () => {
|
|
6459
|
+
if (playing || trackIndices.length === 0) return;
|
|
6460
|
+
if (activePlayer && activePlayer !== instance) activePlayer.stop();
|
|
6461
|
+
activePlayer = instance;
|
|
6462
|
+
setPlayingUI(true);
|
|
6463
|
+
void options.onResumeAudio?.();
|
|
6464
|
+
if (useSynth) {
|
|
6465
|
+
const ctx = ensureCtx();
|
|
6466
|
+
if (ctx.state === "suspended") void ctx.resume();
|
|
6467
|
+
}
|
|
6468
|
+
if (voicesAvailable && lyricTracks.size > 0) ensureVoices().reset();
|
|
6469
|
+
void startWhenReady();
|
|
6470
|
+
};
|
|
6471
|
+
const stop = () => {
|
|
6472
|
+
if (!playing) return;
|
|
6473
|
+
seq.stop();
|
|
6474
|
+
peekVoices()?.stopStream();
|
|
6475
|
+
finish();
|
|
6476
|
+
};
|
|
6477
|
+
playBtn.addEventListener("click", () => {
|
|
6478
|
+
if (playing) stop();
|
|
6479
|
+
else play();
|
|
6480
|
+
});
|
|
6481
|
+
const destroy = () => {
|
|
6482
|
+
seq.stop();
|
|
6483
|
+
peekVoices()?.stopStream();
|
|
6484
|
+
if (activePlayer === instance) activePlayer = null;
|
|
6485
|
+
if (audioCtx) {
|
|
6486
|
+
void audioCtx.close();
|
|
6487
|
+
audioCtx = null;
|
|
6488
|
+
}
|
|
6489
|
+
for (const t of blinkTimers) clearTimeout(t);
|
|
6490
|
+
clearJumpTimers();
|
|
6491
|
+
root.remove();
|
|
6492
|
+
};
|
|
6493
|
+
const instance = {
|
|
6494
|
+
play,
|
|
6495
|
+
stop,
|
|
6496
|
+
isPlaying: () => playing,
|
|
6497
|
+
destroy
|
|
6498
|
+
};
|
|
6499
|
+
return instance;
|
|
6500
|
+
};
|
|
6501
|
+
|
|
4131
6502
|
// src/piano-roll.ts
|
|
4132
6503
|
var createPianoRoll = (options, handlers) => {
|
|
4133
6504
|
const {
|
|
@@ -4412,23 +6783,43 @@ var createPianoRoll = (options, handlers) => {
|
|
|
4412
6783
|
};
|
|
4413
6784
|
export {
|
|
4414
6785
|
DAW_CSS,
|
|
6786
|
+
DEFAULT_BPM,
|
|
6787
|
+
DEFAULT_GATE,
|
|
6788
|
+
DEFAULT_PAN,
|
|
6789
|
+
DEFAULT_PLAYBACK_VELOCITY,
|
|
6790
|
+
DEFAULT_STEPS_PER_BAR,
|
|
6791
|
+
DEFAULT_VELOCITY,
|
|
6792
|
+
DEFAULT_VOCAL_VOLUME,
|
|
4415
6793
|
DRUM_FONT,
|
|
4416
6794
|
DRUM_KEYS,
|
|
4417
6795
|
DRUM_PATTERNS,
|
|
4418
6796
|
INSTRUMENT_PRESETS,
|
|
6797
|
+
KOE_BASE_URL,
|
|
6798
|
+
KOE_VOICEBANKS,
|
|
6799
|
+
KOE_VOICEBANK_LABELS,
|
|
6800
|
+
KOE_VOICEBANK_TERMS,
|
|
4419
6801
|
LinkedList,
|
|
6802
|
+
MAX_VOCAL_VOLUME,
|
|
4420
6803
|
MMLCore,
|
|
4421
6804
|
PITCH_MAP,
|
|
6805
|
+
PREWARM_NOTES,
|
|
4422
6806
|
TRACKS_ADVANCED,
|
|
4423
6807
|
TRACKS_SIMPLE,
|
|
6808
|
+
VOICE_IMAGE_KEY,
|
|
4424
6809
|
analyzeMidiTracks,
|
|
4425
6810
|
applyHarmonicFilter,
|
|
4426
6811
|
applyMonophonic,
|
|
4427
6812
|
buildChordPlacements,
|
|
4428
6813
|
buildNameToKeyMapping,
|
|
6814
|
+
collectPitchTokens,
|
|
4429
6815
|
createAudioContext,
|
|
6816
|
+
createKlattVoice,
|
|
6817
|
+
createKoeVoice,
|
|
6818
|
+
createLyricsConductor,
|
|
4430
6819
|
createPianoRoll,
|
|
4431
6820
|
createSequencer,
|
|
6821
|
+
createSingingVoices,
|
|
6822
|
+
createVoiceRegistry,
|
|
4432
6823
|
decomposeToMonophonic,
|
|
4433
6824
|
drawGrid,
|
|
4434
6825
|
drawHeader,
|
|
@@ -4440,6 +6831,7 @@ export {
|
|
|
4440
6831
|
extractMidiPlacements,
|
|
4441
6832
|
extractMidiPlacementsByTrack,
|
|
4442
6833
|
fetchSoundFontList,
|
|
6834
|
+
formatMmlMeta,
|
|
4443
6835
|
generateRandomPattern,
|
|
4444
6836
|
getDrawOffset,
|
|
4445
6837
|
getGridCanvas,
|
|
@@ -4453,10 +6845,19 @@ export {
|
|
|
4453
6845
|
init,
|
|
4454
6846
|
injectStyles,
|
|
4455
6847
|
isChordHeavyTrack,
|
|
6848
|
+
koeUrl,
|
|
4456
6849
|
mountDAW,
|
|
6850
|
+
mountMmlPlayer,
|
|
6851
|
+
normalizeLyrics,
|
|
4457
6852
|
onClick,
|
|
6853
|
+
panToStereo,
|
|
6854
|
+
parseLyrics,
|
|
4458
6855
|
parseMML,
|
|
6856
|
+
parseMmlMeta,
|
|
4459
6857
|
setDrawOffset,
|
|
4460
6858
|
setupRecorder,
|
|
4461
|
-
shiftNotes
|
|
6859
|
+
shiftNotes,
|
|
6860
|
+
stripLyrics,
|
|
6861
|
+
stripMmlMeta,
|
|
6862
|
+
vocalVolumeToGain
|
|
4462
6863
|
};
|