@onjmin/dtm 2.1.12 → 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.mjs CHANGED
@@ -2657,7 +2657,7 @@ var buildChordPlacements = (options) => {
2657
2657
  return placements;
2658
2658
  };
2659
2659
 
2660
- // node_modules/.pnpm/@onjmin+koe@1.0.5/node_modules/@onjmin/koe/dist/index.js
2660
+ // node_modules/.pnpm/@onjmin+koe@1.0.10/node_modules/@onjmin/koe/dist/index.js
2661
2661
  var u8 = Uint8Array;
2662
2662
  var u16 = Uint16Array;
2663
2663
  var i32 = Int32Array;
@@ -3045,6 +3045,130 @@ var Worldline = class _Worldline {
3045
3045
  * @returns Float32 PCM, or null when `pcm` is shorter than
3046
3046
  * {@link MIN_WORLDLINE_SAMPLES} (too short for stable F0 analysis).
3047
3047
  */
3048
+ renderPhrase(params) {
3049
+ const {
3050
+ units: units2,
3051
+ pitch,
3052
+ gender = 0.5,
3053
+ tension = 0.5,
3054
+ breathiness = 0.5,
3055
+ voicing = 1,
3056
+ tempo = 120
3057
+ } = params;
3058
+ if (units2.length === 0) return null;
3059
+ const WL = this.wasm;
3060
+ const FS2 = WORLDLINE_SAMPLE_RATE;
3061
+ let totalMs = 0;
3062
+ for (const u of units2) {
3063
+ const endMs = u.posMs + u.lengthMs;
3064
+ if (endMs > totalMs) totalMs = endMs;
3065
+ }
3066
+ const ps = WL._PhraseSynthNew();
3067
+ if (!ps) return null;
3068
+ const pointersToFree = [];
3069
+ for (const u of units2) {
3070
+ if (!u.pcm || u.pcm.length < MIN_WORLDLINE_SAMPLES) continue;
3071
+ const reqPtr = WL._malloc(SYNTH_REQ_SIZE);
3072
+ if (!reqPtr) continue;
3073
+ pointersToFree.push(reqPtr);
3074
+ const samplePtr = WL._malloc(u.pcm.length * 8);
3075
+ if (!samplePtr) continue;
3076
+ pointersToFree.push(samplePtr);
3077
+ WL.HEAPF64.set(u.pcm, samplePtr >> 3);
3078
+ const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
3079
+ sv(0, FS2, "i32");
3080
+ sv(4, u.pcm.length, "i32");
3081
+ sv(8, samplePtr, "*");
3082
+ sv(12, 0, "i32");
3083
+ sv(16, 0, "*");
3084
+ sv(20, u.tone ?? 69, "i32");
3085
+ sv(24, 100, "double");
3086
+ sv(32, 0, "double");
3087
+ sv(40, u.requiredLengthMs ?? u.lengthMs, "double");
3088
+ sv(48, u.consonantMs, "double");
3089
+ const cutMs = u.cutMs ?? WL_FRAME_MS * 2;
3090
+ sv(56, cutMs, "double");
3091
+ sv(64, u.volume ?? 100, "double");
3092
+ sv(72, 0, "double");
3093
+ sv(80, tempo, "double");
3094
+ sv(88, 0, "i32");
3095
+ sv(92, 0, "*");
3096
+ sv(96, 0, "i32");
3097
+ sv(100, 0, "i32");
3098
+ sv(104, 100, "i32");
3099
+ sv(108, 0, "i32");
3100
+ sv(112, 0, "i32");
3101
+ sv(116, 100, "i32");
3102
+ WL._PhraseSynthAddRequest(
3103
+ ps,
3104
+ reqPtr,
3105
+ u.posMs,
3106
+ u.skipMs,
3107
+ u.lengthMs,
3108
+ u.fadeInMs,
3109
+ u.fadeOutMs,
3110
+ 0
3111
+ );
3112
+ }
3113
+ totalMs += WL_FRAME_MS * 2;
3114
+ const nFrames = Math.ceil(totalMs / WL_FRAME_MS) + 4;
3115
+ const f0Arr = new Float64Array(nFrames);
3116
+ const gArr = new Float64Array(nFrames);
3117
+ const tArr = new Float64Array(nFrames);
3118
+ const bArr = new Float64Array(nFrames);
3119
+ const vArr = new Float64Array(nFrames);
3120
+ for (let i2 = 0; i2 < nFrames; i2++) {
3121
+ const tMs = i2 * WL_FRAME_MS;
3122
+ f0Arr[i2] = sampleCurve(tMs, pitch, totalMs);
3123
+ gArr[i2] = sampleCurve(tMs, gender, totalMs);
3124
+ tArr[i2] = sampleCurve(tMs, tension, totalMs);
3125
+ bArr[i2] = sampleCurve(tMs, breathiness, totalMs);
3126
+ vArr[i2] = sampleCurve(tMs, voicing, totalMs);
3127
+ }
3128
+ const f0Ptr = WL._malloc(nFrames * 8);
3129
+ const gPtr = WL._malloc(nFrames * 8);
3130
+ const tPtr = WL._malloc(nFrames * 8);
3131
+ const bPtr = WL._malloc(nFrames * 8);
3132
+ const vPtr = WL._malloc(nFrames * 8);
3133
+ if (f0Ptr && gPtr && tPtr && bPtr && vPtr) {
3134
+ WL.HEAPF64.set(f0Arr, f0Ptr >> 3);
3135
+ WL.HEAPF64.set(gArr, gPtr >> 3);
3136
+ WL.HEAPF64.set(tArr, tPtr >> 3);
3137
+ WL.HEAPF64.set(bArr, bPtr >> 3);
3138
+ WL.HEAPF64.set(vArr, vPtr >> 3);
3139
+ WL._PhraseSynthSetCurves(
3140
+ ps,
3141
+ f0Ptr,
3142
+ gPtr,
3143
+ tPtr,
3144
+ bPtr,
3145
+ vPtr,
3146
+ nFrames,
3147
+ WL_FRAME_MS
3148
+ );
3149
+ }
3150
+ if (f0Ptr) WL._free(f0Ptr);
3151
+ if (gPtr) WL._free(gPtr);
3152
+ if (tPtr) WL._free(tPtr);
3153
+ if (bPtr) WL._free(bPtr);
3154
+ if (vPtr) WL._free(vPtr);
3155
+ const yPtrPtr = WL._malloc(4);
3156
+ let audio = null;
3157
+ if (yPtrPtr) {
3158
+ const outLen = WL._PhraseSynthSynth(ps, yPtrPtr, 0);
3159
+ const yPtr = WL.getValue(yPtrPtr, "*");
3160
+ if (outLen > 0 && yPtr) {
3161
+ audio = new Float32Array(WL.HEAPF32.buffer, yPtr, outLen).slice();
3162
+ WL._free(yPtr);
3163
+ }
3164
+ WL._free(yPtrPtr);
3165
+ }
3166
+ for (const ptr of pointersToFree) {
3167
+ WL._free(ptr);
3168
+ }
3169
+ WL._PhraseSynthDelete(ps);
3170
+ return audio;
3171
+ }
3048
3172
  renderNote(params) {
3049
3173
  const {
3050
3174
  pcm,
@@ -3060,8 +3184,12 @@ var Worldline = class _Worldline {
3060
3184
  } = params;
3061
3185
  if (!pcm || pcm.length < MIN_WORLDLINE_SAMPLES) return null;
3062
3186
  const WL = this.wasm;
3063
- const FS = WORLDLINE_SAMPLE_RATE;
3064
- const basePitch = sampleCurve(preMs + durationMs / 2, pitch, preMs + durationMs);
3187
+ const FS2 = WORLDLINE_SAMPLE_RATE;
3188
+ const basePitch = sampleCurve(
3189
+ preMs + durationMs / 2,
3190
+ pitch,
3191
+ preMs + durationMs
3192
+ );
3065
3193
  const midiNote2 = Math.round(69 + 12 * Math.log2(basePitch / 440));
3066
3194
  const posMs = 0;
3067
3195
  const reqLen = preMs + durationMs;
@@ -3081,7 +3209,7 @@ var Worldline = class _Worldline {
3081
3209
  }
3082
3210
  WL.HEAPF64.set(pcm, samplePtr >> 3);
3083
3211
  const sv = (off, val, type) => WL.setValue(reqPtr + off, val, type);
3084
- sv(0, FS, "i32");
3212
+ sv(0, FS2, "i32");
3085
3213
  sv(4, pcm.length, "i32");
3086
3214
  sv(8, samplePtr, "*");
3087
3215
  sv(12, 0, "i32");
@@ -3169,6 +3297,836 @@ var Worldline = class _Worldline {
3169
3297
  return audio;
3170
3298
  }
3171
3299
  };
3300
+ var RATE = 16e3;
3301
+ var HOP_MS = 2;
3302
+ var HOP = RATE * HOP_MS / 1e3;
3303
+ var FFT_SIZE = 512;
3304
+ var BINS = FFT_SIZE / 2;
3305
+ var PITCH_DECIM = 2;
3306
+ var PITCH_RATE = RATE / PITCH_DECIM;
3307
+ var WINDOW_CENTRE_MS = FFT_SIZE / 2 / RATE * 1e3;
3308
+ var DEFAULT_CACHE = "koe-tts-assets-v1";
3309
+ async function openCache(name) {
3310
+ if (name === null) return null;
3311
+ try {
3312
+ if (typeof caches === "undefined") return null;
3313
+ return await caches.open(name ?? DEFAULT_CACHE);
3314
+ } catch {
3315
+ return null;
3316
+ }
3317
+ }
3318
+ async function cachedCopyIsCurrent(url2, cached, signal) {
3319
+ let head;
3320
+ try {
3321
+ head = await fetch(url2, { method: "HEAD", signal, cache: "no-cache" });
3322
+ } catch {
3323
+ return true;
3324
+ }
3325
+ if (!head.ok) return true;
3326
+ for (const name of ["etag", "last-modified", "content-length"]) {
3327
+ const remote = head.headers.get(name);
3328
+ const local = cached.headers.get(name);
3329
+ if (remote && local) return remote === local;
3330
+ }
3331
+ return true;
3332
+ }
3333
+ async function fetchAsset(url2, options = {}) {
3334
+ const { onProgress, signal, revalidate = true } = options;
3335
+ const cache2 = await openCache(options.cacheName);
3336
+ if (cache2) {
3337
+ try {
3338
+ const hit = await cache2.match(url2);
3339
+ if (hit && (!revalidate || await cachedCopyIsCurrent(url2, hit, signal))) {
3340
+ const total2 = Number(hit.headers.get("content-length")) || 0;
3341
+ onProgress?.({ url: url2, loaded: total2, total: total2, fromCache: true });
3342
+ return hit;
3343
+ }
3344
+ } catch {
3345
+ }
3346
+ }
3347
+ const response = await fetch(url2, { signal });
3348
+ if (!response.ok) {
3349
+ throw new Error(`fetch ${url2}: HTTP ${response.status}`);
3350
+ }
3351
+ const total = Number(response.headers.get("content-length")) || 0;
3352
+ let body;
3353
+ if (response.body && onProgress) {
3354
+ const reader = response.body.getReader();
3355
+ const parts = [];
3356
+ let loaded = 0;
3357
+ onProgress({ url: url2, loaded, total, fromCache: false });
3358
+ for (; ; ) {
3359
+ const { done, value } = await reader.read();
3360
+ if (done) break;
3361
+ parts.push(value);
3362
+ loaded += value.byteLength;
3363
+ onProgress({ url: url2, loaded, total, fromCache: false });
3364
+ }
3365
+ body = new Uint8Array(new ArrayBuffer(loaded));
3366
+ let offset = 0;
3367
+ for (const part of parts) {
3368
+ body.set(part, offset);
3369
+ offset += part.byteLength;
3370
+ }
3371
+ } else {
3372
+ body = new Uint8Array(await response.arrayBuffer());
3373
+ onProgress?.({
3374
+ url: url2,
3375
+ loaded: body.byteLength,
3376
+ total: total || body.byteLength,
3377
+ fromCache: false
3378
+ });
3379
+ }
3380
+ const headers = new Headers();
3381
+ for (const name of ["content-type", "etag", "last-modified"]) {
3382
+ const value = response.headers.get(name);
3383
+ if (value) headers.set(name, value);
3384
+ }
3385
+ headers.set("content-length", String(body.byteLength));
3386
+ const buffered = new Response(body, { status: 200, headers });
3387
+ if (cache2) {
3388
+ try {
3389
+ await cache2.put(url2, buffered.clone());
3390
+ } catch {
3391
+ }
3392
+ }
3393
+ return buffered;
3394
+ }
3395
+ async function fetchAssetBytes(url2, options) {
3396
+ return new Uint8Array(await (await fetchAsset(url2, options)).arrayBuffer());
3397
+ }
3398
+ async function fetchAssetText(url2, options) {
3399
+ return (await fetchAsset(url2, options)).text();
3400
+ }
3401
+ var NAIST_JDIC_FILES = [
3402
+ "metadata.json",
3403
+ "char_def.bin",
3404
+ "matrix.mtx",
3405
+ "dict.da",
3406
+ "dict.vals",
3407
+ "unk.bin",
3408
+ "dict.wordsidx",
3409
+ "dict.words"
3410
+ ];
3411
+ async function inflate(bytes) {
3412
+ if (typeof DecompressionStream === "undefined") {
3413
+ throw new Error(
3414
+ "DecompressionStream is not available; serve the dictionary uncompressed"
3415
+ );
3416
+ }
3417
+ const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
3418
+ return new Uint8Array(await new Response(stream).arrayBuffer());
3419
+ }
3420
+ async function loadNaistJdic(baseUrl, options = {}) {
3421
+ const { compressed = true, ...fetchOptions } = options;
3422
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
3423
+ const entries = await Promise.all(
3424
+ NAIST_JDIC_FILES.map(async (name) => {
3425
+ const raw = await fetchAssetBytes(
3426
+ `${base}${name}${compressed ? ".gz" : ""}`,
3427
+ fetchOptions
3428
+ );
3429
+ return [name, compressed ? await inflate(raw) : raw];
3430
+ })
3431
+ );
3432
+ return Object.fromEntries(entries);
3433
+ }
3434
+ function initJpreprocessDictionary(module, data) {
3435
+ module.init_dictionary(
3436
+ data["metadata.json"],
3437
+ data["char_def.bin"],
3438
+ data["matrix.mtx"],
3439
+ data["dict.da"],
3440
+ data["dict.vals"],
3441
+ data["unk.bin"],
3442
+ data["dict.wordsidx"],
3443
+ data["dict.words"]
3444
+ );
3445
+ }
3446
+ var NUCLEUS = /* @__PURE__ */ new Set([
3447
+ "a",
3448
+ "i",
3449
+ "u",
3450
+ "e",
3451
+ "o",
3452
+ "A",
3453
+ "I",
3454
+ "U",
3455
+ "E",
3456
+ "O",
3457
+ "N",
3458
+ "cl"
3459
+ ]);
3460
+ function softLimit(value, knee, max2) {
3461
+ const magnitude = Math.abs(value);
3462
+ const limited = magnitude <= knee ? magnitude : knee + (magnitude - knee) * 0.5;
3463
+ return Math.sign(value) * Math.min(max2, limited);
3464
+ }
3465
+ var DEVOICED = /* @__PURE__ */ new Set(["A", "I", "U", "E", "O"]);
3466
+ function segmentPhonemes(phonemes) {
3467
+ const segments = [];
3468
+ let openStart = null;
3469
+ for (let i2 = 0; i2 < phonemes.length; i2++) {
3470
+ const { phone, start_ms, duration_ms } = phonemes[i2];
3471
+ if (phone === "sil" || phone === "pau") {
3472
+ if (openStart !== null) {
3473
+ segments.push({
3474
+ pause: false,
3475
+ startMs: openStart,
3476
+ durationMs: start_ms - openStart,
3477
+ devoiced: false
3478
+ });
3479
+ openStart = null;
3480
+ }
3481
+ if (i2 === 0) continue;
3482
+ segments.push({
3483
+ pause: true,
3484
+ startMs: start_ms,
3485
+ durationMs: duration_ms,
3486
+ devoiced: false
3487
+ });
3488
+ continue;
3489
+ }
3490
+ if (openStart === null) openStart = start_ms;
3491
+ if (NUCLEUS.has(phone)) {
3492
+ segments.push({
3493
+ pause: false,
3494
+ startMs: openStart,
3495
+ durationMs: start_ms + duration_ms - openStart,
3496
+ devoiced: DEVOICED.has(phone)
3497
+ });
3498
+ openStart = null;
3499
+ }
3500
+ }
3501
+ if (openStart !== null) {
3502
+ const last = phonemes[phonemes.length - 1];
3503
+ segments.push({
3504
+ pause: false,
3505
+ startMs: openStart,
3506
+ durationMs: last.start_ms + last.duration_ms - openStart,
3507
+ devoiced: false
3508
+ });
3509
+ }
3510
+ return segments;
3511
+ }
3512
+ function alignHtsProsody(frames, features, options = {}) {
3513
+ const {
3514
+ intonationStrength = 1,
3515
+ frameMs = 10,
3516
+ fallbackPauseMs = 150,
3517
+ kneeCents = 400,
3518
+ maxCents = 700
3519
+ } = options;
3520
+ const segments = segmentPhonemes(frames.phonemes);
3521
+ const durations = new Array(features.length).fill(0);
3522
+ const htsStart = new Array(features.length).fill(null);
3523
+ const devoiced = new Array(features.length).fill(false);
3524
+ let s = 0;
3525
+ for (let f2 = 0; f2 < features.length; f2++) {
3526
+ const feature = features[f2];
3527
+ while (s < segments.length && segments[s].pause && !feature.pause) s++;
3528
+ const segment = segments[s];
3529
+ if (feature.pause) {
3530
+ if (segment?.pause) {
3531
+ durations[f2] = segment.durationMs;
3532
+ htsStart[f2] = segment.startMs;
3533
+ s++;
3534
+ } else {
3535
+ durations[f2] = fallbackPauseMs;
3536
+ }
3537
+ continue;
3538
+ }
3539
+ if (!segment) return null;
3540
+ durations[f2] = segment.durationMs;
3541
+ htsStart[f2] = segment.startMs;
3542
+ devoiced[f2] = segment.devoiced;
3543
+ s++;
3544
+ }
3545
+ if (segments.slice(s).some((segment) => !segment.pause)) return null;
3546
+ const f0 = frames.f0_hz;
3547
+ const logF0 = new Float64Array(f0.length);
3548
+ const voiced = [];
3549
+ for (let i2 = 0; i2 < f0.length; i2++) if (f0[i2] > 0) voiced.push(i2);
3550
+ if (voiced.length === 0) return null;
3551
+ const sorted = voiced.map((i2) => f0[i2]).sort((a, b) => a - b);
3552
+ const medianHz = sorted[Math.floor(sorted.length / 2)];
3553
+ for (let i2 = 0, v = 0; i2 < f0.length; i2++) {
3554
+ if (f0[i2] > 0) {
3555
+ logF0[i2] = Math.log2(f0[i2]);
3556
+ continue;
3557
+ }
3558
+ while (v < voiced.length && voiced[v] < i2) v++;
3559
+ const right = voiced[v];
3560
+ const left = v > 0 ? voiced[v - 1] : void 0;
3561
+ if (left === void 0) logF0[i2] = Math.log2(f0[right]);
3562
+ else if (right === void 0) logF0[i2] = Math.log2(f0[left]);
3563
+ else {
3564
+ const t = (i2 - left) / (right - left);
3565
+ logF0[i2] = Math.log2(f0[left]) * (1 - t) + Math.log2(f0[right]) * t;
3566
+ }
3567
+ }
3568
+ const logMedian = Math.log2(medianHz);
3569
+ const htsCentsAt = (tMs) => {
3570
+ const position = Math.max(0, tMs) / frames.frame_ms;
3571
+ const left = Math.min(logF0.length - 1, Math.floor(position));
3572
+ const right = Math.min(logF0.length - 1, left + 1);
3573
+ const t = position - Math.floor(position);
3574
+ return (logF0[left] * (1 - t) + logF0[right] * t - logMedian) * 1200;
3575
+ };
3576
+ const planStart = [];
3577
+ let cursor = 0;
3578
+ for (let f2 = 0; f2 < features.length; f2++) {
3579
+ planStart.push(cursor);
3580
+ cursor += durations[f2];
3581
+ }
3582
+ const durationMs = cursor;
3583
+ const count = Math.max(2, Math.ceil(durationMs / frameMs) + 2);
3584
+ const cents = new Array(count);
3585
+ let f = 0;
3586
+ for (let frame = 0; frame < count; frame++) {
3587
+ const tMs = frame * frameMs;
3588
+ while (f + 1 < features.length && tMs >= planStart[f + 1]) f++;
3589
+ const start = htsStart[f];
3590
+ let htsMs;
3591
+ if (start !== null) htsMs = start + (tMs - planStart[f]);
3592
+ else {
3593
+ const next = htsStart.slice(f + 1).find((value) => value !== null);
3594
+ htsMs = next ?? frames.phonemes.at(-1)?.start_ms ?? 0;
3595
+ }
3596
+ cents[frame] = softLimit(
3597
+ htsCentsAt(htsMs) * intonationStrength,
3598
+ kneeCents,
3599
+ maxCents
3600
+ );
3601
+ }
3602
+ return {
3603
+ moraDurationsMs: durations,
3604
+ pitchCurve: { frame_ms: frameMs, cents },
3605
+ medianHz,
3606
+ durationMs,
3607
+ devoiced
3608
+ };
3609
+ }
3610
+ var PUNCTUATION = /* @__PURE__ */ new Set(["\u3001", "\u3002", "\uFF1F", "\uFF01", ",", ".", "?", "!"]);
3611
+ var SMALL_KANA = /* @__PURE__ */ new Set(["\u3041", "\u3043", "\u3045", "\u3047", "\u3049", "\u3083", "\u3085", "\u3087", "\u308E", "\u3095", "\u3096"]);
3612
+ function toHiragana2(character) {
3613
+ const code = character.charCodeAt(0);
3614
+ if (12449 <= code && code <= 12534) {
3615
+ return String.fromCharCode(code - 96);
3616
+ }
3617
+ return character;
3618
+ }
3619
+ function splitMorae(reading) {
3620
+ const result = [];
3621
+ const normalized = reading.normalize("NFC").replace(/'/g, "").replace(/’/g, "");
3622
+ for (const character of normalized) {
3623
+ if (/\s/.test(character) || PUNCTUATION.has(character)) {
3624
+ if (result.length > 0 && !result[result.length - 1].pause) {
3625
+ result.push({ mora: "", pause: true });
3626
+ }
3627
+ continue;
3628
+ }
3629
+ const mora = toHiragana2(character);
3630
+ if (SMALL_KANA.has(mora) && result.length > 0 && !result[result.length - 1].pause) {
3631
+ result[result.length - 1].mora += mora;
3632
+ } else {
3633
+ result.push({ mora, pause: false });
3634
+ }
3635
+ }
3636
+ return result;
3637
+ }
3638
+ function isHigh(position, accent) {
3639
+ if (accent === 1) return position === 1;
3640
+ if (accent > 1) return 2 <= position && position <= accent;
3641
+ return position >= 2;
3642
+ }
3643
+ function analyze2(nodes) {
3644
+ const reading_parts = [];
3645
+ const result = [];
3646
+ let index = 0;
3647
+ while (index < nodes.length) {
3648
+ const node = nodes[index];
3649
+ const pronunciation = (node.pron || "").replace(/'/g, "").replace(/’/g, "");
3650
+ if (node.mora_size === 0 || PUNCTUATION.has(node.string)) {
3651
+ reading_parts.push(node.string || "\u3001");
3652
+ if (result.length > 0 && !result[result.length - 1].pause) {
3653
+ result.push({ mora: "", pause: true });
3654
+ }
3655
+ index++;
3656
+ continue;
3657
+ }
3658
+ const phrase_nodes = [];
3659
+ while (index < nodes.length) {
3660
+ const current = nodes[index];
3661
+ if (current.mora_size === 0 || PUNCTUATION.has(current.string)) {
3662
+ break;
3663
+ }
3664
+ if (phrase_nodes.length > 0 && current.chain_flag !== 1) {
3665
+ break;
3666
+ }
3667
+ const current_pronunciation = (current.pron || "").replace(/'/g, "").replace(/’/g, "");
3668
+ const morae = splitMorae(current_pronunciation).filter((item) => !item.pause);
3669
+ phrase_nodes.push({ current, morae });
3670
+ reading_parts.push(current_pronunciation);
3671
+ index++;
3672
+ }
3673
+ const phrase_length = phrase_nodes.reduce((sum, item) => sum + item.morae.length, 0);
3674
+ const accent = phrase_nodes[0].current.acc || 0;
3675
+ let phrase_position = 0;
3676
+ for (const { current, morae } of phrase_nodes) {
3677
+ for (let word_position = 1; word_position <= morae.length; word_position++) {
3678
+ phrase_position++;
3679
+ result.push({
3680
+ mora: morae[word_position - 1].mora,
3681
+ pause: false,
3682
+ accent_phrase_position: phrase_position,
3683
+ accent_phrase_length: phrase_length,
3684
+ accent_nucleus: accent,
3685
+ accent_high: isHigh(phrase_position, accent),
3686
+ accent_phrase_start: phrase_position === 1,
3687
+ accent_phrase_end: phrase_position === phrase_length,
3688
+ word_start: word_position === 1,
3689
+ word_end: word_position === morae.length,
3690
+ pos: current.pos || "*",
3691
+ pos_group1: current.pos_group1 || "*"
3692
+ });
3693
+ }
3694
+ }
3695
+ }
3696
+ return { reading: reading_parts.join(""), features: result };
3697
+ }
3698
+ function sparse_features(token) {
3699
+ if (token.pause || token.accent_phrase_position === void 0 || token.accent_phrase_length === void 0 || token.accent_nucleus === void 0) {
3700
+ return {};
3701
+ }
3702
+ const phrase_length = Math.max(1, token.accent_phrase_length);
3703
+ const phrase_position = token.accent_phrase_position;
3704
+ const nucleus = token.accent_nucleus;
3705
+ const result = {
3706
+ "accent_position": phrase_position / phrase_length,
3707
+ "accent_from_end": (phrase_length - phrase_position) / phrase_length,
3708
+ "accent_nucleus_position": nucleus / phrase_length,
3709
+ "accent_high": token.accent_high ? 1 : 0,
3710
+ "accent_phrase_start": token.accent_phrase_start ? 1 : 0,
3711
+ "accent_phrase_end": token.accent_phrase_end ? 1 : 0,
3712
+ "word_start": token.word_start ? 1 : 0,
3713
+ "word_end": token.word_end ? 1 : 0
3714
+ };
3715
+ result[`pos=${token.pos || "*"}`] = 1;
3716
+ result[`pos_group1=${token.pos_group1 || "*"}`] = 1;
3717
+ if (nucleus === 0) {
3718
+ result["accent_type=heiban"] = 1;
3719
+ } else if (phrase_position < nucleus) {
3720
+ result["accent_type=before"] = 1;
3721
+ } else if (phrase_position === nucleus) {
3722
+ result["accent_type=nucleus"] = 1;
3723
+ } else {
3724
+ result["accent_type=after"] = 1;
3725
+ }
3726
+ return result;
3727
+ }
3728
+ var TRAILING = /[\s」』))〕】>>"'”’]+$/u;
3729
+ function isQuestion(text) {
3730
+ return /[??]$/u.test(text.replace(TRAILING, ""));
3731
+ }
3732
+ function smoothstep(t) {
3733
+ const x2 = Math.min(1, Math.max(0, t));
3734
+ return x2 * x2 * (3 - 2 * x2);
3735
+ }
3736
+ function shapeProsody(prosody, features, options = {}) {
3737
+ const {
3738
+ question = false,
3739
+ questionRiseCents = 350,
3740
+ energyDbPerSemitone = 0.5,
3741
+ devoicedGain = 0.5,
3742
+ minGain = 0.35,
3743
+ maxGain = 1.8
3744
+ } = options;
3745
+ const { frame_ms: frameMs } = prosody.pitchCurve;
3746
+ const cents = prosody.pitchCurve.cents.slice();
3747
+ const count = Math.min(features.length, prosody.moraDurationsMs.length);
3748
+ const startFrame = [];
3749
+ const endFrame = [];
3750
+ let cursor = 0;
3751
+ for (let f = 0; f < count; f++) {
3752
+ startFrame.push(cursor / frameMs);
3753
+ cursor += prosody.moraDurationsMs[f];
3754
+ endFrame.push(cursor / frameMs);
3755
+ }
3756
+ const frameRange = (f) => [
3757
+ Math.max(0, Math.min(cents.length, Math.round(startFrame[f]))),
3758
+ Math.max(0, Math.min(cents.length, Math.round(endFrame[f])))
3759
+ ];
3760
+ if (question && questionRiseCents > 0) {
3761
+ let last = -1;
3762
+ for (let f = count - 1; f >= 0; f--) {
3763
+ if (!features[f].pause) {
3764
+ last = f;
3765
+ break;
3766
+ }
3767
+ }
3768
+ if (last >= 0) {
3769
+ const [from, to] = frameRange(last);
3770
+ if (to > from) {
3771
+ const existing = cents[to - 1] - cents[from];
3772
+ const shortfall = questionRiseCents - existing;
3773
+ if (shortfall > 0) {
3774
+ for (let frame = from; frame < cents.length; frame++) {
3775
+ const t = (frame - from) / (to - from);
3776
+ cents[frame] += shortfall * smoothstep((t - 0.25) / 0.75);
3777
+ }
3778
+ }
3779
+ }
3780
+ }
3781
+ }
3782
+ const moraGains = new Array(features.length).fill(1);
3783
+ for (let f = 0; f < count; f++) {
3784
+ if (features[f].pause) continue;
3785
+ let gainDb = 0;
3786
+ if (energyDbPerSemitone !== 0) {
3787
+ const [from, to] = frameRange(f);
3788
+ if (to > from) {
3789
+ let sum = 0;
3790
+ for (let frame = from; frame < to; frame++) sum += cents[frame];
3791
+ gainDb += sum / (to - from) / 100 * energyDbPerSemitone;
3792
+ }
3793
+ }
3794
+ let gain = 10 ** (gainDb / 20);
3795
+ if (prosody.devoiced[f]) gain *= devoicedGain;
3796
+ moraGains[f] = Math.min(maxGain, Math.max(minGain, gain));
3797
+ }
3798
+ return {
3799
+ ...prosody,
3800
+ pitchCurve: { frame_ms: frameMs, cents },
3801
+ moraGains
3802
+ };
3803
+ }
3804
+ function readingFromFeatures(features) {
3805
+ return features.map((frame) => frame.pause ? "\u3001" : frame.mora).join("");
3806
+ }
3807
+ var FS = WORLDLINE_SAMPLE_RATE;
3808
+ var msToSamples2 = (ms) => Math.round(ms / 1e3 * FS);
3809
+ function f0At(timeline, tMs) {
3810
+ const curve = timeline.f0_curve;
3811
+ if (curve.length === 0) return timeline.reference_hz || 220;
3812
+ const position = Math.max(0, tMs) / timeline.frame_ms;
3813
+ const left = Math.floor(position);
3814
+ if (left >= curve.length - 1) return curve[curve.length - 1];
3815
+ const progress = position - left;
3816
+ return curve[left] * (1 - progress) + curve[left + 1] * progress;
3817
+ }
3818
+ function applyMoraGains(plan, gains) {
3819
+ const morae = plan.morae ?? [];
3820
+ const timings = plan.mora_timings ?? [];
3821
+ const byIndex = morae.length === gains.length;
3822
+ const moraAt = (unit) => {
3823
+ if (byIndex) return unit.position;
3824
+ let found = -1;
3825
+ for (let i2 = 0; i2 < timings.length && i2 < gains.length; i2++) {
3826
+ if (timings[i2].StartMS <= unit.note_start_ms + 1e-6) found = i2;
3827
+ else break;
3828
+ }
3829
+ return found;
3830
+ };
3831
+ for (const unit of plan.timeline.units) {
3832
+ const index = moraAt(unit);
3833
+ const gain = gains[index];
3834
+ if (index < 0 || gain === void 0 || !Number.isFinite(gain)) continue;
3835
+ unit.volume *= gain;
3836
+ }
3837
+ }
3838
+ function planChunks(units2, firstChunkUnits, chunkUnits) {
3839
+ const ranges = [];
3840
+ if (units2.length === 0) return ranges;
3841
+ let start = 0;
3842
+ let coveredEndMs = units2[0].position_ms + units2[0].length_ms;
3843
+ let headSeam = false;
3844
+ for (let i2 = 1; i2 <= units2.length; i2++) {
3845
+ const limit = ranges.length === 0 ? firstChunkUnits : chunkUnits;
3846
+ const atEnd = i2 === units2.length;
3847
+ const clean = !atEnd && units2[i2].position_ms >= coveredEndMs - 1e-6;
3848
+ const full = !atEnd && i2 - start >= limit;
3849
+ if (atEnd || clean || full) {
3850
+ const tailSeam = !atEnd && !clean;
3851
+ ranges.push({ start, end: i2, headSeam, tailSeam });
3852
+ start = i2;
3853
+ headSeam = tailSeam;
3854
+ if (!atEnd) coveredEndMs = units2[i2].position_ms + units2[i2].length_ms;
3855
+ continue;
3856
+ }
3857
+ coveredEndMs = Math.max(
3858
+ coveredEndMs,
3859
+ units2[i2].position_ms + units2[i2].length_ms
3860
+ );
3861
+ }
3862
+ return ranges;
3863
+ }
3864
+ function seamTimeMs(next, crossfadeMs) {
3865
+ const earliest = next.position_ms + next.fade_in_ms + crossfadeMs / 2;
3866
+ const latest = next.position_ms + next.length_ms - crossfadeMs / 2;
3867
+ return Math.min(
3868
+ earliest,
3869
+ Math.max(next.position_ms + crossfadeMs / 2, latest)
3870
+ );
3871
+ }
3872
+ function applyFade(pcm, fromSample, samples, fadeIn) {
3873
+ const n = Math.max(1, Math.min(samples, pcm.length - fromSample));
3874
+ for (let k = 0; k < n; k++) {
3875
+ const t = (k + 0.5) / n;
3876
+ const gain = fadeIn ? Math.sin(Math.PI / 2 * t) : Math.cos(Math.PI / 2 * t);
3877
+ pcm[fromSample + k] *= gain;
3878
+ }
3879
+ }
3880
+ var UtauTTSAdapter = class _UtauTTSAdapter {
3881
+ constructor(worldline) {
3882
+ this.worldline = worldline;
3883
+ }
3884
+ worldline;
3885
+ static modelId = null;
3886
+ bankAliases = /* @__PURE__ */ new WeakMap();
3887
+ currentBank = null;
3888
+ pcmCache = /* @__PURE__ */ new Map();
3889
+ /**
3890
+ * Load the UtauTTS Go wasm. `wasm_exec.js` must already be on the page.
3891
+ * Pass `fetch` (e.g. koe's `fetchAsset`) to stream from the Cache API.
3892
+ */
3893
+ static async initializeWasm(wasmUrl = "utautts.wasm", options = {}) {
3894
+ if (typeof utautts_plan === "function") return;
3895
+ if (typeof Go === "undefined") {
3896
+ throw new Error(
3897
+ "wasm_exec.js must be loaded before calling initializeWasm."
3898
+ );
3899
+ }
3900
+ const go = new Go();
3901
+ const responsePromise = (options.fetch ?? fetch)(wasmUrl);
3902
+ let instance;
3903
+ try {
3904
+ instance = (await WebAssembly.instantiateStreaming(responsePromise, go.importObject)).instance;
3905
+ } catch {
3906
+ const bytes = await (await (options.fetch ?? fetch)(wasmUrl)).arrayBuffer();
3907
+ instance = (await WebAssembly.instantiate(bytes, go.importObject)).instance;
3908
+ }
3909
+ void go.run(instance);
3910
+ for (let attempt = 0; attempt < 100 && typeof utautts_plan !== "function"; attempt++) {
3911
+ await new Promise((resolve) => setTimeout(resolve, 10));
3912
+ }
3913
+ if (typeof utautts_plan !== "function") {
3914
+ throw new Error("utautts_plan failed to initialize in global scope.");
3915
+ }
3916
+ }
3917
+ static get ready() {
3918
+ return typeof utautts_plan === "function";
3919
+ }
3920
+ /** Parse and cache a prosody model (e.g. `frame-intonation-v8.json`) inside the wasm. */
3921
+ static setModel(modelJSON) {
3922
+ _UtauTTSAdapter.assertReady();
3923
+ const result = utautts_set_model(modelJSON);
3924
+ if (!result.success)
3925
+ throw new Error(`UtauTTS model error: ${result.error}`);
3926
+ _UtauTTSAdapter.modelId = result.id ?? null;
3927
+ return _UtauTTSAdapter.modelId;
3928
+ }
3929
+ static get currentModelId() {
3930
+ return _UtauTTSAdapter.modelId;
3931
+ }
3932
+ static assertReady() {
3933
+ if (typeof utautts_plan !== "function") {
3934
+ throw new Error(
3935
+ "UtauTTS wasm is not initialized; call UtauTTSAdapter.initializeWasm() first."
3936
+ );
3937
+ }
3938
+ }
3939
+ /**
3940
+ * Register a koe voice bank with the planner: the manifest becomes a virtual
3941
+ * oto.ini (koe PCM is pre-trimmed, so offset is 0) plus each sample's
3942
+ * recorded pitch. Called automatically by {@link plan}; cheap when unchanged.
3943
+ */
3944
+ setBank(bank) {
3945
+ _UtauTTSAdapter.assertReady();
3946
+ if (this.currentBank === bank && this.bankAliases.has(bank)) return;
3947
+ const entries = {};
3948
+ const pitch = {};
3949
+ for (const [alias, phoneme] of Object.entries(bank.manifest.phonemes)) {
3950
+ entries[alias] = [
3951
+ {
3952
+ Filename: `koe:${alias}`,
3953
+ Alias: alias,
3954
+ Offset: 0,
3955
+ Fixed: phoneme.consonant / 48,
3956
+ Blank: 0,
3957
+ Preutterance: phoneme.pre / 48,
3958
+ Overlap: phoneme.overlap / 48,
3959
+ SourceGroup: "koe"
3960
+ }
3961
+ ];
3962
+ if (phoneme.pitch > 0) pitch[alias] = phoneme.pitch;
3963
+ }
3964
+ const result = utautts_set_bank(
3965
+ JSON.stringify({
3966
+ name: "koe",
3967
+ oto_entries: entries,
3968
+ source_pitch_hz: pitch
3969
+ })
3970
+ );
3971
+ if (!result.success) throw new Error(`UtauTTS bank error: ${result.error}`);
3972
+ this.bankAliases.set(bank, result.aliases ?? 0);
3973
+ this.currentBank = bank;
3974
+ this.pcmCache.clear();
3975
+ }
3976
+ /**
3977
+ * Plan an utterance: unit selection, timing, pitch contour and worldline
3978
+ * placement. `features` are the mora-level frames from `openjtalkAnalyze`
3979
+ * (pauses included); the kana reading is derived from them.
3980
+ */
3981
+ plan(bank, text, features, options = {}) {
3982
+ this.setBank(bank);
3983
+ const request = {
3984
+ text,
3985
+ reading: readingFromFeatures(features),
3986
+ frames: features.map((frame) => sparse_features(frame)),
3987
+ tone: options.tone ?? "C4",
3988
+ mora_duration_ms: options.moraDurationMs ?? 0,
3989
+ pause_duration_ms: options.pauseDurationMs ?? 0,
3990
+ release_ms: options.releaseMs ?? 20,
3991
+ leading_preutterance_ms: options.leadingPreutteranceMs ?? 0,
3992
+ apply_pitch: options.applyPitch ?? true,
3993
+ intonation_strength: options.intonationStrength ?? 1,
3994
+ speech_timing: options.speechTiming ?? false,
3995
+ word_boundary_envelope: options.wordBoundaryEnvelope ?? false,
3996
+ mora_durations_ms: options.prosody?.moraDurationsMs,
3997
+ pitch_curve: options.prosody?.pitchCurve
3998
+ };
3999
+ const response = utautts_plan(JSON.stringify(request));
4000
+ if (!response.success || !response.plan) {
4001
+ throw new Error(`UtauTTS error: ${response.error ?? "no plan"}`);
4002
+ }
4003
+ const plan = JSON.parse(response.plan);
4004
+ const gains = options.prosody?.moraGains;
4005
+ if (gains) applyMoraGains(plan, gains);
4006
+ return plan;
4007
+ }
4008
+ getPcm(bank, alias) {
4009
+ let cached = this.pcmCache.get(alias);
4010
+ if (!cached) {
4011
+ cached = bank.getPcm(alias);
4012
+ this.pcmCache.set(alias, cached);
4013
+ }
4014
+ return cached;
4015
+ }
4016
+ /**
4017
+ * Render a plan chunk by chunk. Each chunk is independent audio positioned
4018
+ * at `startMs`; schedule them as they arrive (see the demo) or sum them.
4019
+ *
4020
+ * Chunk breaks fall on pauses when possible. Inside a phrase a break renders
4021
+ * one neighbouring unit of context on each side so the unit crossfade stays
4022
+ * WORLD's spectral one, then the two renders are joined with a short
4023
+ * equal-power crossfade in the following vowel.
4024
+ */
4025
+ async *renderChunks(bank, plan, options = {}) {
4026
+ const {
4027
+ firstChunkUnits = 3,
4028
+ chunkUnits = 6,
4029
+ seamCrossfadeMs = 20,
4030
+ signal
4031
+ } = options;
4032
+ const timeline = plan.timeline;
4033
+ const units2 = timeline.units.filter((unit) => unit.length_ms > 0);
4034
+ const ranges = planChunks(
4035
+ units2,
4036
+ Math.max(1, firstChunkUnits),
4037
+ Math.max(1, chunkUnits)
4038
+ );
4039
+ const crossfadeSamples = Math.max(2, msToSamples2(seamCrossfadeMs));
4040
+ for (let index = 0; index < ranges.length; index++) {
4041
+ if (signal?.aborted) return;
4042
+ const range = ranges[index];
4043
+ const renderStart = range.headSeam ? range.start - 1 : range.start;
4044
+ const renderEnd = range.tailSeam ? range.end + 1 : range.end;
4045
+ const rendered = units2.slice(renderStart, renderEnd);
4046
+ const baseMs = Math.min(...rendered.map((unit) => unit.position_ms));
4047
+ const phraseUnits = [];
4048
+ for (const unit of rendered) {
4049
+ const pcm2 = await this.getPcm(bank, unit.alias);
4050
+ if (!pcm2 || pcm2.length < MIN_WORLDLINE_SAMPLES) {
4051
+ console.warn(
4052
+ `[utautts] no usable PCM for alias "${unit.alias}"; skipped`
4053
+ );
4054
+ continue;
4055
+ }
4056
+ phraseUnits.push({
4057
+ pcm: pcm2,
4058
+ posMs: unit.position_ms - baseMs,
4059
+ skipMs: unit.skip_ms,
4060
+ lengthMs: unit.length_ms,
4061
+ fadeInMs: unit.fade_in_ms,
4062
+ fadeOutMs: unit.fade_out_ms,
4063
+ consonantMs: unit.consonant_ms,
4064
+ requiredLengthMs: unit.required_length_ms,
4065
+ volume: unit.volume,
4066
+ tone: unit.tone
4067
+ });
4068
+ }
4069
+ if (signal?.aborted) return;
4070
+ if (phraseUnits.length === 0) continue;
4071
+ const audio = this.worldline.renderPhrase({
4072
+ units: phraseUnits,
4073
+ pitch: (tMs) => f0At(timeline, baseMs + tMs),
4074
+ gender: options.gender,
4075
+ tension: options.tension,
4076
+ breathiness: options.breathiness,
4077
+ voicing: options.voicing
4078
+ });
4079
+ if (!audio || audio.length === 0) continue;
4080
+ let fromSample = 0;
4081
+ let toSample = audio.length;
4082
+ let startMs = baseMs;
4083
+ if (range.headSeam) {
4084
+ const seamMs = seamTimeMs(units2[range.start], seamCrossfadeMs);
4085
+ fromSample = Math.max(
4086
+ 0,
4087
+ msToSamples2(seamMs - baseMs) - crossfadeSamples / 2
4088
+ );
4089
+ startMs = baseMs + fromSample / FS * 1e3;
4090
+ }
4091
+ if (range.tailSeam) {
4092
+ const seamMs = seamTimeMs(units2[range.end], seamCrossfadeMs);
4093
+ toSample = Math.min(
4094
+ audio.length,
4095
+ msToSamples2(seamMs - baseMs) + crossfadeSamples / 2
4096
+ );
4097
+ }
4098
+ if (toSample <= fromSample) continue;
4099
+ const pcm = audio.slice(fromSample, toSample);
4100
+ if (range.headSeam) applyFade(pcm, 0, crossfadeSamples, true);
4101
+ if (range.tailSeam)
4102
+ applyFade(
4103
+ pcm,
4104
+ Math.max(0, pcm.length - crossfadeSamples),
4105
+ crossfadeSamples,
4106
+ false
4107
+ );
4108
+ yield { pcm, startMs, index, units: units2.slice(range.start, range.end) };
4109
+ await new Promise((resolve) => setTimeout(resolve, 0));
4110
+ }
4111
+ }
4112
+ /**
4113
+ * Plan + render an utterance into one buffer (Float32, 48 kHz) covering the
4114
+ * whole timeline. Use {@link plan} + {@link renderChunks} for streaming.
4115
+ */
4116
+ async synthesizeText(bank, text, features, options = {}) {
4117
+ const plan = this.plan(bank, text, features, options);
4118
+ const total = msToSamples2(plan.timeline.duration_ms) + msToSamples2(200);
4119
+ const out = new Float32Array(total);
4120
+ let any = false;
4121
+ for await (const chunk of this.renderChunks(bank, plan, options)) {
4122
+ any = true;
4123
+ const offset = msToSamples2(chunk.startMs);
4124
+ const n = Math.min(chunk.pcm.length, out.length - offset);
4125
+ for (let k = 0; k < n; k++) out[offset + k] += chunk.pcm[k];
4126
+ }
4127
+ return any ? out : null;
4128
+ }
4129
+ };
3172
4130
 
3173
4131
  // src/vibrato.ts
3174
4132
  var VIBRATO_MIN_SEC = 0.35;
@@ -3190,7 +4148,7 @@ var MAX_PORTAMENTO_MS = 400;
3190
4148
  var MIN_GLIDE_MS = 4;
3191
4149
  var MAX_GLIDE_SPAN_RATIO = 0.9;
3192
4150
  var unitsToHz2 = (units2) => 440 * 2 ** ((units2 - 2139) / 372);
3193
- var smoothstep = (u) => u * u * (3 - 2 * u);
4151
+ var smoothstep2 = (u) => u * u * (3 - 2 * u);
3194
4152
  var glideMsForSegments = (segments, totalMs) => segments.map((seg, i2) => {
3195
4153
  const atMs = seg.atSec * 1e3;
3196
4154
  const prevMs = i2 === 0 ? 0 : segments[i2 - 1].atSec * 1e3;
@@ -3224,7 +4182,7 @@ var pitchCurveFor = (baseHz, segments, preMs, vibrato, totalMs) => {
3224
4182
  continue;
3225
4183
  }
3226
4184
  if (sinceOnset <= k.startMs) break;
3227
- const u = smoothstep((sinceOnset - k.startMs) / k.glideMs);
4185
+ const u = smoothstep2((sinceOnset - k.startMs) / k.glideMs);
3228
4186
  hz = hz * (k.hz / hz) ** u;
3229
4187
  break;
3230
4188
  }
@@ -3240,6 +4198,177 @@ var segmentsCacheKey = (segments) => segments?.length ? `|s${segments.map(
3240
4198
  (s) => `${s.pitch}@${Math.round(s.atSec * 100)}${s.portamento ? "~" : ""}`
3241
4199
  ).join(",")}` : "";
3242
4200
 
4201
+ // src/speech.ts
4202
+ var DEFAULT_TTS_BASE_URL = "https://onjmin.github.io/koe/demo/utautts/";
4203
+ var joinUrl = (base, path) => `${base.endsWith("/") ? base : `${base}/`}${path}`;
4204
+ var loadGoRuntime = (url2) => new Promise((resolve, reject) => {
4205
+ if (typeof globalThis.Go !== "undefined") {
4206
+ resolve();
4207
+ return;
4208
+ }
4209
+ if (typeof document === "undefined") {
4210
+ reject(new Error("wasm_exec.js needs a document to load into"));
4211
+ return;
4212
+ }
4213
+ const script = document.createElement("script");
4214
+ script.src = url2;
4215
+ script.async = true;
4216
+ script.onload = () => resolve();
4217
+ script.onerror = () => reject(new Error(`failed to load ${url2}`));
4218
+ document.head.appendChild(script);
4219
+ });
4220
+ var importJpreprocess = (url2) => import(
4221
+ /* @vite-ignore */
4222
+ /* webpackIgnore: true */
4223
+ url2
4224
+ );
4225
+ var shared = null;
4226
+ var getSpeechPlanner = (options = {}) => {
4227
+ const baseUrl = options.baseUrl ?? DEFAULT_TTS_BASE_URL;
4228
+ if (shared) {
4229
+ if (shared.baseUrl !== baseUrl) {
4230
+ console.warn(
4231
+ `[dtm] TTS assets are already loaded from ${shared.baseUrl}; ignoring ${baseUrl}`
4232
+ );
4233
+ }
4234
+ return shared.planner;
4235
+ }
4236
+ const planner = createSpeechPlanner({ ...options, baseUrl });
4237
+ shared = { baseUrl, planner };
4238
+ return planner;
4239
+ };
4240
+ var createSpeechPlanner = (options = {}) => {
4241
+ const baseUrl = options.baseUrl ?? DEFAULT_TTS_BASE_URL;
4242
+ let jp = null;
4243
+ let adapter = null;
4244
+ let readyPromise = null;
4245
+ let isReady = false;
4246
+ const load2 = async () => {
4247
+ const progress = /* @__PURE__ */ new Map();
4248
+ const onProgress = (p) => {
4249
+ if (!options.onProgress) return;
4250
+ progress.set(p.url, p);
4251
+ let loaded = 0;
4252
+ let total = 0;
4253
+ for (const item of progress.values()) {
4254
+ loaded += item.loaded;
4255
+ total += item.total;
4256
+ }
4257
+ options.onProgress(loaded, total);
4258
+ };
4259
+ const fetchWithProgress = (url2) => fetchAsset(url2, { onProgress });
4260
+ const jpreprocessReady = (async () => {
4261
+ const glue2 = await importJpreprocess(
4262
+ joinUrl(baseUrl, "jpreprocess_wasm/jpreprocess_wasm.js")
4263
+ );
4264
+ await glue2.default({
4265
+ module_or_path: fetchWithProgress(
4266
+ joinUrl(baseUrl, "jpreprocess_wasm/jpreprocess_wasm_bg.wasm")
4267
+ )
4268
+ });
4269
+ if (!glue2.is_ready()) {
4270
+ const dict = await loadNaistJdic(
4271
+ joinUrl(baseUrl, "jpreprocess_wasm/naist-jdic"),
4272
+ { onProgress }
4273
+ );
4274
+ initJpreprocessDictionary(glue2, dict);
4275
+ }
4276
+ return glue2;
4277
+ })();
4278
+ const utauttsReady = (async () => {
4279
+ await loadGoRuntime(joinUrl(baseUrl, "wasm_exec.js"));
4280
+ await UtauTTSAdapter.initializeWasm(joinUrl(baseUrl, "utautts.wasm"), {
4281
+ fetch: fetchWithProgress
4282
+ });
4283
+ UtauTTSAdapter.setModel(
4284
+ await fetchAssetText(joinUrl(baseUrl, "frame-intonation-v8.json"), {
4285
+ onProgress
4286
+ })
4287
+ );
4288
+ })();
4289
+ const htsVoiceReady = fetchAssetBytes(
4290
+ joinUrl(baseUrl, "hts/tohoku-f01-neutral.htsvoice"),
4291
+ { onProgress }
4292
+ );
4293
+ const [glue, , htsVoice] = await Promise.all([
4294
+ jpreprocessReady,
4295
+ utauttsReady,
4296
+ htsVoiceReady
4297
+ ]);
4298
+ if (!glue.is_voice_ready()) glue.init_voice(htsVoice);
4299
+ jp = glue;
4300
+ adapter = new UtauTTSAdapter(null);
4301
+ isReady = true;
4302
+ };
4303
+ const ready = () => {
4304
+ if (!readyPromise) {
4305
+ readyPromise = load2().catch((err2) => {
4306
+ readyPromise = null;
4307
+ throw err2;
4308
+ });
4309
+ }
4310
+ return readyPromise;
4311
+ };
4312
+ const plan = (bank, text, o = {}) => {
4313
+ if (!jp || !adapter) {
4314
+ throw new Error("speech planner is not ready; await ready() first");
4315
+ }
4316
+ const intonationStrength = o.intonationStrength ?? 1;
4317
+ const nodes = JSON.parse(jp.analyze_text(text));
4318
+ const { features } = analyze2(nodes);
4319
+ if (features.length === 0) {
4320
+ throw new Error(`no readable morae in "${text}"`);
4321
+ }
4322
+ let prosody = null;
4323
+ if (jp.is_voice_ready()) {
4324
+ const frames = JSON.parse(jp.analyze_prosody(text, 1));
4325
+ prosody = alignHtsProsody(frames, features, { intonationStrength });
4326
+ if (prosody) {
4327
+ prosody = shapeProsody(prosody, features, {
4328
+ question: isQuestion(text)
4329
+ });
4330
+ }
4331
+ }
4332
+ return adapter.plan(bank, text, features, {
4333
+ tone: o.tone ?? "C4",
4334
+ intonationStrength,
4335
+ prosody: prosody ?? void 0
4336
+ });
4337
+ };
4338
+ return { ready, isReady: () => isReady, plan };
4339
+ };
4340
+ var prepareSpeechPlan = (plan, ratio, toReal = (a) => a) => {
4341
+ const timeline = plan.timeline;
4342
+ return {
4343
+ ...plan,
4344
+ timeline: {
4345
+ ...timeline,
4346
+ f0_curve: timeline.f0_curve.map((hz) => hz * ratio),
4347
+ units: timeline.units.map((u) => ({
4348
+ ...u,
4349
+ alias: toReal(u.alias),
4350
+ target_f0_hz: u.target_f0_hz * ratio
4351
+ }))
4352
+ }
4353
+ };
4354
+ };
4355
+ var speechPlanLeadingSec = (plan) => Math.max(0, plan.timeline.leading_ms || 0) / 1e3;
4356
+ var speechPlanDurationSec = (plan) => {
4357
+ const timeline = plan.timeline;
4358
+ let endMs = 0;
4359
+ for (const u of timeline.units) {
4360
+ endMs = Math.max(endMs, u.position_ms + u.length_ms);
4361
+ }
4362
+ if (endMs <= 0) endMs = timeline.duration_ms;
4363
+ return Math.max(0, endMs / 1e3 - speechPlanLeadingSec(plan));
4364
+ };
4365
+ var medianRecordedPitchHz = (phonemes) => {
4366
+ const pitches = Object.values(phonemes).map((p) => p.pitch).filter((hz) => hz > 0).sort((a, b) => a - b);
4367
+ if (pitches.length === 0) return void 0;
4368
+ const mid = pitches.length >> 1;
4369
+ return pitches.length % 2 ? pitches[mid] : (pitches[mid - 1] + pitches[mid]) / 2;
4370
+ };
4371
+
3243
4372
  // src/types.ts
3244
4373
  var DEFAULT_VOCAL_VOLUME = 200;
3245
4374
  var DEFAULT_BPM = 120;
@@ -3338,7 +4467,7 @@ var kanaTable = {
3338
4467
  \u307D: ["p", "o"],
3339
4468
  \u3093: ["N", "N"]
3340
4469
  };
3341
- var SMALL_KANA = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087";
4470
+ var SMALL_KANA2 = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087";
3342
4471
  var VOWEL_KANA = {
3343
4472
  a: "\u3042",
3344
4473
  i: "\u3044",
@@ -3353,13 +4482,43 @@ var REST_MARK = "_";
3353
4482
  var BREATH_MARK = "\u3001";
3354
4483
  var FADE_OUT_MARK = "\u2193";
3355
4484
  var FADE_IN_MARK = "\u2191";
4485
+ var SPEAK_OPEN = "\u300C";
4486
+ var SPEAK_CLOSE = "\u300D";
4487
+ var splitSpeech = (text) => {
4488
+ const pieces = [];
4489
+ let sung = "";
4490
+ let i2 = 0;
4491
+ while (i2 < text.length) {
4492
+ const ch = text[i2];
4493
+ if (ch === SPEAK_OPEN || ch === "\uFF62") {
4494
+ let end = -1;
4495
+ for (let j = i2 + 1; j < text.length; j++) {
4496
+ if (text[j] === SPEAK_CLOSE || text[j] === "\uFF63") {
4497
+ end = j;
4498
+ break;
4499
+ }
4500
+ }
4501
+ if (sung) pieces.push({ sung });
4502
+ sung = "";
4503
+ const body = end < 0 ? text.slice(i2 + 1) : text.slice(i2 + 1, end);
4504
+ pieces.push({ speak: body });
4505
+ i2 = end < 0 ? text.length : end + 1;
4506
+ continue;
4507
+ }
4508
+ sung += ch;
4509
+ i2++;
4510
+ }
4511
+ if (sung) pieces.push({ sung });
4512
+ return pieces;
4513
+ };
4514
+ var sanitizeSpeech = (text) => text.replace(/[\r\n;]+/g, " ").replace(/\s+/g, " ").trim();
3356
4515
  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, "");
3357
4516
  var splitSyllables = (text) => {
3358
4517
  const MARKS = `${TIE_MARK}${PORTAMENTO_MARK}${REST_MARK}${BREATH_MARK}${FADE_OUT_MARK}${FADE_IN_MARK}`;
3359
4518
  const result = [];
3360
4519
  for (const ch of text) {
3361
4520
  const prev = result[result.length - 1];
3362
- if (prev !== void 0 && SMALL_KANA.includes(ch) && !MARKS.includes(prev[prev.length - 1])) {
4521
+ if (prev !== void 0 && SMALL_KANA2.includes(ch) && !MARKS.includes(prev[prev.length - 1])) {
3363
4522
  result[result.length - 1] += ch;
3364
4523
  } else {
3365
4524
  result.push(ch);
@@ -3408,42 +4567,52 @@ var analyzeSyllable = (syllable) => {
3408
4567
  var normalizeLyrics = (text) => {
3409
4568
  const result = [];
3410
4569
  let prevVowel = "";
3411
- for (const mark of splitSyllables(sanitizeText(text))) {
3412
- if (mark === BREATH_MARK) {
3413
- const last = result[result.length - 1];
3414
- if (last) last.breathAfter = true;
4570
+ for (const piece of splitSpeech(text)) {
4571
+ if ("speak" in piece) {
4572
+ const body = sanitizeSpeech(piece.speak);
4573
+ result.push(
4574
+ body ? { kana: body, consonant: "", vowel: "", kind: "speak", text: body } : { kana: REST_MARK, consonant: "", vowel: "", kind: "rest" }
4575
+ );
3415
4576
  prevVowel = "";
3416
4577
  continue;
3417
4578
  }
3418
- if (mark === FADE_OUT_MARK) {
3419
- const last = result[result.length - 1];
3420
- if (last) last.fadeOut = true;
3421
- continue;
3422
- }
3423
- if (mark === FADE_IN_MARK) {
3424
- const last = result[result.length - 1];
3425
- if (last) last.fadeIn = true;
3426
- continue;
3427
- }
3428
- const syl = analyzeSyllable(mark);
3429
- if (syl.kind === "tie") {
3430
- if (!prevVowel) continue;
3431
- result.push({
3432
- ...syl,
3433
- kana: prevVowel === "N" ? "\u3093" : VOWEL_KANA[prevVowel] ?? syl.kana,
3434
- consonant: "",
3435
- vowel: prevVowel
3436
- });
3437
- continue;
4579
+ for (const mark of splitSyllables(sanitizeText(piece.sung))) {
4580
+ if (mark === BREATH_MARK) {
4581
+ const last = result[result.length - 1];
4582
+ if (last) last.breathAfter = true;
4583
+ prevVowel = "";
4584
+ continue;
4585
+ }
4586
+ if (mark === FADE_OUT_MARK) {
4587
+ const last = result[result.length - 1];
4588
+ if (last) last.fadeOut = true;
4589
+ continue;
4590
+ }
4591
+ if (mark === FADE_IN_MARK) {
4592
+ const last = result[result.length - 1];
4593
+ if (last) last.fadeIn = true;
4594
+ continue;
4595
+ }
4596
+ const syl = analyzeSyllable(mark);
4597
+ if (syl.kind === "tie") {
4598
+ if (!prevVowel) continue;
4599
+ result.push({
4600
+ ...syl,
4601
+ kana: prevVowel === "N" ? "\u3093" : VOWEL_KANA[prevVowel] ?? syl.kana,
4602
+ consonant: "",
4603
+ vowel: prevVowel
4604
+ });
4605
+ continue;
4606
+ }
4607
+ if (syl.kind === "rest") prevVowel = "";
4608
+ else if (syl.vowel) prevVowel = syl.vowel;
4609
+ result.push(syl);
3438
4610
  }
3439
- if (syl.kind === "rest") prevVowel = "";
3440
- else if (syl.vowel) prevVowel = syl.vowel;
3441
- result.push(syl);
3442
4611
  }
3443
4612
  return result;
3444
4613
  };
3445
4614
  var displayKana = (syl) => {
3446
- const head = syl.kind === "tie" ? syl.portamento ? PORTAMENTO_MARK : TIE_MARK : syl.kind === "rest" ? REST_MARK : syl.kana;
4615
+ 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;
3447
4616
  let out = syl.breathAfter ? head + BREATH_MARK : head;
3448
4617
  if (syl.fadeIn) out += FADE_IN_MARK;
3449
4618
  if (syl.fadeOut) out += FADE_OUT_MARK;
@@ -3932,6 +5101,40 @@ var collectPitchTokens = (aliases) => {
3932
5101
  }
3933
5102
  return [...seen].map(([token, midi]) => ({ token, midi }));
3934
5103
  };
5104
+ var nearestPitchToken = (pitchTokens, noteNum) => {
5105
+ let best = null;
5106
+ for (const t of pitchTokens) {
5107
+ if (!best || Math.abs(t.midi - noteNum) < Math.abs(best.midi - noteNum))
5108
+ best = t;
5109
+ }
5110
+ return best;
5111
+ };
5112
+ var createSpeechToneView = (phonemes, token) => {
5113
+ if (!token) {
5114
+ return {
5115
+ view: { manifest: { phonemes } },
5116
+ toReal: (a) => a,
5117
+ referenceHz: medianRecordedPitchHz(phonemes)
5118
+ };
5119
+ }
5120
+ const suffix = `_${token}`;
5121
+ const stripped = {};
5122
+ const real = /* @__PURE__ */ new Map();
5123
+ for (const [alias, entry] of Object.entries(phonemes)) {
5124
+ if (alias.endsWith(suffix)) {
5125
+ const bare = alias.slice(0, -suffix.length);
5126
+ stripped[bare] = entry;
5127
+ real.set(bare, alias);
5128
+ } else if (!PITCH_SUFFIX.test(alias) && !(alias in stripped)) {
5129
+ stripped[alias] = entry;
5130
+ }
5131
+ }
5132
+ return {
5133
+ view: { manifest: { phonemes: stripped } },
5134
+ toReal: (a) => real.get(a) ?? a,
5135
+ referenceHz: medianRecordedPitchHz(stripped)
5136
+ };
5137
+ };
3935
5138
  var resolveKoeAlias = (hasAlias, pitchTokens, syl, prevVowel, noteNum) => {
3936
5139
  const kana = syl.kana;
3937
5140
  const cons = syl.consonant === "N" ? "n" : syl.consonant;
@@ -4087,10 +5290,29 @@ var createLocalBackend = async (options) => {
4087
5290
  rate
4088
5291
  };
4089
5292
  };
5293
+ let speechAdapter = null;
5294
+ const renderSpeech = async (plan, expr, onChunk, signal) => {
5295
+ if (!worldline) return;
5296
+ speechAdapter ??= new UtauTTSAdapter(worldline);
5297
+ try {
5298
+ for await (const chunk of speechAdapter.renderChunks(bank, plan, {
5299
+ signal,
5300
+ gender: expr?.gender,
5301
+ breathiness: expr?.breathiness,
5302
+ tension: expr?.tension
5303
+ })) {
5304
+ onChunk({ pcm: chunk.pcm, startMs: chunk.startMs, index: chunk.index });
5305
+ }
5306
+ } catch (err2) {
5307
+ console.warn("[dtm] speech synthesis failed", err2);
5308
+ }
5309
+ };
4090
5310
  return {
4091
5311
  hasAlias: (a) => bank.has(a),
4092
5312
  pitchTokens: collectPitchTokens(Object.keys(bank.manifest.phonemes)),
4093
5313
  renderAlias,
5314
+ phonemes: bank.manifest.phonemes,
5315
+ renderSpeech,
4094
5316
  dispose: () => {
4095
5317
  }
4096
5318
  };
@@ -4106,7 +5328,9 @@ var spawnVoiceWorker = async (url2) => {
4106
5328
  var createWorkerBackend = async (workerUrl, options) => {
4107
5329
  const worker2 = await spawnVoiceWorker(workerUrl);
4108
5330
  const aliasSet = /* @__PURE__ */ new Set();
5331
+ let phonemes = {};
4109
5332
  const pending = /* @__PURE__ */ new Map();
5333
+ const speechPending = /* @__PURE__ */ new Map();
4110
5334
  let reqId = 0;
4111
5335
  let onReady = null;
4112
5336
  let onFail = null;
@@ -4114,6 +5338,7 @@ var createWorkerBackend = async (workerUrl, options) => {
4114
5338
  const m = ev.data;
4115
5339
  if (m.type === "ready") {
4116
5340
  for (const a of m.aliases) aliasSet.add(a);
5341
+ phonemes = m.phonemes ?? {};
4117
5342
  onReady?.();
4118
5343
  } else if (m.type === "error") {
4119
5344
  onFail?.(new Error(m.message));
@@ -4123,6 +5348,15 @@ var createWorkerBackend = async (workerUrl, options) => {
4123
5348
  pending.delete(m.id);
4124
5349
  cb(m);
4125
5350
  }
5351
+ } else if (m.type === "speech-chunk") {
5352
+ speechPending.get(m.id)?.onChunk({ pcm: m.pcm, startMs: m.startMs, index: m.index });
5353
+ } else if (m.type === "speech-end") {
5354
+ const s = speechPending.get(m.id);
5355
+ if (s) {
5356
+ speechPending.delete(m.id);
5357
+ if (m.error) console.warn("[dtm] speech synthesis failed", m.error);
5358
+ s.done();
5359
+ }
4126
5360
  }
4127
5361
  };
4128
5362
  worker2.onerror = (e) => {
@@ -4162,10 +5396,32 @@ var createWorkerBackend = async (workerUrl, options) => {
4162
5396
  pitchSegments
4163
5397
  });
4164
5398
  });
5399
+ const renderSpeech = (plan, expr, onChunk, signal) => new Promise((resolve) => {
5400
+ const id = ++reqId;
5401
+ speechPending.set(id, { onChunk, done: resolve });
5402
+ signal?.addEventListener("abort", () => {
5403
+ worker2.postMessage({
5404
+ type: "speak-abort",
5405
+ id
5406
+ });
5407
+ });
5408
+ worker2.postMessage({
5409
+ type: "speak",
5410
+ id,
5411
+ plan,
5412
+ gender: expr?.gender,
5413
+ breathiness: expr?.breathiness,
5414
+ tension: expr?.tension
5415
+ });
5416
+ });
4165
5417
  return {
4166
5418
  hasAlias: (a) => aliasSet.has(a),
4167
5419
  pitchTokens: collectPitchTokens(aliasSet),
4168
5420
  renderAlias,
5421
+ get phonemes() {
5422
+ return phonemes;
5423
+ },
5424
+ renderSpeech,
4169
5425
  dispose: () => worker2.terminate()
4170
5426
  };
4171
5427
  };
@@ -4353,6 +5609,206 @@ var createKoeVoice = async (ctx, destination, options) => {
4353
5609
  durationSec
4354
5610
  );
4355
5611
  };
5612
+ const planner = getSpeechPlanner({
5613
+ baseUrl: options.ttsBaseUrl
5614
+ });
5615
+ const toneViews = /* @__PURE__ */ new Map();
5616
+ const toneViewFor = (token) => {
5617
+ const k = token ?? "";
5618
+ let v = toneViews.get(k);
5619
+ if (!v) {
5620
+ v = createSpeechToneView(backend.phonemes, token);
5621
+ toneViews.set(k, v);
5622
+ }
5623
+ return v;
5624
+ };
5625
+ const toneFor = (pitch) => nearestPitchToken(backend.pitchTokens, unitsToMidiFloat(pitch))?.token ?? null;
5626
+ const plans = /* @__PURE__ */ new Map();
5627
+ const planFor = (text, token) => {
5628
+ const k = `${token ?? ""}|${text}`;
5629
+ let p = plans.get(k);
5630
+ if (!p) {
5631
+ p = (async () => {
5632
+ try {
5633
+ await planner.ready();
5634
+ return planner.plan(toneViewFor(token).view, text, {
5635
+ tone: token ?? "C4"
5636
+ });
5637
+ } catch (err2) {
5638
+ console.warn(`[dtm] speech plan failed for "${text}"`, err2);
5639
+ return null;
5640
+ }
5641
+ })();
5642
+ plans.set(k, p);
5643
+ }
5644
+ return p;
5645
+ };
5646
+ const speechCache = /* @__PURE__ */ new Map();
5647
+ 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)}` : ""}`;
5648
+ const SPEECH_PITCH_RATIO_MAX = 4;
5649
+ const speechEntryFor = (text, pitch, expr) => {
5650
+ const key = speechKeyOf(text, pitch, expr);
5651
+ let entry = speechCache.get(key);
5652
+ if (entry) return entry;
5653
+ const abort = new AbortController();
5654
+ let resolvePlanned;
5655
+ const planned = new Promise((r) => {
5656
+ resolvePlanned = r;
5657
+ });
5658
+ const e = {
5659
+ planned,
5660
+ rendered: Promise.resolve(),
5661
+ durationSec: 0,
5662
+ chunks: [],
5663
+ done: false,
5664
+ listeners: /* @__PURE__ */ new Set(),
5665
+ abort
5666
+ };
5667
+ e.rendered = (async () => {
5668
+ const token = toneFor(pitch);
5669
+ const plan = await planFor(text, token);
5670
+ if (!plan) {
5671
+ e.done = true;
5672
+ resolvePlanned(null);
5673
+ return;
5674
+ }
5675
+ const view = toneViewFor(token);
5676
+ const reference = plan.timeline.reference_hz || view.referenceHz || 0;
5677
+ const ratio = reference > 0 ? Math.min(
5678
+ SPEECH_PITCH_RATIO_MAX,
5679
+ Math.max(
5680
+ 1 / SPEECH_PITCH_RATIO_MAX,
5681
+ unitsToFreq(pitch) / reference
5682
+ )
5683
+ ) : 1;
5684
+ const prepared = prepareSpeechPlan(plan, ratio, view.toReal);
5685
+ e.durationSec = speechPlanDurationSec(prepared);
5686
+ const leadingSec = speechPlanLeadingSec(prepared);
5687
+ resolvePlanned(e.durationSec);
5688
+ await backend.renderSpeech(
5689
+ prepared,
5690
+ expr,
5691
+ (chunk) => {
5692
+ const buf = ctx.createBuffer(1, chunk.pcm.length, KOE_SAMPLE_RATE);
5693
+ buf.copyToChannel(chunk.pcm, 0);
5694
+ const r = {
5695
+ audio: buf,
5696
+ startSec: chunk.startMs / 1e3 - leadingSec
5697
+ };
5698
+ e.chunks.push(r);
5699
+ for (const l of e.listeners) l(r);
5700
+ },
5701
+ abort.signal
5702
+ );
5703
+ e.done = true;
5704
+ e.listeners.clear();
5705
+ })();
5706
+ entry = e;
5707
+ speechCache.set(key, entry);
5708
+ return entry;
5709
+ };
5710
+ const placeSpeechChunk = (r, t0, peak, pan, reverbSend = 0, delaySend = 0, destOverride) => {
5711
+ const dest = destOverride ?? destination;
5712
+ let startAt = t0 + r.startSec;
5713
+ let offset = 0;
5714
+ const earliest = ctx.currentTime + 0.01;
5715
+ if (startAt < earliest) {
5716
+ offset = earliest - startAt;
5717
+ startAt = earliest;
5718
+ }
5719
+ if (offset >= r.audio.duration - 5e-3) return;
5720
+ let out = dest;
5721
+ let panner = null;
5722
+ if (typeof ctx.createStereoPanner === "function") {
5723
+ panner = ctx.createStereoPanner();
5724
+ panner.pan.value = Math.max(-1, Math.min(1, pan));
5725
+ panner.connect(dest);
5726
+ out = panner;
5727
+ }
5728
+ let reverbSendGain = null;
5729
+ if (options.reverbBus && reverbSend > 0 && panner) {
5730
+ reverbSendGain = ctx.createGain();
5731
+ reverbSendGain.gain.value = Math.max(0, Math.min(1, reverbSend));
5732
+ panner.connect(reverbSendGain).connect(options.reverbBus);
5733
+ }
5734
+ let delaySendGain = null;
5735
+ if (options.delayBus && delaySend > 0 && panner) {
5736
+ delaySendGain = ctx.createGain();
5737
+ delaySendGain.gain.value = Math.max(0, Math.min(1, delaySend));
5738
+ panner.connect(delaySendGain).connect(options.delayBus);
5739
+ }
5740
+ const env = ctx.createGain();
5741
+ if (offset > 0) {
5742
+ env.gain.setValueAtTime(1e-4, startAt);
5743
+ env.gain.exponentialRampToValueAtTime(peak, startAt + 5e-3);
5744
+ } else {
5745
+ env.gain.setValueAtTime(peak, startAt);
5746
+ }
5747
+ const src = ctx.createBufferSource();
5748
+ src.buffer = r.audio;
5749
+ src.connect(env).connect(out);
5750
+ src.start(startAt, offset);
5751
+ active.add(src);
5752
+ src.onended = () => {
5753
+ active.delete(src);
5754
+ src.disconnect();
5755
+ env.disconnect();
5756
+ panner?.disconnect();
5757
+ reverbSendGain?.disconnect();
5758
+ delaySendGain?.disconnect();
5759
+ };
5760
+ };
5761
+ const speechListeners = /* @__PURE__ */ new Set();
5762
+ model.speakToCache = async (text, pitch, expr, awaitRender) => {
5763
+ const entry = speechEntryFor(text, pitch, expr);
5764
+ const duration = await entry.planned;
5765
+ if (duration === null) return null;
5766
+ if (awaitRender) await entry.rendered;
5767
+ return speechKeyOf(text, pitch, expr);
5768
+ };
5769
+ model.scheduleSpeech = (key, t0, peak, pan, reverbSend, delaySend, dest) => {
5770
+ const entry = speechCache.get(key);
5771
+ if (!entry) return;
5772
+ const place = (r) => placeSpeechChunk(r, t0, peak, pan, reverbSend, delaySend, dest);
5773
+ for (const r of entry.chunks) place(r);
5774
+ if (!entry.done) {
5775
+ entry.listeners.add(place);
5776
+ const reg = { entry, listener: place };
5777
+ speechListeners.add(reg);
5778
+ void entry.rendered.then(() => speechListeners.delete(reg));
5779
+ }
5780
+ };
5781
+ model.speechDurationSec = (key) => {
5782
+ const entry = speechCache.get(key);
5783
+ return entry && entry.durationSec > 0 ? entry.durationSec : void 0;
5784
+ };
5785
+ const previewDurations = /* @__PURE__ */ new Map();
5786
+ model.planSpeech = async (text) => {
5787
+ const cached = previewDurations.get(text);
5788
+ if (cached !== void 0) return cached;
5789
+ const token = backend.pitchTokens.length > 0 ? nearestPitchToken(
5790
+ backend.pitchTokens,
5791
+ unitsToMidiFloat(model.speechReferenceUnits?.() ?? 0)
5792
+ )?.token ?? null : null;
5793
+ const plan = await planFor(text, token);
5794
+ if (!plan) return null;
5795
+ const d = speechPlanDurationSec(plan);
5796
+ previewDurations.set(text, d);
5797
+ return d;
5798
+ };
5799
+ model.peekSpeechDurationSec = (text) => previewDurations.get(text);
5800
+ model.speechReferenceUnits = () => {
5801
+ const tokens = backend.pitchTokens;
5802
+ let hz;
5803
+ if (tokens.length > 0) {
5804
+ const sorted = tokens.slice().sort((a, b) => a.midi - b.midi);
5805
+ hz = toneViewFor(sorted[sorted.length >> 1].token).referenceHz;
5806
+ } else {
5807
+ hz = toneViewFor(null).referenceHz;
5808
+ }
5809
+ if (!hz || hz <= 0) return void 0;
5810
+ return Math.round(2139 + 372 * Math.log2(hz / 440));
5811
+ };
4356
5812
  model.stopAll = () => {
4357
5813
  for (const src of active) {
4358
5814
  try {
@@ -4362,6 +5818,9 @@ var createKoeVoice = async (ctx, destination, options) => {
4362
5818
  src.disconnect();
4363
5819
  }
4364
5820
  active.clear();
5821
+ for (const { entry, listener } of speechListeners)
5822
+ entry.listeners.delete(listener);
5823
+ speechListeners.clear();
4365
5824
  };
4366
5825
  model.reset = () => {
4367
5826
  prevVowel = "";
@@ -4540,7 +5999,8 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4540
5999
  lightweight: options.lightweight,
4541
6000
  voiceWorkerUrl: options.voiceWorkerUrl,
4542
6001
  reverbBus: options.reverbBus,
4543
- delayBus: options.delayBus
6002
+ delayBus: options.delayBus,
6003
+ ttsBaseUrl: options.ttsBaseUrl
4544
6004
  })
4545
6005
  ))().then((v) => {
4546
6006
  loaded.set(m, v);
@@ -4575,6 +6035,11 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4575
6035
  prevVowel = "";
4576
6036
  continue;
4577
6037
  }
6038
+ if (syl.kind === "speak") {
6039
+ fn(note, "");
6040
+ prevVowel = "";
6041
+ continue;
6042
+ }
4578
6043
  if (syl.consonant === "Q" || syl.vowel === "") continue;
4579
6044
  fn(note, prevVowel);
4580
6045
  if (syl.vowel && syl.vowel !== "N") prevVowel = syl.vowel;
@@ -4595,6 +6060,17 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4595
6060
  forEachSungNote(track, (note, prevVowel) => {
4596
6061
  if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
4597
6062
  n++;
6063
+ if (note.syllable.kind === "speak") {
6064
+ tasks.push({
6065
+ model: m,
6066
+ note,
6067
+ prevVowel: "",
6068
+ pitch: note.pitch,
6069
+ expr,
6070
+ speak: note.syllable.text ?? ""
6071
+ });
6072
+ return;
6073
+ }
4598
6074
  tasks.push({
4599
6075
  model: m,
4600
6076
  note,
@@ -4625,15 +6101,24 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4625
6101
  let done = 0;
4626
6102
  onProgress?.(done, total);
4627
6103
  const promises = tasks.map(async (task) => {
4628
- await (task.model.renderToCache?.(
4629
- task.note.syllable,
4630
- task.prevVowel,
4631
- task.pitch,
4632
- task.note.durationSec * 1e3,
4633
- task.vibrato,
4634
- task.expr,
4635
- task.pitchSegments
4636
- ) ?? Promise.resolve(null));
6104
+ if (task.speak !== void 0) {
6105
+ await (task.model.speakToCache?.(
6106
+ task.speak,
6107
+ task.pitch,
6108
+ task.expr,
6109
+ true
6110
+ ) ?? Promise.resolve(null));
6111
+ } else {
6112
+ await (task.model.renderToCache?.(
6113
+ task.note.syllable,
6114
+ task.prevVowel,
6115
+ task.pitch,
6116
+ task.note.durationSec * 1e3,
6117
+ task.vibrato,
6118
+ task.expr,
6119
+ task.pitchSegments
6120
+ ) ?? Promise.resolve(null));
6121
+ }
4637
6122
  done++;
4638
6123
  onProgress?.(done, total);
4639
6124
  });
@@ -4670,6 +6155,49 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4670
6155
  }
4671
6156
  if (opts?.isAudible && !opts.isAudible(track)) continue;
4672
6157
  const t0 = anchorTime + startSec;
6158
+ if (note.syllable.kind === "speak") {
6159
+ const text = note.syllable.text ?? "";
6160
+ if (text && model.speakToCache && model.scheduleSpeech) {
6161
+ const speakToCache = model.speakToCache;
6162
+ const scheduleSpeech = model.scheduleSpeech;
6163
+ const dest = options.getTrackDestination?.(track.id ?? "");
6164
+ const effPeak = dest ? peak * masterVolumeScalar : peak;
6165
+ void (async () => {
6166
+ const key = await speakToCache(text, note.pitch, {
6167
+ gender: track.gender,
6168
+ breathiness: track.breathiness,
6169
+ tension: track.tension
6170
+ });
6171
+ if (session !== streamSession || !key) return;
6172
+ const delay = ctx.currentTime - t0;
6173
+ if (delay < 1) {
6174
+ scheduleSpeech(
6175
+ key,
6176
+ t0,
6177
+ effPeak,
6178
+ track.pan,
6179
+ track.reverbSend,
6180
+ track.delaySend,
6181
+ dest
6182
+ );
6183
+ opts?.onScheduled?.(
6184
+ track,
6185
+ {
6186
+ ...note,
6187
+ durationSec: model.speechDurationSec?.(key) ?? note.durationSec
6188
+ },
6189
+ t0
6190
+ );
6191
+ } else {
6192
+ console.warn(
6193
+ `[dtm] Speech late skip: \u300C${text}\u300D at ${startSec}s (delayed by ${delay.toFixed(3)}s)`
6194
+ );
6195
+ opts?.onLateSkip?.(note, delay);
6196
+ }
6197
+ })();
6198
+ }
6199
+ continue;
6200
+ }
4673
6201
  const dispatchNote = (offsetUnits, peakScale) => {
4674
6202
  const pitch = units(note.pitch + offsetUnits);
4675
6203
  const pitchSegments = transposeSegments(
@@ -4796,6 +6324,12 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4796
6324
  masterGain.gain.value = g;
4797
6325
  masterVolumeScalar = g;
4798
6326
  };
6327
+ const planSpeech = async (model, text) => {
6328
+ const v = await load2(model);
6329
+ return v?.planSpeech ? v.planSpeech(text) : null;
6330
+ };
6331
+ const peekSpeechDurationSec = (model, text) => loaded.get(model.toLowerCase())?.peekSpeechDurationSec?.(text);
6332
+ const getSpeechReferenceUnits = (model) => loaded.get(model.toLowerCase())?.speechReferenceUnits?.();
4799
6333
  return {
4800
6334
  loadModels,
4801
6335
  registerVoicebanks,
@@ -4803,7 +6337,10 @@ var createSingingVoices = (ctx, destination, options = {}) => {
4803
6337
  startStream,
4804
6338
  stopStream,
4805
6339
  reset,
4806
- setVolume
6340
+ setVolume,
6341
+ planSpeech,
6342
+ peekSpeechDurationSec,
6343
+ getSpeechReferenceUnits
4807
6344
  };
4808
6345
  };
4809
6346
  var createVoiceRegistry = (models = {}, fallback = "klatt") => {
@@ -4824,7 +6361,7 @@ var NATURAL_STEPS2 = {
4824
6361
  };
4825
6362
  var isNatural = (ch) => Object.hasOwn(NATURAL_STEPS2[12], ch);
4826
6363
  var clamp3 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
4827
- var META_DIRECTIVE = /#(inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo|loop)=([\w:-]+)/gi;
6364
+ var META_DIRECTIVE = /#(ver|seed|compose|inst|drum|drumfont|volume|drumvolume|reverb|reverbdecay|reverbpredelay|delay|delaydiv|mastercomp|fadein|fadeout|mode|edo|loop)=([\w:.-]+)/gi;
4828
6365
  var AUDIO_URL_DIRECTIVE = /#audio=([^\s#;\r\n]+)/gi;
4829
6366
  var AUDIO_NUM_DIRECTIVE = /#audio(start|end|offset|at|vol)=(-?\d+(?:\.\d+)?)/gi;
4830
6367
  var TRACK_INST_DIRECTIVE = /#t(\d+)inst=([^#;\r\n]+)/gi;
@@ -4841,6 +6378,10 @@ var parseMmlMeta = (mml) => {
4841
6378
  for (const m of mml.matchAll(META_DIRECTIVE)) {
4842
6379
  const key = m[1].toLowerCase();
4843
6380
  if (key === "ver") meta.version = m[2];
6381
+ else if (key === "seed") {
6382
+ const n = Number.parseInt(m[2], 10);
6383
+ if (Number.isFinite(n) && n >= 0) meta.seed = n;
6384
+ } else if (key === "compose") meta.compose = m[2];
4844
6385
  else if (key === "inst") meta.instrument = m[2];
4845
6386
  else if (key === "drum") meta.drum = m[2];
4846
6387
  else if (key === "drumfont") meta.drumFont = m[2];
@@ -5008,6 +6549,8 @@ var formatMmlMeta = (meta, space = "") => {
5008
6549
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
5009
6550
  if (meta.edo !== void 0 && meta.edo !== 12) parts.push(`#edo=${meta.edo}`);
5010
6551
  if (meta.loop) parts.push("#loop=on");
6552
+ if (meta.seed !== void 0) parts.push(`#seed=${meta.seed}`);
6553
+ if (meta.compose) parts.push(`#compose=${meta.compose}`);
5011
6554
  if (meta.audio) {
5012
6555
  parts.push(`#audio=${meta.audio}`);
5013
6556
  if (meta.audioStart) parts.push(`#audiostart=${round3(meta.audioStart)}`);
@@ -8438,6 +9981,78 @@ var DAW_CSS = `
8438
9981
  .dtm-daw *::before,
8439
9982
  .dtm-daw *::after { box-sizing: border-box; }
8440
9983
 
9984
+ /* \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
9985
+ \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
9986
+ \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
9987
+ \u4F55\u679A\u958B\u3044\u3066\u3082\u30ED\u30FC\u30EB\u3068\u306E\u8DDD\u96E2\u306F0\u306E\u307E\u307E\u306B\u306A\u308B\u3002
9988
+
9989
+ \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
9990
+ \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
9991
+ \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
9992
+ \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
9993
+ \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
9994
+ \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 */
9995
+ .dtm-daw-panels {
9996
+ display: flex;
9997
+ flex-direction: column;
9998
+ gap: var(--dtm-gap);
9999
+ min-width: 0;
10000
+ }
10001
+ /* \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
10002
+ \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 */
10003
+ .dtm-daw-head { display: contents; }
10004
+ .dtm-topbar {
10005
+ position: sticky;
10006
+ top: 0;
10007
+ z-index: 20;
10008
+ }
10009
+ @media (min-height: 760px) {
10010
+ .dtm-daw-head {
10011
+ display: flex;
10012
+ flex-direction: column;
10013
+ gap: var(--dtm-gap);
10014
+ min-width: 0;
10015
+ position: sticky;
10016
+ top: 0;
10017
+ z-index: 20;
10018
+ /* \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
10019
+ .dtm-daw \u306E gap \u3076\u3093\u3092 padding \u3067\u57CB\u3081\u3001\u9699\u9593\u304B\u3089\u4E2D\u8EAB\u3092\u898B\u305B\u306A\u3044\u3002 */
10020
+ background: var(--dtm-bg);
10021
+ padding-bottom: var(--dtm-gap);
10022
+ }
10023
+ }
10024
+
10025
+ /* \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
10026
+ \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
10027
+ \u753B\u9762\u306E\u9AD8\u3055\u306B\u95A2\u4FC2\u306A\u304F\u8CBC\u308C\u308B\u3057\u3001\u30ED\u30FC\u30EB\u3082\u5E83\u304F\u4F7F\u3048\u308B
10028
+ \uFF08.dtm-daw--wide \u306F daw.ts \u306E ResizeObserver \u304C\u4ED8\u3051\u308B\uFF09\u3002 */
10029
+ .dtm-daw--wide {
10030
+ flex-direction: row;
10031
+ align-items: flex-start;
10032
+ }
10033
+ .dtm-daw--wide .dtm-daw-head {
10034
+ display: flex;
10035
+ flex-direction: column;
10036
+ gap: var(--dtm-gap);
10037
+ min-width: 0;
10038
+ flex: 1 1 auto;
10039
+ position: sticky;
10040
+ top: 0;
10041
+ z-index: 20;
10042
+ background: var(--dtm-bg);
10043
+ padding-bottom: 0;
10044
+ }
10045
+ .dtm-daw--wide .dtm-daw-panels {
10046
+ flex: 0 0 360px;
10047
+ }
10048
+ /* 768px \u6642\u70B9\u306E height:420px \u306F max-height:32vh \u306B\u982D\u6253\u3061\u306B\u3055\u308C\u3066\u3044\u305F\u3002
10049
+ 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
10050
+ \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 */
10051
+ .dtm-daw--wide .dtm-roll {
10052
+ height: min(420px, 45vh);
10053
+ max-height: none;
10054
+ }
10055
+
8441
10056
  /* \u2500\u2500\u2500 \u30B2\u30FC\u30E0\u30A6\u30A3\u30F3\u30C9\u30A6\u5171\u901A\u67A0 \u2500\u2500\u2500 */
8442
10057
  /* \u5916\u67A0(\u9ED22px) \u2192 \u8272\u4ED8\u304D2px border \u2192 \u5185\u67A0(\u9ED2inset2px) \u306E3\u91CD\u69CB\u9020 */
8443
10058
  .dtm-win {
@@ -8500,9 +10115,6 @@ var DAW_CSS = `
8500
10115
 
8501
10116
  /* \u2500\u2500\u2500 \u30C8\u30E9\u30F3\u30B9\u30DD\u30FC\u30C8\u30D0\u30FC\uFF08HUD\u30B9\u30BF\u30A4\u30EB\uFF09 \u2500\u2500\u2500 */
8502
10117
  .dtm-topbar {
8503
- position: sticky;
8504
- top: 0;
8505
- z-index: 20;
8506
10118
  display: flex;
8507
10119
  flex-wrap: wrap;
8508
10120
  align-items: center;
@@ -8947,19 +10559,17 @@ var DAW_CSS = `
8947
10559
  .dtm-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
8948
10560
  .dtm-track-body { display: flex; flex-direction: column; gap: 10px; }
8949
10561
 
8950
- /* \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 */
8951
- .dtm-compose-container {
8952
- background: var(--dtm-deep);
8953
- border: 2px solid var(--c-black);
10562
+ /* \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 */
10563
+ .dtm-panel--compose {
8954
10564
  box-shadow:
8955
- inset 0 0 0 1px var(--dtm-border2),
8956
- 0 0 0 1px var(--dtm-success),
8957
- 3px 3px 0 var(--c-black);
8958
- padding: 10px 12px 12px;
8959
- display: flex;
8960
- flex-direction: column;
8961
- gap: 10px;
8962
- margin-bottom: 6px;
10565
+ inset 0 0 0 2px var(--c-black),
10566
+ 0 0 0 2px var(--dtm-success),
10567
+ 4px 4px 0 var(--c-black);
10568
+ }
10569
+ .dtm-panel--compose > summary { color: var(--dtm-success); }
10570
+ .dtm-panel--compose > summary::before,
10571
+ .dtm-panel--compose[open] > summary::before {
10572
+ background: var(--dtm-success);
8963
10573
  }
8964
10574
 
8965
10575
  /* \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 */
@@ -10072,6 +11682,8 @@ var DAW_CSS = `
10072
11682
  /* \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 */
10073
11683
  .dtm-tk--lyric-stop,
10074
11684
  .dtm-tk--lyric-rest { color: var(--dtm-border2); }
11685
+ /* \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 */
11686
+ .dtm-tk--lyric-speak { color: var(--dtm-primary); letter-spacing: 0; }
10075
11687
  .dtm-tk--break { color: var(--dtm-muted); opacity: 0.7; margin: 0 2px; }
10076
11688
  .dtm-tk--meta { color: var(--dtm-border2); margin-right: 4px; }
10077
11689
  .dtm-tk.is-active {
@@ -21765,6 +23377,16 @@ var BASS_SKELETONS = [
21765
23377
  "pedal"
21766
23378
  ];
21767
23379
  var pick = (items, rnd) => items[Math.floor(rnd() * items.length)];
23380
+ var seededRandom = (seed) => {
23381
+ let a = seed >>> 0;
23382
+ return () => {
23383
+ a = a + 1831565813 >>> 0;
23384
+ let t = a;
23385
+ t = Math.imul(t ^ t >>> 15, t | 1);
23386
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
23387
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
23388
+ };
23389
+ };
21768
23390
  var durationEntropy = (durations) => {
21769
23391
  if (durations.length === 0) return 0;
21770
23392
  const counts = /* @__PURE__ */ new Map();
@@ -24708,6 +26330,10 @@ var buildUI = (target, options) => {
24708
26330
  ).join("");
24709
26331
  target.innerHTML = `
24710
26332
  <div class="dtm-daw" data-dtm="root">
26333
+ <!-- \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
26334
+ \u753B\u9762\u4E0A\u90E8\u3078\u8CBC\u308A\u4ED8\u3051\u308B\uFF08.dtm-daw-head \u304C position:sticky\uFF09\u3002\u30D1\u30CD\u30EB\u3092
26335
+ \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 -->
26336
+ <div class="dtm-daw-head">
24711
26337
  <div class="dtm-topbar" data-dtm="transport">
24712
26338
  <div class="dtm-topbar-row1">
24713
26339
  <button class="dtm-iconbtn" data-dtm="prev-bar" title="1\u5C0F\u7BC0\u524D">${icon("chevronLeft")}</button>
@@ -24752,6 +26378,10 @@ var buildUI = (target, options) => {
24752
26378
  <div class="dtm-vscroll" data-dtm="vscroll"><div class="dtm-vscroll-thumb" data-dtm="vscroll-thumb"></div></div>
24753
26379
  </div>
24754
26380
  <div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
26381
+ </div>
26382
+
26383
+ <!-- \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 -->
26384
+ <div class="dtm-daw-panels">
24755
26385
 
24756
26386
  <details class="dtm-panel" data-dtm-acc="track" open>
24757
26387
  <summary>\u500B\u5225\u30C8\u30E9\u30C3\u30AF\u8A2D\u5B9A</summary>
@@ -24950,7 +26580,7 @@ var buildUI = (target, options) => {
24950
26580
  </details>
24951
26581
 
24952
26582
  <details class="dtm-panel ${showMidi ? "" : "dtm-hidden"}" data-dtm="midi-panel" data-dtm-acc="io-in">
24953
- <summary>MIDI / UST / MML \u5165\u529B</summary>
26583
+ <summary>MIDI / MusicXML / UST / MML \u5165\u529B</summary>
24954
26584
  <div class="dtm-panel-body">
24955
26585
  <div class="dtm-row" style="flex-wrap:nowrap">
24956
26586
  <div style="display: inline-flex; flex-direction: column; align-items: center; gap: 4px; justify-content: center; flex-shrink:0;">
@@ -24961,6 +26591,16 @@ var buildUI = (target, options) => {
24961
26591
  <button class="dtm-btn dtm-btn--success" data-dtm="midi-load" style="flex-shrink:0">\u8AAD\u8FBC</button>
24962
26592
  </div>
24963
26593
  <div class="dtm-row dtm-hidden" data-dtm="midi-track-selection"></div>
26594
+ <div class="dtm-row" style="flex-wrap:nowrap">
26595
+ <div style="display: inline-flex; flex-direction: column; align-items: center; gap: 4px; justify-content: center; flex-shrink:0;">
26596
+ <span class="dtm-label" style="line-height: 1;">MusicXML</span>
26597
+ <button class="dtm-infobtn" data-dtm="musicxml-info" title="MusicXML\u306E\u8AAD\u307F\u8FBC\u307F\u89E3\u8AAC">${icon("info", 12)}</button>
26598
+ </div>
26599
+ <input type="file" class="dtm-input dtm-grow" accept=".musicxml,.xml" data-dtm="musicxml-input" style="min-width:0">
26600
+ <button class="dtm-btn dtm-btn--success" data-dtm="musicxml-load" style="flex-shrink:0">\u8AAD\u8FBC</button>
26601
+ </div>
26602
+ <div class="dtm-row dtm-hidden" data-dtm="musicxml-part-selection"></div>
26603
+ <p class="dtm-load-note dtm-hidden" data-dtm="musicxml-load-note"></p>
24964
26604
  <div class="dtm-row" style="flex-wrap:nowrap">
24965
26605
  <div style="display: inline-flex; flex-direction: column; align-items: center; gap: 4px; justify-content: center; flex-shrink:0;">
24966
26606
  <span class="dtm-label" style="line-height: 1;">UST</span>
@@ -24990,129 +26630,135 @@ var buildUI = (target, options) => {
24990
26630
  </div>
24991
26631
  </details>
24992
26632
 
24993
- <details class="dtm-panel" data-dtm-acc="macro">
24994
- <summary>\u30DE\u30AF\u30ED</summary>
26633
+ <details class="dtm-panel dtm-panel--compose ${showCompose ? "" : "dtm-hidden"}" data-dtm-acc="compose">
26634
+ <summary>\u81EA\u52D5\u4F5C\u66F2</summary>
24995
26635
  <div class="dtm-panel-body">
24996
- <div class="dtm-compose-container ${showCompose ? "" : "dtm-hidden"}" data-dtm="compose-container">
24997
- <div class="dtm-row" data-dtm="compose-row">
24998
- <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>
24999
- <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>
25000
- <button class="dtm-infobtn" data-dtm="macro-compose-info" title="\u4F5C\u66F2\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
25001
- <!--
25002
- **\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
25003
- \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
25004
- 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
25005
- \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
25006
- -->
25007
- <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>
25008
- <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>
25009
- <span class="dtm-grow"></span>
25010
- </div>
25011
- <div class="dtm-row" data-dtm="compose-template-row">
25012
- <span class="dtm-label">\u69CB\u6210</span>
25013
- <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">
25014
- <option value="custom">\u81EA\u7531\u9078\u629E\uFF08\u4E0B\u8A18\u30C1\u30A7\u30C3\u30AF\uFF09</option>
25015
- <option value="1chorus">1\u30B3\u30FC\u30E9\u30B9\uFF08\u77ED\u3081\u30FB\u521D\u5FC3\u8005\u5411\u3051\uFF09</option>
25016
- <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>
25017
- <option value="jpop_drop">\u843D\u3061\u30B5\u30D3\u5165\u308A\uFF08JPOP\u738B\u9053 + \u30E9\u30B9\u30B5\u30D3\u524D\u843D\u3061\u30B5\u30D3\uFF09</option>
25018
- <option value="vocaloid">\u30DC\u30AB\u30ED\u738B\u9053\uFF08\u75BE\u8D70\u30FB2\u756A/C\u30E1\u30ED/\u30E9\u30B9\u30B5\u30D3\uFF09</option>
25019
- <option value="verse_chorus">Verse-Chorus\uFF08B\u30E1\u30ED\u306A\u3057\u30FB\u6D0B\u697D\u98A8\uFF09</option>
25020
- </select>
25021
- </div>
25022
- <div class="dtm-row" data-dtm="compose-sections-row">
25023
- <span class="dtm-label">\u4F5C\u308B\u90E8\u5206</span>
25024
- <div class="dtm-checks" data-dtm="compose-sections">
25025
- <label class="dtm-check"><input type="checkbox" value="intro" checked>\u30A4\u30F3\u30C8\u30ED</label>
25026
- <label class="dtm-check"><input type="checkbox" value="verse" checked>A\u30E1\u30ED</label>
25027
- <label class="dtm-check"><input type="checkbox" value="prechorus" checked>B\u30E1\u30ED</label>
25028
- <label class="dtm-check"><input type="checkbox" value="chorus" checked>\u30B5\u30D3</label>
25029
- <label class="dtm-check"><input type="checkbox" value="bridge">C\u30E1\u30ED</label>
25030
- <label class="dtm-check"><input type="checkbox" value="drop_chorus">\u843D\u3061\u30B5\u30D3</label>
25031
- <label class="dtm-check"><input type="checkbox" value="interlude">\u9593\u594F</label>
25032
- <label class="dtm-check"><input type="checkbox" value="outro">\u30A2\u30A6\u30C8\u30ED</label>
25033
- </div>
25034
- <span class="dtm-grow"></span>
25035
- <span class="dtm-hint" data-dtm="compose-sections-len"></span>
25036
- </div>
25037
- <div class="dtm-row" data-dtm="compose-key-row">
25038
- <span class="dtm-label">\u30D9\u30FC\u30B9\u8ABF</span>
25039
- <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">
25040
- <option value="any" title="\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057</option>
25041
- <optgroup label="\u57FA\u672C">
25042
- <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>
25043
- <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>
25044
- </optgroup>
25045
- <optgroup label="\u96F0\u56F2\u6C17\u304B\u3089\u9078\u3076\uFF08\u62BD\u9078\uFF09">
25046
- <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>
25047
- <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>
25048
- <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>
25049
- <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>
25050
- <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>
25051
- <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>
25052
- <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>
25053
- <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>
25054
- </optgroup>
25055
- <optgroup label="\u9577\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
25056
- <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>
25057
- <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>
25058
- <option value="key_D" title="\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0">\u30CB\u9577\u8ABF (D)</option>
25059
- <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>
25060
- <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>
25061
- <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>
25062
- <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>
25063
- <option value="key_G" title="\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F">\u30C8\u9577\u8ABF (G)</option>
25064
- <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>
25065
- <option value="key_A" title="\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D">\u30A4\u9577\u8ABF (A)</option>
25066
- <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>
25067
- <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>
25068
- </optgroup>
25069
- <optgroup label="\u77ED\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
25070
- <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>
25071
- <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>
25072
- <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>
25073
- <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>
25074
- <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>
25075
- <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>
25076
- <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>
25077
- <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>
25078
- <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>
25079
- <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>
25080
- <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>
25081
- <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>
25082
- </optgroup>
25083
- </select>
25084
- <span class="dtm-grow"></span>
25085
- <span class="dtm-hint" data-dtm="compose-key-hint"></span>
25086
- </div>
25087
- <div class="dtm-row" data-dtm="compose-scale-row">
25088
- <span class="dtm-label">\u97F3\u968E</span>
25089
- <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">
25090
- <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>
25091
- <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>
25092
- <optgroup label="\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF085\u97F3\u97F3\u968E\uFF09">
25093
- <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>
25094
- <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>
25095
- <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>
25096
- <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>
25097
- <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>
25098
- </optgroup>
25099
- <optgroup label="\u30C1\u30E3\u30FC\u30C1\u30E2\u30FC\u30C9\uFF087\u97F3\u97F3\u968E\uFF09">
25100
- <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>
25101
- <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>
25102
- <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>
25103
- <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>
25104
- </optgroup>
25105
- <optgroup label="\u7279\u6B8A\u97F3\u968E\uFF08\u97F3\u7A0B\u96C6\u5408\u3054\u3068\u5165\u308C\u66FF\u308F\u308B\uFF09">
25106
- <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>
25107
- <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>
25108
- <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>
25109
- <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>
25110
- </optgroup>
25111
- </select>
25112
- <span class="dtm-grow"></span>
25113
- <span class="dtm-hint" data-dtm="compose-scale-hint"></span>
26636
+ <div class="dtm-row" data-dtm="compose-row">
26637
+ <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>
26638
+ <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>
26639
+ <button class="dtm-infobtn" data-dtm="macro-compose-info" title="\u4F5C\u66F2\u306E\u89E3\u8AAC">${icon("info", 12)}</button>
26640
+ <!--
26641
+ **\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
26642
+ \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
26643
+ 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
26644
+ \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
26645
+ \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
26646
+ 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
26647
+ -->
26648
+ <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>
26649
+ <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>
26650
+ <span class="dtm-grow"></span>
26651
+ </div>
26652
+ <div class="dtm-row" data-dtm="compose-template-row">
26653
+ <span class="dtm-label">\u69CB\u6210</span>
26654
+ <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">
26655
+ <option value="custom">\u81EA\u7531\u9078\u629E\uFF08\u4E0B\u8A18\u30C1\u30A7\u30C3\u30AF\uFF09</option>
26656
+ <option value="1chorus">1\u30B3\u30FC\u30E9\u30B9\uFF08\u77ED\u3081\u30FB\u521D\u5FC3\u8005\u5411\u3051\uFF09</option>
26657
+ <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>
26658
+ <option value="jpop_drop">\u843D\u3061\u30B5\u30D3\u5165\u308A\uFF08JPOP\u738B\u9053 + \u30E9\u30B9\u30B5\u30D3\u524D\u843D\u3061\u30B5\u30D3\uFF09</option>
26659
+ <option value="vocaloid">\u30DC\u30AB\u30ED\u738B\u9053\uFF08\u75BE\u8D70\u30FB2\u756A/C\u30E1\u30ED/\u30E9\u30B9\u30B5\u30D3\uFF09</option>
26660
+ <option value="verse_chorus">Verse-Chorus\uFF08B\u30E1\u30ED\u306A\u3057\u30FB\u6D0B\u697D\u98A8\uFF09</option>
26661
+ </select>
26662
+ </div>
26663
+ <div class="dtm-row" data-dtm="compose-sections-row">
26664
+ <span class="dtm-label">\u4F5C\u308B\u90E8\u5206</span>
26665
+ <div class="dtm-checks" data-dtm="compose-sections">
26666
+ <label class="dtm-check"><input type="checkbox" value="intro" checked>\u30A4\u30F3\u30C8\u30ED</label>
26667
+ <label class="dtm-check"><input type="checkbox" value="verse" checked>A\u30E1\u30ED</label>
26668
+ <label class="dtm-check"><input type="checkbox" value="prechorus" checked>B\u30E1\u30ED</label>
26669
+ <label class="dtm-check"><input type="checkbox" value="chorus" checked>\u30B5\u30D3</label>
26670
+ <label class="dtm-check"><input type="checkbox" value="bridge">C\u30E1\u30ED</label>
26671
+ <label class="dtm-check"><input type="checkbox" value="drop_chorus">\u843D\u3061\u30B5\u30D3</label>
26672
+ <label class="dtm-check"><input type="checkbox" value="interlude">\u9593\u594F</label>
26673
+ <label class="dtm-check"><input type="checkbox" value="outro">\u30A2\u30A6\u30C8\u30ED</label>
25114
26674
  </div>
26675
+ <span class="dtm-grow"></span>
26676
+ <span class="dtm-hint" data-dtm="compose-sections-len"></span>
26677
+ </div>
26678
+ <div class="dtm-row" data-dtm="compose-key-row">
26679
+ <span class="dtm-label">\u30D9\u30FC\u30B9\u8ABF</span>
26680
+ <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">
26681
+ <option value="any" title="\u516824\u8ABF\u304B\u3089\u30E9\u30F3\u30C0\u30E0\u306B\u6C7A\u5B9A\u3057\u307E\u3059">\u5E0C\u671B\u306A\u3057</option>
26682
+ <optgroup label="\u57FA\u672C">
26683
+ <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>
26684
+ <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>
26685
+ </optgroup>
26686
+ <optgroup label="\u96F0\u56F2\u6C17\u304B\u3089\u9078\u3076\uFF08\u62BD\u9078\uFF09">
26687
+ <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>
26688
+ <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>
26689
+ <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>
26690
+ <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>
26691
+ <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>
26692
+ <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>
26693
+ <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>
26694
+ <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>
26695
+ </optgroup>
26696
+ <optgroup label="\u9577\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
26697
+ <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>
26698
+ <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>
26699
+ <option value="key_D" title="\u610F\u6C17\u63DA\u3005\u3068\u3057\u305F\u3001\u52DD\u5229\u306E\u3001\u558A\u58F0">\u30CB\u9577\u8ABF (D)</option>
26700
+ <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>
26701
+ <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>
26702
+ <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>
26703
+ <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>
26704
+ <option value="key_G" title="\u53B3\u7C9B\u306A\u3001\u5D07\u9AD8\u306A\u3001\u5E7B\u60F3\u3001\u8AA0\u5B9F">\u30C8\u9577\u8ABF (G)</option>
26705
+ <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>
26706
+ <option value="key_A" title="\u3046\u308C\u3057\u3044\u3001\u7267\u6B4C\u7684\u306A\u3001\u611B\u306E\u544A\u767D">\u30A4\u9577\u8ABF (A)</option>
26707
+ <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>
26708
+ <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>
26709
+ </optgroup>
26710
+ <optgroup label="\u77ED\u8ABF\uFF08\u500B\u5225\u6307\u5B9A\uFF09">
26711
+ <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>
26712
+ <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>
26713
+ <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>
26714
+ <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>
26715
+ <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>
26716
+ <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>
26717
+ <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>
26718
+ <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>
26719
+ <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>
26720
+ <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>
26721
+ <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>
26722
+ <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>
26723
+ </optgroup>
26724
+ </select>
26725
+ <span class="dtm-grow"></span>
26726
+ <span class="dtm-hint" data-dtm="compose-key-hint"></span>
26727
+ </div>
26728
+ <div class="dtm-row" data-dtm="compose-scale-row">
26729
+ <span class="dtm-label">\u97F3\u968E</span>
26730
+ <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">
26731
+ <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>
26732
+ <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>
26733
+ <optgroup label="\u30DA\u30F3\u30BF\u30C8\u30CB\u30C3\u30AF\uFF085\u97F3\u97F3\u968E\uFF09">
26734
+ <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>
26735
+ <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>
26736
+ <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>
26737
+ <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>
26738
+ <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>
26739
+ </optgroup>
26740
+ <optgroup label="\u30C1\u30E3\u30FC\u30C1\u30E2\u30FC\u30C9\uFF087\u97F3\u97F3\u968E\uFF09">
26741
+ <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>
26742
+ <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>
26743
+ <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>
26744
+ <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>
26745
+ </optgroup>
26746
+ <optgroup label="\u7279\u6B8A\u97F3\u968E\uFF08\u97F3\u7A0B\u96C6\u5408\u3054\u3068\u5165\u308C\u66FF\u308F\u308B\uFF09">
26747
+ <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>
26748
+ <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>
26749
+ <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>
26750
+ <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>
26751
+ </optgroup>
26752
+ </select>
26753
+ <span class="dtm-grow"></span>
26754
+ <span class="dtm-hint" data-dtm="compose-scale-hint"></span>
25115
26755
  </div>
26756
+ </div>
26757
+ </details>
26758
+
26759
+ <details class="dtm-panel" data-dtm-acc="macro">
26760
+ <summary>\u4E00\u62EC\u7DE8\u96C6</summary>
26761
+ <div class="dtm-panel-body">
25116
26762
  <div class="dtm-row">
25117
26763
  <span class="dtm-label">\u5168\u4F53\u30B7\u30D5\u30C8</span>
25118
26764
  <select class="dtm-select" data-dtm="shift-select">
@@ -25163,10 +26809,11 @@ var buildUI = (target, options) => {
25163
26809
  </details>
25164
26810
 
25165
26811
  <details class="dtm-panel" data-dtm-acc="io-out">
25166
- <summary>MIDI / UST / MML \u51FA\u529B</summary>
26812
+ <summary>MIDI / MusicXML / UST / MML \u51FA\u529B</summary>
25167
26813
  <div class="dtm-panel-body">
25168
26814
  <div class="dtm-row">
25169
26815
  <button class="dtm-btn dtm-btn--accent" data-dtm="export-midi">MIDI\u51FA\u529B</button>
26816
+ <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>
25170
26817
  <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>
25171
26818
  <button class="dtm-btn dtm-btn--success" data-dtm="generate-mml">MML\u751F\u6210</button>
25172
26819
  <button class="dtm-btn dtm-btn--primary dtm-hidden" data-dtm="export-wav">WAV\u66F8\u304D\u51FA\u3057</button>
@@ -25216,6 +26863,7 @@ var buildUI = (target, options) => {
25216
26863
  </div>
25217
26864
  </div>
25218
26865
  </details>
26866
+ </div>
25219
26867
 
25220
26868
  <!-- \u2550\u2550\u2550\u2550 \u89E3\u8AAC\u30E2\u30FC\u30C0\u30EB \u2550\u2550\u2550\u2550 -->
25221
26869
  <div class="dtm-modal-overlay" data-dtm="modal-overlay" hidden>
@@ -25322,6 +26970,11 @@ var buildUI = (target, options) => {
25322
26970
  midiTrackSelection: sel("midi-track-selection"),
25323
26971
  midiPanel: sel("midi-panel"),
25324
26972
  midiSearchOpenBtn: sel("midi-search-open"),
26973
+ musicXmlInput: sel("musicxml-input"),
26974
+ musicXmlLoadBtn: sel("musicxml-load"),
26975
+ musicXmlInfoBtn: sel("musicxml-info"),
26976
+ musicXmlPartSelection: sel("musicxml-part-selection"),
26977
+ musicXmlLoadNote: sel("musicxml-load-note"),
25325
26978
  ustInput: sel("ust-input"),
25326
26979
  ustLoadBtn: sel("ust-load"),
25327
26980
  ustInfoBtn: sel("ust-info"),
@@ -25353,6 +27006,7 @@ var buildUI = (target, options) => {
25353
27006
  macroHarmonic: sel("macro-harmonic"),
25354
27007
  macroMono: sel("macro-mono"),
25355
27008
  exportMidiBtn: sel("export-midi"),
27009
+ exportMusicXmlBtn: sel("export-musicxml"),
25356
27010
  exportUstBtn: sel("export-ust"),
25357
27011
  exportWavBtn: sel("export-wav"),
25358
27012
  drumJsonExportBtn: sel("drum-json-export"),
@@ -25421,6 +27075,32 @@ var readMacroSections = () => {
25421
27075
  var writeMacroSections = (sections) => {
25422
27076
  writeMacroSetting("sections", JSON.stringify(sections));
25423
27077
  };
27078
+ var KEPT_STORAGE_KEY = "dtm-macro:kept";
27079
+ var readKeptSong = () => {
27080
+ try {
27081
+ if (typeof localStorage === "undefined" || !localStorage) return null;
27082
+ const raw = localStorage.getItem(KEPT_STORAGE_KEY);
27083
+ if (!raw) return null;
27084
+ const parsed = JSON.parse(raw);
27085
+ if (parsed && typeof parsed === "object" && typeof parsed.mml === "string" && parsed.mml.length > 0) {
27086
+ const startStep = typeof parsed.startStep === "number" && Number.isFinite(parsed.startStep) && parsed.startStep >= 0 ? Math.floor(parsed.startStep) : 0;
27087
+ return { mml: parsed.mml, startStep };
27088
+ }
27089
+ } catch (_) {
27090
+ }
27091
+ return null;
27092
+ };
27093
+ var writeKeptSong = (kept) => {
27094
+ try {
27095
+ if (typeof localStorage === "undefined" || !localStorage) return;
27096
+ if (kept === null) {
27097
+ localStorage.removeItem(KEPT_STORAGE_KEY);
27098
+ return;
27099
+ }
27100
+ localStorage.setItem(KEPT_STORAGE_KEY, JSON.stringify(kept));
27101
+ } catch (_) {
27102
+ }
27103
+ };
25424
27104
 
25425
27105
  // src/macros.ts
25426
27106
  var SCALES = [
@@ -25567,7 +27247,7 @@ var transposeNotes = (cores, steps) => {
25567
27247
  };
25568
27248
 
25569
27249
  // src/version.ts
25570
- var DTM_VERSION = "2.1.12";
27250
+ var DTM_VERSION = "2.1.14";
25571
27251
 
25572
27252
  // src/midi-io.ts
25573
27253
  var STEPS_PER_BEAT3 = 48;
@@ -26734,6 +28414,275 @@ var isChordHeavyTrack = (notes, threshold = 0.6) => {
26734
28414
  return chordNotes / notes.length >= threshold;
26735
28415
  };
26736
28416
 
28417
+ // src/musicxml-io.ts
28418
+ var STEPS_PER_BEAT4 = 48;
28419
+ var PITCH_CLASS = {
28420
+ C: 0,
28421
+ D: 2,
28422
+ E: 4,
28423
+ F: 5,
28424
+ G: 7,
28425
+ A: 9,
28426
+ B: 11
28427
+ };
28428
+ var textOf = (el, tag) => el?.getElementsByTagName(tag)[0]?.textContent?.trim() ?? "";
28429
+ var numOf = (el, tag, fallback = 0) => {
28430
+ const raw = textOf(el, tag);
28431
+ if (!raw) return fallback;
28432
+ const v = Number.parseFloat(raw);
28433
+ return Number.isFinite(v) ? v : fallback;
28434
+ };
28435
+ var pitchOf = (note) => {
28436
+ const p = note.getElementsByTagName("pitch")[0];
28437
+ if (!p) return null;
28438
+ const step = textOf(p, "step").toUpperCase();
28439
+ const base = PITCH_CLASS[step];
28440
+ if (base === void 0) return null;
28441
+ const octave = numOf(p, "octave", 4);
28442
+ const alter = Math.round(numOf(p, "alter", 0));
28443
+ return (octave + 1) * 12 + base + alter;
28444
+ };
28445
+ var parseMusicXML = (xml) => {
28446
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
28447
+ if (doc.getElementsByTagName("parserror").length > 0)
28448
+ throw new Error("MusicXML \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093");
28449
+ const nameById = /* @__PURE__ */ new Map();
28450
+ for (const sp of Array.from(doc.getElementsByTagName("score-part"))) {
28451
+ const id = sp.getAttribute("id");
28452
+ if (id) nameById.set(id, textOf(sp, "part-name") || id);
28453
+ }
28454
+ const placements = [];
28455
+ const parts = [];
28456
+ let bpm = 0;
28457
+ const partEls = Array.from(doc.getElementsByTagName("part"));
28458
+ partEls.forEach((partEl, partIndex) => {
28459
+ const id = partEl.getAttribute("id") ?? "";
28460
+ let divisions = 1;
28461
+ let cursor = 0;
28462
+ let measureStart = 0;
28463
+ let lastStart = 0;
28464
+ let lastDurSteps = 0;
28465
+ const tied = /* @__PURE__ */ new Map();
28466
+ let count = 0;
28467
+ let pitchSum = 0;
28468
+ let lyricSeen = false;
28469
+ for (const measure of Array.from(partEl.getElementsByTagName("measure"))) {
28470
+ measureStart += cursor;
28471
+ cursor = 0;
28472
+ for (const child of Array.from(measure.children)) {
28473
+ const tag = child.tagName.toLowerCase();
28474
+ if (tag === "attributes") {
28475
+ const d = numOf(child, "divisions", 0);
28476
+ if (d > 0) divisions = d;
28477
+ continue;
28478
+ }
28479
+ if (tag === "direction") {
28480
+ const sound = child.getElementsByTagName("sound")[0];
28481
+ const t = Number.parseFloat(sound?.getAttribute("tempo") ?? "");
28482
+ if (!bpm && Number.isFinite(t) && t > 0) bpm = t;
28483
+ continue;
28484
+ }
28485
+ if (tag === "backup" || tag === "forward") {
28486
+ const steps = numOf(child, "duration", 0) / divisions * STEPS_PER_BEAT4;
28487
+ cursor += tag === "backup" ? -steps : steps;
28488
+ continue;
28489
+ }
28490
+ if (tag !== "note") continue;
28491
+ const durSteps = Math.round(
28492
+ numOf(child, "duration", 0) / divisions * STEPS_PER_BEAT4
28493
+ );
28494
+ const isChord = child.getElementsByTagName("chord").length > 0;
28495
+ const isRest = child.getElementsByTagName("rest").length > 0;
28496
+ const start = isChord ? lastStart : measureStart + cursor;
28497
+ if (!isRest) {
28498
+ const pitch = pitchOf(child);
28499
+ if (pitch !== null) {
28500
+ const lyric = textOf(
28501
+ child.getElementsByTagName("lyric")[0],
28502
+ "text"
28503
+ );
28504
+ if (lyric) lyricSeen = true;
28505
+ const ties = Array.from(child.getElementsByTagName("tie"));
28506
+ const stops = ties.some((t) => t.getAttribute("type") === "stop");
28507
+ const starts = ties.some((t) => t.getAttribute("type") === "start");
28508
+ const held = tied.get(pitch);
28509
+ if (stops && held) {
28510
+ held.durationSteps += durSteps;
28511
+ if (!starts) tied.delete(pitch);
28512
+ } else {
28513
+ const placed = {
28514
+ partIndex,
28515
+ startStep: Math.max(0, Math.round(start)),
28516
+ pitch,
28517
+ durationSteps: Math.max(1, durSteps),
28518
+ lyric
28519
+ };
28520
+ placements.push(placed);
28521
+ count++;
28522
+ pitchSum += pitch;
28523
+ if (starts) tied.set(pitch, placed);
28524
+ }
28525
+ }
28526
+ }
28527
+ if (!isChord) {
28528
+ lastStart = start;
28529
+ lastDurSteps = durSteps;
28530
+ cursor += durSteps;
28531
+ } else {
28532
+ lastDurSteps = Math.max(lastDurSteps, durSteps);
28533
+ }
28534
+ }
28535
+ }
28536
+ parts.push({
28537
+ index: partIndex,
28538
+ name: nameById.get(id) || `Part ${partIndex + 1}`,
28539
+ noteCount: count,
28540
+ hasLyrics: lyricSeen,
28541
+ avgPitch: count > 0 ? pitchSum / count : 0
28542
+ });
28543
+ });
28544
+ return { parts, placements, bpm: bpm > 0 ? Math.round(bpm) : 120 };
28545
+ };
28546
+ var SHARP_SPELLING = [
28547
+ ["C", 0],
28548
+ ["C", 1],
28549
+ ["D", 0],
28550
+ ["D", 1],
28551
+ ["E", 0],
28552
+ ["F", 0],
28553
+ ["F", 1],
28554
+ ["G", 0],
28555
+ ["G", 1],
28556
+ ["A", 0],
28557
+ ["A", 1],
28558
+ ["B", 0]
28559
+ ];
28560
+ var NOTE_TYPES = [
28561
+ [STEPS_PER_BEAT4 * 4, "whole"],
28562
+ [STEPS_PER_BEAT4 * 2, "half"],
28563
+ [STEPS_PER_BEAT4, "quarter"],
28564
+ [STEPS_PER_BEAT4 / 2, "eighth"],
28565
+ [STEPS_PER_BEAT4 / 4, "16th"],
28566
+ [STEPS_PER_BEAT4 / 8, "32nd"]
28567
+ ];
28568
+ var typeOf = (steps) => {
28569
+ for (const [len, type] of NOTE_TYPES) {
28570
+ if (steps >= len) {
28571
+ const dots = steps >= len * 1.5 && steps < len * 2 ? 1 : 0;
28572
+ return { type, dots };
28573
+ }
28574
+ }
28575
+ return { type: "32nd", dots: 0 };
28576
+ };
28577
+ var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
28578
+ var exportMusicXML = (options) => {
28579
+ const { parts, bpm, stepsPerBar, title } = options;
28580
+ const beatsPerBar = Math.max(1, Math.round(stepsPerBar / STEPS_PER_BEAT4));
28581
+ const partList = parts.map(
28582
+ (p, i2) => ` <score-part id="P${i2 + 1}">
28583
+ <part-name>${esc(p.name)}</part-name>
28584
+ </score-part>`
28585
+ ).join("\n");
28586
+ const body = parts.map((part, pi) => {
28587
+ const notes = [...part.notes].sort((a, b) => a.startStep - b.startStep);
28588
+ const end = notes.reduce(
28589
+ (m, n) => Math.max(m, n.startStep + n.durationSteps),
28590
+ 0
28591
+ );
28592
+ const bars = Math.max(1, Math.ceil(end / stepsPerBar));
28593
+ const lines = [` <part id="P${pi + 1}">`];
28594
+ let li = 0;
28595
+ for (let bar = 0; bar < bars; bar++) {
28596
+ const from = bar * stepsPerBar;
28597
+ const to = from + stepsPerBar;
28598
+ lines.push(` <measure number="${bar + 1}">`);
28599
+ if (bar === 0) {
28600
+ lines.push(
28601
+ ` <attributes>
28602
+ <divisions>${STEPS_PER_BEAT4}</divisions>`,
28603
+ ` <key><fifths>0</fifths></key>`,
28604
+ ` <time><beats>${beatsPerBar}</beats><beat-type>4</beat-type></time>`,
28605
+ ` <clef><sign>G</sign><line>2</line></clef>
28606
+ </attributes>`
28607
+ );
28608
+ if (pi === 0)
28609
+ lines.push(
28610
+ ` <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>`
28611
+ );
28612
+ }
28613
+ let cursor = from;
28614
+ const inBar = notes.filter(
28615
+ (n) => n.startStep >= from && n.startStep < to
28616
+ );
28617
+ const groups = /* @__PURE__ */ new Map();
28618
+ for (const n of inBar) {
28619
+ const g = groups.get(n.startStep);
28620
+ if (g) g.push(n);
28621
+ else groups.set(n.startStep, [n]);
28622
+ }
28623
+ const starts = [...groups.keys()].sort((a, b) => a - b);
28624
+ for (let gi = 0; gi < starts.length; gi++) {
28625
+ const at = starts[gi];
28626
+ const group = groups.get(at);
28627
+ if (at > cursor) {
28628
+ const { type, dots } = typeOf(at - cursor);
28629
+ lines.push(
28630
+ ` <note><rest/><duration>${at - cursor}</duration><type>${type}</type>${"<dot/>".repeat(dots)}</note>`
28631
+ );
28632
+ cursor = at;
28633
+ }
28634
+ const nextAt = gi + 1 < starts.length ? starts[gi + 1] : to;
28635
+ const room = Math.min(to, nextAt) - at;
28636
+ const dur = Math.max(
28637
+ 1,
28638
+ Math.min(Math.max(...group.map((n) => n.durationSteps)), room)
28639
+ );
28640
+ group.forEach((n, ni) => {
28641
+ const semi = Math.round(n.pitchUnits / UNITS_PER_SEMITONE);
28642
+ const [step, alter] = SHARP_SPELLING[(semi % 12 + 12) % 12];
28643
+ const octave = Math.floor(semi / 12) - 1;
28644
+ const { type, dots } = typeOf(dur);
28645
+ const lyric = part.lyrics?.[li];
28646
+ lines.push(
28647
+ ` <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>`
28648
+ );
28649
+ if (ni === 0) li++;
28650
+ });
28651
+ cursor = at + dur;
28652
+ }
28653
+ if (cursor < to) {
28654
+ const { type, dots } = typeOf(to - cursor);
28655
+ lines.push(
28656
+ ` <note><rest/><duration>${to - cursor}</duration><type>${type}</type>${"<dot/>".repeat(dots)}</note>`
28657
+ );
28658
+ }
28659
+ lines.push(" </measure>");
28660
+ }
28661
+ lines.push(" </part>");
28662
+ return lines.join("\n");
28663
+ }).join("\n");
28664
+ return [
28665
+ '<?xml version="1.0" encoding="UTF-8"?>',
28666
+ '<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">',
28667
+ '<score-partwise version="4.0">',
28668
+ ` <work><work-title>${esc(title ?? "Untitled")}</work-title></work>`,
28669
+ // 書き出し元を残す。MML・MIDI と揃えてある(`src/version.ts` に理由)。
28670
+ ` <identification><encoding><software>dtm ${DTM_VERSION}</software></encoding></identification>`,
28671
+ " <part-list>",
28672
+ partList,
28673
+ " </part-list>",
28674
+ body,
28675
+ "</score-partwise>",
28676
+ ""
28677
+ ].join("\n");
28678
+ };
28679
+ var musicXmlToNotes = (placements, partIndex) => placements.filter((p) => p.partIndex === partIndex).map((p) => ({
28680
+ startStep: p.startStep,
28681
+ durationSteps: p.durationSteps,
28682
+ pitchUnits: p.pitch * UNITS_PER_SEMITONE,
28683
+ velocity: 100
28684
+ }));
28685
+
26737
28686
  // src/renderer.ts
26738
28687
  var KEYBOARD_WIDTH = 60;
26739
28688
  var HEADER_HEIGHT = 20;
@@ -27062,6 +29011,76 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
27062
29011
  }
27063
29012
  }
27064
29013
  };
29014
+ const drawSpeechSpans = (spans, color = [255, 241, 232]) => {
29015
+ if (spans.length === 0) return;
29016
+ const {
29017
+ keyHeight,
29018
+ stepWidth,
29019
+ keyCount,
29020
+ pitchRangeStart,
29021
+ unitsPerRow: upr = UNITS_PER_SEMITONE
29022
+ } = g_config;
29023
+ const [r, g, b] = color;
29024
+ g_grid_ctx.save();
29025
+ for (const { note, durationSteps } of spans) {
29026
+ if (durationSteps <= 0) continue;
29027
+ const x2 = note.startStep * stepWidth - g_draw_offset_x;
29028
+ const y = (keyCount - 1 - (note.pitchUnits - pitchRangeStart) / upr) * keyHeight - g_draw_offset_y;
29029
+ const w = durationSteps * stepWidth;
29030
+ if (x2 + w < 0 || x2 > g_grid_canvas.width) continue;
29031
+ if (y + keyHeight < 0 || y > g_grid_canvas.height) continue;
29032
+ const h = Math.max(2, Math.floor(keyHeight * 0.35));
29033
+ const top = y + keyHeight - h - 1;
29034
+ g_grid_ctx.fillStyle = `rgba(${r},${g},${b},0.28)`;
29035
+ g_grid_ctx.fillRect(x2 + 1, top, w - 2, h);
29036
+ g_grid_ctx.strokeStyle = `rgba(${r},${g},${b},0.75)`;
29037
+ g_grid_ctx.lineWidth = 1;
29038
+ g_grid_ctx.setLineDash([3, 2]);
29039
+ g_grid_ctx.strokeRect(x2 + 1.5, top + 0.5, w - 3, h - 1);
29040
+ g_grid_ctx.setLineDash([]);
29041
+ g_grid_ctx.beginPath();
29042
+ g_grid_ctx.moveTo(x2 + w - 1.5, y + 1);
29043
+ g_grid_ctx.lineTo(x2 + w - 1.5, y + keyHeight - 1);
29044
+ g_grid_ctx.stroke();
29045
+ }
29046
+ g_grid_ctx.restore();
29047
+ };
29048
+ const drawPitchGuide = (pitchUnits, label) => {
29049
+ const {
29050
+ keyHeight,
29051
+ keyCount,
29052
+ pitchRangeStart,
29053
+ unitsPerRow: upr = UNITS_PER_SEMITONE
29054
+ } = g_config;
29055
+ const row = Math.round((pitchUnits - pitchRangeStart) / upr);
29056
+ if (row < 0 || row >= keyCount) return;
29057
+ const y = (keyCount - 1 - row) * keyHeight - g_draw_offset_y;
29058
+ if (y + keyHeight < 0 || y > g_grid_canvas.height) return;
29059
+ g_grid_ctx.save();
29060
+ g_grid_ctx.fillStyle = "rgba(255,236,39,0.10)";
29061
+ g_grid_ctx.fillRect(0, y, g_grid_canvas.width, keyHeight);
29062
+ g_grid_ctx.strokeStyle = "rgba(255,236,39,0.55)";
29063
+ g_grid_ctx.lineWidth = 1;
29064
+ g_grid_ctx.setLineDash([6, 4]);
29065
+ g_grid_ctx.beginPath();
29066
+ g_grid_ctx.moveTo(0, y + keyHeight / 2 + 0.5);
29067
+ g_grid_ctx.lineTo(g_grid_canvas.width, y + keyHeight / 2 + 0.5);
29068
+ g_grid_ctx.stroke();
29069
+ if (keyHeight >= 7) {
29070
+ const fontSize = Math.min(11, Math.floor(keyHeight * 0.85));
29071
+ g_grid_ctx.setLineDash([]);
29072
+ g_grid_ctx.font = `${fontSize}px 'k8x12',sans-serif`;
29073
+ g_grid_ctx.textAlign = "left";
29074
+ g_grid_ctx.textBaseline = "middle";
29075
+ g_grid_ctx.lineWidth = 3;
29076
+ g_grid_ctx.lineJoin = "round";
29077
+ g_grid_ctx.strokeStyle = "rgba(0,0,0,0.85)";
29078
+ g_grid_ctx.strokeText(label, 4, y + keyHeight / 2);
29079
+ g_grid_ctx.fillStyle = "#ffec27";
29080
+ g_grid_ctx.fillText(label, 4, y + keyHeight / 2);
29081
+ }
29082
+ g_grid_ctx.restore();
29083
+ };
27065
29084
  const drawNoteLyrics = (notes, syllables) => {
27066
29085
  if (syllables.length === 0) return;
27067
29086
  const {
@@ -27211,6 +29230,8 @@ var createRenderer = (mountTarget, width = 800, height = 450, config) => {
27211
29230
  drawGrid,
27212
29231
  drawNotes,
27213
29232
  drawNoteLyrics,
29233
+ drawSpeechSpans,
29234
+ drawPitchGuide,
27214
29235
  drawSelectionRect,
27215
29236
  drawSelectedNotes,
27216
29237
  getXY,
@@ -27809,7 +29830,7 @@ var VOWEL_KANA2 = {
27809
29830
  o: "\u304A",
27810
29831
  \u00FC: "\u3086"
27811
29832
  };
27812
- var SMALL_KANA2 = {
29833
+ var SMALL_KANA3 = {
27813
29834
  a: "\u3083",
27814
29835
  u: "\u3085",
27815
29836
  e: "\u3047",
@@ -27881,7 +29902,7 @@ var pinyinToMoras = (raw) => {
27881
29902
  const zero = initial === "" && medial === "i";
27882
29903
  const head = zero ? ZERO_PALATAL[nucleus] : onset[medial];
27883
29904
  if (!head) return null;
27884
- const small = SMALL_KANA2[nucleus];
29905
+ const small = SMALL_KANA3[nucleus];
27885
29906
  if (zero) moras.push(head);
27886
29907
  else if (head.length === 1 && small) moras.push(head + small);
27887
29908
  else moras.push(head, VOWEL_KANA2[nucleus]);
@@ -27891,10 +29912,10 @@ var pinyinToMoras = (raw) => {
27891
29912
  };
27892
29913
 
27893
29914
  // src/ust-io.ts
27894
- var STEPS_PER_BEAT4 = 48;
29915
+ var STEPS_PER_BEAT5 = 48;
27895
29916
  var UST_TICKS_PER_BEAT = 480;
27896
- var TICKS_TO_STEPS = STEPS_PER_BEAT4 / UST_TICKS_PER_BEAT;
27897
- var STEPS_TO_TICKS = UST_TICKS_PER_BEAT / STEPS_PER_BEAT4;
29917
+ var TICKS_TO_STEPS = STEPS_PER_BEAT5 / UST_TICKS_PER_BEAT;
29918
+ var STEPS_TO_TICKS = UST_TICKS_PER_BEAT / STEPS_PER_BEAT5;
27898
29919
  var MORA_TAIL_MAX_STEPS = 12;
27899
29920
  var UST_CONTINUE_LYRICS = ["+", "+~", "+-", "+*", "*", "\u30FC", "-"];
27900
29921
  var TIE = "\u30FC";
@@ -27977,12 +29998,12 @@ var ROMAJI_KANA = (() => {
27977
29998
  return map;
27978
29999
  })();
27979
30000
  var KANA_HEAD_RE = /^[ぁ-ゖァ-ヶー〜]+/;
27980
- var SMALL_KANA3 = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087";
30001
+ var SMALL_KANA4 = "\u3041\u3043\u3045\u3047\u3049\u3083\u3085\u3087";
27981
30002
  var splitMoras = (kana) => {
27982
30003
  const moras = [];
27983
30004
  for (const ch of kana) {
27984
30005
  const last = moras.length - 1;
27985
- if (last >= 0 && SMALL_KANA3.includes(ch)) moras[last] += ch;
30006
+ if (last >= 0 && SMALL_KANA4.includes(ch)) moras[last] += ch;
27986
30007
  else moras.push(ch);
27987
30008
  }
27988
30009
  return moras;
@@ -28167,7 +30188,7 @@ var parseUst = (source, name = "") => {
28167
30188
  const syllables = [];
28168
30189
  let unknownLyricCount = 0;
28169
30190
  let hasVoiced = false;
28170
- const msPerStep = 6e4 / (bpm ?? 120) / STEPS_PER_BEAT4;
30191
+ const msPerStep = 6e4 / (bpm ?? 120) / STEPS_PER_BEAT5;
28171
30192
  for (const fields of sections) {
28172
30193
  const length = Number.parseFloat(fields.length ?? "");
28173
30194
  if (!Number.isFinite(length) || length <= 0) continue;
@@ -28263,6 +30284,7 @@ var ustLyricOf = (kana) => {
28263
30284
  if (kana === "") return DEFAULT_UST_LYRIC;
28264
30285
  if (kana === TIE || kana === "\u301C") return "+";
28265
30286
  if (kana === REST) return "R";
30287
+ if (kana.startsWith("\u300C")) return "R";
28266
30288
  return kana;
28267
30289
  };
28268
30290
  var UST_ENVELOPE_ATTACK_MS = 5;
@@ -28406,7 +30428,9 @@ var buildUst = (options) => {
28406
30428
  cursorTick = startTick + length;
28407
30429
  }
28408
30430
  assignFadeEnvelopes(out);
28409
- out.forEach((note, index) => lines.push(...ustNoteSection(index, note)));
30431
+ for (const [index, note] of out.entries()) {
30432
+ lines.push(...ustNoteSection(index, note));
30433
+ }
28410
30434
  lines.push("[#TRACKEND]");
28411
30435
  return `${lines.join("\r\n")}\r
28412
30436
  `;
@@ -28460,8 +30484,22 @@ c d e f g \u2190 \u97F3\u7B265\u3064</pre>
28460
30484
  <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>
28461
30485
  <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>
28462
30486
  <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>
30487
+ <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>
30488
+ </ul>
30489
+ <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>
30490
+
30491
+ <h4>\u6B4C\u306E\u4E2D\u3067\u8A9E\u308B\uFF08<code>\u300C\u2026\u300D</code>\uFF09</h4>
30492
+ <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>
30493
+ <pre>\u3075\u3064\u3046\u306B\u3046\u305F\u3046\u300C\u304B\u305F\u308B\u3070\u3057\u3087\u300D\u3075\u3064\u3046\u306B\u3046\u305F\u3046
30494
+ \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
30495
+ \u300C\u30BB\u30EA\u30D5\u3060\u3051\u306E\u30C8\u30E9\u30C3\u30AF\u3082\u4F5C\u308C\u307E\u3059\u3002\u300D</pre>
30496
+ <ul>
30497
+ <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>
30498
+ <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>
30499
+ <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>
30500
+ <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>
30501
+ <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>
28463
30502
  </ul>
28464
- <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>
28465
30503
 
28466
30504
  <h4>\u5F37\u5F31\u3092\u3064\u3051\u308B\uFF08<code>\u2193</code> <code>\u2191</code>\uFF09</h4>
28467
30505
  <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>
@@ -28848,6 +30886,27 @@ var MIDI_INFO_HTML = `
28848
30886
  <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>
28849
30887
  </div>
28850
30888
  `;
30889
+ var MUSICXML_INFO_HTML = `
30890
+ <div class="dtm-modal-body-content">
30891
+ <h4>1. MusicXML\u3068\u306F</h4>
30892
+ <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>
30893
+
30894
+ <h4>2. \u8AAD\u307F\u8FBC\u307F\u306E\u3057\u304B\u305F</h4>
30895
+ <ul>
30896
+ <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>
30897
+ <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>
30898
+ <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>
30899
+ <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>
30900
+ <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>
30901
+ </ul>
30902
+
30903
+ <h4>3. \u66F8\u304D\u51FA\u3057</h4>
30904
+ <ul>
30905
+ <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>
30906
+ <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>
30907
+ </ul>
30908
+ </div>
30909
+ `;
28851
30910
  var UST_INFO_HTML = `
28852
30911
  <div class="dtm-modal-body-content">
28853
30912
  <h4>1. UST\u30D5\u30A1\u30A4\u30EB\u3068\u306F</h4>
@@ -28989,6 +31048,7 @@ var HELP_INFO_HTML = `
28989
31048
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="koe">\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0(.koe)</button>
28990
31049
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="chord">\u30B3\u30FC\u30C9\u9032\u884C</button>
28991
31050
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="midi">MIDI\u306E\u8AAD\u307F\u8FBC\u307F</button>
31051
+ <button class="dtm-btn dtm-btn--ghost" data-dtm-help="musicxml">MusicXML</button>
28992
31052
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="ust">UST(UTAU)</button>
28993
31053
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="mml">MML\u306E\u66F8\u304D\u65B9</button>
28994
31054
  <button class="dtm-btn dtm-btn--ghost" data-dtm-help="loop">\u30EB\u30FC\u30D7\u518D\u751F</button>
@@ -29007,6 +31067,7 @@ var HELP_TOPICS = {
29007
31067
  koe: { title: "\u30AB\u30B9\u30BF\u30E0\u97F3\u58F0(.koe)\u306E\u4F7F\u3044\u65B9", html: KOE_INFO_HTML },
29008
31068
  chord: { title: "\u30B3\u30FC\u30C9\u9032\u884C\u306E\u81EA\u52D5\u5165\u529B\u89E3\u8AAC", html: CHORD_INFO_HTML2 },
29009
31069
  midi: { title: "MIDI\u306E\u8AAD\u307F\u8FBC\u307F\u89E3\u8AAC", html: MIDI_INFO_HTML },
31070
+ musicxml: { title: "MusicXML\u306E\u5165\u51FA\u529B\u89E3\u8AAC", html: MUSICXML_INFO_HTML },
29010
31071
  ust: { title: "UST\u306E\u8AAD\u307F\u8FBC\u307F\u89E3\u8AAC", html: UST_INFO_HTML },
29011
31072
  mml: { title: "MML\u306E\u66F8\u304D\u65B9\u89E3\u8AAC", html: MML_INFO_HTML },
29012
31073
  loop: { title: "\u30EB\u30FC\u30D7\u518D\u751F\u306E\u89E3\u8AAC", html: LOOP_INFO_HTML },
@@ -29461,6 +31522,8 @@ var mountDAW = (target, options = {}) => {
29461
31522
  };
29462
31523
  let currentInstrument = "";
29463
31524
  let autoComposeInstrument = null;
31525
+ let composeSeed = null;
31526
+ let composeSetting = null;
29464
31527
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
29465
31528
  let trackFxAdvancedOpen = readPanelOpen("track-fx-advanced") ?? false;
29466
31529
  let lyricAdvancedOpen = readPanelOpen("lyric-advanced") ?? false;
@@ -29703,17 +31766,66 @@ var mountDAW = (target, options = {}) => {
29703
31766
  const getActive = () => trackStates.find((t) => t.config.id === activeTrackId) ?? trackStates[0];
29704
31767
  let lyricKanaCacheText = "";
29705
31768
  let lyricKanaCache = [];
29706
- const getActiveLyricKana = () => {
31769
+ let lyricSyllableCache = [];
31770
+ const getActiveLyricSyllables = () => {
29707
31771
  const t = getActive();
29708
31772
  if (!t?.lyricModel.trim()) return [];
29709
31773
  const text = t.lyrics.trim();
29710
31774
  if (!text) return [];
29711
31775
  if (text !== lyricKanaCacheText) {
29712
31776
  lyricKanaCacheText = text;
29713
- lyricKanaCache = normalizeLyrics(text).map(displayKana);
31777
+ lyricSyllableCache = normalizeLyrics(text);
31778
+ lyricKanaCache = lyricSyllableCache.map(displayKana);
29714
31779
  }
31780
+ return lyricSyllableCache;
31781
+ };
31782
+ const getActiveLyricKana = () => {
31783
+ getActiveLyricSyllables();
29715
31784
  return lyricKanaCache;
29716
31785
  };
31786
+ const speechPreviewRequested = /* @__PURE__ */ new Set();
31787
+ const requestSpeechPreview = (model, text) => {
31788
+ const voices = options.singingVoices;
31789
+ if (!voices?.planSpeech) return;
31790
+ const key = `${model}|${text}`;
31791
+ if (speechPreviewRequested.has(key)) return;
31792
+ speechPreviewRequested.add(key);
31793
+ void voices.planSpeech(model, text).then((d) => {
31794
+ if (d !== null) redrawAll();
31795
+ }).catch(() => {
31796
+ });
31797
+ };
31798
+ const drawSpeechPreview = (notes) => {
31799
+ const voices = options.singingVoices;
31800
+ if (!voices) return;
31801
+ const syllables = getActiveLyricSyllables();
31802
+ if (!syllables.some((s) => s.kind === "speak")) return;
31803
+ const model = getActive().lyricModel.trim().toLowerCase();
31804
+ const sorted = [...notes].sort((a, b) => a.startStep - b.startStep);
31805
+ const count = Math.min(sorted.length, syllables.length);
31806
+ const secondsPerStep = 60 / bpm / 48;
31807
+ const spans = [];
31808
+ for (let i2 = 0; i2 < count; i2++) {
31809
+ const syl = syllables[i2];
31810
+ if (syl.kind !== "speak" || !syl.text) continue;
31811
+ const d = voices.peekSpeechDurationSec?.(model, syl.text);
31812
+ if (d === void 0) {
31813
+ requestSpeechPreview(model, syl.text);
31814
+ continue;
31815
+ }
31816
+ spans.push({ note: sorted[i2], durationSteps: d / secondsPerStep });
31817
+ }
31818
+ renderer.drawSpeechSpans(spans, getActive().config.color);
31819
+ };
31820
+ const drawSpeechPitchGuide = () => {
31821
+ const voices = options.singingVoices;
31822
+ if (!voices?.getSpeechReferenceUnits) return;
31823
+ if (!getActiveLyricSyllables().some((s) => s.kind === "speak")) return;
31824
+ const ref = voices.getSpeechReferenceUnits(
31825
+ getActive().lyricModel.trim().toLowerCase()
31826
+ );
31827
+ if (ref !== void 0) renderer.drawPitchGuide(ref, "\u8A9E\u308A\u306E\u57FA\u6E96");
31828
+ };
29717
31829
  let showModal;
29718
31830
  let showConfirm;
29719
31831
  const playableNotes = (t) => {
@@ -29798,6 +31910,7 @@ var mountDAW = (target, options = {}) => {
29798
31910
  };
29799
31911
  const redrawAll = () => {
29800
31912
  renderer.drawGrid(gridLineSteps);
31913
+ drawSpeechPitchGuide();
29801
31914
  let lyricTargetNotes = null;
29802
31915
  let activeTrackState = null;
29803
31916
  for (const t of trackStates) {
@@ -29846,8 +31959,10 @@ var mountDAW = (target, options = {}) => {
29846
31959
  1
29847
31960
  ]);
29848
31961
  }
29849
- if (lyricTargetNotes)
31962
+ if (lyricTargetNotes) {
31963
+ drawSpeechPreview(lyricTargetNotes);
29850
31964
  renderer.drawNoteLyrics(lyricTargetNotes, getActiveLyricKana());
31965
+ }
29851
31966
  drawStartLine();
29852
31967
  if (playbackState === "playing") drawPlayhead();
29853
31968
  updateScrollbars();
@@ -31452,9 +33567,9 @@ var mountDAW = (target, options = {}) => {
31452
33567
  </details>
31453
33568
  <div class="dtm-row">
31454
33569
  <span class="dtm-label dtm-grow">\u6B4C\u8A5E</span>
31455
- <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>
33570
+ <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>
31456
33571
  </div>
31457
- <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>
33572
+ <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>
31458
33573
  </div>`;
31459
33574
  refs.trackBody.appendChild(lyricDiv);
31460
33575
  lyricDiv.querySelector(
@@ -31654,8 +33769,10 @@ var mountDAW = (target, options = {}) => {
31654
33769
  lyricVibrato.checked = active.vocalVibrato;
31655
33770
  lyricOctaveUnison.value = active.vocalOctaveUnison;
31656
33771
  const updateLyricCount = () => {
31657
- const n = normalizeLyrics(lyricInput.value).length;
31658
- lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
33772
+ const syllables = normalizeLyrics(lyricInput.value);
33773
+ const n = syllables.length;
33774
+ const speak = syllables.filter((s) => s.kind === "speak").length;
33775
+ lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0${speak > 0 ? `\uFF08\u8A9E\u308A${speak}\uFF09` : ""}` : "";
31659
33776
  };
31660
33777
  const syncLyricTerms = () => {
31661
33778
  if (customVocalsMap.has(active.lyricModel)) {
@@ -31967,6 +34084,8 @@ var mountDAW = (target, options = {}) => {
31967
34084
  mode,
31968
34085
  edo: renderConfig.edo,
31969
34086
  loop: loopEnabled ? true : void 0,
34087
+ seed: composeSeed ?? void 0,
34088
+ compose: composeSetting ?? void 0,
31970
34089
  // アップロードされたファイルは url が空=音源関連の宣言ごと出力されない
31971
34090
  audio: backing.url || void 0,
31972
34091
  audioStart: backing.rangeStartSec || void 0,
@@ -32006,6 +34125,8 @@ var mountDAW = (target, options = {}) => {
32006
34125
  mode,
32007
34126
  edo: renderConfig.edo,
32008
34127
  loop: loopEnabled ? true : void 0,
34128
+ seed: composeSeed ?? void 0,
34129
+ compose: composeSetting ?? void 0,
32009
34130
  // アップロードされたファイルは url が空=音源関連の宣言ごと出力されない
32010
34131
  audio: backing.url || void 0,
32011
34132
  audioStart: backing.rangeStartSec || void 0,
@@ -32062,7 +34183,8 @@ var mountDAW = (target, options = {}) => {
32062
34183
  const lyricLines = trackStates.map((t, i2) => ({
32063
34184
  i: i2,
32064
34185
  notes: clipNotes(t.core.getNotes()),
32065
- text: t.lyrics.replace(/[\r\n]+/g, " ").trim(),
34186
+ // `;` MML の区切り文字なので、語り(「…」)の本文に紛れていても全角へ逃がす。
34187
+ text: t.lyrics.replace(/[\r\n]+/g, " ").replace(/;/g, "\uFF1B").trim(),
32066
34188
  model: t.lyricModel.trim(),
32067
34189
  vol: t.vocalVolume,
32068
34190
  gate: t.vocalGate,
@@ -32205,6 +34327,8 @@ var mountDAW = (target, options = {}) => {
32205
34327
  clampTrackCount: trackStates.length
32206
34328
  });
32207
34329
  if (!applyActiveOnly) {
34330
+ composeSeed = meta.seed ?? null;
34331
+ composeSetting = meta.compose ?? null;
32208
34332
  if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
32209
34333
  currentInstrument = meta.instrument;
32210
34334
  options.onInstrumentChange?.(meta.instrument);
@@ -32594,6 +34718,92 @@ var mountDAW = (target, options = {}) => {
32594
34718
  dropped: ustTracks.length - targets.length
32595
34719
  };
32596
34720
  };
34721
+ const applyMusicXmlSelection = (xml, selectedIndices, startIndex) => {
34722
+ stop();
34723
+ const applyActiveOnly = refs.applyActiveOnly?.checked ?? false;
34724
+ const from = Math.max(
34725
+ 0,
34726
+ startIndex ?? (applyActiveOnly ? activeTrackIndexOf() : selectedIndices.length > 1 ? 0 : activeTrackIndexOf())
34727
+ );
34728
+ const maxCount = applyActiveOnly ? 1 : trackStates.length - from;
34729
+ const targetIndices = selectedIndices.slice(0, Math.max(0, maxCount));
34730
+ const voice = trackStates[from]?.lyricModel || pickComposeVocal();
34731
+ targetIndices.forEach((partIndex, i2) => {
34732
+ const t = trackStates[from + i2];
34733
+ if (!t) return;
34734
+ const notes = musicXmlToNotes(xml.placements, partIndex);
34735
+ t.core.setLoadMode(true);
34736
+ t.core.clearNotesWithoutHistory();
34737
+ for (const n of notes) {
34738
+ t.core.addNote(n.startStep, snapToEdoGrid(n.pitchUnits), {
34739
+ noteLengthSteps: n.durationSteps,
34740
+ velocity: n.velocity
34741
+ });
34742
+ }
34743
+ t.core.setLoadMode(false);
34744
+ t.core.addHistoryOnce();
34745
+ const partPlacements = xml.placements.filter((p) => p.partIndex === partIndex).sort((a, b) => a.startStep - b.startStep);
34746
+ const hasLyrics = partPlacements.some(
34747
+ (p) => Boolean(p.lyric && p.lyric.trim())
34748
+ );
34749
+ if (hasLyrics) {
34750
+ const syllables = [];
34751
+ for (const p of partPlacements) {
34752
+ const l = p.lyric?.trim();
34753
+ syllables.push(l || "\u30FC");
34754
+ }
34755
+ t.lyrics = syllables.join("");
34756
+ t.vocalVolume = DEFAULT_VOCAL_VOLUME;
34757
+ t.vocalGate = 100;
34758
+ t.vocalPan = 64;
34759
+ t.vocalOctave = 0;
34760
+ t.vocalVibrato = false;
34761
+ t.vocalReverb = 0;
34762
+ t.vocalDelay = 0;
34763
+ t.vocalGender = 50;
34764
+ t.vocalBreathiness = 50;
34765
+ t.vocalTension = 50;
34766
+ t.vocalOctaveUnison = "none";
34767
+ if (!t.lyricModel) t.lyricModel = voice;
34768
+ fireLyricsChange(t);
34769
+ } else {
34770
+ t.lyrics = "";
34771
+ t.lyricModel = "";
34772
+ }
34773
+ });
34774
+ if (xml.bpm > 0) setBpm(Math.round(xml.bpm));
34775
+ playStartStep = 0;
34776
+ currentOffsetX = 0;
34777
+ const firstPitch = getFirstDetectedPitch();
34778
+ centerPitch(firstPitch ?? pitchV1ToUnits(60));
34779
+ redrawAll();
34780
+ updateTrackPanel();
34781
+ updateUndoRedo();
34782
+ return {
34783
+ applied: targetIndices.length,
34784
+ dropped: selectedIndices.length - targetIndices.length
34785
+ };
34786
+ };
34787
+ const exportMusicXML2 = () => {
34788
+ const parts = trackStates.map((t) => {
34789
+ const notes = playableNotes(t);
34790
+ const syllables = t.lyrics ? normalizeLyrics(t.lyrics).map(displayKana) : void 0;
34791
+ return {
34792
+ name: t.config.name,
34793
+ notes,
34794
+ lyrics: syllables
34795
+ };
34796
+ });
34797
+ const xml = exportMusicXML({
34798
+ parts,
34799
+ bpm,
34800
+ stepsPerBar: renderConfig.stepsPerBar,
34801
+ title: "DTM Project"
34802
+ });
34803
+ return new Blob([xml], {
34804
+ type: "application/vnd.recordare.musicxml+xml;charset=utf-8"
34805
+ });
34806
+ };
32597
34807
  const exportUST = () => {
32598
34808
  const t = getActive();
32599
34809
  const syllables = normalizeLyrics(t.lyrics).map(displayKana);
@@ -33273,17 +35483,29 @@ var mountDAW = (target, options = {}) => {
33273
35483
  stop();
33274
35484
  overlayDuring(() => {
33275
35485
  const tmpl = selectedComposeTemplate();
35486
+ const sections = selectedComposeSections();
35487
+ const baseKey = refs.composeKey?.value ?? "any";
35488
+ const scale = refs.composeScale?.value ?? "auto";
35489
+ const seed = Math.random() * 4294967296 >>> 0;
33276
35490
  const song = composeSong({
33277
35491
  stepsPerBar: renderConfig.stepsPerBar,
33278
35492
  edo: renderConfig.edo,
33279
- sections: selectedComposeSections(),
35493
+ sections,
33280
35494
  template: tmpl,
33281
- baseKey: refs.composeKey?.value ?? "any",
33282
- scale: refs.composeScale?.value ?? "auto",
35495
+ baseKey,
35496
+ scale,
35497
+ random: seededRandom(seed),
33283
35498
  // 直近に作った曲の特徴を渡すと、それらから離れた候補に加点される。
33284
35499
  // 「作曲」を続けて押したときに似た曲が並ぶのを防ぐ。
33285
35500
  recent: recentComposeFingerprints
33286
35501
  });
35502
+ composeSeed = seed;
35503
+ composeSetting = [
35504
+ tmpl ?? "custom",
35505
+ baseKey,
35506
+ scale,
35507
+ sections.join("-")
35508
+ ].join(":");
33287
35509
  recentComposeFingerprints.push(song.stats.fingerprint);
33288
35510
  if (recentComposeFingerprints.length > 5)
33289
35511
  recentComposeFingerprints.shift();
@@ -33464,6 +35686,7 @@ var mountDAW = (target, options = {}) => {
33464
35686
  updateTrackPanel();
33465
35687
  updateUndoRedo();
33466
35688
  composedSignature = trackSignature();
35689
+ void play();
33467
35690
  });
33468
35691
  };
33469
35692
  const composeWithConfirm = (withVocal) => {
@@ -33483,28 +35706,47 @@ var mountDAW = (target, options = {}) => {
33483
35706
  refs.macroCompose.addEventListener("click", () => {
33484
35707
  composeWithConfirm(false);
33485
35708
  });
33486
- let keptMml = null;
35709
+ let kept = readKeptSong();
33487
35710
  const updateKeepUI = () => {
33488
- refs.composeRecall.disabled = keptMml === null;
33489
- refs.composeKeep.textContent = keptMml === null ? "\u30AD\u30FC\u30D7" : "\u30AD\u30FC\u30D7\u6E08";
35711
+ refs.composeRecall.disabled = kept === null;
35712
+ refs.composeKeep.textContent = kept === null ? "\u30AD\u30FC\u30D7" : "\u30AD\u30FC\u30D7\u6E08";
33490
35713
  };
35714
+ const snapshotKept = () => ({
35715
+ // 小節数の上限は共有リンクの文字数のためのもので、アプリ内の退避には
35716
+ // 効かせない(効かせると長い曲がキープした時点で切れる)。
35717
+ mml: generateMML({ ignoreBarLimit: true }).full,
35718
+ startStep: playStartStep
35719
+ });
33491
35720
  refs.composeKeep.addEventListener("click", () => {
33492
35721
  const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
33493
35722
  if (!hasNotes) return;
33494
- keptMml = generateMML({ ignoreBarLimit: true }).full;
35723
+ kept = snapshotKept();
35724
+ writeKeptSong(kept);
33495
35725
  updateKeepUI();
33496
35726
  });
33497
- refs.composeRecall.addEventListener("click", () => {
33498
- if (keptMml === null) return;
35727
+ const loadKept = (song) => {
33499
35728
  const box = refs.applyActiveOnly;
33500
35729
  const was = box?.checked ?? false;
33501
35730
  if (box) box.checked = false;
33502
35731
  try {
33503
- loadMML(keptMml);
35732
+ loadMML(song.mml);
33504
35733
  } finally {
33505
35734
  if (box) box.checked = was;
33506
35735
  }
35736
+ playStartStep = Math.min(song.startStep, getMaxNoteStep());
33507
35737
  composedSignature = null;
35738
+ void play();
35739
+ };
35740
+ refs.composeRecall.addEventListener("click", () => {
35741
+ if (kept === null) return;
35742
+ const previous = kept;
35743
+ const hasNotes = trackStates.some((t) => t.core.getNotes().length > 0);
35744
+ if (hasNotes) {
35745
+ kept = snapshotKept();
35746
+ writeKeptSong(kept);
35747
+ }
35748
+ loadKept(previous);
35749
+ updateKeepUI();
33508
35750
  });
33509
35751
  updateKeepUI();
33510
35752
  refs.macroComposeVocal.addEventListener("click", () => {
@@ -33563,6 +35805,9 @@ var mountDAW = (target, options = {}) => {
33563
35805
  refs.exportMidiBtn.addEventListener("click", () => {
33564
35806
  download(exportMIDI2(), "dtm.mid");
33565
35807
  });
35808
+ refs.exportMusicXmlBtn.addEventListener("click", () => {
35809
+ download(exportMusicXML2(), "dtm.musicxml");
35810
+ });
33566
35811
  refs.exportUstBtn.addEventListener("click", () => {
33567
35812
  const name = getActive().config.name.replace(/[/:*?"<>|\s]+/g, "_");
33568
35813
  download(exportUST(), `dtm_${name}.ust`);
@@ -33830,6 +36075,7 @@ var mountDAW = (target, options = {}) => {
33830
36075
  });
33831
36076
  if (showMidi) {
33832
36077
  wireMidi();
36078
+ wireMusicXml();
33833
36079
  wireUst();
33834
36080
  }
33835
36081
  if (showAudio) {
@@ -33930,6 +36176,125 @@ var mountDAW = (target, options = {}) => {
33930
36176
  overlayDuring(() => applyMidiSelection(pendingMidi, selected));
33931
36177
  });
33932
36178
  };
36179
+ let pendingMusicXml = null;
36180
+ const setMusicXmlNote = (text) => {
36181
+ refs.musicXmlLoadNote.textContent = text;
36182
+ refs.musicXmlLoadNote.classList.toggle("dtm-hidden", text === "");
36183
+ };
36184
+ const getSelectedMusicXmlPartIndices = () => {
36185
+ if (!pendingMusicXml) return [];
36186
+ const selected = [];
36187
+ const btns = refs.musicXmlPartSelection.querySelectorAll("button");
36188
+ btns.forEach((b, i2) => {
36189
+ if (b.dataset.selected === "true") {
36190
+ selected.push(pendingMusicXml.parts[i2].index);
36191
+ }
36192
+ });
36193
+ return selected;
36194
+ };
36195
+ const updateMusicXmlAssignmentNote = () => {
36196
+ if (!pendingMusicXml) return;
36197
+ const selected = getSelectedMusicXmlPartIndices();
36198
+ if (selected.length === 0) {
36199
+ setMusicXmlNote("\u8AAD\u307F\u8FBC\u3080\u30D1\u30FC\u30C8\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044");
36200
+ return;
36201
+ }
36202
+ const applyActiveOnly = refs.applyActiveOnly?.checked ?? false;
36203
+ const from = applyActiveOnly ? activeTrackIndexOf() : selected.length > 1 ? 0 : activeTrackIndexOf();
36204
+ const maxCount = applyActiveOnly ? 1 : trackStates.length - from;
36205
+ const appliedCount = Math.min(selected.length, maxCount);
36206
+ const selectedParts = selected.map((idx) => pendingMusicXml.parts.find((p) => p.index === idx)).filter(Boolean);
36207
+ const names = selectedParts.slice(0, appliedCount).map(
36208
+ (p, i2) => `${trackStates[from + i2]?.config.name ?? `\u30C8\u30E9\u30C3\u30AF${from + i2 + 1}`}\u2190${p.name}`
36209
+ ).join(" / ");
36210
+ const dropped = selected.length - appliedCount;
36211
+ 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";
36212
+ setMusicXmlNote(
36213
+ dropped > 0 ? `${names}\uFF08${dropReason}${dropped}\u30D1\u30FC\u30C8\u306F\u8AAD\u307F\u8FBC\u307F\u307E\u305B\u3093\uFF09` : names
36214
+ );
36215
+ };
36216
+ const wireMusicXml = () => {
36217
+ refs.musicXmlInfoBtn.addEventListener("click", () => {
36218
+ showModal("MusicXML\u306E\u5165\u51FA\u529B\u89E3\u8AAC", MUSICXML_INFO_HTML);
36219
+ });
36220
+ refs.musicXmlInput.addEventListener("change", async () => {
36221
+ const file = refs.musicXmlInput.files?.[0];
36222
+ pendingMusicXml = null;
36223
+ refs.musicXmlPartSelection.innerHTML = "";
36224
+ if (!file) {
36225
+ setMusicXmlNote("");
36226
+ refs.musicXmlPartSelection.classList.add("dtm-hidden");
36227
+ return;
36228
+ }
36229
+ refs.overlay.hidden = false;
36230
+ setLoading(true);
36231
+ try {
36232
+ const text = await file.text();
36233
+ pendingMusicXml = parseMusicXML(text);
36234
+ if (pendingMusicXml.parts.length === 0) {
36235
+ setMusicXmlNote("\u30D1\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F");
36236
+ refs.musicXmlPartSelection.classList.add("dtm-hidden");
36237
+ return;
36238
+ }
36239
+ refs.musicXmlPartSelection.innerHTML = `<span class="dtm-label">\u30D1\u30FC\u30C8</span>`;
36240
+ pendingMusicXml.parts.forEach((p) => {
36241
+ const btn = document.createElement("button");
36242
+ btn.className = "dtm-btn dtm-btn--primary";
36243
+ btn.dataset.selected = "true";
36244
+ const lyricBadge = p.hasLyrics ? " \u{1F3A4}" : "";
36245
+ btn.textContent = `${p.name} (${p.noteCount}\u97F3${lyricBadge})`;
36246
+ btn.title = `\u5E73\u5747\u97F3\u9AD8: MIDI ${Math.round(p.avgPitch)}${p.hasLyrics ? " / \u6B4C\u8A5E\u3042\u308A" : ""}`;
36247
+ btn.addEventListener("click", () => {
36248
+ const on = btn.dataset.selected !== "true";
36249
+ btn.dataset.selected = String(on);
36250
+ btn.classList.toggle("dtm-btn--primary", on);
36251
+ btn.classList.toggle("dtm-btn--ghost", !on);
36252
+ updateMusicXmlAssignmentNote();
36253
+ });
36254
+ refs.musicXmlPartSelection.appendChild(btn);
36255
+ });
36256
+ refs.musicXmlPartSelection.classList.remove("dtm-hidden");
36257
+ updateMusicXmlAssignmentNote();
36258
+ } catch (e) {
36259
+ setMusicXmlNote("MusicXML\u3068\u3057\u3066\u89E3\u6790\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F");
36260
+ refs.musicXmlPartSelection.classList.add("dtm-hidden");
36261
+ console.error(e);
36262
+ } finally {
36263
+ refs.overlay.hidden = true;
36264
+ setLoading(false);
36265
+ }
36266
+ });
36267
+ refs.musicXmlLoadBtn.addEventListener("click", async () => {
36268
+ if (!pendingMusicXml) return;
36269
+ const selected = getSelectedMusicXmlPartIndices();
36270
+ if (selected.length === 0) return;
36271
+ const applyActiveOnly = refs.applyActiveOnly?.checked ?? false;
36272
+ const from = applyActiveOnly ? activeTrackIndexOf() : selected.length > 1 ? 0 : activeTrackIndexOf();
36273
+ const maxCount = applyActiveOnly ? 1 : trackStates.length - from;
36274
+ if (!isAdvanced && options.onRequestAdvancedMode && !applyActiveOnly && selected.length > maxCount) {
36275
+ const confirmed = await showConfirmModal(
36276
+ "\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"
36277
+ );
36278
+ if (confirmed) {
36279
+ const xml = pendingMusicXml;
36280
+ const sel = selected.slice();
36281
+ options.onRequestAdvancedMode(void 0, (newDaw) => {
36282
+ newDaw.applyMusicXmlParsed?.(xml, sel);
36283
+ });
36284
+ return;
36285
+ }
36286
+ }
36287
+ overlayDuring(() => {
36288
+ const { applied, dropped } = applyMusicXmlSelection(
36289
+ pendingMusicXml,
36290
+ selected
36291
+ );
36292
+ setMusicXmlNote(
36293
+ 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`
36294
+ );
36295
+ });
36296
+ });
36297
+ };
33933
36298
  let pendingUsts = [];
33934
36299
  const setUstNote = (text) => {
33935
36300
  refs.ustLoadNote.textContent = text;
@@ -34526,6 +36891,15 @@ var mountDAW = (target, options = {}) => {
34526
36891
  resizeTimer = setTimeout(() => setupCanvas(), 150);
34527
36892
  });
34528
36893
  resizeObserver.observe(refs.rollContainer);
36894
+ const WIDE_LAYOUT_MIN_WIDTH = 1e3;
36895
+ const applyWideLayout = (width) => {
36896
+ refs.root.classList.toggle("dtm-daw--wide", width >= WIDE_LAYOUT_MIN_WIDTH);
36897
+ };
36898
+ const layoutObserver = new ResizeObserver((entries) => {
36899
+ for (const entry of entries) applyWideLayout(entry.contentRect.width);
36900
+ });
36901
+ layoutObserver.observe(refs.root);
36902
+ applyWideLayout(refs.root.getBoundingClientRect().width);
34529
36903
  document.addEventListener("pointermove", onPointerMove);
34530
36904
  document.addEventListener("pointerup", onPointerUp);
34531
36905
  const setLoading = (loading) => {
@@ -34627,10 +37001,14 @@ var mountDAW = (target, options = {}) => {
34627
37001
  applyMidiParsed: (midi, selectedIndices) => {
34628
37002
  overlayDuring(() => applyMidiSelection(midi, selectedIndices));
34629
37003
  },
37004
+ applyMusicXmlParsed: (xml, selectedIndices) => {
37005
+ overlayDuring(() => applyMusicXmlSelection(xml, selectedIndices));
37006
+ },
34630
37007
  applyUstParsed: (ustTracks, startIndex) => {
34631
37008
  overlayDuring(() => applyUstTracks(ustTracks, startIndex));
34632
37009
  },
34633
37010
  exportMIDI: exportMIDI2,
37011
+ exportMusicXML: exportMusicXML2,
34634
37012
  exportUST,
34635
37013
  setBpm,
34636
37014
  getLoop: () => loopEnabled,
@@ -34765,6 +37143,7 @@ var mountDAW = (target, options = {}) => {
34765
37143
  unsubscribeClip?.();
34766
37144
  stopPeakSampling();
34767
37145
  resizeObserver.disconnect();
37146
+ layoutObserver.disconnect();
34768
37147
  document.removeEventListener("pointermove", onPointerMove);
34769
37148
  document.removeEventListener("pointerup", onPointerUp);
34770
37149
  document.removeEventListener("keydown", onKeyDown);
@@ -34817,7 +37196,7 @@ var readGlobalBool = (key) => {
34817
37196
  };
34818
37197
 
34819
37198
  // src/headless-singing-player.ts
34820
- var STEPS_PER_BEAT5 = 48;
37199
+ var STEPS_PER_BEAT6 = 48;
34821
37200
  var STEPS_PER_BAR3 = 192;
34822
37201
  var TRACK_ID_BY_INDEX2 = ["melody", "submelody", "bass", "chord"];
34823
37202
  var playSingingMML = async (mml, options = {}) => {
@@ -34834,7 +37213,7 @@ var playSingingMML = async (mml, options = {}) => {
34834
37213
  parseCustomVocals(mml).map((d) => [d.key, d])
34835
37214
  );
34836
37215
  const bpm = parsedBpm ?? options.defaultBpm ?? DEFAULT_BPM;
34837
- const secondsPerStep = 60 / bpm / STEPS_PER_BEAT5;
37216
+ const secondsPerStep = 60 / bpm / STEPS_PER_BEAT6;
34838
37217
  const drumPatternDict = {
34839
37218
  ...DRUM_PATTERNS,
34840
37219
  ...SONG_DRUM_PATTERNS,
@@ -35002,7 +37381,8 @@ var playSingingMML = async (mml, options = {}) => {
35002
37381
  if (lyricTracks.size > 0) {
35003
37382
  if (!voices) {
35004
37383
  voices = createSingingVoices(ctx, destination, {
35005
- voiceWorkerUrl: options.voiceWorkerUrl
37384
+ voiceWorkerUrl: options.voiceWorkerUrl,
37385
+ ttsBaseUrl: options.ttsBaseUrl
35006
37386
  });
35007
37387
  }
35008
37388
  if (customVocalByKey.size > 0 && voices.registerVoicebanks) {
@@ -35066,275 +37446,6 @@ var playSingingMML = async (mml, options = {}) => {
35066
37446
  return playback;
35067
37447
  };
35068
37448
 
35069
- // src/musicxml-io.ts
35070
- var STEPS_PER_BEAT6 = 48;
35071
- var PITCH_CLASS = {
35072
- C: 0,
35073
- D: 2,
35074
- E: 4,
35075
- F: 5,
35076
- G: 7,
35077
- A: 9,
35078
- B: 11
35079
- };
35080
- var textOf = (el, tag) => el?.getElementsByTagName(tag)[0]?.textContent?.trim() ?? "";
35081
- var numOf = (el, tag, fallback = 0) => {
35082
- const raw = textOf(el, tag);
35083
- if (!raw) return fallback;
35084
- const v = Number.parseFloat(raw);
35085
- return Number.isFinite(v) ? v : fallback;
35086
- };
35087
- var pitchOf = (note) => {
35088
- const p = note.getElementsByTagName("pitch")[0];
35089
- if (!p) return null;
35090
- const step = textOf(p, "step").toUpperCase();
35091
- const base = PITCH_CLASS[step];
35092
- if (base === void 0) return null;
35093
- const octave = numOf(p, "octave", 4);
35094
- const alter = Math.round(numOf(p, "alter", 0));
35095
- return (octave + 1) * 12 + base + alter;
35096
- };
35097
- var parseMusicXML = (xml) => {
35098
- const doc = new DOMParser().parseFromString(xml, "application/xml");
35099
- if (doc.getElementsByTagName("parserror").length > 0)
35100
- throw new Error("MusicXML \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093");
35101
- const nameById = /* @__PURE__ */ new Map();
35102
- for (const sp of Array.from(doc.getElementsByTagName("score-part"))) {
35103
- const id = sp.getAttribute("id");
35104
- if (id) nameById.set(id, textOf(sp, "part-name") || id);
35105
- }
35106
- const placements = [];
35107
- const parts = [];
35108
- let bpm = 0;
35109
- const partEls = Array.from(doc.getElementsByTagName("part"));
35110
- partEls.forEach((partEl, partIndex) => {
35111
- const id = partEl.getAttribute("id") ?? "";
35112
- let divisions = 1;
35113
- let cursor = 0;
35114
- let measureStart = 0;
35115
- let lastStart = 0;
35116
- let lastDurSteps = 0;
35117
- const tied = /* @__PURE__ */ new Map();
35118
- let count = 0;
35119
- let pitchSum = 0;
35120
- let lyricSeen = false;
35121
- for (const measure of Array.from(partEl.getElementsByTagName("measure"))) {
35122
- measureStart += cursor;
35123
- cursor = 0;
35124
- for (const child of Array.from(measure.children)) {
35125
- const tag = child.tagName.toLowerCase();
35126
- if (tag === "attributes") {
35127
- const d = numOf(child, "divisions", 0);
35128
- if (d > 0) divisions = d;
35129
- continue;
35130
- }
35131
- if (tag === "direction") {
35132
- const sound = child.getElementsByTagName("sound")[0];
35133
- const t = Number.parseFloat(sound?.getAttribute("tempo") ?? "");
35134
- if (!bpm && Number.isFinite(t) && t > 0) bpm = t;
35135
- continue;
35136
- }
35137
- if (tag === "backup" || tag === "forward") {
35138
- const steps = numOf(child, "duration", 0) / divisions * STEPS_PER_BEAT6;
35139
- cursor += tag === "backup" ? -steps : steps;
35140
- continue;
35141
- }
35142
- if (tag !== "note") continue;
35143
- const durSteps = Math.round(
35144
- numOf(child, "duration", 0) / divisions * STEPS_PER_BEAT6
35145
- );
35146
- const isChord = child.getElementsByTagName("chord").length > 0;
35147
- const isRest = child.getElementsByTagName("rest").length > 0;
35148
- const start = isChord ? lastStart : measureStart + cursor;
35149
- if (!isRest) {
35150
- const pitch = pitchOf(child);
35151
- if (pitch !== null) {
35152
- const lyric = textOf(
35153
- child.getElementsByTagName("lyric")[0],
35154
- "text"
35155
- );
35156
- if (lyric) lyricSeen = true;
35157
- const ties = Array.from(child.getElementsByTagName("tie"));
35158
- const stops = ties.some((t) => t.getAttribute("type") === "stop");
35159
- const starts = ties.some((t) => t.getAttribute("type") === "start");
35160
- const held = tied.get(pitch);
35161
- if (stops && held) {
35162
- held.durationSteps += durSteps;
35163
- if (!starts) tied.delete(pitch);
35164
- } else {
35165
- const placed = {
35166
- partIndex,
35167
- startStep: Math.max(0, Math.round(start)),
35168
- pitch,
35169
- durationSteps: Math.max(1, durSteps),
35170
- lyric
35171
- };
35172
- placements.push(placed);
35173
- count++;
35174
- pitchSum += pitch;
35175
- if (starts) tied.set(pitch, placed);
35176
- }
35177
- }
35178
- }
35179
- if (!isChord) {
35180
- lastStart = start;
35181
- lastDurSteps = durSteps;
35182
- cursor += durSteps;
35183
- } else {
35184
- lastDurSteps = Math.max(lastDurSteps, durSteps);
35185
- }
35186
- }
35187
- }
35188
- parts.push({
35189
- index: partIndex,
35190
- name: nameById.get(id) || `Part ${partIndex + 1}`,
35191
- noteCount: count,
35192
- hasLyrics: lyricSeen,
35193
- avgPitch: count > 0 ? pitchSum / count : 0
35194
- });
35195
- });
35196
- return { parts, placements, bpm: bpm > 0 ? Math.round(bpm) : 120 };
35197
- };
35198
- var SHARP_SPELLING = [
35199
- ["C", 0],
35200
- ["C", 1],
35201
- ["D", 0],
35202
- ["D", 1],
35203
- ["E", 0],
35204
- ["F", 0],
35205
- ["F", 1],
35206
- ["G", 0],
35207
- ["G", 1],
35208
- ["A", 0],
35209
- ["A", 1],
35210
- ["B", 0]
35211
- ];
35212
- var NOTE_TYPES = [
35213
- [STEPS_PER_BEAT6 * 4, "whole"],
35214
- [STEPS_PER_BEAT6 * 2, "half"],
35215
- [STEPS_PER_BEAT6, "quarter"],
35216
- [STEPS_PER_BEAT6 / 2, "eighth"],
35217
- [STEPS_PER_BEAT6 / 4, "16th"],
35218
- [STEPS_PER_BEAT6 / 8, "32nd"]
35219
- ];
35220
- var typeOf = (steps) => {
35221
- for (const [len, type] of NOTE_TYPES) {
35222
- if (steps >= len) {
35223
- const dots = steps >= len * 1.5 && steps < len * 2 ? 1 : 0;
35224
- return { type, dots };
35225
- }
35226
- }
35227
- return { type: "32nd", dots: 0 };
35228
- };
35229
- var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
35230
- var exportMusicXML = (options) => {
35231
- const { parts, bpm, stepsPerBar, title } = options;
35232
- const beatsPerBar = Math.max(1, Math.round(stepsPerBar / STEPS_PER_BEAT6));
35233
- const partList = parts.map(
35234
- (p, i2) => ` <score-part id="P${i2 + 1}">
35235
- <part-name>${esc(p.name)}</part-name>
35236
- </score-part>`
35237
- ).join("\n");
35238
- const body = parts.map((part, pi) => {
35239
- const notes = [...part.notes].sort((a, b) => a.startStep - b.startStep);
35240
- const end = notes.reduce(
35241
- (m, n) => Math.max(m, n.startStep + n.durationSteps),
35242
- 0
35243
- );
35244
- const bars = Math.max(1, Math.ceil(end / stepsPerBar));
35245
- const lines = [` <part id="P${pi + 1}">`];
35246
- let li = 0;
35247
- for (let bar = 0; bar < bars; bar++) {
35248
- const from = bar * stepsPerBar;
35249
- const to = from + stepsPerBar;
35250
- lines.push(` <measure number="${bar + 1}">`);
35251
- if (bar === 0) {
35252
- lines.push(
35253
- ` <attributes>
35254
- <divisions>${STEPS_PER_BEAT6}</divisions>`,
35255
- ` <key><fifths>0</fifths></key>`,
35256
- ` <time><beats>${beatsPerBar}</beats><beat-type>4</beat-type></time>`,
35257
- ` <clef><sign>G</sign><line>2</line></clef>
35258
- </attributes>`
35259
- );
35260
- if (pi === 0)
35261
- lines.push(
35262
- ` <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>`
35263
- );
35264
- }
35265
- let cursor = from;
35266
- const inBar = notes.filter(
35267
- (n) => n.startStep >= from && n.startStep < to
35268
- );
35269
- const groups = /* @__PURE__ */ new Map();
35270
- for (const n of inBar) {
35271
- const g = groups.get(n.startStep);
35272
- if (g) g.push(n);
35273
- else groups.set(n.startStep, [n]);
35274
- }
35275
- const starts = [...groups.keys()].sort((a, b) => a - b);
35276
- for (let gi = 0; gi < starts.length; gi++) {
35277
- const at = starts[gi];
35278
- const group = groups.get(at);
35279
- if (at > cursor) {
35280
- const { type, dots } = typeOf(at - cursor);
35281
- lines.push(
35282
- ` <note><rest/><duration>${at - cursor}</duration><type>${type}</type>${"<dot/>".repeat(dots)}</note>`
35283
- );
35284
- cursor = at;
35285
- }
35286
- const nextAt = gi + 1 < starts.length ? starts[gi + 1] : to;
35287
- const room = Math.min(to, nextAt) - at;
35288
- const dur = Math.max(
35289
- 1,
35290
- Math.min(Math.max(...group.map((n) => n.durationSteps)), room)
35291
- );
35292
- group.forEach((n, ni) => {
35293
- const semi = Math.round(n.pitchUnits / UNITS_PER_SEMITONE);
35294
- const [step, alter] = SHARP_SPELLING[(semi % 12 + 12) % 12];
35295
- const octave = Math.floor(semi / 12) - 1;
35296
- const { type, dots } = typeOf(dur);
35297
- const lyric = part.lyrics?.[li];
35298
- lines.push(
35299
- ` <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>`
35300
- );
35301
- if (ni === 0) li++;
35302
- });
35303
- cursor = at + dur;
35304
- }
35305
- if (cursor < to) {
35306
- const { type, dots } = typeOf(to - cursor);
35307
- lines.push(
35308
- ` <note><rest/><duration>${to - cursor}</duration><type>${type}</type>${"<dot/>".repeat(dots)}</note>`
35309
- );
35310
- }
35311
- lines.push(" </measure>");
35312
- }
35313
- lines.push(" </part>");
35314
- return lines.join("\n");
35315
- }).join("\n");
35316
- return [
35317
- '<?xml version="1.0" encoding="UTF-8"?>',
35318
- '<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">',
35319
- '<score-partwise version="4.0">',
35320
- ` <work><work-title>${esc(title ?? "Untitled")}</work-title></work>`,
35321
- // 書き出し元を残す。MML・MIDI と揃えてある(`src/version.ts` に理由)。
35322
- ` <identification><encoding><software>dtm ${DTM_VERSION}</software></encoding></identification>`,
35323
- " <part-list>",
35324
- partList,
35325
- " </part-list>",
35326
- body,
35327
- "</score-partwise>",
35328
- ""
35329
- ].join("\n");
35330
- };
35331
- var musicXmlToNotes = (placements, partIndex) => placements.filter((p) => p.partIndex === partIndex).map((p) => ({
35332
- startStep: p.startStep,
35333
- durationSteps: p.durationSteps,
35334
- pitchUnits: p.pitch * UNITS_PER_SEMITONE,
35335
- velocity: 100
35336
- }));
35337
-
35338
37449
  // src/piano-roll.ts
35339
37450
  var createPianoRoll = (options, handlers) => {
35340
37451
  const {
@@ -36269,6 +38380,7 @@ var createDtmStudio = async (options = {}) => {
36269
38380
  voiceWorkerUrl,
36270
38381
  voicebanks,
36271
38382
  worldlineScriptUrl: options.worldlineScriptUrl,
38383
+ ttsBaseUrl: options.ttsBaseUrl,
36272
38384
  // ボーカルトラック個別のリバーブセンド(`r`トークン)先。マスタリバーブの
36273
38385
  // Convolver 入力へ直接センドする(PreDelayは経由しない。チャンネルストリップの
36274
38386
  // reverbSend とは別経路の、音節単位でより細かく制御できるセンド)。
@@ -37318,6 +39430,7 @@ export {
37318
39430
  DEFAULT_PLAYBACK_VELOCITY,
37319
39431
  DEFAULT_SECTIONS,
37320
39432
  DEFAULT_STEPS_PER_BAR,
39433
+ DEFAULT_TTS_BASE_URL,
37321
39434
  DEFAULT_UST_LYRIC,
37322
39435
  DEFAULT_VELOCITY,
37323
39436
  DEFAULT_VOCAL_VOLUME,
@@ -37361,6 +39474,8 @@ export {
37361
39474
  SECTION_ORDER,
37362
39475
  SECTION_SPECS,
37363
39476
  SEQUENCER_START_DELAY,
39477
+ SPEAK_CLOSE,
39478
+ SPEAK_OPEN,
37364
39479
  STOP_MARK,
37365
39480
  TIE_MARK,
37366
39481
  TIE_MERGE_MAX_SEC,
@@ -37402,6 +39517,8 @@ export {
37402
39517
  createRenderer,
37403
39518
  createSequencer,
37404
39519
  createSingingVoices,
39520
+ createSpeechPlanner,
39521
+ createSpeechToneView,
37405
39522
  createSynth,
37406
39523
  createVoiceRegistry,
37407
39524
  decodeMml,
@@ -37430,6 +39547,7 @@ export {
37430
39547
  getComposeScaleDescription,
37431
39548
  getDrumPatternKeys,
37432
39549
  getMidiBPM,
39550
+ getSpeechPlanner,
37433
39551
  hasSeenTour,
37434
39552
  icon,
37435
39553
  injectStyles,
@@ -37444,6 +39562,7 @@ export {
37444
39562
  looksLikePinyin,
37445
39563
  lyricToSyllable,
37446
39564
  markTourSeen,
39565
+ medianRecordedPitchHz,
37447
39566
  midiNote,
37448
39567
  midiToUnits,
37449
39568
  mountChordPlayer,
@@ -37469,10 +39588,12 @@ export {
37469
39588
  playNote,
37470
39589
  playPlacements,
37471
39590
  playSingingMML,
39591
+ prepareSpeechPlan,
37472
39592
  programOfInstrumentName,
37473
39593
  readGlobalBool,
37474
39594
  readGlobalNumber,
37475
39595
  readGlobalSetting,
39596
+ readKeptSong,
37476
39597
  readMacroSections,
37477
39598
  readMacroSetting,
37478
39599
  resolveCenter,
@@ -37487,6 +39608,8 @@ export {
37487
39608
  semitonesToUnits,
37488
39609
  shiftNotes,
37489
39610
  showLoadingOverlay,
39611
+ speechPlanDurationSec,
39612
+ speechPlanLeadingSec,
37490
39613
  spelledToUnits,
37491
39614
  spellingToUnits,
37492
39615
  startTour,
@@ -37506,6 +39629,7 @@ export {
37506
39629
  unitsToPitchV1,
37507
39630
  vocalVolumeToGain,
37508
39631
  writeGlobalSetting,
39632
+ writeKeptSong,
37509
39633
  writeMacroSections,
37510
39634
  writeMacroSetting
37511
39635
  };