Hope is a Dream. Dream is a Hope.

非公開ブログは再開しました。

NAudioで信号処理 (その9)

NAudioで信号処理 (目次) - Hope is a Dream. Dream is a Hope.

NAudioで信号処理 (その9)

f:id:hope_is_dream:20170506140459p:plain

エフェクト処理 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();

NAudioで信号処理 (その8)

NAudioで信号処理 (目次) - Hope is a Dream. Dream is a Hope.

NAudioで信号処理 (その8)

f:id:hope_is_dream:20170506133840p:plain

エフェクトの準備その2

C# Audio Tutorial 8 - EffectStream Part 2

その6)の続きです。

エフェクト用のインターフェイスを準備

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Tutorial
{
    public interface IEffect
    {
        float ApplyEffect(float sample);
    }
}
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>(); // エフェクト初期化
        }

        public override WaveFormat WaveFormat
        {
            get { return this.SourceStream.WaveFormat; }
        }

        public override long Length
        {
            get { return SourceStream.Length; }
        }

        public override long Position
        {
            get { return this.SourceStream.Position; }
            set { this.SourceStream.Position = value; }
        }

        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;
                }

                // 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;
        }
    }
}


NAudioで信号処理 (その7)

NAudioで信号処理 (目次) - Hope is a Dream. Dream is a Hope.

NAudioで信号処理 (その7)

f:id:hope_is_dream:20170506131304p:plain

録音デバイスの選択と、再生

C# Audio Tutorial 6 - Audio Loopback using

Tutorial6ではやっとデバイスの選択方法がでてきました。まずは録音デバイスということで入力側のデバイスの一覧を取得し、ListViewに表示します。

Tutorial7では、録音デバイスからのオーディオストリームを、Waveファイルへ書き込みます

オーディオチェーンは以下のようになります。

WaveIn(rec) -> Callback() -> waveWriter

private void button_ToWav_Click(object sender, EventArgs e)
{
    if (listView_Sources.SelectedItems.Count == 0) return;

    // オーディオチェーン : WaveIn(rec) -> Callback() -> waveWriter 

    // 録音先のWavファイル
    SaveFileDialog save = new SaveFileDialog();
    save.Filter = "Wave File (*.wav)|*.wav;";
    if (save.ShowDialog() != DialogResult.OK) return;

    // 録音デバイス番号
    int deviceNumber = listView_Sources.SelectedItems[0].Index;

    // waveIn Select Recording Device
    sourceStream = new WaveIn();
    sourceStream.DeviceNumber = deviceNumber;
    sourceStream.WaveFormat = new WaveFormat(44100, WaveIn.GetCapabilities(deviceNumber).Channels);

    // 録音のコールバック
    sourceStream.DataAvailable += new EventHandler<WaveInEventArgs>(sourceStream_DataAvailable);

    // wave出力
    waveWriter = new WaveFileWriter(save.FileName, sourceStream.WaveFormat);

    // 録音開始
    sourceStream.StartRecording();

}
private void sourceStream_DataAvailable(object sender, WaveInEventArgs e)
{
    if (waveWriter == null) return;
    // Waveファイルへ書き込み
    waveWriter.Write(e.Buffer, 0, e.BytesRecorded);
    waveWriter.Flush();
}
using System;
using System.Windows.Forms;
using NAudio.Wave;
using System.Collections.Generic;

namespace Tutorial
{
    public partial class Form1 : Form
    {
        #region form
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// フォームのクロージング処理
        /// Wave関連オブジェクトのDispose処理を担当
        /// </summary>
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
        }
        #endregion


        private void button_Refresh_Click(object sender, EventArgs e)
        {

            // SetUp WaveIn Devices
            List<WaveInCapabilities> sources = new List<WaveInCapabilities>();

            for (int i = 0; i < WaveIn.DeviceCount; i++)
            {
                sources.Add(WaveIn.GetCapabilities(i));
            }

            listView_Sources.Items.Clear();

            foreach (var source in sources)
            {
                ListViewItem item = new ListViewItem(source.ProductName);
                item.SubItems.Add(new ListViewItem.ListViewSubItem(item, source.Channels.ToString()));

                listView_Sources.Items.Add(item);
            }

        }

        #region Member
        private NAudio.Wave.WaveIn sourceStream = null;
        private NAudio.Wave.DirectSoundOut waveOut = null;
        private NAudio.Wave.WaveFileWriter waveWriter = null;
        #endregion


        private void button_Start_Click(object sender, EventArgs e)
        {
            if (listView_Sources.SelectedItems.Count == 0) return;

            // オーディオチェーン : WaveIn(rec) -> WaveInProvider -> WaveOut(DirectSoundOut)

            int deviceNumber = listView_Sources.SelectedItems[0].Index;

            // waveIn Select Recording Device
            sourceStream = new NAudio.Wave.WaveIn();
            sourceStream.DeviceNumber = deviceNumber;
            sourceStream.WaveFormat = new NAudio.Wave.WaveFormat(44100, WaveIn.GetCapabilities(deviceNumber).Channels);

            WaveInProvider waveIn = new NAudio.Wave.WaveInProvider(sourceStream); // ?

            // waveOut
            waveOut = new DirectSoundOut();
            waveOut.Init(waveIn);

            sourceStream.StartRecording();
            waveOut.Play();

        }

        private void button_ToWav_Click(object sender, EventArgs e)
        {
            if (listView_Sources.SelectedItems.Count == 0) return;

            // オーディオチェーン : WaveIn(rec) -> Callback() -> waveWriter 

            // 録音先のWavファイル
            SaveFileDialog save = new SaveFileDialog();
            save.Filter = "Wave File (*.wav)|*.wav;";
            if (save.ShowDialog() != DialogResult.OK) return;

            // 録音デバイス番号
            int deviceNumber = listView_Sources.SelectedItems[0].Index;

            // waveIn Select Recording Device
            sourceStream = new WaveIn();
            sourceStream.DeviceNumber = deviceNumber;
            sourceStream.WaveFormat = new WaveFormat(44100, WaveIn.GetCapabilities(deviceNumber).Channels);

            // 録音のコールバックkな数
            sourceStream.DataAvailable += new EventHandler<WaveInEventArgs>(sourceStream_DataAvailable);

            // wave出力
            waveWriter = new WaveFileWriter(save.FileName, sourceStream.WaveFormat);

            // 録音開始
            sourceStream.StartRecording();

        }
        private void sourceStream_DataAvailable(object sender, WaveInEventArgs e)
        {
            if (waveWriter == null) return;

            waveWriter.Write(e.Buffer, 0, e.BytesRecorded);
            waveWriter.Flush();
        }

        private void button_Stop_Click(object sender, EventArgs e)
        {
            waveOut?.Stop();
            waveOut?.Dispose();
            waveOut = null;

            sourceStream?.StopRecording();
            sourceStream?.Dispose();
            sourceStream = null;

            waveWriter?.Dispose();
            waveWriter = null;
        }

        private void button_Exit_Click(object sender, EventArgs e)
        {
            button_Stop_Click(sender, e);
            this.Close();
        }


    }

}