Commit 06b3c9c0 authored by Stefan Majewsky's avatar Stefan Majewsky

port KGoldRunner from TagaroAudio to KgAudio

The branches using Phonon directly are removed completely. As discussed
with Ian, sound is enabled only if low latency playback via the OpenAL
backend is available.

svn path=/trunk/KDE/kdegames/kgoldrunner/; revision=1280243
parent 5e900bca
project(kgoldrunner)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
macro_optional_find_package(SndFile)
macro_log_feature(SNDFILE_FOUND "SndFile" "SndFile (libsndfile) is a C library written by Erik de Castro Lopo for reading and writing audio files." "http://www.mega-nerd.com/libsndfile/" FALSE "" "KGoldrunner needs SndFile to decode sound files. If SndFile cannot be provided on some platform or distribution, please email the KDE Games list <kde-games-devel at kde dot org>.")
# KGoldrunner sound requires access to the OpenAL and SndFile libraries. The
# use of Phonon, as an alternative, does not give good results and is deprecated
# (2011-05-09).
if(OPENAL_FOUND AND SNDFILE_FOUND)
include_directories(${OPENAL_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIRS})
include_directories(src/TagaroAudio)
set(KGOLDRUNNER_OPENAL TRUE)
ADD_DEFINITIONS(-DKGOLDRUNNER_USE_OPENAL)
ADD_DEFINITIONS(-DENABLE_SOUND_SUPPORT)
endif(OPENAL_FOUND AND SNDFILE_FOUND)
add_subdirectory( src )
add_subdirectory( gamedata )
add_subdirectory( themes )
......
#
# Try to find libsndfile
# Once done this will define
#
# SNDFILE_FOUND - libsndfile was found
# SNDFILE_INCLUDE_DIR - the libsndfile include directory
# SNDFILE_LIBRARIES - libsndfile libraries to link to
#
# Copyright (c) 2008, Sebastian Trueg, <trueg@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
if ( SNDFILE_INCLUDE_DIR AND SNDFILE_LIBRARIES )
# in cache already
SET(Sndfile_FIND_QUIETLY TRUE)
endif ( SNDFILE_INCLUDE_DIR AND SNDFILE_LIBRARIES )
IF (NOT WIN32)
# use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
INCLUDE(UsePkgConfig)
PKGCONFIG(sndfile _SndfileIncDir _SndfileLinkDir _SndfileLinkFlags _SndfileCflags)
ENDIF (NOT WIN32)
FIND_PATH(SNDFILE_INCLUDE_DIR
NAMES sndfile.h
PATHS ${_SndfileIncDir}
NO_DEFAULT_PATH
)
FIND_LIBRARY(SNDFILE_LIBRARIES
NAMES sndfile
PATHS ${_SndfileLinkDir}
NO_DEFAULT_PATH
)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Sndfile DEFAULT_MSG SNDFILE_INCLUDE_DIR SNDFILE_LIBRARIES )
# show the SNDFILE_INCLUDE_DIR and SNDFILE_LIBRARIES variables only in the advanced view
MARK_AS_ADVANCED(SNDFILE_INCLUDE_DIR SNDFILE_LIBRARIES )
......@@ -17,33 +17,13 @@ set(kgoldrunner_SRCS
kgreditor.cpp
main.cpp
kgrselector.cpp
kgrsounds.cpp
kgrdialog.cpp )
if(KGOLDRUNNER_OPENAL)
set(kgoldrunner_SRCS
${kgoldrunner_SRCS}
kgrsounds.cpp
TagaroAudio/audioscene.cpp
TagaroAudio/sound.cpp )
else(KGOLDRUNNER_OPENAL)
# KGoldrunner sound is supported only with OpenAL, else silent (2011-05-09).
# set(kgoldrunner_SRCS
# ${kgoldrunner_SRCS}
# kgrsoundbank.cpp )
endif(KGOLDRUNNER_OPENAL)
kde4_add_app_icon(kgoldrunner_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/hi*-app-kgoldrunner.png")
kde4_add_executable(kgoldrunner ${kgoldrunner_SRCS})
if(KGOLDRUNNER_OPENAL)
target_link_libraries(kgoldrunner ${KDE4_KDEUI_LIBS} kdegames
${OPENAL_LIBRARY} ${SNDFILE_LIBRARIES} )
else(KGOLDRUNNER_OPENAL)
# KGoldrunner sound is supported only with OpenAL, else silent (2011-05-09).
# target_link_libraries(kgoldrunner ${KDE4_KDEUI_LIBS} kdegames
# ${KDE4_PHONON_LIBS})
target_link_libraries(kgoldrunner ${KDE4_KDEUI_LIBS} kdegames)
endif(KGOLDRUNNER_OPENAL)
target_link_libraries(kgoldrunner ${KDE4_KDEUI_LIBS} kdegames )
install(TARGETS kgoldrunner ${INSTALL_TARGETS_DEFAULT_ARGS} )
......
/***************************************************************************
* Copyright 2010 Stefan Majewsky <majewsky@gmx.net> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License *
* version 2 as published by the Free Software Foundation *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "audioscene.h"
#include "openalruntime_p.h"
#include <KDE/KDebug>
#include <KDE/KGlobal>
K_GLOBAL_STATIC(Tagaro::OpenALRuntime, g_runtime)
//BEGIN Tagaro::AudioScene
QPointF Tagaro::AudioScene::listenerPos()
{
return g_runtime->m_listenerPos;
}
void Tagaro::AudioScene::setListenerPos(const QPointF& pos)
{
if (g_runtime->m_listenerPos != pos)
{
g_runtime->m_listenerPos = pos;
g_runtime->configureListener();
}
}
qreal Tagaro::AudioScene::volume()
{
return g_runtime->m_volume;
}
void Tagaro::AudioScene::setVolume(qreal volume)
{
if (g_runtime->m_volume != volume)
{
g_runtime->m_volume = volume;
g_runtime->configureListener();
}
}
//END Tagaro::AudioScene
//BEGIN Tagaro::OpenALRuntime
Tagaro::OpenALRuntime::OpenALRuntime()
: m_volume(1)
, m_context(0)
, m_device(alcOpenDevice(""))
{
if (!m_device)
{
kDebug() << "Failed to create OpenAL device";
return;
}
m_context = alcCreateContext(m_device, 0);
int error = alcGetError(m_device);
if (error != AL_NO_ERROR)
{
kDebug() << "Failed to create OpenAL context: Error code" << error;
return;
}
alcMakeContextCurrent(m_context);
configureListener();
}
Tagaro::OpenALRuntime::~OpenALRuntime()
{
if (m_context == alcGetCurrentContext())
{
alcMakeContextCurrent(0);
}
alcDestroyContext(m_context);
alcCloseDevice(m_device);
}
Tagaro::OpenALRuntime* Tagaro::OpenALRuntime::instance()
{
return g_runtime;
}
void Tagaro::OpenALRuntime::configureListener()
{
int error; alGetError(); //clear error cache
alListener3f(AL_POSITION, m_listenerPos.x(), m_listenerPos.y(), 0);
alListenerf(AL_GAIN, m_volume);
if ((error = alGetError()) != AL_NO_ERROR)
{
kDebug() << "Failed to setup OpenAL listener: Error code" << error;
}
}
//END Tagaro::OpenALRuntime
/***************************************************************************
* Copyright 2010 Stefan Majewsky <majewsky@gmx.net> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License *
* version 2 as published by the Free Software Foundation *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#ifndef TAGARO_AUDIOSCENE_H
#define TAGARO_AUDIOSCENE_H
#include <QtCore/QPointF>
namespace Tagaro {
/**
* @class Tagaro::AudioScene audioscene.h <Tagaro/AudioScene>
*
* This class exposes general properties of the audio playback context. Actual
* sounds are represented in this context by Tagaro::Sound instances.
*
* The audio scene basically consists of a listener. The position of this
* listener is relevant when sounds are played at certain positions: The audio
* channels will then be balanced to make the sound appear to come from that
* direction.
*
* Because there can only be one listener, all methods in this class are static.
*/
class AudioScene
{
public:
///@return the position of the listener
static QPointF listenerPos();
///Sets the position of the listener. The default is (0.0, 0.0), the
///point of origin.
static void setListenerPos(const QPointF& pos);
///@return the master volume for sounds outputted by TagaroAudio
static qreal volume();
///Sets the master volume for sounds outputted by TagaroAudio. The
///default is 1.0, which means no volume change, compared to the
///original sounds. 0.0 means that all sounds are muted.
static void setVolume(qreal volume);
private:
class Private;
//prohibit instantiation etc.
AudioScene();
Q_DISABLE_COPY(AudioScene)
};
} //namespace Tagaro
#endif // TAGARO_AUDIOSCENE_H
/***************************************************************************
* Copyright 2010 Stefan Majewsky <majewsky@gmx.net> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License *
* version 2 as published by the Free Software Foundation *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#ifndef TAGARO_OPENALRUNTIME_P_H
#define TAGARO_OPENALRUNTIME_P_H
#include <QtCore/QHash>
#include <QtCore/QPointF>
//OpenAL includes (without "AL/" include directory; see FindOpenAL.cmake)
#include <al.h>
#include <alc.h>
namespace Tagaro {
class Sound;
///@internal
class PlaybackEvent
{
public:
//Creates and starts the playback. Also registers with the OpenALRuntime.
PlaybackEvent(Tagaro::Sound* sound, const QPointF& pos);
//Stops playback if it is still running.
~PlaybackEvent();
//Is playback still running?
bool isRunning() const;
private:
ALuint m_source;
bool m_valid;
};
typedef QList<Tagaro::PlaybackEvent*> PlaybackEventList;
///@internal
class OpenALRuntime
{
public:
OpenALRuntime();
~OpenALRuntime();
static Tagaro::OpenALRuntime* instance();
void configureListener();
void cleanupUnusedSources();
//global properties
QPointF m_listenerPos;
qreal m_volume;
//active sound and playback instances
QHash<Tagaro::Sound*, Tagaro::PlaybackEventList> m_soundsEvents;
private:
ALCcontext* m_context;
ALCdevice* m_device;
};
} //namespace Tagaro
#endif // TAGARO_OPENALRUNTIME_P_H
/***************************************************************************
* Copyright 2010 Stefan Majewsky <majewsky@gmx.net> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License *
* version 2 as published by the Free Software Foundation *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "sound.h"
#include "openalruntime_p.h"
#include <sndfile.hh> //TODO: use Phonon instead of libsndfile for decoding
#include <KDE/KDebug>
struct Tagaro::Sound::Private
{
Tagaro::Sound::PlaybackType m_type;
qreal m_volume;
QPointF m_pos;
bool m_valid;
ALuint m_buffer;
Private() : m_type(Tagaro::Sound::AmbientPlayback), m_volume(1.0), m_valid(false), m_buffer(AL_NONE) {}
};
//BEGIN Tagaro::Sound
Tagaro::Sound::Sound(const QString& file, QObject* parent)
: QObject(parent)
, d(new Private)
{
//open sound file
SndfileHandle handle(file.toUtf8());
if (handle.error())
{
kDebug() << "Failed to load sound file. Error message from libsndfile follows.";
kDebug() << handle.strError();
return;
}
const int channelCount = handle.channels();
const int sampleCount = channelCount * handle.frames();
const int sampleRate = handle.samplerate();
//load data from sound file
QVector<ALshort> samples(sampleCount);
if (handle.read(samples.data(), sampleCount) < sampleCount)
{
kDebug() << "Failed to read sound file" << file;
kDebug() << "File ended unexpectedly.";
return;
}
//determine file format from number of channels
ALenum format;
switch (channelCount)
{
case 1:
format = AL_FORMAT_MONO16;
break;
case 2:
format = AL_FORMAT_STEREO16;
break;
default:
kDebug() << "Failed to read sound file" << file;
kDebug() << "More than two channels are not supported.";
return;
}
//make sure OpenAL is initialized; clear OpenAL error storage
Tagaro::OpenALRuntime::instance();
int error; alGetError();
//create OpenAL buffer
alGenBuffers(1, &d->m_buffer);
if ((error = alGetError()) != AL_NO_ERROR)
{
kDebug() << "Failed to create OpenAL buffer: Error code" << error;
return;
}
alBufferData(d->m_buffer, format, samples.data(), sampleCount * sizeof(ALshort), sampleRate);
if ((error = alGetError()) != AL_NO_ERROR)
{
kDebug() << "Failed to fill OpenAL buffer: Error code" << error;
alDeleteBuffers(1, &d->m_buffer);
return;
}
//loading finished
d->m_valid = true;
}
Tagaro::Sound::~Sound()
{
if (d->m_valid)
{
stop();
Tagaro::OpenALRuntime::instance()->m_soundsEvents.remove(this);
alDeleteBuffers(1, &d->m_buffer);
}
delete d;
}
bool Tagaro::Sound::isValid() const
{
return d->m_valid;
}
Tagaro::Sound::PlaybackType Tagaro::Sound::playbackType() const
{
return d->m_type;
}
void Tagaro::Sound::setPlaybackType(Tagaro::Sound::PlaybackType type)
{
d->m_type = type;
}
QPointF Tagaro::Sound::pos() const
{
return d->m_pos;
}
void Tagaro::Sound::setPos(const QPointF& pos)
{
d->m_pos = pos;
}
qreal Tagaro::Sound::volume() const
{
return d->m_volume;
}
void Tagaro::Sound::setVolume(qreal volume)
{
d->m_volume = volume;
}
void Tagaro::Sound::start()
{
if (d->m_valid)
{
new Tagaro::PlaybackEvent(this, d->m_pos);
}
}
void Tagaro::Sound::start(const QPointF& pos)
{
if (d->m_valid)
{
new Tagaro::PlaybackEvent(this, pos);
}
}
void Tagaro::Sound::stop()
{
qDeleteAll(Tagaro::OpenALRuntime::instance()->m_soundsEvents.take(this));
}
//END Tagaro::Sound
//BEGIN Tagaro::PlaybackEvent
Tagaro::PlaybackEvent::PlaybackEvent(Tagaro::Sound* sound, const QPointF& pos)
: m_valid(false)
{
//make sure OpenAL is initialized
Tagaro::OpenALRuntime* runtime = Tagaro::OpenALRuntime::instance();
//clear OpenAL error storage
int error; alGetError();
//create source for playback
alGenSources(1, &m_source);
if ((error = alGetError()) != AL_NO_ERROR)
{
kDebug() << "Failed to create OpenAL source: Error code" << error;
return;
}
//store in OpenALRuntime
runtime->m_soundsEvents[sound] << this;
m_valid = true;
//connect to sound (buffer)
alSource3f(m_source, AL_POSITION, pos.x(), pos.y(), 0);
alSourcef(m_source, AL_PITCH, 1.0);
alSourcef(m_source, AL_GAIN, sound->volume());
alSourcei(m_source, AL_BUFFER, sound->d->m_buffer);
const Tagaro::Sound::PlaybackType type = sound->playbackType();
alSourcef(m_source, AL_ROLLOFF_FACTOR, type == Tagaro::Sound::AmbientPlayback ? 0.0 : 1.0);
alSourcei(m_source, AL_SOURCE_RELATIVE, type == Tagaro::Sound::RelativePlayback ? AL_TRUE : AL_FALSE);
if ((error = alGetError()) != AL_NO_ERROR)
{
kDebug() << "Failed to setup OpenAL source: Error code" << error;
return;
}
//start playback
alSourcePlay(m_source);
}
Tagaro::PlaybackEvent::~PlaybackEvent()
{
if (m_valid)
{
alSourceStop(m_source);
alDeleteSources(1, &m_source);
}
}
bool Tagaro::PlaybackEvent::isRunning() const
{
ALint state;
alGetSourcei(m_source, AL_SOURCE_STATE, &state);
return state == AL_PLAYING;
}
//END Tagaro::PlaybackEvent
#include "sound.moc"
/***************************************************************************
* Copyright 2010 Stefan Majewsky <majewsky@gmx.net> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License *
* version 2 as published by the Free Software Foundation *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#ifndef TAGARO_SOUND_H
#define TAGARO_SOUND_H
#include <QtCore/QObject>
#include <QtCore/QPointF>
namespace Tagaro {
class PlaybackEvent;
/**
* @class Tagaro::Sound sound.h <Tagaro/Sound>
*
* This class models a sound file. Because it is implicitly added to this
* application's Tagaro::AudioScene, it can be played at different positions.
*
* Compared to many other media playback classes, the notable difference of
* Tagaro::Sound is that one sound instance can be played multiple times at the
* same point in time, by calling start() multiple times (possibly with
* different playback positions). This behavior can be suppressed by calling
* stop() before start().
*
* @note Sound files are loaded with libsndfile, which means that WAV, FLAC and
* Ogg/Vorbis are supported. (The non-WAV formats require a reasonably
* recent version of libsndfile.)
*/