From 9d40020756424ba966d6bd0d8e933aede264b4ee Mon Sep 17 00:00:00 2001 From: Flamefire Date: Fri, 2 May 2014 17:18:47 +0200 Subject: [PATCH 01/13] Fix wrong latency value with PA --- Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs index 1042e0f8..62123b1f 100644 --- a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs @@ -49,6 +49,7 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _PaStreamCallback; private IAudioDecoder _Decoder; private float _BytesPerSecond; + private float _Latency; private bool _NoMoreData; private bool _FileOpened; @@ -293,6 +294,8 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio return false; } + _Latency = CConfig.AudioLatency / 1000f + (float)PortAudioSharp.PortAudio.Pa_GetStreamInfo(_Stream).outputLatency; + //From now on closing the driver and the decoder is handled by the thread ONLY! _Paused = true; @@ -462,7 +465,7 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio if (_Data.BytesNotRead < _Beginrefill && !_NoMoreData) _EventDecode.Set(); - float latency = buf.Length / _BytesPerSecond + CConfig.AudioLatency / 1000f; + float latency = buf.Length / _BytesPerSecond + _Latency; float time = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - latency; if (!_NoMoreData) From 0692fe3c9ba066e29d131a991df1d4ec7a112775 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Fri, 2 May 2014 17:30:34 +0200 Subject: [PATCH 02/13] Make equalizers show something --- VocaluxeLib/Menu/CEqualizer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/VocaluxeLib/Menu/CEqualizer.cs b/VocaluxeLib/Menu/CEqualizer.cs index 58680d90..83bb74c0 100644 --- a/VocaluxeLib/Menu/CEqualizer.cs +++ b/VocaluxeLib/Menu/CEqualizer.cs @@ -218,14 +218,18 @@ namespace VocaluxeLib.Menu if (weights == null || weights.Length == 0 || _Bars == null) return; if (volume < 0.001) + { + for (int i = 0; i < _Bars.Length; i++) + _Bars[i] = 0f; return; + } if (volume > _MaxVolume) _MaxVolume = volume; _MaxBar = 0; float maxVal = -99f; for (int i = 0; i < _Bars.Length; i++) { - if (weights.Length > i) + if (i < weights.Length) { if (_Theme.DrawNegative == EOffOn.TR_CONFIG_OFF && weights[i] < 0) _Bars[i] = 0f; @@ -261,10 +265,12 @@ namespace VocaluxeLib.Menu return; float dx = Rect.W / _Bars.Length; + float scaleVal = (_Bars[_MaxBar] < 0.00001f) ? 0f : 1 / _Bars[_MaxBar]; for (int i = 0; i < _Bars.Length; i++) { - var bar = new SRectF(Rect.X + dx * i, Rect.Y + Rect.H - _Bars[i] * Rect.H, dx - Space, _Bars[i] * Rect.H, Rect.Z); + float value = _Bars[i] * scaleVal; + var bar = new SRectF(Rect.X + dx * i, Rect.Y + Rect.H - value * Rect.H, dx - Space, value * Rect.H, Rect.Z); SColorF color = Color; if (i == _MaxBar) color = MaxColor; From a4b1c3eb865cc97c27807b6fb7b1bf67fa803991 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Mon, 5 May 2014 02:09:37 +0200 Subject: [PATCH 03/13] 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); From bafc79af157e99cac270abdb290f6ee04aa4061b Mon Sep 17 00:00:00 2001 From: Flamefire Date: Tue, 6 May 2014 18:32:47 +0200 Subject: [PATCH 04/13] Fix possible Bug with Gst# not detecting Length Make usages aware, that the length might not be given instantly --- Vocaluxe/Base/CBackgroundMusic.cs | 30 ++++++++++++-- Vocaluxe/Base/CSound.cs | 22 ++++++++-- Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs | 8 ++-- .../CGstreamerSharpAudioStream.cs | 40 +++++++++++-------- .../Sound/Playback/OpenAL/COpenAlStream.cs | 3 +- .../Playback/PortAudio/CPortAudioStream.cs | 4 +- 6 files changed, 77 insertions(+), 30 deletions(-) diff --git a/Vocaluxe/Base/CBackgroundMusic.cs b/Vocaluxe/Base/CBackgroundMusic.cs index b68dab47..b4be306f 100644 --- a/Vocaluxe/Base/CBackgroundMusic.cs +++ b/Vocaluxe/Base/CBackgroundMusic.cs @@ -31,7 +31,6 @@ namespace Vocaluxe.Base private static bool _OwnSongsAvailable; private static EBackgroundMusicSource _MusicSource; - private static float _Volume; private static bool _VideoEnabled; private static CPlaylistElement _CurrentPlaylistElement; //Currently played music @@ -151,6 +150,27 @@ namespace Vocaluxe.Base _Initialized = true; } + public static void Close() + { + if (_CurrentMusicStream >= 0) + { + CSound.Close(_CurrentMusicStream); + _CurrentMusicStream = -1; + } + if (_Video != -1) + { + CVideo.Close(_Video); + CDraw.RemoveTexture(ref _CurrentVideoTexture); + _Video = -1; + } + _BGMusicFiles.Clear(); + _NotPlayedFiles.Clear(); + _PreviousFiles.Clear(); + + IsPlaying = false; + _Initialized = false; + } + public static void Play() { if (IsPlaying || CConfig.BackgroundMusic == EOffOn.TR_CONFIG_OFF) @@ -254,8 +274,12 @@ namespace Vocaluxe.Base Debug.Assert(_CurrentMusicStream != -1 && _CurrentPlaylistElement != null); float timeToPlay; - if (Math.Abs(_CurrentPlaylistElement.Finish) < 0.001) //No End-Tag defined - timeToPlay = CSound.GetLength(_CurrentMusicStream) - CSound.GetPosition(_CurrentMusicStream); + if (Math.Abs(_CurrentPlaylistElement.Finish) < 0.001) + { + //No End-Tag defined + float len = CSound.GetLength(_CurrentMusicStream); + timeToPlay = (len > 0f) ? len - CSound.GetPosition(_CurrentMusicStream) : CSettings.BackgroundMusicFadeTime + 1f; + } else //End-Tag found timeToPlay = _CurrentPlaylistElement.Finish - CSound.GetPosition(_CurrentMusicStream); diff --git a/Vocaluxe/Base/CSound.cs b/Vocaluxe/Base/CSound.cs index 8d9ad0e8..d00d8e71 100644 --- a/Vocaluxe/Base/CSound.cs +++ b/Vocaluxe/Base/CSound.cs @@ -23,6 +23,7 @@ using Vocaluxe.Lib.Sound.Playback.GstreamerSharp; using Vocaluxe.Lib.Sound.Playback.OpenAL; using Vocaluxe.Lib.Sound.Playback.PortAudio; using VocaluxeLib; +using System.Threading; namespace Vocaluxe.Base { @@ -198,11 +199,26 @@ namespace Vocaluxe.Base if (!File.Exists(file)) return -1; - int stream = Load(file, true); - float length = GetLength(stream); + int stream = Load(file, fade); + if (stream < 0) + return -1; Play(stream); if (fade) - FadeAndClose(stream, 100f, length); + { + float length = -1f; + for (int i = 0; i < 5; i++) + { + length = GetLength(stream); + if (length >= 0f) + break; + Thread.Sleep(1); + } + if (length > 0f) + { + SetStreamVolume(stream, 0f); + Fade(stream, 100f, length); + } + } return stream; } #endregion Sounds diff --git a/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs b/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs index d472d808..d94e3780 100644 --- a/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs +++ b/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs @@ -200,26 +200,26 @@ namespace Vocaluxe.Lib.Sound.Playback public float GetLength(int streamID) { if (!_Initialized) - return 0f; + return -1f; lock (_Streams) { if (_StreamExists(streamID)) return _Streams[_GetStreamIndex(streamID)].Length; } - return 0f; + return -1f; } public float GetPosition(int streamID) { if (!_Initialized) - return 0f; + return -1f; lock (_Streams) { if (_StreamExists(streamID)) return _Streams[_GetStreamIndex(streamID)].Position; } - return 0f; + return -1f; } public void SetPosition(int streamID, float position) diff --git a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs index 52296826..994353ef 100644 --- a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs @@ -25,13 +25,15 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp class CGstreamerSharpAudioStream : CAudioStreamBase { private Element _Element; + private bool _FileOpened; private volatile bool _IsFinished; private volatile float _Position; private volatile bool _QueryingDuration; + private System.Threading.Thread _QueryDurationThread; - private readonly object _DurationLock = new object(); //For atomic check and update of _QueryingDuration + 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 @@ -58,11 +60,11 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp { get { - if (base.Length < 0 && !_QueryingDuration) + if (base.Length < 0 && _QueryDurationThread == null && _Element != null) { - _QueryingDuration = true; // Set this to avoid race conditions - var t = new System.Threading.Thread(_UpdateDuration) {Name = "GSt Update Duration"}; - t.Start(); + // _QueryDurationThread does not get reset so it is created only once! + _QueryDurationThread = new System.Threading.Thread(_UpdateDuration) {Name = "GSt Update Duration"}; + _QueryDurationThread.Start(); } return base.Length >= 0f ? base.Length : 0f; } @@ -72,11 +74,14 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp { get { - long position; - if (!_Element.QueryPosition(Format.Time, out position)) - CLog.LogError("Could not query position"); - else - _Position = ((float)position / Constants.SECOND); + if (_Element != null) + { + long position; + if (!_Element.QueryPosition(Format.Time, out position)) + CLog.LogError("Could not query position"); + else + _Position = ((float)position / Constants.SECOND); + } return _Position; } set @@ -88,7 +93,7 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp public override bool IsFinished { - get { return _IsFinished; } + get { return _Element == null || _IsFinished; } } public override bool IsPaused @@ -96,9 +101,8 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp get { return _Element == null || _Element.TargetState == State.Paused; } set { - if (_Element == null) - return; - _Element.SetState(value ? State.Paused : State.Playing); + if (_Element != null) + _Element.SetState(value ? State.Paused : State.Playing); } } @@ -106,6 +110,10 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp public override bool Open(bool prescan) { + System.Diagnostics.Debug.Assert(!_FileOpened); + if (_FileOpened) + return false; + Length = -1; Element convert = ElementFactory.Make("audioconvert", "convert"); Element audiosink = ElementFactory.Make("directsoundsink", "audiosink"); @@ -178,6 +186,7 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp if (msg.Handle != IntPtr.Zero) _UpdateDuration(); } + _FileOpened = true; return true; } @@ -248,8 +257,7 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp CLog.LogError("Gstreamer error: message" + error.Message + ", code" + error.Code + " ,debug information" + debug); break; case MessageType.DurationChanged: - if (!_QueryingDuration) - _UpdateDuration(); + _UpdateDuration(); break; } msg.Unref(); diff --git a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs index f53f13d4..7428b494 100644 --- a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs @@ -150,11 +150,10 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL public override bool Open(bool prescan) { + Debug.Assert(!_FileOpened); if (_FileOpened) return false; - Length = 0f; - if (!File.Exists(_Medium)) { Dispose(); diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs index a63c0272..cb579f97 100644 --- a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs @@ -16,6 +16,7 @@ #endregion using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading; @@ -126,11 +127,10 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio public override bool Open(bool prescan) { + Debug.Assert(!_FileOpened); if (_FileOpened) return false; - Length = 0f; - if (!File.Exists(_Medium)) { Dispose(); From c07526e1952e94f67706079afe043f83dd2ab3a2 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Tue, 6 May 2014 18:37:46 +0200 Subject: [PATCH 05/13] Fix close order Close open audio streams (or at least give opportunity to do so) --- Vocaluxe/Base/CCover.cs | 25 ++++++---- Vocaluxe/Base/CDraw.cs | 2 +- Vocaluxe/Base/CGraphics.cs | 7 +++ Vocaluxe/Base/CLanguage.cs | 6 --- Vocaluxe/Base/Fonts/CFonts.cs | 7 --- Vocaluxe/Program.cs | 50 ++++++++++--------- Vocaluxe/Screens/CScreenOptionsTheme.cs | 2 +- .../Menu/SongMenu/CSongMenuTileBoard.cs | 9 ++-- 8 files changed, 53 insertions(+), 55 deletions(-) diff --git a/Vocaluxe/Base/CCover.cs b/Vocaluxe/Base/CCover.cs index 5b52371b..26eb6866 100644 --- a/Vocaluxe/Base/CCover.cs +++ b/Vocaluxe/Base/CCover.cs @@ -18,9 +18,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Xml; using VocaluxeLib; using VocaluxeLib.Draw; @@ -35,7 +32,6 @@ namespace Vocaluxe.Base static class CCover { - private static readonly XmlWriterSettings _Settings = new XmlWriterSettings(); private static readonly Dictionary _Covers = new Dictionary(); private static readonly List _CoverThemes = new List(); @@ -43,14 +39,16 @@ namespace Vocaluxe.Base public static void Init() { - _Settings.Indent = true; - _Settings.Encoding = Encoding.UTF8; - _Settings.ConformanceLevel = ConformanceLevel.Document; - _LoadCoverThemes(); _LoadCover(CConfig.CoverTheme); } + public static void Close() + { + _UnloadCovers(); + _CoverThemes.Clear(); + } + /// /// Returns an array of CoverThemes-Names /// @@ -106,18 +104,23 @@ namespace Vocaluxe.Base /// /// Reloads cover to use a new cover-theme /// - public static void ReloadCover() + public static void ReloadCovers() + { + _UnloadCovers(); + _LoadCover(CConfig.CoverTheme); + } + + private static void _UnloadCovers() { lock (_Covers) { - foreach (string key in _Covers.Keys.ToList()) + foreach (string key in _Covers.Keys) { CTexture texture = _Covers[key]; CDraw.RemoveTexture(ref texture); } _Covers.Clear(); } - _LoadCover(CConfig.CoverTheme); } /// diff --git a/Vocaluxe/Base/CDraw.cs b/Vocaluxe/Base/CDraw.cs index f43d8542..f109645b 100644 --- a/Vocaluxe/Base/CDraw.cs +++ b/Vocaluxe/Base/CDraw.cs @@ -28,7 +28,7 @@ namespace Vocaluxe.Base { private static IDraw _Draw; - public static bool InitDraw() + public static bool Init() { if (_Draw != null) return false; diff --git a/Vocaluxe/Base/CGraphics.cs b/Vocaluxe/Base/CGraphics.cs index a4b5adcb..837cd713 100644 --- a/Vocaluxe/Base/CGraphics.cs +++ b/Vocaluxe/Base/CGraphics.cs @@ -106,6 +106,13 @@ namespace Vocaluxe.Base CLog.StopBenchmark("Load Theme"); } + public static void Close() + { + if (_CurrentPopupScreen != EPopupScreens.NoPopup) + _PopupScreens[(int)_CurrentPopupScreen].OnClose(); + _Screens[(int)_CurrentScreen].OnClose(); + } + public static void LoadTheme() { _Cursor.LoadTextures(); diff --git a/Vocaluxe/Base/CLanguage.cs b/Vocaluxe/Base/CLanguage.cs index 2cf54d9d..343aabc0 100644 --- a/Vocaluxe/Base/CLanguage.cs +++ b/Vocaluxe/Base/CLanguage.cs @@ -19,8 +19,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Xml; using VocaluxeLib; namespace Vocaluxe.Base @@ -42,7 +40,6 @@ namespace Vocaluxe.Base static class CLanguage { - private static readonly XmlWriterSettings _Settings = new XmlWriterSettings(); private static List _Languages; private static int _CurrentLanguage; private static int _FallbackLanguage; @@ -75,9 +72,6 @@ namespace Vocaluxe.Base public static void Init() { _Languages = new List(); - _Settings.Indent = true; - _Settings.Encoding = Encoding.UTF8; - _Settings.ConformanceLevel = ConformanceLevel.Document; var files = new List(); files.AddRange(CHelper.ListFiles(CSettings.FolderLanguages, "*.xml", true, true)); diff --git a/Vocaluxe/Base/Fonts/CFonts.cs b/Vocaluxe/Base/Fonts/CFonts.cs index 1c904e98..2c838aac 100644 --- a/Vocaluxe/Base/Fonts/CFonts.cs +++ b/Vocaluxe/Base/Fonts/CFonts.cs @@ -19,7 +19,6 @@ using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; -using System.Text; using System.Xml; using VocaluxeLib; using VocaluxeLib.Menu; @@ -28,8 +27,6 @@ namespace Vocaluxe.Base.Fonts { static class CFonts { - private static readonly XmlWriterSettings _Settings = new XmlWriterSettings(); - private static readonly List _Fonts = new List(); private static int _CurrentFont; private static float _Height = 1f; @@ -58,10 +55,6 @@ namespace Vocaluxe.Base.Fonts public static void Init() { - _Settings.Indent = true; - _Settings.Encoding = Encoding.UTF8; - _Settings.ConformanceLevel = ConformanceLevel.Document; - _CurrentFont = 0; PartyModeID = -1; _BuildFonts(); diff --git a/Vocaluxe/Program.cs b/Vocaluxe/Program.cs index 5ee3826e..b7e0d20c 100644 --- a/Vocaluxe/Program.cs +++ b/Vocaluxe/Program.cs @@ -107,26 +107,17 @@ namespace Vocaluxe // Create folders CSettings.CreateFolders(); - Application.DoEvents(); _SplashScreen = new CSplashScreen(); Application.DoEvents(); // Init Draw CLog.StartBenchmark("Init Draw"); - if (!CDraw.InitDraw()) + if (!CDraw.Init()) throw new CLoadingException("drawing"); CLog.StopBenchmark("Init Draw"); Application.DoEvents(); - // Init Database - CLog.StartBenchmark("Init Database"); - if (!CDataBase.Init()) - throw new CLoadingException("database"); - CLog.StopBenchmark("Init Database"); - - Application.DoEvents(); - // Init Playback CLog.StartBenchmark("Init Playback"); if (!CSound.Init()) @@ -143,6 +134,22 @@ namespace Vocaluxe Application.DoEvents(); + // Init VideoDecoder + CLog.StartBenchmark("Init Videodecoder"); + if (!CVideo.Init()) + throw new CLoadingException("video"); + CLog.StopBenchmark("Init Videodecoder"); + + Application.DoEvents(); + + // Init Database + CLog.StartBenchmark("Init Database"); + if (!CDataBase.Init()) + throw new CLoadingException("database"); + CLog.StopBenchmark("Init Database"); + + Application.DoEvents(); + //Init Webcam CLog.StartBenchmark("Init Webcam"); if (!CWebcam.Init()) @@ -172,14 +179,6 @@ namespace Vocaluxe Application.DoEvents(); - // Init VideoDecoder - CLog.StartBenchmark("Init Videodecoder"); - if (!CVideo.Init()) - throw new CLoadingException("video"); - CLog.StopBenchmark("Init Videodecoder"); - - Application.DoEvents(); - // Load Cover CLog.StartBenchmark("Init Cover"); CCover.Init(); @@ -257,19 +256,22 @@ namespace Vocaluxe private static void _CloseProgram() { - // Unloading + // Unloading in reverse order try { - CVocaluxeServer.Close(); CController.Close(); + CVocaluxeServer.Close(); + CGraphics.Close(); + CCover.Close(); + CBackgroundMusic.Close(); + CWebcam.Close(); + CDataBase.Close(); + CVideo.Close(); CRecord.Close(); CSound.Close(); - CVideo.Close(); CDraw.Unload(); - CDataBase.Close(); - CWebcam.Close(); GC.Collect(); // Do a GC run here before we close logs to have finalizers run - CLog.CloseAll(); + CLog.CloseAll(); // Do this last, so we get all log entries! } catch (Exception) {} Environment.Exit(Environment.ExitCode); diff --git a/Vocaluxe/Screens/CScreenOptionsTheme.cs b/Vocaluxe/Screens/CScreenOptionsTheme.cs index 5bcbe620..a58226a8 100644 --- a/Vocaluxe/Screens/CScreenOptionsTheme.cs +++ b/Vocaluxe/Screens/CScreenOptionsTheme.cs @@ -179,7 +179,7 @@ namespace Vocaluxe.Screens CConfig.SaveConfig(); if (_OldCoverTheme != _SelectSlides[_SelectSlideCover].Selection) - CCover.ReloadCover(); + CCover.ReloadCovers(); if (_OldTheme != _SelectSlides[_SelectSlideTheme].Selection) { diff --git a/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs b/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs index 46781018..e15be31d 100644 --- a/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs +++ b/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs @@ -67,11 +67,11 @@ namespace VocaluxeLib.Menu.SongMenu { set { - _PlaySong(value); - _PreviewIdInternal = value; + if (value == base._PreviewId) + return; + base._PreviewId = value; _UpdatePreview(); } - get { return _PreviewIdInternal; } } public override int GetActualSelection() @@ -373,8 +373,7 @@ namespace VocaluxeLib.Menu.SongMenu if (!keyEvent.Handled) { _ActualSelection = _Locked; - if (_PreviewId != _Locked) - _PreviewId = _Locked; + _PreviewId = _Locked; for (int i = 0; i < _Tiles.Count; i++) _Tiles[i].Selected = _Locked == i + _Offset; From 2195af6700864fb52c7ecf0551dd3163c2167d58 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Tue, 6 May 2014 21:34:48 +0200 Subject: [PATCH 06/13] Fix GStreamer Video (IDs+include) --- GstreamerHelper/GstreamerVideo.cpp | 21 +++++++-------------- GstreamerHelper/GstreamerVideoStream.h | 2 +- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/GstreamerHelper/GstreamerVideo.cpp b/GstreamerHelper/GstreamerVideo.cpp index ab025608..3393e2a5 100644 --- a/GstreamerHelper/GstreamerVideo.cpp +++ b/GstreamerHelper/GstreamerVideo.cpp @@ -3,7 +3,7 @@ #include "GstreamerVideoStream.h" map VideoStreams; -queue VideoIDs; +int NextID; DllExport void SetVideoLogCallback (LogCallback Callback) { @@ -12,15 +12,7 @@ DllExport void SetVideoLogCallback (LogCallback Callback) DllExport bool InitVideo() { - for(int i = 0; i < 1000; i++) - { - VideoIDs.push(i); - } -#if _WIN64 - SetDllDirectory(L".\\x64\\gstreamer"); -#else - SetDllDirectory(L".\\x86\\gstreamer"); -#endif + NextID = 0; gst_init(NULL, NULL); @@ -28,8 +20,10 @@ DllExport bool InitVideo() #if _WIN64 gst_registry_scan_path(registry, ".\\x64\\gstreamer"); -#else +#elif _WIN32 gst_registry_scan_path(registry, ".\\x86\\gstreamer"); +#else + #error Define either _WIN64 or _WIN32! #endif return true; } @@ -45,9 +39,8 @@ DllExport void CloseAllVideos(void) DllExport int LoadVideo(const wchar_t* Media) { - GstreamerVideoStream *s = new GstreamerVideoStream(VideoIDs.front()); - VideoStreams.insert(pair (VideoIDs.front(), s)); - VideoIDs.pop(); + GstreamerVideoStream *s = new GstreamerVideoStream(NextID++); + VideoStreams.insert(pair (s->ID, s)); return s->LoadVideo(Media); } diff --git a/GstreamerHelper/GstreamerVideoStream.h b/GstreamerHelper/GstreamerVideoStream.h index 7d69e019..b258f924 100644 --- a/GstreamerHelper/GstreamerVideoStream.h +++ b/GstreamerHelper/GstreamerVideoStream.h @@ -3,7 +3,7 @@ #include #include "gst/gst.h" #include "GstreamerVideo.h" -#include "gst\app\gstappsink.h" +#include "gst/app/gstappsink.h" #include "VocaluxeClock.h" using namespace std; From cb328c3209905f956965257073d970436b230d08 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Tue, 6 May 2014 21:40:13 +0200 Subject: [PATCH 07/13] Remove GStreamer audio (replaced by GStreamer# and outdated) Also some cleanup --- GstreamerHelper/GstreamerAudio.cpp | 254 ------------- GstreamerHelper/GstreamerAudio.h | 49 --- GstreamerHelper/GstreamerAudioStream.cpp | 338 ------------------ GstreamerHelper/GstreamerAudioStream.h | 73 ---- GstreamerHelper/GstreamerHelper.vcxproj | 36 +- .../GstreamerHelper.vcxproj.filters | 24 -- GstreamerHelper/GstreamerVideo.cpp | 1 - GstreamerHelper/GstreamerVideoStream.cpp | 1 - GstreamerHelper/dllmain.cpp | 19 - GstreamerHelper/stdafx.cpp | 8 - GstreamerHelper/stdafx.h | 16 - GstreamerHelper/targetver.h | 8 - Vocaluxe/Base/CConfig.cs | 2 +- Vocaluxe/Base/CSound.cs | 5 - .../Playback/Gstreamer/CGstreamerAudio.cs | 162 --------- .../Gstreamer/CGstreamerAudioWrapper.cs | 119 ------ Vocaluxe/Vocaluxe.csproj | 2 - VocaluxeLib/Enums.cs | 1 - gstreamerhelper/VocaluxeClock.cpp | 1 - 19 files changed, 2 insertions(+), 1117 deletions(-) delete mode 100644 GstreamerHelper/GstreamerAudio.cpp delete mode 100644 GstreamerHelper/GstreamerAudio.h delete mode 100644 GstreamerHelper/GstreamerAudioStream.cpp delete mode 100644 GstreamerHelper/GstreamerAudioStream.h delete mode 100644 GstreamerHelper/dllmain.cpp delete mode 100644 GstreamerHelper/stdafx.cpp delete mode 100644 GstreamerHelper/stdafx.h delete mode 100644 GstreamerHelper/targetver.h delete mode 100644 Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudio.cs delete mode 100644 Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudioWrapper.cs diff --git a/GstreamerHelper/GstreamerAudio.cpp b/GstreamerHelper/GstreamerAudio.cpp deleted file mode 100644 index 79b0d4f5..00000000 --- a/GstreamerHelper/GstreamerAudio.cpp +++ /dev/null @@ -1,254 +0,0 @@ -#include "stdafx.h" -#include "GstreamerAudio.h" - -#define DllExport extern "C" __declspec(dllexport) - -map Streams; -queue IDs; -float GlobalVolume = 1.f; -EXTERN_C IMAGE_DOS_HEADER __ImageBase; -DllExport void SetLogCallback (LogCallback Callback) -{ - Log = Callback; -} - -DllExport bool Init() -{ - for(int i = 0; i < 1000; i++) - { - IDs.push(i); - } -#if _WIN64 - SetDllDirectory(L".\\x64\\gstreamer"); -#else - SetDllDirectory(L".\\x86\\gstreamer"); -#endif - - gst_init(NULL, NULL); - GstRegistry* registry = gst_registry_get(); - -#if _WIN64 - gst_registry_scan_path(registry, ".\\x64\\gstreamer"); -#else - gst_registry_scan_path(registry, ".\\x86\\gstreamer"); -#endif - - return true; -} - - -DllExport void SetGlobalVolume(float Volume) -{ - map::iterator p; - - for(p = Streams.begin(); p != Streams.end(); p++) { - 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; - - for(p = Streams.begin(); p != Streams.end(); p++) { - p->second->Close(); - } -} - -DllExport int Load(const wchar_t* Media) -{ - GstreamerAudioStream *s = new GstreamerAudioStream(IDs.front()); - Streams.insert(pair (IDs.front(), s)); - IDs.pop(); - int result = s->Load(Media); - s->SetStreamVolume(1.f); - s->SetStreamVolumeMax(GlobalVolume); - return result; -} - -DllExport int LoadPrescan(const wchar_t* Media, bool Prescan) -{ - GstreamerAudioStream *s = new GstreamerAudioStream(IDs.front()); - Streams.insert(pair (IDs.front(), s)); - IDs.pop(); - int result = s->Load(Media, Prescan); - s->SetStreamVolume(1.f); - s->SetStreamVolumeMax(GlobalVolume); - return result; -} - -DllExport void Close(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - delete it->second; - IDs.push(it->first); - Streams.erase(it); - } -} - -DllExport void Play(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - it->second->Play(); - } -} - -DllExport void PlayLoop(int Stream, bool Loop) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - it->second->Play(Loop); - } -} - -DllExport void Pause(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - it->second->Pause(); - } -} - -DllExport void Stop(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - it->second->Stop(); - } -} - -DllExport void Fade(int Stream, float TargetVolume, float Seconds) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - it->second->Fade(TargetVolume, Seconds); - } -} - -DllExport void FadeAndPause(int Stream, float TargetVolume, float Seconds) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - it->second->FadeAndPause(TargetVolume, Seconds); - } -} - -DllExport void FadeAndStop(int Stream, float TargetVolume, float Seconds) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - it->second->FadeAndStop(TargetVolume, Seconds); - } -} - -DllExport void SetStreamVolume(int Stream, float Volume) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - return it->second->SetStreamVolume(Volume); - } -} - -DllExport float GetLength(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - return it->second->GetLength(); - } - else return -1.0; -} - -DllExport float GetPosition(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - return it->second->GetPosition(); - } - else return -1; -} - -DllExport bool IsPlaying(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - return it->second->IsPlaying(); - } - else return false; -} - -DllExport bool IsPaused(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - return it->second->IsPaused(); - } - else return false; -} - -DllExport bool IsFinished(int Stream) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - return it->second->IsFinished(); - } - else return false; -} - -DllExport void SetPosition(int Stream, float Position) -{ - map::iterator it = Streams.find(Stream); - if(it != Streams.end()) - { - return it->second->SetPosition(Position); - } -} - -DllExport void Update(void) -{ - map::iterator p = Streams.begin(); - - while(p != Streams.end()) - { - if(p->second->Closed) - { - delete p->second; - IDs.push(p->first); - Streams.erase(p++); - } - else { - p->second->Update(); - ++p; - } - } -} - -void LogError(const char* msg) -{ - Log(msg); -} \ No newline at end of file diff --git a/GstreamerHelper/GstreamerAudio.h b/GstreamerHelper/GstreamerAudio.h deleted file mode 100644 index 75f2d6f5..00000000 --- a/GstreamerHelper/GstreamerAudio.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include -#include -#include -#include "gst/gst.h" -#include "GstreamerAudioStream.h" -#include "gst\gst.h" - -#define DllExport extern "C" __declspec(dllexport) - -using namespace std; - - typedef void (__stdcall * LogCallback)(const char* text); - static LogCallback Log; - - DllExport void SetLogCallback(LogCallback Callback); - - DllExport bool Init(); - DllExport void SetGlobalVolume(float Volume); - DllExport float GetGlobalVolume(); - DllExport int GetStreamCount(void); - DllExport void CloseAll(void); - - //Stream handling - DllExport int Load(const wchar_t* Media); - DllExport int LoadPrescan(const wchar_t* Media, bool Prescan); - DllExport void Close(int Stream); - - DllExport void Play(int Stream); - DllExport void PlayLoop(int Stream, bool Loop); - DllExport void Pause(int Stream); - DllExport void Stop(int Stream); - DllExport void Fade(int Stream, float TargetVolume, float Seconds); - 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 float GetLength(int Stream); - DllExport float GetPosition(int Stream); - - DllExport bool IsPlaying(int Stream); - DllExport bool IsPaused(int Stream); - DllExport bool IsFinished(int Stream); - - DllExport void SetPosition(int Stream, float Position); - - DllExport void Update(); - - void LogError(const char* msg); diff --git a/GstreamerHelper/GstreamerAudioStream.cpp b/GstreamerHelper/GstreamerAudioStream.cpp deleted file mode 100644 index ed8d2c4a..00000000 --- a/GstreamerHelper/GstreamerAudioStream.cpp +++ /dev/null @@ -1,338 +0,0 @@ -#include "stdafx.h" -#include "GstreamerAudioStream.h" - - -typedef enum { - GST_PLAY_FLAG_VIDEO = (1 << 0), - GST_PLAY_FLAG_AUDIO = (1 << 1), - GST_PLAY_FLAG_TEXT = (1 << 2), - GST_PLAY_FLAG_VIS = (1 << 3), - GST_PLAY_FLAG_SOFT_VOLUME = (1 << 4), - GST_PLAY_FLAG_NATIVE_AUDIO = (1 << 5), - GST_PLAY_FLAG_NATIVE_VIDEO = (1 << 6), - GST_PLAY_FLAG_DOWNLOAD = (1 << 7), - GST_PLAY_FLAG_BUFFERING = (1 << 8), - GST_PLAY_FLAG_DEINTERLACE = (1 << 9), - GST_PLAY_FLAG_SOFT_COLORBALANCE = (1 << 10) -} GstPlayFlags; - -GstreamerAudioStream::GstreamerAudioStream(int id) -{ - ID = id; - MaxVolume = 1.0; - Volume = 1.0; - Duration = -1; - Loop = false; - Running = true; - - Finished = false; - - //Fading - FadeTimer = g_timer_new(); - FadeTime = 0.0; - TargetVolume = 1.0; - StartVolume = 1.0; - CloseStreamAfterFade = false; - PauseStreamAfterFade = false; - Fading = false; - - Closed = false; -} - - -GstreamerAudioStream::~GstreamerAudioStream(void) -{ - Close(); -} - -// Loads a stream but does not wait for it to get initialized, -// so querying duration or position directly after initialization might -// return strange values! To work around this use the prescan load function! -int GstreamerAudioStream::Load(const wchar_t* Media) -{ - Element = gst_element_factory_make("playbin", "playbin"); - - Convert = gst_element_factory_make("audioconvert", "audioconvert"); - Audiosink = gst_element_factory_make("directsoundsink", "directsoundsink"); - SinkBin = gst_bin_new("SinkBin"); - gst_bin_add_many (GST_BIN (SinkBin), Convert, Audiosink, NULL); - gst_element_link_many (Convert, Audiosink, NULL); - Pad = gst_element_get_static_pad (Convert, "sink"); - GhostPad = gst_ghost_pad_new ("sink", Pad); - gst_pad_set_active (GhostPad, TRUE); - gst_element_add_pad (SinkBin, GhostPad); - - Bus = gst_element_get_bus(Element); - if(!Element) - { - LogError ("Could not create element!"); - return -1; - } - Running = true; - gst_element_set_state(Element, GST_STATE_NULL); - g_object_set(Element, "uri", Media, NULL); - g_object_set(Element, "audio-sink", SinkBin, NULL); - g_object_set(Element, "flags", GST_PLAY_FLAG_AUDIO, NULL); - - gst_element_set_state(Element, GST_STATE_PAUSED); - - return ID; -} - -//Loads a stream and waits for its initialization -int GstreamerAudioStream::Load(const wchar_t* Media, bool Prescan) -{ - Load(Media); - if(Prescan){ - gst_bus_timed_pop_filtered(Bus, -1, GST_MESSAGE_ASYNC_DONE); - //Workaround: Waiting for the stream to be loaded (Length>0) - for(int i=0;i<40;i++){ - if(GetLength()>0.f) - break; - Sleep(5); - } - } - return ID; -} - -void GstreamerAudioStream::Close(void) -{ - if(Element) - { - gst_element_set_state(Element, GST_STATE_NULL); - gst_object_unref(GST_OBJECT (Element)); - } - - /* without that there are no crashes any more. but i don't know if there is a memory leak if we do that. - if(Bus) - gst_object_unref(Bus); - if(Message) - gst_message_unref(Message); - if(Convert) - gst_object_unref(Convert); - if(Audiosink) - gst_object_unref(Audiosink); - if(Pad) - gst_object_unref(Pad); - if(GhostPad) - gst_object_unref(GhostPad); - if(SinkBin) - gst_object_unref(SinkBin); - */ - - if(FadeTimer) - g_timer_destroy (FadeTimer); - - Running = false; - Closed = true; -} - -void GstreamerAudioStream::Play(void) -{ - if(Element) - { - PauseStreamAfterFade = false; - Running = true; - if(gst_element_set_state(Element, GST_STATE_PLAYING) == GST_STATE_CHANGE_ASYNC) - gst_bus_timed_pop_filtered(Bus, -1, GST_MESSAGE_ASYNC_DONE); - SetStreamVolume(Volume * 100.0); - } - -} - -void GstreamerAudioStream::Play(bool Loop) -{ - this->Loop = Loop; - Play(); -} - -void GstreamerAudioStream::Pause() -{ - Running = true; - if(Element) - gst_element_set_state(Element, GST_STATE_PAUSED); - SetStreamVolume(Volume * 100.0); -} - -void GstreamerAudioStream::Stop() -{ - if(Element) - { - gst_element_set_state(Element, GST_STATE_NULL); - gst_element_seek_simple(Element, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, 0); - } -} - -void GstreamerAudioStream::SetStreamVolume(float Volume) -{ - this->Volume = Volume / 100.0; - if(Element) - { - g_object_set(this->Element, "volume", (gdouble)(Volume / 100.0) * MaxVolume, NULL); - } -} - -void GstreamerAudioStream::SetStreamVolumeMax(float MaxVolume) -{ - this->MaxVolume = (gdouble) (MaxVolume / 100.0); - if(Element) - { - g_object_set(this->Element, "volume", Volume * this->MaxVolume, NULL); - } -} - -float GstreamerAudioStream::GetLength() -{ - if(Duration <= 0.f) - RefreshDuration(); - return Duration; -} - -void GstreamerAudioStream::RefreshDuration() -{ - if(Element) - { - gint64 time = -1000; - if(gst_element_query_duration(Element, GST_FORMAT_TIME, &time)) - Duration = (gfloat)((gdouble)time/GST_SECOND); - } -} - -float GstreamerAudioStream::GetPosition() -{ - if(Element) - { - gint64 time = 0; - if (gst_element_query_position(Element, GST_FORMAT_TIME, &time)) - return (float)((gdouble)time/GST_SECOND); - else - return -1; - } - else return -1; -} - -void GstreamerAudioStream::SetPosition(float Position) -{ - if(Element) - { - //Not sure if we need GST_SEEK_FLAG_ACCURATE - if(!gst_element_seek_simple(Element, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE), Position * GST_SECOND)) - LogError("Seek failed"); - } -} - -void GstreamerAudioStream::Fade(float TargetVolume, float Seconds) -{ - Fading = true; - g_timer_stop(FadeTimer); - StartVolume = Volume; - this->TargetVolume = TargetVolume / 100.0; - FadeTime = (gdouble)Seconds; - g_timer_start(FadeTimer); -} - -void GstreamerAudioStream::FadeAndPause(float TargetVolume, float Seconds) -{ - PauseStreamAfterFade = true; - - Fade(TargetVolume, Seconds); -} - -void GstreamerAudioStream::FadeAndStop(float TargetVolume, float Seconds) -{ - CloseStreamAfterFade = true; - - Fade(TargetVolume, Seconds); -} - -bool GstreamerAudioStream::IsPlaying() -{ - GstStateChangeReturn ret; - GstState current, pending; - - //This might block!!! - ret = gst_element_get_state (Element, ¤t, &pending, GST_CLOCK_TIME_NONE); - return current == GST_STATE_PLAYING && !Finished; -} - -bool GstreamerAudioStream::IsPaused() -{ - GstStateChangeReturn ret; - GstState current, pending; - - //This might block!!! - ret = gst_element_get_state (Element, ¤t, &pending, GST_CLOCK_TIME_NONE); - return current == GST_STATE_PAUSED && !Finished; -} - -bool GstreamerAudioStream::IsFinished() -{ - return Finished; -} - -void GstreamerAudioStream::UpdateVolume() -{ - if (Fading) - { - if (g_timer_elapsed(FadeTimer, NULL) < FadeTime) { - gdouble vol = (StartVolume + (TargetVolume - StartVolume) * (g_timer_elapsed(FadeTimer, NULL) / FadeTime)); - SetStreamVolume(100.0 * vol); - } - else - { - SetStreamVolume (TargetVolume * 100.0); - g_timer_stop(FadeTimer); - Fading = false; - - if (CloseStreamAfterFade) - { - Closed = true; - } - - if (PauseStreamAfterFade) - Pause(); - } - } -} - -void GstreamerAudioStream::Update() -{ - if(Running) { - UpdateVolume(); - Message = gst_bus_pop (Bus); - - /* Parse message */ - if (Message != NULL) { - GError *err; - gchar *debug_info; - string m; - - switch (GST_MESSAGE_TYPE (Message)) { - case GST_MESSAGE_ERROR: - gst_message_parse_error (Message, &err, &debug_info); - m = err->message; - LogError(m.c_str()); - g_clear_error (&err); - g_free (debug_info); - break; - case GST_MESSAGE_DURATION_CHANGED: - RefreshDuration(); - break; - case GST_MESSAGE_STATE_CHANGED: - break; - case GST_MESSAGE_EOS: - if(Loop) - SetPosition(0); - else - Finished = true; - break; - default: - /* We should not reach here */ - //LogError ("Unexpected message received.\n"); - break; - } - gst_message_unref (Message); - } - } -} - diff --git a/GstreamerHelper/GstreamerAudioStream.h b/GstreamerHelper/GstreamerAudioStream.h deleted file mode 100644 index 7f46488d..00000000 --- a/GstreamerHelper/GstreamerAudioStream.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once -#pragma warning(disable: 4244) // possible loss of data -#pragma warning(disable: 4267) // possible loss of data -#pragma warning(disable: 4800) - -#include -#include "gst/gst.h" -#include "GstreamerAudio.h" - -using namespace std; - -class GstreamerAudioStream -{ -public: - int ID; - bool Closed; - - GstreamerAudioStream(int ID); - ~GstreamerAudioStream(void); - - int Load(const wchar_t* Media); - int Load(const wchar_t* Media, bool Prescan); - void Close(); - - void Play(); - void Play(bool Loop); - void Pause(); - void Stop(); - - void Fade(float TargetVolume, float Seconds); - void FadeAndPause(float TargetVolume, float Seconds); - void FadeAndStop(float TargetVolume, float Seconds); - void SetStreamVolume(float Volume); - void SetStreamVolumeMax(float Volume); - - float GetLength(); - float GetPosition(); - void SetPosition(float Position); - - bool IsPlaying(); - bool IsPaused(); - bool IsFinished(); - - void Update(); -private: - GstElement *Element, *Audiosink, *Convert, *SinkBin; - GstPad *Pad, *GhostPad; - GstBus *Bus; - GstMessage *Message; - - gboolean Running; - gboolean Loop; - - gboolean Finished; - - gdouble MaxVolume; - gdouble Volume; - - gfloat Duration; - - GTimer *FadeTimer; - - gdouble FadeTime; - gdouble TargetVolume; - gdouble StartVolume; - gboolean CloseStreamAfterFade; - gboolean PauseStreamAfterFade; - gboolean Fading; - - void RefreshDuration(); - void UpdateVolume(); -}; - diff --git a/GstreamerHelper/GstreamerHelper.vcxproj b/GstreamerHelper/GstreamerHelper.vcxproj index 1c6e02b6..facd22f3 100644 --- a/GstreamerHelper/GstreamerHelper.vcxproj +++ b/GstreamerHelper/GstreamerHelper.vcxproj @@ -169,7 +169,7 @@ - Use + NotUsing Level3 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;GSTREAMERHELPER_EXPORTS;%(PreprocessorDefinitions) @@ -282,47 +282,13 @@ - - - - - - false - false - - - - - false - false - false - false - - - - - - - - - - - - - Create - Create - Create - Create - Create - Create - diff --git a/GstreamerHelper/GstreamerHelper.vcxproj.filters b/GstreamerHelper/GstreamerHelper.vcxproj.filters index fc78f1e9..1e3c4788 100644 --- a/GstreamerHelper/GstreamerHelper.vcxproj.filters +++ b/GstreamerHelper/GstreamerHelper.vcxproj.filters @@ -15,18 +15,6 @@ - - Headerdateien - - - Headerdateien - - - Headerdateien - - - Headerdateien - Headerdateien @@ -38,18 +26,6 @@ - - Quelldateien - - - Quelldateien - - - Quelldateien - - - Quelldateien - Quelldateien diff --git a/GstreamerHelper/GstreamerVideo.cpp b/GstreamerHelper/GstreamerVideo.cpp index 3393e2a5..b5b98d97 100644 --- a/GstreamerHelper/GstreamerVideo.cpp +++ b/GstreamerHelper/GstreamerVideo.cpp @@ -1,4 +1,3 @@ -#include "stdafx.h" #include "GstreamerVideo.h" #include "GstreamerVideoStream.h" diff --git a/GstreamerHelper/GstreamerVideoStream.cpp b/GstreamerHelper/GstreamerVideoStream.cpp index 93c98df0..e90d321d 100644 --- a/GstreamerHelper/GstreamerVideoStream.cpp +++ b/GstreamerHelper/GstreamerVideoStream.cpp @@ -1,4 +1,3 @@ -#include "stdafx.h" #include "GstreamerVideoStream.h" typedef enum { diff --git a/GstreamerHelper/dllmain.cpp b/GstreamerHelper/dllmain.cpp deleted file mode 100644 index aa837bd4..00000000 --- a/GstreamerHelper/dllmain.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// dllmain.cpp : Definiert den Einstiegspunkt für die DLL-Anwendung. -#include "stdafx.h" - -BOOL APIENTRY DllMain( HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved - ) -{ - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; - } - return TRUE; -} - diff --git a/GstreamerHelper/stdafx.cpp b/GstreamerHelper/stdafx.cpp deleted file mode 100644 index 84f3b8da..00000000 --- a/GstreamerHelper/stdafx.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// stdafx.cpp : Quelldatei, die nur die Standard-Includes einbindet. -// GstreamerHelper.pch ist der vorkompilierte Header. -// stdafx.obj enthält die vorkompilierten Typinformationen. - -#include "stdafx.h" - -// TODO: Auf zusätzliche Header verweisen, die in STDAFX.H -// und nicht in dieser Datei erforderlich sind. diff --git a/GstreamerHelper/stdafx.h b/GstreamerHelper/stdafx.h deleted file mode 100644 index fbc6220a..00000000 --- a/GstreamerHelper/stdafx.h +++ /dev/null @@ -1,16 +0,0 @@ -// stdafx.h : Includedatei für Standardsystem-Includedateien -// oder häufig verwendete projektspezifische Includedateien, -// die nur in unregelmäßigen Abständen geändert werden. -// - -#pragma once - -#include "targetver.h" - -#define WIN32_LEAN_AND_MEAN // Selten verwendete Teile der Windows-Header nicht einbinden. -// Windows-Headerdateien: -#include - - - -// TODO: Hier auf zusätzliche Header, die das Programm erfordert, verweisen. diff --git a/GstreamerHelper/targetver.h b/GstreamerHelper/targetver.h deleted file mode 100644 index a7654135..00000000 --- a/GstreamerHelper/targetver.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -// Durch Einbeziehen von"SDKDDKVer.h" wird die höchste verfügbare Windows-Plattform definiert. - -// Wenn Sie die Anwendung für eine frühere Windows-Plattform erstellen möchten, schließen Sie "WinSDKVer.h" ein, und -// legen Sie das _WIN32_WINNT-Makro auf die zu unterstützende Plattform fest, bevor Sie "SDKDDKVer.h" einschließen. - -#include diff --git a/Vocaluxe/Base/CConfig.cs b/Vocaluxe/Base/CConfig.cs index 97eaa16e..cc523c70 100644 --- a/Vocaluxe/Base/CConfig.cs +++ b/Vocaluxe/Base/CConfig.cs @@ -95,7 +95,7 @@ namespace Vocaluxe.Base public static ELyricStyle LyricStyle = ELyricStyle.Slide; // Sound - public static EPlaybackLib PlayBackLib = EPlaybackLib.Gstreamer; + public static EPlaybackLib PlayBackLib = EPlaybackLib.GstreamerSharp; public static EBufferSize AudioBufferSize = EBufferSize.B2048; public static int AudioLatency; private static int _BackgroundMusicVolume = 30; diff --git a/Vocaluxe/Base/CSound.cs b/Vocaluxe/Base/CSound.cs index d00d8e71..4a39e03e 100644 --- a/Vocaluxe/Base/CSound.cs +++ b/Vocaluxe/Base/CSound.cs @@ -18,7 +18,6 @@ using System; using System.IO; using Vocaluxe.Lib.Sound.Playback; -using Vocaluxe.Lib.Sound.Playback.Gstreamer; using Vocaluxe.Lib.Sound.Playback.GstreamerSharp; using Vocaluxe.Lib.Sound.Playback.OpenAL; using Vocaluxe.Lib.Sound.Playback.PortAudio; @@ -51,10 +50,6 @@ namespace Vocaluxe.Base _Playback = new COpenALPlay(); break; - case EPlaybackLib.Gstreamer: - _Playback = new CGstreamerAudio(); - break; - case EPlaybackLib.GstreamerSharp: _Playback = new CGstreamerSharpAudio(); break; diff --git a/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudio.cs b/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudio.cs deleted file mode 100644 index 4dcd9006..00000000 --- a/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudio.cs +++ /dev/null @@ -1,162 +0,0 @@ -#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 Vocaluxe.Base; - -namespace Vocaluxe.Lib.Sound.Playback.Gstreamer -{ - class CGstreamerAudio : IPlayback - { - private readonly CGstreamerAudioWrapper.LogCallback _LogCallback; - - #region log - private static void _LogHandler(string text) - { - CLog.LogError(text); - } - #endregion log - - public CGstreamerAudio() - { - _LogCallback = _LogHandler; - CGstreamerAudioWrapper.SetLogCallback(_LogCallback); - } - - public bool Init() - { - return CGstreamerAudioWrapper.Init(); - } - - public void Close() - { - CloseAll(); - CGstreamerAudioWrapper.SetLogCallback(null); - } - - public float GetGlobalVolume() - { - return CGstreamerAudioWrapper.GetGlobalVolume(); - } - - public void SetGlobalVolume(float volume) - { - CGstreamerAudioWrapper.SetGlobalVolume(volume); - } - - public int GetStreamCount() - { - return CGstreamerAudioWrapper.GetStreamCount(); - } - - public void CloseAll() - { - CGstreamerAudioWrapper.CloseAll(); - } - - public int Load(string medium, bool loop = false, bool prescan = false) - { - 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; - } - - public void Close(int stream) - { - CGstreamerAudioWrapper.Close(stream); - } - - public void Play(int stream) - { - CGstreamerAudioWrapper.Play(stream); - } - - public void Pause(int stream) - { - CGstreamerAudioWrapper.Pause(stream); - } - - public void Stop(int stream) - { - CGstreamerAudioWrapper.Stop(stream); - } - - public void Fade(int streamID, float targetVolume, float seconds, EStreamAction afterFadeAction = EStreamAction.Nothing) - { - 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) - { - CGstreamerAudioWrapper.SetStreamVolume(stream, volume); - } - - public float GetLength(int stream) - { - return CGstreamerAudioWrapper.GetLength(stream); - } - - public float GetPosition(int stream) - { - return CGstreamerAudioWrapper.GetPosition(stream); - } - - public bool IsPlaying(int stream) - { - return CGstreamerAudioWrapper.IsPlaying(stream); - } - - public bool IsPaused(int stream) - { - return CGstreamerAudioWrapper.IsPaused(stream); - } - - public bool IsFinished(int stream) - { - return CGstreamerAudioWrapper.IsFinished(stream); - } - - public void SetPosition(int stream, float position) - { - CGstreamerAudioWrapper.SetPosition(stream, position); - } - - public void Update() - { - CGstreamerAudioWrapper.Update(); - } - } -} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudioWrapper.cs b/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudioWrapper.cs deleted file mode 100644 index f091c887..00000000 --- a/Vocaluxe/Lib/Sound/Playback/Gstreamer/CGstreamerAudioWrapper.cs +++ /dev/null @@ -1,119 +0,0 @@ -#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.Runtime.InteropServices; - -namespace Vocaluxe.Lib.Sound.Playback.Gstreamer -{ - public static class CGstreamerAudioWrapper - { - #region arch -#if ARCH_X86 -#if WIN - private const string _Dll = "x86\\gstreamer\\GstreamerHelper.dll"; -#endif -#endif - -#if ARCH_X64 -#if WIN - private const string _Dll = "x64\\gstreamer\\GstreamerHelper.dll"; -#endif -#endif - #endregion arch - - [UnmanagedFunctionPointer(CallingConvention.StdCall)] - public delegate void LogCallback(string message); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void SetLogCallback(LogCallback c); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern bool Init(); - - [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(); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void CloseAll(); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern int Load(string media); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "LoadPrescan")] - public static extern int Load(string media, - [MarshalAs(UnmanagedType.U1)] bool prescan); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void Close(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void Play(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PlayLoop")] - public static extern void Play(int stream, - [MarshalAs(UnmanagedType.U1)] bool loop); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void Pause(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void Stop(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void Fade(int stream, float targetVolume, float seconds); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void FadeAndPause(int stream, float targetVolume, float seconds); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void FadeAndStop(int stream, float targetVolume, float seconds); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void SetStreamVolume(int stream, float volume); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern float GetLength(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern float GetPosition(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern bool IsPlaying(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern bool IsPaused(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public static extern bool IsFinished(int stream); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void SetPosition(int stream, float position); - - [DllImport(_Dll, CallingConvention = CallingConvention.Cdecl)] - public static extern void Update(); - } -} \ No newline at end of file diff --git a/Vocaluxe/Vocaluxe.csproj b/Vocaluxe/Vocaluxe.csproj index 8cd6f665..3319c884 100644 --- a/Vocaluxe/Vocaluxe.csproj +++ b/Vocaluxe/Vocaluxe.csproj @@ -308,14 +308,12 @@ - - diff --git a/VocaluxeLib/Enums.cs b/VocaluxeLib/Enums.cs index 2f647bd1..cba7554d 100644 --- a/VocaluxeLib/Enums.cs +++ b/VocaluxeLib/Enums.cs @@ -144,7 +144,6 @@ namespace VocaluxeLib { PortAudio, OpenAL, - Gstreamer, GstreamerSharp } diff --git a/gstreamerhelper/VocaluxeClock.cpp b/gstreamerhelper/VocaluxeClock.cpp index ba84cfd0..d1a47f7a 100644 --- a/gstreamerhelper/VocaluxeClock.cpp +++ b/gstreamerhelper/VocaluxeClock.cpp @@ -1,4 +1,3 @@ -#include "stdafx.h" #include "VocaluxeClock.h" static void vocaluxe_clock_class_init (VocaluxeClockClass * klass); From fda3541e47f953e9e4c0b17c88be3ff91224440b Mon Sep 17 00:00:00 2001 From: Flamefire Date: Tue, 6 May 2014 22:11:28 +0200 Subject: [PATCH 08/13] Remove unused imports --- .../Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs | 1 - Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs index 3bc2a989..651dcd21 100644 --- a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs +++ b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudio.cs @@ -15,7 +15,6 @@ // along with Vocaluxe. If not, see . #endregion -using System.Collections.Generic; using Gst; namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp diff --git a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs index 12f7cd2b..74a3704e 100644 --- a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs +++ b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs @@ -15,11 +15,7 @@ // along with Vocaluxe. If not, see . #endregion -using System.Linq; -using System.Threading; using OpenTK.Audio; -using System; -using System.Collections.Generic; namespace Vocaluxe.Lib.Sound.Playback.OpenAL { From cc08e01590659821f1e85fdf4d4133ebf3dfeeae Mon Sep 17 00:00:00 2001 From: Flamefire Date: Tue, 6 May 2014 23:30:22 +0200 Subject: [PATCH 09/13] Split off CDataBase into individual classes (more to come) --- Vocaluxe/Base/CDataBase.cs | 1522 +----------------------- Vocaluxe/Lib/Database/CCoverDB.cs | 234 ++++ Vocaluxe/Lib/Database/CDatabaseBase.cs | 112 ++ Vocaluxe/Lib/Database/CHighscoreDB.cs | 1021 ++++++++++++++++ Vocaluxe/Lib/Database/CResourceDB.cs | 253 ++++ Vocaluxe/Vocaluxe.csproj | 4 + 6 files changed, 1663 insertions(+), 1483 deletions(-) create mode 100644 Vocaluxe/Lib/Database/CCoverDB.cs create mode 100644 Vocaluxe/Lib/Database/CDatabaseBase.cs create mode 100644 Vocaluxe/Lib/Database/CHighscoreDB.cs create mode 100644 Vocaluxe/Lib/Database/CResourceDB.cs diff --git a/Vocaluxe/Base/CDataBase.cs b/Vocaluxe/Base/CDataBase.cs index 7721f663..065e7170 100644 --- a/Vocaluxe/Base/CDataBase.cs +++ b/Vocaluxe/Base/CDataBase.cs @@ -17,65 +17,36 @@ using System; using System.Collections.Generic; -using System.Data; -using System.Drawing; -using System.Drawing.Imaging; using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using Community.CsharpSqlite; +using Vocaluxe.Lib.Database; using VocaluxeLib; using VocaluxeLib.Draw; -using VocaluxeLib.Songs; -#if WIN -using System.Data.SQLite; - -#else -using Mono.Data.Sqlite; -using SQLiteConnection = Mono.Data.Sqlite.SqliteConnection; -using SQLiteTransaction = Mono.Data.Sqlite.SqliteTransaction; -using SQLiteCommand = Mono.Data.Sqlite.SqliteCommand; -using SQLiteDataReader = Mono.Data.Sqlite.SqliteDataReader; -#endif namespace Vocaluxe.Base { static class CDataBase { - private struct SData - { - public int Id; - public long Ticks; - public string Str1; - public string Str2; - } - - private static string _HighscoreFilePath; - private static string _CoverFilePath; - private static string _CreditsRessourcesFilePath; - - //You have to lock all actions using cover connection or transaction, otherwhise order is not guaranted - private static readonly object _CoverMutex = new object(); - private static SQLiteConnection _ConnectionCover; - private static SQLiteTransaction _TransactionCover; + private static CHighscoreDB _HighscoreDB; + private static CCoverDB _CoverDB; + private static CResourceDB _ResourceDB; public static bool Init() { - _HighscoreFilePath = Path.Combine(Environment.CurrentDirectory, CConfig.FileHighscoreDB); - _CoverFilePath = Path.Combine(Environment.CurrentDirectory, CSettings.FileCoverDB); - _CreditsRessourcesFilePath = Path.Combine(Environment.CurrentDirectory, CSettings.FileCreditsRessourcesDB); + _HighscoreDB = new CHighscoreDB(Path.Combine(Environment.CurrentDirectory, CConfig.FileHighscoreDB)); + _CoverDB = new CCoverDB(Path.Combine(Environment.CurrentDirectory, CSettings.FileCoverDB)); + _ResourceDB = new CResourceDB(Path.Combine(Environment.CurrentDirectory, CSettings.FileCreditsRessourcesDB)); - if (!_InitHighscoreDB()) + if (!_HighscoreDB.Init()) { CLog.LogError("Error initializing Highscore-DB", true, true); return false; } - if (!_InitCoverDB()) + if (!_CoverDB.Init()) { CLog.LogError("Error initializing Cover-DB", true, true); return false; } - if (!_InitCreditsRessourcesDB()) + if (!_ResourceDB.Init()) { CLog.LogError("Error initializing Credits-DB", true, true); return false; @@ -83,1473 +54,58 @@ namespace Vocaluxe.Base return true; } - #region Highscores + public static void Close() + { + if (_HighscoreDB != null) + { + _HighscoreDB.Close(); + _HighscoreDB = null; + } + if (_CoverDB != null) + { + _CoverDB.Close(); + _CoverDB = null; + } + if (_ResourceDB != null) + { + _ResourceDB.Close(); + _ResourceDB = null; + } + } + public static bool GetDataBaseSongInfos(string artist, string title, out int numPlayed, out DateTime dateAdded, out int highscoreID) { - string sArtist; - string sTitle; - int songID; - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _HighscoreFilePath; - - try - { - connection.Open(); - } - catch (Exception) {} - - using (var command = new SQLiteCommand(connection)) - { - songID = _GetDataBaseSongID(artist, title, 0, command); - highscoreID = songID; - } - } - return _GetDataBaseSongInfos(songID, out sArtist, out sTitle, out numPlayed, out dateAdded, _HighscoreFilePath); - } - - public static void IncreaseSongCounter(int dataBaseSongID) - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _HighscoreFilePath; - - try - { - connection.Open(); - } - catch (Exception) {} - - using (var command = new SQLiteCommand(connection)) - _IncreaseSongCounter(dataBaseSongID, command); - } - } - - public static int AddScore(string playerName, int score, int lineNr, long date, int medley, int duet, int shortSong, int difficulty, - string artist, string title, int numPlayed, string filePath) - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + filePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return -1; - } - - using (var command = new SQLiteCommand(connection)) - { - int dataBaseSongID = _GetDataBaseSongID(artist, title, numPlayed, command); - int result = _AddScore(playerName, score, lineNr, date, medley, duet, shortSong, difficulty, dataBaseSongID, command); - return result; - } - } - } - - public static int AddScore(SPlayer player) - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _HighscoreFilePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return -1; - } - - int medley = 0; - int duet = 0; - int shortSong = 0; - switch (player.GameMode) - { - case EGameMode.TR_GAMEMODE_MEDLEY: - medley = 1; - break; - case EGameMode.TR_GAMEMODE_DUET: - duet = 1; - break; - case EGameMode.TR_GAMEMODE_SHORTSONG: - shortSong = 1; - break; - } - - using (var command = new SQLiteCommand(connection)) - { - int dataBaseSongID = CSongs.GetSong(player.SongID).DataBaseSongID; - return _AddScore(CProfiles.GetPlayerName(player.ProfileID), (int)Math.Round(player.Points), player.VoiceNr, player.DateTicks, medley, - duet, shortSong, (int)CProfiles.GetDifficulty(player.ProfileID), dataBaseSongID, command); - } - } - } - - private static int _AddScore(string playerName, int score, int lineNr, long date, int medley, int duet, int shortSong, int difficulty, - int dataBaseSongID, SQLiteCommand command) - { - int lastInsertID = -1; - - if (dataBaseSongID >= 0) - { - command.CommandText = "SELECT id FROM Scores WHERE SongID = @SongID AND PlayerName = @PlayerName AND Score = @Score AND " + - "LineNr = @LineNr AND Date = @Date AND Medley = @Medley AND Duet = @Duet AND ShortSong = @ShortSong AND Difficulty = @Difficulty"; - command.Parameters.Add("@SongID", DbType.Int32, 0).Value = dataBaseSongID; - command.Parameters.Add("@PlayerName", DbType.String, 0).Value = playerName; - command.Parameters.Add("@Score", DbType.Int32, 0).Value = score; - command.Parameters.Add("@LineNr", DbType.Int32, 0).Value = lineNr; - command.Parameters.Add("@Date", DbType.Int64, 0).Value = date; - command.Parameters.Add("@Medley", DbType.Int32, 0).Value = medley; - command.Parameters.Add("@Duet", DbType.Int32, 0).Value = duet; - command.Parameters.Add("@ShortSong", DbType.Int32, 0).Value = shortSong; - command.Parameters.Add("@Difficulty", DbType.Int32, 0).Value = difficulty; - - SQLiteDataReader reader = null; - try - { - reader = command.ExecuteReader(); - } - catch (Exception) {} - - if (reader != null && reader.HasRows) - { - if (reader.Read()) - return reader.GetInt32(0); - } - - if (reader != null) - reader.Dispose(); - - command.CommandText = "INSERT INTO Scores (SongID, PlayerName, Score, LineNr, Date, Medley, Duet, ShortSong, Difficulty) " + - "VALUES (@SongID, @PlayerName, @Score, @LineNr, @Date, @Medley, @Duet, @ShortSong, @Difficulty)"; - command.Parameters.Add("@SongID", DbType.Int32, 0).Value = dataBaseSongID; - command.Parameters.Add("@PlayerName", DbType.String, 0).Value = playerName; - command.Parameters.Add("@Score", DbType.Int32, 0).Value = score; - command.Parameters.Add("@LineNr", DbType.Int32, 0).Value = lineNr; - command.Parameters.Add("@Date", DbType.Int64, 0).Value = date; - command.Parameters.Add("@Medley", DbType.Int32, 0).Value = medley; - command.Parameters.Add("@Duet", DbType.Int32, 0).Value = duet; - command.Parameters.Add("@ShortSong", DbType.Int32, 0).Value = shortSong; - command.Parameters.Add("@Difficulty", DbType.Int32, 0).Value = difficulty; - command.ExecuteNonQuery(); - - //Read last insert line - command.CommandText = "SELECT id FROM Scores ORDER BY id DESC LIMIT 0, 1"; - - reader = command.ExecuteReader(); - - if (reader != null && reader.HasRows) - { - while (reader.Read()) - lastInsertID = reader.GetInt32(0); - reader.Dispose(); - } - } - - return lastInsertID; + return _HighscoreDB.GetDataBaseSongInfos(artist, title, out numPlayed, out dateAdded, out highscoreID); } public static List LoadScore(int songID, EGameMode gameMode) { - var scores = new List(); - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _HighscoreFilePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return scores; - } - - using (var command = new SQLiteCommand(connection)) - { - int medley = 0; - int duet = 0; - int shortSong = 0; - switch (gameMode) - { - case EGameMode.TR_GAMEMODE_MEDLEY: - medley = 1; - break; - case EGameMode.TR_GAMEMODE_DUET: - duet = 1; - break; - case EGameMode.TR_GAMEMODE_SHORTSONG: - shortSong = 1; - break; - } - - int dataBaseSongID = _GetDataBaseSongID(songID, command); - if (dataBaseSongID < 0) - return scores; - - command.CommandText = "SELECT PlayerName, Score, Date, Difficulty, LineNr, id FROM Scores " + - "WHERE [SongID] = @SongID AND [Medley] = @Medley AND [Duet] = @Duet AND [ShortSong] = @ShortSong " + - "ORDER BY [Score] DESC"; - command.Parameters.Add("@SongID", DbType.Int32, 0).Value = dataBaseSongID; - command.Parameters.Add("@Medley", DbType.Int32, 0).Value = medley; - command.Parameters.Add("@Duet", DbType.Int32, 0).Value = duet; - command.Parameters.Add("@ShortSong", DbType.Int32, 0).Value = shortSong; - - SQLiteDataReader reader = command.ExecuteReader(); - if (reader != null && reader.HasRows) - { - while (reader.Read()) - { - var score = new SDBScoreEntry - { - Name = reader.GetString(0), - Score = reader.GetInt32(1), - Date = new DateTime(reader.GetInt64(2)).ToString("dd/MM/yyyy"), - Difficulty = (EGameDifficulty)reader.GetInt32(3), - VoiceNr = reader.GetInt32(4), - ID = reader.GetInt32(5) - }; - - scores.Add(score); - } - reader.Dispose(); - } - } - } - return scores; + return _HighscoreDB.LoadScore(songID, gameMode); } - private static void _IncreaseSongCounter(int dataBaseSongID, SQLiteCommand command) + public static int AddScore(SPlayer player) { - command.CommandText = "UPDATE Songs SET NumPlayed = NumPlayed + 1 WHERE [id] = @id"; - command.Parameters.Add("@id", DbType.Int32, 0).Value = dataBaseSongID; - command.ExecuteNonQuery(); + return _HighscoreDB.AddScore(player); } - private static int _GetDataBaseSongID(int songID, SQLiteCommand command) + public static void IncreaseSongCounter(int dataBaseSongID) { - CSong song = CSongs.GetSong(songID); - - if (song == null) - return -1; - - return _GetDataBaseSongID(song.Artist, song.Title, 0, command); + _HighscoreDB.IncreaseSongCounter(dataBaseSongID); } - private static int _GetDataBaseSongID(string artist, string title, int defNumPlayed, SQLiteCommand command) + public static bool GetCover(string fileName, ref CTexture tex, int maxSize) { - command.CommandText = "SELECT id FROM Songs WHERE [Title] = @title AND [Artist] = @artist"; - command.Parameters.Add("@title", DbType.String, 0).Value = title; - command.Parameters.Add("@artist", DbType.String, 0).Value = artist; - - SQLiteDataReader reader = command.ExecuteReader(); - - if (reader != null && reader.HasRows) - { - reader.Read(); - int id = reader.GetInt32(0); - reader.Dispose(); - return id; - } - - if (reader != null) - reader.Close(); - - command.CommandText = "INSERT INTO Songs (Title, Artist, NumPlayed, DateAdded) " + - "VALUES (@title, @artist, @numplayed, @dateadded)"; - command.Parameters.Add("@title", DbType.String, 0).Value = title; - command.Parameters.Add("@artist", DbType.String, 0).Value = artist; - command.Parameters.Add("@numplayed", DbType.Int32, 0).Value = defNumPlayed; - command.Parameters.Add("@dateadded", DbType.Int64, 0).Value = DateTime.Now.Ticks; - command.ExecuteNonQuery(); - - command.CommandText = "SELECT id FROM Songs WHERE [Title] = @title AND [Artist] = @artist"; - command.Parameters.Add("@title", DbType.String, 0).Value = title; - command.Parameters.Add("@artist", DbType.String, 0).Value = artist; - - reader = command.ExecuteReader(); - - if (reader != null) - { - reader.Read(); - int id = reader.GetInt32(0); - reader.Dispose(); - return id; - } - - return -1; - } - - private static bool _GetDataBaseSongInfos(int songID, out string artist, out string title, out int numPlayed, out DateTime dateAdded, string filePath) - { - artist = String.Empty; - title = String.Empty; - numPlayed = 0; - dateAdded = DateTime.Today; - - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + filePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return false; - } - - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "SELECT Artist, Title, NumPlayed, DateAdded FROM Songs WHERE [id] = @id"; - command.Parameters.Add("@id", DbType.String, 0).Value = songID; - - SQLiteDataReader reader; - try - { - reader = command.ExecuteReader(); - } - catch (Exception) - { - return false; - } - - if (reader != null && reader.HasRows) - { - reader.Read(); - artist = reader.GetString(0); - title = reader.GetString(1); - numPlayed = reader.GetInt32(2); - dateAdded = new DateTime(reader.GetInt64(3)); - reader.Dispose(); - return true; - } - if (reader != null) - reader.Dispose(); - } - } - - return false; - } - - private static bool _InitHighscoreDB() - { - string oldDBFilePath = Path.Combine(Environment.CurrentDirectory, CSettings.FileOldHighscoreDB); - if (File.Exists(oldDBFilePath)) - { - if (File.Exists(_HighscoreFilePath)) - { - if (!_CreateOrConvert(oldDBFilePath)) - { - CLog.LogError("Cannot init Highscore DB: Error opening database: " + oldDBFilePath, true, true); - return false; - } - if (!_CreateOrConvert(_HighscoreFilePath)) - { - CLog.LogError("Cannot init Highscore DB: Error opening database: " + _HighscoreFilePath, true, true); - return false; - } - if (!_ImportData(oldDBFilePath)) - { - CLog.LogError("Cannot init Highscore DB: Error importing data", true, true); - return false; - } - } - else - { - File.Copy(oldDBFilePath, _HighscoreFilePath); - if (!_CreateOrConvert(_HighscoreFilePath)) - { - CLog.LogError("Cannot init Highscore DB: Error opening database: " + _HighscoreFilePath, true, true); - return false; - } - } - File.Delete(oldDBFilePath); - } - else if (!_CreateOrConvert(_HighscoreFilePath)) - { - CLog.LogError("Cannot init Highscore DB: Error opening database: " + _HighscoreFilePath, true, true); - return false; - } - return true; - } - - private static void _CreateHighscoreDB(string filePath) - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + filePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return; - } - - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "CREATE TABLE IF NOT EXISTS Version ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Value INTEGER NOT NULL);"; - command.ExecuteNonQuery(); - - command.CommandText = "INSERT INTO Version (id, Value) VALUES(NULL, @Value)"; - command.Parameters.Add("@Value", DbType.Int32).Value = CSettings.DatabaseHighscoreVersion; - command.ExecuteNonQuery(); - - command.CommandText = "CREATE TABLE IF NOT EXISTS Songs ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "Artist TEXT NOT NULL, Title TEXT NOT NULL, NumPlayed INTEGER, DateAdded BIGING);"; - command.ExecuteNonQuery(); - - command.CommandText = "CREATE TABLE IF NOT EXISTS Scores ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "SongID INTEGER NOT NULL, PlayerName TEXT NOT NULL, Score INTEGER NOT NULL, LineNr INTEGER NOT NULL, Date BIGINT NOT NULL, " + - "Medley INTEGER NOT NULL, Duet INTEGER NOT NULL, ShortSong INTEGER NOT NULL, Difficulty INTEGER NOT NULL);"; - command.ExecuteNonQuery(); - } - } - } - - private static void _CreateHighscoreDBV1(string filePath) - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + filePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return; - } - - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "CREATE TABLE IF NOT EXISTS Version ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Value INTEGER NOT NULL);"; - command.ExecuteNonQuery(); - - command.CommandText = "INSERT INTO Version (id, Value) VALUES(NULL, 1 )"; - command.ExecuteNonQuery(); - - command.CommandText = "CREATE TABLE IF NOT EXISTS Songs ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "Artist TEXT NOT NULL, Title TEXT NOT NULL, NumPlayed INTEGER);"; - command.ExecuteNonQuery(); - - command.CommandText = "CREATE TABLE IF NOT EXISTS Scores ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "SongID INTEGER NOT NULL, PlayerName TEXT NOT NULL, Score INTEGER NOT NULL, LineNr INTEGER NOT NULL, Date BIGINT NOT NULL, " + - "Medley INTEGER NOT NULL, Duet INTEGER NOT NULL, Difficulty INTEGER NOT NULL);"; - command.ExecuteNonQuery(); - } - } - } - - /// - /// Creates a new Vocaluxe Database if no file exists. Converts an existing old Ultrastar Deluxe highscore database into vocaluxe format. - /// - /// Database file path - /// - private static bool _CreateOrConvert(string filePath) - { - bool result = true; - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + filePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return false; - } - - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "PRAGMA user_version"; - SQLiteDataReader reader = command.ExecuteReader(); - reader.Read(); - - int version = reader.GetInt32(0); - - reader.Dispose(); - - //Check if old scores table exists - command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='US_Scores';"; - reader = command.ExecuteReader(); - reader.Read(); - bool scoresTableExists = reader.HasRows; - - reader.Dispose(); - - command.CommandText = "SELECT Value FROM Version"; - reader = null; - - try - { - reader = command.ExecuteReader(); - } - catch (Exception) {} - - if (reader == null || reader.FieldCount == 0) - { - // create new database/tables - if (version == 1) - { - //Check for USDX 1.1 DB - _CreateHighscoreDBV1(filePath); - result &= !_ConvertFrom110(filePath); - result &= _UpdateDatabase(1, connection); - result &= _UpdateDatabase(2, connection); - } - else if (version == 0 && scoresTableExists) - { - //Check for USDX 1.01 or CMD Mod DB - _CreateHighscoreDBV1(filePath); - result &= _ConvertFrom101(filePath); - result &= _UpdateDatabase(1, connection); - result &= _UpdateDatabase(2, connection); - } - else - _CreateHighscoreDB(filePath); - } - else - { - reader.Read(); - int currentVersion = reader.GetInt32(0); - if (currentVersion < CSettings.DatabaseHighscoreVersion) - { - // update database - result &= _UpdateDatabase(currentVersion, connection); - } - } - - if (reader != null) - reader.Dispose(); - } - } - - return result; - } - - /// - /// Converts a USDX 1.1 database into the Vocaluxe format - /// - /// Database file path - /// True if succeeded - private static bool _ConvertFrom110(string filePath) - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + filePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return false; - } - - using (var command = new SQLiteCommand(connection)) - { - //The USDX database has no column for LineNr, Medley and Duet so just fill 0 in there - command.CommandText = - "INSERT INTO Scores (SongID, PlayerName, Score, LineNr, Date, Medley, Duet, Difficulty) SELECT SongID, Player, Score, '0', Date, '0', '0', Difficulty from US_Scores"; - command.ExecuteNonQuery(); - - command.CommandText = "INSERT INTO Songs SELECT ID, Artist, Title, TimesPlayed from US_Songs"; - command.ExecuteNonQuery(); - - var scores = new List(); - var songs = new List(); - - command.CommandText = "SELECT id, PlayerName, Date FROM Scores"; - SQLiteDataReader reader = command.ExecuteReader(); - - if (reader != null && reader.HasRows) - { - while (reader.Read()) - { - var data = new SData {Id = reader.GetInt32(0), Str1 = reader.GetString(1)}; - Int64 ticks = 0; - - try - { - ticks = reader.GetInt64(2); - } - catch {} - - data.Ticks = _UnixTimeToTicks((int)ticks); - - scores.Add(data); - } - reader.Close(); - } - - command.CommandText = "SELECT id, Artist, Title FROM Songs"; - - reader = command.ExecuteReader(); - - if (reader != null && reader.HasRows) - { - while (reader.Read()) - { - var data = new SData {Id = reader.GetInt32(0), Str1 = reader.GetString(1), Str2 = reader.GetString(2)}; - songs.Add(data); - } - } - - if (reader != null) - reader.Dispose(); - - SQLiteTransaction transaction = connection.BeginTransaction(); - // update Title and Artist strings - foreach (SData data in songs) - { - command.CommandText = "UPDATE Songs SET [Artist] = @artist, [Title] = @title WHERE [ID] = @id"; - command.Parameters.Add("@title", DbType.String, 0).Value = data.Str2; - command.Parameters.Add("@artist", DbType.String, 0).Value = data.Str1; - command.Parameters.Add("@id", DbType.Int32, 0).Value = data.Id; - command.ExecuteNonQuery(); - } - - // update player names - foreach (SData data in scores) - { - command.CommandText = "UPDATE Scores SET [PlayerName] = @player, [Date] = @date WHERE [id] = @id"; - command.Parameters.Add("@player", DbType.String, 0).Value = data.Str1; - command.Parameters.Add("@date", DbType.Int64, 0).Value = data.Ticks; - command.Parameters.Add("@id", DbType.Int32, 0).Value = data.Id; - command.ExecuteNonQuery(); - } - transaction.Commit(); - - //Delete old tables after conversion - command.CommandText = "DROP TABLE US_Scores;"; - command.ExecuteNonQuery(); - - command.CommandText = "DROP TABLE US_Songs;"; - command.ExecuteNonQuery(); - - command.CommandText = "DROP TABLE us_statistics_info;"; - command.ExecuteNonQuery(); - - //This versioning is not used in Vocaluxe so reset it to 0 - command.CommandText = "PRAGMA user_version = 0"; - command.ExecuteNonQuery(); - } - } - - return true; - } - - /// - /// Converts a USDX 1.01 or CMD 1.01 database to Vocaluxe format - /// - /// Database file path - /// True if succeeded - private static bool _ConvertFrom101(string filePath) - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + filePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return false; - } - - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "PRAGMA table_info(US_Scores);"; - bool dateExists = false; - using (SQLiteDataReader reader = command.ExecuteReader()) - { - //Check for column Date - while (reader.Read()) - { - for (int i = 0; i < reader.FieldCount; i++) - { - if (reader.GetName(i) == "name") - { - if (reader.GetString(i) == "Date") - dateExists = true; - break; - } - } - } - } - - //This is a USDX 1.01 DB - command.CommandText = !dateExists - ? "INSERT INTO Scores (SongID, PlayerName, Score, LineNr, Date, Medley, Duet, Difficulty) SELECT SongID, Player, Score, '0', '0', '0', '0', Difficulty from US_Scores" - : "INSERT INTO Scores (SongID, PlayerName, Score, LineNr, Date, Medley, Duet, Difficulty) SELECT SongID, Player, Score, '0', Date, '0', '0', Difficulty from US_Scores"; - command.ExecuteNonQuery(); - - command.CommandText = "INSERT INTO Songs SELECT ID, Artist, Title, TimesPlayed from US_Songs"; - command.ExecuteNonQuery(); - - // convert from CP1252 to UTF8 - var scores = new List(); - var songs = new List(); - - Sqlite3.sqlite3 oldDB; - int res = Sqlite3.sqlite3_open(filePath, out oldDB); - - if (res != Sqlite3.SQLITE_OK) - CLog.LogError("Error opening Database: " + filePath + " (" + Sqlite3.sqlite3_errmsg(oldDB) + ")"); - else - { - var stmt = new Sqlite3.Vdbe(); - res = Sqlite3.sqlite3_prepare_v2(oldDB, "SELECT id, Artist, Title FROM Songs", -1, ref stmt, 0); - - if (res != Sqlite3.SQLITE_OK) - CLog.LogError("Error query Database: " + filePath + " (" + Sqlite3.sqlite3_errmsg(oldDB) + ")"); - else - { - //Sqlite3.sqlite3_step(Stmt); - Encoding utf8 = Encoding.UTF8; - Encoding cp1252 = Encoding.GetEncoding(1252); - - while (Sqlite3.sqlite3_step(stmt) == Sqlite3.SQLITE_ROW) - { - var data = new SData {Id = Sqlite3.sqlite3_column_int(stmt, 0)}; - - byte[] bytes = Sqlite3.sqlite3_column_rawbytes(stmt, 1); - data.Str1 = bytes != null ? utf8.GetString(Encoding.Convert(cp1252, utf8, bytes)) : "Someone"; - - bytes = Sqlite3.sqlite3_column_rawbytes(stmt, 2); - data.Str2 = bytes != null ? utf8.GetString(Encoding.Convert(cp1252, utf8, bytes)) : "Someone"; - - songs.Add(data); - } - Sqlite3.sqlite3_finalize(stmt); - } - - stmt = new Sqlite3.Vdbe(); - - // ReSharper disable ConvertIfStatementToConditionalTernaryExpression - if (!dateExists) - // ReSharper restore ConvertIfStatementToConditionalTernaryExpression - res = Sqlite3.sqlite3_prepare_v2(oldDB, "SELECT id, PlayerName FROM Scores", -1, ref stmt, 0); - else - res = Sqlite3.sqlite3_prepare_v2(oldDB, "SELECT id, PlayerName, Date FROM Scores", -1, ref stmt, 0); - - if (res != Sqlite3.SQLITE_OK) - CLog.LogError("Error query Database: " + filePath + " (" + Sqlite3.sqlite3_errmsg(oldDB) + ")"); - else - { - //Sqlite3.sqlite3_step(Stmt); - Encoding utf8 = Encoding.UTF8; - Encoding cp1252 = Encoding.GetEncoding(1252); - - while (Sqlite3.sqlite3_step(stmt) == Sqlite3.SQLITE_ROW) - { - var data = new SData {Id = Sqlite3.sqlite3_column_int(stmt, 0)}; - - byte[] bytes = Sqlite3.sqlite3_column_rawbytes(stmt, 1); - data.Str1 = bytes != null ? utf8.GetString(Encoding.Convert(cp1252, utf8, bytes)) : "Someone"; - - if (dateExists) - data.Ticks = _UnixTimeToTicks(Sqlite3.sqlite3_column_int(stmt, 2)); - - scores.Add(data); - } - Sqlite3.sqlite3_finalize(stmt); - } - } - Sqlite3.sqlite3_close(oldDB); - - SQLiteTransaction transaction = connection.BeginTransaction(); - - // update Title and Artist strings - foreach (SData data in songs) - { - command.CommandText = "UPDATE Songs SET [Artist] = @artist, [Title] = @title WHERE [ID] = @id"; - command.Parameters.Add("@title", DbType.String, 0).Value = data.Str2; - command.Parameters.Add("@artist", DbType.String, 0).Value = data.Str1; - command.Parameters.Add("@id", DbType.Int32, 0).Value = data.Id; - command.ExecuteNonQuery(); - } - - // update player names - foreach (SData data in scores) - { - if (!dateExists) - command.CommandText = "UPDATE Scores SET [PlayerName] = @player WHERE [id] = @id"; - else - { - command.CommandText = "UPDATE Scores SET [PlayerName] = @player, [Date] = @date WHERE [id] = @id"; - command.Parameters.Add("@date", DbType.Int64, 0).Value = data.Ticks; - } - command.Parameters.Add("@player", DbType.String, 0).Value = data.Str1; - command.Parameters.Add("@id", DbType.Int32, 0).Value = data.Id; - command.ExecuteNonQuery(); - } - transaction.Commit(); - - //Delete old tables after conversion - command.CommandText = "DROP TABLE US_Scores;"; - command.ExecuteNonQuery(); - - command.CommandText = "DROP TABLE US_Songs;"; - command.ExecuteNonQuery(); - } - } - - return true; - } - - private static bool _UpdateDatabase(int currentVersion, SQLiteConnection connection) - { - bool updated = true; - - if (currentVersion < 2) - updated &= _ConvertV1toV2(connection); - if (currentVersion < 3) - updated &= _ConvertV2toV3(connection); - - return updated; - } - - private static bool _ConvertV1toV2(SQLiteConnection connection) - { - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "ALTER TABLE Scores ADD ShortSong INTEGER"; - command.ExecuteNonQuery(); - command.CommandText = "UPDATE Scores SET [ShortSong] = @ShortSong"; - command.Parameters.Add("@ShortSong", DbType.Int32, 0).Value = 0; - command.ExecuteNonQuery(); - command.CommandText = "UPDATE Version SET [Value] = @version"; - command.Parameters.Add("@version", DbType.Int32, 0).Value = 2; - command.ExecuteNonQuery(); - } - - return true; - } - - private static bool _ConvertV2toV3(SQLiteConnection connection) - { - var command = new SQLiteCommand(connection) {CommandText = "ALTER TABLE Songs ADD DateAdded BIGINT"}; - - command.ExecuteNonQuery(); - command.CommandText = "UPDATE Songs SET [DateAdded] = @DateAdded"; - command.Parameters.Add("@DateAdded", DbType.Int64, 0).Value = DateTime.Now.Ticks; - command.ExecuteNonQuery(); - command.CommandText = "UPDATE Version SET [Value] = @version"; - command.Parameters.Add("@version", DbType.Int32, 0).Value = 3; - command.ExecuteNonQuery(); - - //Read NumPlayed from Scores and save to Songs - command.CommandText = "SELECT SongID, Date FROM Scores ORDER BY Date ASC"; - - SQLiteDataReader reader; - try - { - reader = command.ExecuteReader(); - } - catch (Exception) - { - return false; - } - - long lastDateAdded = -1; - int lastID = -1; - DateTime dt = new DateTime(1, 1, 1, 0, 0, 5); - long sec = dt.Ticks; - List ids = new List(); - while (reader.Read()) - { - int id = reader.GetInt32(0); - long dateAdded = reader.GetInt64(1); - if (id != lastID || dateAdded > lastDateAdded + sec) - { - ids.Add(id); - lastID = id; - lastDateAdded = dateAdded; - } - } - reader.Dispose(); - - foreach (int id in ids) - _IncreaseSongCounter(id, command); - command.Dispose(); - - return true; - } - - private static bool _ImportData(string sourceDBPath) - { - #region open db - using (var connSource = new SQLiteConnection()) - { - connSource.ConnectionString = "Data Source=" + sourceDBPath; - - try - { - connSource.Open(); - } - catch (Exception e) - { - CLog.LogError("Error on import high score data. Can't open source database \"" + sourceDBPath + "\" (" + e.Message + ")"); - return false; - } - #endregion open db - - using (var cmdSource = new SQLiteCommand(connSource)) - { - #region import table scores - cmdSource.CommandText = "SELECT SongID, PlayerName, Score, LineNr, Date, Medley, Duet, ShortSong, Difficulty FROM Scores"; - SQLiteDataReader source = cmdSource.ExecuteReader(); - if (source == null) - return false; - - if (source.FieldCount == 0) - { - source.Close(); - return true; - } - - while (source.Read()) - { - int songid = source.GetInt32(0); - string player = source.GetString(1); - int score = source.GetInt32(2); - int linenr = source.GetInt32(3); - long date = source.GetInt64(4); - int medley = source.GetInt32(5); - int duet = source.GetInt32(6); - int shortsong = source.GetInt32(7); - int diff = source.GetInt32(8); - - string artist, title; - DateTime dateadded; - int numplayed; - if (_GetDataBaseSongInfos(songid, out artist, out title, out numplayed, out dateadded, sourceDBPath)) - AddScore(player, score, linenr, date, medley, duet, shortsong, diff, artist, title, numplayed, _HighscoreFilePath); - } - #endregion import table scores - - source.Close(); - } - } - - return true; - } - #endregion Highscores - - #region Cover - public static bool GetCover(string coverPath, ref CTexture tex, int maxSize) - { - if (!File.Exists(coverPath)) - { - CLog.LogError("Can't find File: " + coverPath); - return false; - } - - lock (_CoverMutex) - { - if (_ConnectionCover == null) - { - _ConnectionCover = new SQLiteConnection {ConnectionString = "Data Source=" + _CoverFilePath}; - _ConnectionCover.Open(); - } - - using (var command = new SQLiteCommand(_ConnectionCover)) - { - command.CommandText = "SELECT id, width, height FROM Cover WHERE [Path] = @path"; - command.Parameters.Add("@path", DbType.String, 0).Value = coverPath; - - SQLiteDataReader reader = command.ExecuteReader(); - - if (reader != null && reader.HasRows) - { - reader.Read(); - int id = reader.GetInt32(0); - int w = reader.GetInt32(1); - int h = reader.GetInt32(2); - reader.Close(); - - command.CommandText = "SELECT Data FROM CoverData WHERE CoverID = @id"; - command.Parameters.Add("@id", DbType.Int32).Value = id; - reader = command.ExecuteReader(); - - if (reader.HasRows) - { - reader.Read(); - byte[] data = _GetBytes(reader); - reader.Dispose(); - tex = CDraw.EnqueueTexture(w, h, data); - return true; - } - } - else - { - if (reader != null) - reader.Close(); - - if (_TransactionCover == null) - _TransactionCover = _ConnectionCover.BeginTransaction(); - - Bitmap origin; - try - { - origin = new Bitmap(coverPath); - } - catch (Exception) - { - CLog.LogError("Error loading Texture: " + coverPath); - return false; - } - - int w = maxSize; - int h = maxSize; - byte[] data; - - try - { - if (origin.Width >= origin.Height && origin.Width > w) - h = (int)Math.Round((float)w / origin.Width * origin.Height); - else if (origin.Height > origin.Width && origin.Height > h) - w = (int)Math.Round((float)h / origin.Height * origin.Width); - - using (var bmp = new Bitmap(w, h)) - { - using (Graphics g = Graphics.FromImage(bmp)) - g.DrawImage(origin, new Rectangle(0, 0, w, h)); - - data = new byte[w * h * 4]; - BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); - Marshal.Copy(bmpData.Scan0, data, 0, w * h * 4); - bmp.UnlockBits(bmpData); - } - } - finally - { - origin.Dispose(); - } - - tex = CDraw.EnqueueTexture(w, h, data); - - command.CommandText = "INSERT INTO Cover (Path, width, height) VALUES (@path, @w, @h)"; - command.Parameters.Add("@w", DbType.Int32).Value = w; - command.Parameters.Add("@h", DbType.Int32).Value = h; - command.Parameters.Add("@path", DbType.String, 0).Value = coverPath; - command.ExecuteNonQuery(); - - command.CommandText = "SELECT id FROM Cover WHERE [Path] = @path"; - command.Parameters.Add("@path", DbType.String, 0).Value = coverPath; - reader = command.ExecuteReader(); - - if (reader != null) - { - reader.Read(); - int id = reader.GetInt32(0); - reader.Dispose(); - command.CommandText = "INSERT INTO CoverData (CoverID, Data) VALUES (@id, @data)"; - command.Parameters.Add("@id", DbType.Int32).Value = id; - command.Parameters.Add("@data", DbType.Binary, 20).Value = data; - command.ExecuteReader(); - return true; - } - } - } - } - return false; + return _CoverDB.GetCover(fileName, ref tex, maxSize); } public static void CommitCovers() { - lock (_CoverMutex) - { - _CommitCovers(); - } + _CoverDB.CommitCovers(); } - /// - /// You have to hold the CoverMutex when calling this! - /// - private static void _CommitCovers() - { - if (_TransactionCover != null) - { - _TransactionCover.Commit(); - _TransactionCover.Dispose(); - _TransactionCover = null; - } - } - - public static void Close() - { - //Do commit and close atomicly otherwhise we may loose changes - lock (_CoverMutex) - { - CommitCovers(); - - if (_ConnectionCover != null) - { - _ConnectionCover.Dispose(); - _ConnectionCover = null; - } - } - } - - private static bool _InitCoverDB() - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _CoverFilePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return false; - } - - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "SELECT Value FROM Version"; - - SQLiteDataReader reader = null; - - try - { - reader = command.ExecuteReader(); - } - catch (Exception) {} - - if (reader == null) - { - // create new database/tables - _CreateCoverDB(); - } - else if (reader.FieldCount == 0) - { - // create new database/tables - _CreateCoverDB(); - } - else - { - reader.Read(); - - if (reader.GetInt32(0) < CSettings.DatabaseHighscoreVersion) - { - // update database - } - } - if (reader != null) - reader.Dispose(); - } - } - return true; - } - - private static void _CreateCoverDB() - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _CoverFilePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return; - } - - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "CREATE TABLE IF NOT EXISTS Version ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Value INTEGER NOT NULL);"; - command.ExecuteNonQuery(); - - command.CommandText = "INSERT INTO Version (id, Value) VALUES(NULL, @Value)"; - command.Parameters.Add("@Value", DbType.Int32).Value = CSettings.DatabaseCoverVersion; - command.ExecuteNonQuery(); - - command.CommandText = "CREATE TABLE IF NOT EXISTS Cover ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "Path TEXT NOT NULL, width INTEGER NOT NULL, height INTEGER NOT NULL);"; - command.ExecuteNonQuery(); - - command.CommandText = "CREATE TABLE IF NOT EXISTS CoverData ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "CoverID INTEGER NOT NULL, Data BLOB NOT NULL);"; - command.ExecuteNonQuery(); - } - } - } - #endregion Cover - - #region CreditsRessources public static bool GetCreditsRessource(string fileName, ref CTexture tex) { - bool result = false; - - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _CreditsRessourcesFilePath; - try - { - connection.Open(); - } - catch (Exception) - { - return false; - } - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "SELECT id, width, height FROM Images WHERE [Path] = @path"; - command.Parameters.Add("@path", DbType.String, 0).Value = fileName; - - SQLiteDataReader reader = command.ExecuteReader(); - - if (reader != null && reader.HasRows) - { - reader.Read(); - int id = reader.GetInt32(0); - int w = reader.GetInt32(1); - int h = reader.GetInt32(2); - reader.Close(); - - command.CommandText = "SELECT Data FROM ImageData WHERE ImageID = @id"; - command.Parameters.Add("@id", DbType.Int32).Value = id; - reader = command.ExecuteReader(); - - if (reader.HasRows) - { - result = true; - reader.Read(); - byte[] data = _GetBytes(reader); - tex = CDraw.AddTexture(w, h, data); - /*using (Bitmap bmp = new Bitmap(w, h)) - { - BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); - Marshal.Copy(data, 0, bmpData.Scan0, w * h * 4); - bmp.UnlockBits(bmpData); - bmp.Save(fileName); - }*/ - } - } - - if (reader != null) - reader.Dispose(); - } - } - - return result; - } - - //If you want to add an image to db, call this method! - /* - private static bool _AddImageToCreditsDB(String imagePath) - { - bool result = false; - - if (File.Exists(imagePath)) - { - using (SQLiteConnection connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _CreditsRessourcesFilePath; - try - { - connection.Open(); - } - catch (Exception) - { - return false; - } - using (SQLiteCommand command = new SQLiteCommand(connection)) - { - Bitmap origin; - try - { - origin = new Bitmap(imagePath); - } - catch (Exception) - { - CLog.LogError("Error loading Texture: " + imagePath); - return false; - } - int w = origin.Width; - int h = origin.Height; - byte[] data; - - try - { - data = new byte[w * h * 4]; - - BitmapData bmpData = origin.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); - Marshal.Copy(bmpData.Scan0, data, 0, w * h * 4); - origin.UnlockBits(bmpData); - - command.CommandText = "INSERT INTO Images (Path, width, height) VALUES (@path, @w, @h)"; - command.Parameters.Add("@path", DbType.String, 0).Value = Path.GetFileName(imagePath); - command.Parameters.Add("@w", DbType.Int32, 0).Value = w; - command.Parameters.Add("@h", DbType.Int32, 0).Value = h; - command.ExecuteNonQuery(); - - command.CommandText = "SELECT id FROM Images WHERE [Path] = @path"; - command.Parameters.Add("@path", DbType.String, 0).Value = Path.GetFileName(imagePath); - SQLiteDataReader reader = command.ExecuteReader(); - - if (reader != null) - { - reader.Read(); - int id = reader.GetInt32(0); - reader.Close(); - command.CommandText = "INSERT INTO ImageData (ImageID, Data) VALUES (@id, @data)"; - command.Parameters.Add("@id", DbType.Int32, 20).Value = id; - command.Parameters.Add("@data", DbType.Binary, 20).Value = data; - command.ExecuteReader(); - result = true; - } - } - finally - { - origin.Dispose(); - } - } - } - } - - return result; - } -*/ - - private static bool _InitCreditsRessourcesDB() - { - using (var connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _CreditsRessourcesFilePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return false; - } - - using (var command = new SQLiteCommand(connection)) - { - command.CommandText = "SELECT Value FROM Version"; - - SQLiteDataReader reader = null; - - try - { - reader = command.ExecuteReader(); - } - catch (Exception) {} - - if (reader == null) - { - // Log error - CLog.LogError("Can't find Credits-DB!"); - } - else if (reader.FieldCount == 0) - { - // Log error - CLog.LogError("Can't find Credits-DB! Field-Count = 0"); - } - else - { - reader.Read(); - - if (reader.GetInt32(0) < CSettings.DatabaseHighscoreVersion) - { - // update database - } - } - - if (reader != null) - reader.Dispose(); - } - } - return true; - } - - /* - private static void _CreateCreditsRessourcesDB() - { - using (SQLiteConnection connection = new SQLiteConnection()) - { - connection.ConnectionString = "Data Source=" + _CreditsRessourcesFilePath; - - try - { - connection.Open(); - } - catch (Exception) - { - return; - } - - using (SQLiteCommand command = new SQLiteCommand(connection)) - { - command.CommandText = "CREATE TABLE IF NOT EXISTS Version ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Value INTEGER NOT NULL);"; - command.ExecuteNonQuery(); - - command.CommandText = "INSERT INTO Version (id, Value) VALUES(NULL, @Value)"; - command.Parameters.Add("@Value", DbType.Int32).Value = CSettings.DatabaseCreditsRessourcesVersion; - command.ExecuteNonQuery(); - - command.CommandText = "CREATE TABLE IF NOT EXISTS Images ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "Path TEXT NOT NULL, width INTEGER NOT NULL, height INTEGER NOT NULL);"; - command.ExecuteNonQuery(); - - command.CommandText = "CREATE TABLE IF NOT EXISTS ImageData ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + - "ImageID INTEGER NOT NULL, Data BLOB NOT NULL);"; - command.ExecuteNonQuery(); - } - } - } -*/ - #endregion CreditsRessources - - private static byte[] _GetBytes(SQLiteDataReader reader) - { - const int chunkSize = 2 * 1024; - var buffer = new byte[chunkSize]; - long fieldOffset = 0; - using (var stream = new MemoryStream()) - { - long bytesRead; - while ((bytesRead = reader.GetBytes(0, fieldOffset, buffer, 0, buffer.Length)) > 0) - { - var actualRead = new byte[bytesRead]; - Buffer.BlockCopy(buffer, 0, actualRead, 0, (int)bytesRead); - stream.Write(actualRead, 0, actualRead.Length); - fieldOffset += bytesRead; - } - return stream.ToArray(); - } - } - - private static long _UnixTimeToTicks(int unixTime) - { - var t70 = new DateTime(1970, 1, 1, 0, 0, 0, 0); - t70 = t70.AddSeconds(unixTime); - return t70.Ticks; + return _ResourceDB.GetCreditsRessource(fileName, ref tex); } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Database/CCoverDB.cs b/Vocaluxe/Lib/Database/CCoverDB.cs new file mode 100644 index 00000000..7723e2d4 --- /dev/null +++ b/Vocaluxe/Lib/Database/CCoverDB.cs @@ -0,0 +1,234 @@ +#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 System.Data; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using Vocaluxe.Base; +using VocaluxeLib.Draw; +#if WIN +using System.Data.SQLite; + +#else +using Mono.Data.Sqlite; +#endif + +namespace Vocaluxe.Lib.Database +{ + public class CCoverDB : CDatabaseBase + { + //You have to lock all actions using cover connection or transaction, otherwhise order is not guaranted + private readonly object _CoverMutex = new object(); + private SQLiteTransaction _TransactionCover; + + public CCoverDB(string filePath) : base(filePath) {} + + public override bool Init() + { + if (!base.Init()) + return false; + + if (_Version < 0) + return _CreateCoverDB(); + if (_Version < CSettings.DatabaseCoverVersion) + throw new NotImplementedException("Upgrading of cover DB not implemented"); + return true; + } + + public override void Close() + { + //Do commit and close atomicly otherwhise we may loose changes + lock (_CoverMutex) + { + _CommitCovers(); + + base.Close(); + } + } + + public bool GetCover(string coverPath, ref CTexture tex, int maxSize) + { + if (_Connection == null) + return false; + if (!File.Exists(coverPath)) + { + CLog.LogError("Can't find File: " + coverPath); + return false; + } + + lock (_CoverMutex) + { + //Double check here because we may have just closed our connection + if (_Connection == null) + return false; + using (var command = new SQLiteCommand(_Connection)) + { + command.CommandText = "SELECT id, width, height FROM Cover WHERE [Path] = @path"; + command.Parameters.Add("@path", DbType.String, 0).Value = coverPath; + + SQLiteDataReader reader = command.ExecuteReader(); + + if (reader != null && reader.HasRows) + { + reader.Read(); + int id = reader.GetInt32(0); + int w = reader.GetInt32(1); + int h = reader.GetInt32(2); + reader.Close(); + + command.CommandText = "SELECT Data FROM CoverData WHERE CoverID = @id"; + command.Parameters.Add("@id", DbType.Int32).Value = id; + reader = command.ExecuteReader(); + + if (reader.HasRows) + { + reader.Read(); + byte[] data = _GetBytes(reader); + reader.Dispose(); + tex = CDraw.EnqueueTexture(w, h, data); + return true; + } + } + else + { + if (reader != null) + reader.Close(); + + if (_TransactionCover == null) + _TransactionCover = _Connection.BeginTransaction(); + + Bitmap origin; + try + { + origin = new Bitmap(coverPath); + } + catch (Exception) + { + CLog.LogError("Error loading Texture: " + coverPath); + return false; + } + + int w = maxSize; + int h = maxSize; + byte[] data; + + try + { + if (origin.Width >= origin.Height && origin.Width > w) + h = (int)Math.Round((float)w / origin.Width * origin.Height); + else if (origin.Height > origin.Width && origin.Height > h) + w = (int)Math.Round((float)h / origin.Height * origin.Width); + + using (var bmp = new Bitmap(w, h)) + { + using (Graphics g = Graphics.FromImage(bmp)) + g.DrawImage(origin, new Rectangle(0, 0, w, h)); + + data = new byte[w * h * 4]; + BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + Marshal.Copy(bmpData.Scan0, data, 0, w * h * 4); + bmp.UnlockBits(bmpData); + } + } + finally + { + origin.Dispose(); + } + + tex = CDraw.EnqueueTexture(w, h, data); + + command.CommandText = "INSERT INTO Cover (Path, width, height) VALUES (@path, @w, @h)"; + command.Parameters.Add("@w", DbType.Int32).Value = w; + command.Parameters.Add("@h", DbType.Int32).Value = h; + command.Parameters.Add("@path", DbType.String, 0).Value = coverPath; + command.ExecuteNonQuery(); + + command.CommandText = "SELECT id FROM Cover WHERE [Path] = @path"; + command.Parameters.Add("@path", DbType.String, 0).Value = coverPath; + reader = command.ExecuteReader(); + + if (reader != null) + { + reader.Read(); + int id = reader.GetInt32(0); + reader.Dispose(); + command.CommandText = "INSERT INTO CoverData (CoverID, Data) VALUES (@id, @data)"; + command.Parameters.Add("@id", DbType.Int32).Value = id; + command.Parameters.Add("@data", DbType.Binary, 20).Value = data; + command.ExecuteReader(); + return true; + } + } + } + } + return false; + } + + public void CommitCovers() + { + lock (_CoverMutex) + { + _CommitCovers(); + } + } + + /// + /// You have to hold the CoverMutex when calling this! + /// + private void _CommitCovers() + { + if (_TransactionCover == null) + return; + _TransactionCover.Commit(); + _TransactionCover.Dispose(); + _TransactionCover = null; + } + + private bool _CreateCoverDB() + { + try + { + using (var command = new SQLiteCommand(_Connection)) + { + command.CommandText = "CREATE TABLE IF NOT EXISTS Version (Value INTEGER NOT NULL);"; + command.ExecuteNonQuery(); + + command.CommandText = "INSERT INTO Version (Value) VALUES(@Value)"; + command.Parameters.Add("@Value", DbType.Int32).Value = CSettings.DatabaseCoverVersion; + command.ExecuteNonQuery(); + + command.CommandText = "CREATE TABLE IF NOT EXISTS Cover ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "Path TEXT NOT NULL, width INTEGER NOT NULL, height INTEGER NOT NULL);"; + command.ExecuteNonQuery(); + + command.CommandText = "CREATE TABLE IF NOT EXISTS CoverData ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "CoverID INTEGER NOT NULL, Data BLOB NOT NULL);"; + command.ExecuteNonQuery(); + } + } + catch (Exception e) + { + CLog.LogError("Error creating Cover DB " + e); + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Database/CDatabaseBase.cs b/Vocaluxe/Lib/Database/CDatabaseBase.cs new file mode 100644 index 00000000..068530e5 --- /dev/null +++ b/Vocaluxe/Lib/Database/CDatabaseBase.cs @@ -0,0 +1,112 @@ +#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 System.IO; +using Vocaluxe.Base; +#if WIN +using System.Data.SQLite; + +#else +using Mono.Data.Sqlite; +#endif + +namespace Vocaluxe.Lib.Database +{ + public class CDatabaseBase + { + protected readonly string _FilePath; + protected SQLiteConnection _Connection; + + protected int _Version; //Version of the database (value of row "Value" in table "Version") or -1 if none + + public CDatabaseBase(string filePath) + { + _FilePath = filePath; + } + + public virtual bool Init() + { + _Connection = new SQLiteConnection("Data Source=" + _FilePath); + try + { + _Connection.Open(); + } + catch (Exception) + { + _Connection.Dispose(); + _Connection = null; + return false; + } + + using (var command = new SQLiteCommand(_Connection)) + { + command.CommandText = "SELECT Value FROM Version"; + + SQLiteDataReader reader = null; + + try + { + reader = command.ExecuteReader(); + } + catch (Exception) {} + + if (reader == null || !reader.Read() || reader.FieldCount == 0) + _Version = -1; + else + _Version = reader.GetInt32(0); + + if (reader != null) + reader.Dispose(); + } + return true; + } + + public virtual void Close() + { + if (_Connection != null) + { + _Connection.Dispose(); + _Connection = null; + } + } + + protected static long _UnixTimeToTicks(int unixTime) + { + var t70 = new DateTime(1970, 1, 1, 0, 0, 0, 0); + t70 = t70.AddSeconds(unixTime); + return t70.Ticks; + } + + protected static byte[] _GetBytes(SQLiteDataReader reader, int field = 0) + { + const int chunkSize = 2 * 1024; + var buffer = new byte[chunkSize]; + long fieldOffset = 0; + using (var stream = new MemoryStream()) + { + int bytesRead; + while ((bytesRead = (int)reader.GetBytes(field, fieldOffset, buffer, 0, buffer.Length)) > 0) + { + stream.Write(buffer, 0, bytesRead); + fieldOffset += bytesRead; + } + return stream.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Database/CHighscoreDB.cs b/Vocaluxe/Lib/Database/CHighscoreDB.cs new file mode 100644 index 00000000..43d25424 --- /dev/null +++ b/Vocaluxe/Lib/Database/CHighscoreDB.cs @@ -0,0 +1,1021 @@ +#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 System.Collections.Generic; +using System.Data; +using System.IO; +using System.Text; +using Community.CsharpSqlite; +using Vocaluxe.Base; +using VocaluxeLib; +using VocaluxeLib.Songs; +#if WIN +using System.Data.SQLite; + +#else +using Mono.Data.Sqlite; +#endif + +namespace Vocaluxe.Lib.Database +{ + public class CHighscoreDB : CDatabaseBase + { + private struct SData + { + public int Id; + public long Ticks; + public string Str1; + public string Str2; + } + + public CHighscoreDB(string filePath) : base(filePath) {} + + public override bool Init() + { + string oldDBFilePath = Path.Combine(Environment.CurrentDirectory, CSettings.FileOldHighscoreDB); + if (File.Exists(oldDBFilePath)) + { + if (File.Exists(_FilePath)) + { + if (!_CreateOrConvert(oldDBFilePath)) + { + CLog.LogError("Cannot init Highscore DB: Error opening database: " + oldDBFilePath, true, true); + return false; + } + if (!_CreateOrConvert(_FilePath)) + { + CLog.LogError("Cannot init Highscore DB: Error opening database: " + _FilePath, true, true); + return false; + } + if (!_ImportData(oldDBFilePath)) + { + CLog.LogError("Cannot init Highscore DB: Error importing data", true, true); + return false; + } + } + else + { + File.Copy(oldDBFilePath, _FilePath); + if (!_CreateOrConvert(_FilePath)) + { + CLog.LogError("Cannot init Highscore DB: Error opening database: " + _FilePath, true, true); + return false; + } + } + File.Delete(oldDBFilePath); + } + else if (!_CreateOrConvert(_FilePath)) + { + CLog.LogError("Cannot init Highscore DB: Error opening database: " + _FilePath, true, true); + return false; + } + return true; + } + + public override void Close() + { + //Do nothing + } + + public bool GetDataBaseSongInfos(string artist, string title, out int numPlayed, out DateTime dateAdded, out int highscoreID) + { + string sArtist; + string sTitle; + int songID; + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + _FilePath; + + try + { + connection.Open(); + } + catch (Exception) {} + + using (var command = new SQLiteCommand(connection)) + { + songID = _GetDataBaseSongID(artist, title, 0, command); + highscoreID = songID; + } + } + return _GetDataBaseSongInfos(songID, out sArtist, out sTitle, out numPlayed, out dateAdded, _FilePath); + } + + public void IncreaseSongCounter(int dataBaseSongID) + { + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + _FilePath; + + try + { + connection.Open(); + } + catch (Exception) {} + + using (var command = new SQLiteCommand(connection)) + _IncreaseSongCounter(dataBaseSongID, command); + } + } + + public int AddScore(string playerName, int score, int lineNr, long date, int medley, int duet, int shortSong, int difficulty, + string artist, string title, int numPlayed, string filePath) + { + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + filePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return -1; + } + + using (var command = new SQLiteCommand(connection)) + { + int dataBaseSongID = _GetDataBaseSongID(artist, title, numPlayed, command); + int result = _AddScore(playerName, score, lineNr, date, medley, duet, shortSong, difficulty, dataBaseSongID, command); + return result; + } + } + } + + public int AddScore(SPlayer player) + { + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + _FilePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return -1; + } + + int medley = 0; + int duet = 0; + int shortSong = 0; + switch (player.GameMode) + { + case EGameMode.TR_GAMEMODE_MEDLEY: + medley = 1; + break; + case EGameMode.TR_GAMEMODE_DUET: + duet = 1; + break; + case EGameMode.TR_GAMEMODE_SHORTSONG: + shortSong = 1; + break; + } + + using (var command = new SQLiteCommand(connection)) + { + int dataBaseSongID = CSongs.GetSong(player.SongID).DataBaseSongID; + return _AddScore(CProfiles.GetPlayerName(player.ProfileID), (int)Math.Round(player.Points), player.VoiceNr, player.DateTicks, medley, + duet, shortSong, (int)CProfiles.GetDifficulty(player.ProfileID), dataBaseSongID, command); + } + } + } + + private int _AddScore(string playerName, int score, int lineNr, long date, int medley, int duet, int shortSong, int difficulty, + int dataBaseSongID, SQLiteCommand command) + { + int lastInsertID = -1; + + if (dataBaseSongID >= 0) + { + command.CommandText = "SELECT id FROM Scores WHERE SongID = @SongID AND PlayerName = @PlayerName AND Score = @Score AND " + + "LineNr = @LineNr AND Date = @Date AND Medley = @Medley AND Duet = @Duet AND ShortSong = @ShortSong AND Difficulty = @Difficulty"; + command.Parameters.Add("@SongID", DbType.Int32, 0).Value = dataBaseSongID; + command.Parameters.Add("@PlayerName", DbType.String, 0).Value = playerName; + command.Parameters.Add("@Score", DbType.Int32, 0).Value = score; + command.Parameters.Add("@LineNr", DbType.Int32, 0).Value = lineNr; + command.Parameters.Add("@Date", DbType.Int64, 0).Value = date; + command.Parameters.Add("@Medley", DbType.Int32, 0).Value = medley; + command.Parameters.Add("@Duet", DbType.Int32, 0).Value = duet; + command.Parameters.Add("@ShortSong", DbType.Int32, 0).Value = shortSong; + command.Parameters.Add("@Difficulty", DbType.Int32, 0).Value = difficulty; + + SQLiteDataReader reader = null; + try + { + reader = command.ExecuteReader(); + } + catch (Exception) {} + + if (reader != null && reader.HasRows) + { + if (reader.Read()) + return reader.GetInt32(0); + } + + if (reader != null) + reader.Dispose(); + + command.CommandText = "INSERT INTO Scores (SongID, PlayerName, Score, LineNr, Date, Medley, Duet, ShortSong, Difficulty) " + + "VALUES (@SongID, @PlayerName, @Score, @LineNr, @Date, @Medley, @Duet, @ShortSong, @Difficulty)"; + command.Parameters.Add("@SongID", DbType.Int32, 0).Value = dataBaseSongID; + command.Parameters.Add("@PlayerName", DbType.String, 0).Value = playerName; + command.Parameters.Add("@Score", DbType.Int32, 0).Value = score; + command.Parameters.Add("@LineNr", DbType.Int32, 0).Value = lineNr; + command.Parameters.Add("@Date", DbType.Int64, 0).Value = date; + command.Parameters.Add("@Medley", DbType.Int32, 0).Value = medley; + command.Parameters.Add("@Duet", DbType.Int32, 0).Value = duet; + command.Parameters.Add("@ShortSong", DbType.Int32, 0).Value = shortSong; + command.Parameters.Add("@Difficulty", DbType.Int32, 0).Value = difficulty; + command.ExecuteNonQuery(); + + //Read last insert line + command.CommandText = "SELECT id FROM Scores ORDER BY id DESC LIMIT 0, 1"; + + reader = command.ExecuteReader(); + + if (reader != null && reader.HasRows) + { + while (reader.Read()) + lastInsertID = reader.GetInt32(0); + reader.Dispose(); + } + } + + return lastInsertID; + } + + public List LoadScore(int songID, EGameMode gameMode) + { + var scores = new List(); + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + _FilePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return scores; + } + + using (var command = new SQLiteCommand(connection)) + { + int medley = 0; + int duet = 0; + int shortSong = 0; + switch (gameMode) + { + case EGameMode.TR_GAMEMODE_MEDLEY: + medley = 1; + break; + case EGameMode.TR_GAMEMODE_DUET: + duet = 1; + break; + case EGameMode.TR_GAMEMODE_SHORTSONG: + shortSong = 1; + break; + } + + int dataBaseSongID = _GetDataBaseSongID(songID, command); + if (dataBaseSongID < 0) + return scores; + + command.CommandText = "SELECT PlayerName, Score, Date, Difficulty, LineNr, id FROM Scores " + + "WHERE [SongID] = @SongID AND [Medley] = @Medley AND [Duet] = @Duet AND [ShortSong] = @ShortSong " + + "ORDER BY [Score] DESC"; + command.Parameters.Add("@SongID", DbType.Int32, 0).Value = dataBaseSongID; + command.Parameters.Add("@Medley", DbType.Int32, 0).Value = medley; + command.Parameters.Add("@Duet", DbType.Int32, 0).Value = duet; + command.Parameters.Add("@ShortSong", DbType.Int32, 0).Value = shortSong; + + SQLiteDataReader reader = command.ExecuteReader(); + if (reader != null && reader.HasRows) + { + while (reader.Read()) + { + var score = new SDBScoreEntry + { + Name = reader.GetString(0), + Score = reader.GetInt32(1), + Date = new DateTime(reader.GetInt64(2)).ToString("dd/MM/yyyy"), + Difficulty = (EGameDifficulty)reader.GetInt32(3), + VoiceNr = reader.GetInt32(4), + ID = reader.GetInt32(5) + }; + + scores.Add(score); + } + reader.Dispose(); + } + } + } + return scores; + } + + private void _IncreaseSongCounter(int dataBaseSongID, SQLiteCommand command) + { + command.CommandText = "UPDATE Songs SET NumPlayed = NumPlayed + 1 WHERE [id] = @id"; + command.Parameters.Add("@id", DbType.Int32, 0).Value = dataBaseSongID; + command.ExecuteNonQuery(); + } + + private int _GetDataBaseSongID(int songID, SQLiteCommand command) + { + CSong song = CSongs.GetSong(songID); + + if (song == null) + return -1; + + return _GetDataBaseSongID(song.Artist, song.Title, 0, command); + } + + private int _GetDataBaseSongID(string artist, string title, int defNumPlayed, SQLiteCommand command) + { + command.CommandText = "SELECT id FROM Songs WHERE [Title] = @title AND [Artist] = @artist"; + command.Parameters.Add("@title", DbType.String, 0).Value = title; + command.Parameters.Add("@artist", DbType.String, 0).Value = artist; + + SQLiteDataReader reader = command.ExecuteReader(); + + if (reader != null && reader.HasRows) + { + reader.Read(); + int id = reader.GetInt32(0); + reader.Dispose(); + return id; + } + + if (reader != null) + reader.Close(); + + command.CommandText = "INSERT INTO Songs (Title, Artist, NumPlayed, DateAdded) " + + "VALUES (@title, @artist, @numplayed, @dateadded)"; + command.Parameters.Add("@title", DbType.String, 0).Value = title; + command.Parameters.Add("@artist", DbType.String, 0).Value = artist; + command.Parameters.Add("@numplayed", DbType.Int32, 0).Value = defNumPlayed; + command.Parameters.Add("@dateadded", DbType.Int64, 0).Value = DateTime.Now.Ticks; + command.ExecuteNonQuery(); + + command.CommandText = "SELECT id FROM Songs WHERE [Title] = @title AND [Artist] = @artist"; + command.Parameters.Add("@title", DbType.String, 0).Value = title; + command.Parameters.Add("@artist", DbType.String, 0).Value = artist; + + reader = command.ExecuteReader(); + + if (reader != null) + { + reader.Read(); + int id = reader.GetInt32(0); + reader.Dispose(); + return id; + } + + return -1; + } + + private bool _GetDataBaseSongInfos(int songID, out string artist, out string title, out int numPlayed, out DateTime dateAdded, string filePath) + { + artist = String.Empty; + title = String.Empty; + numPlayed = 0; + dateAdded = DateTime.Today; + + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + filePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return false; + } + + using (var command = new SQLiteCommand(connection)) + { + command.CommandText = "SELECT Artist, Title, NumPlayed, DateAdded FROM Songs WHERE [id] = @id"; + command.Parameters.Add("@id", DbType.String, 0).Value = songID; + + SQLiteDataReader reader; + try + { + reader = command.ExecuteReader(); + } + catch (Exception) + { + return false; + } + + if (reader != null && reader.HasRows) + { + reader.Read(); + artist = reader.GetString(0); + title = reader.GetString(1); + numPlayed = reader.GetInt32(2); + dateAdded = new DateTime(reader.GetInt64(3)); + reader.Dispose(); + return true; + } + if (reader != null) + reader.Dispose(); + } + } + + return false; + } + + private void _CreateHighscoreDB(string filePath) + { + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + filePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return; + } + + using (var command = new SQLiteCommand(connection)) + { + command.CommandText = "CREATE TABLE IF NOT EXISTS Version ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Value INTEGER NOT NULL);"; + command.ExecuteNonQuery(); + + command.CommandText = "INSERT INTO Version (id, Value) VALUES(NULL, @Value)"; + command.Parameters.Add("@Value", DbType.Int32).Value = CSettings.DatabaseHighscoreVersion; + command.ExecuteNonQuery(); + + command.CommandText = "CREATE TABLE IF NOT EXISTS Songs ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "Artist TEXT NOT NULL, Title TEXT NOT NULL, NumPlayed INTEGER, DateAdded BIGING);"; + command.ExecuteNonQuery(); + + command.CommandText = "CREATE TABLE IF NOT EXISTS Scores ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "SongID INTEGER NOT NULL, PlayerName TEXT NOT NULL, Score INTEGER NOT NULL, LineNr INTEGER NOT NULL, Date BIGINT NOT NULL, " + + "Medley INTEGER NOT NULL, Duet INTEGER NOT NULL, ShortSong INTEGER NOT NULL, Difficulty INTEGER NOT NULL);"; + command.ExecuteNonQuery(); + } + } + } + + private void _CreateHighscoreDBV1(string filePath) + { + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + filePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return; + } + + using (var command = new SQLiteCommand(connection)) + { + command.CommandText = "CREATE TABLE IF NOT EXISTS Version ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Value INTEGER NOT NULL);"; + command.ExecuteNonQuery(); + + command.CommandText = "INSERT INTO Version (id, Value) VALUES(NULL, 1 )"; + command.ExecuteNonQuery(); + + command.CommandText = "CREATE TABLE IF NOT EXISTS Songs ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "Artist TEXT NOT NULL, Title TEXT NOT NULL, NumPlayed INTEGER);"; + command.ExecuteNonQuery(); + + command.CommandText = "CREATE TABLE IF NOT EXISTS Scores ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "SongID INTEGER NOT NULL, PlayerName TEXT NOT NULL, Score INTEGER NOT NULL, LineNr INTEGER NOT NULL, Date BIGINT NOT NULL, " + + "Medley INTEGER NOT NULL, Duet INTEGER NOT NULL, Difficulty INTEGER NOT NULL);"; + command.ExecuteNonQuery(); + } + } + } + + /// + /// Creates a new Vocaluxe Database if no file exists. Converts an existing old Ultrastar Deluxe highscore database into vocaluxe format. + /// + /// Database file path + /// + private bool _CreateOrConvert(string filePath) + { + bool result = true; + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + filePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return false; + } + + using (var command = new SQLiteCommand(connection)) + { + command.CommandText = "PRAGMA user_version"; + SQLiteDataReader reader = command.ExecuteReader(); + reader.Read(); + + int version = reader.GetInt32(0); + + reader.Dispose(); + + //Check if old scores table exists + command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='US_Scores';"; + reader = command.ExecuteReader(); + reader.Read(); + bool scoresTableExists = reader.HasRows; + + reader.Dispose(); + + command.CommandText = "SELECT Value FROM Version"; + reader = null; + + try + { + reader = command.ExecuteReader(); + } + catch (Exception) {} + + if (reader == null || reader.FieldCount == 0) + { + // create new database/tables + if (version == 1) + { + //Check for USDX 1.1 DB + _CreateHighscoreDBV1(filePath); + result &= !_ConvertFrom110(filePath); + result &= _UpdateDatabase(1, connection); + result &= _UpdateDatabase(2, connection); + } + else if (version == 0 && scoresTableExists) + { + //Check for USDX 1.01 or CMD Mod DB + _CreateHighscoreDBV1(filePath); + result &= _ConvertFrom101(filePath); + result &= _UpdateDatabase(1, connection); + result &= _UpdateDatabase(2, connection); + } + else + _CreateHighscoreDB(filePath); + } + else + { + reader.Read(); + int currentVersion = reader.GetInt32(0); + if (currentVersion < CSettings.DatabaseHighscoreVersion) + { + // update database + result &= _UpdateDatabase(currentVersion, connection); + } + } + + if (reader != null) + reader.Dispose(); + } + } + + return result; + } + + /// + /// Converts a USDX 1.1 database into the Vocaluxe format + /// + /// Database file path + /// True if succeeded + private bool _ConvertFrom110(string filePath) + { + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + filePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return false; + } + + using (var command = new SQLiteCommand(connection)) + { + //The USDX database has no column for LineNr, Medley and Duet so just fill 0 in there + command.CommandText = + "INSERT INTO Scores (SongID, PlayerName, Score, LineNr, Date, Medley, Duet, Difficulty) SELECT SongID, Player, Score, '0', Date, '0', '0', Difficulty from US_Scores"; + command.ExecuteNonQuery(); + + command.CommandText = "INSERT INTO Songs SELECT ID, Artist, Title, TimesPlayed from US_Songs"; + command.ExecuteNonQuery(); + + var scores = new List(); + var songs = new List(); + + command.CommandText = "SELECT id, PlayerName, Date FROM Scores"; + SQLiteDataReader reader = command.ExecuteReader(); + + if (reader != null && reader.HasRows) + { + while (reader.Read()) + { + var data = new SData {Id = reader.GetInt32(0), Str1 = reader.GetString(1)}; + Int64 ticks = 0; + + try + { + ticks = reader.GetInt64(2); + } + catch {} + + data.Ticks = _UnixTimeToTicks((int)ticks); + + scores.Add(data); + } + reader.Close(); + } + + command.CommandText = "SELECT id, Artist, Title FROM Songs"; + + reader = command.ExecuteReader(); + + if (reader != null && reader.HasRows) + { + while (reader.Read()) + { + var data = new SData {Id = reader.GetInt32(0), Str1 = reader.GetString(1), Str2 = reader.GetString(2)}; + songs.Add(data); + } + } + + if (reader != null) + reader.Dispose(); + + SQLiteTransaction transaction = connection.BeginTransaction(); + // update Title and Artist strings + foreach (SData data in songs) + { + command.CommandText = "UPDATE Songs SET [Artist] = @artist, [Title] = @title WHERE [ID] = @id"; + command.Parameters.Add("@title", DbType.String, 0).Value = data.Str2; + command.Parameters.Add("@artist", DbType.String, 0).Value = data.Str1; + command.Parameters.Add("@id", DbType.Int32, 0).Value = data.Id; + command.ExecuteNonQuery(); + } + + // update player names + foreach (SData data in scores) + { + command.CommandText = "UPDATE Scores SET [PlayerName] = @player, [Date] = @date WHERE [id] = @id"; + command.Parameters.Add("@player", DbType.String, 0).Value = data.Str1; + command.Parameters.Add("@date", DbType.Int64, 0).Value = data.Ticks; + command.Parameters.Add("@id", DbType.Int32, 0).Value = data.Id; + command.ExecuteNonQuery(); + } + transaction.Commit(); + + //Delete old tables after conversion + command.CommandText = "DROP TABLE US_Scores;"; + command.ExecuteNonQuery(); + + command.CommandText = "DROP TABLE US_Songs;"; + command.ExecuteNonQuery(); + + command.CommandText = "DROP TABLE us_statistics_info;"; + command.ExecuteNonQuery(); + + //This versioning is not used in Vocaluxe so reset it to 0 + command.CommandText = "PRAGMA user_version = 0"; + command.ExecuteNonQuery(); + } + } + + return true; + } + + /// + /// Converts a USDX 1.01 or CMD 1.01 database to Vocaluxe format + /// + /// Database file path + /// True if succeeded + private bool _ConvertFrom101(string filePath) + { + using (var connection = new SQLiteConnection()) + { + connection.ConnectionString = "Data Source=" + filePath; + + try + { + connection.Open(); + } + catch (Exception) + { + return false; + } + + using (var command = new SQLiteCommand(connection)) + { + command.CommandText = "PRAGMA table_info(US_Scores);"; + bool dateExists = false; + using (SQLiteDataReader reader = command.ExecuteReader()) + { + //Check for column Date + while (reader.Read()) + { + for (int i = 0; i < reader.FieldCount; i++) + { + if (reader.GetName(i) == "name") + { + if (reader.GetString(i) == "Date") + dateExists = true; + break; + } + } + } + } + + //This is a USDX 1.01 DB + command.CommandText = !dateExists + ? "INSERT INTO Scores (SongID, PlayerName, Score, LineNr, Date, Medley, Duet, Difficulty) SELECT SongID, Player, Score, '0', '0', '0', '0', Difficulty from US_Scores" + : "INSERT INTO Scores (SongID, PlayerName, Score, LineNr, Date, Medley, Duet, Difficulty) SELECT SongID, Player, Score, '0', Date, '0', '0', Difficulty from US_Scores"; + command.ExecuteNonQuery(); + + command.CommandText = "INSERT INTO Songs SELECT ID, Artist, Title, TimesPlayed from US_Songs"; + command.ExecuteNonQuery(); + + // convert from CP1252 to UTF8 + var scores = new List(); + var songs = new List(); + + Sqlite3.sqlite3 oldDB; + int res = Sqlite3.sqlite3_open(filePath, out oldDB); + + if (res != Sqlite3.SQLITE_OK) + CLog.LogError("Error opening Database: " + filePath + " (" + Sqlite3.sqlite3_errmsg(oldDB) + ")"); + else + { + var stmt = new Sqlite3.Vdbe(); + res = Sqlite3.sqlite3_prepare_v2(oldDB, "SELECT id, Artist, Title FROM Songs", -1, ref stmt, 0); + + if (res != Sqlite3.SQLITE_OK) + CLog.LogError("Error query Database: " + filePath + " (" + Sqlite3.sqlite3_errmsg(oldDB) + ")"); + else + { + //Sqlite3.sqlite3_step(Stmt); + Encoding utf8 = Encoding.UTF8; + Encoding cp1252 = Encoding.GetEncoding(1252); + + while (Sqlite3.sqlite3_step(stmt) == Sqlite3.SQLITE_ROW) + { + var data = new SData {Id = Sqlite3.sqlite3_column_int(stmt, 0)}; + + byte[] bytes = Sqlite3.sqlite3_column_rawbytes(stmt, 1); + data.Str1 = bytes != null ? utf8.GetString(Encoding.Convert(cp1252, utf8, bytes)) : "Someone"; + + bytes = Sqlite3.sqlite3_column_rawbytes(stmt, 2); + data.Str2 = bytes != null ? utf8.GetString(Encoding.Convert(cp1252, utf8, bytes)) : "Someone"; + + songs.Add(data); + } + Sqlite3.sqlite3_finalize(stmt); + } + + stmt = new Sqlite3.Vdbe(); + + // ReSharper disable ConvertIfStatementToConditionalTernaryExpression + if (!dateExists) + // ReSharper restore ConvertIfStatementToConditionalTernaryExpression + res = Sqlite3.sqlite3_prepare_v2(oldDB, "SELECT id, PlayerName FROM Scores", -1, ref stmt, 0); + else + res = Sqlite3.sqlite3_prepare_v2(oldDB, "SELECT id, PlayerName, Date FROM Scores", -1, ref stmt, 0); + + if (res != Sqlite3.SQLITE_OK) + CLog.LogError("Error query Database: " + filePath + " (" + Sqlite3.sqlite3_errmsg(oldDB) + ")"); + else + { + //Sqlite3.sqlite3_step(Stmt); + Encoding utf8 = Encoding.UTF8; + Encoding cp1252 = Encoding.GetEncoding(1252); + + while (Sqlite3.sqlite3_step(stmt) == Sqlite3.SQLITE_ROW) + { + var data = new SData {Id = Sqlite3.sqlite3_column_int(stmt, 0)}; + + byte[] bytes = Sqlite3.sqlite3_column_rawbytes(stmt, 1); + data.Str1 = bytes != null ? utf8.GetString(Encoding.Convert(cp1252, utf8, bytes)) : "Someone"; + + if (dateExists) + data.Ticks = _UnixTimeToTicks(Sqlite3.sqlite3_column_int(stmt, 2)); + + scores.Add(data); + } + Sqlite3.sqlite3_finalize(stmt); + } + } + Sqlite3.sqlite3_close(oldDB); + + SQLiteTransaction transaction = connection.BeginTransaction(); + + // update Title and Artist strings + foreach (SData data in songs) + { + command.CommandText = "UPDATE Songs SET [Artist] = @artist, [Title] = @title WHERE [ID] = @id"; + command.Parameters.Add("@title", DbType.String, 0).Value = data.Str2; + command.Parameters.Add("@artist", DbType.String, 0).Value = data.Str1; + command.Parameters.Add("@id", DbType.Int32, 0).Value = data.Id; + command.ExecuteNonQuery(); + } + + // update player names + foreach (SData data in scores) + { + if (!dateExists) + command.CommandText = "UPDATE Scores SET [PlayerName] = @player WHERE [id] = @id"; + else + { + command.CommandText = "UPDATE Scores SET [PlayerName] = @player, [Date] = @date WHERE [id] = @id"; + command.Parameters.Add("@date", DbType.Int64, 0).Value = data.Ticks; + } + command.Parameters.Add("@player", DbType.String, 0).Value = data.Str1; + command.Parameters.Add("@id", DbType.Int32, 0).Value = data.Id; + command.ExecuteNonQuery(); + } + transaction.Commit(); + + //Delete old tables after conversion + command.CommandText = "DROP TABLE US_Scores;"; + command.ExecuteNonQuery(); + + command.CommandText = "DROP TABLE US_Songs;"; + command.ExecuteNonQuery(); + } + } + + return true; + } + + private bool _UpdateDatabase(int currentVersion, SQLiteConnection connection) + { + bool updated = true; + + if (currentVersion < 2) + updated &= _ConvertV1toV2(connection); + if (currentVersion < 3) + updated &= _ConvertV2toV3(connection); + + return updated; + } + + private bool _ConvertV1toV2(SQLiteConnection connection) + { + using (var command = new SQLiteCommand(connection)) + { + command.CommandText = "ALTER TABLE Scores ADD ShortSong INTEGER"; + command.ExecuteNonQuery(); + command.CommandText = "UPDATE Scores SET [ShortSong] = @ShortSong"; + command.Parameters.Add("@ShortSong", DbType.Int32, 0).Value = 0; + command.ExecuteNonQuery(); + command.CommandText = "UPDATE Version SET [Value] = @version"; + command.Parameters.Add("@version", DbType.Int32, 0).Value = 2; + command.ExecuteNonQuery(); + } + + return true; + } + + private bool _ConvertV2toV3(SQLiteConnection connection) + { + var command = new SQLiteCommand(connection) {CommandText = "ALTER TABLE Songs ADD DateAdded BIGINT"}; + + command.ExecuteNonQuery(); + command.CommandText = "UPDATE Songs SET [DateAdded] = @DateAdded"; + command.Parameters.Add("@DateAdded", DbType.Int64, 0).Value = DateTime.Now.Ticks; + command.ExecuteNonQuery(); + command.CommandText = "UPDATE Version SET [Value] = @version"; + command.Parameters.Add("@version", DbType.Int32, 0).Value = 3; + command.ExecuteNonQuery(); + + //Read NumPlayed from Scores and save to Songs + command.CommandText = "SELECT SongID, Date FROM Scores ORDER BY Date ASC"; + + SQLiteDataReader reader; + try + { + reader = command.ExecuteReader(); + } + catch (Exception) + { + return false; + } + + long lastDateAdded = -1; + int lastID = -1; + DateTime dt = new DateTime(1, 1, 1, 0, 0, 5); + long sec = dt.Ticks; + List ids = new List(); + while (reader.Read()) + { + int id = reader.GetInt32(0); + long dateAdded = reader.GetInt64(1); + if (id != lastID || dateAdded > lastDateAdded + sec) + { + ids.Add(id); + lastID = id; + lastDateAdded = dateAdded; + } + } + reader.Dispose(); + + foreach (int id in ids) + _IncreaseSongCounter(id, command); + command.Dispose(); + + return true; + } + + private bool _ImportData(string sourceDBPath) + { + #region open db + using (var connSource = new SQLiteConnection()) + { + connSource.ConnectionString = "Data Source=" + sourceDBPath; + + try + { + connSource.Open(); + } + catch (Exception e) + { + CLog.LogError("Error on import high score data. Can't open source database \"" + sourceDBPath + "\" (" + e.Message + ")"); + return false; + } + #endregion open db + + using (var cmdSource = new SQLiteCommand(connSource)) + { + #region import table scores + cmdSource.CommandText = "SELECT SongID, PlayerName, Score, LineNr, Date, Medley, Duet, ShortSong, Difficulty FROM Scores"; + SQLiteDataReader source = cmdSource.ExecuteReader(); + if (source == null) + return false; + + if (source.FieldCount == 0) + { + source.Close(); + return true; + } + + while (source.Read()) + { + int songid = source.GetInt32(0); + string player = source.GetString(1); + int score = source.GetInt32(2); + int linenr = source.GetInt32(3); + long date = source.GetInt64(4); + int medley = source.GetInt32(5); + int duet = source.GetInt32(6); + int shortsong = source.GetInt32(7); + int diff = source.GetInt32(8); + + string artist, title; + DateTime dateadded; + int numplayed; + if (_GetDataBaseSongInfos(songid, out artist, out title, out numplayed, out dateadded, sourceDBPath)) + AddScore(player, score, linenr, date, medley, duet, shortsong, diff, artist, title, numplayed, _FilePath); + } + #endregion import table scores + + source.Close(); + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Database/CResourceDB.cs b/Vocaluxe/Lib/Database/CResourceDB.cs new file mode 100644 index 00000000..51e07bf5 --- /dev/null +++ b/Vocaluxe/Lib/Database/CResourceDB.cs @@ -0,0 +1,253 @@ +#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 System.Data; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using Vocaluxe.Base; +using VocaluxeLib.Draw; +#if WIN +using System.Data.SQLite; + +#else +using Mono.Data.Sqlite; +#endif + +namespace Vocaluxe.Lib.Database +{ + public class CResourceDB : CDatabaseBase + { +#if DEBUG + private readonly string[] _FilesV1 = new string[] + { + "Logo_voc.png", "PerfectNoteStar.png", "redDot.png", "blueDot.png", "brunzel.png", "Darkice.png", "flokuep.png", "flamefire.png", "bohning.png", "mesand.png", + "babene03.png", "pantero.png", "Pinky007.png" + }; +#endif + public CResourceDB(string filePath) : base(filePath) {} + + public override bool Init() + { + if (!base.Init()) + return false; + if (_Version < 0) + { + CLog.LogError("Can't find Ressource-DB!"); + return false; + } + + if (_Version < CSettings.DatabaseCreditsRessourcesVersion) + { +#if DEBUG + return _CreateDB() && _UpdateV1(); +#else + CLog.LogError("Upgrading Ressource-DB not possible"); + return false; +#endif + } + + return true; + } + + public bool GetCreditsRessource(string fileName, ref CTexture tex) + { + if (_Connection == null) + return false; + bool result = false; + + using (var command = new SQLiteCommand(_Connection)) + { + command.CommandText = "SELECT id, width, height FROM Images WHERE [Path] = @path"; + command.Parameters.Add("@path", DbType.String, 0).Value = fileName; + + SQLiteDataReader reader = command.ExecuteReader(); + + if (reader != null && reader.HasRows) + { + reader.Read(); + int id = reader.GetInt32(0); + int w = reader.GetInt32(1); + int h = reader.GetInt32(2); + reader.Close(); + + command.CommandText = "SELECT Data FROM ImageData WHERE ImageID = @id"; + command.Parameters.Add("@id", DbType.Int32).Value = id; + reader = command.ExecuteReader(); + + if (reader.HasRows) + { + result = true; + reader.Read(); + byte[] data = _GetBytes(reader); + tex = CDraw.AddTexture(w, h, data); + } + } + + if (reader != null) + reader.Dispose(); + } + + return result; + } + +#if DEBUG + //Update from V0 (empty) to V1 + private bool _UpdateV1() + { + if (_Connection == null || _Version >= 1) + return false; + using (SQLiteTransaction transaction = _Connection.BeginTransaction()) + { + try + { + foreach (string file in _FilesV1) + { + string filePath = Path.Combine(Environment.CurrentDirectory, file); + if (!_AddImageToCreditsDB(filePath, transaction)) + { + transaction.Rollback(); + return false; + } + } + using (SQLiteCommand command = new SQLiteCommand(_Connection)) + { + command.Transaction = transaction; + command.CommandText = "Update Version SET Value=@Value)"; + command.Parameters.Add("@Value", DbType.Int32).Value = 1; + command.ExecuteNonQuery(); + } + transaction.Commit(); + } + catch (Exception) + { + transaction.Rollback(); + return false; + } + } + return true; + } + + //If you want to add an image to db, call this method! + private bool _AddImageToCreditsDB(string imagePath, SQLiteTransaction transaction) + { + if (_Connection == null || !File.Exists(imagePath)) + return false; + + Bitmap origin; + try + { + origin = new Bitmap(imagePath); + } + catch (Exception) + { + CLog.LogError("Error loading image: " + imagePath); + return false; + } + try + { + int w = origin.Width; + int h = origin.Height; + byte[] data = new byte[w * h * 4]; + + BitmapData bmpData = origin.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + Marshal.Copy(bmpData.Scan0, data, 0, w * h * 4); + origin.UnlockBits(bmpData); + + using (SQLiteCommand command = new SQLiteCommand(_Connection)) + { + command.Transaction = transaction; + command.CommandText = "INSERT INTO Images (Path, width, height) VALUES (@path, @w, @h)"; + command.Parameters.Add("@path", DbType.String).Value = Path.GetFileName(imagePath); + command.Parameters.Add("@w", DbType.Int32).Value = w; + command.Parameters.Add("@h", DbType.Int32).Value = h; + command.ExecuteNonQuery(); + } + + int id = -1; + using (SQLiteCommand command = new SQLiteCommand(_Connection)) + { + command.Transaction = transaction; + command.CommandText = "SELECT id FROM Images WHERE [Path] = @path"; + command.Parameters.Add("@path", DbType.String, 0).Value = Path.GetFileName(imagePath); + using (SQLiteDataReader reader = command.ExecuteReader()) + { + if (reader != null) + { + reader.Read(); + id = reader.GetInt32(0); + } + } + } + if (id < 0) + return false; + using (SQLiteCommand command = new SQLiteCommand(_Connection)) + { + command.Transaction = transaction; + command.CommandText = "INSERT INTO ImageData (ImageID, Data) VALUES (@id, @data)"; + command.Parameters.Add("@id", DbType.Int32).Value = id; + command.Parameters.Add("@data", DbType.Binary).Value = data; + command.ExecuteNonQuery(); + return true; + } + } + catch (Exception) + { + return false; + } + finally + { + origin.Dispose(); + } + } + + private bool _CreateDB() + { + if (_Connection == null) + return false; + + try + { + using (SQLiteCommand command = new SQLiteCommand(_Connection)) + { + command.CommandText = "CREATE TABLE IF NOT EXISTS Version (Value INTEGER NOT NULL);"; + command.ExecuteNonQuery(); + + command.CommandText = "INSERT INTO Version (Value) VALUES(0)"; + command.ExecuteNonQuery(); + + command.CommandText = "CREATE TABLE IF NOT EXISTS Images ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "Path TEXT NOT NULL, width INTEGER NOT NULL, height INTEGER NOT NULL);"; + command.ExecuteNonQuery(); + + command.CommandText = "CREATE TABLE IF NOT EXISTS ImageData ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + + "ImageID INTEGER NOT NULL, Data BLOB NOT NULL);"; + command.ExecuteNonQuery(); + } + } + catch (Exception e) + { + CLog.LogError("Error creating Ressource DB " + e); + return false; + } + return true; + } +#endif + } +} \ No newline at end of file diff --git a/Vocaluxe/Vocaluxe.csproj b/Vocaluxe/Vocaluxe.csproj index 3319c884..76a901b5 100644 --- a/Vocaluxe/Vocaluxe.csproj +++ b/Vocaluxe/Vocaluxe.csproj @@ -219,6 +219,10 @@ + + + + From c23eaa08346dca9b224950c1ef90af2219eb551e Mon Sep 17 00:00:00 2001 From: Flamefire Date: Wed, 7 May 2014 00:38:39 +0200 Subject: [PATCH 10/13] Notify listener on close --- Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs | 17 ++++++++++++++++- .../CGstreamerSharpAudioStream.cs | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs b/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs index d94e3780..05f5a35f 100644 --- a/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs +++ b/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs @@ -25,6 +25,8 @@ namespace Vocaluxe.Lib.Sound.Playback { protected bool _Initialized; protected readonly List _Streams = new List(); + private readonly List _StreamsToDelete = new List(); + private bool _InUpdate; private int _NextID; protected float _GlobalVolume = 1f; @@ -92,8 +94,18 @@ namespace Vocaluxe.Lib.Sound.Playback return; lock (_Streams) { + _InUpdate = true; foreach (IAudioStream stream in _Streams) stream.Update(); + //This is required because a stream may call the close listener from inside the update method + //lock() only protects from different threads not from the same, so we use StreamsToDelete to not modify _Streams while iterating it + _InUpdate = false; + if (_StreamsToDelete.Count > 0) + { + foreach (IAudioStream stream in _StreamsToDelete) + _Streams.Remove(stream); + _StreamsToDelete.Clear(); + } } } @@ -301,7 +313,10 @@ namespace Vocaluxe.Lib.Sound.Playback { lock (_Streams) { - _Streams.Remove(stream); + if (_InUpdate) //This is only possible if this is called from inside the update method + _StreamsToDelete.Add(stream); + else + _Streams.Remove(stream); } } } diff --git a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs index 994353ef..36b6a7d8 100644 --- a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs @@ -205,6 +205,8 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp } if (element.TargetState == State.Playing) element.SetState(State.Paused); //Stop output + if (_CloseStreamListener != null) + _CloseStreamListener.OnCloseStream(this); //Now really close it in the background var t = new System.Threading.Thread(() => _TerminateStream(element)) {Name = "GSt Terminate"}; t.Start(); From fc278e4ad99c49ecdc76d5da99f382ba6bf3a747 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Wed, 7 May 2014 00:39:12 +0200 Subject: [PATCH 11/13] Do not Dispose on finalize (might be already finalized) --- Vocaluxe/Lib/Sound/Record/CBuffer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Vocaluxe/Lib/Sound/Record/CBuffer.cs b/Vocaluxe/Lib/Sound/Record/CBuffer.cs index 65bf720f..c55e572f 100644 --- a/Vocaluxe/Lib/Sound/Record/CBuffer.cs +++ b/Vocaluxe/Lib/Sound/Record/CBuffer.cs @@ -45,7 +45,7 @@ namespace Vocaluxe.Lib.Sound.Record ~CBuffer() { - _Dispose(); + _Dispose(false); } public int GetNumHalfTones() @@ -110,13 +110,13 @@ namespace Vocaluxe.Lib.Sound.Record public void Dispose() { - _Dispose(); + _Dispose(true); GC.SuppressFinalize(this); } - private void _Dispose() + private void _Dispose(bool disposing) { - if (_PitchTracker != null) + if (disposing && _PitchTracker != null) { _PitchTracker.Dispose(); _PitchTracker = null; From 292a9f3c69b01c2de41bbd655eda08c65374d9c4 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Wed, 7 May 2014 00:39:30 +0200 Subject: [PATCH 12/13] Stop PA stream on pause --- Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs index cb579f97..ac913aaf 100644 --- a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs +++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs @@ -98,13 +98,21 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio get { return _Paused; } set { + if (_Paused == value) + return; + if (!_FileOpened || _Terminated) + return; _Paused = value; lock (_LockData) { if (_Paused) + { + _PaHandle.CheckError("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Stream)); _SyncTimer.Pause(); + } else { + _PaHandle.CheckError("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Stream)); _SyncTimer.Resume(); _EventDecode.Set(); } From e908f923e80f06f434fe029398dc91d2f5091810 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Wed, 7 May 2014 00:39:58 +0200 Subject: [PATCH 13/13] Fix unintended song loop due to api change --- .../PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs | 4 ++-- Vocaluxe/Base/CMain.cs | 4 ++-- Vocaluxe/Base/CSound.cs | 11 +++-------- Vocaluxe/Screens/CScreenSing.cs | 2 +- VocaluxeLib/IBasic.cs | 2 +- VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs | 2 +- 6 files changed, 10 insertions(+), 15 deletions(-) diff --git a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs index 6b5ccaad..2131cd14 100644 --- a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs +++ b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs @@ -503,7 +503,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe CBase.BackgroundMusic.SetStatus(true); - _PreviewStream = CBase.Sound.Load(song.GetMP3(), false); + _PreviewStream = CBase.Sound.Load(song.GetMP3()); CBase.Sound.SetPosition(_PreviewStream, song.Preview.StartTime); CBase.Sound.SetStreamVolume(_PreviewStream, 0f); CBase.Sound.Play(_PreviewStream); @@ -527,7 +527,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe CSong song = CBase.Songs.GetSongByID(songID); CBase.BackgroundMusic.SetStatus(true); - _PreviewStream = CBase.Sound.Load(song.GetMP3(), false); + _PreviewStream = CBase.Sound.Load(song.GetMP3()); CBase.Sound.SetPosition(_PreviewStream, song.Preview.StartTime); CBase.Sound.SetStreamVolume(_PreviewStream, 0f); CBase.Sound.Play(_PreviewStream); diff --git a/Vocaluxe/Base/CMain.cs b/Vocaluxe/Base/CMain.cs index 533444fd..f8d0b073 100644 --- a/Vocaluxe/Base/CMain.cs +++ b/Vocaluxe/Base/CMain.cs @@ -773,9 +773,9 @@ namespace Vocaluxe.Base class CBsound : ISound { - public int Load(string soundFile, bool prescan) + public int Load(string soundFile, bool loop = false, bool prescan = false) { - return CSound.Load(soundFile, prescan); + return CSound.Load(soundFile, loop, prescan); } public void SetPosition(int soundStream, float newPosition) diff --git a/Vocaluxe/Base/CSound.cs b/Vocaluxe/Base/CSound.cs index 4a39e03e..9d7f65dd 100644 --- a/Vocaluxe/Base/CSound.cs +++ b/Vocaluxe/Base/CSound.cs @@ -85,14 +85,9 @@ namespace Vocaluxe.Base } #region Stream Handling - public static int Load(string media) + public static int Load(string media, bool loop=false, bool prescan=false) { - return _Playback.Load(media); - } - - public static int Load(string media, bool prescan) - { - return _Playback.Load(media, prescan); + return _Playback.Load(media, loop, prescan); } public static void Close(int stream) @@ -194,7 +189,7 @@ namespace Vocaluxe.Base if (!File.Exists(file)) return -1; - int stream = Load(file, fade); + int stream = Load(file, false, fade); if (stream < 0) return -1; Play(stream); diff --git a/Vocaluxe/Screens/CScreenSing.cs b/Vocaluxe/Screens/CScreenSing.cs index 42cdab5d..d39a6202 100644 --- a/Vocaluxe/Screens/CScreenSing.cs +++ b/Vocaluxe/Screens/CScreenSing.cs @@ -597,7 +597,7 @@ namespace Vocaluxe.Screens songname += " (" + CGame.RoundNr + "/" + rounds + ")"; _Texts[_TextSongName].Text = songname; - _CurrentStream = CSound.Load(song.GetMP3(), true); + _CurrentStream = CSound.Load(song.GetMP3(), false, true); CSound.SetStreamVolume(_CurrentStream, _Volume); CSound.SetPosition(_CurrentStream, song.Start); _CurrentTime = song.Start; diff --git a/VocaluxeLib/IBasic.cs b/VocaluxeLib/IBasic.cs index 3ec5204c..739bbdf7 100644 --- a/VocaluxeLib/IBasic.cs +++ b/VocaluxeLib/IBasic.cs @@ -256,7 +256,7 @@ namespace VocaluxeLib public interface ISound { - int Load(string soundFile, bool prescan); + int Load(string soundFile, bool loop = false, bool prescan = false); void SetPosition(int soundStream, float newPosition); void Play(int soundStream); void Fade(int soundStream, float targetVolume, float duration); diff --git a/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs b/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs index 6743c1f8..b1106b7b 100644 --- a/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs +++ b/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs @@ -523,7 +523,7 @@ namespace VocaluxeLib.Menu.SongMenu if (song == null) return; - int stream = CBase.Sound.Load(Path.Combine(song.Folder, song.MP3FileName), true); + int stream = CBase.Sound.Load(Path.Combine(song.Folder, song.MP3FileName), true, true); float startposition = song.Preview.StartTime; float length = CBase.Sound.GetLength(stream);