@onjmin/dtm 0.1.25 → 0.1.27

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
@@ -7,34 +7,6 @@ function createAudioContext() {
7
7
  drumGainNode.connect(audioCtx.destination);
8
8
  return { audioCtx, gainNode, drumGainNode };
9
9
  }
10
- function setupRecorder(audioCtx, gainNode, drumGainNode) {
11
- let isRecording = false;
12
- let recordedData = [[], []];
13
- const recorderProcessor = audioCtx.createScriptProcessor(4096, 2, 2);
14
- recorderProcessor.onaudioprocess = (e) => {
15
- if (!isRecording) return;
16
- const left = e.inputBuffer.getChannelData(0);
17
- const right = e.inputBuffer.getChannelData(1);
18
- recordedData[0].push(left.slice());
19
- recordedData[1].push(right.slice());
20
- };
21
- gainNode.connect(recorderProcessor);
22
- drumGainNode.connect(recorderProcessor);
23
- recorderProcessor.connect(audioCtx.destination);
24
- return {
25
- startRecording: () => {
26
- isRecording = true;
27
- },
28
- stopRecording: () => {
29
- isRecording = false;
30
- },
31
- getRecordedData: () => recordedData,
32
- isRecording: () => isRecording,
33
- clearRecordedData: () => {
34
- recordedData = [[], []];
35
- }
36
- };
37
- }
38
10
  async function fetchSoundFontList(ttl) {
39
11
  const res = await fetch(`https://rpgen3.github.io/soundfont/list/${ttl}.txt`);
40
12
  const str = await res.text();
@@ -1131,8 +1103,9 @@ var buildUI = (target, options) => {
1131
1103
  target.innerHTML = `
1132
1104
  <div class="dtm-daw" data-dtm="root">
1133
1105
  <div class="dtm-topbar" data-dtm="transport">
1106
+ <button class="dtm-iconbtn" data-dtm="prev-bar" title="1\u5C0F\u7BC0\u524D">${icon("chevronLeft")}</button>
1134
1107
  <button class="dtm-play" data-dtm="play" disabled>${icon("play")}<span>\u8A66\u8074</span></button>
1135
- <button class="dtm-iconbtn dtm-rec" data-dtm="rec" title="\u9332\u97F3">${icon("record")}</button>
1108
+ <button class="dtm-iconbtn" data-dtm="next-bar" title="1\u5C0F\u7BC0\u5F8C">${icon("chevronRight")}</button>
1136
1109
  <label class="dtm-toggle"><input type="checkbox" data-dtm="solo"><span>\u30BD\u30ED</span></label>
1137
1110
  <span class="dtm-topbar-loading dtm-blink" data-dtm="topbar-loading">... LOADING ...</span>
1138
1111
  <span class="dtm-grow"></span>
@@ -1297,10 +1270,12 @@ var buildUI = (target, options) => {
1297
1270
  </div>
1298
1271
  <div class="dtm-output dtm-hidden" data-dtm="output-container">
1299
1272
  <p class="dtm-label" data-dtm="output-status"></p>
1273
+ <div class="dtm-output-label">\u6539\u884C\u3042\u308A\u7248</div>
1300
1274
  <div class="dtm-output-row">
1301
1275
  <pre><code data-dtm="output-full"></code></pre>
1302
1276
  <button class="dtm-btn dtm-btn--primary dtm-btn--icon" data-dtm="copy-full" title="\u30B3\u30D4\u30FC">${icon("copy")}</button>
1303
1277
  </div>
1278
+ <div class="dtm-output-label">\uFF11\u884C\u7248</div>
1304
1279
  <div class="dtm-output-row">
1305
1280
  <pre><code data-dtm="output-mini"></code></pre>
1306
1281
  <button class="dtm-btn dtm-btn--primary dtm-btn--icon" data-dtm="copy-mini" title="\u30B3\u30D4\u30FC">${icon("copy")}</button>
@@ -1328,7 +1303,8 @@ var buildUI = (target, options) => {
1328
1303
  topbar: sel("transport"),
1329
1304
  topbarLoading: sel("topbar-loading"),
1330
1305
  playBtn: sel("play"),
1331
- recBtn: sel("rec"),
1306
+ prevBarBtn: sel("prev-bar"),
1307
+ nextBarBtn: sel("next-bar"),
1332
1308
  soloCheckbox: sel("solo"),
1333
1309
  toolPen: sel("tool-pen"),
1334
1310
  toolSelect: sel("tool-select"),
@@ -2633,7 +2609,7 @@ var createKoeVoice = async (ctx, destination, options) => {
2633
2609
  return model;
2634
2610
  };
2635
2611
  var PREWARM_NOTES = 3;
2636
- var STREAM_LOOKAHEAD_SEC = 1.5;
2612
+ var STREAM_LOOKAHEAD_SEC = 6;
2637
2613
  var STREAM_POLL_MS = 100;
2638
2614
  var FALLBACK_MODEL = "klatt";
2639
2615
  var createSingingVoices = (ctx, destination, options = {}) => {
@@ -2697,7 +2673,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2697
2673
  if (!m?.renderToCache) continue;
2698
2674
  let n = 0;
2699
2675
  forEachSungNote(track, (note, prevVowel) => {
2700
- if (n >= count) return;
2676
+ if (n >= count && note.startSec >= STREAM_LOOKAHEAD_SEC) return;
2701
2677
  n++;
2702
2678
  promises.push(
2703
2679
  m.renderToCache?.(
@@ -2730,14 +2706,18 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2730
2706
  if (opts?.isAudible && !opts.isAudible(track)) continue;
2731
2707
  const t0 = anchorTime + note.startSec;
2732
2708
  if (model.renderToCache && model.scheduleCached) {
2733
- const key = await model.renderToCache(
2734
- note.syllable,
2735
- prevVowel,
2736
- note.pitch,
2737
- note.durationSec * 1e3
2738
- );
2739
- if (session !== streamSession) return;
2740
- if (key) model.scheduleCached(key, t0, peak, track.pan);
2709
+ const renderToCache = model.renderToCache;
2710
+ const scheduleCached = model.scheduleCached;
2711
+ void (async () => {
2712
+ const key = await renderToCache(
2713
+ note.syllable,
2714
+ prevVowel,
2715
+ note.pitch,
2716
+ note.durationSec * 1e3
2717
+ );
2718
+ if (session !== streamSession) return;
2719
+ if (key) scheduleCached(key, t0, peak, track.pan);
2720
+ })();
2741
2721
  } else {
2742
2722
  const when = t0 - ctx.currentTime;
2743
2723
  model(note.syllable, {
@@ -4146,7 +4126,7 @@ var PITCH_MAP2 = {
4146
4126
  b: 11
4147
4127
  };
4148
4128
  var clamp2 = (value, lo, hi) => Math.min(hi, Math.max(lo, value));
4149
- var META_DIRECTIVE = /#(inst|drum|volume|mode)=([\w-]+)/gi;
4129
+ var META_DIRECTIVE = /#(inst|drum|volume|drumvolume|mode)=([\w-]+)/gi;
4150
4130
  var parseMmlMeta = (mml) => {
4151
4131
  const meta = {};
4152
4132
  for (const m of mml.matchAll(META_DIRECTIVE)) {
@@ -4156,6 +4136,9 @@ var parseMmlMeta = (mml) => {
4156
4136
  else if (key === "volume") {
4157
4137
  const v = Number.parseInt(m[2], 10);
4158
4138
  if (!Number.isNaN(v)) meta.volume = v;
4139
+ } else if (key === "drumvolume") {
4140
+ const dv = Number.parseInt(m[2], 10);
4141
+ if (!Number.isNaN(dv)) meta.drumVolume = dv;
4159
4142
  } else if (key === "mode") {
4160
4143
  if (m[2] === "simple" || m[2] === "advanced") {
4161
4144
  meta.mode = m[2];
@@ -4170,6 +4153,8 @@ var formatMmlMeta = (meta) => {
4170
4153
  if (meta.instrument) parts.push(`#inst=${meta.instrument}`);
4171
4154
  if (meta.drum) parts.push(`#drum=${meta.drum}`);
4172
4155
  if (meta.volume !== void 0) parts.push(`#volume=${meta.volume}`);
4156
+ if (meta.drumVolume !== void 0)
4157
+ parts.push(`#drumvolume=${meta.drumVolume}`);
4173
4158
  if (meta.mode) parts.push(`#mode=${meta.mode}`);
4174
4159
  return parts.join(" ");
4175
4160
  };
@@ -4891,7 +4876,7 @@ var DAW_CSS = `
4891
4876
  top: 0;
4892
4877
  z-index: 20;
4893
4878
  display: flex;
4894
- flex-wrap: wrap;
4879
+ flex-wrap: nowrap;
4895
4880
  align-items: center;
4896
4881
  gap: var(--dtm-gap);
4897
4882
  padding: 6px;
@@ -4901,6 +4886,17 @@ var DAW_CSS = `
4901
4886
  inset 0 0 0 2px var(--c-black),
4902
4887
  0 0 0 2px var(--dtm-success),
4903
4888
  4px 4px 0 var(--c-black);
4889
+ overflow-x: auto;
4890
+ scrollbar-width: none;
4891
+ }
4892
+ .dtm-topbar::-webkit-scrollbar {
4893
+ display: none;
4894
+ }
4895
+ .dtm-topbar > * {
4896
+ flex-shrink: 0;
4897
+ }
4898
+ .dtm-topbar > .dtm-grow {
4899
+ flex-shrink: 1;
4904
4900
  }
4905
4901
 
4906
4902
  /* PLAY\u30DC\u30BF\u30F3 \u2014 \u30B2\u30FC\u30E0\u306E\u300C\u6C7A\u5B9A\u30DC\u30BF\u30F3\u300D\u7684\u5B58\u5728\u611F */
@@ -5251,6 +5247,15 @@ var DAW_CSS = `
5251
5247
  line-height: 1.8;
5252
5248
  color: var(--dtm-success);
5253
5249
  }
5250
+ .dtm-output-label {
5251
+ font-size: 11px;
5252
+ color: var(--dtm-muted);
5253
+ font-family: var(--dtm-font);
5254
+ margin-top: 10px;
5255
+ }
5256
+ .dtm-output-label:first-of-type {
5257
+ margin-top: 0;
5258
+ }
5254
5259
  .dtm-output-row { display: flex; gap: 8px; align-items: flex-start; margin-top: 6px; }
5255
5260
  .dtm-output-row pre { flex: 1; }
5256
5261
 
@@ -6094,6 +6099,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6094
6099
  const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
6095
6100
  const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
6096
6101
  const trackVolume = meta.volume ?? options.volume ?? 100;
6102
+ const drumVolume = meta.drumVolume ?? 80;
6097
6103
  const colors = options.trackColors ?? DEFAULT_TRACK_COLORS;
6098
6104
  const useSynth = options.synth ?? !options.onPlayNote;
6099
6105
  const secondsPerStep = 60 / bpm / STEPS_PER_BEAT3;
@@ -6710,9 +6716,9 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6710
6716
  }
6711
6717
  target.appendChild(root);
6712
6718
  let consentOverlayEl = null;
6713
- const checkConsentAndShow = () => {
6719
+ const checkConsentAndShow = (onAgree) => {
6714
6720
  try {
6715
- if (options.skipConsent) return;
6721
+ if (options.skipConsent) return false;
6716
6722
  const unagreed = termsModels.filter((model) => {
6717
6723
  if (agreedModelsInSession.has(model)) return false;
6718
6724
  try {
@@ -6726,7 +6732,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6726
6732
  return true;
6727
6733
  }
6728
6734
  });
6729
- if (unagreed.length === 0) return;
6735
+ if (unagreed.length === 0) return false;
6730
6736
  const consentOverlay = doc.createElement("div");
6731
6737
  consentOverlay.className = "dtm-consent-overlay";
6732
6738
  const modal = doc.createElement("div");
@@ -6769,17 +6775,19 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6769
6775
  }
6770
6776
  consentOverlay.remove();
6771
6777
  consentOverlayEl = null;
6778
+ if (onAgree) onAgree();
6772
6779
  };
6773
6780
  footer.appendChild(btn);
6774
6781
  modal.append(header, body2, footer);
6775
6782
  consentOverlay.appendChild(modal);
6776
6783
  doc.body.appendChild(consentOverlay);
6777
6784
  consentOverlayEl = consentOverlay;
6785
+ return true;
6778
6786
  } catch (err2) {
6779
6787
  console.error("[dtm-player] Error in checkConsentAndShow:", err2);
6788
+ return false;
6780
6789
  }
6781
6790
  };
6782
- checkConsentAndShow();
6783
6791
  const autoScroll = (lane, el) => {
6784
6792
  if (el.offsetWidth === 0 || lane.clientWidth === 0) return;
6785
6793
  const elementCenter = el.offsetLeft + el.offsetWidth / 2;
@@ -6838,7 +6846,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6838
6846
  if (useSynth) ensureSynth().playNote(e);
6839
6847
  },
6840
6848
  onPlayDrum: (e) => {
6841
- const velocity = e.velocity * (trackVolume / 100);
6849
+ const velocity = e.velocity * (drumVolume / 100) * (trackVolume / 100);
6842
6850
  options.onPlayDrum?.({ ...e, velocity });
6843
6851
  if (useSynth) ensureSynth().playDrum({ ...e, velocity });
6844
6852
  },
@@ -6912,6 +6920,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6912
6920
  };
6913
6921
  const play = () => {
6914
6922
  if (playing || trackIndices.length === 0) return;
6923
+ if (checkConsentAndShow(() => play())) return;
6915
6924
  if (activePlayer && activePlayer !== instance) activePlayer.stop();
6916
6925
  activePlayer = instance;
6917
6926
  setPlayingUI(true);
@@ -7940,7 +7949,7 @@ var mountDAW = (target, options = {}) => {
7940
7949
  };
7941
7950
  const updateTransport = () => {
7942
7951
  const playing = playbackState === "playing";
7943
- const label = playing ? "\u4E00\u6642\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7952
+ const label = playing ? "\u505C\u6B62" : playbackState === "paused" ? "\u518D\u958B" : "\u8A66\u8074";
7944
7953
  refs.playBtn.innerHTML = `${icon(playing ? "pause" : "play")}<span>${label}</span>`;
7945
7954
  refs.playBtn.classList.toggle("dtm-play--stop", playing);
7946
7955
  };
@@ -8208,6 +8217,7 @@ var mountDAW = (target, options = {}) => {
8208
8217
  instrument: currentInstrument || void 0,
8209
8218
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
8210
8219
  volume: masterVolume,
8220
+ drumVolume,
8211
8221
  mode
8212
8222
  });
8213
8223
  if (refs.decomposeChordToggle.checked) {
@@ -8359,6 +8369,11 @@ var mountDAW = (target, options = {}) => {
8359
8369
  refs.masterVolume.value = String(meta.volume);
8360
8370
  refs.masterVolumeLabel.textContent = `${meta.volume}%`;
8361
8371
  }
8372
+ if (meta.drumVolume !== void 0) {
8373
+ drumVolume = meta.drumVolume;
8374
+ refs.drumVolume.value = String(meta.drumVolume);
8375
+ refs.drumVolumeLabel.textContent = `${meta.drumVolume}%`;
8376
+ }
8362
8377
  for (const t of trackStates) {
8363
8378
  t.lyrics = "";
8364
8379
  t.lyricModel = "";
@@ -8512,8 +8527,17 @@ var mountDAW = (target, options = {}) => {
8512
8527
  const wireEvents = () => {
8513
8528
  refs.playBtn.addEventListener("click", togglePlay);
8514
8529
  refs.playBtn.disabled = false;
8515
- refs.recBtn.addEventListener("click", () => options.onToggleRecord?.());
8516
- refs.recBtn.style.display = options.onToggleRecord ? "" : "none";
8530
+ refs.prevBarBtn.addEventListener("click", () => {
8531
+ const targetStep = Math.max(
8532
+ 0,
8533
+ Math.floor((getCurrentPlayStep() - 1) / renderConfig.stepsPerBar) * renderConfig.stepsPerBar
8534
+ );
8535
+ jumpTo(targetStep);
8536
+ });
8537
+ refs.nextBarBtn.addEventListener("click", () => {
8538
+ const targetStep = Math.floor(getCurrentPlayStep() / renderConfig.stepsPerBar + 1) * renderConfig.stepsPerBar;
8539
+ jumpTo(targetStep);
8540
+ });
8517
8541
  refs.soloCheckbox.addEventListener("change", () => {
8518
8542
  isSolo = refs.soloCheckbox.checked;
8519
8543
  });
@@ -8876,6 +8900,20 @@ var mountDAW = (target, options = {}) => {
8876
8900
  if (playbackState === "paused") return pausedPlayStep;
8877
8901
  return playStartStep;
8878
8902
  };
8903
+ const jumpTo = async (step) => {
8904
+ const wasPlaying = playbackState === "playing";
8905
+ if (wasPlaying) {
8906
+ sequencer.stop();
8907
+ options.singingVoices?.stopStream();
8908
+ playStartStep = step;
8909
+ pausedPlayStep = step;
8910
+ currentPlayStep = step;
8911
+ playbackState = "paused";
8912
+ await play();
8913
+ } else {
8914
+ forcePauseAt(step);
8915
+ }
8916
+ };
8879
8917
  const forcePauseAt = (step) => {
8880
8918
  playStartStep = step;
8881
8919
  pausedPlayStep = step;
@@ -8950,6 +8988,7 @@ var playMML = (mml, options = {}) => {
8950
8988
  const drumPatternDict = options.drumPatterns ?? DRUM_PATTERNS;
8951
8989
  const drumPattern = meta.drum ? drumPatternDict[meta.drum] ?? null : null;
8952
8990
  let masterVolume = meta.volume ?? options.volume ?? 100;
8991
+ let drumVolume = meta.drumVolume ?? 80;
8953
8992
  const trackIndices = [...new Set(placements.map((p) => p.trackIndex))].sort(
8954
8993
  (a, b) => a - b
8955
8994
  );
@@ -8986,7 +9025,7 @@ var playMML = (mml, options = {}) => {
8986
9025
  synth?.playNote(e);
8987
9026
  },
8988
9027
  onPlayDrum: (e) => {
8989
- const velocity = e.velocity * (masterVolume / 100);
9028
+ const velocity = e.velocity * (drumVolume / 100) * (masterVolume / 100);
8990
9029
  options.onPlayDrum?.({ ...e, velocity });
8991
9030
  synth?.playDrum({ ...e, velocity });
8992
9031
  },
@@ -9544,7 +9583,6 @@ var importFrom = async (url2, name) => {
9544
9583
  var createDtmStudio = async (options = {}) => {
9545
9584
  const cdn = { ...DEFAULT_CDN, ...options.cdn };
9546
9585
  const features = {
9547
- recorder: true,
9548
9586
  midi: true,
9549
9587
  chord: true,
9550
9588
  presetUI: true,
@@ -9583,68 +9621,6 @@ var createDtmStudio = async (options = {}) => {
9583
9621
  const singingVoices = createSingingVoices(audioCtx, masterGain, {
9584
9622
  voiceWorkerUrl
9585
9623
  });
9586
- const recorder = features.recorder ? setupRecorder(audioCtx, masterGain, drumGain) : null;
9587
- const downloadWav = () => {
9588
- if (!recorder) return;
9589
- const recordedData = recorder.getRecordedData();
9590
- const ch = recordedData.length;
9591
- const len = recordedData[0].length;
9592
- if (len === 0) return;
9593
- const bufSize = recordedData[0][0].length;
9594
- const wave = new Float32Array(ch * len * bufSize);
9595
- let idx = 0;
9596
- for (let i = 0; i < len; i++)
9597
- for (let j = 0; j < bufSize; j++)
9598
- for (let k = 0; k < ch; k++) wave[idx++] = recordedData[k][i][j];
9599
- const sampleRate = audioCtx.sampleRate;
9600
- const channels = 2;
9601
- const bitRate = 16;
9602
- const step = bitRate / 8;
9603
- const blockSize = channels * step;
9604
- const byteLen = wave.length * step;
9605
- const view = new DataView(new ArrayBuffer(44 + byteLen));
9606
- const ws = (off2, s) => {
9607
- for (let i = 0; i < s.length; i++)
9608
- view.setUint8(off2 + i, s.charCodeAt(i));
9609
- };
9610
- ws(0, "RIFF");
9611
- view.setUint32(4, 32 + byteLen, true);
9612
- ws(8, "WAVE");
9613
- ws(12, "fmt ");
9614
- view.setUint32(16, 16, true);
9615
- view.setUint16(20, 1, true);
9616
- view.setUint16(22, channels, true);
9617
- view.setUint32(24, sampleRate, true);
9618
- view.setUint32(28, sampleRate * blockSize, true);
9619
- view.setUint16(32, blockSize, true);
9620
- view.setUint16(34, bitRate, true);
9621
- ws(36, "data");
9622
- view.setUint32(40, byteLen, true);
9623
- const clamp4 = (n, a2, b) => Math.max(a2, Math.min(b, n));
9624
- let off = 44;
9625
- for (let i = 0; i < wave.length; i++, off += step)
9626
- view.setInt16(
9627
- off,
9628
- clamp4(Math.round(wave[i] * 32768), -32768, 32767),
9629
- true
9630
- );
9631
- const blob2 = new Blob([view], { type: "audio/wav" });
9632
- const url2 = URL.createObjectURL(blob2);
9633
- const a = document.createElement("a");
9634
- a.href = url2;
9635
- a.download = "record.wav";
9636
- a.click();
9637
- URL.revokeObjectURL(url2);
9638
- };
9639
- const onToggleRecord = recorder ? () => {
9640
- if (recorder.isRecording()) {
9641
- recorder.stopRecording();
9642
- downloadWav();
9643
- } else {
9644
- recorder.clearRecordedData();
9645
- recorder.startRecording();
9646
- }
9647
- } : void 0;
9648
9624
  const listReady = new Promise((resolve) => {
9649
9625
  SoundFont_list.init();
9650
9626
  SoundFont_list.onload(() => resolve());
@@ -9851,7 +9827,6 @@ var createDtmStudio = async (options = {}) => {
9851
9827
  onPlayDrum: playDrum,
9852
9828
  singingVoices,
9853
9829
  parseMidi,
9854
- onToggleRecord,
9855
9830
  onInstrumentChange: handleInstrumentChange,
9856
9831
  ...dawOverrides
9857
9832
  };
@@ -10142,7 +10117,6 @@ export {
10142
10117
  playMML,
10143
10118
  playSingingMML,
10144
10119
  setDrawOffset,
10145
- setupRecorder,
10146
10120
  shiftNotes,
10147
10121
  stripLyrics,
10148
10122
  stripMmlMeta,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "onjmin",
3
3
  "license": "MIT",
4
4
  "name": "@onjmin/dtm",
5
- "version": "0.1.25",
5
+ "version": "0.1.27",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {