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 (notenextBeat) 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.