# HG changeset patch # User tomwalters # Date 1287121253 0 # Node ID 7a573750b186c8a568f6aff67aa47ed7376a4e56 # Parent 06a26f5cdad7b73bd221041540bc90fcea812b71 - First add of a lot of graphics code from the old version. Not working yet, not even compiling yet. diff -r 06a26f5cdad7 -r 7a573750b186 trunk/SConstruct --- a/trunk/SConstruct Wed Sep 29 00:24:03 2010 +0000 +++ b/trunk/SConstruct Fri Oct 15 05:40:53 2010 +0000 @@ -51,6 +51,16 @@ 'Modules/Output/FileOutputHTK.cc', 'Modules/Output/FileOutputAIMC.cc', 'Modules/Features/ModuleGaussians.cc'] + +graphics_sources = [ 'Modules/Output/Graphics/GraphAxisSpec.cc', + 'Modules/Output/Graphics/GraphicsView.cc', + 'Modules/Output/Graphics/GraphicsViewTime.cc', + 'Modules/Output/Graphics/Devices/GraphicsOutputDevice.cc', + 'Modules/Output/Graphics/Devices/GraphicsOutputDeviceCairo.cc', + 'Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovie.cc', + 'Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovieDirect.cc' ] +graphics_libraries = [ 'cairo', + '' ] # List of currently incative source files which we may want to add back in sources_disabled = ['Modules/SNR/ModuleNoise.cc', diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevice.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevice.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,53 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Support/Common.h" +#include "Modules/Output/Graphics/Devices/GraphicsOutputDevice.h" + +GraphicsOutputDevice::GraphicsOutputDevice(Parameters *pParam) { + m_pParam = pParam; +} + +void GraphicsOutputDevice::gVertex3f(float x, + float y, + float z, + float r, + float g, + float b) { + gColor3f(r, g, b); + gVertex3f(x, y, z); +} + +void GraphicsOutputDevice::gVertex2f(float x, + float y, + float r, + float g, + float b) { + gColor3f(r, g, b); + gVertex3f(x, y, 0); +} + +void GraphicsOutputDevice::gVertex2f(float x, float y) { + gVertex3f(x, y, 0); +} + +void GraphicsOutputDevice::gText2f(float x, + float y, + const char *sStr, + bool bRotated) { + gText3f(x, y, 0, sStr, bRotated); +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevice.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevice.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,191 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __GRAPHICS_OUTPUT_DEVICE_H__ +#define __GRAPHICS_OUTPUT_DEVICE_H__ + +#include "Support/Parameters.h" + +/*! + * \class GraphicsOutputDevice "Output/GraphicsOutputDevice.h" + * \brief General output device class + * + * This class provides basic drawing primitives in a device-independent manner. + * It is structured like the OpenGL interface. Following is an example drawing + * five horizontal lines, with the colour changing between red and blue over + * time. + * \code + * GraphicsOutputDevice oOutput = static_cast(new GraphicsOutputDeviceSomething()); + * unsigned int a=0; + * ... + * oOutput->Start(); + * while ( bIsRunning ) { + * // Start a drawing operation + * oOutput->gGrab(); + * // Draw five horizontal lines + * for (int y=0; y<5; y++) { + * // Start a new line + * oOutput->gBegin(); + * // Give each line it's own colour + * oOutput->gColor3f( (a%255)/255, 0, 1-(a%255)/255 ); + * // Draw the line + * oOutput->gVertex2f(0, y); + * oOutput->gVertex2f(1, y); + * // End the line + * oOutput->gEnd(); + * } + * oOutput->gRelease(); + * Sleep(1); + * a++; + * } + * oOutput->Stop(); + * \endcode + * + * This class includes some basic overloading definitions to ease the + * children's implementation. Note that the basic operations that need + * implementation are glVertex3f(x,y,z) and glColor(r,g,b). + */ +class GraphicsOutputDevice { + public: + GraphicsOutputDevice(AimParameters *pParam); + virtual ~GraphicsOutputDevice() { }; + + /*! \brief Initialize the module, sets up everything to Start(). + * \return true on success, false on error + * + * Initialize() needs to be called before any other function. + * + * This method is called in it's form as displayed here by the GraphicsView, + * but you may want to setup your own Initialize(...) function with + * different arguments and call it yourself. + * + * Thus make sure you do all memory allocations here. They can be cleaned + * up by the destructor. Because Initialize() may fail, it's not put in + * the constructor, so it can return a value. + * + * \sa Module::Initialize() + */ + virtual bool Initialize(unsigned int iVerticesMax) { return true; }; + /*! \overload + * This function reloads the parameters; make sure to have at least the + * function with maximum parameters called once. + */ + virtual bool Initialize() { return true; }; + + /*! \brief Create a new drawing + * Run this before any other drawing command. + * \sa glRelease() + */ + virtual void gGrab() = 0; + + //! \brief Start a new vertex group for drawing a line strip + virtual void gBeginLineStrip() = 0; + //! \brief Start a new vertex group for drawing a quad strip + virtual void gBeginQuadStrip() = 0; + + /*! \brief Specify a vertex to draw + * \param[in] x X-coordinate of the vertex + * \param[in] y Y-coordinate of the vertex + * \param[in] z Z-coordinate of the vertex + * \param[in] r Red component of colour + * \param[in] g Green component of colour + * \param[in] b Blue component of colour + * + * Currently, only lines are implemented. + */ + virtual void gVertex3f(float x, float y, float z, float r, float g, float b); + + /*! \overload + * This will add a vertex with the last specified colour. + */ + + virtual void gVertex3f(float x, float y, float z) = 0; + /*! \overload + * This will add a vertex in the 2d-plane with z=0. + */ + + virtual void gVertex2f(float x, float y, float r, float g, float b); + + /*! \overload + * This will add a vertex in the 2d-plane with z=0 with the last + * specified colour. + */ + virtual void gVertex2f(float x, float y); + + /*! \brief Sets the current colour + * \param[in] r Red component + * \param[in] g Green component + * \param[in] b Blue component + */ + virtual void gColor3f(float r, float g, float b) = 0; + + //! \brief End a vertex group + virtual void gEnd() = 0; + + /*! \brief Render a text string + * \param[in] x X-coordinate of the text's alignment point + * \param[in] y Y-coordinate of the text's alignment point + * \param[in] z Z-coordinate of the text's alignment point + * \param[in] sStr Text to render + * \param[in] bRotated \c true for vertically rotated text + * + * Current alignment is horizontal:left and vertical:bottom + * \todo Allow multiple alignment points + */ + virtual void gText3f(float x, + float y, + float z, + const char *sStr, + bool bRotated = false) = 0; + + /*! \overload + * This will render a text string in the 2d-plane with z=0. + */ + virtual void gText2f(float x, + float y, + const char *sStr, + bool bRight = false); + + /*! \brief Finish drawing + * Call this when a drawing is finished. It also makes sure that the + * rendering is actually done. + * \sa glGrab() + */ + virtual void gRelease() = 0; + + /*! \brief Called when animation starts + * + * You may wonder what Start() and Stop() do here. Some implementations + * may want to behave differently with respect to updating, if an + * animation is running or not (e.g. updating). + */ + virtual void Start() { m_bRunning = true; } + + //! \brief Called when animation stops + virtual void Stop() { m_bRunning = false; } + + protected: + //! \brief True when animation is running + bool m_bRunning; + //! \brief Parameter store + AimParameters *m_pParam; + + //! \brief Pixel Formats + enum PixelFormat {AIM_PIX_FMT_RGB24_32, AIM_PIX_FMT_RGB24_24}; +}; + +#endif /* __GRAPHICS_OUTPUT_DEVICE__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceCairo.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceCairo.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,283 @@ +// Copyright 2007, Thomas Walters +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output to a graphics file using cairo (LGPL) + * + * \author Tom Walters and Willem van Engen + * \date created 2007/09/17 + * \version \$Header: $ + */ + +#include "Support/Common.h" + +#include +#include +#include +#include +#include + +#include "Support/util.h" +#include "Output/GraphicsOutputDeviceCairo.h" + +GraphicsOutputDeviceCairo::GraphicsOutputDeviceCairo(Parameters *pParam) + : GraphicsOutputDevice(pParam) { + m_bOutputFile = false; + m_iFileNumber = 0; + m_iVertexType = VertexTypeNone; + m_bUseMemoryBuffer=false; +} + +bool GraphicsOutputDeviceCairo::Initialize(const char *sDir) { + Init(); + + //! \todo Output to file if sDir is a file, to directory with + //! multiple images if it's a directory. + strncpy(m_sDir, sDir, sizeof(m_sDir)/sizeof(m_sDir[0])); + + /* Try to open an image to see if everything is allright. We want to avoid + * errors in the main Process()ing loop. */ + if ( !OpenFile(0) ) { + //! \todo Better error message that is more specific about the cause. + AIM_ERROR(_T("Could not open output directory '%s' using graphics format '%s'."), + m_sDir, m_pParam->GetString("output.img.format") ); + return false; + } + CloseFile(); + + return true; +} + +bool GraphicsOutputDeviceCairo::Initialize() { + Init(); + m_bUseMemoryBuffer = true; + return(true); +} + +void GraphicsOutputDeviceCairo::Init() { + AIM_ASSERT(m_pParam); + /* + * Set parameters + */ + m_pParam->GetString("output.img.color.background"); + + m_bInvertColors = m_pParam->GetBool("output.img.color.invert"); + + // Output size! + m_iWidth = m_pParam->GetUInt("output.img.width"); + m_iHeight = m_pParam->GetUInt("output.img.height"); +} + +unsigned char* GraphicsOutputDeviceCairo::GetBuffer() { + if(m_bUseMemoryBuffer) + return (cairo_image_surface_get_data (m_cSurface)); + else + return NULL; +} + +bool GraphicsOutputDeviceCairo::OpenFile(unsigned int index) { + const char *strPlottype = m_pParam->GetString("output.img.format"); + if (!m_bUseMemoryBuffer) { + struct stat fileinfo; + // Get filename without trailing slash + strncpy(m_sFilename, m_sDir, sizeof(m_sFilename)/sizeof(m_sFilename[0])); +#ifdef _WINDOWS + if (m_sFilename[strlen(m_sFilename)-1]=='\\') { + m_sFilename[strlen(m_sFilename)-1]='\0'; + } +#else + if (m_sFilename[strlen(m_sFilename)-1]=='/') { + m_sFilename[strlen(m_sFilename)-1]='\0'; + } +#endif + // Enumerate files it m_sDir is a directory. + if (stat(m_sFilename, &fileinfo) == 0 && (fileinfo.st_mode & S_IFDIR)) { + // We have a directory: enumerate with index + snprintf(m_sFilename, sizeof(m_sFilename)/sizeof(m_sFilename[0]),"%s%06d.%s", + m_sDir, + index, + strPlottype); + // If type is 'auto', fallback to 'png' + if (strcmp(strPlottype, "auto")==0) + strPlottype = "png"; + } else { + // We have a (probably non-existant) file. Auto-detect type by extension if requested + strncpy(m_sFilename, m_sDir, sizeof(m_sFilename)/sizeof(m_sFilename[0])); + char *pDot = strrchr(m_sFilename, '.'); + if (!pDot) { + AIM_ERROR(_T("Please supply extension on filename when using 'auto' format: '%s'"), + m_sFilename); + return false; + } + strPlottype = &pDot[1]; + } + m_bOutputFile= true; //! \todo Should check that it's possible to write to the file + } + // Cairo's RGB24 format has 32-bit pixels with the upper 8 bits unused. + // This is not the same as the plotutils PNG format. This information is transferred by the + // function GetPixelFormat. The pixel format is dealt with by the reciever. + m_cSurface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, + m_iWidth, + m_iHeight); + m_cCr = cairo_create (m_cSurface); + cairo_scale(m_cCr, (float)m_iWidth, (float)m_iHeight); + // Now setup things for this plotter. + cairo_select_font_face(m_cCr, + m_pParam->GetString("output.img.fontname"), + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size (m_cCr, 0.015); + return true; +} + +void GraphicsOutputDeviceCairo::CloseFile() { + // Plotting library + if (m_iPlotHandle>0) { + cairo_destroy(m_cCr); + m_iPlotHandle = 0; + } + // And the output file + if (m_bOutputFile) { + cairo_surface_write_to_png(m_cSurface, m_sFilename); + m_bOutputFile = false; + } + cairo_surface_destroy(m_cSurface); +} + +GraphicsOutputDeviceCairo::~GraphicsOutputDeviceCairo() { + AIM_ASSERT(!m_iPlotHandle); + CloseFile(); +} + +void GraphicsOutputDeviceCairo::gGrab() { + // Open file. + if (!OpenFile(m_iFileNumber)) { + return; + } + // Setup plotting area. + cairo_set_line_width (m_cCr, 0.001f); + gColor3f (0.0f, 0.0f, 0.0f); + cairo_paint (m_cCr); + gColor3f(1.0f, 1.0f, 0.0f); +} + +int GraphicsOutputDeviceCairo::GetPixelFormat() { + return AIM_PIX_FMT_RGB24_32; +} + +void GraphicsOutputDeviceCairo::gBeginLineStrip() { + m_bIsFirstVertex = true; + m_iVertexType = VertexTypeLine; + //! \todo Make line width user-settable + cairo_set_line_width (m_cCr, 0.001f); +} + +void GraphicsOutputDeviceCairo::gBeginQuadStrip() { + m_bIsFirstVertex = true; + m_iVertexType = VertexTypeQuad; + m_iPrevVertexCount = 0; + cairo_set_line_width (m_cCr, 0.001f); +} + +void GraphicsOutputDeviceCairo::gColor3f(float r, float g, float b) { + if (m_bInvertColors) { + r = 1-r; + g = 1-g; + b = 1-b; + } + cairo_set_source_rgb (m_cCr, r, g, b); +} + +void GraphicsOutputDeviceCairo::gVertex3f(float x, float y, float z) { + switch(m_iVertexType) { + case VertexTypeLine: + if (m_bIsFirstVertex) { + m_bIsFirstVertex = false; + //pl_fmove(x, y); + cairo_move_to(m_cCr, x, 1-y); + } else { + //pl_fcont(x, y); + cairo_line_to(m_cCr, x, 1-y); + } + break; + case VertexTypeQuad: + /* Store vertices until we got four in a row. + * The order of vertices when processing quads is: + * 1-----3-----5 + * | | | + * 0-----2-----4 + */ + if (m_iPrevVertexCount >= 3) { + // Plot this quad + cairo_move_to(m_cCr, m_aPrevX[0], 1-m_aPrevY[0]); + cairo_line_to(m_cCr, m_aPrevX[1], 1-m_aPrevY[1]); + cairo_line_to(m_cCr, x, y); + cairo_line_to(m_cCr, m_aPrevX[2], 1-m_aPrevY[2]); + cairo_close_path (m_cCr); + + // Last vertices of this quad are the first of the next + m_aPrevX[0] = m_aPrevX[2]; + m_aPrevY[0] = m_aPrevY[2]; + m_aPrevX[1] = x; + m_aPrevY[1] = y; + m_iPrevVertexCount = 2; + } else { + // Not at the fourth, keep storing + m_aPrevX[m_iPrevVertexCount] = x; + m_aPrevY[m_iPrevVertexCount] = y; + m_iPrevVertexCount++; + } + break; + default: + // Should not happen + AIM_ASSERT(0); + } +} + +void GraphicsOutputDeviceCairo::gEnd() { + if(m_iVertexType==VertexTypeLine) + cairo_stroke (m_cCr); + else + cairo_fill (m_cCr); + m_iVertexType = VertexTypeNone; +} + +void GraphicsOutputDeviceCairo::gText3f(float x, + float y, + float z, + const char *sStr, + bool bRotated) { + cairo_text_extents_t te; + if (bRotated) { + cairo_rotate(m_cCr, M_PI/2); + cairo_move_to(m_cCr, x ,1-y); + cairo_show_text(m_cCr, sStr); + //cairo_identity_matrix(m_cCr); + cairo_rotate(m_cCr, -M_PI/2); + } else { + cairo_move_to(m_cCr, x ,1-y); + cairo_show_text(m_cCr, sStr); + } +} + +void GraphicsOutputDeviceCairo::gRelease() { + AIM_ASSERT(m_iPlotHandle>0); + CloseFile(); + // Finished this one, up to the next! + m_iFileNumber++; +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceCairo.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceCairo.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,133 @@ +// Copyright 2007, Thomas Walters +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output to a graphics file using cairo + * + * \author Tom Walters and Willem van Engen + * \date created 2007/09/17 + * \version \$Header: $ + */ + +#ifndef __GRAPHICS_OUTPUT_DEVICE_CAIRO_H__ +#define __GRAPHICS_OUTPUT_DEVICE_CAIRO_H__ + +#include +#include + +#include "cairo.h" + +#include "Modules/Output/Graphics/Devices/GraphicsOutputDevice.h" + +/*! + * \class GraphicsOutputDeviceCairo "Output/GraphicsOutputDeviceCairo.h" + * \brief Output class for output to a graphics file using Cairo + * + * This class outputs a graphics operation to file. It only supports 2d though, + * so the z-component is ignored. + */ +class GraphicsOutputDeviceCairo : public GraphicsOutputDevice +{ + public: + GraphicsOutputDeviceCairo(AimParameters *pParam); + virtual ~GraphicsOutputDeviceCairo(); + + /*! \brief Initializes this output device, prepares plotting tools. + * \param sDir Directory or filename where to put images, max length is + * _MAX_PATH. Must end with slash!!! + * \return true on success, false on failure. + * + * sDir can be either a filename, in which case the output will be + * to that file, or a directory, in which case it will be filled + * with 6-digit numbered files. A new file is then created at every + * call to gGrab(). + * + * As usual, make sure to call this function before any other. If this + * Initialize() failed, you shouldn't try the other functions either. + */ + bool Initialize(const char *sDir); + bool Initialize(); + void gGrab(); + void gBeginLineStrip(); + void gBeginQuadStrip(); + using GraphicsOutputDevice::gVertex3f; // Because we overload it + void gVertex3f(float x, float y, float z); + void gColor3f(float r, float g, float b); + void gEnd(); + void gText3f(float x, float y, float z, const char *sStr, bool bRotated = false); + void gRelease(); + unsigned char* GetBuffer(); + int GetPixelFormat(); + protected: + /*! \brief Internal initialisation + * + */ + void Init(); + + /*! \brief Open the file with given index for output + * \param index File number to open + * \return true on success, false on error + * + * This opens a file for output and sets up the plotting library. + */ + bool OpenFile(unsigned int index); + + //! \brief Closes a plot output file, if any is open. + void CloseFile(); + + //! \brief Set to true if the input file can be written to + bool m_bOutputFile; + //! \brief The Cairo plotter + int m_iPlotHandle; + //! \brief Output directory + char m_sDir[PATH_MAX]; + //! \brief Current file number + unsigned int m_iFileNumber; + //! \brief true if this is the first vertex after gBegin() + bool m_bIsFirstVertex; + + enum VertexType { + VertexTypeNone, + VertexTypeLine, + VertexTypeQuad + }; + //! \brief The current vertex type + VertexType m_iVertexType; + //! \brief Begin vertex of current quad + float m_aPrevX[3], m_aPrevY[3]; + //! \brief Current number of quad vertices stored + unsigned int m_iPrevVertexCount; + + //! \brief Whether to invert the colors or not + bool m_bInvertColors; + + //! \brief Cairo Drawing Surface + cairo_surface_t *m_cSurface; + + //! \brief Cairo Context + cairo_t *m_cCr; + + //! \brief Internal store for the input filename + char m_sFilename[PATH_MAX]; + + unsigned int m_iWidth; + unsigned int m_iHeight; + bool m_bUseMemoryBuffer; +}; + +#endif /* __GRAPHICS_OUTPUT_DEVICE_CAIRO_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovie.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovie.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,256 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output to a movie + * + * \author Willem van Engen + * \date created 2006/10/16 + * \version \$Id: GraphicsOutputDeviceMovie.cpp 633 2008-09-11 04:20:16Z tom $ + */ + +/*! \todo + * A recent experiment showed that the video was about an audioframe + * (something like 30 ms) behind the audio in rdct.wav. It seems odd + * to me, since I already output a frame at the beginning to compensate + * for the missed buffer. + * A solution that would solve this and be a broader improvement, is to + * include the source's time when Fire()ing. The outputdevice can then + * do audio/video synchronization. In the case of the movie, the very + * first gGrab() looks at the time and emits as much empty output frames + * as needed until the correct signal time is reached. + */ +#include "Support/Common.h" + +#ifdef _WINDOWS +# include // for _mkdir&_rmdir +#else +# include +# include // for opendir&friends +#endif +#include +#include + + +#include "Output/GraphicsOutputDeviceMovie.h" + +GraphicsOutputDeviceMovie::GraphicsOutputDeviceMovie(Parameters *pParam) + : GraphicsOutputDeviceCairo(pParam) { // or GraphicsOutputDevicePlotutils + m_sMovieFile[0] = '\0'; + m_sSoundFile[0] = '\0'; +} + +bool GraphicsOutputDeviceMovie::Initialize(const char *sSoundFile, + const char *sMovieFile) { + FILE *f; + AIM_ASSERT(sSoundFile); + AIM_ASSERT(sMovieFile); + + // Check sound file exists + if ((f = fopen(sSoundFile, "r")) == NULL) { + AIM_ERROR(_T("Couldn't open sound file '%s' for movie creation."), + sSoundFile); + return false; + } + fclose(f); + strcpy(m_sSoundFile, sSoundFile); + + // Check movie output file can be made + if ( (f=fopen(sMovieFile, "w"))==NULL ) { + aimERROR(_T("Couldn't open movie file '%s' to write to."), + sMovieFile); + return false; + } + fclose(f); + strcpy(m_sMovieFile, sMovieFile); + + // Get a temporary image output directory + //! \warning Not really safe ... but windows has no mkdtemp() + //! \todo Make build system check for mkdtemp() to use it when available. See TODO.txt. + char *sTmpDir = NULL; +#ifdef _WINDOWS + if ((sTmpDir = _tempnam(NULL, AIM_NAME)) + && _mkdir(sTmpDir) >= 0) { + strcpy(m_sDir, sTmpDir); + strcat(m_sDir, "\\"); // Make sure to end with trailing slash + } else +#else + strcpy(m_sDir, "/tmp/"AIM_NAME"-movie.XXXXXX"); + if (mkdtemp(m_sDir)) { + strcat(m_sDir, "/"); // Make sure to end with trailing slash + } else +#endif + { + AIM_ERROR(_T("Couldn't create a temporary directory for movie output.")); + if (sTmpDir) free(sTmpDir); + return false; + } + if (sTmpDir) { + free(sTmpDir); + } + + // We want png for movie conversion + //! \bug This may change the user preference in GUI, hmm what to do? See TODO.txt + m_pParam->SetString("output.img.format", "png"); + //if ( !GraphicsOutputDevicePlotutils::Initialize(m_sDir) ) { + if ( !GraphicsOutputDeviceCairo::Initialize(m_sDir) ) { + return false; + } + + return true; +} + +void GraphicsOutputDeviceMovie::Start() { + //GraphicsOutputDevicePlotutils::Start(); + GraphicsOutputDeviceCairo::Start(); + // Just output a single frame to get audio/video in sync, put params in there + gGrab(); + PlotParameterScreen(); + gRelease(); +} + +void GraphicsOutputDeviceMovie::Stop() { + // Make sure Plotutils is really done writing. + //GraphicsOutputDevicePlotutils::Stop(); + GraphicsOutputDeviceCairo::Stop(); + CloseFile(); + +#ifdef __WX__ + // GUI only: popup dialog +#else + printf("Generating movie ... \n"); +#endif + AIM_ASSERT(m_pParam); + // Convert images and sound file to a movie + //! \warning Movie files are overwritten without warning + //! \bug ffmpeg only works with colour images, not with bw. So make sure to not use bw only in drawing.. + // Always convert to audio stream of 44.1kHz or problems may occur in playing or conversio. + float fFps = 1000.0 / m_pParam->GetFloat("output.frameperiod"); + char sffmpegPath[1024]; + if (!m_pParam->IsSet("output.ffmpeg_path")) { + strcpy(sffmpegPath,"ffmpeg"); + } else { + strcpy(sffmpegPath, m_pParam->GetString("output.ffmpeg_path")); + } + char sCodecOptions[1024]; + if (!m_pParam->IsSet("output.ffmpeg_codec_options")) { + strcpy(sCodecOptions,""); + } else { + strcpy(sCodecOptions, m_pParam->GetString("output.ffmpeg_codec_options")); + } + + char sCmdLine[1024]; //!\todo check that snprintf does not want a larger buffer + snprintf(sCmdLine, sizeof(sCmdLine)/sizeof(sCmdLine[0]), + "%s -r %.2f -y -i \"%s\" -i \"%s%%06d.png\" " + "-title \"%s\" -comment \"Generated by "AIM_NAME" "AIM_VERSION_STRING"\" " + "-sameq -r %.2f -ar 44100 -acodec pcm_s16le %s \"%s\"", + sffmpegPath, fFps, m_sSoundFile, m_sDir, + m_pParam->GetString("output.movie.title"), + fFps, sCodecOptions, m_sMovieFile); + printf(sCmdLine); + printf("\n"); + if (system(sCmdLine)) { + AIM_ERROR(_T("Couldn't create movie output.")); + } + +#ifdef __WX__ + // GUI only: close dialog again +#endif + // Remove files in temporary directory and the dir itself + //! \todo make portable function, possibly decided on by build system +#ifdef _WINDOWS + HANDLE hList; + WIN32_FIND_DATA FileData; + snprintf(sCmdLine, sizeof(sCmdLine)/sizeof(sCmdLine[0]), "%s/*.*", m_sDir); + if ((hList = FindFirstFile(sCmdLine, &FileData)) == INVALID_HANDLE_VALUE) { + AIM_ERROR(_T("Couldn't remove files from temporary directory.")); + return; + } + bool bRMfinished = false; + while (!bRMfinished) { + snprintf(sCmdLine, + sizeof(sCmdLine)/sizeof(sCmdLine[0]), + "%s%s", + m_sDir, + FileData.cFileName); + remove(sCmdLine); + if (!FindNextFile(hList, &FileData) && GetLastError() == ERROR_NO_MORE_FILES) { + bRMfinished = true; + } + } + FindClose(hList); + _rmdir(m_sDir); +#else + DIR *dir; + struct dirent *dirent; + if (!(dir = opendir(m_sDir))) { + AIM_ERROR(_T("Couldn't remove files in temporary directory.")); + return; + } + while (dirent = readdir(dir)) { + snprintf(sCmdLine, + sizeof(sCmdLine)/sizeof(sCmdLine[0]), + "%s%s", + m_sDir, + dirent->d_name); + unlink(sCmdLine); + } + closedir(dir); + rmdir(m_sDir); +#endif +} + +void GraphicsOutputDeviceMovie::PlotParameterScreen() { + AIM_ASSERT(m_pParam); + char sStr[50]; + int lineno = 1; + + float fMarL = m_pParam->GetFloat(_S("graph.margin.left")); + float fMarT = m_pParam->GetFloat(_S("graph.margin.top")); + float fTextHeight = 1.0f / 50.0f * 1.2; // change this when fontsizing is there! + + gText2f(fMarL, 1-(fMarT+fTextHeight*lineno++), + _S("AIM-C")); + gText2f(fMarL, + 1-(fMarT+fTextHeight*lineno++), + _S("(c) 2006-2010, Thomas Walters, Willem van Engen")); + gText2f(fMarL, + 1-(fMarT+fTextHeight*lineno++), + _S("http://aimc.acousticscale.org/")); + lineno++; + + static const char *pPlotParams[] = { + _S("input.buffersize"), + _S("input.samplerate"), + _S("bmm.freqstart"), + _S("bmm.freqend"), + _S("bmm.numchannels"), + _S("preset.name"), + _S("preset.title"), + NULL + }; + for (int i = 0; pPlotParams[i]; i++) { + snprintf(sStr, + sizeof(sStr)/sizeof(sStr[0]), _S("%s=%s"), + pPlotParams[i], + m_pParam->GetString(pPlotParams[i])); + gText2f(fMarL, + 1-(fMarT+fTextHeight*lineno++), + sStr); + } +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovie.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovie.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,75 @@ +// Copyright 2006-2010, Willem van Engen, Thomas Walters +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output to a movie + * + * \author Willem van Engen + * \date created 2006/10/16 + * \version \$Id: $ + */ + +#ifndef __GRAPHICS_OUTPUT_DEVICE_MOVIE_H__ +#define __GRAPHICS_OUTPUT_DEVICE_MOVIE_H__ + +#include "Support/util.h" +//#include "Modules/Output/Graphics/Devices/GraphicsOutputDevicePlotutils.h" +#include "Modules/Output/Graphics/Devices/GraphicsOutputDeviceCairo.h" + +/*! + * \class GraphicsOutputDeviceMovie "Output/GraphicsOutputDeviceMovie.h" + * \brief Output class for output to a movie + */ + // GraphicsOutputDevicePlotutils is also possible here +class GraphicsOutputDeviceMovie : public GraphicsOutputDeviceCairo { + public: + GraphicsOutputDeviceMovie(AimParameters *pParam); + virtual ~GraphicsOutputDeviceMovie() { }; + + /*! \brief Initializes this output device, prepares plotting tools. + * \param sSoundFile Sound file for the movie + * \param sMovieFile Movie filename to produce + * \return true on success, false on failure. + * + * As usual, make sure to call this function before any other. If this + * Initialize() failed, you shouldn't try the other functions either. + */ + bool Initialize(const char *sSoundFile, const char *sMovieFile); + + void Start(); + //! \brief This function now also generates the output movie. + void Stop(); + + protected: + /*! \brief Plots a summary of relevant parameters on the output + * + * This is intended for use in a movie as the first frame, which has no + * interesting data anyway, since at least one buffer of data is needed + * to be able to show someting. + * + * The caller must do the gGrab() and gRelease(). + */ + void PlotParameterScreen(); + + //! \brief Name of the sound file to be merged with the video + char m_sSoundFile[PATH_MAX]; + //! \brief Name of the movie file to produce + char m_sMovieFile[PATH_MAX]; +}; + +#endif /* __GRAPHICS_OUTPUT_DEVICE_MOVIE_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovieDirect.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovieDirect.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,396 @@ +// Copyright 2007, Thomas Walters +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output direct to a movie via local calls to libavcodec + * + * \author Tom Walters + * \date created 2007/10/05 + * \version \$Id: $ + */ + +#include "Support/Common.h" + +#include +#include +#include +#include + +#include "Support/util.h" +#include "Output/GraphicsOutputDeviceMovieDirect.h" + +GraphicsOutputDeviceMovieDirect::GraphicsOutputDeviceMovieDirect(Parameters *params) + : GraphicsOutputDeviceMovie(params) { + m_sMovieFile[0] = '\0'; + m_sSoundFile[0] = '\0'; +} + +bool GraphicsOutputDeviceMovieDirect::Initialize(const char *sSoundFile, + const char *sMovieFile) { + // We want pnm for direct movie conversion as the data format is nice and simple + //! \bug This may change the user preference in GUI, hmm what to do? See TODO.txt + //m_pParam->SetString("output.img.format", "pnm"); + + // Initialise GraphicsOutputDevicePlotutils for memory buffer use + if(!GraphicsOutputDeviceCairo::Initialize()) + return false; + + int width = m_pParam->GetUInt("output.img.width"); + int height = m_pParam->GetUInt("output.img.height"); + //float framerate = 1000.0f/m_pParam->GetFloat("output.frameperiod"); + float framerate=1000.0f/20.0f; + + m_pOutputMovie = new LibavformatWriter; + m_pOutputMovie->Init(sMovieFile, width, height, framerate); + + return true; +} + +void GraphicsOutputDeviceMovieDirect::Stop() { + // Make sure Plotutils is really done writing. + GraphicsOutputDeviceCairo::Stop(); + m_pOutputMovie->End(); + delete m_pOutputMovie; + +} + +void GraphicsOutputDeviceMovieDirect::gRelease() { + // write the buffer + unsigned char *buf = GraphicsOutputDeviceCairo::GetBuffer(); + + if (buf != NULL) + m_pOutputMovie->WriteFrame(buf); + + GraphicsOutputDeviceCairo::gRelease(); +} + + +// Everything below here is hacked from the Libavformat API example: +/* + * Libavformat API example: Output a media file in any supported + * libavformat format. The default codecs are used. + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +LibavformatWriter::LibavformatWriter() { + sws_flags = SWS_BICUBIC; + pixfmt= PIX_FMT_RGB24; + fmt = NULL; +} + +bool LibavformatWriter::Init(const char *sMovieFile, + int width, + int height, + float framerate) { + /* initialize libavcodec, and register all codecs and formats */ + av_register_all(); + + /* auto detect the output format from the name. default is mpeg. */ + fmt = guess_format(NULL, sMovieFile, NULL); + if (!fmt) { + printf("Could not deduce output format from file extension: using MPEG.\n"); + fmt = guess_format("mpeg", NULL, NULL); + } + if (!fmt) { + fprintf(stderr, "Could not find suitable output format\n"); + return false; + } + + /* allocate the output media context */ + oc = av_alloc_format_context(); + if (!oc) { + fprintf(stderr, "Memory error\n"); + return false; + } + oc->oformat = fmt; + snprintf(oc->filename, sizeof(oc->filename), "%s", sMovieFile); + + /* add the audio and video streams using the default format codecs + and initialize the codecs */ + video_st = NULL; + fmt->video_codec=CODEC_ID_PNG; + if (fmt->video_codec != CODEC_ID_NONE) { + video_st = add_video_stream(oc, fmt->video_codec, width, height, framerate); + } + + /* set the output parameters (must be done even if no + parameters). */ + if (av_set_parameters(oc, NULL) < 0) { + fprintf(stderr, "Invalid output format parameters\n"); + return false; + } + + dump_format(oc, 0, sMovieFile, 1); + + /* now that all the parameters are set, we can open the audio and + video codecs and allocate the necessary encode buffers */ + if (video_st) { + open_video(oc, video_st); + } + + /* open the output file, if needed */ + if (!(fmt->flags & AVFMT_NOFILE)) { + if (url_fopen(&oc->pb, sMovieFile, URL_WRONLY) < 0) { + fprintf(stderr, "Could not open '%s'\n", sMovieFile); + return false; + } + } + + /* write the stream header, if any */ + av_write_header(oc); + return true; +} + + +/**************************************************************/ +/* video output */ + +/* add a video output stream */ +AVStream* LibavformatWriter::add_video_stream(AVFormatContext *oc, + CodecID codec_id, + int width, + int height, + float framerate) { + AVCodecContext *c; + AVStream *st; + + st = av_new_stream(oc, 0); + if (!st) { + fprintf(stderr, "Could not alloc stream\n"); + return NULL; + } + + c = st->codec; + c->codec_id = codec_id; + c->codec_type = CODEC_TYPE_VIDEO; + + /* put sample parameters */ + /* resolution must be a multiple of two */ + c->width = width; + c->height = height; + /* time base: this is the fundamental unit of time (in seconds) in terms + of which frame timestamps are represented. for fixed-fps content, + timebase should be 1/framerate and timestamp increments should be + identically 1. */ + c->time_base.den = (int)framerate; + c->time_base.num = 1; + c->gop_size = 12; /* emit one intra frame every twelve frames at most */ + c->pix_fmt = pixfmt; + // some formats want stream headers to be separate + if(!strcmp(oc->oformat->name, "mp4") + || !strcmp(oc->oformat->name, "mov") + || !strcmp(oc->oformat->name, "3gp")) { + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + return st; +} + +AVFrame* LibavformatWriter::alloc_picture(int pix_fmt, + int width, + int height) { + AVFrame *picture; + uint8_t *picture_buf; + int size; + + picture = avcodec_alloc_frame(); + if (!picture) { + return NULL; + } + size = avpicture_get_size(pix_fmt, width, height); + picture_buf = (uint8_t*)av_malloc(size); + if (!picture_buf) { + av_free(picture); + return NULL; + } + avpicture_fill((AVPicture *)picture, picture_buf, + pix_fmt, width, height); + return picture; +} + +void LibavformatWriter::open_video(AVFormatContext *oc, AVStream *st) { + AVCodec *codec; + AVCodecContext *c; + c = st->codec; + + /* find the video encoder */ + codec = avcodec_find_encoder(c->codec_id); + if (!codec) { + fprintf(stderr, "codec not found\n"); + exit(1); + } + + /* open the codec */ + if (avcodec_open(c, codec) < 0) { + fprintf(stderr, "could not open codec\n"); + exit(1); + } + + video_outbuf = NULL; + if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) { + /* allocate output buffer */ + /* XXX: API change will be done */ + /* buffers passed into lav* can be allocated any way you prefer, + as long as they're aligned enough for the architecture, and + they're freed appropriately (such as using av_free for buffers + allocated with av_malloc) */ + video_outbuf_size = 200000; + video_outbuf = (uint8_t*)av_malloc(video_outbuf_size); + } + + /* allocate the encoded raw picture */ + picture = alloc_picture(c->pix_fmt, c->width, c->height); + if (!picture) { + fprintf(stderr, "Could not allocate picture\n"); + exit(1); + } + + /* if the output format is not RGB32, then a temporary RGB32 + picture is needed too. It is then converted to the required + output format */ + tmp_picture = NULL; + if (c->pix_fmt != PIX_FMT_RGB32) { + tmp_picture = alloc_picture(PIX_FMT_RGB32, c->width, c->height); + if (!tmp_picture) { + fprintf(stderr, "Could not allocate temporary picture\n"); + exit(1); + } + } +} + + +void LibavformatWriter::close_video(AVFormatContext *oc, + AVStream *st) { + avcodec_close(st->codec); + av_free(picture->data[0]); + av_free(picture); + if (tmp_picture) { + av_free(tmp_picture->data[0]); + av_free(tmp_picture); + } + av_free(video_outbuf); +} + +void LibavformatWriter::WriteFrame(unsigned char *pFrameBuffer) { + /* compute current video time */ + if (video_st) + video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den; + else + video_pts = 0.0; + + int out_size, ret; + AVCodecContext *c; + static struct SwsContext *img_convert_ctx; + c = video_st->codec; + if (c->pix_fmt != PIX_FMT_RGB32) { + /* as we only generate a RGB32 picture, we must convert it + to the codec pixel format if needed */ + if (img_convert_ctx == NULL) { + img_convert_ctx = sws_getContext(c->width, + c->height, + PIX_FMT_RGB32, + c->width, + c->height, + c->pix_fmt, + sws_flags, + NULL, + NULL, + NULL); + if (img_convert_ctx == NULL) { + fprintf(stderr, "Cannot initialize the conversion context\n"); + exit(1); + } + } + fill_image(tmp_picture, pFrameBuffer, c->width, c->height); + sws_scale(img_convert_ctx, tmp_picture->data, tmp_picture->linesize, + 0, c->height, picture->data, picture->linesize); + } else { + fill_image(picture, pFrameBuffer, c->width, c->height); + } + /* encode the image */ + out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture); + /* if zero size, it means the image was buffered */ + if (out_size > 0) { + AVPacket pkt; + av_init_packet(&pkt); + + pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, video_st->time_base); + if(c->coded_frame->key_frame) { + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= video_st->index; + pkt.data= video_outbuf; + pkt.size= out_size; + /* write the compressed frame in the media file */ + ret = av_write_frame(oc, &pkt); + } + } else { + ret = 0; + } + if (ret != 0) { + fprintf(stderr, "Error while writing video frame\n"); + exit(1); + } +} + +void LibavformatWriter::End() { + /* close each codec */ + if (video_st) { + close_video(oc, video_st); + } + /* write the trailer, if any */ + av_write_trailer(oc); + + /* free the streams */ + for(i = 0; i < oc->nb_streams; i++) { + av_freep(&oc->streams[i]->codec); + av_freep(&oc->streams[i]); + } + + if (!(fmt->flags & AVFMT_NOFILE)) { + /* close the output file */ + url_fclose(&oc->pb); + } + /* free the stream */ + av_free(oc); +} + +void LibavformatWriter::fill_image(AVFrame *pict, + unsigned char *pFrameBuffer, + int width, + int height) { + memcpy((void*)&(pict->data[0][0]), (void*)pFrameBuffer, width*height*4); +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovieDirect.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovieDirect.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,89 @@ +// Copyright 2006, Thomas Walters +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output direct to a movie via local calls to libavcodec + * + * \author Tom Walters + * \date created 2007/10/8 + * \version \$Id$ + */ + +#ifndef __GRAPHICS_OUTPUT_DEVICE_MOVIE_DIRECT_H__ +#define __GRAPHICS_OUTPUT_DEVICE_MOVIE_DIRECT_H__ + +#include "Modules/Output/Graphics/Devices/GraphicsOutputDeviceMovie.h" + +extern "C" { +#include +#include +} + +/*! + * \class LibavformatWriter "Output/GraphicsOutputDeviceMovieDirect.h" + * \brief Helper class to use libavcodec to write a movie file + */ +class LibavformatWriter { + public: + LibavformatWriter(); + ~LibavformatWriter() { }; + bool Init(const char *sMovieFile, int width, int height, float framerate); + void WriteFrame(unsigned char *pFrameBuffer); + void End(); + private: + AVFrame *picture, *tmp_picture; + uint8_t *video_outbuf; + int frame_count, video_outbuf_size; + int sws_flags; + PixelFormat pixfmt; + AVOutputFormat *fmt; + AVFormatContext *oc; + AVStream *video_st; + double video_pts; + int i; + AVStream* add_video_stream(AVFormatContext *oc, CodecID codec_id, int width, int height, float framerate); + AVFrame* alloc_picture(int pix_fmt, int width, int height); + void open_video(AVFormatContext *oc, AVStream *st); + void close_video(AVFormatContext *oc, AVStream *st); + void fill_image(AVFrame *pict,unsigned char *pFrameBuffer , int width, int height); +}; + +/*! + * \class GraphicsOutputDeviceMovie "Output/GraphicsOutputDeviceMovie.h" + * \brief Output class for output to a movie + */ +class GraphicsOutputDeviceMovieDirect : public GraphicsOutputDeviceMovie { + public: + GraphicsOutputDeviceMovieDirect(AimParameters *pParam); + virtual ~GraphicsOutputDeviceMovieDirect() { }; + /*! \brief Initializes this output device, prepares plotting tools. + * \param sSoundFile Sound file for the movie + * \param sMovieFile Movie filename to produce + * \return true on success, false on failure. + * + * As usual, make sure to call this function before any other. If this + * Initialize() failed, you shouldn't try the other functions either. + */ + bool Initialize(const char *sSoundFile, const char *sMovieFile); + void Stop(); + void gRelease(); + private: + LibavformatWriter* m_pOutputMovie; +}; + +#endif /* __GRAPHICS_OUTPUT_DEVICE_MOVIE_DIRECT_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevicePlotutils.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevicePlotutils.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,309 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output to a graphics file using plotutils + * + * \author Willem van Engen + * \date created 2006/10/13 + * \version \$Id: GraphicsOutputDevicePlotutils.cpp 493 2007-11-27 10:59:20Z tom $ + */ +#include "Support/Common.h" + +#include +#include +#include +#include +#include + +#include "Support/util.h" +#include "Modules/Output/Graphics/Devices/GraphicsOutputDevicePlotutils.h" + +GraphicsOutputDevicePlotutils::GraphicsOutputDevicePlotutils(Parameters *pParam) + : GraphicsOutputDevice(pParam) { + m_iPlotHandle = 0; + m_pOutputFile = NULL; + m_iFileNumber = 0; + m_iVertexType = VertexTypeNone; + m_bUseMemoryBuffer = false; + m_pMemoryBuffer = NULL; + m_uWidth=0; + m_uHeight=0; +} + +bool GraphicsOutputDevicePlotutils::Initialize(const char *sDir) { + Init(); + + //! \todo Output to file if sDir is a file, to directory with multiple + //! images if it's a directory. + strncpy(m_sDir, sDir, sizeof(m_sDir)/sizeof(m_sDir[0])); + + /* Try to open an image to see if everything is allright. We want to avoid + * errors in the main Process()ing loop. */ + if (!OpenFile(0)) { + //! \todo Better error message that is more specific about the cause. + AIM_ERROR(_T("Could not open output directory '%s' using graphics format '%s'."), + m_sDir, m_pParam->GetString("output.img.format") ); + return false; + } + CloseFile(); + return true; +} + +void GraphicsOutputDevicePlotutils::Init() { + AIM_ASSERT(m_pParam); + /* + * Set parameters + */ + pl_parampl("BG_COLOR", (void*)m_pParam->GetString("output.img.color.background")); + // Handle GIFs as other output formats, don't merge frames into single GIF. + pl_parampl("GIF_ANIMATION", (void*)"no"); + pl_parampl("INTERLACE", (void*)"no"); + + m_bInvertColors = m_pParam->GetBool("output.img.color.invert"); + + // Output size! + m_uWidth = m_pParam->GetUInt("output.img.width"); + m_uHeight = m_pParam->GetUInt("output.img.height"); + char strSize[100]; + snprintf(strSize, sizeof(strSize)/sizeof(strSize[0]), "%ux%u", m_uWidth, m_uHeight); + pl_parampl("BITMAPSIZE", (void*)strSize); +} + +bool GraphicsOutputDevicePlotutils::Initialize() { + Init(); + m_bUseMemoryBuffer = true; + return(true); +} + +bool GraphicsOutputDevicePlotutils::OpenFile(unsigned int index) { + const char *strPlottype = m_pParam->GetString("output.img.format"); + if (!m_bUseMemoryBuffer) { + char sFilename[PATH_MAX]; + struct stat fileinfo; + + // Get filename without trailing slash + strncpy(sFilename, m_sDir, sizeof(sFilename)/sizeof(sFilename[0])); +#ifdef _WINDOWS + if (sFilename[strlen(sFilename)-1]=='\\') { + sFilename[strlen(sFilename)-1]='\0'; + } +#else + if (sFilename[strlen(sFilename)-1]=='/') { + sFilename[strlen(sFilename)-1]='\0'; + } +#endif + // Enumerate files it m_sDir is a directory. + if ( stat(sFilename, &fileinfo)==0 && (fileinfo.st_mode & S_IFDIR) ) { + // We have a directory: enumerate with index + snprintf(sFilename, sizeof(sFilename)/sizeof(sFilename[0]),"%s%06d.%s", m_sDir, index, strPlottype); + // If type is 'auto', fallback to 'png' + if (strcmp(strPlottype, "auto")==0) + strPlottype = "png"; + } else { + // We have a (probabely non-existant) file. Auto-detect type by extension if requested + strncpy(sFilename, m_sDir, sizeof(sFilename)/sizeof(sFilename[0])); + char *pDot = strrchr(sFilename, '.'); + if (!pDot) { + AIM_ERROR(_T("Please supply extension on filename when using 'auto' format: '%s'"), sFilename); + return false; + } + strPlottype = &pDot[1]; + } + if ((m_pOutputFile = fopen(sFilename, "wb")) == NULL) { + return false; + } + } else { +#if !defined(_WINDOWS) && !defined(_MACOSX) + if ((m_pOutputFile=open_memstream(&m_pMemoryBuffer, &m_sMemoryBufferSize))==NULL) +#endif + return false; + strPlottype="pnm"; // Force pnm format or this doesn't work + } + + /* + * Create a plotter + * + * Plotutils knows the following types for file output: + * pnm gif ai ps gif pcl hpgl tek meta + */ + if ((m_iPlotHandle = pl_newpl(strPlottype, + NULL, + m_pOutputFile, + stderr)) < 0 ) { + return false; + } + pl_selectpl(m_iPlotHandle); + + if ( pl_openpl() < 0) { + return false; + } + + // Now setup things for this plotter + pl_fontname(m_pParam->GetString("output.img.fontname")); + //! \todo Make fontsize work in Plotutils, currently disabled + //pl_ffontsize(m_pParam->GetUInt("output.fontsize")); + + return true; +} + +char* GraphicsOutputDevicePlotutils::GetBuffer() { + if(m_bUseMemoryBuffer && (m_pMemoryBuffer!=NULL)) + return (&m_pMemoryBuffer[m_sMemoryBufferSize-(m_uWidth*m_uHeight*3)]); + else + return NULL; +} + +void GraphicsOutputDevicePlotutils::CloseFile() { + // Plotting library + if (m_iPlotHandle>0) { + pl_closepl(); + + pl_selectpl(0); + pl_deletepl(m_iPlotHandle); + m_iPlotHandle = 0; + } + + // And the output file + if (m_pOutputFile) { + fclose(m_pOutputFile); + m_pOutputFile = NULL; + } +} + +GraphicsOutputDevicePlotutils::~GraphicsOutputDevicePlotutils() { + // Output file should be closed by gRelease() + AIM_ASSERT(!m_pOutputFile); + AIM_ASSERT(!m_iPlotHandle); + CloseFile(); +} + +PixelFormat GraphicsOutputDevicePlotutils::GetPixelFormat() { + return AIM_PIX_FMT_RGB24_24; +} + +void GraphicsOutputDevicePlotutils::gGrab() { + // Open file + if (!OpenFile(m_iFileNumber)) { + return; + } + // Setup plotting area + pl_fspace(0.0, 0.0, 1.0, 1.0); + pl_flinewidth(0.0001); + pl_pencolorname("darkblue"); + pl_erase(); +} + +void GraphicsOutputDevicePlotutils::gBeginLineStrip() { + m_bIsFirstVertex = true; + m_iVertexType = VertexTypeLine; + pl_filltype(0); +} + +void GraphicsOutputDevicePlotutils::gBeginQuadStrip() { + m_bIsFirstVertex = true; + m_iVertexType = VertexTypeQuad; + m_iPrevVertexCount = 0; + pl_filltype(1); +} + +void GraphicsOutputDevicePlotutils::gColor3f(float r, float g, float b) { + if (m_bInvertColors) { + r = 1-r; + g = 1-g; + b = 1-b; + } + int ir = (int)(r*0xffff); + int ig = (int)(g*0xffff); + int ib = (int)(b*0xffff); + ir = MIN(0xffff, MAX(0,ir)); + ig = MIN(0xffff, MAX(0,ig)); + ib = MIN(0xffff, MAX(0,ib)); + pl_color(ir, ig, ib); +} + +void GraphicsOutputDevicePlotutils::gVertex3f(float x, float y, float z) { + switch(m_iVertexType) { + case VertexTypeLine: + if (m_bIsFirstVertex) { + m_bIsFirstVertex = false; + pl_fmove(x, y); + } else { + pl_fcont(x, y); + } + break; + case VertexTypeQuad: + /* Store vertices until we got four in a row. + * The order of vertices when processing quads is: + * 1-----3-----5 + * | | | + * 0-----2-----4 + */ + if (m_iPrevVertexCount>=3) { + // Plot this quad + pl_fmove(m_aPrevX[0],m_aPrevY[0]); + pl_fcont(m_aPrevX[1],m_aPrevY[1]); + pl_fcont(x,y); + pl_fcont(m_aPrevX[2],m_aPrevY[2]); + pl_endpath(); + // Last vertices of this quad are the first of the next + m_aPrevX[0] = m_aPrevX[2]; + m_aPrevY[0] = m_aPrevY[2]; + m_aPrevX[1] = x; + m_aPrevY[1] = y; + m_iPrevVertexCount = 2; + } else { + // Not at the fourth, keep storing + m_aPrevX[m_iPrevVertexCount] = x; + m_aPrevY[m_iPrevVertexCount] = y; + m_iPrevVertexCount++; + } + break; + default: + // Should not happen + AIM_ASSERT(0); + } +} + +void GraphicsOutputDevicePlotutils::gEnd() { + pl_endpath(); + m_iVertexType = VertexTypeNone; +} + +void GraphicsOutputDevicePlotutils::gText3f(float x, + float y, + float z, + const char *sStr, + bool bRotated) { + if (bRotated) { + pl_textangle(90); + pl_fmove(x, y); + pl_alabel('l', 't', sStr); + } else { + pl_textangle(0); + pl_fmove(x, y); + pl_alabel('l', 'b', sStr); + } +} + +void GraphicsOutputDevicePlotutils::gRelease() { + AIM_ASSERT(m_pOutputFile); + AIM_ASSERT(m_iPlotHandle>0); + CloseFile(); + m_iFileNumber++; +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevicePlotutils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevicePlotutils.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,133 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output to a graphics file using plotutils + * + * \author Willem van Engen + * \date created 2006/10/13 + * \version \$Id: $ + */ + +#ifndef __GRAPHICS_OUTPUT_DEVICE_PLOTUTILS_H__ +#define __GRAPHICS_OUTPUT_DEVICE_PLOTUTILS_H__ + +#include +#include + +#include + +#include "Support/util.h" +#include "Modules/Output/Graphics/Devices/GraphicsOutputDevice.h" + +/*! + * \class GraphicsOutputDevicePlotutils "Output/GraphicsOutputDevicePlotutils.h" + * \brief Output class for output to a graphics file using plotutils + * + * This class outputs a graphics operation to file. It only supports 2d though, + * so the z-component is ignored. + */ +class GraphicsOutputDevicePlotutils : public GraphicsOutputDevice { + public: + GraphicsOutputDevicePlotutils(Parameters *pParam); + virtual ~GraphicsOutputDevicePlotutils(); + + /*! \brief Initializes this output device, prepares plotting tools. + * \param sDir Directory or filename where to put images, max length + * is _MAX_PATH. Must end with slash!!! + * \return true on success, false on failure. + * + * sDir can be either a filename, in which case the output will be + * to that file, or a directory, in which case it will be filled + * with 6-digit numbered files. A new file is then created at every + * call to gGrab(). + * + * As usual, make sure to call this function before any other. If this + * Initialize() failed, you shouldn't try the other functions either. + */ + bool Initialize(const char *sDir); + bool Initialize(); + + void gGrab(); + void gBeginLineStrip(); + void gBeginQuadStrip(); + using GraphicsOutputDevice::gVertex3f; // Because we overload it + void gVertex3f(float x, float y, float z); + void gColor3f(float r, float g, float b); + void gEnd(); + void gText3f(float x, float y, float z, const char *sStr, bool bRotated = false); + void gRelease(); + char* GetBuffer(); + protected: + /*! \brief Open the file with given index for output + * \param index File number to open + * \return true on success, false on error + * + * This opens a file for output and sets up the plotting library. + */ + bool OpenFile(unsigned int index); + + //! \brief Closes a plot output file, if any is open. + void CloseFile(); + + //! \brief Internal initialisation function called by overloaded variants of Initialize() + void Init(); + + //! \brief The current output file's handle + FILE *m_pOutputFile; + //! \brief The plotutils plotter + int m_iPlotHandle; + //! \brief Output directory + char m_sDir[PATH_MAX]; + //! \brief Current file number + unsigned int m_iFileNumber; + //! \brief true if this is the first vertex after gBegin() + bool m_bIsFirstVertex; + + enum VertexType { + VertexTypeNone, + VertexTypeLine, + VertexTypeQuad + }; + //! \brief The current vertex type + VertexType m_iVertexType; + //! \brief Begin vertex of current quad + float m_aPrevX[3], m_aPrevY[3]; + //! \brief Current number of quad vertices stored + unsigned int m_iPrevVertexCount; + + //! \brief Whether to invert the colors or not + bool m_bInvertColors; + + //! \brief The size of the memory bitmap buffer as provided by open_memstream + size_t m_sMemoryBufferSize; + + //! \brief Are we using a memory buffer for writing to (as opposed to a file)? + bool m_bUseMemoryBuffer; + + //! \brief Pointer to the memory buffer in question + char *m_pMemoryBuffer; + + //! \brief Width of the bitmap + unsigned int m_uWidth; + + //! \brief Height of the bitmap + unsigned int m_uHeight; +}; + +#endif /* __GRAPHICS_OUTPUT_DEVICE_PLOTUTILS_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevicewxGLCanvas.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevicewxGLCanvas.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,506 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output to a wxWidgets OpenGL canvas + * + * \author Willem van Engen + * \date created 2006/09/21 + * \version \$Id: $ + */ + +#include "Support/Common.h" + +/*! \class GraphicsOutputDevicewxGLCanvas + * + * Graphics output takes a large part of the application's performance at the + * moment when it is inline with the Process() loop. Much is gained by + * putting it in a separate thread, which can be done using ShotTargetThreaded. + * + * OpenGL-related documents: + * - http://www.opengl.org/ + * - http://www.sgi.com/products/software/opengl/ + * - http://developer.apple.com/graphicsimaging/opengl/ + * - http://developer.nvidia.com/page/documentation.html + * - Vertex arrays + * - http://www.opengl.org/registry/specs/EXT/vertex_array.txt + * - http://www.awprofessional.com/articles/article.asp?p=461848&seqNum=2&rl=1 + * - http://jdobry.webpark.cz/opengl/opengl_maximum_performance.html + * - Fonts and OpenGL + * - http://gltt.sourceforge.net/ + */ + +// And finally our own +#include "Support/util.h" +#include "Output/GraphicsOutputDevice.h" +#include "Output/GraphicsOutputDevicewxGLCanvas.h" + +BEGIN_EVENT_TABLE(GraphicsOutputDevicewxGLCanvas, wxGLCanvas) + EVT_SIZE(GraphicsOutputDevicewxGLCanvas::OnSize) + EVT_PAINT(GraphicsOutputDevicewxGLCanvas::OnPaint) + EVT_ERASE_BACKGROUND(GraphicsOutputDevicewxGLCanvas::OnEraseBackground) +END_EVENT_TABLE() + +// wxGLCanvas attributes +int GraphicsOutputDevicewxGLCanvas::GLAttrlist[] = { + WX_GL_RGBA, 1, + WX_GL_DOUBLEBUFFER, 1, + WX_GL_MIN_RED, 5, + WX_GL_MIN_GREEN, 5, + WX_GL_MIN_BLUE, 5, + WX_GL_MIN_ALPHA, 3, + WX_GL_DEPTH_SIZE, 16, + 0 +}; + +// OpenGL get procaddress function pointer, differs across platforms +typedef void (*(*glGetProcAddressPtr_t)(const char*))(); + +GraphicsOutputDevicewxGLCanvas::GraphicsOutputDevicewxGLCanvas(Parameters *pParam, + wxWindow *parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) + : wxGLCanvas(parent, (wxGLCanvas*) NULL, id, pos, size, + style|wxFULL_REPAINT_ON_RESIZE, name, GLAttrlist), + GraphicsOutputDevice(pParam) { + m_init = false; + m_gllist = 0; + m_pWorkerContext = NULL; + m_bAntialiasing = true; + m_pFont = NULL; + m_sFontFile = NULL; + m_iFontsize = -1; +#if !defined(_MACOSX) + s_bWorkerNeedsInit = false; +#endif + +#ifdef WITH_GL_VERTEX_ARRAYS + m_iVertexType = 0xffff; // no gBegin() has happened yet + m_bStaticColor = false; + m_pVertices = NULL; + m_iVerticesMax = 0; + // Enable vertex arrays if possible +#ifdef _MACOSX + m_glLockArraysEXT = ::glLockArraysEXT; + m_glUnlockArraysEXT = ::glUnlockArraysEXT; + m_bVertexArrayLock = true; +#else + m_bVertexArrayLock = false; + // OpenGL command needed to fetch entry point, do it in InitGL() +#endif /* _MACOSX */ +#endif /* WITH_GL_VERTEX_ARRAYS */ +} + +GraphicsOutputDevicewxGLCanvas::~GraphicsOutputDevicewxGLCanvas() { + // Cleanup OpenGL display list + if (m_init) { + glDeleteLists(m_gllist, 1); + } + DELETE_IF_NONNULL(m_pWorkerContext); + DELETE_IF_NONNULL(m_pFont); +#ifdef WITH_GL_VERTEX_ARRAYS + DELETE_ARRAY_IF_NONNULL(m_pVertices); +#endif +} + +void GraphicsOutputDevicewxGLCanvas::Start() { + // This seems to be needed to prevent a crash on windows, but why???? + SetCurrent(); + return GraphicsOutputDevice::Start(); +} + +bool GraphicsOutputDevicewxGLCanvas::Initialize(unsigned int iVerticesMax) { + AIM_ASSERT(m_pParam); + // Give a chance to update anti-aliasing settings + if (m_bAntialiasing != m_pParam->GetBool("output.antialias")) { + m_bAntialiasing = m_pParam->GetBool("output.antialias"); + if (SetCurrent()) { + InitGL(); +#if !defined(_MACOSX) + { + wxMutexLocker lock(s_mutexOpenGL); + s_bWorkerNeedsInit = true; + } +#endif + } + } + +#ifdef WITH_GL_VERTEX_ARRAYS + // Re-allocate vertices + if (iVerticesMax > m_iVerticesMax) { + DELETE_IF_NONNULL(m_pVertices); + m_iVerticesMax = iVerticesMax; + // If color is static, we need not store the color + if (m_bStaticColor) + m_pVertices = new GLfloat[(iVerticesMax+1)*3]; + else + m_pVertices = new GLfloat[(iVerticesMax+1)*6]; + } +#endif + + // Change font if requested + const char *sFontFile = m_pParam->GetString("output.gl.fontfile"); + unsigned int iFontsize = m_pParam->GetUInt("output.fontsize"); + if (!m_sFontFile + || !strcmp(m_sFontFile,sFontFile)==0 + || m_iFontsize!=(int)iFontsize) { + wxMutexLocker lock(s_mutexOpenGL); + DELETE_IF_NONNULL(m_pFont); + wxString sWorkingFontFilename = wxString::FromAscii(sFontFile); + if (!wxFileExists(sWorkingFontFilename)) { + sWorkingFontFilename = wxString::FromAscii(aimDataDir()); + sWorkingFontFilename += _T("/"); + sWorkingFontFilename += wxString::FromAscii(sFontFile); + } + //if (!wxFileExists(sWorkingFontFilename)) + //sWorkingFontFilename.replace("Font:").append(sFontFile); + m_pFont = static_cast(new FTGLBitmapFont(sWorkingFontFilename.fn_str())); + if (!m_pFont || m_pFont->Error()) { + aimERROR(_T("Couldn't load font '%s'"), sFontFile); + DELETE_IF_NONNULL(m_pFont); + } else { + // Display lists don't mix with our own usage :( + // May not be needed for a Bitmap font + //m_pFont->UseDisplayList(false); + if ( !m_pFont->FaceSize(iFontsize) ) { + AIM_ERROR(_T("Couldn't select font size %u on font '%s'"), iFontsize, sFontFile); + DELETE_IF_NONNULL(m_pFont); + } + } + m_sFontFile = sFontFile; + m_iFontsize = iFontsize; + } + return true; +} +bool GraphicsOutputDevicewxGLCanvas::Initialize() { + return Initialize(0); +} + +void GraphicsOutputDevicewxGLCanvas::Render() { + wxPaintDC dc(this); + // We want to initialize first from main thread. + if (!m_init) { + if (!SetCurrent()) return; + InitGL(); + } + // Render saved list only if not animating (redrawn anyway in that case) + if (!m_bRunning) { + if (!SetCurrent()) { + return; + } + glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/); + glCallList(m_gllist); + SwapBuffers(); + } +} + +void GraphicsOutputDevicewxGLCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { + Render(); +} + +void GraphicsOutputDevicewxGLCanvas::OnSize(wxSizeEvent& event) { + // this is also necessary to update the context on some platforms + wxGLCanvas::OnSize(event); + + // set GL viewport + // (not called by wxGLCanvas::OnSize on all platforms...) + if (SetCurrent()) { + DoResize(); + // It is only sensible to update the other thread when it's running + // Don't acquire the mutex when s_bWorkerNeedsInit already to avoid deadlock + if (/*m_bRunning &&*/ !s_bWorkerNeedsInit) { + wxMutexLocker lock(s_mutexOpenGL); + s_bWorkerNeedsInit = true; + } + } +} + +void GraphicsOutputDevicewxGLCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event)) { +} + +bool GraphicsOutputDevicewxGLCanvas::SetCurrent() { + bool bRet=true; + +#ifndef __WXMOTIF__ + bRet = (GetContext()!=NULL); + if (bRet) +#endif + { + wxGLCanvas::SetCurrent(); + } + return bRet; +} + +void GraphicsOutputDevicewxGLCanvas::DoResize() { + int w, h; + GetClientSize(&w, &h); + glViewport(0, 0, (GLint)w, (GLint)h); +} + +void GraphicsOutputDevicewxGLCanvas::InitGL() { + /* No SetCurrent() here, because this can be called from different GL contexts. + * Convenient for multi-threaded operation. */ + //aimERROR(_T("InitGL Called")); +#if defined(WITH_GL_VERTEX_ARRAYS) && !defined(_MACOSX) + if (!m_init) { + /* This needs to be done here, because OpenGL commands may need SetCurrent() + * and an already shown window. */ + char *extensions = (char *)glGetString(GL_EXTENSIONS); + if (!extensions) { + AIM_INFO(_T("Could not query OpenGL extensions, vertex arrays disabled")); + } else if (strstr(extensions, "GL_EXT_compiled_vertex_array")) { + m_glLockArraysEXT = (LOCAL_PFNGLLOCKARRAYSEXTPROC)GL_GET_PROC_ADDRESS("glLockArraysEXT"); + m_glUnlockArraysEXT = (LOCAL_PFNGLUNLOCKARRAYSEXTPROC)GL_GET_PROC_ADDRESS("glUnlockArraysEXT"); + if(!m_glLockArraysEXT || !m_glUnlockArraysEXT) + AIM_ERROR(_T("OpenGL error on GL_EXT_compiled_vertex_array")); + else + m_bVertexArrayLock = true; + } + } +#endif + DoResize(); + glClearColor(0, 0, 0, 1); + glMatrixMode( GL_PROJECTION ); + glLoadIdentity( ); + + glEnable(GL_VERTEX_ARRAY); + + // Window limits in OpenGL co-ordiantes + //! \todo Make this configurable, or change and document fixed values + glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0); + glTranslatef(0.0, 0.0, 0.0); + + if (m_bAntialiasing) { + glEnable(GL_LINE_SMOOTH); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //glBlendFunc(GL_ONE, GL_ONE); + glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE); + } else { + glDisable(GL_LINE_SMOOTH); + glDisable(GL_BLEND); + } + glLineWidth(1.0); + + // Get a free display list only the first time + if (!m_init) { +#if !defined(_MACOSX) + // Windows and Linux need a separate worker context + aimASSERT(wxIsMainThread()); +#if wxCHECK_VERSION(2,8,0) + m_pWorkerContext = new wxGLContext(this, m_glContext); +#else + m_pWorkerContext = new wxGLContext(true, + this, + wxNullPalette, + m_glContext); +#endif + aimASSERT(m_pWorkerContext); + s_bWorkerNeedsInit = true; +#endif + m_gllist = glGenLists(1); + aimASSERT(m_gllist); + // Empty window at start + glNewList(m_gllist, GL_COMPILE_AND_EXECUTE); + glEndList(); + m_init = true; + } +} + +// Call before any other render* functions +void GraphicsOutputDevicewxGLCanvas::gGrab() { + AimwxGuiLocker __lock__; +#if !defined(_MACOSX) + // Detect if we're the main thread or not. + if (!wxIsMainThread()) { + // We're called by a worker thread, make sure there's a right context + AIM_ASSERT(m_pWorkerContext); +#if wxCHECK_VERSION(2,8,0) + m_pWorkerContext->SetCurrent(*this); +#else + m_pWorkerContext->SetCurrent(); +#endif + // Update OpenGL settings if needed + wxMutexLocker lock(s_mutexOpenGL); + if (s_bWorkerNeedsInit) { + InitGL(); + s_bWorkerNeedsInit = false; + } + } else +#endif + { + // Either called by main thread, or we need no special worker glContext + if (!SetCurrent()) { + return; + } + // Init OpenGL once, but after SetCurrent + if (!m_init) { + InitGL(); + } + } + glClear(GL_COLOR_BUFFER_BIT); + + // Start and store in a display list for redrawing + glNewList(m_gllist, GL_COMPILE); +} + +void GraphicsOutputDevicewxGLCanvas::gBeginLineStrip() { +#ifdef WITH_GL_VERTEX_ARRAYS + aimASSERT(m_iVertexType == 0xffff); // Previous gBegin*() must be gEnd()ed + // New lines vertex array + m_iVertexCount = 0; + m_iVertexType = GL_LINE_STRIP; +#else + AimwxGuiLocker __lock__; + glBegin(GL_LINE_STRIP); +#endif +} + +void GraphicsOutputDevicewxGLCanvas::gBeginQuadStrip() { +#ifdef WITH_GL_VERTEX_ARRAYS + aimASSERT(m_iVertexType == 0xffff); // Previous gBegin*() must be gEnd()ed + // New quads vertex array + m_iVertexCount = 0; + m_iVertexType = GL_QUAD_STRIP; +#else + AimwxGuiLocker __lock__; + glBegin(GL_QUAD_STRIP); +#endif +} + +void GraphicsOutputDevicewxGLCanvas::gVertex3f(float x, float y, float z) { +#ifdef WITH_GL_VERTEX_ARRAYS + aimASSERT(m_iVertexType != 0xffff); // Must be inside gBegin*() + if (m_iVertexCount>=m_iVerticesMax) { + static bool errShown=false; + if (!errShown) { + aimERROR(_T("Error: max vertex count reached: %d"), m_iVertexCount); + errShown=true; + } + return; + } + if (m_bStaticColor) { + m_pVertices[m_iVertexCount*3+0] = x; + m_pVertices[m_iVertexCount*3+1] = y; + m_pVertices[m_iVertexCount*3+2] = z; + } else { + m_pVertices[m_iVertexCount*6+0] = m_fCurColorR; + m_pVertices[m_iVertexCount*6+1] = m_fCurColorG; + m_pVertices[m_iVertexCount*6+2] = m_fCurColorB; + m_pVertices[m_iVertexCount*6+3] = x; + m_pVertices[m_iVertexCount*6+4] = y; + m_pVertices[m_iVertexCount*6+5] = z; + } + m_iVertexCount++; +#else + AimwxGuiLocker __lock__; + glVertex3f(x,y,z); +#endif +} + +void GraphicsOutputDevicewxGLCanvas::gColor3f(float r, float g, float b) { +#ifdef WITH_GL_VERTEX_ARRAYS + if (m_iVertexType==0xffff || m_bStaticColor) { + // If not inside vertex array run, use the ordinary command + glColor3f(r, g, b); + } + if (!m_bStaticColor) { + // Set current color for vertex array usage + m_fCurColorR = r; + m_fCurColorG = g; + m_fCurColorB = b; + } +#else + AimwxGuiLocker __lock__; + glColor3f(r, g, b); +#endif +} + +void GraphicsOutputDevicewxGLCanvas::gEnd() { +#ifdef WITH_GL_VERTEX_ARRAYS + aimASSERT(m_iVertexType != 0xffff); // Must be inside gBegin*() + AimwxGuiLocker __lock__; + + // Draw the vertex array + glEnableClientState(GL_VERTEX_ARRAY); + + // Draw vertices + if (m_bStaticColor) + glVertexPointer(3, GL_FLOAT, 0, m_pVertices); + else + glInterleavedArrays(GL_C3F_V3F, 0, m_pVertices); + if (m_bVertexArrayLock) m_glLockArraysEXT(0, m_iVertexCount); + glDrawArrays(m_iVertexType, 0, m_iVertexCount); + if (m_bVertexArrayLock) m_glUnlockArraysEXT(); + + glDisableClientState(GL_VERTEX_ARRAY); + + // Remember we're outside a gBegin()..gEnd() loop + m_iVertexType = 0xffff; +#else + AimwxGuiLocker __lock__; + glEnd(); +#endif +} + +void GraphicsOutputDevicewxGLCanvas::gText3f(float x, + float y, + float z, + const char *sStr, + bool bRotated) { +#ifdef WITH_GL_VERTEX_ARRAYS + aimASSERT(m_iVertexType == 0xffff); // Must be outside gBegin*() +#endif + + if (!m_pFont) + return; + + //! \todo make rotation work + if (bRotated) + return; + + { + AimwxGuiLocker __lock__; + /* + if (bRotated) { + glPushMatrix(); + glTranslatef(x,y,z); + glRotatef(90.0f, 0, 0, 1.0f); + glRasterPos3f(0,0,0); + m_pFont->Render(sStr); + glPopMatrix(); + } else { + */ + glRasterPos3f(x, y, z); + m_pFont->Render(sStr); + } +} + +void GraphicsOutputDevicewxGLCanvas::gRelease() { +#ifdef WITH_GL_VERTEX_ARRAYS + aimASSERT(m_iVertexType == 0xffff); // Must be gEnd()ed +#endif + AimwxGuiLocker __lock__; + glEndList(); + glCallList(m_gllist); + //glFlush(); + SwapBuffers(); // Doesn't matter in what context +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevicewxGLCanvas.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Devices/GraphicsOutputDevicewxGLCanvas.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,234 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Output device for output to a wxWidgets OpenGL canvas + * + * \author Willem van Engen + * \date created 2006/09/21 + * \version \$Id: $ + */ + +#ifndef __GRAPHICS_OUTPUT_DEVICE_GL_CANVAS_H__ +#define __GRAPHICS_OUTPUT_DEVICE_GL_CANVAS_H__ + +// Precompiled wxWidgets headers +#include "stdwx.h" + +// Make sure GLCANVAS is compiled into wxWidgets +#if !wxUSE_GLCANVAS +# error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library" +#endif + +#if defined (_MACOSX) +# include +# include +#elif defined (_WINDOWS) +# include +# define GL_GET_PROC_ADDRESS wglGetProcAddress +#else +# include +# define GL_GET_PROC_ADDRESS(x) glXGetProcAddress((const GLubyte*)x) +#endif +/* Define them just ourselves, easiest way to get it working cross-platform + * and -Mesa/OpenGL-version. */ +#ifndef APIENTRY +# define APIENTRY +#endif +typedef void (APIENTRY * LOCAL_PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRY * LOCAL_PFNGLUNLOCKARRAYSEXTPROC) (void); + +#ifdef FTGL_SUBDIR +# include +#else +# include +#endif + +#include "Output/GraphicsOutputDevice.h" + +// Use by default +#define WITH_GL_VERTEX_ARRAYS + +/*! + * \class GraphicsOutputDevicewxGLCanvas "Output/GraphicsOutputDevicewxGLCanvas.h" + * \brief Output class for output to a wxWidgets OpenGL canvas + * + * On windows, OpenGL needs a different context when two different threads + * want to issue OpenGL commands. This is handled automatically for two + * wxThread s. + */ +class GraphicsOutputDevicewxGLCanvas : public wxGLCanvas, + public GraphicsOutputDevice { + public: + GraphicsOutputDevicewxGLCanvas(Parameters *pParam, + wxWindow *parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxString& name + = _T("GraphicsOutputDeviceGLCanvas")); + virtual ~GraphicsOutputDevicewxGLCanvas(); + void OnPaint(wxPaintEvent& event); + void OnSize(wxSizeEvent& event); + void OnEraseBackground(wxEraseEvent& event); + + /*! \param iVerticesMax Maximum number of vertices to be draw inside a gBegin()..gEnd() + * + * When iVerticesMax is zero, this variable will not be updated. Note that is _has_ + * to be set at least once before using this class. + */ + bool Initialize(unsigned int iVerticesMax); + bool Initialize(); + + void Start(); + + void gGrab(); + void gBeginLineStrip(); + void gBeginQuadStrip(); + using GraphicsOutputDevice::gVertex3f; // Because we overload it + void gVertex3f(float x, float y, float z); + void gColor3f(float r, float g, float b); + void gEnd(); + void gText3f(float x, float y, float z, const char *sStr, bool bRotated = false); + void gRelease(); + protected: + /*! \brief Smarter SetCurrent() replacement + * \return true on success, false on error + * + * This function tries GetContext() first. If that fails, it returns false. + */ + bool SetCurrent(); + void Render(); + + /*! \brief Initialize the OpenGL environment. + * + * This must be called after the canvas is realized but before any other + * OpenGL operation is done. Make sure to run SetCurrent() beforehand. + * Usually only needed when m_init is false. + */ + void InitGL(); + + /*! \brief Handle a resize (notify OpenGL of the new area) + * + * This is a separate function, because in multi-threading environments + * multiple contexts have to call it. + */ + void DoResize(); + + /*! \brief Only need to initialize OpenGL once. + * + * This is false at start and true when OpenGL has been initialized. + * No mutex needed, since it's set once at InitGL() and only read afterwards. + */ + bool m_init; + + /*! \brief Vertex list for last drawing so it can be updated on repaint. + * + * No mutex needed, since it's set once at InitGL() and only read afterwards. + */ + GLuint m_gllist; + + //! \brief OpenGL context for worker thread, use when wxIsMainThread() returns false. + wxGLContext *m_pWorkerContext; + + //! \brief Mutex for inter-thread communication + wxMutex s_mutexOpenGL; + + //! \brief When true, OpenGL needs to be reinitialized (in the worker thread) + bool s_bWorkerNeedsInit; + + //! \brief OpenGL attributes used for initialization. + static int GLAttrlist[]; + + //! \brief Whether to use anti-aliasing or not + bool m_bAntialiasing; + + //! \brief FTGL Font class + FTFont *m_pFont; + //! \brief Current font filename + const char *m_sFontFile; + //! \brief Current font size + int m_iFontsize; + +#if defined(WITH_GL_VERTEX_ARRAYS) || defined(DOXYGEN) + //! \brief OpenGL vertex type of the current m_pVertices, or 0xffff is outside gBegin()..gEnd() + int m_iVertexType; + //! \brief Maximum number of vertices begin gBegin()..gEnd() + unsigned int m_iVerticesMax; + //! \brief Vertex array to draw at gEnd(), this becomes m_pVertices[m_iVerticesMax*3] + GLfloat *m_pVertices; + //! \brief The current number of vertices inside m_pVertices + unsigned int m_iVertexCount; + + /*! \brief Whether to use coloring in vertex lists + * + * This variable must not change after Initialize(), or the program may crash. + * When this variable is true, color information is stored in vertex lists. If + * it is false, only vertex data is stored. + * + * This variable exists for performance reasons, but is currently only set in + * the constructor of this object. + */ + bool m_bStaticColor; + //! \brief Current color for vertex list drawing + float m_fCurColorR, m_fCurColorG, m_fCurColorB; + + //! \brief Whether to use vertex array locking or not + bool m_bVertexArrayLock; + //! \brief Pointer to vertex array locking function; can be NULL. + LOCAL_PFNGLLOCKARRAYSEXTPROC m_glLockArraysEXT; + //! \brief Pointer to vertex array unlocking function; can be NULL. + LOCAL_PFNGLUNLOCKARRAYSEXTPROC m_glUnlockArraysEXT; +#endif + + /*! \brief wxMutexGuiEnter() / wxMutexGuiLeave() wrapper + * + * This is a wxMutexLocker-alike for the main gui mutex. Any method that + * is public, can be called from within another thread and does OpenGL or + * other gui calls must use this. Example: + * \code + * void DoFoo() { + * AimwxGuiLocker __lock__; + * glAmazingMethod(); + * } + * \endcode + * + * It is mostly on X-Windows (Xorg/XFree86) that the gui mutex appears to + * be needed. Otherwise the error "Xlib: unexpected async reply" can occur. + * + * On windows, the ui may occasionally lock up for a short while with these + * mutexes. Since they aren't really needed on that platform, it's left out + * alltogether. + */ + class AimwxGuiLocker { + public: + inline AimwxGuiLocker() { +#ifndef _WINDOWS + if (!wxIsMainThread()) wxMutexGuiEnter(); +#endif + } + inline ~AimwxGuiLocker() { +#ifndef _WINDOWS + if (!wxIsMainThread()) wxMutexGuiLeave(); +#endif + } + }; + DECLARE_EVENT_TABLE() +}; +#endif /* __GRAPHICS_OUTPUT_DEVICE_GL_CANVAS_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/GraphAxisSpec.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/GraphAxisSpec.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,126 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Support/Common.h" +#include +#include + +#include "Modules/Output/Graphics/GraphAxisSpec.h" + +GraphAxisSpec::GraphAxisSpec(float fMin, float fMax, Scale::ScaleType iScale) { + m_pScale = NULL; + m_sLabel = NULL; + SetDisplayRange(fMin, fMax); + SetDisplayScale(iScale); +} + +GraphAxisSpec::GraphAxisSpec() { + m_pScale = NULL; + m_sLabel = NULL; + m_fMin = 0; + m_fMax = 0; +} + +GraphAxisSpec::~GraphAxisSpec() { + DELETE_IF_NONNULL(m_pScale); +} + +void GraphAxisSpec::SetDisplayRange(float fMin, float fMax) { + AIM_ASSERT(fMin <= fMax); + m_fMin = fMin; + m_fMax = fMax; + if (m_pScale) + m_pScale->FromLinearScaledExtrema(m_fMin, m_fMax); +} + +void GraphAxisSpec::SetDisplayScale(Scale::ScaleType iScale) { + DELETE_IF_NONNULL(m_pScale); + m_pScale = Scale::Create(iScale); + aimASSERT(m_pScale); + m_pScale->FromLinearScaledExtrema(m_fMin, m_fMax); +} + +bool GraphAxisSpec::Initialize(Parameters *parameters, + const char *sPrefix, + float fMin, + float fMax, + Scale::ScaleType iScale) { + AIM_ASSERT(pParam); + AIM_ASSERT(sPrefix && sPrefix[0]!='\0'); + char sParamName[Parameters::MaxParamNameLength]; + + //! The following parameters are recognized for each axis: + //! - \c ".min", the minimum value; a float or \c 'auto' + snprintf(sParamName, sizeof(sParamName)/sizeof(sParamName[0]), + "%s.min", + sPrefix); + if (pParam->IsSet(sParamName)) { + if (strcmp(parameters->GetString(sParamName), "auto") == 0) + m_fMin = fMin; + else + m_fMin = parameters->GetFloat(sParamName); + } + + //! - \c ".max", the maximum value; a float or \c 'auto' + snprintf(sParamName, sizeof(sParamName)/sizeof(sParamName[0]), + "%s.max", + sPrefix); + if (pParam->IsSet(sParamName)) { + if (strcmp(parameters->GetString(sParamName), "auto")==0) + m_fMax = fMax; + else + m_fMax = parameters->GetFloat(sParamName); + } + + // Make sure ranges are updated properly + SetDisplayRange(m_fMin, m_fMax); + + //! - \c ".scale", the scale; one of \c 'auto', + //! \c 'linear', \c 'erb' or \c 'log' + snprintf(sParamName, sizeof(sParamName)/sizeof(sParamName[0]), + "%s.scale", + sPrefix); + if (pParam->IsSet(sParamName)) { + // Scale change, we updated min/max values already so no need to + Scale::ScaleType iThisScale; + const char *sVal = parameters->GetString(sParamName); + if (strcmp(sVal, "auto")==0) + iThisScale = iScale; + else if (strcmp(sVal, "linear")==0) + iThisScale = Scale::SCALE_LINEAR; + else if (strcmp(sVal, "erb")==0) + iThisScale = Scale::SCALE_ERB; + else if (strcmp(sVal, "log")==0) + iThisScale = Scale::SCALE_LOG; + else { + AIM_ERROR(_T("Unrecognized scale type in parameter '%s': '%s'"), + sParamName, + sVal); + return false; + } + SetDisplayScale(iThisScale); + } + + //! - \c ".label", the label; a string + snprintf(sParamName, sizeof(sParamName)/sizeof(sParamName[0]), + "%s.label", + sPrefix); + if (parameters->IsSet(sParamName)) + m_sLabel = parameters->GetString(sParamName); // Assumes strings remains valid + + return true; +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/GraphAxisSpec.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/GraphAxisSpec.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,88 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __GRAPH_AXIS_SPEC_H__ +#define __GRAPH_AXIS_SPEC_H__ + +#include "Support/Parameters.h" +#include "Modules/Output/Graphics/Scale/Scale.h" + +/*! \class GraphAxisSpec "Output/GraphAxisSpec.h" + * \brief Axis specification for a GraphicsView + */ +class GraphAxisSpec { +public: + /*! \brief Create a new GraphAxisSpec + * \param fMin Minimum value on the axis to show + * \param fMax Maximum value on the axis to show + * \param iScale Scale to use + * + * Please see SetScale() and SetRange() for more details. + */ + GraphAxisSpec(float fMin, float fMax, Scale::ScaleType iScale); + //! \brief Create a new GraphAxisSpec from defaults + GraphAxisSpec(); + + ~GraphAxisSpec(); + + /*! \brief Set the scale to use + * \param iScale Scale to use + */ + void SetDisplayScale(Scale::ScaleType iScale); + /*! \brief Set the minumum and maximum values to show on the axis + * \param fMin Minimum value on the axis to show + * \param fMax Maximum value on the axis to show + * + * Either fMin _must_ be smaller than fMax, or fMin==fMax but then + * this function must be called later to fulfil the former condition + * before it is used to scale data. + */ + void SetDisplayRange(float fMin, float fMax); + /*! \brief Set the label of this axis + * \param sLabel New label, or NULL to remove label + */ + void SetLabel(const char *sLabel); + + /*! \brief Read axis specification from parameter store + * \param pParam Parameter store to read from + * \param sPrefix Prefix to use, e.g. "view.x" + * \param fMin Default minumum value for 'auto' + * \param fMax Default maximum value for 'auto' + * \param iScale Default scale for 'auto' + * \return true if no error occured in reading + */ + bool Initialize(Parameters *pParam, + const char *sPrefix, + float fMin, + float fMax, + Scale::ScaleType iScale); + +protected: + //! \brief Minimum value on the axis to display + float m_fMin; + //! \brief Maximum value on the axis to display + float m_fMax; + //! \brief Scale to use + Scale *m_pScale; + //! \brief Axis label, NULL for no label + const char *m_sLabel; + + friend class GraphicsView; + friend class GraphicsViewTime; +}; + +#endif /* __GRAPH_AXIS_SPEC_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/GraphicsView.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/GraphicsView.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,234 @@ +// Copyright 2006, Willem van Engen +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Support/Common.h" + +#include "Output/GraphicsView.h" +#include "Output/GraphicsOutputDevice.h" + +GraphicsView::GraphicsView(Parameters *parameters) : Module(parameters) { + module_description_ = ""; + module_identifier_ = "graphics"; + module_type_ = "output"; + module_version_ = "$Id: $"; + + m_pDev = NULL; + m_bPlotLabels = false; + m_pAxisX = new GraphAxisSpec(); + AIM_ASSERT(m_pAxisX); + m_pAxisY = new GraphAxisSpec(); + AIM_ASSERT(m_pAxisY); + m_pAxisFreq = new GraphAxisSpec(); + AIM_ASSERT(m_pAxisFreq); + + m_pAxisY->Initialize(m_pParam, + _S("graph.y"), + -1, + 1, + Scale::SCALE_LINEAR); + m_fMarginLeft = m_pParam->GetFloat(_S("graph.margin.left")); + m_fMarginRight = m_pParam->GetFloat(_S("graph.margin.right")); + m_fMarginTop = m_pParam->GetFloat(_S("graph.margin.top")); + m_fMarginBottom = m_pParam->GetFloat(_S("graph.margin.bottom")); + m_bPlotLabels = m_pParam->GetBool(_S("graph.plotlabels")); + + const char *sGraphType = m_pParam->GetString(_S("graph.type")); + if (strcmp(sGraphType, _S("line"))==0) + m_iGraphType = GraphTypeLine; + else if (strcmp(sGraphType, _S("colormap"))==0) + m_iGraphType = GraphTypeColormap; + else if (strcmp(sGraphType, _S("none"))==0) + m_iGraphType = GraphTypeNone; + else { + ret = false; + AIM_ERROR(_T("Unrecognized graph type: '%s'"), sGraphType); + } + + if (strcmp(m_pParam->GetString(_S("graph.mindistance")),"auto") == 0) + // -1 means detect later, based on type and Fire() argument + m_fMinPlotDistance = -1; + else + m_fMinPlotDistance = m_pParam->GetFloat(_S("graph.mindistance")); + +} + +GraphicsView::~GraphicsView() { + DELETE_IF_NONNULL(m_pAxisX); + DELETE_IF_NONNULL(m_pAxisY); + DELETE_IF_NONNULL(m_pAxisFreq); +} + +bool + +bool GraphicsView::InitializeInternal(const SignalBank &bank) { + if (!m_pDev) { + LOG_ERROR("Output device not connected"); + return false; + } + + float y_min = bank.centre_frequency(0); + float y_max = bank.centre_frequency(bank.channel_count() - 1); + if (!m_pAxisFreq->Initialize(m_pParam, + "graph.freq", + y_min, + y_max, + Scale::SCALE_ERB)) { + LOG_ERROR(""); + return false; + } + + float x_min = 0.0; + float x_max = 1000.0 * bank.buffer_length() / bank.sample_rate(); + if (!m_pAxisX->Initialize(m_pParam, + "graph.x", + x_min, + x_max, + Scale::SCALE_LINEAR)) { + LOG_ERROR(""); + return false; + } + + /* Inform graphics output of maximum number of vertices between + * gBegin*() and gEnd(), for any type of plot. Colormap needs most. + */ + if (!m_pDev->Initialize(MAX(10, bank.buffer_length() * 2 + 2))) { + LOG_ERROR(""); + return false; + } +} + +void GraphicsView::Process(const SignalBank &bank) { + float height = 1.0 / bank.channel_count(); + float heightMinMargin = height * (1.0f - m_fMarginBottom - m_fMarginTop); + float xScaling = 1.0f; + + m_pDev->gGrab(); + PlotAxes(bank); + m_pDev->gColor3f(1.0f, 1.0f, 0.8f); + for (int i = 0; i < bank.channel_count(); i++) { + float yOffs = bank.centre_frequency(i); + yOffs = m_pAxisFreq->m_pScale->FromLinearScaled(yOffs) + 0.5f; + /* Don't plot below zero and stop when above 1, since yOffs is + * monotonically increasing. Because of rounding errors, we need + * to check for yOffs < -1e-6 instead of yOffs < 0. */ + if (yOffs < -1e-6) + continue; + if (yOffs > 1) + break; + + // Scale to single channel graphing. + yOffs = yOffs * (1.0f - height) + height / 2.0; + yOffs = yOffs * (1.0f - m_fMarginTop - m_fMarginBottom) + m_fMarginBottom; + PlotData(bank[i], yOffs, heightMinMargin, xScaling); + } + m_pDev->gRelease(); +} + +void GraphicsView::SetAxisScale(Scale::ScaleType iHori, + Scale::ScaleType iVert, + Scale::ScaleType iFreq) { + AIM_ASSERT(m_pAxisX); + AIM_ASSERT(m_pAxisY); + AIM_ASSERT(m_pAxisFreq); + m_pAxisX->SetDisplayScale(iHori); + m_pAxisY->SetDisplayScale(iVert); + m_pAxisFreq->SetDisplayScale(iFreq); +} + +void GraphicsView::BeginDataStrip() { + m_iPrevValEqual=0; + m_bFirstPoint=true; + switch(m_iGraphType) { + case GraphTypeLine: + m_pDev->gBeginLineStrip(); + break; + case GraphTypeColormap: + m_pDev->gBeginQuadStrip(); + break; + case GraphTypeNone: + // Nothing: just for testing computation overhead of graphing + break; + } +} + +void GraphicsView::PlotDataPoint(float x, + float y, + float val, + float height, + bool isLast) { + AIM_ASSERT(m_pDev); + + /* Reduce the number of points plotted by eliminating duplicate values: + * + * oooo o--o + * o / o + * oooo ooo => o--o o--o + * oooo ooo o--o o-o + * + * with 'o' points that are plotted, and '-' by the graphics output + * device interpolated points. We could be smarter and include + * first-order interpolation, but we leave that as an exercise for + * the reader. Please send your patches :) + * + * So, we don't draw points that are too close to the previous value. + * But if the value changes (or it's the last point), we must draw the + * previous point too. + */ + if (!m_bFirstPoint + && !isLast + && fabs(m_fPrevVal-val) < m_fMinPlotDistance) { + m_iPrevValEqual++; + // Don't set m_fPrevVal to avoid not catching slow changes + m_fPrevX = x; + m_fPrevY = y; + m_fPrevHeight = height; + return; + } else { + if (m_iPrevValEqual > 0) { + // Draw previous point + PlotDataPointDirect(m_fPrevX, m_fPrevY, m_fPrevVal, m_fPrevHeight); + } + m_iPrevValEqual = 0; + m_fPrevVal = val; + m_bFirstPoint = false; + } + PlotDataPointDirect(x, y, val, height); +} + +void GraphicsView::PlotDataPointDirect(float x, + float y, + float val, + float height) { + // Draw it in the right way + switch(m_iGraphType) { + case GraphTypeLine: + m_pDev->gVertex2f(x, y + val * height); + break; + case GraphTypeColormap: + //! \todo make it a real colormap instead of grayscale + m_pDev->gColor3f(val + 0.5, val + 0.5, val + 0.5); + m_pDev->gVertex2f(x, y - height / 2); + m_pDev->gVertex2f(x, y + height / 2); + break; + case GraphTypeNone: + // Nothing: just for testing computation overhead of graphing + break; + default: + // Shouldn't happen + AIM_ASSERT(0); + } +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/GraphicsView.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/GraphicsView.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,182 @@ +// Copyright 2006-2010, Willem van Engen, Thomas Walters +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief General graphics view definition + * + * \author Willem van Engen + * \date created 2006/09/26 + * \version \$Id: $ + */ + +#ifndef __GRAPHICS_VIEW_H__ +#define __GRAPHICS_VIEW_H__ + +#include "Support/Parameters.h" +#include "Support/SignalBank.h" +#include "Modules/Output/Graphics/Scale/Scale.h" +#include "Modules/Output/Graphics/Devices/GraphicsOutputDevice.h" +#include "Modules/Output/Graphics/GraphAxisSpec.h" + +/*! + * \class GraphicsView "Modules/Output/Graphics/GraphicsView.h" + * \brief General graphics view module + * + * This class is a general graph drawing class for one or more Signals. + * \sa Signal, SignalBank + */ +class GraphicsView : public Module { +public: + /*! \brief Create a new view + * \param params A parameter store + * + */ + GraphicsView(Parameters *params); + virtual ~GraphicsView(); + + /*! \brief Create a copy of this GraphicsView + * \param pDev Output device to bind to + * \return Newly created GraphicsView, to be freed by the caller. + * \deprecated Possibly, use Initialize() + */ + virtual GraphicsView *Clone(GraphicsOutputDevice *pDev) = 0; + + /*! \brief (Re-)initialize this graphics view and it's device + * \param pParam Main parameter store to bind to + * \param pDev Graphics output device to draw to + * \return true on success, false on error + * + * It is possible to call Initialize() multiple times for binding + * to another graphics device or updating the parameters. This method + * does read all parameters than can be changed at run-time. + * + * One of the Initialize() functions _must_ be called before actually + * using it for plotting. + */ + virtual bool InitializeInternal(const SignalBank &bank); + + /*! \brief Set the axes' scale + * \param iXScale Scale type of the horizontal axis + * \param iYScale Scale type of the vertical axis for signal data + * \param iFreqScale Scale type of the vertical axis + * \deprecated Possibly, use AimParameters and Initialize + * \todo const arguments + */ + virtual void SetAxisScale(Scale::ScaleType iXScale, + Scale::ScaleType iYScale, + Scale::ScaleType iFreqScale); + + + virtual void Process(const SignalBank &bank); + + protected: + /*! \brief Plot the data of a signal + * \param pSig Signal to plot + * \param yOffset Vertical offset (between 0 and 1), where to plot 0 signal value. + * \param height Height of the signal to plot (between 0 and 1) + * \param xScale Scaling in x-direction. 1.0 makes it cover the whole length. 0.5 only the left half. + */ + virtual void PlotData(const vector &signal, + float yOffset, + float height, + float xScale) = 0; + + /*! \brief Plot the axes for a signal bank + * \param pSig Signal to plot the axes for + */ + virtual void PlotAxes(const SignalBank &bank) = 0; + + //! \brief Calls the correct m_pDev->gBegin*() for the current PlotType + virtual void BeginDataStrip(); + /*! \brief Plot a data point (with smart reduction) + * \param x X-coordinate of point (in OpenGL space) + * \param y y-coordinate of point (in OpenGL space) + * \param val Value to plot (from -0.5..0.5) + * \param height Height to use for plotting (in OpenGL space) + * \param isLast True if this is the last point of this batch to draw + * + * This method tries to reduce the number of actual graphics output + * operations using it's interpolation methods (e.g., a line). + */ + virtual void PlotDataPoint(float x, + float y, + float val, + float height, + bool isLast); + + /*! \brief Plot a data point (directly) + * \param x X-coordinate of point (in OpenGL space) + * \param y y-coordinate of point (in OpenGL space) + * \param val Value to plot (from -0.5..0.5) + * \param height Height to use for plotting (in OpenGL space) + */ + virtual void PlotDataPointDirect(float x, float y, float val, float height); + + /*! \brief Plot a strobe point + * \param x X-coordinate of centre (in OpenGL space) + * \param y Y-coordinate of centre (in OpenGL space) + * \param size Size of the block drawn + * \param colour_r Red colour intensity + * \param colour_g Green colour intensity + * \param colour_b Blue colour intensity + */ + virtual void PlotStrobePoint(float x, + float y, + float size, + float color_r, + float color_g, + float color_b); + + //! \brief Where to plot to + GraphicsOutputDevice *m_pDev; + + //! \brief Main parameter store + Parameters *m_pParam; + + //! \brief Axes specifications + GraphAxisSpec *m_pAxisX, *m_pAxisY, *m_pAxisFreq; + + //! \brief Graph margins + float m_fMarginLeft, m_fMarginRight, m_fMarginTop, m_fMarginBottom; + + //! \brief Whether labels are plotted or not + bool m_bPlotLabels; + + //! \brief Graphics device type + enum GraphType { + GraphTypeNone, + GraphTypeLine, + GraphTypeColormap + }; + GraphType m_iGraphType; + + /*! \brief Minimum distance between subsequent values for plotting a point + * + * This value is set in Initialize() from parameter graph.mindistance. + */ + float m_fMinPlotDistance; + + //! \brief true if this next point is the first of the strip + bool m_bFirstPoint; + //! \brief Value of previously plotted point + float m_fPrevVal, m_fPrevX, m_fPrevY, m_fPrevHeight; + //! \brief Number of times m_fValPrev was within range m_fMinPlotDistance + int m_iPrevValEqual; +}; + +#endif /* __GRAPHICS_VIEW_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/GraphicsViewTime.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/GraphicsViewTime.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,139 @@ +// Copyright 2006-2010, Willem van Engen, Thomas Walters +// +// AIM-C: A C++ implementation of the Auditory Image Model +// http://www.acousticscale.org/AIMC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/*! + * \file + * \brief Time-representation graphics view + * + * \author Willem van Engen + * \date created 2006/09/26 + * \version \$Id: GraphicsViewTime.cpp 607 2008-06-25 00:14:14Z tom $ + */ + +#include "Support/Common.h" + +#include + +#include "Support/SignalBank.h" +#include "Modules/Output/Graphics/GraphicsView.h" +#include "Modules/Output/Graphics/GraphicsViewTime.h" + +GraphicsViewTime::GraphicsViewTime(Parameters *pParam) + : GraphicsView(pParam) { +} + +GraphicsViewTime *GraphicsViewTime::Clone(GraphicsOutputDevice *pDev) { + GraphicsViewTime *pView = new GraphicsViewTime(m_pParam); + // Copy everything + pView->m_pAxisX->SetDisplayRange(m_pAxisX->m_fMax, m_pAxisX->m_fMin); + pView->m_pAxisX->SetDisplayScale(m_pAxisX->m_pScale->getType()); + pView->m_pAxisY->SetDisplayRange(m_pAxisY->m_fMax, m_pAxisY->m_fMin); + pView->m_pAxisY->SetDisplayScale(m_pAxisY->m_pScale->getType()); + pView->m_pAxisFreq->SetDisplayRange(m_pAxisFreq->m_fMax, m_pAxisFreq->m_fMin); + pView->m_pAxisFreq->SetDisplayScale(m_pAxisFreq->m_pScale->getType()); + return pView; +} + +void GraphicsViewTime::PlotAxes(const SignalBank &bank) { + m_pDev->gColor3f(0.0f, 0.7f, 0.7f); + // Vertical axis + m_pDev->gBeginLineStrip(); + m_pDev->gVertex2f(m_fMarginLeft, m_fMarginBottom); + m_pDev->gVertex2f(m_fMarginLeft, 1.0f - m_fMarginTop); + m_pDev->gEnd(); + // Horizontal axis + m_pDev->gBeginLineStrip(); + m_pDev->gVertex2f(m_fMarginLeft, m_fMarginBottom); + m_pDev->gVertex2f(1.0f-m_fMarginRight, m_fMarginBottom); + m_pDev->gEnd(); + + if (!m_bPlotLabels) + return; + + // Labels + char sTxt[80]; + snprintf(sTxt, sizeof(sTxt) / sizeof(sTxt[0]), + _S("%s [%.0f..%.0f Hz, %s scale]"), + m_pAxisFreq->m_sLabel ? m_pAxisFreq->m_sLabel : "", + m_pAxisFreq->m_fMin, m_pAxisFreq->m_fMax, + m_pAxisFreq->m_pScale->getName()); + m_pDev->gText2f(0.0025f, 0.35f, sTxt, true); + if (m_bPlotScaled) { + snprintf(sTxt, sizeof(sTxt) / sizeof(sTxt[0]), + _S("%s [cycles, %s scale]"), + m_pAxisX->m_sLabel ? m_pAxisX->m_sLabel : "", + m_pAxisX->m_pScale->getName()); + } else { + snprintf(sTxt, sizeof(sTxt) / sizeof(sTxt[0]), + _S("%s [%.2f..%.2f ms, %s scale]"), + m_pAxisX->m_sLabel ? m_pAxisX->m_sLabel : "", + m_pAxisX->m_fMin, + m_pAxisX->m_fMax, + m_pAxisX->m_pScale->getName()); + } + m_pDev->gText2f(m_fMarginLeft, 0.0025f, sTxt, false); + + // Frame time + snprintf(sTxt, sizeof(sTxt)/sizeof(sTxt[0]), _S("t=%.0f ms"), + pBank->getSampleTime(0)); + m_pDev->gText2f(0.8f, 0.0025f, sTxt, false); +} + +void GraphicsViewTime::PlotData(const vector &signal, + float sample_rate, + float yOffset, + float height, + float xScale) { + AIM_ASSERT(pSig); + AIM_ASSERT(xScale >= 0 && xScale <= 1); + AIM_ASSERT(height > 0 && height <= 1); + AIM_ASSERT(yOffset >= 0 && yOffset <= 1); + AIM_ASSERT(m_pAxisX && m_pAxisX->m_pScale); + AIM_ASSERT(m_pAxisY && m_pAxisY->m_pScale); + + // Make sure we get time is ms as x value + xScale *= 1000.0 / sample_rate; + m_pDev->gColor3f(1.0f, 1.0f, 0.8f); + BeginDataStrip(); + + // Draw the signal. + float x = 0; + float y = 0; + for (int i = 0; i < signal.size(); i++) { + // Find out where to draw and do so + x = xScale * i; + y = signal[i]; + x = m_pAxisX->m_pScale->FromLinearScaled(x) + 0.5f; + y = m_pAxisY->m_pScale->FromLinearScaled(y); + + if (x < 0.0) + continue; + + // Now fit it into the drawing area + x = x * (1.0f - m_fMarginLeft - m_fMarginRight) + m_fMarginLeft; // fit inside x-axis area + PlotDataPoint(x, yOffset, y, height, false); + /* There's no point in drawing anything when x>1.0, outside of view. + * x is continuously increasing, so we can just stop completely. We need to + * plot this point however, to finish the final line. */ + if (x > 1.0) + break; + } + // Redraw the last point in case it's needed + PlotDataPoint(x, yOffset, y, height, true); + + m_pDev->gEnd(); +} diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/GraphicsViewTime.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/GraphicsViewTime.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,43 @@ +/*! + * \file + * \brief Time-representation graphics view definition + * + * \author Willem van Engen + * \date created 2006/09/26 + * \version \$Id: GraphicsViewTime.h 459 2007-11-08 11:50:04Z tom $ + */ +/* (c) 2006, University of Cambridge, Medical Research Council + * http://www.pdn.cam.ac.uk/groups/cnbh/aimmanual + */ +#ifndef __GRAPHICS_VIEW_TIME_H__ +#define __GRAPHICS_VIEW_TIME_H__ + +#include "Support/Signal.h" +#include "Support/SignalBank.h" +#include "Output/GraphicsOutputDevice.h" +#include "Output/GraphicsView.h" + +/*! + * \class GraphicsViewTime "Output/GraphicsViewTime.h" + * \brief Time-definition graphics view class + * + * This plots a Signal or SignalBank in the time domain. + */ +class GraphicsViewTime : public GraphicsView { +public: + /*! \brief Create a new view + * \param pParam Main parameter store + */ + GraphicsViewTime(AimParameters *pParam); + virtual ~GraphicsViewTime() { }; + + virtual GraphicsViewTime *Clone(GraphicsOutputDevice *pDev); + +private: + void PlotData(Signal* pSig, float yOffset, float height, float xScale = 1.0); + void PlotAxes(Signal* pSig); + void PlotAxes(SignalBank* pBank); + +}; + +#endif /* __GRAPHICS_VIEW_TIME_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Scale/Scale.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Scale/Scale.cc Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,106 @@ +/*! + * \file + * \brief Frequency scale for generating filter banks and their frequencies + * + * \author Willem van Engen + * \date created 2006/09/28 + * \version \$Id: Scale.cpp 459 2007-11-08 11:50:04Z tom $ + */ +/* (c) 2006, University of Cambridge, Medical Research Council + * http://www.pdn.cam.ac.uk/groups/cnbh/aimmanual + */ +#include "Support/common.h" +#include "Support/util.h" +#include "Modules/Scale/Scale.h" +#include "Modules/Scale/ScaleLinear.h" +#include "Modules/Scale/ScaleERB.h" +#include "Modules/Scale/ScaleLog.h" +#include "Modules/Scale/ScaleLogScaled.h" + +Scale *Scale::Create(ScaleType iType, unsigned int min, unsigned int max, float density) +{ + switch(iType) { + case SCALE_LINEAR: + return static_cast(new ScaleLinear(min, max, density)); + case SCALE_ERB: + return static_cast(new ScaleERB(min, max, density)); + case SCALE_LOG: + return static_cast(new ScaleLog(min, max, density)); + case SCALE_LOGSCALED: + return static_cast(new ScaleLogScaled(min, max, density)); + default: + aimASSERT(0); + break; + } + // Unreachable code + aimASSERT(0); + return NULL; +} + +Scale *Scale::Create(ScaleType iType) +{ + return Create(iType, 0, 0, 0); +} + +Scale *Scale::Clone() +{ + Scale *pScale = Create(m_iType, m_iMin, m_iMax, m_fDensity); + aimASSERT(pScale); + pScale->m_fScaledCurHalfSum = m_fScaledCurHalfSum; + pScale->m_fScaledCurDiff = m_fScaledCurDiff; + return pScale; +} + +float Scale::FromLinearScaled(float fVal) +{ + /*! This function returns + * ( FromLinear(fVal) - (fMinScaled+fMaxScaled)/2 ) / (fMaxScaled-fMinScaled) + */ + float fValScaled = FromLinear(fVal); + return (fValScaled - m_fScaledCurHalfSum) / m_fScaledCurDiff; +} +void Scale::FromLinearScaledExtrema(float fMin, float fMax) +{ + float fMinScaled = FromLinear(fMin); + float fMaxScaled = FromLinear(fMax); + m_fScaledCurHalfSum = (fMinScaled+fMaxScaled)/2; + m_fScaledCurDiff = fMaxScaled-fMinScaled; + m_fMin = fMin; + m_fMax = fMax; +} +void Scale::FromLinearScaledExtrema(Scale *pScale) +{ + aimASSERT(pScale); + FromLinearScaledExtrema(pScale->m_fMin, pScale->m_fMax); +} + +SignalBank* Scale::CreateSignalBank(unsigned int iChannels, unsigned int iBufferlength, unsigned int iSamplerate) +{ + SignalBank *pBank; + double intpart, fracpart; + fracpart = modf((m_iMax-m_iMin)*m_fDensity, &intpart); + unsigned int nBankChan = (unsigned int) intpart; + if (fracpart >= 0.5f) + nBankChan++; + + pBank = new SignalBank(iChannels, iBufferlength, iSamplerate, nBankChan); + aimASSERT(pBank); + + float scaleDelta = ( FromLinear(m_iMax) - FromLinear(m_iMin) ) / (nBankChan-1); + float scaleCur = FromLinear(m_iMin); + + for (unsigned int i=0; isetCentreFrequency(i, ToLinear(scaleCur)); + scaleCur+=scaleDelta; + } + + return pBank; +} + + + +SignalBank* Scale::CreateSignalBank(Signal* pSig) { + aimASSERT(pSig); + return CreateSignalBank(pSig->getAudioChannels(), pSig->getBufferlength(), pSig->getSamplerate()); +} + diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Scale/Scale.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Scale/Scale.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,159 @@ +/*! + * \file + * \brief General definition for frequency scale management + * + * \author Willem van Engen + * \date created 2006/09/26 + * \version \$Id: Scale.h 576 2008-05-29 12:51:16Z tom $ + */ +/* (c) 2006, University of Cambridge, Medical Research Council + * http://www.pdn.cam.ac.uk/groups/cnbh/aimmanual + */ +#ifndef __MODULE_SCALE_H__ +#define __MODULE_SCALE_H__ + +#include "Support/SignalBank.h" + +/*! + * \class Scale "Modules/Scale/Scale.h" + * \brief General class for frequency scale management + * + * This class + * - Converts between a linear and specified scale + * - Creates filterbank signals based on that scale + * - Creates any of the children filterbanks with Scale::Create. + * To create a new scale, all you need to do is override ToLinear() and + * FromLinear(). See existing scales like ScaleERB for example: + */ +class Scale; +class Scale { +public: + //! \brief A list of possible scales + enum ScaleType { + SCALE_LINEAR, SCALE_ERB, SCALE_LOG, SCALE_LOGSCALED + }; + /*! \brief Create a new scale based on type + * \param iType Scale type to create + * \param min Bottom frequency + * \param max Top frequency + * \param density Density of distribution on the scale (scale-dependent) + * \return Newly created scale, to be freed by the caller. + * + * This is on purpose no virtual function, always use it Scale::Create(). + * \todo Split into scaling and filterbank creation parts. + * Maybe even a separate ScaleMaker that implements Create(). + */ + static Scale *Create(ScaleType iType, unsigned int min, unsigned int max, float density); + + /*! \overload + * This function is for scaling-only Scales, you must not create a SignalBank + * with the returned Scale. + */ + static Scale *Create(ScaleType iType); + + /*! \brief Create a new Scale + * \param min Bottom frequency + * \param max Top frequency + * \param density Density of distribution on the scale (scale-dependent) + */ + Scale(unsigned int min, unsigned int max, float density) { + m_iMin = min; + m_iMax = max; + m_fDensity = density; + m_sName = NULL; + }; + + virtual ~Scale() { }; + + /*! \brief Create an exact copy of this Scale + * \return A newly created Scale, to be freed by the caller. + */ + virtual Scale *Clone(); + + //! \return this Signal's ScaleType + ScaleType getType() { return m_iType; }; + //! \return this Signal's name + const char *getName() { return m_sName; }; + + /*! \brief Scale a frequency from linear to this scale + * \param fFreq Frequency to scale + * \return Scaled frequency + */ + virtual float FromLinear(float fFreq) = 0; + + /*! \brief Scale a frequency from this scale to linear + * \param fFreq Scaled frequency to scale back + * \return Linear frequency + */ + virtual float ToLinear(float fFreq) = 0; + + /*! \brief Scale from [fMin..fMax] to [-0.5..0.5] + * \param fVal Value to scale, must be within [fMin..fMax] + * \return Float in [-0.5..0.5] according to this scale + * \sa FromLinearScaledExtrema + * + * This is mainly for displaying any value on a scale and to make sure + * that it fits in on screen. Don't use this for any real maths! + * + * The default implementation assumes that the scale is monotonic, so the + * FromLinear(fMin) and FromLinear(fMax) are scale's output boundaries. + * + * fMin and fMax are set by FromLinearScaledExtrema. Do not use this before + * calling that. + */ + float FromLinearScaled(float fVal); + /*! \brief Update the FromLinearScaled min/max values + * \param fMin Minimum value to be input + * \param fMax Maxmimum value to be input + * \sa FromLinearScaled + */ + void FromLinearScaledExtrema(float fMin, float fMax); + /*! \overload + * \brief Copy min/max values from another Scale + * \param pScale Scale from which to copy min/max values, must not be NULL + */ + void FromLinearScaledExtrema(Scale *pScale); + + /*! \brief Create a new signal bank + * \param iChannels Number of audio channels + * \param iBufferlength Length of the buffer in frames + * \param iSamplerate Samplerate in Hz + * Note that the caller must free the signal bank again. + */ + virtual SignalBank* CreateSignalBank(unsigned int iChannels, unsigned int iBufferlength, unsigned int iSamplerate); + + /*! \overload + * \brief Create a signal bank based on a Signal's parameters + * \param pSig Signal to get parameters from + * pSig is only used to look at parameters like samplerate, nothing is done + * with its contents. + * Note that the caller must free the signal bank again. + */ + virtual SignalBank* CreateSignalBank(/*! \todo const*/ Signal* pSig); + + +protected: + //! \brief Bottom frequency + unsigned int m_iMin; + //! \brief Top frequency + unsigned int m_iMax; + //! \brief Density of distribution on the scale (scale-dependent) + float m_fDensity; + //! \brief The type of this scale, used by Clone(); set this in children. + ScaleType m_iType; + //! \brief The name of this scale; set this in children. + const char *m_sName; + + /*! \brief Minimum value for scaling as input + * \sa FromLinearScaled(), FromLinearScaledExtrema() */ + float m_fMin; + /*! \brief Maximum value for scaling as input + * \sa FromLinearScaled(), FromLinearScaledExtrema() */ + float m_fMax; + //! \brief Value used in FromLinearScaled + float m_fScaledCurHalfSum; + //! \brief Value used in FromLinearScaled + float m_fScaledCurDiff; +}; + +#endif /* __MODULE_SCALE_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Scale/ScaleERB.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Scale/ScaleERB.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,43 @@ +/*! + * \file + * \brief ERB frequency scale for generating filter banks and their frequencies + * + * \author Tom Walters + * \date created 2006/09/26 + * \version \$Id: ScaleERB.h 459 2007-11-08 11:50:04Z tom $ + */ +/* (c) 2006, University of Cambridge, Medical Research Council + * http://www.pdn.cam.ac.uk/groups/cnbh/aimmanual + */ +#ifndef __MODULE_SCALE_ERB_H__ +#define __MODULE_SCALE_ERB_H__ + +#include + +#include "Modules/Scale/Scale.h" + +/*! + * \class ScaleERB "Modules/Scale/ScaleERB.h" + * \brief ERB frequency scale for generating filter banks and their frequencies + * + * It is very advisable to use Scale::Create() to an instance of this scale. + * + * References: + * - J. Smith and J. Abel (1999), "Bark and ERB bilinear transforms" + * http://www-ccrma.stanford.edu/~jos/bbt/ + */ +class ScaleERB : public Scale { +public: + ScaleERB(unsigned int min, unsigned int max, float density) + : Scale(min, max, density) { m_iType = SCALE_ERB; m_sName = "erb"; }; + + float FromLinear(float fFreq) { + return 21.4f*log10(0.00437f*fFreq + 1.0f); + }; + + float ToLinear(float fFreq) { + return (pow(10, fFreq/21.4f) - 1.0f)/0.00437f; + }; +}; + +#endif /* __MODULE_SCALE_ERB_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Scale/ScaleLinear.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Scale/ScaleLinear.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,39 @@ +/*! + * \file + * \brief Linear frequency scale for generating filter banks and their frequencies + * + * \author Willem van Engen + * \date created 2006/09/26 + * \version \$Id: ScaleLinear.h 459 2007-11-08 11:50:04Z tom $ + */ +/* (c) 2006, University of Cambridge, Medical Research Council + * http://www.pdn.cam.ac.uk/groups/cnbh/aimmanual + */ +#ifndef __MODULE_SCALE_LINEAR_H__ +#define __MODULE_SCALE_LINEAR_H__ + +#include "Modules/Scale/Scale.h" + +/*! + * \class ScaleLinear "Modules/Scale/ScaleLinear.h" + * \brief Linear frequency scale for generating filter banks and their frequencies + * + * It is very advisable to use Scale::Create() to an instance of this scale. + * + * In terms of scaling, this is of course an identity transformation. + */ +class ScaleLinear : public Scale { +public: + ScaleLinear(unsigned int min, unsigned int max, float density) + : Scale(min, max, density) { m_iType = SCALE_LINEAR; m_sName = "linear"; }; + + float FromLinear(float fFreq) { + return fFreq; + }; + + float ToLinear(float fFreq) { + return fFreq; + }; +}; + +#endif /* __MODULE_SCALE_LINEAR_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Scale/ScaleLog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Scale/ScaleLog.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,42 @@ +/*! + * \file + * \brief Logarithmic frequency scale for generating filter banks and their frequencies + * + * \author Willem van Engen + * \date created 2006/09/28 + * \version \$Id: ScaleLog.h 459 2007-11-08 11:50:04Z tom $ + */ +/* (c) 2006, University of Cambridge, Medical Research Council + * http://www.pdn.cam.ac.uk/groups/cnbh/aimmanual + */ +#ifndef __MODULE_SCALE_LOG_H__ +#define __MODULE_SCALE_LOG_H__ + +#include + +#include "Modules/Scale/Scale.h" + +/*! + * \class ScaleLog "Modules/Scale/ScaleLog.h" + * \brief Logarithmic frequency scale for generating filter banks and their frequencies + * + * It is very advisable to use Scale::Create() to an instance of this scale. + */ +class ScaleLog : public Scale { +public: + ScaleLog(unsigned int min, unsigned int max, float density) + : Scale(min, max, density) { m_iType = SCALE_LOG; m_sName="log"; }; + + /*! The log scale has a problem, because log(0)=inf, so all values below + * 1e-5 are truncated to 1e-5. */ + float FromLinear(float fFreq) { + if (fFreq<1e-5f) fFreq=1e-5f; + return log(fFreq); + }; + + float ToLinear(float fFreq) { + return exp(fFreq); + }; +}; + +#endif /* __MODULE_SCALE_LOG_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/Output/Graphics/Scale/ScaleLogScaled.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/trunk/src/Modules/Output/Graphics/Scale/ScaleLogScaled.h Fri Oct 15 05:40:53 2010 +0000 @@ -0,0 +1,43 @@ +/*! + * \file + * \brief Log frequency scale for generating filter banks and their frequencies. Based on the scaling of the ERB scale + * + * \author Tom Walters + * \date created 2006/09/26 + * \version \$Id: ScaleLogScaled.h 459 2007-11-08 11:50:04Z tom $ + */ +/* (c) 2006, University of Cambridge, Medical Research Council + * http://www.pdn.cam.ac.uk/groups/cnbh/aimmanual + */ +#ifndef __MODULE_SCALE_LOGSCALED_H__ +#define __MODULE_SCALE_LOGSCALED_H__ + +#include + +#include "Modules/Scale/Scale.h" + +/*! + * \class ScaleERB "Modules/Scale/ScaleERB.h" + * \brief ERB frequency scale for generating filter banks and their frequencies + * + * It is very advisable to use Scale::Create() to an instance of this scale. + * + * References: + * - J. Smith and J. Abel (1999), "Bark and ERB bilinear transforms" + * http://www-ccrma.stanford.edu/~jos/bbt/ + */ +class ScaleLogScaled : public Scale { +public: + ScaleLogScaled(unsigned int min, unsigned int max, float density) + : Scale(min, max, density) { m_iType = SCALE_ERB; m_sName = "logscaled"; }; + + float FromLinear(float fFreq) { + return 21.4f*log10(0.00437f*fFreq); + }; + + float ToLinear(float fFreq) { + return (pow(10, fFreq/21.4f))/0.00437f; + }; +}; + +#endif /* __MODULE_SCALE_ERB_H__ */ diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/SSI/ModuleSSI.cc --- a/trunk/src/Modules/SSI/ModuleSSI.cc Wed Sep 29 00:24:03 2010 +0000 +++ b/trunk/src/Modules/SSI/ModuleSSI.cc Fri Oct 15 05:40:53 2010 +0000 @@ -69,6 +69,13 @@ // The centre frequency of the channel which will just fill the complete // width of the SSI buffer pivot_cf_ = parameters_->DefaultFloat("ssi.pivot_cf", 1000.0f); + + // Whether or not to do smooth offset when the pitch cutoff is active. + do_smooth_offset_ = parameters_->DefaultBool("ssi.do_smooth_offset", false); + + // The number of cycles, centered on the pitch line, over which the SSI is taken + // to zero when doing the pitch cutoff. + smooth_offset_cycles_ = parameters_->DefaultFloat("ssi.smooth_offset_cycles", 3.0f); } ModuleSSI::~ModuleSSI() { @@ -146,16 +153,45 @@ if (do_pitch_cutoff_) { pitch_index = ExtractPitchIndex(input); } + + float gamma_min = -1.0f; + float gamma_max = log2(ssi_width_cycles_); for (int ch = 0; ch < channel_count_; ++ch) { float centre_frequency = input.centre_frequency(ch); + + float channel_weight = 1.0f; + int cutoff_index = buffer_length_ - 1; + if (do_pitch_cutoff_) { + if (pitch_index < cutoff_index) { + if (weight_by_cutoff_) { + channel_weight = static_cast(buffer_length_) + / static_cast(pitch_index); + } + cutoff_index = pitch_index; + } + } + + // tanh(3) is about 0.995. Seems reasonable. + float smooth_pitch_constant = smooth_offset_cycles_ * 3.0f; + float pitch_h = 0.0f; + if (do_smooth_offset_) { + if (log_cycles_axis_) { + float gamma = gamma_min + (gamma_max - gamma_min) + * static_cast(pitch_index) + / static_cast(ssi_width_samples_); + pitch_h = pow(2.0f, gamma); + } else { + pitch_h = static_cast(pitch_index) * ssi_width_cycles_ + / static_cast(ssi_width_samples_); + } + } + // Copy the buffer from input to output, addressing by h-value for (int i = 0; i < ssi_width_samples_; ++i) { float h; float cycle_samples = sample_rate_ / centre_frequency; if (log_cycles_axis_) { - float gamma_min = -1.0f; - float gamma_max = log2(ssi_width_cycles_); float gamma = gamma_min + (gamma_max - gamma_min) * static_cast(i) / static_cast(ssi_width_samples_); @@ -173,17 +209,11 @@ float frac_part = modf(h * cycle_samples, &whole_part); int sample = floor(whole_part); - float weight = 1.0f; - - int cutoff_index = buffer_length_ - 1; - if (do_pitch_cutoff_) { - if (pitch_index < cutoff_index) { - if (weight_by_cutoff_) { - weight *= static_cast(buffer_length_) - / static_cast(pitch_index); - } - cutoff_index = pitch_index; - } + float weight = channel_weight; + + if (do_smooth_offset_ && do_pitch_cutoff_) { + // Smoothing around the pitch cutoff line. + weight *= (1.0f + tanh(smooth_pitch_constant * (pitch_h - h))) / 2.0f; } if (weight_by_scaling_) { @@ -193,7 +223,7 @@ } float val; - if (sample < cutoff_index) { + if (sample < cutoff_index || do_smooth_offset_) { float curr_sample = input.sample(ch, sample); float next_sample = input.sample(ch, sample + 1); val = weight * (curr_sample diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Modules/SSI/ModuleSSI.h --- a/trunk/src/Modules/SSI/ModuleSSI.h Wed Sep 29 00:24:03 2010 +0000 +++ b/trunk/src/Modules/SSI/ModuleSSI.h Fri Oct 15 05:40:53 2010 +0000 @@ -62,6 +62,8 @@ bool weight_by_scaling_; bool log_cycles_axis_; float pitch_search_start_ms_; + bool do_smooth_offset_; + float smooth_offset_cycles_; }; } // namespace aimc diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Support/Module.cc --- a/trunk/src/Support/Module.cc Wed Sep 29 00:24:03 2010 +0000 +++ b/trunk/src/Support/Module.cc Fri Oct 15 05:40:53 2010 +0000 @@ -87,7 +87,6 @@ if (!initialized_) return; - // LOG_INFO("Resetting module %s", module_identifier_.c_str()); ResetInternal(); // Iterate through all the targets of this module, resetting diff -r 06a26f5cdad7 -r 7a573750b186 trunk/src/Support/SignalBank.h --- a/trunk/src/Support/SignalBank.h Wed Sep 29 00:24:03 2010 +0000 +++ b/trunk/src/Support/SignalBank.h Fri Oct 15 05:40:53 2010 +0000 @@ -54,14 +54,27 @@ bool Initialize(const SignalBank &input); bool Validate() const; + // Return a const reference to an individual signal. Allows for + // signal[channel][sample] referencing of SignalBanks. inline const vector &operator[](int channel) const { return signals_[channel]; }; + // Return a const reference to an individual signal. inline const vector &get_signal(int channel) const { return signals_[channel]; }; + // Return a reference to the signal vector. The reference is not + // const, so the vector is directly modifiable. In order to maintain + // consistency of the data within the filterbank, the size of this + // vector should not be changed. Changes to the vector size can be picked + // up with a call to Validate(), which will return false if the sizes of + // channle vectors are not consistent. + inline vector &get_mutable_signal(int channel) { + return signals_[channel]; + }; + inline void set_signal(int channel, vector input) { signals_[channel] = input; }