Skip to content

Commit

Permalink
Use NAudio.Wasapi
Browse files Browse the repository at this point in the history
  • Loading branch information
Felscream committed Aug 26, 2024
1 parent 4149a4a commit e31f956
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 246 deletions.
188 changes: 64 additions & 124 deletions DragoonMayCry/Audio/AudioEngine.cs
Original file line number Diff line number Diff line change
@@ -1,150 +1,90 @@
using DragoonMayCry.Score.Style;
using DragoonMayCry.Util;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;

namespace DragoonMayCry.Audio
{

// Stolen from: https://markheath.net/post/fire-and-forget-audio-playback-with
class CachedSound
{
internal float[] AudioData { get; private set; }
internal WaveFormat WaveFormat { get; private set; }
internal CachedSound(string audioFileName)
{
using (var audioFileReader = new AudioFileReader(audioFileName))
{
WaveFormat = audioFileReader.WaveFormat;
var wholeFile = new List<float>((int)(audioFileReader.Length / 4));
var readBuffer = new float[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels];
int samplesRead;
while ((samplesRead = audioFileReader.Read(readBuffer, 0, readBuffer.Length)) > 0)
{
wholeFile.AddRange(readBuffer.Take(samplesRead));
}
AudioData = wholeFile.ToArray();
}
}
}

class CachedSoundSampleProvider : ISampleProvider
{
private readonly CachedSound cachedSound;
private long position;

public CachedSoundSampleProvider(CachedSound cachedSound)
{
this.cachedSound = cachedSound;
}

public int Read(float[] buffer, int offset, int count)
{
var availableSamples = cachedSound.AudioData.Length - position;
var samplesToCopy = Math.Min(availableSamples, count);
Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy);
position += samplesToCopy;
return (int)samplesToCopy;
}

public WaveFormat WaveFormat { get { return cachedSound.WaveFormat; } }
}

public class AudioEngine
{
private readonly IWavePlayer sfxOutputDevice;
private readonly VolumeSampleProvider sfxSampleProvider;
private readonly MixingSampleProvider sfxMixer;
private Dictionary<StyleType, CachedSound> sounds;

public AudioEngine()
{
sfxOutputDevice = new WaveOutEvent();

sounds = new();

sfxMixer = new(WaveFormat.CreateIeeeFloatWaveFormat(44100, 2)) {
ReadFully = true
};

sfxSampleProvider = new(sfxMixer);

sfxOutputDevice.Init(sfxSampleProvider);
sfxOutputDevice.Play();
}

public void Init(DoubleLinkedList<StyleRank> styles)
{
DoubleLinkedNode<StyleRank> current = styles.Head!;

while (current != null)
{
if (string.IsNullOrEmpty(current.Value.SfxPath))
{
current = current.Next!;
continue;
}

StyleRank rank = current.Value;
Service.Log.Debug($"Registering sound for {rank.StyleType}, {rank.SfxPath}");
sounds.Add(rank.StyleType, new CachedSound(rank.SfxPath));
current = current.Next!;
}
}
// TODO remove later
public void AddSfx(StyleType type, string path)
{
if (sounds.ContainsKey(type))
{
return;
}
Service.Log.Debug($"Registering sound for {type}, {path}");
sounds.Add(type, new CachedSound(path));
}
private static readonly IDictionary<StyleType, byte> SoundState = new ConcurrentDictionary<StyleType, byte>();

public void PlaySfx(StyleType trigger)
// Copied from PeepingTom plugin, by ascclemens:
// https://git.anna.lgbt/anna/PeepingTom/src/commit/b1de54bcae64edf97c9f90614a588e64b5d0ae34/Peeping%20Tom/TargetWatcher.cs#L161
public static void PlaySfx(StyleType trigger, string path)
{
if (!sounds.ContainsKey(trigger))
if (string.IsNullOrEmpty(path) || !File.Exists(path))
{
Service.Log.Warning($"Audio trigger {trigger} has no audio associated");
Service.Log.Error($"Could not find audio file: [{path}]");
return;
}

sfxSampleProvider.Volume = (Plugin.Configuration!.SfxVolume / 100f) * GetGameSfxVolume() ;
Service.Log.Debug($"Playing audio for trigger {trigger}");
AddSFXMixerInput(new CachedSoundSampleProvider(sounds[trigger]));
}

private ISampleProvider ConvertToRightChannelCount(ISampleProvider input)
{
if (input.WaveFormat.Channels == sfxMixer.WaveFormat.Channels)
{
return input;
}
if (input.WaveFormat.Channels == 1 && sfxMixer.WaveFormat.Channels == 2)
var soundDevice = DirectSoundOut.DSDEVID_DefaultPlayback;
new Thread(() =>
{
return new MonoToStereoSampleProvider(input);
}
throw new NotImplementedException("Not yet implemented this channel count conversion");
}
WaveStream reader;
try
{
reader = new MediaFoundationReader(path);
}
catch (Exception e)
{
Service.Log.Error(e.Message);
return;
}
using var channel = new WaveChannel32(reader)
{
Volume = GetSfxVolume(),
PadWithZeroes = false,
};
private void AddSFXMixerInput(ISampleProvider input)
{
ISampleProvider mixerInput = ConvertToRightChannelCount(input);
sfxMixer.AddMixerInput(mixerInput);
using (reader)
{
using var output = new DirectSoundOut(soundDevice);
try
{
output.Init(channel);
output.Play();
SoundState[trigger] = 1;
while (output.PlaybackState == PlaybackState.Playing)
{
if (!SoundState.ContainsKey(trigger))
{
output.Stop();
}
Thread.Sleep(500);
}
SoundState.Remove(trigger);
}
catch (Exception ex)
{
Service.Log.Error(ex, "Exception playing sound");
}
}
}).Start();
}

private float GetGameSfxVolume()
private static float GetSfxVolume()
{
if (Service.GameConfig.System.GetBool("IsSndSe") ||
Service.GameConfig.System.GetBool("IsSndMaster"))
if (Plugin.Configuration!.ApplyGameVolume && (Service.GameConfig.System.GetBool("IsSndSe") ||
Service.GameConfig.System.GetBool("IsSndMaster")))
{
return 0;
}
return Service.GameConfig.System.GetUInt("SoundSe") / 100f * (Service.GameConfig.System.GetUInt("SoundMaster") / 100f);

var gameVolume = Plugin.Configuration!.ApplyGameVolume
? Service.GameConfig.System
.GetUInt("SoundSe") / 100f *
(Service.GameConfig.System.GetUInt(
"SoundMaster") / 100f)
: 1;
return gameVolume * (Plugin.Configuration!.SfxVolume / 100f);
}
}
}
1 change: 1 addition & 0 deletions DragoonMayCry/Configuration/DmcConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class DmcConfiguration : IPluginConfiguration
public bool Enabled = true;
public int SfxVolume { get; set; } = 80;
public bool PlaySoundEffects { get; set; } = true;
public bool ApplyGameVolume { get; set; } = true;
public bool ActiveOutsideInstance { get; set; } = false;
public int TimeBeforeDemotion { get; set; } = 3000; //milliseconds
public float GcdDropThreshold { get; set; } = 0.1f;
Expand Down
2 changes: 1 addition & 1 deletion DragoonMayCry/DragoonMayCry.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="NAudio.Wasapi" Version="2.1.0" />
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>false</Private>
Expand Down
16 changes: 2 additions & 14 deletions DragoonMayCry/Plugin.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
using Dalamud.Game.Command;
using Dalamud.Game.Gui.FlyText;
using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using DragoonMayCry.Configuration;
using DragoonMayCry.Score;
using DragoonMayCry.State;
using DragoonMayCry.UI;
using FFXIVClientStructs.FFXIV.Client.UI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using DragoonMayCry.Util;
using Action = Lumina.Excel.GeneratedSheets.Action;
using DragoonMayCry.Score.Action;
using DragoonMayCry.Score.Style;
using DragoonMayCry.State;
using DragoonMayCry.UI;

namespace DragoonMayCry;

Expand Down
9 changes: 1 addition & 8 deletions DragoonMayCry/Score/CombatStopwatch.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Plugin.Services;
using DragoonMayCry.State;
using FFXIVClientStructs;
using ImGuiNET;
using System.Diagnostics;

namespace DragoonMayCry.Score
{
Expand Down
8 changes: 2 additions & 6 deletions DragoonMayCry/Score/Style/StyleRankHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,11 @@ public RankChangeData(
public DoubleLinkedNode<StyleRank>? CurrentRank { get; private set; }
public DoubleLinkedNode<StyleRank>? PreviousRank { get; private set; }
private DoubleLinkedList<StyleRank> styles;
private readonly AudioEngine audioEngine;

public StyleRankHandler(ActionTracker actionTracker)
{
styles = DEFAULT_STYLE_RANK;
Reset();
audioEngine = new AudioEngine();
audioEngine.Init(styles!);
audioEngine.AddSfx(StyleType.DEAD_WEIGHT, GetPathToAudio("dead_weight"));

var playerState = PlayerState.GetInstance();
playerState.RegisterJobChangeHandler(OnJobChange!);
Expand All @@ -71,7 +67,7 @@ public void GoToNextRank(bool playSfx, bool loop)
StyleRankChange?.Invoke(this, new(CurrentRank!.Previous!.Value, CurrentRank.Value, false));
if (Plugin.Configuration!.PlaySoundEffects)
{
audioEngine.PlaySfx(CurrentRank.Value.StyleType);
AudioEngine.PlaySfx(CurrentRank.Value.StyleType, CurrentRank.Value.SfxPath);
}
}
}
Expand Down Expand Up @@ -145,7 +141,7 @@ private void OnGcdDropped(object? sender, EventArgs args)
ReturnToPreviousRank(true);
if (Plugin.Configuration!.PlaySoundEffects)
{
audioEngine.PlaySfx(StyleType.DEAD_WEIGHT);
AudioEngine.PlaySfx(StyleType.DEAD_WEIGHT, GetPathToAudio("dead_weight"));
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions DragoonMayCry/UI/ConfigWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ public override void Draw()
configuration.Save();
}

var applyGameVolume = configuration.ApplyGameVolume;
if (ImGui.Checkbox("Apply game volume", ref applyGameVolume))
{
configuration.ApplyGameVolume = applyGameVolume;
configuration.Save();
}

var soundEffectVolume = configuration.SfxVolume;
if (ImGui.SliderInt("Sound effect volume", ref soundEffectVolume, 0,
100))
Expand Down
18 changes: 7 additions & 11 deletions DragoonMayCry/UI/StyleRankUI.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
using System;
using System.Diagnostics;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Textures.TextureWraps;
using ImGuiNET;
using System.Reflection;
using Dalamud.Interface.Animation;
using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Textures.TextureWraps;
using DragoonMayCry.Score;
using DragoonMayCry.Score.Style;
using DragoonMayCry.State;
using DragoonMayCry.Util;
using static DragoonMayCry.Score.ScoreManager;
using ImGuiNET;
using System;
using System.Diagnostics;
using System.Numerics;
using System.Reflection;
using Vector2 = System.Numerics.Vector2;
using Vector4 = FFXIVClientStructs.FFXIV.Common.Math.Vector4;
using System.Drawing;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using DragoonMayCry.Score.Style;

namespace DragoonMayCry.UI
{
Expand Down
Loading

0 comments on commit e31f956

Please sign in to comment.