From a1db18bc7cce48620534c2d6a0f9589d1dd8a95e Mon Sep 17 00:00:00 2001 From: brunzelchen Date: Fri, 15 Oct 2010 13:20:11 +0000 Subject: - added medley loading - added previewstart tag, fallback to medleystart and 1/4-length-rule todo: choosing medley in screenscore, adjust screensing, screenscore and themes git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2667 b956fd51-792f-4845-bead-9b4dfca2ff2c --- medley_new/src/base/UNote.pas | 19 +- medley_new/src/base/USong.pas | 306 ++++++++++++++++++++++++++++++++- medley_new/src/screens/UScreenSong.pas | 141 ++++++++++++++- 3 files changed, 461 insertions(+), 5 deletions(-) diff --git a/medley_new/src/base/UNote.pas b/medley_new/src/base/UNote.pas index ff9c6b57..bc22ac62 100644 --- a/medley_new/src/base/UNote.pas +++ b/medley_new/src/base/UNote.pas @@ -37,7 +37,6 @@ uses SysUtils, Classes, SDL, - UMusic, URecord, UTime, UDisplay, @@ -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 @@ -133,6 +149,7 @@ uses UCovers, UCatCovers, UDataBase, + UMusic, UPlaylist, UParty, UConfig, diff --git a/medley_new/src/base/USong.pas b/medley_new/src/base/USong.pas index e92c5b45..a736b4ae 100644 --- a/medley_new/src/base/USong.pas +++ b/medley_new/src/base/USong.pas @@ -64,7 +64,17 @@ uses 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; @@ -138,6 +149,9 @@ type GAP: real; // in miliseconds 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; @@ -166,6 +180,7 @@ type function LoadXMLSong: boolean; function Analyse(const ReadCustomTags: Boolean = false): boolean; function AnalyseXML(): boolean; + procedure SetMedleyMode(); procedure Clear(); end; @@ -179,6 +194,10 @@ uses UMusic, //needed for Lines UNote; //needed for Player +const + DEFAULT_FADE_IN_TIME = 8; // for medley fade-in + DEFAULT_FADE_OUT_TIME = 2; // for medley fade-out + constructor TSong.Create(); begin inherited; @@ -468,6 +487,7 @@ begin Exit; end; + SetLength(Lines, 0); SetLength(Lines, 2); for Count := 0 to High(Lines) do begin @@ -867,6 +887,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 +908,7 @@ var begin Result := true; Done := 0; + MedleyFlags := 0; FullFileName := Path.Append(Filename).ToNative; @@ -1090,6 +1112,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 @@ -1124,6 +1175,32 @@ 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 +1300,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 +1549,9 @@ begin NotesGAP := 0; Resolution := 4; Creator := ''; + PreviewStart := 0; + CalcMedley := false; + Medley.Source := msNone; Relative := false; end; @@ -1277,7 +1572,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/medley_new/src/screens/UScreenSong.pas b/medley_new/src/screens/UScreenSong.pas index 6fe8d204..9b741788 100644 --- a/medley_new/src/screens/UScreenSong.pas +++ b/medley_new/src/screens/UScreenSong.pas @@ -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 @@ -1518,6 +1524,9 @@ begin // reset video playback engine fCurrentVideo := nil; + // reset Medley-Playlist + SetLength(PlaylistMedley.Song, 0); + if Ini.Players <= 3 then PlayersPlay := Ini.Players + 1; if Ini.Players = 4 then PlayersPlay := 6; @@ -1809,10 +1818,13 @@ begin if AudioPlayback.Open(Song.Path.Append(Song.Mp3)) then begin PreviewOpened := Interaction; + if (Song.PreviewStart>0) then + PreviewPos := Song.PreviewStart + else + PreviewPos := AudioPlayback.Length / 4; - 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; @@ -2153,4 +2165,129 @@ 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. -- cgit v1.2.3