Perfected Audio
This commit is contained in:
@ -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"
|
||||
|
77
src/dawn/asset/assets/AudioAsset.cpp
Normal file
77
src/dawn/asset/assets/AudioAsset.cpp
Normal 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;
|
||||
}
|
33
src/dawn/asset/assets/AudioAsset.hpp
Normal file
33
src/dawn/asset/assets/AudioAsset.hpp
Normal 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;
|
||||
};
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
# Sources
|
||||
target_sources(${DAWN_TARGET_NAME}
|
||||
PRIVATE
|
||||
AudioAsset.cpp
|
||||
LanguageAsset.cpp
|
||||
TextureAsset.cpp
|
||||
TilesetAsset.cpp
|
||||
|
@ -15,7 +15,7 @@ namespace Dawn {
|
||||
void onSceneUnpausedUpdate();
|
||||
|
||||
public:
|
||||
bool_t onlyUpdateUnpaused = true;
|
||||
bool_t onlyUpdateUnpaused = false;
|
||||
|
||||
SubSceneController(SceneItem *item);
|
||||
|
||||
|
@ -7,7 +7,6 @@
|
||||
target_link_libraries(${DAWN_TARGET_NAME}
|
||||
PUBLIC
|
||||
OpenAL
|
||||
AudioFile
|
||||
)
|
||||
|
||||
# Includes
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
@ -6,6 +6,5 @@
|
||||
# Sources
|
||||
target_sources(${DAWN_TARGET_NAME}
|
||||
PRIVATE
|
||||
AudioData.cpp
|
||||
AudioManager.cpp
|
||||
)
|
@ -5,5 +5,4 @@
|
||||
|
||||
#pragma once
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include "AudioFile.h"
|
||||
#include <AL/alc.h>
|
@ -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();
|
||||
}
|
@ -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.
|
||||
|
@ -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
|
||||
)
|
@ -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<AudioSource>();
|
||||
source = sourceItem->addComponent<AudioSource>();
|
||||
source->transform->setLocalPosition(glm::vec3(1, 0, 0));
|
||||
source->eventFinished.addListener(this, &Scene_1::onFinished);
|
||||
|
||||
auto audio = this->game->assetManager.get<AudioAsset>("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<Scene_1>(vnManager, this, &Scene_1::onSceneEnded))
|
||||
// ->then(new VisualNovelTextboxEvent(vnManager, nullptr, "scene.1.1"))
|
||||
// ->then(new VisualNovelCallbackEvent<Scene_1>(vnManager, this, &Scene_1::onSceneEnded))
|
||||
;
|
||||
|
||||
return start;
|
||||
@ -64,6 +69,7 @@ namespace Dawn {
|
||||
auto man = &this->game->assetManager;
|
||||
std::vector<Asset*> assets = PixelVNScene::getRequiredAssets();
|
||||
vectorAppend(&assets, DeathPrefab::getRequiredAssets(man));
|
||||
assets.push_back(man->get<AudioAsset>("audio_test"));
|
||||
return assets;
|
||||
}
|
||||
};
|
||||
|
@ -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)
|
15
src/dawntools/audio/CMakeLists.txt
Normal file
15
src/dawntools/audio/CMakeLists.txt
Normal 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()
|
23
src/dawntools/audio/audiogen/CMakeLists.txt
Normal file
23
src/dawntools/audio/audiogen/CMakeLists.txt
Normal 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
|
||||
)
|
78
src/dawntools/audio/audiogen/main.cpp
Normal file
78
src/dawntools/audio/audiogen/main.cpp
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user