diff --git a/render.c b/render.c index aa36523..cb31bae 100644 --- a/render.c +++ b/render.c @@ -350,10 +350,11 @@ struct gl_data { int lww, lwh; /* last window height */ int rate; /* framerate */ double tcounter; - int fcounter, ucounter; - bool print_fps, avg_window; + int fcounter, ucounter, kcounter; + bool print_fps, avg_window, interpolate; void** t_data; - float gravity_step, target_spu; + float gravity_step, target_spu, fr, ur; + float* interpolate_buf[4]; }; #ifdef GLAD_DEBUG @@ -402,11 +403,13 @@ void transform_gravity(struct gl_data* d, void** udata, void* data) { float* applied; }* u; ALLOC_ONCE(u, udata, { .applied = calloc(sz, sizeof(float)) }); + + float g = d->gravity_step * (1.0F / d->ur); for (t = 0; t < sz; ++t) { if (b[t] >= u->applied[t]) { - u->applied[t] = b[t] - d->gravity_step; - } else u->applied[t] -= d->gravity_step; + u->applied[t] = b[t] - g; + } else u->applied[t] -= g; b[t] = u->applied[t]; } } @@ -563,11 +566,15 @@ struct renderer* rd_new(int x, int y, int w, int h, const char* data) { .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 + .gravity_step = 0.1, + .interpolate = true, }; #ifdef GLAD_DEBUG @@ -679,6 +686,8 @@ struct renderer* rd_new(int x, int y, int w, int h, const char* data) { .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 = "setinterpolate", .fmt = "b", + .handler = RHANDLER(name, args, { gl->interpolate = *(bool*) args[0]; }) }, { .name = "transform", .fmt = "ss", .handler = RHANDLER(name, args, { @@ -898,12 +907,20 @@ struct renderer* rd_new(int x, int y, int w, int h, const char* data) { /* target seconds per update */ gl->target_spu = (float) (r->samplesize_request / 4) / (float) r->rate_request; - /* multiply gravity step by time occurred for each step */ - gl->gravity_step *= gl->target_spu; gl->audio_tex_r = create_1d_tex(); gl->audio_tex_l = create_1d_tex(); + if (gl->interpolate) { + size_t isz = (r->bufsize_request / gl->bufscale) * sizeof(float); + float* ibuf = malloc(isz * 4); + gl->interpolate_buf[0] = &ibuf[isz * 0]; + gl->interpolate_buf[1] = &ibuf[isz * 1]; + gl->interpolate_buf[2] = &ibuf[isz * 2]; + gl->interpolate_buf[3] = &ibuf[isz * 3]; + } + + { gl->t_data = malloc(sizeof(void*) * t_count); size_t t; @@ -925,16 +942,20 @@ void rd_time(struct renderer* r) { glfwSetTime(0.0D); /* reset time for measuring this frame */ } +#define IB_START_LEFT 0 +#define IB_END_LEFT 1 +#define IB_START_RIGHT 2 +#define IB_END_RIGHT 3 + void rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modified) { struct gl_data* gl = r->gl; - size_t t, a; + size_t t, a, fbsz = bsz * sizeof(float); r->alive = !glfwWindowShouldClose(gl->w); if (!r->alive) return; /* Perform buffer scaling */ - size_t nsz = gl->bufscale > 1 ? (bsz / gl->bufscale) : 0; float nlb[nsz], nrb[nsz]; if (gl->bufscale > 1) { @@ -958,6 +979,24 @@ void rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi lb = nlb; rb = nrb; bsz = nsz; + fbsz = bsz * sizeof(float); + } + + /* Linear interpolation */ + float ilb[gl->interpolate ? bsz : 0], irb[gl->interpolate ? bsz : 0]; + if (gl->interpolate) { + for (t = 0; t < bsz; ++t) { + /* Obtain start/end values at this index for left & right buffers */ + float + ilbs = gl->interpolate_buf[IB_START_LEFT ][t], + ilbe = gl->interpolate_buf[IB_END_LEFT ][t], + irbs = gl->interpolate_buf[IB_START_RIGHT][t], + irbe = gl->interpolate_buf[IB_END_RIGHT ][t], + mod = (gl->ur / gl->fr) * gl->kcounter; /* modifier for this frame */ + if (mod > 1.0F) mod = 1.0F; + ilb[t] = ilbs + ((ilbe - ilbs) * mod); + irb[t] = irbs + ((irbe - irbs) * mod); + } } /* Resize screen textures if needed */ @@ -1003,7 +1042,7 @@ void rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi struct gl_bind* bind = ¤t->binds[b]; /* Handle transformations and bindings for 1D samplers */ - void handle_1d_tex(GLuint tex, float* buf, size_t sz, int offset) { + void handle_1d_tex(GLuint tex, float* buf, float* ubuf, size_t sz, int offset) { /* Only apply transformations if the buffers we were given are newly copied from PA */ @@ -1022,7 +1061,7 @@ void rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi if (!needed[offset]) { glActiveTexture(GL_TEXTURE0 + offset); - update_1d_tex(tex, sz, buf); + update_1d_tex(tex, sz, gl->interpolate ? (ubuf ? ubuf : buf) : buf); glBindTexture(GL_TEXTURE_1D, tex); needed[offset] = true; } @@ -1044,8 +1083,8 @@ void rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi } glUniform1i(bind->uniform, 0); break; - case SRC_AUDIO_L: handle_1d_tex(gl->audio_tex_l, lb, bsz, 1); break; - case SRC_AUDIO_R: handle_1d_tex(gl->audio_tex_r, rb, bsz, 2); break; + case SRC_AUDIO_L: handle_1d_tex(gl->audio_tex_l, lb, ilb, bsz, 1); break; + case SRC_AUDIO_R: handle_1d_tex(gl->audio_tex_r, rb, irb, bsz, 2); break; case SRC_AUDIO_SZ: glUniform1i(bind->uniform, bsz); break; case SRC_SCREEN: glUniform2i(bind->uniform, (GLint) ww, (GLint) wh); break; } @@ -1061,6 +1100,14 @@ void rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi prev = current; } + /* Push and copy buffer if we need to interpolate from it later */ + if (gl->interpolate && modified) { + memcpy(gl->interpolate_buf[IB_START_LEFT ], gl->interpolate_buf[IB_END_LEFT ], fbsz); + memcpy(gl->interpolate_buf[IB_START_RIGHT], gl->interpolate_buf[IB_END_RIGHT], fbsz); + memcpy(gl->interpolate_buf[IB_END_LEFT ], lb, fbsz); + memcpy(gl->interpolate_buf[IB_END_RIGHT ], rb, fbsz); + } + /* Swap buffers, handle events, etc. (vsync is potentially included here, too) */ glfwSwapBuffers(gl->w); glfwPollEvents(); @@ -1081,20 +1128,23 @@ void rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi } } - /* print FPS counter (if needed) */ - if (gl->print_fps) { - ++gl->fcounter; - if (modified) - ++gl->ucounter; - gl->tcounter += duration; - if (gl->tcounter >= 1.0D) { + /* Handle counters and print FPS counter (if needed) */ + + ++gl->fcounter; /* increment frame counter */ + if (modified) { /* if this is an update/key frame */ + ++gl->ucounter; /* increment update frame counter */ + gl->kcounter = 0; /* reset keyframe counter (for interpolation) */ + } else ++gl->kcounter; /* increment keyframe counter otherwise */ + gl->tcounter += duration; /* timer counter, measuring when a >1s has occurred */ + if (gl->tcounter >= 1.0D) { + gl->fr = gl->fcounter / gl->tcounter; /* frame rate (FPS) */ + gl->ur = gl->ucounter / gl->tcounter; /* update rate (UPS) */ + if (gl->print_fps) /* print FPS */ printf("FPS: %.2f, UPS: %.2f\n", - ((double) gl->fcounter / gl->tcounter), - ((double) gl->ucounter / gl->tcounter)); - gl->tcounter = 0; - gl->fcounter = 0; - gl->ucounter = 0; - } + (double) gl->fr, (double) gl->ur); + gl->tcounter = 0; /* reset timer */ + gl->fcounter = 0; /* reset frame counter */ + gl->ucounter = 0; /* reset update counter */ } } diff --git a/shaders/rc.glsl b/shaders/rc.glsl index 1d35aa0..c282e6d 100644 --- a/shaders/rc.glsl +++ b/shaders/rc.glsl @@ -29,6 +29,18 @@ amount of frames. */ #request setswap 1 +/* Linear interpolation for audio data frames. Drastically + improves smoothness with configurations that yield low UPS + (`setsamplerate` and `setsamplesize`), or monitors that have + high refresh rates. + + This feature itself, however, will effect performance as it + will have to interpolate data every frame on the CPU. + + This will delay data output by two update frames, so it can + desync audio with visual effects on low UPS configs. */ +#request setinterpolate true + /* Frame limiter, set to the frames per second (FPS) desired or simple set to zero (or lower) to disable the frame limiter. */ #request setframerate 0 @@ -41,6 +53,24 @@ perform over time) */ #request setprintframes true +/* PulseAudio sample buffer size. Lower values result in more + frequent audio updates (also depends on sampling rate), but + will also require all transformations to be applied much + more frequently (CPU intensive). + + High (>2048, with 22050 kHz) values will decrease accuracy + (as some signals can be missed by transformations like FFT) + + The following settings (@22050 kHz) produce the listed rates: + + Sample UPS Description + - 2048 -> 43.0 (low accuracy, cheap), use with ~60 FPS + - 1024 -> 86.1 (high accuracy, expensive), use with 120+ FPS + - 512 -> 172.3 (extreme accuracy, very expensive), use only + for graphing accurate spectrum data with + custom modules. */ +#request setsamplesize 1024 + /* Audio buffer size to be used for processing and shaders. Increasing this value can have the effect of adding 'gravity' to FFT output, as the audio signal will remain in the buffer @@ -50,7 +80,17 @@ quality for some modules. */ #request setbufsize 4096 -/* Scale down the audio buffer before any operations are +/* PulseAudio sample rate. Lower values can add 'gravity' to + FFT output, but can also reduce accuracy. Most hardware + samples at 44100Hz. + + Lower sample rates also can make output more choppy, when + not using interpolation. It's generally OK to leave this + value unless you have a strange PulseAudio configuration. */ +#request setsamplerate 22050 + +/* ** DEPRECATED ** + Scale down the audio buffer before any operations are performed on the data. Higher values are faster. This value can affect the output of various transformations, @@ -59,20 +99,6 @@ `setsamplesize` to improve performance or accuracy instead. */ #request setbufscale 1 -/* PulseAudio sample rate. Lower values can add 'gravity' to - FFT output, but can also reduce accuracy (especially for - higher frequencies). Most hardware samples at 44100Hz. - - Lower sample rates also can make output more choppy, when - not using smoothing algorithms. */ -#request setsamplerate 22000 - -/* PulseAudio sample buffer size. Lower values result in more - frequent audio updates (also depends on sampling rate), but - will also require all transformations to be applied much - more frequently (slower) */ -#request setsamplesize 1024 - /* OpenGL context and GLSL shader versions, do not change unless you _absolutely_ know what you are doing. */ #request setversion 3 3