#include "sound.h" #include "arena.h" #include "log.h" #include "sys.h" #include "scratch.h" #include "resource.h" #include "asset_cache.h" #include "mp3.h" #include "work.h" struct sound_task_params { struct sound_task_params *next_free; u32 flags; struct asset *asset; u64 path_len; char path_cstr[1024]; }; struct sound_task_params_store { struct sound_task_params *head_free; struct arena arena; struct sys_mutex mutex; }; /* ========================== * * Global state * ========================== */ GLOBAL struct { struct sound_task_params_store params; } G = ZI, DEBUG_ALIAS(G, G_sound); /* ========================== * * Startup * ========================== */ struct sound_startup_receipt sound_startup(struct work_startup_receipt *work_sr, struct asset_cache_startup_receipt *asset_cache_sr, struct resource_startup_receipt *resource_sr) { (UNUSED)work_sr; (UNUSED)asset_cache_sr; (UNUSED)resource_sr; G.params.arena = arena_alloc(GIGABYTE(64)); G.params.mutex = sys_mutex_alloc(); return (struct sound_startup_receipt) { 0 }; } /* ========================== * * Load task param store * ========================== */ INTERNAL struct sound_task_params *sound_task_params_alloc(void) { struct sound_task_params *p = NULL; { struct sys_lock lock = sys_mutex_lock_e(&G.params.mutex); if (G.params.head_free) { p = G.params.head_free; G.params.head_free = p->next_free; } else { p = arena_push_zero(&G.params.arena, struct sound_task_params); } sys_mutex_unlock(&lock); } return p; } INTERNAL void sound_task_params_release(struct sound_task_params *p) { struct sys_lock lock = sys_mutex_lock_e(&G.params.mutex); p->next_free = G.params.head_free; G.params.head_free = p; sys_mutex_unlock(&lock); } /* ========================== * * Load * ========================== */ INTERNAL WORK_TASK_FUNC_DEF(sound_load_asset_task, vparams) { __prof; struct sound_task_params *params = (struct sound_task_params *)vparams; struct temp_arena scratch = scratch_begin_no_conflict(); struct string path = string_from_cstr_len(params->path_cstr, params->path_len); struct asset *asset = params->asset; u32 flags = params->flags; logf_info("Loading sound \"%F\"", FMT_STR(path)); sys_timestamp_t start_ts = sys_timestamp(); b32 success = true; struct string error_msg = STR("Unknown error"); ASSERT(string_ends_with(path, STR(".mp3"))); if (resource_exists(path)) { u64 decode_flags = 0; if (flags & SOUND_FLAG_STEREO) { decode_flags |= MP3_DECODE_FLAG_STEREO; } /* Decode */ struct resource sound_rs = resource_open(path); struct mp3_decode_result decoded = mp3_decode(scratch.arena, sound_rs.bytes, decode_flags); resource_close(sound_rs); if (!decoded.success) { success = false; error_msg = STR("Failed to decode sound file"); goto abort; } /* Store */ struct sound *sound = NULL; i16 *samples = NULL; { struct asset_cache_store store = asset_cache_store_open(); sound = arena_push(store.arena, struct sound); samples = arena_push_array(store.arena, i16, decoded.pcm.count); asset_cache_store_close(&store); } /* Initialize */ *sound = (struct sound) { .flags = flags, .pcm.count = decoded.pcm.count, .pcm.samples = samples }; MEMCPY(sound->pcm.samples, decoded.pcm.samples, decoded.pcm.count * sizeof(*decoded.pcm.samples)); logf_info("Finished loading sound \"%F\" in %F seconds", FMT_STR(path), FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts))); asset_cache_mark_ready(asset, sound); } else { success = false; error_msg = STR("Resource not found"); goto abort; } abort: if (!success) { logf_error("Error loading sound \"%F\": %F", FMT_STR(path), FMT_STR(error_msg)); /* Store */ struct sound *sound = NULL; { struct asset_cache_store store = asset_cache_store_open(); sound = arena_push(store.arena, struct sound); asset_cache_store_close(&store); } *sound = (struct sound) { 0 }; asset_cache_mark_ready(asset, sound); } sound_task_params_release(params); scratch_end(scratch); } struct asset *sound_load_asset(struct string path, u32 flags, b32 help) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); /* Generate and append sound flags to path key */ struct string key = string_format(scratch.arena, STR("%F%F_sound"), FMT_STR(path), FMT_UINT((u64)flags)); u64 hash = asset_cache_hash(key); b32 is_first_touch; struct asset *asset = asset_cache_touch(key, hash, &is_first_touch); if (is_first_touch) { /* Assemble task params */ struct sound_task_params *params = sound_task_params_alloc(); if (path.len > (sizeof(params->path_cstr) - 1)) { sys_panic(string_format(scratch.arena, STR("Sound path \"%F\" too long!"), FMT_STR(path))); } cstr_buff_from_string(BUFFER_FROM_ARRAY(params->path_cstr), path); params->path_len = path.len; params->asset = asset; params->flags = flags; /* Push task */ asset_cache_mark_loading(asset); struct work_handle wh = ZI; if (help) { wh = work_push_task_and_help(&sound_load_asset_task, params, WORK_PRIORITY_NORMAL); } else { wh = work_push_task(&sound_load_asset_task, params, WORK_PRIORITY_NORMAL); } asset_cache_set_work(asset, &wh); } scratch_end(scratch); return asset; } struct sound *sound_load_async(struct string path, u32 flags) { __prof; struct asset *asset = sound_load_asset(path, flags, false); struct sound *sound = (struct sound *)asset_cache_get_store_data(asset); return sound; } struct sound *sound_load(struct string path, u32 flags) { __prof; struct asset *asset = sound_load_asset(path, flags, true); asset_cache_wait(asset); struct sound *sound = (struct sound *)asset_cache_get_store_data(asset); return sound; }