1
Fork 0
This repository has been archived on 2024-04-09. You can view files and clone it, but cannot push or open issues/pull-requests.
podcastoscope/src/podcastoscope.cpp

1725 lines
50 KiB
C++

/*
** Podcast Video Renderer
*/
#include "base/base_inc.h"
#include "os/os_inc.h"
#include "gfx/gfx_essential.h"
#include "gfx/opengl_sys/opengl_sys_inc.h"
#include "gfx/gfx_essential.h"
#include "art/color.h"
#include "wave/riff_format.h"
#include "wave/wave_format.h"
#include "wave/wave_parser.h"
#include "audio_gen.h"
#include "temp_wave.h"
#include "podcastoscope.h"
#include "base/base_inc.cpp"
#include "os/os_inc.cpp"
#include "gfx/win32/win32_gfx_essential.h"
#include "gfx/win32/win32_gfx_essential.cpp"
#include "gfx/opengl_sys/opengl_sys_inc.cpp"
#include "art/color.cpp"
#include "wave/wave_parser.cpp"
#include "audio_gen.cpp"
#include "temp_wave.cpp"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"
////////////////////////////////
// TODO(allen):
// [ ] Colors for each speaker
// [ ] Master track timeline
// [ ] Push towards center during active moments
// [ ] More interesting layout with 3+ speakers
// [ ] Backdrop shapes for avatars
// [ ] Direct video rendering path
////////////////////////////////
// NOTE(allen): Base Worthy Stuff
#define CeilIntDiv(n,d) (((n) + (d) - 1)/(d))
#define Swap(T,a,b) do{ T t = a; a = b; b = t; }while(0)
////////////////////////////////
// NOTE(allen): OpenGL Definitions
#define GL_SRGB 0x8C40
#define GL_SRGB8 0x8C41
#define GL_SRGB_ALPHA 0x8C42
#define GL_SRGB8_ALPHA8 0x8C43
#define GL_FRAMEBUFFER_SRGB 0x8DB9
////////////////////////////////
// NOTE(allen): Configuration Stuff
#define PLAY_TEST_TRACK 0
#define TEST_TRACK "C:\\mr4th\\podcast\\test__anti_allen\\test__combined.wav"
#define AVATAR_DISPLAY_SIDE 280
#define AVATAR_DISPLAY_RADIUS 140.f
#define AVATAR_DISPLAY_RADIUS_D 140.0
#define AVATAR_RING1_RATIO 0.85f
#define AVATAR_RING2_RATIO 0.92f
#define AVATAR_QUITE_RATIO 0.9f
#define WIGGLE_BASE_MAG 4.5f
#define WIGGLE_BASE_TEMPORAL_FREQ 5.f
#define WIGGLE_SPATIAL_MAG 3.f
#define WIGGLE_SPATIAL_FREQ 40.f
#define WIGGLE_TEMPORAL_FREQ 2.f
#define BARS_MIN_HEIGHT 20.f
#define BARS_MAX_HEIGHT 100.f
#define ENABLE_SRGB_CONVERSION 1
#define ENABLE_MULTISAMPLE 0
#define CONSIDERED_SILENCE_DB -40.f
#define CONSIDERED_MINIMUM_DB -33.f
#define CONSIDERED_FULL_DB -15.f
////////////////////////////////
// NOTE(allen): OpenGL Renderer State
#define GLSL_UNPACK_COLOR(u32c, c0, c1, c2, c3) \
"float " c0 " = ((" u32c " >> 0)&0xFFu)/255.0;\n" \
"float " c1 " = ((" u32c " >> 8)&0xFFu)/255.0;\n" \
"float " c2 " = ((" u32c " >> 16)&0xFFu)/255.0;\n" \
"float " c3 " = ((" u32c " >> 24)&0xFFu)/255.0;\n"
static char scratch_glsl_vert_vshader[] =
"#version 330\n"
"uniform vec2 u_view_xform;\n"
"layout (location = 0) in vec2 v_p;\n"
"layout (location = 1) in vec2 v_uv;\n"
"layout (location = 2) in float v_s;\n"
"layout (location = 3) in float v_v;\n"
"layout (location = 4) in float v_over;\n"
"out vec2 f_uv;\n"
"out float f_s;\n"
"out float f_v;\n"
"out float f_over;\n"
"void main(){\n"
// position
"vec2 norm_pos = v_p*u_view_xform + vec2(-1.0, -1.0);\n"
// fill outputs
"gl_Position = vec4(norm_pos, 0.0, 1.0);\n"
"f_uv = v_uv;\n"
"f_s = v_s;\n"
"f_v = v_v;\n"
"f_over = v_over;\n"
"}\n"
;
static char scratch_glsl_vert_fshader[] =
"#version 330\n"
"uniform sampler2D u_tex;\n"
"in vec2 f_uv;\n"
"in float f_s;\n"
"in float f_v;\n"
"in float f_over;\n"
"out vec4 out_color;\n"
"vec3 rgb_from_hsv(vec3 hsv){\n"
" vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);\n"
" vec3 p = abs(fract(hsv.xxx + K.xyz)*6.0 - K.www);\n"
" return(hsv.z*mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsv.y));\n"
"}\n"
"vec3 hsv_from_rgb(vec3 c){\n"
" vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);\n"
" vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n"
" vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n"
" float d = q.x - min(q.w, q.y);\n"
" float e = 1.0e-10;\n"
" return(vec3(abs(q.z + (q.w - q.y)/(6.0*d + e)), d/(q.x + e), q.x));\n"
"}\n"
"void main(){\n"
"vec3 tex_c = texture(u_tex, f_uv).rgb;\n"
"vec3 hsv = hsv_from_rgb(tex_c);\n"
"float s_space = pow(f_over, 6);\n"
"float s = f_s*(hsv.y*(1 - s_space) + 0.00*s_space);\n"
"float v_space = pow(f_over, 2);\n"
"float v = f_v*(hsv.z*(1 - v_space) + 0.05*v_space);\n"
"vec3 hsv_mod = vec3(hsv.x, s, v);\n"
"vec3 rgb = rgb_from_hsv(hsv_mod);\n"
"float a_space = pow(f_over, 2);\n"
"float a = 1*(1 - a_space) + 0*a_space;\n"
"out_color = vec4(rgb, a);\n"
"}\n"
;
OpenGL_Helper_Shader vert_vshader = {};
OpenGL_Helper_Shader vert_fshader = {};
OpenGL_Helper_Shader vert_program = {};
GLint vert_u_view_xform = -1;
GLint vert_u_tex = -1;
static char scratch_glsl_rect_vshader[] =
"#version 330\n"
"uniform vec2 u_view_xform;\n"
"layout (location = 0) in vec2 v_p;\n"
"layout (location = 1) in uint v_color;\n"
"layout (location = 2) in vec4 v_rect;\n"
"out vec4 f_color;\n"
"out vec4 f_rect;\n"
"void main(){\n"
// position
"vec2 norm_pos = v_p*u_view_xform + vec2(-1.0, -1.0);\n"
// color
GLSL_UNPACK_COLOR("v_color", "r", "g", "b", "a")
// fill outputs
"gl_Position = vec4(norm_pos, 0.0, 1.0);\n"
"f_color = vec4(r, g, b, a);\n"
"f_rect = v_rect;\n"
"}\n"
;
static char scratch_glsl_rect_fshader[] =
"#version 330\n"
"uniform sampler2D u_tex;\n"
"in vec4 gl_FragCoord;\n"
"in vec4 f_color;\n"
"in vec4 f_rect;\n"
"out vec4 out_color;\n"
"void main(){\n"
"float frag_min_x = gl_FragCoord.x - 0.5;\n"
"float frag_max_x = gl_FragCoord.x + 0.5;\n"
"float frag_min_y = gl_FragCoord.y - 0.5;\n"
"float frag_max_y = gl_FragCoord.y + 0.5;\n"
"float int_min_x = max(f_rect.x, frag_min_x);\n"
"float int_max_x = min(f_rect.z, frag_max_x);\n"
"float int_min_y = max(f_rect.y, frag_min_y);\n"
"float int_max_y = min(f_rect.w, frag_max_y);\n"
"float a = (int_max_x - int_min_x)*(int_max_y - int_min_y);\n"
"float r_lin = pow(f_color.r, 2.2);\n"
"float g_lin = pow(f_color.g, 2.2);\n"
"float b_lin = pow(f_color.b, 2.2);\n"
"out_color = vec4(r_lin, g_lin, b_lin, f_color.a*a);\n"
"}\n"
;
OpenGL_Helper_Shader rect_vshader = {};
OpenGL_Helper_Shader rect_fshader = {};
OpenGL_Helper_Shader rect_program = {};
GLint rect_u_view_xform = -1;
GLuint srgb_framebuffer = 0;
GLuint srgb_canvas_texture = 0;
GLuint vertex_buffer = 0;
GLuint fallback_texture = 0;
GLuint avatar_texture = 0;
function void
setup_opengl_state(void){
M_ArenaTemp scratch = m_get_scratch(0, 0);
opengl_sys_begin_nonrender_section();
// setup GPU program "vert"
{
vert_vshader =
opengl_helper_make_shader(scratch.arena,
scratch_glsl_vert_vshader,
GL_VERTEX_SHADER);
if (vert_vshader.log.size > 0){
gfx_message_box(str8_lit("error"), vert_vshader.log);
os_exit_process(1);
}
}
{
vert_fshader =
opengl_helper_make_shader(scratch.arena,
scratch_glsl_vert_fshader,
GL_FRAGMENT_SHADER);
if (vert_fshader.log.size > 0){
gfx_message_box(str8_lit("error"), vert_fshader.log);
os_exit_process(1);
}
}
{
GLuint shaders[2];
shaders[0] = vert_vshader.handle;
shaders[1] = vert_fshader.handle;
vert_program = opengl_helper_make_program(scratch.arena, shaders, 2);
if (vert_program.log.size > 0){
gfx_message_box(str8_lit("error"), vert_program.log);
os_exit_process(1);
}
}
// setup GPU program "rect"
{
rect_vshader =
opengl_helper_make_shader(scratch.arena,
scratch_glsl_rect_vshader,
GL_VERTEX_SHADER);
if (rect_vshader.log.size > 0){
gfx_message_box(str8_lit("error"), rect_vshader.log);
os_exit_process(1);
}
}
{
rect_fshader =
opengl_helper_make_shader(scratch.arena,
scratch_glsl_rect_fshader,
GL_FRAGMENT_SHADER);
if (rect_fshader.log.size > 0){
gfx_message_box(str8_lit("error"), rect_fshader.log);
os_exit_process(1);
}
}
{
GLuint shaders[2];
shaders[0] = rect_vshader.handle;
shaders[1] = rect_fshader.handle;
rect_program = opengl_helper_make_program(scratch.arena, shaders, 2);
if (rect_program.log.size > 0){
gfx_message_box(str8_lit("error"), rect_program.log);
os_exit_process(1);
}
}
// extract uniform locations
{
vert_u_view_xform =
glGetUniformLocation(vert_program.handle, "u_view_xform");
vert_u_tex =
glGetUniformLocation(vert_program.handle, "u_tex");
if (glGetError() != 0){
gfx_message_box(str8_lit("error"), str8_lit("failed to locate uniforms"));
os_exit_process(1);
}
}
{
rect_u_view_xform =
glGetUniformLocation(rect_program.handle, "u_view_xform");
if (glGetError() != 0){
gfx_message_box(str8_lit("error"), str8_lit("failed to locate uniforms"));
os_exit_process(1);
}
}
// setup vertex array object
{
GLuint vao = 0;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
if (glGetError() != 0 || vao == 0){
gfx_message_box(str8_lit("error"),
str8_lit("failed to setup vertex array object"));
os_exit_process(1);
}
}
// setup buffers
{
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
if (glGetError() != 0 || vertex_buffer == 0){
gfx_message_box(str8_lit("error"), str8_lit("failed to vertex buffer"));
os_exit_process(1);
}
}
// setup blend mode
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
if (glGetError() != 0){
gfx_message_box(str8_lit("error"), str8_lit("failed to setup blending"));
os_exit_process(1);
}
}
// set texture pack & unpack alignment to 1
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (glGetError() != 0){
gfx_message_box(str8_lit("error"), str8_lit("failed to set pixel unpack"));
os_exit_process(1);
}
glPixelStorei(GL_PACK_ALIGNMENT, 1);
if (glGetError() != 0){
gfx_message_box(str8_lit("error"), str8_lit("failed to set pixel pack"));
os_exit_process(1);
}
}
// setup a fallback texture
{
U8 *texture_data = push_array_zero(scratch.arena, U8, 16*3);
for (U64 i = 0; i < 16*3; i += 1){
texture_data[i] = 0xFF;
}
glGenTextures(1, &fallback_texture);
glBindTexture(GL_TEXTURE_2D, fallback_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 4, 4, 0,
GL_RGB, GL_UNSIGNED_BYTE, texture_data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (glGetError() != 0 || fallback_texture == 0){
gfx_message_box(str8_lit("error"), str8_lit("failed to setup texture"));
os_exit_process(1);
}
}
// setup an srgb-texture
{
glGenTextures(1, &srgb_canvas_texture);
#if ENABLE_MULTISAMPLE
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, srgb_canvas_texture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 8, GL_SRGB8,
2048, 2048, true);
#else
glBindTexture(GL_TEXTURE_2D, srgb_canvas_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8,
2048, 2048, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
#endif
if (glGetError() != 0 || srgb_canvas_texture == 0){
gfx_message_box(str8_lit("error"),
str8_lit("failed to setup srgb canvas texture"));
os_exit_process(1);
}
}
// setup a frame buffer
{
glGenFramebuffers(1, &srgb_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, srgb_framebuffer);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
srgb_canvas_texture, 0);
GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (glGetError() != 0 || srgb_framebuffer == 0){
gfx_message_box(str8_lit("error"),
str8_lit("failed to setup srgb framebuffer"));
os_exit_process(1);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
// enable multi-sample
#if ENABLE_MULTISAMPLE
{
glEnable(GL_MULTISAMPLE);
}
#endif
// enable convertion linear -> SRGB in rendering
#if ENABLE_SRGB_CONVERSION
{
glEnable(GL_FRAMEBUFFER_SRGB);
}
#endif
opengl_sys_end_nonrender_section();
m_release_scratch(scratch);
}
function GLuint
avatar_gpu_texture_from_file_path(String8 full_path){
GLuint result = 0;
M_ArenaTemp scratch = m_get_scratch(0, 0);
String8 full_path_copy = str8_push_copy(scratch.arena, full_path);
char *full_path_cstr = (char*)full_path_copy.str;
//- load raw texture data
int w_raw = 0, h_raw = 0, comp = 0;
U8 *avatar_buf_raw = stbi_load(full_path_cstr, &w_raw, &h_raw, &comp, 3);
if (avatar_buf_raw != 0){
//- make sure the texture is a square
// TODO(allen): if it's not a square, make it into a square first.
Assert(w_raw == h_raw);
//- resize texture data
U32 size = AVATAR_DISPLAY_SIDE*AVATAR_DISPLAY_SIDE*3;
U8 *avatar_buf = push_array(scratch.arena, U8, size);
stbir_resize_uint8_srgb(avatar_buf_raw,
w_raw, h_raw, 0,
avatar_buf,
AVATAR_DISPLAY_SIDE, AVATAR_DISPLAY_SIDE, 0,
3, STBIR_ALPHA_CHANNEL_NONE, 0);
//- setup GPU texture
GLuint internal_format = GL_RGB;
#if ENABLE_SRGB_CONVERSION
internal_format = GL_SRGB8;
#endif
glGenTextures(1, &result);
glBindTexture(GL_TEXTURE_2D, result);
glTexImage2D(GL_TEXTURE_2D, 0, internal_format,
AVATAR_DISPLAY_SIDE, AVATAR_DISPLAY_SIDE, 0,
GL_RGB, GL_UNSIGNED_BYTE, avatar_buf);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (glGetError() != 0 || result == 0){
gfx_message_box(str8_lit("error"),
str8_lit("failed to setup avatar texture"));
os_exit_process(1);
}
stbi_image_free(avatar_buf_raw);
}
m_release_scratch(scratch);
return(result);
}
function void
draw_geometry_vert(Vertex *v, U32 count, GLuint tex, V2F32 windim){
glBufferData(GL_ARRAY_BUFFER, count*sizeof(*v), v, GL_STREAM_DRAW);
glUseProgram(vert_program.handle);
glUniform2f(vert_u_view_xform, 2.f/windim.x, 2.f/windim.y);
glUniform1i(vert_u_tex, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(Vertex),
PtrFromInt(OffsetOfMember(Vertex, p)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(Vertex),
PtrFromInt(OffsetOfMember(Vertex, uv)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 1, GL_FLOAT, false, sizeof(Vertex),
PtrFromInt(OffsetOfMember(Vertex, s_mul)));
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 1, GL_FLOAT, false, sizeof(Vertex),
PtrFromInt(OffsetOfMember(Vertex, v_mul)));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 1, GL_FLOAT, false, sizeof(Vertex),
PtrFromInt(OffsetOfMember(Vertex, over)));
glDrawArrays(GL_TRIANGLES, 0, count);
}
function void
draw_geometry_rect(Vertex *v, U32 count, V2F32 windim){
glBufferData(GL_ARRAY_BUFFER, count*sizeof(*v), v, GL_STREAM_DRAW);
glUseProgram(rect_program.handle);
glUniform2f(rect_u_view_xform, 2.f/windim.x, 2.f/windim.y);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(Vertex),
PtrFromInt(OffsetOfMember(Vertex, p)));
glEnableVertexAttribArray(1);
glVertexAttribIPointer(1, 1, GL_UNSIGNED_INT, sizeof(Vertex),
PtrFromInt(OffsetOfMember(Vertex, color)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 4, GL_FLOAT, false, sizeof(Vertex),
PtrFromInt(OffsetOfMember(Vertex, rect)));
glDrawArrays(GL_TRIANGLES, 0, count);
}
function void
draw_avatar(V2F32 center, GLuint texture, AvatarParams *params,
V2F32 windim){
#define CIRCLE_APPROX_N 192
#define VERT_PER_APPROX 15
// setup screen geometry
F32 radius = params->radius;
F32 theta_delta = tau_F32/CIRCLE_APPROX_N;
F32 wiggle_time = ((params->frame_counter%60)/60.f);
F32 wiggle_time_theta = WIGGLE_TEMPORAL_FREQ*wiggle_time*tau_F32;
F32 wiggle_temporal = sin_F32(wiggle_time_theta);
F32 wiggle_base_time_theta = WIGGLE_BASE_TEMPORAL_FREQ*wiggle_time*tau_F32;
F32 wiggle_base_temporal = sin_F32(wiggle_base_time_theta);
U32 band_count = params->band_count;
V2F32 w[CIRCLE_APPROX_N + 1] = {0};
V2F32 ring3[CIRCLE_APPROX_N + 1] = {0};
{
for (U32 i = 0; i < CIRCLE_APPROX_N; i += 1){
F32 theta = i*theta_delta;
w[i] = v2f32_polar(theta, 1.f);
F32 theta_idx = 0.f;
if (0.f <= theta && theta <= pi_F32/2.f){
theta_idx = theta/(pi_F32/2.f);
}
else if (pi_F32/2.f <= theta && theta <= pi_F32*3.f/2.f){
theta_idx = lerp(-1.f, unlerp(pi_F32*3.f/2.f,
theta,
pi_F32/2.f), 1.f);
}
else if (pi_F32*3.f/2.f <= theta && theta <= tau_F32){
theta_idx = lerp(-1.f, unlerp(pi_F32*3.f/2.f,
theta,
tau_F32), 0.f);
}
F32 freq_idx_f32 = (band_count - 1)*(theta_idx + 1.f)/2.f;
U32 freq_idx_u32 = (U32)freq_idx_f32;
F32 freq_interp = freq_idx_f32 - (F32)freq_idx_u32;
F32 wiggle_param = 0.f;
if (freq_idx_u32 < band_count - 1){
wiggle_param = lerp(params->freq_wiggles[freq_idx_u32],
freq_interp,
params->freq_wiggles[freq_idx_u32 + 1]);
}
else{
wiggle_param = params->freq_wiggles[freq_idx_u32];
}
F32 wiggle_spatial = sin_F32(theta*WIGGLE_SPATIAL_FREQ);
F32 wiggle_amt =
(wiggle_param*
(wiggle_temporal*wiggle_spatial*WIGGLE_SPATIAL_MAG +
wiggle_base_temporal*WIGGLE_BASE_MAG));
ring3[i] = center + w[i]*(radius - wiggle_amt);
}
w[CIRCLE_APPROX_N] = w[0];
ring3[CIRCLE_APPROX_N] = ring3[0];
}
// fill full graphics geometry
V2F32 uv_center = v2f32(0.5f, 0.5f);
Vertex verts[VERT_PER_APPROX*CIRCLE_APPROX_N] = {0};
{
Vertex *v = verts;
F32 mid_level_a = unlerp(1.f, AVATAR_RING2_RATIO, AVATAR_RING1_RATIO);
for (U32 i = 0; i < CIRCLE_APPROX_N;
i += 1, v += VERT_PER_APPROX){
V2F32 w1 = w[i];
V2F32 w2 = w[i + 1];
V2F32 w1f = v2f32(w1.x, -w1.y);
V2F32 w2f = v2f32(w2.x, -w2.y);
// approx points
V2F32 p1ring1 = center + radius*w1*AVATAR_RING1_RATIO;
V2F32 p1ring2 = center + radius*w1*AVATAR_RING2_RATIO;
V2F32 p1ring3 = ring3[i];
V2F32 p2ring1 = center + radius*w2*AVATAR_RING1_RATIO;
V2F32 p2ring2 = center + radius*w2*AVATAR_RING2_RATIO;
V2F32 p2ring3 = ring3[i + 1];
V2F32 uv1ring1 = uv_center + 0.5f*w1f*AVATAR_RING1_RATIO;
V2F32 uv1ring2 = uv_center + 0.5f*w1f*AVATAR_RING2_RATIO;
V2F32 uv1ring3 = uv_center + 0.5f*w1f;
V2F32 uv2ring1 = uv_center + 0.5f*w2f*AVATAR_RING1_RATIO;
V2F32 uv2ring2 = uv_center + 0.5f*w2f*AVATAR_RING2_RATIO;
V2F32 uv2ring3 = uv_center + 0.5f*w2f;
// inner triangle
v[0].p = center; v[0].uv = uv_center;
v[1].p = p1ring1; v[1].uv = uv1ring1;
v[2].p = p2ring1; v[2].uv = uv2ring1;
// ring1->ring2 triangle 1
v[3].p = p1ring1; v[3].uv = uv1ring1;
v[4].p = p1ring2; v[4].uv = uv1ring2;
v[5].p = p2ring1; v[5].uv = uv2ring1;
// ring1->ring2 triangle 2
v[6].p = p1ring2; v[6].uv = uv1ring2;
v[7].p = p2ring1; v[7].uv = uv2ring1;
v[8].p = p2ring2; v[8].uv = uv2ring2;
// ring2->ring3 triangle 1
v[ 9].p = p1ring2; v[ 9].uv = uv1ring2;
v[10].p = p1ring3; v[10].uv = uv1ring3;
v[11].p = p2ring2; v[11].uv = uv2ring2;
// ring2->ring3 triangle 2
v[12].p = p1ring3; v[12].uv = uv1ring3;
v[13].p = p2ring2; v[13].uv = uv2ring2;
v[14].p = p2ring3; v[14].uv = uv2ring3;
// inner/outer parameters
U32 inner_idx[] = {0, 1, 2, 3, 5, 7};
U32 mid_idx[] = {4, 6, 8, 9, 11, 13};
U32 outer_idx[] = {10, 12, 14};
for (U32 j = 0; j < ArrayCount(inner_idx); j += 1){
v[inner_idx[j]].over = 0.f;
}
for (U32 j = 0; j < ArrayCount(mid_idx); j += 1){
v[mid_idx[j]].over = 1.f - mid_level_a;
}
for (U32 j = 0; j < ArrayCount(outer_idx); j += 1){
v[outer_idx[j]].over = 1.f;
}
// common to all
for (U32 j = 0; j < VERT_PER_APPROX; j += 1){
v[j].s_mul = params->sat_mul;
v[j].v_mul = params->val_mul;
}
}
}
draw_geometry_vert(verts, VERT_PER_APPROX*CIRCLE_APPROX_N, texture, windim);
}
function void
rectangle_push(M_Arena *arena, RectList *list,
I2F32 rect, V4F32 top_color, V4F32 bot_color){
RectNode *node = push_array(arena, RectNode, 1);
SLLQueuePush(list->first, list->last, node);
list->count += 1;
node->rect = rect;
node->top_color = top_color;
node->bot_color = bot_color;
}
function void
draw_rectangle_list(RectList *list, V2F32 windim){
M_ArenaTemp scratch = m_get_scratch(0, 0);
U64 vert_count = list->count*6;
Vertex *verts = push_array(scratch.arena, Vertex, vert_count);
{
Vertex *v = verts;
for (RectNode *node = list->first;
node != 0;
node = node->next, v += 6){
I2F32 rect = node->rect;
I2F32 irect = i2f32(floor_F32(rect.x0), floor_F32(rect.y0),
ceil_F32(rect.x1), ceil_F32(rect.y1));
v[0].p = v2f32(irect.x0, irect.y0);
v[1].p = v2f32(irect.x0, irect.y1);
v[2].p = v2f32(irect.x1, irect.y0);
v[3].p = v2f32(irect.x0, irect.y1);
v[4].p = v2f32(irect.x1, irect.y0);
v[5].p = v2f32(irect.x1, irect.y1);
U32 top_color_u32 = color_u32_from_4f32(V4_EXPANDED(node->top_color));
U32 bot_color_u32 = color_u32_from_4f32(V4_EXPANDED(node->bot_color));
v[0].color = bot_color_u32;
v[2].color = bot_color_u32;
v[4].color = bot_color_u32;
v[1].color = top_color_u32;
v[3].color = top_color_u32;
v[5].color = top_color_u32;
for (U32 i = 0; i < 6; i += 1){
v[i].rect = rect;
}
}
}
draw_geometry_rect(verts, vert_count, windim);
m_release_scratch(scratch);
}
////////////////////////////////
// NOTE(allen): Audio Processing
function String8
bop_f32_from_s16(M_Arena *arena, String8 in){
// round size
U64 el_count = in.size/2;
// allocate out
F32 *out = push_array(arena, F32, el_count);
// fill loop
S16 *in_ptr = (S16*)in.str;
F32 *out_ptr = out;
F32 *opl_ptr = out_ptr + el_count;
for (; out_ptr < opl_ptr; out_ptr += 1, in_ptr += 1){
// read s16
S16 x = *in_ptr;
// divide to f32
F32 fx = 0;
if (x >= 0){
fx = (F32)(x)/(F32)(0x7FFF);
}
else{
fx = (F32)(x)/(F32)(0x8000);
}
// write f32
*out_ptr = fx;
}
// package output buffer
String8 result = {};
result.str = (U8*)out;
result.size = el_count*sizeof(F32);
return(result);
}
function Track
track_from_file_path(M_Arena *arena, String8 full_path){
M_ArenaTemp scratch = m_get_scratch(&arena, 1);
// load data
String8 data = os_file_read(scratch.arena, full_path);
// parse wave file
U32 block_count = 0;
F32 *channels[2] = {0};
if (data.str != 0){
WAVE_SubChunkList sub_chunks =
wave_sub_chunks_from_data(scratch.arena, data);
WAVE_SubChunkNode *fmt_chunk =
wave_chunk_from_id(sub_chunks, WAVE_ID_fmt);
WAVE_SubChunkNode *data_chunk =
wave_chunk_from_id(sub_chunks, WAVE_ID_data);
WAVE_FormatData fmt = wave_format_data_from_fmt_chunk(fmt_chunk, data);
// check for supported wav format
Assert(fmt.channel_count == 2);
Assert(fmt.bytes_per_block == 4);
Assert(fmt.blocks_per_second == 44100);
Assert(fmt.bytes_stride_per_sample == 2);
Assert(fmt.bits_per_sample == 16);
// convert to seperate f32 arrays
block_count = data_chunk->size/fmt.bytes_per_block;
void *sample_data = data.str + data_chunk->off;
String8 *split_sample_data = bop_uninterleave(scratch.arena, sample_data,
fmt.channel_count,
fmt.bytes_stride_per_sample,
block_count);
for (U32 channel_i = 0; channel_i < fmt.channel_count; channel_i += 1){
String8 f32_sample_data =
bop_f32_from_s16(arena, split_sample_data[channel_i]);
channels[channel_i] = (F32*)f32_sample_data.str;
}
}
// fill result
Track result = {};
result.channels[0] = channels[0];
result.channels[1] = channels[1];
result.sample_count = block_count;
m_release_scratch(scratch);
return(result);
}
function F32
decibel_from_linear(F32 r){
static F32 normalize_log10 = 0;
if (normalize_log10 == 0){
normalize_log10 = 1.f/ln_F32(10.f);
}
F32 db = 10.f*ln_F32(r)*normalize_log10;
return(db);
}
function Array_F32
db_per_frame_from_samples(M_Arena *arena,
F32 *samples, U32 sample_count,
U32 sample_frequency){
U32 frame_count = CeilIntDiv(sample_count*60, sample_frequency);
U32 sample_per_frame = sample_frequency/60;
U32 window_size = 150;
F32 window_mean_norm = 1.f/(F32)window_size;
F32 *out_buf = push_array(arena, F32, frame_count);
for (U32 i = 0; i < frame_count; i += 1){
U32 first_sample_i = i*sample_per_frame;
U32 opl_sample_i_raw = first_sample_i + sample_per_frame;
U32 opl_sample_i = ClampTop(opl_sample_i_raw, sample_count);
F32 max_var_db = -100.f;
for (U32 j = first_sample_i; j < opl_sample_i; j += window_size){
U32 first_sample_j = j;
U32 opl_sample_j_raw = first_sample_j + window_size;
U32 opl_sample_j = ClampTop(opl_sample_j_raw, sample_count);
F32 *first_sample = samples + first_sample_j;
F32 *opl_sample = samples + opl_sample_j;
F32 mean = 0.f;
for (F32 *sample = first_sample;
sample < opl_sample;
sample += 1){
mean += *sample;
}
mean *= window_mean_norm;
F32 var = 0.f;
for (F32 *sample = first_sample;
sample < opl_sample;
sample += 1){
F32 d = *sample - mean;
var += d*d;
}
var *= window_mean_norm;
F32 var_db = decibel_from_linear(var);
max_var_db = Max(max_var_db, var_db);
}
out_buf[i] = max_var_db;
}
Array_F32 result = {0};
result.v = out_buf;
result.count = frame_count;
return(result);
}
function void
speaker_marker_push(M_Arena *arena, SpeakerMarkerList *list,
U32 frame_idx, U32 speaker_idx, B32 begin){
SpeakerMarkerNode *node = push_array(arena, SpeakerMarkerNode, 1);
SLLQueuePush(list->first, list->last, node);
list->count += 1;
node->marker.frame_idx = frame_idx;
node->marker.speaker_idx = speaker_idx;
node->marker.begin = begin;
}
function SpeakerMarkerArray
speaker_marker_array_from_list(M_Arena *arena, SpeakerMarkerList *list){
// fill array
U64 count = list->count;
SpeakerMarker *markers = push_array(arena, SpeakerMarker, count);
{
SpeakerMarker *ptr = markers;
for (SpeakerMarkerNode *node = list->first;
node != 0;
node = node->next, ptr += 1){
*ptr = node->marker;
}
}
// fill result
SpeakerMarkerArray result = {0};
result.markers = markers;
result.count = count;
return(result);
}
function void
speaker_marker_array_sort_in_place(SpeakerMarker *markers, U64 count){
M_ArenaTemp scratch = m_get_scratch(0, 0);
// identify already-sorted ranges
SortRange *first_range = 0;
SortRange *last_range = 0;
{
U64 i = 0;
for (;i < count;){
U64 first = i;
for (i += 1; i < count; i += 1){
if (markers[i].frame_idx < markers[i - 1].frame_idx){
break;
}
}
U64 opl = i;
SortRange *range = push_array(scratch.arena, SortRange, 1);
SLLQueuePush(first_range, last_range, range);
range->first = first;
range->opl = opl;
}
}
// setup a swap buffer
SpeakerMarker *swap_memory = push_array(scratch.arena, SpeakerMarker, count);
// sort from src to dst by merging
SpeakerMarker *src_markers = markers;
SpeakerMarker *dst_markers = swap_memory;
for (;;){
// check if we're done
if (first_range == last_range){
break;
}
// transfer ranges to a stack and reset the queue
SortRange *range_stack = first_range;
first_range = 0;
last_range = 0;
// merge neighboring ranges
for (;;){
// range popping
SortRange *range1 = range_stack;
if (range1 == 0){
break;
}
SLLStackPop(range_stack);
SortRange *range2 = range_stack;
if (range2 == 0){
SLLQueuePush(first_range, last_range, range1);
break;
}
SLLStackPop(range_stack);
// array merge
U64 i = range1->first;
U64 j1 = range1->first;
U64 j1_opl = range1->opl;
U64 j2 = range2->first;
U64 j2_opl = range2->opl;
for (;j1 < j1_opl && j2 < j2_opl;){
U32 frame_idx1 = src_markers[j1].frame_idx;
U32 frame_idx2 = src_markers[j2].frame_idx;
if (frame_idx1 <= frame_idx2){
dst_markers[i] = src_markers[j1];
j1 += 1;
}
else{
dst_markers[i] = src_markers[j2];
j2 += 1;
}
i += 1;
}
for (;j1 < j1_opl;){
dst_markers[i] = src_markers[j1];
j1 += 1;
i += 1;
}
for (;j2 < j2_opl;){
dst_markers[i] = src_markers[j2];
j2 += 1;
i += 1;
}
// combine into a single range on range queue
range1->opl = range2->opl;
SLLQueuePush(first_range, last_range, range1);
}
// after a pass swap src & dst
Swap(SpeakerMarker*, src_markers, dst_markers);
}
// transfer data if it's in the wrong buffer
if (src_markers != markers){
MemoryCopy(markers, src_markers, sizeof(*markers)*count);
}
m_release_scratch(scratch);
}
////////////////////////////////
// NOTE(allen): Main
function void
window_resize_handler(GFX_Window window, U32 width, U32 height){
// begin render
opengl_sys_begin_render(window);
// render
glClearColor(1.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
// end render
opengl_sys_end_render();
}
function ProcessedTrack
processed_track_from_samples(M_Arena *arena,
F32 *samples, U32 sample_count,
U32 sample_frequency,
F32 *band_freq_hz, U32 band_count){
M_ArenaTemp scratch = m_get_scratch(&arena, 1);
// approximate db levels on each frame
Array_F32 max_level_db_per_frame =
db_per_frame_from_samples(arena, samples, sample_count, sample_frequency);
// frequency analysis
AUDGEN_Rate rate = audgen_rate((F32)sample_frequency);
F32 end_t = rate.delta_t*sample_count;
F32 *band_level_db_per_frame[MAX_BAND_COUNT] = {0};
for (U32 i = 0; i < band_count; i += 1){
// filter the signal
F32 *filtered = push_array(scratch.arena, F32, sample_count);
MemoryCopy(filtered, samples, sizeof(*samples)*sample_count);
F32 lo_hz = band_freq_hz[i];
F32 hi_hz = band_freq_hz[i + 1];
AUDGEN_Buffer buffer = {};
buffer.buf = filtered;
buffer.count = sample_count;
buffer.rate = rate;
audgen_high_pass_in_place(buffer, i1f32(0.f, end_t), lo_hz);
audgen_low_pass_in_place(buffer, i1f32(0.f, end_t), hi_hz);
// approximate db levels on each frame
Array_F32 db_per_frame =
db_per_frame_from_samples(arena, filtered, sample_count,
sample_frequency);
band_level_db_per_frame[i] = db_per_frame.v;
}
// fill result
ProcessedTrack result = {0};
result.frame_count = max_level_db_per_frame.count;
result.max_level_db_per_frame = max_level_db_per_frame.v;
for (U32 i = 0; i < band_count; i += 1){
result.band_level_db_per_frame[i] =
band_level_db_per_frame[i];
}
result.band_count = band_count;
m_release_scratch(scratch);
return(result);
}
int
WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd){
w32_WinMain_init(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
M_Arena *arena = m_alloc_arena();
M_Arena *frame_arena = m_alloc_arena();
//- setup input parameters
String8List args = os_command_line_arguments();
U32 speaker_count = (args.node_count - 1)/2;
Speaker *speakers = push_array_zero(arena, Speaker, speaker_count);
String8Node *first_arg = args.first;
if (first_arg != 0){
Speaker *speaker_ptr = speakers;
for (String8Node *node = first_arg->next;
node != 0;){
speaker_ptr->avatar_path = node->string;
node = node->next;
if (node != 0){
speaker_ptr->audio_path = node->string;
node = node->next;
}
speaker_ptr += 1;
}
}
//- initialize graphics
B32 gfx_init_success = gfx_init();
if (!gfx_init_success){
String8 error = er_get_last_error();
gfx_message_box(str8_lit("error"), error);
os_exit_process(1);
}
//- setup graphics hooks
gfx_set_resizing_hook(window_resize_handler);
//- initialize opengl
B32 gl_init_success = opengl_sys_init();
if (!gl_init_success){
String8 error = er_get_last_error();
gfx_message_box(str8_lit("error"), error);
os_exit_process(1);
}
//- setup opengl state
setup_opengl_state();
//- process speakers
B32 failed_to_load = false;
GLuint *avatar_textures = push_array(arena, GLuint, speaker_count);
ProcessedTrack *processed_tracks =
push_array(arena, ProcessedTrack, speaker_count);
ProcessedTrack processed_master_track = {0};
{
M_ArenaTemp scratch = m_get_scratch(&arena, 1);
// process input avatars
opengl_sys_begin_nonrender_section();
for (U32 i = 0; i < speaker_count; i += 1){
GLuint texture = avatar_gpu_texture_from_file_path(speakers[i].avatar_path);
if (texture == 0){
fprintf(stderr, "could not load avatar: '%.*s'\n",
str8_expand(speakers[i].avatar_path));
failed_to_load = true;
}
avatar_textures[i] = texture;
}
opengl_sys_end_nonrender_section();
U32 sample_frequency = 44100;
// load input audio
F32 **speaker_mixed = push_array(scratch.arena, F32*, speaker_count);
U32 *speaker_sample_count = push_array(scratch.arena, U32, speaker_count);
for (U32 speaker_i = 0; speaker_i < speaker_count; speaker_i += 1){
Speaker *speaker = speakers + speaker_i;
Track track = track_from_file_path(scratch.arena, speaker->audio_path);
if (track.sample_count == 0){
fprintf(stderr, "could not load tarck: '%.*s'\n",
str8_expand(speaker->audio_path));
failed_to_load = true;
}
U32 sample_count = track.sample_count;
F32 *mixed = push_array(scratch.arena, F32, sample_count);
{
F32 *out = mixed;
F32 *in0 = track.channels[0];
F32 *in1 = track.channels[1];
for (U32 i = 0; i < sample_count;
i += 1, out += 1, in0 += 1, in1 += 1){
*out = (*in0 + *in1)*0.5f;
}
}
speaker_mixed[speaker_i] = mixed;
speaker_sample_count[speaker_i] = sample_count;
}
// master track
{
U32 master_track_sample_count = 0;
for (U32 speaker_i = 0; speaker_i < speaker_count; speaker_i += 1){
master_track_sample_count =
Max(master_track_sample_count, speaker_sample_count[speaker_i]);
}
F32 *master_track =
push_array_zero(scratch.arena, F32, master_track_sample_count);
for (U32 speaker_i = 0; speaker_i < speaker_count; speaker_i += 1){
F32 *mixed = speaker_mixed[speaker_i];
U32 sample_count = speaker_sample_count[speaker_i];
for (U32 i = 0; i < sample_count; i += 1){
master_track[i] += mixed[i];
}
}
processed_master_track =
processed_track_from_samples(arena,
master_track, master_track_sample_count,
sample_frequency,
master_band_frequencies_hz,
MASTER_BAND_COUNT);
}
// process input audio
for (U32 speaker_i = 0; speaker_i < speaker_count; speaker_i += 1){
F32 *mixed = speaker_mixed[speaker_i];
U32 sample_count = speaker_sample_count[speaker_i];
processed_tracks[speaker_i] =
processed_track_from_samples(arena, mixed, sample_count, sample_frequency,
avatar_band_frequencies_hz,
AVATAR_BAND_COUNT);
}
m_release_scratch(scratch);
}
//- exit if failed to load input data
if (failed_to_load){
os_exit_process(1);
}
//- speaker marker processing
B8 **this_speaker_is_speaking_per_frame =
push_array(arena, B8*, speaker_count);
{
M_ArenaTemp scratch = m_get_scratch(&arena, 1);
// add markers to speaker marker list
SpeakerMarkerList list = {0};
for (U32 speaker_i = 0; speaker_i < speaker_count; speaker_i += 1){
ProcessedTrack *track = &processed_tracks[speaker_i];
U32 frame_count = track->frame_count;
F32 *db_per_frame = track->max_level_db_per_frame;
F32 prev_db = -100.f;
for (U32 i = 0; i < frame_count; i += 1){
if (prev_db <= CONSIDERED_SILENCE_DB &&
db_per_frame[i] > CONSIDERED_SILENCE_DB){
speaker_marker_push(scratch.arena, &list, i, speaker_i, true);
}
else if (prev_db > CONSIDERED_SILENCE_DB &&
db_per_frame[i] <= CONSIDERED_SILENCE_DB){
speaker_marker_push(scratch.arena, &list, i, speaker_i, false);
}
prev_db = db_per_frame[i];
}
if (prev_db > CONSIDERED_SILENCE_DB){
speaker_marker_push(scratch.arena, &list, frame_count, speaker_i, false);
}
}
// convert marker list to array & sort
SpeakerMarkerArray array =
speaker_marker_array_from_list(scratch.arena, &list);
speaker_marker_array_sort_in_place(array.markers, array.count);
// simplify the markers in place
{
SpeakerMarker *dst = array.markers;
SpeakerMarker *opl = array.markers + array.count;
for (SpeakerMarker *src = array.markers; src < opl;){
if (!src->begin &&
src + 1 < opl && (src + 1)->begin &&
src->speaker_idx == (src + 1)->speaker_idx){
src += 2;
}
else{
*dst = *src;
src += 1;
dst += 1;
}
}
array.count = (U64)(dst - array.markers);
}
// fill "is this speaker speaking" arrays
for (U32 speaker_i = 0; speaker_i < speaker_count; speaker_i += 1){
U32 frame_count = processed_tracks[speaker_i].frame_count;
B8 *is_speaking_per_frame = push_array_zero(arena, B8, frame_count);
SpeakerMarker *opl = array.markers + array.count;
for (SpeakerMarker *ptr = array.markers; ptr < opl;){
// find range beginning
SpeakerMarker *range_beginning = 0;
for (; ptr < opl; ptr += 1){
if (ptr->speaker_idx == speaker_i && ptr->begin){
range_beginning = ptr;
ptr += 1;
break;
}
}
// exit if no range
if (range_beginning == 0){
break;
}
// find range ending
SpeakerMarker *range_ending = 0;
for (; ptr < opl; ptr += 1){
if (ptr->speaker_idx == speaker_i && !ptr->begin){
range_ending = ptr;
ptr += 1;
break;
}
}
// extract frame index range
U32 frame_idx_beginning = range_beginning->frame_idx;
U32 frame_idx_ending = frame_count;
if (range_ending != 0){
frame_idx_ending = range_ending->frame_idx;
}
// set 1s
B8 *bopl = is_speaking_per_frame + frame_idx_ending;
for (B8 *bptr = is_speaking_per_frame + frame_idx_beginning;
bptr < bopl;
bptr += 1){
*bptr = 1;
}
}
this_speaker_is_speaking_per_frame[speaker_i] = is_speaking_per_frame;
}
m_release_scratch(scratch);
}
//- initialize speaker state
SpeakerState *speaker_state =
push_array_zero(arena, SpeakerState, speaker_count);
SpeakerState master_state[2] = {0};
//- create window
GFX_Window window = gfx_window_create();
if (window == 0){
String8 error = er_get_last_error();
if (error.str != 0){
gfx_message_box(str8_lit("error"), error);
os_exit_process(1);
}
}
//- equip gl window
B32 equip_success = opengl_sys_equip_window(window);
if (!equip_success){
String8 error = er_get_last_error();
gfx_message_box(str8_lit("error"), error);
os_exit_process(1);
}
//- frame controls
U64 frame_counter = 0;
#if PLAY_TEST_TRACK
PlaySoundA(TEST_TRACK, 0, SND_FILENAME|SND_NODEFAULT|SND_ASYNC);
#endif
//- begin UI loop
gfx_window_set_full_screen(window, true);
gfx_window_set_visible(window, true);
for (;;){
m_arena_pop_to(frame_arena, 0);
// wait for input
if (!gfx_peek_input()){
break;
}
// draw gl window
if (gfx_window_is_valid(window)){
// get window dim
V2S32 window_dim_s32 = {};
{
I2S32 rect = {};
if (gfx_window_get_inner_rect(window, &rect)){
window_dim_s32 = intr_dim(rect);
}
}
V2F32 window_dim = v2f32(V2_EXPANDED(window_dim_s32));
// check this is intended output resolution
if (frame_counter == 0){
if (!(window_dim_s32.x == 1920 &&
window_dim_s32.y == 1080)){
gfx_message_box(str8_lit("error"),
str8_lit("This program is tuned to run on monitors with a resolution of 1920 by 1080 only."));
os_exit_process(1);
}
}
// avatar layout
V2F32 *avatar_pos = push_array_zero(arena, V2F32, speaker_count);
{
F32 y = window_dim.y*0.5f;
F32 x_delta = window_dim.x/(1.f + speaker_count);
F32 x = x_delta;
for (U32 i = 0; i < speaker_count; i += 1){
avatar_pos[i].x = x;
avatar_pos[i].y = y;
x += x_delta;
}
}
// begin render
opengl_sys_begin_render(window);
// bind srgb framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, srgb_framebuffer);
// render frame
glViewport(0, 0, window_dim_s32.x, window_dim_s32.y);
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
// render rectangles
RectList rect_list = {0};
{
// rect layout information
U32 rect_count = 384;
F32 rect_spread_x = window_dim.x*1.f;
F32 rect_spread_min_x = (window_dim.x - rect_spread_x)/2.f;
F32 rect_spacing_x = rect_spread_x/rect_count;
F32 rect_margin_x = 1.f;
F32 rect_dim_x = rect_spacing_x - rect_margin_x*2.f;
F32 center_y[2] = {0, window_dim.y};
F32 min_dim_y = BARS_MIN_HEIGHT;
F32 max_dim_y = BARS_MAX_HEIGHT;
F32 bright[] = {
0.20f,
0.00f,
};
F32 dark[] = {
0.02f,
0.20f,
};
// effect: frequency analysis graphed from left to right
#if 1
// extract master track band levels for this frame
for (U32 i = 0; i < MASTER_BAND_COUNT; i += 1){
F32 level = 0;
if (frame_counter < processed_master_track.frame_count){
F32 db =
processed_master_track.band_level_db_per_frame[i][frame_counter];
F32 level_raw = unlerp(CONSIDERED_MINIMUM_DB, db, CONSIDERED_FULL_DB);
level = Clamp(0.f, level_raw, 1.f);
}
F32 rate = 0.08f;
F32 rate_decay = 0.07f;
master_state[0].freq_wiggles[i] =
lerp(master_state[0].freq_wiggles[i], rate, level);
rate = rate*rate_decay;
for (U32 j = 1; j < ArrayCount(master_state); j += 1){
if (!(level > master_state[j].freq_wiggles[i])){
master_state[j].freq_wiggles[i] =
lerp(master_state[j].freq_wiggles[i], rate, level);
}
rate *= rate_decay;
master_state[j].freq_wiggles[i] =
Max(master_state[j].freq_wiggles[i],
master_state[0].freq_wiggles[i]);
}
}
// fill rectangles list
for (U32 i = 0; i < rect_count; i += 1){
// x position
F32 layout_x = rect_spread_min_x + i*rect_spacing_x;
F32 left = layout_x + rect_margin_x;
F32 right = left + rect_dim_x;
// freq idx
F32 freq_norm = (F32)i/(F32)(rect_count - 1);
F32 freq_idx_f32 = (MASTER_BAND_COUNT - 1)*freq_norm;
U32 freq_idx_u32 = (U32)freq_idx_f32;
F32 freq_interp = freq_idx_f32 - (F32)freq_idx_u32;
// for each "master state"
F32 prev_dim_y = 0;
for (S32 j = 0; j < ArrayCount(master_state); j += 1){
// wiggle param
F32 wiggle_param = 0;
if (freq_idx_u32 < MASTER_BAND_COUNT - 1){
wiggle_param = lerp(master_state[j].freq_wiggles[freq_idx_u32],
freq_interp,
master_state[j].freq_wiggles[freq_idx_u32 + 1]);
}
else{
wiggle_param = master_state[j].freq_wiggles[freq_idx_u32];
}
// y position
F32 dim_y = lerp(min_dim_y,
wiggle_param,
max_dim_y);
// color
V4F32 bright_color = v4f32(bright[j], bright[j], bright[j], 1.f);
V4F32 dark_color = v4f32(dark[j], dark[j], dark[j], 1.f);
// push rectangles
for (U32 k = 0; k < ArrayCount(center_y); k += 1){
F32 l = left;
F32 r = right;
V4F32 top_color = bright_color;
V4F32 bot_color = dark_color;
F32 bottom = 0;
F32 top = 0;
if (k == 0){
bottom = center_y[k] + prev_dim_y;
top = center_y[k] + dim_y;
}
else{
top = center_y[k] - prev_dim_y;
bottom = center_y[k] - dim_y;
top_color = dark_color;
bot_color = bright_color;
}
rectangle_push(frame_arena, &rect_list,
i2f32(l, bottom, r, top),
top_color, bot_color);
}
prev_dim_y = dim_y;
}
}
#endif
}
draw_rectangle_list(&rect_list, window_dim);
// render avatars
for (U32 i = 0; i < speaker_count; i += 1){
SpeakerState *state = &speaker_state[i];
ProcessedTrack *track = &processed_tracks[i];
U32 frame_count = track->frame_count;
F32 is_speaking_target = 0.f;
if (frame_counter < frame_count &&
this_speaker_is_speaking_per_frame[i][frame_counter]){
is_speaking_target = 1.f;
}
if (is_speaking_target > state->is_speaking_level){
state->is_speaking_level =
lerp(state->is_speaking_level, 0.15f, is_speaking_target);
}
else{
state->is_speaking_level =
lerp(state->is_speaking_level, 0.035f, is_speaking_target);
}
for (U32 i = 0; i < AVATAR_BAND_COUNT; i += 1){
F32 level = 0;
if (frame_counter < frame_count){
F32 db = track->band_level_db_per_frame[i][frame_counter];
F32 level_raw = unlerp(CONSIDERED_MINIMUM_DB, db, CONSIDERED_FULL_DB);
level = Clamp(0.f, level_raw, 1.f);
}
if (level > state->freq_wiggles[i]){
state->freq_wiggles[i] =
lerp(state->freq_wiggles[i], 0.5f, level);
}
else{
state->freq_wiggles[i] =
lerp(state->freq_wiggles[i], 0.15f, level);
}
}
F32 is_speaking_level = state->is_speaking_level;
AvatarParams params = {0};
params.frame_counter = frame_counter;
params.radius =
AVATAR_DISPLAY_RADIUS*lerp(AVATAR_QUITE_RATIO, is_speaking_level, 1.0f);
params.sat_mul = lerp(0.00f, is_speaking_level, 1.0f);
params.val_mul = lerp(0.05f, is_speaking_level, 1.0f);
params.band_count = AVATAR_BAND_COUNT;
MemoryCopyArray(params.freq_wiggles, state->freq_wiggles);
draw_avatar(avatar_pos[i], avatar_textures[i], &params, window_dim);
}
// resolve srgb frame buffer onto window
glBindFramebuffer(GL_READ_FRAMEBUFFER, srgb_framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, window_dim_s32.x, window_dim_s32.y,
0, 0, window_dim_s32.x, window_dim_s32.y,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// end render
opengl_sys_end_render();
}
//- end frame
frame_counter += 1;
}
return(0);
}
//$ graphical //