From 8910b0d471b52c52b1391d386b5b78d9da659881 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 4 Mar 2012 20:59:23 +0100 Subject: [PATCH 001/152] use shaders for all NIF materials. this has numerous advantages: - vertex colours are now working as they should (and they have a huge impact on the look) - the lighting is per pixel and looks a lot better - by using shaders, we can use more lights at a time (fixed function has max of 8) --- apps/openmw/mwrender/sky.cpp | 1 + components/nifogre/ogre_nif_loader.cpp | 131 +++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index b8bd588c4..dda95c188 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -439,6 +439,7 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) : vshader->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); vshader->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); mAtmosphereMaterial->getTechnique(0)->getPass(0)->setVertexProgram(vshader->getName()); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setFragmentProgram(""); // Clouds NifOgre::NIFLoader::load("meshes\\sky_clouds_01.nif"); diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 8b5540019..7ffc9561f 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -297,7 +297,138 @@ void NIFLoader::createMaterial(const String &name, material->setSelfIllumination(emissive.array[0], emissive.array[1], emissive.array[2]); material->setShininess(glossiness); + // Create shader for the material + // vertex + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + HighLevelGpuProgramPtr vertex; + if (mgr.getByName("main_vp").isNull()) + { + vertex = mgr.createProgram("main_vp", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_VERTEX_PROGRAM); + vertex->setParameter("profiles", "vs_4_0 vs_2_x vp40 arbvp1"); + vertex->setParameter("entry_point", "main_vp"); + StringUtil::StrStreamType outStream; + outStream << + "void main_vp( \n" + " float4 position : POSITION, \n" + " float4 normal : NORMAL, \n" + " float4 colour : COLOR, \n" + " in float2 uv : TEXCOORD0, \n" + " out float2 oUV : TEXCOORD0, \n" + " out float4 oPosition : POSITION, \n" + " out float4 oPositionObjSpace : TEXCOORD1, \n" + " out float4 oNormal : TEXCOORD2, \n" + " out float oFogValue : TEXCOORD3, \n" + " out float4 oVertexColour : TEXCOORD4, \n" + " uniform float4 fogParams, \n" + " uniform float4x4 worldViewProj \n" + ") \n" + "{ \n" + " oVertexColour = colour; \n" + " oUV = uv; \n" + " oNormal = normal; \n" + " oPosition = mul( worldViewProj, position ); \n" + " oFogValue = saturate((oPosition.z - fogParams.y) * fogParams.w); \n" + " oPositionObjSpace = position; \n" + "}"; + vertex->setSource(outStream.str()); + vertex->load(); + vertex->getDefaultParameters()->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); + vertex->getDefaultParameters()->setNamedAutoConstant("fogParams", GpuProgramParameters::ACT_FOG_PARAMS); + } + else + vertex = mgr.getByName("main_vp"); + material->getTechnique(0)->getPass(0)->setVertexProgram(vertex->getName()); + + // the number of lights to support. + // when rendering an object, OGRE automatically picks the lights that are + // closest to the object being rendered. unfortunately this mechanism does + // not work perfectly for objects batched together (they will all use the same + // lights). to work around this, we are simply pushing the maximum number + // of lights here in order to minimize disappearing lights. + float num_lights; + if (GpuProgramManager::getSingleton().isSyntaxSupported("fp40") || + GpuProgramManager::getSingleton().isSyntaxSupported("ps_4_0")) + num_lights = 16 /* 32 */; + else + num_lights = 8; + + // fragment + HighLevelGpuProgramPtr fragment; + if (mgr.getByName("main_fp").isNull()) + { + fragment = mgr.createProgram("main_fp", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_FRAGMENT_PROGRAM); + fragment->setParameter("profiles", "ps_4_0 ps_2_x fp40 arbfp1"); + fragment->setParameter("entry_point", "main_fp"); + StringUtil::StrStreamType outStream; + outStream << + "void main_fp( \n" + " in float2 uv : TEXCOORD0, \n" + " out float4 oColor : COLOR, \n" + " uniform sampler2D texture : TEXUNIT0, \n" + " float4 positionObjSpace : TEXCOORD1, \n" + " float4 normal : TEXCOORD2, \n" + " float fogValue : TEXCOORD3, \n" + " float4 vertexColour : TEXCOORD4, \n" + " uniform float4 fogColour, \n"; + + for (int i=0; isetSource(outStream.str()); + fragment->load(); + + for (int i=0; igetDefaultParameters()->setNamedAutoConstant("lightPositionObjSpace"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE, i); + fragment->getDefaultParameters()->setNamedAutoConstant("lightDiffuse"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, i); + fragment->getDefaultParameters()->setNamedAutoConstant("lightAttenuation"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_ATTENUATION, i); + } + fragment->getDefaultParameters()->setNamedAutoConstant("emissive", GpuProgramParameters::ACT_SURFACE_EMISSIVE_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("diffuse", GpuProgramParameters::ACT_SURFACE_DIFFUSE_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("ambient", GpuProgramParameters::ACT_SURFACE_AMBIENT_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("lightAmbient", GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); + fragment->getDefaultParameters()->setNamedAutoConstant("fogColour", GpuProgramParameters::ACT_FOG_COLOUR); + } + else + fragment = mgr.getByName("main_fp"); + material->getTechnique(0)->getPass(0)->setFragmentProgram(fragment->getName()); } // Takes a name and adds a unique part to it. This is just used to From b2109a13028513bf4f81759b5cc880fd687f925d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 4 Mar 2012 22:01:02 +0100 Subject: [PATCH 002/152] make the vertex colours only affect diffuse, this caused some areas to be darker than they should be --- components/nifogre/ogre_nif_loader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 7ffc9561f..3e11eda66 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -407,8 +407,8 @@ void NIFLoader::createMaterial(const String &name, } outStream << - " float3 lightingFinal = lightColour.xyz * diffuse.xyz + ambient.xyz * lightAmbient.xyz + emissive.xyz; \n" - " oColor.xyz = lerp(lightingFinal * tex.xyz * vertexColour.xyz, fogColour, fogValue); \n" + " float3 lightingFinal = lightColour.xyz * diffuse.xyz * vertexColour.xyz + ambient.xyz * lightAmbient.xyz + emissive.xyz; \n" + " oColor.xyz = lerp(lightingFinal * tex.xyz, fogColour, fogValue); \n" " oColor.a = tex.a * diffuse.a * vertexColour.a; \n" "}"; fragment->setSource(outStream.str()); From 332671b43d15f6859c4aebb3dda4399f7f678fc8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 4 Mar 2012 22:26:40 +0100 Subject: [PATCH 003/152] lighting fix --- components/nifogre/ogre_nif_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 3e11eda66..91ac3918e 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -396,7 +396,7 @@ void NIFLoader::createMaterial(const String &name, for (int i=0; i Date: Sun, 4 Mar 2012 23:18:40 +0100 Subject: [PATCH 004/152] fix batch lighting, removed the "bumping number of lights" hack --- apps/openmw/mwrender/objects.cpp | 9 ++++++++- components/nifogre/ogre_nif_loader.cpp | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 4e2a3caab..896d78385 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -101,6 +101,14 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) sg = mRenderer.getScene()->createStaticGeometry( "sg" + Ogre::StringConverter::toString(uniqueID)); //Create the scenenode and put it in the map mStaticGeometry[ptr.getCell()] = sg; + + // This specifies the size of a single batch region. + // If it is set too high: + // - there will be problems choosing the correct lights + // - the culling will be more inefficient + // If it is set too low: + // - there will be too many batches. + sg->setRegionDimensions(Ogre::Vector3(1000,1000,1000)); } else { @@ -108,7 +116,6 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) } sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); - sg->setRegionDimensions(Ogre::Vector3(100000,10000,100000)); mRenderer.getScene()->destroyEntity(ent); } diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 91ac3918e..baf33d51f 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -350,7 +350,7 @@ void NIFLoader::createMaterial(const String &name, float num_lights; if (GpuProgramManager::getSingleton().isSyntaxSupported("fp40") || GpuProgramManager::getSingleton().isSyntaxSupported("ps_4_0")) - num_lights = 16 /* 32 */; + num_lights = 8 /* 32 */; else num_lights = 8; From 5b38b17baf090976496825446dbe9bbd6a20252e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 10 Mar 2012 15:28:18 +0100 Subject: [PATCH 005/152] local map rendering (nothing to see yet, as it is not displayed in GUI) --- apps/openmw/CMakeLists.txt | 3 +- apps/openmw/mwrender/localmap.cpp | 160 ++++++++++++++++++++++ apps/openmw/mwrender/localmap.hpp | 48 +++++++ apps/openmw/mwrender/objects.cpp | 12 ++ apps/openmw/mwrender/objects.hpp | 4 + apps/openmw/mwrender/renderingmanager.cpp | 11 ++ apps/openmw/mwrender/renderingmanager.hpp | 6 + apps/openmw/mwworld/scene.cpp | 3 +- 8 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 apps/openmw/mwrender/localmap.cpp create mode 100644 apps/openmw/mwrender/localmap.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 39cd99cf6..2af808e9a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -14,7 +14,8 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - renderingmanager debugging sky player animation npcanimation creatureanimation actors objects renderinginterface + renderingmanager debugging sky player animation npcanimation creatureanimation actors objects + renderinginterface localmap ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp new file mode 100644 index 000000000..3e3ff77db --- /dev/null +++ b/apps/openmw/mwrender/localmap.cpp @@ -0,0 +1,160 @@ +#include "localmap.hpp" +#include "renderingmanager.hpp" + +#include +#include + +#include + +using namespace MWRender; +using namespace Ogre; + +#define CACHE_EXTENSION ".jpg" + +#define MAP_RESOLUTION 1024 // 1024*1024 pixels for a 8192*8192 area in world units + +LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) +{ + mRendering = rend; + + mCellCamera = mRendering->getScene()->createCamera("CellCamera"); + mCellCamera->setProjectionType(PT_ORTHOGRAPHIC); + // look down -y + const float sqrt0pt5 = 0.707106781; + mCellCamera->setOrientation(Quaternion(sqrt0pt5, -sqrt0pt5, 0, 0)); + + // Debug overlay to view the maps + /* + render(0, 0, 10000, 10000, 8192, 8192, "Cell_0_0"); + + MaterialPtr mat = MaterialManager::getSingleton().create("testMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0"); + + OverlayManager& ovm = OverlayManager::getSingleton(); + + Overlay* mOverlay = ovm.create( "testOverlay" ); + + OverlayContainer* overlay_panel; + overlay_panel = (OverlayContainer*)ovm.createOverlayElement("Panel", "testPanel"); + + overlay_panel->_setPosition(0, 0); + overlay_panel->_setDimensions(0.5, 0.5); + + overlay_panel->setMaterialName( "testMaterial" ); + overlay_panel->show(); + mOverlay->add2D(overlay_panel); + mOverlay->show(); + */ +} + +void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) +{ + std::string name = "Cell_" + StringConverter::toString(cell->cell->data.gridX) + + "_" + StringConverter::toString(cell->cell->data.gridY); + + const int x = cell->cell->data.gridX; + const int y = cell->cell->data.gridY; + + render((x+0.5)*8192, (-y-0.5)*8192, -10000, 10000, 8192, 8192, name); +} + +void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, + AxisAlignedBox bounds) +{ + Vector2 z(bounds.getMaximum().y, bounds.getMinimum().y); + Vector2 min(bounds.getMinimum().x, bounds.getMinimum().z); + Vector2 max(bounds.getMaximum().x, bounds.getMaximum().z); + + /// \todo why is this workaround needed? + min *= 1.3; + max *= 1.3; + + Vector2 length = max-min; + Vector2 center(bounds.getCenter().x, bounds.getCenter().z); + + // divide into 8192*8192 segments + const int segsX = std::ceil( length.x / 8192 ); + const int segsY = std::ceil( length.y / 8192 ); + + for (int x=0; xcell->name + "_" + StringConverter::toString(x) + "_" + StringConverter::toString(y)); + } + } +} + +void LocalMap::render(const float x, const float y, + const float zlow, const float zhigh, + const float xw, const float yw, const std::string& texture) +{ + // disable fog + // changing FOG_MODE is not a solution when using shaders, thus we have to push linear start/end + const float fStart = mRendering->getScene()->getFogStart(); + const float fEnd = mRendering->getScene()->getFogEnd(); + const ColourValue& clr = mRendering->getScene()->getFogColour(); + mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, 1000000, 10000000); + + // make everything visible + mRendering->getScene()->setAmbientLight(ColourValue(1,1,1)); + + mCellCamera->setPosition(Vector3(x, zhigh, y)); + mCellCamera->setFarClipDistance( (zhigh-zlow) * 1.1 ); + + mCellCamera->setOrthoWindow(xw, yw); + + TexturePtr tex; + // try loading from memory + tex = TextureManager::getSingleton().getByName(texture); + if (tex.isNull()) + { + // try loading from disk + //if (boost::filesystem::exists(texture+CACHE_EXTENSION)) + //{ + /// \todo + //} + //else + { + // render + tex = TextureManager::getSingleton().createManual( + texture, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + xw*MAP_RESOLUTION/8192, yw*MAP_RESOLUTION/8192, + 0, + PF_R8G8B8, + TU_RENDERTARGET); + + RenderTarget* rtt = tex->getBuffer()->getRenderTarget(); + rtt->setAutoUpdated(false); + Viewport* vp = rtt->addViewport(mCellCamera); + vp->setOverlaysEnabled(false); + vp->setShadowsEnabled(false); + vp->setBackgroundColour(ColourValue(0, 0, 0)); + //vp->setVisibilityMask( ... ); + + rtt->update(); + + /// \todo + // save to cache for next time + //rtt->writeContentsToFile("./" + texture + CACHE_EXTENSION); + } + } + + + /* + if (!MaterialManager::getSingleton().getByName("testMaterial").isNull()) + { + MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial"); + mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(texture); + } + */ + + // re-enable fog + mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, fStart, fEnd); +} diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp new file mode 100644 index 000000000..ac62031f4 --- /dev/null +++ b/apps/openmw/mwrender/localmap.hpp @@ -0,0 +1,48 @@ +#ifndef _GAME_RENDER_LOCALMAP_H +#define _GAME_RENDER_LOCALMAP_H + +#include "../mwworld/ptr.hpp" + +#include + +namespace MWRender +{ + /// + /// \brief Local map rendering + /// + class LocalMap + { + public: + LocalMap(OEngine::Render::OgreRenderer*); + + /** + * Request the local map for an exterior cell. + * It will either be loaded from a disk cache, + * or rendered if it is not already cached. + * @param exterior cell + */ + void requestMap (MWWorld::Ptr::CellStore* cell); + + /** + * Request the local map for an interior cell. + * It will either be loaded from a disk cache, + * or rendered if it is not already cached. + * @param interior cell + * @param bounding box of the cell + */ + void requestMap (MWWorld::Ptr::CellStore* cell, + Ogre::AxisAlignedBox bounds); + + private: + OEngine::Render::OgreRenderer* mRendering; + + Ogre::Camera* mCellCamera; + + void render(const float x, const float y, + const float zlow, const float zhigh, + const float xw, const float yw, + const std::string& texture); + }; + +} +#endif diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 717064ada..e4e721227 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -109,6 +109,9 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) // If it is set too low: // - there will be too many batches. sg->setRegionDimensions(Ogre::Vector3(2500,2500,2500)); + + mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; + mBounds[ptr.getCell()].merge(ent->getBoundingBox()); } else { @@ -116,6 +119,7 @@ void Objects::insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh) } sg->addEntity(ent,insert->_getDerivedPosition(),insert->_getDerivedOrientation(),insert->_getDerivedScale()); + mBounds[ptr.getCell()].merge(insert->_getDerivedPosition()); mRenderer.getScene()->destroyEntity(ent); } @@ -202,6 +206,9 @@ void Objects::removeCell(MWWorld::Ptr::CellStore* store) mRenderer.getScene()->destroyStaticGeometry (sg); sg = 0; } + + if(mBounds.find(store) != mBounds.end()) + mBounds.erase(store); } void Objects::buildStaticGeometry(ESMS::CellStore& cell) @@ -212,3 +219,8 @@ void Objects::buildStaticGeometry(ESMS::CellStore& cell) sg->build(); } } + +Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell) +{ + return mBounds[cell]; +} diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index d58455b9f..1ca81331d 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -14,6 +14,7 @@ class Objects{ OEngine::Render::OgreRenderer &mRenderer; std::map mCellSceneNodes; std::map mStaticGeometry; + std::map mBounds; Ogre::SceneNode* mMwRoot; bool mIsStatic; static int uniqueID; @@ -42,6 +43,9 @@ public: void insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh); void insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, float radius); + Ogre::AxisAlignedBox getDimensions(MWWorld::Ptr::CellStore*); + ///< get a bounding box that encloses all objects in the specified cell + bool deleteObject (const MWWorld::Ptr& ptr); ///< \return found? diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7b58a80d7..41f4e72d6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -55,6 +55,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode); mSun = 0; + + mLocalMap = new MWRender::LocalMap(&mRendering); } RenderingManager::~RenderingManager () @@ -62,6 +64,7 @@ RenderingManager::~RenderingManager () //TODO: destroy mSun? delete mPlayer; delete mSkyManager; + delete mLocalMap; } MWRender::SkyManager* RenderingManager::getSkyManager() @@ -322,4 +325,12 @@ void RenderingManager::setGlare(bool glare) mSkyManager->setGlare(glare); } +void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) +{ + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + mLocalMap->requestMap(cell); + else + mLocalMap->requestMap(cell, mObjects.getDimensions(cell)); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index d84ee43e0..65aa46b01 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -24,6 +24,7 @@ #include "objects.hpp" #include "actors.hpp" #include "player.hpp" +#include "localmap.hpp" namespace Ogre { @@ -102,6 +103,9 @@ class RenderingManager: private RenderingInterface { int skyGetSecundaPhase() const; void skySetMoonColour (bool red); void configureAmbient(ESMS::CellStore &mCell); + + void requestMap (MWWorld::Ptr::CellStore* cell); + ///< request the local map for a cell /// configure fog according to cell void configureFog(ESMS::CellStore &mCell); @@ -148,6 +152,8 @@ class RenderingManager: private RenderingInterface { MWRender::Player *mPlayer; MWRender::Debugging mDebugging; + + MWRender::LocalMap* mLocalMap; }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 47d5f1a2d..e7a3f2d5d 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -101,7 +101,8 @@ namespace MWWorld insertCell(*cell, mEnvironment); mRendering.cellAdded (cell); mRendering.configureAmbient(*cell); - + mRendering.requestMap(cell); + mRendering.configureAmbient(*cell); } From fa68be2b190919eb43d0219d849bbcbd1280ff7b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 10 Mar 2012 16:05:12 +0100 Subject: [PATCH 006/152] set map window cell name --- apps/openmw/mwgui/window_manager.cpp | 5 +++++ apps/openmw/mwgui/window_manager.hpp | 2 ++ apps/openmw/mwworld/scene.cpp | 13 +++++++++++++ 3 files changed, 20 insertions(+) diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index aca9fbd9a..347db09e2 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -401,3 +401,8 @@ const ESMS::ESMStore& WindowManager::getStore() const { return environment.mWorld->getStore(); } + +void WindowManager::setCellName(const std::string& cellName) +{ + map->setCellName(cellName); +} diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 89ff4b9bb..258ab7993 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -151,6 +151,8 @@ namespace MWGui void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty + void setCellName(const std::string& cellName); ///< set the cell name to display in the map window + template void removeDialog(T*& dialog); ///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr. void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted. diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index e7a3f2d5d..a63f6d150 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -6,6 +6,8 @@ #include "../mwsound/soundmanager.hpp" +#include "../mwgui/window_manager.hpp" + #include "ptr.hpp" #include "environment.hpp" #include "player.hpp" @@ -118,6 +120,17 @@ namespace MWWorld // TODO orientation mEnvironment.mMechanicsManager->addActor (mWorld->getPlayer().getPlayer()); mEnvironment.mMechanicsManager->watchActor (mWorld->getPlayer().getPlayer()); + + // set map window cell name + if (!(mCurrentCell->cell->data.flags & ESM::Cell::Interior)) + { + if (mCurrentCell->cell->name != "") + mEnvironment.mWindowManager->setCellName( mCurrentCell->cell->name ); + else + mEnvironment.mWindowManager->setCellName( mCurrentCell->cell->region ); + } + else + mEnvironment.mWindowManager->setCellName( mCurrentCell->cell->name ); } void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) From 583a25f63424f2da8da0f771cd7c751657cc4f28 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 14 Mar 2012 14:51:58 +0100 Subject: [PATCH 007/152] fog of war rendering (incomplete) --- apps/openmw/mwrender/localmap.cpp | 96 +++++++++++++++++++++-- apps/openmw/mwrender/localmap.hpp | 19 ++++- apps/openmw/mwrender/renderingmanager.cpp | 2 + 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 3e3ff77db..8c56bb511 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -12,6 +12,7 @@ using namespace Ogre; #define CACHE_EXTENSION ".jpg" #define MAP_RESOLUTION 1024 // 1024*1024 pixels for a 8192*8192 area in world units +#define FOGOFWAR_RESOLUTION 1024 LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) { @@ -24,11 +25,14 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) mCellCamera->setOrientation(Quaternion(sqrt0pt5, -sqrt0pt5, 0, 0)); // Debug overlay to view the maps - /* + render(0, 0, 10000, 10000, 8192, 8192, "Cell_0_0"); MaterialPtr mat = MaterialManager::getSingleton().create("testMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0"); + mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + mat->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); + mat->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); OverlayManager& ovm = OverlayManager::getSingleton(); @@ -44,23 +48,27 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) overlay_panel->show(); mOverlay->add2D(overlay_panel); mOverlay->show(); - */ + } void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) { + mInterior = false; + std::string name = "Cell_" + StringConverter::toString(cell->cell->data.gridX) + "_" + StringConverter::toString(cell->cell->data.gridY); const int x = cell->cell->data.gridX; const int y = cell->cell->data.gridY; - + render((x+0.5)*8192, (-y-0.5)*8192, -10000, 10000, 8192, 8192, name); } void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, AxisAlignedBox bounds) { + mInterior = true; + Vector2 z(bounds.getMaximum().y, bounds.getMinimum().y); Vector2 min(bounds.getMinimum().x, bounds.getMinimum().z); Vector2 max(bounds.getMaximum().x, bounds.getMaximum().z); @@ -68,7 +76,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, /// \todo why is this workaround needed? min *= 1.3; max *= 1.3; - + Vector2 length = max-min; Vector2 center(bounds.getCenter().x, bounds.getCenter().z); @@ -102,7 +110,7 @@ void LocalMap::render(const float x, const float y, // make everything visible mRendering->getScene()->setAmbientLight(ColourValue(1,1,1)); - + mCellCamera->setPosition(Vector3(x, zhigh, y)); mCellCamera->setFarClipDistance( (zhigh-zlow) * 1.1 ); @@ -140,6 +148,16 @@ void LocalMap::render(const float x, const float y, rtt->update(); + // create "fog of war" texture + TexturePtr tex2 = TextureManager::getSingleton().createManual( + texture + "_fog", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + xw*FOGOFWAR_RESOLUTION/8192, yw*FOGOFWAR_RESOLUTION/8192, + 0, + PF_A8R8G8B8, + TU_DYNAMIC); + /// \todo // save to cache for next time //rtt->writeContentsToFile("./" + texture + CACHE_EXTENSION); @@ -158,3 +176,71 @@ void LocalMap::render(const float x, const float y, // re-enable fog mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, fStart, fEnd); } + +void LocalMap::setPlayerPosition (const Ogre::Vector3& position) +{ + // retrieve the x,y grid coordinates the player is in + int x,y; + Vector2 pos(position.x, -position.z); + if (!mInterior) + { + x = std::ceil(pos.x / 8192.f); + y = std::ceil(pos.y / 8192.f); + } + else + { + /// \todo + } + + // convert from world coordinates to texture UV coordinates + float u,v; + std::string texName; + if (!mInterior) + { + u = std::abs((pos.x - (8192*x))/8192.f); + v = std::abs((pos.y - (8192*y))/8192.f); + texName = "Cell_" + StringConverter::toString(x) + "_" + + StringConverter::toString(y) + "_fog"; + } + else + { + /// \todo + } + + //std::cout << "u " << u<< " v " << v << std::endl; + + // explore radius (squared) + const float sqrExploreRadius = 0.001 * FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION; + + // get the appropriate fog of war texture + TexturePtr tex = TextureManager::getSingleton().getByName(texName); + HardwarePixelBufferSharedPtr buffer = tex->getBuffer(); + + /*void* data = buffer->lock(HardwareBuffer::HBL_NORMAL); + + for (int texU = 0; texU> 24); + uint8 r=0; + uint8 g=0; + uint8 b=0; + alpha = std::max( alpha, (uint8) (std::max(0.f, std::min(1.f, 1.f-(sqrDist/sqrExploreRadius)))*255) ); + *((uint32*)data) = (r) + (g<<8) + (b<<16) + (alpha << 24); + + // move to next texel + data = static_cast (data) + sizeof(uint32); + } + } + + buffer->unlock();*/ + + if (!MaterialManager::getSingleton().getByName("testMaterial").isNull()) + { + MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial"); + mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(tex->getName()); + } +} diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index ac62031f4..afce94c8d 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -17,7 +17,7 @@ namespace MWRender /** * Request the local map for an exterior cell. - * It will either be loaded from a disk cache, + * @remarks It will either be loaded from a disk cache, * or rendered if it is not already cached. * @param exterior cell */ @@ -25,7 +25,7 @@ namespace MWRender /** * Request the local map for an interior cell. - * It will either be loaded from a disk cache, + * @remarks It will either be loaded from a disk cache, * or rendered if it is not already cached. * @param interior cell * @param bounding box of the cell @@ -33,6 +33,14 @@ namespace MWRender void requestMap (MWWorld::Ptr::CellStore* cell, Ogre::AxisAlignedBox bounds); + /** + * Set the position of the player. + * @remarks This is used to draw a "fog of war" effect + * to hide areas on the map the player has not discovered yet. + * @param position (OGRE coordinates) + */ + void setPlayerPosition (const Ogre::Vector3& position); + private: OEngine::Render::OgreRenderer* mRendering; @@ -42,6 +50,13 @@ namespace MWRender const float zlow, const float zhigh, const float xw, const float yw, const std::string& texture); + + bool mInterior; + + // a buffer for the "fog of war" texture of the current cell. + // interior cells could be divided into multiple textures, + // so we store in a map. + std::map mBuffer; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 41f4e72d6..bddf575d9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -140,6 +140,8 @@ void RenderingManager::update (float duration){ mSkyManager->update(duration); mRendering.update(duration); + + mLocalMap->setPlayerPosition( mRendering.getCamera()->getRealPosition() ); } void RenderingManager::skyEnable () From 5a46d58da5912be269ef7a07cc027bee2f2cda57 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 14 Mar 2012 17:44:19 +0100 Subject: [PATCH 008/152] fully working fog of war --- apps/openmw/mwrender/localmap.cpp | 157 +++++++++++++++++++++++------- apps/openmw/mwrender/localmap.hpp | 11 ++- 2 files changed, 129 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 8c56bb511..496a162dd 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -12,7 +12,13 @@ using namespace Ogre; #define CACHE_EXTENSION ".jpg" #define MAP_RESOLUTION 1024 // 1024*1024 pixels for a 8192*8192 area in world units -#define FOGOFWAR_RESOLUTION 1024 + +// warning: don't set this too high! dynamic textures are a bottleneck +#define FOGOFWAR_RESOLUTION 32 + +// how many frames to skip before rendering the fog of war. +// example: at 60 fps, a value of 2 would mean to render it at 20 fps. +#define FOGOFWAR_SKIP 2 LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) { @@ -25,7 +31,7 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) mCellCamera->setOrientation(Quaternion(sqrt0pt5, -sqrt0pt5, 0, 0)); // Debug overlay to view the maps - + render(0, 0, 10000, 10000, 8192, 8192, "Cell_0_0"); MaterialPtr mat = MaterialManager::getSingleton().create("testMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); @@ -33,11 +39,17 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); mat->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mat->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + + mat = MaterialManager::getSingleton().create("testMaterial2", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0"); + mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); + mat->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); + mat->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); OverlayManager& ovm = OverlayManager::getSingleton(); Overlay* mOverlay = ovm.create( "testOverlay" ); - + mOverlay->setZOrder(0); OverlayContainer* overlay_panel; overlay_panel = (OverlayContainer*)ovm.createOverlayElement("Panel", "testPanel"); @@ -48,11 +60,42 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) overlay_panel->show(); mOverlay->add2D(overlay_panel); mOverlay->show(); + + Overlay* mOverlay2 = ovm.create( "testOverlay2" ); + mOverlay2->setZOrder(1); + OverlayContainer* overlay_panel2; + overlay_panel2 = (OverlayContainer*)ovm.createOverlayElement("Panel", "testPanel2"); + overlay_panel2->_setPosition(0, 0); + overlay_panel2->_setDimensions(0.5, 0.5); + + overlay_panel2->setMaterialName( "testMaterial2" ); + overlay_panel2->show(); + mOverlay2->add2D(overlay_panel2); + + mOverlay2->show(); + +} + +LocalMap::~LocalMap() +{ + deleteBuffers(); +} + +void LocalMap::deleteBuffers() +{ + for (std::map::iterator it=mBuffers.begin(); + it != mBuffers.end(); ++it) + { + delete it->second; + } + mBuffers.clear(); } void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) { + deleteBuffers(); + mInterior = false; std::string name = "Cell_" + StringConverter::toString(cell->cell->data.gridX) @@ -67,7 +110,10 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, AxisAlignedBox bounds) { + deleteBuffers(); + mInterior = true; + mBounds = bounds; Vector2 z(bounds.getMaximum().y, bounds.getMinimum().y); Vector2 min(bounds.getMinimum().x, bounds.getMinimum().z); @@ -81,9 +127,12 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, Vector2 center(bounds.getCenter().x, bounds.getCenter().z); // divide into 8192*8192 segments + /// \todo interiors with more than 1 segment are untested const int segsX = std::ceil( length.x / 8192 ); const int segsY = std::ceil( length.y / 8192 ); + mInteriorName = cell->cell->name; + for (int x=0; xgetBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION*4); + tex2->getBuffer()->unlock(); + + mBuffers[texture] = buffer; /// \todo // save to cache for next time @@ -165,13 +229,13 @@ void LocalMap::render(const float x, const float y, } - /* + if (!MaterialManager::getSingleton().getByName("testMaterial").isNull()) { MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial"); mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(texture); } - */ + // re-enable fog mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, fStart, fEnd); @@ -179,17 +243,28 @@ void LocalMap::render(const float x, const float y, void LocalMap::setPlayerPosition (const Ogre::Vector3& position) { + #if FOGOFWAR_SKIP != 0 + static int count=0; + if (++count % FOGOFWAR_SKIP != 0) + return; + #endif + // retrieve the x,y grid coordinates the player is in int x,y; - Vector2 pos(position.x, -position.z); + Vector2 pos(position.x, position.z); if (!mInterior) { - x = std::ceil(pos.x / 8192.f); - y = std::ceil(pos.y / 8192.f); + x = (int) (pos.x / 8192.f); + y = (int) (pos.y / 8192.f); } else { - /// \todo + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); + min *= 1.3; + + /// \todo interiors with more than 1 segment are untested + x = (int) ((pos.x - min.x)/8192.f); + y = (int) ((pos.y - min.y)/8192.f); } // convert from world coordinates to texture UV coordinates @@ -200,47 +275,57 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) u = std::abs((pos.x - (8192*x))/8192.f); v = std::abs((pos.y - (8192*y))/8192.f); texName = "Cell_" + StringConverter::toString(x) + "_" - + StringConverter::toString(y) + "_fog"; + + StringConverter::toString(y); } else { - /// \todo + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); + min *= 1.3; + + u = (pos.x - min.x - 8192*x)/8192.f; + v = (pos.y - min.y - 8192*y)/8192.f; + + texName = mInteriorName + "_" + StringConverter::toString(x) + "_" + + StringConverter::toString(y); } //std::cout << "u " << u<< " v " << v << std::endl; // explore radius (squared) - const float sqrExploreRadius = 0.001 * FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION; + const float sqrExploreRadius = 0.01 * FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION; // get the appropriate fog of war texture - TexturePtr tex = TextureManager::getSingleton().getByName(texName); - HardwarePixelBufferSharedPtr buffer = tex->getBuffer(); - - /*void* data = buffer->lock(HardwareBuffer::HBL_NORMAL); - - for (int texU = 0; texU> 24); - uint8 r=0; - uint8 g=0; - uint8 b=0; - alpha = std::max( alpha, (uint8) (std::max(0.f, std::min(1.f, 1.f-(sqrDist/sqrExploreRadius)))*255) ); - *((uint32*)data) = (r) + (g<<8) + (b<<16) + (alpha << 24); - - // move to next texel - data = static_cast (data) + sizeof(uint32); + for (int texU = 0; texU> 24); + alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); + *((uint32*)pointer) = (alpha << 24); + + // move to next texel + ++pointer; + } } - } - buffer->unlock();*/ + // copy to the texture + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION*4); + tex->getBuffer()->unlock(); - if (!MaterialManager::getSingleton().getByName("testMaterial").isNull()) - { - MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial"); - mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(tex->getName()); + if (!MaterialManager::getSingleton().getByName("testMaterial2").isNull()) + { + MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial2"); + mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(tex->getName()); + } + } } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index afce94c8d..588e76633 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -14,6 +14,7 @@ namespace MWRender { public: LocalMap(OEngine::Render::OgreRenderer*); + ~LocalMap(); /** * Request the local map for an exterior cell. @@ -51,12 +52,16 @@ namespace MWRender const float xw, const float yw, const std::string& texture); - bool mInterior; - // a buffer for the "fog of war" texture of the current cell. // interior cells could be divided into multiple textures, // so we store in a map. - std::map mBuffer; + std::map mBuffers; + + void deleteBuffers(); + + bool mInterior; + Ogre::AxisAlignedBox mBounds; + std::string mInteriorName; }; } From 2edd7e59f407fa21f165fc44b698d3e8dc720837 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 14 Mar 2012 20:44:06 +0100 Subject: [PATCH 009/152] some fixes and facilities for saving the fog of war to disk --- apps/openmw/mwrender/localmap.cpp | 150 ++++++++++++++-------- apps/openmw/mwrender/localmap.hpp | 13 ++ apps/openmw/mwrender/renderingmanager.cpp | 5 + apps/openmw/mwrender/renderingmanager.hpp | 3 + apps/openmw/mwworld/scene.cpp | 2 + 5 files changed, 118 insertions(+), 55 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 496a162dd..fe9b3c191 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -4,14 +4,10 @@ #include #include -#include - using namespace MWRender; using namespace Ogre; -#define CACHE_EXTENSION ".jpg" - -#define MAP_RESOLUTION 1024 // 1024*1024 pixels for a 8192*8192 area in world units +#define MAP_RESOLUTION 1024 // 1024*1024 pixels for a SIZE*SIZE area in world units // warning: don't set this too high! dynamic textures are a bottleneck #define FOGOFWAR_RESOLUTION 32 @@ -20,6 +16,9 @@ using namespace Ogre; // example: at 60 fps, a value of 2 would mean to render it at 20 fps. #define FOGOFWAR_SKIP 2 +// size of a map segment (for exterior regions, this equals 1 cell) +#define SIZE 8192.f + LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) { mRendering = rend; @@ -32,16 +31,16 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) // Debug overlay to view the maps - render(0, 0, 10000, 10000, 8192, 8192, "Cell_0_0"); + render(0, 0, 10000, 10000, SIZE, SIZE, "Cell_0_0_"); MaterialPtr mat = MaterialManager::getSingleton().create("testMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0"); + mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0_"); mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); mat->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mat->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); mat = MaterialManager::getSingleton().create("testMaterial2", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0"); + mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0_"); mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); mat->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mat->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); @@ -92,26 +91,75 @@ void LocalMap::deleteBuffers() mBuffers.clear(); } -void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) +void LocalMap::saveTexture(const std::string& texname, const std::string& filename) { - deleteBuffers(); + TexturePtr tex = TextureManager::getSingleton().getByName(texname); + if (tex.isNull()) return; + HardwarePixelBufferSharedPtr readbuffer = tex->getBuffer(); + readbuffer->lock(HardwareBuffer::HBL_NORMAL ); + const PixelBox &readrefpb = readbuffer->getCurrentLock(); + uchar *readrefdata = static_cast(readrefpb.data); + + Image img; + img = img.loadDynamicImage (readrefdata, tex->getWidth(), + tex->getHeight(), tex->getFormat()); + img.save("./" + filename); + + readbuffer->unlock(); +} +std::string LocalMap::coordStr(const int x, const int y) +{ + return StringConverter::toString(x) + "_" + StringConverter::toString(y); +} + +void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) +{ + if (!mInterior) + { + /*saveTexture("Cell_"+coordStr(mCellX, mCellY)+"_fog", + "Cell_"+coordStr(mCellX, mCellY)+"_fog.png");*/ + } + else + { + Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); + Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().z); + /// \todo why is this workaround needed? + min *= 1.3; + max *= 1.3; + Vector2 length = max-min; + + // divide into segments + const int segsX = std::ceil( length.x / SIZE ); + const int segsY = std::ceil( length.y / SIZE ); + + for (int x=0; xcell->data.gridX) - + "_" + StringConverter::toString(cell->cell->data.gridY); + std::string name = "Cell_"+coordStr(cell->cell->data.gridX, cell->cell->data.gridY); - const int x = cell->cell->data.gridX; - const int y = cell->cell->data.gridY; + int x = cell->cell->data.gridX; + int y = cell->cell->data.gridY; - render((x+0.5)*8192, (-y-0.5)*8192, -10000, 10000, 8192, 8192, name); + render((x+0.5)*SIZE, (-y-0.5)*SIZE, -10000, 10000, SIZE, SIZE, name); } void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, AxisAlignedBox bounds) { - deleteBuffers(); - mInterior = true; mBounds = bounds; @@ -126,10 +174,9 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, Vector2 length = max-min; Vector2 center(bounds.getCenter().x, bounds.getCenter().z); - // divide into 8192*8192 segments - /// \todo interiors with more than 1 segment are untested - const int segsX = std::ceil( length.x / 8192 ); - const int segsY = std::ceil( length.y / 8192 ); + // divide into segments + const int segsX = std::ceil( length.x / SIZE ); + const int segsY = std::ceil( length.y / SIZE ); mInteriorName = cell->cell->name; @@ -137,11 +184,11 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, { for (int y=0; ycell->name + "_" + StringConverter::toString(x) + "_" + StringConverter::toString(y)); + render(newcenter.x, newcenter.y, z.y, z.x, SIZE, SIZE, + cell->cell->name + "_" + coordStr(x,y)); } } } @@ -171,7 +218,7 @@ void LocalMap::render(const float x, const float y, if (tex.isNull()) { // try loading from disk - //if (boost::filesystem::exists(texture+CACHE_EXTENSION)) + //if (boost::filesystem::exists(texture+".jpg")) //{ /// \todo //} @@ -182,7 +229,7 @@ void LocalMap::render(const float x, const float y, texture, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, - xw*MAP_RESOLUTION/8192, yw*MAP_RESOLUTION/8192, + xw*MAP_RESOLUTION/SIZE, yw*MAP_RESOLUTION/SIZE, 0, PF_R8G8B8, TU_RENDERTARGET); @@ -202,7 +249,7 @@ void LocalMap::render(const float x, const float y, texture + "_fog", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, - xw*FOGOFWAR_RESOLUTION/8192, yw*FOGOFWAR_RESOLUTION/8192, + xw*FOGOFWAR_RESOLUTION/SIZE, yw*FOGOFWAR_RESOLUTION/SIZE, 0, PF_A8R8G8B8, TU_DYNAMIC_WRITE_ONLY); @@ -221,20 +268,11 @@ void LocalMap::render(const float x, const float y, tex2->getBuffer()->unlock(); mBuffers[texture] = buffer; - - /// \todo + // save to cache for next time - //rtt->writeContentsToFile("./" + texture + CACHE_EXTENSION); + //rtt->writeContentsToFile("./" + texture + ".jpg"); } } - - - - if (!MaterialManager::getSingleton().getByName("testMaterial").isNull()) - { - MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial"); - mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(texture); - } // re-enable fog @@ -254,17 +292,18 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) Vector2 pos(position.x, position.z); if (!mInterior) { - x = (int) (pos.x / 8192.f); - y = (int) (pos.y / 8192.f); + x = std::ceil(pos.x / SIZE)-1; + y = std::ceil(-pos.y / SIZE)-1; + mCellX = x; + mCellY = y; } else { Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); min *= 1.3; - /// \todo interiors with more than 1 segment are untested - x = (int) ((pos.x - min.x)/8192.f); - y = (int) ((pos.y - min.y)/8192.f); + x = std::ceil((pos.x - min.x)/SIZE)-1; + y = std::ceil((pos.y - min.y)/SIZE)-1; } // convert from world coordinates to texture UV coordinates @@ -272,34 +311,30 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) std::string texName; if (!mInterior) { - u = std::abs((pos.x - (8192*x))/8192.f); - v = std::abs((pos.y - (8192*y))/8192.f); - texName = "Cell_" + StringConverter::toString(x) + "_" - + StringConverter::toString(y); + u = std::abs((pos.x - (SIZE*x))/SIZE); + v = 1-std::abs((pos.y + (SIZE*y))/SIZE); + texName = "Cell_"+coordStr(x,y); } else { Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); min *= 1.3; - u = (pos.x - min.x - 8192*x)/8192.f; - v = (pos.y - min.y - 8192*y)/8192.f; + u = (pos.x - min.x - SIZE*x)/SIZE; + v = (pos.y - min.y - SIZE*y)/SIZE; - texName = mInteriorName + "_" + StringConverter::toString(x) + "_" - + StringConverter::toString(y); + texName = mInteriorName + "_" + coordStr(x,y); } - //std::cout << "u " << u<< " v " << v << std::endl; - // explore radius (squared) const float sqrExploreRadius = 0.01 * FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION; // get the appropriate fog of war texture TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); - if (!tex.isNull()) { // get its buffer + if (mBuffers.find(texName) == mBuffers.end()) return; uint32* buffer = mBuffers[texName]; uint32* pointer = buffer; for (int texV = 0; texV> 24); alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); *((uint32*)pointer) = (alpha << 24); @@ -321,6 +356,11 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION*4); tex->getBuffer()->unlock(); + if (!MaterialManager::getSingleton().getByName("testMaterial").isNull()) + { + MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial"); + mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(texName); + } if (!MaterialManager::getSingleton().getByName("testMaterial2").isNull()) { MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial2"); diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 588e76633..9e06200c1 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -42,6 +42,14 @@ namespace MWRender */ void setPlayerPosition (const Ogre::Vector3& position); + /** + * Save the fog of war for the current cell to disk. + * @remarks This should be called before loading a + * new cell, as well as when the game is quit. + * @param current cell + */ + void saveFogOfWar(MWWorld::Ptr::CellStore* cell); + private: OEngine::Render::OgreRenderer* mRendering; @@ -52,6 +60,10 @@ namespace MWRender const float xw, const float yw, const std::string& texture); + void saveTexture(const std::string& texname, const std::string& filename); + + std::string coordStr(const int x, const int y); + // a buffer for the "fog of war" texture of the current cell. // interior cells could be divided into multiple textures, // so we store in a map. @@ -60,6 +72,7 @@ namespace MWRender void deleteBuffers(); bool mInterior; + int mCellX, mCellY; Ogre::AxisAlignedBox mBounds; std::string mInteriorName; }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bddf575d9..a8887e64f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -335,4 +335,9 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) mLocalMap->requestMap(cell, mObjects.getDimensions(cell)); } +void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell) +{ + mLocalMap->saveFogOfWar(cell); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 65aa46b01..78a1d2fdb 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -76,6 +76,9 @@ class RenderingManager: private RenderingInterface { /// when rebatching is needed and update automatically at the end of each frame. void cellAdded (MWWorld::Ptr::CellStore *store); + void preCellChange (MWWorld::Ptr::CellStore* store); + ///< this event is fired immediately before changing cell + void addObject (const MWWorld::Ptr& ptr); void removeObject (const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index a63f6d150..84c10072a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -135,6 +135,8 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { + mRendering.preCellChange(mCurrentCell); + // remove active mEnvironment.mMechanicsManager->removeActor (mWorld->getPlayer().getPlayer()); From 91d2031eb7d54a6c0ffdf9c7a0cb666ef4999f33 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 16 Mar 2012 17:09:31 +0100 Subject: [PATCH 010/152] first attempt at map window --- apps/openmw/mwgui/layouts.hpp | 28 ++++++++++- apps/openmw/mwgui/window_manager.cpp | 18 ++++++- apps/openmw/mwgui/window_manager.hpp | 4 +- apps/openmw/mwrender/localmap.cpp | 6 +-- apps/openmw/mwworld/scene.cpp | 11 +---- .../openmw_map_window_layout.xml | 48 +++++++++++++++++-- 6 files changed, 94 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 9917dcdcc..7b7c84225 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -67,7 +67,7 @@ namespace MWGui { setCoord(500,0,320,300); setText("WorldButton", "World"); - setImage("Compass", "compass.dds"); + setImage("Compass", "textures\\compass.dds"); // Obviously you should override this later on setCellName("No Cell Loaded"); @@ -77,6 +77,32 @@ namespace MWGui { mMainWidget->setCaption(cellName); } + + // for interiors: cell name, for exteriors: "Cell" + void setCellPrefix(const std::string& prefix) + { + mPrefix = prefix; + } + + void setActiveCell(const int x, const int y) + { + for (int mx=0; mx<3; ++mx) + { + for (int my=0; my<3; ++my) + { + std::string name = "Map_" + boost::lexical_cast(mx) + "_" + + boost::lexical_cast(my); + + std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" + + boost::lexical_cast(y - (my-1)); + setImage(name, image); + setImage(name+"_fog", image+"_fog"); + } + } + } + + private: + std::string mPrefix; }; class MainMenu : public OEngine::GUI::Layout diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 347db09e2..014aa8108 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -402,7 +402,21 @@ const ESMS::ESMStore& WindowManager::getStore() const return environment.mWorld->getStore(); } -void WindowManager::setCellName(const std::string& cellName) +void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) { - map->setCellName(cellName); + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + { + if (cell->cell->name != "") + map->setCellName( cell->cell->name ); + else + map->setCellName( cell->cell->region ); + map->setCellPrefix("Cell"); + map->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY ); + } + else + { + map->setCellName( cell->cell->name ); + map->setCellPrefix( cell->cell->name ); + } + } diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 258ab7993..c867fedbe 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -18,6 +18,7 @@ #include #include #include "../mwmechanics/stat.hpp" +#include "../mwworld/ptr.hpp" #include "mode.hpp" namespace MyGUI @@ -150,8 +151,7 @@ namespace MWGui void setBounty (int bounty); ///< set the current bounty value void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty - - void setCellName(const std::string& cellName); ///< set the cell name to display in the map window + void changeCell(MWWorld::Ptr::CellStore* cell); ///< change the active cell template void removeDialog(T*& dialog); ///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr. diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index fe9b3c191..ee5413cab 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -58,7 +58,7 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) overlay_panel->setMaterialName( "testMaterial" ); overlay_panel->show(); mOverlay->add2D(overlay_panel); - mOverlay->show(); + //mOverlay->show(); Overlay* mOverlay2 = ovm.create( "testOverlay2" ); mOverlay2->setZOrder(1); @@ -72,7 +72,7 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) overlay_panel2->show(); mOverlay2->add2D(overlay_panel2); - mOverlay2->show(); + //mOverlay2->show(); } @@ -252,7 +252,7 @@ void LocalMap::render(const float x, const float y, xw*FOGOFWAR_RESOLUTION/SIZE, yw*FOGOFWAR_RESOLUTION/SIZE, 0, PF_A8R8G8B8, - TU_DYNAMIC_WRITE_ONLY); + TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); // create a buffer to use for dynamic operations uint32* buffer = new uint32[FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION]; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 84c10072a..22955bf32 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -121,16 +121,7 @@ namespace MWWorld mEnvironment.mMechanicsManager->addActor (mWorld->getPlayer().getPlayer()); mEnvironment.mMechanicsManager->watchActor (mWorld->getPlayer().getPlayer()); - // set map window cell name - if (!(mCurrentCell->cell->data.flags & ESM::Cell::Interior)) - { - if (mCurrentCell->cell->name != "") - mEnvironment.mWindowManager->setCellName( mCurrentCell->cell->name ); - else - mEnvironment.mWindowManager->setCellName( mCurrentCell->cell->region ); - } - else - mEnvironment.mWindowManager->setCellName( mCurrentCell->cell->name ); + mEnvironment.mWindowManager->changeCell( mCurrentCell ); } void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) diff --git a/extern/mygui_3.0.1/openmw_resources/openmw_map_window_layout.xml b/extern/mygui_3.0.1/openmw_resources/openmw_map_window_layout.xml index f4ff50f72..d4b1b5b0a 100644 --- a/extern/mygui_3.0.1/openmw_resources/openmw_map_window_layout.xml +++ b/extern/mygui_3.0.1/openmw_resources/openmw_map_window_layout.xml @@ -2,8 +2,50 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 42b445383f7b3193f12588142f4762ff1af6c72e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 16 Mar 2012 17:08:13 -0700 Subject: [PATCH 011/152] Remove Mangle- and OpenEngine-related sound code Start of the sound code refactoring. Currently there is no sound, but it will be added back. --- CMakeLists.txt | 19 +- apps/openmw/mwsound/soundmanager.cpp | 509 ++++++------------ apps/openmw/mwsound/soundmanager.hpp | 38 -- libs/mangle/sound/.gitignore | 1 - .../sound/clients/ogre_listener_mover.hpp | 79 --- .../sound/clients/ogre_output_updater.hpp | 31 -- libs/mangle/sound/filters/input_filter.hpp | 68 --- libs/mangle/sound/filters/openal_audiere.hpp | 24 - libs/mangle/sound/filters/openal_ffmpeg.hpp | 23 - libs/mangle/sound/filters/openal_mpg123.hpp | 24 - libs/mangle/sound/filters/openal_sndfile.hpp | 24 - .../sound/filters/openal_sndfile_mpg123.hpp | 33 -- libs/mangle/sound/filters/openal_various.hpp | 39 -- libs/mangle/sound/filters/pure_filter.hpp | 73 --- libs/mangle/sound/filters/source_splicer.hpp | 90 ---- libs/mangle/sound/output.hpp | 183 ------- libs/mangle/sound/outputs/openal_out.cpp | 500 ----------------- libs/mangle/sound/outputs/openal_out.hpp | 44 -- libs/mangle/sound/source.hpp | 62 --- libs/mangle/sound/sources/audiere_source.cpp | 77 --- libs/mangle/sound/sources/audiere_source.hpp | 48 -- libs/mangle/sound/sources/ffmpeg_source.cpp | 189 ------- libs/mangle/sound/sources/ffmpeg_source.hpp | 52 -- libs/mangle/sound/sources/libsndfile.cpp | 48 -- libs/mangle/sound/sources/libsndfile.hpp | 36 -- libs/mangle/sound/sources/loadertemplate.hpp | 28 - libs/mangle/sound/sources/mpg123_source.cpp | 115 ---- libs/mangle/sound/sources/mpg123_source.hpp | 47 -- libs/mangle/sound/sources/sample_reader.cpp | 99 ---- libs/mangle/sound/sources/sample_reader.hpp | 48 -- libs/mangle/sound/sources/stream_source.hpp | 47 -- libs/mangle/sound/sources/wav_source.cpp | 99 ---- libs/mangle/sound/sources/wav_source.hpp | 49 -- libs/mangle/sound/tests/.gitignore | 1 - libs/mangle/sound/tests/Makefile | 38 -- .../sound/tests/audiere_source_test.cpp | 68 --- libs/mangle/sound/tests/cow.raw | Bin 37502 -> 0 bytes libs/mangle/sound/tests/cow.wav | Bin 37546 -> 0 bytes .../mangle/sound/tests/ffmpeg_source_test.cpp | 62 --- .../sound/tests/openal_audiere_test.cpp | 52 -- .../mangle/sound/tests/openal_ffmpeg_test.cpp | 52 -- .../mangle/sound/tests/openal_mpg123_test.cpp | 54 -- .../mangle/sound/tests/openal_output_test.cpp | 59 -- .../sound/tests/openal_sndfile_test.cpp | 52 -- .../sound/tests/openal_various_test.cpp | 51 -- .../tests/output/audiere_source_test.out | 21 - .../sound/tests/output/ffmpeg_source_test.out | 12 - .../tests/output/openal_audiere_test.out | 3 - .../sound/tests/output/openal_ffmpeg_test.out | 2 - .../sound/tests/output/openal_mpg123_test.out | 1 - .../sound/tests/output/openal_output_test.out | 5 - .../tests/output/openal_sndfile_test.out | 2 - .../tests/output/openal_various_test.out | 1 - .../sound/tests/output/wav_source_test.out | 12 - libs/mangle/sound/tests/owl.ogg | Bin 18641 -> 0 bytes libs/mangle/sound/tests/test.sh | 18 - libs/mangle/sound/tests/wav_source_test.cpp | 48 -- libs/openengine/sound/sndmanager.cpp | 219 -------- libs/openengine/sound/sndmanager.hpp | 93 ---- libs/openengine/sound/tests/Makefile | 16 - .../sound/tests/output/sound_3d_test.out | 3 - .../sound/tests/output/sound_manager_test.out | 5 - libs/openengine/sound/tests/sound_3d_test.cpp | 46 -- .../sound/tests/sound_manager_test.cpp | 73 --- libs/openengine/sound/tests/test.sh | 18 - 65 files changed, 167 insertions(+), 3766 deletions(-) delete mode 100644 libs/mangle/sound/.gitignore delete mode 100644 libs/mangle/sound/clients/ogre_listener_mover.hpp delete mode 100644 libs/mangle/sound/clients/ogre_output_updater.hpp delete mode 100644 libs/mangle/sound/filters/input_filter.hpp delete mode 100644 libs/mangle/sound/filters/openal_audiere.hpp delete mode 100644 libs/mangle/sound/filters/openal_ffmpeg.hpp delete mode 100644 libs/mangle/sound/filters/openal_mpg123.hpp delete mode 100644 libs/mangle/sound/filters/openal_sndfile.hpp delete mode 100644 libs/mangle/sound/filters/openal_sndfile_mpg123.hpp delete mode 100644 libs/mangle/sound/filters/openal_various.hpp delete mode 100644 libs/mangle/sound/filters/pure_filter.hpp delete mode 100644 libs/mangle/sound/filters/source_splicer.hpp delete mode 100644 libs/mangle/sound/output.hpp delete mode 100644 libs/mangle/sound/outputs/openal_out.cpp delete mode 100644 libs/mangle/sound/outputs/openal_out.hpp delete mode 100644 libs/mangle/sound/source.hpp delete mode 100644 libs/mangle/sound/sources/audiere_source.cpp delete mode 100644 libs/mangle/sound/sources/audiere_source.hpp delete mode 100644 libs/mangle/sound/sources/ffmpeg_source.cpp delete mode 100644 libs/mangle/sound/sources/ffmpeg_source.hpp delete mode 100644 libs/mangle/sound/sources/libsndfile.cpp delete mode 100644 libs/mangle/sound/sources/libsndfile.hpp delete mode 100644 libs/mangle/sound/sources/loadertemplate.hpp delete mode 100644 libs/mangle/sound/sources/mpg123_source.cpp delete mode 100644 libs/mangle/sound/sources/mpg123_source.hpp delete mode 100644 libs/mangle/sound/sources/sample_reader.cpp delete mode 100644 libs/mangle/sound/sources/sample_reader.hpp delete mode 100644 libs/mangle/sound/sources/stream_source.hpp delete mode 100644 libs/mangle/sound/sources/wav_source.cpp delete mode 100644 libs/mangle/sound/sources/wav_source.hpp delete mode 100644 libs/mangle/sound/tests/.gitignore delete mode 100644 libs/mangle/sound/tests/Makefile delete mode 100644 libs/mangle/sound/tests/audiere_source_test.cpp delete mode 100644 libs/mangle/sound/tests/cow.raw delete mode 100644 libs/mangle/sound/tests/cow.wav delete mode 100644 libs/mangle/sound/tests/ffmpeg_source_test.cpp delete mode 100644 libs/mangle/sound/tests/openal_audiere_test.cpp delete mode 100644 libs/mangle/sound/tests/openal_ffmpeg_test.cpp delete mode 100644 libs/mangle/sound/tests/openal_mpg123_test.cpp delete mode 100644 libs/mangle/sound/tests/openal_output_test.cpp delete mode 100644 libs/mangle/sound/tests/openal_sndfile_test.cpp delete mode 100644 libs/mangle/sound/tests/openal_various_test.cpp delete mode 100644 libs/mangle/sound/tests/output/audiere_source_test.out delete mode 100644 libs/mangle/sound/tests/output/ffmpeg_source_test.out delete mode 100644 libs/mangle/sound/tests/output/openal_audiere_test.out delete mode 100644 libs/mangle/sound/tests/output/openal_ffmpeg_test.out delete mode 100644 libs/mangle/sound/tests/output/openal_mpg123_test.out delete mode 100644 libs/mangle/sound/tests/output/openal_output_test.out delete mode 100644 libs/mangle/sound/tests/output/openal_sndfile_test.out delete mode 100644 libs/mangle/sound/tests/output/openal_various_test.out delete mode 100644 libs/mangle/sound/tests/output/wav_source_test.out delete mode 100644 libs/mangle/sound/tests/owl.ogg delete mode 100755 libs/mangle/sound/tests/test.sh delete mode 100644 libs/mangle/sound/tests/wav_source_test.cpp delete mode 100644 libs/openengine/sound/sndmanager.cpp delete mode 100644 libs/openengine/sound/sndmanager.hpp delete mode 100644 libs/openengine/sound/tests/Makefile delete mode 100644 libs/openengine/sound/tests/output/sound_3d_test.out delete mode 100644 libs/openengine/sound/tests/output/sound_manager_test.out delete mode 100644 libs/openengine/sound/tests/sound_3d_test.cpp delete mode 100644 libs/openengine/sound/tests/sound_manager_test.cpp delete mode 100755 libs/openengine/sound/tests/test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dbd27a68..d06085322 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,10 +120,6 @@ set(OENGINE_BULLET # Sound setup if (USE_AUDIERE) - set(MANGLE_SOUND_OUTPUT - ${LIBDIR}/mangle/sound/sources/audiere_source.cpp - ${LIBDIR}/mangle/sound/sources/sample_reader.cpp - ${LIBDIR}/mangle/stream/clients/audiere_file.cpp) find_package(Audiere REQUIRED) set(SOUND_INPUT_INCLUDES ${AUDIERE_INCLUDE_DIR}) set(SOUND_INPUT_LIBRARY ${AUDIERE_LIBRARY}) @@ -131,8 +127,6 @@ if (USE_AUDIERE) endif (USE_AUDIERE) if (USE_FFMPEG) - set(MANGLE_SOUND_OUTPUT - ${LIBDIR}/mangle/sound/sources/ffmpeg_source.cpp) find_package(FFMPEG REQUIRED) set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIR}) set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES}) @@ -140,10 +134,6 @@ if (USE_FFMPEG) endif (USE_FFMPEG) if (USE_MPG123) - set(MANGLE_SOUND_OUTPUT - ${LIBDIR}/mangle/sound/sources/mpg123_source.cpp - ${LIBDIR}/mangle/sound/sources/libsndfile.cpp - ${LIBDIR}/mangle/sound/sources/sample_reader.cpp) find_package(MPG123 REQUIRED) find_package(SNDFILE REQUIRED) set(SOUND_INPUT_INCLUDES ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) @@ -151,14 +141,7 @@ if (USE_MPG123) set(SOUND_DEFINE -DOPENMW_USE_MPG123) endif (USE_MPG123) -set(OENGINE_SOUND - # Mangle and OEngine sound files are sort of intertwined, so put - # them together here - ${LIBDIR}/openengine/sound/sndmanager.cpp - ${LIBDIR}/mangle/sound/outputs/openal_out.cpp - ${MANGLE_SOUND_OUTPUT} -) -set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_SOUND} ${OENGINE_BULLET}) +set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET}) source_group(libs\\openengine FILES ${OENGINE_ALL}) set(OPENMW_LIBS ${MANGLE_ALL} ${OENGINE_ALL}) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index a5ba04e26..e0d7167de 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -6,10 +6,6 @@ #include -#include -#include -#include - #include #include "../mwworld/environment.hpp" @@ -21,39 +17,23 @@ CMakeLists.txt. */ #ifdef OPENMW_USE_AUDIERE -#include #define SOUND_FACTORY OpenAL_Audiere_Factory #define SOUND_OUT "OpenAL" #define SOUND_IN "Audiere" #endif #ifdef OPENMW_USE_FFMPEG -#include #define SOUND_FACTORY OpenAL_FFMpeg_Factory #define SOUND_OUT "OpenAL" #define SOUND_IN "FFmpeg" #endif #ifdef OPENMW_USE_MPG123 -#include #define SOUND_FACTORY OpenAL_SndFile_Mpg123_Factory #define SOUND_OUT "OpenAL" #define SOUND_IN "mpg123,sndfile" #endif -using namespace Mangle::Sound; -typedef OEngine::Sound::SoundManager OEManager; - -// Set the position on a sound based on a Ptr. -static void setPos(SoundPtr &snd, const MWWorld::Ptr ref) -{ - // Get sound position from the reference - const float *pos = ref.getCellRef().pos.pos; - - // Move the sound, converting from MW coordinates to Ogre - // coordinates. - snd->setPos(pos[0], pos[2], -pos[1]); -} namespace MWSound { @@ -63,50 +43,36 @@ namespace MWSound bool useSound, bool fsstrict, MWWorld::Environment& environment) : mFSStrict(fsstrict) , mEnvironment(environment) - , mgr(new OEManager(SoundFactoryPtr(new SOUND_FACTORY))) - , updater(mgr) - , cameraTracker(mgr) , mCurrentPlaylist(NULL) { - if(useSound) - { - // The music library will accept these filetypes - // If none is given then it will accept all filetypes - std::vector acceptableExtensions; - acceptableExtensions.push_back(".mp3"); - acceptableExtensions.push_back(".wav"); - acceptableExtensions.push_back(".ogg"); - acceptableExtensions.push_back(".flac"); - - // Makes a list of all sound files, searches in reverse for priority reasons - for (Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) - { - Files::FileLister(*it / std::string("Sound"), mSoundFiles, true); - } + if(!useSound) + return; - // Makes a FileLibrary of all music files, searches in reverse for priority reasons - for (Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) - { - mMusicLibrary.add(*it / std::string("Music"), true, mFSStrict, acceptableExtensions); - } + // The music library will accept these filetypes + // If none is given then it will accept all filetypes + std::vector acceptableExtensions; + acceptableExtensions.push_back(".mp3"); + acceptableExtensions.push_back(".wav"); + acceptableExtensions.push_back(".ogg"); + acceptableExtensions.push_back(".flac"); - std::string anything = "anything"; // anything is better that a segfault - mCurrentPlaylist = mMusicLibrary.section(anything, mFSStrict); // now points to an empty path + // Makes a list of all sound files, searches in reverse for priority reasons + for(Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) + Files::FileLister(*it / std::string("Sound"), mSoundFiles, true); - std::cout << "Sound output: " << SOUND_OUT << std::endl; - std::cout << "Sound decoder: " << SOUND_IN << std::endl; - // Attach the camera to the camera tracker - cameraTracker.followCamera(camera); + // Makes a FileLibrary of all music files, searches in reverse for priority reasons + for(Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) + mMusicLibrary.add(*it / std::string("Music"), true, mFSStrict, acceptableExtensions); - // Tell Ogre to update the sound system each frame - root->addFrameListener(&updater); - } - } + std::string anything = "anything"; // anything is better that a segfault + mCurrentPlaylist = mMusicLibrary.section(anything, mFSStrict); // now points to an empty path + + std::cout << "Sound output: " << SOUND_OUT << std::endl; + std::cout << "Sound decoder: " << SOUND_IN << std::endl; + } SoundManager::~SoundManager() { - Ogre::Root::getSingleton().removeFrameListener(&updater); - cameraTracker.unfollowCamera(); } // Convert a soundId to file name, and modify the volume @@ -115,28 +81,28 @@ namespace MWSound std::string SoundManager::lookup(const std::string &soundId, float &volume, float &min, float &max) { - const ESM::Sound *snd = mEnvironment.mWorld->getStore().sounds.search(soundId); - if(snd == NULL) return ""; - - if(snd->data.volume == 0) - volume = 0.0f; - else - volume *= pow(10.0, (snd->data.volume/255.0f*3348.0 - 3348.0) / 2000.0); - - if(snd->data.minRange == 0 && snd->data.maxRange == 0) - { - min = 100.0f; - max = 2000.0f; - } - else - { - min = snd->data.minRange * 20.0f; - max = snd->data.maxRange * 50.0f; - min = std::max(min, 1.0f); - max = std::max(min, max); - } - - return Files::FileListLocator(mSoundFiles, snd->sound, mFSStrict, false); + const ESM::Sound *snd = mEnvironment.mWorld->getStore().sounds.search(soundId); + if(snd == NULL) return ""; + + if(snd->data.volume == 0) + volume = 0.0f; + else + volume *= pow(10.0, (snd->data.volume/255.0f*3348.0 - 3348.0) / 2000.0); + + if(snd->data.minRange == 0 && snd->data.maxRange == 0) + { + min = 100.0f; + max = 2000.0f; + } + else + { + min = snd->data.minRange * 20.0f; + max = snd->data.maxRange * 50.0f; + min = std::max(min, 1.0f); + max = std::max(min, max); + } + + return Files::FileListLocator(mSoundFiles, snd->sound, mFSStrict, false); } // Add a sound to the list and play it @@ -147,190 +113,79 @@ namespace MWSound float min, float max, bool loop, bool untracked) { - try - { - SoundPtr snd = mgr->load(file); - snd->setRepeat(loop); - snd->setVolume(volume); - snd->setPitch(pitch); - snd->setRange(min,max); - setPos(snd, ptr); - snd->play(); - - if (!untracked) - { - sounds[ptr][id] = WSoundPtr(snd); - } - } - catch(...) - { - std::cout << "Error loading " << file << ", skipping.\n"; - } - } - - // Clears all the sub-elements of a given iterator, and then - // removes it from 'sounds'. - void SoundManager::clearAll(PtrMap::iterator& it) - { - IDMap::iterator sit = it->second.begin(); - - while(sit != it->second.end()) - { - // Get sound pointer, if any - SoundPtr snd = sit->second.lock(); - - // Stop the sound - if(snd) snd->stop(); - - sit++; - } - - // Remove the ptr reference - sounds.erase(it); + //std::cout << "Cannot load " << file << ", skipping.\n"; } // Stop a sound and remove it from the list. If id="" then // remove the entire object and stop all its sounds. void SoundManager::remove(MWWorld::Ptr ptr, const std::string &id) { - PtrMap::iterator it = sounds.find(ptr); - if(it != sounds.end()) - { - if(id == "") - // Kill all references to 'ptr' - clearAll(it); - else - { - // Only find the id we're looking for - IDMap::iterator it2 = it->second.find(id); - if(it2 != it->second.end()) - { - // Stop the sound and remove it from the list - SoundPtr snd = it2->second.lock(); - if(snd) snd->stop(); - it->second.erase(it2); - } - } - } } bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { - PtrMap::const_iterator it = sounds.find(ptr); - if(it != sounds.end()) - { - IDMap::const_iterator it2 = it->second.find(id); - if(it2 != it->second.end()) - { - // Get a shared_ptr from the weak_ptr - SoundPtr snd = it2->second.lock();; - - // Is it still alive? - if(snd) - { - // Then return its status! - return snd->isPlaying(); - } - } - } - // Nothing found, sound is not playing - return false; + // HACK: Return true to prevent the engine from trying to keep playing + // sounds and tanking the framerate. + return true; } // Remove all references to objects belonging to a given cell void SoundManager::removeCell(const MWWorld::Ptr::CellStore *cell) { - PtrMap::iterator it2, it = sounds.begin(); - while(it != sounds.end()) - { - // Make sure to increase the iterator before we erase it. - it2 = it++; - if(it2->first.getCell() == cell) - clearAll(it2); - } } void SoundManager::updatePositions(MWWorld::Ptr ptr) { - // Find the reference (if any) - PtrMap::iterator it = sounds.find(ptr); - if(it != sounds.end()) - { - // Then find all sounds in it (if any) - IDMap::iterator it2 = it->second.begin(); - for(;it2 != it->second.end(); it2++) - { - // Get the sound (if it still exists) - SoundPtr snd = it2->second.lock(); - if(snd) - // Update position - setPos(snd, ptr); - } - } } void SoundManager::stopMusic() { - if (music) - music->stop(); setPlaylist(); } - void SoundManager::streamMusicFull(const std::string& filename) - { - // Play the sound and tell it to stream, if possible. TODO: - // Store the reference, the jukebox will need to check status, - // control volume etc. - if (music) - music->stop(); - music = mgr->load(filename); - music->setStreaming(true); - music->setVolume(0.4); - music->play(); - - } + void SoundManager::streamMusicFull(const std::string& filename) + { + // Play the sound and tell it to stream, if possible. TODO: + // Store the reference, the jukebox will need to check status, + // control volume etc. + } void SoundManager::streamMusic(const std::string& filename) { std::string filePath = mMusicLibrary.locate(filename, mFSStrict, true).string(); if(!filePath.empty()) - { streamMusicFull(filePath); - } } - void SoundManager::startRandomTitle() - { - if(mCurrentPlaylist && !mCurrentPlaylist->empty()) + void SoundManager::startRandomTitle() { - Files::PathContainer::const_iterator fileIter = mCurrentPlaylist->begin(); - srand( time(NULL) ); - int r = rand() % mCurrentPlaylist->size() + 1; //old random code + if(mCurrentPlaylist && !mCurrentPlaylist->empty()) + { + Files::PathContainer::const_iterator fileIter = mCurrentPlaylist->begin(); + srand( time(NULL) ); + int r = rand() % mCurrentPlaylist->size() + 1; //old random code - std::advance(fileIter, r - 1); - std::string music = fileIter->string(); - std::cout << "Playing " << music << "\n"; + std::advance(fileIter, r - 1); + std::string music = fileIter->string(); + //std::cout << "Playing " << music << "\n"; - try - { - streamMusicFull(music); - } - catch (std::exception &e) - { - std::cout << " Music Error: " << e.what() << "\n"; + try + { + streamMusicFull(music); + } + catch (std::exception &e) + { + std::cout << "Music Error: " << e.what() << "\n"; + } } } - } bool SoundManager::isMusicPlaying() { - bool test = false; - if(music) - { - test = music->isPlaying(); - } - return test; + // HACK: Return true to prevent the engine from trying to keep playing + // music and tanking the framerate. + return true; } bool SoundManager::setPlaylist(std::string playlist) @@ -373,154 +228,122 @@ namespace MWSound } } - void SoundManager::say (MWWorld::Ptr ptr, const std::string& filename) - { - // The range values are not tested - std::string filePath = Files::FileListLocator(mSoundFiles, filename, mFSStrict, true); - if(!filePath.empty()) - add(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); - else - std::cout << "Sound file " << filename << " not found, skipping.\n"; - } - - bool SoundManager::sayDone (MWWorld::Ptr ptr) const - { - return !isPlaying(ptr, "_say_sound"); - } - - - void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) - { - float min, max; - const std::string &file = lookup(soundId, volume, min, max); - if (file != "") + void SoundManager::say(MWWorld::Ptr ptr, const std::string& filename) { - SoundPtr snd = mgr->load(file); - snd->setRepeat(loop); - snd->setVolume(volume); - snd->setRange(min,max); - snd->setPitch(pitch); - snd->setRelative(true); - snd->play(); - - if (loop) - { - // Only add the looping sound once - IDMap::iterator it = mLoopedSounds.find(soundId); - if(it == mLoopedSounds.end()) - { - mLoopedSounds[soundId] = WSoundPtr(snd); - } - } + // The range values are not tested + std::string filePath = Files::FileListLocator(mSoundFiles, filename, mFSStrict, true); + if(!filePath.empty()) + add(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); + else + std::cout << "Sound file " << filename << " not found, skipping.\n"; + } + + bool SoundManager::sayDone(MWWorld::Ptr ptr) const + { + return !isPlaying(ptr, "_say_sound"); + } + + + void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) + { + float min, max; + const std::string &file = lookup(soundId, volume, min, max); + std::cout << "Cannot play " << file << ", skipping.\n"; + } + + void SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, + float volume, float pitch, bool loop, bool untracked) + { + // Look up the sound in the ESM data + float min, max; + const std::string &file = lookup(soundId, volume, min, max); + if(file != "") + add(file, ptr, soundId, volume, pitch, min, max, loop, untracked); + } + + void SoundManager::stopSound3D(MWWorld::Ptr ptr, const std::string& soundId) + { + remove(ptr, soundId); + } + + void SoundManager::stopSound(MWWorld::Ptr::CellStore *cell) + { + removeCell(cell); } - } - - void SoundManager::playSound3D (MWWorld::Ptr ptr, const std::string& soundId, - float volume, float pitch, bool loop, bool untracked) - { - // Look up the sound in the ESM data - float min, max; - const std::string &file = lookup(soundId, volume, min, max); - if (file != "") - add(file, ptr, soundId, volume, pitch, min, max, loop, untracked); - } - - void SoundManager::stopSound3D (MWWorld::Ptr ptr, const std::string& soundId) - { - remove(ptr, soundId); - } - - void SoundManager::stopSound (MWWorld::Ptr::CellStore *cell) - { - removeCell(cell); - } void SoundManager::stopSound(const std::string& soundId) { - IDMap::iterator it = mLoopedSounds.find(soundId); - if(it != mLoopedSounds.end()) - { - SoundPtr snd = it->second.lock(); - if(snd) snd->stop(); - mLoopedSounds.erase(it); - } } - bool SoundManager::getSoundPlaying (MWWorld::Ptr ptr, const std::string& soundId) const - { - // Mark all sounds as playing, otherwise the scripts will just - // keep trying to play them every frame. + bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const + { + // Mark all sounds as playing, otherwise the scripts will just + // keep trying to play them every frame. - return isPlaying(ptr, soundId); - } + return isPlaying(ptr, soundId); + } - void SoundManager::updateObject(MWWorld::Ptr ptr) - { - updatePositions(ptr); - } + void SoundManager::updateObject(MWWorld::Ptr ptr) + { + updatePositions(ptr); + } - void SoundManager::update (float duration) - { + void SoundManager::update(float duration) + { MWWorld::Ptr::CellStore *current = mEnvironment.mWorld->getPlayer().getPlayer().getCell(); static int total = 0; static std::string regionName = ""; static float timePassed = 0.0; - timePassed += duration; //If the region has changed - if(!(current->cell->data.flags & current->cell->Interior) && timePassed >= 10) + timePassed += duration; + if((current->cell->data.flags & current->cell->Interior) || timePassed < 10) + return; + + ESM::Region test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region)); + + timePassed = 0; + if(regionName != current->cell->region) { + regionName = current->cell->region; + total = 0; + } - ESM::Region test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region)); + if(test.soundList.size() == 0) + return; - timePassed = 0; - if (regionName != current->cell->region) + std::vector::iterator soundIter; + if(total == 0) + { + soundIter = test.soundList.begin(); + while(soundIter != test.soundList.end()) { - regionName = current->cell->region; - total = 0; + int chance = (int) soundIter->chance; + //ESM::NAME32 go = soundIter->sound; + //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; + soundIter++; + total += chance; } + } - if(test.soundList.size() > 0) + int r = rand() % total; //old random code + int pos = 0; + + soundIter = test.soundList.begin(); + while(soundIter != test.soundList.end()) + { + const std::string go = soundIter->sound.toString(); + int chance = (int) soundIter->chance; + //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; + soundIter++; + if(r - pos < chance) { - std::vector::iterator soundIter = test.soundList.begin(); - //mEnvironment.mSoundManager - if(total == 0) - { - while (soundIter != test.soundList.end()) - { - int chance = (int) soundIter->chance; - //ESM::NAME32 go = soundIter->sound; - //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; - soundIter++; - total += chance; - } - } - - int r = rand() % total; //old random code - int pos = 0; - soundIter = test.soundList.begin(); - while (soundIter != test.soundList.end()) - { - const std::string go = soundIter->sound.toString(); - int chance = (int) soundIter->chance; - //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; - soundIter++; - if( r - pos < chance) - { - //play sound - std::cout << "Sound: " << go <<" Chance:" << chance << "\n"; - mEnvironment.mSoundManager->playSound(go, 20.0, 1.0); - - break; - } - pos += chance; - } + //play sound + std::cout << "Sound: " << go <<" Chance:" << chance << "\n"; + playSound(go, 20.0, 1.0); + break; } + pos += chance; } - else if(current->cell->data.flags & current->cell->Interior) - { - regionName = ""; - } - - } + } } diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index bd3b67679..9db7fe1b7 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -3,11 +3,6 @@ #include -#include -#include - -#include - #include #include "../mwworld/ptr.hpp" @@ -19,16 +14,6 @@ namespace Ogre class Camera; } -namespace Mangle -{ - namespace Sound - { - typedef boost::shared_ptr SoundPtr; - } -} - -typedef OEngine::Sound::SoundManagerPtr OEManagerPtr; - namespace MWWorld { struct Environment; @@ -51,26 +36,6 @@ namespace MWSound ///< Play a soundifle /// \param absolute filename - /* This is the sound manager. It loades, stores and deletes - sounds based on the sound factory it is given. - */ - OEManagerPtr mgr; - Mangle::Sound::SoundPtr music; - - /* This class calls update() on the sound manager each frame - using and Ogre::FrameListener - */ - Mangle::Sound::OgreOutputUpdater updater; - - /* This class tracks the movement of an Ogre::Camera and moves - a sound listener automatically to follow it. - */ - Mangle::Sound::OgreListenerMover cameraTracker; - - typedef std::map IDMap; - typedef std::map PtrMap; - PtrMap sounds; - // A list of all sound files used to lookup paths Files::PathContainer mSoundFiles; @@ -80,15 +45,12 @@ namespace MWSound // Points to the current playlist of music files stored in the music library const Files::PathContainer* mCurrentPlaylist; - IDMap mLoopedSounds; - std::string lookup(const std::string &soundId, float &volume, float &min, float &max); void add(const std::string &file, MWWorld::Ptr ptr, const std::string &id, float volume, float pitch, float min, float max, bool loop, bool untracked=false); - void clearAll(PtrMap::iterator& it); void remove(MWWorld::Ptr ptr, const std::string &id = ""); bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; void removeCell(const MWWorld::Ptr::CellStore *cell); diff --git a/libs/mangle/sound/.gitignore b/libs/mangle/sound/.gitignore deleted file mode 100644 index 8b1378917..000000000 --- a/libs/mangle/sound/.gitignore +++ /dev/null @@ -1 +0,0 @@ - diff --git a/libs/mangle/sound/clients/ogre_listener_mover.hpp b/libs/mangle/sound/clients/ogre_listener_mover.hpp deleted file mode 100644 index 74c21db32..000000000 --- a/libs/mangle/sound/clients/ogre_listener_mover.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef MANGLE_SOUND_OGRELISTENERMOVER_H -#define MANGLE_SOUND_OGRELISTENERMOVER_H - -#include -#include -#include "../output.hpp" - -namespace Mangle { -namespace Sound { - - /** This class lets a sound listener (ie. the SoundFactory) track a - given camera in Ogre3D. The position and orientation of the - listener will be updated to match the camera whenever the camera - is moved. - */ - struct OgreListenerMover : Ogre::Camera::Listener - { - OgreListenerMover(Mangle::Sound::SoundFactoryPtr snd) - : soundFact(snd), camera(NULL) - {} - - /// Follow a camera. WARNING: This will OVERRIDE any other - /// MovableObject::Listener you may have attached to the camera. - void followCamera(Ogre::Camera *cam) - { - camera = cam; - camera->addListener(this); - } - - void unfollowCamera() - { - // If the camera is null, this object wasn't following a camera. - // It doesn't make sense to call unfollow - assert(camera != NULL); - - camera->removeListener(this); - camera = NULL; - } - - private: - Mangle::Sound::SoundFactoryPtr soundFact; - Ogre::Camera *camera; - Ogre::Vector3 pos, dir, up; - - /// From Camera::Listener. This is called once per - /// frame. Unfortunately, Ogre doesn't allow us to be notified - /// only when the camera itself has moved, so we must poll every - /// frame. - void cameraPreRenderScene(Ogre::Camera *cam) - { - assert(cam == camera); - - Ogre::Vector3 nPos, nDir, nUp; - - nPos = camera->getRealPosition(); - nDir = camera->getRealDirection(); - nUp = camera->getRealUp(); - - // Don't bother the sound system needlessly - if(nDir != dir || nPos != pos || nUp != up) - { - pos = nPos; - dir = nDir; - up = nUp; - - soundFact->setListenerPos(pos.x, pos.y, pos.z, - dir.x, dir.y, dir.z, - up.x, up.y, up.z); - } - } - - void cameraDestroyed(Ogre::Camera *cam) - { - assert(cam == camera); - camera = NULL; - } - }; -}} -#endif diff --git a/libs/mangle/sound/clients/ogre_output_updater.hpp b/libs/mangle/sound/clients/ogre_output_updater.hpp deleted file mode 100644 index b73168c75..000000000 --- a/libs/mangle/sound/clients/ogre_output_updater.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef MANGLE_SOUND_OGREUPDATER_H -#define MANGLE_SOUND_OGREUPDATER_H - -/* - This Ogre FrameListener calls update on a SoundFactory - */ - -#include -#include "../output.hpp" -#include - -namespace Mangle { -namespace Sound { - - struct OgreOutputUpdater : Ogre::FrameListener - { - Mangle::Sound::SoundFactoryPtr driver; - - OgreOutputUpdater(Mangle::Sound::SoundFactoryPtr drv) - : driver(drv) - { assert(drv->needsUpdate); } - - bool frameStarted(const Ogre::FrameEvent &evt) - { - driver->update(); - return true; - } - }; -}} - -#endif diff --git a/libs/mangle/sound/filters/input_filter.hpp b/libs/mangle/sound/filters/input_filter.hpp deleted file mode 100644 index 00ee18766..000000000 --- a/libs/mangle/sound/filters/input_filter.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef MANGLE_INPUT_FILTER_H -#define MANGLE_INPUT_FILTER_H - -#include "../output.hpp" - -#include - -namespace Mangle { -namespace Sound { - -/** - @brief This filter class adds file loading capabilities to a - Sound::SoundFactory class, by associating a SampleSourceLoader with - it. - - The class takes an existing SoundFactory able to load streams, and - associates a SampleSourceLoader with it. The combined class is able - to load files directly. */ -class InputFilter : public SoundFactory -{ - protected: - SoundFactoryPtr snd; - SampleSourceLoaderPtr inp; - - public: - /// Empty constructor - InputFilter() {} - - /// Assign an input manager and a sound manager to this object - InputFilter(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp) - { set(_snd, _inp); } - - /// Assign an input manager and a sound manager to this object - void set(SoundFactoryPtr _snd, SampleSourceLoaderPtr _inp) - { - inp = _inp; - snd = _snd; - - // Set capabilities - needsUpdate = snd->needsUpdate; - has3D = snd->has3D; - canLoadStream = inp->canLoadStream; - - // Both these should be true, or the use of this class is pretty - // pointless - canLoadSource = snd->canLoadSource; - canLoadFile = inp->canLoadFile; - assert(canLoadSource && canLoadFile); - } - - virtual SoundPtr load(const std::string &file) - { return loadRaw(inp->load(file)); } - - virtual SoundPtr load(Stream::StreamPtr input) - { return loadRaw(inp->load(input)); } - - virtual SoundPtr loadRaw(SampleSourcePtr input) - { return snd->loadRaw(input); } - - virtual void update() { snd->update(); } - virtual void setListenerPos(float x, float y, float z, - float fx, float fy, float fz, - float ux, float uy, float uz) - { snd->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_audiere.hpp b/libs/mangle/sound/filters/openal_audiere.hpp deleted file mode 100644 index 5b9b51824..000000000 --- a/libs/mangle/sound/filters/openal_audiere.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MANGLE_AUDIERE_OPENAL_H -#define MANGLE_AUDIERE_OPENAL_H - -#include "input_filter.hpp" -#include "../sources/audiere_source.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that adds audiere decoding to OpenAL. Audiere has -/// it's own output, but OpenAL sports 3D and other advanced features. -class OpenAL_Audiere_Factory : public InputFilter -{ - public: - OpenAL_Audiere_Factory() - { - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(new AudiereLoader)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_ffmpeg.hpp b/libs/mangle/sound/filters/openal_ffmpeg.hpp deleted file mode 100644 index 42c76af0c..000000000 --- a/libs/mangle/sound/filters/openal_ffmpeg.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef MANGLE_FFMPEG_OPENAL_H -#define MANGLE_FFMPEG_OPENAL_H - -#include "input_filter.hpp" -#include "../sources/ffmpeg_source.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that adds ffmpeg decoding to OpenAL. -class OpenAL_FFMpeg_Factory : public InputFilter -{ - public: - OpenAL_FFMpeg_Factory() - { - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(new FFMpegLoader)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_mpg123.hpp b/libs/mangle/sound/filters/openal_mpg123.hpp deleted file mode 100644 index bfd926c0b..000000000 --- a/libs/mangle/sound/filters/openal_mpg123.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MANGLE_MPG123_OPENAL_H -#define MANGLE_MPG123_OPENAL_H - -#include "input_filter.hpp" -#include "../sources/mpg123_source.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that adds mpg123 decoding to OpenAL. Only supports -/// MP3 files. -class OpenAL_Mpg123_Factory : public InputFilter -{ - public: - OpenAL_Mpg123_Factory() - { - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(new Mpg123Loader)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_sndfile.hpp b/libs/mangle/sound/filters/openal_sndfile.hpp deleted file mode 100644 index fd7e78025..000000000 --- a/libs/mangle/sound/filters/openal_sndfile.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MANGLE_SNDFILE_OPENAL_H -#define MANGLE_SNDFILE_OPENAL_H - -#include "input_filter.hpp" -#include "../sources/libsndfile.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that adds libsnd decoding to OpenAL. libsndfile -/// supports most formats except MP3. -class OpenAL_SndFile_Factory : public InputFilter -{ - public: - OpenAL_SndFile_Factory() - { - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(new SndFileLoader)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp b/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp deleted file mode 100644 index 6e5db4d0e..000000000 --- a/libs/mangle/sound/filters/openal_sndfile_mpg123.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef MANGLE_SNDFILE_MPG123_OPENAL_H -#define MANGLE_SNDFILE_MPG123_OPENAL_H - -#include "input_filter.hpp" -#include "source_splicer.hpp" -#include "../sources/mpg123_source.hpp" -#include "../sources/libsndfile.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/// A InputFilter that uses OpenAL for output, and mpg123 (for MP3) + -/// libsndfile (for everything else) to decode files. Can only load -/// from the file system, and uses the file name to differentiate -/// between mp3 and non-mp3 types. -class OpenAL_SndFile_Mpg123_Factory : public InputFilter -{ - public: - OpenAL_SndFile_Mpg123_Factory() - { - SourceSplicer *splice = new SourceSplicer; - - splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader)); - splice->setDefault(SampleSourceLoaderPtr(new SndFileLoader)); - - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(splice)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/openal_various.hpp b/libs/mangle/sound/filters/openal_various.hpp deleted file mode 100644 index 945b3dabd..000000000 --- a/libs/mangle/sound/filters/openal_various.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef MANGLE_VARIOUS_OPENAL_H -#define MANGLE_VARIOUS_OPENAL_H - -#include "input_filter.hpp" -#include "source_splicer.hpp" -#include "../sources/mpg123_source.hpp" -#include "../sources/wav_source.hpp" -#include "../outputs/openal_out.hpp" - -namespace Mangle { -namespace Sound { - -/** A InputFilter that uses OpenAL for output, and load input from - various individual sources, depending on file extension. Currently - supports: - - MP3: mpg123 - WAV: custom wav loader (PCM only) - - This could be an alternative to using eg. 3rd party decoder - libraries like libsndfile. - */ -class OpenAL_Various_Factory : public InputFilter -{ - public: - OpenAL_Various_Factory() - { - SourceSplicer *splice = new SourceSplicer; - - splice->add("mp3", SampleSourceLoaderPtr(new Mpg123Loader)); - splice->add("wav", SampleSourceLoaderPtr(new WavLoader)); - - set(SoundFactoryPtr(new OpenAL_Factory), - SampleSourceLoaderPtr(splice)); - } -}; - -}} -#endif diff --git a/libs/mangle/sound/filters/pure_filter.hpp b/libs/mangle/sound/filters/pure_filter.hpp deleted file mode 100644 index fc5e62574..000000000 --- a/libs/mangle/sound/filters/pure_filter.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef MANGLE_SOUND_OUTPUT_PUREFILTER_H -#define MANGLE_SOUND_OUTPUT_PUREFILTER_H - -#include "../output.hpp" - -namespace Mangle -{ - namespace Sound - { - // For use in writing other filters - class SoundFilter : public Sound - { - protected: - SoundPtr client; - - public: - SoundFilter(SoundPtr c) : client(c) {} - void play() { client->play(); } - void stop() { client->stop(); } - void pause() { client->pause(); } - bool isPlaying() const { return client->isPlaying(); } - void setVolume(float f) { client->setVolume(f); } - void setPan(float f) { client->setPan(f); } - void setPos(float x, float y, float z) - { client->setPos(x,y,z); } - void setPitch(float p) { client->setPitch(p); } - void setRepeat(bool b) { client->setRepeat(b); } - void setRange(float a, float b=0, float c=0) - { client->setRange(a,b,c); } - void setStreaming(bool b) { client->setStreaming(b); } - void setRelative(bool b) { client->setRelative(b); } - - // The clone() function is not implemented here, as you will - // almost certainly want to override it yourself - }; - - class FactoryFilter : public SoundFactory - { - protected: - SoundFactoryPtr client; - - public: - FactoryFilter(SoundFactoryPtr c) : client(c) - { - needsUpdate = client->needsUpdate; - has3D = client->has3D; - canLoadFile = client->canLoadFile; - canLoadStream = client->canLoadStream; - canLoadSource = client->canLoadSource; - } - - SoundPtr loadRaw(SampleSourcePtr input) - { return client->loadRaw(input); } - - SoundPtr load(Stream::StreamPtr input) - { return client->load(input); } - - SoundPtr load(const std::string &file) - { return client->load(file); } - - void update() - { client->update(); } - - void setListenerPos(float x, float y, float z, - float fx, float fy, float fz, - float ux, float uy, float uz) - { - client->setListenerPos(x,y,z,fx,fy,fz,ux,uy,uz); - } - }; - } -} -#endif diff --git a/libs/mangle/sound/filters/source_splicer.hpp b/libs/mangle/sound/filters/source_splicer.hpp deleted file mode 100644 index 9c7623086..000000000 --- a/libs/mangle/sound/filters/source_splicer.hpp +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef MANGLE_SOUND_SOURCE_SPLICE_H -#define MANGLE_SOUND_SOURCE_SPLICE_H - -#include "../source.hpp" -#include -#include -#include -#include - -namespace Mangle -{ - namespace Sound - { - class SourceSplicer : public SampleSourceLoader - { - struct SourceType - { - std::string type; - SampleSourceLoaderPtr loader; - }; - - typedef std::list TypeList; - TypeList list; - SampleSourceLoaderPtr catchAll; - - static bool isMatch(char a, char b) - { - if(a >= 'A' && a <= 'Z') - a += 'a' - 'A'; - if(b >= 'A' && b <= 'Z') - b += 'a' - 'A'; - return a == b; - } - - public: - SourceSplicer() - { - canLoadStream = false; - canLoadFile = true; - } - - void add(const std::string &type, SampleSourceLoaderPtr fact) - { - SourceType tp; - tp.type = type; - tp.loader = fact; - list.push_back(tp); - } - - void setDefault(SampleSourceLoaderPtr def) - { - catchAll = def; - } - - SampleSourcePtr load(const std::string &file) - { - // Search the list for this file type. - for(TypeList::iterator it = list.begin(); - it != list.end(); it++) - { - const std::string &t = it->type; - - int diff = file.size() - t.size(); - if(diff < 0) continue; - - bool match = true; - for(unsigned i=0; iloader->load(file); - } - // If not found, use the catch-all - if(catchAll) - return catchAll->load(file); - - throw std::runtime_error("No handler for sound file " + file); - } - - SampleSourcePtr load(Stream::StreamPtr input) { assert(0); } - }; - } -} - -#endif diff --git a/libs/mangle/sound/output.hpp b/libs/mangle/sound/output.hpp deleted file mode 100644 index e30bf21e2..000000000 --- a/libs/mangle/sound/output.hpp +++ /dev/null @@ -1,183 +0,0 @@ -#ifndef MANGLE_SOUND_OUTPUT_H -#define MANGLE_SOUND_OUTPUT_H - -#include -#include - -#include "source.hpp" -#include "../stream/stream.hpp" - -namespace Mangle { -namespace Sound { - -/// Abstract interface for a single playable sound -/** This class represents one sound outlet, which may be played, - stopped, paused and so on. - - Sound instances are created from the SoundFactory class. Sounds - may be connected to a SampleSource or read directly from a file, - and they may support 3d sounds, looping and other features - depending on the capabilities of the backend system. - - To create multiple instances of one sound, it is recommended to - 'clone' an existing instance instead of reloading it from - file. Cloned sounds will often (depending on the back-end) use - less memory due to shared buffers. -*/ -class Sound; -typedef boost::shared_ptr SoundPtr; -typedef boost::weak_ptr WSoundPtr; - -class Sound -{ - public: - /// Play or resume the sound - virtual void play() = 0; - - /// Stop the sound - virtual void stop() = 0; - - /// Pause the sound, may be resumed later - virtual void pause() = 0; - - /// Check if the sound is still playing - virtual bool isPlaying() const = 0; - - /// Set the volume. The parameter must be between 0.0 and 1.0. - virtual void setVolume(float) = 0; - - /// Set left/right pan. -1.0 is left, 0.0 is center and 1.0 is right. - virtual void setPan(float) = 0; - - /// Set pitch (1.0 is normal speed) - virtual void setPitch(float) = 0; - - /// Set range factors for 3D sounds. The meaning of the fields - /// depend on implementation. - virtual void setRange(float a, float b=0.0, float c=0.0) = 0; - - /// Set the position. May not work with all backends. - virtual void setPos(float x, float y, float z) = 0; - - /// Set loop mode - virtual void setRepeat(bool) = 0; - - /// If set to true the sound will not be affected by player movement - virtual void setRelative(bool) = 0; - - /// Set streaming mode. - /** This may be used by implementations to optimize for very large - files. If streaming mode is off (default), most implementations - will load the entire file into memory before starting playback. - */ - virtual void setStreaming(bool) = 0; - - /// Create a new instance of this sound. - /** Playback status is not cloned, only the sound data - itself. Back-ends can use this as a means of sharing data and - saving memory. */ - virtual SoundPtr clone() = 0; - - /// Virtual destructor - virtual ~Sound() {} -}; - -/// Factory interface for creating Sound objects -/** The SoundFactory is the main entry point to a given sound output - system. It is used to create Sound objects, which may be connected - to a sound file or stream, and which may be individually played, - paused, and so on. - - The class also contains a set of public bools which describe the - capabilities the particular system. These should be set by - implementations (base classes) in their respective constructors. - */ -class SoundFactory -{ - public: - /// Virtual destructor - virtual ~SoundFactory() {} - - /** @brief If set to true, you should call update() regularly (every frame - or so) on this sound manager. If false, update() should not be - called. - */ - bool needsUpdate; - - /** @brief true if 3D functions are available. If false, all use of - 3D sounds and calls to setPos / setListenerPos will result in - undefined behavior. - */ - bool has3D; - - /// true if we can load sounds directly from file (containing encoded data) - bool canLoadFile; - - /// If true, we can lound sound files from a Stream (containing encoded data) - bool canLoadStream; - - /// true if we can load sounds from a SampleSource (containing raw data) - bool canLoadSource; - - /** - @brief Load a sound from a sample source. Only valid if - canLoadSource is true. - - This function loads a sound from a given stream as defined by - SampleSource. - - @param input the input source - @param stream true if the file should be streamed. - Implementations may use this for optimizing playback of - large files, but they are not required to. - @return a new Sound object - */ - virtual SoundPtr loadRaw(SampleSourcePtr input) = 0; - - /** - @brief Load a sound file from stream. Only valid if canLoadStream - is true. - - @param input audio file stream - @param stream true if the file should be streamed - @see load(InputSource*,bool) - */ - virtual SoundPtr load(Stream::StreamPtr input) = 0; - - /** - @brief Load a sound directly from file. Only valid if canLoadFile - is true. - - @param file filename - @param stream true if the file should be streamed - @see load(InputSource*,bool) - */ - virtual SoundPtr load(const std::string &file) = 0; - - /// Call this every frame if needsUpdate is true - /** - This should be called regularly (about every frame in a normal - game setting.) Implementions may use this for filling streaming - buffers and similar tasks. Implementations that do not need this - should set needsUpdate to false. - */ - virtual void update() { assert(0); } - - /// Set listener position (coordinates, front and up vectors) - /** - Only valid if has3D is true. - - @param x,y,z listener position - @param fx,fy,fz listener's looking direction - @param ux,uy,uz listener's up direction - */ - virtual void setListenerPos(float x, float y, float z, - float fx, float fy, float fz, - float ux, float uy, float uz) = 0; -}; - -typedef boost::shared_ptr SoundFactoryPtr; - -}} // Namespaces - -#endif diff --git a/libs/mangle/sound/outputs/openal_out.cpp b/libs/mangle/sound/outputs/openal_out.cpp deleted file mode 100644 index 2056b4f60..000000000 --- a/libs/mangle/sound/outputs/openal_out.cpp +++ /dev/null @@ -1,500 +0,0 @@ -#include "openal_out.hpp" -#include -#include - -#include "../../stream/filters/buffer_stream.hpp" - -#ifdef _WIN32 -#include -#include -#elif defined(__APPLE__) -#include -#include -#else -#include -#include -#endif - -using namespace Mangle::Sound; - -// ---- Helper functions and classes ---- - -// Static buffer used to shuffle sound data from the input into -// OpenAL. The data is only stored temporarily and then immediately -// shuffled off to the library. This is not thread safe, but it works -// fine with multiple sounds in one thread. It could be made thread -// safe simply by using thread local storage. -const size_t BSIZE = 32*1024; -static char tmp_buffer[BSIZE]; - -// Number of buffers used (per sound) for streaming sounds. Each -// buffer is of size BSIZE. Increasing this will make streaming sounds -// more fault tolerant against temporary lapses in call to update(), -// but will also increase memory usage. -// This was changed from 4 to 150 for an estimated 30 seconds tolerance. -// At some point we should replace it with a more multithreading-ish -// solution. -const int STREAM_BUF_NUM = 150; - -static void fail(const std::string &msg) -{ throw std::runtime_error("OpenAL exception: " + msg); } - -/* - Check for AL error. Since we're always calling this with string - literals, and it only makes sense to optimize for the non-error - case, the parameter is const char* rather than std::string. - - This way we don't force the compiler to create a string object each - time we're called (since the string is never used unless there's an - error), although a good compiler might have optimized that away in - any case. - */ -static void checkALError(const char *where) -{ - ALenum err = alGetError(); - if(err != AL_NO_ERROR) - { - std::string msg = where; - - const ALchar* errmsg = alGetString(err); - if(errmsg) - fail("\"" + std::string(alGetString(err)) + "\" while " + msg); - else - fail("non-specified error while " + msg + " (did you forget to initialize OpenAL?)"); - } -} - -static void getALFormat(SampleSourcePtr inp, int &fmt, int &rate) -{ - boost::int32_t rate_, ch, bits; - inp->getInfo(&rate_, &ch, &bits); - rate = rate_; - - fmt = 0; - - if(bits == 8) - { - if(ch == 1) fmt = AL_FORMAT_MONO8; - if(ch == 2) fmt = AL_FORMAT_STEREO8; - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) - { - if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD8"); - if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN8"); - } - } - if(bits == 16) - { - if(ch == 1) fmt = AL_FORMAT_MONO16; - if(ch == 2) fmt = AL_FORMAT_STEREO16; - if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); - if(alIsExtensionPresent("AL_EXT_MCFORMATS")) - { - if(ch == 4) fmt = alGetEnumValue("AL_FORMAT_QUAD16"); - if(ch == 6) fmt = alGetEnumValue("AL_FORMAT_51CHN16"); - } - } - - if(fmt == 0) - fail("Unsupported input format"); -} - -/// OpenAL sound output -class Mangle::Sound::OpenAL_Sound : public Sound -{ - ALuint inst; - - // Buffers. Only the first is used for non-streaming sounds. - ALuint bufferID[STREAM_BUF_NUM]; - - // Number of buffers used - int bufNum; - - // Parameters used for filling buffers - int fmt, rate; - - // Poor mans reference counting. Might improve this later. When - // NULL, the buffer has not been set up yet. - int *refCnt; - - bool streaming; - - // Input stream - SampleSourcePtr input; - - OpenAL_Factory *owner; - bool ownerAlive; - - // Used for streamed sound list - OpenAL_Sound *next, *prev; - - void setupBuffer(); - - // Fill data into the given buffer and queue it, if there is any - // data left to queue. Assumes the buffer is already unqueued, if - // necessary. - void queueBuffer(ALuint buf) - { - // If there is no more data, do nothing - if(!input) return; - if(input->eof()) - { - input.reset(); - return; - } - - // Get some new data - size_t bytes = input->read(tmp_buffer, BSIZE); - if(bytes == 0) - { - input.reset(); - return; - } - - // Move data into the OpenAL buffer - alBufferData(buf, fmt, tmp_buffer, bytes, rate); - // Queue it - alSourceQueueBuffers(inst, 1, &buf); - checkALError("Queueing buffer data"); - } - - public: - /// Read samples from the given input buffer - OpenAL_Sound(SampleSourcePtr input, OpenAL_Factory *fact); - - /// Play an existing buffer, with a given ref counter. Used - /// internally for cloning. - OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact); - - ~OpenAL_Sound(); - - // Must be called regularly on streamed sounds - void update() - { - if(!streaming) return; - if(!input) return; - - // Get the number of processed buffers - ALint count; - alGetSourcei(inst, AL_BUFFERS_PROCESSED, &count); - checkALError("getting number of unprocessed buffers"); - - for(int i=0; iupdate(); -} - -void OpenAL_Factory::notifyStreaming(OpenAL_Sound *snd) -{ - // Add the sound to the streaming list - streaming.push_back(snd); -} - -void OpenAL_Factory::notifyDelete(OpenAL_Sound *snd) -{ - // Remove the sound from the stream list - streaming.remove(snd); -} - -OpenAL_Factory::~OpenAL_Factory() -{ - // Notify remaining streamed sounds that we're dying - StreamList::iterator it = streaming.begin(); - for(;it != streaming.end(); it++) - (*it)->notifyOwnerDeath(); - - // Deinitialize sound system - if(didSetup) - { - alcMakeContextCurrent(NULL); - if(context) alcDestroyContext((ALCcontext*)context); - if(device) alcCloseDevice((ALCdevice*)device); - } -} - -// ---- OpenAL_Sound ---- - -void OpenAL_Sound::play() -{ - setupBuffer(); - alSourcePlay(inst); - checkALError("starting playback"); -} - -void OpenAL_Sound::stop() -{ - alSourceStop(inst); - checkALError("stopping"); -} - -void OpenAL_Sound::pause() -{ - alSourcePause(inst); - checkALError("pausing"); -} - -bool OpenAL_Sound::isPlaying() const -{ - ALint state; - alGetSourcei(inst, AL_SOURCE_STATE, &state); - - return state == AL_PLAYING; -} - -void OpenAL_Sound::setVolume(float volume) -{ - if(volume > 1.0) volume = 1.0; - if(volume < 0.0) volume = 0.0; - alSourcef(inst, AL_GAIN, volume); - checkALError("setting volume"); -} - -void OpenAL_Sound::setRange(float a, float b, float) -{ - alSourcef(inst, AL_REFERENCE_DISTANCE, a); - alSourcef(inst, AL_MAX_DISTANCE, b); - checkALError("setting sound ranges"); -} - -void OpenAL_Sound::setPos(float x, float y, float z) -{ - alSource3f(inst, AL_POSITION, x, y, z); - checkALError("setting position"); -} - -void OpenAL_Sound::setPitch(float pitch) -{ - alSourcef(inst, AL_PITCH, pitch); - checkALError("setting pitch"); -} - -void OpenAL_Sound::setRepeat(bool rep) -{ - alSourcei(inst, AL_LOOPING, rep?AL_TRUE:AL_FALSE); -} - -void OpenAL_Sound::setRelative(bool rel) -{ - alSourcei(inst, AL_SOURCE_RELATIVE, rel?AL_TRUE:AL_FALSE); - checkALError("setting relative"); -} - -SoundPtr OpenAL_Sound::clone() -{ - setupBuffer(); - assert(!streaming && "cloning streamed sounds not supported"); - return SoundPtr(new OpenAL_Sound(bufferID[0], refCnt, owner)); -} - -// Constructor used for cloned sounds -OpenAL_Sound::OpenAL_Sound(ALuint buf, int *ref, OpenAL_Factory *fact) - : refCnt(ref), streaming(false), owner(fact), ownerAlive(false) -{ - // Increase the reference count - assert(ref != NULL); - (*refCnt)++; - - // Set up buffer - bufferID[0] = buf; - bufNum = 1; - - // Create a source - alGenSources(1, &inst); - checkALError("creating instance (clone)"); - alSourcei(inst, AL_BUFFER, bufferID[0]); - checkALError("assigning buffer (clone)"); -} - -// Constructor used for original (non-cloned) sounds -OpenAL_Sound::OpenAL_Sound(SampleSourcePtr _input, OpenAL_Factory *fact) - : refCnt(NULL), streaming(false), input(_input), owner(fact), ownerAlive(false) -{ - // Create a source - alGenSources(1, &inst); - checkALError("creating source"); - - // By default, the sound starts out in a buffer-less mode. We don't - // create a buffer until the sound is played. This gives the user - // the chance to call setStreaming(true) first. -} - -void OpenAL_Sound::setupBuffer() -{ - if(refCnt != NULL) return; - - assert(input); - - // Get the format - getALFormat(input, fmt, rate); - - // Create a cheap reference counter for the buffer - refCnt = new int; - *refCnt = 1; - - if(streaming) bufNum = STREAM_BUF_NUM; - else bufNum = 1; - - // Set up the OpenAL buffer(s) - alGenBuffers(bufNum, bufferID); - checkALError("generating buffer(s)"); - assert(bufferID[0] != 0); - - // STREAMING. - if(streaming) - { - // Just queue all the buffers with data and exit. queueBuffer() - // will work correctly also in the case where there is not - // enough data to fill all the buffers. - for(int i=0; inotifyStreaming(this); - ownerAlive = true; - - return; - } - - // NON-STREAMING. We have to load all the data and shove it into the - // buffer. - - // Does the stream support pointer operations? - if(input->hasPtr) - { - // If so, we can read the data directly from the stream - alBufferData(bufferID[0], fmt, input->getPtr(), input->size(), rate); - } - else - { - // Read the entire stream into a temporary buffer first - Mangle::Stream::BufferStream buf(input, 128*1024); - - // Then copy that into OpenAL - alBufferData(bufferID[0], fmt, buf.getPtr(), buf.size(), rate); - } - checkALError("loading sound data"); - - // We're done with the input stream, release the pointer - input.reset(); - - alSourcei(inst, AL_BUFFER, bufferID[0]); - checkALError("assigning buffer"); -} - -OpenAL_Sound::~OpenAL_Sound() -{ - // Stop - alSourceStop(inst); - - // Return sound - alDeleteSources(1, &inst); - - // Notify the factory that we quit. You will hear from our union - // rep. The bool check is to handle cases where the manager goes out - // of scope before the sounds do. In that case, don't try to contact - // the factory. - if(ownerAlive) - owner->notifyDelete(this); - - // Decrease the reference counter - if((-- (*refCnt)) == 0) - { - // We're the last owner. Delete the buffer(s) and the counter - // itself. - alDeleteBuffers(bufNum, bufferID); - checkALError("deleting buffer"); - delete refCnt; - } -} diff --git a/libs/mangle/sound/outputs/openal_out.hpp b/libs/mangle/sound/outputs/openal_out.hpp deleted file mode 100644 index 44d03ecf8..000000000 --- a/libs/mangle/sound/outputs/openal_out.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef MANGLE_SOUND_OPENAL_OUT_H -#define MANGLE_SOUND_OPENAL_OUT_H - -#include "../output.hpp" -#include - -namespace Mangle { -namespace Sound { - -class OpenAL_Sound; - -class OpenAL_Factory : public SoundFactory -{ - void *device; - void *context; - bool didSetup; - - // List of streaming sounds that need to be updated every frame. - typedef std::list StreamList; - StreamList streaming; - - friend class OpenAL_Sound; - void notifyStreaming(OpenAL_Sound*); - void notifyDelete(OpenAL_Sound*); - - public: - /// Initialize object. Pass true (default) if you want the - /// constructor to set up the current ALCdevice and ALCcontext for - /// you. - OpenAL_Factory(bool doSetup = true); - ~OpenAL_Factory(); - - SoundPtr load(const std::string &file) { assert(0); return SoundPtr(); } - SoundPtr load(Stream::StreamPtr input) { assert(0); return SoundPtr(); } - SoundPtr loadRaw(SampleSourcePtr input); - - void update(); - void setListenerPos(float x, float y, float z, - float fx, float fy, float fz, - float ux, float uy, float uz); -}; - -}} // namespaces -#endif diff --git a/libs/mangle/sound/source.hpp b/libs/mangle/sound/source.hpp deleted file mode 100644 index fbe7cf958..000000000 --- a/libs/mangle/sound/source.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef MANGLE_SOUND_SOURCE_H -#define MANGLE_SOUND_SOURCE_H - -#include -#include -#include - -#include "../stream/stream.hpp" - -namespace Mangle { -namespace Sound { - -typedef boost::int32_t int32_t; - -/// A stream containing raw sound data and information about the format -class SampleSource : public Stream::Stream -{ - protected: - bool isEof; - - public: - SampleSource() : isEof(false) {} - - /// Get the sample rate, number of channels, and bits per - /// sample. NULL parameters are ignored. - virtual void getInfo(int32_t *rate, int32_t *channels, int32_t *bits) = 0; - - bool eof() const { return isEof; } - - // Disabled functions by default. You can still override them in - // subclasses. - void seek(size_t pos) { assert(0); } - size_t tell() const { assert(0); return 0; } - size_t size() const { assert(0); return 0; } -}; - -typedef boost::shared_ptr SampleSourcePtr; - -/// A factory interface for loading SampleSources from file or stream -class SampleSourceLoader -{ - public: - /// If true, the stream version of load() works - bool canLoadStream; - - /// If true, the file version of load() works - bool canLoadFile; - - /// Load a sound input source from file (if canLoadFile is true) - virtual SampleSourcePtr load(const std::string &file) = 0; - - /// Load a sound input source from stream (if canLoadStream is true) - virtual SampleSourcePtr load(Stream::StreamPtr input) = 0; - - /// Virtual destructor - virtual ~SampleSourceLoader() {} -}; - -typedef boost::shared_ptr SampleSourceLoaderPtr; - -}} // namespaces -#endif diff --git a/libs/mangle/sound/sources/audiere_source.cpp b/libs/mangle/sound/sources/audiere_source.cpp deleted file mode 100644 index faaa3c8c5..000000000 --- a/libs/mangle/sound/sources/audiere_source.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "audiere_source.hpp" - -#include "../../stream/clients/audiere_file.hpp" - -#include - -using namespace Mangle::Stream; - -static void fail(const std::string &msg) -{ throw std::runtime_error("Audiere exception: " + msg); } - -using namespace audiere; -using namespace Mangle::Sound; - -// --- SampleSource --- - -void AudiereSource::getInfo(Mangle::Sound::int32_t *rate, - Mangle::Sound::int32_t *channels, Mangle::Sound::int32_t *bits) -{ - SampleFormat fmt; - int channels_, rate_; - sample->getFormat(channels_, rate_, fmt); - *channels = channels_; - *rate = rate_; - if(bits) - { - if(fmt == SF_U8) - *bits = 8; - else if(fmt == SF_S16) - *bits = 16; - else assert(0); - } -} - -// --- Constructors --- - -AudiereSource::AudiereSource(const std::string &file) -{ - sample = OpenSampleSource(file.c_str()); - - if(!sample) - fail("Couldn't load file " + file); - - doSetup(); -} - -AudiereSource::AudiereSource(StreamPtr input) -{ - // Use our Stream::AudiereFile implementation to convert a Mangle - // 'Stream' to an Audiere 'File' - sample = OpenSampleSource(new AudiereFile(input)); - if(!sample) - fail("Couldn't load stream"); - - doSetup(); -} - -AudiereSource::AudiereSource(audiere::SampleSourcePtr src) - : sample(src) -{ assert(sample); doSetup(); } - -// Common function called from all constructors -void AudiereSource::doSetup() -{ - assert(sample); - - SampleFormat fmt; - int channels, rate; - sample->getFormat(channels, rate, fmt); - - // Calculate the size of one frame, and pass it to SampleReader. - setup(GetSampleSize(fmt) * channels); - - isSeekable = sample->isSeekable(); - hasPosition = true; - hasSize = true; -} diff --git a/libs/mangle/sound/sources/audiere_source.hpp b/libs/mangle/sound/sources/audiere_source.hpp deleted file mode 100644 index d797c55c8..000000000 --- a/libs/mangle/sound/sources/audiere_source.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef MANGLE_SOUND_AUDIERE_SOURCE_H -#define MANGLE_SOUND_AUDIERE_SOURCE_H - -#include "sample_reader.hpp" - -// audiere.h from 1.9.4 (latest) release uses -// cstring routines like strchr() and strlen() without -// including cstring itself. -#include -#include - -namespace Mangle { -namespace Sound { - -/// A sample source that decodes files using Audiere -class AudiereSource : public SampleReader -{ - audiere::SampleSourcePtr sample; - - size_t readSamples(void *data, size_t length) - { return sample->read(length, data); } - - void doSetup(); - - public: - /// Decode the given sound file - AudiereSource(const std::string &file); - - /// Decode the given sound stream - AudiereSource(Mangle::Stream::StreamPtr src); - - /// Read directly from an existing audiere::SampleSource - AudiereSource(audiere::SampleSourcePtr src); - - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); - - void seek(size_t pos) { sample->setPosition(pos/frameSize); } - size_t tell() const { return sample->getPosition()*frameSize; } - size_t size() const { return sample->getLength()*frameSize; } -}; - -#include "loadertemplate.hpp" - -/// A factory that loads AudiereSources from file and stream -typedef SSL_Template AudiereLoader; - -}} // Namespace -#endif diff --git a/libs/mangle/sound/sources/ffmpeg_source.cpp b/libs/mangle/sound/sources/ffmpeg_source.cpp deleted file mode 100644 index 6349be691..000000000 --- a/libs/mangle/sound/sources/ffmpeg_source.cpp +++ /dev/null @@ -1,189 +0,0 @@ -#include "ffmpeg_source.hpp" - -#include - -using namespace Mangle::Sound; - -// Static output buffer. Not thread safe, but supports multiple -// streams operated from the same thread. -static uint8_t outBuf[AVCODEC_MAX_AUDIO_FRAME_SIZE]; - -static void fail(const std::string &msg) -{ throw std::runtime_error("FFMpeg exception: " + msg); } - -// --- Loader --- - -static bool init = false; - -FFMpegLoader::FFMpegLoader(bool setup) -{ - if(setup && !init) - { - av_register_all(); - av_log_set_level(AV_LOG_ERROR); - init = true; - } -} - -// --- Source --- - -FFMpegSource::FFMpegSource(const std::string &file) -{ - std::string msg; - AVCodec *codec; - - if(av_open_input_file(&FmtCtx, file.c_str(), NULL, 0, NULL) != 0) - fail("Error loading audio file " + file); - - if(av_find_stream_info(FmtCtx) < 0) - { - msg = "Error in file stream " + file; - goto err; - } - - // Pick the first audio stream, if any - for(StreamNum = 0; StreamNum < FmtCtx->nb_streams; StreamNum++) - { - // Pick the first audio stream - if(FmtCtx->streams[StreamNum]->codec->codec_type == CODEC_TYPE_AUDIO) - break; - } - - if(StreamNum == FmtCtx->nb_streams) - fail("File '" + file + "' didn't contain any audio streams"); - - // Open the decoder - CodecCtx = FmtCtx->streams[StreamNum]->codec; - codec = avcodec_find_decoder(CodecCtx->codec_id); - - if(!codec || avcodec_open(CodecCtx, codec) < 0) - { - msg = "Error loading '" + file + "': "; - if(codec) - msg += "coded error"; - else - msg += "no codec found"; - goto err; - } - - // No errors, we're done - return; - - // Handle errors - err: - av_close_input_file(FmtCtx); - fail(msg); -} - -FFMpegSource::~FFMpegSource() -{ - avcodec_close(CodecCtx); - av_close_input_file(FmtCtx); -} - -void FFMpegSource::getInfo(int32_t *rate, int32_t *channels, int32_t *bits) -{ - if(rate) *rate = CodecCtx->sample_rate; - if(channels) *channels = CodecCtx->channels; - if(bits) *bits = 16; -} - -size_t FFMpegSource::read(void *data, size_t length) -{ - if(isEof) return 0; - - size_t left = length; - uint8_t *outPtr = (uint8_t*)data; - - // First, copy over any stored data we might be sitting on - { - size_t s = storage.size(); - size_t copy = s; - if(s) - { - // Make sure there's room - if(copy > left) - copy = left; - - // Copy - memcpy(outPtr, &storage[0], copy); - outPtr += copy; - left -= copy; - - // Is there anything left in the storage? - assert(s>= copy); - s -= copy; - if(s) - { - assert(left == 0); - - // Move it to the start and resize - memmove(&storage[0], &storage[copy], s); - storage.resize(s); - } - } - } - - // Next, get more input data from stream, and decode it - while(left) - { - AVPacket packet; - - // Get the next packet, if any - if(av_read_frame(FmtCtx, &packet) < 0) - break; - - // We only allow one stream per file at the moment - assert((int)StreamNum == packet.stream_index); - - // Decode the packet - int len = AVCODEC_MAX_AUDIO_FRAME_SIZE; - int tmp = avcodec_decode_audio2(CodecCtx, (int16_t*)outBuf, - &len, packet.data, packet.size); - assert(tmp < 0 || tmp == packet.size); - - // We don't need the input packet any longer - av_free_packet(&packet); - - if(tmp < 0) - fail("Error decoding audio stream"); - - // Copy whatever data we got, and advance the pointer - if(len > 0) - { - // copy = how many bytes do we copy now - size_t copy = len; - if(copy > left) - copy = left; - - // len = how many bytes are left uncopied - len -= copy; - - // copy data - memcpy(outPtr, outBuf, copy); - - // left = how much space is left in the caller output - // buffer. This loop repeats as long left is > 0 - left -= copy; - outPtr += copy; - assert(left >= 0); - - if(len > 0) - { - // There were uncopied bytes. Store them for later. - assert(left == 0); - storage.resize(len); - memcpy(&storage[0], outBuf, len); - } - } - } - - // End of loop. Return the number of bytes copied. - assert(left <= length); - - // If we're returning less than asked for, then we're done - if(left > 0) - isEof = true; - - return length - left; -} diff --git a/libs/mangle/sound/sources/ffmpeg_source.hpp b/libs/mangle/sound/sources/ffmpeg_source.hpp deleted file mode 100644 index d422b9809..000000000 --- a/libs/mangle/sound/sources/ffmpeg_source.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef MANGLE_SOUND_FFMPEG_H -#define MANGLE_SOUND_FFMPEG_H - -#include "../source.hpp" -#include -#include - -extern "C" -{ -#include -#include -} - -namespace Mangle { -namespace Sound { - -class FFMpegSource : public SampleSource -{ - AVFormatContext *FmtCtx; - AVCodecContext *CodecCtx; - unsigned int StreamNum; - - std::vector storage; - - public: - /// Decode the given sound file - FFMpegSource(const std::string &file); - - /// Decode the given sound stream (not supported by FFmpeg) - FFMpegSource(Mangle::Stream::StreamPtr src) { assert(0); } - - ~FFMpegSource(); - - // Overrides - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); - size_t read(void *data, size_t length); -}; - -#include "loadertemplate.hpp" - -/// A factory that loads FFMpegSources from file -class FFMpegLoader : public SSL_Template -{ - public: - - /// Sets up the libavcodec library. If you want to do your own - /// setup, send a setup=false parameter. - FFMpegLoader(bool setup=true); -}; - -}} // namespaces -#endif diff --git a/libs/mangle/sound/sources/libsndfile.cpp b/libs/mangle/sound/sources/libsndfile.cpp deleted file mode 100644 index b69a2d436..000000000 --- a/libs/mangle/sound/sources/libsndfile.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "libsndfile.hpp" - -#include -#include - -using namespace Mangle::Stream; - -static void fail(const std::string &msg) -{ throw std::runtime_error("Mangle::libsndfile: " + msg); } - -using namespace Mangle::Sound; - -void SndFileSource::getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) -{ - *_rate = rate; - *_channels = channels; - *_bits = bits; -} - -size_t SndFileSource::readSamples(void *data, size_t length) -{ - // readf_* reads entire frames, including channels - return sf_readf_short((SNDFILE*)handle, (short*)data, length); -} - -SndFileSource::SndFileSource(const std::string &file) -{ - SF_INFO info; - info.format = 0; - handle = sf_open(file.c_str(), SFM_READ, &info); - if(handle == NULL) - fail("Failed to open " + file); - - // I THINK that using sf_read_short forces the library to convert to - // 16 bits no matter what, but the libsndfile docs aren't exactly - // very clear on this point. - channels = info.channels; - rate = info.samplerate; - bits = 16; - - // 16 bits per sample times number of channels - setup(2*channels); -} - -SndFileSource::~SndFileSource() -{ - sf_close((SNDFILE*)handle); -} diff --git a/libs/mangle/sound/sources/libsndfile.hpp b/libs/mangle/sound/sources/libsndfile.hpp deleted file mode 100644 index 7286cf0fe..000000000 --- a/libs/mangle/sound/sources/libsndfile.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef MANGLE_SOUND_SNDFILE_SOURCE_H -#define MANGLE_SOUND_SNDFILE_SOURCE_H - -#include "sample_reader.hpp" - -namespace Mangle { -namespace Sound { - -/// A sample source that decodes files using libsndfile. Supports most -/// formats except mp3. -class SndFileSource : public SampleReader -{ - void *handle; - int channels, rate, bits; - - size_t readSamples(void *data, size_t length); - - public: - /// Decode the given sound file - SndFileSource(const std::string &file); - - /// Decode the given sound stream (not supported) - SndFileSource(Mangle::Stream::StreamPtr src) { assert(0); } - - ~SndFileSource(); - - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); -}; - -#include "loadertemplate.hpp" - -/// A factory that loads SndFileSources from file and stream -typedef SSL_Template SndFileLoader; - -}} // Namespace -#endif diff --git a/libs/mangle/sound/sources/loadertemplate.hpp b/libs/mangle/sound/sources/loadertemplate.hpp deleted file mode 100644 index a27a77d10..000000000 --- a/libs/mangle/sound/sources/loadertemplate.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SSL_TEMPL_H -#define SSL_TEMPL_H - -template -class SSL_Template : public SampleSourceLoader -{ - public: - - SSL_Template() - { - canLoadStream = stream; - canLoadFile = file; - } - - SampleSourcePtr load(const std::string &filename) - { - assert(canLoadFile); - return SampleSourcePtr(new SourceT(filename)); - } - - SampleSourcePtr load(Stream::StreamPtr input) - { - assert(canLoadStream); - return SampleSourcePtr(new SourceT(input)); - } -}; - -#endif diff --git a/libs/mangle/sound/sources/mpg123_source.cpp b/libs/mangle/sound/sources/mpg123_source.cpp deleted file mode 100644 index 24d6ecce1..000000000 --- a/libs/mangle/sound/sources/mpg123_source.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "mpg123_source.hpp" - -#include - -#include - -using namespace Mangle::Stream; - -/* - TODOs: - - - mpg123 impressively enough supports custom stream reading. Which - means we could (and SHOULD!) support reading from Mangle::Streams - as well. But I'll save it til I need it. - - An alternative way to do this is through feeding (which they also - support), but that's more messy. - - - the library also supports output, via various other sources, - including ALSA, OSS, PortAudio, PulseAudio and SDL. Using this - library as a pure output library (if that is possible) would be a - nice shortcut over using those libraries - OTOH it's another - dependency. - - - we could implement seek(), tell() and size(), but they aren't - really necessary. Furthermore, since the returned size is only a - guess, it is not safe to rely on it. - */ - -static void fail(const std::string &msg) -{ throw std::runtime_error("Mangle::Mpg123 exception: " + msg); } - -static void checkError(int err, void *mh = NULL) -{ - if(err != MPG123_OK) - { - std::string msg; - if(mh) msg = mpg123_strerror((mpg123_handle*)mh); - else msg = mpg123_plain_strerror(err); - fail(msg); - } -} - -using namespace Mangle::Sound; - -void Mpg123Source::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits) -{ - // Use the values we found in the constructor - *pRate = rate; - *pChannels = channels; - *pBits = bits; -} - -size_t Mpg123Source::read(void *data, size_t length) -{ - size_t done; - // This is extraordinarily nice. I like this library. - int err = mpg123_read((mpg123_handle*)mh, (unsigned char*)data, length, &done); - assert(done <= length); - if(err == MPG123_DONE) - isEof = true; - else - checkError(err, mh); - return done; -} - -Mpg123Loader::Mpg123Loader(bool setup) -{ - // Do as we're told - if(setup) - { - int err = mpg123_init(); - checkError(err); - } - didSetup = setup; -} - -Mpg123Loader::~Mpg123Loader() -{ - // Deinitialize the library on exit - if(didSetup) - mpg123_exit(); -} - -Mpg123Source::Mpg123Source(const std::string &file) -{ - int err; - - // Create a new handle - mh = mpg123_new(NULL, &err); - if(mh == NULL) - checkError(err, mh); - - mpg123_handle *mhh = (mpg123_handle*)mh; - - // Open the file (hack around constness) - err = mpg123_open(mhh, (char*)file.c_str()); - checkError(err, mh); - - // Get the format - int encoding; - err = mpg123_getformat(mhh, &rate, &channels, &encoding); - checkError(err, mh); - if(encoding != MPG123_ENC_SIGNED_16) - fail("Unsupported encoding in " + file); - - // This is the only bit size we support. - bits = 16; -} - -Mpg123Source::~Mpg123Source() -{ - mpg123_close((mpg123_handle*)mh); - mpg123_delete((mpg123_handle*)mh); -} diff --git a/libs/mangle/sound/sources/mpg123_source.hpp b/libs/mangle/sound/sources/mpg123_source.hpp deleted file mode 100644 index 1ac16b530..000000000 --- a/libs/mangle/sound/sources/mpg123_source.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef MANGLE_SOUND_MPG123_SOURCE_H -#define MANGLE_SOUND_MPG123_SOURCE_H - -#include "../source.hpp" -#include - -namespace Mangle { -namespace Sound { - -/// A sample source that decodes files using libmpg123. Only supports -/// MP3 files. -class Mpg123Source : public SampleSource -{ - void *mh; - long int rate; - int channels, bits; - - public: - /// Decode the given sound file - Mpg123Source(const std::string &file); - - /// Needed by SSL_Template but not yet supported - Mpg123Source(Mangle::Stream::StreamPtr data) - { assert(0); } - - ~Mpg123Source(); - - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); - size_t read(void *data, size_t length); -}; - -#include "loadertemplate.hpp" - -/// A factory that loads Mpg123Sources from file and stream -struct Mpg123Loader : SSL_Template -{ - /** Sets up libmpg123 for you, and closes it on destruction. If you - want to do this yourself, send setup=false. - */ - Mpg123Loader(bool setup=true); - ~Mpg123Loader(); -private: - bool didSetup; -}; - -}} // Namespace -#endif diff --git a/libs/mangle/sound/sources/sample_reader.cpp b/libs/mangle/sound/sources/sample_reader.cpp deleted file mode 100644 index c30de654a..000000000 --- a/libs/mangle/sound/sources/sample_reader.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "sample_reader.hpp" - -#include - -using namespace Mangle::Sound; - -void SampleReader::setup(int size) -{ - pullSize = 0; - frameSize = size; - pullOver = new char[size]; -} - -SampleReader::~SampleReader() -{ - if(pullOver) - delete[] pullOver; -} - -size_t SampleReader::read(void *_data, size_t length) -{ - if(isEof) return 0; - char *data = (char*)_data; - - // Pullsize holds the number of bytes that were copied "extra" at - // the end of LAST round. If non-zero, it also means there is data - // left in the pullOver buffer. - if(pullSize) - { - // Amount of data left - size_t doRead = frameSize - pullSize; - assert(doRead > 0); - - // Make sure we don't read more than we're supposed to - if(doRead > length) doRead = length; - - memcpy(data, pullOver+pullSize, doRead); - - // Update the number of bytes now copied - pullSize += doRead; - assert(pullSize <= frameSize); - - if(pullSize < frameSize) - { - // There is STILL data left in the pull buffer, and we've - // done everything we were supposed to. Leave it and return. - assert(doRead == length); - return doRead; - } - - // Set up variables for further reading below. No need to update - // pullSize, it is overwritten anyway. - length -= doRead; - data += doRead; - } - - // Number of whole frames - size_t frames = length / frameSize; - - // Read the data - size_t res = readSamples(data, frames); - assert(res <= frames); - - // Total bytes read - size_t num = res*frameSize; - data += num; - - if(res < frames) - { - // End of stream. - isEof = true; - // Determine how much we read - return data-(char*)_data; - } - - // Determine the overshoot - pullSize = length - num; - assert(pullSize < frameSize && pullSize >= 0); - - // Are we missing data? - if(pullSize) - { - // Fill in one sample - res = readSamples(pullOver,1); - assert(res == 1 || res == 0); - if(res) - { - // Move as much as we can into the output buffer - memcpy(data, pullOver, pullSize); - data += pullSize; - } - else - // Failed reading, we're out of data - isEof = true; - } - - // Return the total number of bytes stored - return data-(char*)_data; -} diff --git a/libs/mangle/sound/sources/sample_reader.hpp b/libs/mangle/sound/sources/sample_reader.hpp deleted file mode 100644 index 89ddf1f65..000000000 --- a/libs/mangle/sound/sources/sample_reader.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef MANGLE_SOUND_SAMPLE_READER_H -#define MANGLE_SOUND_SAMPLE_READER_H - -#include "../source.hpp" - -namespace Mangle { -namespace Sound { - - /* This is a helper base class for other SampleSource - implementations. Certain sources (like Audiere and libsndfile) - insist on reading whole samples rather than bytes. This class - compensates for that, and allows you to read bytes rather than - samples. - - There are two ways for subclasses to use this class. EITHER call - setup() with the size of frameSize. This will allocate a buffer, - which the destructor frees. OR set frameSize manually and - manipulate the pullOver pointer yourself. In that case you MUST - reset it to NULL if you don't want the destructor to call - delete[] on it. - */ -class SampleReader : public SampleSource -{ - // How much of the above buffer is in use. - int pullSize; - -protected: - // Pullover buffer - char* pullOver; - - // Size of one frame, in bytes. This is also the size of the - // pullOver buffer. - int frameSize; - - // The parameter gives the size of one sample/frame, in bytes. - void setup(int); - - // Read the given number of samples, in multiples of frameSize. Does - // not have to set or respect isEof. - virtual size_t readSamples(void *data, size_t num) = 0; - - public: - SampleReader() : pullSize(0), pullOver(NULL) {} - ~SampleReader(); - size_t read(void *data, size_t length); -}; -}} // Namespace -#endif diff --git a/libs/mangle/sound/sources/stream_source.hpp b/libs/mangle/sound/sources/stream_source.hpp deleted file mode 100644 index 43c605a00..000000000 --- a/libs/mangle/sound/sources/stream_source.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef MANGLE_SOUND_STREAMSOURCE_H -#define MANGLE_SOUND_STREAMSOURCE_H - -#include "../source.hpp" - -namespace Mangle { -namespace Sound { - -/// A class for reading raw samples directly from a stream. -class Stream2Samples : public SampleSource -{ - Mangle::Stream::StreamPtr inp; - int32_t rate, channels, bits; - - public: - Stream2Samples(Mangle::Stream::StreamPtr _inp, int32_t _rate, int32_t _channels, int32_t _bits) - : inp(_inp), rate(_rate), channels(_channels), bits(_bits) - { - isSeekable = inp->isSeekable; - hasPosition = inp->hasPosition; - hasSize = inp->hasSize; - hasPtr = inp->hasPtr; - } - - /// Get the sample rate, number of channels, and bits per - /// sample. NULL parameters are ignored. - void getInfo(int32_t *_rate, int32_t *_channels, int32_t *_bits) - { - if(_rate) *_rate = rate; - if(_channels) *_channels = channels; - if(_bits) *_bits = bits; - } - - size_t read(void *out, size_t count) - { return inp->read(out, count); } - - void seek(size_t pos) { inp->seek(pos); } - size_t tell() const { return inp->tell(); } - size_t size() const { return inp->size(); } - bool eof() const { return inp->eof(); } - const void *getPtr() { return inp->getPtr(); } - const void *getPtr(size_t size) { return inp->getPtr(size); } - const void *getPtr(size_t pos, size_t size) { return inp->getPtr(pos, size); } -}; - -}} // namespaces -#endif diff --git a/libs/mangle/sound/sources/wav_source.cpp b/libs/mangle/sound/sources/wav_source.cpp deleted file mode 100644 index a46b3d27e..000000000 --- a/libs/mangle/sound/sources/wav_source.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "wav_source.hpp" - -#include "../../stream/servers/file_stream.hpp" - -#include - -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -static void fail(const std::string &msg) -{ throw std::runtime_error("Mangle::Wav exception: " + msg); } - -void WavSource::getInfo(int32_t *pRate, int32_t *pChannels, int32_t *pBits) -{ - // Use the values we found in the constructor - *pRate = rate; - *pChannels = channels; - *pBits = bits; -} - -void WavSource::seek(size_t pos) -{ - // Seek the stream and set 'left' - assert(isSeekable); - if(pos > total) pos = total; - input->seek(dataOffset + pos); - left = total-pos; -} - -size_t WavSource::read(void *data, size_t length) -{ - if(length > left) - length = left; - size_t read = input->read(data, length); - if(read < length) - // Something went wrong - fail("WAV read error"); - return length; -} - -void WavSource::open(Mangle::Stream::StreamPtr data) -{ - input = data; - - hasPosition = true; - hasSize = true; - // If we can check position and seek in the input stream, then we - // can seek the wav data too. - isSeekable = input->isSeekable && input->hasPosition; - - // Read header - unsigned int val; - - input->read(&val,4); // header - if(val != 0x46464952) // "RIFF" - fail("Not a WAV file"); - - input->read(&val,4); // size (ignored) - input->read(&val,4); // file format - if(val != 0x45564157) // "WAVE" - fail("Not a valid WAV file"); - - input->read(&val,4); // "fmt " - input->read(&val,4); // chunk size (must be 16) - if(val != 16) - fail("Unsupported WAV format"); - - input->read(&val,2); - if(val != 1) - fail("Non-PCM (compressed) WAV files not supported"); - - // Sound data specification - channels = 0; - input->read(&channels,2); - input->read(&rate, 4); - - // Skip next 6 bytes - input->read(&val, 4); - input->read(&val, 2); - - // Bits per sample - bits = 0; - input->read(&bits,2); - - input->read(&val,4); // Data header - if(val != 0x61746164) // "data" - fail("Expected data block"); - - // Finally, read the data size - input->read(&total,4); - left = total; - - // Store the beginning of the data block for later - if(input->hasPosition) - dataOffset = input->tell(); -} - -WavSource::WavSource(const std::string &file) -{ open(StreamPtr(new FileStream(file))); } diff --git a/libs/mangle/sound/sources/wav_source.hpp b/libs/mangle/sound/sources/wav_source.hpp deleted file mode 100644 index 227f4da73..000000000 --- a/libs/mangle/sound/sources/wav_source.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef MANGLE_SOUND_WAV_SOURCE_H -#define MANGLE_SOUND_WAV_SOURCE_H - -#include "../source.hpp" -#include - -namespace Mangle { -namespace Sound { - -/// WAV file decoder. Has no external library dependencies. -class WavSource : public SampleSource -{ - // Sound info - uint32_t rate, channels, bits; - - // Total size (of output) and bytes left - uint32_t total, left; - - // Offset in input of the beginning of the data block - size_t dataOffset; - - Mangle::Stream::StreamPtr input; - - void open(Mangle::Stream::StreamPtr); - - public: - /// Decode the given sound file - WavSource(const std::string&); - - /// Decode from stream - WavSource(Mangle::Stream::StreamPtr s) - { open(s); } - - void getInfo(int32_t *rate, int32_t *channels, int32_t *bits); - size_t read(void *data, size_t length); - - void seek(size_t); - size_t tell() const { return total-left; } - size_t size() const { return total; } - bool eof() const { return left > 0; } -}; - -#include "loadertemplate.hpp" - -/// A factory that loads WavSources from file and stream -typedef SSL_Template WavLoader; - -}} // Namespace -#endif diff --git a/libs/mangle/sound/tests/.gitignore b/libs/mangle/sound/tests/.gitignore deleted file mode 100644 index 814490404..000000000 --- a/libs/mangle/sound/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_test diff --git a/libs/mangle/sound/tests/Makefile b/libs/mangle/sound/tests/Makefile deleted file mode 100644 index 6fcac72da..000000000 --- a/libs/mangle/sound/tests/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -GCC=g++ -I../ -Wall - -all: audiere_source_test ffmpeg_source_test openal_output_test openal_audiere_test openal_ffmpeg_test openal_mpg123_test openal_sndfile_test wav_source_test openal_various_test - -L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) -I_FFMPEG=-I/usr/include/libavcodec -I/usr/include/libavformat -L_OPENAL=$(shell pkg-config --libs openal) -L_AUDIERE=-laudiere - -wav_source_test: wav_source_test.cpp ../sources/wav_source.cpp - $(GCC) $^ -o $@ - -openal_various_test: openal_various_test.cpp ../sources/mpg123_source.cpp ../sources/wav_source.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ -lmpg123 ${L_OPENAL} - -openal_audiere_test: openal_audiere_test.cpp ../sources/audiere_source.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp ../../stream/clients/audiere_file.cpp - $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) - -openal_ffmpeg_test: openal_ffmpeg_test.cpp ../sources/ffmpeg_source.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ $(L_FFMPEG) $(L_OPENAL) $(I_FFMPEG) - -openal_mpg123_test: openal_mpg123_test.cpp ../sources/mpg123_source.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ -lmpg123 ${L_OPENAL} - -openal_sndfile_test: openal_sndfile_test.cpp ../sources/libsndfile.cpp ../sources/sample_reader.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ -lsndfile ${L_OPENAL} - -openal_output_test: openal_output_test.cpp ../outputs/openal_out.cpp - $(GCC) $^ -o $@ $(L_OPENAL) - -audiere_source_test: audiere_source_test.cpp ../sources/audiere_source.cpp ../../stream/clients/audiere_file.cpp ../sources/sample_reader.cpp - $(GCC) $^ -o $@ $(L_AUDIERE) - -ffmpeg_source_test: ffmpeg_source_test.cpp ../sources/ffmpeg_source.cpp - $(GCC) $^ -o $@ $(L_FFMPEG) $(I_FFMPEG) - -clean: - rm *_test diff --git a/libs/mangle/sound/tests/audiere_source_test.cpp b/libs/mangle/sound/tests/audiere_source_test.cpp deleted file mode 100644 index 637d743b2..000000000 --- a/libs/mangle/sound/tests/audiere_source_test.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../sources/audiere_source.hpp" - -#include -#include - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -// Contents and size of cow.raw -void *orig; -size_t orig_size; - -void run(SampleSourcePtr &src) -{ - size_t ss = src->size(); - assert(ss == orig_size); - - cout << "Source size: " << ss << endl; - int rate, channels, bits; - src->getInfo(&rate, &channels, &bits); - cout << "rate=" << rate << "\nchannels=" << channels - << "\nbits=" << bits << endl; - - cout << "Reading entire buffer into memory\n"; - void *buf = malloc(ss); - src->read(buf, ss); - - cout << "Comparing...\n"; - if(memcmp(buf, orig, ss) != 0) - { - cout << "Oops!\n"; - assert(0); - } - - cout << "Done\n"; -} - -int main() -{ - { - cout << "Reading cow.raw first\n"; - FileStream tmp("cow.raw"); - orig_size = tmp.size(); - cout << "Size: " << orig_size << endl; - orig = malloc(orig_size); - tmp.read(orig, orig_size); - cout << "Done\n"; - } - - { - cout << "\nLoading cow.wav by filename:\n"; - SampleSourcePtr cow_file( new AudiereSource("cow.wav") ); - run(cow_file); - } - - { - cout << "\nLoading cow.wav by stream:\n"; - StreamPtr inp( new FileStream("cow.wav") ); - SampleSourcePtr cow_stream( new AudiereSource(inp) ); - run(cow_stream); - } - - return 0; -} diff --git a/libs/mangle/sound/tests/cow.raw b/libs/mangle/sound/tests/cow.raw deleted file mode 100644 index c4d155bbfb1caba2e41b1a97e01f29276c945c07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37502 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 F{|CY68e9MX diff --git a/libs/mangle/sound/tests/cow.wav b/libs/mangle/sound/tests/cow.wav deleted file mode 100644 index 494e6c4ac12a3d318532fdf8178d72f775bc40af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37546 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�b}Ou2(@Nod9rZet}z#nWoL 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 - -#include "../../stream/servers/file_stream.hpp" -#include "../sources/ffmpeg_source.hpp" - -#include -#include - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -// Contents and size of cow.raw -void *orig; -size_t orig_size; - -void run(SampleSourcePtr &src) -{ - int rate, channels, bits; - src->getInfo(&rate, &channels, &bits); - cout << "rate=" << rate << "\nchannels=" << channels - << "\nbits=" << bits << endl; - - cout << "Reading entire buffer into memory\n"; - void *buf = malloc(orig_size); - size_t ss = src->read(buf, orig_size); - cout << "Actually read: " << ss << endl; - assert(ss == orig_size); - - cout << "Comparing...\n"; - if(memcmp(buf, orig, ss) != 0) - { - cout << "Oops!\n"; - assert(0); - } - - cout << "Done\n"; -} - -int main() -{ - { - cout << "Reading cow.raw first\n"; - FileStream tmp("cow.raw"); - orig_size = tmp.size(); - cout << "Size: " << orig_size << endl; - orig = malloc(orig_size); - tmp.read(orig, orig_size); - cout << "Done\n"; - } - - // Initializes the library, not used for anything else. - FFMpegLoader fm; - - { - cout << "\nLoading cow.wav by filename:\n"; - SampleSourcePtr cow_file( new FFMpegSource("cow.wav") ); - run(cow_file); - } - - return 0; -} diff --git a/libs/mangle/sound/tests/openal_audiere_test.cpp b/libs/mangle/sound/tests/openal_audiere_test.cpp deleted file mode 100644 index ced7fe5d2..000000000 --- a/libs/mangle/sound/tests/openal_audiere_test.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_audiere.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_Audiere_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main() -{ - play("cow.wav"); - play("owl.ogg"); - play("cow.wav", true); - return 0; -} diff --git a/libs/mangle/sound/tests/openal_ffmpeg_test.cpp b/libs/mangle/sound/tests/openal_ffmpeg_test.cpp deleted file mode 100644 index d4b8e9300..000000000 --- a/libs/mangle/sound/tests/openal_ffmpeg_test.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_ffmpeg.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_FFMpeg_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main() -{ - play("cow.wav"); - play("owl.ogg"); - play("cow.wav", true); - return 0; -} diff --git a/libs/mangle/sound/tests/openal_mpg123_test.cpp b/libs/mangle/sound/tests/openal_mpg123_test.cpp deleted file mode 100644 index fef1a5605..000000000 --- a/libs/mangle/sound/tests/openal_mpg123_test.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_mpg123.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_Mpg123_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->setStreaming(true); - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main(int argc, char**argv) -{ - if(argc != 2) - cout << "Please specify an MP3 file\n"; - else - play(argv[1]); - return 0; -} diff --git a/libs/mangle/sound/tests/openal_output_test.cpp b/libs/mangle/sound/tests/openal_output_test.cpp deleted file mode 100644 index a8059ec65..000000000 --- a/libs/mangle/sound/tests/openal_output_test.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../sources/stream_source.hpp" -#include "../outputs/openal_out.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -int main() -{ - cout << "Loading cow.raw\n"; - - int rate = 11025; - int chan = 1; - int bits = 16; - - cout << " rate=" << rate << "\n channels=" << chan - << "\n bits=" << bits << endl; - - StreamPtr file( new FileStream("cow.raw") ); - SampleSourcePtr source( new Stream2Samples( file, rate, chan, bits)); - - cout << "Playing\n"; - - OpenAL_Factory mg; - - SoundPtr snd = mg.loadRaw(source); - - try - { - // Try setting all kinds of stuff before playing. OpenAL_Sound - // uses delayed buffer loading, but these should still work - // without a buffer. - snd->stop(); - snd->pause(); - snd->setVolume(0.8); - snd->setPitch(0.9); - - // Also test streaming, since all the other examples test - // non-streaming sounds. - snd->setStreaming(true); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } - return 0; -} diff --git a/libs/mangle/sound/tests/openal_sndfile_test.cpp b/libs/mangle/sound/tests/openal_sndfile_test.cpp deleted file mode 100644 index bd5f117a5..000000000 --- a/libs/mangle/sound/tests/openal_sndfile_test.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_sndfile.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_SndFile_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main() -{ - play("cow.wav"); - play("owl.ogg"); - play("cow.wav", true); - return 0; -} diff --git a/libs/mangle/sound/tests/openal_various_test.cpp b/libs/mangle/sound/tests/openal_various_test.cpp deleted file mode 100644 index 9426a672e..000000000 --- a/libs/mangle/sound/tests/openal_various_test.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include - -#include "../../stream/servers/file_stream.hpp" -#include "../filters/openal_various.hpp" - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; - -OpenAL_Various_Factory mg; - -void play(const char* name, bool stream=false) -{ - // Only load streams if the backend supports it - if(stream && !mg.canLoadStream) - return; - - cout << "Playing " << name; - if(stream) cout << " (from stream)"; - cout << "\n"; - - SoundPtr snd; - - try - { - if(stream) - snd = mg.load(StreamPtr(new FileStream(name))); - else - snd = mg.load(name); - - snd->play(); - - while(snd->isPlaying()) - { - usleep(10000); - if(mg.needsUpdate) mg.update(); - } - } - catch(exception &e) - { - cout << " ERROR: " << e.what() << "\n"; - } -} - -int main() -{ - play("cow.wav"); - play("cow.wav", true); - return 0; -} diff --git a/libs/mangle/sound/tests/output/audiere_source_test.out b/libs/mangle/sound/tests/output/audiere_source_test.out deleted file mode 100644 index 47a5a9e41..000000000 --- a/libs/mangle/sound/tests/output/audiere_source_test.out +++ /dev/null @@ -1,21 +0,0 @@ -Reading cow.raw first -Size: 37502 -Done - -Loading cow.wav by filename: -Source size: 37502 -rate=11025 -channels=1 -bits=16 -Reading entire buffer into memory -Comparing... -Done - -Loading cow.wav by stream: -Source size: 37502 -rate=11025 -channels=1 -bits=16 -Reading entire buffer into memory -Comparing... -Done diff --git a/libs/mangle/sound/tests/output/ffmpeg_source_test.out b/libs/mangle/sound/tests/output/ffmpeg_source_test.out deleted file mode 100644 index 1c7d49113..000000000 --- a/libs/mangle/sound/tests/output/ffmpeg_source_test.out +++ /dev/null @@ -1,12 +0,0 @@ -Reading cow.raw first -Size: 37502 -Done - -Loading cow.wav by filename: -rate=11025 -channels=1 -bits=16 -Reading entire buffer into memory -Actually read: 37502 -Comparing... -Done diff --git a/libs/mangle/sound/tests/output/openal_audiere_test.out b/libs/mangle/sound/tests/output/openal_audiere_test.out deleted file mode 100644 index 4fe01eac2..000000000 --- a/libs/mangle/sound/tests/output/openal_audiere_test.out +++ /dev/null @@ -1,3 +0,0 @@ -Playing cow.wav -Playing owl.ogg -Playing cow.wav (from stream) diff --git a/libs/mangle/sound/tests/output/openal_ffmpeg_test.out b/libs/mangle/sound/tests/output/openal_ffmpeg_test.out deleted file mode 100644 index 96e1db0f9..000000000 --- a/libs/mangle/sound/tests/output/openal_ffmpeg_test.out +++ /dev/null @@ -1,2 +0,0 @@ -Playing cow.wav -Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_mpg123_test.out b/libs/mangle/sound/tests/output/openal_mpg123_test.out deleted file mode 100644 index e55dabbb1..000000000 --- a/libs/mangle/sound/tests/output/openal_mpg123_test.out +++ /dev/null @@ -1 +0,0 @@ -Please specify an MP3 file diff --git a/libs/mangle/sound/tests/output/openal_output_test.out b/libs/mangle/sound/tests/output/openal_output_test.out deleted file mode 100644 index 04392a72e..000000000 --- a/libs/mangle/sound/tests/output/openal_output_test.out +++ /dev/null @@ -1,5 +0,0 @@ -Loading cow.raw - rate=11025 - channels=1 - bits=16 -Playing diff --git a/libs/mangle/sound/tests/output/openal_sndfile_test.out b/libs/mangle/sound/tests/output/openal_sndfile_test.out deleted file mode 100644 index 96e1db0f9..000000000 --- a/libs/mangle/sound/tests/output/openal_sndfile_test.out +++ /dev/null @@ -1,2 +0,0 @@ -Playing cow.wav -Playing owl.ogg diff --git a/libs/mangle/sound/tests/output/openal_various_test.out b/libs/mangle/sound/tests/output/openal_various_test.out deleted file mode 100644 index f25a55513..000000000 --- a/libs/mangle/sound/tests/output/openal_various_test.out +++ /dev/null @@ -1 +0,0 @@ -Playing cow.wav diff --git a/libs/mangle/sound/tests/output/wav_source_test.out b/libs/mangle/sound/tests/output/wav_source_test.out deleted file mode 100644 index b6fc8e6fc..000000000 --- a/libs/mangle/sound/tests/output/wav_source_test.out +++ /dev/null @@ -1,12 +0,0 @@ -Source size: 37502 -rate=11025 -channels=1 -bits=16 -Reading entire buffer into memory - -Reading cow.raw -Size: 37502 - -Comparing... - -Done diff --git a/libs/mangle/sound/tests/owl.ogg b/libs/mangle/sound/tests/owl.ogg deleted file mode 100644 index e992f24d48206663c794ce1f835b483aed730319..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18641 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"`&tnb`wrwjv1+m1l^X62V>RQ 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 uxXE2GB)VyFe*N* "output/$a.out" - git add "output/$a.out" - fi -done diff --git a/libs/mangle/sound/tests/wav_source_test.cpp b/libs/mangle/sound/tests/wav_source_test.cpp deleted file mode 100644 index 749af1849..000000000 --- a/libs/mangle/sound/tests/wav_source_test.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include - -#include "../sources/wav_source.hpp" -#include "../../stream/servers/file_stream.hpp" - -#include -#include - -using namespace std; -using namespace Mangle::Sound; -using namespace Mangle::Stream; - -int main() -{ - WavSource wav("cow.wav"); - - cout << "Source size: " << wav.size() << endl; - int rate, channels, bits; - wav.getInfo(&rate, &channels, &bits); - cout << "rate=" << rate << "\nchannels=" << channels - << "\nbits=" << bits << endl; - - cout << "Reading entire buffer into memory\n"; - void *buf = malloc(wav.size()); - wav.read(buf, wav.size()); - - cout << "\nReading cow.raw\n"; - FileStream tmp("cow.raw"); - cout << "Size: " << tmp.size() << endl; - void *buf2 = malloc(tmp.size()); - tmp.read(buf2, tmp.size()); - - cout << "\nComparing...\n"; - if(tmp.size() != wav.size()) - { - cout << "SIZE MISMATCH!\n"; - assert(0); - } - - if(memcmp(buf, buf2, wav.size()) != 0) - { - cout << "CONTENT MISMATCH!\n"; - assert(0); - } - - cout << "\nDone\n"; - return 0; -} diff --git a/libs/openengine/sound/sndmanager.cpp b/libs/openengine/sound/sndmanager.cpp deleted file mode 100644 index 02c6ba1e7..000000000 --- a/libs/openengine/sound/sndmanager.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include "sndmanager.hpp" - -#include "../misc/list.hpp" -#include - -using namespace OEngine::Sound; -using namespace Mangle::Sound; - -/** This is our own internal implementation of the - Mangle::Sound::Sound interface. This class links a SoundPtr to - itself and prevents itself from being deleted as long as the sound - is playing. - */ -struct OEngine::Sound::ManagedSound : SoundFilter -{ -private: - /** Who's your daddy? This is set if and only if we are listed - internally in the given SoundManager. - - It may be NULL if the manager has been deleted but the user - keeps their own SoundPtrs to the object. - */ - SoundManager *mgr; - - /** Keep a weak pointer to ourselves, which we convert into a - 'strong' pointer when we are playing. When 'self' is pointing to - ourselves, the object will never be deleted. - - This is used to make sure the sound is not deleted while - playing, unless it is explicitly ordered to do so by the - manager. - - TODO: This kind of construct is useful. If we need it elsewhere - later, template it. It would be generally useful in any system - where we poll to check if a resource is still needed, but where - manual references are allowed. - */ - WSoundPtr weak; - SoundPtr self; - - // Keep this object from being deleted - void lock() - { - self = SoundPtr(weak); - } - - // Release the lock. This may or may not delete the object. Never do - // anything after calling unlock()! - void unlock() - { - self.reset(); - } - -public: - // Used for putting ourselves in linked lists - ManagedSound *next, *prev; - - /** Detach this sound from its manager. This means that the manager - will no longer know we exist. Typically only called when either - the sound or the manager is about to get deleted. - - Since this means update() will no longer be called, we also have - to unlock the sound manually since it will no longer be able to - do that itself. This means that the sound may be deleted, even - if it is still playing, when the manager is deleted. - - However, you are still allowed to keep and manage your own - SoundPtr references, but the lock/unlock system is disabled - after the manager is gone. - */ - void detach() - { - if(mgr) - { - mgr->detach(this); - mgr = NULL; - } - - // Unlock must be last command. Object may get deleted at this - // point. - unlock(); - } - - ManagedSound(SoundPtr snd, SoundManager *mg) - : SoundFilter(snd), mgr(mg) - {} - ~ManagedSound() { detach(); } - - // Needed to set up the weak pointer - void setup(SoundPtr self) - { - weak = WSoundPtr(self); - } - - // Override play() to mark the object as locked - void play() - { - SoundFilter::play(); - - // Lock the object so that it is not deleted while playing. Only - // do this if we have a manager, otherwise the object will never - // get unlocked. - if(mgr) lock(); - } - - // Called regularly by the manager - void update() - { - // If we're no longer playing, don't force object retention. - if(!isPlaying()) - unlock(); - - // unlock() may delete the object, so don't do anything below this - // point. - } - - SoundPtr clone() - { - // Cloning only works when we have a manager. - assert(mgr); - return mgr->wrap(client->clone()); - } -}; - -struct SoundManager::SoundManagerList -{ -private: - // A linked list of ManagedSound objects. - typedef Misc::List SoundList; - SoundList list; - -public: - // Add a new sound to the list - void addNew(ManagedSound* snd) - { - list.insert(snd); - } - - // Remove a sound from the list - void remove(ManagedSound *snd) - { - list.remove(snd); - } - - // Number of sounds in the list - int numSounds() { return list.getNum(); } - - // Update all sounds - void updateAll() - { - ManagedSound *s = list.getHead(); - while(s) - { - ManagedSound *cur = s; - // Propagate first, since update() may delete object - s = s->next; - cur->update(); - } - } - - // Detach and unlock all sounds - void detachAll() - { - ManagedSound *s = list.getHead(); - while(s) - { - ManagedSound *cur = s; - s = s->next; - cur->detach(); - } - } -}; - -SoundManager::SoundManager(SoundFactoryPtr fact) - : FactoryFilter(fact) -{ - needsUpdate = true; - list = new SoundManagerList; -} - -SoundManager::~SoundManager() -{ - // Detach all sounds - list->detachAll(); -} - -SoundPtr SoundManager::wrap(SoundPtr client) -{ - // Create and set up the sound wrapper - ManagedSound *snd = new ManagedSound(client,this); - SoundPtr ptr(snd); - snd->setup(ptr); - - // Add ourselves to the list of all sounds - list->addNew(snd); - - return ptr; -} - -// Remove the sound from this manager. -void SoundManager::detach(ManagedSound *sound) -{ - list->remove(sound); -} - -int SoundManager::numSounds() -{ - return list->numSounds(); -} - -void SoundManager::update() -{ - // Update all the sounds we own - list->updateAll(); - - // Update the source if it needs it - if(client->needsUpdate) - client->update(); -} diff --git a/libs/openengine/sound/sndmanager.hpp b/libs/openengine/sound/sndmanager.hpp deleted file mode 100644 index 5ea0c4fc3..000000000 --- a/libs/openengine/sound/sndmanager.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef OENGINE_SOUND_MANAGER_H -#define OENGINE_SOUND_MANAGER_H - -#include - -namespace OEngine -{ - namespace Sound - { - using namespace Mangle::Sound; - - class ManagedSound; - - /** A manager of Mangle::Sounds. - - The sound manager is a wrapper around the more low-level - SoundFactory - although it is also itself an implementation of - SoundFactory. It will: - - keep a list of all created sounds - - let you iterate the list - - keep references to playing sounds so you don't have to - - auto-release references to sounds that are finished playing - (ie. deleting them if you're not referencing them) - */ - class SoundManager : public FactoryFilter - { - // Shove the implementation details into the cpp file. - struct SoundManagerList; - SoundManagerList *list; - - // Create a new sound wrapper based on the given source sound. - SoundPtr wrap(SoundPtr snd); - - /** Internal function. Will completely disconnect the given - sound from this manager. Called from ManagedSound. - */ - friend class ManagedSound; - void detach(ManagedSound *sound); - public: - SoundManager(SoundFactoryPtr fact); - ~SoundManager(); - void update(); - - /// Get number of sounds currently managed by this manager. - int numSounds(); - - SoundPtr loadRaw(SampleSourcePtr input) - { return wrap(client->loadRaw(input)); } - - SoundPtr load(Mangle::Stream::StreamPtr input) - { return wrap(client->load(input)); } - - SoundPtr load(const std::string &file) - { return wrap(client->load(file)); } - - // Play a sound immediately, and release when done unless you - // keep the returned SoundPtr. - SoundPtr play(Mangle::Stream::StreamPtr sound) - { - SoundPtr snd = load(sound); - snd->play(); - return snd; - } - - SoundPtr play(const std::string &sound) - { - SoundPtr snd = load(sound); - snd->play(); - return snd; - } - - // Ditto for 3D sounds - SoundPtr play3D(Mangle::Stream::StreamPtr sound, float x, float y, float z) - { - SoundPtr snd = load(sound); - snd->setPos(x,y,z); - snd->play(); - return snd; - } - - SoundPtr play3D(const std::string &sound, float x, float y, float z) - { - SoundPtr snd = load(sound); - snd->setPos(x,y,z); - snd->play(); - return snd; - } - }; - - typedef boost::shared_ptr SoundManagerPtr; - } -} -#endif diff --git a/libs/openengine/sound/tests/Makefile b/libs/openengine/sound/tests/Makefile deleted file mode 100644 index 04952167f..000000000 --- a/libs/openengine/sound/tests/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -GCC=g++ -I../ - -all: sound_manager_test sound_3d_test - -L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) -L_OPENAL=$(shell pkg-config --libs openal) -L_AUDIERE=-laudiere - -sound_manager_test: sound_manager_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp - $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. - -sound_3d_test: sound_3d_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp - $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. - -clean: - rm *_test diff --git a/libs/openengine/sound/tests/output/sound_3d_test.out b/libs/openengine/sound/tests/output/sound_3d_test.out deleted file mode 100644 index a443c84f0..000000000 --- a/libs/openengine/sound/tests/output/sound_3d_test.out +++ /dev/null @@ -1,3 +0,0 @@ -Playing at 0,0,0 -Playing at 1,1,0 -Playing at -1,0,0 diff --git a/libs/openengine/sound/tests/output/sound_manager_test.out b/libs/openengine/sound/tests/output/sound_manager_test.out deleted file mode 100644 index 2b458493d..000000000 --- a/libs/openengine/sound/tests/output/sound_manager_test.out +++ /dev/null @@ -1,5 +0,0 @@ -Playing ../../mangle/sound/tests/cow.wav -Replaying -pause -restart -Done playing. diff --git a/libs/openengine/sound/tests/sound_3d_test.cpp b/libs/openengine/sound/tests/sound_3d_test.cpp deleted file mode 100644 index f5b197fd0..000000000 --- a/libs/openengine/sound/tests/sound_3d_test.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include - -#include -#include - -#include - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; -using namespace OEngine::Sound; - -const std::string sound = "../../mangle/sound/tests/cow.wav"; - -SoundManagerPtr m; - -// Play and wait for finish -void play(float x, float y, float z) -{ - cout << "Playing at " << x << "," << y << "," << z << endl; - - SoundPtr snd = m->play3D(sound,x,y,z); - - while(snd->isPlaying()) - { - usleep(10000); - m->update(); - } -} - -int main() -{ - SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); - SoundManagerPtr mg(new SoundManager(oaf)); - m = mg; - - mg->setListenerPos(0,0,0,0,1,0,0,0,1); - - play(0,0,0); - play(1,1,0); - play(-1,0,0); - - return 0; -} diff --git a/libs/openengine/sound/tests/sound_manager_test.cpp b/libs/openengine/sound/tests/sound_manager_test.cpp deleted file mode 100644 index 3794c4a3c..000000000 --- a/libs/openengine/sound/tests/sound_manager_test.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include - -#include -#include - -#include - -using namespace std; -using namespace Mangle::Stream; -using namespace Mangle::Sound; -using namespace OEngine::Sound; - -const std::string sound = "../../mangle/sound/tests/cow.wav"; - -int main() -{ - SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); - SoundManagerPtr mg(new SoundManager(oaf)); - - cout << "Playing " << sound << "\n"; - - assert(mg->numSounds() == 0); - - /** Start the sound playing, and then let the pointer go out of - scope. Lower-level players (like 'oaf' above) will immediately - delete the sound. SoundManager OTOH will keep it until it's - finished. - */ - mg->play(sound); - - assert(mg->numSounds() == 1); - - // Loop while there are still sounds to manage - while(mg->numSounds() != 0) - { - assert(mg->numSounds() == 1); - usleep(10000); - if(mg->needsUpdate) - mg->update(); - } - - SoundPtr snd = mg->play(sound); - cout << "Replaying\n"; - int i = 0; - while(mg->numSounds() != 0) - { - assert(mg->numSounds() == 1); - usleep(10000); - if(mg->needsUpdate) - mg->update(); - - if(i++ == 70) - { - cout << "pause\n"; - snd->pause(); - } - if(i == 130) - { - cout << "restart\n"; - snd->play(); - // Let the sound go out of scope - snd.reset(); - } - } - - cout << "Done playing.\n"; - - assert(mg->numSounds() == 0); - - return 0; -} diff --git a/libs/openengine/sound/tests/test.sh b/libs/openengine/sound/tests/test.sh deleted file mode 100755 index 2d07708ad..000000000 --- a/libs/openengine/sound/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done From 45b612ab3bf32b98209e8a8b205bce573bd358a7 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 16 Mar 2012 22:12:17 -0700 Subject: [PATCH 012/152] Add a skeleton output classs using OpenAL --- CMakeLists.txt | 12 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwsound/openal_output.cpp | 60 +++++++++ apps/openmw/mwsound/openal_output.hpp | 29 +++++ apps/openmw/mwsound/soundmanager.cpp | 26 ++-- apps/openmw/mwsound/soundmanager.hpp | 167 ++++++++++++++------------ 6 files changed, 204 insertions(+), 92 deletions(-) create mode 100644 apps/openmw/mwsound/openal_output.cpp create mode 100644 apps/openmw/mwsound/openal_output.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d06085322..876ea8273 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,12 @@ set(OENGINE_BULLET ${LIBDIR}/openengine/bullet/BulletShapeLoader.h ) +set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET}) +source_group(libs\\openengine FILES ${OENGINE_ALL}) + +set(OPENMW_LIBS ${MANGLE_ALL} ${OENGINE_ALL}) +set(OPENMW_LIBS_HEADER) + # Sound setup if (USE_AUDIERE) find_package(Audiere REQUIRED) @@ -141,12 +147,6 @@ if (USE_MPG123) set(SOUND_DEFINE -DOPENMW_USE_MPG123) endif (USE_MPG123) -set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET}) -source_group(libs\\openengine FILES ${OENGINE_ALL}) - -set(OPENMW_LIBS ${MANGLE_ALL} ${OENGINE_ALL}) -set(OPENMW_LIBS_HEADER) - # Platform specific if (WIN32) set(PLATFORM_INCLUDE_DIR "platform") diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 77f465b4c..e9002f111 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -38,7 +38,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanager + soundmanager openal_output ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp new file mode 100644 index 000000000..59e9037c4 --- /dev/null +++ b/apps/openmw/mwsound/openal_output.cpp @@ -0,0 +1,60 @@ +#include "openal_output.hpp" + +namespace MWSound +{ + +static void fail(const std::string &msg) +{ throw std::runtime_error("OpenAL exception: " + msg); } + + +bool OpenAL_Output::Initialize(const std::string &devname) +{ + if(Context) + fail("Device already initialized"); + + Device = alcOpenDevice(devname.c_str()); + if(!Device) + { + std::cout << "Failed to open \""<Initialize()) + { + Output.reset(); + return; + } + // The music library will accept these filetypes // If none is given then it will accept all filetypes std::vector acceptableExtensions; @@ -66,13 +72,11 @@ namespace MWSound std::string anything = "anything"; // anything is better that a segfault mCurrentPlaylist = mMusicLibrary.section(anything, mFSStrict); // now points to an empty path - - std::cout << "Sound output: " << SOUND_OUT << std::endl; - std::cout << "Sound decoder: " << SOUND_IN << std::endl; } SoundManager::~SoundManager() { + Output.reset(); } // Convert a soundId to file name, and modify the volume @@ -258,7 +262,7 @@ namespace MWSound float min, max; const std::string &file = lookup(soundId, volume, min, max); if(file != "") - add(file, ptr, soundId, volume, pitch, min, max, loop, untracked); + std::cout << "Cannot play " << file << ", skipping.\n"; } void SoundManager::stopSound3D(MWWorld::Ptr ptr, const std::string& soundId) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 9db7fe1b7..f77222cb4 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -21,103 +21,122 @@ namespace MWWorld namespace MWSound { + class Sound_Output; + class SoundManager { + // This is used for case insensitive and slash-type agnostic file + // finding. It takes DOS paths (any case, \\ slashes or / slashes) + // relative to the sound dir, and translates them into full paths + // of existing files in the filesystem, if they exist. + bool mFSStrict; + + MWWorld::Environment& mEnvironment; + + std::auto_ptr Output; + + void streamMusicFull(const std::string& filename); + ///< Play a soundifle + /// \param absolute filename + + // A list of all sound files used to lookup paths + Files::PathContainer mSoundFiles; + + // A library of all Music file paths stored by the folder they are contained in + Files::FileLibrary mMusicLibrary; + + // Points to the current playlist of music files stored in the music library + const Files::PathContainer* mCurrentPlaylist; + + std::string lookup(const std::string &soundId, + float &volume, float &min, float &max); + void add(const std::string &file, + MWWorld::Ptr ptr, const std::string &id, + float volume, float pitch, float min, float max, + bool loop, bool untracked=false); + void remove(MWWorld::Ptr ptr, const std::string &id = ""); + bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; + void removeCell(const MWWorld::Ptr::CellStore *cell); + void updatePositions(MWWorld::Ptr ptr); + + public: + SoundManager(Ogre::Root*, Ogre::Camera*, + const Files::PathContainer& dataDir, bool useSound, bool fsstrict, + MWWorld::Environment& environment); + ~SoundManager(); - // This is used for case insensitive and slash-type agnostic file - // finding. It takes DOS paths (any case, \\ slashes or / slashes) - // relative to the sound dir, and translates them into full paths - // of existing files in the filesystem, if they exist. - bool mFSStrict; - - MWWorld::Environment& mEnvironment; - - void streamMusicFull (const std::string& filename); - ///< Play a soundifle - /// \param absolute filename - - // A list of all sound files used to lookup paths - Files::PathContainer mSoundFiles; - - // A library of all Music file paths stored by the folder they are contained in - Files::FileLibrary mMusicLibrary; + void stopMusic(); + ///< Stops music if it's playing - // Points to the current playlist of music files stored in the music library - const Files::PathContainer* mCurrentPlaylist; + void streamMusic(const std::string& filename); + ///< Play a soundifle + /// \param filename name of a sound file in "Music/" in the data directory. - std::string lookup(const std::string &soundId, - float &volume, float &min, float &max); - void add(const std::string &file, - MWWorld::Ptr ptr, const std::string &id, - float volume, float pitch, float min, float max, - bool loop, bool untracked=false); - void remove(MWWorld::Ptr ptr, const std::string &id = ""); - bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; - void removeCell(const MWWorld::Ptr::CellStore *cell); - void updatePositions(MWWorld::Ptr ptr); + void startRandomTitle(); + ///< Starts a random track from the current playlist - public: + bool isMusicPlaying(); + ///< Returns true if music is playing - SoundManager(Ogre::Root*, Ogre::Camera*, - const Files::PathContainer& dataDir, bool useSound, bool fsstrict, - MWWorld::Environment& environment); - ~SoundManager(); + bool setPlaylist(std::string playlist=""); + ///< Set the playlist to an existing folder + /// \param name of the folder that contains the playlist + /// if none is set then it is set to an empty playlist + /// \return Return true if the previous playlist was the same - void stopMusic(); - ///< Stops music if it's playing + void playPlaylist(std::string playlist=""); + ///< Start playing music from the selected folder + /// \param name of the folder that contains the playlist + /// if none is set then it plays from the current playlist - void streamMusic(const std::string& filename); - ///< Play a soundifle - /// \param filename name of a sound file in "Music/" in the data directory. + void say(MWWorld::Ptr reference, const std::string& filename); + ///< Make an actor say some text. + /// \param filename name of a sound file in "Sound/Vo/" in the data directory. - void startRandomTitle(); - ///< Starts a random track from the current playlist + bool sayDone(MWWorld::Ptr reference) const; + ///< Is actor not speaking? - bool isMusicPlaying(); - ///< Returns true if music is playing + void playSound(const std::string& soundId, float volume, float pitch, bool loop=false); + ///< Play a sound, independently of 3D-position - bool setPlaylist(std::string playlist=""); - ///< Set the playlist to an existing folder - /// \param name of the folder that contains the playlist - /// if none is set then it is set to an empty playlist - /// \return Return true if the previous playlist was the same + void playSound3D(MWWorld::Ptr reference, const std::string& soundId, + float volume, float pitch, bool loop, + bool untracked=false); + ///< Play a sound from an object - void playPlaylist(std::string playlist=""); - ///< Start playing music from the selected folder - /// \param name of the folder that contains the playlist - /// if none is set then it plays from the current playlist + void stopSound3D(MWWorld::Ptr reference, const std::string& soundId=""); + ///< Stop the given object from playing the given sound, If no soundId is given, + /// all sounds for this reference will stop. - void say (MWWorld::Ptr reference, const std::string& filename); - ///< Make an actor say some text. - /// \param filename name of a sound file in "Sound/Vo/" in the data directory. + void stopSound(MWWorld::Ptr::CellStore *cell); + ///< Stop all sounds for the given cell. - bool sayDone (MWWorld::Ptr reference) const; - ///< Is actor not speaking? + void stopSound(const std::string& soundId); + ///< Stop a non-3d looping sound - void playSound (const std::string& soundId, float volume, float pitch, bool loop=false); - ///< Play a sound, independently of 3D-position + bool getSoundPlaying(MWWorld::Ptr reference, const std::string& soundId) const; + ///< Is the given sound currently playing on the given object? - void playSound3D (MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, bool loop, bool untracked=false); - ///< Play a sound from an object + void updateObject(MWWorld::Ptr reference); + ///< Update the position of all sounds connected to the given object. - void stopSound3D (MWWorld::Ptr reference, const std::string& soundId = ""); - ///< Stop the given object from playing the given sound, If no soundId is given, - /// all sounds for this reference will stop. + void update(float duration); + }; - void stopSound (MWWorld::Ptr::CellStore *cell); - ///< Stop all sounds for the given cell. + class Sound_Output + { + SoundManager &mgr; - void stopSound(const std::string& soundId); - ///< Stop a non-3d looping sound + virtual bool Initialize(const std::string &devname="") = 0; + virtual void Deinitialize() = 0; - bool getSoundPlaying (MWWorld::Ptr reference, const std::string& soundId) const; - ///< Is the given sound currently playing on the given object? + Sound_Output(SoundManager &mgr) : mgr(mgr) { } - void updateObject(MWWorld::Ptr reference); - ///< Update the position of all sounds connected to the given object. + public: + virtual ~Sound_Output() { } - void update (float duration); + friend class OpenAL_Output; + friend class SoundManager; }; } From 10037e79e70ff5a4822f73e7da792c7ba6178e54 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 16 Mar 2012 23:18:15 -0700 Subject: [PATCH 013/152] Add a skeleton decoder class using mpg123 and libsndfile --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwsound/mpgsnd_decoder.cpp | 29 ++++++++++++++++++++++++++ apps/openmw/mwsound/mpgsnd_decoder.hpp | 29 ++++++++++++++++++++++++++ apps/openmw/mwsound/sound_decoder.hpp | 19 +++++++++++++++++ apps/openmw/mwsound/soundmanager.cpp | 2 ++ apps/openmw/mwsound/soundmanager.hpp | 2 +- 6 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 apps/openmw/mwsound/mpgsnd_decoder.cpp create mode 100644 apps/openmw/mwsound/mpgsnd_decoder.hpp create mode 100644 apps/openmw/mwsound/sound_decoder.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e9002f111..bb7655a3c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -38,7 +38,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanager openal_output + soundmanager openal_output mpgsnd_decoder ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp new file mode 100644 index 000000000..cdc392e70 --- /dev/null +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -0,0 +1,29 @@ +#include "mpgsnd_decoder.hpp" + + +namespace MWSound +{ + +bool MpgSnd_Decoder::Open(const std::string &fname) +{ + return false; +} + +void MpgSnd_Decoder::Close() +{ +} + + +MpgSnd_Decoder::MpgSnd_Decoder() +{ + static bool initdone = false; + if(!initdone) + mpg123_init(); + initdone = true; +} + +MpgSnd_Decoder::~MpgSnd_Decoder() +{ +} + +} diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp new file mode 100644 index 000000000..ffa9037a7 --- /dev/null +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_SOUND_MPGSND_DECODER_H +#define GAME_SOUND_MPGSND_DECODER_H + +#include + +#include "mpg123.h" +#include "sndfile.h" + +#include "sound_decoder.hpp" + + +namespace MWSound +{ + class MpgSnd_Decoder : public Sound_Decoder + { + virtual bool Open(const std::string &fname); + virtual void Close(); + + MpgSnd_Decoder(); + virtual ~MpgSnd_Decoder(); + + friend class SoundManager; + }; +#ifndef DEFAULT_DECODER +#define DEFAULT_DECODER (::MWSound::MpgSnd_Decoder) +#endif +}; + +#endif diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp new file mode 100644 index 000000000..0d7e3d9f8 --- /dev/null +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -0,0 +1,19 @@ +#ifndef GAME_SOUND_SOUND_DECODER_H +#define GAME_SOUND_SOUND_DECODER_H + +namespace MWSound +{ + class Sound_Decoder + { + public: + virtual bool Open(const std::string &fname) = 0; + virtual void Close() = 0; + + virtual ~Sound_Decoder() { } + + friend class OpenAL_Output; + friend class SoundManager; + }; +} + +#endif diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 8b852a746..c22550a2a 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -12,6 +12,7 @@ #include "../mwworld/world.hpp" #include "../mwworld/player.hpp" +#include "sound_decoder.hpp" #include "openal_output.hpp" #define SOUND_OUT "OpenAL" @@ -28,6 +29,7 @@ #endif #ifdef OPENMW_USE_MPG123 +#include "mpgsnd_decoder.hpp" #define SOUND_IN "mpg123,sndfile" #endif diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index f77222cb4..8d2184c54 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -22,6 +22,7 @@ namespace MWWorld namespace MWSound { class Sound_Output; + class Sound_Decoder; class SoundManager { @@ -131,7 +132,6 @@ namespace MWSound virtual void Deinitialize() = 0; Sound_Output(SoundManager &mgr) : mgr(mgr) { } - public: virtual ~Sound_Output() { } From 1322b1e160e2c6a3109b1d623d8ccc87443d8d5c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 16 Mar 2012 23:40:07 -0700 Subject: [PATCH 014/152] Move Sound_Output's definition to a separate header --- apps/openmw/mwsound/openal_output.cpp | 3 +++ apps/openmw/mwsound/openal_output.hpp | 6 +++++- apps/openmw/mwsound/sound_output.hpp | 26 ++++++++++++++++++++++++++ apps/openmw/mwsound/soundmanager.cpp | 1 + apps/openmw/mwsound/soundmanager.hpp | 15 --------------- 5 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 apps/openmw/mwsound/sound_output.hpp diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 59e9037c4..3f9910038 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1,3 +1,6 @@ +#include +#include + #include "openal_output.hpp" namespace MWSound diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 6d0169366..d5748ee30 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -1,13 +1,17 @@ #ifndef GAME_SOUND_OPENAL_OUTPUT_H #define GAME_SOUND_OPENAL_OUTPUT_H -#include "soundmanager.hpp" +#include #include "alc.h" #include "al.h" +#include "sound_output.hpp" + namespace MWSound { + class SoundManager; + class OpenAL_Output : public Sound_Output { ALCdevice *Device; diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp new file mode 100644 index 000000000..b36a4e89f --- /dev/null +++ b/apps/openmw/mwsound/sound_output.hpp @@ -0,0 +1,26 @@ +#ifndef GAME_SOUND_SOUND_OUTPUT_H +#define GAME_SOUND_SOUND_OUTPUT_H + +#include + +namespace MWSound +{ + class SoundManager; + + class Sound_Output + { + SoundManager &mgr; + + virtual bool Initialize(const std::string &devname="") = 0; + virtual void Deinitialize() = 0; + + Sound_Output(SoundManager &mgr) : mgr(mgr) { } + public: + virtual ~Sound_Output() { } + + friend class OpenAL_Output; + friend class SoundManager; + }; +} + +#endif diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index c22550a2a..cb9c58715 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -12,6 +12,7 @@ #include "../mwworld/world.hpp" #include "../mwworld/player.hpp" +#include "sound_output.hpp" #include "sound_decoder.hpp" #include "openal_output.hpp" diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 8d2184c54..5a58c60b7 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -123,21 +123,6 @@ namespace MWSound void update(float duration); }; - - class Sound_Output - { - SoundManager &mgr; - - virtual bool Initialize(const std::string &devname="") = 0; - virtual void Deinitialize() = 0; - - Sound_Output(SoundManager &mgr) : mgr(mgr) { } - public: - virtual ~Sound_Output() { } - - friend class OpenAL_Output; - friend class SoundManager; - }; } #endif From 46cd84aac56f2a396845cd2caec8e9d6596b9c65 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 16 Mar 2012 23:59:21 -0700 Subject: [PATCH 015/152] Add a skeleton ffmpeg decoder --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwsound/ffmpeg_decoder.cpp | 29 +++++++++++++++++++++++ apps/openmw/mwsound/ffmpeg_decoder.hpp | 32 ++++++++++++++++++++++++++ apps/openmw/mwsound/mpgsnd_decoder.cpp | 4 ++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwsound/ffmpeg_decoder.cpp create mode 100644 apps/openmw/mwsound/ffmpeg_decoder.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index bb7655a3c..749b5bc5d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -38,7 +38,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanager openal_output mpgsnd_decoder + soundmanager openal_output mpgsnd_decoder ffmpeg_decoder ) add_openmw_dir (mwworld diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp new file mode 100644 index 000000000..8bd6b3f8e --- /dev/null +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -0,0 +1,29 @@ +#ifdef OPENMW_USE_FFMPEG + +#include "ffmpeg_decoder.hpp" + + +namespace MWSound +{ + +bool FFmpeg_Decoder::Open(const std::string &fname) +{ + return false; +} + +void FFmpeg_Decoder::Close() +{ +} + + +FFmpeg_Decoder::FFmpeg_Decoder() +{ +} + +FFmpeg_Decoder::~FFmpeg_Decoder() +{ +} + +} + +#endif diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp new file mode 100644 index 000000000..2b7363f26 --- /dev/null +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -0,0 +1,32 @@ +#ifndef GAME_SOUND_FFMPEG_DECODER_H +#define GAME_SOUND_FFMPEG_DECODER_H + +#include + +extern "C" +{ +#include +#include +} + +#include "sound_decoder.hpp" + + +namespace MWSound +{ + class FFmpeg_Decoder : public Sound_Decoder + { + virtual bool Open(const std::string &fname); + virtual void Close(); + + FFmpeg_Decoder(); + virtual ~FFmpeg_Decoder(); + + friend class SoundManager; + }; +#ifndef DEFAULT_DECODER +#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder) +#endif +}; + +#endif diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index cdc392e70..6ca46381d 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -1,3 +1,5 @@ +#ifdef OPENMW_USE_MPG123 + #include "mpgsnd_decoder.hpp" @@ -27,3 +29,5 @@ MpgSnd_Decoder::~MpgSnd_Decoder() } } + +#endif From 246b0266fb5b7a326f8cccd9d23244ff858e2f8e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 00:11:59 -0700 Subject: [PATCH 016/152] Remove references to Audiere. It's not supported for now. --- CMakeLists.txt | 8 -------- apps/openmw/mwsound/soundmanager.cpp | 4 ---- 2 files changed, 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 876ea8273..db8f9d171 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,6 @@ set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VE configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") # Sound source selection -option(USE_AUDIERE "use Audiere for sound" OFF) option(USE_FFMPEG "use ffmpeg for sound" OFF) option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) @@ -125,13 +124,6 @@ set(OPENMW_LIBS ${MANGLE_ALL} ${OENGINE_ALL}) set(OPENMW_LIBS_HEADER) # Sound setup -if (USE_AUDIERE) - find_package(Audiere REQUIRED) - set(SOUND_INPUT_INCLUDES ${AUDIERE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${AUDIERE_LIBRARY}) - set(SOUND_DEFINE -DOPENMW_USE_AUDIERE) -endif (USE_AUDIERE) - if (USE_FFMPEG) find_package(FFMPEG REQUIRED) set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIR}) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index cb9c58715..17dfa5ef5 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -21,10 +21,6 @@ MPG123/libsndfile for input. The OPENMW_USE_x macros are set in CMakeLists.txt. */ -#ifdef OPENMW_USE_AUDIERE -#define SOUND_IN "Audiere" -#endif - #ifdef OPENMW_USE_FFMPEG #define SOUND_IN "FFmpeg" #endif From 637617056b4a6015a151ce8342e57e312c40a662 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 02:45:18 -0700 Subject: [PATCH 017/152] Make a skeleton Sound class --- apps/openmw/mwsound/sound.hpp | 22 ++++++++++++++++++++++ apps/openmw/mwsound/soundmanager.cpp | 14 ++++++++------ apps/openmw/mwsound/soundmanager.hpp | 3 +++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 apps/openmw/mwsound/sound.hpp diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp new file mode 100644 index 000000000..9d262416e --- /dev/null +++ b/apps/openmw/mwsound/sound.hpp @@ -0,0 +1,22 @@ +#ifndef GAME_SOUND_SOUND_H +#define GAME_SOUND_SOUND_H + +#include + +namespace MWSound +{ + class Sound + { + virtual bool Play() = 0; + virtual void Stop() = 0; + virtual bool isPlaying() = 0; + + public: + virtual ~Sound() { } + + friend class OpenAL_Output; + friend class SoundManager; + }; +} + +#endif diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 17dfa5ef5..d231f1d4a 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -14,6 +14,7 @@ #include "sound_output.hpp" #include "sound_decoder.hpp" +#include "sound.hpp" #include "openal_output.hpp" #define SOUND_OUT "OpenAL" @@ -143,15 +144,18 @@ namespace MWSound void SoundManager::stopMusic() { + if(mMusic) + mMusic->Stop(); setPlaylist(); } void SoundManager::streamMusicFull(const std::string& filename) { - // Play the sound and tell it to stream, if possible. TODO: - // Store the reference, the jukebox will need to check status, - // control volume etc. + if(mMusic) + mMusic->Stop(); + std::auto_ptr decoder(new DEFAULT_DECODER); + //mMusic.reset(Output->StreamSound(filename, decoder)); } void SoundManager::streamMusic(const std::string& filename) @@ -186,9 +190,7 @@ namespace MWSound bool SoundManager::isMusicPlaying() { - // HACK: Return true to prevent the engine from trying to keep playing - // music and tanking the framerate. - return true; + return mMusic && mMusic->isPlaying(); } bool SoundManager::setPlaylist(std::string playlist) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 5a58c60b7..709ab2817 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -23,6 +23,7 @@ namespace MWSound { class Sound_Output; class Sound_Decoder; + class Sound; class SoundManager { @@ -36,6 +37,8 @@ namespace MWSound std::auto_ptr Output; + boost::shared_ptr mMusic; + void streamMusicFull(const std::string& filename); ///< Play a soundifle /// \param absolute filename From 9cf42f6d0fcde91c25f5ffe0027cd186223421aa Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 02:51:46 -0700 Subject: [PATCH 018/152] Flesh out the sound decoder a bit more --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 15 ++++ apps/openmw/mwsound/ffmpeg_decoder.hpp | 3 + apps/openmw/mwsound/mpgsnd_decoder.cpp | 95 +++++++++++++++++++++++++- apps/openmw/mwsound/mpgsnd_decoder.hpp | 9 +++ apps/openmw/mwsound/sound_decoder.hpp | 11 +++ 5 files changed, 132 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 8bd6b3f8e..d3715d5fe 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -6,8 +6,12 @@ namespace MWSound { +static void fail(const std::string &msg) +{ throw std::runtime_error("FFmpeg exception: "+msg); } + bool FFmpeg_Decoder::Open(const std::string &fname) { + fail("Not currently working"); return false; } @@ -15,6 +19,17 @@ void FFmpeg_Decoder::Close() { } +void FFmpeg_Decoder::GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type) +{ + fail("Not currently working"); +} + +size_t FFmpeg_Decoder::Read(char *buffer, size_t bytes) +{ + fail("Not currently working"); + return 0; +} + FFmpeg_Decoder::FFmpeg_Decoder() { diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 2b7363f26..6ff93904a 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -19,6 +19,9 @@ namespace MWSound virtual bool Open(const std::string &fname); virtual void Close(); + virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type); + virtual size_t Read(char *buffer, size_t bytes); + FFmpeg_Decoder(); virtual ~FFmpeg_Decoder(); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 6ca46381d..4ba48f607 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -1,22 +1,115 @@ #ifdef OPENMW_USE_MPG123 +#include +#include + #include "mpgsnd_decoder.hpp" +static void fail(const std::string &msg) +{ throw std::runtime_error("MpgSnd exception: "+msg); } + namespace MWSound { bool MpgSnd_Decoder::Open(const std::string &fname) { + Close(); + + SF_INFO info; + sndFile = sf_open(fname.c_str(), SFM_READ, &info); + if(sndFile) + { + if(info.channels == 1) + chanConfig = MonoChannels; + else if(info.channels == 1) + chanConfig = MonoChannels; + else + { + sf_close(sndFile); + sndFile = NULL; + fail("Unsupported channel count in "+fname); + } + sampleRate = info.samplerate; + return true; + } + + mpgFile = mpg123_new(NULL, NULL); + if(mpgFile && mpg123_open(mpgFile, fname.c_str()) == MPG123_OK) + { + try + { + int encoding, channels; + long rate; + if(mpg123_getformat(mpgFile, &rate, &channels, &encoding) != MPG123_OK) + fail("Failed to get audio format"); + if(encoding != MPG123_ENC_SIGNED_16) + fail("Unsupported encoding in "+fname); + if(channels != 1 && channels != 2) + fail("Unsupported channel count in "+fname); + chanConfig = ((channels==2)?StereoChannels:MonoChannels); + sampleRate = rate; + return true; + } + catch(std::exception &e) + { + mpg123_close(mpgFile); + mpg123_delete(mpgFile); + throw; + } + mpg123_close(mpgFile); + } + if(mpgFile) + mpg123_delete(mpgFile); + mpgFile = NULL; + + fail("Unsupported file type: "+fname); return false; } void MpgSnd_Decoder::Close() { + if(sndFile) + sf_close(sndFile); + sndFile = NULL; + + if(mpgFile) + { + mpg123_close(mpgFile); + mpg123_delete(mpgFile); + mpgFile = NULL; + } } +void MpgSnd_Decoder::GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type) +{ + if(!sndFile && !mpgFile) + fail("No open file"); + + *samplerate = sampleRate; + *chans = chanConfig; + *type = Int16Sample; +} + +size_t MpgSnd_Decoder::Read(char *buffer, size_t bytes) +{ + size_t got = 0; + + if(sndFile) + { + got = sf_read_short(sndFile, (short*)buffer, bytes/2)*2; + } + else if(mpgFile) + { + int err; + err = mpg123_read(mpgFile, (unsigned char*)buffer, bytes, &got); + if(err != MPG123_OK && err != MPG123_DONE) + fail("Failed to read from file"); + } + return got; +} -MpgSnd_Decoder::MpgSnd_Decoder() +MpgSnd_Decoder::MpgSnd_Decoder() : sndFile(NULL), mpgFile(NULL) { static bool initdone = false; if(!initdone) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index ffa9037a7..641d891a3 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -13,9 +13,18 @@ namespace MWSound { class MpgSnd_Decoder : public Sound_Decoder { + SNDFILE *sndFile; + mpg123_handle *mpgFile; + + ChannelConfig chanConfig; + int sampleRate; + virtual bool Open(const std::string &fname); virtual void Close(); + virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type); + virtual size_t Read(char *buffer, size_t bytes); + MpgSnd_Decoder(); virtual ~MpgSnd_Decoder(); diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 0d7e3d9f8..fd8f4ac51 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -6,9 +6,20 @@ namespace MWSound class Sound_Decoder { public: + enum SampleType { + UInt8Sample, + Int16Sample + }; + enum ChannelConfig { + MonoChannels, + StereoChannels + }; virtual bool Open(const std::string &fname) = 0; virtual void Close() = 0; + virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; + virtual size_t Read(char *buffer, size_t bytes) = 0; + virtual ~Sound_Decoder() { } friend class OpenAL_Output; From 1ade01edc84c69692c6b3dc39e0c83389eb9e9cb Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 02:55:08 -0700 Subject: [PATCH 019/152] Add a function to stream sounds --- apps/openmw/mwsound/openal_output.cpp | 188 ++++++++++++++++++++++++++ apps/openmw/mwsound/openal_output.hpp | 4 + apps/openmw/mwsound/sound_output.hpp | 5 + apps/openmw/mwsound/soundmanager.cpp | 2 +- 4 files changed, 198 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 3f9910038..45be36687 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1,7 +1,11 @@ #include #include +#include #include "openal_output.hpp" +#include "sound_decoder.hpp" +#include "sound.hpp" + namespace MWSound { @@ -9,6 +13,176 @@ namespace MWSound static void fail(const std::string &msg) { throw std::runtime_error("OpenAL exception: " + msg); } +static void throwALerror() +{ + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + fail(alGetString(err)); +} + + +static ALenum getALFormat(Sound_Decoder::ChannelConfig chans, Sound_Decoder::SampleType type) +{ + if(chans == Sound_Decoder::MonoChannels) + { + if(type == Sound_Decoder::Int16Sample) + return AL_FORMAT_MONO16; + else if(type == Sound_Decoder::UInt8Sample) + return AL_FORMAT_MONO8; + else + fail("Unsupported sample type"); + } + else if(chans == Sound_Decoder::StereoChannels) + { + if(type == Sound_Decoder::Int16Sample) + return AL_FORMAT_STEREO16; + else if(type == Sound_Decoder::UInt8Sample) + return AL_FORMAT_STEREO8; + else + fail("Unsupported sample type"); + } + else + fail("Unsupported channel config"); + return AL_NONE; +} + + +class OpenAL_SoundStream : public Sound +{ + // This should be something sane, like 4, but currently cell loads tend to + // cause the stream to underrun + static const ALuint NumBuffers = 150; + static const ALuint BufferSize = 32768; + + ALuint Source; + ALuint Buffers[NumBuffers]; + + ALenum Format; + ALsizei SampleRate; + + std::auto_ptr Decoder; + +public: + OpenAL_SoundStream(std::auto_ptr decoder) : Decoder(decoder) + { + throwALerror(); + + alGenSources(1, &Source); + throwALerror(); + try + { + alGenBuffers(NumBuffers, Buffers); + throwALerror(); + } + catch(std::exception &e) + { + alDeleteSources(1, &Source); + alGetError(); + throw; + } + + try + { + int srate; + enum Sound_Decoder::ChannelConfig chans; + enum Sound_Decoder::SampleType type; + + Decoder->GetInfo(&srate, &chans, &type); + Format = getALFormat(chans, type); + SampleRate = srate; + } + catch(std::exception &e) + { + alDeleteSources(1, &Source); + alDeleteBuffers(NumBuffers, Buffers); + alGetError(); + throw; + } + } + virtual ~OpenAL_SoundStream() + { + alDeleteSources(1, &Source); + alDeleteBuffers(NumBuffers, Buffers); + alGetError(); + Decoder->Close(); + } + + virtual bool Play() + { + std::vector data(BufferSize); + + alSourceStop(Source); + alSourcei(Source, AL_BUFFER, 0); + throwALerror(); + + for(ALuint i = 0;i < NumBuffers;i++) + { + size_t got; + got = Decoder->Read(&data[0], data.size()); + alBufferData(Buffers[i], Format, &data[0], got, SampleRate); + } + throwALerror(); + + alSourceQueueBuffers(Source, NumBuffers, Buffers); + alSourcePlay(Source); + throwALerror(); + + return true; + } + + virtual void Stop() + { + alSourceStop(Source); + alSourcei(Source, AL_BUFFER, 0); + throwALerror(); + // FIXME: Rewind decoder + } + + virtual bool isPlaying() + { + ALint processed, state; + + alGetSourcei(Source, AL_SOURCE_STATE, &state); + alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed); + throwALerror(); + + if(processed > 0) + { + std::vector data(BufferSize); + do { + ALuint bufid; + size_t got; + + alSourceUnqueueBuffers(Source, 1, &bufid); + processed--; + + got = Decoder->Read(&data[0], data.size()); + if(got > 0) + { + alBufferData(bufid, Format, &data[0], got, SampleRate); + alSourceQueueBuffers(Source, 1, &bufid); + } + } while(processed > 0); + throwALerror(); + } + + if(state != AL_PLAYING && state != AL_PAUSED) + { + ALint queued; + + alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued); + throwALerror(); + if(queued == 0) + return false; + + alSourcePlay(Source); + throwALerror(); + } + + return true; + } +}; + bool OpenAL_Output::Initialize(const std::string &devname) { @@ -50,6 +224,20 @@ void OpenAL_Output::Deinitialize() } +Sound* OpenAL_Output::StreamSound(const std::string &fname, std::auto_ptr decoder) +{ + std::auto_ptr sound; + + if(!decoder->Open(fname)) + return NULL; + + sound.reset(new OpenAL_SoundStream(decoder)); + sound->Play(); + + return sound.release(); +} + + OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), Device(0), Context(0) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index d5748ee30..b0cd469e5 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -11,6 +11,8 @@ namespace MWSound { class SoundManager; + class Sound_Decoder; + class Sound; class OpenAL_Output : public Sound_Output { @@ -20,6 +22,8 @@ namespace MWSound virtual bool Initialize(const std::string &devname=""); virtual void Deinitialize(); + virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder); + OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index b36a4e89f..16639de7b 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -2,10 +2,13 @@ #define GAME_SOUND_SOUND_OUTPUT_H #include +#include namespace MWSound { class SoundManager; + class Sound_Decoder; + class Sound; class Sound_Output { @@ -14,6 +17,8 @@ namespace MWSound virtual bool Initialize(const std::string &devname="") = 0; virtual void Deinitialize() = 0; + virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder) = 0; + Sound_Output(SoundManager &mgr) : mgr(mgr) { } public: virtual ~Sound_Output() { } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index d231f1d4a..9ba5479d7 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -155,7 +155,7 @@ namespace MWSound if(mMusic) mMusic->Stop(); std::auto_ptr decoder(new DEFAULT_DECODER); - //mMusic.reset(Output->StreamSound(filename, decoder)); + mMusic.reset(Output->StreamSound(filename, decoder)); } void SoundManager::streamMusic(const std::string& filename) From 207d7dd89e66d5eccc4f2b62a2288148b1df5fbe Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 03:01:51 -0700 Subject: [PATCH 020/152] Stop and delete the current music before deleting the sound output --- apps/openmw/mwsound/soundmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 9ba5479d7..8b8ffcedc 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -76,6 +76,9 @@ namespace MWSound SoundManager::~SoundManager() { + if(mMusic) + mMusic->Stop(); + mMusic.reset(); Output.reset(); } From 2f6b73d46149d08478d220d38073d389a673151d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 03:06:35 -0700 Subject: [PATCH 021/152] Prevent streamMusic from throwing an exception --- apps/openmw/mwsound/soundmanager.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 8b8ffcedc..b215fd7c5 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -165,7 +165,16 @@ namespace MWSound { std::string filePath = mMusicLibrary.locate(filename, mFSStrict, true).string(); if(!filePath.empty()) - streamMusicFull(filePath); + { + try + { + streamMusicFull(filePath); + } + catch(std::exception &e) + { + std::cout << "Music Error: " << e.what() << "\n"; + } + } } void SoundManager::startRandomTitle() From 1b41987e184fc63576096ad0fbc608105049cc75 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 03:15:07 -0700 Subject: [PATCH 022/152] Move OpenAL_SoundStream function definitions out of the class --- apps/openmw/mwsound/openal_output.cpp | 196 ++++++++++++++------------ 1 file changed, 102 insertions(+), 94 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 45be36687..766fa018a 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -63,125 +63,133 @@ class OpenAL_SoundStream : public Sound std::auto_ptr Decoder; public: - OpenAL_SoundStream(std::auto_ptr decoder) : Decoder(decoder) + OpenAL_SoundStream(std::auto_ptr decoder); + virtual ~OpenAL_SoundStream(); + + virtual bool Play(); + virtual void Stop(); + virtual bool isPlaying(); +}; + +OpenAL_SoundStream::OpenAL_SoundStream(std::auto_ptr decoder) + : Decoder(decoder) +{ + throwALerror(); + + alGenSources(1, &Source); + throwALerror(); + try { + alGenBuffers(NumBuffers, Buffers); throwALerror(); + } + catch(std::exception &e) + { + alDeleteSources(1, &Source); + alGetError(); + throw; + } - alGenSources(1, &Source); - throwALerror(); - try - { - alGenBuffers(NumBuffers, Buffers); - throwALerror(); - } - catch(std::exception &e) - { - alDeleteSources(1, &Source); - alGetError(); - throw; - } - - try - { - int srate; - enum Sound_Decoder::ChannelConfig chans; - enum Sound_Decoder::SampleType type; - - Decoder->GetInfo(&srate, &chans, &type); - Format = getALFormat(chans, type); - SampleRate = srate; - } - catch(std::exception &e) - { - alDeleteSources(1, &Source); - alDeleteBuffers(NumBuffers, Buffers); - alGetError(); - throw; - } + try + { + int srate; + Sound_Decoder::ChannelConfig chans; + Sound_Decoder::SampleType type; + + Decoder->GetInfo(&srate, &chans, &type); + Format = getALFormat(chans, type); + SampleRate = srate; } - virtual ~OpenAL_SoundStream() + catch(std::exception &e) { alDeleteSources(1, &Source); alDeleteBuffers(NumBuffers, Buffers); alGetError(); - Decoder->Close(); + throw; } +} +OpenAL_SoundStream::~OpenAL_SoundStream() +{ + alDeleteSources(1, &Source); + alDeleteBuffers(NumBuffers, Buffers); + alGetError(); + Decoder->Close(); +} + +bool OpenAL_SoundStream::Play() +{ + std::vector data(BufferSize); - virtual bool Play() + alSourceStop(Source); + alSourcei(Source, AL_BUFFER, 0); + throwALerror(); + + for(ALuint i = 0;i < NumBuffers;i++) { - std::vector data(BufferSize); + size_t got; + got = Decoder->Read(&data[0], data.size()); + alBufferData(Buffers[i], Format, &data[0], got, SampleRate); + } + throwALerror(); - alSourceStop(Source); - alSourcei(Source, AL_BUFFER, 0); - throwALerror(); + alSourceQueueBuffers(Source, NumBuffers, Buffers); + alSourcePlay(Source); + throwALerror(); - for(ALuint i = 0;i < NumBuffers;i++) - { - size_t got; - got = Decoder->Read(&data[0], data.size()); - alBufferData(Buffers[i], Format, &data[0], got, SampleRate); - } - throwALerror(); + return true; +} - alSourceQueueBuffers(Source, NumBuffers, Buffers); - alSourcePlay(Source); - throwALerror(); +void OpenAL_SoundStream::Stop() +{ + alSourceStop(Source); + alSourcei(Source, AL_BUFFER, 0); + throwALerror(); + // FIXME: Rewind decoder +} - return true; - } +bool OpenAL_SoundStream::isPlaying() +{ + ALint processed, state; + + alGetSourcei(Source, AL_SOURCE_STATE, &state); + alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed); + throwALerror(); - virtual void Stop() + if(processed > 0) { - alSourceStop(Source); - alSourcei(Source, AL_BUFFER, 0); + std::vector data(BufferSize); + do { + ALuint bufid; + size_t got; + + alSourceUnqueueBuffers(Source, 1, &bufid); + processed--; + + got = Decoder->Read(&data[0], data.size()); + if(got > 0) + { + alBufferData(bufid, Format, &data[0], got, SampleRate); + alSourceQueueBuffers(Source, 1, &bufid); + } + } while(processed > 0); throwALerror(); - // FIXME: Rewind decoder } - virtual bool isPlaying() + if(state != AL_PLAYING && state != AL_PAUSED) { - ALint processed, state; + ALint queued; - alGetSourcei(Source, AL_SOURCE_STATE, &state); - alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued); throwALerror(); + if(queued == 0) + return false; - if(processed > 0) - { - std::vector data(BufferSize); - do { - ALuint bufid; - size_t got; - - alSourceUnqueueBuffers(Source, 1, &bufid); - processed--; - - got = Decoder->Read(&data[0], data.size()); - if(got > 0) - { - alBufferData(bufid, Format, &data[0], got, SampleRate); - alSourceQueueBuffers(Source, 1, &bufid); - } - } while(processed > 0); - throwALerror(); - } - - if(state != AL_PLAYING && state != AL_PAUSED) - { - ALint queued; - - alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued); - throwALerror(); - if(queued == 0) - return false; - - alSourcePlay(Source); - throwALerror(); - } - - return true; + alSourcePlay(Source); + throwALerror(); } -}; + + return true; +} bool OpenAL_Output::Initialize(const std::string &devname) From caf5d71d44b8bb0382842b56b65148cde4baa8d1 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 03:18:28 -0700 Subject: [PATCH 023/152] Make the sound decoder's Open method return void Errors are thrown, not returned --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 3 +-- apps/openmw/mwsound/ffmpeg_decoder.hpp | 2 +- apps/openmw/mwsound/mpgsnd_decoder.cpp | 7 +++---- apps/openmw/mwsound/mpgsnd_decoder.hpp | 2 +- apps/openmw/mwsound/openal_output.cpp | 3 +-- apps/openmw/mwsound/sound_decoder.hpp | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index d3715d5fe..5c64e4dd1 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -9,10 +9,9 @@ namespace MWSound static void fail(const std::string &msg) { throw std::runtime_error("FFmpeg exception: "+msg); } -bool FFmpeg_Decoder::Open(const std::string &fname) +void FFmpeg_Decoder::Open(const std::string &fname) { fail("Not currently working"); - return false; } void FFmpeg_Decoder::Close() diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 6ff93904a..205fe8865 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -16,7 +16,7 @@ namespace MWSound { class FFmpeg_Decoder : public Sound_Decoder { - virtual bool Open(const std::string &fname); + virtual void Open(const std::string &fname); virtual void Close(); virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 4ba48f607..2c35adeb3 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -12,7 +12,7 @@ static void fail(const std::string &msg) namespace MWSound { -bool MpgSnd_Decoder::Open(const std::string &fname) +void MpgSnd_Decoder::Open(const std::string &fname) { Close(); @@ -31,7 +31,7 @@ bool MpgSnd_Decoder::Open(const std::string &fname) fail("Unsupported channel count in "+fname); } sampleRate = info.samplerate; - return true; + return; } mpgFile = mpg123_new(NULL, NULL); @@ -49,7 +49,7 @@ bool MpgSnd_Decoder::Open(const std::string &fname) fail("Unsupported channel count in "+fname); chanConfig = ((channels==2)?StereoChannels:MonoChannels); sampleRate = rate; - return true; + return; } catch(std::exception &e) { @@ -64,7 +64,6 @@ bool MpgSnd_Decoder::Open(const std::string &fname) mpgFile = NULL; fail("Unsupported file type: "+fname); - return false; } void MpgSnd_Decoder::Close() diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 641d891a3..391cfbd6d 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -19,7 +19,7 @@ namespace MWSound ChannelConfig chanConfig; int sampleRate; - virtual bool Open(const std::string &fname); + virtual void Open(const std::string &fname); virtual void Close(); virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 766fa018a..7668dcac2 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -236,8 +236,7 @@ Sound* OpenAL_Output::StreamSound(const std::string &fname, std::auto_ptr sound; - if(!decoder->Open(fname)) - return NULL; + decoder->Open(fname); sound.reset(new OpenAL_SoundStream(decoder)); sound->Play(); diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index fd8f4ac51..4f778c3b3 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -14,7 +14,7 @@ namespace MWSound MonoChannels, StereoChannels }; - virtual bool Open(const std::string &fname) = 0; + virtual void Open(const std::string &fname) = 0; virtual void Close() = 0; virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; From 2429755bf1daa4ddad0cae74171cf5ef41c71703 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 03:20:43 -0700 Subject: [PATCH 024/152] Make the sound's Play method return void --- apps/openmw/mwsound/openal_output.cpp | 6 ++---- apps/openmw/mwsound/sound.hpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 7668dcac2..246fce7e1 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -66,7 +66,7 @@ public: OpenAL_SoundStream(std::auto_ptr decoder); virtual ~OpenAL_SoundStream(); - virtual bool Play(); + virtual void Play(); virtual void Stop(); virtual bool isPlaying(); }; @@ -116,7 +116,7 @@ OpenAL_SoundStream::~OpenAL_SoundStream() Decoder->Close(); } -bool OpenAL_SoundStream::Play() +void OpenAL_SoundStream::Play() { std::vector data(BufferSize); @@ -135,8 +135,6 @@ bool OpenAL_SoundStream::Play() alSourceQueueBuffers(Source, NumBuffers, Buffers); alSourcePlay(Source); throwALerror(); - - return true; } void OpenAL_SoundStream::Stop() diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 9d262416e..5ffb48820 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,7 +7,7 @@ namespace MWSound { class Sound { - virtual bool Play() = 0; + virtual void Play() = 0; virtual void Stop() = 0; virtual bool isPlaying() = 0; From 2dabdcb9e551a97ff9d2d67a344cc66ffbf7117a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 04:22:54 -0700 Subject: [PATCH 025/152] Add a function to update the sound listener --- apps/openmw/mwsound/openal_output.cpp | 12 ++++++++++++ apps/openmw/mwsound/openal_output.hpp | 2 ++ apps/openmw/mwsound/sound_output.hpp | 3 +++ 3 files changed, 17 insertions(+) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 246fce7e1..1a02dc258 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -243,6 +243,18 @@ Sound* OpenAL_Output::StreamSound(const std::string &fname, std::auto_ptr decoder); + virtual void UpdateListener(float pos[3], float atdir[3], float updir[3]); + OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 16639de7b..a8a91cca1 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -19,6 +19,9 @@ namespace MWSound virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder) = 0; + // FIXME: This should take an MWWorld::Ptr that represents the in-world camera + virtual void UpdateListener(float pos[3], float atdir[3], float updir[3]) = 0; + Sound_Output(SoundManager &mgr) : mgr(mgr) { } public: virtual ~Sound_Output() { } From 5e939e4818b987f12ee27be8b4ab980446374070 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 17 Mar 2012 13:59:51 +0100 Subject: [PATCH 026/152] map window now works for interiors --- apps/openmw/mwgui/layouts.hpp | 4 +- apps/openmw/mwgui/window_manager.cpp | 5 ++ apps/openmw/mwgui/window_manager.hpp | 3 + apps/openmw/mwrender/localmap.cpp | 68 +++-------------------- apps/openmw/mwrender/localmap.hpp | 8 ++- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 6 files changed, 26 insertions(+), 64 deletions(-) diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 7b7c84225..9cf22685a 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -84,7 +84,7 @@ namespace MWGui mPrefix = prefix; } - void setActiveCell(const int x, const int y) + void setActiveCell(const int x, const int y, bool interior=false) { for (int mx=0; mx<3; ++mx) { @@ -94,7 +94,7 @@ namespace MWGui + boost::lexical_cast(my); std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" - + boost::lexical_cast(y - (my-1)); + + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); setImage(name, image); setImage(name+"_fog", image+"_fog"); } diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 014aa8108..c8c518a93 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -420,3 +420,8 @@ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) } } + +void WindowManager::setInteriorMapTexture(const int x, const int y) +{ + map->setActiveCell(x,y, true); +} diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index c867fedbe..0345cb99d 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -152,6 +152,9 @@ namespace MWGui void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty void changeCell(MWWorld::Ptr::CellStore* cell); ///< change the active cell + + void setInteriorMapTexture(const int x, const int y); + ///< set the index of the map texture that should be used (for interiors) template void removeDialog(T*& dialog); ///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr. diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index ee5413cab..d4a25750f 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -1,6 +1,9 @@ #include "localmap.hpp" #include "renderingmanager.hpp" +#include "../mwworld/environment.hpp" +#include "../mwgui/window_manager.hpp" + #include #include @@ -19,61 +22,16 @@ using namespace Ogre; // size of a map segment (for exterior regions, this equals 1 cell) #define SIZE 8192.f -LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend) +LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWWorld::Environment* env) { mRendering = rend; - + mEnvironment = env; + mCellCamera = mRendering->getScene()->createCamera("CellCamera"); mCellCamera->setProjectionType(PT_ORTHOGRAPHIC); // look down -y const float sqrt0pt5 = 0.707106781; mCellCamera->setOrientation(Quaternion(sqrt0pt5, -sqrt0pt5, 0, 0)); - - // Debug overlay to view the maps - - render(0, 0, 10000, 10000, SIZE, SIZE, "Cell_0_0_"); - - MaterialPtr mat = MaterialManager::getSingleton().create("testMaterial", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0_"); - mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mat->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); - mat->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - - mat = MaterialManager::getSingleton().create("testMaterial2", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - mat->getTechnique(0)->getPass(0)->createTextureUnitState("Cell_0_0_"); - mat->getTechnique(0)->getPass(0)->setDepthWriteEnabled(false); - mat->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); - mat->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); - - OverlayManager& ovm = OverlayManager::getSingleton(); - - Overlay* mOverlay = ovm.create( "testOverlay" ); - mOverlay->setZOrder(0); - OverlayContainer* overlay_panel; - overlay_panel = (OverlayContainer*)ovm.createOverlayElement("Panel", "testPanel"); - - overlay_panel->_setPosition(0, 0); - overlay_panel->_setDimensions(0.5, 0.5); - - overlay_panel->setMaterialName( "testMaterial" ); - overlay_panel->show(); - mOverlay->add2D(overlay_panel); - //mOverlay->show(); - - Overlay* mOverlay2 = ovm.create( "testOverlay2" ); - mOverlay2->setZOrder(1); - OverlayContainer* overlay_panel2; - overlay_panel2 = (OverlayContainer*)ovm.createOverlayElement("Panel", "testPanel2"); - - overlay_panel2->_setPosition(0, 0); - overlay_panel2->_setDimensions(0.5, 0.5); - - overlay_panel2->setMaterialName( "testMaterial2" ); - overlay_panel2->show(); - mOverlay2->add2D(overlay_panel2); - - //mOverlay2->show(); - } LocalMap::~LocalMap() @@ -304,6 +262,8 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) x = std::ceil((pos.x - min.x)/SIZE)-1; y = std::ceil((pos.y - min.y)/SIZE)-1; + + mEnvironment->mWindowManager->setInteriorMapTexture(x,y); } // convert from world coordinates to texture UV coordinates @@ -355,17 +315,5 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) // copy to the texture memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION*4); tex->getBuffer()->unlock(); - - if (!MaterialManager::getSingleton().getByName("testMaterial").isNull()) - { - MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial"); - mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(texName); - } - if (!MaterialManager::getSingleton().getByName("testMaterial2").isNull()) - { - MaterialPtr mat = MaterialManager::getSingleton().getByName("testMaterial2"); - mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(tex->getName()); - } - } } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 9e06200c1..fd5d4770b 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -5,6 +5,11 @@ #include +namespace MWWorld +{ + class Environment; +} + namespace MWRender { /// @@ -13,7 +18,7 @@ namespace MWRender class LocalMap { public: - LocalMap(OEngine::Render::OgreRenderer*); + LocalMap(OEngine::Render::OgreRenderer*, MWWorld::Environment* env); ~LocalMap(); /** @@ -52,6 +57,7 @@ namespace MWRender private: OEngine::Render::OgreRenderer* mRendering; + MWWorld::Environment* mEnvironment; Ogre::Camera* mCellCamera; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f83e16717..e25a6a7a0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -56,7 +56,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode); mSun = 0; - mLocalMap = new MWRender::LocalMap(&mRendering); + mLocalMap = new MWRender::LocalMap(&mRendering, &environment); } RenderingManager::~RenderingManager () From a46f8ced05c519a6d6b921ae3eb1ca12385c6583 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 06:18:59 -0700 Subject: [PATCH 027/152] Keep the sound output's listener updated with the camera position --- apps/openmw/mwsound/openal_output.cpp | 7 ++----- apps/openmw/mwsound/soundmanager.cpp | 23 ++++++++++++++++++++++- apps/openmw/mwsound/soundmanager.hpp | 1 + 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 1a02dc258..2dbe4baad 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -245,11 +245,8 @@ Sound* OpenAL_Output::StreamSound(const std::string &fname, std::auto_ptrgetPlayer().getPlayer().getCell(); static int total = 0; @@ -363,4 +363,25 @@ namespace MWSound pos += chance; } } + + void SoundManager::update(float duration) + { + static float timePassed = 0.0; + + timePassed += duration; + if(timePassed > (1.0f/30.0f)) + { + timePassed = 0.0f; + Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); + + Ogre::Vector3 nPos, nDir, nUp; + nPos = cam->getRealPosition(); + nDir = cam->getRealDirection(); + nUp = cam->getRealUp(); + + Output->UpdateListener(&nPos[0], &nDir[0], &nUp[0]); + } + + updateRegionSound(duration); + } } diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 709ab2817..b31e1db7f 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -62,6 +62,7 @@ namespace MWSound bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; void removeCell(const MWWorld::Ptr::CellStore *cell); void updatePositions(MWWorld::Ptr ptr); + void updateRegionSound(float duration); public: SoundManager(Ogre::Root*, Ogre::Camera*, From 656863ec6e13ab9c73b66eca069de9c73a716608 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 06:51:44 -0700 Subject: [PATCH 028/152] Add functions to play sounds --- apps/openmw/mwsound/openal_output.cpp | 16 +++++++++++ apps/openmw/mwsound/openal_output.hpp | 6 ++++ apps/openmw/mwsound/sound_output.hpp | 7 +++++ apps/openmw/mwsound/soundmanager.cpp | 41 +++++++++++++++++++++------ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 2dbe4baad..a183909d3 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -230,6 +230,22 @@ void OpenAL_Output::Deinitialize() } +Sound* OpenAL_Output::PlaySound(const std::string &fname, std::auto_ptr decoder, + float volume, float pitch, bool loop) +{ + fail("PlaySound not yet supported"); + return NULL; +} + +Sound* OpenAL_Output::PlaySound3D(const std::string &fname, std::auto_ptr decoder, + MWWorld::Ptr ptr, float volume, float pitch, + float min, float max, bool loop) +{ + fail("PlaySound3D not yet supported"); + return NULL; +} + + Sound* OpenAL_Output::StreamSound(const std::string &fname, std::auto_ptr decoder) { std::auto_ptr sound; diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 6dd5a2c23..0ab8dd349 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -22,6 +22,12 @@ namespace MWSound virtual bool Initialize(const std::string &devname=""); virtual void Deinitialize(); + virtual Sound *PlaySound(const std::string &fname, std::auto_ptr decoder, + float volume, float pitch, bool loop); + virtual Sound *PlaySound3D(const std::string &fname, std::auto_ptr decoder, + MWWorld::Ptr ptr, float volume, float pitch, + float min, float max, bool loop); + virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder); virtual void UpdateListener(float pos[3], float atdir[3], float updir[3]); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index a8a91cca1..ba9809291 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -4,6 +4,8 @@ #include #include +#include "../mwworld/ptr.hpp" + namespace MWSound { class SoundManager; @@ -17,6 +19,11 @@ namespace MWSound virtual bool Initialize(const std::string &devname="") = 0; virtual void Deinitialize() = 0; + virtual Sound *PlaySound(const std::string &fname, std::auto_ptr decoder, + float volume, float pitch, bool loop) = 0; + virtual Sound *PlaySound3D(const std::string &fname, std::auto_ptr decoder, + MWWorld::Ptr ptr, float volume, float pitch, + float min, float max, bool loop) = 0; virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder) = 0; // FIXME: This should take an MWWorld::Ptr that represents the in-world camera diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 242755e95..b24a74868 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -120,7 +120,17 @@ namespace MWSound float min, float max, bool loop, bool untracked) { - //std::cout << "Cannot load " << file << ", skipping.\n"; + try + { + Sound *sound; + std::auto_ptr decoder(new DEFAULT_DECODER); + sound = Output->PlaySound3D(file, decoder, ptr, volume, pitch, min, max, loop); + delete sound; + } + catch(std::exception &e) + { + std::cout <<"Sound play error: "< decoder(new DEFAULT_DECODER); + sound = Output->PlaySound(file, decoder, volume, pitch, loop); + delete sound; + } + catch(std::exception &e) + { + std::cout <<"Sound play error: "< Date: Sat, 17 Mar 2012 08:02:46 -0700 Subject: [PATCH 029/152] Keep a handle on played sounds --- apps/openmw/mwsound/soundmanager.cpp | 56 ++++++++++++++++++++++++---- apps/openmw/mwsound/soundmanager.hpp | 8 +++- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index b24a74868..535dbe774 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -76,6 +76,8 @@ namespace MWSound SoundManager::~SoundManager() { + LooseSounds.clear(); + ActiveSounds.clear(); if(mMusic) mMusic->Stop(); mMusic.reset(); @@ -113,7 +115,7 @@ namespace MWSound } // Add a sound to the list and play it - void SoundManager::add(const std::string &file, + void SoundManager::play3d(const std::string &file, MWWorld::Ptr ptr, const std::string &id, float volume, float pitch, @@ -125,7 +127,10 @@ namespace MWSound Sound *sound; std::auto_ptr decoder(new DEFAULT_DECODER); sound = Output->PlaySound3D(file, decoder, ptr, volume, pitch, min, max, loop); - delete sound; + if(untracked) + LooseSounds[id] = SoundPtr(sound); + else + ActiveSounds[ptr][id] = SoundPtr(sound); } catch(std::exception &e) { @@ -137,6 +142,22 @@ namespace MWSound // remove the entire object and stop all its sounds. void SoundManager::remove(MWWorld::Ptr ptr, const std::string &id) { + SoundMap::iterator snditer = ActiveSounds.find(ptr); + if(snditer == ActiveSounds.end()) + return; + + if(!id.empty()) + { + IDMap::iterator iditer = snditer->second.find(id); + if(iditer != snditer->second.end()) + { + snditer->second.erase(iditer); + if(snditer->second.size() == 0) + ActiveSounds.erase(snditer); + } + } + else + ActiveSounds.erase(snditer); } bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const @@ -149,6 +170,14 @@ namespace MWSound // Remove all references to objects belonging to a given cell void SoundManager::removeCell(const MWWorld::Ptr::CellStore *cell) { + SoundMap::iterator snditer = ActiveSounds.begin(); + while(snditer != ActiveSounds.end()) + { + if(snditer->first.getCell() == cell) + ActiveSounds.erase(snditer++); + else + snditer++; + } } void SoundManager::updatePositions(MWWorld::Ptr ptr) @@ -260,7 +289,7 @@ namespace MWSound // The range values are not tested std::string filePath = Files::FileListLocator(mSoundFiles, filename, mFSStrict, true); if(!filePath.empty()) - add(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); + play3d(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); } bool SoundManager::sayDone(MWWorld::Ptr ptr) const @@ -280,7 +309,7 @@ namespace MWSound Sound *sound; std::auto_ptr decoder(new DEFAULT_DECODER); sound = Output->PlaySound(file, decoder, volume, pitch, loop); - delete sound; + LooseSounds[soundId] = SoundPtr(sound); } catch(std::exception &e) { @@ -298,7 +327,7 @@ namespace MWSound float min, max; std::string file = lookup(soundId, volume, min, max); if(!file.empty()) - add(file, ptr, soundId, volume, pitch, min, max, false); + play3d(file, ptr, soundId, volume, pitch, min, max, false); else std::cout << "Sound file " << soundId << " not found, skipping.\n"; } @@ -315,6 +344,9 @@ namespace MWSound void SoundManager::stopSound(const std::string& soundId) { + IDMap::iterator iditer = LooseSounds.find(soundId); + if(iditer != LooseSounds.end()) + LooseSounds.erase(iditer); } bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const @@ -382,7 +414,7 @@ namespace MWSound { //play sound std::cout << "Sound: " << go <<" Chance:" << chance << "\n"; - playSound(go, 20.0, 1.0); + playSound(go, 1.0f, 1.0f); break; } pos += chance; @@ -397,14 +429,24 @@ namespace MWSound if(timePassed > (1.0f/30.0f)) { timePassed = 0.0f; - Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); + Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); Ogre::Vector3 nPos, nDir, nUp; nPos = cam->getRealPosition(); nDir = cam->getRealDirection(); nUp = cam->getRealUp(); Output->UpdateListener(&nPos[0], &nDir[0], &nUp[0]); + + + IDMap::iterator snditer = LooseSounds.begin(); + while(snditer != LooseSounds.end()) + { + if(!snditer->second->isPlaying()) + LooseSounds.erase(snditer++); + else + snditer++; + } } updateRegionSound(duration); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index b31e1db7f..7b103dbad 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -52,9 +52,15 @@ namespace MWSound // Points to the current playlist of music files stored in the music library const Files::PathContainer* mCurrentPlaylist; + typedef boost::shared_ptr SoundPtr; + typedef std::map IDMap; + typedef std::map SoundMap; + SoundMap ActiveSounds; + IDMap LooseSounds; + std::string lookup(const std::string &soundId, float &volume, float &min, float &max); - void add(const std::string &file, + void play3d(const std::string &file, MWWorld::Ptr ptr, const std::string &id, float volume, float pitch, float min, float max, bool loop, bool untracked=false); From d57051375dda480be65fbc5127b4d4433d99e0e1 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 09:15:47 -0700 Subject: [PATCH 030/152] Implement non-streaming sounds with OpenAL --- apps/openmw/mwsound/openal_output.cpp | 166 +++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index a183909d3..ac639416f 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -47,6 +47,32 @@ static ALenum getALFormat(Sound_Decoder::ChannelConfig chans, Sound_Decoder::Sam } +ALuint LoadBuffer(std::auto_ptr decoder) +{ + int srate; + Sound_Decoder::ChannelConfig chans; + Sound_Decoder::SampleType type; + ALenum format; + + decoder->GetInfo(&srate, &chans, &type); + format = getALFormat(chans, type); + + std::vector data(32768); + size_t got, total = 0; + while((got=decoder->Read(&data[total], data.size()-total)) > 0) + { + total += got; + data.resize(total*2); + } + data.resize(total); + + ALuint buf; + alGenBuffers(1, &buf); + alBufferData(buf, format, &data[0], total, srate); + return buf; +} + + class OpenAL_SoundStream : public Sound { // This should be something sane, like 4, but currently cell loads tend to @@ -71,6 +97,21 @@ public: virtual bool isPlaying(); }; +class OpenAL_Sound : public Sound +{ +public: + ALuint Source; + ALuint Buffer; + + OpenAL_Sound(ALuint src, ALuint buf); + virtual ~OpenAL_Sound(); + + virtual void Play(); + virtual void Stop(); + virtual bool isPlaying(); +}; + + OpenAL_SoundStream::OpenAL_SoundStream(std::auto_ptr decoder) : Decoder(decoder) { @@ -190,6 +231,38 @@ bool OpenAL_SoundStream::isPlaying() } +OpenAL_Sound::OpenAL_Sound(ALuint src, ALuint buf) + : Source(src), Buffer(buf) +{ +} +OpenAL_Sound::~OpenAL_Sound() +{ + alDeleteSources(1, &Source); + alDeleteBuffers(1, &Buffer); + alGetError(); +} + +void OpenAL_Sound::Play() +{ +} + +void OpenAL_Sound::Stop() +{ + alSourceStop(Source); + throwALerror(); +} + +bool OpenAL_Sound::isPlaying() +{ + ALint state; + + alGetSourcei(Source, AL_SOURCE_STATE, &state); + throwALerror(); + + return state==AL_PLAYING; +} + + bool OpenAL_Output::Initialize(const std::string &devname) { if(Context) @@ -233,16 +306,97 @@ void OpenAL_Output::Deinitialize() Sound* OpenAL_Output::PlaySound(const std::string &fname, std::auto_ptr decoder, float volume, float pitch, bool loop) { - fail("PlaySound not yet supported"); - return NULL; + throwALerror(); + + decoder->Open(fname); + + ALuint src=0, buf=0; + try + { + buf = LoadBuffer(decoder); + alGenSources(1, &src); + throwALerror(); + } + catch(std::exception &e) + { + if(alIsSource(buf)) + alDeleteSources(1, &src); + if(alIsBuffer(buf)) + alDeleteBuffers(1, &buf); + alGetError(); + throw; + } + + std::auto_ptr sound(new OpenAL_Sound(src, buf)); + alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); + alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + + alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); + alSourcef(src, AL_MAX_DISTANCE, 1000.0f); + alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); + + alSourcef(src, AL_GAIN, volume); + alSourcef(src, AL_PITCH, pitch); + + alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE)); + throwALerror(); + + alSourcei(src, AL_BUFFER, buf); + alSourcePlay(src); + throwALerror(); + + return sound.release(); } Sound* OpenAL_Output::PlaySound3D(const std::string &fname, std::auto_ptr decoder, - MWWorld::Ptr ptr, float volume, float pitch, - float min, float max, bool loop) + MWWorld::Ptr ptr, float volume, float pitch, + float min, float max, bool loop) { - fail("PlaySound3D not yet supported"); - return NULL; + throwALerror(); + + decoder->Open(fname); + + ALuint src=0, buf=0; + try + { + buf = LoadBuffer(decoder); + alGenSources(1, &src); + throwALerror(); + } + catch(std::exception &e) + { + if(alIsSource(buf)) + alDeleteSources(1, &src); + if(alIsBuffer(buf)) + alDeleteBuffers(1, &buf); + alGetError(); + throw; + } + + std::auto_ptr sound(new OpenAL_Sound(src, buf)); + const float *pos = ptr.getCellRef().pos.pos; + alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + + alSourcef(src, AL_REFERENCE_DISTANCE, min); + alSourcef(src, AL_MAX_DISTANCE, max); + alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); + + alSourcef(src, AL_GAIN, volume); + alSourcef(src, AL_PITCH, pitch); + + alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); + alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE)); + throwALerror(); + + alSourcei(src, AL_BUFFER, buf); + alSourcePlay(src); + throwALerror(); + + return sound.release(); } From e49a090af787faf4115f0dece1a4aee7dd0bb70d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 09:16:09 -0700 Subject: [PATCH 031/152] Remove unnecessary hack --- apps/openmw/mwsound/soundmanager.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 535dbe774..796c490d9 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -162,9 +162,15 @@ namespace MWSound bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { - // HACK: Return true to prevent the engine from trying to keep playing - // sounds and tanking the framerate. - return true; + SoundMap::const_iterator snditer = ActiveSounds.find(ptr); + if(snditer == ActiveSounds.end()) + return false; + + IDMap::const_iterator iditer = snditer->second.find(id); + if(iditer == snditer->second.end()) + return false; + + return iditer->second->isPlaying(); } // Remove all references to objects belonging to a given cell From cac07d0fbf5035f47dbeb4d8ed7fa64dac615394 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 09:27:31 -0700 Subject: [PATCH 032/152] Remove some unnecessary methods --- apps/openmw/mwsound/openal_output.cpp | 7 +------ apps/openmw/mwsound/sound.hpp | 1 - apps/openmw/mwsound/soundmanager.cpp | 6 +----- apps/openmw/mwsound/soundmanager.hpp | 1 - 4 files changed, 2 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ac639416f..bf972512b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -92,7 +92,7 @@ public: OpenAL_SoundStream(std::auto_ptr decoder); virtual ~OpenAL_SoundStream(); - virtual void Play(); + void Play(); virtual void Stop(); virtual bool isPlaying(); }; @@ -106,7 +106,6 @@ public: OpenAL_Sound(ALuint src, ALuint buf); virtual ~OpenAL_Sound(); - virtual void Play(); virtual void Stop(); virtual bool isPlaying(); }; @@ -242,10 +241,6 @@ OpenAL_Sound::~OpenAL_Sound() alGetError(); } -void OpenAL_Sound::Play() -{ -} - void OpenAL_Sound::Stop() { alSourceStop(Source); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 5ffb48820..5fb996e28 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,7 +7,6 @@ namespace MWSound { class Sound { - virtual void Play() = 0; virtual void Stop() = 0; virtual bool isPlaying() = 0; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 796c490d9..b1caf7ff3 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -186,10 +186,6 @@ namespace MWSound } } - void SoundManager::updatePositions(MWWorld::Ptr ptr) - { - } - void SoundManager::stopMusic() { if(mMusic) @@ -365,7 +361,7 @@ namespace MWSound void SoundManager::updateObject(MWWorld::Ptr ptr) { - updatePositions(ptr); + // FIXME: Update tracked sounds that are using this ptr } void SoundManager::updateRegionSound(float duration) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 7b103dbad..df7e696da 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -67,7 +67,6 @@ namespace MWSound void remove(MWWorld::Ptr ptr, const std::string &id = ""); bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; void removeCell(const MWWorld::Ptr::CellStore *cell); - void updatePositions(MWWorld::Ptr ptr); void updateRegionSound(float duration); public: From 979ae89aab8dadde4efff46e5a01e91436665fa7 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 09:37:41 -0700 Subject: [PATCH 033/152] Pass volume and pitch parameters to streamed sounds --- apps/openmw/mwsound/openal_output.cpp | 10 ++++++---- apps/openmw/mwsound/openal_output.hpp | 3 ++- apps/openmw/mwsound/sound_output.hpp | 3 ++- apps/openmw/mwsound/soundmanager.cpp | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index bf972512b..9131bbe29 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -92,7 +92,7 @@ public: OpenAL_SoundStream(std::auto_ptr decoder); virtual ~OpenAL_SoundStream(); - void Play(); + void Play(float volume, float pitch); virtual void Stop(); virtual bool isPlaying(); }; @@ -156,12 +156,14 @@ OpenAL_SoundStream::~OpenAL_SoundStream() Decoder->Close(); } -void OpenAL_SoundStream::Play() +void OpenAL_SoundStream::Play(float volume, float pitch) { std::vector data(BufferSize); alSourceStop(Source); alSourcei(Source, AL_BUFFER, 0); + alSourcef(Source, AL_GAIN, volume); + alSourcef(Source, AL_PITCH, pitch); throwALerror(); for(ALuint i = 0;i < NumBuffers;i++) @@ -395,14 +397,14 @@ Sound* OpenAL_Output::PlaySound3D(const std::string &fname, std::auto_ptr decoder) +Sound* OpenAL_Output::StreamSound(const std::string &fname, std::auto_ptr decoder, float volume, float pitch) { std::auto_ptr sound; decoder->Open(fname); sound.reset(new OpenAL_SoundStream(decoder)); - sound->Play(); + sound->Play(volume, pitch); return sound.release(); } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 0ab8dd349..65fe89c9e 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -28,7 +28,8 @@ namespace MWSound MWWorld::Ptr ptr, float volume, float pitch, float min, float max, bool loop); - virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder); + virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder, + float volume, float pitch); virtual void UpdateListener(float pos[3], float atdir[3], float updir[3]); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index ba9809291..9c8d0303b 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -24,7 +24,8 @@ namespace MWSound virtual Sound *PlaySound3D(const std::string &fname, std::auto_ptr decoder, MWWorld::Ptr ptr, float volume, float pitch, float min, float max, bool loop) = 0; - virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder) = 0; + virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder, + float volume, float pitch) = 0; // FIXME: This should take an MWWorld::Ptr that represents the in-world camera virtual void UpdateListener(float pos[3], float atdir[3], float updir[3]) = 0; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index b1caf7ff3..cab179f61 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -199,7 +199,7 @@ namespace MWSound if(mMusic) mMusic->Stop(); std::auto_ptr decoder(new DEFAULT_DECODER); - mMusic.reset(Output->StreamSound(filename, decoder)); + mMusic.reset(Output->StreamSound(filename, decoder, 0.4f, 1.0f)); } void SoundManager::streamMusic(const std::string& filename) From 5563f583ff6821337ae49c385986c84730925c6f Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 09:51:03 -0700 Subject: [PATCH 034/152] Add and implement methods to update tracked sounds on an object --- apps/openmw/mwsound/openal_output.cpp | 20 ++++++++++++++++++++ apps/openmw/mwsound/sound.hpp | 3 +++ apps/openmw/mwsound/soundmanager.cpp | 14 ++++++++++---- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 9131bbe29..c460aeb29 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -95,6 +95,7 @@ public: void Play(float volume, float pitch); virtual void Stop(); virtual bool isPlaying(); + virtual void Update(MWWorld::Ptr ptr); }; class OpenAL_Sound : public Sound @@ -108,6 +109,7 @@ public: virtual void Stop(); virtual bool isPlaying(); + virtual void Update(MWWorld::Ptr ptr); }; @@ -231,6 +233,15 @@ bool OpenAL_SoundStream::isPlaying() return true; } +void OpenAL_SoundStream::Update(MWWorld::Ptr ptr) +{ + const float *pos = ptr.getCellRef().pos.pos; + alSource3f(Source, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(Source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(Source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + OpenAL_Sound::OpenAL_Sound(ALuint src, ALuint buf) : Source(src), Buffer(buf) @@ -259,6 +270,15 @@ bool OpenAL_Sound::isPlaying() return state==AL_PLAYING; } +void OpenAL_Sound::Update(MWWorld::Ptr ptr) +{ + const float *pos = ptr.getCellRef().pos.pos; + alSource3f(Source, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(Source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(Source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + bool OpenAL_Output::Initialize(const std::string &devname) { diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 5fb996e28..99be9dfeb 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -3,12 +3,15 @@ #include +#include "../mwworld/ptr.hpp" + namespace MWSound { class Sound { virtual void Stop() = 0; virtual bool isPlaying() = 0; + virtual void Update(MWWorld::Ptr ptr) = 0; public: virtual ~Sound() { } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index cab179f61..5918a9cf7 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -353,15 +353,21 @@ namespace MWSound bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const { - // Mark all sounds as playing, otherwise the scripts will just - // keep trying to play them every frame. - return isPlaying(ptr, soundId); } void SoundManager::updateObject(MWWorld::Ptr ptr) { - // FIXME: Update tracked sounds that are using this ptr + SoundMap::iterator snditer = ActiveSounds.find(ptr); + if(snditer == ActiveSounds.end()) + return; + + IDMap::iterator iditer = snditer->second.begin(); + while(iditer != snditer->second.end()) + { + iditer->second->Update(ptr); + iditer++; + } } void SoundManager::updateRegionSound(float duration) From 87adf6002a4c8933269177038771db4a71d449d2 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 09:57:39 -0700 Subject: [PATCH 035/152] Fix a copy-paste typo in the openal output --- apps/openmw/mwsound/openal_output.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index c460aeb29..c8ed2ad8b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -336,7 +336,7 @@ Sound* OpenAL_Output::PlaySound(const std::string &fname, std::auto_ptr Date: Sat, 17 Mar 2012 10:16:22 -0700 Subject: [PATCH 036/152] Pass the loop and untracked flags when playing a 3d sound --- apps/openmw/mwsound/soundmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 5918a9cf7..9f80a8e20 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -329,7 +329,7 @@ namespace MWSound float min, max; std::string file = lookup(soundId, volume, min, max); if(!file.empty()) - play3d(file, ptr, soundId, volume, pitch, min, max, false); + play3d(file, ptr, soundId, volume, pitch, min, max, loop, untracked); else std::cout << "Sound file " << soundId << " not found, skipping.\n"; } From 7160d20db33f4b9e3d136dc613aa64a4c780412d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 10:36:34 -0700 Subject: [PATCH 037/152] Be more consistent with the vector orientations given the sound handler --- apps/openmw/mwsound/openal_output.cpp | 13 ++++++++----- apps/openmw/mwsound/openal_output.hpp | 4 ++-- apps/openmw/mwsound/sound_output.hpp | 5 ++--- apps/openmw/mwsound/soundmanager.cpp | 8 ++++++-- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index c8ed2ad8b..0cff2303f 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -368,7 +368,7 @@ Sound* OpenAL_Output::PlaySound(const std::string &fname, std::auto_ptr decoder, - MWWorld::Ptr ptr, float volume, float pitch, + const float *pos, float volume, float pitch, float min, float max, bool loop) { throwALerror(); @@ -393,7 +393,6 @@ Sound* OpenAL_Output::PlaySound3D(const std::string &fname, std::auto_ptr sound(new OpenAL_Sound(src, buf)); - const float *pos = ptr.getCellRef().pos.pos; alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -430,10 +429,14 @@ Sound* OpenAL_Output::StreamSound(const std::string &fname, std::auto_ptr decoder, float volume, float pitch, bool loop); virtual Sound *PlaySound3D(const std::string &fname, std::auto_ptr decoder, - MWWorld::Ptr ptr, float volume, float pitch, + const float *pos, float volume, float pitch, float min, float max, bool loop); virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder, float volume, float pitch); - virtual void UpdateListener(float pos[3], float atdir[3], float updir[3]); + virtual void UpdateListener(const float *pos, const float *atdir, const float *updir); OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 9c8d0303b..c69247cc9 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -22,13 +22,12 @@ namespace MWSound virtual Sound *PlaySound(const std::string &fname, std::auto_ptr decoder, float volume, float pitch, bool loop) = 0; virtual Sound *PlaySound3D(const std::string &fname, std::auto_ptr decoder, - MWWorld::Ptr ptr, float volume, float pitch, + const float *pos, float volume, float pitch, float min, float max, bool loop) = 0; virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder, float volume, float pitch) = 0; - // FIXME: This should take an MWWorld::Ptr that represents the in-world camera - virtual void UpdateListener(float pos[3], float atdir[3], float updir[3]) = 0; + virtual void UpdateListener(const float *pos, const float *atdir, const float *updir) = 0; Sound_Output(SoundManager &mgr) : mgr(mgr) { } public: diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 9f80a8e20..af1cdf76d 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -125,8 +125,9 @@ namespace MWSound try { Sound *sound; + const float *pos = ptr.getCellRef().pos.pos; std::auto_ptr decoder(new DEFAULT_DECODER); - sound = Output->PlaySound3D(file, decoder, ptr, volume, pitch, min, max, loop); + sound = Output->PlaySound3D(file, decoder, pos, volume, pitch, min, max, loop); if(untracked) LooseSounds[id] = SoundPtr(sound); else @@ -444,7 +445,10 @@ namespace MWSound nDir = cam->getRealDirection(); nUp = cam->getRealUp(); - Output->UpdateListener(&nPos[0], &nDir[0], &nUp[0]); + float pos[3] = { nPos[0], -nPos[2], nPos[1] }; + float at[3] = { nDir[0], -nDir[2], nDir[1] }; + float up[3] = { nUp[0], -nUp[2], nUp[1] }; + Output->UpdateListener(pos, at, up); IDMap::iterator snditer = LooseSounds.begin(); From a91085a1b904bb26cf8410721e978fc73c3392b6 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 10:45:11 -0700 Subject: [PATCH 038/152] Add a couple comments --- apps/openmw/mwsound/soundmanager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index af1cdf76d..5d872476e 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -445,12 +445,16 @@ namespace MWSound nDir = cam->getRealDirection(); nUp = cam->getRealUp(); + // The output handler is expecting vectors oriented like the game + // (that is, -Z goes down, +Y goes forward), but that's not what we + // get from Ogre's camera, so we have to convert. float pos[3] = { nPos[0], -nPos[2], nPos[1] }; float at[3] = { nDir[0], -nDir[2], nDir[1] }; float up[3] = { nUp[0], -nUp[2], nUp[1] }; Output->UpdateListener(pos, at, up); - + // Check if any "untracked" sounds are finished playing, and trash + // them IDMap::iterator snditer = LooseSounds.begin(); while(snditer != LooseSounds.end()) { From a69ec91242eb1a781d944283af1c17b2d4110b2b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 22:13:57 -0700 Subject: [PATCH 039/152] Remove some unnecessary wrappers and do some small cleanups --- apps/openmw/mwsound/soundmanager.cpp | 68 ++++++++++++---------------- apps/openmw/mwsound/soundmanager.hpp | 2 - 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 5d872476e..5b050e612 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -78,8 +78,6 @@ namespace MWSound { LooseSounds.clear(); ActiveSounds.clear(); - if(mMusic) - mMusic->Stop(); mMusic.reset(); Output.reset(); } @@ -135,30 +133,8 @@ namespace MWSound } catch(std::exception &e) { - std::cout <<"Sound play error: "<second.find(id); - if(iditer != snditer->second.end()) - { - snditer->second.erase(iditer); - if(snditer->second.size() == 0) - ActiveSounds.erase(snditer); - } + std::cout <<"Sound Error: "<second->isPlaying(); } - // Remove all references to objects belonging to a given cell - void SoundManager::removeCell(const MWWorld::Ptr::CellStore *cell) - { - SoundMap::iterator snditer = ActiveSounds.begin(); - while(snditer != ActiveSounds.end()) - { - if(snditer->first.getCell() == cell) - ActiveSounds.erase(snditer++); - else - snditer++; - } - } void SoundManager::stopMusic() { @@ -194,7 +158,6 @@ namespace MWSound setPlaylist(); } - void SoundManager::streamMusicFull(const std::string& filename) { if(mMusic) @@ -337,12 +300,37 @@ namespace MWSound void SoundManager::stopSound3D(MWWorld::Ptr ptr, const std::string& soundId) { - remove(ptr, soundId); + // Stop a sound and remove it from the list. If soundId="" then + // stop all its sounds. + SoundMap::iterator snditer = ActiveSounds.find(ptr); + if(snditer == ActiveSounds.end()) + return; + + if(!soundId.empty()) + { + IDMap::iterator iditer = snditer->second.find(soundId); + if(iditer != snditer->second.end()) + { + snditer->second.erase(iditer); + if(snditer->second.size() == 0) + ActiveSounds.erase(snditer); + } + } + else + ActiveSounds.erase(snditer); } void SoundManager::stopSound(MWWorld::Ptr::CellStore *cell) { - removeCell(cell); + // Remove all references to objects belonging to a given cell + SoundMap::iterator snditer = ActiveSounds.begin(); + while(snditer != ActiveSounds.end()) + { + if(snditer->first.getCell() == cell) + ActiveSounds.erase(snditer++); + else + snditer++; + } } void SoundManager::stopSound(const std::string& soundId) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index df7e696da..62e452cde 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -64,9 +64,7 @@ namespace MWSound MWWorld::Ptr ptr, const std::string &id, float volume, float pitch, float min, float max, bool loop, bool untracked=false); - void remove(MWWorld::Ptr ptr, const std::string &id = ""); bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; - void removeCell(const MWWorld::Ptr::CellStore *cell); void updateRegionSound(float duration); public: From ddfa9069220c3560df1a14332f1c8b2266c7b04c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 22:45:28 -0700 Subject: [PATCH 040/152] Add a missing include --- apps/openmw/mwsound/soundmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 5b050e612..7cd8f4ea7 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -23,6 +23,7 @@ CMakeLists.txt. */ #ifdef OPENMW_USE_FFMPEG +#include "ffmpeg_decoder.hpp" #define SOUND_IN "FFmpeg" #endif From 44fc204864f15485c53d9f80492679d0b7b19d5a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 23:30:43 -0700 Subject: [PATCH 041/152] Avoid passing a sound decoder to the play methods --- apps/openmw/mwsound/mpgsnd_decoder.hpp | 1 + apps/openmw/mwsound/openal_output.cpp | 20 +++++++++++--------- apps/openmw/mwsound/openal_output.hpp | 9 +++------ apps/openmw/mwsound/sound_output.hpp | 9 +++------ apps/openmw/mwsound/soundmanager.cpp | 17 ++++++++++------- apps/openmw/mwsound/soundmanager.hpp | 6 ++++++ 6 files changed, 34 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 391cfbd6d..cd7e468da 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -26,6 +26,7 @@ namespace MWSound virtual size_t Read(char *buffer, size_t bytes); MpgSnd_Decoder(); + public: virtual ~MpgSnd_Decoder(); friend class SoundManager; diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 0cff2303f..e9df0738b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -5,6 +5,7 @@ #include "openal_output.hpp" #include "sound_decoder.hpp" #include "sound.hpp" +#include "soundmanager.hpp" namespace MWSound @@ -47,7 +48,7 @@ static ALenum getALFormat(Sound_Decoder::ChannelConfig chans, Sound_Decoder::Sam } -ALuint LoadBuffer(std::auto_ptr decoder) +ALuint LoadBuffer(DecoderPtr decoder) { int srate; Sound_Decoder::ChannelConfig chans; @@ -86,10 +87,10 @@ class OpenAL_SoundStream : public Sound ALenum Format; ALsizei SampleRate; - std::auto_ptr Decoder; + DecoderPtr Decoder; public: - OpenAL_SoundStream(std::auto_ptr decoder); + OpenAL_SoundStream(DecoderPtr decoder); virtual ~OpenAL_SoundStream(); void Play(float volume, float pitch); @@ -113,7 +114,7 @@ public: }; -OpenAL_SoundStream::OpenAL_SoundStream(std::auto_ptr decoder) +OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) : Decoder(decoder) { throwALerror(); @@ -320,11 +321,11 @@ void OpenAL_Output::Deinitialize() } -Sound* OpenAL_Output::PlaySound(const std::string &fname, std::auto_ptr decoder, - float volume, float pitch, bool loop) +Sound* OpenAL_Output::PlaySound(const std::string &fname, float volume, float pitch, bool loop) { throwALerror(); + DecoderPtr decoder = mgr.getDecoder(); decoder->Open(fname); ALuint src=0, buf=0; @@ -367,12 +368,12 @@ Sound* OpenAL_Output::PlaySound(const std::string &fname, std::auto_ptr decoder, - const float *pos, float volume, float pitch, +Sound* OpenAL_Output::PlaySound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop) { throwALerror(); + DecoderPtr decoder = mgr.getDecoder(); decoder->Open(fname); ALuint src=0, buf=0; @@ -416,10 +417,11 @@ Sound* OpenAL_Output::PlaySound3D(const std::string &fname, std::auto_ptr decoder, float volume, float pitch) +Sound* OpenAL_Output::StreamSound(const std::string &fname, float volume, float pitch) { std::auto_ptr sound; + DecoderPtr decoder = mgr.getDecoder(); decoder->Open(fname); sound.reset(new OpenAL_SoundStream(decoder)); diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index b941aea3a..7d5bd25f6 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -22,14 +22,11 @@ namespace MWSound virtual bool Initialize(const std::string &devname=""); virtual void Deinitialize(); - virtual Sound *PlaySound(const std::string &fname, std::auto_ptr decoder, - float volume, float pitch, bool loop); - virtual Sound *PlaySound3D(const std::string &fname, std::auto_ptr decoder, - const float *pos, float volume, float pitch, + virtual Sound *PlaySound(const std::string &fname, float volume, float pitch, bool loop); + virtual Sound *PlaySound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop); - virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder, - float volume, float pitch); + virtual Sound *StreamSound(const std::string &fname, float volume, float pitch); virtual void UpdateListener(const float *pos, const float *atdir, const float *updir); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index c69247cc9..8dfc0f21e 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -19,13 +19,10 @@ namespace MWSound virtual bool Initialize(const std::string &devname="") = 0; virtual void Deinitialize() = 0; - virtual Sound *PlaySound(const std::string &fname, std::auto_ptr decoder, - float volume, float pitch, bool loop) = 0; - virtual Sound *PlaySound3D(const std::string &fname, std::auto_ptr decoder, - const float *pos, float volume, float pitch, + virtual Sound *PlaySound(const std::string &fname, float volume, float pitch, bool loop) = 0; + virtual Sound *PlaySound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop) = 0; - virtual Sound *StreamSound(const std::string &fname, std::auto_ptr decoder, - float volume, float pitch) = 0; + virtual Sound *StreamSound(const std::string &fname, float volume, float pitch) = 0; virtual void UpdateListener(const float *pos, const float *atdir, const float *updir) = 0; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 7cd8f4ea7..a603763e5 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -83,6 +83,12 @@ namespace MWSound Output.reset(); } + // Return a new decoder instance, used as needed by the output implementations + DecoderPtr SoundManager::getDecoder() + { + return DecoderPtr(new DEFAULT_DECODER); + } + // Convert a soundId to file name, and modify the volume // according to the sounds local volume setting, minRange and // maxRange. @@ -124,9 +130,8 @@ namespace MWSound try { Sound *sound; - const float *pos = ptr.getCellRef().pos.pos; - std::auto_ptr decoder(new DEFAULT_DECODER); - sound = Output->PlaySound3D(file, decoder, pos, volume, pitch, min, max, loop); + const ESM::Position &pos = ptr.getCellRef().pos; + sound = Output->PlaySound3D(file, pos.pos, volume, pitch, min, max, loop); if(untracked) LooseSounds[id] = SoundPtr(sound); else @@ -163,8 +168,7 @@ namespace MWSound { if(mMusic) mMusic->Stop(); - std::auto_ptr decoder(new DEFAULT_DECODER); - mMusic.reset(Output->StreamSound(filename, decoder, 0.4f, 1.0f)); + mMusic.reset(Output->StreamSound(filename, 0.4f, 1.0f)); } void SoundManager::streamMusic(const std::string& filename) @@ -274,8 +278,7 @@ namespace MWSound try { Sound *sound; - std::auto_ptr decoder(new DEFAULT_DECODER); - sound = Output->PlaySound(file, decoder, volume, pitch, loop); + sound = Output->PlaySound(file, volume, pitch, loop); LooseSounds[soundId] = SoundPtr(sound); } catch(std::exception &e) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 62e452cde..6b188c9e4 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -25,6 +25,8 @@ namespace MWSound class Sound_Decoder; class Sound; + typedef boost::shared_ptr DecoderPtr; + class SoundManager { // This is used for case insensitive and slash-type agnostic file @@ -67,6 +69,10 @@ namespace MWSound bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; void updateRegionSound(float duration); + protected: + DecoderPtr getDecoder(); + friend class OpenAL_Output; + public: SoundManager(Ogre::Root*, Ogre::Camera*, const Files::PathContainer& dataDir, bool useSound, bool fsstrict, From f7ac94d6860fc43344e403ce7904b2197262059a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 23:41:45 -0700 Subject: [PATCH 042/152] Pass the new position to the sound update method --- apps/openmw/mwsound/openal_output.cpp | 10 ++++------ apps/openmw/mwsound/sound.hpp | 2 +- apps/openmw/mwsound/soundmanager.cpp | 3 ++- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index e9df0738b..e37924088 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -96,7 +96,7 @@ public: void Play(float volume, float pitch); virtual void Stop(); virtual bool isPlaying(); - virtual void Update(MWWorld::Ptr ptr); + virtual void Update(const float *pos); }; class OpenAL_Sound : public Sound @@ -110,7 +110,7 @@ public: virtual void Stop(); virtual bool isPlaying(); - virtual void Update(MWWorld::Ptr ptr); + virtual void Update(const float *pos); }; @@ -234,9 +234,8 @@ bool OpenAL_SoundStream::isPlaying() return true; } -void OpenAL_SoundStream::Update(MWWorld::Ptr ptr) +void OpenAL_SoundStream::Update(const float *pos) { - const float *pos = ptr.getCellRef().pos.pos; alSource3f(Source, AL_POSITION, pos[0], pos[2], -pos[1]); alSource3f(Source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(Source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -271,9 +270,8 @@ bool OpenAL_Sound::isPlaying() return state==AL_PLAYING; } -void OpenAL_Sound::Update(MWWorld::Ptr ptr) +void OpenAL_Sound::Update(const float *pos) { - const float *pos = ptr.getCellRef().pos.pos; alSource3f(Source, AL_POSITION, pos[0], pos[2], -pos[1]); alSource3f(Source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(Source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 99be9dfeb..3b4736fd6 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -11,7 +11,7 @@ namespace MWSound { virtual void Stop() = 0; virtual bool isPlaying() = 0; - virtual void Update(MWWorld::Ptr ptr) = 0; + virtual void Update(const float *pos) = 0; public: virtual ~Sound() { } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index a603763e5..650c8f0d9 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -358,7 +358,8 @@ namespace MWSound IDMap::iterator iditer = snditer->second.begin(); while(iditer != snditer->second.end()) { - iditer->second->Update(ptr); + const ESM::Position &pos = ptr.getCellRef().pos; + iditer->second->Update(pos.pos); iditer++; } } From 3fea3e7d25e59e45c5357616f46fc05e3fb56fa5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Mar 2012 23:47:12 -0700 Subject: [PATCH 043/152] Fix stereo files with libsndfile --- apps/openmw/mwsound/mpgsnd_decoder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 2c35adeb3..9b51319dd 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -22,8 +22,8 @@ void MpgSnd_Decoder::Open(const std::string &fname) { if(info.channels == 1) chanConfig = MonoChannels; - else if(info.channels == 1) - chanConfig = MonoChannels; + else if(info.channels == 2) + chanConfig = StereoChannels; else { sf_close(sndFile); From a256b9a7b0952803aacad7a27d4c8c2225b1418b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 08:42:55 -0700 Subject: [PATCH 044/152] Remove a comment about Audiere --- apps/openmw/mwsound/soundmanager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 650c8f0d9..895d580db 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -18,9 +18,8 @@ #include "openal_output.hpp" #define SOUND_OUT "OpenAL" -/* Set up the sound manager to use Audiere, FFMPEG or - MPG123/libsndfile for input. The OPENMW_USE_x macros are set in - CMakeLists.txt. +/* Set up the sound manager to use FFMPEG or MPG123+libsndfile for input. The + * OPENMW_USE_x macros are set in CMakeLists.txt. */ #ifdef OPENMW_USE_FFMPEG #include "ffmpeg_decoder.hpp" From 2f92559fc7b7c6d64b2f5d7cd0435da20689e67a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 09:05:38 -0700 Subject: [PATCH 045/152] Use OpenAL's linear attenuation model We should use the inverse distance clamped model (the default), but we first need to handle muting sounds that are beyond their max distance. Linear attenuation doesn't give a proper rolloff, but it makes the sounds silent at max distance. --- apps/openmw/mwsound/openal_output.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index e37924088..5ce793168 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -303,6 +303,8 @@ bool OpenAL_Output::Initialize(const std::string &devname) Device = 0; return false; } + alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); + throwALerror(); return true; } From 162642e6726ec741731520fde4909e4d681e7bc2 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 11:17:45 -0700 Subject: [PATCH 046/152] Prefix some SoundManager class member variables --- apps/openmw/mwsound/soundmanager.cpp | 60 ++++++++++++++-------------- apps/openmw/mwsound/soundmanager.hpp | 6 +-- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 895d580db..d78251ef1 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -47,10 +47,10 @@ namespace MWSound std::cout << "Sound output: " << SOUND_OUT << std::endl; std::cout << "Sound decoder: " << SOUND_IN << std::endl; - Output.reset(new DEFAULT_OUTPUT(*this)); - if(!Output->Initialize()) + mOutput.reset(new DEFAULT_OUTPUT(*this)); + if(!mOutput->Initialize()) { - Output.reset(); + mOutput.reset(); return; } @@ -76,10 +76,10 @@ namespace MWSound SoundManager::~SoundManager() { - LooseSounds.clear(); - ActiveSounds.clear(); + mLooseSounds.clear(); + mActiveSounds.clear(); mMusic.reset(); - Output.reset(); + mOutput.reset(); } // Return a new decoder instance, used as needed by the output implementations @@ -130,11 +130,11 @@ namespace MWSound { Sound *sound; const ESM::Position &pos = ptr.getCellRef().pos; - sound = Output->PlaySound3D(file, pos.pos, volume, pitch, min, max, loop); + sound = mOutput->PlaySound3D(file, pos.pos, volume, pitch, min, max, loop); if(untracked) - LooseSounds[id] = SoundPtr(sound); + mLooseSounds[id] = SoundPtr(sound); else - ActiveSounds[ptr][id] = SoundPtr(sound); + mActiveSounds[ptr][id] = SoundPtr(sound); } catch(std::exception &e) { @@ -144,8 +144,8 @@ namespace MWSound bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { - SoundMap::const_iterator snditer = ActiveSounds.find(ptr); - if(snditer == ActiveSounds.end()) + SoundMap::const_iterator snditer = mActiveSounds.find(ptr); + if(snditer == mActiveSounds.end()) return false; IDMap::const_iterator iditer = snditer->second.find(id); @@ -167,7 +167,7 @@ namespace MWSound { if(mMusic) mMusic->Stop(); - mMusic.reset(Output->StreamSound(filename, 0.4f, 1.0f)); + mMusic.reset(mOutput->StreamSound(filename, 0.4f, 1.0f)); } void SoundManager::streamMusic(const std::string& filename) @@ -277,8 +277,8 @@ namespace MWSound try { Sound *sound; - sound = Output->PlaySound(file, volume, pitch, loop); - LooseSounds[soundId] = SoundPtr(sound); + sound = mOutput->PlaySound(file, volume, pitch, loop); + mLooseSounds[soundId] = SoundPtr(sound); } catch(std::exception &e) { @@ -305,8 +305,8 @@ namespace MWSound { // Stop a sound and remove it from the list. If soundId="" then // stop all its sounds. - SoundMap::iterator snditer = ActiveSounds.find(ptr); - if(snditer == ActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr); + if(snditer == mActiveSounds.end()) return; if(!soundId.empty()) @@ -316,21 +316,21 @@ namespace MWSound { snditer->second.erase(iditer); if(snditer->second.size() == 0) - ActiveSounds.erase(snditer); + mActiveSounds.erase(snditer); } } else - ActiveSounds.erase(snditer); + mActiveSounds.erase(snditer); } void SoundManager::stopSound(MWWorld::Ptr::CellStore *cell) { // Remove all references to objects belonging to a given cell - SoundMap::iterator snditer = ActiveSounds.begin(); - while(snditer != ActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { if(snditer->first.getCell() == cell) - ActiveSounds.erase(snditer++); + mActiveSounds.erase(snditer++); else snditer++; } @@ -338,9 +338,9 @@ namespace MWSound void SoundManager::stopSound(const std::string& soundId) { - IDMap::iterator iditer = LooseSounds.find(soundId); - if(iditer != LooseSounds.end()) - LooseSounds.erase(iditer); + IDMap::iterator iditer = mLooseSounds.find(soundId); + if(iditer != mLooseSounds.end()) + mLooseSounds.erase(iditer); } bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const @@ -350,8 +350,8 @@ namespace MWSound void SoundManager::updateObject(MWWorld::Ptr ptr) { - SoundMap::iterator snditer = ActiveSounds.find(ptr); - if(snditer == ActiveSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(ptr); + if(snditer == mActiveSounds.end()) return; IDMap::iterator iditer = snditer->second.begin(); @@ -443,15 +443,15 @@ namespace MWSound float pos[3] = { nPos[0], -nPos[2], nPos[1] }; float at[3] = { nDir[0], -nDir[2], nDir[1] }; float up[3] = { nUp[0], -nUp[2], nUp[1] }; - Output->UpdateListener(pos, at, up); + mOutput->UpdateListener(pos, at, up); // Check if any "untracked" sounds are finished playing, and trash // them - IDMap::iterator snditer = LooseSounds.begin(); - while(snditer != LooseSounds.end()) + IDMap::iterator snditer = mLooseSounds.begin(); + while(snditer != mLooseSounds.end()) { if(!snditer->second->isPlaying()) - LooseSounds.erase(snditer++); + mLooseSounds.erase(snditer++); else snditer++; } diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 6b188c9e4..3c7826821 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -37,7 +37,7 @@ namespace MWSound MWWorld::Environment& mEnvironment; - std::auto_ptr Output; + std::auto_ptr mOutput; boost::shared_ptr mMusic; @@ -57,8 +57,8 @@ namespace MWSound typedef boost::shared_ptr SoundPtr; typedef std::map IDMap; typedef std::map SoundMap; - SoundMap ActiveSounds; - IDMap LooseSounds; + SoundMap mActiveSounds; + IDMap mLooseSounds; std::string lookup(const std::string &soundId, float &volume, float &min, float &max); From 362e25472017839f468a3194058a2d83b5a4a715 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 11:30:53 -0700 Subject: [PATCH 047/152] Rename some more sound class member variables and functions --- apps/openmw/mwsound/openal_output.cpp | 162 +++++++++++++------------- apps/openmw/mwsound/openal_output.hpp | 16 +-- apps/openmw/mwsound/sound_output.hpp | 16 +-- apps/openmw/mwsound/soundmanager.cpp | 10 +- 4 files changed, 102 insertions(+), 102 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 5ce793168..ad21aefbe 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -78,16 +78,16 @@ class OpenAL_SoundStream : public Sound { // This should be something sane, like 4, but currently cell loads tend to // cause the stream to underrun - static const ALuint NumBuffers = 150; - static const ALuint BufferSize = 32768; + static const ALuint sNumBuffers = 150; + static const ALuint sBufferSize = 32768; - ALuint Source; - ALuint Buffers[NumBuffers]; + ALuint mSource; + ALuint mBuffers[sNumBuffers]; - ALenum Format; - ALsizei SampleRate; + ALenum mFormat; + ALsizei mSampleRate; - DecoderPtr Decoder; + DecoderPtr mDecoder; public: OpenAL_SoundStream(DecoderPtr decoder); @@ -102,8 +102,8 @@ public: class OpenAL_Sound : public Sound { public: - ALuint Source; - ALuint Buffer; + ALuint mSource; + ALuint mBuffer; OpenAL_Sound(ALuint src, ALuint buf); virtual ~OpenAL_Sound(); @@ -115,20 +115,20 @@ public: OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) - : Decoder(decoder) + : mDecoder(decoder) { throwALerror(); - alGenSources(1, &Source); + alGenSources(1, &mSource); throwALerror(); try { - alGenBuffers(NumBuffers, Buffers); + alGenBuffers(sNumBuffers, mBuffers); throwALerror(); } catch(std::exception &e) { - alDeleteSources(1, &Source); + alDeleteSources(1, &mSource); alGetError(); throw; } @@ -139,53 +139,53 @@ OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) Sound_Decoder::ChannelConfig chans; Sound_Decoder::SampleType type; - Decoder->GetInfo(&srate, &chans, &type); - Format = getALFormat(chans, type); - SampleRate = srate; + mDecoder->GetInfo(&srate, &chans, &type); + mFormat = getALFormat(chans, type); + mSampleRate = srate; } catch(std::exception &e) { - alDeleteSources(1, &Source); - alDeleteBuffers(NumBuffers, Buffers); + alDeleteSources(1, &mSource); + alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); throw; } } OpenAL_SoundStream::~OpenAL_SoundStream() { - alDeleteSources(1, &Source); - alDeleteBuffers(NumBuffers, Buffers); + alDeleteSources(1, &mSource); + alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); - Decoder->Close(); + mDecoder->Close(); } void OpenAL_SoundStream::Play(float volume, float pitch) { - std::vector data(BufferSize); + std::vector data(sBufferSize); - alSourceStop(Source); - alSourcei(Source, AL_BUFFER, 0); - alSourcef(Source, AL_GAIN, volume); - alSourcef(Source, AL_PITCH, pitch); + alSourceStop(mSource); + alSourcei(mSource, AL_BUFFER, 0); + alSourcef(mSource, AL_GAIN, volume); + alSourcef(mSource, AL_PITCH, pitch); throwALerror(); - for(ALuint i = 0;i < NumBuffers;i++) + for(ALuint i = 0;i < sNumBuffers;i++) { size_t got; - got = Decoder->Read(&data[0], data.size()); - alBufferData(Buffers[i], Format, &data[0], got, SampleRate); + got = mDecoder->Read(&data[0], data.size()); + alBufferData(mBuffers[i], mFormat, &data[0], got, mSampleRate); } throwALerror(); - alSourceQueueBuffers(Source, NumBuffers, Buffers); - alSourcePlay(Source); + alSourceQueueBuffers(mSource, sNumBuffers, mBuffers); + alSourcePlay(mSource); throwALerror(); } void OpenAL_SoundStream::Stop() { - alSourceStop(Source); - alSourcei(Source, AL_BUFFER, 0); + alSourceStop(mSource); + alSourcei(mSource, AL_BUFFER, 0); throwALerror(); // FIXME: Rewind decoder } @@ -194,25 +194,25 @@ bool OpenAL_SoundStream::isPlaying() { ALint processed, state; - alGetSourcei(Source, AL_SOURCE_STATE, &state); - alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed); throwALerror(); if(processed > 0) { - std::vector data(BufferSize); + std::vector data(sBufferSize); do { ALuint bufid; size_t got; - alSourceUnqueueBuffers(Source, 1, &bufid); + alSourceUnqueueBuffers(mSource, 1, &bufid); processed--; - got = Decoder->Read(&data[0], data.size()); + got = mDecoder->Read(&data[0], data.size()); if(got > 0) { - alBufferData(bufid, Format, &data[0], got, SampleRate); - alSourceQueueBuffers(Source, 1, &bufid); + alBufferData(bufid, mFormat, &data[0], got, mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); } } while(processed > 0); throwALerror(); @@ -222,12 +222,12 @@ bool OpenAL_SoundStream::isPlaying() { ALint queued; - alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued); + alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); throwALerror(); if(queued == 0) return false; - alSourcePlay(Source); + alSourcePlay(mSource); throwALerror(); } @@ -236,27 +236,27 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::Update(const float *pos) { - alSource3f(Source, AL_POSITION, pos[0], pos[2], -pos[1]); - alSource3f(Source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(Source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); } OpenAL_Sound::OpenAL_Sound(ALuint src, ALuint buf) - : Source(src), Buffer(buf) + : mSource(src), mBuffer(buf) { } OpenAL_Sound::~OpenAL_Sound() { - alDeleteSources(1, &Source); - alDeleteBuffers(1, &Buffer); + alDeleteSources(1, &mSource); + alDeleteBuffers(1, &mBuffer); alGetError(); } void OpenAL_Sound::Stop() { - alSourceStop(Source); + alSourceStop(mSource); throwALerror(); } @@ -264,7 +264,7 @@ bool OpenAL_Sound::isPlaying() { ALint state; - alGetSourcei(Source, AL_SOURCE_STATE, &state); + alGetSourcei(mSource, AL_SOURCE_STATE, &state); throwALerror(); return state==AL_PLAYING; @@ -272,35 +272,35 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::Update(const float *pos) { - alSource3f(Source, AL_POSITION, pos[0], pos[2], -pos[1]); - alSource3f(Source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(Source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); } -bool OpenAL_Output::Initialize(const std::string &devname) +bool OpenAL_Output::init(const std::string &devname) { - if(Context) + if(mContext) fail("Device already initialized"); - Device = alcOpenDevice(devname.c_str()); - if(!Device) + mDevice = alcOpenDevice(devname.c_str()); + if(!mDevice) { std::cout << "Failed to open \""<Open(fname); ALuint src=0, buf=0; @@ -368,12 +368,12 @@ Sound* OpenAL_Output::PlaySound(const std::string &fname, float volume, float pi return sound.release(); } -Sound* OpenAL_Output::PlaySound3D(const std::string &fname, const float *pos, float volume, float pitch, +Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop) { throwALerror(); - DecoderPtr decoder = mgr.getDecoder(); + DecoderPtr decoder = mManager.getDecoder(); decoder->Open(fname); ALuint src=0, buf=0; @@ -417,11 +417,11 @@ Sound* OpenAL_Output::PlaySound3D(const std::string &fname, const float *pos, fl } -Sound* OpenAL_Output::StreamSound(const std::string &fname, float volume, float pitch) +Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) { std::auto_ptr sound; - DecoderPtr decoder = mgr.getDecoder(); + DecoderPtr decoder = mManager.getDecoder(); decoder->Open(fname); sound.reset(new OpenAL_SoundStream(decoder)); @@ -431,7 +431,7 @@ Sound* OpenAL_Output::StreamSound(const std::string &fname, float volume, float } -void OpenAL_Output::UpdateListener(const float *pos, const float *atdir, const float *updir) +void OpenAL_Output::updateListener(const float *pos, const float *atdir, const float *updir) { float orient[6] = { atdir[0], atdir[2], -atdir[1], @@ -445,13 +445,13 @@ void OpenAL_Output::UpdateListener(const float *pos, const float *atdir, const f OpenAL_Output::OpenAL_Output(SoundManager &mgr) - : Sound_Output(mgr), Device(0), Context(0) + : Sound_Output(mgr), mDevice(0), mContext(0) { } OpenAL_Output::~OpenAL_Output() { - Deinitialize(); + deinit(); } } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 7d5bd25f6..25c414c6d 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -16,19 +16,19 @@ namespace MWSound class OpenAL_Output : public Sound_Output { - ALCdevice *Device; - ALCcontext *Context; + ALCdevice *mDevice; + ALCcontext *mContext; - virtual bool Initialize(const std::string &devname=""); - virtual void Deinitialize(); + virtual bool init(const std::string &devname=""); + virtual void deinit(); - virtual Sound *PlaySound(const std::string &fname, float volume, float pitch, bool loop); - virtual Sound *PlaySound3D(const std::string &fname, const float *pos, float volume, float pitch, + virtual Sound *playSound(const std::string &fname, float volume, float pitch, bool loop); + virtual Sound *playSound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop); - virtual Sound *StreamSound(const std::string &fname, float volume, float pitch); + virtual Sound *streamSound(const std::string &fname, float volume, float pitch); - virtual void UpdateListener(const float *pos, const float *atdir, const float *updir); + virtual void updateListener(const float *pos, const float *atdir, const float *updir); OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 8dfc0f21e..6497cc1bd 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -14,19 +14,19 @@ namespace MWSound class Sound_Output { - SoundManager &mgr; + SoundManager &mManager; - virtual bool Initialize(const std::string &devname="") = 0; - virtual void Deinitialize() = 0; + virtual bool init(const std::string &devname="") = 0; + virtual void deinit() = 0; - virtual Sound *PlaySound(const std::string &fname, float volume, float pitch, bool loop) = 0; - virtual Sound *PlaySound3D(const std::string &fname, const float *pos, float volume, float pitch, + virtual Sound *playSound(const std::string &fname, float volume, float pitch, bool loop) = 0; + virtual Sound *playSound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop) = 0; - virtual Sound *StreamSound(const std::string &fname, float volume, float pitch) = 0; + virtual Sound *streamSound(const std::string &fname, float volume, float pitch) = 0; - virtual void UpdateListener(const float *pos, const float *atdir, const float *updir) = 0; + virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; - Sound_Output(SoundManager &mgr) : mgr(mgr) { } + Sound_Output(SoundManager &mgr) : mManager(mgr) { } public: virtual ~Sound_Output() { } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index d78251ef1..c496701c7 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -48,7 +48,7 @@ namespace MWSound std::cout << "Sound decoder: " << SOUND_IN << std::endl; mOutput.reset(new DEFAULT_OUTPUT(*this)); - if(!mOutput->Initialize()) + if(!mOutput->init()) { mOutput.reset(); return; @@ -130,7 +130,7 @@ namespace MWSound { Sound *sound; const ESM::Position &pos = ptr.getCellRef().pos; - sound = mOutput->PlaySound3D(file, pos.pos, volume, pitch, min, max, loop); + sound = mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop); if(untracked) mLooseSounds[id] = SoundPtr(sound); else @@ -167,7 +167,7 @@ namespace MWSound { if(mMusic) mMusic->Stop(); - mMusic.reset(mOutput->StreamSound(filename, 0.4f, 1.0f)); + mMusic.reset(mOutput->streamSound(filename, 0.4f, 1.0f)); } void SoundManager::streamMusic(const std::string& filename) @@ -277,7 +277,7 @@ namespace MWSound try { Sound *sound; - sound = mOutput->PlaySound(file, volume, pitch, loop); + sound = mOutput->playSound(file, volume, pitch, loop); mLooseSounds[soundId] = SoundPtr(sound); } catch(std::exception &e) @@ -443,7 +443,7 @@ namespace MWSound float pos[3] = { nPos[0], -nPos[2], nPos[1] }; float at[3] = { nDir[0], -nDir[2], nDir[1] }; float up[3] = { nUp[0], -nUp[2], nUp[1] }; - mOutput->UpdateListener(pos, at, up); + mOutput->updateListener(pos, at, up); // Check if any "untracked" sounds are finished playing, and trash // them From 9656456d30adb6a30a0de6637ca4e0f6579ce6af Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 11:34:23 -0700 Subject: [PATCH 048/152] Make sure the sound decoders are closed when they're finished with --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 1 + apps/openmw/mwsound/mpgsnd_decoder.cpp | 1 + apps/openmw/mwsound/openal_output.cpp | 2 ++ 3 files changed, 4 insertions(+) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 5c64e4dd1..41e25e5b6 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -36,6 +36,7 @@ FFmpeg_Decoder::FFmpeg_Decoder() FFmpeg_Decoder::~FFmpeg_Decoder() { + Close(); } } diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 9b51319dd..dbe1d0a50 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -118,6 +118,7 @@ MpgSnd_Decoder::MpgSnd_Decoder() : sndFile(NULL), mpgFile(NULL) MpgSnd_Decoder::~MpgSnd_Decoder() { + Close(); } } diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ad21aefbe..f28b09c07 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -332,6 +332,7 @@ Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pi try { buf = LoadBuffer(decoder); + decoder->Close(); alGenSources(1, &src); throwALerror(); } @@ -380,6 +381,7 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl try { buf = LoadBuffer(decoder); + decoder->Close(); alGenSources(1, &src); throwALerror(); } From efae7dfe8385a9a4455a3b730a5da2c0e6599de3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 11:47:15 -0700 Subject: [PATCH 049/152] Rename some sound decoder class member variables and functions --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 10 ++-- apps/openmw/mwsound/ffmpeg_decoder.hpp | 8 +-- apps/openmw/mwsound/mpgsnd_decoder.cpp | 79 +++++++++++++------------- apps/openmw/mwsound/mpgsnd_decoder.hpp | 16 +++--- apps/openmw/mwsound/openal_output.cpp | 22 +++---- apps/openmw/mwsound/sound_decoder.hpp | 8 +-- 6 files changed, 72 insertions(+), 71 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 41e25e5b6..2b84439c0 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -9,21 +9,21 @@ namespace MWSound static void fail(const std::string &msg) { throw std::runtime_error("FFmpeg exception: "+msg); } -void FFmpeg_Decoder::Open(const std::string &fname) +void FFmpeg_Decoder::open(const std::string &fname) { fail("Not currently working"); } -void FFmpeg_Decoder::Close() +void FFmpeg_Decoder::close() { } -void FFmpeg_Decoder::GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type) +void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { fail("Not currently working"); } -size_t FFmpeg_Decoder::Read(char *buffer, size_t bytes) +size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { fail("Not currently working"); return 0; @@ -36,7 +36,7 @@ FFmpeg_Decoder::FFmpeg_Decoder() FFmpeg_Decoder::~FFmpeg_Decoder() { - Close(); + close(); } } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 205fe8865..676a576da 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -16,11 +16,11 @@ namespace MWSound { class FFmpeg_Decoder : public Sound_Decoder { - virtual void Open(const std::string &fname); - virtual void Close(); + virtual void open(const std::string &fname); + virtual void close(); - virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type); - virtual size_t Read(char *buffer, size_t bytes); + virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); + virtual size_t read(char *buffer, size_t bytes); FFmpeg_Decoder(); virtual ~FFmpeg_Decoder(); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index dbe1d0a50..6be6566f0 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -12,103 +12,104 @@ static void fail(const std::string &msg) namespace MWSound { -void MpgSnd_Decoder::Open(const std::string &fname) +void MpgSnd_Decoder::open(const std::string &fname) { - Close(); + close(); SF_INFO info; - sndFile = sf_open(fname.c_str(), SFM_READ, &info); - if(sndFile) + mSndFile = sf_open(fname.c_str(), SFM_READ, &info); + if(mSndFile) { if(info.channels == 1) - chanConfig = MonoChannels; + mChanConfig = MonoChannels; else if(info.channels == 2) - chanConfig = StereoChannels; + mChanConfig = StereoChannels; else { - sf_close(sndFile); - sndFile = NULL; + sf_close(mSndFile); + mSndFile = NULL; fail("Unsupported channel count in "+fname); } - sampleRate = info.samplerate; + mSampleRate = info.samplerate; return; } - mpgFile = mpg123_new(NULL, NULL); - if(mpgFile && mpg123_open(mpgFile, fname.c_str()) == MPG123_OK) + mMpgFile = mpg123_new(NULL, NULL); + if(mMpgFile && mpg123_open(mMpgFile, fname.c_str()) == MPG123_OK) { try { int encoding, channels; long rate; - if(mpg123_getformat(mpgFile, &rate, &channels, &encoding) != MPG123_OK) + if(mpg123_getformat(mMpgFile, &rate, &channels, &encoding) != MPG123_OK) fail("Failed to get audio format"); if(encoding != MPG123_ENC_SIGNED_16) fail("Unsupported encoding in "+fname); if(channels != 1 && channels != 2) fail("Unsupported channel count in "+fname); - chanConfig = ((channels==2)?StereoChannels:MonoChannels); - sampleRate = rate; + mChanConfig = ((channels==2)?StereoChannels:MonoChannels); + mSampleRate = rate; return; } catch(std::exception &e) { - mpg123_close(mpgFile); - mpg123_delete(mpgFile); + mpg123_close(mMpgFile); + mpg123_delete(mMpgFile); + mMpgFile = NULL; throw; } - mpg123_close(mpgFile); + mpg123_close(mMpgFile); } - if(mpgFile) - mpg123_delete(mpgFile); - mpgFile = NULL; + if(mMpgFile) + mpg123_delete(mMpgFile); + mMpgFile = NULL; fail("Unsupported file type: "+fname); } -void MpgSnd_Decoder::Close() +void MpgSnd_Decoder::close() { - if(sndFile) - sf_close(sndFile); - sndFile = NULL; + if(mSndFile) + sf_close(mSndFile); + mSndFile = NULL; - if(mpgFile) + if(mMpgFile) { - mpg123_close(mpgFile); - mpg123_delete(mpgFile); - mpgFile = NULL; + mpg123_close(mMpgFile); + mpg123_delete(mMpgFile); + mMpgFile = NULL; } } -void MpgSnd_Decoder::GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type) +void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { - if(!sndFile && !mpgFile) + if(!mSndFile && !mMpgFile) fail("No open file"); - *samplerate = sampleRate; - *chans = chanConfig; + *samplerate = mSampleRate; + *chans = mChanConfig; *type = Int16Sample; } -size_t MpgSnd_Decoder::Read(char *buffer, size_t bytes) +size_t MpgSnd_Decoder::read(char *buffer, size_t bytes) { size_t got = 0; - if(sndFile) + if(mSndFile) { - got = sf_read_short(sndFile, (short*)buffer, bytes/2)*2; + got = sf_read_short(mSndFile, (short*)buffer, bytes/2)*2; } - else if(mpgFile) + else if(mMpgFile) { int err; - err = mpg123_read(mpgFile, (unsigned char*)buffer, bytes, &got); + err = mpg123_read(mMpgFile, (unsigned char*)buffer, bytes, &got); if(err != MPG123_OK && err != MPG123_DONE) fail("Failed to read from file"); } return got; } -MpgSnd_Decoder::MpgSnd_Decoder() : sndFile(NULL), mpgFile(NULL) +MpgSnd_Decoder::MpgSnd_Decoder() : mSndFile(NULL), mMpgFile(NULL) { static bool initdone = false; if(!initdone) @@ -118,7 +119,7 @@ MpgSnd_Decoder::MpgSnd_Decoder() : sndFile(NULL), mpgFile(NULL) MpgSnd_Decoder::~MpgSnd_Decoder() { - Close(); + close(); } } diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index cd7e468da..4d3c7f4ab 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -13,17 +13,17 @@ namespace MWSound { class MpgSnd_Decoder : public Sound_Decoder { - SNDFILE *sndFile; - mpg123_handle *mpgFile; + SNDFILE *mSndFile; + mpg123_handle *mMpgFile; - ChannelConfig chanConfig; - int sampleRate; + ChannelConfig mChanConfig; + int mSampleRate; - virtual void Open(const std::string &fname); - virtual void Close(); + virtual void open(const std::string &fname); + virtual void close(); - virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type); - virtual size_t Read(char *buffer, size_t bytes); + virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); + virtual size_t read(char *buffer, size_t bytes); MpgSnd_Decoder(); public: diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index f28b09c07..af8569ad7 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -55,12 +55,12 @@ ALuint LoadBuffer(DecoderPtr decoder) Sound_Decoder::SampleType type; ALenum format; - decoder->GetInfo(&srate, &chans, &type); + decoder->getInfo(&srate, &chans, &type); format = getALFormat(chans, type); std::vector data(32768); size_t got, total = 0; - while((got=decoder->Read(&data[total], data.size()-total)) > 0) + while((got=decoder->read(&data[total], data.size()-total)) > 0) { total += got; data.resize(total*2); @@ -139,7 +139,7 @@ OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) Sound_Decoder::ChannelConfig chans; Sound_Decoder::SampleType type; - mDecoder->GetInfo(&srate, &chans, &type); + mDecoder->getInfo(&srate, &chans, &type); mFormat = getALFormat(chans, type); mSampleRate = srate; } @@ -156,7 +156,7 @@ OpenAL_SoundStream::~OpenAL_SoundStream() alDeleteSources(1, &mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); - mDecoder->Close(); + mDecoder->close(); } void OpenAL_SoundStream::Play(float volume, float pitch) @@ -172,7 +172,7 @@ void OpenAL_SoundStream::Play(float volume, float pitch) for(ALuint i = 0;i < sNumBuffers;i++) { size_t got; - got = mDecoder->Read(&data[0], data.size()); + got = mDecoder->read(&data[0], data.size()); alBufferData(mBuffers[i], mFormat, &data[0], got, mSampleRate); } throwALerror(); @@ -208,7 +208,7 @@ bool OpenAL_SoundStream::isPlaying() alSourceUnqueueBuffers(mSource, 1, &bufid); processed--; - got = mDecoder->Read(&data[0], data.size()); + got = mDecoder->read(&data[0], data.size()); if(got > 0) { alBufferData(bufid, mFormat, &data[0], got, mSampleRate); @@ -326,13 +326,13 @@ Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pi throwALerror(); DecoderPtr decoder = mManager.getDecoder(); - decoder->Open(fname); + decoder->open(fname); ALuint src=0, buf=0; try { buf = LoadBuffer(decoder); - decoder->Close(); + decoder->close(); alGenSources(1, &src); throwALerror(); } @@ -375,13 +375,13 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl throwALerror(); DecoderPtr decoder = mManager.getDecoder(); - decoder->Open(fname); + decoder->open(fname); ALuint src=0, buf=0; try { buf = LoadBuffer(decoder); - decoder->Close(); + decoder->close(); alGenSources(1, &src); throwALerror(); } @@ -424,7 +424,7 @@ Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float std::auto_ptr sound; DecoderPtr decoder = mManager.getDecoder(); - decoder->Open(fname); + decoder->open(fname); sound.reset(new OpenAL_SoundStream(decoder)); sound->Play(volume, pitch); diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 4f778c3b3..551cd2411 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -14,11 +14,11 @@ namespace MWSound MonoChannels, StereoChannels }; - virtual void Open(const std::string &fname) = 0; - virtual void Close() = 0; + virtual void open(const std::string &fname) = 0; + virtual void close() = 0; - virtual void GetInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; - virtual size_t Read(char *buffer, size_t bytes) = 0; + virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; + virtual size_t read(char *buffer, size_t bytes) = 0; virtual ~Sound_Decoder() { } From 403e51cef377b1dbc9725c7f9f7efc819babc1f5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 11:56:54 -0700 Subject: [PATCH 050/152] Move the sample type and channel config enums to MWSound and give use appropriate names for the values --- apps/openmw/mwsound/mpgsnd_decoder.cpp | 8 ++++---- apps/openmw/mwsound/openal_output.cpp | 22 +++++++++++----------- apps/openmw/mwsound/sound_decoder.hpp | 19 +++++++++++-------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 6be6566f0..e112d30b1 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -21,9 +21,9 @@ void MpgSnd_Decoder::open(const std::string &fname) if(mSndFile) { if(info.channels == 1) - mChanConfig = MonoChannels; + mChanConfig = ChannelConfig_Mono; else if(info.channels == 2) - mChanConfig = StereoChannels; + mChanConfig = ChannelConfig_Stereo; else { sf_close(mSndFile); @@ -47,7 +47,7 @@ void MpgSnd_Decoder::open(const std::string &fname) fail("Unsupported encoding in "+fname); if(channels != 1 && channels != 2) fail("Unsupported channel count in "+fname); - mChanConfig = ((channels==2)?StereoChannels:MonoChannels); + mChanConfig = ((channels==2)?ChannelConfig_Stereo:ChannelConfig_Mono); mSampleRate = rate; return; } @@ -88,7 +88,7 @@ void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * *samplerate = mSampleRate; *chans = mChanConfig; - *type = Int16Sample; + *type = SampleType_Int16; } size_t MpgSnd_Decoder::read(char *buffer, size_t bytes) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index af8569ad7..cda6c89dd 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -22,22 +22,22 @@ static void throwALerror() } -static ALenum getALFormat(Sound_Decoder::ChannelConfig chans, Sound_Decoder::SampleType type) +static ALenum getALFormat(ChannelConfig chans, SampleType type) { - if(chans == Sound_Decoder::MonoChannels) + if(chans == ChannelConfig_Mono) { - if(type == Sound_Decoder::Int16Sample) + if(type == SampleType_Int16) return AL_FORMAT_MONO16; - else if(type == Sound_Decoder::UInt8Sample) + else if(type == SampleType_UInt8) return AL_FORMAT_MONO8; else fail("Unsupported sample type"); } - else if(chans == Sound_Decoder::StereoChannels) + else if(chans == ChannelConfig_Stereo) { - if(type == Sound_Decoder::Int16Sample) + if(type == SampleType_Int16) return AL_FORMAT_STEREO16; - else if(type == Sound_Decoder::UInt8Sample) + else if(type == SampleType_UInt8) return AL_FORMAT_STEREO8; else fail("Unsupported sample type"); @@ -51,8 +51,8 @@ static ALenum getALFormat(Sound_Decoder::ChannelConfig chans, Sound_Decoder::Sam ALuint LoadBuffer(DecoderPtr decoder) { int srate; - Sound_Decoder::ChannelConfig chans; - Sound_Decoder::SampleType type; + ChannelConfig chans; + SampleType type; ALenum format; decoder->getInfo(&srate, &chans, &type); @@ -136,8 +136,8 @@ OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) try { int srate; - Sound_Decoder::ChannelConfig chans; - Sound_Decoder::SampleType type; + ChannelConfig chans; + SampleType type; mDecoder->getInfo(&srate, &chans, &type); mFormat = getALFormat(chans, type); diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 551cd2411..e25321a90 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -1,19 +1,22 @@ #ifndef GAME_SOUND_SOUND_DECODER_H #define GAME_SOUND_SOUND_DECODER_H +#include + namespace MWSound { + enum SampleType { + SampleType_UInt8, + SampleType_Int16 + }; + enum ChannelConfig { + ChannelConfig_Mono, + ChannelConfig_Stereo + }; + class Sound_Decoder { public: - enum SampleType { - UInt8Sample, - Int16Sample - }; - enum ChannelConfig { - MonoChannels, - StereoChannels - }; virtual void open(const std::string &fname) = 0; virtual void close() = 0; From 1965b5bc796353326746129c0f8fc1a6802192d8 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 12:03:15 -0700 Subject: [PATCH 051/152] Rename some Sound class member functions --- apps/openmw/mwsound/openal_output.cpp | 22 +++++++++++----------- apps/openmw/mwsound/sound.hpp | 8 ++------ apps/openmw/mwsound/soundmanager.cpp | 6 +++--- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index cda6c89dd..4aa76cc63 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -93,10 +93,10 @@ public: OpenAL_SoundStream(DecoderPtr decoder); virtual ~OpenAL_SoundStream(); - void Play(float volume, float pitch); - virtual void Stop(); + void play(float volume, float pitch); + virtual void stop(); virtual bool isPlaying(); - virtual void Update(const float *pos); + virtual void update(const float *pos); }; class OpenAL_Sound : public Sound @@ -108,9 +108,9 @@ public: OpenAL_Sound(ALuint src, ALuint buf); virtual ~OpenAL_Sound(); - virtual void Stop(); + virtual void stop(); virtual bool isPlaying(); - virtual void Update(const float *pos); + virtual void update(const float *pos); }; @@ -159,7 +159,7 @@ OpenAL_SoundStream::~OpenAL_SoundStream() mDecoder->close(); } -void OpenAL_SoundStream::Play(float volume, float pitch) +void OpenAL_SoundStream::play(float volume, float pitch) { std::vector data(sBufferSize); @@ -182,7 +182,7 @@ void OpenAL_SoundStream::Play(float volume, float pitch) throwALerror(); } -void OpenAL_SoundStream::Stop() +void OpenAL_SoundStream::stop() { alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); @@ -234,7 +234,7 @@ bool OpenAL_SoundStream::isPlaying() return true; } -void OpenAL_SoundStream::Update(const float *pos) +void OpenAL_SoundStream::update(const float *pos) { alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); @@ -254,7 +254,7 @@ OpenAL_Sound::~OpenAL_Sound() alGetError(); } -void OpenAL_Sound::Stop() +void OpenAL_Sound::stop() { alSourceStop(mSource); throwALerror(); @@ -270,7 +270,7 @@ bool OpenAL_Sound::isPlaying() return state==AL_PLAYING; } -void OpenAL_Sound::Update(const float *pos) +void OpenAL_Sound::update(const float *pos) { alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); @@ -427,7 +427,7 @@ Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float decoder->open(fname); sound.reset(new OpenAL_SoundStream(decoder)); - sound->Play(volume, pitch); + sound->play(volume, pitch); return sound.release(); } diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 3b4736fd6..84725464b 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -1,17 +1,13 @@ #ifndef GAME_SOUND_SOUND_H #define GAME_SOUND_SOUND_H -#include - -#include "../mwworld/ptr.hpp" - namespace MWSound { class Sound { - virtual void Stop() = 0; + virtual void stop() = 0; virtual bool isPlaying() = 0; - virtual void Update(const float *pos) = 0; + virtual void update(const float *pos) = 0; public: virtual ~Sound() { } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index c496701c7..5d9a37065 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -159,14 +159,14 @@ namespace MWSound void SoundManager::stopMusic() { if(mMusic) - mMusic->Stop(); + mMusic->stop(); setPlaylist(); } void SoundManager::streamMusicFull(const std::string& filename) { if(mMusic) - mMusic->Stop(); + mMusic->stop(); mMusic.reset(mOutput->streamSound(filename, 0.4f, 1.0f)); } @@ -358,7 +358,7 @@ namespace MWSound while(iditer != snditer->second.end()) { const ESM::Position &pos = ptr.getCellRef().pos; - iditer->second->Update(pos.pos); + iditer->second->update(pos.pos); iditer++; } } From b938fd7b368470ee780378ff37414a16d79ae038 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 12:19:54 -0700 Subject: [PATCH 052/152] Make the sound output init return void --- apps/openmw/mwsound/openal_output.cpp | 24 ++++++------------------ apps/openmw/mwsound/openal_output.hpp | 2 +- apps/openmw/mwsound/sound_output.hpp | 2 +- apps/openmw/mwsound/soundmanager.cpp | 9 +++++++-- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 4aa76cc63..b1359d5f4 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -279,34 +279,22 @@ void OpenAL_Sound::update(const float *pos) } -bool OpenAL_Output::init(const std::string &devname) +void OpenAL_Output::init(const std::string &devname) { - if(mContext) - fail("Device already initialized"); + if(mDevice || mContext) + fail("Device already open"); mDevice = alcOpenDevice(devname.c_str()); if(!mDevice) - { - std::cout << "Failed to open \""<init()) + try + { + mOutput.reset(new DEFAULT_OUTPUT(*this)); + mOutput->init(); + } + catch(std::exception &e) { + std::cout <<"Sound init failed: "< Date: Sun, 18 Mar 2012 20:44:56 +0100 Subject: [PATCH 053/152] removed preprocessor constants --- apps/openmw/mwrender/localmap.cpp | 73 +++++++++++++------------------ apps/openmw/mwrender/localmap.hpp | 12 +++++ 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index d4a25750f..e08559d26 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -10,18 +10,6 @@ using namespace MWRender; using namespace Ogre; -#define MAP_RESOLUTION 1024 // 1024*1024 pixels for a SIZE*SIZE area in world units - -// warning: don't set this too high! dynamic textures are a bottleneck -#define FOGOFWAR_RESOLUTION 32 - -// how many frames to skip before rendering the fog of war. -// example: at 60 fps, a value of 2 would mean to render it at 20 fps. -#define FOGOFWAR_SKIP 2 - -// size of a map segment (for exterior regions, this equals 1 cell) -#define SIZE 8192.f - LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWWorld::Environment* env) { mRendering = rend; @@ -88,8 +76,8 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) Vector2 length = max-min; // divide into segments - const int segsX = std::ceil( length.x / SIZE ); - const int segsY = std::ceil( length.y / SIZE ); + const int segsX = std::ceil( length.x / sSize ); + const int segsY = std::ceil( length.y / sSize ); for (int x=0; xcell->data.gridX; int y = cell->cell->data.gridY; - render((x+0.5)*SIZE, (-y-0.5)*SIZE, -10000, 10000, SIZE, SIZE, name); + render((x+0.5)*sSize, (-y-0.5)*sSize, -10000, 10000, sSize, sSize, name); } void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, @@ -133,8 +121,8 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, Vector2 center(bounds.getCenter().x, bounds.getCenter().z); // divide into segments - const int segsX = std::ceil( length.x / SIZE ); - const int segsY = std::ceil( length.y / SIZE ); + const int segsX = std::ceil( length.x / sSize ); + const int segsY = std::ceil( length.y / sSize ); mInteriorName = cell->cell->name; @@ -142,10 +130,10 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, { for (int y=0; ycell->name + "_" + coordStr(x,y)); } } @@ -187,7 +175,7 @@ void LocalMap::render(const float x, const float y, texture, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, - xw*MAP_RESOLUTION/SIZE, yw*MAP_RESOLUTION/SIZE, + xw*sMapResolution/sSize, yw*sMapResolution/sSize, 0, PF_R8G8B8, TU_RENDERTARGET); @@ -207,22 +195,22 @@ void LocalMap::render(const float x, const float y, texture + "_fog", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, - xw*FOGOFWAR_RESOLUTION/SIZE, yw*FOGOFWAR_RESOLUTION/SIZE, + xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize, 0, PF_A8R8G8B8, TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); // create a buffer to use for dynamic operations - uint32* buffer = new uint32[FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION]; + uint32* buffer = new uint32[sFogOfWarResolution*sFogOfWarResolution]; // initialize to (0, 0, 0, 1) uint32* pointer = buffer; - for (int p=0; pgetBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION*4); + memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, sFogOfWarResolution*sFogOfWarResolution*4); tex2->getBuffer()->unlock(); mBuffers[texture] = buffer; @@ -239,19 +227,20 @@ void LocalMap::render(const float x, const float y, void LocalMap::setPlayerPosition (const Ogre::Vector3& position) { - #if FOGOFWAR_SKIP != 0 - static int count=0; - if (++count % FOGOFWAR_SKIP != 0) - return; - #endif + if (sFogOfWarSkip != 0) + { + static int count=0; + if (++count % sFogOfWarSkip != 0) + return; + } // retrieve the x,y grid coordinates the player is in int x,y; Vector2 pos(position.x, position.z); if (!mInterior) { - x = std::ceil(pos.x / SIZE)-1; - y = std::ceil(-pos.y / SIZE)-1; + x = std::ceil(pos.x / sSize)-1; + y = std::ceil(-pos.y / sSize)-1; mCellX = x; mCellY = y; } @@ -260,8 +249,8 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); min *= 1.3; - x = std::ceil((pos.x - min.x)/SIZE)-1; - y = std::ceil((pos.y - min.y)/SIZE)-1; + x = std::ceil((pos.x - min.x)/sSize)-1; + y = std::ceil((pos.y - min.y)/sSize)-1; mEnvironment->mWindowManager->setInteriorMapTexture(x,y); } @@ -271,8 +260,8 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) std::string texName; if (!mInterior) { - u = std::abs((pos.x - (SIZE*x))/SIZE); - v = 1-std::abs((pos.y + (SIZE*y))/SIZE); + u = std::abs((pos.x - (sSize*x))/sSize); + v = 1-std::abs((pos.y + (sSize*y))/sSize); texName = "Cell_"+coordStr(x,y); } else @@ -280,14 +269,14 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z); min *= 1.3; - u = (pos.x - min.x - SIZE*x)/SIZE; - v = (pos.y - min.y - SIZE*y)/SIZE; + u = (pos.x - min.x - sSize*x)/sSize; + v = (pos.y - min.y - sSize*y)/sSize; texName = mInteriorName + "_" + coordStr(x,y); } // explore radius (squared) - const float sqrExploreRadius = 0.01 * FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION; + const float sqrExploreRadius = 0.01 * sFogOfWarResolution*sFogOfWarResolution; // get the appropriate fog of war texture TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); @@ -297,11 +286,11 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) if (mBuffers.find(texName) == mBuffers.end()) return; uint32* buffer = mBuffers[texName]; uint32* pointer = buffer; - for (int texV = 0; texV> 24); alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); @@ -313,7 +302,7 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) } // copy to the texture - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, FOGOFWAR_RESOLUTION*FOGOFWAR_RESOLUTION*4); + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, sFogOfWarResolution*sFogOfWarResolution*4); tex->getBuffer()->unlock(); } } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index fd5d4770b..13e01b7f4 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -59,6 +59,18 @@ namespace MWRender OEngine::Render::OgreRenderer* mRendering; MWWorld::Environment* mEnvironment; + // 1024*1024 pixels for a cell + static const int sMapResolution = 1024; + + // the dynamic texture is a bottleneck, so don't set this too high + static const int sFogOfWarResolution = 32; + + // frames to skip before rendering fog of war + static const int sFogOfWarSkip = 2; + + // size of a map segment (for exteriors, 1 cell) + static const int sSize = 8192; + Ogre::Camera* mCellCamera; void render(const float x, const float y, From 7194114669124df31704f513c8cfa022c7d1b88e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sun, 18 Mar 2012 14:27:22 -0700 Subject: [PATCH 054/152] Use a background thread to keep OpenAL streams fed Maybe this could be moved to the SoundManager instead of in OpenAL, but it's good enough for now. --- apps/openmw/mwsound/openal_output.cpp | 93 +++++++++++++++++++++++---- apps/openmw/mwsound/openal_output.hpp | 4 ++ 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index b1359d5f4..9d0cd7d56 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -76,9 +77,7 @@ ALuint LoadBuffer(DecoderPtr decoder) class OpenAL_SoundStream : public Sound { - // This should be something sane, like 4, but currently cell loads tend to - // cause the stream to underrun - static const ALuint sNumBuffers = 150; + static const ALuint sNumBuffers = 4; static const ALuint sBufferSize = 32768; ALuint mSource; @@ -89,14 +88,18 @@ class OpenAL_SoundStream : public Sound DecoderPtr mDecoder; + volatile bool mIsFinished; + public: OpenAL_SoundStream(DecoderPtr decoder); virtual ~OpenAL_SoundStream(); - void play(float volume, float pitch); virtual void stop(); virtual bool isPlaying(); virtual void update(const float *pos); + + void play(float volume, float pitch); + bool process(); }; class OpenAL_Sound : public Sound @@ -114,8 +117,52 @@ public: }; +struct StreamThread { + typedef std::vector StreamVec; + static StreamVec sStreams; + static boost::mutex sMutex; + + void operator()() + { + while(1) + { + sMutex.lock(); + StreamVec::iterator iter = sStreams.begin(); + while(iter != sStreams.end()) + { + if((*iter)->process() == false) + iter = sStreams.erase(iter); + else + iter++; + } + sMutex.unlock(); + boost::this_thread::sleep(boost::posix_time::milliseconds(20)); + } + } + + static void add(OpenAL_SoundStream *stream) + { + sMutex.lock(); + if(std::find(sStreams.begin(), sStreams.end(), stream) == sStreams.end()) + sStreams.push_back(stream); + sMutex.unlock(); + } + + static void remove(OpenAL_SoundStream *stream) + { + sMutex.lock(); + StreamVec::iterator iter = std::find(sStreams.begin(), sStreams.end(), stream); + if(iter != sStreams.end()) + sStreams.erase(iter); + sMutex.unlock(); + } +}; +StreamThread::StreamVec StreamThread::sStreams; +boost::mutex StreamThread::sMutex; + + OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) - : mDecoder(decoder) + : mDecoder(decoder), mIsFinished(true) { throwALerror(); @@ -153,9 +200,12 @@ OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) } OpenAL_SoundStream::~OpenAL_SoundStream() { + StreamThread::remove(this); + alDeleteSources(1, &mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); + mDecoder->close(); } @@ -180,17 +230,36 @@ void OpenAL_SoundStream::play(float volume, float pitch) alSourceQueueBuffers(mSource, sNumBuffers, mBuffers); alSourcePlay(mSource); throwALerror(); + + StreamThread::add(this); + mIsFinished = false; } void OpenAL_SoundStream::stop() { + StreamThread::remove(this); + alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); // FIXME: Rewind decoder + mIsFinished = true; } bool OpenAL_SoundStream::isPlaying() +{ + return !mIsFinished; +} + +void OpenAL_SoundStream::update(const float *pos) +{ + alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + +bool OpenAL_SoundStream::process() { ALint processed, state; @@ -225,7 +294,10 @@ bool OpenAL_SoundStream::isPlaying() alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); throwALerror(); if(queued == 0) + { + mIsFinished = true; return false; + } alSourcePlay(mSource); throwALerror(); @@ -234,14 +306,6 @@ bool OpenAL_SoundStream::isPlaying() return true; } -void OpenAL_SoundStream::update(const float *pos) -{ - alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); - alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - throwALerror(); -} - OpenAL_Sound::OpenAL_Sound(ALuint src, ALuint buf) : mSource(src), mBuffer(buf) @@ -435,12 +499,13 @@ void OpenAL_Output::updateListener(const float *pos, const float *atdir, const f OpenAL_Output::OpenAL_Output(SoundManager &mgr) - : Sound_Output(mgr), mDevice(0), mContext(0) + : Sound_Output(mgr), mDevice(0), mContext(0), mStreamThread(StreamThread()) { } OpenAL_Output::~OpenAL_Output() { + mStreamThread.interrupt(); deinit(); } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 54c3268d7..9a6d7f9d2 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -3,6 +3,8 @@ #include +#include + #include "alc.h" #include "al.h" @@ -19,6 +21,8 @@ namespace MWSound ALCdevice *mDevice; ALCcontext *mContext; + boost::thread mStreamThread; + virtual void init(const std::string &devname=""); virtual void deinit(); From 6a256d399392131293a1ce413b295ddcaca4f31d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 00:38:56 -0700 Subject: [PATCH 055/152] Make sure the OpenAL stream list is clear before shutting down --- apps/openmw/mwsound/openal_output.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 9d0cd7d56..556c1bb64 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -156,6 +156,13 @@ struct StreamThread { sStreams.erase(iter); sMutex.unlock(); } + + static void removeAll() + { + sMutex.lock(); + sStreams.clear(); + sMutex.unlock(); + } }; StreamThread::StreamVec StreamThread::sStreams; boost::mutex StreamThread::sMutex; @@ -363,6 +370,8 @@ void OpenAL_Output::init(const std::string &devname) void OpenAL_Output::deinit() { + StreamThread::removeAll(); + alcMakeContextCurrent(0); if(mContext) alcDestroyContext(mContext); From e234b901731ef88df22fff5ed710757e2ea73377 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 00:49:52 -0700 Subject: [PATCH 056/152] Use a loop to find the OpenAL format from the decoder format --- apps/openmw/mwsound/openal_output.cpp | 34 +++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 556c1bb64..71cf33b75 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -25,26 +25,24 @@ static void throwALerror() static ALenum getALFormat(ChannelConfig chans, SampleType type) { - if(chans == ChannelConfig_Mono) - { - if(type == SampleType_Int16) - return AL_FORMAT_MONO16; - else if(type == SampleType_UInt8) - return AL_FORMAT_MONO8; - else - fail("Unsupported sample type"); - } - else if(chans == ChannelConfig_Stereo) + static const struct { + ALenum format; + ChannelConfig chans; + SampleType type; + } fmtlist[] = { + { AL_FORMAT_MONO16, ChannelConfig_Mono, SampleType_Int16 }, + { AL_FORMAT_MONO8, ChannelConfig_Mono, SampleType_UInt8 }, + { AL_FORMAT_STEREO16, ChannelConfig_Stereo, SampleType_Int16 }, + { AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 }, + }; + static const size_t fmtlistsize = sizeof(fmtlist)/sizeof(fmtlist[0]); + + for(size_t i = 0;i < fmtlistsize;i++) { - if(type == SampleType_Int16) - return AL_FORMAT_STEREO16; - else if(type == SampleType_UInt8) - return AL_FORMAT_STEREO8; - else - fail("Unsupported sample type"); + if(fmtlist[i].chans == chans && fmtlist[i].type == type) + return fmtlist[i].format; } - else - fail("Unsupported channel config"); + fail("Unsupported sound format"); return AL_NONE; } From 2c27827e4fbb62b4351ca9bef27c2300d3e42baf Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 01:33:33 -0700 Subject: [PATCH 057/152] Add some comment markers to the OpenAL sound classes --- apps/openmw/mwsound/openal_output.cpp | 101 ++++++++++++++------------ 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 71cf33b75..4b07e204f 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -46,33 +46,9 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) return AL_NONE; } - -ALuint LoadBuffer(DecoderPtr decoder) -{ - int srate; - ChannelConfig chans; - SampleType type; - ALenum format; - - decoder->getInfo(&srate, &chans, &type); - format = getALFormat(chans, type); - - std::vector data(32768); - size_t got, total = 0; - while((got=decoder->read(&data[total], data.size()-total)) > 0) - { - total += got; - data.resize(total*2); - } - data.resize(total); - - ALuint buf; - alGenBuffers(1, &buf); - alBufferData(buf, format, &data[0], total, srate); - return buf; -} - - +// +// A streaming OpenAL sound. +// class OpenAL_SoundStream : public Sound { static const ALuint sNumBuffers = 4; @@ -100,26 +76,15 @@ public: bool process(); }; -class OpenAL_Sound : public Sound -{ -public: - ALuint mSource; - ALuint mBuffer; - - OpenAL_Sound(ALuint src, ALuint buf); - virtual ~OpenAL_Sound(); - - virtual void stop(); - virtual bool isPlaying(); - virtual void update(const float *pos); -}; - - +// +// A background streaming thread (keeps active streams processed) +// struct StreamThread { typedef std::vector StreamVec; static StreamVec sStreams; static boost::mutex sMutex; + // boost::thread entry point void operator()() { while(1) @@ -243,12 +208,12 @@ void OpenAL_SoundStream::play(float volume, float pitch) void OpenAL_SoundStream::stop() { StreamThread::remove(this); + mIsFinished = true; alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); // FIXME: Rewind decoder - mIsFinished = true; } bool OpenAL_SoundStream::isPlaying() @@ -311,6 +276,49 @@ bool OpenAL_SoundStream::process() return true; } +// +// A regular OpenAL sound +// +class OpenAL_Sound : public Sound +{ + ALuint mSource; + ALuint mBuffer; + +public: + OpenAL_Sound(ALuint src, ALuint buf); + virtual ~OpenAL_Sound(); + + virtual void stop(); + virtual bool isPlaying(); + virtual void update(const float *pos); + + static ALuint LoadBuffer(DecoderPtr decoder); +}; + +ALuint OpenAL_Sound::LoadBuffer(DecoderPtr decoder) +{ + int srate; + ChannelConfig chans; + SampleType type; + ALenum format; + + decoder->getInfo(&srate, &chans, &type); + format = getALFormat(chans, type); + + std::vector data(32768); + size_t got, total = 0; + while((got=decoder->read(&data[total], data.size()-total)) > 0) + { + total += got; + data.resize(total*2); + } + data.resize(total); + + ALuint buf; + alGenBuffers(1, &buf); + alBufferData(buf, format, &data[0], total, srate); + return buf; +} OpenAL_Sound::OpenAL_Sound(ALuint src, ALuint buf) : mSource(src), mBuffer(buf) @@ -348,6 +356,9 @@ void OpenAL_Sound::update(const float *pos) } +// +// An OpenAL output device +// void OpenAL_Output::init(const std::string &devname) { if(mDevice || mContext) @@ -390,7 +401,7 @@ Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pi ALuint src=0, buf=0; try { - buf = LoadBuffer(decoder); + buf = OpenAL_Sound::LoadBuffer(decoder); decoder->close(); alGenSources(1, &src); throwALerror(); @@ -439,7 +450,7 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl ALuint src=0, buf=0; try { - buf = LoadBuffer(decoder); + buf = OpenAL_Sound::LoadBuffer(decoder); decoder->close(); alGenSources(1, &src); throwALerror(); From 4698e8c0a23f04b7d32920d2089d96cb0c4a6578 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 02:15:08 -0700 Subject: [PATCH 058/152] Make the sound stream thread object per-device --- apps/openmw/mwsound/openal_output.cpp | 83 ++++++++++++++++----------- apps/openmw/mwsound/openal_output.hpp | 9 ++- 2 files changed, 52 insertions(+), 40 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 4b07e204f..2f993aa01 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "openal_output.hpp" #include "sound_decoder.hpp" #include "sound.hpp" @@ -54,6 +56,8 @@ class OpenAL_SoundStream : public Sound static const ALuint sNumBuffers = 4; static const ALuint sBufferSize = 32768; + OpenAL_Output &mOutput; + ALuint mSource; ALuint mBuffers[sNumBuffers]; @@ -65,7 +69,7 @@ class OpenAL_SoundStream : public Sound volatile bool mIsFinished; public: - OpenAL_SoundStream(DecoderPtr decoder); + OpenAL_SoundStream(OpenAL_Output &output, DecoderPtr decoder); virtual ~OpenAL_SoundStream(); virtual void stop(); @@ -79,60 +83,68 @@ public: // // A background streaming thread (keeps active streams processed) // -struct StreamThread { +struct OpenAL_Output::StreamThread { typedef std::vector StreamVec; - static StreamVec sStreams; - static boost::mutex sMutex; + StreamVec mStreams; + boost::mutex mMutex; + boost::thread mThread; + + StreamThread() + : mThread(boost::ref(*this)) + { + } + ~StreamThread() + { + mThread.interrupt(); + } // boost::thread entry point void operator()() { while(1) { - sMutex.lock(); - StreamVec::iterator iter = sStreams.begin(); - while(iter != sStreams.end()) + mMutex.lock(); + StreamVec::iterator iter = mStreams.begin(); + while(iter != mStreams.end()) { if((*iter)->process() == false) - iter = sStreams.erase(iter); + iter = mStreams.erase(iter); else iter++; } - sMutex.unlock(); + mMutex.unlock(); boost::this_thread::sleep(boost::posix_time::milliseconds(20)); } } - static void add(OpenAL_SoundStream *stream) + void add(OpenAL_SoundStream *stream) { - sMutex.lock(); - if(std::find(sStreams.begin(), sStreams.end(), stream) == sStreams.end()) - sStreams.push_back(stream); - sMutex.unlock(); + mMutex.lock(); + if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end()) + mStreams.push_back(stream); + mMutex.unlock(); } - static void remove(OpenAL_SoundStream *stream) + void remove(OpenAL_SoundStream *stream) { - sMutex.lock(); - StreamVec::iterator iter = std::find(sStreams.begin(), sStreams.end(), stream); - if(iter != sStreams.end()) - sStreams.erase(iter); - sMutex.unlock(); + mMutex.lock(); + StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream); + if(iter != mStreams.end()) + mStreams.erase(iter); + mMutex.unlock(); } - static void removeAll() + void removeAll() { - sMutex.lock(); - sStreams.clear(); - sMutex.unlock(); + mMutex.lock(); + mStreams.clear(); + mMutex.unlock(); } }; -StreamThread::StreamVec StreamThread::sStreams; -boost::mutex StreamThread::sMutex; -OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) - : mDecoder(decoder), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, DecoderPtr decoder) + : mOutput(output), mDecoder(decoder), mIsFinished(true) { throwALerror(); @@ -170,7 +182,7 @@ OpenAL_SoundStream::OpenAL_SoundStream(DecoderPtr decoder) } OpenAL_SoundStream::~OpenAL_SoundStream() { - StreamThread::remove(this); + mOutput.mStreamThread->remove(this); alDeleteSources(1, &mSource); alDeleteBuffers(sNumBuffers, mBuffers); @@ -201,13 +213,13 @@ void OpenAL_SoundStream::play(float volume, float pitch) alSourcePlay(mSource); throwALerror(); - StreamThread::add(this); + mOutput.mStreamThread->add(this); mIsFinished = false; } void OpenAL_SoundStream::stop() { - StreamThread::remove(this); + mOutput.mStreamThread->remove(this); mIsFinished = true; alSourceStop(mSource); @@ -379,7 +391,8 @@ void OpenAL_Output::init(const std::string &devname) void OpenAL_Output::deinit() { - StreamThread::removeAll(); + if(mStreamThread.get() != 0) + mStreamThread->removeAll(); alcMakeContextCurrent(0); if(mContext) @@ -496,7 +509,7 @@ Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float DecoderPtr decoder = mManager.getDecoder(); decoder->open(fname); - sound.reset(new OpenAL_SoundStream(decoder)); + sound.reset(new OpenAL_SoundStream(*this, decoder)); sound->play(volume, pitch); return sound.release(); @@ -517,13 +530,13 @@ void OpenAL_Output::updateListener(const float *pos, const float *atdir, const f OpenAL_Output::OpenAL_Output(SoundManager &mgr) - : Sound_Output(mgr), mDevice(0), mContext(0), mStreamThread(StreamThread()) + : Sound_Output(mgr), mDevice(0), mContext(0), mStreamThread(new StreamThread) { } OpenAL_Output::~OpenAL_Output() { - mStreamThread.interrupt(); + mStreamThread.reset(); deinit(); } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 9a6d7f9d2..cd6146e39 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -3,8 +3,6 @@ #include -#include - #include "alc.h" #include "al.h" @@ -13,7 +11,6 @@ namespace MWSound { class SoundManager; - class Sound_Decoder; class Sound; class OpenAL_Output : public Sound_Output @@ -21,8 +18,6 @@ namespace MWSound ALCdevice *mDevice; ALCcontext *mContext; - boost::thread mStreamThread; - virtual void init(const std::string &devname=""); virtual void deinit(); @@ -37,6 +32,10 @@ namespace MWSound OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); + class StreamThread; + std::auto_ptr mStreamThread; + + friend class OpenAL_SoundStream; friend class SoundManager; }; #ifndef DEFAULT_OUTPUT From 4a0b5b791865dc0b8fb2f666b7868684311ef4ff Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 02:19:13 -0700 Subject: [PATCH 059/152] Increase the sound stream thread sleep time to 50ms --- apps/openmw/mwsound/openal_output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 2f993aa01..04ae47c72 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -113,7 +113,7 @@ struct OpenAL_Output::StreamThread { iter++; } mMutex.unlock(); - boost::this_thread::sleep(boost::posix_time::milliseconds(20)); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); } } From dc6354b2f9edd6880cf460e1365393ec538ca732 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 02:31:40 -0700 Subject: [PATCH 060/152] Add functions to get string names for sample types and channel configs --- apps/openmw/mwsound/openal_output.cpp | 2 +- apps/openmw/mwsound/sound_decoder.hpp | 3 +++ apps/openmw/mwsound/soundmanager.cpp | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 04ae47c72..81b874dbf 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -44,7 +44,7 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) if(fmtlist[i].chans == chans && fmtlist[i].type == type) return fmtlist[i].format; } - fail("Unsupported sound format"); + fail(std::string("Unsupported sound format (")+getChannelConfigName(chans)+", "+getSampleTypeName(type)+")"); return AL_NONE; } diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index e25321a90..957681e93 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -9,10 +9,13 @@ namespace MWSound SampleType_UInt8, SampleType_Int16 }; + const char *getSampleTypeName(SampleType type); + enum ChannelConfig { ChannelConfig_Mono, ChannelConfig_Stereo }; + const char *getChannelConfigName(ChannelConfig config); class Sound_Decoder { diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 447c860ff..a263fb1ac 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -464,4 +464,25 @@ namespace MWSound updateRegionSound(duration); } + + + const char *getSampleTypeName(SampleType type) + { + switch(type) + { + case SampleType_UInt8: return "U8"; + case SampleType_Int16: return "S16"; + } + return "(unknown sample type)"; + } + + const char *getChannelConfigName(ChannelConfig config) + { + switch(config) + { + case ChannelConfig_Mono: return "Mono"; + case ChannelConfig_Stereo: return "Stereo"; + } + return "(unknown channel config)"; + } } From 8f9d4ff841af39f90426b03e9dc738e30cf17b09 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 05:29:04 -0700 Subject: [PATCH 061/152] Use 6 125ms buffers for OpenAL streams --- apps/openmw/mwsound/openal_output.cpp | 12 ++++++++---- apps/openmw/mwsound/sound_decoder.hpp | 3 +++ apps/openmw/mwsound/soundmanager.cpp | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 81b874dbf..3e07c3f64 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -53,8 +53,8 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) // class OpenAL_SoundStream : public Sound { - static const ALuint sNumBuffers = 4; - static const ALuint sBufferSize = 32768; + static const ALuint sNumBuffers = 6; + static const ALfloat sBufferLength = 0.125f; OpenAL_Output &mOutput; @@ -63,6 +63,7 @@ class OpenAL_SoundStream : public Sound ALenum mFormat; ALsizei mSampleRate; + ALuint mBufferSize; DecoderPtr mDecoder; @@ -171,6 +172,9 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, DecoderPtr decoder mDecoder->getInfo(&srate, &chans, &type); mFormat = getALFormat(chans, type); mSampleRate = srate; + + mBufferSize = static_cast(sBufferLength*srate); + mBufferSize = framesToBytes(mBufferSize, chans, type); } catch(std::exception &e) { @@ -193,7 +197,7 @@ OpenAL_SoundStream::~OpenAL_SoundStream() void OpenAL_SoundStream::play(float volume, float pitch) { - std::vector data(sBufferSize); + std::vector data(mBufferSize); alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); @@ -251,7 +255,7 @@ bool OpenAL_SoundStream::process() if(processed > 0) { - std::vector data(sBufferSize); + std::vector data(mBufferSize); do { ALuint bufid; size_t got; diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 957681e93..2b16eaeee 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -17,6 +17,9 @@ namespace MWSound }; const char *getChannelConfigName(ChannelConfig config); + size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type); + size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type); + class Sound_Decoder { public: diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index a263fb1ac..d86c5c2f5 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -485,4 +485,24 @@ namespace MWSound } return "(unknown channel config)"; } + + size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type) + { + switch(config) + { + case ChannelConfig_Mono: frames *= 1; break; + case ChannelConfig_Stereo: frames *= 2; break; + } + switch(type) + { + case SampleType_UInt8: frames *= 1; break; + case SampleType_Int16: frames *= 2; break; + } + return frames; + } + + size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type) + { + return bytes / framesToBytes(1, config, type); + } } From ae8218bf03f5a6b7799c358f58de351a5ed7f371 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 07:11:01 -0700 Subject: [PATCH 062/152] Allocate OpenAL sources when opening the device This allows sources to be more efficiently retrieved and returned --- apps/openmw/mwsound/openal_output.cpp | 165 ++++++++++++++++++-------- apps/openmw/mwsound/openal_output.hpp | 5 + 2 files changed, 119 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 3e07c3f64..62a7076e9 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -17,6 +17,13 @@ namespace MWSound static void fail(const std::string &msg) { throw std::runtime_error("OpenAL exception: " + msg); } +static void throwALCerror(ALCdevice *device) +{ + ALCenum err = alcGetError(device); + if(err != ALC_NO_ERROR) + fail(alcGetString(device, err)); +} + static void throwALerror() { ALenum err = alGetError(); @@ -70,14 +77,14 @@ class OpenAL_SoundStream : public Sound volatile bool mIsFinished; public: - OpenAL_SoundStream(OpenAL_Output &output, DecoderPtr decoder); + OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); virtual ~OpenAL_SoundStream(); virtual void stop(); virtual bool isPlaying(); virtual void update(const float *pos); - void play(float volume, float pitch); + void play(); bool process(); }; @@ -144,25 +151,13 @@ struct OpenAL_Output::StreamThread { }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, DecoderPtr decoder) - : mOutput(output), mDecoder(decoder), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder) + : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true) { throwALerror(); - alGenSources(1, &mSource); + alGenBuffers(sNumBuffers, mBuffers); throwALerror(); - try - { - alGenBuffers(sNumBuffers, mBuffers); - throwALerror(); - } - catch(std::exception &e) - { - alDeleteSources(1, &mSource); - alGetError(); - throw; - } - try { int srate; @@ -178,7 +173,7 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, DecoderPtr decoder } catch(std::exception &e) { - alDeleteSources(1, &mSource); + mOutput.mFreeSources.push_back(mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); throw; @@ -188,21 +183,22 @@ OpenAL_SoundStream::~OpenAL_SoundStream() { mOutput.mStreamThread->remove(this); - alDeleteSources(1, &mSource); + alSourceStop(mSource); + alSourcei(mSource, AL_BUFFER, 0); + + mOutput.mFreeSources.push_back(mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); mDecoder->close(); } -void OpenAL_SoundStream::play(float volume, float pitch) +void OpenAL_SoundStream::play() { std::vector data(mBufferSize); alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); - alSourcef(mSource, AL_GAIN, volume); - alSourcef(mSource, AL_PITCH, pitch); throwALerror(); for(ALuint i = 0;i < sNumBuffers;i++) @@ -297,11 +293,12 @@ bool OpenAL_SoundStream::process() // class OpenAL_Sound : public Sound { + OpenAL_Output &mOutput; + ALuint mSource; ALuint mBuffer; - public: - OpenAL_Sound(ALuint src, ALuint buf); + OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf); virtual ~OpenAL_Sound(); virtual void stop(); @@ -330,19 +327,22 @@ ALuint OpenAL_Sound::LoadBuffer(DecoderPtr decoder) } data.resize(total); - ALuint buf; + ALuint buf=0; alGenBuffers(1, &buf); alBufferData(buf, format, &data[0], total, srate); return buf; } -OpenAL_Sound::OpenAL_Sound(ALuint src, ALuint buf) - : mSource(src), mBuffer(buf) +OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) + : mOutput(output), mSource(src), mBuffer(buf) { } OpenAL_Sound::~OpenAL_Sound() { - alDeleteSources(1, &mSource); + alSourceStop(mSource); + alSourcei(mSource, AL_BUFFER, 0); + + mOutput.mFreeSources.push_back(mSource); alDeleteBuffers(1, &mBuffer); alGetError(); } @@ -391,13 +391,37 @@ void OpenAL_Output::init(const std::string &devname) alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); throwALerror(); + + ALCint maxmono, maxstereo; + alcGetIntegerv(mDevice, ALC_MONO_SOURCES, 1, &maxmono); + alcGetIntegerv(mDevice, ALC_STEREO_SOURCES, 1, &maxstereo); + throwALCerror(mDevice); + + mFreeSources.resize(std::min(maxmono+maxstereo, 256)); + for(size_t i = 0;i < mFreeSources.size();i++) + { + ALuint src; + alGenSources(1, &src); + if(alGetError() != AL_NO_ERROR) + { + mFreeSources.resize(i); + break; + } + mFreeSources[i] = src; + } + if(mFreeSources.size() == 0) + fail("Could not allocate any sources"); } void OpenAL_Output::deinit() { - if(mStreamThread.get() != 0) - mStreamThread->removeAll(); + mStreamThread->removeAll(); + if(!mFreeSources.empty()) + { + alDeleteSources(mFreeSources.size(), mFreeSources.data()); + mFreeSources.clear(); + } alcMakeContextCurrent(0); if(mContext) alcDestroyContext(mContext); @@ -412,28 +436,33 @@ Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pi { throwALerror(); - DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); - + std::auto_ptr sound; ALuint src=0, buf=0; + + if(mFreeSources.empty()) + fail("No free sources"); + src = mFreeSources.back(); + mFreeSources.pop_back(); + try { + DecoderPtr decoder = mManager.getDecoder(); + decoder->open(fname); buf = OpenAL_Sound::LoadBuffer(decoder); - decoder->close(); - alGenSources(1, &src); throwALerror(); + decoder->close(); + + sound.reset(new OpenAL_Sound(*this, src, buf)); } catch(std::exception &e) { - if(alIsSource(src)) - alDeleteSources(1, &src); + mFreeSources.push_back(src); if(alIsBuffer(buf)) alDeleteBuffers(1, &buf); alGetError(); throw; } - std::auto_ptr sound(new OpenAL_Sound(src, buf)); alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -461,28 +490,33 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl { throwALerror(); - DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); - + std::auto_ptr sound; ALuint src=0, buf=0; + + if(mFreeSources.empty()) + fail("No free sources"); + src = mFreeSources.back(); + mFreeSources.pop_back(); + try { + DecoderPtr decoder = mManager.getDecoder(); + decoder->open(fname); buf = OpenAL_Sound::LoadBuffer(decoder); - decoder->close(); - alGenSources(1, &src); throwALerror(); + decoder->close(); + + sound.reset(new OpenAL_Sound(*this, src, buf)); } catch(std::exception &e) { - if(alIsSource(src)) - alDeleteSources(1, &src); + mFreeSources.push_back(src); if(alIsBuffer(buf)) alDeleteBuffers(1, &buf); alGetError(); throw; } - std::auto_ptr sound(new OpenAL_Sound(src, buf)); alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -508,14 +542,44 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) { + throwALerror(); + std::auto_ptr sound; + ALuint src; + + if(mFreeSources.empty()) + fail("No free sources"); + src = mFreeSources.back(); + mFreeSources.pop_back(); + + try + { + DecoderPtr decoder = mManager.getDecoder(); + decoder->open(fname); + sound.reset(new OpenAL_SoundStream(*this, src, decoder)); + } + catch(std::exception &e) + { + mFreeSources.push_back(src); + throw; + } - DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); + alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f); + alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - sound.reset(new OpenAL_SoundStream(*this, decoder)); - sound->play(volume, pitch); + alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f); + alSourcef(src, AL_MAX_DISTANCE, 1000.0f); + alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); + + alSourcef(src, AL_GAIN, volume); + alSourcef(src, AL_PITCH, pitch); + + alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(src, AL_LOOPING, AL_FALSE); + throwALerror(); + sound->play(); return sound.release(); } @@ -540,7 +604,6 @@ OpenAL_Output::OpenAL_Output(SoundManager &mgr) OpenAL_Output::~OpenAL_Output() { - mStreamThread.reset(); deinit(); } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index cd6146e39..80c6db851 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -2,6 +2,7 @@ #define GAME_SOUND_OPENAL_OUTPUT_H #include +#include #include "alc.h" #include "al.h" @@ -18,6 +19,9 @@ namespace MWSound ALCdevice *mDevice; ALCcontext *mContext; + typedef std::vector IDVec; + IDVec mFreeSources; + virtual void init(const std::string &devname=""); virtual void deinit(); @@ -35,6 +39,7 @@ namespace MWSound class StreamThread; std::auto_ptr mStreamThread; + friend class OpenAL_Sound; friend class OpenAL_SoundStream; friend class SoundManager; }; From 4f69972a9c7a10aea930567d3431eea6381049b6 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 07:28:03 -0700 Subject: [PATCH 063/152] Add a method to stream a sound in 3D --- apps/openmw/mwsound/openal_output.cpp | 44 +++++++++++++++++++++++++++ apps/openmw/mwsound/openal_output.hpp | 2 ++ apps/openmw/mwsound/sound_output.hpp | 2 ++ 3 files changed, 48 insertions(+) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 62a7076e9..d63482273 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -583,6 +583,50 @@ Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float return sound.release(); } +Sound* OpenAL_Output::streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max) +{ + throwALerror(); + + std::auto_ptr sound; + ALuint src; + + if(mFreeSources.empty()) + fail("No free sources"); + src = mFreeSources.back(); + mFreeSources.pop_back(); + + try + { + DecoderPtr decoder = mManager.getDecoder(); + decoder->open(fname); + sound.reset(new OpenAL_SoundStream(*this, src, decoder)); + } + catch(std::exception &e) + { + mFreeSources.push_back(src); + throw; + } + + alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + + alSourcef(src, AL_REFERENCE_DISTANCE, min); + alSourcef(src, AL_MAX_DISTANCE, max); + alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); + + alSourcef(src, AL_GAIN, volume); + alSourcef(src, AL_PITCH, pitch); + + alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); + alSourcei(src, AL_LOOPING, AL_FALSE); + throwALerror(); + + sound->play(); + return sound.release(); +} + void OpenAL_Output::updateListener(const float *pos, const float *atdir, const float *updir) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 80c6db851..dddd1d6dc 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -30,6 +30,8 @@ namespace MWSound float min, float max, bool loop); virtual Sound *streamSound(const std::string &fname, float volume, float pitch); + virtual Sound *streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max); virtual void updateListener(const float *pos, const float *atdir, const float *updir); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index be721831f..a2a035e71 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -23,6 +23,8 @@ namespace MWSound virtual Sound *playSound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop) = 0; virtual Sound *streamSound(const std::string &fname, float volume, float pitch) = 0; + virtual Sound *streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max) = 0; virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; From afa2cb6de7a708a89630d30478bd21bbbef968b6 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 07:51:28 -0700 Subject: [PATCH 064/152] Stop trying to read decoded audio once it's finished --- apps/openmw/mwsound/openal_output.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index d63482273..f3cc261a4 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -230,6 +230,13 @@ void OpenAL_SoundStream::stop() bool OpenAL_SoundStream::isPlaying() { + ALint state; + + alGetSourcei(mSource, AL_SOURCE_STATE, &state); + throwALerror(); + + if(state == AL_PLAYING) + return true; return !mIsFinished; } @@ -259,7 +266,11 @@ bool OpenAL_SoundStream::process() alSourceUnqueueBuffers(mSource, 1, &bufid); processed--; + if(mIsFinished) + continue; + got = mDecoder->read(&data[0], data.size()); + mIsFinished = (got < data.size()); if(got > 0) { alBufferData(bufid, mFormat, &data[0], got, mSampleRate); @@ -275,17 +286,14 @@ bool OpenAL_SoundStream::process() alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); throwALerror(); - if(queued == 0) + if(queued > 0) { - mIsFinished = true; - return false; + alSourcePlay(mSource); + throwALerror(); } - - alSourcePlay(mSource); - throwALerror(); } - return true; + return !mIsFinished; } // From db46bf39b33f4271bebab20e0a34f917c668fef6 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 08:48:25 -0700 Subject: [PATCH 065/152] Add a rewind method to the sound decoder --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 4 ++++ apps/openmw/mwsound/ffmpeg_decoder.hpp | 2 ++ apps/openmw/mwsound/mpgsnd_decoder.cpp | 17 +++++++++++++++++ apps/openmw/mwsound/mpgsnd_decoder.hpp | 2 ++ apps/openmw/mwsound/openal_output.cpp | 3 ++- apps/openmw/mwsound/sound_decoder.hpp | 2 ++ 6 files changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 2b84439c0..cb14df946 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -29,6 +29,10 @@ size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) return 0; } +void FFmpeg_Decoder::rewind() +{ + fail("Not currently working"); +} FFmpeg_Decoder::FFmpeg_Decoder() { diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 676a576da..f622b115e 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -20,7 +20,9 @@ namespace MWSound virtual void close(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); + virtual size_t read(char *buffer, size_t bytes); + virtual void rewind(); FFmpeg_Decoder(); virtual ~FFmpeg_Decoder(); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index e112d30b1..1c13b5b1a 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -109,6 +109,23 @@ size_t MpgSnd_Decoder::read(char *buffer, size_t bytes) return got; } +void MpgSnd_Decoder::rewind() +{ + if(!mSndFile && !mMpgFile) + fail("No open file"); + + if(mSndFile) + { + if(sf_seek(mSndFile, 0, SEEK_SET) == -1) + fail("seek failed"); + } + else if(mMpgFile) + { + if(mpg123_seek(mMpgFile, 0, SEEK_SET) < 0) + fail("seek failed"); + } +} + MpgSnd_Decoder::MpgSnd_Decoder() : mSndFile(NULL), mMpgFile(NULL) { static bool initdone = false; diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 4d3c7f4ab..19a6079b8 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -23,7 +23,9 @@ namespace MWSound virtual void close(); virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); + virtual size_t read(char *buffer, size_t bytes); + virtual void rewind(); MpgSnd_Decoder(); public: diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index f3cc261a4..084c269dd 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -225,7 +225,8 @@ void OpenAL_SoundStream::stop() alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); throwALerror(); - // FIXME: Rewind decoder + + mDecoder->rewind(); } bool OpenAL_SoundStream::isPlaying() diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 2b16eaeee..5abd4371d 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -27,7 +27,9 @@ namespace MWSound virtual void close() = 0; virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; + virtual size_t read(char *buffer, size_t bytes) = 0; + virtual void rewind() = 0; virtual ~Sound_Decoder() { } From 91821ccd8c26ea0db1893f5fa07a86694343e833 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 09:08:59 -0700 Subject: [PATCH 066/152] Add the sound stream to the thread after resetting the mIsFinished flag --- apps/openmw/mwsound/openal_output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 084c269dd..468c1565d 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -213,8 +213,8 @@ void OpenAL_SoundStream::play() alSourcePlay(mSource); throwALerror(); - mOutput.mStreamThread->add(this); mIsFinished = false; + mOutput.mStreamThread->add(this); } void OpenAL_SoundStream::stop() From 6c45d6668ba73cbe131ef21fb53b8320ddeae622 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 10:33:06 -0700 Subject: [PATCH 067/152] Cache OpenAL buffers for easy reuse --- apps/openmw/mwsound/openal_output.cpp | 118 ++++++++++++++++---------- apps/openmw/mwsound/openal_output.hpp | 14 +++ 2 files changed, 87 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 468c1565d..a0884fb40 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -313,35 +313,8 @@ public: virtual void stop(); virtual bool isPlaying(); virtual void update(const float *pos); - - static ALuint LoadBuffer(DecoderPtr decoder); }; -ALuint OpenAL_Sound::LoadBuffer(DecoderPtr decoder) -{ - int srate; - ChannelConfig chans; - SampleType type; - ALenum format; - - decoder->getInfo(&srate, &chans, &type); - format = getALFormat(chans, type); - - std::vector data(32768); - size_t got, total = 0; - while((got=decoder->read(&data[total], data.size()-total)) > 0) - { - total += got; - data.resize(total*2); - } - data.resize(total); - - ALuint buf=0; - alGenBuffers(1, &buf); - alBufferData(buf, format, &data[0], total, srate); - return buf; -} - OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) : mOutput(output), mSource(src), mBuffer(buf) { @@ -352,8 +325,7 @@ OpenAL_Sound::~OpenAL_Sound() alSourcei(mSource, AL_BUFFER, 0); mOutput.mFreeSources.push_back(mSource); - alDeleteBuffers(1, &mBuffer); - alGetError(); + mOutput.bufferFinished(mBuffer); } void OpenAL_Sound::stop() @@ -431,6 +403,15 @@ void OpenAL_Output::deinit() alDeleteSources(mFreeSources.size(), mFreeSources.data()); mFreeSources.clear(); } + + mBufferRefs.clear(); + mUnusedBuffers.clear(); + while(!mBufferCache.empty()) + { + alDeleteBuffers(1, &mBufferCache.begin()->second); + mBufferCache.erase(mBufferCache.begin()); + } + alcMakeContextCurrent(0); if(mContext) alcDestroyContext(mContext); @@ -441,6 +422,63 @@ void OpenAL_Output::deinit() } +ALuint OpenAL_Output::getBuffer(const std::string &fname) +{ + ALuint buf = 0; + + NameMap::iterator iditer = mBufferCache.find(fname); + if(iditer != mBufferCache.end()) + { + buf = iditer->second; + if(mBufferRefs[buf]++ == 0) + { + IDDq::iterator iter = std::find(mUnusedBuffers.begin(), + mUnusedBuffers.end(), buf); + if(iter != mUnusedBuffers.end()) + mUnusedBuffers.erase(iter); + } + + return buf; + } + throwALerror(); + + int srate; + ChannelConfig chans; + SampleType type; + ALenum format; + + DecoderPtr decoder = mManager.getDecoder(); + decoder->open(fname); + decoder->getInfo(&srate, &chans, &type); + format = getALFormat(chans, type); + + std::vector data(32768); + size_t got, total = 0; + while((got=decoder->read(&data[total], data.size()-total)) > 0) + { + total += got; + data.resize(total*2); + } + data.resize(total); + decoder->close(); + + alGenBuffers(1, &buf); + throwALerror(); + + alBufferData(buf, format, &data[0], total, srate); + mBufferCache[fname] = buf; + mBufferRefs[buf] = 1; + + return buf; +} + +void OpenAL_Output::bufferFinished(ALuint buf) +{ + if(mBufferRefs.at(buf)-- == 1) + mUnusedBuffers.push_back(buf); +} + + Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop) { throwALerror(); @@ -455,19 +493,14 @@ Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pi try { - DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); - buf = OpenAL_Sound::LoadBuffer(decoder); - throwALerror(); - decoder->close(); - + buf = getBuffer(fname); sound.reset(new OpenAL_Sound(*this, src, buf)); } catch(std::exception &e) { mFreeSources.push_back(src); - if(alIsBuffer(buf)) - alDeleteBuffers(1, &buf); + if(buf && alIsBuffer(buf)) + bufferFinished(buf); alGetError(); throw; } @@ -509,19 +542,14 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl try { - DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); - buf = OpenAL_Sound::LoadBuffer(decoder); - throwALerror(); - decoder->close(); - + buf = getBuffer(fname); sound.reset(new OpenAL_Sound(*this, src, buf)); } catch(std::exception &e) { mFreeSources.push_back(src); - if(alIsBuffer(buf)) - alDeleteBuffers(1, &buf); + if(buf && alIsBuffer(buf)) + bufferFinished(buf); alGetError(); throw; } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index dddd1d6dc..62ec39aba 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include "alc.h" #include "al.h" @@ -22,6 +24,18 @@ namespace MWSound typedef std::vector IDVec; IDVec mFreeSources; + typedef std::map NameMap; + NameMap mBufferCache; + + typedef std::map IDRefMap; + IDRefMap mBufferRefs; + + typedef std::deque IDDq; + IDDq mUnusedBuffers; + + ALuint getBuffer(const std::string &fname); + void bufferFinished(ALuint buffer); + virtual void init(const std::string &devname=""); virtual void deinit(); From 04638516b282bf9550758690678095bf1924ef04 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 12:08:40 -0700 Subject: [PATCH 068/152] Check for stopped active sounds too and remove them --- apps/openmw/mwsound/soundmanager.cpp | 42 ++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index d86c5c2f5..f3480766b 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -133,13 +133,11 @@ namespace MWSound { try { - Sound *sound; const ESM::Position &pos = ptr.getCellRef().pos; - sound = mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop); - if(untracked) - mLooseSounds[id] = SoundPtr(sound); - else - mActiveSounds[ptr][id] = SoundPtr(sound); + SoundPtr sound(mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop)); + + if(untracked) mLooseSounds[id] = sound; + else mActiveSounds[ptr][id] = sound; } catch(std::exception &e) { @@ -157,7 +155,7 @@ namespace MWSound if(iditer == snditer->second.end()) return false; - return iditer->second->isPlaying(); + return true; } @@ -320,7 +318,7 @@ namespace MWSound if(iditer != snditer->second.end()) { snditer->second.erase(iditer); - if(snditer->second.size() == 0) + if(snditer->second.empty()) mActiveSounds.erase(snditer); } } @@ -450,16 +448,32 @@ namespace MWSound float up[3] = { nUp[0], -nUp[2], nUp[1] }; mOutput->updateListener(pos, at, up); - // Check if any "untracked" sounds are finished playing, and trash - // them - IDMap::iterator snditer = mLooseSounds.begin(); - while(snditer != mLooseSounds.end()) + // Check if any sounds are finished playing, and trash them + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - if(!snditer->second->isPlaying()) - mLooseSounds.erase(snditer++); + IDMap::iterator iditer = snditer->second.begin(); + while(iditer != snditer->second.end()) + { + if(!iditer->second->isPlaying()) + snditer->second.erase(iditer++); + else + iditer++; + } + if(snditer->second.empty()) + mActiveSounds.erase(snditer++); else snditer++; } + + IDMap::iterator iditer = mLooseSounds.begin(); + while(iditer != mLooseSounds.end()) + { + if(!iditer->second->isPlaying()) + mLooseSounds.erase(iditer++); + else + iditer++; + } } updateRegionSound(duration); From fd37a4827c1d993b719abf2f8ada23153144c04a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 19 Mar 2012 13:19:22 -0700 Subject: [PATCH 069/152] Enforce a 15MB limit on the sound buffer cache --- apps/openmw/mwsound/openal_output.cpp | 36 ++++++++++++++++++++++++++- apps/openmw/mwsound/openal_output.hpp | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index a0884fb40..7768119b6 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -469,13 +469,46 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) mBufferCache[fname] = buf; mBufferRefs[buf] = 1; + ALint bufsize = 0; + alGetBufferi(buf, AL_SIZE, &bufsize); + mBufferCacheMemSize += bufsize; + + // NOTE: Max buffer cache: 15MB + while(mBufferCacheMemSize > 15*1024*1024) + { + if(mUnusedBuffers.empty()) + { + std::cout <<"No more unused buffers to clear!"<< std::endl; + break; + } + + ALuint oldbuf = mUnusedBuffers.front(); + mUnusedBuffers.pop_front(); + + NameMap::iterator nameiter = mBufferCache.begin(); + while(nameiter != mBufferCache.end()) + { + if(nameiter->second == oldbuf) + mBufferCache.erase(nameiter++); + else + nameiter++; + } + + bufsize = 0; + alGetBufferi(oldbuf, AL_SIZE, &bufsize); + alDeleteBuffers(1, &oldbuf); + mBufferCacheMemSize -= bufsize; + } return buf; } void OpenAL_Output::bufferFinished(ALuint buf) { if(mBufferRefs.at(buf)-- == 1) + { + mBufferRefs.erase(mBufferRefs.find(buf)); mUnusedBuffers.push_back(buf); + } } @@ -679,7 +712,8 @@ void OpenAL_Output::updateListener(const float *pos, const float *atdir, const f OpenAL_Output::OpenAL_Output(SoundManager &mgr) - : Sound_Output(mgr), mDevice(0), mContext(0), mStreamThread(new StreamThread) + : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), + mStreamThread(new StreamThread) { } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 62ec39aba..aeb64ad0d 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -33,6 +33,8 @@ namespace MWSound typedef std::deque IDDq; IDDq mUnusedBuffers; + uint64_t mBufferCacheMemSize; + ALuint getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); From 80dbf82a748f7b821b8eabbdf7a795e1998fecc2 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 07:22:17 -0700 Subject: [PATCH 070/152] Explicitly stop sounds instead of relying on their deletion to do it --- apps/openmw/mwsound/soundmanager.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 508e9144d..e5449e09b 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -40,7 +40,6 @@ namespace MWSound : mFSStrict(fsstrict) , mEnvironment(environment) , mCurrentPlaylist(NULL) - , mUsingSound(useSound) { if(!useSound) return; @@ -239,9 +238,6 @@ namespace MWSound void SoundManager::playPlaylist(std::string playlist) { - if (!mUsingSound) - return; - if (playlist == "") { if(!isMusicPlaying()) @@ -321,13 +317,22 @@ namespace MWSound IDMap::iterator iditer = snditer->second.find(soundId); if(iditer != snditer->second.end()) { + iditer->second->stop(); snditer->second.erase(iditer); if(snditer->second.empty()) mActiveSounds.erase(snditer); } } else + { + IDMap::iterator iditer = snditer->second.begin(); + while(iditer != snditer->second.end()) + { + iditer->second->stop(); + iditer++; + } mActiveSounds.erase(snditer); + } } void SoundManager::stopSound(MWWorld::Ptr::CellStore *cell) @@ -337,7 +342,15 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(snditer->first.getCell() == cell) + { + IDMap::iterator iditer = snditer->second.begin(); + while(iditer != snditer->second.end()) + { + iditer->second->stop(); + iditer++; + } mActiveSounds.erase(snditer++); + } else snditer++; } @@ -347,7 +360,10 @@ namespace MWSound { IDMap::iterator iditer = mLooseSounds.find(soundId); if(iditer != mLooseSounds.end()) + { + iditer->second->stop(); mLooseSounds.erase(iditer); + } } bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const From 0261aac5184d961b9cfb8e5aaba8261ffdb3f5cf Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 10:34:36 -0700 Subject: [PATCH 071/152] Use Ogre's resource group manager to handle sound files --- apps/openmw/mwsound/mpgsnd_decoder.cpp | 79 +++++++++++++++- apps/openmw/mwsound/mpgsnd_decoder.hpp | 11 +++ apps/openmw/mwsound/sound_decoder.hpp | 12 ++- apps/openmw/mwsound/soundmanager.cpp | 122 ++++++------------------- apps/openmw/mwsound/soundmanager.hpp | 23 +---- 5 files changed, 125 insertions(+), 122 deletions(-) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 1c13b5b1a..f9bef9774 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -12,12 +12,83 @@ static void fail(const std::string &msg) namespace MWSound { +// +// libSndFile io callbacks +// +sf_count_t MpgSnd_Decoder::ogresf_get_filelen(void *user_data) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + return stream->size(); +} + +sf_count_t MpgSnd_Decoder::ogresf_seek(sf_count_t offset, int whence, void *user_data) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + + if(whence == SEEK_CUR) + stream->seek(stream->tell()+offset); + else if(whence == SEEK_SET) + stream->seek(offset); + else if(whence == SEEK_END) + stream->seek(stream->size()+offset); + else + return -1; + + return stream->tell(); +} + +sf_count_t MpgSnd_Decoder::ogresf_read(void *ptr, sf_count_t count, void *user_data) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + return stream->read(ptr, count); +} + +sf_count_t MpgSnd_Decoder::ogresf_write(const void*, sf_count_t, void*) +{ return -1; } + +sf_count_t MpgSnd_Decoder::ogresf_tell(void *user_data) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + return stream->tell(); +} + +// +// libmpg13 io callbacks +// +ssize_t MpgSnd_Decoder::ogrempg_read(void *user_data, void *ptr, size_t count) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + return stream->read(ptr, count); +} + +off_t MpgSnd_Decoder::ogrempg_lseek(void *user_data, off_t offset, int whence) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + + if(whence == SEEK_CUR) + stream->seek(stream->tell()+offset); + else if(whence == SEEK_SET) + stream->seek(offset); + else if(whence == SEEK_END) + stream->seek(stream->size()+offset); + else + return -1; + + return stream->tell(); +} + + void MpgSnd_Decoder::open(const std::string &fname) { close(); + mDataStream = mResourceMgr.openResource(fname); SF_INFO info; - mSndFile = sf_open(fname.c_str(), SFM_READ, &info); + SF_VIRTUAL_IO streamIO = { + ogresf_get_filelen, ogresf_seek, + ogresf_read, ogresf_write, ogresf_tell + }; + mSndFile = sf_open_virtual(&streamIO, SFM_READ, &info, this); if(mSndFile) { if(info.channels == 1) @@ -33,9 +104,11 @@ void MpgSnd_Decoder::open(const std::string &fname) mSampleRate = info.samplerate; return; } + mDataStream->seek(0); mMpgFile = mpg123_new(NULL, NULL); - if(mMpgFile && mpg123_open(mMpgFile, fname.c_str()) == MPG123_OK) + if(mMpgFile && mpg123_replace_reader_handle(mMpgFile, ogrempg_read, ogrempg_lseek, NULL) == MPG123_OK && + mpg123_open_handle(mMpgFile, this) == MPG123_OK) { try { @@ -79,6 +152,8 @@ void MpgSnd_Decoder::close() mpg123_delete(mMpgFile); mMpgFile = NULL; } + + mDataStream.setNull(); } void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 19a6079b8..35c753ec8 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -3,6 +3,8 @@ #include +#include + #include "mpg123.h" #include "sndfile.h" @@ -16,6 +18,15 @@ namespace MWSound SNDFILE *mSndFile; mpg123_handle *mMpgFile; + Ogre::DataStreamPtr mDataStream; + static sf_count_t ogresf_get_filelen(void *user_data); + static sf_count_t ogresf_seek(sf_count_t offset, int whence, void *user_data); + static sf_count_t ogresf_read(void *ptr, sf_count_t count, void *user_data); + static sf_count_t ogresf_write(const void*, sf_count_t, void*); + static sf_count_t ogresf_tell(void *user_data); + static ssize_t ogrempg_read(void*, void*, size_t); + static off_t ogrempg_lseek(void*, off_t, int); + ChannelConfig mChanConfig; int mSampleRate; diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 5abd4371d..858cc6353 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -3,6 +3,8 @@ #include +#include + namespace MWSound { enum SampleType { @@ -20,9 +22,10 @@ namespace MWSound size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type); size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type); - class Sound_Decoder + struct Sound_Decoder { - public: + Ogre::ResourceGroupManager &mResourceMgr; + virtual void open(const std::string &fname) = 0; virtual void close() = 0; @@ -31,10 +34,9 @@ namespace MWSound virtual size_t read(char *buffer, size_t bytes) = 0; virtual void rewind() = 0; + Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) + { } virtual ~Sound_Decoder() { } - - friend class OpenAL_Output; - friend class SoundManager; }; } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index e5449e09b..c0a1a8a83 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -39,7 +39,6 @@ namespace MWSound bool useSound, bool fsstrict, MWWorld::Environment& environment) : mFSStrict(fsstrict) , mEnvironment(environment) - , mCurrentPlaylist(NULL) { if(!useSound) return; @@ -59,24 +58,7 @@ namespace MWSound return; } - // The music library will accept these filetypes - // If none is given then it will accept all filetypes - std::vector acceptableExtensions; - acceptableExtensions.push_back(".mp3"); - acceptableExtensions.push_back(".wav"); - acceptableExtensions.push_back(".ogg"); - acceptableExtensions.push_back(".flac"); - - // Makes a list of all sound files, searches in reverse for priority reasons - for(Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) - Files::FileLister(*it / std::string("Sound"), mSoundFiles, true); - - // Makes a FileLibrary of all music files, searches in reverse for priority reasons - for(Files::PathContainer::const_reverse_iterator it = dataDirs.rbegin(); it != dataDirs.rend(); ++it) - mMusicLibrary.add(*it / std::string("Music"), true, mFSStrict, acceptableExtensions); - - std::string anything = "anything"; // anything is better that a segfault - mCurrentPlaylist = mMusicLibrary.section(anything, mFSStrict); // now points to an empty path + mResourceMgr = Ogre::ResourceGroupManager::getSingletonPtr(); } SoundManager::~SoundManager() @@ -120,7 +102,14 @@ namespace MWSound max = std::max(min, max); } - return Files::FileListLocator(mSoundFiles, snd->sound, mFSStrict, false); + std::string fname = std::string("Sound\\")+snd->sound; + if(!mResourceMgr->resourceExistsInAnyGroup(fname)) + { + std::string::size_type pos = fname.rfind('.'); + if(pos != std::string::npos) + fname = fname.substr(0, pos)+".mp3"; + } + return fname; } // Add a sound to the list and play it @@ -163,53 +152,25 @@ namespace MWSound { if(mMusic) mMusic->stop(); - setPlaylist(); - } - - void SoundManager::streamMusicFull(const std::string& filename) - { - if(mMusic) - mMusic->stop(); - mMusic.reset(mOutput->streamSound(filename, 0.4f, 1.0f)); + mMusic.reset(); } void SoundManager::streamMusic(const std::string& filename) { - std::string filePath = mMusicLibrary.locate(filename, mFSStrict, true).string(); - if(!filePath.empty()) + try { - try - { - streamMusicFull(filePath); - } - catch(std::exception &e) - { - std::cout << "Music Error: " << e.what() << "\n"; - } + if(mMusic) + mMusic->stop(); + mMusic.reset(mOutput->streamSound(filename, 0.4f, 1.0f)); + } + catch(std::exception &e) + { + std::cout << "Music Error: " << e.what() << "\n"; } } void SoundManager::startRandomTitle() { - if(mCurrentPlaylist && !mCurrentPlaylist->empty()) - { - Files::PathContainer::const_iterator fileIter = mCurrentPlaylist->begin(); - srand( time(NULL) ); - int r = rand() % mCurrentPlaylist->size() + 1; //old random code - - std::advance(fileIter, r - 1); - std::string music = fileIter->string(); - //std::cout << "Playing " << music << "\n"; - - try - { - streamMusicFull(music); - } - catch (std::exception &e) - { - std::cout << "Music Error: " << e.what() << "\n"; - } - } } bool SoundManager::isMusicPlaying() @@ -217,52 +178,21 @@ namespace MWSound return mMusic && mMusic->isPlaying(); } - bool SoundManager::setPlaylist(std::string playlist) - { - const Files::PathContainer* previousPlaylist; - previousPlaylist = mCurrentPlaylist; - if (playlist == "") - { - mCurrentPlaylist = mMusicLibrary.section(playlist, mFSStrict); - } - else if(mMusicLibrary.containsSection(playlist, mFSStrict)) - { - mCurrentPlaylist = mMusicLibrary.section(playlist, mFSStrict); - } - else - { - std::cout << "Warning: playlist named " << playlist << " does not exist.\n"; - } - return previousPlaylist == mCurrentPlaylist; - } - void SoundManager::playPlaylist(std::string playlist) { - if (playlist == "") - { - if(!isMusicPlaying()) - { - startRandomTitle(); - } - return; - } - - if(!setPlaylist(playlist)) - { - startRandomTitle(); - } - else if (!isMusicPlaying()) - { - startRandomTitle(); - } } void SoundManager::say(MWWorld::Ptr ptr, const std::string& filename) { // The range values are not tested - std::string filePath = Files::FileListLocator(mSoundFiles, filename, mFSStrict, true); - if(!filePath.empty()) - play3d(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); + std::string filePath = std::string("Sound\\")+filename; + if(!mResourceMgr->resourceExistsInAnyGroup(filePath)) + { + std::string::size_type pos = filePath.rfind('.'); + if(pos != std::string::npos) + filePath = filePath.substr(0, pos)+".mp3"; + } + play3d(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); } bool SoundManager::sayDone(MWWorld::Ptr ptr) const diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 3c7826821..2a474e5f5 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -3,6 +3,8 @@ #include +#include + #include #include "../mwworld/ptr.hpp" @@ -29,6 +31,8 @@ namespace MWSound class SoundManager { + Ogre::ResourceGroupManager *mResourceMgr; + // This is used for case insensitive and slash-type agnostic file // finding. It takes DOS paths (any case, \\ slashes or / slashes) // relative to the sound dir, and translates them into full paths @@ -41,19 +45,6 @@ namespace MWSound boost::shared_ptr mMusic; - void streamMusicFull(const std::string& filename); - ///< Play a soundifle - /// \param absolute filename - - // A list of all sound files used to lookup paths - Files::PathContainer mSoundFiles; - - // A library of all Music file paths stored by the folder they are contained in - Files::FileLibrary mMusicLibrary; - - // Points to the current playlist of music files stored in the music library - const Files::PathContainer* mCurrentPlaylist; - typedef boost::shared_ptr SoundPtr; typedef std::map IDMap; typedef std::map SoundMap; @@ -92,12 +83,6 @@ namespace MWSound bool isMusicPlaying(); ///< Returns true if music is playing - bool setPlaylist(std::string playlist=""); - ///< Set the playlist to an existing folder - /// \param name of the folder that contains the playlist - /// if none is set then it is set to an empty playlist - /// \return Return true if the previous playlist was the same - void playPlaylist(std::string playlist=""); ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist From 5ae47f783ebdf9cef3407b96d005f67aa78fd851 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 10:46:10 -0700 Subject: [PATCH 072/152] Use the sound manager's update to make sure music is still playing --- apps/openmw/engine.cpp | 4 ---- apps/openmw/mwsound/soundmanager.cpp | 4 ++++ apps/openmw/mwsound/soundmanager.hpp | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 89068ce53..57723cac9 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -117,11 +117,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // sound if (mUseSound) - { - mEnvironment.mSoundManager->playPlaylist(); - mEnvironment.mSoundManager->update (evt.timeSinceLastFrame); - } // update GUI Ogre::RenderWindow* window = mOgre->getWindow(); diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index c0a1a8a83..b4a2409ca 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -384,6 +384,10 @@ namespace MWSound { timePassed = 0.0f; + // Make sure music is still playing + if(!mMusic || !mMusic->isPlaying()) + startRandomTitle(); + Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); Ogre::Vector3 nPos, nDir, nUp; nPos = cam->getRealPosition(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 2a474e5f5..2f850b569 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -83,10 +83,9 @@ namespace MWSound bool isMusicPlaying(); ///< Returns true if music is playing - void playPlaylist(std::string playlist=""); + void playPlaylist(std::string playlist); ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist - /// if none is set then it plays from the current playlist void say(MWWorld::Ptr reference, const std::string& filename); ///< Make an actor say some text. From fc27d5cc198e85f0b7ddcf934b606ff312dd65c2 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 11:31:13 -0700 Subject: [PATCH 073/152] Restore music playback --- apps/openmw/mwsound/soundmanager.cpp | 15 +++++++++++++-- apps/openmw/mwsound/soundmanager.hpp | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index b4a2409ca..7b3346331 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -157,6 +157,7 @@ namespace MWSound void SoundManager::streamMusic(const std::string& filename) { + std::cout <<"Playing "<findResourceNames(Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "Music/"+mCurrentPlaylist+"/*"); + if(!filelist->size()) + return; + + int i = rand()%filelist->size(); + streamMusic((*filelist)[i]); } bool SoundManager::isMusicPlaying() @@ -178,8 +187,10 @@ namespace MWSound return mMusic && mMusic->isPlaying(); } - void SoundManager::playPlaylist(std::string playlist) + void SoundManager::playPlaylist(const std::string &playlist) { + mCurrentPlaylist = playlist; + startRandomTitle(); } void SoundManager::say(MWWorld::Ptr ptr, const std::string& filename) @@ -385,7 +396,7 @@ namespace MWSound timePassed = 0.0f; // Make sure music is still playing - if(!mMusic || !mMusic->isPlaying()) + if(!isMusicPlaying()) startRandomTitle(); Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 2f850b569..206b7a1b9 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -44,6 +44,7 @@ namespace MWSound std::auto_ptr mOutput; boost::shared_ptr mMusic; + std::string mCurrentPlaylist; typedef boost::shared_ptr SoundPtr; typedef std::map IDMap; @@ -83,7 +84,7 @@ namespace MWSound bool isMusicPlaying(); ///< Returns true if music is playing - void playPlaylist(std::string playlist); + void playPlaylist(const std::string &playlist); ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist From e48745b68ec25b351b4dd511dac1c1c1bb00e647 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 12:39:49 -0700 Subject: [PATCH 074/152] Fix streamMusic's path lookup --- apps/openmw/mwsound/soundmanager.cpp | 9 +++++++-- apps/openmw/mwsound/soundmanager.hpp | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 7b3346331..ec5ab310b 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -155,7 +155,7 @@ namespace MWSound mMusic.reset(); } - void SoundManager::streamMusic(const std::string& filename) + void SoundManager::streamMusicFull(const std::string& filename) { std::cout <<"Playing "<size(); - streamMusic((*filelist)[i]); + streamMusicFull((*filelist)[i]); } bool SoundManager::isMusicPlaying() diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 206b7a1b9..f350da2be 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -58,6 +58,7 @@ namespace MWSound MWWorld::Ptr ptr, const std::string &id, float volume, float pitch, float min, float max, bool loop, bool untracked=false); + void streamMusicFull(const std::string& filename); bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; void updateRegionSound(float duration); From 9a48002025b266a6767e8c8ea26de3c43dbc5749 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 14:13:58 -0700 Subject: [PATCH 075/152] Fix compilation of the FFmpeg decoder --- CMakeLists.txt | 15 +++++++++------ apps/openmw/mwsound/ffmpeg_decoder.hpp | 6 ++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97cb33685..d836cccaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,19 +126,22 @@ set(OPENMW_LIBS ${MANGLE_ALL} ${OENGINE_ALL}) set(OPENMW_LIBS_HEADER) # Sound setup +set(SOUND_INPUT_INCLUDES "") +set(SOUND_INPUT_LIBRARY "") +set(SOUND_DEFINE "") if (USE_FFMPEG) find_package(FFMPEG REQUIRED) - set(SOUND_INPUT_INCLUDES ${FFMPEG_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES}) - set(SOUND_DEFINE -DOPENMW_USE_FFMPEG) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${FFMPEG_INCLUDE_DIR}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${FFMPEG_LIBRARIES}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_FFMPEG) endif (USE_FFMPEG) if (USE_MPG123) find_package(MPG123 REQUIRED) find_package(SNDFILE REQUIRED) - set(SOUND_INPUT_INCLUDES ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) - set(SOUND_DEFINE -DOPENMW_USE_MPG123) + set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) + set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) + set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123) endif (USE_MPG123) # Platform specific diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index f622b115e..de3925988 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -3,6 +3,11 @@ #include +// FIXME: This can't be right? The headers refuse to build without UINT64_C, +// which only gets defined in stdint.h in either C99 mode or with this macro +// defined... +#define __STDC_CONSTANT_MACROS +#include extern "C" { #include @@ -25,6 +30,7 @@ namespace MWSound virtual void rewind(); FFmpeg_Decoder(); + public: virtual ~FFmpeg_Decoder(); friend class SoundManager; From deb473b9ae69a37a4c9db91a39f08c7184b5bb24 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 16:45:01 -0700 Subject: [PATCH 076/152] Implement the ffmpeg decoder --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 348 ++++++++++++++++++++++++- apps/openmw/mwsound/ffmpeg_decoder.hpp | 12 + 2 files changed, 354 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index cb14df946..b8ad80926 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -9,33 +9,369 @@ namespace MWSound static void fail(const std::string &msg) { throw std::runtime_error("FFmpeg exception: "+msg); } + +struct PacketList { + AVPacket pkt; + PacketList *next; +}; + +struct FFmpeg_Decoder::MyStream { + AVCodecContext *mCodecCtx; + int mStreamIdx; + + PacketList *mPackets; + + char *mDecodedData; + size_t mDecodedDataSize; + + FFmpeg_Decoder *mParent; + + void clearPackets(); + void *getAVAudioData(size_t *length); + size_t readAVAudioData(void *data, size_t length); +}; + + +int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + return stream->read(buf, buf_size); +} + +int FFmpeg_Decoder::writePacket(void *user_data, uint8_t *buf, int buf_size) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + return stream->write(buf, buf_size); +} + +int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) +{ + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + + whence &= ~AVSEEK_FORCE; + if(whence == AVSEEK_SIZE) + return stream->size(); + if(whence == SEEK_SET) + stream->seek(offset); + else if(whence == SEEK_CUR) + stream->seek(stream->tell()+offset); + else if(whence == SEEK_END) + stream->seek(stream->size()+offset); + else + return -1; + + return stream->tell(); +} + + +/* Used by getAV*Data to search for more compressed data, and buffer it in the + * correct stream. It won't buffer data for streams that the app doesn't have a + * handle for. */ +bool FFmpeg_Decoder::getNextPacket(int streamidx) +{ + PacketList *packet; + + packet = (PacketList*)av_malloc(sizeof(*packet)); + packet->next = NULL; + +next_packet: + while(av_read_frame(mFormatCtx, &packet->pkt) >= 0) + { + std::vector::iterator iter = mStreams.begin(); + + /* Check each stream the user has a handle for, looking for the one + * this packet belongs to */ + while(iter != mStreams.end()) + { + if((*iter)->mStreamIdx == packet->pkt.stream_index) + { + PacketList **last; + + last = &(*iter)->mPackets; + while(*last != NULL) + last = &(*last)->next; + + *last = packet; + if((*iter)->mStreamIdx == streamidx) + return true; + + packet = (PacketList*)av_malloc(sizeof(*packet)); + packet->next = NULL; + goto next_packet; + } + iter++; + } + /* Free the packet and look for another */ + av_free_packet(&packet->pkt); + } + av_free(packet); + + return false; +} + +void FFmpeg_Decoder::MyStream::clearPackets() +{ + while(mPackets) + { + PacketList *self = mPackets; + mPackets = self->next; + + av_free_packet(&self->pkt); + av_free(self); + } +} + +void *FFmpeg_Decoder::MyStream::getAVAudioData(size_t *length) +{ + int size; + int len; + + if(length) *length = 0; + if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) + return NULL; + + mDecodedDataSize = 0; + +next_packet: + if(!mPackets && !mParent->getNextPacket(mStreamIdx)) + return NULL; + + /* Decode some data, and check for errors */ + size = AVCODEC_MAX_AUDIO_FRAME_SIZE; + while((len=avcodec_decode_audio3(mCodecCtx, (int16_t*)mDecodedData, &size, + &mPackets->pkt)) == 0) + { + PacketList *self; + + if(size > 0) + break; + + /* Packet went unread and no data was given? Drop it and try the next, + * I guess... */ + self = mPackets; + mPackets = self->next; + + av_free_packet(&self->pkt); + av_free(self); + + if(!mPackets) + goto next_packet; + + size = AVCODEC_MAX_AUDIO_FRAME_SIZE; + } + + if(len < 0) + return NULL; + + if(len < mPackets->pkt.size) + { + /* Move the unread data to the front and clear the end bits */ + int remaining = mPackets->pkt.size - len; + memmove(mPackets->pkt.data, &mPackets->pkt.data[len], remaining); + memset(&mPackets->pkt.data[remaining], 0, mPackets->pkt.size - remaining); + mPackets->pkt.size -= len; + } + else + { + PacketList *self; + + self = mPackets; + mPackets = self->next; + + av_free_packet(&self->pkt); + av_free(self); + } + + if(size == 0) + goto next_packet; + + /* Set the output buffer size */ + mDecodedDataSize = size; + if(length) *length = mDecodedDataSize; + + return mDecodedData; +} + +size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) +{ + size_t dec = 0; + + while(dec < length) + { + /* If there's no decoded data, find some */ + if(mDecodedDataSize == 0) + { + if(getAVAudioData(NULL) == NULL) + break; + } + + if(mDecodedDataSize > 0) + { + /* Get the amount of bytes remaining to be written, and clamp to + * the amount of decoded data we have */ + size_t rem = length-dec; + if(rem > mDecodedDataSize) + rem = mDecodedDataSize; + + /* Copy the data to the app's buffer and increment */ + if(data != NULL) + { + memcpy(data, mDecodedData, rem); + data = (char*)data + rem; + } + dec += rem; + + /* If there's any decoded data left, move it to the front of the + * buffer for next time */ + if(rem < mDecodedDataSize) + memmove(mDecodedData, &mDecodedData[rem], mDecodedDataSize - rem); + mDecodedDataSize -= rem; + } + } + + /* Return the number of bytes we were able to get */ + return dec; +} + + + void FFmpeg_Decoder::open(const std::string &fname) { - fail("Not currently working"); + close(); + mDataStream = mResourceMgr.openResource(fname); + + if((mFormatCtx=avformat_alloc_context()) == NULL) + fail("Failed to allocate context"); + + try + { + mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); + if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) + fail("Failed to open input"); + + if(avformat_find_stream_info(mFormatCtx, NULL) < 0) + fail("Failed to find stream info"); + + for(size_t j = 0;j < mFormatCtx->nb_streams;j++) + { + if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO) + { + std::auto_ptr stream(new MyStream); + stream->mCodecCtx = mFormatCtx->streams[j]->codec; + stream->mStreamIdx = j; + stream->mPackets = NULL; + + AVCodec *codec; + codec = avcodec_find_decoder(stream->mCodecCtx->codec_id); + if(!codec || avcodec_open(stream->mCodecCtx, codec) < 0) + fail("Could not open audio codec"); + + stream->mDecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); + stream->mDecodedDataSize = 0; + + stream->mParent = this; + mStreams.push_back(stream.release()); + break; + } + } + if(mStreams.empty()) + fail("No audio streams"); + } + catch(std::exception &e) + { + av_close_input_file(mFormatCtx); + mFormatCtx = NULL; + throw; + } } void FFmpeg_Decoder::close() { + while(!mStreams.empty()) + { + MyStream *stream = mStreams.front(); + + stream->clearPackets(); + avcodec_close(stream->mCodecCtx); + av_free(stream->mDecodedData); + + mStreams.erase(mStreams.begin()); + } + if(mFormatCtx) + av_close_input_file(mFormatCtx); + mFormatCtx = NULL; } void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { - fail("Not currently working"); + if(mStreams.empty()) + fail("No audio stream info"); + + MyStream *stream = mStreams[0]; + if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8) + *type = SampleType_UInt8; + else if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16) + *type = SampleType_Int16; + else + fail(std::string("Unsupported sample format:")+ + av_get_sample_fmt_name(stream->mCodecCtx->sample_fmt)); + + if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) + *chans = ChannelConfig_Mono; + else if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_STEREO) + *chans = ChannelConfig_Stereo; + else if(stream->mCodecCtx->channel_layout == 0) + { + /* Unknown channel layout. Try to guess. */ + if(stream->mCodecCtx->channels == 1) + *chans = ChannelConfig_Mono; + else if(stream->mCodecCtx->channels == 2) + *chans = ChannelConfig_Stereo; + else + { + std::stringstream sstr("Unsupported raw channel count: "); + sstr << stream->mCodecCtx->channels; + fail(sstr.str()); + } + } + else + { + char str[1024]; + av_get_channel_layout_string(str, sizeof(str), stream->mCodecCtx->channels, + stream->mCodecCtx->channel_layout); + fail(std::string("Unsupported channel layout: ")+str); + } + + *samplerate = stream->mCodecCtx->sample_rate; } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { - fail("Not currently working"); - return 0; + if(mStreams.empty()) + fail("No audio streams"); + + return mStreams.front()->readAVAudioData(buffer, bytes); } void FFmpeg_Decoder::rewind() { - fail("Not currently working"); + av_seek_frame(mFormatCtx, -1, 0, 0); + for(size_t i = 0;i < mStreams.size();i++) + mStreams[i]->clearPackets(); } -FFmpeg_Decoder::FFmpeg_Decoder() +FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL) { + static bool done_init = false; + + /* We need to make sure ffmpeg is initialized. Optionally silence warning + * output from the lib */ + if(!done_init) + { + av_register_all(); + av_log_set_level(AV_LOG_ERROR); + done_init = true; + } } FFmpeg_Decoder::~FFmpeg_Decoder() diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index de3925988..9211bcc0d 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -21,6 +21,18 @@ namespace MWSound { class FFmpeg_Decoder : public Sound_Decoder { + AVFormatContext *mFormatCtx; + + struct MyStream; + std::vector mStreams; + + bool getNextPacket(int streamidx); + + Ogre::DataStreamPtr mDataStream; + static int readPacket(void *user_data, uint8_t *buf, int buf_size); + static int writePacket(void *user_data, uint8_t *buf, int buf_size); + static int64_t seek(void *user_data, int64_t offset, int whence); + virtual void open(const std::string &fname); virtual void close(); From 26a441f29a418c7c87db881491526c854ff07b97 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 17:57:28 -0700 Subject: [PATCH 077/152] Add a readAll method to the sound decoder, for potentially more efficient reading --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 12 ++++++++++++ apps/openmw/mwsound/ffmpeg_decoder.hpp | 1 + apps/openmw/mwsound/mpgsnd_decoder.cpp | 22 +++++++++++++++++----- apps/openmw/mwsound/mpgsnd_decoder.hpp | 2 ++ apps/openmw/mwsound/openal_output.cpp | 15 +++++---------- apps/openmw/mwsound/sound_decoder.hpp | 1 + apps/openmw/mwsound/soundmanager.cpp | 16 ++++++++++++++++ 7 files changed, 54 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index b8ad80926..55ccad43d 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -353,6 +353,18 @@ size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) return mStreams.front()->readAVAudioData(buffer, bytes); } +void FFmpeg_Decoder::readAll(std::vector &output) +{ + if(mStreams.empty()) + fail("No audio streams"); + MyStream *stream = mStreams.front(); + char *inbuf; + size_t got; + + while((inbuf=(char*)stream->getAVAudioData(&got)) != NULL && got > 0) + output.insert(output.end(), inbuf, inbuf+got); +} + void FFmpeg_Decoder::rewind() { av_seek_frame(mFormatCtx, -1, 0, 0); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 9211bcc0d..ae71c0052 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -39,6 +39,7 @@ namespace MWSound virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); + virtual void readAll(std::vector &output); virtual void rewind(); FFmpeg_Decoder(); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index f9bef9774..f576833a8 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -83,17 +83,16 @@ void MpgSnd_Decoder::open(const std::string &fname) close(); mDataStream = mResourceMgr.openResource(fname); - SF_INFO info; SF_VIRTUAL_IO streamIO = { ogresf_get_filelen, ogresf_seek, ogresf_read, ogresf_write, ogresf_tell }; - mSndFile = sf_open_virtual(&streamIO, SFM_READ, &info, this); + mSndFile = sf_open_virtual(&streamIO, SFM_READ, &mSndInfo, this); if(mSndFile) { - if(info.channels == 1) + if(mSndInfo.channels == 1) mChanConfig = ChannelConfig_Mono; - else if(info.channels == 2) + else if(mSndInfo.channels == 2) mChanConfig = ChannelConfig_Stereo; else { @@ -101,7 +100,7 @@ void MpgSnd_Decoder::open(const std::string &fname) mSndFile = NULL; fail("Unsupported channel count in "+fname); } - mSampleRate = info.samplerate; + mSampleRate = mSndInfo.samplerate; return; } mDataStream->seek(0); @@ -184,6 +183,19 @@ size_t MpgSnd_Decoder::read(char *buffer, size_t bytes) return got; } +void MpgSnd_Decoder::readAll(std::vector &output) +{ + if(mSndFile && mSndInfo.frames > 0) + { + size_t pos = output.size(); + output.resize(pos + mSndInfo.frames*mSndInfo.channels*2); + sf_readf_short(mSndFile, (short*)(output.data()+pos), mSndInfo.frames); + return; + } + // Fallback in case we don't know the total already + Sound_Decoder::readAll(output); +} + void MpgSnd_Decoder::rewind() { if(!mSndFile && !mMpgFile) diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 35c753ec8..1d9e9d5e2 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -15,6 +15,7 @@ namespace MWSound { class MpgSnd_Decoder : public Sound_Decoder { + SF_INFO mSndInfo; SNDFILE *mSndFile; mpg123_handle *mMpgFile; @@ -36,6 +37,7 @@ namespace MWSound virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); + virtual void readAll(std::vector &output); virtual void rewind(); MpgSnd_Decoder(); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 7768119b6..1efb70db4 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -442,30 +442,25 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) } throwALerror(); - int srate; + std::vector data; ChannelConfig chans; SampleType type; ALenum format; + int srate; DecoderPtr decoder = mManager.getDecoder(); decoder->open(fname); + decoder->getInfo(&srate, &chans, &type); format = getALFormat(chans, type); - std::vector data(32768); - size_t got, total = 0; - while((got=decoder->read(&data[total], data.size()-total)) > 0) - { - total += got; - data.resize(total*2); - } - data.resize(total); + decoder->readAll(data); decoder->close(); alGenBuffers(1, &buf); throwALerror(); - alBufferData(buf, format, &data[0], total, srate); + alBufferData(buf, format, data.data(), data.size(), srate); mBufferCache[fname] = buf; mBufferRefs[buf] = 1; diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index 858cc6353..e076c7b56 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -32,6 +32,7 @@ namespace MWSound virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0; virtual size_t read(char *buffer, size_t bytes) = 0; + virtual void readAll(std::vector &output); virtual void rewind() = 0; Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index ec5ab310b..ded94aee3 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -449,6 +449,22 @@ namespace MWSound updateRegionSound(duration); } + // Default readAll implementation, for decoders that can't do anything + // better + void Sound_Decoder::readAll(std::vector &output) + { + size_t total = output.size(); + size_t got; + + output.resize(total+32768); + while((got=read(&output[total], output.size()-total)) > 0) + { + total += got; + output.resize(total*2); + } + output.resize(total); + } + const char *getSampleTypeName(SampleType type) { From 2989a1e06e7b70273aeb2f8e9c1e57bbcc79b9c7 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 18:49:52 -0700 Subject: [PATCH 078/152] Improve ffmpeg failure messages --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 55ccad43d..d1a90da13 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -247,10 +247,10 @@ void FFmpeg_Decoder::open(const std::string &fname) { mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) - fail("Failed to open input"); + fail("Failed to open input stream for "+fname); if(avformat_find_stream_info(mFormatCtx, NULL) < 0) - fail("Failed to find stream info"); + fail("Failed to find stream info in "+fname); for(size_t j = 0;j < mFormatCtx->nb_streams;j++) { @@ -261,10 +261,15 @@ void FFmpeg_Decoder::open(const std::string &fname) stream->mStreamIdx = j; stream->mPackets = NULL; - AVCodec *codec; - codec = avcodec_find_decoder(stream->mCodecCtx->codec_id); - if(!codec || avcodec_open(stream->mCodecCtx, codec) < 0) - fail("Could not open audio codec"); + AVCodec *codec = avcodec_find_decoder(stream->mCodecCtx->codec_id); + if(!codec) + { + std::stringstream ss("No codec found for id "); + ss << stream->mCodecCtx->codec_id; + fail(ss.str()); + } + if(avcodec_open(stream->mCodecCtx, codec) < 0) + fail("Failed to open audio codec " + std::string(codec->long_name)); stream->mDecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); stream->mDecodedDataSize = 0; @@ -275,7 +280,7 @@ void FFmpeg_Decoder::open(const std::string &fname) } } if(mStreams.empty()) - fail("No audio streams"); + fail("No audio streams in "+fname); } catch(std::exception &e) { @@ -313,7 +318,7 @@ void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType * else if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16) *type = SampleType_Int16; else - fail(std::string("Unsupported sample format:")+ + fail(std::string("Unsupported sample format: ")+ av_get_sample_fmt_name(stream->mCodecCtx->sample_fmt)); if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) From 7b22ee6fd1a4113a131bc74016a92fef5cbe582a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 21:46:12 -0700 Subject: [PATCH 079/152] Use for_each to clear the ffmpeg stream packets --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index d1a90da13..fb530de91 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -373,8 +373,7 @@ void FFmpeg_Decoder::readAll(std::vector &output) void FFmpeg_Decoder::rewind() { av_seek_frame(mFormatCtx, -1, 0, 0); - for(size_t i = 0;i < mStreams.size();i++) - mStreams[i]->clearPackets(); + std::for_each(mStreams.begin(), mStreams.end(), std::mem_fun(&MyStream::clearPackets)); } FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL) From fd8326e9585259609122b8d289848328749c13a2 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 12:20:36 -0700 Subject: [PATCH 080/152] Better handle some ffmpeg errors --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index fb530de91..3ba1af525 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -243,12 +243,16 @@ void FFmpeg_Decoder::open(const std::string &fname) if((mFormatCtx=avformat_alloc_context()) == NULL) fail("Failed to allocate context"); - try + mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); + if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) { - mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); - if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) - fail("Failed to open input stream for "+fname); + avformat_free_context(mFormatCtx); + mFormatCtx = NULL; + fail("Failed to allocate input stream"); + } + try + { if(avformat_find_stream_info(mFormatCtx, NULL) < 0) fail("Failed to find stream info in "+fname); From 0d973ac8ffa5d7cf196642c83907c85eef52c869 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 14:38:37 -0700 Subject: [PATCH 081/152] Use the vector's data field instead of the address of the first element Same thing really, but less convoluted --- apps/openmw/mwsound/openal_output.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 1efb70db4..d41d692db 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -204,8 +204,8 @@ void OpenAL_SoundStream::play() for(ALuint i = 0;i < sNumBuffers;i++) { size_t got; - got = mDecoder->read(&data[0], data.size()); - alBufferData(mBuffers[i], mFormat, &data[0], got, mSampleRate); + got = mDecoder->read(data.data(), data.size()); + alBufferData(mBuffers[i], mFormat, data.data(), got, mSampleRate); } throwALerror(); @@ -270,11 +270,11 @@ bool OpenAL_SoundStream::process() if(mIsFinished) continue; - got = mDecoder->read(&data[0], data.size()); + got = mDecoder->read(data.data(), data.size()); mIsFinished = (got < data.size()); if(got > 0) { - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); + alBufferData(bufid, mFormat, data.data(), got, mSampleRate); alSourceQueueBuffers(mSource, 1, &bufid); } } while(processed > 0); From 6a85ef12299f12b302d9c7c14751ba426c5873b9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 14:46:29 -0700 Subject: [PATCH 082/152] Set Ogre's data stream to NULL when closing the audio file --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 3ba1af525..41859f7fd 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -309,6 +309,8 @@ void FFmpeg_Decoder::close() if(mFormatCtx) av_close_input_file(mFormatCtx); mFormatCtx = NULL; + + mDataStream.setNull(); } void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) From 8c5f85ca83907f31ba4532bbe0afae15a3986630 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 14:49:29 -0700 Subject: [PATCH 083/152] Use a local variable to mark sound streams as finished while processing This avoids a race condition where the source can underrun while the final buffers are being queued and the sound can be detected as stopped --- apps/openmw/mwsound/openal_output.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index d41d692db..ed7be21f6 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -251,6 +251,7 @@ void OpenAL_SoundStream::update(const float *pos) bool OpenAL_SoundStream::process() { + bool finished = mIsFinished; ALint processed, state; alGetSourcei(mSource, AL_SOURCE_STATE, &state); @@ -267,11 +268,11 @@ bool OpenAL_SoundStream::process() alSourceUnqueueBuffers(mSource, 1, &bufid); processed--; - if(mIsFinished) + if(finished) continue; got = mDecoder->read(data.data(), data.size()); - mIsFinished = (got < data.size()); + finished = (got < data.size()); if(got > 0) { alBufferData(bufid, mFormat, data.data(), got, mSampleRate); @@ -294,7 +295,8 @@ bool OpenAL_SoundStream::process() } } - return !mIsFinished; + mIsFinished = finished; + return !finished; } // From 56c3b988ccb60c66daa20db7ba0dd0fe333de2e0 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 15:19:40 -0700 Subject: [PATCH 084/152] Avoid copying the region when looking for a sound to play --- apps/openmw/mwsound/soundmanager.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index ded94aee3..a46c17e79 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -343,38 +343,34 @@ namespace MWSound timePassed += duration; if((current->cell->data.flags & current->cell->Interior) || timePassed < 10) return; - - ESM::Region test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region)); - timePassed = 0; + if(regionName != current->cell->region) { regionName = current->cell->region; total = 0; } - if(test.soundList.size() == 0) + const ESM::Region *regn = mEnvironment.mWorld->getStore().regions.find(regionName); + if(regn->soundList.size() == 0) return; - std::vector::iterator soundIter; + std::vector::const_iterator soundIter; if(total == 0) { - soundIter = test.soundList.begin(); - while(soundIter != test.soundList.end()) + soundIter = regn->soundList.begin(); + while(soundIter != regn->soundList.end()) { - int chance = (int) soundIter->chance; - //ESM::NAME32 go = soundIter->sound; - //std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n"; + total += (int)soundIter->chance; soundIter++; - total += chance; } } int r = rand() % total; //old random code int pos = 0; - soundIter = test.soundList.begin(); - while(soundIter != test.soundList.end()) + soundIter = regn->soundList.begin(); + while(soundIter != regn->soundList.end()) { const std::string go = soundIter->sound.toString(); int chance = (int) soundIter->chance; From 9a139f511f5937e5ad34e2765332857817a5502e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 15:29:05 -0700 Subject: [PATCH 085/152] Avoid redefining SOUND_IN --- apps/openmw/mwsound/soundmanager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index a46c17e79..5a6b07e0a 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -23,13 +23,17 @@ */ #ifdef OPENMW_USE_FFMPEG #include "ffmpeg_decoder.hpp" +#ifndef SOUND_IN #define SOUND_IN "FFmpeg" #endif +#endif #ifdef OPENMW_USE_MPG123 #include "mpgsnd_decoder.hpp" +#ifndef SOUND_IN #define SOUND_IN "mpg123,sndfile" #endif +#endif namespace MWSound From ebc49de85117bca4137b45cf95fe4a9e9a98d2fd Mon Sep 17 00:00:00 2001 From: Roman Melnik Date: Thu, 22 Mar 2012 00:39:19 +0200 Subject: [PATCH 086/152] Fix Bug #1: "Meshes rendered with wrong orientation" Discard the tranformation of the root NiNode when loading nif files (set the 'identity' transformation instead). After applying the fix test the following types of interiors: - Dwemer ruins (the bug was common here before the fix) - Ancestral tombs (the bug was common here before the fix) - Daedric shrines - Caves/grottos - Dunmer strongholds - Telvanni, imperial, redoran houses/towers/castles Also checked exteriors (although it is hard to do without terrain rendering) --- components/nif/nif_file.cpp | 9 +++++++++ components/nif/nif_types.hpp | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/components/nif/nif_file.cpp b/components/nif/nif_file.cpp index 52a37ba5c..80ea7a0b7 100644 --- a/components/nif/nif_file.cpp +++ b/components/nif/nif_file.cpp @@ -162,6 +162,15 @@ void NIFFile::parse() r->recName = rec; records[i] = r; r->read(this); + + // Discard tranformations for the root node, otherwise some meshes + // occasionally get wrong orientation. Only for NiNode-s for now, but + // can be expanded if needed. + // This should be rewritten when the method is cleaned up. + if (0 == i && rec == "NiNode") + { + static_cast(r)->trafo = Nif::Transformation::getIdentity(); + } } /* After the data, the nif contains an int N and then a list of N diff --git a/components/nif/nif_types.hpp b/components/nif/nif_types.hpp index 17cec19bc..ce7a0de62 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/nif_types.hpp @@ -59,6 +59,21 @@ struct Transformation Matrix rotation; float scale; Vector velocity; + + static const Transformation* getIdentity() + { + static Transformation* identity = NULL; + if (NULL == identity) + { + identity = new Transformation(); + identity->scale = 1.0f; + identity->rotation.v[0].array[0] = 1.0f; + identity->rotation.v[1].array[1] = 1.0f; + identity->rotation.v[2].array[2] = 1.0f; + } + + return identity; + } }; #pragma pack(pop) From 8056a7f20b22d57c77a9cb42addcad84da022672 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 18:20:32 -0700 Subject: [PATCH 087/152] Throw an exception when looking up a sound instead of returning an empty string --- apps/openmw/mwsound/soundmanager.cpp | 39 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 5a6b07e0a..cce1bfefe 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -86,7 +86,8 @@ namespace MWSound float &volume, float &min, float &max) { const ESM::Sound *snd = mEnvironment.mWorld->getStore().sounds.search(soundId); - if(snd == NULL) return ""; + if(snd == NULL) + throw std::runtime_error(std::string("Failed to lookup sound ")+soundId); if(snd->data.volume == 0) volume = 0.0f; @@ -224,34 +225,32 @@ namespace MWSound void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) { float min, max; - std::string file = lookup(soundId, volume, min, max); - if(!file.empty()) + try { - try - { - Sound *sound; - sound = mOutput->playSound(file, volume, pitch, loop); - mLooseSounds[soundId] = SoundPtr(sound); - } - catch(std::exception &e) - { - std::cout <<"Sound play error: "<playSound(file, volume, pitch, loop); + mLooseSounds[soundId] = SoundPtr(sound); + } + catch(std::exception &e) + { + std::cout <<"Sound play error: "< Date: Wed, 21 Mar 2012 18:35:20 -0700 Subject: [PATCH 088/152] Replace the sound file extension when opening fails This should make it more efficient to lookup a sound instead of checking each time it's played. A better method would perhaps be to check if the resource exists when the ESM is loaded and replace it then as needed. --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 12 +++++++++++- apps/openmw/mwsound/mpgsnd_decoder.cpp | 12 +++++++++++- apps/openmw/mwsound/soundmanager.cpp | 15 +-------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 41859f7fd..edc9e6e29 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -238,7 +238,17 @@ size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) void FFmpeg_Decoder::open(const std::string &fname) { close(); - mDataStream = mResourceMgr.openResource(fname); + try + { + mDataStream = mResourceMgr.openResource(fname); + } + catch(Ogre::Exception &e) + { + std::string::size_type pos = fname.rfind('.'); + if(pos == std::string::npos) + throw; + mDataStream = mResourceMgr.openResource(fname.substr(0, pos)+".mp3"); + } if((mFormatCtx=avformat_alloc_context()) == NULL) fail("Failed to allocate context"); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index f576833a8..e014008a0 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -81,7 +81,17 @@ off_t MpgSnd_Decoder::ogrempg_lseek(void *user_data, off_t offset, int whence) void MpgSnd_Decoder::open(const std::string &fname) { close(); - mDataStream = mResourceMgr.openResource(fname); + try + { + mDataStream = mResourceMgr.openResource(fname); + } + catch(Ogre::Exception &e) + { + std::string::size_type pos = fname.rfind('.'); + if(pos == std::string::npos) + throw; + mDataStream = mResourceMgr.openResource(fname.substr(0, pos)+".mp3"); + } SF_VIRTUAL_IO streamIO = { ogresf_get_filelen, ogresf_seek, diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index cce1bfefe..6c415957f 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -107,14 +107,7 @@ namespace MWSound max = std::max(min, max); } - std::string fname = std::string("Sound\\")+snd->sound; - if(!mResourceMgr->resourceExistsInAnyGroup(fname)) - { - std::string::size_type pos = fname.rfind('.'); - if(pos != std::string::npos) - fname = fname.substr(0, pos)+".mp3"; - } - return fname; + return std::string("Sound/")+snd->sound; } // Add a sound to the list and play it @@ -207,12 +200,6 @@ namespace MWSound { // The range values are not tested std::string filePath = std::string("Sound\\")+filename; - if(!mResourceMgr->resourceExistsInAnyGroup(filePath)) - { - std::string::size_type pos = filePath.rfind('.'); - if(pos != std::string::npos) - filePath = filePath.substr(0, pos)+".mp3"; - } play3d(filePath, ptr, "_say_sound", 1, 1, 100, 20000, false); } From e6fe1c026117afba201d0abf607f50d3e94e37c4 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 19:08:11 -0700 Subject: [PATCH 089/152] Remove a mostly unneeded wrapper function --- apps/openmw/mwsound/soundmanager.cpp | 47 ++++++++++++---------------- apps/openmw/mwsound/soundmanager.hpp | 4 --- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 6c415957f..36e058c6f 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -110,27 +110,6 @@ namespace MWSound return std::string("Sound/")+snd->sound; } - // Add a sound to the list and play it - void SoundManager::play3d(const std::string &file, - MWWorld::Ptr ptr, - const std::string &id, - float volume, float pitch, - float min, float max, - bool loop, bool untracked) - { - try - { - const ESM::Position &pos = ptr.getCellRef().pos; - SoundPtr sound(mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop)); - - if(untracked) mLooseSounds[id] = sound; - else mActiveSounds[ptr][id] = sound; - } - catch(std::exception &e) - { - std::cout <<"Sound Error: "<playSound3D(filePath, pos.pos, 1.0f, 1.0f, 100.0f, 20000.0f, false)); + mActiveSounds[ptr]["_say_sound"] = sound; + } + catch(std::exception &e) + { + std::cout <<"Sound Error: "<playSound3D(file, pos.pos, volume, pitch, min, max, loop)); + if(untracked) mLooseSounds[soundId] = sound; + else mActiveSounds[ptr][soundId] = sound; } catch(std::exception &e) { - std::cout <<"Sound play error: "< Date: Wed, 21 Mar 2012 19:21:36 -0700 Subject: [PATCH 090/152] Use a separate method to check for finished sounds and update the listener --- apps/openmw/mwsound/soundmanager.cpp | 90 +++++++++++++++------------- apps/openmw/mwsound/soundmanager.hpp | 1 + 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 36e058c6f..4cc4eec93 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -370,64 +370,68 @@ namespace MWSound } } - void SoundManager::update(float duration) + void SoundManager::updateSounds(float duration) { static float timePassed = 0.0; timePassed += duration; - if(timePassed > (1.0f/30.0f)) + if(timePassed < (1.0f/30.0f)) + return; + timePassed = 0.0f; + + // Make sure music is still playing + if(!isMusicPlaying()) + startRandomTitle(); + + Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); + Ogre::Vector3 nPos, nDir, nUp; + nPos = cam->getRealPosition(); + nDir = cam->getRealDirection(); + nUp = cam->getRealUp(); + + // The output handler is expecting vectors oriented like the game + // (that is, -Z goes down, +Y goes forward), but that's not what we + // get from Ogre's camera, so we have to convert. + float pos[3] = { nPos[0], -nPos[2], nPos[1] }; + float at[3] = { nDir[0], -nDir[2], nDir[1] }; + float up[3] = { nUp[0], -nUp[2], nUp[1] }; + mOutput->updateListener(pos, at, up); + + // Check if any sounds are finished playing, and trash them + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - timePassed = 0.0f; - - // Make sure music is still playing - if(!isMusicPlaying()) - startRandomTitle(); - - Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); - Ogre::Vector3 nPos, nDir, nUp; - nPos = cam->getRealPosition(); - nDir = cam->getRealDirection(); - nUp = cam->getRealUp(); - - // The output handler is expecting vectors oriented like the game - // (that is, -Z goes down, +Y goes forward), but that's not what we - // get from Ogre's camera, so we have to convert. - float pos[3] = { nPos[0], -nPos[2], nPos[1] }; - float at[3] = { nDir[0], -nDir[2], nDir[1] }; - float up[3] = { nUp[0], -nUp[2], nUp[1] }; - mOutput->updateListener(pos, at, up); - - // Check if any sounds are finished playing, and trash them - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) - { - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) - { - if(!iditer->second->isPlaying()) - snditer->second.erase(iditer++); - else - iditer++; - } - if(snditer->second.empty()) - mActiveSounds.erase(snditer++); - else - snditer++; - } - - IDMap::iterator iditer = mLooseSounds.begin(); - while(iditer != mLooseSounds.end()) + IDMap::iterator iditer = snditer->second.begin(); + while(iditer != snditer->second.end()) { if(!iditer->second->isPlaying()) - mLooseSounds.erase(iditer++); + snditer->second.erase(iditer++); else iditer++; } + if(snditer->second.empty()) + mActiveSounds.erase(snditer++); + else + snditer++; } + IDMap::iterator iditer = mLooseSounds.begin(); + while(iditer != mLooseSounds.end()) + { + if(!iditer->second->isPlaying()) + mLooseSounds.erase(iditer++); + else + iditer++; + } + } + + void SoundManager::update(float duration) + { + updateSounds(duration); updateRegionSound(duration); } + // Default readAll implementation, for decoders that can't do anything // better void Sound_Decoder::readAll(std::vector &output) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 5c64d912d..433f2c169 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -56,6 +56,7 @@ namespace MWSound float &volume, float &min, float &max); void streamMusicFull(const std::string& filename); bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const; + void updateSounds(float duration); void updateRegionSound(float duration); protected: From f11e3e39a146187ac9c4df8bd8f6a8d051f491f4 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 20:15:01 -0700 Subject: [PATCH 091/152] Add an enumerate method to the sound output interface --- apps/openmw/mwsound/openal_output.cpp | 14 ++++++++++++++ apps/openmw/mwsound/openal_output.hpp | 1 + apps/openmw/mwsound/sound_output.hpp | 1 + apps/openmw/mwsound/soundmanager.cpp | 6 ++++++ 4 files changed, 22 insertions(+) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ed7be21f6..6ee36b1ab 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -358,6 +358,20 @@ void OpenAL_Sound::update(const float *pos) // // An OpenAL output device // +std::vector OpenAL_Output::enumerate() +{ + std::vector devlist; + const ALCchar *devnames; + + devnames = alcGetString(NULL, ALC_DEVICE_SPECIFIER); + while(devnames && *devnames) + { + devlist.push_back(devnames); + devnames += strlen(devnames)+1; + } + return devlist; +} + void OpenAL_Output::init(const std::string &devname) { if(mDevice || mContext) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index aeb64ad0d..6e41c7dee 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -38,6 +38,7 @@ namespace MWSound ALuint getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); + virtual std::vector enumerate(); virtual void init(const std::string &devname=""); virtual void deinit(); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index a2a035e71..14b61e609 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -16,6 +16,7 @@ namespace MWSound { SoundManager &mManager; + virtual std::vector enumerate() = 0; virtual void init(const std::string &devname="") = 0; virtual void deinit() = 0; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 4cc4eec93..e3931afd6 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -53,6 +53,12 @@ namespace MWSound try { mOutput.reset(new DEFAULT_OUTPUT(*this)); + + std::vector names = mOutput->enumerate(); + std::cout <<"Enumerated output devices:"<< std::endl; + for(size_t i = 0;i < names.size();i++) + std::cout <<" "<init(); } catch(std::exception &e) From 15317796bf67b5834ef924ef0b09d61c14c6b0be Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 22:49:40 -0700 Subject: [PATCH 092/152] Handle the wav -> mp3 extension conversion in the sound output backend --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 12 +----------- apps/openmw/mwsound/mpgsnd_decoder.cpp | 12 +----------- apps/openmw/mwsound/openal_output.cpp | 12 +++++++++++- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index edc9e6e29..41859f7fd 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -238,17 +238,7 @@ size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) void FFmpeg_Decoder::open(const std::string &fname) { close(); - try - { - mDataStream = mResourceMgr.openResource(fname); - } - catch(Ogre::Exception &e) - { - std::string::size_type pos = fname.rfind('.'); - if(pos == std::string::npos) - throw; - mDataStream = mResourceMgr.openResource(fname.substr(0, pos)+".mp3"); - } + mDataStream = mResourceMgr.openResource(fname); if((mFormatCtx=avformat_alloc_context()) == NULL) fail("Failed to allocate context"); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index e014008a0..f576833a8 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -81,17 +81,7 @@ off_t MpgSnd_Decoder::ogrempg_lseek(void *user_data, off_t offset, int whence) void MpgSnd_Decoder::open(const std::string &fname) { close(); - try - { - mDataStream = mResourceMgr.openResource(fname); - } - catch(Ogre::Exception &e) - { - std::string::size_type pos = fname.rfind('.'); - if(pos == std::string::npos) - throw; - mDataStream = mResourceMgr.openResource(fname.substr(0, pos)+".mp3"); - } + mDataStream = mResourceMgr.openResource(fname); SF_VIRTUAL_IO streamIO = { ogresf_get_filelen, ogresf_seek, diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 6ee36b1ab..456ee534c 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -465,7 +465,17 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) int srate; DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); + try + { + decoder->open(fname); + } + catch(Ogre::FileNotFoundException &e) + { + std::string::size_type pos = fname.rfind('.'); + if(pos == std::string::npos) + throw; + decoder->open(fname.substr(0, pos)+".mp3"); + } decoder->getInfo(&srate, &chans, &type); format = getALFormat(chans, type); From bac6df5563ea7ca50c932a416b53e60dd6237af0 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 21 Mar 2012 23:32:24 -0700 Subject: [PATCH 093/152] Avoid leaking an ffmpeg stream --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 41859f7fd..9298bf848 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -303,6 +303,7 @@ void FFmpeg_Decoder::close() stream->clearPackets(); avcodec_close(stream->mCodecCtx); av_free(stream->mDecodedData); + delete stream; mStreams.erase(mStreams.begin()); } From fd4826d06fc9cb6489f10c177c2fcee4786939e0 Mon Sep 17 00:00:00 2001 From: Pieter van der Kloet Date: Thu, 22 Mar 2012 12:34:43 +0100 Subject: [PATCH 094/152] Fix for Bug #222, config is always written to user location now --- apps/launcher/datafilespage.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index c96fc2c7b..6d8c1f19d 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -1057,16 +1057,8 @@ void DataFilesPage::writeConfig(QString profile) return; } - // Prepare the OpenMW config - QString config = QString::fromStdString((mCfgMgr.getLocalPath() / "openmw.cfg").string()); - QFile file(config); - - if (!file.exists()) { - config = QString::fromStdString((mCfgMgr.getUserPath() / "openmw.cfg").string()); - } - - // Open the config as a QFile - file.setFileName(config); + // Open the OpenMW config as a QFile + QFile file(QString::fromStdString((mCfgMgr.getUserPath() / "openmw.cfg").string())); if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { // File cannot be opened or created From 73a1b256a880d69dbd0de24c35be4752223b16e0 Mon Sep 17 00:00:00 2001 From: Pieter van der Kloet Date: Thu, 22 Mar 2012 12:42:08 +0100 Subject: [PATCH 095/152] Changed warning dialog text to be less ambiguous --- apps/launcher/datafilespage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 6d8c1f19d..c15274e74 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -225,7 +225,7 @@ void DataFilesPage::setupDataFiles() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText(tr("
Could not find the Data Files location

\ - The directory containing the Data Files was not found.

\ + The directory containing the data files was not found.

\ Press \"Browse...\" to specify the location manually.
")); QAbstractButton *dirSelectButton = From 6df6cbc71a002480f37f0f3296b4715c01d02891 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 22 Mar 2012 14:12:43 +0100 Subject: [PATCH 096/152] updated changelog once more --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index 8f360235f..4c265c462 100644 --- a/readme.txt +++ b/readme.txt @@ -144,6 +144,7 @@ Bug #207: Ogre.log not written Bug #209: Sounds do not play Bug #210: Ogre crash at Dren plantation Bug #214: Unsupported file format version +Bug #222: Launcher is writing openmw.cfg file to wrong location Feature #9: NPC Dialogue Window Feature #16/42: New sky/weather implementation Feature #40: Fading From cc9f20a04f69a8907161f4e75d828293caf53e61 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 22 Mar 2012 20:25:41 +0100 Subject: [PATCH 097/152] MW_MapView skin --- apps/openmw/mwgui/layouts.hpp | 3 +++ files/mygui/CMakeLists.txt | 1 + files/mygui/core.xml | 1 + files/mygui/openmw_map_window_layout.xml | 29 ++++++++++++------------ files/mygui/openmw_map_window_skin.xml | 11 +++++++++ 5 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 files/mygui/openmw_map_window_skin.xml diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 058aac6a5..13f80badc 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -71,6 +71,8 @@ namespace MWGui // Obviously you should override this later on setCellName("No Cell Loaded"); + + getWidget(mMap, "Map"); } void setCellName(const std::string& cellName) @@ -103,6 +105,7 @@ namespace MWGui private: std::string mPrefix; + MyGUI::ScrollView* mMap; }; class MainMenu : public OEngine::GUI::Layout diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 9ea84747c..9a6cde7ba 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -42,6 +42,7 @@ configure_file("${SDIR}/openmw_layers.xml" "${DDIR}/openmw_layers.xml" COPYONLY) configure_file("${SDIR}/openmw_mainmenu_layout.xml" "${DDIR}/openmw_mainmenu_layout.xml" COPYONLY) configure_file("${SDIR}/openmw_mainmenu_skin.xml" "${DDIR}/openmw_mainmenu_skin.xml" COPYONLY) configure_file("${SDIR}/openmw_map_window_layout.xml" "${DDIR}/openmw_map_window_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_map_window_skin.xml" "${DDIR}/openmw_map_window_skin.xml" COPYONLY) configure_file("${SDIR}/openmw.pointer.xml" "${DDIR}/openmw.pointer.xml" COPYONLY) configure_file("${SDIR}/openmw_progress.skin.xml" "${DDIR}/openmw_progress.skin.xml" COPYONLY) configure_file("${SDIR}/openmw_stats_window_layout.xml" "${DDIR}/openmw_stats_window_layout.xml" COPYONLY) diff --git a/files/mygui/core.xml b/files/mygui/core.xml index 30e01a8f3..5bec13aef 100644 --- a/files/mygui/core.xml +++ b/files/mygui/core.xml @@ -20,6 +20,7 @@ + diff --git a/files/mygui/openmw_map_window_layout.xml b/files/mygui/openmw_map_window_layout.xml index 2b0d4b2ac..1bb304455 100644 --- a/files/mygui/openmw_map_window_layout.xml +++ b/files/mygui/openmw_map_window_layout.xml @@ -2,50 +2,49 @@ - - - - - - + + - + - + - + - + - + - + - + - + - + + + + diff --git a/files/mygui/openmw_map_window_skin.xml b/files/mygui/openmw_map_window_skin.xml new file mode 100644 index 000000000..452177464 --- /dev/null +++ b/files/mygui/openmw_map_window_skin.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + From cf3515a8989bb3480e043c3ce6c668ca535c2331 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 22 Mar 2012 21:27:21 +0100 Subject: [PATCH 098/152] it is now possible to drag the minimap with the mouse --- apps/openmw/mwgui/layouts.hpp | 44 ++++++++++++++++++++++-- files/mygui/openmw_map_window_layout.xml | 4 ++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 13f80badc..6c66bb51f 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -73,6 +73,15 @@ namespace MWGui setCellName("No Cell Loaded"); getWidget(mMap, "Map"); + + MyGUI::Button* button; + getWidget(button, "WorldButton"); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); + + MyGUI::Button* eventbox; + getWidget(eventbox, "EventBox"); + eventbox->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); + eventbox->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); } void setCellName(const std::string& cellName) @@ -88,6 +97,7 @@ namespace MWGui void setActiveCell(const int x, const int y, bool interior=false) { + if (x==mCurX && y==mCurY && mInterior==interior) return; // don't do anything if we're still in the same cell for (int mx=0; mx<3; ++mx) { for (int my=0; my<3; ++my) @@ -97,15 +107,45 @@ namespace MWGui std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); - setImage(name, image); - setImage(name+"_fog", image+"_fog"); + + if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) + setImage(name, image); + if (MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) + setImage(name+"_fog", image+"_fog"); } } + mInterior = interior; + mCurX = x; + mCurY = y; + } + + void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) + { + if (_id!=MyGUI::MouseButton::Left) return; + mLastDragPos = MyGUI::IntPoint(_left, _top); + } + + void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) + { + if (_id!=MyGUI::MouseButton::Left) return; + + MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; + mMap->setViewOffset( mMap->getViewOffset() + diff ); + + mLastDragPos = MyGUI::IntPoint(_left, _top); + } + + void onWorldButtonClicked(MyGUI::Widget* _sender) + { + /// \todo } private: std::string mPrefix; MyGUI::ScrollView* mMap; + MyGUI::IntPoint mLastDragPos; + int mCurX, mCurY; + bool mInterior; }; class MainMenu : public OEngine::GUI::Layout diff --git a/files/mygui/openmw_map_window_layout.xml b/files/mygui/openmw_map_window_layout.xml index 1bb304455..90ad70edb 100644 --- a/files/mygui/openmw_map_window_layout.xml +++ b/files/mygui/openmw_map_window_layout.xml @@ -4,7 +4,7 @@ - + @@ -44,6 +44,8 @@ + + From 2e288192433c8834ea959a901633b89f061477a4 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 22 Mar 2012 18:39:10 -0700 Subject: [PATCH 099/152] Avoid trying to play an ambient sound if there's no chance for any to play --- apps/openmw/mwsound/soundmanager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index e3931afd6..38b9bee25 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -341,9 +341,6 @@ namespace MWSound } const ESM::Region *regn = mEnvironment.mWorld->getStore().regions.find(regionName); - if(regn->soundList.size() == 0) - return; - std::vector::const_iterator soundIter; if(total == 0) { @@ -353,6 +350,8 @@ namespace MWSound total += (int)soundIter->chance; soundIter++; } + if(total == 0) + return; } int r = rand() % total; //old random code From 277597c567efa0b0fe50191dc4026c43716f374a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 22 Mar 2012 18:44:55 -0700 Subject: [PATCH 100/152] Fix DEFAULT_OUTPUT for OpenAL_Output --- apps/openmw/mwsound/openal_output.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 6e41c7dee..33ab7a2aa 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -63,7 +63,7 @@ namespace MWSound friend class SoundManager; }; #ifndef DEFAULT_OUTPUT -#define DEFAULT_OUTPUT OpenAL_Output +#define DEFAULT_OUTPUT (::MWSound::OpenAL_Output) #endif }; From 2f0af42261ca048d20034e7273a32b92494aa545 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 22 Mar 2012 23:51:00 -0700 Subject: [PATCH 101/152] Use the ALC_ENUMERATE_ALL_EXT extension with OpenAL when available --- apps/openmw/mwsound/openal_output.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 456ee534c..b9a32d57c 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -363,7 +363,10 @@ std::vector OpenAL_Output::enumerate() std::vector devlist; const ALCchar *devnames; - devnames = alcGetString(NULL, ALC_DEVICE_SPECIFIER); + if(alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT")) + devnames = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); + else + devnames = alcGetString(NULL, ALC_DEVICE_SPECIFIER); while(devnames && *devnames) { devlist.push_back(devnames); @@ -380,7 +383,10 @@ void OpenAL_Output::init(const std::string &devname) mDevice = alcOpenDevice(devname.c_str()); if(!mDevice) fail("Failed to open \""+devname+"\""); - std::cout << "Opened \""< Date: Fri, 23 Mar 2012 08:16:04 +0100 Subject: [PATCH 102/152] automatically zoom in on the player while moving --- apps/openmw/mwgui/layouts.hpp | 26 ++++++++++++++++++++++++++ apps/openmw/mwgui/window_manager.cpp | 5 +++++ apps/openmw/mwgui/window_manager.hpp | 1 + apps/openmw/mwrender/localmap.cpp | 2 ++ 4 files changed, 34 insertions(+) diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 6c66bb51f..e55feceb2 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -84,6 +84,15 @@ namespace MWGui eventbox->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); } + void setVisible(bool b) + { + mMainWidget->setVisible(b); + if (b) + mVisible = true; + else + mVisible = false; + } + void setCellName(const std::string& cellName) { static_cast(mMainWidget)->setCaption(cellName); @@ -110,8 +119,13 @@ namespace MWGui if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) setImage(name, image); + else + setImage(name, "black.png"); + if (MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) setImage(name+"_fog", image+"_fog"); + else + setImage(name+"_fog", "black.png"); } } mInterior = interior; @@ -119,6 +133,17 @@ namespace MWGui mCurY = y; } + void setPlayerPos(const float x, const float y) + { + if (mVisible) return; + MyGUI::IntSize size = mMap->getCanvasSize(); + MyGUI::IntPoint middle = MyGUI::IntPoint(x*size.width,y*size.height); + MyGUI::IntCoord viewsize = mMap->getCoord(); + MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); + std::cout << pos.left << " top " << pos.top << std::endl; + mMap->setViewOffset(pos); + } + void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; @@ -146,6 +171,7 @@ namespace MWGui MyGUI::IntPoint mLastDragPos; int mCurX, mCurY; bool mInterior; + bool mVisible; }; class MainMenu : public OEngine::GUI::Layout diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index bb04543a0..c7ee1438e 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -424,3 +424,8 @@ void WindowManager::setInteriorMapTexture(const int x, const int y) { map->setActiveCell(x,y, true); } + +void WindowManager::setPlayerPos(const float x, const float y) +{ + map->setPlayerPos(x,y); +} diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 2b141acf5..922009e4d 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -154,6 +154,7 @@ namespace MWGui void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty void changeCell(MWWorld::Ptr::CellStore* cell); ///< change the active cell + void setPlayerPos(const float x, const float y); ///< set player position in map space void setInteriorMapTexture(const int x, const int y); ///< set the index of the map texture that should be used (for interiors) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index e08559d26..b7deb6f7f 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -263,6 +263,7 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) u = std::abs((pos.x - (sSize*x))/sSize); v = 1-std::abs((pos.y + (sSize*y))/sSize); texName = "Cell_"+coordStr(x,y); + } else { @@ -274,6 +275,7 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) texName = mInteriorName + "_" + coordStr(x,y); } + mEnvironment->mWindowManager->setPlayerPos(1/3.f + u/3.f, 1/3.f + v/3.f); // explore radius (squared) const float sqrExploreRadius = 0.01 * sFogOfWarResolution*sFogOfWarResolution; From 8e299bd25b12c134f4abd36252c0c388363d73c6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 23 Mar 2012 08:16:32 +0100 Subject: [PATCH 103/152] remove unuseful std::cout --- apps/openmw/mwgui/layouts.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index e55feceb2..20ee1a80c 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -140,7 +140,6 @@ namespace MWGui MyGUI::IntPoint middle = MyGUI::IntPoint(x*size.width,y*size.height); MyGUI::IntCoord viewsize = mMap->getCoord(); MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); - std::cout << pos.left << " top " << pos.top << std::endl; mMap->setViewOffset(pos); } From 1ccddefe282c2d1b84a95312bfa02c78b6fc2872 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 23 Mar 2012 00:31:01 -0700 Subject: [PATCH 104/152] Properly report the default device when opening sound fails --- apps/openmw/mwsound/openal_output.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index b9a32d57c..5e61cb94f 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -382,7 +382,12 @@ void OpenAL_Output::init(const std::string &devname) mDevice = alcOpenDevice(devname.c_str()); if(!mDevice) - fail("Failed to open \""+devname+"\""); + { + if(devname.empty()) + fail("Failed to open default device"); + else + fail("Failed to open \""+devname+"\""); + } if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT")) std::cout << "Opened \""< Date: Fri, 23 Mar 2012 09:00:00 +0100 Subject: [PATCH 105/152] player arrow --- apps/openmw/mwgui/layouts.hpp | 10 +++++++++- apps/openmw/mwrender/localmap.cpp | 2 +- files/mygui/core.skin | 3 +++ files/mygui/openmw_map_window_layout.xml | 12 ++++++------ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 20ee1a80c..1f0bdd0fa 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -73,6 +73,7 @@ namespace MWGui setCellName("No Cell Loaded"); getWidget(mMap, "Map"); + getWidget(mPlayerArrow, "Compass"); MyGUI::Button* button; getWidget(button, "WorldButton"); @@ -137,10 +138,16 @@ namespace MWGui { if (mVisible) return; MyGUI::IntSize size = mMap->getCanvasSize(); - MyGUI::IntPoint middle = MyGUI::IntPoint(x*size.width,y*size.height); + MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height); MyGUI::IntCoord viewsize = mMap->getCoord(); MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); mMap->setViewOffset(pos); + + mPlayerArrow->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); + + MyGUI::ISubWidget* main = mPlayerArrow->getSubWidgetMain(); + MyGUI::RotatingSkin* rotatingSubskin = main->castType(); + rotatingSubskin->setAngle(3.141 * 0.5); } void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) @@ -167,6 +174,7 @@ namespace MWGui private: std::string mPrefix; MyGUI::ScrollView* mMap; + MyGUI::ImageBox* mPlayerArrow; MyGUI::IntPoint mLastDragPos; int mCurX, mCurY; bool mInterior; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index b7deb6f7f..e7a7c9f05 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -275,7 +275,7 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) texName = mInteriorName + "_" + coordStr(x,y); } - mEnvironment->mWindowManager->setPlayerPos(1/3.f + u/3.f, 1/3.f + v/3.f); + mEnvironment->mWindowManager->setPlayerPos(u, v); // explore radius (squared) const float sqrExploreRadius = 0.01 * sFogOfWarResolution*sFogOfWarResolution; diff --git a/files/mygui/core.skin b/files/mygui/core.skin index 83dcd4b9f..28838c234 100644 --- a/files/mygui/core.skin +++ b/files/mygui/core.skin @@ -14,4 +14,7 @@ + + + diff --git a/files/mygui/openmw_map_window_layout.xml b/files/mygui/openmw_map_window_layout.xml index 90ad70edb..0ec99f450 100644 --- a/files/mygui/openmw_map_window_layout.xml +++ b/files/mygui/openmw_map_window_layout.xml @@ -4,7 +4,7 @@ - + @@ -23,7 +23,10 @@ - + + + + @@ -42,11 +45,8 @@ - - - - + From 770b0f2106e36caa74f1e4bc52bf8c714ca0b548 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 23 Mar 2012 10:25:55 +0100 Subject: [PATCH 106/152] gui changes --- apps/openmw/mwgui/layouts.hpp | 45 +++++++++++++++--------- files/mygui/openmw_list.skin.xml | 4 +-- files/mygui/openmw_map_window_layout.xml | 13 +++++-- files/mygui/openmw_map_window_skin.xml | 6 ++-- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 1f0bdd0fa..49b956f0a 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -63,7 +63,7 @@ namespace MWGui { public: MapWindow() - : Layout("openmw_map_window_layout.xml") + : Layout("openmw_map_window_layout.xml"), mGlobal(false) { setCoord(500,0,320,300); setText("WorldButton", "World"); @@ -72,12 +72,12 @@ namespace MWGui // Obviously you should override this later on setCellName("No Cell Loaded"); - getWidget(mMap, "Map"); + getWidget(mLocalMap, "LocalMap"); + getWidget(mGlobalMap, "GlobalMap"); getWidget(mPlayerArrow, "Compass"); - MyGUI::Button* button; - getWidget(button, "WorldButton"); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); + getWidget(mButton, "WorldButton"); + mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); MyGUI::Button* eventbox; getWidget(eventbox, "EventBox"); @@ -136,49 +136,62 @@ namespace MWGui void setPlayerPos(const float x, const float y) { - if (mVisible) return; - MyGUI::IntSize size = mMap->getCanvasSize(); + if (mGlobal || mVisible) return; + MyGUI::IntSize size = mLocalMap->getCanvasSize(); MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height); - MyGUI::IntCoord viewsize = mMap->getCoord(); + MyGUI::IntCoord viewsize = mLocalMap->getCoord(); MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); - mMap->setViewOffset(pos); + mLocalMap->setViewOffset(pos); mPlayerArrow->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); MyGUI::ISubWidget* main = mPlayerArrow->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); - rotatingSubskin->setAngle(3.141 * 0.5); + rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); + rotatingSubskin->setAngle(3.141); } void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; - mLastDragPos = MyGUI::IntPoint(_left, _top); + if (!mGlobal) + mLastDragPos = MyGUI::IntPoint(_left, _top); } void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; - MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; - mMap->setViewOffset( mMap->getViewOffset() + diff ); + if (!mGlobal) + { + MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; + mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff ); - mLastDragPos = MyGUI::IntPoint(_left, _top); + mLastDragPos = MyGUI::IntPoint(_left, _top); + } } void onWorldButtonClicked(MyGUI::Widget* _sender) { - /// \todo + mGlobal = !mGlobal; + mGlobalMap->setVisible(mGlobal); + mLocalMap->setVisible(!mGlobal); + + mButton->setCaption( mGlobal ? "Local" : "World" ); } private: std::string mPrefix; - MyGUI::ScrollView* mMap; + MyGUI::ScrollView* mLocalMap; + MyGUI::ScrollView* mGlobalMap; MyGUI::ImageBox* mPlayerArrow; + MyGUI::Button* mButton; MyGUI::IntPoint mLastDragPos; int mCurX, mCurY; bool mInterior; bool mVisible; + + bool mGlobal; }; class MainMenu : public OEngine::GUI::Layout diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index c3b97c752..98390367c 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -197,7 +197,7 @@ - + @@ -233,7 +233,7 @@ - + diff --git a/files/mygui/openmw_map_window_layout.xml b/files/mygui/openmw_map_window_layout.xml index 0ec99f450..f5c2c9991 100644 --- a/files/mygui/openmw_map_window_layout.xml +++ b/files/mygui/openmw_map_window_layout.xml @@ -2,7 +2,13 @@ - + + + + + + + @@ -46,7 +52,10 @@ - + + + + diff --git a/files/mygui/openmw_map_window_skin.xml b/files/mygui/openmw_map_window_skin.xml index 452177464..fbb7af7ae 100644 --- a/files/mygui/openmw_map_window_skin.xml +++ b/files/mygui/openmw_map_window_skin.xml @@ -2,10 +2,10 @@ - + - - + + From dcdc75971990d09252cbe51744b9cb7a75d76654 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 23 Mar 2012 13:02:07 +0100 Subject: [PATCH 107/152] Adding readme to windows builds --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a454f211b..7a1b98335 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,6 +347,7 @@ if(WIN32) FILE(GLOB files "${OpenMW_BINARY_DIR}/Release/*.*") INSTALL(FILES ${files} DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/readme.txt" DESTINATION ".") INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") SET(CPACK_GENERATOR "NSIS") @@ -357,6 +358,7 @@ if(WIN32) SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;esmtool;Esmtool;omwlauncher;OpenMW Launcher") + set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/readme.txt") SET(CPACK_RESOURCE_FILE_LICENSE "${OpenMW_SOURCE_DIR}/GPL3.txt") SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") From 06fa310e292ebc7db3e270a804dc6b5b03630dd4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 23 Mar 2012 15:00:48 +0100 Subject: [PATCH 108/152] player arrow rotated correctly --- apps/openmw/mwgui/layouts.hpp | 9 ++++++++- apps/openmw/mwgui/window_manager.cpp | 5 +++++ apps/openmw/mwgui/window_manager.hpp | 1 + apps/openmw/mwrender/localmap.cpp | 3 ++- apps/openmw/mwrender/localmap.hpp | 5 +++-- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 49b956f0a..7ae28b4d8 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -14,6 +14,8 @@ #include "../mwmechanics/stat.hpp" #include "window_base.hpp" +#include + /* This file contains classes corresponding to window layouts defined in resources/mygui/ *.xml. @@ -144,11 +146,16 @@ namespace MWGui mLocalMap->setViewOffset(pos); mPlayerArrow->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); + } + void setPlayerDir(const float x, const float y) + { + if (!mVisible) return; MyGUI::ISubWidget* main = mPlayerArrow->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); - rotatingSubskin->setAngle(3.141); + float angle = std::atan2(x,y); + rotatingSubskin->setAngle(angle); } void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index c7ee1438e..a263ee2a0 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -429,3 +429,8 @@ void WindowManager::setPlayerPos(const float x, const float y) { map->setPlayerPos(x,y); } + +void WindowManager::setPlayerDir(const float x, const float y) +{ + map->setPlayerDir(x,y); +} diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 922009e4d..582f438e8 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -155,6 +155,7 @@ namespace MWGui void changeCell(MWWorld::Ptr::CellStore* cell); ///< change the active cell void setPlayerPos(const float x, const float y); ///< set player position in map space + void setPlayerDir(const float x, const float y); ///< set player view direction in map space void setInteriorMapTexture(const int x, const int y); ///< set the index of the map texture that should be used (for interiors) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index e7a7c9f05..83fe40166 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -225,7 +225,7 @@ void LocalMap::render(const float x, const float y, mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, fStart, fEnd); } -void LocalMap::setPlayerPosition (const Ogre::Vector3& position) +void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& direction) { if (sFogOfWarSkip != 0) { @@ -276,6 +276,7 @@ void LocalMap::setPlayerPosition (const Ogre::Vector3& position) texName = mInteriorName + "_" + coordStr(x,y); } mEnvironment->mWindowManager->setPlayerPos(u, v); + mEnvironment->mWindowManager->setPlayerDir(direction.x, -direction.z); // explore radius (squared) const float sqrExploreRadius = 0.01 * sFogOfWarResolution*sFogOfWarResolution; diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 13e01b7f4..3bef475ea 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -40,12 +40,13 @@ namespace MWRender Ogre::AxisAlignedBox bounds); /** - * Set the position of the player. + * Set the position & direction of the player. * @remarks This is used to draw a "fog of war" effect * to hide areas on the map the player has not discovered yet. * @param position (OGRE coordinates) + * @param view direction (OGRE coordinates) */ - void setPlayerPosition (const Ogre::Vector3& position); + void updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& direction); /** * Save the fog of war for the current cell to disk. diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6b7c21e51..e2aea19c6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -141,7 +141,7 @@ void RenderingManager::update (float duration){ mRendering.update(duration); - mLocalMap->setPlayerPosition( mRendering.getCamera()->getRealPosition() ); + mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealDirection() ); } void RenderingManager::skyEnable () From ce63d29d4abac02025cb2afd44ef4744d444b640 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 23 Mar 2012 15:26:24 +0100 Subject: [PATCH 109/152] rotate hud player arrow --- apps/openmw/mwgui/layouts.cpp | 10 ++++++++++ apps/openmw/mwgui/layouts.hpp | 2 ++ apps/openmw/mwgui/window_manager.cpp | 1 + files/mygui/openmw_hud_layout.xml | 2 +- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index ebabc6faf..be8c0db17 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -142,3 +142,13 @@ void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& v } } } + +void HUD::setPlayerDir(const float x, const float y) +{ + MyGUI::ISubWidget* main = compass->getSubWidgetMain(); + MyGUI::RotatingSkin* rotatingSubskin = main->castType(); + rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); + float angle = std::atan2(x,y); + rotatingSubskin->setAngle(angle); +} + diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 7ae28b4d8..240406018 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -46,6 +46,8 @@ namespace MWGui void setTriangleCount(size_t count); void setBatchCount(size_t count); + void setPlayerDir(const float x, const float y); + MyGUI::ProgressPtr health, magicka, stamina; MyGUI::ImageBox *weapImage, *spellImage; MyGUI::ProgressPtr weapStatus, spellStatus; diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index a263ee2a0..5137a109f 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -433,4 +433,5 @@ void WindowManager::setPlayerPos(const float x, const float y) void WindowManager::setPlayerDir(const float x, const float y) { map->setPlayerDir(x,y); + hud->setPlayerDir(x,y); } diff --git a/files/mygui/openmw_hud_layout.xml b/files/mygui/openmw_hud_layout.xml index 86f4df172..56708eb18 100644 --- a/files/mygui/openmw_hud_layout.xml +++ b/files/mygui/openmw_hud_layout.xml @@ -40,7 +40,7 @@ align="Right Bottom"> - From 789fbb460fe5191c316ff80753f03296a7b2390e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 23 Mar 2012 15:34:54 +0100 Subject: [PATCH 110/152] move implementation to .cpp --- apps/openmw/mwgui/layouts.cpp | 124 +++++++++++++++++++++++++++++++ apps/openmw/mwgui/layouts.hpp | 135 +++------------------------------- 2 files changed, 136 insertions(+), 123 deletions(-) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index be8c0db17..195297260 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -152,3 +152,127 @@ void HUD::setPlayerDir(const float x, const float y) rotatingSubskin->setAngle(angle); } +MapWindow::MapWindow() + : Layout("openmw_map_window_layout.xml"), mGlobal(false) +{ + setCoord(500,0,320,300); + setText("WorldButton", "World"); + setImage("Compass", "textures\\compass.dds"); + + // Obviously you should override this later on + setCellName("No Cell Loaded"); + + getWidget(mLocalMap, "LocalMap"); + getWidget(mGlobalMap, "GlobalMap"); + getWidget(mPlayerArrow, "Compass"); + + getWidget(mButton, "WorldButton"); + mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); + + MyGUI::Button* eventbox; + getWidget(eventbox, "EventBox"); + eventbox->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); + eventbox->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); +} + +void MapWindow::setVisible(bool b) +{ + mMainWidget->setVisible(b); + if (b) + mVisible = true; + else + mVisible = false; +} + +void MapWindow::setCellName(const std::string& cellName) +{ + static_cast(mMainWidget)->setCaption(cellName); +} + +void MapWindow::setCellPrefix(const std::string& prefix) +{ + mPrefix = prefix; +} + +void MapWindow::setActiveCell(const int x, const int y, bool interior) +{ + if (x==mCurX && y==mCurY && mInterior==interior) return; // don't do anything if we're still in the same cell + for (int mx=0; mx<3; ++mx) + { + for (int my=0; my<3; ++my) + { + std::string name = "Map_" + boost::lexical_cast(mx) + "_" + + boost::lexical_cast(my); + + std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" + + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); + + if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) + setImage(name, image); + else + setImage(name, "black.png"); + + if (MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) + setImage(name+"_fog", image+"_fog"); + else + setImage(name+"_fog", "black.png"); + } + } + mInterior = interior; + mCurX = x; + mCurY = y; +} + +void MapWindow::setPlayerPos(const float x, const float y) +{ + if (mGlobal || mVisible) return; + MyGUI::IntSize size = mLocalMap->getCanvasSize(); + MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height); + MyGUI::IntCoord viewsize = mLocalMap->getCoord(); + MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); + mLocalMap->setViewOffset(pos); + + mPlayerArrow->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); +} + +void MapWindow::setPlayerDir(const float x, const float y) +{ + if (!mVisible) return; + MyGUI::ISubWidget* main = mPlayerArrow->getSubWidgetMain(); + MyGUI::RotatingSkin* rotatingSubskin = main->castType(); + rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); + float angle = std::atan2(x,y); + rotatingSubskin->setAngle(angle); +} + +void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) +{ + if (_id!=MyGUI::MouseButton::Left) return; + if (!mGlobal) + mLastDragPos = MyGUI::IntPoint(_left, _top); +} + +void MapWindow::onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) +{ + if (_id!=MyGUI::MouseButton::Left) return; + + if (!mGlobal) + { + MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; + mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff ); + + mLastDragPos = MyGUI::IntPoint(_left, _top); + } +} + +void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender) +{ + mGlobal = !mGlobal; + mGlobalMap->setVisible(mGlobal); + mLocalMap->setVisible(!mGlobal); + + mButton->setCaption( mGlobal ? "Local" : "World" ); +} + + + diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 240406018..3fa759bf2 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -66,130 +66,20 @@ namespace MWGui class MapWindow : public OEngine::GUI::Layout { public: - MapWindow() - : Layout("openmw_map_window_layout.xml"), mGlobal(false) - { - setCoord(500,0,320,300); - setText("WorldButton", "World"); - setImage("Compass", "textures\\compass.dds"); - - // Obviously you should override this later on - setCellName("No Cell Loaded"); - - getWidget(mLocalMap, "LocalMap"); - getWidget(mGlobalMap, "GlobalMap"); - getWidget(mPlayerArrow, "Compass"); - - getWidget(mButton, "WorldButton"); - mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); - - MyGUI::Button* eventbox; - getWidget(eventbox, "EventBox"); - eventbox->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); - eventbox->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); - } - - void setVisible(bool b) - { - mMainWidget->setVisible(b); - if (b) - mVisible = true; - else - mVisible = false; - } - - void setCellName(const std::string& cellName) - { - static_cast(mMainWidget)->setCaption(cellName); - } - - // for interiors: cell name, for exteriors: "Cell" - void setCellPrefix(const std::string& prefix) - { - mPrefix = prefix; - } - - void setActiveCell(const int x, const int y, bool interior=false) - { - if (x==mCurX && y==mCurY && mInterior==interior) return; // don't do anything if we're still in the same cell - for (int mx=0; mx<3; ++mx) - { - for (int my=0; my<3; ++my) - { - std::string name = "Map_" + boost::lexical_cast(mx) + "_" - + boost::lexical_cast(my); - - std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" - + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); - - if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) - setImage(name, image); - else - setImage(name, "black.png"); - - if (MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) - setImage(name+"_fog", image+"_fog"); - else - setImage(name+"_fog", "black.png"); - } - } - mInterior = interior; - mCurX = x; - mCurY = y; - } - - void setPlayerPos(const float x, const float y) - { - if (mGlobal || mVisible) return; - MyGUI::IntSize size = mLocalMap->getCanvasSize(); - MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height); - MyGUI::IntCoord viewsize = mLocalMap->getCoord(); - MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); - mLocalMap->setViewOffset(pos); - - mPlayerArrow->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); - } - - void setPlayerDir(const float x, const float y) - { - if (!mVisible) return; - MyGUI::ISubWidget* main = mPlayerArrow->getSubWidgetMain(); - MyGUI::RotatingSkin* rotatingSubskin = main->castType(); - rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); - float angle = std::atan2(x,y); - rotatingSubskin->setAngle(angle); - } - - void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) - { - if (_id!=MyGUI::MouseButton::Left) return; - if (!mGlobal) - mLastDragPos = MyGUI::IntPoint(_left, _top); - } - - void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) - { - if (_id!=MyGUI::MouseButton::Left) return; - - if (!mGlobal) - { - MyGUI::IntPoint diff = MyGUI::IntPoint(_left, _top) - mLastDragPos; - mLocalMap->setViewOffset( mLocalMap->getViewOffset() + diff ); - - mLastDragPos = MyGUI::IntPoint(_left, _top); - } - } - - void onWorldButtonClicked(MyGUI::Widget* _sender) - { - mGlobal = !mGlobal; - mGlobalMap->setVisible(mGlobal); - mLocalMap->setVisible(!mGlobal); - - mButton->setCaption( mGlobal ? "Local" : "World" ); - } + MapWindow(); + void setVisible(bool b); + void setCellName(const std::string& cellName); + void setCellPrefix(const std::string& prefix); + void setActiveCell(const int x, const int y, bool interior=false); + void setPlayerPos(const float x, const float y); + void setPlayerDir(const float x, const float y); + private: + void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); + void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); + void onWorldButtonClicked(MyGUI::Widget* _sender); + std::string mPrefix; MyGUI::ScrollView* mLocalMap; MyGUI::ScrollView* mGlobalMap; @@ -199,7 +89,6 @@ namespace MWGui int mCurX, mCurY; bool mInterior; bool mVisible; - bool mGlobal; }; From a1f80e029edff28c3a0464369b5e2c09bc4ba881 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 23 Mar 2012 16:16:31 +0100 Subject: [PATCH 111/152] abstracted some code to be reused for hud --- apps/openmw/mwgui/layouts.cpp | 79 ++++++++++++++++++++--------------- apps/openmw/mwgui/layouts.hpp | 30 ++++++++----- 2 files changed, 65 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index 195297260..57004906e 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -173,6 +173,8 @@ MapWindow::MapWindow() getWidget(eventbox, "EventBox"); eventbox->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); eventbox->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + + LocalMapBase::init(mLocalMap, this); } void MapWindow::setVisible(bool b) @@ -189,40 +191,6 @@ void MapWindow::setCellName(const std::string& cellName) static_cast(mMainWidget)->setCaption(cellName); } -void MapWindow::setCellPrefix(const std::string& prefix) -{ - mPrefix = prefix; -} - -void MapWindow::setActiveCell(const int x, const int y, bool interior) -{ - if (x==mCurX && y==mCurY && mInterior==interior) return; // don't do anything if we're still in the same cell - for (int mx=0; mx<3; ++mx) - { - for (int my=0; my<3; ++my) - { - std::string name = "Map_" + boost::lexical_cast(mx) + "_" - + boost::lexical_cast(my); - - std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" - + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); - - if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) - setImage(name, image); - else - setImage(name, "black.png"); - - if (MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) - setImage(name+"_fog", image+"_fog"); - else - setImage(name+"_fog", "black.png"); - } - } - mInterior = interior; - mCurX = x; - mCurY = y; -} - void MapWindow::setPlayerPos(const float x, const float y) { if (mGlobal || mVisible) return; @@ -274,5 +242,48 @@ void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender) mButton->setCaption( mGlobal ? "Local" : "World" ); } +void LocalMapBase::init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout) +{ + mLocalMap = widget; + mLayout = layout; +} + +void LocalMapBase::setCellPrefix(const std::string& prefix) +{ + mPrefix = prefix; +} +void LocalMapBase::setActiveCell(const int x, const int y, bool interior) +{ + if (x==mCurX && y==mCurY && mInterior==interior) return; // don't do anything if we're still in the same cell + for (int mx=0; mx<3; ++mx) + { + for (int my=0; my<3; ++my) + { + std::string name = "Map_" + boost::lexical_cast(mx) + "_" + + boost::lexical_cast(my); + + std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" + + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); + + MyGUI::ImageBox* box; + mLayout->getWidget(box, name); + MyGUI::ImageBox* fog; + mLayout->getWidget(fog, name+"_fog"); + + if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) + box->setImageTexture(image); + else + box->setImageTexture("black.png"); + + if (MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) + fog->setImageTexture(image+"_fog"); + else + fog->setImageTexture("black.png"); + } + } + mInterior = interior; + mCurX = x; + mCurY = y; +} diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 3fa759bf2..8029dabe7 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -31,7 +31,24 @@ namespace MWGui { - class HUD : public OEngine::GUI::Layout + class LocalMapBase + { + public: + void init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout); + + void setCellPrefix(const std::string& prefix); + void setActiveCell(const int x, const int y, bool interior=false); + + protected: + int mCurX, mCurY; + bool mInterior; + MyGUI::ScrollView* mLocalMap; + std::string mPrefix; + + OEngine::GUI::Layout* mLayout; + }; + + class HUD : public OEngine::GUI::Layout, public LocalMapBase { public: HUD(int width, int height, int fpsLevel); @@ -45,7 +62,6 @@ namespace MWGui void setFPS(float fps); void setTriangleCount(size_t count); void setBatchCount(size_t count); - void setPlayerDir(const float x, const float y); MyGUI::ProgressPtr health, magicka, stamina; @@ -63,31 +79,25 @@ namespace MWGui MyGUI::TextBox* batchcounter; }; - class MapWindow : public OEngine::GUI::Layout + class MapWindow : public OEngine::GUI::Layout, public LocalMapBase { public: MapWindow(); void setVisible(bool b); - void setCellName(const std::string& cellName); - void setCellPrefix(const std::string& prefix); - void setActiveCell(const int x, const int y, bool interior=false); void setPlayerPos(const float x, const float y); void setPlayerDir(const float x, const float y); + void setCellName(const std::string& cellName); private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onWorldButtonClicked(MyGUI::Widget* _sender); - std::string mPrefix; - MyGUI::ScrollView* mLocalMap; MyGUI::ScrollView* mGlobalMap; MyGUI::ImageBox* mPlayerArrow; MyGUI::Button* mButton; MyGUI::IntPoint mLastDragPos; - int mCurX, mCurY; - bool mInterior; bool mVisible; bool mGlobal; }; From 7a3034701fb488472da92a95a40a0a1920dbf9e9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 23 Mar 2012 16:51:56 +0100 Subject: [PATCH 112/152] functional HUD map --- apps/openmw/mwgui/layouts.cpp | 13 ++++++++ apps/openmw/mwgui/layouts.hpp | 3 +- apps/openmw/mwgui/window_manager.cpp | 13 ++++++-- files/mygui/openmw_hud_layout.xml | 46 +++++++++++++++++++++++++--- 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index 57004906e..34d0cf1b6 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -61,6 +61,8 @@ HUD::HUD(int width, int height, int fpsLevel) setSpellIcon("icons\\s\\b_tx_s_rstor_health.dds"); setSpellStatus(65, 100); setEffect("icons\\s\\tx_s_chameleon.dds"); + + LocalMapBase::init(minimap, this); } void HUD::setFPS(float fps) @@ -152,6 +154,17 @@ void HUD::setPlayerDir(const float x, const float y) rotatingSubskin->setAngle(angle); } +void HUD::setPlayerPos(const float x, const float y) +{ + MyGUI::IntSize size = minimap->getCanvasSize(); + MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height); + MyGUI::IntCoord viewsize = minimap->getCoord(); + MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); + + minimap->setViewOffset(pos); + compass->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); +} + MapWindow::MapWindow() : Layout("openmw_map_window_layout.xml"), mGlobal(false) { diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 8029dabe7..6601850ca 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -63,13 +63,14 @@ namespace MWGui void setTriangleCount(size_t count); void setBatchCount(size_t count); void setPlayerDir(const float x, const float y); + void setPlayerPos(const float x, const float y); MyGUI::ProgressPtr health, magicka, stamina; MyGUI::ImageBox *weapImage, *spellImage; MyGUI::ProgressPtr weapStatus, spellStatus; MyGUI::WidgetPtr effectBox; MyGUI::ImageBox* effect1; - MyGUI::ImageBox* minimap; + MyGUI::ScrollView* minimap; MyGUI::ImageBox* compass; MyGUI::ImageBox* crosshair; diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 5137a109f..fa6dedc77 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -405,17 +405,24 @@ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) { if (!(cell->cell->data.flags & ESM::Cell::Interior)) { + std::string name; if (cell->cell->name != "") - map->setCellName( cell->cell->name ); + name = cell->cell->name; else - map->setCellName( cell->cell->region ); + name = cell->cell->region; + + map->setCellName( name ); + map->setCellPrefix("Cell"); + hud->setCellPrefix("Cell"); map->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY ); + hud->setActiveCell( cell->cell->data.gridX, cell->cell->data.gridY ); } else { map->setCellName( cell->cell->name ); map->setCellPrefix( cell->cell->name ); + hud->setCellPrefix( cell->cell->name ); } } @@ -423,11 +430,13 @@ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) void WindowManager::setInteriorMapTexture(const int x, const int y) { map->setActiveCell(x,y, true); + hud->setActiveCell(x,y, true); } void WindowManager::setPlayerPos(const float x, const float y) { map->setPlayerPos(x,y); + hud->setPlayerPos(x,y); } void WindowManager::setPlayerDir(const float x, const float y) diff --git a/files/mygui/openmw_hud_layout.xml b/files/mygui/openmw_hud_layout.xml index 56708eb18..20370770e 100644 --- a/files/mygui/openmw_hud_layout.xml +++ b/files/mygui/openmw_hud_layout.xml @@ -38,10 +38,48 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a6259a1b0d83fef93d49ec96638853c525f80ad5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 23 Mar 2012 17:37:56 +0100 Subject: [PATCH 113/152] bugfixes --- apps/openmw/mwgui/layouts.cpp | 4 +++- apps/openmw/mwgui/layouts.hpp | 1 + apps/openmw/mwrender/localmap.cpp | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index 34d0cf1b6..5c5a977d3 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -264,11 +264,12 @@ void LocalMapBase::init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout) void LocalMapBase::setCellPrefix(const std::string& prefix) { mPrefix = prefix; + mChanged = true; } void LocalMapBase::setActiveCell(const int x, const int y, bool interior) { - if (x==mCurX && y==mCurY && mInterior==interior) return; // don't do anything if we're still in the same cell + if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell for (int mx=0; mx<3; ++mx) { for (int my=0; my<3; ++my) @@ -298,5 +299,6 @@ void LocalMapBase::setActiveCell(const int x, const int y, bool interior) mInterior = interior; mCurX = x; mCurY = y; + mChanged = false; } diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp index 6601850ca..8d9a41a22 100644 --- a/apps/openmw/mwgui/layouts.hpp +++ b/apps/openmw/mwgui/layouts.hpp @@ -44,6 +44,7 @@ namespace MWGui bool mInterior; MyGUI::ScrollView* mLocalMap; std::string mPrefix; + bool mChanged; OEngine::GUI::Layout* mLayout; }; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 83fe40166..b83a98220 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -153,8 +153,9 @@ void LocalMap::render(const float x, const float y, // make everything visible mRendering->getScene()->setAmbientLight(ColourValue(1,1,1)); - mCellCamera->setPosition(Vector3(x, zhigh, y)); - mCellCamera->setFarClipDistance( (zhigh-zlow) * 1.1 ); + mCellCamera->setPosition(Vector3(x, zhigh+100000, y)); + //mCellCamera->setFarClipDistance( (zhigh-zlow) * 1.1 ); + mCellCamera->setFarClipDistance(0); // infinite mCellCamera->setOrthoWindow(xw, yw); From 6d33ad248d49258342cd7f56efa2adf64b414f11 Mon Sep 17 00:00:00 2001 From: Roman Melnik Date: Fri, 23 Mar 2012 22:08:02 +0200 Subject: [PATCH 114/152] Fix memory leak in getIdentity method Replace static pointer and new() operator with normal static variable --- components/nif/nif_types.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/components/nif/nif_types.hpp b/components/nif/nif_types.hpp index ce7a0de62..83f912bfd 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/nif_types.hpp @@ -62,17 +62,18 @@ struct Transformation static const Transformation* getIdentity() { - static Transformation* identity = NULL; - if (NULL == identity) + static Transformation identity; + static bool iset = false; + if (!iset) { - identity = new Transformation(); - identity->scale = 1.0f; - identity->rotation.v[0].array[0] = 1.0f; - identity->rotation.v[1].array[1] = 1.0f; - identity->rotation.v[2].array[2] = 1.0f; + identity.scale = 1.0f; + identity.rotation.v[0].array[0] = 1.0f; + identity.rotation.v[1].array[1] = 1.0f; + identity.rotation.v[2].array[2] = 1.0f; + iset = true; } - return identity; + return &identity; } }; #pragma pack(pop) From e6c55c144c7603a52e76cb814331f462b16f66fd Mon Sep 17 00:00:00 2001 From: Roman Melnik Date: Fri, 23 Mar 2012 22:15:45 +0200 Subject: [PATCH 115/152] Fix tabulation/formatting --- components/nif/nif_types.hpp | 40 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/components/nif/nif_types.hpp b/components/nif/nif_types.hpp index 83f912bfd..ee796cc99 100644 --- a/components/nif/nif_types.hpp +++ b/components/nif/nif_types.hpp @@ -55,26 +55,26 @@ struct Matrix struct Transformation { - Vector pos; - Matrix rotation; - float scale; - Vector velocity; - - static const Transformation* getIdentity() - { - static Transformation identity; - static bool iset = false; - if (!iset) - { - identity.scale = 1.0f; - identity.rotation.v[0].array[0] = 1.0f; - identity.rotation.v[1].array[1] = 1.0f; - identity.rotation.v[2].array[2] = 1.0f; - iset = true; - } - - return &identity; - } + Vector pos; + Matrix rotation; + float scale; + Vector velocity; + + static const Transformation* getIdentity() + { + static Transformation identity; + static bool iset = false; + if (!iset) + { + identity.scale = 1.0f; + identity.rotation.v[0].array[0] = 1.0f; + identity.rotation.v[1].array[1] = 1.0f; + identity.rotation.v[2].array[2] = 1.0f; + iset = true; + } + + return &identity; + } }; #pragma pack(pop) From fa70a72b75690f095fb0bf84576245c274ba8742 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 24 Mar 2012 00:22:54 -0700 Subject: [PATCH 116/152] Remove some unused sound manager fields and constructor parameters --- apps/openmw/engine.cpp | 5 +---- apps/openmw/mwsound/soundmanager.cpp | 12 ++++-------- apps/openmw/mwsound/soundmanager.hpp | 12 ++---------- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 57723cac9..5e49ae2f7 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -333,10 +333,7 @@ void OMW::Engine::go() mExtensions, mFpsLevel, mNewGame, mOgre, mCfgMgr.getLogPath().string() + std::string("/")); // Create sound system - mEnvironment.mSoundManager = new MWSound::SoundManager(mOgre->getRoot(), - mOgre->getCamera(), - mDataDirs, - mUseSound, mFSStrict, mEnvironment); + mEnvironment.mSoundManager = new MWSound::SoundManager(mUseSound, mEnvironment); // Create script system mScriptContext = new MWScript::CompilerContext (MWScript::CompilerContext::Type_Full, diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 38b9bee25..272956082 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -38,10 +38,8 @@ namespace MWSound { - SoundManager::SoundManager(Ogre::Root *root, Ogre::Camera *camera, - const Files::PathContainer& dataDirs, - bool useSound, bool fsstrict, MWWorld::Environment& environment) - : mFSStrict(fsstrict) + SoundManager::SoundManager(bool useSound, MWWorld::Environment& environment) + : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) , mEnvironment(environment) { if(!useSound) @@ -67,8 +65,6 @@ namespace MWSound mOutput.reset(); return; } - - mResourceMgr = Ogre::ResourceGroupManager::getSingletonPtr(); } SoundManager::~SoundManager() @@ -161,8 +157,8 @@ namespace MWSound void SoundManager::startRandomTitle() { Ogre::StringVectorPtr filelist; - filelist = mResourceMgr->findResourceNames(Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - "Music/"+mCurrentPlaylist+"/*"); + filelist = mResourceMgr.findResourceNames(Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "Music/"+mCurrentPlaylist+"/*"); if(!filelist->size()) return; diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 433f2c169..aef4a8ace 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -31,13 +31,7 @@ namespace MWSound class SoundManager { - Ogre::ResourceGroupManager *mResourceMgr; - - // This is used for case insensitive and slash-type agnostic file - // finding. It takes DOS paths (any case, \\ slashes or / slashes) - // relative to the sound dir, and translates them into full paths - // of existing files in the filesystem, if they exist. - bool mFSStrict; + Ogre::ResourceGroupManager& mResourceMgr; MWWorld::Environment& mEnvironment; @@ -64,9 +58,7 @@ namespace MWSound friend class OpenAL_Output; public: - SoundManager(Ogre::Root*, Ogre::Camera*, - const Files::PathContainer& dataDir, bool useSound, bool fsstrict, - MWWorld::Environment& environment); + SoundManager(bool useSound, MWWorld::Environment& environment); ~SoundManager(); void stopMusic(); From 5cb90ab7044299fddad6028af3b2ab3985cd402a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 24 Mar 2012 03:49:03 -0700 Subject: [PATCH 117/152] Add some dummy copy constructors and assignment operators to prevent implicit versions from being used --- apps/openmw/mwsound/ffmpeg_decoder.hpp | 3 +++ apps/openmw/mwsound/mpgsnd_decoder.hpp | 3 +++ apps/openmw/mwsound/openal_output.cpp | 11 +++++++++++ apps/openmw/mwsound/openal_output.hpp | 3 +++ apps/openmw/mwsound/sound.hpp | 4 ++++ apps/openmw/mwsound/sound_decoder.hpp | 4 ++++ apps/openmw/mwsound/sound_output.hpp | 3 +++ apps/openmw/mwsound/soundmanager.hpp | 3 +++ 8 files changed, 34 insertions(+) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index ae71c0052..4344397c7 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -42,6 +42,9 @@ namespace MWSound virtual void readAll(std::vector &output); virtual void rewind(); + FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); + FFmpeg_Decoder(const FFmpeg_Decoder &rhs); + FFmpeg_Decoder(); public: virtual ~FFmpeg_Decoder(); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp index 1d9e9d5e2..870773edc 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.hpp @@ -40,6 +40,9 @@ namespace MWSound virtual void readAll(std::vector &output); virtual void rewind(); + MpgSnd_Decoder& operator=(const MpgSnd_Decoder &rhs); + MpgSnd_Decoder(const MpgSnd_Decoder &rhs); + MpgSnd_Decoder(); public: virtual ~MpgSnd_Decoder(); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 5e61cb94f..a0b9c3a72 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -76,6 +76,9 @@ class OpenAL_SoundStream : public Sound volatile bool mIsFinished; + OpenAL_SoundStream(const OpenAL_SoundStream &rhs); + OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); + public: OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); virtual ~OpenAL_SoundStream(); @@ -148,6 +151,10 @@ struct OpenAL_Output::StreamThread { mStreams.clear(); mMutex.unlock(); } + +private: + StreamThread(const StreamThread &rhs); + StreamThread& operator=(const StreamThread &rhs); }; @@ -308,6 +315,10 @@ class OpenAL_Sound : public Sound ALuint mSource; ALuint mBuffer; + + OpenAL_Sound(const OpenAL_Sound &rhs); + OpenAL_Sound& operator=(const OpenAL_Sound &rhs); + public: OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf); virtual ~OpenAL_Sound(); diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 33ab7a2aa..e8154e906 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -52,6 +52,9 @@ namespace MWSound virtual void updateListener(const float *pos, const float *atdir, const float *updir); + OpenAL_Output& operator=(const OpenAL_Output &rhs); + OpenAL_Output(const OpenAL_Output &rhs); + OpenAL_Output(SoundManager &mgr); virtual ~OpenAL_Output(); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 84725464b..f9e7ab427 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -9,7 +9,11 @@ namespace MWSound virtual bool isPlaying() = 0; virtual void update(const float *pos) = 0; + Sound& operator=(const Sound &rhs); + Sound(const Sound &rhs); + public: + Sound() { } virtual ~Sound() { } friend class OpenAL_Output; diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index e076c7b56..9c28d5ff5 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -38,6 +38,10 @@ namespace MWSound Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) { } virtual ~Sound_Decoder() { } + + private: + Sound_Decoder(const Sound_Decoder &rhs); + Sound_Decoder& operator=(const Sound_Decoder &rhs); }; } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 14b61e609..1722165e4 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -29,6 +29,9 @@ namespace MWSound virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; + Sound_Output& operator=(const Sound_Output &rhs); + Sound_Output(const Sound_Output &rhs); + Sound_Output(SoundManager &mgr) : mManager(mgr) { } public: virtual ~Sound_Output() { } diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index aef4a8ace..b7c883a13 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -53,6 +53,9 @@ namespace MWSound void updateSounds(float duration); void updateRegionSound(float duration); + SoundManager(const SoundManager &rhs); + SoundManager& operator=(const SoundManager &rhs); + protected: DecoderPtr getDecoder(); friend class OpenAL_Output; From 71d3f9bd512edc078272468e1243e2deaee1bb0e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 24 Mar 2012 08:12:04 -0700 Subject: [PATCH 118/152] Get the object reference's position once when updating its sounds --- apps/openmw/mwsound/soundmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 272956082..3b1f188e7 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -308,10 +308,10 @@ namespace MWSound if(snditer == mActiveSounds.end()) return; + const ESM::Position &pos = ptr.getCellRef().pos; IDMap::iterator iditer = snditer->second.begin(); while(iditer != snditer->second.end()) { - const ESM::Position &pos = ptr.getCellRef().pos; iditer->second->update(pos.pos); iditer++; } From 155cd76f3733f1b17596bf556d5c9cb68e78d2b1 Mon Sep 17 00:00:00 2001 From: Pieter van der Kloet Date: Sat, 24 Mar 2012 20:43:35 +0100 Subject: [PATCH 119/152] Changed Launcher font to EB Garamond and added license info --- Bitstream Vera License.txt | 123 ++++++++++++++++++ OFL.txt | 93 +++++++++++++ apps/launcher/maindialog.cpp | 21 ++- .../resources/images/openmw-header.png | Bin 45163 -> 50727 bytes files/launcher.qss | 6 +- files/mygui/CMakeLists.txt | 2 +- files/mygui/EBGaramond-Regular.ttf | Bin 0 -> 231904 bytes readme.txt | 4 + 8 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 Bitstream Vera License.txt create mode 100644 OFL.txt create mode 100644 files/mygui/EBGaramond-Regular.ttf diff --git a/Bitstream Vera License.txt b/Bitstream Vera License.txt new file mode 100644 index 000000000..2b37cc1df --- /dev/null +++ b/Bitstream Vera License.txt @@ -0,0 +1,123 @@ +Bitstream Vera Fonts Copyright + +The fonts have a generous copyright, allowing derivative works (as +long as "Bitstream" or "Vera" are not in the names), and full +redistribution (so long as they are not *sold* by themselves). They +can be be bundled, redistributed and sold with any software. + +The fonts are distributed under the following copyright: + +Copyright +========= + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream +Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and associated +documentation files (the "Font Software"), to reproduce and distribute +the Font Software, including without limitation the rights to use, +copy, merge, publish, distribute, and/or sell copies of the Font +Software, and to permit persons to whom the Font Software is furnished +to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words "Bitstream" or the word "Vera". + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +"Bitstream Vera" names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, +OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT +SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font +Software without prior written authorization from the Gnome Foundation +or Bitstream Inc., respectively. For further information, contact: +fonts at gnome dot org. + +Copyright FAQ +============= + + 1. I don't understand the resale restriction... What gives? + + Bitstream is giving away these fonts, but wishes to ensure its + competitors can't just drop the fonts as is into a font sale system + and sell them as is. It seems fair that if Bitstream can't make money + from the Bitstream Vera fonts, their competitors should not be able to + do so either. You can sell the fonts as part of any software package, + however. + + 2. I want to package these fonts separately for distribution and + sale as part of a larger software package or system. Can I do so? + + Yes. A RPM or Debian package is a "larger software package" to begin + with, and you aren't selling them independently by themselves. + See 1. above. + + 3. Are derivative works allowed? + Yes! + + 4. Can I change or add to the font(s)? + Yes, but you must change the name(s) of the font(s). + + 5. Under what terms are derivative works allowed? + + You must change the name(s) of the fonts. This is to ensure the + quality of the fonts, both to protect Bitstream and Gnome. We want to + ensure that if an application has opened a font specifically of these + names, it gets what it expects (though of course, using fontconfig, + substitutions could still could have occurred during font + opening). You must include the Bitstream copyright. Additional + copyrights can be added, as per copyright law. Happy Font Hacking! + + 6. If I have improvements for Bitstream Vera, is it possible they might get + adopted in future versions? + + Yes. The contract between the Gnome Foundation and Bitstream has + provisions for working with Bitstream to ensure quality additions to + the Bitstream Vera font family. Please contact us if you have such + additions. Note, that in general, we will want such additions for the + entire family, not just a single font, and that you'll have to keep + both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add + glyphs to the font, they must be stylistically in keeping with Vera's + design. Vera cannot become a "ransom note" font. Jim Lyles will be + providing a document describing the design elements used in Vera, as a + guide and aid for people interested in contributing to Vera. + + 7. I want to sell a software package that uses these fonts: Can I do so? + + Sure. Bundle the fonts with your software and sell your software + with the fonts. That is the intent of the copyright. + + 8. If applications have built the names "Bitstream Vera" into them, + can I override this somehow to use fonts of my choosing? + + This depends on exact details of the software. Most open source + systems and software (e.g., Gnome, KDE, etc.) are now converting to + use fontconfig (see www.fontconfig.org) to handle font configuration, + selection and substitution; it has provisions for overriding font + names and subsituting alternatives. An example is provided by the + supplied local.conf file, which chooses the family Bitstream Vera for + "sans", "serif" and "monospace". Other software (e.g., the XFree86 + core server) has other mechanisms for font substitution. diff --git a/OFL.txt b/OFL.txt new file mode 100644 index 000000000..619d1f429 --- /dev/null +++ b/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2010, 2011 Georg Duffner (http://www.georgduffner.at) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 49c0bd960..ef9cfa851 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -45,9 +45,28 @@ MainDialog::MainDialog() setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); setMinimumSize(QSize(575, 575)); + // Install the stylesheet font + QFile file; + QFontDatabase fontDatabase; + + const QStringList fonts = fontDatabase.families(); + + // Check if the font is installed + if (!fonts.contains("EB Garamond")) { + + QString font = QString::fromStdString((mCfgMgr.getGlobalDataPath() / "resources/mygui/EBGaramond-Regular.ttf").string()); + file.setFileName(font); + + if (!file.exists()) { + font = QString::fromStdString((mCfgMgr.getLocalPath() / "resources/mygui/EBGaramond-Regular.ttf").string()); + } + + fontDatabase.addApplicationFont(font); + } + // Load the stylesheet QString config = QString::fromStdString((mCfgMgr.getGlobalDataPath() / "resources/launcher.qss").string()); - QFile file(config); + file.setFileName(config); if (!file.exists()) { file.setFileName(QString::fromStdString((mCfgMgr.getLocalPath() / "launcher.qss").string())); diff --git a/apps/launcher/resources/images/openmw-header.png b/apps/launcher/resources/images/openmw-header.png index a168d4d2a816cb816dd50eb9ff1961291c2fe899..a2ffab68b8b447193d1153950e5c4d7f43348641 100644 GIT binary patch literal 50727 zcmV)UK(N1wP)|D^_ww@lRz|vCuzLs)$;-`! zo*{AqUjza0dRV*yaMRE;fKCVhpQKsoe1Yhg01=zBIT!& zC1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz*<$V$@wQAMN5qJ)hzm2hoGcOF60t^# zFqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su;^hF~NC{tY+*d5%WDCTXa!E_i;d2ub z1#}&jF5T4HnnCyEWTkKf0>c0%E1Ah>(_PY1)0w;+02c53Su*0<(nUqKG_|(0G&D0Z z{i;y^b@OjZ+}lNZ8Th$p5Uu}MTtq^NHl z*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*PvzoDiKdLpOAxi2$L0#SX*@cY_n(^h55xYX z#km%V()bZjV~l{*bt*u9?FT3d5g^g~#a;iSZ@&02Abxq_DwB(I|L-^bXThc7C4-yr zInE_0gw7K3GZ**7&k~>k0Z0NWkO#^@9q0fwx1%qjZ=)yBuQ3=5 z4Wo^*!gyjLF-e%Um=erBOdIALW)L%unZshS@>qSW9o8Sq#0s#5*edK%>{;v(b^`kb zN5rY%%y90wC>#%$kE_5P!JWYk;U;klcqzOl-UjcFXXA75rT9jCH~u<)0>40zCTJ7v z2qAyk54cquI@7b&LHdZ`+zlTss6bJ7%PQ)z$cROu4wBhpu-r)01)S~6}jY?%U? zgEALn#wiFzo#H}aQ8rT=DHkadR18&{>P1bW7E`~Y4p3)hWn`DhhRJ5j*2tcg9i<^O zEt(fCg;q*CP8+7ZTcWhYX$fb^_9d-LhL+6BEtPYWVlfK zTBusSTASKKb%HuWJzl+By+?gkLq)?+BTu761jmyXF)a;mc z^>(B7bo*HQ1NNg1st!zt28YLv>W*y3CdWx9U8f|cqfXDAO`Q48?auQqHZJR2&bcD4 z9Ip>EY~kKEPV6Wm+eXFV)D)_R=tM0@&p?(!V*Qu1PXHG9o^TY0bZ?)4%0 z1p8F`JoeS|<@=<@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK8LKk71XR(_ zRKHM1xJ5XYX`fk>`6eqY>qNG6HZQwBM=xi4&Sb88?zd}EYguc1@>KIS<&CX#T35dw zS|7K*XM_5Nf(;WJJvJWRMA($P>8E^?{IdL4o5MGE7bq2MEEwP7v8AO@qL5!WvekBL z-8R%V?zVyL=G&{be=K4bT`e{#t|)$A!YaA?jp;X)-+bB;zhj`(vULAW%ue3U;av{9 z4wp%n<(7@__S@Z2PA@Mif3+uO&y|X06?J#o zSi8M;ejj_^(0<4Lt#wLu#dYrva1Y$6_o(k^&}yhSh&h;f@JVA>W8b%oZ=0JGnu?n~ z9O4}sJsfnnx7n(>`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8No_-(u{qS+0 z<2@%BCt82d{Gqm;(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-UsyQuty7Ua; zOu?B?XLHZaol8GAb3Wnxcu!2v{R_`T4=x`(GvqLI{-*2AOSimkUAw*F_TX^n z@STz9kDQ$NC=!KfXWC z8h`dn#xL(D3Z9UkR7|Q&Hcy#Notk!^zVUSB(}`#4&lYA1f0h2V_PNgUAAWQEt$#LR zcH#y9#i!p(Udq2b^lI6wp1FXzN3T;~FU%Lck$-deE#qz9yYP3D3t8{6?<+s(e(3(_ z^YOu_)K8!O1p}D#{JO;G(*OVf24YJ`L;wH)0002_L%V+f000SaNLh0L01FcU01FcV z0GgZ_00007bV*G`2iyY~6DAP5Ed4zI03ZNKL_t(|+T6W+yk*-}*ZCb|&b9VFr>bre zlJF80XapKjfq-Z}5dqOO5)lc{fCP*@e4vI_Kv0Ow@Q*RpKBsQgO-SqK6JjNG@2%SVtiASHYtAvo@An(O zA@1(HS$;YAuj*a*z29-<`s>%ff;)bCqsP0qzw`L**Z+;h>u&M7835*n+sO9~+y}Vi z@eQ*MX89UOewI5UMqv5#4?^+73 zib|Z_=nJY!(TQ#zpC!UrSH$C@ND04cQNT|_D_ zovBttE4>Yh7Cf($OhSHS%flr7uY+aGMW3+`*3n28-<9b>=kIfs? zw1e&pYjH1elnHRkRN}j@N8AP?6RHI*1;#+C;v)3cXvZVdJVO2e!Ksa}BsWq`#g4=uPAgcTd{48w{Pp)DOP6&1y; zgX|D7%o@Uks2~ER7Wx?A9d8|x9o;YE)9Ws`kppgucZ+ohh&T{yS<4*PRwm3_e5MPc z6&nk+?C8TVyF|+bbi^mdI3lH@GRM}^4bu*N#Ix#75g_-NJ~~xrx(%lN8KXC_!Fs$z zqyq+e7VFqLjvy4KIwN)Bc;`057JS6+DW#)QC}qdm7pl$-zofSV;yYA#dA(wfIxd%p z-DTn?E8uR(TTB4)>!IXPKX`i0^Z$NtFu4{qP65RY-14vN6hPbz&!EB1MoRL^i3w9zSE8RLjjfexkO7>p9vRP+9b#-F=k^s69^AnKrwNMX6S%e32r zZr9e(*yRueM36GY#qLIDq0}?PVokXYyeOqkjAfx#!K`CA_(ZKYuyKLlI292EZ-_)7 z@1s#_#TjvLydnzbf~sI+L3PI6&^pCqLkDrj+VEjGgK63``a+pzMz;*el~Q*&jb%Nc zV%Ug4T;+^z4FJ`OYs0#sI#H(@$H0*i_s;rYK0A-+qIaqk`dGn1OQr74v9aXt8vzlZ zP6bfb?3aGX*VL|{z703YZ)VM|Bn9d11WjtYG&6q(}OGY=`E3?GQqc&4Qy0**#( zq+mX9R;14OhtCHI3!+=e^YKt&k_QWZraPjicV!k`ofgfYNvL8StbFt~wPL+dX0s2xO7#Pm(nUDnOwY#d=;l0V6g;A90PPXlxMfj6Nu3#!WCW+*YPK(_1VW zyx=8+pm#v>S{uVM+Y&I<$Y33C&Fe7GId-i9VcC1VymE;?mKn z;<7_rSdX{a?QTSNRVp}Gccg-fB0e)B$D&ZL1-y!lhE7ui@B%Uf^AQWtIz=ajk9;O2 zg8$l4Ra9eRTl+#SmC-vYik1muAQYhC=Cr=Bn{QxQHA+>)CGM37odM?9u-Jf00gGp% z6lIuY@Se~CQl_XJxN@>#dp4_=xXp zI7OlY)>>ktvC8mo|6O%OtH_C9US51yuzG))&w@mP=L} zTk;s!KtNzwR%#{|_(Glclrk|!>=hzLA1jmIj6_zAwqBrR7wfxdJfGtDh=kMnf>c3u zigoWoZwtd$rV^D?TaQ3zT^n^eL+A*VK1QxJ$3#F1g#j5@M|ZSD%IQ7!Jl6@c9+i1t zsioqhQ~5Ri>l7s9K1}Nkgd4x_@8q6kbNxMXb#U>_{&UlhG%Kr=2v}y3eVZF1u{h#^ z^V5lHmhni*zaJ4`tR*0%2^nQYWD(_uMfr;esyp0{xri;Zl!3bqaTU=uu8bVNk9rv>|AT4B19N76Gp*f}kQGH8%XIA}%M|niUofp5N6M5Tn*} zrd`3kMJ1^fY{_R(7^745JfHQbj<?sghG@qj0_PZ@f{(@|I|hc-^Vs~?xGtDP#W&&_Ih`?L z1EmP9F9AVu#$XLd*+JZK5!zUxugudKLp#3n&2}MrC_sgAN9P0t^pWs;E#)p;@C&0XvS~Y5l-F-HP?^igS#>ZxgEf z$od_Os6fV8PeDm*B%?i_ckj+zCx+Jz)kY;?u-$GB9Ah+UQTDqTnCR`2sZ>UX?nW)~ ze8fEu$a#!`vBFpxnUq$U66V)8V+Haie5_yJ@cjWW)*IkJt{|+fDzSSYtav#q6pO0C zBfSo;;skF+c4pSIq5vCf?7<1+#G(uA+RRTGjg5}m8t_cQHnAwO+VBw=wH;9jeB50U z-yb;nevi)%*lR>KiX&PWt>Fwx*#(^D5r%b&p5bcf*p^Sjhods(#=K%G6seKvj9jED&<3JH?-v0}l@bf2S!C7@ zy|0MEqA;8Jl;EU%$?Q6_>@4amVK5&W+)DCTk~~74&Os|!!}?&* z5gDjg3}M>MjIrk8jx4&=8N9Qu3)3{^y&7?Uv_!B_rS%0JB_OH-%1rMo)EXJFEmT+5 zwIM|)qO9%6uAW0te8lEiw9tKx`dDi4I0H(ptm{fGQ&ch*OolP2r6LG@44g=qM1)c` zAYiHS)U*W{sP1B|*uZ3|yS5Gb#bG4jR36 zgc8q|8dYZyp)W_I2A>6nOYj$?M@8M1C7@dItlna!Eq7l>(VMu&b?yCjRDuK4l~QUX zY_`%Hc^!|D0BVgL#yk>{VW=xc!AHRMC84WkN08v1EQ9;6`3mF>RVI&JkjQ-jNA71% zt{ova*8n2oPd+kZmi)6_RTvx5Q7Uc&Gd;yHDUua|CG6F+N~wX{1HJb+o3RBF&qY2n z#xXKzm7oKd0`75-EKlPB7t<(d-Gfk7VV-YcUGiMwp-1#oW4R9)mJ394v1E{QV=M=> z*2s)=ppD|6XVryawC51|;|EjFrBL4)H7_@mN}mxxrPM3sT=C{i$AFY3mWy$X4gwKA;-8 zf@npYfiuk$(-1_@a92DbAJK}mNWA7UpPoN6ONd1%1@KNOnW(7dIW@#BIFm6V*(n8F zDa1YVgrlZuADg!%r{p1eC>r8~u^uUPj&+|%0J3qJv93x9E~w1A;1DdHkuvQWqvtx@ zv#tlQK~>2rMlzWkNu0+s6qW1Du(Y(aHOnC&jGLrY=p7Zg>LT?XjJZvO;^ zIo1c}Yo1e~)+yF%#KNe;v{TC-D-+h%(QWJU_LX|Lhl`^%3PmBJ1!I* zI02i!1yvyTS0Sg`#IL{hpbCU5mDPzv_j}3ndx-UXEwc_!CD!TZ%@en8`92RQW^9Dk zb}YFHAf6#Cxr^TKQtBDjJ3e{_p8UBa@uM0^NNKddTYZfTyc=4!s23ZV4Z|hXNY<0c zFHznTgr|>j1-~WHzg-iR2^DA1P)aN^>vRiJ4K0k`(5i^Rus9ddnZm@d#Ck^tt`cxv zKrc`zp=_#7NtKjaC|jH!0gcRXd!v>qux?r?fV9%uCA-}@qJbe>U(jg|xWOyk8-;+m z+&tD2QFeDfqB=(fB0_5`-Hue%Sk$`1?0}4pd&N8m>!?@)UmxR;D!M5$M|xy~Jrz@; z7|Bh!BLaPZsw2|`qr+&S&=C*F#SN$_K`J27+GVP^5_u#xopn7@YE)@s7-|8vN{u8& z%fz;%ORCNy_wiF=H9{8!#W7ox;}YB#M3q|4SleX;TG@!&sGx>9Mdv&(MT*dS$f9Z~ zs20|KpvoR8N@jnfH*9Pw ztl&tr#D?m_6RPg_XfnyXo<)eW8>&6O~5mODNYIhDAViTLKm( zpv*eW_*iihT%w|C%f*QuE;>ggvcd2*0;0OWfVM0siWQj3#PCp6wsk@5fR~CeGnPwK z22N0AE<`eD7jMY{#P6*u+#iEPzb4-$pSmQz?+qH3!NKkcE>TTf3pi#c^%b@SpY1(d zJH|M{L+%C%Y1u}&kA4MzNtTf2q3ZF5c&KFD$p>cR5K>a>osCA=6eY0$HA%?!sGuo% zc@hRo)t6gjL>rl1%Y1b`)fp~9xP_QExQDF*)Zkho$j#U|L_pJ!VoGB1W&zU$**qvx zgS20d5n$NV$dM6Csn|&Mj+Z!|M|o~z9Oe?66C#Lx8jbN4~5iuZvDu^C7xtCd0Tajhb*Ib}X$cs!HV2X(%Id+;HnYkXHU zcq$3P-fgh12d1*mb&^o06q+3n3Por7xD!P2bd%yGxu6nwy#zc*ZrB?{HI*}@7J6R; z?%YjaPMvm1<2P(I+=W_{;TG9%!fzlU^OcT`!1B#I&O&9Tk0nGN^BfY_c0jaT<5)Ds z?jaeTc01N(#Rc|fw;27nZ3~gaoOTjLGc((yw6>8GRf>C{b!wRzWf&8@XNrI(9qV ztHe7g8k|%tTES9XN6Kii6>0{lH5FzFcS49?$i0A~Q56u<@sMaAP?bnfC{Pq7rTbNb zhr%#-v_>Km5TD^al8P}72$d*oqdKy7q~~{BmxrnKoT59%+Tt3#c>>6Y@--By`;o85q4WFw3asBT%HO8r5EK`@6B z;wtD#XzblkhoV8KV*Ogh64{=K2bP~Czsmqi6_98^Vu`HelN^h~ozX*yC7#fi^{5`$ zq`v{psa8xzN_90eEwYjb>86&zuN{lccEj;eT%yvk7HX?a&Rpt@i3PDs#cU0DNq3BX zi4tlq*Mtp6kG-A1$N{=%AA$tVTqG4KB?w_jWs>L=!NZJhD^oqEXpsJETXM55Xi&eal~3mNkjH45>jsQfU?Yk@Hg*>?&9ej(A!<=?kq(J zj%$$Bqb&>{6wUq%nL{;%LW(Oy3Q}wGW(^y0A8V-$hdNJ)R7StVhBMD+@%hZS3~U`} z0ar&nK8KQljp%a-rQJd)`?zQ3tji-nkpIzd6q$Cnfrv5bc?2pC7^>6~l?pZcSLTyq z&|D~W2Sg@cOGs=Pona#}c01`(1fg5NPAIt+!NpLonaU{EFR1g5F&b`ymWfiQsPMPs z&McCVLUNWNZzcmc1E%?m<#-#N=Tnl@+g(bT!KN!!rX>ul0WtR;YN?R$Hmrk88VHz5 zYG(5quxV(6#V9(360aXa!FS52piC2eEcggg8*g|fX94paLIFGmwZw6H-hY*eW`}s5 zraiVcO4*~OvaWaIIps)^9cFS;upKWSVcy-sfwo-6K2Qeh;gUMv0Gz&F288)*Er9r= zIF2_N!s0q%{-6rvdd1*Zf4&F0x+d+-s>L!x-vWr_<`vb&2y#7xRLI74& zHA`Jfyt8MCnOL>isi!2F)hnLDMyimb+Hkf8sZGUIPfD}V*GLPq!a!=0_YvI2SYhK* zC@1o9^fj(kt*F+hd`6J}!$W0+c)&pUIUzd?^-f6@mopfxM^-99Vn?G@4RYOx?7BE& z)9KpxcA3p*gHq(wVA%Tr9f?op`63Kk>HSElyTH-iL89r-`hnOj<9r3dMx(V$w4TNH z6By$z;!|eDBlxY9bcXa(vYI0Jr?p2>B(bnd5}u5b=5@oecsg=H7qGitB zppx0OF#275z;I)#Hz+%$9Ye&>$0c=liz3lbYNI1IXln~-P*OfSHra1fRi$b+`Av)p zRVv=s=rMAGTH<-gq=*{Nu%?uFj6tmx>k&9ZR5D?{P|B1Ih%3X|Ra2-bbuRREM8&9u zQY)jc+0zo)z7MFo8wi~??jp8FYXlmVNchI+6bX^SZhsb)j62;fQ9Z+KjjY}*IJkC+ z$a!-8sb;egf*EQiSsk;G-D{a>ePQ$i^L#Tz-W~}+9~~QwdAbRVNRY}d)P-5OZ2c-l z&oGp++=0?lMdtM7z_i~dLJ^!<(ZbqSdcO=P(un5|4>+`(N9Eyy1I1x&x9N*Po$>WH zUUwk~GL0ryb+8q;`eqCuZ&Z@|tE)in2^fzVK)9zGx#B&p0?F2OzUld|coR7ViXd0F zOdxz={BBX=s|Nm$7*oB85%1fY!yV{L3W zn&u)XWsb_Dtx+mN20EdXvnY$V#U>6z6S3{l3GLBv-kqS+xx!DNya;)9G1II5l?`l;#Hjf$8d>*xpl#>h`QQTUHXA&E5WE63#Sg-31(sqAF1h4U0ftG!g?Z@jfkhU*cy&GKZEL{Y8C=HSc!CMaqa~KrcY@ zISfR0L6l3wY)$b+N`FO8^5WiG_KOK;XSZ07cTqlUTgwZ$ETMHo1VQF{32es}q@f3 zNJ44bbARf3Dm&Z?%@@=M)<-sGI-+}Ef^4L;rK7tkq|kk(x3FjtGrBkCdPXS|rU}VK z27+eKMf8ZI>It$jF;OGWUY9aqV>u;WQ5lX58l9<5w6S8XYCfcV*h$3^Kj1wB-=}Og?Dqfw}BZRr(k#$Ggn)jQB8E08w^VQ~r)_i-R6j)N-*>l$>m$zHd{K~GhdSv&QCY*E-P8>6za8NDZjab*`F zqKtM(^+k(~*RR1~dEP3TYN`Ou-L85+ghJaou|2Xw&jv9uJrUKi{ZC?358+ofJh~um z*{8Dgt!Q?Nhu4;;G`31b>l8S;Ie1DNHT!=x^zzY}41<9J>NO;^<_vRc&CEM11)cT` zYm9DO91h&Ra|bi!>}=0NH_kXei|+7lTd#^?y)&#)O2Jz8PZSNwp@5rY^WjRVbKDoA zQA|ZCBCP$0%Y^qeUX!h{Gn6@^bMOF}y}BK*d5CS&$P!Kjwx(LZ0;1IDEisE80HiQ! z!Nh};Y7V7cQ%Id5#Up_G058F>2-)DbrZ^xf+G*NFZ;WL{+*@YN(Pa%Ie3pC)rvZT? z3A?CMO2CLldXZ&sN+~2RUqS`8RTmI~fRu=9*_{-9rG%YFNG0~j6dm2Ao<$#qMioj) z>8sQe1glzt??a-CUTg9&E2Fnei25lXZ0nNNCw>L9YVeJ=MiM8rL?t7^e{F%YNbpyi zl}U&tCUicJ3c5SfG@X(z&Gj^VLD*9eT0fj1+CGe0D_UyEaaE!ZXoJIRRA3=w2rsUH zqeESGTCTvrt7(Cvtg5elqj!R;7aY!lk?E~P}{>GAM-7#-C;1Hs4ff06)lp9<^I zW{QsiVUf_)7EJDexBQf~ae}aV%JO=SXN0P22sh-f0l-Pt-LpnKHsDiSN z3MfHOl>*t^gIfwOoiUC{RoBz`3(+(e&48OB-3VE5zV6AjNGe=TaGMRLDagdumRuB} zMOQ7TPSJ5ZwvOCbWTAx*KO7w9dC#zMY9Mo|^tMEnJx$ayV-7F>S3k|a{E>gnPyO3h z@iYJCKk!Sx{MrZJ?TQ1nmHxb$4x^)Y$wBy3G+XYPEMq$FlC?fQA2@j_Nq)B1in}DE- zPT4%?;WFTBN@F#urj(fLG^b&wq+)ELTgXRAC`?dVyG@;Lg|K1@DQ!Su6Y3#drUsuf z)&-G(X{FBm@~_o-dX$WE~B7@qQ4!DWPt0I=IVj2^UOTQdTM=eJYLe8u`CN``-u=tF61%fS0TWdL~q ziGHd;?hO*5vU-%6;wJS?$coKIWP`^t=xo8{B$qu&e|uCO`B}30>TC@~4t*RG1Gf{_ z8f(Ks`Z~rbsGNMeglG!O%}#{WfsZt7D_5!@_t=OuNJMePXc5Rpngsfiuw13o$nJ^+ zW?!^09Et{}9sLd2**!vX>fSL;!$P1Y6chFfgEF1-^Z)r5`I@hPF)#V zX?cgYe_NjYtUtsjJoD+C@54ewH3(@xxfib|XVicc!>&Xy`d9*o*KE^C<(vD8O9%$Y zrpw_W=>(&cG|ZDLnX!*fEkVwfdKTB;2jb4MTrka3WZh~}ziNV1AaN$e&E7foxk6)ThIKydS)awvzUn`|$L@U@lsW_aU`*!+DW^vc}|vH+mTHN$KY6cJaGzgTL-z1 zevd?X^XH1*0kR)uO~sl9p|`a)OLT;|1;M?xW5|uoPj-4j4?A-y5vb@C>dE56Qdm&S z9NoWVkGltCm9Si?8rpJ7Se0Z~?|*&G>-g(m@j|}k#s4fD?>Jd@{I=in9z6B^pTci? zmv`jJPkbV0XJ;IbM;^I-hky5~|G*Ew;>UQ!kN!mV16=*Ob?YXd{h80eWt_oKO! zAapG{l~uz$P$4?*XrTq&5A1;U6)LNP5hvHl< zWvL>Wu=F6cEu`67@6kwTO*6oY!Wb7!aw|H%2`Oh4p|wLmmgM)+U3j`>UyFHIg_l}S zkWwvYlxfE={>sDr$WQznKm21q&G)?YU-B!jd-x5m?=Su0zrZIx^Y_!%3r4%Z`k);z z(1gGkB|gJftSzU6$})Ly^E4@qJ{~PQq)aDe;4tUz3h(#y=RD>)s47o=|EKWbzyCw{ zh-Z8-HztXSsI4@Vmwe9;^1^TZE?)kMpLnDD)gSoK58)F(?ho<1f9F%^?Sj!8|K?S% z=7rz-PxyeL3Is?cd8Qf9$8@%Uu{s(Lpz8>qS6?{YV=c`_o9s@XZ=PeicE)-{8;qi3AsUfFkQrmB-d0 z{vi48y`AZHa@F*cE?WZIye|38PNE`q70k#cB=aPVH)1~e41|DS zPF075qUy@{G05+*R~Xq<9+ntICGdaMAmeRKLaqlP?T+X!;jj=6$adW%J(0$mnXMq% zDpvOq{SN46V@++@V!p()(@Nlsxb0~DMR8h^RD$m*E(IHulXCyl4 zEx9OB&ra~oNN0r6qDQ5HNU_oz{OG^ulRxu$_kH#>O+5EAKZ8H|DId>U zJ#>RHR{HUFy7(H_kKsgDca&+zPyFo9^T$8y3;B<){)GpAm#4hXd-HwY`OUQBUHW>U zuZJt1QI+F>ulf3KIr=HS9}>yd+PhrmIHmg2#$4x_{3~5&yVA?zVJ)=#ut6ZeV_4Jf9liu+&}XfSUa*F z9_D!QFzexUw8N3!VkS&q@6g;RUh#gHHljbP>J(3CU#_(QznO$s|NTEMr|kXS?vY39 zwEP}7Eay5u@+c*j=(Tssl?{BF1c*epYC0mP8|=t_4n3XO7=cB{fFnzPtC0|jrB*u~ zTcw;VBFy@=m=zUCxb&t)j|x(1`a{j93TY~Hyib+l73*u@zit#MDgRV_T!x%cvb#O| zMBE!|dp)S3)5Nq3=g{7dtjEicIO@bS@0fQx>Qs{#oY86KvNb;C&wLSo;fwy(J;CF{ zKkVuJ&^gZlug!!KCpw`?NOOmQl5mnuVbvXtSrjv0k z3$@%pMQL_7Z9Bpy-P3+X#PD&sWhsbtifmqf(bX*`;Z7>SYm9E$shvb-nV9EW*;t$c zi+%{+DPhOBkHMo@kIwNRWqT^~ob4%;vQH&ZV_hy;mx~yPH9CXnxfsM?s~B?SnQRDi zovAKSbxF+WkeFPAVl3@;NV%<3=RMPW3zdpk1mZDrR8g5Sm>RCpx3Ss#*zpo?4Xp7o zf9ON`rWgEON)C;B^sgU$`70Pw(P@v?9a<+OZBEKmqd6gGUWi9yVL0KQN03OdL!iNL{gEjCJ9? z-}7Dho^Sg)-uaz=;{(6Td%VZHXW}yOOlW+>{WKfwYvE6R>a+M4-}NHi@i+YX2OeW> znH2g+F-Rnyb#u!W;RRp)<$S=?-XCiXTQ8#D#zrJ$y+;tWl}~&2N8fk+zx)NC$6x&1 zKZDyEGeXt{YfDt2x+Wu8uFOmv6;rHRBG8V z*L_qhZN$_+W~MSxYK^%MzEwRHDRY=2YUQ=BKkzZn`5eCYU%dRDEBu9@|L1tYSN|Oz z|F|3U^~iF(gO4M+>KIwZLIA!?GV5F@eb!ZQ>KRy*@xYSdiEPARWk~wCzdl(g*36--3UvQ(eZwP zYF;ZX)M+1^D4Rc4MI@%S)oF_P6WJ`LMX7b4!8_ocLR2QM%1FCxEiqkDbw-(kJCWd@ zoQldsks#JpgsJRNo0yrXTB-FWRcEHTQm2~LK#eYJjk>4IQ6ZRE28)Sq$25jcRhM;? zN-5zyyWigmencxigwZYdn(3U9O_-R6M4g`BA*Kt4a6m%Q(=Kw*)yh+T>;KBfKJyPg za7iBy2Y&LWUqzXB)ajfuogq~zS{cK^!uq975m=%RM^DmP&nbjT$hLdC4B4|d(|n7f zGcW&FKXu>de)u2wFy8-t-v>sc9dEN-Jj{B$3;>~be8lXFF$QKAxOYZ7a_fBNAN<|t zKQJIZ{O}{W&LzeHKqQUgw05Mo1O0g9o!;SX`L-ASecs{Mz5RX1c=12^&VZ29&WMCS zzwc!~1i+_1=h^(u_j|9H|1mCO;8s7<)=SoY$Qd%B?0VeI{XNJ2;1B!&{`8;tqx9v# z@#0~Y%STubucxht5Tuk6jic%~3%fF7eaQiA;lXG9A`%Ga%@IK4Q4!$3FOvAL)?({X zHt_NouesaFL+9kz?N>o)gS&>sI7_pQzsQc)>>shw!tz)&<(@%eZl5C((!~=jVxFb9 zg|=U=!~mYv4=MeP<7r4+`!UJ$85a$r(=72I$7JI;O2#qG_j@!CS*n`c>0^zFe3Zyy zMQ~jiN%AVYFcg$QAK{)WQn`CHKIS=}&AdKQo=BNg}8k!&h3r zOCOyVkfH8~|RR6KUITvRB! zL#CL$qT-opr@tB%Hwj2+2{`B7-}Rjzb2Y#8pZ_4W?!q=cXV^qn`AjKOc#xUpDBr8u zBU0vQmQB+^sMPreXS*BhrW=gWc=dmM4L|e%?&1lLe;lJPC$1w#Kgas9(PBM|#draU zCfenAm*e3P-uX8?nP-2(N8NYKSHAK`*=Ag;hJe8>hEHiGhrO@#cHnKEbc?_KC0}^o zG5+zlz8FNA<}c+@ z3;+4Q+;i-|@`ay6TP|3Sx9RHzZM{ujF0dAZ%z9hsYe=O>j_{Qd*6!9WndU;>KNNQF zB5!60>%X%Q@dpGac_ZN_hoC;tGG(hIuD|Nn-Fvq|vs=-UimV`W`w73c-mgwjVyIL6 z&e57Nxcl^xV{Df1k#UHY95l4y-yUwUWy*OMlF&?8af{T`xZABCplHAnMW<+l+MqI* z=p!kLPE%~Q8lyx@os-1x6se(f>d|>yO3i^xIXp-kT4oI4kNvqX;^+S3y_oRFeDp{1 zS%3PEGuknT(Y7*{L(ZL#pcQ3y8os6gAu{%;z-;aG<&yPqL0>NUgTX z;+%q{@5k(2*d(n184XJbD$(uhQh+cc42Md^v$rOkYCSlWoM+vw;p2!>QrQ-g@Tgw) z;mQlq;hutx_~@7=!sZff#3r0`DZMqI!GpnV{u!tu=$-0WBpUTdBcdjo6TN>tX3DfAkMO z^S)#L;xGLYul(_!I>F!}ub-o;N~l=q*jTaA`Jku0A0PMuPrc_DuleO)=B5Aqhd`8C zb`({<`=vib?}JbOlxOkKts7WxwDkg84(V*XPY=FGDy*-#89(&OtIF#`KKOU@?!W0B z>B}9~%R7u^!Mo9VXIRddv6V84dW*$4Xd5KI*jU@$;M?9j+rPXCHO7NgTk=@J zLLT%-xzAhczIh^75#ipyJ7HpS)rFp}w;_KC;uE>*yV%BJo|*;CP7R40hAFy5AwA6( z3GU;nM%uMLoZJN_?%y9Ce9iA95;Q=;>p4jR8fc@S&3EIu~MqRKOZR>IPmO z<83v4yNZP5bM%F=@;#qtW_1>DAbEdN!dC%kd)G~oglzN_lr=XtPUnywW zp=G9EDMc4FNoy4tz9iflaIfV!+sFF#>R10KzW)bbj@CJyeQOp~Q60PH^)GP^OD&Y$ zK6+qeN;{G8>7o{FEKIvIe&vys7k&Fbd*Elb))GZXA3`%|_Z{PFU+|65b2aTK z)1Ght&hKNMC!X`{k4rzT6(0?|mKY;WEmJ~H;e7mkFZ+?xF+b^9AH`@#`g+8&vJb!{ z+zfgP=ZhlBx*qUhw04NTqGstr+-U6(t_9YAjZ`4=Kk$8iZ@9R(OI@!1DU#-O86^Cg zwTfRg7H;aTt7wz9=o_zL*QXTbi~y-*e`@j;ay33yrm#2Yhkz9#tjBEZZCc?rLO1Q3 zrOe>RfOy}D zR1uAVLO=KFU*hk4)eG;t1^?F<{}mqp&`sKUm);IhZl|g&6e$3)Ku*6olcbcK5Ewj< z^{oLjzoQbJzhRY&^~QcGJn!?Ld*5gN%4=W8=l|s|MH54>(=O&KlwD4jQ$!|eol`0s z5Q$33;XZ&)IW^MgJx1griR%+YaWoB%4Az`rD9!uo!(vKb-A7}hRwy-L5Jjhw-O@G8 zzEaKRqIj8-o2Wp?Mot}c$36VKn#U*sksz^a*@wAe-JOP@6%E)&wNf>PkeRP>`!yQ| zRhY^-jEx73VB|5XFnBe>u~^gA-Af4)-(xJUl2BPr7YsX+7DTve(rbFkyjy$2y&+%w zf)`TC97#Z(k|0k)JT3R<{aM6q58f_@PgzvY7`6ud8Oh(^D_`&(wALT^dA;{c=JaF< z6VBWNGpO@9!^4j&P`fE*^f^amUHQWwcR#Q5kG|#GxO;Jo&A;v=_-MFkW0FZp@L)C; zKIm!h$2|9j1AdFl86OFrTmPv=Qbd_q>eOL&cGHib&8 z;nt49YoR=R`@k!IJY>8#&d>QhANW+PuQ5M9L{oyhj6LdcR>Pbk(l zufwD}X`DV^3+H@UM7v1;MyNl7VX>~ZLZduz&gJ8lbAG~lPAxH^%HmXI@+ zed5PXQDT;YtvRG7N_La5o)DSx+gVse5IqKEx)Ke1!I%8M_vzB#^F7~#kNv2Ri1DXm z34f>2uI7zQyMzS@`+1M~@S}(K5~^u3DG8ZG@4aoZY8yQB86U#?zRz#F@3wx+x4ncP z`O%-D&gWSrB<`* zENqnGcApUsVp*oN+zx*%B?cX-hL4erLj+MKRHq1fdImb;zo#2$-Gfx-xrSU-qFJz( zN~z}|AD4O>6(Cx2!eR{>HepXvq_CUMnde(+p|3fx>yWk@J5<9M)3LOA58p4TVYd=P zk9zi{1jIDWyL3HPd~E@9mR(HrQv>f@US7Vz75yDA`A%MU`!WJ-%GrbHCx$CYr7ug) z4Uuf13?Tw(yHZjVQumMJ_Ql{I{-YN^=H~^375A%1Wcwl&vv7kRBdk?)s?p&u<5V5@ z?AZRW-}`|);R%nsPZaP?-|`(uol-G5XaCTabB+^#>K0P=PyD!#ysxVMn_vD_l1ms4}JOvGZ$yH1Ge?IESFI! zml8ojYK#KTebC(K7^5HP7Q>`=a+9e(j#3|o%lzgFApSr22>vEjFIQexa`juvm6A*4 zs(i^Qd7EF=2`b9>s_Mxidow*0(P&ekp*5n=}?ZzW3-5f!;@dN>L1!qyT z1h-Mj=_~^Z2on+!N3_dvNtA&OWDZz5iMqTOgmr>%M;NQe+{NN%>R%WWF%6k zB|Ugb4ZfvJOuI9F_Eo>Y_y6E4?z_yN_MA__Mv#P~E$QVXDR&M#n9ZUkzdmh^dFRo^ zK2A`A6Y}Q}6eJ$s8`f7o`2k!Az+d~aza4%-bw{b8l2VC`Jp3m$vUSlDBNpl<&1z%5 zHe5+g{XC*OC^byzrWx3C@9RmelO(-`u1b#%Mi~4H3N77`1C1Jl%XzE-o*w$lSm51rLOj zzTunxF{RGb{moM)<38vudwj~~ben2Nv6IJCoo>V!;2O~5-+RHgb2u)PQuyRgde#Fz zFTKB<+|0AmNNXI)vC~*%^ox_UI|LY07+%WY5^-98k>N{ zGxO4yU9HcLc*fIlbK0`dm&+9YWOeA^z6L1Yqam~I_NU5Ue4^?OEwHS2xqIgqSdX{S zY4_%m@yeS3Px)g?cpp1Z$QyW5LRZ4I>f_M|-yn{Fa5larvNgX($a(tzZ<~Uj?BCgN zPQOm5kKCBH8S2qI=+~e#Tr)-9$kCD;NRt0%CpbouOb__VqG>KOZP9XiIF*BV!3Ui1;p6>uSnQjDN7$;u{z_fg7yq0yQgznyV?$2;5K-;X1vAIed)!9x+x zgtP+JGQo4%PC=AGKuV&gsNfw(cCzd?h(CsJHXR|E76BlS{rCC=`g(gi)jW6HjaOcO zORA7MpDIm)?P+MKptQCy+QMdasH_IkmIVrt!>^Rdcw8Z`FNgDC{xAllG4zMvrUw?fQoF@F*QN5NF^k< zuQi#35?T02(ss-^@>mA$N?RevW>rO;*9Ks1KXg`ut@ZI^C<^x4mxh>uU(v2%X#`%C$~Pg%YqO;(owo^ryq z?MIdmNDdAm1jetE7zdY)l(_IRHqae=)L}T{utU37Afj+28TuG9w zwTecRjKdDc9nsmwn)B>)c;vCCMb6E#eb^j{PutRJK-vGX4L zW697`969|^F@=VT#H~evrbxkJ8ms;!fwR(e ziZc!EF|~#VtditcWmCv7x`ijVZBf2jvK>P8JNw{Z^FHJ~19C!QG%vut(bA@g zK%-gU`kQX=bQJHpGx~cI@`4@atm5DSoXvYhHO83_>}GZ>n~q6%kX1&k7z;$9?5FoH zMvvef>bb^&`|a8Fin;pQoA}gcv&z^PgX6Xq$&I38#rM+MAkTYwF*J2^fmCPH&8-QWs z=_~G9001BWNklDyo3iIegekkT=j}iM#Utl7GIW<7AX|8%~0=|P|fSC z$#L;jGqG%V1jZPA?H^7-Z*RS`0`abr3v2oPi2!XG(ZWVWevdiH&s5z94K3GaiFGHh zhV8fAqO%J5{S1~r8v|oAs2KP#XLH`HC7g?7o#<9o7X=$~4>|b2sxy+zo52+l0l^0? zLg2N96ds@ZGKQA2^MBvHc7rtmp&VuTeB_pFj3rl&kvkJ1dZ^4mk>%=Hf#V8{fiXF( z$r0iRLOBFw6>7HrclLq&PdZxa{r0K4cm~_dhA9%VT@b8POF>oTttUoMGG4XKRaB}3 zjA4mkYLChAlo!nm##nL2ropSKXtvZ zEcay6#i$y^k|8l&E2dQv;RGkY)YIYJEI}j_gIzCWp~1yW~sG!deBLXRvu4Ht$7N>qlOn z0FztTT8_LnfV@74EbBv-_rla_Ts_$yD4WBW9^Ra+W%7Ww0J6$UVQc-k>V`Y<@@wzG zWPPw1JHBnk5vN)i=)4YH>p|8xh&=B{miNKf99gXoww4xjBSBYOb=ka!c~~i%CIB%s zrQ+k51D-c2EE!q~09be3$sh{Yf8X7)<{GPa9i~?jb_L$FvpQ_829xIkj*LKzu+^rm zWUw1C(ip{gzrO|maOCuZFnR6O(NoX6EY790{zyb7a(DnCM3$WUGRTRY)q@Cx;0yRr zvQu5uRNbnOYp%T!L&GEd+9Z>gWTqB^XBjdeVwQ+qKE4AcueD~!<4l^g3Z`znA*;87 zs}5+&WYAena&I8*YR)9N^{$rs>W~BWL<|I1j3PvjKmoxy5j_NGWE|$Es*os&nqbk1 z)fO;-_YK%=09kEfDmKU^JfxKONy_=>7- zeg$zv+7N;a>!<{QhrUouT#b=?)*&hees?kM zeeg*<`}`|dw0H@6dV8?iq?NJt7Mo()_FF1r^XHg#_e1#oIsb-_Oq~i}mQbO|CrqU<+9>egBhTRJ=U&1a zZ@z=FbXa}0Rj|h{J7Am5r}9xGj`HVeWX=~jY)P3UACT&rg1D#IGm(t2i-pF#?&P%u z)e>>iiO1o4Klstuh4;D}@$WzQ4*K&td}zqV%-4QdHiQobxtFL?6=r!abk>XCU2+2! zEg9nK<11eP#fY9-=LRw|G9oR*)}fF+ND(n6{#cOTm0Pkg<%@`d3xN>JCidBVM~ojo z4oj8{jj0}HTyY&ff8vqcP7VPkfHU5unot7rqv#Ps1IHgd9pC@aS!1aSqYX5Rl2wt~ zRO^}6Vq8_6fTd)E*CxkpcRd6E7&mSZyL@~H=9`5mF~Cj9w9&9u!}|zrGx(w*hc})F zMdw9{r=W>A-q{1uYUpUtEQkI-*g(_@Dj4eg2POYoIW1&^Xq70586>vDD&`g~g0EsU zMGLqn?XHw!NQ$2$xN4y8V~WDnEUhK(r<0r<>K zPZCr`1J6&i%JB4Z zj`Q^O(M>l2;AQ?(a=+HCyl48K4;ssAjK&Lhg9whH%!{Uu21~1wK-o#kjoB`2c zy>-_@UvDo)mQ(kH5HR1_t{#D#E>mJ;A^PQOUT#J!3ZrlKR zdIu3)10fhhm*blM{v$4&aUC9ea?T1b!SUk;aoQ=L#%GT|44P_eIEy7;4(|A32Rt=~~LU z!h1tQIRBFCasEYDqbM2Nx8~ZDIlT`A?SY3LeFhiIxE8m}y0@#`_3ZOzV2@p<;iv!l zO-vZ*h0+B_0+WYTU=LEz@G){*L@2qW94u;Vj13Wn6y95~2mr9wnrm=7GL$&}=uhHb ze|#3ovIQ1wHk-KO>Kkym724Zli*E^TGrAwDVB|xOf5Zs6;oH%ZpTZ1Id`nKd!p)D(=;6edy4KibK_#yk^ z*B8tfI|#q%Qhebv$AA<^9Yhec{7D^jlC^GU&bpJhs zgkXh~>m}~jhzLLfHpeUT-oh*M80Wsp*@jL75wk~;V8xU2+mZ{l@mTl+$F_Wo%BBR~ZOZFyiE ziD0z>sSMA(@Jh$~Ua!~D+tY(`bcmxh(rGEUYG|&+G{{>o`zgE_2V+~fmr`0hoe@x; zTQMxeM~IvWuS^aL!X_JUfZ30BVk%yJ?R9KAbqWV~2|}WDglT&pjo06JtLwG1%E~Jv ztMws7!X=kqg`b`MTfF(!yMOVTTQam1|MbJNaKp@5xb(L_!$5y8qBBszv3p$7C6%=> zQH$%|v*8X3|NL*~;`f)cTYBBeYvG7b?vG8Uei+7D%zyJ8+#VaLOsxkGJ^B>R{LwG* z@MBN?#ozDNJMP61pE?cKTzW1BdixOz5j#XcMXp+NTN7oN2RlS`H8)X)4Uh{K@&>Z@ z+G{d4KLm^)-;YBM+y|Fmb=`7iyczi1iATd`J#3zh0R|SjCPvx7cQYg3g~$Nl%4=`K zJMS)p)*4?u_49I{0rjkNsT&?1VKGNLVZvw_U6V|7SCN;g%AmI#OeQhW+V&v$h|B=T zPCu|?19|?17xBRC$FSX&o51-J(YID1Mz9bc-iU+bCap3a+ikNY9-RH?*tXv9XW+1d z_N}shyrguh%7VZxJPHPXyz_y!_SIepuF2}E#N|^45e3M3_VSQM3|xIEW#Oqr1cNC< zXhK^B20U2=8inb}fJiX(L1V*`2mnK^ju$!GA6-YJ7+ViLa38F{-n!C`iKy4Jj`w5P@G{A1mrj7HK`Dn28wjz< zL<6rx4y_~yhG!lj7O+-Klo`i9um7Rduv@j2)(5Gy@;o?#J+M7}j#cK=Oek zp-UhVc|NfEYLhx%>s@!>i&tNpk2NL@%37hgBBv70vBJ@2OlZFKP6nDz7(Wivw%Z!s zjS9(v>A6Hzt{NhFcpn6&s+g@8f`sA)WO*IYbH|``!%$Jdib*t>`Ue2#uHNrf@qTup zzd%y#c%0=7hi&!`YJYJ`E*?oAOw`_FuG2~j{(=O9z`e#4I-52b<{6RTNLZ>0!Db}I zmzJKrQUsGRqso`fD%>f?P-R5$a5;rnl``~>)v%akux_;x!<%P?kc0pkYmw!3C>Y6I zma;uU_)Jk+N8&ZnIhKw#y1v5`CQPVs3LHMAp-G1D)!QieaLrO8Hfj;HCdRSDR9O$K z98;X%tXLU?7{nx*bA%}=3=Z~peLce?qmY3PKc50jW^m!TKfxbnoQEvyTu`2W@kQ*q z_rd7z@52Lk-hfNa|2dAHejqmAbR$ezZ!)&rY!iI?*y*_X(qH4jyKchHpP1J17>_;i zCw%jt{{z})$Z{S)rZWgAvQg7yh&scuUubO}=luMK_|{iXL1qopU%ep3-3VMUf=xG? zg3B)YuNFN4!1)(m24#4#42k32Z?9ePxlbLAo_eS1D8`77?X)NEf9Mfha>3bn>cLy^ z^B?~UPWjyNIR5BE@wpR^!I|IvD(;_kJubcQ9E=|~)*yH7^)oU1v8Q3OI<(0-TSX+i z;|%ISY~}f7mxrpf`+v< zrZvFvY!FHa5S(>x^FAok!<$Vx%xDoGyE@aayJ034EF5A_nB_%29V)G1=%z8uvy0y2 zz6T!edQD$E<0=?igUNeDrp-beqXoREa}14?cvRZ0`|qa|2Q<=yNUq zGGriKK^4^)kA_O#4@3d#nO0-0io4a4uMsQrTnH@ZMXW@f!cZa!5R{e=NtQtHRa7l2 zb=nkT4CUd$IFaN|y}`Eh!g9e+4m5* zZ?Ck*@NoAHM2pdkLM;VA7IPLykLhm@cuIQv}ylSCY!DXDM_<>ZBi?FQ?BWgPV6-LU3v9&XcUo2yd?fCtwAb? zVvwtNZZBE<=Wi}y)7$}jyyYDUN zzJXkNInPo|4LV~Xh{|{-!Z;b=w!0p#+~XMp@iM z%CP#+W@0p>7JrLMkq+0OweYqO5JfAmwPrn5Xo&hN6Q2qpga#98$w(WHB#o}dzK9ql zgMedCeH?8855b7%ZeNYjM$4=RldPA}7136S8P8VgGRB3o(#eMgVq~O;(QJIwc`t8b zAShA2X``9kqjM-@Fwo!E_4V}h)T;%GB%~`5b-j1^^zq%D?BD<1cVI2Yc6whT_>!TH zZWO_dA{3+Wt_fd`!WX0Xmv5hn?Y8Zd6aVOE=RjML$eIjFFeN6d;ep4V!Fd<)pf}4h zeD$<1SG+DrKpCe8mM}`xXY$$fFZcU48wFnr+Efbk7z1|QX~!{}P5L_HYo}w4HCC-? zu#DPgNopuY;j1LQQEa{0#yISd1D0#0+|O!2*$64}Vnf1jqsR&{P*TqUTG#RJ0$voY zwOD24mDtA+yu8N+4n1&h3=Rx*sHCpG_C{#K@@A$!WOB_AqhxH3EUV#`S+nu{i#(hA z#_1#EJ(O+t9(OCZnr2)HAt<9vGi^NtNONQ$tJ9!#}rvV-j zBzz_S;+Es)GpNg7PPKqM!pDW2OP1VlGdZ769w1Vurl zdyyf_Tn3^Yj}RkMugrNOS18;O71FEavRA4c#+M|WCZVsdxBJZDz4xm84CaHi-ZO^p zMJQy53h*&u)*YLd6w7>}SX~xLBSyz@qS3Qa4J7j#4p)ic8>)e92gLv4&&|v zG0B)DwQcfRYhr^bWAiCiUU?;KwdEE9bd+i_22d#F=h(gP%Tb9C9R)&(Gr#k-j^}^$ zu_y586LZ*zXKl;HuPuIh_Jw0Mx;(GTCC2C*W9w{W%W{tT%`*@*UVQm=JoWUmZEaVD zHP%{#NxcHTOcH5|9y@Heb;s*EV86YE4lTZ%Sg<-x66}~Z9S~$;-+%Aj#{B%kMT_vx zdkfi=DMV>a;1-P5Ol&1?3$bxhBO>Chw|N6ub<)aJt5oR>2o5oL)H01Dr+2o0F8uwa z&^Cw7a@d^F<~#%8YuaXP+Wh%B{GJ}T-=5fb!wq;Q6g;PbO1yDTPiI?i*|KGFfC|+$ ztCiw=ivnEH z2nuh!vjDU1z8`C>zA8@s{AaN5-knC--(7r}m}D(`aI#u;$ZeJ$cMD$V!2R~%h~>bM z+R;a75VBlo3Z&r527(VLeS?uanmr)FaZZsn23frqWSR7=h~PpY8!1Ny{~d|tl=3>`~ATcko3L=vv!3afcQe;pfS2z^ewM@awKtD=?oecUklUsS7A}(}fAe10t zGa*TK2nqdH8`iV4WSD`5O4k4|6puCL3R)|SA3xajBU!d=8I}w!!9dNnz=hFqI+_Ru z_>^s-wTKYBg0>9WQ^=tei9{L#4kigwih#~};h{tX;2U_^vSnRg&)RFPC5HeHf)^v7 zXF-5600peE#;SPXrI)Lp*>HmmSX7)`?-UV8vRWvLO3om{!$g3TM~E6*ZZ;JgZ?qwv zo%{S)L*wmtW2?Sj}<07`Z(syeE}P-zqY8nz|ptl5DBnZ4Y%L(FrIvRu2dhVv^Gc;7}&|##oafM z;Ss3SC&~fJB7`6SNd=0UXBJ8$2(Jw>Na`X%YsEPlK|>jhXaYhg@Zl*R!q!`U6pt*Y za69KW7vivk_JQ)Os8lM#<_w@zM&sfc*Mf*};_*kp7=z=FI|?`a_slVmG4tkIv0%{> zOdPDkyF#c68VbOj_dkrLA4N+J$>-OA(Wb1r7`llWfc3S zOfSoL5#gI90tJaT)`(>jF%%ylocsT1m`q?r+F_qG8B8?Cr>?oGHrU86XGEpEIn+OgQoNNa{Felk@ z30+ zIFB`(;{^0*q`g8QKCN1bay1%?#BC30T^U9{P!*7+1%Gakuq{Ssa)Wy;T{hhHwXD1D zWT`k}(o8}eI--D~j`Ye&lU70Sjt7pU#1b%$a&ujDX>TB~c@AWtE#+z?r$)HiY_`i_1CI=m0CWw1u{4|z}FNde77j?lvER7W1uDme}cNV2%ClD zVQ$vX*O+41C$qW~jH&a&6?MgJN^DIfeDsQ`OHkS&l35rD(JIp}LItUR(vw={0 z5nyoGoRND?MT<}r2+a}bpxBX~)uBQ}S(J#R5FMGEh;1fRu)|7Dn1u&?JFIHI@njJX{p% z^F|v^>H7XY{ODu6um=*PiIX^3R&`M1!Ced`qbe*%$`TYv<#4V*j7=zz*X6sK|CpAuYt^30am8#KqE7ftpQt| zC=@gY8kw4?n;drTvVpw%>OA2}S;WW+y}R#kDQtRsdvW_8Z^9e%p2Pf?pT@lBAIH48 zkKom3XXBMQ58#zK58~BlXXDjpAHu88&c^%~AIE#IJ&lL%`fvQxw@<@BkA*LX5z0mt zfW!wIRdJ}sUgJutW=>V)jZipjia=VmWa~(X4c)zqkT!TDNr>7=A|4OsWAaZBBWY3v zvdSteRWj}1IoBe+?&u19>e$ZN)Ql^xM$-jp?`DEcW0A`J4?T+6kMc?X8>gKj68j)6 z+XNm}AOzIwwJsi>A*s4qU_y#d-^^bt5vc*40H}~^2H?2^fwSx@eSayfL5~CW+8Kib zoeIJK`t=2jk4)ss@qB-{_C~z7aELQTOjg+eF1(b7=bt{|XvVX<0%c2=`wWi7E=0QY&hdd#H-F@=E6{ zL-eBXN>RF|#%K~^x2aZuwML+HEv-PTKw;AE30}Z!kza@R*kz~fyT04^-TxroTew8% zrMZ|Z6FNPw6iJ!wVW^xa!=MyY#wT@>-xs$LLClA}wTy&yl2z>gOyOWpy=QV-m6H za!2s>$gLw){ob5BnBE9gp>UL@F1JA|-ash>o7JFFtJ7L&rD07kJJ10%%d*0lOq@8r zm2ncIv=+5Um?N^x;Hbk7>3GfyF1l2zIY`D%jz7(P1$W&$TV@|$WOR=VgCYnV9MKJ>M z1(eQUYjtF~t?Jwl#uxqV_Ka%jS1W*IsrKz6sjjq~{MB-U8C8J_D_U=oERq+8ajX%dD1Etv|U-bpV%1`fhut>u!hJPAR&|-Mw1G#3PUcl3n?694A^eVsaUHEMKy$g%dWTv5`ze> zED!@+F)XpVv4Y1bkw`}Hz?vi^hubTotWJZMObacTdRcH30kD`e_r%MFv>D zS1VN4NSwLCn;Z|2gS-Yp;P;u-3T`r)PeF~{Sfk>ADjBX52=m)E`SIh%ar$H|g@8*U zL1*Dd^bW@y-Z?+G|AB|_!b|h`#GSDs>KEty9ssc0&eO2vW*@CYCW&W7PKXF1W~q0X zfh-vsVqT`PyrD@(h=vv#1rxUg^-#^1NU}A&FQAdZm|h7)^Nb~l0UNKsF1FfY(=L+U zOArFd0mR^<%a|O@iG!lb^hIShJoczhcBy1;z(}KnwieDu%#``Z!3XREqm>-?ct#hA zQw6nRyo|P%3kkWyV{lwOMk_E=2XXw7+L1_!`OCT~%=7Y0=sK3$o z62?+@S5U^c%XrfwUgeroyAZh5ORRFzQx(ug%uT)CR$d1u#>Hx22?4-~o4k@TwpwFB zZfbyo?Gutck$)-jMQpQNu+t(05bTOFX>l`v4j|X$z#Yl-hA{}9eGm+&i`C75vie})IN|6|c71RE>)i9;C4fg8PKUF$M^2i64<5lcc*9eG(Yb66qX>S4 zJ8Y_#2kbh7lX(#d8I@4*Ytsr3KGMl?+H;THu=ZMO$O$ZEh=eK^gpFLhI4pm8xU#H3 znL$pPtSD1e#TOCezB6*-q(9CF}3+;$W7pf-sH;1OaeL0`ndVx*Z{ z2gB_*aNbV6XOOEr;yccH$Go%J1O#d_awyXvtv_Iqw(5NrCG_XpRciUx6#cn}5#WeI z+mJj}FT!O~?M`sCOhnSg;^wv^cMY#7hLDlUl3IGzcXr zbS4!(tn^5ww9cy2AS7^!7Xn5dFwKJvbckY%f`-u=1TWdzmQ(FyzC$5`WVi`qxME3( zyWgQs4@nwSd^GHV*?Z# zEcX>p(ZVi#u&uY+Lasy&3ZaAB_14>OP({LdeAXK%L1mvZ=eJ*k&-L< zR4GxjQe8zcV0Uf93TsRmU7di9sR6`lInJ(0$$z>U8h85&u@rS$25W1uSzgWJ00l)H zWc7Mm1v1dzFQ8Gr7YJU|Zk|J_q?dZ!k(~?grI%faNCq~mXODLE-731zEQ#s42$Fh?GA$m`6ijf%57S|L`+aZbsl48c1jmpodT-)vmu!L~9A z#)6|Yp1(Ht?}(ju`WS*M zWMkGXTn6#NMoF^RWnnCe5?Wb-ZAbZhIG{&KWGgD`6t_35?Saw;ONKiAD0}R-i?paX zBgI;S@yiW}Z@uj<0c}Q*WZS38Ho{A8232z|mM@cR{y{ixfYzy6(1=u2sgvFA6D5+( z=mW?{xN<}kdufmw#3&cqGIP3D@w};NF~rKxQDp9!;f?sU*WVnYJmX7RHX4dRAW%?Z zl;yVFLHq6A;oDfSU?Hx(;by%0`a8J#T3$5I`07_U`O-NA@8JXEeYCM+ifc$rf$3U- zFw$L=fRZ{Iq>@xJFefESz|w)dOGHo3WbqMYQ6Tt+&?i#UphSozatj>RW$}Ofi6`;E z?8k7yMVA2pjy-BRAADR>Otzt#*oEkEY?pb;V~;71Ns~Iysyio)o*d-`1A}58)AxCrpS*=$2f8hSEEv&yL*7kk0u-ZKx zZKo|-{&9)g%zsHyGR7#v|CYXo#+nQh95GcPYWe+(QtC*Wi7*Lwj0!>qy1XGNp>oP5 zqS{+=$!RWuQlip|Da1<95fn=iZSq3H3ygv>d3ADSm%I$EwPs$CwQ}kb3b0}06z^~H z+Nk6cyS9Q>P|W=)3w-9d>0NOd3l=WKH~;ZWXq$_KS681!BqwQ$S_D7h0wyIn@5HiK z;gRt?CUgs=a=PW`cz8La{KSb9u;(7TG8j^V!YI0q_v`F)&tv4iY#u}k6pak>HKL}9 zBj$ZT3=u|aXwo3B5gjk`&Xa^8dzNa8nXcPcj82qAB-z@WIH%-VWRHd@?^d)4B}Jkl z#8Aoxr(kSNDj|8V6yde`Z?$b8b#C{@LR!CCd>0(96s5ZE@9V(<`*u39zxCbk;mG4o zLW~hRP1^z6ZL^su>jF{`Tf!2>$e$IYOPqBAr5e*0yxL_2!#^IcJYJ&z@bMRhx;F#ja65jgxz-i1TWx`xnwc8 z>iIwsn{T=yrf#%B$739L=n++x%AtoGz@9XU@U9TD0bi?JRpo%!fS0sUqoM79bYLvV zHF!=L`JfajFo72lCgW-{h5|m0q9_^&W%KX3fvErachH~z?iKk8mq#ZOE&un{EQC+` zov%U*ql(`ahW*h~TmCItxpjP$rEh)TpuivTbtPdimh8Z6a>u8d18yG)xBzZk*s|~`~KIp6m+SVD3U1e*_&3^J>EkQvzrgcz~?auVnlUwjFdT>b|b+argHBwNn;b2J$M zBZf>}v93rPC?g?2GKWxNh~o`9@7kuT;+j=#nFL1G*@S3v4A(J5Ls58)uD3k1X!h+X=d2- zVsq{uP{@D~ph$hdV2bL0@qP3c%9rK69%G#6snk$ai>sQ3$Xp=xvXTBjRVHFqg2b^_ z#--{+BuX7=dBj+6gt{=|)dczLA{B6C->qWFW|DA!BkBQRCa+``L1>t7-Vw*H1-o zP55FI!4>=*Dbk+&m=igX0;1_q7%|?P?PJ?>v~%<#OXi9^2#MLW zOOs>ldhT2%h?o!xSYePUIUy>!9;wRraj;r*rt#AI?QzkcdyKKr#MbW9 zR%WLQJDuN&#`L9B+ajJC1?f%HV!DE27cK;G!bkwgqfjhwWNB|Er=k|UH^#`rV`XK~ zC8+D@In1R}f+ZePQWa`xuf=NN*Gg!iNV;|}Jzzrc9;7_0xHPN6h={BFENcy2RfLg2 zyGgbH&Ac4WV~7aEFuUNI*m1j$;4 zsK}BUrTuH(Yp;nTS&L(x(fTRpq%}W4rM}ZP$M0rbGX|;h^Pm1ZdV6xt^l$}=atP5C z2(F1Klh?$qpV+Zu<2vN<6Y$>RrO+m4Zd#h3k&}6h48XyOd5=8)9Cp~_Xeg^$Wf#3{ z@S+x!EcO-MFHyjWK7Ed%$zlPSfr$&Pdle(?NZvS%C;_Dfs|`qLL3QMKdEQ%N0?1yQ z|Ayq}uBSLYz3ZW7-7!7Ef!De;ntQRl_HZ#zsAI9_onh;_W-Z8N@l0zs9 zu3Sn6RUWkW&RwxETW_@`KD@zth@pgcO}OS#M(G46hbk|k5QqN09ETjR&k8q?G=GY+ ziMZ0S88@=R(&(IxkkLU=&L(726V#Aw3PgxLwB+DgP%bh@$@4mA&)YO{`(W=F-Txyk z_m{t@-coJqtTuku7}|TAyIi+BM08bQK($m%tyYubp0sQxY%J52RF|s*5(%oW5C&R& z5+r|DD#>q9MNg811W`JIRH=h82{5HEug1jpax2XX@`MnGTN5^`@nBxmSV4?jkO7;9 z0gMdnrP2z)fi!~|wc_k$1p=Joo0cNHYhuq`rs497&gr1_zPDf@_B-Tg+yAJv9q4-15z%7rJqLC#dul5W;r}5oP5T=;`*6)RFD0kb=Sd-f0%)l276I9m%*3I zIHMs(I5z?UV_s6_p^$tgp%cOhV9}z*c>0-VD+~yuY*4+AEQ0VL#i|x)MeKX<(Rgysi!dffUh9RmHNa$I z#N*Jr&8?SZU*yxZ|D&#{B4Qv+m~0RqiF5^;A-Js!}?Y(4`57 zbwQ(VzumUjd8cWD+%Hg;O*l7Nc|O>Wp}19?RCii;RmwT%@YK`K!J4|z2Z-JCSq{=x zDniYkmB{a#fdU22vyVh@WfQR+fz2&~xYl_#7Np}GQOgt#J#ep%=RfMm>D9c0W!eS2 zD}@-qYmE<0#IlJ{j^Ko&4(@o2_1B+*O*h$y&8E&F1c%^@YPOh!IkB5x`oGPtpBdwG~Gvy(uPQv?-wAqgx;49=r$jBsXw z*6e2yRJ$viY^yB-+Hk*o*Ki?T>_2Nq}WA}YOiLZR)Og#A590X;N*ZN`e zIsyTAJ@f=l{@V9(_z7Ra$jB(Z^tn%QHUx>vPy8I2pkWp+Uc6k-eS`!Pt+|1+HZ^2fU8Lbw z4insV^`T@hjxVv{l(q5Ek8C{VF}{7qSJ`Fl3nmat>o#~VSvS0?X_NCDPjf4H_9IVr zT`hj{v;Sndy2+5|z0es~bkT8j#QE+lQ1In4lx34Ej8MS)5g5q~iP8bkCSxbJ)*RgA zn($=yMnlwdZ7Ax0o7@p1%g@zIU2*`{OD8xK45AW?7e zYi8MQ2AkzDnv*=ED?!2YEKV^jlRQ_&AvEO>U}Uc9TOAHQ{t*BFNP1#;8- z|562_{)(S#-yBrOC_O1=RaYS}jUk{^$srZ`qTOJnSlL8!f=ZbjhyZ*GVUGeiWe?#>O979e2#U0w)}IWJmA)-1C2r zEk3q4et6bz@cg{Dp)8-S8F5k*=9h-fYG^bAZoTtieCo^J!vRNq3D3R2VL`Q8j_-Zv z>$u^H3o)+04quMI7b6Hw&gk&2Kol8oM;>E$@@qRXKzV__6{ zVX-!cHI_rXOosa(ehPaWd?MyP|FX0Y_QwV1o&`jQP>#Zv4c^c^FYG~_i#r5D@X&8*k%+<&o(-&Ic(Hk}FdYc%V*IfRa`QIx`q;7Wlz`{Ho(M{_5QG zuykaE$;;5lbS4K2Mw}ayq0uPunJ;}8?<|+DSC%D?IPPT7x{~MqA z;%Rti!4Q;X(T1_P_>v5C-Y17QgFLHqhKVRA>-7QXoCOG>TqX&IO-VHsz$F+@Gj;v- zu-T>?waL79#>8>GY!WRS6=6CZ@U&J?#KBlW?m1VC;M2#AHSr#K*uhLOC>!vh!Jlb@ zuObTnDj9*nRTe2BfJ4-OLJRid5T?NP8$Eq7TGw@xD~O z)U}00V_Hzua-+B9DpWFA`9q>)YB|tji=UK!R%RNJg7gt%MHo#tF62WAA`4Bkh!6|D z5rX5L9TAEYLU3ZB3P5x`y&xm;t|@!H)zWDQLx;fysL?{Zpsm?)x5UYwxt= z+uLrv1r9xEA8fku23T|TRnXU4N2Afe6MuRRmt1i@Zn>S&5qX~BAI~@qr=D_>IL1r( za+D)!wdA{#cuznG1X-NwD5AnqCwvL_-v7}1zH7AB_|6$$!9Sex86m6|5`jAm1cN`` z`7mzy<6W3}>pg6Y?D92v?KN@4VF%*4!}i9?69<^%6hp;nd-3J@xcG`2aqUgBuxRm6 z*T?AV>&3wb?1fJqbuc#HWCM6#q9{t-dEdjh`=TZkIB z-~BM2obw{?zV8vtdG4k6_1^Bd{nprSt4;8+tvA6In@)j>d{1)rd6T^#kx?v6gsve( zLeW?ZM1w4=0h(a)L0I{3#^$Un%zJUgjd$Ve-~AB);J4?Vg@gCq73Js<%H}dr402w1 zh!I9%aMCl8i#-5Qd%y|6BI2_JUS1qVyY{I6wE-8=(d~koy4P-@gjlb;%(%KAK z63j}HNz{fPX>BlxteX}eAc`^~wrxggOr|r5i3+lSll0k@@S`{jJc!=iRFzBuy|HtZ z{SF*h6{-Y8sEjcWp=c?xGD$P*X_(jB6 zQLVr|Sj~&Bf&%F%ra$hnTe47A(gA~$yavr}6`R4>J{W89_*2i}qD!yBwb$Q-p`}az zsw-rj$!p<=PacS4jye=;uemz6pgbF{$Om{o!l(Y2Okig*LjV9E07*naR86v}iHeEC zs8kzRncEFntrv%X`s=voz7}46-+lJR(ML|l_s;wwUU>1(FF3{+9Jt?}_{VRYiYXsj zM+ToBzT`eODN(*e2cs;YGsa=4oKJVgBCGd8=^Q^l=R*AZPyVyg^RMV@+K$`d2mkyn zZ1#~4^J&wS2;~S<38Ke^SKfqw{QfV;{7i3e55^4+q9{uY4J~Cvi+rs*X=OZh@3rip zcFbOS``v}uVee0I>m&(ovkc<~2Vkwm;w3}kmuZ`8TzdgNvhfrg|AlYjk9XYvSFZxr zTHJT@C0KinNqis>5rDCE9$4EPhaCH5G@2#un0X~aDB%l^-VJ3UEHlrFl!ijYY2W=B zuDap&|JCaB^)H`*FP(T8yc>mU4zsy4wX`B3Mlleo2wy5>c`r<+5LJLy?C#ehXfd@6 z#`K^F8e8ph1Tt&!^z2(;bwJr1fiFiuo;O1yiGzs^mV8u!0&G@?$$Id!b1%nF&;Bib z^UHt7LHq6o-yDH=qwr0xW+FMn&;&)xI3)tSZvc_xz))7ON%9IIG{i(&KHkv&!dmZ8Y35TaD}@y) zrNq<0n}!lB0?S4{v@r<5i&`s4g`mg0F2+fVgID%K4hT{eJBtTgxrA8-8SL@v(MDJ8 zne@Rc#gmz&CITe`S*ZG)TD1DSpK4Gn5EUm6TB8vYzfkdigXWeKG+vWo1nH&}3}< z;q~#c?YF|Zlh+itwU9z%MC2Ekf5njTtyK$Xk$T82+DC-ogcVdd|y$))*W&4&%m6z_{__F*rCLgMn#yi{9Q|Or1If2*d+t#~ZZG41D1# z>Alt#OGlRBsk!q((C8l+#Nfan1_sArU~nAz2l~<9H-Mrj(P%U=JUon%kzouEFU9ci zFou^6AvX$}PhA%|M^D1J1`r%dH_SXZJpf-ULx=_E+vprwUKb&Z;mTjPFg!p+X8WLP zHT?U3UW|3uSqsNZ-xm}DyemXSSU^j5j4>I4D>>?bM9MA!R5q(&v>^QJFD}5p{?pf> zwT5dhhHo}dmZMdt1A;UOt`uRAw5=jz>b)CVAXjfqO+slkaeSrjpf#H0pR`9xnpsk#^!jU=0~Vx>~Qm`pq* zj?InAiuWUfl0G-*IdD!nWcbOcY_9rzQdm%G|4?ipx*2sI#XG=!D`O&JAcPQLbY5`- zDkO~7T$Cto zTy;WPm@JgYs%s_94In6VV--iV>b&yHK(_=JAjQ9bly+6wg!j@f4{|+u$AsEQylH1O zKwDUwx2fG?Wuin1Idg>sJEgb)M%^d^OTi_70}^@!rN>MZL%Kd?bLaPom~Cci0(4X` zSxo?kZJD2`G7qk$LJB3{H&q)p2=d%ivoc1j#0s2fLRoKfa1Y;a1P#&iXDv*19t;J9LE3(LWf|0r^$Pu!I*g?V9HR82>ySFTgwD? z%=;!l3O+O?P)>-!p7~u`=Lm?LxGY9!4xnK4P&Y7P+JXr!bx-<1m@(lUcm^r$x@yEmeob&B%%hvj>{E#mH zaOUJUuA6dIZQWbsV+Q1%xrt1ir~npqT2X@pThGY0BgQ&Kk&;Nu0st1^P?R^(l|!th z`nK-|J^ze()Pt%lg{Zbek@;@3@BH(z;w+qkv{Ks@FMJ$`leM;)Yq{S6aN(%h7+hOY zk0c@kE$9SE>GZz`58jyU$H*iPht}mr231Wkntu-P(!L4FUQLE5CRhtq|M1Uf-2Z z@n=%^0ZNGxaKHE6&UA)S;s6XZA+U{^~>7n74(HZ=hqBV9!AQlWkcY)?r80R*etY7 z)!=Taml=@NKnNiOGJNuqfF#Zzg{%p{MJVePDLD#jLK6UA+as>pfdm2u#6UXHB0ibS z4xcqmW5YVRuNalf4}>H#4!FFPnZzKbI5W3{G3&v-1o0>n#H!z9uFxrveqm-nl$KZ@C(D^Kk#X^*3`$r+UQrop$`n((PK99J zUrQQGq%ymaF)`0Ks4F}!UNrnHOYej>22TODA%+z2QUve|(@XL$%7U0w?ZZT*LM<`p z1=S~r0)_*~<2P`CB>pU};y0z8kcaQ)-9hm2*X7;RlKJtd%C$_5EG=ChlGir}oq&hM zTBK*b`%t2lGB4|AU?+4}``4D+GRhZ!a5wFPw>{;9f=SVOxfgAB=M?PVS0sEB%yDuT zQAt>2dCCE3>9;Asj8`t%r9rYIKn>iq^Yiw^hzp|if}8b#7nWsFVk`P^C{hNK@~S{| zOt8>uj5|d9(cGq;_dr42jIk-FD!JPkJp%%VL<7(Oam;a5xbA(xlYd98L28Mv{kg_< z-i+4+6S2Bgo=UCBU(yO<6+BIv85RP0ML1Kru&==0$}YICR>wNnq5&%KF<39^lt!7a zaLS9qeN#m2uglLb{H!W#!3ij90+~<=Kw9nF_;L6UpUh;q^L6T0cM;pR^-Q_|TiILJ zbRO;uhK>mn;rGT4z=_`(e#a=F)N0>U2ra=fk=aO_7C^g_*_9`;H)8ue!ms0k$bnga zLsdLLm+AF2lK8B~kATLyi;U{&j;$yWSu@p-xcb z@!T=ax6Qy)?dX-4LVC%)4DeE#KOn;I_i_8FoS_q48+=}W$pa++gzSgl*AW-<5B%@^ z#qY{D(~0aL-kSK|OJMIvDDNnxJFQ&xwU4ifNpLM7;zDWDr%ty8RLGTCsOW0x%i7Io zJ!ExDGNX{|>mQ$h9ehJ)?Kc+kgze(g;f3sfx?3EEUKOORb_E@CUYx2Ru)~1t1u~?D z>WrQhX9>vEYCL0QDEbXKCjtW!>}^+7-fTBUWT96ERBv0_q4R_X=k!4wdh((2KeO5ZQjc{IBU89)H#K6nqU)QR{d~JN}vJW;8AWT?xsfNUk#ANyyC;; z8!O|ah5d2C%r_in1S;aGK6f4j)`-zpjmk{Xa5829Ze&brEwhCapSE6dC#MfZqfFHs z_Z6#yr*89{0)gRguy(_|2D&}bHarK@*!~8y8!|{y9_Jez6KXGLw;NzJt@7Q1@IeNc zaM_)s5DrCbjzK!_9CzGqKf*oW{uO2ii<-m`W1{yPCm?lPcZZokHQSBNEo1IB(>?Zj@wYHIUyKfwa=%xpnj1JXn8TMXGcuiptAH zIUf>H63yJG2oVK5Int`qa#+oAW=C}p0Af$o(l+1CQ18Z>Dw*OiLs>w~kn>&x(!yvv zEa-$v#N%rdCQ;Yn7jePti8Z12lHQ z{o;)A_cnz&F&U?eH> ztFE+?;F2d4jcM%fzQl4rj29XL9v`znskO6--90Ws#J5L4>_E&{bm1ZG5s;Z^?S_Gq zLufK^#sjpK92s@1N98gCYP=0IzmiC^-EF~a09|`39iB^qi8??Znsr{ICTi>eEN|Qs z4Gju`tJkd>nDgQ^XPuXrxd5X@k6ybDzFeEE4h;(Du# zQ|$ty!Tl?M4$X!c7yoxAVksq19LKGM5M}N0yhAc^JpT?e21Gl;zal))j!#e|T7SXY z`GptN6okxOT9=V%?FBG)se}(IB#Pp8Q7QQg9w72Zm*{_b0#Uwy*yMW=3tLW-yU2&qXTV!=(MPQTa8^w!i*%r5*l^L!|2D0jj>^VpSHvtfmEI6>k*TFez40og9Gg4@ zwOdV9C8fa1--2upf5CQy7^;S3q_`H|CDwI03*R61I!lHIOQ4SU$>-)Kiv`mcM z&K6sq@^Y?;D-T#qs6N$$tR_hIN$yby}eYSmQ)9N?t@QT=V4k?1u+&xQXKsgT!Dxal7Nq-4BD(32?LGA%bL2Q0tvx2?o3jP z0LABuoDhHJ>o_K^JkjI;Vl{Kt#1a!?$Ck+Ec&lkkOL`@gbn2{$Y#^P4SZ=^Nrj!w+ z2+6Ezeb;~DqjqM>*F^@qQ_A*jshnJlhX)magir1ptv|!ma9wYkRFEE!`rfU|WdPiF zjHp_LXE<0wrA^?z_JKSIBfRQ(!PjlIN>XyXL-khA1-`zUJkbF;Cn81_L^1oNMv2<& z01!CuU!kVBz5E1;0YC4Yh>$q%Umy|at>f}LBn1#5mFWj{p+a1pGYz z(g#TX=?Bg?8;|!N^ACw!wF>!;?42JDlMlK^HP|svkoE7$uDIR!<$BC}E!7_g@X}gU z_IO=S{Cyxj`qjzHS}kzRWKRuykq(DSqdgdmHUs$aFtQHar*WCFOXP-XQ{QdK)rT}`;hk+R<#(0B!XANy&S5HTlyjS(lF)m~|HKituaI*QP-#LZ07u?UUI9g(Q zU?Pfa%xB{mpP~B9T^0sZ;Rth*#;DNFdTI~v|J~sh+*%xF_jB=k*T4U}*>S4wj-jNQg1KJw( zc_~}-LcRQ$_aJO3z`k%{9EX8 z1E#}}NHsu{lZo}ZJn-iq^W>!fjSkPIkOZ#l4XrB^?)HQYBDhY5eE5SO|BCe8j27!aDGp=Pqkw29ByCSV zDjk6Wz$IX^th8GDfeYy;fR1%+tp)q;42dMMw>17gquDiwH#~j5Y`_iSmEdH z*K5_6f;ZOcSO7Sw@qziakug-fMp?XhpAc=-mgB;>_@;y?Jd|h{UGmRB)u4=l<*%2ZNO zzQw_zE%Td0;lqE$_R~x1?li~ln;=K#k#1jr!=)n^_^#lK3`?!hU4_+RAxUQGp;8^_ zsJcxB^Z=Zc(Mat6bd}{F7o<$63H-V$-AcL2L`kZ}sJ)Qa1DmP&Srr)w}INPTqp1-yy9U;Dj6=77xwZ*DUHV3z{P7KkwGre}TqBFR1S)uEKlQBDl0| zA|5VE4l0Pb?63|AgCrj&(zvvZQA^Rd!;Ys1q4j$f!1ePNe(z`S`@!sn7OAx4KjCx{ z1;>miV!_~nDax;(L;=Mdg~B%Omc*U1QQ zk}o~#`^&FaTH6)N7^4pF15FKcqAnMJIA1e4%UTPfLZaM`-Ja3y8L5GBzS5;NTkZ8K z+?IEeA5vjrkaSoIBqpo~6C_x#%+E=)h4brrKu;ZGylsNR%3roli79HAVYg3}dCC2= zkM!DqCDO~W6ah^jP&LZFau$i4CsIztRWyhM^r#x@Xq>P$A(ICaU1lAvJs~4t{Ry~U zalQS^;@v%C;ACX0OB zzW8@WY4p06^&ic}z8B_9gjRRDc8=-}7SzLiU2w<{rqbkIVcur_HsLQY7svUf>68KZ)6NA2sW zSu3)ZpylS%25alJGO2^B@%BE?x}eknT|=v&L43fS@=J=Ml$&yRwX&X{KnScGkT<&S z;=-c0vK6v3pCPCv^vWPL{##CZLP~Ioh(terr1e^ymAm=iv!QZKF;Ad9?Sd@eAMA7YF!W{x>w5}@3 z8MqAaO_8CPc}Ire^e{7iRmXVFt+NM!C#LF&sQAn2c+`CM(`-G+qz=4blQE7EktxSzzoL`{&g!|XOMls8eY9l3HVw4^1s}HEW`@Qv|gWc zZaGLrpvl6W8D8$O1IS7`iy(w==JbmJG-ab>4(1T3^9w{SC>T0JTnH^`CcMI+T+~N6 z|BL~W&K(w0WO%#(za`lSQW_mt)}cs@dG0y`$)h?eE>3UI3Z!v5T#b#YT1XnYsiH6_ zK2FMFl2DzMiFQ(7gVt=5#JE@_1t&?sj^{FiWKCGLBO4cU3&M{hDSV9tor@5lNrhU` zFveHb&8BgOBVPHFf#D}iZxCZ()8`!~H~6HOERv8`Av|qR<(|?k)xokU1+hw~oDwVqk~Aua0n&ct zfqgk5zaVkIvKMpC0C&tx^k#?&PL`tRO70fq2@!_Y2c~~PKYj#S%jGr^otp&T{^qYQOj1I=ExyDMQ;>{xGjc?KdeFe{g$HTl?vMPltuGAi@Y z3JFqsrkD(O)%V|haOy!}K;+!5kKu!?qNIl3+p*=5^?$xB+Nyg$VWJp=nmqOSC?{7` zhk_GA8M)ngvZq<4G!u?7y}SpNSx_!Y!-NZBExo=9o^njS9<0zbK`AUU5%XTHdwd|N z8ZQpB+*W#{^C}3`Y@PMGFfMh)cz}pUIRF3~Gf6~2RIsADY}jp~uy$02iO!pG6_c&< zN~%0u=SB30n(o(r7s{gUKSS-Alkq`m(G~@J9cEJhW|HdcxhA${7zl{m%ED?$5p;W@ z#N@n<(~QAMt?LUsIY}d#aY7ojPrw~&9b?>CVAl9rO-Y~yVVJJaCYV0Z`x8dofs6Yr z6RE@cg654k`O}%{nIsaJ{SMP6x+%u}6=+hE1?AEvhA|vZFQ0HtTAh0{z9!08V@)u< zV5|lJ9TG**Hd=ghI(mP?tc$^%d@r|>W0~h-2FmP(rj{7HM9%8KOYl->DS%GqQUV0|8s!>MN)Qp8aHL>xQTvjWFxJw)v$#!Idvx%S5^YSK z?_;new3ubR71-^if{cZnmX4Sct$nIsBQP%Br~shZ6FibP#+-l$W_i$2Z=BY+7!_$1 zx0F38BnAz>SxNgPPl=M&n^#d)%eERF0-`KZgH z`>d5D2&CMRWknS=Ouulnm-;;GVbl1aI`kQ;H=NhR|M`D?M7IC)|LK3m=Py6wrMG=y zi11p5va@^o$)55c=+EO`=Hx+h!qGTrmU%|=BHHdN-#)_^MLdePRUkt=Rl}i|VS>AN2LD=JvPj#tmAe` zGbSb`G*v$EzxK#8JU(Xby3|6L6ljjV@1w1MTg57;C z3b^NEFH+yPdA;t&LM?aKgAGMKm!xBQ6kxB{6jbF?rpW93U;>~lRlA~aGI@+>j>TP4 zHQuyqfgC;oGnm{SUF}kDx@!RLXomtg0BO*6z~?J(G;)+{4dtLwHU&c9$xaAe0X8K} zk9dxxRTTzwV>@DD^ik+`;v-&|I{={ef>48LhuDFC{=fY1_^zCh0$GIEGm(7>>53NdMr)y7QE3T-|AxFX* zb|o>T!!;cijU0k;o#@BGUWqY~^|RuG%S-l}3^CS&QlgMJdE#BMCrue;0?ERsIA)m) zv{<6EURSz}Bce;vLZQhYMuG^+ORYauGbarVs|C=f6W<~YiaRiQ5lImDAqvdU+MOK` zADBWTK&!n;KiaOxl@#Pucus@{Vt%DXw`{V8Uc_8ZsTxsEJF>Gx8Pb&$c)V55{cX26 zp4S_C`-B-^AYrsit8fx7W>>r2Fs`#MSAj@H$^{8tN{U@cqeBs^k;j%ueFg-tNV#z* zqz$6ghd#X6cx55PsyH^7Bs|Wtp)237al+AlBL~PIIYhpj_V}X*$YWRWzEtYNf3F97 zF0^+Ujo2)l?;p4|Rfh^vVJe4#>ODV6dwo&;c`hxipt^SJQFhP?0jJR)Y+)Hv& zfZf!7$I(AQn(_7?1nXrz^z3-SfB!%HkNB_t*?)`w=U@EIuX?5a2mjvR$3Oco{|o%% z|KuMMe?5s$S#BZ8a*_p|H(a6z1CO$!LHrrE4gt~cx|i8f%jQPbQB5|gh6JlsvwZ!W z?zofDek&^sU2$T)3=kC>^H-?8kXC?_h}!cz1pe4NEm<_=l38cDwA9KX8_XfhxM20# zHAtBSIy^2!ol;i8wJ67yBx_k`FgFk}0K3)hFW@1?nv*I`;-X*BYL`?8AiSv2y(FJH zIMZNel-x{e317^$N}X#>-K`x&nb?iaL^EU-XmZs~gZLUXLBt&(7`m z(X8X`>sMIsr5@xC{brRnF$j5(71law4P_noIbprMd*%Rkyg(x`=2xgb5#cfcX#&%R zF)6{ZVn7gn?njo=v7?7jEuM4UCJewRC)g59D znAaO5Iqi*kqPHhBJANY)R{jaUwa*~=1J?5bOu|EKeNeqGNQKVY-0PsN>^@e5_1;32P&H=&&XvWwF zhbis0o~uuNou#u11ewb)wSpFMIc`vl!5xG_nEl8BJBr4c54a<;@(U{*IcLd$l(w=- z!5D!telG6NLFLV^S_Hm5B)d#5t*gn=b3X9;kx&>rCvw2Wi^NJ_J&d+fl?oQ>vcrC& zte9gLYGty`@#~j2{F8t5AK`}|e!}m5|2zEt_rJsMe*f3_@yDO=`uc{?pFiX0FF)h+ z-~EiwKYzx!F1+5aD%L6_NtT474Y~03br84*)@Ht)XE|`F)RX@Jfki7HXNFXIlr>MWQ8l2kI-puK&!-h5Xs~7VpFBwkbd!yNQYpu zB|w&iz15}2FeaPtkcwuMhZTwGl=xcf5ZoVbJ{`Cw6YR`a0OJw>H(b}>pdX*O^JvPY za--y$QQ*Z0PHMHdzs}DL4JvtAV-D^!t^vEzsJ$%*jfL%K{R*mV(s z7_WdF8)>A@+>P3;-dE(iasPP|Yjb}i3S~yQ;AWAk6P^P;9IXL4PguKEy{{nWS6tW6 zxV2j~QW~rR(bu@p8!sf&LI$nPZ5 z^ZkPSeTh{5Vj9VfV2|(ox_={aJ*>O3=(~v>a$AeXMc5^fY%R5OFV+dmtG}}!%6KP* zaq-Qb3EsRpv6L$)o%<019atFz=|#E9>K2#IMLiVn8WfkMm=C%PzR^XDkip`L=E|Cg zmK-yna?rYotVLzHIh>7u(se@t;REgq7|F~>hLiJUw)<6hJ)%||Sk&>XU}dGjD?>s4 zRPB?JQ^gMOCEMFlV&5t=?nvRXyxDV=H8qHKwEhY0c!u>3X;!j_mRW+ltu_6z!09Vn zt^M3{i0!K7j4 zeIs%*EA4VoRWMCe3ZUej+~8v6}41v5vvHCZmzjwfgY z$qhwb!0IgzFB#cbCtCzxX$+2D=ZWKZs&M42T@WvXG`O@)(YX+4|hW3mLM?h63T&ZtatDWboF+CA?=) zgeZ&TVluHAEvkM~5g}ELIZJ}7eT_4fZrfp`Bum7-EVpeN3;1We;E9wb7GFs11wLQ7 zu+~nzLl71?{UoC(6WWiusa|L$Tg5I=T6q~|Kru#C%O`ic~w-=3l9 zaN%-(1s4U5oP>okuQhmJ&RUWeAz~dKBL`&O(3)(ZB!6}lz6)lg+Kq&P=}sQnw&{<` zCLM@zFFDoTDGV17Mrs6nC(os%v&BV=xL|V7f~>XYE2=kf^NxPp2-bwKu&}*g&aXfk z=J! z@z*yJqgYxwY3(fF;07?_v16VCg;EF;2=C2k35P5={Mzy1v%H~je1zmFLK zD}#?p$2Ch`s16fDD;aU6)DtNNRifTKC^Lr#=5@iE(oy(ZK0toW;ql!GmNME^C626wGnaQfdZW3wu-*Z6xWQjTTWZfxW(FR@)NZCYW>9q(vgHSuuWm z!i6b#(S-*eJG6xq48JHTsKFYYDIWCe+a?oBO)^zx^KMe)m_9H@QG5jueI}7M&YI_g z4-_y_&O@X=rq-T01?_k$CrdBfzSJ^ZcLzw~BKR-juWjA zE!-jIE+><DZsc+wEo1yA+OCKW0F46Z@21d zaR3Kq+yNwdf5td3gaV35X=z+Ij^~niFN7GZg2RFXb~LzOG!fX1MhF#He?nel*g+G$ zvw(`r_J;5i0)usfpi0@m#XT>94sFUkU?Vitr=c}YzSs2z061<1FI?cudQP<4OF>)% z5}i~2IFXv@rVQ;`=X<4){f8;WHF3ZHjN|$D={&$%AbF(W*CdhCVX7EDtF^E~bPm88#`Ov_-9=Vh%xk&DoIM5g-|Hqm zr&pSF$Hr<`gU9!bn^{N(B3aUv^Ueof0r^nVLrgAQ19T_x=V8^yL7OoH7ZswD0~v2+ z9ev!ud6*1H5LF)j#*dBKb9uSstQW$c324CulONy+l5XSV z12F;V2Yk@f!1`V84_L=rH_mnxQ_3Of3`#aK&SJd5+A}9gul|^K*`|C~zNBDib-ApN zcLca23SlM%alz~byX7UnK=p-F7-n5yDoChwRb^p%SOTWh)Oq3;zihoHN^-5e+&Mp^ z>5a~^vZkvZ$Z(c91w(vvdL=w*9o0PrAGFBk1TbK7+c_#(vY#jGyV0~>2XCP6sz^1Fg@`lJex+%`{XY}JIwvEOcu0_wy zb3e-h4c2aSR|@l7W(H`-yiP=3=+be;h2Ea({m;s3h+xh@#2x+ij2MA9Um>mGy6%Yl z`WrhyepNi=j~^gAVZOLX@10j`I(g5z`Y@HmL)MD;urJAcf8fMkrYAS*Wea9E`9hVv zXKF>vjk`cp5BjQ9Uv%A_PG2(FeAL=9HAh*h)}Ktv){bUS3h+fgw<%r8+^wc7d!{79nd;7usYWBs{Bhp(J;Ma zF9d*2!mseQPk2*S^0^YsQI}(1G8LE==?7)EyjPD&0D0lo|BAOK;qqQh!S{qh+JW$U zh1O)V$+Q*99QN+D7ez>=6UD4?FS5|)B6T~?lKLiHH(HHy$=jqVkk%@nkid*xW~MlR z6im6-RNIPeF}M2;SwXo;xvnme<^4ZsH{H-lK6u1rSG@|VmC>EA3juOYHe+THfopNj zi^iQGN^#!M?RaN@ab7YqD#`o2 z%1FRW`1>NH+-_uBK6bJRPRnC%`nMo~rX}4?jFZ4mEpVh1iGiATB)XE#nraaKVUs#nKx^d}sj0gwc5`~Du{JQ=QZF7A7~F<_4gxS$;Zzp@HHvtIU< zN6Cqq_4&+6UYG;zH&JPu|K|VBFS6?S4c`6T?&I6ZWXo0`K#KW`e>f@u{_w6xU2KJ& z!$J)@<1u9j-dX*$L6g?Kgx3;$C30I;){ck0I2}H-!alWt<@%YeVY$GBj{|fa0Px&- z3Iv0CJRA*>v%ra5@=Ia9QA@SnZb+pzE}*`w#Fr8!QhgmWPKY=WAws4UYfCF7t(|)W z(9L7bFy`3H=9o-)hw%`@s=!@^Q(}lwmQn4HJw;tG+z=oZLsTI0gfj-LFeV71vmu30 zU+DH^`b^hb-SByqtd_}QRLucYNfPzgrK=1Rnbo416Eg#c^g0OGIYUoWWu4L) zl(fdSg6pi%T?f=F#}Kuzk%CvYjm~Gg>Y!{?MvrF4RuAUxzu^PiEzrQMi&$e@=DtAP zFmoWMkQo(G!P%4LP2-N%8pe3H!<5DV;PF)N*I7T8$@gAfXvb6Kf;!-d>2Gj#9Jd$5 zOlX@J18Cr6XbI%J%CdbGUR7)H9q{X-^DZ5|{eb)Ve?##DOb*aYZFE(IW8y0!9rHY^ ziq^1ug355x+=B|Mp^cf8C$|eh2zH zkoWh9-qq;!(4%TMl1v)Y0((^W$jQs8gK76LFHX|hmzeEJ*Yt_-!GK@YqDDQisD%f+ zAFtkgXuq+S@ekW!#$3Cl^iq5p&jtW`QwdHzK);h%IvpOfaz9ZC>NV|AR)R3#MkIS%GcXVQMma=PVeJ{2T2@Y4inYq5;E6F_ z`JguDbC3zcT4G;y2-=oLecmz0z1&sY6M1R;5kRIuyCQ{-Gg5$8O|BI^8Xq!I+#B&{ z-P#S-+m>LM;Xz3z%r!-_rO-3bK(aD-AlL;^DYUgS06K7uV#V1LOQ2#R<{2A!BcC0{&UHI(xIOEb4Lrnj|D3E@-zGT<6aiGe8BcpqmLq2r?UH0JFL}6i)zn`J{YXM7=~I z_1{WnqzYkq6$mpHUIzs07*qoM6N<$f+888 Ai~s-t literal 45163 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

f8@q%9Y%{e3dEe5KuuYGX{P+Sv; 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 diff --git a/files/launcher.qss b/files/launcher.qss index dad87022c..8be235f71 100644 --- a/files/launcher.qss +++ b/files/launcher.qss @@ -22,7 +22,7 @@ stop:0.9 rgba(0, 0, 0, 55), stop:1 rgba(0, 0, 0, 100)); - font: 24pt "Trebuchet MS"; + font: 26pt "EB Garamond"; color: black; border-right: 1px solid rgba(0, 0, 0, 155); @@ -54,7 +54,7 @@ } #ProfileLabel { - font: 14pt "Trebuchet MS"; + font: 18pt "EB Garamond"; } #ProfilesComboBox { @@ -82,7 +82,7 @@ padding-top: 3px; padding-left: 4px; - font: 11pt "Trebuchet MS"; + font: 12pt "EB Garamond"; } #ProfilesComboBox::drop-down { diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 9a6cde7ba..dbc20f3f8 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -6,7 +6,6 @@ set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) configure_file("${SDIR}/bigbars.png" "${DDIR}/bigbars.png" COPYONLY) configure_file("${SDIR}/black.png" "${DDIR}/black.png" COPYONLY) -configure_file("${SDIR}/Comic.TTF" "${DDIR}/Comic.TTF" COPYONLY) configure_file("${SDIR}/core.skin" "${DDIR}/core.skin" COPYONLY) configure_file("${SDIR}/core.xml" "${DDIR}/core.xml" COPYONLY) configure_file("${SDIR}/mwpointer.png" "${DDIR}/mwpointer.png" COPYONLY) @@ -54,4 +53,5 @@ configure_file("${SDIR}/openmw_journal_layout.xml" "${DDIR}/openmw_journal_layou configure_file("${SDIR}/openmw_journal_skin.xml" "${DDIR}/openmw_journal_skin.xml" COPYONLY) configure_file("${SDIR}/smallbars.png" "${DDIR}/smallbars.png" COPYONLY) configure_file("${SDIR}/transparent.png" "${DDIR}/transparent.png" COPYONLY) +configure_file("${SDIR}/EBGaramond-Regular.ttf" "${DDIR}/EBGaramond-Regular.ttf" COPYONLY) configure_file("${SDIR}/VeraMono.ttf" "${DDIR}/VeraMono.ttf" COPYONLY) diff --git a/files/mygui/EBGaramond-Regular.ttf b/files/mygui/EBGaramond-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dde4869030022bf4d25ac5ea4403795ebd864fd1 GIT binary patch literal 231904 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 T4s-SWr@ZM*DnIDIeBu8Aczgq4 literal 0 HcmV?d00001 diff --git a/readme.txt b/readme.txt index 8f360235f..36a732015 100644 --- a/readme.txt +++ b/readme.txt @@ -7,6 +7,10 @@ Version: 0.13.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org +Font Licenses: +EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) +VeraMono.ttf: custom (see Bitstream Vera License.txt for more information) + THIS IS A WORK IN PROGRESS From 426c2785058a0a4b31148351e5c57dfea18cba22 Mon Sep 17 00:00:00 2001 From: Pieter van der Kloet Date: Sat, 24 Mar 2012 22:02:55 +0100 Subject: [PATCH 120/152] Changed the OpenMW default font from Comic sans to EB Garamond --- files/mygui/Comic.TTF | Bin 129196 -> 0 bytes files/mygui/core.skin | 6 +- files/mygui/core.xml | 48 +++---- files/mygui/openmw.font.xml | 133 +++++------------- files/mygui/openmw_button.skin.xml | 5 +- files/mygui/openmw_console.skin.xml | 2 - files/mygui/openmw_dialogue_window_layout.xml | 5 +- files/mygui/openmw_edit.skin.xml | 8 +- .../openmw_interactive_messagebox_layout.xml | 7 +- files/mygui/openmw_journal_skin.xml | 4 +- files/mygui/openmw_list.skin.xml | 8 +- files/mygui/openmw_messagebox_layout.xml | 7 +- files/mygui/openmw_progress.skin.xml | 5 +- files/mygui/openmw_settings.xml | 4 +- files/mygui/openmw_text.skin.xml | 18 +-- files/mygui/openmw_windows.skin.xml | 15 +- 16 files changed, 95 insertions(+), 180 deletions(-) delete mode 100644 files/mygui/Comic.TTF diff --git a/files/mygui/Comic.TTF b/files/mygui/Comic.TTF deleted file mode 100644 index 309894bafc18692591a1efb70da12fe958eab200..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129196 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

ZhZw<>0$6AUeSrBtF!O_< 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 - - + + - + diff --git a/files/mygui/core.xml b/files/mygui/core.xml index 5bec13aef..7417328cf 100644 --- a/files/mygui/core.xml +++ b/files/mygui/core.xml @@ -1,28 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw.font.xml b/files/mygui/openmw.font.xml index 454c3caec..e7d0f50c8 100644 --- a/files/mygui/openmw.font.xml +++ b/files/mygui/openmw.font.xml @@ -1,95 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/files/mygui/openmw_button.skin.xml b/files/mygui/openmw_button.skin.xml index 0533a360f..1c6893026 100644 --- a/files/mygui/openmw_button.skin.xml +++ b/files/mygui/openmw_button.skin.xml @@ -45,10 +45,7 @@ - - - - + diff --git a/files/mygui/openmw_console.skin.xml b/files/mygui/openmw_console.skin.xml index 1c8740ede..598252734 100644 --- a/files/mygui/openmw_console.skin.xml +++ b/files/mygui/openmw_console.skin.xml @@ -2,8 +2,6 @@ - - diff --git a/files/mygui/openmw_dialogue_window_layout.xml b/files/mygui/openmw_dialogue_window_layout.xml index 6e833004b..11ac41cb3 100644 --- a/files/mygui/openmw_dialogue_window_layout.xml +++ b/files/mygui/openmw_dialogue_window_layout.xml @@ -12,7 +12,6 @@ - @@ -21,8 +20,8 @@ - + align="Right Top" name="Disposition"> + diff --git a/files/mygui/openmw_edit.skin.xml b/files/mygui/openmw_edit.skin.xml index 609dfe2c8..a86317d62 100644 --- a/files/mygui/openmw_edit.skin.xml +++ b/files/mygui/openmw_edit.skin.xml @@ -11,8 +11,7 @@ - - + @@ -33,8 +32,7 @@ - - + @@ -51,7 +49,7 @@ - + diff --git a/files/mygui/openmw_interactive_messagebox_layout.xml b/files/mygui/openmw_interactive_messagebox_layout.xml index 4ef2243d4..744f21227 100644 --- a/files/mygui/openmw_interactive_messagebox_layout.xml +++ b/files/mygui/openmw_interactive_messagebox_layout.xml @@ -3,14 +3,13 @@ + + + - - - - diff --git a/files/mygui/openmw_journal_skin.xml b/files/mygui/openmw_journal_skin.xml index a0d6ee2e8..0ef87852f 100644 --- a/files/mygui/openmw_journal_skin.xml +++ b/files/mygui/openmw_journal_skin.xml @@ -14,13 +14,13 @@ - + - + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 98390367c..0ac8e03ba 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -167,8 +167,7 @@ - - + @@ -177,10 +176,9 @@ - - + - + diff --git a/files/mygui/openmw_messagebox_layout.xml b/files/mygui/openmw_messagebox_layout.xml index 244f58c99..81d1c0a57 100644 --- a/files/mygui/openmw_messagebox_layout.xml +++ b/files/mygui/openmw_messagebox_layout.xml @@ -6,14 +6,13 @@ + + + - - - - diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml index a5fbfb0a3..c4b94e28e 100644 --- a/files/mygui/openmw_progress.skin.xml +++ b/files/mygui/openmw_progress.skin.xml @@ -19,11 +19,10 @@ - - + - + diff --git a/files/mygui/openmw_settings.xml b/files/mygui/openmw_settings.xml index ca62294de..c63f962fb 100644 --- a/files/mygui/openmw_settings.xml +++ b/files/mygui/openmw_settings.xml @@ -1,9 +1,9 @@ - - + + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index d62a5b8c0..6ae14c558 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -2,40 +2,35 @@ - - + - - + - - + - - + - - + @@ -45,8 +40,7 @@ - - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index ea8eb5330..a986dcffc 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -189,14 +189,7 @@ - - - + @@ -221,8 +214,7 @@ ------------------------------------------------------ --> - - + @@ -298,8 +290,7 @@ - - + From 8e76451cda571ae4c58a795a3c8a18e5fa9bdcc5 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 25 Mar 2012 02:00:49 +0400 Subject: [PATCH 121/152] Fix launcher style installation on OS X again --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a1b98335..d5f0bffa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -501,6 +501,7 @@ if (APPLE) install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/plugins.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) + install(FILES "${OpenMW_BINARY_DIR}/launcher.qss" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) set(CPACK_GENERATOR "DragNDrop") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) From f959a5cbeb48e23ebc67d4f8ec571e51b9849167 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 23:24:19 +0100 Subject: [PATCH 122/152] auto adjust size of map window title bar --- apps/openmw/mwgui/layouts.cpp | 1 + libs/openengine/gui/layout.hpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index 5c5a977d3..9c49c62ac 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -202,6 +202,7 @@ void MapWindow::setVisible(bool b) void MapWindow::setCellName(const std::string& cellName) { static_cast(mMainWidget)->setCaption(cellName); + adjustWindowCaption(); } void MapWindow::setPlayerPos(const float x, const float y) diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp index 3c03423c3..e73b2d1ce 100644 --- a/libs/openengine/gui/layout.hpp +++ b/libs/openengine/gui/layout.hpp @@ -80,6 +80,29 @@ namespace GUI mMainWidget->setCoord(x,y,w,h); } + void adjustWindowCaption() + { + // adjust the size of the window caption so that all text is visible + // NOTE: this assumes that mMainWidget is of type Window. + MyGUI::TextBox* box = static_cast(mMainWidget)->getCaptionWidget(); + box->setSize(box->getTextSize().width + 48, box->getSize().height); + + // in order to trigger alignment updates, we need to update the parent + // mygui doesn't provide a proper way of doing this, so we are just changing size + box->getParent()->setCoord(MyGUI::IntCoord( + box->getParent()->getCoord().left, + box->getParent()->getCoord().top, + box->getParent()->getCoord().width, + box->getParent()->getCoord().height+1 + )); + box->getParent()->setCoord(MyGUI::IntCoord( + box->getParent()->getCoord().left, + box->getParent()->getCoord().top, + box->getParent()->getCoord().width, + box->getParent()->getCoord().height-1 + )); + } + void setVisible(bool b) { mMainWidget->setVisible(b); From 3e98e28059b1c26e1973a543e1a3d00489cb94e9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 24 Mar 2012 21:05:03 -0700 Subject: [PATCH 123/152] Use a better method to get a more even randomization --- apps/openmw/mwsound/soundmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 3b1f188e7..f626ec158 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -350,7 +350,7 @@ namespace MWSound return; } - int r = rand() % total; //old random code + int r = (int)(rand()/((double)RAND_MAX+1) * total); int pos = 0; soundIter = regn->soundList.begin(); From 7b3ecc290e2a75a6caed1cf42df4c2e93d3fe1bd Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 26 Mar 2012 01:12:06 -0700 Subject: [PATCH 124/152] Fix compilation with older OpenAL headers --- apps/openmw/mwsound/openal_output.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index a0b9c3a72..c06947403 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -10,6 +10,10 @@ #include "sound.hpp" #include "soundmanager.hpp" +#ifndef ALC_ALL_DEVICES_SPECIFIER +#define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#endif + namespace MWSound { From 0d552c10bc913771b73ca4deb38f3dbaf97477db Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 26 Mar 2012 04:10:47 -0700 Subject: [PATCH 125/152] Use an empty MWWorld::Ptr object for non-3D sounds --- apps/openmw/mwsound/soundmanager.cpp | 29 ++++++++++++---------------- apps/openmw/mwsound/soundmanager.hpp | 1 - 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index f626ec158..4d47e5f2c 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -69,7 +69,6 @@ namespace MWSound SoundManager::~SoundManager() { - mLooseSounds.clear(); mActiveSounds.clear(); mMusic.reset(); mOutput.reset(); @@ -207,7 +206,7 @@ namespace MWSound { std::string file = lookup(soundId, volume, min, max); Sound *sound = mOutput->playSound(file, volume, pitch, loop); - mLooseSounds[soundId] = SoundPtr(sound); + mActiveSounds[MWWorld::Ptr()][soundId] = SoundPtr(sound); } catch(std::exception &e) { @@ -226,8 +225,7 @@ namespace MWSound std::string file = lookup(soundId, volume, min, max); SoundPtr sound(mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop)); - if(untracked) mLooseSounds[soundId] = sound; - else mActiveSounds[ptr][soundId] = sound; + mActiveSounds[untracked?MWWorld::Ptr():ptr][soundId] = sound; } catch(std::exception &e) { @@ -272,7 +270,7 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->first.getCell() == cell) + if(snditer->first != MWWorld::Ptr() && snditer->first.getCell() == cell) { IDMap::iterator iditer = snditer->second.begin(); while(iditer != snditer->second.end()) @@ -289,11 +287,17 @@ namespace MWSound void SoundManager::stopSound(const std::string& soundId) { - IDMap::iterator iditer = mLooseSounds.find(soundId); - if(iditer != mLooseSounds.end()) + SoundMap::iterator snditer = mActiveSounds.find(MWWorld::Ptr()); + if(snditer == mActiveSounds.end()) + return; + + IDMap::iterator iditer = snditer->second.find(soundId); + if(iditer != snditer->second.end()) { iditer->second->stop(); - mLooseSounds.erase(iditer); + snditer->second.erase(iditer); + if(snditer->second.empty()) + mActiveSounds.erase(snditer); } } @@ -415,15 +419,6 @@ namespace MWSound else snditer++; } - - IDMap::iterator iditer = mLooseSounds.begin(); - while(iditer != mLooseSounds.end()) - { - if(!iditer->second->isPlaying()) - mLooseSounds.erase(iditer++); - else - iditer++; - } } void SoundManager::update(float duration) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index b7c883a13..8d76ba100 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -44,7 +44,6 @@ namespace MWSound typedef std::map IDMap; typedef std::map SoundMap; SoundMap mActiveSounds; - IDMap mLooseSounds; std::string lookup(const std::string &soundId, float &volume, float &min, float &max); From 633e80cded155919dd01a7f33f2181ce68e6b327 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 25 Mar 2012 16:12:23 +0200 Subject: [PATCH 126/152] Issue #225: Added cleanup of maps used in PhysicEngine. --- libs/openengine/bullet/physic.cpp | 53 +++++++++++++++++++++++++++---- libs/openengine/bullet/physic.hpp | 10 ++++-- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 07bad3053..756f3b0c6 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -173,6 +173,7 @@ namespace Physic mShapeLoader = shapeLoader; isDebugCreated = false; + mDebugDrawer = NULL; } void PhysicEngine::createDebugRendering() @@ -202,6 +203,35 @@ namespace Physic PhysicEngine::~PhysicEngine() { + + RigidBodyContainer::iterator rb_it = RigidBodyMap.begin(); + for (; rb_it != RigidBodyMap.end(); ++rb_it) + { + if (rb_it->second != NULL) + { + dynamicsWorld->removeRigidBody(rb_it->second); + + delete rb_it->second; + rb_it->second = NULL; + } + } + + PhysicActorContainer::iterator pa_it = PhysicActorMap.begin(); + for (; pa_it != PhysicActorMap.end(); ++pa_it) + { + if (pa_it->second != NULL) + { + dynamicsWorld->removeCollisionObject(pa_it->second->externalGhostObject); + dynamicsWorld->removeCollisionObject(pa_it->second->internalGhostObject); + dynamicsWorld->removeAction(pa_it->second->mCharacter); + + delete pa_it->second; + pa_it->second = NULL; + } + } + + delete mDebugDrawer; + delete dynamicsWorld; delete solver; delete collisionConfiguration; @@ -239,32 +269,39 @@ namespace Physic dynamicsWorld->addRigidBody(body,COL_WORLD,COL_NOTHING); } body->setActivationState(DISABLE_DEACTIVATION); + RigidBody* oldBody = RigidBodyMap[body->mName]; + if (oldBody != NULL) + { + dynamicsWorld->removeRigidBody(oldBody); + delete oldBody; + } + RigidBodyMap[body->mName] = body; } void PhysicEngine::removeRigidBody(std::string name) { - std::map::iterator it = RigidBodyMap.find(name); + RigidBodyContainer::iterator it = RigidBodyMap.find(name); if (it != RigidBodyMap.end() ) { RigidBody* body = it->second; if(body != NULL) { // broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); - /*std::map::iterator it2 = PhysicActorMap.begin(); + /*PhysicActorContainer::iterator it2 = PhysicActorMap.begin(); for(;it2!=PhysicActorMap.end();it++) { it2->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); it2->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); }*/ - dynamicsWorld->removeRigidBody(RigidBodyMap[name]); + dynamicsWorld->removeRigidBody(body); } } } void PhysicEngine::deleteRigidBody(std::string name) { - std::map::iterator it = RigidBodyMap.find(name); + RigidBodyContainer::iterator it = RigidBodyMap.find(name); if (it != RigidBodyMap.end() ) { RigidBody* body = it->second; @@ -293,6 +330,10 @@ namespace Physic void PhysicEngine::addCharacter(std::string name) { + // Remove character with given name, so we don't make memory + // leak when character would be added twice + removeCharacter(name); + PhysicActor* newActor = new PhysicActor(name); dynamicsWorld->addCollisionObject( newActor->externalGhostObject, COL_ACTOR_EXTERNAL, COL_WORLD |COL_ACTOR_EXTERNAL ); dynamicsWorld->addCollisionObject( newActor->internalGhostObject, COL_ACTOR_INTERNAL, COL_WORLD |COL_ACTOR_INTERNAL ); @@ -303,7 +344,7 @@ namespace Physic void PhysicEngine::removeCharacter(std::string name) { //std::cout << "remove"; - std::map::iterator it = PhysicActorMap.find(name); + PhysicActorContainer::iterator it = PhysicActorMap.find(name); if (it != PhysicActorMap.end() ) { PhysicActor* act = it->second; @@ -311,7 +352,7 @@ namespace Physic { /*broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); - std::map::iterator it2 = PhysicActorMap.begin(); + PhysicActorContainer::iterator it2 = PhysicActorMap.begin(); for(;it2!=PhysicActorMap.end();it++) { it->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 88e3699ae..200b96207 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -42,6 +42,8 @@ namespace Physic :btPairCachingGhostObject(),mName(name) { } + virtual ~PairCachingGhostObject(){} + std::string mName; }; @@ -106,6 +108,7 @@ namespace Physic { public: RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); + virtual ~RigidBody() {} std::string mName; //is this body used for raycasting only? @@ -217,8 +220,11 @@ namespace Physic //the NIF file loader. BulletShapeLoader* mShapeLoader; - std::map RigidBodyMap; - std::map PhysicActorMap; + typedef std::map RigidBodyContainer; + RigidBodyContainer RigidBodyMap; + + typedef std::map PhysicActorContainer; + PhysicActorContainer PhysicActorMap; //debug rendering BtOgre::DebugDrawer* mDebugDrawer; From a7ac0e526e2d942df619cddc737204f5c4b2d283 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 25 Mar 2012 18:12:00 +0200 Subject: [PATCH 127/152] Issue #225: Added cleanup of parts of PhysicEngine. Added cleanup of CMotionState inserted to RigidBody, and btSortedOverlappingPairCache inserted to btDbvtBroadphase in PhysicEngine. --- libs/openengine/bullet/physic.cpp | 13 +++++++++---- libs/openengine/bullet/physic.hpp | 3 ++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 756f3b0c6..8b9f3dfec 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -134,10 +134,15 @@ namespace Physic RigidBody::RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name) - :btRigidBody(CI),mName(name) + : btRigidBody(CI) + , mName(name) { + } - }; + RigidBody::~RigidBody() + { + delete getMotionState(); + } @@ -155,8 +160,7 @@ namespace Physic // The actual physics solver solver = new btSequentialImpulseConstraintSolver; - //TODO: memory leak? - btOverlappingPairCache* pairCache = new btSortedOverlappingPairCache(); + pairCache = new btSortedOverlappingPairCache(); //pairCache->setInternalGhostPairCallback( new btGhostPairCallback() ); broadphase = new btDbvtBroadphase(pairCache); @@ -237,6 +241,7 @@ namespace Physic delete collisionConfiguration; delete dispatcher; delete broadphase; + delete pairCache; delete mShapeLoader; } diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 200b96207..57ffe9130 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -108,7 +108,7 @@ namespace Physic { public: RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); - virtual ~RigidBody() {} + virtual ~RigidBody(); std::string mName; //is this body used for raycasting only? @@ -211,6 +211,7 @@ namespace Physic std::list PEventList; //Bullet Stuff + btOverlappingPairCache* pairCache; btBroadphaseInterface* broadphase; btDefaultCollisionConfiguration* collisionConfiguration; btSequentialImpulseConstraintSolver* solver; From d3b88b9e34ae0d46153262ebd1acf2627bb15984 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 25 Mar 2012 20:37:05 +0200 Subject: [PATCH 128/152] Issue #225: Added cleanup of allocated memory in BulletNifLoader and BulletShapeLoader. --- components/nifbullet/bullet_nif_loader.cpp | 22 ++++++++++++++++++-- components/nifbullet/bullet_nif_loader.hpp | 2 +- libs/openengine/bullet/BulletShapeLoader.cpp | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/components/nifbullet/bullet_nif_loader.cpp b/components/nifbullet/bullet_nif_loader.cpp index 82e9d7adc..e9aa626db 100644 --- a/components/nifbullet/bullet_nif_loader.cpp +++ b/components/nifbullet/bullet_nif_loader.cpp @@ -51,7 +51,11 @@ using namespace Mangle::VFS; using namespace NifBullet; -//==================================================================================================== +ManualBulletShapeLoader::~ManualBulletShapeLoader() +{ + delete vfs; +} + Ogre::Matrix3 ManualBulletShapeLoader::getMatrix(Nif::Transformation* tr) { Ogre::Matrix3 rot(tr->rotation.v[0].array[0],tr->rotation.v[0].array[1],tr->rotation.v[0].array[2], @@ -135,7 +139,21 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) handleNode(node,0,Ogre::Matrix3::IDENTITY,Ogre::Vector3::ZERO,1,hasCollisionNode,false,true); } - currentShape = new btBvhTriangleMeshShape(mTriMesh,true); + struct TriangleMeshShape : public btBvhTriangleMeshShape + { + TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) + : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression) + { + } + + virtual ~TriangleMeshShape() + { + delete getTriangleInfoMap(); + delete m_meshInterface; + } + }; + + currentShape = new TriangleMeshShape(mTriMesh,true); cShape->Shape = currentShape; } diff --git a/components/nifbullet/bullet_nif_loader.hpp b/components/nifbullet/bullet_nif_loader.hpp index 1fa2b6aa5..ed3aceac4 100644 --- a/components/nifbullet/bullet_nif_loader.hpp +++ b/components/nifbullet/bullet_nif_loader.hpp @@ -69,7 +69,7 @@ class ManualBulletShapeLoader : public BulletShapeLoader public: ManualBulletShapeLoader():resourceGroup("General"){vfs = 0;} - virtual ~ManualBulletShapeLoader() {} + virtual ~ManualBulletShapeLoader(); void warn(std::string msg) { diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp index 48b87e1e8..4593bad52 100644 --- a/libs/openengine/bullet/BulletShapeLoader.cpp +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -22,6 +22,7 @@ Ogre::Resource(creator, name, handle, group, isManual, loader) BulletShape::~BulletShape() { + deleteShape(Shape); } // farm out to BulletShapeLoader From 5185a28b6002d1e04b89632ff27a00a613634d54 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 25 Mar 2012 21:56:22 +0200 Subject: [PATCH 129/152] Issue #225: Initialize all class members in constructor. --- apps/openmw/mwgui/layouts.cpp | 16 ++++++++++ apps/openmw/mwgui/stats_window.cpp | 13 ++++++++ apps/openmw/mwgui/window_manager.cpp | 29 ++++++++++++++++-- apps/openmw/mwrender/animation.cpp | 25 ++++++++++++++- apps/openmw/mwrender/animation.hpp | 16 +++++----- libs/openengine/ogre/renderer.hpp | 46 +++++++++++++++++++++------- 6 files changed, 123 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp index 9c49c62ac..dbd6154b7 100644 --- a/apps/openmw/mwgui/layouts.cpp +++ b/apps/openmw/mwgui/layouts.cpp @@ -15,6 +15,22 @@ using namespace MWGui; HUD::HUD(int width, int height, int fpsLevel) : Layout("openmw_hud_layout.xml") + , health(NULL) + , magicka(NULL) + , stamina(NULL) + , weapImage(NULL) + , spellImage(NULL) + , weapStatus(NULL) + , spellStatus(NULL) + , effectBox(NULL) + , effect1(NULL) + , minimap(NULL) + , compass(NULL) + , crosshair(NULL) + , fpsbox(NULL) + , fpscounter(NULL) + , trianglecounter(NULL) + , batchcounter(NULL) { setCoord(0,0, width, height); diff --git a/apps/openmw/mwgui/stats_window.cpp b/apps/openmw/mwgui/stats_window.cpp index 243b6272a..12b0dcc79 100644 --- a/apps/openmw/mwgui/stats_window.cpp +++ b/apps/openmw/mwgui/stats_window.cpp @@ -13,9 +13,22 @@ const int StatsWindow::lineHeight = 18; StatsWindow::StatsWindow (WindowManager& parWindowManager) : WindowBase("openmw_stats_window_layout.xml", parWindowManager) + , skillAreaWidget(NULL) + , skillClientWidget(NULL) + , skillScrollerWidget(NULL) , lastPos(0) + , clientHeight(0) + , majorSkills() + , minorSkills() + , miscSkills() + , skillValues() + , skillWidgetMap() + , factionWidgetMap() + , factions() + , birthSignId() , reputation(0) , bounty(0) + , skillWidgets() { setCoord(0,0,498, 342); diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index fa6dedc77..a04e2dcb8 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -22,15 +22,40 @@ using namespace MWGui; WindowManager::WindowManager(MWWorld::Environment& environment, const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string logpath) - : environment(environment) + : mGuiManager(NULL) + , environment(environment) + , hud(NULL) + , map(NULL) + , menu(NULL) + , stats(NULL) + , mMessageBoxManager(NULL) + , console(NULL) + , mJournal(NULL) , dialogueWindow(nullptr) + , mCharGen(NULL) + , playerClass() + , playerName() + , playerRaceId() + , playerBirthSignId() + , playerAttributes() + , playerMajorSkills() + , playerMinorSkills() + , playerSkillValues() + , playerHealth() + , playerMagicka() + , playerFatigue() + , gui(NULL) , mode(GM_Game) , nextMode(GM_Game) , needModeChange(false) + , garbageDialogs() , shown(GW_ALL) , allowed(newGame ? GW_None : GW_ALL) + , showFPSLevel(fpsLevel) + , mFPS(0.0f) + , mTriangleCount(0) + , mBatchCount(0) { - showFPSLevel = fpsLevel; // Set up the GUI system mGuiManager = new OEngine::GUI::MyGUIManager(mOgre->getWindow(), mOgre->getScene(), false, logpath); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7b0d7015c..f3a8f64d5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -4,7 +4,30 @@ namespace MWRender{ std::map Animation::mUniqueIDs; - Animation::~Animation(){ + Animation::Animation(MWWorld::Environment& _env, OEngine::Render::OgreRenderer& _rend) + : insert(NULL) + , mRend(_rend) + , mEnvironment(_env) + , vecRotPos() + , shapeparts() + , time(0.0f) + , startTime(0.0f) + , stopTime(0.0f) + , animate(0) + , rindexI() + , tindexI() + , shapeNumber(0) + , shapeIndexI() + , shapes(NULL) + , entityparts() + , transformations(NULL) + , textmappings(NULL) + , base(NULL) + { + } + + Animation::~Animation() + { } std::string Animation::getUniqueID(std::string mesh){ diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index d1e8071f0..7692c7128 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -60,14 +60,14 @@ class Animation{ std::string getUniqueID(std::string mesh); public: - Animation(MWWorld::Environment& _env, OEngine::Render::OgreRenderer& _rend): mRend(_rend), mEnvironment(_env), animate(0){}; - virtual void runAnimation(float timepassed) = 0; - void startScript(std::string groupname, int mode, int loops); - void stopScript(); - - - virtual ~Animation(); + Animation(MWWorld::Environment& _env, OEngine::Render::OgreRenderer& _rend); + virtual void runAnimation(float timepassed) = 0; + void startScript(std::string groupname, int mode, int loops); + void stopScript(); + + + virtual ~Animation(); }; } -#endif \ No newline at end of file +#endif diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index 6516b0a80..179515aa9 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -44,27 +44,51 @@ namespace Render Ogre::SceneManager *mScene; Ogre::Camera *mCamera; Ogre::Viewport *mView; - #ifdef ENABLE_PLUGIN_CgProgramManager +#ifdef ENABLE_PLUGIN_CgProgramManager Ogre::CgPlugin* mCgPlugin; - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager +#endif +#ifdef ENABLE_PLUGIN_OctreeSceneManager Ogre::OctreePlugin* mOctreePlugin; - #endif - #ifdef ENABLE_PLUGIN_ParticleFX +#endif +#ifdef ENABLE_PLUGIN_ParticleFX Ogre::ParticleFXPlugin* mParticleFXPlugin; - #endif - #ifdef ENABLE_PLUGIN_GL +#endif +#ifdef ENABLE_PLUGIN_GL Ogre::GLPlugin* mGLPlugin; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 +#endif +#ifdef ENABLE_PLUGIN_Direct3D9 Ogre::D3D9Plugin* mD3D9Plugin; - #endif +#endif Fader* mFader; bool logging; public: OgreRenderer() - : mRoot(NULL), mWindow(NULL), mScene(NULL), mFader(NULL) {} + : mRoot(NULL) + , mWindow(NULL) + , mScene(NULL) + , mCamera(NULL) + , mView(NULL) +#ifdef ENABLE_PLUGIN_CgProgramManager + , mCgPlugin(NULL) +#endif +#ifdef ENABLE_PLUGIN_OctreeSceneManager + , mOctreePlugin(NULL) +#endif +#ifdef ENABLE_PLUGIN_ParticleFX + , mParticleFXPlugin(NULL) +#endif +#ifdef ENABLE_PLUGIN_GL + , mGLPlugin(NULL) +#endif +#ifdef ENABLE_PLUGIN_Direct3D9 + , mD3D9Plugin(NULL) +#endif + , mFader(NULL) + , logging(false) + { + } + ~OgreRenderer() { cleanup(); } /** Configure the renderer. This will load configuration files and From 2b9845a5b69de39203e970e326b5a15e22731732 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 25 Mar 2012 23:34:32 +0200 Subject: [PATCH 130/152] Issue #225: Free memory allocated for sending into HardwareVertexBuffer. Free memory allocated for sending into HardwareVertexBuffer in NIFLoader class. --- components/nifogre/ogre_nif_loader.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 2e68cfe90..1367c138a 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -509,7 +509,8 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[index+1] = original.y; datamod[index+2] = original.z; } - vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); + vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); + delete datamod; } else { @@ -550,6 +551,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[index+2] = original.z; } vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); + delete datamod; } else { @@ -601,6 +603,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[i + 1] =y; } vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); + delete datamod; } else vbuf->writeData(0, vbuf->getSizeInBytes(), data->uvlist.ptr, false); @@ -644,15 +647,13 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std index += 3; } - ibuf->writeData(0, ibuf->getSizeInBytes(), datamod, false); + ibuf->writeData(0, ibuf->getSizeInBytes(), datamod, false); + delete datamod; } else - ibuf->writeData(0, ibuf->getSizeInBytes(), data->triangles.ptr, false); + ibuf->writeData(0, ibuf->getSizeInBytes(), data->triangles.ptr, false); sub->indexData->indexBuffer = ibuf; - - - } // Set material if one was given From 28dfba55e01bd84c82ddf9abea995e4e734d9d66 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 26 Mar 2012 20:29:07 +0200 Subject: [PATCH 131/152] delete fix --- apps/openmw/mwrender/localmap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index b83a98220..44be09eef 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -32,7 +32,7 @@ void LocalMap::deleteBuffers() for (std::map::iterator it=mBuffers.begin(); it != mBuffers.end(); ++it) { - delete it->second; + delete[] it->second; } mBuffers.clear(); } From 2362d0a02929e457c218ead29610e7990c7a0390 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 26 Mar 2012 20:40:04 +0200 Subject: [PATCH 132/152] possible EOL problem --- OFL.txt | 186 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/OFL.txt b/OFL.txt index 619d1f429..043e85e83 100644 --- a/OFL.txt +++ b/OFL.txt @@ -1,93 +1,93 @@ -Copyright (c) 2010, 2011 Georg Duffner (http://www.georgduffner.at) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. +Copyright (c) 2010, 2011 Georg Duffner (http://www.georgduffner.at) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. From 362e6cb9ed55d68b1142020e1b29c88b7beb870a Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Mon, 26 Mar 2012 23:21:51 +0400 Subject: [PATCH 133/152] Removed predefined SDK path. Found that it's generally not good idea to force it. Also -Wno-unused-but-set-parameter only set for gcc 4.6 or newer --- CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5f0bffa8..7ff287246 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,9 +4,6 @@ if (APPLE) set(APP_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app") set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}") - - # using 10.6 sdk - set(CMAKE_OSX_SYSROOT "/Developer/SDKs/MacOSX10.6.sdk") endif (APPLE) # Macros @@ -287,7 +284,13 @@ endif (APPLE) # Compiler settings if (CMAKE_COMPILER_IS_GNUCC) - add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-unused-but-set-parameter -Wno-reorder) + add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-reorder) + + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion + OUTPUT_VARIABLE GCC_VERSION) + if ("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) + add_definitions (-Wno-unused-but-set-parameter) + endif("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) endif (CMAKE_COMPILER_IS_GNUCC) if(DPKG_PROGRAM) From f8afc22f04f1224ccd17edf1552adac2555a0e7f Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Mon, 26 Mar 2012 23:16:59 +0200 Subject: [PATCH 134/152] Issue #225: Corrected wrong delete operator introduced by one of previous commit. --- components/nifogre/ogre_nif_loader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 1367c138a..f943231d0 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -510,7 +510,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[index+2] = original.z; } vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); - delete datamod; + delete [] datamod; } else { @@ -551,7 +551,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[index+2] = original.z; } vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); - delete datamod; + delete [] datamod; } else { @@ -603,7 +603,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std datamod[i + 1] =y; } vbuf->writeData(0, vbuf->getSizeInBytes(), datamod, false); - delete datamod; + delete [] datamod; } else vbuf->writeData(0, vbuf->getSizeInBytes(), data->uvlist.ptr, false); @@ -648,7 +648,7 @@ void NIFLoader::createOgreSubMesh(NiTriShape *shape, const String &material, std } ibuf->writeData(0, ibuf->getSizeInBytes(), datamod, false); - delete datamod; + delete [] datamod; } else From bf421d2873344faa845507c47bde786b7dfb9814 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 27 Mar 2012 00:18:09 +0200 Subject: [PATCH 135/152] use vector instead of array --- apps/openmw/mwrender/localmap.cpp | 25 +++++++++---------------- apps/openmw/mwrender/localmap.hpp | 2 +- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 44be09eef..ed218dc97 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -29,11 +29,6 @@ LocalMap::~LocalMap() void LocalMap::deleteBuffers() { - for (std::map::iterator it=mBuffers.begin(); - it != mBuffers.end(); ++it) - { - delete[] it->second; - } mBuffers.clear(); } @@ -202,16 +197,16 @@ void LocalMap::render(const float x, const float y, TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); // create a buffer to use for dynamic operations - uint32* buffer = new uint32[sFogOfWarResolution*sFogOfWarResolution]; + std::vector buffer; + buffer.resize(sFogOfWarResolution*sFogOfWarResolution); // initialize to (0, 0, 0, 1) - uint32* pointer = buffer; for (int p=0; pgetBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, sFogOfWarResolution*sFogOfWarResolution*4); + memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); tex2->getBuffer()->unlock(); mBuffers[texture] = buffer; @@ -288,25 +283,23 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& { // get its buffer if (mBuffers.find(texName) == mBuffers.end()) return; - uint32* buffer = mBuffers[texName]; - uint32* pointer = buffer; + int i=0; for (int texV = 0; texV> 24); alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); - *((uint32*)pointer) = (alpha << 24); + mBuffers[texName][i] = (uint32) (alpha << 24); - // move to next texel - ++pointer; + ++i; } } // copy to the texture - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), buffer, sFogOfWarResolution*sFogOfWarResolution*4); + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4); tex->getBuffer()->unlock(); } } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 3bef475ea..efbccf884 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -86,7 +86,7 @@ namespace MWRender // a buffer for the "fog of war" texture of the current cell. // interior cells could be divided into multiple textures, // so we store in a map. - std::map mBuffers; + std::map > mBuffers; void deleteBuffers(); From 6d6ed909bf62a328c927fbbdb52b6a98ef677e28 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Tue, 27 Mar 2012 00:31:15 +0200 Subject: [PATCH 136/152] Issue #225: Deallocate memory used by actors animations. --- apps/openmw/mwrender/actors.cpp | 10 ++++++++++ apps/openmw/mwrender/actors.hpp | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index d8ca78e3a..6eb4a182b 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -8,6 +8,15 @@ using namespace Ogre; using namespace MWRender; using namespace NifOgre; +Actors::~Actors(){ + + std::map::iterator it = mAllActors.begin(); + for (; it != mAllActors.end(); ++it) { + delete it->second; + it->second = NULL; + } +} + void Actors::setMwRoot(Ogre::SceneNode* root){ mMwRoot = root; } @@ -61,6 +70,7 @@ void Actors::insertCreature (const MWWorld::Ptr& ptr){ insertBegin(ptr, true, true); CreatureAnimation* anim = new MWRender::CreatureAnimation(ptr, mEnvironment, mRend); //mAllActors.insert(std::pair(ptr,anim)); + delete mAllActors[ptr]; mAllActors[ptr] = anim; //mAllActors.push_back(&anim);*/ } diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp index 7179c08fb..d49c6e0f8 100644 --- a/apps/openmw/mwrender/actors.hpp +++ b/apps/openmw/mwrender/actors.hpp @@ -30,7 +30,7 @@ namespace MWRender{ public: Actors(OEngine::Render::OgreRenderer& _rend, MWWorld::Environment& _env): mRend(_rend), mEnvironment(_env){} - ~Actors(){} + ~Actors(); void setMwRoot(Ogre::SceneNode* root); void insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_); void insertCreature (const MWWorld::Ptr& ptr); From be94da15272937a8719a849a51162c2fd276374d Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Tue, 27 Mar 2012 00:34:06 +0200 Subject: [PATCH 137/152] Issue #225: Initialize class members in constructor. --- apps/openmw/mwrender/sky.cpp | 38 +++++++++++++++++++++++--- apps/openmw/mwsound/mpgsnd_decoder.cpp | 8 +++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 5e8578002..a41bc21e0 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -254,7 +254,7 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType) // Get a pointer to the vertex colour ves_diffuse->baseVertexPointerToElement( pData, ¤tVertex ); - unsigned char alpha; + unsigned char alpha=0; if (meshType == 0) alpha = i%2 ? 0 : 255; // this is a cylinder, so every second vertex belongs to the bottom-most row else if (meshType == 1) { @@ -292,10 +292,40 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType) ent->getMesh()->getSubMesh(0)->vertexData->vertexBufferBinding->getBuffer(ves_diffuse->getSource())->unlock(); } -SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environment* env) : - mGlareFade(0), mGlareEnabled(false) +SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environment* env) + : mEnvironment(env) + , mHour(0.0f) + , mDay(0) + , mMonth(0) + , mSun(NULL) + , mSunGlare(NULL) + , mMasser(NULL) + , mSecunda(NULL) + , mViewport(NULL) + , mRootNode(NULL) + , mSceneMgr(NULL) + , mAtmosphereDay(NULL) + , mAtmosphereNight(NULL) + , mCloudMaterial() + , mAtmosphereMaterial() + , mCloudFragmentShader() + , mClouds() + , mNextClouds() + , mCloudBlendFactor(0.0f) + , mCloudOpacity(0.0f) + , mCloudSpeed(0.0f) + , mStarsOpacity(0.0f) + , mThunderOverlay(NULL) + , mThunderTextureUnit(NULL) + , mRemainingTransitionTime(0.0f) + , mGlareFade(0.0f) + , mEnabled(true) + , mGlareEnabled(true) + , mSunEnabled(true) + , mMasserEnabled(true) + , mSecundaEnabled(true) { - mEnvironment = env; + mViewport = pCamera->getViewport(); mSceneMgr = pMwRoot->getCreator(); mRootNode = pCamera->getParentSceneNode()->createChildSceneNode(); diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index f576833a8..9b91b4e74 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -213,7 +213,13 @@ void MpgSnd_Decoder::rewind() } } -MpgSnd_Decoder::MpgSnd_Decoder() : mSndFile(NULL), mMpgFile(NULL) +MpgSnd_Decoder::MpgSnd_Decoder() + : mSndInfo() + , mSndFile(NULL) + , mMpgFile(NULL) + , mDataStream() + , mChanConfig(ChannelConfig_Stereo) + , mSampleRate(0) { static bool initdone = false; if(!initdone) From 55f1053b4fd9e813caa8838586fec895c6f42da6 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 27 Mar 2012 00:36:53 +0200 Subject: [PATCH 138/152] Windows fixes for compiling and linking --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/messagebox.hpp | 1 + apps/openmw/mwgui/widgets.hpp | 3 +++ apps/openmw/mwsound/openal_output.cpp | 4 +++- apps/openmw/mwsound/soundmanager.hpp | 2 +- cmake/FindFFMPEG.cmake | 17 ++++++++++++++++- cmake/FindMyGUI.cmake | 2 +- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2e9191e81..6dc9382d0 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -89,7 +89,7 @@ target_link_libraries(openmw ${SOUND_INPUT_LIBRARY} ${BULLET_LIBRARIES} ${MYGUI_LIBRARIES} - MyGUI.OgrePlatform #TODO MyGUI ogre platform is not added by the find script + ${MYGUI_PLATFORM_LIBRARIES} components ) diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index bf3307acc..33155b2a0 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -7,6 +7,7 @@ #include "window_base.hpp" #include "window_manager.hpp" +#undef MessageBox namespace MWGui { diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 4d31ad521..a7916285e 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -7,6 +7,9 @@ #include "../mwmechanics/stat.hpp" +#undef MYGUI_EXPORT +#define MYGUI_EXPORT + /* This file contains various custom widgets used in OpenMW. */ diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index c06947403..edd923f43 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -65,7 +65,7 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) class OpenAL_SoundStream : public Sound { static const ALuint sNumBuffers = 6; - static const ALfloat sBufferLength = 0.125f; + static const ALfloat sBufferLength; OpenAL_Output &mOutput; @@ -95,6 +95,8 @@ public: bool process(); }; +const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; + // // A background streaming thread (keeps active streams processed) // diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index b7c883a13..a076c1cc0 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -24,7 +24,7 @@ namespace MWWorld namespace MWSound { class Sound_Output; - class Sound_Decoder; + struct Sound_Decoder; class Sound; typedef boost::shared_ptr DecoderPtr; diff --git a/cmake/FindFFMPEG.cmake b/cmake/FindFFMPEG.cmake index ff6d0c598..2e755d047 100644 --- a/cmake/FindFFMPEG.cmake +++ b/cmake/FindFFMPEG.cmake @@ -9,9 +9,23 @@ SET( FFMPEG_FOUND "NO" ) +FIND_PATH( FFMPEG_general_INCLUDE_DIR libavcodec/avcodec.h libavformat/avformat.h + HINTS + PATHS + /usr/include + /usr/local/include + /usr/include/ffmpeg + /usr/local/include/ffmpeg + /usr/include/ffmpeg/libavcodec + /usr/local/include/ffmpeg/libavcodec + /usr/include/libavcodec + /usr/local/include/libavcodec + ) + FIND_PATH( FFMPEG_avcodec_INCLUDE_DIR avcodec.h HINTS PATHS + ${FFMPEG_general_INCLUDE_DIR}/libavcodec /usr/include /usr/local/include /usr/include/ffmpeg @@ -25,6 +39,7 @@ FIND_PATH( FFMPEG_avcodec_INCLUDE_DIR avcodec.h FIND_PATH( FFMPEG_avformat_INCLUDE_DIR avformat.h HINTS PATHS + ${FFMPEG_general_INCLUDE_DIR}/libavformat /usr/include /usr/local/include /usr/include/ffmpeg @@ -35,7 +50,7 @@ FIND_PATH( FFMPEG_avformat_INCLUDE_DIR avformat.h /usr/local/include/libavformat ) -set(FFMPEG_INCLUDE_DIR ${FFMPEG_avcodec_INCLUDE_DIR} ${FFMPEG_avformat_INCLUDE_DIR}) +set(FFMPEG_INCLUDE_DIR ${FFMPEG_general_INCLUDE_DIR} ${FFMPEG_avcodec_INCLUDE_DIR} ${FFMPEG_avformat_INCLUDE_DIR}) IF( FFMPEG_INCLUDE_DIR ) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index 339f494dd..fa0b25383 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -103,7 +103,7 @@ SEPARATE_ARGUMENTS(MYGUI_PLATFORM_LIBRARIES) SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS} CACHE PATH "") SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") -SET(MYGUI_LIBRARIES ${MYGUI_PLATFORM_LIBRARIES} CACHE STRING "") +SET(MYGUI_PLATFORM_LIBRARIES ${MYGUI_PLATFORM_LIBRARIES} CACHE STRING "") SET(MYGUI_LIB_DIR ${MYGUI_LIB_DIR} CACHE PATH "") IF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES) From c2611d035c44faa25a008f3842d5f043148b3b30 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 27 Mar 2012 02:50:45 -0700 Subject: [PATCH 139/152] Use a pair to match the MWWorld::Ptr object and sound ID, instead of nested maps --- apps/openmw/mwsound/soundmanager.cpp | 96 +++++++++++----------------- apps/openmw/mwsound/soundmanager.hpp | 8 +-- 2 files changed, 40 insertions(+), 64 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 4d47e5f2c..f20bf736e 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -114,14 +114,10 @@ namespace MWSound bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { - SoundMap::const_iterator snditer = mActiveSounds.find(ptr); + SoundMap::const_iterator snditer = mActiveSounds.find(std::make_pair(ptr, id)); if(snditer == mActiveSounds.end()) return false; - IDMap::const_iterator iditer = snditer->second.find(id); - if(iditer == snditer->second.end()) - return false; - return true; } @@ -185,7 +181,7 @@ namespace MWSound std::string filePath = std::string("Sound/")+filename; SoundPtr sound(mOutput->playSound3D(filePath, pos.pos, 1.0f, 1.0f, 100.0f, 20000.0f, false)); - mActiveSounds[ptr]["_say_sound"] = sound; + mActiveSounds[std::make_pair(ptr, std::string("_say_sound"))] = sound; } catch(std::exception &e) { @@ -205,8 +201,8 @@ namespace MWSound try { std::string file = lookup(soundId, volume, min, max); - Sound *sound = mOutput->playSound(file, volume, pitch, loop); - mActiveSounds[MWWorld::Ptr()][soundId] = SoundPtr(sound); + SoundPtr sound = SoundPtr(mOutput->playSound(file, volume, pitch, loop)); + mActiveSounds[std::make_pair(MWWorld::Ptr(), soundId)] = sound; } catch(std::exception &e) { @@ -225,7 +221,7 @@ namespace MWSound std::string file = lookup(soundId, volume, min, max); SoundPtr sound(mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop)); - mActiveSounds[untracked?MWWorld::Ptr():ptr][soundId] = sound; + mActiveSounds[std::make_pair((untracked?MWWorld::Ptr():ptr), soundId)] = sound; } catch(std::exception &e) { @@ -237,30 +233,28 @@ namespace MWSound { // Stop a sound and remove it from the list. If soundId="" then // stop all its sounds. - SoundMap::iterator snditer = mActiveSounds.find(ptr); - if(snditer == mActiveSounds.end()) - return; - if(!soundId.empty()) { - IDMap::iterator iditer = snditer->second.find(soundId); - if(iditer != snditer->second.end()) - { - iditer->second->stop(); - snditer->second.erase(iditer); - if(snditer->second.empty()) - mActiveSounds.erase(snditer); - } + SoundMap::iterator snditer = mActiveSounds.find(std::make_pair(ptr, soundId)); + if(snditer == mActiveSounds.end()) + return; + + snditer->second->stop(); + mActiveSounds.erase(snditer); } else { - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - iditer->second->stop(); - iditer++; + if(snditer->first.first == ptr) + { + snditer->second->stop(); + mActiveSounds.erase(snditer++); + } + else + snditer++; } - mActiveSounds.erase(snditer); } } @@ -270,14 +264,10 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->first != MWWorld::Ptr() && snditer->first.getCell() == cell) + if(snditer->first.first != MWWorld::Ptr() && + snditer->first.first.getCell() == cell) { - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) - { - iditer->second->stop(); - iditer++; - } + snditer->second->stop(); mActiveSounds.erase(snditer++); } else @@ -287,18 +277,12 @@ namespace MWSound void SoundManager::stopSound(const std::string& soundId) { - SoundMap::iterator snditer = mActiveSounds.find(MWWorld::Ptr()); + SoundMap::iterator snditer = mActiveSounds.find(std::make_pair(MWWorld::Ptr(), soundId)); if(snditer == mActiveSounds.end()) return; - IDMap::iterator iditer = snditer->second.find(soundId); - if(iditer != snditer->second.end()) - { - iditer->second->stop(); - snditer->second.erase(iditer); - if(snditer->second.empty()) - mActiveSounds.erase(snditer); - } + snditer->second->stop(); + mActiveSounds.erase(snditer); } bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const @@ -308,16 +292,16 @@ namespace MWSound void SoundManager::updateObject(MWWorld::Ptr ptr) { - SoundMap::iterator snditer = mActiveSounds.find(ptr); - if(snditer == mActiveSounds.end()) - return; - - const ESM::Position &pos = ptr.getCellRef().pos; - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - iditer->second->update(pos.pos); - iditer++; + if(snditer->first.first == ptr) + { + snditer->second->stop(); + mActiveSounds.erase(snditer++); + } + else + snditer++; } } @@ -406,15 +390,7 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - IDMap::iterator iditer = snditer->second.begin(); - while(iditer != snditer->second.end()) - { - if(!iditer->second->isPlaying()) - snditer->second.erase(iditer++); - else - iditer++; - } - if(snditer->second.empty()) + if(!snditer->second->isPlaying()) mActiveSounds.erase(snditer++); else snditer++; diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 8d76ba100..b7b21b945 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -2,11 +2,11 @@ #define GAME_SOUND_SOUNDMANAGER_H #include +#include +#include #include -#include - #include "../mwworld/ptr.hpp" @@ -41,8 +41,8 @@ namespace MWSound std::string mCurrentPlaylist; typedef boost::shared_ptr SoundPtr; - typedef std::map IDMap; - typedef std::map SoundMap; + typedef std::pair PtrIDPair; + typedef std::map SoundMap; SoundMap mActiveSounds; std::string lookup(const std::string &soundId, From 033faba9c41b45bd19240df280358d3f9aab6693 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 27 Mar 2012 03:00:04 -0700 Subject: [PATCH 140/152] Make a function parameter const --- apps/openmw/mwsound/soundmanager.cpp | 2 +- apps/openmw/mwsound/soundmanager.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index f20bf736e..5450c113a 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -258,7 +258,7 @@ namespace MWSound } } - void SoundManager::stopSound(MWWorld::Ptr::CellStore *cell) + void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell) { // Remove all references to objects belonging to a given cell SoundMap::iterator snditer = mActiveSounds.begin(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index b7b21b945..7f20425af 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -99,7 +99,7 @@ namespace MWSound ///< Stop the given object from playing the given sound, If no soundId is given, /// all sounds for this reference will stop. - void stopSound(MWWorld::Ptr::CellStore *cell); + void stopSound(const MWWorld::Ptr::CellStore *cell); ///< Stop all sounds for the given cell. void stopSound(const std::string& soundId); From f0db2ab82fb928a2c955750478b0a785f2d229ea Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 27 Mar 2012 03:20:50 -0700 Subject: [PATCH 141/152] Split stopSound3D into separate functions to deal with stopping all sounds on an object --- apps/openmw/mwsound/soundmanager.cpp | 37 ++++++++++++---------------- apps/openmw/mwsound/soundmanager.hpp | 8 +++--- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 5450c113a..449b5100b 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -231,36 +231,31 @@ namespace MWSound void SoundManager::stopSound3D(MWWorld::Ptr ptr, const std::string& soundId) { - // Stop a sound and remove it from the list. If soundId="" then - // stop all its sounds. - if(!soundId.empty()) - { - SoundMap::iterator snditer = mActiveSounds.find(std::make_pair(ptr, soundId)); - if(snditer == mActiveSounds.end()) - return; + SoundMap::iterator snditer = mActiveSounds.find(std::make_pair(ptr, soundId)); + if(snditer == mActiveSounds.end()) + return; - snditer->second->stop(); - mActiveSounds.erase(snditer); - } - else + snditer->second->stop(); + mActiveSounds.erase(snditer); + } + + void SoundManager::stopSound3D(MWWorld::Ptr ptr) + { + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) { - SoundMap::iterator snditer = mActiveSounds.begin(); - while(snditer != mActiveSounds.end()) + if(snditer->first.first == ptr) { - if(snditer->first.first == ptr) - { - snditer->second->stop(); - mActiveSounds.erase(snditer++); - } - else - snditer++; + snditer->second->stop(); + mActiveSounds.erase(snditer++); } + else + snditer++; } } void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell) { - // Remove all references to objects belonging to a given cell SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 7f20425af..d79aface6 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -95,9 +95,11 @@ namespace MWSound bool untracked=false); ///< Play a sound from an object - void stopSound3D(MWWorld::Ptr reference, const std::string& soundId=""); - ///< Stop the given object from playing the given sound, If no soundId is given, - /// all sounds for this reference will stop. + void stopSound3D(MWWorld::Ptr reference, const std::string& soundId); + ///< Stop the given object from playing the given sound, + + void stopSound3D(MWWorld::Ptr reference); + ///< Stop the given object from playing all sounds. void stopSound(const MWWorld::Ptr::CellStore *cell); ///< Stop all sounds for the given cell. From c6c06f1140aadc0bfc7b5d2d109f00aedd6431d4 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 27 Mar 2012 05:59:09 -0700 Subject: [PATCH 142/152] Return SoundPtr objects from the playSound and streamSound methods --- apps/openmw/mwsound/openal_output.cpp | 28 +++++++++++++-------------- apps/openmw/mwsound/openal_output.hpp | 12 ++++++------ apps/openmw/mwsound/sound_output.hpp | 14 ++++++++------ apps/openmw/mwsound/soundmanager.cpp | 8 ++++---- apps/openmw/mwsound/soundmanager.hpp | 2 +- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index c06947403..bb083cb8b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -559,11 +559,11 @@ void OpenAL_Output::bufferFinished(ALuint buf) } -Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop) +SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop) { throwALerror(); - std::auto_ptr sound; + boost::shared_ptr sound; ALuint src=0, buf=0; if(mFreeSources.empty()) @@ -604,15 +604,15 @@ Sound* OpenAL_Output::playSound(const std::string &fname, float volume, float pi alSourcePlay(src); throwALerror(); - return sound.release(); + return sound; } -Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop) +SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max, bool loop) { throwALerror(); - std::auto_ptr sound; + boost::shared_ptr sound; ALuint src=0, buf=0; if(mFreeSources.empty()) @@ -653,15 +653,15 @@ Sound* OpenAL_Output::playSound3D(const std::string &fname, const float *pos, fl alSourcePlay(src); throwALerror(); - return sound.release(); + return sound; } -Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) +SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) { throwALerror(); - std::auto_ptr sound; + boost::shared_ptr sound; ALuint src; if(mFreeSources.empty()) @@ -697,15 +697,15 @@ Sound* OpenAL_Output::streamSound(const std::string &fname, float volume, float throwALerror(); sound->play(); - return sound.release(); + return sound; } -Sound* OpenAL_Output::streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max) +SoundPtr OpenAL_Output::streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max) { throwALerror(); - std::auto_ptr sound; + boost::shared_ptr sound; ALuint src; if(mFreeSources.empty()) @@ -741,7 +741,7 @@ Sound* OpenAL_Output::streamSound3D(const std::string &fname, const float *pos, throwALerror(); sound->play(); - return sound.release(); + return sound; } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index e8154e906..2b0897bdb 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -42,13 +42,13 @@ namespace MWSound virtual void init(const std::string &devname=""); virtual void deinit(); - virtual Sound *playSound(const std::string &fname, float volume, float pitch, bool loop); - virtual Sound *playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop); + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop); + virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max, bool loop); - virtual Sound *streamSound(const std::string &fname, float volume, float pitch); - virtual Sound *streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max); + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); + virtual SoundPtr streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max); virtual void updateListener(const float *pos, const float *atdir, const float *updir); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 1722165e4..794383591 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -4,6 +4,8 @@ #include #include +#include "soundmanager.hpp" + #include "../mwworld/ptr.hpp" namespace MWSound @@ -20,12 +22,12 @@ namespace MWSound virtual void init(const std::string &devname="") = 0; virtual void deinit() = 0; - virtual Sound *playSound(const std::string &fname, float volume, float pitch, bool loop) = 0; - virtual Sound *playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop) = 0; - virtual Sound *streamSound(const std::string &fname, float volume, float pitch) = 0; - virtual Sound *streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max) = 0; + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop) = 0; + virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max, bool loop) = 0; + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; + virtual SoundPtr streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, + float min, float max) = 0; virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 449b5100b..ce5f93acd 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -136,7 +136,7 @@ namespace MWSound { if(mMusic) mMusic->stop(); - mMusic.reset(mOutput->streamSound(filename, 0.4f, 1.0f)); + mMusic = mOutput->streamSound(filename, 0.4f, 1.0f); } catch(std::exception &e) { @@ -180,7 +180,7 @@ namespace MWSound const ESM::Position &pos = ptr.getCellRef().pos; std::string filePath = std::string("Sound/")+filename; - SoundPtr sound(mOutput->playSound3D(filePath, pos.pos, 1.0f, 1.0f, 100.0f, 20000.0f, false)); + SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, 1.0f, 1.0f, 100.0f, 20000.0f, false); mActiveSounds[std::make_pair(ptr, std::string("_say_sound"))] = sound; } catch(std::exception &e) @@ -201,7 +201,7 @@ namespace MWSound try { std::string file = lookup(soundId, volume, min, max); - SoundPtr sound = SoundPtr(mOutput->playSound(file, volume, pitch, loop)); + SoundPtr sound = mOutput->playSound(file, volume, pitch, loop); mActiveSounds[std::make_pair(MWWorld::Ptr(), soundId)] = sound; } catch(std::exception &e) @@ -220,7 +220,7 @@ namespace MWSound const ESM::Position &pos = ptr.getCellRef().pos; std::string file = lookup(soundId, volume, min, max); - SoundPtr sound(mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop)); + SoundPtr sound = mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop); mActiveSounds[std::make_pair((untracked?MWWorld::Ptr():ptr), soundId)] = sound; } catch(std::exception &e) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index d79aface6..539e81888 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -28,6 +28,7 @@ namespace MWSound class Sound; typedef boost::shared_ptr DecoderPtr; + typedef boost::shared_ptr SoundPtr; class SoundManager { @@ -40,7 +41,6 @@ namespace MWSound boost::shared_ptr mMusic; std::string mCurrentPlaylist; - typedef boost::shared_ptr SoundPtr; typedef std::pair PtrIDPair; typedef std::map SoundMap; SoundMap mActiveSounds; From 6c8be7205a712381067e59755dc1301a7820b2bf Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 27 Mar 2012 20:13:48 +0200 Subject: [PATCH 143/152] build fix --- cmake/FindMyGUI.cmake | 104 +++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/cmake/FindMyGUI.cmake b/cmake/FindMyGUI.cmake index fa0b25383..cc9799208 100644 --- a/cmake/FindMyGUI.cmake +++ b/cmake/FindMyGUI.cmake @@ -19,57 +19,57 @@ include(FindPkgMacros) IF (WIN32) #Windows MESSAGE(STATUS "Looking for MyGUI") - SET(MYGUISDK $ENV{MYGUI_HOME}) +SET(MYGUISDK $ENV{MYGUI_HOME}) IF (MYGUISDK) - findpkg_begin ( "MYGUI" ) +findpkg_begin ( "MYGUI" ) MESSAGE(STATUS "Using MyGUI in OGRE SDK") - STRING(REGEX REPLACE "[\\]" "/" MYGUISDK "${MYGUISDK}" ) - - find_path ( MYGUI_INCLUDE_DIRS - MyGUI.h - "${MYGUISDK}/MyGUIEngine/include" - NO_DEFAULT_PATH ) - - find_path ( MYGUI_PLATFORM_INCLUDE_DIRS - MyGUI_OgrePlatform.h - "${MYGUISDK}/Platforms/Ogre/OgrePlatform/include" - NO_DEFAULT_PATH ) - - SET ( MYGUI_LIB_DIR ${MYGUISDK}/*/lib ) - - find_library ( MYGUI_LIBRARIES_REL NAMES - MyGUIEngine.lib - MyGUI.OgrePlatform.lib - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" release relwithdebinfo minsizerel ) - - find_library ( MYGUI_LIBRARIES_DBG NAMES - MyGUIEngine_d.lib - MyGUI.OgrePlatform_d.lib - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" debug ) - - find_library ( MYGUI_PLATFORM_LIBRARIES_REL NAMES - MyGUI.OgrePlatform.lib - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" release relwithdebinfo minsizerel ) - - find_library ( MYGUI_PLATFORM_LIBRARIES_DBG NAMES - MyGUI.OgrePlatform_d.lib - HINTS - ${MYGUI_LIB_DIR} - PATH_SUFFIXES "" debug ) - - make_library_set ( MYGUI_LIBRARIES ) - make_library_set ( MYGUI_PLATFORM_LIBRARIES ) - - MESSAGE ("${MYGUI_LIBRARIES}") - MESSAGE ("${MYGUI_PLATFORM_LIBRARIES}") - - findpkg_finish ( "MYGUI" ) +STRING(REGEX REPLACE "[\\]" "/" MYGUISDK "${MYGUISDK}" ) + +find_path ( MYGUI_INCLUDE_DIRS +MyGUI.h +"${MYGUISDK}/MyGUIEngine/include" +NO_DEFAULT_PATH ) + +find_path ( MYGUI_PLATFORM_INCLUDE_DIRS +MyGUI_OgrePlatform.h +"${MYGUISDK}/Platforms/Ogre/OgrePlatform/include" +NO_DEFAULT_PATH ) + +SET ( MYGUI_LIB_DIR ${MYGUISDK}/*/lib ) + +find_library ( MYGUI_LIBRARIES_REL NAMES +MyGUIEngine.lib +MyGUI.OgrePlatform.lib +HINTS +${MYGUI_LIB_DIR} +PATH_SUFFIXES "" release relwithdebinfo minsizerel ) + +find_library ( MYGUI_LIBRARIES_DBG NAMES +MyGUIEngine_d.lib +MyGUI.OgrePlatform_d.lib +HINTS +${MYGUI_LIB_DIR} +PATH_SUFFIXES "" debug ) + +find_library ( MYGUI_PLATFORM_LIBRARIES_REL NAMES +MyGUI.OgrePlatform.lib +HINTS +${MYGUI_LIB_DIR} +PATH_SUFFIXES "" release relwithdebinfo minsizerel ) + +find_library ( MYGUI_PLATFORM_LIBRARIES_DBG NAMES +MyGUI.OgrePlatform_d.lib +HINTS +${MYGUI_LIB_DIR} +PATH_SUFFIXES "" debug ) + +make_library_set ( MYGUI_LIBRARIES ) +make_library_set ( MYGUI_PLATFORM_LIBRARIES ) + +MESSAGE ("${MYGUI_LIBRARIES}") +MESSAGE ("${MYGUI_PLATFORM_LIBRARIES}") + +findpkg_finish ( "MYGUI" ) ENDIF (MYGUISDK) IF (OGRESOURCE) @@ -87,9 +87,11 @@ ELSE (WIN32) #Unix SET(MYGUI_INCLUDE_DIRS ${MYGUI_INCLUDE_DIRS}) SET(MYGUI_LIB_DIR ${MYGUI_LIBDIR}) SET(MYGUI_LIBRARIES ${MYGUI_LIBRARIES} CACHE STRING "") + SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") ELSE (MYGUI_INCLUDE_DIRS) FIND_PATH(MYGUI_INCLUDE_DIRS MyGUI.h PATHS /usr/local/include /usr/include PATH_SUFFIXES MyGUI MYGUI) FIND_LIBRARY(MYGUI_LIBRARIES mygui PATHS /usr/lib /usr/local/lib) + SET(MYGUI_PLATFORM_LIBRARIES "MyGUI.OgrePlatform") SET(MYGUI_LIB_DIR ${MYGUI_LIBRARIES}) STRING(REGEX REPLACE "(.*)/.*" "\\1" MYGUI_LIB_DIR "${MYGUI_LIB_DIR}") STRING(REGEX REPLACE ".*/" "" MYGUI_LIBRARIES "${MYGUI_LIBRARIES}") @@ -111,7 +113,7 @@ IF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES) ENDIF (MYGUI_INCLUDE_DIRS AND MYGUI_LIBRARIES) IF (MYGUI_FOUND) - MARK_AS_ADVANCED(MYGUI_LIB_DIR) +MARK_AS_ADVANCED(MYGUI_LIB_DIR) IF (NOT MYGUI_FIND_QUIETLY) MESSAGE(STATUS " libraries : ${MYGUI_LIBRARIES} from ${MYGUI_LIB_DIR}") MESSAGE(STATUS " includes : ${MYGUI_INCLUDE_DIRS}") @@ -122,4 +124,4 @@ ELSE (MYGUI_FOUND) ENDIF (MYGUI_FIND_REQUIRED) ENDIF (MYGUI_FOUND) -CMAKE_POLICY(POP) \ No newline at end of file +CMAKE_POLICY(POP) From 66b31d3d43f63b8c68d7025a90f38443d47e0afc Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Wed, 28 Mar 2012 12:04:42 +0400 Subject: [PATCH 144/152] Bug #229 (Fixed): On OS X Launcher cannot launch game if path to binary contains spaces --- apps/launcher/maindialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 49c0bd960..b31235726 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -175,6 +175,7 @@ void MainDialog::play() QDir dir(QCoreApplication::applicationDirPath()); QString game = dir.absoluteFilePath("openmw"); QFile file(game); + game = "\"" + game + "\""; #else QString game = "./openmw"; QFile file(game); From 089c3409354dc776e7a921d4c0c34903c2322e22 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 28 Mar 2012 03:48:51 -0700 Subject: [PATCH 145/152] Switch the map so the SoundPtr is used as a key --- apps/openmw/mwsound/soundmanager.cpp | 79 ++++++++++++++++------------ apps/openmw/mwsound/soundmanager.hpp | 2 +- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index ce5f93acd..a89c34ded 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -114,11 +114,14 @@ namespace MWSound bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const { - SoundMap::const_iterator snditer = mActiveSounds.find(std::make_pair(ptr, id)); - if(snditer == mActiveSounds.end()) - return false; - - return true; + SoundMap::const_iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(snditer->second.first == ptr && snditer->second.second == id) + return snditer->first->isPlaying(); + snditer++; + } + return false; } @@ -181,7 +184,7 @@ namespace MWSound std::string filePath = std::string("Sound/")+filename; SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, 1.0f, 1.0f, 100.0f, 20000.0f, false); - mActiveSounds[std::make_pair(ptr, std::string("_say_sound"))] = sound; + mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) { @@ -201,8 +204,9 @@ namespace MWSound try { std::string file = lookup(soundId, volume, min, max); + SoundPtr sound = mOutput->playSound(file, volume, pitch, loop); - mActiveSounds[std::make_pair(MWWorld::Ptr(), soundId)] = sound; + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } catch(std::exception &e) { @@ -221,7 +225,8 @@ namespace MWSound std::string file = lookup(soundId, volume, min, max); SoundPtr sound = mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop); - mActiveSounds[std::make_pair((untracked?MWWorld::Ptr():ptr), soundId)] = sound; + mActiveSounds[sound] = (!untracked ? std::make_pair(ptr, soundId) : + std::make_pair(MWWorld::Ptr(), std::string())); } catch(std::exception &e) { @@ -231,12 +236,17 @@ namespace MWSound void SoundManager::stopSound3D(MWWorld::Ptr ptr, const std::string& soundId) { - SoundMap::iterator snditer = mActiveSounds.find(std::make_pair(ptr, soundId)); - if(snditer == mActiveSounds.end()) - return; - - snditer->second->stop(); - mActiveSounds.erase(snditer); + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(snditer->second.first == ptr && snditer->second.second == soundId) + { + snditer->first->stop(); + mActiveSounds.erase(snditer++); + } + else + snditer++; + } } void SoundManager::stopSound3D(MWWorld::Ptr ptr) @@ -244,9 +254,9 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->first.first == ptr) + if(snditer->second.first == ptr) { - snditer->second->stop(); + snditer->first->stop(); mActiveSounds.erase(snditer++); } else @@ -259,10 +269,10 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->first.first != MWWorld::Ptr() && - snditer->first.first.getCell() == cell) + if(snditer->second.first != MWWorld::Ptr() && + snditer->second.first.getCell() == cell) { - snditer->second->stop(); + snditer->first->stop(); mActiveSounds.erase(snditer++); } else @@ -272,12 +282,18 @@ namespace MWSound void SoundManager::stopSound(const std::string& soundId) { - SoundMap::iterator snditer = mActiveSounds.find(std::make_pair(MWWorld::Ptr(), soundId)); - if(snditer == mActiveSounds.end()) - return; - - snditer->second->stop(); - mActiveSounds.erase(snditer); + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(snditer->second.first == MWWorld::Ptr() && + snditer->second.second == soundId) + { + snditer->first->stop(); + mActiveSounds.erase(snditer++); + } + else + snditer++; + } } bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const @@ -287,16 +303,13 @@ namespace MWSound void SoundManager::updateObject(MWWorld::Ptr ptr) { + const ESM::Position &pos = ptr.getCellRef().pos; SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(snditer->first.first == ptr) - { - snditer->second->stop(); - mActiveSounds.erase(snditer++); - } - else - snditer++; + if(snditer->second.first == ptr) + snditer->first->update(pos.pos); + snditer++; } } @@ -385,7 +398,7 @@ namespace MWSound SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { - if(!snditer->second->isPlaying()) + if(!snditer->first->isPlaying()) mActiveSounds.erase(snditer++); else snditer++; diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 539e81888..5808b0142 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -42,7 +42,7 @@ namespace MWSound std::string mCurrentPlaylist; typedef std::pair PtrIDPair; - typedef std::map SoundMap; + typedef std::map SoundMap; SoundMap mActiveSounds; std::string lookup(const std::string &soundId, From c072babd17f5cfeec9f6608ab2725e30d44507dd Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 28 Mar 2012 04:56:40 -0700 Subject: [PATCH 146/152] Better handle bad OpenAL source counts --- apps/openmw/mwsound/openal_output.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index bb083cb8b..6e5806ee3 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -415,12 +415,12 @@ void OpenAL_Output::init(const std::string &devname) alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); throwALerror(); - ALCint maxmono, maxstereo; + ALCint maxmono=0, maxstereo=0; alcGetIntegerv(mDevice, ALC_MONO_SOURCES, 1, &maxmono); alcGetIntegerv(mDevice, ALC_STEREO_SOURCES, 1, &maxstereo); throwALCerror(mDevice); - mFreeSources.resize(std::min(maxmono+maxstereo, 256)); + mFreeSources.resize(std::min(maxmono+maxstereo, 256)); for(size_t i = 0;i < mFreeSources.size();i++) { ALuint src; From 7008bd2fe1e8b1e6e275bfd00d8025345af6b6f1 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 28 Mar 2012 04:58:47 -0700 Subject: [PATCH 147/152] Store some sound properties in the Sound class --- apps/openmw/mwsound/sound.hpp | 12 +++++++++- apps/openmw/mwsound/soundmanager.cpp | 34 ++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index f9e7ab427..a5617bc5a 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -12,8 +12,18 @@ namespace MWSound Sound& operator=(const Sound &rhs); Sound(const Sound &rhs); + protected: + float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ + float mBaseVolume; + float mMinDistance; + float mMaxDistance; + public: - Sound() { } + Sound() : mVolume(1.0f) + , mBaseVolume(1.0f) + , mMinDistance(20.0f) /* 1 * min_range_scale */ + , mMaxDistance(12750.0f) /* 255 * max_range_scale */ + { } virtual ~Sound() { } friend class OpenAL_Output; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index a89c34ded..8e2ef3a27 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -140,6 +140,7 @@ namespace MWSound if(mMusic) mMusic->stop(); mMusic = mOutput->streamSound(filename, 0.4f, 1.0f); + mMusic->mBaseVolume = 0.4f; } catch(std::exception &e) { @@ -180,10 +181,13 @@ namespace MWSound try { // The range values are not tested - const ESM::Position &pos = ptr.getCellRef().pos; + float basevol = 1.0f; /* TODO: volume settings */ std::string filePath = std::string("Sound/")+filename; + const ESM::Position &pos = ptr.getCellRef().pos; + + SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, 20.0f, 12750.0f, false); + sound->mBaseVolume = basevol; - SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, 1.0f, 1.0f, 100.0f, 20000.0f, false); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) @@ -200,12 +204,18 @@ namespace MWSound void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) { - float min, max; try { - std::string file = lookup(soundId, volume, min, max); + float basevol = 1.0f; /* TODO: volume settings */ + float min, max; + std::string file = lookup(soundId, basevol, min, max); + + SoundPtr sound = mOutput->playSound(file, volume*basevol, pitch, loop); + sound->mVolume = volume; + sound->mBaseVolume = basevol; + sound->mMinDistance = min; + sound->mMaxDistance = max; - SoundPtr sound = mOutput->playSound(file, volume, pitch, loop); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } catch(std::exception &e) @@ -217,16 +227,22 @@ namespace MWSound void SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, float volume, float pitch, bool loop, bool untracked) { - float min, max; try { // Look up the sound in the ESM data + float basevol = 1.0f; /* TODO: volume settings */ + float min, max; + std::string file = lookup(soundId, basevol, min, max); const ESM::Position &pos = ptr.getCellRef().pos; - std::string file = lookup(soundId, volume, min, max); - SoundPtr sound = mOutput->playSound3D(file, pos.pos, volume, pitch, min, max, loop); + SoundPtr sound = mOutput->playSound3D(file, pos.pos, volume*basevol, pitch, min, max, loop); + sound->mVolume = volume; + sound->mBaseVolume = basevol; + sound->mMinDistance = min; + sound->mMaxDistance = max; + mActiveSounds[sound] = (!untracked ? std::make_pair(ptr, soundId) : - std::make_pair(MWWorld::Ptr(), std::string())); + std::make_pair(MWWorld::Ptr(), soundId)); } catch(std::exception &e) { From a3291ef360f3d16212252005f181c49e6efcef3e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 28 Mar 2012 05:19:35 -0700 Subject: [PATCH 148/152] Add a sound method to update the volume --- apps/openmw/mwsound/openal_output.cpp | 16 ++++++++++++++++ apps/openmw/mwsound/sound.hpp | 1 + 2 files changed, 17 insertions(+) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 6e5806ee3..032d79ab8 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -89,6 +89,7 @@ public: virtual void stop(); virtual bool isPlaying(); + virtual void setVolume(float volume); virtual void update(const float *pos); void play(); @@ -252,6 +253,13 @@ bool OpenAL_SoundStream::isPlaying() return !mIsFinished; } +void OpenAL_SoundStream::setVolume(float volume) +{ + alSourcef(mSource, AL_GAIN, volume*mBaseVolume); + throwALerror(); + mVolume = volume; +} + void OpenAL_SoundStream::update(const float *pos) { alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); @@ -329,6 +337,7 @@ public: virtual void stop(); virtual bool isPlaying(); + virtual void setVolume(float volume); virtual void update(const float *pos); }; @@ -361,6 +370,13 @@ bool OpenAL_Sound::isPlaying() return state==AL_PLAYING; } +void OpenAL_Sound::setVolume(float volume) +{ + alSourcef(mSource, AL_GAIN, volume*mBaseVolume); + throwALerror(); + mVolume = volume; +} + void OpenAL_Sound::update(const float *pos) { alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index a5617bc5a..b5d010aa0 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,6 +7,7 @@ namespace MWSound { virtual void stop() = 0; virtual bool isPlaying() = 0; + virtual void setVolume(float volume) = 0; virtual void update(const float *pos) = 0; Sound& operator=(const Sound &rhs); From 293f33914e78b8d8aeb44ff06a4cf487a194c85b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 28 Mar 2012 05:35:51 -0700 Subject: [PATCH 149/152] Use a deque fpr OpenAL's free sources --- apps/openmw/mwsound/openal_output.cpp | 43 ++++++++++++++------------- apps/openmw/mwsound/openal_output.hpp | 8 ++--- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 032d79ab8..ea49eb2f2 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -436,19 +436,22 @@ void OpenAL_Output::init(const std::string &devname) alcGetIntegerv(mDevice, ALC_STEREO_SOURCES, 1, &maxstereo); throwALCerror(mDevice); - mFreeSources.resize(std::min(maxmono+maxstereo, 256)); - for(size_t i = 0;i < mFreeSources.size();i++) + try { - ALuint src; - alGenSources(1, &src); - if(alGetError() != AL_NO_ERROR) + ALCuint maxtotal = std::min(maxmono+maxstereo, 256); + for(size_t i = 0;i < maxtotal;i++) { - mFreeSources.resize(i); - break; + ALuint src = 0; + alGenSources(1, &src); + throwALerror(); + mFreeSources.push_back(src); } - mFreeSources[i] = src; } - if(mFreeSources.size() == 0) + catch(std::exception &e) + { + std::cout <<"Error: "<removeAll(); - if(!mFreeSources.empty()) + while(!mFreeSources.empty()) { - alDeleteSources(mFreeSources.size(), mFreeSources.data()); - mFreeSources.clear(); + alDeleteSources(1, &mFreeSources.front()); + mFreeSources.pop_front(); } mBufferRefs.clear(); @@ -584,8 +587,8 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.back(); - mFreeSources.pop_back(); + src = mFreeSources.front(); + mFreeSources.pop_front(); try { @@ -633,8 +636,8 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const float *pos, if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.back(); - mFreeSources.pop_back(); + src = mFreeSources.front(); + mFreeSources.pop_front(); try { @@ -682,8 +685,8 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.back(); - mFreeSources.pop_back(); + src = mFreeSources.front(); + mFreeSources.pop_front(); try { @@ -726,8 +729,8 @@ SoundPtr OpenAL_Output::streamSound3D(const std::string &fname, const float *pos if(mFreeSources.empty()) fail("No free sources"); - src = mFreeSources.back(); - mFreeSources.pop_back(); + src = mFreeSources.front(); + mFreeSources.pop_front(); try { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 2b0897bdb..d288a62f3 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -21,8 +21,9 @@ namespace MWSound ALCdevice *mDevice; ALCcontext *mContext; - typedef std::vector IDVec; - IDVec mFreeSources; + typedef std::deque IDDq; + IDDq mFreeSources; + IDDq mUnusedBuffers; typedef std::map NameMap; NameMap mBufferCache; @@ -30,9 +31,6 @@ namespace MWSound typedef std::map IDRefMap; IDRefMap mBufferRefs; - typedef std::deque IDDq; - IDDq mUnusedBuffers; - uint64_t mBufferCacheMemSize; ALuint getBuffer(const std::string &fname); From be337ef7cc99bb5230105e8f19ba943dd06cb60c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Wed, 28 Mar 2012 06:08:25 -0700 Subject: [PATCH 150/152] Return SoundPtr objects from playSound[3D] Note that each Sound object currently contains "precious" resources even after the sound is stopped. The reference should be reliquished as soon as it's no longer needed (the SoundManager will make sure the sound continues to play until it's finished). --- apps/openmw/mwsound/sound.hpp | 7 ++++--- apps/openmw/mwsound/soundmanager.cpp | 18 ++++++++++++------ apps/openmw/mwsound/soundmanager.hpp | 8 ++++---- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index b5d010aa0..2cbd48d96 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -5,9 +5,6 @@ namespace MWSound { class Sound { - virtual void stop() = 0; - virtual bool isPlaying() = 0; - virtual void setVolume(float volume) = 0; virtual void update(const float *pos) = 0; Sound& operator=(const Sound &rhs); @@ -20,6 +17,10 @@ namespace MWSound float mMaxDistance; public: + virtual void stop() = 0; + virtual bool isPlaying() = 0; + virtual void setVolume(float volume) = 0; + Sound() : mVolume(1.0f) , mBaseVolume(1.0f) , mMinDistance(20.0f) /* 1 * min_range_scale */ diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 8e2ef3a27..ad9e47f72 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -185,7 +185,8 @@ namespace MWSound std::string filePath = std::string("Sound/")+filename; const ESM::Position &pos = ptr.getCellRef().pos; - SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, 20.0f, 12750.0f, false); + SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, + 20.0f, 12750.0f, false); sound->mBaseVolume = basevol; mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); @@ -202,15 +203,16 @@ namespace MWSound } - void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) + SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) { + SoundPtr sound; try { float basevol = 1.0f; /* TODO: volume settings */ float min, max; std::string file = lookup(soundId, basevol, min, max); - SoundPtr sound = mOutput->playSound(file, volume*basevol, pitch, loop); + sound = mOutput->playSound(file, volume*basevol, pitch, loop); sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; @@ -222,11 +224,14 @@ namespace MWSound { std::cout <<"Sound Error: "<playSound3D(file, pos.pos, volume*basevol, pitch, min, max, loop); + sound = mOutput->playSound3D(file, pos.pos, volume*basevol, pitch, min, max, loop); sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; @@ -248,6 +253,7 @@ namespace MWSound { std::cout <<"Sound Error: "< Date: Sat, 24 Mar 2012 14:46:48 +0100 Subject: [PATCH 151/152] ogre 1.8 fixes --- components/bsa/bsa_archive.cpp | 31 ++++++++++++++++++-- components/bsa/bsa_file.cpp | 4 +-- components/bsa/bsa_file.hpp | 4 +-- libs/openengine/bullet/BulletShapeLoader.cpp | 8 ++--- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 72d15944d..80d92dd52 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -256,8 +256,12 @@ public: return DataStreamPtr(new Mangle2OgreStream(strm)); } +bool exists(const String& filename) { + return cexists(filename); +} + // Check if the file exists. - bool exists(const String& filename) { + bool cexists(const String& filename) const { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' @@ -308,6 +312,29 @@ return arc.exists(passed.c_str()); located in BSAs. So instead we channel it through exists() and set up a single-element result list if the file is found. */ + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, + bool dirs = false) const + { + FileInfoListPtr ptr = FileInfoListPtr(new FileInfoList()); + + // Check if the file exists (only works for single files - wild + // cards and recursive search isn't implemented.) + if(cexists(pattern)) + { + FileInfo fi; + fi.archive = this; + fi.filename = pattern; + // It apparently doesn't matter that we return bogus + // information + fi.path = ""; + fi.compressedSize = fi.uncompressedSize = 0; + + ptr->push_back(fi); + } + + return ptr; + } + FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, bool dirs = false) { @@ -315,7 +342,7 @@ return arc.exists(passed.c_str()); // Check if the file exists (only works for single files - wild // cards and recursive search isn't implemented.) - if(exists(pattern)) + if(cexists(pattern)) { FileInfo fi; fi.archive = this; diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 95358a362..f19606703 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -148,9 +148,9 @@ void BSAFile::readHeader() } /// Get the index of a given file name, or -1 if not found -int BSAFile::getIndex(const char *str) +int BSAFile::getIndex(const char *str) const { - Lookup::iterator it; + Lookup::const_iterator it; it = lookup.find(str); if(it == lookup.end()) return -1; diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index f54a64d2a..95fac0f4d 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -93,7 +93,7 @@ class BSAFile void readHeader(); /// Get the index of a given file name, or -1 if not found - int getIndex(const char *str); + int getIndex(const char *str) const; public: @@ -119,7 +119,7 @@ class BSAFile */ /// Check if a file exists - bool exists(const char *file) { return getIndex(file) != -1; } + bool exists(const char *file) const { return getIndex(file) != -1; } /** Open a file contained in the archive. Throws an exception if the file doesn't exist. diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp index 4593bad52..59a414f30 100644 --- a/libs/openengine/bullet/BulletShapeLoader.cpp +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -63,17 +63,17 @@ size_t BulletShape::calculateSize() const //============================================================================================================= -template<> BulletShapeManager *Ogre::Singleton::ms_Singleton = 0; +template<> BulletShapeManager *Ogre::Singleton::msSingleton = 0; BulletShapeManager *BulletShapeManager::getSingletonPtr() { - return ms_Singleton; + return msSingleton; } BulletShapeManager &BulletShapeManager::getSingleton() { - assert(ms_Singleton); - return(*ms_Singleton); + assert(msSingleton); + return(*msSingleton); } BulletShapeManager::BulletShapeManager() From f2075c7f4fb3caebd7c8d0156ef55f39050719e9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 28 Mar 2012 21:40:06 +0200 Subject: [PATCH 152/152] temporarily silence warnigns in OGRE headers --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c531e475..3273cfe0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,7 +160,7 @@ endif (APPLE) # Dependencies -# Fix for not visible pthreads functions for linker with glibc 2.15 +# Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) find_package (Threads) endif() @@ -257,6 +257,9 @@ endif (APPLE) if (CMAKE_COMPILER_IS_GNUCC) add_definitions (-Wall -Wextra -Wno-unused-parameter -Wno-reorder) + # Silence warnings in OGRE headers. Remove once OGRE got fixed! + add_definitions (-Wno-ignored-qualifiers) + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) if ("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) @@ -332,7 +335,7 @@ if(WIN32) SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;esmtool;Esmtool;omwlauncher;OpenMW Launcher") - set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") + set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/readme.txt") SET(CPACK_RESOURCE_FILE_LICENSE "${OpenMW_SOURCE_DIR}/GPL3.txt") SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".")