From a4b1c3eb865cc97c27807b6fb7b1bf67fa803991 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Mon, 5 May 2014 02:09:37 +0200 Subject: [PATCH] Unify playback libs with common base class Remove duplicate code Fix bugs in implementations (threading, logic) Remove SetStreamVolumeMax and use SetGlobalVolume instead --- GstreamerHelper/GstreamerAudio.cpp | 27 +- GstreamerHelper/GstreamerAudio.h | 2 +- .../CPartyScreenTicTacToeMain.cs | 12 +- Vocaluxe/Base/CBackgroundMusic.cs | 12 +- Vocaluxe/Base/CConfig.cs | 35 +- Vocaluxe/Base/CGraphics.cs | 14 +- Vocaluxe/Base/CMain.cs | 7 +- Vocaluxe/Base/CSound.cs | 20 +- .../Lib/Sound/Playback/CAudioStreamBase.cs | 120 +++++ Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs | 308 ++++++++++++ Vocaluxe/Lib/Sound/Playback/CRingBuffer.cs | 19 +- .../Playback/Decoder/CAudioDecoderFFmpeg.cs | 16 +- .../Sound/Playback/Decoder/IAudioDecoder.cs | 3 +- .../Playback/Gstreamer/CGstreamerAudio.cs | 68 ++- .../Gstreamer/CGstreamerAudioWrapper.cs | 10 +- .../GstreamerSharp/CGstreamerSharpAudio.cs | 178 +------ .../CGstreamerSharpAudioStream.cs | 365 +++++++------- Vocaluxe/Lib/Sound/Playback/IAudioStream.cs | 48 ++ Vocaluxe/Lib/Sound/Playback/IPlayback.cs | 61 ++- .../Lib/Sound/Playback/OpenAL/COpenALPlay.cs | 333 +------------ .../Sound/Playback/OpenAL/COpenAlStream.cs | 446 +++++++----------- .../Playback/PortAudio/CPortAudioPlay.cs | 316 +------------ .../Playback/PortAudio/CPortAudioStream.cs | 364 +++++--------- .../Sound/Playback/PortAudio/CSyncTimer.cs | 3 +- .../Record/DirectSound/CDirectSoundRecord.cs | 2 - Vocaluxe/Screens/CPopupScreenVolumeControl.cs | 24 +- Vocaluxe/Screens/CScreenOptionsSound.cs | 2 +- Vocaluxe/Screens/CScreenSing.cs | 31 +- Vocaluxe/Screens/CScreenSong.cs | 36 +- Vocaluxe/Vocaluxe.csproj | 3 + VocaluxeLib/IBasic.cs | 3 +- VocaluxeLib/Menu/SongMenu/CSongMenu.cs | 5 - .../Menu/SongMenu/CSongMenuFramework.cs | 15 +- VocaluxeLib/Menu/SongMenu/ISongMenu.cs | 2 - 34 files changed, 1160 insertions(+), 1750 deletions(-) create mode 100644 Vocaluxe/Lib/Sound/Playback/CAudioStreamBase.cs create mode 100644 Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs create mode 100644 Vocaluxe/Lib/Sound/Playback/IAudioStream.cs diff --git a/GstreamerHelper/GstreamerAudio.cpp b/GstreamerHelper/GstreamerAudio.cpp index 1e5f6baf..79b0d4f5 100644 --- a/GstreamerHelper/GstreamerAudio.cpp +++ b/GstreamerHelper/GstreamerAudio.cpp @@ -5,6 +5,7 @@ map Streams; queue IDs; +float GlobalVolume = 1.f; EXTERN_C IMAGE_DOS_HEADER __ImageBase; DllExport void SetLogCallback (LogCallback Callback) { @@ -41,17 +42,20 @@ DllExport void SetGlobalVolume(float Volume) map::iterator p; for(p = Streams.begin(); p != Streams.end(); p++) { - p->second->SetStreamVolume(Volume); + p->second->SetStreamVolumeMax(Volume); } + GlobalVolume = Volume; } +DllExport float GetGlobalVolume(){ + return GlobalVolume; +} DllExport int GetStreamCount(void) { return Streams.size(); } - DllExport void CloseAll(void) { map::iterator p; @@ -66,7 +70,10 @@ DllExport int Load(const wchar_t* Media) GstreamerAudioStream *s = new GstreamerAudioStream(IDs.front()); Streams.insert(pair (IDs.front(), s)); IDs.pop(); - return s->Load(Media); + int result = s->Load(Media); + s->SetStreamVolume(1.f); + s->SetStreamVolumeMax(GlobalVolume); + return result; } DllExport int LoadPrescan(const wchar_t* Media, bool Prescan) @@ -74,7 +81,10 @@ DllExport int LoadPrescan(const wchar_t* Media, bool Prescan) GstreamerAudioStream *s = new GstreamerAudioStream(IDs.front()); Streams.insert(pair (IDs.front(), s)); IDs.pop(); - return s->Load(Media, Prescan); + int result = s->Load(Media, Prescan); + s->SetStreamVolume(1.f); + s->SetStreamVolumeMax(GlobalVolume); + return result; } DllExport void Close(int Stream) @@ -160,15 +170,6 @@ DllExport void SetStreamVolume(int Stream, float Volume) } } -DllExport void SetStreamVolumeMax(int Stream, float Volume) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - return it->second->SetStreamVolumeMax(Volume); - } -} - DllExport float GetLength(int Stream) { map::iterator it = Streams.find(Stream); diff --git a/GstreamerHelper/GstreamerAudio.h b/GstreamerHelper/GstreamerAudio.h index a9ea49b0..75f2d6f5 100644 --- a/GstreamerHelper/GstreamerAudio.h +++ b/GstreamerHelper/GstreamerAudio.h @@ -17,6 +17,7 @@ using namespace std; DllExport bool Init(); DllExport void SetGlobalVolume(float Volume); + DllExport float GetGlobalVolume(); DllExport int GetStreamCount(void); DllExport void CloseAll(void); @@ -33,7 +34,6 @@ using namespace std; DllExport void FadeAndPause(int Stream, float TargetVolume, float Seconds); DllExport void FadeAndStop(int Stream, float TargetVolume, float Seconds); DllExport void SetStreamVolume(int Stream, float Volume); - DllExport void SetStreamVolumeMax(int Stream, float Volume); DllExport float GetLength(int Stream); DllExport float GetPosition(int Stream); diff --git a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs index 6cfad473..6b5ccaad 100644 --- a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs +++ b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs @@ -379,7 +379,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe { base.OnClose(); CBase.BackgroundMusic.SetStatus(false); - CBase.Sound.FadeAndStop(_PreviewStream, 0f, 0.5f); + CBase.Sound.FadeAndClose(_PreviewStream, 0f, 0.5f); } private void _CreateFields() @@ -574,7 +574,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe { _GameData.NumJokerRandom[teamNr]--; if (!CBase.Sound.IsFinished(_PreviewStream)) - CBase.Sound.FadeAndStop(_PreviewStream, 0, 1); + CBase.Sound.FadeAndClose(_PreviewStream, 0, 1); _FieldSelected(true); } break; @@ -586,7 +586,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe _GameData.NumJokerRetry[teamNr]--; _GameData.Team = teamNr; if (!CBase.Sound.IsFinished(_PreviewStream)) - CBase.Sound.FadeAndStop(_PreviewStream, 0, 1); + CBase.Sound.FadeAndClose(_PreviewStream, 0, 1); _Status = EStatus.JokerRetry; _OldSelectedField = _SelectedField; _SelectedField = -1; @@ -627,7 +627,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe private void _NextRound() { - CBase.Sound.FadeAndStop(_PreviewStream, 0f, 0.5f); + CBase.Sound.FadeAndClose(_PreviewStream, 0f, 0.5f); _Data.ScreenMain.Rounds = _GameData.Rounds; _Data.ScreenMain.FadeToNameSelection = false; _Data.ScreenMain.FadeToSinging = true; @@ -640,7 +640,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe private void _EndParty() { - CBase.Sound.FadeAndStop(_PreviewStream, 0f, 0.5f); + CBase.Sound.FadeAndClose(_PreviewStream, 0f, 0.5f); _FadeTo(EScreens.ScreenParty); } @@ -659,7 +659,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe private void _Back() { - CBase.Sound.FadeAndStop(_PreviewStream, 0f, 0.5f); + CBase.Sound.FadeAndClose(_PreviewStream, 0f, 0.5f); _Data.ScreenMain.FadeToNameSelection = true; _Data.ScreenMain.FadeToSinging = false; _Data.ScreenMain.SingRoundNr = _SelectedField; diff --git a/Vocaluxe/Base/CBackgroundMusic.cs b/Vocaluxe/Base/CBackgroundMusic.cs index 09bddfcc..b68dab47 100644 --- a/Vocaluxe/Base/CBackgroundMusic.cs +++ b/Vocaluxe/Base/CBackgroundMusic.cs @@ -146,7 +146,6 @@ namespace Vocaluxe.Base _PreviousMusicIndex = -1; IsPlaying = false; - _Volume = CConfig.BackgroundMusicVolume; _VideoEnabled = (CConfig.VideoBackgrounds == EOffOn.TR_CONFIG_ON && CConfig.VideosToBackground == EOffOn.TR_CONFIG_ON); SetMusicSource(CConfig.BackgroundMusicSource); _Initialized = true; @@ -161,7 +160,7 @@ namespace Vocaluxe.Base { //Resume CSound.SetStreamVolume(_CurrentMusicStream, 0f); - CSound.Fade(_CurrentMusicStream, _Volume, CSettings.BackgroundMusicFadeTime); + CSound.Fade(_CurrentMusicStream, 100f, CSettings.BackgroundMusicFadeTime); CSound.Play(_CurrentMusicStream); if (_VideoEnabled && _Video != -1) CVideo.Resume(_Video); @@ -312,13 +311,6 @@ namespace Vocaluxe.Base } } - public static void SetVolume(float volume) - { - _Volume = volume; - if (_CurrentMusicStream >= 0) - CSound.SetStreamVolume(_CurrentMusicStream, _Volume); - } - /// /// (Re-)Starts the _CurrentPlaylistElement /// @@ -333,7 +325,7 @@ namespace Vocaluxe.Base if (_CurrentMusicStream < 0) return; CSound.SetStreamVolume(_CurrentMusicStream, 0f); - CSound.Fade(_CurrentMusicStream, _Volume, CSettings.BackgroundMusicFadeTime); + CSound.Fade(_CurrentMusicStream, 100f, CSettings.BackgroundMusicFadeTime); } //Seek to #Start-Tag, if found if (_CurrentPlaylistElement.Start > 0.001 && CConfig.BackgroundMusicUseStart == EOffOn.TR_CONFIG_ON) diff --git a/Vocaluxe/Base/CConfig.cs b/Vocaluxe/Base/CConfig.cs index 6b6d7bf0..97eaa16e 100644 --- a/Vocaluxe/Base/CConfig.cs +++ b/Vocaluxe/Base/CConfig.cs @@ -102,8 +102,8 @@ namespace Vocaluxe.Base public static EOffOn BackgroundMusic = EOffOn.TR_CONFIG_ON; public static EBackgroundMusicSource BackgroundMusicSource = EBackgroundMusicSource.TR_CONFIG_NO_OWN_MUSIC; public static EOffOn BackgroundMusicUseStart = EOffOn.TR_CONFIG_ON; - public static int PreviewMusicVolume = 50; - public static int GameMusicVolume = 80; + private static int _PreviewMusicVolume = 50; + private static int _GameMusicVolume = 80; //Folders public static readonly List SongFolders = new List @@ -171,12 +171,27 @@ namespace Vocaluxe.Base get { return _BackgroundMusicVolume; } set { - if (value < 0) - _BackgroundMusicVolume = 0; - else if (value > 100) - _BackgroundMusicVolume = 100; - else - _BackgroundMusicVolume = value; + _BackgroundMusicVolume = value.Clamp(0, 100); + SaveConfig(); + } + } + + public static int GameMusicVolume + { + get { return _GameMusicVolume; } + set + { + _GameMusicVolume = value.Clamp(0, 100); + SaveConfig(); + } + } + + public static int PreviewMusicVolume + { + get { return _PreviewMusicVolume; } + set + { + _PreviewMusicVolume = value.Clamp(0, 100); SaveConfig(); } } @@ -248,8 +263,8 @@ namespace Vocaluxe.Base xmlReader.TryGetIntValueRange("//root/Sound/BackgroundMusicVolume", ref _BackgroundMusicVolume); xmlReader.TryGetEnumValue("//root/Sound/BackgroundMusicSource", ref BackgroundMusicSource); xmlReader.TryGetEnumValue("//root/Sound/BackgroundMusicUseStart", ref BackgroundMusicUseStart); - xmlReader.TryGetIntValueRange("//root/Sound/PreviewMusicVolume", ref PreviewMusicVolume); - xmlReader.TryGetIntValueRange("//root/Sound/GameMusicVolume", ref GameMusicVolume); + xmlReader.TryGetIntValueRange("//root/Sound/PreviewMusicVolume", ref _PreviewMusicVolume); + xmlReader.TryGetIntValueRange("//root/Sound/GameMusicVolume", ref _GameMusicVolume); #endregion Sound #region Game diff --git a/Vocaluxe/Base/CGraphics.cs b/Vocaluxe/Base/CGraphics.cs index 06a28e45..a4b5adcb 100644 --- a/Vocaluxe/Base/CGraphics.cs +++ b/Vocaluxe/Base/CGraphics.cs @@ -408,6 +408,7 @@ namespace Vocaluxe.Base if (popupVolumeControlAllowed) { + //TODO: Handle this in the volume popup int diff = 0; if ((keyEvent.ModShift && (keyEvent.Key == Keys.Add || keyEvent.Key == Keys.PageUp)) || (keyEvent.Sender == ESender.WiiMote && keyEvent.Key == Keys.Add)) diff = 5; @@ -423,23 +424,23 @@ namespace Vocaluxe.Base if (CSongs.IsInCategory) { CConfig.PreviewMusicVolume += diff; - _Screens[(int)_CurrentScreen].ApplyVolume(); + CSound.SetGlobalVolume(CConfig.PreviewMusicVolume); } else { CConfig.BackgroundMusicVolume += diff; - CBackgroundMusic.SetVolume(CConfig.BackgroundMusicVolume); + CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume); } break; case EScreens.ScreenSing: CConfig.GameMusicVolume += diff; - _Screens[(int)_CurrentScreen].ApplyVolume(); + CSound.SetGlobalVolume(CConfig.GameMusicVolume); break; default: CConfig.BackgroundMusicVolume += diff; - CBackgroundMusic.SetVolume(CConfig.BackgroundMusicVolume); + CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume); break; } CConfig.SaveConfig(); @@ -521,13 +522,8 @@ namespace Vocaluxe.Base bool handled = false; if (_CurrentPopupScreen != EPopupScreens.NoPopup) - { handled = _PopupScreens[(int)_CurrentPopupScreen].HandleMouse(mouseEvent); - if (handled && _CurrentPopupScreen == EPopupScreens.PopupVolumeControl) - _Screens[(int)_CurrentScreen].ApplyVolume(); - } - if (!handled && _Fading == null && (_Cursor.IsActive || mouseEvent.LB || mouseEvent.RB || mouseEvent.MB)) resume &= _Screens[(int)_CurrentScreen].HandleMouse(mouseEvent); diff --git a/Vocaluxe/Base/CMain.cs b/Vocaluxe/Base/CMain.cs index a66a960d..533444fd 100644 --- a/Vocaluxe/Base/CMain.cs +++ b/Vocaluxe/Base/CMain.cs @@ -808,7 +808,7 @@ namespace Vocaluxe.Base return CSound.GetLength(soundStream); } - public void FadeAndStop(int soundStream, float targetVolume, float duration) + public void FadeAndClose(int soundStream, float targetVolume, float duration) { CSound.FadeAndClose(soundStream, targetVolume, duration); } @@ -817,11 +817,6 @@ namespace Vocaluxe.Base { CSound.SetStreamVolume(soundStream, volume); } - - public void SetStreamVolumeMax(int soundStream, float maxVolume) - { - CSound.SetStreamVolumeMax(soundStream, maxVolume); - } } class CBcover : ICover diff --git a/Vocaluxe/Base/CSound.cs b/Vocaluxe/Base/CSound.cs index d5fbd50d..8d9ad0e8 100644 --- a/Vocaluxe/Base/CSound.cs +++ b/Vocaluxe/Base/CSound.cs @@ -109,11 +109,6 @@ namespace Vocaluxe.Base _Playback.Play(stream); } - public static void Play(int stream, bool loop) - { - _Playback.Play(stream, loop); - } - public static void Pause(int stream) { _Playback.Pause(stream); @@ -124,24 +119,24 @@ namespace Vocaluxe.Base _Playback.Stop(stream); } - public static void Fade(int stream, float targetVolume, float seconds) + public static void Fade(int stream, float targetVolume, float seconds, EStreamAction afterFadeAction = EStreamAction.Nothing) { - _Playback.Fade(stream, targetVolume, seconds); + _Playback.Fade(stream, targetVolume, seconds, afterFadeAction); } public static void FadeAndPause(int stream, float targetVolume, float seconds) { - _Playback.FadeAndPause(stream, targetVolume, seconds); + Fade(stream, targetVolume, seconds, EStreamAction.Pause); } public static void FadeAndClose(int stream, float targetVolume, float seconds) { - _Playback.FadeAndClose(stream, targetVolume, seconds); + Fade(stream, targetVolume, seconds, EStreamAction.Close); } public static void FadeAndStop(int stream, float targetVolume, float seconds) { - _Playback.FadeAndStop(stream, targetVolume, seconds); + Fade(stream, targetVolume, seconds, EStreamAction.Stop); } public static void SetStreamVolume(int stream, float volume) @@ -149,11 +144,6 @@ namespace Vocaluxe.Base _Playback.SetStreamVolume(stream, volume); } - public static void SetStreamVolumeMax(int stream, float volume) - { - _Playback.SetStreamVolumeMax(stream, volume); - } - public static float GetLength(int stream) { return _Playback.GetLength(stream); diff --git a/Vocaluxe/Lib/Sound/Playback/CAudioStreamBase.cs b/Vocaluxe/Lib/Sound/Playback/CAudioStreamBase.cs new file mode 100644 index 00000000..d8046b4d --- /dev/null +++ b/Vocaluxe/Lib/Sound/Playback/CAudioStreamBase.cs @@ -0,0 +1,120 @@ +#region license +// This file is part of Vocaluxe. +// +// Vocaluxe is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vocaluxe is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vocaluxe. If not, see . +#endregion + +using System; +using VocaluxeLib; + +namespace Vocaluxe.Lib.Sound.Playback +{ + abstract class CAudioStreamBase : IAudioStream + { + private readonly int _ID; + protected readonly string _Medium; + protected readonly bool _Loop; + + protected ICloseStreamListener _CloseStreamListener; + + protected EStreamAction _AfterFadeAction = EStreamAction.Nothing; + protected CFading _Fading; + + public int ID + { + get { return _ID; } + } + + public virtual float Volume { get; set; } + public virtual float VolumeMax { get; set; } + public virtual float Length { get; protected set; } + public abstract float Position { get; set; } + public abstract bool IsPaused { get; set; } + public abstract bool IsFinished { get; } + + public CAudioStreamBase(int id, string medium, bool loop) + { + _ID = id; + _Medium = medium; + _Loop = loop; + } + + ~CAudioStreamBase() + { + _Dispose(false); + } + + public void Dispose() + { + _Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void _Dispose(bool disposing) + { + if (!disposing) + CBase.Log.LogDebug("Audio stream " + _Medium + " was not closed."); + } + + public void Close() + { + Dispose(); + } + + public void SetOnCloseListener(ICloseStreamListener listener) + { + _CloseStreamListener = listener; + } + + public void Fade(float targetVolume, float seconds, EStreamAction afterFadeAction) + { + _Fading = new CFading(Volume, targetVolume, seconds); + _AfterFadeAction = afterFadeAction; + } + + public void CancelFading() + { + _Fading = null; + } + + public virtual void Update() + { + if (_Fading != null) + { + bool finished; + Volume = _Fading.GetValue(out finished); + if (finished) + { + switch (_AfterFadeAction) + { + case EStreamAction.Close: + Close(); + break; + case EStreamAction.Stop: + Stop(); + break; + case EStreamAction.Pause: + IsPaused = true; + break; + } + _Fading = null; + } + } + } + + public abstract bool Open(bool prescan); + public abstract void Play(); + public abstract void Stop(); + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs b/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs new file mode 100644 index 00000000..d472d808 --- /dev/null +++ b/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs @@ -0,0 +1,308 @@ +#region license +// This file is part of Vocaluxe. +// +// Vocaluxe is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vocaluxe is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vocaluxe. If not, see . +#endregion + +using System.Collections.Generic; +using System.Threading; +using VocaluxeLib; + +namespace Vocaluxe.Lib.Sound.Playback +{ + public abstract class CPlaybackBase : IPlayback, ICloseStreamListener + { + protected bool _Initialized; + protected readonly List _Streams = new List(); + private int _NextID; + protected float _GlobalVolume = 1f; + + public abstract bool Init(); + + public virtual void Close() + { + if (!_Initialized) + return; + List streams = new List(); + //Get all streams but do not modify list without holding the lock + //Calling dispose from within the lock may deadlock in closeproc (if a thread is already there) + lock (_Streams) + { + streams.AddRange(_Streams); + } + foreach (IAudioStream stream in streams) + stream.Dispose(); + while (_Streams.Count > 0) + Thread.Sleep(5); + _Initialized = false; + } + + public void CloseAll() + { + if (!_Initialized) + return; + lock (_Streams) + { + foreach (IAudioStream stream in _Streams) + stream.Close(); + _Streams.Clear(); + } + } + + public float GetGlobalVolume() + { + return (_Initialized) ? _GlobalVolume * 100f : 100f; + } + + public void SetGlobalVolume(float volume) + { + if (!_Initialized) + return; + volume = volume.Clamp(0f, 100f) / 100f; + foreach (IAudioStream stream in _Streams) + stream.VolumeMax = volume; + _GlobalVolume = volume; + } + + public int GetStreamCount() + { + if (!_Initialized) + return 0; + + lock (_Streams) + { + return _Streams.Count; + } + } + + public void Update() + { + if (!_Initialized) + return; + lock (_Streams) + { + foreach (IAudioStream stream in _Streams) + stream.Update(); + } + } + + #region Stream Handling + //Factory method to get a stream instance + protected abstract IAudioStream _CreateStream(int id, string media, bool loop); + + public int Load(string medium, bool loop = false, bool prescan = false) + { + if (!_Initialized) + return -1; + IAudioStream stream = _CreateStream(_NextID, medium, loop); + + if (stream.Open(prescan)) + { + lock (_Streams) + { + stream.Volume = 1f; + stream.VolumeMax = _GlobalVolume; + stream.SetOnCloseListener(this); + _Streams.Add(stream); + _NextID++; + return stream.ID; + } + } + return -1; + } + + public void Close(int streamID) + { + if (!_Initialized) + return; + lock (_Streams) + { + int index = _GetStreamIndex(streamID); + if (index >= 0) + { + _Streams[index].Close(); + _Streams.RemoveAt(index); + } + } + } + + public void Play(int streamID) + { + if (!_Initialized) + return; + lock (_Streams) + { + if (_StreamExists(streamID)) + _Streams[_GetStreamIndex(streamID)].Play(); + } + } + + public void Pause(int streamID) + { + if (!_Initialized) + return; + lock (_Streams) + { + if (_StreamExists(streamID)) + _Streams[_GetStreamIndex(streamID)].IsPaused = true; + } + } + + public void Stop(int streamID) + { + if (!_Initialized) + return; + lock (_Streams) + { + if (_StreamExists(streamID)) + _Streams[_GetStreamIndex(streamID)].Stop(); + } + } + + public void Fade(int streamID, float targetVolume, float seconds, EStreamAction afterFadeAction = EStreamAction.Nothing) + { + if (!_Initialized) + return; + targetVolume = targetVolume.Clamp(0f, 100f) / 100f; + lock (_Streams) + { + if (_StreamExists(streamID)) + _Streams[_GetStreamIndex(streamID)].Fade(targetVolume, seconds, afterFadeAction); + } + } + + public void SetStreamVolume(int streamID, float volume) + { + if (!_Initialized) + return; + volume = volume.Clamp(0f, 100f) / 100f; + lock (_Streams) + { + if (_StreamExists(streamID)) + { + _Streams[_GetStreamIndex(streamID)].Volume = volume; + _Streams[_GetStreamIndex(streamID)].CancelFading(); + } + } + } + + public float GetLength(int streamID) + { + if (!_Initialized) + return 0f; + lock (_Streams) + { + if (_StreamExists(streamID)) + return _Streams[_GetStreamIndex(streamID)].Length; + } + return 0f; + } + + public float GetPosition(int streamID) + { + if (!_Initialized) + return 0f; + lock (_Streams) + { + if (_StreamExists(streamID)) + return _Streams[_GetStreamIndex(streamID)].Position; + } + + return 0f; + } + + public void SetPosition(int streamID, float position) + { + if (!_Initialized) + return; + lock (_Streams) + { + if (_StreamExists(streamID)) + _Streams[_GetStreamIndex(streamID)].Position = position; + } + } + + public bool IsPlaying(int streamID) + { + if (!_Initialized) + return false; + lock (_Streams) + { + if (_StreamExists(streamID)) + return !_Streams[_GetStreamIndex(streamID)].IsPaused && !_Streams[_GetStreamIndex(streamID)].IsFinished; + } + return false; + } + + public bool IsPaused(int streamID) + { + if (!_Initialized) + return false; + lock (_Streams) + { + if (_StreamExists(streamID)) + return _Streams[_GetStreamIndex(streamID)].IsPaused; + } + return false; + } + + public bool IsFinished(int streamID) + { + if (!_Initialized) + return true; + lock (_Streams) + { + if (_StreamExists(streamID)) + return _Streams[_GetStreamIndex(streamID)].IsFinished; + } + return true; + } + + /// + /// Returns true if stream with given id is found + /// MUST hold _Stream-lock + /// + /// Stream id + /// + protected bool _StreamExists(int streamID) + { + return _GetStreamIndex(streamID) != -1; + } + + /// + /// Returns the index of the stream with the given id + /// MUST hold _Stream-lock + /// + /// Stream id + /// + protected int _GetStreamIndex(int streamID) + { + for (int i = 0; i < _Streams.Count; i++) + { + if (_Streams[i].ID == streamID) + return i; + } + return -1; + } + #endregion Stream Handling + + public void OnCloseStream(IAudioStream stream) + { + lock (_Streams) + { + _Streams.Remove(stream); + } + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/CRingBuffer.cs b/Vocaluxe/Lib/Sound/Playback/CRingBuffer.cs index 73d0ebe3..2d1c4dc7 100644 --- a/Vocaluxe/Lib/Sound/Playback/CRingBuffer.cs +++ b/Vocaluxe/Lib/Sound/Playback/CRingBuffer.cs @@ -1,4 +1,21 @@ -using System; +#region license +// This file is part of Vocaluxe. +// +// Vocaluxe is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vocaluxe is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vocaluxe. If not, see . +#endregion + +using System; namespace Vocaluxe.Lib.Sound.Playback { diff --git a/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs b/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs index 1668deb0..505b3604 100644 --- a/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs +++ b/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs @@ -34,9 +34,7 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder private string _FileName; private bool _FileOpened; - public void Init() {} - - public void Open(string fileName) + public bool Open(string fileName) { _FileName = fileName; @@ -49,14 +47,15 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder catch (Exception) { CLog.LogError("Error opening audio file: " + _FileName); - return; + Close(); + return false; } if (!_Instance.Opened) { Close(); - return; + return false; } int audioStreamIndex; @@ -71,13 +70,13 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder { CLog.LogError("Error opening audio file (can't find decoder): " + _FileName); Close(); - return; + return false; } if (audioStreamIndex < 0) { Close(); - return; + return false; } _FormatInfo = new SFormatInfo @@ -93,9 +92,10 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder { CLog.LogError("Unsupported BitDepth in file " + fileName); Close(); - return; + return false; } _FileOpened = true; + return true; } public void Close() diff --git a/Vocaluxe/Lib/Sound/Playback/Decoder/IAudioDecoder.cs b/Vocaluxe/Lib/Sound/Playback/Decoder/IAudioDecoder.cs index 6187c279..98505b37 100644 --- a/Vocaluxe/Lib/Sound/Playback/Decoder/IAudioDecoder.cs +++ b/Vocaluxe/Lib/Sound/Playback/Decoder/IAudioDecoder.cs @@ -26,10 +26,9 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder interface IAudioDecoder { - void Init(); void Close(); - void Open(string fileName); + bool Open(string fileName); SFormatInfo GetFormatInfo(); float GetLength(); diff --git a/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudio.cs b/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudio.cs index 2b6a663a..4dcd9006 100644 --- a/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudio.cs +++ b/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudio.cs @@ -22,7 +22,6 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer { class CGstreamerAudio : IPlayback { - //static float LastPosition; private readonly CGstreamerAudioWrapper.LogCallback _LogCallback; #region log @@ -35,8 +34,6 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer public CGstreamerAudio() { _LogCallback = _LogHandler; - //Is this really needed? CodaAnalyzer complains about it... - //GC.SuppressFinalize(_LogCallback); CGstreamerAudioWrapper.SetLogCallback(_LogCallback); } @@ -48,6 +45,12 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer public void Close() { CloseAll(); + CGstreamerAudioWrapper.SetLogCallback(null); + } + + public float GetGlobalVolume() + { + return CGstreamerAudioWrapper.GetGlobalVolume(); } public void SetGlobalVolume(float volume) @@ -65,17 +68,13 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer CGstreamerAudioWrapper.CloseAll(); } - public int Load(string media) + public int Load(string medium, bool loop = false, bool prescan = false) { - var u = new Uri(media); - int i = CGstreamerAudioWrapper.Load(u.AbsoluteUri); - return i; - } - - public int Load(string media, bool prescan) - { - var u = new Uri(media); + var u = new Uri(medium); int i = CGstreamerAudioWrapper.Load(u.AbsoluteUri, prescan); + //Workaround b/c I'm lazy: + CGstreamerAudioWrapper.Play(i, loop); + CGstreamerAudioWrapper.Pause(i); return i; } @@ -89,11 +88,6 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer CGstreamerAudioWrapper.Play(stream); } - public void Play(int stream, bool loop) - { - CGstreamerAudioWrapper.Play(stream, loop); - } - public void Pause(int stream) { CGstreamerAudioWrapper.Pause(stream); @@ -104,19 +98,25 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer CGstreamerAudioWrapper.Stop(stream); } - public void Fade(int stream, float targetVolume, float seconds) + public void Fade(int streamID, float targetVolume, float seconds, EStreamAction afterFadeAction = EStreamAction.Nothing) { - CGstreamerAudioWrapper.Fade(stream, targetVolume, seconds); - } - - public void FadeAndPause(int stream, float targetVolume, float seconds) - { - CGstreamerAudioWrapper.FadeAndPause(stream, targetVolume, seconds); - } - - public void FadeAndClose(int stream, float targetVolume, float seconds) - { - CGstreamerAudioWrapper.FadeAndStop(stream, targetVolume, seconds); + switch (afterFadeAction) + { + case EStreamAction.Nothing: + CGstreamerAudioWrapper.Fade(streamID, targetVolume, seconds); + break; + case EStreamAction.Pause: + CGstreamerAudioWrapper.FadeAndPause(streamID, targetVolume, seconds); + break; + case EStreamAction.Stop: + CGstreamerAudioWrapper.FadeAndStop(streamID, targetVolume, seconds); + break; + case EStreamAction.Close: + CGstreamerAudioWrapper.FadeAndStop(streamID, targetVolume, seconds); + break; + default: + throw new ArgumentOutOfRangeException("afterFadeAction"); + } } public void SetStreamVolume(int stream, float volume) @@ -124,11 +124,6 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer CGstreamerAudioWrapper.SetStreamVolume(stream, volume); } - public void SetStreamVolumeMax(int stream, float volume) - { - CGstreamerAudioWrapper.SetStreamVolumeMax(stream, volume); - } - public float GetLength(int stream) { return CGstreamerAudioWrapper.GetLength(stream); @@ -163,10 +158,5 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer { CGstreamerAudioWrapper.Update(); } - - public void FadeAndStop(int stream, float targetVolume, float seconds) - { - throw new NotImplementedException(); - } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudioWrapper.cs b/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudioWrapper.cs index bf600899..f091c887 100644 --- a/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudioWrapper.cs +++ b/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudioWrapper.cs @@ -48,6 +48,9 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] public static extern void SetGlobalVolume(float volume); + [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] + public static extern float GetGlobalVolume(); + [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetStreamCount(); @@ -57,7 +60,7 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int Load(string media); - [DllImport(_Dll, EntryPoint = "LoadPrescan", CallingConvention = CallingConvention.Cdecl)] + [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "LoadPrescan")] public static extern int Load(string media, [MarshalAs(UnmanagedType.U1)] bool prescan); @@ -67,7 +70,7 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] public static extern void Play(int stream); - [DllImport(_Dll, EntryPoint = "PlayLoop")] + [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PlayLoop")] public static extern void Play(int stream, [MarshalAs(UnmanagedType.U1)] bool loop); @@ -89,9 +92,6 @@ namespace Vocaluxe.Lib.Sound.Playback.Gstreamer [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] public static extern void SetStreamVolume(int stream, float volume); - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void SetStreamVolumeMax(int stream, float volume); - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] public static extern float GetLength(int stream); diff --git a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs index f95c2c91..3bc2a989 100644 --- a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs +++ b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs @@ -20,14 +20,11 @@ using Gst; namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp { - public class CGstreamerSharpAudio : IPlayback + public class CGstreamerSharpAudio : CPlaybackBase { - private readonly Dictionary _Streams = new Dictionary(); - private static int _IDCount; - - public bool Init() + public override bool Init() { - if (Application.IsInitialized) + if (_Initialized) return false; #if ARCH_X86 const string path = ".\\x86\\gstreamer"; @@ -40,176 +37,21 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp Registry reg = Registry.Get(); reg.ScanPath(path); - return Application.IsInitialized; + _Initialized = Application.IsInitialized; + return _Initialized; } - public void Close() + public override void Close() { - if (!Application.IsInitialized) + if (!_Initialized) return; - CloseAll(); + base.Close(); Application.Deinit(); } - public void SetGlobalVolume(float volume) + protected override IAudioStream _CreateStream(int id, string media, bool loop) { - foreach (CGstreamerSharpAudioStream stream in _Streams.Values) - stream.Volume = volume; - } - - public int GetStreamCount() - { - return _Streams.Count; - } - - public void CloseAll() - { - foreach (CGstreamerSharpAudioStream stream in _Streams.Values) - stream.Close(); - } - - public int Load(string media) - { - return Load(media, false); - } - - public int Load(string media, bool prescan) - { - var stream = new CGstreamerSharpAudioStream(media, prescan); - _Streams[_IDCount] = stream; - return _IDCount++; - } - - public void Close(int stream) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].Close(); - } - - public void Play(int stream) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].Playing = true; - } - - public void Play(int stream, bool loop) - { - if (!_Streams.ContainsKey(stream)) - return; - Play(stream, loop); - } - - public void Pause(int stream) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].Paused = true; - } - - public void Stop(int stream) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].Stop(); - } - - public void Fade(int stream, float targetVolume, float seconds) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].Fade(targetVolume, seconds); - } - - public void FadeAndPause(int stream, float targetVolume, float seconds) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].FadeAndPause(targetVolume, seconds); - } - - public void FadeAndClose(int stream, float targetVolume, float seconds) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].FadeAndClose(targetVolume, seconds); - } - - public void FadeAndStop(int stream, float targetVolume, float seconds) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].FadeAndStop(targetVolume, seconds); - } - - public void SetStreamVolume(int stream, float volume) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].Volume = volume; - } - - public void SetStreamVolumeMax(int stream, float volume) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].MaxVolume = volume; - } - - public float GetLength(int stream) - { - if (!_Streams.ContainsKey(stream)) - return -1; - return _Streams[stream].Length; - } - - public float GetPosition(int stream) - { - if (!_Streams.ContainsKey(stream)) - return -1f; - return _Streams[stream].Position; - } - - public bool IsPlaying(int stream) - { - if (!_Streams.ContainsKey(stream)) - return false; - return _Streams[stream].Playing; - } - - public bool IsPaused(int stream) - { - if (!_Streams.ContainsKey(stream)) - return false; - return _Streams[stream].Paused; - } - - public bool IsFinished(int stream) - { - if (!_Streams.ContainsKey(stream)) - return true; - return _Streams[stream].Finished; - } - - public void SetPosition(int stream, float position) - { - if (!_Streams.ContainsKey(stream)) - return; - _Streams[stream].Position = position; - } - - public void Update() - { - var streamsToDelete = new List(); - foreach (KeyValuePair stream in _Streams) - { - if (stream.Value.Closed) - streamsToDelete.Add(stream.Key); - stream.Value.Update(); - } - foreach (int key in streamsToDelete) - _Streams.Remove(key); + return new CGstreamerSharpAudioStream(id, media, loop); } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs index c566c9b0..52296826 100644 --- a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs @@ -19,39 +19,107 @@ using System; using GLib; using Gst; using Vocaluxe.Base; -using VocaluxeLib; namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp { - public class CGstreamerSharpAudioStream + class CGstreamerSharpAudioStream : CAudioStreamBase { private Element _Element; - private bool _Loop; - public volatile bool Closed = true; - public volatile bool Finished; - private CFading _Fading; - private EStreamAction _AfterFadeAction; - private float _Volume = 1f; + private volatile bool _IsFinished; - private float _MaxVolume = 1f; - - private volatile float _Duration = -1f; private volatile float _Position; private volatile bool _QueryingDuration; - public CGstreamerSharpAudioStream(string media, bool prescan) + private readonly object _DurationLock = new object(); //For atomic check and update of _QueryingDuration + private readonly object _ElementLock = new object(); //Hold this if you set _Element=null or when accessing _Element from outside the main thread + + public override float Volume + { + get { return base.Volume; } + set + { + base.Volume = value; + _SetElementVolume(); + } + } + + public override float VolumeMax + { + get { return base.VolumeMax; } + set + { + base.VolumeMax = value; + _SetElementVolume(); + } + } + + public override float Length + { + get + { + if (base.Length < 0 && !_QueryingDuration) + { + _QueryingDuration = true; // Set this to avoid race conditions + var t = new System.Threading.Thread(_UpdateDuration) {Name = "GSt Update Duration"}; + t.Start(); + } + return base.Length >= 0f ? base.Length : 0f; + } + } + + public override float Position + { + get + { + long position; + if (!_Element.QueryPosition(Format.Time, out position)) + CLog.LogError("Could not query position"); + else + _Position = ((float)position / Constants.SECOND); + return _Position; + } + set + { + if (_Element != null) + _Element.SeekSimple(Format.Time, SeekFlags.Accurate | SeekFlags.Flush, (long)(value * Constants.SECOND)); + } + } + + public override bool IsFinished + { + get { return _IsFinished; } + } + + public override bool IsPaused + { + get { return _Element == null || _Element.TargetState == State.Paused; } + set + { + if (_Element == null) + return; + _Element.SetState(value ? State.Paused : State.Playing); + } + } + + public CGstreamerSharpAudioStream(int id, string medium, bool loop) : base(id, medium, loop) {} + + public override bool Open(bool prescan) { Element convert = ElementFactory.Make("audioconvert", "convert"); Element audiosink = ElementFactory.Make("directsoundsink", "audiosink"); - var audioSinkBin = new Bin("Audiosink"); if (convert == null || audiosink == null) { CLog.LogError("Could not create pipeline"); - return; + if (convert != null) + convert.Dispose(); + if (audiosink != null) + audiosink.Dispose(); + return false; } + var audioSinkBin = new Bin("Audiosink"); audioSinkBin.Add(convert); audioSinkBin.Add(audiosink); convert.Link(audiosink); @@ -61,33 +129,46 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp if (pad == null) { CLog.LogError("Could not create pads"); - return; + convert.Dispose(); + audiosink.Dispose(); + audioSinkBin.Dispose(); + return false; } if (!ghostpad.SetActive(true)) { CLog.LogError("Could not link pads"); - return; + convert.Dispose(); + audiosink.Dispose(); + audioSinkBin.Dispose(); + ghostpad.Dispose(); + return false; } if (!audioSinkBin.AddPad(ghostpad)) { CLog.LogError("Could not add pad"); - return; + convert.Dispose(); + audiosink.Dispose(); + audioSinkBin.Dispose(); + ghostpad.Dispose(); + return false; } _Element = ElementFactory.Make("playbin", "playbin"); if (_Element == null) { CLog.LogError("Could not create playbin"); - return; + convert.Dispose(); + audiosink.Dispose(); + audioSinkBin.Dispose(); + ghostpad.Dispose(); + return false; } _Element["audio-sink"] = audioSinkBin; _Element["flags"] = 1 << 1; - _Element["uri"] = new Uri(media).AbsoluteUri; + _Element["uri"] = new Uri(_Medium).AbsoluteUri; _Element.SetState(State.Paused); - Closed = false; //Enable stream - // Passing CLOCK_TIME_NONE here causes the pipeline to block for a long time so with // prescan enabled the pipeline will wait 500ms for stream to initialize and then continue // if it takes more than 500ms, duration queries will be performed asynchronously @@ -97,6 +178,55 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp if (msg.Handle != IntPtr.Zero) _UpdateDuration(); } + return true; + } + + protected override void _Dispose(bool disposing) + { + if (_Element == null) + return; + Element element; + lock (_ElementLock) + { + //Atomic get and set + if (_Element == null) + return; + element = _Element; + _Element = null; //Now everything "seems" closed + } + if (element.TargetState == State.Playing) + element.SetState(State.Paused); //Stop output + //Now really close it in the background + var t = new System.Threading.Thread(() => _TerminateStream(element)) {Name = "GSt Terminate"}; + t.Start(); + } + + private static void _TerminateStream(Element element) + { + if (element == null) + return; + //One of these might take a bit, so better do this in the background to avoid lags + element.SetState(State.Null); + element.Dispose(); + } + + public override void Play() + { + if (_Element != null) + _Element.SetState(State.Playing); + } + + public override void Stop() + { + if (_Element != null) + _Element.SetState(State.Ready); + Position = 0; + } + + private void _SetElementVolume() + { + if (_Element != null) + _Element["volume"] = Volume * VolumeMax; } private void _OnMessage(Message msg) @@ -109,10 +239,7 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp if (_Loop) Position = 0; else - { - Finished = true; - Close(); - } + _IsFinished = true; break; case MessageType.Error: GException error; @@ -128,187 +255,35 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp msg.Unref(); } - public void Close() - { - if (!Closed) - { - Closed = true; - Finished = true; - var t = new System.Threading.Thread(_TerminateStream) {Name = "GSt Terminate"}; - t.Start(); - } - } - - private void _TerminateStream() - { - if (_Element != null) - { - _Element.SetState(State.Null); - _Element.Dispose(); - _Element = null; - } - } - - public void Play(bool loop = false) - { - if (_Element != null) - _Element.SetState(State.Playing); - _Loop = loop; - } - - public void Stop() - { - if (_Element != null) - _Element.SetState(State.Ready); - Position = 0; - } - - public void Fade(float targetVolume, float seconds) - { - targetVolume.Clamp(0f, 100f); - _Fading = new CFading(_Volume, targetVolume / 100f, seconds); - _AfterFadeAction = EStreamAction.Nothing; - } - - public void FadeAndPause(float targetVolume, float seconds) - { - Fade(targetVolume, seconds); - _AfterFadeAction = EStreamAction.Pause; - } - - public void FadeAndClose(float targetVolume, float seconds) - { - Fade(targetVolume, seconds); - _AfterFadeAction = EStreamAction.Close; - } - - public void FadeAndStop(float targetVolume, float seconds) - { - Fade(targetVolume, seconds); - _AfterFadeAction = EStreamAction.Stop; - } - - public float Volume - { - get { return _Volume * 100f; } - set - { - value.Clamp(0f, 100f); - _Volume = value / 100f; - _SetElementVolume(); - } - } - - private void _SetElementVolume() - { - if (_Element != null) - _Element["volume"] = (_Volume * _MaxVolume); - } - - public float MaxVolume - { - get { return _MaxVolume * 100f; } - set - { - value.Clamp(0f, 100f); - _MaxVolume = value / 100f; - _SetElementVolume(); - } - } - - public float Length - { - get - { - if (_Duration < 0 && !_QueryingDuration) - { - _QueryingDuration = true; // Set this to avoid race conditions - var t = new System.Threading.Thread(_UpdateDuration) {Name = "GSt Update Duration"}; - t.Start(); - } - return _Duration > 0 ? _Duration : -1; - } - } - - public float Position - { - get - { - long position; - if (!_Element.QueryPosition(Format.Time, out position)) - CLog.LogError("Could not query position"); - else - _Position = (float)(position / (double)Constants.SECOND); - return _Position; - } - set - { - if (_Element != null) - _Element.SeekSimple(Format.Time, SeekFlags.Accurate | SeekFlags.Flush, (long)(value * (double)Constants.SECOND)); - } - } - - public bool Paused - { - get { return _Element == null || _Element.TargetState == State.Paused; } - set - { - if (value && _Element != null) - _Element.SetState(State.Paused); - } - } - - public bool Playing - { - get { return _Element != null && (_Element.TargetState == State.Playing && !Finished); } - set - { - if (value && _Element != null) - _Element.SetState(State.Playing); - } - } - - public void Update() + public override void Update() { + base.Update(); while (_Element != null && _Element.Bus != null && _Element.Bus.HavePending()) _OnMessage(_Element.Bus.Pop()); - - if (_Fading != null) - { - bool finished; - _Volume = _Fading.GetValue(out finished); - _SetElementVolume(); - if (finished) - { - switch (_AfterFadeAction) - { - case EStreamAction.Pause: - Paused = true; - break; - case EStreamAction.Stop: - Stop(); - break; - case EStreamAction.Close: - Close(); - break; - } - _Fading = null; - } - } } private void _UpdateDuration() { - _QueryingDuration = true; - long duration = -1; - while (duration < 0 && !Closed && !Finished && _Element != null) + if (_QueryingDuration) + return; + lock (_DurationLock) { - if (_Element.QueryDuration(Format.Time, out duration)) + if (_QueryingDuration) + return; + _QueryingDuration = true; + } + long duration = -1; + while (duration < 0 && !IsFinished) + { + lock (_ElementLock) { - _Duration = duration / (float)Constants.SECOND; - _QueryingDuration = false; + if (_Element == null) + break; + if (_Element.QueryDuration(Format.Time, out duration)) + Length = duration / (float)Constants.SECOND; } } + _QueryingDuration = false; } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/IAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/IAudioStream.cs new file mode 100644 index 00000000..8ec763fe --- /dev/null +++ b/Vocaluxe/Lib/Sound/Playback/IAudioStream.cs @@ -0,0 +1,48 @@ +#region license +// This file is part of Vocaluxe. +// +// Vocaluxe is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vocaluxe is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vocaluxe. If not, see . +#endregion + +using System; + +namespace Vocaluxe.Lib.Sound.Playback +{ + public interface ICloseStreamListener + { + void OnCloseStream(IAudioStream stream); + } + + public interface IAudioStream : IDisposable + { + int ID { get; } + float Volume { get; set; } + float VolumeMax { get; set; } + float Length { get; } + float Position { get; set; } + bool IsPaused { get; set; } + bool IsFinished { get; } + + void SetOnCloseListener(ICloseStreamListener listener); + + void Update(); + + bool Open(bool prescan); + void Close(); + void Play(); + void Stop(); + void Fade(float targetVolume, float seconds, EStreamAction afterFadeAction); + void CancelFading(); + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/IPlayback.cs b/Vocaluxe/Lib/Sound/Playback/IPlayback.cs index b4a13057..04d9be75 100644 --- a/Vocaluxe/Lib/Sound/Playback/IPlayback.cs +++ b/Vocaluxe/Lib/Sound/Playback/IPlayback.cs @@ -25,52 +25,47 @@ namespace Vocaluxe.Lib.Sound.Playback Close } - struct SAudioStreams - { - public int Handle; - - public SAudioStreams(int stream) - { - Handle = stream; - } - } - - delegate void Closeproc(int stream); - interface IPlayback { bool Init(); void Close(); + float GetGlobalVolume(); + + /// + /// Set the global (maximum) volume
+ /// All streams (existent and future ones) will have their maximum volume set to this value + ///
+ /// Volume in percent void SetGlobalVolume(float volume); + int GetStreamCount(); void CloseAll(); - #region Stream Handling - int Load(string media); - int Load(string media, bool prescan); - void Close(int stream); + #region stream Handling + int Load(string medium, bool loop = false, bool prescan = false); + void Close(int streamID); - void Play(int stream); - void Play(int stream, bool loop); - void Pause(int stream); - void Stop(int stream); - void Fade(int stream, float targetVolume, float seconds); - void FadeAndPause(int stream, float targetVolume, float seconds); - void FadeAndClose(int stream, float targetVolume, float seconds); - void FadeAndStop(int stream, float targetVolume, float seconds); - void SetStreamVolume(int stream, float volume); - void SetStreamVolumeMax(int stream, float volume); + void Play(int streamID); + void Pause(int streamID); + void Stop(int streamID); + void Fade(int streamID, float targetVolume, float seconds, EStreamAction afterFadeAction = EStreamAction.Nothing); - float GetLength(int stream); - float GetPosition(int stream); + /// + /// Set the streams current volume. Cancels fading + /// + /// Id of the stream (optained by Load) + /// Volume in percent + void SetStreamVolume(int streamID, float volume); - bool IsPlaying(int stream); - bool IsPaused(int stream); - bool IsFinished(int stream); + float GetLength(int streamID); + float GetPosition(int streamID); + void SetPosition(int streamID, float position); - void SetPosition(int stream, float position); + bool IsPlaying(int streamID); + bool IsPaused(int streamID); + bool IsFinished(int streamID); void Update(); - #endregion Stream Handling + #endregion stream Handling } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs index 558ab7ee..12f7cd2b 100644 --- a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs +++ b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs @@ -23,351 +23,34 @@ using System.Collections.Generic; namespace Vocaluxe.Lib.Sound.Playback.OpenAL { - class COpenALPlay : IPlayback, IDisposable + class COpenALPlay : CPlaybackBase { - private bool _Initialized; - private readonly List _Decoder = new List(); - private Closeproc _Closeproc; - private int _Count = 1; private AudioContext _Context; - private readonly Object _MutexDecoder = new Object(); - - private List _Streams; - - ~COpenALPlay() - { - _Dispose(false); - } - - public bool Init() + public override bool Init() { if (_Initialized) return false; _Context = new AudioContext(); - _Context.MakeCurrent(); - - _Closeproc = _CloseProc; _Initialized = true; - - _Streams = new List(); return true; } - public void Close() - { - Dispose(); - } - - public void CloseAll() - { - lock (_MutexDecoder) - { - for (int i = 0; i < _Decoder.Count; i++) - _Decoder[i].Free(_Closeproc, i + 1, _MutexDecoder); - } - } - - public void SetGlobalVolume(float volume) - { - if (_Initialized) - { - //Bass.BASS_SetVolume(Volume / 100f); - } - } - - public int GetStreamCount() - { - if (!_Initialized) - return 0; - - lock (_MutexDecoder) - { - return _Streams.Count; - } - } - - public void Update() - { - if (_Initialized) - { - lock (_MutexDecoder) - { - foreach (COpenAlStream stream in _Decoder) - stream.UploadData(); - } - } - } - - #region Stream Handling - public int Load(string media) - { - return Load(media, false); - } - - public int Load(string media, bool prescan) - { - var stream = new SAudioStreams(0); - var decoder = new COpenAlStream(); - - if (decoder.Open(media) > -1) - { - lock (_MutexDecoder) - { - _Decoder.Add(decoder); - stream.Handle = _Count++; - _Streams.Add(stream); - return stream.Handle; - } - } - return 0; - } - - public void Close(int stream) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - _Decoder[_GetStreamIndex(stream)].Free(_Closeproc, stream, _MutexDecoder); - } - } - } - - public void Play(int stream) - { - Play(stream, false); - } - - public void Play(int stream, bool loop) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - { - _Decoder[_GetStreamIndex(stream)].Loop = loop; - _Decoder[_GetStreamIndex(stream)].Play(); - } - } - } - } - - public void Pause(int stream) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - _Decoder[_GetStreamIndex(stream)].Paused = true; - } - } - } - - public void Stop(int stream) - { - Pause(stream); - } - - public void Fade(int stream, float targetVolume, float seconds) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - _Decoder[_GetStreamIndex(stream)].Fade(targetVolume, seconds); - } - } - } - - public void FadeAndPause(int stream, float targetVolume, float seconds) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - _Decoder[_GetStreamIndex(stream)].FadeAndPause(targetVolume, seconds); - } - } - } - - public void FadeAndClose(int stream, float targetVolume, float seconds) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - _Decoder[_GetStreamIndex(stream)].FadeAndStop(targetVolume, seconds, _Closeproc, stream); - } - } - } - - public void SetStreamVolume(int stream, float volume) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - _Decoder[_GetStreamIndex(stream)].Volume = volume; - } - } - } - - public void SetStreamVolumeMax(int stream, float volume) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - _Decoder[_GetStreamIndex(stream)].VolumeMax = volume; - } - } - } - - public float GetLength(int stream) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - return _Decoder[_GetStreamIndex(stream)].Length; - } - } - return 0f; - } - - public float GetPosition(int stream) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - return _Decoder[_GetStreamIndex(stream)].Position; - } - - return 0f; - } - return 0f; - } - - public bool IsPlaying(int stream) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - return !_Decoder[_GetStreamIndex(stream)].Paused && !_Decoder[_GetStreamIndex(stream)].Finished; - } - } - return false; - } - - public bool IsPaused(int stream) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - return _Decoder[_GetStreamIndex(stream)].Paused; - } - } - return false; - } - - public bool IsFinished(int stream) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - return _Decoder[_GetStreamIndex(stream)].Finished; - } - } - return true; - } - - public void SetPosition(int stream, float position) - { - if (_Initialized) - { - lock (_MutexDecoder) - { - if (_AlreadyAdded(stream)) - _Decoder[_GetStreamIndex(stream)].Skip(position); - } - } - } - #endregion Stream Handling - - private bool _AlreadyAdded(int stream) - { - return _Streams.Any(st => st.Handle == stream); - } - - private int _GetStreamIndex(int stream) - { - for (int i = 0; i < _Streams.Count; i++) - { - if (_Streams[i].Handle == stream) - return i; - } - return -1; - } - - private void _CloseProc(int streamID) + public override void Close() { if (!_Initialized) return; - lock (_MutexDecoder) - { - if (_AlreadyAdded(streamID)) - { - int index = _GetStreamIndex(streamID); - _Decoder.RemoveAt(index); - _Streams.RemoveAt(index); - } - } + base.Close(); + _Context.Dispose(); + _Context = null; } - public void Dispose() + protected override IAudioStream _CreateStream(int id, string media, bool loop) { - _Dispose(true); - } - - private void _Dispose(bool disposing) - { - CloseAll(); - if (disposing) - { - while (_Decoder.Count > 0) - Thread.Sleep(5); - } - if (_Context != null) - { - _Context.Dispose(); - _Context = null; - } - } - - public void FadeAndStop(int stream, float targetVolume, float seconds) - { - throw new NotImplementedException(); + return new COpenAlStream(id, media, loop); } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs index ec5bdefa..f53f13d4 100644 --- a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs @@ -22,57 +22,41 @@ using System.Threading; using OpenTK.Audio; using Vocaluxe.Base; using Vocaluxe.Lib.Sound.Playback.Decoder; -using VocaluxeLib; namespace Vocaluxe.Lib.Sound.Playback.OpenAL { - class COpenAlStream + class COpenAlStream : CAudioStreamBase { - private const int _BufferSize = 2048; private const int _BufferCount = 5; - private const int _Bufsize = 50000; - - private Object _CloseMutex; + private const int _Bufsize = 500000; + private const int _BeginRefill = 50000; private int[] _Buffers; - private int _State; + private byte[] _SampleBuf; private int _Source; private SFormatInfo _Format; - private bool _Initialized; - private int _ByteCount = 4; - private float _Volume = 1f; - private float _VolumeMax = 1f; - - private EStreamAction _AfterFadeAction; - private CFading _Fading; - private readonly Stopwatch _Timer = new Stopwatch(); - private Closeproc _Closeproc; - private int _StreamID; private IAudioDecoder _Decoder; + private int _ByteCount; private float _BytesPerSecond; private bool _NoMoreData; private bool _FileOpened; - private bool _Waiting; private bool _Skip; - private bool _Loop; - private float _Duration; private float _CurrentTime; private float _TimeCode; private bool _Paused; private CRingBuffer _Data; - private float _SetStart; + private volatile float _SetStart; private float _Start; - private bool _SetLoop; - private bool _SetSkip; - private bool _Terminated; + private volatile bool _SetSkip; + private volatile bool _Terminated; private Thread _DecoderThread; @@ -81,199 +65,157 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL private readonly Object _MutexData = new Object(); private readonly Object _MutexSyncSignals = new Object(); - public void Free(Closeproc closeProc, int streamID, Object closeMutex) - { - _Closeproc = closeProc; - _StreamID = streamID; - _Terminated = true; - _CloseMutex = closeMutex; - _EventDecode.Set(); - } - - public float Length - { - get - { - if (_FileOpened) - return _Duration; - - return 0f; - } - } - - public bool Finished + public override bool IsFinished { get { lock (_MutexData) { - return _NoMoreData && _Data.BytesNotRead == 0L; + return _NoMoreData && _Data.BytesNotRead == 0 && Position >= Length; } } } - public float Volume - { - get { return _Volume * 100f; } - set - { - lock (_MutexData) - { - value.Clamp(0f, 100f); - _Volume = value / 100f; - } - } - } - - public float VolumeMax - { - get { return _VolumeMax * 100f; } - set - { - lock (_MutexData) - { - value.Clamp(0f, 100f); - _VolumeMax = value / 100f; - } - } - } - - public float Position + public override float Position { get { - if (Finished) + float time = _CurrentTime + _Timer.ElapsedMilliseconds / 1000f; + if (time > Length) + { _Timer.Stop(); - //TODO: Why not use Decoder.GetPosition()? - return _CurrentTime + _Timer.ElapsedMilliseconds / 1000f; + time = Length; + } + return time; + } + set + { + lock (_MutexSyncSignals) + { + _SetStart = value; + _SetSkip = true; + } + //Set position here in case we immediately request it, it will be reset in Update method + _CurrentTime = value; + _Timer.Restart(); + _EventDecode.Set(); } } - public bool Paused + public override bool IsPaused { get { return _Paused; } set { _Paused = value; - lock (_MutexSyncSignals) + if (_Paused) { - if (_Paused) - { - _Timer.Stop(); - AL.SourceStop(_Source); - } - else - { - _Timer.Start(); - _EventDecode.Set(); - AL.SourcePlay(_Source); - } + _Timer.Stop(); + AL.SourceStop(_Source); + } + else + { + _Timer.Start(); + _EventDecode.Set(); + AL.SourcePlay(_Source); } } } - public void Fade(float targetVolume, float fadeTime) + public COpenAlStream(int id, string medium, bool loop) : base(id, medium, loop) {} + + protected override void _Dispose(bool disposing) { - targetVolume.Clamp(0f, 100f); - _Fading = new CFading(_Volume, targetVolume / 100f, fadeTime); - _AfterFadeAction = EStreamAction.Nothing; + base._Dispose(disposing); + if (!_Terminated) + { + _Terminated = true; + if (_DecoderThread != null) + _EventDecode.Set(); + else + _DoFree(); + } } - public void FadeAndPause(float targetVolume, float fadeTime) + public override void Play() { - Fade(targetVolume, fadeTime); - _AfterFadeAction = EStreamAction.Pause; + IsPaused = false; } - public void FadeAndStop(float targetVolume, float fadeTime, Closeproc closeProc, int streamID) + public override void Stop() { - _Closeproc = closeProc; - _StreamID = streamID; - Fade(targetVolume, fadeTime); - _AfterFadeAction = EStreamAction.Stop; + IsPaused = true; + Position = 0f; } - public void Play() - { - Paused = false; - _AfterFadeAction = EStreamAction.Nothing; - AL.SourcePlay(_Source); - } - - public void Stop() - { - AL.SourceStop(_Source); - Skip(0f); - } - - public bool Loop - { - get { return _SetLoop; } - set { _SetLoop = value; } - } - - public int Open(string fileName) + public override bool Open(bool prescan) { if (_FileOpened) - return -1; + return false; - if (!File.Exists(fileName)) - return -1; + Length = 0f; - _Decoder = new CAudioDecoderFFmpeg(); - _Decoder.Init(); + if (!File.Exists(_Medium)) + { + Dispose(); + return false; + } + bool ok = true; try { _Source = AL.GenSource(); _Buffers = new int[_BufferCount]; for (int i = 0; i < _BufferCount; i++) + { _Buffers[i] = AL.GenBuffer(); - - _State = 0; - //AL.SourceQueueBuffers(_source, _buffers.Length, _buffers); + ok = ok && _Buffers[i] != 0; + } } catch (Exception) { - _Initialized = false; + ok = false; + } + if (!ok) + { + Dispose(); CLog.LogError("Error Init OpenAL Playback"); - return -1; + return false; } - _Decoder.Open(fileName); - _Duration = _Decoder.GetLength(); + _Decoder = new CAudioDecoderFFmpeg(); + if (!_Decoder.Open(_Medium)) + { + Dispose(); + CLog.LogError("Error opening audio file: " + _Medium); + return false; + } _Format = _Decoder.GetFormatInfo(); + if (_Format.SamplesPerSecond == 0) + { + Dispose(); + CLog.LogError("Error Init OpenAL Playback (samples=0)"); + return false; + } + + Length = _Decoder.GetLength(); + _ByteCount = 2 * _Format.ChannelCount; _BytesPerSecond = _Format.SamplesPerSecond * _ByteCount; + _CurrentTime = 0f; + _TimeCode = 0f; _Timer.Reset(); + _Data = new CRingBuffer(_Bufsize); + _NoMoreData = false; + _SampleBuf = new byte[(int)CConfig.AudioBufferSize]; + //From now on closing the driver and the decoder is handled by the thread ONLY! - var stream = new SAudioStreams(0) {Handle = _Buffers[0]}; - - if (stream.Handle != 0) - { - _FileOpened = true; - _Data = new CRingBuffer(_Bufsize); - _NoMoreData = false; - _DecoderThread = new Thread(_Execute) {Priority = ThreadPriority.Normal, Name = Path.GetFileName(fileName)}; - _DecoderThread.Start(); - - return stream.Handle; - } - _Initialized = true; - return -1; - } - - public bool Skip(float time) - { - lock (_MutexSyncSignals) - { - _SetStart = time; - _SetSkip = true; - } - _EventDecode.Set(); + _DecoderThread = new Thread(_Execute) {Priority = ThreadPriority.Normal, Name = Path.GetFileName(_Medium)}; + _DecoderThread.Start(); + _FileOpened = true; return true; } @@ -281,10 +223,7 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL private void _DoSkip() { _Decoder.SetPosition(_Start); - _CurrentTime = _Start; _TimeCode = _Start; - _Timer.Reset(); - _Waiting = false; lock (_MutexData) { @@ -297,30 +236,25 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL { while (!_Terminated) { - _Waiting = false; - if (_EventDecode.WaitOne(1) || !_Waiting) + lock (_MutexSyncSignals) { - if (_Skip) + if (_SetSkip) { - _DoSkip(); - _Skip = false; - } - - _DoDecode(); - } - if (!_Terminated) - { - lock (_MutexSyncSignals) - { - if (_SetSkip) - _Skip = true; - + _Skip = true; _SetSkip = false; - - _Start = _SetStart; - _Loop = _SetLoop; } + + _Start = _SetStart; } + + if (_Skip) + { + _DoSkip(); + _Skip = false; + } + + _DoDecode(); + _EventDecode.WaitOne(); } _DoFree(); @@ -328,40 +262,25 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL private void _DoDecode() { - if (!_FileOpened) - return; - - if (_Paused) - return; - - if (_Terminated) + if (_Paused || _Terminated || _NoMoreData) return; float timecode; byte[] buffer; - bool doIt = false; lock (_MutexData) { - if (_Bufsize - 10000L > _Data.BytesNotRead) - doIt = true; + if (_Data.BytesNotRead > _BeginRefill) + return; } - if (!doIt) - return; - _Decoder.Decode(out buffer, out timecode); if (buffer == null) { if (_Loop) { - lock (_MutexSyncSignals) - { - _CurrentTime = 0f; - _Start = 0f; - } - + _Start = 0f; _DoSkip(); } else @@ -373,118 +292,101 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL { _Data.Write(buffer); _TimeCode = timecode; + if (_Data.BytesNotRead < _BeginRefill) + _EventDecode.Set(); } } private void _DoFree() { - if (_Initialized) + if (_Source != 0) { - lock (_CloseMutex) + AL.SourceStop(_Source); + if (_Buffers != null) { - Stop(); - AL.DeleteSource(_Source); AL.DeleteBuffers(_Buffers); + _Buffers = null; } - _Decoder.Close(); + AL.DeleteSource(_Source); + _Source = 0; } - - _Closeproc(_StreamID); + if (_DecoderThread != null) + { + if (Thread.CurrentThread.ManagedThreadId != _DecoderThread.ManagedThreadId) + throw new Exception("Another thread should never free the decoder thread!"); + _DecoderThread = null; + } + if (_Decoder != null) + { + _Decoder.Close(); + _Decoder = null; + } + _SampleBuf = null; if (_EventDecode != null) { - _EventDecode.Dispose(); + _EventDecode.Close(); _EventDecode = null; } + if (_CloseStreamListener != null) + _CloseStreamListener.OnCloseStream(this); } #endregion Threading - private void _Update() + public override void Update() { - if (_Fading != null) - { - bool finished; - _Volume = _Fading.GetValue(out finished); - if (finished) - { - switch (_AfterFadeAction) - { - case EStreamAction.Close: - case EStreamAction.Stop: - _Terminated = true; - break; - case EStreamAction.Pause: - Paused = true; - break; - } - _Fading = null; - } - } - } + base.Update(); - public void UploadData() - { - _Update(); - - if (_Paused) + if (_Paused || _Terminated || IsFinished) return; int queuedCount; - bool doit = true; + bool useQueuedBuffer = false; AL.GetSource(_Source, ALGetSourcei.BuffersQueued, out queuedCount); - int processedCount = _BufferCount; + int freeBufferCt = _BufferCount; if (queuedCount > 0) { - AL.GetSource(_Source, ALGetSourcei.BuffersProcessed, out processedCount); - doit = false; + AL.GetSource(_Source, ALGetSourcei.BuffersProcessed, out freeBufferCt); + useQueuedBuffer = true; //Console.WriteLine("Buffers Processed on Stream " + _Source + " = " + processedCount); - if (processedCount < 1) + if (freeBufferCt < 1) return; } - var buf = new byte[_BufferSize]; - lock (_MutexData) { - while (processedCount > 0) + queuedCount = 0; + for (int j = 0; j < freeBufferCt; j++) { - if (_Data.BytesNotRead >= buf.Length) + if (_Data.BytesNotRead < _SampleBuf.Length && !(_NoMoreData && _Data.BytesNotRead > 0)) + break; + _Data.Read(_SampleBuf); + + float volume = Volume * VolumeMax; + //We want to scale all values. No matter how many channels we have (_ByteCount=2 or 4) we have short values + //So just process 2 bytes a time + for (int i = 0; i < _SampleBuf.Length; i += 2) { - _Data.Read(buf); - - //We want to scale all values. No matter how many channels we have (_ByteCount=2 or 4) we have short values - //So just process 2 bytes a time - for (int i = 0; i < buf.Length; i += 2) - { - byte[] b = BitConverter.GetBytes((Int16)(BitConverter.ToInt16(buf, i) * _Volume * _VolumeMax)); - buf[i] = b[0]; - buf[i + 1] = b[1]; - } - - int buffer; - if (!doit) - buffer = AL.SourceUnqueueBuffer(_Source); - else - { - buffer = _Buffers[queuedCount]; - queuedCount++; - } - - if (buffer != 0) - { - ALFormat alFormat = (_Format.ChannelCount == 2) ? ALFormat.Stereo16 : ALFormat.Mono16; - AL.BufferData(buffer, alFormat, buf, buf.Length, _Format.SamplesPerSecond); - AL.SourceQueueBuffer(_Source, buffer); - } + byte[] b = BitConverter.GetBytes((Int16)(BitConverter.ToInt16(_SampleBuf, i) * volume)); + _SampleBuf[i] = b[0]; + _SampleBuf[i + 1] = b[1]; } - processedCount--; + + int buffer = useQueuedBuffer ? AL.SourceUnqueueBuffer(_Source) : _Buffers[queuedCount]; + + + if (buffer != 0) + { + ALFormat alFormat = (_Format.ChannelCount == 2) ? ALFormat.Stereo16 : ALFormat.Mono16; + AL.BufferData(buffer, alFormat, _SampleBuf, _SampleBuf.Length, _Format.SamplesPerSecond); + AL.SourceQueueBuffer(_Source, buffer); + } + queuedCount++; } - AL.GetSource(_Source, ALGetSourcei.SourceState, out _State); - if ((ALSourceState)_State != ALSourceState.Playing) - AL.SourcePlay(_Source); } - _CurrentTime = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - 0.1f; + float latency = CConfig.AudioLatency / 1000f + queuedCount * _SampleBuf.Length / _BytesPerSecond + 0.1f; + _CurrentTime = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - latency; _Timer.Restart(); } } diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioPlay.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioPlay.cs index 96e6d3d7..e96eeeab 100644 --- a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioPlay.cs +++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioPlay.cs @@ -15,17 +15,11 @@ // along with Vocaluxe. If not, see . #endregion -using System.Collections.Generic; - namespace Vocaluxe.Lib.Sound.Playback.PortAudio { - class CPortAudioPlay : IPlayback + class CPortAudioPlay : CPlaybackBase { - private volatile bool _Initialized; - private readonly List _Streams = new List(); - private int _NextID; - - public bool Init() + public override bool Init() { if (_Initialized) return false; @@ -35,311 +29,9 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio return true; } - public void Close() + protected override IAudioStream _CreateStream(int id, string media, bool loop) { - _Initialized = false; //Set this first, so threads don't call closeproc anymore - List streams = new List(); - //Get all streams but do not modify list without holding the lock - //Calling dispose from within the lock may deadlock in closeproc (if a thread is already there) - lock (_Streams) - { - streams.AddRange(_Streams); - _Streams.Clear(); - } - foreach (CPortAudioStream stream in streams) - stream.Dispose(); - } - - public void CloseAll() - { - lock (_Streams) - { - foreach (CPortAudioStream stream in _Streams) - stream.Close(); - _Streams.Clear(); - } - } - - public void SetGlobalVolume(float volume) - { - if (_Initialized) - { - //Bass.BASS_SetVolume(Volume / 100f); - } - } - - public int GetStreamCount() - { - if (!_Initialized) - return 0; - - lock (_Streams) - { - return _Streams.Count; - } - } - - public void Update() {} - - #region Stream Handling - public int Load(string media) - { - return Load(media, false); - } - - public int Load(string media, bool prescan) - { - //Note: We ignore prescan here as the stream does it always - - var stream = new CPortAudioStream(_NextID++, _CloseProc); - - if (stream.Open(media)) - { - lock (_Streams) - { - _Streams.Add(stream); - return stream.ID; - } - } - return 0; - } - - public void Close(int stream) - { - if (!_Initialized) - return; - lock (_Streams) - { - int index = _GetStreamIndex(stream); - if (index >= 0) - { - _Streams[index].Close(); - _Streams.RemoveAt(index); - } - } - } - - public void Play(int stream) - { - Play(stream, false); - } - - public void Play(int stream, bool loop) - { - if (!_Initialized) - return; - lock (_Streams) - { - int index = _GetStreamIndex(stream); - if (index >= 0) - { - _Streams[index].Loop = loop; - _Streams[index].Play(); - } - } - } - - public void Pause(int stream) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].Paused = true; - } - } - - public void Stop(int stream) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].Stop(); - } - } - - public void Fade(int stream, float targetVolume, float seconds) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].Fade(targetVolume, seconds); - } - } - - public void FadeAndPause(int stream, float targetVolume, float seconds) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].FadeAndPause(targetVolume, seconds); - } - } - - public void FadeAndClose(int stream, float targetVolume, float seconds) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].FadeAndClose(targetVolume, seconds); - } - } - - public void FadeAndStop(int stream, float targetVolume, float seconds) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].FadeAndStop(targetVolume, seconds); - } - } - - public void SetStreamVolume(int stream, float volume) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].Volume = volume; - } - } - - public void SetStreamVolumeMax(int stream, float volume) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].VolumeMax = volume; - } - } - - public float GetLength(int stream) - { - if (!_Initialized) - return 0f; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - return _Streams[_GetStreamIndex(stream)].Length; - } - return 0f; - } - - public float GetPosition(int stream) - { - if (!_Initialized) - return 0f; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - return _Streams[_GetStreamIndex(stream)].Position; - } - - return 0f; - } - - public bool IsPlaying(int stream) - { - if (!_Initialized) - return false; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - return !_Streams[_GetStreamIndex(stream)].Paused && !_Streams[_GetStreamIndex(stream)].Finished; - } - return false; - } - - public bool IsPaused(int stream) - { - if (!_Initialized) - return false; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - return _Streams[_GetStreamIndex(stream)].Paused; - } - return false; - } - - public bool IsFinished(int stream) - { - if (!_Initialized) - return true; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - return _Streams[_GetStreamIndex(stream)].Finished; - } - return true; - } - - public void SetPosition(int stream, float position) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams[_GetStreamIndex(stream)].Skip(position); - } - } - #endregion Stream Handling - - /// - /// Returns true if strem with given id is found - /// MUST hold _Stream-lock - /// - /// Stream id - /// - private bool _AlreadyAdded(int stream) - { - return _GetStreamIndex(stream) >= 0; - } - - /// - /// Returns the index of the stream with the given id - /// MUST hold _Stream-lock - /// - /// Stream id - /// - private int _GetStreamIndex(int stream) - { - for (int i = 0; i < _Streams.Count; i++) - { - if (_Streams[i].ID == stream) - return i; - } - return -1; - } - - /// - /// Removes the stream with the given handle - /// - /// Stream handle - private void _CloseProc(int stream) - { - if (!_Initialized) - return; - lock (_Streams) - { - if (_AlreadyAdded(stream)) - _Streams.RemoveAt(_GetStreamIndex(stream)); - } + return new CPortAudioStream(id, media, loop); } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs index 62123b1f..a63c0272 100644 --- a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs @@ -21,29 +21,20 @@ using System.Runtime.InteropServices; using System.Threading; using Vocaluxe.Base; using Vocaluxe.Lib.Sound.Playback.Decoder; -using VocaluxeLib; namespace Vocaluxe.Lib.Sound.Playback.PortAudio { - class CPortAudioStream : IDisposable + class CPortAudioStream : CAudioStreamBase { private const int _Bufsize = 1000000; - private const int _Beginrefill = 800000; + private const int _BeginRefill = 800000; + + private readonly CSyncTimer _SyncTimer = new CSyncTimer(0f, 1f, 0.02f); + private int _ByteCount; - public readonly int ID; - private readonly Closeproc _Closeproc; private CPortAudioHandle _PaHandle; - - private readonly CSyncTimer _SyncTimer; - private int _ByteCount = 4; - private float _Volume = 1f; - private float _VolumeMax = 1f; - - private EStreamAction _AfterFadeAction; - private CFading _Fading; - - private static PortAudioSharp.PortAudio.PaHostApiInfo _ApiInfo; - private static PortAudioSharp.PortAudio.PaDeviceInfo _OutputDeviceInfo; + private PortAudioSharp.PortAudio.PaHostApiInfo _ApiInfo; + private PortAudioSharp.PortAudio.PaDeviceInfo _OutputDeviceInfo; private IntPtr _Stream = IntPtr.Zero; private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _PaStreamCallback; @@ -56,9 +47,6 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio private bool _Skip; - private bool _Loop; - private float _Duration; - private float _CurrentTime; private volatile float _TimeCode; private volatile bool _Paused; @@ -66,7 +54,6 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio private CRingBuffer _Data; private volatile float _SetStart; private float _Start; - private volatile bool _SetLoop; private volatile bool _SetSkip; private volatile bool _Terminated; @@ -77,83 +64,35 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio private readonly Object _LockData = new Object(); private readonly Object _LockSyncSignals = new Object(); - public CPortAudioStream(int id, Closeproc closeproc) - { - ID = id; - _Closeproc = closeproc; - _SyncTimer = new CSyncTimer(0f, 1f, 0.02f); - } - - ~CPortAudioStream() - { - _Dispose(false); - if (_DecoderThread != null) - _DecoderThread.Join(); - } - - public void Close() - { - _Terminated = true; - _EventDecode.Set(); - } - - public float Length - { - get - { - if (_FileOpened) - return _Duration; - - return 0f; - } - } - - public bool Finished + public override float Position { get { lock (_LockData) { - return _NoMoreData && _Data.BytesNotRead == 0 && _SyncTimer.Time >= _Duration; - } - } - } - - public float Volume - { - get { return _Volume * 100f; } - set - { - value.Clamp(0f, 100f); - _Volume = value / 100f; - } - } - - public float VolumeMax - { - get { return _VolumeMax * 100f; } - set - { - value.Clamp(0f, 100f); - _VolumeMax = value / 100f; - } - } - - public float Position - { - get - { - lock (_LockData) - { - if (Finished) + // Decoder may return wrong timestamps. This is why we use the synctimer + // If you change this do exessive testing for monoton timestamps espacially for ogg files + float time = Math.Max(0f, _SyncTimer.Time); + if (time > Length) + { _SyncTimer.Pause(); - //Decoder may return wrong timestamps. If you change this do exessive testing for monoton timestamps espacially for ogg files - return _SyncTimer.Time; + time = Length; + } + return time; + } + } + set + { + lock (_LockSyncSignals) + { + _SetStart = value; + _SetSkip = true; + _EventDecode.Set(); } } } - public bool Paused + public override bool IsPaused { get { return _Paused; } set @@ -172,57 +111,31 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio } } - public void Fade(float targetVolume, float fadeTime) + public override bool IsFinished { - targetVolume = targetVolume.Clamp(0f, 100f); - _Fading = new CFading(_Volume, targetVolume / 100f, fadeTime); - _AfterFadeAction = EStreamAction.Nothing; + get + { + lock (_LockData) + { + return _NoMoreData && _Data.BytesNotRead == 0 && _SyncTimer.Time >= Length; + } + } } - public void FadeAndPause(float targetVolume, float fadeTime) - { - Fade(targetVolume, fadeTime); - _AfterFadeAction = EStreamAction.Pause; - } + public CPortAudioStream(int id, string medium, bool loop) : base(id, medium, loop) {} - public void FadeAndClose(float targetVolume, float fadeTime) - { - Fade(targetVolume, fadeTime); - _AfterFadeAction = EStreamAction.Close; - } - - public void FadeAndStop(float targetVolume, float fadeTime) - { - Fade(targetVolume, fadeTime); - _AfterFadeAction = EStreamAction.Stop; - } - - public void Play() - { - Paused = false; - _AfterFadeAction = EStreamAction.Nothing; - _PaHandle.CheckError("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Stream)); - } - - public void Stop() - { - _PaHandle.CheckError("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Stream)); - Skip(0f); - } - - public bool Loop - { - get { return _SetLoop; } - set { _SetLoop = value; } - } - - public bool Open(string fileName) + public override bool Open(bool prescan) { if (_FileOpened) return false; - if (!File.Exists(fileName)) + Length = 0f; + + if (!File.Exists(_Medium)) + { + Dispose(); return false; + } try { @@ -238,36 +151,32 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio } catch (Exception) { - if (_PaHandle != null) - { - _PaHandle.Close(); - _PaHandle = null; - } + Dispose(); CLog.LogError("Error Init PortAudio Playback"); return false; } _Decoder = new CAudioDecoderFFmpeg(); - _Decoder.Init(); - _Decoder.Open(fileName); + if (!_Decoder.Open(_Medium)) + { + Dispose(); + CLog.LogError("Error opening audio file: " + _Medium); + return false; + } SFormatInfo format = _Decoder.GetFormatInfo(); if (format.SamplesPerSecond == 0) { - _PaHandle.Close(); - _PaHandle = null; - _Decoder.Close(); - _Decoder = null; + Dispose(); CLog.LogError("Error Init PortAudio Playback (samples=0)"); return false; } - _Duration = _Decoder.GetLength(); + Length = _Decoder.GetLength(); _ByteCount = 2 * format.ChannelCount; _BytesPerSecond = format.SamplesPerSecond * _ByteCount; - _CurrentTime = 0f; _SyncTimer.Pause(); - _SyncTimer.Time = _CurrentTime; + _SyncTimer.Time = 0f; PortAudioSharp.PortAudio.PaStreamParameters? outputParams = new PortAudioSharp.PortAudio.PaStreamParameters { @@ -282,15 +191,12 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio out _Stream, ref outputParams, format.SamplesPerSecond, - (uint)CConfig.AudioBufferSize, + (uint)CConfig.AudioBufferSize / 2, PortAudioSharp.PortAudio.PaStreamFlags.paNoFlag, _PaStreamCallback, IntPtr.Zero) || _Stream == IntPtr.Zero) { - _PaHandle.Close(); - _PaHandle = null; - _Decoder.Close(); - _Decoder = null; + Dispose(); return false; } @@ -302,33 +208,52 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio _FileOpened = true; _Data = new CRingBuffer(_Bufsize); _NoMoreData = false; - _DecoderThread = new Thread(_Execute) {Priority = ThreadPriority.Normal, Name = Path.GetFileName(fileName)}; + _DecoderThread = new Thread(_Execute) {Priority = ThreadPriority.Normal, Name = Path.GetFileName(_Medium)}; _DecoderThread.Start(); return true; } - public void Skip(float time) + protected override void _Dispose(bool disposing) { - lock (_LockSyncSignals) + base._Dispose(disposing); + if (!_Terminated) { - _SetStart = time; - _SetSkip = true; - _EventDecode.Set(); + _Terminated = true; + if (_DecoderThread != null) + _EventDecode.Set(); + else + _DoFree(); } } + public override void Play() + { + if (!_FileOpened || _Terminated || !IsPaused) + return; + IsPaused = false; + _PaHandle.CheckError("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Stream)); + } + + public override void Stop() + { + if (!_FileOpened || _Terminated) + return; + _Paused = true; + _PaHandle.CheckError("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Stream)); + Position = 0f; + } + #region Threading private void _DoSkip() { + _Decoder.SetPosition(_Start); lock (_LockData) { - _Decoder.SetPosition(_Start); - _CurrentTime = _Start; + _SyncTimer.Time = _Start; _TimeCode = _Start; _Data.Reset(); _NoMoreData = false; - _SyncTimer.Time = _Start; } } @@ -345,7 +270,6 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio } _Start = _SetStart; - _Loop = _SetLoop; } if (_Skip) @@ -355,7 +279,6 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio } _DoDecode(); - _Update(); _EventDecode.WaitOne(); } @@ -364,37 +287,25 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio private void _DoDecode() { - if (_Paused) - return; - - if (_Terminated) + if (_Paused || _Terminated || _NoMoreData) return; float timecode; byte[] buffer; - bool doIt = false; lock (_LockData) { - if (!_Skip && _Beginrefill > _Data.BytesNotRead) - doIt = true; + if (_Data.BytesNotRead > _BeginRefill) + return; } - if (!doIt) - return; - _Decoder.Decode(out buffer, out timecode); if (buffer == null) { if (_Loop) { - lock (_LockSyncSignals) - { - _CurrentTime = 0f; - _Start = 0f; - } - + _Start = 0f; _DoSkip(); } else @@ -406,20 +317,38 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio { _Data.Write(buffer); _TimeCode = timecode; - if (_Data.BytesNotRead < _Beginrefill) + if (_Data.BytesNotRead < _BeginRefill) _EventDecode.Set(); } } private void _DoFree() { - _PaHandle.CloseStream(_Stream); - _PaHandle.Close(); - _PaHandle = null; - _Decoder.Close(); - _Decoder = null; - - _Closeproc(ID); + if (_PaHandle != null) + { + if (_Stream != IntPtr.Zero) + _PaHandle.CloseStream(_Stream); + _PaHandle.Close(); + _PaHandle = null; + } + if (_DecoderThread != null) + { + if (Thread.CurrentThread.ManagedThreadId != _DecoderThread.ManagedThreadId) + throw new Exception("Another thread should never free the decoder thread!"); + _DecoderThread = null; + } + if (_Decoder != null) + { + _Decoder.Close(); + _Decoder = null; + } + if (_EventDecode != null) + { + _EventDecode.Close(); + _EventDecode = null; + } + if (_CloseStreamListener != null) + _CloseStreamListener.OnCloseStream(this); } #endregion Threading @@ -449,27 +378,27 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio lock (_LockData) { - if (_NoMoreData || _Data.BytesNotRead >= buf.Length) + int dataLen = _Data.BytesNotRead; + if ((_NoMoreData && dataLen > 0) || dataLen >= buf.Length) { + dataLen = Math.Min(dataLen, buf.Length); _Data.Read(buf); //We want to scale all values. No matter how many channels we have (_ByteCount=2 or 4) we have short values //So just process 2 bytes a time - for (int i = 0; i < buf.Length; i += 2) + float volume = Volume * VolumeMax; + for (int i = 0; i < dataLen; i += 2) { - byte[] b = BitConverter.GetBytes((Int16)(BitConverter.ToInt16(buf, i) * _Volume * _VolumeMax)); + byte[] b = BitConverter.GetBytes((Int16)(BitConverter.ToInt16(buf, i) * volume)); buf[i] = b[0]; buf[i + 1] = b[1]; } + float latency = buf.Length / _BytesPerSecond + _Latency; + float time = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - latency; + _SyncTimer.Update(time); } - if (_Data.BytesNotRead < _Beginrefill && !_NoMoreData) + if (_Data.BytesNotRead < _BeginRefill && !_NoMoreData) _EventDecode.Set(); - - float latency = buf.Length / _BytesPerSecond + _Latency; - float time = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - latency; - - if (!_NoMoreData) - _CurrentTime = _SyncTimer.Update(time); } try @@ -484,54 +413,5 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio return PortAudioSharp.PortAudio.PaStreamCallbackResult.paContinue; } #endregion Callbacks - - private void _Update() - { - if (_Fading != null) - { - bool finished; - _Volume = _Fading.GetValue(out finished); - if (finished) - { - switch (_AfterFadeAction) - { - case EStreamAction.Close: - _Terminated = true; - break; - case EStreamAction.Stop: - Stop(); - break; - case EStreamAction.Pause: - Paused = true; - break; - } - _Fading = null; - } - } - } - - public void Dispose() - { - _Dispose(true); - GC.SuppressFinalize(this); - } - - private void _Dispose(bool disposing) - { - Close(); - if (disposing) - { - if (_DecoderThread != null) - { - _DecoderThread.Join(); - _DecoderThread = null; - } - if (_EventDecode != null) - { - _EventDecode.Close(); - _EventDecode = null; - } - } - } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CSyncTimer.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CSyncTimer.cs index 1b4eb0cc..887c37cf 100644 --- a/Vocaluxe/Lib/Sound/Playback/PortAudio/CSyncTimer.cs +++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CSyncTimer.cs @@ -65,8 +65,7 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio float diff = et - dt; if (Math.Abs(diff) > 0.05f) { - _Timer.Reset(); - _Timer.Start(); + _Timer.Restart(); _SetValue = dt + diff; dt = _SetValue; //Console.WriteLine("DRIFTED!!! " + diff.ToString()); diff --git a/Vocaluxe/Lib/Sound/Record/DirectSound/CDirectSoundRecord.cs b/Vocaluxe/Lib/Sound/Record/DirectSound/CDirectSoundRecord.cs index 61b54b23..43e501bf 100644 --- a/Vocaluxe/Lib/Sound/Record/DirectSound/CDirectSoundRecord.cs +++ b/Vocaluxe/Lib/Sound/Record/DirectSound/CDirectSoundRecord.cs @@ -16,10 +16,8 @@ #endregion using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using SlimDX.DirectSound; -using Vocaluxe.Base; namespace Vocaluxe.Lib.Sound.Record.DirectSound { diff --git a/Vocaluxe/Screens/CPopupScreenVolumeControl.cs b/Vocaluxe/Screens/CPopupScreenVolumeControl.cs index 42b132bb..4bfde64c 100644 --- a/Vocaluxe/Screens/CPopupScreenVolumeControl.cs +++ b/Vocaluxe/Screens/CPopupScreenVolumeControl.cs @@ -107,49 +107,49 @@ namespace Vocaluxe.Screens private void _SaveConfig() { + int volume = _SelectSlides[_SelectSlideVolume].Selection * 5; switch (CGraphics.CurrentScreen) { case EScreens.ScreenSong: if (CSongs.IsInCategory) - CConfig.PreviewMusicVolume = _SelectSlides[_SelectSlideVolume].Selection * 5; + CConfig.PreviewMusicVolume = volume; else - { - CConfig.BackgroundMusicVolume = _SelectSlides[_SelectSlideVolume].Selection * 5; - CBackgroundMusic.SetVolume(CConfig.BackgroundMusicVolume); - } + CConfig.BackgroundMusicVolume = volume; break; case EScreens.ScreenSing: - CConfig.GameMusicVolume = _SelectSlides[_SelectSlideVolume].Selection * 5; + CConfig.GameMusicVolume = volume; break; default: - CConfig.BackgroundMusicVolume = _SelectSlides[_SelectSlideVolume].Selection * 5; - CBackgroundMusic.SetVolume(CConfig.BackgroundMusicVolume); + CConfig.BackgroundMusicVolume = volume; break; } CConfig.SaveConfig(); + CSound.SetGlobalVolume(volume); } private void _UpdateSlides() { + int volume; switch (CGraphics.CurrentScreen) { case EScreens.ScreenSong: if (CSongs.IsInCategory) - _SelectSlides[_SelectSlideVolume].Selection = CConfig.PreviewMusicVolume / 5; + volume = CConfig.PreviewMusicVolume; else - _SelectSlides[_SelectSlideVolume].Selection = CConfig.BackgroundMusicVolume / 5; + volume = CConfig.BackgroundMusicVolume; break; case EScreens.ScreenSing: - _SelectSlides[_SelectSlideVolume].Selection = CConfig.GameMusicVolume / 5; + volume = CConfig.GameMusicVolume; break; default: - _SelectSlides[_SelectSlideVolume].Selection = CConfig.BackgroundMusicVolume / 5; + volume = CConfig.BackgroundMusicVolume; break; } + _SelectSlides[_SelectSlideVolume].Selection = volume / 5; } } } \ No newline at end of file diff --git a/Vocaluxe/Screens/CScreenOptionsSound.cs b/Vocaluxe/Screens/CScreenOptionsSound.cs index 8f087207..502f4c28 100644 --- a/Vocaluxe/Screens/CScreenOptionsSound.cs +++ b/Vocaluxe/Screens/CScreenOptionsSound.cs @@ -156,7 +156,7 @@ namespace Vocaluxe.Screens CConfig.SaveConfig(); CBackgroundMusic.SetMusicSource(CConfig.BackgroundMusicSource); - CBackgroundMusic.SetVolume(CConfig.BackgroundMusicVolume); + CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume); if (CConfig.BackgroundMusic == EOffOn.TR_CONFIG_ON) CBackgroundMusic.Play(); else diff --git a/Vocaluxe/Screens/CScreenSing.cs b/Vocaluxe/Screens/CScreenSing.cs index c7956c9c..42cdab5d 100644 --- a/Vocaluxe/Screens/CScreenSing.cs +++ b/Vocaluxe/Screens/CScreenSing.cs @@ -258,11 +258,8 @@ namespace Vocaluxe.Screens case Keys.PageUp: if (keyEvent.ModShift) { - CConfig.GameMusicVolume = CConfig.GameMusicVolume + 5; - if (CConfig.GameMusicVolume > 100) - CConfig.GameMusicVolume = 100; - CConfig.SaveConfig(); - ApplyVolume(); + CConfig.GameMusicVolume += 5; + CSound.SetGlobalVolume(CConfig.GameMusicVolume); } break; @@ -270,11 +267,8 @@ namespace Vocaluxe.Screens case Keys.PageDown: if (keyEvent.ModShift) { - CConfig.GameMusicVolume = CConfig.GameMusicVolume - 5; - if (CConfig.GameMusicVolume < 0) - CConfig.GameMusicVolume = 0; - CConfig.SaveConfig(); - ApplyVolume(); + CConfig.GameMusicVolume -= 5; + CSound.SetGlobalVolume(CConfig.GameMusicVolume); } break; } @@ -441,6 +435,7 @@ namespace Vocaluxe.Screens CBackgroundMusic.Disabled = true; _CloseSong(); + CSound.SetGlobalVolume(CConfig.GameMusicVolume); } public override void OnShowFinish() @@ -449,7 +444,6 @@ namespace Vocaluxe.Screens CGame.Start(); _LoadNextSong(); - CBackgroundMusic.Disabled = true; } public override bool Draw() @@ -540,22 +534,20 @@ namespace Vocaluxe.Screens public override void OnClose() { base.OnClose(); - CBackgroundMusic.Disabled = false; + _CloseSong(); if (_Webcam) CWebcam.Stop(); - _CloseSong(); - } - - public override void ApplyVolume() - { - if (_CurrentStream > -1) - CSound.SetStreamVolumeMax(_CurrentStream, CConfig.GameMusicVolume); + CBackgroundMusic.Disabled = false; + CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume); } private void _CloseSong() { if (_CurrentStream > -1) + { CSound.FadeAndClose(_CurrentStream, 0f, 0.5f); + _CurrentStream = -1; + } CRecord.Stop(); if (_CurrentVideo != -1) { @@ -606,7 +598,6 @@ namespace Vocaluxe.Screens _Texts[_TextSongName].Text = songname; _CurrentStream = CSound.Load(song.GetMP3(), true); - CSound.SetStreamVolumeMax(_CurrentStream, CConfig.GameMusicVolume); CSound.SetStreamVolume(_CurrentStream, _Volume); CSound.SetPosition(_CurrentStream, song.Start); _CurrentTime = song.Start; diff --git a/Vocaluxe/Screens/CScreenSong.cs b/Vocaluxe/Screens/CScreenSong.cs index 50043dc4..996f427c 100644 --- a/Vocaluxe/Screens/CScreenSong.cs +++ b/Vocaluxe/Screens/CScreenSong.cs @@ -148,8 +148,6 @@ namespace Vocaluxe.Screens _Playlists[_Playlist].Init(); _AvailableGameModes.Clear(); - - ApplyVolume(); } public override bool HandleInput(SKeyEvent keyEvent) @@ -365,21 +363,23 @@ namespace Vocaluxe.Screens } } + int volumeDiff = 0; if (keyEvent.ModShift && (keyEvent.Key == Keys.Add || keyEvent.Key == Keys.PageUp)) - { - CConfig.PreviewMusicVolume = CConfig.PreviewMusicVolume + 5; - if (CConfig.PreviewMusicVolume > 100) - CConfig.PreviewMusicVolume = 100; - CConfig.SaveConfig(); - ApplyVolume(); - } + volumeDiff = 5; else if (keyEvent.ModShift && (keyEvent.Key == Keys.Subtract || keyEvent.Key == Keys.PageDown)) + volumeDiff = -5; + if (volumeDiff != 0) { - CConfig.PreviewMusicVolume = CConfig.PreviewMusicVolume - 5; - if (CConfig.PreviewMusicVolume < 0) - CConfig.PreviewMusicVolume = 0; - CConfig.SaveConfig(); - ApplyVolume(); + if (CSongs.IsInCategory) + { + CConfig.PreviewMusicVolume += volumeDiff; + CSound.SetGlobalVolume(CConfig.PreviewMusicVolume); + } + else + { + CConfig.BackgroundMusicVolume += volumeDiff; + CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume); + } } return true; @@ -643,6 +643,8 @@ namespace Vocaluxe.Screens _Playlists[_Playlist].DragAndDropSongID = -1; UpdateGame(); + if (CSongs.IsInCategory) + CSound.SetGlobalVolume(CConfig.PreviewMusicVolume); } public override bool UpdateGame() @@ -700,14 +702,10 @@ namespace Vocaluxe.Screens { base.OnClose(); CBackgroundMusic.Disabled = false; + CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume); _SongMenus[_SongMenu].OnHide(); } - public override void ApplyVolume() - { - _SongMenus[_SongMenu].ApplyVolume(CConfig.PreviewMusicVolume); - } - private void _HandlePartySongSelection(int songNr) { if ((CSongs.Category >= 0) && (songNr >= 0)) diff --git a/Vocaluxe/Vocaluxe.csproj b/Vocaluxe/Vocaluxe.csproj index eea26470..8cd6f665 100644 --- a/Vocaluxe/Vocaluxe.csproj +++ b/Vocaluxe/Vocaluxe.csproj @@ -219,8 +219,11 @@ + + + diff --git a/VocaluxeLib/IBasic.cs b/VocaluxeLib/IBasic.cs index 433e5fc8..3ec5204c 100644 --- a/VocaluxeLib/IBasic.cs +++ b/VocaluxeLib/IBasic.cs @@ -264,10 +264,9 @@ namespace VocaluxeLib bool IsFinished(int soundStream); float GetPosition(int soundStream); float GetLength(int soundStream); - void FadeAndStop(int soundStream, float targetVolume, float duration); + void FadeAndClose(int soundStream, float targetVolume, float duration); void SetStreamVolume(int soundStream, float volume); - void SetStreamVolumeMax(int soundStream, float maxVolume); } public interface ICover diff --git a/VocaluxeLib/Menu/SongMenu/CSongMenu.cs b/VocaluxeLib/Menu/SongMenu/CSongMenu.cs index 964a2ab5..84e729b6 100644 --- a/VocaluxeLib/Menu/SongMenu/CSongMenu.cs +++ b/VocaluxeLib/Menu/SongMenu/CSongMenu.cs @@ -81,11 +81,6 @@ namespace VocaluxeLib.Menu.SongMenu _SongMenu.Draw(); } - public void ApplyVolume(float volumeMax) - { - _SongMenu.ApplyVolume(volumeMax); - } - public bool IsActive() { return _SongMenu.IsActive(); diff --git a/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs b/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs index 9cdd19dd..6743c1f8 100644 --- a/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs +++ b/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs @@ -402,7 +402,6 @@ namespace VocaluxeLib.Menu.SongMenu { _PreviewSongStream = -1; _Vidtex = null; - ApplyVolume(CBase.Config.GetPreviewMusicVolume()); } public virtual void OnHide() @@ -423,14 +422,6 @@ namespace VocaluxeLib.Menu.SongMenu CBase.Drawing.DrawTexture(_Vidtex, new SRectF(0, 0, 1280, 720, 0)); } - public virtual void ApplyVolume(float volumeMax) - { - _MaxVolume = volumeMax; - - foreach (int stream in _Streams) - CBase.Sound.SetStreamVolumeMax(stream, _MaxVolume); - } - public virtual bool IsActive() { return _Active; @@ -533,8 +524,6 @@ namespace VocaluxeLib.Menu.SongMenu return; int stream = CBase.Sound.Load(Path.Combine(song.Folder, song.MP3FileName), true); - CBase.Sound.SetStreamVolumeMax(stream, _MaxVolume); - CBase.Sound.SetStreamVolume(stream, 0f); float startposition = song.Preview.StartTime; float length = CBase.Sound.GetLength(stream); @@ -549,7 +538,7 @@ namespace VocaluxeLib.Menu.SongMenu CBase.Sound.SetPosition(stream, startposition); CBase.Sound.Play(stream); - CBase.Sound.SetStreamVolumeMax(stream, CBase.Config.GetPreviewMusicVolume()); + CBase.Sound.SetStreamVolume(stream, 0f); CBase.Sound.Fade(stream, 100f, 3f); _Streams.Add(stream); _PreviewSongStream = stream; @@ -570,7 +559,7 @@ namespace VocaluxeLib.Menu.SongMenu protected void _Reset() { foreach (int stream in _Streams) - CBase.Sound.FadeAndStop(stream, 0f, 0.75f); + CBase.Sound.FadeAndClose(stream, 0f, 0.75f); _Streams.Clear(); CBase.Video.Close(_PreviewVideoStream); diff --git a/VocaluxeLib/Menu/SongMenu/ISongMenu.cs b/VocaluxeLib/Menu/SongMenu/ISongMenu.cs index 8d8e82c2..e1ea8998 100644 --- a/VocaluxeLib/Menu/SongMenu/ISongMenu.cs +++ b/VocaluxeLib/Menu/SongMenu/ISongMenu.cs @@ -29,8 +29,6 @@ namespace VocaluxeLib.Menu.SongMenu void HandleMouse(ref SMouseEvent mouseEvent, SScreenSongOptions songOptions); void Draw(); - void ApplyVolume(float volumeMax); - int GetSelectedSong(); CStatic GetSelectedSongCover(); void SetSelectedSong(int visibleSongNr);