mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-19 23:23:52 +00:00
Merge branch 'master' into equipment
This commit is contained in:
commit
222b5b741e
232 changed files with 15400 additions and 395 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,6 +0,0 @@
|
|||
[submodule "libs/mangle"]
|
||||
path = libs/mangle
|
||||
url = git://github.com/zinnschlag/mangle.git
|
||||
[submodule "libs/openengine"]
|
||||
path = libs/openengine
|
||||
url = git://github.com/zinnschlag/OpenEngine
|
|
@ -255,6 +255,19 @@ if (APPLE)
|
|||
"${APP_BUNDLE_DIR}/Contents/Resources/OpenMW.icns" COPYONLY)
|
||||
|
||||
# prepare plugins
|
||||
if (${CMAKE_BUILD_TYPE} MATCHES "Release")
|
||||
set(OPENMW_RELEASE_BUILD 1)
|
||||
endif()
|
||||
if (${CMAKE_BUILD_TYPE} MATCHES "RelWithDebugInfo")
|
||||
set(OPENMW_RELEASE_BUILD 1)
|
||||
endif()
|
||||
|
||||
if (${OPENMW_RELEASE_BUILD})
|
||||
set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL})
|
||||
else()
|
||||
set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_DBG})
|
||||
endif()
|
||||
|
||||
foreach(plugin ${USED_OGRE_PLUGINS})
|
||||
configure_file("${OGRE_PLUGIN_DIR}/${plugin}.dylib"
|
||||
"${APP_BUNDLE_DIR}/Contents/Plugins/${plugin}.dylib"
|
||||
|
@ -343,6 +356,9 @@ if(WIN32)
|
|||
SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org")
|
||||
SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org")
|
||||
SET(CPACK_NSIS_INSTALLED_ICON_NAME "omwlauncher.exe")
|
||||
SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/apps/launcher/resources/images/openmw.ico")
|
||||
SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/apps/launcher/resources/images/openmw.ico")
|
||||
# SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp")
|
||||
|
||||
SET(VCREDIST32 "${OpenMW_BINARY_DIR}/vcredist_x86.exe")
|
||||
if(EXISTS ${VCREDIST32})
|
||||
|
@ -434,7 +450,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})
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR})
|
||||
|
|
|
@ -41,10 +41,11 @@ source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER} ${LAUNCHER_HEADER_MOC
|
|||
find_package(Qt4 REQUIRED)
|
||||
set(QT_USE_QTGUI 1)
|
||||
|
||||
if (NOT APPLE) # this dependency can be completely removed, but now it only tested on OS X
|
||||
find_package(PNG REQUIRED)
|
||||
include_directories(${PNG_INCLUDE_DIR})
|
||||
endif()
|
||||
# Set some platform specific settings
|
||||
if(WIN32)
|
||||
set(GUI_TYPE WIN32)
|
||||
set(QT_USE_QTMAIN TRUE)
|
||||
endif(WIN32)
|
||||
|
||||
QT4_ADD_RESOURCES(RCC_SRCS resources.qrc)
|
||||
QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
|
||||
|
@ -53,6 +54,7 @@ include(${QT_USE_FILE})
|
|||
|
||||
# Main executable
|
||||
add_executable(omwlauncher
|
||||
${GUI_TYPE}
|
||||
${LAUNCHER}
|
||||
${RCC_SRCS}
|
||||
${MOC_SRCS}
|
||||
|
@ -62,7 +64,6 @@ target_link_libraries(omwlauncher
|
|||
${Boost_LIBRARIES}
|
||||
${OGRE_LIBRARIES}
|
||||
${QT_LIBRARIES}
|
||||
${PNG_LIBRARY}
|
||||
components
|
||||
)
|
||||
|
||||
|
@ -84,5 +85,5 @@ else()
|
|||
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.qss")
|
||||
|
||||
configure_file(${CMAKE_SOURCE_DIR}/files/launcher.cfg
|
||||
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/launcher.cfg")
|
||||
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}launcher.cfg")
|
||||
endif()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#include <QtGui>
|
||||
|
||||
#include <components/esm/esm_reader.hpp>
|
||||
#include <components/files/collections.hpp>
|
||||
#include <components/files/multidircollection.hpp>
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
|
||||
#include "datafilespage.hpp"
|
||||
|
@ -11,6 +9,23 @@
|
|||
#include "pluginsmodel.hpp"
|
||||
#include "pluginsview.hpp"
|
||||
|
||||
#include <boost/version.hpp>
|
||||
/**
|
||||
* Workaround for problems with whitespaces in paths in older versions of Boost library
|
||||
*/
|
||||
#if (BOOST_VERSION <= 104600)
|
||||
namespace boost
|
||||
{
|
||||
|
||||
template<>
|
||||
inline boost::filesystem::path lexical_cast<boost::filesystem::path, std::string>(const std::string& arg)
|
||||
{
|
||||
return boost::filesystem::path(arg);
|
||||
}
|
||||
|
||||
} /* namespace boost */
|
||||
#endif /* (BOOST_VERSION <= 104600) */
|
||||
|
||||
using namespace ESM;
|
||||
using namespace std;
|
||||
|
||||
|
@ -120,11 +135,12 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent)
|
|||
connect(mPluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
|
||||
connect(mPluginsTable, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showContextMenu(const QPoint&)));
|
||||
|
||||
connect(mProfilesComboBox, SIGNAL(textChanged(const QString&, const QString&)), this, SLOT(profileChanged(const QString&, const QString&)));
|
||||
|
||||
|
||||
createActions();
|
||||
setupConfig();
|
||||
setupDataFiles();
|
||||
createActions();
|
||||
|
||||
}
|
||||
|
||||
void DataFilesPage::setupConfig()
|
||||
|
@ -138,8 +154,7 @@ void DataFilesPage::setupConfig()
|
|||
|
||||
// Open our config file
|
||||
mLauncherConfig = new QSettings(config, QSettings::IniFormat);
|
||||
mLauncherConfig->sync();
|
||||
|
||||
file.close();
|
||||
|
||||
// Make sure we have no groups open
|
||||
while (!mLauncherConfig->group().isEmpty()) {
|
||||
|
@ -162,13 +177,14 @@ void DataFilesPage::setupConfig()
|
|||
// No current profile selected
|
||||
currentProfile = "Default";
|
||||
}
|
||||
mProfilesComboBox->setCurrentIndex(mProfilesComboBox->findText(currentProfile));
|
||||
|
||||
const int currentIndex = mProfilesComboBox->findText(currentProfile);
|
||||
if (currentIndex != -1) {
|
||||
// Profile is found
|
||||
mProfilesComboBox->setCurrentIndex(currentIndex);
|
||||
}
|
||||
|
||||
mLauncherConfig->endGroup();
|
||||
|
||||
// Now we connect the combobox to do something if the profile changes
|
||||
// This prevents strange behaviour while reading and appending the profiles
|
||||
connect(mProfilesComboBox, SIGNAL(textChanged(const QString&, const QString&)), this, SLOT(profileChanged(const QString&, const QString&)));
|
||||
}
|
||||
|
||||
|
||||
|
@ -188,10 +204,11 @@ void DataFilesPage::setupDataFiles()
|
|||
|
||||
// Put the paths in a boost::filesystem vector to use with Files::Collections
|
||||
Files::PathContainer dataDirs(variables["data"].as<Files::PathContainer>());
|
||||
mDataDirs = dataDirs;
|
||||
|
||||
// std::string local(variables["data-local"].as<std::string>());
|
||||
// if (!local.empty())
|
||||
// {
|
||||
// std::string local = variables["data-local"].as<std::string>();
|
||||
// if (!local.empty()) {
|
||||
// mDataLocal.push_back(Files::PathContainer::value_type(local));
|
||||
// dataDirs.push_back(Files::PathContainer::value_type(local));
|
||||
// }
|
||||
|
||||
|
@ -200,6 +217,43 @@ void DataFilesPage::setupDataFiles()
|
|||
|
||||
mCfgMgr.processPaths(dataDirs);
|
||||
|
||||
while (dataDirs.empty()) {
|
||||
// No valid data files directory found
|
||||
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Error detecting Morrowind installation");
|
||||
msgBox.setIcon(QMessageBox::Critical);
|
||||
msgBox.setStandardButtons(QMessageBox::Cancel);
|
||||
msgBox.setText(tr("<br><b>Could not find the Data Files location</b><br><br> \
|
||||
The directory containing the Data Files was not found.<br><br> \
|
||||
Press \"Browse...\" to specify the location manually.<br>"));
|
||||
|
||||
QAbstractButton *dirSelectButton =
|
||||
msgBox.addButton(tr("B&rowse..."), QMessageBox::ActionRole);
|
||||
|
||||
msgBox.exec();
|
||||
|
||||
if (msgBox.clickedButton() == dirSelectButton) {
|
||||
|
||||
QString dataDir = QFileDialog::getExistingDirectory(
|
||||
this, tr("Select Data Files Directory"),
|
||||
"/home",
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
|
||||
dataDirs.push_back(Files::PathContainer::value_type(dataDir.toStdString()));
|
||||
mDataDirs.push_back(Files::PathContainer::value_type(dataDir.toStdString()));
|
||||
} else {
|
||||
// Cancel
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if cancel was clicked because we can't exit from while loop
|
||||
if (dataDirs.empty()) {
|
||||
QApplication::exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a file collection for the dataDirs
|
||||
Files::Collections fileCollections(dataDirs, !variables["fs-strict"].as<bool>());
|
||||
|
||||
|
@ -207,15 +261,13 @@ void DataFilesPage::setupDataFiles()
|
|||
const Files::MultiDirCollection &esm = fileCollections.getCollection(".esm");
|
||||
unsigned int i = 0; // Row number
|
||||
|
||||
for (Files::MultiDirCollection::TIter iter(esm.begin()); iter!=esm.end(); ++iter)
|
||||
{
|
||||
for (Files::MultiDirCollection::TIter iter(esm.begin()); iter!=esm.end(); ++iter) {
|
||||
QString currentMaster = QString::fromStdString(
|
||||
boost::filesystem::path (iter->second.filename()).string());
|
||||
|
||||
const QList<QTableWidgetItem*> itemList = mMastersWidget->findItems(currentMaster, Qt::MatchExactly);
|
||||
|
||||
if (itemList.isEmpty()) // Master is not yet in the widget
|
||||
{
|
||||
if (itemList.isEmpty()) { // Master is not yet in the widget
|
||||
mMastersWidget->insertRow(i);
|
||||
QTableWidgetItem *item = new QTableWidgetItem(currentMaster);
|
||||
mMastersWidget->setItem(i, 0, item);
|
||||
|
@ -226,8 +278,7 @@ void DataFilesPage::setupDataFiles()
|
|||
// Now on to the plugins
|
||||
const Files::MultiDirCollection &esp = fileCollections.getCollection(".esp");
|
||||
|
||||
for (Files::MultiDirCollection::TIter iter(esp.begin()); iter!=esp.end(); ++iter)
|
||||
{
|
||||
for (Files::MultiDirCollection::TIter iter(esp.begin()); iter!=esp.end(); ++iter) {
|
||||
ESMReader fileReader;
|
||||
QStringList availableMasters; // Will contain all found masters
|
||||
|
||||
|
@ -243,8 +294,7 @@ void DataFilesPage::setupDataFiles()
|
|||
|
||||
const QList<QTableWidgetItem*> itemList = mMastersWidget->findItems(currentMaster, Qt::MatchExactly);
|
||||
|
||||
if (itemList.isEmpty()) // Master is not yet in the widget
|
||||
{
|
||||
if (itemList.isEmpty()) { // Master is not yet in the widget
|
||||
mMastersWidget->insertRow(i);
|
||||
|
||||
QTableWidgetItem *item = new QTableWidgetItem(currentMaster);
|
||||
|
@ -433,18 +483,18 @@ void DataFilesPage::deleteProfile()
|
|||
return;
|
||||
}
|
||||
|
||||
QMessageBox deleteMessageBox(this);
|
||||
deleteMessageBox.setWindowTitle(tr("Delete Profile"));
|
||||
deleteMessageBox.setText(tr("Are you sure you want to delete <b>%0</b>?").arg(profile));
|
||||
deleteMessageBox.setIcon(QMessageBox::Warning);
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Delete Profile"));
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.setStandardButtons(QMessageBox::Cancel);
|
||||
msgBox.setText(tr("Are you sure you want to delete <b>%0</b>?").arg(profile));
|
||||
|
||||
QAbstractButton *deleteButton =
|
||||
deleteMessageBox.addButton(tr("Delete"), QMessageBox::ActionRole);
|
||||
msgBox.addButton(tr("Delete"), QMessageBox::ActionRole);
|
||||
|
||||
deleteMessageBox.addButton(QMessageBox::Cancel);
|
||||
msgBox.exec();
|
||||
|
||||
deleteMessageBox.exec();
|
||||
|
||||
if (deleteMessageBox.clickedButton() == deleteButton) {
|
||||
if (msgBox.clickedButton() == deleteButton) {
|
||||
// Make sure we have no groups open
|
||||
while (!mLauncherConfig->group().isEmpty()) {
|
||||
mLauncherConfig->endGroup();
|
||||
|
@ -501,7 +551,6 @@ void DataFilesPage::moveUp()
|
|||
void DataFilesPage::moveDown()
|
||||
{
|
||||
// Shift the selected plugins down one row
|
||||
|
||||
if (!mPluginsTable->selectionModel()->hasSelection()) {
|
||||
return;
|
||||
}
|
||||
|
@ -917,9 +966,18 @@ void DataFilesPage::filterChanged(const QString filter)
|
|||
|
||||
void DataFilesPage::profileChanged(const QString &previous, const QString ¤t)
|
||||
{
|
||||
// Prevent the deletion of the default profile
|
||||
if (current == "Default") {
|
||||
mDeleteProfileAction->setEnabled(false);
|
||||
} else {
|
||||
mDeleteProfileAction->setEnabled(true);
|
||||
}
|
||||
|
||||
if (!previous.isEmpty()) {
|
||||
writeConfig(previous);
|
||||
mLauncherConfig->sync();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
uncheckPlugins();
|
||||
|
@ -992,14 +1050,6 @@ void DataFilesPage::writeConfig(QString profile)
|
|||
return;
|
||||
}
|
||||
|
||||
if (profile.isEmpty()) {
|
||||
profile = mProfilesComboBox->currentText();
|
||||
}
|
||||
|
||||
if (profile.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the OpenMW config
|
||||
QString config = QString::fromStdString((mCfgMgr.getLocalPath() / "openmw.cfg").string());
|
||||
QFile file(config);
|
||||
|
@ -1028,10 +1078,14 @@ void DataFilesPage::writeConfig(QString profile)
|
|||
QTextStream in(&file);
|
||||
QByteArray buffer;
|
||||
|
||||
// Remove all previous master/plugin entries from config
|
||||
// Remove all previous entries from config
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine();
|
||||
if (!line.contains("master") && !line.contains("plugin")) {
|
||||
if (!line.startsWith("master") &&
|
||||
!line.startsWith("plugin") &&
|
||||
!line.startsWith("data") &&
|
||||
!line.startsWith("data-local"))
|
||||
{
|
||||
buffer += line += "\n";
|
||||
}
|
||||
}
|
||||
|
@ -1052,9 +1106,52 @@ void DataFilesPage::writeConfig(QString profile)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!buffer.isEmpty()) {
|
||||
file.write(buffer);
|
||||
}
|
||||
|
||||
QTextStream gameConfig(&file);
|
||||
|
||||
// First write the list of data dirs
|
||||
mCfgMgr.processPaths(mDataDirs);
|
||||
mCfgMgr.processPaths(mDataLocal);
|
||||
|
||||
QString path;
|
||||
|
||||
// data= directories
|
||||
for (Files::PathContainer::iterator it = mDataDirs.begin(); it != mDataDirs.end(); ++it) {
|
||||
path = QString::fromStdString(it->string());
|
||||
path.remove(QChar('\"'));
|
||||
|
||||
// Make sure the string is quoted when it contains spaces
|
||||
if (path.contains(" ")) {
|
||||
gameConfig << "data=\"" << path << "\"" << endl;
|
||||
} else {
|
||||
gameConfig << "data=" << path << endl;
|
||||
}
|
||||
}
|
||||
|
||||
// data-local directory
|
||||
if (!mDataLocal.empty()) {
|
||||
path = QString::fromStdString(mDataLocal.front().string());
|
||||
path.remove(QChar('\"'));
|
||||
|
||||
if (path.contains(" ")) {
|
||||
gameConfig << "data-local=\"" << path << "\"" << endl;
|
||||
} else {
|
||||
gameConfig << "data-local=" << path << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (profile.isEmpty()) {
|
||||
profile = mProfilesComboBox->currentText();
|
||||
}
|
||||
|
||||
if (profile.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we have no groups open
|
||||
while (!mLauncherConfig->group().isEmpty()) {
|
||||
mLauncherConfig->endGroup();
|
||||
|
@ -1067,7 +1164,7 @@ void DataFilesPage::writeConfig(QString profile)
|
|||
mLauncherConfig->beginGroup(profile);
|
||||
mLauncherConfig->remove(""); // Clear the subgroup
|
||||
|
||||
// First write the masters to the configs
|
||||
// Now write the masters to the configs
|
||||
const QStringList masters = selectedMasters();
|
||||
|
||||
// We don't use foreach because we need i
|
||||
|
@ -1079,11 +1176,10 @@ void DataFilesPage::writeConfig(QString profile)
|
|||
|
||||
}
|
||||
|
||||
// Now write all checked plugins
|
||||
// And finally write all checked plugins
|
||||
const QStringList plugins = checkedPlugins();
|
||||
|
||||
for (int i = 0; i < plugins.size(); ++i)
|
||||
{
|
||||
for (int i = 0; i < plugins.size(); ++i) {
|
||||
const QString currentPlugin = plugins.at(i);
|
||||
mLauncherConfig->setValue(QString("Plugin%1").arg(i), currentPlugin);
|
||||
gameConfig << "plugin=" << currentPlugin << endl;
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#include <QWidget>
|
||||
#include <QModelIndex>
|
||||
|
||||
#include <components/files/collections.hpp>
|
||||
|
||||
#include "combobox.hpp"
|
||||
|
||||
class QTableWidget;
|
||||
|
@ -77,6 +80,8 @@ private:
|
|||
QAction *mUncheckAction;
|
||||
|
||||
Files::ConfigurationManager &mCfgMgr;
|
||||
Files::PathContainer mDataDirs;
|
||||
Files::PathContainer mDataLocal;
|
||||
|
||||
QSettings *mLauncherConfig;
|
||||
|
||||
|
|
|
@ -246,13 +246,7 @@ void GraphicsPage::setupOgre()
|
|||
if (mOpenGLRenderSystem) {
|
||||
mOGLRTTComboBox->addItems(getAvailableOptions(QString("RTT Preferred Mode"), mOpenGLRenderSystem));
|
||||
mOGLAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mOpenGLRenderSystem));
|
||||
|
||||
QStringList videoModes = getAvailableOptions(QString("Video Mode"), mOpenGLRenderSystem);
|
||||
// Remove extraneous spaces
|
||||
videoModes.replaceInStrings(QRegExp("\\s{2,}"), QString(" "));
|
||||
videoModes.replaceInStrings(QRegExp("^\\s"), QString());
|
||||
|
||||
mOGLResolutionComboBox->addItems(videoModes);
|
||||
mOGLResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mOpenGLRenderSystem));
|
||||
mOGLFrequencyComboBox->addItems(getAvailableOptions(QString("Display Frequency"), mOpenGLRenderSystem));
|
||||
}
|
||||
|
||||
|
@ -261,12 +255,7 @@ void GraphicsPage::setupOgre()
|
|||
mD3DRenderDeviceComboBox->addItems(getAvailableOptions(QString("Rendering Device"), mDirect3DRenderSystem));
|
||||
mD3DAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mDirect3DRenderSystem));
|
||||
mD3DFloatingPointComboBox->addItems(getAvailableOptions(QString("Floating-point mode"), mDirect3DRenderSystem));
|
||||
|
||||
QStringList videoModes = getAvailableOptions(QString("Video Mode"), mDirect3DRenderSystem);
|
||||
// Remove extraneous spaces
|
||||
videoModes.replaceInStrings(QRegExp("\\s{2,}"), QString(" "));
|
||||
videoModes.replaceInStrings(QRegExp("^\\s"), QString());
|
||||
mD3DResolutionComboBox->addItems(videoModes);
|
||||
mD3DResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mDirect3DRenderSystem));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -482,7 +471,7 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy
|
|||
{
|
||||
|
||||
if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0)
|
||||
result << (*opt_it).c_str();
|
||||
result << QString::fromStdString((*opt_it).c_str()).simplified();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -118,8 +118,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt)
|
|||
// sound
|
||||
if (mUseSound)
|
||||
{
|
||||
if (!mEnvironment.mSoundManager->isMusicPlaying())
|
||||
mEnvironment.mSoundManager->startRandomTitle();
|
||||
mEnvironment.mSoundManager->playPlaylist();
|
||||
|
||||
mEnvironment.mSoundManager->update (evt.timeSinceLastFrame);
|
||||
}
|
||||
|
@ -341,7 +340,6 @@ void OMW::Engine::go()
|
|||
// Create sound system
|
||||
mEnvironment.mSoundManager = new MWSound::SoundManager(mOgre->getRoot(),
|
||||
mOgre->getCamera(),
|
||||
mEnvironment.mWorld->getStore(),
|
||||
mDataDirs,
|
||||
mUseSound, mFSStrict, mEnvironment);
|
||||
|
||||
|
@ -390,7 +388,7 @@ void OMW::Engine::go()
|
|||
mOgre->getRoot()->addFrameListener (this);
|
||||
|
||||
// Play some good 'ol tunes
|
||||
mEnvironment.mSoundManager->startRandomTitle();
|
||||
mEnvironment.mSoundManager->playPlaylist(std::string("Explore"));
|
||||
|
||||
// scripts
|
||||
if (mCompileAll)
|
||||
|
|
|
@ -276,6 +276,7 @@ namespace MWRender{
|
|||
rotmult = bonePtr->getOrientation();
|
||||
scale = bonePtr->getScale().x;
|
||||
boneSequenceIter++;
|
||||
|
||||
for(; boneSequenceIter != boneSequence.end(); boneSequenceIter++)
|
||||
{
|
||||
if(creaturemodel->getSkeleton()->hasBone(*boneSequenceIter)){
|
||||
|
@ -330,7 +331,7 @@ namespace MWRender{
|
|||
}
|
||||
|
||||
}
|
||||
bool Animation::timeIndex( float time, std::vector<float> times, int & i, int & j, float & x ){
|
||||
bool Animation::timeIndex( float time, const std::vector<float> & times, int & i, int & j, float & x ){
|
||||
int count;
|
||||
if ( (count = times.size()) > 0 )
|
||||
{
|
||||
|
@ -388,6 +389,8 @@ namespace MWRender{
|
|||
}
|
||||
|
||||
void Animation::handleAnimationTransforms(){
|
||||
|
||||
|
||||
Ogre::SkeletonInstance* skel = base->getSkeleton();
|
||||
|
||||
|
||||
|
@ -404,10 +407,10 @@ namespace MWRender{
|
|||
for(unsigned int i = 0; i < entityparts.size(); i++){
|
||||
//Ogre::SkeletonInstance* skel = entityparts[i]->getSkeleton();
|
||||
|
||||
Ogre::Bone* b = skel->getRootBone();
|
||||
b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3));//This is a trick
|
||||
//Ogre::Bone* b = skel->getRootBone();
|
||||
//b->setOrientation(Ogre::Real(.3),Ogre::Real(.3),Ogre::Real(.3), Ogre::Real(.3));//This is a trick
|
||||
|
||||
entityparts[i]->getAllAnimationStates()->_notifyDirty();
|
||||
//entityparts[i]->getAllAnimationStates()->_notifyDirty();
|
||||
}
|
||||
|
||||
|
||||
|
@ -424,18 +427,19 @@ namespace MWRender{
|
|||
float x;
|
||||
float x2;
|
||||
|
||||
std::vector<Ogre::Quaternion> quats = iter->getQuat();
|
||||
const std::vector<Ogre::Quaternion> & quats = iter->getQuat();
|
||||
|
||||
std::vector<float> ttime = iter->gettTime();
|
||||
std::vector<float>::iterator ttimeiter = ttime.begin();
|
||||
const std::vector<float> & ttime = iter->gettTime();
|
||||
|
||||
|
||||
const std::vector<float> & rtime = iter->getrTime();
|
||||
int rindexJ = rindexI[slot];
|
||||
|
||||
std::vector<float> rtime = iter->getrTime();
|
||||
int rindexJ = 0;
|
||||
timeIndex(time, rtime, rindexI[slot], rindexJ, x2);
|
||||
int tindexJ = 0;
|
||||
int tindexJ = tindexI[slot];
|
||||
|
||||
|
||||
std::vector<Ogre::Vector3> translist1 = iter->getTranslist1();
|
||||
const std::vector<Ogre::Vector3> & translist1 = iter->getTranslist1();
|
||||
|
||||
timeIndex(time, ttime, tindexI[slot], tindexJ, x);
|
||||
|
||||
|
@ -443,34 +447,35 @@ namespace MWRender{
|
|||
Ogre::Quaternion r;
|
||||
|
||||
bool bTrans = translist1.size() > 0;
|
||||
|
||||
|
||||
bool bQuats = quats.size() > 0;
|
||||
|
||||
if(skel->hasBone(iter->getBonename())){
|
||||
Ogre::Bone* bone = skel->getBone(iter->getBonename());
|
||||
if(bTrans){
|
||||
Ogre::Vector3 v1 = translist1[tindexI[slot]];
|
||||
Ogre::Vector3 v2 = translist1[tindexJ];
|
||||
t = (v1 + (v2 - v1) * x);
|
||||
bone->setPosition(t);
|
||||
|
||||
}
|
||||
|
||||
bool bQuats = quats.size() > 0;
|
||||
if(bQuats){
|
||||
r = Ogre::Quaternion::Slerp(x2, quats[rindexI[slot]], quats[rindexJ], true);
|
||||
}
|
||||
skel = base->getSkeleton();
|
||||
if(skel->hasBone(iter->getBonename())){
|
||||
Ogre::Bone* bone = skel->getBone(iter->getBonename());
|
||||
if(bTrans)
|
||||
bone->setPosition(t);
|
||||
if(bQuats)
|
||||
bone->setOrientation(r);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
skel->_updateTransforms();
|
||||
base->getAllAnimationStates()->_notifyDirty();
|
||||
|
||||
}
|
||||
|
||||
|
||||
slot++;
|
||||
}
|
||||
skel->_updateTransforms();
|
||||
base->getAllAnimationStates()->_notifyDirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ class Animation{
|
|||
static std::map<std::string, int> mUniqueIDs;
|
||||
|
||||
|
||||
|
||||
std::vector<std::vector<Nif::NiTriShapeCopy>* > shapeparts; //All the NiTriShape data that we need for animating an npc
|
||||
|
||||
float time;
|
||||
|
@ -55,7 +56,7 @@ class Animation{
|
|||
Ogre::Entity* base;
|
||||
void handleShapes(std::vector<Nif::NiTriShapeCopy>* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel);
|
||||
void handleAnimationTransforms();
|
||||
bool timeIndex( float time, std::vector<float> times, int & i, int & j, float & x );
|
||||
bool timeIndex( float time, const std::vector<float> & times, int & i, int & j, float & x );
|
||||
std::string getUniqueID(std::string mesh);
|
||||
|
||||
public:
|
||||
|
|
|
@ -42,6 +42,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, MWWorld::Environment& _env,O
|
|||
std::string bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4);
|
||||
char secondtolast = bodyRaceID.at(bodyRaceID.length() - 2);
|
||||
bool female = tolower(secondtolast) == 'f';
|
||||
std::transform(bodyRaceID.begin(), bodyRaceID.end(), bodyRaceID.begin(), ::tolower);
|
||||
bool beast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_";
|
||||
|
||||
/*std::cout << "Race: " << ref->base->race ;
|
||||
|
@ -276,6 +277,7 @@ void NpcAnimation::runAnimation(float timepassed){
|
|||
shapepartsiter++;
|
||||
entitypartsiter++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,10 +10,8 @@
|
|||
#include <mangle/sound/clients/ogre_listener_mover.hpp>
|
||||
#include <mangle/sound/clients/ogre_output_updater.hpp>
|
||||
|
||||
#include <components/file_finder/file_finder.hpp>
|
||||
#include <components/esm_store/store.hpp>
|
||||
|
||||
|
||||
#include "../mwworld/environment.hpp"
|
||||
#include "../mwworld/world.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
@ -45,7 +43,6 @@
|
|||
|
||||
using namespace Mangle::Sound;
|
||||
typedef OEngine::Sound::SoundManager OEManager;
|
||||
typedef OEngine::Sound::SoundManagerPtr OEManagerPtr;
|
||||
|
||||
// Set the position on a sound based on a Ptr.
|
||||
static void setPos(SoundPtr &snd, const MWWorld::Ptr ref)
|
||||
|
@ -60,54 +57,41 @@ static void setPos(SoundPtr &snd, const MWWorld::Ptr ref)
|
|||
|
||||
namespace MWSound
|
||||
{
|
||||
struct SoundManager::SoundImpl
|
||||
{
|
||||
/* This is the sound manager. It loades, stores and deletes
|
||||
sounds based on the sound factory it is given.
|
||||
*/
|
||||
OEManagerPtr mgr;
|
||||
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;
|
||||
|
||||
const ESMS::ESMStore &store;
|
||||
|
||||
typedef std::map<std::string,Mangle::Sound::WSoundPtr> IDMap;
|
||||
typedef std::map<MWWorld::Ptr,IDMap> PtrMap;
|
||||
PtrMap sounds;
|
||||
|
||||
// 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 FSstrict;
|
||||
FileFinder::LessTreeFileFinder files;
|
||||
FileFinder::StrictTreeFileFinder strict;
|
||||
FileFinder::LessTreeFileFinder musicpath;
|
||||
FileFinder::StrictTreeFileFinder musicpathStrict;
|
||||
|
||||
SoundImpl(Ogre::Root *root, Ogre::Camera *camera, const ESMS::ESMStore &str,
|
||||
const Files::PathContainer& soundDir,
|
||||
const Files::PathContainer& musicDir,
|
||||
bool fsstrict)
|
||||
: mgr(new OEManager(SoundFactoryPtr(new SOUND_FACTORY)))
|
||||
SoundManager::SoundManager(Ogre::Root *root, Ogre::Camera *camera,
|
||||
const Files::PathContainer& dataDirs,
|
||||
bool useSound, bool fsstrict, MWWorld::Environment& environment)
|
||||
: mFSStrict(fsstrict)
|
||||
, mEnvironment(environment)
|
||||
, mgr(new OEManager(SoundFactoryPtr(new SOUND_FACTORY)))
|
||||
, updater(mgr)
|
||||
, cameraTracker(mgr)
|
||||
, store(str)
|
||||
, FSstrict(fsstrict)
|
||||
, files(soundDir)
|
||||
, strict(soundDir)
|
||||
, musicpath(musicDir)
|
||||
, musicpathStrict(musicDir)
|
||||
, mCurrentPlaylist(NULL)
|
||||
{
|
||||
if(useSound)
|
||||
{
|
||||
// The music library will accept these filetypes
|
||||
// If none is given then it will accept all filetypes
|
||||
std::vector<std::string> 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
|
||||
|
||||
std::cout << "Sound output: " << SOUND_OUT << std::endl;
|
||||
std::cout << "Sound decoder: " << SOUND_IN << std::endl;
|
||||
|
@ -117,118 +101,21 @@ namespace MWSound
|
|||
// Tell Ogre to update the sound system each frame
|
||||
root->addFrameListener(&updater);
|
||||
}
|
||||
}
|
||||
|
||||
~SoundImpl()
|
||||
SoundManager::~SoundManager()
|
||||
{
|
||||
Ogre::Root::getSingleton().removeFrameListener(&updater);
|
||||
cameraTracker.unfollowCamera();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static std::string toMp3(std::string str)
|
||||
{
|
||||
std::string::size_type i = str.rfind('.');
|
||||
if(str.find('/', i) == std::string::npos &&
|
||||
str.find('\\', i) == std::string::npos)
|
||||
str = str.substr(0, i) + ".mp3";
|
||||
else
|
||||
str += ".mp3";
|
||||
return str;
|
||||
}
|
||||
|
||||
bool hasFile(const std::string &str, bool music = false)
|
||||
{
|
||||
bool found = false;
|
||||
if(!FSstrict)
|
||||
{
|
||||
if(music)
|
||||
{
|
||||
found = musicpath.has(str);
|
||||
// Not found? Try with .mp3
|
||||
if (!found)
|
||||
{
|
||||
found = musicpath.has(toMp3(str));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
found = files.has(str);
|
||||
if (!found)
|
||||
{
|
||||
found = files.has(toMp3(str));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(music)
|
||||
{
|
||||
found = musicpathStrict.has(str);
|
||||
// Not found? Try with .mp3
|
||||
if (!found)
|
||||
{
|
||||
found = musicpathStrict.has(toMp3(str));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
found = strict.has(str);
|
||||
if (!found)
|
||||
{
|
||||
found = strict.has(toMp3(str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
// Convert a Morrowind sound path (eg. Fx\funny.wav) to full path
|
||||
// with proper slash conversion (eg. datadir/Sound/Fx/funny.wav)
|
||||
std::string convertPath(const std::string &str, bool music = false)
|
||||
{
|
||||
if(FSstrict == false)
|
||||
{
|
||||
// Search and return
|
||||
if(music && musicpath.has(str))
|
||||
return musicpath.lookup(str);
|
||||
else if(files.has(str))
|
||||
return files.lookup(str);
|
||||
|
||||
// Try mp3 if the wav wasn't found
|
||||
std::string mp3 = toMp3(str);
|
||||
if(music && musicpath.has(mp3))
|
||||
return musicpath.lookup(mp3);
|
||||
else if(files.has(mp3))
|
||||
return files.lookup(mp3);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(music && musicpathStrict.has(str))
|
||||
return musicpathStrict.lookup(str);
|
||||
else if(strict.has(str))
|
||||
return strict.lookup(str);
|
||||
|
||||
// Try mp3 if the wav wasn't found
|
||||
std::string mp3 = toMp3(str);
|
||||
if(music && musicpathStrict.has(mp3))
|
||||
return musicpathStrict.lookup(mp3);
|
||||
else if(strict.has(str))
|
||||
return strict.lookup(mp3);
|
||||
}
|
||||
|
||||
// Give up
|
||||
return "";
|
||||
}
|
||||
|
||||
// Convert a soundId to file name, and modify the volume
|
||||
// according to the sounds local volume setting, minRange and
|
||||
// maxRange.
|
||||
std::string lookup(const std::string &soundId,
|
||||
std::string SoundManager::lookup(const std::string &soundId,
|
||||
float &volume, float &min, float &max)
|
||||
{
|
||||
const ESM::Sound *snd = store.sounds.search(soundId);
|
||||
const ESM::Sound *snd = mEnvironment.mWorld->getStore().sounds.search(soundId);
|
||||
if(snd == NULL) return "";
|
||||
|
||||
if(snd->data.volume == 0)
|
||||
|
@ -249,11 +136,11 @@ namespace MWSound
|
|||
max = std::max(min, max);
|
||||
}
|
||||
|
||||
return convertPath(snd->sound);
|
||||
return Files::FileListLocator(mSoundFiles, snd->sound, mFSStrict);
|
||||
}
|
||||
|
||||
// Add a sound to the list and play it
|
||||
void add(const std::string &file,
|
||||
void SoundManager::add(const std::string &file,
|
||||
MWWorld::Ptr ptr,
|
||||
const std::string &id,
|
||||
float volume, float pitch,
|
||||
|
@ -280,7 +167,7 @@ namespace MWSound
|
|||
|
||||
// Clears all the sub-elements of a given iterator, and then
|
||||
// removes it from 'sounds'.
|
||||
void clearAll(PtrMap::iterator& it)
|
||||
void SoundManager::clearAll(PtrMap::iterator& it)
|
||||
{
|
||||
IDMap::iterator sit = it->second.begin();
|
||||
|
||||
|
@ -301,7 +188,7 @@ namespace MWSound
|
|||
|
||||
// Stop a sound and remove it from the list. If id="" then
|
||||
// remove the entire object and stop all its sounds.
|
||||
void remove(MWWorld::Ptr ptr, const std::string &id = "")
|
||||
void SoundManager::remove(MWWorld::Ptr ptr, const std::string &id)
|
||||
{
|
||||
PtrMap::iterator it = sounds.find(ptr);
|
||||
if(it != sounds.end())
|
||||
|
@ -324,7 +211,7 @@ namespace MWSound
|
|||
}
|
||||
}
|
||||
|
||||
bool isPlaying(MWWorld::Ptr ptr, const std::string &id) const
|
||||
bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const
|
||||
{
|
||||
PtrMap::const_iterator it = sounds.find(ptr);
|
||||
if(it != sounds.end())
|
||||
|
@ -348,7 +235,7 @@ namespace MWSound
|
|||
}
|
||||
|
||||
// Remove all references to objects belonging to a given cell
|
||||
void removeCell(const MWWorld::Ptr::CellStore *cell)
|
||||
void SoundManager::removeCell(const MWWorld::Ptr::CellStore *cell)
|
||||
{
|
||||
PtrMap::iterator it2, it = sounds.begin();
|
||||
while(it != sounds.end())
|
||||
|
@ -360,7 +247,7 @@ namespace MWSound
|
|||
}
|
||||
}
|
||||
|
||||
void updatePositions(MWWorld::Ptr ptr)
|
||||
void SoundManager::updatePositions(MWWorld::Ptr ptr)
|
||||
{
|
||||
// Find the reference (if any)
|
||||
PtrMap::iterator it = sounds.find(ptr);
|
||||
|
@ -378,85 +265,45 @@ namespace MWSound
|
|||
}
|
||||
}
|
||||
}
|
||||
}; /* SoundImpl */
|
||||
|
||||
void SoundManager::stopMusic()
|
||||
{
|
||||
if (music)
|
||||
music->stop();
|
||||
setPlaylist();
|
||||
}
|
||||
|
||||
|
||||
void SoundManager::streamMusicFull(const std::string& filename)
|
||||
{
|
||||
if(!mData) return;
|
||||
|
||||
// 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 (mData->music)
|
||||
mData->music->stop();
|
||||
mData->music = mData->mgr->load(filename);
|
||||
mData->music->setStreaming(true);
|
||||
mData->music->setVolume(0.4);
|
||||
mData->music->play();
|
||||
if (music)
|
||||
music->stop();
|
||||
music = mgr->load(filename);
|
||||
music->setStreaming(true);
|
||||
music->setVolume(0.4);
|
||||
music->play();
|
||||
|
||||
}
|
||||
|
||||
SoundManager::SoundManager(Ogre::Root *root, Ogre::Camera *camera,
|
||||
const ESMS::ESMStore &store, const Files::PathContainer& dataDirs,
|
||||
bool useSound, bool fsstrict, MWWorld::Environment& environment)
|
||||
: mData(NULL)
|
||||
, fsStrict(fsstrict)
|
||||
, mEnvironment(environment)
|
||||
{
|
||||
for (Files::PathContainer::const_iterator it = dataDirs.begin(); it != dataDirs.end(); ++it)
|
||||
{
|
||||
MP3Lookup((*it) / "Music/Explore/");
|
||||
}
|
||||
|
||||
if(useSound)
|
||||
{
|
||||
Files::PathContainer soundDirs;;
|
||||
for (Files::PathContainer::const_iterator it = dataDirs.begin(); it != dataDirs.end(); ++it)
|
||||
{
|
||||
soundDirs.push_back( *it / std::string("Sound"));
|
||||
}
|
||||
mData = new SoundImpl(root, camera, store, soundDirs /* Sound */, dataDirs /* Music */, fsstrict);
|
||||
}
|
||||
|
||||
test.name = "";
|
||||
total = 0;
|
||||
}
|
||||
|
||||
SoundManager::~SoundManager()
|
||||
{
|
||||
if(mData)
|
||||
delete mData;
|
||||
}
|
||||
|
||||
void SoundManager::streamMusic(const std::string& filename)
|
||||
{
|
||||
if(mData->hasFile(filename, true))
|
||||
std::string filePath = mMusicLibrary.locate(filename, mFSStrict).string();
|
||||
if(!filePath.empty())
|
||||
{
|
||||
streamMusicFull(mData->convertPath(filename, true));
|
||||
}
|
||||
}
|
||||
|
||||
void SoundManager::MP3Lookup(const boost::filesystem::path& dir)
|
||||
{
|
||||
boost::filesystem::directory_iterator dir_iter(dir), dir_end;
|
||||
|
||||
std::string mp3extension = ".mp3";
|
||||
for(;dir_iter != dir_end; dir_iter++)
|
||||
{
|
||||
if(boost::filesystem::extension(*dir_iter) == mp3extension)
|
||||
{
|
||||
files.push_back(*dir_iter);
|
||||
}
|
||||
streamMusicFull(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundManager::startRandomTitle()
|
||||
{
|
||||
if(!files.empty())
|
||||
if(mCurrentPlaylist && !mCurrentPlaylist->empty())
|
||||
{
|
||||
Files::PathContainer::iterator fileIter = files.begin();
|
||||
Files::PathContainer::const_iterator fileIter = mCurrentPlaylist->begin();
|
||||
srand( time(NULL) );
|
||||
int r = rand() % files.size() + 1; //old random code
|
||||
int r = rand() % mCurrentPlaylist->size() + 1; //old random code
|
||||
|
||||
std::advance(fileIter, r - 1);
|
||||
std::string music = fileIter->string();
|
||||
|
@ -476,105 +323,157 @@ namespace MWSound
|
|||
bool SoundManager::isMusicPlaying()
|
||||
{
|
||||
bool test = false;
|
||||
if(mData && mData->music)
|
||||
if(music)
|
||||
{
|
||||
test = mData->music->isPlaying();
|
||||
test = music->isPlaying();
|
||||
}
|
||||
return test;
|
||||
}
|
||||
|
||||
SoundManager::SoundImpl SoundManager::getMData()
|
||||
bool SoundManager::setPlaylist(std::string playlist)
|
||||
{
|
||||
// bool test = mData->music->isPlaying();
|
||||
return *mData;
|
||||
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
|
||||
if(!mData) return;
|
||||
if(mData->hasFile(filename))
|
||||
mData->add(mData->convertPath(filename), ptr, "_say_sound", 1, 1, 100, 20000, false);
|
||||
std::string filePath = Files::FileListLocator(mSoundFiles, filename, mFSStrict);
|
||||
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
|
||||
{
|
||||
if(!mData) return false;
|
||||
return !mData->isPlaying(ptr, "_say_sound");
|
||||
return !isPlaying(ptr, "_say_sound");
|
||||
}
|
||||
|
||||
|
||||
void SoundManager::playSound(const std::string& soundId, float volume, float pitch)
|
||||
void SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop)
|
||||
{
|
||||
if(!mData) return;
|
||||
// Play and forget
|
||||
float min, max;
|
||||
const std::string &file = mData->lookup(soundId, volume, min, max);
|
||||
const std::string &file = lookup(soundId, volume, min, max);
|
||||
if (file != "")
|
||||
{
|
||||
SoundPtr snd = mData->mgr->load(file);
|
||||
SoundPtr snd = mgr->load(file);
|
||||
snd->setRepeat(loop);
|
||||
snd->setVolume(volume);
|
||||
snd->setRange(min,max);
|
||||
snd->setPitch(pitch);
|
||||
snd->play();
|
||||
|
||||
if (loop)
|
||||
{
|
||||
// Only add the looping sound once
|
||||
IDMap::iterator it = mLoopedSounds.find(soundId);
|
||||
if(it == mLoopedSounds.end())
|
||||
{
|
||||
mLoopedSounds[soundId] = WSoundPtr(snd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SoundManager::playSound3D (MWWorld::Ptr ptr, const std::string& soundId,
|
||||
float volume, float pitch, bool loop)
|
||||
{
|
||||
if(!mData) return;
|
||||
|
||||
// Look up the sound in the ESM data
|
||||
float min, max;
|
||||
const std::string &file = mData->lookup(soundId, volume, min, max);
|
||||
const std::string &file = lookup(soundId, volume, min, max);
|
||||
if (file != "")
|
||||
mData->add(file, ptr, soundId, volume, pitch, min, max, loop);
|
||||
add(file, ptr, soundId, volume, pitch, min, max, loop);
|
||||
}
|
||||
|
||||
void SoundManager::stopSound3D (MWWorld::Ptr ptr, const std::string& soundId)
|
||||
{
|
||||
if(!mData) return;
|
||||
mData->remove(ptr, soundId);
|
||||
remove(ptr, soundId);
|
||||
}
|
||||
|
||||
void SoundManager::stopSound (MWWorld::Ptr::CellStore *cell)
|
||||
{
|
||||
if(!mData) return;
|
||||
mData->removeCell(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.
|
||||
if(!mData) return true;
|
||||
|
||||
return mData->isPlaying(ptr, soundId);
|
||||
return isPlaying(ptr, soundId);
|
||||
}
|
||||
|
||||
void SoundManager::updateObject(MWWorld::Ptr ptr)
|
||||
{
|
||||
if (mData != NULL)
|
||||
{
|
||||
mData->updatePositions(ptr);
|
||||
}
|
||||
updatePositions(ptr);
|
||||
}
|
||||
|
||||
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) && timer.elapsed() >= 10)
|
||||
if(!(current->cell->data.flags & current->cell->Interior) && timePassed >= 10)
|
||||
{
|
||||
timer.restart();
|
||||
if (test.name != current->cell->region)
|
||||
|
||||
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;
|
||||
test = (ESM::Region) *(mEnvironment.mWorld->getStore().regions.find(current->cell->region));
|
||||
}
|
||||
|
||||
if(test.soundList.size() > 0)
|
||||
|
@ -598,15 +497,15 @@ namespace MWSound
|
|||
soundIter = test.soundList.begin();
|
||||
while (soundIter != test.soundList.end())
|
||||
{
|
||||
const ESM::NAME32 go = soundIter->sound;
|
||||
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.name <<" Chance:" << chance << "\n";
|
||||
mEnvironment.mSoundManager->playSound(go.name, 20.0, 1.0);
|
||||
std::cout << "Sound: " << go <<" Chance:" << chance << "\n";
|
||||
mEnvironment.mSoundManager->playSound(go, 20.0, 1.0);
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -616,7 +515,7 @@ namespace MWSound
|
|||
}
|
||||
else if(current->cell->data.flags & current->cell->Interior)
|
||||
{
|
||||
test.name = "";
|
||||
regionName = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/timer.hpp>
|
||||
#include <mangle/sound/clients/ogre_output_updater.hpp>
|
||||
#include <mangle/sound/clients/ogre_listener_mover.hpp>
|
||||
|
||||
#include <openengine/sound/sndmanager.hpp>
|
||||
|
||||
#include <components/files/filelibrary.hpp>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include <openengine/sound/sndmanager.hpp>
|
||||
#include <components/files/multidircollection.hpp>
|
||||
|
||||
|
||||
|
||||
namespace Ogre
|
||||
|
@ -18,10 +19,15 @@ namespace Ogre
|
|||
class Camera;
|
||||
}
|
||||
|
||||
namespace ESMS
|
||||
namespace Mangle
|
||||
{
|
||||
struct ESMStore;
|
||||
namespace Sound
|
||||
{
|
||||
typedef boost::shared_ptr<Sound> SoundPtr;
|
||||
}
|
||||
}
|
||||
|
||||
typedef OEngine::Sound::SoundManagerPtr OEManagerPtr;
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
@ -30,43 +36,94 @@ namespace MWWorld
|
|||
|
||||
namespace MWSound
|
||||
{
|
||||
//SoundPtr *music;
|
||||
class SoundManager
|
||||
{
|
||||
// Hide implementation details - engine.cpp is compiling
|
||||
// enough as it is.
|
||||
struct SoundImpl;
|
||||
|
||||
SoundImpl *mData;
|
||||
Files::PathContainer files;
|
||||
bool fsStrict;
|
||||
// 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;
|
||||
|
||||
int total;
|
||||
ESM::Region test;
|
||||
boost::timer timer;
|
||||
|
||||
void streamMusicFull (const std::string& filename);
|
||||
///< 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<std::string,Mangle::Sound::WSoundPtr> IDMap;
|
||||
typedef std::map<MWWorld::Ptr,IDMap> PtrMap;
|
||||
PtrMap sounds;
|
||||
|
||||
// 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;
|
||||
|
||||
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);
|
||||
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);
|
||||
void updatePositions(MWWorld::Ptr ptr);
|
||||
|
||||
public:
|
||||
|
||||
SoundManager(Ogre::Root*, Ogre::Camera*, const ESMS::ESMStore &store,
|
||||
SoundManager(Ogre::Root*, Ogre::Camera*,
|
||||
const Files::PathContainer& dataDir, bool useSound, bool fsstrict,
|
||||
MWWorld::Environment& environment);
|
||||
~SoundManager();
|
||||
|
||||
void stopMusic();
|
||||
///< Stops music if it's playing
|
||||
|
||||
void streamMusic(const std::string& filename);
|
||||
///< Play a soundifle
|
||||
/// \param filename name of a sound file in "Music/" in the data directory.
|
||||
|
||||
void startRandomTitle();
|
||||
void MP3Lookup(const boost::filesystem::path& dir);
|
||||
///< Starts a random track from the current playlist
|
||||
|
||||
bool isMusicPlaying();
|
||||
///< Returns true if music is playing
|
||||
|
||||
SoundImpl getMData();
|
||||
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
|
||||
/// 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.
|
||||
|
@ -75,7 +132,7 @@ namespace MWSound
|
|||
bool sayDone (MWWorld::Ptr reference) const;
|
||||
///< Is actor not speaking?
|
||||
|
||||
void playSound (const std::string& soundId, float volume, float pitch);
|
||||
void playSound (const std::string& soundId, float volume, float pitch, bool loop=false);
|
||||
///< Play a sound, independently of 3D-position
|
||||
|
||||
void playSound3D (MWWorld::Ptr reference, const std::string& soundId,
|
||||
|
@ -89,6 +146,9 @@ namespace MWSound
|
|||
void stopSound (MWWorld::Ptr::CellStore *cell);
|
||||
///< Stop all sounds for the given cell.
|
||||
|
||||
void stopSound(const std::string& soundId);
|
||||
///< Stop a non-3d looping sound
|
||||
|
||||
bool getSoundPlaying (MWWorld::Ptr reference, const std::string& soundId) const;
|
||||
///< Is the given sound currently playing on the given object?
|
||||
|
||||
|
|
|
@ -722,6 +722,40 @@ void WeatherManager::update(float duration)
|
|||
mRendering->skyDisable();
|
||||
mRendering->getSkyManager()->setThunder(0.f);
|
||||
}
|
||||
|
||||
// play sounds
|
||||
std::string ambientSnd = (mNextWeather == "" ? mWeatherSettings[mCurrentWeather].mAmbientLoopSoundID : "");
|
||||
if (ambientSnd != "")
|
||||
{
|
||||
if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), ambientSnd) == mSoundsPlaying.end())
|
||||
{
|
||||
mSoundsPlaying.push_back(ambientSnd);
|
||||
mEnvironment->mSoundManager->playSound(ambientSnd, 1.0, 1.0, true);
|
||||
}
|
||||
}
|
||||
|
||||
std::string rainSnd = (mNextWeather == "" ? mWeatherSettings[mCurrentWeather].mRainLoopSoundID : "");
|
||||
if (rainSnd != "")
|
||||
{
|
||||
if (std::find(mSoundsPlaying.begin(), mSoundsPlaying.end(), rainSnd) == mSoundsPlaying.end())
|
||||
{
|
||||
mSoundsPlaying.push_back(rainSnd);
|
||||
mEnvironment->mSoundManager->playSound(rainSnd, 1.0, 1.0, true);
|
||||
}
|
||||
}
|
||||
|
||||
// stop sounds
|
||||
std::vector<std::string>::iterator it=mSoundsPlaying.begin();
|
||||
while (it!=mSoundsPlaying.end())
|
||||
{
|
||||
if ( *it != ambientSnd && *it != rainSnd)
|
||||
{
|
||||
mEnvironment->mSoundManager->stopSound(*it);
|
||||
it = mSoundsPlaying.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void WeatherManager::setHour(const float hour)
|
||||
|
|
|
@ -246,6 +246,8 @@ namespace MWWorld
|
|||
|
||||
std::map<std::string, std::string> mRegionOverrides;
|
||||
|
||||
std::vector<std::string> mSoundsPlaying;
|
||||
|
||||
Ogre::String mCurrentWeather;
|
||||
Ogre::String mNextWeather;
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ add_component_dir (misc
|
|||
|
||||
add_component_dir (files
|
||||
linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager
|
||||
filelibrary
|
||||
)
|
||||
|
||||
add_component_dir (compiler
|
||||
|
|
120
components/files/filelibrary.cpp
Normal file
120
components/files/filelibrary.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#include "filelibrary.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace Files
|
||||
{
|
||||
// Looks for a string in a vector of strings
|
||||
bool containsVectorString(const StringVector& list, const std::string& str)
|
||||
{
|
||||
for (StringVector::const_iterator iter = list.begin();
|
||||
iter != list.end(); iter++)
|
||||
{
|
||||
if (*iter == str)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Searches a path and adds the results to the library
|
||||
void FileLibrary::add(const boost::filesystem::path &root, bool recursive, bool strict,
|
||||
const StringVector &acceptableExtensions)
|
||||
{
|
||||
if (!boost::filesystem::exists(root))
|
||||
{
|
||||
std::cout << "Warning " << root.string() << " does not exist.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string fileExtension;
|
||||
std::string type;
|
||||
|
||||
// remember the last location of the priority list when listing new items
|
||||
int length = mPriorityList.size();
|
||||
|
||||
// First makes a list of all candidate files
|
||||
FileLister(root, mPriorityList, recursive);
|
||||
|
||||
// Then sort these files into sections according to the folder they belong to
|
||||
for (PathContainer::iterator listIter = mPriorityList.begin() + length;
|
||||
listIter != mPriorityList.end(); ++listIter)
|
||||
{
|
||||
if( !acceptableExtensions.empty() )
|
||||
{
|
||||
fileExtension = boost::filesystem::path (listIter->extension()).string();
|
||||
boost::algorithm::to_lower(fileExtension);
|
||||
if(!containsVectorString(acceptableExtensions, fileExtension))
|
||||
continue;
|
||||
}
|
||||
|
||||
type = boost::filesystem::path (listIter->parent_path().leaf()).string();
|
||||
if (!strict)
|
||||
boost::algorithm::to_lower(type);
|
||||
|
||||
mMap[type].push_back(*listIter);
|
||||
// std::cout << "Added path: " << listIter->string() << " in section "<< type <<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the named section exists
|
||||
bool FileLibrary::containsSection(std::string sectionName, bool strict)
|
||||
{
|
||||
if (!strict)
|
||||
boost::algorithm::to_lower(sectionName);
|
||||
StringPathContMap::const_iterator mapIter = mMap.find(sectionName);
|
||||
if (mapIter == mMap.end())
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a pointer to const for a section of the library
|
||||
const PathContainer* FileLibrary::section(std::string sectionName, bool strict)
|
||||
{
|
||||
if (!strict)
|
||||
boost::algorithm::to_lower(sectionName);
|
||||
StringPathContMap::const_iterator mapIter = mMap.find(sectionName);
|
||||
if (mapIter == mMap.end())
|
||||
{
|
||||
//std::cout << "Empty\n";
|
||||
return &mEmptyPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return &(mapIter->second);
|
||||
}
|
||||
}
|
||||
|
||||
// Searches the library for an item and returns a boost path to it
|
||||
boost::filesystem::path FileLibrary::locate(std::string item, bool strict, std::string sectionName)
|
||||
{
|
||||
boost::filesystem::path result("");
|
||||
if (sectionName == "")
|
||||
{
|
||||
return FileListLocator(mPriorityList, boost::filesystem::path(item), strict);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!containsSection(sectionName, strict))
|
||||
{
|
||||
std::cout << "Warning: There is no section named " << sectionName << "\n";
|
||||
return result;
|
||||
}
|
||||
result = FileListLocator(mMap[sectionName], boost::filesystem::path(item), strict);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Prints all the available sections, used for debugging
|
||||
void FileLibrary::printSections()
|
||||
{
|
||||
for(StringPathContMap::const_iterator mapIter = mMap.begin();
|
||||
mapIter != mMap.end(); mapIter++)
|
||||
{
|
||||
std::cout << mapIter->first <<std::endl;
|
||||
}
|
||||
}
|
||||
}
|
49
components/files/filelibrary.hpp
Normal file
49
components/files/filelibrary.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#ifndef COMPONENTS_FILES_FILELIBRARY_HPP
|
||||
#define COMPONENTS_FILES_FILELIBRARY_HPP
|
||||
|
||||
#include <components/files/fileops.hpp>
|
||||
|
||||
namespace Files
|
||||
{
|
||||
typedef std::map<std::string, PathContainer> StringPathContMap;
|
||||
typedef std::vector<std::string> StringVector;
|
||||
|
||||
/// Looks for a string in a vector of strings
|
||||
bool containsVectorString(const StringVector& list, const std::string& str);
|
||||
|
||||
/// \brief Searches directories and makes lists of files according to folder name
|
||||
class FileLibrary
|
||||
{
|
||||
private:
|
||||
StringPathContMap mMap;
|
||||
PathContainer mEmptyPath;
|
||||
PathContainer mPriorityList;
|
||||
|
||||
public:
|
||||
/// Searches a path and adds the results to the library
|
||||
/// Recursive search and fs strict options are available
|
||||
/// Takes a vector of acceptable files extensions, if none is given it lists everything.
|
||||
void add(const boost::filesystem::path &root, bool recursive, bool strict,
|
||||
const StringVector &acceptableExtensions);
|
||||
|
||||
/// Returns true if the named section exists
|
||||
/// You can run this check before running section()
|
||||
bool containsSection(std::string sectionName, bool strict);
|
||||
|
||||
/// Returns a pointer to const for a section of the library
|
||||
/// which is essentially a PathContainer.
|
||||
/// If the section does not exists it returns a pointer to an empty path.
|
||||
const PathContainer* section(std::string sectionName, bool strict);
|
||||
|
||||
/// Searches the library for an item and returns a boost path to it
|
||||
/// Optionally you can provide a specific section
|
||||
/// The result is the first that comes up according to alphabetical
|
||||
/// section naming
|
||||
boost::filesystem::path locate(std::string item, bool strict, std::string sectionName="");
|
||||
|
||||
/// Prints all the available sections, used for debugging
|
||||
void printSections();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,5 +1,9 @@
|
|||
#include "fileops.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace Files
|
||||
{
|
||||
|
@ -9,4 +13,90 @@ bool isFile(const char *name)
|
|||
return boost::filesystem::exists(boost::filesystem::path(name));
|
||||
}
|
||||
|
||||
// Makes a list of files from a directory
|
||||
void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive)
|
||||
{
|
||||
if (!boost::filesystem::exists(currentPath))
|
||||
{
|
||||
std::cout << "WARNING: " << currentPath.string() << " does not exist.\n";
|
||||
return ;
|
||||
}
|
||||
if (recursive)
|
||||
{
|
||||
for ( boost::filesystem::recursive_directory_iterator end, itr(currentPath.string());
|
||||
itr != end; ++itr )
|
||||
{
|
||||
if ( boost::filesystem::is_regular_file(*itr))
|
||||
list.push_back(itr->path());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( boost::filesystem::directory_iterator end, itr(currentPath.string());
|
||||
itr != end; ++itr )
|
||||
{
|
||||
if ( boost::filesystem::is_regular_file(*itr))
|
||||
list.push_back(itr->path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Locates path in path container
|
||||
boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, bool strict)
|
||||
{
|
||||
boost::filesystem::path result("");
|
||||
if (list.empty())
|
||||
return result;
|
||||
|
||||
std::string toFindStr = toFind.string();
|
||||
|
||||
std::string fullPath;
|
||||
|
||||
// The filesystems slash sets the default slash
|
||||
std::string slash;
|
||||
std::string wrongslash;
|
||||
if(list[0].string().find("\\") != std::string::npos)
|
||||
{
|
||||
slash = "\\";
|
||||
wrongslash = "/";
|
||||
}
|
||||
else
|
||||
{
|
||||
slash = "/";
|
||||
wrongslash = "\\";
|
||||
}
|
||||
|
||||
// The file being looked for is converted to the new slash
|
||||
if(toFindStr.find(wrongslash) != std::string::npos )
|
||||
{
|
||||
boost::replace_all(toFindStr, wrongslash, slash);
|
||||
}
|
||||
|
||||
if (!strict)
|
||||
{
|
||||
boost::algorithm::to_lower(toFindStr);
|
||||
}
|
||||
|
||||
for (Files::PathContainer::const_iterator it = list.begin(); it != list.end(); ++it)
|
||||
{
|
||||
fullPath = it->string();
|
||||
if (!strict)
|
||||
{
|
||||
boost::algorithm::to_lower(fullPath);
|
||||
}
|
||||
if(fullPath.find(toFindStr) != std::string::npos)
|
||||
{
|
||||
result = *it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Overloaded form of the locator that takes a string and returns a string
|
||||
std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict)
|
||||
{
|
||||
return FileListLocator(list, boost::filesystem::path(toFind), strict).string();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
#ifndef COMPONENTS_FILES_FILEOPS_HPP
|
||||
#define COMPONENTS_FILES_FILEOPS_HPP
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
namespace Files
|
||||
{
|
||||
|
||||
|
@ -8,6 +14,24 @@ namespace Files
|
|||
///\param [in] name - filename
|
||||
bool isFile(const char *name);
|
||||
|
||||
/// A vector of Boost Paths, very handy
|
||||
typedef std::vector<boost::filesystem::path> PathContainer;
|
||||
|
||||
/// Makes a list of files from a directory by taking a boost
|
||||
/// path and a Path Container and adds to the Path container
|
||||
/// all files in the path. It has a recursive option.
|
||||
void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive);
|
||||
|
||||
/// Locates boost path in path container
|
||||
/// returns the path from the container
|
||||
/// that contains the searched path.
|
||||
/// If it's not found it returns and empty path
|
||||
/// Takes care of slashes, backslashes and it has a strict option.
|
||||
boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, bool strict);
|
||||
|
||||
/// Overloaded form of the locator that takes a string and returns a string
|
||||
std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict);
|
||||
|
||||
}
|
||||
|
||||
#endif /* COMPONENTS_FILES_FILEOPS_HPP */
|
||||
|
|
|
@ -1330,6 +1330,9 @@ void NIFLoader::loadResource(Resource *resource)
|
|||
|
||||
(*iter)->addBoneAssignment(vba);
|
||||
}
|
||||
//Don't link on npc parts to eliminate redundant skeletons
|
||||
//Will have to be changed later slightly for robes/skirts
|
||||
if(triname == "")
|
||||
mesh->_notifySkeleton(mSkel);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 14b2851e72f610ae81dd296598867e6fb0babd2a
|
3
libs/mangle/.gitignore
vendored
Normal file
3
libs/mangle/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
upload_docs.sh
|
||||
docs
|
||||
*~
|
1510
libs/mangle/Doxyfile
Normal file
1510
libs/mangle/Doxyfile
Normal file
File diff suppressed because it is too large
Load diff
26
libs/mangle/LICENSE.txt
Normal file
26
libs/mangle/LICENSE.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
Minimal Abstraction Game Layer (Mangle) is licensed under the
|
||||
'zlib/libpng' license:
|
||||
|
||||
----
|
||||
|
||||
Copyright (c) 2009 Nicolay Korslund
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
129
libs/mangle/README.txt
Normal file
129
libs/mangle/README.txt
Normal file
|
@ -0,0 +1,129 @@
|
|||
Welcome to Mangle v0.1
|
||||
----------------------
|
||||
|
||||
Written by: Nicolay Korslund (korslund@gmail.com)
|
||||
License: zlib/png (see LICENSE.txt)
|
||||
WWW: http://asm-soft.com/mangle/
|
||||
Documentation: http://asm-soft.com/mangle/docs
|
||||
|
||||
|
||||
|
||||
Mangle is the project name for a small set of generic interfaces for
|
||||
various game middleware libraries, such as sound, input, graphics, and
|
||||
so on. You can imagine that it stands for "Minimal Abstraction Game
|
||||
Layer", if you like. It will consist of several more or less
|
||||
independent modules, one for each of these areas. These may be used
|
||||
together to build an entire game engine, or they can be used
|
||||
individually as separate libraries.
|
||||
|
||||
However, Mangle does NOT actually implement a game engine, or any new
|
||||
fundamental functionality. More on that below.
|
||||
|
||||
Currently there's modules for sound and streams / archives (virtual
|
||||
file systems.) More will come in the future (including input, 2D/3D
|
||||
graphics, GUI, physics, and more.)
|
||||
|
||||
|
||||
Main idea
|
||||
---------
|
||||
|
||||
The idea behind Mangle is to provide a uniform, consistent interface
|
||||
to other game libraries. The library does not provide ANY
|
||||
functionality on its own. Instead it connects to a backend
|
||||
implementation of your choice (or of your making.)
|
||||
|
||||
The Sound module, for example, currently has backends for OpenAL
|
||||
(output only), FFmpeg (input only) and for Audiere. Hopefully we'll
|
||||
add IrrKlang, FMod, DirectSound, Miles and more in the future. It can
|
||||
combine libraries to get more complete functionality (like using
|
||||
OpenAL for output and FFmpeg to decode sound files), and it's also
|
||||
easy to write your own backend if you're using a different (or
|
||||
home-brewed) sound system.
|
||||
|
||||
Regardless of what backend you use, the front-end interfaces (found
|
||||
eg. in sound/output.h) is identical, and as a library user you
|
||||
shouldn't notice much difference at all if you swap one backend for
|
||||
another at a later point. It should Just Work.
|
||||
|
||||
The interfaces themselves are also quite simple. Setting up a sound
|
||||
stream from FFmpeg or other decoder into OpenAL can be quite hairy -
|
||||
but with Mangle the hairy parts have already been written for you. You
|
||||
just plug the parts together.
|
||||
|
||||
The goal in the long run is to support a wide variety of game-related
|
||||
libraries, and as many backend libraries (free and commercial) as
|
||||
possible, so that you the user will have to write as little code as
|
||||
possible.
|
||||
|
||||
|
||||
|
||||
What is it good for
|
||||
-------------------
|
||||
|
||||
The main point of Mangle, as we said above, is that it connects to any
|
||||
library of your choice "behind the scenes" but provides the same,
|
||||
super-simple interface front-end for all of them. There can benefit
|
||||
you in many ways:
|
||||
|
||||
- If you want to use a new library that Mangle support. You don't have
|
||||
to scour the net for tutorials and usage examples, since much of the
|
||||
common usage code is already included in the implementation classes.
|
||||
|
||||
- If you don't want to pollute your code with library-specific code.
|
||||
The Mangle interfaces can help you keep your code clean, and its
|
||||
user interface is often simpler than the exteral library one.
|
||||
|
||||
- If you want to quickly connect different libraries together, it
|
||||
really helps if they speak a common language. The Mangle interfaces
|
||||
are exactly that - a common language between libraries. Do you need
|
||||
Audiere to load sounds from a weird archive format only implemented
|
||||
for PhysFS, all channeled through the OGRE resource system? No
|
||||
problem!
|
||||
|
||||
- If you are creating a library that depends on a specific feature
|
||||
(such as sound), but you don't want to lock your users into any
|
||||
specific sound library. Mangle works as an abstraction that lets
|
||||
your users select their own implementation.
|
||||
|
||||
- If you want to support multiple backends for your game/app, or want
|
||||
to make it possible to easily switch backends later. You can select
|
||||
backends at compile time or even at runtime. For example you might
|
||||
want to switch to to a commercial sound library at a later stage in
|
||||
development, or you may want to use a different input library on
|
||||
console platforms than on PC.
|
||||
|
||||
The Mangle implementations are extremely light-weight - often just one
|
||||
or two cpp/h pairs per module. You can plug them directly into your
|
||||
program, there's no separate library building step required.
|
||||
|
||||
Since the library aims to be very modularly put together, you can
|
||||
also, in many cases, just copy-and-paste the parts you need and ignore
|
||||
the rest. Or modify stuff without fearing that the whole 'system' will
|
||||
come crashing down, because there is no big 'system' to speak of.
|
||||
|
||||
|
||||
Past and future
|
||||
---------------
|
||||
|
||||
Mangle started out as (and still is) a spin-off from OpenMW, another
|
||||
project I am personally working on ( http://openmw.com/ ). OpenMW is
|
||||
an attempt to recreate the engine behind the commercial game
|
||||
Morrowind, using only open source software.
|
||||
|
||||
The projects are still tightly interlinked, and they will continue to
|
||||
be until OpenMW is finished. Most near-future work on Mangle will be
|
||||
focused chiefly on OpenMW at the moment. However I will gladly include
|
||||
external contributions and suggestions that are not OpenMW-related if
|
||||
someone sends them to me.
|
||||
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
As you might have guessed, Mangle is more a concept in development
|
||||
than a finished library right now.
|
||||
|
||||
All feedback, ideas, concepts, questions and code are very
|
||||
welcome. Send them to: korslund@gmail.com
|
||||
|
||||
I will put up a forum later as well if there's enough interest.
|
29
libs/mangle/input/clients/ogre_input_capture.hpp
Normal file
29
libs/mangle/input/clients/ogre_input_capture.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#ifndef MANGLE_INPUT_OGREINPUTFRAME_H
|
||||
#define MANGLE_INPUT_OGREINPUTFRAME_H
|
||||
|
||||
/*
|
||||
This Ogre FrameListener calls capture() on an input driver every frame.
|
||||
*/
|
||||
|
||||
#include <OgreFrameListener.h>
|
||||
#include "../driver.hpp"
|
||||
|
||||
namespace Mangle {
|
||||
namespace Input {
|
||||
|
||||
struct OgreInputCapture : Ogre::FrameListener
|
||||
{
|
||||
Mangle::Input::Driver &driver;
|
||||
|
||||
OgreInputCapture(Mangle::Input::Driver &drv)
|
||||
: driver(drv) {}
|
||||
|
||||
bool frameStarted(const Ogre::FrameEvent &evt)
|
||||
{
|
||||
driver.capture();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}}
|
||||
|
||||
#endif
|
69
libs/mangle/input/driver.hpp
Normal file
69
libs/mangle/input/driver.hpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#ifndef MANGLE_INPUT_DRIVER_H
|
||||
#define MANGLE_INPUT_DRIVER_H
|
||||
|
||||
#include "event.hpp"
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Input
|
||||
{
|
||||
/** Input::Driver is the main interface to any input system that
|
||||
handles keyboard and/or mouse input, along with any other
|
||||
input source like joysticks.
|
||||
|
||||
It is really a generalized event system, and could also be
|
||||
used for non-input related events. The definition of the event
|
||||
codes and structures are entirely dependent on the
|
||||
implementation.
|
||||
|
||||
A system-independent key code list will be found in keys.hpp,
|
||||
and input drivers should privide optional translations to/from
|
||||
this list for full compatibility.
|
||||
*/
|
||||
struct Driver
|
||||
{
|
||||
Driver() {}
|
||||
virtual ~Driver() {}
|
||||
|
||||
/** Captures input and produces the relevant events from it. An
|
||||
event callback must be set with setEvent(), or all events
|
||||
will be ignored.
|
||||
*/
|
||||
virtual void capture() = 0;
|
||||
|
||||
/** Check the state of a given key or button. The key/button
|
||||
definitions depends on the driver.
|
||||
*/
|
||||
virtual bool isDown(int index) = 0;
|
||||
|
||||
/** Show or hide system mouse cursor
|
||||
*/
|
||||
virtual void showMouse(bool show) = 0;
|
||||
|
||||
/** Set the event handler for input events. The evt->event()
|
||||
function is called for each event. The meaning of the index
|
||||
and *p parameters will be specific to each driver and to
|
||||
each input system.
|
||||
*/
|
||||
void setEvent(EventPtr evt)
|
||||
{ event = evt; }
|
||||
|
||||
/** Instigate an event. Is used internally for all events, but
|
||||
can also be called from the outside to "fake" events from
|
||||
this driver.
|
||||
*/
|
||||
void makeEvent(Event::Type type, int index, const void *p=NULL)
|
||||
{
|
||||
if(event)
|
||||
event->event(type,index,p);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Holds the event callback set byt setEvent()
|
||||
EventPtr event;
|
||||
};
|
||||
|
||||
typedef boost::shared_ptr<Driver> DriverPtr;
|
||||
}
|
||||
}
|
||||
#endif
|
46
libs/mangle/input/event.hpp
Normal file
46
libs/mangle/input/event.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef MANGLE_INPUT_EVENT_H
|
||||
#define MANGLE_INPUT_EVENT_H
|
||||
|
||||
#include "../tools/shared_ptr.hpp"
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Input
|
||||
{
|
||||
/** Generic callback for input events. The meaning of the
|
||||
parameters depend on the system producing the events.
|
||||
*/
|
||||
struct Event
|
||||
{
|
||||
/// Event types
|
||||
enum Type
|
||||
{
|
||||
EV_Unknown = 1, // Unknown event type
|
||||
EV_KeyDown = 2, // Keyboard button was pressed
|
||||
EV_KeyUp = 4, // Keyboard button was released
|
||||
EV_Keyboard = 6, // All keyboard events
|
||||
|
||||
EV_MouseMove = 8, // Mouse movement
|
||||
EV_MouseDown = 16, // Mouse button pressed
|
||||
EV_MouseUp = 32, // Mouse button released
|
||||
EV_Mouse = 56, // All mouse events
|
||||
|
||||
EV_ALL = 63 // All events
|
||||
};
|
||||
|
||||
/**
|
||||
Called upon all events. The first parameter give the event
|
||||
type, the second gives additional data (usually the local
|
||||
keysym or button index as defined by the driver), and the
|
||||
pointer points to the full custom event structure provided by
|
||||
the driver (the type may vary depending on the EventType,
|
||||
this is defined in the Driver documentation.)
|
||||
*/
|
||||
virtual void event(Type type, int index, const void *p) = 0;
|
||||
virtual ~Event() {}
|
||||
};
|
||||
|
||||
typedef boost::shared_ptr<Event> EventPtr;
|
||||
}
|
||||
}
|
||||
#endif
|
47
libs/mangle/input/filters/eventlist.hpp
Normal file
47
libs/mangle/input/filters/eventlist.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef MANGLE_INPUT_EVENTLIST_H
|
||||
#define MANGLE_INPUT_EVENTLIST_H
|
||||
|
||||
#include "../event.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Input
|
||||
{
|
||||
/** And Event handler that distributes each event to a list of
|
||||
other handlers. Supports filtering events by their Type
|
||||
parameter.
|
||||
*/
|
||||
struct EventList : Event
|
||||
{
|
||||
struct Filter
|
||||
{
|
||||
EventPtr evt;
|
||||
int flags;
|
||||
};
|
||||
std::vector<Filter> list;
|
||||
|
||||
void add(EventPtr e, int flags = EV_ALL)
|
||||
{
|
||||
Filter f;
|
||||
f.evt = e;
|
||||
f.flags = flags;
|
||||
list.push_back(f);
|
||||
}
|
||||
|
||||
virtual void event(Type type, int index, const void *p)
|
||||
{
|
||||
std::vector<Filter>::iterator it;
|
||||
|
||||
for(it=list.begin(); it!=list.end(); it++)
|
||||
{
|
||||
if(type & it->flags)
|
||||
it->evt->event(type,index,p);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef boost::shared_ptr<EventList> EventListPtr;
|
||||
}
|
||||
}
|
||||
#endif
|
148
libs/mangle/input/servers/ois_driver.cpp
Normal file
148
libs/mangle/input/servers/ois_driver.cpp
Normal file
|
@ -0,0 +1,148 @@
|
|||
#include "ois_driver.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <sstream>
|
||||
#include <OgreRenderWindow.h>
|
||||
#include <OIS/OIS.h>
|
||||
|
||||
#ifdef __APPLE_CC__
|
||||
#include <Carbon/Carbon.h>
|
||||
#endif
|
||||
|
||||
using namespace Mangle::Input;
|
||||
using namespace OIS;
|
||||
|
||||
struct Mangle::Input::OISListener : OIS::KeyListener, OIS::MouseListener
|
||||
{
|
||||
OISDriver &drv;
|
||||
|
||||
OISListener(OISDriver &driver)
|
||||
: drv(driver) {}
|
||||
|
||||
bool keyPressed( const OIS::KeyEvent &arg )
|
||||
{
|
||||
drv.makeEvent(Event::EV_KeyDown, arg.key, &arg);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool keyReleased( const OIS::KeyEvent &arg )
|
||||
{
|
||||
drv.makeEvent(Event::EV_KeyUp, arg.key, &arg);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
|
||||
{
|
||||
// Mouse button events are handled as key events
|
||||
// TODO: Translate mouse buttons into pseudo-keysyms
|
||||
drv.makeEvent(Event::EV_MouseDown, id, &arg);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
|
||||
{
|
||||
// TODO: ditto
|
||||
drv.makeEvent(Event::EV_MouseUp, id, &arg);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mouseMoved( const OIS::MouseEvent &arg )
|
||||
{
|
||||
drv.makeEvent(Event::EV_MouseMove, -1, &arg);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
OISDriver::OISDriver(Ogre::RenderWindow *window, bool exclusive)
|
||||
{
|
||||
assert(window);
|
||||
|
||||
size_t windowHnd;
|
||||
|
||||
window->getCustomAttribute("WINDOW", &windowHnd);
|
||||
|
||||
std::ostringstream windowHndStr;
|
||||
ParamList pl;
|
||||
|
||||
windowHndStr << windowHnd;
|
||||
pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
|
||||
|
||||
// Set non-exclusive mouse and keyboard input if the user requested
|
||||
// it.
|
||||
if(!exclusive)
|
||||
{
|
||||
#if defined OIS_WIN32_PLATFORM
|
||||
pl.insert(std::make_pair(std::string("w32_mouse"),
|
||||
std::string("DISCL_FOREGROUND" )));
|
||||
pl.insert(std::make_pair(std::string("w32_mouse"),
|
||||
std::string("DISCL_NONEXCLUSIVE")));
|
||||
pl.insert(std::make_pair(std::string("w32_keyboard"),
|
||||
std::string("DISCL_FOREGROUND")));
|
||||
pl.insert(std::make_pair(std::string("w32_keyboard"),
|
||||
std::string("DISCL_NONEXCLUSIVE")));
|
||||
#elif defined OIS_LINUX_PLATFORM
|
||||
pl.insert(std::make_pair(std::string("x11_mouse_grab"),
|
||||
std::string("false")));
|
||||
pl.insert(std::make_pair(std::string("x11_mouse_hide"),
|
||||
std::string("false")));
|
||||
pl.insert(std::make_pair(std::string("x11_keyboard_grab"),
|
||||
std::string("false")));
|
||||
pl.insert(std::make_pair(std::string("XAutoRepeatOn"),
|
||||
std::string("true")));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __APPLE_CC__
|
||||
// Give the application window focus to receive input events
|
||||
ProcessSerialNumber psn = { 0, kCurrentProcess };
|
||||
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
|
||||
SetFrontProcess(&psn);
|
||||
#endif
|
||||
|
||||
inputMgr = InputManager::createInputSystem( pl );
|
||||
|
||||
// Create all devices
|
||||
keyboard = static_cast<Keyboard*>(inputMgr->createInputObject
|
||||
( OISKeyboard, true ));
|
||||
mouse = static_cast<Mouse*>(inputMgr->createInputObject
|
||||
( OISMouse, true ));
|
||||
|
||||
// Set mouse region
|
||||
const MouseState &ms = mouse->getMouseState();
|
||||
ms.width = window->getWidth();
|
||||
ms.height = window->getHeight();
|
||||
|
||||
// Set up the input listener
|
||||
listener = new OISListener(*this);
|
||||
keyboard-> setEventCallback(listener);
|
||||
mouse-> setEventCallback(listener);
|
||||
}
|
||||
|
||||
OISDriver::~OISDriver()
|
||||
{
|
||||
// Delete the listener object
|
||||
if(listener)
|
||||
delete listener;
|
||||
|
||||
if(inputMgr == NULL) return;
|
||||
|
||||
// Kill the input systems. This will reset input options such as key
|
||||
// repeat rate.
|
||||
inputMgr->destroyInputObject(keyboard);
|
||||
inputMgr->destroyInputObject(mouse);
|
||||
InputManager::destroyInputSystem(inputMgr);
|
||||
inputMgr = NULL;
|
||||
}
|
||||
|
||||
void OISDriver::capture()
|
||||
{
|
||||
// Capture keyboard and mouse events
|
||||
keyboard->capture();
|
||||
mouse->capture();
|
||||
}
|
||||
|
||||
bool OISDriver::isDown(int index)
|
||||
{
|
||||
// TODO: Extend to mouse buttons as well
|
||||
return keyboard->isKeyDown((OIS::KeyCode)index);
|
||||
}
|
48
libs/mangle/input/servers/ois_driver.hpp
Normal file
48
libs/mangle/input/servers/ois_driver.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#ifndef MANGLE_INPUT_OIS_DRIVER_H
|
||||
#define MANGLE_INPUT_OIS_DRIVER_H
|
||||
|
||||
#include "../driver.hpp"
|
||||
|
||||
namespace OIS
|
||||
{
|
||||
class InputManager;
|
||||
class Mouse;
|
||||
class Keyboard;
|
||||
}
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class RenderWindow;
|
||||
}
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Input
|
||||
{
|
||||
struct OISListener;
|
||||
|
||||
/** Input driver for OIS, the input manager typically used with
|
||||
Ogre.
|
||||
*/
|
||||
struct OISDriver : Driver
|
||||
{
|
||||
/// If exclusive=true, then we capture mouse and keyboard from
|
||||
/// the OS.
|
||||
OISDriver(Ogre::RenderWindow *window, bool exclusive=true);
|
||||
~OISDriver();
|
||||
|
||||
void capture();
|
||||
bool isDown(int index);
|
||||
/// Not currently supported.
|
||||
void showMouse(bool) {}
|
||||
|
||||
private:
|
||||
OIS::InputManager *inputMgr;
|
||||
OIS::Mouse *mouse;
|
||||
OIS::Keyboard *keyboard;
|
||||
|
||||
OISListener *listener;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
54
libs/mangle/input/servers/sdl_driver.cpp
Normal file
54
libs/mangle/input/servers/sdl_driver.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include "sdl_driver.hpp"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
using namespace Mangle::Input;
|
||||
|
||||
void SDLDriver::capture()
|
||||
{
|
||||
// Poll for events
|
||||
SDL_Event evt;
|
||||
while(SDL_PollEvent(&evt))
|
||||
{
|
||||
Event::Type type = Event::EV_Unknown;
|
||||
int index = -1;
|
||||
|
||||
switch(evt.type)
|
||||
{
|
||||
// For key events, send the keysym as the index.
|
||||
case SDL_KEYDOWN:
|
||||
type = Event::EV_KeyDown;
|
||||
index = evt.key.keysym.sym;
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
type = Event::EV_KeyUp;
|
||||
index = evt.key.keysym.sym;
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
type = Event::EV_MouseMove;
|
||||
break;
|
||||
// Add more event types later
|
||||
}
|
||||
|
||||
// Pass the event along, using -1 as index for unidentified
|
||||
// event types.
|
||||
makeEvent(type, index, &evt);
|
||||
}
|
||||
}
|
||||
|
||||
bool SDLDriver::isDown(int index)
|
||||
{
|
||||
int num;
|
||||
Uint8 *keys = SDL_GetKeyState(&num);
|
||||
assert(index >= 0 && index < num);
|
||||
|
||||
// The returned array from GetKeyState is indexed by the
|
||||
// SDLK_KEYNAME enums and is just a list of bools. If the indexed
|
||||
// value is true, the button is down.
|
||||
return keys[index];
|
||||
}
|
||||
|
||||
void SDLDriver::showMouse(bool show)
|
||||
{
|
||||
SDL_ShowCursor(show?SDL_ENABLE:SDL_DISABLE);
|
||||
}
|
27
libs/mangle/input/servers/sdl_driver.hpp
Normal file
27
libs/mangle/input/servers/sdl_driver.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef MANGLE_INPUT_SDL_DRIVER_H
|
||||
#define MANGLE_INPUT_SDL_DRIVER_H
|
||||
|
||||
#include "../driver.hpp"
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Input
|
||||
{
|
||||
/** Input driver for SDL. As the input system of SDL is seldomly
|
||||
used alone (most often along with the video system), it is
|
||||
assumed that you do your own initialization and cleanup of SDL
|
||||
before and after using this driver.
|
||||
|
||||
The Event.event() calls will be given the proper EV_ type, the
|
||||
key index (for key up/down events), and a pointer to the full
|
||||
SDL_Event structure.
|
||||
*/
|
||||
struct SDLDriver : Driver
|
||||
{
|
||||
void capture();
|
||||
bool isDown(int index);
|
||||
void showMouse(bool);
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
2
libs/mangle/input/tests/.gitignore
vendored
Normal file
2
libs/mangle/input/tests/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*_test
|
||||
ogre.cfg
|
15
libs/mangle/input/tests/Makefile
Normal file
15
libs/mangle/input/tests/Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
GCC=g++ -Wall
|
||||
|
||||
all: sdl_driver_test ois_driver_test evtlist_test
|
||||
|
||||
sdl_driver_test: sdl_driver_test.cpp
|
||||
$(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL
|
||||
|
||||
ois_driver_test: ois_driver_test.cpp
|
||||
$(GCC) $< ../servers/ois_driver.cpp -o $@ -I/usr/local/include/OGRE/ -lOgreMain -lOIS -lboost_filesystem
|
||||
|
||||
evtlist_test: evtlist_test.cpp ../filters/eventlist.hpp ../event.hpp
|
||||
$(GCC) $< -o $@
|
||||
|
||||
clean:
|
||||
rm *_test
|
35
libs/mangle/input/tests/common.cpp
Normal file
35
libs/mangle/input/tests/common.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include <iostream>
|
||||
#include "../driver.hpp"
|
||||
#include <unistd.h>
|
||||
using namespace std;
|
||||
using namespace Mangle::Input;
|
||||
|
||||
Driver *input;
|
||||
|
||||
struct MyCB : Event
|
||||
{
|
||||
void event(Event::Type type, int i, const void *p)
|
||||
{
|
||||
cout << "got event: type=" << type << " index=" << i << endl;
|
||||
}
|
||||
};
|
||||
|
||||
void mainLoop(int argc, int quitKey)
|
||||
{
|
||||
cout << "Hold the Q key to quit:\n";
|
||||
input->setEvent(EventPtr(new MyCB));
|
||||
while(!input->isDown(quitKey))
|
||||
{
|
||||
input->capture();
|
||||
usleep(20000);
|
||||
|
||||
if(argc == 1)
|
||||
{
|
||||
cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete input;
|
||||
cout << "\nBye bye!\n";
|
||||
}
|
45
libs/mangle/input/tests/evtlist_test.cpp
Normal file
45
libs/mangle/input/tests/evtlist_test.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include <iostream>
|
||||
#include "../filters/eventlist.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace Mangle::Input;
|
||||
|
||||
struct MyEvent : Event
|
||||
{
|
||||
int ii;
|
||||
MyEvent(int i) : ii(i) {}
|
||||
|
||||
void event(Event::Type type, int i, const void *p)
|
||||
{
|
||||
cout << " #" << ii << " got event: type=" << type << " index=" << i << endl;
|
||||
}
|
||||
};
|
||||
|
||||
EventList lst;
|
||||
|
||||
int iii=1;
|
||||
void make(int flags)
|
||||
{
|
||||
lst.add(EventPtr(new MyEvent(iii++)), flags);
|
||||
}
|
||||
|
||||
void send(Event::Type type)
|
||||
{
|
||||
cout << "Sending type " << type << endl;
|
||||
lst.event(type,0,NULL);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
make(Event::EV_ALL);
|
||||
make(Event::EV_KeyDown);
|
||||
make(Event::EV_KeyUp | Event::EV_MouseDown);
|
||||
|
||||
send(Event::EV_Unknown);
|
||||
send(Event::EV_KeyDown);
|
||||
send(Event::EV_KeyUp);
|
||||
send(Event::EV_MouseDown);
|
||||
|
||||
cout << "Enough of that\n";
|
||||
return 0;
|
||||
}
|
51
libs/mangle/input/tests/ois_driver_test.cpp
Normal file
51
libs/mangle/input/tests/ois_driver_test.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "common.cpp"
|
||||
|
||||
#include "../servers/ois_driver.hpp"
|
||||
#include <Ogre.h>
|
||||
#include <OIS/OIS.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
bool isFile(const char *name)
|
||||
{
|
||||
boost::filesystem::path cfg_file_path(name);
|
||||
return boost::filesystem::exists(cfg_file_path);
|
||||
}
|
||||
|
||||
using namespace Ogre;
|
||||
using namespace OIS;
|
||||
|
||||
Root *root;
|
||||
RenderWindow *window;
|
||||
|
||||
void setupOgre()
|
||||
{
|
||||
// Disable logging
|
||||
new LogManager;
|
||||
Log *log = LogManager::getSingleton().createLog("");
|
||||
log->setDebugOutputEnabled(false);
|
||||
|
||||
bool useConfig = isFile("ogre.cfg");
|
||||
|
||||
// Set up Root
|
||||
root = new Root("plugins.cfg", "ogre.cfg", "");
|
||||
|
||||
// Configure
|
||||
if(!useConfig)
|
||||
root->showConfigDialog();
|
||||
else
|
||||
root->restoreConfig();
|
||||
|
||||
// Initialize OGRE window
|
||||
window = root->initialise(true, "test", "");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
setupOgre();
|
||||
input = new OISDriver(window);
|
||||
|
||||
mainLoop(argc, KC_Q);
|
||||
|
||||
delete root;
|
||||
return 0;
|
||||
}
|
12
libs/mangle/input/tests/output/evtlist_test.out
Normal file
12
libs/mangle/input/tests/output/evtlist_test.out
Normal file
|
@ -0,0 +1,12 @@
|
|||
Sending type 1
|
||||
#1 got event: type=1 index=0
|
||||
Sending type 2
|
||||
#1 got event: type=2 index=0
|
||||
#2 got event: type=2 index=0
|
||||
Sending type 4
|
||||
#1 got event: type=4 index=0
|
||||
#3 got event: type=4 index=0
|
||||
Sending type 16
|
||||
#1 got event: type=16 index=0
|
||||
#3 got event: type=16 index=0
|
||||
Enough of that
|
5
libs/mangle/input/tests/output/ois_driver_test.out
Normal file
5
libs/mangle/input/tests/output/ois_driver_test.out
Normal file
|
@ -0,0 +1,5 @@
|
|||
Hold the Q key to quit:
|
||||
got event: type=8 index=-1
|
||||
You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly
|
||||
|
||||
Bye bye!
|
5
libs/mangle/input/tests/output/sdl_driver_test.out
Normal file
5
libs/mangle/input/tests/output/sdl_driver_test.out
Normal file
|
@ -0,0 +1,5 @@
|
|||
Hold the Q key to quit:
|
||||
got event: type=1 index=-1
|
||||
You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly
|
||||
|
||||
Bye bye!
|
12
libs/mangle/input/tests/plugins.cfg
Normal file
12
libs/mangle/input/tests/plugins.cfg
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Defines plugins to load
|
||||
|
||||
# Define plugin folder
|
||||
PluginFolder=/usr/local/lib/OGRE/
|
||||
|
||||
# Define plugins
|
||||
Plugin=RenderSystem_GL
|
||||
Plugin=Plugin_ParticleFX
|
||||
Plugin=Plugin_OctreeSceneManager
|
||||
# Plugin=Plugin_CgProgramManager
|
||||
|
||||
|
16
libs/mangle/input/tests/sdl_driver_test.cpp
Normal file
16
libs/mangle/input/tests/sdl_driver_test.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "common.cpp"
|
||||
|
||||
#include "../servers/sdl_driver.hpp"
|
||||
#include <SDL.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE);
|
||||
input = new SDLDriver();
|
||||
|
||||
mainLoop(argc, SDLK_q);
|
||||
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
18
libs/mangle/input/tests/test.sh
Executable file
18
libs/mangle/input/tests/test.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/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
|
63
libs/mangle/rend2d/driver.hpp
Normal file
63
libs/mangle/rend2d/driver.hpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#ifndef MANGLE_REND2D_DRIVER_H
|
||||
#define MANGLE_REND2D_DRIVER_H
|
||||
|
||||
#include <string>
|
||||
#include "sprite.hpp"
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Rend2D
|
||||
{
|
||||
/**
|
||||
The driver is the connection to the backend system that powers
|
||||
2D sprite rendering. For example the backend could be SDL or
|
||||
any other 2D-capable graphics library.
|
||||
*/
|
||||
struct Driver
|
||||
{
|
||||
/// Get the screen sprite
|
||||
virtual Sprite *getScreen() = 0;
|
||||
|
||||
/// Sets the video mode.
|
||||
virtual void setVideoMode(int width, int height, int bpp=32, bool fullscreen=false) = 0;
|
||||
|
||||
/** Update the screen. Until this function is called, none of
|
||||
the changes written to the screen sprite will be visible.
|
||||
*/
|
||||
virtual void update() = 0;
|
||||
|
||||
/// Set the window title, as well as the title of the window
|
||||
/// when "iconified"
|
||||
virtual void setWindowTitle(const std::string &title,
|
||||
const std::string &icon) = 0;
|
||||
|
||||
/// Set the window title
|
||||
void setWindowTitle(const std::string &title) { setWindowTitle(title,title); }
|
||||
|
||||
/// Load sprite from an image file. Thows an exception on
|
||||
/// failure.
|
||||
virtual Sprite* loadImage(const std::string &file) = 0;
|
||||
|
||||
/// Load a sprite from an image file stored in memory. Throws
|
||||
/// exception on failure.
|
||||
virtual Sprite* loadImage(const void* data, size_t size) = 0;
|
||||
|
||||
/** @brief Set gamma value for all colors.
|
||||
|
||||
Note: Setting this in windowed mode will affect the ENTIRE
|
||||
SCREEN!
|
||||
*/
|
||||
virtual void setGamma(float gamma) = 0;
|
||||
|
||||
/// Set gamma individually for red, green, blue
|
||||
virtual void setGamma(float red, float green, float blue) = 0;
|
||||
|
||||
/// Get screen width
|
||||
virtual int width() = 0;
|
||||
|
||||
/// Get screen height
|
||||
virtual int height() = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
259
libs/mangle/rend2d/servers/sdl_driver.cpp
Normal file
259
libs/mangle/rend2d/servers/sdl_driver.cpp
Normal file
|
@ -0,0 +1,259 @@
|
|||
#include "sdl_driver.hpp"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_image.h>
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
|
||||
using namespace Mangle::Rend2D;
|
||||
|
||||
const SpriteData *SDL_Sprite::lock()
|
||||
{
|
||||
// Make sure we aren't already locked
|
||||
assert(!data.pixels);
|
||||
|
||||
// Lock the surface and set up the data structure
|
||||
SDL_LockSurface(surface);
|
||||
|
||||
data.pixels = surface->pixels;
|
||||
data.w = surface->w;
|
||||
data.h = surface->h;
|
||||
data.pitch = surface->pitch;
|
||||
data.bypp = surface->format->BytesPerPixel;
|
||||
|
||||
return &data;
|
||||
}
|
||||
|
||||
void SDL_Sprite::unlock()
|
||||
{
|
||||
if(data.pixels)
|
||||
{
|
||||
SDL_UnlockSurface(surface);
|
||||
data.pixels = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// This is a really crappy and slow implementation, only intended for
|
||||
// testing purposes. Use lock/unlock for faster pixel drawing.
|
||||
void SDL_Sprite::pixel(int x, int y, int color)
|
||||
{
|
||||
SDL_LockSurface(surface);
|
||||
|
||||
int bpp = surface->format->BytesPerPixel;
|
||||
char *p = (char*)surface->pixels + y*surface->pitch + x*bpp;
|
||||
|
||||
switch(bpp)
|
||||
{
|
||||
case 1: *p = color; break;
|
||||
case 3:
|
||||
if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
|
||||
{
|
||||
p[0] = (color >> 16) & 0xff;
|
||||
p[1] = (color >> 8) & 0xff;
|
||||
p[2] = color & 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
p[0] = color & 0xff;
|
||||
p[1] = (color >> 8) & 0xff;
|
||||
p[2] = (color >> 16) & 0xff;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
*(int*)p = color;
|
||||
break;
|
||||
}
|
||||
SDL_UnlockSurface(surface);
|
||||
}
|
||||
|
||||
void SDL_Sprite::draw(Sprite *s, // Must be SDL_Sprite
|
||||
int x, int y, // Destination position
|
||||
int sx, int sy, // Source position
|
||||
int w, int h // Amount to draw. -1 means remainder.
|
||||
)
|
||||
{
|
||||
// Get source surface
|
||||
SDL_Sprite *other = dynamic_cast<SDL_Sprite*>(s);
|
||||
assert(other != NULL);
|
||||
SDL_Surface *img = other->getSurface();
|
||||
|
||||
// Check coordinate validity
|
||||
assert(sx <= img->w && sy <= img->h);
|
||||
assert(x <= surface->w && y <= surface->h);
|
||||
assert(sx >= 0 && sy >= 0);
|
||||
|
||||
// Compute width and height if necessary
|
||||
if(w == -1) w = img->w - sx;
|
||||
if(h == -1) h = img->h - sy;
|
||||
|
||||
// Check them if they're valid
|
||||
assert(w >= 0 && w <= img->w);
|
||||
assert(h >= 0 && h <= img->h);
|
||||
|
||||
SDL_Rect dest;
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
dest.w = w;
|
||||
dest.h = h;
|
||||
|
||||
SDL_Rect src;
|
||||
src.x = sx;
|
||||
src.y = sy;
|
||||
src.w = w;
|
||||
src.h = h;
|
||||
|
||||
// Do the Blitman
|
||||
SDL_BlitSurface(img, &src, surface, &dest);
|
||||
}
|
||||
|
||||
SDL_Sprite::SDL_Sprite(SDL_Surface *s, bool autoDelete)
|
||||
: surface(s), autoDel(autoDelete)
|
||||
{
|
||||
assert(surface != NULL);
|
||||
data.pixels = NULL;
|
||||
}
|
||||
|
||||
SDL_Sprite::~SDL_Sprite()
|
||||
{
|
||||
if(autoDel)
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
|
||||
void SDL_Sprite::fill(int value)
|
||||
{
|
||||
SDL_FillRect(surface, NULL, value);
|
||||
}
|
||||
|
||||
int SDL_Sprite::width() { return surface->w; }
|
||||
int SDL_Sprite::height() { return surface->h; }
|
||||
|
||||
SDLDriver::SDLDriver() : display(NULL), realDisp(NULL), softDouble(false)
|
||||
{
|
||||
if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1)
|
||||
throw std::runtime_error("Error initializing SDL video");
|
||||
}
|
||||
SDLDriver::~SDLDriver()
|
||||
{
|
||||
if(display) delete display;
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void SDLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen)
|
||||
{
|
||||
unsigned int flags;
|
||||
|
||||
if(display) delete display;
|
||||
|
||||
if (fullscreen)
|
||||
// Assume fullscreen mode allows a double-bufferd hardware
|
||||
// mode. We need more test code for this to be safe though.
|
||||
flags = SDL_FULLSCREEN | SDL_HWSURFACE | SDL_DOUBLEBUF;
|
||||
else
|
||||
flags = SDL_SWSURFACE;
|
||||
|
||||
// Create the surface and check it
|
||||
realDisp = SDL_SetVideoMode(width, height, bpp, flags);
|
||||
if(realDisp == NULL)
|
||||
throw std::runtime_error("Failed setting SDL video mode");
|
||||
|
||||
// Code for software double buffering. I haven't found this to be
|
||||
// any speed advantage at all in windowed mode (it's slower, as one
|
||||
// would expect.) Not properly tested in fullscreen mode with
|
||||
// hardware buffers, but it will probably only be an improvement if
|
||||
// we do excessive writing (ie. write each pixel on average more
|
||||
// than once) or try to read from the display buffer.
|
||||
if(softDouble)
|
||||
{
|
||||
// Make a new surface with the same attributes as the real
|
||||
// display surface.
|
||||
SDL_Surface *back = SDL_DisplayFormat(realDisp);
|
||||
assert(back != NULL);
|
||||
|
||||
// Create a sprite representing the double buffer
|
||||
display = new SDL_Sprite(back);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a sprite directly representing the display surface.
|
||||
// The 'false' parameter means do not autodelete the screen
|
||||
// surface upon exit (since SDL manages it)
|
||||
display = new SDL_Sprite(realDisp, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the screen
|
||||
void SDLDriver::update()
|
||||
{
|
||||
// Blit the soft double buffer onto the real display buffer
|
||||
if(softDouble)
|
||||
SDL_BlitSurface(display->getSurface(), NULL, realDisp, NULL );
|
||||
|
||||
if(realDisp)
|
||||
SDL_Flip(realDisp);
|
||||
}
|
||||
|
||||
/// Set the window title, as well as the title of the window when
|
||||
/// "iconified"
|
||||
void SDLDriver::setWindowTitle(const std::string &title,
|
||||
const std::string &icon)
|
||||
{
|
||||
SDL_WM_SetCaption( title.c_str(), icon.c_str() );
|
||||
}
|
||||
|
||||
// Convert the given surface to display format.
|
||||
static SDL_Surface* convertImage(SDL_Surface* surf)
|
||||
{
|
||||
if(surf != NULL)
|
||||
{
|
||||
// Convert the image to the display buffer format, for faster
|
||||
// blitting
|
||||
SDL_Surface *surf2 = SDL_DisplayFormat(surf);
|
||||
SDL_FreeSurface(surf);
|
||||
surf = surf2;
|
||||
}
|
||||
return surf;
|
||||
}
|
||||
|
||||
/// Load sprite from an image file, using SDL_image.
|
||||
Sprite* SDLDriver::loadImage(const std::string &file)
|
||||
{
|
||||
SDL_Surface *surf = IMG_Load(file.c_str());
|
||||
surf = convertImage(surf);
|
||||
if(surf == NULL)
|
||||
throw std::runtime_error("SDL failed to load image file '" + file + "'");
|
||||
return spriteFromSDL(surf);
|
||||
}
|
||||
|
||||
/// Load sprite from an SDL_RWops structure. autoFree determines
|
||||
/// whether the RWops struct should be closed/freed after use.
|
||||
Sprite* SDLDriver::loadImage(SDL_RWops *src, bool autoFree)
|
||||
{
|
||||
SDL_Surface *surf = IMG_Load_RW(src, autoFree);
|
||||
surf = convertImage(surf);
|
||||
if(surf == NULL)
|
||||
throw std::runtime_error("SDL failed to load image");
|
||||
return spriteFromSDL(surf);
|
||||
}
|
||||
|
||||
/// Load a sprite from an image file stored in memory. Uses
|
||||
/// SDL_image.
|
||||
Sprite* SDLDriver::loadImage(const void* data, size_t size)
|
||||
{
|
||||
SDL_RWops *rw = SDL_RWFromConstMem(data, size);
|
||||
return loadImage(rw, true);
|
||||
}
|
||||
|
||||
void SDLDriver::setGamma(float red, float green, float blue)
|
||||
{
|
||||
SDL_SetGamma(red,green,blue);
|
||||
}
|
||||
|
||||
/// Convert an existing SDL surface into a sprite
|
||||
Sprite* SDLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree)
|
||||
{
|
||||
assert(surf);
|
||||
return new SDL_Sprite(surf, autoFree);
|
||||
}
|
||||
|
||||
void SDLDriver::sleep(int ms) { SDL_Delay(ms); }
|
||||
unsigned int SDLDriver::ticks() { return SDL_GetTicks(); }
|
125
libs/mangle/rend2d/servers/sdl_driver.hpp
Normal file
125
libs/mangle/rend2d/servers/sdl_driver.hpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
#ifndef MANGLE_DRAW2D_SDL_H
|
||||
#define MANGLE_DRAW2D_SDL_H
|
||||
|
||||
#include "../driver.hpp"
|
||||
|
||||
// Predeclarations keep the streets safe at night
|
||||
struct SDL_Surface;
|
||||
struct SDL_RWops;
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Rend2D
|
||||
{
|
||||
/// SDL-implementation of Sprite
|
||||
struct SDL_Sprite : Sprite
|
||||
{
|
||||
/** Draw a sprite in the given position. Can only draw other SDL
|
||||
sprites.
|
||||
*/
|
||||
void draw(Sprite *s, // Must be SDL_Sprite
|
||||
int x, int y, // Destination position
|
||||
int sx=0, int sy=0, // Source position
|
||||
int w=-1, int h=-1 // Amount to draw. -1 means remainder.
|
||||
);
|
||||
|
||||
SDL_Sprite(SDL_Surface *s, bool autoDelete=true);
|
||||
~SDL_Sprite();
|
||||
|
||||
// Information retrieval
|
||||
int width();
|
||||
int height();
|
||||
SDL_Surface *getSurface() { return surface; }
|
||||
|
||||
// Fill with a given pixel value
|
||||
void fill(int value);
|
||||
|
||||
// Set one pixel
|
||||
void pixel(int x, int y, int value);
|
||||
|
||||
const SpriteData *lock();
|
||||
void unlock();
|
||||
|
||||
private:
|
||||
// The SDL surface
|
||||
SDL_Surface* surface;
|
||||
|
||||
// Used for locking
|
||||
SpriteData data;
|
||||
|
||||
// If true, delete this surface when the canvas is destructed
|
||||
bool autoDel;
|
||||
};
|
||||
|
||||
class SDLDriver : public Driver
|
||||
{
|
||||
// The main display surface
|
||||
SDL_Sprite *display;
|
||||
|
||||
// The actual display surface. May or may not be the same
|
||||
// surface pointed to by 'display' above, depending on the
|
||||
// softDouble flag.
|
||||
SDL_Surface *realDisp;
|
||||
|
||||
// If true, we do software double buffering.
|
||||
bool softDouble;
|
||||
|
||||
public:
|
||||
SDLDriver();
|
||||
~SDLDriver();
|
||||
|
||||
/// Sets the video mode. Will create the window if it is not
|
||||
/// already set up. Note that for SDL, bpp=0 means use current
|
||||
/// bpp.
|
||||
void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false);
|
||||
|
||||
/// Update the screen
|
||||
void update();
|
||||
|
||||
/// Set the window title, as well as the title of the window
|
||||
/// when "iconified"
|
||||
void setWindowTitle(const std::string &title,
|
||||
const std::string &icon);
|
||||
|
||||
// Include overloads from our Glorious parent
|
||||
using Driver::setWindowTitle;
|
||||
|
||||
/// Load sprite from an image file, using SDL_image.
|
||||
Sprite* loadImage(const std::string &file);
|
||||
|
||||
/// Load sprite from an SDL_RWops structure. autoFree determines
|
||||
/// whether the RWops struct should be closed/freed after use.
|
||||
Sprite* loadImage(SDL_RWops *src, bool autoFree=false);
|
||||
|
||||
/// Load a sprite from an image file stored in memory. Uses
|
||||
/// SDL_image.
|
||||
Sprite* loadImage(const void* data, size_t size);
|
||||
|
||||
/// Set gamma value
|
||||
void setGamma(float gamma) { setGamma(gamma,gamma,gamma); }
|
||||
|
||||
/// Set gamma individually for red, green, blue
|
||||
void setGamma(float red, float green, float blue);
|
||||
|
||||
/// Convert an existing SDL surface into a sprite
|
||||
Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true);
|
||||
|
||||
// Get width and height
|
||||
int width() { return display ? display->width() : 0; }
|
||||
int height() { return display ? display->height() : 0; }
|
||||
|
||||
/// Get the screen sprite
|
||||
Sprite *getScreen() { return display; }
|
||||
|
||||
/// Not really a graphic-related function, but very
|
||||
/// handly. Sleeps the given number of milliseconds using
|
||||
/// SDL_Delay().
|
||||
void sleep(int ms);
|
||||
|
||||
/// Get the number of ticks since SDL initialization, using
|
||||
/// SDL_GetTicks().
|
||||
unsigned int ticks();
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
311
libs/mangle/rend2d/servers/sdl_gl_driver.cpp
Normal file
311
libs/mangle/rend2d/servers/sdl_gl_driver.cpp
Normal file
|
@ -0,0 +1,311 @@
|
|||
#include "sdl_gl_driver.hpp"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_image.h>
|
||||
#include <SDL_opengl.h>
|
||||
#include <stdexcept>
|
||||
#include <assert.h>
|
||||
|
||||
using namespace Mangle::Rend2D;
|
||||
|
||||
void SDLGL_Sprite::draw(Sprite *s, // Must be SDLGL_Sprite
|
||||
int x, int y, // Destination position
|
||||
int sx, int sy, // Source position
|
||||
int w, int h // Amount to draw. -1 means remainder.
|
||||
)
|
||||
{
|
||||
// Get source surface
|
||||
SDLGL_Sprite *other = dynamic_cast<SDLGL_Sprite*>(s);
|
||||
assert(other != NULL);
|
||||
SDL_Surface *img = other->getSurface();
|
||||
|
||||
// Check coordinate validity
|
||||
assert(sx <= img->w && sy <= img->h);
|
||||
assert(x <= surface->w && y <= surface->h);
|
||||
assert(sx >= 0 && sy >= 0);
|
||||
|
||||
// Compute width and height if necessary
|
||||
if(w == -1) w = img->w - sx;
|
||||
if(h == -1) h = img->h - sy;
|
||||
|
||||
// Check them if they're valid
|
||||
assert(w >= 0 && w <= img->w);
|
||||
assert(h >= 0 && h <= img->h);
|
||||
|
||||
SDL_Rect dest;
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
dest.w = w;
|
||||
dest.h = h;
|
||||
|
||||
SDL_Rect src;
|
||||
src.x = sx;
|
||||
src.y = sy;
|
||||
src.w = w;
|
||||
src.h = h;
|
||||
|
||||
// Do the Blitman
|
||||
SDL_BlitSurface(img, &src, surface, &dest);
|
||||
}
|
||||
|
||||
SDLGL_Sprite::SDLGL_Sprite(SDL_Surface *s, bool autoDelete)
|
||||
: surface(s), autoDel(autoDelete)
|
||||
{
|
||||
assert(surface != NULL);
|
||||
}
|
||||
|
||||
SDLGL_Sprite::~SDLGL_Sprite()
|
||||
{
|
||||
if(autoDel)
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
|
||||
void SDLGL_Sprite::fill(int value)
|
||||
{
|
||||
SDL_FillRect(surface, NULL, value);
|
||||
}
|
||||
|
||||
int SDLGL_Sprite::width() { return surface->w; }
|
||||
int SDLGL_Sprite::height() { return surface->h; }
|
||||
|
||||
SDLGLDriver::SDLGLDriver() : display(NULL), realDisp(NULL)
|
||||
{
|
||||
if (SDL_InitSubSystem( SDL_INIT_VIDEO ) == -1)
|
||||
throw std::runtime_error("Error initializing SDL video");
|
||||
}
|
||||
SDLGLDriver::~SDLGLDriver()
|
||||
{
|
||||
if(display) delete display;
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
// Surface used for the screen. Since OpenGL surfaces must have sizes
|
||||
// that are powers of 2, we have to "fake" the returned display size
|
||||
// to match the screen, not the surface itself. If we don't use this,
|
||||
// the client program will get confused about the actual size of our
|
||||
// screen, thinking it is bigger than it is.
|
||||
struct FakeSizeSprite : SDLGL_Sprite
|
||||
{
|
||||
int fakeW, fakeH;
|
||||
|
||||
FakeSizeSprite(SDL_Surface *s, int fw, int fh)
|
||||
: SDLGL_Sprite(s), fakeW(fw), fakeH(fh)
|
||||
{}
|
||||
|
||||
int width() { return fakeW; }
|
||||
int height() { return fakeH; }
|
||||
};
|
||||
|
||||
static int makePow2(int num)
|
||||
{
|
||||
assert(num);
|
||||
if((num & (num-1)) != 0)
|
||||
{
|
||||
int cnt = 0;
|
||||
while(num)
|
||||
{
|
||||
num >>= 1;
|
||||
cnt++;
|
||||
}
|
||||
num = 1 << cnt;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
void SDLGLDriver::setVideoMode(int width, int height, int bpp, bool fullscreen)
|
||||
{
|
||||
unsigned int flags;
|
||||
|
||||
if(display) delete display;
|
||||
|
||||
flags = SDL_OPENGL;
|
||||
|
||||
if (fullscreen)
|
||||
flags |= SDL_FULLSCREEN;
|
||||
|
||||
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
|
||||
SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 1 );
|
||||
|
||||
// Create the surface and check it
|
||||
screen = SDL_SetVideoMode(width, height, bpp, flags);
|
||||
if(screen == NULL)
|
||||
throw std::runtime_error("Failed setting SDL video mode");
|
||||
|
||||
// Expand width and height to be powers of 2
|
||||
int width2 = makePow2(width);
|
||||
int height2 = makePow2(height);
|
||||
|
||||
// Create a new SDL surface of this size
|
||||
const SDL_PixelFormat& fmt = *(screen->format);
|
||||
realDisp = SDL_CreateRGBSurface(SDL_SWSURFACE,width2,height2,
|
||||
fmt.BitsPerPixel,
|
||||
fmt.Rmask,fmt.Gmask,fmt.Bmask,fmt.Amask);
|
||||
|
||||
// Create a sprite directly representing the display surface. This
|
||||
// allows the user to blit to it directly.
|
||||
display = new FakeSizeSprite(realDisp, width, height);
|
||||
|
||||
// Set up the OpenGL format
|
||||
nOfColors = fmt.BytesPerPixel;
|
||||
|
||||
if(nOfColors == 4)
|
||||
{
|
||||
if (fmt.Rmask == 0x000000ff)
|
||||
texture_format = GL_RGBA;
|
||||
else
|
||||
texture_format = GL_BGRA;
|
||||
}
|
||||
else if(nOfColors == 3)
|
||||
{
|
||||
if (fmt.Rmask == 0x000000ff)
|
||||
texture_format = GL_RGB;
|
||||
else
|
||||
texture_format = GL_BGR;
|
||||
}
|
||||
else
|
||||
assert(0 && "unsupported screen format");
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
// Have OpenGL generate a texture object handle for us
|
||||
glGenTextures( 1, &texture );
|
||||
|
||||
// Bind the texture object
|
||||
glBindTexture( GL_TEXTURE_2D, texture );
|
||||
|
||||
// Set the texture's stretching properties
|
||||
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
||||
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
||||
}
|
||||
|
||||
void SDLGLDriver::updateNoSwap()
|
||||
{
|
||||
if(!realDisp) return;
|
||||
|
||||
// Fist, set up the screen texture:
|
||||
|
||||
// Bind the texture object
|
||||
glBindTexture( GL_TEXTURE_2D, texture );
|
||||
|
||||
// Edit the texture object's image data
|
||||
glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, realDisp->w, realDisp->h, 0,
|
||||
texture_format, GL_UNSIGNED_BYTE, realDisp->pixels );
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
|
||||
glLoadIdentity();
|
||||
|
||||
// OpenGL barf. Set up the projection to match our screen
|
||||
int vPort[4];
|
||||
glGetIntegerv(GL_VIEWPORT, vPort);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
glOrtho(0, vPort[2], 0, vPort[3], -1, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
glBegin( GL_QUADS );
|
||||
|
||||
// Needed to move the screen into the right place
|
||||
int diff = screen->h - realDisp->h;
|
||||
|
||||
// Bottom-left vertex (corner)
|
||||
glTexCoord2i( 0, 1 );
|
||||
glVertex3f(0,diff,0);
|
||||
|
||||
// Bottom-right vertex (corner)
|
||||
glTexCoord2i( 1, 1 );
|
||||
glVertex3f( realDisp->w, diff, 0.f );
|
||||
|
||||
// Top-right vertex (corner)
|
||||
glTexCoord2i( 1, 0 );
|
||||
glVertex3f( realDisp->w, screen->h, 0.f );
|
||||
|
||||
// Top-left vertex (corner)
|
||||
glTexCoord2i( 0, 0 );
|
||||
glVertex3f( 0, screen->h, 0.f );
|
||||
glEnd();
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPopMatrix();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
void SDLGLDriver::swap()
|
||||
{
|
||||
SDL_GL_SwapBuffers();
|
||||
}
|
||||
|
||||
void SDLGLDriver::update()
|
||||
{
|
||||
updateNoSwap();
|
||||
swap();
|
||||
}
|
||||
|
||||
/// Set the window title, as well as the title of the window when
|
||||
/// "iconified"
|
||||
void SDLGLDriver::setWindowTitle(const std::string &title,
|
||||
const std::string &icon)
|
||||
{
|
||||
SDL_WM_SetCaption( title.c_str(), icon.c_str() );
|
||||
}
|
||||
|
||||
// Convert the given surface to display format.
|
||||
static SDL_Surface* convertImage(SDL_Surface* surf)
|
||||
{
|
||||
if(surf != NULL)
|
||||
{
|
||||
// Convert the image to the display buffer format, for faster
|
||||
// blitting
|
||||
SDL_Surface *surf2 = SDL_DisplayFormat(surf);
|
||||
SDL_FreeSurface(surf);
|
||||
surf = surf2;
|
||||
}
|
||||
return surf;
|
||||
}
|
||||
|
||||
/// Load sprite from an image file, using SDL_image.
|
||||
Sprite* SDLGLDriver::loadImage(const std::string &file)
|
||||
{
|
||||
SDL_Surface *surf = IMG_Load(file.c_str());
|
||||
surf = convertImage(surf);
|
||||
if(surf == NULL)
|
||||
throw std::runtime_error("SDL failed to load image file '" + file + "'");
|
||||
return spriteFromSDL(surf);
|
||||
}
|
||||
|
||||
/// Load sprite from an SDL_RWops structure. autoFree determines
|
||||
/// whether the RWops struct should be closed/freed after use.
|
||||
Sprite* SDLGLDriver::loadImage(SDL_RWops *src, bool autoFree)
|
||||
{
|
||||
SDL_Surface *surf = IMG_Load_RW(src, autoFree);
|
||||
surf = convertImage(surf);
|
||||
if(surf == NULL)
|
||||
throw std::runtime_error("SDL failed to load image");
|
||||
return spriteFromSDL(surf);
|
||||
}
|
||||
|
||||
/// Load a sprite from an image file stored in memory. Uses
|
||||
/// SDL_image.
|
||||
Sprite* SDLGLDriver::loadImage(const void* data, size_t size)
|
||||
{
|
||||
SDL_RWops *rw = SDL_RWFromConstMem(data, size);
|
||||
return loadImage(rw, true);
|
||||
}
|
||||
|
||||
void SDLGLDriver::setGamma(float red, float green, float blue)
|
||||
{
|
||||
SDL_SetGamma(red,green,blue);
|
||||
}
|
||||
|
||||
/// Convert an existing SDL surface into a sprite
|
||||
Sprite* SDLGLDriver::spriteFromSDL(SDL_Surface *surf, bool autoFree)
|
||||
{
|
||||
assert(surf);
|
||||
return new SDLGL_Sprite(surf, autoFree);
|
||||
}
|
||||
|
||||
void SDLGLDriver::sleep(int ms) { SDL_Delay(ms); }
|
||||
unsigned int SDLGLDriver::ticks() { return SDL_GetTicks(); }
|
132
libs/mangle/rend2d/servers/sdl_gl_driver.hpp
Normal file
132
libs/mangle/rend2d/servers/sdl_gl_driver.hpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
#ifndef MANGLE_DRAW2D_SDLGL_H
|
||||
#define MANGLE_DRAW2D_SDLGL_H
|
||||
|
||||
/** This driver is similar to SDLDriver, except that it uses SDL on
|
||||
top of OpenGL.
|
||||
|
||||
I've decided to make it a separate file instead of just adding
|
||||
optional OpenGL support to the original, so that pure SDL users
|
||||
don't have to add OpenGL as a dependency.
|
||||
*/
|
||||
|
||||
#include "../driver.hpp"
|
||||
|
||||
// Predeclarations keep the streets safe at night
|
||||
struct SDL_Surface;
|
||||
struct SDL_RWops;
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Rend2D
|
||||
{
|
||||
/// SDL-implementation of Sprite
|
||||
struct SDLGL_Sprite : Sprite
|
||||
{
|
||||
/** Draw a sprite in the given position. Can only draw other SDL
|
||||
sprites.
|
||||
*/
|
||||
void draw(Sprite *s, // Must be SDLGL_Sprite
|
||||
int x, int y, // Destination position
|
||||
int sx=0, int sy=0, // Source position
|
||||
int w=-1, int h=-1 // Amount to draw. -1 means remainder.
|
||||
);
|
||||
|
||||
SDLGL_Sprite(SDL_Surface *s, bool autoDelete=true);
|
||||
~SDLGL_Sprite();
|
||||
|
||||
// Information retrieval
|
||||
virtual int width();
|
||||
virtual int height();
|
||||
SDL_Surface *getSurface() { return surface; }
|
||||
|
||||
// Fill with a given pixel value
|
||||
void fill(int value);
|
||||
|
||||
private:
|
||||
// The SDL surface
|
||||
SDL_Surface* surface;
|
||||
|
||||
// If true, delete this surface when the canvas is destructed
|
||||
bool autoDel;
|
||||
};
|
||||
|
||||
class SDLGLDriver : public Driver
|
||||
{
|
||||
// The main display surface
|
||||
SDLGL_Sprite *display;
|
||||
|
||||
// The screen surface. This is completely unused.
|
||||
SDL_Surface *screen;
|
||||
|
||||
// The display surface and main GL texture. These are used when
|
||||
// drawing the entire screen as one surface, as a drop-in
|
||||
// replacement for SDLDriver.
|
||||
SDL_Surface *realDisp;
|
||||
unsigned int texture;
|
||||
int nOfColors, texture_format;
|
||||
|
||||
public:
|
||||
SDLGLDriver();
|
||||
~SDLGLDriver();
|
||||
|
||||
/// Sets the video mode. Will create the window if it is not
|
||||
/// already set up. Note that for SDL, bpp=0 means use current
|
||||
/// bpp.
|
||||
void setVideoMode(int width, int height, int bpp=0, bool fullscreen=false);
|
||||
|
||||
/// Update the screen
|
||||
void update();
|
||||
|
||||
/// Calls SDL_GL_SwapBuffers
|
||||
void swap();
|
||||
|
||||
/// Draw surface to screen but do not call SDL_GL_SwapBuffers()
|
||||
void updateNoSwap();
|
||||
|
||||
/// Set the window title, as well as the title of the window
|
||||
/// when "iconified"
|
||||
void setWindowTitle(const std::string &title,
|
||||
const std::string &icon);
|
||||
|
||||
// Include overloads from our Glorious parent
|
||||
using Driver::setWindowTitle;
|
||||
|
||||
/// Load sprite from an image file, using SDL_image.
|
||||
Sprite* loadImage(const std::string &file);
|
||||
|
||||
/// Load sprite from an SDL_RWops structure. autoFree determines
|
||||
/// whether the RWops struct should be closed/freed after use.
|
||||
Sprite* loadImage(SDL_RWops *src, bool autoFree=false);
|
||||
|
||||
/// Load a sprite from an image file stored in memory. Uses
|
||||
/// SDL_image.
|
||||
Sprite* loadImage(const void* data, size_t size);
|
||||
|
||||
/// Set gamma value
|
||||
void setGamma(float gamma) { setGamma(gamma,gamma,gamma); }
|
||||
|
||||
/// Set gamma individually for red, green, blue
|
||||
void setGamma(float red, float green, float blue);
|
||||
|
||||
/// Convert an existing SDL surface into a sprite
|
||||
Sprite* spriteFromSDL(SDL_Surface *surf, bool autoFree = true);
|
||||
|
||||
// Get width and height
|
||||
int width() { return display ? display->width() : 0; }
|
||||
int height() { return display ? display->height() : 0; }
|
||||
|
||||
/// Get the screen sprite
|
||||
Sprite *getScreen() { return display; }
|
||||
|
||||
/// Not really a graphic-related function, but very
|
||||
/// handly. Sleeps the given number of milliseconds using
|
||||
/// SDL_Delay().
|
||||
void sleep(int ms);
|
||||
|
||||
/// Get the number of ticks since SDL initialization, using
|
||||
/// SDL_GetTicks().
|
||||
unsigned int ticks();
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
57
libs/mangle/rend2d/sprite.hpp
Normal file
57
libs/mangle/rend2d/sprite.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef MANGLE_REND2D_SPRITE_H
|
||||
#define MANGLE_REND2D_SPRITE_H
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Rend2D
|
||||
{
|
||||
/**
|
||||
A pointer to sprite data for direct drawing. Only to be used
|
||||
while the corresponding sprite is locked.
|
||||
*/
|
||||
struct SpriteData
|
||||
{
|
||||
void *pixels; // Pixel data
|
||||
int w, h; // Width and height
|
||||
int pitch, bypp; // Pitch (bytes) and bytes per pixel
|
||||
};
|
||||
|
||||
/**
|
||||
A Sprite is either a bitmap to be drawn or an output of area
|
||||
for blitting other bitmaps, or both. They are created by the
|
||||
Driver.
|
||||
*/
|
||||
struct Sprite
|
||||
{
|
||||
/// Draw a sprite in the given position
|
||||
virtual void draw(Sprite *s, // The sprite to draw
|
||||
int x, int y, // Destination position
|
||||
int sx=0, int sy=0, // Source position
|
||||
int w=-1, int h=-1 // Amount to draw. -1 means remainder.
|
||||
) = 0;
|
||||
|
||||
virtual ~Sprite() {}
|
||||
|
||||
// Information retrieval
|
||||
virtual int width() = 0;
|
||||
virtual int height() = 0;
|
||||
|
||||
/// Fill the sprite with the given pixel value. The pixel format
|
||||
/// depends on the format of the sprite.
|
||||
virtual void fill(int value) = 0;
|
||||
|
||||
/// Set one pixel value. The pixel format depends on the sprite
|
||||
/// format. This is not expected to be fast, and in some
|
||||
/// implementations may not work at all.
|
||||
virtual void pixel(int x, int y, int value) {}
|
||||
|
||||
/// Lock sprite for direct drawing, and return a struct
|
||||
/// containing the necessary pointer. When finished, unlock the
|
||||
/// sprite with unlock(). May return NULL, if so then direct
|
||||
/// drawing is not possible.
|
||||
virtual const SpriteData *lock() { return NULL; }
|
||||
virtual void unlock() {}
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
1
libs/mangle/rend2d/tests/.gitignore
vendored
Normal file
1
libs/mangle/rend2d/tests/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*_test
|
15
libs/mangle/rend2d/tests/Makefile
Normal file
15
libs/mangle/rend2d/tests/Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
GCC=g++ -Wall -Werror
|
||||
|
||||
all: sdl_test sdl_move_test sdlgl_move_test
|
||||
|
||||
sdl_test: sdl_test.cpp
|
||||
$(GCC) $< ../servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image
|
||||
|
||||
sdl_move_test: sdl_move_test.cpp ../servers/sdl_driver.cpp
|
||||
$(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image
|
||||
|
||||
sdlgl_move_test: sdlgl_move_test.cpp ../servers/sdl_gl_driver.cpp
|
||||
$(GCC) $^ -o $@ -I/usr/include/SDL/ -lSDL -lSDL_image -lGL
|
||||
|
||||
clean:
|
||||
rm *_test
|
0
libs/mangle/rend2d/tests/output/sdl_move_test.out
Normal file
0
libs/mangle/rend2d/tests/output/sdl_move_test.out
Normal file
11
libs/mangle/rend2d/tests/output/sdl_test.out
Normal file
11
libs/mangle/rend2d/tests/output/sdl_test.out
Normal file
|
@ -0,0 +1,11 @@
|
|||
Loading SDL driver.
|
||||
Creating window.
|
||||
Current mode: 640x480
|
||||
Setting fancy title, cause we like fancy titles.
|
||||
Loading tile1-blue.png from file.
|
||||
Loading tile1-yellow.png from memory.
|
||||
Going bananas.
|
||||
Taking a breather.
|
||||
WOW DID YOU SEE THAT!?
|
||||
Mucking about with the gamma settings
|
||||
Done.
|
0
libs/mangle/rend2d/tests/output/sdlgl_move_test.out
Normal file
0
libs/mangle/rend2d/tests/output/sdlgl_move_test.out
Normal file
30
libs/mangle/rend2d/tests/sdl_move_test.cpp
Normal file
30
libs/mangle/rend2d/tests/sdl_move_test.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#include "../servers/sdl_driver.hpp"
|
||||
|
||||
using namespace Mangle::Rend2D;
|
||||
|
||||
int main()
|
||||
{
|
||||
SDLDriver sdl;
|
||||
|
||||
sdl.setVideoMode(640,480,0,false);
|
||||
sdl.setWindowTitle("Testing 123");
|
||||
Sprite *screen = sdl.getScreen();
|
||||
const char* imgName = "tile1-blue.png";
|
||||
Sprite *image = sdl.loadImage(imgName);
|
||||
|
||||
for(int frames=0; frames<170; frames++)
|
||||
{
|
||||
screen->fill(0);
|
||||
for(int j=0; j<10; j++)
|
||||
for(int i=0; i<25; i++)
|
||||
screen->draw(image, 2*frames+30*j, 20*i);
|
||||
sdl.update();
|
||||
sdl.sleep(10);
|
||||
}
|
||||
return 0;
|
||||
}
|
65
libs/mangle/rend2d/tests/sdl_test.cpp
Normal file
65
libs/mangle/rend2d/tests/sdl_test.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#include "../servers/sdl_driver.hpp"
|
||||
|
||||
using namespace Mangle::Rend2D;
|
||||
|
||||
int main()
|
||||
{
|
||||
cout << "Loading SDL driver.\n";
|
||||
SDLDriver sdl;
|
||||
|
||||
cout << "Creating window.\n";
|
||||
sdl.setVideoMode(640,480);
|
||||
cout << "Current mode: " << sdl.width() << "x" << sdl.height() << endl;
|
||||
|
||||
cout << "Setting fancy title, cause we like fancy titles.\n";
|
||||
sdl.setWindowTitle("Chief executive window");
|
||||
|
||||
// Display surface
|
||||
Sprite *screen = sdl.getScreen();
|
||||
|
||||
const char* imgName = "tile1-blue.png";
|
||||
cout << "Loading " << imgName << " from file.\n";
|
||||
Sprite *image = sdl.loadImage(imgName);
|
||||
|
||||
const char* imgName2 = "tile1-yellow.png";
|
||||
cout << "Loading " << imgName2 << " from memory.\n";
|
||||
Sprite *image2;
|
||||
{
|
||||
// This is hard-coded for file sizes below 500 bytes, so obviously
|
||||
// you shouldn't mess with the image files.
|
||||
ifstream file(imgName2, ios::binary);
|
||||
char buf[500];
|
||||
file.read(buf, 500);
|
||||
int size = file.gcount();
|
||||
image2 = sdl.loadImage(buf, size);
|
||||
}
|
||||
|
||||
cout << "Going bananas.\n";
|
||||
for(int i=1; i<20; i++)
|
||||
screen->draw(image, 30*i, 20*i);
|
||||
|
||||
cout << "Taking a breather.\n";
|
||||
sdl.update();
|
||||
for(int i=1; i<20; i++)
|
||||
screen->draw(image2, 30*(20-i), 20*i);
|
||||
sdl.sleep(800);
|
||||
sdl.update();
|
||||
cout << "WOW DID YOU SEE THAT!?\n";
|
||||
sdl.sleep(800);
|
||||
|
||||
cout << "Mucking about with the gamma settings\n";
|
||||
sdl.setGamma(2.0, 0.1, 0.8);
|
||||
sdl.sleep(100);
|
||||
sdl.setGamma(0.6, 2.1, 2.1);
|
||||
sdl.sleep(100);
|
||||
sdl.setGamma(1.6);
|
||||
sdl.sleep(100);
|
||||
|
||||
cout << "Done.\n";
|
||||
return 0;
|
||||
}
|
31
libs/mangle/rend2d/tests/sdlgl_move_test.cpp
Normal file
31
libs/mangle/rend2d/tests/sdlgl_move_test.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#include "../servers/sdl_gl_driver.hpp"
|
||||
|
||||
using namespace Mangle::Rend2D;
|
||||
|
||||
int main()
|
||||
{
|
||||
SDLGLDriver sdl;
|
||||
|
||||
sdl.setVideoMode(640,480,0,false);
|
||||
sdl.setWindowTitle("Testing 123");
|
||||
Sprite *screen = sdl.getScreen();
|
||||
const char* imgName = "tile1-blue.png";
|
||||
Sprite *image = sdl.loadImage(imgName);
|
||||
|
||||
for(int frames=0; frames<170; frames++)
|
||||
{
|
||||
screen->fill(0);
|
||||
for(int j=0; j<10; j++)
|
||||
for(int i=0; i<25; i++)
|
||||
screen->draw(image, 2*frames+30*j, 20*i);
|
||||
sdl.update();
|
||||
sdl.sleep(5);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
18
libs/mangle/rend2d/tests/test.sh
Executable file
18
libs/mangle/rend2d/tests/test.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/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
|
BIN
libs/mangle/rend2d/tests/tile1-blue.png
Normal file
BIN
libs/mangle/rend2d/tests/tile1-blue.png
Normal file
Binary file not shown.
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 273 B |
BIN
libs/mangle/rend2d/tests/tile1-yellow.png
Normal file
BIN
libs/mangle/rend2d/tests/tile1-yellow.png
Normal file
Binary file not shown.
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
1
libs/mangle/sound/.gitignore
vendored
Normal file
1
libs/mangle/sound/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
|
79
libs/mangle/sound/clients/ogre_listener_mover.hpp
Normal file
79
libs/mangle/sound/clients/ogre_listener_mover.hpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#ifndef MANGLE_SOUND_OGRELISTENERMOVER_H
|
||||
#define MANGLE_SOUND_OGRELISTENERMOVER_H
|
||||
|
||||
#include <OgreCamera.h>
|
||||
#include <assert.h>
|
||||
#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->getPosition();
|
||||
nDir = camera->getDirection();
|
||||
nUp = camera->getUp();
|
||||
|
||||
// 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
|
31
libs/mangle/sound/clients/ogre_output_updater.hpp
Normal file
31
libs/mangle/sound/clients/ogre_output_updater.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#ifndef MANGLE_SOUND_OGREUPDATER_H
|
||||
#define MANGLE_SOUND_OGREUPDATER_H
|
||||
|
||||
/*
|
||||
This Ogre FrameListener calls update on a SoundFactory
|
||||
*/
|
||||
|
||||
#include <OgreFrameListener.h>
|
||||
#include "../output.hpp"
|
||||
#include <assert.h>
|
||||
|
||||
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
|
68
libs/mangle/sound/filters/input_filter.hpp
Normal file
68
libs/mangle/sound/filters/input_filter.hpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#ifndef MANGLE_INPUT_FILTER_H
|
||||
#define MANGLE_INPUT_FILTER_H
|
||||
|
||||
#include "../output.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
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
|
24
libs/mangle/sound/filters/openal_audiere.hpp
Normal file
24
libs/mangle/sound/filters/openal_audiere.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#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
|
23
libs/mangle/sound/filters/openal_ffmpeg.hpp
Normal file
23
libs/mangle/sound/filters/openal_ffmpeg.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#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
|
24
libs/mangle/sound/filters/openal_mpg123.hpp
Normal file
24
libs/mangle/sound/filters/openal_mpg123.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#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
|
24
libs/mangle/sound/filters/openal_sndfile.hpp
Normal file
24
libs/mangle/sound/filters/openal_sndfile.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#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
|
33
libs/mangle/sound/filters/openal_sndfile_mpg123.hpp
Normal file
33
libs/mangle/sound/filters/openal_sndfile_mpg123.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#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
|
39
libs/mangle/sound/filters/openal_various.hpp
Normal file
39
libs/mangle/sound/filters/openal_various.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#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
|
72
libs/mangle/sound/filters/pure_filter.hpp
Normal file
72
libs/mangle/sound/filters/pure_filter.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#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); }
|
||||
|
||||
// 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
|
90
libs/mangle/sound/filters/source_splicer.hpp
Normal file
90
libs/mangle/sound/filters/source_splicer.hpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#ifndef MANGLE_SOUND_SOURCE_SPLICE_H
|
||||
#define MANGLE_SOUND_SOURCE_SPLICE_H
|
||||
|
||||
#include "../source.hpp"
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <assert.h>
|
||||
|
||||
namespace Mangle
|
||||
{
|
||||
namespace Sound
|
||||
{
|
||||
class SourceSplicer : public SampleSourceLoader
|
||||
{
|
||||
struct SourceType
|
||||
{
|
||||
std::string type;
|
||||
SampleSourceLoaderPtr loader;
|
||||
};
|
||||
|
||||
typedef std::list<SourceType> 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; i<t.size(); i++)
|
||||
if(!isMatch(t[i], file[i+diff]))
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Got something! We're done.
|
||||
if(match)
|
||||
return it->loader->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
|
180
libs/mangle/sound/output.hpp
Normal file
180
libs/mangle/sound/output.hpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
#ifndef MANGLE_SOUND_OUTPUT_H
|
||||
#define MANGLE_SOUND_OUTPUT_H
|
||||
|
||||
#include <string>
|
||||
#include <assert.h>
|
||||
|
||||
#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<Sound> SoundPtr;
|
||||
typedef boost::weak_ptr <Sound> 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;
|
||||
|
||||
/// 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<SoundFactory> SoundFactoryPtr;
|
||||
|
||||
}} // Namespaces
|
||||
|
||||
#endif
|
492
libs/mangle/sound/outputs/openal_out.cpp
Normal file
492
libs/mangle/sound/outputs/openal_out.cpp
Normal file
|
@ -0,0 +1,492 @@
|
|||
#include "openal_out.hpp"
|
||||
#include <assert.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../../stream/filters/buffer_stream.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <al.h>
|
||||
#include <alc.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <OpenAL/alc.h>
|
||||
#include <OpenAL/al.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#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; i<count; i++)
|
||||
{
|
||||
ALuint buf;
|
||||
// Unqueue one of the processed buffer
|
||||
alSourceUnqueueBuffers(inst, 1, &buf);
|
||||
|
||||
// Then reload it with data (if any) and queue it up
|
||||
queueBuffer(buf);
|
||||
}
|
||||
}
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
void pause();
|
||||
bool isPlaying() const;
|
||||
void setVolume(float);
|
||||
void setPos(float x, float y, float z);
|
||||
void setPitch(float);
|
||||
void setRepeat(bool);
|
||||
|
||||
void notifyOwnerDeath()
|
||||
{ ownerAlive = false; }
|
||||
|
||||
// We can enable streaming, but never disable it.
|
||||
void setStreaming(bool s)
|
||||
{ if(s) streaming = true; }
|
||||
|
||||
SoundPtr clone();
|
||||
|
||||
// a = AL_REFERENCE_DISTANCE
|
||||
// b = AL_MAX_DISTANCE
|
||||
// c = ignored
|
||||
void setRange(float a, float b=0.0, float c=0.0);
|
||||
|
||||
/// Not implemented
|
||||
void setPan(float) {}
|
||||
};
|
||||
|
||||
// ---- OpenAL_Factory ----
|
||||
|
||||
SoundPtr OpenAL_Factory::loadRaw(SampleSourcePtr input)
|
||||
{
|
||||
return SoundPtr(new OpenAL_Sound(input, this));
|
||||
}
|
||||
|
||||
void OpenAL_Factory::setListenerPos(float x, float y, float z,
|
||||
float fx, float fy, float fz,
|
||||
float ux, float uy, float uz)
|
||||
{
|
||||
ALfloat orient[6];
|
||||
orient[0] = fx;
|
||||
orient[1] = fy;
|
||||
orient[2] = fz;
|
||||
orient[3] = ux;
|
||||
orient[4] = uy;
|
||||
orient[5] = uz;
|
||||
alListener3f(AL_POSITION, x, y, z);
|
||||
alListenerfv(AL_ORIENTATION, orient);
|
||||
}
|
||||
|
||||
OpenAL_Factory::OpenAL_Factory(bool doSetup)
|
||||
: device(NULL), context(NULL), didSetup(doSetup)
|
||||
{
|
||||
needsUpdate = true;
|
||||
has3D = true;
|
||||
canLoadFile = false;
|
||||
canLoadStream = false;
|
||||
canLoadSource = true;
|
||||
|
||||
ALCdevice *Device;
|
||||
ALCcontext *Context;
|
||||
|
||||
if(doSetup)
|
||||
{
|
||||
// Set up sound system
|
||||
Device = alcOpenDevice(NULL);
|
||||
Context = alcCreateContext(Device, NULL);
|
||||
|
||||
if(!Device || !Context)
|
||||
fail("Failed to initialize context or device");
|
||||
|
||||
alcMakeContextCurrent(Context);
|
||||
|
||||
device = Device;
|
||||
context = Context;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenAL_Factory::update()
|
||||
{
|
||||
// Loop through all streaming sounds and update them
|
||||
StreamList::iterator it = streaming.begin();
|
||||
for(;it != streaming.end(); it++)
|
||||
(*it)->update();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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; i<bufNum; i++)
|
||||
queueBuffer(bufferID[i]);
|
||||
|
||||
// Notify the manager what we're doing
|
||||
owner->notifyStreaming(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;
|
||||
}
|
||||
}
|
44
libs/mangle/sound/outputs/openal_out.hpp
Normal file
44
libs/mangle/sound/outputs/openal_out.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef MANGLE_SOUND_OPENAL_OUT_H
|
||||
#define MANGLE_SOUND_OPENAL_OUT_H
|
||||
|
||||
#include "../output.hpp"
|
||||
#include <list>
|
||||
|
||||
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<OpenAL_Sound*> 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
|
62
libs/mangle/sound/source.hpp
Normal file
62
libs/mangle/sound/source.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef MANGLE_SOUND_SOURCE_H
|
||||
#define MANGLE_SOUND_SOURCE_H
|
||||
|
||||
#include <string>
|
||||
#include <boost/cstdint.hpp>
|
||||
#include <assert.h>
|
||||
|
||||
#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<SampleSource> 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<SampleSourceLoader> SampleSourceLoaderPtr;
|
||||
|
||||
}} // namespaces
|
||||
#endif
|
77
libs/mangle/sound/sources/audiere_source.cpp
Normal file
77
libs/mangle/sound/sources/audiere_source.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#include "audiere_source.hpp"
|
||||
|
||||
#include "../../stream/clients/audiere_file.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
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;
|
||||
}
|
48
libs/mangle/sound/sources/audiere_source.hpp
Normal file
48
libs/mangle/sound/sources/audiere_source.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#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 <cstring>
|
||||
#include <audiere.h>
|
||||
|
||||
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<AudiereSource,true,true> AudiereLoader;
|
||||
|
||||
}} // Namespace
|
||||
#endif
|
189
libs/mangle/sound/sources/ffmpeg_source.cpp
Normal file
189
libs/mangle/sound/sources/ffmpeg_source.cpp
Normal file
|
@ -0,0 +1,189 @@
|
|||
#include "ffmpeg_source.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
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;
|
||||
}
|
52
libs/mangle/sound/sources/ffmpeg_source.hpp
Normal file
52
libs/mangle/sound/sources/ffmpeg_source.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef MANGLE_SOUND_FFMPEG_H
|
||||
#define MANGLE_SOUND_FFMPEG_H
|
||||
|
||||
#include "../source.hpp"
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <avcodec.h>
|
||||
#include <avformat.h>
|
||||
}
|
||||
|
||||
namespace Mangle {
|
||||
namespace Sound {
|
||||
|
||||
class FFMpegSource : public SampleSource
|
||||
{
|
||||
AVFormatContext *FmtCtx;
|
||||
AVCodecContext *CodecCtx;
|
||||
unsigned int StreamNum;
|
||||
|
||||
std::vector<uint8_t> 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<FFMpegSource,false,true>
|
||||
{
|
||||
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
|
48
libs/mangle/sound/sources/libsndfile.cpp
Normal file
48
libs/mangle/sound/sources/libsndfile.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include "libsndfile.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <sndfile.h>
|
||||
|
||||
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);
|
||||
}
|
36
libs/mangle/sound/sources/libsndfile.hpp
Normal file
36
libs/mangle/sound/sources/libsndfile.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#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<SndFileSource,false,true> SndFileLoader;
|
||||
|
||||
}} // Namespace
|
||||
#endif
|
28
libs/mangle/sound/sources/loadertemplate.hpp
Normal file
28
libs/mangle/sound/sources/loadertemplate.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef SSL_TEMPL_H
|
||||
#define SSL_TEMPL_H
|
||||
|
||||
template <class SourceT, bool stream, bool file>
|
||||
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
|
115
libs/mangle/sound/sources/mpg123_source.cpp
Normal file
115
libs/mangle/sound/sources/mpg123_source.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "mpg123_source.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <mpg123.h>
|
||||
|
||||
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);
|
||||
}
|
47
libs/mangle/sound/sources/mpg123_source.hpp
Normal file
47
libs/mangle/sound/sources/mpg123_source.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef MANGLE_SOUND_MPG123_SOURCE_H
|
||||
#define MANGLE_SOUND_MPG123_SOURCE_H
|
||||
|
||||
#include "../source.hpp"
|
||||
#include <assert.h>
|
||||
|
||||
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<Mpg123Source,false,true>
|
||||
{
|
||||
/** 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
|
99
libs/mangle/sound/sources/sample_reader.cpp
Normal file
99
libs/mangle/sound/sources/sample_reader.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#include "sample_reader.hpp"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
48
libs/mangle/sound/sources/sample_reader.hpp
Normal file
48
libs/mangle/sound/sources/sample_reader.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#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
|
47
libs/mangle/sound/sources/stream_source.hpp
Normal file
47
libs/mangle/sound/sources/stream_source.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#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
|
99
libs/mangle/sound/sources/wav_source.cpp
Normal file
99
libs/mangle/sound/sources/wav_source.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#include "wav_source.hpp"
|
||||
|
||||
#include "../../stream/servers/file_stream.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
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))); }
|
49
libs/mangle/sound/sources/wav_source.hpp
Normal file
49
libs/mangle/sound/sources/wav_source.hpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#ifndef MANGLE_SOUND_WAV_SOURCE_H
|
||||
#define MANGLE_SOUND_WAV_SOURCE_H
|
||||
|
||||
#include "../source.hpp"
|
||||
#include <assert.h>
|
||||
|
||||
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<WavSource,true,true> WavLoader;
|
||||
|
||||
}} // Namespace
|
||||
#endif
|
1
libs/mangle/sound/tests/.gitignore
vendored
Normal file
1
libs/mangle/sound/tests/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*_test
|
38
libs/mangle/sound/tests/Makefile
Normal file
38
libs/mangle/sound/tests/Makefile
Normal file
|
@ -0,0 +1,38 @@
|
|||
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
|
68
libs/mangle/sound/tests/audiere_source_test.cpp
Normal file
68
libs/mangle/sound/tests/audiere_source_test.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include <iostream>
|
||||
|
||||
#include "../../stream/servers/file_stream.hpp"
|
||||
#include "../sources/audiere_source.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
BIN
libs/mangle/sound/tests/cow.raw
Normal file
BIN
libs/mangle/sound/tests/cow.raw
Normal file
Binary file not shown.
BIN
libs/mangle/sound/tests/cow.wav
Normal file
BIN
libs/mangle/sound/tests/cow.wav
Normal file
Binary file not shown.
62
libs/mangle/sound/tests/ffmpeg_source_test.cpp
Normal file
62
libs/mangle/sound/tests/ffmpeg_source_test.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include <iostream>
|
||||
|
||||
#include "../../stream/servers/file_stream.hpp"
|
||||
#include "../sources/ffmpeg_source.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
52
libs/mangle/sound/tests/openal_audiere_test.cpp
Normal file
52
libs/mangle/sound/tests/openal_audiere_test.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#include <iostream>
|
||||
#include <exception>
|
||||
|
||||
#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;
|
||||
}
|
52
libs/mangle/sound/tests/openal_ffmpeg_test.cpp
Normal file
52
libs/mangle/sound/tests/openal_ffmpeg_test.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#include <iostream>
|
||||
#include <exception>
|
||||
|
||||
#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;
|
||||
}
|
54
libs/mangle/sound/tests/openal_mpg123_test.cpp
Normal file
54
libs/mangle/sound/tests/openal_mpg123_test.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include <iostream>
|
||||
#include <exception>
|
||||
|
||||
#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;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue