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 _BGPlayer = new CSongPlayer();
|
||||||
private static readonly CSongPlayer _PreviewPlayer = new CSongPlayer(true);
|
private static readonly CSongPlayer _PreviewPlayer = new CSongPlayer(true);
|
||||||
|
private static readonly CSongPlayer _EventPlayer = new CSongPlayer(true);
|
||||||
private static CSongPlayer _CurPlayer;
|
private static CSongPlayer _CurPlayer;
|
||||||
|
private static CSongPlayer _PlayerBeforeEvent;
|
||||||
|
|
||||||
private static bool _OwnSongsAvailable;
|
private static bool _OwnSongsAvailable;
|
||||||
private static EBackgroundMusicSource _MusicSource;
|
private static EBackgroundMusicSource _MusicSource;
|
||||||
@@ -63,7 +65,7 @@ namespace Vocaluxe.Base
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (IsPlayingPreview)
|
if (IsPlayingPreview || IsPlayingEventMusic)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -134,6 +136,11 @@ namespace Vocaluxe.Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsPlayingEventMusic
|
||||||
|
{
|
||||||
|
get { return _CurPlayer == _EventPlayer; }
|
||||||
|
}
|
||||||
|
|
||||||
public static string ArtistAndTitle
|
public static string ArtistAndTitle
|
||||||
{
|
{
|
||||||
get { return _CurPlayer.ArtistAndTitle; }
|
get { return _CurPlayer.ArtistAndTitle; }
|
||||||
@@ -195,6 +202,7 @@ namespace Vocaluxe.Base
|
|||||||
{
|
{
|
||||||
_BGPlayer.Close();
|
_BGPlayer.Close();
|
||||||
_PreviewPlayer.Close();
|
_PreviewPlayer.Close();
|
||||||
|
_EventPlayer.Close();
|
||||||
|
|
||||||
_BGMusicFiles.Clear();
|
_BGMusicFiles.Clear();
|
||||||
_NotPlayedFiles.Clear();
|
_NotPlayedFiles.Clear();
|
||||||
@@ -215,7 +223,7 @@ namespace Vocaluxe.Base
|
|||||||
CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume);
|
CSound.SetGlobalVolume(CConfig.BackgroundMusicVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsPlayingPreview || _BGPlayer.SoundLoaded)
|
if (IsPlayingPreview || IsPlayingEventMusic || _BGPlayer.SoundLoaded)
|
||||||
{
|
{
|
||||||
_CurPlayer.Play();
|
_CurPlayer.Play();
|
||||||
}
|
}
|
||||||
@@ -241,7 +249,7 @@ namespace Vocaluxe.Base
|
|||||||
|
|
||||||
public static void Next()
|
public static void Next()
|
||||||
{
|
{
|
||||||
if (IsPlayingPreview)
|
if (IsPlayingPreview || IsPlayingEventMusic)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -278,7 +286,7 @@ namespace Vocaluxe.Base
|
|||||||
|
|
||||||
public static void Previous()
|
public static void Previous()
|
||||||
{
|
{
|
||||||
if (IsPlayingPreview)
|
if (IsPlayingPreview || IsPlayingEventMusic)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -321,12 +329,45 @@ namespace Vocaluxe.Base
|
|||||||
}
|
}
|
||||||
|
|
||||||
_CurPlayer.Update();
|
_CurPlayer.Update();
|
||||||
if (!IsPlayingPreview && _BGPlayer.IsFinished)
|
if (_CurPlayer == _BGPlayer && _BGPlayer.IsFinished)
|
||||||
{
|
{
|
||||||
Next();
|
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()
|
public static CTextureRef GetVideoTexture()
|
||||||
{
|
{
|
||||||
return _CurPlayer.GetVideoTexture();
|
return _CurPlayer.GetVideoTexture();
|
||||||
|
|||||||
@@ -970,6 +970,11 @@ namespace Vocaluxe.Base
|
|||||||
{
|
{
|
||||||
return CSound.IsPaused(streamId);
|
return CSound.IsPaused(streamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetLoop(int streamId, bool loop)
|
||||||
|
{
|
||||||
|
CSound.SetLoop(streamId, loop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CBcover : ICover
|
class CBcover : ICover
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ namespace Vocaluxe.Base
|
|||||||
public const string FileNameSoundHighscore = "Highscore.mp3";
|
public const string FileNameSoundHighscore = "Highscore.mp3";
|
||||||
public const string FileNameSoundProgressBar = "ProgressBar.mp3";
|
public const string FileNameSoundProgressBar = "ProgressBar.mp3";
|
||||||
public const string FileNameSoundWarning = "Warning.mp3";
|
public const string FileNameSoundWarning = "Warning.mp3";
|
||||||
|
public const string FileNameMusicCredits = "CreditsMusic.mp3";
|
||||||
|
|
||||||
public const string FolderNameSongs = "Songs";
|
public const string FolderNameSongs = "Songs";
|
||||||
public const string FolderNameProfiles = "Profiles";
|
public const string FolderNameProfiles = "Profiles";
|
||||||
|
|||||||
@@ -154,6 +154,11 @@ namespace Vocaluxe.Base
|
|||||||
_Playback.SetStreamVolume(stream, volume);
|
_Playback.SetStreamVolume(stream, volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetLoop(int stream, bool loop)
|
||||||
|
{
|
||||||
|
_Playback.SetLoop(stream, loop);
|
||||||
|
}
|
||||||
|
|
||||||
public static float GetLength(int stream)
|
public static float GetLength(int stream)
|
||||||
{
|
{
|
||||||
return _Playback.GetLength(stream);
|
return _Playback.GetLength(stream);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Vocaluxe.Lib.Sound.Playback
|
|||||||
abstract class CAudioStreamBase : IAudioStream
|
abstract class CAudioStreamBase : IAudioStream
|
||||||
{
|
{
|
||||||
protected readonly string _Medium;
|
protected readonly string _Medium;
|
||||||
protected readonly bool _Loop;
|
protected volatile bool _Loop;
|
||||||
protected readonly EAudioEffect _Effect;
|
protected readonly EAudioEffect _Effect;
|
||||||
|
|
||||||
protected ICloseStreamListener _CloseStreamListener;
|
protected ICloseStreamListener _CloseStreamListener;
|
||||||
@@ -39,6 +39,12 @@ namespace Vocaluxe.Lib.Sound.Playback
|
|||||||
get { return _Fading != null; }
|
get { return _Fading != null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual bool Loop
|
||||||
|
{
|
||||||
|
get { return _Loop; }
|
||||||
|
set { _Loop = value; }
|
||||||
|
}
|
||||||
|
|
||||||
public virtual float Volume { get; set; }
|
public virtual float Volume { get; set; }
|
||||||
public virtual float VolumeMax { get; set; }
|
public virtual float VolumeMax { get; set; }
|
||||||
public virtual float Length { get; protected 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)
|
public float GetLength(int streamId)
|
||||||
{
|
{
|
||||||
if (!_Initialized)
|
if (!_Initialized)
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
|
|||||||
private SACInstance _Instance;
|
private SACInstance _Instance;
|
||||||
private SFormatInfo _FormatInfo;
|
private SFormatInfo _FormatInfo;
|
||||||
private float _CurrentTime;
|
private float _CurrentTime;
|
||||||
|
private const int _MaxDecodeTries = 10;
|
||||||
|
private bool _ConvertFromFloat;
|
||||||
|
|
||||||
private string _FileName;
|
private string _FileName;
|
||||||
private bool _FileOpened;
|
private bool _FileOpened;
|
||||||
@@ -88,6 +90,12 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
|
|||||||
|
|
||||||
_CurrentTime = 0f;
|
_CurrentTime = 0f;
|
||||||
|
|
||||||
|
_ConvertFromFloat = _FormatInfo.BitDepth == 32;
|
||||||
|
if (_ConvertFromFloat)
|
||||||
|
{
|
||||||
|
_FormatInfo.BitDepth = 16;
|
||||||
|
}
|
||||||
|
|
||||||
if (_FormatInfo.BitDepth != 16)
|
if (_FormatInfo.BitDepth != 16)
|
||||||
{
|
{
|
||||||
CLog.Error("Unsupported BitDepth in file " + fileName);
|
CLog.Error("Unsupported BitDepth in file " + fileName);
|
||||||
@@ -150,6 +158,25 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
|
|||||||
return _FileOpened ? _CurrentTime : 0f;
|
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)
|
public void Decode(out byte[] buffer, out float timeStamp)
|
||||||
{
|
{
|
||||||
if (!_FileOpened)
|
if (!_FileOpened)
|
||||||
@@ -159,7 +186,11 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
|
|||||||
return;
|
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)
|
if (frameFinished)
|
||||||
{
|
{
|
||||||
@@ -168,6 +199,12 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
|
|||||||
timeStamp = (float)decoder.Timecode;
|
timeStamp = (float)decoder.Timecode;
|
||||||
_CurrentTime = timeStamp;
|
_CurrentTime = timeStamp;
|
||||||
//Console.WriteLine(_CurrentTime.ToString("#0.000") + " Buffer size: " + Decoder.buffer_size.ToString());
|
//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];
|
buffer = new byte[decoder.BufferSize];
|
||||||
|
|
||||||
if (decoder.BufferSize > 0)
|
if (decoder.BufferSize > 0)
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ namespace Vocaluxe.Lib.Sound.Playback.GstreamerSharp
|
|||||||
_Element["audio-sink"] = audioSinkBin;
|
_Element["audio-sink"] = audioSinkBin;
|
||||||
_Element["flags"] = 1 << 1;
|
_Element["flags"] = 1 << 1;
|
||||||
_Element["uri"] = new Uri(_Medium).AbsoluteUri;
|
_Element["uri"] = new Uri(_Medium).AbsoluteUri;
|
||||||
|
_Element.Connect("about-to-finish", _OnAboutToFinish);
|
||||||
_Element.SetState(State.Paused);
|
_Element.SetState(State.Paused);
|
||||||
|
|
||||||
// Passing CLOCK_TIME_NONE here causes the pipeline to block for a long time so with
|
// 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)
|
private bool _OnMessage(Message msg)
|
||||||
{
|
{
|
||||||
if (msg == null || msg.Handle == IntPtr.Zero)
|
if (msg == null || msg.Handle == IntPtr.Zero)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace Vocaluxe.Lib.Sound.Playback
|
|||||||
{
|
{
|
||||||
int Id { get; }
|
int Id { get; }
|
||||||
bool IsFading { get; }
|
bool IsFading { get; }
|
||||||
|
bool Loop { get; set; }
|
||||||
float Volume { get; set; }
|
float Volume { get; set; }
|
||||||
float VolumeMax { get; set; }
|
float VolumeMax { get; set; }
|
||||||
float Length { get; }
|
float Length { get; }
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ namespace Vocaluxe.Lib.Sound.Playback
|
|||||||
/// <param name="volume">Volume in percent</param>
|
/// <param name="volume">Volume in percent</param>
|
||||||
void SetStreamVolume(int streamId, int volume);
|
void SetStreamVolume(int streamId, int volume);
|
||||||
|
|
||||||
|
void SetLoop(int streamId, bool loop);
|
||||||
|
|
||||||
float GetLength(int streamId);
|
float GetLength(int streamId);
|
||||||
float GetPosition(int streamId);
|
float GetPosition(int streamId);
|
||||||
void SetPosition(int streamId, float position);
|
void SetPosition(int streamId, float position);
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
|
|||||||
private readonly Stopwatch _Timer = new Stopwatch();
|
private readonly Stopwatch _Timer = new Stopwatch();
|
||||||
|
|
||||||
private IAudioDecoder _Decoder;
|
private IAudioDecoder _Decoder;
|
||||||
|
private bool _DataSinceLoop;
|
||||||
|
private bool _Looped;
|
||||||
private int _ByteCount;
|
private int _ByteCount;
|
||||||
private float _BytesPerSecond;
|
private float _BytesPerSecond;
|
||||||
private bool _NoMoreData;
|
private bool _NoMoreData;
|
||||||
@@ -84,7 +86,11 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
|
|||||||
var time = _CurrentTime + _Timer.ElapsedMilliseconds / 1000f;
|
var time = _CurrentTime + _Timer.ElapsedMilliseconds / 1000f;
|
||||||
if (time > Length)
|
if (time > Length)
|
||||||
{
|
{
|
||||||
_Timer.Stop();
|
if (!_Loop)
|
||||||
|
{
|
||||||
|
_Timer.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
time = Length;
|
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()
|
private void _Execute()
|
||||||
{
|
{
|
||||||
while (!_Terminated)
|
while (!_Terminated)
|
||||||
@@ -292,19 +319,28 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
|
|||||||
|
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
{
|
{
|
||||||
if (_Loop)
|
if (_Loop && _DataSinceLoop)
|
||||||
{
|
{
|
||||||
_Start = 0f;
|
_DoLoop();
|
||||||
_DoSkip();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (_Loop)
|
||||||
|
{
|
||||||
|
CLog.Warning("Could not loop audio file, no data decoded since (re)opening: " + _Medium);
|
||||||
|
}
|
||||||
|
|
||||||
_NoMoreData = true;
|
_NoMoreData = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffer.Length > 0)
|
||||||
|
{
|
||||||
|
_DataSinceLoop = true;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_MutexData)
|
lock (_MutexData)
|
||||||
{
|
{
|
||||||
_Data.Write(buffer);
|
_Data.Write(buffer);
|
||||||
@@ -420,10 +456,25 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
|
|||||||
|
|
||||||
queuedCount++;
|
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;
|
var latency = CConfig.Config.Sound.AudioLatency / 1000f + queuedCount * _SampleBuf.Length / _BytesPerSecond + 0.1f;
|
||||||
_CurrentTime = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - latency;
|
_CurrentTime = _TimeCode - _Data.BytesNotRead / _BytesPerSecond - latency;
|
||||||
|
if (_Loop && _CurrentTime < 0f && Length > 0f)
|
||||||
|
{
|
||||||
|
_CurrentTime += Length;
|
||||||
|
}
|
||||||
|
|
||||||
_Timer.Restart();
|
_Timer.Restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
|
|
||||||
private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _PaStreamCallback;
|
private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _PaStreamCallback;
|
||||||
private IAudioDecoder _Decoder;
|
private IAudioDecoder _Decoder;
|
||||||
|
private bool _DataSinceLoop;
|
||||||
|
private bool _Looped;
|
||||||
private float _BytesPerSecond;
|
private float _BytesPerSecond;
|
||||||
private float _Latency;
|
private float _Latency;
|
||||||
private bool _NoMoreData;
|
private bool _NoMoreData;
|
||||||
@@ -77,7 +79,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
var time = Math.Max(0f, _SyncTimer.Time);
|
var time = Math.Max(0f, _SyncTimer.Time);
|
||||||
if (time > Length)
|
if (time > Length)
|
||||||
{
|
{
|
||||||
_SyncTimer.Pause();
|
if (!_Loop)
|
||||||
|
{
|
||||||
|
_SyncTimer.Pause();
|
||||||
|
}
|
||||||
|
|
||||||
time = Length;
|
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()
|
private void _Execute()
|
||||||
{
|
{
|
||||||
while (!_Terminated)
|
while (!_Terminated)
|
||||||
@@ -335,19 +362,28 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
|
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
{
|
{
|
||||||
if (_Loop)
|
if (_Loop && _DataSinceLoop)
|
||||||
{
|
{
|
||||||
_Start = 0f;
|
_DoLoop();
|
||||||
_DoSkip();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (_Loop)
|
||||||
|
{
|
||||||
|
CLog.Warning("Could not loop audio file, no data decoded since (re)opening: " + _Medium);
|
||||||
|
}
|
||||||
|
|
||||||
_NoMoreData = true;
|
_NoMoreData = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffer.Length > 0)
|
||||||
|
{
|
||||||
|
_DataSinceLoop = true;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_LockData)
|
lock (_LockData)
|
||||||
{
|
{
|
||||||
_Data.Write(buffer);
|
_Data.Write(buffer);
|
||||||
@@ -445,6 +481,19 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
|
|
||||||
var latency = buf.Length / _BytesPerSecond + _Latency;
|
var latency = buf.Length / _BytesPerSecond + _Latency;
|
||||||
var time = _TimeCode - _Data.BytesNotRead / _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);
|
_SyncTimer.Update(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,14 +36,24 @@ namespace Vocaluxe.Screens
|
|||||||
get { return 1; }
|
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 CStatic _Logo;
|
||||||
|
|
||||||
private Stopwatch _ScrollTimer;
|
private Stopwatch _ScrollTimer;
|
||||||
|
|
||||||
private List<dynamic> _ScrollingElements;
|
private List<dynamic> _ScrollingElements;
|
||||||
private Dictionary<dynamic, float> _ElementStartYPositions;
|
private Dictionary<dynamic, float> _ElementStartYPositions;
|
||||||
|
private List<CText> _HeadlineTexts;
|
||||||
|
private List<CStatic> _HeadlineLines;
|
||||||
private float _previousVideoElapsedMilliseconds;
|
private float _previousVideoElapsedMilliseconds;
|
||||||
private float _previousAnimationElapsedMilliseconds;
|
private float _MaxScrollOffset;
|
||||||
|
private int _NumEndElements;
|
||||||
|
private bool _Leaving;
|
||||||
|
private EScreen _ReturnScreen = EScreen.OptionsGame;
|
||||||
|
|
||||||
private CTextureRef _TexLogo;
|
private CTextureRef _TexLogo;
|
||||||
|
|
||||||
@@ -62,6 +72,8 @@ namespace Vocaluxe.Screens
|
|||||||
_ScrollTimer = new Stopwatch();
|
_ScrollTimer = new Stopwatch();
|
||||||
_ScrollingElements = new List<dynamic>();
|
_ScrollingElements = new List<dynamic>();
|
||||||
_ElementStartYPositions = new Dictionary<dynamic, float>();
|
_ElementStartYPositions = new Dictionary<dynamic, float>();
|
||||||
|
_HeadlineTexts = new List<CText>();
|
||||||
|
_HeadlineLines = new List<CStatic>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void LoadTheme(string xmlPath)
|
public override void LoadTheme(string xmlPath)
|
||||||
@@ -90,15 +102,21 @@ namespace Vocaluxe.Screens
|
|||||||
// Position Y for the first scrolling element
|
// Position Y for the first scrolling element
|
||||||
var scrollY = CSettings.RenderH - 1f;
|
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
|
// Create logo
|
||||||
if (_TexLogo != null)
|
if (_TexLogo != null)
|
||||||
{
|
{
|
||||||
_Logo = GetNewStatic(_TexLogo, new SColorF(1, 1, 1, 1),
|
_Logo = AddLogo();
|
||||||
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;
|
|
||||||
|
|
||||||
scrollY += _Logo.Rect.H + 20f; // Update scrollY after logo
|
scrollY += _Logo.Rect.H + 20f; // Update scrollY after logo
|
||||||
}
|
}
|
||||||
@@ -111,7 +129,7 @@ namespace Vocaluxe.Screens
|
|||||||
var paragraphSpacing = 30f; // Space between paragraphs
|
var paragraphSpacing = 30f; // Space between paragraphs
|
||||||
|
|
||||||
// Helper method to add text with formatting
|
// 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));
|
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;
|
text.Visible = false;
|
||||||
@@ -119,95 +137,120 @@ namespace Vocaluxe.Screens
|
|||||||
_AddText(text);
|
_AddText(text);
|
||||||
_ElementStartYPositions[text] = text.Y;
|
_ElementStartYPositions[text] = text.Y;
|
||||||
scrollY += size + yOffset;
|
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
|
// Intro
|
||||||
AddText("14 Years Vocaluxe!", bigHeadlineSize, EStyle.Bold, 0);
|
AddText("Bringing people together to sing since 2011", bigHeadlineSize, EStyle.Bold, 0);
|
||||||
scrollY += paragraphSpacing;
|
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("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. Keep contributing or re-join and make the world sing!", 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;
|
scrollY += paragraphSpacing * 3;
|
||||||
AddText("VOCALUXE Team (2011-today)", bigHeadlineSize, EStyle.Bold, 0);
|
AddText("VOCALUXE Team (2011-present)", bigHeadlineSize, EStyle.Bold, 0);
|
||||||
scrollY += paragraphSpacing * 2;
|
scrollY += paragraphSpacing * 2;
|
||||||
|
|
||||||
// Production
|
// Production
|
||||||
AddText("PRODUCTION", headlineSize, EStyle.Bold, 0);
|
AddHeadline("PRODUCTION");
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Project Leader", boldSize, EStyle.Bold, 0);
|
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;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Project Management", boldSize, EStyle.Bold, 0);
|
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;
|
scrollY += paragraphSpacing * 3;
|
||||||
|
|
||||||
// Design
|
// Design
|
||||||
AddText("DESIGN", headlineSize, EStyle.Bold, 0);
|
AddHeadline("DESIGN");
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Original Concept/Idea", boldSize, EStyle.Bold, 0);
|
AddText("Original Concept/Idea", boldSize, EStyle.Bold, 0);
|
||||||
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
|
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Party Mode Design", boldSize, EStyle.Bold, 0);
|
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("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Florian Ostertag (2013-today)", textSize, EStyle.Normal, 0);
|
|
||||||
scrollY += paragraphSpacing * 3;
|
scrollY += paragraphSpacing * 3;
|
||||||
|
|
||||||
// Programming/Engineering
|
// Programming/Engineering
|
||||||
AddText("PROGRAMMING/ENGINEERING", headlineSize, EStyle.Bold, 0);
|
AddHeadline("PROGRAMMING/ENGINEERING");
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Technical Director", boldSize, EStyle.Bold, 0);
|
AddText("Technical Director", boldSize, EStyle.Bold, 0);
|
||||||
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
|
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Software Engineer", boldSize, EStyle.Bold, 0);
|
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("LukeIam (2013-2019)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
|
AddText("Alexander Eckhart (2011-2015)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Alexander Grund (2013-2015)", textSize, EStyle.Normal, 0);
|
AddText("Alexander Grund (2013-2015)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Darkice", textSize, EStyle.Normal, 0);
|
AddText("Darkice", textSize, EStyle.Normal, 0);
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Programmer", boldSize, EStyle.Bold, 0);
|
AddText("Programmer", boldSize, EStyle.Bold, 0);
|
||||||
AddText("McSchneidar (2026-today)", textSize, EStyle.Normal, 0);
|
AddText("Jiiniasu (2016-present)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Metin Celik (2025-today)", textSize, EStyle.Normal, 0);
|
AddText("Dirk Heinke (2025-present)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Jeff Dolan (2025-today)", textSize, EStyle.Normal, 0);
|
AddText("Edi316 (2025-present)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Kacper Majorkowski (2025-today)", textSize, EStyle.Normal, 0);
|
AddText("Florian Schulze (2025-present)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Franz Schuster (2025-today)", textSize, EStyle.Normal, 0);
|
AddText("Jeff Dolan (2025-present)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Dirk Heinke (2025-today)", textSize, EStyle.Normal, 0);
|
AddText("Kacper Majorkowski (2025-present)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Florian Schulze (2025-today)", textSize, EStyle.Normal, 0);
|
AddText("Metin Celik (2025-present)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Edi316 (2025-today)", textSize, EStyle.Normal, 0);
|
AddText("friedPotat0 (2026-present)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Rap-Voca (2024)", 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("GRaff844 (2024)", textSize, EStyle.Normal, 0);
|
||||||
|
AddText("Rap-Voca (2024)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Stefan1200 (2020-2022)", textSize, EStyle.Normal, 0);
|
AddText("Stefan1200 (2020-2022)", textSize, EStyle.Normal, 0);
|
||||||
AddText("GaryCXJk (2020-2021)", 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("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("Markus Bohning (2012)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Mesand (2012)", textSize, EStyle.Normal, 0);
|
AddText("Mesand (2012)", textSize, EStyle.Normal, 0);
|
||||||
AddText("Babene03 (2012)", textSize, EStyle.Normal, 0);
|
|
||||||
scrollY += paragraphSpacing * 3;
|
scrollY += paragraphSpacing * 3;
|
||||||
|
|
||||||
// Art/Graphics
|
// Art/Graphics
|
||||||
AddText("ART/GRAPHICS", headlineSize, EStyle.Bold, 0);
|
AddHeadline("ART/GRAPHICS");
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("UI Artist/Graphics", boldSize, EStyle.Bold, 0);
|
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);
|
AddText("Jiiniasu (2016-2020)", textSize, EStyle.Normal, 0);
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Sound/Audio/Music Design", boldSize, EStyle.Bold, 0);
|
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;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Audio Producer", boldSize, EStyle.Bold, 0);
|
AddText("Audio Producer", boldSize, EStyle.Bold, 0);
|
||||||
AddText("Ivymusic", textSize, EStyle.Normal, 0);
|
AddText("Ivymusic", textSize, EStyle.Normal, 0);
|
||||||
scrollY += paragraphSpacing * 3;
|
scrollY += paragraphSpacing * 3;
|
||||||
|
|
||||||
// Translation
|
// Translation
|
||||||
AddText("TRANSLATION", headlineSize, EStyle.Bold, 0);
|
AddHeadline("TRANSLATION");
|
||||||
scrollY += paragraphSpacing;
|
scrollY += paragraphSpacing;
|
||||||
AddText("Thanks to everyone translating Vocaluxe into different languages.", textSize, EStyle.Normal, 0);
|
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
|
// Website
|
||||||
AddText("www.vocaluxe.org", headlineSize, EStyle.Bold, 0);
|
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) { }
|
public override void ReloadTheme(string xmlPath) { }
|
||||||
@@ -265,7 +308,7 @@ namespace Vocaluxe.Screens
|
|||||||
CVideo.GetFrame(_BackgroundVideo, _BackgroundVideoTime);
|
CVideo.GetFrame(_BackgroundVideo, _BackgroundVideoTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_Animation() && CGraphics.NextScreen != CGraphics.GetScreen(EScreen.Main))
|
if (!_Animation())
|
||||||
{
|
{
|
||||||
_LeaveScreen();
|
_LeaveScreen();
|
||||||
}
|
}
|
||||||
@@ -277,22 +320,39 @@ namespace Vocaluxe.Screens
|
|||||||
{
|
{
|
||||||
base.OnShow();
|
base.OnShow();
|
||||||
|
|
||||||
|
_Leaving = false;
|
||||||
|
_ReturnScreen = CGraphics.CurrentScreen == CGraphics.GetScreen(EScreen.Main) ? EScreen.Main : EScreen.OptionsGame;
|
||||||
|
|
||||||
// Reset positions
|
// Reset positions
|
||||||
foreach (var element in _ScrollingElements)
|
foreach (var element in _ScrollingElements)
|
||||||
{
|
{
|
||||||
if (_ElementStartYPositions.TryGetValue(element, out float startY))
|
if (_ElementStartYPositions.TryGetValue(element, out float startY))
|
||||||
{
|
{
|
||||||
if (element is CText text)
|
element.Y = startY;
|
||||||
{
|
element.Alpha = 1f;
|
||||||
text.Y = startY;
|
|
||||||
}
|
|
||||||
else if (element == _Logo)
|
|
||||||
{
|
|
||||||
_Logo.Y = startY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
_previousVideoElapsedMilliseconds = 0f;
|
||||||
|
|
||||||
var path = Path.Combine(CSettings.ProgramFolder, CSettings.FolderNameGraphics, CSettings.FileNameCreditsVideo);
|
var path = Path.Combine(CSettings.ProgramFolder, CSettings.FolderNameGraphics, CSettings.FileNameCreditsVideo);
|
||||||
@@ -310,6 +370,12 @@ namespace Vocaluxe.Screens
|
|||||||
CLog.Error("Background video not found: " + path);
|
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)
|
foreach (var element in _ScrollingElements)
|
||||||
{
|
{
|
||||||
element.Visible = true;
|
element.Visible = true;
|
||||||
@@ -318,13 +384,13 @@ namespace Vocaluxe.Screens
|
|||||||
// Start the scroll timer immediately
|
// Start the scroll timer immediately
|
||||||
_ScrollTimer.Reset();
|
_ScrollTimer.Reset();
|
||||||
_ScrollTimer.Start();
|
_ScrollTimer.Start();
|
||||||
_previousAnimationElapsedMilliseconds = 0;
|
|
||||||
_BackgroundVideoTime = 0f;
|
_BackgroundVideoTime = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var element in _ScrollingElements)
|
public override void OnClose()
|
||||||
{
|
{
|
||||||
element.Visible = true;
|
base.OnClose();
|
||||||
}
|
CBackgroundMusic.StopEventMusic();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
@@ -342,7 +408,14 @@ namespace Vocaluxe.Screens
|
|||||||
|
|
||||||
private void _LeaveScreen()
|
private void _LeaveScreen()
|
||||||
{
|
{
|
||||||
CGraphics.FadeTo(EScreen.OptionsGame);
|
if (_Leaving)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Leaving = true;
|
||||||
|
CBackgroundMusic.StopEventMusic();
|
||||||
|
CGraphics.FadeTo(_ReturnScreen);
|
||||||
|
|
||||||
if (_BackgroundVideo != null)
|
if (_BackgroundVideo != null)
|
||||||
{
|
{
|
||||||
@@ -358,42 +431,40 @@ namespace Vocaluxe.Screens
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var deltaTime = (_ScrollTimer.ElapsedMilliseconds - _previousAnimationElapsedMilliseconds) / 1000f; // Convert milliseconds to seconds
|
var elapsed = _ScrollTimer.ElapsedMilliseconds / 1000f; // Convert milliseconds to seconds
|
||||||
_previousAnimationElapsedMilliseconds = _ScrollTimer.ElapsedMilliseconds;
|
var offset = elapsed * _ScrollSpeed;
|
||||||
|
var endOffset = Math.Min(offset, _MaxScrollOffset);
|
||||||
|
var endAlpha = 1f;
|
||||||
|
|
||||||
var scrollSpeed = 60f;
|
if (endOffset >= _MaxScrollOffset)
|
||||||
|
|
||||||
foreach (var element in _ScrollingElements)
|
|
||||||
{
|
{
|
||||||
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
|
for (var i = 0; i < _ScrollingElements.Count; i++)
|
||||||
var lastElement = _ScrollingElements[_ScrollingElements.Count - 1];
|
|
||||||
float elementBottomY = 0;
|
|
||||||
if (lastElement is CText lastText)
|
|
||||||
{
|
{
|
||||||
elementBottomY = lastText.Y + lastText.H;
|
var element = _ScrollingElements[i];
|
||||||
}
|
if (_ElementStartYPositions.TryGetValue(element, out float startY))
|
||||||
else if (lastElement == _Logo)
|
{
|
||||||
{
|
var isEndElement = i >= _ScrollingElements.Count - _NumEndElements;
|
||||||
elementBottomY = _Logo.Y + _Logo.Rect.H;
|
element.Y = startY - (isEndElement ? endOffset : offset);
|
||||||
}
|
SRectF rect = element.Rect;
|
||||||
|
var edgeDistance = Math.Min(rect.Y, CSettings.RenderH - rect.Y - rect.H);
|
||||||
if (elementBottomY < 0)
|
element.Alpha = Math.Max(0f, Math.Min(1f, edgeDistance / _EdgeFadeHeight)) * (isEndElement ? endAlpha : 1f);
|
||||||
{
|
}
|
||||||
_ScrollTimer.Stop();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,6 +291,7 @@ namespace VocaluxeLib
|
|||||||
void SetStreamVolume(int streamId, int volume);
|
void SetStreamVolume(int streamId, int volume);
|
||||||
void SetGlobalVolume(int volume);
|
void SetGlobalVolume(int volume);
|
||||||
bool IsPaused(int streamId);
|
bool IsPaused(int streamId);
|
||||||
|
void SetLoop(int streamId, bool loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ICover
|
public interface ICover
|
||||||
|
|||||||
@@ -25,7 +25,20 @@ namespace VocaluxeLib.Utils.Player
|
|||||||
protected readonly float _FadeTime = CBase.Settings.GetSoundPlayerFadeTime();
|
protected readonly float _FadeTime = CBase.Settings.GetSoundPlayerFadeTime();
|
||||||
public string FilePath { get; private set; }
|
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>
|
/// <summary>
|
||||||
/// Gets the current stream position or sets it
|
/// Gets the current stream position or sets it
|
||||||
@@ -75,7 +88,7 @@ namespace VocaluxeLib.Utils.Player
|
|||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
|
|
||||||
_StreamId = CBase.Sound.Load(file, false, true);
|
_StreamId = CBase.Sound.Load(file, Loop, true);
|
||||||
if (_StreamId < 0)
|
if (_StreamId < 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -163,19 +176,12 @@ namespace VocaluxeLib.Utils.Player
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var finished = CBase.Sound.IsFinished(_StreamId);
|
|
||||||
if (Loop)
|
if (Loop)
|
||||||
{
|
{
|
||||||
if (finished)
|
|
||||||
{
|
|
||||||
// Restart
|
|
||||||
Stop();
|
|
||||||
Play();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var finished = CBase.Sound.IsFinished(_StreamId);
|
||||||
var len = CBase.Sound.GetLength(_StreamId);
|
var len = CBase.Sound.GetLength(_StreamId);
|
||||||
var timeToPlay = len > 0f ? len - CBase.Sound.GetPosition(_StreamId) : _FadeTime + 1f;
|
var timeToPlay = len > 0f ? len - CBase.Sound.GetPosition(_StreamId) : _FadeTime + 1f;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user