diff --git a/Output/Sounds/CreditsMusic.mp3 b/Output/Sounds/CreditsMusic.mp3
new file mode 100644
index 00000000..58c8af7c
Binary files /dev/null and b/Output/Sounds/CreditsMusic.mp3 differ
diff --git a/Vocaluxe/Base/CBackgroundMusic.cs b/Vocaluxe/Base/CBackgroundMusic.cs
index 47e3f1f1..547df50e 100644
--- a/Vocaluxe/Base/CBackgroundMusic.cs
+++ b/Vocaluxe/Base/CBackgroundMusic.cs
@@ -32,7 +32,9 @@ namespace Vocaluxe.Base
private static readonly CSongPlayer _BGPlayer = new CSongPlayer();
private static readonly CSongPlayer _PreviewPlayer = new CSongPlayer(true);
+ private static readonly CSongPlayer _EventPlayer = new CSongPlayer(true);
private static CSongPlayer _CurPlayer;
+ private static CSongPlayer _PlayerBeforeEvent;
private static bool _OwnSongsAvailable;
private static EBackgroundMusicSource _MusicSource;
@@ -63,7 +65,7 @@ namespace Vocaluxe.Base
{
get
{
- if (IsPlayingPreview)
+ if (IsPlayingPreview || IsPlayingEventMusic)
{
return false;
}
@@ -134,6 +136,11 @@ namespace Vocaluxe.Base
}
}
+ public static bool IsPlayingEventMusic
+ {
+ get { return _CurPlayer == _EventPlayer; }
+ }
+
public static string ArtistAndTitle
{
get { return _CurPlayer.ArtistAndTitle; }
@@ -195,6 +202,7 @@ namespace Vocaluxe.Base
{
_BGPlayer.Close();
_PreviewPlayer.Close();
+ _EventPlayer.Close();
_BGMusicFiles.Clear();
_NotPlayedFiles.Clear();
@@ -215,7 +223,7 @@ namespace Vocaluxe.Base
CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume);
}
- if (IsPlayingPreview || _BGPlayer.SoundLoaded)
+ if (IsPlayingPreview || IsPlayingEventMusic || _BGPlayer.SoundLoaded)
{
_CurPlayer.Play();
}
@@ -241,7 +249,7 @@ namespace Vocaluxe.Base
public static void Next()
{
- if (IsPlayingPreview)
+ if (IsPlayingPreview || IsPlayingEventMusic)
{
return;
}
@@ -278,7 +286,7 @@ namespace Vocaluxe.Base
public static void Previous()
{
- if (IsPlayingPreview)
+ if (IsPlayingPreview || IsPlayingEventMusic)
{
return;
}
@@ -321,12 +329,45 @@ namespace Vocaluxe.Base
}
_CurPlayer.Update();
- if (!IsPlayingPreview && _BGPlayer.IsFinished)
+ if (_CurPlayer == _BGPlayer && _BGPlayer.IsFinished)
{
Next();
}
}
+ public static bool PlayEventMusic(string file)
+ {
+ if (!IsPlayingEventMusic)
+ {
+ Pause();
+ _PlayerBeforeEvent = _CurPlayer;
+ }
+
+ _EventPlayer.Load(file);
+ if (!_EventPlayer.SoundLoaded)
+ {
+ _CurPlayer = _PlayerBeforeEvent;
+ Play();
+ return false;
+ }
+
+ _CurPlayer = _EventPlayer;
+ Play();
+ return true;
+ }
+
+ public static void StopEventMusic()
+ {
+ if (!IsPlayingEventMusic)
+ {
+ return;
+ }
+
+ _EventPlayer.Close();
+ _CurPlayer = _PlayerBeforeEvent;
+ Play();
+ }
+
public static CTextureRef GetVideoTexture()
{
return _CurPlayer.GetVideoTexture();
diff --git a/Vocaluxe/Base/CMain.cs b/Vocaluxe/Base/CMain.cs
index eba613c8..ca1ade46 100644
--- a/Vocaluxe/Base/CMain.cs
+++ b/Vocaluxe/Base/CMain.cs
@@ -970,6 +970,11 @@ namespace Vocaluxe.Base
{
return CSound.IsPaused(streamId);
}
+
+ public void SetLoop(int streamId, bool loop)
+ {
+ CSound.SetLoop(streamId, loop);
+ }
}
class CBcover : ICover
diff --git a/Vocaluxe/Base/CSettings.cs b/Vocaluxe/Base/CSettings.cs
index 546d7257..9656f2f7 100644
--- a/Vocaluxe/Base/CSettings.cs
+++ b/Vocaluxe/Base/CSettings.cs
@@ -125,6 +125,7 @@ namespace Vocaluxe.Base
public const string FileNameSoundHighscore = "Highscore.mp3";
public const string FileNameSoundProgressBar = "ProgressBar.mp3";
public const string FileNameSoundWarning = "Warning.mp3";
+ public const string FileNameMusicCredits = "CreditsMusic.mp3";
public const string FolderNameSongs = "Songs";
public const string FolderNameProfiles = "Profiles";
diff --git a/Vocaluxe/Base/CSound.cs b/Vocaluxe/Base/CSound.cs
index b23aded8..6073a177 100644
--- a/Vocaluxe/Base/CSound.cs
+++ b/Vocaluxe/Base/CSound.cs
@@ -154,6 +154,11 @@ namespace Vocaluxe.Base
_Playback.SetStreamVolume(stream, volume);
}
+ public static void SetLoop(int stream, bool loop)
+ {
+ _Playback.SetLoop(stream, loop);
+ }
+
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
index d53011b3..de49dde5 100644
--- a/Vocaluxe/Lib/Sound/Playback/CAudioStreamBase.cs
+++ b/Vocaluxe/Lib/Sound/Playback/CAudioStreamBase.cs
@@ -24,7 +24,7 @@ namespace Vocaluxe.Lib.Sound.Playback
abstract class CAudioStreamBase : IAudioStream
{
protected readonly string _Medium;
- protected readonly bool _Loop;
+ protected volatile bool _Loop;
protected readonly EAudioEffect _Effect;
protected ICloseStreamListener _CloseStreamListener;
@@ -39,6 +39,12 @@ namespace Vocaluxe.Lib.Sound.Playback
get { return _Fading != null; }
}
+ public virtual bool Loop
+ {
+ get { return _Loop; }
+ set { _Loop = value; }
+ }
+
public virtual float Volume { get; set; }
public virtual float VolumeMax { get; set; }
public virtual float Length { get; protected set; }
diff --git a/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs b/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs
index 7bc417ea..481a4439 100644
--- a/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs
+++ b/Vocaluxe/Lib/Sound/Playback/CPlaybackBase.cs
@@ -277,6 +277,22 @@ namespace Vocaluxe.Lib.Sound.Playback
}
}
+ public void SetLoop(int streamId, bool loop)
+ {
+ if (!_Initialized)
+ {
+ return;
+ }
+
+ lock (_Streams)
+ {
+ if (_StreamExists(streamId))
+ {
+ _Streams[_GetStreamIndex(streamId)].Loop = loop;
+ }
+ }
+ }
+
public float GetLength(int streamId)
{
if (!_Initialized)
diff --git a/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs b/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs
index bfbda592..83a1caea 100644
--- a/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs
+++ b/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs
@@ -30,6 +30,8 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
private SACInstance _Instance;
private SFormatInfo _FormatInfo;
private float _CurrentTime;
+ private const int _MaxDecodeTries = 10;
+ private bool _ConvertFromFloat;
private string _FileName;
private bool _FileOpened;
@@ -88,6 +90,12 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
_CurrentTime = 0f;
+ _ConvertFromFloat = _FormatInfo.BitDepth == 32;
+ if (_ConvertFromFloat)
+ {
+ _FormatInfo.BitDepth = 16;
+ }
+
if (_FormatInfo.BitDepth != 16)
{
CLog.Error("Unsupported BitDepth in file " + fileName);
@@ -150,6 +158,25 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
return _FileOpened ? _CurrentTime : 0f;
}
+ private static byte[] _ConvertFloatToInt16(IntPtr source, int sourceSize)
+ {
+ var samples = new float[sourceSize / sizeof(float)];
+ if (samples.Length > 0)
+ {
+ Marshal.Copy(source, samples, 0, samples.Length);
+ }
+
+ var buffer = new byte[samples.Length * sizeof(Int16)];
+ for (var i = 0; i < samples.Length; i++)
+ {
+ var sample = (Int16)(Math.Max(-1f, Math.Min(1f, samples[i])) * Int16.MaxValue);
+ buffer[2 * i] = (byte)sample;
+ buffer[2 * i + 1] = (byte)(sample >> 8);
+ }
+
+ return buffer;
+ }
+
public void Decode(out byte[] buffer, out float timeStamp)
{
if (!_FileOpened)
@@ -159,7 +186,11 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
return;
}
- var frameFinished = CAcinerella.AcGetAudioFrame(_InstancePtr, _Audiodecoder);
+ var frameFinished = false;
+ for (var i = 0; i < _MaxDecodeTries && !frameFinished; i++)
+ {
+ frameFinished = CAcinerella.AcGetAudioFrame(_InstancePtr, _Audiodecoder);
+ }
if (frameFinished)
{
@@ -168,6 +199,12 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
timeStamp = (float)decoder.Timecode;
_CurrentTime = timeStamp;
//Console.WriteLine(_CurrentTime.ToString("#0.000") + " Buffer size: " + Decoder.buffer_size.ToString());
+ if (_ConvertFromFloat)
+ {
+ buffer = _ConvertFloatToInt16(decoder.Buffer, decoder.BufferSize);
+ return;
+ }
+
buffer = new byte[decoder.BufferSize];
if (decoder.BufferSize > 0)
diff --git a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs
index f94cc1b4..63f7c416 100644
--- a/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs
+++ b/Vocaluxe/Lib/Sound/Playback/GstreamerSharp/CGstreamerSharpAudioStream.cs
@@ -237,6 +237,7 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp
_Element["audio-sink"] = audioSinkBin;
_Element["flags"] = 1 << 1;
_Element["uri"] = new Uri(_Medium).AbsoluteUri;
+ _Element.Connect("about-to-finish", _OnAboutToFinish);
_Element.SetState(State.Paused);
// Passing CLOCK_TIME_NONE here causes the pipeline to block for a long time so with
@@ -332,6 +333,22 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp
}
}
+ private void _OnAboutToFinish(object sender, GLib.SignalArgs args)
+ {
+ if (!_Loop)
+ {
+ return;
+ }
+
+ lock (_ElementLock)
+ {
+ if (_Element != null)
+ {
+ _Element["uri"] = new Uri(_Medium).AbsoluteUri;
+ }
+ }
+ }
+
private bool _OnMessage(Message msg)
{
if (msg == null || msg.Handle == IntPtr.Zero)
diff --git a/Vocaluxe/Lib/Sound/Playback/IAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/IAudioStream.cs
index c4302c98..74ccb953 100644
--- a/Vocaluxe/Lib/Sound/Playback/IAudioStream.cs
+++ b/Vocaluxe/Lib/Sound/Playback/IAudioStream.cs
@@ -29,6 +29,7 @@ namespace Vocaluxe.Lib.Sound.Playback
{
int Id { get; }
bool IsFading { get; }
+ bool Loop { get; set; }
float Volume { get; set; }
float VolumeMax { get; set; }
float Length { get; }
diff --git a/Vocaluxe/Lib/Sound/Playback/IPlayback.cs b/Vocaluxe/Lib/Sound/Playback/IPlayback.cs
index 6543bbf6..dc33648f 100644
--- a/Vocaluxe/Lib/Sound/Playback/IPlayback.cs
+++ b/Vocaluxe/Lib/Sound/Playback/IPlayback.cs
@@ -51,6 +51,8 @@ namespace Vocaluxe.Lib.Sound.Playback
/// Volume in percent
void SetStreamVolume(int streamId, int volume);
+ void SetLoop(int streamId, bool loop);
+
float GetLength(int streamId);
float GetPosition(int streamId);
void SetPosition(int streamId, float position);
diff --git a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs
index ef71ca51..0b1939f7 100644
--- a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs
+++ b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs
@@ -40,6 +40,8 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
private readonly Stopwatch _Timer = new Stopwatch();
private IAudioDecoder _Decoder;
+ private bool _DataSinceLoop;
+ private bool _Looped;
private int _ByteCount;
private float _BytesPerSecond;
private bool _NoMoreData;
@@ -84,7 +86,11 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
var time = _CurrentTime + _Timer.ElapsedMilliseconds / 1000f;
if (time > Length)
{
- _Timer.Stop();
+ if (!_Loop)
+ {
+ _Timer.Stop();
+ }
+
time = Length;
}
@@ -242,6 +248,27 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
}
}
+ private void _DoLoop()
+ {
+ if (!_Looped)
+ {
+ CLog.Information("Looping audio file: " + _Medium);
+ _Looped = true;
+ }
+
+ _DataSinceLoop = false;
+
+ _Decoder.Close();
+ if (!_Decoder.Open(_Medium))
+ {
+ CLog.Error("Could not reopen audio file for looping: " + _Medium);
+ lock (_MutexData)
+ {
+ _NoMoreData = true;
+ }
+ }
+ }
+
private void _Execute()
{
while (!_Terminated)
@@ -292,19 +319,28 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
if (buffer == null)
{
- if (_Loop)
+ if (_Loop && _DataSinceLoop)
{
- _Start = 0f;
- _DoSkip();
+ _DoLoop();
}
else
{
+ if (_Loop)
+ {
+ CLog.Warning("Could not loop audio file, no data decoded since (re)opening: " + _Medium);
+ }
+
_NoMoreData = true;
}
return;
}
+ if (buffer.Length > 0)
+ {
+ _DataSinceLoop = true;
+ }
+
lock (_MutexData)
{
_Data.Write(buffer);
@@ -420,10 +456,25 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
queuedCount++;
}
+
+ if (_Data.BytesNotRead < _BeginRefill && !_NoMoreData)
+ {
+ _EventDecode.Set();
+ }
+ }
+
+ if (queuedCount > 0 && AL.GetSourceState(_Source) != ALSourceState.Playing)
+ {
+ AL.SourcePlay(_Source);
}
var latency = CConfig.Config.Sound.AudioLatency / 1000f + queuedCount * _SampleBuf.Length / _BytesPerSecond + 0.1f;
_CurrentTime = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - latency;
+ if (_Loop && _CurrentTime < 0f && Length > 0f)
+ {
+ _CurrentTime += Length;
+ }
+
_Timer.Restart();
}
}
diff --git a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs
index 8980d9dd..2c75bedc 100644
--- a/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs
+++ b/Vocaluxe/Lib/Sound/Playback/PortAudio/CPortAudioStream.cs
@@ -41,6 +41,8 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _PaStreamCallback;
private IAudioDecoder _Decoder;
+ private bool _DataSinceLoop;
+ private bool _Looped;
private float _BytesPerSecond;
private float _Latency;
private bool _NoMoreData;
@@ -77,7 +79,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
var time = Math.Max(0f, _SyncTimer.Time);
if (time > Length)
{
- _SyncTimer.Pause();
+ if (!_Loop)
+ {
+ _SyncTimer.Pause();
+ }
+
time = Length;
}
@@ -285,6 +291,27 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
}
}
+ private void _DoLoop()
+ {
+ if (!_Looped)
+ {
+ CLog.Information("Looping audio file: " + _Medium);
+ _Looped = true;
+ }
+
+ _DataSinceLoop = false;
+
+ _Decoder.Close();
+ if (!_Decoder.Open(_Medium))
+ {
+ CLog.Error("Could not reopen audio file for looping: " + _Medium);
+ lock (_LockData)
+ {
+ _NoMoreData = true;
+ }
+ }
+ }
+
private void _Execute()
{
while (!_Terminated)
@@ -335,19 +362,28 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
if (buffer == null)
{
- if (_Loop)
+ if (_Loop && _DataSinceLoop)
{
- _Start = 0f;
- _DoSkip();
+ _DoLoop();
}
else
{
+ if (_Loop)
+ {
+ CLog.Warning("Could not loop audio file, no data decoded since (re)opening: " + _Medium);
+ }
+
_NoMoreData = true;
}
return;
}
+ if (buffer.Length > 0)
+ {
+ _DataSinceLoop = true;
+ }
+
lock (_LockData)
{
_Data.Write(buffer);
@@ -445,6 +481,19 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
var latency = buf.Length / _BytesPerSecond + _Latency;
var time = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - latency;
+ if (_Loop && Length > 0f)
+ {
+ if (time < 0f)
+ {
+ time += Length;
+ }
+
+ if (time < _SyncTimer.Time - Length / 2f)
+ {
+ _SyncTimer.Time = time;
+ }
+ }
+
_SyncTimer.Update(time);
}
diff --git a/Vocaluxe/Screens/CScreenCredits.cs b/Vocaluxe/Screens/CScreenCredits.cs
index 088976a3..47c88f08 100644
--- a/Vocaluxe/Screens/CScreenCredits.cs
+++ b/Vocaluxe/Screens/CScreenCredits.cs
@@ -36,14 +36,24 @@ namespace Vocaluxe.Screens
get { return 1; }
}
+ private const float _ScrollSpeed = 60f;
+ private const float _EdgeFadeHeight = 150f;
+ private const float _EndHoldTime = 4f;
+ private const float _EndFadeTime = 1.5f;
+
private CStatic _Logo;
private Stopwatch _ScrollTimer;
private List _ScrollingElements;
private Dictionary _ElementStartYPositions;
+ private List _HeadlineTexts;
+ private List _HeadlineLines;
private float _previousVideoElapsedMilliseconds;
- private float _previousAnimationElapsedMilliseconds;
+ private float _MaxScrollOffset;
+ private int _NumEndElements;
+ private bool _Leaving;
+ private EScreen _ReturnScreen = EScreen.OptionsGame;
private CTextureRef _TexLogo;
@@ -62,6 +72,8 @@ namespace Vocaluxe.Screens
_ScrollTimer = new Stopwatch();
_ScrollingElements = new List();
_ElementStartYPositions = new Dictionary();
+ _HeadlineTexts = new List();
+ _HeadlineLines = new List();
}
public override void LoadTheme(string xmlPath)
@@ -90,15 +102,21 @@ namespace Vocaluxe.Screens
// Position Y for the first scrolling element
var scrollY = CSettings.RenderH - 1f;
+ CStatic AddLogo()
+ {
+ var logo = GetNewStatic(_TexLogo, new SColorF(1, 1, 1, 1),
+ new SRectF((float)(CSettings.RenderW - _TexLogo.OrigSize.Width) / 2, scrollY, _TexLogo.OrigSize.Width, _TexLogo.OrigSize.Height, -2));
+ logo.Visible = false;
+ _AddStatic(logo);
+ _ScrollingElements.Add(logo);
+ _ElementStartYPositions[logo] = scrollY;
+ return logo;
+ }
+
// Create logo
if (_TexLogo != null)
{
- _Logo = GetNewStatic(_TexLogo, new SColorF(1, 1, 1, 1),
- new SRectF((float)(CSettings.RenderW - _TexLogo.OrigSize.Width) / 2, scrollY, _TexLogo.OrigSize.Width, _TexLogo.OrigSize.Height, -2));
- _Logo.Visible = false;
- _AddStatic(_Logo);
- _ScrollingElements.Add(_Logo);
- _ElementStartYPositions[_Logo] = scrollY;
+ _Logo = AddLogo();
scrollY += _Logo.Rect.H + 20f; // Update scrollY after logo
}
@@ -111,7 +129,7 @@ namespace Vocaluxe.Screens
var paragraphSpacing = 30f; // Space between paragraphs
// Helper method to add text with formatting
- void AddText(string content, int size, EStyle style, float yOffset, EAlignment alignment = EAlignment.Center)
+ CText AddText(string content, int size, EStyle style, float yOffset, EAlignment alignment = EAlignment.Center)
{
var text = GetNewText(new CText(CSettings.RenderW / 2, scrollY + yOffset, -2f, size, -1, alignment, style, "Outline", new SColorF(1, 1, 1, 1), content));
text.Visible = false;
@@ -119,95 +137,120 @@ namespace Vocaluxe.Screens
_AddText(text);
_ElementStartYPositions[text] = text.Y;
scrollY += size + yOffset;
+ return text;
+ }
+
+ void AddHeadline(string content)
+ {
+ _HeadlineTexts.Add(AddText(content, headlineSize, EStyle.Bold, 0));
+ var line = GetNewStatic(null, new SColorF(1, 1, 1, 1), new SRectF(CSettings.RenderW / 2 - 200f, scrollY + 6f, 400f, 3f, -2));
+ line.Visible = false;
+ _AddStatic(line);
+ _ScrollingElements.Add(line);
+ _ElementStartYPositions[line] = line.Y;
+ _HeadlineLines.Add(line);
+ scrollY += 9f;
}
// Intro
- AddText("14 Years Vocaluxe!", bigHeadlineSize, EStyle.Bold, 0);
+ AddText("Bringing people together to sing since 2011", bigHeadlineSize, EStyle.Bold, 0);
scrollY += paragraphSpacing;
- AddText("A heartfelt thank you to all the people who have contributed to making Vocaluxe a reality over the past 14 years.", textSize, EStyle.Normal, 0);
- AddText("Your dedication, time, and open-source spirit have been incredible. Keep contributing or re-join and make the world sing!", textSize, EStyle.Normal, 0);
+ AddText("A heartfelt thank you to all the people who have contributed to making Vocaluxe a reality.", textSize, EStyle.Normal, 0);
+ AddText("Your dedication, time, and open-source spirit have been incredible.", textSize, EStyle.Normal, 0);
+ AddText("Keep contributing or re-join and make the world sing!", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing * 3;
- AddText("VOCALUXE Team (2011-today)", bigHeadlineSize, EStyle.Bold, 0);
+ AddText("VOCALUXE Team (2011-present)", bigHeadlineSize, EStyle.Bold, 0);
scrollY += paragraphSpacing * 2;
// Production
- AddText("PRODUCTION", headlineSize, EStyle.Bold, 0);
+ AddHeadline("PRODUCTION");
scrollY += paragraphSpacing;
AddText("Project Leader", boldSize, EStyle.Bold, 0);
- AddText("Florian Ostertag (2013-today)", textSize, EStyle.Normal, 0);
+ AddText("Florian Ostertag (2013-present)", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing;
AddText("Project Management", boldSize, EStyle.Bold, 0);
- AddText("Marwin (2023-today)", textSize, EStyle.Normal, 0);
+ AddText("Marwin (2023-2025)", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing * 3;
// Design
- AddText("DESIGN", headlineSize, EStyle.Bold, 0);
+ AddHeadline("DESIGN");
scrollY += paragraphSpacing;
AddText("Original Concept/Idea", boldSize, EStyle.Bold, 0);
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing;
AddText("Party Mode Design", boldSize, EStyle.Bold, 0);
+ AddText("Florian Ostertag (2013-present)", textSize, EStyle.Normal, 0);
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
- AddText("Florian Ostertag (2013-today)", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing * 3;
// Programming/Engineering
- AddText("PROGRAMMING/ENGINEERING", headlineSize, EStyle.Bold, 0);
+ AddHeadline("PROGRAMMING/ENGINEERING");
scrollY += paragraphSpacing;
AddText("Technical Director", boldSize, EStyle.Bold, 0);
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing;
AddText("Software Engineer", boldSize, EStyle.Bold, 0);
- AddText("Florian Ostertag (2013-today)", textSize, EStyle.Normal, 0);
+ AddText("Florian Ostertag (2013-present)", textSize, EStyle.Normal, 0);
AddText("LukeIam (2013-2019)", textSize, EStyle.Normal, 0);
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
AddText("Alexander Grund (2013-2015)", textSize, EStyle.Normal, 0);
AddText("Darkice", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing;
AddText("Programmer", boldSize, EStyle.Bold, 0);
- AddText("McSchneidar (2026-today)", textSize, EStyle.Normal, 0);
- AddText("Metin Celik (2025-today)", textSize, EStyle.Normal, 0);
- AddText("Jeff Dolan (2025-today)", textSize, EStyle.Normal, 0);
- AddText("Kacper Majorkowski (2025-today)", textSize, EStyle.Normal, 0);
- AddText("Franz Schuster (2025-today)", textSize, EStyle.Normal, 0);
- AddText("Dirk Heinke (2025-today)", textSize, EStyle.Normal, 0);
- AddText("Florian Schulze (2025-today)", textSize, EStyle.Normal, 0);
- AddText("Edi316 (2025-today)", textSize, EStyle.Normal, 0);
- AddText("Rap-Voca (2024)", textSize, EStyle.Normal, 0);
+ AddText("Jiiniasu (2016-present)", textSize, EStyle.Normal, 0);
+ AddText("Dirk Heinke (2025-present)", textSize, EStyle.Normal, 0);
+ AddText("Edi316 (2025-present)", textSize, EStyle.Normal, 0);
+ AddText("Florian Schulze (2025-present)", textSize, EStyle.Normal, 0);
+ AddText("Jeff Dolan (2025-present)", textSize, EStyle.Normal, 0);
+ AddText("Kacper Majorkowski (2025-present)", textSize, EStyle.Normal, 0);
+ AddText("Metin Celik (2025-present)", textSize, EStyle.Normal, 0);
+ AddText("friedPotat0 (2026-present)", textSize, EStyle.Normal, 0);
+ AddText("McSchneidar (2026-present)", textSize, EStyle.Normal, 0);
+ AddText("Quentin Bray (2026-present)", textSize, EStyle.Normal, 0);
+ AddText("Franz Schuster (2025)", textSize, EStyle.Normal, 0);
+ AddText("Damien Laguerre (2024)", textSize, EStyle.Normal, 0);
AddText("GRaff844 (2024)", textSize, EStyle.Normal, 0);
+ AddText("Rap-Voca (2024)", textSize, EStyle.Normal, 0);
AddText("Stefan1200 (2020-2022)", textSize, EStyle.Normal, 0);
AddText("GaryCXJk (2020-2021)", textSize, EStyle.Normal, 0);
- AddText("Jiiniasu (2016-today)", textSize, EStyle.Normal, 0);
AddText("Stephan Sundermann (2012-2014)", textSize, EStyle.Normal, 0);
- AddText("Damien Laguerre (2024)", textSize, EStyle.Normal, 0);
+ AddText("Babene03 (2012)", textSize, EStyle.Normal, 0);
AddText("Markus Bohning (2012)", textSize, EStyle.Normal, 0);
AddText("Mesand (2012)", textSize, EStyle.Normal, 0);
- AddText("Babene03 (2012)", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing * 3;
// Art/Graphics
- AddText("ART/GRAPHICS", headlineSize, EStyle.Bold, 0);
+ AddHeadline("ART/GRAPHICS");
scrollY += paragraphSpacing;
AddText("UI Artist/Graphics", boldSize, EStyle.Bold, 0);
- AddText("Marwin (2023-today)", textSize, EStyle.Normal, 0);
+ AddText("Marwin (2023-2025)", textSize, EStyle.Normal, 0);
AddText("Jiiniasu (2016-2020)", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing;
AddText("Sound/Audio/Music Design", boldSize, EStyle.Bold, 0);
- AddText("Marwin (2023-today)", textSize, EStyle.Normal, 0);
+ AddText("Marwin (2023-2025)", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing;
AddText("Audio Producer", boldSize, EStyle.Bold, 0);
AddText("Ivymusic", textSize, EStyle.Normal, 0);
scrollY += paragraphSpacing * 3;
// Translation
- AddText("TRANSLATION", headlineSize, EStyle.Bold, 0);
+ AddHeadline("TRANSLATION");
scrollY += paragraphSpacing;
AddText("Thanks to everyone translating Vocaluxe into different languages.", textSize, EStyle.Normal, 0);
- scrollY += paragraphSpacing * 4;
+ scrollY += paragraphSpacing * 3;
+
+ var endBlockTop = scrollY;
+ var numElementsBeforeEnd = _ScrollingElements.Count;
+ if (_TexLogo != null)
+ {
+ scrollY += AddLogo().Rect.H + 20f;
+ }
// Website
AddText("www.vocaluxe.org", headlineSize, EStyle.Bold, 0);
- AddText("www.open-music-games.org", headlineSize, EStyle.Bold, 0);
+
+ _NumEndElements = _ScrollingElements.Count - numElementsBeforeEnd;
+ _MaxScrollOffset = endBlockTop - (CSettings.RenderH - (scrollY - endBlockTop)) / 2;
}
public override void ReloadTheme(string xmlPath) { }
@@ -265,7 +308,7 @@ namespace Vocaluxe.Screens
CVideo.GetFrame(_BackgroundVideo, _BackgroundVideoTime);
}
- if (!_Animation() && CGraphics.NextScreen != CGraphics.GetScreen(EScreen.Main))
+ if (!_Animation())
{
_LeaveScreen();
}
@@ -277,22 +320,39 @@ namespace Vocaluxe.Screens
{
base.OnShow();
+ _Leaving = false;
+ _ReturnScreen = CGraphics.CurrentScreen == CGraphics.GetScreen(EScreen.Main) ? EScreen.Main : EScreen.OptionsGame;
+
// Reset positions
foreach (var element in _ScrollingElements)
{
if (_ElementStartYPositions.TryGetValue(element, out float startY))
{
- if (element is CText text)
- {
- text.Y = startY;
- }
- else if (element == _Logo)
- {
- _Logo.Y = startY;
- }
+ element.Y = startY;
+ element.Alpha = 1f;
}
}
+ if (CBase.Themes.GetColor("ButtonSelColor", PartyModeId, out SColorF headlineColor))
+ {
+ foreach (var text in _HeadlineTexts)
+ {
+ text.Color = headlineColor;
+ }
+
+ foreach (var line in _HeadlineLines)
+ {
+ line.Color = headlineColor;
+ }
+ }
+
+ for (var i = 0; i < _HeadlineTexts.Count; i++)
+ {
+ var textRect = _HeadlineTexts[i].Rect;
+ _HeadlineLines[i].X = textRect.X;
+ _HeadlineLines[i].W = textRect.W;
+ }
+
_previousVideoElapsedMilliseconds = 0f;
var path = Path.Combine(CSettings.ProgramFolder, CSettings.FolderNameGraphics, CSettings.FileNameCreditsVideo);
@@ -310,6 +370,12 @@ namespace Vocaluxe.Screens
CLog.Error("Background video not found: " + path);
}
+ var musicPath = Path.Combine(CSettings.ProgramFolder, CSettings.FolderNameSounds, CSettings.FileNameMusicCredits);
+ if (!File.Exists(musicPath) || !CBackgroundMusic.PlayEventMusic(musicPath))
+ {
+ CLog.Warning("Credits music not found or not playable: " + musicPath);
+ }
+
foreach (var element in _ScrollingElements)
{
element.Visible = true;
@@ -318,13 +384,13 @@ namespace Vocaluxe.Screens
// Start the scroll timer immediately
_ScrollTimer.Reset();
_ScrollTimer.Start();
- _previousAnimationElapsedMilliseconds = 0;
_BackgroundVideoTime = 0f;
+ }
- foreach (var element in _ScrollingElements)
- {
- element.Visible = true;
- }
+ public override void OnClose()
+ {
+ base.OnClose();
+ CBackgroundMusic.StopEventMusic();
}
public override void Draw()
@@ -342,7 +408,14 @@ namespace Vocaluxe.Screens
private void _LeaveScreen()
{
- CGraphics.FadeTo(EScreen.OptionsGame);
+ if (_Leaving)
+ {
+ return;
+ }
+
+ _Leaving = true;
+ CBackgroundMusic.StopEventMusic();
+ CGraphics.FadeTo(_ReturnScreen);
if (_BackgroundVideo != null)
{
@@ -358,42 +431,40 @@ namespace Vocaluxe.Screens
return false;
}
- var deltaTime = (_ScrollTimer.ElapsedMilliseconds - _previousAnimationElapsedMilliseconds) / 1000f; // Convert milliseconds to seconds
- _previousAnimationElapsedMilliseconds = _ScrollTimer.ElapsedMilliseconds;
+ var elapsed = _ScrollTimer.ElapsedMilliseconds / 1000f; // Convert milliseconds to seconds
+ var offset = elapsed * _ScrollSpeed;
+ var endOffset = Math.Min(offset, _MaxScrollOffset);
+ var endAlpha = 1f;
- var scrollSpeed = 60f;
-
- foreach (var element in _ScrollingElements)
+ if (endOffset >= _MaxScrollOffset)
{
- if (element is CText text)
+ var endTime = elapsed - _MaxScrollOffset / _ScrollSpeed;
+ if (endTime >= _EndHoldTime + _EndFadeTime)
{
- text.Y -= scrollSpeed * deltaTime;
+ _ScrollTimer.Stop();
+ return false;
}
- else if (element == _Logo)
+
+ if (endTime > _EndHoldTime)
{
- _Logo.Y -= scrollSpeed * deltaTime;
+ endAlpha = 1f - (endTime - _EndHoldTime) / _EndFadeTime;
}
}
- // Check if the last element has scrolled off the top
- var lastElement = _ScrollingElements[_ScrollingElements.Count - 1];
- float elementBottomY = 0;
- if (lastElement is CText lastText)
+ for (var i = 0; i < _ScrollingElements.Count; i++)
{
- elementBottomY = lastText.Y + lastText.H;
- }
- else if (lastElement == _Logo)
- {
- elementBottomY = _Logo.Y + _Logo.Rect.H;
- }
-
- if (elementBottomY < 0)
- {
- _ScrollTimer.Stop();
- return false;
+ var element = _ScrollingElements[i];
+ if (_ElementStartYPositions.TryGetValue(element, out float startY))
+ {
+ var isEndElement = i >= _ScrollingElements.Count - _NumEndElements;
+ element.Y = startY - (isEndElement ? endOffset : offset);
+ SRectF rect = element.Rect;
+ var edgeDistance = Math.Min(rect.Y, CSettings.RenderH - rect.Y - rect.H);
+ element.Alpha = Math.Max(0f, Math.Min(1f, edgeDistance / _EdgeFadeHeight)) * (isEndElement ? endAlpha : 1f);
+ }
}
return true;
}
}
-}
\ No newline at end of file
+}
diff --git a/VocaluxeLib/IBasic.cs b/VocaluxeLib/IBasic.cs
index ba91a727..6b5a8c02 100644
--- a/VocaluxeLib/IBasic.cs
+++ b/VocaluxeLib/IBasic.cs
@@ -291,6 +291,7 @@ namespace VocaluxeLib
void SetStreamVolume(int streamId, int volume);
void SetGlobalVolume(int volume);
bool IsPaused(int streamId);
+ void SetLoop(int streamId, bool loop);
}
public interface ICover
diff --git a/VocaluxeLib/Utils/Player/CSoundPlayer.cs b/VocaluxeLib/Utils/Player/CSoundPlayer.cs
index 9f2832d6..abd3a5dc 100644
--- a/VocaluxeLib/Utils/Player/CSoundPlayer.cs
+++ b/VocaluxeLib/Utils/Player/CSoundPlayer.cs
@@ -25,7 +25,20 @@ namespace VocaluxeLib.Utils.Player
protected readonly float _FadeTime = CBase.Settings.GetSoundPlayerFadeTime();
public string FilePath { get; private set; }
- public bool Loop;
+ private bool _Loop;
+
+ public bool Loop
+ {
+ get { return _Loop; }
+ set
+ {
+ _Loop = value;
+ if (SoundLoaded)
+ {
+ CBase.Sound.SetLoop(_StreamId, value);
+ }
+ }
+ }
///
/// Gets the current stream position or sets it
@@ -75,7 +88,7 @@ namespace VocaluxeLib.Utils.Player
{
Close();
- _StreamId = CBase.Sound.Load(file, false, true);
+ _StreamId = CBase.Sound.Load(file, Loop, true);
if (_StreamId < 0)
{
return;
@@ -163,19 +176,12 @@ namespace VocaluxeLib.Utils.Player
return;
}
- var finished = CBase.Sound.IsFinished(_StreamId);
if (Loop)
{
- if (finished)
- {
- // Restart
- Stop();
- Play();
- }
-
return;
}
+ var finished = CBase.Sound.IsFinished(_StreamId);
var len = CBase.Sound.GetLength(_StreamId);
var timeToPlay = len > 0f ? len - CBase.Sound.GetPosition(_StreamId) : _FadeTime + 1f;