Seamless audio loops and own credits music (#945)
Loops
- Looping is now done by the playback backends instead of restarting
the stream with Stop/Play. Loop can be toggled at runtime (SetLoop in
ISound/IPlayback/IAudioStream), so the repeat button keeps working.
- GStreamer: gapless loops via playbin's about-to-finish signal.
- PortAudio/OpenAL: the decoder is reopened at the end of the file
(Acinerella's seek does not reset FFmpeg after EOF) while the buffered
data keeps playing, so the loop has no gap. The reported position wraps
correctly.
Credits screen
- New event music player in CBackgroundMusic that replaces the current
background music and resumes it afterwards.
- The credits screen loops Sounds/CreditsMusic.mp3
("Atmosphere Calm Background" by lvymusic, Pixabay Content License).
- As the credits screen was touched anyway, its content was updated as
well.
Fixes for PortAudio/OpenAL playback found on the way
- Acinerella delivers 32 bit float samples for mp3, so every file was
rejected with "Unsupported BitDepth". Float samples are converted to
16 bit now.
- Acinerella reports a missing frame (EAGAIN) like the end of the file,
so decoding is retried a few times before treating it as the end.
- OpenAL stopped after half a second because the decoder thread was
never woken up to refill its buffer. A source that ran out of data is
restarted as well.
Part of #677
Co-authored-by: Florian Ostertag <flokuep@users.noreply.github.com>
This commit is contained in:
1 parent
336d08040e
commit
4ee6689ff4
16 files changed
+411
-102
No files matched your search
Binary file not shown.
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -51,6 +51,8 @@ namespace Vocaluxe.Lib.Sound.Playback
|
||||
/// <param name="volume">Volume in percent</param>
|
||||
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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<dynamic> _ScrollingElements;
|
||||
private Dictionary<dynamic, float> _ElementStartYPositions;
|
||||
private List<CText> _HeadlineTexts;
|
||||
private List<CStatic> _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<dynamic>();
|
||||
_ElementStartYPositions = new Dictionary<dynamic, float>();
|
||||
_HeadlineTexts = new List<CText>();
|
||||
_HeadlineLines = new List<CStatic>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
|
||||
Reference in new issue
Block a user