@onjmin/dtm 0.1.59 → 0.1.61

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.d.mts CHANGED
@@ -406,6 +406,13 @@ type SingingVoices = {
406
406
  stopStream: () => void;
407
407
  /** ストリーム停止+各モデルの内部状態を初期化する。 */
408
408
  reset: () => void;
409
+ /**
410
+ * マスタ音量(0〜1のゲイン)をリアルタイムに反映する。
411
+ * 発音時にゲインを焼き込む方式だと再生中の音量変更が既にスケジュール済み・
412
+ * ストリーミング中の音符に効かないため、全モデル共通の GainNode を介して
413
+ * 常時ライブ反映できるようにする。
414
+ */
415
+ setVolume: (gain: number) => void;
409
416
  };
410
417
  /** {@link SingingVoices.warm} の既定先合成数(各トラック先頭からの音数)。 */
411
418
  declare const PREWARM_NOTES = 3;
package/dist/index.d.ts CHANGED
@@ -406,6 +406,13 @@ type SingingVoices = {
406
406
  stopStream: () => void;
407
407
  /** ストリーム停止+各モデルの内部状態を初期化する。 */
408
408
  reset: () => void;
409
+ /**
410
+ * マスタ音量(0〜1のゲイン)をリアルタイムに反映する。
411
+ * 発音時にゲインを焼き込む方式だと再生中の音量変更が既にスケジュール済み・
412
+ * ストリーミング中の音符に効かないため、全モデル共通の GainNode を介して
413
+ * 常時ライブ反映できるようにする。
414
+ */
415
+ setVolume: (gain: number) => void;
409
416
  };
410
417
  /** {@link SingingVoices.warm} の既定先合成数(各トラック先頭からの音数)。 */
411
418
  declare const PREWARM_NOTES = 3;
package/dist/index.js CHANGED
@@ -2676,8 +2676,10 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2676
2676
  for (const [k, v] of Object.entries(options.voicebanks ?? {}))
2677
2677
  catalog[k.toLowerCase()] = v;
2678
2678
  let streamSession = 0;
2679
+ const masterGain = ctx.createGain();
2680
+ masterGain.connect(destination);
2679
2681
  const loaded = /* @__PURE__ */ new Map([
2680
- [FALLBACK_MODEL, createKlattVoice(ctx, destination)]
2682
+ [FALLBACK_MODEL, createKlattVoice(ctx, masterGain)]
2681
2683
  ]);
2682
2684
  const loading = /* @__PURE__ */ new Map();
2683
2685
  const load2 = (model) => {
@@ -2693,7 +2695,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2693
2695
  // マニフェストだけ先読みし、音素PCMは歌う直前にオンデマンド取得する
2694
2696
  // (= 初回に .koe 全体をDLしない。モバイル初回ロードの待ちを解消)。
2695
2697
  // Blob/File が直接渡されたケース(ローカル読み込み)はそのまま BlobVoiceSource。
2696
- createKoeVoice(ctx, destination, {
2698
+ createKoeVoice(ctx, masterGain, {
2697
2699
  koe,
2698
2700
  worldlineScriptUrl: options.worldlineScriptUrl,
2699
2701
  lightweight: options.lightweight,
@@ -2830,13 +2832,17 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2830
2832
  stopStream();
2831
2833
  for (const v of loaded.values()) v.reset?.();
2832
2834
  };
2835
+ const setVolume = (gain) => {
2836
+ masterGain.gain.value = Math.max(0, gain);
2837
+ };
2833
2838
  return {
2834
2839
  loadModels,
2835
2840
  registerVoicebanks,
2836
2841
  warm,
2837
2842
  startStream,
2838
2843
  stopStream,
2839
- reset
2844
+ reset,
2845
+ setVolume
2840
2846
  };
2841
2847
  };
2842
2848
  var createVoiceRegistry = (models = {}, fallback = "klatt") => {
@@ -5605,6 +5611,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5605
5611
  if (!voices) {
5606
5612
  const ctx = ensureCtx();
5607
5613
  voices = createSingingVoices(ctx, ctx.destination);
5614
+ voices.setVolume(masterVolume / 100);
5608
5615
  }
5609
5616
  return voices;
5610
5617
  };
@@ -6350,6 +6357,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6350
6357
  };
6351
6358
  const setVolume = (volume) => {
6352
6359
  masterVolume = volume;
6360
+ peekVoices()?.setVolume(masterVolume / 100);
6353
6361
  };
6354
6362
  const buildStreamTracks = () => [...lyricTracks.entries()].map(([index, lt]) => {
6355
6363
  const seqTrack = seqTracks.find((t) => Number(t.id) === index);
@@ -6372,7 +6380,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6372
6380
  return {
6373
6381
  id: String(index),
6374
6382
  model: lt.model,
6375
- volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100) * (masterVolume / 100),
6383
+ volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
6376
6384
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
6377
6385
  notes
6378
6386
  };
@@ -6606,7 +6614,7 @@ var findZone = (ctx, fontName, zones, pitchs = []) => {
6606
6614
  for (let i = zones.length - 1; i >= 0; i--)
6607
6615
  for (const v of set) {
6608
6616
  const zone = zones[i];
6609
- if (v < zone.keyRangeLow || v > zone.keyRangeHigh + 1) continue;
6617
+ if (v < zone.keyRangeLow || v > zone.keyRangeHigh) continue;
6610
6618
  set.delete(v);
6611
6619
  map.set(v, { ...zone });
6612
6620
  }
@@ -6704,7 +6712,7 @@ var adjustZone = async (ctx, fontName, zone) => {
6704
6712
  const oldData = oldBuf.getChannelData(ch);
6705
6713
  const newData = newBuf.getChannelData(ch);
6706
6714
  for (let i = 0; i < attackLength; i++) {
6707
- const ratio = attackLength > 0 ? i / attackLength : 0;
6715
+ const ratio = attackLength > 1 ? i / (attackLength - 1) : 0;
6708
6716
  const m = 1 + (gainMultiplier - 1) * ratio;
6709
6717
  newData[i] = oldData[i] * m;
6710
6718
  }
@@ -9029,6 +9037,7 @@ var init = (mountTarget, width = 800, height = 450, config) => {
9029
9037
  gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
9030
9038
  gridCanvas.style.top = `${HEADER_HEIGHT}px`;
9031
9039
  gridCanvas.style.touchAction = "none";
9040
+ gridCanvas.style.userSelect = "none";
9032
9041
  const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
9033
9042
  if (!gridCtx) throw new Error("Failed to get 2D rendering context for grid.");
9034
9043
  g_grid_ctx = gridCtx;
@@ -10022,6 +10031,7 @@ var mountDAW = (target, options = {}) => {
10022
10031
  let zoomY = 100;
10023
10032
  let bpm = options.defaultBpm ?? DEFAULT_BPM;
10024
10033
  let masterVolume = options.masterVolume ?? 50;
10034
+ options.singingVoices?.setVolume(masterVolume / 100);
10025
10035
  let drumVolume = options.drumVolume ?? 80;
10026
10036
  let currentDrumPattern = refs.drumSelect.value;
10027
10037
  let currentInstrument = "";
@@ -10899,7 +10909,7 @@ var mountDAW = (target, options = {}) => {
10899
10909
  return {
10900
10910
  id: trackState?.config.id,
10901
10911
  model: lt.model,
10902
- volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (masterVolume / 100),
10912
+ volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
10903
10913
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
10904
10914
  notes
10905
10915
  };
@@ -11908,6 +11918,7 @@ var mountDAW = (target, options = {}) => {
11908
11918
  refs.masterVolume.addEventListener("input", () => {
11909
11919
  masterVolume = Number.parseInt(refs.masterVolume.value, 10) || 0;
11910
11920
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
11921
+ options.singingVoices?.setVolume(masterVolume / 100);
11911
11922
  });
11912
11923
  refs.drumSelect.addEventListener("change", () => {
11913
11924
  currentDrumPattern = refs.drumSelect.value;
@@ -12784,11 +12795,13 @@ var mountDAW = (target, options = {}) => {
12784
12795
  masterVolume = clamp3(volume, 0, 100);
12785
12796
  refs.masterVolume.value = String(masterVolume);
12786
12797
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
12798
+ options.singingVoices?.setVolume(masterVolume / 100);
12787
12799
  },
12788
12800
  setVolume: (volume) => {
12789
12801
  masterVolume = clamp3(volume, 0, 100);
12790
12802
  refs.masterVolume.value = String(masterVolume);
12791
12803
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
12804
+ options.singingVoices?.setVolume(masterVolume / 100);
12792
12805
  },
12793
12806
  setDrumVolume: (volume) => {
12794
12807
  drumVolume = clamp3(volume, 0, 100);
package/dist/index.mjs CHANGED
@@ -2554,8 +2554,10 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2554
2554
  for (const [k, v] of Object.entries(options.voicebanks ?? {}))
2555
2555
  catalog[k.toLowerCase()] = v;
2556
2556
  let streamSession = 0;
2557
+ const masterGain = ctx.createGain();
2558
+ masterGain.connect(destination);
2557
2559
  const loaded = /* @__PURE__ */ new Map([
2558
- [FALLBACK_MODEL, createKlattVoice(ctx, destination)]
2560
+ [FALLBACK_MODEL, createKlattVoice(ctx, masterGain)]
2559
2561
  ]);
2560
2562
  const loading = /* @__PURE__ */ new Map();
2561
2563
  const load2 = (model) => {
@@ -2571,7 +2573,7 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2571
2573
  // マニフェストだけ先読みし、音素PCMは歌う直前にオンデマンド取得する
2572
2574
  // (= 初回に .koe 全体をDLしない。モバイル初回ロードの待ちを解消)。
2573
2575
  // Blob/File が直接渡されたケース(ローカル読み込み)はそのまま BlobVoiceSource。
2574
- createKoeVoice(ctx, destination, {
2576
+ createKoeVoice(ctx, masterGain, {
2575
2577
  koe,
2576
2578
  worldlineScriptUrl: options.worldlineScriptUrl,
2577
2579
  lightweight: options.lightweight,
@@ -2708,13 +2710,17 @@ var createSingingVoices = (ctx, destination, options = {}) => {
2708
2710
  stopStream();
2709
2711
  for (const v of loaded.values()) v.reset?.();
2710
2712
  };
2713
+ const setVolume = (gain) => {
2714
+ masterGain.gain.value = Math.max(0, gain);
2715
+ };
2711
2716
  return {
2712
2717
  loadModels,
2713
2718
  registerVoicebanks,
2714
2719
  warm,
2715
2720
  startStream,
2716
2721
  stopStream,
2717
- reset
2722
+ reset,
2723
+ setVolume
2718
2724
  };
2719
2725
  };
2720
2726
  var createVoiceRegistry = (models = {}, fallback = "klatt") => {
@@ -5483,6 +5489,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
5483
5489
  if (!voices) {
5484
5490
  const ctx = ensureCtx();
5485
5491
  voices = createSingingVoices(ctx, ctx.destination);
5492
+ voices.setVolume(masterVolume / 100);
5486
5493
  }
5487
5494
  return voices;
5488
5495
  };
@@ -6228,6 +6235,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6228
6235
  };
6229
6236
  const setVolume = (volume) => {
6230
6237
  masterVolume = volume;
6238
+ peekVoices()?.setVolume(masterVolume / 100);
6231
6239
  };
6232
6240
  const buildStreamTracks = () => [...lyricTracks.entries()].map(([index, lt]) => {
6233
6241
  const seqTrack = seqTracks.find((t) => Number(t.id) === index);
@@ -6250,7 +6258,7 @@ var mountMmlPlayer = (target, mml, options = {}) => {
6250
6258
  return {
6251
6259
  id: String(index),
6252
6260
  model: lt.model,
6253
- volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100) * (masterVolume / 100),
6261
+ volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (trackVolume / 100),
6254
6262
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
6255
6263
  notes
6256
6264
  };
@@ -6484,7 +6492,7 @@ var findZone = (ctx, fontName, zones, pitchs = []) => {
6484
6492
  for (let i = zones.length - 1; i >= 0; i--)
6485
6493
  for (const v of set) {
6486
6494
  const zone = zones[i];
6487
- if (v < zone.keyRangeLow || v > zone.keyRangeHigh + 1) continue;
6495
+ if (v < zone.keyRangeLow || v > zone.keyRangeHigh) continue;
6488
6496
  set.delete(v);
6489
6497
  map.set(v, { ...zone });
6490
6498
  }
@@ -6582,7 +6590,7 @@ var adjustZone = async (ctx, fontName, zone) => {
6582
6590
  const oldData = oldBuf.getChannelData(ch);
6583
6591
  const newData = newBuf.getChannelData(ch);
6584
6592
  for (let i = 0; i < attackLength; i++) {
6585
- const ratio = attackLength > 0 ? i / attackLength : 0;
6593
+ const ratio = attackLength > 1 ? i / (attackLength - 1) : 0;
6586
6594
  const m = 1 + (gainMultiplier - 1) * ratio;
6587
6595
  newData[i] = oldData[i] * m;
6588
6596
  }
@@ -8907,6 +8915,7 @@ var init = (mountTarget, width = 800, height = 450, config) => {
8907
8915
  gridCanvas.style.left = `${KEYBOARD_WIDTH}px`;
8908
8916
  gridCanvas.style.top = `${HEADER_HEIGHT}px`;
8909
8917
  gridCanvas.style.touchAction = "none";
8918
+ gridCanvas.style.userSelect = "none";
8910
8919
  const gridCtx = gridCanvas.getContext("2d", { willReadFrequently: true });
8911
8920
  if (!gridCtx) throw new Error("Failed to get 2D rendering context for grid.");
8912
8921
  g_grid_ctx = gridCtx;
@@ -9900,6 +9909,7 @@ var mountDAW = (target, options = {}) => {
9900
9909
  let zoomY = 100;
9901
9910
  let bpm = options.defaultBpm ?? DEFAULT_BPM;
9902
9911
  let masterVolume = options.masterVolume ?? 50;
9912
+ options.singingVoices?.setVolume(masterVolume / 100);
9903
9913
  let drumVolume = options.drumVolume ?? 80;
9904
9914
  let currentDrumPattern = refs.drumSelect.value;
9905
9915
  let currentInstrument = "";
@@ -10777,7 +10787,7 @@ var mountDAW = (target, options = {}) => {
10777
10787
  return {
10778
10788
  id: trackState?.config.id,
10779
10789
  model: lt.model,
10780
- volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME) * (masterVolume / 100),
10790
+ volume: vocalVolumeToGain(lt.volume ?? DEFAULT_VOCAL_VOLUME),
10781
10791
  pan: panToStereo(lt.pan ?? DEFAULT_PAN),
10782
10792
  notes
10783
10793
  };
@@ -11786,6 +11796,7 @@ var mountDAW = (target, options = {}) => {
11786
11796
  refs.masterVolume.addEventListener("input", () => {
11787
11797
  masterVolume = Number.parseInt(refs.masterVolume.value, 10) || 0;
11788
11798
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
11799
+ options.singingVoices?.setVolume(masterVolume / 100);
11789
11800
  });
11790
11801
  refs.drumSelect.addEventListener("change", () => {
11791
11802
  currentDrumPattern = refs.drumSelect.value;
@@ -12662,11 +12673,13 @@ var mountDAW = (target, options = {}) => {
12662
12673
  masterVolume = clamp3(volume, 0, 100);
12663
12674
  refs.masterVolume.value = String(masterVolume);
12664
12675
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
12676
+ options.singingVoices?.setVolume(masterVolume / 100);
12665
12677
  },
12666
12678
  setVolume: (volume) => {
12667
12679
  masterVolume = clamp3(volume, 0, 100);
12668
12680
  refs.masterVolume.value = String(masterVolume);
12669
12681
  refs.masterVolumeLabel.textContent = `${masterVolume}%`;
12682
+ options.singingVoices?.setVolume(masterVolume / 100);
12670
12683
  },
12671
12684
  setDrumVolume: (volume) => {
12672
12685
  drumVolume = clamp3(volume, 0, 100);
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.59",
5
+ "version": "0.1.61",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {