From 44ebefd06a186719379e52b809a32e7b74af1880 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Sun, 22 Sep 2024 00:10:34 +0200 Subject: [PATCH] fix sound playing, we are actually playing an audio file :) --- asset/AssetManagementSystem.h | 2 +- audio/Audio.cpp | 34 +++++++++++++++ audio/Audio.h | 29 +++++++++++-- audio/AudioSetting.h | 17 ++++++++ audio/Wav.h | 17 +++++--- camera/Camera.h | 2 +- log/Log.h | 2 + memory/DebugMemory.h | 1 - platform/win32/Library.h | 2 +- platform/win32/audio/DirectSound.h | 69 ++++++++++++++++-------------- platform/win32/audio/XAudio2.h | 68 ++++++++++++++++------------- 11 files changed, 168 insertions(+), 75 deletions(-) create mode 100644 audio/Audio.cpp diff --git a/asset/AssetManagementSystem.h b/asset/AssetManagementSystem.h index e5bb784..03ae04c 100644 --- a/asset/AssetManagementSystem.h +++ b/asset/AssetManagementSystem.h @@ -55,7 +55,7 @@ void ams_create(AssetManagementSystem* ams, BufferMemory* buf, int chunk_size, i ams->asset_data_memory.chunk_size = chunk_size; ams->asset_data_memory.last_pos = -1; ams->asset_data_memory.alignment = 1; - ams->asset_data_memory.memory = buffer_get_memory(buf, chunk_size * count); + ams->asset_data_memory.memory = buffer_get_memory(buf, chunk_size * count, 64); ams->asset_data_memory.free = (uint64 *) buffer_get_memory(buf, CEIL_DIV(count, 64) * sizeof(uint64)); ams->first = NULL; diff --git a/audio/Audio.cpp b/audio/Audio.cpp new file mode 100644 index 0000000..0c98b39 --- /dev/null +++ b/audio/Audio.cpp @@ -0,0 +1,34 @@ +/** + * Jingga + * + * @copyright Jingga + * @license OMS License 2.0 + * @version 1.0.0 + * @link https://jingga.app + */ +#ifndef TOS_AUDIO_C +#define TOS_AUDIO_C + +#include "../utils/StringUtils.h" +#include "../memory/RingMemory.h" + +#if _WIN32 + #include "../platform/win32/UtilsWin32.h" +#else + #include "../platform/linux/UtilsLinux.h" +#endif + +#include "Audio.h" +#include "Wav.h" + +void audio_from_file(RingMemory* ring, const char* path, Audio* audio) +{ + FileBody file; + file_read(path, &file, ring); + + if (str_ends_with(path, ".wav")) { + wav_audio_generate(&file, audio); + } +} + +#endif diff --git a/audio/Audio.h b/audio/Audio.h index 4772c4e..38a4f6c 100644 --- a/audio/Audio.h +++ b/audio/Audio.h @@ -10,14 +10,37 @@ #define TOS_AUDIO_H #include "../stdlib/Types.h" +#include "../utils/StringUtils.h" +#include "../memory/RingMemory.h" + +#if _WIN32 + #include "../platform/win32/UtilsWin32.h" +#else + #include "../platform/linux/UtilsLinux.h" +#endif + +#include "Audio.h" struct Audio { - uint32 sample_rate; // bits_per_sample - uint32 sample_size; // byte_per_bloc - uint32 frequency; + // bits per sample + // usually 48000 or 44100 + uint32 sample_rate; + + // bytes per bloc + // channel count * bit + // usually 2 * 16 = 4 + uint32 sample_size; + + // audio channels + // usually 2 uint32 channels; + + // usually 16 = 2 uint32 bloc_size; + + // sample_rate * sample_size uint32 byte_per_sec; + uint32 size; byte* data; // owner of data }; diff --git a/audio/AudioSetting.h b/audio/AudioSetting.h index 50ce6f0..4a5fb1e 100644 --- a/audio/AudioSetting.h +++ b/audio/AudioSetting.h @@ -15,13 +15,28 @@ #define SOUND_API_XAUDIO2 1 struct AudioSetting { + // bits per sample + // usually 48000 or 44100 uint32 sample_rate; + + // bytes per bloc + // channel count * bit + // usually 2 * 16 = 4 uint32 sample_size; + + // position in the audio data + // WARNING: not the byte position, but the index based on the sample size uint32 sample_index; + uint32 latency; + // how often has the audio_play been called (required for xaudio) + uint32 sample_output; + + // 0 - 100 int16 volume; + // max buffer content/size uint32 buffer_size; // Actual samples inside the buffer @@ -31,6 +46,8 @@ struct AudioSetting { bool is_playing = false; byte type = SOUND_API_DIRECT_SOUND; + + // @todo add more settings e.g. repeat etc }; #endif diff --git a/audio/Wav.h b/audio/Wav.h index 032943d..f48fe19 100644 --- a/audio/Wav.h +++ b/audio/Wav.h @@ -64,7 +64,7 @@ void generate_default_wav_references(const FileBody* file, Wav* wav) // Check if we can copy memory directly // The struct layout and header size should match on x86, but we still check it - if (sizeof(WavHeader) == WAV_HEADER_SIZE) { + if constexpr (sizeof(WavHeader) == WAV_HEADER_SIZE) { memcpy(&wav->header, file->content, WAV_HEADER_SIZE); // swap endian if we are on big endian system @@ -129,23 +129,26 @@ void generate_default_wav_references(const FileBody* file, Wav* wav) wav->header.data_size = SWAP_ENDIAN_LITTLE(*((uint32 *) *(wav->data + 40))); } - wav->sample_data = wav->data + WAV_HEADER_SIZE; + wav->sample_data = wav->data + WAV_HEADER_SIZE; } -void generate_wav_audio(const FileBody* src_data, Audio* audio) +void wav_audio_generate(const FileBody* src_data, Audio* audio) { // @performance We are generating the struct and then filling the data. // There is some asignment/copy overhead Wav src = {}; generate_default_wav_references(src_data, &src); - audio->sample_rate = src.header.bits_per_sample; - audio->sample_size = src.header.byte_per_bloc; - audio->frequency = src.header.frequency; + if (!src.size) { + return; + } + + audio->sample_rate = src.header.frequency; + audio->sample_size = (src.header.bits_per_sample / 8) * src.header.nbr_channels; audio->channels = src.header.nbr_channels; audio->byte_per_sec = src.header.byte_per_sec; audio->bloc_size = src.header.bloc_size; - audio->size = src.size - WAV_HEADER_SIZE; + audio->size = src.header.data_size; memcpy((void *) audio->data, src.sample_data, audio->size); } diff --git a/camera/Camera.h b/camera/Camera.h index 627b3d5..3b02339 100644 --- a/camera/Camera.h +++ b/camera/Camera.h @@ -54,7 +54,7 @@ camera_update_vectors(Camera* camera) vec3_normalize_f32(&camera->up); } -void camera_rotate(Camera* camera, float dx, float dy, float dt) +void camera_rotate(Camera* camera, int32 dx, int32 dy, float dt) { camera->orientation.x += dy * camera->sensitivity; camera->orientation.y -= dx * camera->sensitivity; diff --git a/log/Log.h b/log/Log.h index 82d1e62..0643626 100644 --- a/log/Log.h +++ b/log/Log.h @@ -12,6 +12,8 @@ #include #include +// @todo Make this file not rely on any other header except Types. + #include "../stdlib/Types.h" #include "../platform/win32/UtilsWin32.h" #include "../memory/RingMemory.h" diff --git a/memory/DebugMemory.h b/memory/DebugMemory.h index 5fde562..babcf1c 100644 --- a/memory/DebugMemory.h +++ b/memory/DebugMemory.h @@ -77,7 +77,6 @@ void debug_memory_add_range(DebugMemory* mem, uint64 start, uint64 end) inline void debug_memory_reset(DebugMemory* mem) { - mem->size = 0; mem->usage = 0; mem->debug_range_idx = 0; diff --git a/platform/win32/Library.h b/platform/win32/Library.h index 24ec39f..f225d9a 100644 --- a/platform/win32/Library.h +++ b/platform/win32/Library.h @@ -76,7 +76,7 @@ bool library_load(Library* lib) lib->is_valid = true; for (int c = 0; c < lib->function_count; ++c) { - void* function = GetProcAddress(lib->handle, lib->function_names[c]); + void* function = (void *) GetProcAddress(lib->handle, (LPCSTR) lib->function_names[c]); if (function) { lib->functions[c] = function; } else { diff --git a/platform/win32/audio/DirectSound.h b/platform/win32/audio/DirectSound.h index 2a0e639..9bca3d6 100644 --- a/platform/win32/audio/DirectSound.h +++ b/platform/win32/audio/DirectSound.h @@ -15,9 +15,10 @@ #include "../../../stdlib/Types.h" #include "../../../audio/AudioSetting.h" #include "../../../utils/MathUtils.h" +#include "../../../log/Log.h" struct DirectSoundSetting { - LPDIRECTSOUND8 direct_sound; + LPDIRECTSOUND8 audio_handle; LPDIRECTSOUNDBUFFER primary_buffer; LPDIRECTSOUNDBUFFER secondary_buffer; }; @@ -32,19 +33,21 @@ HRESULT WINAPI DirectSoundCreate8Stub(LPCGUID, LPDIRECTSOUND8*, LPUNKNOWN) { void audio_load(HWND hwnd, AudioSetting* setting, DirectSoundSetting* api_setting) { HMODULE lib = LoadLibraryExA((LPCSTR) "dsound.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!lib) { - // @todo Log + LOG(log_memory, "DirectSound: Couldn't load dsound.dll\n", log_fp, true, true); + return; } audio_create* DirectSoundCreate8 = (audio_create *) GetProcAddress(lib, "DirectSoundCreate8"); - if (!DirectSoundCreate8 || !SUCCEEDED(DirectSoundCreate8(0, &api_setting->direct_sound, 0))) { - // @todo Log + if (!DirectSoundCreate8 || !SUCCEEDED(DirectSoundCreate8(0, &api_setting->audio_handle, 0))) { + LOG(log_memory, "DirectSound: DirectSoundCreate8 failed\n", log_fp, true, true); + return; } - if(!SUCCEEDED(api_setting->direct_sound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY))) { - // @todo Log + if(!SUCCEEDED(api_setting->audio_handle->SetCooperativeLevel(hwnd, DSSCL_PRIORITY))) { + LOG(log_memory, "DirectSound: SetCooperativeLevel failed.\n", log_fp, true, true); return; } @@ -59,35 +62,36 @@ void audio_load(HWND hwnd, AudioSetting* setting, DirectSoundSetting* api_settin wf.cbSize = 0; // Create primary buffer - DSBUFFERDESC bufferDesc; - ZeroMemory(&bufferDesc, sizeof(DSBUFFERDESC)); + DSBUFFERDESC buffer_desc; + ZeroMemory(&buffer_desc, sizeof(DSBUFFERDESC)); - bufferDesc.dwSize = sizeof(DSBUFFERDESC); - bufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER; + buffer_desc.dwSize = sizeof(DSBUFFERDESC); + buffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER; - if(!SUCCEEDED(api_setting->direct_sound->CreateSoundBuffer(&bufferDesc, &api_setting->primary_buffer, 0))) { - // @todo Log + if(!SUCCEEDED(api_setting->audio_handle->CreateSoundBuffer(&buffer_desc, &api_setting->primary_buffer, 0))) { + LOG(log_memory, "DirectSound: CreateSoundBuffer1 failed.\n", log_fp, true, true); return; } if (!SUCCEEDED(api_setting->primary_buffer->SetFormat(&wf))) { - // @todo Log + LOG(log_memory, "DirectSound: SetFormat failed.\n", log_fp, true, true); return; } // Create secondary buffer - DSBUFFERDESC bufferDesc2; - ZeroMemory(&bufferDesc2, sizeof(DSBUFFERDESC)); + DSBUFFERDESC buffer_desc2; + ZeroMemory(&buffer_desc2, sizeof(DSBUFFERDESC)); - bufferDesc2.dwSize = sizeof(DSBUFFERDESC); - bufferDesc2.dwFlags = 0; - bufferDesc2.dwBufferBytes = setting->buffer_size; - bufferDesc2.lpwfxFormat = &wf; + buffer_desc2.dwSize = sizeof(DSBUFFERDESC); + // @todo check alterntaive flags + buffer_desc2.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLFX | DSBCAPS_CTRLVOLUME | DSBCAPS_GETCURRENTPOSITION2; + buffer_desc2.dwBufferBytes = setting->buffer_size; + buffer_desc2.lpwfxFormat = &wf; - if(!SUCCEEDED(api_setting->direct_sound->CreateSoundBuffer(&bufferDesc2, &api_setting->secondary_buffer, 0))) { - // @todo Log + if(!SUCCEEDED(api_setting->audio_handle->CreateSoundBuffer(&buffer_desc2, &api_setting->secondary_buffer, 0))) { + LOG(log_memory, "DirectSound: CreateSoundBuffer2 failed.\n", log_fp, true, true); return; } @@ -107,8 +111,8 @@ void audio_play(AudioSetting* setting, DirectSoundSetting* api_setting) inline void audio_free(AudioSetting*, DirectSoundSetting* api_setting) { - if (api_setting->direct_sound) { - api_setting->direct_sound->Release(); + if (api_setting->audio_handle) { + api_setting->audio_handle->Release(); } if (api_setting->primary_buffer) { @@ -129,7 +133,8 @@ uint32 audio_buffer_fillable(const AudioSetting* setting, const DirectSoundSetti DWORD player_cursor; DWORD write_cursor; if (!SUCCEEDED(api_setting->secondary_buffer->GetCurrentPosition(&player_cursor, &write_cursor))) { - // @todo Log + LOG(log_memory, "DirectSound: GetCurrentPosition failed.\n", log_fp, true, true); + return 0; } @@ -151,12 +156,16 @@ uint32 audio_buffer_fillable(const AudioSetting* setting, const DirectSoundSetti } inline -void audio_play_buffer(AudioSetting* setting, DirectSoundSetting* api_setting, uint32 bytes_to_write) +void audio_play_buffer(AudioSetting* setting, DirectSoundSetting* api_setting) { - if (bytes_to_write == 0) { + if (setting->sample_buffer_size == 0) { return; } + if (!setting->is_playing) { + audio_play(setting, api_setting); + } + void *region1; DWORD region1_size; @@ -166,16 +175,12 @@ void audio_play_buffer(AudioSetting* setting, DirectSoundSetting* api_setting, u DWORD bytes_to_lock = (setting->sample_index * setting->sample_size) % setting->buffer_size; api_setting->secondary_buffer->Lock( - bytes_to_lock, bytes_to_write, + bytes_to_lock, setting->sample_buffer_size, ®ion1, ®ion1_size, ®ion2, ®ion2_size, 0 ); - // @question Do we even need to use memcpy? Can't we use the buffer directly? - // Probably depends on what lock actually does to region1/region2 - // Of course we would than need some mechanism to check when we can write into the buffer - // See XAudio2 for this, we would probably need a second buffer as well memcpy( (void *) region1, (void *) setting->buffer, @@ -192,7 +197,7 @@ void audio_play_buffer(AudioSetting* setting, DirectSoundSetting* api_setting, u api_setting->secondary_buffer->Unlock(region1, region1_size, region2, region2_size); - setting->sample_index += bytes_to_write / setting->sample_size; + setting->sample_index += setting->sample_buffer_size / setting->sample_size; setting->sample_buffer_size = 0; } diff --git a/platform/win32/audio/XAudio2.h b/platform/win32/audio/XAudio2.h index d8f0fd2..eda81c0 100644 --- a/platform/win32/audio/XAudio2.h +++ b/platform/win32/audio/XAudio2.h @@ -11,13 +11,15 @@ #include #include +#include #include "../../../stdlib/Types.h" #include "../../../audio/AudioSetting.h" #include "../../../utils/MathUtils.h" +#include "../../../log/Log.h" struct XAudio2Setting { - IXAudio2* xaudio2; + IXAudio2* audio_handle; IXAudio2SourceVoice* source_voice; IXAudio2MasteringVoice* mastering_voice; @@ -32,31 +34,37 @@ HRESULT WINAPI XAudio2CreateStub(IXAudio2**, UINT32, XAUDIO2_PROCESSOR) { // END: Dynamically load XAudio2 void audio_load(HWND hwnd, AudioSetting* setting, XAudio2Setting* api_setting) { + CoInitialize(NULL); HMODULE lib = LoadLibraryExA((LPCSTR) "xaudio2_9.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if (!lib) { - // @todo Log + LOG(log_memory, "Xaudio2: Couldn't load xaudio2_9.dll\n", log_fp, true, true); + lib = LoadLibraryExA((LPCSTR) "xaudio2_8.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); } if (!lib) { - // @todo Log + LOG(log_memory, "Xaudio2: Couldn't load xaudio2_8.dll\n", log_fp, true, true); + return; } audio_create* XAudio2Create = (audio_create *) GetProcAddress(lib, "XAudio2Create"); - if (!XAudio2Create || !SUCCEEDED(XAudio2Create(&api_setting->xaudio2, 0, XAUDIO2_DEFAULT_PROCESSOR))) { - // @todo Log + if (!XAudio2Create || !SUCCEEDED(XAudio2Create(&api_setting->audio_handle, 0, XAUDIO2_DEFAULT_PROCESSOR))) { + LOG(log_memory, "Xaudio2: XAudio2Create failed\n", log_fp, true, true); + return; } - if (!SUCCEEDED(api_setting->xaudio2->CreateMasteringVoice( + HRESULT hr; + if (!SUCCEEDED(hr = api_setting->audio_handle->CreateMasteringVoice( &api_setting->mastering_voice, XAUDIO2_DEFAULT_CHANNELS, setting->sample_rate, 0, - NULL - ))) { - // @todo Log + NULL)) + ) { + LOG(log_memory, "Xaudio2: CreateMasteringVoice failed\n", log_fp, true, true); + return; } @@ -69,15 +77,16 @@ void audio_load(HWND hwnd, AudioSetting* setting, XAudio2Setting* api_setting) { wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; // = buffer_size wf.cbSize = 0; - if (!SUCCEEDED(api_setting->xaudio2->CreateSourceVoice(&api_setting->source_voice, &wf))) { - // @todo Log + if (!SUCCEEDED(api_setting->audio_handle->CreateSourceVoice(&api_setting->source_voice, &wf))) { + LOG(log_memory, "Xaudio2: CreateSourceVoice failed\n", log_fp, true, true); + return; } + // @todo consider to remove mallocs/callocs setting->buffer_size = setting->sample_rate * setting->sample_size; setting->buffer = (int16 *) calloc(setting->sample_rate, setting->sample_size); - // @question Consider to move to the heap? api_setting->internal_buffer[0].Flags = 0; api_setting->internal_buffer[0].AudioBytes = setting->buffer_size; api_setting->internal_buffer[0].pAudioData = (byte *) malloc(setting->buffer_size * sizeof(byte)); @@ -104,13 +113,15 @@ void audio_load(HWND hwnd, AudioSetting* setting, XAudio2Setting* api_setting) { inline void audio_play(AudioSetting* setting, XAudio2Setting* api_setting) { if (!api_setting->source_voice) { - // @todo Log - return; } api_setting->source_voice->Start(0, XAUDIO2_COMMIT_NOW); setting->is_playing = true; + + if (setting->sample_index > 1) { + setting->sample_index = 0; + } } inline @@ -136,8 +147,8 @@ void audio_free(AudioSetting* setting, XAudio2Setting* api_setting) api_setting->mastering_voice->DestroyVoice(); } - if (api_setting->xaudio2) { - api_setting->xaudio2->Release(); + if (api_setting->audio_handle) { + api_setting->audio_handle->Release(); } } @@ -151,8 +162,6 @@ inline uint32 audio_buffer_fillable(const AudioSetting* setting, const XAudio2Setting* api_setting) { if (!api_setting->source_voice) { - // @todo Log - return 0; } @@ -166,29 +175,30 @@ uint32 audio_buffer_fillable(const AudioSetting* setting, const XAudio2Setting* } inline -void audio_play_buffer(AudioSetting* setting, XAudio2Setting* api_setting, uint32 bytes_to_write) { - if (!api_setting->source_voice) { - // @todo Log - +void audio_play_buffer(AudioSetting* setting, XAudio2Setting* api_setting) { + if (!api_setting->source_voice || setting->sample_buffer_size == 0) { return; } - if (bytes_to_write == 0) { - return; + if (!setting->is_playing) { + audio_play(setting, api_setting); } + uint32 idx = setting->sample_output % 2; + memcpy( - (void *) api_setting->internal_buffer[setting->sample_index].pAudioData, + (void *) api_setting->internal_buffer[idx].pAudioData, setting->buffer, - bytes_to_write + setting->sample_buffer_size ); - if (!SUCCEEDED(api_setting->source_voice->SubmitSourceBuffer(&api_setting->internal_buffer[setting->sample_index]))) { - // @todo Log + if (!SUCCEEDED(api_setting->source_voice->SubmitSourceBuffer(&api_setting->internal_buffer[idx]))) { + LOG(log_memory, "Xaudio2: SubmitSourceBuffer failed\n", log_fp, true, true); + return; } - setting->sample_index = (setting->sample_index + 1) % 2; + setting->sample_index += setting->sample_buffer_size / setting->sample_size; setting->sample_buffer_size = 0; }