C#で音声波形を表示する音楽プレーヤーを作る

#**こんなものを作ります**
![AudioSpectrum](http://f.cl.ly/items/3v2h460h0d1q391N0Q1Q/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%20(56).png)
音楽を再生すると同時に、その音声波形を表示します。
#**C#での図形の描画**
**WPF(Windows Presentation Foundation)**によりアプリケーションを作成しました。
##**WPFとは**
MSDNでは、以下のように説明されています。
`Windows Presentation Foundation (WPF) は、デスクトップクライアントアプリケーションを作成する UI フレームワークです。WPF の開発プラットフォームは、アプリケーション モデル、リソース、コントロール、グラフィックス、レイアウト、データバインディング、ドキュメント、セキュリティなどのさまざまなアプリケーション開発機能の一式をサポートします。`
(https://docs.microsoft.com/ja-jp/visualstudio/designers/getting-started-with-wpf)
###**コードビハインド**
**WPF**では、**XAML**で記述した**GUI**に対して、**C#**などのプログラミング言語を用いてイベント処理を記述することができます。このように、**デザイン**部分と**コーディング**部分を別々のファイルに分けることを**コードビハインド**といいます。
###**WPFアプリケーション in Visual Studio**
**C#**では、一般的に、**Main()**メソッドが**エントリポイント**となり、アプリケーション起動時に呼ばれます。しかし、**Visual Studio**で作成した**WPFアプリケーション**ではこの**Main()**メソッドがありません。
実は、**WPFアプリケーション**では、**Application.xaml(またはApp.xaml)**ファイルをプロジェクトに含めている場合、ビルド時に、**Main()**メソッドが**自動生成**されます。
簡単に言えば、その**Main()**メソッド内に、**MainWindow.xaml**を実行するように書いてあるため、**MainWindow.xaml**が自動で実行されるわけです。
#**NAudioライブラリ**
**NAudioライブラリ**とは、主に**音声データ**を扱うために設計された**ライブラリ**です。

[GitHub - NAudio](https://github.com/naudio/NAudio)
##**NAudioのインストール**
###**GUI**
ソリューション エクスプローラーで対象のプロジェクトの[参照]を右クリックし、[NuGet パッケージの管理]を選択します。そして[参照]を選択し、**NAudio**と検索します。
###**CLI**
メニューの【ツール → NuGet パッケージ マネージャー → パッケージ マネージャー コンソール】からコンソールを開き、
`PM> Install-Package NAudio`
とすると、インストールできます。
#**コード**
まず、要所の説明をし、そのあとでコード全体を示します。
一応、**MainWindow.xaml**も載せておきます。
コードにコメントとして説明を書いているため、詳細の説明は省略させていただきます。
##**イベント**
**WPF**では、**イベント**と**イベントハンドラ**を頻繁に使うので、コードの説明に入る前に、ここで、**イベント**への**イベントハンドラ**の登録の仕方の説明をします。
**イベントハンドラ**とは、**イベント**が発生したときに実行される処理のことをいいます。
詳細の説明を知りたい方は、**イベントハンドラ**、**デリゲート**、**ラムダ式**などでググってみてください。
**イベントハンドラ**は、以下の様に登録します。

```cs
namespace Event
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            // イベントハンドラの登録
            // 以降すべて同じ意味
            
            this.イベント変数 += new EventHandler( this.Func_Event );

            this.イベント変数 += this.Func_Event;

            this.イベント変数 += delegate( object sender, EventArgs e )
            {
                処理A;
            }

            this.イベント変数 += ( sender, e ) => { 処理A; }

            // 処理Aが1文のとき
            this.イベント変数 += ( sender, e ) => 処理A;
        }

        /// <summary>
        /// 処理Aを行うイベントハンドラ
        /// </summary>
        /// <param name="sender">イベント送信元</param>
        /// <param name="e">イベント引数</param>
        private void Func_Event( object sender, EventArgs e )
        {
            処理A;
        }
    }
}

```

##**使用するライブラリ**
```cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

using NAudio.Wave;
using NAudio.Dsp;
```
**Nuget**などで取得します。
##**タイマーの生成**
```cs

            // タイマーの生成
            timer = new DispatcherTimer(DispatcherPriority.Normal);
            // Tickの発生間隔の設定
            timer.Interval = new TimeSpan(reciprocal_of_FPS);
            // タイマーイベントの登録
            timer.Tick += new EventHandler(timer_Tick);
```
```cs
        /// <summary>
        /// Timer.Tickが発生したときのイベントハンドラ
        /// </summary>
        /// <param name="sender">イベント送信元</param>
        /// <param name="e">イベント引数</param>
        private void timer_Tick(object sender, EventArgs e)
        {
            // この中の処理はメインスレッドで行われる

            // 再生位置 (秒)を計算
            playPosition_s = (int)audioStream.Position / bytePerSec;

            // 音声波形表示を描画する配列のオフセット(インデックス)を計算
            drawPosition = (int)(((double)audioStream.Position / (double)audioStream.Length) * result.GetLength(0));
            Make_AudioSpectrum();
        }
```
**タイマー**の機能は、*System.Timers.Timer*クラスで実装されています。しかし、*System.Timers.Timer*クラスは**UIスレッド**とは独立したスレッドで実行されるため、**イベントハンドラ**から**UI**を操作することができません。スレッド上のタスク管理のために*Dispatcher*というクラスが用意されており、**WPF**の**UI**には*Dispatcher*クラスのオブジェクトが関連づけられています。この*Dispatcher*オブジェクトと連携して**UI**を操作できるようにした**タイマー**が*DispatcherTimer*クラスです。

ここでは、オブジェクトを作成する際に、**タイマー**の優先順位として、*Dispatcher.Normal*を指定し、**イベントハンドラ**として*timer_Tick()*メソッドを登録しています。
##**音楽の再生**
```cs

            // 再生するファイル名
            fileName = "music\\sample.wav";

            // ファイル名の拡張子によって、異なるストリームを生成
            audioStream = new AudioFileReader(fileName);
```
```cs
            // コンストラクタを呼んだ際に、Positionが最後尾に移動したため、0に戻す
            audioStream.Position = 0;
```
```cs
            // プレーヤーの生成
            outputDevice = new WaveOutEvent();
            // 音楽ストリームの入力
            outputDevice.Init(audioStream);
```
```cs
            // 音楽の再生 (おそらく非同期処理)
            outputDevice.Play();
```
再生は現在のストリームの位置から開始されるため、*Play()*メソッドで必ずしも最初から再生されるわけではありません。**コンストラクタ**により、オブジェクトを生成した際、*Position*はストリームの終端に移動したため、`audioStream.Position = 0;`というように位置を先頭に戻す必要があります。
また、この*Play()*メソッドは、**非同期処理**であるため、*Play()*メソッドを呼び出した後も、すぐに次のコードが実行されます。つまり、音楽を再生しながら別の処理を行うことができます。
##**ハミング窓と高速フーリエ変換**
```cs
        /// <summary>
        /// 音楽の波形データにハミング窓をかけ、高速フーリエ変換する
        /// </summary>
        /// <returns>フーリエ変換後の音楽データ</returns>
        private float[,] FFT_HammingWindow_ver1()
        {
            // 波形データを配列samplesに格納
            float[] samples = new float[audioStream.Length / audioStream.BlockAlign * audioStream.WaveFormat.Channels];
            audioStream.Read(samples, 0, samples.Length);

            //1サンプルのデータ数
            int fftLength = 256;
            //1サンプルごとに実行するためのイテレータ用変数
            int fftPos = 0;

            // フーリエ変換後の音楽データを格納する配列 (標本化定理より、半分は冗長)
            float[,] result = new float[samples.Length / fftLength, fftLength / 2];

            // 波形データにハミング窓をかけたデータを格納する配列
            Complex[] buffer = new Complex[fftLength];
            for (int i = 0; i < samples.Length; i++)
            {
                // ハミング窓をかける
                buffer[fftPos].X = (float)(samples[i] * FastFourierTransform.HammingWindow(fftPos, fftLength));
                buffer[fftPos].Y = 0.0f;
                fftPos++;

                // 1サンプル分のデータが溜まったとき
                if (fftLength <= fftPos)
                {
                    fftPos = 0;

                    // サンプル数の対数をとる (高速フーリエ変換に使用)
                    int m = (int)Math.Log(fftLength, 2.0);
                    // 高速フーリエ変換
                    FastFourierTransform.FFT(true, m, buffer);

                    for (int k = 0; k < result.GetLength(1); k++)
                    {
                        // 複素数の大きさを計算
                        double diagonal = Math.Sqrt(buffer[k].X * buffer[k].X + buffer[k].Y * buffer[k].Y);
                        // デシベルの値を計算
                        double intensityDB = 10.0 * Math.Log10(diagonal);

                        const double minDB = -60.0;

                        // 音の大きさを百分率に変換
                        double percent = (intensityDB < minDB) ? 1.0 : intensityDB / minDB;
                        // 結果を代入
                        result[i / fftLength, k] = (float)diagonal;
                    }
                }
            }

            return result;
        }
```
**ハミング窓(Hamming Window)**のについての詳しい説明は、ここでは省きます。簡単に言えば、要するに**信号の一部を切り出す**ために、もとの関数にかける**両端がなめらか**に絞られた関数です。

次に、**高速フーリエ変換(Fast Fourier Transform:FFT)**の説明です。
**高速フーリエ変換**とは、**離散フーリエ変換(Discreate Fourier Transform:DFT)**を計算機上で高速に計算する**アルゴリズム**です。
つまり、計算の結果は普通の**離散フーリエ変換**と変わりません。よって、ここでは、本筋からずれることを避けるため、**離散フーリエ変換**の式のみを紹介します。**高速フーリエ変換**の詳細を知りたい方は、ぜひググってみてください。
**複素関数f(x)**の**(N次)離散フーリエ変換**である**複素関数F(t)**は以下で定義されます。

```math
F(t)=\sum_{x=0}^{N-1}f(x)e^{-i\frac{2\pi tx}{N}}
```
**高速フーリエ変換**は、*NAudio.Dsp.FastFourierTransform.FFT ()*メソッドを使えばできます。NAudio、とても優秀ですね！
次に、引数について説明します。
`FastFourierTransform.FFT (bool forward, int m, Complex[] data)`
<dl>
  <dt>forward</dt>
  <dd>おそらく、<em>true</em>で高速フーリエ変換、<em>false</em>で逆高速フーリエ変換なので、今回は<em>true</em>。</dd>
  <dt>m</dt>
  <dd> サンプル数が2のm乗の時のmの値。</dd>
  <dt>data</dt>
  <dd>変換したいデータを入れます。結果もこの配列に入ります。</dd>
</dl>
**離散フーリエ変換**後のデータは、以下のように扱います。配列の要素のインデックスを**k**とすると、

```math
f=\frac{k×f_{sampling}}{N_{sampling}}
```
が成り立ち、その要素は、周波数成分**f**の情報を持ちます。
ただし、**標本化定理(サンプリング定理)**より、**標本化周波数(サンプリング周波数)**が**f_sample**のとき、**f_sample / 2**(**ナイキスト周波数**)以下の原信号が**復元可能**なので、**k < N / 2**となります。
また、その複素数の大きさは振幅の半分に相当します。
その計算のあとは**デシベル**の値を計算し、大きさを百分率で表します。計算方法は上記のコードの通りなので、説明は省略します。
##**音声波形表示の描画**
```cs
            // 音声波形表示に使用するLine(バー)の配列を確保 (この時点では、コンストラクタは呼び出されていない)
            bar = new Line[result.GetLength(1)];
            for (int i = 0; i < result.GetLength(1); i++)
            {
                bar[i] = new Line(); // 各要素のコンストラクタを明示的に呼び出す
            }
            // Line(バー)に使用するブラシ
            brush = new SolidColorBrush(Color.FromArgb(128, 61, 221, 200));
```
```cs
            // 1秒あたりのバイト数を計算
            bytePerSec = (audioStream.WaveFormat.BitsPerSample / 8) * audioStream.WaveFormat.SampleRate * audioStream.WaveFormat.Channels;
            // 音楽の長さ (秒)を計算
            musicLength_s = (int)audioStream.Length / bytePerSec;
```
```cs
            // 再生位置 (秒)を計算
            playPosition_s = (int)audioStream.Position / bytePerSec;

            // 音声波形表示を描画する配列のオフセット(インデックス)を計算
            drawPosition = (int)(((double)audioStream.Position / (double)audioStream.Length) * result.GetLength(0));
```
```cs
        /// <summary>
        /// 音声波形表示を描画
        /// </summary>
        private void Make_AudioSpectrum()
        {
            // 描画済みのLine(バー)がある場合
            if (barDrawn)
            {
                for (int j = 0; j < result.GetLength(1); j++)
                {
                    // 画面からLine(バー)を削除
                    grid.Children.Remove(bar[j]);
                }
            }

            if (drawPosition >= result.GetLength(0))    // マネージリソース(Line bar[])の解放は自動でガベージコレクションが行う
                return;

            for (int j = 0; j < result.GetLength(1);)
            {
                // 描画する方法 (Brush)を設定
                bar[j].Stroke = brush;  // System.Windows.Media.Brushes.LightBlue;
                // (親要素内に作成されるときに適用される)水平方向の配置特性を、(親要素のレイアウトのスロットの)左側に設定
                bar[j].HorizontalAlignment = HorizontalAlignment.Left;
                // (親要素内に作成されるときに適用される)垂直方向の配置特性を、(親要素のレイアウトのスロットの)中央に設定
                bar[j].VerticalAlignment = VerticalAlignment.Center;

                // 始点のx座標を設定
                bar[j].X1 = j * 7 + 32;
                // 終点のx座標を設定
                bar[j].X2 = j * 7 + 32;

                // 始点のy座標を設定
                bar[j].Y1 = 0;
                // 終点のy座標を設定 (result[,]は、0 ~ 1の値)
                bar[j].Y2 = 7700 * result[drawPosition, j];
                // 長さが400より大きい場合は長さを400にする
                if (bar[j].Y2 >= 400)
                    bar[j].Y2 = 400;
                // 幅を設定
                bar[j].StrokeThickness = 5;

                // 画面にLine(バー)を追加
                grid.Children.Add(bar[j]);
                j += 1;
            }
            // 描画済みにする
            barDrawn = true;

        }

```
描画済みのバーがある場合はそれを削除し、**高速フーリエ変換**後の配列のデータのうち、*drawPosition*の要素を描画します。
##**コード全体**
###**MainWindow.xaml.cs**
```cs:MainWindow.xaml.cs
namespace AudioSpectrum_ver._2
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// 60(fps)の逆数 (100ns)
        /// </summary>
        private readonly long reciprocal_of_FPS = 167000;

        /// <summary>
        /// 音楽プレーヤー
        /// </summary>
        private WaveOutEvent outputDevice;

        /// <summary>
        /// フーリエ変換前の音楽データ
        /// </summary>
        private AudioFileReader audioStream;

        /// <summary>
        /// フーリエ変換後の音楽データ
        /// </summary>
        private float[,] result;

        /// <summary>
        /// タイマー割込みに使用するタイマー
        /// </summary>
        private DispatcherTimer timer = null;

        /// <summary>
        /// 再生する音楽ファイルのパス
        /// </summary>
        private string fileName;

        /// <summary>
        /// 音声波形表示に使用するLine(バー)
        /// </summary>
        private Line[] bar;

        /// <summary>
        /// 音声波形表示のLine(バー)に使用するブラシ
        /// </summary>
        private Brush brush;

        /// <summary>
        /// 1秒当たりのバイト数
        /// </summary>
        private int bytePerSec;

        /// <summary>
        /// 音楽の長さ (秒)
        /// </summary>
        private int musicLength_s;

        /// <summary>
        /// 再生位置 (秒)
        /// </summary>
        private int playPosition_s;

        /// <summary>
        /// 音声波形表示位置
        /// </summary>
        private int drawPosition;

        /// <summary>
        /// 描画済みのLine(バー)があるかを示すフラグ (生成済み = true, 未生成 = false)
        /// </summary>
        private bool barDrawn = false;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            // ウィンドウをマウスのドラッグで移動できるようにする 
            this.MouseLeftButtonDown += (sender, e) => this.DragMove();

            // Loaded(要素のレイアウトやレンダリングが完了し、操作を受け入れる準備が整ったときに発生)イベントの登録
            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
        }

        /// <summary>
        /// MainWindowの初期化が終わったとき (Loadedが発生したとき) のイベントハンドラ
        /// </summary>
        /// <param name="sender">イベント送信元</param>
        /// <param name="e">イベント引数</param>
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // タイマーの生成
            timer = new DispatcherTimer(DispatcherPriority.Normal);
            // Tickの発生間隔の設定
            timer.Interval = new TimeSpan(reciprocal_of_FPS);
            // タイマーイベントの登録
            timer.Tick += new EventHandler(timer_Tick);

            // 再生するファイル名
            fileName = "music\\sample.wav";

            // ファイル名の拡張子によって、異なるストリームを生成
            audioStream = new AudioFileReader(fileName);

            // ハミング窓をかけ、高速フーリエ変換を行ったデータを配列resultに格納
            result = FFT_HammingWindow_ver1();

            // 音声波形表示に使用するLine(バー)の配列を確保 (この時点では、コンストラクタは呼び出されていない)
            bar = new Line[result.GetLength(1)];
            for (int i = 0; i < result.GetLength(1); i++)
            {
                bar[i] = new Line(); // 各要素のコンストラクタを明示的に呼び出す
            }
            // Line(バー)に使用するブラシ
            brush = new SolidColorBrush(Color.FromArgb(128, 61, 221, 200));

            // コンストラクタを呼んだ際に、Positionが最後尾に移動したため、0に戻す
            audioStream.Position = 0;

            // プレーヤーの生成
            outputDevice = new WaveOutEvent();
            // 音楽ストリームの入力
            outputDevice.Init(audioStream);

            // 1秒あたりのバイト数を計算
            bytePerSec = (audioStream.WaveFormat.BitsPerSample / 8) * audioStream.WaveFormat.SampleRate * audioStream.WaveFormat.Channels;
            // 音楽の長さ (秒)を計算
            musicLength_s = (int)audioStream.Length / bytePerSec;

            // 音楽の再生 (おそらく非同期処理)
            outputDevice.Play();

            // タイマーの実行開始
            timer.Start();
        }

        /// <summary>
        /// Timer.Tickが発生したときのイベントハンドラ
        /// </summary>
        /// <param name="sender">イベント送信元</param>
        /// <param name="e">イベント引数</param>
        private void timer_Tick(object sender, EventArgs e)
        {
            // この中の処理はメインスレッドで行われる

            // 再生位置 (秒)を計算
            playPosition_s = (int)audioStream.Position / bytePerSec;

            // 音声波形表示を描画する配列のオフセット(インデックス)を計算
            drawPosition = (int)(((double)audioStream.Position / (double)audioStream.Length) * result.GetLength(0));
            Make_AudioSpectrum();
        }

        /// <summary>
        /// 音楽の波形データにハミング窓をかけ、高速フーリエ変換する
        /// </summary>
        /// <returns>フーリエ変換後の音楽データ</returns>
        private float[,] FFT_HammingWindow_ver1()
        {
            // 波形データを配列samplesに格納
            float[] samples = new float[audioStream.Length / audioStream.BlockAlign * audioStream.WaveFormat.Channels];
            audioStream.Read(samples, 0, samples.Length);

            //1サンプルのデータ数
            int fftLength = 256;
            //1サンプルごとに実行するためのイテレータ用変数
            int fftPos = 0;

            // フーリエ変換後の音楽データを格納する配列
            float[,] result = new float[samples.Length / fftLength, fftLength / 2];

            // 波形データにハミング窓をかけたデータを格納する配列
            Complex[] buffer = new Complex[fftLength];
            for (int i = 0; i < samples.Length; i++)
            {
                // ハミング窓をかける
                buffer[fftPos].X = (float)(samples[i] * FastFourierTransform.HammingWindow(fftPos, fftLength));
                buffer[fftPos].Y = 0.0f;
                fftPos++;

                // 1サンプル分のデータが溜まったとき
                if (fftLength <= fftPos)
                {
                    fftPos = 0;
                    
                    // サンプル数の対数をとる (高速フーリエ変換に使用)
                    int m = (int)Math.Log(fftLength, 2.0);
                    // 高速フーリエ変換
                    FastFourierTransform.FFT(true, m, buffer);

                    for (int k = 0; k < result.GetLength(1); k++)
                    {
                        // 複素数の大きさを計算
                        double diagonal = Math.Sqrt(buffer[k].X * buffer[k].X + buffer[k].Y * buffer[k].Y);
                        double intensityDB = 10.0 * Math.Log10(diagonal);

                        const double minDB = -60.0;

                        // 音の大きさを百分率に変換
                        double percent = (intensityDB < minDB) ? 1.0 : intensityDB / minDB;
                        // 結果を代入
                        result[i / fftLength, k] = (float)diagonal;
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// 音声波形表示を描画
        /// </summary>
        private void Make_AudioSpectrum()
        {
            // 描画済みのLine(バー)がある場合
            if (barDrawn)
            {
                for (int j = 0; j < result.GetLength(1); j++)
                {
                    // 画面からLine(バー)を削除
                    grid.Children.Remove(bar[j]);
                }
            }

            if (drawPosition >= result.GetLength(0))    // マネージリソース(Line bar[])の解放は自動でガベージコレクションが行う
                return;

            for (int j = 0; j < result.GetLength(1);)
            {
                // 描画する方法 (Brush)を設定
                bar[j].Stroke = brush;  // System.Windows.Media.Brushes.LightBlue;
                // (親要素内に作成されるときに適用される)水平方向の配置特性を、(親要素のレイアウトのスロットの)左側に設定
                bar[j].HorizontalAlignment = HorizontalAlignment.Left;
                // (親要素内に作成されるときに適用される)垂直方向の配置特性を、(親要素のレイアウトのスロットの)中央に設定
                bar[j].VerticalAlignment = VerticalAlignment.Center;

                // 始点のx座標を設定
                bar[j].X1 = j * 7 + 32;
                // 終点のx座標を設定
                bar[j].X2 = j * 7 + 32;

                // 始点のy座標を設定
                bar[j].Y1 = 0;
                // 終点のy座標を設定 (result[,]は、0 ~ 1の値)
                bar[j].Y2 = 7700 * result[drawPosition, j];
                // 長さが400より大きい場合は長さを400にする
                if (bar[j].Y2 >= 400)
                    bar[j].Y2 = 400;
                // 幅を設定
                bar[j].StrokeThickness = 5;

                // 画面にLine(バー)を追加
                grid.Children.Add(bar[j]);
                j += 1;
            }
            // 描画済みにする
            barDrawn = true;

        }

        /// <summary>
        /// コンテキストメニューのExitが押されたときのイベントハンドラ
        /// </summary>
        /// <param name="sender">イベント送信元</param>
        /// <param name="e">イベント引数</param>
        private void Quit_Clicked(object sender, RoutedEventArgs e)
        {
            Close();
        }
    }
}
```
###**MainWindow.xaml**
最後に、**MainWindow.xaml**も載せておきます。

```xml:MainWindow.xaml
<Window x:Class="AudioSpectrum_ver._2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AudioSpectrum_ver._2"
        mc:Ignorable="d"
        Title="MainWindow" Height="540" Width="960"
        AllowsTransparency="True" 
        Background="#80000024"
        WindowStyle="None"
        ResizeMode="CanResizeWithGrip"
        >
    <Window.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Exit" Click="Quit_Clicked"/>
        </ContextMenu>
    </Window.ContextMenu>
    <Grid Name="grid">
    </Grid>
</Window>
```
####**レイヤードウィンドウ**
```xml
        AllowsTransparency="True" 
        Background="#80000024"
        WindowStyle="None"
```
**レイヤードウィンドウ**とは、非矩形ウィンドウ、**アルファブレンド**効果を使用するウィンドウのことです。要するにウィンドウを**透明**、もしくは**半透明**にする機能です。

```xml
        AllowsTransparency="True" 
        WindowStyle="None"
```
この記述を追加することで、描画が**レイヤードウィンドウ**上で行われます。
さらに、

```xml
Background="#80000024"
```
というように**ARGB**の順番で*Background*の値を指定することで、**半透明**、

```xml
Background="TransParent"
```
とすることで**透明**にすることができます。(**デバッグ中**は**半透明**にするのが良いと思います。)
#**感想**
**AviUtl**という無料の動画編集ソフトで、音声波形表示のスクリプト(言語は**lua**)を遊びで作ったことが、**Windowsアプリケーション**として、音声波形表示のアプリケーションを作ることのきっかけになりました。
実は、私は、**C#**でコードを書くのはこれが初めてで、なので当然**WPF**も初めてでした。そんな中、**NAudio**という日本語で説明されたサイトが少ないライブラリを用いらなければならなかったので、最初は不安でしたが、**C++**ではよくコードを書いていることもあり、なんとかここまで来ることができました。
次は音声波形の表示方法をアレンジしたいと思います。

余談ですが、これが初投稿で、記事を書く大変さも知ることができました(+o+)笑
では、**最後までご覧くださりありがとうございました**。