aboutsummaryrefslogblamecommitdiffstats
path: root/src/screens/UScreenCredits.pas
blob: 90c0fa19603e9c098d7f9cade445a76bb96183d5 (plain) (tree)
























                                                                        









                    
    
           

            

        

           


          
        
                  
 











                                                                            
    
                                                        

                               

                         
 

                              
                              
 


                                    
                                             











                                    
 
                                                    


                                  






                                            


                                                 
































                                                                                   
                       
                                   
                                                                                                             
                                                                                                    

                                 
                                       


       
                      

                                                                                                                                  


                                                                                                                                                             
                                                          
                                                
                                                     











                                                  
 












































































                                                                                                                                           
 


              








            
             
 
                                                                                                            

                 
                       



                      

                     



                                                 
 
                 



                                                      


                 

    






















                                                                                                                                      



                                  




                                                                                                         
                   













                                                                                                        



                            
                                


            
                           

                        
                                 
             
                        

                                                       
                                                                                   










                           

    
                                

                     
 


                    
                        

    










































































































                                                                                                    
                                  

                          






                                                          

      



                       



























































































































































































                                                                                                                                             
   

                       
                      
     
                  
 
                       
                              
       

                              

                            

      
                             
                                                   
       




                                                                          

                                                                                                         







                                        



                                                                 

                                           
        



                                                                           

                              





                                                                          


                            














                                           

    





                                   
     












                                                    
       

                                                                                                                     
         











                                                                                                                                               
        
 
                       
                 

                                                            

                                                      
                            
 



                                                                         
         

                                                                                                              

        
                                                                                                                            
 
                             
                        
                















                                                              
 





















                                                   
         

                                                              
           



                                                                                                                           

          
    
 










                                                                   
 




                          
 

                                                      
 




                                                    
 





























                                                                   
 

                           
 


                     
 











                                                                
 


















                                                                      
 














                                                                         
 














                                                                          
 


                                                                       
 

                                                        
 






                                                       
 

















                                                                       
 


                                                                    
 
                                           
 






                                                       
 


                                                                         
 
                                                              
 






                                                       
 




                                                                  
 
                              
 






                                                                           
 




                                                                        
 
                              
 






                                                                                       
 




                                                                    
 
                              
 






                                                                     
 




                                                                        
 
                              
 






                                                                           
 














                                                                          
 




                                                                     
 
                      
 


                                         
 






                                                       
 


                                                                         
 
                                                              
 






                                                       
 















                                                                           
 




                                                                       
 
                              
 






                                                                           
 




                                                                         
 
                              
 















































































































                                                                                                                                                 


    
{* UltraStar Deluxe - Karaoke Game
 *
 * UltraStar Deluxe is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING. If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *}

unit UScreenCredits;

interface

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

{$I switches.inc}

uses
  SysUtils,
  SDL,
  SDL_Image,
  gl,
  UMenu,
  UDisplay,
  UTexture,
  UMusic,
  UFiles,
  UThemes,
  UPath,
  UGraphicClasses;

{ beat detection constants and types }
const
  SubChannelCount = 32;
  HistoryLength = 44;
  SamplesPerChannel = (FFTSize div 2) div SubChannelCount;
  BeatEnergyModifier = 80; // modifies detected energy
                           // higher values equal a more sensitive detection

type
  TEnergyHistory = array [0..HistoryLength-1] of single;
  TSubchannelHistory = array [0..SubChannelCount-1] of TEnergyHistory;

type
  TCreditsStages=(InitialDelay, Intro, MainPart, Outro);

  TScreenCredits = class(TMenu)
    private
      CreditsPath: IPath;

      Credits_X:     real;
      Credits_Time:  cardinal;
      CTime_hold:    cardinal;

      credits_bg_tex:      TTexture;
      credits_bg_ovl:      TTexture;
      //credits_bg_logo:   TTexture;
      credits_names:       array of TTexture;
      intro_layer01:       TTexture;
      intro_layer02:       TTexture;
      intro_layer03:       TTexture;
      intro_layer04:       TTexture;
      intro_layer05:       TTexture;
      intro_layer06:       TTexture;
      intro_layer07:       TTexture;
      intro_layer08:       TTexture;
      intro_layer09:       TTexture;
      outro_bg:            TTexture;
      outro_esc:           TTexture;
      outro_exd:           TTexture;

      CurrentScrollStart, CurrentScrollEnd: integer;

      CRDTS_Stage: TCreditsStages;

      { beat detection }
      SubChannelHistory: TSubchannelHistory;

      { mouse movement easter eggs: }
      MouseMoved: boolean;
      MouseX, MouseY: double;

      { saves last x and y angle for easter egg }
      LogoAngleX, LogoAngleY: single;

      procedure LoadNameTextures;

      { draw different stages }
      procedure DrawInitialDelay;

      { Intro }
      procedure DrawIntro;
      procedure DrawLayeredLogo(Separation, Scale, AngleX, AngleY, AngleZ: single);

      { Main }
      procedure DrawMain;
      procedure DrawMainBG;
      procedure DrawFunkyText;

      procedure DrawMainFG;

      procedure DrawNames;
      procedure DoLogoBling;

      { Outro }
      procedure DrawOutro;


      { beat detection }
      procedure DetectBeat;
    protected
      { beat detection stuff
        protected cause we need this information for "on beat
        effect"}
      LastBeatTime: cardinal;
      BeatDetected: boolean;
      CTime:        cardinal;
    public
      Fadeout: boolean;
      constructor Create; override;
      function ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown: boolean): boolean; override;
      function ParseMouse(MouseButton: integer; BtnDown: boolean; X, Y: integer): boolean; override;
      procedure OnShow; override;
      procedure OnHide; override;
      function Draw: boolean; override;
   end;

const
  Funky_Text: string =
    'Grandma Deluxe has arrived! Thanks to Corvus5 for the massive work on UltraStar, Wome for the nice tune you are hearing, '+
    'all the people who put massive effort and work in new songs (do not forget UltraStar w/o songs would be nothing), ppl from '+
    'irc helping us - eBandit and Gabari, scene ppl who really helped instead of compiling and running away. Greetings to DennisTheMenace for betatesting, '+
    'Demoscene.tv, pouet.net, KakiArts, Sourceforge,..';

{ texture names (loaded from gameshared/resources/credits}
  CRDTS_BG_FILE           = 'credits_v5_bg.png';
  CRDTS_OVL_FILE          = 'credits_v5_overlay.png';
  INTRO_L01_FILE          = 'intro-l-01.png';
  INTRO_L02_FILE          = 'intro-l-02.png';
  INTRO_L03_FILE          = 'intro-l-03.png';
  INTRO_L04_FILE          = 'intro-l-04.png';
  INTRO_L05_FILE          = 'intro-l-05.png';
  INTRO_L06_FILE          = 'intro-l-06.png';
  INTRO_L07_FILE          = 'intro-l-07.png';
  INTRO_L08_FILE          = 'intro-l-08.png';
  INTRO_L09_FILE          = 'intro-l-09.png';
  OUTRO_BG_FILE           = 'outro-bg.png';
  OUTRO_ESC_FILE          = 'outro-esc.png';
  OUTRO_EXD_FILE          = 'outro-exit-dark.png';

{ some timings }
  Delay_Before_Start = 20;
  Intro_Flare_Start = 60;
  Intro_Zoom_End = 149;
  Intro_Stand_End = 155;
  Intro_Separation_End = 170;
  Intro_FadeToWhite_Start = 261;
  Intro_Zoomout_Start = 271;
  Main_Start = 271;
  Main_OnBeatTwinkle_Start = 280;
  Main_Names_Start = 359;
  Main_Names_End = 2833;
  Main_FadeOut_Start = 3096;
  Tune_End = 3366;

{ cosntants for developer names }

type
  TFadeEffect = procedure (const Tex: TTexture; Progress: double);
  TCRTZ_Developer = record
    Name: string;           // developer name for texture loading (names_"devel".png)
    Twinkle: boolean;       // should there be twinkles on show
    FadeIn:   TFadeEffect;  // fade in effect
    Draw:     TFadeEffect;  // effect during draw
    FadeOut:  TFadeEffect;  // fade out effect
  end;

{ effects are called with blending, texture and matrix prepared }
procedure Effect_Draw             (const Tex: TTexture; Progress: double);
procedure Effect_OnBeatJitter     (const Tex: TTexture; Progress: double);

procedure Effect_Rotate_Left_Top  (const Tex: TTexture; Progress: double);
procedure Effect_Rotate_Right_Bot (const Tex: TTexture; Progress: double);
procedure Effect_ZoomIn_Rotate    (const Tex: TTexture; Progress: double);
procedure Effect_ZoomOut_Shift    (const Tex: TTexture; Progress: double);
procedure Effect_Shift_Left       (const Tex: TTexture; Progress: double);
procedure Effect_Shift_Right_Top  (const Tex: TTexture; Progress: double);
procedure Effect_Flip_Bot         (const Tex: TTexture; Progress: double);
procedure Effect_Flip_Right_Top   (const Tex: TTexture; Progress: double);
procedure Effect_Flip_Right       (const Tex: TTexture; Progress: double);
procedure Effect_Flip_Right_Bot   (const Tex: TTexture; Progress: double);
procedure Effect_Rotate_Right_Top (const Tex: TTexture; Progress: double);
procedure Effect_Shift_Weird      (const Tex: TTexture; Progress: double);
procedure Effect_Shift_Right_Bot  (const Tex: TTexture; Progress: double);
procedure Effect_Rotate_Right_Top2(const Tex: TTexture; Progress: double);
procedure Effect_Flip_Left_Bot    (const Tex: TTexture; Progress: double);
procedure Effect_Flip_Right_Top2  (const Tex: TTexture; Progress: double);
procedure Effect_Twinkle_Down     (const Tex: TTexture; Progress: double);

const
  Developers: array[0..10] of TCRTZ_Developer = (
    (Name: 'alexanders';  Twinkle: true;  FadeIn: Effect_Rotate_Left_Top;   Draw: Effect_OnBeatJitter;  FadeOut: Effect_Rotate_Right_Bot),
    (Name: 'blindy';      Twinkle: true;  FadeIn: Effect_ZoomIn_Rotate;     Draw: Effect_OnBeatJitter;  FadeOut: Effect_ZoomOut_Shift),
    (Name: 'brunzel';     Twinkle: true;  FadeIn: Effect_Shift_Left;        Draw: Effect_Draw;          FadeOut: Effect_Shift_Right_Top),
    (Name: 'canni';       Twinkle: true;  FadeIn: Effect_Flip_Bot;          Draw: Effect_Draw;          FadeOut: Effect_Flip_Right_Top),
    (Name: 'hennymcc';    Twinkle: true;  FadeIn: Effect_Flip_Right;        Draw: Effect_OnBeatJitter;  FadeOut: Effect_Flip_Right_Bot),
    (Name: 'jaybinks';    Twinkle: true;  FadeIn: Effect_Rotate_Right_Top;  Draw: Effect_OnBeatJitter;  FadeOut: Effect_Shift_Weird),
    (Name: 'krueger';     Twinkle: true;  FadeIn: Effect_Shift_Right_Bot;   Draw: Effect_OnBeatJitter;  FadeOut: Effect_Rotate_Right_Top2),
    (Name: 'mezzox';      Twinkle: true;  FadeIn: Effect_Flip_Left_Bot;     Draw: Effect_OnBeatJitter;  FadeOut: Effect_Flip_Right_Top),
    (Name: 'mischi';      Twinkle: true;  FadeIn: Effect_Shift_Weird;       Draw: Effect_OnBeatJitter;  FadeOut: Effect_Flip_Bot),
    (Name: 'mog';         Twinkle: false; FadeIn: Effect_Twinkle_Down;      Draw: Effect_OnBeatJitter;  FadeOut: Effect_ZoomIn_Rotate),
    (Name: 'whiteshark';  Twinkle: true;  FadeIn: Effect_Rotate_Right_Top2; Draw: Effect_OnBeatJitter;  FadeOut: Effect_Shift_Left)
  );

  { name specific times }
  TimePerName = (Main_Names_End - Main_Names_Start) div Length(Developers);
  NameFadeTime = 12;   // duration of fade in/out in 1/100 secs
  NameWaitTime = 5;    // delay between fade out and fade in of the next devel in 1/100 secs
  NameTwinkleTime = 2; // duration of star effects in 1/100 secs
  BeatJitterTime = 3;  // duration of on beat jitter effect
  { position at which the names show up
    note: due to use of translate this is the center
    of the names not the upper left corner as usual }
  NameX = 223;
  NameY = 329;
  NameW = 326;
  NameH = 258;

implementation

uses
  Math,
  ULog,
  UGraphic,
  UMain,
  UIni,
  USongs,
  Textgl,
  ULanguage,
  UCommon,
  UPathUtils;

function TScreenCredits.ParseInput(PressedKey: cardinal; CharCode: UCS4Char; PressedDown: boolean): boolean;
begin
  Result := true;
  if (PressedDown) then
  begin // Key Down
    case PressedKey of

      SDLK_ESCAPE,
      SDLK_BACKSPACE,
      SDLK_RETURN :
        begin
          FadeTo(@ScreenMain);
          AudioPlayback.PlaySound(SoundLib.Back);
        end;
{
      SDLK_SPACE:
         begin
           setlength(CTime_hold,length(CTime_hold)+1);
           CTime_hold[high(CTime_hold)]:=CTime;
         end;
}
     end; // esac
    end;  // fi
end;

function TScreenCredits.ParseMouse(MouseButton: integer; BtnDown: boolean; X, Y: integer): boolean;
begin
  Result := inherited ParseMouse(MouseButton, BtnDown, X, Y);
  
  { calculate mouse coordinates from -1 to 1
    relative to screen center }
  MouseX := (X - (ScreenW / Screens) / 2) / ((ScreenW / Screens) / 2);
  MouseY := (Y - ScreenH / 2) / (ScreenH / 2);

  MouseMoved := true;
end;

procedure TScreenCredits.LoadNameTextures;
  var I: integer;
begin
  SetLength(credits_names, Length(Developers));

  for I  := 0 to High(Developers) do
  begin
    credits_names[I] := Texture.LoadTexture(CreditsPath.Append('names_' + Developers[I].Name + '.png'),  TEXTURE_TYPE_TRANSPARENT, 0);
  end;
end;

constructor TScreenCredits.Create;
begin
  inherited Create;

  CreditsPath := ResourcesPath.Append('credits', pdAppend);

  credits_bg_tex := Texture.LoadTexture(CreditsPath.Append(CRDTS_BG_FILE), TEXTURE_TYPE_PLAIN, 0);
  credits_bg_ovl := Texture.LoadTexture(CreditsPath.Append(CRDTS_OVL_FILE), TEXTURE_TYPE_TRANSPARENT, 0);

  LoadNameTextures;

  intro_layer01 := Texture.LoadTexture(CreditsPath.Append(INTRO_L01_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  intro_layer02 := Texture.LoadTexture(CreditsPath.Append(INTRO_L02_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  intro_layer03 := Texture.LoadTexture(CreditsPath.Append(INTRO_L03_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  intro_layer04 := Texture.LoadTexture(CreditsPath.Append(INTRO_L04_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  intro_layer05 := Texture.LoadTexture(CreditsPath.Append(INTRO_L05_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  intro_layer06 := Texture.LoadTexture(CreditsPath.Append(INTRO_L06_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  intro_layer07 := Texture.LoadTexture(CreditsPath.Append(INTRO_L07_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  intro_layer08 := Texture.LoadTexture(CreditsPath.Append(INTRO_L08_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  intro_layer09 := Texture.LoadTexture(CreditsPath.Append(INTRO_L09_FILE), TEXTURE_TYPE_TRANSPARENT, 0);

  outro_bg  := Texture.LoadTexture(CreditsPath.Append(OUTRO_BG_FILE),  TEXTURE_TYPE_PLAIN, 0);
  outro_esc := Texture.LoadTexture(CreditsPath.Append(OUTRO_ESC_FILE), TEXTURE_TYPE_TRANSPARENT, 0);
  outro_exd := Texture.LoadTexture(CreditsPath.Append(OUTRO_EXD_FILE), TEXTURE_TYPE_TRANSPARENT, 0);

  CRDTS_Stage:=InitialDelay;
end;

procedure TScreenCredits.OnShow;
begin
  inherited;

 { pause background music }
  SoundLib.PauseBgMusic;

  CRDTS_Stage    := InitialDelay;
  CTime := 0;
  Credits_X      := 580;

  { open credits tune, we play it after initial delay }
  AudioPlayback.Open(soundpath.Append('wome-credits-tune.mp3')); // thank you wetue

  { reset twinkling stars }
  GoldenRec.KillAll;
  
  { reset mouse coords }
  MouseMoved := false;
  MouseX := 0;
  MouseY := 0;

  { hide cursor }
  Display.SetCursor;
end;

procedure TScreenCredits.OnHide;
begin
  AudioPlayback.Stop;

  { show cursor }
  Display.SetCursor;

  SoundLib.StartBgMusic;
end;

function TScreenCredits.Draw: boolean;
  var
    T: cardinal;
begin
  Result := true;
  
  // reset beat detection
  BeatDetected := false;

  T := SDL_GetTicks() div 33;
  if T <> Credits_Time then
  begin
    Credits_Time := T;
    inc(CTime);
    inc(CTime_hold);
    Credits_X := Credits_X-2;

    if (CRDTS_Stage = InitialDelay) and (CTime >= Delay_Before_Start) then
    begin
      CRDTS_Stage := Intro;
      CTime := 0;
      AudioPlayback.Play;
    end
    else if (CRDTS_Stage = Intro) and (CTime >= Main_Start) then
    begin
      CRDTS_Stage := MainPart;
    end
    else if (CRDTS_Stage = MainPart) and (CTime >= Tune_End) then
    begin
      CRDTS_Stage := Outro;
    end;
    
    // dis does teh muiwk y0r   to be translated :-)
    DetectBeat;
  end;

  case CRDTS_Stage of
    InitialDelay: DrawInitialDelay;
    Intro:        DrawIntro;
    MainPart:     DrawMain;
    Outro:        DrawOutro;  
  end;

  // make the stars shine
  GoldenRec.Draw;
end;

procedure TScreenCredits.DrawInitialDelay;
begin
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
end;

procedure TScreenCredits.DrawIntro;
  var
    Separation, Scale,
    AngleX, AngleY, AngleZ: single;
    FlareX, FlareY: single;
    I: integer;
begin
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  { rotate logo anti clockwise and make it grow }
  if (CTime >= Intro_Separation_End) then
  begin
    Separation := 1;
    Scale := 1 + sqr(CTime - Intro_Separation_End) / (32 * (Main_Start - Intro_Separation_End));
    AngleX := 0;
    AngleY := 0;
    AngleZ := 20 * sqr(CTime - Intro_Separation_End) / sqr((Main_Start - Intro_Separation_End) / 2);
  end

  { separate layers }
  else if (CTime >= Intro_Stand_End) then
  begin
    Separation := 0.5 + 0.5 * (CTime - Intro_Stand_End) / (Intro_Separation_End - Intro_Stand_End);
    Scale := 1;
    AngleX := 0;
    AngleY := 0;
    AngleZ := 0;
  end

  { stand still }
  else if (CTime >= Intro_Zoom_End) then
  begin
    Separation := 0.5;
    Scale := 1;
    AngleX := 0;
    AngleY := 0;
    AngleZ := 0;
  end

  { rotate left }
  else 
  begin
    Separation := 0.5 + 0.5 * (Intro_Zoom_End - CTime) / (Intro_Zoom_End);
    Scale := 1;
    AngleX := 10 * (Intro_Zoom_End - CTime) / (Intro_Zoom_End);
    AngleY := 20 * (Intro_Zoom_End - CTime) / (Intro_Zoom_End);
    AngleZ := 0;
  end;

  { the user moved the mouse, overwrite X and Y angle with
    according to mouse position }
  if (MouseMoved) then
  begin
    // calculate destination angle
    AngleX := 30 * MouseY;
    AngleY := 30 * MouseX;

    { move angle towards destination }
    if not SameValue(LogoAngleX, AngleX, 0.001) then
      AngleX := LogoAngleX + 0.05 * (AngleX - LogoAngleX);

    if not SameValue(LogoAngleY, AngleY, 0.001) then
      AngleY := LogoAngleY + 0.05 * (AngleY - LogoAngleY);
  end;

  // save last angle
  LogoAngleX := AngleX;
  LogoAngleY := AngleY;

  DrawLayeredLogo(Separation, Scale, AngleX, AngleY, AngleZ);

  { do some sparkling effects }
  if (CTime < Intro_Zoom_End) and (CTime > Intro_Flare_Start) then
  begin
    for I := 1 to 3 do
    begin
       FlareX := 410 + Floor((CTime - Intro_Flare_Start) / (Intro_Zoom_End - Intro_Flare_Start) * (536 - 410)) + RandomRange(-5, 5);
       FlareY := Floor((Intro_Zoom_End - CTime) / 22) + RandomRange(285, 301);
       GoldenRec.Spawn(FlareX, FlareY, 1, 16, 0, -1, Flare, 0);
    end;
  end;

  { fade to white at end }
  if Ctime > Intro_FadeToWhite_Start then
  begin
    glColor4f(1, 1, 1, sqr(CTime - Intro_FadeToWhite_Start) * (CTime - Intro_FadeToWhite_Start) / sqr(Main_Start - Intro_FadeToWhite_Start));
    glEnable(GL_BLEND);
    glBegin(GL_QUADS);
      glVertex2f(  0,   0);
      glVertex2f(  0, 600);
      glVertex2f(800, 600);
      glVertex2f(800,   0);
    glEnd;
    glDisable(GL_BLEND);
  end;
end;

procedure Start3D;
begin
  glMatrixMode(GL_PROJECTION);
  glPushMatrix;
  glLoadIdentity;
  glFrustum(-0.3 * 4 / 3, 0.3 * 4 / 3, -0.3, 0.3, 1, 1000);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

procedure End3D;
begin
  glMatrixMode(GL_PROJECTION);
  glPopMatrix;
  glMatrixMode(GL_MODELVIEW);
end;

procedure TScreenCredits.DrawLayeredLogo(Separation, Scale, AngleX, AngleY, AngleZ: single);
  var
    TotalAngle: single;
begin
  Start3D;
  glPushMatrix;

  glEnable(GL_TEXTURE_2D);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);

  glTranslatef(0, 0, -5 + 0.5 * Separation);

  TotalAngle := Abs(AngleX) + Abs(AngleY) + Abs(AngleZ);
  if not isZero(TotalAngle) then
    glRotatef(TotalAngle, AngleX / TotalAngle, AngleY / TotalAngle, AngleZ / TotalAngle);

  glScalef(Scale, Scale, 1);

  glScalef(4/3, -1, 1);
  glColor4f(1, 1, 1, 1);

  glBindTexture(GL_TEXTURE_2D, intro_layer01.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.4 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.4 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.4 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.4 * Separation);
  glEnd;

  glBindTexture(GL_TEXTURE_2D, intro_layer02.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.3 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.3 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.3 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.3 * Separation);
  glEnd;

  glBindTexture(GL_TEXTURE_2D, intro_layer03.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.2 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.2 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.2 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.2 * Separation);
  glEnd;

  glBindTexture(GL_TEXTURE_2D, intro_layer04.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.1 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.1 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.1 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.1 * Separation);
  glEnd;

  glBindTexture(GL_TEXTURE_2D, intro_layer05.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, 0 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, 0 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, 0 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, 0 * Separation);
  glEnd;

  glBindTexture(GL_TEXTURE_2D, intro_layer06.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, 0.1 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, 0.1 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, 0.1 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, 0.1 * Separation);
  glEnd;

  glBindTexture(GL_TEXTURE_2D, intro_layer07.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, 0.2 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, 0.2 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, 0.2 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, 0.2 * Separation);
  glEnd;

  glBindTexture(GL_TEXTURE_2D, intro_layer08.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, 0.3 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, 0.3 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, 0.3 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, 0.3 * Separation);
  glEnd;

  glBindTexture(GL_TEXTURE_2D, intro_layer09.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex3f(-1, -1, 0.22 * Separation);
    glTexCoord2f(0, 1); glVertex3f(-1,  1, 0.22 * Separation);
    glTexCoord2f(1, 1); glVertex3f( 1,  1, 0.22 * Separation);
    glTexCoord2f(1, 0); glVertex3f( 1, -1, 0.22 * Separation);
  glEnd;

  glDisable(Gl_Texture_2D);
  glDisable(GL_BLEND);

  glPopMatrix;
  End3D;
end;

procedure TScreenCredits.DrawMain;  
begin
  DrawMainBG;
  DrawFunkyText;
  DrawNames;
  DrawMainFG;
  DoLogoBling;

  // fade out at end of main part
  if (Ctime > Main_FadeOut_Start) then
  begin
    glColor4f(0, 0, 0, (CTime - Main_FadeOut_Start) / (Tune_End - Main_FadeOut_Start));
    glEnable(GL_BLEND);
    glBegin(GL_QUADS);
      glVertex2f(  0,   0);
      glVertex2f(  0, 600);
      glVertex2f(800, 600);
      glVertex2f(800,   0);
    glEnd;
    glDisable(GL_BLEND);
  end;
end;

procedure TScreenCredits.DrawMainBG;
begin
  glEnable(GL_TEXTURE_2D);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);

  glColor4f(1, 1, 1, 1);
  glBindTexture(GL_TEXTURE_2D, credits_bg_tex.TexNum);
  glBegin(Gl_Quads);
    glTexCoord2f(       0,        0); glVertex2f(      0,       0);
    glTexCoord2f(       0, 600/1024); glVertex2f(      0, RenderH);
    glTexCoord2f(800/1024, 600/1024); glVertex2f(RenderW, RenderH);
    glTexCoord2f(800/1024,        0); glVertex2f(RenderW,       0);
  glEnd;
  glDisable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);
end;

procedure TScreenCredits.DrawFunkyText;
var
  S:           integer;
  X, Y, A:     real;
  visibleText: string;
begin
  SetFontSize(30);

  // init ScrollingText
  if (CTime = Main_Start) then
  begin
    // set position of text
    Credits_X          := 600;
    CurrentScrollStart := 1;
    CurrentScrollEnd   := 1;
  end;

  if (CTime > Main_Start) and
     (CurrentScrollStart < length(Funky_Text)) then
  begin
    X := 0;
    visibleText := Copy(Funky_Text, CurrentScrollStart, CurrentScrollEnd);

    for S := 1 to length(visibleText) do
    begin
      Y := abs(sin((Credits_X + X) * 0.93 { * (((Credits_X + X)) / 1200) } / 100 * pi));
      SetFontPos(Credits_X + X, 538 - Y * (Credits_X + X) * (Credits_X + X) * (Credits_X + X) / 1000000);

      if (Credits_X + X > 32) then
        A := 17
      else if (Credits_X + X >= 15) then
        A := Credits_X + X - 15
      else
        A := 0;

      glColor4f(230 / 255 - 40 / 255 + Y * (Credits_X + X)/  900,
                200 / 255 - 30 / 255 + Y * (Credits_X + X)/ 1000,
                155 / 255 - 20 / 255 + Y * (Credits_X + X)/ 1100,
                  A / 17);
      glPrint(visibleText[S]);
      X := X + glTextWidth(visibleText[S]);
    end;

    if (Credits_X < 0) and (CurrentScrollStart < length(Funky_Text)) then
    begin
      Credits_X := Credits_X + glTextWidth(Funky_Text[CurrentScrollStart]);
      inc(CurrentScrollStart);
    end;

    visibleText := Copy(Funky_Text, CurrentScrollStart, CurrentScrollEnd);

    if (Credits_X + glTextWidth(visibleText) < 600) and
       (CurrentScrollEnd < length(Funky_Text)) then
    begin
      inc(CurrentScrollEnd);
    end;
  end;
{
// timing hack
  X:=5;
  SetFontStyle(2);
  SetFontItalic(false);
  SetFontSize(27);
  glColor4f(1, 1, 1, 1);
  for S := 0 to high(CTime_hold) do
  begin
    visibleText := inttostr(CTime_hold[S]);
    SetFontPos (500, X);
    glPrint(visibleText[0]);
    X := X + 20;
  end;
}
end;

procedure TScreenCredits.DrawNames;
  var
    Dev: integer;
    Ticks: integer;
    DevTicks: integer;
    TwinkleW, TwinkleH: integer;
begin
  Ticks := (CTime - Main_Names_Start);
  Dev := Ticks div TimePerName;
  DevTicks := Ticks mod TimePerName;

  {// debug stuff
  SetFontPos(20, 20);
  glPrint('Ticks: ' + IntToStr(Ticks));
  SetFontPos(20, 45);
  glPrint('Dev: ' + IntToStr(Dev));
  SetFontPos(20, 70);
  glPrint('DevTicks: ' + IntToStr(DevTicks)); //}

  if (Ticks >= 0) and (Dev <= High(Developers)) then
  begin
    { spawn twinkling stars }
    if (Developers[Dev].Twinkle) and (DevTicks >= NameFadeTime) and (DevTicks <= NameFadeTime + NameTwinkleTime) then
    begin
      TwinkleW := Round(NameW * 0.6);
      TwinkleH := Round(NameH * 0.6);

      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 0);
      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 1);
      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 5);
      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 0);
      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 1);
      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 5);
      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 0);
      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 1);
      GoldenRec.Spawn(NameX + RandomRange(-TwinkleW, TwinkleW), NameY + RandomRange(-TwinkleH, TwinkleH), 1, 16, 0, -1, PerfectLineTwinkle, 5);
    end;

    { prepare drawing }
    glPushMatrix;
    glTranslatef(NameX, NameY, 0);
    glBindTexture(GL_TEXTURE_2D, credits_names[Dev].TexNum);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glEnable(GL_TEXTURE_2D);

    // calculate progress and call effect
    if (DevTicks <= NameFadeTime) then
      Developers[Dev].FadeIn(credits_names[Dev], DevTicks / NameFadeTime)
    else if (DevTicks >= TimePerName - NameFadeTime - NameWaitTime) then
    begin
      if (DevTicks < TimePerName - NameWaitTime) then
        Developers[Dev].FadeOut(credits_names[Dev], ((TimePerName - NameWaitTime) - DevTicks) / NameFadeTime);
    end
    else
      Developers[Dev].Draw(credits_names[Dev], (DevTicks - NameFadeTime) / (TimePerName - NameFadeTime * 2 - NameWaitTime));

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

procedure TScreenCredits.DrawMainFG;
begin
  glEnable(GL_TEXTURE_2D);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);
  glColor4f(1, 1, 1, 1);
  glBindTexture(GL_TEXTURE_2D, credits_bg_ovl.TexNum);
  glBegin(gl_Quads);
    glTexCoord2f(      0,        0); glVertex2f(800-393,   0);
    glTexCoord2f(      0, 600/1024); glVertex2f(800-393, 600);
    glTexCoord2f(393/512, 600/1024); glVertex2f(800,     600);
    glTexCoord2f(393/512,        0); glVertex2f(800,       0);
  glEnd;


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

procedure TScreenCredits.DoLogoBling;
  const
    myLogoCoords: array[0..27,0..1] of cardinal = (
      ( 39,32),( 84,32),(100,16),(125,24),
      (154,31),(156,58),(168,32),(203,36),
      (258,34),(251,50),(274,93),(294,84),
      (232,54),(278,62),(319,34),(336,92),
      (347,23),(374,32),(377,58),(361,83),
      (385,91),(405,91),(429,35),(423,51),
      (450,32),(485,34),(444,91),(486,93)
    );
  var
    Coords: integer;
    StartFrame: integer;
begin
  if (CTime > Main_OnBeatTwinkle_Start ) and
     (CTime < Main_FadeOut_Start) then
    begin
      { spawn stars only in frames where a beat was detected }
      if BeatDetected then
      begin
         StartFrame := RandomRange(6, 16);
         Coords := RandomRange(0, 27);

         GoldenRec.Spawn(myLogoCoords[Coords,0], myLogoCoords[Coords,1], 16-StartFrame, StartFrame, 0, -1, PerfectNote, 0);
      end;
    end;
end;

procedure TScreenCredits.DrawOutro;
begin
  if CTime = Tune_End then
  begin
    CTime_hold := 0;
    AudioPlayback.Stop;
    AudioPlayback.Open(SoundPath.Append('credits-outro-tune.mp3'));
    AudioPlayback.SetVolume(0.2);
    AudioPlayback.SetLoop(true);
    AudioPlayback.Play;
  end;

  if CTime_hold > 231 then
  begin
    AudioPlayback.Play;
    Ctime_hold := 0;
  end;

  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  // do something useful
  // outro background
  glEnable(GL_TEXTURE_2D);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);

  glColor4f(1, 1, 1, 1);
  glBindTexture(GL_TEXTURE_2D, outro_bg.TexNum);
  glBegin(gl_quads);
    glTexCoord2f(       0,        0); glVertex2f(  0,   0);
    glTexCoord2f(       0, 600/1024); glVertex2f(  0, 600);
    glTexCoord2f(800/1024, 600/1024); glVertex2f(800, 600);
    glTexCoord2f(800/1024,        0); glVertex2f(800,   0);
  glEnd;

  // outro overlays
  glColor4f(1, 1, 1, (2 + sin(CTime / 15)) / 3);
  glBindTexture(GL_TEXTURE_2D, outro_esc.TexNum);
  glBegin(Gl_Quads);
    glTexCoord2f(      0,       0); glVertex2f(  0,   0);
    glTexCoord2f(      0, 223/256); glVertex2f(  0, 223);
    glTexCoord2f(487/512, 223/256); glVertex2f(487, 223);
    glTexCoord2f(487/512,       0); glVertex2f(487,   0);
  glEnd;

  if (RandomRange(0,20) <= 18) then
  begin
    glColor4f(1, 1, 1, 1);
    glBindTexture(GL_TEXTURE_2D, outro_exd.TexNum);
    glBegin(Gl_Quads);
      glTexCoord2f(      0,       0); glVertex2f(800-310, 600-247);
      glTexCoord2f(      0, 247/256); glVertex2f(800-310, 600    );
      glTexCoord2f(310/512, 247/256); glVertex2f(800,     600    );
      glTexCoord2f(310/512,       0); glVertex2f(800,     600-247);
    glEnd;
  end;

  glDisable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);

  // outro scrollers?
  // ...
end;

{ name effects }
{ effects are called with blending texture and matrix prepared }
procedure Effect_Draw (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, 1);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_OnBeatJitter (const Tex: TTexture; Progress: double);
  var
    Diff: cardinal;
    Alpha: double;
begin
  Diff := ScreenCredits.CTime - ScreenCredits.LastBeatTime;
  if (Diff < BeatJitterTime) then  
    Alpha := 0.5 + 0.5 * Diff / BeatJitterTime
  else
    Alpha := 1;

  glColor4f(1, 1, 1, Alpha);
  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Rotate_Left_Top (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, Progress);

  gltranslatef(-NameX, 0, 0);
  glrotatef(Progress * 90 + 270, 0, 0, 1);
  gltranslatef(NameX, 0, 0);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Rotate_Right_Bot (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, Progress);

  gltranslatef(NameX, 0, 0);
  glrotatef((Progress - 1) * 90, 0, 0, 1);
  gltranslatef(-NameX, 0, 0);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_ZoomIn_Rotate (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, Progress);

  glscalef(sqr(Progress), sqr(Progress), sqr(Progress));
  glrotatef(Progress * 360, 0, 0, 1);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_ZoomOut_Shift (const Tex: TTexture; Progress: double);
  var
    X: double;
begin
  glColor4f(1, 1, 1, Progress);

  X := (1 - Progress);
  gltranslatef(X * 300, -X * 100, 0);
  glscalef(1 + X, 1 + X, 1 + X);
  glrotatef(X * 90, 0, 0, 1);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Shift_Left (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, Progress);

  glTranslatef((Progress - 1) * 210, 0, 0);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Shift_Right_Top (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, Progress);

  glTranslatef((1 - Progress) * 210, (Progress - 1) * 105, 0);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Flip_Bot (const Tex: TTexture; Progress: double);
  var
    X: double;
begin
  glColor4f(1, 1, 1, Progress);

  X := NameH * (1 - Progress);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2 - 1.5 * X, -NameH/2 + 1.5 * X);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2 + 1.5 * X, -NameH/2 + 1.5 * X);
  glEnd;
end;

procedure Effect_Flip_Right_Top (const Tex: TTexture; Progress: double);
  var
    X: double;
begin
  glColor4f(1, 1, 1, Progress);

  X := NameW * (1 - Progress);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2 + X, -NameH/2 - X/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2 + X,  NameH/2 - (X * 1.5 * NameH / NameW));
    glTexCoord2f(1, 1); glVertex2f( NameW/2 + X,  NameH/2 + X / 4);
    glTexCoord2f(1, 0); glVertex2f( NameW/2 + X, -NameH/2 - X / 4);
  glEnd;
end;

procedure Effect_Flip_Right (const Tex: TTexture; Progress: double);
  var
    X: double;
begin
  glColor4f(1, 1, 1, Progress);

  X := NameW * (1 - Progress);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2,     -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,      NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2 - X,  NameH/2 + X * 1.5);
    glTexCoord2f(1, 0); glVertex2f( NameW/2 - X, -NameH/2 - X * 1.5);
  glEnd;
end;

procedure Effect_Flip_Right_Bot (const Tex: TTexture; Progress: double);
  var
    X: double;
begin
  glColor4f(1, 1, 1, Progress);

  X := NameW * (1 - Progress);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2 + X * 1.5, -NameH/2 + X * 1.5);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2 + X * 1.2,  NameH/2 + X);
    glTexCoord2f(1, 1); glVertex2f( NameW/2 + X / 2,    NameH/2 + X / 4);
    glTexCoord2f(1, 0); glVertex2f( NameW/2 + X * 1.5, -NameH/2);
  glEnd;
end;

procedure Effect_Rotate_Right_Top (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, Progress);

  glTranslatef(NameX, 0, 0);
  glrotatef((1 - Progress) * 90, 0, 0, 1);
  glTranslatef(-NameX, 0, 0);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Shift_Weird (const Tex: TTexture; Progress: double);
  var
    X: double;
begin
  glColor4f(1, 1, 1, Progress);

  X := (Progress - 1);

  glTranslatef(X * 200, X * 100, 0);
  glScalef(Progress, Progress, Progress);
  glRotatef(X * 90, 0, 0, 1);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Shift_Right_Bot (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, Progress);

  glTranslatef((1 - Progress) * 200, (1 - Progress) * 100, 0);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Rotate_Right_Top2 (const Tex: TTexture; Progress: double);
begin
  glColor4f(1, 1, 1, Progress);

  glTranslatef(0, -NameX, 0);
  glRotatef((Progress - 1) * 90, 0, 0, 1);
  glTranslatef(0, NameX, 0);
  glRotatef((1 - Progress) * 90, 0, 0, 1);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH/2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2,  NameH/2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2,  NameH/2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH/2);
  glEnd;
end;

procedure Effect_Flip_Left_Bot (const Tex: TTexture; Progress: double);
  var
    X: double;
begin
  glColor4f(1, 1, 1, Progress);

  X := (1 - Progress) * NameW;

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2 - X,       -NameH/2 + X / 4);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2 - X / 4,    NameH/2 + X / 4);
    glTexCoord2f(1, 1); glVertex2f( NameW/2 - X * 1.2,  NameH/2 + X / 2);
    glTexCoord2f(1, 0); glVertex2f( NameW/2 - X * 1.5, -NameH/2 + X * 1.5);
  glEnd;
end;

procedure Effect_Flip_Right_Top2 (const Tex: TTexture; Progress: double);
  var
    X: double;
begin
  glColor4f(1, 1, 1, Progress);

  X := (1 - Progress) * NameW;

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2 + X,     -NameH/2 - X / 2);
    glTexCoord2f(0, 1); glVertex2f(-NameW/2 + X,      NameH/2 + X / 2);
    glTexCoord2f(1, 1); glVertex2f( NameW/2 + X / 4,  NameH/2 - X / 4);
    glTexCoord2f(1, 0); glVertex2f( NameW/2 + X / 4, -NameH/2 + X / 4);
  glEnd;
end;

procedure Effect_Twinkle_Down     (const Tex: TTexture; Progress: double);
begin
  // draw name
  glColor4f(1, 1, 1, 1);

  glTranslatef(0, NameH/2, 0);

  glBegin(gl_Quads);
    glTexCoord2f(0, 0); glVertex2f(-NameW/2, -NameH * Progress);
    glTexCoord2f(0, Progress); glVertex2f(-NameW/2, 0);
    glTexCoord2f(1, Progress); glVertex2f( NameW/2, 0);
    glTexCoord2f(1, 0); glVertex2f( NameW/2, -NameH * Progress);
  glEnd;

  //spawn some stars on the edge
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 0);
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 1);
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 5);
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 0);
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 1);
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 5);
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 0);
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 1);
  GoldenRec.Spawn(NameX + RandomRange(-NameW div 2, NameW div 2), NameY - NameH/2 + (1 - Progress) * NameH, 1, 16, 0, -1, PerfectLineTwinkle, 5);
end;

{ beat detection algorithm
  based on a tutorial from Fr�d�ric Patin on gamedev.net
  http://www.gamedev.net/reference/programming/features/beatdetection/default.asp }

{ calculates average value of a history buffer }
function Average(History: TEnergyHistory): single;
  var I: integer; 
begin
  Result := 0;

  for I := 0 to HistoryLength - 1 do
    Result := Result + History[I];

  Result := Result / HistoryLength;
end;

{ calculates variance value of a history buffer }
function Variance(History: TEnergyHistory; Average: single): single;
  var I: integer;
begin
  Result := 0;

  for I := 0 to HistoryLength - 1 do
    Result := Result + sqr(History[I] - Average);

  Result := Result / HistoryLength;
end;

{ shifts all values of the history to the right and
  adds the new value at the front }
procedure AddHistory(Value: single; var History: TEnergyHistory);
  var I: integer;
begin
  for I := HistoryLength - 1 downto 1 do
    History[I] := History[I-1];

  History[0] := Value;
end;

{ calculates instant energy from FFT data for a specific
  subchannel (0..SubChannelCount - 1) }
function CalculateInstantEnergy(SubChannel: integer; Data: TFFTData): single;
  var I: integer;
begin
  Result := 0;
  for I := SubChannel * SamplesPerChannel to (SubChannel + 1) * SamplesPerChannel - 1 do
    Result := Result + Data[I] * BeatEnergyModifier;

  Result := Result / SamplesPerChannel;
end;

procedure TScreenCredits.DetectBeat;
  var
    Data: TFFTData;
    I: integer;
    Instant: single;
    C, E, V: single;
begin
  AudioPlayback.GetFFTData(Data);

  // do beatdetection for every subchannel
  for I := 0 to SubChannelCount - 1 do
  begin
    Instant := CalculateInstantEnergy(I, Data);
    E := Average(SubchannelHistory[I]);
    V := Variance(SubchannelHistory[I], E);

    C := (-0.0025714 * V) + 1.5142857;

    AddHistory(Instant, SubChannelHistory[I]);

    if (Instant > 2) and (Instant > C * E) then
    begin
      // beat detected
      BeatDetected := true;
      LastBeatTime := CTime;
    end;
  end;
end;

end.