2017年8月7日月曜日

XNAでサウンド出力

 C#で音をリアルタイムに作って出したかったので、XNAでやってみた。前にWin32でやった気がするけど覚えてない。

 XNAが開発終了って以外は、あんまり欠点のない方法、だと思う。

 DynamicSoundEffectInstanceのコンストラクタでサンプリングレートとチャンネル(Mono/Stereo)を選択。最初のデータをSubmitBufferで設定してPlayで開始。データが無くなるとBufferNeededイベントが来るので、ハンドラ内でデータを生成してSubmitBufferで追加。たぶんStopとか使えば止められるんじゃないかなー。
 Playは最初の1回だけでいいが、予めSubmitBufferでダミーデータでも追加しておかないと、ブツブツ途切れて再生してしまう。ちょっとしたダブルバッファみたいな感じになってるんだと思う。ということでPlayの前にBufferNeededを蹴っている。
 あとFrameworkDispatcher.Update()のタイミングが結構シビア。場所を変えると例外が投げられる。とりあえず今は動いているが、いつ動かなくなるかはわからない。
 それとFrameworkDispatcher.Updateを呼ぶタイミングは、SubmitBufferで設定した時間よりも短い間隔で呼ぶ必要がある。例えばBufferが50msec分なら、Updateは50msec以下のインターバルで呼ぶ必要がある。とはいえ、Thread.Sleepは往々にして設定時間の数割増しのインターバルとなるが、それでも正常に動いてるから、よほど大きく離れてない限りは問題ない様子。

 C#は例外的にSerialPortがある程度で、ファイルアクセス以外はほとんどIOが無い(マウスやキーボードはFormの機能)。音声IOも例外ではなく、使いたいならWin32とかで工夫する必要がある。
 XNAは前述の通り開発が終了しているが、ちょっとした音声IOで遊びたいなら有効なツールだと思う。


using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using System;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication9
{
    public partial class Form1 : Form
    {
        Thread t;
        bool loop;

        public Form1()
        {
            InitializeComponent();

            loop = true;

            t = new Thread(new ThreadStart(hoge));
            t.IsBackground = true;
            t.Start();

            FormClosed += Form1_FormClosed;
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            loop = false;
        }

        void hoge()
        {
            using (DynamicSoundEffectInstance audio = new DynamicSoundEffectInstance(44100, AudioChannels.Mono))
            {
                audio.BufferNeeded += Audio_BufferNeeded;
                Audio_BufferNeeded(audio, EventArgs.Empty);

                FrameworkDispatcher.Update();

                audio.Play();

                while (loop)
                {
                    Thread.Sleep(10);
                    FrameworkDispatcher.Update();
                }
            }
        }

        private void Audio_BufferNeeded(object sender, EventArgs e)
        {
            DynamicSoundEffectInstance audio = sender as DynamicSoundEffectInstance;
            if (audio == null)
            {
                return;
            }

            byte[] buff = new byte[audio.GetSampleSizeInBytes(new TimeSpan(0, 0, 0, 0, 50))];

            Random rand = new Random();

            for (int i = 0; i < buff.Length;)
            {
                Int16 data = (Int16)rand.Next(-16384, +16383);
                byte[] datas = BitConverter.GetBytes(data);
                Array.Copy(datas, 0, buff, i, datas.Length);
                i += datas.Length;
            }

            audio.SubmitBuffer(buff);
        }
    }
}

0 件のコメント:

コメントを投稿