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