C# - NAudio - WaveFormRenderer

クラウディア 
1. 概要
2. プログラム
3. 開始ボタン
4. 停止ボタン
5. コールバック

1. 概要

 「WaveFormRenderer」は、音声の波形を表示するときのグラフを表示するものです。  本ページは、下記のサイトを参考にさせていただきました。
NAudio.WaveFormRenderer/WaveFormRendererLib/WaveFormRenderer.cs
 以下の環境での結果です。 ・Windows11 24H2 ・Microsoft Visual Studio Community 2022 Version 17.12.4 ・C# ツール 4.12.0-3.24631.1+da7c6c4257b2f661024b9a506773372a09023eee ・NAudio 2.2.1 ・NAudio.WaveFormRenderer 2.0.0

2. プログラム

 下記の「Form」を描きまして。
「NAudio.WaveFormRenderer」-「フォーム」

 下記のソースを書きます。


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

namespace WaveFormRendererX
{
	public partial class Form1 : Form
	{
		private int samplingRate = 44100;				// サンプリング周波数
		private int bit = 24;							// ビットレゾリューション
		private int bpd =  3;							// 1データあたりバイト数
		private NAudio.Wave.AsioOut? asioOut;

		public Form1()
		{
			InitializeComponent();

			bpd = bit / 8;
			button2.Enabled = false;

			pictureBox2.Location = new System.Drawing.Point(0, 0);
			pictureBox2.Parent = pictureBox1;
			pictureBox2.BackColor = Color.Transparent;
			pictureBox1.Image = Properties.Resources.grid;
		}

		/// <summary>
		/// 開始ボタン
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void button1_Click(object sender, EventArgs e)
		{
			asioOut = new AsioOut(AsioOut.GetDriverNames()[0]); ;
			asioOut.InitRecordAndPlayback(null, asioOut.DriverOutputChannelCount, samplingRate);

			// 録音する先頭CH
			asioOut.InputChannelOffset = 0;
			asioOut.AudioAvailable += OnAsioOutAudioAvailable;
			asioOut.Play();

			button1.Enabled = false;
			button2.Enabled = true;
		}

		/// <summary>
		/// 停止ボタン
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void button2_Click(object sender, EventArgs e)
		{
			if (asioOut != null)
			{
				asioOut.Stop();
				asioOut.Dispose();
				asioOut = null;
			}

			button1.Enabled = true;
			button2.Enabled = false;
		}

		private void button3_Click(object sender, EventArgs e)
		{
			if (asioOut != null)
			{
				asioOut.Stop();
				asioOut.Dispose();
			}

			Close();
		}

		/// <summary>
		/// Asio 受信完了時コールバック
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void OnAsioOutAudioAvailable(object? sender, AsioAudioAvailableEventArgs e)
		{
			float[] samples = new float[e.SamplesPerBuffer * e.InputBuffers.Length];
			int Count = e.GetAsInterleavedSamples(samples);

			List<float[]> sampleBuffer = new List<float[]>();

			for (int ch = 0; ch < e.InputBuffers.Length; ch++)
			{
				sampleBuffer.Add(new float[e.SamplesPerBuffer]);
			}

			Parallel.For(0, e.SamplesPerBuffer, i =>
			{
				for (int ch = 0; ch < e.InputBuffers.Length; ch++)
				{
					sampleBuffer[ch][i] = samples[i * e.InputBuffers.Length];
				}
			});

			List<Byte[]> data = new List<byte[]>();

			for (int ch = 0; ch < e.InputBuffers.Length; ch++)
			{
				data.Add(new Byte[e.SamplesPerBuffer * bpd]);
			}

			// データ変換
			for (int ch = 0; ch < e.InputBuffers.Length; ch++)
			{
				Parallel.For(0, e.SamplesPerBuffer, i =>
				{
					Int32 lData = (Int32)(sampleBuffer[ch][i] * 0x7fffff);
					Byte[] bData = BitConverter.GetBytes(lData);

					for (int j = 0; j < bpd; j++)
					{
						data[ch][i * bpd + j] = bData[j];
					}
				});
			}

			// 波形の色設定
			var soundCloudDarkBlocks =
				new SoundCloudBlockWaveFormSettings(Color.FromArgb(0, 0, 255), Color.FromArgb(0, 0, 255),
													Color.FromArgb(0, 0, 255), Color.FromArgb(0, 0, 255));

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

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

			var ms = new MemoryStream(data[0]);
			var rs = new RawSourceWaveStream(ms, new WaveFormat(samplingRate, bit, 1));

			// レンダリングした画像を PictureBox に設定
			pictureBox1.BackgroundImage = renderer.Render(rs, averagePeakProvider, soundCloudDarkBlocks);
		}
	}
}
 例外処理等をはしょっていたりしますので、荒いプログラムではありますが、一応、動作します。  実行して、開始ボタンを押下し、音が聞こえると反応して、下記のようなグラフが表示されます。
「NAudio.WaveFormRenderer」-「実行」

3. 開始ボタン

 開始ボタン押下時の処理を下記のように書いています。

		private void button1_Click(object sender, EventArgs e)
		{
			asioOut = new AsioOut(AsioOut.GetDriverNames()[0]); ;
			asioOut.InitRecordAndPlayback(null, asioOut.DriverOutputChannelCount, samplingRate);

			// 録音する先頭CH
			asioOut.InputChannelOffset = 0;
			asioOut.AudioAvailable += OnAsioOutAudioAvailable;
			asioOut.Play();

			button1.Enabled = false;
			button2.Enabled = true;
		}
 34行で、オーディオドライバのインスタンスを取得しています。  「ASIO」ドライバはひとつしか認識していませんので、決め打ちで、先頭のデバイス名を設定しています。  35行で、デバイスの諸元、入力チャンネル数・サンプリング周波数を設定しています。  38行で、入力するチャンネルの先頭を設定しています。  39行で、入力発生のイベントで、処理するコールバックメソッドを設定しています。  40行で、入力を開始します。

4. 停止ボタン

 停止ボタン、押下時の処理です。  これは、説明というほどのこともありませんが。

		private void button3_Click(object sender, EventArgs e)
		{
			if (asioOut != null)
			{
				asioOut.Stop();
				asioOut.Dispose();
			}

			Close();
		}
 入力を停止して、インスタンスを破棄しています。

5. コールバック

 入力発生のイベントで処理するコールバックメソッドの定義です。  ここが、本ページのミソですわな。

		void OnAsioOutAudioAvailable(object? sender, AsioAudioAvailableEventArgs e)
		{
			float[] samples = new float[e.SamplesPerBuffer * e.InputBuffers.Length];
			int Count = e.GetAsInterleavedSamples(samples);

			List<float[]> sampleBuffer = new List<float[]>();

			for (int ch = 0; ch < e.InputBuffers.Length; ch++)
			{
				sampleBuffer.Add(new float[e.SamplesPerBuffer]);
			}

			Parallel.For(0, e.SamplesPerBuffer, i =>
			{
				for (int ch = 0; ch < e.InputBuffers.Length; ch++)
				{
					sampleBuffer[ch][i] = samples[i * e.InputBuffers.Length];
				}
			});

			List<Byte[]> data = new List<byte[]>();

			for (int ch = 0; ch < e.InputBuffers.Length; ch++)
			{
				data.Add(new Byte[e.SamplesPerBuffer * bpd]);
			}

			// データ変換
			for (int ch = 0; ch < e.InputBuffers.Length; ch++)
			{
				Parallel.For(0, e.SamplesPerBuffer, i =>
				{
					Int32 lData = (Int32)(sampleBuffer[ch][i] * 0x7fffff);
					Byte[] bData = BitConverter.GetBytes(lData);

					for (int j = 0; j < bpd; j++)
					{
						data[ch][i * bpd + j] = bData[j];
					}
				});
			}

			// 波形の色設定
			var soundCloudDarkBlocks =
				new SoundCloudBlockWaveFormSettings(Color.FromArgb(0, 0, 255), Color.FromArgb(0, 0, 255),
													Color.FromArgb(0, 0, 255), Color.FromArgb(0, 0, 255));

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

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

			var ms = new MemoryStream(data[0]);
			var rs = new RawSourceWaveStream(ms, new WaveFormat(samplingRate, bit, 1));

			// レンダリングした画像を PictureBox に設定
			pictureBox1.BackgroundImage = renderer.Render(rs, averagePeakProvider, soundCloudDarkBlocks);
		}
 入力データは、何故か、「float」ではいってきます。  ビットレゾリューションにより、1データ分の長さが違ったり、エンディアンが異なったりするのを吸収するために、「float」にいれているのかと思っております。  82~120行で、はいってきたデータを、チャンネルごとの「byte」列に変換しております。  ここは、今んとこ、詳細は割愛。  123~140行で、チャンネル1の「byte」列を波形にして出力しています。  123~125行は、波形の色を設定しています。  4つの色は、波形ピークの色、ピークの立ち上がりの色、オフピークの色、オフピーク立ち上がりの色・・・?  色番号「R」「G」「B」であらわしていて、どうも慣習的に青を使っているのが多いようなので、なんとなく青を使っています。  127行は、画像全体の横幅です。  描画するピクチャーボックスに合わせています。  128、129行は、上下の幅です。  全体を4分の1に分けて、上に2、下に2振幅するようにしています。  130行は、背景色の設定で、背景は、透明にしています。  131行は、波形の線の幅で、最小値(?)を設定しています。  133行は、単にインスタンスを取得しているだけに見えます。  134行は、波形のスケーリングを設定しています。  136、137行は、  140行は、上記までのものを使用して、実際に波形出力を行うメソッドを呼び出しています。
AbemaTV 無料体験
JETBOY
U-NEXT
それがだいじWi-Fi
損保との違い
ベルリッツ