何かあれば GitHub のリポジトリに issue を作るか ryukau@gmail.com までお気軽にどうぞ。


インデックスに戻る

Update: 2024-08-06

Table of Contents

数式上は \(\phi\)\(n\) が循環参照になっていますが、実装では \(\phi\) から \(n\) を計算できます。

class PTROscillator:
    def __init__(self, samplerate, frequency):
        self.phi = 0.0
        self.T = frequency / samplerate
        self.h = 1.0

    def process(self):
        self.phi += self.T
        if self.phi >= 1.0:
            self.phi -= 1.0
        return self.PTRSaw3(self.phi, self.T, self.h)

    def PTRSaw3(self, phi, T, h):
        n = phi / T
        if n >= 3.0:
            return 2*T*n-3*T-1
        if 0.0 <= n and n < 1.0:
            return -(h*n**3)/3+2*T*n+2*h-3*T-1
        if 1.0 <= n and n < 2.0:
            return (2*h*n**3)/3-3*h*n**2+3*h*n+2*T*n+h-3*T-1
        if 2.0 <= n and n < 3.0:
            return -(h*n**3)/3+3*h*n**2-9*h*n+2*T*n+9*h-3*T-1
        return 0  # 念のため。

以下のコードを wxMaxima にコピペすれば式が得られます。

DPWSaw(N) := block([result: [], eq: x0],
  integration_constant: 'C,
  reset(integration_constant_counter),
  for i: 1 thru N do block([a, c],
    eq: integrate(eq, x),
    a: rhs(solve(eq, concat('C, i))[1]),
    if i > 1 then (
      c: rhs(solve(subst(1, x, a) = subst(-1, x, a), concat('C, i - 1))[1]),
      a: subst(c, concat('C, i - 1), a),
      eq: subst(c, concat('C, i - 1), eq)
    ),
    a: num(a),
    a: a / coeff(a, x, hipow(a, x)),
    result: endcons(expand(a), result)
  ),
  push(x, result)
);

PTRSaw(dpw, N) := block([ptr],
  removeTinverse(eq) := block([r: 0],
    if atom(eq) then r: eq
    else for term in args(eq) do block([den],
      den: denom(term),
      if atom(den) then
        if den # T then r: rterm
    ),
    r
  ),

  phi(n, boundary) :=
    if boundary = 'n or n > boundary then n * T
    else n * T + h,

  s(n, boundary) := 2 * phi(n, boundary) - 1,

  y(dpw, N) := block([M: N - 1, result: []],
    for b: 0 thru M do block([eq: 0],
      for k: 0 thru M do
        eq: eq + (-1)^k * binomial(M, k) * subst(s(n - k, n - b), x, dpw[N]),
      result: endcons(eq, result)
    ),
    result / (2 * T)^(N - 1) / N!
  ),

  ptr: expand(ratsimp(y(dpw, N))),
  for i: 1 thru length(ptr) do ptr[i]: removeTinverse(ptr[i]),
  ptr
);

dispptrsaw(dpwsaw) := block([N: length(dpwsaw)],
  for i: 1 thru N do block([eq],
    eq: PTRSaw(dpwsaw, i),

    disp(concat('PTRSaw, i - 1)),
    disp([(length(eq) - 1) * T <= phi, eq[1]]),
    for j: 2 thru length(eq) do
      disp([(j - 2) * T <= phi and phi < (j - 1) * T, eq[j]])
  )
);

dpwsaw: DPWSaw(10);
dispptrsaw(dpwsaw);

/* format.py で利用。 */
/*
dispptrlist(dpw, ptrfunc) := block([L: []],
  for i: 1 thru length(dpw) do L: endcons(ptrfunc(dpw, i), L),
  L
);
dispptrlist(dpwsaw, PTRSaw);
*/

位相が \([0, 1)\) で一周するとき、 \([0, 0.5)\) の範囲の計算式だけを求めています。

導出中の遷移領域の位置 k と、波形の不連続点 b の関係に応じて絶対値 abs(x)x-x に置き換えています。位相が \([0, 1)\) で一周するとき、 \([0, 0.5)\) の範囲の計算式だけを求めています。

Saw とは \(\phi\)\(s\) が異なります。スケーリング係数は Saw の 2 倍です。

DPWTri(N) := block([result: []],
  tri(N) := block([poly: 0, poly_diff: [], A: [], D],
    for i: 1 thru N do (
      if oddp(i) then
        poly: polyif iN then x^i else concat(a, i) * x^i
      elseif iN then
        poly: poly + x^(i - 1) * abs(x)
    ),

    D: subst(x, abs(x), poly),
    D: diff(D, x),
    poly_diff: endcons(D, poly_diff),
    while hipow(D, x) > 1 do (
      D: diff(D, x, 2),
      poly_diff: endcons(D, poly_diff)
    ),

    for i: length(poly_diff) step -1 thru 1 do (
      D: solve(subst(1 / 2, x, poly_diff[i]) = 0, concat(a, i2 - 1)),
      A: append(A, subst(A, D))
    ),

    subst(A, poly)
  ),

  for i: 1 thru N do result: endcons(tri(i), result),
  result
);

PTRTri(dpw, N) := block([ptr: []],
  phi(n, boundary) :=
    if boundary = 'n or n > boundary then n * T
    else nT1,

  s_odd(n, boundary) := 1 / 2abs(1 - 2 * phi(n, boundary)),
  s_even(n, boundary) := 1 - 2 * phi(n, boundary),

  replaceabs(eq, sign) := block(
    f(x) := if not atom(x) and op(x) = abs then signpart(x, 1) else x,
    if atom(eq) then eq else scanmap(f, eq)
  ),

  y(dpw, N, s) := block([M: N - 1, result: []],
    for b: 0 thru M do block([eq: 0, boundary],
      boundary: 'n - b,
      for k: 0 thru M do block([abs_x],
        abs_x: if b = 0 then x elseif kb then x else -x,
        eq: eq + (-1)^k * binomial(M, k) * replaceabs(
          subst(s(n - k, boundary), x, dpw[N]),
          if b = 0 then -1 elseif kb then -1 else 1
        )
      ),
      result: endcons(eq, result)
    ),
    result * 2 / (2 * T)^(N - 1) / N!
  ),

  ptr: y(dpw, N, if oddp(N) then s_odd else s_even),
  ptr: expand(ratsimp(ptr)),
  ptr
);

dispptrtri(dpwtri) := block([N: length(dpwtri)],
  for i: 1 thru N do block([eq],
    eq: PTRTri(dpwtri, i),
    disp(concat('PTRTri, i - 1)),
    for j: 1 thru length(eq) do disp([eq[j]])
  )
);

dispptrlist(dpw) := block([L: []],
  for i: 1 thru length(dpw) do L: endcons(PTRTri(dpw, i), L),
  L
);

dpwtri: DPWTri(11);
dispptrtri(dpwtri);

Saw を元にして境界の向こう側を 0 に置き直しました。

DPWRamp(N) := block([result: [], eq: x10],
  integration_constant: 'C,
  reset(integration_constant_counter),
  for i: 1 thru N do block([a, c],
    eq: integrate(eq, x),
    a: rhs(solve(eq, concat('C, i))[1]),
    if i > 1 then (
      /* solve の式を変更。 */
      c: rhs(solve(subst(-1, x, a) = 0, concat('C, i - 1))[1]),
      a: subst(c, concat('C, i - 1), a),
      eq: subst(c, concat('C, i - 1), eq)
    ),
    a: num(a),
    a: a / coeff(a, x, hipow(a, x)),
    result: endcons(expand(a), result)
  ),
  push(x, result)
);

PTRRamp(dpw, N) := block([ptr],
  removeTinverse(eq) := block([r: 0],
    if atom(eq) then r: eq
    else for term in args(eq) do block([den],
      den: denom(term),
      if atom(den) then
        if den # T then r: rterm
    ),
    r
  ),

  phi(n, boundary) :=
    if boundary = 'n or n > boundary then n * T
    else 0, /* 境界を超えると 0 。 */

  s(n, boundary) := 2 * phi(n, boundary) - 1,

  y(dpw, N) := block([M: N - 1, result: []],
    for b: 0 thru M do block([eq: 0],
      for k: 0 thru M do
        eq: eq + (-1)^k * binomial(M, k) * subst(s(n - k, n - b), x, dpw[N]),
      result: endcons(eq, result)
    ),
    result / (2 * T)^(N - 1) / N!
  ),

  ptr: expand(ratsimp(y(dpw, N))),
  for i: 1 thru length(ptr) do ptr[i]: removeTinverse(ptr[i]),
  ptr
);

dispptrramp(dpwramp) := block([N: length(dpwramp)],
  for i: 1 thru N do block([eq],
    eq: PTRRamp(dpwramp, i),

    disp(concat('PTRRamp, i - 1)),
    disp([(length(eq) - 1) * T <= phi, eq[1]]),
    for j: 2 thru length(eq) do
      disp([(j - 2) * T <= phi and phi < (j - 1) * T, eq[j]])
  )
);

dispptrlist(dpw) := block([L: []],
  for i: 1 thru length(dpw) do L: endcons(PTRRamp(dpw, i), L),
  L
);

dpwramp: DPWRamp(10);
dispptrramp(dpwramp);

Ramp 関数の微分です。

DPWStep(N) := block([result: [], eq: x10],
  integration_constant: 'C,
  reset(integration_constant_counter),
  for i: 1 thru N do block([a, c],
    eq: integrate(eq, x),
    a: rhs(solve(eq, concat('C, i))[1]),
    if i > 1 then (
      c: rhs(solve(subst(-1, x, a) = 0, concat('C, i - 1))[1]),
      a: subst(c, concat('C, i - 1), a),
      eq: subst(c, concat('C, i - 1), eq)
    ),
    a: num(a),
    a: a / coeff(a, x, hipow(a, x)),
    result: endcons(diff(expand(a), x), result) /* diff を追加。 */
  ),
  push(x, result)
);

PTRStep(dpw, N) := block([ptr],
  removeTinverse(eq) := block([r: 0],
    if atom(eq) then r: eq
    else for term in args(eq) do block([den],
      den: denom(term),
      if atom(den) then
        if den # T then r: rterm
    ),
    r
  ),

  phi(n, boundary) :=
    if boundary = 'n or n > boundary then n * T
    else h, /* 0 でも h でも結果が同じになる。 */

  s(n, boundary) := 2 * phi(n, boundary) - 1,

  y(dpw, N) := block([M: N - 1, result: []],
    for b: 0 thru M do block([eq: 0],
      for k: 0 thru M do
        eq: eq + (-1)^k * binomial(M, k) * subst(s(n - k, n - b), x, dpw[N]),
      result: endcons(eq, result)
    ),
    result / (2 * T)^(N - 1) / N!
  ),

  ptr: expand(ratsimp(y(dpw, N))),
  for i: 1 thru length(ptr) do ptr[i]: removeTinverse(ptr[i]),
  ptr
);

dpwstep: DPWStep(10);
dispptrramp(dpwstep);