From 3853cc51df31f6ed128a4958a3a71fb79a29a627 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 23 Oct 2026 17:09:29 +0200 Subject: [PATCH 1/2] audio: preserve integration gain during PCM takeover --- .../Runtime/PinMameGamelogicEngine.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs b/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs index 8cc7328..7f6aa91 100644 --- a/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs +++ b/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs @@ -88,7 +88,19 @@ public class PinMameGamelogicEngine : MonoBehaviour, IGamelogicEngine, IDisplayF DisplayEncoding IDisplayResourceMetadata.DisplayFormats => DisplayEncoding.Gray2 | DisplayEncoding.Gray4 | DisplayEncoding.Luminance8; DisplayEncoding IDisplayResourceMetadata.DefaultDisplayFormat => DisplayEncoding.Luminance8; bool IDisplayResourceMetadata.FormatAppliesToAllDisplays => true; - IPluginAudioSource IGamelogicAudioResourceProvider.AudioSource => _pluginAudio; + IPluginAudioSource IGamelogicAudioResourceProvider.AudioSource => _playbackAudio ??= new PlaybackAudioSource(this); + private PlaybackAudioSource _playbackAudio; + + // The raw stream is shared; output mute/gain belongs only to its playback owner. + private sealed class PlaybackAudioSource : IPluginAudioSource, IPluginAudioPlaybackControl, IPluginAudioPlaybackGainSource + { + private readonly PinMameGamelogicEngine _engine; + public ResourceKind Kind => ResourceKind.Audio; + public float PlaybackGain => Volatile.Read(ref _engine.DisableAudio) ? 0 : _engine.OutputGain; + internal PlaybackAudioSource(PinMameGamelogicEngine engine) { _engine = engine; } + public IDisposable Subscribe(Action receive) => _engine._pluginAudio.Subscribe(receive); + public IDisposable AcquirePlayback(string pluginId) => _engine._pluginAudio.AcquirePlayback(pluginId); + } [Tooltip("Disable built-in mechs")] public bool DisableMechs = true; @@ -457,11 +469,13 @@ public float OutputGain { set { if (float.IsNaN(value) || float.IsInfinity(value) || value < 0 || value > 1) throw new ArgumentOutOfRangeException(nameof(value)); Volatile.Write(ref _outputGain, value); + _pluginAudio.PlaybackGain = value; } } - private readonly AudioResourceSource _pluginAudio = new(error => Logger.Warn(error, "[PinMAME] Plugin audio subscriber failed.")); + private readonly GainControlledAudioResourceSource _pluginAudio = new(error => Logger.Warn(error, "[PinMAME] Plugin audio subscriber failed.")); private byte[] _pluginAudioBuffer = Array.Empty(); private long _pluginAudioSequence; + private bool _audioPlaybackWasReplaced; // Producer thread only. private readonly HashSet _loggedUnresolvedMappingIds = new(StringComparer.Ordinal); private int _audioFilterChannels; @@ -1657,13 +1671,19 @@ private int HandleAudioAvailable(PinMameAudioInfo audioInfo) private int HandleAudioUpdated(IntPtr framePtr, int frameSize) { + bool playbackReplaced = _pluginAudio.PlaybackReplaced; if (_pluginAudio.HasSubscribers && framePtr != IntPtr.Zero && frameSize > 0) { int length = checked(frameSize * _audioInfo.Channels * sizeof(float)); if (_pluginAudioBuffer.Length < length) _pluginAudioBuffer = new byte[length]; Marshal.Copy(framePtr, _pluginAudioBuffer, 0, length); - _pluginAudio.Publish(new PluginPcmFrame(_pluginAudioBuffer.AsMemory(0, length), PcmEncoding.Float32, (int)_audioInfo.SampleRate, _audioInfo.Channels, frameSize, ++_pluginAudioSequence)); - } - if (_pluginAudio.PlaybackReplaced || DisableAudio) return _audioInfo.SamplesPerFrame; + playbackReplaced = _pluginAudio.PublishForPlayback(new PluginPcmFrame(_pluginAudioBuffer.AsMemory(0, length), PcmEncoding.Float32, (int)_audioInfo.SampleRate, _audioInfo.Channels, frameSize, ++_pluginAudioSequence)); + } + bool returningToLocalPlayback = _audioPlaybackWasReplaced && !playbackReplaced; + _audioPlaybackWasReplaced = playbackReplaced; + // The local ramp was suspended during takeover. Resume at the current gain, + // otherwise a saved/live mute can briefly ramp down from a stale audible level. + if (playbackReplaced || returningToLocalPlayback) _queuedGain = OutputGain; + if (playbackReplaced || DisableAudio) return _audioInfo.SamplesPerFrame; if (_audioFilterChannels == 0) { // don't know how many channels yet return _audioInfo.SamplesPerFrame; From f487bab003c8ac4d314bd9ccbd26e76e6df5f733 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 23 Oct 2026 22:24:03 +0200 Subject: [PATCH 2/2] pinmame: exclude authoring audio preferences from table packages --- .../Runtime/PinMameGamelogicEngine.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs b/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs index 7f6aa91..37f2c95 100644 --- a/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs +++ b/VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs @@ -109,7 +109,7 @@ private sealed class PlaybackAudioSource : IPluginAudioSource, IPluginAudioPlayb [Tooltip("Delay after startup to listen for solenoid events.")] public float SolenoidDelay; - [Tooltip("Disable audio")] + [Tooltip("Disable audio for direct play in Unity. Not exported to .vpe tables; playback settings belong to the player.")] public bool DisableAudio; #endregion @@ -123,8 +123,7 @@ public byte[] Pack() GameId = _legacyGameId, RomId = romId, DisableMechs = DisableMechs, - SolenoidDelay = SolenoidDelay, - DisableAudio = DisableAudio + SolenoidDelay = SolenoidDelay }; return PackageApi.Packer.Pack(data); } @@ -139,7 +138,7 @@ public void Unpack(byte[] bytes) romId = data.RomId; DisableMechs = data.DisableMechs; SolenoidDelay = data.SolenoidDelay; - DisableAudio = data.DisableAudio; + // Playback preferences belong to the player; ignore legacy package mute settings. #if UNITY_EDITOR ClearAvailableHardware(); UpdateCaches(false); @@ -2382,7 +2381,6 @@ public struct PinMameGamelogicEnginePackable public string RomId; public bool DisableMechs; public float SolenoidDelay; - public bool DisableAudio; } #if UNITY_EDITOR