From cfd07eee9c88bd44d4cdefdaae1f85a6fd8de190 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Wed, 26 Aug 2026 20:38:11 -0600 Subject: [PATCH] feat: add MockAudioDevice and factory for audio simulation --- .../Audio/MockAudioDevice.cs | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/PepperDash.Essentials.Devices.Common/Audio/MockAudioDevice.cs diff --git a/src/PepperDash.Essentials.Devices.Common/Audio/MockAudioDevice.cs b/src/PepperDash.Essentials.Devices.Common/Audio/MockAudioDevice.cs new file mode 100644 index 00000000..e368a521 --- /dev/null +++ b/src/PepperDash.Essentials.Devices.Common/Audio/MockAudioDevice.cs @@ -0,0 +1,155 @@ +using System.Collections.Generic; +using System.Timers; + +using PepperDash.Core; +using PepperDash.Core.Logging; +using PepperDash.Essentials.Core; +using PepperDash.Essentials.Core.Config; +using Serilog.Events; + +namespace PepperDash.Essentials.Devices.Common; + +/// +/// Mock audio device for testing and simulation purposes. Behaves like a real DSP fader channel, +/// with ramping volume up/down while pressed and mute on/off/toggle, all backed by fake in-memory +/// state rather than any real hardware communication. +/// +public class MockAudioDevice : EssentialsDevice, IBasicVolumeWithFeedback +{ + private const int VolumeHeldRepeatIntervalMs = 100; + private const ushort VolumeStep = 655; + + private ushort _volumeLevel = 32768; + private bool _isMuted; + private Timer _volumeUpTimer; + private Timer _volumeDownTimer; + + /// + public IntFeedback VolumeLevelFeedback { get; private set; } + + /// + public BoolFeedback MuteFeedback { get; private set; } + + /// + /// Constructor for MockAudioDevice + /// + /// Device key + /// Device name + public MockAudioDevice(string key, string name) + : base(key, name) + { + VolumeLevelFeedback = new IntFeedback("volume", () => _volumeLevel); + MuteFeedback = new BoolFeedback("muteOn", () => _isMuted); + + // Seed the feedback's cached value immediately so the UI reflects the starting level + // rather than 0 until the first volume change fires an update. + VolumeLevelFeedback.FireUpdate(); + MuteFeedback.FireUpdate(); + } + + /// + public void SetVolume(ushort level) + { + _volumeLevel = level; + VolumeLevelFeedback.InvokeFireUpdate(); + this.LogDebug("SetVolume: {Level}", _volumeLevel); + } + + /// + public void MuteOn() + { + _isMuted = true; + MuteFeedback.InvokeFireUpdate(); + } + + /// + public void MuteOff() + { + _isMuted = false; + MuteFeedback.InvokeFireUpdate(); + } + + /// + public void MuteToggle() + { + if (_isMuted) + MuteOff(); + else + MuteOn(); + } + + /// + public void VolumeUp(bool pressRelease) + { + if (pressRelease) + { + RampVolume(VolumeStep); + + if (_volumeUpTimer == null) + { + _volumeUpTimer = new Timer(VolumeHeldRepeatIntervalMs) { AutoReset = true }; + _volumeUpTimer.Elapsed += (s, e) => RampVolume(VolumeStep); + _volumeUpTimer.Start(); + } + } + else + { + _volumeUpTimer?.Stop(); + _volumeUpTimer = null; + } + } + + /// + public void VolumeDown(bool pressRelease) + { + if (pressRelease) + { + RampVolume(-VolumeStep); + + if (_volumeDownTimer == null) + { + _volumeDownTimer = new Timer(VolumeHeldRepeatIntervalMs) { AutoReset = true }; + _volumeDownTimer.Elapsed += (s, e) => RampVolume(-VolumeStep); + _volumeDownTimer.Start(); + } + } + else + { + _volumeDownTimer?.Stop(); + _volumeDownTimer = null; + } + } + + // Clamps to ushort range so repeated ramping at the extremes doesn't wrap around. + private void RampVolume(int delta) + { + var newLevel = _volumeLevel + delta; + if (newLevel < ushort.MinValue) + newLevel = ushort.MinValue; + else if (newLevel > ushort.MaxValue) + newLevel = ushort.MaxValue; + + SetVolume((ushort)newLevel); + } +} + +/// +/// Factory for building devices. +/// +public class MockAudioDeviceFactory : EssentialsDeviceFactory +{ + /// + /// Initializes a new instance of the class. + /// + public MockAudioDeviceFactory() + { + TypeNames = new List { "mockaudiodevice", "mockaudio" }; + } + + /// + public override EssentialsDevice BuildDevice(DeviceConfig dc) + { + Debug.LogMessage(LogEventLevel.Debug, "Factory attempting to create new Mock Audio Device"); + return new MockAudioDevice(dc.Key, dc.Name); + } +}