@onjmin/dtm 0.1.57 → 0.1.58

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
  }
@@ -6874,11 +6873,13 @@ var loadDrumFont = (ctx, pitch) => {
6874
6873
  _drumLoading.set(pitch, p);
6875
6874
  return p;
6876
6875
  };
6876
+ var BAR_SEP = /[|ll→]/;
6877
+ var NON_EVENT_RE = /^[=_]$|^N\.C\.$|^N$/i;
6877
6878
  var buildDisplayLines = (chords, eventsCount) => {
6878
6879
  const rawLines = chords.split("\n").map((l) => l.trim()).filter((l) => l);
6879
6880
  const displayLines = [];
6880
6881
  let eventCounter = 0;
6881
- let colorIdx = -1;
6882
+ let colorIdx = 0;
6882
6883
  for (const line of rawLines) {
6883
6884
  if (/^#/.test(line)) {
6884
6885
  const label = line.replace(/^#\s*/, "").trim();
@@ -6886,19 +6887,21 @@ var buildDisplayLines = (chords, eventsCount) => {
6886
6887
  if (/^tone\s*=/i.test(label)) continue;
6887
6888
  if (/^instrument\s*=/i.test(label)) continue;
6888
6889
  if (/^metronome/i.test(label)) continue;
6889
- colorIdx++;
6890
+ if (displayLines.length > 0 || eventCounter > 0) colorIdx++;
6890
6891
  displayLines.push({
6891
6892
  type: "section",
6892
6893
  section: label,
6893
- colorIdx: Math.max(0, colorIdx)
6894
+ colorIdx
6894
6895
  });
6895
6896
  continue;
6896
6897
  }
6897
- const segments = line.split("|");
6898
- if (segments.length <= 1) continue;
6898
+ const segments = line.split(BAR_SEP);
6899
+ if (segments.length === 0) continue;
6900
+ const hasMultiSeg = segments.length > 1;
6899
6901
  const parts = [];
6900
6902
  for (let i = 0; i < segments.length; i++) {
6901
- if (i > 0) parts.push({ text: "|", isChord: false, eventIdx: -1 });
6903
+ if (hasMultiSeg && i > 0)
6904
+ parts.push({ text: "|", isChord: false, eventIdx: -1 });
6902
6905
  const bar = segments[i].trim();
6903
6906
  if (!bar) continue;
6904
6907
  const splitAt = [];
@@ -6921,7 +6924,7 @@ var buildDisplayLines = (chords, eventsCount) => {
6921
6924
  splitAt.push(j + 1);
6922
6925
  continue;
6923
6926
  }
6924
- if (/^[A-G]$/i.test(char)) {
6927
+ if (/^[A-G]$/.test(char)) {
6925
6928
  const prev = bar[j - 1];
6926
6929
  const prev2 = bar.slice(Math.max(0, j - 2), j);
6927
6930
  if (prev === "/" || prev2.toLowerCase() === "on") continue;
@@ -6930,12 +6933,13 @@ var buildDisplayLines = (chords, eventsCount) => {
6930
6933
  }
6931
6934
  const uniqueSplitAt = Array.from(new Set(splitAt)).filter((idx) => idx >= 0 && idx < bar.length).sort((a, b) => a - b);
6932
6935
  if (uniqueSplitAt.length === 0) {
6936
+ const isEvent = !NON_EVENT_RE.test(bar);
6933
6937
  parts.push({
6934
6938
  text: bar,
6935
6939
  isChord: true,
6936
- eventIdx: eventCounter < eventsCount ? eventCounter : -1
6940
+ eventIdx: isEvent && eventCounter < eventsCount ? eventCounter : -1
6937
6941
  });
6938
- eventCounter++;
6942
+ if (isEvent) eventCounter++;
6939
6943
  continue;
6940
6944
  }
6941
6945
  for (let ci = 0; ci < uniqueSplitAt.length; ci++) {
@@ -6944,19 +6948,20 @@ var buildDisplayLines = (chords, eventsCount) => {
6944
6948
  const end = ci < uniqueSplitAt.length - 1 ? uniqueSplitAt[ci + 1] : bar.length;
6945
6949
  const symbol = bar.slice(start, end).trim();
6946
6950
  if (symbol) {
6951
+ const isEvent = !NON_EVENT_RE.test(symbol);
6947
6952
  parts.push({
6948
6953
  text: symbol,
6949
6954
  isChord: true,
6950
- eventIdx: eventCounter < eventsCount ? eventCounter : -1
6955
+ eventIdx: isEvent && eventCounter < eventsCount ? eventCounter : -1
6951
6956
  });
6952
- eventCounter++;
6957
+ if (isEvent) eventCounter++;
6953
6958
  }
6954
6959
  }
6955
6960
  }
6956
6961
  if (parts.length > 0) {
6957
6962
  displayLines.push({
6958
6963
  type: "bar",
6959
- colorIdx: Math.max(0, colorIdx),
6964
+ colorIdx,
6960
6965
  parts
6961
6966
  });
6962
6967
  }
@@ -8890,6 +8895,18 @@ var MidiSearchClient = class {
8890
8895
  if (!res.ok) throw new Error(`picotune fetch failed: ${res.status}`);
8891
8896
  return res.arrayBuffer();
8892
8897
  }
8898
+ async searchRechord(word) {
8899
+ if (!this.enabled) return [];
8900
+ const qs = new URLSearchParams();
8901
+ qs.set("word", word);
8902
+ qs.set("guest", "true");
8903
+ const url2 = `${this.baseUrl}/rechord/scores?${qs.toString()}`;
8904
+ const res = await fetch(url2, { headers: this.headers() });
8905
+ if (!res.ok) throw new Error(`rechord search failed: ${res.status}`);
8906
+ const body = await res.json();
8907
+ const list = body.result ?? body.data ?? body;
8908
+ return Array.isArray(list) ? list : [];
8909
+ }
8893
8910
  };
8894
8911
 
8895
8912
  // src/linked-list.ts
@@ -11314,6 +11331,7 @@ var mountDAW = (target, options = {}) => {
11314
11331
  <div style="display: inline-flex; align-items: center; gap: 6px;">
11315
11332
  <span class="dtm-label">\u548C\u97F3</span>
11316
11333
  <button class="dtm-infobtn" data-dtm="chord-info" title="\u30B3\u30FC\u30C9\u9032\u884C\u306E\u66F8\u304D\u65B9\u89E3\u8AAC">${icon("info", 12)}</button>
11334
+ ${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
11335
  </div>
11318
11336
  <select class="dtm-select" data-dtm="chord-pattern">
11319
11337
  <option value="block">\u30D6\u30ED\u30C3\u30AF</option>
@@ -11349,6 +11367,14 @@ var mountDAW = (target, options = {}) => {
11349
11367
  save();
11350
11368
  applyChord();
11351
11369
  });
11370
+ const searchChordBtn = div.querySelector(
11371
+ '[data-dtm="chord-search"]'
11372
+ );
11373
+ if (searchChordBtn) {
11374
+ searchChordBtn.addEventListener("click", () => {
11375
+ openChordSearchModal(input);
11376
+ });
11377
+ }
11352
11378
  }
11353
11379
  };
11354
11380
  const switchTrack = (id) => {
@@ -12212,6 +12238,8 @@ var mountDAW = (target, options = {}) => {
12212
12238
  searchPreviewBtn = null;
12213
12239
  }
12214
12240
  };
12241
+ let mmlSearchCache = null;
12242
+ let chordSearchCache = null;
12215
12243
  const wireMidiSearch = () => {
12216
12244
  if (!midiSearchClient) return;
12217
12245
  refs.midiSearchOpenBtn.addEventListener("click", () => {
@@ -12222,6 +12250,7 @@ var mountDAW = (target, options = {}) => {
12222
12250
  <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
12251
  <button class="dtm-btn dtm-btn--primary" data-dtm="modal-midi-search-btn" style="flex-shrink:0">\u691C\u7D22</button>
12224
12252
  </div>
12253
+ <div data-dtm="modal-midi-search-status" style="font-size:11px;color:rgba(255,255,255,0.5);min-height:1.4em"></div>
12225
12254
  <div class="dtm-row" data-dtm="modal-midi-search-results" style="flex-direction:column;align-items:stretch;gap:4px"></div>
12226
12255
  `
12227
12256
  );
@@ -12231,9 +12260,17 @@ var mountDAW = (target, options = {}) => {
12231
12260
  const searchBtn = refs.modalBody.querySelector(
12232
12261
  '[data-dtm="modal-midi-search-btn"]'
12233
12262
  );
12263
+ const statusEl = refs.modalBody.querySelector(
12264
+ '[data-dtm="modal-midi-search-status"]'
12265
+ );
12234
12266
  const results = refs.modalBody.querySelector(
12235
12267
  '[data-dtm="modal-midi-search-results"]'
12236
12268
  );
12269
+ if (mmlSearchCache) {
12270
+ input.value = mmlSearchCache.query;
12271
+ statusEl.textContent = `\u300C${mmlSearchCache.query}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${mmlSearchCache.count}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12272
+ mmlSearchCache.renderResults(results);
12273
+ }
12237
12274
  const runSearch = async () => {
12238
12275
  const q2 = input.value.trim();
12239
12276
  if (!q2) return;
@@ -12241,37 +12278,85 @@ var mountDAW = (target, options = {}) => {
12241
12278
  searchBtn.disabled = true;
12242
12279
  searchBtn.textContent = "\u691C\u7D22\u4E2D...";
12243
12280
  results.innerHTML = "";
12281
+ statusEl.textContent = "";
12244
12282
  try {
12245
12283
  const songs = await midiSearchClient.searchSongs({ title: q2 });
12246
12284
  if (songs.length === 0) {
12285
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u30000\u4EF6\u306E\u30D2\u30C3\u30C8`;
12247
12286
  results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12287
+ mmlSearchCache = {
12288
+ query: q2,
12289
+ count: 0,
12290
+ renderResults: (c) => {
12291
+ c.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12292
+ }
12293
+ };
12248
12294
  return;
12249
12295
  }
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();
12296
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${songs.length}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12297
+ const renderMmlResults = (container) => {
12298
+ container.innerHTML = "";
12299
+ for (const song of songs) {
12300
+ const box = document.createElement("div");
12301
+ box.className = "dtm-modal-sample-box";
12302
+ const row = document.createElement("div");
12303
+ row.className = "dtm-row";
12304
+ row.style.cssText = "flex-wrap:nowrap;gap:4px;padding:2px 0";
12305
+ const label = document.createElement("span");
12306
+ label.className = "dtm-label";
12307
+ label.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
12308
+ label.textContent = `${song.title} - ${song.user}`;
12309
+ const previewBtn = document.createElement("button");
12310
+ previewBtn.className = "dtm-btn dtm-btn--ghost";
12311
+ previewBtn.style.cssText = "flex-shrink:0";
12312
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12313
+ const loadBtn = document.createElement("button");
12314
+ loadBtn.className = "dtm-btn dtm-btn--primary";
12315
+ loadBtn.style.cssText = "flex-shrink:0";
12316
+ loadBtn.textContent = "\u8AAD\u307F\u8FBC\u307F";
12317
+ const playerContainer = document.createElement("div");
12318
+ playerContainer.className = "dtm-modal-sample-player-container";
12319
+ previewBtn.addEventListener("click", async () => {
12320
+ if (searchPreviewBtn === previewBtn) {
12321
+ if (searchPreviewPlayer?.isPlaying()) {
12322
+ searchPreviewPlayer.stop();
12323
+ } else {
12324
+ stop();
12325
+ previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12326
+ try {
12327
+ const midi = await fetchVerifiedMidi(song.file);
12328
+ const mml = buildPreviewMml(midi);
12329
+ playerContainer.innerHTML = "";
12330
+ const player = mountMmlPlayer(playerContainer, mml, {
12331
+ onPlayNote: options.onPlayNote,
12332
+ onPlayDrum: options.onPlayDrum,
12333
+ onResumeAudio: options.onResumeAudio,
12334
+ getAudioTime: options.getAudioTime,
12335
+ singingVoices: options.singingVoices,
12336
+ drumPatterns: options.drumPatterns,
12337
+ volume: masterVolume,
12338
+ _skipInfoModals: true,
12339
+ onStop: () => {
12340
+ if (searchPreviewBtn === previewBtn) {
12341
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12342
+ previewBtn.classList.remove("dtm-btn--danger");
12343
+ previewBtn.classList.add("dtm-btn--ghost");
12344
+ }
12345
+ }
12346
+ });
12347
+ searchPreviewPlayer = player;
12348
+ searchPreviewBtn = previewBtn;
12349
+ player.play();
12350
+ previewBtn.textContent = "\u25A0 \u505C\u6B62";
12351
+ previewBtn.classList.remove("dtm-btn--ghost");
12352
+ previewBtn.classList.add("dtm-btn--danger");
12353
+ } catch (e) {
12354
+ console.error("[dtm] MIDI preview failed", e);
12355
+ previewBtn.textContent = "\u5931\u6557";
12356
+ }
12357
+ }
12274
12358
  } else {
12359
+ collapseSearchPreview();
12275
12360
  stop();
12276
12361
  previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12277
12362
  try {
@@ -12306,31 +12391,169 @@ var mountDAW = (target, options = {}) => {
12306
12391
  previewBtn.textContent = "\u5931\u6557";
12307
12392
  }
12308
12393
  }
12394
+ });
12395
+ loadBtn.addEventListener("click", async () => {
12396
+ loadBtn.disabled = true;
12397
+ loadBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12398
+ try {
12399
+ const pending = await fetchVerifiedMidi(song.file);
12400
+ const analysis = analyzeMidiTracks(pending);
12401
+ const sel = analysis.filter((a) => a.selected).map((a) => a.index);
12402
+ collapseSearchPreview();
12403
+ overlayDuring(() => applyMidiSelection(pending, sel));
12404
+ refs.modalOverlay.setAttribute("hidden", "");
12405
+ } catch (e) {
12406
+ console.error("[dtm] MIDI fetch failed", e);
12407
+ loadBtn.textContent = "\u5931\u6557";
12408
+ } finally {
12409
+ loadBtn.disabled = false;
12410
+ }
12411
+ });
12412
+ row.appendChild(label);
12413
+ row.appendChild(previewBtn);
12414
+ row.appendChild(loadBtn);
12415
+ box.appendChild(row);
12416
+ box.appendChild(playerContainer);
12417
+ container.appendChild(box);
12418
+ }
12419
+ };
12420
+ renderMmlResults(results);
12421
+ mmlSearchCache = {
12422
+ query: q2,
12423
+ count: songs.length,
12424
+ renderResults: renderMmlResults
12425
+ };
12426
+ } catch (e) {
12427
+ console.error("[dtm] MIDI search failed", e);
12428
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F</span>';
12429
+ } finally {
12430
+ searchBtn.disabled = false;
12431
+ searchBtn.textContent = "\u691C\u7D22";
12432
+ }
12433
+ };
12434
+ searchBtn.addEventListener("click", () => void runSearch());
12435
+ input.addEventListener("keydown", (e) => {
12436
+ if (e.key === "Enter") void runSearch();
12437
+ });
12438
+ input.focus();
12439
+ });
12440
+ };
12441
+ const openChordSearchModal = (chordTextArea) => {
12442
+ if (!midiSearchClient) return;
12443
+ const active = trackStates.find((t) => t.config.id === activeTrackId);
12444
+ if (!active) return;
12445
+ showModal(
12446
+ "\u30B3\u30FC\u30C9\u9032\u884C\u691C\u7D22",
12447
+ `
12448
+ <div class="dtm-row" style="flex-wrap:nowrap">
12449
+ <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">
12450
+ <button class="dtm-btn dtm-btn--primary" data-dtm="modal-chord-search-btn" style="flex-shrink:0">\u691C\u7D22</button>
12451
+ </div>
12452
+ <div data-dtm="modal-chord-search-status" style="font-size:11px;color:rgba(255,255,255,0.5);min-height:1.4em"></div>
12453
+ <div class="dtm-row" data-dtm="modal-chord-search-results" style="flex-direction:column;align-items:stretch;gap:4px"></div>
12454
+ `
12455
+ );
12456
+ const input = refs.modalBody.querySelector(
12457
+ '[data-dtm="modal-chord-search-input"]'
12458
+ );
12459
+ const searchBtn = refs.modalBody.querySelector(
12460
+ '[data-dtm="modal-chord-search-btn"]'
12461
+ );
12462
+ const statusEl = refs.modalBody.querySelector(
12463
+ '[data-dtm="modal-chord-search-status"]'
12464
+ );
12465
+ const results = refs.modalBody.querySelector(
12466
+ '[data-dtm="modal-chord-search-results"]'
12467
+ );
12468
+ if (chordSearchCache) {
12469
+ input.value = chordSearchCache.query;
12470
+ statusEl.textContent = `\u300C${chordSearchCache.query}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${chordSearchCache.count}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12471
+ chordSearchCache.renderResults(results);
12472
+ }
12473
+ const runSearch = async () => {
12474
+ const q2 = input.value.trim();
12475
+ if (!q2) return;
12476
+ collapseSearchPreview();
12477
+ searchBtn.disabled = true;
12478
+ searchBtn.textContent = "\u691C\u7D22\u4E2D...";
12479
+ results.innerHTML = "";
12480
+ statusEl.textContent = "";
12481
+ try {
12482
+ const scores = await midiSearchClient.searchRechord(q2);
12483
+ if (scores.length === 0) {
12484
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u30000\u4EF6\u306E\u30D2\u30C3\u30C8`;
12485
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12486
+ chordSearchCache = {
12487
+ query: q2,
12488
+ count: 0,
12489
+ renderResults: (c) => {
12490
+ c.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12491
+ }
12492
+ };
12493
+ return;
12494
+ }
12495
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${scores.length}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12496
+ const renderChordResults = (container) => {
12497
+ container.innerHTML = "";
12498
+ for (const score of scores) {
12499
+ const box = document.createElement("div");
12500
+ box.className = "dtm-modal-sample-box";
12501
+ const row = document.createElement("div");
12502
+ row.className = "dtm-row";
12503
+ row.style.cssText = "flex-wrap:nowrap;gap:4px;padding:2px 0";
12504
+ const label = document.createElement("span");
12505
+ label.className = "dtm-label";
12506
+ label.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
12507
+ label.textContent = `${score.title}${score.user?.screen_name ? ` - ${score.user.screen_name}` : ""}`;
12508
+ const previewBtn = document.createElement("button");
12509
+ previewBtn.className = "dtm-btn dtm-btn--ghost";
12510
+ previewBtn.style.cssText = "flex-shrink:0";
12511
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12512
+ const loadBtn = document.createElement("button");
12513
+ loadBtn.className = "dtm-btn dtm-btn--primary";
12514
+ loadBtn.style.cssText = "flex-shrink:0";
12515
+ loadBtn.textContent = "\u8AAD\u307F\u8FBC\u307F";
12516
+ const playerContainer = document.createElement("div");
12517
+ playerContainer.className = "dtm-modal-sample-player-container";
12518
+ previewBtn.addEventListener("click", async () => {
12519
+ if (searchPreviewBtn === previewBtn) {
12520
+ if (searchPreviewPlayer?.isPlaying()) {
12521
+ searchPreviewPlayer.stop();
12522
+ } else {
12523
+ stop();
12524
+ previewBtn.textContent = "\u518D\u751F\u4E2D...";
12525
+ try {
12526
+ searchPreviewPlayer?.play();
12527
+ previewBtn.textContent = "\u25A0 \u505C\u6B62";
12528
+ previewBtn.classList.remove("dtm-btn--ghost");
12529
+ previewBtn.classList.add("dtm-btn--danger");
12530
+ } catch (e) {
12531
+ console.error("[dtm] Chord preview play failed", e);
12532
+ previewBtn.textContent = "\u5931\u6557";
12533
+ }
12534
+ }
12309
12535
  } else {
12310
12536
  collapseSearchPreview();
12311
12537
  stop();
12312
12538
  previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12313
12539
  try {
12314
- const midi = await fetchVerifiedMidi(song.file);
12315
- const mml = buildPreviewMml(midi);
12316
12540
  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");
12541
+ const player = mountChordPlayer(
12542
+ playerContainer,
12543
+ score.content,
12544
+ {
12545
+ volume: masterVolume,
12546
+ bpm: score.bpm ?? bpm ?? 120,
12547
+ _skipInfoModals: true,
12548
+ onStop: () => {
12549
+ if (searchPreviewBtn === previewBtn) {
12550
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12551
+ previewBtn.classList.remove("dtm-btn--danger");
12552
+ previewBtn.classList.add("dtm-btn--ghost");
12553
+ }
12331
12554
  }
12332
12555
  }
12333
- });
12556
+ );
12334
12557
  searchPreviewPlayer = player;
12335
12558
  searchPreviewBtn = previewBtn;
12336
12559
  player.play();
@@ -12338,49 +12561,48 @@ var mountDAW = (target, options = {}) => {
12338
12561
  previewBtn.classList.remove("dtm-btn--ghost");
12339
12562
  previewBtn.classList.add("dtm-btn--danger");
12340
12563
  } catch (e) {
12341
- console.error("[dtm] MIDI preview failed", e);
12564
+ console.error("[dtm] Chord preview mount failed", e);
12342
12565
  previewBtn.textContent = "\u5931\u6557";
12343
12566
  }
12344
12567
  }
12345
12568
  });
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;
12569
+ loadBtn.addEventListener("click", () => {
12570
+ collapseSearchPreview();
12571
+ chordTextArea.value = score.content;
12572
+ active.savedChordInput = score.content;
12573
+ if (score.bpm) {
12574
+ setBpm(score.bpm);
12361
12575
  }
12576
+ applyChord();
12577
+ refs.modalOverlay.setAttribute("hidden", "");
12362
12578
  });
12363
12579
  row.appendChild(label);
12364
12580
  row.appendChild(previewBtn);
12365
12581
  row.appendChild(loadBtn);
12366
12582
  box.appendChild(row);
12367
12583
  box.appendChild(playerContainer);
12368
- results.appendChild(box);
12584
+ container.appendChild(box);
12369
12585
  }
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();
12586
+ };
12587
+ renderChordResults(results);
12588
+ chordSearchCache = {
12589
+ query: q2,
12590
+ count: scores.length,
12591
+ renderResults: renderChordResults
12592
+ };
12593
+ } catch (e) {
12594
+ console.error("[dtm] Chord search failed", e);
12595
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F</span>';
12596
+ } finally {
12597
+ searchBtn.disabled = false;
12598
+ searchBtn.textContent = "\u691C\u7D22";
12599
+ }
12600
+ };
12601
+ searchBtn.addEventListener("click", () => void runSearch());
12602
+ input.addEventListener("keydown", (e) => {
12603
+ if (e.key === "Enter") void runSearch();
12383
12604
  });
12605
+ input.focus();
12384
12606
  };
12385
12607
  const onKeyDown = (e) => {
12386
12608
  if (!(e.ctrlKey || e.metaKey)) return;