// Copyright (c) 2023 Dominic Masters // // This software is released under the MIT License. // https://opensource.org/licenses/MIT #include "AudioSource.hpp" #include "game/DawnGame.hpp" using namespace Dawn; AudioSource::AudioSource(SceneItem *i) : SceneItemComponent(i) { } void AudioSource::fillBuffer(ALuint buffer) { // Read in some data uint8_t *temporaryBuffer = (uint8_t*)memoryAllocate(AUDIO_SOURCE_BUFFER_SIZE); /* OK A big writeup here because things get complicated and I am sure that I will need to revist this. First thing to note is that OpenAL requires that the data buffered is a multiple of the frame size. Frame Size from what I can tell is; dataSize * channelCount e.g. sizeof(int16_t) * stereo = 4 EDIT 23017 - Something about how OpenAL handles things means the following statement is probably false, I'm leaving it here but DON'T READ IT PLEASE Additionally, we need to handle cases where the buffer "wraps around". So let's say we are at 8179/8192 bytes, we go to read "128 bytes", we need to first read the remaining 13 bytes and then the remaining (128-13)bytes. After that we need to do our frame size division, and make sure we only advance our buffer by that many bytes, so that the next buffer can pick up exactly where this buffer left off. */ // Try to read the entire buffer this->data->loader.setPosition(this->data->bufferStart + bufferPosition); size_t readLength = this->data->loader.read( temporaryBuffer, AUDIO_SOURCE_BUFFER_SIZE ); size_t bufferLength = (readLength / this->data->frameSize) * this->data->frameSize; // Did we run out of data? size_t newPosition; if(readLength < AUDIO_SOURCE_BUFFER_SIZE) { newPosition = 0; } else { newPosition = bufferPosition + bufferLength; } // Determine format, may do this somewhere else for efficiency sake. ALenum format; switch(this->data->channelCount) { case 2: format = AL_FORMAT_STEREO16; break; case 1: format = AL_FORMAT_MONO16; break; default: assertUnreachable(); } // Buffer data alBufferData( buffer, format, temporaryBuffer, bufferLength, (ALsizei)this->data->sampleRate ); // Cleanup memoryFree(temporaryBuffer); bufferPosition = newPosition; } void AudioSource::detatchBuffers() { ALint buffersQueued; ALuint buffersUnqueued[AUDIO_SOURCE_BUFFER_COUNT]; alGetSourcei(this->source, AL_BUFFERS_QUEUED, &buffersQueued); if(buffersQueued > 0) { alSourceUnqueueBuffers(this->source, buffersQueued, buffersUnqueued); } } void AudioSource::attachBuffers() { alSourceQueueBuffers(this->source, AUDIO_SOURCE_BUFFER_COUNT, this->buffers); } void AudioSource::onStart() { // Create source and buffers alGenSources((ALuint)1, &this->source); alGenBuffers((ALuint)AUDIO_SOURCE_BUFFER_COUNT, this->buffers); // Set up the source positions glm::vec3 position = this->transform->getWorldPosition(); alSource3f(this->source, AL_POSITION, position.x, position.y, position.z); alSource3f(this->source, AL_VELOCITY, 0, 0, 0); // In future these will probably be tied to the layer alSourcef(this->source, AL_PITCH, 1); alSourcef(this->source, AL_GAIN, 1); // Mark as ready. this->ready = true; // Listen for events this->transform->eventTransformUpdated.addListener(this, &AudioSource::onTransformUpdate); this->getScene()->eventSceneUpdate.addListener(this, &AudioSource::onSceneUpdate); } AudioSourceState AudioSource::getRealState() { return this->internalState; } void AudioSource::rewind() { this->bufferPosition = 0; } AudioAsset * AudioSource::getAudioData() { return this->data; } void AudioSource::setAudioData(AudioAsset *data) { this->data = data; this->rewind(); } enum AudioSourcePlayMode AudioSource::getPlayMode() { return this->playMode; } void AudioSource::setPlayMode(enum AudioSourcePlayMode playMode) { this->playMode = playMode; } void AudioSource::onDispose() { assertTrue(this->ready); this->ready = false; this->getScene()->eventSceneUpdate.removeListener(this, &AudioSource::onSceneUpdate); this->transform->eventTransformUpdated.removeListener(this, &AudioSource::onTransformUpdate); alDeleteSources((ALuint)1, &this->source); alDeleteBuffers((ALuint)AUDIO_SOURCE_BUFFER_COUNT, this->buffers); } void AudioSource::onSceneUpdate() { ALuint bufferId; ALint buffersProcessed; assertTrue(this->ready); assertTrue(this->data != nullptr); assertTrue(this->data->loaded); // What is the user trying to do? if(this->state == AUDIO_SOURCE_STATE_PLAYING) { // Handle the special game-paused music-paused state. if(this->playMode == AUDIO_PLAY_MODE_UNPAUSED && this->getGame()->timeManager.isPaused) { if(this->internalState == AUDIO_SOURCE_STATE_PLAYING) { // Functionally, this is the same as pausing alSourcePause(this->source); this->internalState = AUDIO_SOURCE_STATE_PAUSED; } return;// Do nothing else, at all. } // They are trying to play. We need to check if we are already playing if(this->internalState == AUDIO_SOURCE_STATE_PLAYING) { // We are good to continue buffering audio data. buffersProcessed = 0; alGetSourcei(this->source, AL_BUFFERS_PROCESSED, &buffersProcessed); while(buffersProcessed > 0) { alSourceUnqueueBuffers(this->source, 1, &bufferId); this->fillBuffer(bufferId); alSourceQueueBuffers(this->source, 1, &bufferId); buffersProcessed--; // Now, before we go to the next buffer let's see if we need to // update the internal state if(this->bufferPosition != 0) continue; // We reached the end of the buffer whilst filling. What to do now? if(this->loop) { // We're looping so it's fine this->eventLooped.invoke(); continue; } // We are NOT looping, so we need to finish here. this->state = AUDIO_SOURCE_STATE_STOPPED; this->eventFinished.invoke(); this->bufferPosition = 0; buffersProcessed = 0; } } else if(this->internalState == AUDIO_SOURCE_STATE_STOPPED) { // Not yet playing. First thing we need to do is fill the buffers. for(int32_t i = 0; i < AUDIO_SOURCE_BUFFER_COUNT; i++) { this->fillBuffer(this->buffers[i]); } // Now attach the buffers this->attachBuffers(); // And begin playing alSourcePlay(this->source); this->internalState = AUDIO_SOURCE_STATE_PLAYING; this->eventPlaying.invoke(); } else if(AUDIO_SOURCE_STATE_PAUSED) { // Request to resume alSourcePlay(this->source); this->eventResumed.invoke(); this->internalState = AUDIO_SOURCE_STATE_PLAYING; } else { assertUnreachable(); } } else if(this->state == AUDIO_SOURCE_STATE_PAUSED) { // Pause has been requested. if(this->internalState != AUDIO_SOURCE_STATE_PLAYING) return; // We are playing something, pause it alSourcePause(this->source); this->internalState = AUDIO_SOURCE_STATE_PAUSED; this->eventPaused.invoke(); } else if(this->state == AUDIO_SOURCE_STATE_STOPPED) { if(this->internalState == AUDIO_SOURCE_STATE_STOPPED) return; // Release the buffers alSourceStop(this->source); this->detatchBuffers(); this->internalState = AUDIO_SOURCE_STATE_STOPPED; this->eventStopped.invoke(); this->bufferPosition = 0; } else { assertUnreachable(); } } void AudioSource::onTransformUpdate() { glm::vec3 position = this->transform->getWorldPosition(); alSource3f(this->source, AL_POSITION, position.x, position.y, position.z); }