Make fft work #1

Merged
goliath merged 3 commits from tyrolyean/shadermeh:master into master 2022-11-09 20:48:05 +01:00
7 changed files with 441 additions and 28 deletions

View file

@ -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 LDFLAGS = -lX11 -lGL -lGLEW -lfftw3 -lm -lrt
shadermeh: shadermeh.o window.o shader.o shadermeh: shadermeh.o window.o shader.o

View file

@ -21,47 +21,101 @@ static GLfloat vertex_buffer[] = {
}; };
static GLubyte audio_buffer[AUDIO_SAMPLES * AUDIO_CHANNELS]; static GLubyte audio_buffer[AUDIO_SAMPLES * AUDIO_CHANNELS];
static float audio_sample_data[AUDIO_SAMPLES]; size_t sample_pointer = 0;
static fftw_complex fftw_in[AUDIO_SAMPLES]; size_t sample_data_pointer = 0;
static fftw_complex fftw_out[AUDIO_SAMPLES]; 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 fftw_plan plan;
static int try_fetch_audio(void) static int try_fetch_audio(float iTimeDelta)
{ {
size_t i, count = 0; /* To avoid generating stale images, we keep our own sample buffer,
int ret; * 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 (;;) { for (;;) {
ret = read(STDIN_FILENO, ret = read(STDIN_FILENO, (char *)audio_receive_data,
(char *)audio_sample_data + count, sizeof(*audio_receive_data)*AUDIO_BUFFER_SIZE);
sizeof(audio_sample_data) - count);
if (ret < 0) { if (ret < 0) {
if (errno == EINTR) if (errno == EINTR)
continue; continue;
if (errno == EAGAIN) if (errno == EAGAIN || errno == EWOULDBLOCK)
break; break;
perror("stdin"); perror("stdin");
return -1; return -1;
} }
if (ret == 0) if (ret == 0 || ret % sizeof(float) != 0){
break; break;
count += ret;
} }
for (i = 0; i < AUDIO_SAMPLES; ++i) ret /= 4;
fftw_in[i][0] = audio_sample_data[i]; 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;
}
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); fftw_execute(plan);
for (i = 0; i < AUDIO_SAMPLES; ++i) { for (i = 0; i < AUDIO_SAMPLES; ++i) {
float x = fftw_out[i][0], y = fftw_out[i][1]; float a = cabs(fftw_out[i]);
float a = sqrt(x * x + y * y);
audio_buffer[i + AUDIO_SAMPLES] = audio_sample_data[i] * 127.0f + 127.0f; audio_buffer[i + AUDIO_SAMPLES] = audio_sample_data[sample_pointer+i] * 127.0f + 127.0f;
audio_buffer[i] = 127.0f + a * 127.0f; audio_buffer[i] = log(fabsf(a)+1) * 50;
} }
return 0; return 0;
@ -159,11 +213,11 @@ static const struct option long_opts[] = {
{ "height", required_argument, NULL, 'h' }, { "height", required_argument, NULL, 'h' },
{ "shader", required_argument, NULL, 's' }, { "shader", required_argument, NULL, 's' },
{ "to-stdout", no_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 }, { 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 = static const char *usage_str =
"shadermeh OPTIONS...\n" "shadermeh OPTIONS...\n"
@ -174,7 +228,7 @@ static const char *usage_str =
" --height, -h <pixels>\n" " --height, -h <pixels>\n"
"\n" "\n"
" --to-stdout, -S Poop raw RGB24 frames to stdout (blocking)\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 <sample rate> Read raw PCM audio from stdin (non-blocking)\n"
"\n" "\n"
" --shader, -s <shader file>\n" " --shader, -s <shader file>\n"
"\n"; "\n";
@ -188,7 +242,7 @@ int main(int argc, char **argv)
void *fb32 = NULL, *fb24 = NULL; void *fb32 = NULL, *fb24 = NULL;
const char *shader_file = NULL; const char *shader_file = NULL;
GLint major, minor, prog; GLint major, minor, prog;
float iTime, iTimeDelta; float iTime, iTimeDelta = 0;
bool have_audio = false; bool have_audio = false;
bool to_stdout = false; bool to_stdout = false;
window *wnd; window *wnd;
@ -218,6 +272,11 @@ int main(int argc, char **argv)
break; break;
case 'a': case 'a':
have_audio = true; 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; break;
default: default:
fputs(usage_str, stderr); fputs(usage_str, stderr);
@ -341,8 +400,12 @@ int main(int argc, char **argv)
glBindSampler(0, sampler_sound); glBindSampler(0, sampler_sound);
if (have_audio) { if (have_audio) {
plan = fftw_plan_dft_1d(AUDIO_SAMPLES, fftw_in, fftw_out, fftw_in = fftw_alloc_real(AUDIO_BUFFER_SIZE);
FFTW_FORWARD, FFTW_ESTIMATE); 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 ********************/ /******************** framebuffer object ********************/
@ -377,7 +440,7 @@ int main(int argc, char **argv)
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
if (have_audio) { if (have_audio) {
if (try_fetch_audio()) if (try_fetch_audio(iTimeDelta))
break; break;
glBindTexture(GL_TEXTURE_2D, sound_tex); glBindTexture(GL_TEXTURE_2D, sound_tex);
@ -451,6 +514,8 @@ fail_vao:
window_make_current(NULL); window_make_current(NULL);
free(fb32); free(fb32);
free(fb24); free(fb24);
fftw_free(fftw_in);
fftw_free(fftw_out);
window_destroy(wnd); window_destroy(wnd);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View file

@ -27,10 +27,13 @@
#include <poll.h> #include <poll.h>
#include <time.h> #include <time.h>
#include <fftw3.h>
#include <math.h> #include <math.h>
#include <complex.h>
#include <fftw3.h>
#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) #define AUDIO_CHANNELS (2)
typedef struct { typedef struct {

84
shaders/eclipse.frag Normal file
View file

@ -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);
}

65
shaders/fft.frag Normal file
View file

@ -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);
}

158
shaders/grid.frag Normal file
View file

@ -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.; y<cells.y; y++) {
for (float x=0.; x<cells.x; x++) {
p1=vec3(x-hcells.x,y-hcells.y,0.)*.1; p1.z=getz(p1.xy);
p2=vec3(p1.x+.1,p1.y ,0.); p2.z=getz(p2.xy);
p3=vec3(p1.x ,p1.y+.1,0.); p3.z=getz(p3.xy);
p1*=camrot; p2*=camrot; p3*=camrot;
p1.xy*=persp/max(0.1,p1.z+dist);
p2.xy*=persp/max(0.1,p2.z+dist);
p3.xy*=persp/max(0.1,p3.z+dist);
if (max(p1.x,p2.x)>uv.x-linewidth/4. && min(p1.x,p2.x)<uv.x+linewidth/4. && x<cells.x-1.) {
if (max(p1.y,p2.y)>uv.y-linewidth/4. && min(p1.y,p2.y)<uv.y+linewidth/4.) {
#ifdef OPAQUE_MODE
c=max(c,segment(uv,p1,p2,linewidth,dist)*1.5);
#else
c+=segment(uv,p1,p2,linewidth,dist);
#endif
}
}
if (max(p1.x,p3.x)>uv.x-linewidth/4. && min(p1.x,p3.x)<uv.x+linewidth/4. && y<cells.y-1.) {
if (max(p1.y,p3.y)>uv.y-linewidth/4. && min(p1.y,p3.y)<uv.y+linewidth/4.) {
#ifdef OPAQUE_MODE
c=max(c,segment(uv,p1,p3,linewidth,dist)*1.5);
#else
c+=segment(uv,p1,p3,linewidth,dist);
#endif
}
}
}
}
c*=brightness;
#ifdef INVERSE
fragColor = vec4(1.-c,1.);
#else
fragColor = vec4(c,1.);
#endif
}

38
shaders/spectrum.frag Normal file
View file

@ -0,0 +1,38 @@
/*
2D LED Spectrum - Visualiser
Based on Led Spectrum Analyser by: simesgreen - 27th February, 2013 https://www.shadertoy.com/view/Msl3zr
2D LED Spectrum by: uNiversal - 27th May, 2015
Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
*/
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// create pixel coordinates
vec2 uv = fragCoord.xy / iResolution.xy;
// quantize coordinates
const float bands = 30.0;
const float segs = 40.0;
vec2 p;
p.x = floor(uv.x*bands)/bands;
p.y = floor(uv.y*segs)/segs;
// read frequency data from first row of texture
float fft = texture( iChannel0, vec2(p.x,0.0) ).x;
// led color
vec3 color = mix(vec3(0.0, 2.0, 0.0), vec3(2.0, 0.0, 0.0), sqrt(uv.y));
// mask for bar graph
float mask = (p.y < fft) ? 1.0 : 0.1;
// led shape
vec2 d = fract((uv - p) *vec2(bands, segs)) - 0.5;
float led = smoothstep(0.5, 0.35, abs(d.x)) *
smoothstep(0.5, 0.35, abs(d.y));
vec3 ledColor = led*color*mask;
// output final color
fragColor = vec4(ledColor, 1.0);
}