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