NAudioで信号処理 (その11)
NAudioで信号処理 (目次) - Hope is a Dream. Dream is a Hope.
NAudioで信号処理 2 (その11)
NAudio.Gui.WaveViewerをカスタムしてグラフ描画!
いよいよグラフ描画です。信号処理はグラフを描画することから始まります。チュートリアルでは独自グラフの実装ではなく、chartクラスと、NAudioのWaveViewerクラスを使っての実装となります。
CustomWaveViewer
NAudio.Gui.WaveViewerをコピペして、ズームやらフィットやらをカスタム
using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Data; using System.Windows.Forms; using NAudio.Wave; namespace Tutorial { /// <summary> /// Control for viewing waveforms /// </summary> public class CustomWaveViewer : System.Windows.Forms.UserControl { #region プロパティ public Color PenColor { get; set; } public float PenWidth { get; set; } #endregion public void FitToScreen() { if (waveStream == null) return; int samples = (int)(waveStream.Length / bytesPerSample); this.startPosition = 0; this.SamplesPerPixel = samples / this.Width; } public void Zoom(int leftSample, int rightSample) { this.startPosition = leftSample * bytesPerSample; // [byte] this.SamplesPerPixel = (rightSample - leftSample) / this.Width; // [sample/pixel] } #region Mouse private Point mousePos, startPos; private bool mouseDrag = false; protected override void OnMouseDown(MouseEventArgs e) { if (e.Button == System.Windows.Forms.MouseButtons.Left) { startPos = e.Location; mousePos = new Point(-1, 1); mouseDrag = true; DrawVerticalLine(e.X); } base.OnMouseDown(e); } protected override void OnMouseMove(MouseEventArgs e) { if (mouseDrag) { DrawVerticalLine(e.X); // マウス位置にライン描画 if (mousePos.X != -1) DrawVerticalLine(mousePos.X); //前のラインを消す mousePos = e.Location; } base.OnMouseMove(e); } protected override void OnMouseUp(MouseEventArgs e) { if (mouseDrag && e.Button == System.Windows.Forms.MouseButtons.Left) { mouseDrag = false; DrawVerticalLine(startPos.X); if (mousePos.X == -1) return; DrawVerticalLine(mousePos.X); int leftSample = (int)(StartPosition / bytesPerSample + SamplesPerPixel * Math.Min(startPos.X, mousePos.X)); int rightSample = (int)(StartPosition / bytesPerSample + SamplesPerPixel * Math.Max(startPos.X, mousePos.X)); Zoom(leftSample, rightSample); }else if(e.Button == MouseButtons.Middle){ this.FitToScreen(); } base.OnMouseUp(e); } #endregion private void DrawVerticalLine(int x) { ControlPaint.DrawReversibleLine( PointToScreen(new Point(x, 0)), PointToScreen(new Point(x, Height)), Color.Black); } protected override void OnResize(EventArgs e) { this.FitToScreen(); base.OnResize(e); } /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.Container components = null; private WaveStream waveStream; private int samplesPerPixel = 128; private long startPosition; private int bytesPerSample; /// <summary> /// Creates a new WaveViewer control /// </summary> public CustomWaveViewer() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); this.DoubleBuffered = true; this.PenColor = Color.DodgerBlue; this.PenWidth = 1; } /// <summary> /// sets the associated wavestream /// </summary> public WaveStream WaveStream { get { return waveStream; } set { waveStream = value; if (waveStream != null) { bytesPerSample = (waveStream.WaveFormat.BitsPerSample / 8) * waveStream.WaveFormat.Channels; } this.Invalidate(); } } /// <summary> /// The zoom level, in samples per pixel /// </summary> public int SamplesPerPixel { get { return samplesPerPixel; } set { samplesPerPixel = Math.Max(1, value); this.Invalidate(); } } /// <summary> /// Start position (currently in bytes) /// </summary> public long StartPosition { get { return startPosition; } set { startPosition = value; } } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose(bool disposing) { if (disposing) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); } /// <summary> /// <see cref="Control.OnPaint"/> /// </summary> protected override void OnPaint(PaintEventArgs e) { if (waveStream != null) { waveStream.Position = 0; int bytesRead; byte[] waveData = new byte[samplesPerPixel * bytesPerSample]; waveStream.Position = startPosition + (e.ClipRectangle.Left * bytesPerSample * samplesPerPixel); using (Pen linePen = new Pen(PenColor, PenWidth)) { for (float x = e.ClipRectangle.X; x < e.ClipRectangle.Right; x += 1) { short low = 0; short high = 0; bytesRead = waveStream.Read(waveData, 0, samplesPerPixel * bytesPerSample); if (bytesRead == 0) break; for (int n = 0; n < bytesRead; n += 2) { short sample = BitConverter.ToInt16(waveData, n); if (sample < low) low = sample; if (sample > high) high = sample; } float lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue); float highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue); e.Graphics.DrawLine(linePen, x, this.Height * lowPercent, x, this.Height * highPercent); } } } base.OnPaint(e); } #region Component Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { components = new System.ComponentModel.Container(); } #endregion } }
NAudioで信号処理 (その10)
NAudioで信号処理 (目次) - Hope is a Dream. Dream is a Hope.
NAudioで信号処理 (その10)
グラフ描画!
C# Audio Tutorial 10 - Plotting Audio Waveforms
いよいよグラフ描画です。信号処理はグラフを描画することから始まります。チュートリアルでは独自グラフの実装ではなく、chartクラスと、NAudioのWaveViewerクラスを使っての実装となります。
using System; using System.Windows.Forms; using NAudio.Wave; using System.Drawing; namespace Tutorial { public partial class Form1 : Form { #region form public Form1() { InitializeComponent(); } private void openToolStripMenuItem_Click(object sender, EventArgs e) { // WAV File Open OpenFileDialog open = new OpenFileDialog(); open.Filter = "WAV File (*.wav)|*.wav;"; if (open.ShowDialog() != DialogResult.OK) return; // NAudio WaveViewerのセットアップ waveViewer1.BackColor = Color.White; waveViewer1.SamplesPerPixel = 400; waveViewer1.StartPosition = 40000; waveViewer1.WaveStream = new WaveFileReader(open.FileName); // ストリームを指定するだけでよい // 以下、chartのセットアップ chart1.Series.Add("wave"); chart1.Series["wave"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine; chart1.Series["wave"].ChartArea = "ChartArea1"; WaveChannel32 wave = new WaveChannel32(new WaveFileReader(open.FileName)); byte[] buffer = new byte[16384]; int read = 0; while(wave.Position < wave.Length) { read = wave.Read(buffer, 0, 16384); for (int i = 0; i < read/4; i++) { chart1.Series["wave"].Points.Add(BitConverter.ToSingle(buffer, i * 4)); } } } } }
NAudioで信号処理 (その9)
NAudioで信号処理 (目次) - Hope is a Dream. Dream is a Hope.
NAudioで信号処理 (その9)
エフェクト処理 Part3 (Echo!!!!)
C# Audio Tutorial 9 - EffectStream Part 3 (Echo!)
やっとエフェクトの処理を実装します!
Echo.cs
先ほど作ったIEffect.csインターフェイスを実装したEcho.csを作ります。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Tutorial { public class Echo : IEffect { public int EchoLength { get; private set; } public float EchoFactor { get; set; } private Queue<float> samples; public Echo(int length = 20000, float factor = 0.5f) { this.EchoLength = length; this.EchoFactor = factor; this.samples = new Queue<float>(); for (int i = 0; i < length; i++) samples.Enqueue(0f); } float IEffect.ApplyEffect(float sample) { samples.Enqueue(sample); // add que return sample + EchoFactor * samples.Dequeue(); } } }
EffectStream.cs
using System; using System.Collections.Generic; using NAudio.Wave; namespace Tutorial { public class EffectStream : WaveStream { public WaveStream SourceStream { get; set; } public List<IEffect> Effects { get; set; } public EffectStream(WaveStream stream) { this.SourceStream = stream; this.Effects = new List<IEffect>(); } //... 省略 private int channel = 0; public override int Read(byte[] buffer, int offset, int count) { //*****************************************************************// // ここで信号処理をする //*****************************************************************// Console.WriteLine("DirectSoundOut request {0} bytes", count); int read = this.SourceStream.Read(buffer, offset, count); // 以下信号処理 for (int i = 0; i < read/4; i++) { // float = single = 32bit float sample = BitConverter.ToSingle(buffer, i * 4); // Single=4Byte // エフェクト処理 if (Effects.Count == WaveFormat.Channels) { sample = Effects[channel].ApplyEffect(sample); // ? channel = (channel + 1) % WaveFormat.Channels; // [1ch, 2ch, ...] } // float -> byte列に変換 byte[] bytes = BitConverter.GetBytes(sample); // 4Byteなので,bytes[4] // コピー //bytes.CopyTo(buffer, i * 4); // 遅い buffer[i * 4 + 0] = bytes[0]; buffer[i * 4 + 1] = bytes[1]; buffer[i * 4 + 2] = bytes[2]; buffer[i * 4 + 3] = bytes[3]; } return read; } } }
form1.cs
あとは再生系にエフェクトを指定すると使えます。
// WAV File Open OpenFileDialog open = new OpenFileDialog(); open.Filter = "WAV File (*.wav)|*.wav;"; if (open.ShowDialog() != DialogResult.OK) return; // Audio Chain WaveChannel32 wave = new WaveChannel32(new WaveFileReader(open.FileName)); EffectStream effect = new EffectStream(wave); // エフェクトをかけるためのパイプライン stream = new BlockAlignReductionStream(effect); // Effects for (int i = 0; i < wave.WaveFormat.Channels; i++) { effect.Effects.Add(new Echo(20000, 0.5f)); } // Out output = new DirectSoundOut(200); output.Init(stream); output.Play();