diff options
| author | Jonathan Bradley <jcb@pikum.xyz> | 2025-07-14 16:35:32 -0400 |
|---|---|---|
| committer | Jonathan Bradley <jcb@pikum.xyz> | 2025-07-14 16:35:32 -0400 |
| commit | 7f0b0a59425321dcc880ddc0f4b479bce85e0bb0 (patch) | |
| tree | c5c10d187faf26a248d859deac68ba83bbe26e97 | |
| parent | f88ca0bc946bae086e02eacdc6c129f00e2e07e3 (diff) | |
pke: audio: process each tick, impl buffer copy
| -rw-r--r-- | src/audio-impl-pw.cpp | 238 | ||||
| -rw-r--r-- | src/audio-impl-shared.cpp | 17 | ||||
| -rw-r--r-- | src/audio-impl-shared.hpp | 6 | ||||
| -rw-r--r-- | src/audio-types.hpp | 11 | ||||
| -rw-r--r-- | src/audio.cpp | 266 | ||||
| -rw-r--r-- | src/game.cpp | 4 | ||||
| -rw-r--r-- | tests/pke-test-audio.cpp | 32 |
7 files changed, 329 insertions, 245 deletions
diff --git a/src/audio-impl-pw.cpp b/src/audio-impl-pw.cpp index 92acd8d..1bc2525 100644 --- a/src/audio-impl-pw.cpp +++ b/src/audio-impl-pw.cpp @@ -1,15 +1,10 @@ #include "audio-impl-pw.hpp" -#include "audio-impl-shared.hpp" -#include "asset-manager.hpp" #include "audio-types.hpp" #include "game-settings.hpp" -#include "math-helpers.hpp" #include "pipewire/keys.h" #include "pipewire/thread-loop.h" #include "pk.h" -#include "vendor-glm-include.hpp" -#include "window.hpp" // see header file #pragma GCC diagnostic push @@ -114,7 +109,7 @@ void pke_audio_pw_init() { spa_audio_info_raw spa_audio_info_raw_init{}; spa_audio_info_raw_init.flags = 0; spa_audio_info_raw_init.format = SPA_AUDIO_FORMAT_F32; - spa_audio_info_raw_init.rate = 48000; + spa_audio_info_raw_init.rate = PKE_AUDIO_BITRATE; spa_audio_info_raw_init.channels = pke_audio_mstr.channel_count; spa_audio_info_raw_init.position[0] = SPA_AUDIO_CHANNEL_FL; spa_audio_info_raw_init.position[1] = SPA_AUDIO_CHANNEL_FR; @@ -200,6 +195,10 @@ void pke_audio_pw_remap_outputs() { const char *str; uint32_t *out_ports = pk_new<uint32_t>(pke_audio_mstr.channel_count, pkeSettings.mem_bkt.game_transient); uint32_t *in_ports = pk_new<uint32_t>(pke_audio_mstr.channel_count, pkeSettings.mem_bkt.game_transient); + for (i = 0; i < pke_audio_mstr.channel_count; ++i) { + out_ports[i] = 0; + in_ports[i] = 0; + } pke_audio_mstr.mtx_buffer.lock(); pw_thread_loop_lock(pke_audio_pw.loop); @@ -276,19 +275,8 @@ void on_pipewire_process(void *user_data) { pw_buffer *b; spa_buffer *buf; int stride; - uint8_t pc, pc2; - uint32_t i, ii, c; - int64_t n_frames, i_frame; + int64_t n_frames; float *dst; - float val, vol, vol2; - float *spatial_volumes = nullptr; - glm::vec3 listener_origin; - glm::vec3 audio_dir = glm::vec3(0); - glm::vec3 *spatial_normals = nullptr; - pke_audio_fx_params_reverb *params_reverb; - pke_audio_fx_params_delay *params_delay; - pke_audio_fx_params_low_pass_filter *params_low_pass_filter; - pk_mem_bucket_reset(pke_audio_mstr.bkt_transient); if ((b = pw_stream_dequeue_buffer(pke_audio_pw.stream)) == NULL) { fprintf(stderr, "[" __FILE__ "][on_pipewire_process] out of buffers"); @@ -301,64 +289,6 @@ void on_pipewire_process(void *user_data) { return; } - spatial_volumes = pk_new<float>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt_transient); - spatial_normals = pk_new<glm::vec3>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt_transient); - params_reverb = pk_new<pke_audio_fx_params_reverb>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt_transient); - params_delay = pk_new<pke_audio_fx_params_delay>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt_transient); - params_low_pass_filter = pk_new<pke_audio_fx_params_low_pass_filter>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt_transient); - - // calculate spatial_normals - // TODO maybe don't use UBO - // TODO project-defined? - // TODO instanced audio manager? (easier to handle explicit stereo sources?) - listener_origin = glm::inverse(UBO.model) * glm::vec4(0, 0, 0, 1); - for (c = 0; c < pke_audio_mstr.channel_count; ++c) { - switch (c) { - case 0: - // left-speaker - if (pke_audio_mstr.channel_count > 2) { - spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 1.f)); - } else { - spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 0.f)); - } - break; - case 1: - // right - if (pke_audio_mstr.channel_count > 2) { - spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, 1.f)); - } else { - spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, 0.f)); - } - break; - case 2: - // center - spatial_normals[c] = glm::normalize(glm::vec3( 0.f, 0.f, 1.f)); - break; - case 3: - // subwoofer - spatial_normals[c] = glm::normalize(glm::vec3( 0.f, 0.f, 0.f)); - break; - case 4: - // left surround - spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 0.f)); - break; - case 5: - // right surround - spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, 0.f)); - break; - case 6: - // rear left surround - spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, -1.f)); - break; - case 7: - // rear right surround - spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, -1.f)); - break; - default: - break; - } - } - pke_audio_mstr.mtx_buffer.lock(); stride = sizeof(float) * pke_audio_mstr.channel_count; @@ -367,7 +297,8 @@ void on_pipewire_process(void *user_data) { if (b->requested) { n_frames = PK_MIN((int64_t)b->requested, n_frames); } - // fprintf(stdout, "[pw] frame count: %li, requested: %lu\n", n_frames, b->requested); + // fprintf(stdout, "[pw] frame count available: %li, requested: %lu, ready: %li\n", n_frames, b->requested, pke_audio_mstr.buffer_frames); + n_frames = PK_MIN(n_frames, pke_audio_mstr.buffer_frames); buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = stride; @@ -377,141 +308,24 @@ void on_pipewire_process(void *user_data) { goto audio_done; } - for (i = 0; i < pke_audio_mstr.playing_objects.next; ++i) { - pke_audio_obj *aobj = &pke_audio_mstr.playing_objects[i]; - const Asset *a = AM_Get(aobj->handle); - vol = 1.0; - vol *= pke_audio_mstr.master_volume; - vol *= pke_audio_mstr.source_volumes[static_cast<pke_audio_source_T>(aobj->source)]; - - for (pc = 0; pc < aobj->play_count; ++pc) { - // TODO configurable - // >= 5.0 meters is 100% volume - // <=50.0 meters is 0% volume - float distance_volume = glm::distance(aobj->position_source[pc], listener_origin); - distance_volume = 1.0 - ((distance_volume - 5.0) / 50 - 5); - distance_volume = glm::clamp(distance_volume, 0.f, 1.f); - - // calculate panning - // TODO handle subwoofer explicitly - float sum = 0.0; - for (c = 0; c < pke_audio_mstr.channel_count; ++c) { - audio_dir = glm::normalize(aobj->position_source[pc] - listener_origin); - for (ii = 0; ii < 3; ++ii) { - if (glm::isnan(audio_dir[ii])) { - audio_dir = glm::vec3(0); - spatial_volumes[c] = 1.f; - break; - } - } - float dot_dir = glm::dot(spatial_normals[c], audio_dir); - if (!glm::isnan(dot_dir) && dot_dir < 0.0) { - // fprintf(stderr, "[pw] dot: %f\n", dot_dir); - - // 20k max, 500 min - params_low_pass_filter[c].cutoff_freq = log_interp(500.f, 20000.f, abs(dot_dir)); - } else { - params_low_pass_filter[c].cutoff_freq = 0.f; - } - params_reverb[c].reverb_strength = 0.f; // TODO - params_delay[c].delay_frames = 0; // TODO - // spatial_volumes[c] = glm::clamp(dot_dir, 0.f, 1.f); - spatial_volumes[c] = lerp(0.f, 1.f, dot_dir + 0.85f); - - if (isnan(spatial_volumes[c]) || spatial_volumes[c] == 0.0f) { - /* - fprintf(stderr, "[pw] NaN or 0: chan: %i, norm: %f,%f,%f, src: %f,%f,%f, origin: %f,%f,%f\n", - c, - audio_dir.x, audio_dir.y, audio_dir.z, - aobj->position_source[pc].x, aobj->position_source[pc].y, aobj->position_source[pc].z, - listener_origin.x, listener_origin.y, listener_origin.z - ); - */ - spatial_volumes[c] = 1.0; - } - - sum += spatial_volumes[c]; - } - // normalize (TODO do we want this?) - /* - if (sum > 1.f) { - for (c = 0; c < pke_audio_mstr.channel_count; ++c) { - spatial_volumes[c] /= sum; - } - } - */ - - /* - for (c = 0; c < pke_audio_mstr.channel_count; ++c) { - fprintf(stderr, "[pw] obj: %i, chan: %i vol: %f, spatial: %f, dist: %f\n", - i, c, vol, spatial_volumes[c], distance_volume - ); - } - */ - - for (i_frame = 0; i_frame < n_frames; ++i_frame) { - dst = (float*)buf->datas[0].data; - dst += (i_frame * (uint64_t)pke_audio_mstr.channel_count); // advance - - for (c = 0; c < pke_audio_mstr.channel_count; ++c) { - if (i == 0 && pc == 0) { *dst = 0.f; } // clear buffer as we go - - vol2 = vol; - if (PK_HAS_FLAG(aobj->flags[pc], pke_audio_flag_pos_spatial)) { - vol2 *= (spatial_volumes[c] * distance_volume); - } - if (vol2 <= 0.0) { - // fprintf(stderr, "[pw] chan: %i vol2 is <= 0.0\n", c); - dst += 1; - continue; - } - - /* - if (isnan(vol2)) { - fprintf(stderr, "[pw] vol2 is NaN\n"); - } - if (vol2 == 0.0) { - fprintf(stderr, "[pw] vol2 is 0, %f, %f, %f\n", pke_audio_mstr.master_volume, pke_audio_mstr.source_volumes[c], spatial_volumes[c]); - } - */ - - // val = ((float*)a->ptr)[aobj->play_heads[pc]]; // val is read inside fx_low_pass_filter - val = pke_audio_fx_low_pass_filter((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], ¶ms_low_pass_filter[pc]); - val += pke_audio_fx_reverb((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], ¶ms_reverb[pc]); - val += pke_audio_fx_delay((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], ¶ms_delay[pc]); - *dst += val * vol2; - /* - if (isnan(*dst)) { - fprintf(stderr, "[pw] *dst is NaN\n"); - } - */ - dst += 1; - - } - - // TODO type-specific attributes for assets so we can pre-calculate this or just KNOW the frame length ahead of time - aobj->play_heads[pc] += 1; - if (aobj->play_heads[pc] >= a->size / sizeof(float)) { - for (pc2 = 0; pc2 < aobj->play_count-1; ++pc2) { - aobj->flags[pc2] = aobj->flags[pc2+1]; - aobj->play_heads[pc2] = aobj->play_heads[pc2+1]; - aobj->position_source[pc2] = aobj->position_source[pc2+1]; - } - pc -= 1; - aobj->play_count -= 1; - break; - } - } - } - AM_Release(aobj->handle); - if (aobj->play_count == 0) { - pk_arr_remove_at(&pke_audio_mstr.playing_objects, i); - i -= 1; - } - } - - if (pke_audio_mstr.playing_objects.next == 0) { - memset(dst, 0, sizeof(float) * n_frames * pke_audio_mstr.channel_count); + memcpy(dst, pke_audio_mstr.buffer, buf->datas[0].chunk->size); + + if (n_frames > 0 && n_frames < pke_audio_mstr.buffer_frames) { + // TODO PERF I think if I always had two buffers allocated + // I could alternate between them for better perf. + float* new_buffer = pk_new<float>(PKE_AUDIO_BUFFER_FRAMES * pke_audio_mstr.channel_count * sizeof(float), pke_audio_mstr.bkt); + memcpy( + new_buffer, + pke_audio_mstr.buffer + (pke_audio_mstr.channel_count * n_frames), + stride * (pke_audio_mstr.buffer_frames - n_frames) + ); + pk_delete<float>(pke_audio_mstr.buffer, PKE_AUDIO_BUFFER_FRAMES * pke_audio_mstr.channel_count * sizeof(float), pke_audio_mstr.bkt); + pke_audio_mstr.buffer = new_buffer; + + // fprintf(stdout, "[pw] shift buffer. buffer_frames before: %li, processed_frames: %li, after: %li\n", pke_audio_mstr.buffer_frames, n_frames, pke_audio_mstr.buffer_frames - n_frames); + pke_audio_mstr.buffer_frames -= n_frames; + } else { + pke_audio_mstr.buffer_frames = 0; } audio_done: diff --git a/src/audio-impl-shared.cpp b/src/audio-impl-shared.cpp index 0c3c730..b7890ac 100644 --- a/src/audio-impl-shared.cpp +++ b/src/audio-impl-shared.cpp @@ -1,21 +1,24 @@ #include "audio-impl-shared.hpp" -float pke_audio_fx_reverb(float *buffer, uint64_t buffer_len, uint64_t buffer_idx, pke_audio_fx_params_reverb *params) { - if (buffer_idx >= buffer_len-1) return 0.f; +float pke_audio_fx_reverb(float *buffer, int64_t buffer_len, int64_t buffer_idx, pke_audio_fx_params_reverb *params) { + if (buffer_idx == 0) return 0.f; + if (buffer_idx >= buffer_len) return 0.f; // simple feedback return buffer[buffer_idx - 1] * params->reverb_strength; } -float pke_audio_fx_delay(float *buffer, uint64_t buffer_len, uint64_t buffer_idx, pke_audio_fx_params_delay *params) { - if (buffer_idx >= buffer_len-1) return 0.f; +float pke_audio_fx_delay(float *buffer, int64_t buffer_len, int64_t buffer_idx, pke_audio_fx_params_delay *params) { + if (buffer_idx >= buffer_len) return 0.f; + assert ((buffer_idx - params->delay_frames) >= 0); + // if (params->delay_frames > buffer_len) return 0.f; return buffer[buffer_idx - params->delay_frames]; } -float pke_audio_fx_low_pass_filter(float *buffer, uint64_t buffer_len, uint64_t buffer_idx, pke_audio_fx_params_low_pass_filter *params) { - if (buffer_idx >= buffer_len-1) return 0.f; +float pke_audio_fx_low_pass_filter(float *buffer, int64_t buffer_len, int64_t buffer_idx, pke_audio_fx_params_low_pass_filter *params) { + if (buffer_idx >= buffer_len) return 0.f; float rc = 1.f / std::numbers::pi * 2.0 * params->cutoff_freq; - float dt = 1.f / 48000.f; // TODO reference global or pass this in + float dt = 1.f / PKE_AUDIO_BITRATE; float alpha = dt / (rc + dt); params->prev_output = (alpha * buffer[buffer_idx]) + ((1.f - alpha) * params->prev_output); return params->prev_output; diff --git a/src/audio-impl-shared.hpp b/src/audio-impl-shared.hpp index 3b7b158..0c171d3 100644 --- a/src/audio-impl-shared.hpp +++ b/src/audio-impl-shared.hpp @@ -5,8 +5,8 @@ #include <cstdint> -float pke_audio_fx_reverb(float *buffer, uint64_t buffer_len, uint64_t buffer_idx, pke_audio_fx_params_reverb *params); -float pke_audio_fx_delay(float *buffer, uint64_t buffer_len, uint64_t buffer_idx, pke_audio_fx_params_delay *params); -float pke_audio_fx_low_pass_filter(float *buffer, uint64_t buffer_len, uint64_t buffer_idx, pke_audio_fx_params_low_pass_filter *params); +float pke_audio_fx_reverb(float *buffer, int64_t buffer_len, int64_t buffer_idx, pke_audio_fx_params_reverb *params); +float pke_audio_fx_delay(float *buffer, int64_t buffer_len, int64_t buffer_idx, pke_audio_fx_params_delay *params); +float pke_audio_fx_low_pass_filter(float *buffer, int64_t buffer_len, int64_t buffer_idx, pke_audio_fx_params_low_pass_filter *params); #endif /* PKE_AUDIO_IMPL_SHARED_HPP */ diff --git a/src/audio-types.hpp b/src/audio-types.hpp index a0ab3fb..c573f2b 100644 --- a/src/audio-types.hpp +++ b/src/audio-types.hpp @@ -9,6 +9,8 @@ #define PKE_AUDIO_MAX_CONCURRENT_COUNT 8 #define PKE_AUDIO_MAX_SPATIAL_DISTANCE 256.f +#define PKE_AUDIO_BITRATE 48000 +#define PKE_AUDIO_BUFFER_FRAMES 2048 TypeSafeInt_constexpr(pke_audio_flags, uint8_t, 0xFF); TypeSafeInt_constexpr(pke_audio_source, uint8_t, 0x40); @@ -26,7 +28,7 @@ struct pke_audio_fx_params_reverb { }; struct pke_audio_fx_params_delay { - uint64_t delay_frames; + int64_t delay_frames; }; struct pke_audio_fx_params_low_pass_filter { @@ -46,11 +48,16 @@ struct pke_audio_obj { struct pke_audio_master { pk_arr_t<pke_audio_obj> playing_objects; - pk_membucket *bkt_transient; + pk_membucket *bkt; float master_volume; float source_volumes[pke_audio_source_T_MAX]; uint32_t channel_count; // mono, stereo, 7.1 + float *buffer; + int64_t buffer_size; + int64_t buffer_frames; + int64_t elapsed_ns; std::mutex mtx_buffer; + std::chrono::time_point<std::chrono::steady_clock> last_tick_tp{}; }; extern struct pke_audio_master pke_audio_mstr; diff --git a/src/audio.cpp b/src/audio.cpp index 33ea740..e42d7e0 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -2,36 +2,53 @@ #include "asset-manager.hpp" #include "audio-impl-pw.hpp" +#include "audio-impl-shared.hpp" #include "audio.hpp" #include "ecs.hpp" +#include "game-settings.hpp" #include "math-helpers.hpp" #include "pk.h" +#include "window.hpp" +#include <chrono> struct pke_audio_master pke_audio_mstr{}; +void pke_audio_process_frames(int64_t frame_count); + void pke_audio_init() { pke_audio_mstr.master_volume = 1.f; pke_audio_mstr.channel_count = 2; - pke_audio_mstr.bkt_transient = pk_mem_bucket_create("pke_audio", PK_MEM_DEFAULT_BUCKET_SIZE, PK_MEMBUCKET_FLAG_TRANSIENT); + // pke_audio_mstr.last_tick_tp = {}; + pke_audio_mstr.last_tick_tp = pkeSettings.steadyClock.now(); + pke_audio_mstr.bkt= pk_mem_bucket_create("pke_audio", PK_MEM_DEFAULT_BUCKET_SIZE, PK_MEMBUCKET_FLAG_NONE); for (uint8_t i = 0; i < pke_audio_mstr.channel_count; ++i) { pke_audio_mstr.source_volumes[i] = 1.f; } + pke_audio_mstr.buffer_size = PKE_AUDIO_BUFFER_FRAMES * pke_audio_mstr.channel_count * sizeof(float); + pke_audio_mstr.buffer_frames = 0; + pke_audio_mstr.elapsed_ns = 0; + pke_audio_mstr.buffer = pk_new<float>(pke_audio_mstr.buffer_size, pke_audio_mstr.bkt); #ifdef PKE_AUDIO_IMPL_PIPEWIRE pke_audio_pw_init(); #endif } + void pke_audio_teardown() { #ifdef PKE_AUDIO_IMPL_PIPEWIRE pke_audio_pw_teardown(); #endif - pk_mem_bucket_destroy(pke_audio_mstr.bkt_transient); + pk_delete<float>(pke_audio_mstr.buffer, pke_audio_mstr.buffer_size, pke_audio_mstr.bkt); + pk_mem_bucket_destroy(pke_audio_mstr.bkt); } + void pke_audio_tick(double delta) { (void)delta; uint32_t i, k; #ifdef PKE_AUDIO_IMPL_PIPEWIRE if (pke_audio_pw.is_needing_output_remapped == true) { pke_audio_pw_remap_outputs(); + pke_audio_mstr.last_tick_tp = pkeSettings.steadyClock.now(); + return; } #endif pke_audio_mstr.mtx_buffer.lock(); @@ -50,6 +67,21 @@ void pke_audio_tick(double delta) { ); } } + + constexpr int64_t ns_per_bit_chunk = std::chrono::nanoseconds::period::den / 4000; + constexpr int64_t bits_per_chunk = PKE_AUDIO_BITRATE / 4000; + + std::chrono::time_point<std::chrono::steady_clock> tp_now = pkeSettings.steadyClock.now(); + auto diff = (tp_now - pke_audio_mstr.last_tick_tp).count(); + pke_audio_mstr.elapsed_ns += diff; + + int64_t frame_count = floor((pke_audio_mstr.elapsed_ns / ns_per_bit_chunk)) * bits_per_chunk; + if (frame_count > 0) { + // fprintf(stdout, "[pw_tick] frame_count: %li, running_count: %li, elapsed_ns: %li\n", frame_count, pke_audio_mstr.buffer_frames, pke_audio_mstr.elapsed_ns); + pke_audio_process_frames(frame_count); + pke_audio_mstr.elapsed_ns %= ns_per_bit_chunk; + } + pke_audio_mstr.last_tick_tp = tp_now; pke_audio_mstr.mtx_buffer.unlock(); } @@ -66,6 +98,7 @@ bool pke_audio_playing_objects_find_first_by_key(void *user_data, void *arr_data pke_audio_obj &audio_obj = *reinterpret_cast<pke_audio_obj*>(arr_data); return std::get<0>(tup) == audio_obj.handle && std::get<1>(tup) == audio_obj.source; } + void pke_audio_play(AssetHandle handle, pke_audio_source audio_source, pke_audio_flags flags, glm::vec3 position_source, InstanceHandle instance_handle) { // TODO validation: audio length (does it fit in uint32_t), etc // TODO rethink threading: first-pass only mutex @@ -93,6 +126,7 @@ void pke_audio_play(AssetHandle handle, pke_audio_source audio_source, pke_audio aobj->play_heads[idx] = 0; pke_audio_mstr.mtx_buffer.unlock(); } + void pke_audio_stop_all() { // TODO fade-out instead of hard-cut? Maybe that should be a separate function. uint32_t i; @@ -103,3 +137,231 @@ void pke_audio_stop_all() { pk_arr_clear(&pke_audio_mstr.playing_objects); pke_audio_mstr.mtx_buffer.unlock(); } + +void pke_audio_process_frames(int64_t frame_count) { + uint8_t pc, pc2; + uint32_t i, ii, c; + int64_t i_frame; + float *dst; + float val, vol, vol2; + glm::vec3 listener_origin; + glm::vec3 audio_dir = glm::vec3(0); + float *spatial_volumes = nullptr; + glm::vec3 *spatial_normals = nullptr; + pke_audio_fx_params_reverb *params_reverb = nullptr; + pke_audio_fx_params_delay *params_delay = nullptr; + pke_audio_fx_params_low_pass_filter *params_low_pass_filter = nullptr; + + int64_t stride = pke_audio_mstr.channel_count * sizeof(float); + frame_count = PK_MIN(frame_count, (pke_audio_mstr.buffer_size / stride) - pke_audio_mstr.buffer_frames); + + // init + spatial_volumes = pk_new<float>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + spatial_normals = pk_new<glm::vec3>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + params_reverb = pk_new<pke_audio_fx_params_reverb>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + params_delay = pk_new<pke_audio_fx_params_delay>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + params_low_pass_filter = pk_new<pke_audio_fx_params_low_pass_filter>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + + dst = pke_audio_mstr.buffer + (pke_audio_mstr.buffer_frames * pke_audio_mstr.channel_count); + + // calculate spatial_normals + // TODO maybe don't use UBO + // TODO project-defined? + // TODO instanced audio manager? (easier to handle explicit stereo sources?) + listener_origin = glm::inverse(UBO.model) * glm::vec4(0, 0, 0, 1); + for (c = 0; c < pke_audio_mstr.channel_count; ++c) { + switch (c) { + case 0: + // left-speaker + if (pke_audio_mstr.channel_count > 2) { + spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 1.f)); + } else { + spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 0.f)); + } + break; + case 1: + // right + if (pke_audio_mstr.channel_count > 2) { + spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, 1.f)); + } else { + spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, 0.f)); + } + break; + case 2: + // center + spatial_normals[c] = glm::normalize(glm::vec3( 0.f, 0.f, 1.f)); + break; + case 3: + // subwoofer + spatial_normals[c] = glm::normalize(glm::vec3( 0.f, 0.f, 0.f)); + break; + case 4: + // left surround + spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 0.f)); + break; + case 5: + // right surround + spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, 0.f)); + break; + case 6: + // rear left surround + spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, -1.f)); + break; + case 7: + // rear right surround + spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, -1.f)); + break; + default: + break; + } + } + + // calculate + for (i = 0; i < pke_audio_mstr.playing_objects.next; ++i) { + pke_audio_obj *aobj = &pke_audio_mstr.playing_objects[i]; + const Asset *a = AM_Get(aobj->handle); + vol = 1.0; + vol *= pke_audio_mstr.master_volume; + vol *= pke_audio_mstr.source_volumes[static_cast<pke_audio_source_T>(aobj->source)]; + + for (pc = 0; pc < aobj->play_count; ++pc) { + // TODO configurable + // >= 5.0 meters is 100% volume + // <=50.0 meters is 0% volume + float distance_volume = glm::distance(aobj->position_source[pc], listener_origin); + distance_volume = 1.0 - ((distance_volume - 5.0) / 50 - 5); + distance_volume = glm::clamp(distance_volume, 0.f, 1.f); + + // calculate panning + // TODO handle subwoofer explicitly + // float sum = 0.0; + for (c = 0; c < pke_audio_mstr.channel_count; ++c) { + audio_dir = glm::normalize(aobj->position_source[pc] - listener_origin); + for (ii = 0; ii < 3; ++ii) { + if (glm::isnan(audio_dir[ii])) { + audio_dir = glm::vec3(0); + spatial_volumes[c] = 1.f; + break; + } + } + float dot_dir = glm::dot(spatial_normals[c], audio_dir); + if (!glm::isnan(dot_dir) && dot_dir < 0.0) { + // fprintf(stderr, "[pw] dot: %f\n", dot_dir); + + // 20k max, 500 min + params_low_pass_filter[c].cutoff_freq = log_interp(500.f, 20000.f, abs(dot_dir)); + } else { + params_low_pass_filter[c].cutoff_freq = 0.f; + } + params_reverb[c].reverb_strength = 0.f; // TODO + params_delay[c].delay_frames = 0; // TODO + // spatial_volumes[c] = glm::clamp(dot_dir, 0.f, 1.f); + spatial_volumes[c] = lerp(0.f, 1.f, dot_dir + 0.85f); + + if (isnan(spatial_volumes[c]) || spatial_volumes[c] == 0.0f) { + /* + fprintf(stderr, "[pw] NaN or 0: chan: %i, norm: %f,%f,%f, src: %f,%f,%f, origin: %f,%f,%f\n", + c, + audio_dir.x, audio_dir.y, audio_dir.z, + aobj->position_source[pc].x, aobj->position_source[pc].y, aobj->position_source[pc].z, + listener_origin.x, listener_origin.y, listener_origin.z + ); + */ + spatial_volumes[c] = 1.0; + } + + // sum += spatial_volumes[c]; + } + // normalize (TODO do we want this?) + /* + if (sum > 1.f) { + for (c = 0; c < pke_audio_mstr.channel_count; ++c) { + spatial_volumes[c] /= sum; + } + } + */ + + /* + for (c = 0; c < pke_audio_mstr.channel_count; ++c) { + fprintf(stderr, "[pw] obj: %i, chan: %i vol: %f, spatial: %f, dist: %f\n", + i, c, vol, spatial_volumes[c], distance_volume + ); + } + */ + + for (i_frame = 0; i_frame < frame_count; ++i_frame) { + dst = pke_audio_mstr.buffer + (pke_audio_mstr.buffer_frames * pke_audio_mstr.channel_count); + int64_t buffer_idx_advance = (i_frame * (int64_t)pke_audio_mstr.channel_count); + // fprintf(stdout, "[pw] frame_count: %li, buffer_idx_advance : %li\n", frame_count, buffer_idx_advance); + dst += buffer_idx_advance; // advance + + for (c = 0; c < pke_audio_mstr.channel_count; ++c) { + if (i == 0 && pc == 0) { *dst = 0.f; } // clear buffer as we go + + vol2 = vol; + if (PK_HAS_FLAG(aobj->flags[pc], pke_audio_flag_pos_spatial)) { + vol2 *= (spatial_volumes[c] * distance_volume); + } + if (vol2 <= 0.0) { + // fprintf(stderr, "[pw] chan: %i vol2 is <= 0.0\n", c); + dst += 1; + continue; + } + + /* + if (isnan(vol2)) { + fprintf(stderr, "[pw] vol2 is NaN\n"); + } + if (vol2 == 0.0) { + fprintf(stderr, "[pw] vol2 is 0, %f, %f, %f\n", pke_audio_mstr.master_volume, pke_audio_mstr.source_volumes[c], spatial_volumes[c]); + } + */ + + // val = ((float*)a->ptr)[aobj->play_heads[pc]]; // val is read inside fx_low_pass_filter + val = pke_audio_fx_low_pass_filter((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], ¶ms_low_pass_filter[c]); + val += pke_audio_fx_reverb((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], ¶ms_reverb[c]); + val += pke_audio_fx_delay((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], ¶ms_delay[c]); + *dst += val * vol2; + /* + if (isnan(*dst)) { + fprintf(stderr, "[pw] *dst is NaN\n"); + } + */ + dst += 1; + + } + + // TODO type-specific attributes for assets so we can pre-calculate this or just KNOW the frame length ahead of time + aobj->play_heads[pc] += 1; + if (aobj->play_heads[pc] >= a->size / sizeof(float)) { + for (pc2 = 0; pc2 < aobj->play_count-1; ++pc2) { + aobj->flags[pc2] = aobj->flags[pc2+1]; + aobj->play_heads[pc2] = aobj->play_heads[pc2+1]; + aobj->position_source[pc2] = aobj->position_source[pc2+1]; + } + pc -= 1; + aobj->play_count -= 1; + break; + } + } + } + AM_Release(aobj->handle); + if (aobj->play_count == 0) { + pk_arr_remove_at(&pke_audio_mstr.playing_objects, i); + i -= 1; + } + } + + if (pke_audio_mstr.playing_objects.next == 0) { + memset(dst, 0, sizeof(float) * frame_count * pke_audio_mstr.channel_count); + } + + // cleanup (reverse order) + pk_delete<pke_audio_fx_params_low_pass_filter>(params_low_pass_filter, pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + pk_delete<pke_audio_fx_params_delay>(params_delay, pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + pk_delete<pke_audio_fx_params_reverb>(params_reverb, pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + pk_delete<glm::vec3>(spatial_normals, pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + pk_delete<float>(spatial_volumes, pke_audio_mstr.channel_count, pke_audio_mstr.bkt); + + pke_audio_mstr.buffer_frames += frame_count; +} diff --git a/src/game.cpp b/src/game.cpp index 67ca5fb..4293ce7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -125,8 +125,8 @@ void Game_Main(PKEWindowProperties windowProps, const char *executablePath) { PkeThreads_Init(); AM_Init(); ECS_Init(); - pke_audio_init(); Physics_Init(); + pke_audio_init(); PkeCamera_Init(); pke_level_init(); pke_scene_master_init(); @@ -311,10 +311,10 @@ GAME_SHUTDOWN: pke_scene_master_teardown(); pke_level_teardown(); PkeCamera_Teardown(); + pke_audio_teardown(); Physics_Teardown(); ECS_Teardown(); DestroyWindow(); - pke_audio_teardown(); AM_DebugPrint(); AM_Teardown(); PkeThreads_Teardown(); diff --git a/tests/pke-test-audio.cpp b/tests/pke-test-audio.cpp index 2d48022..4607033 100644 --- a/tests/pke-test-audio.cpp +++ b/tests/pke-test-audio.cpp @@ -50,23 +50,23 @@ int pke_test_audio_001() { UBO.model = glm::translate(glm::mat4(1.f), glm::vec3(0, 0, 0)); - float *zip_bop_bytes = pk_new<float>(48000, bkt); + float *zip_bop_bytes = pk_new<float>(PKE_AUDIO_BITRATE, bkt); float phase = 0.0f; - float phase_increment = 440.f / 48000.f; - for (i = 0; i < 48000; ++i) { + float phase_increment = 440.f / float(PKE_AUDIO_BITRATE); + for (i = 0; i < PKE_AUDIO_BITRATE; ++i) { zip_bop_bytes[i] = 2.f * (phase - floor(phase + 0.5f)); phase += phase_increment; if (phase >= 1.f) phase -= 1.f; } const AssetKey ak_sawtooth {"sawthooth"}; - AssetHandle ah_sawtooth = AM_Register(ak_sawtooth, PKE_ASSET_TYPE_AUDIO, zip_bop_bytes, sizeof(float) * 48000, 64); - pk_delete<float>(zip_bop_bytes, 48000, bkt); + AssetHandle ah_sawtooth = AM_Register(ak_sawtooth, PKE_ASSET_TYPE_AUDIO, zip_bop_bytes, sizeof(float) * PKE_AUDIO_BITRATE, 64); + pk_delete<float>(zip_bop_bytes, PKE_AUDIO_BITRATE, bkt); pke_audio_play(ah_sawtooth, pke_audio_source_music, pke_audio_flag_none, glm::vec3(0)); while (pke_audio_mstr.playing_objects.next > 0) { pke_audio_tick(0.001f); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::sleep_for(std::chrono::nanoseconds(500000)); } AM_Release(ah_sawtooth); @@ -96,16 +96,16 @@ int pke_test_audio_002() { }; for(k = 0; k < 3; ++k) { - float *zip_bop_bytes = pk_new<float>(48000, bkt); + float *zip_bop_bytes = pk_new<float>(PKE_AUDIO_BITRATE, bkt); float phase = 0.0f; - float phase_increment = freqs[k] / 48000.f; - for (i = 0; i < 48000; ++i) { + float phase_increment = freqs[k] / float(PKE_AUDIO_BITRATE); + for (i = 0; i < PKE_AUDIO_BITRATE; ++i) { zip_bop_bytes[i] = 2.f * (phase - floor(phase + 0.5f)); phase += phase_increment; if (phase >= 1.f) phase -= 1.f; } - ahs[k] = AM_Register(aks[k], PKE_ASSET_TYPE_AUDIO, zip_bop_bytes, sizeof(float) * 48000, 64); - pk_delete<float>(zip_bop_bytes, 48000, bkt); + ahs[k] = AM_Register(aks[k], PKE_ASSET_TYPE_AUDIO, zip_bop_bytes, sizeof(float) * PKE_AUDIO_BITRATE, 64); + pk_delete<float>(zip_bop_bytes, PKE_AUDIO_BITRATE, bkt); } std::chrono::milliseconds(1); for(k = 0; k < 3; ++k) { @@ -114,7 +114,7 @@ int pke_test_audio_002() { while (pke_audio_mstr.playing_objects.next > 0) { pke_audio_tick(0.001f); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::sleep_for(std::chrono::nanoseconds(500000)); } for(k = 0; k < 3; ++k) { @@ -141,8 +141,7 @@ int pke_test_audio_003() { CompInstance *inst = ECS_CreateInstance(ent, pk_uuid_zed, nullptr, &inst_pos); uint64_t dur_seconds = 2; - uint64_t bit_rate = 48000; - uint64_t total_frames = bit_rate * dur_seconds; + uint64_t total_frames = PKE_AUDIO_BITRATE * dur_seconds; AssetHandle ahs[1]; const AssetKey aks[1] {"sawtooth"}; float freqs[1] = {2000.f}; @@ -153,7 +152,7 @@ int pke_test_audio_003() { for(k = 0; k < 1; ++k) { float *zip_bop_bytes = pk_new<float>(total_frames, bkt); float phase = 0.0f; - float phase_increment = freqs[k] / float(bit_rate); + float phase_increment = freqs[k] / float(PKE_AUDIO_BITRATE); for (i = 0; i < total_frames; ++i) { zip_bop_bytes[i] = 2.f * (phase - floor(phase + 0.5f)); phase += phase_increment; @@ -167,7 +166,6 @@ int pke_test_audio_003() { pke_audio_play(ahs[k], pke_audio_source_music, pke_audio_flag_pos_spatial, src_poss[k], inst->instanceHandle); } - float delta = 0.f; float delta_elapsed = 0.f; btTransform trfm{}; @@ -187,7 +185,7 @@ int pke_test_audio_003() { inst->bt.rigidBody->setWorldTransform(trfm); // fprintf(stdout, "%f,%f,%f\n", rot_pos.x, rot_pos.y, rot_pos.z); - std::this_thread::sleep_for(std::chrono::nanoseconds(1)); + std::this_thread::sleep_for(std::chrono::nanoseconds(500000)); } for(k = 0; k < 1; ++k) { |
