diff options
| author | Jonathan Bradley <jcb@pikum.xyz> | 2025-07-04 07:06:45 -0400 |
|---|---|---|
| committer | Jonathan Bradley <jcb@pikum.xyz> | 2025-07-04 07:06:45 -0400 |
| commit | 34863f5b702c0dbb00d8db5c00efd43d895fcd4c (patch) | |
| tree | 09e0249e16e937c22ec0c04cfa1bc4b0e4de337e /src/audio-impl-pw.cpp | |
| parent | 1c87a0e431d30aaf19195f8a45c7607add21018a (diff) | |
pke: audio: first-pass actually play an asset
Diffstat (limited to 'src/audio-impl-pw.cpp')
| -rw-r--r-- | src/audio-impl-pw.cpp | 190 |
1 files changed, 177 insertions, 13 deletions
diff --git a/src/audio-impl-pw.cpp b/src/audio-impl-pw.cpp index 4c0d909..628976e 100644 --- a/src/audio-impl-pw.cpp +++ b/src/audio-impl-pw.cpp @@ -1,10 +1,13 @@ #include "audio-impl-pw.hpp" +#include "asset-manager.hpp" #include "audio-types.hpp" #include "game-settings.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 @@ -59,7 +62,9 @@ void pke_audio_pw_init() { pke_audio_pw.registry_listener = {}; pke_audio_pw.metadata = nullptr; pke_audio_pw.metadata_listener = {}; + pke_audio_pw.pw_objects = {}; pke_audio_pw.pw_objects.bkt = pke_audio_pw.bkt; + pke_audio_pw.created_objects = {}; pke_audio_pw.created_objects.bkt = pke_audio_pw.bkt; pke_audio_pw.default_sink_name = pk_new<char>(PKE_AUDIO_PW_NAME_RESERVE_LEN, pke_audio_pw.bkt); pke_audio_pw.default_sink_id = 0; @@ -69,7 +74,7 @@ void pke_audio_pw_init() { pw_init(NULL, NULL); - pke_audio_pw.loop = pw_thread_loop_new("pke audio thread", NULL); + pke_audio_pw.loop = pw_thread_loop_new("pke-audio-thrd", NULL); if (pke_audio_pw.loop == NULL) { fprintf(stderr, "[" __FILE__ "][pke_audio_pw_init] failed to create pw_loop"); return; @@ -177,17 +182,19 @@ void pke_audio_pw_teardown() { pke_audio_mstr.mtx_buffer.unlock(); } -bool pke_audio_pw_remap_outputs() { +void pke_audio_pw_remap_outputs() { uint32_t stream_id; uint32_t i; uint32_t port_count = {0}; void *created_obj = {0}; + if (pke_audio_pw.pw_objects.data == nullptr) return; pw_properties *props = pw_properties_new(NULL, NULL); pke_audio_pw_object *objs = (pke_audio_pw_object *)pke_audio_pw.pw_objects.data; 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); + pke_audio_mstr.mtx_buffer.lock(); pw_thread_loop_lock(pke_audio_pw.loop); // remove all existing links @@ -202,6 +209,9 @@ bool pke_audio_pw_remap_outputs() { // + build out_ports and in_ports { stream_id = pw_stream_get_node_id(pke_audio_pw.stream); + if (stream_id == 0) { + goto remap_done; + } for (i = 0; i < pke_audio_pw.pw_objects.next; ++i) { if (objs[i].type != PKE_AUDIO_PW_OBJECT_TYPE_NODE) continue; if ((str = spa_dict_lookup(&objs[i].props->dict, "node.name")), str == NULL) continue; @@ -211,10 +221,8 @@ bool pke_audio_pw_remap_outputs() { } } // assert(stream_id != 0); - if (stream_id == 0) { - pw_properties_free(props); - pw_thread_loop_unlock(pke_audio_pw.loop); - return false; + if (pke_audio_pw.default_sink_id == 0) { + goto remap_done; } for (i = 0; i < pke_audio_pw.pw_objects.next; ++i) { if (objs[i].type != PKE_AUDIO_PW_OBJECT_TYPE_PORT) continue; @@ -230,9 +238,7 @@ bool pke_audio_pw_remap_outputs() { } // assert(port_count != 0); if (port_count == 0) { - pw_properties_free(props); - pw_thread_loop_unlock(pke_audio_pw.loop); - return false; + goto remap_done; } // do links @@ -245,10 +251,13 @@ bool pke_audio_pw_remap_outputs() { if (created_obj != NULL) pk_arr_append(&pke_audio_pw.created_objects, created_obj); } } + fprintf(stdout, "[pke_audio_pw_remap_outputs] remapped %i objs\n", pke_audio_pw.created_objects.next); - pw_properties_free(props); +remap_done: + if (props != nullptr) pw_properties_free(props); + pke_audio_pw.is_needing_output_remapped = pke_audio_pw.created_objects.next != pke_audio_mstr.channel_count; pw_thread_loop_unlock(pke_audio_pw.loop); - return true; + pke_audio_mstr.mtx_buffer.unlock(); } void on_pipewire_process(void *user_data) { @@ -256,8 +265,17 @@ void on_pipewire_process(void *user_data) { pw_buffer *b; spa_buffer *buf; int stride; - uint64_t n_frames; + uint8_t pc, pc2; + uint32_t i, c; + uint64_t n_frames, i_frame; float *dst; + float val, vol; + float *spatial_volumes = nullptr; + glm::vec3 listener_origin; + glm::vec3 relative_audio_pos = glm::vec3(0); + glm::vec3 *spatial_sources = nullptr; + glm::vec3 *spatial_normals = nullptr; + 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"); @@ -276,11 +294,157 @@ void on_pipewire_process(void *user_data) { n_frames = PK_MIN(b->requested, n_frames); } + spatial_volumes = pk_new<float>(pke_audio_mstr.channel_count, pke_audio_mstr.bkt_transient); + spatial_sources = pk_new<glm::vec3>(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); + + // calculate spatial_sources + // 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-facing-speaker + spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 1.f)); + break; + case 1: + // right + spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 1.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(); - memset(dst, 0, sizeof(float) * n_frames * pke_audio_mstr.channel_count); buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = stride; buf->datas[0].chunk->size = n_frames * stride; + + 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) { + + // calculate panning + // TODO handle subwoofer explicitly + float sum = 0.0; + for (c = 0; c < pke_audio_mstr.channel_count; ++c) { + if (aobj->position_source[pc] != glm::vec3(0)) { + relative_audio_pos = glm::normalize(aobj->position_source[pc] - listener_origin); + } + spatial_volumes[c] = glm::clamp(glm::dot(relative_audio_pos, spatial_sources[c]), 0.f, 1.f); + + if (isnan(spatial_volumes[c])) { + /* + fprintf(stderr, "[pw] NaN: norm: %f,%f,%f, src: %f,%f,%f, origin: %f,%f,%f\n", + relative_audio_pos.x, relative_audio_pos.y, relative_audio_pos.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]; + } + if (sum > 1.f) { + for (c = 0; c < pke_audio_mstr.channel_count; ++c) { + spatial_volumes[c] /= sum; + } + } + + for (i_frame = 0; i_frame < n_frames; ++i_frame) { + dst = (float*)buf->datas[0].data; + dst += (i_frame * 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 + + if (PK_HAS_FLAG(aobj->flags[pc], pke_audio_flag_pos_spatial)) { + vol *= spatial_volumes[c]; + } + if (vol <= 0.0) { + dst += 1; + continue; + } + + val = ((float*)a->ptr)[aobj->play_heads[pc]]; + + // vol = PK_CLAMP(vol, 0.0, 0.5); + /* + if (isnan(vol)) { + fprintf(stderr, "[pw] vol is NaN\n"); + } + if (vol == 0.0) { + fprintf(stderr, "[pw] vol is 0, %f, %f, %f\n", pke_audio_mstr.master_volume, pke_audio_mstr.source_volumes[c], spatial_volumes[c]); + } + */ + *dst += val * vol; + /* + 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); + } + pw_stream_queue_buffer(pke_audio_pw.stream, b); pke_audio_mstr.mtx_buffer.unlock(); return; |
