落胆がらくた街

多分三日坊主で終わると思うんですけど

もっと真面目にパナ改を作った

suzublog.hatenablog.com

 元ネタの「パナ改」に若干の改良が加えられており、ここにきてようやく自分でも回路の動作原理が理解できたので部品選定しつつまた作った。
1804 :「ファンタム式パナ改マイク fetⅡ」誕生8年記念。進化した回路を発表 | ShinさんのPA工作室

結論としてはホワイトノイズの低減に成功しつつ、コストと作りやすさが向上するという満足行く結果が出た。
テスト結果は以下のように。

↓あたらしいの


↓ふるいの

詳細は以下より。

続きを読む

バイノーラルマイクと簡易アンプ作ったりした

これでキモオタ童貞おじさんの僕とデート気分を楽しめる音声作品が作れるぞ。テロ認定されそう。

続きを読む

JUCEプログラミング::Projucerで作ったLabel/Textの色が反映されない

JUCEのAudioPluginをProjucerのGUIデザイナで作ってると、Labelの色情報が正しく反映されない。

f:id:suzumodoki:20180224094609j:plain f:id:suzumodoki:20180224094611j:plain

色設定のコードおかしくね?みたいな記事を昨日書いたんだけど違ったから消した。

どうも、デフォルトの設定でOK!wとしてると色設定コードを吐いてくれないらしい。
一旦色設定画面↓を開くだけ開いて閉じるとコードがちゃんと生成される。なんじゃそりゃ。
f:id:suzumodoki:20180225103619j:plain

UINT32正規化版高速sinは何をしてるのか

suzublog.hatenablog.com
この記事でやってる、0~2PIをUINT32に正規化してやると高速でSinを計算できるという話。
ようやくこいつが何をしてるのか理解できたので忘れない内に書いとく。

続きを読む

2PIでループする位相変数をUINTに正規化すると美味い

http://www.musicdsp.org/archive.php?classid=1
これの Fast SIN approximation for usage in e.g. additive synthesizers を試してみた。

float fastsin(UINT32 phase)
{
    const float frf3 = -1.0f / 6.0f;
    const float frf5 = 1.0f / 120.0f;
    const float frf7 = -1.0f / 5040.0f;
    const float frf9 = 1.0f / 362880.0f;
    const float f0pi5 = 1.570796327f;
    float x, x2, asin;
    UINT32 tmp = 0x3f800000 | ( phase >> 7 );
    if (phase & 0x40000000)
        tmp ^= 0x007fffff;
    x = ( *( (float*)&tmp ) - 1.0f ) * f0pi5;
    x2 = x * x;
    asin = ( ( ( ( frf9 * x2 + frf7 ) * x2 + frf5 ) * x2 + frf3 ) * x2 + 1.0f ) * x;
    return ( phase & 0x80000000 ) ? -asin : asin;
}

検証コード↓

int main() {
    timerInit();

    float delta_f = 2 * PI * 1000 / SRATE;
    float phase_f = 0;
    UINT32 delta_i = 1000.0 / SRATE * UINT32_MAX;
    UINT32 phase_i = 0;
    

    //特に工夫無く、位相phaseと角速度deltaをfloatにしてmath.hのsinfを呼ぶ
    double t = 0;
    float test = 0;

    for (size_t i = 0; i < SRATE; i++)
    {
        timerStart();

        test += sinf(phase_f);
        phase_f += delta_f;

        t += timerStop();

        if (phase_f >= 2 * PI)phase_f -= 2 * PI; //2PIで位相がループするのはとりあえず時間計測の外とする
    }

    cout << "sinf(float)::" << "total:" << t << "[ms]\n";

    //UINT32正規化版
    t = 0;
    float test2 = 0;

    for (size_t i = 0; i < SRATE; i++)
    {
        timerStart();

        test2 += fastsin(phase_i);
        phase_i += delta_i;

        t += timerStop();
    }

    cout << "sinf(UINT32)::" << "total:" << t << "[ms]\n";

    cout << "test:" << test << ", test2:" << test2 << endl; //何らかの形で変数を参照してやらないと最適化で省かれる

}

結果。

sinf(float)::total:3.06791[ms]
sinf(UINT32)::total:0.292572[ms]
test:0.0168861, test2:6.52373e-05

UINT32、速い(確信)。
ちなみに計測環境はWin8.1、cpu:intel 4690k、DRAM 16GBでコンパイル環境はVisual studio 2015のVisual C++、x64 Releaseビルドで最適化レベル最大。
sinの出力結果の総和が微妙に異なってるのは精度の問題なんだが、別に精度計測のソース書いたら1サンプル毎の精度は十分だったので割愛。


位相をUINT32に正規化するメリットを考えてみる。

  • 検証コードでは時間計測から省いたけど、真面目に2PIでループさせようとする場合1サンプル毎に位相が2PI超えたら戻す処理がいる。UINTならオーバーフローするので不要。
  • 位相を元にテーブルを引きたいみたいな時、位相変数の上何桁と下何桁を分けるみたいな処理が高速で出来る float->intキャストよりずっとはやい!
  • fastsinが速い
  • 位相変数そのものの計算もビット演算が使える分そこそこ速い。特に頻出の2n倍が高速なのは嬉しい

とかこんな感じかな。結構嬉しいと思うんだけど、逆にデメリットはどんなんあるんだろう。誰か教えてください。

デジタル信号処理チートシートと、BLITと殴り合ったメモ

デジタル信号処理でよく出てくる式とか変数とか関係をリストにしてみた
シンセとかプログラミングするのにこんがらがった時はどうぞ


  • int  R:サンプルレート。殆どの場合、44100か48000のどっちか。ほぼ定数とみなして良い。
  • int  F=R/2:ナイキストレート。やはり実質ほぼ定数で、22050または24000。
  • float  f:鳴らしたい周波数[Hz]。↓の f_{min}を用いて、 f_{min} \leq f \leq Fになる。
  • float  f_{min}:鳴らしたい周波数の下限。大抵10[Hz]とかそんなもん。
  • float  T=1/f:周期。単位は秒。
  • float  p:現在の位相。2πでループするんだから、 0 \leq p \leq 2\pi
  • float  {\omega}=2{\pi}f:fにおける角速度。つまりf[Hz]のサイン波は現実世界では sin({\omega})=sin(2{\pi}f)
  • float  d=2{\pi}f/R:1サンプル毎に進む位相差。つまり1サンプル処理する毎にp+=d; if(p>=2*PI) p-=2*PI;をするということ。 \omegaのデジタル世界版。fの条件から、  0 \leq d \leq \piになる。
  • int  H=(int)F/f=(int){\pi}/d:ある鳴らしたい周波数fにおいて、倍音が何個出せるか(F以下に収まる倍音の数)。整数倍音は全部持つ(ノコギリみたいに)と仮定。
  • float  P=R/f:周波数fの信号が1週するのにかかるサンプル数。ぴったり整数個でループするとは限らないので実数。
  • float  sin(2{\pi}\frac{f}{R}n):サンプルレートR、周波数fのサイン波のn番目の値。

あとBLITのメモ
 \displaystyle \begin{align}
Sinc_{M}(x)=\frac{sin(\pi x)}{Msin(\frac{\pi x}{M})}, \\
y(n)=\frac{M}{P}Sinc_M(\frac{M}{P}n) \\
=\frac{1}{P}\frac{sin(M\frac{\pi}{P}n)}{sin(\frac{\pi}{P}n)}
 \end{align}
ここでMはPを超えない最大の奇数(int)。
こいつを日本語訳すると、 「y(n)はナイキストレートまでの周波数で帯域制限したf[Hz]のインパルス列のn番目の値」。
Pは1周するのにかかるサンプル数なので上記チートシートの通りR/f。つまりサンプルレートを鳴らしたい周波数で割ったもの。
この帯域制限されたインパルス列から帯域制限されたSAWやSQUAREを作れる。
詳しくはg200kg氏のページ参照(必読)。

BLITのお話 | g200kg Music & Software


ところで、おもむろに  P=R/f \Rightarrow \frac{1}{P}=f/R をBLITの式に突っ込んでみよう。
 \displaystyle \begin{align}
y(n)=\frac{1}{P}\frac{sin(M\frac{\pi}{P}n)}{sin(\frac{\pi}{P}n)} \\
=\frac{1}{P}\frac{sin(M{\pi}\frac{f}{R}n)}{sin({\pi}\frac{f}{R}n)}
 \end{align}
sinの中身をよく見てみると、f[Hz]のサイン波が sin(2{\pi}\frac{f}{R}n)なんだから、 sin({\pi}\frac{f}{R}n)は鳴らしたい周波数の半分の速度のsinだ。つまりBLITとは「f/2[Hz]のサインでMf/2[Hz]のサインを割ったもの」と言える。ここから、

BLIT(p)=\frac{f}{R}\frac{sin(M\frac{p}{2})}{sin(\frac{p}{2})}
が得られる。
y(n)は「BLITのn番目の値」を引っ張ってくるわけだが、BLIT波形の1周が綺麗に整数個で収まるとは限らない(=Pが整数とは限らない)ので、BLIT(p)=「p[rad]におけるBLITの値」を算出できるようにしておいたほうが都合がいい場合もあると思う。


F[Hz]に帯域制限されたf[Hz]のインパルス列が、なんでこの式で得られるのか。
式の算出過程は理想インパルス列(デルタ関数級数)を理想LPF(時間領域でsinc関数)と畳み込んだものなんだが、得られた式は何がどういう意味を持っているのか。
早速Maximaさんにグラフを描いてもらった。 f:id:suzumodoki:20180121023528j:plain
極限の扱いが上手く出来ておらず、分母が0の時なにやら変な感じになってしまっているが、理想的には滑らかなsincの列なので脳内補完して図を眺める。
まずBLITの式を簡略化すると、振幅を決める \frac{1}{P}はこの際重要じゃないので無視して、意味があるのは \frac{sin(Mx)}{sin(x)}の部分。
ここで x = \pi\frac{f}{R}n だが一旦おいといて、結局こいつは角速度 MxのSinと xのSinの逆数との積だと言ってるわけだ。

まず \frac{1}{sin(x)}はこんな感じ。 f:id:suzumodoki:20180121025414j:plain
赤線が1/sin(x)なんだが、ソフトに書かせると無限の部分を真面目に描いちゃうので、あえて手書きで概略図にしている。
で、こいつにsin(Mx)をかけると… f:id:suzumodoki:20180121030914j:plain
こんな感じ。イマイチ分かりにくいが、大雑把にまとめると

  • 1/sin(x)が発散するタイミングで、 \lim_{x \to \infty} \frac{sin(Ax)}{x}=AよりMになる(インパルスがぴこっと立つ)
  • よって1/sin(x)は「インパルス列そのものの周波数」に対して影響を及ぼしている。
  • よく見るとx=0,π,2πで1/sin(x)が∞になるので、インパルスが立つのはsin(x)の倍の周期。BLITがf/2のsinで割っているのはこういう理由。
  • 分子のsin(Mx)は帯域制限する周波数に対するパラメータ。事実上Mが帯域制限周波数になる*1
  • もしMが奇数だと、インパルスが立つ瞬間の1/sin(x)とsin(Mx)は同じ符号になるので、必ずインパルスはプラス側に立つ。
  • 逆にMが偶数だと、インパルスがプラスとマイナス交互に立つ。これを「バイポーラBLIT」と言う。
  • 矩形波作りたくてバイポーラBLITをやると周波数が半分になる、というのはこの理屈から1/sin(x)だけ倍の周波数にすれば解決する。多分。

といったところ。


現実的な問題と備忘録。
 Sinc_M(0)=1より BLIT(0)=M/Pであるべきだ。黙ってsin/sinをプログラミングすると、sinはともかく割り算が出るので一々0除算じゃないかチェックしなきゃならず辛い。
この間「sinをテーブル引きにすると速い」というネタを自分の環境で試したら速いどころか100倍以上遅くてゲンナリだったんだけど、Sincとか1/sinみたいなのをテーブル化するんならやぶさかでない気もする。というわけで今後は本格的にどんな実装が速いのかとか、数学よりもっとC++とかx64で効率的なやり方を考えようと思います。どっとはらい(あ、あと位相変数は2πをUINT32に正規化するとオーバーフローのお陰で周期性チェックいらなくて便利じゃねっての思ったのでこれも今度試します)

*1:うまいことやればPを整数に決め打ちしても任意のMで帯域制限できそうなんだけど勉強不足で未検証