@onjmin/dtm 1.0.0 → 1.0.2

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
@@ -9916,6 +9916,36 @@ var SoundFont = class _SoundFont {
9916
9916
  static afterTime = 0.5;
9917
9917
  static fonts = /* @__PURE__ */ new Map();
9918
9918
  static ch = -1;
9919
+ // ── ヒューマナイズ(疑似ラウンドロビン)──
9920
+ // サンプルを複数持たない代わりに、発音のたびピッチと音量をごく僅かにランダムへ
9921
+ // ブレさせて「毎回寸分違わず同じ波形」という機械的な均一さを崩す。
9922
+ /** 発音ごとのピッチ微揺らぎ幅(セント、±この値の範囲で一様乱数)。 */
9923
+ static humanizeDetuneCents = 4;
9924
+ /** 発音ごとの音量微揺らぎ幅(比率、0.06 = ±6%の範囲で一様乱数)。 */
9925
+ static humanizeGainRatio = 0.06;
9926
+ // ── ベロシティ→明るさ連動フィルタ ──
9927
+ // volume は音量にしか効かず音色(明るさ)が変わらないのを補うため、
9928
+ // ローパスのカットオフを volume に連動させる(弱いほど丸く、強いほど明るく)。
9929
+ /** volume=0 のときのローパスカットオフ(Hz)。 */
9930
+ static brightnessMinHz = 1200;
9931
+ /** volume=1 のときのローパスカットオフ(Hz)。サンプルの帯域を実質塞がない値。 */
9932
+ static brightnessMaxHz = 18e3;
9933
+ /** ベロシティ→明るさフィルタのQ値。共鳴が付かないButterworth特性。 */
9934
+ static brightnessQ = Math.SQRT1_2;
9935
+ // ── ロングトーン用ビブラート(ピッチLFO) ──
9936
+ // 伸ばす音が完全に静止していると不自然に聞こえるため、一定長以上持続する
9937
+ // 音符にだけ、発音直後フェードインしつつ穏やかなピッチ揺れを掛ける。
9938
+ // 閾値・速さは歌声合成側の vibrato.ts (VIBRATO_MIN_SEC/VIBRATO_RATE_HZ) と
9939
+ // 同じ考え方だが、MIDI楽器はここでは独立した定数として持つ
9940
+ // (koeのCurveInput契約に縛られない汎用のWeb Audio LFOのため)。
9941
+ /** ロングトーン判定の最短秒数。これ未満の音符にはビブラートを掛けない。 */
9942
+ static longToneThresholdSec = 0.35;
9943
+ /** ロングトーンビブラートの速さ(Hz)。 */
9944
+ static vibratoRateHz = 5.5;
9945
+ /** ロングトーンビブラートの深さ(セント)。歌声用(±35セント)より控えめ。 */
9946
+ static vibratoDepthCents = 12;
9947
+ /** ロングトーンビブラートのフェードイン秒数。発音直後は素直な音程を保つ。 */
9948
+ static vibratoFadeSec = 0.2;
9919
9949
  static toURL(fontName) {
9920
9950
  return `https://surikov.github.io/webaudiofontdata/sound/${fontName}.js`;
9921
9951
  }
@@ -9966,6 +9996,7 @@ var SoundFont = class _SoundFont {
9966
9996
  const zone = zones.get(pitch);
9967
9997
  if (!zone) return;
9968
9998
  const src = ctx.createBufferSource();
9999
+ const filter = ctx.createBiquadFilter();
9969
10000
  const g = ctx.createGain();
9970
10001
  const _when = when + ctx.currentTime;
9971
10002
  const { buffer, _param } = zone;
@@ -9973,23 +10004,53 @@ var SoundFont = class _SoundFont {
9973
10004
  src.buffer = buffer;
9974
10005
  src.playbackRate.setValueAtTime(_param.playbackRate, 0);
9975
10006
  Object.assign(src, _param.src);
10007
+ const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
10008
+ const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
10009
+ src.detune.setValueAtTime(humanizeCents, 0);
10010
+ const effectiveVolume = volume * humanizeGainMul;
10011
+ filter.type = "lowpass";
10012
+ const brightness = Math.max(0, Math.min(1, volume));
10013
+ filter.frequency.setValueAtTime(
10014
+ _SoundFont.brightnessMinHz + (_SoundFont.brightnessMaxHz - _SoundFont.brightnessMinHz) * brightness,
10015
+ 0
10016
+ );
10017
+ filter.Q.setValueAtTime(_SoundFont.brightnessQ, 0);
9976
10018
  g.gain.setValueAtTime(0, ctx.currentTime);
9977
10019
  const attackTime = 5e-3;
9978
10020
  const startGainTime = Math.max(ctx.currentTime, _when);
9979
10021
  g.gain.setValueAtTime(0, startGainTime);
9980
- g.gain.linearRampToValueAtTime(volume, startGainTime + attackTime);
10022
+ g.gain.linearRampToValueAtTime(effectiveVolume, startGainTime + attackTime);
9981
10023
  const _duration = duration + _SoundFont.afterTime;
9982
10024
  const end = _when + (isDrum ? buffer.duration : src.loop ? _duration : Math.min(_duration, _param.max));
9983
10025
  if (!isDrum) {
9984
- g.gain.setValueAtTime(volume, startGainTime + attackTime);
10026
+ g.gain.setValueAtTime(effectiveVolume, startGainTime + attackTime);
9985
10027
  g.gain.linearRampToValueAtTime(0, end);
9986
10028
  }
9987
- src.connect(g).connect(destination);
10029
+ src.connect(filter).connect(g).connect(destination);
10030
+ let vibratoOsc;
10031
+ let vibratoDepth;
10032
+ if (!isDrum && duration >= _SoundFont.longToneThresholdSec) {
10033
+ vibratoOsc = ctx.createOscillator();
10034
+ vibratoOsc.type = "sine";
10035
+ vibratoOsc.frequency.setValueAtTime(_SoundFont.vibratoRateHz, 0);
10036
+ vibratoDepth = ctx.createGain();
10037
+ vibratoDepth.gain.setValueAtTime(0, startGainTime);
10038
+ vibratoDepth.gain.linearRampToValueAtTime(
10039
+ _SoundFont.vibratoDepthCents,
10040
+ startGainTime + _SoundFont.vibratoFadeSec
10041
+ );
10042
+ vibratoOsc.connect(vibratoDepth).connect(src.detune);
10043
+ vibratoOsc.start(_when);
10044
+ vibratoOsc.stop(end);
10045
+ }
9988
10046
  src.start(_when);
9989
10047
  src.stop(end);
9990
10048
  src.onended = () => {
9991
10049
  src.disconnect();
10050
+ filter.disconnect();
9992
10051
  g.disconnect();
10052
+ vibratoOsc?.disconnect();
10053
+ vibratoDepth?.disconnect();
9993
10054
  };
9994
10055
  }
9995
10056
  };
@@ -14193,6 +14254,13 @@ var mountDAW = (target, options = {}) => {
14193
14254
  let currentDrumPattern = refs.drumSelect.value;
14194
14255
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
14195
14256
  refs.drumFontSelect.value = currentDrumFont;
14257
+ const applyDrumPatternFont = (name) => {
14258
+ const font = drumPatterns[name]?.font;
14259
+ if (!font) return;
14260
+ currentDrumFont = font;
14261
+ refs.drumFontSelect.value = font;
14262
+ options.onDrumFontChange?.(font);
14263
+ };
14196
14264
  let currentInstrument = "";
14197
14265
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
14198
14266
  let trackFxAdvancedOpen = false;
@@ -16170,6 +16238,7 @@ var mountDAW = (target, options = {}) => {
16170
16238
  {
16171
16239
  instrument: currentInstrument || void 0,
16172
16240
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
16241
+ drumFont: currentDrumFont,
16173
16242
  volume: masterVolume,
16174
16243
  drumVolume,
16175
16244
  reverb: reverbAmount,
@@ -16197,6 +16266,7 @@ var mountDAW = (target, options = {}) => {
16197
16266
  {
16198
16267
  instrument: currentInstrument || void 0,
16199
16268
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
16269
+ drumFont: currentDrumFont,
16200
16270
  volume: masterVolume,
16201
16271
  drumVolume,
16202
16272
  reverb: reverbAmount,
@@ -16414,6 +16484,7 @@ var mountDAW = (target, options = {}) => {
16414
16484
  currentDrumPattern = meta.drum;
16415
16485
  refs.drumSelect.value = meta.drum;
16416
16486
  options.onDrumChange?.(meta.drum);
16487
+ if (!meta.drumFont) applyDrumPatternFont(meta.drum);
16417
16488
  }
16418
16489
  if (meta.volume !== void 0) {
16419
16490
  masterVolume = meta.volume;
@@ -16578,7 +16649,6 @@ var mountDAW = (target, options = {}) => {
16578
16649
  t.vocalGender = lt.gender ?? 50;
16579
16650
  t.vocalBreathiness = lt.breathiness ?? 50;
16580
16651
  t.vocalTension = lt.tension ?? 50;
16581
- t.vocalTension = lt.tension ?? 50;
16582
16652
  t.vocalOctaveUnison = lt.octaveUnison ?? "none";
16583
16653
  });
16584
16654
  for (const p of placements) {
@@ -17098,6 +17168,7 @@ var mountDAW = (target, options = {}) => {
17098
17168
  refs.drumSelect.addEventListener("change", () => {
17099
17169
  currentDrumPattern = refs.drumSelect.value;
17100
17170
  options.onDrumChange?.(currentDrumPattern);
17171
+ applyDrumPatternFont(currentDrumPattern);
17101
17172
  });
17102
17173
  refs.drumFontSelect.addEventListener("change", () => {
17103
17174
  currentDrumFont = refs.drumFontSelect.value;
@@ -18033,6 +18104,7 @@ var mountDAW = (target, options = {}) => {
18033
18104
  currentDrumPattern = name;
18034
18105
  refs.drumSelect.value = name;
18035
18106
  options.onDrumChange?.(name);
18107
+ applyDrumPatternFont(name);
18036
18108
  },
18037
18109
  getViewState,
18038
18110
  setViewState: (state) => {
package/dist/index.mjs CHANGED
@@ -9783,6 +9783,36 @@ var SoundFont = class _SoundFont {
9783
9783
  static afterTime = 0.5;
9784
9784
  static fonts = /* @__PURE__ */ new Map();
9785
9785
  static ch = -1;
9786
+ // ── ヒューマナイズ(疑似ラウンドロビン)──
9787
+ // サンプルを複数持たない代わりに、発音のたびピッチと音量をごく僅かにランダムへ
9788
+ // ブレさせて「毎回寸分違わず同じ波形」という機械的な均一さを崩す。
9789
+ /** 発音ごとのピッチ微揺らぎ幅(セント、±この値の範囲で一様乱数)。 */
9790
+ static humanizeDetuneCents = 4;
9791
+ /** 発音ごとの音量微揺らぎ幅(比率、0.06 = ±6%の範囲で一様乱数)。 */
9792
+ static humanizeGainRatio = 0.06;
9793
+ // ── ベロシティ→明るさ連動フィルタ ──
9794
+ // volume は音量にしか効かず音色(明るさ)が変わらないのを補うため、
9795
+ // ローパスのカットオフを volume に連動させる(弱いほど丸く、強いほど明るく)。
9796
+ /** volume=0 のときのローパスカットオフ(Hz)。 */
9797
+ static brightnessMinHz = 1200;
9798
+ /** volume=1 のときのローパスカットオフ(Hz)。サンプルの帯域を実質塞がない値。 */
9799
+ static brightnessMaxHz = 18e3;
9800
+ /** ベロシティ→明るさフィルタのQ値。共鳴が付かないButterworth特性。 */
9801
+ static brightnessQ = Math.SQRT1_2;
9802
+ // ── ロングトーン用ビブラート(ピッチLFO) ──
9803
+ // 伸ばす音が完全に静止していると不自然に聞こえるため、一定長以上持続する
9804
+ // 音符にだけ、発音直後フェードインしつつ穏やかなピッチ揺れを掛ける。
9805
+ // 閾値・速さは歌声合成側の vibrato.ts (VIBRATO_MIN_SEC/VIBRATO_RATE_HZ) と
9806
+ // 同じ考え方だが、MIDI楽器はここでは独立した定数として持つ
9807
+ // (koeのCurveInput契約に縛られない汎用のWeb Audio LFOのため)。
9808
+ /** ロングトーン判定の最短秒数。これ未満の音符にはビブラートを掛けない。 */
9809
+ static longToneThresholdSec = 0.35;
9810
+ /** ロングトーンビブラートの速さ(Hz)。 */
9811
+ static vibratoRateHz = 5.5;
9812
+ /** ロングトーンビブラートの深さ(セント)。歌声用(±35セント)より控えめ。 */
9813
+ static vibratoDepthCents = 12;
9814
+ /** ロングトーンビブラートのフェードイン秒数。発音直後は素直な音程を保つ。 */
9815
+ static vibratoFadeSec = 0.2;
9786
9816
  static toURL(fontName) {
9787
9817
  return `https://surikov.github.io/webaudiofontdata/sound/${fontName}.js`;
9788
9818
  }
@@ -9833,6 +9863,7 @@ var SoundFont = class _SoundFont {
9833
9863
  const zone = zones.get(pitch);
9834
9864
  if (!zone) return;
9835
9865
  const src = ctx.createBufferSource();
9866
+ const filter = ctx.createBiquadFilter();
9836
9867
  const g = ctx.createGain();
9837
9868
  const _when = when + ctx.currentTime;
9838
9869
  const { buffer, _param } = zone;
@@ -9840,23 +9871,53 @@ var SoundFont = class _SoundFont {
9840
9871
  src.buffer = buffer;
9841
9872
  src.playbackRate.setValueAtTime(_param.playbackRate, 0);
9842
9873
  Object.assign(src, _param.src);
9874
+ const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
9875
+ const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
9876
+ src.detune.setValueAtTime(humanizeCents, 0);
9877
+ const effectiveVolume = volume * humanizeGainMul;
9878
+ filter.type = "lowpass";
9879
+ const brightness = Math.max(0, Math.min(1, volume));
9880
+ filter.frequency.setValueAtTime(
9881
+ _SoundFont.brightnessMinHz + (_SoundFont.brightnessMaxHz - _SoundFont.brightnessMinHz) * brightness,
9882
+ 0
9883
+ );
9884
+ filter.Q.setValueAtTime(_SoundFont.brightnessQ, 0);
9843
9885
  g.gain.setValueAtTime(0, ctx.currentTime);
9844
9886
  const attackTime = 5e-3;
9845
9887
  const startGainTime = Math.max(ctx.currentTime, _when);
9846
9888
  g.gain.setValueAtTime(0, startGainTime);
9847
- g.gain.linearRampToValueAtTime(volume, startGainTime + attackTime);
9889
+ g.gain.linearRampToValueAtTime(effectiveVolume, startGainTime + attackTime);
9848
9890
  const _duration = duration + _SoundFont.afterTime;
9849
9891
  const end = _when + (isDrum ? buffer.duration : src.loop ? _duration : Math.min(_duration, _param.max));
9850
9892
  if (!isDrum) {
9851
- g.gain.setValueAtTime(volume, startGainTime + attackTime);
9893
+ g.gain.setValueAtTime(effectiveVolume, startGainTime + attackTime);
9852
9894
  g.gain.linearRampToValueAtTime(0, end);
9853
9895
  }
9854
- src.connect(g).connect(destination);
9896
+ src.connect(filter).connect(g).connect(destination);
9897
+ let vibratoOsc;
9898
+ let vibratoDepth;
9899
+ if (!isDrum && duration >= _SoundFont.longToneThresholdSec) {
9900
+ vibratoOsc = ctx.createOscillator();
9901
+ vibratoOsc.type = "sine";
9902
+ vibratoOsc.frequency.setValueAtTime(_SoundFont.vibratoRateHz, 0);
9903
+ vibratoDepth = ctx.createGain();
9904
+ vibratoDepth.gain.setValueAtTime(0, startGainTime);
9905
+ vibratoDepth.gain.linearRampToValueAtTime(
9906
+ _SoundFont.vibratoDepthCents,
9907
+ startGainTime + _SoundFont.vibratoFadeSec
9908
+ );
9909
+ vibratoOsc.connect(vibratoDepth).connect(src.detune);
9910
+ vibratoOsc.start(_when);
9911
+ vibratoOsc.stop(end);
9912
+ }
9855
9913
  src.start(_when);
9856
9914
  src.stop(end);
9857
9915
  src.onended = () => {
9858
9916
  src.disconnect();
9917
+ filter.disconnect();
9859
9918
  g.disconnect();
9919
+ vibratoOsc?.disconnect();
9920
+ vibratoDepth?.disconnect();
9860
9921
  };
9861
9922
  }
9862
9923
  };
@@ -14060,6 +14121,13 @@ var mountDAW = (target, options = {}) => {
14060
14121
  let currentDrumPattern = refs.drumSelect.value;
14061
14122
  let currentDrumFont = options.drumFont ?? "FluidR3_GM_sf2_file:0";
14062
14123
  refs.drumFontSelect.value = currentDrumFont;
14124
+ const applyDrumPatternFont = (name) => {
14125
+ const font = drumPatterns[name]?.font;
14126
+ if (!font) return;
14127
+ currentDrumFont = font;
14128
+ refs.drumFontSelect.value = font;
14129
+ options.onDrumFontChange?.(font);
14130
+ };
14063
14131
  let currentInstrument = "";
14064
14132
  let activeTrackId = options.initialActiveTrack ?? trackConfigs[0].id;
14065
14133
  let trackFxAdvancedOpen = false;
@@ -16037,6 +16105,7 @@ var mountDAW = (target, options = {}) => {
16037
16105
  {
16038
16106
  instrument: currentInstrument || void 0,
16039
16107
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
16108
+ drumFont: currentDrumFont,
16040
16109
  volume: masterVolume,
16041
16110
  drumVolume,
16042
16111
  reverb: reverbAmount,
@@ -16064,6 +16133,7 @@ var mountDAW = (target, options = {}) => {
16064
16133
  {
16065
16134
  instrument: currentInstrument || void 0,
16066
16135
  drum: currentDrumPattern !== "none" ? currentDrumPattern : void 0,
16136
+ drumFont: currentDrumFont,
16067
16137
  volume: masterVolume,
16068
16138
  drumVolume,
16069
16139
  reverb: reverbAmount,
@@ -16281,6 +16351,7 @@ var mountDAW = (target, options = {}) => {
16281
16351
  currentDrumPattern = meta.drum;
16282
16352
  refs.drumSelect.value = meta.drum;
16283
16353
  options.onDrumChange?.(meta.drum);
16354
+ if (!meta.drumFont) applyDrumPatternFont(meta.drum);
16284
16355
  }
16285
16356
  if (meta.volume !== void 0) {
16286
16357
  masterVolume = meta.volume;
@@ -16445,7 +16516,6 @@ var mountDAW = (target, options = {}) => {
16445
16516
  t.vocalGender = lt.gender ?? 50;
16446
16517
  t.vocalBreathiness = lt.breathiness ?? 50;
16447
16518
  t.vocalTension = lt.tension ?? 50;
16448
- t.vocalTension = lt.tension ?? 50;
16449
16519
  t.vocalOctaveUnison = lt.octaveUnison ?? "none";
16450
16520
  });
16451
16521
  for (const p of placements) {
@@ -16965,6 +17035,7 @@ var mountDAW = (target, options = {}) => {
16965
17035
  refs.drumSelect.addEventListener("change", () => {
16966
17036
  currentDrumPattern = refs.drumSelect.value;
16967
17037
  options.onDrumChange?.(currentDrumPattern);
17038
+ applyDrumPatternFont(currentDrumPattern);
16968
17039
  });
16969
17040
  refs.drumFontSelect.addEventListener("change", () => {
16970
17041
  currentDrumFont = refs.drumFontSelect.value;
@@ -17900,6 +17971,7 @@ var mountDAW = (target, options = {}) => {
17900
17971
  currentDrumPattern = name;
17901
17972
  refs.drumSelect.value = name;
17902
17973
  options.onDrumChange?.(name);
17974
+ applyDrumPatternFont(name);
17903
17975
  },
17904
17976
  getViewState,
17905
17977
  setViewState: (state) => {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "onjmin",
3
3
  "license": "MIT",
4
4
  "name": "@onjmin/dtm",
5
- "version": "1.0.0",
5
+ "version": "1.0.2",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {