From cb43cc7e322014ce2bd0ee73b06d403be62fa8d5 Mon Sep 17 00:00:00 2001 From: sharmander Date: Thu, 23 Dec 2021 11:33:56 -0500 Subject: [PATCH] UI - Add Volume Controls + Mute Toggle (F2) (#2871) * Add the ability to toggle mute in the status bar. * Add the ability to toggle mute in the status bar. * Formatting fixes * Add hotkey (F2) to mute * Add default hotkey to config.json * Add ability to change volume via slider. * Fix Headless * Fix SDL2 Problem : Credits to d3xMachina * Remove unnecessary work * Address gdk comments * Toggling with Hotkey now properly restores volume to original level. * Toggling with Hotkey now properly restores volume to original level. * Update UI to show Volume % instead of Muted/Unmuted * Clean up the volume ui a bit. * Undo unintentionally committed code. * Implement AudRen Support * Restore intiial volume level in function definition. * Finalize UI * Finalize UI * Use clamp for bounds check * Use Math.Clamp for volume in soundio * Address comments by gdkchan * Address remaining comments * Fix missing semicolon * Address remaining gdkchan comment * Fix comment * Change /* to // * Allow volume slider to change volume immediately. Also force label text to cast to int to prevent decimals from showing in status bar * Remove blank line * Undo setting of volume level when "Cancel" is pressed. * Fix allignment for settings window code --- .../OpenALHardwareDeviceDriver.cs | 4 +- .../OpenALHardwareDeviceSession.cs | 3 +- .../SDL2HardwareDeviceDriver.cs | 4 +- .../SDL2HardwareDeviceSession.cs | 19 ++-- .../SoundIoHardwareDeviceDriver.cs | 6 +- .../SoundIoHardwareDeviceSession.cs | 8 +- .../CompatLayerHardwareDeviceDriver.cs | 6 +- .../Dummy/DummyHardwareDeviceDriver.cs | 4 +- .../Dummy/DummyHardwareDeviceSessionOutput.cs | 4 +- Ryujinx.Audio/Common/AudioDeviceSession.cs | 2 +- .../Integration/HardwareDeviceImpl.cs | 14 ++- Ryujinx.Audio/Integration/IHardwareDevice.cs | 12 ++ .../Integration/IHardwareDeviceDriver.cs | 2 +- Ryujinx.Audio/Output/AudioOutputManager.cs | 40 ++++++- Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs | 31 ++++- .../Renderer/Server/AudioRendererManager.cs | 27 ++++- .../Renderer/Utils/FileHardwareDevice.cs | 11 ++ .../Renderer/Utils/SplitterHardwareDevice.cs | 11 ++ .../Configuration/Hid/KeyboardHotkeys.cs | 1 + Ryujinx.HLE/HLEConfiguration.cs | 9 +- Ryujinx.HLE/HOS/Horizon.cs | 13 +++ .../HOS/Services/Audio/AudioOutManager.cs | 4 +- .../Services/Audio/AudioOutManagerServer.cs | 4 +- .../Services/Audio/AudioRendererManager.cs | 2 +- .../HOS/Services/Audio/IAudioOutManager.cs | 2 +- Ryujinx.HLE/Switch.cs | 15 +++ Ryujinx.Headless.SDL2/Options.cs | 3 + Ryujinx.Headless.SDL2/Program.cs | 3 +- .../Configuration/ConfigurationFileFormat.cs | 7 +- Ryujinx/Configuration/ConfigurationState.cs | 28 +++++ Ryujinx/Ui/MainWindow.cs | 45 ++++++-- Ryujinx/Ui/MainWindow.glade | 107 ++++++++++++------ Ryujinx/Ui/RendererWidgetBase.cs | 22 +++- Ryujinx/Ui/StatusUpdatedEventArgs.cs | 4 +- Ryujinx/Ui/Windows/SettingsWindow.cs | 28 ++++- 35 files changed, 411 insertions(+), 94 deletions(-) diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs index 453208a1..0c793f24 100644 --- a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs +++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs @@ -52,7 +52,7 @@ namespace Ryujinx.Audio.Backends.OpenAL } } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) { if (channelCount == 0) { @@ -73,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL throw new ArgumentException($"{channelCount}"); } - OpenALHardwareDeviceSession session = new OpenALHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount); + OpenALHardwareDeviceSession session = new OpenALHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); _sessions.TryAdd(session, 0); diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs index f0c0f498..ac3319e0 100644 --- a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs +++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs @@ -19,7 +19,7 @@ namespace Ryujinx.Audio.Backends.OpenAL private object _lock = new object(); - public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { _driver = driver; _queuedBuffers = new Queue(); @@ -27,6 +27,7 @@ namespace Ryujinx.Audio.Backends.OpenAL _targetFormat = GetALFormat(); _isActive = false; _playedSampleCount = 0; + SetVolume(requestedVolume); } private ALFormat GetALFormat() diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs index 77545b57..54548c11 100644 --- a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs +++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs @@ -51,7 +51,7 @@ namespace Ryujinx.Audio.Backends.SDL2 return _pauseEvent; } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) { if (channelCount == 0) { @@ -68,7 +68,7 @@ namespace Ryujinx.Audio.Backends.SDL2 throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!"); } - SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount); + SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); _sessions.TryAdd(session, 0); diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs index 843de01a..33e1632d 100644 --- a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs +++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs @@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2 private float _volume; private ushort _nativeSampleFormat; - public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { _driver = driver; _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); @@ -37,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2 _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat); _sampleCount = uint.MaxValue; _started = false; - _volume = 1.0f; + _volume = requestedVolume; } private void EnsureAudioStreamSetup(AudioBuffer buffer) @@ -82,7 +82,7 @@ namespace Ryujinx.Audio.Backends.SDL2 if (frameCount == 0) { - // SDL2 left the responsability to the user to clear the buffer. + // SDL2 left the responsibility to the user to clear the buffer. streamSpan.Fill(0); return; @@ -92,11 +92,16 @@ namespace Ryujinx.Audio.Backends.SDL2 _ringBuffer.Read(samples, 0, samples.Length); - samples.AsSpan().CopyTo(streamSpan); - streamSpan.Slice(samples.Length).Fill(0); + fixed (byte* p = samples) + { + IntPtr pStreamSrc = (IntPtr)p; - // Apply volume to written data - SDL_MixAudioFormat(stream, stream, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME)); + // Zero the dest buffer + streamSpan.Fill(0); + + // Apply volume to written data + SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME)); + } ulong sampleCount = GetSampleCount(samples.Length); diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs index cde5b3d4..02a9a228 100644 --- a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs +++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs @@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.SoundIo return _pauseEvent; } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) { if (channelCount == 0) { @@ -142,12 +142,14 @@ namespace Ryujinx.Audio.Backends.SoundIo sampleRate = Constants.TargetSampleRate; } + volume = Math.Clamp(volume, 0, 1); + if (direction != Direction.Output) { throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!"); } - SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount); + SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); _sessions.TryAdd(session, 0); diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs index ee2eeb77..1e8c814e 100644 --- a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs +++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs @@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Backends.SoundIo private ManualResetEvent _updateRequiredEvent; private int _disposeState; - public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { _driver = driver; _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); _queuedBuffers = new ConcurrentQueue(); _ringBuffer = new DynamicRingBuffer(); - SetupOutputStream(); + SetupOutputStream(requestedVolume); } - private void SetupOutputStream() + private void SetupOutputStream(float requestedVolume) { _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount); _outputStream.WriteCallback += Update; - + _outputStream.Volume = requestedVolume; // TODO: Setup other callbacks (errors, ect). _outputStream.Open(); diff --git a/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs b/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs index 0ae6a620..d9e170c3 100644 --- a/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs +++ b/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs @@ -68,7 +68,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer }; } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) { if (channelCount == 0) { @@ -80,6 +80,8 @@ namespace Ryujinx.Audio.Backends.CompatLayer sampleRate = Constants.TargetSampleRate; } + volume = Math.Clamp(volume, 0, 1); + if (!_realDriver.SupportsDirection(direction)) { if (direction == Direction.Input) @@ -94,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer uint hardwareChannelCount = SelectHardwareChannelCount(channelCount); - IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, hardwareChannelCount); + IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, hardwareChannelCount, volume); if (hardwareChannelCount == channelCount) { diff --git a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs index d729d3f6..f9783ee5 100644 --- a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs +++ b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs @@ -35,7 +35,7 @@ namespace Ryujinx.Audio.Backends.Dummy _pauseEvent = new ManualResetEvent(true); } - public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) { if (sampleRate == 0) { @@ -49,7 +49,7 @@ namespace Ryujinx.Audio.Backends.Dummy if (direction == Direction.Output) { - return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount); + return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); } else { diff --git a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs index 7cc19997..1e6a198a 100644 --- a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs +++ b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs @@ -30,9 +30,9 @@ namespace Ryujinx.Audio.Backends.Dummy private ulong _playedSampleCount; - public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { - _volume = 1.0f; + _volume = requestedVolume; _manager = manager; } diff --git a/Ryujinx.Audio/Common/AudioDeviceSession.cs b/Ryujinx.Audio/Common/AudioDeviceSession.cs index fbdb5a75..511f3905 100644 --- a/Ryujinx.Audio/Common/AudioDeviceSession.cs +++ b/Ryujinx.Audio/Common/AudioDeviceSession.cs @@ -106,7 +106,7 @@ namespace Ryujinx.Audio.Common _bufferAppendedCount = 0; _bufferRegisteredCount = 0; _bufferReleasedCount = 0; - _volume = 1.0f; + _volume = deviceSession.GetVolume(); _state = AudioDeviceState.Stopped; } diff --git a/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs b/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs index d489b008..d51aa3fb 100644 --- a/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs +++ b/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs @@ -30,9 +30,9 @@ namespace Ryujinx.Audio.Integration private byte[] _buffer; - public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate) + public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume) { - _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount); + _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume); _channelCount = channelCount; _sampleRate = sampleRate; _currentBufferTag = 0; @@ -56,6 +56,16 @@ namespace Ryujinx.Audio.Integration _currentBufferTag = _currentBufferTag % 4; } + public void SetVolume(float volume) + { + _session.SetVolume(volume); + } + + public float GetVolume() + { + return _session.GetVolume(); + } + public uint GetChannelCount() { return _channelCount; diff --git a/Ryujinx.Audio/Integration/IHardwareDevice.cs b/Ryujinx.Audio/Integration/IHardwareDevice.cs index 0f67b2c8..1eeb5ca4 100644 --- a/Ryujinx.Audio/Integration/IHardwareDevice.cs +++ b/Ryujinx.Audio/Integration/IHardwareDevice.cs @@ -25,6 +25,18 @@ namespace Ryujinx.Audio.Integration /// public interface IHardwareDevice : IDisposable { + /// + /// Sets the volume level for this device. + /// + /// The volume level to set. + void SetVolume(float volume); + + /// + /// Gets the volume level for this device. + /// + /// The volume level of this device. + float GetVolume(); + /// /// Get the supported sample rate of this device. /// diff --git a/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs b/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs index 1a53fa9b..c1869ce1 100644 --- a/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs +++ b/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs @@ -33,7 +33,7 @@ namespace Ryujinx.Audio.Integration Output } - IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount); + IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f); ManualResetEvent GetUpdateRequiredEvent(); ManualResetEvent GetPauseEvent(); diff --git a/Ryujinx.Audio/Output/AudioOutputManager.cs b/Ryujinx.Audio/Output/AudioOutputManager.cs index 852632fa..dd115295 100644 --- a/Ryujinx.Audio/Output/AudioOutputManager.cs +++ b/Ryujinx.Audio/Output/AudioOutputManager.cs @@ -208,13 +208,14 @@ namespace Ryujinx.Audio.Output SampleFormat sampleFormat, ref AudioInputConfiguration parameter, ulong appletResourceUserId, - uint processHandle) + uint processHandle, + float volume) { int sessionId = AcquireSessionId(); _sessionsBufferEvents[sessionId].Clear(); - IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount); + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume); AudioOutputSystem audioOut = new AudioOutputSystem(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); @@ -247,6 +248,41 @@ namespace Ryujinx.Audio.Output return result; } + /// + /// Sets the volume for all output devices. + /// + /// The volume to set. + public void SetVolume(float volume) + { + if (_sessions != null) + { + foreach (AudioOutputSystem session in _sessions) + { + session?.SetVolume(volume); + } + } + } + + /// + /// Gets the volume for all output devices. + /// + /// A float indicating the volume level. + public float GetVolume() + { + if (_sessions != null) + { + foreach (AudioOutputSystem session in _sessions) + { + if (session != null) + { + return session.GetVolume(); + } + } + } + + return 0.0f; + } + public void Dispose() { if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) diff --git a/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs index e15165b9..303de9bb 100644 --- a/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs +++ b/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs @@ -78,7 +78,7 @@ namespace Ryujinx.Audio.Renderer.Dsp } } - public void Start(IHardwareDeviceDriver deviceDriver) + public void Start(IHardwareDeviceDriver deviceDriver, float volume) { OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax]; @@ -89,7 +89,7 @@ namespace Ryujinx.Audio.Renderer.Dsp for (int i = 0; i < OutputDevices.Length; i++) { // TODO: Don't hardcode sample rate. - OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate); + OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume); } _mailbox = new Mailbox(); @@ -245,6 +245,33 @@ namespace Ryujinx.Audio.Renderer.Dsp _mailbox.SendResponse(MailboxMessage.Stop); } + public float GetVolume() + { + if (OutputDevices != null) + { + foreach (IHardwareDevice outputDevice in OutputDevices) + { + if (outputDevice != null) + { + return outputDevice.GetVolume(); + } + } + } + + return 0f; + } + + public void SetVolume(float volume) + { + if (OutputDevices != null) + { + foreach (IHardwareDevice outputDevice in OutputDevices) + { + outputDevice?.SetVolume(volume); + } + } + } + public void Dispose() { Dispose(true); diff --git a/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs index 7518c447..d20c3c03 100644 --- a/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs +++ b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs @@ -186,12 +186,12 @@ namespace Ryujinx.Audio.Renderer.Server /// /// Start the and worker thread. /// - private void StartLocked() + private void StartLocked(float volume) { _isRunning = true; // TODO: virtual device mapping (IAudioDevice) - Processor.Start(_deviceDriver); + Processor.Start(_deviceDriver, volume); _workerThread = new Thread(SendCommands) { @@ -263,7 +263,7 @@ namespace Ryujinx.Audio.Renderer.Server /// Register a new . /// /// The to register. - private void Register(AudioRenderSystem renderer) + private void Register(AudioRenderSystem renderer, float volume) { lock (_sessionLock) { @@ -274,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server { if (!_isRunning) { - StartLocked(); + StartLocked(volume); } } } @@ -314,7 +314,7 @@ namespace Ryujinx.Audio.Renderer.Server /// The guest work buffer size. /// The process handle of the application. /// A reporting an error or a success. - public ResultCode OpenAudioRenderer(out AudioRenderSystem renderer, IVirtualMemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle) + public ResultCode OpenAudioRenderer(out AudioRenderSystem renderer, IVirtualMemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle, float volume) { int sessionId = AcquireSessionId(); @@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server { renderer = audioRenderer; - Register(renderer); + Register(renderer, volume); } else { @@ -338,6 +338,21 @@ namespace Ryujinx.Audio.Renderer.Server return result; } + public float GetVolume() + { + if (Processor != null) + { + return Processor.GetVolume(); + } + + return 0f; + } + + public void SetVolume(float volume) + { + Processor?.SetVolume(volume); + } + public void Dispose() { if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) diff --git a/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs b/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs index 2008bafc..8d717f6a 100644 --- a/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs +++ b/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs @@ -76,6 +76,17 @@ namespace Ryujinx.Audio.Renderer.Utils _stream.Flush(); } + public void SetVolume(float volume) + { + // Do nothing, volume is not used for FileHardwareDevice at the moment. + } + + public float GetVolume() + { + // FileHardwareDevice does not incorporate volume. + return 0; + } + public uint GetChannelCount() { return _channelCount; diff --git a/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs b/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs index c5411ac0..e6be07c0 100644 --- a/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs +++ b/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs @@ -37,6 +37,17 @@ namespace Ryujinx.Audio.Renderer.Utils _secondaryDevice?.AppendBuffer(data, channelCount); } + public void SetVolume(float volume) + { + _baseDevice.SetVolume(volume); + _secondaryDevice.SetVolume(volume); + } + + public float GetVolume() + { + return _baseDevice.GetVolume(); + } + public uint GetChannelCount() { return _baseDevice.GetChannelCount(); diff --git a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs index dd0d7c21..d8183abc 100644 --- a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs +++ b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -6,5 +6,6 @@ public Key Screenshot { get; set; } public Key ShowUi { get; set; } public Key Pause { get; set; } + public Key ToggleMute { get; set; } } } diff --git a/Ryujinx.HLE/HLEConfiguration.cs b/Ryujinx.HLE/HLEConfiguration.cs index 6dfa6a26..bba21435 100644 --- a/Ryujinx.HLE/HLEConfiguration.cs +++ b/Ryujinx.HLE/HLEConfiguration.cs @@ -139,6 +139,11 @@ namespace Ryujinx.HLE /// public AspectRatio AspectRatio { get; set; } + /// + /// The audio volume level. + /// + public float AudioVolume { get; set; } + /// /// An action called when HLE force a refresh of output after docked mode changed. /// @@ -164,7 +169,8 @@ namespace Ryujinx.HLE string timeZone, MemoryManagerMode memoryManagerMode, bool ignoreMissingServices, - AspectRatio aspectRatio) + AspectRatio aspectRatio, + float audioVolume) { VirtualFileSystem = virtualFileSystem; LibHacHorizonManager = libHacHorizonManager; @@ -187,6 +193,7 @@ namespace Ryujinx.HLE MemoryManagerMode = memoryManagerMode; IgnoreMissingServices = ignoreMissingServices; AspectRatio = aspectRatio; + AudioVolume = audioVolume; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 74bdb647..b21bf3e3 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -243,6 +243,7 @@ namespace Ryujinx.HLE.HOS AudioOutputManager = new AudioOutputManager(); AudioInputManager = new AudioInputManager(); AudioRendererManager = new AudioRendererManager(); + AudioRendererManager.SetVolume(Device.Configuration.AudioVolume); AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(); IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax]; @@ -255,6 +256,7 @@ namespace Ryujinx.HLE.HOS } AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents); + AudioOutputManager.SetVolume(Device.Configuration.AudioVolume); IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax]; @@ -326,6 +328,17 @@ namespace Ryujinx.HLE.HOS } } + public void SetVolume(float volume) + { + AudioOutputManager.SetVolume(volume); + AudioRendererManager.SetVolume(volume); + } + + public float GetVolume() + { + return AudioOutputManager.GetVolume() == 0 ? AudioRendererManager.GetVolume() : AudioOutputManager.GetVolume(); + } + public void ReturnFocus() { AppletState.SetFocus(true); diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs index 29490553..7b289196 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs @@ -20,11 +20,11 @@ namespace Ryujinx.HLE.HOS.Services.Audio return _impl.ListAudioOuts(); } - public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle) + public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume) { var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory; - ResultCode result = (ResultCode)_impl.OpenAudioOut(out outputDeviceName, out outputConfiguration, out AudioOutputSystem outSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle); + ResultCode result = (ResultCode)_impl.OpenAudioOut(out outputDeviceName, out outputConfiguration, out AudioOutputSystem outSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle, volume); if (result == ResultCode.Success) { diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs index 3040696e..8a987dba 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs @@ -75,7 +75,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize); - ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume); if (resultCode == ResultCode.Success) { @@ -142,7 +142,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize); - ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume); if (resultCode == ResultCode.Success) { diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs index 5aff3475..62bd0ba6 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs @@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio { var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory; - ResultCode result = (ResultCode)_impl.OpenAudioRenderer(out AudioRenderSystem renderer, memoryManager, ref parameter, appletResourceUserId, workBufferTransferMemory.Address, workBufferTransferMemory.Size, processHandle); + ResultCode result = (ResultCode)_impl.OpenAudioRenderer(out AudioRenderSystem renderer, memoryManager, ref parameter, appletResourceUserId, workBufferTransferMemory.Address, workBufferTransferMemory.Size, processHandle, context.Device.Configuration.AudioVolume); if (result == ResultCode.Success) { diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs index 0b164019..70e60d2e 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs @@ -7,6 +7,6 @@ namespace Ryujinx.HLE.HOS.Services.Audio { public string[] ListAudioOuts(); - public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle); + public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume); } } diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index eeb0e7b9..29d2ddd4 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -146,6 +146,21 @@ namespace Ryujinx.HLE Gpu.Window.Present(swapBuffersCallback); } + public void SetVolume(float volume) + { + System.SetVolume(volume); + } + + public float GetVolume() + { + return System.GetVolume(); + } + + public bool IsAudioMuted() + { + return System.GetVolume() == 0; + } + public void DisposeGpu() { Gpu.Dispose(); diff --git a/Ryujinx.Headless.SDL2/Options.cs b/Ryujinx.Headless.SDL2/Options.cs index 179246c9..2c635a59 100644 --- a/Ryujinx.Headless.SDL2/Options.cs +++ b/Ryujinx.Headless.SDL2/Options.cs @@ -109,6 +109,9 @@ namespace Ryujinx.Headless.SDL2 [Option("memory-manager-mode", Required = false, Default = MemoryManagerMode.HostMappedUnsafe, HelpText = "The selected memory manager mode.")] public MemoryManagerMode MemoryManagerMode { get; set; } + [Option("audio-volume", Required = false, Default = 1.0f, HelpText ="The audio level (0 to 1).")] + public float AudioVolume { get; set; } + // Logging [Option("enable-file-logging", Required = false, Default = false, HelpText = "Enables logging to a file on disk.")] diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index 39dbddcd..eddf9351 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -465,7 +465,8 @@ namespace Ryujinx.Headless.SDL2 options.SystemTimeZone, options.MemoryManagerMode, (bool)options.IgnoreMissingServices, - options.AspectRatio); + options.AspectRatio, + options.AudioVolume); return new Switch(configuration); } diff --git a/Ryujinx/Configuration/ConfigurationFileFormat.cs b/Ryujinx/Configuration/ConfigurationFileFormat.cs index dda86ff5..648004d5 100644 --- a/Ryujinx/Configuration/ConfigurationFileFormat.cs +++ b/Ryujinx/Configuration/ConfigurationFileFormat.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 32; + public const int CurrentVersion = 33; public int Version { get; set; } @@ -179,6 +179,11 @@ namespace Ryujinx.Configuration /// public AudioBackend AudioBackend { get; set; } + /// + /// The audio volume + /// + public float AudioVolume { get; set; } + /// /// The selected memory manager mode /// diff --git a/Ryujinx/Configuration/ConfigurationState.cs b/Ryujinx/Configuration/ConfigurationState.cs index 9cf3f650..02136591 100644 --- a/Ryujinx/Configuration/ConfigurationState.cs +++ b/Ryujinx/Configuration/ConfigurationState.cs @@ -220,6 +220,11 @@ namespace Ryujinx.Configuration /// public ReactiveObject AudioBackend { get; private set; } + /// + /// The audio backend volume + /// + public ReactiveObject AudioVolume { get; private set; } + /// /// The selected memory manager mode /// @@ -257,6 +262,8 @@ namespace Ryujinx.Configuration ExpandRam.Event += static (sender, e) => LogValueChange(sender, e, nameof(ExpandRam)); IgnoreMissingServices = new ReactiveObject(); IgnoreMissingServices.Event += static (sender, e) => LogValueChange(sender, e, nameof(IgnoreMissingServices)); + AudioVolume = new ReactiveObject(); + AudioVolume.Event += static (sender, e) => LogValueChange(sender, e, nameof(AudioVolume)); } } @@ -460,6 +467,7 @@ namespace Ryujinx.Configuration EnableFsIntegrityChecks = System.EnableFsIntegrityChecks, FsGlobalAccessLogMode = System.FsGlobalAccessLogMode, AudioBackend = System.AudioBackend, + AudioVolume = System.AudioVolume, MemoryManagerMode = System.MemoryManagerMode, ExpandRam = System.ExpandRam, IgnoreMissingServices = System.IgnoreMissingServices, @@ -553,6 +561,7 @@ namespace Ryujinx.Configuration Hid.Hotkeys.Value = new KeyboardHotkeys { ToggleVsync = Key.Tab, + ToggleMute = Key.F2, Screenshot = Key.F8, ShowUi = Key.F4, Pause = Key.F5 @@ -929,6 +938,24 @@ namespace Ryujinx.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 33) + { + Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 33."); + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + Screenshot = configurationFileFormat.Hotkeys.Screenshot, + ShowUi = configurationFileFormat.Hotkeys.ShowUi, + Pause = configurationFileFormat.Hotkeys.Pause, + ToggleMute = Key.F2 + }; + + configurationFileFormat.AudioVolume = 1; + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading; Graphics.ResScale.Value = configurationFileFormat.ResScale; @@ -960,6 +987,7 @@ namespace Ryujinx.Configuration System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks; System.FsGlobalAccessLogMode.Value = configurationFileFormat.FsGlobalAccessLogMode; System.AudioBackend.Value = configurationFileFormat.AudioBackend; + System.AudioVolume.Value = configurationFileFormat.AudioVolume; System.MemoryManagerMode.Value = configurationFileFormat.MemoryManagerMode; System.ExpandRam.Value = configurationFileFormat.ExpandRam; System.IgnoreMissingServices.Value = configurationFileFormat.IgnoreMissingServices; diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index c399400a..51a09a31 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Reflection; @@ -132,6 +132,7 @@ namespace Ryujinx.Ui [GUI] ProgressBar _progressBar; [GUI] Box _viewBox; [GUI] Label _vSyncStatus; + [GUI] Label _volumeStatus; [GUI] Box _listStatusBox; [GUI] Label _loadingStatusLabel; [GUI] ProgressBar _loadingStatusBar; @@ -205,6 +206,7 @@ namespace Ryujinx.Ui ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; if (ConfigurationState.Instance.Ui.StartFullscreen) { @@ -305,6 +307,11 @@ namespace Ryujinx.Ui } } + private void UpdateAudioVolumeState(object sender, ReactiveEventArgs e) + { + _emulationContext?.SetVolume(e.NewValue); + } + private void WindowStateEvent_Changed(object o, WindowStateEventArgs args) { _fullScreen.Label = args.Event.NewWindowState.HasFlag(Gdk.WindowState.Fullscreen) ? "Exit Fullscreen" : "Enter Fullscreen"; @@ -562,7 +569,8 @@ namespace Ryujinx.Ui ConfigurationState.Instance.System.TimeZone, ConfigurationState.Instance.System.MemoryManagerMode, ConfigurationState.Instance.System.IgnoreMissingServices, - ConfigurationState.Instance.Graphics.AspectRatio); + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume); _emulationContext = new HLE.Switch(configuration); } @@ -1108,11 +1116,12 @@ namespace Ryujinx.Ui { Application.Invoke(delegate { - _gameStatus.Text = args.GameStatus; - _fifoStatus.Text = args.FifoStatus; - _gpuName.Text = args.GpuName; - _dockedMode.Text = args.DockedMode; - _aspectRatio.Text = args.AspectRatio; + _gameStatus.Text = args.GameStatus; + _fifoStatus.Text = args.FifoStatus; + _gpuName.Text = args.GpuName; + _dockedMode.Text = args.DockedMode; + _aspectRatio.Text = args.AspectRatio; + _volumeStatus.Text = GetVolumeLabelText(args.Volume); if (args.VSyncEnabled) { @@ -1173,6 +1182,28 @@ namespace Ryujinx.Ui ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; } + private string GetVolumeLabelText(float volume) + { + string icon = volume == 0 ? "🔇" : "🔊"; + + return $"{icon} {(int)(volume * 100)}%"; + } + + private void VolumeStatus_Clicked(object sender, ButtonReleaseEventArgs args) + { + if (_emulationContext != null) + { + if (_emulationContext.IsAudioMuted()) + { + _emulationContext.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + _emulationContext.SetVolume(0); + } + } + } + private void AspectRatio_Clicked(object sender, ButtonReleaseEventArgs args) { AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value; diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade index 66b307fb..a9ab43e1 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -294,35 +294,35 @@ True False - - True - False - Pause emulation - Pause Emulation - True - - - - - - True - False - Resume emulation - Resume Emulation - True - - - - - - True - False - Stop emulation of the current game and return to game selection - Stop Emulation - True - - - + + True + False + Pause emulation + Pause Emulation + True + + + + + + True + False + Resume emulation + Resume Emulation + True + + + + + + True + False + Stop emulation of the current game and return to game selection + Stop Emulation + True + + + True @@ -647,14 +647,15 @@ True False - + - + True False start 5 5 + @@ -676,12 +677,19 @@ - + True False - start - 5 - 5 + + + + True + False + start + 5 + 5 + + False @@ -701,7 +709,7 @@ - + True False start @@ -725,6 +733,31 @@ 9 + + + True + False + start + 5 + 5 + + + False + True + 10 + + + + + True + False + + + False + True + 11 + + True @@ -736,7 +769,7 @@ True True - 10 + 12 diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index c25e2163..8830a4f5 100644 --- a/Ryujinx/Ui/RendererWidgetBase.cs +++ b/Ryujinx/Ui/RendererWidgetBase.cs @@ -425,6 +425,7 @@ namespace Ryujinx.Ui StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( Device.EnableDeviceVsync, + Device.GetVolume(), dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", @@ -598,6 +599,19 @@ namespace Ryujinx.Ui (Toplevel as MainWindow)?.TogglePause(); } + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute)) + { + if (Device.IsAudioMuted()) + { + Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + Device.SetVolume(0); + } + } + _prevHotkeyState = currentHotkeyState; } @@ -627,7 +641,8 @@ namespace Ryujinx.Ui ToggleVSync = 1 << 0, Screenshot = 1 << 1, ShowUi = 1 << 2, - Pause = 1 << 3 + Pause = 1 << 3, + ToggleMute = 1 << 4 } private KeyboardHotkeyState GetHotkeyState() @@ -654,6 +669,11 @@ namespace Ryujinx.Ui state |= KeyboardHotkeyState.Pause; } + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) + { + state |= KeyboardHotkeyState.ToggleMute; + } + return state; } } diff --git a/Ryujinx/Ui/StatusUpdatedEventArgs.cs b/Ryujinx/Ui/StatusUpdatedEventArgs.cs index c9fba77b..df83efa4 100644 --- a/Ryujinx/Ui/StatusUpdatedEventArgs.cs +++ b/Ryujinx/Ui/StatusUpdatedEventArgs.cs @@ -5,15 +5,17 @@ namespace Ryujinx.Ui public class StatusUpdatedEventArgs : EventArgs { public bool VSyncEnabled; + public float Volume; public string DockedMode; public string AspectRatio; public string GameStatus; public string FifoStatus; public string GpuName; - public StatusUpdatedEventArgs(bool vSyncEnabled, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) + public StatusUpdatedEventArgs(bool vSyncEnabled, float volume, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) { VSyncEnabled = vSyncEnabled; + Volume = volume; DockedMode = dockedMode; AspectRatio = aspectRatio; GameStatus = gameStatus; diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs index da2301f0..c5aeebda 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/Ryujinx/Ui/Windows/SettingsWindow.cs @@ -30,7 +30,8 @@ namespace Ryujinx.Ui.Windows private readonly TimeZoneContentManager _timeZoneContentManager; private readonly HashSet _validTzRegions; - private long _systemTimeOffset; + private long _systemTimeOffset; + private float _previousVolumeLevel; #pragma warning disable CS0649, IDE0044 [GUI] CheckButton _errorLogToggle; @@ -65,6 +66,8 @@ namespace Ryujinx.Ui.Windows [GUI] EntryCompletion _systemTimeZoneCompletion; [GUI] Box _audioBackendBox; [GUI] ComboBox _audioBackendSelect; + [GUI] Label _audioVolumeLabel; + [GUI] Scale _audioVolumeSlider; [GUI] SpinButton _systemTimeYearSpin; [GUI] SpinButton _systemTimeMonthSpin; [GUI] SpinButton _systemTimeDaySpin; @@ -364,6 +367,20 @@ namespace Ryujinx.Ui.Windows _audioBackendBox.Add(_audioBackendSelect); _audioBackendSelect.Show(); + _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume; + _audioVolumeLabel = new Label("Volume: "); + _audioVolumeSlider = new Scale(Orientation.Horizontal, 0, 100, 1); + _audioVolumeLabel.MarginStart = 10; + _audioVolumeSlider.ValuePos = PositionType.Right; + _audioVolumeSlider.WidthRequest = 200; + + _audioVolumeSlider.Value = _previousVolumeLevel * 100; + _audioVolumeSlider.ValueChanged += VolumeSlider_OnChange; + _audioBackendBox.Add(_audioVolumeLabel); + _audioBackendBox.Add(_audioVolumeSlider); + _audioVolumeLabel.Show(); + _audioVolumeSlider.Show(); + bool openAlIsSupported = false; bool soundIoIsSupported = false; bool sdl2IsSupported = false; @@ -498,6 +515,9 @@ namespace Ryujinx.Ui.Windows ConfigurationState.Instance.Graphics.BackendThreading.Value = backendThreading; ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId); ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom; + ConfigurationState.Instance.System.AudioVolume.Value = (float)_audioVolumeSlider.Value / 100.0f; + + _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; if (_audioBackendSelect.GetActiveIter(out TreeIter activeIter)) { @@ -651,6 +671,11 @@ namespace Ryujinx.Ui.Windows controllerWindow.Show(); } + private void VolumeSlider_OnChange(object sender, EventArgs args) + { + ConfigurationState.Instance.System.AudioVolume.Value = (float)(_audioVolumeSlider.Value / 100); + } + private void SaveToggle_Activated(object sender, EventArgs args) { SaveSettings(); @@ -664,6 +689,7 @@ namespace Ryujinx.Ui.Windows private void CloseToggle_Activated(object sender, EventArgs args) { + ConfigurationState.Instance.System.AudioVolume.Value = _previousVolumeLevel; Dispose(); } }