diff --git a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs index b50c51ad..6cfad473 100644 --- a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs +++ b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs @@ -504,7 +504,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe CBase.BackgroundMusic.SetStatus(true); _PreviewStream = CBase.Sound.Load(song.GetMP3(), false); - CBase.Sound.SetPosition(_PreviewStream, song.PreviewStart); + CBase.Sound.SetPosition(_PreviewStream, song.Preview.StartTime); CBase.Sound.SetStreamVolume(_PreviewStream, 0f); CBase.Sound.Play(_PreviewStream); CBase.Sound.Fade(_PreviewStream, CBase.Config.GetBackgroundMusicVolume(), 1f); @@ -528,7 +528,7 @@ namespace VocaluxeLib.PartyModes.TicTacToe CBase.BackgroundMusic.SetStatus(true); _PreviewStream = CBase.Sound.Load(song.GetMP3(), false); - CBase.Sound.SetPosition(_PreviewStream, song.PreviewStart); + CBase.Sound.SetPosition(_PreviewStream, song.Preview.StartTime); CBase.Sound.SetStreamVolume(_PreviewStream, 0f); CBase.Sound.Play(_PreviewStream); CBase.Sound.Fade(_PreviewStream, CBase.Config.GetBackgroundMusicVolume(), 1f); diff --git a/Vocaluxe.sln b/Vocaluxe.sln index d894246e..0896a6f6 100644 --- a/Vocaluxe.sln +++ b/Vocaluxe.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Express 2012 for Windows Desktop +# Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vocaluxe", "Vocaluxe\Vocaluxe.csproj", "{2A1E0F2D-5392-4BB7-B0A1-A9DFA13E79FF}" ProjectSection(ProjectDependencies) = postProject {6F3E1874-F28A-40E9-AFBD-31934D593678} = {6F3E1874-F28A-40E9-AFBD-31934D593678} @@ -154,7 +154,4 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection EndGlobal diff --git a/Vocaluxe/Base/CBackgroundMusic.cs b/Vocaluxe/Base/CBackgroundMusic.cs index 20a1c8ea..4f315e97 100644 --- a/Vocaluxe/Base/CBackgroundMusic.cs +++ b/Vocaluxe/Base/CBackgroundMusic.cs @@ -90,11 +90,6 @@ namespace Vocaluxe.Base get { return _CurrentPlaylistElement.SongID; } } - public static bool Duet - { - get { return _CurrentPlaylistElement.Duet; } - } - public static bool SongHasVideo { get { return File.Exists(_CurrentPlaylistElement.VideoFilePath); } @@ -581,17 +576,6 @@ namespace Vocaluxe.Base } } - public bool Duet - { - get - { - if (_Song != null) - return _Song.IsDuet; - - return false; - } - } - public CPlaylistElement(CSong song) { MusicFilePath = string.Empty; diff --git a/Vocaluxe/Base/CConfig.cs b/Vocaluxe/Base/CConfig.cs index 00d8b55a..f4074ad1 100644 --- a/Vocaluxe/Base/CConfig.cs +++ b/Vocaluxe/Base/CConfig.cs @@ -37,6 +37,7 @@ namespace Vocaluxe.Base // Debug public static EDebugLevel DebugLevel = EDebugLevel.TR_CONFIG_OFF; + public static EOffOn SaveModifiedSongs = EOffOn.TR_CONFIG_OFF; // Graphics #if WIN @@ -169,6 +170,7 @@ namespace Vocaluxe.Base return; xmlReader.TryGetEnumValue("//root/Debug/DebugLevel", ref DebugLevel); + xmlReader.TryGetEnumValue("//root/Debug/SaveModifiedSongs", ref SaveModifiedSongs); #region Graphics xmlReader.TryGetEnumValue("//root/Graphics/Renderer", ref Renderer); @@ -328,6 +330,9 @@ namespace Vocaluxe.Base writer.WriteComment("DebugLevel: " + CHelper.ListStrings(Enum.GetNames(typeof(EDebugLevel)))); writer.WriteElementString("DebugLevel", Enum.GetName(typeof(EDebugLevel), DebugLevel)); + writer.WriteComment("SaveModifiedSongs: " + CHelper.ListStrings(Enum.GetNames(typeof(EOffOn)))); + writer.WriteElementString("SaveModifiedSongs", Enum.GetName(typeof(EOffOn), SaveModifiedSongs)); + writer.WriteEndElement(); #endregion Debug diff --git a/Vocaluxe/Base/CGame.cs b/Vocaluxe/Base/CGame.cs index dced2fa5..6eb98838 100644 --- a/Vocaluxe/Base/CGame.cs +++ b/Vocaluxe/Base/CGame.cs @@ -234,7 +234,7 @@ namespace Vocaluxe.Base for (int beat = _LastEvalBeat + 1; beat <= RecordedBeat; beat++) { if ((_SongQueue.GetCurrentGameMode() == EGameMode.TR_GAMEMODE_MEDLEY && song.Medley.EndBeat == beat) || - (_SongQueue.GetCurrentGameMode() == EGameMode.TR_GAMEMODE_SHORTSONG && song.ShortEnd == beat)) + (_SongQueue.GetCurrentGameMode() == EGameMode.TR_GAMEMODE_SHORTSONG && song.ShortEnd.EndBeat == beat)) Players[p].SongFinished = true; CSongLine[] lines = song.Notes.GetVoice(Players[p].VoiceNr).Lines; @@ -290,7 +290,7 @@ namespace Vocaluxe.Base //CSound.RecordSetTone(p, Tone); double points = (CSettings.MaxScore - CSettings.LinebonusScore) * (double)notes[note].PointsForBeat / song.Notes.GetVoice(Players[p].VoiceNr).Points; - if (notes[note].NoteType == ENoteType.Golden) + if (notes[note].Type == ENoteType.Golden) Players[p].PointsGoldenNotes += points; Players[p].Points += points; @@ -384,7 +384,7 @@ namespace Vocaluxe.Base { if (note.StartBeat < nextStart) { - if (note.Hit && note.HitNote.NoteType==ENoteType.Golden) + if (note.Hit && note.HitNote.Type == ENoteType.Golden) Players[p].PointsGoldenNotes += note.Points; Players[p].Points += note.Points; } @@ -394,7 +394,7 @@ namespace Vocaluxe.Base } while (line.NoteCount > n && n >= 0) { - if (line.Notes[n].Hit && line.Notes[n].HitNote.NoteType == ENoteType.Golden) + if (line.Notes[n].Hit && line.Notes[n].HitNote.Type == ENoteType.Golden) Players[p].PointsGoldenNotes -= line.Notes[n].Points; Players[p].Points -= line.Notes[n].Points; line.DeleteNote(n); @@ -406,9 +406,9 @@ namespace Vocaluxe.Base } Players[p].SungLines.RemoveRange(deleteLine, Players[p].SungLines.Count - deleteLine); } - CSong song = GetSong(); - for (int i = 0; i < GetSong().Notes.Voices[0].Lines.Length; i++) - CLog.LogError((i + 1) + ". " + song.Notes.Voices[0].Lines[i].NoteCount + " - " + Players[0].SungLines[i].NoteCount); + //CSong song = GetSong(); + //for (int i = 0; i < GetSong().Notes.Voices[0].Lines.Length; i++) + // CLog.LogError((i + 1) + ". " + song.Notes.Voices[0].Lines[i].NoteCount + " - " + Players[0].SungLines[i].NoteCount); CSound.SetPosition(soundStream, time); CVideo.Skip(vidStream, time, GetSong().VideoGap); diff --git a/Vocaluxe/Base/CLog.cs b/Vocaluxe/Base/CLog.cs index 350950bb..6f87a74c 100644 --- a/Vocaluxe/Base/CLog.cs +++ b/Vocaluxe/Base/CLog.cs @@ -117,8 +117,7 @@ namespace Vocaluxe.Base MessageBox.Show(errorText, CSettings.ProgramName, MessageBoxButtons.OK, MessageBoxIcon.Error); if (e != null) errorText += ": " + e; - _ErrorLog.Add(_NumErrors + ") " + errorText + "\r\n"); - _ErrorLog.Add(String.Empty); + _ErrorLog.Add(_NumErrors + ") " + errorText); if (exit) Environment.Exit(Environment.ExitCode); } diff --git a/Vocaluxe/Base/CMain.cs b/Vocaluxe/Base/CMain.cs index 2d2a9ea9..6ff4068a 100644 --- a/Vocaluxe/Base/CMain.cs +++ b/Vocaluxe/Base/CMain.cs @@ -60,6 +60,11 @@ namespace Vocaluxe.Base class CBconfig : IConfig { + public EOffOn GetSaveModifiedSongs() + { + return CConfig.SaveModifiedSongs; + } + public void SetBackgroundMusicVolume(int newVolume) { CConfig.BackgroundMusicVolume = newVolume; @@ -110,7 +115,7 @@ namespace Vocaluxe.Base return CConfig.CoverSize; } - public IEnumerable GetSongFolder() + public IEnumerable GetSongFolders() { return CConfig.SongFolder; } @@ -527,6 +532,11 @@ namespace Vocaluxe.Base return CGame.GetTimeFromBeats(beat, bpm); } + public float GetBeatFromTime(float time, float bpm, float gap) + { + return CGame.GetBeatFromTime(time, bpm, gap); + } + public void AddSong(int songID, EGameMode gameMode) { CGame.AddSong(songID, gameMode); diff --git a/Vocaluxe/Base/CSongSorter.cs b/Vocaluxe/Base/CSongSorter.cs index 9526cedb..eefb796f 100644 --- a/Vocaluxe/Base/CSongSorter.cs +++ b/Vocaluxe/Base/CSongSorter.cs @@ -157,10 +157,10 @@ namespace Vocaluxe.Base switch (_SongSorting) { case ESongSorting.TR_CONFIG_EDITION: - fieldName = "Edition"; + fieldName = "Editions"; break; case ESongSorting.TR_CONFIG_GENRE: - fieldName = "Genre"; + fieldName = "Genres"; break; case ESongSorting.TR_CONFIG_FOLDER: fieldName = "FolderName"; @@ -177,7 +177,7 @@ namespace Vocaluxe.Base fieldName = "Year"; break; case ESongSorting.TR_CONFIG_LANGUAGE: - fieldName = "Language"; + fieldName = "Languages"; break; case ESongSorting.TR_CONFIG_DATEADDED: fieldName = "DateAdded"; diff --git a/Vocaluxe/Base/CSongs.cs b/Vocaluxe/Base/CSongs.cs index aeed4b92..c45fc8a7 100644 --- a/Vocaluxe/Base/CSongs.cs +++ b/Vocaluxe/Base/CSongs.cs @@ -312,7 +312,7 @@ namespace Vocaluxe.Base if (song == null) continue; song.ID = _Songs.Count; - if (song.ReadNotes()) + if (song.LoadNotes()) _Songs.Add(song); } CLog.StopBenchmark(2, "Read TXTs"); diff --git a/Vocaluxe/GameModes/CGameModes.cs b/Vocaluxe/GameModes/CGameModes.cs index 460943ea..a7e7d4b2 100644 --- a/Vocaluxe/GameModes/CGameModes.cs +++ b/Vocaluxe/GameModes/CGameModes.cs @@ -52,7 +52,7 @@ namespace Vocaluxe.GameModes { protected override CSong _PrepareSong(CSong song) { - return song; + return (song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_NORMAL)) ? song : null; } } @@ -60,7 +60,7 @@ namespace Vocaluxe.GameModes { protected override CSong _PrepareSong(CSong song) { - return (song.IsDuet) ? song : null; + return (song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_DUET)) ? song : null; } } @@ -68,9 +68,11 @@ namespace Vocaluxe.GameModes { protected override CSong _PrepareSong(CSong song) { - var newSong = new CSong(song) {Finish = CGame.GetTimeFromBeats(song.ShortEnd, song.BPM) + CSettings.DefaultMedleyFadeOutTime + song.Gap}; + if (!song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_SHORTSONG)) + return null; + var newSong = new CSong(song) {Finish = CGame.GetTimeFromBeats(song.ShortEnd.EndBeat, song.BPM) + CSettings.DefaultMedleyFadeOutTime + song.Gap}; // set lines to short mode - newSong.Notes.SetMedley(song.Notes.GetVoice(0).Lines[0].FirstNoteBeat, song.ShortEnd); + newSong.Notes.SetMedley(0, song.ShortEnd.EndBeat); return newSong; } @@ -80,6 +82,8 @@ namespace Vocaluxe.GameModes { protected override CSong _PrepareSong(CSong song) { + if (!song.IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY)) + return null; var newSong = new CSong(song) {Start = CGame.GetTimeFromBeats(song.Medley.StartBeat, song.BPM) - song.Medley.FadeInTime + song.Gap}; if (newSong.Start < 0f) newSong.Start = 0f; diff --git a/Vocaluxe/Lib/Playlist/CPlaylistFile.cs b/Vocaluxe/Lib/Playlist/CPlaylistFile.cs index 3c17a424..6c7f5228 100644 --- a/Vocaluxe/Lib/Playlist/CPlaylistFile.cs +++ b/Vocaluxe/Lib/Playlist/CPlaylistFile.cs @@ -180,7 +180,11 @@ namespace Vocaluxe.Lib.Playlist public void AddSong(int songID) { - var song = new CPlaylistSong {SongID = songID, GameMode = CSongs.GetSong(songID).IsDuet ? EGameMode.TR_GAMEMODE_DUET : EGameMode.TR_GAMEMODE_NORMAL}; + var song = new CPlaylistSong + { + SongID = songID, + GameMode = CSongs.GetSong(songID).IsGameModeAvailable(EGameMode.TR_GAMEMODE_DUET) ? EGameMode.TR_GAMEMODE_DUET : EGameMode.TR_GAMEMODE_NORMAL + }; Songs.Add(song); } diff --git a/Vocaluxe/Screens/CPopupScreenPlayerControl.cs b/Vocaluxe/Screens/CPopupScreenPlayerControl.cs index c48b2f08..9ffe1779 100644 --- a/Vocaluxe/Screens/CPopupScreenPlayerControl.cs +++ b/Vocaluxe/Screens/CPopupScreenPlayerControl.cs @@ -200,7 +200,7 @@ namespace Vocaluxe.Screens CGame.ClearSongs(); var gm = EGameMode.TR_GAMEMODE_NORMAL; - if (CSongs.AllSongs[songNr].IsDuet) + if (CSongs.AllSongs[songNr].IsGameModeAvailable(EGameMode.TR_GAMEMODE_DUET)) gm = EGameMode.TR_GAMEMODE_DUET; CGame.AddSong(songNr, gm); diff --git a/Vocaluxe/Screens/CScreenNames.cs b/Vocaluxe/Screens/CScreenNames.cs index f1f548ab..62a99c37 100644 --- a/Vocaluxe/Screens/CScreenNames.cs +++ b/Vocaluxe/Screens/CScreenNames.cs @@ -22,6 +22,7 @@ using Vocaluxe.Base; using VocaluxeLib; using VocaluxeLib.Draw; using VocaluxeLib.Menu; +using VocaluxeLib.Songs; namespace Vocaluxe.Screens { @@ -597,6 +598,8 @@ namespace Vocaluxe.Screens _CheckMics(); _CheckPlayers(); + CSong firstSong = CGame.GetSong(0); + for (int i = 0; i < CSettings.MaxNumPlayer; i++) { _Statics[_StaticPlayerAvatar[i]].Texture = CProfiles.IsProfileIDValid(CGame.Players[i].ProfileID) ? @@ -607,8 +610,9 @@ namespace Vocaluxe.Screens { _SelectSlides[_SelectSlideDuetPlayer[i]].Clear(); _SelectSlides[_SelectSlideDuetPlayer[i]].Visible = i + 1 <= CGame.NumPlayer; - _SelectSlides[_SelectSlideDuetPlayer[i]].AddValue(CGame.GetSong(1).DuetPart1); - _SelectSlides[_SelectSlideDuetPlayer[i]].AddValue(CGame.GetSong(1).DuetPart2); + + for (int j = 0; j < firstSong.Notes.VoiceCount; j++) + _SelectSlides[_SelectSlideDuetPlayer[i]].AddValue(firstSong.Notes.VoiceNames[j]); _SelectSlides[_SelectSlideDuetPlayer[i]].Selection = i % 2; } else diff --git a/Vocaluxe/Screens/CScreenScore.cs b/Vocaluxe/Screens/CScreenScore.cs index fac4c499..b1e06b1e 100644 --- a/Vocaluxe/Screens/CScreenScore.cs +++ b/Vocaluxe/Screens/CScreenScore.cs @@ -297,10 +297,8 @@ namespace Vocaluxe.Screens string name = CProfiles.GetPlayerName(players[p].ProfileID, p); if (song != null && song.IsDuet) { - if (players[p].VoiceNr == 0 && song.DuetPart1 != "Part 1") - name += " (" + song.DuetPart1 + ")"; - else if (players[p].VoiceNr == 1 && song.DuetPart2 != "Part 2") - name += " (" + song.DuetPart2 + ")"; + if (song.Notes.VoiceNames.IsSet(players[p].VoiceNr)) + name += " (" + song.Notes.VoiceNames[players[p].VoiceNr] + ")"; } _Texts[_TextNames[p, CGame.NumPlayer - 1]].Text = name; diff --git a/Vocaluxe/Screens/CScreenSing.cs b/Vocaluxe/Screens/CScreenSing.cs index e485c684..e792ba4e 100644 --- a/Vocaluxe/Screens/CScreenSing.cs +++ b/Vocaluxe/Screens/CScreenSing.cs @@ -327,7 +327,7 @@ namespace Vocaluxe.Screens _LoadNextSong(); _UpdateSongText(); - _UpdateDuetText(); + _UpdateDuetNames(); if (_FadeOut) return true; @@ -387,12 +387,8 @@ namespace Vocaluxe.Screens for (int p = 0; p < CGame.NumPlayer; p++) { - // ReSharper disable ConvertIfStatementToConditionalTernaryExpression - if (CGame.Players[p].Points < 10000) - // ReSharper restore ConvertIfStatementToConditionalTernaryExpression - _Texts[_TextScores[p, CGame.NumPlayer - 1]].Text = CGame.Players[p].Points.ToString("0000"); - else - _Texts[_TextScores[p, CGame.NumPlayer - 1]].Text = CGame.Players[p].Points.ToString("00000"); + string fmtString = (CGame.Players[p].Points < 10000) ? "0000" : "00000"; + _Texts[_TextScores[p, CGame.NumPlayer - 1]].Text = CGame.Players[p].Points.ToString(fmtString); } if (_CurrentVideo != -1 && !_FadeOut && CConfig.VideosInSongs == EOffOn.TR_CONFIG_ON) @@ -607,12 +603,12 @@ namespace Vocaluxe.Screens _FinishTime = song.Finish; _TimeToFirstNote = 0f; _TimeToFirstNoteDuet = 0f; - var duetPlayer = new int[CGame.NumPlayer]; + var voiceAssignments = new int[CGame.NumPlayer]; if (song.IsDuet) { //Save duet-assignment before resetting - for (int i = 0; i < duetPlayer.Length; i++) - duetPlayer[i] = CGame.Players[i].VoiceNr; + for (int i = 0; i < voiceAssignments.Length; i++) + voiceAssignments[i] = CGame.Players[i].VoiceNr; } CGame.ResetPlayer(); @@ -634,8 +630,9 @@ namespace Vocaluxe.Screens if (song.IsDuet) { - _Texts[_TextDuetName1].Text = song.DuetPart1; - _Texts[_TextDuetName2].Text = song.DuetPart2; + //TODO: Show more than 2 voicenames + _Texts[_TextDuetName1].Text = song.Notes.VoiceNames[0]; + _Texts[_TextDuetName2].Text = song.Notes.VoiceNames[1]; //More than one song: Player is not assigned to line by user //Otherwise, this is done by CScreenNames if (CGame.GetNumSongs() > 1) @@ -646,7 +643,7 @@ namespace Vocaluxe.Screens else { for (int i = 0; i < CGame.NumPlayer; i++) - CGame.Players[i].VoiceNr = duetPlayer[i]; + CGame.Players[i].VoiceNr = voiceAssignments[i]; } } @@ -672,14 +669,11 @@ namespace Vocaluxe.Screens NoteLines[3] = SingNotes[SingBars].AddPlayer(new SRectF(640f, 350f, 590f, 200f, -0.5f), CTheme.ThemeColors.Player[3]); break; */ - _TimerSongText.Stop(); _TimerSongText.Reset(); - _TimerDuetText1.Stop(); _TimerDuetText1.Reset(); - _TimerDuetText2.Stop(); _TimerDuetText2.Reset(); - if (song.Notes.Voices.Length != 2) + if (!song.IsDuet) _TimerSongText.Start(); _StartSong(); @@ -781,7 +775,7 @@ namespace Vocaluxe.Screens _CurrentBeat = CGame.CurrentBeat; - for (int i = 0; i < song.Notes.LinesCount; i++) + for (int i = 0; i < song.Notes.VoiceCount; i++) { if (i > 1) break; // for later @@ -1090,10 +1084,10 @@ namespace Vocaluxe.Screens if (song == null || !song.NotesLoaded) return null; - var alpha = new float[song.Notes.Voices.Length * 2]; + var alpha = new float[song.Notes.VoiceCount * 2]; float currentTime = _CurrentTime - song.Gap; - for (int i = 0; i < song.Notes.LinesCount; i++) + for (int i = 0; i < song.Notes.VoiceCount; i++) { CVoice voice = song.Notes.GetVoice(i); CSongLine[] lines = voice.Lines; @@ -1197,8 +1191,8 @@ namespace Vocaluxe.Screens } else { - _Statics[_StaticSongText].Color.A = (3f - (t - 7f)) / 3f; - _Texts[_TextSongName].Color.A = (3f - (t - 7f)) / 3f; + _Statics[_StaticSongText].Color.A = 10f - t / 3f; + _Texts[_TextSongName].Color.A = 10f - t / 3f; } } else @@ -1215,61 +1209,65 @@ namespace Vocaluxe.Screens } } - private void _UpdateDuetText() + private void _UpdateDuetNames(CText textName, Stopwatch timer) { - if (CGame.GetSong() != null) + if (timer.IsRunning) { - //Timer for first duet-part - if (_TimerDuetText1.IsRunning) + float t = timer.ElapsedMilliseconds / 1000f; + if (t < 10f) { - float t = _TimerDuetText1.ElapsedMilliseconds / 1000f; - if (t < 10f) - { - _Texts[_TextDuetName1].Visible = true; + textName.Visible = true; - if (t < 3f) - _Texts[_TextDuetName1].Color.A = (3f - (3f - t)) / 3f; - else if (t < 7f) - _Texts[_TextDuetName1].Color.A = 1f; - else - _Texts[_TextDuetName1].Color.A = (3f - (t - 7f)) / 3f; - } + if (t < 3f) + textName.Color.A = t / 3f; + else if (t < 7f) + textName.Color.A = 1f; else - { - _Texts[_TextDuetName1].Visible = false; - _TimerDuetText1.Stop(); - } + textName.Color.A = (10f - t) / 3f; } - else if (!_TimerDuetText1.IsRunning && _TimerDuetText1.ElapsedMilliseconds == 0 && _Lyrics[_LyricMainDuet].Alpha > 0 && CGame.GetSong().IsDuet) - _TimerDuetText1.Start(); else - _Texts[_TextDuetName1].Visible = false; - //Timer for second duet-part - if (_TimerDuetText2.IsRunning) { - float t = _TimerDuetText2.ElapsedMilliseconds / 1000f; - if (t < 10f) - { - _Texts[_TextDuetName2].Visible = true; - - if (t < 3f) - _Texts[_TextDuetName2].Color.A = (3f - (3f - t)) / 3f; - else if (t < 7f) - _Texts[_TextDuetName2].Color.A = 1f; - else - _Texts[_TextDuetName2].Color.A = (3f - (t - 7f)) / 3f; - } - else - { - _Texts[_TextDuetName2].Visible = false; - _TimerDuetText2.Stop(); - } + textName.Visible = false; + timer.Stop(); } - else if (!_TimerDuetText2.IsRunning && _TimerDuetText2.ElapsedMilliseconds == 0 && _Lyrics[_LyricMain].Alpha > 0 && CGame.GetSong().IsDuet) - _TimerDuetText2.Start(); - else - _Texts[_TextDuetName2].Visible = false; } + else if (!timer.IsRunning && timer.ElapsedMilliseconds == 0 && _Lyrics[_LyricMainDuet].Alpha > 0 && CGame.GetSong().IsDuet) + timer.Start(); + else + textName.Visible = false; + } + + private void _UpdateDuetNames() + { + if (CGame.GetSong() == null) + return; + //Timer for first duet-part + _UpdateDuetNames(_Texts[_TextDuetName1], _TimerDuetText1); + //Timer for second duet-part + if (_TimerDuetText2.IsRunning) + { + float t = _TimerDuetText2.ElapsedMilliseconds / 1000f; + if (t < 10f) + { + _Texts[_TextDuetName2].Visible = true; + + if (t < 3f) + _Texts[_TextDuetName2].Color.A = (3f - (3f - t)) / 3f; + else if (t < 7f) + _Texts[_TextDuetName2].Color.A = 1f; + else + _Texts[_TextDuetName2].Color.A = (3f - (t - 7f)) / 3f; + } + else + { + _Texts[_TextDuetName2].Visible = false; + _TimerDuetText2.Stop(); + } + } + else if (!_TimerDuetText2.IsRunning && _TimerDuetText2.ElapsedMilliseconds == 0 && _Lyrics[_LyricMain].Alpha > 0 && CGame.GetSong().IsDuet) + _TimerDuetText2.Start(); + else + _Texts[_TextDuetName2].Visible = false; } private void _UpdateTimeLine() diff --git a/Vocaluxe/Screens/CScreenSong.cs b/Vocaluxe/Screens/CScreenSong.cs index 17586fe5..5c27e577 100644 --- a/Vocaluxe/Screens/CScreenSong.cs +++ b/Vocaluxe/Screens/CScreenSong.cs @@ -819,9 +819,8 @@ namespace Vocaluxe.Screens CGame.Reset(); CGame.ClearSongs(); - CGame.AddVisibleSong(songNr, gm); - - CGraphics.FadeTo(EScreens.ScreenNames); + if (CGame.AddVisibleSong(songNr, gm)) + CGraphics.FadeTo(EScreens.ScreenNames); } } @@ -834,9 +833,8 @@ namespace Vocaluxe.Screens CGame.Reset(); CGame.ClearSongs(); - CGame.AddVisibleSong(songNr, gm); - - CGraphics.FadeTo(EScreens.ScreenNames); + if (CGame.AddVisibleSong(songNr, gm)) + CGraphics.FadeTo(EScreens.ScreenNames); } } @@ -844,14 +842,13 @@ namespace Vocaluxe.Screens { if ((CSongs.Category >= 0) && (songNr >= 0)) { - if (CSongs.VisibleSongs[songNr].Medley.Source == EMedleySource.None) + if (!CSongs.VisibleSongs[songNr].IsGameModeAvailable(EGameMode.TR_GAMEMODE_MEDLEY)) return; CGame.Reset(); CGame.ClearSongs(); - CGame.AddVisibleSong(songNr, EGameMode.TR_GAMEMODE_MEDLEY); - - CGraphics.FadeTo(EScreens.ScreenNames); + if (CGame.AddVisibleSong(songNr, EGameMode.TR_GAMEMODE_MEDLEY)) + CGraphics.FadeTo(EScreens.ScreenNames); } } @@ -1165,6 +1162,15 @@ namespace Vocaluxe.Screens } } + private void _CheckAndAddGameMode(EGameMode gameMode) + { + if (CSongs.VisibleSongs[_SongMenus[_SongMenu].GetSelectedSong()].IsGameModeAvailable(gameMode)) + { + _SelectSlides[_SelectSlideOptionsMode].AddValue(Enum.GetName(typeof(EGameMode), gameMode)); + _AvailableGameModes.Add(gameMode); + } + } + private void _ShowSongOptionsSong() { var lastMode = EGameMode.TR_GAMEMODE_NORMAL; @@ -1172,23 +1178,11 @@ namespace Vocaluxe.Screens lastMode = _AvailableGameModes[_SelectSlides[_SelectSlideOptionsMode].Selection]; _AvailableGameModes.Clear(); _SelectSlides[_SelectSlideOptionsMode].Clear(); - if (CSongs.VisibleSongs[_SongMenus[_SongMenu].GetSelectedSong()].IsDuet) - { - _SelectSlides[_SelectSlideOptionsMode].AddValue(Enum.GetName(typeof(EGameMode), EGameMode.TR_GAMEMODE_DUET)); - _AvailableGameModes.Add(EGameMode.TR_GAMEMODE_DUET); - } - else - { - _SelectSlides[_SelectSlideOptionsMode].AddValue(Enum.GetName(typeof(EGameMode), EGameMode.TR_GAMEMODE_NORMAL)); - _AvailableGameModes.Add(EGameMode.TR_GAMEMODE_NORMAL); - _SelectSlides[_SelectSlideOptionsMode].AddValue(Enum.GetName(typeof(EGameMode), EGameMode.TR_GAMEMODE_SHORTSONG)); - _AvailableGameModes.Add(EGameMode.TR_GAMEMODE_SHORTSONG); - } - if (CSongs.VisibleSongs[_SongMenus[_SongMenu].GetSelectedSong()].Medley.Source != EMedleySource.None) - { - _SelectSlides[_SelectSlideOptionsMode].AddValue(Enum.GetName(typeof(EGameMode), EGameMode.TR_GAMEMODE_MEDLEY)); - _AvailableGameModes.Add(EGameMode.TR_GAMEMODE_MEDLEY); - } + _CheckAndAddGameMode(EGameMode.TR_GAMEMODE_NORMAL); + _CheckAndAddGameMode(EGameMode.TR_GAMEMODE_DUET); + _CheckAndAddGameMode(EGameMode.TR_GAMEMODE_SHORTSONG); + _CheckAndAddGameMode(EGameMode.TR_GAMEMODE_MEDLEY); + //Set SelectSlide-Selection to last selected game-mode if possible for (int i = 0; i < _AvailableGameModes.Count; i++) { diff --git a/Vocaluxe/SongQueue/CSongQueue.cs b/Vocaluxe/SongQueue/CSongQueue.cs index b23f1288..e2a461cf 100644 --- a/Vocaluxe/SongQueue/CSongQueue.cs +++ b/Vocaluxe/SongQueue/CSongQueue.cs @@ -69,7 +69,7 @@ namespace Vocaluxe.SongQueue private bool _AddSong(int songID, EGameMode gameMode) { - if (gameMode == EGameMode.TR_GAMEMODE_DUET && !CSongs.GetSong(songID).IsDuet) + if (!CSongs.GetSong(songID).IsGameModeAvailable(gameMode)) return false; _SongQueue.Add(new SSongQueueEntry(songID, gameMode)); diff --git a/VocaluxeLib/CHelper.cs b/VocaluxeLib/CHelper.cs index ad04b6db..bd06cecb 100644 --- a/VocaluxeLib/CHelper.cs +++ b/VocaluxeLib/CHelper.cs @@ -53,7 +53,6 @@ namespace VocaluxeLib public static string ListStrings(string[] str) { string result = string.Empty; - for (int i = 0; i < str.Length; i++) { result += str[i]; @@ -160,13 +159,7 @@ namespace VocaluxeLib return files; } - public static bool TryParse(string value, out T result) - where T : struct - { - return TryParse(value, out result, false); - } - - public static bool TryParse(string value, out T result, bool ignoreCase) + public static bool TryParse(string value, out T result, bool ignoreCase=false) where T : struct { result = default(T); @@ -199,7 +192,7 @@ namespace VocaluxeLib static class CEncoding { - public static Encoding GetEncoding(string encodingName) + public static Encoding GetEncoding(this string encodingName) { switch (encodingName) { @@ -214,11 +207,11 @@ namespace VocaluxeLib case "UTF8": return Encoding.UTF8; default: - return Encoding.UTF8; + return Encoding.Default; } } - public static string GetEncodingName(Encoding enc) + public static string GetEncodingName(this Encoding enc) { string result = "UTF8"; diff --git a/VocaluxeLib/Extensions.cs b/VocaluxeLib/Extensions.cs new file mode 100644 index 00000000..d6c78529 --- /dev/null +++ b/VocaluxeLib/Extensions.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace VocaluxeLib +{ + public static class Extensions + { + /// + /// Resizes the given list to the given size. Removes elements or adds default values + /// + /// + /// + /// + /// + public static void Resize(this List list, int size, T defaultValue = default(T)) + { + int curSize = list.Count; + if (size < curSize) + list.RemoveRange(size, curSize - size); + else if (size > curSize) + list.AddRange(Enumerable.Repeat(defaultValue, size - curSize)); + } + + /// + /// Makes the list at least the given size adding the given default values. + /// + /// + /// + /// + /// + public static void EnsureSize(this List list, int size, T defaultValue) + { + int curSize = list.Count; + if (size > curSize) + list.AddRange(Enumerable.Repeat(defaultValue, size - curSize)); + } + + /// + /// Makes the list at least the given size adding a new instance of T + /// + /// + /// + /// + public static void EnsureSize(this List list, int size) where T : new() + { + if (size > list.Count) + list.Add(new T()); + } + + /// + /// Gets all set bits starting from lowest (Bit 0) + /// + /// + /// + public static IEnumerable GetSetBits(this int value) + { + var result = new List(); + int curBit = 0; + //Evaluate as bitset + while (value > 0) + { + if ((value & 1) != 0) + result.Add(curBit); + value >>= 1; + curBit++; + } + return result; + } + + public static bool ContainsIgnoreCase(this string value, string other) + { + return value.IndexOf(other, StringComparison.CurrentCultureIgnoreCase) >= 0; + } + + public static string TrimMultipleWs(this string value) + { + if (string.IsNullOrEmpty(value)) + return ""; + int start = 0; + int len = value.Length; + while (value[start] == ' ') + { + start++; + if (start >= len) + return ""; + } + if (start > 0) + start--; + int end = --len; + while (value[end] == ' ') + end--; + if (end < len) + end++; + if (end - start < len) + value = value.Substring(start, end - start + 1); + return value; + } + + /// + /// Converts value to a string in fixed point notation using invariant formating (english style decimal point) + /// + /// Value to be converted + /// String representation of value + public static string ToInvariantString(this float value) + { + return value.ToString("F", NumberFormatInfo.InvariantInfo); + } + } +} \ No newline at end of file diff --git a/VocaluxeLib/IBasic.cs b/VocaluxeLib/IBasic.cs index 2c34490b..efdb45df 100644 --- a/VocaluxeLib/IBasic.cs +++ b/VocaluxeLib/IBasic.cs @@ -29,6 +29,8 @@ namespace VocaluxeLib { public interface IConfig { + EOffOn GetSaveModifiedSongs(); + void SetBackgroundMusicVolume(int newVolume); int GetBackgroundMusicVolume(); @@ -45,7 +47,7 @@ namespace VocaluxeLib int GetCoverSize(); - IEnumerable GetSongFolder(); + IEnumerable GetSongFolders(); ESongSorting GetSongSorting(); EOffOn GetTabs(); EOffOn GetIgnoreArticles(); @@ -179,6 +181,7 @@ namespace VocaluxeLib double GetRandomDouble(); float GetTimeFromBeats(float beat, float bpm); + float GetBeatFromTime(float time, float bpm, float gap); void AddSong(int songID, EGameMode gameMode); void Reset(); diff --git a/VocaluxeLib/Menu/CLyric.cs b/VocaluxeLib/Menu/CLyric.cs index 0191be9e..7c1376a5 100644 --- a/VocaluxeLib/Menu/CLyric.cs +++ b/VocaluxeLib/Menu/CLyric.cs @@ -23,15 +23,6 @@ using VocaluxeLib.Songs; namespace VocaluxeLib.Menu { - struct SNote - { - public string Text; - public int StartBeat; - public int EndBeat; - public int Duration; - public ENoteType Type; - } - struct SThemeLyrics { public string Name; @@ -46,7 +37,10 @@ namespace VocaluxeLib.Menu private SThemeLyrics _Theme; private bool _ThemeLoaded; - private readonly List _Notes; + /// + /// Holds a reference to Songline. DO NOT MODIFY + /// + private CSongLine _Line; private CText _Text; private float _X; @@ -109,7 +103,7 @@ namespace VocaluxeLib.Menu _MaxW = 1f; _H = 1f; _Width = 1f; - _Notes = new List(); + _Line = new CSongLine(); _Text = new CText(_PartyModeID); LyricStyle = ELyricStyle.Fill; @@ -201,28 +195,27 @@ namespace VocaluxeLib.Menu public void SetLine(CSongLine line) { - _Notes.Clear(); - + _Line = line; _Width = 0f; foreach (CSongNote note in line.Notes) { - var n = new SNote {Text = note.Text, StartBeat = note.StartBeat, EndBeat = note.EndBeat, Duration = note.Duration, Type = note.NoteType}; - - _Text.Text = note.Text; - _Text.Style = EStyle.Bold; - - if (n.Type == ENoteType.Freestyle) - _Text.Style = EStyle.BoldItalic; - - RectangleF rect = CBase.Fonts.GetTextBounds(_Text); - _Width += rect.Width; - _Notes.Add(n); + _SetText(note); + _Width += _Text.Rect.W; } } + private void _SetText(CSongNote note) + { + _Text.Text = note.Text; + _Text.Style = EStyle.Bold; + + if (note.Type == ENoteType.Freestyle) + _Text.Style = EStyle.BoldItalic; + } + public void Clear() { - _Notes.Clear(); + _Line = new CSongLine(); } public float GetCurrentLyricPosX() @@ -260,15 +253,10 @@ namespace VocaluxeLib.Menu { float x = _X - _Width / 2; - foreach (SNote note in _Notes) + foreach (CSongNote note in _Line.Notes) { _Text.X = x; - _Text.Style = EStyle.Bold; - _Text.Text = note.Text; - RectangleF rect = CBase.Fonts.GetTextBounds(_Text); - - if (note.Type == ENoteType.Freestyle) - _Text.Style = EStyle.BoldItalic; + _SetText(note); if (currentBeat >= note.StartBeat) { @@ -276,9 +264,8 @@ namespace VocaluxeLib.Menu { _Text.Color = _ColorProcessed; - - float diff = note.EndBeat - note.StartBeat; - if (Math.Abs(diff) < float.Epsilon) + int diff = note.EndBeat - note.StartBeat; + if (diff <= 0) _Text.Draw(0f, 1f); else { @@ -299,7 +286,7 @@ namespace VocaluxeLib.Menu _Text.Draw(); } - x += rect.Width; + x += _Text.Rect.W; } } @@ -308,37 +295,27 @@ namespace VocaluxeLib.Menu float x = _X - _Width / 2; // most left position //find last active note - int lastNote = -1; - for (int i = 0; i < _Notes.Count; i++) - { - if (currentBeat >= _Notes[i].StartBeat) - lastNote = i; - } + int lastNote = _Line.FindPreviousNote((int)currentBeat); int zoomNote = -1; int endBeat = -1; float zoomx = 0f; - for (int note = 0; note < _Notes.Count; note++) + for (int note = 0; note < _Line.Notes.Length; note++) { _Text.X = x; - _Text.Style = EStyle.Bold; - _Text.Text = _Notes[note].Text; - RectangleF rect = CBase.Fonts.GetTextBounds(_Text); + _SetText(_Line.Notes[note]); - if (_Notes[note].Type == ENoteType.Freestyle) - _Text.Style = EStyle.BoldItalic; - - if (currentBeat >= _Notes[note].StartBeat) + if (currentBeat >= _Line.Notes[note].StartBeat) { - int endbeat = _Notes[note].EndBeat; - if (note < _Notes.Count - 1) - endbeat = _Notes[note + 1].StartBeat - 1; + int curEndBeat = _Line.Notes[note].EndBeat; + if (note < _Line.Notes.Length - 1) + curEndBeat = _Line.Notes[note + 1].StartBeat - 1; - if (currentBeat <= endbeat) + if (currentBeat <= curEndBeat) { zoomNote = note; - endBeat = endbeat; + endBeat = curEndBeat; zoomx = _Text.X; } else @@ -356,49 +333,37 @@ namespace VocaluxeLib.Menu _Text.Draw(); } - x += rect.Width; + x += _Text.Rect.W; } if (zoomNote > -1) { - if (_Notes[zoomNote].Duration == 0) - return; - _Text.X = zoomx; - _Text.Text = _Notes[zoomNote].Text; - _Text.Color = _ColorProcessed; - _Text.Style = EStyle.Bold; + _SetText(_Line.Notes[zoomNote]); - if (_Notes[zoomNote].Type == ENoteType.Freestyle) - _Text.Style = EStyle.BoldItalic; - - RectangleF rect = CBase.Fonts.GetTextBounds(_Text); - - float diff = endBeat - _Notes[zoomNote].StartBeat; + float diff = endBeat - _Line.Notes[zoomNote].StartBeat; if (diff <= 0f) diff = 1f; - float p = (currentBeat - _Notes[zoomNote].StartBeat) / diff; + float p = (currentBeat - _Line.Notes[zoomNote].StartBeat) / diff; if (p > 1f) p = 1f; p = 1f - p; float ty = _Text.Y; - float tx = _Text.X; float th = _Text.Height; float tz = _Text.Z; + SRectF normalRect = _Text.Rect; _Text.Height += _Text.Height * p * 0.4f; - RectangleF rectz = CBase.Fonts.GetTextBounds(_Text); - _Text.X -= (rectz.Width - rect.Width) / 2f; - _Text.Y -= (rectz.Height - rect.Height) / 2f; + _Text.X -= (_Text.Rect.W - normalRect.W) / 2f; + _Text.Y -= (_Text.Rect.W - normalRect.H) / 2f; _Text.Z -= 0.1f; _Text.Draw(); _Text.Y = ty; - _Text.X = tx; _Text.Height = th; _Text.Z = tz; } @@ -408,15 +373,10 @@ namespace VocaluxeLib.Menu { float x = _X - _Width / 2; // most left position - foreach (SNote note in _Notes) + foreach (CSongNote note in _Line.Notes) { _Text.X = x; - _Text.Style = EStyle.Bold; - _Text.Text = note.Text; - RectangleF rect = CBase.Fonts.GetTextBounds(_Text); - - if (note.Type == ENoteType.Freestyle) - _Text.Style = EStyle.BoldItalic; + _SetText(note); if (currentBeat >= note.StartBeat) { @@ -430,7 +390,7 @@ namespace VocaluxeLib.Menu _Text.Draw(); } - x += rect.Width; + x += _Text.Rect.W; } } @@ -439,29 +399,23 @@ namespace VocaluxeLib.Menu float x = _X - _Width / 2; // most left position //find last active note - int lastNote = -1; - for (int i = 0; i < _Notes.Count; i++) - { - if (currentBeat >= _Notes[i].StartBeat) - lastNote = i; - } + int lastNote = _Line.FindPreviousNote((int)currentBeat); int jumpNote = -1; float jumpx = 0f; - for (int note = 0; note < _Notes.Count; note++) + for (int note = 0; note < _Line.Notes.Length; note++) { _Text.X = x; - _Text.Style = EStyle.Bold; - _Text.Text = _Notes[note].Text; - RectangleF rect = CBase.Fonts.GetTextBounds(_Text); + _SetText(_Line.Notes[note]); - if (_Notes[note].Type == ENoteType.Freestyle) - _Text.Style = EStyle.BoldItalic; - - if (currentBeat >= _Notes[note].StartBeat) + if (currentBeat >= _Line.Notes[note].StartBeat) { - if (currentBeat <= _Notes[note].EndBeat) + int curEndBeat = _Line.Notes[note].EndBeat; + if (note < _Line.Notes.Length - 1) + curEndBeat = _Line.Notes[note + 1].StartBeat - 1; + + if (currentBeat <= curEndBeat) { jumpNote = note; jumpx = _Text.X; @@ -481,27 +435,20 @@ namespace VocaluxeLib.Menu _Text.Draw(); } - x += rect.Width; + x += _Text.Rect.W; } if (jumpNote < 0) return; - if (_Notes[jumpNote].Duration == 0) - return; _Text.X = jumpx; - _Text.Text = _Notes[jumpNote].Text; - _Text.Color = _ColorProcessed; - _Text.Style = EStyle.Bold; + _SetText(_Line.Notes[jumpNote]); - if (_Notes[jumpNote].Type == ENoteType.Freestyle) - _Text.Style = EStyle.BoldItalic; - - int diff = _Notes[jumpNote].EndBeat - _Notes[jumpNote].StartBeat; + int diff = _Line.Notes[jumpNote].EndBeat - _Line.Notes[jumpNote].StartBeat; if (diff <= 0) diff = 1; - float p = 1f - (currentBeat - _Notes[jumpNote].StartBeat) / diff; + float p = 1f - (currentBeat - _Line.Notes[jumpNote].StartBeat) / diff; if (Math.Abs(p) < float.Epsilon) _Text.Draw(); diff --git a/VocaluxeLib/Menu/CPlaylist.cs b/VocaluxeLib/Menu/CPlaylist.cs index 82c5f93a..1610fd52 100644 --- a/VocaluxeLib/Menu/CPlaylist.cs +++ b/VocaluxeLib/Menu/CPlaylist.cs @@ -84,7 +84,7 @@ namespace VocaluxeLib.Menu { private class CPlaylistElementContent { - public EGameMode[] Modes; + public IList Modes; public int SongID; public EGameMode Mode; } @@ -971,11 +971,11 @@ namespace VocaluxeLib.Menu if (!mouseEvent.LBH && DragAndDropSongID != -1) { - var gm = EGameMode.TR_GAMEMODE_NORMAL; CSong song = CBase.Songs.GetSongByID(DragAndDropSongID); if (song != null) { + var gm = EGameMode.TR_GAMEMODE_NORMAL; if (song.IsDuet) gm = EGameMode.TR_GAMEMODE_DUET; @@ -1220,7 +1220,7 @@ namespace VocaluxeLib.Menu string t1 = CBase.Language.Translate(_Theme.Text1.Text).Replace("%a", song.Artist).Replace("%t", song.Title); _PlaylistElements[i].Text1.Text = /*(Offset + i + 1) + ") " + */ t1; //TODO: Add text field for the number _PlaylistElements[i].SelectSlide.Clear(); - for (int g = 0; g < pec.Modes.Length; g++) + for (int g = 0; g < pec.Modes.Count; g++) { _PlaylistElements[i].SelectSlide.AddValue(Enum.GetName(typeof(EGameMode), pec.Modes[g])); if (pec.Modes[g] == pec.Mode) diff --git a/VocaluxeLib/Menu/CText.cs b/VocaluxeLib/Menu/CText.cs index 21c07a6f..94c2c01c 100644 --- a/VocaluxeLib/Menu/CText.cs +++ b/VocaluxeLib/Menu/CText.cs @@ -561,7 +561,7 @@ namespace VocaluxeLib.Menu float y = Y; RectangleF bounds = CBase.Fonts.GetTextBounds(this); - if (bounds.Width > MaxWidth && MaxWidth > 0f && bounds.Width > 0f) + if (MaxWidth > 0f && bounds.Width > MaxWidth && bounds.Width > 0f) { float factor = MaxWidth / bounds.Width; float step = h * (1 - factor); diff --git a/VocaluxeLib/Menu/SingNotes/CSingNotes.cs b/VocaluxeLib/Menu/SingNotes/CSingNotes.cs index 0904d121..a3a189a3 100644 --- a/VocaluxeLib/Menu/SingNotes/CSingNotes.cs +++ b/VocaluxeLib/Menu/SingNotes/CSingNotes.cs @@ -319,7 +319,7 @@ namespace VocaluxeLib.Menu.SingNotes int nr = 1; foreach (CSongNote note in line.Notes) { - if (note.NoteType != ENoteType.Freestyle) + if (note.Type != ENoteType.Freestyle) { float width = note.Duration / beats * w; @@ -334,7 +334,7 @@ namespace VocaluxeLib.Menu.SingNotes _DrawNoteBG(rect, color, 1f, _PlayerNotes[n].Timer); _DrawNote(rect, new SColorF(5f, 5f, 5f, 0.7f * _PlayerNotes[n].Alpha), 0.7f); - if (note.NoteType == ENoteType.Golden) + if (note.Type == ENoteType.Golden) { _AddGoldenNote(rect, n, nr); nr++; @@ -386,7 +386,7 @@ namespace VocaluxeLib.Menu.SingNotes _DrawNote(rect, color, f); - if (note.EndBeat >= CBase.Game.GetRecordedBeat() && note.Hit && note.HitNote.NoteType == ENoteType.Golden) + if (note.EndBeat >= CBase.Game.GetRecordedBeat() && note.Hit && note.HitNote.Type == ENoteType.Golden) { var re = new SRectF(rect) {W = (CBase.Game.GetMidRecordedBeat() - note.StartBeat) / beats * w}; _AddFlare(re, n); diff --git a/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs b/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs index c1e6ea5b..7a717f7d 100644 --- a/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs +++ b/VocaluxeLib/Menu/SongMenu/CSongMenuFramework.cs @@ -22,6 +22,7 @@ using System.IO; using System.Xml; using VocaluxeLib.Draw; using VocaluxeLib.PartyModes; +using VocaluxeLib.Songs; namespace VocaluxeLib.Menu.SongMenu { @@ -101,7 +102,7 @@ namespace VocaluxeLib.Menu.SongMenu private readonly Stopwatch _Timer = new Stopwatch(); private readonly Stopwatch _VideoFadeTimer = new Stopwatch(); private readonly List _Streams = new List(); - private int _Actsong = -1; + private int _ActSongId = -1; private int _Actsongstream = -1; protected CTexture _Vidtex; @@ -186,7 +187,7 @@ namespace VocaluxeLib.Menu.SongMenu protected int _ActSong { - get { return _Actsong; } + get { return _ActSongId; } } public SRectF Rect @@ -206,7 +207,7 @@ namespace VocaluxeLib.Menu.SongMenu public virtual int GetActualSelection() { - return _Actsong; + return _ActSongId; } private bool _Selected; @@ -434,7 +435,7 @@ namespace VocaluxeLib.Menu.SongMenu if (!_Initialized) return; - if (_Actsong != _PreviewSelected) + if (_ActSongId != _PreviewSelected) _SelectSong(_PreviewSelected); if (_Streams.Count <= 0 || _Video == -1) @@ -607,7 +608,7 @@ namespace VocaluxeLib.Menu.SongMenu protected void _SelectSong(int nr) { - if (CBase.Songs.IsInCategory() && (CBase.Songs.GetNumSongsVisible() > 0) && (nr >= 0) && ((_Actsong != nr) || (_Streams.Count == 0))) + if (CBase.Songs.IsInCategory() && (CBase.Songs.GetNumSongsVisible() > 0) && (nr >= 0) && ((_ActSongId != nr) || (_Streams.Count == 0))) { _Streams.ForEach(soundStream => CBase.Sound.FadeAndStop(soundStream, 0f, 1f)); _Streams.Clear(); @@ -617,18 +618,19 @@ namespace VocaluxeLib.Menu.SongMenu CBase.Drawing.RemoveTexture(ref _Vidtex); - _Actsong = nr; - if (_Actsong >= CBase.Songs.GetNumSongsVisible()) - _Actsong = 0; + _ActSongId = nr; + if (_ActSongId >= CBase.Songs.GetNumSongsVisible()) + _ActSongId = 0; - int stream = CBase.Sound.Load(Path.Combine(CBase.Songs.GetVisibleSong(_Actsong).Folder, CBase.Songs.GetVisibleSong(_Actsong).MP3FileName), true); + CSong actSong = CBase.Songs.GetVisibleSong(_ActSongId); + int stream = CBase.Sound.Load(Path.Combine(actSong.Folder, actSong.MP3FileName), true); CBase.Sound.SetStreamVolumeMax(stream, _MaxVolume); CBase.Sound.SetStreamVolume(stream, 0f); - float startposition = CBase.Songs.GetVisibleSong(_Actsong).PreviewStart; + float startposition = actSong.Preview.StartTime; - if (Math.Abs(startposition) < 0.001) + if (actSong.Preview.Source == EDataSource.None) startposition = CBase.Sound.GetLength(stream) / 4f; if (startposition >= 0.5f) @@ -640,12 +642,12 @@ namespace VocaluxeLib.Menu.SongMenu _Streams.Add(stream); _Actsongstream = stream; - if (CBase.Songs.GetVisibleSong(_Actsong).VideoFileName != "" && CBase.Config.GetVideoPreview() == EOffOn.TR_CONFIG_ON) + if (actSong.VideoFileName != "" && CBase.Config.GetVideoPreview() == EOffOn.TR_CONFIG_ON) { - _Video = CBase.Video.Load(Path.Combine(CBase.Songs.GetVisibleSong(_Actsong).Folder, CBase.Songs.GetVisibleSong(_Actsong).VideoFileName)); + _Video = CBase.Video.Load(Path.Combine(actSong.Folder, actSong.VideoFileName)); if (_Video == -1) return; - CBase.Video.Skip(_Video, startposition, CBase.Songs.GetVisibleSong(_Actsong).VideoGap); + CBase.Video.Skip(_Video, startposition, actSong.VideoGap); _VideoFadeTimer.Stop(); _VideoFadeTimer.Reset(); _VideoFadeTimer.Start(); diff --git a/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs b/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs index a0ba7fe1..dc18e486 100644 --- a/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs +++ b/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs @@ -413,8 +413,8 @@ namespace VocaluxeLib.Menu.SongMenu _Title.Text = song.Title; _DuetIcon.Visible = song.IsDuet; _VideoIcon.Visible = song.VideoFileName != ""; - _MedleyCalcIcon.Visible = song.Medley.Source == EMedleySource.Calculated; - _MedleyTagIcon.Visible = song.Medley.Source == EMedleySource.Tag; + _MedleyCalcIcon.Visible = song.Medley.Source == EDataSource.Calculated; + _MedleyTagIcon.Visible = song.Medley.Source == EDataSource.Tag; float time = CBase.Sound.GetLength(_SongStream); if (Math.Abs(song.Finish) > 0.001) diff --git a/VocaluxeLib/Songs/CBaseNote.cs b/VocaluxeLib/Songs/CBaseNote.cs index e6fa193d..45043aa7 100644 --- a/VocaluxeLib/Songs/CBaseNote.cs +++ b/VocaluxeLib/Songs/CBaseNote.cs @@ -15,6 +15,8 @@ // along with Vocaluxe. If not, see . #endregion +using System; + namespace VocaluxeLib.Songs { /// @@ -38,12 +40,17 @@ namespace VocaluxeLib.Songs StartBeat = startBeat; Duration = duration; Tone = tone; + if (Duration < 1) + throw new Exception("Note to short. All notes should be at least 1 beat long!"); } #endregion Constructors #region Properties public int StartBeat { get; set; } + /// + /// Last beat of this note. First valid beat for a note after that is at EndBeat+1 + /// public int EndBeat { get { return StartBeat + _Duration - 1; } diff --git a/VocaluxeLib/Songs/CLineBase.cs b/VocaluxeLib/Songs/CLineBase.cs index 0a819509..937f5647 100644 --- a/VocaluxeLib/Songs/CLineBase.cs +++ b/VocaluxeLib/Songs/CLineBase.cs @@ -79,7 +79,11 @@ namespace VocaluxeLib.Songs #endregion Properties #region Methods - //Find last note with StartBeat<=Beat + /// + /// Find last note with StartBeat<=Beat + /// + /// + /// Note index of last note with StartBeat<=Beat or -1 if all notes start after beat public int FindPreviousNote(int beat) { //If no notes -> No previous note diff --git a/VocaluxeLib/Songs/CNotes.cs b/VocaluxeLib/Songs/CNotes.cs index f1ab9cbb..79598dcd 100644 --- a/VocaluxeLib/Songs/CNotes.cs +++ b/VocaluxeLib/Songs/CNotes.cs @@ -21,24 +21,63 @@ namespace VocaluxeLib.Songs { public class CNotes { - private readonly List _Voices = new List(); - - public CVoice[] Voices + public class CVoiceNames { - get { return _Voices.ToArray(); } + private readonly List _Names = new List(); + + public CVoiceNames() {} + + public CVoiceNames(CVoiceNames names) + { + _Names.AddRange(names._Names); + } + + public string this[int index] + { + get + { + if (IsSet(index)) + return _Names[index]; + return "Part " + (index + 1); + } + set + { + _Names.EnsureSize(index + 1, null); + _Names[index] = value; + } + } + + public bool IsSet(int index) + { + return _Names.Count > index && !string.IsNullOrEmpty(_Names[index]); + } + + public void Reset() + { + _Names.Clear(); + } } + private readonly List _Voices = new List(); + public readonly CVoiceNames VoiceNames = new CVoiceNames(); + public CNotes() {} - public int LinesCount - { - get { return _Voices.Count; } - } - public CNotes(CNotes notes) { foreach (CVoice voice in notes._Voices) _Voices.Add(new CVoice(voice)); + VoiceNames = new CVoiceNames(notes.VoiceNames); + } + + public IEnumerable Voices + { + get { return _Voices; } + } + + public int VoiceCount + { + get { return _Voices.Count; } } public CVoice GetVoice(int index) @@ -79,9 +118,11 @@ namespace VocaluxeLib.Songs return true; } - public void Reset() + public void Reset(bool resetVoices = false) { _Voices.Clear(); + if (resetVoices) + VoiceNames.Reset(); } public void SetMedley(int startBeat, int endBeat) diff --git a/VocaluxeLib/Songs/CSong.cs b/VocaluxeLib/Songs/CSong.cs index 35e15d34..aa0ced4b 100644 --- a/VocaluxeLib/Songs/CSong.cs +++ b/VocaluxeLib/Songs/CSong.cs @@ -20,7 +20,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using VocaluxeLib.Draw; namespace VocaluxeLib.Songs @@ -45,12 +44,11 @@ namespace VocaluxeLib.Songs Artist = 2, MP3 = 4, BPM = 8, - PreviewStart = 16, - MedleyStartBeat = 32, - MedleyEndBeat = 64 + MedleyStartBeat = 16, + MedleyEndBeat = 32 } - public enum EMedleySource + public enum EDataSource { None = 0, Calculated, @@ -59,30 +57,43 @@ namespace VocaluxeLib.Songs public struct SMedley { - public EMedleySource Source; + public EDataSource Source; public int StartBeat; public int EndBeat; public float FadeInTime; public float FadeOutTime; } - public class CSong + public struct SShortEnd + { + public EDataSource Source; + public int EndBeat; + } + + public struct SPreview + { + public EDataSource Source; + public float StartTime; + } + + public partial class CSong { private CTexture _CoverTextureSmall; private CTexture _CoverTextureBig; public SMedley Medley; - public bool CalculateMedley = true; - public float PreviewStart; + private bool _CalculateMedley = true; + public SPreview Preview; - public int ShortEnd; + public SShortEnd ShortEnd; public Encoding Encoding = Encoding.Default; + public bool ManualEncoding; public string Folder = String.Empty; public string FolderName = String.Empty; public string FileName = String.Empty; - public EOffOn Relative = EOffOn.TR_CONFIG_OFF; + public bool Relative; public string MP3FileName = String.Empty; public string CoverFileName = String.Empty; @@ -117,17 +128,31 @@ namespace VocaluxeLib.Songs public string TitleSorting = String.Empty; public string ArtistSorting = String.Empty; + public string Creator = ""; + public string Version = ""; + public string Source = ""; + public readonly List UnknownTags = new List(); + + /// + /// Start of the song in s (s in txt) + /// public float Start; + /// + /// End of the song in s (ms in txt) + /// public float Finish; public float BPM = 1f; + /// + /// Gap of the mp3 in s (ms in txt) + /// public float Gap; + /// + /// Gap of the video in s (s in txt) + /// public float VideoGap; - public string DuetPart1 = String.Empty; - public string DuetPart2 = String.Empty; - - private readonly List _Comment = new List(); + private string _Comment = ""; // Sorting public int ID; @@ -136,14 +161,15 @@ namespace VocaluxeLib.Songs private readonly bool _Selected; public bool IsDuet { - get { return Notes.Voices.Length > 1; } + get { return Notes.VoiceCount > 1; } } - public readonly List Edition = new List(); - public readonly List Genre = new List(); + public readonly List Editions = new List(); + public readonly List Genres = new List(); + public string Album = ""; public string Year = ""; - public readonly List Language = new List(); + public readonly List Languages = new List(); public int DataBaseSongID = -1; public string DateAdded = ""; @@ -152,50 +178,47 @@ namespace VocaluxeLib.Songs // Notes public readonly CNotes Notes = new CNotes(); - public EGameMode[] AvailableGameModes + public IList AvailableGameModes { get { var gms = new List {IsDuet ? EGameMode.TR_GAMEMODE_DUET : EGameMode.TR_GAMEMODE_NORMAL}; - if (Medley.Source != EMedleySource.None) + if (Medley.Source != EDataSource.None) gms.Add(EGameMode.TR_GAMEMODE_MEDLEY); - gms.Add(EGameMode.TR_GAMEMODE_SHORTSONG); + if (ShortEnd.Source != EDataSource.None) + gms.Add(EGameMode.TR_GAMEMODE_SHORTSONG); - return gms.ToArray(); + return gms; } } + /// + /// Returns true if the requested game mode is available + /// + /// + /// true if the requested game mode is available + public bool IsGameModeAvailable(EGameMode gameMode) + { + return AvailableGameModes.Any(gm => gm == gameMode); + } + //No point creating a song without a text file --> Use factory method LoadSong private CSong() {} - public static CSong LoadSong(string filePath) - { - var song = new CSong(); - return song.ReadHeader(filePath) ? song : null; - } - public CSong(CSong song) { _CoverTextureSmall = song._CoverTextureSmall; _CoverTextureBig = song._CoverTextureBig; - Medley = new SMedley - { - Source = song.Medley.Source, - StartBeat = song.Medley.StartBeat, - EndBeat = song.Medley.EndBeat, - FadeInTime = song.Medley.FadeInTime, - FadeOutTime = song.Medley.FadeOutTime - }; + Medley = song.Medley; - CalculateMedley = song.CalculateMedley; - PreviewStart = song.PreviewStart; + _CalculateMedley = song._CalculateMedley; + Preview = song.Preview; ShortEnd = song.ShortEnd; - DuetPart1 = song.DuetPart1; - DuetPart2 = song.DuetPart2; Encoding = song.Encoding; + ManualEncoding = song.ManualEncoding; Folder = song.Folder; FolderName = song.FolderName; FileName = song.FileName; @@ -211,6 +234,13 @@ namespace VocaluxeLib.Songs Artist = song.Artist; Title = song.Title; + ArtistSorting = song.ArtistSorting; + TitleSorting = song.TitleSorting; + + Creator = song.Creator; + Version = song.Version; + Source = song.Source; + UnknownTags = new List(song.UnknownTags); Start = song.Start; Finish = song.Finish; @@ -219,28 +249,19 @@ namespace VocaluxeLib.Songs Gap = song.Gap; VideoGap = song.VideoGap; - _Comment = new List(); - foreach (string value in song._Comment) - _Comment.Add(value); + _Comment = song._Comment; ID = song.ID; _Visible = song._Visible; _CatIndex = song._CatIndex; _Selected = song._Selected; - Edition = new List(); - foreach (string value in song.Edition) - Edition.Add(value); - - Genre = new List(); - foreach (string value in song.Genre) - Genre.Add(value); - + Editions = new List(song.Editions); + Genres = new List(song.Genres); + Album = song.Album; Year = song.Year; - Language = new List(); - foreach (string value in song.Language) - Language.Add(value); + Languages = new List(song.Languages); DataBaseSongID = song.DataBaseSongID; DateAdded = song.DateAdded; @@ -249,6 +270,25 @@ namespace VocaluxeLib.Songs Notes = new CNotes(song.Notes); } + public static CSong LoadSong(string filePath) + { + var song = new CSong(); + var loader = new CSongLoader(song); + return loader.InitPaths(filePath) && loader.ReadHeader() ? song : null; + } + + public bool LoadNotes() + { + var loader = new CSongLoader(this); + return loader.ReadNotes(); + } + + public bool Save(string filePath) + { + var writer = new CSongWriter(this); + return writer.SaveFile(filePath); + } + public string GetMP3() { return Path.Combine(Folder, MP3FileName); @@ -259,494 +299,6 @@ namespace VocaluxeLib.Songs return Path.Combine(Folder, VideoFileName); } - public bool ReadHeader(string filePath) - { - if (!File.Exists(filePath)) - return false; - - Folder = Path.GetDirectoryName(filePath); - if (Folder == null) - return false; - - foreach (string folder in CBase.Config.GetSongFolder()) - { - if (!Folder.Contains(folder)) - continue; - if (Folder.Length == folder.Length) - FolderName = "Songs"; - else - { - FolderName = Folder.Substring(folder.Length + 1, Folder.Length - folder.Length - 1); - - int pos = FolderName.IndexOf("\\", StringComparison.Ordinal); - if (pos >= 0) - FolderName = FolderName.Substring(0, pos); - } - break; - } - - FileName = Path.GetFileName(filePath); - - var headerFlags = new EHeaderFlags(); - StreamReader sr = null; - try - { - sr = new StreamReader(filePath, Encoding.Default, true); - - while (!sr.EndOfStream) - { - string line = sr.ReadLine(); - if (line == "") - continue; - if (!line[0].ToString().Equals("#")) - break; - - int pos = line.IndexOf(":", StringComparison.Ordinal); - - if (pos <= 0) - continue; - string identifier = line.Substring(1, pos - 1).Trim().ToUpper(); - string value = line.Substring(pos + 1, line.Length - pos - 1).Trim(); - - if (value != "") - { - switch (identifier) - { - case "ENCODING": - Encoding = CEncoding.GetEncoding(value); - sr.Dispose(); - sr = null; - sr = new StreamReader(filePath, Encoding); - - //Scip everything till encoding - while (!sr.EndOfStream) - { - line = sr.ReadLine(); - if (line == "") - continue; - if (!line[0].ToString().Equals("#")) - break; - - pos = line.IndexOf(":", StringComparison.Ordinal); - - if (pos > 0) - { - identifier = line.Substring(1, pos - 1).Trim().ToUpper(); - if (identifier == "ENCODING") - break; - } - } - break; - case "TITLE": - if (value != "") - { - Title = value; - headerFlags |= EHeaderFlags.Title; - } - break; - case "ARTIST": - if (value != "") - { - Artist = value; - headerFlags |= EHeaderFlags.Artist; - } - break; - case "TITLE-ON-SORTING": - if (value != "") - TitleSorting = value; - break; - case "ARTIST-ON-SORTING": - if (value != "") - ArtistSorting = value; - break; - case "DUETSINGERP1": - case "P1": - if (value != "") - DuetPart1 = value; - break; - case "DUETSINGERP2": - case "P2": - if (value != "") - DuetPart2 = value; - break; - case "MP3": - if (File.Exists(Path.Combine(Folder, value))) - { - MP3FileName = value; - headerFlags |= EHeaderFlags.MP3; - } - else - { - CBase.Log.LogError("Can't find audio file: " + Path.Combine(Folder, value)); - return false; - } - break; - case "BPM": - if (CHelper.TryParse(value, out BPM)) - { - BPM *= 4; - headerFlags |= EHeaderFlags.BPM; - } - break; - case "EDITION": - if (value.Length > 1) - Edition.Add(value); - break; - case "GENRE": - if (value.Length > 1) - Genre.Add(value); - break; - case "YEAR": - int num; - if (value.Length == 4 && int.TryParse(value, out num) && num != 0) - Year = value; - break; - case "LANGUAGE": - if (value.Length > 1) - Language.Add(value); - break; - case "COMMENT": - if (value.Length > 1) - _Comment.Add(value); - break; - case "GAP": - if (CHelper.TryParse(value, out Gap)) - Gap /= 1000f; - break; - case "COVER": - if (File.Exists(Path.Combine(Folder, value))) - CoverFileName = value; - break; - case "BACKGROUND": - if (File.Exists(Path.Combine(Folder, value))) - BackgroundFileName = value; - break; - case "VIDEO": - if (File.Exists(Path.Combine(Folder, value))) - VideoFileName = value; - else - CBase.Log.LogError("Can't find video file: " + Path.Combine(Folder, value)); - break; - case "VIDEOGAP": - CHelper.TryParse(value, out VideoGap); - break; - case "VIDEOASPECT": - CHelper.TryParse(value, out VideoAspect, true); - break; - case "START": - CHelper.TryParse(value, out Start); - break; - case "END": - if (CHelper.TryParse(value, out Finish)) - Finish /= 1000f; - break; - case "PREVIEWSTART": - if (CHelper.TryParse(value, out PreviewStart)) - { - if (PreviewStart < 0f) - PreviewStart = 0f; - else - headerFlags |= EHeaderFlags.PreviewStart; - } - break; - case "MEDLEYSTARTBEAT": - if (int.TryParse(value, out Medley.StartBeat)) - headerFlags |= EHeaderFlags.MedleyStartBeat; - break; - case "MEDLEYENDBEAT": - if (int.TryParse(value, out Medley.EndBeat)) - headerFlags |= EHeaderFlags.MedleyEndBeat; - break; - case "CALCMEDLEY": - if (value.ToUpper() == "OFF") - CalculateMedley = false; - break; - case "RELATIVE": - if (value.ToUpper() == "YES" && value.ToUpper() != "NO") - Relative = EOffOn.TR_CONFIG_ON; - break; - } - } - } //end of while - - if (sr.EndOfStream) - { - //No other data then header - CBase.Log.LogError("Lyrics/Notes missing: " + filePath); - return false; - } - - if ((headerFlags & EHeaderFlags.Title) == 0) - { - CBase.Log.LogError("Title tag missing: " + filePath); - return false; - } - - if ((headerFlags & EHeaderFlags.Artist) == 0) - { - CBase.Log.LogError("Artist tag missing: " + filePath); - return false; - } - - if ((headerFlags & EHeaderFlags.MP3) == 0) - { - CBase.Log.LogError("MP3 tag missing: " + filePath); - return false; - } - - if ((headerFlags & EHeaderFlags.BPM) == 0) - { - CBase.Log.LogError("BPM tag missing: " + filePath); - return false; - } - - #region check medley tags - if ((headerFlags & EHeaderFlags.MedleyStartBeat) != 0 && (headerFlags & EHeaderFlags.MedleyEndBeat) != 0) - { - if (Medley.StartBeat > Medley.EndBeat) - { - CBase.Log.LogError("MedleyStartBeat is bigger than MedleyEndBeat in file: " + filePath); - headerFlags = headerFlags - EHeaderFlags.MedleyStartBeat - EHeaderFlags.MedleyEndBeat; - } - } - - if ((headerFlags & EHeaderFlags.PreviewStart) == 0 || Math.Abs(PreviewStart) < float.Epsilon) - { - //PreviewStart is not set or <=0 - PreviewStart = (headerFlags & EHeaderFlags.MedleyStartBeat) != 0 ? CBase.Game.GetTimeFromBeats(Medley.StartBeat, BPM) : 0f; - } - - if ((headerFlags & EHeaderFlags.MedleyStartBeat) != 0 && (headerFlags & EHeaderFlags.MedleyEndBeat) != 0) - { - Medley.Source = EMedleySource.Tag; - Medley.FadeInTime = CBase.Settings.GetDefaultMedleyFadeInTime(); - Medley.FadeOutTime = CBase.Settings.GetDefaultMedleyFadeOutTime(); - } - #endregion check medley tags - - Encoding = sr.CurrentEncoding; - } - catch (Exception e) - { - if (sr != null) - sr.Dispose(); - CBase.Log.LogError("Error reading txt header in file \"" + filePath + "\": " + e.Message); - return false; - } - sr.Dispose(); - _CheckFiles(); - - CBase.DataBase.GetDataBaseSongInfos(Artist, Title, out NumPlayed, out DateAdded, out DataBaseSongID); - - //Before saving this tags to .txt: Check, if ArtistSorting and Artist are equal, then don't save this tag. - if (ArtistSorting == "") - ArtistSorting = Artist; - - if (TitleSorting == "") - TitleSorting = Title; - - return true; - } - - public bool ReadNotes() - { - return _ReadNotes(Path.Combine(Folder, FileName)); - } - - private bool _ReadNotes(string filePath, bool forceReload = false) - { - //Skip loading if already done and no reload is forced - if (NotesLoaded && !forceReload) - return true; - - if (!File.Exists(filePath)) - { - CBase.Log.LogError("Error loading song. The file does not exist: " + filePath); - return false; - } - - char tempC = char.MinValue; - int currentPos = 0; - bool isNewSentence = false; - - int player = 0; - int fileLineNo = 0; - - StreamReader sr = null; - try - { - sr = new StreamReader(filePath, Encoding, true); - - Notes.Reset(); - - //Search for Note Beginning - while (!sr.EndOfStream) - { - sr.ReadLine(); - fileLineNo++; - - tempC = (char)sr.Peek(); - if ((tempC == '#') || (tempC == '\r') || (tempC == '\n')) - continue; - if ((tempC == ':') || (tempC == 'F') || (tempC == '*') || (tempC == 'P')) - { - tempC = (char)sr.Read(); - break; - } - } - - if (sr.EndOfStream) - { - CBase.Log.LogError("Error loading song. Line No.: " + fileLineNo + ". No lyrics/notes found: " + filePath); - return false; - } - - do - { - int param2; - int param1; - switch (tempC) - { - case 'P': - char chr; - while ((chr = (char)sr.Read()) == ' ') {} - - int.TryParse(chr.ToString(), out param1); - if (param1 == 1) - player = 0; - else if (param1 == 2) - player = 1; - else if (param1 == 3) - player = 2; - else - { - CBase.Log.LogError("Error loading song. Line No.: " + fileLineNo + ". Wrong or missing number after \"P\": " + filePath); - return false; - } - sr.ReadLine(); - break; - case ':': - case '*': - case 'F': - sr.Read(); - param1 = CHelper.TryReadInt(sr); - - sr.Read(); - param2 = CHelper.TryReadInt(sr); - - sr.Read(); - int param3 = CHelper.TryReadInt(sr); - - sr.Read(); - string paramS = sr.ReadLine(); - - ENoteType noteType; - - if (tempC.CompareTo('*') == 0) - noteType = ENoteType.Golden; - else if (tempC.CompareTo('F') == 0) - noteType = ENoteType.Freestyle; - else - noteType = ENoteType.Normal; - - if (Relative == EOffOn.TR_CONFIG_ON) - param1 += currentPos; - - if (player != 2) - // one singer - _ParseNote(player, noteType, param1, param2, param3, paramS); - else - { - // both singer - _ParseNote(0, noteType, param1, param2, param3, paramS); - _ParseNote(1, noteType, param1, param2, param3, paramS); - } - isNewSentence = false; - break; - case '-': - if (isNewSentence) - { - CBase.Log.LogError("Error loading song. Line No.: " + fileLineNo + ". Double sentence break: " + filePath); - return false; - } - sr.Read(); - param1 = CHelper.TryReadInt(sr); - - if (Relative == EOffOn.TR_CONFIG_ON) - { - param1 += currentPos; - sr.Read(); - param2 = CHelper.TryReadInt(sr); - currentPos += param2; - } - - if (player != 2) - // one singer - _NewSentence(player, param1); - else - { - // both singer - _NewSentence(0, param1); - _NewSentence(1, param1); - } - - isNewSentence = true; - sr.ReadLine(); - break; - default: - CBase.Log.LogError("Error loading song. Line No.: " + fileLineNo + ". Unexpected or missing character (" + tempC + "): " + filePath); - return false; - } - int c; - do - { - c = sr.Read(); - } while (!sr.EndOfStream && (c == 19 || c == 16 || c == 13 || c == 10)); - tempC = (char)c; - fileLineNo++; - } while (!sr.EndOfStream && (tempC != 'E')); - - foreach (CVoice voice in Notes.Voices) - voice.UpdateTimings(); - } - catch (Exception e) - { - CBase.Log.LogError("Error loading song. Line No.: " + fileLineNo + ". An unhandled exception occured (" + e.Message + "): " + filePath); - if (sr != null) - sr.Dispose(); - return false; - } - sr.Dispose(); - try - { - _FindRefrain(); - _FindShortEnd(); - NotesLoaded = true; - if (IsDuet) - _CheckDuet(); - } - catch (Exception e) - { - CBase.Log.LogError("Error loading song. An unhandled exception occured (" + e.Message + "): " + filePath); - return false; - } - return true; - } - - private bool _ParseNote(int player, ENoteType noteType, int start, int length, int tone, string text) - { - var note = new CSongNote(start, length, tone, text, noteType); - CVoice voice = Notes.GetVoice(player); - return voice.AddNote(note); - } - - private void _NewSentence(int player, int start) - { - CVoice voice = Notes.GetVoice(player); - var line = new CSongLine {StartBeat = start}; - voice.AddLine(line); - } - public void LoadSmallCover() { if (_CoverTextureSmall != null) @@ -765,11 +317,13 @@ namespace VocaluxeLib.Songs if (CoverFileName == "") { List files = CHelper.ListFiles(Folder, "*.jpg"); + files.AddRange(CHelper.ListFiles(Folder, "*.jpeg")); files.AddRange(CHelper.ListFiles(Folder, "*.png")); + files.AddRange(CHelper.ListFiles(Folder, "*.gif")); foreach (String file in files) { - if (Regex.IsMatch(file, @".[CO].", RegexOptions.IgnoreCase) && - (Regex.IsMatch(file, @"" + Regex.Escape(Title), RegexOptions.IgnoreCase) || Regex.IsMatch(file, @"" + Regex.Escape(Artist), RegexOptions.IgnoreCase))) + if (file.ContainsIgnoreCase("[CO]") && + (file.ContainsIgnoreCase(Title) || file.ContainsIgnoreCase(Artist))) CoverFileName = file; } } @@ -777,11 +331,13 @@ namespace VocaluxeLib.Songs if (BackgroundFileName == "") { List files = CHelper.ListFiles(Folder, "*.jpg"); + files.AddRange(CHelper.ListFiles(Folder, "*.jpeg")); files.AddRange(CHelper.ListFiles(Folder, "*.png")); + files.AddRange(CHelper.ListFiles(Folder, "*.gif")); foreach (String file in files) { - if (Regex.IsMatch(file, @".[BG].", RegexOptions.IgnoreCase) && - (Regex.IsMatch(file, @"" + Regex.Escape(Title), RegexOptions.IgnoreCase) || Regex.IsMatch(file, @"" + Regex.Escape(Artist), RegexOptions.IgnoreCase))) + if (file.ContainsIgnoreCase("[BG]") && + (file.ContainsIgnoreCase(Title) || file.ContainsIgnoreCase(Artist))) BackgroundFileName = file; } } @@ -789,15 +345,10 @@ namespace VocaluxeLib.Songs private void _CheckDuet() { - if (DuetPart1 == "") + for (int i = 0; i < Notes.VoiceCount; i++) { - DuetPart1 = "Part 1"; - CBase.Log.LogError("Warning: Can't find #P1-tag for duets in \"" + Artist + " - " + Title + "\"."); - } - if (DuetPart2 == "") - { - DuetPart2 = "Part 2"; - CBase.Log.LogError("Warning: Can't find #P2-tag for duets in \"" + Artist + " - " + Title + "\"."); + if (!Notes.VoiceNames.IsSet(i)) + CBase.Log.LogError("Warning: Can't find #P" + (i + 1) + "-tag for duets in \"" + Artist + " - " + Title + "\"."); } } @@ -853,14 +404,11 @@ namespace VocaluxeLib.Songs { if (IsDuet) { - Medley.Source = EMedleySource.None; + Medley.Source = EDataSource.None; return; } - if (Medley.Source == EMedleySource.Tag) - return; - - if (!CalculateMedley) + if (!_CalculateMedley) return; List series = _GetSeries(); @@ -883,41 +431,44 @@ namespace VocaluxeLib.Songs Medley.StartBeat = voice.Lines[series[longest].Start].FirstNoteBeat; Medley.EndBeat = voice.Lines[series[longest].End].LastNoteBeat; - bool foundEnd = CBase.Game.GetTimeFromBeats(Medley.StartBeat, BPM) + CBase.Settings.GetMedleyMinDuration() > - CBase.Game.GetTimeFromBeats(Medley.EndBeat, BPM); + bool foundEnd = CBase.Game.GetTimeFromBeats(Medley.EndBeat, BPM) - CBase.Game.GetTimeFromBeats(Medley.StartBeat, BPM) < CBase.Settings.GetMedleyMinDuration(); - // set end if duration > MedleyMinDuration + // set end if duration < MedleyMinDuration if (!foundEnd) { - for (int i = series[longest].Start + 1; i < voice.NumLines - 1; i++) + for (int i = series[longest].End + 1; i < voice.NumLines - 1; i++) { - if (CBase.Game.GetTimeFromBeats(Medley.StartBeat, BPM) + CBase.Settings.GetMedleyMinDuration() > - CBase.Game.GetTimeFromBeats(voice.Lines[i].LastNoteBeat, BPM)) + if (CBase.Game.GetTimeFromBeats(voice.Lines[i].LastNoteBeat, BPM) - CBase.Game.GetTimeFromBeats(Medley.StartBeat, BPM) < + CBase.Settings.GetMedleyMinDuration()) { foundEnd = true; Medley.EndBeat = voice.Lines[i].LastNoteBeat; + break; } } } if (foundEnd) { - Medley.Source = EMedleySource.Calculated; + Medley.Source = EDataSource.Calculated; Medley.FadeInTime = CBase.Settings.GetDefaultMedleyFadeInTime(); Medley.FadeOutTime = CBase.Settings.GetDefaultMedleyFadeOutTime(); } } - if (Math.Abs(PreviewStart) < 0.001) + if (Preview.Source == EDataSource.None) { - if (Medley.Source == EMedleySource.Calculated) - PreviewStart = CBase.Game.GetTimeFromBeats(Medley.StartBeat, BPM); + if (Medley.Source != EDataSource.None) + Preview.StartTime = CBase.Game.GetTimeFromBeats(Medley.StartBeat, BPM); } } private void _FindShortEnd() { + if (ShortEnd.Source != EDataSource.None) + return; + List series = _GetSeries(); if (series == null) return; @@ -934,10 +485,10 @@ namespace VocaluxeLib.Songs { if (stop < (voice.Lines[series[i].Start].FirstNoteBeat + ((voice.Lines[series[i].End].LastNoteBeat - voice.Lines[series[i].Start].FirstNoteBeat) / 2))) { - ShortEnd = voice.Lines[series[i].Start - 1].LastNote.EndBeat; + ShortEnd.EndBeat = voice.Lines[series[i].Start - 1].LastNote.EndBeat; return; } - ShortEnd = voice.Lines[series[i].End].LastNote.EndBeat; + ShortEnd.EndBeat = voice.Lines[series[i].End].LastNote.EndBeat; return; } } @@ -947,12 +498,13 @@ namespace VocaluxeLib.Songs { if (line.FirstNoteBeat < stop && line.LastNoteBeat > stop) { - ShortEnd = line.LastNoteBeat; + ShortEnd.EndBeat = line.LastNoteBeat; return; } } - ShortEnd = stop; + ShortEnd.EndBeat = stop; + ShortEnd.Source = EDataSource.Calculated; } } } \ No newline at end of file diff --git a/VocaluxeLib/Songs/CSongLine.cs b/VocaluxeLib/Songs/CSongLine.cs index 9f678a5c..4dffe501 100644 --- a/VocaluxeLib/Songs/CSongLine.cs +++ b/VocaluxeLib/Songs/CSongLine.cs @@ -22,7 +22,7 @@ namespace VocaluxeLib.Songs { public class CSongLine : CLineBase { - private int _StartBeat = int.MinValue; + private int _StartBeat = int.MaxValue; private int _EndBeat = int.MinValue; #region Constructors @@ -46,7 +46,7 @@ namespace VocaluxeLib.Songs public int StartBeat { - get { return _StartBeat; } + get { return Math.Min(_StartBeat, FirstNoteBeat); } set { if (value <= FirstNoteBeat) @@ -56,7 +56,7 @@ namespace VocaluxeLib.Songs public int EndBeat { - get { return _EndBeat; } + get { return Math.Min(_EndBeat, LastNoteBeat); } set { if (value >= LastNoteBeat) @@ -77,12 +77,19 @@ namespace VocaluxeLib.Songs _Notes.Add(note); else { + //Insert AFTER this note int insPos = FindPreviousNote(note.StartBeat); //Check for overlapping notes - if (insPos >= 0 && _Notes[insPos].EndBeat > note.StartBeat) - return false; - if (insPos < _Notes.Count - 1 && _Notes[insPos + 1].StartBeat > note.EndBeat) - return false; + if (insPos >= 0) //Check note before + { + if (_Notes[insPos].EndBeat >= note.StartBeat) + return false; + } + if (insPos < _Notes.Count - 1) //Check note after + { + if (_Notes[insPos + 1].StartBeat <= note.EndBeat) + return false; + } _Notes.Insert(insPos + 1, note); } return true; @@ -90,11 +97,8 @@ namespace VocaluxeLib.Songs public void SetMedley(int startBeat, int endBeat) { - foreach (CSongNote note in _Notes) - { - if (note.StartBeat < startBeat || note.EndBeat > endBeat) - note.NoteType = ENoteType.Freestyle; - } + foreach (CSongNote note in _Notes.Where(note => note.StartBeat < startBeat || note.EndBeat > endBeat)) + note.Type = ENoteType.Freestyle; VisibleInTimeLine = FirstNoteBeat >= startBeat && LastNoteBeat <= endBeat; } diff --git a/VocaluxeLib/Songs/CSongLoader.cs b/VocaluxeLib/Songs/CSongLoader.cs new file mode 100644 index 00000000..597398d4 --- /dev/null +++ b/VocaluxeLib/Songs/CSongLoader.cs @@ -0,0 +1,740 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; + +namespace VocaluxeLib.Songs +{ + /// + /// Part of CSong that is required for note loading + /// + public partial class CSong + { + /// + /// Factor the bpm given in the txt is multiplied with + /// + private const int _BPMFactor = 4; + + private class CSongLoader + { + private readonly CSong _Song; + private int _LineNr; + + public CSongLoader(CSong song) + { + _Song = song; + } + + public bool InitPaths(string filePath) + { + if (!File.Exists(filePath)) + return false; + + _Song.Folder = Path.GetDirectoryName(filePath); + if (_Song.Folder == null) + return false; + + foreach (string folder in CBase.Config.GetSongFolders().Where(folder => _Song.Folder.StartsWith(folder))) + { + if (_Song.Folder.Length == folder.Length) + _Song.FolderName = "Songs"; + else + { + _Song.FolderName = _Song.Folder.Substring(folder.Length + 1); + + int pos = _Song.FolderName.IndexOf("\\", StringComparison.Ordinal); + if (pos >= 0) + _Song.FolderName = _Song.FolderName.Substring(0, pos); + } + break; + } + + _Song.FileName = Path.GetFileName(filePath); + return true; + } + + /// + /// Logs a given message with file name and line# + /// + /// Message + /// True prepends "Error: "; False prepends "Warning: " + /// Adds line# + private void _LogMsg(string msg, bool error, bool withLineNr) + { + msg = (error ? "Error: " : "Warning: ") + msg; + if (withLineNr) + msg += " in line #" + _LineNr; + CBase.Log.LogError(msg + " (" + Path.Combine(_Song.Folder, _Song.FileName) + ")"); + } + + private void _LogError(string msg, bool withLineNr = true) + { + _LogMsg(msg, true, withLineNr); + } + + private void _LogWarning(string msg, bool withLineNr = true) + { + _LogMsg(msg, false, withLineNr); + } + + public bool ReadHeader(bool useSetEncoding = false) + { + string filePath = Path.Combine(_Song.Folder, _Song.FileName); + + if (!File.Exists(filePath)) + return false; + + _Song.Languages.Clear(); + _Song.Genres.Clear(); + _Song.UnknownTags.Clear(); + _Song._Comment = ""; + _Song.ManualEncoding = false; + _Song.Medley.Source = EDataSource.None; + _Song._CalculateMedley = true; + _Song.Preview.Source = EDataSource.None; + _Song.ShortEnd.Source = EDataSource.None; + + var headerFlags = new EHeaderFlags(); + StreamReader sr = null; + _LineNr = 0; + try + { + sr = new StreamReader(filePath, _Song.Encoding, true); + while (!sr.EndOfStream) + { + string line = sr.ReadLine(); + _LineNr++; + if (line == "") + continue; + if (!line[0].Equals('#')) + break; + + int pos = line.IndexOf(":", StringComparison.Ordinal); + + if (pos <= 1) + { + _Song.UnknownTags.Add(line); + continue; + } + string identifier = line.Substring(1, pos - 1).Trim().ToUpper(); + if (identifier.Contains(" ")) + { + _Song.UnknownTags.Add(line); + continue; + } + string value = line.Substring(pos + 1).Trim(); + + if (value == "") + { + _Song.UnknownTags.Add(line); + _LogWarning("Empty value skipped"); + continue; + } + + switch (identifier) + { + case "ENCODING": + Encoding newEncoding = value.GetEncoding(); + _Song.ManualEncoding = true; + if (!newEncoding.Equals(sr.CurrentEncoding)) + { + if (useSetEncoding) + { + _LogWarning("Duplicate encoding ignored"); + continue; + } + sr.Dispose(); + sr = null; + _Song.Encoding = newEncoding; + return ReadHeader(true); + } + break; + case "TITLE": + _Song.Title = value; + headerFlags |= EHeaderFlags.Title; + break; + case "ARTIST": + _Song.Artist = value; + headerFlags |= EHeaderFlags.Artist; + break; + case "TITLE-ON-SORTING": + _Song.TitleSorting = value; + break; + case "ARTIST-ON-SORTING": + _Song.ArtistSorting = value; + break; + case "CREATOR": + case "AUTHOR": + case "AUTOR": + _Song.Creator = value; + break; + case "VERSION": + _Song.Version = value; + break; + case "SOURCE": + case "YOUTUBE": + _Song.Source = value; + break; + case "MP3": + if (File.Exists(Path.Combine(_Song.Folder, value))) + { + _Song.MP3FileName = value; + headerFlags |= EHeaderFlags.MP3; + } + else + { + _LogError("Can't find audio file: " + Path.Combine(_Song.Folder, value)); + return false; + } + break; + case "BPM": + if (CHelper.TryParse(value, out _Song.BPM)) + { + _Song.BPM *= _BPMFactor; + headerFlags |= EHeaderFlags.BPM; + } + else + _LogWarning("Invalid BPM value"); + break; + case "EDITION": + if (value.Length > 1) + _Song.Editions.Add(value); + else + _LogWarning("Invalid edition"); + break; + case "GENRE": + if (value.Length > 1) + _Song.Genres.Add(value); + else + _LogWarning("Invalid genre"); + break; + case "ALBUM": + _Song.Album = value; + break; + case "YEAR": + int num; + if (value.Length == 4 && int.TryParse(value, out num) && num > 0) + _Song.Year = value; + else + _LogWarning("Invalid year"); + break; + case "LANGUAGE": + if (value.Length > 1) + _Song.Languages.Add(value); + else + _LogWarning("Invalid language"); + break; + case "COMMENT": + if (!String.IsNullOrEmpty(_Song._Comment)) + _Song._Comment += "\r\n"; + _Song._Comment += value; + break; + case "GAP": + if (CHelper.TryParse(value, out _Song.Gap)) + _Song.Gap /= 1000f; + else + _LogWarning("Invalid gap"); + break; + case "COVER": + if (File.Exists(Path.Combine(_Song.Folder, value))) + _Song.CoverFileName = value; + else + _LogWarning("Can't find cover file: " + Path.Combine(_Song.Folder, value)); + break; + case "BACKGROUND": + if (File.Exists(Path.Combine(_Song.Folder, value))) + _Song.BackgroundFileName = value; + else + _LogWarning("Can't find background file: " + Path.Combine(_Song.Folder, value)); + break; + case "VIDEO": + if (File.Exists(Path.Combine(_Song.Folder, value))) + _Song.VideoFileName = value; + else + _LogWarning("Can't find video file: " + Path.Combine(_Song.Folder, value)); + break; + case "VIDEOGAP": + if (!CHelper.TryParse(value, out _Song.VideoGap)) + _LogWarning("Invalid videogap"); + break; + case "VIDEOASPECT": + if (!CHelper.TryParse(value, out _Song.VideoAspect, true)) + _LogWarning("Invalid videoaspect"); + break; + case "START": + if (!CHelper.TryParse(value, out _Song.Start)) + _LogWarning("Invalid start"); + break; + case "END": + if (CHelper.TryParse(value, out _Song.Finish)) + _Song.Finish /= 1000f; + else + _LogWarning("Invalid end"); + break; + case "PREVIEWSTART": + if (CHelper.TryParse(value, out _Song.Preview.StartTime) && _Song.Preview.StartTime >= 0f) + _Song.Preview.Source = EDataSource.Tag; + else + _LogWarning("Invalid previewstart"); + break; + case "PREVIEW": + if (CHelper.TryParse(value, out _Song.Preview.StartTime) && _Song.Preview.StartTime >= 0f) + { + //This is stored in ms not like PREVIEWSTART! + _Song.Preview.StartTime /= 1000f; + _Song.Preview.Source = EDataSource.Tag; + } + else + _LogWarning("Invalid previewstart"); + break; + case "MEDLEYSTARTBEAT": + if (int.TryParse(value, out _Song.Medley.StartBeat)) + headerFlags |= EHeaderFlags.MedleyStartBeat; + else + _LogWarning("Invalid medleystartbeat"); + break; + case "MEDLEYENDBEAT": + if (int.TryParse(value, out _Song.Medley.EndBeat)) + headerFlags |= EHeaderFlags.MedleyEndBeat; + else + _LogWarning("Invalid medleyendbeat"); + break; + case "ENDSHORT": + if ((headerFlags & EHeaderFlags.BPM) != 0) + { + int endTime; + if (int.TryParse(value, out endTime) || endTime < 0) + { + _Song.ShortEnd.EndBeat = (int)CBase.Game.GetBeatFromTime(endTime / 1000f, _Song.BPM, _Song.Gap); + _Song.ShortEnd.Source = EDataSource.Tag; + } + else + _LogWarning("Invalid medleyendbeat"); + } + break; + case "CALCMEDLEY": + if (value.ToUpper() == "OFF") + _Song._CalculateMedley = false; + break; + case "RELATIVE": + if (value.ToUpper() == "YES") + _Song.Relative = true; + break; + case "RESOLUTION": + case "NOTESGAP": + //Outdated/not used + _Song.UnknownTags.Add(line); + break; + default: + if (identifier.StartsWith("DUETSINGER")) + { + identifier = identifier.Substring(10); + if (identifier.StartsWith("P")) // fix for missing "P" + identifier = "P" + identifier; + } + if (identifier.StartsWith("P")) + { + int player; + if (int.TryParse(identifier.Substring(1).Trim(), out player)) + { + foreach (int curPlayer in player.GetSetBits()) + _Song.Notes.VoiceNames[curPlayer] = value; + } + } + else + { + _Song.UnknownTags.Add(line); + _LogWarning("Unknown tag: #" + identifier); + } + + break; + } + } //end of while + + if (sr.EndOfStream) + { + //No other data then header + _LogError("Lyrics/Notes missing", false); + return false; + } + + if ((headerFlags & EHeaderFlags.Title) == 0) + { + _LogError("Title tag missing", false); + return false; + } + + if ((headerFlags & EHeaderFlags.Artist) == 0) + { + _LogError("Artist tag missing", false); + return false; + } + + if ((headerFlags & EHeaderFlags.MP3) == 0) + { + _LogError("MP3 tag missing", false); + return false; + } + + if ((headerFlags & EHeaderFlags.BPM) == 0) + { + _LogError("BPM tag missing", false); + return false; + } + + #region check medley tags + if ((headerFlags & EHeaderFlags.MedleyStartBeat) != 0 && (headerFlags & EHeaderFlags.MedleyEndBeat) != 0) + { + if (_Song.Medley.StartBeat > _Song.Medley.EndBeat) + { + _LogError("MedleyStartBeat is bigger than MedleyEndBeat in file", false); + headerFlags = headerFlags - EHeaderFlags.MedleyStartBeat - EHeaderFlags.MedleyEndBeat; + } + } + + if (_Song.Preview.Source == EDataSource.None) + { + //PreviewStart is not set or <=0 + _Song.Preview.StartTime = (headerFlags & EHeaderFlags.MedleyStartBeat) != 0 ? CBase.Game.GetTimeFromBeats(_Song.Medley.StartBeat, _Song.BPM) : 0f; + } + + if ((headerFlags & EHeaderFlags.MedleyStartBeat) != 0 && (headerFlags & EHeaderFlags.MedleyEndBeat) != 0) + { + _Song.Medley.Source = EDataSource.Tag; + _Song.Medley.FadeInTime = CBase.Settings.GetDefaultMedleyFadeInTime(); + _Song.Medley.FadeOutTime = CBase.Settings.GetDefaultMedleyFadeOutTime(); + } + #endregion check medley tags + } + catch (Exception e) + { + if (sr != null) + sr.Dispose(); + _LogError("Error reading txt header" + e.Message, false); + return false; + } + _Song.Encoding = sr.CurrentEncoding; + sr.Dispose(); + _Song._CheckFiles(); + + CBase.DataBase.GetDataBaseSongInfos(_Song.Artist, _Song.Title, out _Song.NumPlayed, out _Song.DateAdded, out _Song.DataBaseSongID); + + //Before saving this tags to .txt: Check, if ArtistSorting and Artist are equal, then don't save this tag. + if (String.IsNullOrEmpty(_Song.ArtistSorting)) + _Song.ArtistSorting = _Song.Artist; + + if (String.IsNullOrEmpty(_Song.TitleSorting)) + _Song.TitleSorting = _Song.Title; + + return true; + } + + private enum ENoteReadMode + { + Normal, + ZeroBased, + OneBased + } + + private class CAutoChanges + { + public int ZeroLengthNoteCt; + public int OverlapNoteCt; + public int NoTextNoteCt; + public int NoLengthBreakCt; + public int AjustedBreakCt; + public int InvalidPosBreakCt; + + public bool IsModified + { + get { return ZeroLengthNoteCt + OverlapNoteCt + NoTextNoteCt + NoLengthBreakCt + AjustedBreakCt + InvalidPosBreakCt > 0; } + } + + public override string ToString() + { + string result = ""; + int skippedNotesCt = ZeroLengthNoteCt + OverlapNoteCt + NoTextNoteCt; + if (skippedNotesCt > 0) + result += "Skipped " + skippedNotesCt + " notes (0-Length: " + ZeroLengthNoteCt + ", Overlapping: " + OverlapNoteCt + ", No text: " + NoTextNoteCt + ")\r\n"; + if (InvalidPosBreakCt > 0) + result += "Skipped " + InvalidPosBreakCt + " line breaks (Invalid position)\r\n"; + int adjustedBreakCt = AjustedBreakCt + NoLengthBreakCt; + if (adjustedBreakCt > 0) + result += "Adjusted " + adjustedBreakCt + " line breaks (Overlapping previous note: " + AjustedBreakCt + ", No length: " + NoLengthBreakCt + ")\r\n"; + return result; + } + } + + private ENoteReadMode _CurrentReadMode = ENoteReadMode.Normal; + private const int _MaxZeroNoteCt = 1; + private const int _MaxOverlapNoteCt = 3; + + /// + /// Read notes. First try to read notes normally (assume standard)
+ /// If there are more than _MaxZeroNoteCt with length < 1, try to read notes adding 1 to length
+ /// Then if there are more than _MaxOverlapNoteCt fallback to first version ignoring notes with length < 1 + ///
+ /// + /// + public bool ReadNotes(bool forceReload = false) + { + //Skip loading if already done and no reload is forced + if (_Song.NotesLoaded && !forceReload) + return true; + + string filePath = Path.Combine(_Song.Folder, _Song.FileName); + + if (!File.Exists(filePath)) + { + _LogError("The file does not exist", false); + return false; + } + + int currentBeat = 0; //Used for relative songs + CSongNote lastNote = null; //Holds last parsed note. Get's reset on player change + bool endFound = false; // True if end tag was found + + int player = 1; + _LineNr = 0; + + char[] trimChars = {' ', ':'}; + char[] splitChars = {' '}; + + var changesMade = new CAutoChanges(); + + StreamReader sr = null; + try + { + sr = new StreamReader(filePath, _Song.Encoding, true); + + _Song.Notes.Reset(); + + //Search for Note Beginning + while (!sr.EndOfStream && !endFound) + { + string line = sr.ReadLine(); + _LineNr++; + + if (String.IsNullOrEmpty(line)) + continue; + + char tag = line[0]; + //Remove tag and potential space + line = (line.Length >= 2 && line[1] == ' ') ? line.Substring(2) : line.Substring(1); + + int beat, length; + switch (tag) + { + case '#': + continue; + case 'E': + endFound = true; + break; + case 'P': + line = line.Trim(trimChars); + + if (!int.TryParse(line, out player)) + { + _LogError("Wrong or missing number after \"P\""); + return false; + } + currentBeat = 0; + lastNote = null; + break; + case ':': + case '*': + case 'F': + string[] noteData = line.Split(splitChars, 4); + if (noteData.Length < 4) + { + if (noteData.Length == 3) + { + _LogWarning("Ignored note without text"); + changesMade.NoTextNoteCt++; + continue; + } + _LogError("Invalid note found"); + sr.Dispose(); + return false; + } + int tone; + if (!int.TryParse(noteData[0], out beat) || !int.TryParse(noteData[1], out length) || !int.TryParse(noteData[2], out tone)) + { + _LogError("Invalid note found (non-numeric values)"); + sr.Dispose(); + return false; + } + string text = noteData[3].TrimMultipleWs(); + if (text == "") + { + _LogWarning("Ignored note without text"); + changesMade.NoTextNoteCt++; + continue; + } + if (_CurrentReadMode == ENoteReadMode.ZeroBased) + length++; + if (length < 1) + { + changesMade.ZeroLengthNoteCt++; + if (_CurrentReadMode == ENoteReadMode.Normal && changesMade.ZeroLengthNoteCt > _MaxZeroNoteCt && changesMade.OverlapNoteCt <= _MaxOverlapNoteCt) + { + _LogWarning("Found more than " + _MaxZeroNoteCt + " note with length < 1. Trying alternative read mode."); + _CurrentReadMode = ENoteReadMode.ZeroBased; + sr.Dispose(); + return ReadNotes(true); + } + _LogWarning("Ignored note with length < 1"); + } + else + { + ENoteType noteType; + + if (tag.Equals('*')) + noteType = ENoteType.Golden; + else if (tag.Equals('F')) + noteType = ENoteType.Freestyle; + else + noteType = ENoteType.Normal; + + if (_Song.Relative) + beat += currentBeat; + + bool ignored = false; + foreach (int curPlayer in player.GetSetBits()) + { + //Create the note here as we want independent instances in the lines. Otherwhise we can't modify them later + lastNote = new CSongNote(beat, length, tone, text, noteType); + if (!_AddNote(curPlayer, lastNote)) + { + if (!ignored) + { + ignored = true; + changesMade.OverlapNoteCt++; + if (changesMade.OverlapNoteCt > _MaxOverlapNoteCt && _CurrentReadMode == ENoteReadMode.ZeroBased) + { + _LogWarning("Found more than " + _MaxOverlapNoteCt + " overlapping notes. Using standard mode."); + _CurrentReadMode = ENoteReadMode.OneBased; + sr.Dispose(); + return ReadNotes(true); + } + } + _LogWarning("Ignored note for player " + (curPlayer + 1) + " because it overlaps with other note"); + } + } + } + break; + case '-': + string[] lineBreakData = line.Split(splitChars); + if (lineBreakData.Length < 1) + { + _LogError("Invalid line break found (No beat)"); + sr.Dispose(); + return false; + } + if (!int.TryParse(lineBreakData[0], out beat)) + { + _LogError("Invalid line break found (Non-numeric value)"); + sr.Dispose(); + return false; + } + + if (_Song.Relative) + { + beat += currentBeat; + if (lineBreakData.Length < 2 || !int.TryParse(lineBreakData[1], out length)) + { + _LogWarning("Missing line break length"); + changesMade.NoLengthBreakCt++; + currentBeat = beat; + } + else + currentBeat += length; + } + + if (lastNote != null && beat <= lastNote.EndBeat) + { + _LogWarning("Line break is before previous note end. Adjusted."); + changesMade.AjustedBreakCt++; + if (_Song.Relative) + currentBeat += lastNote.EndBeat - beat + 1; + beat = lastNote.EndBeat + 1; + } + + if (beat < 1) + { + _LogWarning("Ignored line break because position is < 1"); + changesMade.InvalidPosBreakCt++; + } + else + { + foreach (int curPlayer in player.GetSetBits()) + { + if (!_NewSentence(curPlayer, beat)) + _LogWarning("Ignored line break for player " + (curPlayer + 1) + " (Overlapping or duplicate)"); + } + } + break; + default: + _LogError("Unexpected or missing character (" + tag + ")"); + return false; + } + } + + for (int i = 0; i < _Song.Notes.VoiceCount; i++) + { + CVoice voice = _Song.Notes.GetVoice(i); + int emptyLines = voice.RemoveEmptyLines(); + if (emptyLines > 0) + _LogWarning("Removed " + emptyLines + " empty lines from P" + ". This often indicates a problem with the line breaks in the file", false); + voice.UpdateTimings(); + } + } + catch (Exception e) + { + _LogError("An unhandled exception occured (" + e.Message + ")"); + if (sr != null) + sr.Dispose(); + return false; + } + sr.Dispose(); + try + { + _Song._FindRefrain(); + _Song._FindShortEnd(); + _Song.NotesLoaded = true; + if (_Song.IsDuet) + _Song._CheckDuet(); + } + catch (Exception e) + { + _LogError("An unhandled exception occured (" + e.Message + ")", false); + return false; + } + + if (changesMade.IsModified) + { + string msg = "Automatic changes have been made to " + filePath + " Please check result!\r\n" + changesMade; + CBase.Log.LogError("Warning: " + msg); + if (CBase.Config.GetSaveModifiedSongs() == EOffOn.TR_CONFIG_ON) + { + string name = Path.GetFileNameWithoutExtension(_Song.FileName); + _Song.Save(Path.Combine(_Song.Folder, name + ".fix")); + } + } + return true; + } + + private bool _AddNote(int player, CSongNote note) + { + CVoice voice = _Song.Notes.GetVoice(player); + return voice.AddNote(note, false); + } + + private bool _NewSentence(int player, int start) + { + CVoice voice = _Song.Notes.GetVoice(player); + return voice.AddLine(start); + } + } + } +} \ No newline at end of file diff --git a/VocaluxeLib/Songs/CSongNote.cs b/VocaluxeLib/Songs/CSongNote.cs index a300fa31..3079c61e 100644 --- a/VocaluxeLib/Songs/CSongNote.cs +++ b/VocaluxeLib/Songs/CSongNote.cs @@ -25,20 +25,20 @@ namespace VocaluxeLib.Songs #region Contructors public CSongNote(CSongNote note) : base(note) { - NoteType = note.NoteType; + Type = note.Type; Text = note.Text; } public CSongNote(int startBeat, int duration, int tone, string text, ENoteType noteType) : base(startBeat, duration, tone) { - NoteType = noteType; + Type = noteType; Text = text; } #endregion Constructors #region Properties - public ENoteType NoteType { get; set; } + public ENoteType Type { get; set; } public string Text { get; set; } @@ -46,7 +46,7 @@ namespace VocaluxeLib.Songs { get { - switch (NoteType) + switch (Type) { case ENoteType.Normal: return 1; diff --git a/VocaluxeLib/Songs/CSongWriter.cs b/VocaluxeLib/Songs/CSongWriter.cs new file mode 100644 index 00000000..906c358f --- /dev/null +++ b/VocaluxeLib/Songs/CSongWriter.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace VocaluxeLib.Songs +{ + public partial class CSong + { + private class CSongWriter + { + private readonly CSong _Song; + private TextWriter _Tw; + + public CSongWriter(CSong song) + { + _Song = song; + } + + private void _WriteHeaderEntry(string id, string value) + { + if (!String.IsNullOrEmpty(value)) + _Tw.WriteLine("#" + id.ToUpper() + ":" + value); + } + + private void _WriteHeaderEntry(string id, bool value) + { + if (value) + _WriteHeaderEntry(id, "YES"); + } + + private void _WriteHeaderEntry(string id, float value, float def = 0f) + { + if (Math.Abs(value - def) > 0.0001) + _WriteHeaderEntry(id, value.ToInvariantString()); + } + + private void _WriteHeaderEntry(string id, int value, int def = 0) + { + if (value != def) + _WriteHeaderEntry(id, value.ToString()); + } + + private void _WriteHeaderEntrys(string id, ICollection value) + { + if (value == null || value.Count <= 0) + return; + foreach (String val in value) + _WriteHeaderEntry(id, val); + } + + private void _WriteHeader() + { + if (_Song.ManualEncoding) + _WriteHeaderEntry("ENCODING", _Song.Encoding.GetEncodingName()); + _WriteHeaderEntry("CREATOR", _Song.Creator); + _WriteHeaderEntry("VERSION", _Song.Version); + _WriteHeaderEntry("SOURCE", _Song.Source); + if (!String.IsNullOrEmpty(_Song._Comment)) + { + string comment = _Song._Comment.Replace("\r\n", "\n").Replace('\r', '\n'); + char[] splitChar = {'\n'}; + _WriteHeaderEntrys("COMMENT", comment.Split(splitChar)); + } + _WriteHeaderEntry("TITLE", _Song.Title); + _WriteHeaderEntry("ARTIST", _Song.Artist); + if (!_Song.Title.Equals(_Song.TitleSorting)) + _WriteHeaderEntry("TITLE-ON-SORTING", _Song.TitleSorting); + if (!_Song.Artist.Equals(_Song.ArtistSorting)) + _WriteHeaderEntry("ARTIST-ON-SORTING", _Song.ArtistSorting); + _WriteHeaderEntrys("EDITION", _Song.Editions); + _WriteHeaderEntrys("GENRE", _Song.Genres); + _WriteHeaderEntrys("LANGUAGE", _Song.Languages); + _WriteHeaderEntry("ALBUM", _Song.Album); + _WriteHeaderEntry("YEAR", _Song.Year); + _WriteHeaderEntry("MP3", _Song.MP3FileName); + _WriteHeaderEntry("COVER", _Song.CoverFileName); + _WriteHeaderEntry("BACKGROUND", _Song.BackgroundFileName); + _WriteHeaderEntry("VIDEO", _Song.VideoFileName); + _WriteHeaderEntry("VIDEOGAP", _Song.VideoGap); + if (_Song.VideoAspect != EAspect.Crop) + _WriteHeaderEntry("VIDEOASPECT", _Song.VideoAspect.ToString()); + _WriteHeaderEntry("RELATIVE", _Song.Relative); + _WriteHeaderEntry("BPM", _Song.BPM / _BPMFactor); + _WriteHeaderEntry("GAP", (int)(_Song.Gap * 1000f)); + if (_Song.Preview.Source == EDataSource.Tag) + _WriteHeaderEntry("PREVIEWSTART", _Song.Preview.StartTime); + _WriteHeaderEntry("START", _Song.Start); + _WriteHeaderEntry("END", (int)(_Song.Finish * 1000f)); + if (_Song.ShortEnd.Source == EDataSource.Tag) + _WriteHeaderEntry("ENDSHORT", (int)(CBase.Game.GetTimeFromBeats(_Song.ShortEnd.EndBeat, _Song.BPM) + _Song.Gap) * 1000); + if (!_Song._CalculateMedley) + _WriteHeaderEntry("CALCMEDLEY", "OFF"); + if (_Song.Medley.Source == EDataSource.Tag) + { + _WriteHeaderEntry("MEDLEYSTARTBEAT", _Song.Medley.StartBeat); + _WriteHeaderEntry("MEDLEYENDBEAT", _Song.Medley.EndBeat); + } + for (int i = 0; i < _Song.Notes.VoiceCount; i++) + { + if (_Song.Notes.VoiceNames.IsSet(i)) + _WriteHeaderEntry("P" + i, _Song.Notes.VoiceNames[i]); + } + foreach (string addLine in _Song.UnknownTags) + _Tw.WriteLine(addLine); + } + + private void _WriteNotes() + { + for (int i = 0; i < _Song.Notes.VoiceCount; i++) + { + CVoice voice = _Song.Notes.GetVoice(i); + if (_Song.Notes.VoiceCount > 1) + _Tw.WriteLine("P" + Math.Pow(2, i)); + bool firstLine = true; + int currentBeat = 0; + foreach (CSongLine line in voice.Lines) + { + if (!firstLine) + { + string lineTxt = "- " + (line.StartBeat - currentBeat); + if (_Song.Relative) + { + lineTxt += " " + (line.FirstNoteBeat - currentBeat); + currentBeat = line.FirstNoteBeat; + } + _Tw.WriteLine(lineTxt); + } + else + firstLine = false; + foreach (CSongNote note in line.Notes) + { + string tag; + switch (note.Type) + { + case ENoteType.Normal: + tag = ":"; + break; + case ENoteType.Golden: + tag = "*"; + break; + case ENoteType.Freestyle: + tag = "F"; + break; + default: + throw new NotImplementedException("Note type " + note.Type); + } + _Tw.WriteLine(tag + " " + (note.StartBeat - currentBeat) + " " + note.Duration + " " + note.Tone + " " + note.Text); + } + } + } + _Tw.WriteLine("E"); + } + + public bool SaveFile(string filePath) + { + try + { + _Tw = new StreamWriter(filePath, false, _Song.Encoding); + _WriteHeader(); + _WriteNotes(); + } + catch (Exception e) + { + CBase.Log.LogError("Unhandled exception while writing " + filePath + ": " + e); + return false; + } + return true; + } + } + } +} \ No newline at end of file diff --git a/VocaluxeLib/Songs/CSungNote.cs b/VocaluxeLib/Songs/CSungNote.cs index b84bfe05..a624d4c5 100644 --- a/VocaluxeLib/Songs/CSungNote.cs +++ b/VocaluxeLib/Songs/CSungNote.cs @@ -48,7 +48,7 @@ namespace VocaluxeLib.Songs } #region Properties - public double Points; + public new double Points; // for drawing player notes public bool Hit { diff --git a/VocaluxeLib/Songs/CVoice.cs b/VocaluxeLib/Songs/CVoice.cs index cee926c9..61eac5a4 100644 --- a/VocaluxeLib/Songs/CVoice.cs +++ b/VocaluxeLib/Songs/CVoice.cs @@ -15,6 +15,7 @@ // along with Vocaluxe. If not, see . #endregion +using System; using System.Collections.Generic; using System.Linq; @@ -67,7 +68,11 @@ namespace VocaluxeLib.Songs } #region Methods - //Find last line with StartBeat<=Beat + /// + /// Find last line with StartBeat<=Beat + /// + /// + /// public int FindPreviousLine(int beat) { //If no line -> No previous line @@ -92,15 +97,49 @@ namespace VocaluxeLib.Songs return start; } - public void AddLine(CSongLine line) + public bool AddLine(CSongLine line, bool updateTimings = true) { if (_Lines.Count == 0) _Lines.Add(line); else { int insPos = FindPreviousLine(line.StartBeat); + //Check if previous line ends before this one + if (insPos >= 0 && _Lines[insPos].LastNoteBeat > line.StartBeat) + return false; + //Check if next line starts before this one ends + if (insPos + 1 < _Lines.Count && _Lines[insPos + 1].FirstNoteBeat < line.LastNoteBeat) + return false; _Lines.Insert(insPos + 1, line); } + if (updateTimings) + UpdateTimings(); + return true; + } + + public bool AddLine(int startBeat) + { + int insPos = FindPreviousLine(startBeat); + //Check for actual notes (startbeat may be lower than first note) + while (insPos >= 0 && _Lines[insPos].NoteCount > 0 && _Lines[insPos].FirstNoteBeat > startBeat) + insPos--; + CSongLine line = new CSongLine {StartBeat = startBeat}; + if (insPos >= 0) + { + CSongLine prevLine = _Lines[insPos]; + //We already have a line break here + if (prevLine.StartBeat == startBeat && (prevLine.NoteCount==0 || prevLine.FirstNoteBeat == startBeat)) + return false; + //Maybe we have to split the previous line + while (prevLine.NoteCount > 0 && prevLine.LastNote.StartBeat >= startBeat) + { + //throw new Exception("This should not happen on song loading!"); + line.AddNote(prevLine.LastNote); + prevLine.DeleteNote(prevLine.NoteCount - 1); + } + } + _Lines.Insert(insPos + 1, line); + return true; } public bool DeleteLine(int index) @@ -119,15 +158,26 @@ namespace VocaluxeLib.Songs _Lines.Clear(); } - public bool AddNote(CSongNote note, bool updateTimings = false) + public bool AddNote(CSongNote note, bool updateTimings = true) { int lineIndex = FindPreviousLine(note.StartBeat); + if (lineIndex + 1 < _Lines.Count && _Lines[lineIndex + 1].FirstNoteBeat < note.EndBeat) //First note in next line starts before this one ends + return false; if (lineIndex < 0) { //Note is before ALL lines - var line = new CSongLine {StartBeat = note.StartBeat}; - line.AddNote(note); - _Lines.Insert(0, line); + if (_Lines.Count > 0) + { + //Add to first line + if (!_Lines[0].AddNote(note)) + return false; + } + else + { + var line = new CSongLine(); + line.AddNote(note); + _Lines.Add(line); + } } else { @@ -139,24 +189,27 @@ namespace VocaluxeLib.Songs return true; } - public bool InsertNote(CSongNote note, int lineIndex, bool updateTimings = false) + /*Check this first for consistency (next line might be affected) + * public bool InsertNote(CSongNote note, int lineIndex, bool updateTimings = false) { if (_Lines.Count > lineIndex && _Lines[lineIndex].StartBeat <= note.StartBeat) { - _Lines[lineIndex].AddNote(note); + if (!_Lines[lineIndex].AddNote(note)) + return false; if (updateTimings) UpdateTimings(); return true; } return false; - } + }*/ public void UpdateTimings() { CSongNote lastNote; - if (_Lines.Count > 0) - _Lines[0].StartBeat = -10000; + if (_Lines.Count == 0) + return; + _Lines[0].StartBeat = -10000; for (int i = 1; i < _Lines.Count; i++) { @@ -191,11 +244,24 @@ namespace VocaluxeLib.Songs _Lines[i].StartBeat = s; _Lines[i - 1].EndBeat = min; } + else if (firstNote != null) + { + _Lines[i - 1].EndBeat = Math.Min(_Lines[i - 1].StartBeat, firstNote.StartBeat - 2); + _Lines[i].StartBeat = Math.Max(_Lines[i - 1].EndBeat, firstNote.StartBeat - 2); + } + else if (lastNote != null) + { + _Lines[i - 1].EndBeat = lastNote.EndBeat; + _Lines[i].StartBeat = Math.Max(lastNote.EndBeat, _Lines[i].StartBeat); //Prefer current setting + } + else + { + //No note + _Lines[i - 1].EndBeat = _Lines[i - 1].StartBeat; //Assume at least startbeats were set right + } } - lastNote = _Lines[_Lines.Count - 1].LastNote; - if (lastNote != null) - _Lines[_Lines.Count - 1].EndBeat = lastNote.EndBeat; + _Lines[_Lines.Count - 1].EndBeat = Math.Max(_Lines[_Lines.Count - 1].LastNoteBeat, _Lines[_Lines.Count - 1].StartBeat); //Use note or (when not set) start beat } public void SetMedley(int startBeat, int endBeat) @@ -204,5 +270,10 @@ namespace VocaluxeLib.Songs line.SetMedley(startBeat, endBeat); } #endregion Methods + + public int RemoveEmptyLines() + { + return _Lines.RemoveAll(line => line.NoteCount == 0); + } } } \ No newline at end of file diff --git a/VocaluxeLib/VocaluxeLib.csproj b/VocaluxeLib/VocaluxeLib.csproj index 5b895271..8a784a53 100644 --- a/VocaluxeLib/VocaluxeLib.csproj +++ b/VocaluxeLib/VocaluxeLib.csproj @@ -130,6 +130,7 @@ + @@ -174,9 +175,11 @@ + + @@ -195,4 +198,4 @@ --> - + \ No newline at end of file