unit UVisualizer; (* TODO: * - fix video/visualizer switching * - use GL_EXT_framebuffer_object for rendering to a separate framebuffer, * this will prevent plugins from messing up our render-context * (-> no stack corruption anymore, no need for Save/RestoreOpenGLState()). * - create a generic (C-compatible) interface for visualization plugins * - create a visualization plugin manager * - write a plugin for projectM in C/C++ (so we need no wrapper anymore) *) interface {$IFDEF FPC} {$MODE DELPHI} {$ENDIF} {$I switches.inc} uses SDL, UGraphicClasses, textgl, math, gl, SysUtils, UIni, projectM, UMusic; implementation uses UGraphic, UMain, UConfig, ULog; {$IF PROJECTM_VERSION < 1000000} // < 1.0 const meshX = 32; meshY = 24; fps = 30; textureSize = 512; {$IFEND} type TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoPlayback, IVideoVisualization ) private pm: TProjectM; ProjectMPath : string; Initialized: boolean; VisualizerStarted: boolean; 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 function GetName: String; function Init(): boolean; function Finalize(): boolean; 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; function TVideoPlayback_ProjectM.GetName: String; begin result := 'ProjectM'; end; function TVideoPlayback_ProjectM.Init(): boolean; begin Result := true; if (Initialized) then Exit; Initialized := true; RndPCMcount := 0; ProjectMPath := ProjectM_DataDir + PathDelim; 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.Finalize(): boolean; begin VisualizerStop(); {$IFDEF UseTexture} glDeleteTextures(1, PglUint(@VisualTex)); {$ENDIF} Result := true; end; function TVideoPlayback_ProjectM.Open(const aFileName : string): boolean; // true if succeed begin result := false; end; procedure TVideoPlayback_ProjectM.Close; begin VisualizerStop(); 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 if assigned(pm) then pm.NextPreset(); 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); // Note: we do not use glPushMatrix() for the GL_PROJECTION and GL_TEXTURE stacks. // OpenGL specifies the depth of those stacks to be at least 2 but projectM // already uses 2 stack-entries so overflows might be possible on older hardware. // In contrast to this the GL_MODELVIEW stack-size is at least 32, so we can // use glPushMatrix() for this stack. // save projection-matrix glMatrixMode(GL_PROJECTION); glGetDoublev(GL_PROJECTION_MATRIX, @projMatrix); {$IF (PROJECTM_VERSION >= 1000000) and (PROJECTM_VERSION < 1010000)} // [1.0..1.1) // bugfix (1.0 and 1.01): projection-matrix is popped without being pushed first glPushMatrix(); {$IFEND} // save texture-matrix glMatrixMode(GL_TEXTURE); glGetDoublev(GL_TEXTURE_MATRIX, @texMatrix); // save modelview-matrix glMatrixMode(GL_MODELVIEW); glPushMatrix(); {$IF (PROJECTM_VERSION >= 1000000) and (PROJECTM_VERSION < 1010000)} // [1.0..1.1) // bugfix (1.0 and 1.01): modelview-matrix is popped without being pushed first glPushMatrix(); {$IFEND} end; procedure TVideoPlayback_ProjectM.RestoreOpenGLState(); begin // restore projection-matrix glMatrixMode(GL_PROJECTION); glLoadMatrixd(@projMatrix); // restore texture-matrix glMatrixMode(GL_TEXTURE); 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 if VisualizerStarted then Exit; try {$IF PROJECTM_VERSION >= 1000000} // >= 1.0 pm := TProjectM.Create(ProjectMPath + 'config.inp'); {$ELSE} pm := TProjectM.Create( meshX, meshY, fps, textureSize, ScreenW, ScreenH, ProjectMPath + 'presets', ProjectMPath + 'fonts'); {$IFEND} except on E: Exception do begin // Create() might fail if the config-file is not found Log.LogError('TProjectM.Create: ' + E.Message, 'TVideoPlayback_ProjectM.VisualizerStart'); Exit; end; end; VisualizerStarted := True; // skip projectM preset pm.RandomPreset(); // initialize OpenGL SaveOpenGLState(); pm.ResetGL(ScreenW, ScreenH); RestoreOpenGLState(); end; procedure TVideoPlayback_ProjectM.VisualizerStop; begin if VisualizerStarted then begin VisualizerStarted := False; FreeAndNil(pm); 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); if (nSamples > 0) then pm.AddPCM16Data(PSmallInt(@PcmData), nSamples); // store OpenGL state (might be messed up otherwise) SaveOpenGLState(); pm.ResetGL(ScreenW, ScreenH); // let projectM render a frame pm.RenderFrame(); {$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 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 FillChar(data, SizeOf(TPCMData), 0); end else begin for i := 0 to 511 do begin data[i][0] := Random(High(Word)+1); data[i][1] := Random(High(Word)+1); end; end; Inc(RndPCMcount); result := 512; end; initialization MediaManager.Add(TVideoPlayback_ProjectM.Create); end.