aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--medley_new/src/base/UNote.pas19
-rw-r--r--medley_new/src/base/USong.pas306
-rw-r--r--medley_new/src/screens/UScreenSong.pas141
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.