#include "ALAudio.hpp" #include #include #include "../../debug/Logger.hpp" #include "alutil.hpp" static debug::Logger logger("al-audio"); using namespace audio; ALSound::ALSound( ALAudio* al, uint buffer, const std::shared_ptr& pcm, bool keepPCM ) : al(al), buffer(buffer) { duration = pcm->getDuration(); if (keepPCM) { this->pcm = pcm; } } ALSound::~ALSound() { al->freeBuffer(buffer); buffer = 0; } std::unique_ptr ALSound::newInstance(int priority, int channel) const { uint source = al->getFreeSource(); if (source == 0) { return nullptr; } AL_CHECK(alSourcei(source, AL_BUFFER, buffer)); auto speaker = std::make_unique(al, source, priority, channel); speaker->duration = duration; return speaker; } ALStream::ALStream( ALAudio* al, std::shared_ptr source, bool keepSource ) : al(al), source(std::move(source)), keepSource(keepSource) { } ALStream::~ALStream() { bindSpeaker(0); source = nullptr; while (!unusedBuffers.empty()) { al->freeBuffer(unusedBuffers.front()); unusedBuffers.pop(); } } std::shared_ptr ALStream::getSource() const { if (keepSource) { return source; } else { return nullptr; } } bool ALStream::preloadBuffer(uint buffer, bool loop) { size_t read = source->readFully(this->buffer, BUFFER_SIZE, loop); if (!read) return false; ALenum format = AL::to_al_format(source->getChannels(), source->getBitsPerSample()); AL_CHECK(alBufferData( buffer, format, this->buffer, read, source->getSampleRate() )); return true; } std::unique_ptr ALStream::createSpeaker(bool loop, int channel) { this->loop = loop; uint source = al->getFreeSource(); if (source == 0) { return nullptr; } for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) { uint buffer = al->getFreeBuffer(); if (!preloadBuffer(buffer, loop)) { break; } AL_CHECK(alSourceQueueBuffers(source, 1, &buffer)); } return std::make_unique(al, source, PRIORITY_HIGH, channel); } void ALStream::bindSpeaker(speakerid_t speaker) { auto sp = audio::get_speaker(this->speaker); if (sp) { sp->stop(); } this->speaker = speaker; sp = audio::get_speaker(speaker); if (sp) { auto alspeaker = dynamic_cast(sp); //FIXME: Potentional null pointer alspeaker->stream = this; //-V522 alspeaker->duration = source->getTotalDuration(); } } speakerid_t ALStream::getSpeaker() const { return speaker; } void ALStream::unqueueBuffers(uint alsource) { uint processed = AL::getSourcei(alsource, AL_BUFFERS_PROCESSED); while (processed--) { uint buffer; AL_CHECK(alSourceUnqueueBuffers(alsource, 1, &buffer)); unusedBuffers.push(buffer); uint bps = source->getBitsPerSample() / 8; uint channels = source->getChannels(); ALint bufferSize; alGetBufferi(buffer, AL_SIZE, &bufferSize); totalPlayedSamples += bufferSize / bps / channels; if (source->isSeekable()) { totalPlayedSamples %= source->getTotalSamples(); } } } uint ALStream::enqueueBuffers(uint alsource) { uint preloaded = 0; if (!unusedBuffers.empty()) { uint buffer = unusedBuffers.front(); if (preloadBuffer(buffer, loop)) { preloaded++; unusedBuffers.pop(); AL_CHECK(alSourceQueueBuffers(alsource, 1, &buffer)); } } return preloaded; } void ALStream::update(double delta) { if (this->speaker == 0) { return; } auto speaker = audio::get_speaker(this->speaker); if (speaker == nullptr) { speaker = 0; return; } ALSpeaker* alspeaker = dynamic_cast(speaker); //FIXME: Potentional null pointer if (alspeaker->stopped) { //-V522 speaker = 0; return; } uint alsource = alspeaker->source; unqueueBuffers(alsource); uint preloaded = enqueueBuffers(alsource); if (speaker->isStopped() && !alspeaker->stopped) { if (preloaded) { speaker->play(); } else { speaker->stop(); } } } duration_t ALStream::getTime() const { uint total = totalPlayedSamples; auto alspeaker = dynamic_cast(audio::get_speaker(this->speaker)); if (alspeaker) { uint alsource = alspeaker->source; total += static_cast(AL::getSourcef(alsource, AL_SAMPLE_OFFSET)); if (source->isSeekable()) { total %= source->getTotalSamples(); } } return total / static_cast(source->getSampleRate()); } void ALStream::setTime(duration_t time) { if (!source->isSeekable()) return; uint sample = time * source->getSampleRate(); source->seek(sample); auto alspeaker = dynamic_cast(audio::get_speaker(this->speaker)); if (alspeaker) { bool paused = alspeaker->isPaused(); AL_CHECK(alSourceStop(alspeaker->source)); unqueueBuffers(alspeaker->source); totalPlayedSamples = sample; enqueueBuffers(alspeaker->source); AL_CHECK(alSourcePlay(alspeaker->source)); if (paused) { AL_CHECK(alSourcePause(alspeaker->source)); } } else { totalPlayedSamples = sample; } } ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel) : al(al), priority(priority), channel(channel), source(source) { } ALSpeaker::~ALSpeaker() { if (source) { stop(); } } void ALSpeaker::update(const Channel* channel) { if (source == 0) return; float gain = this->volume * channel->getVolume(); AL_CHECK(alSourcef(source, AL_GAIN, gain)); if (!paused) { if (isPaused() && !channel->isPaused()) { play(); } else if (isPlaying() && channel->isPaused()) { AL_CHECK(alSourcePause(source)); } } } int ALSpeaker::getChannel() const { return channel; } State ALSpeaker::getState() const { int state = AL::getSourcei(source, AL_SOURCE_STATE, AL_STOPPED); switch (state) { case AL_PLAYING: return State::playing; case AL_PAUSED: return State::paused; default: return State::stopped; } } float ALSpeaker::getVolume() const { return volume; } void ALSpeaker::setVolume(float volume) { this->volume = volume; } float ALSpeaker::getPitch() const { return AL::getSourcef(source, AL_PITCH); } void ALSpeaker::setPitch(float pitch) { AL_CHECK(alSourcef(source, AL_PITCH, pitch)); } bool ALSpeaker::isLoop() const { return AL::getSourcei(source, AL_LOOPING) == AL_TRUE; } void ALSpeaker::setLoop(bool loop) { AL_CHECK(alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE)); } void ALSpeaker::play() { paused = false; stopped = false; auto channel = get_channel(this->channel); AL_CHECK(alSourcef( source, AL_GAIN, volume * channel->getVolume() * get_channel(0)->getVolume() )); AL_CHECK(alSourcePlay(source)); } void ALSpeaker::pause() { paused = true; AL_CHECK(alSourcePause(source)); } void ALSpeaker::stop() { stopped = true; if (source) { AL_CHECK(alSourceStop(source)); uint processed = AL::getSourcei(source, AL_BUFFERS_PROCESSED); while (processed--) { uint buffer; AL_CHECK(alSourceUnqueueBuffers(source, 1, &buffer)); al->freeBuffer(buffer); } AL_CHECK(alSourcei(source, AL_BUFFER, 0)); al->freeSource(source); source = 0; } } duration_t ALSpeaker::getTime() const { if (stream) { return stream->getTime(); } return static_cast(AL::getSourcef(source, AL_SEC_OFFSET)); } duration_t ALSpeaker::getDuration() const { return duration; } void ALSpeaker::setTime(duration_t time) { if (stream) { return stream->setTime(time); } AL_CHECK(alSourcef(source, AL_SEC_OFFSET, static_cast(time))); } void ALSpeaker::setPosition(glm::vec3 pos) { AL_CHECK(alSource3f(source, AL_POSITION, pos.x, pos.y, pos.z)); } glm::vec3 ALSpeaker::getPosition() const { return AL::getSource3f(source, AL_POSITION); } void ALSpeaker::setVelocity(glm::vec3 vel) { AL_CHECK(alSource3f(source, AL_VELOCITY, vel.x, vel.y, vel.z)); } glm::vec3 ALSpeaker::getVelocity() const { return AL::getSource3f(source, AL_VELOCITY); } void ALSpeaker::setRelative(bool relative) { AL_CHECK( alSourcei(source, AL_SOURCE_RELATIVE, relative ? AL_TRUE : AL_FALSE) ); } bool ALSpeaker::isRelative() const { return AL::getSourcei(source, AL_SOURCE_RELATIVE) == AL_TRUE; } int ALSpeaker::getPriority() const { return priority; } ALAudio::ALAudio(ALCdevice* device, ALCcontext* context) : device(device), context(context) { ALCint size; alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size); std::vector attrs(size); alcGetIntegerv(device, ALC_ALL_ATTRIBUTES, size, &attrs[0]); for (size_t i = 0; i < attrs.size(); ++i) { if (attrs[i] == ALC_MONO_SOURCES) { logger.info() << "max mono sources: " << attrs[i + 1]; maxSources = attrs[i + 1]; } } auto devices = getAvailableDevices(); logger.info() << "devices:"; for (auto& name : devices) { logger.info() << " " << name; } } ALAudio::~ALAudio() { for (uint source : allsources) { int state = AL::getSourcei(source, AL_SOURCE_STATE); if (state == AL_PLAYING || state == AL_PAUSED) { AL_CHECK(alSourceStop(source)); } AL_CHECK(alDeleteSources(1, &source)); } for (uint buffer : allbuffers) { AL_CHECK(alDeleteBuffers(1, &buffer)); } AL_CHECK(alcMakeContextCurrent(context)); alcDestroyContext(context); if (!alcCloseDevice(device)) { logger.error() << "device not closed!"; } device = nullptr; context = nullptr; } std::unique_ptr ALAudio::createSound( std::shared_ptr pcm, bool keepPCM ) { auto format = AL::to_al_format(pcm->channels, pcm->bitsPerSample); uint buffer = getFreeBuffer(); AL_CHECK(alBufferData( buffer, format, pcm->data.data(), pcm->data.size(), pcm->sampleRate )); return std::make_unique(this, buffer, pcm, keepPCM); } std::unique_ptr ALAudio::openStream( std::shared_ptr stream, bool keepSource ) { return std::make_unique(this, stream, keepSource); } std::unique_ptr ALAudio::create() { ALCdevice* device = alcOpenDevice(nullptr); if (device == nullptr) return nullptr; ALCcontext* context = alcCreateContext(device, nullptr); if (!alcMakeContextCurrent(context)) { alcCloseDevice(device); return nullptr; } AL_CHECK(); logger.info() << "initialized"; return std::make_unique(device, context); } uint ALAudio::getFreeSource() { if (!freesources.empty()) { uint source = freesources.back(); freesources.pop_back(); return source; } if (allsources.size() == maxSources) { logger.error() << "attempted to create new source, but limit is " << maxSources; return 0; } ALuint id; alGenSources(1, &id); if (!AL_GET_ERROR()) { return 0; } allsources.push_back(id); return id; } uint ALAudio::getFreeBuffer() { if (!freebuffers.empty()) { uint buffer = freebuffers.back(); freebuffers.pop_back(); return buffer; } ALuint id; alGenBuffers(1, &id); if (!AL_GET_ERROR()) { return 0; } allbuffers.push_back(id); return id; } void ALAudio::freeSource(uint source) { freesources.push_back(source); } void ALAudio::freeBuffer(uint buffer) { freebuffers.push_back(buffer); } std::vector ALAudio::getAvailableDevices() const { std::vector devicesVec; const ALCchar* devices; devices = alcGetString(device, ALC_DEVICE_SPECIFIER); if (!AL_GET_ERROR()) { return devicesVec; } const char* ptr = devices; do { devicesVec.emplace_back(ptr); ptr += devicesVec.back().size() + 1; } while (ptr[0]); return devicesVec; } void ALAudio::setListener( glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up ) { ALfloat listenerOri[] = {at.x, at.y, at.z, up.x, up.y, up.z}; AL_CHECK(alListener3f(AL_POSITION, position.x, position.y, position.z)); AL_CHECK(alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z)); AL_CHECK(alListenerfv(AL_ORIENTATION, listenerOri)); AL_CHECK(alListenerf(AL_GAIN, get_channel(0)->getVolume())); } void ALAudio::update(double) { }