get stereo audo working on linux
This commit is contained in:
parent
ae67c6adbc
commit
8e679f2da7
|
@ -203,6 +203,7 @@ SET(framework_SOURCES ${framework_SOURCES}
|
|||
${CMAKE_CURRENT_LIST_DIR}/sound/soundmanager.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sound/oggsoundfile.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sound/streamsoundsource.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/sound/combinedsoundsource.cpp
|
||||
|
||||
# framework otml
|
||||
${CMAKE_CURRENT_LIST_DIR}/otml/otmldocument.cpp
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2012 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "combinedsoundsource.h"
|
||||
|
||||
CombinedSoundSource::CombinedSoundSource() : SoundSource(0)
|
||||
{
|
||||
}
|
||||
|
||||
void CombinedSoundSource::addSource(const SoundSourcePtr& source)
|
||||
{
|
||||
m_sources.push_back(source);
|
||||
}
|
||||
|
||||
void CombinedSoundSource::play()
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->play();
|
||||
}
|
||||
|
||||
void CombinedSoundSource::stop()
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->stop();
|
||||
}
|
||||
|
||||
bool CombinedSoundSource::isPlaying()
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources) {
|
||||
if(source->isPlaying())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CombinedSoundSource::setLooping(bool looping)
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->setLooping(looping);
|
||||
}
|
||||
|
||||
void CombinedSoundSource::setRelative(bool relative)
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->setRelative(relative);
|
||||
}
|
||||
|
||||
void CombinedSoundSource::setReferenceDistance(float distance)
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->setReferenceDistance(distance);
|
||||
}
|
||||
|
||||
void CombinedSoundSource::setGain(float gain)
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->setGain(gain);
|
||||
}
|
||||
|
||||
void CombinedSoundSource::setPitch(float pitch)
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->setPitch(pitch);
|
||||
}
|
||||
|
||||
void CombinedSoundSource::setPosition(const Point& pos)
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->setPosition(pos);
|
||||
}
|
||||
|
||||
void CombinedSoundSource::setVelocity(const Point& velocity)
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->setVelocity(velocity);
|
||||
}
|
||||
|
||||
void CombinedSoundSource::update()
|
||||
{
|
||||
for(const SoundSourcePtr& source : m_sources)
|
||||
source->update();
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2012 OTClient <https://github.com/edubart/otclient>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef COMBINEDSOUNDSOURCE_H
|
||||
#define COMBINEDSOUNDSOURCE_H
|
||||
|
||||
#include "soundsource.h"
|
||||
|
||||
class CombinedSoundSource : public SoundSource
|
||||
{
|
||||
public:
|
||||
CombinedSoundSource();
|
||||
|
||||
void addSource(const SoundSourcePtr& source);
|
||||
std::vector<SoundSourcePtr> getSources() { return m_sources; }
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
bool isPlaying();
|
||||
|
||||
void setLooping(bool looping);
|
||||
void setRelative(bool relative);
|
||||
void setReferenceDistance(float distance);
|
||||
void setGain(float gain);
|
||||
void setPitch(float pitch);
|
||||
void setPosition(const Point& pos);
|
||||
void setVelocity(const Point& velocity);
|
||||
|
||||
protected:
|
||||
virtual void update();
|
||||
|
||||
private:
|
||||
std::vector<SoundSourcePtr> m_sources;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -35,12 +35,14 @@ class SoundSource;
|
|||
class SoundBuffer;
|
||||
class SoundFile;
|
||||
class StreamSoundSource;
|
||||
class CombinedSoundSource;
|
||||
class OggSoundFile;
|
||||
|
||||
typedef std::shared_ptr<SoundSource> SoundSourcePtr;
|
||||
typedef std::shared_ptr<SoundFile> SoundFilePtr;
|
||||
typedef std::shared_ptr<SoundBuffer> SoundBufferPtr;
|
||||
typedef std::shared_ptr<StreamSoundSource> StreamSoundSourcePtr;
|
||||
typedef std::shared_ptr<CombinedSoundSource> CombinedSoundSourcePtr;
|
||||
typedef std::shared_ptr<OggSoundFile> OggSoundFilePtr;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -23,8 +23,6 @@
|
|||
#include "soundbuffer.h"
|
||||
#include "soundfile.h"
|
||||
|
||||
#include <framework/util/databuffer.h>
|
||||
|
||||
SoundBuffer::SoundBuffer()
|
||||
{
|
||||
m_bufferId = 0;
|
||||
|
@ -38,7 +36,7 @@ SoundBuffer::~SoundBuffer()
|
|||
assert(alGetError() == AL_NO_ERROR);
|
||||
}
|
||||
|
||||
bool SoundBuffer::loadSoundFile(const SoundFilePtr& soundFile)
|
||||
bool SoundBuffer::fillBuffer(const SoundFilePtr& soundFile)
|
||||
{
|
||||
ALenum format = soundFile->getSampleFormat();
|
||||
if(format == AL_UNDETERMINED) {
|
||||
|
@ -53,12 +51,16 @@ bool SoundBuffer::loadSoundFile(const SoundFilePtr& soundFile)
|
|||
return false;
|
||||
}
|
||||
|
||||
alBufferData(m_bufferId, format, &samples[0], soundFile->getSize(), soundFile->getRate());
|
||||
ALenum err = alGetError();
|
||||
if(err != AL_NO_ERROR) {
|
||||
logError("unable to fill audio buffer data for '", soundFile->getName(), "': ", alGetString(err));
|
||||
return false;
|
||||
return fillBuffer(format, samples, samples.size(), soundFile->getRate());
|
||||
}
|
||||
|
||||
bool SoundBuffer::fillBuffer(ALenum sampleFormat, const DataBuffer<char>& data, int size, int rate)
|
||||
{
|
||||
alBufferData(m_bufferId, sampleFormat, &data[0], size, rate);
|
||||
ALenum err = alGetError();
|
||||
if(err != AL_NO_ERROR) {
|
||||
logError("unable to fill audio buffer data: ", alGetString(err));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -25,15 +25,18 @@
|
|||
|
||||
#include "declarations.h"
|
||||
|
||||
#include <framework/util/databuffer.h>
|
||||
|
||||
class SoundBuffer
|
||||
{
|
||||
public:
|
||||
SoundBuffer();
|
||||
~SoundBuffer();
|
||||
|
||||
bool loadSoundFile(const SoundFilePtr& soundFile);
|
||||
bool fillBuffer(const SoundFilePtr& soundFile);
|
||||
bool fillBuffer(ALenum sampleFormat, const DataBuffer<char>& data, int size, int rate);
|
||||
|
||||
int getBufferId() { return m_bufferId; }
|
||||
ALuint getBufferId() { return m_bufferId; }
|
||||
|
||||
private:
|
||||
ALuint m_bufferId;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "soundbuffer.h"
|
||||
#include "soundfile.h"
|
||||
#include "streamsoundsource.h"
|
||||
#include "combinedsoundsource.h"
|
||||
|
||||
#include <framework/core/clock.h>
|
||||
#include <framework/core/eventdispatcher.h>
|
||||
|
@ -124,7 +125,7 @@ void SoundManager::preload(const std::string& filename)
|
|||
return;
|
||||
|
||||
SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer);
|
||||
if(buffer->loadSoundFile(soundFile))
|
||||
if(buffer->fillBuffer(soundFile))
|
||||
m_buffers[filename] = buffer;
|
||||
}
|
||||
|
||||
|
@ -186,30 +187,53 @@ void SoundManager::stopMusic(float fadetime)
|
|||
|
||||
SoundSourcePtr SoundManager::createSoundSource(const std::string& filename)
|
||||
{
|
||||
SoundSourcePtr soundSource;
|
||||
SoundSourcePtr source;
|
||||
|
||||
auto it = m_buffers.find(filename);
|
||||
if(it != m_buffers.end()) {
|
||||
soundSource = SoundSourcePtr(new SoundSource);
|
||||
soundSource->setBuffer(it->second);
|
||||
source = SoundSourcePtr(new SoundSource);
|
||||
source->setBuffer(it->second);
|
||||
} else {
|
||||
SoundFilePtr soundFile = SoundFile::loadSoundFile(filename);
|
||||
if(!soundFile)
|
||||
return nullptr;
|
||||
|
||||
if(soundFile->getSize() <= MAX_CACHE_SIZE) {
|
||||
soundSource = SoundSourcePtr(new SoundSource);
|
||||
source = SoundSourcePtr(new SoundSource);
|
||||
SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer);
|
||||
buffer->loadSoundFile(soundFile);
|
||||
soundSource->setBuffer(buffer);
|
||||
buffer->fillBuffer(soundFile);
|
||||
source->setBuffer(buffer);
|
||||
m_buffers[filename] = buffer;
|
||||
logWarning("uncached sound '", filename, "' requested to be played");
|
||||
} else {
|
||||
StreamSoundSourcePtr streamSoundSource(new StreamSoundSource);
|
||||
streamSoundSource->setSoundFile(soundFile);
|
||||
soundSource = streamSoundSource;
|
||||
StreamSoundSourcePtr streamSource(new StreamSoundSource);
|
||||
streamSource->setSoundFile(soundFile);
|
||||
source = streamSource;
|
||||
|
||||
#ifdef __linux
|
||||
// due to OpenAL implementation bug stereo buffers are always downmixed to mono on linux systems
|
||||
// this is hack to work around the issue
|
||||
// solution taken from http://opensource.creative.com/pipermail/openal/2007-April/010355.html
|
||||
if(soundFile->getSampleFormat() == AL_FORMAT_STEREO16) {
|
||||
CombinedSoundSourcePtr combinedSource(new CombinedSoundSource);
|
||||
|
||||
streamSource->downMix(StreamSoundSource::DownMixLeft);
|
||||
streamSource->setRelative(true);
|
||||
streamSource->setPosition(Point(-128, 0));
|
||||
combinedSource->addSource(streamSource);
|
||||
|
||||
streamSource = StreamSoundSourcePtr(new StreamSoundSource);
|
||||
streamSource->setSoundFile(SoundFile::loadSoundFile(filename));
|
||||
streamSource->downMix(StreamSoundSource::DownMixRight);
|
||||
streamSource->setRelative(true);
|
||||
streamSource->setPosition(Point(128,0));
|
||||
combinedSource->addSource(streamSource);
|
||||
|
||||
source = combinedSource;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return soundSource;
|
||||
return source;
|
||||
}
|
||||
|
|
|
@ -33,10 +33,12 @@ SoundSource::SoundSource()
|
|||
|
||||
SoundSource::~SoundSource()
|
||||
{
|
||||
if(m_sourceId != 0) {
|
||||
stop();
|
||||
alDeleteSources(1, &m_sourceId);
|
||||
assert(alGetError() == AL_NO_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundSource::play()
|
||||
{
|
||||
|
@ -93,3 +95,13 @@ void SoundSource::setPitch(float pitch)
|
|||
{
|
||||
alSourcef(m_sourceId, AL_PITCH, pitch);
|
||||
}
|
||||
|
||||
void SoundSource::setPosition(const Point& pos)
|
||||
{
|
||||
alSource3f(m_sourceId, AL_POSITION, pos.x, pos.y, 0);
|
||||
}
|
||||
|
||||
void SoundSource::setVelocity(const Point& velocity)
|
||||
{
|
||||
alSource3f(m_sourceId, AL_VELOCITY, velocity.x, velocity.y, 0);
|
||||
}
|
||||
|
|
|
@ -27,27 +27,31 @@
|
|||
|
||||
class SoundSource
|
||||
{
|
||||
protected:
|
||||
SoundSource(ALuint sourceId) : m_sourceId(sourceId) { }
|
||||
|
||||
public:
|
||||
SoundSource();
|
||||
virtual ~SoundSource();
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
virtual void play();
|
||||
virtual void stop();
|
||||
virtual bool isPlaying();
|
||||
|
||||
bool isPlaying();
|
||||
|
||||
void setBuffer(const SoundBufferPtr& buffer);
|
||||
virtual void setLooping(bool looping);
|
||||
void setRelative(bool relative);
|
||||
void setReferenceDistance(float distance);
|
||||
void setGain(float gain);
|
||||
void setPitch(float pitch);
|
||||
|
||||
// TODO: velocity, position
|
||||
virtual void setRelative(bool relative);
|
||||
virtual void setReferenceDistance(float distance);
|
||||
virtual void setGain(float gain);
|
||||
virtual void setPitch(float pitch);
|
||||
virtual void setPosition(const Point& pos);
|
||||
virtual void setVelocity(const Point& velocity);
|
||||
|
||||
protected:
|
||||
void setBuffer(const SoundBufferPtr& buffer);
|
||||
|
||||
virtual void update() { }
|
||||
friend class SoundManager;
|
||||
friend class CombinedSoundSource;
|
||||
|
||||
ALuint m_sourceId;
|
||||
SoundBufferPtr m_buffer;
|
||||
|
|
|
@ -32,25 +32,39 @@ StreamSoundSource::StreamSoundSource()
|
|||
for(auto& buffer : m_buffers)
|
||||
buffer = SoundBufferPtr(new SoundBuffer);
|
||||
m_fadeState = NoFading;
|
||||
m_downMix = NoDownMix;
|
||||
}
|
||||
|
||||
StreamSoundSource::~StreamSoundSource()
|
||||
{
|
||||
stop();
|
||||
|
||||
ALint queued;
|
||||
alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued);
|
||||
for(int i = 0; i < queued; ++i) {
|
||||
ALuint buffer;
|
||||
alSourceUnqueueBuffers(m_sourceId, 1, &buffer);
|
||||
}
|
||||
m_buffers.fill(nullptr);
|
||||
}
|
||||
|
||||
void StreamSoundSource::setSoundFile(const SoundFilePtr& soundFile)
|
||||
{
|
||||
m_soundFile = soundFile;
|
||||
}
|
||||
|
||||
void StreamSoundSource::play()
|
||||
{
|
||||
if(!m_soundFile) {
|
||||
logError("there is not sound file to play the stream");
|
||||
return;
|
||||
}
|
||||
|
||||
queueBuffers();
|
||||
|
||||
SoundSource::play();
|
||||
}
|
||||
|
||||
void StreamSoundSource::stop()
|
||||
{
|
||||
SoundSource::stop();
|
||||
unqueueBuffers();
|
||||
}
|
||||
|
||||
void StreamSoundSource::queueBuffers()
|
||||
{
|
||||
ALint queued;
|
||||
alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued);
|
||||
for(int i = 0; i < STREAM_FRAGMENTS - queued; ++i) {
|
||||
|
@ -59,6 +73,16 @@ void StreamSoundSource::setSoundFile(const SoundFilePtr& soundFile)
|
|||
}
|
||||
}
|
||||
|
||||
void StreamSoundSource::unqueueBuffers()
|
||||
{
|
||||
ALint queued;
|
||||
alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued);
|
||||
for(int i = 0; i < queued; ++i) {
|
||||
ALuint buffer;
|
||||
alSourceUnqueueBuffers(m_sourceId, 1, &buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void StreamSoundSource::update()
|
||||
{
|
||||
ALint processed = 0;
|
||||
|
@ -76,9 +100,8 @@ void StreamSoundSource::update()
|
|||
if(processed == 0 || !m_looping)
|
||||
return;
|
||||
|
||||
// we might have to restart the source if we had a buffer underrun
|
||||
//log_info << "Restarting audio source because of buffer underrun" << std::endl;
|
||||
//play();
|
||||
logTraceError("restarting audio source because of buffer underrun");
|
||||
play();
|
||||
}
|
||||
|
||||
float realTime = g_clock.asyncTime();
|
||||
|
@ -104,7 +127,8 @@ void StreamSoundSource::update()
|
|||
bool StreamSoundSource::fillBufferAndQueue(ALuint buffer)
|
||||
{
|
||||
// fill buffer
|
||||
DataBuffer<char> bufferData(STREAM_FRAGMENT_SIZE);
|
||||
static DataBuffer<char> bufferData(STREAM_FRAGMENT_SIZE);
|
||||
ALenum format = m_soundFile->getSampleFormat();
|
||||
|
||||
int bytesRead = 0;
|
||||
do {
|
||||
|
@ -119,8 +143,21 @@ bool StreamSoundSource::fillBufferAndQueue(ALuint buffer)
|
|||
}
|
||||
} while(bytesRead < STREAM_FRAGMENT_SIZE);
|
||||
|
||||
bool done = bytesRead >= STREAM_FRAGMENT_SIZE;
|
||||
|
||||
if(m_downMix != NoDownMix) {
|
||||
if(format == AL_FORMAT_STEREO16) {
|
||||
if(bytesRead > 0) {
|
||||
uint16_t *data = (uint16_t*)bufferData.data();
|
||||
bytesRead /= 2;
|
||||
for(int i=0;i<bytesRead;i++)
|
||||
data[i] = data[2*i + (m_downMix == DownMixLeft ? 0 : 1)];
|
||||
}
|
||||
format = AL_FORMAT_MONO16;
|
||||
}
|
||||
}
|
||||
|
||||
if(bytesRead > 0) {
|
||||
ALenum format = m_soundFile->getSampleFormat();
|
||||
alBufferData(buffer, format, &bufferData[0], bytesRead, m_soundFile->getRate());
|
||||
ALenum err = alGetError();
|
||||
if(err != AL_NO_ERROR)
|
||||
|
@ -133,7 +170,22 @@ bool StreamSoundSource::fillBufferAndQueue(ALuint buffer)
|
|||
}
|
||||
|
||||
// return false if there aren't more buffers to fill
|
||||
return bytesRead >= STREAM_FRAGMENT_SIZE;
|
||||
return done;
|
||||
}
|
||||
|
||||
void StreamSoundSource::downMix(StreamSoundSource::DownMix downMix)
|
||||
{
|
||||
if(!m_soundFile) {
|
||||
logError("down mix must be set after setting a sound file");
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_soundFile->getSampleFormat() != AL_FORMAT_STEREO16) {
|
||||
logError("can only downmix 16 bit stereo audio files");
|
||||
return;
|
||||
}
|
||||
|
||||
m_downMix = downMix;
|
||||
}
|
||||
|
||||
void StreamSoundSource::setFading(FadeState state, float fadeTime)
|
||||
|
|
|
@ -34,23 +34,31 @@ class StreamSoundSource : public SoundSource
|
|||
};
|
||||
|
||||
public:
|
||||
enum DownMix { NoDownMix, DownMixLeft, DownMixRight };
|
||||
enum FadeState { NoFading, FadingOn, FadingOff };
|
||||
|
||||
StreamSoundSource();
|
||||
virtual ~StreamSoundSource();
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
|
||||
void setSoundFile(const SoundFilePtr& soundFile);
|
||||
|
||||
void downMix(DownMix downMix);
|
||||
void setFading(FadeState state, float fadetime);
|
||||
FadeState getFadeState() { return m_fadeState; }
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
void queueBuffers();
|
||||
void unqueueBuffers();
|
||||
bool fillBufferAndQueue(ALuint buffer);
|
||||
|
||||
SoundFilePtr m_soundFile;
|
||||
std::array<SoundBufferPtr,STREAM_FRAGMENTS> m_buffers;
|
||||
DownMix m_downMix;
|
||||
FadeState m_fadeState;
|
||||
float m_fadeStartTime;
|
||||
float m_fadeTime;
|
||||
|
|
Loading…
Reference in New Issue