249 lines
7.6 KiB
C++
249 lines
7.6 KiB
C++
// 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);
|
|
} |