aboutsummaryrefslogblamecommitdiffstats
path: root/Game/Code/Classes/UFiles.pas
blob: e38ad7f171b6254992fa0d056fb088cab0deed2a (plain) (tree)
1
2
3
4
5
6
7
8
9
10





               
               
           

                




                                             











                                                                              








                                                                                                        



                                                                                                    








                                             
                                     






                            
                            
                            
                            






                                                                             



                           














                                                                                       


                                       






                                                
                                              
                                              
                                   






                                                    

                                                            


















                                                               


                                                              


                                                              


                                                        















                                            



                                
                                               
                   


                         
                          


                    
                          
                        



                           
                              





                              
                           

                                 


                                







                               











                                                          
                 












                                                              










                                                                             











                                                                             






                                                                                                           

                            
                                                                               



                     
 




















































































                                                                                                          



                                                                                          




                                                 



                                                                                               























                                                                                          







                                               




                                              







                                                 













                                               


                                           








                                                                                                  

            























































                                                                              


















                                                   
















                                           
                                                                                            
















                                                  
                                                                     
                                                  
                                                                           
                                                     
                                                                         
                                                    
                                                                        
                           
                                                                                            



































                                                                                               
                   



















































                                                                                                                







































                                    

                                                        





                                       

                      


























































                                                                                                                                                                                                                                                                  
                                                              



                                                                     

                           






                                                                     








                               
                              

      
                  

                             








                                         
                                      
        




                                            
                        

                                         








                                                                          

                                                                                  
                                                                       
                         





                                                      
                          
            
                                                                                                                         
                                                                         
                           



                                              
                                                                                                                           
                                                                   
                                                                         
                           






                                                           
                                                                                                                                        
                                                                     
                                                                           
                             


                                                      
                                                             
              
                                                                                                                                            
                                                                     
                                                                           





                                                                                                                                            
                                                                     
                                                                           


                             








                                                                       
                                                                                                                                               
                                                                     
                                                                           
                             















                                                                                                                          
                                                                     
                       


                                                                                                                        
                                                                     
                       
       
 

                                     














                                                                                                            
                           

























































                                                                              
                                                               
              


                               
                






                                                                                     
               
 


                                                                                                            










                                                            











                                                                                 


                                                                                                                           


     






                                                                 
                                               
                     



                     
                     


                          
                   






                                                                





                                               
 


                 
 

                                
 


                         
 

                              
 

                                               
 











                                                                                     
         
 





                               
     




                                                                              


                         
 


                                                                              
 




                              
                

                            
 











                                                    
 



















                                                                                                            
            















                                                                                       
           
 

                          















                                                                                                                   










                                                                                                  














                                                                                                       

                                 
                                                                
                             
                                                                                                                       
                            

                                                                                                                
              
                                                                                                                                                                                                                

                                

               

                                        










                                                                                                                                                                                                                          
           
 


                                                                                     
 

                       
 

                                 






                                   
     
                     
                                                                                        


          

                        




                       
                                                                                                   

                   



                               
                   

                                                   


                  








                                                     
       
 



















































                                                                                             










                                                                                       
 

                                                                                               




                                                                                            
 
                                                                                        

 










                                                                                                    
                                                                                                                   






                                                                              








                                                             











                                                                                                             
                                      
        
                           
          


                                
 
                                   
          
                       
 
                                

                                                     
                                                          

              


                                                                              




                              


























                                                                             
                                        

          


                                 




























                                                                                                                       
                                                                 










                                      
                                                                           











                                                                     
            

                           




























































                                                                                       
                                                              



































                                                                                                  

                                                               




                                                                                     























                                                                                                                                         

                                             




         
unit UFiles;

interface

uses USongs,
     SysUtils,
     StrUtils,
     ULog,
     UMusic,
     UDataBase;

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;
procedure SongQuality(Check: 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';

  //Quality
  Song.Quality.Syntax := 0;
  Song.Quality.BPM := 0;
  Song.Quality.NoteGaps := 0;
  Song.Quality.NoteJumps := 0;
  Song.Quality.Scores := 50;
  Song.Quality.Value := 0;
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;

  if (Ini.EnableQualityCheck=1) then
    SongQuality(Result);
end;

procedure SongQuality(Check: boolean);
var
  p, line, note:  integer;
  numLines:       integer;
  numNotes:       integer;
  firstNote:      boolean;

  lastNoteTone:   integer;
  lastNoteEnd:    integer;

  Gaps:           array[0..2] of integer; //0=total; 1=gaps with length 0; 2=gaps with length 0 + note jump
  GoldenNotes:    integer;
  gn:             real;
  Sum:            integer;

begin
  // syntax quality
  if Check then
    AktSong.Quality.Syntax := 100
  else
    AktSong.Quality.Syntax := 0;

  // BPM quality (check only 1st)
  if (AktSong.BPM[0].BPM/4 < 100) then
    AktSong.Quality.BPM := 5
  else if (AktSong.BPM[0].BPM/4 < 200) then
    AktSong.Quality.BPM := (AktSong.BPM[0].BPM/4-100)*0.95 + 5
  else if (AktSong.BPM[0].BPM/4 < 1000) then
    AktSong.Quality.BPM := 100
  else
    AktSong.Quality.BPM := 50;

  // Score quality
  p := Database.GetMaxScore(AktSong.Artist, AktSong.Title, 0);
  if (p=0) then
  begin
    p := round(Database.GetMaxScore(AktSong.Artist, AktSong.Title, 1)*1.2);
    if (p=0) then
      p := round(Database.GetMaxScore(AktSong.Artist, AktSong.Title, 2)*1.5);
  end;

  if (p=0) then
    AktSong.Quality.Scores := 50
  else
  begin
    if (p>10000) then
      p := 10000;

    AktSong.Quality.Scores := p/100;
  end;

  Gaps[0] := 0;
  Gaps[1] := 0;
  Gaps[2] := 0;

  GoldenNotes := 0;
  gn := 0;

  for p := 0 to Length(Czesci) - 1 do
  begin
    numLines := Length(Czesci[p].Czesc);
    firstNote := true;

    for line := 0 to numLines - 1 do
    begin
      numNotes := Length(Czesci[p].Czesc[line].Nuta);

      for note := 0 to numNotes - 1 do
      begin
        if (Czesci[p].Czesc[line].Nuta[note].Wartosc = 2) then
          Inc(GoldenNotes);

        if not Czesci[p].Czesc[line].Nuta[note].FreeStyle then
        begin
          if firstNote then
            firstNote := false
          else
          begin
            Gaps[0] := Gaps[0] + 1;
            if (lastNoteEnd = Czesci[p].Czesc[line].Nuta[note].Start) then
            begin
              Gaps[1] := Gaps[1] + 1;
              if (abs(lastNoteTone - Czesci[p].Czesc[line].Nuta[note].Ton) > 1) then
                Gaps[2] := Gaps[2] + 1;
            end;
          end;

          lastNoteTone := Czesci[p].Czesc[line].Nuta[note].Ton;
          lastNoteEnd := Czesci[p].Czesc[line].Nuta[note].Start + Czesci[p].Czesc[line].Nuta[note].Dlugosc;
        end;
      end;
    end;
  end;

  if (Gaps[0]>0) then
  begin
    AktSong.Quality.NoteGaps := 100 * (1-Gaps[1]/Gaps[0]);
    AktSong.Quality.NoteJumps := 100 * (1-Gaps[2]/Gaps[0]);
    gn := 100 * GoldenNotes/(Gaps[0]+1);
  end;

  Sum := Ini.Qualityfactors[0] + Ini.Qualityfactors[1] + Ini.Qualityfactors[2] +
    Ini.Qualityfactors[3] + Ini.Qualityfactors[4];

  if (Sum>0) then
    AktSong.Quality.Value := (
      AktSong.Quality.Syntax * Ini.Qualityfactors[0] +
      AktSong.Quality.BPM  * Ini.Qualityfactors[1] +
      AktSong.Quality.NoteGaps * Ini.Qualityfactors[2] +
      AktSong.Quality.NoteJumps * Ini.Qualityfactors[3] +
      AktSong.Quality.Scores * Ini.Qualityfactors[4]) / Sum
  else
    AktSong.Quality.Value := 0;

  Log.LogSongQuality(AktSong.Artist, AktSong.Title, AktSong.Quality.Syntax, AktSong.Quality.BPM, AktSong.Quality.NoteGaps,
    AktSong.Quality.NoteJumps, AktSong.Quality.Scores, AktSong.Quality.Value, gn);
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.