diff options
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | config.mk | 2 | ||||
| -rw-r--r-- | src/audio-impl-pw.cpp | 190 | ||||
| -rw-r--r-- | src/audio-impl-pw.hpp | 2 | ||||
| -rw-r--r-- | src/audio-types.hpp | 19 | ||||
| -rw-r--r-- | src/audio.cpp | 56 | ||||
| -rw-r--r-- | src/audio.hpp | 3 | ||||
| -rw-r--r-- | tests/pke-test-audio.cpp | 93 | ||||
| -rw-r--r-- | tests/pke-test-audio.h | 16 | ||||
| -rw-r--r-- | tests/pke-test.cpp | 1 |
10 files changed, 357 insertions, 26 deletions
@@ -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) @@ -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" |
