summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Bradley <jcb@pikum.xyz>2025-07-14 16:35:32 -0400
committerJonathan Bradley <jcb@pikum.xyz>2025-07-14 16:35:32 -0400
commit7f0b0a59425321dcc880ddc0f4b479bce85e0bb0 (patch)
treec5c10d187faf26a248d859deac68ba83bbe26e97
parentf88ca0bc946bae086e02eacdc6c129f00e2e07e3 (diff)
pke: audio: process each tick, impl buffer copy
-rw-r--r--src/audio-impl-pw.cpp238
-rw-r--r--src/audio-impl-shared.cpp17
-rw-r--r--src/audio-impl-shared.hpp6
-rw-r--r--src/audio-types.hpp11
-rw-r--r--src/audio.cpp266
-rw-r--r--src/game.cpp4
-rw-r--r--tests/pke-test-audio.cpp32
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], &params_low_pass_filter[pc]);
- val += pke_audio_fx_reverb((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], &params_reverb[pc]);
- val += pke_audio_fx_delay((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], &params_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], &params_low_pass_filter[c]);
+ val += pke_audio_fx_reverb((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], &params_reverb[c]);
+ val += pke_audio_fx_delay((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], &params_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) {