@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.mjs CHANGED
@@ -5020,8 +5020,6 @@ var DAW_CSS = `
5020
5020
  max-height: 180px;
5021
5021
  overflow-y: auto;
5022
5022
  padding: 8px 10px 10px;
5023
- overflow-x: auto;
5024
- white-space: nowrap;
5025
5023
  scrollbar-width: none;
5026
5024
  display: flex;
5027
5025
  flex-direction: column;
@@ -5050,6 +5048,7 @@ var DAW_CSS = `
5050
5048
  .dtm-cp-bar {
5051
5049
  display: flex;
5052
5050
  align-items: center;
5051
+ flex-wrap: wrap;
5053
5052
  gap: 2px;
5054
5053
  margin-top: 2px;
5055
5054
  }
@@ -5418,6 +5417,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5418
5417
  const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
5419
5418
  const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
5420
5419
  const trackVolume = meta.volume ?? options.volume ?? 100;
5420
+ let masterVolume = options.masterVolume ?? 100;
5421
5421
  const drumVolume = meta.drumVolume ?? 80;
5422
5422
  const colors = options.trackColors ?? DEFAULT_TRACK_COLORS;
5423
5423
  const useSynth = options.synth ?? !options.onPlayNote;
@@ -6197,11 +6197,12 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6197
6197
  const em = emojiByTrack.get(trackIdx);
6198
6198
  if (em) jumpEmojiAt(em, e.when);
6199
6199
  if (lyricTracks.has(trackIdx) && !skipSinging) return;
6200
- options.onPlayNote?.(e);
6201
- if (useSynth) ensureSynth().playNote(e);
6200
+ const volume = e.volume * (masterVolume / 100);
6201
+ options.onPlayNote?.({ ...e, volume });
6202
+ if (useSynth) ensureSynth().playNote({ ...e, volume });
6202
6203
  },
6203
6204
  onPlayDrum: (e) => {
6204
- const velocity = e.velocity * (drumVolume / 100) * (trackVolume / 100);
6205
+ const velocity = e.velocity * (drumVolume / 100) * (trackVolume / 100) * (masterVolume / 100);
6205
6206
  options.onPlayDrum?.({ ...e, velocity });
6206
6207
  if (useSynth) ensureSynth().playDrum({ ...e, velocity });
6207
6208
  },
@@ -6225,6 +6226,9 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6225
6226
  if (activePlayer === instance) activePlayer = null;
6226
6227
  options.onStop?.();
6227
6228
  };
6229
+ const setVolume = (volume) => {
6230
+ masterVolume = volume;
6231
+ };
6228
6232
  const buildStreamTracks = () => [...lyricTracks.entries()].map(([index, lt]) => {
6229
6233
  const seqTrack = seqTracks.find((t) => Number(t.id) === index);
6230
6234
  const sorted = [...seqTrack?.notes ?? []].sort(
@@ -6246,7 +6250,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6246
6250
  return {
6247
6251
  id: String(index),
6248
6252
  model: lt.model,
6249
- volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
6253
+ volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100) * (masterVolume / 100),
6250
6254
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
6251
6255
  notes
6252
6256
  };
@@ -6360,6 +6364,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6360
6364
  play,
6361
6365
  stop,
6362
6366
  isPlaying: () => playing,
6367
+ setVolume,
6363
6368
  destroy
6364
6369
  };
6365
6370
  return instance;
@@ -6752,11 +6757,13 @@ var loadDrumFont = (ctx, pitch) => {
6752
6757
  _drumLoading.set(pitch, p);
6753
6758
  return p;
6754
6759
  };
6760
+ var BAR_SEP = /[|ll→]/;
6761
+ var NON_EVENT_RE = /^[=_]$|^N\.C\.$|^N$/i;
6755
6762
  var buildDisplayLines = (chords, eventsCount) => {
6756
6763
  const rawLines = chords.split("\n").map((l) => l.trim()).filter((l) => l);
6757
6764
  const displayLines = [];
6758
6765
  let eventCounter = 0;
6759
- let colorIdx = -1;
6766
+ let colorIdx = 0;
6760
6767
  for (const line of rawLines) {
6761
6768
  if (/^#/.test(line)) {
6762
6769
  const label = line.replace(/^#\s*/, "").trim();
@@ -6764,19 +6771,21 @@ var buildDisplayLines = (chords, eventsCount) => {
6764
6771
  if (/^tone\s*=/i.test(label)) continue;
6765
6772
  if (/^instrument\s*=/i.test(label)) continue;
6766
6773
  if (/^metronome/i.test(label)) continue;
6767
- colorIdx++;
6774
+ if (displayLines.length > 0 || eventCounter > 0) colorIdx++;
6768
6775
  displayLines.push({
6769
6776
  type: "section",
6770
6777
  section: label,
6771
- colorIdx: Math.max(0, colorIdx)
6778
+ colorIdx
6772
6779
  });
6773
6780
  continue;
6774
6781
  }
6775
- const segments = line.split("|");
6776
- if (segments.length <= 1) continue;
6782
+ const segments = line.split(BAR_SEP);
6783
+ if (segments.length === 0) continue;
6784
+ const hasMultiSeg = segments.length > 1;
6777
6785
  const parts = [];
6778
6786
  for (let i = 0; i < segments.length; i++) {
6779
- if (i > 0) parts.push({ text: "|", isChord: false, eventIdx: -1 });
6787
+ if (hasMultiSeg && i > 0)
6788
+ parts.push({ text: "|", isChord: false, eventIdx: -1 });
6780
6789
  const bar = segments[i].trim();
6781
6790
  if (!bar) continue;
6782
6791
  const splitAt = [];
@@ -6799,7 +6808,7 @@ var buildDisplayLines = (chords, eventsCount) => {
6799
6808
  splitAt.push(j + 1);
6800
6809
  continue;
6801
6810
  }
6802
- if (/^[A-G]$/i.test(char)) {
6811
+ if (/^[A-G]$/.test(char)) {
6803
6812
  const prev = bar[j - 1];
6804
6813
  const prev2 = bar.slice(Math.max(0, j - 2), j);
6805
6814
  if (prev === "/" || prev2.toLowerCase() === "on") continue;
@@ -6808,12 +6817,13 @@ var buildDisplayLines = (chords, eventsCount) => {
6808
6817
  }
6809
6818
  const uniqueSplitAt = Array.from(new Set(splitAt)).filter((idx) => idx >= 0 && idx < bar.length).sort((a, b) => a - b);
6810
6819
  if (uniqueSplitAt.length === 0) {
6820
+ const isEvent = !NON_EVENT_RE.test(bar);
6811
6821
  parts.push({
6812
6822
  text: bar,
6813
6823
  isChord: true,
6814
- eventIdx: eventCounter < eventsCount ? eventCounter : -1
6824
+ eventIdx: isEvent && eventCounter < eventsCount ? eventCounter : -1
6815
6825
  });
6816
- eventCounter++;
6826
+ if (isEvent) eventCounter++;
6817
6827
  continue;
6818
6828
  }
6819
6829
  for (let ci = 0; ci < uniqueSplitAt.length; ci++) {
@@ -6822,19 +6832,20 @@ var buildDisplayLines = (chords, eventsCount) => {
6822
6832
  const end = ci < uniqueSplitAt.length - 1 ? uniqueSplitAt[ci + 1] : bar.length;
6823
6833
  const symbol = bar.slice(start, end).trim();
6824
6834
  if (symbol) {
6835
+ const isEvent = !NON_EVENT_RE.test(symbol);
6825
6836
  parts.push({
6826
6837
  text: symbol,
6827
6838
  isChord: true,
6828
- eventIdx: eventCounter < eventsCount ? eventCounter : -1
6839
+ eventIdx: isEvent && eventCounter < eventsCount ? eventCounter : -1
6829
6840
  });
6830
- eventCounter++;
6841
+ if (isEvent) eventCounter++;
6831
6842
  }
6832
6843
  }
6833
6844
  }
6834
6845
  if (parts.length > 0) {
6835
6846
  displayLines.push({
6836
6847
  type: "bar",
6837
- colorIdx: Math.max(0, colorIdx),
6848
+ colorIdx,
6838
6849
  parts
6839
6850
  });
6840
6851
  }
@@ -6848,6 +6859,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
6848
6859
  let activeIndex = -1;
6849
6860
  let activePlayback = null;
6850
6861
  let isPlaying = false;
6862
+ let masterVolume = options.volume ?? 80;
6851
6863
  let isLoading = false;
6852
6864
  let loadAbortId = 0;
6853
6865
  let loopEnabled = false;
@@ -7207,7 +7219,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
7207
7219
  container2.innerHTML = "";
7208
7220
  modalSamplePlayer = mountChordPlayer(container2, sampleChords, {
7209
7221
  audioContext: options.audioContext ?? ownCtx ?? void 0,
7210
- volume: options.volume ?? 100,
7222
+ volume: masterVolume,
7211
7223
  bpm: options.bpm ?? 120,
7212
7224
  studio: options.studio,
7213
7225
  // 再帰的なモーダル生成を防ぐ。
@@ -7548,7 +7560,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
7548
7560
  };
7549
7561
  const playInternal = (startIdx, ctx, cutGain) => {
7550
7562
  const C32 = 48;
7551
- const volume = options.volume ?? 80;
7563
+ const volume = masterVolume;
7552
7564
  const placements = [];
7553
7565
  for (const event of chordEvents) {
7554
7566
  if (event.when < playOffsetSec) continue;
@@ -7705,6 +7717,10 @@ var mountChordPlayer = (target, chords, options = {}) => {
7705
7717
  }
7706
7718
  }
7707
7719
  };
7720
+ const setVolume = (volume) => {
7721
+ masterVolume = Math.max(0, Math.min(100, volume));
7722
+ activePlayback?.setVolume(masterVolume);
7723
+ };
7708
7724
  const destroy = () => {
7709
7725
  stop();
7710
7726
  closeInfoModal();
@@ -7834,6 +7850,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
7834
7850
  element: container,
7835
7851
  play,
7836
7852
  stop,
7853
+ setVolume,
7837
7854
  destroy,
7838
7855
  toggleMetronome,
7839
7856
  isPlaying: () => isPlaying
@@ -8768,6 +8785,18 @@ var MidiSearchClient = class {
8768
8785
  if (!res.ok) throw new Error(`picotune fetch failed: ${res.status}`);
8769
8786
  return res.arrayBuffer();
8770
8787
  }
8788
+ async searchRechord(word) {
8789
+ if (!this.enabled) return [];
8790
+ const qs = new URLSearchParams();
8791
+ qs.set("word", word);
8792
+ qs.set("guest", "true");
8793
+ const url2 = `${this.baseUrl}/rechord/scores?${qs.toString()}`;
8794
+ const res = await fetch(url2, { headers: this.headers() });
8795
+ if (!res.ok) throw new Error(`rechord search failed: ${res.status}`);
8796
+ const body = await res.json();
8797
+ const list = body.result ?? body.data ?? body;
8798
+ return Array.isArray(list) ? list : [];
8799
+ }
8771
8800
  };
8772
8801
 
8773
8802
  // src/linked-list.ts
@@ -9855,6 +9884,10 @@ var mountDAW = (target, options = {}) => {
9855
9884
  showChord,
9856
9885
  showMidiSearch
9857
9886
  });
9887
+ refs.masterVolume.value = String(options.masterVolume ?? 50);
9888
+ refs.masterVolumeLabel.textContent = `${options.masterVolume ?? 50}%`;
9889
+ refs.drumVolume.value = String(options.drumVolume ?? 80);
9890
+ refs.drumVolumeLabel.textContent = `${options.drumVolume ?? 80}%`;
9858
9891
  const renderConfig = {
9859
9892
  stepsPerBar: 192,
9860
9893
  keyCount: 128,
@@ -9866,8 +9899,8 @@ var mountDAW = (target, options = {}) => {
9866
9899
  let zoomX = 100;
9867
9900
  let zoomY = 100;
9868
9901
  let bpm = options.defaultBpm ?? DEFAULT_BPM;
9869
- let masterVolume = 50;
9870
- let drumVolume = 80;
9902
+ let masterVolume = options.masterVolume ?? 50;
9903
+ let drumVolume = options.drumVolume ?? 80;
9871
9904
  let currentDrumPattern = refs.drumSelect.value;
9872
9905
  let currentInstrument = "";
9873
9906
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
@@ -11192,6 +11225,7 @@ var mountDAW = (target, options = {}) => {
11192
11225
  <div style="display: inline-flex; align-items: center; gap: 6px;">
11193
11226
  <span class="dtm-label">\u548C\u97F3</span>
11194
11227
  <button class="dtm-infobtn" data-dtm="chord-info" title="\u30B3\u30FC\u30C9\u9032\u884C\u306E\u66F8\u304D\u65B9\u89E3\u8AAC">${icon("info", 12)}</button>
11228
+ ${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>` : ""}
11195
11229
  </div>
11196
11230
  <select class="dtm-select" data-dtm="chord-pattern">
11197
11231
  <option value="block">\u30D6\u30ED\u30C3\u30AF</option>
@@ -11227,6 +11261,14 @@ var mountDAW = (target, options = {}) => {
11227
11261
  save();
11228
11262
  applyChord();
11229
11263
  });
11264
+ const searchChordBtn = div.querySelector(
11265
+ '[data-dtm="chord-search"]'
11266
+ );
11267
+ if (searchChordBtn) {
11268
+ searchChordBtn.addEventListener("click", () => {
11269
+ openChordSearchModal(input);
11270
+ });
11271
+ }
11230
11272
  }
11231
11273
  };
11232
11274
  const switchTrack = (id) => {
@@ -12090,6 +12132,8 @@ var mountDAW = (target, options = {}) => {
12090
12132
  searchPreviewBtn = null;
12091
12133
  }
12092
12134
  };
12135
+ let mmlSearchCache = null;
12136
+ let chordSearchCache = null;
12093
12137
  const wireMidiSearch = () => {
12094
12138
  if (!midiSearchClient) return;
12095
12139
  refs.midiSearchOpenBtn.addEventListener("click", () => {
@@ -12100,6 +12144,7 @@ var mountDAW = (target, options = {}) => {
12100
12144
  <input type="text" class="dtm-input dtm-grow" data-dtm="modal-midi-search-input" placeholder="\u66F2\u540D\u3067MML\u691C\u7D22" style="min-width:0">
12101
12145
  <button class="dtm-btn dtm-btn--primary" data-dtm="modal-midi-search-btn" style="flex-shrink:0">\u691C\u7D22</button>
12102
12146
  </div>
12147
+ <div data-dtm="modal-midi-search-status" style="font-size:11px;color:rgba(255,255,255,0.5);min-height:1.4em"></div>
12103
12148
  <div class="dtm-row" data-dtm="modal-midi-search-results" style="flex-direction:column;align-items:stretch;gap:4px"></div>
12104
12149
  `
12105
12150
  );
@@ -12109,9 +12154,17 @@ var mountDAW = (target, options = {}) => {
12109
12154
  const searchBtn = refs.modalBody.querySelector(
12110
12155
  '[data-dtm="modal-midi-search-btn"]'
12111
12156
  );
12157
+ const statusEl = refs.modalBody.querySelector(
12158
+ '[data-dtm="modal-midi-search-status"]'
12159
+ );
12112
12160
  const results = refs.modalBody.querySelector(
12113
12161
  '[data-dtm="modal-midi-search-results"]'
12114
12162
  );
12163
+ if (mmlSearchCache) {
12164
+ input.value = mmlSearchCache.query;
12165
+ statusEl.textContent = `\u300C${mmlSearchCache.query}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${mmlSearchCache.count}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12166
+ mmlSearchCache.renderResults(results);
12167
+ }
12115
12168
  const runSearch = async () => {
12116
12169
  const q2 = input.value.trim();
12117
12170
  if (!q2) return;
@@ -12119,37 +12172,85 @@ var mountDAW = (target, options = {}) => {
12119
12172
  searchBtn.disabled = true;
12120
12173
  searchBtn.textContent = "\u691C\u7D22\u4E2D...";
12121
12174
  results.innerHTML = "";
12175
+ statusEl.textContent = "";
12122
12176
  try {
12123
12177
  const songs = await midiSearchClient.searchSongs({ title: q2 });
12124
12178
  if (songs.length === 0) {
12179
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u30000\u4EF6\u306E\u30D2\u30C3\u30C8`;
12125
12180
  results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12181
+ mmlSearchCache = {
12182
+ query: q2,
12183
+ count: 0,
12184
+ renderResults: (c) => {
12185
+ c.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12186
+ }
12187
+ };
12126
12188
  return;
12127
12189
  }
12128
- for (const song of songs) {
12129
- const box = document.createElement("div");
12130
- box.className = "dtm-modal-sample-box";
12131
- const row = document.createElement("div");
12132
- row.className = "dtm-row";
12133
- row.style.cssText = "flex-wrap:nowrap;gap:4px;padding:2px 0";
12134
- const label = document.createElement("span");
12135
- label.className = "dtm-label";
12136
- label.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
12137
- label.textContent = `${song.title} - ${song.user}`;
12138
- const previewBtn = document.createElement("button");
12139
- previewBtn.className = "dtm-btn dtm-btn--ghost";
12140
- previewBtn.style.cssText = "flex-shrink:0";
12141
- previewBtn.textContent = "\u25B6 \u8A66\u8074";
12142
- const loadBtn = document.createElement("button");
12143
- loadBtn.className = "dtm-btn dtm-btn--primary";
12144
- loadBtn.style.cssText = "flex-shrink:0";
12145
- loadBtn.textContent = "\u8AAD\u307F\u8FBC\u307F";
12146
- const playerContainer = document.createElement("div");
12147
- playerContainer.className = "dtm-modal-sample-player-container";
12148
- previewBtn.addEventListener("click", async () => {
12149
- if (searchPreviewBtn === previewBtn) {
12150
- if (searchPreviewPlayer?.isPlaying()) {
12151
- searchPreviewPlayer.stop();
12190
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${songs.length}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12191
+ const renderMmlResults = (container) => {
12192
+ container.innerHTML = "";
12193
+ for (const song of songs) {
12194
+ const box = document.createElement("div");
12195
+ box.className = "dtm-modal-sample-box";
12196
+ const row = document.createElement("div");
12197
+ row.className = "dtm-row";
12198
+ row.style.cssText = "flex-wrap:nowrap;gap:4px;padding:2px 0";
12199
+ const label = document.createElement("span");
12200
+ label.className = "dtm-label";
12201
+ label.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
12202
+ label.textContent = `${song.title} - ${song.user}`;
12203
+ const previewBtn = document.createElement("button");
12204
+ previewBtn.className = "dtm-btn dtm-btn--ghost";
12205
+ previewBtn.style.cssText = "flex-shrink:0";
12206
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12207
+ const loadBtn = document.createElement("button");
12208
+ loadBtn.className = "dtm-btn dtm-btn--primary";
12209
+ loadBtn.style.cssText = "flex-shrink:0";
12210
+ loadBtn.textContent = "\u8AAD\u307F\u8FBC\u307F";
12211
+ const playerContainer = document.createElement("div");
12212
+ playerContainer.className = "dtm-modal-sample-player-container";
12213
+ previewBtn.addEventListener("click", async () => {
12214
+ if (searchPreviewBtn === previewBtn) {
12215
+ if (searchPreviewPlayer?.isPlaying()) {
12216
+ searchPreviewPlayer.stop();
12217
+ } else {
12218
+ stop();
12219
+ previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12220
+ try {
12221
+ const midi = await fetchVerifiedMidi(song.file);
12222
+ const mml = buildPreviewMml(midi);
12223
+ playerContainer.innerHTML = "";
12224
+ const player = mountMmlPlayer(playerContainer, mml, {
12225
+ onPlayNote: options.onPlayNote,
12226
+ onPlayDrum: options.onPlayDrum,
12227
+ onResumeAudio: options.onResumeAudio,
12228
+ getAudioTime: options.getAudioTime,
12229
+ singingVoices: options.singingVoices,
12230
+ drumPatterns: options.drumPatterns,
12231
+ volume: masterVolume,
12232
+ _skipInfoModals: true,
12233
+ onStop: () => {
12234
+ if (searchPreviewBtn === previewBtn) {
12235
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12236
+ previewBtn.classList.remove("dtm-btn--danger");
12237
+ previewBtn.classList.add("dtm-btn--ghost");
12238
+ }
12239
+ }
12240
+ });
12241
+ searchPreviewPlayer = player;
12242
+ searchPreviewBtn = previewBtn;
12243
+ player.play();
12244
+ previewBtn.textContent = "\u25A0 \u505C\u6B62";
12245
+ previewBtn.classList.remove("dtm-btn--ghost");
12246
+ previewBtn.classList.add("dtm-btn--danger");
12247
+ } catch (e) {
12248
+ console.error("[dtm] MIDI preview failed", e);
12249
+ previewBtn.textContent = "\u5931\u6557";
12250
+ }
12251
+ }
12152
12252
  } else {
12253
+ collapseSearchPreview();
12153
12254
  stop();
12154
12255
  previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12155
12256
  try {
@@ -12184,31 +12285,169 @@ var mountDAW = (target, options = {}) => {
12184
12285
  previewBtn.textContent = "\u5931\u6557";
12185
12286
  }
12186
12287
  }
12288
+ });
12289
+ loadBtn.addEventListener("click", async () => {
12290
+ loadBtn.disabled = true;
12291
+ loadBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12292
+ try {
12293
+ const pending = await fetchVerifiedMidi(song.file);
12294
+ const analysis = analyzeMidiTracks(pending);
12295
+ const sel = analysis.filter((a) => a.selected).map((a) => a.index);
12296
+ collapseSearchPreview();
12297
+ overlayDuring(() => applyMidiSelection(pending, sel));
12298
+ refs.modalOverlay.setAttribute("hidden", "");
12299
+ } catch (e) {
12300
+ console.error("[dtm] MIDI fetch failed", e);
12301
+ loadBtn.textContent = "\u5931\u6557";
12302
+ } finally {
12303
+ loadBtn.disabled = false;
12304
+ }
12305
+ });
12306
+ row.appendChild(label);
12307
+ row.appendChild(previewBtn);
12308
+ row.appendChild(loadBtn);
12309
+ box.appendChild(row);
12310
+ box.appendChild(playerContainer);
12311
+ container.appendChild(box);
12312
+ }
12313
+ };
12314
+ renderMmlResults(results);
12315
+ mmlSearchCache = {
12316
+ query: q2,
12317
+ count: songs.length,
12318
+ renderResults: renderMmlResults
12319
+ };
12320
+ } catch (e) {
12321
+ console.error("[dtm] MIDI search failed", e);
12322
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F</span>';
12323
+ } finally {
12324
+ searchBtn.disabled = false;
12325
+ searchBtn.textContent = "\u691C\u7D22";
12326
+ }
12327
+ };
12328
+ searchBtn.addEventListener("click", () => void runSearch());
12329
+ input.addEventListener("keydown", (e) => {
12330
+ if (e.key === "Enter") void runSearch();
12331
+ });
12332
+ input.focus();
12333
+ });
12334
+ };
12335
+ const openChordSearchModal = (chordTextArea) => {
12336
+ if (!midiSearchClient) return;
12337
+ const active = trackStates.find((t) => t.config.id === activeTrackId);
12338
+ if (!active) return;
12339
+ showModal(
12340
+ "\u30B3\u30FC\u30C9\u9032\u884C\u691C\u7D22",
12341
+ `
12342
+ <div class="dtm-row" style="flex-wrap:nowrap">
12343
+ <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">
12344
+ <button class="dtm-btn dtm-btn--primary" data-dtm="modal-chord-search-btn" style="flex-shrink:0">\u691C\u7D22</button>
12345
+ </div>
12346
+ <div data-dtm="modal-chord-search-status" style="font-size:11px;color:rgba(255,255,255,0.5);min-height:1.4em"></div>
12347
+ <div class="dtm-row" data-dtm="modal-chord-search-results" style="flex-direction:column;align-items:stretch;gap:4px"></div>
12348
+ `
12349
+ );
12350
+ const input = refs.modalBody.querySelector(
12351
+ '[data-dtm="modal-chord-search-input"]'
12352
+ );
12353
+ const searchBtn = refs.modalBody.querySelector(
12354
+ '[data-dtm="modal-chord-search-btn"]'
12355
+ );
12356
+ const statusEl = refs.modalBody.querySelector(
12357
+ '[data-dtm="modal-chord-search-status"]'
12358
+ );
12359
+ const results = refs.modalBody.querySelector(
12360
+ '[data-dtm="modal-chord-search-results"]'
12361
+ );
12362
+ if (chordSearchCache) {
12363
+ input.value = chordSearchCache.query;
12364
+ statusEl.textContent = `\u300C${chordSearchCache.query}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${chordSearchCache.count}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12365
+ chordSearchCache.renderResults(results);
12366
+ }
12367
+ const runSearch = async () => {
12368
+ const q2 = input.value.trim();
12369
+ if (!q2) return;
12370
+ collapseSearchPreview();
12371
+ searchBtn.disabled = true;
12372
+ searchBtn.textContent = "\u691C\u7D22\u4E2D...";
12373
+ results.innerHTML = "";
12374
+ statusEl.textContent = "";
12375
+ try {
12376
+ const scores = await midiSearchClient.searchRechord(q2);
12377
+ if (scores.length === 0) {
12378
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u30000\u4EF6\u306E\u30D2\u30C3\u30C8`;
12379
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12380
+ chordSearchCache = {
12381
+ query: q2,
12382
+ count: 0,
12383
+ renderResults: (c) => {
12384
+ c.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F</span>';
12385
+ }
12386
+ };
12387
+ return;
12388
+ }
12389
+ statusEl.textContent = `\u300C${q2}\u300D\u306E\u691C\u7D22\u7D50\u679C\u3000${scores.length}\u4EF6\u306E\u30D2\u30C3\u30C8`;
12390
+ const renderChordResults = (container) => {
12391
+ container.innerHTML = "";
12392
+ for (const score of scores) {
12393
+ const box = document.createElement("div");
12394
+ box.className = "dtm-modal-sample-box";
12395
+ const row = document.createElement("div");
12396
+ row.className = "dtm-row";
12397
+ row.style.cssText = "flex-wrap:nowrap;gap:4px;padding:2px 0";
12398
+ const label = document.createElement("span");
12399
+ label.className = "dtm-label";
12400
+ label.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
12401
+ label.textContent = `${score.title}${score.user?.screen_name ? ` - ${score.user.screen_name}` : ""}`;
12402
+ const previewBtn = document.createElement("button");
12403
+ previewBtn.className = "dtm-btn dtm-btn--ghost";
12404
+ previewBtn.style.cssText = "flex-shrink:0";
12405
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12406
+ const loadBtn = document.createElement("button");
12407
+ loadBtn.className = "dtm-btn dtm-btn--primary";
12408
+ loadBtn.style.cssText = "flex-shrink:0";
12409
+ loadBtn.textContent = "\u8AAD\u307F\u8FBC\u307F";
12410
+ const playerContainer = document.createElement("div");
12411
+ playerContainer.className = "dtm-modal-sample-player-container";
12412
+ previewBtn.addEventListener("click", async () => {
12413
+ if (searchPreviewBtn === previewBtn) {
12414
+ if (searchPreviewPlayer?.isPlaying()) {
12415
+ searchPreviewPlayer.stop();
12416
+ } else {
12417
+ stop();
12418
+ previewBtn.textContent = "\u518D\u751F\u4E2D...";
12419
+ try {
12420
+ searchPreviewPlayer?.play();
12421
+ previewBtn.textContent = "\u25A0 \u505C\u6B62";
12422
+ previewBtn.classList.remove("dtm-btn--ghost");
12423
+ previewBtn.classList.add("dtm-btn--danger");
12424
+ } catch (e) {
12425
+ console.error("[dtm] Chord preview play failed", e);
12426
+ previewBtn.textContent = "\u5931\u6557";
12427
+ }
12428
+ }
12187
12429
  } else {
12188
12430
  collapseSearchPreview();
12189
12431
  stop();
12190
12432
  previewBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12191
12433
  try {
12192
- const midi = await fetchVerifiedMidi(song.file);
12193
- const mml = buildPreviewMml(midi);
12194
12434
  playerContainer.innerHTML = "";
12195
- const player = mountMmlPlayer(playerContainer, mml, {
12196
- onPlayNote: options.onPlayNote,
12197
- onPlayDrum: options.onPlayDrum,
12198
- onResumeAudio: options.onResumeAudio,
12199
- getAudioTime: options.getAudioTime,
12200
- singingVoices: options.singingVoices,
12201
- drumPatterns: options.drumPatterns,
12202
- volume: masterVolume,
12203
- _skipInfoModals: true,
12204
- onStop: () => {
12205
- if (searchPreviewBtn === previewBtn) {
12206
- previewBtn.textContent = "\u25B6 \u8A66\u8074";
12207
- previewBtn.classList.remove("dtm-btn--danger");
12208
- previewBtn.classList.add("dtm-btn--ghost");
12435
+ const player = mountChordPlayer(
12436
+ playerContainer,
12437
+ score.content,
12438
+ {
12439
+ volume: masterVolume,
12440
+ bpm: score.bpm ?? bpm ?? 120,
12441
+ _skipInfoModals: true,
12442
+ onStop: () => {
12443
+ if (searchPreviewBtn === previewBtn) {
12444
+ previewBtn.textContent = "\u25B6 \u8A66\u8074";
12445
+ previewBtn.classList.remove("dtm-btn--danger");
12446
+ previewBtn.classList.add("dtm-btn--ghost");
12447
+ }
12209
12448
  }
12210
12449
  }
12211
- });
12450
+ );
12212
12451
  searchPreviewPlayer = player;
12213
12452
  searchPreviewBtn = previewBtn;
12214
12453
  player.play();
@@ -12216,49 +12455,48 @@ var mountDAW = (target, options = {}) => {
12216
12455
  previewBtn.classList.remove("dtm-btn--ghost");
12217
12456
  previewBtn.classList.add("dtm-btn--danger");
12218
12457
  } catch (e) {
12219
- console.error("[dtm] MIDI preview failed", e);
12458
+ console.error("[dtm] Chord preview mount failed", e);
12220
12459
  previewBtn.textContent = "\u5931\u6557";
12221
12460
  }
12222
12461
  }
12223
12462
  });
12224
- loadBtn.addEventListener("click", async () => {
12225
- loadBtn.disabled = true;
12226
- loadBtn.textContent = "\u8AAD\u8FBC\u4E2D...";
12227
- try {
12228
- const pending = await fetchVerifiedMidi(song.file);
12229
- const analysis = analyzeMidiTracks(pending);
12230
- const sel = analysis.filter((a) => a.selected).map((a) => a.index);
12231
- collapseSearchPreview();
12232
- overlayDuring(() => applyMidiSelection(pending, sel));
12233
- refs.modalOverlay.setAttribute("hidden", "");
12234
- } catch (e) {
12235
- console.error("[dtm] MIDI fetch failed", e);
12236
- loadBtn.textContent = "\u5931\u6557";
12237
- } finally {
12238
- loadBtn.disabled = false;
12463
+ loadBtn.addEventListener("click", () => {
12464
+ collapseSearchPreview();
12465
+ chordTextArea.value = score.content;
12466
+ active.savedChordInput = score.content;
12467
+ if (score.bpm) {
12468
+ setBpm(score.bpm);
12239
12469
  }
12470
+ applyChord();
12471
+ refs.modalOverlay.setAttribute("hidden", "");
12240
12472
  });
12241
12473
  row.appendChild(label);
12242
12474
  row.appendChild(previewBtn);
12243
12475
  row.appendChild(loadBtn);
12244
12476
  box.appendChild(row);
12245
12477
  box.appendChild(playerContainer);
12246
- results.appendChild(box);
12478
+ container.appendChild(box);
12247
12479
  }
12248
- } catch (e) {
12249
- console.error("[dtm] MIDI search failed", e);
12250
- results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F</span>';
12251
- } finally {
12252
- searchBtn.disabled = false;
12253
- searchBtn.textContent = "\u691C\u7D22";
12254
- }
12255
- };
12256
- searchBtn.addEventListener("click", () => void runSearch());
12257
- input.addEventListener("keydown", (e) => {
12258
- if (e.key === "Enter") void runSearch();
12259
- });
12260
- input.focus();
12480
+ };
12481
+ renderChordResults(results);
12482
+ chordSearchCache = {
12483
+ query: q2,
12484
+ count: scores.length,
12485
+ renderResults: renderChordResults
12486
+ };
12487
+ } catch (e) {
12488
+ console.error("[dtm] Chord search failed", e);
12489
+ results.innerHTML = '<span class="dtm-label" style="color:var(--dtm-warn)">\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F</span>';
12490
+ } finally {
12491
+ searchBtn.disabled = false;
12492
+ searchBtn.textContent = "\u691C\u7D22";
12493
+ }
12494
+ };
12495
+ searchBtn.addEventListener("click", () => void runSearch());
12496
+ input.addEventListener("keydown", (e) => {
12497
+ if (e.key === "Enter") void runSearch();
12261
12498
  });
12499
+ input.focus();
12262
12500
  };
12263
12501
  const onKeyDown = (e) => {
12264
12502
  if (!(e.ctrlKey || e.metaKey)) return;
@@ -12420,6 +12658,21 @@ var mountDAW = (target, options = {}) => {
12420
12658
  getCurrentPlayStep,
12421
12659
  forcePauseAt,
12422
12660
  setLoading,
12661
+ setMasterVolume: (volume) => {
12662
+ masterVolume = clamp3(volume, 0, 100);
12663
+ refs.masterVolume.value = String(masterVolume);
12664
+ refs.masterVolumeLabel.textContent = `${masterVolume}%`;
12665
+ },
12666
+ setVolume: (volume) => {
12667
+ masterVolume = clamp3(volume, 0, 100);
12668
+ refs.masterVolume.value = String(masterVolume);
12669
+ refs.masterVolumeLabel.textContent = `${masterVolume}%`;
12670
+ },
12671
+ setDrumVolume: (volume) => {
12672
+ drumVolume = clamp3(volume, 0, 100);
12673
+ refs.drumVolume.value = String(drumVolume);
12674
+ refs.drumVolumeLabel.textContent = `${drumVolume}%`;
12675
+ },
12423
12676
  applyPatch: (trackId, added, removed) => {
12424
12677
  const track = trackStates.find((t) => t.config.id === trackId);
12425
12678
  if (!track) return;