From 2eb92f1b07828c0631d297bd26cfb249ccbd20ef Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 5 Feb 2008 16:26:40 +0000 Subject: - DirWatch-support with FPC (windows only) - added UAudioCore_Bass.pas git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@823 b956fd51-792f-4845-bead-9b4dfca2ff2c --- Game/Code/Classes/UAudioCore_Bass.pas | 116 +++ Game/Code/Classes/USong.pas | 1459 ++++++++++++++++----------------- Game/Code/Classes/USongs.pas | 8 +- 3 files changed, 845 insertions(+), 738 deletions(-) create mode 100644 Game/Code/Classes/UAudioCore_Bass.pas (limited to 'Game/Code/Classes') diff --git a/Game/Code/Classes/UAudioCore_Bass.pas b/Game/Code/Classes/UAudioCore_Bass.pas new file mode 100644 index 00000000..55148f95 --- /dev/null +++ b/Game/Code/Classes/UAudioCore_Bass.pas @@ -0,0 +1,116 @@ +unit UAudioCore_Bass; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + Classes, + SysUtils; + +type + TAudioCore_Bass = class + private + public + class function ErrorGetString(): string; overload; + class function ErrorGetString(errCode: integer): string; overload; + end; + + +implementation + +uses + UMain, + ULog, + bass; + +class function TAudioCore_Bass.ErrorGetString(): string; +begin + ErrorGetString(BASS_ErrorGetCode()); +end; + +class function TAudioCore_Bass.ErrorGetString(errCode: integer): string; +begin + case errCode of + BASS_OK: + result := 'No error'; + BASS_ERROR_MEM: + result := 'Insufficient memory'; + BASS_ERROR_FILEOPEN: + result := 'File could not be opened'; + BASS_ERROR_DRIVER: + result := 'Device driver not available'; + BASS_ERROR_BUFLOST: + result := 'Buffer lost'; + BASS_ERROR_HANDLE: + result := 'Invalid Handle'; + BASS_ERROR_FORMAT: + result := 'Sample-Format not supported'; + BASS_ERROR_POSITION: + result := 'Illegal position'; + BASS_ERROR_INIT: + result := 'BASS_Init has not been successfully called'; + BASS_ERROR_START: + result := 'Paused/stopped'; + BASS_ERROR_ALREADY: + result := 'Already created/used'; + BASS_ERROR_NOPAUSE: + result := 'No pause'; + BASS_ERROR_NOCHAN: + result := 'No free channels'; + BASS_ERROR_ILLTYPE: + result := 'Type is invalid'; + BASS_ERROR_ILLPARAM: + result := 'Illegal parameter'; + BASS_ERROR_NO3D: + result := 'No 3D support'; + BASS_ERROR_NOEAX: + result := 'No EAX support'; + BASS_ERROR_DEVICE: + result := 'Invalid device number'; + BASS_ERROR_NOPLAY: + result := 'Channel not playing'; + BASS_ERROR_FREQ: + result := 'Freq out of range'; + BASS_ERROR_NOTFILE: + result := 'Not a file stream'; + BASS_ERROR_NOHW: + result := 'No hardware support'; + BASS_ERROR_EMPTY: + result := 'Is empty'; + BASS_ERROR_NONET: + result := 'Network unavailable'; + BASS_ERROR_CREATE: + result := 'Creation error'; + BASS_ERROR_NOFX: + result := 'DX8 effects unavailable'; + BASS_ERROR_PLAYING: + result := 'Channel is playing'; + BASS_ERROR_NOTAVAIL: + result := 'Not available'; + BASS_ERROR_DECODE: + result := 'Is a decoding channel'; + BASS_ERROR_DX: + result := 'Insufficient version of DirectX'; + BASS_ERROR_TIMEOUT: + result := 'Timeout'; + BASS_ERROR_FILEFORM: + result := 'File-Format not recognised/supported'; + BASS_ERROR_SPEAKER: + result := 'Requested speaker(s) not support'; + BASS_ERROR_VERSION: + result := 'Version error'; + BASS_ERROR_CODEC: + result := 'Codec not available/supported'; + BASS_ERROR_UNKNOWN: + result := 'Unknown error'; + else + result := 'Unknown error'; + end; +end; + +end. \ No newline at end of file diff --git a/Game/Code/Classes/USong.pas b/Game/Code/Classes/USong.pas index e5777c75..39220f1c 100644 --- a/Game/Code/Classes/USong.pas +++ b/Game/Code/Classes/USong.pas @@ -1,733 +1,726 @@ -unit USong; - -interface - -{$IFDEF FPC} - {$MODE Delphi} -{$ENDIF} - -{$I switches.inc} - -uses - {$IFDEF MSWINDOWS} - Windows, - {$ifdef Delphi} - DirWatch, - {$endif} - {$ELSE} - {$IFNDEF DARWIN} -// oldlinux, - syscall, - {$ENDIF} - baseunix, - UnixType, - {$ENDIF} - SysUtils, - Classes, - UPlatform, - ULog, - UTexture, - UCommon, - {$IFDEF DARWIN} - cthreads, - {$ENDIF} - {$IFDEF USE_PSEUDO_THREAD} - PseudoThread, - {$ENDIF} - UCatCovers; - -type - - - TSingMode = ( smNormal, smPartyMode, smPlaylistRandom ); - - TBPM = record - BPM: real; - StartBeat: real; - end; - - TScore = record - Name: widestring; - Score: integer; - Length: string; - end; - - TSong = class - - FileLineNo : integer; //Line which is readed at Last, for error reporting - - procedure ParseNote(NrCzesci: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: string); - procedure NewSentence(NrCzesciP: integer; Param1, Param2: integer); - - function ReadTXTHeader( const aFileName : WideString ): boolean; - public - - Path: widestring; - Folder: widestring; // for sorting by folder - fFileName, - FileName: widestring; - - // sorting methods - Category: array of widestring; // I think I won't need this - Genre: widestring; - Edition: widestring; - Language: widestring; // 0.5.0: new - - Title: widestring; - Artist: widestring; - - Text: widestring; - Creator: widestring; - - Cover: widestring; - CoverTex: TTexture; - Mp3: widestring; - Background: widestring; - Video: widestring; - VideoGAP: real; - VideoLoaded: boolean; // 0.5.0: true if the video has been loaded - NotesGAP: integer; - Start: real; // in seconds - Finish: integer; // in miliseconds - Relative: boolean; - Resolution: integer; - BPM: array of TBPM; - GAP: real; // in miliseconds - - Score: array[0..2] of array of TScore; - - // these are used when sorting is enabled - Visible: boolean; // false if hidden, true if visible - Main: boolean; // false for songs, true for category buttons - OrderNum: integer; // has a number of category for category buttons and songs - OrderTyp: integer; // type of sorting for this button (0=name) - CatNumber: integer; // Count of Songs in Category for Cats and Number of Song in Category for Songs - - SongFile: TextFile; // all procedures in this unit operates on this file - - Base : array[0..1] of integer; - Rel : array[0..1] of integer; - Mult : integer; - MultBPM : integer; - - constructor create ( const aFileName : WideString ); - function LoadSong: boolean; - function Analyse(): boolean; - procedure clear(); - end; - -implementation - -uses - TextGL, - UIni, - UMusic, // needed for Czesci .. ( whatever that is ) - UMain; //needed for Player - -constructor TSong.create( const aFileName : WideString ); -begin - - Mult := 1; - - MultBPM := 4; - - - fFileName := aFileName; - - - if fileexists( aFileName ) then - - begin - - self.Path := ExtractFilePath( aFileName ); - self.Folder := ExtractFilePath( aFileName ); - self.FileName := ExtractFileName( aFileName ); - -(* - - if ReadTXTHeader( aFileName ) then - - begin - - LoadSong(); - - end - else - begin - Log.LogError('Error Loading SongHeader, abort Song Loading'); - Exit; - end; -*) - end; - -end; - - -function TSong.LoadSong(): boolean; - -var - TempC: char; - Tekst: string; - CP: integer; // Current Player (0 or 1) - Pet: integer; - Both: boolean; - Param1: integer; - Param2: integer; - Param3: integer; - ParamS: string; - I: Integer; -begin - Result := false; - - if not FileExists(Path + PathDelim + FileName) then - begin - Log.LogError('File not found: "' + Path + PathDelim + FileName + '"', 'WczytajCzesci'); - exit; - end; - - try - MultBPM := 4; // 4 - mnoznik dla czasu nut - Mult := 1; // 4 - dokladnosc pomiaru nut - Base[0] := 100; // high number - Czesci[0].Wartosc := 0; - self.Relative := false; - Rel[0] := 0; - CP := 0; - Both := false; - - if Length(Player) = 2 then - Both := true; - - // Open song file for reading..... - FileMode := fmOpenRead; - AssignFile(SongFile, fFileName); - Reset(SongFile); - - //Clear old Song Header - if (self.Path = '') then - self.Path := ExtractFilePath(FileName); - - if (self.FileName = '') then - self.Filename := ExtractFileName(FileName); - - Result := False; - - Reset(SongFile); - FileLineNo := 0; - //Search for Note Begining - repeat - ReadLn(SongFile, Tekst); - Inc(FileLineNo); - - if (EoF(SongFile)) then - begin //Song File Corrupted - No Notes - CloseFile(SongFile); - Log.LogError('Could not load txt File, no Notes found: ' + FileName); - Result := False; - Exit; - end; - Read(SongFile, TempC); - until ((TempC = ':') or (TempC = 'F') or (TempC = '*')); - - SetLength(Czesci, 2); - for Pet := 0 to High(Czesci) do begin - SetLength(Czesci[Pet].Czesc, 1); - Czesci[Pet].High := 0; - Czesci[Pet].Ilosc := 1; - Czesci[Pet].Akt := 0; - Czesci[Pet].Resolution := self.Resolution; - Czesci[Pet].NotesGAP := self.NotesGAP; - Czesci[Pet].Czesc[0].IlNut := 0; - Czesci[Pet].Czesc[0].HighNut := -1; - end; - - // TempC := ':'; - // TempC := Tekst[1]; // read from backup variable, don't use default ':' value - - while (TempC <> 'E') AND (not EOF(SongFile)) do - begin - - if (TempC = ':') or (TempC = '*') or (TempC = 'F') then begin - // wczytuje nute - Read(SongFile, Param1); - Read(SongFile, Param2); - Read(SongFile, Param3); - Read(SongFile, ParamS); - - // dodaje nute - if not Both then - // P1 - ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS) - else begin - // P1 + P2 - ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS); - ParseNote(1, TempC, (Param1+Rel[1]) * Mult, Param2 * Mult, Param3, ParamS); - end; - end; // if - - if TempC = '-' then - begin - // reads sentence - Read(SongFile, Param1); - if self.Relative then Read(SongFile, Param2); // read one more data for relative system - - // new sentence - if not Both then - // P1 - NewSentence(0, (Param1 + Rel[0]) * Mult, Param2) - else begin - // P1 + P2 - NewSentence(0, (Param1 + Rel[0]) * Mult, Param2); - NewSentence(1, (Param1 + Rel[1]) * Mult, Param2); - end; - end; // if - - if TempC = 'B' then - begin - SetLength(self.BPM, Length(self.BPM) + 1); - Read(SongFile, self.BPM[High(self.BPM)].StartBeat); - self.BPM[High(self.BPM)].StartBeat := self.BPM[High(self.BPM)].StartBeat + Rel[0]; - - Read(SongFile, Tekst); - self.BPM[High(self.BPM)].BPM := StrToFloat(Tekst); - self.BPM[High(self.BPM)].BPM := self.BPM[High(self.BPM)].BPM * Mult * MultBPM; - end; - - - if not Both then - begin - Czesci[CP].Czesc[Czesci[CP].High].BaseNote := Base[CP]; - Czesci[CP].Czesc[Czesci[CP].High].LyricWidth := glTextWidth(PChar(Czesci[CP].Czesc[Czesci[CP].High].Lyric)); - //Total Notes Patch - Czesci[CP].Czesc[Czesci[CP].High].TotalNotes := 0; - for I := low(Czesci[CP].Czesc[Czesci[CP].High].Nuta) to high(Czesci[CP].Czesc[Czesci[CP].High].Nuta) do - begin - Czesci[CP].Czesc[Czesci[CP].High].TotalNotes := Czesci[CP].Czesc[Czesci[CP].High].TotalNotes + Czesci[CP].Czesc[Czesci[CP].High].Nuta[I].Dlugosc * Czesci[CP].Czesc[Czesci[CP].High].Nuta[I].Wartosc; - end; - //Total Notes Patch End - end else begin - for Pet := 0 to High(Czesci) do begin - Czesci[Pet].Czesc[Czesci[Pet].High].BaseNote := Base[Pet]; - Czesci[Pet].Czesc[Czesci[Pet].High].LyricWidth := glTextWidth(PChar(Czesci[Pet].Czesc[Czesci[Pet].High].Lyric)); - //Total Notes Patch - Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes := 0; - for I := low(Czesci[Pet].Czesc[Czesci[Pet].High].Nuta) to high(Czesci[Pet].Czesc[Czesci[Pet].High].Nuta) do - begin - Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes := Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes + Czesci[Pet].Czesc[Czesci[Pet].High].Nuta[I].Dlugosc * Czesci[Pet].Czesc[Czesci[Pet].High].Nuta[I].Wartosc; - end; - //Total Notes Patch End - end; - end; - - Read(SongFile, TempC); - Inc(FileLineNo); - end; // while} - - CloseFile(SongFile); - except - try - CloseFile(SongFile); - except - - end; - - Log.LogError('Error Loading File: "' + fFileName + '" in Line ' + inttostr(FileLineNo)); - exit; - end; - - Result := true; -end; - - -function TSong.ReadTXTHeader(const aFileName : WideString): boolean; - -var - Line, Identifier, Value: String; - Temp : word; - Done : byte; -begin - Result := true; - Done := 0; - - //Read first Line - ReadLn (SongFile, Line); - - if (Length(Line)<=0) then - begin - Log.LogError('File Starts with Empty Line: ' + aFileName); - Result := False; - Exit; - end; - - //Read Lines while Line starts with # or its empty - 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 - //----------- - - {$IFDEF UTF8_FILENAMES} - if ((Identifier = 'MP3') or (Identifier = 'BACKGROUND') or (Identifier = 'COVER') or (Identifier = 'VIDEO')) then - Value := Utf8Encode(Value); - {$ENDIF} - - //Title - if (Identifier = 'TITLE') then - begin - self.Title := Value; - - //Add Title Flag to Done - Done := Done or 1; - end - - //Artist - else if (Identifier = 'ARTIST') then - begin - self.Artist := Value; - - //Add Artist Flag to Done - Done := Done or 2; - end - - //MP3 File //Test if Exists - else if (Identifier = 'MP3') AND - (FileExists(self.Path + Value)) then - begin - self.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(self.BPM, 1); - self.BPM[0].StartBeat := 0; - - self.BPM[0].BPM := StrtoFloatDef(Value, 0) * Mult * MultBPM; - - if self.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)] := ','; - - self.GAP := StrtoFloatDef (Value, 0); - end - - //Cover Picture - else if (Identifier = 'COVER') then - self.Cover := Value - - //Background Picture - else if (Identifier = 'BACKGROUND') then - self.Background := Value - - // Video File - else if (Identifier = 'VIDEO') then - begin - if (FileExists(self.Path + Value)) then - self.Video := Value - else - Log.LogError('Can''t find Video File in Song: ' + aFileName); - end - - // Video Gap - else if (Identifier = 'VIDEOGAP') then - begin - // Replace . with , - if (Pos('.', Value) <> 0) then - Value[Pos('.', Value)] := ','; - - self.VideoGAP := StrtoFloatDef (Value, 0); - end - - //Genre Sorting - else if (Identifier = 'GENRE') then - self.Genre := Value - - //Edition Sorting - else if (Identifier = 'EDITION') then - self.Edition := Value - - //Creator Tag - else if (Identifier = 'CREATOR') then - self.Creator := Value - - //Language Sorting - else if (Identifier = 'LANGUAGE') then - self.Language := Value - - // Song Start - else if (Identifier = 'START') then - begin - // Replace . with , - if (Pos('.', Value) <> 0) then - Value[Pos('.', Value)] := ','; - - self.Start := StrtoFloatDef(Value, 0); - end - - // Song Ending - else if (Identifier = 'END') then - TryStrtoInt(Value, self.Finish) - - // Resolution - else if (Identifier = 'RESOLUTION') then - TryStrtoInt(Value, self.Resolution) - - // Notes Gap - else if (Identifier = 'NOTESGAP') then - TryStrtoInt(Value, self.NotesGAP) - // Relative Notes - else if (Identifier = 'RELATIVE') AND (uppercase(Value) = 'YES') then - self.Relative := True; - - end; - end; - - if not EOf(SongFile) then - ReadLn (SongFile, Line) - else - begin - Result := False; - Log.LogError('File Incomplete or not Ultrastar TxT (A): ' + aFileName); - break; - end; - - end; - - if self.Cover = '' then - self.Cover := platform.FindSongFile(Path, '*[CO].jpg'); - - //Check if all Required Values are given - if (Done <> 15) then - begin - Result := False; - if (Done and 8) = 0 then //No BPM Flag - Log.LogError('BPM Tag Missing: ' + self.FileName) - else if (Done and 4) = 0 then //No MP3 Flag - Log.LogError('MP3 Tag/File Missing: ' + self.FileName) - else if (Done and 2) = 0 then //No Artist Flag - Log.LogError('Artist Tag Missing: ' + self.FileName) - else if (Done and 1) = 0 then //No Title Flag - Log.LogError('Title Tag Missing: ' + self.FileName) - else //unknown Error - Log.LogError('File Incomplete or not Ultrastar TxT (B - '+ inttostr(Done) +'): ' + aFileName); - end; - -end; - -procedure TSong.ParseNote(NrCzesci: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: string); -var - Space: boolean; -begin - case Ini.Solmization of - 1: // european - begin - case (NoteP mod 12) of - 0..1: LyricS := ' do '; - 2..3: LyricS := ' re '; - 4: LyricS := ' mi '; - 5..6: LyricS := ' fa '; - 7..8: LyricS := ' sol '; - 9..10: LyricS := ' la '; - 11: LyricS := ' si '; - end; - end; - 2: // japanese - begin - case (NoteP mod 12) of - 0..1: LyricS := ' do '; - 2..3: LyricS := ' re '; - 4: LyricS := ' mi '; - 5..6: LyricS := ' fa '; - 7..8: LyricS := ' so '; - 9..10: LyricS := ' la '; - 11: LyricS := ' shi '; - end; - end; - 3: // american - begin - case (NoteP mod 12) of - 0..1: LyricS := ' do '; - 2..3: LyricS := ' re '; - 4: LyricS := ' mi '; - 5..6: LyricS := ' fa '; - 7..8: LyricS := ' sol '; - 9..10: LyricS := ' la '; - 11: LyricS := ' ti '; - end; - end; - end; // case - - with Czesci[NrCzesci].Czesc[Czesci[NrCzesci].High] do begin - SetLength(Nuta, Length(Nuta) + 1); - IlNut := IlNut + 1; - HighNut := HighNut + 1; - Muzyka.IlNut := Muzyka.IlNut + 1; - - Nuta[HighNut].Start := StartP; - if IlNut = 1 then begin - StartNote := Nuta[HighNut].Start; - if Czesci[NrCzesci].Ilosc = 1 then - Start := -100; -// Start := Nuta[HighNut].Start; - end; - - Nuta[HighNut].Dlugosc := DurationP; - Muzyka.DlugoscNut := Muzyka.DlugoscNut + Nuta[HighNut].Dlugosc; - - // back to the normal system with normal, golden and now freestyle notes - case TypeP of - 'F': Nuta[HighNut].Wartosc := 0; - ':': Nuta[HighNut].Wartosc := 1; - '*': Nuta[HighNut].Wartosc := 2; - end; - - Czesci[NrCzesci].Wartosc := Czesci[NrCzesci].Wartosc + Nuta[HighNut].Dlugosc * Nuta[HighNut].Wartosc; - - Nuta[HighNut].Ton := NoteP; - if Nuta[HighNut].Ton < Base[NrCzesci] then Base[NrCzesci] := Nuta[HighNut].Ton; - Nuta[HighNut].TonGamy := Nuta[HighNut].TonGamy mod 12; - - Nuta[HighNut].Tekst := Copy(LyricS, 2, 100); - Lyric := Lyric + Nuta[HighNut].Tekst; - - if TypeP = 'F' then - Nuta[HighNut].FreeStyle := true; - - Koniec := Nuta[HighNut].Start + Nuta[HighNut].Dlugosc; - end; // with -end; - -procedure TSong.NewSentence(NrCzesciP: integer; Param1, Param2: integer); -var -I: Integer; -begin - - // stara czesc //Alter Satz //Update Old Part - Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].BaseNote := Base[NrCzesciP]; - Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].LyricWidth := glTextWidth(PChar(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Lyric)); - - //Total Notes Patch - Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes := 0; - for I := low(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta) to high(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta) do - begin - Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes := Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta[I].Dlugosc * Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta[I].Wartosc; - end; - //Total Notes Patch End - - - // nowa czesc //Neuer Satz //Update New Part - SetLength(Czesci[NrCzesciP].Czesc, Czesci[NrCzesciP].Ilosc + 1); - Czesci[NrCzesciP].High := Czesci[NrCzesciP].High + 1; - Czesci[NrCzesciP].Ilosc := Czesci[NrCzesciP].Ilosc + 1; - Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].HighNut := -1; - - if self.Relative then - begin - Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Start := Param1; - Rel[NrCzesciP] := Rel[NrCzesciP] + Param2; - end - else - Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Start := Param1; - - Base[NrCzesciP] := 100; // high number -end; - -procedure TSong.clear(); -begin - //Main Information - Title := ''; - Artist := ''; - - //Sortings: - Genre := 'Unknown'; - Edition := 'Unknown'; - Language := 'Unknown'; //Language Patch - - //Required Information - Mp3 := ''; - {$IFDEF FPC} - setlength( BPM, 0 ); - {$ELSE} - BPM := 0; - {$ENDIF} - - GAP := 0; - Start := 0; - Finish := 0; - - //Additional Information - Background := ''; - Cover := ''; - Video := ''; - VideoGAP := 0; - NotesGAP := 0; - Resolution := 4; - Creator := ''; - -end; - -function TSong.Analyse(): boolean; -begin - Result := False; - - //Reset LineNo - FileLineNo := 0; - - //Open File and set File Pointer to the beginning - AssignFile(SongFile, self.Path + self.FileName); - - try - Reset(SongFile); - - //Clear old Song Header - self.clear; - - //Read Header - Result := self.ReadTxTHeader( FileName ) - - //And Close File - finally - CloseFile(SongFile); - end; -end; - - - -end. +unit USong; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses + {$IFDEF MSWINDOWS} + Windows, + {$ELSE} + {$IFNDEF DARWIN} + syscall, + {$ENDIF} + baseunix, + UnixType, + {$ENDIF} + SysUtils, + Classes, + UPlatform, + ULog, + UTexture, + UCommon, + {$IFDEF DARWIN} + cthreads, + {$ENDIF} + {$IFDEF USE_PSEUDO_THREAD} + PseudoThread, + {$ENDIF} + UCatCovers; + +type + + TSingMode = ( smNormal, smPartyMode, smPlaylistRandom ); + + TBPM = record + BPM: real; + StartBeat: real; + end; + + TScore = record + Name: widestring; + Score: integer; + Length: string; + end; + + TSong = class + FileLineNo : integer; //Line which is readed at Last, for error reporting + + procedure ParseNote(NrCzesci: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: string); + procedure NewSentence(NrCzesciP: integer; Param1, Param2: integer); + + function ReadTXTHeader( const aFileName : WideString ): boolean; + public + Path: widestring; + Folder: widestring; // for sorting by folder + fFileName, + FileName: widestring; + + // sorting methods + Category: array of widestring; // I think I won't need this + Genre: widestring; + Edition: widestring; + Language: widestring; // 0.5.0: new + + Title: widestring; + Artist: widestring; + + Text: widestring; + Creator: widestring; + + Cover: widestring; + CoverTex: TTexture; + Mp3: widestring; + Background: widestring; + Video: widestring; + VideoGAP: real; + VideoLoaded: boolean; // 0.5.0: true if the video has been loaded + NotesGAP: integer; + Start: real; // in seconds + Finish: integer; // in miliseconds + Relative: boolean; + Resolution: integer; + BPM: array of TBPM; + GAP: real; // in miliseconds + + Score: array[0..2] of array of TScore; + + // these are used when sorting is enabled + Visible: boolean; // false if hidden, true if visible + Main: boolean; // false for songs, true for category buttons + OrderNum: integer; // has a number of category for category buttons and songs + OrderTyp: integer; // type of sorting for this button (0=name) + CatNumber: integer; // Count of Songs in Category for Cats and Number of Song in Category for Songs + + SongFile: TextFile; // all procedures in this unit operates on this file + + Base : array[0..1] of integer; + Rel : array[0..1] of integer; + Mult : integer; + MultBPM : integer; + + constructor create ( const aFileName : WideString ); + function LoadSong: boolean; + function Analyse(): boolean; + procedure clear(); + end; + +implementation + +uses + TextGL, + UIni, + UMusic, // needed for Czesci .. ( whatever that is ) + UMain; //needed for Player + +constructor TSong.create( const aFileName : WideString ); +begin + + Mult := 1; + + MultBPM := 4; + + + fFileName := aFileName; + + + if fileexists( aFileName ) then + + begin + + self.Path := ExtractFilePath( aFileName ); + self.Folder := ExtractFilePath( aFileName ); + self.FileName := ExtractFileName( aFileName ); + +(* + + if ReadTXTHeader( aFileName ) then + + begin + + LoadSong(); + + end + else + begin + Log.LogError('Error Loading SongHeader, abort Song Loading'); + Exit; + end; +*) + end; + +end; + + +function TSong.LoadSong(): boolean; + +var + TempC: char; + Tekst: string; + CP: integer; // Current Player (0 or 1) + Pet: integer; + Both: boolean; + Param1: integer; + Param2: integer; + Param3: integer; + ParamS: string; + I: Integer; +begin + Result := false; + + if not FileExists(Path + PathDelim + FileName) then + begin + Log.LogError('File not found: "' + Path + PathDelim + FileName + '"', 'TSong.LoadSong()'); + exit; + end; + + MultBPM := 4; // multiply beat-count of note by 4 + Mult := 1; // accuracy of measurement of note + Base[0] := 100; // high number + Czesci[0].Wartosc := 0; + self.Relative := false; + Rel[0] := 0; + CP := 0; + Both := false; + + if Length(Player) = 2 then + Both := true; + + try + // Open song file for reading..... + FileMode := fmOpenRead; + AssignFile(SongFile, fFileName); + Reset(SongFile); + + //Clear old Song Header + if (self.Path = '') then + self.Path := ExtractFilePath(FileName); + + if (self.FileName = '') then + self.Filename := ExtractFileName(FileName); + + Result := False; + + Reset(SongFile); + FileLineNo := 0; + //Search for Note Begining + repeat + ReadLn(SongFile, Tekst); + Inc(FileLineNo); + + if (EoF(SongFile)) then + begin //Song File Corrupted - No Notes + CloseFile(SongFile); + Log.LogError('Could not load txt File, no Notes found: ' + FileName); + Result := False; + Exit; + end; + Read(SongFile, TempC); + until ((TempC = ':') or (TempC = 'F') or (TempC = '*')); + + SetLength(Czesci, 2); + for Pet := 0 to High(Czesci) do begin + SetLength(Czesci[Pet].Czesc, 1); + Czesci[Pet].High := 0; + Czesci[Pet].Ilosc := 1; + Czesci[Pet].Akt := 0; + Czesci[Pet].Resolution := self.Resolution; + Czesci[Pet].NotesGAP := self.NotesGAP; + Czesci[Pet].Czesc[0].IlNut := 0; + Czesci[Pet].Czesc[0].HighNut := -1; + end; + + // TempC := ':'; + // TempC := Tekst[1]; // read from backup variable, don't use default ':' value + + while (TempC <> 'E') AND (not EOF(SongFile)) do + begin + + if (TempC = ':') or (TempC = '*') or (TempC = 'F') then begin + // read notes + Read(SongFile, Param1); + Read(SongFile, Param2); + Read(SongFile, Param3); + Read(SongFile, ParamS); + + // add notes + if not Both then + // P1 + ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS) + else begin + // P1 + P2 + ParseNote(0, TempC, (Param1+Rel[0]) * Mult, Param2 * Mult, Param3, ParamS); + ParseNote(1, TempC, (Param1+Rel[1]) * Mult, Param2 * Mult, Param3, ParamS); + end; + end; // if + + if TempC = '-' then + begin + // reads sentence + Read(SongFile, Param1); + if self.Relative then Read(SongFile, Param2); // read one more data for relative system + + // new sentence + if not Both then + // P1 + NewSentence(0, (Param1 + Rel[0]) * Mult, Param2) + else begin + // P1 + P2 + NewSentence(0, (Param1 + Rel[0]) * Mult, Param2); + NewSentence(1, (Param1 + Rel[1]) * Mult, Param2); + end; + end; // if + + if TempC = 'B' then + begin + SetLength(self.BPM, Length(self.BPM) + 1); + Read(SongFile, self.BPM[High(self.BPM)].StartBeat); + self.BPM[High(self.BPM)].StartBeat := self.BPM[High(self.BPM)].StartBeat + Rel[0]; + + Read(SongFile, Tekst); + self.BPM[High(self.BPM)].BPM := StrToFloat(Tekst); + self.BPM[High(self.BPM)].BPM := self.BPM[High(self.BPM)].BPM * Mult * MultBPM; + end; + + + if not Both then + begin + Czesci[CP].Czesc[Czesci[CP].High].BaseNote := Base[CP]; + Czesci[CP].Czesc[Czesci[CP].High].LyricWidth := glTextWidth(PChar(Czesci[CP].Czesc[Czesci[CP].High].Lyric)); + //Total Notes Patch + Czesci[CP].Czesc[Czesci[CP].High].TotalNotes := 0; + for I := low(Czesci[CP].Czesc[Czesci[CP].High].Nuta) to high(Czesci[CP].Czesc[Czesci[CP].High].Nuta) do + begin + Czesci[CP].Czesc[Czesci[CP].High].TotalNotes := Czesci[CP].Czesc[Czesci[CP].High].TotalNotes + Czesci[CP].Czesc[Czesci[CP].High].Nuta[I].Dlugosc * Czesci[CP].Czesc[Czesci[CP].High].Nuta[I].Wartosc; + end; + //Total Notes Patch End + end else begin + for Pet := 0 to High(Czesci) do begin + Czesci[Pet].Czesc[Czesci[Pet].High].BaseNote := Base[Pet]; + Czesci[Pet].Czesc[Czesci[Pet].High].LyricWidth := glTextWidth(PChar(Czesci[Pet].Czesc[Czesci[Pet].High].Lyric)); + //Total Notes Patch + Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes := 0; + for I := low(Czesci[Pet].Czesc[Czesci[Pet].High].Nuta) to high(Czesci[Pet].Czesc[Czesci[Pet].High].Nuta) do + begin + Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes := Czesci[Pet].Czesc[Czesci[Pet].High].TotalNotes + Czesci[Pet].Czesc[Czesci[Pet].High].Nuta[I].Dlugosc * Czesci[Pet].Czesc[Czesci[Pet].High].Nuta[I].Wartosc; + end; + //Total Notes Patch End + end; + end; + + Read(SongFile, TempC); + Inc(FileLineNo); + end; // while} + + CloseFile(SongFile); + except + try + CloseFile(SongFile); + except + + end; + + Log.LogError('Error Loading File: "' + fFileName + '" in Line ' + inttostr(FileLineNo)); + exit; + end; + + Result := true; +end; + + +function TSong.ReadTXTHeader(const aFileName : WideString): boolean; + +var + Line, Identifier, Value: String; + Temp : word; + Done : byte; +begin + Result := true; + Done := 0; + + //Read first Line + ReadLn (SongFile, Line); + + if (Length(Line)<=0) then + begin + Log.LogError('File Starts with Empty Line: ' + aFileName); + Result := False; + Exit; + end; + + //Read Lines while Line starts with # or its empty + 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 + //----------- + + {$IFDEF UTF8_FILENAMES} + if ((Identifier = 'MP3') or (Identifier = 'BACKGROUND') or (Identifier = 'COVER') or (Identifier = 'VIDEO')) then + Value := Utf8Encode(Value); + {$ENDIF} + + //Title + if (Identifier = 'TITLE') then + begin + self.Title := Value; + + //Add Title Flag to Done + Done := Done or 1; + end + + //Artist + else if (Identifier = 'ARTIST') then + begin + self.Artist := Value; + + //Add Artist Flag to Done + Done := Done or 2; + end + + //MP3 File //Test if Exists + else if (Identifier = 'MP3') AND + (FileExists(self.Path + Value)) then + begin + self.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(self.BPM, 1); + self.BPM[0].StartBeat := 0; + + self.BPM[0].BPM := StrtoFloatDef(Value, 0) * Mult * MultBPM; + + if self.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)] := ','; + + self.GAP := StrtoFloatDef (Value, 0); + end + + //Cover Picture + else if (Identifier = 'COVER') then + self.Cover := Value + + //Background Picture + else if (Identifier = 'BACKGROUND') then + self.Background := Value + + // Video File + else if (Identifier = 'VIDEO') then + begin + if (FileExists(self.Path + Value)) then + self.Video := Value + else + Log.LogError('Can''t find Video File in Song: ' + aFileName); + end + + // Video Gap + else if (Identifier = 'VIDEOGAP') then + begin + // Replace . with , + if (Pos('.', Value) <> 0) then + Value[Pos('.', Value)] := ','; + + self.VideoGAP := StrtoFloatDef (Value, 0); + end + + //Genre Sorting + else if (Identifier = 'GENRE') then + self.Genre := Value + + //Edition Sorting + else if (Identifier = 'EDITION') then + self.Edition := Value + + //Creator Tag + else if (Identifier = 'CREATOR') then + self.Creator := Value + + //Language Sorting + else if (Identifier = 'LANGUAGE') then + self.Language := Value + + // Song Start + else if (Identifier = 'START') then + begin + // Replace . with , + if (Pos('.', Value) <> 0) then + Value[Pos('.', Value)] := ','; + + self.Start := StrtoFloatDef(Value, 0); + end + + // Song Ending + else if (Identifier = 'END') then + TryStrtoInt(Value, self.Finish) + + // Resolution + else if (Identifier = 'RESOLUTION') then + TryStrtoInt(Value, self.Resolution) + + // Notes Gap + else if (Identifier = 'NOTESGAP') then + TryStrtoInt(Value, self.NotesGAP) + // Relative Notes + else if (Identifier = 'RELATIVE') AND (uppercase(Value) = 'YES') then + self.Relative := True; + + end; + end; + + if not EOf(SongFile) then + ReadLn (SongFile, Line) + else + begin + Result := False; + Log.LogError('File Incomplete or not Ultrastar TxT (A): ' + aFileName); + break; + end; + + end; + + if self.Cover = '' then + self.Cover := platform.FindSongFile(Path, '*[CO].jpg'); + + //Check if all Required Values are given + if (Done <> 15) then + begin + Result := False; + if (Done and 8) = 0 then //No BPM Flag + Log.LogError('BPM Tag Missing: ' + self.FileName) + else if (Done and 4) = 0 then //No MP3 Flag + Log.LogError('MP3 Tag/File Missing: ' + self.FileName) + else if (Done and 2) = 0 then //No Artist Flag + Log.LogError('Artist Tag Missing: ' + self.FileName) + else if (Done and 1) = 0 then //No Title Flag + Log.LogError('Title Tag Missing: ' + self.FileName) + else //unknown Error + Log.LogError('File Incomplete or not Ultrastar TxT (B - '+ inttostr(Done) +'): ' + aFileName); + end; + +end; + +procedure TSong.ParseNote(NrCzesci: integer; TypeP: char; StartP, DurationP, NoteP: integer; LyricS: string); +var + Space: boolean; +begin + case Ini.Solmization of + 1: // european + begin + case (NoteP mod 12) of + 0..1: LyricS := ' do '; + 2..3: LyricS := ' re '; + 4: LyricS := ' mi '; + 5..6: LyricS := ' fa '; + 7..8: LyricS := ' sol '; + 9..10: LyricS := ' la '; + 11: LyricS := ' si '; + end; + end; + 2: // japanese + begin + case (NoteP mod 12) of + 0..1: LyricS := ' do '; + 2..3: LyricS := ' re '; + 4: LyricS := ' mi '; + 5..6: LyricS := ' fa '; + 7..8: LyricS := ' so '; + 9..10: LyricS := ' la '; + 11: LyricS := ' shi '; + end; + end; + 3: // american + begin + case (NoteP mod 12) of + 0..1: LyricS := ' do '; + 2..3: LyricS := ' re '; + 4: LyricS := ' mi '; + 5..6: LyricS := ' fa '; + 7..8: LyricS := ' sol '; + 9..10: LyricS := ' la '; + 11: LyricS := ' ti '; + end; + end; + end; // case + + with Czesci[NrCzesci].Czesc[Czesci[NrCzesci].High] do begin + SetLength(Nuta, Length(Nuta) + 1); + IlNut := IlNut + 1; + HighNut := HighNut + 1; + Muzyka.IlNut := Muzyka.IlNut + 1; + + Nuta[HighNut].Start := StartP; + if IlNut = 1 then begin + StartNote := Nuta[HighNut].Start; + if Czesci[NrCzesci].Ilosc = 1 then + Start := -100; +// Start := Nuta[HighNut].Start; + end; + + Nuta[HighNut].Dlugosc := DurationP; + Muzyka.DlugoscNut := Muzyka.DlugoscNut + Nuta[HighNut].Dlugosc; + + // back to the normal system with normal, golden and now freestyle notes + case TypeP of + 'F': Nuta[HighNut].Wartosc := 0; + ':': Nuta[HighNut].Wartosc := 1; + '*': Nuta[HighNut].Wartosc := 2; + end; + + Czesci[NrCzesci].Wartosc := Czesci[NrCzesci].Wartosc + Nuta[HighNut].Dlugosc * Nuta[HighNut].Wartosc; + + Nuta[HighNut].Ton := NoteP; + if Nuta[HighNut].Ton < Base[NrCzesci] then Base[NrCzesci] := Nuta[HighNut].Ton; + Nuta[HighNut].TonGamy := Nuta[HighNut].TonGamy mod 12; + + Nuta[HighNut].Tekst := Copy(LyricS, 2, 100); + Lyric := Lyric + Nuta[HighNut].Tekst; + + if TypeP = 'F' then + Nuta[HighNut].FreeStyle := true; + + Koniec := Nuta[HighNut].Start + Nuta[HighNut].Dlugosc; + end; // with +end; + +procedure TSong.NewSentence(NrCzesciP: integer; Param1, Param2: integer); +var +I: Integer; +begin + + // stara czesc //Alter Satz //Update Old Part + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].BaseNote := Base[NrCzesciP]; + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].LyricWidth := glTextWidth(PChar(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Lyric)); + + //Total Notes Patch + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes := 0; + for I := low(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta) to high(Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta) do + begin + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes := Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].TotalNotes + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta[I].Dlugosc * Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Nuta[I].Wartosc; + end; + //Total Notes Patch End + + + // nowa czesc //Neuer Satz //Update New Part + SetLength(Czesci[NrCzesciP].Czesc, Czesci[NrCzesciP].Ilosc + 1); + Czesci[NrCzesciP].High := Czesci[NrCzesciP].High + 1; + Czesci[NrCzesciP].Ilosc := Czesci[NrCzesciP].Ilosc + 1; + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].HighNut := -1; + + if self.Relative then + begin + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Start := Param1; + Rel[NrCzesciP] := Rel[NrCzesciP] + Param2; + end + else + Czesci[NrCzesciP].Czesc[Czesci[NrCzesciP].High].Start := Param1; + + Base[NrCzesciP] := 100; // high number +end; + +procedure TSong.clear(); +begin + //Main Information + Title := ''; + Artist := ''; + + //Sortings: + Genre := 'Unknown'; + Edition := 'Unknown'; + Language := 'Unknown'; //Language Patch + + //Required Information + Mp3 := ''; + {$IFDEF FPC} + setlength( BPM, 0 ); + {$ELSE} + BPM := 0; + {$ENDIF} + + GAP := 0; + Start := 0; + Finish := 0; + + //Additional Information + Background := ''; + Cover := ''; + Video := ''; + VideoGAP := 0; + NotesGAP := 0; + Resolution := 4; + Creator := ''; + +end; + +function TSong.Analyse(): boolean; +begin + Result := False; + + //Reset LineNo + FileLineNo := 0; + + //Open File and set File Pointer to the beginning + AssignFile(SongFile, self.Path + self.FileName); + + try + Reset(SongFile); + + //Clear old Song Header + self.clear; + + //Read Header + Result := self.ReadTxTHeader( FileName ) + + //And Close File + finally + CloseFile(SongFile); + end; +end; + + + +end. diff --git a/Game/Code/Classes/USongs.pas b/Game/Code/Classes/USongs.pas index 53d5556f..97e8d073 100644 --- a/Game/Code/Classes/USongs.pas +++ b/Game/Code/Classes/USongs.pas @@ -17,9 +17,7 @@ interface uses {$IFDEF MSWINDOWS} Windows, - {$IFDEF Delphi} - DirWatch, - {$ENDIF} + DirWatch, {$ELSE} {$IFNDEF DARWIN} syscall, @@ -66,7 +64,7 @@ type fWatch : longint; fParseSongDirectory : boolean; fProcessing : boolean; - {$ifdef Delphi} + {$ifdef MSWINDOWS} fDirWatch : TDirectoryWatch; {$endif} procedure int_LoadSongList; @@ -151,7 +149,7 @@ begin SongList := TList.create(); - {$ifdef Delphi} + {$ifdef MSWINDOWS} fDirWatch := TDirectoryWatch.create(nil); fDirWatch.OnChange := DoDirChanged; fDirWatch.Directory := SongPath; -- cgit v1.2.3