diff --git a/TODO b/TODO index a529007..ca72ba7 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,6 @@ - Audio - Player volume control - - Spatial audio - Optimize - Spam simd - Concurrent jobs for players? @@ -20,15 +19,11 @@ - Setting audio source to a format which differs from audio output format in both channels and bit_width at the same time will produce pure loud noise. - 24-Bit audio conversion doesn't really work -- Juice - - draw_line - - vx_length - - Occlude quads out of view - - General bugs & issues - Release freeze in run_tests - Needs testing: - Audio format channel conversions - sample rate downsampling - - non stereo or mono audio \ No newline at end of file + - non stereo or mono audio + - Audio spacialization on anything that's not stereo \ No newline at end of file diff --git a/build.c b/build.c index 1c5825e..537bcb7 100644 --- a/build.c +++ b/build.c @@ -3,6 +3,8 @@ /// // Build config stuff +#define OOGABOOGA_DEV 1 + #define INITIAL_PROGRAM_MEMORY_SIZE MB(5) typedef struct Context_Extra { @@ -26,13 +28,13 @@ typedef struct Context_Extra { // // This is a minimal starting point for new projects. Copy & rename to get started -#include "oogabooga/examples/minimal_game_loop.c" +// #include "oogabooga/examples/minimal_game_loop.c" // #include "oogabooga/examples/text_rendering.c" // #include "oogabooga/examples/custom_logger.c" // #include "oogabooga/examples/renderer_stress_test.c" // #include "oogabooga/examples/tile_game.c" -// #include "oogabooga/examples/audio_test.c" +#include "oogabooga/examples/audio_test.c" // This is where you swap in your own project! // #include "entry_yourepicgamename.c" diff --git a/changelog.txt b/changelog.txt index df40609..fb93210 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,23 @@ +## v0.01.001 - Spacial audio, + - Audio + - Implemented spacial audio playback + Simply set player->position (it's in ndc space (-1 to 1), see audio_test.c) + Save some computation with player->disable_spacialization = true + - Added position overloads for play_one_clip + play_one_audio_clip_source_at_position(source, pos) + play_one_audio_clip_at_position(path, pos) + - Renderer + - Added draw_line(p0, p1, width, color) + - Implemented culling of quads out of view + + - Misc + - Added some useful Vector procedures: + vx_length() + vx_normalize() + vx_average() + vx_dot() + vx_abs() + vx_cross() ## v0.01.000 - AUDIO! - Added audio sources diff --git a/oogabooga/audio.c b/oogabooga/audio.c index 44b1404..407bf15 100644 --- a/oogabooga/audio.c +++ b/oogabooga/audio.c @@ -12,6 +12,8 @@ void play_one_audio_clip_source(Audio_Source source); void play_one_audio_clip(string path); + void play_one_audio_clip_source_at_position(Audio_Source source, Vector3 pos); + void play_one_audio_clip_at_position(string path, Vector3 pos); Playing audio (with players): @@ -25,6 +27,7 @@ float64 audio_player_get_current_progression_factor(Audio_Player *p); void audio_player_set_source(Audio_Player *p, Audio_Source src, bool retain_progression_factor); void audio_player_clear_source(Audio_Player *p); + void audio_player_clear_source(Audio_Player *p); void audio_player_set_looping(Audio_Player *p, bool looping); */ @@ -1154,10 +1157,14 @@ typedef struct Audio_Player { u64 fade_frames; u64 fade_frames_total; bool release_when_done; - // I think we only need to sync when audio thread samples the source, which should be // very quick and low contention, hence a spinlock. Spinlock sample_lock; + + // These can be set safely + Vector3 position; // ndc space -1 to 1 + bool disable_spacialization; + } Audio_Player; #define AUDIO_PLAYERS_PER_BLOCK 128 typedef struct Audio_Player_Block { @@ -1325,14 +1332,20 @@ Hash_Table just_audio_clips; bool just_audio_clips_initted = false; void -play_one_audio_clip_source(Audio_Source source) { +play_one_audio_clip_source_at_position(Audio_Source source, Vector3 pos) { Audio_Player *p = audio_player_get_one(); audio_player_set_source(p, source, false); audio_player_set_state(p, AUDIO_PLAYER_STATE_PLAYING); + p->position = pos; p->release_when_done = true; } + +void inline +play_one_audio_clip_source(Audio_Source source) { + play_one_audio_clip_source_at_position(source, v3(0, 0, 0)); +} void -play_one_audio_clip(string path) { +play_one_audio_clip_at_position(string path, Vector3 pos) { if (!just_audio_clips_initted) { just_audio_clips_initted = true; just_audio_clips = make_hash_table(string, Audio_Source, get_heap_allocator()); @@ -1340,7 +1353,7 @@ play_one_audio_clip(string path) { Audio_Source *src_ptr = hash_table_find(&just_audio_clips, path); if (src_ptr) { - play_one_audio_clip_source(*src_ptr); + play_one_audio_clip_source_at_position(*src_ptr, pos); } else { Audio_Source new_src; bool ok = audio_open_source_load(&new_src, path, get_heap_allocator()); @@ -1349,8 +1362,13 @@ play_one_audio_clip(string path) { return; } hash_table_add(&just_audio_clips, path, new_src); - play_one_audio_clip_source(new_src); + play_one_audio_clip_source_at_position(new_src, pos); } + +} +void inline +play_one_audio_clip(string path) { + play_one_audio_clip_at_position(path, v3(0, 0, 0)); } void @@ -1406,6 +1424,131 @@ audio_apply_fade_out(void *frames, u64 number_of_frames, Audio_Format format, } } +void spacialize_audio_mono(void* frames, Audio_Format format, u64 number_of_frames, Vector3 pos) { + // No idea if this actually gives the perception of audio being positioned. + // I also don't have a mono audio device to test it. + float* audio_data = (float*)frames; + + float distance = sqrtf(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z); + float attenuation = 1.0f / (1.0f + distance); + + float alpha = 0.1f; + float prev_sample = 0.0f; + + u64 comp_size = get_audio_bit_width_byte_size(format.bit_width); + u64 frame_size = comp_size * format.channels; + + for (u64 i = 0; i < number_of_frames; ++i) { + float sample = audio_data[i]; + convert_one_component( + &sample, + AUDIO_BITS_32, + (u8*)frames+i*frame_size, + format.bit_width + ); + + sample *= attenuation; + + sample = alpha * sample + (1.0f - alpha) * prev_sample; + prev_sample = sample; + + convert_one_component( + (u8*)frames+i*frame_size, + format.bit_width, + &sample, + AUDIO_BITS_32 + ); + } +} +void spacialize_audio(void* frames, Audio_Format format, u64 number_of_frames, Vector3 pos) { + + if (format.channels == 1) { + spacialize_audio_mono(frames, format, number_of_frames, pos); + } + + float distance = sqrtf(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z); + float attenuation = 1.0f / (1.0f + distance); + + float left_right_pan = (pos.x + 1.0f) * 0.5f; + float up_down_pan = (pos.y + 1.0f) * 0.5f; + float front_back_pan = (pos.z + 1.0f) * 0.5f; + + u64 comp_size = get_audio_bit_width_byte_size(format.bit_width); + u64 frame_size = comp_size * format.channels; + + float high_pass_coeff = 0.8f + 0.2f * up_down_pan; + float low_pass_coeff = 1.0f - high_pass_coeff; + + // Apply gains to each frame + for (u64 i = 0; i < number_of_frames; ++i) { + for (u64 c = 0; c < format.channels; ++c) { + // Convert whatever to float -1 to 1 + float sample; + convert_one_component( + &sample, + AUDIO_BITS_32, + (u8*)frames+i*frame_size+c*comp_size, + format.bit_width + ); + float gain = 1.0f / format.channels; + + if (format.channels == 2) { + + // time delay and phase shift for vertical position + float time_delay = (up_down_pan - 0.5f) * 0.0005f; // 0.5ms delay range + float phase_shift = (up_down_pan - 0.5f) * 0.5f; // 0.5 radians phase shift range + + // Stereo + if (c == 0) { + gain = (1.0f - left_right_pan) * attenuation; + sample = sample * cos(phase_shift) - sample * sin(phase_shift); + } else if (c == 1) { + gain = left_right_pan * attenuation; + sample = sample * cos(phase_shift) + sample * sin(phase_shift); + } + } else if (format.channels == 4) { + // Quadraphonic sound (left-right, front-back) + if (c == 0) { + gain = (1.0f - left_right_pan) * (1.0f - front_back_pan) * attenuation; + } else if (c == 1) { + gain = left_right_pan * (1.0f - front_back_pan) * attenuation; + } else if (c == 2) { + gain = (1.0f - left_right_pan) * front_back_pan * attenuation; + } else if (c == 3) { + gain = left_right_pan * front_back_pan * attenuation; + } + } else if (format.channels == 6) { + // 5.1 surround sound (left, right, center, LFE, rear left, rear right) + if (c == 0) { + gain = (1.0f - left_right_pan) * attenuation; + } else if (c == 1) { + gain = left_right_pan * attenuation; + } else if (c == 2) { + gain = (1.0f - front_back_pan) * attenuation; + } else if (c == 3) { + gain = 0.5f * attenuation; // LFE (subwoofer) channel + } else if (c == 4) { + gain = (1.0f - left_right_pan) * front_back_pan * attenuation; + } else if (c == 5) { + gain = left_right_pan * front_back_pan * attenuation; + } + } else { + // No idea what device this is, just distribute equally + gain = attenuation / format.channels; + } + + sample *= gain; + // Convert back to whatever + convert_one_component( + (u8*)frames+i*frame_size+c*comp_size, + format.bit_width, + &sample, + AUDIO_BITS_32 + ); + } + } +} + // This is supposed to be called by OS layer audio thread whenever it wants more audio samples void do_program_audio_sample(u64 number_of_output_frames, Audio_Format out_format, @@ -1580,6 +1723,10 @@ do_program_audio_sample(u64 number_of_output_frames, Audio_Format out_format, ); assert(converted == number_of_output_frames); } + + if (!p->disable_spacialization) { + spacialize_audio(mix_buffer, out_format, number_of_output_frames, p->position); + } mix_frames(output, mix_buffer, number_of_output_frames, out_format); diff --git a/oogabooga/drawing.c b/oogabooga/drawing.c index 0939845..071fbd7 100644 --- a/oogabooga/drawing.c +++ b/oogabooga/drawing.c @@ -127,12 +127,23 @@ void pop_z_layer() { draw_frame.z_count -= 1; } +Draw_Quad _nil_quad = {0}; Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) { quad.bottom_left = m4_transform(world_to_clip, v4(v2_expand(quad.bottom_left), 0, 1)).xy; quad.top_left = m4_transform(world_to_clip, v4(v2_expand(quad.top_left), 0, 1)).xy; quad.top_right = m4_transform(world_to_clip, v4(v2_expand(quad.top_right), 0, 1)).xy; quad.bottom_right = m4_transform(world_to_clip, v4(v2_expand(quad.bottom_right), 0, 1)).xy; + bool should_cull = + (quad.bottom_left.x < -1 || quad.bottom_left.x > 1 || quad.bottom_left.y < -1 || quad.bottom_left.y > 1) + && (quad.top_left.x < -1 || quad.top_left.x > 1 || quad.top_left.y < -1 || quad.top_left.y > 1) + && (quad.top_right.x < -1 || quad.top_right.x > 1 || quad.top_right.y < -1 || quad.top_right.y > 1) + && (quad.bottom_right.x < -1 || quad.bottom_right.x > 1 || quad.bottom_right.y < -1 || quad.bottom_right.y > 1); + + if (should_cull) { + return &_nil_quad; + } + quad.image_min_filter = GFX_FILTER_MODE_NEAREST; quad.image_mag_filter = GFX_FILTER_MODE_NEAREST; @@ -272,6 +283,16 @@ Gfx_Text_Metrics draw_text_and_measure(Gfx_Font *font, string text, u32 raster_h return measure_text(font, text, raster_height, scale); } +void draw_line(Vector2 p0, Vector2 p1, float line_width, Vector4 color) { + Vector2 dir = v2(p1.x - p0.x, p1.y - p0.y); + float length = sqrt(dir.x * dir.x + dir.y * dir.y); + float r = atan2(-dir.y, dir.x); + Matrix4 line_xform = m4_scalar(1); + line_xform = m4_translate(line_xform, v3(p0.x, p0.y, 0)); + line_xform = m4_rotate_z(line_xform, r); + line_xform = m4_translate(line_xform, v3(0, -line_width/2, 0)); + draw_rect_xform(line_xform, v2(length, line_width), color); +} #define COLOR_RED ((Vector4){1.0, 0.0, 0.0, 1.0}) #define COLOR_GREEN ((Vector4){0.0, 1.0, 0.0, 1.0}) diff --git a/oogabooga/examples/audio_test.c b/oogabooga/examples/audio_test.c index 8566706..639371f 100644 --- a/oogabooga/examples/audio_test.c +++ b/oogabooga/examples/audio_test.c @@ -57,8 +57,13 @@ int entry(int argc, char **argv) { draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10); if (is_key_just_pressed(MOUSE_BUTTON_RIGHT)) { + float mx = input_frame.mouse_x; + float my = input_frame.mouse_y; // Easy mode (when you don't care and just want to play a clip) - play_one_audio_clip(STR("oogabooga/examples/block.wav")); + Vector3 p = v3(mx/(f32)window.width*2.0-1, my/(f32)window.height*2.0-1, 0); + log("%f, %f", p.x, p.y); + play_one_audio_clip_at_position(STR("oogabooga/examples/block.wav"), p); + // Or just play_one_audio_clip if you don't care about spacialization } diff --git a/oogabooga/examples/minimal_game_loop.c b/oogabooga/examples/minimal_game_loop.c index fa22949..e530de1 100644 --- a/oogabooga/examples/minimal_game_loop.c +++ b/oogabooga/examples/minimal_game_loop.c @@ -10,7 +10,7 @@ int entry(int argc, char **argv) { while (!window.should_close) { reset_temporary_storage(); - + float64 now = os_get_current_time_in_seconds(); Matrix4 rect_xform = m4_scalar(1.0); rect_xform = m4_rotate_z(rect_xform, (f32)now); @@ -19,6 +19,12 @@ int entry(int argc, char **argv) { draw_rect(v2(sin(now), -.8), v2(.5, .25), COLOR_RED); + float aspect = (f32)window.width/(f32)window.height; + float mx = (input_frame.mouse_x/(f32)window.width * 2.0 - 1.0)*aspect; + float my = input_frame.mouse_y/(f32)window.height * 2.0 - 1.0; + + draw_line(v2(-.75, -.75), v2(mx, my), 0.005, COLOR_WHITE); + os_update(); gfx_update(); } diff --git a/oogabooga/linmath.c b/oogabooga/linmath.c index e67eeb6..449bd47 100644 --- a/oogabooga/linmath.c +++ b/oogabooga/linmath.c @@ -127,22 +127,81 @@ inline Vector4 v4_divf(LMATH_ALIGN Vector4 a, float32 s) { return v4_div(a, v4(s, s, s, s)); } - +// #Simd +inline float32 v2_length(LMATH_ALIGN Vector2 a) { + return sqrt(a.x*a.x + a.y*a.y); +} inline Vector2 v2_normalize(LMATH_ALIGN Vector2 a) { - float32 length = sqrt(a.x * a.x + a.y * a.y); + float32 length = v2_length(a); if (length == 0) { return (Vector2){0, 0}; } return v2_divf(a, length); } - -inline float v2_dot_product(LMATH_ALIGN Vector2 a, LMATH_ALIGN Vector2 b) { +inline float32 v2_average(LMATH_ALIGN Vector2 a) { + return (a.x+a.y)/2.0; +} +inline Vector2 v2_abs(LMATH_ALIGN Vector2 a) { + return v2(fabsf(a.x), fabsf(a.y)); +} +inline float32 v2_cross(LMATH_ALIGN Vector2 a, LMATH_ALIGN Vector2 b) { + return (a.x * b.y) - (a.y * b.x); +} +inline float v2_dot(LMATH_ALIGN Vector2 a, LMATH_ALIGN Vector2 b) { return simd_dot_product_float32_64((float*)&a, (float*)&b); } -inline float v3_dot_product(LMATH_ALIGN Vector3 a, LMATH_ALIGN Vector3 b) { + +inline float32 v3_length(LMATH_ALIGN Vector3 a) { + return sqrt(a.x * a.x + a.y * a.y + a.z * a.z); +} + +inline Vector3 v3_normalize(LMATH_ALIGN Vector3 a) { + float32 length = v3_length(a); + if (length == 0) { + return (Vector3){0, 0, 0}; + } + return v3_divf(a, length); +} + +inline float32 v3_average(LMATH_ALIGN Vector3 a) { + return (a.x + a.y + a.z) / 3.0; +} + +inline Vector3 v3_abs(LMATH_ALIGN Vector3 a) { + return v3(fabsf(a.x), fabsf(a.y), fabsf(a.z)); +} + + +inline Vector3 v3_cross(LMATH_ALIGN Vector3 a, LMATH_ALIGN Vector3 b) { + return (Vector3){ + (a.y * b.z) - (a.z * b.y), + (a.z * b.x) - (a.x * b.z), + (a.x * b.y) - (a.y * b.x) + }; +} +inline float v3_dot(LMATH_ALIGN Vector3 a, LMATH_ALIGN Vector3 b) { return simd_dot_product_float32_96((float*)&a, (float*)&b); } -inline float v4_dot_product(LMATH_ALIGN Vector4 a, LMATH_ALIGN Vector4 b) { +inline float32 v4_length(LMATH_ALIGN Vector4 a) { + return sqrt(a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w); +} + +inline Vector4 v4_normalize(LMATH_ALIGN Vector4 a) { + float32 length = v4_length(a); + if (length == 0) { + return (Vector4){0, 0, 0, 0}; + } + return v4_divf(a, length); +} + +inline float32 v4_average(LMATH_ALIGN Vector4 a) { + return (a.x + a.y + a.z + a.w) / 4.0; +} + +inline Vector4 v4_abs(LMATH_ALIGN Vector4 a) { + return v4(fabsf(a.x), fabsf(a.y), fabsf(a.z), fabsf(a.w)); +} +inline float v4_dot(LMATH_ALIGN Vector4 a, LMATH_ALIGN Vector4 b) { return simd_dot_product_float32_128_aligned((float*)&a, (float*)&b); } diff --git a/oogabooga/oogabooga.c b/oogabooga/oogabooga.c index a0a415c..566d7f5 100644 --- a/oogabooga/oogabooga.c +++ b/oogabooga/oogabooga.c @@ -107,7 +107,7 @@ #define OGB_VERSION_MAJOR 0 #define OGB_VERSION_MINOR 1 -#define OGB_VERSION_PATCH 0 +#define OGB_VERSION_PATCH 1 #define OGB_VERSION (OGB_VERSION_MAJOR*1000000+OGB_VERSION_MINOR*1000+OGB_VERSION_PATCH) diff --git a/oogabooga/tests.c b/oogabooga/tests.c index aaa8809..b546d47 100644 --- a/oogabooga/tests.c +++ b/oogabooga/tests.c @@ -1011,13 +1011,13 @@ void test_linmath() { assert(mixed_v4_result.x == 1.0f && mixed_v4_result.y == 2.0f && mixed_v4_result.z == 3.0f && mixed_v4_result.w == 4.0f, "Mixed Vector4 scalar multiplication failed"); - float v2_dot = v2_dot_product(v2(2, 7), v2(3, 2)); - float v3_dot = v3_dot_product(v3(2, 7, 2), v3(3, 2, 9)); - float v4_dot = v4_dot_product(v4(2, 7, 6, 1), v4(3, 2, 1, 4)); + float v2_dot_product = v2_dot(v2(2, 7), v2(3, 2)); + float v3_dot_product = v3_dot(v3(2, 7, 2), v3(3, 2, 9)); + float v4_dot_product = v4_dot(v4(2, 7, 6, 1), v4(3, 2, 1, 4)); - assert(floats_roughly_match(v2_dot, 20), "Failed: v2_dot_product"); - assert(floats_roughly_match(v3_dot, 38), "Failed: v3_dot_product"); - assert(floats_roughly_match(v4_dot, 30), "Failed: v4_dot_product"); + assert(floats_roughly_match(v2_dot_product, 20), "Failed: v2_dot"); + assert(floats_roughly_match(v3_dot_product, 38), "Failed: v3_dot"); + assert(floats_roughly_match(v4_dot_product, 30), "Failed: v4_dot"); } void test_hash_table() { Hash_Table table = make_hash_table(string, int, get_heap_allocator()); diff --git a/oogabooga/third_party/stb_vorbis.c b/oogabooga/third_party/stb_vorbis.c index 9d568a0..13ca4d4 100644 --- a/oogabooga/third_party/stb_vorbis.c +++ b/oogabooga/third_party/stb_vorbis.c @@ -12,7 +12,7 @@ - All FILE * stdio procedures are replaced with the equivalent for the oogabooga file API. - + - all draw_line -> stb_vorbis_draw_line */ @@ -2125,7 +2125,7 @@ static float inverse_db_table[256] = int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB #endif -static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) +static __forceinline void stb_vorbis_draw_line(float *output, int x0, int y0, int x1, int y1, int n) { int dy = y1 - y0; int adx = x1 - x0; @@ -3186,13 +3186,13 @@ static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *f int hy = finalY[j] * g->floor1_multiplier; int hx = g->Xlist[j]; if (lx != hx) - draw_line(target, lx,ly, hx,hy, n2); + stb_vorbis_draw_line(target, lx,ly, hx,hy, n2); CHECK(f); lx = hx, ly = hy; } } if (lx < n2) { - // optimization of: draw_line(target, lx,ly, n,ly, n2); + // optimization of: stb_vorbis_draw_line(target, lx,ly, n,ly, n2); for (j=lx; j < n2; ++j) LINE_OP(target[j], inverse_db_table[ly]); CHECK(f);