From 16ecad36bd1fd998167d79f5b07221305cf64f4c Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Fri, 22 Feb 2013 21:25:08 +0000 Subject: merge the first part of the medley_new branch. This part has the functionality and most GUI changes. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2943 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/base/UDraw.pas | 18 ++- src/base/UNote.pas | 44 ++++--- src/base/USong.pas | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 362 insertions(+), 30 deletions(-) (limited to 'src/base') 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; -- cgit v1.2.3