From 821074dfc4cb09dd2a372aa88bc6d5a3ac1c8c40 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 17 Jan 2023 19:34:47 -0800 Subject: [PATCH] Perfected Audio --- src/dawn/asset/AssetManager.hpp | 1 + src/dawn/asset/assets/AudioAsset.cpp | 77 +++++ src/dawn/asset/assets/AudioAsset.hpp | 33 ++ src/dawn/asset/assets/CMakeLists.txt | 1 + .../components/scene/SubSceneController.hpp | 2 +- src/dawnopenal/CMakeLists.txt | 1 - src/dawnopenal/audio/AudioData.cpp | 82 ----- src/dawnopenal/audio/AudioData.hpp | 31 -- src/dawnopenal/audio/CMakeLists.txt | 1 - src/dawnopenal/dawnopenal.hpp | 3 +- .../scene/components/audio/AudioSource.cpp | 309 +++++++++++------- .../scene/components/audio/AudioSource.hpp | 79 ++--- src/dawnpokergame/CMakeLists.txt | 4 + src/dawnpokergame/scenes/Scene_1.hpp | 26 +- src/dawntools/CMakeLists.txt | 1 + src/dawntools/audio/CMakeLists.txt | 15 + src/dawntools/audio/audiogen/CMakeLists.txt | 23 ++ src/dawntools/audio/audiogen/main.cpp | 78 +++++ 18 files changed, 459 insertions(+), 308 deletions(-) create mode 100644 src/dawn/asset/assets/AudioAsset.cpp create mode 100644 src/dawn/asset/assets/AudioAsset.hpp delete mode 100644 src/dawnopenal/audio/AudioData.cpp delete mode 100644 src/dawnopenal/audio/AudioData.hpp create mode 100644 src/dawntools/audio/CMakeLists.txt create mode 100644 src/dawntools/audio/audiogen/CMakeLists.txt create mode 100644 src/dawntools/audio/audiogen/main.cpp diff --git a/src/dawn/asset/AssetManager.hpp b/src/dawn/asset/AssetManager.hpp index 5cafab58..d4eccc36 100644 --- a/src/dawn/asset/AssetManager.hpp +++ b/src/dawn/asset/AssetManager.hpp @@ -7,6 +7,7 @@ #include "Asset.hpp" #include "util/array.hpp" +#include "assets/AudioAsset.hpp" #include "assets/LanguageAsset.hpp" #include "assets/TextureAsset.hpp" #include "assets/TilesetAsset.hpp" diff --git a/src/dawn/asset/assets/AudioAsset.cpp b/src/dawn/asset/assets/AudioAsset.cpp new file mode 100644 index 00000000..41219db3 --- /dev/null +++ b/src/dawn/asset/assets/AudioAsset.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "AudioAsset.hpp" + +using namespace Dawn; + +AudioAsset::AudioAsset(AssetManager *man, std::string name) : + Asset(man, name), + loader(name + ".audio") +{ + +} + +void AudioAsset::updateSync() { + if(this->state != 0x07) return; + + // THIS WILL DEFINITELY CHANGE + this->state = 0x08; + this->loaded = true; + this->eventLoaded.invoke(); +} + +void AudioAsset::updateAsync() { + if(this->state != 0x00) return; + char *start, *end; + char buffer[1024]; + + // Open asset for reading + this->state = 0x01; + this->loader.open(); + + // Parse header data. + this->state = 0x02; + this->loader.read((uint8_t *)buffer, 1024); + + // Channel count + this->state = 0x03; + start = buffer; + end = strchr(start, '|'); + *end = '\0'; + this->channelCount = atoi(start); + assertTrue(this->channelCount > 0); + + // Sample Rate + this->state = 0x04; + start = end + 1; + end = strchr(start, '|'); + *end = '\0'; + this->sampleRate = (uint32_t)strtoul(start, NULL, 10); + assertTrue(this->sampleRate > 0); + + // Number of samples per channel + this->state = 0x05; + start = end + 1; + end = strchr(start, '|'); + *end = '\0'; + this->samplesPerChannel = atoi(start); + assertTrue(this->samplesPerChannel > 0); + + // Total Data Length + this->state = 0x06; + start = end + 1; + end = strchr(start, '|'); + *end = '\0'; + this->bufferSize = (size_t)atoll(start); + assertTrue(this->bufferSize > 0); + + // Determine frame size + this->frameSize = sizeof(int16_t) * this->channelCount; + + // Indicated start of data + this->state = 0x07; + this->bufferStart = end - buffer; +} \ No newline at end of file diff --git a/src/dawn/asset/assets/AudioAsset.hpp b/src/dawn/asset/assets/AudioAsset.hpp new file mode 100644 index 00000000..71cb11d1 --- /dev/null +++ b/src/dawn/asset/assets/AudioAsset.hpp @@ -0,0 +1,33 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "../Asset.hpp" +#include "../AssetLoader.hpp" + +namespace Dawn { + class AudioSource; + + class AudioAsset : public Asset { + protected: + AssetLoader loader; + uint8_t state = 0x00; + + int32_t channelCount; + uint32_t sampleRate; + int32_t samplesPerChannel; + size_t bufferSize; + size_t bufferStart; + size_t frameSize; + + public: + AudioAsset(AssetManager *man, std::string name); + + void updateSync() override; + void updateAsync() override; + + friend class AudioSource; + }; +} \ No newline at end of file diff --git a/src/dawn/asset/assets/CMakeLists.txt b/src/dawn/asset/assets/CMakeLists.txt index ffa28cda..6a1e322e 100644 --- a/src/dawn/asset/assets/CMakeLists.txt +++ b/src/dawn/asset/assets/CMakeLists.txt @@ -6,6 +6,7 @@ # Sources target_sources(${DAWN_TARGET_NAME} PRIVATE + AudioAsset.cpp LanguageAsset.cpp TextureAsset.cpp TilesetAsset.cpp diff --git a/src/dawn/scene/components/scene/SubSceneController.hpp b/src/dawn/scene/components/scene/SubSceneController.hpp index 03eabd91..1b7d6801 100644 --- a/src/dawn/scene/components/scene/SubSceneController.hpp +++ b/src/dawn/scene/components/scene/SubSceneController.hpp @@ -15,7 +15,7 @@ namespace Dawn { void onSceneUnpausedUpdate(); public: - bool_t onlyUpdateUnpaused = true; + bool_t onlyUpdateUnpaused = false; SubSceneController(SceneItem *item); diff --git a/src/dawnopenal/CMakeLists.txt b/src/dawnopenal/CMakeLists.txt index 39e29770..9936ab69 100644 --- a/src/dawnopenal/CMakeLists.txt +++ b/src/dawnopenal/CMakeLists.txt @@ -7,7 +7,6 @@ target_link_libraries(${DAWN_TARGET_NAME} PUBLIC OpenAL - AudioFile ) # Includes diff --git a/src/dawnopenal/audio/AudioData.cpp b/src/dawnopenal/audio/AudioData.cpp deleted file mode 100644 index 43a137a8..00000000 --- a/src/dawnopenal/audio/AudioData.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2023 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#include "AudioData.hpp" - -using namespace Dawn; - -AudioData::AudioData() { - -} - -void AudioData::init() { - alGenBuffers((ALsizei)1, &this->buffer); - - // Test - AudioFile audioFile; - audioFile.load ("C:\\sample.wav"); - audioFile.printSummary(); - - ALenum format; - switch(audioFile.getBitDepth()) { - case 16: - switch(audioFile.getNumChannels()) { - case 2: - format = AL_FORMAT_STEREO16; - break; - case 1: - format = AL_FORMAT_MONO16; - break; - default: - assertUnreachable(); - } - break; - - case 8: - switch(audioFile.getNumChannels()) { - case 2: - format = AL_FORMAT_STEREO8; - break; - case 1: - format = AL_FORMAT_MONO8; - break; - default: - assertUnreachable(); - } - break; - - default: - assertUnreachable(); - } - - format = AL_FORMAT_MONO16; - - std::vector data; - for (int i = 0; i < audioFile.getNumSamplesPerChannel(); i++) { - // for(int y = 0; y < audioFile.getNumChannels(); y++) { - for(int y = 0; y < 1; y++) { - double sample = audioFile.samples[y][i]; - sample = mathClamp(sample, -1., 1.); - auto q = static_cast (sample * 32767.); - - uint8_t bytes[2]; - bytes[0] = (q >> 8) & 0xFF; - bytes[1] = q & 0xFF; - data.push_back(bytes[1]); - data.push_back(bytes[0]); - } - } - alBufferData(buffer, format, &data[0], data.size(), audioFile.getSampleRate()); - - - - - - this->ready = true; -} - -AudioData::~AudioData() { - if(this->ready) alDeleteBuffers((ALsizei)1, &this->buffer); -} \ No newline at end of file diff --git a/src/dawnopenal/audio/AudioData.hpp b/src/dawnopenal/audio/AudioData.hpp deleted file mode 100644 index 853db7f5..00000000 --- a/src/dawnopenal/audio/AudioData.hpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2023 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#pragma once -#include "dawnlibs.hpp" -#include "dawnopenal.hpp" -#include "assert/assert.hpp" -#include "util/mathutils.hpp" - -namespace Dawn { - class AudioSource; - - class AudioData { - private: - ALuint buffer; - bool_t ready = false; - ALenum format; - - public: - - AudioData(); - - void init(); - - ~AudioData(); - - friend class AudioSource; - }; -} \ No newline at end of file diff --git a/src/dawnopenal/audio/CMakeLists.txt b/src/dawnopenal/audio/CMakeLists.txt index cdfb11f6..5f432591 100644 --- a/src/dawnopenal/audio/CMakeLists.txt +++ b/src/dawnopenal/audio/CMakeLists.txt @@ -6,6 +6,5 @@ # Sources target_sources(${DAWN_TARGET_NAME} PRIVATE - AudioData.cpp AudioManager.cpp ) \ No newline at end of file diff --git a/src/dawnopenal/dawnopenal.hpp b/src/dawnopenal/dawnopenal.hpp index a615dfa2..c34a9ffa 100644 --- a/src/dawnopenal/dawnopenal.hpp +++ b/src/dawnopenal/dawnopenal.hpp @@ -5,5 +5,4 @@ #pragma once #include -#include -#include "AudioFile.h" \ No newline at end of file +#include \ No newline at end of file diff --git a/src/dawnopenal/scene/components/audio/AudioSource.cpp b/src/dawnopenal/scene/components/audio/AudioSource.cpp index 1f36a426..85f03f2b 100644 --- a/src/dawnopenal/scene/components/audio/AudioSource.cpp +++ b/src/dawnopenal/scene/components/audio/AudioSource.cpp @@ -12,146 +12,120 @@ AudioSource::AudioSource(SceneItem *i) : SceneItemComponent(i) { } -void AudioSource::updateAudioSource() { - if(!this->ready || data == nullptr || !data->ready) return; + +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 - switch(this->requestedAudioState) { - // When the engine wants the sound to stop - case AUDIO_SOURCE_STATE_STOPPED: - if(this->currentAudioState == AUDIO_SOURCE_STATE_STOPPED) return; - this->currentAudioState = AUDIO_SOURCE_STATE_STOPPED; - alSourceStop(this->source); - this->eventStopped.invoke(); + 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; - - // When the engine wants the sound to pause - case AUDIO_SOURCE_STATE_PAUSED: - if(this->currentAudioState != AUDIO_SOURCE_STATE_PLAYING) return; - this->currentAudioState = AUDIO_SOURCE_STATE_PAUSED; - alSourcePause(this->source); - this->eventPaused.invoke(); + case 1: + format = AL_FORMAT_MONO16; break; - - // When the engine wants the sound to play... - case AUDIO_SOURCE_STATE_PLAYING: - switch(this->playMode) { - // This should be playing, right now. - case AUDIO_PLAY_MODE_ALWAYS: - if(this->currentAudioState == AUDIO_SOURCE_STATE_PLAYING) return; - this->currentAudioState = AUDIO_SOURCE_STATE_PLAYING; - alSourcePlay(this->source); - this->eventPlaying.invoke(); - break; - - case AUDIO_PLAY_MODE_UNPAUSED: - if(this->getGame()->timeManager.isPaused) { - if(this->currentAudioState != AUDIO_SOURCE_STATE_PLAYING) return; - this->currentAudioState = AUDIO_SOURCE_STATE_PAUSED; - alSourcePause(this->source); - this->eventPaused.invoke(); - } else { - if(this->currentAudioState == AUDIO_SOURCE_STATE_PLAYING) return; - this->currentAudioState = AUDIO_SOURCE_STATE_PLAYING; - alSourcePlay(this->source); - this->eventPlaying.invoke(); - } - break; - - default: - assertUnreachable(); - } - 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->getLocalPosition(); + 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); - glm::vec3 position = this->transform->getLocalPosition(); - alSource3f(this->source, AL_POSITION, position.x, position.y, position.z); - - // Velocity is always zero for now - alSource3f(this->source, AL_VELOCITY, 0, 0, 0); - - // Looping - alSourcei(this->source, AL_LOOPING, this->loop); - - // Source - if(this->data != nullptr && this->data->ready) { - alSourcei(source, AL_BUFFER, this->data->buffer); - } - - // Ready. + // Mark as ready. this->ready = true; - // Update the source now we are ready. - this->updateAudioSource(); - // Listen for events this->transform->eventTransformUpdated.addListener(this, &AudioSource::onTransformUpdate); - this->getGame()->timeManager.eventTimePaused.addListener(this, &AudioSource::onGamePause); - this->getGame()->timeManager.eventTimeResumed.addListener(this, &AudioSource::onGameUnpause); - -} - -bool_t AudioSource::isLooping() { - return this->loop; -} - -void AudioSource::setLoop(bool_t loop) { - this->loop = loop; - if(this->ready) alSourcei(this->source, AL_LOOPING, loop); -} - -AudioSourceState AudioSource::getPlayingState() { - return this->requestedAudioState; -} - -void AudioSource::setPlayingState(enum AudioSourceState state) { - this->requestedAudioState = state; - this->updateAudioSource(); + this->getScene()->eventSceneUpdate.addListener(this, &AudioSource::onSceneUpdate); } AudioSourceState AudioSource::getRealState() { - return this->currentAudioState; + return this->internalState; } -void AudioSource::play() { - this->setPlayingState(AUDIO_SOURCE_STATE_PLAYING); +void AudioSource::rewind() { + this->bufferPosition = 0; } -void AudioSource::pause() { - this->setPlayingState(AUDIO_SOURCE_STATE_PAUSED); -} - -void AudioSource::stop() { - this->setPlayingState(AUDIO_SOURCE_STATE_STOPPED); -} - -// void AudioSource::rewind() { -// if(this->ready && data != nullptr && data->ready) { -// alSourceRewind(this->source); -// } -// } - -AudioData * AudioSource::getAudioData() { +AudioAsset * AudioSource::getAudioData() { return this->data; } -void AudioSource::setAudioData(AudioData *data) { - // Set up source. - if(this->ready && data != nullptr && data->ready) { - alSourcei(source, AL_BUFFER, data->buffer); - } +void AudioSource::setAudioData(AudioAsset *data) { this->data = data; - this->updateAudioSource(); + this->rewind(); } enum AudioSourcePlayMode AudioSource::getPlayMode() { @@ -160,27 +134,116 @@ enum AudioSourcePlayMode AudioSource::getPlayMode() { void AudioSource::setPlayMode(enum AudioSourcePlayMode playMode) { this->playMode = playMode; - this->updateAudioSource(); } void AudioSource::onDispose() { - this->transform->eventTransformUpdated.removeListener(this, &AudioSource::onTransformUpdate); - this->getGame()->timeManager.eventTimePaused.removeListener(this, &AudioSource::onGamePause); - this->getGame()->timeManager.eventTimeResumed.removeListener(this, &AudioSource::onGameUnpause); - + 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); -} - -void AudioSource::onGamePause() { - this->updateAudioSource(); -} - -void AudioSource::onGameUnpause() { - this->updateAudioSource(); } \ No newline at end of file diff --git a/src/dawnopenal/scene/components/audio/AudioSource.hpp b/src/dawnopenal/scene/components/audio/AudioSource.hpp index 7488b0ca..be202e6f 100644 --- a/src/dawnopenal/scene/components/audio/AudioSource.hpp +++ b/src/dawnopenal/scene/components/audio/AudioSource.hpp @@ -6,7 +6,10 @@ #pragma once #include "dawnopenal.hpp" #include "scene/SceneItemComponent.hpp" -#include "audio/AudioData.hpp" +#include "asset/assets/AudioAsset.hpp" + +#define AUDIO_SOURCE_BUFFER_COUNT 3 +#define AUDIO_SOURCE_BUFFER_SIZE 8192 namespace Dawn { enum AudioSourceState { @@ -24,13 +27,13 @@ namespace Dawn { private: ALuint source; bool_t ready = false; + ALuint buffers[AUDIO_SOURCE_BUFFER_COUNT]; + size_t bufferPosition = 0; // Settings - bool_t loop = false; - enum AudioSourceState requestedAudioState = AUDIO_SOURCE_STATE_STOPPED; - enum AudioSourceState currentAudioState = AUDIO_SOURCE_STATE_STOPPED; + enum AudioSourceState internalState = AUDIO_SOURCE_STATE_STOPPED; int32_t layer = 0; - AudioData *data = nullptr; + AudioAsset *data = nullptr; AudioSourcePlayMode playMode = AUDIO_PLAY_MODE_UNPAUSED; /** @@ -38,15 +41,24 @@ namespace Dawn { */ void updateAudioSource(); + void fillBuffer(ALuint buffer); + void detatchBuffers(); + void attachBuffers(); + // Events + void onSceneUpdate(); void onTransformUpdate(); - void onGamePause(); - void onGameUnpause(); public: + bool_t loop = false; + enum AudioSourceState state = AUDIO_SOURCE_STATE_STOPPED; + Event<> eventPlaying; + Event<> eventResumed; Event<> eventPaused; Event<> eventStopped; + Event<> eventFinished; + Event<> eventLooped; /** * Creates an Audio Source item component. @@ -55,38 +67,6 @@ namespace Dawn { */ AudioSource(SceneItem *item); - /** - * Returns whether or not the audio source is set to loop or not. - * - * @return True if the source is looping otherwise false. - */ - bool_t isLooping(); - - /** - * Sets whether the audio source should loop or not. - * - * @param loop Loop or not. - */ - void setLoop(bool_t loop); - - /** - * Returns the requested playing state of the source. Due to some weird - * limitations of hardware, as well as some nice controls to decide how - * sources should play, this is basically "What you would like the sound - * to do" mode, so you really should be controlling this value where - * possible. - * - * @return The requested playing state. - */ - AudioSourceState getPlayingState(); - - /** - * Sets the ideal playing state you want of this audio source. - * - * @param state State to set. - */ - void setPlayingState(enum AudioSourceState state); - /** * Returns the real current state of the sound. Refer to getPlayingState * but basically the actually requested play state and what the hardware @@ -97,33 +77,18 @@ namespace Dawn { */ AudioSourceState getRealState(); - /** - * Plays the audio source. - */ - void play(); - - /** - * Pauses the audio source. - */ - void pause(); - - /** - * Stops the audio source. - */ - void stop(); - /** * Rewinds the audio source. At the time of writing this comment (23017) I * am unsure of the behaviour when rewinding whilst the source is active. */ - // void rewind(); + void rewind(); /** * Returns the current audio data for this source. * * @return Audio data that is atached to this source. */ - AudioData * getAudioData(); + AudioAsset * getAudioData(); /** * Sets the audio data for this source. Currently switching between the @@ -131,7 +96,7 @@ namespace Dawn { * * @param data Data to set. */ - void setAudioData(AudioData *data); + void setAudioData(AudioAsset *data); /** * Returns the play mode for the source. diff --git a/src/dawnpokergame/CMakeLists.txt b/src/dawnpokergame/CMakeLists.txt index c23205df..70b1f645 100644 --- a/src/dawnpokergame/CMakeLists.txt +++ b/src/dawnpokergame/CMakeLists.txt @@ -36,6 +36,8 @@ tool_tileset(tileset_death texture_death ${DIR_GAME_ASSETS}/characters/death/she tool_truetype(truetype_alice ${DIR_GAME_ASSETS}/font/Alice-Regular.ttf truetype_alice 2048 2048 120) +tool_audio(audio_test borrowed/sample.wav) + add_dependencies(${DAWN_TARGET_NAME} language_en language_jp @@ -45,4 +47,6 @@ add_dependencies(${DAWN_TARGET_NAME} truetype_alice texture_test + + audio_test ) \ No newline at end of file diff --git a/src/dawnpokergame/scenes/Scene_1.hpp b/src/dawnpokergame/scenes/Scene_1.hpp index 78a32dea..77a86439 100644 --- a/src/dawnpokergame/scenes/Scene_1.hpp +++ b/src/dawnpokergame/scenes/Scene_1.hpp @@ -7,7 +7,6 @@ #include "scenes/PixelVNScene.hpp" #include "scenes/Scene_2.hpp" #include "prefabs/characters/DeathPrefab.hpp" - #include "scene/components/audio/AudioListener.hpp" #include "scene/components/audio/AudioSource.hpp" @@ -15,6 +14,12 @@ namespace Dawn { class Scene_1 : public PixelVNScene { protected: DeathPrefab *death; + AudioSource *source; + + void onFinished() { + std::cout << "Finished" << std::endl; + source->rewind(); + } void vnStage() override { PixelVNScene::vnStage(); @@ -23,15 +28,15 @@ namespace Dawn { // this->death->vnCharacter.setOpacity(0); auto sourceItem = this->createSceneItem(); - auto source = sourceItem->addComponent(); + source = sourceItem->addComponent(); source->transform->setLocalPosition(glm::vec3(1, 0, 0)); + source->eventFinished.addListener(this, &Scene_1::onFinished); + + auto audio = this->game->assetManager.get("audio_test"); - auto data = new AudioData(); - data->init(); - - source->setAudioData(data); - source->play(); - source->setLoop(true); + source->setAudioData(audio); + source->loop = true; + source->state = AUDIO_SOURCE_STATE_PLAYING; } void onSceneEnded() { @@ -48,8 +53,8 @@ namespace Dawn { auto start = new VisualNovelPauseEvent(vnManager, 1.0f); start - ->then(new VisualNovelTextboxEvent(vnManager, nullptr, "scene.1.1")) - ->then(new VisualNovelCallbackEvent(vnManager, this, &Scene_1::onSceneEnded)) + // ->then(new VisualNovelTextboxEvent(vnManager, nullptr, "scene.1.1")) + // ->then(new VisualNovelCallbackEvent(vnManager, this, &Scene_1::onSceneEnded)) ; return start; @@ -64,6 +69,7 @@ namespace Dawn { auto man = &this->game->assetManager; std::vector assets = PixelVNScene::getRequiredAssets(); vectorAppend(&assets, DeathPrefab::getRequiredAssets(man)); + assets.push_back(man->get("audio_test")); return assets; } }; diff --git a/src/dawntools/CMakeLists.txt b/src/dawntools/CMakeLists.txt index a6dfa96e..d73b2021 100644 --- a/src/dawntools/CMakeLists.txt +++ b/src/dawntools/CMakeLists.txt @@ -3,6 +3,7 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT +add_subdirectory(audio) add_subdirectory(display) add_subdirectory(file) add_subdirectory(locale) \ No newline at end of file diff --git a/src/dawntools/audio/CMakeLists.txt b/src/dawntools/audio/CMakeLists.txt new file mode 100644 index 00000000..cda9d556 --- /dev/null +++ b/src/dawntools/audio/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(audiogen) + +# Texture Tool +function(tool_audio target in) + add_custom_target(${target} + COMMAND audiogen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}/${target}" + COMMENT "Generating audio ${target} from ${in}" + DEPENDS audiogen + ) +endfunction() \ No newline at end of file diff --git a/src/dawntools/audio/audiogen/CMakeLists.txt b/src/dawntools/audio/audiogen/CMakeLists.txt new file mode 100644 index 00000000..ce2e048a --- /dev/null +++ b/src/dawntools/audio/audiogen/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Texture Build Tool +project(audiogen VERSION 1.0) +add_executable(audiogen) +target_sources(audiogen + PRIVATE + main.cpp + ../../utils/file.c +) +target_include_directories(audiogen + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../ + ${CMAKE_CURRENT_LIST_DIR} +) +target_link_libraries(audiogen + PUBLIC + ${DAWN_BUILD_HOST_LIBS} + AudioFile +) \ No newline at end of file diff --git a/src/dawntools/audio/audiogen/main.cpp b/src/dawntools/audio/audiogen/main.cpp new file mode 100644 index 00000000..0172c482 --- /dev/null +++ b/src/dawntools/audio/audiogen/main.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +extern "C" { + #include "../../utils/common.h" + #include "../../utils/file.h" +} +#include "AudioFile.h" + +int main(int argc, char *argv[]) { + FILE *fileOut; + char buffer[FILENAME_MAX]; + size_t bufferLength = 0; + + if(argc != 3) { + printf("Invalid number of arguments\n"); + return 1; + } + + fileNormalizeSlashes(argv[1]); + fileNormalizeSlashes(argv[2]); + std::string strFileIn = std::string(argv[1]); + std::string strFileOut = std::string(argv[2]); + + buffer[0] = '\0'; + sprintf(buffer, "%s.audio", strFileOut.c_str()); + fileOut = fopen(buffer, "rb"); + if(fileOut != NULL) { + fclose(fileOut); + return 0; + } + + // Load input file + AudioFile audioFile; + if(!audioFile.load(strFileIn)) { + printf("Failed to load audio file.\n"); + return 1; + } + + // Open Output File + fileMkdirp(buffer); + fileOut = fopen(buffer, "wb"); + if(fileOut == NULL) { + printf("Failed to create output file\n"); + return 1; + } + + // Write header + buffer[0] = '\0'; + sprintf(buffer, "%i|%i|%i|%i|", + audioFile.getNumChannels(), + audioFile.getSampleRate(), + audioFile.getNumSamplesPerChannel(), + audioFile.getNumSamplesPerChannel() * audioFile.getNumChannels()*( + sizeof(int16_t) / sizeof(uint8_t) + ) + ); + bufferLength = strlen(buffer); + fwrite(buffer, sizeof(char), bufferLength, fileOut); + + // Convert Data to 16 bit audio + for (int i = 0; i < audioFile.getNumSamplesPerChannel(); i++) { + for(int y = 0; y < audioFile.getNumChannels(); y++) { + double_t sample = audioFile.samples[y][i]; + sample = sample < -1 ? -1 : sample > 1 ? 1 : sample; + auto q = static_cast (sample * 32767.); + + buffer[0] = (q >> 8) & 0xFF; + buffer[1] = q & 0xFF; + fwrite(buffer, sizeof(uint8_t), 2, fileOut); + } + } + + fclose(fileOut); + return 0; +} \ No newline at end of file