summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--config.mk2
-rw-r--r--src/audio-impl-pw.cpp190
-rw-r--r--src/audio-impl-pw.hpp2
-rw-r--r--src/audio-types.hpp19
-rw-r--r--src/audio.cpp56
-rw-r--r--src/audio.hpp3
-rw-r--r--tests/pke-test-audio.cpp93
-rw-r--r--tests/pke-test-audio.h16
-rw-r--r--tests/pke-test.cpp1
10 files changed, 357 insertions, 26 deletions
diff --git a/Makefile b/Makefile
index def9419..18d09b7 100644
--- a/Makefile
+++ b/Makefile
@@ -230,6 +230,7 @@ $(DIR_OBJ)/libpke-example.$(LIB_EXT): $(DIR_OBJ)/example.$(OBJ_EXT)
ranlib $@
$(DIR_OBJ)/libpke-test.$(LIB_EXT): $(DIR_OBJ)/pke-test-dummy.$(OBJ_EXT)
+$(DIR_OBJ)/libpke-test.$(LIB_EXT): $(DIR_OBJ)/pke-test-audio.$(OBJ_EXT)
$(DIR_OBJ)/libpke-test.$(LIB_EXT): $(DIR_OBJ)/pke-test-static-ui.$(OBJ_EXT)
$(DIR_OBJ)/libpke-test.$(LIB_EXT): $(DIR_OBJ)/pke-test-serialization.$(OBJ_EXT)
$(DIR_OBJ)/libpke-test.$(LIB_EXT): $(DIR_OBJ)/pke-test-asset-manager.$(OBJ_EXT)
diff --git a/config.mk b/config.mk
index 473e1bc..40d9088 100644
--- a/config.mk
+++ b/config.mk
@@ -27,6 +27,8 @@ INCS = `$(PKG_CONFIG) --cflags $(USED_LIBS)`
LIBS = -lm `$(PKG_CONFIG) --libs $(USED_LIBS)` -lpthread
# flags
+# -fsanitize=address \
+
SHARED_FLAGS = -D_DEFAULT_SOURCE \
-D_POSIX_C_SOURCE=200809L \
-DPKE_VERSION=\"$(PKE_VERSION)\" \
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;
diff --git a/src/audio-impl-pw.hpp b/src/audio-impl-pw.hpp
index d9bac99..7d195d5 100644
--- a/src/audio-impl-pw.hpp
+++ b/src/audio-impl-pw.hpp
@@ -63,7 +63,7 @@ struct pke_audio_implementation_pipewire {
extern struct pke_audio_implementation_pipewire pke_audio_pw;
void pke_audio_pw_init();
void pke_audio_pw_teardown();
-bool pke_audio_pw_remap_outputs();
+void pke_audio_pw_remap_outputs();
void on_pipewire_process(void *user_data);
void on_pipewire_stream_param_changed(void *, uint32_t id, const struct spa_pod *param);
void on_registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props);
diff --git a/src/audio-types.hpp b/src/audio-types.hpp
index 84ab155..aeba5db 100644
--- a/src/audio-types.hpp
+++ b/src/audio-types.hpp
@@ -4,12 +4,16 @@
#include "asset-manager.hpp"
#include "pk.h"
+#include "vendor-glm-include.hpp"
+
#define PKE_AUDIO_MAX_CONCURRENT_COUNT 8
+#define PKE_AUDIO_MAX_SPATIAL_DISTANCE 256.f
TypeSafeInt_constexpr(pke_audio_flags, uint8_t, 0xFF);
-TypeSafeInt_constexpr(pke_audio_source, uint8_t, 0x0F);
+TypeSafeInt_constexpr(pke_audio_source, uint8_t, 0x40);
-const pke_audio_flags pke_audio_flag_none = pke_audio_flags{0x00};
+const pke_audio_flags pke_audio_flag_none = pke_audio_flags{0x00};
+const pke_audio_flags pke_audio_flag_pos_spatial = pke_audio_flags{0x01};
const pke_audio_source pke_audio_source_music = pke_audio_source{0x00};
const pke_audio_source pke_audio_source_ambient = pke_audio_source{0x01};
@@ -19,16 +23,19 @@ const pke_audio_source pke_audio_source_sfx = pke_audio_source{0x03};
struct pke_audio_obj {
AssetHandle handle; // key
pke_audio_source source; // key
+ 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];
uint8_t play_count;
};
struct pke_audio_master {
- pk_arr_t<pke_audio_obj> playing_objects;
- float source_volumes[pke_audio_source_T_MAX];
- uint32_t channel_count; // mono, stereo, 7.1
- std::mutex mtx_buffer;
+ pk_arr_t<pke_audio_obj> playing_objects;
+ pk_membucket *bkt_transient;
+ float master_volume;
+ float source_volumes[pke_audio_source_T_MAX];
+ uint32_t channel_count; // mono, stereo, 7.1
+ std::mutex mtx_buffer;
};
extern struct pke_audio_master pke_audio_mstr;
diff --git a/src/audio.cpp b/src/audio.cpp
index c3f152e..194c01c 100644
--- a/src/audio.cpp
+++ b/src/audio.cpp
@@ -1,13 +1,19 @@
#define PKE_AUDIO_IMPL_PIPEWIRE
-#include "audio.hpp"
-
+#include "asset-manager.hpp"
#include "audio-impl-pw.hpp"
+#include "audio.hpp"
+#include "pk.h"
struct pke_audio_master pke_audio_mstr{};
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);
+ for (uint8_t i = 0; i < pke_audio_mstr.channel_count; ++i) {
+ pke_audio_mstr.source_volumes[i] = 1.f;
+ }
#ifdef PKE_AUDIO_IMPL_PIPEWIRE
pke_audio_pw_init();
#endif
@@ -16,12 +22,13 @@ void pke_audio_teardown() {
#ifdef PKE_AUDIO_IMPL_PIPEWIRE
pke_audio_pw_teardown();
#endif
+ pk_mem_bucket_destroy(pke_audio_mstr.bkt_transient);
}
void pke_audio_tick(double delta) {
(void)delta;
#ifdef PKE_AUDIO_IMPL_PIPEWIRE
if (pke_audio_pw.is_needing_output_remapped == true) {
- pke_audio_pw.is_needing_output_remapped = !pke_audio_pw_remap_outputs();
+ pke_audio_pw_remap_outputs();
}
#endif
}
@@ -34,5 +41,44 @@ void pke_audio_set_volume(pke_audio_source source, float volume) {
pke_audio_mstr.source_volumes[static_cast<pke_audio_source_T>(source)] = volume;
}
-void pke_audio_play(AssetHandle handle, pke_audio_source audio_source, pke_audio_flags flags);
-void pke_audio_stop_all();
+bool pke_audio_playing_objects_find_first_by_key(void *user_data, void *arr_data) {
+ std::tuple<AssetHandle, pke_audio_source> &tup = *reinterpret_cast<std::tuple<AssetHandle, pke_audio_source>*>(user_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) {
+ // 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};
+ pke_audio_mstr.mtx_buffer.lock();
+ uint32_t idx = pk_arr_find_first_index(&pke_audio_mstr.playing_objects, &tup, pke_audio_playing_objects_find_first_by_key);
+ pke_audio_obj *aobj = NULL;
+ if (idx == uint32_t(-1)) {
+ AM_Get(handle); // keep the asset in memory, freed when play_count hits 0
+ pk_arr_append_t(&pke_audio_mstr.playing_objects, {});
+ aobj = &pke_audio_mstr.playing_objects[pke_audio_mstr.playing_objects.next-1];
+ memset(aobj, 0, sizeof(pke_audio_obj));
+ aobj->handle = handle;
+ aobj->source = audio_source;
+ aobj->play_count = 1;
+ idx = 0;
+ } else {
+ aobj = &pke_audio_mstr.playing_objects[idx];
+ idx = aobj->play_count;
+ aobj->play_count += 1;
+ }
+ aobj->position_source[idx] = position_source;
+ aobj->flags[idx] = flags;
+ 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;
+ pke_audio_mstr.mtx_buffer.lock();
+ for (i = 0; i < pke_audio_mstr.playing_objects.next; ++i) {
+ AM_Release(pke_audio_mstr.playing_objects[i].handle);
+ }
+ pk_arr_clear(&pke_audio_mstr.playing_objects);
+ pke_audio_mstr.mtx_buffer.unlock();
+}
diff --git a/src/audio.hpp b/src/audio.hpp
index 3084c84..b2eaa80 100644
--- a/src/audio.hpp
+++ b/src/audio.hpp
@@ -4,6 +4,7 @@
#include "audio-types.hpp"
#include "asset-manager.hpp"
+#include "vendor-glm-include.hpp"
void pke_audio_init();
void pke_audio_teardown();
@@ -12,7 +13,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, pke_audio_flags flags = pke_audio_flag_none);
+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_stop_all();
#endif /* PKE_AUDIO_HPP */
diff --git a/tests/pke-test-audio.cpp b/tests/pke-test-audio.cpp
new file mode 100644
index 0000000..2c1ac97
--- /dev/null
+++ b/tests/pke-test-audio.cpp
@@ -0,0 +1,93 @@
+
+#include "./pke-test-audio.h"
+
+#include "game-settings.hpp"
+#include "pk.h"
+
+#include "audio-types.hpp"
+#include "audio.hpp"
+#include "asset-manager.hpp"
+#include "thread-pool.hpp"
+#include "window.hpp"
+
+#include <chrono>
+#include <thread>
+#include <threads.h>
+
+pk_membucket *bkt = nullptr;
+
+void pke_test_audio_spinup() {
+ // pk_funcinstr_init();
+ pkeSettings.isSimulationPaused = true;
+ PkeThreads_Init();
+ AM_Init();
+ pke_audio_init();
+ // pk_funcinstr_teardown();
+};
+
+void pke_test_audio_teardown() {
+ // pk_funcinstr_init();
+ pke_audio_teardown();
+ AM_Teardown();
+ PkeThreads_Teardown();
+ bkt = nullptr;
+ // pk_funcinstr_teardown();
+};
+
+
+int pke_test_audio_001() {
+ uint32_t i;
+ 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));
+
+ float *zip_bop_bytes = pk_new<float>(48000, bkt);
+ float phase = 0.0f;
+ float phase_increment = 440.f / 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;
+ }
+ 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);
+
+ pke_audio_play(ah_sawtooth, pke_audio_source_music, glm::vec3(0), pke_audio_flag_none);
+
+ while (pke_audio_mstr.playing_objects.next > 0) {
+ pke_audio_tick(0.001f);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ AM_Release(ah_sawtooth);
+
+ } 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 struct pke_test tests[test_count] = {
+ {
+ .title = "test 001",
+ .func = pke_test_audio_001,
+ .expected_result = 0,
+ },
+ };
+ static struct pke_test_group group = {};
+ group.title = "audio";
+ group.group_setup = nullptr;
+ group.group_teardown = nullptr;
+ group.test_setup = pke_test_audio_spinup;
+ group.test_teardown = pke_test_audio_teardown;
+ group.n_tests = test_count;
+ group.tests = &tests[0];
+
+ return &group;
+}
diff --git a/tests/pke-test-audio.h b/tests/pke-test-audio.h
new file mode 100644
index 0000000..851c695
--- /dev/null
+++ b/tests/pke-test-audio.h
@@ -0,0 +1,16 @@
+#ifndef PKE_PKE_TEST_AUDIO_H
+#define PKE_PKE_TEST_AUDIO_H
+
+#include "pke-test-types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pke_test_group *pke_test_audio_get_group();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PKE_PKE_TEST_AUDIO_H */
diff --git a/tests/pke-test.cpp b/tests/pke-test.cpp
index 473327e..d695aa8 100644
--- a/tests/pke-test.cpp
+++ b/tests/pke-test.cpp
@@ -1,6 +1,7 @@
#include "./pke-test-types.h"
+#include "./pke-test-audio.h"
#include "./pke-test-asset-manager.h"
#include "./pke-test-dummy.h"
#include "./pke-test-serialization.h"