From 517e6872ed12e6a9144c4fbd0b02063feeab5528 Mon Sep 17 00:00:00 2001
From: tobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>
Date: Sat, 19 Jul 2008 12:48:23 +0000
Subject: lyric engine patches, solves - pixeled/transparent fonts bug - some
 lines were erroneously marked active or disabled - state was not initialized
 correctly - negative gap problem (TLyricLine.Start was of type cardinal) -
 some more lyrics bugs

git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1211 b956fd51-792f-4845-bead-9b4dfca2ff2c
---
 Game/Code/Classes/UDraw.pas           |  46 +--
 Game/Code/Classes/ULyrics.pas         | 700 ++++++++++++++++++++--------------
 Game/Code/Classes/UMain.pas           |  77 +---
 Game/Code/Classes/UMusic.pas          |  81 +++-
 Game/Code/Screens/UScreenSing.pas     | 112 +++---
 Game/Code/Screens/UScreenSingModi.pas |  11 +-
 6 files changed, 568 insertions(+), 459 deletions(-)

(limited to 'Game')

diff --git a/Game/Code/Classes/UDraw.pas b/Game/Code/Classes/UDraw.pas
index 24a52264..a5bd8afa 100644
--- a/Game/Code/Classes/UDraw.pas
+++ b/Game/Code/Classes/UDraw.pas
@@ -434,7 +434,7 @@ begin
         // Perfect note is stored
         if Perfect and (Ini.EffectSing=1) then
         begin
-          A := 1 - 2*(LineState.CurrentTime - GetTimeFromBeat(Start+Length));
+          A := 1 - 2*(LineState.GetCurrentTime() - GetTimeFromBeat(Start+Length));
           if not (Start+Length-1 = LineState.CurrentBeatD) then
           begin
             //Star animation counter
@@ -1336,13 +1336,15 @@ begin
 end;
 
 procedure SingDrawTimeBar();
-var x,y:           real;
-    width, height: real;
-    lTmp         : real;
+var
+  x,y:            real;
+  width, height:  real;
+  LyricsProgress: real;
+  CurLyricsTime:  real;
 begin
   x := Theme.Sing.StaticTimeProgress.x;
   y := Theme.Sing.StaticTimeProgress.y;
-  
+
   width  := Theme.Sing.StaticTimeProgress.w;
   height := Theme.Sing.StaticTimeProgress.h;
 
@@ -1356,30 +1358,28 @@ begin
   glBindTexture(GL_TEXTURE_2D, Tex_TimeProgress.TexNum);
 
   glBegin(GL_QUADS);
-  try
     glTexCoord2f(0, 0);
-    glVertex2f(x,y);
-    
-    if ( LineState.CurrentTime > 0 ) AND
-       ( LineState.TotalTime > 0 ) THEN
-    BEGIN
-      lTmp := LineState.CurrentTime/LineState.TotalTime;
-      glTexCoord2f((width*LineState.CurrentTime/LineState.TotalTime)/8, 0);
-      glVertex2f(x+width*LineState.CurrentTime/LineState.TotalTime, y);
-
-      glTexCoord2f((width*LineState.CurrentTime/LineState.TotalTime)/8, 1);
-      glVertex2f(x+width*LineState.CurrentTime/LineState.TotalTime, y+height);
-    END;
+    glVertex2f(x, y);
+
+    CurLyricsTime := LineState.GetCurrentTime();
+    if (CurLyricsTime > 0) and
+       (LineState.TotalTime > 0) then
+    begin
+      LyricsProgress := CurLyricsTime / LineState.TotalTime;
+      glTexCoord2f((width * LyricsProgress) / 8, 0);
+      glVertex2f(x + width * LyricsProgress, y);
+
+      glTexCoord2f((width * LyricsProgress) / 8, 1);
+      glVertex2f(x + width * LyricsProgress, y + height);
+    end;
 
     glTexCoord2f(0, 1);
-    glVertex2f(x, y+height);
-  finally
-    glEnd;
-  end;
+    glVertex2f(x, y + height);
+  glEnd;
 
  glDisable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);
- glcolor4f(1,1,1,1);
+ glcolor4f(1, 1, 1, 1);
 end;
 
 end.
diff --git a/Game/Code/Classes/ULyrics.pas b/Game/Code/Classes/ULyrics.pas
index e5427773..eced2991 100644
--- a/Game/Code/Classes/ULyrics.pas
+++ b/Game/Code/Classes/ULyrics.pas
@@ -16,6 +16,10 @@ uses
   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
@@ -34,34 +38,35 @@ type
       Size:           Byte;         // fontsize
       Words:          ALyricWord;   // words in this line
       CurWord:        Integer;      // current active word idx (only valid if line is active)
-      Start:          Cardinal;     // start of this line in quarters
-      Length:         Cardinal;     // length in quarters
+      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)
-      Done:           Boolean;      // is sentence already sung?
-      LastLine:       Boolean;      // is this the last line ob the song?
+      LastLine:       Boolean;      // is this the last line of the song?
+
+      constructor Create();
+      destructor Destroy(); override;
+      procedure Reset();
   end;
 
   TLyricEngine = class
     private
-      EoLastSentence: Real;          // end of the previous sentence (in beats)
-      LastDrawBeat: Real;
+      LastDrawBeat:   Real;
       UpperLine:      TLyricLine;    // first line displayed (top)
       LowerLine:      TLyricLine;    // second lind displayed (bottom)
-      QueueLine:      TLyricLine;    // third line (queue and will be displayed when next line is finished)
+      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
+      IndicatorTex:   TTexture;      // texture for lyric indikator
+      BallTex:        TTexture;      // texture of the ball for the lyric effect
 
-      inQueue:        Boolean;        // is line in queue
-      LCounter:       Word;           // line counter
+      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
-      PlayerIconTex:  array[0..5] of  // player idx
-                      array [0..1] of // enabled disabled
-                      TTexture;
-
+      // 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);
@@ -93,14 +98,14 @@ type
        FadeOutEffect:  Byte;       //Effect for Line Fading out: 0: No Effect; 1: Fade Effect; 2: Move Upwards
       }
 
-      UseLinearFilter:Boolean;    //Should Linear Tex Filter be used
+      UseLinearFilter: Boolean;    //Should Linear Tex Filter be used
 
       // song specific settings
       BPM:            Real;
       Resolution:     Integer;
 
       // properties to easily read options of this class
-      property LineinQueue: Boolean read inQueue;    // line in queue?
+      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
@@ -112,6 +117,8 @@ type
       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;
@@ -151,7 +158,42 @@ 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
@@ -163,18 +205,14 @@ begin
   BPM := 0;
   Resolution := 0;
   LCounter := 0;
-  inQueue := False;
+  QueueFull := False;
 
   UpperLine := TLyricLine.Create;
   LowerLine := TLyricLine.Create;
   QueueLine := TLyricLine.Create;
 
-  UpperLine.Done := True;
-  LowerLine.Done := True;
-  QueueLine.Done := True;
-
   UseLinearFilter := True;
-  LastDrawBeat:=0;
+  LastDrawBeat := 0;
 end;
 
 constructor TLyricEngine.Create(ULX,ULY,ULW,ULS,LLX,LLY,LLW,LLS:Real);
@@ -214,12 +252,8 @@ begin
   BPM := cBPM;
   Resolution := cResolution;
   LCounter := 0;
-  inQueue := False;
+  QueueFull := False;
 
-  UpperLine.Done := True;
-  LowerLine.Done := True;
-  QueueLine.Done := True;
-  
   LastDrawBeat:=0;
 end;
 
@@ -230,37 +264,25 @@ end;
 procedure TLyricEngine.LoadTextures;
 var
   I: Integer;
-  
+
   function CreateLineTex: glUint;
-  var
-    PTexData: Pointer;
   begin
-    try
-      // get memory
-      GetMem(pTexData, 1024*64*4); 
-
-      // generate and bind Texture
-      glGenTextures(1, @Result);
-      glBindTexture(GL_TEXTURE_2D, Result);
+    // generate and bind Texture
+    glGenTextures(1, @Result);
+    glBindTexture(GL_TEXTURE_2D, Result);
 
-      // get texture memeory
-      glTexImage2D(GL_TEXTURE_2D, 0, 4, 1024, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, pTexData);
-      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    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;
-
-    finally  
-      // free unused memory
-      FreeMem(pTexData);
+    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);
 
@@ -268,12 +290,12 @@ begin
   BallTex := Texture.LoadTexture(Skin.GetTextureFileName('Ball'), TEXTURE_TYPE_TRANSPARENT, 0);
 
   // duet mode: load player icon
-  For I := 0 to 5 do
+  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;
@@ -283,15 +305,20 @@ 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);
+procedure TLyricEngine.AddLine(Line: PLine);
 var
   LyricLine: TLyricLine;
-  countNotes: Cardinal;
-  Viewport: Array[0..3] of Integer;
-
   PosX: Real;
-  I:  Integer;
+  I: Integer;
+  CurWord: PLyricWord;
+  RenderPass: Integer;
 
   function CalcWidth(LyricLine: TLyricLine): Real;
   begin
@@ -306,20 +333,18 @@ var
 
 begin
   // only add lines, if there is space
-  If not LineinQueue then
+  if not IsQueueFull then
   begin
-    // set pointer to line to write
-
-    If (LineCounter = 0) then
+    // 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;
-
-      //now there is a queued line
-      inQueue   := True;
+      QueueFull := True;
     end;
   end
   else
@@ -330,48 +355,38 @@ begin
     QueueLine := LyricLine;
   end;
 
-  // sentence has notes?
-  If  Line = nil then
-  begin
-    // reset all values, if the new line is nil (lines after the last line)
-    LyricLine.Start     := 0;
-    LyricLine.Length    := 0;
-    LyricLine.CurWord   := -1;
-    LyricLine.LastLine  := False;
-    LyricLine.Width     := 0;
-    SetLength(LyricLine.Words, 0);
-  end
-  else if Length(Line.Note) > 0 then
+  // 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
-    CountNotes          := High(Line.Note);
-    LyricLine.Start     := Line.Note[0].Start;
-    LyricLine.Length    := Line.Note[CountNotes].Start + Line.Note[CountNotes].Length - LyricLine.Start;
-    LyricLine.CurWord   := -1;
+    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;
 
-    // default values - set later
-    LyricLine.HasFreestyle := False;
-    LyricLine.CountFreestyle := 0;
-    LyricLine.Text      := '';
-
-    // duet mode: players of that line
-    LyricLine.Players   := 127;
-
-    //copy words
-    SetLength(LyricLine.Words, CountNotes + 1);
-    For I := 0 to CountNotes do
+    // 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.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
+      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
@@ -380,6 +395,7 @@ begin
     LyricLine.Size := UpperLineSize;
     SetFontSize(LyricLine.Size);
     SetFontItalic(False);
+    SetFontReflection(False, 0);
     glColor4f(1, 1, 1, 1);
 
     // change fontsize to fit the screen
@@ -395,61 +411,95 @@ begin
       LyricLine.Width := CalcWidth(LyricLine);
     end;
 
-    // create LyricTexture - prepare OpenGL
-    glGetIntegerv(GL_VIEWPORT, @ViewPort);
-    glClearColor(0.0,0.0,0.0,0.0);
+    // 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);
-    glViewPort(0,0,800,600);
 
-    // set word positions and line size
-    PosX := 0;
-    for I := 0 to High(LyricLine.Words) do
+    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
-      with LyricLine.Words[I] do
+      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
-        SetFontItalic(Freestyle);
+        // second pass: now we will blend overlapping characters.
+        SetFontBlend(true);
+        glDisable(GL_DEPTH_TEST);
+      end;
 
-        X := PosX;
+      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];
 
-        //Draw Lyrics
+        SetFontItalic(CurWord.Freestyle);
+
+        CurWord.X := PosX;
+
+        // Draw Lyrics
         SetFontPos(PosX, 0);
-        glPrint(PChar(Text));
-
-        Width := glTextWidth(PChar(Text));
-        if (I < High(LyricLine.Words)) AND Freestyle AND not LyricLine.Words[I+1].Freestyle then
-          Width := Width + 10
-        else
-          if (I = High(LyricLine.Words)) AND Freestyle then
-            Width := Width + 12;
-        PosX := PosX + Width;
+        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;
-  end
-  else
-  begin
-    // create LyricTexture - prepare OpenGL
-    glGetIntegerv(GL_VIEWPORT, @ViewPort);
-    glClearColor(0.0,0.0,0.0,0.0);
-    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
-    glViewPort(0,0,800,600);
-  end;
-
-  //for debugging, is this used anymore?
-  //Display.ScreenShot;
-
-  //Copy to Texture
-  glEnable(GL_ALPHA);
-  glBindTexture(GL_TEXTURE_2D, LyricLine.Tex);
-  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 600-64, 1024, 64, 0);
-  glDisable(GL_ALPHA);
+    
+    // copy back-buffer to texture
+    glBindTexture(GL_TEXTURE_2D, LyricLine.Tex);
+    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 600-64, 1024, 64, 0);
 
-  //Clear Buffer
-  glClearColor(0,0,0,0);
-  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
+    // restore OpenGL state
+    glPopAttrib();
 
-  glViewPort(ViewPort[0], ViewPort[1], ViewPort[2], ViewPort[3]);
+    // 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
+  // increase the counter
   Inc(LCounter);
 end;
 
@@ -458,7 +508,7 @@ 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);
+procedure TLyricEngine.Draw(Beat: Real);
 begin
   DrawLyrics(Beat);
   LastDrawBeat := Beat;
@@ -467,7 +517,7 @@ end;
 //---------------
 // DrawLyrics(private) - Helper for Draw; main Drawing procedure
 //---------------
-procedure TLyricEngine.DrawLyrics (Beat: Real);
+procedure TLyricEngine.DrawLyrics(Beat: Real);
 begin
   DrawLyricsLine(UpperLineX, UpperLineW, UpperlineY, 15, Upperline, Beat);
   DrawLyricsLine(LowerLineX, LowerLineW, LowerlineY, 15, Lowerline, Beat);
@@ -477,31 +527,29 @@ end;
 // DrawPlayerIcon(private) - Helper for Draw; Draws a Playericon
 //---------------
 procedure TLyricEngine.DrawPlayerIcon(Player: Byte; Enabled: Boolean; X, Y, Size, Alpha: Real);
-var IEnabled: Byte;
+var
+  IEnabled: Byte;
 begin
   case Enabled of
-    True: 	IEnabled := 0;
+    True:   IEnabled := 0;
     False:  IEnabled := 1;
   end;
 
-  try
-    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;
-
-  finally
-    glDisable(GL_BLEND);
-    glDisable(GL_TEXTURE_2D);
-  end;
+  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;
 
 //---------------
@@ -509,24 +557,21 @@ end;
 //---------------
 procedure TLyricEngine.DrawBall(XBall, YBall, Alpha: Real);
 begin
-  try
-    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;
-
-  finally
-    glDisable(GL_BLEND);
-    glDisable(GL_TEXTURE_2D);
-  end;
+  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;
 
 //---------------
@@ -542,31 +587,36 @@ var
   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
-  // lines with a width lower than 0, have not to be draw
-  if Line.Width <= 0 then
-    exit;
+  // 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);
-  
+  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;
+
+  LyricX := X + W/2 - Line.Width/2;
   LyricX2 := LyricX + Line.Width;
 
   // maybe center smaller lines
@@ -575,70 +625,70 @@ begin
 
   Alpha := 1;
 
-  // word in the sentence is active?
-  if (Line.Start < Beat) then
+  // 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, then CurWord is still -1
-    // this means, we should try to make the first word active
-    // then we check if the current active word is still meant to be active
-    // if not, we proceed to the next word
-    if Line.CurWord = -1 then
-      Line.CurWord:=0;
-
-    if (Line.CurWord < High(Line.Words)) and (Beat >= (Line.Words[Line.CurWord + 1].Start)) then
-      Line.CurWord:=Line.CurWord+1;
+    // 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;
 
-    // last word of this line finished, but this line did not hide
-    if (Line.CurWord > High(Line.Words)) then
+    // 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 := Line.Words[High(Line.Words)].X + Line.Words[High(Line.Words)].Width;
+      CurWordStart := CurWordStart + CurWord.Width * Progress;
       CurWordEnd := CurWordStart;
+    end;
 
-      // fade out last line
-      if Line.LastLine then
-      begin
-        Alpha := 1 - (Beat - (Line.Words[High(Line.Words)].Start + Line.Words[High(Line.Words)].Length)) / 15;
-        if (Alpha < 0) then
-          Alpha := 0;
-      end;
-    end
-    else
+    if CurWord.Freestyle then
     begin
-      with Line.Words[Line.CurWord] do
+      if (Line.CurWord < High(Line.Words)) and
+         (not Line.Words[Line.CurWord + 1].Freestyle) then
       begin
-        Progress := (Beat - Start) / Length;
-        if Progress >= 1 then
-          Progress := 1;
-
-        if Progress <= 0 then
-          Progress := 0;
-
-        CurWordStart:=X;
-        CurWordEnd:=X+Width;
-
-        // Slide Effect
-        // simply paint the active texture to the current position
-        if Ini.LyricsEffect = 2 then
-        begin
-          CurWordStart := CurWordStart + Width * progress;
-          CurWordEnd := CurWordStart;
-        end;
-
-        if (Line.CurWord < High(Line.Words)) and
-           Freestyle and
-           (not Line.Words[Line.CurWord + 1].Freestyle) then
-        begin
-          FreestyleDiff := 2;
-        end
-        else
-          if Freestyle then
-          begin
-            FreestyleDiff := 12;
-            CurWordStart := CurWordStart - 1;
-            CurWordEnd := CurWordEnd - 2;
-          end;
+        FreestyleDiff := 2;
+      end
+      else
+      begin
+        FreestyleDiff := 12;
+        CurWordStart := CurWordStart - 1;
+        CurWordEnd := CurWordEnd - 2;
       end;
     end;
 
@@ -648,26 +698,43 @@ begin
     glBindTexture(GL_TEXTURE_2D, Line.Tex);
 
     // draw sentence up to current word
-    if (Ini.LyricsEffect = 3) or (Ini.LyricsEffect = 4) then
+    // 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);
+      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);
+    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);
+      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:
@@ -675,78 +742,99 @@ begin
     // type 3: ball lyric effect
     // type 4: shift lyric effect
     // only change the color of the current word
-    if (Ini.LyricsEffect = 0) or (Ini.LyricsEffect = 3) or (Ini.LyricsEffect = 4) then
+    if (Ini.LyricsEffect in [0, 3, 4]) then
     begin
-      { // maybe fade in?
-      glColor4f(LineColor_en.r,LineColor_en.g,LineColor_en.b,1-progress);
-      glBegin(GL_QUADS);
-        glTexCoord2f(CurWordStart/1024, 1); glVertex2f(LyricX+CurWordStart, Y);
-        glTexCoord2f(CurWordStart/1024, 0); glVertex2f(LyricX+CurWordStart, Y + 64);
-        glTexCoord2f(CurWordEnd/1024, 0); glVertex2f(LyricX+CurWordEnd, Y + 64);
-        glTexCoord2f(CurWordEnd/1024, 1); glVertex2f(LyricX+CurWordEnd, Y);
-      glEnd;
-      }
-      
       if (Ini.LyricsEffect = 4) then
-        LyricY := LyricY - 8 * (1-progress);
-      
-      glColor3f(LineColor_act.r,LineColor_act.g,LineColor_act.b);
+        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);
+        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);
+        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);
-      glScalef(1.0+(1-progress)/2,1.0+(1-progress)/2,1.0);
-      glColor4f(LineColor_en.r,LineColor_en.g,LineColor_en.b,1-progress);
-      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;
-      glColor4f(LineColor_act.r,LineColor_act.g,LineColor_act.b,1);
+
+      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);
+        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
-      DrawBall(LyricX + CurWordStart + (CurWordEnd - CurWordStart) * progress, LyricY - 15 - 15*sin(progress * pi), Alpha);
+    begin
+      DrawBall(LyricX + CurWordStart + (CurWordEnd-CurWordStart) * Progress,
+               LyricY - 15 - 15*sin(Progress * Pi), Alpha);
+    end;
   end
   else
   begin
-    // draw complete inactive sentence if line hasn't started but is already shown
+    // 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);
 
-    glColorRGB(LineColor_dis);
+    // 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);
+      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);
@@ -754,15 +842,39 @@ begin
   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.
 
diff --git a/Game/Code/Classes/UMain.pas b/Game/Code/Classes/UMain.pas
index 95c1914a..a9add539 100644
--- a/Game/Code/Classes/UMain.pas
+++ b/Game/Code/Classes/UMain.pas
@@ -671,29 +671,13 @@ var
   CP:       integer;
   Done:     real;
   N:        integer;
+  CurLine:  PLine;
+  CurNote:  PLineFragment;
 begin
-  LineState.OldBeat := LineState.CurrentBeat;
-  // new system with variable BPM in function
-  LineState.MidBeat := GetMidBeat(LineState.CurrentTime -
-                                  (CurrentSong.Gap {+ 90 I've forgotten for what it is}) / 1000);
-  LineState.CurrentBeat := Floor(LineState.MidBeat);
-
-  //LineState.OldHalf := LineState.AktHalf;
-  //LineState.MidHalf := LineState.MidBeat + 0.5;
-  //LineState.AktHalf := Floor(LineState.MidHalf);
-
-  LineState.OldBeatC := LineState.CurrentBeatC;
-  LineState.MidBeatC := GetMidBeat(LineState.CurrentTime - (CurrentSong.Gap) / 1000);
-  LineState.CurrentBeatC := Floor(LineState.MidBeatC);
-
-  LineState.OldBeatD := LineState.CurrentBeatD;
-  // MidBeat with additional GAP
-  LineState.MidBeatD := -0.5+GetMidBeat(LineState.CurrentTime - (CurrentSong.Gap + 120 + 20) / 1000);
-  LineState.CurrentBeatD := Floor(LineState.MidBeatD);
-  LineState.FracBeatD := Frac(LineState.MidBeatD);
+  LineState.UpdateBeats();
 
   // sentences routines
-  for CountGr := 0 to 0 do //High(Gracz)
+  for CountGr := 0 to 0 do //High(Lines)
   begin;
     CP := CountGr;
     // old parts
@@ -713,10 +697,6 @@ begin
 
   end; // for CountGr
 
-  // execute operations on beat 
-  if (LineState.CurrentBeat >= 0) and (LineState.OldBeat <> LineState.CurrentBeat) then
-    NewBeat(Screen);
-
   // make some operations on clicks
   if {(LineState.CurrentBeatC >= 0) and }(LineState.OldBeatC <> LineState.CurrentBeatC) then
     NewBeatClick(Screen);
@@ -725,42 +705,19 @@ begin
   if (LineState.CurrentBeatD >= 0) and (LineState.OldBeatD <> LineState.CurrentBeatD) then
     NewBeatDetect(Screen);
 
-  // execute operations on beat field 
-//  if (LineState.AktHalf >= 1) and (LineState.OldHalf <> LineState.AktHalf) then
-//    NewHalf;
+  CurLine := @Lines[0].Line[Lines[0].Current];
 
   // remove moving text
   Done := 1;
-  for N := 0 to Lines[0].Line[Lines[0].Current].HighNote do
+  for N := 0 to CurLine.HighNote do
   begin
-    if (Lines[0].Line[Lines[0].Current].Note[N].Start <= LineState.MidBeat) and
-       (Lines[0].Line[Lines[0].Current].Note[N].Start + Lines[0].Line[Lines[0].Current].Note[N].Length >= LineState.MidBeat) then
+    CurNote := @CurLine.Note[N];
+    if (CurNote.Start <= LineState.MidBeat) and
+       (CurNote.Start + CurNote.Length >= LineState.MidBeat) then
     begin
-      Done := (LineState.MidBeat - Lines[0].Line[Lines[0].Current].Note[N].Start) / (Lines[0].Line[Lines[0].Current].Note[N].Length);
-    end;
-  end;
-
-  N := Lines[0].Line[Lines[0].Current].HighNote;
-
-  // if last note is used
-  {// todo: Lyrics
-  if (Ini.LyricsEffect = 1) and (Done = 1) and
-    (LineState.MidBeat > Lines[0].Line[Lines[0].Current].Note[N].Start + Lines[0].Line[Lines[0].Current].Note[N].Length)
-    then Screen.LyricMain.Selected := -1;
-
-  if Done > 1 then Done := 1;
-  Screen.LyricMain.Done := Done;
-  }
-
-  // use Done with LCD
-  {
-  with ScreenSing do begin
-    if LyricMain.Selected >= 0 then begin
-      LCD.MoveCursor(1, LyricMain.SelectedLetter + Round((LyricMain.SelectedLength-1) * Done));
-      LCD.ShowCursor;
+      Done := (LineState.MidBeat - CurNote.Start) / CurNote.Length;
     end;
   end;
-  }
 end;
 
 procedure NewSentence(Screen: TScreenSing);
@@ -775,20 +732,6 @@ begin
     SetLength(Player[i].Note, 0);
   end;
 
-  // add words to lyrics
-  with Screen do
-  begin
-    {LyricMain.AddCzesc(Lines[0].Current);
-    if Lines[0].Current < Lines[0].High then
-      LyricSub.AddCzesc(Lines[0].Current+1)
-    else
-      LyricSub.Clear;}
-    while (not Lyrics.LineinQueue) and (Lyrics.LineCounter <= High(Lines[0].Line)) do
-      Lyrics.AddLine(@Lines[0].Line[Lyrics.LineCounter]);
-  end;
-
-  //Screen.UpdateLCD;
-  
   // on sentence change...
   Screen.onSentenceChange(Lines[0].Current);
 end;
diff --git a/Game/Code/Classes/UMusic.pas b/Game/Code/Classes/UMusic.pas
index 3ef6aca2..2df5286e 100644
--- a/Game/Code/Classes/UMusic.pas
+++ b/Game/Code/Classes/UMusic.pas
@@ -65,46 +65,47 @@ type
   end;
 
   (**
-   * TLineState contains all information that concerns the current frames
+   * TLineState contains all information that concerns the
+   * state of the lyrics.
    *)
   TLineState = class
     private
       Timer:        TRelativeTimer; // keeps track of the current time
-
-      function GetCurrentTime(): real;
-      procedure SetCurrentTime(Time: real);
     public
       OldBeat:      integer;    // previous discovered beat
-      CurrentBeat:  integer;
-      MidBeat:      real;       // like CurrentBeat
+      CurrentBeat:  integer;    // current beat (rounded)
+      MidBeat:      real;       // current beat (float)
 
       // now we use this for super synchronization!
       // only used when analyzing voice
       // TODO: change ...D to ...Detect(ed)
       OldBeatD:     integer;    // previous discovered beat
-      CurrentBeatD: integer;
-      MidBeatD:     real;       // like CurrentBeatD
-      FracBeatD:    real;       // fractional part of MidBeatD
+      CurrentBeatD: integer;    // current discovered beat (rounded)
+      MidBeatD:     real;       // current discovered beat (float)
 
       // we use this for audible clicks
       // TODO: Change ...C to ...Click
       OldBeatC:     integer;    // previous discovered beat
       CurrentBeatC: integer;
       MidBeatC:     real;       // like CurrentBeatC
-      FracBeatC:    real;       // fractional part of MidBeatC
 
       OldLine:      integer;    // previous displayed sentence
 
+      StartTime:    real;       // time till start of lyrics (= Gap)
       TotalTime:    real;       // total song time
 
       constructor Create();
       procedure Pause();
       procedure Resume();
 
+      procedure Reset();
+      procedure UpdateBeats();
+
       (**
-       * current song time used as base-timer for lyrics etc.
+       * current song time (in seconds) used as base-timer for lyrics etc.
        *)
-      property CurrentTime: real read GetCurrentTime write SetCurrentTime;
+      function GetCurrentTime(): real;
+      procedure SetCurrentTime(Time: real);
   end;
 
 
@@ -926,11 +927,17 @@ begin
   end;
 end;
 
+
+{ TVoiceRemoval }
+
 constructor TLineState.Create();
 begin
   // create a triggered timer, so we can Pause() it, set the time
   // and Resume() it afterwards for better synching.
   Timer := TRelativeTimer.Create(true);
+
+  // reset state
+  Reset();
 end;
 
 procedure TLineState.Pause();
@@ -955,6 +962,56 @@ begin
   Result := Timer.GetTime();
 end;
 
+(**
+ * Resets the timer and state of the lyrics.
+ * The timer will be stopped afterwards so you have to call Resume()
+ * to start the lyrics timer. 
+ *)
+procedure TLineState.Reset();
+begin
+  Pause();
+  SetCurrentTime(0);
+
+  StartTime := 0;
+  TotalTime := 0;
+
+  OldBeat      := -1;
+  MidBeat      := -1;
+  CurrentBeat  := -1;
+
+  OldBeatC     := -1;
+  MidBeatC     := -1;
+  CurrentBeatC := -1;
+
+  OldBeatD     := -1;
+  MidBeatD     := -1;
+  CurrentBeatD := -1;
+end;
+
+(**
+ * Updates the beat information (CurrentBeat/MidBeat/...) according to the
+ * current lyric time.
+ *)
+procedure TLineState.UpdateBeats();
+var
+  CurLyricsTime: real;
+begin
+  CurLyricsTime := GetCurrentTime();
+
+  OldBeat := CurrentBeat;
+  MidBeat := GetMidBeat(CurLyricsTime - StartTime / 1000);
+  CurrentBeat := Floor(MidBeat);
+
+  OldBeatC := CurrentBeatC;
+  MidBeatC := GetMidBeat(CurLyricsTime - StartTime / 1000);
+  CurrentBeatC := Floor(MidBeatC);
+
+  OldBeatD := CurrentBeatD;
+  // MidBeatD = MidBeat with additional GAP
+  MidBeatD := -0.5 + GetMidBeat(CurLyricsTime - (StartTime + 120 + 20) / 1000);
+  CurrentBeatD := Floor(MidBeatD);
+end;
+
 
 { TAudioConverter }
 
diff --git a/Game/Code/Screens/UScreenSing.pas b/Game/Code/Screens/UScreenSing.pas
index 6dcec3cb..a78c7ce0 100644
--- a/Game/Code/Screens/UScreenSing.pas
+++ b/Game/Code/Screens/UScreenSing.pas
@@ -23,7 +23,6 @@ uses UMenu,
      TextGL,
      gl,
      UThemes,
-     //ULCD, //TODO: maybe LCD Support as Plugin?
      UGraphicClasses,
      USingScores;
 
@@ -113,11 +112,10 @@ type
       function    Draw: boolean; override;
 
       procedure   Finish; virtual;
-      //procedure   UpdateLCD; //TODO: maybe LCD Support as Plugin?
-      procedure   Pause; //Pause Mod(Toggles Pause)
+      procedure   Pause; // Toggle Pause
 
-      procedure   OnSentenceEnd(SentenceIndex: Cardinal);     //OnSentenceEnd for LineBonus + Singbar
-      procedure   OnSentenceChange(SentenceIndex: Cardinal);  //OnSentenceChange (for Golden Notes)
+      procedure   OnSentenceEnd(SentenceIndex: Cardinal);     // for LineBonus + Singbar
+      procedure   OnSentenceChange(SentenceIndex: Cardinal);  // for Golden Notes
   end;
 
 implementation
@@ -495,7 +493,6 @@ begin
   fShowVisualization      := false;
   if (CurrentSong.Video <> '') and FileExists(CurrentSong.Path + CurrentSong.Video) then
   begin
-    // TODO: use VideoGap and start time
     fCurrentVideoPlaybackEngine.Open( CurrentSong.Path + CurrentSong.Video );
     fCurrentVideoPlaybackEngine.Position := CurrentSong.VideoGAP + CurrentSong.Start;
     CurrentSong.VideoLoaded := true;
@@ -506,16 +503,21 @@ begin
     try
       Tex_Background := Texture.LoadTexture(CurrentSong.Path + CurrentSong.Background);
     except
-      log.LogError('Background could not be loaded: ' + CurrentSong.Path + CurrentSong.Background);
+      Log.LogError('Background could not be loaded: ' + CurrentSong.Path + CurrentSong.Background);
       Tex_Background.TexNum := 0;
     end
   else
     Tex_Background.TexNum := 0;
 
   // prepare lyrics timer
-  LineState.Pause();
-  LineState.CurrentTime := CurrentSong.Start;
-  LineState.TotalTime := AudioPlayback.Length;
+  LineState.Reset();
+  LineState.SetCurrentTime(CurrentSong.Start);
+  LineState.StartTime := CurrentSong.Gap;
+  if (CurrentSong.Finish > 0) then
+    LineState.TotalTime := CurrentSong.Finish / 1000
+  else
+    LineState.TotalTime := AudioPlayback.Length;
+  LineState.UpdateBeats();
 
   // prepare music
   AudioPlayback.Stop();
@@ -526,9 +528,6 @@ begin
   // prepare and start voice-capture
   AudioInput.CaptureStart;
 
-  if (CurrentSong.Finish > 0) then
-    LineState.TotalTime := CurrentSong.Finish / 1000;
-  LineState.OldBeat := -1;
   for P := 0 to High(Player) do
     ClearScores(P);
 
@@ -654,11 +653,12 @@ begin
       end;
   end; // case
 
-  // Add lines to lyrics
-  while (not Lyrics.LineinQueue) and (Lyrics.LineCounter <= High(Lines[0].Line)) do
+  // Initialize lyrics by filling its queue
+  while (not Lyrics.IsQueueFull) and
+        (Lyrics.LineCounter <= High(Lines[0].Line)) do
+  begin
     Lyrics.AddLine(@Lines[0].Line[Lyrics.LineCounter]);
-  
-  //UpdateLCD; //TODO: maybe LCD Support as Plugin?
+  end;
 
   // Deactivate pause
   Paused := False;
@@ -928,6 +928,7 @@ var
   Flash:  real;
   S:      integer;
   T:      integer;
+  CurLyricsTime: real;
 begin
   // ScoreBG Mod
   // TODO: remove this commented out section as we do not need it anymore.
@@ -1064,9 +1065,15 @@ begin
   for T := 0 to 1 do
     Text[T].X := Text[T].X + 10*ScreenX;
 
+
+
+  // retrieve current lyrics time, we have to store the value to avoid
+  // that min- and sec-values do not match 
+  CurLyricsTime := LineState.GetCurrentTime();
+  Min := Round(CurLyricsTime) div 60;
+  Sec := Round(CurLyricsTime) mod 60;
+
   // update static menu with time ...
-  Min := Round(LineState.CurrentTime) div 60;
-  Sec := Round(LineState.CurrentTime) mod 60;
   Text[TextTimeText].Text := '';
   if Min < 10 then Text[TextTimeText].Text := '0';
   Text[TextTimeText].Text := Text[TextTimeText].Text + IntToStr(Min) + ':';
@@ -1167,7 +1174,7 @@ begin
   begin
     if assigned( fCurrentVideoPlaybackEngine ) then
     begin
-      fCurrentVideoPlaybackEngine.GetFrame(LineState.CurrentTime);
+      fCurrentVideoPlaybackEngine.GetFrame(LineState.GetCurrentTime());
       fCurrentVideoPlaybackEngine.DrawGL(ScreenAct);
     end;
   end;
@@ -1180,7 +1187,7 @@ begin
   if ShowFinish then
   begin
     if (not AudioPlayback.Finished) and
-       ((CurrentSong.Finish = 0) or (LineState.CurrentTime*1000 <= CurrentSong.Finish)) then
+       ((CurrentSong.Finish = 0) or (LineState.GetCurrentTime()*1000 <= CurrentSong.Finish)) then
     begin
       // analyze song if not paused
       if (not Paused) then
@@ -1270,25 +1277,6 @@ begin
   SetFontItalic (False);
 end;
 
-(*
-procedure TScreenSing.UpdateLCD; //TODO: maybe LCD Support as Plugin?
-var
-  T:    string;
-begin
-  //Todo: Lyrics
-{  LCD.HideCursor;
-  LCD.Clear;
-
-  T := LyricMain.Text;
-  if Copy(T, Length(T), 1) <> ' ' then T := T + ' ';
-  LCD.AddTextBR(T);
-
-  T := LyricSub.Text;
-  if Copy(T, Length(T), 1) <> ' ' then T := T + ' ';
-  LCD.AddTextBR(T);}
-end;
-*)
-
 procedure TScreenSing.OnSentenceEnd(SentenceIndex: Cardinal);
 var
   PlayerIndex: Integer;
@@ -1374,34 +1362,40 @@ begin
     GoldenRec.SpawnPerfectLineTwinkle;
 end;
 
-//Called on Sentence Change S= New Current Sentence
+// Called on sentence change
+// SentenceIndex: index of the new active sentence
 procedure TScreenSing.OnSentenceChange(SentenceIndex: Cardinal);
+var
+  LyricEngine: TLyricEngine;
 begin
-  //GoldenStarsTwinkle Mod
+  //GoldenStarsTwinkle
   GoldenRec.SentenceChange;
-  if (Lyrics.LineCounter <= High(Lines[0].Line)) then
+
+  // Fill lyrics queue and set upper line to the current sentence
+  while (Lyrics.GetUpperLineIndex() < SentenceIndex) or
+        (not Lyrics.IsQueueFull) do
   begin
-      Lyrics.AddLine(@Lines[0].Line[Lyrics.LineCounter]);  
-  end
-  else
-    Lyrics.AddLine(nil);
-  
-  // addline uses display memory
-  // calling draw makes sure, there's the singscreen in it, when the next
-  // swap between onscreen and offscreen buffers is done
-  // (this eliminates the onSentenceChange flickering)
-  // note: maybe it would be better to make sure, a display redraw is done
-  //     right after the sentence change (before buffer swap) or make sure
-  //     onsentencechange is only called right before calling Display.Draw
-  //     (or whatever it was called)
-  Draw;
+    // Add the next line to the queue or a dummy if no more lines are available
+    if (Lyrics.LineCounter <= High(Lines[0].Line)) then
+      Lyrics.AddLine(@Lines[0].Line[Lyrics.LineCounter])
+    else
+      Lyrics.AddLine(nil);
+  end;
 
-  //GoldenStarsTwinkle Mod End
+  // AddLine draws the passed line to the back-buffer of the render context
+  // and copies it into a texture afterwards (offscreen rendering).
+  // This leaves an in invalidated screen. Calling Draw() makes sure,
+  // that the back-buffer stores the sing-screen, when the next
+  // swap between the back- and front-buffer is done (eliminates flickering)
+  // 
+  // Note: calling AddLine() right before the regular screen update (Display.Draw)
+  // would be a better solution.
+  Draw;
 end;
 
 function TLyricsSyncSource.GetClock(): real;
 begin
-  Result := LineState.CurrentTime;
+  Result := LineState.GetCurrentTime();
 end;
 
 end.
diff --git a/Game/Code/Screens/UScreenSingModi.pas b/Game/Code/Screens/UScreenSingModi.pas
index 7606abe6..3eeb0374 100644
--- a/Game/Code/Screens/UScreenSingModi.pas
+++ b/Game/Code/Screens/UScreenSingModi.pas
@@ -277,8 +277,9 @@ var
   Min:    integer;
   Sec:    integer;
   Tekst:  string;
-  S, I:      integer;
+  S, I:   integer;
   T:      integer;
+  CurLyricsTime: real;
 begin
   Result := false;
 
@@ -511,8 +512,10 @@ begin
   if DLLMan.Selected.LoadSong then
   begin
     // update static menu with time ...
-    Min := Round(LineState.CurrentTime) div 60;
-    Sec := Round(LineState.CurrentTime) mod 60;
+    CurLyricsTime := LineState.GetCurrentTime();
+    Min := Round(CurLyricsTime) div 60;
+    Sec := Round(CurLyricsTime) mod 60;
+
     Text[TextTimeText].Text := '';
     if Min < 10 then Text[TextTimeText].Text := '0';
     Text[TextTimeText].Text := Text[TextTimeText].Text + IntToStr(Min) + ':';
@@ -540,7 +543,7 @@ begin
   if ShowFinish then begin
     if DllMan.Selected.LoadSong then
     begin
-      if (not AudioPlayback.Finished) and ((CurrentSong.Finish = 0) or (LineState.CurrentTime*1000 <= CurrentSong.Finish)) then begin
+      if (not AudioPlayback.Finished) and ((CurrentSong.Finish = 0) or (LineState.GetCurrentTime*1000 <= CurrentSong.Finish)) then begin
         //Pause Mod:
         if not Paused then
           Sing(Self);       // analyze song
-- 
cgit v1.2.3