Ryujinx/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs
TSRBerry eb528ae0f0
Add workflow to automatically check code style issues for PRs (#4670)
* Add workflow to perform automated checks for PRs

* Downgrade Microsoft.CodeAnalysis to 4.4.0

This is a workaround to fix issues with dotnet-format.
See:
- https://github.com/dotnet/format/issues/1805
- https://github.com/dotnet/format/issues/1800

* Adjust editorconfig to be more compatible with Ryujinx code-style

* Adjust .editorconfig line endings to match .gitattributes

* Disable 'prefer switch expression' rule

* Remove naming styles

These are the default rules, so we don't need to override them.

* Silence IDE0060 in .editorconfig

* Slightly adjust .editorconfig

* Add lost workflow changes

* Move .editorconfig comment to the top

* .editorconfig: private static readonly fields should be _lowerCamelCase

* .editorconfig: Remove alignment for declarations as well

* editorconfig: Add rule for local constants

* Disable CA1822 for HLE services

* Disable CA1822 for ViewModels

Bindings won't work with static members, but this issue is silently ignored.

* Run dotnet format for the whole solution

* Check result code of SDL_GetDisplayBounds

* Fix dotnet format style issues

* Add missing trailing commas

* Update Microsoft.CodeAnalysis.CSharp to 4.6.0

Skipping 4.5.0 since it breaks dotnet format

* Restore old default naming rules for dotnet format

* Add naming rule exception for CPU tests

* checks: Include all files before excluding paths

* Fix dotnet format issues

* Check dotnet format version

* checks: Run dotnet format with severity info again

* checks: Disable naming style rules until they won't crash the process anymore

* Remove unread private member

* checks: Attempt to run analyzers 3 times before giving up

* checks: Enable naming style rules again with the new retry logic
2023-07-24 18:35:04 +02:00

206 lines
5.9 KiB
C#

using OpenTK.Audio.OpenAL;
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Ryujinx.Audio.Backends.OpenAL
{
class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private readonly OpenALHardwareDeviceDriver _driver;
private readonly int _sourceId;
private readonly ALFormat _targetFormat;
private bool _isActive;
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
private ulong _playedSampleCount;
private readonly object _lock = new();
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>();
_sourceId = AL.GenSource();
_targetFormat = GetALFormat();
_isActive = false;
_playedSampleCount = 0;
SetVolume(requestedVolume);
}
private ALFormat GetALFormat()
{
return RequestedSampleFormat switch
{
SampleFormat.PcmInt16 => RequestedChannelCount switch
{
1 => ALFormat.Mono16,
2 => ALFormat.Stereo16,
6 => ALFormat.Multi51Chn16Ext,
_ => throw new NotImplementedException($"Unsupported channel config {RequestedChannelCount}"),
},
_ => throw new NotImplementedException($"Unsupported sample format {RequestedSampleFormat}"),
};
}
public override void PrepareToClose() { }
private void StartIfNotPlaying()
{
AL.GetSource(_sourceId, ALGetSourcei.SourceState, out int stateInt);
ALSourceState State = (ALSourceState)stateInt;
if (State != ALSourceState.Playing)
{
AL.SourcePlay(_sourceId);
}
}
public override void QueueBuffer(AudioBuffer buffer)
{
lock (_lock)
{
OpenALAudioBuffer driverBuffer = new()
{
DriverIdentifier = buffer.DataPointer,
BufferId = AL.GenBuffer(),
SampleCount = GetSampleCount(buffer),
};
AL.BufferData(driverBuffer.BufferId, _targetFormat, buffer.Data, (int)RequestedSampleRate);
_queuedBuffers.Enqueue(driverBuffer);
AL.SourceQueueBuffer(_sourceId, driverBuffer.BufferId);
if (_isActive)
{
StartIfNotPlaying();
}
}
}
public override void SetVolume(float volume)
{
lock (_lock)
{
AL.Source(_sourceId, ALSourcef.Gain, volume);
}
}
public override float GetVolume()
{
AL.GetSource(_sourceId, ALSourcef.Gain, out float volume);
return volume;
}
public override void Start()
{
lock (_lock)
{
_isActive = true;
StartIfNotPlaying();
}
}
public override void Stop()
{
lock (_lock)
{
SetVolume(0.0f);
AL.SourceStop(_sourceId);
_isActive = false;
}
}
public override void UnregisterBuffer(AudioBuffer buffer) { }
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{
lock (_lock)
{
if (!_queuedBuffers.TryPeek(out OpenALAudioBuffer driverBuffer))
{
return true;
}
return driverBuffer.DriverIdentifier != buffer.DataPointer;
}
}
public override ulong GetPlayedSampleCount()
{
lock (_lock)
{
return _playedSampleCount;
}
}
public bool Update()
{
lock (_lock)
{
if (_isActive)
{
AL.GetSource(_sourceId, ALGetSourcei.BuffersProcessed, out int releasedCount);
if (releasedCount > 0)
{
int[] bufferIds = new int[releasedCount];
AL.SourceUnqueueBuffers(_sourceId, releasedCount, bufferIds);
int i = 0;
while (_queuedBuffers.TryPeek(out OpenALAudioBuffer buffer) && i < bufferIds.Length)
{
if (buffer.BufferId == bufferIds[i])
{
_playedSampleCount += buffer.SampleCount;
_queuedBuffers.TryDequeue(out _);
i++;
}
}
Debug.Assert(i == bufferIds.Length, "Unknown buffer ids found!");
AL.DeleteBuffers(bufferIds);
}
return releasedCount > 0;
}
return false;
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing && _driver.Unregister(this))
{
lock (_lock)
{
PrepareToClose();
Stop();
AL.DeleteSource(_sourceId);
}
}
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}