何かあれば GitHub のリポジトリに issue を作るか ryukau@gmail.com までお気軽にどうぞ。
Update: 2025-01-07
WobblingMetalBoard を作っているときに次のエンベロープを思いつきました。
env(t) := t^α * exp(-β * t);
env_d1: diff(env(t), t);
env_d2: diff(env_d1, t);
solve(env_d1 = 0, t);
次の解が得られました。
実際に計算してみると
アタックの長さから振幅を
NumPy を使った実装例です。
import numpy
def expPoly(α, β, time):
return time**α * numpy.exp(-β * time)
def expPolyNormalized(α, β, time):
= expPoly(α, β, α / β)
normalize return time**α * numpy.exp(-β * time) / normalize
= numpy.linspace(0, 16, 1024)
time = expPolyNormalized(1, 1, time) envelope
solve x = t^a * exp(-b * t) / eta for t
結果です。
正しい値がでるのか試しました。
import numpy
import matplotlib.pyplot as pyplot
import scipy.special.lambertw as lambertw
def getTime(α, β, x, normalize, k=0):
return -α * lambertw(-β * (x * normalize)**(1 / α) / α, k) / β
def expPoly(α, β, time):
return time**α * numpy.exp(-β * time)
def expPolyNormalized(α, β, time):
= expPoly(α, β, α / β)
normalize return (
**α * numpy.exp(-β * time) / normalize,
time
normalize,
)
= 48000
samplerate = 8
duration
= numpy.linspace(0, duration, duration * samplerate)
time = 1
alpha = 1
beta
= expPolyNormalized(alpha, beta, time)
curve, normalize
= numpy.linspace(1, 0, 10)
xx = getTime(alpha, beta, xx, normalize, 0)
tt0 = expPolyNormalized(alpha, beta, tt0)
value_tt0, _
# k = 0 以外の場合は省略。
pyplot.plot(time, curve)="red", label="k=0")
pyplot.scatter(tt0, value_tt0, color pyplot.show()
結果です。
inf
になります。そこで
nan
や
inf
でなければ取り得る全ての値の範囲で計算に問題が無いことが分かります。
import numpy
def printTimeMax(dtype):
= numpy.finfo(dtype).max
fmax = numpy.array([2**i for i in range(1, 16)], dtype=dtype)
alpha = numpy.power(fmax, dtype(1) / alpha, dtype=dtype)
timeMax print(dtype)
for a, t in zip(alpha, timeMax):
print(f"{a:9.1f}, {t}")
printTimeMax(numpy.float32) printTimeMax(numpy.float64)
整形した出力です。左の列が
<class 'numpy.float32'>
2.0, 1.8446742974197924e+19
4.0, 4294967296.0
8.0, 65536.0
16.0, 256.0
32.0, 16.0
64.0, 4.0
128.0, 2.0
256.0, 1.4142135381698608
512.0, 1.1892070770263672
1024.0, 1.0905077457427979
2048.0, 1.0442737340927124
4096.0, 1.0218971967697144
8192.0, 1.0108892917633057
16384.0, 1.0054298639297485
32768.0, 1.002711296081543
<class 'numpy.float64'>
2.0, 1.3407807929942596e+154
4.0, 1.157920892373162e+77
8.0, 3.402823669209385e+38
16.0, 1.8446744073709552e+19
32.0, 4294967296.0
64.0, 65536.0
128.0, 256.0
256.0, 16.0
512.0, 4.0
1024.0, 2.0
2048.0, 1.4142135623730951
4096.0, 1.189207115002721
8192.0, 1.0905077326652577
16384.0, 1.0442737824274138
32768.0, 1.0218971486541166
正規化係数
C++ による実装例です。
#include <cmath>
#include <cfloat>
class ExpPolyEnvelope {
public:
// attack の単位は秒。
// curve は任意の値。 β に相当。
// attack と curve が大きいと計算結果が inf になるときがあるので注意。
void reset(double sampleRate, double attack, double curve)
{
= attack * curve;
alpha
auto betaMin = alpha / getTerminationTime();
if (curve < betaMin) curve = betaMin;
= pow(alpha / curve, alpha) * exp(-alpha);
peak = exp(-curve / sampleRate);
gamma = 1.0 / sampleRate;
tick
= 0.0;
time = 1.0;
value }
bool isReleasing() { return time >= attack; }
double getTerminationTime() { return pow(DBL_MAX, 1.0 / alpha); }
double process()
{
auto output = pow(time, alpha) * value / peak;
if (!std::isfinite(output)) return 0.0; // 念のため。
+= tick;
time *= gamma;
value return output;
}
protected:
double value = 0;
double peak = 1;
double gamma = 0;
double attack = 0;
double tick = 0;
double alpha = 0;
double time = 0;
};
テストコードへのリンクです。
テスト結果です。
ExpPoly エンベロープを GenericDrum の衝突の計算に応用できそうな気がしたので、以下の区間積分が解けるかどうか試しました。
ここでの応用はエネルギーを時間方向に薄く延ばすことです。つまり、任意の正の実数
SymPy で積分します。 Maxima の integrate
では解けず、
Wolfram Alpha では計算時間切れになりました。
import sympy
= sympy.Symbol("t", real=True, positive=True)
t = sympy.Symbol("α", real=True, positive=True)
α = sympy.Symbol("β", real=True, positive=True)
β = sympy.Symbol("τ", real=True, positive=True)
τ
= sympy.integrate(t**α * sympy.E ** (-β * t), (t, τ, sympy.oo))
result print(sympy.latex(result))
出力です。
以下の関数が使われています。
整理します。
上の SymPy のコードに以下をつけ足して解こうとしたのですが、
NotImplementedError
が出て行き詰りました。
= sympy.Symbol("x", real=True, positive=True)
x sympy.solve(sympy.Eq(result, x), τ)