unit UFiles;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
uses SysUtils,
ULog,
UMusic,
USongs;
//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);
function LoadSong(Name: string): boolean;
function SaveSong(Song: TSong; Czesc: TCzesci; Name: string; Relative: boolean): boolean;
var
{*
//Absolute Paths
GamePath: string;
SoundPath: string;
SongPath: string;
LogPath: string;
ThemePath: string;
ScreenshotsPath: string;
CoversPath: string;
LanguagesPath: string;
PluginPath: string;
PlayListPath: 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;
implementation
uses TextGL,
UIni,
UMain;
{*
//--------------------
// Function sets all Absolute Paths e.g. Song Path and makes sure the Directorys exist
//--------------------
procedure InitializePaths;
// Initialize a Path Variable
// After Setting Paths, make sure that Paths exist
function initialize_path( out aPathVar : String; const aLocation : String ): boolean;
var
lWriteable: Boolean;
begin
aPathVar := aLocation;
If DirectoryExists(aPathVar) then
lWriteable := ForceDirectories(aPathVar)
else
lWriteable := false;
if not Writeable then
Log.LogError('Error: Dir ('+ aLocation +') is Readonly');
result := lWriteable;
end;
begin
GamePath := ExtractFilePath(ParamStr(0));
initialize_path( LogPath , GamePath );
initialize_path( SoundPath , GamePath + 'Sounds\' );
initialize_path( SongPath , GamePath + 'Songs\' );
initialize_path( ThemePath , GamePath + 'Themes\' );
initialize_path( ScreenshotsPath , GamePath + 'Screenshots\');
initialize_path( CoversPath , GamePath + 'Covers\' );
initialize_path( LanguagesPath , GamePath + 'Languages\' );
initialize_path( PluginPath , GamePath + 'Plugins\' );
initialize_path( PlaylistPath , GamePath + 'Playlists\' );
DecimalSeparator := ',';
end;
*}
//--------------------
// Clears Song Header values
//--------------------
procedure ClearSong(var Song: TSong);
begin
//Main Information
Song.Title := '';
Song.Artist := '';
//Sortings:
Song.Genre := 'Unknown';
Song.Edition := 'Unknown';
Song.Language := 'Unknown'; //Language Patch
//Required Information
Song.Mp3 := '';
{$IFDEF FPC}
Song.BPM := NULL;
{$ELSE}
Song.BPM := 0;
{$ENDIF}
Song.GAP := 0;
Song.Start := 0;
Song.Finish := 0;
//Additional Information
Song.Background := '';
Song.Cover := '';
Song.Video := '';
Song.VideoGAP := 0;
Song.NotesGAP := 0;
Song.Resolution := 4;
Song.Creator := '';
end;
//--------------------
// Reads Standard TXT Header
//--------------------
function ReadTXTHeader(var Song: TSong): boolean;
var
Line, Identifier, Value: String;
Temp: word;
Done: byte;
begin
Result := true;
//Read first Line
ReadLn (SongFile, Line);
if (Length(Line)<=0) then
begin
Log.LogError('File Starts with Empty Line: ' + 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
Song.Cover := Value;
end
//Background Picture
else if (Identifier = 'BACKGROUND') then
begin
Song.Background := Value;
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
Song.Genre := Value;
end
//Edition Sorting
else if (Identifier = 'EDITION') then
begin
Song.Edition := 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
// 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;
end;
end;
if not EOf(SongFile) then
ReadLn (SongFile, Line)
else
begin
Result := False;
Log.LogError('File Incomplete or not Ultrastar TxT: ' + Song.FileName);
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 (Done and 8) = 0 then //No BPM Flag
Log.LogError('BPM Tag Missing: ' + Song.FileName)
else if (Done and 4) = 0 then //No MP3 Flag
Log.LogError('MP3 Tag/File Missing: ' + Song.FileName)
else if (Done and 2) = 0 then //No Artist Flag
Log.LogError('Artist Tag Missing: ' + Song.FileName)
else if (Done and 1) = 0 then //No Title Flag
Log.LogError('Title Tag Missing: ' + Song.FileName)
else //unknown Error
Log.LogError('File Incomplete or not Ultrastar TxT: ' + Song.FileName);
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);
var
Space: boolean;
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);
var
I: Integer;
begin
// stara czesc //Alter Satz //Update Old Part
Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].BaseNote := Base[NrCzesciP];
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;
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;
//--------------------
// Load a Song
//--------------------
function LoadSong(Name: string): boolean;
var
TempC: char;
Tekst: string;
CP: integer; // Current Player (0 or 1)
Pet: integer;
Both: boolean;
Param1: integer;
Param2: integer;
Param3: integer;
ParamS: string;
I: Integer;
begin
Result := false;
if not FileExists(Name) then begin
Log.LogError('File not found: "' + Name + '"', 'WczytajCzesci');
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
Czesci[0].Wartosc := 0;
// Czesci[1].Wartosc := 0; // here was the error in 0.3.2
AktSong.Relative := false;
Rel[0] := 0;
// Rel[1] := 0;
CP := 0;
Both := false;
if Length(Player) = 2 then Both := true;
FileMode := fmOpenRead;
AssignFile(SongFile, Name);
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);
Log.LogError('Error Loading SongHeader, abort Song Loading');
Exit;
end;
Result := False;
Reset(SongFile);
FileLineNo := 0;
//Search for Note Begining
repeat
ReadLn(SongFile, Tekst);
Inc(FileLineNo);
if (EoF(SongFile)) then
begin //Song File Corrupted - No Notes
CloseFile(SongFile);
Log.LogError('Could not load txt File, no Notes found: ' + Name);
Result := False;
Exit;
end;
Read(SongFile, TempC);
until ((TempC = ':') or (TempC = 'F') or (TempC = '*'));
SetLength(Czesci, 2);
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;
end;
// TempC := ':';
// TempC := Tekst[1]; // read from backup variable, don't use default ':' value
while (TempC <> 'E') AND (not EOF(SongFile)) do begin
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 not Both then
// P1
ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS)
else begin
// P1 + P2
ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS);
ParseNote(1, TempC, (Param1+Rel[1]) * Mult, Param2 * Mult, Param3, ParamS);
end;
end; // if
if TempC = '-' then begin
// reads sentence
Read(SongFile, Param1);
if AktSong.Relative then Read(SongFile, Param2); // read one more data for relative system
// new sentence
if not Both then
// P1
NewSentence(0, (Param1 + Rel[0]) * Mult, Param2)
else begin
// P1 + P2
NewSentence(0, (Param1 + Rel[0]) * Mult, Param2);
NewSentence(1, (Param1 + Rel[1]) * Mult, Param2);
end;
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 Both then begin
Czesci[CP].Czesc[Czesci[CP].High].BaseNote := Base[CP];
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];
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;
Read(SongFile, TempC);
Inc(FileLineNo);
end; // while}
CloseFile(SongFile);
except
try
CloseFile(SongFile);
except
end;
Log.LogError('Error Loading File: "' + Name + '" in Line ' + inttostr(FileLineNo));
exit;
end;
Result := true;
end;
//--------------------
// Saves a Song
//--------------------
function SaveSong(Song: TSong; Czesc: TCzesci; Name: string; Relative: boolean): boolean;
var
C: integer;
N: integer;
S: string;
B: integer;
RelativeSubTime: integer;
NoteState: String;
begin
// Relative := true; // override (idea - use shift+S to save with relative)
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.Edition <> 'Unknown' then WriteLn(SongFile, '#EDITION:' + Song.Edition);
if Song.Genre <> 'Unknown' then WriteLn(SongFile, '#GENRE:' + Song.Genre);
if Song.Language <> 'Unknown' then WriteLn(SongFile, '#LANGUAGE:' + Song.Language);
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 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));
for C := 0 to Czesc.High do begin
for N := 0 to Czesc.Czesc[C].HighNut do begin
with Czesc.Czesc[C].Nuta[N] do begin
//Golden + Freestyle Note Patch
case Czesc.Czesc[C].Nuta[N].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
if C < Czesc.High then begin // don't write end of last sentence
if not Relative then
S := '- ' + IntToStr(Czesc.Czesc[C+1].Start)
else begin
S := '- ' + IntToStr(Czesc.Czesc[C+1].Start - RelativeSubTime) +
' ' + IntToStr(Czesc.Czesc[C+1].Start - RelativeSubTime);
RelativeSubTime := Czesc.Czesc[C+1].Start;
end;
WriteLn(SongFile, S);
end;
end; // C
WriteLn(SongFile, 'E');
CloseFile(SongFile);
end;
end.