開発環境 Microsoft Visual Studio Community 2017
実行環境 Microsoft Windows 10 Home (64-bit)
プロジェクトの種類 Visual C# / Windows フォーム アプリケーション (.NET Framework)
プロジェクト名 WaveLoop

  • 参考

Form1.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using static WaveLoop.WinApi;
 
namespace WaveLoop
{
    public partial class Form1 : Form
    {
        private struct Buffers
        {
            public WAVEHDR wh;
            public IntPtr waveform;
        }
 
        private struct Sequence
        {
            public int msec;
            public int freq;
        }
 
        private const int SAMPLE_RATE = 44100;
        private const int BUFFERS = 2;
        private const int BUF_LEN = 22050;
        private const int TIMEBASE = 24;
 
        private IntPtr hwo = IntPtr.Zero;
        private Buffers[] buffers = new Buffers[BUFFERS];
        private bool playing = false;
        private int wavefreq;
        private int phase;
        private List<Sequence> seq = new List<Sequence>();
        private int nseq;
        private DateTime start;
 
        public Form1()
        {
            InitializeComponent();
 
            Init();
        }
 
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
 
            switch (m.Msg)
            {
                case MM_WOM_DONE:
                    if (playing)
                    {
                        TimerProc();
                        GenerateWave((WAVEHDR)m.GetLParam(typeof(WAVEHDR)));
                    }
                    break;
            }
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            Play();
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            Stop();
        }
 
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            Stop();
        }
 
        //--
 
        private void Init()
        {
            int tempo = 120;
            int[] notes =
            {
                60, 24,
                62, 24,
                64, 24,
                65, 24,
                64, 24,
                62, 24,
                60, 24,
                0, 24,
            };
 
            Sequence s;
            int elapse = 100;  // wait
            for (var i = 0; i < notes.Length; i += 2)
            {
                int gate = (int)((notes[i + 1] / (double)TIMEBASE) * (60.0 / tempo) * 1000);
 
                s.msec = elapse;
                s.freq = (notes[i] <= 0) ? 0 : (int)(440 * Math.Pow(2, (notes[i] - 69) / 12.0));
                seq.Add(s);
 
                elapse += gate;
            }
            s.msec = elapse;
            s.freq = 0;
            seq.Add(s);
        }
 
        private void TimerProc()
        {
            int dur = (int)((DateTime.Now - start).TotalMilliseconds);
            if (seq[nseq].msec <= dur)
            {
                SetFreq(seq[nseq].freq);
                nseq++;
                if (seq.Count <= nseq)
                {
                    //Stop();
                    playing = false;
                }
            }
        }
 
        private void GenerateWave(WAVEHDR wh)
        {
            byte[] data = Enumerable.Repeat((byte)128, BUF_LEN).ToArray();
            for (var i = 0; i < BUF_LEN; i++)
            {
                phase += wavefreq;
                if (SAMPLE_RATE <= phase) phase -= SAMPLE_RATE;
                double t = phase / (double)SAMPLE_RATE;
                data[i] += (byte)((t < 0.5 ? 1 : -1) * 10);
            }
            Marshal.Copy(data, 0, wh.lpData, data.Length);
            waveOutWrite(hwo, wh, (uint)Marshal.SizeOf<WAVEHDR>());
        }
 
        //--
 
        // 再生
        private void Play()
        {
            Trace.WriteLine("Play()");
            if (playing) return;
            playing = true;
 
            SetFreq(0);
            nseq = 0;
            start = DateTime.Now;
 
            uint mmr;
            WAVEFORMATEX wfx = new WAVEFORMATEX();
            wfx.wFormatTag = WAVE_FORMAT_PCM;
            wfx.nChannels = 1;
            wfx.nSamplesPerSec = SAMPLE_RATE;
            wfx.nAvgBytesPerSec = SAMPLE_RATE;
            wfx.nBlockAlign = 1;
            wfx.wBitsPerSample = 8;
            wfx.cbSize = 0;
            mmr = waveOutOpen(ref hwo, WAVE_MAPPER, ref wfx, Handle, 0, CALLBACK_WINDOW);
            Trace.WriteLine("waveOutOpen:" + mmr);
 
            for (var i = 0; i < BUFFERS; i++)
            {
                IntPtr waveform = Marshal.AllocHGlobal(BUF_LEN);
 
                WAVEHDR wh = new WAVEHDR();
                wh.lpData = waveform;
                wh.dwBufferLength = BUF_LEN;
                wh.dwFlags = 0;
                mmr = waveOutPrepareHeader(hwo, wh, (uint)Marshal.SizeOf<WAVEHDR>());
                Trace.WriteLine("waveOutPrepareHeader:" + mmr);
 
                GenerateWave(wh);
 
                buffers[i].wh = wh;
                buffers[i].waveform = waveform;
            }
        }
 
        // 停止
        private void Stop()
        {
            Trace.WriteLine("Stop()");
            playing = false;
 
            if (hwo != IntPtr.Zero)
            {
                uint mmr;
                mmr = waveOutReset(hwo);
                Trace.WriteLine("waveOutReset:" + mmr);
                for (var i = 0; i < BUFFERS; i++)
                {
                    mmr = waveOutUnprepareHeader(hwo, buffers[i].wh, (uint)Marshal.SizeOf<WAVEHDR>());
                    Trace.WriteLine("waveOutUnprepareHeader:" + mmr);
                    Marshal.FreeHGlobal(buffers[i].waveform);
                }
                mmr = waveOutClose(hwo);
                Trace.WriteLine("waveOutClose:" + mmr);
                hwo = IntPtr.Zero;
            }
        }
 
        // 周波数セット
        private void SetFreq(int freq)
        {
            wavefreq = freq;
            phase = 0;
        }
    }
 
    public class WinApi
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct WAVEFORMATEX
        {
            public ushort wFormatTag;
            public ushort nChannels;
            public uint nSamplesPerSec;
            public uint nAvgBytesPerSec;
            public ushort nBlockAlign;
            public ushort wBitsPerSample;
            public ushort cbSize;
        }
 
        [StructLayout(LayoutKind.Sequential)]
        public class WAVEHDR  // マーシャリングのためstructからclassに
        {
            public IntPtr lpData;
            public uint dwBufferLength;
            public uint dwBytesRecorded;
            public uint dwUser;
            public uint dwFlags;
            public uint dwLoops;
            public IntPtr lpNext;
            public uint reserved;
        }
 
        [DllImport("winmm.dll")]
        public static extern uint waveOutOpen(ref IntPtr phwo, uint uDeviceID,
            ref WAVEFORMATEX pwfx, IntPtr dwCallback, uint dwCallbackInstance, uint fdwOpen);
        [DllImport("winmm.dll")]
        public static extern uint waveOutClose(IntPtr hwo);
        [DllImport("winmm.dll")]
        public static extern uint waveOutPrepareHeader(IntPtr hwo, WAVEHDR pwh, uint cbwh);
        [DllImport("winmm.dll")]
        public static extern uint waveOutUnprepareHeader(IntPtr hwo, WAVEHDR pwh, uint cbwh);
        [DllImport("winmm.dll")]
        public static extern uint waveOutWrite(IntPtr hwo, WAVEHDR pwh, uint cbwh);
        [DllImport("winmm.dll")]
        public static extern uint waveOutReset(IntPtr hwo);
 
        public const ushort WAVE_FORMAT_PCM = 1;
        public const uint WAVE_MAPPER = unchecked((uint)-1);
        public const uint CALLBACK_WINDOW = 0x00010000;
        public const int MM_WOM_OPEN = 0x3BB;
        public const int MM_WOM_CLOSE = 0x3BC;
        public const int MM_WOM_DONE = 0x3BD;
        public const uint WAVERR_STILLPLAYING = 33;
    }
}
 
最終更新:2018年05月23日 08:33