Merge pull request #880 from UnknownShadow200/AudioFixup2

Audio fixup part 2
This commit is contained in:
UnknownShadow200 2021-08-10 07:09:52 +10:00 committed by GitHub
commit 2cc65bf627
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 287 additions and 284 deletions

View File

@ -17,25 +17,9 @@
#include "Window.h" #include "Window.h"
#endif #endif
int Audio_SoundsVolume, Audio_MusicVolume; int Audio_SoundsVolume, Audio_MusicVolume;
static const cc_string audio_dir = String_FromConst("audio");
#if defined CC_BUILD_NOAUDIO static void ApplyVolume(cc_int16* samples, int count, int volume) {
/* Can't use mojang's sounds or music assets, so just stub everything out */
static void OnInit(void) { }
static void OnFree(void) { }
void Audio_SetMusic(int volume) {
if (volume) Chat_AddRaw("&cMusic is not supported currently");
}
void Audio_SetSounds(int volume) {
if (volume) Chat_AddRaw("&cSounds are not supported currently");
}
void Audio_PlayDigSound(cc_uint8 type) { }
void Audio_PlayStepSound(cc_uint8 type) { }
#else
static struct StringsBuffer files;
static void Volume_Mix16(cc_int16* samples, int count, int volume) {
int i; int i;
for (i = 0; i < (count & ~0x07); i += 8, samples += 8) { for (i = 0; i < (count & ~0x07); i += 8, samples += 8) {
@ -55,10 +39,17 @@ static void Volume_Mix16(cc_int16* samples, int count, int volume) {
} }
} }
static int GetVolume(const char* volKey, const char* boolKey) {
int volume = Options_GetInt(volKey, 0, 100, 0);
if (volume) return volume;
/* backwards compatibility */
volume = Options_GetBool(boolKey, false) ? 100 : 0;
Options_Set(boolKey, NULL);
return volume;
}
/*########################################################################################################################*
*------------------------------------------------Native implementation----------------------------------------------------*
*#########################################################################################################################*/
#if defined CC_BUILD_OPENAL #if defined CC_BUILD_OPENAL
/*########################################################################################################################* /*########################################################################################################################*
*------------------------------------------------------OpenAL backend-----------------------------------------------------* *------------------------------------------------------OpenAL backend-----------------------------------------------------*
@ -110,12 +101,13 @@ struct AudioContext {
ALuint source; ALuint source;
ALuint buffers[AUDIO_MAX_BUFFERS]; ALuint buffers[AUDIO_MAX_BUFFERS];
ALuint freeIDs[AUDIO_MAX_BUFFERS]; ALuint freeIDs[AUDIO_MAX_BUFFERS];
struct AudioFormat format; int count, free, channels, sampleRate;
int count, free;
ALenum dataFormat; ALenum dataFormat;
cc_uint32 _tmpSize; void* _tmpData;
}; };
static void* audio_device; static void* audio_device;
static void* audio_context; static void* audio_context;
#define AUDIO_HAS_BACKEND
#if defined CC_BUILD_WIN #if defined CC_BUILD_WIN
static const cc_string alLib = String_FromConst("openal32.dll"); static const cc_string alLib = String_FromConst("openal32.dll");
@ -218,9 +210,9 @@ static ALenum GetALFormat(int channels) {
Logger_Abort("Unsupported audio format"); return 0; Logger_Abort("Unsupported audio format"); return 0;
} }
static cc_result AudioBackend_SetFormat(struct AudioContext* ctx, struct AudioFormat* format) { static cc_result AudioBackend_UpdateFormat(struct AudioContext* ctx) {
ALenum i, err; ALenum i, err;
ctx->dataFormat = GetALFormat(format->channels); ctx->dataFormat = GetALFormat(ctx->channels);
if (!ctx->source) { if (!ctx->source) {
_alGenSources(1, &ctx->source); _alGenSources(1, &ctx->source);
@ -244,7 +236,7 @@ cc_result Audio_QueueData(struct AudioContext* ctx, void* data, cc_uint32 size)
if (!ctx->free) return ERR_INVALID_ARGUMENT; if (!ctx->free) return ERR_INVALID_ARGUMENT;
buffer = ctx->freeIDs[--ctx->free]; buffer = ctx->freeIDs[--ctx->free];
_alBufferData(buffer, ctx->dataFormat, data, size, ctx->format.sampleRate); _alBufferData(buffer, ctx->dataFormat, data, size, ctx->sampleRate);
if ((err = _alGetError())) return err; if ((err = _alGetError())) return err;
_alSourceQueueBuffers(ctx->source, 1, &buffer); _alSourceQueueBuffers(ctx->source, 1, &buffer);
if ((err = _alGetError())) return err; if ((err = _alGetError())) return err;
@ -337,11 +329,12 @@ WINMMAPI MMRESULT WINAPI waveOutReset(HWAVEOUT hwo);
struct AudioContext { struct AudioContext {
HWAVEOUT handle; HWAVEOUT handle;
WAVEHDR headers[AUDIO_MAX_BUFFERS]; WAVEHDR headers[AUDIO_MAX_BUFFERS];
struct AudioFormat format; int count, channels, sampleRate;
int count; cc_uint32 _tmpSize; void* _tmpData;
}; };
static cc_bool AudioBackend_Init(void) { return true; } static cc_bool AudioBackend_Init(void) { return true; }
static void AudioBackend_Free(void) { } static void AudioBackend_Free(void) { }
#define AUDIO_HAS_BACKEND
void Audio_Init(struct AudioContext* ctx, int buffers) { void Audio_Init(struct AudioContext* ctx, int buffers) {
int i; int i;
@ -360,16 +353,16 @@ static cc_result AudioBackend_Reset(struct AudioContext* ctx) {
return res; return res;
} }
static cc_result AudioBackend_SetFormat(struct AudioContext* ctx, struct AudioFormat* format) { static cc_result AudioBackend_UpdateFormat(struct AudioContext* ctx) {
WAVEFORMATEX fmt; WAVEFORMATEX fmt;
cc_result res; cc_result res;
int sampleSize = format->channels * 2; /* 16 bits per sample / 8 */ int sampleSize = ctx->channels * 2; /* 16 bits per sample / 8 */
if ((res = AudioBackend_Reset(ctx))) return res; if ((res = AudioBackend_Reset(ctx))) return res;
fmt.wFormatTag = WAVE_FORMAT_PCM; fmt.wFormatTag = WAVE_FORMAT_PCM;
fmt.nChannels = format->channels; fmt.nChannels = ctx->channels;
fmt.nSamplesPerSec = format->sampleRate; fmt.nSamplesPerSec = ctx->sampleRate;
fmt.nAvgBytesPerSec = format->sampleRate * sampleSize; fmt.nAvgBytesPerSec = ctx->sampleRate * sampleSize;
fmt.nBlockAlign = sampleSize; fmt.nBlockAlign = sampleSize;
fmt.wBitsPerSample = 16; fmt.wBitsPerSample = 16;
fmt.cbSize = 0; fmt.cbSize = 0;
@ -430,13 +423,14 @@ cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) {
static SLObjectItf slEngineObject; static SLObjectItf slEngineObject;
static SLEngineItf slEngineEngine; static SLEngineItf slEngineEngine;
static SLObjectItf slOutputObject; static SLObjectItf slOutputObject;
#define AUDIO_HAS_BACKEND
struct AudioContext { struct AudioContext {
struct AudioFormat format; int count, channels, sampleRate;
int count;
SLObjectItf bqPlayerObject; SLObjectItf bqPlayerObject;
SLPlayItf bqPlayerPlayer; SLPlayItf bqPlayerPlayer;
SLBufferQueueItf bqPlayerQueue; SLBufferQueueItf bqPlayerQueue;
cc_uint32 _tmpSize; void* _tmpData;
}; };
static SLresult (SLAPIENTRY *_slCreateEngine)( static SLresult (SLAPIENTRY *_slCreateEngine)(
@ -521,7 +515,7 @@ static void AudioBackend_Reset(struct AudioContext* ctx) {
ctx->bqPlayerQueue = NULL; ctx->bqPlayerQueue = NULL;
} }
static cc_result AudioBackend_SetFormat(struct AudioContext* ctx, struct AudioFormat* format) { static cc_result AudioBackend_UpdateFormat(struct AudioContext* ctx) {
SLDataLocator_AndroidSimpleBufferQueue input; SLDataLocator_AndroidSimpleBufferQueue input;
SLDataLocator_OutputMix output; SLDataLocator_OutputMix output;
SLObjectItf bqPlayerObject; SLObjectItf bqPlayerObject;
@ -534,8 +528,8 @@ static cc_result AudioBackend_SetFormat(struct AudioContext* ctx, struct AudioFo
AudioBackend_Reset(ctx); AudioBackend_Reset(ctx);
fmt.formatType = SL_DATAFORMAT_PCM; fmt.formatType = SL_DATAFORMAT_PCM;
fmt.numChannels = format->channels; fmt.numChannels = ctx->channels;
fmt.samplesPerSec = format->sampleRate * 1000; fmt.samplesPerSec = ctx->sampleRate * 1000;
fmt.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; fmt.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
fmt.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; fmt.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
fmt.channelMask = 0; fmt.channelMask = 0;
@ -598,14 +592,18 @@ cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) {
/*########################################################################################################################* /*########################################################################################################################*
*---------------------------------------------------Common backend code---------------------------------------------------* *---------------------------------------------------Common backend code---------------------------------------------------*
*#########################################################################################################################*/ *#########################################################################################################################*/
struct AudioFormat* Audio_GetFormat(struct AudioContext* ctx) { return &ctx->format; } #ifndef AUDIO_HAS_BACKEND
static void AudioBackend_Free(void) { }
#else
int Audio_GetChannels(struct AudioContext* ctx) { return ctx->channels; }
int Audio_GetSampleRate(struct AudioContext* ctx) { return ctx->sampleRate; }
cc_result Audio_SetFormat(struct AudioContext* ctx, struct AudioFormat* format) { cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate) {
struct AudioFormat* cur = &ctx->format; if (ctx->channels == channels && ctx->sampleRate == sampleRate) return 0;
if (AudioFormat_Eq(cur, format)) return 0;
ctx->format = *format;
return AudioBackend_SetFormat(ctx, format); ctx->channels = channels;
ctx->sampleRate = sampleRate;
return AudioBackend_UpdateFormat(ctx);
} }
void Audio_Close(struct AudioContext* ctx) { void Audio_Close(struct AudioContext* ctx) {
@ -614,30 +612,76 @@ void Audio_Close(struct AudioContext* ctx) {
Audio_Poll(ctx, &inUse); /* unqueue buffers */ Audio_Poll(ctx, &inUse); /* unqueue buffers */
ctx->count = 0; ctx->count = 0;
ctx->format.channels = 0; ctx->channels = 0;
ctx->format.sampleRate = 0; ctx->sampleRate = 0;
Mem_Free(ctx->_tmpData);
ctx->_tmpData = NULL;
ctx->_tmpSize = 0;
AudioBackend_Reset(ctx); AudioBackend_Reset(ctx);
} }
cc_result Audio_PlaySound(struct AudioContext* ctx, struct Sound* snd, int volume) {
cc_result res;
void* data = snd->data;
void* tmp;
/* copy to temp buffer to apply volume */
if (volume < 100) {
if (ctx->_tmpSize < snd->size) {
/* TODO: check if we can realloc NULL without a problem */
if (ctx->_tmpData) {
tmp = Mem_TryRealloc(ctx->_tmpData, snd->size, 1);
} else {
tmp = Mem_TryAlloc(snd->size, 1);
}
if (!tmp) return ERR_OUT_OF_MEMORY;
ctx->_tmpData = tmp;
ctx->_tmpSize = snd->size;
}
data = ctx->_tmpData;
Mem_Copy(data, snd->data, snd->size);
ApplyVolume((cc_int16*)data, snd->size / 2, volume);
}
if ((res = Audio_SetFormat(ctx, snd->channels, snd->sampleRate))) return res;
if ((res = Audio_QueueData(ctx, data, snd->size))) return res;
if ((res = Audio_Play(ctx))) return res;
return 0;
}
#endif
/*########################################################################################################################* /*########################################################################################################################*
*------------------------------------------------------Soundboard---------------------------------------------------------* *--------------------------------------------------------Sounds-----------------------------------------------------------*
*#########################################################################################################################*/ *#########################################################################################################################*/
struct Sound { #ifdef CC_BUILD_NOSOUNDS
struct AudioFormat format; /* Can't use mojang's sound assets, so just stub everything out */
cc_uint8* data; cc_uint32 size; static void Sounds_Init(void) { }
}; static void Sounds_Free(void) { }
static void Sounds_Stop(void) { }
static void Sounds_Start(void) {
Chat_AddRaw("&cSounds are not supported currently");
Audio_SoundsVolume = 0;
}
void Audio_PlayDigSound(cc_uint8 type) { }
void Audio_PlayStepSound(cc_uint8 type) { }
#else
#define AUDIO_MAX_SOUNDS 10 #define AUDIO_MAX_SOUNDS 10
#define SOUND_MAX_CONTEXTS 8
struct SoundGroup { struct SoundGroup {
int count; int count;
struct Sound sounds[AUDIO_MAX_SOUNDS]; struct Sound sounds[AUDIO_MAX_SOUNDS];
}; };
struct Soundboard { struct SoundGroup groups[SOUND_COUNT]; };
struct Soundboard { static struct Soundboard digBoard, stepBoard;
RNGState rnd; cc_bool inited; static struct AudioContext sound_contexts[SOUND_MAX_CONTEXTS];
struct SoundGroup groups[SOUND_COUNT]; static RNGState sounds_rnd;
};
#define WAV_FourCC(a, b, c, d) (((cc_uint32)a << 24) | ((cc_uint32)b << 16) | ((cc_uint32)c << 8) | (cc_uint32)d) #define WAV_FourCC(a, b, c, d) (((cc_uint32)a << 24) | ((cc_uint32)b << 16) | ((cc_uint32)c << 8) | (cc_uint32)d)
#define WAV_FMT_SIZE 16 #define WAV_FMT_SIZE 16
@ -664,8 +708,8 @@ static cc_result Sound_ReadWaveData(struct Stream* stream, struct Sound* snd) {
if ((res = Stream_Read(stream, tmp, sizeof(tmp)))) return res; if ((res = Stream_Read(stream, tmp, sizeof(tmp)))) return res;
if (Stream_GetU16_LE(tmp + 0) != 1) return WAV_ERR_DATA_TYPE; if (Stream_GetU16_LE(tmp + 0) != 1) return WAV_ERR_DATA_TYPE;
snd->format.channels = Stream_GetU16_LE(tmp + 2); snd->channels = Stream_GetU16_LE(tmp + 2);
snd->format.sampleRate = Stream_GetU32_LE(tmp + 4); snd->sampleRate = Stream_GetU32_LE(tmp + 4);
/* tmp[8] (6) alignment data and stuff */ /* tmp[8] (6) alignment data and stuff */
bitsPerSample = Stream_GetU16_LE(tmp + 14); bitsPerSample = Stream_GetU16_LE(tmp + 14);
@ -684,17 +728,12 @@ static cc_result Sound_ReadWaveData(struct Stream* stream, struct Sound* snd) {
} }
} }
static cc_result Sound_ReadWave(const cc_string* filename, struct Sound* snd) { static cc_result Sound_ReadWave(const cc_string* path, struct Sound* snd) {
cc_string path; char pathBuffer[FILENAME_SIZE];
struct Stream stream; struct Stream stream;
cc_result res; cc_result res;
String_InitArray(path, pathBuffer); res = Stream_OpenFile(&stream, path);
String_Format1(&path, "audio/%s", filename);
res = Stream_OpenFile(&stream, &path);
if (res) return res; if (res) return res;
res = Sound_ReadWaveData(&stream, snd); res = Sound_ReadWaveData(&stream, snd);
if (res) { stream.Close(&stream); return res; } if (res) { stream.Close(&stream); return res; }
@ -711,22 +750,18 @@ static struct SoundGroup* Soundboard_Find(struct Soundboard* board, const cc_str
return NULL; return NULL;
} }
static void Soundboard_Init(struct Soundboard* board, const cc_string* boardName) { static void Soundboard_Load(struct Soundboard* board, const cc_string* boardName, const cc_string* file) {
cc_string file, name;
struct SoundGroup* group; struct SoundGroup* group;
struct Sound* snd; struct Sound* snd;
cc_string name = *file;
cc_result res; cc_result res;
int i, dotIndex; int dotIndex;
board->inited = true; Utils_UNSAFE_TrimFirstDirectory(&name);
for (i = 0; i < files.count; i++) {
file = StringsBuffer_UNSAFE_Get(&files, i);
name = file;
/* dig_grass1.wav -> dig_grass1 */ /* dig_grass1.wav -> dig_grass1 */
dotIndex = String_LastIndexOf(&name, '.'); dotIndex = String_LastIndexOf(&name, '.');
if (dotIndex >= 0) name.length = dotIndex; if (dotIndex >= 0) name.length = dotIndex;
if (!String_CaselessStarts(&name, boardName)) continue; if (!String_CaselessStarts(&name, boardName)) return;
/* Convert dig_grass1 to grass */ /* Convert dig_grass1 to grass */
name = String_UNSAFE_SubstringAt(&name, boardName->length); name = String_UNSAFE_SubstringAt(&name, boardName->length);
@ -734,25 +769,24 @@ static void Soundboard_Init(struct Soundboard* board, const cc_string* boardName
group = Soundboard_Find(board, &name); group = Soundboard_Find(board, &name);
if (!group) { if (!group) {
Chat_Add1("&cUnknown sound group '%s'", &name); continue; Chat_Add1("&cUnknown sound group '%s'", &name); return;
} }
if (group->count == Array_Elems(group->sounds)) { if (group->count == Array_Elems(group->sounds)) {
Chat_AddRaw("&cCannot have more than 10 sounds in a group"); continue; Chat_AddRaw("&cCannot have more than 10 sounds in a group"); return;
} }
snd = &group->sounds[group->count]; snd = &group->sounds[group->count];
res = Sound_ReadWave(&file, snd); res = Sound_ReadWave(file, snd);
if (res) { if (res) {
Logger_SysWarn2(res, "decoding", &file); Logger_SysWarn2(res, "decoding", file);
Mem_Free(snd->data); Mem_Free(snd->data);
snd->data = NULL; snd->data = NULL;
snd->size = 0; snd->size = 0;
} else { group->count++; } } else { group->count++; }
}
} }
static struct Sound* Soundboard_PickRandom(struct Soundboard* board, cc_uint8 type) { static const struct Sound* Soundboard_PickRandom(struct Soundboard* board, cc_uint8 type) {
struct SoundGroup* group; struct SoundGroup* group;
int idx; int idx;
@ -762,72 +796,28 @@ static struct Sound* Soundboard_PickRandom(struct Soundboard* board, cc_uint8 ty
group = &board->groups[type]; group = &board->groups[type];
if (!group->count) return NULL; if (!group->count) return NULL;
idx = Random_Next(&board->rnd, group->count); idx = Random_Next(&sounds_rnd, group->count);
return &group->sounds[idx]; return &group->sounds[idx];
} }
/*########################################################################################################################*
*--------------------------------------------------------Sounds-----------------------------------------------------------*
*#########################################################################################################################*/
struct SoundOutput { struct AudioContext ctx; void* buffer; cc_uint32 capacity; };
static struct Soundboard digBoard, stepBoard;
#define AUDIO_MAX_HANDLES 6
static struct SoundOutput monoOutputs[AUDIO_MAX_HANDLES];
static struct SoundOutput stereoOutputs[AUDIO_MAX_HANDLES];
CC_NOINLINE static void Sounds_Fail(cc_result res) { CC_NOINLINE static void Sounds_Fail(cc_result res) {
Logger_SimpleWarn(res, "playing sounds"); Logger_SimpleWarn(res, "playing sounds");
Chat_AddRaw("&cDisabling sounds"); Chat_AddRaw("&cDisabling sounds");
Audio_SetSounds(0); Audio_SetSounds(0);
} }
static void Sounds_PlayRaw(struct SoundOutput* output, struct Sound* snd, struct AudioFormat* fmt, int volume) {
void* data = snd->data;
void* tmp;
cc_result res;
/* copy to temp buffer to apply volume */
if (volume < 100) {
/* TODO: Don't need a per sound temp buffer, just a global one */
if (output->capacity < snd->size) {
/* TODO: check if we can realloc NULL without a problem */
if (output->buffer) {
tmp = Mem_TryRealloc(output->buffer, snd->size, 1);
} else {
tmp = Mem_TryAlloc(snd->size, 1);
}
if (!tmp) { Sounds_Fail(ERR_OUT_OF_MEMORY); return; }
output->buffer = tmp;
output->capacity = snd->size;
}
data = output->buffer;
Mem_Copy(data, snd->data, snd->size);
Volume_Mix16((cc_int16*)data, snd->size / 2, volume);
}
if ((res = Audio_SetFormat(&output->ctx, fmt))) { Sounds_Fail(res); return; }
if ((res = Audio_QueueData(&output->ctx, data, snd->size))) { Sounds_Fail(res); return; }
if ((res = Audio_Play(&output->ctx))) { Sounds_Fail(res); return; }
}
static void Sounds_Play(cc_uint8 type, struct Soundboard* board) { static void Sounds_Play(cc_uint8 type, struct Soundboard* board) {
struct Sound* snd; const struct Sound* snd_;
struct AudioFormat fmt; struct Sound snd;
struct SoundOutput* outputs; struct AudioContext* ctx;
struct SoundOutput* output; int chans, freq;
struct AudioFormat* l; int inUse, i, volume;
int inUse;
int i, volume;
cc_result res; cc_result res;
if (type == SOUND_NONE || !Audio_SoundsVolume) return; if (type == SOUND_NONE || !Audio_SoundsVolume) return;
snd = Soundboard_PickRandom(board, type); snd_ = Soundboard_PickRandom(board, type);
if (!snd) return; if (!snd_) return;
if (!AudioBackend_Init()) { if (!AudioBackend_Init()) {
AudioBackend_Free(); AudioBackend_Free();
@ -835,45 +825,50 @@ static void Sounds_Play(cc_uint8 type, struct Soundboard* board) {
return; return;
} }
fmt = snd->format; snd = *snd_;
volume = Audio_SoundsVolume; volume = Audio_SoundsVolume;
outputs = fmt.channels == 1 ? monoOutputs : stereoOutputs;
if (board == &digBoard) { if (board == &digBoard) {
if (type == SOUND_METAL) fmt.sampleRate = (fmt.sampleRate * 6) / 5; if (type == SOUND_METAL) snd.sampleRate = (snd.sampleRate * 6) / 5;
else fmt.sampleRate = (fmt.sampleRate * 4) / 5; else snd.sampleRate = (snd.sampleRate * 4) / 5;
} else { } else {
volume /= 2; volume /= 2;
if (type == SOUND_METAL) fmt.sampleRate = (fmt.sampleRate * 7) / 5; if (type == SOUND_METAL) snd.sampleRate = (snd.sampleRate * 7) / 5;
} }
/* Try to play on fresh device, or device with same data format */ /* Try to play on fresh device, or device with same data format */
for (i = 0; i < AUDIO_MAX_HANDLES; i++) { for (i = 0; i < SOUND_MAX_CONTEXTS; i++) {
output = &outputs[i]; ctx = &sound_contexts[i];
if (!output->ctx.count) { if (!ctx->count) {
Audio_Init(&output->ctx, 1); Audio_Init(ctx, 1);
} else { } else {
res = Audio_Poll(&output->ctx, &inUse); res = Audio_Poll(ctx, &inUse);
if (res) { Sounds_Fail(res); return; } if (res) { Sounds_Fail(res); return; }
if (inUse > 0) continue; if (inUse > 0) continue;
} }
l = Audio_GetFormat(&output->ctx); chans = Audio_GetChannels(ctx);
if (!l->channels || AudioFormat_Eq(l, &fmt)) { freq = Audio_GetSampleRate(ctx);
Sounds_PlayRaw(output, snd, &fmt, volume); return; if (!chans || (chans == snd.channels && freq == snd.sampleRate)) {
res = Audio_PlaySound(ctx, &snd, volume);
if (res) Sounds_Fail(res);
return;
} }
} }
/* Try again with all devices, even if need to recreate one (expensive) */ /* Try again with all contexts, even if need to recreate one (expensive) */
for (i = 0; i < AUDIO_MAX_HANDLES; i++) { for (i = 0; i < SOUND_MAX_CONTEXTS; i++) {
output = &outputs[i]; ctx = &sound_contexts[i];
res = Audio_Poll(&output->ctx, &inUse); res = Audio_Poll(ctx, &inUse);
if (res) { Sounds_Fail(res); return; } if (res) { Sounds_Fail(res); return; }
if (inUse > 0) continue; if (inUse > 0) continue;
Sounds_PlayRaw(output, snd, &fmt, volume); return; res = Audio_PlaySound(ctx, &snd, volume);
if (res) Sounds_Fail(res);
return;
} }
} }
@ -881,53 +876,63 @@ static void Audio_PlayBlockSound(void* obj, IVec3 coords, BlockID old, BlockID n
if (now == BLOCK_AIR) { if (now == BLOCK_AIR) {
Audio_PlayDigSound(Blocks.DigSounds[old]); Audio_PlayDigSound(Blocks.DigSounds[old]);
} else if (!Game_ClassicMode) { } else if (!Game_ClassicMode) {
/* use StepSounds instead when placing, as don't want */
/* to play glass break sound when placing glass */
Audio_PlayDigSound(Blocks.StepSounds[now]); Audio_PlayDigSound(Blocks.StepSounds[now]);
} }
} }
static void Sounds_FreeOutputs(struct SoundOutput* outputs) { static void Sounds_LoadFile(const cc_string* path, void* obj) {
int i; static const cc_string dig = String_FromConst("dig_");
for (i = 0; i < AUDIO_MAX_HANDLES; i++) { static const cc_string step = String_FromConst("step_");
if (!outputs[i].ctx.count) continue; Soundboard_Load(&digBoard, &dig, path);
Audio_Close(&outputs[i].ctx); Soundboard_Load(&stepBoard, &step, path);
}
Mem_Free(outputs[i].buffer); static cc_bool sounds_loaded;
outputs[i].buffer = NULL; static void Sounds_Start(void) {
outputs[i].capacity = 0; if (sounds_loaded) return;
sounds_loaded = true;
Directory_Enum(&audio_dir, NULL, Sounds_LoadFile);
}
static void Sounds_Stop(void) {
int i;
for (i = 0; i < SOUND_MAX_CONTEXTS; i++) {
if (!sound_contexts[i].count) continue;
Audio_Close(&sound_contexts[i]);
} }
} }
static void Sounds_Init(void) { static void Sounds_Init(void) {
static const cc_string dig = String_FromConst("dig_"); int volume = GetVolume(OPT_SOUND_VOLUME, OPT_USE_SOUND);
static const cc_string step = String_FromConst("step_"); Audio_SetSounds(volume);
Event_Register_(&UserEvents.BlockChanged, NULL, Audio_PlayBlockSound);
if (digBoard.inited || stepBoard.inited) return;
Soundboard_Init(&digBoard, &dig);
Soundboard_Init(&stepBoard, &step);
}
static void Sounds_Free(void) {
Sounds_FreeOutputs(monoOutputs);
Sounds_FreeOutputs(stereoOutputs);
}
void Audio_SetSounds(int volume) {
if (volume) Sounds_Init();
else Sounds_Free();
Audio_SoundsVolume = volume;
} }
static void Sounds_Free(void) { Sounds_Stop(); }
void Audio_PlayDigSound(cc_uint8 type) { Sounds_Play(type, &digBoard); } void Audio_PlayDigSound(cc_uint8 type) { Sounds_Play(type, &digBoard); }
void Audio_PlayStepSound(cc_uint8 type) { Sounds_Play(type, &stepBoard); } void Audio_PlayStepSound(cc_uint8 type) { Sounds_Play(type, &stepBoard); }
#endif
/*########################################################################################################################* /*########################################################################################################################*
*--------------------------------------------------------Music------------------------------------------------------------* *--------------------------------------------------------Music------------------------------------------------------------*
*#########################################################################################################################*/ *#########################################################################################################################*/
#ifdef CC_BUILD_NOMUSIC
/* Can't use mojang's music assets, so just stub everything out */
static void Music_Init(void) { }
static void Music_Free(void) { }
static void Music_Stop(void) { }
static void Music_Start(void) {
Chat_AddRaw("&cMusic is not supported currently");
Audio_MusicVolume = 0;
}
#else
static struct AudioContext music_ctx; static struct AudioContext music_ctx;
static void* music_thread; static void* music_thread;
static void* music_waitable; static void* music_waitable;
static volatile cc_bool music_pendingStop, music_joining; static volatile cc_bool music_stopping, music_joining;
static int music_minDelay, music_maxDelay; static int music_minDelay, music_maxDelay;
static cc_result Music_Buffer(cc_int16* data, int maxSamples, struct VorbisState* ctx) { static cc_result Music_Buffer(cc_int16* data, int maxSamples, struct VorbisState* ctx) {
@ -941,17 +946,17 @@ static cc_result Music_Buffer(cc_int16* data, int maxSamples, struct VorbisState
cur = &data[samples]; cur = &data[samples];
samples += Vorbis_OutputFrame(ctx, cur); samples += Vorbis_OutputFrame(ctx, cur);
} }
if (Audio_MusicVolume < 100) { Volume_Mix16(data, samples, Audio_MusicVolume); } if (Audio_MusicVolume < 100) { ApplyVolume(data, samples, Audio_MusicVolume); }
res2 = Audio_QueueData(&music_ctx, data, samples * 2); res2 = Audio_QueueData(&music_ctx, data, samples * 2);
if (res2) { music_pendingStop = true; return res2; } if (res2) { music_stopping = true; return res2; }
return res; return res;
} }
static cc_result Music_PlayOgg(struct Stream* source) { static cc_result Music_PlayOgg(struct Stream* source) {
struct OggState ogg; struct OggState ogg;
struct VorbisState vorbis = { 0 }; struct VorbisState vorbis = { 0 };
struct AudioFormat fmt; int channels, sampleRate;
int chunkSize, samplesPerSecond; int chunkSize, samplesPerSecond;
cc_int16* data = NULL; cc_int16* data = NULL;
@ -962,14 +967,14 @@ static cc_result Music_PlayOgg(struct Stream* source) {
vorbis.source = &ogg; vorbis.source = &ogg;
if ((res = Vorbis_DecodeHeaders(&vorbis))) goto cleanup; if ((res = Vorbis_DecodeHeaders(&vorbis))) goto cleanup;
fmt.channels = vorbis.channels; channels = vorbis.channels;
fmt.sampleRate = vorbis.sampleRate; sampleRate = vorbis.sampleRate;
if ((res = Audio_SetFormat(&music_ctx, &fmt))) goto cleanup; if ((res = Audio_SetFormat(&music_ctx, channels, sampleRate))) goto cleanup;
/* largest possible vorbis frame decodes to blocksize1 * channels samples, */ /* largest possible vorbis frame decodes to blocksize1 * channels samples, */
/* so can end up decoding slightly over a second of audio */ /* so can end up decoding slightly over a second of audio */
chunkSize = fmt.channels * (fmt.sampleRate + vorbis.blockSizes[1]); chunkSize = channels * (sampleRate + vorbis.blockSizes[1]);
samplesPerSecond = fmt.channels * fmt.sampleRate; samplesPerSecond = channels * sampleRate;
cur = 0; cur = 0;
data = (cc_int16*)Mem_TryAlloc(chunkSize * AUDIO_MAX_BUFFERS, 2); data = (cc_int16*)Mem_TryAlloc(chunkSize * AUDIO_MAX_BUFFERS, 2);
@ -980,18 +985,18 @@ static cc_result Music_PlayOgg(struct Stream* source) {
res = Music_Buffer(&data[chunkSize * cur], samplesPerSecond, &vorbis); res = Music_Buffer(&data[chunkSize * cur], samplesPerSecond, &vorbis);
cur = (cur + 1) % AUDIO_MAX_BUFFERS; cur = (cur + 1) % AUDIO_MAX_BUFFERS;
} }
if (music_pendingStop) goto cleanup; if (music_stopping) goto cleanup;
res = Audio_Play(&music_ctx); res = Audio_Play(&music_ctx);
if (res) goto cleanup; if (res) goto cleanup;
while (!music_pendingStop) { while (!music_stopping) {
#ifdef CC_BUILD_ANDROID #ifdef CC_BUILD_ANDROID
/* Don't play music while in the background on Android */ /* Don't play music while in the background on Android */
/* TODO: Not use such a terrible approach */ /* TODO: Not use such a terrible approach */
if (!WindowInfo.Handle) { if (!WindowInfo.Handle) {
Audio_Pause(&music_ctx); Audio_Pause(&music_ctx);
while (!WindowInfo.Handle && !music_pendingStop) { while (!WindowInfo.Handle && !music_stopping) {
Thread_Sleep(10); continue; Thread_Sleep(10); continue;
} }
Audio_Play(&music_ctx); Audio_Play(&music_ctx);
@ -999,7 +1004,7 @@ static cc_result Music_PlayOgg(struct Stream* source) {
#endif #endif
res = Audio_Poll(&music_ctx, &inUse); res = Audio_Poll(&music_ctx, &inUse);
if (res) { music_pendingStop = true; break; } if (res) { music_stopping = true; break; }
if (inUse >= AUDIO_MAX_BUFFERS) { if (inUse >= AUDIO_MAX_BUFFERS) {
Thread_Sleep(10); continue; Thread_Sleep(10); continue;
@ -1012,7 +1017,7 @@ static cc_result Music_PlayOgg(struct Stream* source) {
if (res) break; if (res) break;
} }
if (music_pendingStop) { if (music_stopping) {
/* must close audio context, as otherwise some of the audio */ /* must close audio context, as otherwise some of the audio */
/* context's internal audio buffers may have a reference */ /* context's internal audio buffers may have a reference */
/* to the `data` buffer which will be freed after this */ /* to the `data` buffer which will be freed after this */
@ -1031,36 +1036,33 @@ cleanup:
return res == ERR_END_OF_STREAM ? 0 : res; return res == ERR_END_OF_STREAM ? 0 : res;
} }
#define MUSIC_MAX_FILES 512 static void Music_AddFile(const cc_string* path, void* obj) {
static void Music_RunLoop(void) { struct StringsBuffer* files = (struct StringsBuffer*)obj;
static const cc_string ogg = String_FromConst(".ogg"); static const cc_string ogg = String_FromConst(".ogg");
char pathBuffer[FILENAME_SIZE];
if (!String_CaselessEnds(path, &ogg)) return;
StringsBuffer_Add(files, path);
}
static void Music_RunLoop(void) {
struct StringsBuffer files;
cc_string path; cc_string path;
unsigned short musicFiles[MUSIC_MAX_FILES];
cc_string file;
RNGState rnd; RNGState rnd;
struct Stream stream; struct Stream stream;
int i, count = 0, idx, delay; int idx, delay;
cc_result res = 0; cc_result res = 0;
for (i = 0; i < files.count && count < MUSIC_MAX_FILES; i++) { StringsBuffer_SetLengthBits(&files, STRINGSBUFFER_DEF_LEN_SHIFT);
file = StringsBuffer_UNSAFE_Get(&files, i); StringsBuffer_Init(&files);
if (!String_CaselessEnds(&file, &ogg)) continue; Directory_Enum(&audio_dir, &files, Music_AddFile);
musicFiles[count++] = i;
}
Random_SeedFromCurrentTime(&rnd); Random_SeedFromCurrentTime(&rnd);
Audio_Init(&music_ctx, AUDIO_MAX_BUFFERS); Audio_Init(&music_ctx, AUDIO_MAX_BUFFERS);
while (!music_pendingStop && count) { while (!music_stopping && files.count) {
idx = Random_Next(&rnd, count); idx = Random_Next(&rnd, files.count);
file = StringsBuffer_UNSAFE_Get(&files, musicFiles[idx]); path = StringsBuffer_UNSAFE_Get(&files, idx);
Platform_Log1("playing music file: %s", &path);
String_InitArray(path, pathBuffer);
String_Format1(&path, "audio/%s", &file);
Platform_Log1("playing music file: %s", &file);
res = Stream_OpenFile(&stream, &path); res = Stream_OpenFile(&stream, &path);
if (res) { Logger_SysWarn2(res, "opening", &path); break; } if (res) { Logger_SysWarn2(res, "opening", &path); break; }
@ -1074,7 +1076,7 @@ static void Music_RunLoop(void) {
res = stream.Close(&stream); res = stream.Close(&stream);
if (res) { Logger_SysWarn2(res, "closing", &path); break; } if (res) { Logger_SysWarn2(res, "closing", &path); break; }
if (music_pendingStop) break; if (music_stopping) break;
delay = Random_Range(&rnd, music_minDelay, music_maxDelay); delay = Random_Range(&rnd, music_minDelay, music_maxDelay);
Waitable_WaitFor(music_waitable, delay); Waitable_WaitFor(music_waitable, delay);
} }
@ -1084,13 +1086,14 @@ static void Music_RunLoop(void) {
Audio_MusicVolume = 0; Audio_MusicVolume = 0;
} }
Audio_Close(&music_ctx); Audio_Close(&music_ctx);
StringsBuffer_Clear(&files);
if (music_joining) return; if (music_joining) return;
Thread_Detach(music_thread); Thread_Detach(music_thread);
music_thread = NULL; music_thread = NULL;
} }
static void Music_Init(void) { static void Music_Start(void) {
if (music_thread) return; if (music_thread) return;
if (!AudioBackend_Init()) { if (!AudioBackend_Init()) {
AudioBackend_Free(); AudioBackend_Free();
@ -1099,70 +1102,62 @@ static void Music_Init(void) {
} }
music_joining = false; music_joining = false;
music_pendingStop = false; music_stopping = false;
music_thread = Thread_Start(Music_RunLoop); music_thread = Thread_Start(Music_RunLoop);
} }
static void Music_Free(void) { static void Music_Stop(void) {
music_joining = true; music_joining = true;
music_pendingStop = true; music_stopping = true;
Waitable_Signal(music_waitable); Waitable_Signal(music_waitable);
if (music_thread) Thread_Join(music_thread); if (music_thread) Thread_Join(music_thread);
music_thread = NULL; music_thread = NULL;
} }
void Audio_SetMusic(int volume) { static void Music_Init(void) {
Audio_MusicVolume = volume; int volume;
if (volume) Music_Init(); /* music is delayed between 2 - 7 minutes by default */
else Music_Free(); music_minDelay = Options_GetInt(OPT_MIN_MUSIC_DELAY, 0, 3600, 120) * MILLIS_PER_SEC;
music_maxDelay = Options_GetInt(OPT_MAX_MUSIC_DELAY, 0, 3600, 420) * MILLIS_PER_SEC;
music_waitable = Waitable_Create();
volume = GetVolume(OPT_MUSIC_VOLUME, OPT_USE_MUSIC);
Audio_SetMusic(volume);
} }
static void Music_Free(void) {
Music_Stop();
Waitable_Free(music_waitable);
}
#endif
/*########################################################################################################################* /*########################################################################################################################*
*--------------------------------------------------------General----------------------------------------------------------* *--------------------------------------------------------General----------------------------------------------------------*
*#########################################################################################################################*/ *#########################################################################################################################*/
static int Audio_LoadVolume(const char* volKey, const char* boolKey) { void Audio_SetSounds(int volume) {
int volume = Options_GetInt(volKey, 0, 100, 0); Audio_SoundsVolume = volume;
if (volume) return volume; if (volume) Sounds_Start();
else Sounds_Stop();
volume = Options_GetBool(boolKey, false) ? 100 : 0;
Options_Set(boolKey, NULL);
return volume;
} }
static void Audio_FilesCallback(const cc_string* path, void* obj) { void Audio_SetMusic(int volume) {
cc_string relPath = *path; Audio_MusicVolume = volume;
Utils_UNSAFE_TrimFirstDirectory(&relPath); if (volume) Music_Start();
StringsBuffer_Add(&files, &relPath); else Music_Stop();
} }
static void OnInit(void) { static void OnInit(void) {
static const cc_string path = String_FromConst("audio"); Sounds_Init();
int volume; Music_Init();
Directory_Enum(&path, NULL, Audio_FilesCallback);
music_waitable = Waitable_Create();
/* music is delayed between 2 - 7 minutes by default */
music_minDelay = Options_GetInt(OPT_MIN_MUSIC_DELAY, 0, 3600, 120) * MILLIS_PER_SEC;
music_maxDelay = Options_GetInt(OPT_MAX_MUSIC_DELAY, 0, 3600, 420) * MILLIS_PER_SEC;
volume = Audio_LoadVolume(OPT_MUSIC_VOLUME, OPT_USE_MUSIC);
Audio_SetMusic(volume);
volume = Audio_LoadVolume(OPT_SOUND_VOLUME, OPT_USE_SOUND);
Audio_SetSounds(volume);
Event_Register_(&UserEvents.BlockChanged, NULL, Audio_PlayBlockSound);
} }
static void OnFree(void) { static void OnFree(void) {
Music_Free();
Sounds_Free(); Sounds_Free();
Waitable_Free(music_waitable); Music_Free();
AudioBackend_Free(); AudioBackend_Free();
} }
#endif
struct IGameComponent Audio_Component = { struct IGameComponent Audio_Component = {
OnInit, /* Init */ OnInit, /* Init */

View File

@ -8,6 +8,11 @@ struct IGameComponent;
extern struct IGameComponent Audio_Component; extern struct IGameComponent Audio_Component;
struct AudioContext; struct AudioContext;
struct Sound {
int channels, sampleRate;
cc_uint8* data; cc_uint32 size;
};
/* Volume sounds are played at, from 0-100. */ /* Volume sounds are played at, from 0-100. */
/* NOTE: Use Audio_SetSounds, don't change this directly. */ /* NOTE: Use Audio_SetSounds, don't change this directly. */
extern int Audio_SoundsVolume; extern int Audio_SoundsVolume;
@ -19,21 +24,19 @@ void Audio_SetMusic(int volume);
void Audio_SetSounds(int volume); void Audio_SetSounds(int volume);
void Audio_PlayDigSound(cc_uint8 type); void Audio_PlayDigSound(cc_uint8 type);
void Audio_PlayStepSound(cc_uint8 type); void Audio_PlayStepSound(cc_uint8 type);
#define AUDIO_MAX_BUFFERS 4 #define AUDIO_MAX_BUFFERS 4
/* Information about a source of audio. */
struct AudioFormat { int channels, sampleRate; };
#define AudioFormat_Eq(a, b) ((a)->channels == (b)->channels && (a)->sampleRate == (b)->sampleRate)
/* Initialises an audio context. */ /* Initialises an audio context. */
void Audio_Init(struct AudioContext* ctx, int buffers); void Audio_Init(struct AudioContext* ctx, int buffers);
/* Stops any playing audio and then frees the audio context. */ /* Stops any playing audio and then frees the audio context. */
void Audio_Close(struct AudioContext* ctx); void Audio_Close(struct AudioContext* ctx);
/* Returns the format audio is played in. */ /* Returns number of channels of the format audio is played in. */
struct AudioFormat* Audio_GetFormat(struct AudioContext* ctx); int Audio_GetChannels(struct AudioContext* ctx);
/* Returns number of channels of the format audio is played in. */
int Audio_GetSampleRate(struct AudioContext* ctx);
/* Sets the format of the audio data to be played. */ /* Sets the format of the audio data to be played. */
/* NOTE: Changing the format can be expensive, depending on the backend. */ /* NOTE: Changing the format can be expensive, depending on the backend. */
cc_result Audio_SetFormat(struct AudioContext* ctx, struct AudioFormat* format); cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate);
/* Queues the given audio data for playing. */ /* Queues the given audio data for playing. */
/* NOTE: You MUST ensure Audio_Poll indicates a buffer is free before calling this. */ /* NOTE: You MUST ensure Audio_Poll indicates a buffer is free before calling this. */
/* NOTE: Some backends directly read from the data - therefore you MUST NOT modify it */ /* NOTE: Some backends directly read from the data - therefore you MUST NOT modify it */
@ -44,4 +47,6 @@ cc_result Audio_Play(struct AudioContext* ctx);
/* Returns the number of buffers being played or queued */ /* Returns the number of buffers being played or queued */
/* (e.g. if inUse is 0, no audio buffers are being played or queued) */ /* (e.g. if inUse is 0, no audio buffers are being played or queued) */
cc_result Audio_Poll(struct AudioContext* ctx, int* inUse); cc_result Audio_Poll(struct AudioContext* ctx, int* inUse);
cc_result Audio_PlaySound(struct AudioContext* ctx, struct Sound* snd, int volume);
#endif #endif

View File

@ -239,7 +239,8 @@ Thus it is **NOT SAFE** to allocate a string on the stack. */
#define CC_BUILD_GLMODERN #define CC_BUILD_GLMODERN
#define CC_BUILD_GLES #define CC_BUILD_GLES
#define CC_BUILD_TOUCH #define CC_BUILD_TOUCH
#define CC_BUILD_NOAUDIO #define CC_BUILD_NOSOUNDS
#define CC_BUILD_NOMUSIC
#define CC_BUILD_MINFILES #define CC_BUILD_MINFILES
#endif #endif
#endif #endif

View File

@ -768,7 +768,7 @@ cc_bool Convert_ParseBool(const cc_string* str, cc_bool* value) {
#define StringsBuffer_GetLength(raw) ((raw) & buffer->_lenMask) #define StringsBuffer_GetLength(raw) ((raw) & buffer->_lenMask)
#define StringsBuffer_PackOffset(off) ((off) << buffer->_lenShift) #define StringsBuffer_PackOffset(off) ((off) << buffer->_lenShift)
CC_NOINLINE static void StringsBuffer_Init(struct StringsBuffer* buffer) { void StringsBuffer_Init(struct StringsBuffer* buffer) {
buffer->count = 0; buffer->count = 0;
buffer->totalLength = 0; buffer->totalLength = 0;
buffer->textBuffer = buffer->_defaultBuffer; buffer->textBuffer = buffer->_defaultBuffer;

View File

@ -226,18 +226,20 @@ struct StringsBuffer {
int _lenMask; int _lenMask;
}; };
/* Sets the number of bits in an entry's flags that are used to store its length. */ /* Resets counts to 0 and other state to default */
void StringsBuffer_Init(struct StringsBuffer* buffer);
/* Sets the number of bits in an entry's flags that are used to store its length */
/* (e.g. if bits is 9, then the maximum length of an entry is 2^9-1 = 511) */ /* (e.g. if bits is 9, then the maximum length of an entry is 2^9-1 = 511) */
void StringsBuffer_SetLengthBits(struct StringsBuffer* buffer, int bits); void StringsBuffer_SetLengthBits(struct StringsBuffer* buffer, int bits);
/* Resets counts to 0, and frees any allocated memory. */ /* Frees any allocated memory and then called StringsBuffer_Init */
CC_NOINLINE void StringsBuffer_Clear(struct StringsBuffer* buffer); CC_NOINLINE void StringsBuffer_Clear(struct StringsBuffer* buffer);
/* UNSAFE: Returns a direct pointer to the i'th string in the given buffer. */ /* UNSAFE: Returns a direct pointer to the i'th string in the given buffer */
/* You MUST NOT change the characters of this string. Copy to another string if necessary.*/ /* You MUST NOT change the characters of this string. Copy to another string if necessary.*/
CC_API STRING_REF cc_string StringsBuffer_UNSAFE_Get(struct StringsBuffer* buffer, int i); CC_API STRING_REF cc_string StringsBuffer_UNSAFE_Get(struct StringsBuffer* buffer, int i);
STRING_REF void StringsBuffer_UNSAFE_GetRaw(struct StringsBuffer* buffer, int i, cc_string* dst); STRING_REF void StringsBuffer_UNSAFE_GetRaw(struct StringsBuffer* buffer, int i, cc_string* dst);
/* Adds a given string to the end of the given buffer. */ /* Adds the given string to the end of the given buffer */
CC_API void StringsBuffer_Add(struct StringsBuffer* buffer, const cc_string* str); CC_API void StringsBuffer_Add(struct StringsBuffer* buffer, const cc_string* str);
/* Removes the i'th string from the given buffer, shifting following strings downwards. */ /* Removes the i'th string from the given buffer, shifting following strings downwards */
CC_API void StringsBuffer_Remove(struct StringsBuffer* buffer, int index); CC_API void StringsBuffer_Remove(struct StringsBuffer* buffer, int index);
/* Performs line wrapping on the given string. */ /* Performs line wrapping on the given string. */