「C#でSoundCloudライクな波形を表示する音楽プレーヤ」を ~~勝手に~~ バージョンアップしてみる

# 前書き
以前、@siy1121さんが[NAudio](https://github.com/naudio/NAudio)ライブラリを使って「[C#でSoundCloudライクな波形を表示する音楽プレーヤを作ってみる](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7)」と言う記事を書かれました。私はこの記事を参考に「[語学学習用アプリの開発](https://qiita.com/guijiu/items/f7382ca825e126c2b436)」を作りました。
しかし時間の経過とともにNAudio(ver2.x)がマルチプラットフォーム対応になり、特に音声波形をレンダリングする[NAudio.WaveFormRenderer](https://github.com/naudio/NAudio.WaveFormRenderer)の仕様が変更になました。またNAudio(ver1.9)は、.NetFramewark4.0で動いていたようで、これが既にサポート外になっており、win11環境では簡単に開発できなくなりました（特別なイントール方法があるようですが、面倒臭そう）。
私の作った語学学習用アプリの開発は、既に３，４年前に作ったもので、その間Androidをやったりjavaをやったり、pythonをやったりするうちに、C#ほぼ忘れてしまいました。そこでC#やNAudioを思い出すために、「C#でSoundCloudライクな波形を表示する音楽プレーヤ」をバージョンアップしてみます。

# こんなものができます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501068/be066ad4-1777-5957-8388-59a24250cbbe.png)
（[原作者のUI](https://camo.qiitausercontent.com/d9d3cffe4ef42d8ce6fb8d059b618821936dae93/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f38393232302f31353533656665392d336432632d383765332d316539612d3361646534383937313030392e706e67)の方がかっこいいです）

# C#で音声ファイルを扱う
次のライブラリを使います

NAudio
NAudio.WaveFormRenderer

# 準備
- C#プロジェクト
Windowsフォームアプリケーション（.NET Framework 4.7.2） 
- NAudioとNAudio.WaveFormRendererのインストール
ともにNuGetからインストールできます。
私の使っているバージョンは
[NAudio](https://www.nuget.org/packages/NAudio)(Ver.2.2.1)
[NAudio.WaveFormRenderer](https://www.nuget.org/packages/NAudio.WaveFormRenderer)(Ver.2.0.0)

# UI部分
UI部分の作り方は、[原作](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#ui%E9%83%A8%E5%88%86)を参照してください。丁寧に記述されています。

# ソースコード
### 改変箇所
改変した箇所は次のとおりです。
WaveFormRendererのインスタンスのパラメータにWaveStreamを代入する箇所です(もともとはファイルパスを入れていた)。
```
        private void openOToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ＜省略＞
                ///////////////////////////////////////////////// ここから /////////////////////////////////////////////////////////////////
                using (var audioFile_rend1 = new AudioFileReader(filePath))
                {
                    pictureBox1.BackgroundImage = renderer.Render(audioFile_rend1, averagePeakProvider, soundCloudDarkBlocks);
                }

                using(var audioFile_rend2 = new AudioFileReader(filePath))
                {
                    pictureBox2.BackgroundImage = renderer.Render(audioFile_rend2, averagePeakProvider, soundCloudOrangeBlocks);
                    pictureBox2.Width = 0;
                }
                ///////////////////////////////////////////////// ここまで変更 /////////////////////////////////////////////////////////////////
                ///////////////////////////////////////////////// 元々は       /////////////////////////////////////////////////////////////////
                // pictureBox1.BackgroundImage = renderer.Render(filePath, averagePeakProvider, soundCloudDarkBlocks);
                // pictureBox2.BackgroundImage = renderer.Render(filePath, averagePeakProvider, soundCloudOrangeBlocks);
                // pictureBox2.Width = 0;
                ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
               ＜省略＞
            }
        }
```


### コード全体
ソースコードの全体を以下に示します。
```Form1.cs
// オリジナル
// https://qiita.com/siy1121/items/dd06a5e700dcf9543af7
// 作成　@siy1121さん
//
// NAudio.WaveFormRendererが、バージョン2では、より良いクロスプラットフォームサポートのために
// オーディオファイルのファイル名からWaveStreamを使用するように変更されたため、
// その対応するためを変更を行った。
//

using System;
using System.Drawing;
using System.Windows.Forms;
using NAudio.WaveFormRenderer;
using NAudio.Wave;
using System.IO;

namespace WaveFormPlayer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            //イベントの設定　共通にしたいので手動で
            pictureBox1.MouseDown += PictureBox_MouseDown;
            pictureBox2.MouseDown += PictureBox_MouseDown;
            pictureBox1.MouseMove += PictureBox_MouseMove;
            pictureBox2.MouseMove += PictureBox_MouseMove;
            pictureBox1.MouseUp += PictureBox_MouseUp;
            pictureBox2.MouseUp += PictureBox_MouseUp;
        }

        WaveOutEvent outputDevice;
        AudioFileReader audioFile;
        string filePath;//音声ファイルのパス
        int bytePerSec;//一秒あたりのバイト数
        int length;//曲の長さ（秒）
        int position;//再生位置（秒）
        bool mouseDownFlag;//ドラッグ時に使うフラグ　MouseDown中にtrue

        private void openOToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                filePath = openFileDialog1.FileName;

                //黒い波形の設定
                var soundCloudDarkBlocks = new SoundCloudBlockWaveFormSettings(Color.FromArgb(52, 52, 52), Color.FromArgb(55, 55, 55), Color.FromArgb(154, 154, 154),
                    Color.FromArgb(204, 204, 204));//バーの色の設定

                soundCloudDarkBlocks.Width = pictureBox1.Width;//生成する画像の幅
                soundCloudDarkBlocks.TopHeight = pictureBox1.Height / 4 * 3;//上に伸びるバーの高さ
                soundCloudDarkBlocks.BottomHeight = pictureBox1.Height / 4;//下に伸びるバーの長さ
                soundCloudDarkBlocks.BackgroundColor = Color.Transparent;//生成される画像の背景色　今回は透明
                soundCloudDarkBlocks.PixelsPerPeak = 2;//バーの幅
                //soundCloudDarkBlocks.SpacerPixels = 1;　バーの間に挟まる細いバーの幅

                //オレンジの波形の設定
                var soundCloudOrangeBlocks = new SoundCloudBlockWaveFormSettings(Color.FromArgb(255, 76, 0), Color.FromArgb(255, 52, 2), Color.FromArgb(255, 171, 141),
                     Color.FromArgb(255, 213, 199));

                soundCloudOrangeBlocks.Width = pictureBox1.Width;
                soundCloudOrangeBlocks.TopHeight = pictureBox1.Height / 4 * 3;
                soundCloudOrangeBlocks.BottomHeight = pictureBox1.Height / 4;
                soundCloudOrangeBlocks.BackgroundColor = Color.Transparent;
                soundCloudOrangeBlocks.PixelsPerPeak = 2;

                var renderer = new WaveFormRenderer(); //波形レンダラの生成
                var averagePeakProvider = new AveragePeakProvider(3); //波形レンダラ内部で使用されるもの

                //レンダリングした画像をPictureBoxに設定

                ///////////////////////////////////////////////// ここから /////////////////////////////////////////////////////////////////
                using (var audioFile_rend1 = new AudioFileReader(filePath))
                {
                    pictureBox1.BackgroundImage = renderer.Render(audioFile_rend1, averagePeakProvider, soundCloudDarkBlocks);
                }

                using(var audioFile_rend2 = new AudioFileReader(filePath))
                {
                    pictureBox2.BackgroundImage = renderer.Render(audioFile_rend2, averagePeakProvider, soundCloudOrangeBlocks);
                    pictureBox2.Width = 0;
                }
                ///////////////////////////////////////////////// ここまで変更 /////////////////////////////////////////////////////////////////
                ///////////////////////////////////////////////// 元々は       /////////////////////////////////////////////////////////////////
                // pictureBox1.BackgroundImage = renderer.Render(filePath, averagePeakProvider, soundCloudDarkBlocks);
                // pictureBox2.BackgroundImage = renderer.Render(filePath, averagePeakProvider, soundCloudOrangeBlocks);
                // pictureBox2.Width = 0;
                ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

                label1.Text = Path.GetFileName(filePath);

                outputDevice = new WaveOutEvent();
                audioFile = new AudioFileReader(filePath);
                outputDevice.Init(audioFile);
                playButton.BackgroundImage = Properties.Resources.play;
                
            }
        }

        private void playButton_Click(object sender, EventArgs e)
        {
            switch (outputDevice.PlaybackState)
            {
                case PlaybackState.Stopped://ファイルが読み込まれてまだ一度も再生されていない場合
                    //必要な値を求める
                    bytePerSec = audioFile.WaveFormat.BitsPerSample / 8 * audioFile.WaveFormat.SampleRate * audioFile.WaveFormat.Channels;
                    length = (int)audioFile.Length / bytePerSec;

                    label3.Text = new TimeSpan(0, 0, length).ToString();
                    timer1.Enabled = true;

                    outputDevice.Play();
                    playButton.BackgroundImage = Properties.Resources.pause;
                    //Thread.Sleep(200);
                    break;

                case PlaybackState.Paused://一時停止時の場合
                    outputDevice.Play();
                    playButton.BackgroundImage = Properties.Resources.pause;
                    break;

                case PlaybackState.Playing://再生中の場合
                    outputDevice.Pause();
                    playButton.BackgroundImage = Properties.Resources.play;
                    break;
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            //再生位置（秒）を計算して表示
            position = (int)audioFile.Position / bytePerSec;
            label2.Text = new TimeSpan(0, 0, position).ToString();

            if (!mouseDownFlag)//ドラッグ時に幅を変更するとチカチカするのを防止
                //再生位置からオレンジ波形をすすめる
                pictureBox2.Width = (int)(((double)position / length) * pictureBox1.Width);
        }

        private void PictureBox_MouseDown(object sender, MouseEventArgs e)
        {
            if (audioFile == null) return;
            mouseDownFlag = true;//ドラッグ時のフラグをtrueに
        }

        private void PictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            if (mouseDownFlag) pictureBox2.Width = e.X;//ドラッグ中にオレンジの波形の幅を変更
        }

        private void PictureBox_MouseUp(object sender, MouseEventArgs e)
        {
            if (!mouseDownFlag) return;
            mouseDownFlag = false;

            //ドラッグが終了した場所から曲の再生位置を計算して設定
            audioFile.Position = (int)(((double)e.X / pictureBox1.Width) * audioFile.Length);
        }

    }
}
```

### コーディング上の注意
波形のレンダリングや音声ファイルの再生の際、WaveStreamのインスタンスaudioFileを以下のように共通にすると、音声の再生(outputDevice.play())や、二つ目のレンダリングができなくなりました。（ひとつ目のレンダリングはでき、pictureBox1のイメージは生成され表示された。気持ち的には共通にしたいが、なぜできないのだろう？深堀出来た方、教えてください。）
```
                //レンダリングした画像をPictureBoxに設定
                audioFile = new AudioFileReader(filePath))   // これを共通にすると動かない
                pictureBox1.BackgroundImage = renderer.Render(audioFile, averagePeakProvider, soundCloudDarkBlocks);
                pictureBox2.BackgroundImage = renderer.Render(audioFile, averagePeakProvider, soundCloudOrangeBlocks);
                pictureBox2.Width = 0;

                outputDevice = new WaveOutEvent();
                // audioFile = new AudioFileReader(filePath);
                outputDevice.Init(audioFile);
                playButton.BackgroundImage = Properties.Resources.play;
```
このようなわけで、WaveStreamをaudioFile、audioFile_rend1、audioFile_rend2に分けました。


### その他ソースコードの動作説明は、原作に丁寧に書かれていますので、参照してください。
[処理の流れ](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#%E5%87%A6%E7%90%86%E3%81%AE%E6%B5%81%E3%82%8C)
[レンダリングの設定](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%AE%E8%A8%AD%E5%AE%9A)
[レンダリングする](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B)
[再生ボタンを押したときの処理](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#%E5%86%8D%E7%94%9F%E3%83%9C%E3%82%BF%E3%83%B3%E3%82%92%E6%8A%BC%E3%81%97%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E5%87%A6%E7%90%86)
[PlaybackState.Stopped (初回再生時)](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#playbackstatestopped-%E5%88%9D%E5%9B%9E%E5%86%8D%E7%94%9F%E6%99%82)
[Timerでの処理](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#timer%E3%81%A7%E3%81%AE%E5%87%A6%E7%90%86)
[ドラッグしてシークする処理](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#%E3%83%89%E3%83%A9%E3%83%83%E3%82%B0%E3%81%97%E3%81%A6%E3%82%B7%E3%83%BC%E3%82%AF%E3%81%99%E3%82%8B%E5%87%A6%E7%90%86)
[完成品](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#%E5%AE%8C%E6%88%90%E5%93%81)
[描画の内部処理を紐解いてみる](https://qiita.com/siy1121/items/dd06a5e700dcf9543af7#%E6%8F%8F%E7%94%BB%E3%81%AE%E5%86%85%E9%83%A8%E5%87%A6%E7%90%86%E3%82%92%E7%B4%90%E8%A7%A3%E3%81%84%E3%81%A6%E3%81%BF%E3%82%8B)

# 謝辞
NAudioの使い方を教えてくれた本音楽プレーヤとその原作者@siy1121さんに、この場を借りてお礼を申し上げます。