diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/base/UDraw.pas | 18 | ||||
-rw-r--r-- | src/base/UNote.pas | 44 | ||||
-rw-r--r-- | src/base/USong.pas | 330 | ||||
-rw-r--r-- | src/screens/UScreenScore.pas | 209 | ||||
-rw-r--r-- | src/screens/UScreenSing.pas | 781 | ||||
-rw-r--r-- | src/screens/UScreenSong.pas | 207 |
6 files changed, 1295 insertions, 294 deletions
diff --git a/src/base/UDraw.pas b/src/base/UDraw.pas index 5bec3eab..374f9cbe 100644 --- a/src/base/UDraw.pas +++ b/src/base/UDraw.pas @@ -93,6 +93,7 @@ uses UMusic, URecord, UScreenSing, + USong, UTexture; procedure SingDrawBackground; @@ -1115,6 +1116,8 @@ var width, height: real; LyricsProgress: real; CurLyricsTime: real; + TotalTime: real; + begin x := Theme.Sing.StaticTimeProgress.x; y := Theme.Sing.StaticTimeProgress.y; @@ -1135,13 +1138,22 @@ begin glTexCoord2f(0, 0); glVertex2f(x, y); - CurLyricsTime := LyricsState.GetCurrentTime(); + if ScreenSong.Mode = smMedley then + begin + CurLyricsTime := LyricsState.GetCurrentTime() - ScreenSing.MedleyStart; + TotalTime := ScreenSing.MedleyEnd - ScreenSing.MedleyStart; + end + else + begin + CurLyricsTime := LyricsState.GetCurrentTime(); + TotalTime := LyricsState.TotalTime; + end; if (CurLyricsTime > 0) and (LyricsState.TotalTime > 0) then begin - LyricsProgress := CurLyricsTime / LyricsState.TotalTime; + LyricsProgress := CurLyricsTime / TotalTime; // avoid that the bar "overflows" for inaccurate song lengths - if (LyricsProgress > 1.0) then + if LyricsProgress > 1.0 then LyricsProgress := 1.0; glTexCoord2f((width * LyricsProgress) / 8, 0); glVertex2f(x + width * LyricsProgress, y); diff --git a/src/base/UNote.pas b/src/base/UNote.pas index 2d71fb67..b9e71ac2 100644 --- a/src/base/UNote.pas +++ b/src/base/UNote.pas @@ -37,16 +37,15 @@ uses SysUtils,
Classes,
SDL,
- UMusic,
- URecord,
- UTime,
+ gl,
UDisplay,
UIni,
ULog,
ULyrics,
+ URecord,
UScreenSing,
USong,
- gl;
+ UTime;
type
PPLayerNote = ^TPlayerNote;
@@ -88,6 +87,21 @@ type Note: array of TPlayerNote;
end;
+ TStats = record
+ Player: array of TPlayer;
+ SongArtist: string;
+ SongTitle: string;
+ end;
+
+ TMedleyPlaylist = record
+ Song: array of integer;
+ NumMedleySongs: integer;
+ CurrentMedleySong: integer;
+ ApplausePlayed: boolean;
+ Stats: array of TStats;
+ NumPlayer: integer;
+ end;
+
{* Player and music info *}
var
{**
@@ -107,6 +121,8 @@ var *}
CurrentSong: TSong;
+ PlaylistMedley: TMedleyPlaylist; // playlist medley
+
const
MAX_SONG_SCORE = 10000; // max. achievable points per song
MAX_SONG_LINE_BONUS = 1000; // max. achievable line bonus per song
@@ -124,23 +140,23 @@ implementation uses
Math,
StrUtils,
- USongs,
- UJoystick,
+ UCatCovers,
UCommandLine,
- ULanguage,
- //SDL_ttf,
- USkins,
+ UCommon,
+ UConfig,
UCovers,
- UCatCovers,
UDataBase,
- UPlaylist,
- UParty,
- UConfig,
- UCommon,
UGraphic,
UGraphicClasses,
+ UJoystick,
+ ULanguage,
+ UMusic,
+ UParty,
UPathUtils,
UPlatform,
+ UPlaylist,
+ USkins,
+ USongs,
UThemes;
function GetTimeForBeats(BPM, Beats: real): real;
diff --git a/src/base/USong.pas b/src/base/USong.pas index e92c5b45..38ba1c12 100644 --- a/src/base/USong.pas +++ b/src/base/USong.pas @@ -45,10 +45,6 @@ uses {$ENDIF} SysUtils, Classes, - UPlatform, - ULog, - UTexture, - UCommon, {$IFDEF DARWIN} cthreads, {$ENDIF} @@ -56,15 +52,29 @@ uses PseudoThread, {$ENDIF} UCatCovers, - UXMLSong, - UUnicodeUtils, - UTextEncoding, + UCommon, UFilesystem, - UPath; + ULog, + UPath, + UPlatform, + UTexture, + UTextEncoding, + UUnicodeUtils, + UXMLSong; type - TSingMode = ( smNormal, smPartyMode, smPlaylistRandom ); + TSingMode = ( smNormal, smPartyMode, smPlaylistRandom, smMedley ); + + TMedleySource = ( msNone, msCalculated, msTag ); + + TMedley = record + Source: TMedleySource; //source of the information + StartBeat: integer; //start beat of medley + EndBeat: integer; //end beat of medley + FadeIn_time: real; //FadeIn-Time in seconds + FadeOut_time: real; //FadeOut-Time in seconds + end; TBPM = record BPM: real; @@ -92,6 +102,7 @@ type function DecodeFilename(Filename: RawByteString): IPath; procedure ParseNote(LineNumber: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: UTF8String); procedure NewSentence(LineNumberP: integer; Param1, Param2: integer); + procedure FindRefrain(); // tries to find a refrain for the medley mode and preview start function ParseLyricStringParam(const Line: RawByteString; var LinePos: integer): RawByteString; function ParseLyricIntParam(const Line: RawByteString; var LinePos: integer): integer; @@ -139,6 +150,10 @@ type Encoding: TEncoding; + PreviewStart: real; // in seconds + CalcMedley: boolean; // if true => do not calc medley for that song + Medley: TMedley; // medley params + CustomTags: array of TCustomHeaderTag; Score: array[0..2] of array of TScore; @@ -166,6 +181,7 @@ type function LoadXMLSong: boolean; function Analyse(const ReadCustomTags: Boolean = false): boolean; function AnalyseXML(): boolean; + procedure SetMedleyMode(); procedure Clear(); end; @@ -175,9 +191,13 @@ uses StrUtils, TextGL, UIni, - UPathUtils, UMusic, //needed for Lines - UNote; //needed for Player + UNote, //needed for Player + UPathUtils; + +const + DEFAULT_FADE_IN_TIME = 8; // for medley fade-in + DEFAULT_FADE_OUT_TIME = 2; // for medley fade-out constructor TSong.Create(); begin @@ -468,6 +488,7 @@ begin Exit; end; + SetLength(Lines, 0); SetLength(Lines, 2); for Count := 0 to High(Lines) do begin @@ -867,6 +888,7 @@ var Value: string; SepPos: integer; // separator position Done: byte; // bit-vector of mandatory fields + MedleyFlags: byte; //bit-vector for medley/preview tags EncFile: IPath; // encoded filename FullFileName: string; @@ -887,6 +909,7 @@ var begin Result := true; Done := 0; + MedleyFlags := 0; FullFileName := Path.Append(Filename).ToNative; @@ -1090,6 +1113,35 @@ begin self.Encoding := ParseEncoding(Value, Ini.DefaultEncoding); end + // PreviewStart + else if (Identifier = 'PREVIEWSTART') then + begin + self.PreviewStart := StrToFloatI18n( Value ); + if (self.PreviewStart>0) then + MedleyFlags := MedleyFlags or 1; + end + + // MedleyStartBeat + else if (Identifier = 'MEDLEYSTARTBEAT') and not self.Relative then + begin + if TryStrtoInt(Value, self.Medley.StartBeat) then + MedleyFlags := MedleyFlags or 2; + end + + // MedleyEndBeat + else if (Identifier = 'MEDLEYENDBEAT') and not self.Relative then + begin + if TryStrtoInt(Value, self.Medley.EndBeat) then + MedleyFlags := MedleyFlags or 4; + end + + // Medley + else if (Identifier = 'CALCMEDLEY') then + begin + if Uppercase(Value) = 'OFF' then + self.CalcMedley := false; + end + // unsupported tag else begin @@ -1099,7 +1151,7 @@ begin end; // End check for non-empty Value // read next line - if (not SongFile.ReadLine(Line)) then + if not SongFile.ReadLine(Line) then begin Result := false; Log.LogError('File incomplete or not Ultrastar txt (A): ' + FullFileName); @@ -1124,6 +1176,33 @@ begin Log.LogError('Title tag missing: ' + FullFileName) else //unknown Error Log.LogError('File incomplete or not Ultrastar txt (B - '+ inttostr(Done) +'): ' + FullFileName); + end + else + begin //check medley tags + if (MedleyFlags and 6) = 6 then //MedleyStartBeat and MedleyEndBeat are both set + begin + if self.Medley.StartBeat >= self.Medley.EndBeat then + MedleyFlags := MedleyFlags - 6; + end; + + if ((MedleyFlags and 1) = 0) or (self.PreviewStart <= 0) then //PreviewStart is not set or <=0 + begin + if (MedleyFlags and 2) = 2 then + self.PreviewStart := GetTimeFromBeat(self.Medley.StartBeat) //fallback to MedleyStart + else + self.PreviewStart := 0; //else set it to 0, it will be set in FindRefrainStart + end; + + if (MedleyFlags and 6) = 6 then + begin + self.Medley.Source := msTag; + + //calculate fade time + self.Medley.FadeIn_time := DEFAULT_FADE_IN_TIME; + + self.Medley.FadeOut_time := DEFAULT_FADE_OUT_TIME; + end else + self.Medley.Source := msNone; end; end; @@ -1223,6 +1302,221 @@ begin Lines[LineNumberP].Line[Lines[LineNumberP].High].LastLine := false; end; +{* new procedure for preview + tries find out the beginning of a refrain + and the end... *} +procedure TSong.FindRefrain(); +Const + MEDLEY_MIN_DURATION = 40; //minimum duration of a medley-song in seconds + +Type + TSeries = record + start: integer; //Start sentence of series + end_: integer; //End sentence of series + len: integer; //Length of sentence series + end; + +var + I, J, K, num_lines: integer; + sentences: array of UTF8String; + series: array of TSeries; + temp_series: TSeries; + max: integer; + len_lines, len_notes: integer; + found_end: boolean; +begin + if self.Medley.Source = msTag then + Exit; + + if not self.CalcMedley then + Exit; + + //relative is not supported for medley! + if self.Relative then + begin + Log.LogError('Song '+self.Artist+'-' + self.Title + ' contains #Relative, this is not supported by medley-function!'); + self.Medley.Source := msNone; + Exit; + end; + + num_lines := Length(Lines[0].Line); + SetLength(sentences, num_lines); + + //build sentences array + for I := 0 to num_lines - 1 do + begin + sentences[I] := ''; + for J := 0 to Length(Lines[0].Line[I].Note) - 1 do + begin + if (Lines[0].Line[I].Note[J].NoteType <> ntFreestyle) then + sentences[I] := sentences[I] + Lines[0].Line[I].Note[J].Text; + end; + end; + + //find equal sentences series + SetLength(series, 0); + + for I := 0 to num_lines - 2 do + begin + for J := I + 1 to num_lines - 1 do + begin + if sentences[I] = sentences[J] then + begin + temp_series.start := I; + temp_series.end_ := I; + + if (J + J - I - 1 > num_lines - 1) then + max:=num_lines-1-J + else + max:=J-I-1; + + for K := 1 to max do + begin + if sentences[I+K] = sentences[J+K] then + temp_series.end_ := I+K + else + break; + end; + temp_series.len := temp_series.end_ - temp_series.start + 1; + SetLength(series, Length(series)+1); + series[Length(series)-1] := temp_series; + end; + end; + end; + + //search for longest sequence + max := 0; + if Length(series) > 0 then + begin + for I := 0 to Length(series) - 1 do + begin + if series[I].len > series[max].len then + max := I; + end; + end; + + len_lines := length(Lines[0].Line); + + if (Length(series) > 0) and (series[max].len > 3) then + begin + self.Medley.StartBeat := Lines[0].Line[series[max].start].Note[0].Start; + len_notes := length(Lines[0].Line[series[max].end_].Note); + self.Medley.EndBeat := Lines[0].Line[series[max].end_].Note[len_notes - 1].Start + + Lines[0].Line[series[max].end_].Note[len_notes - 1].Length; + + found_end := false; + + //set end if duration > MEDLEY_MIN_DURATION + if GetTimeFromBeat(self.Medley.StartBeat) + MEDLEY_MIN_DURATION > + GetTimeFromBeat(self.Medley.EndBeat) then + begin + found_end := true; + end; + + //estimate the end: just go MEDLEY_MIN_DURATION + //ahead an set to a line end (if possible) + if not found_end then + begin + for I := series[max].start + 1 to len_lines - 1 do + begin + len_notes := length(Lines[0].Line[I].Note); + for J := 0 to len_notes - 1 do + begin + if GetTimeFromBeat(self.Medley.StartBeat) + MEDLEY_MIN_DURATION > + GetTimeFromBeat(Lines[0].Line[I].Note[J].Start + + Lines[0].Line[I].Note[J].Length) then + begin + found_end := true; + self.Medley.EndBeat := Lines[0].Line[I].Note[len_notes-1].Start + + Lines[0].Line[I].Note[len_notes - 1].Length; + break; + end; + end; + end; + end; + + if found_end then + begin + self.Medley.Source := msCalculated; + + //calculate fade time + self.Medley.FadeIn_time := DEFAULT_FADE_IN_TIME; + self.Medley.FadeOut_time := DEFAULT_FADE_OUT_TIME; + end; + end; + + //set PreviewStart if not set + if self.PreviewStart = 0 then + begin + if self.Medley.Source = msCalculated then + self.PreviewStart := GetTimeFromBeat(self.Medley.StartBeat); + end; +end; + +//sets a song to medley-mode: +//converts all unneeded notes into freestyle +//updates score values +procedure TSong.SetMedleyMode(); +var + pl, line, note: integer; + cut_line: array of integer; + foundcut: array of boolean; + start: integer; + end_: integer; + +begin + start := self.Medley.StartBeat; + end_ := self.Medley.EndBeat; + SetLength(cut_line, Length(Lines)); + SetLength(foundcut, Length(Lines)); + + for pl := 0 to Length(Lines) - 1 do + begin + foundcut[pl] := false; + cut_line[pl] := high(integer); + Lines[pl].ScoreValue := 0; + for line := 0 to Length(Lines[pl].Line) - 1 do + begin + Lines[pl].Line[line].TotalNotes := 0; + for note := 0 to Length(Lines[pl].Line[line].Note) - 1 do + begin + if Lines[pl].Line[line].Note[note].Start < start then //check start + begin + Lines[pl].Line[line].Note[note].NoteType := ntFreeStyle; + end else if Lines[pl].Line[line].Note[note].Start>= end_ then //check end + begin + Lines[pl].Line[line].Note[note].NoteType := ntFreeStyle; + if not foundcut[pl] then + begin + if (note=0) then + cut_line[pl] := line + else + cut_line[pl] := line + 1; + end; + foundcut[pl] := true; + end + else + begin + //add this notes value ("notes length" * "notes scorefactor") to the current songs entire value + Inc(Lines[pl].ScoreValue, Lines[pl].Line[line].Note[note].Length * ScoreFactor[Lines[pl].Line[line].Note[note].NoteType]); + //and to the current lines entire value + Inc(Lines[pl].Line[line].TotalNotes, Lines[pl].Line[line].Note[note].Length * ScoreFactor[Lines[pl].Line[line].Note[note].NoteType]); + end; + end; + end; + end; + + for pl := 0 to Length(Lines) - 1 do + begin + if (foundcut[pl]) and (Length(Lines[pl].Line) > cut_line[pl]) then + begin + SetLength(Lines[pl].Line, cut_line[pl]); + Lines[pl].High := cut_line[pl]-1; + Lines[pl].Number := Lines[pl].High+1; + end; + end; +end; + procedure TSong.Clear(); begin //Main Information @@ -1257,6 +1551,9 @@ begin NotesGAP := 0; Resolution := 4; Creator := ''; + PreviewStart := 0; + CalcMedley := true; + Medley.Source := msNone; Relative := false; end; @@ -1277,7 +1574,14 @@ begin Self.clear; //Read Header - Result := Self.ReadTxTHeader(SongFile, ReadCustomTags) + Result := Self.ReadTxTHeader(SongFile, ReadCustomTags); + + //Load Song for Medley Tags + CurrentSong := self; + Result := Result and LoadSong(); + + if Result then + Self.FindRefrain(); finally SongFile.Free; end; diff --git a/src/screens/UScreenScore.pas b/src/screens/UScreenScore.pas index de7675bf..4b7332b4 100644 --- a/src/screens/UScreenScore.pas +++ b/src/screens/UScreenScore.pas @@ -34,16 +34,16 @@ interface {$I switches.inc} uses - UMenu, - SDL, SysUtils, + math, + SDL, + gl, UDisplay, + UMenu, UMusic, USongs, - UThemes, - gl, - math, - UTexture; + UTexture, + UThemes; const ZBars: real = 0.8; // Z value for the bars @@ -167,6 +167,11 @@ type TextPhrase_ActualValue: array[1..6] of integer; TextGolden_ActualValue: array[1..6] of integer; + ActualRound: integer; + + procedure RefreshTexts; + procedure ResetScores; + procedure MapPlayersToPosition; procedure FillPlayer(Item, P: integer); @@ -196,7 +201,7 @@ type public constructor Create; override; function ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown: boolean): boolean; override; - function ParseMouse(MouseButton: Integer; BtnDown: Boolean; X, Y: integer): boolean; override; + function ParseMouse(MouseButton: integer; BtnDown: boolean; X, Y: integer): boolean; override; procedure OnShow; override; procedure OnShowFinish; override; function Draw: boolean; override; @@ -206,14 +211,15 @@ implementation uses UGraphic, - UScreenSong, - UMenuStatic, - UTime, UIni, - USkins, ULog, ULanguage, + UMenuStatic, UNote, + UScreenSong, + USkins, + USong, + UTime, UUnicodeUtils; @@ -237,27 +243,96 @@ begin SDLK_BACKSPACE, SDLK_RETURN: begin - FadeTo(@ScreenTop5); - Exit; + if ScreenSong.Mode = smMedley then + FadeTo(@ScreenSong) + else + FadeTo(@ScreenTop5); + + Exit; end; SDLK_SYSREQ: begin Display.SaveScreenShot; end; + + SDLK_RIGHT: + begin + if ActualRound < Length(PlaylistMedley.Stats) - 1 then + begin + AudioPlayback.PlaySound(SoundLib.Change); + inc(ActualRound); + RefreshTexts; + end; + end; + + SDLK_LEFT: + begin + if ActualRound > 0 then + begin + AudioPlayback.PlaySound(SoundLib.Change); + dec(ActualRound); + RefreshTexts; + end; + end; end; end; end; -function TScreenScore.ParseMouse(MouseButton: Integer; BtnDown: Boolean; X, Y: integer): boolean; +function TScreenScore.ParseMouse(MouseButton: integer; BtnDown: boolean; X, Y: integer): boolean; begin Result := True; - if (MouseButton = SDL_BUTTON_LEFT) and BtnDown then begin + if (MouseButton = SDL_BUTTON_LEFT) and BtnDown then + begin //left-click anywhere sends return ParseInput(SDLK_RETURN, 0, true); end; end; +procedure TScreenScore.RefreshTexts; +var + P: integer; + +begin + if ActualRound < Length(PlaylistMedley.Stats) - 1 then + begin + Text[TextArtist].Text := IntToStr(ActualRound+1) + '/' + + IntToStr(Length(PlaylistMedley.Stats)-1) + ': ' + + PlaylistMedley.Stats[ActualRound].SongArtist; + Text[TextTitle].Text := PlaylistMedley.Stats[ActualRound].SongTitle; + Text[TextTitle].Visible := true; + Text[TextArtistTitle].Text := IntToStr(ActualRound+1) + '/' + + IntToStr(Length(PlaylistMedley.Stats)-1) + ': ' + + PlaylistMedley.Stats[ActualRound].SongArtist + + ' - ' + PlaylistMedley.Stats[ActualRound].SongTitle; + end + else + begin + if ScreenSong.Mode = smMedley then + begin + Text[TextArtist].Text := Language.Translate('SING_TOTAL'); + Text[TextTitle].Visible := false; + Text[TextArtistTitle].Text := Language.Translate('SING_TOTAL'); + end + else + begin + Text[TextArtist].Text := PlaylistMedley.Stats[ActualRound].SongArtist; + Text[TextTitle].Text := PlaylistMedley.Stats[ActualRound].SongTitle; + Text[TextTitle].Visible := true; + Text[TextArtistTitle].Text := PlaylistMedley.Stats[ActualRound].SongArtist + ' - ' + + PlaylistMedley.Stats[ActualRound].SongTitle; + end; + end; + + if ScreenSong.Mode = smMedley then + begin + for P := 0 to PlayersPlay - 1 do + Player[P] := PlaylistMedley.Stats[ActualRound].Player[P]; + end; + + ResetScores; +end; + procedure TScreenScore.LoadSwapTextures; var P, I: integer; @@ -621,23 +696,15 @@ begin inherited; - MapPlayersToPosition; - - for P := 1 to PlayersPlay do + ActualRound := 0; + if ScreenSong.Mode = smMedley then begin - // data - aPlayerScoreScreenDatas[P].Bar_Y := Theme.Score.StaticBackLevel[PlayerPositionMap[P-1].Position].Y; - - // ratings - aPlayerScoreScreenRatings[P].RateEaseStep := 1; - aPlayerScoreScreenRatings[P].RateEaseValue := 20; - - // actual values - TextScore_ActualValue[P] := 0; - TextPhrase_ActualValue[P] := 0; - TextGolden_ActualValue[P] := 0; + for P := 0 to PlayersPlay - 1 do + Player[P] := PlaylistMedley.Stats[ActualRound].Player[P]; end; + MapPlayersToPosition; + Text[TextArtist].Text := CurrentSong.Artist; Text[TextTitle].Text := CurrentSong.Title; Text[TextArtistTitle].Text := CurrentSong.Artist + ' - ' + CurrentSong.Title; @@ -672,43 +739,29 @@ begin for P := 1 to 6 do begin - Text[TextName[P]].Visible := V[P]; - Text[TextScore[P]].Visible := V[P]; - - // We set alpha to 0 , so we can nicely blend them in when we need them - Text[TextScore[P]].Alpha := 0; - Text[TextNotesScore[P]].Alpha := 0; - Text[TextNotes[P]].Alpha := 0; - Text[TextLineBonus[P]].Alpha := 0; - Text[TextLineBonusScore[P]].Alpha := 0; - Text[TextGoldenNotes[P]].Alpha := 0; - Text[TextGoldenNotesScore[P]].Alpha := 0; - Text[TextTotal[P]].Alpha := 0; - Text[TextTotalScore[P]].Alpha := 0; - Statics[StaticBoxLightest[P]].Texture.Alpha := 0; - Statics[StaticBoxLight[P]].Texture.Alpha := 0; - Statics[StaticBoxDark[P]].Texture.Alpha := 0; - - Text[TextNotes[P]].Visible := V[P]; - Text[TextNotesScore[P]].Visible := V[P]; - Text[TextLineBonus[P]].Visible := V[P]; - Text[TextLineBonusScore[P]].Visible := V[P]; - Text[TextGoldenNotes[P]].Visible := V[P]; - Text[TextGoldenNotesScore[P]].Visible := V[P]; - Text[TextTotal[P]].Visible := V[P]; - Text[TextTotalScore[P]].Visible := V[P]; + Text[TextName[P]].Visible := V[P]; + Text[TextScore[P]].Visible := V[P]; + + Text[TextNotes[P]].Visible := V[P]; + Text[TextNotesScore[P]].Visible := V[P]; + Text[TextLineBonus[P]].Visible := V[P]; + Text[TextLineBonusScore[P]].Visible := V[P]; + Text[TextGoldenNotes[P]].Visible := V[P]; + Text[TextGoldenNotesScore[P]].Visible := V[P]; + Text[TextTotal[P]].Visible := V[P]; + Text[TextTotalScore[P]].Visible := V[P]; for I := 0 to high(PlayerStatic[P]) do Statics[PlayerStatic[P, I]].Visible := V[P]; for I := 0 to high(PlayerTexts[P]) do - Text[PlayerTexts[P, I]].Visible := V[P]; + Text[PlayerTexts[P, I]].Visible := V[P]; Statics[StaticBoxLightest[P]].Visible := V[P]; Statics[StaticBoxLight[P]].Visible := V[P]; Statics[StaticBoxDark[P]].Visible := V[P]; - - Statics[StaticPlayerIdBox[P]].Visible := V[P]; + + Statics[StaticPlayerIdBox[P]].Visible := V[P]; // we draw that on our own Statics[StaticBackLevel[P]].Visible := false; @@ -717,9 +770,51 @@ begin Statics[StaticLevelRound[P]].Visible := false; end; + RefreshTexts; +end; + +procedure TScreenScore.ResetScores; +var + P: integer; + +begin + for P := 1 to PlayersPlay do + begin + // data + aPlayerScoreScreenDatas[P].Bar_Y := Theme.Score.StaticBackLevel[PlayerPositionMap[P-1].Position].Y; + + // ratings + aPlayerScoreScreenRatings[P].RateEaseStep := 1; + aPlayerScoreScreenRatings[P].RateEaseValue := 20; + + // actual values + TextScore_ActualValue[P] := 0; + TextPhrase_ActualValue[P] := 0; + TextGolden_ActualValue[P] := 0; + end; + + for P := 1 to 6 do + begin + // We set alpha to 0 , so we can nicely blend them in when we need them + Text[TextScore[P]].Alpha := 0; + Text[TextNotesScore[P]].Alpha := 0; + Text[TextNotes[P]].Alpha := 0; + Text[TextLineBonus[P]].Alpha := 0; + Text[TextLineBonusScore[P]].Alpha := 0; + Text[TextGoldenNotes[P]].Alpha := 0; + Text[TextGoldenNotesScore[P]].Alpha := 0; + Text[TextTotal[P]].Alpha := 0; + Text[TextTotalScore[P]].Alpha := 0; + Statics[StaticBoxLightest[P]].Texture.Alpha := 0; + Statics[StaticBoxLight[P]].Texture.Alpha := 0; + Statics[StaticBoxDark[P]].Texture.Alpha := 0; + end; + BarScore_EaseOut_Step := 1; BarPhrase_EaseOut_Step := 1; BarGolden_EaseOut_Step := 1; + + BarTime := SDL_GetTicks(); end; procedure TScreenScore.onShowFinish; diff --git a/src/screens/UScreenSing.pas b/src/screens/UScreenSing.pas index 221526f8..ff4f33e6 100644 --- a/src/screens/UScreenSing.pas +++ b/src/screens/UScreenSing.pas @@ -36,24 +36,30 @@ interface uses SysUtils, SDL, - TextGL, gl, + TextGL, UFiles, UGraphicClasses, + UHookableEvent, UIni, ULog, ULyrics, UMenu, UMusic, + UPath, USingScores, USongs, UTexture, UThemes, - UPath, - UTime, - UHookableEvent; + UTime; type + TPos = record // Lines[part].Line[line].Note[note] + part: integer; + line: integer; + note: integer; + end; + TLyricsSyncSource = class(TSyncSource) function GetClock(): real; override; end; @@ -72,11 +78,18 @@ type TScreenSing = class(TMenu) private fShowVisualization: boolean; - fCurrentVideo: IVideo; - fVideoClip: IVideo; - fLyricsSync: TLyricsSyncSource; - fMusicSync: TMusicSyncSource; - fTimebarMode: TTimebarMode; + fCurrentVideo: IVideo; + fVideoClip: IVideo; + fLyricsSync: TLyricsSyncSource; + fMusicSync: TMusicSyncSource; + fTimebarMode: TTimebarMode; + + StartNote, EndNote: TPos; + + procedure LoadNextSong(); + procedure UpdateMedleyStats(medley_end: boolean); + procedure DrawMedleyCountdown(); + procedure SongError(); protected eSongLoaded: THookableEvent; //< event is called after lyrics of a song are loaded on OnShow Paused: boolean; //pause Mod @@ -97,6 +110,8 @@ type StaticP1ThreeP: integer; TextP1ThreeP: integer; + MedleyStart, MedleyEnd: real; + StaticP2R: integer; TextP2R: integer; @@ -120,12 +135,12 @@ type // some settings to be set by plugins Settings: record - Finish: Boolean; //< if true, screen will finish on next draw + Finish: boolean; //< if true, screen will finish on next draw - LyricsVisible: Boolean; //< shows or hides lyrics - NotesVisible: Integer; //< if bit[playernum] is set the notes for the specified player are visible. By default all players notes are visible + LyricsVisible: boolean; //< shows or hides lyrics + NotesVisible: integer; //< if bit[playernum] is set the notes for the specified player are visible. By default all players notes are visible - PlayerEnabled: Integer; //< defines whether a player can score atm + PlayerEnabled: integer; //< defines whether a player can score atm end; procedure ClearSettings; procedure ApplySettings; //< applies changes of settings record @@ -152,24 +167,24 @@ implementation uses Classes, Math, + UDisplay, UDraw, UGraphic, ULanguage, UNote, + UParty, URecord, USong, - UDisplay, - UParty, UUnicodeUtils; // method for input parsing. if false is returned, getnextwindow // should be checked to know the next window to load; -function TScreenSing.ParseInput(PressedKey: Cardinal; CharCode: UCS4Char; +function TScreenSing.ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown: boolean): boolean; begin Result := true; - if (PressedDown) then + if PressedDown then begin // key down // check normal keys case UCS4UpperCase(CharCode) of @@ -213,7 +228,7 @@ begin // toggle time display Ord('T'): begin - if (fTimebarMode = High(TTimebarMode)) then + if fTimebarMode = High(TTimebarMode) then fTimebarMode := Low(TTimebarMode) else Inc(fTimebarMode); @@ -228,6 +243,8 @@ begin begin // record sound hack: //Sound[0].BufferLong + if ScreenSong.Mode = smMedley then + PlaylistMedley.NumMedleySongs := PlaylistMedley.CurrentMedleySong; Finish; FadeOut := true; @@ -360,17 +377,14 @@ end; procedure TScreenSing.OnShow; var - Index: integer; 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; - VideoFile, BgFile: IPath; - success: boolean; BadPlayer: integer; + begin inherited; @@ -382,22 +396,21 @@ begin ClearSettings; Party.CallBeforeSing; - // reset video playback engine - fCurrentVideo := nil; - - // setup score manager - Scores.ClearPlayers; // clear old player values - Color.R := 0; - Color.G := 0; - Color.B := 0; // dummy atm <- \(O.o)/? B like bummy? + // prepare players + SetLength(Player, PlayersPlay); - // add new players - for Index := 0 to PlayersPlay - 1 do + //Reset Player Medley stats + if (ScreenSong.Mode = smMedley) then begin - Scores.AddPlayer(Tex_ScoreBG[Index], Color); - end; + PlaylistMedley.CurrentMedleySong:=1; - Scores.Init; // get positions for players + PlaylistMedley.NumPlayer := PlayersPlay; + SetLength(PlaylistMedley.Stats, 0); + + fTimebarMode := tbmRemaining; + end + else + fTimebarMode := tbmCurrent; // prepare players SetLength(Player, PlayersPlay); @@ -472,11 +485,265 @@ begin Statics[StaticP3R].Visible := V3R; Text[TextP3R].Visible := V3R; - fTimebarMode := tbmCurrent; + BadPlayer := AudioInputProcessor.CheckPlayersConfig(PlayersPlay); + if BadPlayer <> 0 then + begin + ScreenPopupError.ShowPopup( + Format(Language.Translate('ERROR_PLAYER_NO_DEVICE_ASSIGNMENT'), + [BadPlayer])); + end; + + // set custom options + case Ini.LyricsFont of + 0: // normal fonts + begin + Lyrics.FontStyle := ftNormal; + + Lyrics.LineColor_en.R := Skin_FontR; + Lyrics.LineColor_en.G := Skin_FontG; + Lyrics.LineColor_en.B := Skin_FontB; + Lyrics.LineColor_en.A := 1; + + Lyrics.LineColor_dis.R := 0.4; + Lyrics.LineColor_dis.G := 0.4; + Lyrics.LineColor_dis.B := 0.4; + Lyrics.LineColor_dis.A := 1; + + Lyrics.LineColor_act.R := 0.02; + Lyrics.LineColor_act.G := 0.6; + Lyrics.LineColor_act.B := 0.8; + Lyrics.LineColor_act.A := 1; + end; + 1, 2: // outline fonts + begin + if (Ini.LyricsFont = 1) then + Lyrics.FontStyle := ftOutline1 + else + Lyrics.FontStyle := ftOutline2; + + Lyrics.LineColor_en.R := 0.75; + Lyrics.LineColor_en.G := 0.75; + Lyrics.LineColor_en.B := 1; + Lyrics.LineColor_en.A := 1; + + Lyrics.LineColor_dis.R := 0.8; + Lyrics.LineColor_dis.G := 0.8; + Lyrics.LineColor_dis.B := 0.8; + Lyrics.LineColor_dis.A := 1; + + Lyrics.LineColor_act.R := 0.5; + Lyrics.LineColor_act.G := 0.5; + Lyrics.LineColor_act.B := 1; + Lyrics.LineColor_act.A := 1; + end; + end; // case + + // deactivate pause + Paused := false; + + LoadNextSong(); + + Log.LogStatus('End', 'OnShow'); +end; + +procedure TScreenSing.onShowFinish; +begin + // hide cursor on singscreen show + Display.SetCursor; + + // prepare music + // Important: AudioPlayback must not be initialized in onShow() as TScreenSong + // uses stops AudioPlayback in onHide() which interferes with TScreenSings onShow. + AudioPlayback.Open(CurrentSong.Path.Append(CurrentSong.Mp3)); + if ScreenSong.Mode = smMedley then + AudioPlayback.SetVolume(0.1) + else + AudioPlayback.SetVolume(1.0); + AudioPlayback.Position := LyricsState.GetCurrentTime(); + + // 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 + if ScreenSong.Mode = smMedley then + AudioPlayback.FadeIn(CurrentSong.Medley.FadeIn_time, 1.0) + else + AudioPlayback.Play(); + + + // start timer + CountSkipTimeSet; +end; + +procedure TScreenSing.SongError(); +var + I, len: integer; + +begin + if ScreenSong.Mode <> smMedley then + begin + // error loading song -> go back to previous screen and show some error message + Display.AbortScreenChange; + // select new song in party mode + if ScreenSong.Mode = smPartyMode then + ScreenSong.SelectRandomSong(); + 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 + else + begin + if PlaylistMedley.CurrentMedleySong < PlaylistMedley.NumMedleySongs then + begin + //Error Loading Song in Medley Mode -> skip actual Medley Song an go on if possible + len := Length(PlaylistMedley.Song); + for I := PlaylistMedley.CurrentMedleySong-1 to len - 1 do + PlaylistMedley.Song[I] := PlaylistMedley.Song[I+1]; + + SetLength(PlaylistMedley.Song, Len-1); + Dec(PlaylistMedley.NumMedleySongs); + LoadNextSong; + Exit; + end + else + begin + if PlaylistMedley.NumMedleySongs = 1 then + begin + //Error Loading Song in Medley Mode -> Go back to Song Screen and Show some Error Message + Display.AbortScreenChange; + // select new song in party mode + if ScreenSong.Mode = smPartyMode then + ScreenSong.SelectRandomSong(); + 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 + else + begin + //Error Loading Song in Medley Mode -> Finish actual round + len := Length(PlaylistMedley.Song); + SetLength(PlaylistMedley.Song, len-1); + Dec(PlaylistMedley.NumMedleySongs); + Finish; + Exit; + end; + end; + end; +end; + +procedure TScreenSing.LoadNextSong(); +var + Color: TRGB; + Index: integer; + VideoFile: IPath; + BgFile: IPath; + success: boolean; + + function FindNote(beat: integer): TPos; + var + line: integer; + note: integer; + found: boolean; + min: integer; + diff: integer; + + begin + found := false; + + for line := 0 to length(Lines[0].Line) - 1 do + begin + for note := 0 to length(Lines[0].Line[line].Note) - 1 do + begin + if (beat >= Lines[0].Line[line].Note[line].Start) and + (beat <= Lines[0].Line[line].Note[line].Start + Lines[0].Line[line].Note[note].Length) then + begin + Result.part := 0; + Result.line := line; + Result.note := note; + found:=true; + break; + end; + end; + end; + + if found then //found exactly + exit; + + min := high(integer); + //second try (approximating) + for line := 0 to length(Lines[0].Line) - 1 do + begin + for note := 0 to length(Lines[0].Line[line].Note) - 1 do + begin + diff := abs(Lines[0].Line[line].Note[note].Start - beat); + if diff < min then + begin + Result.part := 0; + Result.line := line; + Result.note := note; + min := diff; + end; + end; + end; + end; + +begin + // reset video playback engine + fCurrentVideo := nil; + + // setup score manager + Scores.ClearPlayers; // clear old player values + Color.R := 0; + Color.G := 0; + Color.B := 0; // dummy atm <- \(O.o)/? B like bummy? + + // add new players + for Index := 0 to PlayersPlay - 1 do + begin + Scores.AddPlayer(Tex_ScoreBG[Index], Color); + end; + + Scores.Init; // get positions for players // FIXME: sets path and filename to '' ResetSingTemp; + PlaylistMedley.ApplausePlayed := false; + + if ScreenSong.Mode = smMedley then + begin + if length(PlaylistMedley.Song) >= PlaylistMedley.CurrentMedleySong then + begin + CatSongs.Selected := PlaylistMedley.Song[PlaylistMedley.CurrentMedleySong-1]; + //Music.Open(CatSongs.Song[CatSongs.Selected].Path + CatSongs.Song[CatSongs.Selected].Mp3); + end + else + begin + SongError; + Exit; + end; + end; + CurrentSong := CatSongs.Song[CatSongs.Selected]; // FIXME: bad style, put the try-except into loadsong() and not here @@ -485,27 +752,40 @@ begin if CurrentSong.FileName.GetExtension.ToUTF8 = '.xml' then success := CurrentSong.AnalyseXML and CurrentSong.LoadXMLSong() else - success := CurrentSong.Analyse and CurrentSong.LoadSong(); + 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 ScreenSong.Mode = smPartyMode then - ScreenSong.SelectRandomSong(); - 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; + SongError(); Exit; end; + // Set up Medley timings + if ScreenSong.Mode = smMedley then + begin + CurrentSong.SetMedleyMode(); + +{ ** ToDo + Text[SongNameText].Text := IntToStr(PlaylistMedley.CurrentMedleySong) + + '/' + IntToStr(PlaylistMedley.NumMedleySongs) + ': ' + + CurrentSong.Artist + ' - ' + CurrentSong.Title; +} + //medley start and end timestamps + StartNote := FindNote(CurrentSong.Medley.StartBeat - round(CurrentSong.BPM[0].BPM*CurrentSong.Medley.FadeIn_time / 60)); + MedleyStart := GetTimeFromBeat(Lines[0].Line[StartNote.line].Note[0].Start); + + //check Medley-Start + if MedleyStart+CurrentSong.Medley.FadeIn_time * 0.5 > GetTimeFromBeat(CurrentSong.Medley.StartBeat) then + MedleyStart := GetTimeFromBeat(CurrentSong.Medley.StartBeat) - CurrentSong.Medley.FadeIn_time; + if MedleyStart < 0 then + MedleyStart := 0; + + MedleyEnd := GetTimeFromBeat(CurrentSong.Medley.EndBeat) + CurrentSong.Medley.FadeOut_time; + end; + {* * == Background == * We have four types of backgrounds: @@ -529,7 +809,10 @@ begin if (fVideoClip <> nil) then begin fShowVisualization := false; - fCurrentVideo.Position := CurrentSong.VideoGAP + CurrentSong.Start; + if ScreenSong.Mode = smMedley then + fCurrentVideo.Position := CurrentSong.VideoGAP + MedleyStart + else + fCurrentVideo.Position := CurrentSong.VideoGAP + CurrentSong.Start; fCurrentVideo.Play; end; end; @@ -572,28 +855,31 @@ begin begin fShowVisualization := true; fCurrentVideo := Visualization.Open(PATH_NONE); - if (fCurrentVideo <> nil) then + 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 - LyricsState.TotalTime := AudioPlayback.Length; - LyricsState.UpdateBeats(); - BadPlayer := AudioInputProcessor.CheckPlayersConfig(PlayersPlay); - if (BadPlayer <> 0) then + if ScreenSong.Mode = smMedley then begin - ScreenPopupError.ShowPopup( - Format(Language.Translate('ERROR_PLAYER_NO_DEVICE_ASSIGNMENT'), - [BadPlayer])); + LyricsState.SetCurrentTime(MedleyStart); + LyricsState.StartTime := CurrentSong.Gap; + LyricsState.TotalTime := MedleyEnd; + end + else + begin + LyricsState.SetCurrentTime(CurrentSong.Start); + LyricsState.StartTime := CurrentSong.Gap; + if CurrentSong.Finish > 0 then + LyricsState.TotalTime := CurrentSong.Finish / 1000 + else + LyricsState.TotalTime := AudioPlayback.Length; end; + LyricsState.UpdateBeats(); + // prepare and start voice-capture AudioInput.CaptureStart; @@ -619,51 +905,6 @@ begin // main text Lyrics.Clear(CurrentSong.BPM[0].BPM, CurrentSong.Resolution); - // set custom options - case Ini.LyricsFont of - 0: // normal fonts - begin - Lyrics.FontStyle := ftNormal; - - Lyrics.LineColor_en.R := Skin_FontR; - Lyrics.LineColor_en.G := Skin_FontG; - Lyrics.LineColor_en.B := Skin_FontB; - Lyrics.LineColor_en.A := 1; - - Lyrics.LineColor_dis.R := 0.4; - Lyrics.LineColor_dis.G := 0.4; - Lyrics.LineColor_dis.B := 0.4; - Lyrics.LineColor_dis.A := 1; - - Lyrics.LineColor_act.R := 0.02; - Lyrics.LineColor_act.G := 0.6; - Lyrics.LineColor_act.B := 0.8; - Lyrics.LineColor_act.A := 1; - end; - 1, 2: // outline fonts - begin - if (Ini.LyricsFont = 1) then - Lyrics.FontStyle := ftOutline1 - else - Lyrics.FontStyle := ftOutline2; - - Lyrics.LineColor_en.R := 0.75; - Lyrics.LineColor_en.G := 0.75; - Lyrics.LineColor_en.B := 1; - Lyrics.LineColor_en.A := 1; - - Lyrics.LineColor_dis.R := 0.8; - Lyrics.LineColor_dis.G := 0.8; - Lyrics.LineColor_dis.B := 0.8; - Lyrics.LineColor_dis.A := 1; - - Lyrics.LineColor_act.R := 0.5; - Lyrics.LineColor_act.G := 0.5; - Lyrics.LineColor_act.B := 1; - Lyrics.LineColor_act.A := 1; - end; - end; // case - // initialize lyrics by filling its queue while (not Lyrics.IsQueueFull) and (Lyrics.LineCounter <= High(Lines[0].Line)) do @@ -671,9 +912,6 @@ begin Lyrics.AddLine(@Lines[0].Line[Lyrics.LineCounter]); end; - // deactivate pause - Paused := false; - // kill all stars not killed yet (goldenstarstwinkle mod) GoldenRec.SentenceChange; @@ -684,51 +922,18 @@ begin if Lines[0].Line[Index].TotalNotes = 0 then Inc(NumEmptySentences); - eSongLoaded.CallHookChain(False); + eSongLoaded.CallHookChain(false); - Log.LogStatus('End', 'OnShow'); -end; - -procedure TScreenSing.onShowFinish; -begin - // hide cursor on singscreen show - Display.SetCursor; - - // prepare music - // Important: AudioPlayback must not be initialized in onShow() as TScreenSong - // uses stops AudioPlayback in onHide() which interferes with TScreenSings onShow. - AudioPlayback.Open(CurrentSong.Path.Append(CurrentSong.Mp3)); - AudioPlayback.SetVolume(1.0); - AudioPlayback.Position := CurrentSong.Start; - - // 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; + if (ScreenSong.Mode = smMedley) and (PlaylistMedley.CurrentMedleySong > 1) then + onShowFinish; end; procedure TScreenSing.ClearSettings; begin - Settings.Finish := False; - Settings.LyricsVisible := True; - Settings.NotesVisible := high(Integer); - Settings.PlayerEnabled := high(Integer); + Settings.Finish := false; + Settings.LyricsVisible := true; + Settings.NotesVisible := high(integer); + Settings.PlayerEnabled := high(integer); end; { applies changes of settings record } @@ -739,13 +944,13 @@ end; procedure TScreenSing.EndSong; begin - Settings.Finish := True; + Settings.Finish := true; end; procedure TScreenSing.OnHide; begin // background texture - if (Tex_Background.TexNum > 0) then + if Tex_Background.TexNum > 0 then begin glDeleteTextures(1, PGLuint(@Tex_Background.TexNum)); Tex_Background.TexNum := 0; @@ -757,15 +962,19 @@ end; function TScreenSing.Draw: boolean; var - DisplayTime: real; - DisplayPrefix: string; - DisplayMin: integer; - DisplaySec: integer; - T: integer; - CurLyricsTime: real; - VideoFrameTime: Extended; - Line: TLyricLine; - LastWord: TLyricWord; + DisplayTime: real; + DisplayPrefix: string; + DisplayMin: integer; + DisplaySec: integer; + T: integer; + CurLyricsTime: real; + TotalTime: real; + VideoFrameTime: extended; + Line: TLyricLine; + LastWord: TLyricWord; + medley_end: boolean; + medley_start_applause: boolean; + begin Background.Draw; @@ -805,19 +1014,28 @@ begin // retrieve current lyrics time, we have to store the value to avoid // that min- and sec-values do not match - CurLyricsTime := LyricsState.GetCurrentTime(); + if ScreenSong.Mode = smMedley then + begin + CurLyricsTime := LyricsState.GetCurrentTime() - ScreenSing.MedleyStart; + TotalTime := ScreenSing.MedleyEnd - ScreenSing.MedleyStart; + end + else + begin + CurLyricsTime := LyricsState.GetCurrentTime(); + TotalTime := LyricsState.TotalTime; + end; // retrieve time for timebar text case (fTimebarMode) of tbmRemaining: begin - DisplayTime := LyricsState.TotalTime - CurLyricsTime; + DisplayTime := TotalTime - CurLyricsTime; DisplayPrefix := '-'; end; tbmTotal: begin - DisplayTime := LyricsState.TotalTime; + DisplayTime := TotalTime; DisplayPrefix := '#'; end; - else begin + else begin // current time DisplayTime := CurLyricsTime; DisplayPrefix := ''; end; @@ -838,6 +1056,19 @@ begin SungToEnd := true; end; + // for medley-mode: + CurLyricsTime := LyricsState.GetCurrentTime(); + if (ScreenSong.Mode = smMedley) and (CurLyricsTime > MedleyEnd) then + medley_end := true + else + medley_end := false; + + if (ScreenSong.Mode = smMedley) and (CurLyricsTime > + GetTimeFromBeat(CurrentSong.Medley.EndBeat)) then + medley_start_applause := true + else + medley_start_applause := false; + // update and draw movie if Assigned(fCurrentVideo) then begin @@ -866,28 +1097,35 @@ begin // draw static menu (FG) DrawFG; + //Medley Countdown + if ScreenSong.Mode = smMedley then + DrawMedleyCountdown; + // 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 AudioPlayback.Finished) and - ((CurrentSong.Finish = 0) or - (LyricsState.GetCurrentTime() * 1000 <= CurrentSong.Finish)) and - (not Settings.Finish) then + if (not AudioPlayback.Finished) and (not medley_end or (ScreenSong.Mode <> smMedley)) and + ((CurrentSong.Finish = 0) or (LyricsState.GetCurrentTime()*1000 <= CurrentSong.Finish)) and + (not Settings.Finish) then begin // analyze song if not paused if (not Paused) then begin Sing(Self); + + //Update Medley Stats + if (ScreenSong.Mode = smMedley) and not FadeOut then + UpdateMedleyStats(medley_start_applause); + Party.CallOnSing; end; end else begin - if (not FadeOut) and (Screens=1) or (ScreenAct=2) then + if (not FadeOut) and (Screens = 1) or (ScreenAct = 2) then begin Finish; - FadeOut := true; end; end; end; @@ -915,6 +1153,10 @@ begin end; procedure TScreenSing.Finish; +var + I, J: integer; + len, num: integer; + begin AudioInput.CaptureStop; AudioPlayback.Stop; @@ -944,34 +1186,134 @@ begin SetFontItalic(false); - if not FadeOut then - Party.CallAfterSing; + if ScreenSong.Mode = smMedley then + begin + if not FadeOut then + begin + for I := 0 to PlayersPlay - 1 do + PlaylistMedley.Stats[Length(PlaylistMedley.Stats) - 1].Player[I] := Player[I]; + + Inc(PlaylistMedley.CurrentMedleySong); + if PlaylistMedley.CurrentMedleySong <= PlaylistMedley.NumMedleySongs then + begin + LoadNextSong; + end + else + begin + //build sums + len := Length(PlaylistMedley.Stats); + num := PlaylistMedley.NumPlayer; + + SetLength(PlaylistMedley.Stats, len + 1); + SetLength(PlaylistMedley.Stats[len].Player, num); + + for J := 0 to len - 1 do + begin + for I := 0 to num - 1 do + begin + PlaylistMedley.Stats[len].Player[I].Score := + PlaylistMedley.Stats[len].Player[I].Score + + PlaylistMedley.Stats[J].Player[I].Score; + + PlaylistMedley.Stats[len].Player[I].ScoreLine := + PlaylistMedley.Stats[len].Player[I].ScoreLine + + PlaylistMedley.Stats[J].Player[I].ScoreLine; + + PlaylistMedley.Stats[len].Player[I].ScoreGolden := + PlaylistMedley.Stats[len].Player[I].ScoreGolden + + PlaylistMedley.Stats[J].Player[I].ScoreGolden; + + PlaylistMedley.Stats[len].Player[I].ScoreInt := + PlaylistMedley.Stats[len].Player[I].ScoreInt + + PlaylistMedley.Stats[J].Player[I].ScoreInt; + + PlaylistMedley.Stats[len].Player[I].ScoreLineInt := + PlaylistMedley.Stats[len].Player[I].ScoreLineInt + + PlaylistMedley.Stats[J].Player[I].ScoreLineInt; + + PlaylistMedley.Stats[len].Player[I].ScoreGoldenInt := + PlaylistMedley.Stats[len].Player[I].ScoreGoldenInt + + PlaylistMedley.Stats[J].Player[I].ScoreGoldenInt; + + PlaylistMedley.Stats[len].Player[I].ScoreTotalInt := + PlaylistMedley.Stats[len].Player[I].ScoreTotalInt + + PlaylistMedley.Stats[J].Player[I].ScoreTotalInt; + end; //of for I + end; //of for J + + //build mean on sum + for I := 0 to num - 1 do + begin + PlaylistMedley.Stats[len].Player[I].Score := round( + PlaylistMedley.Stats[len].Player[I].Score / len); + + PlaylistMedley.Stats[len].Player[I].ScoreLine := round( + PlaylistMedley.Stats[len].Player[I].ScoreLine / len); + + PlaylistMedley.Stats[len].Player[I].ScoreGolden := round( + PlaylistMedley.Stats[len].Player[I].ScoreGolden / len); + + PlaylistMedley.Stats[len].Player[I].ScoreInt := round( + PlaylistMedley.Stats[len].Player[I].ScoreInt / len); + + PlaylistMedley.Stats[len].Player[I].ScoreLineInt := round( + PlaylistMedley.Stats[len].Player[I].ScoreLineInt / len); + + PlaylistMedley.Stats[len].Player[I].ScoreGoldenInt := round( + PlaylistMedley.Stats[len].Player[I].ScoreGoldenInt / len); + + PlaylistMedley.Stats[len].Player[I].ScoreTotalInt := round( + PlaylistMedley.Stats[len].Player[I].ScoreTotalInt / len); + end; + + Party.CallAfterSing; + FadeOut:=true; + end; + end; + end + else + begin + SetLength(PlaylistMedley.Stats, 1); + SetLength(PlaylistMedley.Stats[0].Player, PlayersPlay); + for I := 0 to PlayersPlay - 1 do + PlaylistMedley.Stats[0].Player[I] := Player[I]; + + PlaylistMedley.Stats[0].SongArtist := CurrentSong.Artist; + PlaylistMedley.Stats[0].SongTitle := CurrentSong.Title; + + if not FadeOut then + Party.CallAfterSing; + + FadeOut := true; + end; end; procedure TScreenSing.OnSentenceEnd(SentenceIndex: cardinal); var - PlayerIndex: byte; - CurrentPlayer: PPLayer; - CurrentScore: real; - Line: PLine; + 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 + 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 + if Line.TotalNotes <= 0 then Exit; // set max song score - if (Ini.LineBonus = 0) then + if Ini.LineBonus = 0 then MaxSongScore := MAX_SONG_SCORE else MaxSongScore := MAX_SONG_SCORE - MAX_SONG_LINE_BONUS; @@ -990,7 +1332,7 @@ begin LineScore := CurrentScore - CurrentPlayer.ScoreLast; // check for lines with low points - if (MaxLineScore <= 2) then + if MaxLineScore <= 2 then LinePerfection := 1 else // determine LinePerfection @@ -999,13 +1341,13 @@ begin LinePerfection := LineScore / (MaxLineScore - 2); // clamp LinePerfection to range [0..1] - if (LinePerfection < 0) then + if LinePerfection < 0 then LinePerfection := 0 - else if (LinePerfection > 1) then + else if LinePerfection > 1 then LinePerfection := 1; // add line-bonus if enabled - if (Ini.LineBonus > 0) then + if Ini.LineBonus > 0 then begin // line-bonus points (same for each line, no matter how long the line is) LineBonus := MAX_SONG_LINE_BONUS / (Length(Lines[0].Line) - @@ -1028,7 +1370,7 @@ begin Scores.RaiseScore(PlayerIndex, CurrentPlayer.ScoreTotalInt); // PerfectLineTwinkle (effect), part 1 - if (Ini.EffectSing = 1) then + if Ini.EffectSing = 1 then CurrentPlayer.LastSentencePerfect := (LinePerfection >= 1); // refresh last score @@ -1036,7 +1378,7 @@ begin end; // PerfectLineTwinkle (effect), part 2 - if (Ini.EffectSing = 1) then + if Ini.EffectSing = 1 then GoldenRec.SpawnPerfectLineTwinkle; end; @@ -1052,7 +1394,7 @@ begin (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 + if Lyrics.LineCounter <= High(Lines[0].Line) then Lyrics.AddLine(@Lines[0].Line[Lyrics.LineCounter]) else Lyrics.AddLine(nil); @@ -1069,5 +1411,60 @@ begin Result := AudioPlayback.Position; end; -end. +procedure TScreenSing.UpdateMedleyStats(medley_end: boolean); +var + len, num, I: integer; + +begin + len := Length(PlaylistMedley.Stats); + num := PlaylistMedley.NumPlayer; + + if (PlaylistMedley.CurrentMedleySong > len) and + (PlaylistMedley.CurrentMedleySong <= PlaylistMedley.NumMedleySongs) then + begin + inc(len); + SetLength(PlaylistMedley.Stats, len); + SetLength(PlaylistMedley.Stats[len - 1].Player, num); + PlaylistMedley.Stats[len-1].SongArtist := CurrentSong.Artist; + PlaylistMedley.Stats[len-1].SongTitle := CurrentSong.Title; + end; + + if PlaylistMedley.CurrentMedleySong <= PlaylistMedley.NumMedleySongs then + for I := 0 to num - 1 do + PlaylistMedley.Stats[len - 1].Player[I] := Player[I]; + + if medley_end and not PlaylistMedley.ApplausePlayed and + (PlaylistMedley.CurrentMedleySong <= PlaylistMedley.NumMedleySongs) then + begin + PlaylistMedley.ApplausePlayed := true; + end; +end; +procedure TScreenSing.DrawMedleyCountdown(); +var + w, h: real; + timeDiff: real; + t: real; + CountDownText: UTF8String; + +begin + if AudioPlayback.Position < GetTimeFromBeat(CurrentSong.Medley.StartBeat) then + begin + timeDiff := GetTimeFromBeat(CurrentSong.Medley.StartBeat)-AudioPlayback.Position+1; + t := frac(timeDiff); + + glColor4f(0.15, 0.30, 0.6, t); + + h := 300 * t * ScreenH / RenderH; + SetFontStyle(ftBold); + SetFontItalic(false); + SetFontSize(h); + CountDownText := IntToStr(round(timeDiff - t)); + w := glTextWidth(PChar(CountDownText)); + + SetFontPos (RenderW / 2 - w / 2, RenderH / 2 - h / 2); + glPrint(PChar(CountDownText)); + end; +end; + +end. diff --git a/src/screens/UScreenSong.pas b/src/screens/UScreenSong.pas index 6fe8d204..7558a59b 100644 --- a/src/screens/UScreenSong.pas +++ b/src/screens/UScreenSong.pas @@ -38,7 +38,6 @@ uses SDL, UCommon, UDisplay, - UPath, UFiles, UIni, ULanguage, @@ -46,6 +45,7 @@ uses UMenu, UMenuEqualizer, UMusic, + UPath, USong, USongs, UTexture, @@ -53,6 +53,8 @@ uses UTime; type + TVisArr = array of integer; + TScreenSong = class(TMenu) private Equalizer: Tms_Equalizer; @@ -161,6 +163,10 @@ type //Extensions procedure DrawExtensions; + + //Medley + procedure StartMedley(NumSongs: integer; MinSource: TMedleySource); + function getVisibleMedleyArr(MinSource: TMedleySource): TVisArr; end; implementation @@ -188,7 +194,7 @@ var begin if CatSongs.VisibleSongs > 0 then begin - I2:= 0; + I2 := 0; for I := Low(CatSongs.Song) to High(Catsongs.Song) do begin if CatSongs.Song[I].Visible then @@ -209,7 +215,7 @@ var begin if CatSongs.VisibleSongs > 0 then begin - I2:= 0; + I2 := 0; for I := Low(CatSongs.Song) to High(Catsongs.Song) do begin if CatSongs.Song[I].Visible then @@ -260,9 +266,9 @@ function TScreenSong.ParseInput(PressedKey: cardinal; CharCode: UCS4Char; Presse var I: integer; I2: integer; - SDL_ModState: word; - UpperLetter: UCS4Char; - TempStr: UTF8String; + SDL_ModState: word; + UpperLetter: UCS4Char; + TempStr: UTF8String; begin Result := true; @@ -417,6 +423,39 @@ begin Exit; end; + Ord('S'): + begin + Log.LogError('SDL_ModState: ' + inttostr(SDL_ModState)); + Log.LogError('KMOD_LSHIFT: ' + inttostr(KMOD_LSHIFT)); + Log.LogError('CatSongs.Song[Interaction].Medley.Source: ' + inttostr(ord(CatSongs.Song[Interaction].Medley.Source))); + Log.LogError('msCalculated: ' + inttostr(ord(msCalculated))); + Log.LogError('msTag: ' + inttostr(ord(msTag))); + Log.LogError('Mode: ' + inttostr(ord(Mode))); + Log.LogError('smNormal: ' + inttostr(ord(smNormal))); + if //(SDL_ModState = KMOD_LSHIFT) and + (CatSongs.Song[Interaction].Medley.Source >= msCalculated) and (Mode = smNormal) then + StartMedley(0, msCalculated) + else if (CatSongs.Song[Interaction].Medley.Source >= msTag) and (Mode = smNormal) then + StartMedley(0, msTag); + end; + + Ord('D'): + begin + Log.LogError('SDL_ModState: ' + inttostr(SDL_ModState)); + Log.LogError('KMOD_LSHIFT: ' + inttostr(KMOD_LSHIFT)); + Log.LogError('length(getVisibleMedleyArr(msCalculated)): ' + inttostr(length(getVisibleMedleyArr(msCalculated)))); + Log.LogError('msCalculated: ' + inttostr(ord(msCalculated))); + Log.LogError('msTag: ' + inttostr(ord(msTag))); + Log.LogError('Length(getVisibleMedleyArr(msTag)): ' + inttostr(Length(getVisibleMedleyArr(msTag)))); + Log.LogError('smNormal: ' + inttostr(ord(smNormal))); + Log.LogError('Mode: ' + inttostr(ord(Mode))); + if (Mode = smNormal) and //(SDL_ModState = KMOD_LSHIFT) and + (length(getVisibleMedleyArr(msCalculated)) > 0) then + StartMedley(5, msCalculated) + else if (Mode = smNormal) and (Length(getVisibleMedleyArr(msTag)) > 0) then + StartMedley(5, msTag); + end; + Ord('R'): begin if (Songs.SongList.Count > 0) and @@ -1518,8 +1557,15 @@ begin // reset video playback engine fCurrentVideo := nil; - if Ini.Players <= 3 then PlayersPlay := Ini.Players + 1; - if Ini.Players = 4 then PlayersPlay := 6; + // reset Medley-Playlist + SetLength(PlaylistMedley.Song, 0); + if Mode = smMedley then + Mode := smNormal; + + if Ini.Players <= 3 then + PlayersPlay := Ini.Players + 1; + if Ini.Players = 4 then + PlayersPlay := 6; //Cat Mod etc if (Ini.TabsAtStartup = 1) and (CatSongs.CatNumShow = -1) then @@ -1536,21 +1582,21 @@ begin end; //Playlist Mode - if (Mode = smNormal) then + if Mode = smNormal then begin //If Playlist Shown -> Select Next automatically - if (CatSongs.CatNumShow = -3) then + if CatSongs.CatNumShow = -3 then begin SelectNext; end; end //Party Mode - else if (Mode = smPartyMode) then + else if Mode = smPartyMode then begin SelectRandomSong; //Show Menu directly in PartyMode //But only if selected in Options - if (Ini.PartyPopup = 1) then + if Ini.PartyPopup = 1 then begin ScreenSongMenu.MenuShow(SM_Party_Main); end; @@ -1810,14 +1856,18 @@ begin begin PreviewOpened := Interaction; - PreviewPos := AudioPlayback.Length / 4; + if Song.PreviewStart > 0 then + PreviewPos := Song.PreviewStart + else + PreviewPos := AudioPlayback.Length / 4; + // fix for invalid music file lengths - if (PreviewPos > 60.0) then + if (PreviewPos > 60.0) and (Song.PreviewStart = 0) then PreviewPos := 60.0; AudioPlayback.Position := PreviewPos; // set preview volume - if (Ini.PreviewFading = 0) then + if Ini.PreviewFading = 0 then begin // music fade disabled: start with full volume AudioPlayback.SetVolume(IPreviewVolumeVals[Ini.PreviewVolume]); @@ -2153,4 +2203,131 @@ begin } end; +procedure TScreenSong.StartMedley(NumSongs: integer; MinSource: TMedleySource); + procedure AddSong(SongNr: integer); + begin + SetLength(PlaylistMedley.Song, Length(PlaylistMedley.Song) + 1); + PlaylistMedley.Song[Length(PlaylistMedley.Song) - 1] := SongNr; + end; + + function SongAdded(SongNr: integer): boolean; + var + i: integer; + skipped: boolean; + begin + skipped := false; + for i := 0 to Length(PlaylistMedley.Song) - 1 do + begin + if SongNr = PlaylistMedley.Song[i] then + begin + skipped := true; + break; + end; + end; + Result := skipped; + end; + + function NumSongsAdded(): Integer; + begin + Result := Length(PlaylistMedley.Song); + end; + + function GetNextSongNr(MinS: TMedleySource): integer; + var + I, num: integer; + unused_arr: array of integer; + visible_arr: TVisArr; + begin + SetLength(unused_arr, 0); + visible_arr := getVisibleMedleyArr(MinS); + for I := 0 to Length(visible_arr) - 1 do + begin + if (not SongAdded(visible_arr[I])) then + begin + SetLength(unused_arr, Length(unused_arr)+1); + unused_arr[Length(unused_arr) - 1] := visible_arr[I]; + end; + end; + + num := Random(Length(unused_arr)); + Result := unused_arr[num]; +end; + +var + I: integer; + VS: integer; + +begin + if NumSongs > 0 then + begin + VS := Length(getVisibleMedleyArr(MinSource)); + if VS < NumSongs then + PlaylistMedley.NumMedleySongs := VS + else + PlaylistMedley.NumMedleySongs := NumSongs; + + //set up Playlist Medley + SetLength(PlaylistMedley.Song, 0); + for I := 0 to PlaylistMedley.NumMedleySongs - 1 do + begin + AddSong(GetNextSongNr(MinSource)); + end; + end + else //start this song + begin + SetLength(PlaylistMedley.Song, 1); + PlaylistMedley.Song[0] := Interaction; + PlaylistMedley.NumMedleySongs := 1; + end; + + if Mode = smNormal then + begin + Mode := smMedley; + StopMusicPreview(); + + //TODO: how about case 2? menu for medley mode? + case Ini.OnSongClick of + 0: FadeTo(@ScreenSing); + 1: SelectPlayers; + 2: FadeTo(@ScreenSing); + {2: begin + if CatSongs.CatNumShow = -3 then + ScreenSongMenu.MenuShow(SM_Playlist) + else + ScreenSongMenu.MenuShow(SM_Main); + end;} + end; + end; +end; + +function TScreenSong.getVisibleMedleyArr(MinSource: TMedleySource): TVisArr; +var + I: integer; + +begin + SetLength(Result, 0); + if CatSongs.Song[Interaction].Main then + begin + for I := 0 to Length(CatSongs.Song) - 1 do + begin + if not CatSongs.Song[I].Main and (CatSongs.Song[I].Medley.Source >= MinSource) then + begin + SetLength(Result, Length(Result) + 1); + Result[Length(Result) - 1] := I; + end; + end; + end + else + begin + for I := 0 to Length(CatSongs.Song) - 1 do + begin + if CatSongs.Song[I].Visible and (CatSongs.Song[I].Medley.Source >= MinSource) then + begin + SetLength(Result, Length(Result) + 1); + Result[Length(Result)-1] := I; + end; + end; + end; +end; + end. |