diff options
| author | Jonathan Bradley <jcb@pikum.xyz> | 2025-06-25 17:50:44 -0400 |
|---|---|---|
| committer | Jonathan Bradley <jcb@pikum.xyz> | 2025-06-25 17:50:44 -0400 |
| commit | 9e791d26560b566bb21b5cd39d9042a41f29714c (patch) | |
| tree | 7e95ca82423feb1009b6916bca82142d1326a94a | |
| parent | 3c73b503330eb67ad9489da6941ae3b28a686780 (diff) | |
audio: first-pass, pipewire
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | config.mk | 3 | ||||
| -rw-r--r-- | src/audio-impl-pw.cpp | 364 | ||||
| -rw-r--r-- | src/audio-impl-pw.hpp | 71 | ||||
| -rw-r--r-- | src/audio-types.hpp | 36 | ||||
| -rw-r--r-- | src/audio.cpp | 32 | ||||
| -rw-r--r-- | src/audio.hpp | 18 | ||||
| -rw-r--r-- | src/game.cpp | 3 |
8 files changed, 529 insertions, 0 deletions
@@ -178,6 +178,8 @@ $(DIR_OBJ)/libBullet3.$(LIB_EXT): $(DIR_OBJ)/libpke.$(LIB_EXT): $(DST_SHADERS) $(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)/camera.$(OBJ_EXT) $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/dynamic-array.$(OBJ_EXT) $(DIR_OBJ)/libpke.$(LIB_EXT): $(DIR_OBJ)/ecs.$(OBJ_EXT) @@ -11,6 +11,7 @@ CMAKE = /usr/bin/cmake GLSLC = /usr/bin/glslc # includes and libs +# samplerate USED_LIBS = \ vulkan \ glfw3 \ @@ -18,6 +19,8 @@ USED_LIBS = \ freetype2 \ libpng \ tinyxml2 \ + libpipewire-0.3 \ + libspa-0.2 \ INCS = `$(PKG_CONFIG) --cflags $(USED_LIBS)` diff --git a/src/audio-impl-pw.cpp b/src/audio-impl-pw.cpp new file mode 100644 index 0000000..61e27a2 --- /dev/null +++ b/src/audio-impl-pw.cpp @@ -0,0 +1,364 @@ + +#include "audio-impl-pw.hpp" +#include "audio-types.hpp" +#include "pipewire/keys.h" +#include "pipewire/thread-loop.h" +#include "pk.h" + +// see header file +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#include "spa/utils/json.h" +#pragma GCC diagnostic pop + +#define PKE_AUDIO_PW_NAME_RESERVE_LEN 256 + +pke_audio_implementation_pipewire pke_audio_impl_pw{}; + +int metadata_event_property(void *data, uint32_t subject, const char *key, const char *type, const char *value); + +static const struct pw_stream_events stream_events = { + .version = PW_VERSION_STREAM_EVENTS, + .destroy = nullptr, + .state_changed = nullptr, + .control_info = nullptr, + .io_changed = nullptr, + .param_changed = on_pipewire_stream_param_changed, + .add_buffer = nullptr, + .remove_buffer = nullptr, + .process = on_pipewire_process, + .drained = nullptr, + .command = nullptr, + .trigger_done = nullptr, +}; +static const struct pw_registry_events registry_events = { + .version = PW_VERSION_REGISTRY_EVENTS, + .global = on_registry_event_global, + .global_remove = on_registry_event_global_removal, +}; +static const struct pw_metadata_events metadata_events { + .version = PW_VERSION_METADATA_EVENTS, + .property = metadata_event_property, +}; + +bool pw_objects_find_pw_object_by_id(void *removed_id, void *item) +{ + return reinterpret_cast<pke_audio_pw_object *>(item)->id == *(uint32_t *)removed_id; +} + +void pke_audio_pw_init() { + int i; + + pke_audio_impl_pw.bkt = pk_mem_bucket_create("pipewire bucket", PK_MEM_DEFAULT_BUCKET_SIZE, PK_MEMBUCKET_FLAG_NONE); + pke_audio_impl_pw.loop = nullptr; + pke_audio_impl_pw.pod_buffer = nullptr; + pke_audio_impl_pw.stream = nullptr; + pke_audio_impl_pw.core = nullptr; + pke_audio_impl_pw.registry = nullptr; + pke_audio_impl_pw.registry_listener = {}; + pke_audio_impl_pw.metadata = nullptr; + pke_audio_impl_pw.metadata_listener = {}; + pke_audio_impl_pw.pw_objects.bkt = pke_audio_impl_pw.bkt; + pke_audio_impl_pw.created_objects.bkt = pke_audio_impl_pw.bkt; + pke_audio_impl_pw.default_sink_name = pk_new<char>(PKE_AUDIO_PW_NAME_RESERVE_LEN, pke_audio_impl_pw.bkt); + pke_audio_impl_pw.default_sink_id = 0; + pke_audio_impl_pw.is_needing_output_remapped = true; + + pke_audio_impl_pw.pod_buffer = pk_new<uint8_t>(pke_audio_mstr.channel_count * PKE_AUDIO_IMPL_POD_BUFFER_LEN, pke_audio_impl_pw.bkt); + + pw_init(NULL, NULL); + + pke_audio_impl_pw.loop = pw_thread_loop_new("pke audio thread", NULL); + if (pke_audio_impl_pw.loop == NULL) { + fprintf(stderr, "[" __FILE__ "][pke_audio_pw_init] failed to create pw_loop"); + return; + } + + pw_properties *props = pw_properties_new( + PW_KEY_MEDIA_NAME, "pke-audio-pw", + PW_KEY_APP_NAME, "pke", + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, "Music", + nullptr); + assert(props != NULL); + pke_audio_impl_pw.stream = pw_stream_new_simple( + pw_thread_loop_get_loop(pke_audio_impl_pw.loop), + "pke-audio-pw", + props, + &stream_events, + NULL /* user-data */); + if (pke_audio_impl_pw.stream == NULL) { + fprintf(stderr, "[" __FILE__ "][pke_audio_pw_init] failed to create pw_stream"); + return; + } + + spa_pod_builder b{}; + b.data = pke_audio_impl_pw.pod_buffer; + b.size = PKE_AUDIO_IMPL_POD_BUFFER_LEN * pke_audio_mstr.channel_count; + b._padding = 0; + b.callbacks.data = NULL; + b.callbacks.funcs = NULL; + b.state.flags = 0; + b.state.offset = 0; + b.state.frame = NULL; + + 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.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; + + const struct spa_pod *params[1]; + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + &spa_audio_info_raw_init); + + if (i = pw_stream_connect(pke_audio_impl_pw.stream, + PW_DIRECTION_OUTPUT, + PW_ID_ANY, + pw_stream_flags(PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS), + params, 1), i != 0) { + fprintf(stderr, "[" __FILE__ "][pke_audio_pw_init] failed to connect to pw_stream"); + return; + } + + pke_audio_impl_pw.core = pw_stream_get_core(pke_audio_impl_pw.stream); + if (pke_audio_impl_pw.core == NULL) { + fprintf(stderr, "[" __FILE__ "][pke_audio_pw_init] failed to get core"); + return; + } + + pke_audio_impl_pw.registry = pw_core_get_registry(pke_audio_impl_pw.core, PW_VERSION_REGISTRY, 0); + if (pke_audio_impl_pw.registry == NULL) { + fprintf(stderr, "[" __FILE__ "][pke_audio_pw_init] failed to get registry"); + return; + } + + memset(&pke_audio_impl_pw.registry_listener, 0, sizeof(pke_audio_impl_pw.registry_listener)); + pw_registry_add_listener(pke_audio_impl_pw.registry, &pke_audio_impl_pw.registry_listener, ®istry_events, NULL); + + pw_thread_loop_start(pke_audio_impl_pw.loop); + return; +} + +void pke_audio_pw_teardown() { + uint32_t i; + pke_audio_mstr.mtx_buffer.lock(); + pw_thread_loop_stop(pke_audio_impl_pw.loop); + for (i = pke_audio_impl_pw.created_objects.next; i > 0; --i) { + pw_core_destroy(pke_audio_impl_pw.core, ((void **)pke_audio_impl_pw.created_objects.data)[i - 1]); + } + for (i = pke_audio_impl_pw.pw_objects.next; i > 0; --i) { + pw_properties_free(pke_audio_impl_pw.pw_objects[i - 1].props); + } + if (pke_audio_impl_pw.metadata != NULL) { + pw_proxy_destroy((struct pw_proxy*)pke_audio_impl_pw.metadata); + } + if (pke_audio_impl_pw.registry != NULL) { + pw_proxy_destroy((struct pw_proxy*)pke_audio_impl_pw.registry); + } + if (pke_audio_impl_pw.core != NULL) { + pw_core_disconnect(pke_audio_impl_pw.core); + } + if (pke_audio_impl_pw.stream != NULL) { + pw_stream_destroy(pke_audio_impl_pw.stream); + } + if (pke_audio_impl_pw.loop != NULL) { + pw_thread_loop_destroy(pke_audio_impl_pw.loop); + } + pw_deinit(); + pk_delete<char>(pke_audio_impl_pw.default_sink_name, PKE_AUDIO_PW_NAME_RESERVE_LEN, pke_audio_impl_pw.bkt); + pk_delete<uint8_t>(pke_audio_impl_pw.pod_buffer, pke_audio_mstr.channel_count * PKE_AUDIO_IMPL_POD_BUFFER_LEN, pke_audio_impl_pw.bkt); + pk_mem_bucket_destroy(pke_audio_impl_pw.bkt); + pke_audio_impl_pw.bkt = CAFE_BABE(pk_membucket); + pke_audio_mstr.mtx_buffer.unlock(); +} + +void on_pipewire_process(void *user_data) { + (void)user_data; + pw_buffer *b; + spa_buffer *buf; + int stride; + uint64_t n_frames; + float *dst; + + if ((b = pw_stream_dequeue_buffer(pke_audio_impl_pw.stream)) == NULL) { + fprintf(stderr, "[" __FILE__ "][on_pipewire_process] out of buffers"); + return; + } + + buf = b->buffer; + if ((dst = (float*)buf->datas[0].data) == NULL) { + fprintf(stderr, "[" __FILE__ "][on_pipewire_process] given buffer was null"); + 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); + } + + 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; + pw_stream_queue_buffer(pke_audio_impl_pw.stream, b); + pke_audio_mstr.mtx_buffer.unlock(); + return; +} + +void on_pipewire_stream_param_changed(void *, uint32_t id, const struct spa_pod *param) { + /* NULL means to clear the format */ + if (param == NULL || id != SPA_PARAM_Format) { + // memset(&pke_audio_impl_pw.format, 0, sizeof(struct spa_audio_info)); + return; + } + + if (spa_format_parse(param, &pke_audio_impl_pw.format.media_type, &pke_audio_impl_pw.format.media_subtype) < 0) { + return; + } + + /* only accept raw audio */ + if (pke_audio_impl_pw.format.media_type != SPA_MEDIA_TYPE_audio || pke_audio_impl_pw.format.media_subtype != SPA_MEDIA_SUBTYPE_raw) { + return; + } + + /* call a helper function to parse the format for us. */ + spa_format_audio_raw_parse(param, &pke_audio_impl_pw.format.info.raw); + + fprintf(stdout, "\r\ncapturing rate changed:%d channels:%d\n", pke_audio_impl_pw.format.info.raw.rate, pke_audio_impl_pw.format.info.raw.channels); + return; +} + +int metadata_event_property(void *data, uint32_t subject, const char *key, const char *type, const char *value) +{ + if (subject == PW_ID_CORE) { + if (key == NULL || spa_streq(key, "default.audio.sink")) { + if (pke_audio_impl_pw.default_sink_id != 0) { + // the default just changed?? + pke_audio_impl_pw.is_needing_output_remapped = true; + } + if (value == NULL || spa_json_str_object_find(value, strlen(value), "name", pke_audio_impl_pw.default_sink_name, sizeof(pke_audio_impl_pw.default_sink_name)) < 0) { + pke_audio_impl_pw.default_sink_name[0] = '\0'; + } + } + } + fprintf(stdout, "\r\nPW_METADATA_CB: data: %p, subject: %d, key: '%s', type: '%s', value: '%s'\n", data, subject, key, type, value); + return 0; +} + +void on_registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { + (void)data; + (void)permissions; + const char *str; + pke_audio_pw_object obj{}; + enum PK_STN_RES res; + + // if (props == NULL) return; // why + if (props == NULL) { + printf("\r\nPW CALLBACK: NULL PROPS: object: id:%u type:%s/%d\n", id, type, version); + return; // why - in what scenario does this happen? + } + + if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { + printf("\r\nPW CALLBACK NODE: object: id:%u type:%s/%d\n", id, type, version); + obj.type = PKE_AUDIO_PW_OBJECT_TYPE_NODE; + } else if (spa_streq(type, PW_TYPE_INTERFACE_Port)) { + obj.type = PKE_AUDIO_PW_OBJECT_TYPE_PORT; + if ((str = spa_dict_lookup(props, PW_KEY_PORT_DIRECTION)) == NULL) { + return; + } + if (spa_streq(str, "in")) { + obj.data.port.direction = PW_DIRECTION_INPUT; + } else if (spa_streq(str, "out")) { + obj.data.port.direction = PW_DIRECTION_OUTPUT; + } else { + return; + } + if ((str = spa_dict_lookup(props, PW_KEY_NODE_ID)) == NULL) { + return; + } + if (res = pk_stn(&obj.data.port.node, str, 0), res != PK_STN_RES_SUCCESS) { + fprintf(stderr, "\r\nPW_CALLBACK: failed to parse port node from str: '%s', PK_STN_RES: %i\n", str, res); + return; + } + if ((str = spa_dict_lookup(props, PW_KEY_PORT_ID)) == NULL) { + return; + } + if (res = pk_stn(&obj.data.port.id, str, 0), res != PK_STN_RES_SUCCESS) { + fprintf(stderr, "\r\nPW_CALLBACK: failed to parse port id from str: '%s', PK_STN_RES: %i\n", str, res); + return; + } + } else if (spa_streq(type, PW_TYPE_INTERFACE_Link)) { + obj.type = PKE_AUDIO_PW_OBJECT_TYPE_LINK; + if ((str = spa_dict_lookup(props, PW_KEY_LINK_OUTPUT_PORT)) == NULL) { + return; + } + if (res = pk_stn(&obj.data.link.output_port, str, 0), res != PK_STN_RES_SUCCESS) { + fprintf(stderr, "\r\nPW_CALLBACK: failed to parse link output port from str: '%s', PK_STN_RES: %i\n", str, res); + return; + } + if ((str = spa_dict_lookup(props, PW_KEY_LINK_INPUT_PORT)) == NULL) { + return; + } + if (res = pk_stn(&obj.data.link.input_port, str, 0), res != PK_STN_RES_SUCCESS) { + fprintf(stderr, "\r\nPW_CALLBACK: failed to parse link input port from str: '%s', PK_STN_RES: %i\n", str, res); + return; + } + } else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata)) { + obj.type = PKE_AUDIO_PW_OBJECT_TYPE_METADATA; + if ((str = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) == NULL) { + return; + } + if (!spa_streq(str, "default")) { + return; + } + pke_audio_impl_pw.metadata = (pw_metadata*)pw_registry_bind(pke_audio_impl_pw.registry, id, PW_TYPE_INTERFACE_Metadata, PW_VERSION_METADATA, 0); + + spa_zero(pke_audio_impl_pw.metadata_listener); + + pw_metadata_add_listener(pke_audio_impl_pw.metadata, &pke_audio_impl_pw.metadata_listener, &metadata_events, NULL); + } else { + return; + } + +#if 0 + int i; + for (i = 0; i < props->n_items; ++i) { + printf("\r\n\t'%s': '%s'", props->items[i].key, props->items[i].value); + } + printf("\r\n"); +#endif + + obj.id = id; + obj.props = pw_properties_new_dict(props); + + printf("\r\nPW CALLBACK: object: id:%u type:%s/%d\n", id, type, version); + + pk_arr_append_t(&pke_audio_impl_pw.pw_objects, obj); + return; +} + +void on_registry_event_global_removal(void *data, uint32_t id) { + (void)data; + uint32_t i; + // printf("\r\nPW CALLBACK: object remove: id:%u ...", id); + if (i = pk_arr_find_first_index(&pke_audio_impl_pw.pw_objects, &id, pw_objects_find_pw_object_by_id), i != 0xFFFFFFFF) + { + if (pke_audio_impl_pw.pw_objects[i].type != PKE_AUDIO_PW_OBJECT_TYPE_LINK) { + pke_audio_impl_pw.is_needing_output_remapped = true; + } + if (pke_audio_impl_pw.pw_objects[i].id == pke_audio_impl_pw.default_sink_id) { + // we just lost our target audio device + pke_audio_impl_pw.default_sink_id = 0; + pke_audio_impl_pw.default_sink_name[0] = '\0'; + } + pk_arr_remove_at(&pke_audio_impl_pw.pw_objects, i); + // printf(" removed.\n"); + } + return; +} diff --git a/src/audio-impl-pw.hpp b/src/audio-impl-pw.hpp new file mode 100644 index 0000000..c371973 --- /dev/null +++ b/src/audio-impl-pw.hpp @@ -0,0 +1,71 @@ +#ifndef PKE_AUDIO_IMPL_PW_HPP +#define PKE_AUDIO_IMPL_PW_HPP + +#include <atomic> + +#include "pk.h" + +// 2025-06-24 spa does not have c++ headers so we have to use c headers. +// They throw a ton of pedantic warnings that spam the build output. +// Might be too large of a swath, but none of this is my code anyway. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#include <pipewire/pipewire.h> +#include <pipewire/extensions/metadata.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/audio/raw.h> +#include <spa/param/param.h> +#include <spa/utils/string.h> +#pragma GCC diagnostic pop + +#define PKE_AUDIO_IMPL_POD_BUFFER_LEN 1024 +enum pke_audio_pw_object_type : uint8_t { + PKE_AUDIO_PW_OBJECT_TYPE_ANY, + PKE_AUDIO_PW_OBJECT_TYPE_NODE, + PKE_AUDIO_PW_OBJECT_TYPE_PORT, + PKE_AUDIO_PW_OBJECT_TYPE_LINK, + PKE_AUDIO_PW_OBJECT_TYPE_METADATA, +}; +struct pke_audio_pw_object { + union { + struct { + enum pw_direction direction; + uint32_t node; + uint32_t id; + } port; + struct { + uint32_t output_port; + uint32_t input_port; + uint32_t dummy; + } link; + } data; + uint32_t id; + enum pke_audio_pw_object_type type; + struct pw_properties *props; +}; +struct pke_audio_implementation_pipewire { + pk_membucket *bkt; + pw_thread_loop *loop; + uint8_t *pod_buffer; + pw_stream *stream; + pw_core *core; + pw_registry *registry; + spa_hook registry_listener; + pw_metadata *metadata; + spa_hook metadata_listener; + pk_arr_t<pke_audio_pw_object> pw_objects; + pk_arr_t<void*> created_objects; + char *default_sink_name; + spa_audio_info format; + uint32_t default_sink_id; + std::atomic_bool is_needing_output_remapped; +}; +extern struct pke_audio_implementation_pipewire pke_audio_impl_pw; +void pke_audio_pw_init(); +void pke_audio_pw_teardown(); +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); +void on_registry_event_global_removal(void *data, uint32_t id); + +#endif /* PKE_AUDIO_IMPL_PW_HPP */ diff --git a/src/audio-types.hpp b/src/audio-types.hpp new file mode 100644 index 0000000..84ab155 --- /dev/null +++ b/src/audio-types.hpp @@ -0,0 +1,36 @@ +#ifndef PKE_AUDIO_TYPES_INTERNAL_HPP +#define PKE_AUDIO_TYPES_INTERNAL_HPP + +#include "asset-manager.hpp" +#include "pk.h" + +#define PKE_AUDIO_MAX_CONCURRENT_COUNT 8 + +TypeSafeInt_constexpr(pke_audio_flags, uint8_t, 0xFF); +TypeSafeInt_constexpr(pke_audio_source, uint8_t, 0x0F); + +const pke_audio_flags pke_audio_flag_none = pke_audio_flags{0x00}; + +const pke_audio_source pke_audio_source_music = pke_audio_source{0x00}; +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_obj { + AssetHandle handle; // key + pke_audio_source source; // key + 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; +}; + +extern struct pke_audio_master pke_audio_mstr; + +#endif /* PKE_AUDIO_TYPES_INTERNAL_HPP */ diff --git a/src/audio.cpp b/src/audio.cpp new file mode 100644 index 0000000..002d1be --- /dev/null +++ b/src/audio.cpp @@ -0,0 +1,32 @@ +#define PKE_AUDIO_IMPL_PIPEWIRE + +#include "audio.hpp" + +#include "audio-impl-pw.hpp" + +struct pke_audio_master pke_audio_mstr{}; + +void pke_audio_init() { + pke_audio_mstr.channel_count = 2; +#ifdef PKE_AUDIO_IMPL_PIPEWIRE + pke_audio_pw_init(); +#endif +} +void pke_audio_teardown() { +#ifdef PKE_AUDIO_IMPL_PIPEWIRE + pke_audio_pw_teardown(); +#endif +} +void pke_audio_tick() { +} + +float pke_audio_get_volume(pke_audio_source source) { + return pke_audio_mstr.source_volumes[static_cast<pke_audio_source_T>(source)]; +} + +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(); diff --git a/src/audio.hpp b/src/audio.hpp new file mode 100644 index 0000000..7ba0f1d --- /dev/null +++ b/src/audio.hpp @@ -0,0 +1,18 @@ +#ifndef PKE_AUDIO_HPP +#define PKE_AUDIO_HPP + +#include "audio-types.hpp" + +#include "asset-manager.hpp" + +void pke_audio_init(); +void pke_audio_teardown(); +void pke_audio_tick(); + +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_stop_all(); + +#endif /* PKE_AUDIO_HPP */ diff --git a/src/game.cpp b/src/game.cpp index 67a9600..25473ed 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,6 +1,7 @@ #include "game.hpp" +#include "audio.hpp" #include "camera.hpp" #include "components.hpp" #include "ecs.hpp" @@ -122,6 +123,7 @@ void Game_Main(PKEWindowProperties windowProps, const char *executablePath) { PkeThreads_Init(); AM_Init(); ECS_Init(); + pke_audio_init(); Physics_Init(); PkeCamera_Init(); pke_level_init(); @@ -310,6 +312,7 @@ GAME_SHUTDOWN: Physics_Teardown(); ECS_Teardown(); DestroyWindow(); + pke_audio_teardown(); AM_DebugPrint(); AM_Teardown(); PkeThreads_Teardown(); |
