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"