Perfected Audio

This commit is contained in:
2023-01-17 19:34:47 -08:00
parent a4ad5a3ac4
commit 821074dfc4
18 changed files with 459 additions and 308 deletions

View File

@ -7,6 +7,7 @@
#include "Asset.hpp" #include "Asset.hpp"
#include "util/array.hpp" #include "util/array.hpp"
#include "assets/AudioAsset.hpp"
#include "assets/LanguageAsset.hpp" #include "assets/LanguageAsset.hpp"
#include "assets/TextureAsset.hpp" #include "assets/TextureAsset.hpp"
#include "assets/TilesetAsset.hpp" #include "assets/TilesetAsset.hpp"

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -6,6 +6,7 @@
# Sources # Sources
target_sources(${DAWN_TARGET_NAME} target_sources(${DAWN_TARGET_NAME}
PRIVATE PRIVATE
AudioAsset.cpp
LanguageAsset.cpp LanguageAsset.cpp
TextureAsset.cpp TextureAsset.cpp
TilesetAsset.cpp TilesetAsset.cpp

View File

@ -15,7 +15,7 @@ namespace Dawn {
void onSceneUnpausedUpdate(); void onSceneUnpausedUpdate();
public: public:
bool_t onlyUpdateUnpaused = true; bool_t onlyUpdateUnpaused = false;
SubSceneController(SceneItem *item); SubSceneController(SceneItem *item);

View File

@ -7,7 +7,6 @@
target_link_libraries(${DAWN_TARGET_NAME} target_link_libraries(${DAWN_TARGET_NAME}
PUBLIC PUBLIC
OpenAL OpenAL
AudioFile
) )
# Includes # Includes

View File

@ -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<double> 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<uint8_t> 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<int16_t> (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);
}

View File

@ -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;
};
}

View File

@ -6,6 +6,5 @@
# Sources # Sources
target_sources(${DAWN_TARGET_NAME} target_sources(${DAWN_TARGET_NAME}
PRIVATE PRIVATE
AudioData.cpp
AudioManager.cpp AudioManager.cpp
) )

View File

@ -6,4 +6,3 @@
#pragma once #pragma once
#include <AL/al.h> #include <AL/al.h>
#include <AL/alc.h> #include <AL/alc.h>
#include "AudioFile.h"

View File

@ -12,146 +12,120 @@ AudioSource::AudioSource(SceneItem *i) : SceneItemComponent(i) {
} }
void AudioSource::updateAudioSource() {
if(!this->ready || data == nullptr || !data->ready) return;
switch(this->requestedAudioState) { void AudioSource::fillBuffer(ALuint buffer) {
// When the engine wants the sound to stop // Read in some data
case AUDIO_SOURCE_STATE_STOPPED: uint8_t *temporaryBuffer = (uint8_t*)memoryAllocate(AUDIO_SOURCE_BUFFER_SIZE);
if(this->currentAudioState == AUDIO_SOURCE_STATE_STOPPED) return; /*
this->currentAudioState = AUDIO_SOURCE_STATE_STOPPED; OK A big writeup here because things get complicated and I am sure that I
alSourceStop(this->source); will need to revist this.
this->eventStopped.invoke();
break;
// When the engine wants the sound to pause First thing to note is that OpenAL requires that the data buffered is a
case AUDIO_SOURCE_STATE_PAUSED: multiple of the frame size. Frame Size from what I can tell is;
if(this->currentAudioState != AUDIO_SOURCE_STATE_PLAYING) return; dataSize * channelCount
this->currentAudioState = AUDIO_SOURCE_STATE_PAUSED; e.g. sizeof(int16_t) * stereo = 4
alSourcePause(this->source);
this->eventPaused.invoke();
break;
// When the engine wants the sound to play... EDIT 23017 - Something about how OpenAL handles things means the following
case AUDIO_SOURCE_STATE_PLAYING: statement is probably false, I'm leaving it here but DON'T READ IT PLEASE
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: Additionally, we need to handle cases where the buffer "wraps around". So
if(this->getGame()->timeManager.isPaused) { let's say we are at 8179/8192 bytes, we go to read "128 bytes", we need to
if(this->currentAudioState != AUDIO_SOURCE_STATE_PLAYING) return; first read the remaining 13 bytes and then the remaining (128-13)bytes.
this->currentAudioState = AUDIO_SOURCE_STATE_PAUSED;
alSourcePause(this->source); After that we need to do our frame size division, and make sure we only
this->eventPaused.invoke(); 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 { } else {
if(this->currentAudioState == AUDIO_SOURCE_STATE_PLAYING) return; newPosition = bufferPosition + bufferLength;
this->currentAudioState = AUDIO_SOURCE_STATE_PLAYING;
alSourcePlay(this->source);
this->eventPlaying.invoke();
} }
break;
// 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: default:
assertUnreachable(); assertUnreachable();
} }
break;
default: // Buffer data
assertUnreachable(); 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() { void AudioSource::onStart() {
// Create source and buffers
alGenSources((ALuint)1, &this->source); 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 // In future these will probably be tied to the layer
alSourcef(this->source, AL_PITCH, 1); alSourcef(this->source, AL_PITCH, 1);
alSourcef(this->source, AL_GAIN, 1); alSourcef(this->source, AL_GAIN, 1);
glm::vec3 position = this->transform->getLocalPosition(); // Mark as ready.
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.
this->ready = true; this->ready = true;
// Update the source now we are ready.
this->updateAudioSource();
// Listen for events // Listen for events
this->transform->eventTransformUpdated.addListener(this, &AudioSource::onTransformUpdate); this->transform->eventTransformUpdated.addListener(this, &AudioSource::onTransformUpdate);
this->getGame()->timeManager.eventTimePaused.addListener(this, &AudioSource::onGamePause); this->getScene()->eventSceneUpdate.addListener(this, &AudioSource::onSceneUpdate);
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();
} }
AudioSourceState AudioSource::getRealState() { AudioSourceState AudioSource::getRealState() {
return this->currentAudioState; return this->internalState;
} }
void AudioSource::play() { void AudioSource::rewind() {
this->setPlayingState(AUDIO_SOURCE_STATE_PLAYING); this->bufferPosition = 0;
} }
void AudioSource::pause() { AudioAsset * AudioSource::getAudioData() {
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() {
return this->data; return this->data;
} }
void AudioSource::setAudioData(AudioData *data) { void AudioSource::setAudioData(AudioAsset *data) {
// Set up source.
if(this->ready && data != nullptr && data->ready) {
alSourcei(source, AL_BUFFER, data->buffer);
}
this->data = data; this->data = data;
this->updateAudioSource(); this->rewind();
} }
enum AudioSourcePlayMode AudioSource::getPlayMode() { enum AudioSourcePlayMode AudioSource::getPlayMode() {
@ -160,27 +134,116 @@ enum AudioSourcePlayMode AudioSource::getPlayMode() {
void AudioSource::setPlayMode(enum AudioSourcePlayMode playMode) { void AudioSource::setPlayMode(enum AudioSourcePlayMode playMode) {
this->playMode = playMode; this->playMode = playMode;
this->updateAudioSource();
} }
void AudioSource::onDispose() { void AudioSource::onDispose() {
this->transform->eventTransformUpdated.removeListener(this, &AudioSource::onTransformUpdate); assertTrue(this->ready);
this->getGame()->timeManager.eventTimePaused.removeListener(this, &AudioSource::onGamePause);
this->getGame()->timeManager.eventTimeResumed.removeListener(this, &AudioSource::onGameUnpause);
this->ready = false; this->ready = false;
this->getScene()->eventSceneUpdate.removeListener(this, &AudioSource::onSceneUpdate);
this->transform->eventTransformUpdated.removeListener(this, &AudioSource::onTransformUpdate);
alDeleteSources((ALuint)1, &this->source); 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() { void AudioSource::onTransformUpdate() {
glm::vec3 position = this->transform->getWorldPosition(); glm::vec3 position = this->transform->getWorldPosition();
alSource3f(this->source, AL_POSITION, position.x, position.y, position.z); alSource3f(this->source, AL_POSITION, position.x, position.y, position.z);
} }
void AudioSource::onGamePause() {
this->updateAudioSource();
}
void AudioSource::onGameUnpause() {
this->updateAudioSource();
}

View File

@ -6,7 +6,10 @@
#pragma once #pragma once
#include "dawnopenal.hpp" #include "dawnopenal.hpp"
#include "scene/SceneItemComponent.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 { namespace Dawn {
enum AudioSourceState { enum AudioSourceState {
@ -24,13 +27,13 @@ namespace Dawn {
private: private:
ALuint source; ALuint source;
bool_t ready = false; bool_t ready = false;
ALuint buffers[AUDIO_SOURCE_BUFFER_COUNT];
size_t bufferPosition = 0;
// Settings // Settings
bool_t loop = false; enum AudioSourceState internalState = AUDIO_SOURCE_STATE_STOPPED;
enum AudioSourceState requestedAudioState = AUDIO_SOURCE_STATE_STOPPED;
enum AudioSourceState currentAudioState = AUDIO_SOURCE_STATE_STOPPED;
int32_t layer = 0; int32_t layer = 0;
AudioData *data = nullptr; AudioAsset *data = nullptr;
AudioSourcePlayMode playMode = AUDIO_PLAY_MODE_UNPAUSED; AudioSourcePlayMode playMode = AUDIO_PLAY_MODE_UNPAUSED;
/** /**
@ -38,15 +41,24 @@ namespace Dawn {
*/ */
void updateAudioSource(); void updateAudioSource();
void fillBuffer(ALuint buffer);
void detatchBuffers();
void attachBuffers();
// Events // Events
void onSceneUpdate();
void onTransformUpdate(); void onTransformUpdate();
void onGamePause();
void onGameUnpause();
public: public:
bool_t loop = false;
enum AudioSourceState state = AUDIO_SOURCE_STATE_STOPPED;
Event<> eventPlaying; Event<> eventPlaying;
Event<> eventResumed;
Event<> eventPaused; Event<> eventPaused;
Event<> eventStopped; Event<> eventStopped;
Event<> eventFinished;
Event<> eventLooped;
/** /**
* Creates an Audio Source item component. * Creates an Audio Source item component.
@ -55,38 +67,6 @@ namespace Dawn {
*/ */
AudioSource(SceneItem *item); 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 * Returns the real current state of the sound. Refer to getPlayingState
* but basically the actually requested play state and what the hardware * but basically the actually requested play state and what the hardware
@ -97,33 +77,18 @@ namespace Dawn {
*/ */
AudioSourceState getRealState(); 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 * 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. * am unsure of the behaviour when rewinding whilst the source is active.
*/ */
// void rewind(); void rewind();
/** /**
* Returns the current audio data for this source. * Returns the current audio data for this source.
* *
* @return Audio data that is atached to 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 * Sets the audio data for this source. Currently switching between the
@ -131,7 +96,7 @@ namespace Dawn {
* *
* @param data Data to set. * @param data Data to set.
*/ */
void setAudioData(AudioData *data); void setAudioData(AudioAsset *data);
/** /**
* Returns the play mode for the source. * Returns the play mode for the source.

View File

@ -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_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} add_dependencies(${DAWN_TARGET_NAME}
language_en language_en
language_jp language_jp
@ -45,4 +47,6 @@ add_dependencies(${DAWN_TARGET_NAME}
truetype_alice truetype_alice
texture_test texture_test
audio_test
) )

View File

@ -7,7 +7,6 @@
#include "scenes/PixelVNScene.hpp" #include "scenes/PixelVNScene.hpp"
#include "scenes/Scene_2.hpp" #include "scenes/Scene_2.hpp"
#include "prefabs/characters/DeathPrefab.hpp" #include "prefabs/characters/DeathPrefab.hpp"
#include "scene/components/audio/AudioListener.hpp" #include "scene/components/audio/AudioListener.hpp"
#include "scene/components/audio/AudioSource.hpp" #include "scene/components/audio/AudioSource.hpp"
@ -15,6 +14,12 @@ namespace Dawn {
class Scene_1 : public PixelVNScene { class Scene_1 : public PixelVNScene {
protected: protected:
DeathPrefab *death; DeathPrefab *death;
AudioSource *source;
void onFinished() {
std::cout << "Finished" << std::endl;
source->rewind();
}
void vnStage() override { void vnStage() override {
PixelVNScene::vnStage(); PixelVNScene::vnStage();
@ -23,15 +28,15 @@ namespace Dawn {
// this->death->vnCharacter.setOpacity(0); // this->death->vnCharacter.setOpacity(0);
auto sourceItem = this->createSceneItem(); auto sourceItem = this->createSceneItem();
auto source = sourceItem->addComponent<AudioSource>(); source = sourceItem->addComponent<AudioSource>();
source->transform->setLocalPosition(glm::vec3(1, 0, 0)); source->transform->setLocalPosition(glm::vec3(1, 0, 0));
source->eventFinished.addListener(this, &Scene_1::onFinished);
auto data = new AudioData(); auto audio = this->game->assetManager.get<AudioAsset>("audio_test");
data->init();
source->setAudioData(data); source->setAudioData(audio);
source->play(); source->loop = true;
source->setLoop(true); source->state = AUDIO_SOURCE_STATE_PLAYING;
} }
void onSceneEnded() { void onSceneEnded() {
@ -48,8 +53,8 @@ namespace Dawn {
auto start = new VisualNovelPauseEvent(vnManager, 1.0f); auto start = new VisualNovelPauseEvent(vnManager, 1.0f);
start start
->then(new VisualNovelTextboxEvent(vnManager, nullptr, "scene.1.1")) // ->then(new VisualNovelTextboxEvent(vnManager, nullptr, "scene.1.1"))
->then(new VisualNovelCallbackEvent<Scene_1>(vnManager, this, &Scene_1::onSceneEnded)) // ->then(new VisualNovelCallbackEvent<Scene_1>(vnManager, this, &Scene_1::onSceneEnded))
; ;
return start; return start;
@ -64,6 +69,7 @@ namespace Dawn {
auto man = &this->game->assetManager; auto man = &this->game->assetManager;
std::vector<Asset*> assets = PixelVNScene::getRequiredAssets(); std::vector<Asset*> assets = PixelVNScene::getRequiredAssets();
vectorAppend(&assets, DeathPrefab::getRequiredAssets(man)); vectorAppend(&assets, DeathPrefab::getRequiredAssets(man));
assets.push_back(man->get<AudioAsset>("audio_test"));
return assets; return assets;
} }
}; };

View File

@ -3,6 +3,7 @@
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
add_subdirectory(audio)
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(file) add_subdirectory(file)
add_subdirectory(locale) add_subdirectory(locale)

View File

@ -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()

View File

@ -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
)

View File

@ -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<double_t> 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<int16_t> (sample * 32767.);
buffer[0] = (q >> 8) & 0xFF;
buffer[1] = q & 0xFF;
fwrite(buffer, sizeof(uint8_t), 2, fileOut);
}
}
fclose(fileOut);
return 0;
}