1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NAudioで波形を生成しながら再生する(っぽいやつ)

Last updated at Posted at 2019-04-23

探してもあんまり見つからなかったので。

手段

NAudioのWaveOutはWaveFileReaderを経由してファイルを読み込みながら再生できる。
WaveFileReaderに偽装してやれば好きなデータをWaveOutに食わせられる。

ソース

WaveStreamを継承してテキトーにNotImplementedExceptionの部分を穴埋めしただけ。

class DummyWaveStream : WaveStream
{
    private const int samplingRate = 44100;

    private Int64 pos = 0;

    public override Int64 Length
    {
        get
        {
            return (Int64.MaxValue);
        }
    }

    public override Int64 Position
    {
        get
        {
            return (pos);
        }
        set
        {
            pos = value;
        }
    }

    public override WaveFormat WaveFormat
    {
        get
        {
            return (new WaveFormat(samplingRate, 16, 2));
        }
    }

    public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
    {
        if (offset != 0 || buffer.Length != count) { throw new Exception(); }

        const double freq = 500;
        const int bytePerSample = 4;

        for (int i = 0; i < count; i += bytePerSample)
        {
            double sin = Math.Sin(freq * (pos + i) / samplingRate * Math.PI * 2 / bytePerSample);
            short wav = (short)(sin * 30000);

            buffer[i + 1] = (byte)(wav >> 8);
            buffer[i + 0] = (byte)wav;
        }

        pos += count;

        return (count);
    }
}

DummyWaveStreamWaveOutに与えると、500Hzの正弦波を再生する。
このサンプルでは左側しか音声を与えていないので、右のスピーカーは無音になる。

Readの中でイベントを蹴るなりdelegateを呼ぶなりして外部で音声を作れば、任意の波形を再生できる。

Posを真面目に扱うと面倒なので、setはNotImplementedExceptionにしてしまうと楽かも。

その他

デバイス一覧

void updateAudioDeviceList()
{
    audioDeviceComboBox.Items.Clear();
    audioDeviceComboBox.Items.AddRange(
            Enumerable.Range(0, WaveOut.DeviceCount)
        .Select(i => i + " " + WaveOut.GetCapabilities(i).ProductName)
        .ToArray());

    if (audioDeviceComboBox.Items.Count > 0)
    {
        audioDeviceComboBox.SelectedIndex = 0;
    }
}

みたいな感じでデバイス番号とデバイス名を列挙できる。
WaveOutでデバイスを指定する際にデバイス番号が必要になる。必ずしも必要ではないが、名前に併記したほうが楽。

デバイス名は変なところで切られてしまう。NAudio内部処理の問題な気がする。長い名前が全部必要なら別の手段を講じる必要がある。

Play/Stop

private WaveOut waveOut = null;
private DummyWaveStream waveSrc = null;

private void playCheckBox_Click(Object sender, EventArgs e)
{
    if (waveOut == null)
    {
        string deviceIndexAndNameText = audioDeviceComboBox.SelectedItem as string;

        if (!string.IsNullOrEmpty(deviceIndexAndNameText))
        {
            int devIndex = int.Parse(deviceIndexAndNameText.Split(' ')[0]);

            waveSrc = new DummyWaveStream();

            waveOut = new WaveOut();
            waveOut.DeviceNumber = devIndex;
            waveOut.Init(waveSrc);
            waveOut.Play();
        }
    }
    else
    {
        waveOut.Stop();
        waveOut.Dispose();
        waveOut = null;

        waveSrc.Dispose();
        waveSrc = null;
    }

    playCheckBox.Checked = waveOut != null;
    audioDeviceComboBox.Enabled = waveOut == null;
}

CheckBoxのAutoCheckfalseにしておく。

waveOutがnullならPlay、そうでなければStop、という動作。

Playの場合は、デバイス名の最初の文字列を取り出してint.Parseに与えてデバイス番号を得る。
waveOut.Init()DummyWaveStreamのインスタンスを与える。

StopはStopしてDisposeしてnullを入れるだけ。

再生時間

データ数が$2^{63} - 1$バイト、1サンプルあたり4バイト、44.1kHzなので、160万年くらいの長さ。まぁ、たぶん、大抵の用途では足りるだろう。
特殊な用途だと1サンプル64バイト/2MHzみたいなデータもあるけど、それでも2000年程度は再生できるし、その用途だとNAudioを使ったりはしないだろうから問題ないはず。

追記

Length, PositionはNAudio周りからはアクセスされないっぽい。NotImplementedExceptionのままでも動いた(WaveFormatは実装する必要がある)。

また、Readの戻り値はチェックされないらしく、少ないサンプル数を返した場合は正常に動作しない。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?