{############################################################################ # Visualizer support for UltraStar deluxe # # # # Created by hennymcc # # Slight modifications by Jay Binks # # based on UVideo.pas # #############################################################################} unit UVisualizer; interface {$IFDEF FPC} {$MODE DELPHI} {$ENDIF} {$I switches.inc} uses SDL, UGraphicClasses, textgl, math, OpenGL12, SysUtils, UIni, {$ifdef DebugDisplay} {$ifdef win32} dialogs, {$endif} {$endif} projectM, UMusic; implementation uses UGraphic, UMain, ULog; var singleton_VideoProjectM : IVideoPlayback; const gx = 32; gy = 24; fps = 30; texsize = 512; var ProjectMPath : string; presetsDir : string; fontsDir : string; // FIXME: dirty fix needed because the init method is not // called yet. inited: boolean; type TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoPlayback, IVideoVisualization ) private pm : TProjectM; VisualizerStarted , VisualizerPaused : Boolean; VisualTex : glUint; PCMData : TPCMData; RndPCMcount : integer; projMatrix: array[0..3, 0..3] of GLdouble; texMatrix: array[0..3, 0..3] of GLdouble; procedure VisualizerStart; procedure VisualizerStop; procedure VisualizerTogglePause; function GetRandomPCMData(var data: TPCMData): Cardinal; procedure SaveOpenGLState(); procedure RestoreOpenGLState(); public constructor Create(); procedure Init(); function GetName: String; function Open(const aFileName : string): boolean; // true if succeed procedure Close; procedure Play; procedure Pause; procedure Stop; procedure SetPosition(Time: real); function GetPosition: real; procedure GetFrame(Time: Extended); procedure DrawGL(Screen: integer); end; constructor TVideoPlayback_ProjectM.Create(); begin RndPCMcount := 0; end; procedure TVideoPlayback_ProjectM.Init(); begin // FIXME: dirty fix needed because the init method is not // called yet. inited := true; ProjectMPath := VisualsPath + 'projectM' + PathDelim; presetsDir := ProjectMPath + 'presets'; fontsDir := ProjectMPath + 'fonts'; VisualizerStarted := False; VisualizerPaused := False; {$IFDEF UseTexture} glGenTextures(1, PglUint(@VisualTex)); glBindTexture(GL_TEXTURE_2D, VisualTex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); {$ENDIF} end; function TVideoPlayback_ProjectM.GetName: String; begin result := 'ProjectM'; end; function TVideoPlayback_ProjectM.Open(const aFileName : string): boolean; // true if succeed begin VisualizerStart(); result := true; end; procedure TVideoPlayback_ProjectM.Close; begin end; procedure TVideoPlayback_ProjectM.Play; begin VisualizerStart(); end; procedure TVideoPlayback_ProjectM.Pause; begin VisualizerTogglePause(); end; procedure TVideoPlayback_ProjectM.Stop; begin VisualizerStop(); end; procedure TVideoPlayback_ProjectM.SetPosition(Time: real); begin pm.RandomPreset(); end; function TVideoPlayback_ProjectM.GetPosition: real; begin result := 0; end; procedure TVideoPlayback_ProjectM.SaveOpenGLState(); begin // save all OpenGL state-machine attributes glPushAttrib(GL_ALL_ATTRIB_BITS); // save projection-matrix glMatrixMode(GL_PROJECTION); // - WARNING: projection-matrix stack-depth is only 2! // -> overflow might occur if glPopMatrix() is used for this matrix // -> use glGet() instead of glPushMatrix() glPushMatrix(); //glGetDoublev(GL_PROJECTION_MATRIX, @projMatrix); // save texture-matrix glMatrixMode(GL_TEXTURE); // - WARNING: texture-matrix stack-depth is only 2! // -> overflow might occur if glPopMatrix() is used for this matrix // -> use glGet() instead of glPushMatrix() if problems appear glPushMatrix(); //glGetDoublev(GL_TEXTURE_MATRIX, @texMatrix); // save modelview-matrix glMatrixMode(GL_MODELVIEW); glPushMatrix(); end; procedure TVideoPlayback_ProjectM.RestoreOpenGLState(); begin // restore projection-matrix glMatrixMode(GL_PROJECTION); // - WARNING: projection-matrix stack-depth is only 2! // -> overflow _occurs_ if glPopMatrix() is used for this matrix // -> use glLoadMatrix() instead of glPopMatrix() glPopMatrix(); //glLoadMatrixd(@projMatrix); // restore texture-matrix // -> overflow might occur if glPopMatrix() is used for this matrix glMatrixMode(GL_TEXTURE); glPopMatrix(); //glLoadMatrixd(@texMatrix); // restore modelview-matrix glMatrixMode(GL_MODELVIEW); glPopMatrix(); // restore all OpenGL state-machine attributes glPopAttrib(); end; procedure TVideoPlayback_ProjectM.VisualizerStart; var initResult: Cardinal; begin // FIXME: dirty fix needed because the init method is not // called yet. if (not inited) then Init(); VisualizerStarted := True; pm := TProjectM.Create(gx, gy, fps, texsize, ScreenW, ScreenH, presetsDir, fontsDir); //initResult := projectM_initRenderToTexture(pm); SaveOpenGLState(); pm.ResetGL(ScreenW, ScreenH); RestoreOpenGLState(); end; procedure TVideoPlayback_ProjectM.VisualizerStop; begin if VisualizerStarted then begin VisualizerStarted := False; pm.Free(); end; end; procedure TVideoPlayback_ProjectM.VisualizerTogglePause; begin VisualizerPaused := not VisualizerPaused; end; procedure TVideoPlayback_ProjectM.GetFrame(Time: Extended); var nSamples: cardinal; stackDepth: Integer; begin if not VisualizerStarted then Exit; if VisualizerPaused then Exit; // get audio data nSamples := AudioPlayback.GetPCMData(PcmData); if nSamples = 0 then nSamples := GetRandomPCMData(PcmData); pm.AddPCM16Data(PSmallint(@PcmData), nSamples); // store OpenGL state (might be messed up otherwise) SaveOpenGLState(); pm.ResetGL(ScreenW, ScreenH); //glGetIntegerv(GL_PROJECTION_STACK_DEPTH, @stackDepth); //writeln('StackDepth0: ' + inttostr(stackDepth)); // let projectM render a frame try pm.RenderFrame(); except // this may happen with some presets ( on linux ) if there is a div by zero // in projectM's getBeatVals() function (file: beat_detect.cc) Log.LogStatus('Div by zero!', 'Visualizer'); SetPosition( now ); end; //glGetIntegerv(GL_PROJECTION_STACK_DEPTH, @stackDepth); //writeln('StackDepth1: ' + inttostr(stackDepth)); {$IFDEF UseTexture} glBindTexture(GL_TEXTURE_2D, VisualTex); glFlush(); glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, VisualWidth, VisualHeight, 0); {$ENDIF} // restore USDX OpenGL state RestoreOpenGLState(); // discard projectM's depth buffer information (avoid overlay) glClear(GL_DEPTH_BUFFER_BIT); end; procedure TVideoPlayback_ProjectM.DrawGL(Screen: integer); begin {$IFDEF UseTexture} // have a nice black background to draw on (even if there were errors opening the vid) if Screen=1 then begin glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); end; // exit if there's nothing to draw if not VisualizerStarted then Exit; // setup display glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(0, 1, 0, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glEnable(GL_BLEND); glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBindTexture(GL_TEXTURE_2D, VisualTex); glColor4f(1, 1, 1, 1); // draw projectM frame glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex2f(0, 0); glTexCoord2f(1, 0); glVertex2f(1, 0); glTexCoord2f(1, 1); glVertex2f(1, 1); glTexCoord2f(0, 1); glVertex2f(0, 1); glEnd(); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); // restore state glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); {$ENDIF} end; function TVideoPlayback_ProjectM.GetRandomPCMData(var data: TPCMData): Cardinal; var i: integer; begin // Produce some fake PCM data if ( RndPCMcount mod 500 = 0 ) then begin for i := 0 to 511 do begin data[0][i] := 0; data[1][i] := 0; end; end else begin for i := 0 to 511 do begin if ( i mod 2 = 0 ) then begin data[0][i] := floor(Random * power(2.,14)); data[1][i] := floor(Random * power(2.,14)); end else begin; data[0][i] := floor(Random * power(2.,14)); data[1][i] := floor(Random * power(2.,14)); end; if ( i mod 2 = 1 ) then begin data[0][i] := -data[0][i]; data[1][i] := -data[1][i]; end; end; end; Inc( RndPCMcount ); result := 512; end; initialization singleton_VideoProjectM := TVideoPlayback_ProjectM.create(); AudioManager.add( singleton_VideoProjectM ); finalization AudioManager.Remove( singleton_VideoProjectM ); end.