From b2fa322e3082472771d87c8b97fdb0fb796fff01 Mon Sep 17 00:00:00 2001 From: tyrolyean Date: Wed, 20 Jul 2022 23:43:00 +0200 Subject: [PATCH 1/3] Set c standard to c11 Signed-off-by: tyrolyean --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 705ac2c..0184d78 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CFLAGS = -ansi -pedantic -Wall -Wextra -O2 -Ofast -D_DEFAULT_SOURCE +CFLAGS = -std=c11 -pedantic -Wall -Wextra -O2 -Ofast -D_DEFAULT_SOURCE LDFLAGS = -lX11 -lGL -lGLEW -lfftw3 -lm -lrt shadermeh: shadermeh.o window.o shader.o -- 2.39.2 From 664f8d0305afbea3cddf0383d70220b2f7027537 Mon Sep 17 00:00:00 2001 From: tyrolyean Date: Wed, 20 Jul 2022 23:43:41 +0200 Subject: [PATCH 2/3] Make fft work, add sample rate to stabilize waveform This adds a sliding window buffer approach to the audio sample data as well as force the user to set the sampling rate of the input data. This was needed to stabilize fft output to a usable degree. Also the fft output is now in db, which makes it a lot better to look at. Signed-off-by: tyrolyean --- shadermeh.c | 115 ++++++++++++++++++++++++++++++++++++++++------------ shadermeh.h | 7 +++- 2 files changed, 95 insertions(+), 27 deletions(-) diff --git a/shadermeh.c b/shadermeh.c index f29af1d..fcfd6d6 100644 --- a/shadermeh.c +++ b/shadermeh.c @@ -21,47 +21,101 @@ static GLfloat vertex_buffer[] = { }; static GLubyte audio_buffer[AUDIO_SAMPLES * AUDIO_CHANNELS]; -static float audio_sample_data[AUDIO_SAMPLES]; -static fftw_complex fftw_in[AUDIO_SAMPLES]; -static fftw_complex fftw_out[AUDIO_SAMPLES]; +size_t sample_pointer = 0; +size_t sample_data_pointer = 0; +size_t sample_rate = 0; +static float *audio_sample_data; +static float *audio_receive_data; +static double *fftw_in; +static fftw_complex *fftw_out; static fftw_plan plan; -static int try_fetch_audio(void) +static int try_fetch_audio(float iTimeDelta) { - size_t i, count = 0; - int ret; + /* To avoid generating stale images, we keep our own sample buffer, + * which is then used to move a sliding window of data for the fft and + * wave samples. We need to do this, as otherwise we would set an upper + * limit of fps (20 at 4800kHz sample rate), which would not be good. + * The size of the window is set in the header file. The with our + * approach is that the buffer allows for drifting to occur within the + * buffer limits. If you buffer is 3s long the delay can grow to 3s. + * Choose your buffer size wisely for your application. + */ + size_t i; + ssize_t ret = 0; + memset(audio_receive_data, 0, AUDIO_BUFFER_SIZE * + sizeof(*audio_receive_data)); + sample_pointer += (sample_rate * iTimeDelta); for (;;) { - ret = read(STDIN_FILENO, - (char *)audio_sample_data + count, - sizeof(audio_sample_data) - count); + ret = read(STDIN_FILENO, (char *)audio_receive_data, + sizeof(*audio_receive_data)*AUDIO_BUFFER_SIZE); if (ret < 0) { if (errno == EINTR) continue; - if (errno == EAGAIN) + if (errno == EAGAIN || errno == EWOULDBLOCK) break; perror("stdin"); return -1; } - if (ret == 0) + if (ret == 0 || ret % sizeof(float) != 0){ break; + } - count += ret; + ret /= 4; + if((ret + sample_pointer) > AUDIO_BUFFER_SIZE){ + /* Not enough storage space to store all new audio data, + * will override not output data with new one */ + memset(audio_sample_data, 0, + AUDIO_BUFFER_SIZE * sizeof(*audio_sample_data)); + memcpy(audio_sample_data, audio_receive_data, + ret * sizeof(*audio_sample_data)); + sample_pointer = 0; + sample_data_pointer = ret; + }else{ + memmove(audio_sample_data, + &audio_sample_data[sample_pointer], + (AUDIO_BUFFER_SIZE - sample_pointer)* + sizeof(*audio_sample_data)); + if(sample_data_pointer <= sample_pointer){ + sample_data_pointer = 0; + + }else{ + sample_data_pointer -= sample_pointer; + + } + sample_pointer = 0; + size_t len = ret; + if((ret + sample_data_pointer) >= AUDIO_BUFFER_SIZE){ + len = AUDIO_BUFFER_SIZE - sample_data_pointer; + } + memcpy(&audio_sample_data[sample_data_pointer], + audio_receive_data, len * sizeof(float)); + sample_data_pointer += len; + break; + } + + } + if((sample_pointer+AUDIO_FFT_SIZE) >= sample_data_pointer){ + fprintf(stderr, "shadermeh input to slow %zu > %zu! wrapping around!\n", sample_pointer+AUDIO_FFT_SIZE, sample_data_pointer); + sample_pointer = 0; } - for (i = 0; i < AUDIO_SAMPLES; ++i) - fftw_in[i][0] = audio_sample_data[i]; + memset(fftw_in, 0, sizeof(*fftw_in) * AUDIO_BUFFER_SIZE); + memset(fftw_out, 0, sizeof(*fftw_out) * AUDIO_BUFFER_SIZE); + + for (i = 0; i < AUDIO_FFT_SIZE; ++i) + fftw_in[i] = audio_sample_data[sample_pointer+i]; fftw_execute(plan); for (i = 0; i < AUDIO_SAMPLES; ++i) { - float x = fftw_out[i][0], y = fftw_out[i][1]; - float a = sqrt(x * x + y * y); + float a = cabs(fftw_out[i]); - audio_buffer[i + AUDIO_SAMPLES] = audio_sample_data[i] * 127.0f + 127.0f; - audio_buffer[i] = 127.0f + a * 127.0f; + audio_buffer[i + AUDIO_SAMPLES] = audio_sample_data[sample_pointer+i] * 127.0f + 127.0f; + audio_buffer[i] = log(fabsf(a)+1) * 50; } return 0; @@ -159,11 +213,11 @@ static const struct option long_opts[] = { { "height", required_argument, NULL, 'h' }, { "shader", required_argument, NULL, 's' }, { "to-stdout", no_argument, NULL, 'S' }, - { "stdin-audio", no_argument, NULL, 'a' }, + { "stdin-audio", required_argument, NULL, 'a' }, { NULL, 0, NULL, 0 }, }; -static const char *short_opts = "w:h:s:Sa"; +static const char *short_opts = "w:a:h:s:S"; static const char *usage_str = "shadermeh OPTIONS...\n" @@ -174,7 +228,7 @@ static const char *usage_str = " --height, -h \n" "\n" " --to-stdout, -S Poop raw RGB24 frames to stdout (blocking)\n" -" --stdin-audio, -a Read raw PCM audio from stdin (non-blocking)\n" +" --stdin-audio, -a Read raw PCM audio from stdin (non-blocking)\n" "\n" " --shader, -s \n" "\n"; @@ -188,7 +242,7 @@ int main(int argc, char **argv) void *fb32 = NULL, *fb24 = NULL; const char *shader_file = NULL; GLint major, minor, prog; - float iTime, iTimeDelta; + float iTime, iTimeDelta = 0; bool have_audio = false; bool to_stdout = false; window *wnd; @@ -218,6 +272,11 @@ int main(int argc, char **argv) break; case 'a': have_audio = true; + sample_rate = strtol(optarg, NULL, 10); + audio_sample_data = malloc(AUDIO_BUFFER_SIZE * + sizeof(float)); + audio_receive_data = malloc(AUDIO_BUFFER_SIZE * + sizeof(float)); break; default: fputs(usage_str, stderr); @@ -341,8 +400,12 @@ int main(int argc, char **argv) glBindSampler(0, sampler_sound); if (have_audio) { - plan = fftw_plan_dft_1d(AUDIO_SAMPLES, fftw_in, fftw_out, - FFTW_FORWARD, FFTW_ESTIMATE); + fftw_in = fftw_alloc_real(AUDIO_BUFFER_SIZE); + fftw_out = fftw_alloc_complex(AUDIO_BUFFER_SIZE); + if(fftw_in == NULL || fftw_out == NULL) + goto fail_vao; + plan = fftw_plan_dft_r2c_1d(AUDIO_BUFFER_SIZE, fftw_in, fftw_out, + FFTW_MEASURE); } /******************** framebuffer object ********************/ @@ -377,7 +440,7 @@ int main(int argc, char **argv) glClear(GL_COLOR_BUFFER_BIT); if (have_audio) { - if (try_fetch_audio()) + if (try_fetch_audio(iTimeDelta)) break; glBindTexture(GL_TEXTURE_2D, sound_tex); @@ -451,6 +514,8 @@ fail_vao: window_make_current(NULL); free(fb32); free(fb24); + fftw_free(fftw_in); + fftw_free(fftw_out); window_destroy(wnd); return EXIT_SUCCESS; } diff --git a/shadermeh.h b/shadermeh.h index 1782810..904d5ad 100644 --- a/shadermeh.h +++ b/shadermeh.h @@ -27,10 +27,13 @@ #include #include -#include #include +#include +#include -#define AUDIO_SAMPLES (512) +#define AUDIO_SAMPLES (4096) +#define AUDIO_BUFFER_SIZE (sample_rate * 3) +#define AUDIO_FFT_SIZE (AUDIO_SAMPLES * 2) #define AUDIO_CHANNELS (2) typedef struct { -- 2.39.2 From 12396a8bb0505a3cddeb7bad8f134e0cc2d3a31b Mon Sep 17 00:00:00 2001 From: tyrolyean Date: Wed, 20 Jul 2022 23:45:51 +0200 Subject: [PATCH 3/3] Add more audio shaders Signed-off-by: tyrolyean --- shaders/eclipse.frag | 84 ++++++++++++++++++++++ shaders/fft.frag | 65 +++++++++++++++++ shaders/grid.frag | 158 ++++++++++++++++++++++++++++++++++++++++++ shaders/spectrum.frag | 38 ++++++++++ 4 files changed, 345 insertions(+) create mode 100644 shaders/eclipse.frag create mode 100644 shaders/fft.frag create mode 100644 shaders/grid.frag create mode 100644 shaders/spectrum.frag diff --git a/shaders/eclipse.frag b/shaders/eclipse.frag new file mode 100644 index 0000000..d599602 --- /dev/null +++ b/shaders/eclipse.frag @@ -0,0 +1,84 @@ +// credit: https://www.shadertoy.com/view/4tGXzt + +#define BEATMOVE 1 + +const float FREQ_RANGE = 64.0; +const float PI = 3.1415; +const float RADIUS = 0.6; +const float BRIGHTNESS = 0.2; +const float SPEED = 0.5; + +//convert HSV to RGB +vec3 hsv2rgb(vec3 c){ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +float luma(vec3 color) { + return dot(color, vec3(0.299, 0.587, 0.114)); +} + +float getfrequency(float x) { + return texture(iChannel0, vec2(floor(x * FREQ_RANGE + 1.0) / FREQ_RANGE, 0.25)).x + 0.06; +} + +float getfrequency_smooth(float x) { + float index = floor(x * FREQ_RANGE) / FREQ_RANGE; + float next = floor(x * FREQ_RANGE + 1.0) / FREQ_RANGE; + return mix(getfrequency(index), getfrequency(next), smoothstep(0.0, 1.0, fract(x * FREQ_RANGE))); +} + +float getfrequency_blend(float x) { + return mix(getfrequency(x), getfrequency_smooth(x), 0.5); +} + +vec3 doHalo(vec2 fragment, float radius) { + float dist = length(fragment); + float ring = 1.0 / abs(dist - radius); + + float b = dist < radius ? BRIGHTNESS * 0.3 : BRIGHTNESS; + + vec3 col = vec3(0.0); + + float angle = atan(fragment.x, fragment.y); + col += hsv2rgb( vec3( ( angle + iTime * 0.25 ) / (PI * 2.0), 1.0, 1.0 ) ) * ring * b; + + float frequency = max(getfrequency_blend(abs(angle / PI)) - 0.02, 0.0); + col *= frequency; + + // Black halo + col *= smoothstep(radius * 0.5, radius, dist); + + return col; +} + +vec3 doLine(vec2 fragment, float radius, float x) { + vec3 col = hsv2rgb(vec3(x * 0.23 + iTime * 0.12, 1.0, 1.0)); + + float freq = abs(fragment.x * 0.5); + + col *= (1.0 / abs(fragment.y)) * BRIGHTNESS * getfrequency(freq); + col = col * smoothstep(radius, radius * 1.8, abs(fragment.x)); + + return col; +} + + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec2 fragPos = fragCoord / iResolution.xy; + fragPos = (fragPos - 0.5) * 2.0; + fragPos.x *= iResolution.x / iResolution.y; + + vec3 color = vec3(0.0134, 0.052, 0.1); + color += doHalo(fragPos, RADIUS); + + float c = cos(iTime * SPEED); + float s = sin(iTime * SPEED); + vec2 rot = mat2(c,s,-s,c) * fragPos; + color += doLine(rot, RADIUS, rot.x); + + color += max(luma(color) - 1.0, 0.0); + + fragColor = vec4(color, 1.0); +} diff --git a/shaders/fft.frag b/shaders/fft.frag new file mode 100644 index 0000000..fd38b9d --- /dev/null +++ b/shaders/fft.frag @@ -0,0 +1,65 @@ +/* + + Linear vs Logarithmic FFT + + some good test songs: + + https://soundcloud.com/kraddy/winning + https://soundcloud.com/grey-houston/soothing-piano-melody + https://soundcloud.com/pointpoint/life-in-gr + +*/ + +//from https://stackoverflow.com/questions/35799286 +float toLog(float value, float min, float max){ + float exp = (value-min) / (max-min); + return min * pow(max/min, exp); +} + +float getLevel(float samplePos){ + // the sound texture is 512x2 + int tx = int(samplePos*512.0); + // first row is frequency data (48Khz/4 in 512 texels, meaning 23 Hz per texel) + return texelFetch( iChannel0, ivec2(tx,0), 0 ).x; +} + + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 uv = fragCoord.xy / iResolution.xy; + + float xPos; + float fft; + + if (uv.y > 0.5){ + + //linear sampling + xPos = uv.x; + fft = getLevel(xPos); + + }else{ + + //crop bottom and top of range + uv.x = mix(0.3,0.7, uv.x); + + //logarithmic sampling + xPos = toLog(uv.x, 0.01, 1.0); + + fft = getLevel(xPos); + + //boost contrast + fft = pow(fft,3.0); + + //boost gain + fft *= 1.5; + + //contrast / brightness + float contrast = 1.4; + float brightness = 0.; + fft = (fft - 0.5) * contrast + 0.5 + brightness; + + } + + fragColor = vec4(vec3(fft),1.0); + +} diff --git a/shaders/grid.frag b/shaders/grid.frag new file mode 100644 index 0000000..eb34fe3 --- /dev/null +++ b/shaders/grid.frag @@ -0,0 +1,158 @@ +//https://www.shadertoy.com/view/XlBXRh with mic not soundcloud +#define preset4 + +#ifdef preset1 + #define cells vec2(14.,14.) + #define persp 1.5 + #define height 1. + #define linewidth .5 + #define lineexp 4. + #define brightness .7 +#endif + + +#ifdef preset2 + #define cells vec2(10.,5.) + #define persp 2.5 + #define height 1. + #define linewidth 3. + #define lineexp 6. + #define brightness .4 +#endif + + +#ifdef preset3 + #define OPAQUE_MODE + #define INVERSE + #define cells vec2(16.,16.) + #define persp 1. + #define height 1.5 + #define linewidth .1 + #define lineexp .5 + #define brightness .8 +#endif + +#ifdef preset4 + #define OPAQUE_MODE + #define cells vec2(10.,10.) + #define persp 2. + #define height .75 + #define linewidth .2 + #define lineexp 1. + #define brightness 1.5 +#endif + +#ifdef preset5 + #define INVERSE + #define cells vec2(6.,25.) + #define persp 1. + #define height 2. + #define linewidth .07 + #define lineexp .3 + #define brightness .35 +#endif + + +#ifdef preset6 + #define INVERSE + #define OPAQUE_MODE + #define cells vec2(15.,15.) + #define persp 2.5 + #define height 1. + #define linewidth .05 + #define lineexp .5 + #define brightness 1. +#endif + + + + +#define hcells (cells*.5) + + +vec3 segment(vec2 p, vec3 from, vec3 to, float width, float dist) { +width=1./width; +vec2 seg=from.xy-to.xy; +float halfdist=distance(from.xy,to.xy)*.5; +float ang=atan(seg.y,seg.x); +float sine=sin(ang); +float cose=cos(ang); +p-=from.xy; +p*=mat2(cose,sine,-sine,cose); +float dx=abs(p.x+halfdist)-halfdist; +float dy=abs(p.y); +float h=1.-abs(p.x+halfdist*2.)/halfdist/2.; +float pz=-from.z-(to.z-from.z)*h; +float l=1.-clamp(max(dx,dy)*width/(pz+dist)*dist*dist,0.,.1)/.1; +l=pow(abs(l),lineexp)*(1.-pow(clamp(abs(dist-pz)*.45,0.,1.),.5))*4.; +return normalize(.25+abs(mix(from,to,h)))*l; +} + +mat3 rotmat(vec3 v, float angle) +{ + angle=radians(angle); + float c = cos(angle); + float s = sin(angle); + + return mat3(c + (1.0 - c) * v.x * v.x, (1.0 - c) * v.x * v.y - s * v.z, (1.0 - c) * v.x * v.z + s * v.y, + (1.0 - c) * v.x * v.y + s * v.z, c + (1.0 - c) * v.y * v.y, (1.0 - c) * v.y * v.z - s * v.x, + (1.0 - c) * v.x * v.z - s * v.y, (1.0 - c) * v.y * v.z + s * v.x, c + (1.0 - c) * v.z * v.z + ); +} + +float getz(vec2 xy) { +xy=xy*10.+hcells; +//float pos=length(pow(abs(xy/cells),vec2(3.)))*8.; +float pos=(xy.y*cells.x+xy.x)/(cells.x*cells.y); +float s=texture(iChannel0,vec2(.5+pos*.5,.1)).x; +return .25-pow(s,1.5)*height; +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 uv = (gl_FragCoord.xy / iResolution.xy-.5)*2.; + uv.y*=iResolution.y/iResolution.x; + mat3 camrot=rotmat(normalize(vec3(0.,0.,1.)),iTime*25.)*rotmat(normalize(vec3(1.,0.*sin(iTime*.5),0.)),60.+30.*sin(iTime*.5)); + float s=.1,maxc=0.; + vec3 p1,p2,p3; + vec3 rotv=vec3(0.,0.,1.); + float h; + vec3 col=vec3(0.); + float dist=1.2+pow(abs(sin(iTime*.3)),5.)*.5; + vec3 c=vec3(0.); + for (float y=0.; yuv.x-linewidth/4. && min(p1.x,p2.x)uv.y-linewidth/4. && min(p1.y,p2.y)uv.x-linewidth/4. && min(p1.x,p3.x)uv.y-linewidth/4. && min(p1.y,p3.y)