@onjmin/dtm 0.1.1 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21,6 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DAW_CSS: () => DAW_CSS,
24
+ DEFAULT_BPM: () => DEFAULT_BPM,
25
+ DEFAULT_GATE: () => DEFAULT_GATE,
26
+ DEFAULT_PAN: () => DEFAULT_PAN,
27
+ DEFAULT_PLAYBACK_VELOCITY: () => DEFAULT_PLAYBACK_VELOCITY,
28
+ DEFAULT_STEPS_PER_BAR: () => DEFAULT_STEPS_PER_BAR,
29
+ DEFAULT_VELOCITY: () => DEFAULT_VELOCITY,
30
+ DEFAULT_VOCAL_VOLUME: () => DEFAULT_VOCAL_VOLUME,
24
31
  DRUM_FONT: () => DRUM_FONT,
25
32
  DRUM_KEYS: () => DRUM_KEYS,
26
33
  DRUM_PATTERNS: () => DRUM_PATTERNS,
@@ -28,6 +35,7 @@ __export(index_exports, {
28
35
  KOE_BASE_URL: () => KOE_BASE_URL,
29
36
  KOE_VOICEBANKS: () => KOE_VOICEBANKS,
30
37
  KOE_VOICEBANK_LABELS: () => KOE_VOICEBANK_LABELS,
38
+ KOE_VOICEBANK_TERMS: () => KOE_VOICEBANK_TERMS,
31
39
  LinkedList: () => LinkedList,
32
40
  MAX_VOCAL_VOLUME: () => MAX_VOCAL_VOLUME,
33
41
  MMLCore: () => MMLCore,
@@ -35,6 +43,7 @@ __export(index_exports, {
35
43
  PREWARM_NOTES: () => PREWARM_NOTES,
36
44
  TRACKS_ADVANCED: () => TRACKS_ADVANCED,
37
45
  TRACKS_SIMPLE: () => TRACKS_SIMPLE,
46
+ VOICE_IMAGE_KEY: () => VOICE_IMAGE_KEY,
38
47
  analyzeMidiTracks: () => analyzeMidiTracks,
39
48
  applyHarmonicFilter: () => applyHarmonicFilter,
40
49
  applyMonophonic: () => applyMonophonic,
@@ -42,6 +51,7 @@ __export(index_exports, {
42
51
  buildNameToKeyMapping: () => buildNameToKeyMapping,
43
52
  collectPitchTokens: () => collectPitchTokens,
44
53
  createAudioContext: () => createAudioContext,
54
+ createDtmStudio: () => createDtmStudio,
45
55
  createKlattVoice: () => createKlattVoice,
46
56
  createKoeVoice: () => createKoeVoice,
47
57
  createLyricsConductor: () => createLyricsConductor,
@@ -379,7 +389,10 @@ var buildUI = (target, options) => {
379
389
  <div class="dtm-tracks" data-dtm="track-tabs"></div>
380
390
 
381
391
  <div class="dtm-roll-wrap">
382
- <div class="dtm-roll" data-dtm="roll"><div data-dtm="wrapper" style="position:absolute;inset:0;"></div></div>
392
+ <div class="dtm-roll" data-dtm="roll">
393
+ <div data-dtm="wrapper" style="position:absolute;inset:0;"></div>
394
+ <div class="dtm-overlay" data-dtm="overlay" hidden><div class="dtm-spinner"></div></div>
395
+ </div>
383
396
  <div class="dtm-vscroll" data-dtm="vscroll"><div class="dtm-vscroll-thumb" data-dtm="vscroll-thumb"></div></div>
384
397
  </div>
385
398
  <div class="dtm-hscroll" data-dtm="hscroll"><div class="dtm-hscroll-thumb" data-dtm="hscroll-thumb"></div></div>
@@ -512,8 +525,6 @@ var buildUI = (target, options) => {
512
525
  </div>
513
526
  </div>
514
527
  </details>
515
-
516
- <div class="dtm-overlay" data-dtm="overlay" hidden><div class="dtm-spinner"></div></div>
517
528
  </div>`;
518
529
  const root = q(target, '[data-dtm="root"]');
519
530
  const sel = (name) => q(root, `[data-dtm="${name}"]`);
@@ -1000,6 +1011,15 @@ var Worldline = class _Worldline {
1000
1011
  }
1001
1012
  };
1002
1013
 
1014
+ // src/types.ts
1015
+ var DEFAULT_VOCAL_VOLUME = 200;
1016
+ var DEFAULT_BPM = 120;
1017
+ var DEFAULT_GATE = 100;
1018
+ var DEFAULT_PAN = 64;
1019
+ var DEFAULT_VELOCITY = 100;
1020
+ var DEFAULT_PLAYBACK_VELOCITY = 127;
1021
+ var DEFAULT_STEPS_PER_BAR = 192;
1022
+
1003
1023
  // src/lyrics.ts
1004
1024
  var kanaTable = {
1005
1025
  \u3042: ["", "a"],
@@ -1170,9 +1190,10 @@ var parseLyrics = (mml) => {
1170
1190
  const trackId = Number.parseInt(m[1], 10);
1171
1191
  const tokens = m[2].trim().split(/\s+/);
1172
1192
  const modelToken = tokens.shift() ?? "";
1173
- let volume = 300;
1193
+ let volume = DEFAULT_VOCAL_VOLUME;
1174
1194
  let gate = 100;
1175
1195
  let pan = 64;
1196
+ let octave = 0;
1176
1197
  const colon = modelToken.indexOf(":");
1177
1198
  const model = (colon === -1 ? modelToken : modelToken.slice(0, colon)).toLowerCase();
1178
1199
  if (colon !== -1) {
@@ -1184,12 +1205,15 @@ var parseLyrics = (mml) => {
1184
1205
  const v = /^v(\d+)$/.exec(tokens[0]);
1185
1206
  const q2 = /^q(\d+)$/.exec(tokens[0]);
1186
1207
  const p = /^p(\d+)$/.exec(tokens[0]);
1208
+ const o = /^o(-?\d+)$/.exec(tokens[0]);
1187
1209
  if (v) {
1188
1210
  volume = clamp(Number.parseInt(v[1], 10), 0, MAX_VOCAL_VOLUME);
1189
1211
  } else if (q2) {
1190
1212
  gate = clamp(Number.parseInt(q2[1], 10), 0, 100);
1191
1213
  } else if (p) {
1192
1214
  pan = clamp(Number.parseInt(p[1], 10), 0, 127);
1215
+ } else if (o) {
1216
+ octave = clamp(Number.parseInt(o[1], 10), -2, 2);
1193
1217
  } else {
1194
1218
  break;
1195
1219
  }
@@ -1206,6 +1230,7 @@ var parseLyrics = (mml) => {
1206
1230
  volume,
1207
1231
  gate,
1208
1232
  pan,
1233
+ octave,
1209
1234
  syllables,
1210
1235
  metaText: metaTokens.join(" "),
1211
1236
  ...lineBreaks.length > 0 ? { lineBreaks } : {}
@@ -1239,9 +1264,9 @@ var createLyricsConductor = (lyrics) => {
1239
1264
  return {
1240
1265
  model: track.model,
1241
1266
  syllable,
1242
- volume: vocalVolumeToGain(track.volume ?? 300),
1243
- gate: (track.gate ?? 100) / 100,
1244
- pan: panToStereo(track.pan ?? 64)
1267
+ volume: vocalVolumeToGain(track.volume ?? DEFAULT_VOCAL_VOLUME),
1268
+ gate: (track.gate ?? DEFAULT_GATE) / 100,
1269
+ pan: panToStereo(track.pan ?? DEFAULT_PAN)
1245
1270
  };
1246
1271
  };
1247
1272
  const reset = () => pointers.clear();
@@ -1349,7 +1374,8 @@ var KOE_VOICEBANKS = {
1349
1374
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093.koe",
1350
1375
  rino: "\u6625\u97F3\u30EA\u30CEver0.3.koe",
1351
1376
  roze: "\u675F\u97F3\u30ED\u30BCver0.\uFF151(\u591A\u97F3\u968E).koe",
1352
- ruko: "\u6B32\u97F3\u30EB\u30B3\u2640\u6B4C\u9023\u7D9A\u97F3\u666E1.00.koe",
1377
+ ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642\u9023\u7D9A\u97F3Ver.1.03.koe",
1378
+ ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640\u6B4C\u9023\u7D9A\u97F3\u666E1.00.koe",
1353
1379
  teto: "\u91CD\u97F3\u30C6\u30C8\u5358\u72EC\u97F3.koe",
1354
1380
  shiyo: "\u9769\u547D\u30B7\u30E8.koe"
1355
1381
  };
@@ -1357,10 +1383,30 @@ var KOE_VOICEBANK_LABELS = {
1357
1383
  tsukuyomi: "\u3064\u304F\u3088\u307F\u3061\u3083\u3093",
1358
1384
  rino: "\u6625\u97F3\u30EA\u30CE",
1359
1385
  roze: "\u675F\u97F3\u30ED\u30BC",
1360
- ruko: "\u6B32\u97F3\u30EB\u30B3",
1386
+ ruko_male: "\u6B32\u97F3\u30EB\u30B3\u2642",
1387
+ ruko_female: "\u6B32\u97F3\u30EB\u30B3\u2640",
1361
1388
  teto: "\u91CD\u97F3\u30C6\u30C8",
1362
1389
  shiyo: "\u9769\u547D\u30B7\u30E8"
1363
1390
  };
1391
+ var VOICE_IMAGE_KEY = {
1392
+ klatt: "puyuyu",
1393
+ tsukuyomi: "tsukuyomi",
1394
+ rino: "rino",
1395
+ roze: "roze",
1396
+ ruko_male: "ruko",
1397
+ ruko_female: "ruko",
1398
+ teto: "teto",
1399
+ shiyo: "shiyo"
1400
+ };
1401
+ var KOE_VOICEBANK_TERMS = {
1402
+ tsukuyomi: "https://tyc.rei-yumesaki.net/material/utau/terms/",
1403
+ rino: "https://hatenakun1.github.io/halunelino/",
1404
+ roze: "https://tabaneroze.ninja-web.net/terms-of-use.html",
1405
+ ruko_male: "https://long-sleeper.net/index.php?id=22",
1406
+ ruko_female: "https://long-sleeper.net/index.php?id=22",
1407
+ teto: "https://kasaneteto.jp/guidelines/voice.html",
1408
+ shiyo: "https://kakumeisiyo.my.canva.site/dagkuyjwycs"
1409
+ };
1364
1410
  var koeUrl = (name, base = KOE_BASE_URL) => `${base}/${encodeURIComponent(name)}`;
1365
1411
  var DEFAULT_WORLDLINE_SCRIPT = "https://onjmin.github.io/koe/demo/world/worldline.js";
1366
1412
  var KOE_SAMPLE_RATE = 48e3;
@@ -1508,9 +1554,10 @@ var createWorkerBackend = async (workerUrl, options) => {
1508
1554
  }
1509
1555
  }
1510
1556
  };
1511
- worker.onerror = (e) => onFail?.(
1512
- new Error(`voice worker error: ${e.message || e}`)
1513
- );
1557
+ worker.onerror = (e) => {
1558
+ const ev = e;
1559
+ onFail?.(new Error(ev.message || ev.error || `Event: ${ev.type}`));
1560
+ };
1514
1561
  await new Promise((resolve, reject) => {
1515
1562
  onReady = resolve;
1516
1563
  onFail = reject;
@@ -1686,22 +1733,18 @@ var createSingingVoices = (ctx, destination, options = {}) => {
1686
1733
  if (inflight) return inflight;
1687
1734
  const koe = catalog[m];
1688
1735
  if (!koe) return Promise.resolve(null);
1689
- const p = (async () => {
1690
- let source = koe;
1691
- if (typeof source === "string") {
1692
- const res = await fetch(source);
1693
- if (!res.ok) {
1694
- throw new Error(`fetch failed: ${res.statusText}`);
1695
- }
1696
- source = await res.blob();
1697
- }
1698
- return createKoeVoice(ctx, destination, {
1699
- koe: source,
1736
+ const p = (async () => (
1737
+ // URL文字列はそのまま渡す。koe側が VoiceBank.load 内で HTTP Range により
1738
+ // マニフェストだけ先読みし、音素PCMは歌う直前にオンデマンド取得する
1739
+ // (= 初回に .koe 全体をDLしない。モバイル初回ロードの待ちを解消)。
1740
+ // Blob/File が直接渡されたケース(ローカル読み込み)はそのまま BlobVoiceSource。
1741
+ createKoeVoice(ctx, destination, {
1742
+ koe,
1700
1743
  worldlineScriptUrl: options.worldlineScriptUrl,
1701
1744
  lightweight: options.lightweight,
1702
1745
  voiceWorkerUrl: options.voiceWorkerUrl
1703
- });
1704
- })().then((v) => {
1746
+ })
1747
+ ))().then((v) => {
1705
1748
  loaded.set(m, v);
1706
1749
  return v;
1707
1750
  }).catch((err) => {
@@ -2225,7 +2268,7 @@ var exportMIDI = (options) => {
2225
2268
  (n.startStep + (n.durationSteps || 1)) * tickPerStep
2226
2269
  );
2227
2270
  const vel = Math.round(
2228
- (n.velocity ?? 100) * (track.volume || 100) / 100
2271
+ (n.velocity ?? DEFAULT_VELOCITY) * (track.volume ?? 100) / 100
2229
2272
  );
2230
2273
  events.push({ t: startTick, m: [144 | ch & 15, n.pitch, vel] });
2231
2274
  events.push({ t: endTick, m: [144 | ch & 15, n.pitch, 0] });
@@ -2780,7 +2823,7 @@ var MMLCore = class _MMLCore {
2780
2823
  startStep: step,
2781
2824
  durationSteps: options.noteLengthSteps,
2782
2825
  pitch,
2783
- velocity: options.velocity ?? 100
2826
+ velocity: options.velocity ?? DEFAULT_VELOCITY
2784
2827
  };
2785
2828
  this.notes.push(newNote);
2786
2829
  }
@@ -2955,19 +2998,15 @@ var MMLCore = class _MMLCore {
2955
2998
  notesByStep.set(n.startStep, list);
2956
2999
  }
2957
3000
  const sortedSteps = Array.from(notesByStep.keys()).sort((a, b) => a - b);
3001
+ const MIN_STEP = config.stepsPerBar / 64;
2958
3002
  const fillRests = (until) => {
2959
- while (currentCursor < until) {
3003
+ while (until - currentCursor >= MIN_STEP) {
2960
3004
  const gap = until - currentCursor;
2961
- if (gap <= 2) {
2962
- currentCursor = until;
2963
- break;
2964
- }
2965
3005
  const { dur, steps } = this.findBestFitDuration(gap);
2966
3006
  segments.push(`r${dur}`);
2967
3007
  currentCursor += steps;
2968
3008
  }
2969
3009
  };
2970
- const MIN_STEP = config.stepsPerBar / 64;
2971
3010
  for (let i = 0; i < sortedSteps.length; i++) {
2972
3011
  const startStep = sortedSteps[i];
2973
3012
  const notes = notesByStep.get(startStep);
@@ -2976,7 +3015,6 @@ var MMLCore = class _MMLCore {
2976
3015
  const nextStart = sortedSteps[i + 1] ?? endStep;
2977
3016
  const physicsLimit = nextStart - currentCursor;
2978
3017
  if (physicsLimit < MIN_STEP) {
2979
- currentCursor = startStep;
2980
3018
  continue;
2981
3019
  }
2982
3020
  const idealDuration = notes[0].durationSteps;
@@ -3101,7 +3139,7 @@ var formatMmlMeta = (meta) => {
3101
3139
  return parts.join(" ");
3102
3140
  };
3103
3141
  var parseMML = (mml, options = {}) => {
3104
- const stepsPerBar = options.stepsPerBar ?? 192;
3142
+ const stepsPerBar = options.stepsPerBar ?? DEFAULT_STEPS_PER_BAR;
3105
3143
  const collectTokens = options.collectTokens ?? false;
3106
3144
  const collectLyrics = options.collectLyrics ?? false;
3107
3145
  const clampTrackCount = options.clampTrackCount;
@@ -3178,14 +3216,14 @@ var parseMML = (mml, options = {}) => {
3178
3216
  numStr += body[j];
3179
3217
  j++;
3180
3218
  }
3181
- octave = clamp2(Number.parseInt(numStr, 10) || 4, 0, 8);
3219
+ octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3182
3220
  pushTok("octave", currentStep, 0, tokStart);
3183
3221
  } else if (ch === ">") {
3184
- octave++;
3222
+ octave = Math.min(8, octave + 1);
3185
3223
  j++;
3186
3224
  pushTok("shift", currentStep, 0, tokStart);
3187
3225
  } else if (ch === "<") {
3188
- octave--;
3226
+ octave = Math.max(0, octave - 1);
3189
3227
  j++;
3190
3228
  pushTok("shift", currentStep, 0, tokStart);
3191
3229
  } else if (ch === "l") {
@@ -3232,10 +3270,10 @@ var parseMML = (mml, options = {}) => {
3232
3270
  }
3233
3271
  chordNotes.push((octave + 1) * 12 + pitch);
3234
3272
  } else if (c === ">") {
3235
- octave++;
3273
+ octave = Math.min(8, octave + 1);
3236
3274
  j++;
3237
3275
  } else if (c === "<") {
3238
- octave--;
3276
+ octave = Math.max(0, octave - 1);
3239
3277
  j++;
3240
3278
  } else if (c === "o") {
3241
3279
  j++;
@@ -3244,7 +3282,7 @@ var parseMML = (mml, options = {}) => {
3244
3282
  numStr += body[j];
3245
3283
  j++;
3246
3284
  }
3247
- octave = clamp2(Number.parseInt(numStr, 10) || 4, 0, 8);
3285
+ octave = numStr ? clamp2(Number.parseInt(numStr, 10), 0, 8) : 4;
3248
3286
  } else {
3249
3287
  j++;
3250
3288
  }
@@ -3319,7 +3357,7 @@ var createSequencer = (options) => {
3319
3357
  for (const note of track.notes) {
3320
3358
  const relativeStart = note.startStep - fromStep;
3321
3359
  if (relativeStart < 0) continue;
3322
- const velocity = note.velocity ?? 127;
3360
+ const velocity = note.velocity ?? DEFAULT_PLAYBACK_VELOCITY;
3323
3361
  timeline.push({
3324
3362
  trackId: track.id,
3325
3363
  pitch: note.pitch,
@@ -3938,6 +3976,13 @@ var DAW_CSS = `
3938
3976
 
3939
3977
  .dtm-hidden { display: none !important; }
3940
3978
  .dtm-grow { flex: 1 1 auto; }
3979
+ .dtm-lyric-icon {
3980
+ flex: 0 0 auto;
3981
+ width: 20px;
3982
+ height: 20px;
3983
+ border-radius: 50%;
3984
+ object-fit: cover;
3985
+ }
3941
3986
 
3942
3987
  /* \u2500\u2500\u2500 \u5E83\u5E45\u62E1\u5F35 \u2500\u2500\u2500 */
3943
3988
  @media (min-width: 768px) {
@@ -3980,13 +4025,26 @@ var DAW_CSS = `
3980
4025
  .dtm-player-play:active { transform: translate(2px, 2px); box-shadow: none; }
3981
4026
  .dtm-player-play--stop { background: var(--dtm-danger); }
3982
4027
  .dtm-player-play:disabled { opacity: 0.4; cursor: default; }
3983
- .dtm-player-tempo,
3984
- .dtm-player-time {
4028
+ .dtm-player-beat-row {
4029
+ display: flex;
4030
+ align-items: center;
4031
+ gap: 4px;
4032
+ }
4033
+ .dtm-player-beat-dot {
4034
+ width: 8px;
4035
+ height: 8px;
4036
+ border-radius: 50%;
4037
+ background: var(--dtm-border2);
4038
+ transition: background 0.06s;
4039
+ }
4040
+ .dtm-player-beat-dot--on { background: var(--dtm-primary); }
4041
+ .dtm-player-bar {
3985
4042
  font-family: 'k8x12', monospace;
3986
- font-size: 12px;
3987
- color: var(--dtm-muted);
4043
+ font-size: 11px;
4044
+ color: var(--dtm-text);
4045
+ min-width: 2em;
4046
+ margin-left: 4px;
3988
4047
  }
3989
- .dtm-player-time { color: var(--dtm-text); min-width: 3em; }
3990
4048
  .dtm-player-dots {
3991
4049
  margin-left: auto;
3992
4050
  display: flex;
@@ -3994,6 +4052,40 @@ var DAW_CSS = `
3994
4052
  gap: 6px;
3995
4053
  }
3996
4054
  .dtm-player-dot { width: 8px; height: 8px; display: inline-block; }
4055
+ .dtm-player-mml-header {
4056
+ margin-left: auto;
4057
+ display: flex;
4058
+ align-items: center;
4059
+ gap: 4px;
4060
+ }
4061
+ .dtm-player-mml-link {
4062
+ font-family: 'k8x12', monospace;
4063
+ font-size: 10px;
4064
+ color: var(--dtm-muted);
4065
+ text-decoration: none;
4066
+ white-space: nowrap;
4067
+ }
4068
+ .dtm-player-mml-link:hover { color: var(--dtm-primary); }
4069
+ .dtm-player-emoji {
4070
+ display: inline-flex;
4071
+ align-items: center;
4072
+ justify-content: center;
4073
+ width: 20px;
4074
+ height: 20px;
4075
+ border-radius: 50%;
4076
+ font-size: 18px;
4077
+ line-height: 1;
4078
+ user-select: none;
4079
+ }
4080
+ @keyframes dtm-emoji-jump {
4081
+ 0% { transform: translateY(0); }
4082
+ 35% { transform: translateY(-5px); }
4083
+ 65% { transform: translateY(-5px); }
4084
+ 100% { transform: translateY(0); }
4085
+ }
4086
+ .dtm-player-emoji--jump {
4087
+ animation: dtm-emoji-jump 0.18s ease-out forwards;
4088
+ }
3997
4089
  .dtm-player-chip {
3998
4090
  font-family: 'k8x12', monospace;
3999
4091
  font-size: 9px;
@@ -4002,6 +4094,12 @@ var DAW_CSS = `
4002
4094
  padding: 2px 6px;
4003
4095
  white-space: nowrap;
4004
4096
  }
4097
+ .dtm-player-body {
4098
+ position: relative; /* \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0\u30AA\u30FC\u30D0\u30FC\u30EC\u30A4\u306E\u57FA\u6E96\u3002\u30EC\u30FC\u30F3\u7FA4\u3060\u3051\u3092\u8986\u3046 */
4099
+ display: flex;
4100
+ flex-direction: column;
4101
+ gap: var(--dtm-gap);
4102
+ }
4005
4103
  .dtm-player-lane-row {
4006
4104
  display: flex;
4007
4105
  align-items: stretch;
@@ -4016,6 +4114,12 @@ var DAW_CSS = `
4016
4114
  gap: 2px;
4017
4115
  padding-top: 4px;
4018
4116
  }
4117
+ .dtm-player-lane-label--btn {
4118
+ cursor: pointer;
4119
+ user-select: none;
4120
+ }
4121
+ .dtm-player-lane-label--btn:hover { opacity: 0.7; }
4122
+ .dtm-player-lane-label--muted { opacity: 0.3; }
4019
4123
  .dtm-player-lane-no {
4020
4124
  font-family: 'k8x12', monospace;
4021
4125
  font-size: 9px;
@@ -4098,6 +4202,38 @@ var showLoadingOverlay = (container) => {
4098
4202
  };
4099
4203
  };
4100
4204
 
4205
+ // assets/puyuyu.png
4206
+ var puyuyu_default = "data:image/png;base64,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";
4207
+
4208
+ // assets/rino.png
4209
+ var rino_default = "data:image/png;base64,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";
4210
+
4211
+ // assets/roze.png
4212
+ var roze_default = "data:image/png;base64,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";
4213
+
4214
+ // assets/ruko.png
4215
+ var ruko_default = "data:image/png;base64,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";
4216
+
4217
+ // assets/shiyo.png
4218
+ var shiyo_default = "data:image/png;base64,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";
4219
+
4220
+ // assets/teto.png
4221
+ var teto_default = "data:image/png;base64,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";
4222
+
4223
+ // assets/tsukuyomi.png
4224
+ var tsukuyomi_default = "data:image/png;base64,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";
4225
+
4226
+ // src/voice-images.ts
4227
+ var VOICE_IMAGES = {
4228
+ puyuyu: puyuyu_default,
4229
+ rino: rino_default,
4230
+ roze: roze_default,
4231
+ ruko: ruko_default,
4232
+ shiyo: shiyo_default,
4233
+ teto: teto_default,
4234
+ tsukuyomi: tsukuyomi_default
4235
+ };
4236
+
4101
4237
  // src/daw.ts
4102
4238
  var BASE_STEP_WIDTH = 0.5;
4103
4239
  var BASE_KEY_HEIGHT = 15;
@@ -4264,7 +4400,7 @@ var mountDAW = (target, options = {}) => {
4264
4400
  tracks: trackConfigs,
4265
4401
  drumPatternNames: Object.keys(drumPatterns),
4266
4402
  defaultDrumPattern: drumPatterns.dance ? "dance" : Object.keys(drumPatterns)[0] ?? "none",
4267
- defaultBpm: options.defaultBpm ?? 120,
4403
+ defaultBpm: options.defaultBpm ?? DEFAULT_BPM,
4268
4404
  showMidi,
4269
4405
  showChord
4270
4406
  });
@@ -4279,7 +4415,7 @@ var mountDAW = (target, options = {}) => {
4279
4415
  const leftPaddingSteps = renderConfig.stepsPerBar * 16;
4280
4416
  let zoomX = 100;
4281
4417
  let zoomY = 100;
4282
- let bpm = options.defaultBpm ?? 120;
4418
+ let bpm = options.defaultBpm ?? DEFAULT_BPM;
4283
4419
  let masterVolume = 50;
4284
4420
  let drumVolume = 80;
4285
4421
  let currentDrumPattern = refs.drumSelect.value;
@@ -4324,9 +4460,10 @@ var mountDAW = (target, options = {}) => {
4324
4460
  lyrics: "",
4325
4461
  lyricModel: "",
4326
4462
  // 既定は「なし」(歌わない)
4327
- vocalVolume: 300,
4463
+ vocalVolume: DEFAULT_VOCAL_VOLUME,
4328
4464
  vocalGate: 100,
4329
- vocalPan: 64
4465
+ vocalPan: 64,
4466
+ vocalOctave: 0
4330
4467
  }));
4331
4468
  };
4332
4469
  const buildLyricsMap = () => {
@@ -4343,6 +4480,7 @@ var mountDAW = (target, options = {}) => {
4343
4480
  volume: t.vocalVolume,
4344
4481
  gate: t.vocalGate,
4345
4482
  pan: t.vocalPan,
4483
+ octave: t.vocalOctave,
4346
4484
  syllables
4347
4485
  });
4348
4486
  });
@@ -4861,6 +4999,13 @@ var mountDAW = (target, options = {}) => {
4861
4999
  setDrawOffset(currentOffsetX, currentOffsetY);
4862
5000
  redrawAll();
4863
5001
  };
5002
+ const getViewState = () => ({
5003
+ zoomX,
5004
+ zoomY,
5005
+ decomposeChord: refs.decomposeChordToggle.checked,
5006
+ ignoreChordHeavy: refs.ignoreChordHeavyToggle.checked
5007
+ });
5008
+ const notifyViewState = () => options.onViewStateChange?.(getViewState());
4864
5009
  const dispatchNote = (trackId, pitch, trackVol, velocity, when, duration) => {
4865
5010
  const volume = trackVol / 100 * (velocity / 127) * (masterVolume / 100);
4866
5011
  options.onPlayNote?.({ trackId, pitch, velocity, volume, when, duration });
@@ -4920,7 +5065,8 @@ var mountDAW = (target, options = {}) => {
4920
5065
  const sorted = [...trackState?.core.getNotes() ?? []].sort(
4921
5066
  (a, b) => a.startStep - b.startStep
4922
5067
  );
4923
- const gate = (lt.gate ?? 100) / 100;
5068
+ const gate = (lt.gate ?? DEFAULT_GATE) / 100;
5069
+ const semis = (lt.octave ?? 0) * 12;
4924
5070
  const count = Math.min(sorted.length, lt.syllables.length);
4925
5071
  const notes = [];
4926
5072
  for (let i = 0; i < count; i++) {
@@ -4928,22 +5074,22 @@ var mountDAW = (target, options = {}) => {
4928
5074
  if (n.startStep < fromStep) continue;
4929
5075
  notes.push({
4930
5076
  syllable: lt.syllables[i],
4931
- pitch: n.pitch,
5077
+ pitch: n.pitch + semis,
4932
5078
  startSec: (n.startStep - fromStep) * secondsPerStep,
4933
5079
  durationSec: n.durationSteps * secondsPerStep * gate
4934
5080
  });
4935
5081
  }
4936
5082
  return {
4937
5083
  model: lt.model,
4938
- volume: vocalVolumeToGain(lt.volume ?? 300) * (masterVolume / 100),
4939
- pan: panToStereo(lt.pan ?? 64),
5084
+ volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (masterVolume / 100),
5085
+ pan: panToStereo(lt.pan ?? DEFAULT_PAN),
4940
5086
  notes
4941
5087
  };
4942
5088
  }) : [];
4943
5089
  const voices = options.singingVoices;
4944
5090
  const streaming = !!voices && streamTracks.some((t) => t.notes.length > 0);
4945
5091
  if (streaming && voices) {
4946
- const overlay = showLoadingOverlay(target);
5092
+ const overlay = showLoadingOverlay(refs.rollContainer);
4947
5093
  try {
4948
5094
  await voices.loadModels(streamTracks.map((t) => t.model));
4949
5095
  await voices.warm(streamTracks);
@@ -5036,12 +5182,25 @@ var mountDAW = (target, options = {}) => {
5036
5182
  <div class="dtm-row">
5037
5183
  <span class="dtm-label">\u266A \u6B4C\u8A5E</span>
5038
5184
  <select class="dtm-select" data-dtm="lyric-model" aria-label="\u6B4C\u5531\u30E2\u30C7\u30EB"></select>
5185
+ <img class="dtm-lyric-icon dtm-hidden" data-dtm="lyric-icon" width="20" height="20" alt="" draggable="false">
5186
+ <select class="dtm-select" data-dtm="lyric-octave" aria-label="\u30AA\u30AF\u30BF\u30FC\u30D6\uFF08\u97F3\u6E90\u306E\u5F97\u610F\u97F3\u57DF\u306B\u5408\u308F\u305B\u308B\uFF09" title="\u30AA\u30AF\u30BF\u30FC\u30D6">
5187
+ <option value="2">+2 oct</option>
5188
+ <option value="1">+1 oct</option>
5189
+ <option value="0">\xB10 oct</option>
5190
+ <option value="-1">-1 oct</option>
5191
+ <option value="-2">-2 oct</option>
5192
+ </select>
5039
5193
  <span class="dtm-label dtm-grow" data-dtm="lyric-count" style="text-align:right"></span>
5040
5194
  </div>
5195
+ <div class="dtm-row dtm-hidden" data-dtm="lyric-terms" style="font-size:10px;gap:4px;color:var(--dtm-warn)">
5196
+ <span>\u4F7F\u7528\u6642\u306B\u306F</span>
5197
+ <a data-dtm="lyric-terms-link" target="_blank" rel="noopener" style="color:var(--dtm-primary);text-decoration:underline"></a>
5198
+ <span>\u306E\u5229\u7528\u898F\u7D04\u306B\u5F93\u3063\u3066\u304F\u3060\u3055\u3044</span>
5199
+ </div>
5041
5200
  <div class="dtm-row" data-dtm="lyric-body" style="flex-direction:column;align-items:stretch">
5042
5201
  <div class="dtm-row">
5043
5202
  <span class="dtm-label">\u58F0\u91CF</span>
5044
- <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-vol" min="0" max="${MAX_VOCAL_VOLUME}" aria-label="\u6B4C\u5531\u306E\u58F0\u91CF\uFF08100=\u7B49\u500D\u3001100\u8D85\u3067\u30D6\u30FC\u30B9\u30C8\u3001\u65E2\u5B9A300\uFF09">
5203
+ <input type="range" class="dtm-range dtm-grow" data-dtm="lyric-vol" min="0" max="${MAX_VOCAL_VOLUME}" aria-label="\u6B4C\u5531\u306E\u58F0\u91CF\uFF08100=\u7B49\u500D\u3001100\u8D85\u3067\u30D6\u30FC\u30B9\u30C8\u3001\u65E2\u5B9A200\uFF09">
5045
5204
  <span class="dtm-label" data-dtm="lyric-vol-label"></span>
5046
5205
  </div>
5047
5206
  <div class="dtm-row">
@@ -5055,6 +5214,12 @@ var mountDAW = (target, options = {}) => {
5055
5214
  const lyricModelSel = lyricDiv.querySelector(
5056
5215
  '[data-dtm="lyric-model"]'
5057
5216
  );
5217
+ const lyricOctaveSel = lyricDiv.querySelector(
5218
+ '[data-dtm="lyric-octave"]'
5219
+ );
5220
+ const lyricIcon = lyricDiv.querySelector(
5221
+ '[data-dtm="lyric-icon"]'
5222
+ );
5058
5223
  const lyricBody = lyricDiv.querySelector(
5059
5224
  '[data-dtm="lyric-body"]'
5060
5225
  );
@@ -5076,6 +5241,12 @@ var mountDAW = (target, options = {}) => {
5076
5241
  const lyricPanLabel = lyricDiv.querySelector(
5077
5242
  '[data-dtm="lyric-pan-label"]'
5078
5243
  );
5244
+ const lyricTerms = lyricDiv.querySelector(
5245
+ '[data-dtm="lyric-terms"]'
5246
+ );
5247
+ const lyricTermsLink = lyricDiv.querySelector(
5248
+ '[data-dtm="lyric-terms-link"]'
5249
+ );
5079
5250
  const fmtPan = (pan) => pan === 64 ? "C" : pan < 64 ? `L${64 - pan}` : `R${pan - 64}`;
5080
5251
  const addOpt = (value, label) => {
5081
5252
  const o = document.createElement("option");
@@ -5089,6 +5260,7 @@ var mountDAW = (target, options = {}) => {
5089
5260
  addOpt(active.lyricModel, lyricModelLabel(active.lyricModel));
5090
5261
  }
5091
5262
  lyricModelSel.value = active.lyricModel;
5263
+ lyricOctaveSel.value = String(active.vocalOctave);
5092
5264
  lyricInput.value = active.lyrics;
5093
5265
  lyricVol.value = String(active.vocalVolume);
5094
5266
  lyricVolLabel.textContent = String(active.vocalVolume);
@@ -5098,15 +5270,43 @@ var mountDAW = (target, options = {}) => {
5098
5270
  const n = normalizeLyrics(lyricInput.value).length;
5099
5271
  lyricCount.textContent = active.lyricModel && n > 0 ? `${n}\u97F3\u7BC0` : "";
5100
5272
  };
5273
+ const syncLyricTerms = () => {
5274
+ const url = active.lyricModel ? KOE_VOICEBANK_TERMS[active.lyricModel] : void 0;
5275
+ if (url) {
5276
+ const label = lyricModelLabel(active.lyricModel);
5277
+ lyricTermsLink.textContent = `${label}UTAU\u97F3\u6E90`;
5278
+ lyricTermsLink.href = url;
5279
+ lyricTerms.classList.remove("dtm-hidden");
5280
+ } else {
5281
+ lyricTerms.classList.add("dtm-hidden");
5282
+ }
5283
+ };
5284
+ const syncLyricIcon = () => {
5285
+ const imgKey = active.lyricModel ? VOICE_IMAGE_KEY[active.lyricModel.toLowerCase()] : void 0;
5286
+ const src = imgKey ? VOICE_IMAGES[imgKey] : void 0;
5287
+ if (src) {
5288
+ lyricIcon.src = src;
5289
+ lyricIcon.classList.remove("dtm-hidden");
5290
+ } else {
5291
+ lyricIcon.removeAttribute("src");
5292
+ lyricIcon.classList.add("dtm-hidden");
5293
+ }
5294
+ };
5101
5295
  const syncLyricVisibility = () => {
5102
5296
  lyricBody.style.display = active.lyricModel ? "" : "none";
5297
+ lyricOctaveSel.style.display = active.lyricModel ? "" : "none";
5103
5298
  updateLyricCount();
5299
+ syncLyricTerms();
5300
+ syncLyricIcon();
5104
5301
  };
5105
5302
  syncLyricVisibility();
5106
5303
  lyricModelSel.addEventListener("change", () => {
5107
5304
  active.lyricModel = lyricModelSel.value;
5108
5305
  syncLyricVisibility();
5109
5306
  });
5307
+ lyricOctaveSel.addEventListener("change", () => {
5308
+ active.vocalOctave = Number.parseInt(lyricOctaveSel.value, 10);
5309
+ });
5110
5310
  lyricInput.addEventListener("input", () => {
5111
5311
  active.lyrics = lyricInput.value;
5112
5312
  updateLyricCount();
@@ -5255,12 +5455,14 @@ var mountDAW = (target, options = {}) => {
5255
5455
  model: t.lyricModel.trim(),
5256
5456
  vol: t.vocalVolume,
5257
5457
  gate: t.vocalGate,
5258
- pan: t.vocalPan
5458
+ pan: t.vocalPan,
5459
+ oct: t.vocalOctave
5259
5460
  })).filter((x) => x.model.length > 0 && x.text.length > 0).map((x) => {
5260
5461
  const params = [
5261
- x.vol === 300 ? "" : `v${x.vol}`,
5462
+ x.vol === DEFAULT_VOCAL_VOLUME ? "" : `v${x.vol}`,
5262
5463
  x.gate === 100 ? "" : `q${x.gate}`,
5263
- x.pan === 64 ? "" : `p${x.pan}`
5464
+ x.pan === 64 ? "" : `p${x.pan}`,
5465
+ x.oct === 0 ? "" : `o${x.oct}`
5264
5466
  ].filter((s) => s.length > 0).join(" ");
5265
5467
  const head = params ? `${x.model} ${params}` : x.model;
5266
5468
  return `@@${x.i} ${head} ${x.text}`;
@@ -5341,6 +5543,7 @@ var mountDAW = (target, options = {}) => {
5341
5543
  if (meta.drum && drumPatterns[meta.drum]) {
5342
5544
  currentDrumPattern = meta.drum;
5343
5545
  refs.drumSelect.value = meta.drum;
5546
+ options.onDrumChange?.(meta.drum);
5344
5547
  }
5345
5548
  if (meta.volume !== void 0) {
5346
5549
  masterVolume = meta.volume;
@@ -5350,9 +5553,10 @@ var mountDAW = (target, options = {}) => {
5350
5553
  for (const t of trackStates) {
5351
5554
  t.lyrics = "";
5352
5555
  t.lyricModel = "";
5353
- t.vocalVolume = 300;
5556
+ t.vocalVolume = DEFAULT_VOCAL_VOLUME;
5354
5557
  t.vocalGate = 100;
5355
5558
  t.vocalPan = 64;
5559
+ t.vocalOctave = 0;
5356
5560
  }
5357
5561
  lyrics?.forEach((lt, idx) => {
5358
5562
  const t = trackStates[idx];
@@ -5362,6 +5566,7 @@ var mountDAW = (target, options = {}) => {
5362
5566
  t.vocalVolume = lt.volume;
5363
5567
  t.vocalGate = lt.gate;
5364
5568
  t.vocalPan = lt.pan;
5569
+ t.vocalOctave = lt.octave ?? 0;
5365
5570
  });
5366
5571
  for (const p of placements) {
5367
5572
  const t = trackStates[p.trackIndex];
@@ -5513,25 +5718,32 @@ var mountDAW = (target, options = {}) => {
5513
5718
  refs.zoomXIn.addEventListener("click", () => {
5514
5719
  zoomX = Math.min(200, zoomX + 25);
5515
5720
  applyZoomX();
5721
+ notifyViewState();
5516
5722
  });
5517
5723
  refs.zoomXOut.addEventListener("click", () => {
5518
5724
  zoomX = Math.max(25, zoomX - 25);
5519
5725
  applyZoomX();
5726
+ notifyViewState();
5520
5727
  });
5521
5728
  refs.zoomYIn.addEventListener("click", () => {
5522
5729
  zoomY = Math.min(200, zoomY + 25);
5523
5730
  applyZoomY();
5731
+ notifyViewState();
5524
5732
  });
5525
5733
  refs.zoomYOut.addEventListener("click", () => {
5526
5734
  zoomY = Math.max(50, zoomY - 25);
5527
5735
  applyZoomY();
5736
+ notifyViewState();
5528
5737
  });
5738
+ refs.decomposeChordToggle.addEventListener("change", notifyViewState);
5739
+ refs.ignoreChordHeavyToggle.addEventListener("change", notifyViewState);
5529
5740
  refs.masterVolume.addEventListener("input", () => {
5530
5741
  masterVolume = Number.parseInt(refs.masterVolume.value, 10) || 0;
5531
5742
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
5532
5743
  });
5533
5744
  refs.drumSelect.addEventListener("change", () => {
5534
5745
  currentDrumPattern = refs.drumSelect.value;
5746
+ options.onDrumChange?.(currentDrumPattern);
5535
5747
  });
5536
5748
  refs.drumVolume.addEventListener("input", () => {
5537
5749
  drumVolume = Number.parseInt(refs.drumVolume.value, 10) || 0;
@@ -5724,6 +5936,30 @@ var mountDAW = (target, options = {}) => {
5724
5936
  setInstrument: (name) => {
5725
5937
  currentInstrument = name;
5726
5938
  },
5939
+ getDrum: () => currentDrumPattern,
5940
+ setDrum: (name) => {
5941
+ if (name !== "none" && !drumPatterns[name]) return;
5942
+ currentDrumPattern = name;
5943
+ refs.drumSelect.value = name;
5944
+ options.onDrumChange?.(name);
5945
+ },
5946
+ getViewState,
5947
+ setViewState: (state) => {
5948
+ if (typeof state.zoomX === "number") {
5949
+ zoomX = clamp3(state.zoomX, 25, 200);
5950
+ applyZoomX();
5951
+ }
5952
+ if (typeof state.zoomY === "number") {
5953
+ zoomY = clamp3(state.zoomY, 50, 200);
5954
+ applyZoomY();
5955
+ }
5956
+ if (typeof state.decomposeChord === "boolean") {
5957
+ refs.decomposeChordToggle.checked = state.decomposeChord;
5958
+ }
5959
+ if (typeof state.ignoreChordHeavy === "boolean") {
5960
+ refs.ignoreChordHeavyToggle.checked = state.ignoreChordHeavy;
5961
+ }
5962
+ },
5727
5963
  loadMML,
5728
5964
  loadMIDI,
5729
5965
  exportMIDI: exportMIDI2,
@@ -5849,11 +6085,6 @@ var STEPS_PER_BEAT3 = 48;
5849
6085
  var STEPS_PER_BAR = 192;
5850
6086
  var DEFAULT_TRACK_COLORS = ["#00e436", "#29adff", "#ff77a8", "#ffec27"];
5851
6087
  var activePlayer = null;
5852
- var formatTime = (seconds) => {
5853
- const m = Math.floor(seconds / 60);
5854
- const s = Math.floor(seconds % 60);
5855
- return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
5856
- };
5857
6088
  var freqFromPitch = (pitch) => 440 * 2 ** ((pitch - 69) / 12);
5858
6089
  var mountMmlPlayer = (target, mml, options = {}) => {
5859
6090
  injectStyles(target.ownerDocument ?? document);
@@ -5868,7 +6099,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5868
6099
  collectLyrics: true
5869
6100
  });
5870
6101
  const lyricTracks = lyrics ?? /* @__PURE__ */ new Map();
5871
- const bpm = parsedBpm ?? options.defaultBpm ?? 120;
6102
+ const bpm = parsedBpm ?? options.defaultBpm ?? DEFAULT_BPM;
5872
6103
  const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
5873
6104
  const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
5874
6105
  const trackVolume = meta.volume ?? options.volume ?? 100;
@@ -5983,32 +6214,118 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5983
6214
  playBtn.className = "dtm-player-play";
5984
6215
  playBtn.innerHTML = icon("play", 12);
5985
6216
  playBtn.disabled = trackIndices.length === 0;
5986
- const tempoEl = doc.createElement("span");
5987
- tempoEl.className = "dtm-player-tempo";
5988
- tempoEl.textContent = `\u2669=${bpm}`;
5989
- const timeEl = doc.createElement("span");
5990
- timeEl.className = "dtm-player-time";
5991
- timeEl.textContent = "00:00";
6217
+ const mmlHeader = doc.createElement("div");
6218
+ mmlHeader.className = "dtm-player-mml-header";
6219
+ const emojiEls = [];
6220
+ const emojiByTrack = /* @__PURE__ */ new Map();
6221
+ for (const index of trackIndices) {
6222
+ const em = doc.createElement("span");
6223
+ em.className = "dtm-player-emoji";
6224
+ em.style.backgroundColor = colorOf(index);
6225
+ em.textContent = "\u{1F97A}";
6226
+ mmlHeader.appendChild(em);
6227
+ emojiEls.push(em);
6228
+ emojiByTrack.set(index, em);
6229
+ }
6230
+ const promotedToImage = /* @__PURE__ */ new Set();
6231
+ for (const [index, lt] of lyricTracks) {
6232
+ const em = emojiByTrack.get(index);
6233
+ if (!em) continue;
6234
+ const imgKey = VOICE_IMAGE_KEY[lt.model.toLowerCase()];
6235
+ const src = imgKey ? VOICE_IMAGES[imgKey] : void 0;
6236
+ if (!src) continue;
6237
+ const img = doc.createElement("img");
6238
+ img.src = src;
6239
+ img.width = 20;
6240
+ img.height = 20;
6241
+ img.style.borderRadius = "50%";
6242
+ img.style.objectFit = "cover";
6243
+ img.draggable = false;
6244
+ promotedToImage.add(em);
6245
+ em.textContent = "";
6246
+ em.appendChild(img);
6247
+ }
6248
+ const JUMP_DEDUPE_MS = 50;
6249
+ const lastJumpAt = /* @__PURE__ */ new WeakMap();
6250
+ const jumpEmoji = (em) => {
6251
+ const now = performance.now();
6252
+ const prev = lastJumpAt.get(em);
6253
+ if (prev !== void 0 && now - prev < JUMP_DEDUPE_MS) return;
6254
+ lastJumpAt.set(em, now);
6255
+ em.classList.remove("dtm-player-emoji--jump");
6256
+ void em.offsetWidth;
6257
+ em.classList.add("dtm-player-emoji--jump");
6258
+ };
6259
+ const jumpTimers = [];
6260
+ const jumpEmojiAt = (em, when) => {
6261
+ if (when <= 0) {
6262
+ jumpEmoji(em);
6263
+ return;
6264
+ }
6265
+ jumpTimers.push(setTimeout(() => jumpEmoji(em), when * 1e3));
6266
+ };
6267
+ const clearJumpTimers = () => {
6268
+ for (const t of jumpTimers) clearTimeout(t);
6269
+ jumpTimers.length = 0;
6270
+ };
6271
+ const blinkTimers = [];
6272
+ const scheduleBlink = (em) => {
6273
+ const delay = 2e3 + Math.random() * 5e3;
6274
+ const t = setTimeout(() => {
6275
+ if (promotedToImage.has(em)) return;
6276
+ em.textContent = "\u{1F60C}";
6277
+ const t2 = setTimeout(
6278
+ () => {
6279
+ if (promotedToImage.has(em)) return;
6280
+ em.textContent = "\u{1F97A}";
6281
+ scheduleBlink(em);
6282
+ },
6283
+ 200 + Math.random() * 150
6284
+ );
6285
+ blinkTimers.push(t2);
6286
+ }, delay);
6287
+ blinkTimers.push(t);
6288
+ };
6289
+ for (const em of emojiEls) scheduleBlink(em);
5992
6290
  const dots = doc.createElement("div");
5993
6291
  dots.className = "dtm-player-dots";
6292
+ dots.style.display = "none";
5994
6293
  for (const index of trackIndices) {
5995
6294
  const dot = doc.createElement("span");
5996
6295
  dot.className = "dtm-player-dot";
5997
6296
  dot.style.backgroundColor = colorOf(index);
5998
6297
  dots.appendChild(dot);
5999
6298
  }
6000
- head.append(playBtn, tempoEl, timeEl);
6001
- const addChip = (label) => {
6299
+ const beatRow = doc.createElement("div");
6300
+ beatRow.className = "dtm-player-beat-row";
6301
+ const beatDots = [];
6302
+ for (let i = 0; i < 4; i++) {
6303
+ const d = doc.createElement("span");
6304
+ d.className = "dtm-player-beat-dot";
6305
+ beatRow.appendChild(d);
6306
+ beatDots.push(d);
6307
+ }
6308
+ const barEl = doc.createElement("span");
6309
+ barEl.className = "dtm-player-bar";
6310
+ barEl.textContent = "-";
6311
+ beatRow.appendChild(barEl);
6312
+ const chips = [];
6313
+ const makeChip = (label) => {
6002
6314
  const chip = doc.createElement("span");
6003
6315
  chip.className = "dtm-player-chip";
6004
6316
  chip.textContent = label;
6005
- head.appendChild(chip);
6317
+ chips.push(chip);
6318
+ return chip;
6006
6319
  };
6007
- if (meta.instrument) addChip(`\u266A ${meta.instrument}`);
6008
- if (meta.drum) addChip(`\u{1F941} ${meta.drum}${drumPattern ? "" : " (?)"}`);
6009
- if (meta.volume !== void 0) addChip(`\u{1F50A} ${meta.volume}%`);
6010
- head.appendChild(dots);
6320
+ if (meta.instrument) makeChip(`\u266A ${meta.instrument}`);
6321
+ if (meta.drum) makeChip(`\u{1F941} ${meta.drum}${drumPattern ? "" : " (?)"}`);
6322
+ if (meta.volume !== void 0) makeChip(`\u{1F50A} ${meta.volume}%`);
6323
+ head.append(playBtn, beatRow, ...chips, dots, mmlHeader);
6011
6324
  root.appendChild(head);
6325
+ const body = doc.createElement("div");
6326
+ body.className = "dtm-player-body";
6327
+ root.appendChild(body);
6328
+ const mutedTracks = /* @__PURE__ */ new Set();
6012
6329
  const laneViews = [];
6013
6330
  for (const index of trackIndices) {
6014
6331
  const lyricTrack = lyricTracks.get(index);
@@ -6016,7 +6333,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6016
6333
  const row = doc.createElement("div");
6017
6334
  row.className = "dtm-player-lane-row";
6018
6335
  const label = doc.createElement("div");
6019
- label.className = "dtm-player-lane-label";
6336
+ label.className = "dtm-player-lane-label dtm-player-lane-label--btn";
6020
6337
  const swatch = doc.createElement("span");
6021
6338
  swatch.className = "dtm-player-dot";
6022
6339
  swatch.style.backgroundColor = colorOf(index);
@@ -6024,13 +6341,22 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6024
6341
  no.className = "dtm-player-lane-no";
6025
6342
  no.textContent = `@${index}`;
6026
6343
  label.append(swatch, no);
6344
+ label.addEventListener("click", () => {
6345
+ if (mutedTracks.has(index)) {
6346
+ mutedTracks.delete(index);
6347
+ label.classList.remove("dtm-player-lane-label--muted");
6348
+ } else {
6349
+ mutedTracks.add(index);
6350
+ label.classList.add("dtm-player-lane-label--muted");
6351
+ }
6352
+ });
6027
6353
  const lane = doc.createElement("div");
6028
6354
  lane.className = "dtm-player-lane";
6029
6355
  lane.style.setProperty("--tk", colorOf(index));
6030
6356
  const laneTokens = [];
6031
6357
  if (isLyricLane) {
6032
6358
  const notes = placements.filter((p) => p.trackIndex === index).sort((a, b) => a.startStep - b.startStep);
6033
- const gateScale = (lyricTrack.gate ?? 100) / 100;
6359
+ const gateScale = (lyricTrack.gate ?? DEFAULT_GATE) / 100;
6034
6360
  const breaks = new Set(lyricTrack.lineBreaks ?? []);
6035
6361
  if (lyricTrack.metaText) {
6036
6362
  const metaEl = doc.createElement("span");
@@ -6077,9 +6403,46 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6077
6403
  }
6078
6404
  }
6079
6405
  row.append(label, lane);
6080
- root.appendChild(row);
6406
+ body.appendChild(row);
6081
6407
  laneViews.push({ lane, tokens: laneTokens });
6082
6408
  }
6409
+ const termsModels = [
6410
+ ...new Set([...lyricTracks.values()].map((lt) => lt.model))
6411
+ ].filter((model) => KOE_VOICEBANK_TERMS[model]);
6412
+ if (termsModels.length > 0) {
6413
+ const termsDiv = doc.createElement("div");
6414
+ termsDiv.className = "dtm-player-terms";
6415
+ termsDiv.style.fontSize = "10px";
6416
+ termsDiv.style.color = "var(--dtm-warn)";
6417
+ termsDiv.style.display = "flex";
6418
+ termsDiv.style.flexDirection = "column";
6419
+ termsDiv.style.gap = "4px";
6420
+ termsDiv.style.marginTop = "4px";
6421
+ termsDiv.style.padding = "0 4px";
6422
+ for (const model of termsModels) {
6423
+ const termsRow = doc.createElement("div");
6424
+ termsRow.style.display = "flex";
6425
+ termsRow.style.alignItems = "center";
6426
+ termsRow.style.gap = "4px";
6427
+ termsRow.style.flexWrap = "wrap";
6428
+ const label = KOE_VOICEBANK_LABELS[model] ?? model;
6429
+ const url = KOE_VOICEBANK_TERMS[model];
6430
+ const span1 = doc.createElement("span");
6431
+ span1.textContent = "\u4F7F\u7528\u6642\u306B\u306F";
6432
+ const a = doc.createElement("a");
6433
+ a.textContent = `${label}UTAU\u97F3\u6E90`;
6434
+ a.href = url;
6435
+ a.target = "_blank";
6436
+ a.rel = "noopener";
6437
+ a.style.color = "var(--dtm-primary)";
6438
+ a.style.textDecoration = "underline";
6439
+ const span2 = doc.createElement("span");
6440
+ span2.textContent = "\u306E\u5229\u7528\u898F\u7D04\u306B\u5F93\u3063\u3066\u304F\u3060\u3055\u3044";
6441
+ termsRow.append(span1, a, span2);
6442
+ termsDiv.appendChild(termsRow);
6443
+ }
6444
+ root.appendChild(termsDiv);
6445
+ }
6083
6446
  target.appendChild(root);
6084
6447
  const autoScroll = (lane, el) => {
6085
6448
  if (el.offsetWidth === 0 || lane.clientWidth === 0) return;
@@ -6089,7 +6452,10 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6089
6452
  lane.scrollLeft = Math.max(0, Math.min(next, maxScroll));
6090
6453
  };
6091
6454
  const renderPlayhead = (step) => {
6092
- timeEl.textContent = formatTime(Math.max(0, step) * secondsPerStep);
6455
+ const beatIndex = Math.floor(step / STEPS_PER_BEAT3) % 4;
6456
+ for (let i = 0; i < 4; i++)
6457
+ beatDots[i].classList.toggle("dtm-player-beat-dot--on", i === beatIndex);
6458
+ barEl.textContent = String(Math.floor(step / STEPS_PER_BAR) + 1);
6093
6459
  for (const view of laneViews) {
6094
6460
  let active = null;
6095
6461
  for (const t of view.tokens) {
@@ -6101,7 +6467,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6101
6467
  }
6102
6468
  };
6103
6469
  const resetPlayhead = () => {
6104
- timeEl.textContent = "00:00";
6470
+ for (const d of beatDots) d.classList.remove("dtm-player-beat-dot--on");
6471
+ barEl.textContent = "-";
6105
6472
  for (const view of laneViews) {
6106
6473
  for (const t of view.tokens) t.el.classList.remove("is-active");
6107
6474
  view.lane.scrollLeft = 0;
@@ -6115,7 +6482,11 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6115
6482
  getSoloTrackId: () => null,
6116
6483
  getAudioTime,
6117
6484
  onPlayNote: (e) => {
6118
- if (lyricTracks.has(Number(e.trackId))) return;
6485
+ const trackIdx = Number(e.trackId);
6486
+ if (mutedTracks.has(trackIdx)) return;
6487
+ const em = emojiByTrack.get(trackIdx);
6488
+ if (em) jumpEmojiAt(em, e.when);
6489
+ if (lyricTracks.has(trackIdx)) return;
6119
6490
  options.onPlayNote?.(e);
6120
6491
  if (useSynth) synthPlay(e);
6121
6492
  },
@@ -6138,6 +6509,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6138
6509
  };
6139
6510
  const finish = () => {
6140
6511
  setPlayingUI(false);
6512
+ clearJumpTimers();
6141
6513
  resetPlayhead();
6142
6514
  if (activePlayer === instance) activePlayer = null;
6143
6515
  };
@@ -6146,22 +6518,23 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6146
6518
  const sorted = [...seqTrack?.notes ?? []].sort(
6147
6519
  (a, b) => a.startStep - b.startStep
6148
6520
  );
6149
- const gate = (lt.gate ?? 100) / 100;
6521
+ const gate = (lt.gate ?? DEFAULT_GATE) / 100;
6522
+ const semis = (lt.octave ?? 0) * 12;
6150
6523
  const count = Math.min(sorted.length, lt.syllables.length);
6151
6524
  const notes = [];
6152
6525
  for (let i = 0; i < count; i++) {
6153
6526
  const n = sorted[i];
6154
6527
  notes.push({
6155
6528
  syllable: lt.syllables[i],
6156
- pitch: n.pitch,
6529
+ pitch: n.pitch + semis,
6157
6530
  startSec: n.startStep * secondsPerStep,
6158
6531
  durationSec: n.durationSteps * secondsPerStep * gate
6159
6532
  });
6160
6533
  }
6161
6534
  return {
6162
6535
  model: lt.model,
6163
- volume: vocalVolumeToGain(lt.volume ?? 300) * (trackVolume / 100),
6164
- pan: panToStereo(lt.pan ?? 64),
6536
+ volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
6537
+ pan: panToStereo(lt.pan ?? DEFAULT_PAN),
6165
6538
  notes
6166
6539
  };
6167
6540
  });
@@ -6170,7 +6543,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6170
6543
  const tracks = streaming ? buildStreamTracks() : [];
6171
6544
  if (streaming) {
6172
6545
  const v = ensureVoices();
6173
- const overlay = showLoadingOverlay(root);
6546
+ const overlay = showLoadingOverlay(body);
6174
6547
  try {
6175
6548
  await v.loadModels(tracks.map((t) => t.model));
6176
6549
  await v.warm(tracks);
@@ -6215,6 +6588,8 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6215
6588
  void audioCtx.close();
6216
6589
  audioCtx = null;
6217
6590
  }
6591
+ for (const t of blinkTimers) clearTimeout(t);
6592
+ clearJumpTimers();
6218
6593
  root.remove();
6219
6594
  };
6220
6595
  const instance = {
@@ -6508,9 +6883,334 @@ var createPianoRoll = (options, handlers) => {
6508
6883
  }
6509
6884
  };
6510
6885
  };
6886
+
6887
+ // src/studio.ts
6888
+ var import_meta = {};
6889
+ var DEFAULT_CDN = {
6890
+ soundFont: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont.mjs",
6891
+ soundFontDrum: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_drum.mjs",
6892
+ soundFontList: "https://rpgen3.github.io/soundfont/mjs/surikov/SoundFont_list.mjs",
6893
+ parseChord: "https://rpgen3.github.io/piano/mjs/parseChord.mjs",
6894
+ parseChords: "https://rpgen3.github.io/piano/mjs/parseChords.mjs",
6895
+ midiParser: "https://cdn.jsdelivr.net/npm/midi-parser-js@4.0.4/+esm"
6896
+ };
6897
+ var SOUNDFONT_NAME = "FluidR3_GM_sf2_file";
6898
+ var TRACK_ROLES = ["melody", "submelody", "bass", "chord"];
6899
+ var resolveDefaultVoiceWorkerUrl = () => {
6900
+ try {
6901
+ return new URL("./voice-worker.js", import_meta.url).href;
6902
+ } catch {
6903
+ return void 0;
6904
+ }
6905
+ };
6906
+ var importFrom = async (url, name) => {
6907
+ const mod = await import(
6908
+ /* @vite-ignore */
6909
+ url
6910
+ );
6911
+ return mod[name] ?? mod.default;
6912
+ };
6913
+ var createDtmStudio = async (options = {}) => {
6914
+ const cdn = { ...DEFAULT_CDN, ...options.cdn };
6915
+ const features = {
6916
+ recorder: true,
6917
+ midi: true,
6918
+ chord: true,
6919
+ presetUI: true,
6920
+ ...options.features
6921
+ };
6922
+ const audioCtx = options.audioContext ?? new AudioContext();
6923
+ const masterGain = audioCtx.createGain();
6924
+ masterGain.gain.value = options.masterVolume ?? 1;
6925
+ masterGain.connect(audioCtx.destination);
6926
+ const drumGain = audioCtx.createGain();
6927
+ drumGain.gain.value = options.drumVolume ?? 1;
6928
+ drumGain.connect(audioCtx.destination);
6929
+ const resumeAudio = () => {
6930
+ if (audioCtx.state === "suspended") void audioCtx.resume();
6931
+ };
6932
+ const eng = options.engines ?? {};
6933
+ const [SoundFont, SoundFont_drum, SoundFont_list] = await Promise.all([
6934
+ eng.SoundFont ?? importFrom(cdn.soundFont, "SoundFont"),
6935
+ eng.SoundFont_drum ?? importFrom(cdn.soundFontDrum, "SoundFont_drum"),
6936
+ eng.SoundFont_list ?? importFrom(cdn.soundFontList, "SoundFont_list")
6937
+ ]);
6938
+ let parseChord = eng.parseChord;
6939
+ let parseChords = eng.parseChords;
6940
+ if (features.chord && (!parseChord || !parseChords)) {
6941
+ try {
6942
+ [parseChord, parseChords] = await Promise.all([
6943
+ parseChord ?? importFrom(
6944
+ cdn.parseChord,
6945
+ "parseChord"
6946
+ ),
6947
+ parseChords ?? importFrom(
6948
+ cdn.parseChords,
6949
+ "parseChords"
6950
+ )
6951
+ ]);
6952
+ } catch (e) {
6953
+ console.warn("[dtm] \u30B3\u30FC\u30C9\u89E3\u6790\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F", e);
6954
+ }
6955
+ }
6956
+ let midiParser = null;
6957
+ let parseMidi;
6958
+ if (features.midi) {
6959
+ parseMidi = eng.parseMidi;
6960
+ if (!parseMidi) {
6961
+ const midiPromise = importFrom(
6962
+ cdn.midiParser,
6963
+ "default"
6964
+ ).then((m) => {
6965
+ midiParser = m;
6966
+ }).catch((e) => console.warn("[dtm] midi-parser \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557", e));
6967
+ void midiPromise;
6968
+ parseMidi = (bytes) => {
6969
+ if (!midiParser) throw new Error("midi-parser not ready");
6970
+ return midiParser.parse(bytes);
6971
+ };
6972
+ }
6973
+ }
6974
+ const voiceWorkerUrl = options.voiceWorkerUrl === null ? void 0 : options.voiceWorkerUrl ?? resolveDefaultVoiceWorkerUrl();
6975
+ const singingVoices = createSingingVoices(audioCtx, masterGain, {
6976
+ voiceWorkerUrl
6977
+ });
6978
+ const recorder = features.recorder ? setupRecorder(audioCtx, masterGain, drumGain) : null;
6979
+ const downloadWav = () => {
6980
+ if (!recorder) return;
6981
+ const recordedData = recorder.getRecordedData();
6982
+ const ch = recordedData.length;
6983
+ const len = recordedData[0].length;
6984
+ if (len === 0) return;
6985
+ const bufSize = recordedData[0][0].length;
6986
+ const wave = new Float32Array(ch * len * bufSize);
6987
+ let idx = 0;
6988
+ for (let i = 0; i < len; i++)
6989
+ for (let j = 0; j < bufSize; j++)
6990
+ for (let k = 0; k < ch; k++) wave[idx++] = recordedData[k][i][j];
6991
+ const sampleRate = audioCtx.sampleRate;
6992
+ const channels = 2;
6993
+ const bitRate = 16;
6994
+ const step = bitRate / 8;
6995
+ const blockSize = channels * step;
6996
+ const byteLen = wave.length * step;
6997
+ const view = new DataView(new ArrayBuffer(44 + byteLen));
6998
+ const ws = (off2, s) => {
6999
+ for (let i = 0; i < s.length; i++)
7000
+ view.setUint8(off2 + i, s.charCodeAt(i));
7001
+ };
7002
+ ws(0, "RIFF");
7003
+ view.setUint32(4, 32 + byteLen, true);
7004
+ ws(8, "WAVE");
7005
+ ws(12, "fmt ");
7006
+ view.setUint32(16, 16, true);
7007
+ view.setUint16(20, 1, true);
7008
+ view.setUint16(22, channels, true);
7009
+ view.setUint32(24, sampleRate, true);
7010
+ view.setUint32(28, sampleRate * blockSize, true);
7011
+ view.setUint16(32, blockSize, true);
7012
+ view.setUint16(34, bitRate, true);
7013
+ ws(36, "data");
7014
+ view.setUint32(40, byteLen, true);
7015
+ const clamp4 = (n, a2, b) => Math.max(a2, Math.min(b, n));
7016
+ let off = 44;
7017
+ for (let i = 0; i < wave.length; i++, off += step)
7018
+ view.setInt16(
7019
+ off,
7020
+ clamp4(Math.round(wave[i] * 32768), -32768, 32767),
7021
+ true
7022
+ );
7023
+ const blob = new Blob([view], { type: "audio/wav" });
7024
+ const url = URL.createObjectURL(blob);
7025
+ const a = document.createElement("a");
7026
+ a.href = url;
7027
+ a.download = "record.wav";
7028
+ a.click();
7029
+ URL.revokeObjectURL(url);
7030
+ };
7031
+ const onToggleRecord = recorder ? () => {
7032
+ if (recorder.isRecording()) {
7033
+ recorder.stopRecording();
7034
+ downloadWav();
7035
+ } else {
7036
+ recorder.clearRecordedData();
7037
+ recorder.startRecording();
7038
+ }
7039
+ } : void 0;
7040
+ const listReady = new Promise((resolve) => {
7041
+ SoundFont_list.init();
7042
+ SoundFont_list.onload(() => resolve());
7043
+ });
7044
+ const drumReady = (async () => {
7045
+ try {
7046
+ await SoundFont_drum.load({
7047
+ ctx: audioCtx,
7048
+ font: DRUM_FONT,
7049
+ id: "0",
7050
+ keys: Object.values(DRUM_KEYS)
7051
+ });
7052
+ } catch (e) {
7053
+ console.error("[dtm] \u30C9\u30E9\u30E0\u97F3\u6E90\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557", e);
7054
+ }
7055
+ })();
7056
+ let nameToKey = {};
7057
+ const soundFonts = /* @__PURE__ */ new Map();
7058
+ const loadedKeyByTrack = /* @__PURE__ */ new Map();
7059
+ const loadSoundFont = async (instrumentKey, trackId) => {
7060
+ if (loadedKeyByTrack.get(trackId) === instrumentKey) return;
7061
+ try {
7062
+ const fullName = `${instrumentKey}_${SOUNDFONT_NAME}`;
7063
+ soundFonts.set(
7064
+ trackId,
7065
+ await SoundFont.load({
7066
+ ctx: audioCtx,
7067
+ fontName: `_tone_${fullName}`,
7068
+ url: SoundFont.toURL(fullName)
7069
+ })
7070
+ );
7071
+ loadedKeyByTrack.set(trackId, instrumentKey);
7072
+ } catch (e) {
7073
+ console.error(`[dtm] \u697D\u5668 "${instrumentKey}" \u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557`, e);
7074
+ }
7075
+ };
7076
+ const defaultPreset = options.defaultPreset ?? "retro_game";
7077
+ const instrumentNameFor = (preset, trackId) => preset[trackId] ?? preset.melody;
7078
+ const loadPreset = async (presetKey, trackIds = [...TRACK_ROLES]) => {
7079
+ const preset = INSTRUMENT_PRESETS[presetKey];
7080
+ if (!preset) return;
7081
+ await listReady;
7082
+ await Promise.all(
7083
+ trackIds.map((trackId) => {
7084
+ const key = nameToKey[instrumentNameFor(preset, trackId)];
7085
+ return key ? loadSoundFont(key, trackId) : Promise.resolve();
7086
+ })
7087
+ );
7088
+ };
7089
+ await listReady;
7090
+ nameToKey = await buildNameToKeyMapping();
7091
+ await Promise.all([drumReady, loadPreset(defaultPreset)]);
7092
+ const playNote = (e) => {
7093
+ const sf = soundFonts.get(e.trackId);
7094
+ if (!sf) return;
7095
+ sf.play({
7096
+ ctx: audioCtx,
7097
+ destination: masterGain,
7098
+ pitch: e.pitch,
7099
+ volume: e.volume,
7100
+ when: e.when,
7101
+ duration: e.duration
7102
+ });
7103
+ };
7104
+ const playDrum = (e) => {
7105
+ if (!SoundFont_drum.font) return;
7106
+ SoundFont_drum.play({
7107
+ ctx: audioCtx,
7108
+ destination: drumGain,
7109
+ pitch: e.pitch,
7110
+ volume: e.velocity,
7111
+ when: e.when,
7112
+ duration: e.duration
7113
+ });
7114
+ };
7115
+ const sfForPlayerTrack = (trackId) => soundFonts.get(TRACK_ROLES[Number(trackId)] ?? "") ?? soundFonts.get(`t${trackId}`);
7116
+ const playPlayerNote = (e) => {
7117
+ const sf = sfForPlayerTrack(e.trackId);
7118
+ if (!sf) return;
7119
+ sf.play({
7120
+ ctx: audioCtx,
7121
+ destination: masterGain,
7122
+ pitch: e.pitch,
7123
+ volume: e.volume,
7124
+ when: e.when,
7125
+ duration: e.duration
7126
+ });
7127
+ };
7128
+ const editorPresetSelects = /* @__PURE__ */ new WeakMap();
7129
+ const mountedEditors = [];
7130
+ const mountedPlayers = [];
7131
+ const mountEditor = (target, opts = {}) => {
7132
+ const { preset, presetUI, ...dawOverrides } = opts;
7133
+ const tracks = dawOverrides.tracks ?? TRACKS_SIMPLE;
7134
+ const trackIds = tracks.map((t) => t.id);
7135
+ const base = {
7136
+ getAudioTime: () => audioCtx.currentTime,
7137
+ onResumeAudio: resumeAudio,
7138
+ onPlayNote: playNote,
7139
+ onPlayDrum: playDrum,
7140
+ singingVoices,
7141
+ parseChord,
7142
+ parseChords,
7143
+ parseMidi,
7144
+ onToggleRecord,
7145
+ ...dawOverrides
7146
+ };
7147
+ const wantPresetUI = presetUI ?? features.presetUI;
7148
+ if (wantPresetUI) {
7149
+ const select = target.ownerDocument.createElement("select");
7150
+ select.className = "dtm-studio-preset";
7151
+ for (const [key, p] of Object.entries(INSTRUMENT_PRESETS)) {
7152
+ const opt = target.ownerDocument.createElement("option");
7153
+ opt.value = key;
7154
+ opt.textContent = p.displayName;
7155
+ select.appendChild(opt);
7156
+ }
7157
+ select.value = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
7158
+ target.appendChild(select);
7159
+ editorPresetSelects.set(target, select);
7160
+ select.addEventListener("change", async () => {
7161
+ daw.setInstrument(select.value);
7162
+ await loadPreset(select.value, trackIds);
7163
+ });
7164
+ }
7165
+ const daw = mountDAW(target, base);
7166
+ mountedEditors.push(daw);
7167
+ const presetKey = preset && INSTRUMENT_PRESETS[preset] ? preset : defaultPreset;
7168
+ daw.setInstrument(presetKey);
7169
+ void loadPreset(presetKey, trackIds);
7170
+ return daw;
7171
+ };
7172
+ const mountPlayer = (target, mml, opts = {}) => {
7173
+ const meta = parseMML(mml, {}).meta ?? {};
7174
+ if (meta.instrument && INSTRUMENT_PRESETS[meta.instrument]) {
7175
+ void loadPreset(meta.instrument);
7176
+ }
7177
+ const player = mountMmlPlayer(target, mml, {
7178
+ getAudioTime: () => audioCtx.currentTime,
7179
+ onResumeAudio: resumeAudio,
7180
+ onPlayNote: playPlayerNote,
7181
+ onPlayDrum: playDrum,
7182
+ singingVoices,
7183
+ ...opts
7184
+ });
7185
+ mountedPlayers.push(player);
7186
+ return player;
7187
+ };
7188
+ const dispose = () => {
7189
+ for (const p of mountedPlayers) p.destroy();
7190
+ for (const d of mountedEditors) d.destroy();
7191
+ mountedPlayers.length = 0;
7192
+ mountedEditors.length = 0;
7193
+ void audioCtx.close();
7194
+ };
7195
+ return {
7196
+ audioContext: audioCtx,
7197
+ singingVoices,
7198
+ mountEditor,
7199
+ mountPlayer,
7200
+ loadPreset,
7201
+ dispose
7202
+ };
7203
+ };
6511
7204
  // Annotate the CommonJS export names for ESM import in node:
6512
7205
  0 && (module.exports = {
6513
7206
  DAW_CSS,
7207
+ DEFAULT_BPM,
7208
+ DEFAULT_GATE,
7209
+ DEFAULT_PAN,
7210
+ DEFAULT_PLAYBACK_VELOCITY,
7211
+ DEFAULT_STEPS_PER_BAR,
7212
+ DEFAULT_VELOCITY,
7213
+ DEFAULT_VOCAL_VOLUME,
6514
7214
  DRUM_FONT,
6515
7215
  DRUM_KEYS,
6516
7216
  DRUM_PATTERNS,
@@ -6518,6 +7218,7 @@ var createPianoRoll = (options, handlers) => {
6518
7218
  KOE_BASE_URL,
6519
7219
  KOE_VOICEBANKS,
6520
7220
  KOE_VOICEBANK_LABELS,
7221
+ KOE_VOICEBANK_TERMS,
6521
7222
  LinkedList,
6522
7223
  MAX_VOCAL_VOLUME,
6523
7224
  MMLCore,
@@ -6525,6 +7226,7 @@ var createPianoRoll = (options, handlers) => {
6525
7226
  PREWARM_NOTES,
6526
7227
  TRACKS_ADVANCED,
6527
7228
  TRACKS_SIMPLE,
7229
+ VOICE_IMAGE_KEY,
6528
7230
  analyzeMidiTracks,
6529
7231
  applyHarmonicFilter,
6530
7232
  applyMonophonic,
@@ -6532,6 +7234,7 @@ var createPianoRoll = (options, handlers) => {
6532
7234
  buildNameToKeyMapping,
6533
7235
  collectPitchTokens,
6534
7236
  createAudioContext,
7237
+ createDtmStudio,
6535
7238
  createKlattVoice,
6536
7239
  createKoeVoice,
6537
7240
  createLyricsConductor,