@onjmin/dtm 1.0.1 → 1.0.3

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
@@ -2086,6 +2086,8 @@ type SoundFontInstance = {
2086
2086
  destination: AudioNode;
2087
2087
  pitch: number;
2088
2088
  volume: number;
2089
+ /** 元ノートのvelocity(0-127)。音量ではなく音色の明るさにだけ効く。 */
2090
+ velocity?: number;
2089
2091
  when: number;
2090
2092
  duration: number;
2091
2093
  }) => void;
package/dist/index.d.ts CHANGED
@@ -2086,6 +2086,8 @@ type SoundFontInstance = {
2086
2086
  destination: AudioNode;
2087
2087
  pitch: number;
2088
2088
  volume: number;
2089
+ /** 元ノートのvelocity(0-127)。音量ではなく音色の明るさにだけ効く。 */
2090
+ velocity?: number;
2089
2091
  when: number;
2090
2092
  duration: number;
2091
2093
  }) => void;
package/dist/index.js CHANGED
@@ -9916,6 +9916,45 @@ 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
+ // velocity は音量にしか効かず音色(明るさ)が変わらないのを補うため、
9928
+ // ローパスのカットオフを velocity に連動させる(弱いほど丸く、強いほど明るく)。
9929
+ // 連動元は必ず velocity であって volume ではない。volume には
9930
+ // 「トラック音量フェーダー×velocity」が既に畳み込まれている(sequencer.ts参照)ため、
9931
+ // volume を使うとフェーダーを下げただけで音色まで暗くなり、音量がゲインと音色へ
9932
+ // 二重に効いてしまう。音量フェーダーは音色を変えてはならない。
9933
+ /** velocity=0 のときのローパスカットオフ(Hz)。 */
9934
+ static brightnessMinHz = 2400;
9935
+ /** velocity=127 のときのローパスカットオフ(Hz)。サンプルの帯域を実質塞がない値。 */
9936
+ static brightnessMaxHz = 2e4;
9937
+ /** ベロシティ→明るさフィルタのQ値。共鳴が付かないButterworth特性。 */
9938
+ static brightnessQ = Math.SQRT1_2;
9939
+ /**
9940
+ * この明るさ比率以上ならフィルタ自体を挿入しない。最強ベロシティの音は改修前と
9941
+ * 完全に同一の信号経路を通り、余計な音痩せや位相変化が起きないようにするため。
9942
+ */
9943
+ static brightnessBypassAbove = 0.98;
9944
+ // ── ロングトーン用ビブラート(ピッチLFO) ──
9945
+ // 伸ばす音が完全に静止していると不自然に聞こえるため、一定長以上持続する
9946
+ // 音符にだけ、発音直後フェードインしつつ穏やかなピッチ揺れを掛ける。
9947
+ // 閾値・速さは歌声合成側の vibrato.ts (VIBRATO_MIN_SEC/VIBRATO_RATE_HZ) と
9948
+ // 同じ考え方だが、MIDI楽器はここでは独立した定数として持つ
9949
+ // (koeのCurveInput契約に縛られない汎用のWeb Audio LFOのため)。
9950
+ /** ロングトーン判定の最短秒数。これ未満の音符にはビブラートを掛けない。 */
9951
+ static longToneThresholdSec = 0.35;
9952
+ /** ロングトーンビブラートの速さ(Hz)。 */
9953
+ static vibratoRateHz = 5.5;
9954
+ /** ロングトーンビブラートの深さ(セント)。歌声用(±35セント)より控えめ。 */
9955
+ static vibratoDepthCents = 12;
9956
+ /** ロングトーンビブラートのフェードイン秒数。発音直後は素直な音程を保つ。 */
9957
+ static vibratoFadeSec = 0.2;
9919
9958
  static toURL(fontName) {
9920
9959
  return `https://surikov.github.io/webaudiofontdata/sound/${fontName}.js`;
9921
9960
  }
@@ -9956,6 +9995,7 @@ var SoundFont = class _SoundFont {
9956
9995
  destination,
9957
9996
  pitch = 60,
9958
9997
  volume = 1,
9998
+ velocity,
9959
9999
  when = 0,
9960
10000
  duration = 1
9961
10001
  } = {}) {
@@ -9973,23 +10013,58 @@ var SoundFont = class _SoundFont {
9973
10013
  src.buffer = buffer;
9974
10014
  src.playbackRate.setValueAtTime(_param.playbackRate, 0);
9975
10015
  Object.assign(src, _param.src);
10016
+ const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
10017
+ const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
10018
+ src.detune.setValueAtTime(humanizeCents, 0);
10019
+ const effectiveVolume = volume * humanizeGainMul;
10020
+ const brightness = velocity === void 0 ? 1 : Math.max(0, Math.min(1, velocity / 127));
10021
+ const filter = brightness >= _SoundFont.brightnessBypassAbove ? void 0 : ctx.createBiquadFilter();
10022
+ if (filter) {
10023
+ filter.type = "lowpass";
10024
+ filter.frequency.setValueAtTime(
10025
+ _SoundFont.brightnessMinHz + (_SoundFont.brightnessMaxHz - _SoundFont.brightnessMinHz) * brightness,
10026
+ 0
10027
+ );
10028
+ filter.Q.setValueAtTime(_SoundFont.brightnessQ, 0);
10029
+ }
9976
10030
  g.gain.setValueAtTime(0, ctx.currentTime);
9977
10031
  const attackTime = 5e-3;
9978
10032
  const startGainTime = Math.max(ctx.currentTime, _when);
9979
10033
  g.gain.setValueAtTime(0, startGainTime);
9980
- g.gain.linearRampToValueAtTime(volume, startGainTime + attackTime);
10034
+ g.gain.linearRampToValueAtTime(effectiveVolume, startGainTime + attackTime);
9981
10035
  const _duration = duration + _SoundFont.afterTime;
9982
10036
  const end = _when + (isDrum ? buffer.duration : src.loop ? _duration : Math.min(_duration, _param.max));
9983
10037
  if (!isDrum) {
9984
- g.gain.setValueAtTime(volume, startGainTime + attackTime);
10038
+ g.gain.setValueAtTime(effectiveVolume, startGainTime + attackTime);
9985
10039
  g.gain.linearRampToValueAtTime(0, end);
9986
10040
  }
9987
- src.connect(g).connect(destination);
10041
+ if (filter) src.connect(filter).connect(g);
10042
+ else src.connect(g);
10043
+ g.connect(destination);
10044
+ let vibratoOsc;
10045
+ let vibratoDepth;
10046
+ if (!isDrum && duration >= _SoundFont.longToneThresholdSec) {
10047
+ vibratoOsc = ctx.createOscillator();
10048
+ vibratoOsc.type = "sine";
10049
+ vibratoOsc.frequency.setValueAtTime(_SoundFont.vibratoRateHz, 0);
10050
+ vibratoDepth = ctx.createGain();
10051
+ vibratoDepth.gain.setValueAtTime(0, startGainTime);
10052
+ vibratoDepth.gain.linearRampToValueAtTime(
10053
+ _SoundFont.vibratoDepthCents,
10054
+ startGainTime + _SoundFont.vibratoFadeSec
10055
+ );
10056
+ vibratoOsc.connect(vibratoDepth).connect(src.detune);
10057
+ vibratoOsc.start(_when);
10058
+ vibratoOsc.stop(end);
10059
+ }
9988
10060
  src.start(_when);
9989
10061
  src.stop(end);
9990
10062
  src.onended = () => {
9991
10063
  src.disconnect();
10064
+ filter?.disconnect();
9992
10065
  g.disconnect();
10066
+ vibratoOsc?.disconnect();
10067
+ vibratoDepth?.disconnect();
9993
10068
  };
9994
10069
  }
9995
10070
  };
@@ -11080,6 +11155,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
11080
11155
  };
11081
11156
  const wafPlayDynamic = ({
11082
11157
  pitch,
11158
+ velocity,
11083
11159
  volume,
11084
11160
  when,
11085
11161
  duration,
@@ -11093,6 +11169,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
11093
11169
  destination,
11094
11170
  pitch,
11095
11171
  volume: volume * 0.85,
11172
+ velocity,
11096
11173
  when,
11097
11174
  duration
11098
11175
  });
@@ -19551,6 +19628,7 @@ var createDtmStudio = async (options = {}) => {
19551
19628
  destination: getChannelStrip(e.trackId).input,
19552
19629
  pitch: e.pitch,
19553
19630
  volume: e.volume,
19631
+ velocity: e.velocity,
19554
19632
  when: e.when,
19555
19633
  duration: e.duration
19556
19634
  });
@@ -19867,6 +19945,7 @@ var createDtmStudio = async (options = {}) => {
19867
19945
  destination: getChannelStrip(e.trackId).input,
19868
19946
  pitch: e.pitch,
19869
19947
  volume: e.volume,
19948
+ velocity: e.velocity,
19870
19949
  when: e.when,
19871
19950
  duration: e.duration
19872
19951
  });
@@ -19953,6 +20032,7 @@ var createDtmStudio = async (options = {}) => {
19953
20032
  destination: getChannelStrip(e.trackId).input,
19954
20033
  pitch: e.pitch,
19955
20034
  volume: e.volume,
20035
+ velocity: e.velocity,
19956
20036
  when: e.when,
19957
20037
  duration: e.duration
19958
20038
  });
@@ -20032,6 +20112,7 @@ var createDtmStudio = async (options = {}) => {
20032
20112
  destination: getChannelStrip(e.trackId).input,
20033
20113
  pitch: e.pitch,
20034
20114
  volume: e.volume,
20115
+ velocity: e.velocity,
20035
20116
  when: e.when,
20036
20117
  duration: e.duration
20037
20118
  });
@@ -20071,6 +20152,7 @@ var createDtmStudio = async (options = {}) => {
20071
20152
  destination: getChannelStrip(e.trackId).input,
20072
20153
  pitch: e.pitch,
20073
20154
  volume: e.volume,
20155
+ velocity: e.velocity,
20074
20156
  when: e.when,
20075
20157
  duration: e.duration
20076
20158
  });
@@ -20127,6 +20209,7 @@ var createDtmStudio = async (options = {}) => {
20127
20209
  destination: getChannelStrip("chord").input,
20128
20210
  pitch: e.pitch,
20129
20211
  volume: e.volume,
20212
+ velocity: e.velocity,
20130
20213
  when: e.when,
20131
20214
  duration: e.duration
20132
20215
  });
package/dist/index.mjs CHANGED
@@ -9783,6 +9783,45 @@ 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
+ // velocity は音量にしか効かず音色(明るさ)が変わらないのを補うため、
9795
+ // ローパスのカットオフを velocity に連動させる(弱いほど丸く、強いほど明るく)。
9796
+ // 連動元は必ず velocity であって volume ではない。volume には
9797
+ // 「トラック音量フェーダー×velocity」が既に畳み込まれている(sequencer.ts参照)ため、
9798
+ // volume を使うとフェーダーを下げただけで音色まで暗くなり、音量がゲインと音色へ
9799
+ // 二重に効いてしまう。音量フェーダーは音色を変えてはならない。
9800
+ /** velocity=0 のときのローパスカットオフ(Hz)。 */
9801
+ static brightnessMinHz = 2400;
9802
+ /** velocity=127 のときのローパスカットオフ(Hz)。サンプルの帯域を実質塞がない値。 */
9803
+ static brightnessMaxHz = 2e4;
9804
+ /** ベロシティ→明るさフィルタのQ値。共鳴が付かないButterworth特性。 */
9805
+ static brightnessQ = Math.SQRT1_2;
9806
+ /**
9807
+ * この明るさ比率以上ならフィルタ自体を挿入しない。最強ベロシティの音は改修前と
9808
+ * 完全に同一の信号経路を通り、余計な音痩せや位相変化が起きないようにするため。
9809
+ */
9810
+ static brightnessBypassAbove = 0.98;
9811
+ // ── ロングトーン用ビブラート(ピッチLFO) ──
9812
+ // 伸ばす音が完全に静止していると不自然に聞こえるため、一定長以上持続する
9813
+ // 音符にだけ、発音直後フェードインしつつ穏やかなピッチ揺れを掛ける。
9814
+ // 閾値・速さは歌声合成側の vibrato.ts (VIBRATO_MIN_SEC/VIBRATO_RATE_HZ) と
9815
+ // 同じ考え方だが、MIDI楽器はここでは独立した定数として持つ
9816
+ // (koeのCurveInput契約に縛られない汎用のWeb Audio LFOのため)。
9817
+ /** ロングトーン判定の最短秒数。これ未満の音符にはビブラートを掛けない。 */
9818
+ static longToneThresholdSec = 0.35;
9819
+ /** ロングトーンビブラートの速さ(Hz)。 */
9820
+ static vibratoRateHz = 5.5;
9821
+ /** ロングトーンビブラートの深さ(セント)。歌声用(±35セント)より控えめ。 */
9822
+ static vibratoDepthCents = 12;
9823
+ /** ロングトーンビブラートのフェードイン秒数。発音直後は素直な音程を保つ。 */
9824
+ static vibratoFadeSec = 0.2;
9786
9825
  static toURL(fontName) {
9787
9826
  return `https://surikov.github.io/webaudiofontdata/sound/${fontName}.js`;
9788
9827
  }
@@ -9823,6 +9862,7 @@ var SoundFont = class _SoundFont {
9823
9862
  destination,
9824
9863
  pitch = 60,
9825
9864
  volume = 1,
9865
+ velocity,
9826
9866
  when = 0,
9827
9867
  duration = 1
9828
9868
  } = {}) {
@@ -9840,23 +9880,58 @@ var SoundFont = class _SoundFont {
9840
9880
  src.buffer = buffer;
9841
9881
  src.playbackRate.setValueAtTime(_param.playbackRate, 0);
9842
9882
  Object.assign(src, _param.src);
9883
+ const humanizeCents = (Math.random() * 2 - 1) * _SoundFont.humanizeDetuneCents;
9884
+ const humanizeGainMul = 1 + (Math.random() * 2 - 1) * _SoundFont.humanizeGainRatio;
9885
+ src.detune.setValueAtTime(humanizeCents, 0);
9886
+ const effectiveVolume = volume * humanizeGainMul;
9887
+ const brightness = velocity === void 0 ? 1 : Math.max(0, Math.min(1, velocity / 127));
9888
+ const filter = brightness >= _SoundFont.brightnessBypassAbove ? void 0 : ctx.createBiquadFilter();
9889
+ if (filter) {
9890
+ filter.type = "lowpass";
9891
+ filter.frequency.setValueAtTime(
9892
+ _SoundFont.brightnessMinHz + (_SoundFont.brightnessMaxHz - _SoundFont.brightnessMinHz) * brightness,
9893
+ 0
9894
+ );
9895
+ filter.Q.setValueAtTime(_SoundFont.brightnessQ, 0);
9896
+ }
9843
9897
  g.gain.setValueAtTime(0, ctx.currentTime);
9844
9898
  const attackTime = 5e-3;
9845
9899
  const startGainTime = Math.max(ctx.currentTime, _when);
9846
9900
  g.gain.setValueAtTime(0, startGainTime);
9847
- g.gain.linearRampToValueAtTime(volume, startGainTime + attackTime);
9901
+ g.gain.linearRampToValueAtTime(effectiveVolume, startGainTime + attackTime);
9848
9902
  const _duration = duration + _SoundFont.afterTime;
9849
9903
  const end = _when + (isDrum ? buffer.duration : src.loop ? _duration : Math.min(_duration, _param.max));
9850
9904
  if (!isDrum) {
9851
- g.gain.setValueAtTime(volume, startGainTime + attackTime);
9905
+ g.gain.setValueAtTime(effectiveVolume, startGainTime + attackTime);
9852
9906
  g.gain.linearRampToValueAtTime(0, end);
9853
9907
  }
9854
- src.connect(g).connect(destination);
9908
+ if (filter) src.connect(filter).connect(g);
9909
+ else src.connect(g);
9910
+ g.connect(destination);
9911
+ let vibratoOsc;
9912
+ let vibratoDepth;
9913
+ if (!isDrum && duration >= _SoundFont.longToneThresholdSec) {
9914
+ vibratoOsc = ctx.createOscillator();
9915
+ vibratoOsc.type = "sine";
9916
+ vibratoOsc.frequency.setValueAtTime(_SoundFont.vibratoRateHz, 0);
9917
+ vibratoDepth = ctx.createGain();
9918
+ vibratoDepth.gain.setValueAtTime(0, startGainTime);
9919
+ vibratoDepth.gain.linearRampToValueAtTime(
9920
+ _SoundFont.vibratoDepthCents,
9921
+ startGainTime + _SoundFont.vibratoFadeSec
9922
+ );
9923
+ vibratoOsc.connect(vibratoDepth).connect(src.detune);
9924
+ vibratoOsc.start(_when);
9925
+ vibratoOsc.stop(end);
9926
+ }
9855
9927
  src.start(_when);
9856
9928
  src.stop(end);
9857
9929
  src.onended = () => {
9858
9930
  src.disconnect();
9931
+ filter?.disconnect();
9859
9932
  g.disconnect();
9933
+ vibratoOsc?.disconnect();
9934
+ vibratoDepth?.disconnect();
9860
9935
  };
9861
9936
  }
9862
9937
  };
@@ -10947,6 +11022,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
10947
11022
  };
10948
11023
  const wafPlayDynamic = ({
10949
11024
  pitch,
11025
+ velocity,
10950
11026
  volume,
10951
11027
  when,
10952
11028
  duration,
@@ -10960,6 +11036,7 @@ var mountChordPlayer = (target, chords, options = {}) => {
10960
11036
  destination,
10961
11037
  pitch,
10962
11038
  volume: volume * 0.85,
11039
+ velocity,
10963
11040
  when,
10964
11041
  duration
10965
11042
  });
@@ -19417,6 +19494,7 @@ var createDtmStudio = async (options = {}) => {
19417
19494
  destination: getChannelStrip(e.trackId).input,
19418
19495
  pitch: e.pitch,
19419
19496
  volume: e.volume,
19497
+ velocity: e.velocity,
19420
19498
  when: e.when,
19421
19499
  duration: e.duration
19422
19500
  });
@@ -19733,6 +19811,7 @@ var createDtmStudio = async (options = {}) => {
19733
19811
  destination: getChannelStrip(e.trackId).input,
19734
19812
  pitch: e.pitch,
19735
19813
  volume: e.volume,
19814
+ velocity: e.velocity,
19736
19815
  when: e.when,
19737
19816
  duration: e.duration
19738
19817
  });
@@ -19819,6 +19898,7 @@ var createDtmStudio = async (options = {}) => {
19819
19898
  destination: getChannelStrip(e.trackId).input,
19820
19899
  pitch: e.pitch,
19821
19900
  volume: e.volume,
19901
+ velocity: e.velocity,
19822
19902
  when: e.when,
19823
19903
  duration: e.duration
19824
19904
  });
@@ -19898,6 +19978,7 @@ var createDtmStudio = async (options = {}) => {
19898
19978
  destination: getChannelStrip(e.trackId).input,
19899
19979
  pitch: e.pitch,
19900
19980
  volume: e.volume,
19981
+ velocity: e.velocity,
19901
19982
  when: e.when,
19902
19983
  duration: e.duration
19903
19984
  });
@@ -19937,6 +20018,7 @@ var createDtmStudio = async (options = {}) => {
19937
20018
  destination: getChannelStrip(e.trackId).input,
19938
20019
  pitch: e.pitch,
19939
20020
  volume: e.volume,
20021
+ velocity: e.velocity,
19940
20022
  when: e.when,
19941
20023
  duration: e.duration
19942
20024
  });
@@ -19993,6 +20075,7 @@ var createDtmStudio = async (options = {}) => {
19993
20075
  destination: getChannelStrip("chord").input,
19994
20076
  pitch: e.pitch,
19995
20077
  volume: e.volume,
20078
+ velocity: e.velocity,
19996
20079
  when: e.when,
19997
20080
  duration: e.duration
19998
20081
  });
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.1",
5
+ "version": "1.0.3",
6
6
  "description": "MMLを中間言語に用いた、モバイルファーストなDAW / ピアノロール打ち込みコンポーネント",
7
7
  "homepage": "https://onjmin.github.io/dtm",
8
8
  "repository": {