mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 21:15:33 +00:00
commit
343dccdbcc
12 changed files with 115 additions and 228 deletions
|
@ -389,16 +389,6 @@ void FFmpeg_Decoder::readAll(std::vector<char> &output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpeg_Decoder::rewind()
|
|
||||||
{
|
|
||||||
int stream_idx = mStream - mFormatCtx->streams;
|
|
||||||
if(av_seek_frame(mFormatCtx, stream_idx, 0, 0) < 0)
|
|
||||||
fail("Failed to seek in audio stream");
|
|
||||||
av_free_packet(&mPacket);
|
|
||||||
mFrameSize = mFramePos = 0;
|
|
||||||
mNextPts = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t FFmpeg_Decoder::getSampleOffset()
|
size_t FFmpeg_Decoder::getSampleOffset()
|
||||||
{
|
{
|
||||||
int delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
|
int delay = (mFrameSize-mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) /
|
||||||
|
|
|
@ -74,7 +74,6 @@ namespace MWSound
|
||||||
|
|
||||||
virtual size_t read(char *buffer, size_t bytes);
|
virtual size_t read(char *buffer, size_t bytes);
|
||||||
virtual void readAll(std::vector<char> &output);
|
virtual void readAll(std::vector<char> &output);
|
||||||
virtual void rewind();
|
|
||||||
virtual size_t getSampleOffset();
|
virtual size_t getSampleOffset();
|
||||||
|
|
||||||
void fail(const std::string &msg);
|
void fail(const std::string &msg);
|
||||||
|
|
|
@ -8,15 +8,16 @@
|
||||||
namespace MWSound
|
namespace MWSound
|
||||||
{
|
{
|
||||||
|
|
||||||
void Sound_Loudness::analyzeLoudness(const std::vector< char >& data, int sampleRate, ChannelConfig chans, SampleType type, float valuesPerSecond)
|
void Sound_Loudness::analyzeLoudness(const std::vector< char >& data)
|
||||||
{
|
{
|
||||||
int samplesPerSegment = static_cast<int>(sampleRate / valuesPerSecond);
|
mQueue.insert( mQueue.end(), data.begin(), data.end() );
|
||||||
int numSamples = bytesToFrames(data.size(), chans, type);
|
if (!mQueue.size())
|
||||||
int advance = framesToBytes(1, chans, type);
|
return;
|
||||||
|
|
||||||
|
int samplesPerSegment = static_cast<int>(mSampleRate / mSamplesPerSec);
|
||||||
|
int numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType);
|
||||||
|
int advance = framesToBytes(1, mChannelConfig, mSampleType);
|
||||||
|
|
||||||
mSamplesPerSec = valuesPerSecond;
|
|
||||||
mSamples.clear();
|
|
||||||
mSamples.reserve(numSamples/samplesPerSegment);
|
|
||||||
|
|
||||||
int segment=0;
|
int segment=0;
|
||||||
int sample=0;
|
int sample=0;
|
||||||
|
@ -28,16 +29,16 @@ void Sound_Loudness::analyzeLoudness(const std::vector< char >& data, int sample
|
||||||
{
|
{
|
||||||
// get sample on a scale from -1 to 1
|
// get sample on a scale from -1 to 1
|
||||||
float value = 0;
|
float value = 0;
|
||||||
if (type == SampleType_UInt8)
|
if (mSampleType == SampleType_UInt8)
|
||||||
value = ((char)(data[sample*advance]^0x80))/128.f;
|
value = ((char)(mQueue[sample*advance]^0x80))/128.f;
|
||||||
else if (type == SampleType_Int16)
|
else if (mSampleType == SampleType_Int16)
|
||||||
{
|
{
|
||||||
value = *reinterpret_cast<const int16_t*>(&data[sample*advance]);
|
value = *reinterpret_cast<const int16_t*>(&mQueue[sample*advance]);
|
||||||
value /= float(std::numeric_limits<int16_t>::max());
|
value /= float(std::numeric_limits<int16_t>::max());
|
||||||
}
|
}
|
||||||
else if (type == SampleType_Float32)
|
else if (mSampleType == SampleType_Float32)
|
||||||
{
|
{
|
||||||
value = *reinterpret_cast<const float*>(&data[sample*advance]);
|
value = *reinterpret_cast<const float*>(&mQueue[sample*advance]);
|
||||||
value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already.
|
value = std::max(-1.f, std::min(1.f, value)); // Float samples *should* be scaled to [-1,1] already.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ void Sound_Loudness::analyzeLoudness(const std::vector< char >& data, int sample
|
||||||
++segment;
|
++segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
mReady = true;
|
mQueue.erase(mQueue.begin(), mQueue.begin() + sample*advance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define GAME_SOUND_LOUDNESS_H
|
#define GAME_SOUND_LOUDNESS_H
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
#include "sound_decoder.hpp"
|
#include "sound_decoder.hpp"
|
||||||
|
|
||||||
|
@ -9,29 +10,45 @@ namespace MWSound
|
||||||
{
|
{
|
||||||
|
|
||||||
class Sound_Loudness {
|
class Sound_Loudness {
|
||||||
// Loudness sample info
|
|
||||||
float mSamplesPerSec;
|
float mSamplesPerSec;
|
||||||
|
int mSampleRate;
|
||||||
|
ChannelConfig mChannelConfig;
|
||||||
|
SampleType mSampleType;
|
||||||
|
|
||||||
|
// Loudness sample info
|
||||||
std::vector<float> mSamples;
|
std::vector<float> mSamples;
|
||||||
volatile bool mReady;
|
|
||||||
|
std::deque<char> mQueue;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Sound_Loudness() : mSamplesPerSec(0.0f), mReady(false) { }
|
/**
|
||||||
|
* @param samplesPerSecond How many loudness values per second of audio to compute.
|
||||||
|
* @param sampleRate the sample rate of the sound buffer
|
||||||
|
* @param chans channel layout of the buffer
|
||||||
|
* @param type sample type of the buffer
|
||||||
|
*/
|
||||||
|
Sound_Loudness(float samplesPerSecond, int sampleRate, ChannelConfig chans, SampleType type)
|
||||||
|
: mSamplesPerSec(samplesPerSecond)
|
||||||
|
, mSampleRate(sampleRate)
|
||||||
|
, mChannelConfig(chans)
|
||||||
|
, mSampleType(type)
|
||||||
|
{ }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyzes the energy (closely related to loudness) of a sound buffer.
|
* Analyzes the energy (closely related to loudness) of a sound buffer.
|
||||||
* The buffer will be divided into segments according to \a valuesPerSecond,
|
* The buffer will be divided into segments according to \a valuesPerSecond,
|
||||||
* and for each segment a loudness value in the range of [0,1] will be computed.
|
* and for each segment a loudness value in the range of [0,1] will be computed.
|
||||||
|
* The computed values are then added to the mSamples vector. This method should be called continuously
|
||||||
|
* with chunks of audio until the whole audio file is processed.
|
||||||
|
* If the size of \a data does not exactly fit a number of loudness samples, the remainder
|
||||||
|
* will be kept in the mQueue and used in the next call to analyzeLoudness.
|
||||||
* @param data the sound buffer to analyze, containing raw samples
|
* @param data the sound buffer to analyze, containing raw samples
|
||||||
* @param sampleRate the sample rate of the sound buffer
|
|
||||||
* @param chans channel layout of the buffer
|
|
||||||
* @param type sample type of the buffer
|
|
||||||
* @param valuesPerSecond How many loudness values per second of audio to compute.
|
|
||||||
*/
|
*/
|
||||||
void analyzeLoudness(const std::vector<char>& data, int sampleRate,
|
void analyzeLoudness(const std::vector<char>& data);
|
||||||
ChannelConfig chans, SampleType type,
|
|
||||||
float valuesPerSecond);
|
|
||||||
|
|
||||||
bool isReady() { return mReady; }
|
/**
|
||||||
|
* Get loudness at a particular time. Before calling this, the stream has to be analyzed up to that point in time (see analyzeLoudness()).
|
||||||
|
*/
|
||||||
float getLoudnessAtTime(float sec) const;
|
float getLoudnessAtTime(float sec) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ namespace MWSound
|
||||||
|
|
||||||
virtual void open(const std::string &fname);
|
virtual void open(const std::string &fname);
|
||||||
virtual void close();
|
virtual void close();
|
||||||
virtual void rewind();
|
|
||||||
virtual std::string getName();
|
virtual std::string getName();
|
||||||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
|
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
|
||||||
virtual size_t read(char *buffer, size_t bytes);
|
virtual size_t read(char *buffer, size_t bytes);
|
||||||
|
@ -102,7 +101,6 @@ namespace MWSound
|
||||||
throw std::runtime_error("unimplemented");
|
throw std::runtime_error("unimplemented");
|
||||||
}
|
}
|
||||||
void MWSoundDecoderBridge::close() {}
|
void MWSoundDecoderBridge::close() {}
|
||||||
void MWSoundDecoderBridge::rewind() {}
|
|
||||||
|
|
||||||
std::string MWSoundDecoderBridge::getName()
|
std::string MWSoundDecoderBridge::getName()
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
@ -212,6 +213,8 @@ private:
|
||||||
|
|
||||||
DecoderPtr mDecoder;
|
DecoderPtr mDecoder;
|
||||||
|
|
||||||
|
std::auto_ptr<Sound_Loudness> mLoudnessAnalyzer;
|
||||||
|
|
||||||
volatile bool mIsFinished;
|
volatile bool mIsFinished;
|
||||||
|
|
||||||
void updateAll(bool local);
|
void updateAll(bool local);
|
||||||
|
@ -222,13 +225,15 @@ private:
|
||||||
friend class OpenAL_Output;
|
friend class OpenAL_Output;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OpenAL_SoundStream(ALuint src, DecoderPtr decoder);
|
OpenAL_SoundStream(ALuint src, DecoderPtr decoder, bool getLoudnessData=false);
|
||||||
~OpenAL_SoundStream();
|
~OpenAL_SoundStream();
|
||||||
|
|
||||||
bool isPlaying();
|
bool isPlaying();
|
||||||
double getStreamDelay() const;
|
double getStreamDelay() const;
|
||||||
double getStreamOffset() const;
|
double getStreamOffset() const;
|
||||||
|
|
||||||
|
float getCurrentLoudness() const;
|
||||||
|
|
||||||
bool process();
|
bool process();
|
||||||
ALint refillQueue();
|
ALint refillQueue();
|
||||||
};
|
};
|
||||||
|
@ -242,9 +247,6 @@ struct OpenAL_Output::StreamThread {
|
||||||
typedef std::vector<OpenAL_SoundStream*> StreamVec;
|
typedef std::vector<OpenAL_SoundStream*> StreamVec;
|
||||||
StreamVec mStreams;
|
StreamVec mStreams;
|
||||||
|
|
||||||
typedef std::vector<std::pair<DecoderPtr,Sound_Loudness*> > DecoderLoudnessVec;
|
|
||||||
DecoderLoudnessVec mDecoderLoudness;
|
|
||||||
|
|
||||||
volatile bool mQuitNow;
|
volatile bool mQuitNow;
|
||||||
boost::mutex mMutex;
|
boost::mutex mMutex;
|
||||||
boost::condition_variable mCondVar;
|
boost::condition_variable mCondVar;
|
||||||
|
@ -277,32 +279,6 @@ struct OpenAL_Output::StreamThread {
|
||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only do one loudness decode at a time, in case it takes particularly long we don't
|
|
||||||
// want to block up anything.
|
|
||||||
DecoderLoudnessVec::iterator dliter = mDecoderLoudness.begin();
|
|
||||||
if(dliter != mDecoderLoudness.end())
|
|
||||||
{
|
|
||||||
DecoderPtr decoder = dliter->first;
|
|
||||||
Sound_Loudness *loudness = dliter->second;
|
|
||||||
mDecoderLoudness.erase(dliter);
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
std::vector<char> data;
|
|
||||||
ChannelConfig chans = ChannelConfig_Mono;
|
|
||||||
SampleType type = SampleType_Int16;
|
|
||||||
int srate = 48000;
|
|
||||||
try {
|
|
||||||
decoder->getInfo(&srate, &chans, &type);
|
|
||||||
decoder->readAll(data);
|
|
||||||
}
|
|
||||||
catch(std::exception &e) {
|
|
||||||
std::cerr<< "Failed to decode audio: "<<e.what() <<std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
loudness->analyzeLoudness(data, srate, chans, type, static_cast<float>(sLoudnessFPS));
|
|
||||||
lock.lock();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
mCondVar.timed_wait(lock, boost::posix_time::milliseconds(50));
|
mCondVar.timed_wait(lock, boost::posix_time::milliseconds(50));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,15 +305,6 @@ struct OpenAL_Output::StreamThread {
|
||||||
{
|
{
|
||||||
boost::lock_guard<boost::mutex> lock(mMutex);
|
boost::lock_guard<boost::mutex> lock(mMutex);
|
||||||
mStreams.clear();
|
mStreams.clear();
|
||||||
mDecoderLoudness.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void add(DecoderPtr decoder, Sound_Loudness *loudness)
|
|
||||||
{
|
|
||||||
boost::unique_lock<boost::mutex> lock(mMutex);
|
|
||||||
mDecoderLoudness.push_back(std::make_pair(decoder, loudness));
|
|
||||||
lock.unlock();
|
|
||||||
mCondVar.notify_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -346,7 +313,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder)
|
OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder, bool getLoudnessData)
|
||||||
: mSource(src), mCurrentBufIdx(0), mFrameSize(0), mSilence(0), mDecoder(decoder), mIsFinished(false)
|
: mSource(src), mCurrentBufIdx(0), mFrameSize(0), mSilence(0), mDecoder(decoder), mIsFinished(false)
|
||||||
{
|
{
|
||||||
alGenBuffers(sNumBuffers, mBuffers);
|
alGenBuffers(sNumBuffers, mBuffers);
|
||||||
|
@ -371,6 +338,9 @@ OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder)
|
||||||
mFrameSize = framesToBytes(1, chans, type);
|
mFrameSize = framesToBytes(1, chans, type);
|
||||||
mBufferSize = static_cast<ALuint>(sBufferLength*srate);
|
mBufferSize = static_cast<ALuint>(sBufferLength*srate);
|
||||||
mBufferSize *= mFrameSize;
|
mBufferSize *= mFrameSize;
|
||||||
|
|
||||||
|
if (getLoudnessData)
|
||||||
|
mLoudnessAnalyzer.reset(new Sound_Loudness(sLoudnessFPS, mSampleRate, chans, type));
|
||||||
}
|
}
|
||||||
catch(std::exception&)
|
catch(std::exception&)
|
||||||
{
|
{
|
||||||
|
@ -446,6 +416,15 @@ double OpenAL_SoundStream::getStreamOffset() const
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float OpenAL_SoundStream::getCurrentLoudness() const
|
||||||
|
{
|
||||||
|
if (!mLoudnessAnalyzer.get())
|
||||||
|
return 0.f;
|
||||||
|
|
||||||
|
float time = getStreamOffset();
|
||||||
|
return mLoudnessAnalyzer->getLoudnessAtTime(time);
|
||||||
|
}
|
||||||
|
|
||||||
bool OpenAL_SoundStream::process()
|
bool OpenAL_SoundStream::process()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -493,6 +472,9 @@ ALint OpenAL_SoundStream::refillQueue()
|
||||||
}
|
}
|
||||||
if(got > 0)
|
if(got > 0)
|
||||||
{
|
{
|
||||||
|
if (mLoudnessAnalyzer.get())
|
||||||
|
mLoudnessAnalyzer->analyzeLoudness(data);
|
||||||
|
|
||||||
ALuint bufid = mBuffers[mCurrentBufIdx];
|
ALuint bufid = mBuffers[mCurrentBufIdx];
|
||||||
alBufferData(bufid, mFormat, &data[0], data.size(), mSampleRate);
|
alBufferData(bufid, mFormat, &data[0], data.size(), mSampleRate);
|
||||||
alSourceQueueBuffers(mSource, 1, &bufid);
|
alSourceQueueBuffers(mSource, 1, &bufid);
|
||||||
|
@ -981,7 +963,7 @@ void OpenAL_Output::streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound
|
||||||
sound->mHandle = stream;
|
sound->mHandle = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound)
|
void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound, bool getLoudnessData)
|
||||||
{
|
{
|
||||||
OpenAL_SoundStream *stream = 0;
|
OpenAL_SoundStream *stream = 0;
|
||||||
ALuint source;
|
ALuint source;
|
||||||
|
@ -998,7 +980,7 @@ void OpenAL_Output::streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sou
|
||||||
sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv());
|
sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv());
|
||||||
throwALerror();
|
throwALerror();
|
||||||
|
|
||||||
stream = new OpenAL_SoundStream(source, decoder);
|
stream = new OpenAL_SoundStream(source, decoder, getLoudnessData);
|
||||||
mStreamThread->add(stream);
|
mStreamThread->add(stream);
|
||||||
mActiveStreams.push_back(sound);
|
mActiveStreams.push_back(sound);
|
||||||
}
|
}
|
||||||
|
@ -1045,6 +1027,14 @@ double OpenAL_Output::getStreamOffset(MWBase::SoundStreamPtr sound)
|
||||||
return stream->getStreamOffset();
|
return stream->getStreamOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float OpenAL_Output::getStreamLoudness(MWBase::SoundStreamPtr sound)
|
||||||
|
{
|
||||||
|
if(!sound->mHandle) return 0.0;
|
||||||
|
OpenAL_SoundStream *stream = reinterpret_cast<OpenAL_SoundStream*>(sound->mHandle);
|
||||||
|
boost::lock_guard<boost::mutex> lock(mStreamThread->mMutex);
|
||||||
|
return stream->getCurrentLoudness();
|
||||||
|
}
|
||||||
|
|
||||||
bool OpenAL_Output::isStreamPlaying(MWBase::SoundStreamPtr sound)
|
bool OpenAL_Output::isStreamPlaying(MWBase::SoundStreamPtr sound)
|
||||||
{
|
{
|
||||||
if(!sound->mHandle) return false;
|
if(!sound->mHandle) return false;
|
||||||
|
@ -1144,12 +1134,6 @@ void OpenAL_Output::resumeSounds(int types)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void OpenAL_Output::loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness)
|
|
||||||
{
|
|
||||||
mStreamThread->add(decoder, loudness);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
OpenAL_Output::OpenAL_Output(SoundManager &mgr)
|
OpenAL_Output::OpenAL_Output(SoundManager &mgr)
|
||||||
: Sound_Output(mgr), mDevice(0), mContext(0)
|
: Sound_Output(mgr), mDevice(0), mContext(0)
|
||||||
, mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal)
|
, mListenerPos(0.0f, 0.0f, 0.0f), mListenerEnv(Env_Normal)
|
||||||
|
|
|
@ -63,10 +63,11 @@ namespace MWSound
|
||||||
virtual void updateSound(MWBase::SoundPtr sound);
|
virtual void updateSound(MWBase::SoundPtr sound);
|
||||||
|
|
||||||
virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound);
|
virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound);
|
||||||
virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound);
|
virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound, bool getLoudnessData);
|
||||||
virtual void finishStream(MWBase::SoundStreamPtr sound);
|
virtual void finishStream(MWBase::SoundStreamPtr sound);
|
||||||
virtual double getStreamDelay(MWBase::SoundStreamPtr sound);
|
virtual double getStreamDelay(MWBase::SoundStreamPtr sound);
|
||||||
virtual double getStreamOffset(MWBase::SoundStreamPtr sound);
|
virtual double getStreamOffset(MWBase::SoundStreamPtr sound);
|
||||||
|
virtual float getStreamLoudness(MWBase::SoundStreamPtr sound);
|
||||||
virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound);
|
virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound);
|
||||||
virtual void updateStream(MWBase::SoundStreamPtr sound);
|
virtual void updateStream(MWBase::SoundStreamPtr sound);
|
||||||
|
|
||||||
|
@ -78,8 +79,6 @@ namespace MWSound
|
||||||
virtual void pauseSounds(int types);
|
virtual void pauseSounds(int types);
|
||||||
virtual void resumeSounds(int types);
|
virtual void resumeSounds(int types);
|
||||||
|
|
||||||
virtual void loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness);
|
|
||||||
|
|
||||||
OpenAL_Output(SoundManager &mgr);
|
OpenAL_Output(SoundManager &mgr);
|
||||||
virtual ~OpenAL_Output();
|
virtual ~OpenAL_Output();
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,11 +3,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "soundmanagerimp.hpp"
|
|
||||||
#include "sound_output.hpp"
|
#include "sound_output.hpp"
|
||||||
#include "loudness.hpp"
|
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
|
||||||
|
|
||||||
namespace MWSound
|
namespace MWSound
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,7 +42,6 @@ namespace MWSound
|
||||||
|
|
||||||
virtual size_t read(char *buffer, size_t bytes) = 0;
|
virtual size_t read(char *buffer, size_t bytes) = 0;
|
||||||
virtual void readAll(std::vector<char> &output);
|
virtual void readAll(std::vector<char> &output);
|
||||||
virtual void rewind() = 0;
|
|
||||||
virtual size_t getSampleOffset() = 0;
|
virtual size_t getSampleOffset() = 0;
|
||||||
|
|
||||||
Sound_Decoder(const VFS::Manager* resourceMgr) : mResourceMgr(resourceMgr)
|
Sound_Decoder(const VFS::Manager* resourceMgr) : mResourceMgr(resourceMgr)
|
||||||
|
|
|
@ -12,7 +12,6 @@ namespace MWSound
|
||||||
class SoundManager;
|
class SoundManager;
|
||||||
struct Sound_Decoder;
|
struct Sound_Decoder;
|
||||||
class Sound;
|
class Sound;
|
||||||
class Sound_Loudness;
|
|
||||||
|
|
||||||
// An opaque handle for the implementation's sound buffers.
|
// An opaque handle for the implementation's sound buffers.
|
||||||
typedef void *Sound_Handle;
|
typedef void *Sound_Handle;
|
||||||
|
@ -42,10 +41,11 @@ namespace MWSound
|
||||||
virtual void updateSound(MWBase::SoundPtr sound) = 0;
|
virtual void updateSound(MWBase::SoundPtr sound) = 0;
|
||||||
|
|
||||||
virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0;
|
virtual void streamSound(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0;
|
||||||
virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound) = 0;
|
virtual void streamSound3D(DecoderPtr decoder, MWBase::SoundStreamPtr sound, bool getLoudnessData) = 0;
|
||||||
virtual void finishStream(MWBase::SoundStreamPtr sound) = 0;
|
virtual void finishStream(MWBase::SoundStreamPtr sound) = 0;
|
||||||
virtual double getStreamDelay(MWBase::SoundStreamPtr sound) = 0;
|
virtual double getStreamDelay(MWBase::SoundStreamPtr sound) = 0;
|
||||||
virtual double getStreamOffset(MWBase::SoundStreamPtr sound) = 0;
|
virtual double getStreamOffset(MWBase::SoundStreamPtr sound) = 0;
|
||||||
|
virtual float getStreamLoudness(MWBase::SoundStreamPtr sound) = 0;
|
||||||
virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound) = 0;
|
virtual bool isStreamPlaying(MWBase::SoundStreamPtr sound) = 0;
|
||||||
virtual void updateStream(MWBase::SoundStreamPtr sound) = 0;
|
virtual void updateStream(MWBase::SoundStreamPtr sound) = 0;
|
||||||
|
|
||||||
|
@ -57,11 +57,6 @@ namespace MWSound
|
||||||
virtual void pauseSounds(int types) = 0;
|
virtual void pauseSounds(int types) = 0;
|
||||||
virtual void resumeSounds(int types) = 0;
|
virtual void resumeSounds(int types) = 0;
|
||||||
|
|
||||||
// HACK: The sound output implementation really shouldn't be handling
|
|
||||||
// asynchronous loudness data loading, but it's currently where we have
|
|
||||||
// a background processing thread.
|
|
||||||
virtual void loadLoudnessAsync(DecoderPtr decoder, Sound_Loudness *loudness) = 0;
|
|
||||||
|
|
||||||
Sound_Output& operator=(const Sound_Output &rhs);
|
Sound_Output& operator=(const Sound_Output &rhs);
|
||||||
Sound_Output(const Sound_Output &rhs);
|
Sound_Output(const Sound_Output &rhs);
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ namespace MWSound
|
||||||
return sfx;
|
return sfx;
|
||||||
}
|
}
|
||||||
|
|
||||||
DecoderPtr SoundManager::loadVoice(const std::string &voicefile, Sound_Loudness **lipdata)
|
DecoderPtr SoundManager::loadVoice(const std::string &voicefile)
|
||||||
{
|
{
|
||||||
DecoderPtr decoder = getDecoder();
|
DecoderPtr decoder = getDecoder();
|
||||||
// Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav.
|
// Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav.
|
||||||
|
@ -234,21 +234,6 @@ namespace MWSound
|
||||||
decoder->open(file);
|
decoder->open(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
NameLoudnessRefMap::iterator lipiter = mVoiceLipNameMap.find(voicefile);
|
|
||||||
if(lipiter != mVoiceLipNameMap.end())
|
|
||||||
{
|
|
||||||
*lipdata = lipiter->second;
|
|
||||||
return decoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
mVoiceLipBuffers.insert(mVoiceLipBuffers.end(), Sound_Loudness());
|
|
||||||
lipiter = mVoiceLipNameMap.insert(
|
|
||||||
std::make_pair(voicefile, &mVoiceLipBuffers.back())
|
|
||||||
).first;
|
|
||||||
|
|
||||||
mOutput->loadLoudnessAsync(decoder, lipiter->second);
|
|
||||||
|
|
||||||
*lipdata = lipiter->second;
|
|
||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +258,7 @@ namespace MWSound
|
||||||
{
|
{
|
||||||
sound.reset(new Stream(pos, 1.0f, basevol, 1.0f, minDistance, maxDistance,
|
sound.reset(new Stream(pos, 1.0f, basevol, 1.0f, minDistance, maxDistance,
|
||||||
Play_Normal|Play_TypeVoice|Play_3D));
|
Play_Normal|Play_TypeVoice|Play_3D));
|
||||||
mOutput->streamSound3D(decoder, sound);
|
mOutput->streamSound3D(decoder, sound, true);
|
||||||
}
|
}
|
||||||
return sound;
|
return sound;
|
||||||
}
|
}
|
||||||
|
@ -400,28 +385,22 @@ namespace MWSound
|
||||||
{
|
{
|
||||||
std::string voicefile = "Sound/"+filename;
|
std::string voicefile = "Sound/"+filename;
|
||||||
|
|
||||||
Sound_Loudness *loudness;
|
|
||||||
mVFS->normalizeFilename(voicefile);
|
mVFS->normalizeFilename(voicefile);
|
||||||
DecoderPtr decoder = loadVoice(voicefile, &loudness);
|
DecoderPtr decoder = loadVoice(voicefile);
|
||||||
|
|
||||||
if(!loudness->isReady())
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
mPendingSaySounds[ptr] = std::make_pair(decoder, loudness);
|
const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans();
|
||||||
else
|
|
||||||
|
SaySoundMap::iterator oldIt = mActiveSaySounds.find(ptr);
|
||||||
|
if (oldIt != mActiveSaySounds.end())
|
||||||
{
|
{
|
||||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
mOutput->finishStream(oldIt->second);
|
||||||
const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans();
|
mActiveSaySounds.erase(oldIt);
|
||||||
|
|
||||||
SaySoundMap::iterator oldIt = mActiveSaySounds.find(ptr);
|
|
||||||
if (oldIt != mActiveSaySounds.end())
|
|
||||||
{
|
|
||||||
mOutput->finishStream(oldIt->second.first);
|
|
||||||
mActiveSaySounds.erase(oldIt);
|
|
||||||
}
|
|
||||||
|
|
||||||
MWBase::SoundStreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer()));
|
|
||||||
|
|
||||||
mActiveSaySounds.insert(std::make_pair(ptr, std::make_pair(sound, loudness)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MWBase::SoundStreamPtr sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer()));
|
||||||
|
|
||||||
|
mActiveSaySounds.insert(std::make_pair(ptr, sound));
|
||||||
}
|
}
|
||||||
catch(std::exception &e)
|
catch(std::exception &e)
|
||||||
{
|
{
|
||||||
|
@ -434,10 +413,8 @@ namespace MWSound
|
||||||
SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr);
|
SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr);
|
||||||
if(snditer != mActiveSaySounds.end())
|
if(snditer != mActiveSaySounds.end())
|
||||||
{
|
{
|
||||||
MWBase::SoundStreamPtr sound = snditer->second.first;
|
MWBase::SoundStreamPtr sound = snditer->second;
|
||||||
Sound_Loudness *loudness = snditer->second.second;
|
return mOutput->getStreamLoudness(sound);
|
||||||
float sec = mOutput->getStreamOffset(sound);
|
|
||||||
return loudness->getLoudnessAtTime(sec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
|
@ -451,24 +428,18 @@ namespace MWSound
|
||||||
{
|
{
|
||||||
std::string voicefile = "Sound/"+filename;
|
std::string voicefile = "Sound/"+filename;
|
||||||
|
|
||||||
Sound_Loudness *loudness;
|
|
||||||
mVFS->normalizeFilename(voicefile);
|
mVFS->normalizeFilename(voicefile);
|
||||||
DecoderPtr decoder = loadVoice(voicefile, &loudness);
|
DecoderPtr decoder = loadVoice(voicefile);
|
||||||
|
|
||||||
if(!loudness->isReady())
|
SaySoundMap::iterator oldIt = mActiveSaySounds.find(MWWorld::ConstPtr());
|
||||||
mPendingSaySounds[MWWorld::ConstPtr()] = std::make_pair(decoder, loudness);
|
if (oldIt != mActiveSaySounds.end())
|
||||||
else
|
|
||||||
{
|
{
|
||||||
SaySoundMap::iterator oldIt = mActiveSaySounds.find(MWWorld::ConstPtr());
|
mOutput->finishStream(oldIt->second);
|
||||||
if (oldIt != mActiveSaySounds.end())
|
mActiveSaySounds.erase(oldIt);
|
||||||
{
|
|
||||||
mOutput->finishStream(oldIt->second.first);
|
|
||||||
mActiveSaySounds.erase(oldIt);
|
|
||||||
}
|
|
||||||
|
|
||||||
mActiveSaySounds.insert(std::make_pair(MWWorld::ConstPtr(),
|
|
||||||
std::make_pair(playVoice(decoder, osg::Vec3f(), true), loudness)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mActiveSaySounds.insert(std::make_pair(MWWorld::ConstPtr(),
|
||||||
|
playVoice(decoder, osg::Vec3f(), true)));
|
||||||
}
|
}
|
||||||
catch(std::exception &e)
|
catch(std::exception &e)
|
||||||
{
|
{
|
||||||
|
@ -481,11 +452,11 @@ namespace MWSound
|
||||||
SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr);
|
SaySoundMap::const_iterator snditer = mActiveSaySounds.find(ptr);
|
||||||
if(snditer != mActiveSaySounds.end())
|
if(snditer != mActiveSaySounds.end())
|
||||||
{
|
{
|
||||||
if(mOutput->isStreamPlaying(snditer->second.first))
|
if(mOutput->isStreamPlaying(snditer->second))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return mPendingSaySounds.find(ptr) == mPendingSaySounds.end();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SoundManager::stopSay(const MWWorld::ConstPtr &ptr)
|
void SoundManager::stopSay(const MWWorld::ConstPtr &ptr)
|
||||||
|
@ -493,10 +464,9 @@ namespace MWSound
|
||||||
SaySoundMap::iterator snditer = mActiveSaySounds.find(ptr);
|
SaySoundMap::iterator snditer = mActiveSaySounds.find(ptr);
|
||||||
if(snditer != mActiveSaySounds.end())
|
if(snditer != mActiveSaySounds.end())
|
||||||
{
|
{
|
||||||
mOutput->finishStream(snditer->second.first);
|
mOutput->finishStream(snditer->second);
|
||||||
mActiveSaySounds.erase(snditer);
|
mActiveSaySounds.erase(snditer);
|
||||||
}
|
}
|
||||||
mPendingSaySounds.erase(ptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -691,7 +661,7 @@ namespace MWSound
|
||||||
sayiter->first != MWMechanics::getPlayer() &&
|
sayiter->first != MWMechanics::getPlayer() &&
|
||||||
sayiter->first.getCell() == cell)
|
sayiter->first.getCell() == cell)
|
||||||
{
|
{
|
||||||
mOutput->finishStream(sayiter->second.first);
|
mOutput->finishStream(sayiter->second);
|
||||||
}
|
}
|
||||||
++sayiter;
|
++sayiter;
|
||||||
}
|
}
|
||||||
|
@ -901,51 +871,11 @@ namespace MWSound
|
||||||
++snditer;
|
++snditer;
|
||||||
}
|
}
|
||||||
|
|
||||||
SayDecoderMap::iterator penditer = mPendingSaySounds.begin();
|
|
||||||
while(penditer != mPendingSaySounds.end())
|
|
||||||
{
|
|
||||||
Sound_Loudness *loudness = penditer->second.second;
|
|
||||||
if(loudness->isReady())
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
DecoderPtr decoder = penditer->second.first;
|
|
||||||
decoder->rewind();
|
|
||||||
|
|
||||||
MWBase::SoundStreamPtr sound;
|
|
||||||
MWWorld::ConstPtr ptr = penditer->first;
|
|
||||||
|
|
||||||
SaySoundMap::iterator old = mActiveSaySounds.find(ptr);
|
|
||||||
if (old != mActiveSaySounds.end())
|
|
||||||
{
|
|
||||||
mOutput->finishStream(old->second.first);
|
|
||||||
mActiveSaySounds.erase(old);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ptr == MWWorld::ConstPtr())
|
|
||||||
sound = playVoice(decoder, osg::Vec3f(), true);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
||||||
const osg::Vec3f pos = world->getActorHeadTransform(ptr).getTrans();
|
|
||||||
sound = playVoice(decoder, pos, (ptr == MWMechanics::getPlayer()));
|
|
||||||
}
|
|
||||||
mActiveSaySounds.insert(std::make_pair(ptr, std::make_pair(sound, loudness)));
|
|
||||||
}
|
|
||||||
catch(std::exception &e) {
|
|
||||||
std::cerr<< "Sound Error: "<<e.what() <<std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
mPendingSaySounds.erase(penditer++);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
++penditer;
|
|
||||||
}
|
|
||||||
|
|
||||||
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
|
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
|
||||||
while(sayiter != mActiveSaySounds.end())
|
while(sayiter != mActiveSaySounds.end())
|
||||||
{
|
{
|
||||||
MWWorld::ConstPtr ptr = sayiter->first;
|
MWWorld::ConstPtr ptr = sayiter->first;
|
||||||
MWBase::SoundStreamPtr sound = sayiter->second.first;
|
MWBase::SoundStreamPtr sound = sayiter->second;
|
||||||
if(!ptr.isEmpty() && sound->getIs3D())
|
if(!ptr.isEmpty() && sound->getIs3D())
|
||||||
{
|
{
|
||||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
|
@ -1040,7 +970,7 @@ namespace MWSound
|
||||||
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
|
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
|
||||||
for(;sayiter != mActiveSaySounds.end();++sayiter)
|
for(;sayiter != mActiveSaySounds.end();++sayiter)
|
||||||
{
|
{
|
||||||
MWBase::SoundStreamPtr sound = sayiter->second.first;
|
MWBase::SoundStreamPtr sound = sayiter->second;
|
||||||
sound->setBaseVolume(volumeFromType(sound->getPlayType()));
|
sound->setBaseVolume(volumeFromType(sound->getPlayType()));
|
||||||
mOutput->updateStream(sound);
|
mOutput->updateStream(sound);
|
||||||
}
|
}
|
||||||
|
@ -1080,16 +1010,9 @@ namespace MWSound
|
||||||
SaySoundMap::iterator sayiter = mActiveSaySounds.find(old);
|
SaySoundMap::iterator sayiter = mActiveSaySounds.find(old);
|
||||||
if(sayiter != mActiveSaySounds.end())
|
if(sayiter != mActiveSaySounds.end())
|
||||||
{
|
{
|
||||||
SoundLoudnessPair sndlist = sayiter->second;
|
MWBase::SoundStreamPtr stream = sayiter->second;
|
||||||
mActiveSaySounds.erase(sayiter);
|
mActiveSaySounds.erase(sayiter);
|
||||||
mActiveSaySounds[updated] = sndlist;
|
mActiveSaySounds[updated] = stream;
|
||||||
}
|
|
||||||
SayDecoderMap::iterator penditer = mPendingSaySounds.find(old);
|
|
||||||
if(penditer != mPendingSaySounds.end())
|
|
||||||
{
|
|
||||||
DecoderLoudnessPair dl = penditer->second;
|
|
||||||
mPendingSaySounds.erase(penditer);
|
|
||||||
mPendingSaySounds[updated] = dl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1175,13 +1098,12 @@ namespace MWSound
|
||||||
mActiveSounds.clear();
|
mActiveSounds.clear();
|
||||||
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
|
SaySoundMap::iterator sayiter = mActiveSaySounds.begin();
|
||||||
for(;sayiter != mActiveSaySounds.end();++sayiter)
|
for(;sayiter != mActiveSaySounds.end();++sayiter)
|
||||||
mOutput->finishStream(sayiter->second.first);
|
mOutput->finishStream(sayiter->second);
|
||||||
mActiveSaySounds.clear();
|
mActiveSaySounds.clear();
|
||||||
TrackList::iterator trkiter = mActiveTracks.begin();
|
TrackList::iterator trkiter = mActiveTracks.begin();
|
||||||
for(;trkiter != mActiveTracks.end();++trkiter)
|
for(;trkiter != mActiveTracks.end();++trkiter)
|
||||||
mOutput->finishStream(*trkiter);
|
mOutput->finishStream(*trkiter);
|
||||||
mActiveTracks.clear();
|
mActiveTracks.clear();
|
||||||
mPendingSaySounds.clear();
|
|
||||||
mUnderwaterSound.reset();
|
mUnderwaterSound.reset();
|
||||||
stopMusic();
|
stopMusic();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
|
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "loudness.hpp"
|
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
|
||||||
namespace VFS
|
namespace VFS
|
||||||
|
@ -69,12 +68,6 @@ namespace MWSound
|
||||||
typedef std::map<std::string,Sound_Buffer*> NameBufferMap;
|
typedef std::map<std::string,Sound_Buffer*> NameBufferMap;
|
||||||
NameBufferMap mBufferNameMap;
|
NameBufferMap mBufferNameMap;
|
||||||
|
|
||||||
typedef std::deque<Sound_Loudness> LoudnessList;
|
|
||||||
LoudnessList mVoiceLipBuffers;
|
|
||||||
|
|
||||||
typedef std::map<std::string,Sound_Loudness*> NameLoudnessRefMap;
|
|
||||||
NameLoudnessRefMap mVoiceLipNameMap;
|
|
||||||
|
|
||||||
// NOTE: unused buffers are stored in front-newest order.
|
// NOTE: unused buffers are stored in front-newest order.
|
||||||
typedef std::deque<Sound_Buffer*> SoundList;
|
typedef std::deque<Sound_Buffer*> SoundList;
|
||||||
SoundList mUnusedBuffers;
|
SoundList mUnusedBuffers;
|
||||||
|
@ -84,14 +77,9 @@ namespace MWSound
|
||||||
typedef std::map<MWWorld::ConstPtr,SoundBufferRefPairList> SoundMap;
|
typedef std::map<MWWorld::ConstPtr,SoundBufferRefPairList> SoundMap;
|
||||||
SoundMap mActiveSounds;
|
SoundMap mActiveSounds;
|
||||||
|
|
||||||
typedef std::pair<MWBase::SoundStreamPtr,Sound_Loudness*> SoundLoudnessPair;
|
typedef std::map<MWWorld::ConstPtr,MWBase::SoundStreamPtr> SaySoundMap;
|
||||||
typedef std::map<MWWorld::ConstPtr,SoundLoudnessPair> SaySoundMap;
|
|
||||||
SaySoundMap mActiveSaySounds;
|
SaySoundMap mActiveSaySounds;
|
||||||
|
|
||||||
typedef std::pair<DecoderPtr,Sound_Loudness*> DecoderLoudnessPair;
|
|
||||||
typedef std::map<MWWorld::ConstPtr,DecoderLoudnessPair> SayDecoderMap;
|
|
||||||
SayDecoderMap mPendingSaySounds;
|
|
||||||
|
|
||||||
typedef std::vector<MWBase::SoundStreamPtr> TrackList;
|
typedef std::vector<MWBase::SoundStreamPtr> TrackList;
|
||||||
TrackList mActiveTracks;
|
TrackList mActiveTracks;
|
||||||
|
|
||||||
|
@ -112,9 +100,8 @@ namespace MWSound
|
||||||
Sound_Buffer *lookupSound(const std::string &soundId) const;
|
Sound_Buffer *lookupSound(const std::string &soundId) const;
|
||||||
Sound_Buffer *loadSound(const std::string &soundId);
|
Sound_Buffer *loadSound(const std::string &soundId);
|
||||||
|
|
||||||
// Ensures the loudness/"lip" data gets loaded, and returns a decoder
|
// returns a decoder to start streaming
|
||||||
// to start streaming
|
DecoderPtr loadVoice(const std::string &voicefile);
|
||||||
DecoderPtr loadVoice(const std::string &voicefile, Sound_Loudness **lipdata);
|
|
||||||
|
|
||||||
MWBase::SoundStreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal);
|
MWBase::SoundStreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue