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 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 { 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)