Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ endif()
option(ENABLE_MODERN_GL "Enable the modern OpenGL renderer" ON)
option(ENABLE_NOOP_RENDERER "Enable the no-op renderer (headless stub, no drawing)" OFF)
option(ENABLE_PHYSICS "Enable Box2D physics support" ON)
option(ENABLE_FMOD "Enable standalone FMOD bank/event compatibility" ON)
# Noop renderer is exclusive to the noop desktop backend (no window, no GL).
# Auto-enable for BACKEND=noop and reject otherwise.
if(BACKEND STREQUAL "noop")
Expand Down Expand Up @@ -155,8 +156,14 @@ endif()
if(ENABLE_PHYSICS)
add_compile_definitions(ENABLE_PHYSICS)
endif()
if(ENABLE_FMOD)
add_compile_definitions(ENABLE_FMOD)
endif()

file(GLOB SOURCES src/*.c)
if(ENABLE_FMOD)
list(APPEND SOURCES src/fmod/fmod.c src/fmod/fmod_bank.c src/fmod/fsb.c src/fmod/fmod_dsp.c)
endif()
if(ENABLE_PHYSICS)
file(GLOB PHYSICS_SOURCES CONFIGURE_DEPENDS src/physics/*.c)
else()
Expand Down
52 changes: 46 additions & 6 deletions src/audio/miniaudio/ma_audio_system.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#endif

#include "ma_audio_system.h"
#include "fmod/fmod.h"
#include "data_win.h"
#include "utils.h"

Expand All @@ -55,7 +56,7 @@ static SoundInstance* findFreeSlot(MaAudioSystem* ma) {
SoundInstance* best = nullptr;
repeat(MAX_SOUND_INSTANCES, i) {
SoundInstance* inst = &ma->instances[i];
if (!ma_sound_is_playing(&inst->maSound)) {
if (ma_sound_at_end(&inst->maSound)) {
if (best == nullptr || best->priority > inst->priority) {
best = inst;
}
Expand Down Expand Up @@ -147,6 +148,7 @@ static void maInit(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem)
}

static void maDestroy(AudioSystem* audio) {
Fmod_audioDestroy(audio);
MaAudioSystem* ma = (MaAudioSystem*) audio;

free(audio->groupGains);
Expand Down Expand Up @@ -344,6 +346,34 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior
return slot->instanceId;
}

static int32_t maPlayEncoded(AudioSystem* audio, const uint8_t* data, size_t bytes, bool loop) {
MaAudioSystem* ma = (MaAudioSystem*) audio;
if (!data || !bytes || !ma_engine_get_sample_rate(&ma->engine)) return -1;
SoundInstance* slot = findFreeSlot(ma);
ma_decoder_config config = ma_decoder_config_init_default();
if (!slot || ma_decoder_init_memory(data, bytes, &config, &slot->decoder) != MA_SUCCESS) return -1;
if (ma_sound_init_from_data_source(&ma->engine, &slot->decoder, 0, &ma->listenerGroups[0], &slot->maSound) != MA_SUCCESS) {
ma_decoder_uninit(&slot->decoder);
return -1;
}
slot->ownsDecoder = true;
slot->active = true;
slot->soundIndex = -1;
slot->instanceId = SOUND_INSTANCE_ID_BASE + ma->nextInstanceCounter++;
slot->currentGain = slot->targetGain = slot->startGain = 1;
slot->fadeTimeRemaining = slot->fadeTotalTime = 0;
slot->priority = 0;
ma_sound_set_spatialization_enabled(&slot->maSound, MA_FALSE);
ma_sound_set_looping(&slot->maSound, loop);
if (ma_sound_start(&slot->maSound) != MA_SUCCESS) {
ma_sound_uninit(&slot->maSound);
ma_decoder_uninit(&slot->decoder);
slot->active = false;
return -1;
}
return slot->instanceId;
}

static void maSetSoundSpatial(AudioSystem* audio, int32_t instanceId, float x, float y, float z, float ref, float max, float factor) {
SoundInstance* inst = findInstanceById((MaAudioSystem*)audio, instanceId);
if (inst == nullptr) return;
Expand Down Expand Up @@ -394,7 +424,8 @@ static void maStopAll(AudioSystem* audio) {

repeat(MAX_SOUND_INSTANCES, i) {
SoundInstance* inst = &ma->instances[i];
if (inst->active) {
// encoded voices have separate control ownership.
if (inst->active && inst->soundIndex >= 0) {
ma_sound_stop(&inst->maSound);
ma_sound_uninit(&inst->maSound);
if (inst->ownsDecoder) {
Expand Down Expand Up @@ -464,7 +495,7 @@ static void maPauseAll(AudioSystem* audio) {

repeat(MAX_SOUND_INSTANCES, i) {
SoundInstance* inst = &ma->instances[i];
if (inst->active && ma_sound_is_playing(&inst->maSound)) {
if (inst->active && inst->soundIndex >= 0 && ma_sound_is_playing(&inst->maSound)) {
ma_sound_stop(&inst->maSound);
}
}
Expand All @@ -475,7 +506,7 @@ static void maResumeAll(AudioSystem* audio) {

repeat(MAX_SOUND_INSTANCES, i) {
SoundInstance* inst = &ma->instances[i];
if (inst->active) {
if (inst->active && inst->soundIndex >= 0) {
ma_sound_start(&inst->maSound);
}
}
Expand Down Expand Up @@ -678,19 +709,27 @@ static float maGetTrackPosition(AudioSystem* audio, int32_t soundOrInstance) {
return 0.0f;
}

static void maSeekInstance(SoundInstance* inst, float positionSeconds) {
ma_uint32 sampleRate;
if (ma_sound_get_data_format(&inst->maSound, nullptr, nullptr, &sampleRate, nullptr, 0) != MA_SUCCESS || sampleRate == 0) return;
double frame = (double) positionSeconds * sampleRate;
if (frame < (double) UINT64_MAX) ma_sound_seek_to_pcm_frame(&inst->maSound, (ma_uint64) frame);
}

static void maSetTrackPosition(AudioSystem* audio, int32_t soundOrInstance, float positionSeconds) {
MaAudioSystem* ma = (MaAudioSystem*) audio;
if (!isfinite(positionSeconds) || positionSeconds < 0) return;

if (isValidSoundInstanceId(soundOrInstance)) {
SoundInstance* inst = findInstanceById(ma, soundOrInstance);
if (inst != nullptr) {
ma_sound_seek_to_pcm_frame(&inst->maSound, (ma_uint64) (positionSeconds * 44100.0f));
maSeekInstance(inst, positionSeconds);
}
} else {
repeat(MAX_SOUND_INSTANCES, i) {
SoundInstance* inst = &ma->instances[i];
if (inst->active && inst->soundIndex == soundOrInstance) {
ma_sound_seek_to_pcm_frame(&inst->maSound, (ma_uint64) (positionSeconds * 44100.0f));
maSeekInstance(inst, positionSeconds);
}
}
}
Expand Down Expand Up @@ -960,6 +999,7 @@ MaAudioSystem* MaAudioSystem_create(DataWin* dataWin) {
maAudioSystemVtable.groupIsLoaded = maGroupIsLoaded;
maAudioSystemVtable.createStream = maCreateStream;
maAudioSystemVtable.destroyStream = maDestroyStream;
maAudioSystemVtable.playEncoded = maPlayEncoded;
ma->base.vtable = &maAudioSystemVtable;
return ma;
}
120 changes: 115 additions & 5 deletions src/audio/openal/al_audio_system.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "stb_vorbis.c"
#include "al_audio_system.h"
#include "fmod/fmod.h"
#include "binary_utils.h"
#include "data_win.h"
#include "utils.h"
Expand Down Expand Up @@ -237,6 +238,7 @@ static void maInit(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem)
}

static void maDestroy(AudioSystem* audio) {
Fmod_audioDestroy(audio);
AlAudioSystem* ma = (AlAudioSystem*) audio;

free(audio->groupGains);
Expand Down Expand Up @@ -434,6 +436,64 @@ static FILE* openWavStream(const char* path, int32_t* outChannels, int32_t* outS
return f;
}

static int32_t alPlayEncoded(AudioSystem* audio, const uint8_t* data, size_t bytes, bool loop) {
AlAudioSystem* al = (AlAudioSystem*) audio;
if (!data || !bytes || bytes > INT32_MAX || !al->alContext) return -1;
SoundInstance* slot = findFreeSlot(al);
if (!slot) return -1;
int error = 0;
stb_vorbis* decoder = stb_vorbis_open_memory(data, (int) bytes, &error, nullptr);
if (!decoder) return -1;
stb_vorbis_info info = stb_vorbis_get_info(decoder);
if (info.channels < 1 || info.channels > 2 || info.sample_rate == 0) {
stb_vorbis_close(decoder);
return -1;
}
memset(slot, 0, sizeof(*slot));
slot->active = true;
slot->streaming = true;
slot->loop = loop;
slot->vorbis = decoder;
slot->soundIndex = -1;
slot->instanceId = SOUND_INSTANCE_ID_BASE + al->nextInstanceCounter++;
slot->streamChannels = info.channels;
slot->streamSampleRate = (int) info.sample_rate;
slot->streamFormat = info.channels == 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
slot->streamLengthSamples = stb_vorbis_stream_length_in_samples(decoder);
slot->streamLengthSeconds = (float) slot->streamLengthSamples / info.sample_rate;
slot->currentGain = slot->targetGain = slot->startGain = 1;
slot->decodeScratch = (int16_t*) malloc(AL_STREAM_BUFFER_SAMPLES * info.channels * sizeof(int16_t));
if (!slot->decodeScratch) {
stb_vorbis_close(decoder);
slot->active = false;
return -1;
}
alGetError();
alGenSources(1, &slot->alSource);
alGenBuffers(AL_STREAM_BUFFER_COUNT, slot->streamBuffers);
if (alGetError() != AL_NO_ERROR) {
releaseInstance(slot);
return -1;
}
alSourcei(slot->alSource, AL_SOURCE_RELATIVE, AL_TRUE);
alSourcef(slot->alSource, AL_GAIN, 1);
int queued = 0;
for (int i = 0; i < AL_STREAM_BUFFER_COUNT; i++) {
if (!streamFillBuffer(slot, slot->streamBuffers[i])) {
slot->streamEnded = true;
break;
}
alSourceQueueBuffers(slot->alSource, 1, &slot->streamBuffers[i]);
queued++;
}
if (!queued || alGetError() != AL_NO_ERROR) {
releaseInstance(slot);
return -1;
}
alSourcePlay(slot->alSource);
return slot->instanceId;
}

static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t priority, bool loop) {
AlAudioSystem* ma = (AlAudioSystem*) audio;

Expand Down Expand Up @@ -747,7 +807,9 @@ static void maStopAll(AudioSystem* audio) {
AlAudioSystem* ma = (AlAudioSystem*) audio;

repeat(MAX_SOUND_INSTANCES, i) {
releaseInstance(&ma->instances[i]);
SoundInstance* inst = &ma->instances[i];
// encoded voices have separate control ownership.
if (inst->soundIndex >= 0) releaseInstance(inst);
}
}

Expand Down Expand Up @@ -817,7 +879,7 @@ static void maPauseAll(AudioSystem* audio) {

repeat(MAX_SOUND_INSTANCES, i) {
SoundInstance* inst = &ma->instances[i];
if (inst->active && alSourceIsPlaying(inst->alSource)) {
if (inst->active && inst->soundIndex >= 0 && alSourceIsPlaying(inst->alSource)) {
alSourcePause(inst->alSource);
}
}
Expand All @@ -828,7 +890,7 @@ static void maResumeAll(AudioSystem* audio) {

repeat(MAX_SOUND_INSTANCES, i) {
SoundInstance* inst = &ma->instances[i];
if (inst->active) {
if (inst->active && inst->soundIndex >= 0) {
alSourcePlay(inst->alSource);
}
}
Expand Down Expand Up @@ -1016,19 +1078,66 @@ static float maGetTrackPosition(AudioSystem* audio, int32_t soundOrInstance) {
return 0.0f;
}

static void alSeekInstance(SoundInstance* inst, float positionSeconds) {
if (!isfinite(positionSeconds) || positionSeconds < 0) return;
if (!inst->streaming) {
alSourcef(inst->alSource, AL_SEC_OFFSET, positionSeconds);
return;
}
if (inst->streamSampleRate <= 0) return;
double requestedFrame = (double) positionSeconds * inst->streamSampleRate;
uint64_t frame;
if (inst->streamLengthSamples && requestedFrame >= inst->streamLengthSamples) frame = inst->streamLengthSamples - 1;
else if (requestedFrame < (double) UINT64_MAX) frame = (uint64_t) requestedFrame;
else return;
ALint state, queued;
alGetSourcei(inst->alSource, AL_SOURCE_STATE, &state);
alSourceStop(inst->alSource);
alGetSourcei(inst->alSource, AL_BUFFERS_QUEUED, &queued);
for (int i = 0; i < queued; i++) {
ALuint buffer;
alSourceUnqueueBuffers(inst->alSource, 1, &buffer);
}
if (inst->vorbis) {
if (!stb_vorbis_seek((stb_vorbis*) inst->vorbis, (unsigned) frame)) {
stb_vorbis_seek_start((stb_vorbis*) inst->vorbis);
frame = 0;
}
} else if (inst->wavFile) {
uint64_t bytes = frame * inst->streamChannels * sizeof(int16_t);
fseek(inst->wavFile, (long) (inst->wavDataStart + bytes), SEEK_SET);
inst->wavSampleBytesRemaining = inst->wavDataBytes - (uint32_t) bytes;
}
inst->playedSamples = frame;
inst->streamEnded = false;
queued = 0;
for (int i = 0; i < AL_STREAM_BUFFER_COUNT; i++) {
if (!streamFillBuffer(inst, inst->streamBuffers[i])) {
inst->streamEnded = true;
break;
}
alSourceQueueBuffers(inst->alSource, 1, &inst->streamBuffers[i]);
queued++;
}
if (queued && (state == AL_PLAYING || state == AL_PAUSED)) {
alSourcePlay(inst->alSource);
if (state == AL_PAUSED) alSourcePause(inst->alSource);
}
}

static void maSetTrackPosition(AudioSystem* audio, int32_t soundOrInstance, float positionSeconds) {
AlAudioSystem* ma = (AlAudioSystem*) audio;

if (isValidSoundInstanceId(soundOrInstance)) {
SoundInstance* inst = findInstanceById(ma, soundOrInstance);
if (inst != nullptr) {
alSourcef(inst->alSource, AL_SEC_OFFSET, positionSeconds);
alSeekInstance(inst, positionSeconds);
}
} else {
repeat(MAX_SOUND_INSTANCES, i) {
SoundInstance* inst = &ma->instances[i];
if (inst->active && inst->soundIndex == soundOrInstance) {
alSourcef(inst->alSource, AL_SEC_OFFSET, positionSeconds);
alSeekInstance(inst, positionSeconds);
}
}
}
Expand Down Expand Up @@ -1284,6 +1393,7 @@ AlAudioSystem* AlAudioSystem_create(void) {
AlAudioSystemVtable.groupIsLoaded = maGroupIsLoaded;
AlAudioSystemVtable.createStream = maCreateStream;
AlAudioSystemVtable.destroyStream = maDestroyStream;
AlAudioSystemVtable.playEncoded = alPlayEncoded;
ma->base.vtable = &AlAudioSystemVtable;
return ma;
}
2 changes: 2 additions & 0 deletions src/audio/ps2/ps2_audio_system.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "ps2_audio_system.h"
#include "fmod/fmod.h"
#include "ps2/ps2_utils.h"
#include "utils.h"

Expand Down Expand Up @@ -603,6 +604,7 @@ static void ps2Init(AudioSystem* audio, MAYBE_UNUSED DataWin* dataWin, MAYBE_UNU
}

static void ps2Destroy(AudioSystem* audio) {
Fmod_audioDestroy(audio);
Ps2AudioSystem* ps2 = (Ps2AudioSystem*) audio;
free(audio->groupGains);

Expand Down
7 changes: 7 additions & 0 deletions src/audio_system.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ typedef struct {
// ===[ AudioSystem Vtable ]===

typedef struct AudioSystem AudioSystem;
struct FmodSystem;

typedef struct {
void (*init)(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem);
Expand Down Expand Up @@ -54,6 +55,9 @@ typedef struct {
bool (*groupIsLoaded)(AudioSystem* audio, int32_t groupIndex);
int32_t (*createStream)(AudioSystem* audio, const char* filename);
bool (*destroyStream)(AudioSystem* audio, int32_t streamIndex);
// encoded data stays alive until the voice ends or is stopped.
// returns a voice id, -1 on failure, or -2 for the headless sink.
int32_t (*playEncoded)(AudioSystem* audio, const uint8_t* data, size_t bytes, bool loop);
} AudioSystemVtable;

// ===[ AudioSystem Base Struct ]===
Expand All @@ -65,6 +69,8 @@ struct AudioSystem {
float listenerX, listenerY, listenerZ;
AudioGroupGain* groupGains;
uint32_t groupGainCount;
struct FmodSystem* fmodSystem;
int32_t fmodNextHandle;
};

static inline int32_t AudioSystem_soundGroup(const AudioSystem* audio, int32_t soundIndex) {
Expand All @@ -80,6 +86,7 @@ static inline float AudioSystem_getGroupGain(const AudioSystem* audio, int32_t g
}

static inline float AudioSystem_soundGroupGain(const AudioSystem* audio, int32_t soundIndex) {
if (soundIndex < 0) return 1; // encoded voices don't belong to a gamemaker audio group.
return AudioSystem_getGroupGain(audio, AudioSystem_soundGroup(audio, soundIndex));
}

Expand Down
Loading
Loading