mirror of
https://github.com/ryujinx-mirror/ryujinx.git
synced 2024-12-22 19:05:46 +00:00
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
This commit is contained in:
parent
e7c2dc8ec3
commit
cb43cc7e32
35 changed files with 411 additions and 94 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<OpenALAudioBuffer>();
|
||||
|
@ -27,6 +27,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||
_targetFormat = GetALFormat();
|
||||
_isActive = false;
|
||||
_playedSampleCount = 0;
|
||||
SetVolume(requestedVolume);
|
||||
}
|
||||
|
||||
private ALFormat GetALFormat()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<SoundIoAudioBuffer>();
|
||||
_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();
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ namespace Ryujinx.Audio.Common
|
|||
_bufferAppendedCount = 0;
|
||||
_bufferRegisteredCount = 0;
|
||||
_bufferReleasedCount = 0;
|
||||
_volume = 1.0f;
|
||||
_volume = deviceSession.GetVolume();
|
||||
_state = AudioDeviceState.Stopped;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -25,6 +25,18 @@ namespace Ryujinx.Audio.Integration
|
|||
/// </summary>
|
||||
public interface IHardwareDevice : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the volume level for this device.
|
||||
/// </summary>
|
||||
/// <param name="volume">The volume level to set.</param>
|
||||
void SetVolume(float volume);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the volume level for this device.
|
||||
/// </summary>
|
||||
/// <returns>The volume level of this device.</returns>
|
||||
float GetVolume();
|
||||
|
||||
/// <summary>
|
||||
/// Get the supported sample rate of this device.
|
||||
/// </summary>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the volume for all output devices.
|
||||
/// </summary>
|
||||
/// <param name="volume">The volume to set.</param>
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
if (_sessions != null)
|
||||
{
|
||||
foreach (AudioOutputSystem session in _sessions)
|
||||
{
|
||||
session?.SetVolume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the volume for all output devices.
|
||||
/// </summary>
|
||||
/// <returns>A float indicating the volume level.</returns>
|
||||
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)
|
||||
|
|
|
@ -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<MailboxMessage>();
|
||||
|
@ -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);
|
||||
|
|
|
@ -186,12 +186,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||
/// <summary>
|
||||
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
||||
/// </summary>
|
||||
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 <see cref="AudioRenderSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
||||
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
|
|||
/// <param name="workBufferSize">The guest work buffer size.</param>
|
||||
/// <param name="processHandle">The process handle of the application.</param>
|
||||
/// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
|
||||
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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
public Key Screenshot { get; set; }
|
||||
public Key ShowUi { get; set; }
|
||||
public Key Pause { get; set; }
|
||||
public Key ToggleMute { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,6 +139,11 @@ namespace Ryujinx.HLE
|
|||
/// </summary>
|
||||
public AspectRatio AspectRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The audio volume level.
|
||||
/// </summary>
|
||||
public float AudioVolume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An action called when HLE force a refresh of output after docked mode changed.
|
||||
/// </summary>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.")]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
|
|||
/// <summary>
|
||||
/// The current version of the file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 32;
|
||||
public const int CurrentVersion = 33;
|
||||
|
||||
public int Version { get; set; }
|
||||
|
||||
|
@ -179,6 +179,11 @@ namespace Ryujinx.Configuration
|
|||
/// </summary>
|
||||
public AudioBackend AudioBackend { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The audio volume
|
||||
/// </summary>
|
||||
public float AudioVolume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The selected memory manager mode
|
||||
/// </summary>
|
||||
|
|
|
@ -220,6 +220,11 @@ namespace Ryujinx.Configuration
|
|||
/// </summary>
|
||||
public ReactiveObject<AudioBackend> AudioBackend { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The audio backend volume
|
||||
/// </summary>
|
||||
public ReactiveObject<float> AudioVolume { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The selected memory manager mode
|
||||
/// </summary>
|
||||
|
@ -257,6 +262,8 @@ namespace Ryujinx.Configuration
|
|||
ExpandRam.Event += static (sender, e) => LogValueChange(sender, e, nameof(ExpandRam));
|
||||
IgnoreMissingServices = new ReactiveObject<bool>();
|
||||
IgnoreMissingServices.Event += static (sender, e) => LogValueChange(sender, e, nameof(IgnoreMissingServices));
|
||||
AudioVolume = new ReactiveObject<float>();
|
||||
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;
|
||||
|
|
|
@ -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<float> 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;
|
||||
|
|
|
@ -294,35 +294,35 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="_pauseEmulation">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Pause emulation</property>
|
||||
<property name="label" translatable="yes">Pause Emulation</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="PauseEmulation_Pressed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="_resumeEmulation">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Resume emulation</property>
|
||||
<property name="label" translatable="yes">Resume Emulation</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="ResumeEmulation_Pressed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="_stopEmulation">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Stop emulation of the current game and return to game selection</property>
|
||||
<property name="label" translatable="yes">Stop Emulation</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="StopEmulation_Pressed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<object class="GtkMenuItem" id="_pauseEmulation">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Pause emulation</property>
|
||||
<property name="label" translatable="yes">Pause Emulation</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="PauseEmulation_Pressed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="_resumeEmulation">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Resume emulation</property>
|
||||
<property name="label" translatable="yes">Resume Emulation</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="ResumeEmulation_Pressed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="_stopEmulation">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Stop emulation of the current game and return to game selection</property>
|
||||
<property name="label" translatable="yes">Stop Emulation</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="StopEmulation_Pressed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem">
|
||||
<property name="visible">True</property>
|
||||
|
@ -647,14 +647,15 @@
|
|||
<object class="GtkEventBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="button_release_event" handler="AspectRatio_Clicked" swapped="no"/>
|
||||
<signal name="button_release_event" handler="VolumeStatus_Clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_aspectRatio">
|
||||
<object class="GtkLabel" id="_volumeStatus">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="label" translatable="yes"></property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -676,12 +677,19 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_gameStatus">
|
||||
<object class="GtkEventBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<signal name="button_release_event" handler="AspectRatio_Clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_aspectRatio">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -701,7 +709,7 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_fifoStatus">
|
||||
<object class="GtkLabel" id="_gameStatus">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
|
@ -725,6 +733,31 @@
|
|||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_fifoStatus">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">10</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">11</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_gpuName">
|
||||
<property name="visible">True</property>
|
||||
|
@ -736,7 +769,7 @@
|
|||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">10</property>
|
||||
<property name="position">12</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -30,7 +30,8 @@ namespace Ryujinx.Ui.Windows
|
|||
private readonly TimeZoneContentManager _timeZoneContentManager;
|
||||
private readonly HashSet<string> _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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue