From 9b1cec771244ea2150bd120df0d0ba9227ce4446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Sun, 22 Aug 2021 20:59:13 +0200 Subject: [PATCH] Add support for gapless looping music. --- OpenRA.Game/Sound/Sound.cs | 23 +++++++++++++------ OpenRA.Game/Sound/SoundDevice.cs | 1 + .../Traits/World/MusicPlaylist.cs | 14 ++++++----- .../Widgets/Logic/MusicPlayerLogic.cs | 2 +- OpenRA.Platforms.Default/DummySoundEngine.cs | 1 + OpenRA.Platforms.Default/OpenAlSoundEngine.cs | 20 ++++++++++++---- 6 files changed, 43 insertions(+), 18 deletions(-) diff --git a/OpenRA.Game/Sound/Sound.cs b/OpenRA.Game/Sound/Sound.cs index a8674448d764..7a61235b1d64 100644 --- a/OpenRA.Game/Sound/Sound.cs +++ b/OpenRA.Game/Sound/Sound.cs @@ -137,6 +137,12 @@ public void UnmuteAudio() soundEngine.Volume = 1f; } + public void SetMusicLooped(bool loop) + { + Game.Settings.Sound.Repeat = loop; + soundEngine.SetSoundLooping(loop, music); + } + public bool DisableAllSounds { get; set; } public bool DisableWorldSounds { get; set; } public ISound Play(SoundType type, string name) { return Play(type, null, name, true, WPos.Zero, 1f); } @@ -202,11 +208,6 @@ public void Tick() public bool MusicPlaying { get; private set; } public MusicInfo CurrentMusic => currentMusic; - public void PlayMusic(MusicInfo m) - { - PlayMusicThen(m, () => { }); - } - public void PlayMusicThen(MusicInfo m, Action then) { if (m == null || !m.Exists) @@ -221,13 +222,21 @@ public void PlayMusicThen(MusicInfo m, Action then) return; } + PlayMusic(m, Game.Settings.Sound.Repeat); + } + + public void PlayMusic(MusicInfo m, bool looped = false) + { + if (m == null || !m.Exists) + return; + StopMusic(); Func stream = soundFormat => soundEngine.Play2DStream( soundFormat.GetPCMInputStream(), soundFormat.Channels, soundFormat.SampleBits, soundFormat.SampleRate, - false, true, WPos.Zero, MusicVolume * m.VolumeModifier); - music = LoadSound(m.Filename, stream); + looped, true, WPos.Zero, MusicVolume * m.VolumeModifier); + music = LoadSound(m.Filename, stream); if (music == null) { onMusicComplete = null; diff --git a/OpenRA.Game/Sound/SoundDevice.cs b/OpenRA.Game/Sound/SoundDevice.cs index b8cbd9bc89c1..36640a3d05c1 100644 --- a/OpenRA.Game/Sound/SoundDevice.cs +++ b/OpenRA.Game/Sound/SoundDevice.cs @@ -28,6 +28,7 @@ public interface ISoundEngine : IDisposable void StopAllSounds(); void SetListenerPosition(WPos position); void SetSoundVolume(float volume, ISound music, ISound video); + void SetSoundLooping(bool looping, ISound sound); } public class SoundDevice diff --git a/OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs b/OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs index 5497b8923ce5..9e2f3d122dc8 100644 --- a/OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs +++ b/OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs @@ -162,13 +162,15 @@ void Play() if (!SongExists(currentSong) || (CurrentSongIsBackground && IsBackgroundMusicMuted)) return; - Game.Sound.PlayMusicThen(currentSong, () => - { - if (!CurrentSongIsBackground && !Game.Settings.Sound.Repeat) - currentSong = GetNextSong(); + Game.Sound.PlayMusicThen(currentSong, PlayNextSong); + } - Play(); - }); + void PlayNextSong() + { + if (!CurrentSongIsBackground) + currentSong = GetNextSong(); + + Play(); } public void Play(MusicInfo music) diff --git a/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs index aaf53fc75da8..e21c5e21881e 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MusicPlayerLogic.cs @@ -78,7 +78,7 @@ public MusicPlayerLogic(Widget widget, ModData modData, World world, Action onEx var repeatCheckbox = panel.Get("REPEAT"); repeatCheckbox.IsChecked = () => Game.Settings.Sound.Repeat; - repeatCheckbox.OnClick = () => Game.Settings.Sound.Repeat ^= true; + repeatCheckbox.OnClick = () => Game.Sound.SetMusicLooped(!Game.Settings.Sound.Repeat); repeatCheckbox.IsDisabled = () => musicPlaylist.CurrentSongIsBackground; panel.Get("TIME_LABEL").GetText = () => diff --git a/OpenRA.Platforms.Default/DummySoundEngine.cs b/OpenRA.Platforms.Default/DummySoundEngine.cs index 8557f17e282a..94206a3eca19 100644 --- a/OpenRA.Platforms.Default/DummySoundEngine.cs +++ b/OpenRA.Platforms.Default/DummySoundEngine.cs @@ -56,6 +56,7 @@ public float Volume public void StopSound(ISound sound) { } public void StopAllSounds() { } public void SetListenerPosition(WPos position) { } + public void SetSoundLooping(bool looping, ISound sound) { } public void Dispose() { } } diff --git a/OpenRA.Platforms.Default/OpenAlSoundEngine.cs b/OpenRA.Platforms.Default/OpenAlSoundEngine.cs index f6ef5f6229a4..fb346a009322 100644 --- a/OpenRA.Platforms.Default/OpenAlSoundEngine.cs +++ b/OpenRA.Platforms.Default/OpenAlSoundEngine.cs @@ -66,7 +66,7 @@ static string[] QueryDevices(string label, int type) if (devicesPtr == IntPtr.Zero || AL10.alGetError() != AL10.AL_NO_ERROR) { Log.Write("sound", "Failed to query OpenAL device list using {0}", label); - return new string[0]; + return Array.Empty(); } var devices = new List(); @@ -103,7 +103,7 @@ static string[] PhysicalDevices() if (ALC11.alcIsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATION_EXT")) return QueryDevices("ALC_ENUMERATION_EXT", ALC10.ALC_DEVICE_SPECIFIER); - return new string[] { }; + return Array.Empty(); } internal static int MakeALFormat(int channels, int bits) @@ -137,8 +137,7 @@ public OpenAlSoundEngine(string deviceName) for (var i = 0; i < PoolSize; i++) { - var source = 0U; - AL10.alGenSources(1, out source); + AL10.alGenSources(1, out var source); if (AL10.alGetError() != AL10.AL_NO_ERROR) { Log.Write("sound", "Failed generating OpenAL source {0}", i); @@ -346,6 +345,11 @@ public void SetListenerPosition(WPos position) AL10.alListenerf(EFX.AL_METERS_PER_UNIT, .01f); } + public void SetSoundLooping(bool looping, ISound sound) + { + ((OpenAlSound)sound)?.SetLooping(looping); + } + ~OpenAlSoundEngine() { Dispose(false); @@ -518,6 +522,14 @@ public virtual void Stop() StopSource(); AL10.alSourcei(Source, AL10.AL_BUFFER, 0); } + + public void SetLooping(bool looping) + { + if (done) + return; + + AL10.alSourcei(Source, AL10.AL_LOOPING, looping ? AL10.AL_TRUE : AL10.AL_FALSE); + } } class OpenAlAsyncLoadSound : OpenAlSound