{* 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; //Button of Songtitle 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(CP: integer; SentenceIndex: cardinal); // for linebonus + singbar procedure OnSentenceChange(CP: integer; 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 Filter := GetStringWithNoAccents(UTF8Decode(UTF8LowerCase(Filter))); 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'); finally end; end; end; Button[JukeboxFindSong].Text[0].Text := ''; 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; // allow search for songs Ord('J'): begin if (SongListVisible) then begin LastTick := SDL_GetTicks(); FindSongList := not FindSongList; if (Filter = '') and (FindSongList) then Button[JukeboxFindSong].Text[0].Text := ''; Button[JukeboxFindSong].SetSelect(FindSongList); if FindSongList then FilterSongList(Filter) else FilterSongList(''); Exit; end; end; //Randomixe Playlist Ord('R'): begin if (SongListVisible) 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; 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_ESCAPE: begin if (SongListVisible) then begin SongListVisible := false; FindSongList := false; Button[JukeboxFindSong].SetSelect(FindSongList); Filter:= ''; FilterSongList(''); Button[JukeboxFindSong].Text[0].Text := ''; end else ScreenPopupCheck.ShowPopup('MSG_END_JUKEBOX', OnEscapeJukebox, nil, false) end; SDLK_BACKSPACE: begin if (FindSongList) and (SongListVisible) then begin LastTick := SDL_GetTicks(); if (Filter = '') then begin FindSongList:=false; Button[JukeboxFindSong].SetSelect(FindSongList); Exit; end; 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(); if (FindSongList) then begin FindSongList:=false; Button[JukeboxFindSong].SetSelect(FindSongList); end; if (High(JukeboxVisibleSongs) < 0) then begin FilterSongList(''); end; CurrentSongList := ActualInteraction - 1; Finish; PlayMusic(CurrentSongList); end; end; SDLK_LEFT: begin 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 (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_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); RepeatSongList := true; Button[JukeboxFindSong].Selectable := false; Button[JukeboxRepeatSongList].Selectable := true; 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 // restart 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(CP: integer; 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(CP: integer; 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; try 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; finally end; end; end.