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"
#endif
int Audio_SoundsVolume, Audio_MusicVolume;
static const cc_string audio_dir = String_FromConst("audio");
#if defined CC_BUILD_NOAUDIO
/* 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) {
static void ApplyVolume(cc_int16* samples, int count, int volume) {
int i;
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
/*########################################################################################################################*
*------------------------------------------------------OpenAL backend-----------------------------------------------------*
@ -110,12 +101,13 @@ struct AudioContext {
ALuint source;
ALuint buffers[AUDIO_MAX_BUFFERS];
ALuint freeIDs[AUDIO_MAX_BUFFERS];
struct AudioFormat format;
int count, free;
int count, free, channels, sampleRate;
ALenum dataFormat;
cc_uint32 _tmpSize; void* _tmpData;
};
static void* audio_device;
static void* audio_context;
#define AUDIO_HAS_BACKEND
#if defined CC_BUILD_WIN
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;
}
static cc_result AudioBackend_SetFormat(struct AudioContext* ctx, struct AudioFormat* format) {
static cc_result AudioBackend_UpdateFormat(struct AudioContext* ctx) {
ALenum i, err;
ctx->dataFormat = GetALFormat(format->channels);
ctx->dataFormat = GetALFormat(ctx->channels);
if (!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;
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;
_alSourceQueueBuffers(ctx->source, 1, &buffer);
if ((err = _alGetError())) return err;
@ -337,11 +329,12 @@ WINMMAPI MMRESULT WINAPI waveOutReset(HWAVEOUT hwo);
struct AudioContext {
HWAVEOUT handle;
WAVEHDR headers[AUDIO_MAX_BUFFERS];
struct AudioFormat format;
int count;
int count, channels, sampleRate;
cc_uint32 _tmpSize; void* _tmpData;
};
static cc_bool AudioBackend_Init(void) { return true; }
static void AudioBackend_Free(void) { }
#define AUDIO_HAS_BACKEND
void Audio_Init(struct AudioContext* ctx, int buffers) {
int i;
@ -360,16 +353,16 @@ static cc_result AudioBackend_Reset(struct AudioContext* ctx) {
return res;
}
static cc_result AudioBackend_SetFormat(struct AudioContext* ctx, struct AudioFormat* format) {
static cc_result AudioBackend_UpdateFormat(struct AudioContext* ctx) {
WAVEFORMATEX fmt;
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;
fmt.wFormatTag = WAVE_FORMAT_PCM;
fmt.nChannels = format->channels;
fmt.nSamplesPerSec = format->sampleRate;
fmt.nAvgBytesPerSec = format->sampleRate * sampleSize;
fmt.nChannels = ctx->channels;
fmt.nSamplesPerSec = ctx->sampleRate;
fmt.nAvgBytesPerSec = ctx->sampleRate * sampleSize;
fmt.nBlockAlign = sampleSize;
fmt.wBitsPerSample = 16;
fmt.cbSize = 0;
@ -430,13 +423,14 @@ cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) {
static SLObjectItf slEngineObject;
static SLEngineItf slEngineEngine;
static SLObjectItf slOutputObject;
#define AUDIO_HAS_BACKEND
struct AudioContext {
struct AudioFormat format;
int count;
int count, channels, sampleRate;
SLObjectItf bqPlayerObject;
SLPlayItf bqPlayerPlayer;
SLBufferQueueItf bqPlayerQueue;
cc_uint32 _tmpSize; void* _tmpData;
};
static SLresult (SLAPIENTRY *_slCreateEngine)(
@ -521,7 +515,7 @@ static void AudioBackend_Reset(struct AudioContext* ctx) {
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_OutputMix output;
SLObjectItf bqPlayerObject;
@ -534,8 +528,8 @@ static cc_result AudioBackend_SetFormat(struct AudioContext* ctx, struct AudioFo
AudioBackend_Reset(ctx);
fmt.formatType = SL_DATAFORMAT_PCM;
fmt.numChannels = format->channels;
fmt.samplesPerSec = format->sampleRate * 1000;
fmt.numChannels = ctx->channels;
fmt.samplesPerSec = ctx->sampleRate * 1000;
fmt.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
fmt.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
fmt.channelMask = 0;
@ -598,14 +592,18 @@ cc_result Audio_Poll(struct AudioContext* ctx, int* inUse) {
/*########################################################################################################################*
*---------------------------------------------------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) {
struct AudioFormat* cur = &ctx->format;
if (AudioFormat_Eq(cur, format)) return 0;
ctx->format = *format;
cc_result Audio_SetFormat(struct AudioContext* ctx, int channels, int sampleRate) {
if (ctx->channels == channels && ctx->sampleRate == sampleRate) return 0;
return AudioBackend_SetFormat(ctx, format);
ctx->channels = channels;
ctx->sampleRate = sampleRate;
return AudioBackend_UpdateFormat(ctx);
}
void Audio_Close(struct AudioContext* ctx) {
@ -613,31 +611,77 @@ void Audio_Close(struct AudioContext* ctx) {
AudioBackend_Stop(ctx);
Audio_Poll(ctx, &inUse); /* unqueue buffers */
ctx->count = 0;
ctx->format.channels = 0;
ctx->format.sampleRate = 0;
ctx->count = 0;
ctx->channels = 0;
ctx->sampleRate = 0;
Mem_Free(ctx->_tmpData);
ctx->_tmpData = NULL;
ctx->_tmpSize = 0;
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 {
struct AudioFormat format;
cc_uint8* data; cc_uint32 size;
};
#ifdef CC_BUILD_NOSOUNDS
/* Can't use mojang's sound assets, so just stub everything out */
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 SOUND_MAX_CONTEXTS 8
struct SoundGroup {
int count;
struct Sound sounds[AUDIO_MAX_SOUNDS];
};
struct Soundboard { struct SoundGroup groups[SOUND_COUNT]; };
struct Soundboard {
RNGState rnd; cc_bool inited;
struct SoundGroup groups[SOUND_COUNT];
};
static struct Soundboard digBoard, stepBoard;
static struct AudioContext sound_contexts[SOUND_MAX_CONTEXTS];
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_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 (Stream_GetU16_LE(tmp + 0) != 1) return WAV_ERR_DATA_TYPE;
snd->format.channels = Stream_GetU16_LE(tmp + 2);
snd->format.sampleRate = Stream_GetU32_LE(tmp + 4);
snd->channels = Stream_GetU16_LE(tmp + 2);
snd->sampleRate = Stream_GetU32_LE(tmp + 4);
/* tmp[8] (6) alignment data and stuff */
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) {
cc_string path; char pathBuffer[FILENAME_SIZE];
static cc_result Sound_ReadWave(const cc_string* path, struct Sound* snd) {
struct Stream stream;
cc_result res;
String_InitArray(path, pathBuffer);
String_Format1(&path, "audio/%s", filename);
res = Stream_OpenFile(&stream, &path);
res = Stream_OpenFile(&stream, path);
if (res) return res;
res = Sound_ReadWaveData(&stream, snd);
if (res) { stream.Close(&stream); return res; }
@ -711,48 +750,43 @@ static struct SoundGroup* Soundboard_Find(struct Soundboard* board, const cc_str
return NULL;
}
static void Soundboard_Init(struct Soundboard* board, const cc_string* boardName) {
cc_string file, name;
static void Soundboard_Load(struct Soundboard* board, const cc_string* boardName, const cc_string* file) {
struct SoundGroup* group;
struct Sound* snd;
cc_string name = *file;
cc_result res;
int i, dotIndex;
board->inited = true;
int dotIndex;
Utils_UNSAFE_TrimFirstDirectory(&name);
for (i = 0; i < files.count; i++) {
file = StringsBuffer_UNSAFE_Get(&files, i);
name = file;
/* dig_grass1.wav -> dig_grass1 */
dotIndex = String_LastIndexOf(&name, '.');
if (dotIndex >= 0) name.length = dotIndex;
if (!String_CaselessStarts(&name, boardName)) return;
/* dig_grass1.wav -> dig_grass1 */
dotIndex = String_LastIndexOf(&name, '.');
if (dotIndex >= 0) name.length = dotIndex;
if (!String_CaselessStarts(&name, boardName)) continue;
/* Convert dig_grass1 to grass */
name = String_UNSAFE_SubstringAt(&name, boardName->length);
name = String_UNSAFE_Substring(&name, 0, name.length - 1);
/* Convert dig_grass1 to grass */
name = String_UNSAFE_SubstringAt(&name, boardName->length);
name = String_UNSAFE_Substring(&name, 0, name.length - 1);
group = Soundboard_Find(board, &name);
if (!group) {
Chat_Add1("&cUnknown sound group '%s'", &name); continue;
}
if (group->count == Array_Elems(group->sounds)) {
Chat_AddRaw("&cCannot have more than 10 sounds in a group"); continue;
}
snd = &group->sounds[group->count];
res = Sound_ReadWave(&file, snd);
if (res) {
Logger_SysWarn2(res, "decoding", &file);
Mem_Free(snd->data);
snd->data = NULL;
snd->size = 0;
} else { group->count++; }
group = Soundboard_Find(board, &name);
if (!group) {
Chat_Add1("&cUnknown sound group '%s'", &name); return;
}
if (group->count == Array_Elems(group->sounds)) {
Chat_AddRaw("&cCannot have more than 10 sounds in a group"); return;
}
snd = &group->sounds[group->count];
res = Sound_ReadWave(file, snd);
if (res) {
Logger_SysWarn2(res, "decoding", file);
Mem_Free(snd->data);
snd->data = NULL;
snd->size = 0;
} 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;
int idx;
@ -762,72 +796,28 @@ static struct Sound* Soundboard_PickRandom(struct Soundboard* board, cc_uint8 ty
group = &board->groups[type];
if (!group->count) return NULL;
idx = Random_Next(&board->rnd, group->count);
idx = Random_Next(&sounds_rnd, group->count);
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) {
Logger_SimpleWarn(res, "playing sounds");
Chat_AddRaw("&cDisabling sounds");
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) {
struct Sound* snd;
struct AudioFormat fmt;
struct SoundOutput* outputs;
struct SoundOutput* output;
struct AudioFormat* l;
int inUse;
int i, volume;
const struct Sound* snd_;
struct Sound snd;
struct AudioContext* ctx;
int chans, freq;
int inUse, i, volume;
cc_result res;
if (type == SOUND_NONE || !Audio_SoundsVolume) return;
snd = Soundboard_PickRandom(board, type);
if (!snd) return;
snd_ = Soundboard_PickRandom(board, type);
if (!snd_) return;
if (!AudioBackend_Init()) {
AudioBackend_Free();
@ -835,45 +825,50 @@ static void Sounds_Play(cc_uint8 type, struct Soundboard* board) {
return;
}
fmt = snd->format;
volume = Audio_SoundsVolume;
outputs = fmt.channels == 1 ? monoOutputs : stereoOutputs;
snd = *snd_;
volume = Audio_SoundsVolume;
if (board == &digBoard) {
if (type == SOUND_METAL) fmt.sampleRate = (fmt.sampleRate * 6) / 5;
else fmt.sampleRate = (fmt.sampleRate * 4) / 5;
if (type == SOUND_METAL) snd.sampleRate = (snd.sampleRate * 6) / 5;
else snd.sampleRate = (snd.sampleRate * 4) / 5;
} else {
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 */
for (i = 0; i < AUDIO_MAX_HANDLES; i++) {
output = &outputs[i];
if (!output->ctx.count) {
Audio_Init(&output->ctx, 1);
for (i = 0; i < SOUND_MAX_CONTEXTS; i++) {
ctx = &sound_contexts[i];
if (!ctx->count) {
Audio_Init(ctx, 1);
} else {
res = Audio_Poll(&output->ctx, &inUse);
res = Audio_Poll(ctx, &inUse);
if (res) { Sounds_Fail(res); return; }
if (inUse > 0) continue;
}
l = Audio_GetFormat(&output->ctx);
if (!l->channels || AudioFormat_Eq(l, &fmt)) {
Sounds_PlayRaw(output, snd, &fmt, volume); return;
chans = Audio_GetChannels(ctx);
freq = Audio_GetSampleRate(ctx);
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) */
for (i = 0; i < AUDIO_MAX_HANDLES; i++) {
output = &outputs[i];
res = Audio_Poll(&output->ctx, &inUse);
/* Try again with all contexts, even if need to recreate one (expensive) */
for (i = 0; i < SOUND_MAX_CONTEXTS; i++) {
ctx = &sound_contexts[i];
res = Audio_Poll(ctx, &inUse);
if (res) { Sounds_Fail(res); return; }
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) {
Audio_PlayDigSound(Blocks.DigSounds[old]);
} 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]);
}
}
static void Sounds_FreeOutputs(struct SoundOutput* outputs) {
int i;
for (i = 0; i < AUDIO_MAX_HANDLES; i++) {
if (!outputs[i].ctx.count) continue;
Audio_Close(&outputs[i].ctx);
static void Sounds_LoadFile(const cc_string* path, void* obj) {
static const cc_string dig = String_FromConst("dig_");
static const cc_string step = String_FromConst("step_");
Soundboard_Load(&digBoard, &dig, path);
Soundboard_Load(&stepBoard, &step, path);
}
Mem_Free(outputs[i].buffer);
outputs[i].buffer = NULL;
outputs[i].capacity = 0;
static cc_bool sounds_loaded;
static void Sounds_Start(void) {
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 const cc_string dig = String_FromConst("dig_");
static const cc_string step = String_FromConst("step_");
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;
int volume = GetVolume(OPT_SOUND_VOLUME, OPT_USE_SOUND);
Audio_SetSounds(volume);
Event_Register_(&UserEvents.BlockChanged, NULL, Audio_PlayBlockSound);
}
static void Sounds_Free(void) { Sounds_Stop(); }
void Audio_PlayDigSound(cc_uint8 type) { Sounds_Play(type, &digBoard); }
void Audio_PlayStepSound(cc_uint8 type) { Sounds_Play(type, &stepBoard); }
#endif
/*########################################################################################################################*
*--------------------------------------------------------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 void* music_thread;
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 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];
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);
if (res2) { music_pendingStop = true; return res2; }
if (res2) { music_stopping = true; return res2; }
return res;
}
static cc_result Music_PlayOgg(struct Stream* source) {
struct OggState ogg;
struct VorbisState vorbis = { 0 };
struct AudioFormat fmt;
int channels, sampleRate;
int chunkSize, samplesPerSecond;
cc_int16* data = NULL;
@ -962,14 +967,14 @@ static cc_result Music_PlayOgg(struct Stream* source) {
vorbis.source = &ogg;
if ((res = Vorbis_DecodeHeaders(&vorbis))) goto cleanup;
fmt.channels = vorbis.channels;
fmt.sampleRate = vorbis.sampleRate;
if ((res = Audio_SetFormat(&music_ctx, &fmt))) goto cleanup;
channels = vorbis.channels;
sampleRate = vorbis.sampleRate;
if ((res = Audio_SetFormat(&music_ctx, channels, sampleRate))) goto cleanup;
/* largest possible vorbis frame decodes to blocksize1 * channels samples, */
/* so can end up decoding slightly over a second of audio */
chunkSize = fmt.channels * (fmt.sampleRate + vorbis.blockSizes[1]);
samplesPerSecond = fmt.channels * fmt.sampleRate;
chunkSize = channels * (sampleRate + vorbis.blockSizes[1]);
samplesPerSecond = channels * sampleRate;
cur = 0;
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);
cur = (cur + 1) % AUDIO_MAX_BUFFERS;
}
if (music_pendingStop) goto cleanup;
if (music_stopping) goto cleanup;
res = Audio_Play(&music_ctx);
if (res) goto cleanup;
while (!music_pendingStop) {
while (!music_stopping) {
#ifdef CC_BUILD_ANDROID
/* Don't play music while in the background on Android */
/* TODO: Not use such a terrible approach */
if (!WindowInfo.Handle) {
Audio_Pause(&music_ctx);
while (!WindowInfo.Handle && !music_pendingStop) {
while (!WindowInfo.Handle && !music_stopping) {
Thread_Sleep(10); continue;
}
Audio_Play(&music_ctx);
@ -999,7 +1004,7 @@ static cc_result Music_PlayOgg(struct Stream* source) {
#endif
res = Audio_Poll(&music_ctx, &inUse);
if (res) { music_pendingStop = true; break; }
if (res) { music_stopping = true; break; }
if (inUse >= AUDIO_MAX_BUFFERS) {
Thread_Sleep(10); continue;
@ -1012,7 +1017,7 @@ static cc_result Music_PlayOgg(struct Stream* source) {
if (res) break;
}
if (music_pendingStop) {
if (music_stopping) {
/* must close audio context, as otherwise some of the audio */
/* context's internal audio buffers may have a reference */
/* to the `data` buffer which will be freed after this */
@ -1031,36 +1036,33 @@ cleanup:
return res == ERR_END_OF_STREAM ? 0 : res;
}
#define MUSIC_MAX_FILES 512
static void Music_AddFile(const cc_string* path, void* obj) {
struct StringsBuffer* files = (struct StringsBuffer*)obj;
static const cc_string ogg = String_FromConst(".ogg");
if (!String_CaselessEnds(path, &ogg)) return;
StringsBuffer_Add(files, path);
}
static void Music_RunLoop(void) {
static const cc_string ogg = String_FromConst(".ogg");
char pathBuffer[FILENAME_SIZE];
struct StringsBuffer files;
cc_string path;
unsigned short musicFiles[MUSIC_MAX_FILES];
cc_string file;
RNGState rnd;
struct Stream stream;
int i, count = 0, idx, delay;
int idx, delay;
cc_result res = 0;
for (i = 0; i < files.count && count < MUSIC_MAX_FILES; i++) {
file = StringsBuffer_UNSAFE_Get(&files, i);
if (!String_CaselessEnds(&file, &ogg)) continue;
musicFiles[count++] = i;
}
StringsBuffer_SetLengthBits(&files, STRINGSBUFFER_DEF_LEN_SHIFT);
StringsBuffer_Init(&files);
Directory_Enum(&audio_dir, &files, Music_AddFile);
Random_SeedFromCurrentTime(&rnd);
Audio_Init(&music_ctx, AUDIO_MAX_BUFFERS);
while (!music_pendingStop && count) {
idx = Random_Next(&rnd, count);
file = StringsBuffer_UNSAFE_Get(&files, musicFiles[idx]);
String_InitArray(path, pathBuffer);
String_Format1(&path, "audio/%s", &file);
Platform_Log1("playing music file: %s", &file);
while (!music_stopping && files.count) {
idx = Random_Next(&rnd, files.count);
path = StringsBuffer_UNSAFE_Get(&files, idx);
Platform_Log1("playing music file: %s", &path);
res = Stream_OpenFile(&stream, &path);
if (res) { Logger_SysWarn2(res, "opening", &path); break; }
@ -1074,7 +1076,7 @@ static void Music_RunLoop(void) {
res = stream.Close(&stream);
if (res) { Logger_SysWarn2(res, "closing", &path); break; }
if (music_pendingStop) break;
if (music_stopping) break;
delay = Random_Range(&rnd, music_minDelay, music_maxDelay);
Waitable_WaitFor(music_waitable, delay);
}
@ -1084,13 +1086,14 @@ static void Music_RunLoop(void) {
Audio_MusicVolume = 0;
}
Audio_Close(&music_ctx);
StringsBuffer_Clear(&files);
if (music_joining) return;
Thread_Detach(music_thread);
music_thread = NULL;
}
static void Music_Init(void) {
static void Music_Start(void) {
if (music_thread) return;
if (!AudioBackend_Init()) {
AudioBackend_Free();
@ -1098,71 +1101,63 @@ static void Music_Init(void) {
return;
}
music_joining = false;
music_pendingStop = false;
music_thread = Thread_Start(Music_RunLoop);
music_joining = false;
music_stopping = false;
music_thread = Thread_Start(Music_RunLoop);
}
static void Music_Free(void) {
music_joining = true;
music_pendingStop = true;
static void Music_Stop(void) {
music_joining = true;
music_stopping = true;
Waitable_Signal(music_waitable);
if (music_thread) Thread_Join(music_thread);
music_thread = NULL;
}
void Audio_SetMusic(int volume) {
Audio_MusicVolume = volume;
if (volume) Music_Init();
else Music_Free();
static void Music_Init(void) {
int volume;
/* 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;
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----------------------------------------------------------*
*#########################################################################################################################*/
static int Audio_LoadVolume(const char* volKey, const char* boolKey) {
int volume = Options_GetInt(volKey, 0, 100, 0);
if (volume) return volume;
volume = Options_GetBool(boolKey, false) ? 100 : 0;
Options_Set(boolKey, NULL);
return volume;
void Audio_SetSounds(int volume) {
Audio_SoundsVolume = volume;
if (volume) Sounds_Start();
else Sounds_Stop();
}
static void Audio_FilesCallback(const cc_string* path, void* obj) {
cc_string relPath = *path;
Utils_UNSAFE_TrimFirstDirectory(&relPath);
StringsBuffer_Add(&files, &relPath);
void Audio_SetMusic(int volume) {
Audio_MusicVolume = volume;
if (volume) Music_Start();
else Music_Stop();
}
static void OnInit(void) {
static const cc_string path = String_FromConst("audio");
int volume;
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);
Sounds_Init();
Music_Init();
}
static void OnFree(void) {
Music_Free();
Sounds_Free();
Waitable_Free(music_waitable);
Music_Free();
AudioBackend_Free();
}
#endif
struct IGameComponent Audio_Component = {
OnInit, /* Init */

View File

@ -8,6 +8,11 @@ struct IGameComponent;
extern struct IGameComponent Audio_Component;
struct AudioContext;
struct Sound {
int channels, sampleRate;
cc_uint8* data; cc_uint32 size;
};
/* Volume sounds are played at, from 0-100. */
/* NOTE: Use Audio_SetSounds, don't change this directly. */
extern int Audio_SoundsVolume;
@ -19,21 +24,19 @@ void Audio_SetMusic(int volume);
void Audio_SetSounds(int volume);
void Audio_PlayDigSound(cc_uint8 type);
void Audio_PlayStepSound(cc_uint8 type);
#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. */
void Audio_Init(struct AudioContext* ctx, int buffers);
/* Stops any playing audio and then frees the audio context. */
void Audio_Close(struct AudioContext* ctx);
/* Returns the format audio is played in. */
struct AudioFormat* Audio_GetFormat(struct AudioContext* ctx);
/* Returns number of channels of the format audio is played in. */
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. */
/* 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. */
/* 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 */
@ -44,4 +47,6 @@ cc_result Audio_Play(struct AudioContext* ctx);
/* Returns the number of buffers 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_PlaySound(struct AudioContext* ctx, struct Sound* snd, int volume);
#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_GLES
#define CC_BUILD_TOUCH
#define CC_BUILD_NOAUDIO
#define CC_BUILD_NOSOUNDS
#define CC_BUILD_NOMUSIC
#define CC_BUILD_MINFILES
#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_PackOffset(off) ((off) << buffer->_lenShift)
CC_NOINLINE static void StringsBuffer_Init(struct StringsBuffer* buffer) {
void StringsBuffer_Init(struct StringsBuffer* buffer) {
buffer->count = 0;
buffer->totalLength = 0;
buffer->textBuffer = buffer->_defaultBuffer;

View File

@ -226,18 +226,20 @@ struct StringsBuffer {
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) */
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);
/* 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.*/
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);
/* 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);
/* 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);
/* Performs line wrapping on the given string. */