Perfected Audio
This commit is contained in:
@ -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"
|
||||||
|
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
|
# 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
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
target_link_libraries(${DAWN_TARGET_NAME}
|
target_link_libraries(${DAWN_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
OpenAL
|
OpenAL
|
||||||
AudioFile
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Includes
|
# 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
|
# Sources
|
||||||
target_sources(${DAWN_TARGET_NAME}
|
target_sources(${DAWN_TARGET_NAME}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
AudioData.cpp
|
|
||||||
AudioManager.cpp
|
AudioManager.cpp
|
||||||
)
|
)
|
@ -5,5 +5,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <AL/al.h>
|
#include <AL/al.h>
|
||||||
#include <AL/alc.h>
|
#include <AL/alc.h>
|
||||||
#include "AudioFile.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) {
|
EDIT 23017 - Something about how OpenAL handles things means the following
|
||||||
// When the engine wants the sound to stop
|
statement is probably false, I'm leaving it here but DON'T READ IT PLEASE
|
||||||
case AUDIO_SOURCE_STATE_STOPPED:
|
|
||||||
if(this->currentAudioState == AUDIO_SOURCE_STATE_STOPPED) return;
|
Additionally, we need to handle cases where the buffer "wraps around". So
|
||||||
this->currentAudioState = AUDIO_SOURCE_STATE_STOPPED;
|
let's say we are at 8179/8192 bytes, we go to read "128 bytes", we need to
|
||||||
alSourceStop(this->source);
|
first read the remaining 13 bytes and then the remaining (128-13)bytes.
|
||||||
this->eventStopped.invoke();
|
|
||||||
|
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;
|
break;
|
||||||
|
case 1:
|
||||||
// When the engine wants the sound to pause
|
format = AL_FORMAT_MONO16;
|
||||||
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();
|
|
||||||
break;
|
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:
|
default:
|
||||||
assertUnreachable();
|
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() {
|
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();
|
|
||||||
}
|
}
|
@ -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.
|
||||||
|
@ -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
|
||||||
)
|
)
|
@ -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 audio = this->game->assetManager.get<AudioAsset>("audio_test");
|
||||||
|
|
||||||
auto data = new AudioData();
|
source->setAudioData(audio);
|
||||||
data->init();
|
source->loop = true;
|
||||||
|
source->state = AUDIO_SOURCE_STATE_PLAYING;
|
||||||
source->setAudioData(data);
|
|
||||||
source->play();
|
|
||||||
source->setLoop(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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)
|
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