File indexing completed on 2024-05-12 04:06:03
0001 /* 0002 SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "kgamesound.h" 0008 0009 // own 0010 #include "kgameopenalruntime_p.h" 0011 #include "virtualfileqt-openal.h" 0012 #include <kdegames_audio_logging.h> 0013 // sndfile 0014 #include <sndfile.hh> 0015 0016 class KGameSoundPrivate 0017 { 0018 public: 0019 KGameSound::PlaybackType m_type = KGameSound::AmbientPlayback; 0020 qreal m_volume = 1.0; 0021 QPointF m_pos; 0022 0023 bool m_valid = false; 0024 ALuint m_buffer = AL_NONE; 0025 0026 public: 0027 KGameSoundPrivate() = default; 0028 }; 0029 0030 // BEGIN KGameSound 0031 0032 KGameSound::KGameSound(const QString &file, QObject *parent) 0033 : QObject(parent) 0034 , d_ptr(new KGameSoundPrivate) 0035 { 0036 Q_D(KGameSound); 0037 0038 VirtualFileQt fileInterface(file); 0039 if (!fileInterface.open()) { 0040 qCWarning(KDEGAMES_AUDIO_LOG) << "Failed to open sound file" << file; 0041 return; 0042 } 0043 0044 // open sound file 0045 SndfileHandle handle(VirtualFileQt::getSndfileVirtualIO(), &fileInterface); 0046 if (handle.error()) { 0047 qCWarning(KDEGAMES_AUDIO_LOG) << "Failed to load sound file" << file << ". Error message from libsndfile follows."; 0048 qCWarning(KDEGAMES_AUDIO_LOG) << handle.strError(); 0049 return; 0050 } 0051 const int channelCount = handle.channels(); 0052 const int sampleCount = channelCount * handle.frames(); 0053 const int sampleRate = handle.samplerate(); 0054 // load data from sound file 0055 QList<ALshort> samples(sampleCount); 0056 if (handle.read(samples.data(), sampleCount) < sampleCount) { 0057 qCWarning(KDEGAMES_AUDIO_LOG) << "Failed to read sound file" << file; 0058 qCWarning(KDEGAMES_AUDIO_LOG) << "File ended unexpectedly."; 0059 return; 0060 } 0061 // determine file format from number of channels 0062 ALenum format; 0063 switch (channelCount) { 0064 case 1: 0065 format = AL_FORMAT_MONO16; 0066 break; 0067 case 2: 0068 format = AL_FORMAT_STEREO16; 0069 break; 0070 default: 0071 qCWarning(KDEGAMES_AUDIO_LOG) << "Failed to read sound file" << file; 0072 qCWarning(KDEGAMES_AUDIO_LOG) << "More than two channels are not supported."; 0073 return; 0074 } 0075 // make sure OpenAL is initialized; clear OpenAL error storage 0076 KGameOpenALRuntime::instance(); 0077 int error; 0078 alGetError(); 0079 // create OpenAL buffer 0080 alGenBuffers(1, &d->m_buffer); 0081 if ((error = alGetError()) != AL_NO_ERROR) { 0082 qCWarning(KDEGAMES_AUDIO_LOG) << "Failed to create OpenAL buffer: Error code" << error; 0083 return; 0084 } 0085 alBufferData(d->m_buffer, format, samples.data(), sampleCount * sizeof(ALshort), sampleRate); 0086 if ((error = alGetError()) != AL_NO_ERROR) { 0087 qCWarning(KDEGAMES_AUDIO_LOG) << "Failed to fill OpenAL buffer: Error code" << error; 0088 alDeleteBuffers(1, &d->m_buffer); 0089 return; 0090 } 0091 // loading finished 0092 d->m_valid = true; 0093 } 0094 0095 KGameSound::~KGameSound() 0096 { 0097 Q_D(KGameSound); 0098 0099 if (d->m_valid) { 0100 stop(); 0101 KGameOpenALRuntime::instance()->m_soundsEvents.remove(this); 0102 alDeleteBuffers(1, &d->m_buffer); 0103 } 0104 } 0105 0106 bool KGameSound::isValid() const 0107 { 0108 Q_D(const KGameSound); 0109 0110 return d->m_valid; 0111 } 0112 0113 KGameSound::PlaybackType KGameSound::playbackType() const 0114 { 0115 Q_D(const KGameSound); 0116 0117 return d->m_type; 0118 } 0119 0120 void KGameSound::setPlaybackType(KGameSound::PlaybackType type) 0121 { 0122 Q_D(KGameSound); 0123 0124 if (d->m_type == type) 0125 return; 0126 d->m_type = type; 0127 Q_EMIT playbackTypeChanged(type); 0128 } 0129 0130 QPointF KGameSound::pos() const 0131 { 0132 Q_D(const KGameSound); 0133 0134 return d->m_pos; 0135 } 0136 0137 void KGameSound::setPos(QPointF pos) 0138 { 0139 Q_D(KGameSound); 0140 0141 if (d->m_pos == pos) 0142 return; 0143 d->m_pos = pos; 0144 Q_EMIT posChanged(pos); 0145 } 0146 0147 qreal KGameSound::volume() const 0148 { 0149 Q_D(const KGameSound); 0150 0151 return d->m_volume; 0152 } 0153 0154 void KGameSound::setVolume(qreal volume) 0155 { 0156 Q_D(KGameSound); 0157 0158 if (d->m_volume == volume) 0159 return; 0160 d->m_volume = volume; 0161 Q_EMIT volumeChanged(volume); 0162 } 0163 0164 bool KGameSound::hasError() const 0165 { 0166 Q_D(const KGameSound); 0167 0168 return !d->m_valid; 0169 } 0170 0171 void KGameSound::start() 0172 { 0173 Q_D(KGameSound); 0174 0175 start(d->m_pos); 0176 } 0177 0178 void KGameSound::start(QPointF pos) 0179 { 0180 Q_D(KGameSound); 0181 0182 if (d->m_valid) { 0183 KGameOpenALRuntime *runtime = KGameOpenALRuntime::instance(); 0184 if (!runtime->instance()->m_soundsEvents[this].isEmpty()) { 0185 if (runtime->instance()->m_soundsEvents[this].last()->replay(pos) == false) { 0186 new KGamePlaybackEvent(this, pos); 0187 } 0188 } else { 0189 new KGamePlaybackEvent(this, pos); 0190 } 0191 } 0192 } 0193 0194 void KGameSound::stop() 0195 { 0196 qDeleteAll(KGameOpenALRuntime::instance()->m_soundsEvents.take(this)); 0197 } 0198 0199 // END KGameSound 0200 // BEGIN KGamePlaybackEvent 0201 0202 KGamePlaybackEvent::KGamePlaybackEvent(KGameSound *sound, QPointF pos) 0203 : m_valid(false) 0204 { 0205 // make sure OpenAL is initialized 0206 KGameOpenALRuntime *runtime = KGameOpenALRuntime::instance(); 0207 // clear OpenAL error storage 0208 int error; 0209 alGetError(); 0210 // create source for playback 0211 alGenSources(1, &m_source); 0212 if ((error = alGetError()) != AL_NO_ERROR) { 0213 qCWarning(KDEGAMES_AUDIO_LOG) << "Failed to create OpenAL source: Error code" << error; 0214 return; 0215 } 0216 // store in OpenALRuntime 0217 runtime->m_soundsEvents[sound] << this; 0218 m_valid = true; 0219 // connect to sound (buffer) 0220 alSource3f(m_source, AL_POSITION, pos.x(), pos.y(), 0); 0221 alSourcef(m_source, AL_PITCH, 1.0); // TODO: debug 0222 alSourcef(m_source, AL_GAIN, sound->volume()); 0223 alSourcei(m_source, AL_BUFFER, sound->d_ptr->m_buffer); 0224 const KGameSound::PlaybackType type = sound->playbackType(); 0225 alSourcef(m_source, AL_ROLLOFF_FACTOR, type == KGameSound::AmbientPlayback ? 0.0 : 1.0); 0226 alSourcei(m_source, AL_SOURCE_RELATIVE, type == KGameSound::RelativePlayback ? AL_TRUE : AL_FALSE); 0227 if ((error = alGetError()) != AL_NO_ERROR) { 0228 qCWarning(KDEGAMES_AUDIO_LOG) << "Failed to setup OpenAL source: Error code" << error; 0229 return; 0230 } 0231 // start playback 0232 alSourcePlay(m_source); 0233 } 0234 0235 KGamePlaybackEvent::~KGamePlaybackEvent() 0236 { 0237 if (alIsSource(m_source) == AL_TRUE) { 0238 alSourceStop(m_source); 0239 alDeleteSources(1, &m_source); 0240 } 0241 } 0242 0243 bool KGamePlaybackEvent::isRunning() const 0244 { 0245 ALint state; 0246 alGetSourcei(m_source, AL_SOURCE_STATE, &state); 0247 return state == AL_PLAYING; 0248 } 0249 0250 bool KGamePlaybackEvent::replay(QPointF pos) const 0251 { 0252 if (alIsSource(m_source) == AL_TRUE) { 0253 alSourceStop(m_source); 0254 alSource3f(m_source, AL_POSITION, pos.x(), pos.y(), 0); 0255 alSourcePlay(m_source); 0256 return true; 0257 } else { 0258 return false; 0259 } 0260 } 0261 0262 // END KGamePlaybackEvent 0263 0264 #include "moc_kgamesound.cpp"