diff --git a/render.c b/render.c index 3e772b6..f697fa9 100644 --- a/render.c +++ b/render.c @@ -359,7 +359,7 @@ struct gl_data { int fcounter, ucounter, kcounter; bool print_fps, avg_window, interpolate, force_geometry; void** t_data; - float gravity_step, target_spu, fr, ur; + float gravity_step, target_spu, fr, ur, smooth_distance, smooth_ratio; float* interpolate_buf[6]; int geometry[4]; }; @@ -433,6 +433,59 @@ static struct gl_bind_src bind_sources[] = { *udata = u; \ } else u = (typeof(u)) *udata; +/* type generic clamp/min/max, like in GLSL */ + +#define clamp(v, min, max) \ + ({ \ + __auto_type _v = v; \ + if (_v < min) _v = min; \ + else if (_v > max) _v = max; \ + _v; \ + }) + +#define min(a0, b0) \ + ({ \ + __auto_type _a = a0; \ + __auto_type _b = b0; \ + _a < _b ? _a : _b; \ + }) + +#define max(a0, b0) \ + ({ \ + __auto_type _a = a0; \ + __auto_type _b = b0; \ + _a > _b ? _a : _b; \ + }) + +#define E 2.7182818284590452353 + +void transform_smooth(struct gl_data* d, void** udaa, void* data) { + struct gl_sampler_data* s = (struct gl_sampler_data*) data; + float* b = s->buf; + size_t + sz = s->sz, + asz = (size_t) ceil(s->sz / d->smooth_ratio); + for (int t = 0; t < asz; ++t) { + float + db = log(t), /* buffer index on log scale */ + v = b[t], /* value at this position */ + avg = 0; /* adj value averages (weighted) */ + /* Calculate real indexes for sampling at this position, since the + distance is specified in scalar values */ + int smin = (int) floor(powf(E, max(db - d->smooth_distance, 0))); + int smax = min((int) ceil(powf(E, db + d->smooth_distance)), sz - 1); + int count = 0; + for (int s = smin; s <= smax; ++s) { + if (b[s]) { + avg += b[s] /* / abs(powf(10, db + (t - s))) */; + count++; + } + } + avg /= count; + b[t] = avg; + } +} + void transform_gravity(struct gl_data* d, void** udata, void* data) { struct gl_sampler_data* s = (struct gl_sampler_data*) data; float* b = s->buf; @@ -574,7 +627,8 @@ static struct gl_transform transform_functions[] = { { .name = "fft", .type = BIND_SAMPLER1D, .apply = transform_fft }, { .name = "wrange", .type = BIND_SAMPLER1D, .apply = transform_wrange }, { .name = "avg", .type = BIND_SAMPLER1D, .apply = transform_average }, - { .name = "gravity", .type = BIND_SAMPLER1D, .apply = transform_gravity } + { .name = "gravity", .type = BIND_SAMPLER1D, .apply = transform_gravity }, + { .name = "smooth", .type = BIND_SAMPLER1D, .apply = transform_smooth } }; static struct gl_bind_src* lookup_bind_src(const char* str) { @@ -601,21 +655,23 @@ struct renderer* rd_new(const char** paths, const char* entry, const char* force struct gl_data* gl = r->gl; *gl = (struct gl_data) { - .stages = NULL, - .rate = 0, - .tcounter = 0.0D, - .fcounter = 0, - .ucounter = 0, - .kcounter = 0, - .fr = 1.0F, - .ur = 1.0F, - .print_fps = true, - .bufscale = 1, - .avg_frames = 4, - .avg_window = true, - .gravity_step = 0.1, - .interpolate = true, - .force_geometry = false + .stages = NULL, + .rate = 0, + .tcounter = 0.0D, + .fcounter = 0, + .ucounter = 0, + .kcounter = 0, + .fr = 1.0F, + .ur = 1.0F, + .print_fps = true, + .bufscale = 1, + .avg_frames = 4, + .avg_window = true, + .gravity_step = 0.1, + .interpolate = true, + .force_geometry = false, + .smooth_distance = 0.01, + .smooth_ratio = 4 }; #ifdef GLAD_DEBUG @@ -747,6 +803,10 @@ struct renderer* rd_new(const char** paths, const char* entry, const char* force .handler = RHANDLER(name, args, { gl->avg_window = *(bool*) args[0]; }) }, { .name = "setgravitystep", .fmt = "f", .handler = RHANDLER(name, args, { gl->gravity_step = *(float*) args[0]; }) }, + { .name = "setsmooth", .fmt = "f", + .handler = RHANDLER(name, args, { gl->smooth_distance = *(float*) args[0]; }) }, + { .name = "setsmoothratio", .fmt = "f", + .handler = RHANDLER(name, args, { gl->smooth_ratio = *(float*) args[0]; }) }, { .name = "setinterpolate", .fmt = "b", .handler = RHANDLER(name, args, { gl->interpolate = *(bool*) args[0]; }) }, { diff --git a/shaders/graph.glsl b/shaders/graph.glsl index 08cbae6..3fdab81 100644 --- a/shaders/graph.glsl +++ b/shaders/graph.glsl @@ -1,25 +1,14 @@ -/* Distance (in pixels) for each fragment to sample the audio data */ -#define SAMPLE_RANGE 0.2 - -/* Amount of samples for each fragment, using the above range for each sample */ -#define SAMPLE_AMT 22 - -/* Inverse horizontal scale, larger means less higher frequencies displayed */ -#define WSCALE 11 - /* Vertical scale, larger values will amplify output */ -#define VSCALE 2800 +#define VSCALE 450 +/* Rendering direction, either -1 (outwards) or 1 (inwards). */ +#define DIRECTION 1 +/* Smoothing factor, in normalized width */ +#define SMOOTH 0.008 -/* Whether to apply a window function to samples or not (0 or 1). Slightly - slower but removes some jagged results. Has a side effect of reducing the - graph magnitude, so you should increase the `VSCALE` macro to compensate. */ -#define WINDOW_SAMPLES 1 - -/* Rendering direction, either -1 (inwards) or 1 (outwards). */ -#define DIRECTION -1 - -/* Graph color logic. The shader uses the `COLOR` macro definition for output. */ +/* The `RCOL_OFF`, `LCOL_OFF` AND `LSTEP` definitions are used to calculate + the `COLOR` macro definition for output. You can remove all these values + any simply define the `COLOR` macro yourself. */ /* right color offset */ #define RCOL_OFF (gl_FragCoord.x / 3000) @@ -29,6 +18,5 @@ #define LSTEP (gl_FragCoord.y / 170) /* actual color definition */ #define COLOR vec4((0.3 + RCOL_OFF) + LSTEP, 0.6 - LSTEP, (0.3 + LCOL_OFF) + LSTEP, 1) - /* outline color */ #define OUTLINE vec4(0.15, 0.15, 0.15, 1) diff --git a/shaders/graph/1.frag b/shaders/graph/1.frag index cd4012c..a19f21a 100644 --- a/shaders/graph/1.frag +++ b/shaders/graph/1.frag @@ -69,6 +69,7 @@ uniform sampler1D audio_r; out vec4 fragment; #include "../graph.glsl" +#include "../util/smooth.glsl" /* distance from center */ #define CDIST (abs((screen.x / 2) - gl_FragCoord.x) / screen.x) @@ -79,36 +80,23 @@ out vec4 fragment; #define LEFT_IDX (gl_FragCoord.x) #define RIGHT_IDX (-gl_FragCoord.x + screen.x) /* distance from base frequencies */ -#define BDIST CDIST +#define BDIST FDIST /* distance from high frequencies */ -#define HDIST FDIST +#define HDIST CDIST #else #define LEFT_IDX (half_w - gl_FragCoord.x) #define RIGHT_IDX (gl_FragCoord.x - half_w) -#define BDIST FDIST -#define HDIST CDIST +#define BDIST CDIST +#define HDIST FDIST #endif #define TWOPI 6.28318530718 -#define window(t, sz) (0.53836 - (0.46164 * cos(TWOPI * t / (sz - 1)))) float half_w; -void render_side(sampler1D tex, float idx) { - float s = 0; - int t; - /* perform samples */ - for (t = -SAMPLE_AMT; t <= SAMPLE_AMT; ++t) { - #if WINDOW_SAMPLES != 0 - #define WFACTOR window(t + SAMPLE_AMT, float((SAMPLE_AMT * 2) + 1)) - #else - #define WFACTOR int(1) - #endif - s += (texture(tex, log((idx + (t * SAMPLE_RANGE)) / half_w) / WSCALE).r) * WFACTOR; - #undef WFACTOR - } - /* compute average on samples */ - s /= float(SAMPLE_AMT * 2) + 1; +void render_side(in sampler1D tex, float idx) { + highp float pixel = 1.0F / float(screen.x); + float s = smooth_audio_adj(tex, audio_sz, idx / half_w, SMOOTH, pixel); /* scale the data upwards so we can see it */ s *= VSCALE; /* clamp far ends of the screen down to make the ends of the graph smoother */ diff --git a/shaders/radial.glsl b/shaders/radial.glsl index 80c4813..e701b0b 100644 --- a/shaders/radial.glsl +++ b/shaders/radial.glsl @@ -13,9 +13,13 @@ #define BAR_OUTLINE OUTLINE /* outline width (in pixels, set to 0 to disable outline drawing) */ #define BAR_OUTLINE_WIDTH 0 -/* Inverse horizontal scale, larger means less higher frequencies displayed */ -#define WSCALE 11 /* Amplify magnitude of the results each bar displays */ -#define AMPLIFY 200 +#define AMPLIFY 300 /* Bar color */ #define COLOR (vec4(0.8, 0.2, 0.2, 1) * ((d / 40) + 1)) +/* Angle (in radians) for how much to rotate the visualizer */ +#define ROTATE (PI / 2) +/* Whether to switch left/right audio buffers */ +#define INVERT 0 +/* Smoothing factor, in normalized width */ +#define SMOOTH 0.025 diff --git a/shaders/radial/1.frag b/shaders/radial/1.frag index 5df7034..b5cd6b1 100644 --- a/shaders/radial/1.frag +++ b/shaders/radial/1.frag @@ -12,9 +12,13 @@ uniform int audio_sz; #request setgravitystep 5.2 +#request setsmooth 0.0025 +#request setsmoothratio 1 + #request uniform "audio_l" audio_l #request transform audio_l "window" #request transform audio_l "fft" +// #request transform audio_l "smooth" #request transform audio_l "gravity" #request transform audio_l "avg" uniform sampler1D audio_l; @@ -22,6 +26,7 @@ uniform sampler1D audio_l; #request uniform "audio_r" audio_r #request transform audio_r "window" #request transform audio_r "fft" +// #request transform audio_r "smooth" #request transform audio_r "gravity" #request transform audio_r "avg" uniform sampler1D audio_r; @@ -29,6 +34,7 @@ uniform sampler1D audio_r; out vec4 fragment; #include "../radial.glsl" +#include "../util/smooth.glsl" #define TWOPI 6.28318530718 #define PI 3.14159265359 @@ -36,21 +42,27 @@ out vec4 fragment; void main() { float /* translate (x, y) to use (0, 0) as the center of the screen */ dx = gl_FragCoord.x - (screen.x / 2), - dy = (screen.y / 2) - gl_FragCoord.y; + dy = gl_FragCoord.y - (screen.y / 2); float theta = atan(dy, dx); /* fragment angle with the center of the screen as the origin */ float d = sqrt((dx * dx) + (dy * dy)); /* distance */ if (d > C_RADIUS - (float(C_LINE) / 2F) && d < C_RADIUS + (float(C_LINE) / 2F)) { fragment = OUTLINE; return; } else if (d > C_RADIUS) { - float section = (TWOPI / NBARS); /* range (radians) for each bar */ - float m = mod(theta, section); /* position in section (radians) */ - float center = ((TWOPI / NBARS) / 2F); /* center line angle */ - float ym = d * sin(center - m); /* distance from center line (cartesian coords) */ - if (abs(ym) < BAR_WIDTH / 2) { /* if within width, draw audio */ - /* texture lookup */ - float v = texture(theta > 0 ? audio_l : audio_r, - log(int(abs(theta) / section) / float(NBARS / 2)) / WSCALE).r * AMPLIFY; + const float section = (TWOPI / NBARS); /* range (radians) for each bar */ + const float center = ((TWOPI / NBARS) / 2F); /* center line angle */ + float m = mod(theta, section); /* position in section (radians) */ + float ym = d * sin(center - m); /* distance from center line (cartesian coords) */ + if (abs(ym) < BAR_WIDTH / 2) { /* if within width, draw audio */ + float idx = theta + ROTATE; /* position (radians) in texture */ + float dir = mod(abs(idx), TWOPI); /* absolute position, [0, 2pi) */ + if (dir > PI) + idx = -sign(idx) * (TWOPI - dir); /* Re-correct position values to [-pi, pi) */ + if (INVERT > 0) + idx = -idx; /* Invert if needed */ + float pos = int(abs(idx) / section) / float(NBARS / 2); + float v = smooth_audio(idx > 0 ? audio_l : audio_r, audio_sz, pos, SMOOTH) * AMPLIFY * (1 + pos); + d -= C_RADIUS + (float(C_LINE) / 2F); /* offset to fragment distance from inner circle */ if (d <= v - BAR_OUTLINE_WIDTH) { #if BAR_OUTLINE_WIDTH > 0 diff --git a/shaders/smooth_parameters.glsl b/shaders/smooth_parameters.glsl new file mode 100644 index 0000000..155947b --- /dev/null +++ b/shaders/smooth_parameters.glsl @@ -0,0 +1,21 @@ + +/* Settings for smoothing functions commonly used to display FFT output */ + +/* The type of formula to use for weighting values when smoothing. + Possible values: + + - circular heavily rounded points + - sinusoidal rounded at both low and high weighted values + like a sine wave + - linear not rounded at all, just use linear distance + */ +#define ROUND_FORMULA circular + +/* Factor used to scale frequencies. Lower values allows lower + frequencies to occupy more space. */ +#define SAMPLE_SCALE 8 + +/* The frequency range to sample. 1.0 would be the entire FFT output, + and lower values reduce the displayed frequencies in a log-like + scale. */ +#define SAMPLE_RANGE 0.9 diff --git a/shaders/util/smooth.glsl b/shaders/util/smooth.glsl new file mode 100644 index 0000000..a0a911b --- /dev/null +++ b/shaders/util/smooth.glsl @@ -0,0 +1,64 @@ + +#ifndef _SMOOTH_GLSL /* include gaurd */ +#define _SMOOTH_GLSL + +#ifndef TWOPI +#define TWOPI 6.28318530718 +#endif + +#ifndef PI +#define PI 3.14159265359 +#endif + +#include "../smooth_parameters.glsl" + +/* window value t that resides in range [0, sz)*/ +#define window(t, sz) (0.53836 - (0.46164 * cos(TWOPI * t / (sz - 1)))) +/* this does nothing, but we keep it as an option for config */ +#define linear(x) (x) +/* take value x that scales linearly between [0, 1) and return its sinusoidal curve */ +#define sinusoidal(x) ((0.5 * sin((PI * (x)) - (PI / 2))) + 0.5) +/* take value x that scales linearly between [0, 1) and return its circlar curve */ +#define circular(x) sqrt(1 - (((x) - 1) * ((x) - 1))) + +float scale_audio(float idx) { + return -log((-(SAMPLE_RANGE) * idx) + 1) / (SAMPLE_SCALE); +} + +float iscale_audio(float idx) { + return -log((SAMPLE_RANGE) * idx) / (SAMPLE_SCALE); +} + +float smooth_audio(in sampler1D tex, int tex_sz, highp float idx, float r) { + float + smin = scale_audio(clamp(idx - r, 0, 1)) * tex_sz, + smax = scale_audio(clamp(idx + r, 0, 1)) * tex_sz, + avg = 0, s, weight = 0; + float m = ((smax - smin) / 2.0F); + float rm = smin + m; /* middle */ + for (s = smin; s <= smax; s += 1.0F) { + float w = ROUND_FORMULA(clamp((m - abs(rm - s)) / m, 0, 1)); + weight += w; + avg += texture(tex, float(s) / float(tex_sz)).r * w; + } + avg /= weight; + avg *= max(idx + 0.7, 1); + return avg; +} + +/* Applies the audio smooth sampling function three times to the adjacent values */ +float smooth_audio_adj(in sampler1D tex, int tex_sz, highp float idx, float r, highp float pixel) { + float + al = smooth_audio(tex, tex_sz, max(idx - pixel, 0.0F), r), + am = smooth_audio(tex, tex_sz, idx, r), + ar = smooth_audio(tex, tex_sz, min(idx + pixel, 1.0F), r); + return (al + am + ar) / 3.0F; +} + +#ifdef TWOPI +#undef TWOPI +#endif +#ifdef PI +#undef PI +#endif +#endif /* _SMOOTH_GLSL */