From ebac563f0f8f4fba120cee79e8e6a7973e394677 Mon Sep 17 00:00:00 2001 From: basisbit Date: Fri, 28 Aug 2015 01:45:23 +0000 Subject: * first usable implementation of the JukeBox mode, work in progress - you can start it by pressing "j" in the main menu git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@3128 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/base/UDraw.pas | 175 ++++ src/base/UGraphic.pas | 10 + src/base/UNote.pas | 30 + src/base/UThemes.pas | 80 ++ src/menu/UDisplay.pas | 7 +- src/screens/UScreenJukebox.pas | 1809 ++++++++++++++++++++++++++++++++++++++++ src/screens/UScreenMain.pas | 23 +- src/ultrastardx.dpr | 1 + 8 files changed, 2132 insertions(+), 3 deletions(-) create mode 100644 src/screens/UScreenJukebox.pas diff --git a/src/base/UDraw.pas b/src/base/UDraw.pas index 72e95f4b..6fe19ea8 100644 --- a/src/base/UDraw.pas +++ b/src/base/UDraw.pas @@ -53,6 +53,11 @@ procedure SingDrawTimeBar(); //Draw Editor NoteLines procedure EditDrawLine(Left, Top, Right: real; NrLines: integer; Space: integer); +// Draw Jukebox +procedure SingDrawJukebox; +procedure SingDrawJukeboxBackground; +procedure SingDrawJukeboxTimeBar(); + type TRecR = record Top: real; @@ -93,6 +98,7 @@ uses UMusic, URecord, UScreenSing, + UScreenJukebox, USong, UTexture; @@ -171,6 +177,92 @@ begin end; end; +procedure SingDrawJukeboxBackground; +var + Rec: TRecR; + TexRec: TRecR; +begin + if (ScreenJukebox.Tex_Background.TexNum > 0) then + begin + if (Ini.MovieSize <= 1) then //HalfSize BG + begin + (* half screen + gradient *) + Rec.Top := 110; // 80 + Rec.Bottom := Rec.Top + 20; + Rec.Left := 0; + Rec.Right := 800; + + TexRec.Top := (Rec.Top / 600) * ScreenJukebox.Tex_Background.TexH; + TexRec.Bottom := (Rec.Bottom / 600) * ScreenJukebox.Tex_Background.TexH; + TexRec.Left := 0; + TexRec.Right := ScreenJukebox.Tex_Background.TexW; + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ScreenJukebox.Tex_Background.TexNum); + glEnable(GL_BLEND); + glBegin(GL_QUADS); + (* gradient draw *) + (* top *) + glColor4f(1, 1, 1, 0); + glTexCoord2f(TexRec.Right, TexRec.Top); glVertex2f(Rec.Right, Rec.Top); + glTexCoord2f(TexRec.Left, TexRec.Top); glVertex2f(Rec.Left, Rec.Top); + glColor4f(1, 1, 1, 1); + glTexCoord2f(TexRec.Left, TexRec.Bottom); glVertex2f(Rec.Left, Rec.Bottom); + glTexCoord2f(TexRec.Right, TexRec.Bottom); glVertex2f(Rec.Right, Rec.Bottom); + (* mid *) + Rec.Top := Rec.Bottom; + Rec.Bottom := 490 - 20; // 490 - 20 + TexRec.Top := TexRec.Bottom; + TexRec.Bottom := (Rec.Bottom / 600) * ScreenJukebox.Tex_Background.TexH; + glTexCoord2f(TexRec.Left, TexRec.Top); glVertex2f(Rec.Left, Rec.Top); + glTexCoord2f(TexRec.Left, TexRec.Bottom); glVertex2f(Rec.Left, Rec.Bottom); + glTexCoord2f(TexRec.Right, TexRec.Bottom); glVertex2f(Rec.Right, Rec.Bottom); + glTexCoord2f(TexRec.Right, TexRec.Top); glVertex2f(Rec.Right, Rec.Top); + (* bottom *) + Rec.Top := Rec.Bottom; + Rec.Bottom := 490; // 490 + TexRec.Top := TexRec.Bottom; + TexRec.Bottom := (Rec.Bottom / 600) * ScreenJukebox.Tex_Background.TexH; + glTexCoord2f(TexRec.Right, TexRec.Top); glVertex2f(Rec.Right, Rec.Top); + glTexCoord2f(TexRec.Left, TexRec.Top); glVertex2f(Rec.Left, Rec.Top); + glColor4f(1, 1, 1, 0); + glTexCoord2f(TexRec.Left, TexRec.Bottom); glVertex2f(Rec.Left, Rec.Bottom); + glTexCoord2f(TexRec.Right, TexRec.Bottom); glVertex2f(Rec.Right, Rec.Bottom); + + glEnd; + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + end + else //Full Size BG + begin + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ScreenJukebox.Tex_Background.TexNum); + //glEnable(GL_BLEND); + glBegin(GL_QUADS); + + glTexCoord2f(0, 0); glVertex2f(0, 0); + glTexCoord2f(0, ScreenJukebox.Tex_Background.TexH); glVertex2f(0, 600); + glTexCoord2f( ScreenJukebox.Tex_Background.TexW, ScreenJukebox.Tex_Background.TexH); glVertex2f(800, 600); + glTexCoord2f( ScreenJukebox.Tex_Background.TexW, 0); glVertex2f(800, 0); + + glEnd; + glDisable(GL_TEXTURE_2D); + //glDisable(GL_BLEND); + end; + end + else + begin + // black screen + glColor4f(0, 0, 0, 1); + glbegin(gl_quads); + glVertex2f(0, 0); + glVertex2f(0, 600); + glVertex2f(800, 600); + glVertex2f(800, 0); + glEnd; + end; +end; + procedure SingDrawOscilloscope(X, Y, W, H: real; NrSound: integer); var SampleIndex: integer; @@ -901,6 +993,39 @@ begin glDisable(GL_TEXTURE_2D); end; +procedure SingDrawJukebox; +var + NR: TRecR; // lyrics area bounds (NR = NoteRec?) + LyricEngine: TLyricEngine; +begin + // positions + if Ini.SingWindow = 0 then + NR.Left := 120 + else + NR.Left := 20; + + NR.Right := 780; + + NR.Width := NR.Right - NR.Left; + NR.WMid := NR.Width / 2; + NR.Mid := NR.Left + NR.WMid; + + // FIXME: accessing ScreenJukebox is not that generic + LyricEngine := ScreenJukebox.Lyrics; + + // draw time-bar + if (ScreenAct = 1) and (ScreenJukebox.SongListVisible) then + SingDrawJukeboxTimeBar(); + + // draw Lyrics + if (ScreenJukebox.ShowLyrics) then + LyricEngine.Draw(LyricsState.MidBeat); + //SingDrawLyricHelper(NR.Left, NR.WMid); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +end; + {//SingBar Mod procedure SingDrawSingbar(X, Y, W, H: real; Percent: integer); var @@ -1171,5 +1296,55 @@ begin glcolor4f(1, 1, 1, 1); end; +procedure SingDrawJukeboxTimeBar(); +var + x, y: real; + width, height: real; + LyricsProgress: real; + CurLyricsTime: real; +begin + x := Theme.Jukebox.StaticTimeProgress.x; + y := Theme.Jukebox.StaticTimeProgress.y; + + width := Theme.Jukebox.StaticTimeProgress.w; + height := Theme.Jukebox.StaticTimeProgress.h; + + glColor4f(Theme.Jukebox.StaticTimeProgress.ColR, + Theme.Jukebox.StaticTimeProgress.ColG, + Theme.Jukebox.StaticTimeProgress.ColB, 1); //Set Color + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBindTexture(GL_TEXTURE_2D, Tex_JukeboxTimeProgress.TexNum); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); + glVertex2f(x, y); + + CurLyricsTime := LyricsState.GetCurrentTime(); + if (CurLyricsTime > 0) and + (LyricsState.TotalTime > 0) then + begin + LyricsProgress := CurLyricsTime / LyricsState.TotalTime; + // avoid that the bar "overflows" for inaccurate song lengths + if (LyricsProgress > 1.0) then + LyricsProgress := 1.0; + glTexCoord2f((width * LyricsProgress) / 8, 0); + glVertex2f(x + width * LyricsProgress, y); + + glTexCoord2f((width * LyricsProgress) / 8, 1); + glVertex2f(x + width * LyricsProgress, y + height); + end; + + glTexCoord2f(0, 1); + glVertex2f(x, y + height); + glEnd; + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + glcolor4f(1, 1, 1, 1); + +end; + end. diff --git a/src/base/UGraphic.pas b/src/base/UGraphic.pas index 4f0c8c77..2c80b542 100644 --- a/src/base/UGraphic.pas +++ b/src/base/UGraphic.pas @@ -59,6 +59,7 @@ uses UScreenSong, UScreenSing, UScreenScore, + UScreenJukeBox, UScreenTop5, UScreenEditSub, UScreenEdit, @@ -111,6 +112,7 @@ var ScreenLevel: TScreenLevel; ScreenSong: TScreenSong; ScreenSing: TScreenSing; + ScreenJukebox: TScreenJukebox; ScreenScore: TScreenScore; ScreenTop5: TScreenTop5; ScreenOptions: TScreenOptions; @@ -173,6 +175,7 @@ var FullScreen: boolean; Tex_TimeProgress: TTexture; + Tex_JukeboxTimeProgress: TTexture; //Sing Bar Mod Tex_SingBar_Back: TTexture; @@ -372,6 +375,10 @@ begin //TimeBar mod Tex_TimeProgress := Texture.LoadTexture(Skin.GetTextureFileName('TimeBar')); //eoa TimeBar mod + + //JukeBox mod + Tex_JukeboxTimeProgress := Texture.LoadTexture(Skin.GetTextureFileName('JukeboxTimeBar')); + //end Jukebox Mod //SingBar Mod Tex_SingBar_Back := Texture.LoadTexture(Skin.GetTextureFileName('SingBarBack'), TEXTURE_TYPE_PLAIN, 0); @@ -741,6 +748,8 @@ begin Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Song', 3); Log.BenchmarkStart(3); ScreenSongMenu := TScreenSongMenu.Create; Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Song Menu', 3); Log.BenchmarkStart(3); + ScreenJukebox := TScreenJukebox.Create; + Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Jukebox', 3); Log.BenchmarkStart(3); ScreenSing := TScreenSing.Create; Log.BenchmarkEnd(3); Log.LogBenchmark('====> Screen Sing', 3); Log.BenchmarkStart(3); ScreenScore := TScreenScore.Create; @@ -819,6 +828,7 @@ begin ScreenLevel.Free; ScreenSong.Free; ScreenSing.Free; + ScreenJukebox.Free; ScreenScore.Free; ScreenTop5.Free; ScreenOptions.Free; diff --git a/src/base/UNote.pas b/src/base/UNote.pas index c82cc2e3..dc2c6922 100644 --- a/src/base/UNote.pas +++ b/src/base/UNote.pas @@ -44,6 +44,7 @@ uses ULyrics, URecord, UScreenSing, + UScreenJukebox, USong, UTime; @@ -135,6 +136,8 @@ procedure NewNote(Screen: TScreenSing); // detect note function GetMidBeat(Time: real): real; function GetTimeFromBeat(Beat: integer; SelfSong: TSong = nil): real; +procedure SingJukebox(Screen: TScreenJukebox); + implementation uses @@ -325,6 +328,33 @@ begin NewBeatDetect(Screen); end; +procedure SingJukebox(Screen: TScreenJukebox); +var + Count: integer; + CountGr: integer; + CP: integer; +begin + LyricsState.UpdateBeats(); + + // sentences routines + for CountGr := 0 to 0 do //High(Lines) + begin; + CP := CountGr; + // old parts + LyricsState.OldLine := Lines[CP].Current; + + // choose current parts + for Count := 0 to Lines[CP].High do + begin + if LyricsState.CurrentBeat >= Lines[CP].Line[Count].Start then + Lines[CP].Current := Count; + end; + end; // for CountGr + + // on sentence change... + Screen.onSentenceChange(Lines[0].Current); +end; + procedure NewSentence(Screen: TScreenSing); var i: integer; diff --git a/src/base/UThemes.pas b/src/base/UThemes.pas index c90c7982..7bf4a4e5 100644 --- a/src/base/UThemes.pas +++ b/src/base/UThemes.pas @@ -107,9 +107,13 @@ type W: integer; Z: real; Color: string; + DColor: string; ColR: real; ColG: real; ColB: real; + DColR: real; + DColG: real; + DColB: real; Font: integer; Size: integer; Align: integer; @@ -370,11 +374,44 @@ type PausePopUp: TThemeStatic; end; + TThemeJukebox = class(TThemeBasic) + StaticTimeProgress: TThemeStatic; + StaticTimeBackground: TThemeStatic; + StaticSongBackground: TThemeStatic; + StaticSongListBackground: TThemeStatic; + TextTimeText: TThemeText; + TextTimeDesc: TThemeText; + TextSongText: TThemeText; + SongDescription: TThemeButton; + FindSong: TThemeButton; + RepeatSongList: TThemeButton; + SongListOrder: TThemeButton; + RandomSongList: TThemeButton; + Lyric: TThemeButton; + TextListText: TThemeText; + TextCountText: TThemeText; + SongCover: TThemeStatic; + + //options desc + StaticOptions: TThemeStatic; + TextOptionsSongPosition: TThemeText; + TextOptionsLyric: TThemeText; + TextOptionsSort: TThemeText; + TextOptionsRandom: TThemeText; + TextOptionsRepeat: TThemeText; + TextOptionsFind: TThemeText; + end; + TThemeLyricBar = record IndicatorYOffset, UpperX, UpperW, UpperY, UpperH, LowerX, LowerW, LowerY, LowerH : integer; end; + TThemeLyricBarJukebox = record + IndicatorYOffset, UpperX, UpperW, UpperY, UpperH, + LowerX, LowerW, LowerY, LowerH : integer; + end; + TThemeScore = class(TThemeBasic) TextArtist: TThemeText; TextTitle: TThemeText; @@ -758,6 +795,8 @@ type Song: TThemeSong; Sing: TThemeSing; LyricBar: TThemeLyricBar; + LyricBarJukebox: TThemeLyricBarJukebox; + Jukebox: TThemeJukebox; Score: TThemeScore; Top5: TThemeTop5; Options: TThemeOptions; @@ -1154,6 +1193,44 @@ begin LyricBar.LowerY := ThemeIni.ReadInteger('SingLyricsLowerBar', 'Y', 0); LyricBar.LowerH := ThemeIni.ReadInteger('SingLyricsLowerBar', 'H', 0); + // Lyric Jukebox + LyricBarJukebox.UpperX := ThemeIni.ReadInteger('JukeboxLyricsUpperBar', 'X', 0); + LyricBarJukebox.UpperW := ThemeIni.ReadInteger('JukeboxLyricsUpperBar', 'W', 0); + LyricBarJukebox.UpperY := ThemeIni.ReadInteger('JukeboxLyricsUpperBar', 'Y', 0); + LyricBarJukebox.UpperH := ThemeIni.ReadInteger('JukeboxLyricsUpperBar', 'H', 0); + LyricBarJukebox.LowerX := ThemeIni.ReadInteger('JukeboxLyricsLowerBar', 'X', 0); + LyricBarJukebox.LowerW := ThemeIni.ReadInteger('JukeboxLyricsLowerBar', 'W', 0); + LyricBarJukebox.LowerY := ThemeIni.ReadInteger('JukeboxLyricsLowerBar', 'Y', 0); + LyricBarJukebox.LowerH := ThemeIni.ReadInteger('JukeboxLyricsLowerBar', 'H', 0); + LyricBarJukebox.IndicatorYOffset := ThemeIni.ReadInteger('JukeboxLyricsUpperBar', 'IndicatorYOffset', 0); + + // Jukebox + ThemeLoadStatic(Jukebox.StaticTimeProgress, 'JukeboxTimeProgress'); + ThemeLoadStatic(Jukebox.StaticTimeBackground, 'JukeboxTimeBackground'); + ThemeLoadStatic(Jukebox.StaticSongBackground, 'JukeboxSongBackground'); + ThemeLoadStatic(Jukebox.StaticSongListBackground, 'JukeboxSongListBackground'); + ThemeLoadText(Jukebox.TextTimeText, 'JukeboxTimeText'); + ThemeLoadText(Jukebox.TextTimeDesc, 'JukeboxTimeDesc'); + ThemeLoadText(Jukebox.TextSongText, 'JukeboxTextSong'); + ThemeLoadButton(Jukebox.SongDescription, 'JukeboxSongDescription'); //TODO: BasisBit fish theme loading and fix theme for JukeBox + ThemeLoadButton(Jukebox.FindSong, 'JukeboxFind'); + ThemeLoadButton(Jukebox.RepeatSongList, 'JukeboxRepeat'); + ThemeLoadButton(Jukebox.SongListOrder, 'JukeboxSort'); + ThemeLoadButton(Jukebox.RandomSongList, 'JukeboxRandom'); + ThemeLoadButton(Jukebox.Lyric, 'JukeboxLyric'); + ThemeLoadText(Jukebox.TextListText, 'JukeboxListText'); + ThemeLoadText(Jukebox.TextCountText, 'JukeboxCountText'); + ThemeLoadStatic(Jukebox.SongCover, 'JukeboxSongCover'); + + // options desc + ThemeLoadStatic(Jukebox.StaticOptions, 'JukeboxStaticOptions'); + ThemeLoadText(Jukebox.TextOptionsSongPosition, 'JukeboxOptionsSongPositionDesc'); + ThemeLoadText(Jukebox.TextOptionsLyric, 'JukeboxOptionsLyricDesc'); + ThemeLoadText(Jukebox.TextOptionsRandom, 'JukeboxOptionsRandomDesc'); + ThemeLoadText(Jukebox.TextOptionsRepeat, 'JukeboxOptionsRepeatDesc'); + ThemeLoadText(Jukebox.TextOptionsFind, 'JukeboxOptionsFindDesc'); + ThemeLoadText(Jukebox.TextOptionsSort, 'JukeboxOptionsSortDesc'); + // Sing ThemeLoadBasic(Sing, 'Sing'); //TimeBar mod @@ -2447,6 +2524,9 @@ begin freeandnil(Sing); Sing := TThemeSing.Create; + + freeandnil(Jukebox); + Jukebox := TThemeJukebox.Create; freeandnil(Score); Score := TThemeScore.Create; diff --git a/src/menu/UDisplay.pas b/src/menu/UDisplay.pas index 9a196a74..3dba08fe 100644 --- a/src/menu/UDisplay.pas +++ b/src/menu/UDisplay.pas @@ -702,10 +702,10 @@ begin glDisable(GL_TEXTURE_2D); glColor4f(1, 1, 1, 0.5); glBegin(GL_QUADS); - glVertex2f(690, 44); + glVertex2f(690, 60); glVertex2f(690, 0); glVertex2f(800, 0); - glVertex2f(800, 44); + glVertex2f(800, 60); glEnd; glDisable(GL_BLEND); @@ -740,6 +740,9 @@ begin SetFontPos(695, 26); glColor4f(1, 0, 0, 1); glPrint (OSD_LastError); + SetFontPos(695, 39); + glColor4f(0.5, 0.5, 0, 1); + glPrint ('Pre-alpha'); glColor4f(1, 1, 1, 1); end; diff --git a/src/screens/UScreenJukebox.pas b/src/screens/UScreenJukebox.pas new file mode 100644 index 00000000..de35bc2a --- /dev/null +++ b/src/screens/UScreenJukebox.pas @@ -0,0 +1,1809 @@ +{* UltraStar Deluxe - Karaoke Game + * + * UltraStar Deluxe is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * $URL: $ + * $Id: $ + *} + +unit UScreenJukebox; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + SysUtils, + SDL, + TextGL, + gl, + UCommon, + UFiles, + UGraphicClasses, + UIni, + ULog, + ULyrics, + UMenu, + UMusic, + UPlaylist, + USingScores, + USongs, + UTexture, + UThemes, + UPath, + UTime, + UHookableEvent, + UVideo; + +type + TSongJukebox = class + public + Id: integer; + + // sorting methods + Genre: UTF8String; + Edition: UTF8String; + Language: UTF8String; + Year: Integer; + + Title: UTF8String; + Artist: UTF8String; + + end; + + THandler = record + changed: boolean; + change_time: real; + end; + + TLyricsSyncSource = class(TSyncSource) + function GetClock(): real; override; + end; + + TMusicSyncSource = class(TSyncSource) + function GetClock(): real; override; + end; + + TTimebarMode = ( + tbmCurrent, // current song position + tbmRemaining, // remaining time + tbmTotal // total time + ); + +type + TScreenJukebox = class(TMenu) + private + // items + JukeboxStaticTimeProgress: integer; + JukeboxStaticTimeBackground: integer; + JukeboxStaticSongBackground: integer; + JukeboxStaticSongListBackground: integer; + SongDescription: array[0..9] of integer; + + SelectColR: real; + SelectColG: real; + SelectColB: real; + + // options desc + JukeboxStaticOptions: integer; + JukeboxTextOptionsSongPosition: integer; + JukeboxTextOptionsLyric: integer; + JukeboxTextOptionsRandom: integer; + JukeboxTextOptionsRepeat: integer; + JukeboxTextOptionsFind: integer; + JukeboxTextOptionsSort: integer; + + JukeboxTextTimeText: integer; + JukeboxTextTimeDesc: integer; + //JukeboxTextSongText: integer; + + SongFinish: boolean; + + tmpLyricsUpperY: real; + tmpLyricsLowerY: real; + //tmp_mouse: integer; + + LyricsStart: boolean; + + JukeboxFindSong: integer; + JukeboxRepeatSongList: integer; + JukeboxSongListOrder: integer; + JukeboxRandomSongList: integer; + JukeboxListText: integer; + JukeboxCountText: integer; + JukeboxLyric: integer; + + Filter: UTF8String; + + FindSongList: boolean; + RepeatSongList: boolean; + RandomMode: boolean; + OrderMode: boolean; + OrderType: integer; + + fShowVisualization: boolean; + fCurrentVideo: IVideo; + fVideoClip: IVideo; + fLyricsSync: TLyricsSyncSource; + fMusicSync: TMusicSyncSource; + fTimebarMode: TTimebarMode; + + protected + eSongLoaded: THookableEvent; //< event is called after lyrics of a song are loaded on OnShow + Paused: boolean; //pause Mod + NumEmptySentences: integer; + public + ShowLyrics: boolean; + CurrentSongList: integer; + LastTick: cardinal; + SongListVisible: boolean; + + ChangePosition: integer; + + JukeboxSongsList: array of integer; + JukeboxVisibleSongs: array of integer; + + ActualInteraction: integer; + ListMin: integer; + CurrentSongID: integer; + + //VideoAspect + VideoAspectText: integer; + VideoAspectStatic: integer; + AspectHandler: THandler; + AspectCorrection: TAspectCorrection; + + StaticPausePopup: integer; + + Tex_Background: TTexture; + FadeOut: boolean; + Lyrics: TLyricEngine; + + StaticCover: integer; + + constructor Create; override; + procedure OnShow; override; + procedure OnShowFinish; override; + procedure OnHide; override; + + function ParseInput(PressedKey: cardinal; CharCode: UCS4Char; + PressedDown: boolean): boolean; override; + function Draw: boolean; override; + procedure DrawBlackBars(); + procedure DrawItems(); + + procedure PlayMusic(ID: integer); + procedure Play; + procedure Finish; + procedure Pause; // toggle pause + + procedure OnSentenceEnd(SentenceIndex: cardinal); // for linebonus + singbar + procedure OnSentenceChange(SentenceIndex: cardinal); // for golden notes + + //procedure DeleteSong(Id: integer); + procedure FilterSongList(Filter: UTF8String); + procedure SongListSort(Order: integer); + procedure Sort(Order: integer); + procedure Reset; + + procedure AddSongToJukeboxList(ID: integer); + function FinishedMusic: boolean; + + procedure RefreshCover; + procedure DrawPlaylist; + end; + +implementation + +uses + Classes, + Math, + UDraw, + UGraphic, + ULanguage, + UNote, + URecord, + USong, + UDisplay, + UParty, + UScreenSong, + UUnicodeUtils; + +procedure TScreenJukebox.DrawBlackBars(); +var + X, X1, Y, Y1, Z, H, W: double; +begin + fCurrentVideo.GetScreenPosition(X, Y, Z); + + // Upper + X1 := 0; + Y1 := 0; + H := Y + 1; + W := 800; + + glColor4f(0, 0, 0, 1); + glbegin(gl_quads); + glVertex2f(X1, Y1); + glVertex2f(X1, Y1 + H); + glVertex2f(X1 + W, Y1 + H); + glVertex2f(X1 + W, Y1); + glEnd; + + // Bottom + X1 := 0; + Y1 := 600; + H := Y + 1; + W := 800; + + glColor4f(0, 0, 0, 1); + glbegin(gl_quads); + glVertex2f(X1, Y1); + glVertex2f(X1, Y1 - H); + glVertex2f(X1 + W, Y1 - H); + glVertex2f(X1 + W, Y1); + glEnd; + + // Left + X1 := 0; + Y1 := 0; + H := 600; + W := X + 1; + + glColor4f(0, 0, 0, 1); + glbegin(gl_quads); + glVertex2f(X1, Y1); + glVertex2f(X1, Y1 + H); + glVertex2f(X1 + W, Y1 + H); + glVertex2f(X1 + W, Y1); + glEnd; + + // Right + X1 := 800; + Y1 := 0; + H := 600; + W := X + 1; + + glColor4f(0, 0, 0, 1); + glbegin(gl_quads); + glVertex2f(X1, Y1); + glVertex2f(X1, Y1 + H); + glVertex2f(X1 - W, Y1 + H); + glVertex2f(X1 - W, Y1); + glEnd; + +end; + +procedure TScreenJukebox.SongListSort(Order: integer); +begin + + case Order of + 1 : begin + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_ARTIST'); + Sort(2); + Sort(1); + end; + 2 : begin + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_TITLE'); + Sort(1); + Sort(2); + end; + 3 : begin + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_EDITION'); + Sort(2); + Sort(1); + Sort(3); + end; + 4 : begin + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_GENRE'); + Sort(2); + Sort(1); + Sort(4); + end; + 5 : begin + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_LANGUAGE'); + Sort(2); + Sort(1); + Sort(5); + end; + end; + +end; + +procedure TScreenJukebox.Sort(Order: integer); +var + I, J, X, Tmp, Comp: integer; + Text: UTF8String; + NotEnd: boolean; +begin + + for I:= 0 to High(JukeboxVisibleSongs) do + begin + J := I; + X := JukeboxVisibleSongs[I]; + NotEnd := true; + while (J > 0) and (NotEnd) do + begin + + case Order of + 1 : Comp := UTF8CompareText(CatSongs.Song[X].Artist, CatSongs.Song[JukeboxVisibleSongs[J - 1]].Artist); + 2 : Comp := UTF8CompareText(CatSongs.Song[X].Title, CatSongs.Song[JukeboxVisibleSongs[J - 1]].Title); + 3 : Comp := UTF8CompareText(CatSongs.Song[X].Edition, CatSongs.Song[JukeboxVisibleSongs[J - 1]].Edition); + 4 : Comp := UTF8CompareText(CatSongs.Song[X].Genre, CatSongs.Song[JukeboxVisibleSongs[J - 1]].Genre); + 5 : Comp := UTF8CompareText(CatSongs.Song[X].Language, CatSongs.Song[JukeboxVisibleSongs[J - 1]].Language); + end; + + if (Comp < 0) then + begin + JukeboxVisibleSongs[J] := JukeboxVisibleSongs[J - 1]; + J := J - 1; + end + else + NotEnd := false; + end; + + JukeboxVisibleSongs[J] := X; + end; +end; + +procedure TScreenJukebox.FilterSongList(Filter: UTF8String); +var + I: integer; + SongD: UTF8String; +begin + + if (Filter <> '') then + begin + SetLength(JukeboxVisibleSongs, 0); + for I := 0 to High(JukeboxSongsList) do + begin + SongD := CatSongs.Song[JukeboxSongsList[I]].Artist + ' - ' + CatSongs.Song[JukeboxSongsList[I]].Title; + + if (UTF8ContainsStr(UTF8UpperCase(SongD), UTF8UpperCase(Filter))) then + begin + SetLength(JukeboxVisibleSongs, Length(JukeboxVisibleSongs) + 1); + JukeboxVisibleSongs[High(JukeboxVisibleSongs)] := JukeboxSongsList[I]; + end; + end; + end + else + begin + SetLength(JukeboxVisibleSongs, 0); + + for I := 0 to High(JukeboxSongsList) do + begin + SetLength(JukeboxVisibleSongs, Length(JukeboxVisibleSongs) + 1); + JukeboxVisibleSongs[High(JukeboxVisibleSongs)] := JukeboxSongsList[I]; + end; + end; + + ActualInteraction := 0; + Interaction := 0; + ListMin := 0; +end; + +{ +procedure TScreenJukebox.DeleteSong(Id: integer); +var + I: integer; + JukeboxSongsListTmp: array of integer; + JukeboxVisibleSongsTmp: array of integer; +begin + + SetLength(JukeboxSongsListTmp, 0); + + for I := 0 to High(JukeboxSongsList) do + begin + if (I <> Id) then + begin + SetLength(JukeboxSongsListTmp, Length(JukeboxSongsListTmp) + 1); + JukeboxSongsListTmp[High(JukeboxSongsListTmp)] := JukeboxSongsList[I]; + end; + end; + + SetLength(JukeboxSongsList, Length(JukeboxSongsListTmp)); + for I := 0 to High(JukeboxSongsListTmp) do + JukeboxSongsList[I] := JukeboxSongsListTmp[I]; + + SetLength(JukeboxVisibleSongsTmp, 0); + for I := 0 to High(JukeboxVisibleSongs) do + begin + if (I <> Id) then + begin + SetLength(JukeboxVisibleSongsTmp, Length(JukeboxVisibleSongsTmp) + 1); + JukeboxVisibleSongsTmp[High(JukeboxVisibleSongsTmp)] := JukeboxVisibleSongs[I]; + end; + end; + + SetLength(JukeboxVisibleSongs, Length(JukeboxVisibleSongsTmp)); + for I := 0 to High(JukeboxVisibleSongsTmp) do + JukeboxVisibleSongs[I] := JukeboxVisibleSongsTmp[I]; + + +// ActualInteraction := 0; +// Interaction := 0; +// ListMin := 0; +end; +} + +procedure TScreenJukebox.Reset; +begin + CurrentSongList := 0; + + Interaction := 0; + ActualInteraction := 0; + ListMin := 0; + //RepeatSongList := false; + RandomMode := false; + OrderMode := true; + FindSongList := false; + Filter := ''; + ShowLyrics := true; + + Button[JukeboxSongListOrder].SetSelect(true); + + case (Ini.Sorting) of + 5: begin + OrderType := 1; + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_ARTIST'); + end; + 6: begin + OrderType := 2; + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_TITLE'); + end; + 0: begin + OrderType := 3; + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_EDITION'); + end; + 1: begin + OrderType := 4; + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_GENRE'); + end; + 2: begin + OrderType := 5; + Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_LANGUAGE'); + end; + else + begin + OrderType := 1; + OrderMode := false; + Button[JukeboxSongListOrder].SetSelect(false); + try + //Button[JukeboxSongListOrder].Text[0].Text := Language.Translate('OPTION_VALUE_ARTIST'); /hackyhack + finally + end; + + end; + end; + + //Button[JukeboxFindSong].Text[0].Text := ''; /hackyhack + + Button[JukeboxLyric].SetSelect(true); + Button[JukeboxRandomSongList].SetSelect(false); + Button[JukeboxRepeatSongList].SetSelect(false); + Button[JukeboxFindSong].SetSelect(false); +end; + +procedure OnEscapeJukebox(Value: boolean; Data: Pointer); +var + tmp: integer; +begin + Display.CheckOK := Value; + if (Value) then + begin + Display.CheckOK := false; + + ScreenJukebox.RepeatSongList := false; + + ScreenJukebox.CurrentSongList := High(ScreenJukebox.JukeboxVisibleSongs); + + ScreenJukebox.Finish; + ScreenJukebox.FadeOut := true; + + AudioPlayback.PlaySound(SoundLib.Back); + + end; +end; + +// method for input parsing. if false is returned, getnextwindow +// should be checked to know the next window to load; + +function TScreenJukebox.ParseInput(PressedKey: Cardinal; CharCode: UCS4Char; + PressedDown: boolean): boolean; +var + SDL_ModState: word; + I, RValueI, RValueE: integer; + tmp: integer; + X, Y, Z: double; +begin + Result := true; + + SDL_ModState := SDL_GetModState and (KMOD_LSHIFT + KMOD_RSHIFT + + KMOD_LCTRL + KMOD_RCTRL + KMOD_LALT + KMOD_RALT); + + if (PressedDown) then + begin // key down + // check normal keys + + if (FindSongList) and (SongListVisible) then + begin + if (IsPrintableChar(CharCode)) then + begin + LastTick := SDL_GetTicks(); + + Button[JukeboxFindSong].Text[0].Text := Button[JukeboxFindSong].Text[0].Text + + UCS4ToUTF8String(CharCode); + + Filter := Button[JukeboxFindSong].Text[0].Text; + FilterSongList(Filter); + Exit; + end; + end + else + begin + case UCS4UpperCase(CharCode) of + Ord('Q'): + begin + // when not ask before exit then finish now + if (Ini.AskbeforeDel <> 1) then + Finish + // else just pause and let the popup make the work + else if not Paused then + Pause; + + Result := false; + Exit; + end; + + // show visualization + Ord('V'): + begin + fShowVisualization := not fShowVisualization; + + if fShowVisualization then + begin + fCurrentVideo := Visualization.Open(PATH_NONE); + fCurrentVideo.play; + end + else + begin + fCurrentVideo := fVideoClip; + end; + Exit; + end; + + // pause + Ord('P'): + begin + Pause; + Exit; + end; + + // toggle time display + Ord('T'): + begin + LastTick := SDL_GetTicks(); + + if (fTimebarMode = High(TTimebarMode)) then + fTimebarMode := Low(TTimebarMode) + else + Inc(fTimebarMode); + Exit; + end; + end; + end; + + // check special keys + case PressedKey of + SDLK_A: + begin + // change aspect ratio + if (SDL_ModState = KMOD_LSHIFT) then + begin + if (AspectCorrection = acoCrop) then + AspectCorrection := acoStretch + else + begin + if (AspectCorrection = acoStretch) then + AspectCorrection := acoLetterBox + else + begin + if (AspectCorrection = acoLetterBox) then + AspectCorrection := acoCrop; + end; + end; + + //fCurrentVideo.ToggleAspectCorrection(); + //AspectHandler.changed := true; + //AspectHandler.change_time := Czas.Teraz; + //Static[VideoAspectStatic].Visible := true; + //case UVideo.fAspectCorrection of + // acoStretch: Text[VideoAspectText].Text := Language.Translate('VIDEO_ASPECT_STRETCH'); + // acoCrop: Text[VideoAspectText].Text := Language.Translate('VIDEO_ASPECT_CROP'); + // acoLetterBox: Text[VideoAspectText].Text := Language.Translate('VIDEO_ASPECT_LETTER_BOX'); + //end; + //DataBase.SetAspect(AktSong.Artist, AktSong.Title, integer(UVideo.fAspectCorrection)); + //Text[VideoAspectText].Visible := true; + + //fCurrentVideo.Draw; + end; + end; + + SDLK_L: + begin + if (SDL_ModState = KMOD_LCTRL) then + begin + LastTick := SDL_GetTicks(); + + ShowLyrics := not ShowLyrics; + Button[JukeboxLyric].SetSelect(ShowLyrics); + Exit; + end; + end; + + SDLK_S: + begin + + if (SongListVisible) and (SDL_ModState = KMOD_LCTRL) then + begin + LastTick := SDL_GetTicks(); + + Button[JukeboxRandomSongList].SetSelect(false); + Button[JukeboxSongListOrder].SetSelect(true); + + if (OrderMode) then + begin + if (OrderType < 5) then + begin + OrderType := OrderType + 1; + end + else + OrderType := 1; + end; + + RandomMode := false; + OrderMode := true; + + SongListSort(OrderType); + + Exit; + end; + + end; + + // repeat songlist + SDLK_X: + begin + if (SDL_ModState = KMOD_LCTRL) then + begin + LastTick := SDL_GetTicks(); + + RepeatSongList := not RepeatSongList; + Button[JukeboxRepeatSongList].SetSelect(RepeatSongList); + Exit; + end; + end; + + SDLK_F: + begin + + if (SongListVisible) and (SDL_ModState = KMOD_LCTRL) then + begin + LastTick := SDL_GetTicks(); + + FindSongList := not FindSongList; + + if (Filter = '') then + begin + if (FindSongList) then + Button[JukeboxFindSong].Text[0].Text := '' + end; + + Button[JukeboxFindSong].SetSelect(FindSongList); + + if not (FindSongList) then + FilterSongList('') + else + FilterSongList(Filter); + + Exit; + end; + end; + + SDLK_R: + begin + + if (SongListVisible) and (SDL_ModState = KMOD_LCTRL) then + begin + LastTick := SDL_GetTicks(); + + Button[JukeboxRandomSongList].SetSelect(true); + Button[JukeboxSongListOrder].SetSelect(false); + + RandomMode := true; + OrderMode := false; + + for I := 0 to High(JukeboxVisibleSongs) * 2 do + begin + RValueI := RandomRange(0, High(JukeboxVisibleSongs) + 1); + RValueE := RandomRange(0, High(JukeboxVisibleSongs) + 1); + + tmp := JukeboxVisibleSongs[RValueI]; + JukeboxVisibleSongs[RValueI] := JukeboxVisibleSongs[RValueE]; + JukeboxVisibleSongs[RValueE] := tmp; + + if (RValueI = CurrentSongList) then + CurrentSongList := RValueE + else + begin + if (RValueE = CurrentSongList) then + CurrentSongList := RValueI; + end; + end; + end; + end; + + SDLK_ESCAPE: + begin + if (SongListVisible) then + SongListVisible := false + else + ScreenPopupCheck.ShowPopup('MSG_END_JUKEBOX', OnEscapeJukebox, nil, false) + end; + + SDLK_BACKSPACE: + begin + + if (FindSongList) and (SongListVisible) then + begin + LastTick := SDL_GetTicks(); + Button[JukeboxFindSong].Text[0].DeleteLastLetter(); + Filter := Button[JukeboxFindSong].Text[0].Text; + FilterSongList(Filter); + end + else + begin + ScreenPopupCheck.ShowPopup('MSG_END_JUKEBOX', OnEscapeJukebox, nil, false) + end; + end; + + SDLK_SPACE: + begin + Pause; + end; + + SDLK_TAB: // change visualization preset + begin + if fShowVisualization then + fCurrentVideo.Position := now; // move to a random position + end; + + SDLK_RETURN: + begin + if (SongListVisible) then + begin + LastTick := SDL_GetTicks(); + CurrentSongList := ActualInteraction - 1; + Finish; + PlayMusic(CurrentSongList); + end; + end; + + SDLK_LEFT: + begin + {if not (SongListVisible) and (CurrentSongList > 0) then + begin + CurrentSongList := CurrentSongList - 2; + PlayMusic(CurrentSongList); + end; + } + if (SDL_ModState = KMOD_LSHIFT) then + begin + fCurrentVideo.GetScreenPosition(X, Y, Z); + if (X < 200) then + begin + fCurrentVideo.SetScreenPosition(X + 2, Y, Z); + fCurrentVideo.SetWidth(fCurrentVideo.GetWidth - 4); + end; + end; + + end; + + SDLK_RIGHT: + begin + { + if not (SongListVisible) and (CurrentSongList < High(JukeboxVisibleSongs)) then + begin + CurrentSongList := CurrentSongList + 1; + PlayMusic(CurrentSongList); + end; + } + + if (SDL_ModState = KMOD_LSHIFT) then + begin + fCurrentVideo.GetScreenPosition(X, Y, Z); + fCurrentVideo.SetScreenPosition(X - 2, Y, Z); + fCurrentVideo.SetWidth(fCurrentVideo.GetWidth + 4); + end; + + end; + + SDLK_DELETE: + begin + if (SongListVisible) then + begin + // DeleteSong(ActualInteraction); + end; + end; + + SDLK_PAGEDOWN: + begin + {if (SongListVisible) and ((ActualInteraction + 10) < High(JukeboxVisibleSongs)) then + begin + + LastTick := SDL_GetTicks(); + + // TODO: BUG AT END + if (ListMin + 10 < High(JukeboxVisibleSongs) - 9 + Interaction) then + begin + ListMin := ListMin + 10; + ActualInteraction := ActualInteraction + 10; + end + else + begin + ListMin := High(JukeboxVisibleSongs) - 9 + Interaction; + ActualInteraction := High(JukeboxVisibleSongs) - 9 + Interaction; + end; + + end;} + end; + + SDLK_PAGEUP: + begin + { + if (SongListVisible) and (ActualInteraction - 9 > 0) then + begin + if (ListMin > 10) then + ListMin := ListMin - 10 + else + ListMin := 0; + + ActualInteraction := ActualInteraction - 10; + + if (ActualInteraction < 10) then + Interaction := ActualInteraction; + + LastTick := SDL_GetTicks(); + end;} + end; + + // up and down could be done at the same time, + // but i don't want to declare variables inside + // functions like this one, called so many times + + SDLK_DOWN: + begin + if (SongListVisible) then + begin + LastTick := SDL_GetTicks(); + + if (SDL_ModState = KMOD_LCTRL) and (ActualInteraction < High(JukeboxVisibleSongs)) then + begin + Button[JukeboxSongListOrder].SetSelect(false); + OrderMode := false; + + tmp := JukeboxVisibleSongs[ActualInteraction]; + JukeboxVisibleSongs[ActualInteraction] := JukeboxVisibleSongs[ActualInteraction + 1]; + JukeboxVisibleSongs[ActualInteraction + 1] := tmp; + + if (ActualInteraction + 1 = CurrentSongList) then + CurrentSongList := CurrentSongList - 1 + else + begin + if (ActualInteraction = CurrentSongList) then + CurrentSongList := ActualInteraction + 1; + end; + end; + + if not(SDL_ModState = KMOD_LSHIFT) and not(SDL_ModState = KMOD_LALT) and (ActualInteraction < High(JukeboxVisibleSongs)) then + begin + ActualInteraction := ActualInteraction + 1; + + if (Interaction = 9) then + ListMin := ListMin + 1 + else + InteractInc; + + end; + end; + + if not(SDL_ModState = KMOD_LALT) and not(SDL_ModState = KMOD_LSHIFT) and (not SongListVisible) then + begin + SongListVisible := true; + LastTick := SDL_GetTicks(); + end; + + if (SDL_ModState = KMOD_LALT) then + begin + Lyrics.UpperLineY := Lyrics.UpperLineY + 2; + Lyrics.LowerLineY := Lyrics.LowerLineY + 2; + ChangePosition := ChangePosition + 2; + end; + + if (SDL_ModState = KMOD_LSHIFT) then + begin + fCurrentVideo.GetScreenPosition(X, Y, Z); + if (Y < 200) then + begin + fCurrentVideo.SetScreenPosition(X, Y + 2, Z); + fCurrentVideo.SetHeight(fCurrentVideo.GetHeight - 4); + end; + end; + + end; + SDLK_UP: + begin + if (SongListVisible) and (ActualInteraction > 0) then + begin + LastTick := SDL_GetTicks(); + + if (SDL_ModState = KMOD_LCTRL) and (ActualInteraction > 0) then + begin + Button[JukeboxSongListOrder].SetSelect(false); + OrderMode := false; + + tmp := JukeboxVisibleSongs[ActualInteraction]; + JukeboxVisibleSongs[ActualInteraction] := JukeboxVisibleSongs[ActualInteraction - 1]; + JukeboxVisibleSongs[ActualInteraction - 1] := tmp; + + if (ActualInteraction - 1 = CurrentSongList) then + CurrentSongList := CurrentSongList + 1 + else + begin + if (ActualInteraction = CurrentSongList) then + CurrentSongList := ActualInteraction - 1; + end; + end; + + if not(SDL_ModState = KMOD_LSHIFT) and not(SDL_ModState = KMOD_LALT) then + begin + ActualInteraction := ActualInteraction - 1; + + if (Interaction = 0) then + ListMin := ListMin - 1 + else + InteractDec; + end; + end; + + if not(SDL_ModState = KMOD_LALT) and not(SDL_ModState = KMOD_LSHIFT) and (not SongListVisible) then + begin + SongListVisible := true; + LastTick := SDL_GetTicks(); + end; + + if not(SDL_ModState = KMOD_LSHIFT) and (SDL_ModState = KMOD_LALT) then + begin + Lyrics.UpperLineY := Lyrics.UpperLineY - 2; + Lyrics.LowerLineY := Lyrics.LowerLineY - 2; + ChangePosition := ChangePosition - 2; + end; + + if (SDL_ModState = KMOD_LSHIFT) then + begin + fCurrentVideo.GetScreenPosition(X, Y, Z); + fCurrentVideo.SetScreenPosition(X, Y - 2, Z); + fCurrentVideo.SetHeight(fCurrentVideo.GetHeight + 4); + end; + + end; + + end; + end; +end; + +// pause mod +procedure TScreenJukebox.Pause; +begin + if (not Paused) then // enable pause + begin + // pause time + Paused := true; + + LyricsState.Pause(); + + // pause music + AudioPlayback.Pause; + + // pause video + if (fCurrentVideo <> nil) then + fCurrentVideo.Pause; + + end + else // disable pause + begin + LyricsState.Start(); + + // play music + AudioPlayback.Play; + + // video + if (fCurrentVideo <> nil) then + fCurrentVideo.Pause; + + Paused := false; + end; +end; +// pause mod end + +constructor TScreenJukebox.Create; +var + I, PosY: integer; +begin + inherited Create; + + SongListVisible := false; + ListMin := 0; + ShowLyrics := false; + + //too dangerous, a mouse button is quickly pressed by accident + RightMbESC := false; + + fShowVisualization := false; + + fCurrentVideo := nil; + + LoadFromTheme(Theme.Jukebox); + + StaticPausePopup := AddStatic(Theme.Sing.PausePopUp); + + // pausepopup is not visibile at the beginning + Statics[StaticPausePopup].Visible := false; + + Lyrics := TLyricEngine.Create( + Theme.LyricBar.UpperX, Theme.LyricBar.UpperY, Theme.LyricBar.UpperW, Theme.LyricBar.UpperH, + Theme.LyricBar.LowerX, Theme.LyricBar.LowerY, Theme.LyricBar.LowerW, Theme.LyricBar.LowerH); + + fLyricsSync := TLyricsSyncSource.Create(); + fMusicSync := TMusicSyncSource.Create(); + + //eSongLoaded := THookableEvent.Create('ScreenSing.SongLoaded'); + + //Jukebox Items + JukeboxStaticTimeProgress := AddStatic(Theme.Jukebox.StaticTimeProgress); + JukeboxStaticTimeBackground := AddStatic(Theme.Jukebox.StaticTimeBackground); + JukeboxStaticSongBackground := AddStatic(Theme.Jukebox.StaticSongBackground); + JukeboxStaticSongListBackground := AddStatic(Theme.Jukebox.StaticSongListBackground); + + JukeboxTextTimeText := AddText(Theme.Jukebox.TextTimeText); + JukeboxTextTimeDesc := AddText(Theme.Jukebox.TextTimeDesc); +// JukeboxTextSongText := AddText(Theme.Jukebox.TextSongText); + + PosY := Theme.Jukebox.SongDescription.Y; + for I := 0 to 9 do + begin + Theme.Jukebox.SongDescription.Y := PosY + Theme.Jukebox.SongDescription.H * I; + SongDescription[I] := AddButton(Theme.Jukebox.SongDescription); + end; + + SelectColR := Theme.Jukebox.SongDescription.ColR; + SelectColG := Theme.Jukebox.SongDescription.ColG; + SelectColB := Theme.Jukebox.SongDescription.ColB; + + JukeboxFindSong := AddButton(Theme.Jukebox.FindSong); + JukeboxRepeatSongList := AddButton(Theme.Jukebox.RepeatSongList); + JukeboxSongListOrder := AddButton(Theme.Jukebox.SongListOrder); + JukeboxRandomSongList := AddButton(Theme.Jukebox.RandomSongList); + JukeboxLyric := AddButton(Theme.Jukebox.Lyric); + + Button[JukeboxFindSong].Selectable := false; + Button[JukeboxRepeatSongList].Selectable := false; + Button[JukeboxSongListOrder].Selectable := false; + Button[JukeboxRandomSongList].Selectable := false; + Button[JukeboxLyric].Selectable := false; + + JukeboxListText := AddText(Theme.Jukebox.TextListText); + JukeboxCountText := AddText(Theme.Jukebox.TextCountText); + + StaticCover := AddStatic(Theme.Jukebox.SongCover); + + JukeboxStaticOptions := AddStatic(Theme.Jukebox.StaticOptions); + JukeboxTextOptionsSongPosition := AddText(Theme.Jukebox.TextOptionsSongPosition); + JukeboxTextOptionsLyric := AddText(Theme.Jukebox.TextOptionsLyric); + JukeboxTextOptionsRandom := AddText(Theme.Jukebox.TextOptionsRandom); + JukeboxTextOptionsRepeat := AddText(Theme.Jukebox.TextOptionsRepeat); + JukeboxTextOptionsFind := AddText(Theme.Jukebox.TextOptionsFind); + JukeboxTextOptionsSort := AddText(Theme.Jukebox.TextOptionsSort); +end; + +procedure TScreenJukebox.OnShow; +var + V1: boolean; + V1TwoP: boolean; // position of score box in two player mode + V1ThreeP: boolean; // position of score box in three player mode + V2R: boolean; + V2M: boolean; + V3R: boolean; + Color: TRGB; +begin + inherited; + + Log.LogStatus('Begin', 'OnShow'); + + {** + * Pause background music + *} + SoundLib.PauseBgMusic; + + FadeOut := false; + + AspectCorrection := acoLetterBox; + + ChangePosition := 0; + + Lyrics.UpperLineX := Theme.LyricBarJukebox.UpperX; + Lyrics.UpperLineY := Theme.LyricBarJukebox.UpperY; + Lyrics.UpperLineW := Theme.LyricBarJukebox.UpperW; + Lyrics.UpperLineH := Theme.LyricBarJukebox.UpperH; + + Lyrics.LowerLineX := Theme.LyricBarJukebox.LowerX; + Lyrics.LowerLineY := Theme.LyricBarJukebox.LowerY; + Lyrics.LowerLineW := Theme.LyricBarJukebox.LowerW; + Lyrics.LowerLineH := Theme.LyricBarJukebox.LowerH; + + tmpLyricsUpperY := Lyrics.UpperLineY; + tmpLyricsLowerY := Lyrics.LowerLineY; + + Lyrics.FontStyle := ftOutline1; + Lyrics.LineColor_en.R := 1; + Lyrics.LineColor_en.G := 1; + Lyrics.LineColor_en.B := 1; + Lyrics.LineColor_en.A := 1; + + Lyrics.LineColor_dis.R := 1; + Lyrics.LineColor_dis.G := 1; + Lyrics.LineColor_dis.B := 1; + Lyrics.LineColor_dis.A := 1; + + Lyrics.LineColor_act.R := 1; + Lyrics.LineColor_act.G := 0.75; + Lyrics.LineColor_act.B := 0; + Lyrics.LineColor_act.A := 1; + + Log.LogStatus('End', 'OnShow'); +end; + +procedure TScreenJukebox.OnShowFinish(); +begin + // hide cursor on singscreen show + Display.SetCursor; + + Reset; + + PlayMusic(0); +end; + +procedure TScreenJukebox.Play(); +var + I: integer; +begin + AudioPlayback.Open(CurrentSong.Path.Append(CurrentSong.Mp3)); + AudioPlayback.SetVolume(1.0); + + //AudioPlayback.Position := CurrentSong.Start; + AudioPlayback.Position := LyricsState.GetCurrentTime(); + + // set time + if (CurrentSong.Finish > 0) then + LyricsState.TotalTime := CurrentSong.Finish / 1000 + else + begin + LyricsState.TotalTime := AudioPlayback.Length; + end; + + LyricsState.UpdateBeats(); + + // synchronize music + if (Ini.SyncTo = Ord(stLyrics)) then + AudioPlayback.SetSyncSource(fLyricsSync) + else + AudioPlayback.SetSyncSource(nil); + + // synchronize lyrics (do not set this before AudioPlayback is initialized) + if (Ini.SyncTo = Ord(stMusic)) then + LyricsState.SetSyncSource(fMusicSync) + else + LyricsState.SetSyncSource(nil); + + // start lyrics + LyricsState.Start(true); + + // start music + AudioPlayback.Play(); + + // start timer + CountSkipTimeSet; + + LastTick := SDL_GetTicks(); + +end; + +procedure TScreenJukebox.OnHide; +begin + // background texture + if (Tex_Background.TexNum > 0) then + begin + glDeleteTextures(1, PGLuint(@Tex_Background.TexNum)); + Tex_Background.TexNum := 0; + end; + + Background.OnFinish; + Display.SetCursor; +end; + +function TScreenJukebox.FinishedMusic: boolean; +begin + Result := AudioPlayback.Finished; +end; + +function TScreenJukebox.Draw: boolean; +var + DisplayTime: real; + DisplayPrefix: string; + DisplayMin: integer; + DisplaySec: integer; + CurLyricsTime: real; + VideoFrameTime: Extended; + Line: TLyricLine; + LastWord: TLyricWord; + CurrentTick: cardinal; + Diff: real; +begin + Background.Draw; + + // draw background picture (if any, and if no visualizations) + // when we don't check for visualizations the visualizations would + // be overdrawn by the picture when {UNDEFINED UseTexture} in UVisualizer + if (not fShowVisualization) then + SingDrawJukeboxBackground; + + // retrieve current lyrics time, we have to store the value to avoid + // that min- and sec-values do not match + CurLyricsTime := LyricsState.GetCurrentTime(); + + // retrieve time for timebar text + case (fTimebarMode) of + tbmRemaining: begin + DisplayTime := LyricsState.TotalTime - CurLyricsTime; + DisplayPrefix := '-'; + end; + tbmTotal: begin + DisplayTime := LyricsState.TotalTime; + DisplayPrefix := '#'; + end; + else begin + DisplayTime := CurLyricsTime; + DisplayPrefix := ''; + end; + end; + + DisplayMin := Round(DisplayTime) div 60; + DisplaySec := Round(DisplayTime) mod 60; + Text[JukeboxTextTimeText].Text := Format('%s%.2d:%.2d', [DisplayPrefix, DisplayMin, DisplaySec]); + + // update and draw movie + if Assigned(fCurrentVideo) then + begin + // Just call this once + // when Screens = 2 + if (ScreenAct = 1) then + begin + if (ShowFinish) then + begin + // everything is setup, determine the current position + VideoFrameTime := CurrentSong.VideoGAP + LyricsState.GetCurrentTime(); + end + else + begin + // Important: do not yet start the triggered timer by a call to + // LyricsState.GetCurrentTime() + VideoFrameTime := CurrentSong.VideoGAP; + end; + fCurrentVideo.GetFrame(VideoFrameTime); + end; + + fCurrentVideo.AspectCorrection := AspectCorrection; + fCurrentVideo.SetScreen(ScreenAct); + fCurrentVideo.Draw; + DrawBlackBars(); + end; + + // check for music finish + //Log.LogError('Check for music finish: ' + BoolToStr(Music.Finished) + ' ' + FloatToStr(LyricsState.CurrentTime*1000) + ' ' + IntToStr(CurrentSong.Finish)); + if ShowFinish then + begin + if (not FinishedMusic) and + ((CurrentSong.Finish = 0) or + (LyricsState.GetCurrentTime() * 1000 <= CurrentSong.Finish)) then + begin + // analyze song if not paused + if (not Paused) then + begin + SingJukebox(Self); + end; + end + else + begin + if (not FadeOut) and (Screens=1) or (ScreenAct=2) then + begin + Finish; + end; + end; + end; + + if (ScreenAct = 1) and (SongListVisible) then + DrawPlaylist; + + //if (ShowLyrics) then + //begin + // if (not(LyricsStart)) then + // begin + // if (CurLyricsTime >= GetTimeFromBeat(Line.Words[0].Start) - 2) then + // begin + SingDrawJukebox; + // LyricsStart := true; + // end; + // end + // else + // SingDrawJukebox; + //end; + + // draw pausepopup + // FIXME: this is a workaround that the static is drawn over the lyrics, lines, scores and effects + // maybe someone could find a better solution + if Paused then + begin + Statics[StaticPausePopup].Visible := true; + Statics[StaticPausePopup].Draw; + Statics[StaticPausePopup].Visible := false; + end; + + Result := true; +end; + +procedure TScreenJukebox.Finish; +begin + AudioInput.CaptureStop; + AudioPlayback.Stop; + AudioPlayback.SetSyncSource(nil); + + Lyrics.UpperLineY := tmpLyricsUpperY; + Lyrics.LowerLineY := tmpLyricsLowerY; + + LyricsState.Stop(); + LyricsState.SetSyncSource(nil); + + // close video files + fVideoClip := nil; + fCurrentVideo := nil; + + SetFontItalic(false); + + if (CurrentSongList = High(JukeboxVisibleSongs)) then + begin + if (RepeatSongList) then + begin + // resyart playlist + CurrentSongList := 0; + CatSongs.Selected := JukeboxVisibleSongs[CurrentSongList]; + PlayMusic(CurrentSongList); + end + else + begin + FadeTo(@ScreenMain); + end; + end + else + begin + CurrentSongList := CurrentSongList + 1; + + CatSongs.Selected := JukeboxVisibleSongs[CurrentSongList]; + + PlayMusic(CurrentSongList); + end; +end; + +procedure TScreenJukebox.OnSentenceEnd(SentenceIndex: cardinal); +var + PlayerIndex: byte; + CurrentPlayer: PPLayer; + CurrentScore: real; + Line: PLine; + LinePerfection: real; // perfection of singing performance on the current line + Rating: integer; + LineScore: real; + LineBonus: real; + MaxSongScore: integer; // max. points for the song (without line bonus) + MaxLineScore: real; // max. points for the current line +const + // TODO: move this to a better place + MAX_LINE_RATING = 8; // max. rating for singing performance +begin + Line := @Lines[0].Line[SentenceIndex]; + + // check for empty sentence + if (Line.TotalNotes <= 0) then + Exit; + + // set max song score + if (Ini.LineBonus = 0) then + MaxSongScore := MAX_SONG_SCORE + else + MaxSongScore := MAX_SONG_SCORE - MAX_SONG_LINE_BONUS; + + // Note: ScoreValue is the sum of all note values of the song + MaxLineScore := MaxSongScore * (Line.TotalNotes / Lines[0].ScoreValue); +end; + + // Called on sentence change + // SentenceIndex: index of the new active sentence +procedure TScreenJukebox.OnSentenceChange(SentenceIndex: cardinal); +begin + // fill lyrics queue and set upper line to the current sentence + while (Lyrics.GetUpperLineIndex() < SentenceIndex) or + (not Lyrics.IsQueueFull) do + begin + // add the next line to the queue or a dummy if no more lines are available + if (Lyrics.LineCounter <= High(Lines[0].Line)) then + Lyrics.AddLine(@Lines[0].Line[Lyrics.LineCounter]) + else + Lyrics.AddLine(nil); + end; +end; + +function TLyricsSyncSource.GetClock(): real; +begin + Result := LyricsState.GetCurrentTime(); +end; + +function TMusicSyncSource.GetClock(): real; +begin + Result := AudioPlayback.Position; +end; + +procedure TScreenJukebox.AddSongToJukeboxList(ID: integer); +var + I: integer; + SongExist: boolean; +begin + if (not CatSongs.Song[ID].Main) then + begin + SongExist := false; + for I := 0 to High(JukeboxSongsList) do + begin + if (JukeboxSongsList[I] = ID) then + SongExist := true; + end; + + if (not SongExist) then + begin + SetLength(JukeboxSongsList, Length(JukeboxSongsList) + 1); + JukeboxSongsList[High(JukeboxSongsList)] := ID; + + SetLength(JukeboxVisibleSongs, Length(JukeboxVisibleSongs) + 1); + JukeboxVisibleSongs[High(JukeboxVisibleSongs)] := ID; + end; + end; +end; + +procedure TScreenJukebox.DrawItems(); +begin + + if (SDL_GetTicks() - LastTick <= 3000) then + begin + Statics[JukeboxStaticTimeBackground].Draw; + Statics[JukeboxStaticTimeProgress].Draw; + + // Statics[JukeboxStaticSongBackground].Draw; + + Statics[JukeboxStaticSongListBackground].Draw; + Statics[StaticCover].Draw; + + Text[JukeboxTextTimeText].Draw; + Text[JukeboxTextTimeDesc].Draw; + // Text[JukeboxTextSongText].Draw; + + // options desc + Text[JukeboxTextOptionsSongPosition].Draw; + Text[JukeboxTextOptionsLyric].Draw; + Text[JukeboxTextOptionsRandom].Draw; + Text[JukeboxTextOptionsRepeat].Draw; + Text[JukeboxTextOptionsFind].Draw; + Text[JukeboxTextOptionsSort].Draw; + Statics[JukeboxStaticOptions].Draw; + + end + else + SongListVisible := false; + +end; + +procedure TScreenJukebox.PlayMusic(ID: integer); +var + Index: integer; + VideoFile, BgFile: IPath; + success: boolean; + Max: integer; + CoverPath: IPath; +begin + + // background texture + if (Tex_Background.TexNum > 0) then + begin + glDeleteTextures(1, PGLuint(@Tex_Background.TexNum)); + Tex_Background.TexNum := 0; + end; + + CurrentSong := CatSongs.Song[JukeboxVisibleSongs[ID]]; + + // Cover + RefreshCover; + + // reset video playback engine + fCurrentVideo := nil; + AspectCorrection := acoCrop; + + fTimebarMode := tbmCurrent; + + // FIXME: bad style, put the try-except into loadsong() and not here + try + // check if file is xml + if CurrentSong.FileName.GetExtension.ToUTF8 = '.xml' then + success := CurrentSong.AnalyseXML and CurrentSong.LoadXMLSong() + else + success := CurrentSong.Analyse and CurrentSong.LoadSong(); + except + success := false; + end; + + if (not success) then + begin + // error loading song -> go back to previous screen and show some error message + Display.AbortScreenChange; + // select new song in party mode + if (Length(CurrentSong.LastError) > 0) then + ScreenPopupError.ShowPopup(Format(Language.Translate(CurrentSong.LastError), [CurrentSong.ErrorLineNo])) + else + ScreenPopupError.ShowPopup(Language.Translate('ERROR_CORRUPT_SONG')); + // FIXME: do we need this? + CurrentSong.Path := CatSongs.Song[CatSongs.Selected].Path; + Exit; + end; + + {* + * == Background == + * We have four types of backgrounds: + * + Blank : Nothing has been set, this is our fallback + * + Picture : Picture has been set, and exists - otherwise we fallback + * + Video : Video has been set, and exists - otherwise we fallback + * + Visualization: + Off : No visualization + * + WhenNoVideo: Overwrites blank and picture + * + On : Overwrites blank, picture and video + *} + + {* + * set background to: video + *} + fShowVisualization := false; + VideoFile := CurrentSong.Path.Append(CurrentSong.Video); + if (Ini.VideoEnabled = 1) and CurrentSong.Video.IsSet() and VideoFile.IsFile then + begin + fVideoClip := VideoPlayback.Open(VideoFile); + fCurrentVideo := fVideoClip; + if (fVideoClip <> nil) then + begin + fShowVisualization := false; + fCurrentVideo.Position := CurrentSong.VideoGAP + CurrentSong.Start; + fCurrentVideo.Play; + end; + end; + + {* + * set background to: picture + *} + if (CurrentSong.Background.IsSet) and (fVideoClip = nil) + and (TVisualizerOption(Ini.VisualizerOption) = voOff) then + begin + BgFile := CurrentSong.Path.Append(CurrentSong.Background); + try + Tex_Background := Texture.LoadTexture(BgFile); + except + Log.LogError('Background could not be loaded: ' + BgFile.ToNative); + Tex_Background.TexNum := 0; + end + end + else + begin + Tex_Background.TexNum := 0; + end; + + {* + * set background to: visualization (Overwrites all) + *} + if (TVisualizerOption(Ini.VisualizerOption) in [voOn]) then + begin + fShowVisualization := true; + fCurrentVideo := Visualization.Open(PATH_NONE); + if (fCurrentVideo <> nil) then + fCurrentVideo.Play; + end; + + {* + * set background to: visualization (Videos are still shown) + *} + if ((TVisualizerOption(Ini.VisualizerOption) in [voWhenNoVideo]) and + (fVideoClip = nil)) then + begin + fShowVisualization := true; + fCurrentVideo := Visualization.Open(PATH_NONE); + if (fCurrentVideo <> nil) then + fCurrentVideo.Play; + end; + + // prepare lyrics timer + LyricsState.Reset(); + + LyricsState.SetCurrentTime(CurrentSong.Start); + LyricsState.StartTime := CurrentSong.Gap; + if (CurrentSong.Finish > 0) then + LyricsState.TotalTime := CurrentSong.Finish / 1000 + else + begin + LyricsState.TotalTime := AudioPlayback.Length; + end; + + LyricsState.UpdateBeats(); + + // main text + Lyrics.Clear(CurrentSong.BPM[0].BPM, CurrentSong.Resolution); + + {* + * set background to: picture + *} + if (CurrentSong.Background.IsSet) and (fVideoClip = nil) + and (TVisualizerOption(Ini.VisualizerOption) = voOff) then + begin + BgFile := CurrentSong.Path.Append(CurrentSong.Background); + try + Tex_Background := Texture.LoadTexture(BgFile); + except + Log.LogError('Background could not be loaded: ' + BgFile.ToNative); + Tex_Background.TexNum := 0; + end + end + else + begin + Tex_Background.TexNum := 0; + end; + + // initialize lyrics by filling its queue + while (not Lyrics.IsQueueFull) and + (Lyrics.LineCounter <= High(Lines[0].Line)) do + begin + Lyrics.AddLine(@Lines[0].Line[Lyrics.LineCounter]); + end; + + //Text[JukeboxTextSongText].Visible := true; + //Text[JukeboxTextSongText].Text := CurrentSong.Artist + ' - ' + CurrentSong.Title; + + Max := 9; + + if (High(JukeboxVisibleSongs) < 9) then + Max := High(JukeboxVisibleSongs); + + for Index := 0 to 9 do + Button[SongDescription[Index]].Selectable := false; + + for Index := 0 to Max do + begin + Button[SongDescription[Index]].Visible := true; + Button[SongDescription[Index]].Selectable := true; + end; + + Button[JukeboxFindSong].Visible := true; + Button[JukeboxRepeatSongList].Visible := true; + Button[JukeboxSongListOrder].Visible := true; + Button[JukeboxRandomSongList].Visible := true; + Button[JukeboxLyric].Visible := true; + + CurrentSongID := JukeboxVisibleSongs[CurrentSongList]; + + SongListVisible := true; + + Play(); +end; + +procedure TScreenJukebox.RefreshCover(); +var + CoverPath: IPath; +begin + CoverPath := CurrentSong.Path.Append(CurrentSong.Cover); + Statics[StaticCover].Texture := Texture.GetTexture(CoverPath, TEXTURE_TYPE_PLAIN, false); + Statics[StaticCover].Texture.X := Theme.Jukebox.SongCover.X; + Statics[StaticCover].Texture.Y := Theme.Jukebox.SongCover.Y; + Statics[StaticCover].Texture.W := Theme.Jukebox.SongCover.W; + Statics[StaticCover].Texture.H := Theme.Jukebox.SongCover.H; + Statics[StaticCover].Texture.Alpha := 0.7; +end; + +procedure TScreenJukebox.DrawPlaylist; +var + I, Max: integer; + SongDesc: UTF8String; +begin + DrawItems; + + Max := 9; + if (High(JukeboxVisibleSongs) < 9) then + Max := High(JukeboxVisibleSongs); + + Text[JukeboxCountText].Text := IntToStr(ActualInteraction + 1) + '/' + IntToStr(length(JukeboxVisibleSongs)); + Text[JukeboxListText].Draw; + Text[JukeboxCountText].Draw; + + Button[JukeboxFindSong].Draw; + Button[JukeboxSongListOrder].Draw; + + Button[JukeboxRepeatSongList].Draw; + Button[JukeboxRandomSongList].Draw; + Button[JukeboxLyric].Draw; + + for I := 0 to Max do + begin + Button[SongDescription[I]].Visible := true; + Button[SongDescription[I]].Selectable := true; + + SongDesc := CatSongs.Song[JukeboxVisibleSongs[I + ListMin]].Artist + ' - ' + CatSongs.Song[JukeboxVisibleSongs[I + ListMin]].Title; + + if (JukeboxVisibleSongs[I + ListMin] = CurrentSongID) and (I + ListMin <> ActualInteraction) then + begin + Button[SongDescription[I]].Text[0].ColR := SelectColR; + Button[SongDescription[I]].Text[0].ColG := SelectColG; + Button[SongDescription[I]].Text[0].ColB := SelectColB; + end + else + begin + Button[SongDescription[I]].Text[0].ColR := 1; + Button[SongDescription[I]].Text[0].ColG := 1; + Button[SongDescription[I]].Text[0].ColB := 1; + end; + + Button[SongDescription[I]].Text[0].Text := SongDesc; + Button[SongDescription[I]].Draw; + end; + +end; + +end. + + diff --git a/src/screens/UScreenMain.pas b/src/screens/UScreenMain.pas index aa313cf6..b5e3a8bb 100644 --- a/src/screens/UScreenMain.pas +++ b/src/screens/UScreenMain.pas @@ -85,9 +85,9 @@ function TScreenMain.ParseInput(PressedKey: Cardinal; CharCode: UCS4Char; PressedDown: boolean): boolean; var SDL_ModState: word; + I: integer; begin Result := true; - { reset user interaction timer } UserInteractionTicks := SDL_GetTicks; @@ -126,6 +126,27 @@ begin FadeTo(@ScreenEdit, SoundLib.Start); Exit; end; + + Ord('J'): begin + //ScreenSong.Mode := smPartyJukebox; + AudioPlayback.PlaySound(SoundLib.Start); + + SetLength(ScreenJukebox.JukeboxSongsList, 0); + SetLength(ScreenJukebox.JukeboxVisibleSongs, 0); + + for I := 0 to High(CatSongs.Song) do + begin + if not (CatSongs.Song[I].Main) then + ScreenJukebox.AddSongToJukeboxList(I); + end; + ScreenJukebox.ActualInteraction := 0; + ScreenJukebox.CurrentSongList := 0; + ScreenJukebox.ListMin := 0; + ScreenJukebox.Interaction := 0; + ScreenJukebox.CurrentSongID := ScreenJukebox.JukeboxVisibleSongs[ScreenJukebox.CurrentSongList]; + + FadeTo(@ScreenJukebox); + end; end; // check special keys diff --git a/src/ultrastardx.dpr b/src/ultrastardx.dpr index 39303e79..d798d632 100644 --- a/src/ultrastardx.dpr +++ b/src/ultrastardx.dpr @@ -319,6 +319,7 @@ uses UScreenSong in 'screens\UScreenSong.pas', UScreenSing in 'screens\UScreenSing.pas', UScreenScore in 'screens\UScreenScore.pas', + UScreenJukebox in 'screens\UScreenJukebox.pas', UScreenOptions in 'screens\UScreenOptions.pas', UScreenOptionsGame in 'screens\UScreenOptionsGame.pas', UScreenOptionsGraphics in 'screens\UScreenOptionsGraphics.pas', -- cgit v1.2.3