From 85c2e2a8427796bfe171dfccdd84176b931bc230 Mon Sep 17 00:00:00 2001 From: Gyuri Horak Date: Tue, 6 Feb 2024 16:09:18 +0100 Subject: [PATCH] pulseaudio integration --- TODO.md | 1 + inc/paper.h | 3 +- inc/pulsefft.h | 72 ++++++ meson.build | 6 +- src/main.c | 233 ++++++++--------- src/paper.c | 680 +++++++++++++++++++++++++------------------------ src/pulsefft.c | 349 +++++++++++++++++++++++++ 7 files changed, 888 insertions(+), 456 deletions(-) create mode 100644 TODO.md create mode 100644 inc/pulsefft.h create mode 100644 src/pulsefft.c diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..7dd81dc --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +- add pulse/fft textures diff --git a/inc/paper.h b/inc/paper.h index 2e87a17..7582bfd 100644 --- a/inc/paper.h +++ b/inc/paper.h @@ -20,6 +20,7 @@ #include -void paper_init(char* monitor, char* frag_path, uint16_t fps, char* layer_name, uint16_t width, uint16_t height); +void paper_init(char *monitor, char *frag_path, char *pa_src, uint16_t fps, + char *layer_name, uint16_t width, uint16_t height); #endif diff --git a/inc/pulsefft.h b/inc/pulsefft.h new file mode 100644 index 0000000..d892fc0 --- /dev/null +++ b/inc/pulsefft.h @@ -0,0 +1,72 @@ +#ifndef _PULSEFFT_H +#define _PULSEFFT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +typedef struct pa_fft { + pthread_t thread; + int cont; + + // Pulseaudio + pa_simple *s; + char *dev; + unsigned int rate; + int error; + pa_sample_spec ss; + pa_channel_map map; + + // Buffers + unsigned int samples; + + double *pa_buff_l; + double *pa_buff_r; + size_t pa_buff_size; + unsigned int pa_samples; + + double *fft_buff; + double *fft_buff_l; + double *fft_buff_r; + size_t fft_buff_size; + unsigned int fft_samples; + + // Output buffers + float *pa_output; + size_t pa_output_size; + + float *fft_output; + size_t fft_output_size; + + // FFTW + fftw_complex *output_l; + fftw_complex *output_r; + size_t output_size; + unsigned int output_samples; + fftw_plan plan_l; + fftw_plan plan_r; +} pa_fft; + +void separate_freq_bands(double *out, fftw_complex *in, int n, int *lcf, + int *hcf, float *k, double sensitivity, + int in_samples); +void *pa_fft_thread(void *arg); +void get_pulse_default_sink(pa_fft *pa_fft); +void init_pulse(pa_fft *pa_fft); +void init_buffers(pa_fft *pa_fft); +void init_fft(pa_fft *pa_fft); +void deinit_fft(pa_fft *pa_fft); + +#endif // _PULSEFFT_H diff --git a/meson.build b/meson.build index 59e6fd4..07fbcf5 100644 --- a/meson.build +++ b/meson.build @@ -5,14 +5,18 @@ inc = include_directories('inc') wayland = dependency('wayland-client') wl_egl = dependency('wayland-egl') egl = dependency('egl') +pulse = dependency('libpulse-simple') +math = cc.find_library('m', required : false) +fftw = dependency('fftw3') executable(meson.project_name(), 'src/main.c', 'src/paper.c', +'src/pulsefft.c', 'src/utils.c', 'glad/glad.c', 'proto/wlr-layer-shell-unstable-v1-protocol.c', 'proto/xdg-output-unstable-v1-protocol.c', 'proto/xdg-shell-protocol.c', include_directories : inc, -dependencies : [wayland, wl_egl, egl], install : true) \ No newline at end of file +dependencies : [math, wayland, wl_egl, egl, pulse, fftw], install : true) diff --git a/src/main.c b/src/main.c index 56482f6..3fbdfe2 100644 --- a/src/main.c +++ b/src/main.c @@ -15,139 +15,124 @@ along with GLPaper. If not, see . */ -#include -#include -#include #include -#include +#include +#include #include +#include +#include #include -static void print_usage(char** argv) { - char* slash = strrchr(argv[0], '/'); - uint64_t offset; - if(slash == NULL) { - offset = 0; - } else { - offset = (slash - argv[0]) + 1; - } - printf("%s [options] \n", argv[0] + offset); - printf("Options:\n"); - printf("--help\t\t-h\tDisplays this help message\n"); - printf("--fork\t\t-F\tForks glpaper so you can close the terminal\n"); - printf("--fps\t\t-f\tSets the FPS to render at\n"); - printf("--layer\t\t-l\tSpecifies layer to run on\n"); - printf("--width\t\t-W\tThe width to render at, this does not affect display resolution\n"); - printf("--height\t-H\tThe height to render at, this does not affect display resolution\n"); - exit(0); +static void print_usage(char **argv) { + char *slash = strrchr(argv[0], '/'); + uint64_t offset; + if (slash == NULL) { + offset = 0; + } else { + offset = (slash - argv[0]) + 1; + } + printf("%s [options] \n", argv[0] + offset); + printf("Options:\n"); + printf("--help\t\t-h\tDisplays this help message\n"); + printf("--fork\t\t-F\tForks glpaper so you can close the terminal\n"); + printf("--fps\t\t-f\tSets the FPS to render at\n"); + printf("--layer\t\t-l\tSpecifies layer to run on\n"); + printf("--width\t\t-W\tThe width to render at, this does not affect display " + "resolution\n"); + printf("--height\t-H\tThe height to render at, this does not affect display " + "resolution\n"); + printf("--source\t-s\tPulseaudio source\n"); + exit(0); } -int main(int argc, char** argv) { - if(argc > 2) { - struct option opts[] = { - { - .name = "help", - .has_arg = no_argument, - .flag = NULL, - .val = 'h' - }, - { - .name = "fork", - .has_arg = no_argument, - .flag = NULL, - .val = 'F' - }, - { - .name = "fps", - .has_arg = required_argument, - .flag = NULL, - .val = 'f' - }, - { - .name = "layer", - .has_arg = required_argument, - .flag = NULL, - .val = 'l' - }, - { - .name = "width", - .has_arg = required_argument, - .flag = NULL, - .val = 'W' - }, - { - .name = "height", - .has_arg = required_argument, - .flag = NULL, - .val = 'H' - }, - { - .name = NULL, - .has_arg = 0, - .flag = NULL, - .val = 0 - } - }; - char* fps_str = NULL; - char* layer = NULL; - char* width_str = NULL; - char* height_str = NULL; - char opt; - while((opt = getopt_long(argc, argv, "hFf:l:W:H:", opts, NULL)) != -1) { - switch(opt) { - case 'h': - print_usage(argv); - break; - case 'F': - if(fork() > 0) { - exit(0); - } - fclose(stdout); - fclose(stderr); - fclose(stdin); - break; - case 'f': - fps_str = optarg; - break; - case 'l': - layer = optarg; - break; - case 'W': - width_str = optarg; - break; - case 'H': - height_str = optarg; - break; - } - } - uint16_t fps; - if(fps_str == NULL) { - fps = 0; - } else { - fps = strtol(fps_str, NULL, 10); - } +int main(int argc, char **argv) { + if (argc > 2) { + struct option opts[] = { + {.name = "help", .has_arg = no_argument, .flag = NULL, .val = 'h'}, + {.name = "fork", .has_arg = no_argument, .flag = NULL, .val = 'F'}, + {.name = "fps", .has_arg = required_argument, .flag = NULL, .val = 'f'}, + {.name = "layer", + .has_arg = required_argument, + .flag = NULL, + .val = 'l'}, + {.name = "width", + .has_arg = required_argument, + .flag = NULL, + .val = 'W'}, + {.name = "height", + .has_arg = required_argument, + .flag = NULL, + .val = 'H'}, + {.name = "source", + .has_arg = required_argument, + .flag = NULL, + .val = 's'}, + {.name = NULL, .has_arg = 0, .flag = NULL, .val = 0}}; + char *fps_str = NULL; + char *layer = NULL; + char *width_str = NULL; + char *height_str = NULL; + char *source_str = NULL; + char opt; + while ((opt = getopt_long(argc, argv, "hFf:l:W:H:s:", opts, NULL)) != -1) { + switch (opt) { + case 'h': + print_usage(argv); + break; + case 'F': + if (fork() > 0) { + exit(0); + } + fclose(stdout); + fclose(stderr); + fclose(stdin); + break; + case 'f': + fps_str = optarg; + break; + case 'l': + layer = optarg; + break; + case 'W': + width_str = optarg; + break; + case 'H': + height_str = optarg; + break; + case 's': + source_str = strdup(optarg); + break; + } + } + uint16_t fps; + if (fps_str == NULL) { + fps = 0; + } else { + fps = strtol(fps_str, NULL, 10); + } - uint16_t width; - if(width_str == NULL) { - width = 0; - } else { - width = strtol(width_str, NULL, 10); - } + uint16_t width; + if (width_str == NULL) { + width = 0; + } else { + width = strtol(width_str, NULL, 10); + } - uint16_t height; - if(height_str == NULL) { - height = 0; - } else { - height = strtol(height_str, NULL, 10); - } + uint16_t height; + if (height_str == NULL) { + height = 0; + } else { + height = strtol(height_str, NULL, 10); + } - if(optind + 1 >= argc) { - print_usage(argv); - } + if (optind + 1 >= argc) { + print_usage(argv); + } - paper_init(argv[optind], argv[optind + 1], fps, layer, width, height); - } else { - print_usage(argv); - } + paper_init(argv[optind], argv[optind + 1], source_str, fps, layer, width, + height); + } else { + print_usage(argv); + } } diff --git a/src/paper.c b/src/paper.c index ae99857..72b957b 100644 --- a/src/paper.c +++ b/src/paper.c @@ -17,411 +17,431 @@ #include -#include -#include +#include +#include #include #include -#include #include -#include +#include +#include +#include #include #include -#include #include -#include +#include #include +#include #define PROTO_VERSION(v1, v2) (v1 < v2 ? v1 : v2) -static const char* monitor; -static struct node* output = NULL; +static const char *monitor; +static struct node *output = NULL; static struct wl_list outputs; -static struct wl_compositor* comp; -static struct zwlr_layer_shell_v1* shell; -static struct zxdg_output_manager_v1* output_manager; +static struct wl_compositor *comp; +static struct zwlr_layer_shell_v1 *shell; +static struct zxdg_output_manager_v1 *output_manager; static time_t start; +static struct pa_fft *fft; struct node { - struct wl_output* output; - int32_t width, height; - struct wl_list link; + struct wl_output *output; + int32_t width, height; + struct wl_list link; }; static void nop() {} -static void add_interface(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { - (void) data; - if(strcmp(interface, wl_output_interface.name) == 0) { - struct node* node = malloc(sizeof(struct node)); - node->output = wl_registry_bind(registry, name, &wl_output_interface, PROTO_VERSION(version, 4)); - wl_list_insert(&outputs, &node->link); - } else if(strcmp(interface, wl_compositor_interface.name) == 0) { - comp = wl_registry_bind(registry, name, &wl_compositor_interface, PROTO_VERSION(version, 4)); - } else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { - output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, PROTO_VERSION(version, 3)); - } else if(strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, PROTO_VERSION(version, 4)); - } +static void add_interface(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, + uint32_t version) { + (void)data; + if (strcmp(interface, wl_output_interface.name) == 0) { + struct node *node = malloc(sizeof(struct node)); + node->output = wl_registry_bind(registry, name, &wl_output_interface, + PROTO_VERSION(version, 4)); + wl_list_insert(&outputs, &node->link); + } else if (strcmp(interface, wl_compositor_interface.name) == 0) { + comp = wl_registry_bind(registry, name, &wl_compositor_interface, + PROTO_VERSION(version, 4)); + } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { + output_manager = + wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, + PROTO_VERSION(version, 3)); + } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, + PROTO_VERSION(version, 4)); + } } -static void get_name(void* data, struct zxdg_output_v1* xdg_output, const char* name) { - (void) xdg_output; - struct node* node = data; - if(strcmp(name, monitor) == 0) { - output = node; - } +static void get_name(void *data, struct zxdg_output_v1 *xdg_output, + const char *name) { + (void)xdg_output; + struct node *node = data; + if (strcmp(name, monitor) == 0) { + output = node; + } } -static void config_surface(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width, uint32_t height) { - (void) data; - (void) width; - (void) height; - zwlr_layer_surface_v1_ack_configure(surface, serial); +static void config_surface(void *data, struct zwlr_layer_surface_v1 *surface, + uint32_t serial, uint32_t width, uint32_t height) { + (void)data; + (void)width; + (void)height; + zwlr_layer_surface_v1_ack_configure(surface, serial); } -static void get_res(void* data, struct wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { - (void) output; - (void) refresh; - if((flags & WL_OUTPUT_MODE_CURRENT) == WL_OUTPUT_MODE_CURRENT) { - struct node* node = data; - node->width = width; - node->height = height; - } +static void get_res(void *data, struct wl_output *output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) { + (void)output; + (void)refresh; + if ((flags & WL_OUTPUT_MODE_CURRENT) == WL_OUTPUT_MODE_CURRENT) { + struct node *node = data; + node->width = width; + node->height = height; + } } -static void setup_fbo(GLuint* fbo, GLuint* prog, GLuint* texture, GLuint vert, uint16_t width, uint16_t height) { - const char* frag_data[] = { - "#version 100\n" - "uniform sampler2D tex2D;" +static void setup_fbo(GLuint *fbo, GLuint *prog, GLuint *texture, GLuint vert, + uint16_t width, uint16_t height) { + const char *frag_data[] = {"#version 100\n" + "uniform sampler2D tex2D;" - "varying highp vec2 texCoords;" + "varying highp vec2 texCoords;" - "void main() {" - " gl_FragColor = texture2D(tex2D, texCoords);" - "}" - }; + "void main() {" + " gl_FragColor = texture2D(tex2D, texCoords);" + "}"}; - *prog = glCreateProgram(); - GLuint frag = glCreateShader(GL_FRAGMENT_SHADER); - GLint frag_len[] = {strlen(frag_data[0])}; - glShaderSource(frag, 1, frag_data, frag_len); - glCompileShader(frag); + *prog = glCreateProgram(); + GLuint frag = glCreateShader(GL_FRAGMENT_SHADER); + GLint frag_len[] = {strlen(frag_data[0])}; + glShaderSource(frag, 1, frag_data, frag_len); + glCompileShader(frag); - GLint status; - glGetShaderiv(frag, GL_COMPILE_STATUS, &status); - if(!status) { - char buff[255]; - glGetShaderInfoLog(frag, sizeof(buff), NULL, buff); - fprintf(stderr, "Texture Frag: %s\n", buff); - exit(1); - } + GLint status; + glGetShaderiv(frag, GL_COMPILE_STATUS, &status); + if (!status) { + char buff[255]; + glGetShaderInfoLog(frag, sizeof(buff), NULL, buff); + fprintf(stderr, "Texture Frag: %s\n", buff); + exit(1); + } - glAttachShader(*prog, vert); - glAttachShader(*prog, frag); - glLinkProgram(*prog); + glAttachShader(*prog, vert); + glAttachShader(*prog, frag); + glLinkProgram(*prog); - glBindAttribLocation(*prog, 0, "datIn"); - glBindAttribLocation(*prog, 1, "texIn"); + glBindAttribLocation(*prog, 0, "datIn"); + glBindAttribLocation(*prog, 1, "texIn"); - glGetProgramiv(*prog, GL_LINK_STATUS, &status); - if(!status) { - char buff[255]; - glGetProgramInfoLog(*prog, sizeof(buff), NULL, buff); - fprintf(stderr, "Texture Shader: %s\n", buff); - exit(1); - } + glGetProgramiv(*prog, GL_LINK_STATUS, &status); + if (!status) { + char buff[255]; + glGetProgramInfoLog(*prog, sizeof(buff), NULL, buff); + fprintf(stderr, "Texture Shader: %s\n", buff); + exit(1); + } - glGenFramebuffers(1, fbo); - glBindFramebuffer(GL_FRAMEBUFFER, *fbo); + glGenFramebuffers(1, fbo); + glBindFramebuffer(GL_FRAMEBUFFER, *fbo); - glGenTextures(1, texture); - glBindTexture(GL_TEXTURE_2D, *texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glGenTextures(1, texture); + glBindTexture(GL_TEXTURE_2D, *texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, NULL); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *texture, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + *texture, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +static void setup_vars(GLuint prog) { + glUseProgram(prog); + GLint time_var = glGetUniformLocation(prog, "time"); + glUniform1f(time_var, (utils_get_time_millis() - start) / 1000.0f); + GLint resolution = glGetUniformLocation(prog, "resolution"); + glUniform2f(resolution, output->width, output->height); } static void draw(GLuint prog) { - glUseProgram(prog); - glClear(GL_COLOR_BUFFER_BIT); - GLint time_var = glGetUniformLocation(prog, "time"); - glUniform1f(time_var, (utils_get_time_millis() - start) / 1000.0f); - GLint resolution = glGetUniformLocation(prog, "resolution"); - glUniform2f(resolution, output->width, output->height); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + glUseProgram(prog); + glClear(GL_COLOR_BUFFER_BIT); + setup_vars(prog); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); } -static void draw_fbo(GLuint fbo, GLuint texture, GLuint main_prog, GLuint final_prog, uint16_t width, uint16_t height) { - glViewport(0, 0, width, height); - glUseProgram(main_prog); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - GLint time_var = glGetUniformLocation(main_prog, "time"); - glUniform1f(time_var, (utils_get_time_millis() - start) / 1000.0f); - GLint resolution = glGetUniformLocation(main_prog, "resolution"); - glUniform2f(resolution, width, height); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); +static void draw_fbo(GLuint fbo, GLuint texture, GLuint main_prog, + GLuint final_prog, uint16_t width, uint16_t height) { + glViewport(0, 0, width, height); + glUseProgram(main_prog); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + setup_vars(main_prog); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - glViewport(0, 0, output->width, output->height); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glUseProgram(final_prog); - glBindTexture(GL_TEXTURE_2D, texture); - GLint tex2D = glGetUniformLocation(final_prog, "tex2D"); - glUniform1i(tex2D, 0); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + glViewport(0, 0, output->width, output->height); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glUseProgram(final_prog); + glBindTexture(GL_TEXTURE_2D, texture); + GLint tex2D = glGetUniformLocation(final_prog, "tex2D"); + glUniform1i(tex2D, 0); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); } -void paper_init(char* _monitor, char* frag_path, uint16_t fps, char* layer_name, uint16_t width, uint16_t height) { - monitor = _monitor; - start = utils_get_time_millis(); - wl_list_init(&outputs); - struct wl_display* wl = wl_display_connect(NULL); +void paper_init(char *_monitor, char *frag_path, char *pa_src, uint16_t fps, + char *layer_name, uint16_t width, uint16_t height) { + monitor = _monitor; + start = utils_get_time_millis(); + wl_list_init(&outputs); + struct wl_display *wl = wl_display_connect(NULL); + struct wl_registry *registry = wl_display_get_registry(wl); + struct wl_registry_listener reg_listener = {.global = add_interface, + .global_remove = nop}; + wl_registry_add_listener(registry, ®_listener, NULL); + wl_display_roundtrip(wl); - struct wl_registry* registry = wl_display_get_registry(wl); - struct wl_registry_listener reg_listener = { - .global = add_interface, - .global_remove = nop - }; - wl_registry_add_listener(registry, ®_listener, NULL); - wl_display_roundtrip(wl); + struct node *node; + wl_list_for_each(node, &outputs, link) { + struct zxdg_output_v1 *xdg_output = + zxdg_output_manager_v1_get_xdg_output(output_manager, node->output); + struct zxdg_output_v1_listener xdg_listener = {.description = nop, + .done = nop, + .logical_position = nop, + .logical_size = nop, + .name = get_name}; + zxdg_output_v1_add_listener(xdg_output, &xdg_listener, node); + struct wl_output_listener out_listener = {.done = nop, + .geometry = nop, + .mode = get_res, + .scale = nop, + .name = nop, + .description = nop}; + wl_output_add_listener(node->output, &out_listener, node); + } + wl_display_roundtrip(wl); - struct node* node; - wl_list_for_each(node, &outputs, link) { - struct zxdg_output_v1* xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, node->output); - struct zxdg_output_v1_listener xdg_listener = { - .description = nop, - .done = nop, - .logical_position = nop, - .logical_size = nop, - .name = get_name - }; - zxdg_output_v1_add_listener(xdg_output, &xdg_listener, node); + if (output == NULL) { + fprintf(stderr, + ":/ sorry about this but we can't seem to find that output\n"); + exit(1); + } - struct wl_output_listener out_listener = { - .done = nop, - .geometry = nop, - .mode = get_res, - .scale = nop, - .name = nop, - .description = nop - }; - wl_output_add_listener(node->output, &out_listener, node); - } - wl_display_roundtrip(wl); + // pulseaudio fft + fft = calloc(1, sizeof(struct pa_fft)); + fft->cont = 1; + fft->samples = 2048; + fft->dev = pa_src; + fft->rate = 44100; - if(output == NULL) { - fprintf(stderr, ":/ sorry about this but we can't seem to find that output\n"); - exit(1); - } + init_pulse(fft); + init_buffers(fft); + init_fft(fft); + struct wl_surface *wl_surface = wl_compositor_create_surface(comp); + struct wl_region *input_region = wl_compositor_create_region(comp); + struct wl_region *render_region = wl_compositor_create_region(comp); + wl_region_add(render_region, 0, 0, output->width, output->height); + wl_surface_set_opaque_region(wl_surface, render_region); + wl_surface_set_input_region(wl_surface, input_region); + wl_display_roundtrip(wl); - struct wl_surface* wl_surface = wl_compositor_create_surface(comp); - struct wl_region* input_region = wl_compositor_create_region(comp); - struct wl_region* render_region = wl_compositor_create_region(comp); - wl_region_add(render_region, 0, 0, output->width, output->height); - wl_surface_set_opaque_region(wl_surface, render_region); - wl_surface_set_input_region(wl_surface, input_region); - wl_display_roundtrip(wl); + enum zwlr_layer_shell_v1_layer layer; - enum zwlr_layer_shell_v1_layer layer; + if (layer_name == NULL) { + layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; + } else if (strcasecmp(layer_name, "top") == 0) { + layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; + } else if (strcasecmp(layer_name, "bottom") == 0) { + layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; + } else if (strcasecmp(layer_name, "background") == 0) { + layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; + } else if (strcasecmp(layer_name, "overlay") == 0) { + layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; + } else { + layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; + } + struct zwlr_layer_surface_v1 *surface = zwlr_layer_shell_v1_get_layer_surface( + shell, wl_surface, output->output, layer, "glpaper"); + zwlr_layer_surface_v1_set_exclusive_zone(surface, -1); + zwlr_layer_surface_v1_set_size(surface, output->width, output->height); + struct zwlr_layer_surface_v1_listener surface_listener = { + .closed = nop, .configure = config_surface}; + zwlr_layer_surface_v1_add_listener(surface, &surface_listener, NULL); + wl_surface_commit(wl_surface); + wl_display_roundtrip(wl); - if(layer_name == NULL) { - layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; - } else if(strcasecmp(layer_name, "top") == 0) { - layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; - } else if(strcasecmp(layer_name, "bottom") == 0) { - layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - } else if(strcasecmp(layer_name, "background") == 0) { - layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; - } else if(strcasecmp(layer_name, "overlay") == 0) { - layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; - } else { - layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; - } - struct zwlr_layer_surface_v1* surface = zwlr_layer_shell_v1_get_layer_surface(shell, wl_surface, output->output, layer, "glpaper"); - zwlr_layer_surface_v1_set_exclusive_zone(surface, -1); - zwlr_layer_surface_v1_set_size(surface, output->width, output->height); - struct zwlr_layer_surface_v1_listener surface_listener = { - .closed = nop, - .configure = config_surface - }; - zwlr_layer_surface_v1_add_listener(surface, &surface_listener, NULL); - wl_surface_commit(wl_surface); - wl_display_roundtrip(wl); + struct wl_egl_window *window = + wl_egl_window_create(wl_surface, output->width, output->height); + eglBindAPI(EGL_OPENGL_ES_API); + EGLDisplay egl_display = + eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wl, NULL); + eglInitialize(egl_display, NULL, NULL); + const EGLint win_attrib[] = {EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_NONE}; - struct wl_egl_window* window = wl_egl_window_create(wl_surface, output->width, output->height); - eglBindAPI(EGL_OPENGL_ES_API); - EGLDisplay egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, wl, NULL); - eglInitialize(egl_display, NULL, NULL); - const EGLint win_attrib[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_NONE - }; + EGLConfig config; + EGLint config_len; + eglChooseConfig(egl_display, win_attrib, &config, 1, &config_len); + const EGLint ctx_attrib[] = {EGL_CONTEXT_MAJOR_VERSION, 2, + EGL_CONTEXT_MINOR_VERSION, 0, EGL_NONE}; + EGLContext ctx = + eglCreateContext(egl_display, config, EGL_NO_CONTEXT, ctx_attrib); - EGLConfig config; - EGLint config_len; - eglChooseConfig(egl_display, win_attrib, &config, 1, &config_len); - const EGLint ctx_attrib[] = { - EGL_CONTEXT_MAJOR_VERSION, 2, - EGL_CONTEXT_MINOR_VERSION, 0, - EGL_NONE - }; - EGLContext ctx = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, ctx_attrib); + EGLSurface egl_surface = + eglCreatePlatformWindowSurface(egl_display, config, window, NULL); + eglMakeCurrent(egl_display, egl_surface, egl_surface, ctx); + if (fps == 0) { + eglSwapInterval(egl_display, 1); + } else { + eglSwapInterval(egl_display, 0); + } - EGLSurface egl_surface = eglCreatePlatformWindowSurface(egl_display, config, window, NULL); - eglMakeCurrent(egl_display, egl_surface, egl_surface, ctx); - if(fps == 0) { - eglSwapInterval(egl_display, 1); - } else { - eglSwapInterval(egl_display, 0); - } + gladLoadGLES2Loader((GLADloadproc)eglGetProcAddress); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glViewport(0, 0, output->width, output->height); + GLfloat vbo_data[] = { + // Vertex Data //Texture data + -1.0f, 1.0f, 0.0f, 1.0f, // Top left + -1.0f, -1.0f, 0.0f, 0.0f, // Bottom left + 1.0f, -1.0f, 1.0f, 0.0f, // Bottom right + 1.0f, 1.0f, 1.0f, 1.0f, // Top right + }; - gladLoadGLES2Loader((GLADloadproc) eglGetProcAddress); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glViewport(0, 0, output->width, output->height); - GLfloat vbo_data[] = { - //Vertex Data //Texture data - -1.0f, 1.0f, 0.0f, 1.0f, //Top left - -1.0f, -1.0f, 0.0f, 0.0f, //Bottom left - 1.0f, -1.0f, 1.0f, 0.0f, //Bottom right - 1.0f, 1.0f, 1.0f, 1.0f, //Top right - }; + GLuint vbo; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vbo_data), vbo_data, GL_STATIC_DRAW); - GLuint vbo; - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(vbo_data), vbo_data, GL_STATIC_DRAW); + GLuint ebo_data[] = {0, 1, 2, 2, 3, 0}; - GLuint ebo_data[] = { - 0, 1, 2, - 2, 3, 0 - }; + GLuint ebo; + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ebo_data), ebo_data, + GL_STATIC_DRAW); - GLuint ebo; - glGenBuffers(1, &ebo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ebo_data), ebo_data, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), + (void *)(2 * sizeof(GLfloat))); + glEnableVertexAttribArray(1); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); - glEnableVertexAttribArray(0); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*) (2 * sizeof(GLfloat))); - glEnableVertexAttribArray(1); + const char *vert_data[] = {"#version 100\n" + "attribute highp vec2 datIn;" + "attribute highp vec2 texIn;" - const char* vert_data[] = { - "#version 100\n" - "attribute highp vec2 datIn;" - "attribute highp vec2 texIn;" + "varying vec2 texCoords;" - "varying vec2 texCoords;" + "void main() {" + " texCoords = texIn;" + " gl_Position = vec4(datIn, 0.0, 1.0);" + "}"}; - "void main() {" - " texCoords = texIn;" - " gl_Position = vec4(datIn, 0.0, 1.0);" - "}" - }; + GLuint shader_prog = glCreateProgram(); + GLuint vert = glCreateShader(GL_VERTEX_SHADER); + GLint vert_len[] = {strlen(vert_data[0])}; + glShaderSource(vert, 1, vert_data, vert_len); + glCompileShader(vert); + GLint status; + glGetShaderiv(vert, GL_COMPILE_STATUS, &status); + if (!status) { + char buff[255]; + glGetShaderInfoLog(vert, sizeof(buff), NULL, buff); + fprintf(stderr, "Vert: %s\n", buff); + exit(1); + } - GLuint shader_prog = glCreateProgram(); - GLuint vert = glCreateShader(GL_VERTEX_SHADER); - GLint vert_len[] = {strlen(vert_data[0])}; - glShaderSource(vert, 1, vert_data, vert_len); - glCompileShader(vert); - GLint status; - glGetShaderiv(vert, GL_COMPILE_STATUS, &status); - if(!status) { - char buff[255]; - glGetShaderInfoLog(vert, sizeof(buff), NULL, buff); - fprintf(stderr, "Vert: %s\n", buff); - exit(1); - } + if (access(frag_path, R_OK) != 0) { + fprintf(stderr, "I can't seem to find %s\n", frag_path); + exit(1); + } - if(access(frag_path, R_OK) != 0) { - fprintf(stderr, "I can't seem to find %s\n", frag_path); - exit(1); - } + FILE *f = fopen(frag_path, "r"); + fseek(f, 0L, SEEK_END); + size_t f_size = ftell(f); + const char *frag_data[1]; + frag_data[0] = calloc(1, f_size + 1); + fseek(f, 0L, SEEK_SET); + fread((void *)frag_data[0], 1, f_size, f); + fclose(f); - FILE* f = fopen(frag_path, "r"); - fseek(f, 0L, SEEK_END); - size_t f_size = ftell(f); - const char* frag_data[1]; - frag_data[0] = calloc(1, f_size + 1); - fseek(f, 0L, SEEK_SET); - fread((void*) frag_data[0], 1, f_size, f); - fclose(f); + GLuint frag = glCreateShader(GL_FRAGMENT_SHADER); + GLint frag_len[] = {f_size}; + glShaderSource(frag, 1, frag_data, frag_len); + glCompileShader(frag); + glGetShaderiv(frag, GL_COMPILE_STATUS, &status); + if (!status) { + char buff[255]; + glGetShaderInfoLog(frag, sizeof(buff), NULL, buff); + fprintf(stderr, "Frag: %s\n", buff); + exit(1); + } - GLuint frag = glCreateShader(GL_FRAGMENT_SHADER); - GLint frag_len[] = {f_size}; - glShaderSource(frag, 1, frag_data, frag_len); - glCompileShader(frag); - glGetShaderiv(frag, GL_COMPILE_STATUS, &status); - if(!status) { - char buff[255]; - glGetShaderInfoLog(frag, sizeof(buff), NULL, buff); - fprintf(stderr, "Frag: %s\n", buff); - exit(1); - } + glAttachShader(shader_prog, vert); + glAttachShader(shader_prog, frag); + glLinkProgram(shader_prog); + glBindAttribLocation(shader_prog, 0, "datIn"); + glBindAttribLocation(shader_prog, 1, "texIn"); - glAttachShader(shader_prog, vert); - glAttachShader(shader_prog, frag); - glLinkProgram(shader_prog); - glBindAttribLocation(shader_prog, 0, "datIn"); - glBindAttribLocation(shader_prog, 1, "texIn"); + glGetProgramiv(shader_prog, GL_LINK_STATUS, &status); + if (!status) { + char buff[255]; + glGetProgramInfoLog(shader_prog, sizeof(buff), NULL, buff); + fprintf(stderr, "Shader: %s\n", buff); + exit(1); + } - glGetProgramiv(shader_prog, GL_LINK_STATUS, &status); - if(!status) { - char buff[255]; - glGetProgramInfoLog(shader_prog, sizeof(buff), NULL, buff); - fprintf(stderr, "Shader: %s\n", buff); - exit(1); - } + bool use_fbo = width > 0 || height > 0; + GLuint fbo = 0; + GLuint final_prog = 0; + GLuint render_tex = 0; + if (use_fbo) { + if (width == 0) { + width = output->width; + } + if (height == 0) { + height = output->height; + } + setup_fbo(&fbo, &final_prog, &render_tex, vert, width, height); + } + glDeleteShader(vert); + glDeleteShader(frag); + glUseProgram(shader_prog); - bool use_fbo = width > 0 || height > 0; - GLuint fbo = 0; - GLuint final_prog = 0; - GLuint render_tex = 0; - if(use_fbo) { - if(width == 0) { - width = output->width; - } - if(height == 0) { - height = output->height; - } - setup_fbo(&fbo, &final_prog, &render_tex, vert, width, height); - } + time_t frame_start; - glDeleteShader(vert); - glDeleteShader(frag); - glUseProgram(shader_prog); - - time_t frame_start; - - while(true) { - frame_start = utils_get_time_millis(); - if(wl_display_flush(wl) == -1) { - exit(0); - } - if(use_fbo) { - draw_fbo(fbo, render_tex, shader_prog, final_prog, width, height); - } else { - draw(shader_prog); - } - eglSwapBuffers(egl_display, egl_surface); - if(fps != 0) { - int64_t sleep = (1000 / fps) - (utils_get_time_millis() - frame_start); - utils_sleep_millis(sleep >= 0 ? sleep : 0); - } - } + while (true) { + frame_start = utils_get_time_millis(); + if (wl_display_flush(wl) == -1) { + exit(0); + } + if (use_fbo) { + draw_fbo(fbo, render_tex, shader_prog, final_prog, width, height); + } else { + draw(shader_prog); + } + eglSwapBuffers(egl_display, egl_surface); + if (fps != 0) { + int64_t sleep = (1000 / fps) - (utils_get_time_millis() - frame_start); + utils_sleep_millis(sleep >= 0 ? sleep : 0); + } + } } diff --git a/src/pulsefft.c b/src/pulsefft.c new file mode 100644 index 0000000..994835d --- /dev/null +++ b/src/pulsefft.c @@ -0,0 +1,349 @@ +#include "pulsefft.h" + +// TODO: Add this vars to config +const int smoothing_prec = 100; +float smooth[] = {1.0, 1.0, 1.0, 1.0, 1.0}; +float gravity = 0.0006; +int highf = 18000, lowf = 20; +float logScale = 1.0; +double sensitivity = 1.0; + +void separate_freq_bands(double *out, fftw_complex *in, int n, int *lcf, + int *hcf, float *k, double sensitivity, + int in_samples) { + double peak[n]; + double y[in_samples]; + double temp; + + for (int i = 0; i < n; i++) { + peak[i] = 0; + + for (int j = lcf[i]; j <= hcf[i]; j++) { + y[j] = sqrt(creal(in[j]) * creal(in[j]) + cimag(in[j]) * cimag(in[j])); + peak[i] += y[j]; + } + + peak[i] = peak[i] / (hcf[i] - lcf[i] + 1); + temp = peak[i] * sensitivity * k[i] / 1000000; + out[i] = temp / 100.0; + } +} + +int16_t audio_out_l[65536]; +int16_t audio_out_r[65536]; + +void *pa_fft_thread(void *arg) { + pa_fft *t = (struct pa_fft *)arg; + + float smoothing[smoothing_prec]; + double freqconst = log(highf - lowf) / log(pow(smoothing_prec, logScale)); + + int fall[smoothing_prec]; + float fpeak[smoothing_prec], flast[smoothing_prec], fmem[smoothing_prec]; + + float fc[smoothing_prec], x; + int lcf[smoothing_prec], hcf[smoothing_prec]; + + // Calc freq range for each bar + for (int i = 0; i < smoothing_prec; i++) { + fc[i] = pow(powf(i, (logScale - 1.0) * ((double)i + 1.0) / + ((double)smoothing_prec) + + 1.0), + freqconst) + + lowf; + x = fc[i] / (t->rate / 2); + lcf[i] = x * (t->samples / 2); + if (i != 0) + hcf[i - 1] = lcf[i] - 1 > lcf[i - 1] ? lcf[i] - 1 : lcf[i - 1]; + } + hcf[smoothing_prec - 1] = highf * t->samples / t->rate; + + // Calc smoothing + for (int i = 0; i < smoothing_prec; i++) { + smoothing[i] = pow(fc[i], 0.64); // TODO: Add smoothing factor to config + smoothing[i] *= + smooth[(int)(i / smoothing_prec * sizeof(smooth) / sizeof(*smooth))]; + } + + unsigned int n = 0; + int16_t buf[t->samples]; + + // Clear arrays + for (int i = 0; i < smoothing_prec; i++) + buf[i] = fall[i] = fpeak[i] = flast[i] = fmem[i] = 0; + + while (t->cont) { + // Read from pulseaudio buffer + if (pa_simple_read(t->s, buf, sizeof(buf) / 2, &t->error) < 0) { + printf("ERROR: pa_simple_read() failed: %s\n", pa_strerror(t->error)); + t->cont = 0; + continue; + } + + // Spliting channels + for (unsigned int i = 0; i < t->samples / 2; i += 2) { + if (true) { // TODO Add mono to config + audio_out_l[n] = (buf[i] + buf[i + 1]) / 2; + } else { + audio_out_l[n] = buf[i]; + audio_out_r[n] = buf[i + 1]; + } + n++; + if (n == t->samples - 1) + n = 0; + } + + // Copying buffer + for (unsigned int i = 0; i < t->samples + 2; i++) { + if (i < t->samples) { + t->pa_buff_l[i] = audio_out_l[i]; + t->pa_buff_r[i] = audio_out_r[i]; + t->pa_output[i] = (audio_out_l[i] + audio_out_r[i]) / 2; + } else { + t->pa_buff_l[i] = 0; + t->pa_buff_r[i] = 0; + t->pa_output[i] = 0; + } + } + + if (true) { // TODO mono + // Run fftw + fftw_execute(t->plan_l); + + // Separate fftw output + separate_freq_bands(t->fft_buff, t->output_l, smoothing_prec, lcf, hcf, + smoothing, sensitivity, t->output_samples); + } else { + fftw_execute(t->plan_l); + fftw_execute(t->plan_r); + + separate_freq_bands(t->fft_buff_l, t->output_l, smoothing_prec / 2, lcf, + hcf, smoothing, sensitivity, t->output_samples); + separate_freq_bands(t->fft_buff_r, t->output_r, smoothing_prec / 2, lcf, + hcf, smoothing, sensitivity, t->output_samples); + + // mirror stereo + for (int i = 0; i < smoothing_prec; i++) { + if (i < smoothing_prec / 2) { + t->fft_buff[i] = t->fft_buff_l[smoothing_prec / 2 - i - 1]; + } else { + t->fft_buff[i] = t->fft_buff_r[i - smoothing_prec / 2]; + } + } + } + + /* Processing */ + // Waves + for (int i = 0; i < smoothing_prec; i++) { + t->fft_buff[i] *= 0.8; + for (int j = i - 1; j >= 0; j--) { + if (t->fft_buff[i] - (i - j) * (i - j) / 1000.0 > t->fft_buff[j]) + t->fft_buff[j] = t->fft_buff[i] - (i - j) * (i - j) / 1000.0; + } + for (int j = i + 1; j < smoothing_prec; j++) + if (t->fft_buff[i] - (i - j) * (i - j) / 1000.0 > t->fft_buff[j]) + t->fft_buff[j] = t->fft_buff[i] - (i - j) * (i - j) / 1000.0; + } + + // Gravity + for (int i = 0; i < smoothing_prec; i++) { + if (t->fft_buff[i] < flast[i]) { + t->fft_buff[i] = fpeak[i] - (gravity * fall[i] * fall[i]); + fall[i]++; + } else { + fpeak[i] = t->fft_buff[i]; + fall[i] = 0; + } + + flast[i] = t->fft_buff[i]; + } + + // Integral + for (int i = 0; i < smoothing_prec; i++) { + t->fft_buff[i] = (int)(t->fft_buff[i] * 100); + t->fft_buff[i] += fmem[i] * 0.9; // TODO: Add integral to config + fmem[i] = t->fft_buff[i]; + + int diff = 100 - t->fft_buff[i]; + if (diff < 0) + diff = 0; + double div = 1 / (diff + 1); + fmem[i] *= 1 - div / 20; + t->fft_buff[i] /= 100.0; + } + + // Auto sensitivity + for (int i = 0; i < smoothing_prec; i++) { + if (t->fft_buff[i] > 0.95) { + sensitivity *= 0.985; + break; + } + if (i == smoothing_prec - 1 && sensitivity < 1.0) + sensitivity *= 1.002; + } + if (sensitivity < 0.0001) + sensitivity = 0.0001; + + for (int i = 0; i < smoothing_prec; i++) + t->fft_output[i] = t->fft_buff[i]; + } + + return NULL; +} + +static pa_mainloop *m_pulseaudio_mainloop; + +static void cb(__attribute__((unused)) pa_context *pulseaudio_context, + const pa_server_info *i, void *userdata) { + + // Obtain default sink name + pa_fft *pa_fft = (struct pa_fft *)userdata; + pa_fft->dev = malloc(sizeof(char) * 1024); + + strcpy(pa_fft->dev, i->default_sink_name); + + // Append `.monitor` suffix + pa_fft->dev = strcat(pa_fft->dev, ".monitor"); + + // Quiting mainloop + pa_context_disconnect(pulseaudio_context); + pa_context_unref(pulseaudio_context); + pa_mainloop_quit(m_pulseaudio_mainloop, 0); + pa_mainloop_free(m_pulseaudio_mainloop); +} + +static void pulseaudio_context_state_callback(pa_context *pulseaudio_context, + void *userdata) { + // Ensure loop is ready + switch (pa_context_get_state(pulseaudio_context)) { + case PA_CONTEXT_UNCONNECTED: + break; + case PA_CONTEXT_CONNECTING: + break; + case PA_CONTEXT_AUTHORIZING: + break; + case PA_CONTEXT_SETTING_NAME: + break; + case PA_CONTEXT_READY: // Extract default sink name + pa_operation_unref( + pa_context_get_server_info(pulseaudio_context, cb, userdata)); + break; + case PA_CONTEXT_FAILED: + printf("failed to connect to pulseaudio server\n"); + exit(EXIT_FAILURE); + break; + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit(m_pulseaudio_mainloop, 0); + break; + } +} + +void get_pulse_default_sink(pa_fft *pa_fft) { + pa_mainloop_api *mainloop_api; + pa_context *pulseaudio_context; + int ret; + + // Create a mainloop API and connection to the default server + m_pulseaudio_mainloop = pa_mainloop_new(); + + mainloop_api = pa_mainloop_get_api(m_pulseaudio_mainloop); + pulseaudio_context = pa_context_new(mainloop_api, "xglbg device list"); + + // Connect to the PA server + pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Define a callback so the server will tell us its state + pa_context_set_state_callback( + pulseaudio_context, pulseaudio_context_state_callback, (void *)pa_fft); + + // Start mainloop to get default sink + + // Start with one non blocking iteration in case pulseaudio is not able to run + if (!(ret = pa_mainloop_iterate(m_pulseaudio_mainloop, 0, &ret))) { + printf( + "Could not open pulseaudio mainloop to find default device name: %d\n" + "Check if pulseaudio is running\n", + ret); + + exit(EXIT_FAILURE); + } + + pa_mainloop_run(m_pulseaudio_mainloop, &ret); +} + +void init_pulse(pa_fft *pa_fft) { + if (!pa_fft->dev) + get_pulse_default_sink(pa_fft); + + pa_fft->ss.format = PA_SAMPLE_S16LE; + pa_fft->ss.rate = pa_fft->rate; + pa_fft->ss.channels = 2; + + const pa_buffer_attr pb = {.maxlength = (uint32_t)-1, + .fragsize = pa_fft->samples}; + + if (!(pa_fft->s = pa_simple_new(NULL, "xglbg", PA_STREAM_RECORD, pa_fft->dev, + "xglbg audio source", &pa_fft->ss, NULL, &pb, + &pa_fft->error))) { + printf("ERROR: pa_simple_new() failed: %s\n", pa_strerror(pa_fft->error)); + pa_fft->cont = 0; + return; + } +} + +void init_buffers(pa_fft *pa_fft) { + if (!pa_fft) + return; + + // Pulse buffer + pa_fft->pa_samples = pa_fft->samples + 2; + pa_fft->pa_buff_size = sizeof(double) * pa_fft->pa_samples; + pa_fft->pa_buff_l = malloc(pa_fft->pa_buff_size); + pa_fft->pa_buff_r = malloc(pa_fft->pa_buff_size); + + // FFT buffer + pa_fft->fft_samples = pa_fft->samples + 2; + pa_fft->fft_buff_size = sizeof(double) * pa_fft->fft_samples; + pa_fft->fft_buff = malloc(pa_fft->fft_buff_size); + pa_fft->fft_buff_l = malloc(pa_fft->fft_buff_size); + pa_fft->fft_buff_r = malloc(pa_fft->fft_buff_size); + + // FFTW buffer + pa_fft->output_samples = (pa_fft->samples / 2) + 1; + pa_fft->output_size = sizeof(fftw_complex) * pa_fft->output_samples; + pa_fft->output_l = fftw_malloc(pa_fft->output_size); + pa_fft->output_r = fftw_malloc(pa_fft->output_size); + + // Pulse output buffer + pa_fft->pa_output_size = sizeof(float) * pa_fft->pa_samples; + pa_fft->pa_output = malloc(pa_fft->pa_output_size); + + // FFT output buffer + pa_fft->fft_output_size = sizeof(float) * pa_fft->fft_samples; + pa_fft->fft_output = malloc(pa_fft->fft_output_size); +} + +void init_fft(pa_fft *pa_fft) { + if (!pa_fft) + return; + + pa_fft->plan_l = fftw_plan_dft_r2c_1d(pa_fft->pa_samples, pa_fft->pa_buff_l, + pa_fft->output_l, FFTW_MEASURE); + pa_fft->plan_r = fftw_plan_dft_r2c_1d(pa_fft->pa_samples, pa_fft->pa_buff_r, + pa_fft->output_r, FFTW_MEASURE); +} + +void deinit_fft(pa_fft *pa_fft) { + if (!pa_fft) + return; + + fftw_destroy_plan(pa_fft->plan_l); + fftw_destroy_plan(pa_fft->plan_r); + pa_simple_free(pa_fft->s); + free(pa_fft->pa_buff_l); + free(pa_fft->pa_buff_r); + + pthread_join(pa_fft->thread, NULL); + + free(pa_fft); +}