@onjmin/dtm 2.1.13 → 2.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  DEFAULT_PLAYBACK_VELOCITY: () => DEFAULT_PLAYBACK_VELOCITY,
42
42
  DEFAULT_SECTIONS: () => DEFAULT_SECTIONS,
43
43
  DEFAULT_STEPS_PER_BAR: () => DEFAULT_STEPS_PER_BAR,
44
+ DEFAULT_TTS_BASE_URL: () => DEFAULT_TTS_BASE_URL,
44
45
  DEFAULT_UST_LYRIC: () => DEFAULT_UST_LYRIC,
45
46
  DEFAULT_VELOCITY: () => DEFAULT_VELOCITY,
46
47
  DEFAULT_VOCAL_VOLUME: () => DEFAULT_VOCAL_VOLUME,
@@ -84,6 +85,8 @@ __export(index_exports, {
84
85
  SECTION_ORDER: () => SECTION_ORDER,
85
86
  SECTION_SPECS: () => SECTION_SPECS,
86
87
  SEQUENCER_START_DELAY: () => SEQUENCER_START_DELAY,
88
+ SPEAK_CLOSE: () => SPEAK_CLOSE,
89
+ SPEAK_OPEN: () => SPEAK_OPEN,
87
90
  STOP_MARK: () => STOP_MARK,
88
91
  TIE_MARK: () => TIE_MARK,
89
92
  TIE_MERGE_MAX_SEC: () => TIE_MERGE_MAX_SEC,
@@ -125,6 +128,8 @@ __export(index_exports, {
125
128
  createRenderer: () => createRenderer,
126
129
  createSequencer: () => createSequencer,
127
130
  createSingingVoices: () => createSingingVoices,
131
+ createSpeechPlanner: () => createSpeechPlanner,
132
+ createSpeechToneView: () => createSpeechToneView,
128
133
  createSynth: () => createSynth,
129
134
  createVoiceRegistry: () => createVoiceRegistry,
130
135
  decodeMml: () => decodeMml,
@@ -153,6 +158,7 @@ __export(index_exports, {
153
158
  getComposeScaleDescription: () => getComposeScaleDescription,
154
159
  getDrumPatternKeys: () => getDrumPatternKeys,
155
160
  getMidiBPM: () => getMidiBPM,
161
+ getSpeechPlanner: () => getSpeechPlanner,
156
162
  hasSeenTour: () => hasSeenTour,
157
163
  icon: () => icon,
158
164
  injectStyles: () => injectStyles,
@@ -167,6 +173,7 @@ __export(index_exports, {
167
173
  looksLikePinyin: () => looksLikePinyin,
168
174
  lyricToSyllable: () => lyricToSyllable,
169
175
  markTourSeen: () => markTourSeen,
176
+ medianRecordedPitchHz: () => medianRecordedPitchHz,
170
177
  midiNote: () => midiNote,
171
178
  midiToUnits: () => midiToUnits,
172
179
  mountChordPlayer: () => mountChordPlayer,
@@ -192,10 +199,12 @@ __export(index_exports, {
192
199
  playNote: () => playNote,
193
200
  playPlacements: () => playPlacements,
194
201
  playSingingMML: () => playSingingMML,
202
+ prepareSpeechPlan: () => prepareSpeechPlan,
195
203
  programOfInstrumentName: () => programOfInstrumentName,
196
204
  readGlobalBool: () => readGlobalBool,
197
205
  readGlobalNumber: () => readGlobalNumber,
198
206
  readGlobalSetting: () => readGlobalSetting,
207
+ readKeptSong: () => readKeptSong,
199
208
  readMacroSections: () => readMacroSections,
200
209
  readMacroSetting: () => readMacroSetting,
201
210
  resolveCenter: () => resolveCenter,
@@ -210,6 +219,8 @@ __export(index_exports, {
210
219
  semitonesToUnits: () => semitonesToUnits,
211
220
  shiftNotes: () => shiftNotes,
212
221
  showLoadingOverlay: () => showLoadingOverlay,
222
+ speechPlanDurationSec: () => speechPlanDurationSec,
223
+ speechPlanLeadingSec: () => speechPlanLeadingSec,
213
224
  spelledToUnits: () => spelledToUnits,
214
225
  spellingToUnits: () => spellingToUnits,
215
226
  startTour: () => startTour,
@@ -229,6 +240,7 @@ __export(index_exports, {
229
240
  unitsToPitchV1: () => unitsToPitchV1,
230
241
  vocalVolumeToGain: () => vocalVolumeToGain,
231
242
  writeGlobalSetting: () => writeGlobalSetting,
243
+ writeKeptSong: () => writeKeptSong,
232
244
  writeMacroSections: () => writeMacroSections,
233
245
  writeMacroSetting: () => writeMacroSetting
234
246
  });
@@ -2893,7 +2905,7 @@ var buildChordPlacements = (options) => {
2893
2905
  return placements;
2894
2906
  };
2895
2907
 
2896
- // node_modules/.pnpm/@onjmin+koe@1.0.5/node_modules/@onjmin/koe/dist/index.js
2908
+ // node_modules/.pnpm/@onjmin+koe@1.0.10/node_modules/@onjmin/koe/dist/index.js
2897
2909
  var u8 = Uint8Array;
2898
2910
  var u16 = Uint16Array;
2899
2911
  var i32 = Int32Array;
@@ -3281,6 +3293,130 @@ var Worldline = class _Worldline {
3281
3293
  * @returns Float32 PCM, or null when `pcm` is shorter than
3282
3294
  * {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
3283
3295
  */
3296
+ renderPhrase(params) {
3297
+ const {
3298
+ units: units2,
3299
+ pitch,
3300
+ gender = 0.5,
3301
+ tension = 0.5,
3302
+ breathiness = 0.5,
3303
+ voicing = 1,
3304
+ tempo = 120
3305
+ } = params;
3306
+ if (units2.length === 0) return null;
3307
+ const WL = this.wasm;
3308
+ const FS2 = WORLDLINE_SAMPLE_RATE;
3309
+ let totalMs = 0;
3310
+ for (const u of units2) {
3311
+ const endMs = u.posMs + u.lengthMs;
3312
+ if (endMs > totalMs) totalMs = endMs;
3313
+ }
3314
+ const ps = WL._PhraseSynthNew();
3315
+ if (!ps) return null;
3316
+ const pointersToFree = [];
3317
+ for (const u of units2) {
3318
+ if (!u.pcm || u.pcm.length < MIN_WORLDLINE_SAMPLES) continue;
3319
+ const reqPtr = WL._malloc(SYNTH_REQ_SIZE);
3320
+ if (!reqPtr) continue;
3321
+ pointersToFree.push(reqPtr);
3322
+ const samplePtr = WL._malloc(u.pcm.length * 8);
3323
+ if (!samplePtr) continue;
3324
+ pointersToFree.push(samplePtr);
3325
+ WL.HEAPF64.set(u.pcm, samplePtr >> 3);
3326
+ const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
3327
+ sv(0, FS2, "i32");
3328
+ sv(4, u.pcm.length, "i32");
3329
+ sv(8, samplePtr, "*");
3330
+ sv(12, 0, "i32");
3331
+ sv(16, 0, "*");
3332
+ sv(20, u.tone ?? 69, "i32");
3333
+ sv(24, 100, "double");
3334
+ sv(32, 0, "double");
3335
+ sv(40, u.requiredLengthMs ?? u.lengthMs, "double");
3336
+ sv(48, u.consonantMs, "double");
3337
+ const cutMs = u.cutMs ?? WL_FRAME_MS * 2;
3338
+ sv(56, cutMs, "double");
3339
+ sv(64, u.volume ?? 100, "double");
3340
+ sv(72, 0, "double");
3341
+ sv(80, tempo, "double");
3342
+ sv(88, 0, "i32");
3343
+ sv(92, 0, "*");
3344
+ sv(96, 0, "i32");
3345
+ sv(100, 0, "i32");
3346
+ sv(104, 100, "i32");
3347
+ sv(108, 0, "i32");
3348
+ sv(112, 0, "i32");
3349
+ sv(116, 100, "i32");
3350
+ WL._PhraseSynthAddRequest(
3351
+ ps,
3352
+ reqPtr,
3353
+ u.posMs,
3354
+ u.skipMs,
3355
+ u.lengthMs,
3356
+ u.fadeInMs,
3357
+ u.fadeOutMs,
3358
+ 0
3359
+ );
3360
+ }
3361
+ totalMs += WL_FRAME_MS * 2;
3362
+ const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
3363
+ const f0Arr = new Float64Array(nFrames);
3364
+ const gArr = new Float64Array(nFrames);
3365
+ const tArr = new Float64Array(nFrames);
3366
+ const bArr = new Float64Array(nFrames);
3367
+ const vArr = new Float64Array(nFrames);
3368
+ for (let i2 = 0; i2 < nFrames; i2++) {
3369
+ const tMs = i2 * WL_FRAME_MS;
3370
+ f0Arr[i2] = sampleCurve(tMs, pitch, totalMs);
3371
+ gArr[i2] = sampleCurve(tMs, gender, totalMs);
3372
+ tArr[i2] = sampleCurve(tMs, tension, totalMs);
3373
+ bArr[i2] = sampleCurve(tMs, breathiness, totalMs);
3374
+ vArr[i2] = sampleCurve(tMs, voicing, totalMs);
3375
+ }
3376
+ const f0Ptr = WL._malloc(nFrames * 8);
3377
+ const gPtr = WL._malloc(nFrames * 8);
3378
+ const tPtr = WL._malloc(nFrames * 8);
3379
+ const bPtr = WL._malloc(nFrames * 8);
3380
+ const vPtr = WL._malloc(nFrames * 8);
3381
+ if (f0Ptr && gPtr && tPtr && bPtr && vPtr) {
3382
+ WL.HEAPF64.set(f0Arr, f0Ptr >> 3);
3383
+ WL.HEAPF64.set(gArr, gPtr >> 3);
3384
+ WL.HEAPF64.set(tArr, tPtr >> 3);
3385
+ WL.HEAPF64.set(bArr, bPtr >> 3);
3386
+ WL.HEAPF64.set(vArr, vPtr >> 3);
3387
+ WL._PhraseSynthSetCurves(
3388
+ ps,
3389
+ f0Ptr,
3390
+ gPtr,
3391
+ tPtr,
3392
+ bPtr,
3393
+ vPtr,
3394
+ nFrames,
3395
+ WL_FRAME_MS
3396
+ );
3397
+ }
3398
+ if (f0Ptr) WL._free(f0Ptr);
3399
+ if (gPtr) WL._free(gPtr);
3400
+ if (tPtr) WL._free(tPtr);
3401
+ if (bPtr) WL._free(bPtr);
3402
+ if (vPtr) WL._free(vPtr);
3403
+ const yPtrPtr = WL._malloc(4);
3404
+ let audio = null;
3405
+ if (yPtrPtr) {
3406
+ const outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);
3407
+ const yPtr = WL.getValue(yPtrPtr, "*");
3408
+ if (outLen > 0 && yPtr) {
3409
+ audio = new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice();
3410
+ WL._free(yPtr);
3411
+ }
3412
+ WL._free(yPtrPtr);
3413
+ }
3414
+ for (const ptr of pointersToFree) {
3415
+ WL._free(ptr);
3416
+ }
3417
+ WL._PhraseSynthDelete(ps);
3418
+ return audio;
3419
+ }
3284
3420
  renderNote(params) {
3285
3421
  const {
3286
3422
  pcm,
@@ -3296,8 +3432,12 @@ var Worldline = class _Worldline {
3296
3432
  } = params;
3297
3433
  if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;
3298
3434
  const WL = this.wasm;
3299
- const FS = WORLDLINE_SAMPLE_RATE;
3300
- const basePitch = sampleCurve(preMs + durationMs / 2, pitch, preMs + durationMs);
3435
+ const FS2 = WORLDLINE_SAMPLE_RATE;
3436
+ const basePitch = sampleCurve(
3437
+ preMs + durationMs / 2,
3438
+ pitch,
3439
+ preMs + durationMs
3440
+ );
3301
3441
  const midiNote2 = Math.round(69 + 12 * Math.log2(basePitch / 440));
3302
3442
  const posMs = 0;
3303
3443
  const reqLen = preMs + durationMs;
@@ -3317,7 +3457,7 @@ var Worldline = class _Worldline {
3317
3457
  }
3318
3458
  WL.HEAPF64.set(pcm, samplePtr >> 3);
3319
3459
  const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
3320
- sv(0, FS, "i32");
3460
+ sv(0, FS2, "i32");
3321
3461
  sv(4, pcm.length, "i32");
3322
3462
  sv(8, samplePtr, "*");
3323
3463
  sv(12, 0, "i32");
@@ -3405,6 +3545,836 @@ var Worldline = class _Worldline {
3405
3545
  return audio;
3406
3546
  }
3407
3547
  };
3548
+ var RATE = 16e3;
3549
+ var HOP_MS = 2;
3550
+ var HOP = RATE * HOP_MS / 1e3;
3551
+ var FFT_SIZE = 512;
3552
+ var BINS = FFT_SIZE / 2;
3553
+ var PITCH_DECIM = 2;
3554
+ var PITCH_RATE = RATE / PITCH_DECIM;
3555
+ var WINDOW_CENTRE_MS = FFT_SIZE / 2 / RATE * 1e3;
3556
+ var DEFAULT_CACHE = "koe-tts-assets-v1";
3557
+ async function openCache(name) {
3558
+ if (name === null) return null;
3559
+ try {
3560
+ if (typeof caches === "undefined") return null;
3561
+ return await caches.open(name ?? DEFAULT_CACHE);
3562
+ } catch {
3563
+ return null;
3564
+ }
3565
+ }
3566
+ async function cachedCopyIsCurrent(url2, cached, signal) {
3567
+ let head;
3568
+ try {
3569
+ head = await fetch(url2, { method: "HEAD", signal, cache: "no-cache" });
3570
+ } catch {
3571
+ return true;
3572
+ }
3573
+ if (!head.ok) return true;
3574
+ for (const name of ["etag", "last-modified", "content-length"]) {
3575
+ const remote = head.headers.get(name);
3576
+ const local = cached.headers.get(name);
3577
+ if (remote && local) return remote === local;
3578
+ }
3579
+ return true;
3580
+ }
3581
+ async function fetchAsset(url2, options = {}) {
3582
+ const { onProgress, signal, revalidate = true } = options;
3583
+ const cache2 = await openCache(options.cacheName);
3584
+ if (cache2) {
3585
+ try {
3586
+ const hit = await cache2.match(url2);
3587
+ if (hit && (!revalidate || await cachedCopyIsCurrent(url2, hit, signal))) {
3588
+ const total2 = Number(hit.headers.get("content-length")) || 0;
3589
+ onProgress?.({ url: url2, loaded: total2, total: total2, fromCache: true });
3590
+ return hit;
3591
+ }
3592
+ } catch {
3593
+ }
3594
+ }
3595
+ const response = await fetch(url2, { signal });
3596
+ if (!response.ok) {
3597
+ throw new Error(`fetch ${url2}: HTTP ${response.status}`);
3598
+ }
3599
+ const total = Number(response.headers.get("content-length")) || 0;
3600
+ let body;
3601
+ if (response.body && onProgress) {
3602
+ const reader = response.body.getReader();
3603
+ const parts = [];
3604
+ let loaded = 0;
3605
+ onProgress({ url: url2, loaded, total, fromCache: false });
3606
+ for (; ; ) {
3607
+ const { done, value } = await reader.read();
3608
+ if (done) break;
3609
+ parts.push(value);
3610
+ loaded += value.byteLength;
3611
+ onProgress({ url: url2, loaded, total, fromCache: false });
3612
+ }
3613
+ body = new Uint8Array(new ArrayBuffer(loaded));
3614
+ let offset = 0;
3615
+ for (const part of parts) {
3616
+ body.set(part, offset);
3617
+ offset += part.byteLength;
3618
+ }
3619
+ } else {
3620
+ body = new Uint8Array(await response.arrayBuffer());
3621
+ onProgress?.({
3622
+ url: url2,
3623
+ loaded: body.byteLength,
3624
+ total: total || body.byteLength,
3625
+ fromCache: false
3626
+ });
3627
+ }
3628
+ const headers = new Headers();
3629
+ for (const name of ["content-type", "etag", "last-modified"]) {
3630
+ const value = response.headers.get(name);
3631
+ if (value) headers.set(name, value);
3632
+ }
3633
+ headers.set("content-length", String(body.byteLength));
3634
+ const buffered = new Response(body, { status: 200, headers });
3635
+ if (cache2) {
3636
+ try {
3637
+ await cache2.put(url2, buffered.clone());
3638
+ } catch {
3639
+ }
3640
+ }
3641
+ return buffered;
3642
+ }
3643
+ async function fetchAssetBytes(url2, options) {
3644
+ return new Uint8Array(await (await fetchAsset(url2, options)).arrayBuffer());
3645
+ }
3646
+ async function fetchAssetText(url2, options) {
3647
+ return (await fetchAsset(url2, options)).text();
3648
+ }
3649
+ var NAIST_JDIC_FILES = [
3650
+ "metadata.json",
3651
+ "char_def.bin",
3652
+ "matrix.mtx",
3653
+ "dict.da",
3654
+ "dict.vals",
3655
+ "unk.bin",
3656
+ "dict.wordsidx",
3657
+ "dict.words"
3658
+ ];
3659
+ async function inflate(bytes) {
3660
+ if (typeof DecompressionStream === "undefined") {
3661
+ throw new Error(
3662
+ "DecompressionStream is not available; serve the dictionary uncompressed"
3663
+ );
3664
+ }
3665
+ const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
3666
+ return new Uint8Array(await new Response(stream).arrayBuffer());
3667
+ }
3668
+ async function loadNaistJdic(baseUrl, options = {}) {
3669
+ const { compressed = true, ...fetchOptions } = options;
3670
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3671
+ const entries = await Promise.all(
3672
+ NAIST_JDIC_FILES.map(async (name) => {
3673
+ const raw = await fetchAssetBytes(
3674
+ `${base}${name}${compressed ? ".gz" : ""}`,
3675
+ fetchOptions
3676
+ );
3677
+ return [name, compressed ? await inflate(raw) : raw];
3678
+ })
3679
+ );
3680
+ return Object.fromEntries(entries);
3681
+ }
3682
+ function initJpreprocessDictionary(module2, data) {
3683
+ module2.init_dictionary(
3684
+ data["metadata.json"],
3685
+ data["char_def.bin"],
3686
+ data["matrix.mtx"],
3687
+ data["dict.da"],
3688
+ data["dict.vals"],
3689
+ data["unk.bin"],
3690
+ data["dict.wordsidx"],
3691
+ data["dict.words"]
3692
+ );
3693
+ }
3694
+ var NUCLEUS = /* @__PURE__ */ new Set([
3695
+ "a",
3696
+ "i",
3697
+ "u",
3698
+ "e",
3699
+ "o",
3700
+ "A",
3701
+ "I",
3702
+ "U",
3703
+ "E",
3704
+ "O",
3705
+ "N",
3706
+ "cl"
3707
+ ]);
3708
+ function softLimit(value, knee, max2) {
3709
+ const magnitude = Math.abs(value);
3710
+ const limited = magnitude <= knee ? magnitude : knee + (magnitude - knee) * 0.5;
3711
+ return Math.sign(value) * Math.min(max2, limited);
3712
+ }
3713
+ var DEVOICED = /* @__PURE__ */ new Set(["A", "I", "U", "E", "O"]);
3714
+ function segmentPhonemes(phonemes) {
3715
+ const segments = [];
3716
+ let openStart = null;
3717
+ for (let i2 = 0; i2 < phonemes.length; i2++) {
3718
+ const { phone, start_ms, duration_ms } = phonemes[i2];
3719
+ if (phone === "sil" || phone === "pau") {
3720
+ if (openStart !== null) {
3721
+ segments.push({
3722
+ pause: false,
3723
+ startMs: openStart,
3724
+ durationMs: start_ms - openStart,
3725
+ devoiced: false
3726
+ });
3727
+ openStart = null;
3728
+ }
3729
+ if (i2 === 0) continue;
3730
+ segments.push({
3731
+ pause: true,
3732
+ startMs: start_ms,
3733
+ durationMs: duration_ms,
3734
+ devoiced: false
3735
+ });
3736
+ continue;
3737
+ }
3738
+ if (openStart === null) openStart = start_ms;
3739
+ if (NUCLEUS.has(phone)) {
3740
+ segments.push({
3741
+ pause: false,
3742
+ startMs: openStart,
3743
+ durationMs: start_ms + duration_ms - openStart,
3744
+ devoiced: DEVOICED.has(phone)
3745
+ });
3746
+ openStart = null;
3747
+ }
3748
+ }
3749
+ if (openStart !== null) {
3750
+ const last = phonemes[phonemes.length - 1];
3751
+ segments.push({
3752
+ pause: false,
3753
+ startMs: openStart,
3754
+ durationMs: last.start_ms + last.duration_ms - openStart,
3755
+ devoiced: false
3756
+ });
3757
+ }
3758
+ return segments;
3759
+ }
3760
+ function alignHtsProsody(frames, features, options = {}) {
3761
+ const {
3762
+ intonationStrength = 1,
3763
+ frameMs = 10,
3764
+ fallbackPauseMs = 150,
3765
+ kneeCents = 400,
3766
+ maxCents = 700
3767
+ } = options;
3768
+ const segments = segmentPhonemes(frames.phonemes);
3769
+ const durations = new Array(features.length).fill(0);
3770
+ const htsStart = new Array(features.length).fill(null);
3771
+ const devoiced = new Array(features.length).fill(false);
3772
+ let s = 0;
3773
+ for (let f2 = 0; f2 < features.length; f2++) {
3774
+ const feature = features[f2];
3775
+ while (s < segments.length && segments[s].pause && !feature.pause) s++;
3776
+ const segment = segments[s];
3777
+ if (feature.pause) {
3778
+ if (segment?.pause) {
3779
+ durations[f2] = segment.durationMs;
3780
+ htsStart[f2] = segment.startMs;
3781
+ s++;
3782
+ } else {
3783
+ durations[f2] = fallbackPauseMs;
3784
+ }
3785
+ continue;
3786
+ }
3787
+ if (!segment) return null;
3788
+ durations[f2] = segment.durationMs;
3789
+ htsStart[f2] = segment.startMs;
3790
+ devoiced[f2] = segment.devoiced;
3791
+ s++;
3792
+ }
3793
+ if (segments.slice(s).some((segment) => !segment.pause)) return null;
3794
+ const f0 = frames.f0_hz;
3795
+ const logF0 = new Float64Array(f0.length);
3796
+ const voiced = [];
3797
+ for (let i2 = 0; i2 < f0.length; i2++) if (f0[i2] > 0) voiced.push(i2);
3798
+ if (voiced.length === 0) return null;
3799
+ const sorted = voiced.map((i2) => f0[i2]).sort((a, b) => a - b);
3800
+ const medianHz = sorted[Math.floor(sorted.length / 2)];
3801
+ for (let i2 = 0, v = 0; i2 < f0.length; i2++) {
3802
+ if (f0[i2] > 0) {
3803
+ logF0[i2] = Math.log2(f0[i2]);
3804
+ continue;
3805
+ }
3806
+ while (v < voiced.length && voiced[v] < i2) v++;
3807
+ const right = voiced[v];
3808
+ const left = v > 0 ? voiced[v - 1] : void 0;
3809
+ if (left === void 0) logF0[i2] = Math.log2(f0[right]);
3810
+ else if (right === void 0) logF0[i2] = Math.log2(f0[left]);
3811
+ else {
3812
+ const t = (i2 - left) / (right - left);
3813
+ logF0[i2] = Math.log2(f0[left]) * (1 - t) + Math.log2(f0[right]) * t;
3814
+ }
3815
+ }
3816
+ const logMedian = Math.log2(medianHz);
3817
+ const htsCentsAt = (tMs) => {
3818
+ const position = Math.max(0, tMs) / frames.frame_ms;
3819
+ const left = Math.min(logF0.length - 1, Math.floor(position));
3820
+ const right = Math.min(logF0.length - 1, left + 1);
3821
+ const t = position - Math.floor(position);
3822
+ return (logF0[left] * (1 - t) + logF0[right] * t - logMedian) * 1200;
3823
+ };
3824
+ const planStart = [];
3825
+ let cursor = 0;
3826
+ for (let f2 = 0; f2 < features.length; f2++) {
3827
+ planStart.push(cursor);
3828
+ cursor += durations[f2];
3829
+ }
3830
+ const durationMs = cursor;
3831
+ const count = Math.max(2, Math.ceil(durationMs / frameMs) + 2);
3832
+ const cents = new Array(count);
3833
+ let f = 0;
3834
+ for (let frame = 0; frame < count; frame++) {
3835
+ const tMs = frame * frameMs;
3836
+ while (f + 1 < features.length && tMs >= planStart[f + 1]) f++;
3837
+ const start = htsStart[f];
3838
+ let htsMs;
3839
+ if (start !== null) htsMs = start + (tMs - planStart[f]);
3840
+ else {
3841
+ const next = htsStart.slice(f + 1).find((value) => value !== null);
3842
+ htsMs = next ?? frames.phonemes.at(-1)?.start_ms ?? 0;
3843
+ }
3844
+ cents[frame] = softLimit(
3845
+ htsCentsAt(htsMs) * intonationStrength,
3846
+ kneeCents,
3847
+ maxCents
3848
+ );
3849
+ }
3850
+ return {
3851
+ moraDurationsMs: durations,
3852
+ pitchCurve: { frame_ms: frameMs, cents },
3853
+ medianHz,
3854
+ durationMs,
3855
+ devoiced
3856
+ };
3857
+ }
3858
+ var PUNCTUATION = /* @__PURE__ */ new Set(["\u3001", "\u3002", "\uFF1F", "\uFF01", ",", ".", "?", "!"]);
3859
+ var SMALL_KANA = /* @__PURE__ */ new Set(["\u3041", "\u3043", "\u3045", "\u3047", "\u3049", "\u3083", "\u3085", "\u3087", "\u308E", "\u3095", "\u3096"]);
3860
+ function toHiragana2(character) {
3861
+ const code = character.charCodeAt(0);
3862
+ if (12449 <= code && code <= 12534) {
3863
+ return String.fromCharCode(code - 96);
3864
+ }
3865
+ return character;
3866
+ }
3867
+ function splitMorae(reading) {
3868
+ const result = [];
3869
+ const normalized = reading.normalize("NFC").replace(/'/g, "").replace(/’/g, "");
3870
+ for (const character of normalized) {
3871
+ if (/\s/.test(character) || PUNCTUATION.has(character)) {
3872
+ if (result.length > 0 && !result[result.length - 1].pause) {
3873
+ result.push({ mora: "", pause: true });
3874
+ }
3875
+ continue;
3876
+ }
3877
+ const mora = toHiragana2(character);
3878
+ if (SMALL_KANA.has(mora) && result.length > 0 && !result[result.length - 1].pause) {
3879
+ result[result.length - 1].mora += mora;
3880
+ } else {
3881
+ result.push({ mora, pause: false });
3882
+ }
3883
+ }
3884
+ return result;
3885
+ }
3886
+ function isHigh(position, accent) {
3887
+ if (accent === 1) return position === 1;
3888
+ if (accent > 1) return 2 <= position && position <= accent;
3889
+ return position >= 2;
3890
+ }
3891
+ function analyze2(nodes) {
3892
+ const reading_parts = [];
3893
+ const result = [];
3894
+ let index = 0;
3895
+ while (index < nodes.length) {
3896
+ const node = nodes[index];
3897
+ const pronunciation = (node.pron || "").replace(/'/g, "").replace(/’/g, "");
3898
+ if (node.mora_size === 0 || PUNCTUATION.has(node.string)) {
3899
+ reading_parts.push(node.string || "\u3001");
3900
+ if (result.length > 0 && !result[result.length - 1].pause) {
3901
+ result.push({ mora: "", pause: true });
3902
+ }
3903
+ index++;
3904
+ continue;
3905
+ }
3906
+ const phrase_nodes = [];
3907
+ while (index < nodes.length) {
3908
+ const current = nodes[index];
3909
+ if (current.mora_size === 0 || PUNCTUATION.has(current.string)) {
3910
+ break;
3911
+ }
3912
+ if (phrase_nodes.length > 0 && current.chain_flag !== 1) {
3913
+ break;
3914
+ }
3915
+ const current_pronunciation = (current.pron || "").replace(/'/g, "").replace(/’/g, "");
3916
+ const morae = splitMorae(current_pronunciation).filter((item) => !item.pause);
3917
+ phrase_nodes.push({ current, morae });
3918
+ reading_parts.push(current_pronunciation);
3919
+ index++;
3920
+ }
3921
+ const phrase_length = phrase_nodes.reduce((sum, item) => sum + item.morae.length, 0);
3922
+ const accent = phrase_nodes[0].current.acc || 0;
3923
+ let phrase_position = 0;
3924
+ for (const { current, morae } of phrase_nodes) {
3925
+ for (let word_position = 1; word_position <= morae.length; word_position++) {
3926
+ phrase_position++;
3927
+ result.push({
3928
+ mora: morae[word_position - 1].mora,
3929
+ pause: false,
3930
+ accent_phrase_position: phrase_position,
3931
+ accent_phrase_length: phrase_length,
3932
+ accent_nucleus: accent,
3933
+ accent_high: isHigh(phrase_position, accent),
3934
+ accent_phrase_start: phrase_position === 1,
3935
+ accent_phrase_end: phrase_position === phrase_length,
3936
+ word_start: word_position === 1,
3937
+ word_end: word_position === morae.length,
3938
+ pos: current.pos || "*",
3939
+ pos_group1: current.pos_group1 || "*"
3940
+ });
3941
+ }
3942
+ }
3943
+ }
3944
+ return { reading: reading_parts.join(""), features: result };
3945
+ }
3946
+ function sparse_features(token) {
3947
+ if (token.pause || token.accent_phrase_position === void 0 || token.accent_phrase_length === void 0 || token.accent_nucleus === void 0) {
3948
+ return {};
3949
+ }
3950
+ const phrase_length = Math.max(1, token.accent_phrase_length);
3951
+ const phrase_position = token.accent_phrase_position;
3952
+ const nucleus = token.accent_nucleus;
3953
+ const result = {
3954
+ "accent_position": phrase_position / phrase_length,
3955
+ "accent_from_end": (phrase_length - phrase_position) / phrase_length,
3956
+ "accent_nucleus_position": nucleus / phrase_length,
3957
+ "accent_high": token.accent_high ? 1 : 0,
3958
+ "accent_phrase_start": token.accent_phrase_start ? 1 : 0,
3959
+ "accent_phrase_end": token.accent_phrase_end ? 1 : 0,
3960
+ "word_start": token.word_start ? 1 : 0,
3961
+ "word_end": token.word_end ? 1 : 0
3962
+ };
3963
+ result[`pos=${token.pos || "*"}`] = 1;
3964
+ result[`pos_group1=${token.pos_group1 || "*"}`] = 1;
3965
+ if (nucleus === 0) {
3966
+ result["accent_type=heiban"] = 1;
3967
+ } else if (phrase_position < nucleus) {
3968
+ result["accent_type=before"] = 1;
3969
+ } else if (phrase_position === nucleus) {
3970
+ result["accent_type=nucleus"] = 1;
3971
+ } else {
3972
+ result["accent_type=after"] = 1;
3973
+ }
3974
+ return result;
3975
+ }
3976
+ var TRAILING = /[\s」』))〕】>>"'”’]+$/u;
3977
+ function isQuestion(text) {
3978
+ return /[??]$/u.test(text.replace(TRAILING, ""));
3979
+ }
3980
+ function smoothstep(t) {
3981
+ const x2 = Math.min(1, Math.max(0, t));
3982
+ return x2 * x2 * (3 - 2 * x2);
3983
+ }
3984
+ function shapeProsody(prosody, features, options = {}) {
3985
+ const {
3986
+ question = false,
3987
+ questionRiseCents = 350,
3988
+ energyDbPerSemitone = 0.5,
3989
+ devoicedGain = 0.5,
3990
+ minGain = 0.35,
3991
+ maxGain = 1.8
3992
+ } = options;
3993
+ const { frame_ms: frameMs } = prosody.pitchCurve;
3994
+ const cents = prosody.pitchCurve.cents.slice();
3995
+ const count = Math.min(features.length, prosody.moraDurationsMs.length);
3996
+ const startFrame = [];
3997
+ const endFrame = [];
3998
+ let cursor = 0;
3999
+ for (let f = 0; f < count; f++) {
4000
+ startFrame.push(cursor / frameMs);
4001
+ cursor += prosody.moraDurationsMs[f];
4002
+ endFrame.push(cursor / frameMs);
4003
+ }
4004
+ const frameRange = (f) => [
4005
+ Math.max(0, Math.min(cents.length, Math.round(startFrame[f]))),
4006
+ Math.max(0, Math.min(cents.length, Math.round(endFrame[f])))
4007
+ ];
4008
+ if (question && questionRiseCents > 0) {
4009
+ let last = -1;
4010
+ for (let f = count - 1; f >= 0; f--) {
4011
+ if (!features[f].pause) {
4012
+ last = f;
4013
+ break;
4014
+ }
4015
+ }
4016
+ if (last >= 0) {
4017
+ const [from, to] = frameRange(last);
4018
+ if (to > from) {
4019
+ const existing = cents[to - 1] - cents[from];
4020
+ const shortfall = questionRiseCents - existing;
4021
+ if (shortfall > 0) {
4022
+ for (let frame = from; frame < cents.length; frame++) {
4023
+ const t = (frame - from) / (to - from);
4024
+ cents[frame] += shortfall * smoothstep((t - 0.25) / 0.75);
4025
+ }
4026
+ }
4027
+ }
4028
+ }
4029
+ }
4030
+ const moraGains = new Array(features.length).fill(1);
4031
+ for (let f = 0; f < count; f++) {
4032
+ if (features[f].pause) continue;
4033
+ let gainDb = 0;
4034
+ if (energyDbPerSemitone !== 0) {
4035
+ const [from, to] = frameRange(f);
4036
+ if (to > from) {
4037
+ let sum = 0;
4038
+ for (let frame = from; frame < to; frame++) sum += cents[frame];
4039
+ gainDb += sum / (to - from) / 100 * energyDbPerSemitone;
4040
+ }
4041
+ }
4042
+ let gain = 10 ** (gainDb / 20);
4043
+ if (prosody.devoiced[f]) gain *= devoicedGain;
4044
+ moraGains[f] = Math.min(maxGain, Math.max(minGain, gain));
4045
+ }
4046
+ return {
4047
+ ...prosody,
4048
+ pitchCurve: { frame_ms: frameMs, cents },
4049
+ moraGains
4050
+ };
4051
+ }
4052
+ function readingFromFeatures(features) {
4053
+ return features.map((frame) => frame.pause ? "\u3001" : frame.mora).join("");
4054
+ }
4055
+ var FS = WORLDLINE_SAMPLE_RATE;
4056
+ var msToSamples2 = (ms) => Math.round(ms / 1e3 * FS);
4057
+ function f0At(timeline, tMs) {
4058
+ const curve = timeline.f0_curve;
4059
+ if (curve.length === 0) return timeline.reference_hz || 220;
4060
+ const position = Math.max(0, tMs) / timeline.frame_ms;
4061
+ const left = Math.floor(position);
4062
+ if (left >= curve.length - 1) return curve[curve.length - 1];
4063
+ const progress = position - left;
4064
+ return curve[left] * (1 - progress) + curve[left + 1] * progress;
4065
+ }
4066
+ function applyMoraGains(plan, gains) {
4067
+ const morae = plan.morae ?? [];
4068
+ const timings = plan.mora_timings ?? [];
4069
+ const byIndex = morae.length === gains.length;
4070
+ const moraAt = (unit) => {
4071
+ if (byIndex) return unit.position;
4072
+ let found = -1;
4073
+ for (let i2 = 0; i2 < timings.length && i2 < gains.length; i2++) {
4074
+ if (timings[i2].StartMS <= unit.note_start_ms + 1e-6) found = i2;
4075
+ else break;
4076
+ }
4077
+ return found;
4078
+ };
4079
+ for (const unit of plan.timeline.units) {
4080
+ const index = moraAt(unit);
4081
+ const gain = gains[index];
4082
+ if (index < 0 || gain === void 0 || !Number.isFinite(gain)) continue;
4083
+ unit.volume *= gain;
4084
+ }
4085
+ }
4086
+ function planChunks(units2, firstChunkUnits, chunkUnits) {
4087
+ const ranges = [];
4088
+ if (units2.length === 0) return ranges;
4089
+ let start = 0;
4090
+ let coveredEndMs = units2[0].position_ms + units2[0].length_ms;
4091
+ let headSeam = false;
4092
+ for (let i2 = 1; i2 <= units2.length; i2++) {
4093
+ const limit = ranges.length === 0 ? firstChunkUnits : chunkUnits;
4094
+ const atEnd = i2 === units2.length;
4095
+ const clean = !atEnd && units2[i2].position_ms >= coveredEndMs - 1e-6;
4096
+ const full = !atEnd && i2 - start >= limit;
4097
+ if (atEnd || clean || full) {
4098
+ const tailSeam = !atEnd && !clean;
4099
+ ranges.push({ start, end: i2, headSeam, tailSeam });
4100
+ start = i2;
4101
+ headSeam = tailSeam;
4102
+ if (!atEnd) coveredEndMs = units2[i2].position_ms + units2[i2].length_ms;
4103
+ continue;
4104
+ }
4105
+ coveredEndMs = Math.max(
4106
+ coveredEndMs,
4107
+ units2[i2].position_ms + units2[i2].length_ms
4108
+ );
4109
+ }
4110
+ return ranges;
4111
+ }
4112
+ function seamTimeMs(next, crossfadeMs) {
4113
+ const earliest = next.position_ms + next.fade_in_ms + crossfadeMs / 2;
4114
+ const latest = next.position_ms + next.length_ms - crossfadeMs / 2;
4115
+ return Math.min(
4116
+ earliest,
4117
+ Math.max(next.position_ms + crossfadeMs / 2, latest)
4118
+ );
4119
+ }
4120
+ function applyFade(pcm, fromSample, samples, fadeIn) {
4121
+ const n = Math.max(1, Math.min(samples, pcm.length - fromSample));
4122
+ for (let k = 0; k < n; k++) {
4123
+ const t = (k + 0.5) / n;
4124
+ const gain = fadeIn ? Math.sin(Math.PI / 2 * t) : Math.cos(Math.PI / 2 * t);
4125
+ pcm[fromSample + k] *= gain;
4126
+ }
4127
+ }
4128
+ var UtauTTSAdapter = class _UtauTTSAdapter {
4129
+ constructor(worldline) {
4130
+ this.worldline = worldline;
4131
+ }
4132
+ worldline;
4133
+ static modelId = null;
4134
+ bankAliases = /* @__PURE__ */ new WeakMap();
4135
+ currentBank = null;
4136
+ pcmCache = /* @__PURE__ */ new Map();
4137
+ /**
4138
+ * Load the UtauTTS Go wasm. `wasm_exec.js` must already be on the page.
4139
+ * Pass `fetch` (e.g. koe's `fetchAsset`) to stream from the Cache API.
4140
+ */
4141
+ static async initializeWasm(wasmUrl = "utautts.wasm", options = {}) {
4142
+ if (typeof utautts_plan === "function") return;
4143
+ if (typeof Go === "undefined") {
4144
+ throw new Error(
4145
+ "wasm_exec.js must be loaded before calling initializeWasm."
4146
+ );
4147
+ }
4148
+ const go = new Go();
4149
+ const responsePromise = (options.fetch ?? fetch)(wasmUrl);
4150
+ let instance;
4151
+ try {
4152
+ instance = (await WebAssembly.instantiateStreaming(responsePromise, go.importObject)).instance;
4153
+ } catch {
4154
+ const bytes = await (await (options.fetch ?? fetch)(wasmUrl)).arrayBuffer();
4155
+ instance = (await WebAssembly.instantiate(bytes, go.importObject)).instance;
4156
+ }
4157
+ void go.run(instance);
4158
+ for (let attempt = 0; attempt < 100 && typeof utautts_plan !== "function"; attempt++) {
4159
+ await new Promise((resolve) => setTimeout(resolve, 10));
4160
+ }
4161
+ if (typeof utautts_plan !== "function") {
4162
+ throw new Error("utautts_plan failed to initialize in global scope.");
4163
+ }
4164
+ }
4165
+ static get ready() {
4166
+ return typeof utautts_plan === "function";
4167
+ }
4168
+ /** Parse and cache a prosody model (e.g. `frame-intonation-v8.json`) inside the wasm. */
4169
+ static setModel(modelJSON) {
4170
+ _UtauTTSAdapter.assertReady();
4171
+ const result = utautts_set_model(modelJSON);
4172
+ if (!result.success)
4173
+ throw new Error(`UtauTTS model error: ${result.error}`);
4174
+ _UtauTTSAdapter.modelId = result.id ?? null;
4175
+ return _UtauTTSAdapter.modelId;
4176
+ }
4177
+ static get currentModelId() {
4178
+ return _UtauTTSAdapter.modelId;
4179
+ }
4180
+ static assertReady() {
4181
+ if (typeof utautts_plan !== "function") {
4182
+ throw new Error(
4183
+ "UtauTTS wasm is not initialized; call UtauTTSAdapter.initializeWasm() first."
4184
+ );
4185
+ }
4186
+ }
4187
+ /**
4188
+ * Register a koe voice bank with the planner: the manifest becomes a virtual
4189
+ * oto.ini (koe PCM is pre-trimmed, so offset is 0) plus each sample's
4190
+ * recorded pitch. Called automatically by {@link plan}; cheap when unchanged.
4191
+ */
4192
+ setBank(bank) {
4193
+ _UtauTTSAdapter.assertReady();
4194
+ if (this.currentBank === bank && this.bankAliases.has(bank)) return;
4195
+ const entries = {};
4196
+ const pitch = {};
4197
+ for (const [alias, phoneme] of Object.entries(bank.manifest.phonemes)) {
4198
+ entries[alias] = [
4199
+ {
4200
+ Filename: `koe:${alias}`,
4201
+ Alias: alias,
4202
+ Offset: 0,
4203
+ Fixed: phoneme.consonant / 48,
4204
+ Blank: 0,
4205
+ Preutterance: phoneme.pre / 48,
4206
+ Overlap: phoneme.overlap / 48,
4207
+ SourceGroup: "koe"
4208
+ }
4209
+ ];
4210
+ if (phoneme.pitch > 0) pitch[alias] = phoneme.pitch;
4211
+ }
4212
+ const result = utautts_set_bank(
4213
+ JSON.stringify({
4214
+ name: "koe",
4215
+ oto_entries: entries,
4216
+ source_pitch_hz: pitch
4217
+ })
4218
+ );
4219
+ if (!result.success) throw new Error(`UtauTTS bank error: ${result.error}`);
4220
+ this.bankAliases.set(bank, result.aliases ?? 0);
4221
+ this.currentBank = bank;
4222
+ this.pcmCache.clear();
4223
+ }
4224
+ /**
4225
+ * Plan an utterance: unit selection, timing, pitch contour and worldline
4226
+ * placement. `features` are the mora-level frames from `openjtalkAnalyze`
4227
+ * (pauses included); the kana reading is derived from them.
4228
+ */
4229
+ plan(bank, text, features, options = {}) {
4230
+ this.setBank(bank);
4231
+ const request = {
4232
+ text,
4233
+ reading: readingFromFeatures(features),
4234
+ frames: features.map((frame) => sparse_features(frame)),
4235
+ tone: options.tone ?? "C4",
4236
+ mora_duration_ms: options.moraDurationMs ?? 0,
4237
+ pause_duration_ms: options.pauseDurationMs ?? 0,
4238
+ release_ms: options.releaseMs ?? 20,
4239
+ leading_preutterance_ms: options.leadingPreutteranceMs ?? 0,
4240
+ apply_pitch: options.applyPitch ?? true,
4241
+ intonation_strength: options.intonationStrength ?? 1,
4242
+ speech_timing: options.speechTiming ?? false,
4243
+ word_boundary_envelope: options.wordBoundaryEnvelope ?? false,
4244
+ mora_durations_ms: options.prosody?.moraDurationsMs,
4245
+ pitch_curve: options.prosody?.pitchCurve
4246
+ };
4247
+ const response = utautts_plan(JSON.stringify(request));
4248
+ if (!response.success || !response.plan) {
4249
+ throw new Error(`UtauTTS error: ${response.error ?? "no plan"}`);
4250
+ }
4251
+ const plan = JSON.parse(response.plan);
4252
+ const gains = options.prosody?.moraGains;
4253
+ if (gains) applyMoraGains(plan, gains);
4254
+ return plan;
4255
+ }
4256
+ getPcm(bank, alias) {
4257
+ let cached = this.pcmCache.get(alias);
4258
+ if (!cached) {
4259
+ cached = bank.getPcm(alias);
4260
+ this.pcmCache.set(alias, cached);
4261
+ }
4262
+ return cached;
4263
+ }
4264
+ /**
4265
+ * Render a plan chunk by chunk. Each chunk is independent audio positioned
4266
+ * at `startMs`; schedule them as they arrive (see the demo) or sum them.
4267
+ *
4268
+ * Chunk breaks fall on pauses when possible. Inside a phrase a break renders
4269
+ * one neighbouring unit of context on each side so the unit crossfade stays
4270
+ * WORLD's spectral one, then the two renders are joined with a short
4271
+ * equal-power crossfade in the following vowel.
4272
+ */
4273
+ async *renderChunks(bank, plan, options = {}) {
4274
+ const {
4275
+ firstChunkUnits = 3,
4276
+ chunkUnits = 6,
4277
+ seamCrossfadeMs = 20,
4278
+ signal
4279
+ } = options;
4280
+ const timeline = plan.timeline;
4281
+ const units2 = timeline.units.filter((unit) => unit.length_ms > 0);
4282
+ const ranges = planChunks(
4283
+ units2,
4284
+ Math.max(1, firstChunkUnits),
4285
+ Math.max(1, chunkUnits)
4286
+ );
4287
+ const crossfadeSamples = Math.max(2, msToSamples2(seamCrossfadeMs));
4288
+ for (let index = 0; index < ranges.length; index++) {
4289
+ if (signal?.aborted) return;
4290
+ const range = ranges[index];
4291
+ const renderStart = range.headSeam ? range.start - 1 : range.start;
4292
+ const renderEnd = range.tailSeam ? range.end + 1 : range.end;
4293
+ const rendered = units2.slice(renderStart, renderEnd);
4294
+ const baseMs = Math.min(...rendered.map((unit) => unit.position_ms));
4295
+ const phraseUnits = [];
4296
+ for (const unit of rendered) {
4297
+ const pcm2 = await this.getPcm(bank, unit.alias);
4298
+ if (!pcm2 || pcm2.length < MIN_WORLDLINE_SAMPLES) {
4299
+ console.warn(
4300
+ `[utautts] no usable PCM for alias "${unit.alias}"; skipped`
4301
+ );
4302
+ continue;
4303
+ }
4304
+ phraseUnits.push({
4305
+ pcm: pcm2,
4306
+ posMs: unit.position_ms - baseMs,
4307
+ skipMs: unit.skip_ms,
4308
+ lengthMs: unit.length_ms,
4309
+ fadeInMs: unit.fade_in_ms,
4310
+ fadeOutMs: unit.fade_out_ms,
4311
+ consonantMs: unit.consonant_ms,
4312
+ requiredLengthMs: unit.required_length_ms,
4313
+ volume: unit.volume,
4314
+ tone: unit.tone
4315
+ });
4316
+ }
4317
+ if (signal?.aborted) return;
4318
+ if (phraseUnits.length === 0) continue;
4319
+ const audio = this.worldline.renderPhrase({
4320
+ units: phraseUnits,
4321
+ pitch: (tMs) => f0At(timeline, baseMs + tMs),
4322
+ gender: options.gender,
4323
+ tension: options.tension,
4324
+ breathiness: options.breathiness,
4325
+ voicing: options.voicing
4326
+ });
4327
+ if (!audio || audio.length === 0) continue;
4328
+ let fromSample = 0;
4329
+ let toSample = audio.length;
4330
+ let startMs = baseMs;
4331
+ if (range.headSeam) {
4332
+ const seamMs = seamTimeMs(units2[range.start], seamCrossfadeMs);
4333
+ fromSample = Math.max(
4334
+ 0,
4335
+ msToSamples2(seamMs - baseMs) - crossfadeSamples / 2
4336
+ );
4337
+ startMs = baseMs + fromSample / FS * 1e3;
4338
+ }
4339
+ if (range.tailSeam) {
4340
+ const seamMs = seamTimeMs(units2[range.end], seamCrossfadeMs);
4341
+ toSample = Math.min(
4342
+ audio.length,
4343
+ msToSamples2(seamMs - baseMs) + crossfadeSamples / 2
4344
+ );
4345
+ }
4346
+ if (toSample <= fromSample) continue;
4347
+ const pcm = audio.slice(fromSample, toSample);
4348
+ if (range.headSeam) applyFade(pcm, 0, crossfadeSamples, true);
4349
+ if (range.tailSeam)
4350
+ applyFade(
4351
+ pcm,
4352
+ Math.max(0, pcm.length - crossfadeSamples),
4353
+ crossfadeSamples,
4354
+ false
4355
+ );
4356
+ yield { pcm, startMs, index, units: units2.slice(range.start, range.end) };
4357
+ await new Promise((resolve) => setTimeout(resolve, 0));
4358
+ }
4359
+ }
4360
+ /**
4361
+ * Plan + render an utterance into one buffer (Float32, 48 kHz) covering the
4362
+ * whole timeline. Use {@link plan} + {@link renderChunks} for streaming.
4363
+ */
4364
+ async synthesizeText(bank, text, features, options = {}) {
4365
+ const plan = this.plan(bank, text, features, options);
4366
+ const total = msToSamples2(plan.timeline.duration_ms) + msToSamples2(200);
4367
+ const out = new Float32Array(total);
4368
+ let any = false;
4369
+ for await (const chunk of this.renderChunks(bank, plan, options)) {
4370
+ any = true;
4371
+ const offset = msToSamples2(chunk.startMs);
4372
+ const n = Math.min(chunk.pcm.length, out.length - offset);
4373
+ for (let k = 0; k < n; k++) out[offset + k] += chunk.pcm[k];
4374
+ }
4375
+ return any ? out : null;
4376
+ }
4377
+ };
3408
4378
 
3409
4379
  // src/vibrato.ts
3410
4380
  var VIBRATO_MIN_SEC = 0.35;
@@ -3426,7 +4396,7 @@ var MAX_PORTAMENTO_MS = 400;
3426
4396
  var MIN_GLIDE_MS = 4;
3427
4397
  var MAX_GLIDE_SPAN_RATIO = 0.9;
3428
4398
  var unitsToHz2 = (units2) => 440 * 2 ** ((units2 - 2139) / 372);
3429
- var smoothstep = (u) => u * u * (3 - 2 * u);
4399
+ var smoothstep2 = (u) => u * u * (3 - 2 * u);
3430
4400
  var glideMsForSegments = (segments, totalMs) => segments.map((seg, i2) => {
3431
4401
  const atMs = seg.atSec * 1e3;
3432
4402
  const prevMs = i2 === 0 ? 0 : segments[i2 - 1].atSec * 1e3;
@@ -3460,7 +4430,7 @@ var pitchCurveFor = (baseHz, segments, preMs, vibrato, totalMs) => {
3460
4430
  continue;
3461
4431
  }
3462
4432
  if (sinceOnset <= k.startMs) break;
3463
- const u = smoothstep((sinceOnset - k.startMs) / k.glideMs);
4433
+ const u = smoothstep2((sinceOnset - k.startMs) / k.glideMs);
3464
4434
  hz = hz * (k.hz / hz) ** u;
3465
4435
  break;
3466
4436
  }
@@ -3476,6 +4446,177 @@ var segmentsCacheKey = (segments) => segments?.length ? `|s${segments.map(
3476
4446
  (s) => `${s.pitch}@${Math.round(s.atSec * 100)}${s.portamento ? "~" : ""}`
3477
4447
  ).join(",")}` : "";
3478
4448
 
4449
+ // src/speech.ts
4450
+ var DEFAULT_TTS_BASE_URL = "https://onjmin.github.io/koe/demo/utautts/";
4451
+ var joinUrl = (base, path) => `${base.endsWith("/") ? base : `${base}/`}${path}`;
4452
+ var loadGoRuntime = (url2) => new Promise((resolve, reject) => {
4453
+ if (typeof globalThis.Go !== "undefined") {
4454
+ resolve();
4455
+ return;
4456
+ }
4457
+ if (typeof document === "undefined") {
4458
+ reject(new Error("wasm_exec.js needs a document to load into"));
4459
+ return;
4460
+ }
4461
+ const script = document.createElement("script");
4462
+ script.src = url2;
4463
+ script.async = true;
4464
+ script.onload = () => resolve();
4465
+ script.onerror = () => reject(new Error(`failed to load ${url2}`));
4466
+ document.head.appendChild(script);
4467
+ });
4468
+ var importJpreprocess = (url2) => import(
4469
+ /* @vite-ignore */
4470
+ /* webpackIgnore: true */
4471
+ url2
4472
+ );
4473
+ var shared = null;
4474
+ var getSpeechPlanner = (options = {}) => {
4475
+ const baseUrl = options.baseUrl ?? DEFAULT_TTS_BASE_URL;
4476
+ if (shared) {
4477
+ if (shared.baseUrl !== baseUrl) {
4478
+ console.warn(
4479
+ `[dtm] TTS assets are already loaded from ${shared.baseUrl}; ignoring ${baseUrl}`
4480
+ );
4481
+ }
4482
+ return shared.planner;
4483
+ }
4484
+ const planner = createSpeechPlanner({ ...options, baseUrl });
4485
+ shared = { baseUrl, planner };
4486
+ return planner;
4487
+ };
4488
+ var createSpeechPlanner = (options = {}) => {
4489
+ const baseUrl = options.baseUrl ?? DEFAULT_TTS_BASE_URL;
4490
+ let jp = null;
4491
+ let adapter = null;
4492
+ let readyPromise = null;
4493
+ let isReady = false;
4494
+ const load2 = async () => {
4495
+ const progress = /* @__PURE__ */ new Map();
4496
+ const onProgress = (p) => {
4497
+ if (!options.onProgress) return;
4498
+ progress.set(p.url, p);
4499
+ let loaded = 0;
4500
+ let total = 0;
4501
+ for (const item of progress.values()) {
4502
+ loaded += item.loaded;
4503
+ total += item.total;
4504
+ }
4505
+ options.onProgress(loaded, total);
4506
+ };
4507
+ const fetchWithProgress = (url2) => fetchAsset(url2, { onProgress });
4508
+ const jpreprocessReady = (async () => {
4509
+ const glue2 = await importJpreprocess(
4510
+ joinUrl(baseUrl, "jpreprocess_wasm/jpreprocess_wasm.js")
4511
+ );
4512
+ await glue2.default({
4513
+ module_or_path: fetchWithProgress(
4514
+ joinUrl(baseUrl, "jpreprocess_wasm/jpreprocess_wasm_bg.wasm")
4515
+ )
4516
+ });
4517
+ if (!glue2.is_ready()) {
4518
+ const dict = await loadNaistJdic(
4519
+ joinUrl(baseUrl, "jpreprocess_wasm/naist-jdic"),
4520
+ { onProgress }
4521
+ );
4522
+ initJpreprocessDictionary(glue2, dict);
4523
+ }
4524
+ return glue2;
4525
+ })();
4526
+ const utauttsReady = (async () => {
4527
+ await loadGoRuntime(joinUrl(baseUrl, "wasm_exec.js"));
4528
+ await UtauTTSAdapter.initializeWasm(joinUrl(baseUrl, "utautts.wasm"), {
4529
+ fetch: fetchWithProgress
4530
+ });
4531
+ UtauTTSAdapter.setModel(
4532
+ await fetchAssetText(joinUrl(baseUrl, "frame-intonation-v8.json"), {
4533
+ onProgress
4534
+ })
4535
+ );
4536
+ })();
4537
+ const htsVoiceReady = fetchAssetBytes(
4538
+ joinUrl(baseUrl, "hts/tohoku-f01-neutral.htsvoice"),
4539
+ { onProgress }
4540
+ );
4541
+ const [glue, , htsVoice] = await Promise.all([
4542
+ jpreprocessReady,
4543
+ utauttsReady,
4544
+ htsVoiceReady
4545
+ ]);
4546
+ if (!glue.is_voice_ready()) glue.init_voice(htsVoice);
4547
+ jp = glue;
4548
+ adapter = new UtauTTSAdapter(null);
4549
+ isReady = true;
4550
+ };
4551
+ const ready = () => {
4552
+ if (!readyPromise) {
4553
+ readyPromise = load2().catch((err2) => {
4554
+ readyPromise = null;
4555
+ throw err2;
4556
+ });
4557
+ }
4558
+ return readyPromise;
4559
+ };
4560
+ const plan = (bank, text, o = {}) => {
4561
+ if (!jp || !adapter) {
4562
+ throw new Error("speech planner is not ready; await ready() first");
4563
+ }
4564
+ const intonationStrength = o.intonationStrength ?? 1;
4565
+ const nodes = JSON.parse(jp.analyze_text(text));
4566
+ const { features } = analyze2(nodes);
4567
+ if (features.length === 0) {
4568
+ throw new Error(`no readable morae in "${text}"`);
4569
+ }
4570
+ let prosody = null;
4571
+ if (jp.is_voice_ready()) {
4572
+ const frames = JSON.parse(jp.analyze_prosody(text, 1));
4573
+ prosody = alignHtsProsody(frames, features, { intonationStrength });
4574
+ if (prosody) {
4575
+ prosody = shapeProsody(prosody, features, {
4576
+ question: isQuestion(text)
4577
+ });
4578
+ }
4579
+ }
4580
+ return adapter.plan(bank, text, features, {
4581
+ tone: o.tone ?? "C4",
4582
+ intonationStrength,
4583
+ prosody: prosody ?? void 0
4584
+ });
4585
+ };
4586
+ return { ready, isReady: () => isReady, plan };
4587
+ };
4588
+ var prepareSpeechPlan = (plan, ratio, toReal = (a) => a) => {
4589
+ const timeline = plan.timeline;
4590
+ return {
4591
+ ...plan,
4592
+ timeline: {
4593
+ ...timeline,
4594
+ f0_curve: timeline.f0_curve.map((hz) => hz * ratio),
4595
+ units: timeline.units.map((u) => ({
4596
+ ...u,
4597
+ alias: toReal(u.alias),
4598
+ target_f0_hz: u.target_f0_hz * ratio
4599
+ }))
4600
+ }
4601
+ };
4602
+ };
4603
+ var speechPlanLeadingSec = (plan) => Math.max(0, plan.timeline.leading_ms || 0) / 1e3;
4604
+ var speechPlanDurationSec = (plan) => {
4605
+ const timeline = plan.timeline;
4606
+ let endMs = 0;
4607
+ for (const u of timeline.units) {
4608
+ endMs = Math.max(endMs, u.position_ms + u.length_ms);
4609
+ }
4610
+ if (endMs <= 0) endMs = timeline.duration_ms;
4611
+ return Math.max(0, endMs / 1e3 - speechPlanLeadingSec(plan));
4612
+ };
4613
+ var medianRecordedPitchHz = (phonemes) => {
4614
+ const pitches = Object.values(phonemes).map((p) => p.pitch).filter((hz) => hz > 0).sort((a, b) => a - b);
4615
+ if (pitches.length === 0) return void 0;
4616
+ const mid = pitches.length >> 1;
4617
+ return pitches.length % 2 ? pitches[mid] : (pitches[mid - 1] + pitches[mid]) / 2;
4618
+ };
4619
+
3479
4620
  // src/types.ts
3480
4621
  var DEFAULT_VOCAL_VOLUME = 200;
3481
4622
  var DEFAULT_BPM = 120;
@@ -3574,7 +4715,7 @@ var kanaTable = {
3574
4715
  \u307D: ["p", "o"],
3575
4716
  \u3093: ["N", "N"]
3576
4717
  };
3577
- var SMALL_KANA = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087";
4718
+ var SMALL_KANA2 = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087";
3578
4719
  var VOWEL_KANA = {
3579
4720
  a: "\u3042",
3580
4721
  i: "\u3044",
@@ -3589,13 +4730,43 @@ var REST_MARK = "_";
3589
4730
  var BREATH_MARK = "\u3001";
3590
4731
  var FADE_OUT_MARK = "\u2193";
3591
4732
  var FADE_IN_MARK = "\u2191";
4733
+ var SPEAK_OPEN = "\u300C";
4734
+ var SPEAK_CLOSE = "\u300D";
4735
+ var splitSpeech = (text) => {
4736
+ const pieces = [];
4737
+ let sung = "";
4738
+ let i2 = 0;
4739
+ while (i2 < text.length) {
4740
+ const ch = text[i2];
4741
+ if (ch === SPEAK_OPEN || ch === "\uFF62") {
4742
+ let end = -1;
4743
+ for (let j = i2 + 1; j < text.length; j++) {
4744
+ if (text[j] === SPEAK_CLOSE || text[j] === "\uFF63") {
4745
+ end = j;
4746
+ break;
4747
+ }
4748
+ }
4749
+ if (sung) pieces.push({ sung });
4750
+ sung = "";
4751
+ const body = end < 0 ? text.slice(i2 + 1) : text.slice(i2 + 1, end);
4752
+ pieces.push({ speak: body });
4753
+ i2 = end < 0 ? text.length : end + 1;
4754
+ continue;
4755
+ }
4756
+ sung += ch;
4757
+ i2++;
4758
+ }
4759
+ if (sung) pieces.push({ sung });
4760
+ return pieces;
4761
+ };
4762
+ var sanitizeSpeech = (text) => text.replace(/[\r\n;]+/g, " ").replace(/\s+/g, " ").trim();
3592
4763
  var sanitizeText = (text) => text.normalize("NFKC").replace(/[ァ-ヶ]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 96)).replace(/~/g, PORTAMENTO_MARK).replace(/,/g, BREATH_MARK).replace(/[↡⇩⬇🡇]/gu, FADE_OUT_MARK).replace(/[↟⇧⬆🡅]/gu, FADE_IN_MARK).replace(/[^ぁ-ゖー〜_、↓↑]/g, "");
3593
4764
  var splitSyllables = (text) => {
3594
4765
  const MARKS = `${TIE_MARK}${PORTAMENTO_MARK}${REST_MARK}${BREATH_MARK}${FADE_OUT_MARK}${FADE_IN_MARK}`;
3595
4766
  const result = [];
3596
4767
  for (const ch of text) {
3597
4768
  const prev = result[result.length - 1];
3598
- if (prev !== void 0 && SMALL_KANA.includes(ch) && !MARKS.includes(prev[prev.length - 1])) {
4769
+ if (prev !== void 0 && SMALL_KANA2.includes(ch) && !MARKS.includes(prev[prev.length - 1])) {
3599
4770
  result[result.length - 1] += ch;
3600
4771
  } else {
3601
4772
  result.push(ch);
@@ -3644,42 +4815,52 @@ var analyzeSyllable = (syllable) => {
3644
4815
  var normalizeLyrics = (text) => {
3645
4816
  const result = [];
3646
4817
  let prevVowel = "";
3647
- for (const mark of splitSyllables(sanitizeText(text))) {
3648
- if (mark === BREATH_MARK) {
3649
- const last = result[result.length - 1];
3650
- if (last) last.breathAfter = true;
4818
+ for (const piece of splitSpeech(text)) {
4819
+ if ("speak" in piece) {
4820
+ const body = sanitizeSpeech(piece.speak);
4821
+ result.push(
4822
+ body ? { kana: body, consonant: "", vowel: "", kind: "speak", text: body } : { kana: REST_MARK, consonant: "", vowel: "", kind: "rest" }
4823
+ );
3651
4824
  prevVowel = "";
3652
4825
  continue;
3653
4826
  }
3654
- if (mark === FADE_OUT_MARK) {
3655
- const last = result[result.length - 1];
3656
- if (last) last.fadeOut = true;
3657
- continue;
3658
- }
3659
- if (mark === FADE_IN_MARK) {
3660
- const last = result[result.length - 1];
3661
- if (last) last.fadeIn = true;
3662
- continue;
3663
- }
3664
- const syl = analyzeSyllable(mark);
3665
- if (syl.kind === "tie") {
3666
- if (!prevVowel) continue;
3667
- result.push({
3668
- ...syl,
3669
- kana: prevVowel === "N" ? "\u3093" : VOWEL_KANA[prevVowel] ?? syl.kana,
3670
- consonant: "",
3671
- vowel: prevVowel
3672
- });
3673
- continue;
4827
+ for (const mark of splitSyllables(sanitizeText(piece.sung))) {
4828
+ if (mark === BREATH_MARK) {
4829
+ const last = result[result.length - 1];
4830
+ if (last) last.breathAfter = true;
4831
+ prevVowel = "";
4832
+ continue;
4833
+ }
4834
+ if (mark === FADE_OUT_MARK) {
4835
+ const last = result[result.length - 1];
4836
+ if (last) last.fadeOut = true;
4837
+ continue;
4838
+ }
4839
+ if (mark === FADE_IN_MARK) {
4840
+ const last = result[result.length - 1];
4841
+ if (last) last.fadeIn = true;
4842
+ continue;
4843
+ }
4844
+ const syl = analyzeSyllable(mark);
4845
+ if (syl.kind === "tie") {
4846
+ if (!prevVowel) continue;
4847
+ result.push({
4848
+ ...syl,
4849
+ kana: prevVowel === "N" ? "\u3093" : VOWEL_KANA[prevVowel] ?? syl.kana,
4850
+ consonant: "",
4851
+ vowel: prevVowel
4852
+ });
4853
+ continue;
4854
+ }
4855
+ if (syl.kind === "rest") prevVowel = "";
4856
+ else if (syl.vowel) prevVowel = syl.vowel;
4857
+ result.push(syl);
3674
4858
  }
3675
- if (syl.kind === "rest") prevVowel = "";
3676
- else if (syl.vowel) prevVowel = syl.vowel;
3677
- result.push(syl);
3678
4859
  }
3679
4860
  return result;
3680
4861
  };
3681
4862
  var displayKana = (syl) => {
3682
- const head = syl.kind === "tie" ? syl.portamento ? PORTAMENTO_MARK : TIE_MARK : syl.kind === "rest" ? REST_MARK : syl.kana;
4863
+ const head = syl.kind === "tie" ? syl.portamento ? PORTAMENTO_MARK : TIE_MARK : syl.kind === "rest" ? REST_MARK : syl.kind === "speak" ? `${SPEAK_OPEN}${syl.text ?? syl.kana}${SPEAK_CLOSE}` : syl.kana;
3683
4864
  let out = syl.breathAfter ? head + BREATH_MARK : head;
3684
4865
  if (syl.fadeIn) out += FADE_IN_MARK;
3685
4866
  if (syl.fadeOut) out += FADE_OUT_MARK;
@@ -4168,6 +5349,40 @@ var collectPitchTokens = (aliases) => {
4168
5349
  }
4169
5350
  return [...seen].map(([token, midi]) => ({ token, midi }));
4170
5351
  };
5352
+ var nearestPitchToken = (pitchTokens, noteNum) => {
5353
+ let best = null;
5354
+ for (const t of pitchTokens) {
5355
+ if (!best || Math.abs(t.midi - noteNum) < Math.abs(best.midi - noteNum))
5356
+ best = t;
5357
+ }
5358
+ return best;
5359
+ };
5360
+ var createSpeechToneView = (phonemes, token) => {
5361
+ if (!token) {
5362
+ return {
5363
+ view: { manifest: { phonemes } },
5364
+ toReal: (a) => a,
5365
+ referenceHz: medianRecordedPitchHz(phonemes)
5366
+ };
5367
+ }
5368
+ const suffix = `_${token}`;
5369
+ const stripped = {};
5370
+ const real = /* @__PURE__ */ new Map();
5371
+ for (const [alias, entry] of Object.entries(phonemes)) {
5372
+ if (alias.endsWith(suffix)) {
5373
+ const bare = alias.slice(0, -suffix.length);
5374
+ stripped[bare] = entry;
5375
+ real.set(bare, alias);
5376
+ } else if (!PITCH_SUFFIX.test(alias) && !(alias in stripped)) {
5377
+ stripped[alias] = entry;
5378
+ }
5379
+ }
5380
+ return {
5381
+ view: { manifest: { phonemes: stripped } },
5382
+ toReal: (a) => real.get(a) ?? a,
5383
+ referenceHz: medianRecordedPitchHz(stripped)
5384
+ };
5385
+ };
4171
5386
  var resolveKoeAlias = (hasAlias, pitchTokens, syl, prevVowel, noteNum) => {
4172
5387
  const kana = syl.kana;
4173
5388
  const cons = syl.consonant === "N" ? "n" : syl.consonant;
@@ -4323,10 +5538,29 @@ var createLocalBackend = async (options) => {
4323
5538
  rate
4324
5539
  };
4325
5540
  };
5541
+ let speechAdapter = null;
5542
+ const renderSpeech = async (plan, expr, onChunk, signal) => {
5543
+ if (!worldline) return;
5544
+ speechAdapter ??= new UtauTTSAdapter(worldline);
5545
+ try {
5546
+ for await (const chunk of speechAdapter.renderChunks(bank, plan, {
5547
+ signal,
5548
+ gender: expr?.gender,
5549
+ breathiness: expr?.breathiness,
5550
+ tension: expr?.tension
5551
+ })) {
5552
+ onChunk({ pcm: chunk.pcm, startMs: chunk.startMs, index: chunk.index });
5553
+ }
5554
+ } catch (err2) {
5555
+ console.warn("[dtm] speech synthesis failed", err2);
5556
+ }
5557
+ };
4326
5558
  return {
4327
5559
  hasAlias: (a) => bank.has(a),
4328
5560
  pitchTokens: collectPitchTokens(Object.keys(bank.manifest.phonemes)),
4329
5561
  renderAlias,
5562
+ phonemes: bank.manifest.phonemes,
5563
+ renderSpeech,
4330
5564
  dispose: () => {
4331
5565
  }
4332
5566
  };
@@ -4342,7 +5576,9 @@ var spawnVoiceWorker = async (url2) => {
4342
5576
  var createWorkerBackend = async (workerUrl, options) => {
4343
5577
  const worker2 = await spawnVoiceWorker(workerUrl);
4344
5578
  const aliasSet = /* @__PURE__ */ new Set();
5579
+ let phonemes = {};
4345
5580
  const pending = /* @__PURE__ */ new Map();
5581
+ const speechPending = /* @__PURE__ */ new Map();
4346
5582
  let reqId = 0;
4347
5583
  let onReady = null;
4348
5584
  let onFail = null;
@@ -4350,6 +5586,7 @@ var createWorkerBackend = async (workerUrl, options) => {
4350
5586
  const m = ev.data;
4351
5587
  if (m.type === "ready") {
4352
5588
  for (const a of m.aliases) aliasSet.add(a);
5589
+ phonemes = m.phonemes ?? {};
4353
5590
  onReady?.();
4354
5591
  } else if (m.type === "error") {
4355
5592
  onFail?.(new Error(m.message));
@@ -4359,6 +5596,15 @@ var createWorkerBackend = async (workerUrl, options) => {
4359
5596
  pending.delete(m.id);
4360
5597
  cb(m);
4361
5598
  }
5599
+ } else if (m.type === "speech-chunk") {
5600
+ speechPending.get(m.id)?.onChunk({ pcm: m.pcm, startMs: m.startMs, index: m.index });
5601
+ } else if (m.type === "speech-end") {
5602
+ const s = speechPending.get(m.id);
5603
+ if (s) {
5604
+ speechPending.delete(m.id);
5605
+ if (m.error) console.warn("[dtm] speech synthesis failed", m.error);
5606
+ s.done();
5607
+ }
4362
5608
  }
4363
5609
  };
4364
5610
  worker2.onerror = (e) => {
@@ -4398,10 +5644,32 @@ var createWorkerBackend = async (workerUrl, options) => {
4398
5644
  pitchSegments
4399
5645
  });
4400
5646
  });
5647
+ const renderSpeech = (plan, expr, onChunk, signal) => new Promise((resolve) => {
5648
+ const id = ++reqId;
5649
+ speechPending.set(id, { onChunk, done: resolve });
5650
+ signal?.addEventListener("abort", () => {
5651
+ worker2.postMessage({
5652
+ type: "speak-abort",
5653
+ id
5654
+ });
5655
+ });
5656
+ worker2.postMessage({
5657
+ type: "speak",
5658
+ id,
5659
+ plan,
5660
+ gender: expr?.gender,
5661
+ breathiness: expr?.breathiness,
5662
+ tension: expr?.tension
5663
+ });
5664
+ });
4401
5665
  return {
4402
5666
  hasAlias: (a) => aliasSet.has(a),
4403
5667
  pitchTokens: collectPitchTokens(aliasSet),
4404
5668
  renderAlias,
5669
+ get phonemes() {
5670
+ return phonemes;
5671
+ },
5672
+ renderSpeech,
4405
5673
  dispose: () => worker2.terminate()
4406
5674
  };
4407
5675
  };
@@ -4589,6 +5857,206 @@ var createKoeVoice = async (ctx, destination, options) => {
4589
5857
  durationSec
4590
5858
  );
4591
5859
  };
5860
+ const planner = getSpeechPlanner({
5861
+ baseUrl: options.ttsBaseUrl
5862
+ });
5863
+ const toneViews = /* @__PURE__ */ new Map();
5864
+ const toneViewFor = (token) => {
5865
+ const k = token ?? "";
5866
+ let v = toneViews.get(k);
5867
+ if (!v) {
5868
+ v = createSpeechToneView(backend.phonemes, token);
5869
+ toneViews.set(k, v);
5870
+ }
5871
+ return v;
5872
+ };
5873
+ const toneFor = (pitch) => nearestPitchToken(backend.pitchTokens, unitsToMidiFloat(pitch))?.token ?? null;
5874
+ const plans = /* @__PURE__ */ new Map();
5875
+ const planFor = (text, token) => {
5876
+ const k = `${token ?? ""}|${text}`;
5877
+ let p = plans.get(k);
5878
+ if (!p) {
5879
+ p = (async () => {
5880
+ try {
5881
+ await planner.ready();
5882
+ return planner.plan(toneViewFor(token).view, text, {
5883
+ tone: token ?? "C4"
5884
+ });
5885
+ } catch (err2) {
5886
+ console.warn(`[dtm] speech plan failed for "${text}"`, err2);
5887
+ return null;
5888
+ }
5889
+ })();
5890
+ plans.set(k, p);
5891
+ }
5892
+ return p;
5893
+ };
5894
+ const speechCache = /* @__PURE__ */ new Map();
5895
+ const speechKeyOf = (text, pitch, expr) => `speak|${text}|${Math.round(pitch)}${expr?.gender !== void 0 ? `|g${Math.round(expr.gender * 100)}` : ""}${expr?.breathiness !== void 0 ? `|h${Math.round(expr.breathiness * 100)}` : ""}${expr?.tension !== void 0 ? `|t${Math.round(expr.tension * 100)}` : ""}`;
5896
+ const SPEECH_PITCH_RATIO_MAX = 4;
5897
+ const speechEntryFor = (text, pitch, expr) => {
5898
+ const key = speechKeyOf(text, pitch, expr);
5899
+ let entry = speechCache.get(key);
5900
+ if (entry) return entry;
5901
+ const abort = new AbortController();
5902
+ let resolvePlanned;
5903
+ const planned = new Promise((r) => {
5904
+ resolvePlanned = r;
5905
+ });
5906
+ const e = {
5907
+ planned,
5908
+ rendered: Promise.resolve(),
5909
+ durationSec: 0,
5910
+ chunks: [],
5911
+ done: false,
5912
+ listeners: /* @__PURE__ */ new Set(),
5913
+ abort
5914
+ };
5915
+ e.rendered = (async () => {
5916
+ const token = toneFor(pitch);
5917
+ const plan = await planFor(text, token);
5918
+ if (!plan) {
5919
+ e.done = true;
5920
+ resolvePlanned(null);
5921
+ return;
5922
+ }
5923
+ const view = toneViewFor(token);
5924
+ const reference = plan.timeline.reference_hz || view.referenceHz || 0;
5925
+ const ratio = reference > 0 ? Math.min(
5926
+ SPEECH_PITCH_RATIO_MAX,
5927
+ Math.max(
5928
+ 1 / SPEECH_PITCH_RATIO_MAX,
5929
+ unitsToFreq(pitch) / reference
5930
+ )
5931
+ ) : 1;
5932
+ const prepared = prepareSpeechPlan(plan, ratio, view.toReal);
5933
+ e.durationSec = speechPlanDurationSec(prepared);
5934
+ const leadingSec = speechPlanLeadingSec(prepared);
5935
+ resolvePlanned(e.durationSec);
5936
+ await backend.renderSpeech(
5937
+ prepared,
5938
+ expr,
5939
+ (chunk) => {
5940
+ const buf = ctx.createBuffer(1, chunk.pcm.length, KOE_SAMPLE_RATE);
5941
+ buf.copyToChannel(chunk.pcm, 0);
5942
+ const r = {
5943
+ audio: buf,
5944
+ startSec: chunk.startMs / 1e3 - leadingSec
5945
+ };
5946
+ e.chunks.push(r);
5947
+ for (const l of e.listeners) l(r);
5948
+ },
5949
+ abort.signal
5950
+ );
5951
+ e.done = true;
5952
+ e.listeners.clear();
5953
+ })();
5954
+ entry = e;
5955
+ speechCache.set(key, entry);
5956
+ return entry;
5957
+ };
5958
+ const placeSpeechChunk = (r, t0, peak, pan, reverbSend = 0, delaySend = 0, destOverride) => {
5959
+ const dest = destOverride ?? destination;
5960
+ let startAt = t0 + r.startSec;
5961
+ let offset = 0;
5962
+ const earliest = ctx.currentTime + 0.01;
5963
+ if (startAt < earliest) {
5964
+ offset = earliest - startAt;
5965
+ startAt = earliest;
5966
+ }
5967
+ if (offset >= r.audio.duration - 5e-3) return;
5968
+ let out = dest;
5969
+ let panner = null;
5970
+ if (typeof ctx.createStereoPanner === "function") {
5971
+ panner = ctx.createStereoPanner();
5972
+ panner.pan.value = Math.max(-1, Math.min(1, pan));
5973
+ panner.connect(dest);
5974
+ out = panner;
5975
+ }
5976
+ let reverbSendGain = null;
5977
+ if (options.reverbBus && reverbSend > 0 && panner) {
5978
+ reverbSendGain = ctx.createGain();
5979
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
5980
+ panner.connect(reverbSendGain).connect(options.reverbBus);
5981
+ }
5982
+ let delaySendGain = null;
5983
+ if (options.delayBus && delaySend > 0 && panner) {
5984
+ delaySendGain = ctx.createGain();
5985
+ delaySendGain.gain.value = Math.max(0, Math.min(1, delaySend));
5986
+ panner.connect(delaySendGain).connect(options.delayBus);
5987
+ }
5988
+ const env = ctx.createGain();
5989
+ if (offset > 0) {
5990
+ env.gain.setValueAtTime(1e-4, startAt);
5991
+ env.gain.exponentialRampToValueAtTime(peak, startAt + 5e-3);
5992
+ } else {
5993
+ env.gain.setValueAtTime(peak, startAt);
5994
+ }
5995
+ const src = ctx.createBufferSource();
5996
+ src.buffer = r.audio;
5997
+ src.connect(env).connect(out);
5998
+ src.start(startAt, offset);
5999
+ active.add(src);
6000
+ src.onended = () => {
6001
+ active.delete(src);
6002
+ src.disconnect();
6003
+ env.disconnect();
6004
+ panner?.disconnect();
6005
+ reverbSendGain?.disconnect();
6006
+ delaySendGain?.disconnect();
6007
+ };
6008
+ };
6009
+ const speechListeners = /* @__PURE__ */ new Set();
6010
+ model.speakToCache = async (text, pitch, expr, awaitRender) => {
6011
+ const entry = speechEntryFor(text, pitch, expr);
6012
+ const duration = await entry.planned;
6013
+ if (duration === null) return null;
6014
+ if (awaitRender) await entry.rendered;
6015
+ return speechKeyOf(text, pitch, expr);
6016
+ };
6017
+ model.scheduleSpeech = (key, t0, peak, pan, reverbSend, delaySend, dest) => {
6018
+ const entry = speechCache.get(key);
6019
+ if (!entry) return;
6020
+ const place = (r) => placeSpeechChunk(r, t0, peak, pan, reverbSend, delaySend, dest);
6021
+ for (const r of entry.chunks) place(r);
6022
+ if (!entry.done) {
6023
+ entry.listeners.add(place);
6024
+ const reg = { entry, listener: place };
6025
+ speechListeners.add(reg);
6026
+ void entry.rendered.then(() => speechListeners.delete(reg));
6027
+ }
6028
+ };
6029
+ model.speechDurationSec = (key) => {
6030
+ const entry = speechCache.get(key);
6031
+ return entry && entry.durationSec > 0 ? entry.durationSec : void 0;
6032
+ };
6033
+ const previewDurations = /* @__PURE__ */ new Map();
6034
+ model.planSpeech = async (text) => {
6035
+ const cached = previewDurations.get(text);
6036
+ if (cached !== void 0) return cached;
6037
+ const token = backend.pitchTokens.length > 0 ? nearestPitchToken(
6038
+ backend.pitchTokens,
6039
+ unitsToMidiFloat(model.speechReferenceUnits?.() ?? 0)
6040
+ )?.token ?? null : null;
6041
+ const plan = await planFor(text, token);
6042
+ if (!plan) return null;
6043
+ const d = speechPlanDurationSec(plan);
6044
+ previewDurations.set(text, d);
6045
+ return d;
6046
+ };
6047
+ model.peekSpeechDurationSec = (text) => previewDurations.get(text);
6048
+ model.speechReferenceUnits = () => {
6049
+ const tokens = backend.pitchTokens;
6050
+ let hz;
6051
+ if (tokens.length > 0) {
6052
+ const sorted = tokens.slice().sort((a, b) => a.midi - b.midi);
6053
+ hz = toneViewFor(sorted[sorted.length >> 1].token).referenceHz;
6054
+ } else {
6055
+ hz = toneViewFor(null).referenceHz;
6056
+ }
6057
+ if (!hz || hz <= 0) return void 0;
6058
+ return Math.round(2139 + 372 * Math.log2(hz / 440));
6059
+ };
4592
6060
  model.stopAll = () => {
4593
6061
  for (const src of active) {
4594
6062
  try {
@@ -4598,6 +6066,9 @@ var createKoeVoice = async (ctx, destination, options) => {
4598
6066
  src.disconnect();
4599
6067
  }
4600
6068
  active.clear();
6069
+ for (const { entry, listener } of speechListeners)
6070
+ entry.listeners.delete(listener);
6071
+ speechListeners.clear();
4601
6072
  };
4602
6073
  model.reset = () => {
4603
6074
  prevVowel = "";
@@ -4776,7 +6247,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4776
6247
  lightweight: options.lightweight,
4777
6248
  voiceWorkerUrl: options.voiceWorkerUrl,
4778
6249
  reverbBus: options.reverbBus,
4779
- delayBus: options.delayBus
6250
+ delayBus: options.delayBus,
6251
+ ttsBaseUrl: options.ttsBaseUrl
4780
6252
  })
4781
6253
  ))().then((v) => {
4782
6254
  loaded.set(m, v);
@@ -4811,6 +6283,11 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4811
6283
  prevVowel = "";
4812
6284
  continue;
4813
6285
  }
6286
+ if (syl.kind === "speak") {
6287
+ fn(note, "");
6288
+ prevVowel = "";
6289
+ continue;
6290
+ }
4814
6291
  if (syl.consonant === "Q" || syl.vowel === "") continue;
4815
6292
  fn(note, prevVowel);
4816
6293
  if (syl.vowel && syl.vowel !== "N") prevVowel = syl.vowel;
@@ -4831,6 +6308,17 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4831
6308
  forEachSungNote(track, (note, prevVowel) => {
4832
6309
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
4833
6310
  n++;
6311
+ if (note.syllable.kind === "speak") {
6312
+ tasks.push({
6313
+ model: m,
6314
+ note,
6315
+ prevVowel: "",
6316
+ pitch: note.pitch,
6317
+ expr,
6318
+ speak: note.syllable.text ?? ""
6319
+ });
6320
+ return;
6321
+ }
4834
6322
  tasks.push({
4835
6323
  model: m,
4836
6324
  note,
@@ -4861,15 +6349,24 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4861
6349
  let done = 0;
4862
6350
  onProgress?.(done, total);
4863
6351
  const promises = tasks.map(async (task) => {
4864
- await (task.model.renderToCache?.(
4865
- task.note.syllable,
4866
- task.prevVowel,
4867
- task.pitch,
4868
- task.note.durationSec * 1e3,
4869
- task.vibrato,
4870
- task.expr,
4871
- task.pitchSegments
4872
- ) ?? Promise.resolve(null));
6352
+ if (task.speak !== void 0) {
6353
+ await (task.model.speakToCache?.(
6354
+ task.speak,
6355
+ task.pitch,
6356
+ task.expr,
6357
+ true
6358
+ ) ?? Promise.resolve(null));
6359
+ } else {
6360
+ await (task.model.renderToCache?.(
6361
+ task.note.syllable,
6362
+ task.prevVowel,
6363
+ task.pitch,
6364
+ task.note.durationSec * 1e3,
6365
+ task.vibrato,
6366
+ task.expr,
6367
+ task.pitchSegments
6368
+ ) ?? Promise.resolve(null));
6369
+ }
4873
6370
  done++;
4874
6371
  onProgress?.(done, total);
4875
6372
  });
@@ -4906,6 +6403,49 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4906
6403
  }
4907
6404
  if (opts?.isAudible && !opts.isAudible(track)) continue;
4908
6405
  const t0 = anchorTime + startSec;
6406
+ if (note.syllable.kind === "speak") {
6407
+ const text = note.syllable.text ?? "";
6408
+ if (text && model.speakToCache && model.scheduleSpeech) {
6409
+ const speakToCache = model.speakToCache;
6410
+ const scheduleSpeech = model.scheduleSpeech;
6411
+ const dest = options.getTrackDestination?.(track.id ?? "");
6412
+ const effPeak = dest ? peak * masterVolumeScalar : peak;
6413
+ void (async () => {
6414
+ const key = await speakToCache(text, note.pitch, {
6415
+ gender: track.gender,
6416
+ breathiness: track.breathiness,
6417
+ tension: track.tension
6418
+ });
6419
+ if (session !== streamSession || !key) return;
6420
+ const delay = ctx.currentTime - t0;
6421
+ if (delay < 1) {
6422
+ scheduleSpeech(
6423
+ key,
6424
+ t0,
6425
+ effPeak,
6426
+ track.pan,
6427
+ track.reverbSend,
6428
+ track.delaySend,
6429
+ dest
6430
+ );
6431
+ opts?.onScheduled?.(
6432
+ track,
6433
+ {
6434
+ ...note,
6435
+ durationSec: model.speechDurationSec?.(key) ?? note.durationSec
6436
+ },
6437
+ t0
6438
+ );
6439
+ } else {
6440
+ console.warn(
6441
+ `[dtm] Speech late skip: \u300C${text}\u300D at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
6442
+ );
6443
+ opts?.onLateSkip?.(note, delay);
6444
+ }
6445
+ })();
6446
+ }
6447
+ continue;
6448
+ }
4909
6449
  const dispatchNote = (offsetUnits, peakScale) => {
4910
6450
  const pitch = units(note.pitch + offsetUnits);
4911
6451
  const pitchSegments = transposeSegments(
@@ -5032,6 +6572,12 @@ var createSingingVoices = (ctx, destination, options = {}) => {
5032
6572
  masterGain.gain.value = g;
5033
6573
  masterVolumeScalar = g;
5034
6574
  };
6575
+ const planSpeech = async (model, text) => {
6576
+ const v = await load2(model);
6577
+ return v?.planSpeech ? v.planSpeech(text) : null;
6578
+ };
6579
+ const peekSpeechDurationSec = (model, text) => loaded.get(model.toLowerCase())?.peekSpeechDurationSec?.(text);
6580
+ const getSpeechReferenceUnits = (model) => loaded.get(model.toLowerCase())?.speechReferenceUnits?.();
5035
6581
  return {
5036
6582
  loadModels,
5037
6583
  registerVoicebanks,
@@ -5039,7 +6585,10 @@ var createSingingVoices = (ctx, destination, options = {}) => {
5039
6585
  startStream,
5040
6586
  stopStream,
5041
6587
  reset,
5042
- setVolume
6588
+ setVolume,
6589
+ planSpeech,
6590
+ peekSpeechDurationSec,
6591
+ getSpeechReferenceUnits
5043
6592
  };
5044
6593
  };
5045
6594
  var createVoiceRegistry = (models = {}, fallback = "klatt") => {
@@ -5060,7 +6609,7 @@ var NATURAL_STEPS2 = {
5060
6609
  };
5061
6610
  var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
5062
6611
  var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
5063
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo|loop)=([\w:-]+)/gi;
6612
+ var META_DIRECTIVE = /#(ver|seed|compose|inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo|loop)=([\w:.-]+)/gi;
5064
6613
  var AUDIO_URL_DIRECTIVE = /#audio=([^\s#;\r\n]+)/gi;
5065
6614
  var AUDIO_NUM_DIRECTIVE = /#audio(start|end|offset|at|vol)=(-?\d+(?:\.\d+)?)/gi;
5066
6615
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
@@ -5077,6 +6626,10 @@ var parseMmlMeta = (mml) => {
5077
6626
  for (const m of mml.matchAll(META_DIRECTIVE)) {
5078
6627
  const key = m[1].toLowerCase();
5079
6628
  if (key === "ver") meta.version = m[2];
6629
+ else if (key === "seed") {
6630
+ const n = Number.parseInt(m[2], 10);
6631
+ if (Number.isFinite(n) && n >= 0) meta.seed = n;
6632
+ } else if (key === "compose") meta.compose = m[2];
5080
6633
  else if (key === "inst") meta.instrument = m[2];
5081
6634
  else if (key === "drum") meta.drum = m[2];
5082
6635
  else if (key === "drumfont") meta.drumFont = m[2];
@@ -5244,6 +6797,8 @@ var formatMmlMeta = (meta, space = "") => {
5244
6797
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
5245
6798
  if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
5246
6799
  if (meta.loop) parts.push("#loop=on");
6800
+ if (meta.seed !== void 0) parts.push(`#seed=${meta.seed}`);
6801
+ if (meta.compose) parts.push(`#compose=${meta.compose}`);
5247
6802
  if (meta.audio) {
5248
6803
  parts.push(`#audio=${meta.audio}`);
5249
6804
  if (meta.audioStart) parts.push(`#audiostart=${round3(meta.audioStart)}`);
@@ -8674,6 +10229,78 @@ var DAW_CSS = `
8674
10229
  .dtm-daw *::before,
8675
10230
  .dtm-daw *::after { box-sizing: border-box; }
8676
10231
 
10232
+ /* \u2500\u2500\u2500 \u30EC\u30A4\u30A2\u30A6\u30C8\uFF08\u7DE8\u96C6\u30D8\u30C3\u30C9\u306E\u8CBC\u308A\u4ED8\u3051\uFF09 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
10233
+ \u30D1\u30CD\u30EB\u306F\u7E26\u306B\u7A4D\u3080\u3057\u304B\u306A\u3044\u306E\u3067\u3001\u958B\u304F\u307B\u3069\u30D4\u30A2\u30CE\u30ED\u30FC\u30EB\u304C\u753B\u9762\u5916\u3078\u6D41\u308C\u3066\u3044\u304F\u3002
10234
+ \u30D8\u30C3\u30C9\uFF08\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u30FB\u30C4\u30FC\u30EB\u30FB\u30ED\u30FC\u30EB\uFF09\u3092 sticky \u3067\u753B\u9762\u4E0A\u90E8\u306B\u7559\u3081\u308C\u3070\u3001
10235
+ \u4F55\u679A\u958B\u3044\u3066\u3082\u30ED\u30FC\u30EB\u3068\u306E\u8DDD\u96E2\u306F0\u306E\u307E\u307E\u306B\u306A\u308B\u3002
10236
+
10237
+ \u305F\u3060\u30571\u30AB\u30E9\u30E0\u306E\u3042\u3044\u3060\u3001\u8CBC\u3063\u305F\u30D8\u30C3\u30C9\u306F\u305D\u306E\u9AD8\u3055\u3076\u3093\u753B\u9762\u3092\u5360\u6709\u3057\u7D9A\u3051\u308B\u3002
10238
+ \u30D8\u30C3\u30C9\u306E\u9AD8\u3055\u306F\u300C\u56FA\u5B9A\u90E8\u304A\u3088\u305D194px \uFF0B \u30ED\u30FC\u30EB32vh\u300D\u306A\u306E\u3067\u3001\u753B\u9762\u304C\u4F4E\u3044\u307B\u3069
10239
+ \u5360\u6709\u7387\u304C\u4E0A\u304C\u308B\uFF08844px\u306755%\u3001667px\u306764%\uFF09\u3002\u5360\u6709\u7387\u304C6\u5272\u3092\u5207\u308B\u7E26760px\u4EE5\u4E0A\u3067
10240
+ \u3060\u3051\u8CBC\u308A\u3001\u305D\u308C\u672A\u6E80\u306F\u5F93\u6765\u3069\u304A\u308A\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u3060\u3051\u3092\u8CBC\u308B\u3002
10241
+ \uFF08\u6A2A\u306B\u5272\u3063\u3066\u7E26\u306E\u53D6\u308A\u5408\u3044\u3092\u7121\u304F\u30592\u30AB\u30E9\u30E0\u6848\u306F\u3001\u30C7\u30E2\u306E #app \u304C max-w-4xl \u306E
10242
+ \u305F\u3081\u4ECA\u306E\u307E\u307E\u3060\u3068\u30ED\u30FC\u30EB\u304C\u7D30\u304F\u306A\u308B\u306E\u3067\u898B\u9001\u3063\u3066\u3044\u308B\u3002\uFF09 */
10243
+ .dtm-daw-panels {
10244
+ display: flex;
10245
+ flex-direction: column;
10246
+ gap: var(--dtm-gap);
10247
+ min-width: 0;
10248
+ }
10249
+ /* \u8CBC\u3089\u306A\u3044\u753B\u9762\u3067\u306F\u7BB1\u3054\u3068\u7573\u3080\u3002display:contents \u306A\u3089\u4E2D\u8EAB\u304C .dtm-daw \u306E\u76F4\u63A5\u306E
10250
+ \u5B50\u306B\u623B\u308B\u306E\u3067\u3001\u4F59\u767D\u3082\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u306E sticky \u57FA\u6E96\u3082\u5F93\u6765\u306E\u307E\u307E\u5909\u308F\u3089\u306A\u3044\u3002 */
10251
+ .dtm-daw-head { display: contents; }
10252
+ .dtm-topbar {
10253
+ position: sticky;
10254
+ top: 0;
10255
+ z-index: 20;
10256
+ }
10257
+ @media (min-height: 760px) {
10258
+ .dtm-daw-head {
10259
+ display: flex;
10260
+ flex-direction: column;
10261
+ gap: var(--dtm-gap);
10262
+ min-width: 0;
10263
+ position: sticky;
10264
+ top: 0;
10265
+ z-index: 20;
10266
+ /* \u4E0B\u3092\u304F\u3050\u308B\u30D1\u30CD\u30EB\u3092\u900F\u304B\u3055\u306A\u3044\u305F\u3081\u306E\u4E0D\u900F\u660E\u306A\u4E0B\u6577\u304D\u3002
10267
+ .dtm-daw \u306E gap \u3076\u3093\u3092 padding \u3067\u57CB\u3081\u3001\u9699\u9593\u304B\u3089\u4E2D\u8EAB\u3092\u898B\u305B\u306A\u3044\u3002 */
10268
+ background: var(--dtm-bg);
10269
+ padding-bottom: var(--dtm-gap);
10270
+ }
10271
+ }
10272
+
10273
+ /* \u30A8\u30C7\u30A3\u30BF\u81EA\u8EAB\u304C\u5341\u5206\u306B\u5E83\u3044\u3068\u304D\u306F\u3001\u30D8\u30C3\u30C9\u3068\u30D1\u30CD\u30EB\u3092\u5DE6\u53F3\u306B\u4E26\u3079\u3066\u3001\u7E26\u306E
10274
+ \u7A4D\u307F\u4E0A\u304C\u308A\u3092\u6A2A\u3078\u9003\u304C\u3059\u3002\u3053\u3046\u306A\u308B\u3068\u30D8\u30C3\u30C9\u306F\u30D1\u30CD\u30EB\u304B\u3089\u7E26\u3092\u596A\u308F\u306A\u3044\u306E\u3067\u3001
10275
+ \u753B\u9762\u306E\u9AD8\u3055\u306B\u95A2\u4FC2\u306A\u304F\u8CBC\u308C\u308B\u3057\u3001\u30ED\u30FC\u30EB\u3082\u5E83\u304F\u4F7F\u3048\u308B
10276
+ \uFF08.dtm-daw--wide \u306F daw.ts \u306E ResizeObserver \u304C\u4ED8\u3051\u308B\uFF09\u3002 */
10277
+ .dtm-daw--wide {
10278
+ flex-direction: row;
10279
+ align-items: flex-start;
10280
+ }
10281
+ .dtm-daw--wide .dtm-daw-head {
10282
+ display: flex;
10283
+ flex-direction: column;
10284
+ gap: var(--dtm-gap);
10285
+ min-width: 0;
10286
+ flex: 1 1 auto;
10287
+ position: sticky;
10288
+ top: 0;
10289
+ z-index: 20;
10290
+ background: var(--dtm-bg);
10291
+ padding-bottom: 0;
10292
+ }
10293
+ .dtm-daw--wide .dtm-daw-panels {
10294
+ flex: 0 0 360px;
10295
+ }
10296
+ /* 768px \u6642\u70B9\u306E height:420px \u306F max-height:32vh \u306B\u982D\u6253\u3061\u306B\u3055\u308C\u3066\u3044\u305F\u3002
10297
+ 2\u30AB\u30E9\u30E0\u3067\u306F\u7E26\u3092\u53D6\u308A\u5408\u308F\u306A\u3044\u306E\u3067\u3001\u305D\u306E\u610F\u56F3\u3069\u304A\u308A\u306E\u9AD8\u3055\u3092\u89E3\u7981\u3059\u308B
10298
+ \uFF08\u4F4E\u3044\u753B\u9762\u3067\u306F 45vh \u3067\u982D\u6253\u3061\u306B\u3057\u3066\u753B\u9762\u3092\u98DF\u3044\u6F70\u3055\u306A\u3044\u3088\u3046\u306B\u3059\u308B\uFF09\u3002 */
10299
+ .dtm-daw--wide .dtm-roll {
10300
+ height: min(420px, 45vh);
10301
+ max-height: none;
10302
+ }
10303
+
8677
10304
  /* \u2500\u2500\u2500 \u30B2\u30FC\u30E0\u30A6\u30A3\u30F3\u30C9\u30A6\u5171\u901A\u67A0 \u2500\u2500\u2500 */
8678
10305
  /* \u5916\u67A0(\u9ED22px) \u2192 \u8272\u4ED8\u304D2px border \u2192 \u5185\u67A0(\u9ED2inset2px) \u306E3\u91CD\u69CB\u9020 */
8679
10306
  .dtm-win {
@@ -8736,9 +10363,6 @@ var DAW_CSS = `
8736
10363
 
8737
10364
  /* \u2500\u2500\u2500 \u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u30D0\u30FC\uFF08HUD\u30B9\u30BF\u30A4\u30EB\uFF09 \u2500\u2500\u2500 */
8738
10365
  .dtm-topbar {
8739
- position: sticky;
8740
- top: 0;
8741
- z-index: 20;
8742
10366
  display: flex;
8743
10367
  flex-wrap: wrap;
8744
10368
  align-items: center;
@@ -9183,19 +10807,17 @@ var DAW_CSS = `
9183
10807
  .dtm-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
9184
10808
  .dtm-track-body { display: flex; flex-direction: column; gap: 10px; }
9185
10809
 
9186
- /* \u2500\u2500\u2500 \u81EA\u52D5\u4F5C\u66F2\u30B3\u30F3\u30C6\u30CA\uFF08\u30DE\u30AF\u30ED\u30D1\u30CD\u30EB\u5185\u306E\u5C02\u7528\u30AB\u30FC\u30C9\u98A8\u30B0\u30EB\u30FC\u30D7\u67A0\uFF09 \u2500\u2500\u2500 */
9187
- .dtm-compose-container {
9188
- background: var(--dtm-deep);
9189
- border: 2px solid var(--c-black);
10810
+ /* \u2500\u2500\u2500 \u81EA\u52D5\u4F5C\u66F2\u30D1\u30CD\u30EB\uFF08\u770B\u677F\u6A5F\u80FD\u306A\u306E\u3067\u67A0\u3068\u898B\u51FA\u3057\u3092 success \u8272\u3067\u5DEE\u5225\u5316\u3059\u308B\uFF09 \u2500\u2500\u2500 */
10811
+ .dtm-panel--compose {
9190
10812
  box-shadow:
9191
- inset 0 0 0 1px var(--dtm-border2),
9192
- 0 0 0 1px var(--dtm-success),
9193
- 3px 3px 0 var(--c-black);
9194
- padding: 10px 12px 12px;
9195
- display: flex;
9196
- flex-direction: column;
9197
- gap: 10px;
9198
- margin-bottom: 6px;
10813
+ inset 0 0 0 2px var(--c-black),
10814
+ 0 0 0 2px var(--dtm-success),
10815
+ 4px 4px 0 var(--c-black);
10816
+ }
10817
+ .dtm-panel--compose > summary { color: var(--dtm-success); }
10818
+ .dtm-panel--compose > summary::before,
10819
+ .dtm-panel--compose[open] > summary::before {
10820
+ background: var(--dtm-success);
9199
10821
  }
9200
10822
 
9201
10823
  /* \u2500\u2500\u2500 \u30A2\u30AF\u30C6\u30A3\u30D6\u30C8\u30E9\u30C3\u30AF\u8272\uFF08\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A\u30D1\u30CD\u30EB\u306E\u5DE6\u7AEF\u30E9\u30A4\u30F3\uFF09 \u2500\u2500\u2500 */
@@ -10308,6 +11930,8 @@ var DAW_CSS = `
10308
11930
  /* \u4FC3\u97F3\uFF08\u3063\uFF09\u30FB\u4F11\u7B26\uFF08_\uFF09\u306F\u6B4C\u308F\u306A\u3044\u30CE\u30FC\u30C8\u3002\u3055\u3089\u306B\u843D\u3068\u3057\u3066\u7121\u97F3\u3067\u3042\u308B\u3053\u3068\u3092\u793A\u3059 */
10309
11931
  .dtm-tk--lyric-stop,
10310
11932
  .dtm-tk--lyric-rest { color: var(--dtm-border2); }
11933
+ /* \u8A9E\u308A\uFF08\u300C\u2026\u300D\uFF09\u306F\u6B4C\u308F\u305A\u306B\u8AAD\u307F\u4E0A\u3052\u308B 1 \u30CE\u30FC\u30C8\u3002\u6B4C\u8A5E\u3068\u533A\u5225\u3067\u304D\u308B\u3088\u3046\u5F37\u8ABF\u8272\u306B\u3059\u308B */
11934
+ .dtm-tk--lyric-speak { color: var(--dtm-primary); letter-spacing: 0; }
10311
11935
  .dtm-tk--break { color: var(--dtm-muted); opacity: 0.7; margin: 0 2px; }
10312
11936
  .dtm-tk--meta { color: var(--dtm-border2); margin-right: 4px; }
10313
11937
  .dtm-tk.is-active {
@@ -22001,6 +23625,16 @@ var BASS_SKELETONS = [
22001
23625
  "pedal"
22002
23626
  ];
22003
23627
  var pick = (items, rnd) => items[Math.floor(rnd() * items.length)];
23628
+ var seededRandom = (seed) => {
23629
+ let a = seed >>> 0;
23630
+ return () => {
23631
+ a = a + 1831565813 >>> 0;
23632
+ let t = a;
23633
+ t = Math.imul(t ^ t >>> 15, t | 1);
23634
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
23635
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
23636
+ };
23637
+ };
22004
23638
  var durationEntropy = (durations) => {
22005
23639
  if (durations.length === 0) return 0;
22006
23640
  const counts = /* @__PURE__ */ new Map();
@@ -24944,6 +26578,10 @@ var buildUI = (target, options) => {
24944
26578
  ).join("");
24945
26579
  target.innerHTML = `
24946
26580
  <div class="dtm-daw" data-dtm="root">
26581
+ <!-- \u7DE8\u96C6\u30D8\u30C3\u30C9\u3002\u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u30FB\u30C4\u30FC\u30EB\u30FB\u30D4\u30A2\u30CE\u30ED\u30FC\u30EB\u3092\u3072\u3068\u307E\u3068\u3081\u306B\u3057\u3066
26582
+ \u753B\u9762\u4E0A\u90E8\u3078\u8CBC\u308A\u4ED8\u3051\u308B\uFF08.dtm-daw-head \u304C position:sticky\uFF09\u3002\u30D1\u30CD\u30EB\u3092
26583
+ \u3044\u304F\u3064\u958B\u3044\u3066\u3082\u30ED\u30FC\u30EB\u304C\u8996\u754C\u304B\u3089\u6D88\u3048\u306A\u3044\u3088\u3046\u306B\u3059\u308B\u305F\u3081\u306E\u7BB1\u3002 -->
26584
+ <div class="dtm-daw-head">
24947
26585
  <div class="dtm-topbar" data-dtm="transport">
24948
26586
  <div class="dtm-topbar-row1">
24949
26587
  <button class="dtm-iconbtn" data-dtm="prev-bar" title="1\u5C0F\u7BC0\u524D">${icon("chevronLeft")}</button>
@@ -24988,6 +26626,10 @@ var buildUI = (target, options) => {
24988
26626
  <div class="dtm-vscroll" data-dtm="vscroll"><div class="dtm-vscroll-thumb" data-dtm="vscroll-thumb"></div></div>
24989
26627
  </div>
24990
26628
  <div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
26629
+ </div>
26630
+
26631
+ <!-- \u8A2D\u5B9A\u30D1\u30CD\u30EB\u7FA4\u3002\u5E83\u3044\u753B\u9762\u3067\u306F\u30D8\u30C3\u30C9\u306E\u53F3\u96A3\u306B\u7ACB\u3064\u72EC\u7ACB\u3057\u305F\u5217\u306B\u306A\u308B\u3002 -->
26632
+ <div class="dtm-daw-panels">
24991
26633
 
24992
26634
  <details class="dtm-panel" data-dtm-acc="track" open>
24993
26635
  <summary>\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
@@ -25186,7 +26828,7 @@ var buildUI = (target, options) => {
25186
26828
  </details>
25187
26829
 
25188
26830
  <details class="dtm-panel ${showMidi ? "" : "dtm-hidden"}" data-dtm="midi-panel" data-dtm-acc="io-in">
25189
- <summary>MIDI / UST / MML \u5165\u529B</summary>
26831
+ <summary>MIDI / MusicXML / UST / MML \u5165\u529B</summary>
25190
26832
  <div class="dtm-panel-body">
25191
26833
  <div class="dtm-row" style="flex-wrap:nowrap">
25192
26834
  <div style="display: inline-flex; flex-direction: column; align-items: center; gap: 4px; justify-content: center; flex-shrink:0;">
@@ -25197,6 +26839,16 @@ var buildUI = (target, options) => {
25197
26839
  <button class="dtm-btn dtm-btn--success" data-dtm="midi-load" style="flex-shrink:0">\u8AAD\u8FBC</button>
25198
26840
  </div>
25199
26841
  <div class="dtm-row dtm-hidden" data-dtm="midi-track-selection"></div>
26842
+ <div class="dtm-row" style="flex-wrap:nowrap">
26843
+ <div style="display: inline-flex; flex-direction: column; align-items: center; gap: 4px; justify-content: center; flex-shrink:0;">
26844
+ <span class="dtm-label" style="line-height: 1;">MusicXML</span>
26845
+ <button class="dtm-infobtn" data-dtm="musicxml-info" title="MusicXML\u306E\u8AAD\u307F\u8FBC\u307F\u89E3\u8AAC">${icon("info", 12)}</button>
26846
+ </div>
26847
+ <input type="file" class="dtm-input dtm-grow" accept=".musicxml,.xml" data-dtm="musicxml-input" style="min-width:0">
26848
+ <button class="dtm-btn dtm-btn--success" data-dtm="musicxml-load" style="flex-shrink:0">\u8AAD\u8FBC</button>
26849
+ </div>
26850
+ <div class="dtm-row dtm-hidden" data-dtm="musicxml-part-selection"></div>
26851
+ <p class="dtm-load-note dtm-hidden" data-dtm="musicxml-load-note"></p>
25200
26852
  <div class="dtm-row" style="flex-wrap:nowrap">
25201
26853
  <div style="display: inline-flex; flex-direction: column; align-items: center; gap: 4px; justify-content: center; flex-shrink:0;">
25202
26854
  <span class="dtm-label" style="line-height: 1;">UST</span>
@@ -25226,129 +26878,135 @@ var buildUI = (target, options) => {
25226
26878
  </div>
25227
26879
  </details>
25228
26880
 
25229
- <details class="dtm-panel" data-dtm-acc="macro">
25230
- <summary>\u30DE\u30AF\u30ED</summary>
26881
+ <details class="dtm-panel dtm-panel--compose ${showCompose ? "" : "dtm-hidden"}" data-dtm-acc="compose">
26882
+ <summary>\u81EA\u52D5\u4F5C\u66F2</summary>
25231
26883
  <div class="dtm-panel-body">
25232
- <div class="dtm-compose-container ${showCompose ? "" : "dtm-hidden"}" data-dtm="compose-container">
25233
- <div class="dtm-row" data-dtm="compose-row">
25234
- <button class="dtm-btn dtm-btn--success" data-dtm="macro-compose" title="\u30B3\u30FC\u30C9\u9032\u884C\u30FB\u30E1\u30ED\u30C7\u30A3\u30FB\u30B5\u30D6\u30E1\u30ED\u30FB\u30D9\u30FC\u30B9\u30FB\u4F34\u594F\u30FB\u30C9\u30E9\u30E0\u3092\u81EA\u52D5\u3067\u4F5C\u308A\u307E\u3059">\u4F5C\u66F2</button>
25235
- <button class="dtm-btn dtm-btn--success" data-dtm="macro-compose-vocal" title="\u4F5C\u66F2\u3057\u305F\u3046\u3048\u3067\u3001\u30E1\u30ED\u30C7\u30A3\u306B\u6B4C\u8A5E\u3092\u4ED8\u3051\u3066\u6B4C\u308F\u305B\u307E\u3059">\u6B4C\u5165\u308A\u4F5C\u66F2</button>
25236
- <button class="dtm-infobtn" data-dtm="macro-compose-info" title="\u4F5C\u66F2\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
25237
- <!--
25238
- **\u30AD\u30FC\u30D7\u67A0\u306F1\u3064\u3060\u3051\u3002** \u81EA\u52D5\u4F5C\u66F2\u306F\u6C17\u306B\u5165\u308B\u307E\u3067\u5F15\u304D\u76F4\u3059\u4F7F\u3044\u65B9\u306B\u306A\u308B\u304C\u3001
25239
- \u300C\u5F15\u304D\u76F4\u3059\u3068\u4ECA\u306E\u304C\u6D88\u3048\u308B\u300D\u3068\u601D\u3046\u3068\u5F15\u304D\u76F4\u305B\u306A\u304F\u306A\u308B\u3002\u53D6\u3063\u3066\u304A\u3051\u308B\u5834\u6240\u304C
25240
- 1\u3064\u3042\u308C\u3070\u30012\u3064\u3092\u6BD4\u3079\u3066\u9078\u3076\u3053\u3068\u306F\u6210\u7ACB\u3059\u308B\u3002\u5019\u88DC\u3092\u4E26\u3079\u308BUI\u306F\u30B9\u30DE\u30DB\u3067\u306F
25241
- \u6210\u7ACB\u3057\u306A\u3044\uFF08\u8A66\u8074\u6642\u9593\u30FB\u753B\u9762\u30FB\u751F\u6210\u30B3\u30B9\u30C8\u306E\u3069\u308C\u3082\u8DB3\u308A\u306A\u3044\uFF09\u3002
25242
- -->
25243
- <button class="dtm-btn" data-dtm="compose-keep" title="\u4ECA\u306E\u66F2\u30921\u3064\u3060\u3051\u53D6\u3063\u3066\u304A\u304D\u307E\u3059\u3002\u4F5C\u66F2\u3092\u62BC\u3057\u76F4\u3057\u3066\u3082\u6D88\u3048\u307E\u305B\u3093">\u30AD\u30FC\u30D7</button>
25244
- <button class="dtm-btn" data-dtm="compose-recall" title="\u30AD\u30FC\u30D7\u3057\u305F\u66F2\u306B\u623B\u3057\u307E\u3059" disabled>\u547C\u3073\u51FA\u3059</button>
25245
- <span class="dtm-grow"></span>
25246
- </div>
25247
- <div class="dtm-row" data-dtm="compose-template-row">
25248
- <span class="dtm-label">\u69CB\u6210</span>
25249
- <select class="dtm-select" data-dtm="compose-template" title="J-POP\u738B\u9053\u306A\u3069\u306E\u30D7\u30EA\u30BB\u30C3\u30C8\u69CB\u6210\u3092\u9078\u3073\u307E\u3059">
25250
- <option value="custom">\u81EA\u7531\u9078\u629E\uFF08\u4E0B\u8A18\u30C1\u30A7\u30C3\u30AF\uFF09</option>
25251
- <option value="1chorus">1\u30B3\u30FC\u30E9\u30B9\uFF08\u77ED\u3081\u30FB\u521D\u5FC3\u8005\u5411\u3051\uFF09</option>
25252
- <option value="jpop_standard">JPOP\u738B\u9053\uFF08\u30DE\u30EA\u30FC\u30B4\u30FC\u30EB\u30C9\u578B 2\u756A/C\u30E1\u30ED/\u30E9\u30B9\u30B5\u30D3\uFF09</option>
25253
- <option value="jpop_drop">\u843D\u3061\u30B5\u30D3\u5165\u308A\uFF08JPOP\u738B\u9053 + \u30E9\u30B9\u30B5\u30D3\u524D\u843D\u3061\u30B5\u30D3\uFF09</option>
25254
- <option value="vocaloid">\u30DC\u30AB\u30ED\u738B\u9053\uFF08\u75BE\u8D70\u30FB2\u756A/C\u30E1\u30ED/\u30E9\u30B9\u30B5\u30D3\uFF09</option>
25255
- <option value="verse_chorus">Verse-Chorus\uFF08B\u30E1\u30ED\u306A\u3057\u30FB\u6D0B\u697D\u98A8\uFF09</option>
25256
- </select>
25257
- </div>
25258
- <div class="dtm-row" data-dtm="compose-sections-row">
25259
- <span class="dtm-label">\u4F5C\u308B\u90E8\u5206</span>
25260
- <div class="dtm-checks" data-dtm="compose-sections">
25261
- <label class="dtm-check"><input type="checkbox" value="intro" checked>\u30A4\u30F3\u30C8\u30ED</label>
25262
- <label class="dtm-check"><input type="checkbox" value="verse" checked>A\u30E1\u30ED</label>
25263
- <label class="dtm-check"><input type="checkbox" value="prechorus" checked>B\u30E1\u30ED</label>
25264
- <label class="dtm-check"><input type="checkbox" value="chorus" checked>\u30B5\u30D3</label>
25265
- <label class="dtm-check"><input type="checkbox" value="bridge">C\u30E1\u30ED</label>
25266
- <label class="dtm-check"><input type="checkbox" value="drop_chorus">\u843D\u3061\u30B5\u30D3</label>
25267
- <label class="dtm-check"><input type="checkbox" value="interlude">\u9593\u594F</label>
25268
- <label class="dtm-check"><input type="checkbox" value="outro">\u30A2\u30A6\u30C8\u30ED</label>
25269
- </div>
25270
- <span class="dtm-grow"></span>
25271
- <span class="dtm-hint" data-dtm="compose-sections-len"></span>
25272
- </div>
25273
- <div class="dtm-row" data-dtm="compose-key-row">
25274
- <span class="dtm-label">\u30D9\u30FC\u30B9\u8ABF</span>
25275
- <select class="dtm-select" data-dtm="compose-key" title="\u81EA\u52D5\u4F5C\u66F2\u306E\u30D9\u30FC\u30B9\u3068\u306A\u308B\u8ABF\u3084\u96F0\u56F2\u6C17\u3092\u9078\u3073\u307E\u3059">
25276
- <option value="any" title="\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057</option>
25277
- <optgroup label="\u57FA\u672C">
25278
- <option value="major" title="12\u306E\u9577\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u9577\u8ABF</option>
25279
- <option value="minor" title="12\u306E\u77ED\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u77ED\u8ABF</option>
25280
- </optgroup>
25281
- <optgroup label="\u96F0\u56F2\u6C17\u304B\u3089\u9078\u3076\uFF08\u62BD\u9078\uFF09">
25282
- <option value="mood_happy" title="\u30CF\u9577\u8ABF\u30FB\u30A4\u9577\u8ABF\u30FB\u5909\u30ED\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7267\u6B4C\u7684\u3001\u967D\u6C17\uFF09">\u559C\u3070\u3057\u3044\u30FB\u967D\u6C17\u306A\u66F2</option>
25283
- <option value="mood_triumphant" title="\u30CB\u9577\u8ABF\u30FB\u5909\u30C8\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u610F\u6C17\u63DA\u3005\u3001\u52DD\u5229\u306E\u558A\u58F0\u3001\u56F0\u96E3\u6253\u7834\u3001\u5B89\u5835\uFF09">\u52DD\u5229\u30FB\u529B\u5F37\u3044\u66F2</option>
25284
- <option value="mood_fierce" title="\u30DB\u9577\u8ABF\u30FB\u30D8\u9577\u8ABF\u30FB\u30ED\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u6012\u308A\u72C2\u3063\u305F\u8352\u3005\u3057\u3055\u3001\u3069\u304E\u3064\u304F\u731B\u70C8\uFF09">\u6FC0\u3057\u3044\u30FB\u8352\u3005\u3057\u3044\u66F2</option>
25285
- <option value="mood_solemn" title="\u30C8\u9577\u8ABF\u30FB\u30CB\u77ED\u8ABF\u30FB\u30A4\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u53B3\u7C9B\u3001\u5D07\u9AD8\u3001\u5E7B\u60F3\u3001\u656C\u8654\u3001\u601D\u7D22\u7684\uFF09">\u53B3\u7C9B\u30FB\u5E7B\u60F3\u7684\u306A\u66F2</option>
25286
- <option value="mood_plaintive" title="\u30CF\u77ED\u8ABF\u30FB\u30DB\u77ED\u8ABF\u30FB\u30D8\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u3001\u604B\u308F\u305A\u3089\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044\u3001\u7269\u60B2\u3057\u3044\u54C0\u6101\uFF09">\u7269\u60B2\u3057\u3044\u30FB\u54C0\u6101\u306E\u66F2</option>
25287
- <option value="mood_melancholy" title="\u5909\u30CB\u9577\u8ABF\u30FB\u30ED\u77ED\u8ABF\u30FB\u5B30\u30CF\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u60B2\u3057\u307F\u3001\u6182\u9B31\u3001\u5B64\u72EC\u3001\u5FCD\u8010\u3001\u843D\u80C6\u3001\u60B2\u6D99\uFF09">\u6182\u9B31\u30FB\u5B64\u72EC\u306A\u66F2</option>
25288
- <option value="mood_anxious" title="\u30C8\u77ED\u8ABF\u30FB\u5909\u30DB\u77ED\u8ABF\u30FB\u5B30\u30D8\u77ED\u8ABF\u30FB\u5909\u30A4\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u6DF1\u3044\u82E6\u60A9\u3001\u9670\u6C17\u306A\u61A4\u308A\uFF09">\u4E0D\u5B89\u30FB\u82E6\u60A9\u306A\u66F2</option>
25289
- <option value="mood_dark" title="\u5909\u30DB\u9577\u8ABF\u30FB\u5909\u30A4\u9577\u8ABF\u30FB\u5909\u30ED\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u53B3\u3057\u3044\u611B\u3001\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6697\u95C7\u3001\u6050\u308D\u3057\u3044\u5632\u308A\uFF09">\u6697\u95C7\u30FB\u91CD\u539A\u306A\u66F2</option>
25290
- </optgroup>
25291
- <optgroup label="\u9577\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
25292
- <option value="key_C" title="\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7D14\u7C8B\u3001\u7D20\u6734\u3001\u51FA\u767A">\u30CF\u9577\u8ABF (C)</option>
25293
- <option value="key_Db" title="\u60B2\u3057\u307F\u3001\u6182\u9B31\u306A\u3001\u7518\u7F8E\u306A\u611F\u50B7">\u5909\u30CB\u9577\u8ABF (D\u266D)</option>
25294
- <option value="key_D" title="\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0">\u30CB\u9577\u8ABF (D)</option>
25295
- <option value="key_Eb" title="\u53B3\u3057\u3044\u3001\u304D\u3064\u3044\u3001\u305D\u308C\u3067\u3044\u3066\u611B\u306B\u6E80\u3061\u305F">\u5909\u30DB\u9577\u8ABF (E\u266D)</option>
25296
- <option value="key_E" title="\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u8352\u3005\u3057\u3044\u3001\u8F1D\u304B\u3057\u3044\u60C5\u71B1">\u30DB\u9577\u8ABF (E)</option>
25297
- <option value="key_F" title="\u6012\u308A\u72C2\u3063\u305F\u3001\u6C17\u6027\u306E\u8352\u3044\u3001\u4E00\u6642\u7684\u306A\u60B2\u5606">\u30D8\u9577\u8ABF (F)</option>
25298
- <option value="key_Gb" title="\u56F0\u96E3\u306E\u6253\u7834\u3001\u5B89\u5835\u306E\u305F\u3081\u606F\u3001\u51F1\u65CB">\u5909\u30C8\u9577\u8ABF (G\u266D)</option>
25299
- <option value="key_G" title="\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F">\u30C8\u9577\u8ABF (G)</option>
25300
- <option value="key_Ab" title="\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6DF1\u9060\u306A\u7791\u60F3">\u5909\u30A4\u9577\u8ABF (A\u266D)</option>
25301
- <option value="key_A" title="\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D">\u30A4\u9577\u8ABF (A)</option>
25302
- <option value="key_Bb" title="\u559C\u3070\u3057\u3044\u3001\u98A8\u5909\u308F\u308A\u306A\u3001\u967D\u6C17\u306A\u3001\u8EFD\u5FEB">\u5909\u30ED\u9577\u8ABF (B\u266D)</option>
25303
- <option value="key_B" title="\u3069\u304E\u3064\u3044\u3001\u5F37\u70C8\u306A\u3001\u8352\u3063\u307D\u3044\u3001\u731B\u70C8">\u30ED\u9577\u8ABF (B)</option>
25304
- </optgroup>
25305
- <optgroup label="\u77ED\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
25306
- <option value="key_Am" title="\u67D4\u3089\u304B\u306A\u3001\u7269\u60B2\u3057\u3044\u3001\u656C\u8654\u306A\u3001\u7D20\u6734\u306A\u54C0\u6101">\u30A4\u77ED\u8ABF (Am)</option>
25307
- <option value="key_Bbm" title="\u6050\u308D\u3057\u3044\u3001\u6697\u95C7\u3001\u5632\u308B\u3088\u3046\u306A\u3001\u4E0D\u6C17\u5473">\u5909\u30ED\u77ED\u8ABF (B\u266Dm)</option>
25308
- <option value="key_Bm" title="\u5B64\u72EC\u306A\u3001\u6182\u9B31\u306A\u3001\u5FCD\u8010\u3001\u9759\u304B\u306A\u8AE6\u5FF5">\u30ED\u77ED\u8ABF (Bm)</option>
25309
- <option value="key_Cm" title="\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u306A\u3001\u604B\u308F\u305A\u3089\u3044\u306E\u3001\u60B2\u5287\u7684">\u30CF\u77ED\u8ABF (Cm)</option>
25310
- <option value="key_Csm" title="\u843D\u80C6\u3001\u6CE3\u304D\u53EB\u3093\u3060\u3001\u60B2\u6D99\u306E\u3001\u6DF1\u3044\u5606\u304D">\u5B30\u30CF\u77ED\u8ABF (C\u266Fm)</option>
25311
- <option value="key_Dm" title="\u53B3\u7C9B\u306A\u3001\u656C\u8654\u306A\u3001\u601D\u7D22\u7684\u306A\u3001\u91CD\u539A\u306A\u7948\u308A">\u30CB\u77ED\u8ABF (Dm)</option>
25312
- <option value="key_Ebm" title="\u6DF1\u3044\u82E6\u60A9\u3001\u5B9F\u5B58\u7684\u306A\u4E0D\u5B89\u3001\u6226\u6144">\u5909\u30DB\u77ED\u8ABF (E\u266Dm)</option>
25313
- <option value="key_Em" title="\u5F31\u3005\u3057\u3044\u3001\u306A\u307E\u3081\u304B\u3057\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044">\u30DB\u77ED\u8ABF (Em)</option>
25314
- <option value="key_Fm" title="\u307C\u3093\u3084\u308A\u3057\u305F\u3001\u7269\u60B2\u3057\u3044\u3001\u3057\u3081\u3084\u304B\u306A\u3001\u846C\u9001">\u30D8\u77ED\u8ABF (Fm)</option>
25315
- <option value="key_Fsm" title="\u9670\u6C17\u306A\u3001\u6FC0\u3057\u3044\u61A4\u308A\u3001\u6697\u3044\u60C5\u5FF5">\u5B30\u30D8\u77ED\u8ABF (F\u266Fm)</option>
25316
- <option value="key_Gm" title="\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u3084\u308B\u305B\u306A\u3055\u3001\u60B2\u75DB\u306A\u53EB\u3073">\u30C8\u77ED\u8ABF (Gm)</option>
25317
- <option value="key_Abm" title="\u4E0D\u670D\u306A\u3001\u5606\u304D\u306E\u3001\u6CE3\u304D\u53EB\u3093\u3060">\u5909\u30A4\u77ED\u8ABF (A\u266Dm)</option>
25318
- </optgroup>
25319
- </select>
25320
- <span class="dtm-grow"></span>
25321
- <span class="dtm-hint" data-dtm="compose-key-hint"></span>
25322
- </div>
25323
- <div class="dtm-row" data-dtm="compose-scale-row">
25324
- <span class="dtm-label">\u97F3\u968E</span>
25325
- <select class="dtm-select" data-dtm="compose-scale" title="\u65CB\u5F8B\u304C\u4F7F\u3046\u97F3\u968E\u3092\u9078\u3073\u307E\u3059\u3002\u30D9\u30FC\u30B9\u8ABF\uFF08\u4E3B\u97F3\u306E\u9AD8\u3055\uFF09\u3068\u306F\u72EC\u7ACB\u3057\u305F\u8A2D\u5B9A\u3067\u3059">
25326
- <option value="auto" title="\u30D9\u30FC\u30B9\u8ABF\u306E\u9577\u77ED\u306B\u5408\u308F\u305B\u3066\u3001\u967D\u97F3\u968E\uFF08\u9577\u8ABF\uFF09\u304B\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\uFF09\u3092\u4F7F\u3044\u307E\u3059">\u304A\u307E\u304B\u305B\uFF08\u5F93\u6765\u3069\u304A\u308A\uFF09</option>
25327
- <option value="any" title="9\u3064\u306E\u97F3\u968E\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057\uFF08\u5168\u97F3\u968E\u304B\u3089\u62BD\u9078\uFF09</option>
25328
- <optgroup label="\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF085\u97F3\u97F3\u968E\uFF09">
25329
- <option value="yo" title="J-POP\u306E\u6A19\u6E96\u3002\u660E\u308B\u304F\u7D20\u76F4\u3067\u6B4C\u3044\u3084\u3059\u3044\u3002\u5F93\u6765\u306E\u9577\u8ABF\u3068\u540C\u3058">\u967D\u97F3\u968E\uFF08\u9577\u8ABF\u30DA\u30F3\u30BF\uFF09</option>
25330
- <option value="minyo" title="\u308F\u3089\u3079\u6B4C\u30FB\u6C11\u8B21\u306E\u97F3\u968E\u3002\u7FF3\u308A\u304C\u3042\u308B\u304C\u6697\u3059\u304E\u306A\u3044\u3002\u5F93\u6765\u306E\u77ED\u8ABF\u3068\u540C\u3058">\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\u30DA\u30F3\u30BF\uFF09</option>
25331
- <option value="ryukyu" title="\u6C96\u7E04\u97F3\u968E\u3002\u30EC\u3068\u30E9\u3092\u629C\u304D\u3001\u30D5\u30A1\u3068\u30B7\u3092\u67F1\u306B\u3059\u308B\u3002\u660E\u308B\u304F\u8DF3\u306D\u308B">\u7409\u7403\u97F3\u968E\uFF08\u6C96\u7E04\uFF09</option>
25332
- <option value="miyakobushi" title="\u300E\u3055\u304F\u3089\u3055\u304F\u3089\u300F\u306E\u97F3\u968E\u3002\u4E3B\u97F3\u306E\u3059\u3050\u4E0A\u304C\u534A\u97F3\u3067\u3001\u7FF3\u308A\u304C\u6FC3\u3044">\u90FD\u7BC0\u97F3\u968E\uFF08\u9670\u97F3\u968E\uFF09</option>
25333
- <option value="ritsu" title="\u96C5\u697D\u30FB\u58F0\u660E\u306E\u97F3\u968E\u3002\u534A\u97F3\u3092\u542B\u307E\u305A\u3001\u5E73\u3089\u3067\u8358\u91CD\u306B\u6D41\u308C\u308B">\u5F8B\u97F3\u968E\uFF08\u96C5\u697D\uFF09</option>
25334
- </optgroup>
25335
- <optgroup label="\u30C1\u30E3\u30FC\u30C1\u30E2\u30FC\u30C9\uFF087\u97F3\u97F3\u968E\uFF09">
25336
- <option value="dorian" title="\u77ED\u8ABF\u3060\u304C6\u5EA6\u304C\u660E\u308B\u3044\u3002\u30B1\u30EB\u30C8\u30FB\u30ED\u30C3\u30AF\u30FB\u30B7\u30C6\u30A3\u30DD\u30C3\u30D7">\u30C9\u30EA\u30A2\u30F3</option>
25337
- <option value="phrygian" title="\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3002\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\uFF0F\u30E1\u30BF\u30EB\u306E\u7DCA\u8FEB\u3057\u305F\u97FF\u304D">\u30D5\u30EA\u30B8\u30A2\u30F3</option>
25338
- <option value="lydian" title="4\u5EA6\u304C\u9AD8\u304F\u3001\u6D6E\u904A\u3057\u3066\u5E83\u304C\u308B\u3002\u6620\u753B\u97F3\u697D\u30FB\u30B2\u30FC\u30E0\u306E\u7A7A\u306E\u8272">\u30EA\u30C7\u30A3\u30A2\u30F3</option>
25339
- <option value="mixolydian" title="\u9577\u8ABF\u3060\u304C7\u5EA6\u304C\u4F4E\u3044\u3002\u30D6\u30EB\u30FC\u30B9\u30ED\u30C3\u30AF\u30FB\u6C11\u65CF\u97F3\u697D\u306E\u571F\u304F\u3055\u3055">\u30DF\u30AF\u30BD\u30EA\u30C7\u30A3\u30A2\u30F3</option>
25340
- </optgroup>
25341
- <optgroup label="\u7279\u6B8A\u97F3\u968E\uFF08\u97F3\u7A0B\u96C6\u5408\u3054\u3068\u5165\u308C\u66FF\u308F\u308B\uFF09">
25342
- <option value="harmonic_minor" title="\u5C0E\u97F3\u30BD\u266F\u3092\u6301\u3064\u77ED\u8ABF\u3002\u58972\u5EA6\u304C\u6CE3\u304D\u3092\u4F5C\u308B\u3002\u30AF\u30E9\u30B7\u30C3\u30AF\u30FBV\u7CFB\u30FB\u5287\u4F34">\u548C\u58F0\u7684\u77ED\u97F3\u968E</option>
25343
- <option value="hijaz" title="\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3001\u4E3B\u548C\u97F3\u306F\u9577\u4E09\u548C\u97F3\u3002\u4E2D\u6771\u30FB\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\u30FB\u30E1\u30BF\u30EB">\u30D2\u30B8\u30E3\u30FC\u30BA\uFF08\u30D5\u30EA\u30B8\u30A2\u30F3\u30FB\u30C9\u30DF\u30CA\u30F3\u30C8\uFF09</option>
25344
- <option value="hungarian" title="\u58972\u5EA6\u304C2\u304B\u6240\u3002\u97F3\u968E\u306E\u4E2D\u3067\u3044\u3061\u3070\u3093\u8DF3\u306D\u305F\u3001\u7570\u56FD\u3081\u3044\u305F\u97FF\u304D">\u30CF\u30F3\u30AC\u30EA\u30A2\u30F3\u30FB\u30DE\u30A4\u30CA\u30FC\uFF08\u30B8\u30D7\u30B7\u30FC\uFF09</option>
25345
- <option value="blues" title="\u30D6\u30EB\u30FC\u30CE\u30FC\u30C8\u5165\u308A\u306E6\u97F3\u97F3\u968E\u3002\u77ED3\u5EA6\u3067\u6B4C\u3044\u3001\u4F34\u594F\u306F\u95773\u5EA6\u3067\u9CF4\u308B">\u30D6\u30EB\u30FC\u30B9\u97F3\u968E</option>
25346
- </optgroup>
25347
- </select>
25348
- <span class="dtm-grow"></span>
25349
- <span class="dtm-hint" data-dtm="compose-scale-hint"></span>
26884
+ <div class="dtm-row" data-dtm="compose-row">
26885
+ <button class="dtm-btn dtm-btn--success" data-dtm="macro-compose" title="\u30B3\u30FC\u30C9\u9032\u884C\u30FB\u30E1\u30ED\u30C7\u30A3\u30FB\u30B5\u30D6\u30E1\u30ED\u30FB\u30D9\u30FC\u30B9\u30FB\u4F34\u594F\u30FB\u30C9\u30E9\u30E0\u3092\u81EA\u52D5\u3067\u4F5C\u308A\u307E\u3059">\u4F5C\u66F2</button>
26886
+ <button class="dtm-btn dtm-btn--success" data-dtm="macro-compose-vocal" title="\u4F5C\u66F2\u3057\u305F\u3046\u3048\u3067\u3001\u30E1\u30ED\u30C7\u30A3\u306B\u6B4C\u8A5E\u3092\u4ED8\u3051\u3066\u6B4C\u308F\u305B\u307E\u3059">\u6B4C\u5165\u308A\u4F5C\u66F2</button>
26887
+ <button class="dtm-infobtn" data-dtm="macro-compose-info" title="\u4F5C\u66F2\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
26888
+ <!--
26889
+ **\u30AD\u30FC\u30D7\u67A0\u306F1\u3064\u3060\u3051\u3002** \u81EA\u52D5\u4F5C\u66F2\u306F\u6C17\u306B\u5165\u308B\u307E\u3067\u5F15\u304D\u76F4\u3059\u4F7F\u3044\u65B9\u306B\u306A\u308B\u304C\u3001
26890
+ \u300C\u5F15\u304D\u76F4\u3059\u3068\u4ECA\u306E\u304C\u6D88\u3048\u308B\u300D\u3068\u601D\u3046\u3068\u5F15\u304D\u76F4\u305B\u306A\u304F\u306A\u308B\u3002\u53D6\u3063\u3066\u304A\u3051\u308B\u5834\u6240\u304C
26891
+ 1\u3064\u3042\u308C\u3070\u30012\u3064\u3092\u6BD4\u3079\u3066\u9078\u3076\u3053\u3068\u306F\u6210\u7ACB\u3059\u308B\u3002\u5019\u88DC\u3092\u4E26\u3079\u308BUI\u306F\u30B9\u30DE\u30DB\u3067\u306F
26892
+ \u6210\u7ACB\u3057\u306A\u3044\uFF08\u8A66\u8074\u6642\u9593\u30FB\u753B\u9762\u30FB\u751F\u6210\u30B3\u30B9\u30C8\u306E\u3069\u308C\u3082\u8DB3\u308A\u306A\u3044\uFF09\u3002
26893
+ \u300C\u5165\u308C\u66FF\u3048\u300D\u306F\u4ECA\u306E\u66F2\u3068\u30AD\u30FC\u30D7\u3092\u4EA4\u63DB\u3059\u308B\u3002\u547C\u3073\u51FA\u3059\u3060\u3051\u3060\u3068\u4ECA\u306E\u66F2\u304C\u6D88\u3048\u3066
26894
+ 2\u66F2\u3092\u884C\u304D\u6765\u3067\u304D\u306A\u3044\u305F\u3081\u3002\u67A0\u306F\u30EA\u30ED\u30FC\u30C9\u3092\u307E\u305F\u3044\u3067\u6B8B\u308B\uFF08localStorage\uFF09\u3002
26895
+ -->
26896
+ <button class="dtm-btn" data-dtm="compose-keep" title="\u4ECA\u306E\u66F2\u30921\u3064\u3060\u3051\u53D6\u3063\u3066\u304A\u304D\u307E\u3059\u3002\u4F5C\u66F2\u3092\u62BC\u3057\u76F4\u3057\u3066\u3082\u3001\u30DA\u30FC\u30B8\u3092\u958B\u304D\u76F4\u3057\u3066\u3082\u6D88\u3048\u307E\u305B\u3093">\u30AD\u30FC\u30D7</button>
26897
+ <button class="dtm-btn" data-dtm="compose-recall" title="\u30AD\u30FC\u30D7\u3057\u305F\u66F2\u3068\u4ECA\u306E\u66F2\u3092\u5165\u308C\u66FF\u3048\u3066\u3001\u30AD\u30FC\u30D7\u3057\u3066\u3044\u305F\u66F2\u3092\u9CF4\u3089\u3057\u307E\u3059\u3002\u3082\u3046\u4E00\u5EA6\u62BC\u3059\u3068\u623B\u308A\u307E\u3059" disabled>\u5165\u308C\u66FF\u3048</button>
26898
+ <span class="dtm-grow"></span>
26899
+ </div>
26900
+ <div class="dtm-row" data-dtm="compose-template-row">
26901
+ <span class="dtm-label">\u69CB\u6210</span>
26902
+ <select class="dtm-select" data-dtm="compose-template" title="J-POP\u738B\u9053\u306A\u3069\u306E\u30D7\u30EA\u30BB\u30C3\u30C8\u69CB\u6210\u3092\u9078\u3073\u307E\u3059">
26903
+ <option value="custom">\u81EA\u7531\u9078\u629E\uFF08\u4E0B\u8A18\u30C1\u30A7\u30C3\u30AF\uFF09</option>
26904
+ <option value="1chorus">1\u30B3\u30FC\u30E9\u30B9\uFF08\u77ED\u3081\u30FB\u521D\u5FC3\u8005\u5411\u3051\uFF09</option>
26905
+ <option value="jpop_standard">JPOP\u738B\u9053\uFF08\u30DE\u30EA\u30FC\u30B4\u30FC\u30EB\u30C9\u578B 2\u756A/C\u30E1\u30ED/\u30E9\u30B9\u30B5\u30D3\uFF09</option>
26906
+ <option value="jpop_drop">\u843D\u3061\u30B5\u30D3\u5165\u308A\uFF08JPOP\u738B\u9053 + \u30E9\u30B9\u30B5\u30D3\u524D\u843D\u3061\u30B5\u30D3\uFF09</option>
26907
+ <option value="vocaloid">\u30DC\u30AB\u30ED\u738B\u9053\uFF08\u75BE\u8D70\u30FB2\u756A/C\u30E1\u30ED/\u30E9\u30B9\u30B5\u30D3\uFF09</option>
26908
+ <option value="verse_chorus">Verse-Chorus\uFF08B\u30E1\u30ED\u306A\u3057\u30FB\u6D0B\u697D\u98A8\uFF09</option>
26909
+ </select>
26910
+ </div>
26911
+ <div class="dtm-row" data-dtm="compose-sections-row">
26912
+ <span class="dtm-label">\u4F5C\u308B\u90E8\u5206</span>
26913
+ <div class="dtm-checks" data-dtm="compose-sections">
26914
+ <label class="dtm-check"><input type="checkbox" value="intro" checked>\u30A4\u30F3\u30C8\u30ED</label>
26915
+ <label class="dtm-check"><input type="checkbox" value="verse" checked>A\u30E1\u30ED</label>
26916
+ <label class="dtm-check"><input type="checkbox" value="prechorus" checked>B\u30E1\u30ED</label>
26917
+ <label class="dtm-check"><input type="checkbox" value="chorus" checked>\u30B5\u30D3</label>
26918
+ <label class="dtm-check"><input type="checkbox" value="bridge">C\u30E1\u30ED</label>
26919
+ <label class="dtm-check"><input type="checkbox" value="drop_chorus">\u843D\u3061\u30B5\u30D3</label>
26920
+ <label class="dtm-check"><input type="checkbox" value="interlude">\u9593\u594F</label>
26921
+ <label class="dtm-check"><input type="checkbox" value="outro">\u30A2\u30A6\u30C8\u30ED</label>
25350
26922
  </div>
26923
+ <span class="dtm-grow"></span>
26924
+ <span class="dtm-hint" data-dtm="compose-sections-len"></span>
26925
+ </div>
26926
+ <div class="dtm-row" data-dtm="compose-key-row">
26927
+ <span class="dtm-label">\u30D9\u30FC\u30B9\u8ABF</span>
26928
+ <select class="dtm-select" data-dtm="compose-key" title="\u81EA\u52D5\u4F5C\u66F2\u306E\u30D9\u30FC\u30B9\u3068\u306A\u308B\u8ABF\u3084\u96F0\u56F2\u6C17\u3092\u9078\u3073\u307E\u3059">
26929
+ <option value="any" title="\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057</option>
26930
+ <optgroup label="\u57FA\u672C">
26931
+ <option value="major" title="12\u306E\u9577\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u9577\u8ABF</option>
26932
+ <option value="minor" title="12\u306E\u77ED\u8ABF\u306E\u4E2D\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u77ED\u8ABF</option>
26933
+ </optgroup>
26934
+ <optgroup label="\u96F0\u56F2\u6C17\u304B\u3089\u9078\u3076\uFF08\u62BD\u9078\uFF09">
26935
+ <option value="mood_happy" title="\u30CF\u9577\u8ABF\u30FB\u30A4\u9577\u8ABF\u30FB\u5909\u30ED\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7267\u6B4C\u7684\u3001\u967D\u6C17\uFF09">\u559C\u3070\u3057\u3044\u30FB\u967D\u6C17\u306A\u66F2</option>
26936
+ <option value="mood_triumphant" title="\u30CB\u9577\u8ABF\u30FB\u5909\u30C8\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u610F\u6C17\u63DA\u3005\u3001\u52DD\u5229\u306E\u558A\u58F0\u3001\u56F0\u96E3\u6253\u7834\u3001\u5B89\u5835\uFF09">\u52DD\u5229\u30FB\u529B\u5F37\u3044\u66F2</option>
26937
+ <option value="mood_fierce" title="\u30DB\u9577\u8ABF\u30FB\u30D8\u9577\u8ABF\u30FB\u30ED\u9577\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u6012\u308A\u72C2\u3063\u305F\u8352\u3005\u3057\u3055\u3001\u3069\u304E\u3064\u304F\u731B\u70C8\uFF09">\u6FC0\u3057\u3044\u30FB\u8352\u3005\u3057\u3044\u66F2</option>
26938
+ <option value="mood_solemn" title="\u30C8\u9577\u8ABF\u30FB\u30CB\u77ED\u8ABF\u30FB\u30A4\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u53B3\u7C9B\u3001\u5D07\u9AD8\u3001\u5E7B\u60F3\u3001\u656C\u8654\u3001\u601D\u7D22\u7684\uFF09">\u53B3\u7C9B\u30FB\u5E7B\u60F3\u7684\u306A\u66F2</option>
26939
+ <option value="mood_plaintive" title="\u30CF\u77ED\u8ABF\u30FB\u30DB\u77ED\u8ABF\u30FB\u30D8\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u3001\u604B\u308F\u305A\u3089\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044\u3001\u7269\u60B2\u3057\u3044\u54C0\u6101\uFF09">\u7269\u60B2\u3057\u3044\u30FB\u54C0\u6101\u306E\u66F2</option>
26940
+ <option value="mood_melancholy" title="\u5909\u30CB\u9577\u8ABF\u30FB\u30ED\u77ED\u8ABF\u30FB\u5B30\u30CF\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u60B2\u3057\u307F\u3001\u6182\u9B31\u3001\u5B64\u72EC\u3001\u5FCD\u8010\u3001\u843D\u80C6\u3001\u60B2\u6D99\uFF09">\u6182\u9B31\u30FB\u5B64\u72EC\u306A\u66F2</option>
26941
+ <option value="mood_anxious" title="\u30C8\u77ED\u8ABF\u30FB\u5909\u30DB\u77ED\u8ABF\u30FB\u5B30\u30D8\u77ED\u8ABF\u30FB\u5909\u30A4\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u6DF1\u3044\u82E6\u60A9\u3001\u9670\u6C17\u306A\u61A4\u308A\uFF09">\u4E0D\u5B89\u30FB\u82E6\u60A9\u306A\u66F2</option>
26942
+ <option value="mood_dark" title="\u5909\u30DB\u9577\u8ABF\u30FB\u5909\u30A4\u9577\u8ABF\u30FB\u5909\u30ED\u77ED\u8ABF\u304B\u3089\u62BD\u9078\uFF08\u53B3\u3057\u3044\u611B\u3001\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6697\u95C7\u3001\u6050\u308D\u3057\u3044\u5632\u308A\uFF09">\u6697\u95C7\u30FB\u91CD\u539A\u306A\u66F2</option>
26943
+ </optgroup>
26944
+ <optgroup label="\u9577\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
26945
+ <option value="key_C" title="\u7121\u57A2\u306B\u559C\u3070\u3057\u3044\u3001\u7D14\u7C8B\u3001\u7D20\u6734\u3001\u51FA\u767A">\u30CF\u9577\u8ABF (C)</option>
26946
+ <option value="key_Db" title="\u60B2\u3057\u307F\u3001\u6182\u9B31\u306A\u3001\u7518\u7F8E\u306A\u611F\u50B7">\u5909\u30CB\u9577\u8ABF (D\u266D)</option>
26947
+ <option value="key_D" title="\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0">\u30CB\u9577\u8ABF (D)</option>
26948
+ <option value="key_Eb" title="\u53B3\u3057\u3044\u3001\u304D\u3064\u3044\u3001\u305D\u308C\u3067\u3044\u3066\u611B\u306B\u6E80\u3061\u305F">\u5909\u30DB\u9577\u8ABF (E\u266D)</option>
26949
+ <option value="key_E" title="\u3051\u3093\u304B\u3063\u65E9\u3044\u3001\u8352\u3005\u3057\u3044\u3001\u8F1D\u304B\u3057\u3044\u60C5\u71B1">\u30DB\u9577\u8ABF (E)</option>
26950
+ <option value="key_F" title="\u6012\u308A\u72C2\u3063\u305F\u3001\u6C17\u6027\u306E\u8352\u3044\u3001\u4E00\u6642\u7684\u306A\u60B2\u5606">\u30D8\u9577\u8ABF (F)</option>
26951
+ <option value="key_Gb" title="\u56F0\u96E3\u306E\u6253\u7834\u3001\u5B89\u5835\u306E\u305F\u3081\u606F\u3001\u51F1\u65CB">\u5909\u30C8\u9577\u8ABF (G\u266D)</option>
26952
+ <option value="key_G" title="\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F">\u30C8\u9577\u8ABF (G)</option>
26953
+ <option value="key_Ab" title="\u6B7B\u3001\u6C38\u9060\u3001\u88C1\u304D\u3001\u6DF1\u9060\u306A\u7791\u60F3">\u5909\u30A4\u9577\u8ABF (A\u266D)</option>
26954
+ <option value="key_A" title="\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D">\u30A4\u9577\u8ABF (A)</option>
26955
+ <option value="key_Bb" title="\u559C\u3070\u3057\u3044\u3001\u98A8\u5909\u308F\u308A\u306A\u3001\u967D\u6C17\u306A\u3001\u8EFD\u5FEB">\u5909\u30ED\u9577\u8ABF (B\u266D)</option>
26956
+ <option value="key_B" title="\u3069\u304E\u3064\u3044\u3001\u5F37\u70C8\u306A\u3001\u8352\u3063\u307D\u3044\u3001\u731B\u70C8">\u30ED\u9577\u8ABF (B)</option>
26957
+ </optgroup>
26958
+ <optgroup label="\u77ED\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
26959
+ <option value="key_Am" title="\u67D4\u3089\u304B\u306A\u3001\u7269\u60B2\u3057\u3044\u3001\u656C\u8654\u306A\u3001\u7D20\u6734\u306A\u54C0\u6101">\u30A4\u77ED\u8ABF (Am)</option>
26960
+ <option value="key_Bbm" title="\u6050\u308D\u3057\u3044\u3001\u6697\u95C7\u3001\u5632\u308B\u3088\u3046\u306A\u3001\u4E0D\u6C17\u5473">\u5909\u30ED\u77ED\u8ABF (B\u266Dm)</option>
26961
+ <option value="key_Bm" title="\u5B64\u72EC\u306A\u3001\u6182\u9B31\u306A\u3001\u5FCD\u8010\u3001\u9759\u304B\u306A\u8AE6\u5FF5">\u30ED\u77ED\u8ABF (Bm)</option>
26962
+ <option value="key_Cm" title="\u7D14\u7C8B\u306B\u60B2\u3057\u3052\u306A\u3001\u604B\u308F\u305A\u3089\u3044\u306E\u3001\u60B2\u5287\u7684">\u30CF\u77ED\u8ABF (Cm)</option>
26963
+ <option value="key_Csm" title="\u843D\u80C6\u3001\u6CE3\u304D\u53EB\u3093\u3060\u3001\u60B2\u6D99\u306E\u3001\u6DF1\u3044\u5606\u304D">\u5B30\u30CF\u77ED\u8ABF (C\u266Fm)</option>
26964
+ <option value="key_Dm" title="\u53B3\u7C9B\u306A\u3001\u656C\u8654\u306A\u3001\u601D\u7D22\u7684\u306A\u3001\u91CD\u539A\u306A\u7948\u308A">\u30CB\u77ED\u8ABF (Dm)</option>
26965
+ <option value="key_Ebm" title="\u6DF1\u3044\u82E6\u60A9\u3001\u5B9F\u5B58\u7684\u306A\u4E0D\u5B89\u3001\u6226\u6144">\u5909\u30DB\u77ED\u8ABF (E\u266Dm)</option>
26966
+ <option value="key_Em" title="\u5F31\u3005\u3057\u3044\u3001\u306A\u307E\u3081\u304B\u3057\u3044\u3001\u843D\u3061\u7740\u304D\u306E\u306A\u3044">\u30DB\u77ED\u8ABF (Em)</option>
26967
+ <option value="key_Fm" title="\u307C\u3093\u3084\u308A\u3057\u305F\u3001\u7269\u60B2\u3057\u3044\u3001\u3057\u3081\u3084\u304B\u306A\u3001\u846C\u9001">\u30D8\u77ED\u8ABF (Fm)</option>
26968
+ <option value="key_Fsm" title="\u9670\u6C17\u306A\u3001\u6FC0\u3057\u3044\u61A4\u308A\u3001\u6697\u3044\u60C5\u5FF5">\u5B30\u30D8\u77ED\u8ABF (F\u266Fm)</option>
26969
+ <option value="key_Gm" title="\u4E0D\u6E80\u3001\u4E0D\u5B89\u3001\u3084\u308B\u305B\u306A\u3055\u3001\u60B2\u75DB\u306A\u53EB\u3073">\u30C8\u77ED\u8ABF (Gm)</option>
26970
+ <option value="key_Abm" title="\u4E0D\u670D\u306A\u3001\u5606\u304D\u306E\u3001\u6CE3\u304D\u53EB\u3093\u3060">\u5909\u30A4\u77ED\u8ABF (A\u266Dm)</option>
26971
+ </optgroup>
26972
+ </select>
26973
+ <span class="dtm-grow"></span>
26974
+ <span class="dtm-hint" data-dtm="compose-key-hint"></span>
26975
+ </div>
26976
+ <div class="dtm-row" data-dtm="compose-scale-row">
26977
+ <span class="dtm-label">\u97F3\u968E</span>
26978
+ <select class="dtm-select" data-dtm="compose-scale" title="\u65CB\u5F8B\u304C\u4F7F\u3046\u97F3\u968E\u3092\u9078\u3073\u307E\u3059\u3002\u30D9\u30FC\u30B9\u8ABF\uFF08\u4E3B\u97F3\u306E\u9AD8\u3055\uFF09\u3068\u306F\u72EC\u7ACB\u3057\u305F\u8A2D\u5B9A\u3067\u3059">
26979
+ <option value="auto" title="\u30D9\u30FC\u30B9\u8ABF\u306E\u9577\u77ED\u306B\u5408\u308F\u305B\u3066\u3001\u967D\u97F3\u968E\uFF08\u9577\u8ABF\uFF09\u304B\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\uFF09\u3092\u4F7F\u3044\u307E\u3059">\u304A\u307E\u304B\u305B\uFF08\u5F93\u6765\u3069\u304A\u308A\uFF09</option>
26980
+ <option value="any" title="9\u3064\u306E\u97F3\u968E\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u62BD\u9078\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057\uFF08\u5168\u97F3\u968E\u304B\u3089\u62BD\u9078\uFF09</option>
26981
+ <optgroup label="\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF085\u97F3\u97F3\u968E\uFF09">
26982
+ <option value="yo" title="J-POP\u306E\u6A19\u6E96\u3002\u660E\u308B\u304F\u7D20\u76F4\u3067\u6B4C\u3044\u3084\u3059\u3044\u3002\u5F93\u6765\u306E\u9577\u8ABF\u3068\u540C\u3058">\u967D\u97F3\u968E\uFF08\u9577\u8ABF\u30DA\u30F3\u30BF\uFF09</option>
26983
+ <option value="minyo" title="\u308F\u3089\u3079\u6B4C\u30FB\u6C11\u8B21\u306E\u97F3\u968E\u3002\u7FF3\u308A\u304C\u3042\u308B\u304C\u6697\u3059\u304E\u306A\u3044\u3002\u5F93\u6765\u306E\u77ED\u8ABF\u3068\u540C\u3058">\u6C11\u8B21\u97F3\u968E\uFF08\u77ED\u8ABF\u30DA\u30F3\u30BF\uFF09</option>
26984
+ <option value="ryukyu" title="\u6C96\u7E04\u97F3\u968E\u3002\u30EC\u3068\u30E9\u3092\u629C\u304D\u3001\u30D5\u30A1\u3068\u30B7\u3092\u67F1\u306B\u3059\u308B\u3002\u660E\u308B\u304F\u8DF3\u306D\u308B">\u7409\u7403\u97F3\u968E\uFF08\u6C96\u7E04\uFF09</option>
26985
+ <option value="miyakobushi" title="\u300E\u3055\u304F\u3089\u3055\u304F\u3089\u300F\u306E\u97F3\u968E\u3002\u4E3B\u97F3\u306E\u3059\u3050\u4E0A\u304C\u534A\u97F3\u3067\u3001\u7FF3\u308A\u304C\u6FC3\u3044">\u90FD\u7BC0\u97F3\u968E\uFF08\u9670\u97F3\u968E\uFF09</option>
26986
+ <option value="ritsu" title="\u96C5\u697D\u30FB\u58F0\u660E\u306E\u97F3\u968E\u3002\u534A\u97F3\u3092\u542B\u307E\u305A\u3001\u5E73\u3089\u3067\u8358\u91CD\u306B\u6D41\u308C\u308B">\u5F8B\u97F3\u968E\uFF08\u96C5\u697D\uFF09</option>
26987
+ </optgroup>
26988
+ <optgroup label="\u30C1\u30E3\u30FC\u30C1\u30E2\u30FC\u30C9\uFF087\u97F3\u97F3\u968E\uFF09">
26989
+ <option value="dorian" title="\u77ED\u8ABF\u3060\u304C6\u5EA6\u304C\u660E\u308B\u3044\u3002\u30B1\u30EB\u30C8\u30FB\u30ED\u30C3\u30AF\u30FB\u30B7\u30C6\u30A3\u30DD\u30C3\u30D7">\u30C9\u30EA\u30A2\u30F3</option>
26990
+ <option value="phrygian" title="\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3002\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\uFF0F\u30E1\u30BF\u30EB\u306E\u7DCA\u8FEB\u3057\u305F\u97FF\u304D">\u30D5\u30EA\u30B8\u30A2\u30F3</option>
26991
+ <option value="lydian" title="4\u5EA6\u304C\u9AD8\u304F\u3001\u6D6E\u904A\u3057\u3066\u5E83\u304C\u308B\u3002\u6620\u753B\u97F3\u697D\u30FB\u30B2\u30FC\u30E0\u306E\u7A7A\u306E\u8272">\u30EA\u30C7\u30A3\u30A2\u30F3</option>
26992
+ <option value="mixolydian" title="\u9577\u8ABF\u3060\u304C7\u5EA6\u304C\u4F4E\u3044\u3002\u30D6\u30EB\u30FC\u30B9\u30ED\u30C3\u30AF\u30FB\u6C11\u65CF\u97F3\u697D\u306E\u571F\u304F\u3055\u3055">\u30DF\u30AF\u30BD\u30EA\u30C7\u30A3\u30A2\u30F3</option>
26993
+ </optgroup>
26994
+ <optgroup label="\u7279\u6B8A\u97F3\u968E\uFF08\u97F3\u7A0B\u96C6\u5408\u3054\u3068\u5165\u308C\u66FF\u308F\u308B\uFF09">
26995
+ <option value="harmonic_minor" title="\u5C0E\u97F3\u30BD\u266F\u3092\u6301\u3064\u77ED\u8ABF\u3002\u58972\u5EA6\u304C\u6CE3\u304D\u3092\u4F5C\u308B\u3002\u30AF\u30E9\u30B7\u30C3\u30AF\u30FBV\u7CFB\u30FB\u5287\u4F34">\u548C\u58F0\u7684\u77ED\u97F3\u968E</option>
26996
+ <option value="hijaz" title="\u4E3B\u97F3\u306E\u4E0A\u304C\u534A\u97F3\u3001\u4E3B\u548C\u97F3\u306F\u9577\u4E09\u548C\u97F3\u3002\u4E2D\u6771\u30FB\u30B9\u30D1\u30CB\u30C3\u30B7\u30E5\u30FB\u30E1\u30BF\u30EB">\u30D2\u30B8\u30E3\u30FC\u30BA\uFF08\u30D5\u30EA\u30B8\u30A2\u30F3\u30FB\u30C9\u30DF\u30CA\u30F3\u30C8\uFF09</option>
26997
+ <option value="hungarian" title="\u58972\u5EA6\u304C2\u304B\u6240\u3002\u97F3\u968E\u306E\u4E2D\u3067\u3044\u3061\u3070\u3093\u8DF3\u306D\u305F\u3001\u7570\u56FD\u3081\u3044\u305F\u97FF\u304D">\u30CF\u30F3\u30AC\u30EA\u30A2\u30F3\u30FB\u30DE\u30A4\u30CA\u30FC\uFF08\u30B8\u30D7\u30B7\u30FC\uFF09</option>
26998
+ <option value="blues" title="\u30D6\u30EB\u30FC\u30CE\u30FC\u30C8\u5165\u308A\u306E6\u97F3\u97F3\u968E\u3002\u77ED3\u5EA6\u3067\u6B4C\u3044\u3001\u4F34\u594F\u306F\u95773\u5EA6\u3067\u9CF4\u308B">\u30D6\u30EB\u30FC\u30B9\u97F3\u968E</option>
26999
+ </optgroup>
27000
+ </select>
27001
+ <span class="dtm-grow"></span>
27002
+ <span class="dtm-hint" data-dtm="compose-scale-hint"></span>
25351
27003
  </div>
27004
+ </div>
27005
+ </details>
27006
+
27007
+ <details class="dtm-panel" data-dtm-acc="macro">
27008
+ <summary>\u4E00\u62EC\u7DE8\u96C6</summary>
27009
+ <div class="dtm-panel-body">
25352
27010
  <div class="dtm-row">
25353
27011
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
25354
27012
  <select class="dtm-select" data-dtm="shift-select">
@@ -25399,10 +27057,11 @@ var buildUI = (target, options) => {
25399
27057
  </details>
25400
27058
 
25401
27059
  <details class="dtm-panel" data-dtm-acc="io-out">
25402
- <summary>MIDI / UST / MML \u51FA\u529B</summary>
27060
+ <summary>MIDI / MusicXML / UST / MML \u51FA\u529B</summary>
25403
27061
  <div class="dtm-panel-body">
25404
27062
  <div class="dtm-row">
25405
27063
  <button class="dtm-btn dtm-btn--accent" data-dtm="export-midi">MIDI\u51FA\u529B</button>
27064
+ <button class="dtm-btn dtm-btn--accent" data-dtm="export-musicxml" title="\u5168\u30C8\u30E9\u30C3\u30AF\u3092MusicXML\uFF08\u697D\u8B5C\uFF09\u5F62\u5F0F\u3067\u66F8\u304D\u51FA\u3057\u307E\u3059">MusicXML\u51FA\u529B</button>
25406
27065
  <button class="dtm-btn dtm-btn--accent" data-dtm="export-ust" title="\u73FE\u5728\u9078\u629E\u4E2D\u306E\u30C8\u30E9\u30C3\u30AF\u3060\u3051\u3092UST\u3067\u66F8\u304D\u51FA\u3057\u307E\u3059">UST\u51FA\u529B</button>
25407
27066
  <button class="dtm-btn dtm-btn--success" data-dtm="generate-mml">MML\u751F\u6210</button>
25408
27067
  <button class="dtm-btn dtm-btn--primary dtm-hidden" data-dtm="export-wav">WAV\u66F8\u304D\u51FA\u3057</button>
@@ -25452,6 +27111,7 @@ var buildUI = (target, options) => {
25452
27111
  </div>
25453
27112
  </div>
25454
27113
  </details>
27114
+ </div>
25455
27115
 
25456
27116
  <!-- \u2550\u2550\u2550\u2550 \u89E3\u8AAC\u30E2\u30FC\u30C0\u30EB \u2550\u2550\u2550\u2550 -->
25457
27117
  <div class="dtm-modal-overlay" data-dtm="modal-overlay" hidden>
@@ -25558,6 +27218,11 @@ var buildUI = (target, options) => {
25558
27218
  midiTrackSelection: sel("midi-track-selection"),
25559
27219
  midiPanel: sel("midi-panel"),
25560
27220
  midiSearchOpenBtn: sel("midi-search-open"),
27221
+ musicXmlInput: sel("musicxml-input"),
27222
+ musicXmlLoadBtn: sel("musicxml-load"),
27223
+ musicXmlInfoBtn: sel("musicxml-info"),
27224
+ musicXmlPartSelection: sel("musicxml-part-selection"),
27225
+ musicXmlLoadNote: sel("musicxml-load-note"),
25561
27226
  ustInput: sel("ust-input"),
25562
27227
  ustLoadBtn: sel("ust-load"),
25563
27228
  ustInfoBtn: sel("ust-info"),
@@ -25589,6 +27254,7 @@ var buildUI = (target, options) => {
25589
27254
  macroHarmonic: sel("macro-harmonic"),
25590
27255
  macroMono: sel("macro-mono"),
25591
27256
  exportMidiBtn: sel("export-midi"),
27257
+ exportMusicXmlBtn: sel("export-musicxml"),
25592
27258
  exportUstBtn: sel("export-ust"),
25593
27259
  exportWavBtn: sel("export-wav"),
25594
27260
  drumJsonExportBtn: sel("drum-json-export"),
@@ -25657,6 +27323,32 @@ var readMacroSections = () => {
25657
27323
  var writeMacroSections = (sections) => {
25658
27324
  writeMacroSetting("sections", JSON.stringify(sections));
25659
27325
  };
27326
+ var KEPT_STORAGE_KEY = "dtm-macro:kept";
27327
+ var readKeptSong = () => {
27328
+ try {
27329
+ if (typeof localStorage === "undefined" || !localStorage) return null;
27330
+ const raw = localStorage.getItem(KEPT_STORAGE_KEY);
27331
+ if (!raw) return null;
27332
+ const parsed = JSON.parse(raw);
27333
+ if (parsed && typeof parsed === "object" && typeof parsed.mml === "string" && parsed.mml.length > 0) {
27334
+ const startStep = typeof parsed.startStep === "number" && Number.isFinite(parsed.startStep) && parsed.startStep >= 0 ? Math.floor(parsed.startStep) : 0;
27335
+ return { mml: parsed.mml, startStep };
27336
+ }
27337
+ } catch (_) {
27338
+ }
27339
+ return null;
27340
+ };
27341
+ var writeKeptSong = (kept) => {
27342
+ try {
27343
+ if (typeof localStorage === "undefined" || !localStorage) return;
27344
+ if (kept === null) {
27345
+ localStorage.removeItem(KEPT_STORAGE_KEY);
27346
+ return;
27347
+ }
27348
+ localStorage.setItem(KEPT_STORAGE_KEY, JSON.stringify(kept));
27349
+ } catch (_) {
27350
+ }
27351
+ };
25660
27352
 
25661
27353
  // src/macros.ts
25662
27354
  var SCALES = [
@@ -25803,7 +27495,7 @@ var transposeNotes = (cores, steps) => {
25803
27495
  };
25804
27496
 
25805
27497
  // src/version.ts
25806
- var DTM_VERSION = "2.1.12";
27498
+ var DTM_VERSION = "2.1.14";
25807
27499
 
25808
27500
  // src/midi-io.ts
25809
27501
  var STEPS_PER_BEAT3 = 48;
@@ -26970,6 +28662,275 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
26970
28662
  return chordNotes / notes.length >= threshold;
26971
28663
  };
26972
28664
 
28665
+ // src/musicxml-io.ts
28666
+ var STEPS_PER_BEAT4 = 48;
28667
+ var PITCH_CLASS = {
28668
+ C: 0,
28669
+ D: 2,
28670
+ E: 4,
28671
+ F: 5,
28672
+ G: 7,
28673
+ A: 9,
28674
+ B: 11
28675
+ };
28676
+ var textOf = (el, tag) => el?.getElementsByTagName(tag)[0]?.textContent?.trim() ?? "";
28677
+ var numOf = (el, tag, fallback = 0) => {
28678
+ const raw = textOf(el, tag);
28679
+ if (!raw) return fallback;
28680
+ const v = Number.parseFloat(raw);
28681
+ return Number.isFinite(v) ? v : fallback;
28682
+ };
28683
+ var pitchOf = (note) => {
28684
+ const p = note.getElementsByTagName("pitch")[0];
28685
+ if (!p) return null;
28686
+ const step = textOf(p, "step").toUpperCase();
28687
+ const base = PITCH_CLASS[step];
28688
+ if (base === void 0) return null;
28689
+ const octave = numOf(p, "octave", 4);
28690
+ const alter = Math.round(numOf(p, "alter", 0));
28691
+ return (octave + 1) * 12 + base + alter;
28692
+ };
28693
+ var parseMusicXML = (xml) => {
28694
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
28695
+ if (doc.getElementsByTagName("parserror").length > 0)
28696
+ throw new Error("MusicXML \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093");
28697
+ const nameById = /* @__PURE__ */ new Map();
28698
+ for (const sp of Array.from(doc.getElementsByTagName("score-part"))) {
28699
+ const id = sp.getAttribute("id");
28700
+ if (id) nameById.set(id, textOf(sp, "part-name") || id);
28701
+ }
28702
+ const placements = [];
28703
+ const parts = [];
28704
+ let bpm = 0;
28705
+ const partEls = Array.from(doc.getElementsByTagName("part"));
28706
+ partEls.forEach((partEl, partIndex) => {
28707
+ const id = partEl.getAttribute("id") ?? "";
28708
+ let divisions = 1;
28709
+ let cursor = 0;
28710
+ let measureStart = 0;
28711
+ let lastStart = 0;
28712
+ let lastDurSteps = 0;
28713
+ const tied = /* @__PURE__ */ new Map();
28714
+ let count = 0;
28715
+ let pitchSum = 0;
28716
+ let lyricSeen = false;
28717
+ for (const measure of Array.from(partEl.getElementsByTagName("measure"))) {
28718
+ measureStart += cursor;
28719
+ cursor = 0;
28720
+ for (const child of Array.from(measure.children)) {
28721
+ const tag = child.tagName.toLowerCase();
28722
+ if (tag === "attributes") {
28723
+ const d = numOf(child, "divisions", 0);
28724
+ if (d > 0) divisions = d;
28725
+ continue;
28726
+ }
28727
+ if (tag === "direction") {
28728
+ const sound = child.getElementsByTagName("sound")[0];
28729
+ const t = Number.parseFloat(sound?.getAttribute("tempo") ?? "");
28730
+ if (!bpm && Number.isFinite(t) && t > 0) bpm = t;
28731
+ continue;
28732
+ }
28733
+ if (tag === "backup" || tag === "forward") {
28734
+ const steps = numOf(child, "duration", 0) / divisions * STEPS_PER_BEAT4;
28735
+ cursor += tag === "backup" ? -steps : steps;
28736
+ continue;
28737
+ }
28738
+ if (tag !== "note") continue;
28739
+ const durSteps = Math.round(
28740
+ numOf(child, "duration", 0) / divisions * STEPS_PER_BEAT4
28741
+ );
28742
+ const isChord = child.getElementsByTagName("chord").length > 0;
28743
+ const isRest = child.getElementsByTagName("rest").length > 0;
28744
+ const start = isChord ? lastStart : measureStart + cursor;
28745
+ if (!isRest) {
28746
+ const pitch = pitchOf(child);
28747
+ if (pitch !== null) {
28748
+ const lyric = textOf(
28749
+ child.getElementsByTagName("lyric")[0],
28750
+ "text"
28751
+ );
28752
+ if (lyric) lyricSeen = true;
28753
+ const ties = Array.from(child.getElementsByTagName("tie"));
28754
+ const stops = ties.some((t) => t.getAttribute("type") === "stop");
28755
+ const starts = ties.some((t) => t.getAttribute("type") === "start");
28756
+ const held = tied.get(pitch);
28757
+ if (stops && held) {
28758
+ held.durationSteps += durSteps;
28759
+ if (!starts) tied.delete(pitch);
28760
+ } else {
28761
+ const placed = {
28762
+ partIndex,
28763
+ startStep: Math.max(0, Math.round(start)),
28764
+ pitch,
28765
+ durationSteps: Math.max(1, durSteps),
28766
+ lyric
28767
+ };
28768
+ placements.push(placed);
28769
+ count++;
28770
+ pitchSum += pitch;
28771
+ if (starts) tied.set(pitch, placed);
28772
+ }
28773
+ }
28774
+ }
28775
+ if (!isChord) {
28776
+ lastStart = start;
28777
+ lastDurSteps = durSteps;
28778
+ cursor += durSteps;
28779
+ } else {
28780
+ lastDurSteps = Math.max(lastDurSteps, durSteps);
28781
+ }
28782
+ }
28783
+ }
28784
+ parts.push({
28785
+ index: partIndex,
28786
+ name: nameById.get(id) || `Part ${partIndex + 1}`,
28787
+ noteCount: count,
28788
+ hasLyrics: lyricSeen,
28789
+ avgPitch: count > 0 ? pitchSum / count : 0
28790
+ });
28791
+ });
28792
+ return { parts, placements, bpm: bpm > 0 ? Math.round(bpm) : 120 };
28793
+ };
28794
+ var SHARP_SPELLING = [
28795
+ ["C", 0],
28796
+ ["C", 1],
28797
+ ["D", 0],
28798
+ ["D", 1],
28799
+ ["E", 0],
28800
+ ["F", 0],
28801
+ ["F", 1],
28802
+ ["G", 0],
28803
+ ["G", 1],
28804
+ ["A", 0],
28805
+ ["A", 1],
28806
+ ["B", 0]
28807
+ ];
28808
+ var NOTE_TYPES = [
28809
+ [STEPS_PER_BEAT4 * 4, "whole"],
28810
+ [STEPS_PER_BEAT4 * 2, "half"],
28811
+ [STEPS_PER_BEAT4, "quarter"],
28812
+ [STEPS_PER_BEAT4 / 2, "eighth"],
28813
+ [STEPS_PER_BEAT4 / 4, "16th"],
28814
+ [STEPS_PER_BEAT4 / 8, "32nd"]
28815
+ ];
28816
+ var typeOf = (steps) => {
28817
+ for (const [len, type] of NOTE_TYPES) {
28818
+ if (steps >= len) {
28819
+ const dots = steps >= len * 1.5 && steps < len * 2 ? 1 : 0;
28820
+ return { type, dots };
28821
+ }
28822
+ }
28823
+ return { type: "32nd", dots: 0 };
28824
+ };
28825
+ var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
28826
+ var exportMusicXML = (options) => {
28827
+ const { parts, bpm, stepsPerBar, title } = options;
28828
+ const beatsPerBar = Math.max(1, Math.round(stepsPerBar / STEPS_PER_BEAT4));
28829
+ const partList = parts.map(
28830
+ (p, i2) => ` <score-part id="P${i2 + 1}">
28831
+ <part-name>${esc(p.name)}</part-name>
28832
+ </score-part>`
28833
+ ).join("\n");
28834
+ const body = parts.map((part, pi) => {
28835
+ const notes = [...part.notes].sort((a, b) => a.startStep - b.startStep);
28836
+ const end = notes.reduce(
28837
+ (m, n) => Math.max(m, n.startStep + n.durationSteps),
28838
+ 0
28839
+ );
28840
+ const bars = Math.max(1, Math.ceil(end / stepsPerBar));
28841
+ const lines = [` <part id="P${pi + 1}">`];
28842
+ let li = 0;
28843
+ for (let bar = 0; bar < bars; bar++) {
28844
+ const from = bar * stepsPerBar;
28845
+ const to = from + stepsPerBar;
28846
+ lines.push(` <measure number="${bar + 1}">`);
28847
+ if (bar === 0) {
28848
+ lines.push(
28849
+ ` <attributes>
28850
+ <divisions>${STEPS_PER_BEAT4}</divisions>`,
28851
+ ` <key><fifths>0</fifths></key>`,
28852
+ ` <time><beats>${beatsPerBar}</beats><beat-type>4</beat-type></time>`,
28853
+ ` <clef><sign>G</sign><line>2</line></clef>
28854
+ </attributes>`
28855
+ );
28856
+ if (pi === 0)
28857
+ lines.push(
28858
+ ` <direction placement="above"><direction-type><metronome><beat-unit>quarter</beat-unit><per-minute>${Math.round(bpm)}</per-minute></metronome></direction-type><sound tempo="${Math.round(bpm)}"/></direction>`
28859
+ );
28860
+ }
28861
+ let cursor = from;
28862
+ const inBar = notes.filter(
28863
+ (n) => n.startStep >= from && n.startStep < to
28864
+ );
28865
+ const groups = /* @__PURE__ */ new Map();
28866
+ for (const n of inBar) {
28867
+ const g = groups.get(n.startStep);
28868
+ if (g) g.push(n);
28869
+ else groups.set(n.startStep, [n]);
28870
+ }
28871
+ const starts = [...groups.keys()].sort((a, b) => a - b);
28872
+ for (let gi = 0; gi < starts.length; gi++) {
28873
+ const at = starts[gi];
28874
+ const group = groups.get(at);
28875
+ if (at > cursor) {
28876
+ const { type, dots } = typeOf(at - cursor);
28877
+ lines.push(
28878
+ ` <note><rest/><duration>${at - cursor}</duration><type>${type}</type>${"<dot/>".repeat(dots)}</note>`
28879
+ );
28880
+ cursor = at;
28881
+ }
28882
+ const nextAt = gi + 1 < starts.length ? starts[gi + 1] : to;
28883
+ const room = Math.min(to, nextAt) - at;
28884
+ const dur = Math.max(
28885
+ 1,
28886
+ Math.min(Math.max(...group.map((n) => n.durationSteps)), room)
28887
+ );
28888
+ group.forEach((n, ni) => {
28889
+ const semi = Math.round(n.pitchUnits / UNITS_PER_SEMITONE);
28890
+ const [step, alter] = SHARP_SPELLING[(semi % 12 + 12) % 12];
28891
+ const octave = Math.floor(semi / 12) - 1;
28892
+ const { type, dots } = typeOf(dur);
28893
+ const lyric = part.lyrics?.[li];
28894
+ lines.push(
28895
+ ` <note>${ni > 0 ? "<chord/>" : ""}<pitch><step>${step}</step>${alter ? `<alter>${alter}</alter>` : ""}<octave>${octave}</octave></pitch><duration>${dur}</duration><type>${type}</type>${"<dot/>".repeat(dots)}` + (ni === 0 && lyric ? `<lyric><syllabic>single</syllabic><text>${esc(lyric)}</text></lyric>` : "") + `</note>`
28896
+ );
28897
+ if (ni === 0) li++;
28898
+ });
28899
+ cursor = at + dur;
28900
+ }
28901
+ if (cursor < to) {
28902
+ const { type, dots } = typeOf(to - cursor);
28903
+ lines.push(
28904
+ ` <note><rest/><duration>${to - cursor}</duration><type>${type}</type>${"<dot/>".repeat(dots)}</note>`
28905
+ );
28906
+ }
28907
+ lines.push(" </measure>");
28908
+ }
28909
+ lines.push(" </part>");
28910
+ return lines.join("\n");
28911
+ }).join("\n");
28912
+ return [
28913
+ '<?xml version="1.0" encoding="UTF-8"?>',
28914
+ '<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">',
28915
+ '<score-partwise version="4.0">',
28916
+ ` <work><work-title>${esc(title ?? "Untitled")}</work-title></work>`,
28917
+ // 書き出し元を残す。MML・MIDI と揃えてある(`src/version.ts` に理由)。
28918
+ ` <identification><encoding><software>dtm ${DTM_VERSION}</software></encoding></identification>`,
28919
+ " <part-list>",
28920
+ partList,
28921
+ " </part-list>",
28922
+ body,
28923
+ "</score-partwise>",
28924
+ ""
28925
+ ].join("\n");
28926
+ };
28927
+ var musicXmlToNotes = (placements, partIndex) => placements.filter((p) => p.partIndex === partIndex).map((p) => ({
28928
+ startStep: p.startStep,
28929
+ durationSteps: p.durationSteps,
28930
+ pitchUnits: p.pitch * UNITS_PER_SEMITONE,
28931
+ velocity: 100
28932
+ }));
28933
+
26973
28934
  // src/renderer.ts
26974
28935
  var KEYBOARD_WIDTH = 60;
26975
28936
  var HEADER_HEIGHT = 20;
@@ -27298,6 +29259,76 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
27298
29259
  }
27299
29260
  }
27300
29261
  };
29262
+ const drawSpeechSpans = (spans, color = [255, 241, 232]) => {
29263
+ if (spans.length === 0) return;
29264
+ const {
29265
+ keyHeight,
29266
+ stepWidth,
29267
+ keyCount,
29268
+ pitchRangeStart,
29269
+ unitsPerRow: upr = UNITS_PER_SEMITONE
29270
+ } = g_config;
29271
+ const [r, g, b] = color;
29272
+ g_grid_ctx.save();
29273
+ for (const { note, durationSteps } of spans) {
29274
+ if (durationSteps <= 0) continue;
29275
+ const x2 = note.startStep * stepWidth - g_draw_offset_x;
29276
+ const y = (keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr) * keyHeight - g_draw_offset_y;
29277
+ const w = durationSteps * stepWidth;
29278
+ if (x2 + w < 0 || x2 > g_grid_canvas.width) continue;
29279
+ if (y + keyHeight < 0 || y > g_grid_canvas.height) continue;
29280
+ const h = Math.max(2, Math.floor(keyHeight * 0.35));
29281
+ const top = y + keyHeight - h - 1;
29282
+ g_grid_ctx.fillStyle = `rgba(${r},${g},${b},0.28)`;
29283
+ g_grid_ctx.fillRect(x2 + 1, top, w - 2, h);
29284
+ g_grid_ctx.strokeStyle = `rgba(${r},${g},${b},0.75)`;
29285
+ g_grid_ctx.lineWidth = 1;
29286
+ g_grid_ctx.setLineDash([3, 2]);
29287
+ g_grid_ctx.strokeRect(x2 + 1.5, top + 0.5, w - 3, h - 1);
29288
+ g_grid_ctx.setLineDash([]);
29289
+ g_grid_ctx.beginPath();
29290
+ g_grid_ctx.moveTo(x2 + w - 1.5, y + 1);
29291
+ g_grid_ctx.lineTo(x2 + w - 1.5, y + keyHeight - 1);
29292
+ g_grid_ctx.stroke();
29293
+ }
29294
+ g_grid_ctx.restore();
29295
+ };
29296
+ const drawPitchGuide = (pitchUnits, label) => {
29297
+ const {
29298
+ keyHeight,
29299
+ keyCount,
29300
+ pitchRangeStart,
29301
+ unitsPerRow: upr = UNITS_PER_SEMITONE
29302
+ } = g_config;
29303
+ const row = Math.round((pitchUnits - pitchRangeStart) / upr);
29304
+ if (row < 0 || row >= keyCount) return;
29305
+ const y = (keyCount - 1 - row) * keyHeight - g_draw_offset_y;
29306
+ if (y + keyHeight < 0 || y > g_grid_canvas.height) return;
29307
+ g_grid_ctx.save();
29308
+ g_grid_ctx.fillStyle = "rgba(255,236,39,0.10)";
29309
+ g_grid_ctx.fillRect(0, y, g_grid_canvas.width, keyHeight);
29310
+ g_grid_ctx.strokeStyle = "rgba(255,236,39,0.55)";
29311
+ g_grid_ctx.lineWidth = 1;
29312
+ g_grid_ctx.setLineDash([6, 4]);
29313
+ g_grid_ctx.beginPath();
29314
+ g_grid_ctx.moveTo(0, y + keyHeight / 2 + 0.5);
29315
+ g_grid_ctx.lineTo(g_grid_canvas.width, y + keyHeight / 2 + 0.5);
29316
+ g_grid_ctx.stroke();
29317
+ if (keyHeight >= 7) {
29318
+ const fontSize = Math.min(11, Math.floor(keyHeight * 0.85));
29319
+ g_grid_ctx.setLineDash([]);
29320
+ g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
29321
+ g_grid_ctx.textAlign = "left";
29322
+ g_grid_ctx.textBaseline = "middle";
29323
+ g_grid_ctx.lineWidth = 3;
29324
+ g_grid_ctx.lineJoin = "round";
29325
+ g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
29326
+ g_grid_ctx.strokeText(label, 4, y + keyHeight / 2);
29327
+ g_grid_ctx.fillStyle = "#ffec27";
29328
+ g_grid_ctx.fillText(label, 4, y + keyHeight / 2);
29329
+ }
29330
+ g_grid_ctx.restore();
29331
+ };
27301
29332
  const drawNoteLyrics = (notes, syllables) => {
27302
29333
  if (syllables.length === 0) return;
27303
29334
  const {
@@ -27447,6 +29478,8 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
27447
29478
  drawGrid,
27448
29479
  drawNotes,
27449
29480
  drawNoteLyrics,
29481
+ drawSpeechSpans,
29482
+ drawPitchGuide,
27450
29483
  drawSelectionRect,
27451
29484
  drawSelectedNotes,
27452
29485
  getXY,
@@ -28045,7 +30078,7 @@ var VOWEL_KANA2 = {
28045
30078
  o: "\u304A",
28046
30079
  \u00FC: "\u3086"
28047
30080
  };
28048
- var SMALL_KANA2 = {
30081
+ var SMALL_KANA3 = {
28049
30082
  a: "\u3083",
28050
30083
  u: "\u3085",
28051
30084
  e: "\u3047",
@@ -28117,7 +30150,7 @@ var pinyinToMoras = (raw) => {
28117
30150
  const zero = initial === "" && medial === "i";
28118
30151
  const head = zero ? ZERO_PALATAL[nucleus] : onset[medial];
28119
30152
  if (!head) return null;
28120
- const small = SMALL_KANA2[nucleus];
30153
+ const small = SMALL_KANA3[nucleus];
28121
30154
  if (zero) moras.push(head);
28122
30155
  else if (head.length === 1 && small) moras.push(head + small);
28123
30156
  else moras.push(head, VOWEL_KANA2[nucleus]);
@@ -28127,10 +30160,10 @@ var pinyinToMoras = (raw) => {
28127
30160
  };
28128
30161
 
28129
30162
  // src/ust-io.ts
28130
- var STEPS_PER_BEAT4 = 48;
30163
+ var STEPS_PER_BEAT5 = 48;
28131
30164
  var UST_TICKS_PER_BEAT = 480;
28132
- var TICKS_TO_STEPS = STEPS_PER_BEAT4 / UST_TICKS_PER_BEAT;
28133
- var STEPS_TO_TICKS = UST_TICKS_PER_BEAT / STEPS_PER_BEAT4;
30165
+ var TICKS_TO_STEPS = STEPS_PER_BEAT5 / UST_TICKS_PER_BEAT;
30166
+ var STEPS_TO_TICKS = UST_TICKS_PER_BEAT / STEPS_PER_BEAT5;
28134
30167
  var MORA_TAIL_MAX_STEPS = 12;
28135
30168
  var UST_CONTINUE_LYRICS = ["+", "+~", "+-", "+*", "*", "\u30FC", "-"];
28136
30169
  var TIE = "\u30FC";
@@ -28213,12 +30246,12 @@ var ROMAJI_KANA = (() => {
28213
30246
  return map;
28214
30247
  })();
28215
30248
  var KANA_HEAD_RE = /^[ぁ-ゖァ-ヶー〜]+/;
28216
- var SMALL_KANA3 = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087";
30249
+ var SMALL_KANA4 = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087";
28217
30250
  var splitMoras = (kana) => {
28218
30251
  const moras = [];
28219
30252
  for (const ch of kana) {
28220
30253
  const last = moras.length - 1;
28221
- if (last >= 0 && SMALL_KANA3.includes(ch)) moras[last] += ch;
30254
+ if (last >= 0 && SMALL_KANA4.includes(ch)) moras[last] += ch;
28222
30255
  else moras.push(ch);
28223
30256
  }
28224
30257
  return moras;
@@ -28403,7 +30436,7 @@ var parseUst = (source, name = "") => {
28403
30436
  const syllables = [];
28404
30437
  let unknownLyricCount = 0;
28405
30438
  let hasVoiced = false;
28406
- const msPerStep = 6e4 / (bpm ?? 120) / STEPS_PER_BEAT4;
30439
+ const msPerStep = 6e4 / (bpm ?? 120) / STEPS_PER_BEAT5;
28407
30440
  for (const fields of sections) {
28408
30441
  const length = Number.parseFloat(fields.length ?? "");
28409
30442
  if (!Number.isFinite(length) || length <= 0) continue;
@@ -28499,6 +30532,7 @@ var ustLyricOf = (kana) => {
28499
30532
  if (kana === "") return DEFAULT_UST_LYRIC;
28500
30533
  if (kana === TIE || kana === "\u301C") return "+";
28501
30534
  if (kana === REST) return "R";
30535
+ if (kana.startsWith("\u300C")) return "R";
28502
30536
  return kana;
28503
30537
  };
28504
30538
  var UST_ENVELOPE_ATTACK_MS = 5;
@@ -28642,7 +30676,9 @@ var buildUst = (options) => {
28642
30676
  cursorTick = startTick + length;
28643
30677
  }
28644
30678
  assignFadeEnvelopes(out);
28645
- out.forEach((note, index) => lines.push(...ustNoteSection(index, note)));
30679
+ for (const [index, note] of out.entries()) {
30680
+ lines.push(...ustNoteSection(index, note));
30681
+ }
28646
30682
  lines.push("[#TRACKEND]");
28647
30683
  return `${lines.join("\r\n")}\r
28648
30684
  `;
@@ -28696,8 +30732,22 @@ c d e f g \u2190 \u97F3\u7B265\u3064</pre>
28696
30732
  <li><code>\u3001</code> <strong>\u30D6\u30EC\u30B9</strong>\uFF08\u8AAD\u70B9\uFF09\u2026 <strong>\u97F3\u7B26\u306F\u6D88\u8CBB\u305B\u305A</strong>\u3001\u76F4\u524D\u306E\u97F3\u3092\u5C11\u3057\u77ED\u304F\u3057\u3066\u606F\u7D99\u304E\u3092\u5165\u308C\u307E\u3059\u3002</li>
28697
30733
  <li><code>\u2193</code> <strong>\u3060\u3093\u3060\u3093\u5C0F\u3055\u304F</strong>\uFF08\u4E0B\u5411\u304D\u77E2\u5370\uFF09\u2026 <strong>\u97F3\u7B26\u306F\u6D88\u8CBB\u305B\u305A</strong>\u3001\u305D\u306E\u97F3\u3092\u6B4C\u3063\u305F\u307E\u307E\u58F0\u91CF\u3092\u843D\u3068\u3057\u307E\u3059\u3002\u8907\u6570\u66F8\u304F\u3068\u6E1B\u308A\u65B9\u3092\u523B\u3081\u307E\u3059\u3002</li>
28698
30734
  <li><code>\u2191</code> <strong>\u3060\u3093\u3060\u3093\u5927\u304D\u304F</strong>\uFF08\u4E0A\u5411\u304D\u77E2\u5370\uFF09\u2026 <code>\u2193</code> \u306E\u5BFE\u3002\u5C0F\u3055\u304F\u5165\u3063\u3066\u58F0\u91CF\u3092\u4E0A\u3052\u3066\u3044\u304D\u307E\u3059\u3002</li>
30735
+ <li><code>\u300C\u2026\u300D</code> <strong>\u8A9E\u308A</strong>\uFF08\u304B\u304E\u62EC\u5F27\uFF09\u2026 \u56F2\u3093\u3060\u90E8\u5206\u3092\u6B4C\u308F\u305A\u306B<strong>\u8AAD\u307F\u4E0A\u3052</strong>\u307E\u3059\u3002\u62EC\u5F27\u3072\u3068\u304B\u305F\u307E\u308A\u3067\u97F3\u7B26\u30921\u3064\u6D88\u8CBB\u3057\u3001\u305D\u306E\u97F3\u7B26\u306E\u4F4D\u7F6E\u304B\u3089\u8A71\u3057\u59CB\u3081\u307E\u3059\u3002</li>
30736
+ </ul>
30737
+ <p><small>\u5168\u89D2\u30C1\u30EB\u30C0 <code>\uFF5E</code> \u306F <code>\u301C</code>\u3001\u534A\u89D2\u30AB\u30F3\u30DE <code>,</code> \u306F <code>\u3001</code>\u3001<code>\u21E9</code> <code>\u2B07</code> \u306F <code>\u2193</code>\u3001<code>\u21E7</code> <code>\u2B06</code> \u306F <code>\u2191</code> \u3068\u3057\u3066\u6271\u308F\u308C\u307E\u3059\u3002\u3053\u308C\u3089\u4EE5\u5916\u306E\u8A18\u53F7\uFF08\u82F1\u6570\u5B57\u30FB\u30B9\u30DA\u30FC\u30B9\u30FB\u53E5\u8AAD\u70B9\u306A\u3069\uFF09\u306F\u7121\u8996\u3055\u308C\u307E\u3059\uFF08<code>\u300C\u2026\u300D</code> \u306E\u4E2D\u3060\u3051\u306F\u4F8B\u5916\u3067\u3001\u305D\u306E\u307E\u307E\u8AAD\u307F\u4E0A\u3052\u306B\u6E21\u3055\u308C\u307E\u3059\uFF09\u3002</small></p>
30738
+
30739
+ <h4>\u6B4C\u306E\u4E2D\u3067\u8A9E\u308B\uFF08<code>\u300C\u2026\u300D</code>\uFF09</h4>
30740
+ <p>\u6B4C\u8A5E\u306E\u9014\u4E2D\u306B <code>\u300C\u2026\u300D</code> \u3092\u66F8\u304F\u3068\u3001\u305D\u306E\u90E8\u5206\u3060\u3051\u3092<strong>\u8A71\u3057\u58F0\u3067\u8AAD\u307F\u4E0A\u3052</strong>\u307E\u3059\uFF08UTAU\u97F3\u6E90\u306E\u307F\u3002klatt \u3067\u306F\u9CF4\u308A\u307E\u305B\u3093\uFF09\u3002\u4E2D\u8EAB\u306F\u6F22\u5B57\u30FB\u6570\u5B57\u30FB\u53E5\u8AAD\u70B9\u3092\u542B\u3093\u3067\u304B\u307E\u3044\u307E\u305B\u3093\u2014\u2014\u8AAD\u307F\u3068\u30A2\u30AF\u30BB\u30F3\u30C8\u306F\u8F9E\u66F8\u304C\u6C7A\u3081\u307E\u3059\u3002<code>\uFF1F</code> \u3067\u7D42\u308F\u308B\u3068\u8A9E\u5C3E\u304C\u4E0A\u304C\u308A\u307E\u3059\u3002</p>
30741
+ <pre>\u3075\u3064\u3046\u306B\u3046\u305F\u3046\u300C\u304B\u305F\u308B\u3070\u3057\u3087\u300D\u3075\u3064\u3046\u306B\u3046\u305F\u3046
30742
+ \u3069\u3093\u3050\u308A\u3053\u308D\u3053\u308D\u300C\u307F\u306A\u3055\u3093\u3001\u3053\u3093\u306B\u3061\u306F\uFF01\u300D\u3069\u3093\u3050\u308A\u3053
30743
+ \u300C\u30BB\u30EA\u30D5\u3060\u3051\u306E\u30C8\u30E9\u30C3\u30AF\u3082\u4F5C\u308C\u307E\u3059\u3002\u300D</pre>
30744
+ <ul>
30745
+ <li><strong>\u97F3\u7B26\u306E\u4F4D\u7F6E</strong>\u304C\u8A71\u3057\u59CB\u3081\u3067\u3059\u3002<strong>\u9577\u3055\u306F\u8AAD\u307F\u4E0A\u3052\u304C\u6C7A\u3081\u308B</strong>\u306E\u3067\u3001\u97F3\u7B26\u306E\u9577\u3055\u306F\u4F7F\u3044\u307E\u305B\u3093\u3002\u30D4\u30A2\u30CE\u30ED\u30FC\u30EB\u306B\u306F\u5B9F\u969B\u306B\u558B\u308B\u9577\u3055\u304C\u7834\u7DDA\u306E\u5E2F\u3067\u51FA\u308B\u306E\u3067\u3001\u6B21\u306E\u6B4C\u3044\u51FA\u3057\u3068\u91CD\u306A\u3089\u306A\u3044\u304B\u78BA\u304B\u3081\u3089\u308C\u307E\u3059\u3002</li>
30746
+ <li><strong>\u97F3\u7B26\u306E\u9AD8\u3055</strong>\u306F\u8A71\u3059\u58F0\u306E\u9AD8\u3055\u3067\u3059\u3002\u9EC4\u8272\u3044\u300C\u8A9E\u308A\u306E\u57FA\u6E96\u300D\u306E\u884C\u306B\u7F6E\u304F\u3068\u97F3\u6E90\u306E\u7D20\u306E\u58F0\u3001\u4E0A\u306B\u7F6E\u304F\u307B\u3069\u9AD8\u3044\u58F0\u306B\u306A\u308A\u307E\u3059\uFF08\u76EE\u5B89\u306F\xB17\u534A\u97F3\u307E\u3067\uFF09\u3002</li>
30747
+ <li>\u7A7A\u306E <code>\u300C\u300D</code> \u306F <code>_</code> \u3068\u540C\u3058\uFF08\u97F3\u7B26\u30921\u3064\u6D88\u8CBB\u3057\u3066\u7121\u97F3\uFF09\u3002\u9589\u3058\u62EC\u5F27\u304C\u7121\u3051\u308C\u3070\u884C\u672B\u307E\u3067\u304C\u8A9E\u308A\u306B\u306A\u308A\u307E\u3059\u3002</li>
30748
+ <li>\u4E2D\u8EAB\u306B <code>;</code> \u3068\u6539\u884C\u306F\u66F8\u3051\u307E\u305B\u3093\uFF08MML\u306E\u533A\u5207\u308A\u6587\u5B57\u306E\u305F\u3081\uFF09\u3002</li>
30749
+ <li>\u521D\u3081\u3066\u8A9E\u308A\u3092\u9CF4\u3089\u3059\u3068\u304D\u306F\u8F9E\u66F8\u3068\u97F3\u58F0\u30E2\u30C7\u30EB\uFF08\u7D0445MB\uFF09\u3092\u53D6\u5F97\u3059\u308B\u306E\u3067\u3001\u518D\u751F\u958B\u59CB\u307E\u3067\u5C11\u3057\u5F85\u3061\u307E\u3059\u30022\u56DE\u76EE\u4EE5\u964D\u306F\u4FDD\u5B58\u6E08\u307F\u306E\u3082\u306E\u3092\u4F7F\u3044\u307E\u3059\u3002</li>
28699
30750
  </ul>
28700
- <p><small>\u5168\u89D2\u30C1\u30EB\u30C0 <code>\uFF5E</code> \u306F <code>\u301C</code>\u3001\u534A\u89D2\u30AB\u30F3\u30DE <code>,</code> \u306F <code>\u3001</code>\u3001<code>\u21E9</code> <code>\u2B07</code> \u306F <code>\u2193</code>\u3001<code>\u21E7</code> <code>\u2B06</code> \u306F <code>\u2191</code> \u3068\u3057\u3066\u6271\u308F\u308C\u307E\u3059\u3002\u3053\u308C\u3089\u4EE5\u5916\u306E\u8A18\u53F7\uFF08\u82F1\u6570\u5B57\u30FB\u30B9\u30DA\u30FC\u30B9\u30FB\u53E5\u8AAD\u70B9\u306A\u3069\uFF09\u306F\u7121\u8996\u3055\u308C\u307E\u3059\u3002</small></p>
28701
30751
 
28702
30752
  <h4>\u5F37\u5F31\u3092\u3064\u3051\u308B\uFF08<code>\u2193</code> <code>\u2191</code>\uFF09</h4>
28703
30753
  <p>\u4F38\u3070\u3057\u305F\u97F3\u306B\u4ED8\u3051\u308B\u3068\u3001<strong>\u8A00\u3044\u76F4\u3055\u305A\u306B\u4F38\u3070\u3057\u305F\u307E\u307E</strong>\u58F0\u91CF\u3060\u3051\u304C\u52D5\u304D\u307E\u3059\u3002\u66F2\u306E\u7D42\u308F\u308A\u306E\u4F59\u97FB\u3084\u3001\u30B5\u30D3\u3078\u306E\u76DB\u308A\u4E0A\u3052\u306B\u4F7F\u3044\u307E\u3059\u3002</p>
@@ -29084,6 +31134,27 @@ var MIDI_INFO_HTML = `
29084
31134
  <p>UTAU\u306EUST\u30D5\u30A1\u30A4\u30EB\u306F\u3001\u97F3\u7B26\u3068\u6B4C\u8A5E\u3092\u307E\u3068\u3081\u3066\u76F4\u63A5\u8AAD\u307F\u8FBC\u3081\u307E\u3059\uFF08MIDI\u3078\u66F8\u304D\u51FA\u3059\u5FC5\u8981\u306F\u3042\u308A\u307E\u305B\u3093\uFF09\u3002\u4E0B\u306E\u300CUST\u300D\u6B04\u3092\u304A\u4F7F\u3044\u304F\u3060\u3055\u3044\u3002</p>
29085
31135
  </div>
29086
31136
  `;
31137
+ var MUSICXML_INFO_HTML = `
31138
+ <div class="dtm-modal-body-content">
31139
+ <h4>1. MusicXML\u3068\u306F</h4>
31140
+ <p>MuseScore\u3001Finale\u3001Sibelius\u306A\u3069\u306E\u697D\u8B5C\u4F5C\u6210\u30BD\u30D5\u30C8\u3067\u5E83\u304F\u4F7F\u308F\u308C\u3066\u3044\u308B\u697D\u8B5C\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u3067\u3059\u3002MIDI\u3068\u9055\u3063\u3066<strong>\u30D1\u30FC\u30C8\u69CB\u6210\u3084\u97F3\u7B26\u306B\u7D10\u3065\u3044\u305F\u6B4C\u8A5E</strong>\u3092\u4FDD\u6301\u3067\u304D\u308B\u305F\u3081\u3001\u4E3B\u65CB\u5F8B\u306E\u7279\u5B9A\u3084\u6B4C\u58F0\u5408\u6210\u3078\u306E\u53D7\u3051\u6E21\u3057\u304C\u78BA\u5B9F\u306B\u884C\u3048\u307E\u3059\u3002</p>
31141
+
31142
+ <h4>2. \u8AAD\u307F\u8FBC\u307F\u306E\u3057\u304B\u305F</h4>
31143
+ <ul>
31144
+ <li>\u300C\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u629E\u300D\u304B\u3089 <code>.musicxml</code> \u307E\u305F\u306F <code>.xml</code> \u30D5\u30A1\u30A4\u30EB\u3092\u9078\u3073\u307E\u3059\u3002</li>
31145
+ <li>\u30D5\u30A1\u30A4\u30EB\u5185\u306B\u542B\u307E\u308C\u308B\u30D1\u30FC\u30C8\u4E00\u89A7\u304C\u8868\u793A\u3055\u308C\u308B\u306E\u3067\u3001\u8AAD\u307F\u8FBC\u307F\u305F\u3044\u30D1\u30FC\u30C8\u3092\u9078\u629E\u3057\u3066\u300C\u8AAD\u8FBC\u300D\u3092\u62BC\u3057\u307E\u3059\u3002</li>
31146
+ <li>\u6B4C\u8A5E\u304C\u542B\u307E\u308C\u3066\u3044\u308B\u30D1\u30FC\u30C8\u306F\u81EA\u52D5\u7684\u306B\u6B4C\u58F0\u5408\u6210\uFF08UTAU\uFF09\u30C8\u30E9\u30C3\u30AF\u3068\u3057\u3066\u8A2D\u5B9A\u3055\u308C\u3001\u6B4C\u8A5E\u3082\u6D41\u3057\u8FBC\u307E\u308C\u307E\u3059\u3002</li>
31147
+ <li>\u30C6\u30F3\u30DD\uFF08BPM\uFF09\u60C5\u5831\u304C\u3042\u308B\u5834\u5408\u306F\u81EA\u52D5\u7684\u306B\u53CD\u6620\u3055\u308C\u307E\u3059\u3002</li>
31148
+ <li>\u300C\u73FE\u5728\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u307F\u5BFE\u8C61\u3068\u3059\u308B\u300D\u304C\u6709\u52B9\u306A\u3068\u304D\u306F\u3001\u9078\u629E\u3057\u305F\u6700\u521D\u306E\u30D1\u30FC\u30C8\u3060\u3051\u304C\u73FE\u5728\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u30C8\u30E9\u30C3\u30AF\u306B\u8AAD\u307F\u8FBC\u307E\u308C\u307E\u3059\u3002</li>
31149
+ </ul>
31150
+
31151
+ <h4>3. \u66F8\u304D\u51FA\u3057</h4>
31152
+ <ul>
31153
+ <li>\u300CMIDI / MusicXML / UST / MML \u51FA\u529B\u300D\u306E\u300CMusicXML\u51FA\u529B\u300D\u3067\u3001\u5168\u30C8\u30E9\u30C3\u30AF\u3092 <code>score-partwise</code> \u5F62\u5F0F\u306E MusicXML \u30D5\u30A1\u30A4\u30EB\u3068\u3057\u3066\u66F8\u304D\u51FA\u305B\u307E\u3059\u3002</li>
31154
+ <li>\u97F3\u7B26\u3084\u30C8\u30E9\u30C3\u30AF\u540D\u306B\u52A0\u3048\u3066\u3001\u8A2D\u5B9A\u3055\u308C\u305F\u6B4C\u8A5E\u3082\u305D\u306E\u307E\u307E\u697D\u8B5C\u306E\u6B4C\u8A5E\u3068\u3057\u3066\u51FA\u529B\u3055\u308C\u307E\u3059\u3002</li>
31155
+ </ul>
31156
+ </div>
31157
+ `;
29087
31158
  var UST_INFO_HTML = `
29088
31159
  <div class="dtm-modal-body-content">
29089
31160
  <h4>1. UST\u30D5\u30A1\u30A4\u30EB\u3068\u306F</h4>
@@ -29225,6 +31296,7 @@ var HELP_INFO_HTML = `
29225
31296
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="koe">\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0(.koe)</button>
29226
31297
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="chord">\u30B3\u30FC\u30C9\u9032\u884C</button>
29227
31298
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="midi">MIDI\u306E\u8AAD\u307F\u8FBC\u307F</button>
31299
+ <button class="dtm-btn dtm-btn--ghost" data-dtm-help="musicxml">MusicXML</button>
29228
31300
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="ust">UST(UTAU)</button>
29229
31301
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="mml">MML\u306E\u66F8\u304D\u65B9</button>
29230
31302
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="loop">\u30EB\u30FC\u30D7\u518D\u751F</button>
@@ -29243,6 +31315,7 @@ var HELP_TOPICS = {
29243
31315
  koe: { title: "\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0(.koe)\u306E\u4F7F\u3044\u65B9", html: KOE_INFO_HTML },
29244
31316
  chord: { title: "\u30B3\u30FC\u30C9\u9032\u884C\u306E\u81EA\u52D5\u5165\u529B\u89E3\u8AAC", html: CHORD_INFO_HTML2 },
29245
31317
  midi: { title: "MIDI\u306E\u8AAD\u307F\u8FBC\u307F\u89E3\u8AAC", html: MIDI_INFO_HTML },
31318
+ musicxml: { title: "MusicXML\u306E\u5165\u51FA\u529B\u89E3\u8AAC", html: MUSICXML_INFO_HTML },
29246
31319
  ust: { title: "UST\u306E\u8AAD\u307F\u8FBC\u307F\u89E3\u8AAC", html: UST_INFO_HTML },
29247
31320
  mml: { title: "MML\u306E\u66F8\u304D\u65B9\u89E3\u8AAC", html: MML_INFO_HTML },
29248
31321
  loop: { title: "\u30EB\u30FC\u30D7\u518D\u751F\u306E\u89E3\u8AAC", html: LOOP_INFO_HTML },
@@ -29697,6 +31770,8 @@ var mountDAW = (target, options = {}) => {
29697
31770
  };
29698
31771
  let currentInstrument = "";
29699
31772
  let autoComposeInstrument = null;
31773
+ let composeSeed = null;
31774
+ let composeSetting = null;
29700
31775
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
29701
31776
  let trackFxAdvancedOpen = readPanelOpen("track-fx-advanced") ?? false;
29702
31777
  let lyricAdvancedOpen = readPanelOpen("lyric-advanced") ?? false;
@@ -29939,17 +32014,66 @@ var mountDAW = (target, options = {}) => {
29939
32014
  const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
29940
32015
  let lyricKanaCacheText = "";
29941
32016
  let lyricKanaCache = [];
29942
- const getActiveLyricKana = () => {
32017
+ let lyricSyllableCache = [];
32018
+ const getActiveLyricSyllables = () => {
29943
32019
  const t = getActive();
29944
32020
  if (!t?.lyricModel.trim()) return [];
29945
32021
  const text = t.lyrics.trim();
29946
32022
  if (!text) return [];
29947
32023
  if (text !== lyricKanaCacheText) {
29948
32024
  lyricKanaCacheText = text;
29949
- lyricKanaCache = normalizeLyrics(text).map(displayKana);
32025
+ lyricSyllableCache = normalizeLyrics(text);
32026
+ lyricKanaCache = lyricSyllableCache.map(displayKana);
29950
32027
  }
32028
+ return lyricSyllableCache;
32029
+ };
32030
+ const getActiveLyricKana = () => {
32031
+ getActiveLyricSyllables();
29951
32032
  return lyricKanaCache;
29952
32033
  };
32034
+ const speechPreviewRequested = /* @__PURE__ */ new Set();
32035
+ const requestSpeechPreview = (model, text) => {
32036
+ const voices = options.singingVoices;
32037
+ if (!voices?.planSpeech) return;
32038
+ const key = `${model}|${text}`;
32039
+ if (speechPreviewRequested.has(key)) return;
32040
+ speechPreviewRequested.add(key);
32041
+ void voices.planSpeech(model, text).then((d) => {
32042
+ if (d !== null) redrawAll();
32043
+ }).catch(() => {
32044
+ });
32045
+ };
32046
+ const drawSpeechPreview = (notes) => {
32047
+ const voices = options.singingVoices;
32048
+ if (!voices) return;
32049
+ const syllables = getActiveLyricSyllables();
32050
+ if (!syllables.some((s) => s.kind === "speak")) return;
32051
+ const model = getActive().lyricModel.trim().toLowerCase();
32052
+ const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
32053
+ const count = Math.min(sorted.length, syllables.length);
32054
+ const secondsPerStep = 60 / bpm / 48;
32055
+ const spans = [];
32056
+ for (let i2 = 0; i2 < count; i2++) {
32057
+ const syl = syllables[i2];
32058
+ if (syl.kind !== "speak" || !syl.text) continue;
32059
+ const d = voices.peekSpeechDurationSec?.(model, syl.text);
32060
+ if (d === void 0) {
32061
+ requestSpeechPreview(model, syl.text);
32062
+ continue;
32063
+ }
32064
+ spans.push({ note: sorted[i2], durationSteps: d / secondsPerStep });
32065
+ }
32066
+ renderer.drawSpeechSpans(spans, getActive().config.color);
32067
+ };
32068
+ const drawSpeechPitchGuide = () => {
32069
+ const voices = options.singingVoices;
32070
+ if (!voices?.getSpeechReferenceUnits) return;
32071
+ if (!getActiveLyricSyllables().some((s) => s.kind === "speak")) return;
32072
+ const ref = voices.getSpeechReferenceUnits(
32073
+ getActive().lyricModel.trim().toLowerCase()
32074
+ );
32075
+ if (ref !== void 0) renderer.drawPitchGuide(ref, "\u8A9E\u308A\u306E\u57FA\u6E96");
32076
+ };
29953
32077
  let showModal;
29954
32078
  let showConfirm;
29955
32079
  const playableNotes = (t) => {
@@ -30034,6 +32158,7 @@ var mountDAW = (target, options = {}) => {
30034
32158
  };
30035
32159
  const redrawAll = () => {
30036
32160
  renderer.drawGrid(gridLineSteps);
32161
+ drawSpeechPitchGuide();
30037
32162
  let lyricTargetNotes = null;
30038
32163
  let activeTrackState = null;
30039
32164
  for (const t of trackStates) {
@@ -30082,8 +32207,10 @@ var mountDAW = (target, options = {}) => {
30082
32207
  1
30083
32208
  ]);
30084
32209
  }
30085
- if (lyricTargetNotes)
32210
+ if (lyricTargetNotes) {
32211
+ drawSpeechPreview(lyricTargetNotes);
30086
32212
  renderer.drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
32213
+ }
30087
32214
  drawStartLine();
30088
32215
  if (playbackState === "playing") drawPlayhead();
30089
32216
  updateScrollbars();
@@ -31688,9 +33815,9 @@ var mountDAW = (target, options = {}) => {
31688
33815
  </details>
31689
33816
  <div class="dtm-row">
31690
33817
  <span class="dtm-label dtm-grow">\u6B4C\u8A5E</span>
31691
- <button class="dtm-infobtn" data-dtm="lyric-input-info" title="\u6B4C\u8A5E\u306E\u66F8\u304D\u65B9\uFF08\u30FC\u30FB\u301C\u30FB\u3063\u30FB_\u30FB\u3001\u306E\u610F\u5473\uFF09">${icon("info", 12)}</button>
33818
+ <button class="dtm-infobtn" data-dtm="lyric-input-info" title="\u6B4C\u8A5E\u306E\u66F8\u304D\u65B9\uFF08\u30FC\u30FB\u301C\u30FB\u3063\u30FB_\u30FB\u3001\u30FB\u300C\u300D\u306E\u610F\u5473\uFF09">${icon("info", 12)}</button>
31692
33819
  </div>
31693
- <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&#10;\u30FC=\u4F38\u3070\u3059 \u301C=\u3057\u3083\u304F\u308A \u3063=\u8A70\u307E\u308B _=\u6B4C\u308F\u306A\u3044 \u3001=\u30D6\u30EC\u30B9"></textarea>
33820
+ <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&#10;\u30FC=\u4F38\u3070\u3059 \u301C=\u3057\u3083\u304F\u308A \u3063=\u8A70\u307E\u308B _=\u6B4C\u308F\u306A\u3044 \u3001=\u30D6\u30EC\u30B9 \u300C\u2026\u300D=\u8A9E\u308A\uFF08\u8AAD\u307F\u4E0A\u3052\uFF09"></textarea>
31694
33821
  </div>`;
31695
33822
  refs.trackBody.appendChild(lyricDiv);
31696
33823
  lyricDiv.querySelector(
@@ -31890,8 +34017,10 @@ var mountDAW = (target, options = {}) => {
31890
34017
  lyricVibrato.checked = active.vocalVibrato;
31891
34018
  lyricOctaveUnison.value = active.vocalOctaveUnison;
31892
34019
  const updateLyricCount = () => {
31893
- const n = normalizeLyrics(lyricInput.value).length;
31894
- lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
34020
+ const syllables = normalizeLyrics(lyricInput.value);
34021
+ const n = syllables.length;
34022
+ const speak = syllables.filter((s) => s.kind === "speak").length;
34023
+ lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0${speak > 0 ? `\uFF08\u8A9E\u308A${speak}\uFF09` : ""}` : "";
31895
34024
  };
31896
34025
  const syncLyricTerms = () => {
31897
34026
  if (customVocalsMap.has(active.lyricModel)) {
@@ -32203,6 +34332,8 @@ var mountDAW = (target, options = {}) => {
32203
34332
  mode,
32204
34333
  edo: renderConfig.edo,
32205
34334
  loop: loopEnabled ? true : void 0,
34335
+ seed: composeSeed ?? void 0,
34336
+ compose: composeSetting ?? void 0,
32206
34337
  // アップロードされたファイルは url が空=音源関連の宣言ごと出力されない
32207
34338
  audio: backing.url || void 0,
32208
34339
  audioStart: backing.rangeStartSec || void 0,
@@ -32242,6 +34373,8 @@ var mountDAW = (target, options = {}) => {
32242
34373
  mode,
32243
34374
  edo: renderConfig.edo,
32244
34375
  loop: loopEnabled ? true : void 0,
34376
+ seed: composeSeed ?? void 0,
34377
+ compose: composeSetting ?? void 0,
32245
34378
  // アップロードされたファイルは url が空=音源関連の宣言ごと出力されない
32246
34379
  audio: backing.url || void 0,
32247
34380
  audioStart: backing.rangeStartSec || void 0,
@@ -32298,7 +34431,8 @@ var mountDAW = (target, options = {}) => {
32298
34431
  const lyricLines = trackStates.map((t, i2) => ({
32299
34432
  i: i2,
32300
34433
  notes: clipNotes(t.core.getNotes()),
32301
- text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
34434
+ // `;` MML の区切り文字なので、語り(「…」)の本文に紛れていても全角へ逃がす。
34435
+ text: t.lyrics.replace(/[\r\n]+/g, " ").replace(/;/g, "\uFF1B").trim(),
32302
34436
  model: t.lyricModel.trim(),
32303
34437
  vol: t.vocalVolume,
32304
34438
  gate: t.vocalGate,
@@ -32441,6 +34575,8 @@ var mountDAW = (target, options = {}) => {
32441
34575
  clampTrackCount: trackStates.length
32442
34576
  });
32443
34577
  if (!applyActiveOnly) {
34578
+ composeSeed = meta.seed ?? null;
34579
+ composeSetting = meta.compose ?? null;
32444
34580
  if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
32445
34581
  currentInstrument = meta.instrument;
32446
34582
  options.onInstrumentChange?.(meta.instrument);
@@ -32830,6 +34966,92 @@ var mountDAW = (target, options = {}) => {
32830
34966
  dropped: ustTracks.length - targets.length
32831
34967
  };
32832
34968
  };
34969
+ const applyMusicXmlSelection = (xml, selectedIndices, startIndex) => {
34970
+ stop();
34971
+ const applyActiveOnly = refs.applyActiveOnly?.checked ?? false;
34972
+ const from = Math.max(
34973
+ 0,
34974
+ startIndex ?? (applyActiveOnly ? activeTrackIndexOf() : selectedIndices.length > 1 ? 0 : activeTrackIndexOf())
34975
+ );
34976
+ const maxCount = applyActiveOnly ? 1 : trackStates.length - from;
34977
+ const targetIndices = selectedIndices.slice(0, Math.max(0, maxCount));
34978
+ const voice = trackStates[from]?.lyricModel || pickComposeVocal();
34979
+ targetIndices.forEach((partIndex, i2) => {
34980
+ const t = trackStates[from + i2];
34981
+ if (!t) return;
34982
+ const notes = musicXmlToNotes(xml.placements, partIndex);
34983
+ t.core.setLoadMode(true);
34984
+ t.core.clearNotesWithoutHistory();
34985
+ for (const n of notes) {
34986
+ t.core.addNote(n.startStep, snapToEdoGrid(n.pitchUnits), {
34987
+ noteLengthSteps: n.durationSteps,
34988
+ velocity: n.velocity
34989
+ });
34990
+ }
34991
+ t.core.setLoadMode(false);
34992
+ t.core.addHistoryOnce();
34993
+ const partPlacements = xml.placements.filter((p) => p.partIndex === partIndex).sort((a, b) => a.startStep - b.startStep);
34994
+ const hasLyrics = partPlacements.some(
34995
+ (p) => Boolean(p.lyric && p.lyric.trim())
34996
+ );
34997
+ if (hasLyrics) {
34998
+ const syllables = [];
34999
+ for (const p of partPlacements) {
35000
+ const l = p.lyric?.trim();
35001
+ syllables.push(l || "\u30FC");
35002
+ }
35003
+ t.lyrics = syllables.join("");
35004
+ t.vocalVolume = DEFAULT_VOCAL_VOLUME;
35005
+ t.vocalGate = 100;
35006
+ t.vocalPan = 64;
35007
+ t.vocalOctave = 0;
35008
+ t.vocalVibrato = false;
35009
+ t.vocalReverb = 0;
35010
+ t.vocalDelay = 0;
35011
+ t.vocalGender = 50;
35012
+ t.vocalBreathiness = 50;
35013
+ t.vocalTension = 50;
35014
+ t.vocalOctaveUnison = "none";
35015
+ if (!t.lyricModel) t.lyricModel = voice;
35016
+ fireLyricsChange(t);
35017
+ } else {
35018
+ t.lyrics = "";
35019
+ t.lyricModel = "";
35020
+ }
35021
+ });
35022
+ if (xml.bpm > 0) setBpm(Math.round(xml.bpm));
35023
+ playStartStep = 0;
35024
+ currentOffsetX = 0;
35025
+ const firstPitch = getFirstDetectedPitch();
35026
+ centerPitch(firstPitch ?? pitchV1ToUnits(60));
35027
+ redrawAll();
35028
+ updateTrackPanel();
35029
+ updateUndoRedo();
35030
+ return {
35031
+ applied: targetIndices.length,
35032
+ dropped: selectedIndices.length - targetIndices.length
35033
+ };
35034
+ };
35035
+ const exportMusicXML2 = () => {
35036
+ const parts = trackStates.map((t) => {
35037
+ const notes = playableNotes(t);
35038
+ const syllables = t.lyrics ? normalizeLyrics(t.lyrics).map(displayKana) : void 0;
35039
+ return {
35040
+ name: t.config.name,
35041
+ notes,
35042
+ lyrics: syllables
35043
+ };
35044
+ });
35045
+ const xml = exportMusicXML({
35046
+ parts,
35047
+ bpm,
35048
+ stepsPerBar: renderConfig.stepsPerBar,
35049
+ title: "DTM Project"
35050
+ });
35051
+ return new Blob([xml], {
35052
+ type: "application/vnd.recordare.musicxml+xml;charset=utf-8"
35053
+ });
35054
+ };
32833
35055
  const exportUST = () => {
32834
35056
  const t = getActive();
32835
35057
  const syllables = normalizeLyrics(t.lyrics).map(displayKana);
@@ -33509,17 +35731,29 @@ var mountDAW = (target, options = {}) => {
33509
35731
  stop();
33510
35732
  overlayDuring(() => {
33511
35733
  const tmpl = selectedComposeTemplate();
35734
+ const sections = selectedComposeSections();
35735
+ const baseKey = refs.composeKey?.value ?? "any";
35736
+ const scale = refs.composeScale?.value ?? "auto";
35737
+ const seed = Math.random() * 4294967296 >>> 0;
33512
35738
  const song = composeSong({
33513
35739
  stepsPerBar: renderConfig.stepsPerBar,
33514
35740
  edo: renderConfig.edo,
33515
- sections: selectedComposeSections(),
35741
+ sections,
33516
35742
  template: tmpl,
33517
- baseKey: refs.composeKey?.value ?? "any",
33518
- scale: refs.composeScale?.value ?? "auto",
35743
+ baseKey,
35744
+ scale,
35745
+ random: seededRandom(seed),
33519
35746
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
33520
35747
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
33521
35748
  recent: recentComposeFingerprints
33522
35749
  });
35750
+ composeSeed = seed;
35751
+ composeSetting = [
35752
+ tmpl ?? "custom",
35753
+ baseKey,
35754
+ scale,
35755
+ sections.join("-")
35756
+ ].join(":");
33523
35757
  recentComposeFingerprints.push(song.stats.fingerprint);
33524
35758
  if (recentComposeFingerprints.length > 5)
33525
35759
  recentComposeFingerprints.shift();
@@ -33700,6 +35934,7 @@ var mountDAW = (target, options = {}) => {
33700
35934
  updateTrackPanel();
33701
35935
  updateUndoRedo();
33702
35936
  composedSignature = trackSignature();
35937
+ void play();
33703
35938
  });
33704
35939
  };
33705
35940
  const composeWithConfirm = (withVocal) => {
@@ -33719,28 +35954,47 @@ var mountDAW = (target, options = {}) => {
33719
35954
  refs.macroCompose.addEventListener("click", () => {
33720
35955
  composeWithConfirm(false);
33721
35956
  });
33722
- let keptMml = null;
35957
+ let kept = readKeptSong();
33723
35958
  const updateKeepUI = () => {
33724
- refs.composeRecall.disabled = keptMml === null;
33725
- refs.composeKeep.textContent = keptMml === null ? "\u30AD\u30FC\u30D7" : "\u30AD\u30FC\u30D7\u6E08";
35959
+ refs.composeRecall.disabled = kept === null;
35960
+ refs.composeKeep.textContent = kept === null ? "\u30AD\u30FC\u30D7" : "\u30AD\u30FC\u30D7\u6E08";
33726
35961
  };
35962
+ const snapshotKept = () => ({
35963
+ // 小節数の上限は共有リンクの文字数のためのもので、アプリ内の退避には
35964
+ // 効かせない(効かせると長い曲がキープした時点で切れる)。
35965
+ mml: generateMML({ ignoreBarLimit: true }).full,
35966
+ startStep: playStartStep
35967
+ });
33727
35968
  refs.composeKeep.addEventListener("click", () => {
33728
35969
  const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
33729
35970
  if (!hasNotes) return;
33730
- keptMml = generateMML({ ignoreBarLimit: true }).full;
35971
+ kept = snapshotKept();
35972
+ writeKeptSong(kept);
33731
35973
  updateKeepUI();
33732
35974
  });
33733
- refs.composeRecall.addEventListener("click", () => {
33734
- if (keptMml === null) return;
35975
+ const loadKept = (song) => {
33735
35976
  const box = refs.applyActiveOnly;
33736
35977
  const was = box?.checked ?? false;
33737
35978
  if (box) box.checked = false;
33738
35979
  try {
33739
- loadMML(keptMml);
35980
+ loadMML(song.mml);
33740
35981
  } finally {
33741
35982
  if (box) box.checked = was;
33742
35983
  }
35984
+ playStartStep = Math.min(song.startStep, getMaxNoteStep());
33743
35985
  composedSignature = null;
35986
+ void play();
35987
+ };
35988
+ refs.composeRecall.addEventListener("click", () => {
35989
+ if (kept === null) return;
35990
+ const previous = kept;
35991
+ const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
35992
+ if (hasNotes) {
35993
+ kept = snapshotKept();
35994
+ writeKeptSong(kept);
35995
+ }
35996
+ loadKept(previous);
35997
+ updateKeepUI();
33744
35998
  });
33745
35999
  updateKeepUI();
33746
36000
  refs.macroComposeVocal.addEventListener("click", () => {
@@ -33799,6 +36053,9 @@ var mountDAW = (target, options = {}) => {
33799
36053
  refs.exportMidiBtn.addEventListener("click", () => {
33800
36054
  download(exportMIDI2(), "dtm.mid");
33801
36055
  });
36056
+ refs.exportMusicXmlBtn.addEventListener("click", () => {
36057
+ download(exportMusicXML2(), "dtm.musicxml");
36058
+ });
33802
36059
  refs.exportUstBtn.addEventListener("click", () => {
33803
36060
  const name = getActive().config.name.replace(/[/:*?"<>|\s]+/g, "_");
33804
36061
  download(exportUST(), `dtm_${name}.ust`);
@@ -34066,6 +36323,7 @@ var mountDAW = (target, options = {}) => {
34066
36323
  });
34067
36324
  if (showMidi) {
34068
36325
  wireMidi();
36326
+ wireMusicXml();
34069
36327
  wireUst();
34070
36328
  }
34071
36329
  if (showAudio) {
@@ -34166,6 +36424,125 @@ var mountDAW = (target, options = {}) => {
34166
36424
  overlayDuring(() => applyMidiSelection(pendingMidi, selected));
34167
36425
  });
34168
36426
  };
36427
+ let pendingMusicXml = null;
36428
+ const setMusicXmlNote = (text) => {
36429
+ refs.musicXmlLoadNote.textContent = text;
36430
+ refs.musicXmlLoadNote.classList.toggle("dtm-hidden", text === "");
36431
+ };
36432
+ const getSelectedMusicXmlPartIndices = () => {
36433
+ if (!pendingMusicXml) return [];
36434
+ const selected = [];
36435
+ const btns = refs.musicXmlPartSelection.querySelectorAll("button");
36436
+ btns.forEach((b, i2) => {
36437
+ if (b.dataset.selected === "true") {
36438
+ selected.push(pendingMusicXml.parts[i2].index);
36439
+ }
36440
+ });
36441
+ return selected;
36442
+ };
36443
+ const updateMusicXmlAssignmentNote = () => {
36444
+ if (!pendingMusicXml) return;
36445
+ const selected = getSelectedMusicXmlPartIndices();
36446
+ if (selected.length === 0) {
36447
+ setMusicXmlNote("\u8AAD\u307F\u8FBC\u3080\u30D1\u30FC\u30C8\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044");
36448
+ return;
36449
+ }
36450
+ const applyActiveOnly = refs.applyActiveOnly?.checked ?? false;
36451
+ const from = applyActiveOnly ? activeTrackIndexOf() : selected.length > 1 ? 0 : activeTrackIndexOf();
36452
+ const maxCount = applyActiveOnly ? 1 : trackStates.length - from;
36453
+ const appliedCount = Math.min(selected.length, maxCount);
36454
+ const selectedParts = selected.map((idx) => pendingMusicXml.parts.find((p) => p.index === idx)).filter(Boolean);
36455
+ const names = selectedParts.slice(0, appliedCount).map(
36456
+ (p, i2) => `${trackStates[from + i2]?.config.name ?? `\u30C8\u30E9\u30C3\u30AF${from + i2 + 1}`}\u2190${p.name}`
36457
+ ).join(" / ");
36458
+ const dropped = selected.length - appliedCount;
36459
+ const dropReason = applyActiveOnly ? "\u300C\u73FE\u5728\u306E\u30C8\u30E9\u30C3\u30AF\u306E\u307F\u300D\u304C\u6709\u52B9\u306A\u305F\u3081" : "\u30C8\u30E9\u30C3\u30AF\u4E0D\u8DB3\u306E\u305F\u3081";
36460
+ setMusicXmlNote(
36461
+ dropped > 0 ? `${names}\uFF08${dropReason}${dropped}\u30D1\u30FC\u30C8\u306F\u8AAD\u307F\u8FBC\u307F\u307E\u305B\u3093\uFF09` : names
36462
+ );
36463
+ };
36464
+ const wireMusicXml = () => {
36465
+ refs.musicXmlInfoBtn.addEventListener("click", () => {
36466
+ showModal("MusicXML\u306E\u5165\u51FA\u529B\u89E3\u8AAC", MUSICXML_INFO_HTML);
36467
+ });
36468
+ refs.musicXmlInput.addEventListener("change", async () => {
36469
+ const file = refs.musicXmlInput.files?.[0];
36470
+ pendingMusicXml = null;
36471
+ refs.musicXmlPartSelection.innerHTML = "";
36472
+ if (!file) {
36473
+ setMusicXmlNote("");
36474
+ refs.musicXmlPartSelection.classList.add("dtm-hidden");
36475
+ return;
36476
+ }
36477
+ refs.overlay.hidden = false;
36478
+ setLoading(true);
36479
+ try {
36480
+ const text = await file.text();
36481
+ pendingMusicXml = parseMusicXML(text);
36482
+ if (pendingMusicXml.parts.length === 0) {
36483
+ setMusicXmlNote("\u30D1\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F");
36484
+ refs.musicXmlPartSelection.classList.add("dtm-hidden");
36485
+ return;
36486
+ }
36487
+ refs.musicXmlPartSelection.innerHTML = `<span class="dtm-label">\u30D1\u30FC\u30C8</span>`;
36488
+ pendingMusicXml.parts.forEach((p) => {
36489
+ const btn = document.createElement("button");
36490
+ btn.className = "dtm-btn dtm-btn--primary";
36491
+ btn.dataset.selected = "true";
36492
+ const lyricBadge = p.hasLyrics ? " \u{1F3A4}" : "";
36493
+ btn.textContent = `${p.name} (${p.noteCount}\u97F3${lyricBadge})`;
36494
+ btn.title = `\u5E73\u5747\u97F3\u9AD8: MIDI ${Math.round(p.avgPitch)}${p.hasLyrics ? " / \u6B4C\u8A5E\u3042\u308A" : ""}`;
36495
+ btn.addEventListener("click", () => {
36496
+ const on = btn.dataset.selected !== "true";
36497
+ btn.dataset.selected = String(on);
36498
+ btn.classList.toggle("dtm-btn--primary", on);
36499
+ btn.classList.toggle("dtm-btn--ghost", !on);
36500
+ updateMusicXmlAssignmentNote();
36501
+ });
36502
+ refs.musicXmlPartSelection.appendChild(btn);
36503
+ });
36504
+ refs.musicXmlPartSelection.classList.remove("dtm-hidden");
36505
+ updateMusicXmlAssignmentNote();
36506
+ } catch (e) {
36507
+ setMusicXmlNote("MusicXML\u3068\u3057\u3066\u89E3\u6790\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F");
36508
+ refs.musicXmlPartSelection.classList.add("dtm-hidden");
36509
+ console.error(e);
36510
+ } finally {
36511
+ refs.overlay.hidden = true;
36512
+ setLoading(false);
36513
+ }
36514
+ });
36515
+ refs.musicXmlLoadBtn.addEventListener("click", async () => {
36516
+ if (!pendingMusicXml) return;
36517
+ const selected = getSelectedMusicXmlPartIndices();
36518
+ if (selected.length === 0) return;
36519
+ const applyActiveOnly = refs.applyActiveOnly?.checked ?? false;
36520
+ const from = applyActiveOnly ? activeTrackIndexOf() : selected.length > 1 ? 0 : activeTrackIndexOf();
36521
+ const maxCount = applyActiveOnly ? 1 : trackStates.length - from;
36522
+ if (!isAdvanced && options.onRequestAdvancedMode && !applyActiveOnly && selected.length > maxCount) {
36523
+ const confirmed = await showConfirmModal(
36524
+ "\u521D\u5FC3\u8005\u30E2\u30FC\u30C9\u3067\u306F\u30C8\u30E9\u30C3\u30AF\u304C\u8DB3\u308A\u305A\u3001\u4E00\u90E8\u306E\u30D1\u30FC\u30C8\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3002<br>\u4E0A\u7D1A\u8005\u30E2\u30FC\u30C9\u306B\u5207\u308A\u66FF\u3048\u307E\u3059\u304B\uFF1F"
36525
+ );
36526
+ if (confirmed) {
36527
+ const xml = pendingMusicXml;
36528
+ const sel = selected.slice();
36529
+ options.onRequestAdvancedMode(void 0, (newDaw) => {
36530
+ newDaw.applyMusicXmlParsed?.(xml, sel);
36531
+ });
36532
+ return;
36533
+ }
36534
+ }
36535
+ overlayDuring(() => {
36536
+ const { applied, dropped } = applyMusicXmlSelection(
36537
+ pendingMusicXml,
36538
+ selected
36539
+ );
36540
+ setMusicXmlNote(
36541
+ dropped > 0 ? `${applied}\u30D1\u30FC\u30C8\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3057\u305F\uFF08${dropped}\u30D1\u30FC\u30C8\u306F\u30C8\u30E9\u30C3\u30AF\u4E0D\u8DB3\u306E\u305F\u3081\u7701\u7565\uFF09` : `${applied}\u30D1\u30FC\u30C8\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3057\u305F`
36542
+ );
36543
+ });
36544
+ });
36545
+ };
34169
36546
  let pendingUsts = [];
34170
36547
  const setUstNote = (text) => {
34171
36548
  refs.ustLoadNote.textContent = text;
@@ -34762,6 +37139,15 @@ var mountDAW = (target, options = {}) => {
34762
37139
  resizeTimer = setTimeout(() => setupCanvas(), 150);
34763
37140
  });
34764
37141
  resizeObserver.observe(refs.rollContainer);
37142
+ const WIDE_LAYOUT_MIN_WIDTH = 1e3;
37143
+ const applyWideLayout = (width) => {
37144
+ refs.root.classList.toggle("dtm-daw--wide", width >= WIDE_LAYOUT_MIN_WIDTH);
37145
+ };
37146
+ const layoutObserver = new ResizeObserver((entries) => {
37147
+ for (const entry of entries) applyWideLayout(entry.contentRect.width);
37148
+ });
37149
+ layoutObserver.observe(refs.root);
37150
+ applyWideLayout(refs.root.getBoundingClientRect().width);
34765
37151
  document.addEventListener("pointermove", onPointerMove);
34766
37152
  document.addEventListener("pointerup", onPointerUp);
34767
37153
  const setLoading = (loading) => {
@@ -34863,10 +37249,14 @@ var mountDAW = (target, options = {}) => {
34863
37249
  applyMidiParsed: (midi, selectedIndices) => {
34864
37250
  overlayDuring(() => applyMidiSelection(midi, selectedIndices));
34865
37251
  },
37252
+ applyMusicXmlParsed: (xml, selectedIndices) => {
37253
+ overlayDuring(() => applyMusicXmlSelection(xml, selectedIndices));
37254
+ },
34866
37255
  applyUstParsed: (ustTracks, startIndex) => {
34867
37256
  overlayDuring(() => applyUstTracks(ustTracks, startIndex));
34868
37257
  },
34869
37258
  exportMIDI: exportMIDI2,
37259
+ exportMusicXML: exportMusicXML2,
34870
37260
  exportUST,
34871
37261
  setBpm,
34872
37262
  getLoop: () => loopEnabled,
@@ -35001,6 +37391,7 @@ var mountDAW = (target, options = {}) => {
35001
37391
  unsubscribeClip?.();
35002
37392
  stopPeakSampling();
35003
37393
  resizeObserver.disconnect();
37394
+ layoutObserver.disconnect();
35004
37395
  document.removeEventListener("pointermove", onPointerMove);
35005
37396
  document.removeEventListener("pointerup", onPointerUp);
35006
37397
  document.removeEventListener("keydown", onKeyDown);
@@ -35053,7 +37444,7 @@ var readGlobalBool = (key) => {
35053
37444
  };
35054
37445
 
35055
37446
  // src/headless-singing-player.ts
35056
- var STEPS_PER_BEAT5 = 48;
37447
+ var STEPS_PER_BEAT6 = 48;
35057
37448
  var STEPS_PER_BAR3 = 192;
35058
37449
  var TRACK_ID_BY_INDEX2 = ["melody", "submelody", "bass", "chord"];
35059
37450
  var playSingingMML = async (mml, options = {}) => {
@@ -35070,7 +37461,7 @@ var playSingingMML = async (mml, options = {}) => {
35070
37461
  parseCustomVocals(mml).map((d) => [d.key, d])
35071
37462
  );
35072
37463
  const bpm = parsedBpm ?? options.defaultBpm ?? DEFAULT_BPM;
35073
- const secondsPerStep = 60 / bpm / STEPS_PER_BEAT5;
37464
+ const secondsPerStep = 60 / bpm / STEPS_PER_BEAT6;
35074
37465
  const drumPatternDict = {
35075
37466
  ...DRUM_PATTERNS,
35076
37467
  ...SONG_DRUM_PATTERNS,
@@ -35238,7 +37629,8 @@ var playSingingMML = async (mml, options = {}) => {
35238
37629
  if (lyricTracks.size > 0) {
35239
37630
  if (!voices) {
35240
37631
  voices = createSingingVoices(ctx, destination, {
35241
- voiceWorkerUrl: options.voiceWorkerUrl
37632
+ voiceWorkerUrl: options.voiceWorkerUrl,
37633
+ ttsBaseUrl: options.ttsBaseUrl
35242
37634
  });
35243
37635
  }
35244
37636
  if (customVocalByKey.size > 0 && voices.registerVoicebanks) {
@@ -35302,275 +37694,6 @@ var playSingingMML = async (mml, options = {}) => {
35302
37694
  return playback;
35303
37695
  };
35304
37696
 
35305
- // src/musicxml-io.ts
35306
- var STEPS_PER_BEAT6 = 48;
35307
- var PITCH_CLASS = {
35308
- C: 0,
35309
- D: 2,
35310
- E: 4,
35311
- F: 5,
35312
- G: 7,
35313
- A: 9,
35314
- B: 11
35315
- };
35316
- var textOf = (el, tag) => el?.getElementsByTagName(tag)[0]?.textContent?.trim() ?? "";
35317
- var numOf = (el, tag, fallback = 0) => {
35318
- const raw = textOf(el, tag);
35319
- if (!raw) return fallback;
35320
- const v = Number.parseFloat(raw);
35321
- return Number.isFinite(v) ? v : fallback;
35322
- };
35323
- var pitchOf = (note) => {
35324
- const p = note.getElementsByTagName("pitch")[0];
35325
- if (!p) return null;
35326
- const step = textOf(p, "step").toUpperCase();
35327
- const base = PITCH_CLASS[step];
35328
- if (base === void 0) return null;
35329
- const octave = numOf(p, "octave", 4);
35330
- const alter = Math.round(numOf(p, "alter", 0));
35331
- return (octave + 1) * 12 + base + alter;
35332
- };
35333
- var parseMusicXML = (xml) => {
35334
- const doc = new DOMParser().parseFromString(xml, "application/xml");
35335
- if (doc.getElementsByTagName("parserror").length > 0)
35336
- throw new Error("MusicXML \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093");
35337
- const nameById = /* @__PURE__ */ new Map();
35338
- for (const sp of Array.from(doc.getElementsByTagName("score-part"))) {
35339
- const id = sp.getAttribute("id");
35340
- if (id) nameById.set(id, textOf(sp, "part-name") || id);
35341
- }
35342
- const placements = [];
35343
- const parts = [];
35344
- let bpm = 0;
35345
- const partEls = Array.from(doc.getElementsByTagName("part"));
35346
- partEls.forEach((partEl, partIndex) => {
35347
- const id = partEl.getAttribute("id") ?? "";
35348
- let divisions = 1;
35349
- let cursor = 0;
35350
- let measureStart = 0;
35351
- let lastStart = 0;
35352
- let lastDurSteps = 0;
35353
- const tied = /* @__PURE__ */ new Map();
35354
- let count = 0;
35355
- let pitchSum = 0;
35356
- let lyricSeen = false;
35357
- for (const measure of Array.from(partEl.getElementsByTagName("measure"))) {
35358
- measureStart += cursor;
35359
- cursor = 0;
35360
- for (const child of Array.from(measure.children)) {
35361
- const tag = child.tagName.toLowerCase();
35362
- if (tag === "attributes") {
35363
- const d = numOf(child, "divisions", 0);
35364
- if (d > 0) divisions = d;
35365
- continue;
35366
- }
35367
- if (tag === "direction") {
35368
- const sound = child.getElementsByTagName("sound")[0];
35369
- const t = Number.parseFloat(sound?.getAttribute("tempo") ?? "");
35370
- if (!bpm && Number.isFinite(t) && t > 0) bpm = t;
35371
- continue;
35372
- }
35373
- if (tag === "backup" || tag === "forward") {
35374
- const steps = numOf(child, "duration", 0) / divisions * STEPS_PER_BEAT6;
35375
- cursor += tag === "backup" ? -steps : steps;
35376
- continue;
35377
- }
35378
- if (tag !== "note") continue;
35379
- const durSteps = Math.round(
35380
- numOf(child, "duration", 0) / divisions * STEPS_PER_BEAT6
35381
- );
35382
- const isChord = child.getElementsByTagName("chord").length > 0;
35383
- const isRest = child.getElementsByTagName("rest").length > 0;
35384
- const start = isChord ? lastStart : measureStart + cursor;
35385
- if (!isRest) {
35386
- const pitch = pitchOf(child);
35387
- if (pitch !== null) {
35388
- const lyric = textOf(
35389
- child.getElementsByTagName("lyric")[0],
35390
- "text"
35391
- );
35392
- if (lyric) lyricSeen = true;
35393
- const ties = Array.from(child.getElementsByTagName("tie"));
35394
- const stops = ties.some((t) => t.getAttribute("type") === "stop");
35395
- const starts = ties.some((t) => t.getAttribute("type") === "start");
35396
- const held = tied.get(pitch);
35397
- if (stops && held) {
35398
- held.durationSteps += durSteps;
35399
- if (!starts) tied.delete(pitch);
35400
- } else {
35401
- const placed = {
35402
- partIndex,
35403
- startStep: Math.max(0, Math.round(start)),
35404
- pitch,
35405
- durationSteps: Math.max(1, durSteps),
35406
- lyric
35407
- };
35408
- placements.push(placed);
35409
- count++;
35410
- pitchSum += pitch;
35411
- if (starts) tied.set(pitch, placed);
35412
- }
35413
- }
35414
- }
35415
- if (!isChord) {
35416
- lastStart = start;
35417
- lastDurSteps = durSteps;
35418
- cursor += durSteps;
35419
- } else {
35420
- lastDurSteps = Math.max(lastDurSteps, durSteps);
35421
- }
35422
- }
35423
- }
35424
- parts.push({
35425
- index: partIndex,
35426
- name: nameById.get(id) || `Part ${partIndex + 1}`,
35427
- noteCount: count,
35428
- hasLyrics: lyricSeen,
35429
- avgPitch: count > 0 ? pitchSum / count : 0
35430
- });
35431
- });
35432
- return { parts, placements, bpm: bpm > 0 ? Math.round(bpm) : 120 };
35433
- };
35434
- var SHARP_SPELLING = [
35435
- ["C", 0],
35436
- ["C", 1],
35437
- ["D", 0],
35438
- ["D", 1],
35439
- ["E", 0],
35440
- ["F", 0],
35441
- ["F", 1],
35442
- ["G", 0],
35443
- ["G", 1],
35444
- ["A", 0],
35445
- ["A", 1],
35446
- ["B", 0]
35447
- ];
35448
- var NOTE_TYPES = [
35449
- [STEPS_PER_BEAT6 * 4, "whole"],
35450
- [STEPS_PER_BEAT6 * 2, "half"],
35451
- [STEPS_PER_BEAT6, "quarter"],
35452
- [STEPS_PER_BEAT6 / 2, "eighth"],
35453
- [STEPS_PER_BEAT6 / 4, "16th"],
35454
- [STEPS_PER_BEAT6 / 8, "32nd"]
35455
- ];
35456
- var typeOf = (steps) => {
35457
- for (const [len, type] of NOTE_TYPES) {
35458
- if (steps >= len) {
35459
- const dots = steps >= len * 1.5 && steps < len * 2 ? 1 : 0;
35460
- return { type, dots };
35461
- }
35462
- }
35463
- return { type: "32nd", dots: 0 };
35464
- };
35465
- var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
35466
- var exportMusicXML = (options) => {
35467
- const { parts, bpm, stepsPerBar, title } = options;
35468
- const beatsPerBar = Math.max(1, Math.round(stepsPerBar / STEPS_PER_BEAT6));
35469
- const partList = parts.map(
35470
- (p, i2) => ` <score-part id="P${i2 + 1}">
35471
- <part-name>${esc(p.name)}</part-name>
35472
- </score-part>`
35473
- ).join("\n");
35474
- const body = parts.map((part, pi) => {
35475
- const notes = [...part.notes].sort((a, b) => a.startStep - b.startStep);
35476
- const end = notes.reduce(
35477
- (m, n) => Math.max(m, n.startStep + n.durationSteps),
35478
- 0
35479
- );
35480
- const bars = Math.max(1, Math.ceil(end / stepsPerBar));
35481
- const lines = [` <part id="P${pi + 1}">`];
35482
- let li = 0;
35483
- for (let bar = 0; bar < bars; bar++) {
35484
- const from = bar * stepsPerBar;
35485
- const to = from + stepsPerBar;
35486
- lines.push(` <measure number="${bar + 1}">`);
35487
- if (bar === 0) {
35488
- lines.push(
35489
- ` <attributes>
35490
- <divisions>${STEPS_PER_BEAT6}</divisions>`,
35491
- ` <key><fifths>0</fifths></key>`,
35492
- ` <time><beats>${beatsPerBar}</beats><beat-type>4</beat-type></time>`,
35493
- ` <clef><sign>G</sign><line>2</line></clef>
35494
- </attributes>`
35495
- );
35496
- if (pi === 0)
35497
- lines.push(
35498
- ` <direction placement="above"><direction-type><metronome><beat-unit>quarter</beat-unit><per-minute>${Math.round(bpm)}</per-minute></metronome></direction-type><sound tempo="${Math.round(bpm)}"/></direction>`
35499
- );
35500
- }
35501
- let cursor = from;
35502
- const inBar = notes.filter(
35503
- (n) => n.startStep >= from && n.startStep < to
35504
- );
35505
- const groups = /* @__PURE__ */ new Map();
35506
- for (const n of inBar) {
35507
- const g = groups.get(n.startStep);
35508
- if (g) g.push(n);
35509
- else groups.set(n.startStep, [n]);
35510
- }
35511
- const starts = [...groups.keys()].sort((a, b) => a - b);
35512
- for (let gi = 0; gi < starts.length; gi++) {
35513
- const at = starts[gi];
35514
- const group = groups.get(at);
35515
- if (at > cursor) {
35516
- const { type, dots } = typeOf(at - cursor);
35517
- lines.push(
35518
- ` <note><rest/><duration>${at - cursor}</duration><type>${type}</type>${"<dot/>".repeat(dots)}</note>`
35519
- );
35520
- cursor = at;
35521
- }
35522
- const nextAt = gi + 1 < starts.length ? starts[gi + 1] : to;
35523
- const room = Math.min(to, nextAt) - at;
35524
- const dur = Math.max(
35525
- 1,
35526
- Math.min(Math.max(...group.map((n) => n.durationSteps)), room)
35527
- );
35528
- group.forEach((n, ni) => {
35529
- const semi = Math.round(n.pitchUnits / UNITS_PER_SEMITONE);
35530
- const [step, alter] = SHARP_SPELLING[(semi % 12 + 12) % 12];
35531
- const octave = Math.floor(semi / 12) - 1;
35532
- const { type, dots } = typeOf(dur);
35533
- const lyric = part.lyrics?.[li];
35534
- lines.push(
35535
- ` <note>${ni > 0 ? "<chord/>" : ""}<pitch><step>${step}</step>${alter ? `<alter>${alter}</alter>` : ""}<octave>${octave}</octave></pitch><duration>${dur}</duration><type>${type}</type>${"<dot/>".repeat(dots)}` + (ni === 0 && lyric ? `<lyric><syllabic>single</syllabic><text>${esc(lyric)}</text></lyric>` : "") + `</note>`
35536
- );
35537
- if (ni === 0) li++;
35538
- });
35539
- cursor = at + dur;
35540
- }
35541
- if (cursor < to) {
35542
- const { type, dots } = typeOf(to - cursor);
35543
- lines.push(
35544
- ` <note><rest/><duration>${to - cursor}</duration><type>${type}</type>${"<dot/>".repeat(dots)}</note>`
35545
- );
35546
- }
35547
- lines.push(" </measure>");
35548
- }
35549
- lines.push(" </part>");
35550
- return lines.join("\n");
35551
- }).join("\n");
35552
- return [
35553
- '<?xml version="1.0" encoding="UTF-8"?>',
35554
- '<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">',
35555
- '<score-partwise version="4.0">',
35556
- ` <work><work-title>${esc(title ?? "Untitled")}</work-title></work>`,
35557
- // 書き出し元を残す。MML・MIDI と揃えてある(`src/version.ts` に理由)。
35558
- ` <identification><encoding><software>dtm ${DTM_VERSION}</software></encoding></identification>`,
35559
- " <part-list>",
35560
- partList,
35561
- " </part-list>",
35562
- body,
35563
- "</score-partwise>",
35564
- ""
35565
- ].join("\n");
35566
- };
35567
- var musicXmlToNotes = (placements, partIndex) => placements.filter((p) => p.partIndex === partIndex).map((p) => ({
35568
- startStep: p.startStep,
35569
- durationSteps: p.durationSteps,
35570
- pitchUnits: p.pitch * UNITS_PER_SEMITONE,
35571
- velocity: 100
35572
- }));
35573
-
35574
37697
  // src/piano-roll.ts
35575
37698
  var createPianoRoll = (options, handlers) => {
35576
37699
  const {
@@ -36506,6 +38629,7 @@ var createDtmStudio = async (options = {}) => {
36506
38629
  voiceWorkerUrl,
36507
38630
  voicebanks,
36508
38631
  worldlineScriptUrl: options.worldlineScriptUrl,
38632
+ ttsBaseUrl: options.ttsBaseUrl,
36509
38633
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
36510
38634
  // Convolver 入力へ直接センドする(PreDelayは経由しない。チャンネルストリップの
36511
38635
  // reverbSend とは別経路の、音節単位でより細かく制御できるセンド)。
@@ -37556,6 +39680,7 @@ var createDtmStudio = async (options = {}) => {
37556
39680
  DEFAULT_PLAYBACK_VELOCITY,
37557
39681
  DEFAULT_SECTIONS,
37558
39682
  DEFAULT_STEPS_PER_BAR,
39683
+ DEFAULT_TTS_BASE_URL,
37559
39684
  DEFAULT_UST_LYRIC,
37560
39685
  DEFAULT_VELOCITY,
37561
39686
  DEFAULT_VOCAL_VOLUME,
@@ -37599,6 +39724,8 @@ var createDtmStudio = async (options = {}) => {
37599
39724
  SECTION_ORDER,
37600
39725
  SECTION_SPECS,
37601
39726
  SEQUENCER_START_DELAY,
39727
+ SPEAK_CLOSE,
39728
+ SPEAK_OPEN,
37602
39729
  STOP_MARK,
37603
39730
  TIE_MARK,
37604
39731
  TIE_MERGE_MAX_SEC,
@@ -37640,6 +39767,8 @@ var createDtmStudio = async (options = {}) => {
37640
39767
  createRenderer,
37641
39768
  createSequencer,
37642
39769
  createSingingVoices,
39770
+ createSpeechPlanner,
39771
+ createSpeechToneView,
37643
39772
  createSynth,
37644
39773
  createVoiceRegistry,
37645
39774
  decodeMml,
@@ -37668,6 +39797,7 @@ var createDtmStudio = async (options = {}) => {
37668
39797
  getComposeScaleDescription,
37669
39798
  getDrumPatternKeys,
37670
39799
  getMidiBPM,
39800
+ getSpeechPlanner,
37671
39801
  hasSeenTour,
37672
39802
  icon,
37673
39803
  injectStyles,
@@ -37682,6 +39812,7 @@ var createDtmStudio = async (options = {}) => {
37682
39812
  looksLikePinyin,
37683
39813
  lyricToSyllable,
37684
39814
  markTourSeen,
39815
+ medianRecordedPitchHz,
37685
39816
  midiNote,
37686
39817
  midiToUnits,
37687
39818
  mountChordPlayer,
@@ -37707,10 +39838,12 @@ var createDtmStudio = async (options = {}) => {
37707
39838
  playNote,
37708
39839
  playPlacements,
37709
39840
  playSingingMML,
39841
+ prepareSpeechPlan,
37710
39842
  programOfInstrumentName,
37711
39843
  readGlobalBool,
37712
39844
  readGlobalNumber,
37713
39845
  readGlobalSetting,
39846
+ readKeptSong,
37714
39847
  readMacroSections,
37715
39848
  readMacroSetting,
37716
39849
  resolveCenter,
@@ -37725,6 +39858,8 @@ var createDtmStudio = async (options = {}) => {
37725
39858
  semitonesToUnits,
37726
39859
  shiftNotes,
37727
39860
  showLoadingOverlay,
39861
+ speechPlanDurationSec,
39862
+ speechPlanLeadingSec,
37728
39863
  spelledToUnits,
37729
39864
  spellingToUnits,
37730
39865
  startTour,
@@ -37744,6 +39879,7 @@ var createDtmStudio = async (options = {}) => {
37744
39879
  unitsToPitchV1,
37745
39880
  vocalVolumeToGain,
37746
39881
  writeGlobalSetting,
39882
+ writeKeptSong,
37747
39883
  writeMacroSections,
37748
39884
  writeMacroSetting
37749
39885
  });