diff options
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | src/audio-impl-pw.cpp | 70 | ||||
| -rw-r--r-- | src/audio-impl-shared.cpp | 22 | ||||
| -rw-r--r-- | src/audio-impl-shared.hpp | 12 | ||||
| -rw-r--r-- | src/audio-types.hpp | 15 | ||||
| -rw-r--r-- | src/audio.cpp | 23 | ||||
| -rw-r--r-- | src/audio.hpp | 3 | ||||
| -rw-r--r-- | src/math-helpers.cpp | 16 | ||||
| -rw-r--r-- | src/math-helpers.hpp | 3 | ||||
| -rw-r--r-- | tests/pke-test-audio.cpp | 92 |
10 files changed, 234 insertions, 23 deletions
@@ -180,6 +180,7 @@ $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/arg-handler.$(OBJ_EXT) $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/asset-manager.$(OBJ_EXT) $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/audio.$(OBJ_EXT) $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/audio-impl-pw.$(OBJ_EXT) +$(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/audio-impl-shared.$(OBJ_EXT) $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/camera.$(OBJ_EXT) $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/dynamic-array.$(OBJ_EXT) $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/ecs.$(OBJ_EXT) diff --git a/src/audio-impl-pw.cpp b/src/audio-impl-pw.cpp index 4f5f4cf..92acd8d 100644 --- a/src/audio-impl-pw.cpp +++ b/src/audio-impl-pw.cpp @@ -1,8 +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" @@ -276,13 +278,16 @@ void on_pipewire_process(void *user_data) { int stride; uint8_t pc, pc2; uint32_t i, ii, c; - uint64_t n_frames, i_frame; + int64_t n_frames, i_frame; 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) { @@ -296,14 +301,11 @@ void on_pipewire_process(void *user_data) { return; } - stride = sizeof(float) * pke_audio_mstr.channel_count; - n_frames = buf->datas[0].maxsize / stride; - if (b->requested) { - n_frames = PK_MIN(b->requested, n_frames); - } - - 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); + 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 @@ -314,11 +316,19 @@ void on_pipewire_process(void *user_data) { switch (c) { case 0: // left-speaker - spatial_normals[c] = glm::normalize(glm::vec3(-1.f, 0.f, 1.f)); + 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 - spatial_normals[c] = glm::normalize(glm::vec3( 1.f, 0.f, 1.f)); + 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 @@ -350,10 +360,23 @@ void on_pipewire_process(void *user_data) { } pke_audio_mstr.mtx_buffer.lock(); + + stride = sizeof(float) * pke_audio_mstr.channel_count; + + n_frames = buf->datas[0].maxsize / stride; + 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); + buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = stride; buf->datas[0].chunk->size = n_frames * stride; + if (buf->datas[0].chunk->size == 0) { + 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); @@ -381,11 +404,19 @@ void on_pipewire_process(void *user_data) { break; } } - if (audio_dir != glm::vec3(0)) { - float dot_dir = glm::dot(spatial_normals[c], audio_dir); + 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); - spatial_volumes[c] = glm::clamp(dot_dir, 0.f, 1.f); + + // 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) { /* @@ -430,14 +461,11 @@ void on_pipewire_process(void *user_data) { vol2 *= (spatial_volumes[c] * distance_volume); } if (vol2 <= 0.0) { - // fprintf(stderr, "[pw] vol2 is <= 0.0\n"); + // fprintf(stderr, "[pw] chan: %i vol2 is <= 0.0\n", c); dst += 1; continue; } - val = ((float*)a->ptr)[aobj->play_heads[pc]]; - - // vol2 = PK_CLAMP(vol2, 0.0, 0.5); /* if (isnan(vol2)) { fprintf(stderr, "[pw] vol2 is NaN\n"); @@ -446,6 +474,11 @@ void on_pipewire_process(void *user_data) { 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], ¶ms_low_pass_filter[pc]); + val += pke_audio_fx_reverb((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], ¶ms_reverb[pc]); + val += pke_audio_fx_delay((float*)a->ptr, a->size / sizeof(float), (uint32_t)aobj->play_heads[pc], ¶ms_delay[pc]); *dst += val * vol2; /* if (isnan(*dst)) { @@ -481,6 +514,7 @@ void on_pipewire_process(void *user_data) { memset(dst, 0, sizeof(float) * n_frames * pke_audio_mstr.channel_count); } +audio_done: pw_stream_queue_buffer(pke_audio_pw.stream, b); pke_audio_mstr.mtx_buffer.unlock(); return; diff --git a/src/audio-impl-shared.cpp b/src/audio-impl-shared.cpp new file mode 100644 index 0000000..0c3c730 --- /dev/null +++ b/src/audio-impl-shared.cpp @@ -0,0 +1,22 @@ + +#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; + // 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; + 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 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 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 new file mode 100644 index 0000000..3b7b158 --- /dev/null +++ b/src/audio-impl-shared.hpp @@ -0,0 +1,12 @@ +#ifndef PKE_AUDIO_IMPL_SHARED_HPP +#define PKE_AUDIO_IMPL_SHARED_HPP + +#include "audio-types.hpp" + +#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); + +#endif /* PKE_AUDIO_IMPL_SHARED_HPP */ diff --git a/src/audio-types.hpp b/src/audio-types.hpp index aeba5db..a0ab3fb 100644 --- a/src/audio-types.hpp +++ b/src/audio-types.hpp @@ -2,6 +2,7 @@ #define PKE_AUDIO_TYPES_INTERNAL_HPP #include "asset-manager.hpp" +#include "components.hpp" #include "pk.h" #include "vendor-glm-include.hpp" @@ -20,9 +21,23 @@ const pke_audio_source pke_audio_source_ambient = pke_audio_source{0x01}; const pke_audio_source pke_audio_source_voices = pke_audio_source{0x02}; const pke_audio_source pke_audio_source_sfx = pke_audio_source{0x03}; +struct pke_audio_fx_params_reverb { + float reverb_strength; +}; + +struct pke_audio_fx_params_delay { + uint64_t delay_frames; +}; + +struct pke_audio_fx_params_low_pass_filter { + float cutoff_freq; + float prev_output; +}; + struct pke_audio_obj { AssetHandle handle; // key pke_audio_source source; // key + InstanceHandle instance_handle[PKE_AUDIO_MAX_CONCURRENT_COUNT]; glm::vec3 position_source[PKE_AUDIO_MAX_CONCURRENT_COUNT]; pke_audio_flags flags[PKE_AUDIO_MAX_CONCURRENT_COUNT]; uint32_t play_heads[PKE_AUDIO_MAX_CONCURRENT_COUNT]; diff --git a/src/audio.cpp b/src/audio.cpp index 194c01c..33ea740 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -3,6 +3,8 @@ #include "asset-manager.hpp" #include "audio-impl-pw.hpp" #include "audio.hpp" +#include "ecs.hpp" +#include "math-helpers.hpp" #include "pk.h" struct pke_audio_master pke_audio_mstr{}; @@ -26,11 +28,29 @@ void pke_audio_teardown() { } 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(); } #endif + pke_audio_mstr.mtx_buffer.lock(); + for (i = 0; i < pke_audio_mstr.playing_objects.next; ++i) { + for (k = 0; k < pke_audio_mstr.playing_objects[i].play_count; ++k) { + if (pke_audio_mstr.playing_objects[i].instance_handle[k] == InstanceHandle_MAX) { + continue; + } + CompInstance *inst = ECS_GetInstance(pke_audio_mstr.playing_objects[i].instance_handle[k]); + if (inst == nullptr || inst->instanceHandle == InstanceHandle_MAX) { + continue; + } + BulletToGlm( + inst->bt.rigidBody->getWorldTransform().getOrigin(), + pke_audio_mstr.playing_objects[i].position_source[k] + ); + } + } + pke_audio_mstr.mtx_buffer.unlock(); } float pke_audio_get_volume(pke_audio_source source) { @@ -46,7 +66,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, glm::vec3 position_source, pke_audio_flags flags) { +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 std::tuple<AssetHandle, pke_audio_source> tup {handle, audio_source}; @@ -67,6 +87,7 @@ void pke_audio_play(AssetHandle handle, pke_audio_source audio_source, glm::vec3 idx = aobj->play_count; aobj->play_count += 1; } + aobj->instance_handle[idx] = instance_handle; aobj->position_source[idx] = position_source; aobj->flags[idx] = flags; aobj->play_heads[idx] = 0; diff --git a/src/audio.hpp b/src/audio.hpp index b2eaa80..5c66b58 100644 --- a/src/audio.hpp +++ b/src/audio.hpp @@ -4,6 +4,7 @@ #include "audio-types.hpp" #include "asset-manager.hpp" +#include "components.hpp" #include "vendor-glm-include.hpp" void pke_audio_init(); @@ -13,7 +14,7 @@ void pke_audio_tick(double delta); float pke_audio_get_volume(pke_audio_source source); void pke_audio_set_volume(pke_audio_source source, float volume); -void pke_audio_play(AssetHandle handle, pke_audio_source audio_source, glm::vec3 position_source = glm::vec3(0), pke_audio_flags flags = pke_audio_flag_none); +void pke_audio_play(AssetHandle handle, pke_audio_source audio_source, pke_audio_flags flags, glm::vec3 position_source = glm::vec3(0), InstanceHandle instance_handle = InstanceHandle_MAX); void pke_audio_stop_all(); #endif /* PKE_AUDIO_HPP */ diff --git a/src/math-helpers.cpp b/src/math-helpers.cpp index 5150e55..d153ce5 100644 --- a/src/math-helpers.cpp +++ b/src/math-helpers.cpp @@ -48,3 +48,19 @@ void BulletToGlm(const btTransform &trans, glm::mat4 &glmMat4) { glmMat4[3][3] = 1.0f; } +template<typename T> +T log_interp_T(T min_value, T max_value, T t) { + T start = log10(min_value); + T end = log10(max_value); + T intrp = start + (t * (end - start)); + return pow(T(10), intrp); +} + +float log_interp(float min_value, float max_value, float t) { + return log_interp_T<float>(min_value, max_value, t); +} + +double log_interp(double min_value, double max_value, double t) { + return log_interp_T<double>(min_value, max_value, t); +} + diff --git a/src/math-helpers.hpp b/src/math-helpers.hpp index 2039348..8625758 100644 --- a/src/math-helpers.hpp +++ b/src/math-helpers.hpp @@ -12,4 +12,7 @@ void BulletToGlm(const btVector3 &vec, glm::vec3 &glmVec); void BulletToGlm(const btQuaternion &quat, glm::quat &glmQuat); void BulletToGlm(const btTransform &trans, glm::mat4 &glmMat4); +float log_interp(float min_value, float max_value, float t); +double log_interp(double min_value, double max_value, double t); + #endif /* PKE_MATH_HELPERS_HPP */ diff --git a/tests/pke-test-audio.cpp b/tests/pke-test-audio.cpp index 076634a..2d48022 100644 --- a/tests/pke-test-audio.cpp +++ b/tests/pke-test-audio.cpp @@ -1,7 +1,10 @@ #include "./pke-test-audio.h" +#include "ecs.hpp" #include "game-settings.hpp" +#include "math-helpers.hpp" +#include "physics.hpp" #include "pk.h" #include "audio-types.hpp" @@ -21,13 +24,18 @@ void pke_test_audio_spinup() { pkeSettings.isSimulationPaused = true; PkeThreads_Init(); AM_Init(); + Physics_Init(); + ECS_Init(); pke_audio_init(); + pke_audio_mstr.master_volume = 0.125; // pk_funcinstr_teardown(); }; void pke_test_audio_teardown() { // pk_funcinstr_init(); pke_audio_teardown(); + ECS_Teardown(); + Physics_Teardown(); AM_Teardown(); PkeThreads_Teardown(); bkt = nullptr; @@ -54,7 +62,7 @@ int pke_test_audio_001() { 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); - pke_audio_play(ah_sawtooth, pke_audio_source_music, glm::vec3(0), pke_audio_flag_none); + 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); @@ -101,7 +109,7 @@ int pke_test_audio_002() { } 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); + pke_audio_play(ahs[k], pke_audio_source_music, pke_audio_flag_pos_spatial, src_poss[k]); } while (pke_audio_mstr.playing_objects.next > 0) { @@ -121,8 +129,81 @@ int pke_test_audio_002() { return 0; } +int pke_test_audio_003() { + uint64_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)); + + InstPos inst_pos{}; + Entity_Base *ent = ECS_CreateGenericEntity(); + 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; + AssetHandle ahs[1]; + const AssetKey aks[1] {"sawtooth"}; + float freqs[1] = {2000.f}; + glm::vec3 src_poss[1] = { + glm::vec3(5, 0, 5), + }; + + 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); + for (i = 0; i < total_frames; ++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) * total_frames, 64); + pk_delete<float>(zip_bop_bytes, total_frames, bkt); + } + + for(k = 0; k < 1; ++k) { + 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{}; + std::chrono::time_point ts = std::chrono::steady_clock::now(); + std::chrono::time_point now = std::chrono::steady_clock::now(); + while (pke_audio_mstr.playing_objects.next > 0) { + now = std::chrono::steady_clock::now(); + auto ts_diff = now - ts; + delta = ts_diff.count() / 1000000000.f; + ts = now; + + delta_elapsed += delta; + pke_audio_tick(delta); + glm::vec3 rot_pos = glm::rotate(glm::mat4(1.f), glm::radians(360.f) * delta_elapsed, glm::vec3(0.f, 1.f, 0.f)) * glm::vec4(5.f, 0.f, 5.f, 1.f); + GlmToBullet(rot_pos, trfm.getOrigin()); + // NOTE: fine for a test, but prefer ECS_UpdateInstance() + 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)); + } + + for(k = 0; k < 1; ++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 = 2; + static const uint64_t test_count = 3; static struct pke_test tests[test_count] = { { .title = "test 001", @@ -134,6 +215,11 @@ struct pke_test_group *pke_test_audio_get_group() { .func = pke_test_audio_002, .expected_result = 0, }, + { + .title = "test 003", + .func = pke_test_audio_003, + .expected_result = 0, + }, }; static struct pke_test_group group = {}; group.title = "audio"; |
