@onjmin/dtm 0.1.57 → 0.1.59

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
@@ -5142,8 +5142,6 @@ var DAW_CSS = `
5142
5142
  max-height: 180px;
5143
5143
  overflow-y: auto;
5144
5144
  padding: 8px 10px 10px;
5145
- overflow-x: auto;
5146
- white-space: nowrap;
5147
5145
  scrollbar-width: none;
5148
5146
  display: flex;
5149
5147
  flex-direction: column;
@@ -5172,6 +5170,7 @@ var DAW_CSS = `
5172
5170
  .dtm-cp-bar {
5173
5171
  display: flex;
5174
5172
  align-items: center;
5173
+ flex-wrap: wrap;
5175
5174
  gap: 2px;
5176
5175
  margin-top: 2px;
5177
5176
  }
@@ -5540,6 +5539,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5540
5539
  const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
5541
5540
  const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
5542
5541
  const trackVolume = meta.volume ?? options.volume ?? 100;
5542
+ let masterVolume = options.masterVolume ?? 100;
5543
5543
  const drumVolume = meta.drumVolume ?? 80;
5544
5544
  const colors = options.trackColors ?? DEFAULT_TRACK_COLORS;
5545
5545
  const useSynth = options.synth ?? !options.onPlayNote;
@@ -6319,11 +6319,12 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6319
6319
  const em = emojiByTrack.get(trackIdx);
6320
6320
  if (em) jumpEmojiAt(em, e.when);
6321
6321
  if (lyricTracks.has(trackIdx) && !skipSinging) return;
6322
- options.onPlayNote?.(e);
6323
- if (useSynth) ensureSynth().playNote(e);
6322
+ const volume = e.volume * (masterVolume / 100);
6323
+ options.onPlayNote?.({ ...e, volume });
6324
+ if (useSynth) ensureSynth().playNote({ ...e, volume });
6324
6325
  },
6325
6326
  onPlayDrum: (e) => {
6326
- const velocity = e.velocity * (drumVolume / 100) * (trackVolume / 100);
6327
+ const velocity = e.velocity * (drumVolume / 100) * (trackVolume / 100) * (masterVolume / 100);
6327
6328
  options.onPlayDrum?.({ ...e, velocity });
6328
6329
  if (useSynth) ensureSynth().playDrum({ ...e, velocity });
6329
6330
  },
@@ -6347,6 +6348,9 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6347
6348
  if (activePlayer === instance) activePlayer = null;
6348
6349
  options.onStop?.();
6349
6350
  };
6351
+ const setVolume = (volume) => {
6352
+ masterVolume = volume;
6353
+ };
6350
6354
  const buildStreamTracks = () => [...lyricTracks.entries()].map(([index, lt]) => {
6351
6355
  const seqTrack = seqTracks.find((t) => Number(t.id) === index);
6352
6356
  const sorted = [...seqTrack?.notes ?? []].sort(
@@ -6368,7 +6372,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6368
6372
  return {
6369
6373
  id: String(index),
6370
6374
  model: lt.model,
6371
- volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
6375
+ volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100) * (masterVolume / 100),
6372
6376
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
6373
6377
  notes
6374
6378
  };
@@ -6482,6 +6486,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6482
6486
  play,
6483
6487
  stop,
6484
6488
  isPlaying: () => playing,
6489
+ setVolume,
6485
6490
  destroy
6486
6491
  };
6487
6492
  return instance;
@@ -6874,11 +6879,13 @@ var loadDrumFont = (ctx, pitch) => {
6874
6879
  _drumLoading.set(pitch, p);
6875
6880
  return p;
6876
6881
  };
6882
+ var BAR_SEP = /[|ll→]/;
6883
+ var NON_EVENT_RE = /^[=_]$|^N\.C\.$|^N$/i;
6877
6884
  var buildDisplayLines = (chords, eventsCount) => {
6878
6885
  const rawLines = chords.split("\n").map((l) => l.trim()).filter((l) => l);
6879
6886
  const displayLines = [];
6880
6887
  let eventCounter = 0;
6881
- let colorIdx = -1;
6888
+ let colorIdx = 0;
6882
6889
  for (const line of rawLines) {
6883
6890
  if (/^#/.test(line)) {
6884
6891
  const label = line.replace(/^#\s*/, "").trim();
@@ -6886,19 +6893,21 @@ var buildDisplayLines = (chords, eventsCount) => {
6886
6893
  if (/^tone\s*=/i.test(label)) continue;
6887
6894
  if (/^instrument\s*=/i.test(label)) continue;
6888
6895
  if (/^metronome/i.test(label)) continue;
6889
- colorIdx++;
6896
+ if (displayLines.length > 0 || eventCounter > 0) colorIdx++;
6890
6897
  displayLines.push({
6891
6898
  type: "section",
6892
6899
  section: label,
6893
- colorIdx: Math.max(0, colorIdx)
6900
+ colorIdx
6894
6901
  });
6895
6902
  continue;
6896
6903
  }
6897
- const segments = line.split("|");
6898
- if (segments.length <= 1) continue;
6904
+ const segments = line.split(BAR_SEP);
6905
+ if (segments.length === 0) continue;
6906
+ const hasMultiSeg = segments.length > 1;
6899
6907
  const parts = [];
6900
6908
  for (let i = 0; i < segments.length; i++) {
6901
- if (i > 0) parts.push({ text: "|", isChord: false, eventIdx: -1 });
6909
+ if (hasMultiSeg && i > 0)
6910
+ parts.push({ text: "|", isChord: false, eventIdx: -1 });
6902
6911
  const bar = segments[i].trim();
6903
6912
  if (!bar) continue;
6904
6913
  const splitAt = [];
@@ -6921,7 +6930,7 @@ var buildDisplayLines = (chords, eventsCount) => {
6921
6930
  splitAt.push(j + 1);
6922
6931
  continue;
6923
6932
  }
6924
- if (/^[A-G]$/i.test(char)) {
6933
+ if (/^[A-G]$/.test(char)) {
6925
6934
  const prev = bar[j - 1];
6926
6935
  const prev2 = bar.slice(Math.max(0, j - 2), j);
6927
6936
  if (prev === "/" || prev2.toLowerCase() === "on") continue;
@@ -6930,12 +6939,13 @@ var buildDisplayLines = (chords, eventsCount) => {
6930
6939
  }
6931
6940
  const uniqueSplitAt = Array.from(new Set(splitAt)).filter((idx) => idx >= 0 && idx < bar.length).sort((a, b) => a - b);
6932
6941
  if (uniqueSplitAt.length === 0) {
6942
+ const isEvent = !NON_EVENT_RE.test(bar);
6933
6943
  parts.push({
6934
6944
  text: bar,
6935
6945
  isChord: true,
6936
- eventIdx: eventCounter < eventsCount ? eventCounter : -1
6946
+ eventIdx: isEvent && eventCounter < eventsCount ? eventCounter : -1
6937
6947
  });
6938
- eventCounter++;
6948
+ if (isEvent) eventCounter++;
6939
6949
  continue;
6940
6950
  }
6941
6951
  for (let ci = 0; ci < uniqueSplitAt.length; ci++) {
@@ -6944,19 +6954,20 @@ var buildDisplayLines = (chords, eventsCount) => {
6944
6954
  const end = ci < uniqueSplitAt.length - 1 ? uniqueSplitAt[ci + 1] : bar.length;
6945
6955
  const symbol = bar.slice(start, end).trim();
6946
6956
  if (symbol) {
6957
+ const isEvent = !NON_EVENT_RE.test(symbol);
6947
6958
  parts.push({
6948
6959
  text: symbol,
6949
6960
  isChord: true,
6950
- eventIdx: eventCounter < eventsCount ? eventCounter : -1
6961
+ eventIdx: isEvent && eventCounter < eventsCount ? eventCounter : -1
6951
6962
  });
6952
- eventCounter++;
6963
+ if (isEvent) eventCounter++;
6953
6964
  }
6954
6965
  }
6955
6966
  }
6956
6967
  if (parts.length > 0) {
6957
6968
  displayLines.push({
6958
6969
  type: "bar",
6959
- colorIdx: Math.max(0, colorIdx),
6970
+ colorIdx,
6960
6971
  parts
6961
6972
  });
6962
6973
  }
@@ -6970,6 +6981,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
6970
6981
  let activeIndex = -1;
6971
6982
  let activePlayback = null;
6972
6983
  let isPlaying = false;
6984
+ let masterVolume = options.volume ?? 80;
6973
6985
  let isLoading = false;
6974
6986
  let loadAbortId = 0;
6975
6987
  let loopEnabled = false;
@@ -7329,7 +7341,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
7329
7341
  container2.innerHTML = "";
7330
7342
  modalSamplePlayer = mountChordPlayer(container2, sampleChords, {
7331
7343
  audioContext: options.audioContext ?? ownCtx ?? void 0,
7332
- volume: options.volume ?? 100,
7344
+ volume: masterVolume,
7333
7345
  bpm: options.bpm ?? 120,
7334
7346
  studio: options.studio,
7335
7347
  // 再帰的なモーダル生成を防ぐ。
@@ -7670,7 +7682,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
7670
7682
  };
7671
7683
  const playInternal = (startIdx, ctx, cutGain) => {
7672
7684
  const C32 = 48;
7673
- const volume = options.volume ?? 80;
7685
+ const volume = masterVolume;
7674
7686
  const placements = [];
7675
7687
  for (const event of chordEvents) {
7676
7688
  if (event.when < playOffsetSec) continue;
@@ -7827,6 +7839,10 @@ var mountChordPlayer = (target, chords, options = {}) => {
7827
7839
  }
7828
7840
  }
7829
7841
  };
7842
+ const setVolume = (volume) => {
7843
+ masterVolume = Math.max(0, Math.min(100, volume));
7844
+ activePlayback?.setVolume(masterVolume);
7845
+ };
7830
7846
  const destroy = () => {
7831
7847
  stop();
7832
7848
  closeInfoModal();
@@ -7956,6 +7972,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
7956
7972
  element: container,
7957
7973
  play,
7958
7974
  stop,
7975
+ setVolume,
7959
7976
  destroy,
7960
7977
  toggleMetronome,
7961
7978
  isPlaying: () => isPlaying
@@ -8890,6 +8907,18 @@ var MidiSearchClient = class {
8890
8907
  if (!res.ok) throw new Error(`picotune fetch failed: ${res.status}`);
8891
8908
  return res.arrayBuffer();
8892
8909
  }
8910
+ async searchRechord(word) {
8911
+ if (!this.enabled) return [];
8912
+ const qs = new URLSearchParams();
8913
+ qs.set("word", word);
8914
+ qs.set("guest", "true");
8915
+ const url2 = `${this.baseUrl}/rechord/scores?${qs.toString()}`;
8916
+ const res = await fetch(url2, { headers: this.headers() });
8917
+ if (!res.ok) throw new Error(`rechord search failed: ${res.status}`);
8918
+ const body = await res.json();
8919
+ const list = body.result ?? body.data ?? body;
8920
+ return Array.isArray(list) ? list : [];
8921
+ }
8893
8922
  };
8894
8923
 
8895
8924
  // src/linked-list.ts
@@ -9977,6 +10006,10 @@ var mountDAW = (target, options = {}) => {
9977
10006
  showChord,
9978
10007
  showMidiSearch
9979
10008
  });
10009
+ refs.masterVolume.value = String(options.masterVolume ?? 50);
10010
+ refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
10011
+ refs.drumVolume.value = String(options.drumVolume ?? 80);
10012
+ refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
9980
10013
  const renderConfig = {
9981
10014
  stepsPerBar: 192,
9982
10015
  keyCount: 128,
@@ -9988,8 +10021,8 @@ var mountDAW = (target, options = {}) => {
9988
10021
  let zoomX = 100;
9989
10022
  let zoomY = 100;
9990
10023
  let bpm = options.defaultBpm ?? DEFAULT_BPM;
9991
- let masterVolume = 50;
9992
- let drumVolume = 80;
10024
+ let masterVolume = options.masterVolume ?? 50;
10025
+ let drumVolume = options.drumVolume ?? 80;
9993
10026
  let currentDrumPattern = refs.drumSelect.value;
9994
10027
  let currentInstrument = "";
9995
10028
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
@@ -11314,6 +11347,7 @@ var mountDAW = (target, options = {}) => {
11314
11347
  <div style="display: inline-flex; align-items: center; gap: 6px;">
11315
11348
  <span class="dtm-label">\u548C\u97F3</span>
11316
11349
  <button class="dtm-infobtn" data-dtm="chord-info" title="\u30B3\u30FC\u30C9\u9032\u884C\u306E\u66F8\u304D\u65B9\u89E3\u8AAC">${icon("info", 12)}</button>
11350
+ ${showMidiSearch ? `<button class="dtm-btn dtm-btn--ghost" data-dtm="chord-search" title="\u30B3\u30FC\u30C9\u9032\u884C\u691C\u7D22" style="min-height: 24px; min-width: auto; height: 24px; padding: 0 6px; font-size: 11px; display: inline-flex; align-items: center;">\u30B3\u30FC\u30C9\u691C\u7D22</button>` : ""}
11317
11351
  </div>
11318
11352
  <select class="dtm-select" data-dtm="chord-pattern">
11319
11353
  <option value="block">\u30D6\u30ED\u30C3\u30AF</option>
@@ -11349,6 +11383,14 @@ var mountDAW = (target, options = {}) => {
11349
11383
  save();
11350
11384
  applyChord();
11351
11385
  });
11386
+ const searchChordBtn = div.querySelector(
11387
+ '[data-dtm="chord-search"]'
11388
+ );
11389
+ if (searchChordBtn) {
11390
+ searchChordBtn.addEventListener("click", () => {
11391
+ openChordSearchModal(input);
11392
+ });
11393
+ }
11352
11394
  }
11353
11395
  };
11354
11396
  const switchTrack = (id) => {
@@ -12212,6 +12254,8 @@ var mountDAW = (target, options = {}) => {
12212
12254
  searchPreviewBtn = null;
12213
12255
  }
12214
12256
  };
12257
+ let mmlSearchCache = null;
12258
+ let chordSearchCache = null;
12215
12259
  const wireMidiSearch = () => {
12216
12260
  if (!midiSearchClient) return;
12217
12261
  refs.midiSearchOpenBtn.addEventListener("click", () => {
@@ -12222,6 +12266,7 @@ var mountDAW = (target, options = {}) => {
12222
12266
  <input type="text" class="dtm-input dtm-grow" data-dtm="modal-midi-search-input" placeholder="\u66F2\u540D\u3067MML\u691C\u7D22" style="min-width:0">
12223
12267
  <button class="dtm-btn dtm-btn--primary" data-dtm="modal-midi-search-btn" style="flex-shrink:0">\u691C\u7D22</button>
12224
12268
  </div>
12269
+ <div data-dtm="modal-midi-search-status" style="font-size:11px;color:rgba(255,255,255,0.5);min-height:1.4em"></div>
12225
12270
  <div class="dtm-row" data-dtm="modal-midi-search-results" style="flex-direction:column;align-items:stretch;gap:4px"></div>
12226
12271
  `
12227
12272
  );
@@ -12231,9 +12276,17 @@ var mountDAW = (target, options = {}) => {
12231
12276
  const searchBtn = refs.modalBody.querySelector(
12232
12277
  '[data-dtm="modal-midi-search-btn"]'
12233
12278
  );
12279
+ const statusEl = refs.modalBody.querySelector(
12280
+ '[data-dtm="modal-midi-search-status"]'
12281
+ );
12234
12282
  const results = refs.modalBody.querySelector(
12235
12283
  '[data-dtm="modal-midi-search-results"]'
12236
12284
  );
12285
+ if (mmlSearchCache) {
12286
+ input.value = mmlSearchCache.query;
12287
+ statusEl.textContent = `\u300C${mmlSearchCache.query}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${mmlSearchCache.count}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12288
+ mmlSearchCache.renderResults(results);
12289
+ }
12237
12290
  const runSearch = async () => {
12238
12291
  const q2 = input.value.trim();
12239
12292
  if (!q2) return;
@@ -12241,37 +12294,85 @@ var mountDAW = (target, options = {}) => {
12241
12294
  searchBtn.disabled = true;
12242
12295
  searchBtn.textContent = "\u691C\u7D22\u4E2D...";
12243
12296
  results.innerHTML = "";
12297
+ statusEl.textContent = "";
12244
12298
  try {
12245
12299
  const songs = await midiSearchClient.searchSongs({ title: q2 });
12246
12300
  if (songs.length === 0) {
12301
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u30000\u4EF6\u306E\u30D2\u30C3\u30C8`;
12247
12302
  results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12303
+ mmlSearchCache = {
12304
+ query: q2,
12305
+ count: 0,
12306
+ renderResults: (c) => {
12307
+ c.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12308
+ }
12309
+ };
12248
12310
  return;
12249
12311
  }
12250
- for (const song of songs) {
12251
- const box = document.createElement("div");
12252
- box.className = "dtm-modal-sample-box";
12253
- const row = document.createElement("div");
12254
- row.className = "dtm-row";
12255
- row.style.cssText = "flex-wrap:nowrap;gap:4px;padding:2px 0";
12256
- const label = document.createElement("span");
12257
- label.className = "dtm-label";
12258
- label.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
12259
- label.textContent = `${song.title} - ${song.user}`;
12260
- const previewBtn = document.createElement("button");
12261
- previewBtn.className = "dtm-btn dtm-btn--ghost";
12262
- previewBtn.style.cssText = "flex-shrink:0";
12263
- previewBtn.textContent = "\u25B6 \u8A66\u8074";
12264
- const loadBtn = document.createElement("button");
12265
- loadBtn.className = "dtm-btn dtm-btn--primary";
12266
- loadBtn.style.cssText = "flex-shrink:0";
12267
- loadBtn.textContent = "\u8AAD\u307F\u8FBC\u307F";
12268
- const playerContainer = document.createElement("div");
12269
- playerContainer.className = "dtm-modal-sample-player-container";
12270
- previewBtn.addEventListener("click", async () => {
12271
- if (searchPreviewBtn === previewBtn) {
12272
- if (searchPreviewPlayer?.isPlaying()) {
12273
- searchPreviewPlayer.stop();
12312
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${songs.length}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12313
+ const renderMmlResults = (container) => {
12314
+ container.innerHTML = "";
12315
+ for (const song of songs) {
12316
+ const box = document.createElement("div");
12317
+ box.className = "dtm-modal-sample-box";
12318
+ const row = document.createElement("div");
12319
+ row.className = "dtm-row";
12320
+ row.style.cssText = "flex-wrap:nowrap;gap:4px;padding:2px 0";
12321
+ const label = document.createElement("span");
12322
+ label.className = "dtm-label";
12323
+ label.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
12324
+ label.textContent = `${song.title} - ${song.user}`;
12325
+ const previewBtn = document.createElement("button");
12326
+ previewBtn.className = "dtm-btn dtm-btn--ghost";
12327
+ previewBtn.style.cssText = "flex-shrink:0";
12328
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12329
+ const loadBtn = document.createElement("button");
12330
+ loadBtn.className = "dtm-btn dtm-btn--primary";
12331
+ loadBtn.style.cssText = "flex-shrink:0";
12332
+ loadBtn.textContent = "\u8AAD\u307F\u8FBC\u307F";
12333
+ const playerContainer = document.createElement("div");
12334
+ playerContainer.className = "dtm-modal-sample-player-container";
12335
+ previewBtn.addEventListener("click", async () => {
12336
+ if (searchPreviewBtn === previewBtn) {
12337
+ if (searchPreviewPlayer?.isPlaying()) {
12338
+ searchPreviewPlayer.stop();
12339
+ } else {
12340
+ stop();
12341
+ previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12342
+ try {
12343
+ const midi = await fetchVerifiedMidi(song.file);
12344
+ const mml = buildPreviewMml(midi);
12345
+ playerContainer.innerHTML = "";
12346
+ const player = mountMmlPlayer(playerContainer, mml, {
12347
+ onPlayNote: options.onPlayNote,
12348
+ onPlayDrum: options.onPlayDrum,
12349
+ onResumeAudio: options.onResumeAudio,
12350
+ getAudioTime: options.getAudioTime,
12351
+ singingVoices: options.singingVoices,
12352
+ drumPatterns: options.drumPatterns,
12353
+ volume: masterVolume,
12354
+ _skipInfoModals: true,
12355
+ onStop: () => {
12356
+ if (searchPreviewBtn === previewBtn) {
12357
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12358
+ previewBtn.classList.remove("dtm-btn--danger");
12359
+ previewBtn.classList.add("dtm-btn--ghost");
12360
+ }
12361
+ }
12362
+ });
12363
+ searchPreviewPlayer = player;
12364
+ searchPreviewBtn = previewBtn;
12365
+ player.play();
12366
+ previewBtn.textContent = "\u25A0 \u505C\u6B62";
12367
+ previewBtn.classList.remove("dtm-btn--ghost");
12368
+ previewBtn.classList.add("dtm-btn--danger");
12369
+ } catch (e) {
12370
+ console.error("[dtm] MIDI preview failed", e);
12371
+ previewBtn.textContent = "\u5931\u6557";
12372
+ }
12373
+ }
12274
12374
  } else {
12375
+ collapseSearchPreview();
12275
12376
  stop();
12276
12377
  previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12277
12378
  try {
@@ -12306,31 +12407,169 @@ var mountDAW = (target, options = {}) => {
12306
12407
  previewBtn.textContent = "\u5931\u6557";
12307
12408
  }
12308
12409
  }
12410
+ });
12411
+ loadBtn.addEventListener("click", async () => {
12412
+ loadBtn.disabled = true;
12413
+ loadBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12414
+ try {
12415
+ const pending = await fetchVerifiedMidi(song.file);
12416
+ const analysis = analyzeMidiTracks(pending);
12417
+ const sel = analysis.filter((a) => a.selected).map((a) => a.index);
12418
+ collapseSearchPreview();
12419
+ overlayDuring(() => applyMidiSelection(pending, sel));
12420
+ refs.modalOverlay.setAttribute("hidden", "");
12421
+ } catch (e) {
12422
+ console.error("[dtm] MIDI fetch failed", e);
12423
+ loadBtn.textContent = "\u5931\u6557";
12424
+ } finally {
12425
+ loadBtn.disabled = false;
12426
+ }
12427
+ });
12428
+ row.appendChild(label);
12429
+ row.appendChild(previewBtn);
12430
+ row.appendChild(loadBtn);
12431
+ box.appendChild(row);
12432
+ box.appendChild(playerContainer);
12433
+ container.appendChild(box);
12434
+ }
12435
+ };
12436
+ renderMmlResults(results);
12437
+ mmlSearchCache = {
12438
+ query: q2,
12439
+ count: songs.length,
12440
+ renderResults: renderMmlResults
12441
+ };
12442
+ } catch (e) {
12443
+ console.error("[dtm] MIDI search failed", e);
12444
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F</span>';
12445
+ } finally {
12446
+ searchBtn.disabled = false;
12447
+ searchBtn.textContent = "\u691C\u7D22";
12448
+ }
12449
+ };
12450
+ searchBtn.addEventListener("click", () => void runSearch());
12451
+ input.addEventListener("keydown", (e) => {
12452
+ if (e.key === "Enter") void runSearch();
12453
+ });
12454
+ input.focus();
12455
+ });
12456
+ };
12457
+ const openChordSearchModal = (chordTextArea) => {
12458
+ if (!midiSearchClient) return;
12459
+ const active = trackStates.find((t) => t.config.id === activeTrackId);
12460
+ if (!active) return;
12461
+ showModal(
12462
+ "\u30B3\u30FC\u30C9\u9032\u884C\u691C\u7D22",
12463
+ `
12464
+ <div class="dtm-row" style="flex-wrap:nowrap">
12465
+ <input type="text" class="dtm-input dtm-grow" data-dtm="modal-chord-search-input" placeholder="\u66F2\u540D\u3084\u30B3\u30FC\u30C9\u9032\u884C\u3067\u691C\u7D22" style="min-width:0">
12466
+ <button class="dtm-btn dtm-btn--primary" data-dtm="modal-chord-search-btn" style="flex-shrink:0">\u691C\u7D22</button>
12467
+ </div>
12468
+ <div data-dtm="modal-chord-search-status" style="font-size:11px;color:rgba(255,255,255,0.5);min-height:1.4em"></div>
12469
+ <div class="dtm-row" data-dtm="modal-chord-search-results" style="flex-direction:column;align-items:stretch;gap:4px"></div>
12470
+ `
12471
+ );
12472
+ const input = refs.modalBody.querySelector(
12473
+ '[data-dtm="modal-chord-search-input"]'
12474
+ );
12475
+ const searchBtn = refs.modalBody.querySelector(
12476
+ '[data-dtm="modal-chord-search-btn"]'
12477
+ );
12478
+ const statusEl = refs.modalBody.querySelector(
12479
+ '[data-dtm="modal-chord-search-status"]'
12480
+ );
12481
+ const results = refs.modalBody.querySelector(
12482
+ '[data-dtm="modal-chord-search-results"]'
12483
+ );
12484
+ if (chordSearchCache) {
12485
+ input.value = chordSearchCache.query;
12486
+ statusEl.textContent = `\u300C${chordSearchCache.query}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${chordSearchCache.count}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12487
+ chordSearchCache.renderResults(results);
12488
+ }
12489
+ const runSearch = async () => {
12490
+ const q2 = input.value.trim();
12491
+ if (!q2) return;
12492
+ collapseSearchPreview();
12493
+ searchBtn.disabled = true;
12494
+ searchBtn.textContent = "\u691C\u7D22\u4E2D...";
12495
+ results.innerHTML = "";
12496
+ statusEl.textContent = "";
12497
+ try {
12498
+ const scores = await midiSearchClient.searchRechord(q2);
12499
+ if (scores.length === 0) {
12500
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u30000\u4EF6\u306E\u30D2\u30C3\u30C8`;
12501
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12502
+ chordSearchCache = {
12503
+ query: q2,
12504
+ count: 0,
12505
+ renderResults: (c) => {
12506
+ c.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12507
+ }
12508
+ };
12509
+ return;
12510
+ }
12511
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${scores.length}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12512
+ const renderChordResults = (container) => {
12513
+ container.innerHTML = "";
12514
+ for (const score of scores) {
12515
+ const box = document.createElement("div");
12516
+ box.className = "dtm-modal-sample-box";
12517
+ const row = document.createElement("div");
12518
+ row.className = "dtm-row";
12519
+ row.style.cssText = "flex-wrap:nowrap;gap:4px;padding:2px 0";
12520
+ const label = document.createElement("span");
12521
+ label.className = "dtm-label";
12522
+ label.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
12523
+ label.textContent = `${score.title}${score.user?.screen_name ? ` - ${score.user.screen_name}` : ""}`;
12524
+ const previewBtn = document.createElement("button");
12525
+ previewBtn.className = "dtm-btn dtm-btn--ghost";
12526
+ previewBtn.style.cssText = "flex-shrink:0";
12527
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12528
+ const loadBtn = document.createElement("button");
12529
+ loadBtn.className = "dtm-btn dtm-btn--primary";
12530
+ loadBtn.style.cssText = "flex-shrink:0";
12531
+ loadBtn.textContent = "\u8AAD\u307F\u8FBC\u307F";
12532
+ const playerContainer = document.createElement("div");
12533
+ playerContainer.className = "dtm-modal-sample-player-container";
12534
+ previewBtn.addEventListener("click", async () => {
12535
+ if (searchPreviewBtn === previewBtn) {
12536
+ if (searchPreviewPlayer?.isPlaying()) {
12537
+ searchPreviewPlayer.stop();
12538
+ } else {
12539
+ stop();
12540
+ previewBtn.textContent = "\u518D\u751F\u4E2D...";
12541
+ try {
12542
+ searchPreviewPlayer?.play();
12543
+ previewBtn.textContent = "\u25A0 \u505C\u6B62";
12544
+ previewBtn.classList.remove("dtm-btn--ghost");
12545
+ previewBtn.classList.add("dtm-btn--danger");
12546
+ } catch (e) {
12547
+ console.error("[dtm] Chord preview play failed", e);
12548
+ previewBtn.textContent = "\u5931\u6557";
12549
+ }
12550
+ }
12309
12551
  } else {
12310
12552
  collapseSearchPreview();
12311
12553
  stop();
12312
12554
  previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12313
12555
  try {
12314
- const midi = await fetchVerifiedMidi(song.file);
12315
- const mml = buildPreviewMml(midi);
12316
12556
  playerContainer.innerHTML = "";
12317
- const player = mountMmlPlayer(playerContainer, mml, {
12318
- onPlayNote: options.onPlayNote,
12319
- onPlayDrum: options.onPlayDrum,
12320
- onResumeAudio: options.onResumeAudio,
12321
- getAudioTime: options.getAudioTime,
12322
- singingVoices: options.singingVoices,
12323
- drumPatterns: options.drumPatterns,
12324
- volume: masterVolume,
12325
- _skipInfoModals: true,
12326
- onStop: () => {
12327
- if (searchPreviewBtn === previewBtn) {
12328
- previewBtn.textContent = "\u25B6 \u8A66\u8074";
12329
- previewBtn.classList.remove("dtm-btn--danger");
12330
- previewBtn.classList.add("dtm-btn--ghost");
12557
+ const player = mountChordPlayer(
12558
+ playerContainer,
12559
+ score.content,
12560
+ {
12561
+ volume: masterVolume,
12562
+ bpm: score.bpm ?? bpm ?? 120,
12563
+ _skipInfoModals: true,
12564
+ onStop: () => {
12565
+ if (searchPreviewBtn === previewBtn) {
12566
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12567
+ previewBtn.classList.remove("dtm-btn--danger");
12568
+ previewBtn.classList.add("dtm-btn--ghost");
12569
+ }
12331
12570
  }
12332
12571
  }
12333
- });
12572
+ );
12334
12573
  searchPreviewPlayer = player;
12335
12574
  searchPreviewBtn = previewBtn;
12336
12575
  player.play();
@@ -12338,49 +12577,48 @@ var mountDAW = (target, options = {}) => {
12338
12577
  previewBtn.classList.remove("dtm-btn--ghost");
12339
12578
  previewBtn.classList.add("dtm-btn--danger");
12340
12579
  } catch (e) {
12341
- console.error("[dtm] MIDI preview failed", e);
12580
+ console.error("[dtm] Chord preview mount failed", e);
12342
12581
  previewBtn.textContent = "\u5931\u6557";
12343
12582
  }
12344
12583
  }
12345
12584
  });
12346
- loadBtn.addEventListener("click", async () => {
12347
- loadBtn.disabled = true;
12348
- loadBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12349
- try {
12350
- const pending = await fetchVerifiedMidi(song.file);
12351
- const analysis = analyzeMidiTracks(pending);
12352
- const sel = analysis.filter((a) => a.selected).map((a) => a.index);
12353
- collapseSearchPreview();
12354
- overlayDuring(() => applyMidiSelection(pending, sel));
12355
- refs.modalOverlay.setAttribute("hidden", "");
12356
- } catch (e) {
12357
- console.error("[dtm] MIDI fetch failed", e);
12358
- loadBtn.textContent = "\u5931\u6557";
12359
- } finally {
12360
- loadBtn.disabled = false;
12585
+ loadBtn.addEventListener("click", () => {
12586
+ collapseSearchPreview();
12587
+ chordTextArea.value = score.content;
12588
+ active.savedChordInput = score.content;
12589
+ if (score.bpm) {
12590
+ setBpm(score.bpm);
12361
12591
  }
12592
+ applyChord();
12593
+ refs.modalOverlay.setAttribute("hidden", "");
12362
12594
  });
12363
12595
  row.appendChild(label);
12364
12596
  row.appendChild(previewBtn);
12365
12597
  row.appendChild(loadBtn);
12366
12598
  box.appendChild(row);
12367
12599
  box.appendChild(playerContainer);
12368
- results.appendChild(box);
12600
+ container.appendChild(box);
12369
12601
  }
12370
- } catch (e) {
12371
- console.error("[dtm] MIDI search failed", e);
12372
- results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F</span>';
12373
- } finally {
12374
- searchBtn.disabled = false;
12375
- searchBtn.textContent = "\u691C\u7D22";
12376
- }
12377
- };
12378
- searchBtn.addEventListener("click", () => void runSearch());
12379
- input.addEventListener("keydown", (e) => {
12380
- if (e.key === "Enter") void runSearch();
12381
- });
12382
- input.focus();
12602
+ };
12603
+ renderChordResults(results);
12604
+ chordSearchCache = {
12605
+ query: q2,
12606
+ count: scores.length,
12607
+ renderResults: renderChordResults
12608
+ };
12609
+ } catch (e) {
12610
+ console.error("[dtm] Chord search failed", e);
12611
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F</span>';
12612
+ } finally {
12613
+ searchBtn.disabled = false;
12614
+ searchBtn.textContent = "\u691C\u7D22";
12615
+ }
12616
+ };
12617
+ searchBtn.addEventListener("click", () => void runSearch());
12618
+ input.addEventListener("keydown", (e) => {
12619
+ if (e.key === "Enter") void runSearch();
12383
12620
  });
12621
+ input.focus();
12384
12622
  };
12385
12623
  const onKeyDown = (e) => {
12386
12624
  if (!(e.ctrlKey || e.metaKey)) return;
@@ -12542,6 +12780,21 @@ var mountDAW = (target, options = {}) => {
12542
12780
  getCurrentPlayStep,
12543
12781
  forcePauseAt,
12544
12782
  setLoading,
12783
+ setMasterVolume: (volume) => {
12784
+ masterVolume = clamp3(volume, 0, 100);
12785
+ refs.masterVolume.value = String(masterVolume);
12786
+ refs.masterVolumeLabel.textContent = `${masterVolume}%`;
12787
+ },
12788
+ setVolume: (volume) => {
12789
+ masterVolume = clamp3(volume, 0, 100);
12790
+ refs.masterVolume.value = String(masterVolume);
12791
+ refs.masterVolumeLabel.textContent = `${masterVolume}%`;
12792
+ },
12793
+ setDrumVolume: (volume) => {
12794
+ drumVolume = clamp3(volume, 0, 100);
12795
+ refs.drumVolume.value = String(drumVolume);
12796
+ refs.drumVolumeLabel.textContent = `${drumVolume}%`;
12797
+ },
12545
12798
  applyPatch: (trackId, added, removed) => {
12546
12799
  const track = trackStates.find((t) => t.config.id === trackId);
12547
12800
  if (!track) return;