aboutsummaryrefslogblamecommitdiffstats
path: root/Game/Code/Classes/USong_Txt.pas
blob: a0259bafcf67bb4aa35fa273bc7faf60223f84d6 (plain) (tree)


































































































































































































































































































































































                                                                                                                                 
       






                                              

               















                                                                         
              



                                                    
              
    





                                                             
    
                                                                     
             


                                                                                
              




                                                                             







                                                                                                              
              
            


                                                                                      
          

                    













                                        
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.