音声信号処理の基礎の基礎

DirectSound を使って音声信号をキャプチャし,フィルタリングするコードを書く機会があった.


処理内容は以下のようなもの.

  • ユーザが作成したフィルタを動的にロード
  • デバイスとフィルタを選択する.
  • 再生(停止可)


処理の流れはだいたいこんな感じに.


作りはオーソドックスに.今回,初めて Managed DirectX を使った.


以下作成したコード.何かの参考に.
SoundFilter
※FirFilterは16bitステレオ限定です.直さないと.


DirectSound は今まで使ったことなかったので,これ↓を参照しながら作った.

フィルタ係数の確認

↓とてもありがたいサイト
http://momiji.i.ishikawa-nct.ac.jp/dfdesign/fir/mado.shtml

↑ここでできちゃうけど,後学のために自分でもやってみる.

# -*- coding: utf-8 -*-
import math
PI = math.pi
fs = 44100 # サンプリング
fc = 4000  # カットオフ
M = 10     # 遅延数
N = M*2+1  # 係数の総数

wc = PI*(1 - 2.0*fc/fs)   # サンプリング定理により負にならない

h = [0 for i in range(N)]

# n=0 だけ特殊
h[N/2] = (1 - 2.0*fc/fs)  # n = 0: h[n] = wc/PI
for i in [t for t in range(-M, M+1) if not t==0]:
    h[N/2+i] = (1.0/(PI*i))*math.sin(i*wc)

# ハイパス化
for i in range(-M, M+1):
    h[N/2+i] = ((-1)**i)*h[N/2+i]

# ハミング窓
hamming = lambda i: 0.54 + 0.46*math.cos(i*PI/M)
for i in range(-M, M+1):
    h[N/2+i] = hamming(i)*h[N/2+i]

print h

C#で数値計算&統計処理(そんなにすごくない版)


以前↓ここで紹介したやつです.
http://d.hatena.ne.jp/KrdLab/20071003


かたちになってきたので,公開してみようと思います.

(2012/11/11: github に移りました → http://d.hatena.ne.jp/KrdLab/20121111/1352633124)


まだα版,といった感じですが,もし良かったら使ってみてください.
まだまだ機能が貧弱なので,もうちょいがんばる予定です.
(2009/05/07: Lisys-0.6.4 をリリースしましたhttp://d.hatena.ne.jp/KrdLab/20090507/1241626831)


開発環境

  • WindowsXP Home Edition SP2(Professionalが欲しいなぁ)
  • Visual Studio 2005 SP1


なんか動かねぇぞ?ってひとは,
http://www.microsoft.com/downloads/details.aspx?displaylang=ja&FamilyID=200b2fd9-ae1a-4a14-984d-389c36f85647
をインストールしてみてください.


ライブラリの利用については,特に制限を設けておりません.

ソースコードに関しては,煮るなり焼くなり好きにしてください.
(クラス構成については,一度洗い直す必要があります)



お手柔らかに...

ライブラリの現状

C#で数値計算(その8) - KrdLabの不定期日記で作っていたライブラリを,最近になって書き直しています.
※公開してみました→http://d.hatena.ne.jp/KrdLab/20080101/1199199036

そもそも,このライブラリは・・・

目的:

大学で頑張る研究者の方に,.NET Framework 上(C#とかVBとか)で使える数値計算・統計処理機能を提供したい
(え? .NET Framework 便利じゃないですか?)

実装方針:

既存の資産を最大限活用する(実際,内部ではCLAPACKとGSLを使っています)

実装済み項目:

  • 行列・ベクトル
  • 逆行列計算
  • 固有値分解
  • 特異値分解
  • 連立方程式の解
  • 相関分析(といっても相関行列を求めるだけ)
  • 重回帰分析
  • 主成分分析
  • 判別分析
  • t検定
  • f検定
  • XSV(StreamとMatrixの相互変換)
  • Wgnuplot(gnuplotにコマンドを送る)

実装予定項目:

  • LU分解
  • マハラノビス距離を用いた判別分析
  • 分散分析

その他いろいろ(必要になったら適宜追加)

その他

  • 何たら分解系はCLAPACKを,それ以外の数値計算処理はGSLを使用しています.
  • 統計処理機能は自前で実装
  • ソースコードは公開予定(というかGSLをリンクしている時点で公開の義務が発生している)
  • 最近は本業の関係で,開発が遅れ気味(言い訳)

サンプル:

// 固有値分解
{
    Matrix m = new Matrix(  new RowVector( 0, 1, -1),
                            new RowVector(-1, 1,  0),
                            new RowVector( 2, 1,  0));

    EigenvalueDecomposition evd = new EigenvalueDecomposition(m);
    evd.Sort(EigenvalueDecomposition.SortOrder.Descending);
}
// 重回帰分析とgnuplotによる表示
ColumnVector Y = new ColumnVector(41.7, /* たくさんのデータ */, 48.6);
RowVector[] Xs = new RowVector[]{
    new RowVector( 7, 16),
    
    // たくさんのデータ
    
    new RowVector(15, 18)
};

MultipleLinearRegressionAnalysis mra = new MultipleLinearRegressionAnalysis(Y, Xs);

using (Wgnuplot plot = new Wgnuplot())
{
    XsvFormat format = new XsvFormat();
    format.DataFormat = XsvDataFormatType.Table;
    format.Encoding = Encoding.GetEncoding("shift_jis");
    format.HasHeader = false;
    format.HasIndexes = false;
    format.Separator = '\t';

    using (XSV xsv = new XSV(new FileStream("tmp.dat", FileMode.Create), format))
    {
        // gnuplotのデータフォーマットにする
        Matrix m = new Matrix(Xs.Length, 3);
        for (int r = 0; r < m.RowSize; ++r)
        {
            for (int c = 0; c < m.ColumnSize - 1; ++c)
            {
                m[r, c] = Xs[r][c];
            }
            m[r, 2] = Y[r];
        }
        xsv.Write(m);
    }

    // コマンドを送る
    IVector C = mra.CoefficientVector;
    plot.Send("splot " + C[0] + "+x*" + C[1] + "+y*" + C[2]);
    plot.Send("replot \"tmp.dat\"");
    System.Console.Read();  // 入力待ちにする
}

NDocとNUnitを使っているのですが,SandCastleというのもあるみたい.
ふにゃるんさんが,かなり詳しく紹介してくれています.→Sandcastle Help File Builder を使って、ソースコードからドキュメントを自動作成しよう - ふにゃるん
の,乗り換えるか!?

Gnuplotを操る

http://d.hatena.ne.jp/KrdLab/20060528/1148815875
でもやったけど,今回はC#オンリーで書いてみようかと.
自分の復習を兼ねてます.


Spy++ で調べてみると,コマンド入力ウィンドウのウィンドウクラスは "wgnuplot_text" です.

基本的な方針としては,

  1. プロセス起動
  2. プロセスのメインスレッドに関連づけられている親ウィンドウを取得
  3. 子ウィンドウの中から "wgnuplot_text" を持つウィンドウを検索
  4. ウィンドウハンドルを取得
  5. PostMessage を用いて,コマンドを送信


なので,

  1. Process
  2. Process.MainWindowHandle
  3. P/Invoke で FindWindowEx
  4. P/Invoke で PostMessage


となります.
おそらく,一番オーソドックスな方法ではないかと.(2009/01/06 追記:より簡潔な方法は,コメント欄を参照してください)

http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/jpwinui/html/_win32_findwindowex.asp
http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/jpwinui/html/_win32_postmessage.asp

P/Invokeの宣言がまとめてあるサイト → http://www.pinvoke.net/

P/Invokeのサンプル → http://msdn2.microsoft.com/ja-jp/library/fzhhdwae(VS.80).aspx

成果物

(2013/02/05 追記: こちらに移りました → https://github.com/krdlab/wgnuplot4cs)

(興味のある方は,是非改造してください.そして,それを私にください.f^_^; <おい!)
(2008/03/19 配置場所を変えました.)

使い方

  1. 自分のプロジェクトに Wgnuplot.dll を参照させる
  2. サンプルみたいに呼び出す
  3. 自分が作成した実行ファイルと同じフォルダに wgnuplot.exe を配置する
  4. 実行!

サンプル

namespace KrdLab.Test
{
    class Program
    {
        static void Main(string[] args)
        {
            using (Wgnuplot plot = new Wgnuplot())
            {
                plot.Send("splot x**2 + y**2");
                System.Console.Read();
            }
        }
    }
}

Assemblyの動的ロード

アプリケーションディレクトリ
    |-App.exe (アプリケーション本体)
    |-dlls
       |-Hoge
           |-Hoge.dll(App.exeからロードされるアセンブリ)
           |-Util.dll(Hoge.dllが参照しているアセンブリ)

App.exe.configで,dllsディレクトリをプロービング対象に追加しておきます.


普通に

Assembly asm = Assembly.Load("Hoge");  // もしくはFullNameを指定

とすれば,Hoge.dll のロードには成功しますが,Hoge.dll 内のクラスが Util.dll を利用しようとした段階でFileNotFoundException が throw されてしまいます.


想定しているシチュエーションは,App.exe は Hoge.dll のことしか知らない,という状態です.
App.exeとしては,「Util.dllをくれ」といわれても,どこからロードすればよいのかわからない,ということなのでしょう(Util.dllの解決時に,Hogeディレクトリ以下はプロービング対象にならない).

アプリケーション構成ファイルに,privatePathを追加すればロードできるようになるのですが,アセンブリを追加するたびにパスも追加する,というのは避けたいところです.

全てのアセンブリをごちゃ混ぜにして(Hogeディレクトリをなくして),dllsに配置するしかないのでしょうか?

WndProc を Hook する

前述のように,Clipboard が更新されたことを知るためには,対象となるフォームのウィンドウプロシージャを override する必要があります.でも,何となく Clipboard のために override した WndProc を,Form のコードに書きたくないなぁ,とか思ったので,ウィンドウプロシージャを Hook することにしました.調べてみると,NativeWindow クラス (System.Windows.Forms) という,便利なクラスがあります.


これを使ってみることにしました.

public class ClipboardHelper
{
    #region イベント定義

    /// <summary>
    /// クリップボードの内容が更新されたときに発生するイベント
    /// </summary>
    public event EventHandler DrawClipboard = null;

    protected void OnDrawClipboard()
    {
        if (this.DrawClipboard != null)
        {
            this.DrawClipboard(this, new EventArgs());
        }
    }

    #endregion

    /// <summary>
    /// ウィンドウプロシージャをHookするクラス
    /// </summary>
    private class Hook : NativeWindow
    {
        private ClipboardHelper _helper = null;

        private IntPtr nextHandle = IntPtr.Zero;
        private const int WM_DRAWCLIPBOARD = 0x0308;
        private const int WM_CHANGECBCHAIN = 0x030D;

        [DllImport("user32.dll")]
        private static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
        [DllImport("user32.dll")]
        private static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private extern static int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);


        public Hook(Form target, ClipboardHelper helper)
        {
            target.Load += new EventHandler(target_Load);
            target.FormClosed += new FormClosedEventHandler(target_FormClosed);
            target.HandleCreated += new EventHandler(target_HandleCreated);
            target.HandleDestroyed += new EventHandler(target_HandleDestroyed);

            this._helper = helper;
        }

        void target_Load(object sender, EventArgs e)
        {
            this.nextHandle = SetClipboardViewer(((Form)sender).Handle);
        }

        void target_FormClosed(object sender, FormClosedEventArgs e)
        {
            ChangeClipboardChain(((Form)sender).Handle, this.nextHandle);
        }

        void target_HandleCreated(object sender, EventArgs e)
        {
            AssignHandle(((Form)sender).Handle);
        }

        void target_HandleDestroyed(object sender, EventArgs e)
        {
            ReleaseHandle();
        }


        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_DRAWCLIPBOARD:
                    
                    this._helper.OnDrawClipboard();

                    if (this.nextHandle != IntPtr.Zero)
                    {
                        SendMessage(this.nextHandle, m.Msg, m.WParam, m.LParam);
                    }
                    break;

                case WM_CHANGECBCHAIN:
                    if ((IntPtr)m.WParam == this.nextHandle)
                    {
                        this.nextHandle = (IntPtr)m.LParam;
                    }
                    else if (this.nextHandle != IntPtr.Zero)
                    {
                        SendMessage(this.nextHandle, m.Msg, m.WParam, m.LParam);
                    }
                    break;
            }
            base.WndProc(ref m);
        }
    }
    private Hook hook = null;

    public ClipboardHelper(Form wnd)
    {
        this.hook = new Hook(wnd, this);
    }
}