Newer
Older
Qwilight / Qwilight / System / AudioSystem / AudioSystem.cs
using FMOD;
using Qwilight.UIComponent;
using Qwilight.Utilities;
using Qwilight.ViewModel;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.IO;
using System.Runtime.InteropServices;

namespace Qwilight
{
    public class AudioSystem : Model
    {
        static readonly string FaultEntryPath = Path.Combine(QwilightComponent.FaultEntryPath, nameof(AudioSystem));

        static readonly Dictionary<string, AudioConfigure> _audioConfigureValues = new()
        {
            {
                "Beats Flex",
                new()
                {
                    AudioWait = -173.0,
                    HandleInputAudio = false
                }
            },
            {
                "Buds2 Pro",
                new()
                {
                    AudioWait = -250.0,
                    HandleInputAudio = false
                }
            },
            {
                "Buds2 Stereo",
                new()
                {
                    AudioWait = -250.0,
                    HandleInputAudio = false
                }
            },
            {
                "direm W1",
                new()
                {
                    AudioWait = -48.0,
                    HandleInputAudio = false
                }
            },
            {
                "Galaxy Buds Pro",
                new()
                {
                    AudioWait = -337.0,
                    HandleInputAudio = false
                }
            },
            {
                "MOMENTUM 4",
                new()
                {
                    AudioWait = -249.0,
                    HandleInputAudio = false
                }
            },
            {
                "Razer Hammerhead TWS (2nd Gen)",
                new()
                {
                    AudioWait = -275.0,
                    HandleInputAudio = false
                }
            },
            {
                "Razer Hammerhead TWS Pro",
                new()
                {
                    AudioWait = -259.0,
                    HandleInputAudio = false
                }
            },
            {
                "WF-1000XM5",
                new()
                {
                    AudioWait = -216.0,
                    HandleInputAudio = false
                }
            },
            {
                "WH-1000XM5",
                new()
                {
                    AudioWait = -216.0,
                    HandleInputAudio = false
                }
            }
        };

        public static readonly AudioSystem Instance = QwilightComponent.GetBuiltInData<AudioSystem>(nameof(AudioSystem));

        const int Channel = 4093;
        const MODE LoadingAudioModes = MODE.DEFAULT | MODE._2D | MODE._3D_WORLDRELATIVE | MODE._3D_INVERSEROLLOFF | MODE.OPENMEMORY | MODE.ACCURATETIME | MODE.MPEGSEARCH | MODE.IGNORETAGS | MODE.LOWMEM;

        public const int MainAudio = 0;
        public const int InputAudio = 1;
        public const int SEAudio = 2;
        public const int TotalAudio = 3;

        readonly ReaderWriterLockSlim _audioCSX = new();
        readonly Timer _audioHandler;
        readonly SYSTEM_CALLBACK _onModified;
        readonly float[] _audioVolumes = new float[4];
        readonly ChannelGroup[] _audioGroups = new ChannelGroup[3];
        readonly ConcurrentDictionary<IAudioContainer, ConcurrentDictionary<string, AudioItem>> _audioMap = new();
        readonly ConcurrentDictionary<IAudioHandler, ConcurrentBag<AudioHandlerItem>> _audioHandlerMap = new();
        readonly DSP[] _audioVisualizerComputers = new DSP[2];
        readonly DSP[] _equalizerComputers = new DSP[2];
        readonly DSP[] _tubeComputers = new DSP[2];
        readonly DSP[] _valueSFXComputers = new DSP[2];
        readonly DSP[] _flangeComputers = new DSP[2];
        readonly DSP[] _averagerComputers = new DSP[2];
        readonly DSP[] _audioMultiplierAtoneComputers = new DSP[2];
        readonly double[][] _audioVisualizerValue = new double[2][];
        FMOD.System _targetSystem;
        int _rate;
        bool _isAvailable;
        float _audioInputVolume = 1F;
        AudioValue? _audioValue;

        public AudioItem? BanalAudio { get; set; }

        public void LoadBanalAudio()
        {
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    BanalAudio?.Dispose();
                }
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
            try
            {
                var filePath = Configure.Instance.BanalAudioFilePath;
                if (File.Exists(filePath))
                {
                    BanalAudio = Load(filePath, null, 1F);
                }
                else
                {
                    BanalAudio = null;
                }
            }
            catch
            {
                BanalAudio = null;
            }
        }

        public ConcurrentDictionary<string, AudioItem> DefaultAudioItemMap { get; } = new();

        public string GetDefaultAudioFileName(int randomMillis)
        {
            var defaultAudioFilePathItems = Configure.Instance.DefaultAudioFilePathItems;
            var defaultAudioFilePathItemsLength = defaultAudioFilePathItems.Length;
            return defaultAudioFilePathItems.Length > 0 ? $"{nameof(AudioSystem)}://{defaultAudioFilePathItems[randomMillis < defaultAudioFilePathItemsLength ? randomMillis : randomMillis % defaultAudioFilePathItemsLength].Value}" : null;
        }

        public void WipeDefaultAudioItem(string filePath)
        {
            if (DefaultAudioItemMap.TryRemove($"{nameof(AudioSystem)}://{filePath}", out var defaultAudioItem))
            {
                _audioCSX.EnterWriteLock();
                try
                {
                    if (_isAvailable)
                    {
                        defaultAudioItem.Dispose();
                    }
                }
                finally
                {
                    _audioCSX.ExitWriteLock();
                }
            }
        }

        public void LoadDefaultAudioItem(string filePath)
        {
            WipeDefaultAudioItem(filePath);
            try
            {
                if (File.Exists(filePath))
                {
                    DefaultAudioItemMap[$"{nameof(AudioSystem)}://{filePath}"] = Load(filePath, null, 1F, null, true);
                }
            }
            catch
            {
            }
        }

        public void LoadDefaultAudioItems()
        {
            foreach (var defaultAudioFilePathItem in Configure.Instance.DefaultAudioFilePathItems)
            {
                LoadDefaultAudioItem(defaultAudioFilePathItem.Value);
            }
        }

        public Dictionary<PostableItem, AudioItem> PostableItemAudioMap { get; } = new();

        public AudioItem PostableItemAudio { get; set; }

        public AudioItem PostedItemAudio { get; set; }

        public long LastHandledAudioInputMillis { get; set; }

        public string AudioDate { get; set; }

        public string AudioDateHTML { get; set; }

        public ObservableCollection<AudioValue> AudioValues { get; } = new();

        public int AudioItemCount => _audioMap.Values.Sum(audioItems => audioItems.Count);

        public int AudioHandlerItemCount => _audioHandlerMap.Values.Sum(audioHandlerItems => audioHandlerItems.Count);

        public AudioValue? AudioValue
        {
            get => _audioValue;

            set
            {
                if (SetProperty(ref _audioValue, value, nameof(AudioValue)) && value.HasValue)
                {
                    var audioValue = value.Value;
                    var audioValueID = audioValue.ID;
                    _targetSystem.setDriver(audioValueID);
                    switch (Configure.Instance.AudioVariety)
                    {
                        case OUTPUTTYPE.WASAPI:
                            Configure.Instance.LastWASAPIAudioValueID = audioValueID;
                            break;
                        case OUTPUTTYPE.ASIO:
                            Configure.Instance.LastASIOAudioValueID = audioValueID;
                            break;
                    }
                    var audioValueName = audioValue.Name;
                    if (Configure.Instance.AudioConfigureValues.TryGetValue(audioValueName, out var audioConfigureValue))
                    {
                        Configure.Instance.BanalAudioWait = audioConfigureValue.AudioWait;
                        Configure.Instance.HandleInputAudio = audioConfigureValue.HandleInputAudio;
                    }
                    else
                    {
                        Configure.Instance.AudioConfigureValues[audioValueName] = _audioConfigureValues.FirstOrDefault(audioConfigureValue => audioValueName.Contains(audioConfigureValue.Key)).Value ?? new()
                        {
                            HandleInputAudio = true
                        };
                    }
                }
            }
        }

        public AudioSystem()
        {
            Utility.CopyFile(Path.Combine(QwilightComponent.CPUAssetsEntryPath, "fmod.dll"), Path.Combine(AppContext.BaseDirectory, "fmod.dll"));
            _onModified = (system, type, commanddata1, commanddata2, userdata) =>
            {
                _targetSystem.getNumDrivers(out var audioValueCount);
                var audioValues = Enumerable.Range(0, audioValueCount).Select(i =>
                {
                    _targetSystem.getDriverInfo(i, out var audioValueName, 128, out _, out _, out _, out _);
                    return new AudioValue
                    {
                        ID = i,
                        Name = audioValueName
                    };
                }).ToArray();
                UIHandler.Instance.HandleParallel(() =>
                {
                    AudioValues.Clear();
                    foreach (var audioValue in audioValues)
                    {
                        AudioValues.Add(audioValue);
                    }
                    AudioValue = AudioValues.GetSafely(Configure.Instance.AudioVariety switch
                    {
                        OUTPUTTYPE.WASAPI => Configure.Instance.LastWASAPIAudioValueID,
                        OUTPUTTYPE.ASIO => Configure.Instance.LastASIOAudioValueID,
                        _ => default
                    });
                });
                return RESULT.OK;
            };

            var targetAudioVisualizerValues = new double[2][];
            var targetAudioHeights = new double[2][];
            float[][] audioVisualizerItems = null;
            var lastLength = 0;
            for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
            {
                targetAudioVisualizerValues[audioVariety] = new double[256];
                targetAudioHeights[audioVariety] = new double[256];
                _audioVisualizerValue[audioVariety] = new double[256];
            }
            _audioHandler = new(state =>
            {
                _audioCSX.EnterWriteLock();
                try
                {
                    if (_isAvailable)
                    {
                        if (Configure.Instance.AudioVisualizer)
                        {
                            var audioVisualizerCount = Configure.Instance.AudioVisualizerCount;
                            for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                            {
                                Array.Clear(targetAudioHeights[audioVariety], 0, audioVisualizerCount);
                                if (_audioGroups[audioVariety].getNumChannels(out var available) == RESULT.OK && available > 0)
                                {
                                    if (_audioVisualizerComputers[audioVariety].getParameterData((int)DSP_FFT.SPECTRUMDATA, out var data, out _) == RESULT.OK)
                                    {
                                        var dataFFT = Marshal.PtrToStructure<DSP_PARAMETER_FFT>(data);
                                        if (audioVisualizerItems == null || audioVisualizerItems.GetLength(0) != dataFFT.numchannels || lastLength != dataFFT.length)
                                        {
                                            audioVisualizerItems = new float[dataFFT.numchannels][];
                                            lastLength = dataFFT.length;
                                            for (var i = audioVisualizerItems.Length - 1; i >= 0; --i)
                                            {
                                                audioVisualizerItems[i] = new float[lastLength];
                                            }
                                        }
                                        dataFFT.getSpectrum(ref audioVisualizerItems);
                                        if (audioVisualizerItems.Length > 0)
                                        {
                                            foreach (var audioVisualizerItem in audioVisualizerItems)
                                            {
                                                for (var j = audioVisualizerItem.Length - 1; j >= 0; --j)
                                                {
                                                    var m = (int)(j / ((double)audioVisualizerItem.Length / audioVisualizerCount));
                                                    targetAudioHeights[audioVariety][m] += audioVisualizerItem[m];
                                                }
                                            }
                                            for (var i = audioVisualizerCount - 1; i >= 0; --i)
                                            {
                                                targetAudioHeights[audioVariety][i] = Math.Clamp(targetAudioHeights[audioVariety][i], 0.0, 1.0);
                                            }
                                            targetAudioVisualizerValues[audioVariety] = targetAudioHeights[audioVariety];
                                        }
                                    }
                                }
                                Array.Copy(targetAudioHeights[audioVariety], 0, targetAudioVisualizerValues[audioVariety], 0, audioVisualizerCount);
                                for (var i = audioVisualizerCount - 1; i >= 0; --i)
                                {
                                    _audioVisualizerValue[audioVariety][i] += Utility.GetMove(targetAudioVisualizerValues[audioVariety][i], _audioVisualizerValue[audioVariety][i], 1000.0 / 60);
                                }
                            }
                        }
                    }
                }
                finally
                {
                    _audioCSX.ExitWriteLock();
                }

                _audioInputVolume += (float)Utility.GetMove(LastHandledAudioInputMillis + QwilightComponent.StandardWaitMillis >= Environment.TickCount64 ? (float)(Configure.Instance.WaveFadeVolume / 100.0) : 1F, _audioInputVolume);
                var totalAudioVolume = _audioVolumes[TotalAudio] * _audioInputVolume * (!Configure.Instance.LostPointAudio && !ViewModels.Instance.MainValue.HasPoint ? 0F : 1F);

                for (var audioVariety = SEAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            var targetAudioVolume = (float)(1 - Math.Sqrt(1 - Math.Pow(totalAudioVolume * _audioVolumes[audioVariety], 2)));
                            if (_audioGroups[audioVariety].getVolume(out var audioVolume) == RESULT.OK && audioVolume != targetAudioVolume)
                            {
                                _audioGroups[audioVariety].setVolume(targetAudioVolume);
                            }
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }

                _audioCSX.EnterWriteLock();
                try
                {
                    if (_isAvailable)
                    {
                        _targetSystem.update();
                    }
                }
                finally
                {
                    _audioCSX.ExitWriteLock();
                }
            }, null, TimeSpan.Zero, QwilightComponent.StandardFrametime);
        }

        public virtual void Init()
        {
            _audioCSX.EnterWriteLock();
            try
            {
                if (!_isAvailable)
                {
                    Factory.System_Create(out _targetSystem);
                    _targetSystem.getVersion(out var audioDate);
                    AudioDate = $"v{(audioDate >> 16)}.{((audioDate >> 8) & 255).ToString("00")}.{Convert.ToString(audioDate & 255, 16)}";
                    AudioDateHTML = $"https://fmod.com/resources/documentation-api?version={(audioDate >> 16)}.{((audioDate >> 8) & 255)}&page=welcome-revision-history.html";
                    _targetSystem.setSoftwareChannels(Channel);
                    _targetSystem.getSoftwareFormat(out var rate, out _, out _);
                    _rate = (int)(rate / 1000.0);
                    _targetSystem.setDSPBufferSize(Configure.Instance.AudioDataLength, 4);
                    _targetSystem.init(Channel, INITFLAGS.NORMAL, nint.Zero);

                    for (var audioVariety = SEAudio; audioVariety >= MainAudio; --audioVariety)
                    {
                        _targetSystem.createChannelGroup(null, out _audioGroups[audioVariety]);
                        _audioGroups[audioVariety].setVolumeRamp(false);
                    }

                    _targetSystem.setCallback(_onModified, SYSTEM_CALLBACK_TYPE.DEVICELISTCHANGED | SYSTEM_CALLBACK_TYPE.DEVICELOST);
                    _isAvailable = true;
                }
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }

            for (var i = PostableItem.Values.Length - 1; i >= 0; --i)
            {
                var postableItem = PostableItem.Values[i];
                PostableItemAudioMap.GetValueOrDefault(postableItem).Dispose();
                var audioFilePath = Utility.GetFilePath(Path.Combine(QwilightComponent.AssetsEntryPath, "Audio", "Postable Item", postableItem.VarietyValue.ToString()), Utility.FileFormatFlag.Audio);
                if (File.Exists(audioFilePath))
                {
                    PostableItemAudioMap[postableItem] = Load(audioFilePath, null, 1F);
                }
            }
            PostableItemAudio = Load(Utility.GetFilePath(Path.Combine(QwilightComponent.AssetsEntryPath, "Audio", "Postable Item", "Postable"), Utility.FileFormatFlag.Audio), null, 1F);
            PostedItemAudio = Load(Utility.GetFilePath(Path.Combine(QwilightComponent.AssetsEntryPath, "Audio", "Postable Item", "Posted"), Utility.FileFormatFlag.Audio), null, 1F);

            SetAudioVariety(Configure.Instance.AudioVariety);
            SetVolume(MainAudio, (float)Configure.Instance.MainAudioVolume);
            SetVolume(InputAudio, (float)Configure.Instance.InputAudioVolume);
            SetVolume(SEAudio, (float)Configure.Instance.SEAudioVolume);
            SetVolume(TotalAudio, (float)Configure.Instance.TotalAudioVolume);

            if (Configure.Instance.AudioVisualizer)
            {
                SetAudioVisualizer(true);
            }
            if (Configure.Instance.Equalizer)
            {
                SetEqualizer(true);
            }
            if (Configure.Instance.Tube)
            {
                SetTube(true);
            }
            if (Configure.Instance.SFX)
            {
                SetSFX(true);
            }
            if (Configure.Instance.Flange)
            {
                SetFlange(true);
            }
            if (Configure.Instance.Averager)
            {
                SetAverager(true);
            }
        }

        public void SetAudioVariety(OUTPUTTYPE audioValueVariety)
        {
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    _targetSystem.setOutput(audioValueVariety);
                }
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    _onModified(_targetSystem.handle, SYSTEM_CALLBACK_TYPE.DEVICELISTCHANGED, nint.Zero, nint.Zero, nint.Zero);
                }
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
        }

        public void SetVolume(int audioVariety, float audioVolume) => _audioVolumes[audioVariety] = audioVolume;

        public double GetAudioVisualizerValue(int audioVariety, int audioPosition) => _audioVisualizerValue[audioVariety][audioPosition];

        public void SetAudioMultiplierAtone(bool isAudioMultiplierAtoneSet, double audioMultiplier)
        {
            if (isAudioMultiplierAtoneSet)
            {
                _audioCSX.EnterWriteLock();
                try
                {
                    if (_isAvailable)
                    {
                        for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                        {
                            _targetSystem.createDSPByType(DSP_TYPE.PITCHSHIFT, out _audioMultiplierAtoneComputers[audioVariety]);
                            _audioGroups[audioVariety].addDSP(CHANNELCONTROL_DSP_INDEX.HEAD, _audioMultiplierAtoneComputers[audioVariety]);
                            _audioMultiplierAtoneComputers[audioVariety].setParameterFloat((int)DSP_PITCHSHIFT.PITCH, (float)(1 / audioMultiplier));
                        }
                    }
                }
                finally
                {
                    _audioCSX.ExitWriteLock();
                }
            }
            else
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _audioGroups[audioVariety].removeDSP(_audioMultiplierAtoneComputers[audioVariety]);
                            _audioMultiplierAtoneComputers[audioVariety].release();
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
        }

        public int GetHandlingAudioCount()
        {
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    _targetSystem.getChannelsPlaying(out _, out var handlingAudioCount);
                    return handlingAudioCount;
                }
                return default;
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
        }

        public float GetAudioUnitStatus()
        {
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    _targetSystem.getCPUUsage(out var audioUnitStatus);
                    return audioUnitStatus.dsp;
                }
                return default;
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
        }

        public void SetEqualizer(bool isEqualizerSet)
        {
            if (isEqualizerSet)
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _targetSystem.createDSPByType(DSP_TYPE.MULTIBAND_EQ, out _equalizerComputers[audioVariety]);
                            _equalizerComputers[audioVariety].setParameterInt((int)DSP_MULTIBAND_EQ.A_FILTER, (int)DSP_MULTIBAND_EQ_FILTER_TYPE.PEAKING);
                            _equalizerComputers[audioVariety].setParameterInt((int)DSP_MULTIBAND_EQ.B_FILTER, (int)DSP_MULTIBAND_EQ_FILTER_TYPE.PEAKING);
                            _equalizerComputers[audioVariety].setParameterInt((int)DSP_MULTIBAND_EQ.C_FILTER, (int)DSP_MULTIBAND_EQ_FILTER_TYPE.PEAKING);
                            _equalizerComputers[audioVariety].setParameterInt((int)DSP_MULTIBAND_EQ.D_FILTER, (int)DSP_MULTIBAND_EQ_FILTER_TYPE.PEAKING);
                            _equalizerComputers[audioVariety].setParameterInt((int)DSP_MULTIBAND_EQ.E_FILTER, (int)DSP_MULTIBAND_EQ_FILTER_TYPE.PEAKING);
                            _audioGroups[audioVariety].addDSP(CHANNELCONTROL_DSP_INDEX.HEAD, _equalizerComputers[audioVariety]);
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
                SetEqualizerHz(0, (float)Configure.Instance.EqualizerHz0);
                SetEqualizerHz(1, (float)Configure.Instance.EqualizerHz1);
                SetEqualizerHz(2, (float)Configure.Instance.EqualizerHz2);
                SetEqualizerHz(3, (float)Configure.Instance.EqualizerHz3);
                SetEqualizerHz(4, (float)Configure.Instance.EqualizerHz4);
                SetEqualizer(0, (float)Configure.Instance.Equalizer0);
                SetEqualizer(1, (float)Configure.Instance.Equalizer1);
                SetEqualizer(2, (float)Configure.Instance.Equalizer2);
                SetEqualizer(3, (float)Configure.Instance.Equalizer3);
                SetEqualizer(4, (float)Configure.Instance.Equalizer4);
            }
            else
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _audioGroups[audioVariety].removeDSP(_equalizerComputers[audioVariety]);
                            _equalizerComputers[audioVariety].release();
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
        }

        public void SetEqualizerHz(int equalizerHzPosition, float equalizerHzValue)
        {
            for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
            {
                _audioCSX.EnterWriteLock();
                try
                {
                    if (_isAvailable)
                    {
                        switch (equalizerHzPosition)
                        {
                            case 0:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.A_FREQUENCY, equalizerHzValue);
                                break;
                            case 1:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.B_FREQUENCY, equalizerHzValue);
                                break;
                            case 2:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.C_FREQUENCY, equalizerHzValue);
                                break;
                            case 3:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.D_FREQUENCY, equalizerHzValue);
                                break;
                            case 4:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.E_FREQUENCY, equalizerHzValue);
                                break;
                        }
                    }
                }
                finally
                {
                    _audioCSX.ExitWriteLock();
                }
            }
        }

        public void SetEqualizer(int equalizerPosition, float equalizerValue)
        {
            for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
            {
                _audioCSX.EnterWriteLock();
                try
                {
                    if (_isAvailable)
                    {
                        switch (equalizerPosition)
                        {
                            case 0:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.A_GAIN, equalizerValue);
                                break;
                            case 1:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.B_GAIN, equalizerValue);
                                break;
                            case 2:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.C_GAIN, equalizerValue);
                                break;
                            case 3:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.D_GAIN, equalizerValue);
                                break;
                            case 4:
                                _equalizerComputers[audioVariety].setParameterFloat((int)DSP_MULTIBAND_EQ.E_GAIN, equalizerValue);
                                break;
                        }
                    }
                }
                finally
                {
                    _audioCSX.ExitWriteLock();
                }
            }
        }

        public void SetTube(bool isTubeSet)
        {
            if (isTubeSet)
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _targetSystem.createDSPByType(DSP_TYPE.COMPRESSOR, out _tubeComputers[audioVariety]);
                            _audioGroups[audioVariety].addDSP(CHANNELCONTROL_DSP_INDEX.HEAD, _tubeComputers[audioVariety]);
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
            else
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _audioGroups[audioVariety].removeDSP(_tubeComputers[audioVariety]);
                            _tubeComputers[audioVariety].release();
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
        }

        public void SetAudioVisualizer(bool isAudioVisualizerSet)
        {
            if (isAudioVisualizerSet)
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _targetSystem.createDSPByType(DSP_TYPE.FFT, out _audioVisualizerComputers[audioVariety]);
                            _audioGroups[audioVariety].addDSP(CHANNELCONTROL_DSP_INDEX.HEAD, _audioVisualizerComputers[audioVariety]);
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
            else
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _audioGroups[audioVariety].removeDSP(_audioVisualizerComputers[audioVariety]);
                            _audioVisualizerComputers[audioVariety].release();
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
        }

        public void SetSFX(bool isSFX)
        {
            if (isSFX)
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _targetSystem.createDSPByType(DSP_TYPE.SFXREVERB, out _valueSFXComputers[audioVariety]);
                            _audioGroups[audioVariety].addDSP(CHANNELCONTROL_DSP_INDEX.HEAD, _valueSFXComputers[audioVariety]);
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
            else
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _audioGroups[audioVariety].removeDSP(_valueSFXComputers[audioVariety]);
                            _valueSFXComputers[audioVariety].release();
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
        }

        public void SetFlange(bool isFlange)
        {
            if (isFlange)
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _targetSystem.createDSPByType(DSP_TYPE.FLANGE, out _flangeComputers[audioVariety]);
                            _audioGroups[audioVariety].addDSP(CHANNELCONTROL_DSP_INDEX.HEAD, _flangeComputers[audioVariety]);
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
            else
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _audioGroups[audioVariety].removeDSP(_flangeComputers[audioVariety]);
                            _flangeComputers[audioVariety].release();
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
        }

        public void SetAverager(bool isAverager)
        {
            if (isAverager)
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _targetSystem.createDSPByType(DSP_TYPE.NORMALIZE, out _averagerComputers[audioVariety]);
                            _audioGroups[audioVariety].addDSP(CHANNELCONTROL_DSP_INDEX.HEAD, _averagerComputers[audioVariety]);
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
            else
            {
                for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            _audioGroups[audioVariety].removeDSP(_averagerComputers[audioVariety]);
                            _averagerComputers[audioVariety].release();
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
        }

        public AudioItem Load(string audioFilePath, IAudioContainer audioContainer, float audioVolume, string bmsID = null, bool isLooping = false)
        {
            using var rms = PoolSystem.Instance.GetDataFlow(File.ReadAllBytes(audioFilePath));
            return Load(rms, audioContainer, audioVolume, bmsID, isLooping);
        }

        public AudioItem Load(Stream s, IAudioContainer audioContainer, float audioVolume, string bmsID = null, bool isLooping = false)
        {
            var hash = Utility.GetID128(s);
            var audioItem = new AudioItem();
            if (audioContainer != null && _audioMap.TryGetValue(audioContainer, out var audioItems) && audioItems.TryGetValue(hash, out audioItem))
            {
                return audioItem;
            }
            s.Position = 0;
            var audioInfo = new CREATESOUNDEXINFO
            {
                length = (uint)s.Length,
                cbsize = Marshal.SizeOf<CREATESOUNDEXINFO>()
            };
            var ms = s as MemoryStream;
            using var rms = ms == null ? PoolSystem.Instance.GetDataFlow((int)audioInfo.length) : null;
            if (ms == null)
            {
                s.CopyTo(rms);
                ms = rms;
            }
            _audioCSX.EnterReadLock();
            try
            {
                if (_isAvailable)
                {
                    if (_targetSystem.createSound(ms.GetBuffer(), LoadingAudioModes | (isLooping ? MODE.LOOP_NORMAL : MODE.LOOP_OFF), ref audioInfo, out var audioData) == RESULT.OK)
                    {
                        audioItem.BMSID = bmsID;
                        audioItem.System = _targetSystem.handle;
                        audioItem.AudioData = audioData;
                        audioItem.AudioVolume = audioVolume;
                    }
                }
            }
            finally
            {
                _audioCSX.ExitReadLock();
            }
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    audioItem.AudioData.getLength(out var audioLength, TIMEUNIT.MS);
                    audioItem.Length = audioLength;
                }
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
            if (audioContainer != null)
            {
                _audioMap.AddOrUpdate(audioContainer, (audioContainer, audioItem) => new([KeyValuePair.Create(hash, audioItem)]), (audioContainer, audioItems, audioItem) =>
                {
                    audioItems[hash] = audioItem;
                    return audioItems;
                }, audioItem);
            }
            return audioItem;
        }

        public void Stop(IAudioHandler audioHandler)
        {
            if (_audioHandlerMap.TryRemove(audioHandler, out var audioHandlerItems))
            {
                foreach (var audioHandlerItem in audioHandlerItems)
                {
                    Stop(audioHandlerItem.Channel);
                }
            }
        }

        public void Stop(Channel audioChannel)
        {
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    audioChannel.stop();
                }
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
        }

        public double Fade(IAudioHandler audioHandler, double fadeMillis)
        {
            if (_audioHandlerMap.TryRemove(audioHandler, out var audioHandlerItems))
            {
                foreach (var audioHandlerItem in audioHandlerItems)
                {
                    var audioChannel = audioHandlerItem.Channel;
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            audioChannel.getPosition(out var audioPosition, TIMEUNIT.MS);
                            audioChannel.getDSPClock(out _, out var audioStandardUnit);
                            audioChannel.addFadePoint(audioStandardUnit, 1F);
                            audioChannel.addFadePoint(audioStandardUnit + (ulong)(_rate * fadeMillis), 0F);
                            audioChannel.setDelay(0UL, audioStandardUnit + (ulong)(_rate * fadeMillis));
                            return audioPosition + fadeMillis;
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }

            return default;
        }

        public void Close(IAudioContainer audioContainer, IAudioHandler audioHandler = null)
        {
            if (_audioMap.TryRemove(audioContainer, out var audioItems))
            {
                foreach (var audioItem in audioItems.Values)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            audioItem.Dispose();
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
            if (audioHandler != null)
            {
                _audioHandlerMap.TryRemove(audioHandler, out var audioHandlerItems);
            }
        }

        public void Migrate(IAudioContainer src, IAudioContainer target)
        {
            if (_audioMap.TryRemove(src, out var audioItems))
            {
                _audioMap[target] = audioItems;
            }
        }

        public void Migrate(IAudioHandler src, IAudioHandler target)
        {
            if (_audioHandlerMap.TryRemove(src, out var audioItems))
            {
                _audioHandlerMap[target] = audioItems;
            }
        }

        public void Pause(IAudioHandler audioHandler, bool isPaused)
        {
            if (_audioHandlerMap.TryGetValue(audioHandler, out var audioHandlerItems))
            {
                if (isPaused)
                {
                    foreach (var audioHandlerItem in audioHandlerItems)
                    {
                        var audioChannel = audioHandlerItem.Channel;
                        _audioCSX.EnterWriteLock();
                        try
                        {
                            if (_isAvailable)
                            {
                                audioChannel.isPlaying(out var isHandling);
                                if (isHandling)
                                {
                                    audioChannel.getPosition(out var audioPosition, TIMEUNIT.MS);
                                    audioChannel.setPaused(true);
                                    if (audioHandlerItem.LevyingPosition.HasValue)
                                    {
                                        audioHandlerItem.Position = audioPosition - audioHandlerItem.LevyingPosition.Value;
                                    }
                                }
                            }
                        }
                        finally
                        {
                            _audioCSX.ExitWriteLock();
                        }
                    }
                }
                else
                {
                    foreach (var audioHandlerItem in audioHandlerItems)
                    {
                        var audioChannel = audioHandlerItem.Channel;
                        _audioCSX.EnterWriteLock();
                        try
                        {
                            if (_isAvailable)
                            {
                                if (audioHandlerItem.Length.HasValue)
                                {
                                    audioChannel.setDelay(0UL, audioHandlerItem.AudioStandardUnit + (ulong)(_rate * (audioHandlerItem.Length.Value - audioHandlerItem.Position)));
                                }
                                audioChannel.setPaused(false);
                            }
                        }
                        finally
                        {
                            _audioCSX.ExitWriteLock();
                        }
                    }
                }
            }
        }

        public void SetAudioMultiplier(IAudioHandler audioHandler, double audioMultiplier)
        {
            if (_audioHandlerMap.TryGetValue(audioHandler, out var audioHandlerItems))
            {
                foreach (var audioHandlerItem in audioHandlerItems)
                {
                    _audioCSX.EnterWriteLock();
                    try
                    {
                        if (_isAvailable)
                        {
                            audioHandlerItem.Channel.setPitch((float)audioMultiplier);
                        }
                    }
                    finally
                    {
                        _audioCSX.ExitWriteLock();
                    }
                }
            }
            for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
            {
                _audioCSX.EnterWriteLock();
                try
                {
                    if (_isAvailable)
                    {
                        _audioMultiplierAtoneComputers[audioVariety].setParameterFloat((int)DSP_PITCHSHIFT.PITCH, (float)(1 / audioMultiplier));
                    }
                }
                finally
                {
                    _audioCSX.ExitWriteLock();
                }
            }
        }

        public Channel Handle(AudioNote? audioNote, int audioVariety, double audioMultiplier = 1.0, bool isCounterWave = false, IAudioHandler audioHandler = null, double fadeInLength = 0.0, byte inputPower = byte.MaxValue)
        {
            if (audioNote.HasValue)
            {
                var audioNoteValue = audioNote.Value;
                var audioItem = audioNoteValue.AudioItem;
                if (audioItem.HasValue)
                {
                    var audioItemValue = audioItem.Value;
                    var audioLevyingPosition = audioNoteValue.AudioLevyingPosition;
                    if (audioItemValue.Length > audioLevyingPosition)
                    {
                        _audioCSX.EnterWriteLock();
                        try
                        {
                            if (_isAvailable)
                            {
                                if (audioItemValue.System == _targetSystem.handle)
                                {
                                    _targetSystem.playSound(audioItemValue.AudioData, _audioGroups[audioVariety], false, out var audioChannel);
                                    audioChannel.getDSPClock(out _, out var audioStandardUnit);
                                    var targetVolume = audioItemValue.AudioVolume * inputPower / byte.MaxValue;
                                    if (fadeInLength > 0.0)
                                    {
                                        audioChannel.addFadePoint(audioStandardUnit, 0F);
                                        audioChannel.addFadePoint(audioStandardUnit + (ulong)(_rate * fadeInLength), targetVolume);
                                    }
                                    else
                                    {
                                        audioChannel.setVolume(targetVolume);
                                    }
                                    if (audioMultiplier != 1.0)
                                    {
                                        audioChannel.setPitch((float)audioMultiplier);
                                    }
                                    if (isCounterWave)
                                    {
                                        audioChannel.setPosition(audioItemValue.Length - audioLevyingPosition - 1, TIMEUNIT.MS);
                                        audioChannel.getFrequency(out var audioWave);
                                        audioChannel.setFrequency((float)-audioWave);
                                    }
                                    else
                                    {
                                        audioChannel.setPosition(audioLevyingPosition, TIMEUNIT.MS);
                                    }
                                    var audioLength = audioNoteValue.Length;
                                    if (audioLength.HasValue)
                                    {
                                        audioChannel.getDSPClock(out _, out audioStandardUnit);
                                        audioChannel.setDelay(0UL, audioStandardUnit + (ulong)(_rate * audioLength.Value));
                                    }
                                    if (audioHandler != null)
                                    {
                                        var audioHandlerItem = new AudioHandlerItem
                                        {
                                            Channel = audioChannel,
                                            LevyingPosition = audioLevyingPosition,
                                            Length = audioLength,
                                            AudioStandardUnit = audioStandardUnit
                                        };
                                        _audioHandlerMap.AddOrUpdate(audioHandler, (audioHandler, audioHandlerItem) => new()
                                        {
                                            audioHandlerItem
                                        }, (audioHandler, audioHandlerItems, audioHandlerItem) =>
                                        {
                                            audioHandlerItems.Add(audioHandlerItem);
                                            return audioHandlerItems;
                                        }, audioHandlerItem);
                                    }
                                    return audioChannel;
                                }
                            }
                        }
                        finally
                        {
                            _audioCSX.ExitWriteLock();
                        }
                    }
                }
            }
            return default;
        }

        public void HandleImmediately(string audioFilePath, IAudioContainer audioContainer, IAudioHandler audioHandler, bool isLooping)
        {
            var rms = PoolSystem.Instance.GetDataFlow(File.ReadAllBytes(audioFilePath));
            var hash = Utility.GetID128(rms);
            rms.Position = 0;
            var audioInfo = new CREATESOUNDEXINFO
            {
                length = (uint)rms.Length,
                cbsize = Marshal.SizeOf<CREATESOUNDEXINFO>()
            };
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    if (_targetSystem.createSound(rms.GetBuffer(), LoadingAudioModes | (isLooping ? MODE.LOOP_NORMAL : MODE.LOOP_OFF), ref audioInfo, out var audioData) == RESULT.OK && audioData.getLength(out var audioLength, TIMEUNIT.MS) == RESULT.OK)
                    {
                        _audioMap.AddOrUpdate(audioContainer, (audioContainer, audioItem) => new([KeyValuePair.Create(hash, audioItem)]), (audioContainer, audioItems, audioItem) =>
                        {
                            audioItems[hash] = audioItem;
                            return audioItems;
                        }, new AudioItem
                        {
                            System = _targetSystem.handle,
                            AudioData = audioData,
                            AudioVolume = 1F,
                            Length = audioLength
                        });
                        _targetSystem.playSound(audioData, _audioGroups[MainAudio], false, out var audioChannel);
                        audioChannel.getDSPClock(out _, out var audioStandardUnit);
                        var audioHandlerItem = new AudioHandlerItem
                        {
                            Channel = audioChannel,
                            AudioStandardUnit = audioStandardUnit
                        };
                        _audioHandlerMap.AddOrUpdate(audioHandler, (audioHandler, audioHandlerItem) => new()
                        {
                            audioHandlerItem
                        }, (audioHandler, audioHandlerItems, audioHandlerItem) =>
                        {
                            audioHandlerItems.Add(audioHandlerItem);
                            return audioHandlerItems;
                        }, audioHandlerItem);
                    }
                }
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
        }

        public void Dispose()
        {
            _audioCSX.EnterWriteLock();
            try
            {
                if (_isAvailable)
                {
                    _isAvailable = false;
                    foreach (var audioVisualizerComputer in _audioVisualizerComputers)
                    {
                        audioVisualizerComputer.release();
                    }
                    for (var audioVariety = InputAudio; audioVariety >= MainAudio; --audioVariety)
                    {
                        _audioGroups[audioVariety].removeDSP(_equalizerComputers[audioVariety]);
                        _equalizerComputers[audioVariety].release();
                        _audioGroups[audioVariety].removeDSP(_tubeComputers[audioVariety]);
                        _tubeComputers[audioVariety].release();
                        _audioGroups[audioVariety].removeDSP(_audioMultiplierAtoneComputers[audioVariety]);
                        _audioMultiplierAtoneComputers[audioVariety].release();
                        _audioGroups[audioVariety].removeDSP(_valueSFXComputers[audioVariety]);
                        _valueSFXComputers[audioVariety].release();
                        _audioGroups[audioVariety].removeDSP(_flangeComputers[audioVariety]);
                        _flangeComputers[audioVariety].release();
                        _audioGroups[audioVariety].removeDSP(_averagerComputers[audioVariety]);
                        _averagerComputers[audioVariety].release();
                    }
                    foreach (var audioContainer in _audioMap.Keys)
                    {
                        if (_audioMap.TryRemove(audioContainer, out var audioItems))
                        {
                            foreach (var audioItem in audioItems.Values)
                            {
                                audioItem.Dispose();
                            }
                        }
                    }
                    _audioHandlerMap.Clear();
                    foreach (var audioGroup in _audioGroups)
                    {
                        audioGroup.release();
                    }
                    _targetSystem.release();
                    _targetSystem.close();
                }
            }
            finally
            {
                _audioCSX.ExitWriteLock();
            }
        }
    }
}