aboutsummaryrefslogtreecommitdiffstats
path: root/src/media/UVisualizer.pas
diff options
context:
space:
mode:
Diffstat (limited to 'src/media/UVisualizer.pas')
-rw-r--r--src/media/UVisualizer.pas685
1 files changed, 685 insertions, 0 deletions
diff --git a/src/media/UVisualizer.pas b/src/media/UVisualizer.pas
new file mode 100644
index 00000000..1cdc3500
--- /dev/null
+++ b/src/media/UVisualizer.pas
@@ -0,0 +1,685 @@
+{* 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 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)
+ *)
+
+{* Note:
+ * It would be easier to create a seperate Render-Context (RC) for projectM
+ * and switch to it when necessary. This can be achieved by pbuffers
+ * (slow and platform specific) or the OpenGL FramebufferObject (FBO) extension
+ * (fast and plattform-independent but not supported by older graphic-cards/drivers).
+ *
+ * See http://oss.sgi.com/projects/ogl-sample/registry/EXT/framebuffer_object.txt
+ *
+ * To support as many cards as possible we will stick to the current dirty
+ * solution for now even if it is a pain to save/restore projectM's state due
+ * to bugs etc.
+ *
+ * This also restricts us to projectM. As other plug-ins might have different
+ * needs and bugs concerning the OpenGL state, USDX's state would probably be
+ * corrupted after the plug-in finshed drawing.
+ *}
+
+interface
+
+{$IFDEF FPC}
+ {$MODE DELPHI}
+{$ENDIF}
+
+{$I switches.inc}
+
+{.$DEFINE UseTexture}
+
+uses
+ SDL,
+ UGraphicClasses,
+ textgl,
+ math,
+ gl,
+ {$IFDEF UseTexture}
+ glu,
+ {$ENDIF}
+ SysUtils,
+ UIni,
+ projectM,
+ UMusic;
+
+implementation
+
+uses
+ UGraphic,
+ UMain,
+ UConfig,
+ UPath,
+ ULog;
+
+{$IF PROJECTM_VERSION < 1000000} // < 1.0
+// Initialization data used on projectM 0.9x creation.
+// Since projectM 1.0 this data is passed via the config-file.
+const
+ meshX = 32;
+ meshY = 24;
+ fps = 30;
+ textureSize = 512;
+{$IFEND}
+
+type
+ TProjectMState = ( pmPlay, pmStop, pmPause );
+
+type
+ TGLMatrix = array[0..3, 0..3] of GLdouble;
+ TGLMatrixStack = array of TGLMatrix;
+
+type
+ TVideo_ProjectM = class( TInterfacedObject, IVideo )
+ private
+ fPm: TProjectM;
+ fProjectMPath : string;
+
+ fState: TProjectMState;
+
+ fScreen: integer;
+
+ fVisualTex: GLuint;
+ fPCMData: TPCMData;
+ fRndPCMcount: integer;
+
+ fModelviewMatrixStack: TGLMatrixStack;
+ fProjectionMatrixStack: TGLMatrixStack;
+ fTextureMatrixStack: TGLMatrixStack;
+
+ procedure InitProjectM;
+
+ function GetRandomPCMData(var Data: TPCMData): Cardinal;
+
+ function GetMatrixStackDepth(MatrixMode: GLenum): GLint;
+ procedure SaveMatrixStack(MatrixMode: GLenum; var MatrixStack: TGLMatrixStack);
+ procedure RestoreMatrixStack(MatrixMode: GLenum; var MatrixStack: TGLMatrixStack);
+ procedure SaveOpenGLState();
+ procedure RestoreOpenGLState();
+
+ public
+ constructor Create;
+ destructor Destroy; override;
+
+ procedure Close;
+
+ procedure Play;
+ procedure Pause;
+ procedure Stop;
+
+ procedure SetPosition(Time: real);
+ function GetPosition: real;
+
+ procedure SetLoop(Enable: boolean);
+ function GetLoop(): boolean;
+
+ procedure SetScreen(Screen: integer);
+ function GetScreen(): integer;
+
+ procedure SetScreenPosition(X, Y, Z: double);
+ procedure GetScreenPosition(var X, Y, Z: double);
+
+ procedure SetWidth(Width: double);
+ function GetWidth(): double;
+
+ procedure SetHeight(Height: double);
+ function GetHeight(): double;
+
+ procedure SetFrameRange(Range: TRectCoords);
+ function GetFrameRange(): TRectCoords;
+
+ function GetFrameAspect(): real;
+
+ procedure SetAspectCorrection(AspectCorrection: TAspectCorrection);
+ function GetAspectCorrection(): TAspectCorrection;
+
+ procedure SetAlpha(Alpha: double);
+ function GetAlpha(): double;
+
+ procedure SetReflectionSpacing(Spacing: double);
+ function GetReflectionSpacing(): double;
+
+ procedure GetFrame(Time: Extended);
+ procedure Draw();
+ procedure DrawReflection();
+ end;
+
+ TVideoPlayback_ProjectM = class( TInterfacedObject, IVideoVisualization )
+ private
+ fInitialized: boolean;
+
+ public
+ function GetName: String;
+
+ function Init(): boolean;
+ function Finalize(): boolean;
+
+ function Open(const aFileName: IPath): IVideo;
+ end;
+
+
+{ TVideoPlayback_ProjectM }
+
+function TVideoPlayback_ProjectM.GetName: String;
+begin
+ Result := 'ProjectM';
+end;
+
+function TVideoPlayback_ProjectM.Init(): boolean;
+begin
+ Result := true;
+ if (fInitialized) then
+ Exit;
+ fInitialized := true;
+end;
+
+function TVideoPlayback_ProjectM.Finalize(): boolean;
+begin
+ Result := true;
+end;
+
+function TVideoPlayback_ProjectM.Open(const aFileName: IPath): IVideo;
+begin
+ Result := TVideo_ProjectM.Create;
+end;
+
+
+{ TVideo_ProjectM }
+
+constructor TVideo_ProjectM.Create;
+begin
+ fRndPCMcount := 0;
+
+ fProjectMPath := ProjectM_DataDir + PathDelim;
+
+ fState := pmStop;
+
+ {$IFDEF UseTexture}
+ glGenTextures(1, PglUint(@fVisualTex));
+ glBindTexture(GL_TEXTURE_2D, fVisualTex);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ {$ENDIF}
+
+ InitProjectM();
+end;
+
+destructor TVideo_ProjectM.Destroy;
+begin
+ Close();
+ {$IFDEF UseTexture}
+ glDeleteTextures(1, PglUint(@fVisualTex));
+ {$ENDIF}
+end;
+
+procedure TVideo_ProjectM.Close;
+begin
+ FreeAndNil(fPm);
+end;
+
+procedure TVideo_ProjectM.Play;
+begin
+ if (fState = pmStop) and (assigned(fPm)) then
+ fPm.RandomPreset();
+ fState := pmPlay;
+end;
+
+procedure TVideo_ProjectM.Pause;
+begin
+ if (fState = pmPlay) then
+ fState := pmPause
+ else if (fState = pmPause) then
+ fState := pmPlay;
+end;
+
+procedure TVideo_ProjectM.Stop;
+begin
+ fState := pmStop;
+end;
+
+procedure TVideo_ProjectM.SetPosition(Time: real);
+begin
+ if assigned(fPm) then
+ fPm.RandomPreset();
+end;
+
+function TVideo_ProjectM.GetPosition: real;
+begin
+ Result := 0;
+end;
+
+procedure TVideo_ProjectM.SetLoop(Enable: boolean);
+begin
+end;
+
+function TVideo_ProjectM.GetLoop(): boolean;
+begin
+ Result := true;
+end;
+
+procedure TVideo_ProjectM.SetScreen(Screen: integer);
+begin
+end;
+
+function TVideo_ProjectM.GetScreen(): integer;
+begin
+ Result := 0;
+end;
+
+procedure TVideo_ProjectM.SetScreenPosition(X, Y, Z: double);
+begin
+end;
+
+procedure TVideo_ProjectM.GetScreenPosition(var X, Y, Z: double);
+begin
+ X := 0;
+ Y := 0;
+ Z := 0;
+end;
+
+procedure TVideo_ProjectM.SetWidth(Width: double);
+begin
+end;
+
+function TVideo_ProjectM.GetWidth(): double;
+begin
+ Result := 0;
+end;
+
+procedure TVideo_ProjectM.SetHeight(Height: double);
+begin
+end;
+
+function TVideo_ProjectM.GetHeight(): double;
+begin
+ Result := 0;
+end;
+
+procedure TVideo_ProjectM.SetFrameRange(Range: TRectCoords);
+begin
+end;
+
+function TVideo_ProjectM.GetFrameRange(): TRectCoords;
+begin
+ Result.Left := 0;
+ Result.Right := 0;
+ Result.Upper := 0;
+ Result.Lower := 0;
+end;
+
+function TVideo_ProjectM.GetFrameAspect(): real;
+begin
+ Result := 0;
+end;
+
+procedure TVideo_ProjectM.SetAspectCorrection(AspectCorrection: TAspectCorrection);
+begin
+end;
+
+function TVideo_ProjectM.GetAspectCorrection(): TAspectCorrection;
+begin
+ Result := acoStretch;
+end;
+
+procedure TVideo_ProjectM.SetAlpha(Alpha: double);
+begin
+end;
+
+function TVideo_ProjectM.GetAlpha(): double;
+begin
+ Result := 1;
+end;
+
+procedure TVideo_ProjectM.SetReflectionSpacing(Spacing: double);
+begin
+end;
+
+function TVideo_ProjectM.GetReflectionSpacing(): double;
+begin
+ Result := 0;
+end;
+
+{**
+ * Returns the stack depth of the given OpenGL matrix mode stack.
+ *}
+function TVideo_ProjectM.GetMatrixStackDepth(MatrixMode: GLenum): GLint;
+begin
+ // get number of matrices on stack
+ case (MatrixMode) of
+ GL_PROJECTION:
+ glGetIntegerv(GL_PROJECTION_STACK_DEPTH, @Result);
+ GL_MODELVIEW:
+ glGetIntegerv(GL_MODELVIEW_STACK_DEPTH, @Result);
+ GL_TEXTURE:
+ glGetIntegerv(GL_TEXTURE_STACK_DEPTH, @Result);
+ end;
+end;
+
+{**
+ * Saves the current matrix stack using MatrixMode
+ * (one of GL_PROJECTION/GL_TEXTURE/GL_MODELVIEW)
+ *
+ * Use this function instead of just saving the current matrix with glPushMatrix().
+ * OpenGL specifies the depth of the GL_PROJECTION and GL_TEXTURE 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, but this
+ * function should be used for the modelview stack too. We cannot rely on a
+ * proper stack management of the underlying visualizer (projectM).
+ * For example in the projectM versions 1.0 - 1.01 the modelview- and
+ * projection-matrices were popped without being pushed first.
+ *
+ * By saving the whole stack we are on the safe side, so a nasty bug in the
+ * visualizer does not corrupt USDX.
+ *}
+procedure TVideo_ProjectM.SaveMatrixStack(MatrixMode: GLenum;
+ var MatrixStack: TGLMatrixStack);
+var
+ I: integer;
+ StackDepth: GLint;
+begin
+ glMatrixMode(MatrixMode);
+
+ StackDepth := GetMatrixStackDepth(MatrixMode);
+ SetLength(MatrixStack, StackDepth);
+
+ // save current matrix stack
+ for I := StackDepth-1 downto 0 do
+ begin
+ // save current matrix
+ case (MatrixMode) of
+ GL_PROJECTION:
+ glGetDoublev(GL_PROJECTION_MATRIX, @MatrixStack[I]);
+ GL_MODELVIEW:
+ glGetDoublev(GL_MODELVIEW_MATRIX, @MatrixStack[I]);
+ GL_TEXTURE:
+ glGetDoublev(GL_TEXTURE_MATRIX, @MatrixStack[I]);
+ end;
+
+ // remove matrix from stack
+ if (I > 0) then
+ glPopMatrix();
+ end;
+
+ // reset default (first) matrix
+ glLoadIdentity();
+end;
+
+{**
+ * Restores the OpenGL matrix stack stored with SaveMatrixStack.
+ *}
+procedure TVideo_ProjectM.RestoreMatrixStack(MatrixMode: GLenum;
+ var MatrixStack: TGLMatrixStack);
+var
+ I: integer;
+ StackDepth: GLint;
+begin
+ glMatrixMode(MatrixMode);
+
+ StackDepth := GetMatrixStackDepth(MatrixMode);
+ // remove all (except the first) matrices from current stack
+ for I := 1 to StackDepth-1 do
+ glPopMatrix();
+
+ // rebuild stack
+ for I := 0 to High(MatrixStack) do
+ begin
+ glLoadMatrixd(@MatrixStack[I]);
+ if (I < High(MatrixStack)) then
+ glPushMatrix();
+ end;
+
+ // clean stored stack
+ SetLength(MatrixStack, 0);
+end;
+
+{**
+ * Saves the current OpenGL state.
+ * This is necessary to prevent projectM from corrupting USDX's current
+ * OpenGL state.
+ *
+ * The following steps are performed:
+ * - All attributes are pushed to the attribute-stack
+ * - Projection-/Texture-matrices are saved
+ * - Modelview-matrix is pushed to the Modelview-stack
+ * - the OpenGL error-state (glGetError) is cleared
+ *}
+procedure TVideo_ProjectM.SaveOpenGLState();
+begin
+ // save all OpenGL state-machine attributes
+ glPushAttrib(GL_ALL_ATTRIB_BITS);
+ glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS);
+
+ SaveMatrixStack(GL_PROJECTION, fProjectionMatrixStack);
+ SaveMatrixStack(GL_MODELVIEW, fModelviewMatrixStack);
+ SaveMatrixStack(GL_TEXTURE, fTextureMatrixStack);
+
+ glMatrixMode(GL_MODELVIEW);
+
+ // reset OpenGL error-state
+ glGetError();
+end;
+
+{**
+ * Restores the OpenGL state saved by SaveOpenGLState()
+ * and resets the error-state.
+ *}
+procedure TVideo_ProjectM.RestoreOpenGLState();
+begin
+ // reset OpenGL error-state
+ glGetError();
+
+ // restore matrix stacks
+ RestoreMatrixStack(GL_PROJECTION, fProjectionMatrixStack);
+ RestoreMatrixStack(GL_MODELVIEW, fModelviewMatrixStack);
+ RestoreMatrixStack(GL_TEXTURE, fTextureMatrixStack);
+
+ // restore all OpenGL state-machine attributes
+ // (also restores the matrix mode)
+ glPopClientAttrib();
+ glPopAttrib();
+end;
+
+procedure TVideo_ProjectM.InitProjectM;
+begin
+ // the OpenGL state must be saved before TProjectM.Create is called
+ SaveOpenGLState();
+ try
+
+ try
+ {$IF PROJECTM_VERSION >= 1000000} // >= 1.0
+ fPm := TProjectM.Create(fProjectMPath + 'config.inp');
+ {$ELSE}
+ fPm := TProjectM.Create(
+ meshX, meshY, fps, textureSize, ScreenW, ScreenH,
+ fProjectMPath + 'presets', fProjectMPath + '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;
+
+ // initialize OpenGL
+ fPm.ResetGL(ScreenW, ScreenH);
+ // skip projectM default-preset
+ fPm.RandomPreset();
+ // projectM >= 1.0 uses the OpenGL FramebufferObject (FBO) extension.
+ // Unfortunately it does NOT reset the framebuffer-context after
+ // TProjectM.Create. Either glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0) for
+ // a manual reset or TProjectM.RenderFrame() must be called.
+ // We use the latter so we do not need to load the FBO extension in USDX.
+ fPm.RenderFrame();
+ finally
+ RestoreOpenGLState();
+ end;
+end;
+
+procedure TVideo_ProjectM.GetFrame(Time: Extended);
+var
+ nSamples: cardinal;
+begin
+ if (fState <> pmPlay) then
+ Exit;
+
+ // get audio data
+ nSamples := AudioPlayback.GetPCMData(fPCMData);
+
+ // generate some data if non is available
+ if (nSamples = 0) then
+ nSamples := GetRandomPCMData(fPCMData);
+
+ // send audio-data to projectM
+ if (nSamples > 0) then
+ fPm.AddPCM16Data(PSmallInt(@fPCMData), nSamples);
+
+ // store OpenGL state (might be messed up otherwise)
+ SaveOpenGLState();
+ try
+ // setup projectM's OpenGL state
+ fPm.ResetGL(ScreenW, ScreenH);
+
+ // let projectM render a frame
+ fPm.RenderFrame();
+
+ {$IFDEF UseTexture}
+ glBindTexture(GL_TEXTURE_2D, fVisualTex);
+ glFlush();
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, fVisualWidth, fVisualHeight, 0);
+ {$ENDIF}
+ finally
+ // restore USDX OpenGL state
+ RestoreOpenGLState();
+ end;
+
+ // discard projectM's depth buffer information (avoid overlay)
+ glClear(GL_DEPTH_BUFFER_BIT);
+end;
+
+{**
+ * Draws the current frame to screen.
+ * TODO: this is not used yet. Data is directly drawn on GetFrame().
+ *}
+procedure TVideo_ProjectM.Draw();
+begin
+ {$IFDEF UseTexture}
+ // have a nice black background to draw on
+ if (fScreen = 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 (fState <> pmPlay) then
+ Exit;
+
+ // setup display
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ // Use count of screens instead of 1 for the right corner
+ // otherwise we would draw the visualization streched over both screens
+ // another point is that we draw over the at this time drawn first
+ // screen, if Screen = 2
+ gluOrtho2D(0, Screens, 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, fVisualTex);
+ glColor4f(1, 1, 1, 1);
+
+ // draw projectM frame
+ // Screen is 1 to 2. So current screen is from (Screen - 1) to (Screen)
+ glBegin(GL_QUADS);
+ glTexCoord2f(0, 0); glVertex2f((fScreen - 1), 0);
+ glTexCoord2f(1, 0); glVertex2f(fScreen, 0);
+ glTexCoord2f(1, 1); glVertex2f(fScreen, 1);
+ glTexCoord2f(0, 1); glVertex2f((fScreen - 1), 1);
+ glEnd();
+
+ glDisable(GL_TEXTURE_2D);
+ glDisable(GL_BLEND);
+
+ // restore state
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+ {$ENDIF}
+end;
+
+procedure TVideo_ProjectM.DrawReflection();
+begin
+end;
+
+{**
+ * Produces random "sound"-data in case no audio-data is available.
+ * Otherwise the visualization will look rather boring.
+ *}
+function TVideo_ProjectM.GetRandomPCMData(var Data: TPCMData): Cardinal;
+var
+ i: integer;
+begin
+ // Produce some fake PCM data
+ if (fRndPCMcount 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(fRndPCMcount);
+ Result := 512;
+end;
+
+
+initialization
+ MediaManager.Add(TVideoPlayback_ProjectM.Create);
+
+end.