- 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」を描きまして。

下記のソースを書きます。
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);
}
}
}
例外処理等をはしょっていたりしますので、荒いプログラムではありますが、一応、動作します。
実行して、開始ボタンを押下し、音が聞こえると反応して、下記のようなグラフが表示されます。

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行は、上記までのものを使用して、実際に波形出力を行うメソッドを呼び出しています。
|