summaryrefslogtreecommitdiff
path: root/src/audio-impl-pw.cpp
diff options
context:
space:
mode:
authorJonathan Bradley <jcb@pikum.xyz>2025-07-04 07:06:45 -0400
committerJonathan Bradley <jcb@pikum.xyz>2025-07-04 07:06:45 -0400
commit34863f5b702c0dbb00d8db5c00efd43d895fcd4c (patch)
tree09e0249e16e937c22ec0c04cfa1bc4b0e4de337e /src/audio-impl-pw.cpp
parent1c87a0e431d30aaf19195f8a45c7607add21018a (diff)
pke: audio: first-pass actually play an asset
Diffstat (limited to 'src/audio-impl-pw.cpp')
-rw-r--r--src/audio-impl-pw.cpp190
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;