diff options
| author | Jonathan Bradley <jcb@pikum.xyz> | 2025-07-08 18:48:19 -0400 |
|---|---|---|
| committer | Jonathan Bradley <jcb@pikum.xyz> | 2025-07-08 18:48:19 -0400 |
| commit | e314577d9c775bd189e9494e7700e2e04cf79c21 (patch) | |
| tree | 6fae0c3164a3ed755c795d42add21e7ebf8ec74b | |
| parent | 512df40330e1409516b654857ca031fac19927b6 (diff) | |
pke: spatial audio first-pass
| -rw-r--r-- | src/audio-impl-pw.cpp | 73 | ||||
| -rw-r--r-- | tests/pke-test-audio.cpp | 57 |
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"; |
