aboutsummaryrefslogblamecommitdiffstats
path: root/Game/Code/Classes/ULyrics.pas
blob: 305cb91f250a89f31dcc73739abf2897dfc8151c (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

















                 



                                                    









                                                                  







                                                                                             


                                                                                                                           


                                                                                                                      




                                                                         



                      
                           

                                                                      
                                                                                                  
 

                                                                                
 

                                                                                                                          
 
                                              

                                                                        


                                                



                                                                                        























                                                                                                                                
                                                                     




                               
                                                        
                                                                        
                                                                                                            









                                                                                                 

                                            



                                                                          








              
          

























                                                                           
              
 

































                                                   



                                        
                                





                  
                     
 



                                 
                          
                    

    
                                                                      

         
  








                              
 






                         
                                
     


                 





                                                      
                                                               



                            
                     
 






                                                                
                                    

             
 
                                 
       


                                         
 

                                                                        
 



                                                                       

        
 
     
 






                                                                                                                  
                    



                                                                                                                                     
 








                                    





                                                                             
                 
                                            
   
                        
             


                      

                                                  

                                                 
 
                                                       
 



                                                                                                    
 

                                      
                         
       

                                        
                            
                                  
                            
        
         
                              
                             
                        



                                          



                            

      




                                                   

                                             




                                                              
                                         
 


                                                  




                                                                          
 
                                                                                             
                                                                               
 



                                              
                                      
          
        
 





                                    
                                













                                              
 
























                                                                                  
                                                        
 







                                                                   
         






                                                                                    
           



                                                                 
 





                                                                              
 




                                         
                            










                                                                                    

          


                                                
                                         
                                                                        

                                                                                
 

                           
 



                                                                                  
 
                         







                                                                              
                                        







                                                                
                                              
     

                                                                          




                                                                
                                                                                               

                 
     



                  
 














                                                                       




                                                                                   
                                                           
     














                                                           




                                                                 
                                                                                               
   







                                                                                                       
                                                 
 
               

                                                                

     






                                                                
 

                                                        

                                        



                                         
 



                                                                                               

                                   

                                 

                               

                                                            
             
 

                                                                          
       












                                                                       
 
                       
 



























                                                                              
         
                                                              
                                 
        
 
                             
         

                                                          
           






                                         

          
 


                                                      
                                           

                                       



                                           



                                                                                              
 
                      










                                                                


                            
                                    
                      










                                                              
          
 




                                                
                                           
         
                                    


                                                                          
                        










                                                                  
            
 
                                    
                                            
       
 





                                         







                                                                          
                        










                                                                                  
            
 




                             

                                
                                



                                                                            


       


                                                                           


                                                      
                                           
 





                                               
                      










                                                       






                             


                                                         




                                                 


                                                         



                                                 
 

















                                                            

    
unit ULyrics;

interface

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

{$I switches.inc}

uses
  gl,
  glext,
  UTexture,
  UThemes,
  UMusic;

type
  // stores two textures for enabled/disabled states
  TPlayerIconTex = array [0..1] of TTexture;

  PLyricWord = ^TLyricWord;
  TLyricWord = record
    X:          Real;     // left corner
    Width:      Real;     // width
    Start:      Cardinal; // start of the word in quarters (beats)
    Length:     Cardinal; // length of the word in quarters
    Text:       String;   // text
    Freestyle:  Boolean;  // is freestyle?
  end;
  ALyricWord = array of TLyricWord;

  TLyricLine = class
    public
      Text:           String;       // text
      Tex:            glUInt;       // texture of the text
      Width:          Real;         // width
      Size:           Byte;         // fontsize
      Words:          ALyricWord;   // words in this line
      CurWord:        Integer;      // current active word idx (only valid if line is active)
      Start:          Integer;      // start of this line in quarters (Note: negative start values are possible due to gap)
      StartNote:      Integer;      // start of the first note of this line in quarters
      Length:         Integer;      // length in quarters (from start of first to the end of the last note)
      HasFreestyle:   Boolean;      // one or more word are freestyle?
      CountFreestyle: Integer;      // how often there is a change from freestyle to non freestyle in this line
      Players:        Byte;         // players that should sing that line (bitset, Player1: 1, Player2: 2, Player3: 4)
      LastLine:       Boolean;      // is this the last line of the song?

      constructor Create();
      destructor Destroy(); override;
      procedure Reset();
  end;

  TLyricEngine = class
    private
      LastDrawBeat:   Real;
      UpperLine:      TLyricLine;    // first line displayed (top)
      LowerLine:      TLyricLine;    // second lind displayed (bottom)
      QueueLine:      TLyricLine;    // third line (will be displayed when lower line is finished)

      IndicatorTex:   TTexture;      // texture for lyric indikator
      BallTex:        TTexture;      // texture of the ball for the lyric effect

      QueueFull:      Boolean;       // set to true if the queue is full and a line will be replaced with the next AddLine
      LCounter:       Word;          // line counter

      // duet mode - textures for player icons
      // FIXME: do not use a fixed player count, use MAX_PLAYERS instead
      PlayerIconTex:  array[0..5] of TPlayerIconTex;

      //Some helper Procedures for Lyric Drawing
      procedure DrawLyrics (Beat: Real);
      procedure DrawLyricsLine(X, W, Y: Real; Size: Byte; Line: TLyricLine; Beat: Real);
      procedure DrawPlayerIcon(Player: Byte; Enabled: Boolean; X, Y, Size, Alpha: Real);
      procedure DrawBall(XBall, YBall, Alpha:Real);

    public
      // positions, line specific settings
      UpperLineX:     Real;       //X Start Pos of UpperLine
      UpperLineW:     Real;       //Width of UpperLine with Icon(s) and Text
      UpperLineY:     Real;       //Y Start Pos of UpperLine
      UpperLineSize:  Byte;       //Max Size of Lyrics Text in UpperLine

      LowerLineX:     Real;       //X Start Pos of LowerLine
      LowerLineW:     Real;       //Width of LowerLine with Icon(s) and Text
      LowerLineY:     Real;       //Y Start Pos of LowerLine
      LowerLineSize:  Byte;       //Max Size of Lyrics Text in LowerLine

      // display propertys
      LineColor_en:   TRGBA;      //Color of Words in an Enabled Line
      LineColor_dis:  TRGBA;      //Color of Words in a Disabled Line
      LineColor_act:  TRGBA;      //Color of teh active Word
      FontStyle:      Byte;       //Font for the Lyric Text
      FontReSize:     Boolean;    //ReSize Lyrics if they don't fit Screen
      
      { // currently not used
       FadeInEffect:   Byte;       //Effect for Line Fading in: 0: No Effect; 1: Fade Effect; 2: Move Upwards from Bottom to Pos
       FadeOutEffect:  Byte;       //Effect for Line Fading out: 0: No Effect; 1: Fade Effect; 2: Move Upwards
      }

      UseLinearFilter: Boolean;    //Should Linear Tex Filter be used

      // song specific settings
      BPM:            Real;
      Resolution:     Integer;

      // properties to easily read options of this class
      property IsQueueFull: Boolean read QueueFull;    // line in queue?
      property LineCounter: Word    read LCounter;   // lines that were progressed so far (after last clear)

      procedure AddLine(Line: PLine);              // adds a line to the queue, if there is space
      procedure Draw (Beat: Real);                 // draw the current (active at beat) lyrics

      // clears all cached song specific information
      procedure Clear(cBPM: Real = 0; cResolution: Integer = 0);

      function GetUpperLine(): TLyricLine;
      function GetLowerLine(): TLyricLine;

      function GetUpperLineIndex(): Integer;

      constructor Create; overload;
      constructor Create(ULX,ULY,ULW,ULS,LLX,LLY,LLW,LLS: Real); overload;
      procedure   LoadTextures;
      destructor  Destroy; override;
  end;

implementation

uses SysUtils,
     USkins,
     TextGL,
     UGraphic,
     UDisplay,
     ULog,
     math,
     UIni;

//-----------
//Helper procs to use TRGB in Opengl ...maybe this should be somewhere else
//-----------
procedure glColorRGB(Color: TRGB);  overload;
begin
  glColor3f(Color.R, Color.G, Color.B);
end;

procedure glColorRGB(Color: TRGB; Alpha: Real);  overload;
begin
  glColor4f(Color.R, Color.G, Color.B, Alpha);
end;

procedure glColorRGB(Color: TRGBA); overload;
begin
  glColor4f(Color.R, Color.G, Color.B, Color.A);
end;

procedure glColorRGB(Color: TRGBA; Alpha: Real); overload;
begin
  glColor4f(Color.R, Color.G, Color.B, Min(Color.A, Alpha));
end;

{ TLyricLine }

constructor TLyricLine.Create();
begin
  inherited;
  Reset();
end;

destructor TLyricLine.Destroy();
begin
  SetLength(Words, 0);
  inherited;
end;

procedure TLyricLine.Reset();
begin
  Start     := 0;
  StartNote := 0;
  Length    := 0;
  LastLine  := False;

  Text      := '';
  Width     := 0;

  // duet mode: players of that line (default: all)
  Players   := $FF;

  SetLength(Words, 0);
  CurWord   := -1;

  HasFreestyle := False;
  CountFreestyle := 0;
end;


{ TLyricEngine }

//---------------
// Create - Constructor, just get Memory
//---------------
constructor TLyricEngine.Create;
begin
  inherited;

  BPM := 0;
  Resolution := 0;
  LCounter := 0;
  QueueFull := False;

  UpperLine := TLyricLine.Create;
  LowerLine := TLyricLine.Create;
  QueueLine := TLyricLine.Create;

  UseLinearFilter := True;
  LastDrawBeat := 0;
end;

constructor TLyricEngine.Create(ULX,ULY,ULW,ULS,LLX,LLY,LLW,LLS:Real);
begin
  Create;
  
  UpperLineX := ULX;
  UpperLineW := ULW;
  UpperLineY := ULY;
  UpperLineSize := Trunc(ULS);

  LowerLineX := LLX;
  LowerLineW := LLW;
  LowerLineY := LLY;
  LowerLineSize := Trunc(LLS);

  LoadTextures;
end;


//---------------
// Destroy - Frees Memory
//---------------
destructor TLyricEngine.Destroy;
begin
  UpperLine.Free;
  LowerLine.Free;
  QueueLine.Free;
  inherited;
end;

//---------------
// Clear - Clears all cached Song specific Information
//---------------
procedure TLyricEngine.Clear(cBPM: Real; cResolution: Integer);
begin
  BPM := cBPM;
  Resolution := cResolution;
  LCounter := 0;
  QueueFull := False;

  LastDrawBeat:=0;
end;


//---------------
// LoadTextures - Load Player Textures and Create Lyric Textures
//---------------
procedure TLyricEngine.LoadTextures;
var
  I: Integer;

  function CreateLineTex: glUint;
  begin
    // generate and bind Texture
    glGenTextures(1, @Result);
    glBindTexture(GL_TEXTURE_2D, Result);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    if UseLinearFilter then
    begin
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    end;
  end;

begin

  // lyric indicator (bar that indicates when the line start)
  IndicatorTex := Texture.LoadTexture(Skin.GetTextureFileName('LyricHelpBar'), TEXTURE_TYPE_TRANSPARENT, $FF00FF);

  // ball for current word hover in ball effect
  BallTex := Texture.LoadTexture(Skin.GetTextureFileName('Ball'), TEXTURE_TYPE_TRANSPARENT, 0);

  // duet mode: load player icon
  for I := 0 to 5 do
  begin
    PlayerIconTex[I][0] := Texture.LoadTexture(Skin.GetTextureFileName('LyricIcon_P' + InttoStr(I+1)), TEXTURE_TYPE_TRANSPARENT, 0);
    PlayerIconTex[I][1] := Texture.LoadTexture(Skin.GetTextureFileName('LyricIconD_P' + InttoStr(I+1)), TEXTURE_TYPE_TRANSPARENT, 0);
  end;

  // create line textures
  UpperLine.Tex := CreateLineTex;
  LowerLine.Tex := CreateLineTex;
  QueueLine.Tex := CreateLineTex;
end;


//---------------
// AddLine - Adds LyricLine to queue
// The LyricEngine stores three lines in its queue:
//   UpperLine: the upper line displayed in the lyrics
//   LowerLine: the lower line displayed in the lyrics
//   QueueLine: an offscreen line that precedes LowerLine
// If the queue is full the next call to AddLine will replace UpperLine with
// LowerLine, LowerLine with QueueLine and QueueLine with the Line parameter.
//---------------
procedure TLyricEngine.AddLine(Line: PLine);
var
  LyricLine: TLyricLine;
  PosX: Real;
  I: Integer;
  CurWord: PLyricWord;
  RenderPass: Integer;

  function CalcWidth(LyricLine: TLyricLine): Real;
  begin
    Result := glTextWidth(PChar(LyricLine.Text));

    Result := Result + (LyricLine.CountFreestyle * 10);

    // if the line ends with a freestyle not, then leave the place to finish to draw the text italic
    if (LyricLine.Words[High(LyricLine.Words)].Freestyle) then
      Result := Result + 12;
  end;

begin
  // only add lines, if there is space
  if not IsQueueFull then
  begin
    // set LyricLine to line to write to
    if (LineCounter = 0) then
      LyricLine := UpperLine
    else if (LineCounter = 1) then
      LyricLine := LowerLine
    else
    begin
      // now the queue is full
      LyricLine := QueueLine;
      QueueFull := True;
    end;
  end
  else
  begin // rotate lines (round-robin-like)
    LyricLine  := UpperLine;
    UpperLine := LowerLine;
    LowerLine := QueueLine;
    QueueLine := LyricLine;
  end;

  // reset line state
  LyricLine.Reset();

  // check if sentence has notes
  if (Line <> nil) and (Length(Line.Note) > 0) then
  begin
    // copy values from SongLine to LyricLine
    LyricLine.Start     := Line.Start;
    LyricLine.StartNote := Line.Note[0].Start;
    LyricLine.Length    := Line.Note[High(Line.Note)].Start +
                           Line.Note[High(Line.Note)].Length -
                           Line.Note[0].Start;
    LyricLine.LastLine  := Line.LastLine;

    // copy words
    SetLength(LyricLine.Words, Length(Line.Note));
    for I := 0 to High(Line.Note) do
    begin
      LyricLine.Words[I].Start     := Line.Note[I].Start;
      LyricLine.Words[I].Length    := Line.Note[I].Length;
      LyricLine.Words[I].Text      := Line.Note[I].Text;
      LyricLine.Words[I].Freestyle := Line.Note[I].NoteType = ntFreestyle;

      LyricLine.HasFreestyle       := LyricLine.HasFreestyle or LyricLine.Words[I].Freestyle;
      LyricLine.Text               := LyricLine.Text + LyricLine.Words[I].Text;

      if (I > 0) and
         LyricLine.Words[I-1].Freestyle and
         not LyricLine.Words[I].Freestyle then
      begin
        Inc(LyricLine.CountFreestyle);
      end;
    end;

    // set font params
    SetFontStyle(FontStyle);
    SetFontPos(0, 0);
    LyricLine.Size := UpperLineSize;
    SetFontSize(LyricLine.Size);
    SetFontItalic(False);
    SetFontReflection(False, 0);
    glColor4f(1, 1, 1, 1);

    // change fontsize to fit the screen
    LyricLine.Width := CalcWidth(LyricLine);
    while (LyricLine.Width > UpperLineW) do
    begin
      Dec(LyricLine.Size);

      if (LyricLine.Size <=1) then
        Break;

      SetFontSize(LyricLine.Size);
      LyricLine.Width := CalcWidth(LyricLine);
    end;

    // Offscreen rendering of LyricTexture:
    // First we will create a white transparent background to draw on.
    // If the text was simply drawn to the screen with blending, the translucent
    // parts of the text would be merged with the current color of the background.
    // This would result in a texture that partly contains the screen we are
    // drawing on. This will be visible in the fonts outline when the LyricTexture
    // is drawn to the screen later.
    // So we have to draw the text in TWO passes.
    // At the first pass we copy the characters to the back-buffer in such a way
    // that preceding characters are not repainted by following chars (otherwise
    // some characters would be blended twice in the 2nd pass).
    // To achieve this we disable blending and enable the depth-test. The z-buffer
    // is used as a mask or replacement for the missing stencil-buffer. A z-value
    // of 1 means the pixel was not assigned yet, whereas 0 stands for a pixel
    // that was already drawn on. In addition we set the depth-func in such a way
    // that assigned pixels (0) will not be drawn a second time.
    // At the second pass we draw the text with blending and without depth-test.
    // This will blend overlapping characters but not the background as it was
    // repainted in the first pass.

    glPushAttrib(GL_VIEWPORT_BIT or GL_DEPTH_BUFFER_BIT);
    glViewPort(0, 0, 800, 600);
    glClearColor(0, 0, 0, 0);
    glDepthRange(0, 1);
    glClearDepth(1);
    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

    SetFontZ(0);
    
    // assure blending is off and the correct depth-func is enabled
    glDisable(GL_BLEND);
    glDepthFunc(GL_LESS);

    // we need two passes to draw the font onto the screen.
    for RenderPass := 0 to 1 do
    begin
      if (RenderPass = 0) then
      begin
        // first pass: simply copy each character to the screen without overlapping.
        SetFontBlend(false);
        glEnable(GL_DEPTH_TEST);
      end
      else
      begin
        // second pass: now we will blend overlapping characters.
        SetFontBlend(true);
        glDisable(GL_DEPTH_TEST);
      end;

      PosX := 0;

      // set word positions and line size and draw the line to the back-buffer
      for I := 0 to High(LyricLine.Words) do
      begin
        CurWord := @LyricLine.Words[I];

        SetFontItalic(CurWord.Freestyle);

        CurWord.X := PosX;

        // Draw Lyrics
        SetFontPos(PosX, 0);
        glPrint(PChar(CurWord.Text));

        CurWord.Width := glTextWidth(PChar(CurWord.Text));
        if CurWord.Freestyle then
        begin
          if (I < High(LyricLine.Words)) and not LyricLine.Words[I+1].Freestyle then
            CurWord.Width := CurWord.Width + 10
          else if (I = High(LyricLine.Words)) then
            CurWord.Width := CurWord.Width + 12;
        end;
        PosX := PosX + CurWord.Width;
      end;
    end;
    
    // copy back-buffer to texture
    glBindTexture(GL_TEXTURE_2D, LyricLine.Tex);
    // FIXME: this does not work this way
    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 600-64, 1024, 64, 0);
    if (glGetError() <> GL_NO_ERROR) then
      Log.LogError('Creation of Lyrics-texture failed', 'TLyricEngine.AddLine');

    // restore OpenGL state
    glPopAttrib();

    // clear buffer (use white to avoid flimmering if no cover/video is available)
    glClearColor(1, 1, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  end; // if (Line <> nil) and (Length(Line.Note) > 0)

  // increase the counter
  Inc(LCounter);
end;


//---------------
// Draw - Procedure Draws Lyrics; Beat is curent Beat in Quarters
//        Draw just manage the Lyrics, drawing is done by a call of DrawLyrics
//---------------
procedure TLyricEngine.Draw(Beat: Real);
begin
  DrawLyrics(Beat);
  LastDrawBeat := Beat;
end;

//---------------
// DrawLyrics(private) - Helper for Draw; main Drawing procedure
//---------------
procedure TLyricEngine.DrawLyrics(Beat: Real);
begin
  DrawLyricsLine(UpperLineX, UpperLineW, UpperlineY, 15, Upperline, Beat);
  DrawLyricsLine(LowerLineX, LowerLineW, LowerlineY, 15, Lowerline, Beat);
end;

//---------------
// DrawPlayerIcon(private) - Helper for Draw; Draws a Playericon
//---------------
procedure TLyricEngine.DrawPlayerIcon(Player: Byte; Enabled: Boolean; X, Y, Size, Alpha: Real);
var
  IEnabled: Byte;
begin
  if Enabled then
    IEnabled := 0
  else
    IEnabled := 1;

  glEnable(GL_TEXTURE_2D);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glBindTexture(GL_TEXTURE_2D, PlayerIconTex[Player][IEnabled].TexNum);

  glColor4f(1, 1, 1, Alpha);
  glBegin(GL_QUADS);
    glTexCoord2f(0, 0); glVertex2f(X, Y);
    glTexCoord2f(0, 1); glVertex2f(X, Y + Size);
    glTexCoord2f(1, 1); glVertex2f(X + Size, Y + Size);
    glTexCoord2f(1, 0); glVertex2f(X + Size, Y);
  glEnd;

  glDisable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);
end;

//---------------
// DrawBall(private) - Helper for Draw; Draws the Ball over the LyricLine if needed
//---------------
procedure TLyricEngine.DrawBall(XBall, YBall, Alpha: Real);
begin
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glBindTexture(GL_TEXTURE_2D, BallTex.TexNum);

  glColor4f(1, 1, 1, Alpha);
  glBegin(GL_QUADS);
    glTexCoord2f(0, 0); glVertex2f(XBall - 10, YBall);
    glTexCoord2f(0, 1); glVertex2f(XBall - 10, YBall + 20);
    glTexCoord2f(1, 1); glVertex2f(XBall + 10, YBall + 20);
    glTexCoord2f(1, 0); glVertex2f(XBall + 10, YBall);
  glEnd;

  glDisable(GL_BLEND);
  glDisable(GL_TEXTURE_2D);
end;

//---------------
// DrawLyricsLine(private) - Helper for Draw; Draws one LyricLine
//---------------
procedure TLyricEngine.DrawLyricsLine(X, W, Y: Real; Size: Byte; Line: TLyricLine; Beat: Real);
var
  CurWordStart, CurWordEnd: Real; // screen coordinates of current word and the rest of the sentence
  FreestyleDiff: Integer;         // difference between top and bottom coordiantes for freestyle lyrics
  Progress: Real;                 // progress of singing the current word
  LyricX: Real;                   // left
  LyricX2: Real;                  // right
  LyricY: Real;                   // top
  LyricsHeight: Real;             // height the lyrics are displayed
  Alpha: Real;                    // alphalevel to fade out at end
  CurWord, LastWord: PLyricWord;  // current word

  {// duet mode
  IconSize: Real;                 // size of player icons
  IconAlpha: Real;                // alpha level of player icons
  }
begin
  // do not draw empty lines
  // Note: lines with no words in it do not have a valid texture
  if (Length(Line.Words) = 0) or
     (Line.Width <= 0) then
  begin
    Exit;
  end;

  // this is actually a bit more than the real font size
  // it helps adjusting the "zoom-center"
  LyricsHeight := 30.5 * (Line.Size/10);

  {
  // duet mode
  IconSize := (2 * Size);
  IconAlpha := Frac(Beat/(Resolution*4));

  DrawPlayerIcon (0, True, X, Y + (42 - IconSize) / 2 , IconSize, IconAlpha);
  DrawPlayerIcon (1, True, X + IconSize + 1,  Y + (42 - IconSize) / 2, IconSize, IconAlpha);
  DrawPlayerIcon (2, True, X + (IconSize + 1)*2, Y + (42 - IconSize) / 2, IconSize, IconAlpha);
  }

  LyricX := X + W/2 - Line.Width/2;
  LyricX2 := LyricX + Line.Width;

  // maybe center smaller lines
  //LyricY := Y;
  LyricY := Y + ((Size / Line.Size - 1) * LyricsHeight) / 2;

  Alpha := 1;

  // check if this line is active (at least its first note must be active)
  if (Beat >= Line.StartNote) then
  begin
    // if this line just got active, CurWord is -1,
    // this means we should try to make the first word active
    if (Line.CurWord = -1) then
      Line.CurWord := 0;

    // check if the current active word is still active.
    // Otherwise proceed to the next word if there is one in this line.
    // Note: the max. value of Line.CurWord is High(Line.Words)
    if (Line.CurWord < High(Line.Words)) and
       (Beat >= Line.Words[Line.CurWord + 1].Start) then
    begin
      Inc(Line.CurWord);
    end;

    FreestyleDiff := 0;

    // determine current and last word in this line.
    // If the end of the line is reached use the last word as current word.
    LastWord := @Line.Words[High(Line.Words)];
    CurWord := @Line.Words[Line.CurWord];

    // calc the progress of the lyrics effect
    Progress := (Beat - CurWord.Start) / CurWord.Length;
    if Progress >= 1 then
      Progress := 1;
    if Progress <= 0 then
      Progress := 0;

    // last word of this line finished, but this line did not hide -> fade out
    if Line.LastLine and
       (Beat > LastWord.Start + LastWord.Length) then
    begin
      Alpha := 1 - (Beat - (LastWord.Start + LastWord.Length)) / 15;
      if (Alpha < 0) then
        Alpha := 0;
    end;

    // determine the start-/end positions of the fragment of the current word
    CurWordStart := CurWord.X;
    CurWordEnd := CurWord.X + CurWord.Width;

    // Slide Effect
    // simply paint the active texture to the current position
    if Ini.LyricsEffect = 2 then
    begin
      CurWordStart := CurWordStart + CurWord.Width * Progress;
      CurWordEnd := CurWordStart;
    end;

    if CurWord.Freestyle then
    begin
      if (Line.CurWord < High(Line.Words)) and
         (not Line.Words[Line.CurWord + 1].Freestyle) then
      begin
        FreestyleDiff := 2;
      end
      else
      begin
        FreestyleDiff := 12;
        CurWordStart := CurWordStart - 1;
        CurWordEnd := CurWordEnd - 2;
      end;
    end;

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, Line.Tex);

    // draw sentence up to current word
    // type 0: simple lyric effect
    // type 3: ball lyric effect
    // type 4: shift lyric effect
    if (Ini.LyricsEffect in [0, 3, 4]) then
      // ball lyric effect - only highlight current word and not that ones before in this line
      glColorRGB(LineColor_en, Alpha)
    else
      glColorRGB(LineColor_act, Alpha);

    glBegin(GL_QUADS);
      glTexCoord2f(0, 1);
      glVertex2f(LyricX, LyricY);

      glTexCoord2f(0, 1-LyricsHeight/64);
      glVertex2f(LyricX, LyricY + LyricsHeight);

      glTexCoord2f(CurWordStart/1024, 1-LyricsHeight/64);
      glVertex2f(LyricX + CurWordStart, LyricY + LyricsHeight);

      glTexCoord2f((CurWordStart + FreestyleDiff)/1024, 1);
      glVertex2f(LyricX + CurWordStart + FreestyleDiff, LyricY);
    glEnd;

    // draw rest of sentence
    glColorRGB(LineColor_en, Alpha);
    glBegin(GL_QUADS);
      glTexCoord2f((CurWordEnd + FreestyleDiff)/1024, 1);
      glVertex2f(LyricX + CurWordEnd + FreestyleDiff, LyricY);

      glTexCoord2f(CurWordEnd/1024, 1-LyricsHeight/64);
      glVertex2f(LyricX + CurWordEnd, LyricY + LyricsHeight);

      glTexCoord2f(Line.Width/1024, 1-LyricsHeight/64);
      glVertex2f(LyricX2, LyricY + LyricsHeight);

      glTexCoord2f(Line.Width/1024, 1);
      glVertex2f(LyricX2, LyricY);
    glEnd;

    // draw active word:
    // type 0: simple lyric effect
    // type 3: ball lyric effect
    // type 4: shift lyric effect
    // only change the color of the current word
    if (Ini.LyricsEffect in [0, 3, 4]) then
    begin
      if (Ini.LyricsEffect = 4) then
        LyricY := LyricY - 8 * (1-Progress);

      glColor4f(LineColor_act.r, LineColor_act.g, LineColor_act.b, Alpha);
      glBegin(GL_QUADS);
        glTexCoord2f((CurWordStart + FreestyleDiff)/1024, 1);
        glVertex2f(LyricX + CurWordStart + FreestyleDiff, LyricY);

        glTexCoord2f(CurWordStart/1024, 0);
        glVertex2f(LyricX + CurWordStart, LyricY + 64);

        glTexCoord2f(CurWordEnd/1024, 0);
        glVertex2f(LyricX + CurWordEnd, LyricY + 64);

        glTexCoord2f((CurWordEnd + FreestyleDiff)/1024, 1);
        glVertex2f(LyricX + CurWordEnd + FreestyleDiff, LyricY);
      glEnd;

      if (Ini.LyricsEffect = 4) then
        LyricY := LyricY + 8 * (1-Progress);
    end

    // draw active word:
    // type 1: zoom lyric effect
    // change color and zoom current word
    else if Ini.LyricsEffect = 1 then
    begin
      glPushMatrix;

      glTranslatef(LyricX + CurWordStart + (CurWordEnd-CurWordStart)/2,
                   LyricY + LyricsHeight/2, 0);

      // set current zoom factor
      glScalef(1.0 + (1-Progress) * 0.5, 1.0 + (1-Progress) * 0.5, 1.0);

      glColor4f(LineColor_act.r, LineColor_act.g, LineColor_act.b, Alpha);
      glBegin(GL_QUADS);
        glTexCoord2f((CurWordStart + FreestyleDiff)/1024, 1);
        glVertex2f(-(CurWordEnd-CurWordStart)/2 + FreestyleDiff, -LyricsHeight/2);

        glTexCoord2f(CurWordStart/1024, 1-LyricsHeight/64);
        glVertex2f(-(CurWordEnd-CurWordStart)/2, LyricsHeight/2);

        glTexCoord2f(CurWordEnd/1024, 1-LyricsHeight/64);
        glVertex2f((CurWordEnd-CurWordStart)/2,  LyricsHeight/2);

        glTexCoord2f((CurWordEnd + FreestyleDiff)/1024, 1);
        glVertex2f((CurWordEnd-CurWordStart)/2 + FreestyleDiff, -LyricsHeight/2);
      glEnd;

      glPopMatrix;
    end;

    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);

    // type 3: ball lyric effect
    if Ini.LyricsEffect = 3 then
    begin
      DrawBall(LyricX + CurWordStart + (CurWordEnd-CurWordStart) * Progress,
               LyricY - 15 - 15*sin(Progress * Pi), Alpha);
    end;
  end
  else
  begin
    // this section is called if the whole line can be drawn at once and no
    // word has to be emphasized.

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glBindTexture(GL_TEXTURE_2D, Line.Tex);

    // enable the upper, disable the lower line
    if (Line = UpperLine) then
      glColorRGB(LineColor_en)
    else
      glColorRGB(LineColor_dis);

    glBegin(GL_QUADS);
      glTexCoord2f(0, 1);
      glVertex2f(LyricX, LyricY);

      glTexCoord2f(0, 1-LyricsHeight/64);
      glVertex2f(LyricX, LyricY + LyricsHeight);

      glTexCoord2f(Line.Width/1024, 1-LyricsHeight/64);
      glVertex2f(LyricX2, LyricY + LyricsHeight);

      glTexCoord2f(Line.Width/1024, 1);
      glVertex2f(LyricX2, LyricY);
    glEnd;

    glDisable(GL_BLEND);
    glDisable(GL_TEXTURE_2D);
  end;
end;

//---------------
// GetUpperLine() - Returns a reference to the upper line
//---------------
function TLyricEngine.GetUpperLine(): TLyricLine;
begin
  Result := UpperLine;
end;

//---------------
// GetLowerLine() - Returns a reference to the lower line
//---------------
function TLyricEngine.GetLowerLine(): TLyricLine;
begin
  Result := LowerLine;
end;

//---------------
// GetUpperLineIndex() - Returns the index of the upper line
//---------------
function TLyricEngine.GetUpperLineIndex(): Integer;
const
  QUEUE_SIZE = 3;
begin
  // no line in queue
  if (LineCounter <= 0) then
    Result := -1
  // no line has been removed from queue yet
  else if (LineCounter <= QUEUE_SIZE) then
    Result := 0
  // lines have been removed from queue already
  else
    Result := LineCounter - QUEUE_SIZE;
end;

end.