unit UFiles;
interface
uses USongs,
SysUtils,
StrUtils,
ULog,
UMusic;
const
DEFAULT_FADE_IN_TIME = 8; //TODO in INI
DEFAULT_FADE_OUT_TIME = 2;
//from USDX 1.1:
UTF8_BOM: UTF8String = #$EF#$BB#$BF;
//
type
// from USDX 1.1:
//
// String with unknown encoding. Introduced with Delphi 2009 and maybe soon
// with FPC.
RawByteString = AnsiString;
//
procedure InitializePaths; //Function sets All Absolute Paths eg. for Songs
function ReadTXTHeader(var Song: TSong): boolean; //Reads Standard TXT Header
function AnalyseFile(var Song: TSong): boolean; //Analyse Song File and Read Header
procedure ClearSong(var Song: TSong); //Clears Song Header values
//Methodes Loading and Saving Songfiles
procedure ResetSingTemp;
procedure ParseNote(NrCzesci: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: string);
procedure NewSentence(NrCzesciP: integer; Param1, Param2: integer; LoadFullFile: boolean);
function LoadSong(Name: string; LoadFullFile: boolean): boolean;
function CheckSong: boolean;
function SaveSong(Song: TSong; Czesc: array of TCzesci; Name: string; Relative: boolean): boolean;
procedure FindRefrainStart(var Song: TSong);
procedure SetMedleyMode;
var
//Absolute Paths
GamePath: string;
SoundPath: string;
SongPaths: array of string;
LogPath: string;
ThemePath: string;
ScreenshotsPath: string;
CoversPath: string;
LanguagesPath: string;
PluginPath: string;
PlayListPath: string;
RecordingsPath: string;
SessionLogPath: string;
FontPath: string;
SongFile: TextFile; // all procedures in this unit operates on this file
FileLineNo: integer; //Line which is readed at Last, for error reporting
// variables available for all procedures
Base: array[0..1] of integer;
Rel: array[0..1] of integer;
Mult: integer = 1;
MultBPM: integer = 4;
CheckOK: boolean;
implementation
uses TextGL, UIni, UMain;
//--------------------
// Function sets all Absolute Paths e.g. Song Path and makes sure the Directorys exist
//--------------------
procedure InitializePaths;
var
Writeable: Boolean;
begin
GamePath := ExtractFilePath(ParamStr(0));
SoundPath := GamePath + 'Sounds\';
SetLength(SongPaths, 1);
SongPaths[0] := GamePath + 'Songs\';
LogPath := GamePath;
ThemePath := GamePath + 'Themes\';
ScreenshotsPath := GamePath + 'Screenshots\';
CoversPath := GamePath + 'Covers\';
LanguagesPath := GamePath + 'Languages\';
PluginPath := GamePath + 'Plugins\';
PlaylistPath := GamePath + 'Playlists\';
RecordingsPath := GamePath + 'Recordings\';
SessionLogPath := GamePath + 'SessionLog\';
FontPath := GamePath + 'Fonts\';
Writeable := true;
//After Setting Paths, make sure that Paths exist
If not DirectoryExists(SoundPath) then
Writeable := ForceDirectories(SoundPath);
If Writeable And (not DirectoryExists(SongPaths[0])) then
Writeable := ForceDirectories(SongPaths[0]);
If Writeable And (not DirectoryExists(ThemePath)) then
Writeable := ForceDirectories(ThemePath);
If Writeable And (not DirectoryExists(ScreenshotsPath)) then
Writeable := ForceDirectories(ScreenshotsPath);
If Writeable And (not DirectoryExists(CoversPath)) then
Writeable := ForceDirectories(CoversPath);
If Writeable And (not DirectoryExists(LanguagesPath)) then
Writeable := ForceDirectories(LanguagesPath);
If Writeable And (not DirectoryExists(PluginPath)) then
Writeable := ForceDirectories(PluginPath);
If Writeable And (not DirectoryExists(PlaylistPath)) then
Writeable := ForceDirectories(PlaylistPath);
If Writeable And (not DirectoryExists(RecordingsPath)) then
Writeable := ForceDirectories(RecordingsPath);
If Writeable And (not DirectoryExists(SessionLogPath)) then
Writeable := ForceDirectories(SessionLogPath);
If Writeable And (not DirectoryExists(FontPath)) then
Writeable := ForceDirectories(FontPath);
if not Writeable then
Log.LogError('Error: Dir is Readonly');
DecimalSeparator := ',';
end;
//--------------------
// Clears Song Header values
//--------------------
procedure ClearSong(var Song: TSong);
begin
//Main Information
Song.Title := '';
Song.Artist := '';
//Sortings:
SetLength(Song.Genre, 1);
Song.Genre[0] := 'Unknown';
SetLength(Song.Edition, 1);
Song.Edition[0] := 'Unknown';
Song.Language := 'Unknown'; //Language Patch
Song.Year := -1;
//Required Information
Song.Mp3 := '';
SetLength(Song.BPM, 0);
Song.GAP := 0;
Song.Start := 0;
Song.Finish := 0;
Song.Relative := false;
Song.isDuet := false;
//Additional Information
Song.Background := '';
Song.Cover := '';
Song.CoverTex.TexNum := -1;
Song.Video := '';
Song.VideoGAP := 0;
Song.NotesGAP := 0;
Song.Resolution := 4;
Song.Creator := '';
Song.Medley.Source:=msNone;
Song.CalcMedley := true;
Song.PreviewStart := 0;
SetLength(Song.CustomTags, 0);
SetLength(Song.DuetNames, 2);
Song.DuetNames[0] := 'P1';
Song.DuetNames[1] := 'P2';
end;
//--------------------
// Reads Standard TXT Header
//--------------------
function ReadTXTHeader(var Song: TSong): boolean;
var
Line, Identifier, Value: String;
Temp: word;
Done: byte;
MedleyFlags: byte; //bit-vector for medley/preview tags
lWarnIfTagsNotFound : Boolean;
Len: integer;
{ adds a custom header tag to the song
if there is no ':' in the read line, Tag should be empty
and the whole line should be in Content } //from usdx 1.1
procedure AddCustomTag(const Tag, Content: String);
var Len: Integer;
begin
Len := Length(Song.CustomTags);
SetLength(Song.CustomTags, Len + 1);
Song.CustomTags[Len].Tag := Tag;
Song.CustomTags[Len].Content := Content;
end;
function CheckReplaceUTF8BOM(var Text: RawByteString): boolean;
begin
if AnsiStartsStr(UTF8_BOM, Text) then
begin
Text := Copy(Text, Length(UTF8_BOM)+1, Length(Text)-Length(UTF8_BOM));
Result := true;
Exit;
end;
Result := false;
end;
begin
Result := true;
Done := 0;
MedleyFlags := 0;
lWarnIfTagsNotFound := ( lowercase( Song.Filename ) <> 'license.txt' ) AND
( lowercase( Song.Filename ) <> 'readme.txt' ) ;
//Read first Line
ReadLn (SongFile, Line);
if CheckReplaceUTF8BOM(Line) then
begin
Log.LogError('File is encoded in UTF8 (not supported in this version): ' + Song.Path + Song.FileName);
Result := False;
Exit;
end;
if (Length(Line)<=0) then
begin
Log.LogError('File Starts with Empty Line: ' + Song.Path + Song.FileName);
Result := False;
Exit;
end;
//Read Lines while Line starts with #
While (Length(Line) = 0) OR (Line[1] = '#') do
begin
//Increase Line Number
Inc (FileLineNo);
Temp := Pos(':', Line);
//Line has a Seperator-> Headerline
if (Temp <> 0) then
begin
//Read Identifier and Value
Identifier := Uppercase(Trim(Copy(Line, 2, Temp - 2))); //Uppercase is for Case Insensitive Checks
Value := Trim(Copy(Line, Temp + 1,Length(Line) - Temp));
//Check the Identifier (If Value is given)
if (Length(Value) <> 0) then
begin
//-----------
//Required Attributes
//-----------
//Title
if (Identifier = 'TITLE') then
begin
Song.Title := Value;
//Add Title Flag to Done
Done := Done or 1;
end
//Artist
else if (Identifier = 'ARTIST') then
begin
Song.Artist := Value;
//Add Artist Flag to Done
Done := Done or 2;
end
//MP3 File //Test if Exists
else if (Identifier = 'MP3') AND (FileExists(Song.Path + Value)) then
begin
Song.Mp3 := Value;
//Add Mp3 Flag to Done
Done := Done or 4;
end
//Beats per Minute
else if (Identifier = 'BPM') then
begin
// Replace . with ,
if (Pos('.', Value) <> 0) then
Value[Pos('.', Value)] := ',';
SetLength(Song.BPM, 1);
Song.BPM[0].StartBeat := 0;
Song.BPM[0].BPM := StrtoFloatDef(Value, 0) * Mult * MultBPM;
if Song.BPM[0].BPM <> 0 then
begin
//Add BPM Flag to Done
Done := Done or 8;
end;
end
//---------
//Additional Header Information
//---------
// Video Gap
else if (Identifier = 'GAP') then
begin
// Replace . with ,
if (Pos('.', Value) <> 0) then
Value[Pos('.', Value)] := ',';
Song.GAP := StrtoFloatDef (Value, 0);
end
//Cover Picture
else if (Identifier = 'COVER') then
begin
if (FileExists(Song.Path + Value)) then
Song.Cover := Value
else
Log.LogError('Can''t find Cover File in Song: ' + Song.Path + Song.FileName);
end
//Background Picture
else if (Identifier = 'BACKGROUND') then
begin
if (FileExists(Song.Path + Value)) then
Song.Background := Value
else
Log.LogError('Can''t find Background File in Song: ' + Song.Path + Song.FileName);
end
// Video File
else if (Identifier = 'VIDEO') then
begin
if (FileExists(Song.Path + Value)) then
Song.Video := Value
else
Log.LogError('Can''t find Video File in Song: ' + Song.Path + Song.FileName);
end
// Video Gap
else if (Identifier = 'VIDEOGAP') then
begin
// Replace . with ,
if (Pos('.', Value) <> 0) then
Value[Pos('.', Value)] := ',';
Song.VideoGAP := StrtoFloatDef (Value, 0);
end
//Genre Sorting
else if (Identifier = 'GENRE') then
begin
Len := Length(Song.Genre);
if (Song.Genre[0] <> 'Unknown') then
begin
Inc(Len);
SetLength(Song.Genre, Len);
end;
Song.Genre[Len-1] := Value;
end
//Edition Sorting
else if (Identifier = 'EDITION') then
begin
Len := Length(Song.Edition);
if (Song.Edition[0] <> 'Unknown') then
begin
Inc(Len);
SetLength(Song.Edition, Len);
end;
Song.Edition[Len-1] := Value;
end
//Creator Tag
else if (Identifier = 'CREATOR') then
begin
Song.Creator := Value;
end
//Language Sorting
else if (Identifier = 'LANGUAGE') then
begin
Song.Language := Value;
end
//Year Sorting
else if (Identifier = 'YEAR') then
begin
Len := -1;
if (Length(Value)<>4) then
Log.LogError('YEAR-tag does not has 4 Digits in Song: ' + Song.Path + Song.FileName)
else if (not TryStrtoInt(Value, Len)) then
Log.LogError('YEAR-tag is not a Number in Song: ' + Song.Path + Song.FileName)
else if (Len<1000) or (Len>9999) then
Log.LogError('YEAR-tag is too low or too high in Song: ' + Song.Path + Song.FileName)
else
Song.Year := Len;
end
// Song Start
else if (Identifier = 'START') then
begin
// Replace . with ,
if (Pos('.', Value) <> 0) then
Value[Pos('.', Value)] := ',';
Song.Start := StrtoFloatDef(Value, 0);
end
// Song Ending
else if (Identifier = 'END') then
begin
TryStrtoInt(Value, Song.Finish);
end
// Resolution
else if (Identifier = 'RESOLUTION') then
begin
TryStrtoInt(Value, Song.Resolution);
end
// Notes Gap
else if (Identifier = 'NOTESGAP') then
begin
TryStrtoInt(Value, Song.NotesGAP);
end
// Relative Notes
else if (Identifier = 'RELATIVE') AND (uppercase(Value) = 'YES') then
begin
Song.Relative := True;
end
// PreviewStart
else if (Identifier = 'PREVIEWSTART') then
begin
Song.PreviewStart := StrToFloatDef(Value, 0);
if (Song.PreviewStart>0) then
MedleyFlags := MedleyFlags or 1;
end
// MedleyStartBeat
else if (Identifier = 'MEDLEYSTARTBEAT') and not Song.Relative then
begin
if TryStrtoInt(Value, Song.Medley.StartBeat) then
MedleyFlags := MedleyFlags or 2;
end
// MedleyEndBeat
else if (Identifier = 'MEDLEYENDBEAT') and not Song.Relative then
begin
if TryStrtoInt(Value, Song.Medley.EndBeat) then
MedleyFlags := MedleyFlags or 4;
end
// Medley
else if (Identifier = 'CALCMEDLEY') then
begin
if (Uppercase(Value) = 'OFF') then
Song.CalcMedley := false;
end
// Duet Singer Name P1
else if (Identifier = 'DUETSINGERP1') then
begin
Song.DuetNames[0] := Value;
end
// Duet Singer Name P2
else if (Identifier = 'DUETSINGERP2') then
begin
Song.DuetNames[1] := Value;
end
// unsupported tag
else
begin
AddCustomTag(Identifier, Value);
end;
end;
end;
if not EOf(SongFile) then
ReadLn (SongFile, Line)
else
begin
Result := False;
if lWarnIfTagsNotFound then
begin
Log.LogError('File Incomplete or not Ultrastar TxT: ' + Song.Path + Song.FileName);
end;
break;
end;
{//End on first empty Line
if (Length(Line) = 0) then
break;}
end;
//Check if all Required Values are given
if (Done <> 15) then
begin
Result := False;
If lWarnIfTagsNotFound then
begin
if (Done and 8) = 0 then //No BPM Flag
Log.LogError('BPM Tag Missing: ' + Song.Path + Song.FileName)
else if (Done and 4) = 0 then //No MP3 Flag
Log.LogError('MP3 Tag/File Missing: ' + Song.Path + Song.FileName)
else if (Done and 2) = 0 then //No Artist Flag
Log.LogError('Artist Tag Missing: ' + Song.Path + Song.FileName)
else if (Done and 1) = 0 then //No Title Flag
Log.LogError('Title Tag Missing: ' + Song.Path + Song.FileName)
else //unknown Error
Log.LogError('File Incomplete or not Ultrastar TxT: ' + Song.Path + Song.FileName);
end;
end else
begin //check medley tags
if (MedleyFlags and 6)=6 then //MedleyStartBeat and MedleyEndBeat are both set
begin
if Song.Medley.StartBeat >= Song.Medley.EndBeat then
MedleyFlags := MedleyFlags - 6;
end;
if ((MedleyFlags and 1)=0) or (Song.PreviewStart<=0) then //PreviewStart is not set or <=0
begin
if (MedleyFlags and 2)=2 then
Song.PreviewStart := GetTimeFromBeat(Song.Medley.StartBeat) //fallback to MedleyStart
else
Song.PreviewStart := 0; //else set it to 0, it will be set in FindRefrainStart
end;
if (MedleyFlags and 6)=6 then
begin
Song.Medley.Source := msTag;
//calculate fade time
Song.Medley.FadeIn_time := DEFAULT_FADE_IN_TIME;
Song.Medley.FadeOut_time := DEFAULT_FADE_OUT_TIME;
end else
Song.Medley.Source := msNone;
end;
end;
//--------------------
// Analyse Song File and Read Header
//--------------------
function AnalyseFile(var Song: TSong): boolean;
begin
//Result := False;
{try }
//Reset LineNo
FileLineNo := 0;
//Open File and set File Pointer to the beginning
AssignFile(SongFile, Song.Path + Song.FileName);
Reset(SongFile);
//Clear old Song Header
ClearSong(Song);
//Read Header
Result := ReadTxTHeader(Song);
//And Close File
CloseFile(SongFile);
{except
CloseFile(SongFile);
Result := False;
//Error Reporting
Log.LogError('An Error occured reading Line ' + inttostr(FileLineNo) + ' from SongHeader: ' + Song.FileName);
end;}
end;
//--------------------
// Resets the temporary Sentence Arrays for each Player and some other Variables
//--------------------
procedure ResetSingTemp;
var
Pet: integer;
begin
SetLength(Czesci, Length(Player));
SetLength(AktSong.BPM, 0);
for Pet := 0 to High(Player) do begin
SetLength(Czesci[Pet].Czesc, 1);
SetLength(Czesci[Pet].Czesc[0].Nuta, 0);
Czesci[Pet].Czesc[0].Lyric := '';
Czesci[Pet].Czesc[0].LyricWidth := 0;
Player[pet].Score := 0;
Player[pet].IlNut := 0;
Player[pet].HighNut := -1;
end;
//Reset Path and Filename Values to Prevent Errors in Editor
AktSong.Path := '';
AktSong.FileName := '';
end;
//--------------------
// Parses Note Infos and save them to Array
//--------------------
procedure ParseNote(NrCzesci: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: string);
begin
case Ini.Solmization of
1: // european
begin
case (NoteP mod 12) of
0..1: LyricS := ' do ';
2..3: LyricS := ' re ';
4: LyricS := ' mi ';
5..6: LyricS := ' fa ';
7..8: LyricS := ' sol ';
9..10: LyricS := ' la ';
11: LyricS := ' si ';
end;
end;
2: // japanese
begin
case (NoteP mod 12) of
0..1: LyricS := ' do ';
2..3: LyricS := ' re ';
4: LyricS := ' mi ';
5..6: LyricS := ' fa ';
7..8: LyricS := ' so ';
9..10: LyricS := ' la ';
11: LyricS := ' shi ';
end;
end;
3: // american
begin
case (NoteP mod 12) of
0..1: LyricS := ' do ';
2..3: LyricS := ' re ';
4: LyricS := ' mi ';
5..6: LyricS := ' fa ';
7..8: LyricS := ' sol ';
9..10: LyricS := ' la ';
11: LyricS := ' ti ';
end;
end;
end; // case
with Czesci[NrCzesci].Czesc[Czesci[NrCzesci].High] do
begin
SetLength(Nuta, Length(Nuta) + 1);
IlNut := IlNut + 1;
HighNut := HighNut + 1;
Muzyka.IlNut := Muzyka.IlNut + 1;
Nuta[HighNut].Start := StartP;
if IlNut = 1 then
begin
StartNote := Nuta[HighNut].Start;
if Czesci[NrCzesci].Ilosc = 1 then
Start := -100;
// Start := Nuta[HighNut].Start;
end;
Nuta[HighNut].Dlugosc := DurationP;
Muzyka.DlugoscNut := Muzyka.DlugoscNut + Nuta[HighNut].Dlugosc;
// back to the normal system with normal, golden and now freestyle notes
case TypeP of
'F': Nuta[HighNut].Wartosc := 0;
':': Nuta[HighNut].Wartosc := 1;
'*': Nuta[HighNut].Wartosc := 2;
end;
Czesci[NrCzesci].Wartosc := Czesci[NrCzesci].Wartosc + Nuta[HighNut].Dlugosc * Nuta[HighNut].Wartosc;
Nuta[HighNut].Ton := NoteP;
if Nuta[HighNut].Ton < Base[NrCzesci] then Base[NrCzesci] := Nuta[HighNut].Ton;
Nuta[HighNut].TonGamy := Nuta[HighNut].TonGamy mod 12;
Nuta[HighNut].Tekst := Copy(LyricS, 2, 100);
Lyric := Lyric + Nuta[HighNut].Tekst;
if TypeP = 'F' then
Nuta[HighNut].FreeStyle := true;
Koniec := Nuta[HighNut].Start + Nuta[HighNut].Dlugosc;
end; // with
end;
//--------------------
// Called when a new Sentence is found in the TXT File
//--------------------
procedure NewSentence(NrCzesciP: integer; Param1, Param2: integer; LoadFullFile: boolean);
var
I: Integer;
begin
// stara czesc //Alter Satz //Update Old Part
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].BaseNote := Base[NrCzesciP];
if LoadFullFile then
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].LyricWidth := glTextWidth(PChar(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Lyric));
//Total Notes Patch
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes := 0;
for I := low(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta) to high(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta) do
begin
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes := Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta[I].Dlugosc * Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta[I].Wartosc;
end;
//Total Notes Patch End
// nowa czesc //Neuer Satz //Update New Part
SetLength(Czesci[NrCzesciP].Czesc, Czesci[NrCzesciP].Ilosc + 1);
Czesci[NrCzesciP].High := Czesci[NrCzesciP].High + 1;
Czesci[NrCzesciP].Ilosc := Czesci[NrCzesciP].Ilosc + 1;
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].HighNut := -1;
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].IlNut := 0;
if not AktSong.Relative then
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Start := Param1;
if AktSong.Relative then
begin
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Start := Param1;
Rel[NrCzesciP] := Rel[NrCzesciP] + Param2;
end;
Base[NrCzesciP] := 100; // high number
end;
function CheckSong: boolean;
var
p, line, note: integer;
numLines, numNotes: integer;
bt: integer;
nextBeat: integer;
foundMedleyStart: boolean;
foundMedleyEnd: boolean;
medley: boolean;
singer: string;
begin
Result := true;
foundMedleyStart := false;
foundMedleyEnd := false;
if(AktSong.Medley.Source = msTag) then
begin
medley := true;
foundMedleyStart := false;
foundMedleyEnd := false;
end else
medley := false;
for p := 0 to Length(Czesci) - 1 do
begin
if AktSong.isDuet then
singer := ' (P' + IntToStr(p+1) + ')'
else
singer := '';
bt := low(integer);
numLines := Length(Czesci[p].Czesc);
if (numLines>0) and (Length(Czesci[p].Czesc[numLines-1].Nuta)=0) then
begin
Dec(numLines);
SetLength(Czesci[p].Czesc, numLines);
Dec(Czesci[p].High);
Dec(Czesci[p].Ilosc);
end;
if(numLines=0) then
begin
Log.LogError('Song ' + AktSong.Path + AktSong.Filename + ' has no lines?');
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end;
for line := 0 to numLines - 1 do
begin
numNotes := Length(Czesci[p].Czesc[line].Nuta);
if(numNotes=0) then
begin
Log.LogError('Sentence ' + IntToStr(line+1) + ' in song ' + AktSong.Path + AktSong.Filename + ' has no notes?');
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end;
if(bt>Czesci[p].Czesc[line].Start) then
begin
Log.LogError('Beat error in sentence ' + IntToStr(line+1) + ', on beat ' + IntToStr(Czesci[p].Czesc[line].Start) +
singer + ' in song ' + AktSong.Path + AktSong.Filename);
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end;
bt := Czesci[p].Czesc[line].Start;
for note := 0 to numNotes - 1 do
begin
if(bt>Czesci[p].Czesc[line].Nuta[note].Start) then
begin
Log.LogError('Beat error in sentence ' + IntToStr(line+1) + ', on beat ' + IntToStr(Czesci[p].Czesc[line].Nuta[note].Start) +
singer + ' in song ' + AktSong.Path + AktSong.Filename);
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end;
bt := Czesci[p].Czesc[line].Nuta[note].Start;
if (Czesci[p].Czesc[line].Nuta[note].Dlugosc<0) then
begin
Log.LogError('Note length <0 in sentence ' + IntToStr(line+1) + ', on beat ' + IntToStr(Czesci[p].Czesc[line].Nuta[note].Start) +
singer + ' in song ' + AktSong.Path + AktSong.Filename);
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end;
if (Czesci[p].Czesc[line].Nuta[note].Dlugosc=0) and not Czesci[p].Czesc[line].Nuta[note].FreeStyle then
begin
Log.LogError('Note length =0 in sentence ' + IntToStr(line+1) + ', on beat ' + IntToStr(Czesci[p].Czesc[line].Nuta[note].Start) +
singer + ' in song ' + AktSong.Path + AktSong.Filename);
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end;
if (note<numNotes-1) then
nextBeat := Czesci[p].Czesc[line].Nuta[note+1].Start
else if (line<numLines-1) then
nextBeat := Czesci[p].Czesc[line+1].Start
else
nextBeat := Czesci[p].Czesc[line].Koniec;
if (bt+Czesci[p].Czesc[line].Nuta[note].Dlugosc>nextBeat) then
begin
Log.LogError('Note length error in sentence ' + IntToStr(line+1) + ', on beat ' + IntToStr(Czesci[p].Czesc[line].Nuta[note].Start) +
singer + ' in song ' + AktSong.Path + AktSong.Filename);
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end;
if(medley) then
begin
if(bt = AktSong.Medley.StartBeat) then
foundMedleyStart := true;
if(bt+Czesci[p].Czesc[line].Nuta[note].Dlugosc = AktSong.Medley.EndBeat) then
foundMedleyEnd := true;
end;
end;
end;
end;
if(medley and not foundMedleyStart) then
begin
Log.LogError('Error MedleyStartBeat: no corresponding note start (beat) in song ' + AktSong.Path + AktSong.Filename);
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end else if(medley and not foundMedleyEnd) then
begin
Log.LogError('Error MedleyEndBeat: no corresponding note start+length in song ' + AktSong.Path + AktSong.Filename);
if (Ini.LoadFaultySongs=0) and (Ini.LoadFaultySongs_temp=0) then
Result := false;
end;
end;
//--------------------
// Load a Song
//--------------------
function LoadSong(Name: string; LoadFullFile: boolean): boolean;
var
TempC: char;
Tekst: string;
CP: integer; // Current Actor (0 or 1)
Pet: integer;
Param1: integer;
Param2: integer;
Param3: integer;
ParamS: string;
I: integer;
isNewSentence: boolean;
begin
Result := false;
CheckOK := true;
if not FileExists(Name) then begin
Log.LogError('File not found: "' + Name + '"', 'LoadSong');
exit;
end;
try
MultBPM := 4; // 4 - mnoznik dla czasu nut
Mult := 1; // 4 - dokladnosc pomiaru nut
Base[0] := 100; // high number
Base[1] := 100; // high number
if LoadFullFile then
AktSong.Relative := false;
Rel[0] := 0;
Rel[1] := 0;
CP := 0;
FileMode := fmOpenRead;
AssignFile(SongFile, Name);
if LoadFullFile then
begin
Reset(SongFile);
//Clear old Song Header
ClearSong(AktSong);
if (AktSong.Path = '') then
AktSong.Path := ExtractFilePath(Name);
if (AktSong.FileName = '') then
AktSong.Filename := ExtractFileName(Name);
//Read Header
Result := ReadTxTHeader(AktSong);
if not Result then
begin
CloseFile(SongFile);
FileMode := fmOpenReadWrite;
Log.LogError('Error Loading SongHeader, abort Song Loading. File: ' + Name);
Exit;
end;
end;
Reset(SongFile);
FileLineNo := 0;
//Search for Note Begining
repeat
ReadLn(SongFile, Tekst);
Inc(FileLineNo);
if (EoF(SongFile)) or (Length(Tekst)=0) then
begin //Song File Corrupted - No Notes
CloseFile(SongFile);
FileMode := fmOpenReadWrite;
Log.LogError('Could not load txt/txd File, no Notes found: ' + Name);
Result := False;
Exit;
end;
Read(SongFile, TempC);
until ((TempC = ':') or (TempC = 'F') or (TempC = '*') or (TempC = 'P'));
Inc(FileLineNo);
SetLength(Czesci, 0);
if (TempC = 'P') then
begin
AktSong.isDuet := true;
SetLength(Czesci, 2);
CP := -1;
end else
SetLength(Czesci, 1);
for Pet := 0 to High(Czesci) do
begin
SetLength(Czesci[Pet].Czesc, 1);
Czesci[Pet].High := 0;
Czesci[Pet].Ilosc := 1;
Czesci[Pet].Akt := 0;
Czesci[Pet].Resolution := AktSong.Resolution;
Czesci[Pet].NotesGAP := AktSong.NotesGAP;
Czesci[Pet].Czesc[0].IlNut := 0;
Czesci[Pet].Czesc[0].HighNut := -1;
Czesci[Pet].Wartosc := 0;
end;
isNewSentence := false;
while (TempC <> 'E') AND (not EOF(SongFile)) do
begin
if (TempC = 'P') then
begin
Read(SongFile, Param1);
if (Param1=1) then
CP := 0
else if (Param1=2) then
CP := 1
else if (Param1=3) then
CP := 2
else
begin
Log.LogError('Wrong P-Number in file: "' + Name + '"; Line '+IntToStr(FileLineNo)+' (LoadSong)');
Result := False;
Exit;
end;
end;
if (TempC = ':') or (TempC = '*') or (TempC = 'F') then
begin
// wczytuje nute
Read(SongFile, Param1);
Read(SongFile, Param2);
Read(SongFile, Param3);
Read(SongFile, ParamS);
// dodaje nute
if (CP<>2) then
// one singer
ParseNote(CP, TempC, (Param1+Rel[CP]) * Mult, Param2 * Mult, Param3, ParamS)
else begin
// both singer
ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS);
ParseNote(1, TempC, (Param1+Rel[1]) * Mult, Param2 * Mult, Param3, ParamS);
end;
isNewSentence := false;
end;
if TempC = '-' then
begin
if isNewSentence then
begin
Log.LogError('Double sentence break in file: "' + Name + '"; Line '+IntToStr(FileLineNo)+' (LoadSong)');
Result := False;
Exit;
end;
// reads sentence
Read(SongFile, Param1);
if AktSong.Relative then Read(SongFile, Param2); // read one more data for relative system
// new sentence
if not AktSong.isDuet then
// one singer
NewSentence(CP, (Param1 + Rel[CP]) * Mult, Param2, LoadFullFile)
else
begin
for I := 0 to 1 do
begin
if (Czesci[I].Czesc[Czesci[I].High].IlNut > 0) then
begin
with Czesci[I].Czesc[Czesci[I].High] do
begin
if (Nuta[HighNut].Start + Nuta[HighNut].Dlugosc <= (Param1 + Rel[I]) * Mult) then
NewSentence(I, (Param1 + Rel[I]) * Mult, Param2, LoadFullFile);
end;
end;
end;
end;
isNewSentence := true;
end; // if
if TempC = 'B' then begin
SetLength(AktSong.BPM, Length(AktSong.BPM) + 1);
Read(SongFile, AktSong.BPM[High(AktSong.BPM)].StartBeat);
AktSong.BPM[High(AktSong.BPM)].StartBeat := AktSong.BPM[High(AktSong.BPM)].StartBeat + Rel[0];
Read(SongFile, Tekst);
AktSong.BPM[High(AktSong.BPM)].BPM := StrToFloat(Tekst);
AktSong.BPM[High(AktSong.BPM)].BPM := AktSong.BPM[High(AktSong.BPM)].BPM * Mult * MultBPM;
end;
if not AktSong.isDuet then
begin
Czesci[CP].Czesc[Czesci[CP].High].BaseNote := Base[CP];
if LoadFullFile then
Czesci[CP].Czesc[Czesci[CP].High].LyricWidth := glTextWidth(PChar(Czesci[CP].Czesc[Czesci[CP].High].Lyric));
//Total Notes Patch
Czesci[CP].Czesc[Czesci[CP].High].TotalNotes := 0;
for I := low(Czesci[CP].Czesc[Czesci[CP].High].Nuta) to high(Czesci[CP].Czesc[Czesci[CP].High].Nuta) do
begin
Czesci[CP].Czesc[Czesci[CP].High].TotalNotes := Czesci[CP].Czesc[Czesci[CP].High].TotalNotes + Czesci[CP].Czesc[Czesci[CP].High].Nuta[I].Dlugosc * Czesci[CP].Czesc[Czesci[CP].High].Nuta[I].Wartosc;
end;
//Total Notes Patch End
end else
begin
for Pet := 0 to High(Czesci) do
begin
Czesci[Pet].Czesc[Czesci[Pet].High].BaseNote := Base[Pet];
if LoadFullFile then
Czesci[Pet].Czesc[Czesci[Pet].High].LyricWidth := glTextWidth(PChar(Czesci[Pet].Czesc[Czesci[Pet].High].Lyric));
//Total Notes Patch
Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes := 0;
for I := low(Czesci[Pet].Czesc[Czesci[Pet].High].Nuta) to high(Czesci[Pet].Czesc[Czesci[Pet].High].Nuta) do
begin
Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes := Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes + Czesci[Pet].Czesc[Czesci[Pet].High].Nuta[I].Dlugosc * Czesci[Pet].Czesc[Czesci[Pet].High].Nuta[I].Wartosc;
end;
//Total Notes Patch End
end;
end;
Repeat
Read(SongFile, TempC);
Until ((TempC <> #13) AND (TempC <> #10)) or (TempC = 'E') or (EOF(SongFile));
Inc(FileLineNo);
end; // while}
CloseFile(SongFile);
FileMode := fmOpenReadWrite;
except
try
CloseFile(SongFile);
FileMode := fmOpenReadWrite;
except
end;
Result := false;
Log.LogError('Error Loading File: "' + Name + '" in Line ' + inttostr(FileLineNo));
exit;
end;
CheckOK := CheckSong;
Result := CheckOK;
end;
//--------------------
// Saves a Song
//--------------------
function SaveSong(Song: TSong; Czesc: array of TCzesci; Name: string; Relative: boolean): boolean;
var
C: integer;
S: string;
B: integer;
RelativeSubTime: integer;
NoteState: String;
P: integer;
procedure WriteCustomTags; //from 1.1 (modified)
var
I: integer;
Line: String;
begin
for I := 0 to High(Song.CustomTags) do
begin
Line := Song.CustomTags[I].Content;
if (Length(Song.CustomTags[I].Tag) > 0) then
Line := Song.CustomTags[I].Tag + ':' + Line;
WriteLn(SongFile, '#' + Line);
end;
end;
function isIdenticalLine(line: integer): boolean;
var
I: integer;
begin
Result := false;
if (Czesc[0].Czesc[line].HighNut <> Czesc[1].Czesc[line].HighNut) then
Exit;
if (Czesc[0].Czesc[line].Start <> Czesc[1].Czesc[line].Start) then
Exit;
for I := 0 to Length(Czesc[0].Czesc[line].Nuta) - 1 do
begin
if (Czesc[0].Czesc[line].Nuta[I].Wartosc <> Czesc[1].Czesc[line].Nuta[I].Wartosc) then
Exit;
if (Czesc[0].Czesc[line].Nuta[I].Start <> Czesc[1].Czesc[line].Nuta[I].Start) then
Exit;
if (Czesc[0].Czesc[line].Nuta[I].Dlugosc <> Czesc[1].Czesc[line].Nuta[I].Dlugosc) then
Exit;
if (Czesc[0].Czesc[line].Nuta[I].Ton <> Czesc[1].Czesc[line].Nuta[I].Ton) then
Exit;
if (Czesc[0].Czesc[line].Nuta[I].Tekst <> Czesc[1].Czesc[line].Nuta[I].Tekst) then
Exit;
end;
Result := true;
end;
procedure WriteLine(CP, line: integer);
var
note: integer;
begin
for note := 0 to Czesc[CP].Czesc[line].HighNut do
begin
with Czesc[CP].Czesc[line].Nuta[note] do
begin
//Golden + Freestyle Note Patch
case Czesc[CP].Czesc[line].Nuta[note].Wartosc of
0: NoteState := 'F ';
1: NoteState := ': ';
2: NoteState := '* ';
end; // case
S := NoteState + IntToStr(Start-RelativeSubTime) + ' ' + IntToStr(Dlugosc) +
' ' + IntToStr(Ton) + ' ' + Tekst;
WriteLn(SongFile, S);
end; // with
end; // N
end;
begin
// Relative := true; // override (idea - use shift+S to save with relative)
Result := true;
AssignFile(SongFile, Name);
Rewrite(SongFile);
WriteLn(SongFile, '#TITLE:' + Song.Title + '');
WriteLn(SongFile, '#ARTIST:' + Song.Artist);
if Song.Creator <> '' then WriteLn(SongFile, '#CREATOR:' + Song.Creator);
if Song.Language <> 'Unknown' then WriteLn(SongFile, '#LANGUAGE:' + Song.Language);
for C := 0 to Length(Song.Edition)-1 do
if Song.Edition[C] <> 'Unknown' then WriteLn(SongFile, '#EDITION:' + Song.Edition[C]);
for C := 0 to Length(Song.Genre) - 1 do
if Song.Genre[C] <> 'Unknown' then WriteLn(SongFile, '#GENRE:' + Song.Genre[C]);
if (Song.Year <> -1) then WriteLn(SongFile, '#YEAR:' + IntToStr(Song.Year));
WriteLn(SongFile, '#MP3:' + Song.Mp3);
if Song.Cover <> '' then WriteLn(SongFile, '#COVER:' + Song.Cover);
if Song.Background <> '' then WriteLn(SongFile, '#BACKGROUND:' + Song.Background);
if Song.Video <> '' then WriteLn(SongFile, '#VIDEO:' + Song.Video);
if Song.VideoGAP <> 0 then WriteLn(SongFile, '#VIDEOGAP:' + FloatToStr(Song.VideoGAP));
if Song.Resolution <> 4 then WriteLn(SongFile, '#RESOLUTION:' + IntToStr(Song.Resolution));
if Song.NotesGAP <> 0 then WriteLn(SongFile, '#NOTESGAP:' + IntToStr(Song.NotesGAP));
if Song.Start <> 0 then WriteLn(SongFile, '#START:' + FloatToStr(Song.Start));
if Song.Finish <> 0 then WriteLn(SongFile, '#END:' + IntToStr(Song.Finish));
if Song.PreviewStart<> 0 then WriteLn(SongFile, '#PREVIEWSTART:'+ FormatFloat('#0.000', Song.PREVIEWSTART));
if (Song.Medley.Source=msTag) and not Relative then
begin
WriteLn(SongFile, '#MedleyStartBeat:' + IntToStr(Song.Medley.StartBeat));
WriteLn(SongFile, '#MedleyEndBeat:' + IntToStr(Song.Medley.EndBeat));
end;
if (not Song.CalcMedley) then
WriteLn(SongFile, '#CalcMedley:Off');
{if (Song.isDuet) then
begin
WriteLn(SongFile, '#DuetSingerP1:' + Song.DuetNames[0]);
WriteLn(SongFile, '#DuetSingerP2:' + Song.DuetNames[1]);
end;}
if Relative then WriteLn(SongFile, '#RELATIVE:yes');
WriteLn(SongFile, '#BPM:' + FloatToStr(Song.BPM[0].BPM / 4));
WriteLn(SongFile, '#GAP:' + FloatToStr(Song.GAP));
RelativeSubTime := 0;
for B := 1 to High(AktSong.BPM) do
WriteLn(SongFile, 'B ' + FloatToStr(AktSong.BPM[B].StartBeat) + ' ' + FloatToStr(AktSong.BPM[B].BPM/4));
// write custom header tags (from 1.1)
WriteCustomTags;
for P := 0 to Length(Czesci) - 1 do
begin
if AktSong.isDuet then
begin
S := 'P' + IntToStr(P+1);
WriteLn(SongFile, S);
end;
for C := 0 to Czesc[P].High do
begin
WriteLine(P, C);
if C < Czesc[P].High then
begin // don't write end of last sentence
if not Relative then
S := '- ' + IntToStr(Czesc[P].Czesc[C+1].Start)
else
begin
S := '- ' + IntToStr(Czesc[P].Czesc[C+1].Start - RelativeSubTime) +
' ' + IntToStr(Czesc[P].Czesc[C+1].Start - RelativeSubTime);
RelativeSubTime := Czesc[P].Czesc[C+1].Start;
end;
WriteLn(SongFile, S);
end;
end; // C
end;
WriteLn(SongFile, 'E');
CloseFile(SongFile);
end;
{* new procedure for preview
tries find out the beginning of a refrain
and the end... *}
procedure FindRefrainStart(var Song: TSong);
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 String;
series: array of TSeries;
temp_series: TSeries;
max: integer;
len_lines, len_notes: integer;
found_end: boolean;
begin
if AktSong.Medley.Source = msTag then
Exit;
if not AktSong.CalcMedley then
Exit;
//relative is not supported for medley by now!
if AktSong.Relative then
begin
Log.LogError('Song '+Song.Artist+'-'+Song.Title+' contains #Relative, this is not supported by medley-function!');
Song.Medley.Source := msNone;
Exit;
end;
num_lines := Length(Czesci[0].Czesc);
SetLength(sentences, num_lines);
//build sentences array
for I := 0 to num_lines - 1 do
begin
sentences[I] := '';
for J := 0 to Length(Czesci[0].Czesc[I].Nuta) - 1 do
begin
if not Czesci[0].Czesc[I].Nuta[J].FreeStyle then
sentences[I] := sentences[I] + Czesci[0].Czesc[I].Nuta[J].Tekst;
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]<>'') and (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]<>'') and (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(Czesci[0].Czesc);
if (Length(series)>0) and (series[max].len > 3) then
begin
Song.Medley.StartBeat := Czesci[0].Czesc[series[max].start].Nuta[0].Start;
len_notes := length(Czesci[0].Czesc[series[max].end_].Nuta);
Song.Medley.EndBeat := Czesci[0].Czesc[series[max].end_].Nuta[len_notes-1].Start +
Czesci[0].Czesc[series[max].end_].Nuta[len_notes-1].Dlugosc;
found_end := false;
//set end if duration > MEDLEY_MIN_DURATION
if GetTimeFromBeat(Song.Medley.StartBeat)+ MEDLEY_MIN_DURATION >
GetTimeFromBeat(Song.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(Czesci[0].Czesc[I].Nuta);
for J := 0 to len_notes - 1 do
begin
if GetTimeFromBeat(Song.Medley.StartBeat)+ MEDLEY_MIN_DURATION >
GetTimeFromBeat(Czesci[0].Czesc[I].Nuta[J].Start+
Czesci[0].Czesc[I].Nuta[J].Dlugosc) then
begin
found_end := true;
Song.Medley.EndBeat := Czesci[0].Czesc[I].Nuta[len_notes-1].Start+
Czesci[0].Czesc[I].Nuta[len_notes-1].Dlugosc;
break;
end;
end;
end;
end;
if found_end then
begin
Song.Medley.Source := msCalculated;
//calculate fade time
Song.Medley.FadeIn_time := DEFAULT_FADE_IN_TIME; //TODO in INI
Song.Medley.FadeOut_time := DEFAULT_FADE_OUT_TIME; //TODO in INI
end;
end;
//set PreviewStart if not set
if Song.PreviewStart=0 then
begin
//len_notes := length(Czesci[0].Czesc[len_lines-1].Nuta);
if Song.Medley.Source = msCalculated then
Song.PreviewStart := GetTimeFromBeat(Song.Medley.StartBeat);{
else
Song.PreviewStart := (GetTimeFromBeat(Czesci[0].Czesc[len_lines-1].Nuta[len_notes-1].start+
Czesci[0].Czesc[len_lines-1].Nuta[len_notes-1].Dlugosc))/4; //TODO}
end;
end;
//sets a song to medley-mod:
//converts all unneeded notes into freestyle
//updates score values
procedure SetMedleyMode;
var
pl, line, note: integer;
cut_line: array of integer;
foundcut: array of boolean;
start: integer;
end_: integer;
begin
start := AktSong.Medley.StartBeat;
end_ := AktSong.Medley.EndBeat;
SetLength(cut_line, Length(Czesci));
SetLength(foundcut, Length(Czesci));
for pl := 0 to Length(Czesci) - 1 do
begin
foundcut[pl] := false;
cut_line[pl] := high(Integer);
Czesci[pl].Wartosc := 0;
for line := 0 to Length(Czesci[pl].Czesc) - 1 do
begin
Czesci[pl].Czesc[line].TotalNotes := 0;
for note := 0 to Length(Czesci[pl].Czesc[line].Nuta) - 1 do
begin
if Czesci[pl].Czesc[line].Nuta[note].Start < start then //check start
begin
Czesci[pl].Czesc[line].Nuta[note].FreeStyle := true;
Czesci[pl].Czesc[line].Nuta[note].Wartosc := 0;
end else if Czesci[pl].Czesc[line].Nuta[note].Start>= end_ then //check end
begin
Czesci[pl].Czesc[line].Nuta[note].FreeStyle := true;
Czesci[pl].Czesc[line].Nuta[note].Wartosc := 0;
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(Czesci[pl].Wartosc, Czesci[pl].Czesc[line].Nuta[note].Dlugosc * Czesci[pl].Czesc[line].Nuta[note].Wartosc);
//and to the current lines entire value
Inc(Czesci[pl].Czesc[line].TotalNotes, Czesci[pl].Czesc[line].Nuta[note].Dlugosc * Czesci[pl].Czesc[line].Nuta[note].Wartosc);
end;
end;
end;
end;
for pl := 0 to Length(Czesci) - 1 do
begin
if (foundcut[pl]) and (Length(Czesci[pl].Czesc)>cut_line[pl]) then
begin
SetLength(Czesci[pl].Czesc, cut_line[pl]);
Czesci[pl].High := cut_line[pl]-1;
Czesci[pl].Ilosc := Czesci[pl].High+1;
end;
end;
end;
end.