summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Bradley <jcb@pikum.xyz>2025-07-08 18:48:19 -0400
committerJonathan Bradley <jcb@pikum.xyz>2025-07-08 18:48:19 -0400
commite314577d9c775bd189e9494e7700e2e04cf79c21 (patch)
tree6fae0c3164a3ed755c795d42add21e7ebf8ec74b
parent512df40330e1409516b654857ca031fac19927b6 (diff)
pke: spatial audio first-pass
-rw-r--r--src/audio-impl-pw.cpp73
-rw-r--r--tests/pke-test-audio.cpp57
2 files changed, 106 insertions, 24 deletions
diff --git a/src/audio-impl-pw.cpp b/src/audio-impl-pw.cpp
index 53953ba..4f5f4cf 100644
--- a/src/audio-impl-pw.cpp
+++ b/src/audio-impl-pw.cpp
@@ -275,14 +275,13 @@ void on_pipewire_process(void *user_data) {
spa_buffer *buf;
int stride;
uint8_t pc, pc2;
- uint32_t i, c;
+ uint32_t i, ii, c;
uint64_t n_frames, i_frame;
float *dst;
- float val, vol;
+ float val, vol, vol2;
float *spatial_volumes = nullptr;
glm::vec3 listener_origin;
- glm::vec3 relative_audio_pos = glm::vec3(0);
- glm::vec3 *spatial_sources = nullptr;
+ glm::vec3 audio_dir = glm::vec3(0);
glm::vec3 *spatial_normals = nullptr;
pk_mem_bucket_reset(pke_audio_mstr.bkt_transient);
@@ -304,10 +303,9 @@ void on_pipewire_process(void *user_data) {
}
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
+ // calculate spatial_normals
// TODO maybe don't use UBO
// TODO project-defined?
// TODO instanced audio manager? (easier to handle explicit stereo sources?)
@@ -315,12 +313,12 @@ void on_pipewire_process(void *user_data) {
for (c = 0; c < pke_audio_mstr.channel_count; ++c) {
switch (c) {
case 0:
- // left-facing-speaker
+ // left-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));
+ spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, 1.f));
break;
case 2:
// center
@@ -364,20 +362,36 @@ void on_pipewire_process(void *user_data) {
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) {
- if (aobj->position_source[pc] != glm::vec3(0)) {
- relative_audio_pos = glm::normalize(aobj->position_source[pc] - listener_origin);
+ 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;
+ }
+ }
+ if (audio_dir != glm::vec3(0)) {
+ float dot_dir = glm::dot(spatial_normals[c], audio_dir);
+ // fprintf(stderr, "[pw] dot: %f\n", dot_dir);
+ spatial_volumes[c] = glm::clamp(dot_dir, 0.f, 1.f);
}
- spatial_volumes[c] = glm::clamp(glm::dot(relative_audio_pos, spatial_sources[c]), 0.f, 1.f);
- if (isnan(spatial_volumes[c])) {
+ if (isnan(spatial_volumes[c]) || spatial_volumes[c] == 0.0f) {
/*
- 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,
+ 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
);
@@ -387,39 +401,52 @@ void on_pipewire_process(void *user_data) {
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 * pke_audio_mstr.channel_count); // advance
+ 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)) {
- vol *= spatial_volumes[c];
+ vol2 *= (spatial_volumes[c] * distance_volume);
}
- if (vol <= 0.0) {
+ if (vol2 <= 0.0) {
+ // fprintf(stderr, "[pw] vol2 is <= 0.0\n");
dst += 1;
continue;
}
val = ((float*)a->ptr)[aobj->play_heads[pc]];
- // vol = PK_CLAMP(vol, 0.0, 0.5);
+ // vol2 = PK_CLAMP(vol2, 0.0, 0.5);
/*
- if (isnan(vol)) {
- fprintf(stderr, "[pw] vol is NaN\n");
+ if (isnan(vol2)) {
+ fprintf(stderr, "[pw] vol2 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]);
+ 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]);
}
*/
- *dst += val * vol;
+ *dst += val * vol2;
/*
if (isnan(*dst)) {
fprintf(stderr, "[pw] *dst is NaN\n");
diff --git a/tests/pke-test-audio.cpp b/tests/pke-test-audio.cpp
index 2c1ac97..076634a 100644
--- a/tests/pke-test-audio.cpp
+++ b/tests/pke-test-audio.cpp
@@ -71,14 +71,69 @@ int pke_test_audio_001() {
return 0;
}
+int pke_test_audio_002() {
+ uint32_t i, k;
+ try {
+ bkt = pk_mem_bucket_create("pke_test_serialization", PK_MEM_DEFAULT_BUCKET_SIZE, PK_MEMBUCKET_FLAG_NONE);
+
+ UBO.model = glm::translate(glm::mat4(1.f), glm::vec3(0, 0, 0));
+
+ AssetHandle ahs[3];
+ const AssetKey aks[3] {"sawtooth", "sawtooth2", "sawtooth3"};
+ float freqs[3] = {440.f, 110.f, 330.f};
+ glm::vec3 src_poss[3] = {
+ glm::vec3(-1, 0, 3),
+ glm::vec3( 0, 0, 0),
+ glm::vec3( 1, 0, 0),
+ };
+
+ for(k = 0; k < 3; ++k) {
+ float *zip_bop_bytes = pk_new<float>(48000, bkt);
+ float phase = 0.0f;
+ float phase_increment = freqs[k] / 48000.f;
+ for (i = 0; i < 48000; ++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);
+ }
+ std::chrono::milliseconds(1);
+ for(k = 0; k < 3; ++k) {
+ pke_audio_play(ahs[k], pke_audio_source_music, src_poss[k], pke_audio_flag_pos_spatial);
+ }
+
+ while (pke_audio_mstr.playing_objects.next > 0) {
+ pke_audio_tick(0.001f);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ for(k = 0; k < 3; ++k) {
+ AM_Release(ahs[k]);
+ }
+
+ } catch (const std::exception &ex) {
+ pk_mem_bucket_destroy(bkt);
+ throw;
+ }
+ pk_mem_bucket_destroy(bkt);
+ return 0;
+}
+
struct pke_test_group *pke_test_audio_get_group() {
- static const uint64_t test_count = 1;
+ static const uint64_t test_count = 2;
static struct pke_test tests[test_count] = {
{
.title = "test 001",
.func = pke_test_audio_001,
.expected_result = 0,
},
+ {
+ .title = "test 002",
+ .func = pke_test_audio_002,
+ .expected_result = 0,
+ },
};
static struct pke_test_group group = {};
group.title = "audio";