unit USong_Txt;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses
Classes,
SysUtils,
USong_TextFile;
type
{*******************
Child of the new TSong class.
implements methods to load a song form a txt file (ultrastar file format)
*******************}
TSong_Txt = class(TSong_TextFile)
public
Procedure ReadHeader; override; //Reads Fileheader (Implemented by Child only)
Procedure ReadFile; override; //Reads complete File (Header + Notes) (Implemented by Child only)
Procedure WriteFile; override; //Writes complete File (Header + Notes) (Implemented by Child only)
end;
implementation
uses
UMusic,
UMain,
UPlatform,
ULog;
type
TTags = (ArtistTag, TitleTag, Mp3Tag, BPMTag);
//--------
// Reads Fileheader
//--------
Procedure TSong_Txt.ReadHeader;
var
started: Boolean;
line, key, value: String;
FloatValue: Real;
FoundTags: Set of TTags;
// splits a headerline in key an value
// returns true on success and false if this is not a valid headerline
Function SplitHeaderLine(const Line: String; var Key, Value: String): Boolean;
var
idx: Integer;
begin
Result := False;
idx := Pos(':', Line);
if idx > 0 then
begin
Key := Uppercase(Trim(Copy(Line, 2, idx - 2))); //Uppercase is for Case Insensitive Checks
Value := Trim(Copy(Line, idx + 1,Length(Line) - idx));
if (Length(Key) > 0) AND (Length(Value) > 0) then
Result := True
end;
end;
function song_StrToFloat(const aValue : String): Extended;
var
lValue: String;
begin
lValue := aValue;
if (Pos(',', lValue) <> 0) then
lValue[Pos(',', lValue)] := '.';
Result := StrToFloatDef(lValue, 0);
end;
begin
if not OpenSongFile then
exit;
started := False;
FoundTags := [];
while isDataAvailable do
begin
line := GetNextLine();
// break if header has finished
if started AND ((Length(line) > 0) AND (not (line[1] = '#'))) then
break;
// skip invalid lines at beginning
if (not started) AND (line[1] = '#') then
started := True;
// parse line
if started then
begin
if (Length(line) > 0) AND not SplitHeaderLine(line, key, value) then
begin
Log.LogError('Invalide line in Header of file: ' + FilePath + FileName);
Log.LogError('Line: ' + line);
break;
end;
{$IFDEF UTF8_FILENAMES}
if ((Key = 'MP3') or (Key = 'BACKGROUND') or (Key = 'COVER') or (Key = 'VIDEO')) then
Value := Utf8Encode(Value);
{$ENDIF}
//Title
if (Key = 'TITLE') then
begin
Title := Value;
FoundTags := FoundTags + [TitleTag];
continue;
end;
//Artist
if (Key = 'ARTIST') then
begin
Artist := Value;
FoundTags := FoundTags + [ArtistTag];
continue;
end;
//MP3 File //Test if Exists
if (Key = 'MP3') then
begin
if FileExists(FilePath + Value) then
begin
Mp3 := FilePath + Value;
FoundTags := FoundTags + [Mp3Tag];
end
else
Log.LogError('Can''t find MP3 File: ' + FilePath + Value + ' in Song: ' + FilePath + FileName);
continue;
end;
//Beats per Minute
if (Key = 'BPM') then
begin
FloatValue := song_StrtoFloat( Value ) * Mult * MultBPM;
if FloatValue <> 0 then
begin
SetLength(BPM, 1);
BPM[0].StartBeat := 0;
BPM[0].BPM := floatValue;
FoundTags := FoundTags + [BPMTag];
end;
continue;
end;
//---------
//Additional Header Information
//---------
// Gap
if (Key = 'GAP') then
begin
GAP := song_StrtoFloat( Value );
continue;
end;
//Cover Picture
if (Key = 'COVER') then
begin
if FileExists(FilePath + Value) then
Cover := FilePath + Value
else
Log.LogError('Can''t find Cover File: ' + FilePath + Value + ' in Song: ' + FilePath + FileName);
continue;
end;
//Background Picture
if (Key = 'BACKGROUND') then
begin
if FileExists(FilePath + Value) then
Background := FilePath + Value
else
Log.LogError('Can''t find Background File: ' + FilePath + Value + ' in Song: ' + FilePath + FileName);
continue;
end;
// Video File
if (Key = 'VIDEO') then
begin
if FileExists(FilePath + Value) then
Video := FilePath + Value
else
Log.LogError('Can''t find Video File: ' + FilePath + Value + ' in Song: ' + FilePath + FileName);
continue;
end;
// Video Gap
if (Key = 'VIDEOGAP') then
begin
VideoGAP := song_StrtoFloat(Value);
continue;
end;
//Genre Sorting
if (Key = 'GENRE') then
begin
Genre := Value;
continue;
end;
//Edition Sorting
if (Key = 'EDITION') then
begin
Edition := Value;
continue;
end;
//Creator Tag
if (Key = 'CREATOR') then
begin
Creator := Value;
continue;
end;
//Language Sorting
if (Key = 'LANGUAGE') then
begin
Language := Value;
continue;
end;
// Song Start
if (Key = 'START') then
begin
Start := song_StrtoFloat( Value );
continue;
end;
// Song Ending
if (Key = 'END') then
begin
TryStrtoInt(Value, Finish);
continue;
end;
// Resolution
if (Key = 'RESOLUTION') then
begin
TryStrtoInt(Value, Resolution);
continue;
end;
// Notes Gap
if (Key = 'NOTESGAP') then
begin
TryStrtoInt(Value, NotesGAP);
continue;
end;
// Relative Notes
if (Key = 'RELATIVE') AND (uppercase(Value) = 'YES') then
begin
Relative := True;
continue;
end;
end; // if started
end; // while
if Cover = '' then
begin
Cover := platform.FindSongFile(FilePath, '*[CO].jpg');
end;
// check if all required infos are given
if not (BPMTag in FoundTags) then
Log.LogError('BPM Tag missing: ' + FilePath + FileName);
if not (MP3Tag in FoundTags) then
Log.LogError('MP3 Tag missing or invalid file: ' + FilePath + FileName);
if not (ArtistTag in FoundTags) then
Log.LogError('Artist Tag missing: ' + FilePath + FileName);
if not (TitleTag in FoundTags) then
Log.LogError('Title Tag missing: ' + FilePath + FileName);
CloseSongFile();
end;
//--------
// Reads complete File (Header + Notes)
//--------
Procedure TSong_Txt.ReadFile;
var
Line: String;
NotesRead: Boolean;
Values: TStringList;
Procedure ParseDelimited(const StringList: TStringList; const Value: String; const Delimiter: String; const MaxParts: Integer);
var
idx: Integer;
Source: String;
Delta: Integer;
begin
Delta := Length(Delimiter);
Source := Value;
StringList.BeginUpdate;
StringList.Clear;
try
while ((Pos(Delimiter, Source) > 0) OR ((MaxParts > 0) AND (StringList.count+1 >= MaxParts))) do
begin
idx := Pos(Delimiter, Source);
StringList.Add(Copy(Source,0,idx-1));
Source := Copy(Source,idx+Delta, MaxInt);
end;
if (Length(Source) > 0) then
StringList.Add(Source);
finally
StringList.EndUpdate;
end;
end;
begin
// read Header
Self.ReadHeader;
OpenSongFile();
ResetLyrics;
NotesRead := False;
while isDataAvailable do
begin
line := GetNextLine();
// end of song
if (line[1] = 'E') then
break;
// skip invalid lines
if (line[1] <> ':') OR (line[1] <> 'F') OR (line[1] <> '*') OR (line[1] <> '-') OR (line[1] <> 'B') then
continue;
// aktuelle Zeile in einzelne Werte aufteilen
Values := TStringList.Create;
try
ParseDelimited(Values, line, ' ', 5);
try
if (line[1] = '-') then
if (not NotesRead) then
// skip newline if no notes before
continue
else
begin
// new lyric line
// param count: 1 (relative: 2)
if (Values.Count > 2) then
// relativ offset
begin
// P1
AddLyricLine(0, StrToInt(Values[1]), StrToInt(Values[2]));
// P2
if Length(Player) = 2 Then
AddLyricLine(1, StrToInt(Values[1]), StrToInt(Values[2]))
end
else
begin
AddLyricLine(0, StrToInt(Values[1]));
// P2
if Length(Player) = 2 then
AddLyricLine(1, StrToInt(Values[1]))
end;
end;
// new BPM set
if (line[1] = 'B') then
begin
// param count: 2
AddBPM(StrToInt(Values[1]), StrToFloat(Values[2]));
end;
if (line[1] = ':') OR (line[1] = '*') OR (line[1] = 'F') then
begin
// param count: 4
if (Values.Count < 5) then
Log.LogError('Error parsing line: ' + line, 'Not enough arguments.')
else
begin
// Check for ZeroNote
if StrToInt(Values[2]) = 0 then
Log.LogError('Found ZeroNote: "' + line + '" -> Note ignored!')
else
begin
// P1
AddNote(0, line[1], StrToInt(Values[1]), StrToInt(Values[2]), StrToInt(Values[3]), Values[4]);
// P2
if Length(Player) = 2 then
AddNote(1, line[1], StrToInt(Values[1]), StrToInt(Values[2]), StrToInt(Values[3]), Values[4]);
end;
end;
end;
except
on E : Exception do
Log.LogError('Error parsing line: ' + line, E.ClassName + ': ' + E.Message);
end;
finally
Values.Free();
end;
end;
CloseSongFile();
end;
//--------
// Writes complete File (Header + Notes)
//--------
Procedure TSong_Txt.WriteFile;
begin
end;
end.