From 32ec9b3cc3228a2d0e42f8d68785ac56c90924b2 Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 17 Nov 2023 21:31:16 +0100 Subject: [PATCH 01/25] add sdl full screen option as mentioned in #531 also added quit on q or escape --- cava.c | 6 +++--- config.c | 1 + config.h | 4 ++-- example_files/config | 1 + output/sdl_cava.c | 14 +++++++++++--- output/sdl_cava.h | 2 +- output/sdl_glsl.c | 16 ++++++++++++---- output/sdl_glsl.h | 4 ++-- 8 files changed, 33 insertions(+), 15 deletions(-) diff --git a/cava.c b/cava.c index 9952c2e4..2d147a02 100644 --- a/cava.c +++ b/cava.c @@ -483,15 +483,15 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co #ifdef SDL // output: start sdl mode if (output_mode == OUTPUT_SDL) { - init_sdl_window(p.sdl_width, p.sdl_height, p.sdl_x, p.sdl_y); + init_sdl_window(p.sdl_width, p.sdl_height, p.sdl_x, p.sdl_y, p.sdl_full_screen); height = p.sdl_height; width = p.sdl_width; } #endif #ifdef SDL_GLSL if (output_mode == OUTPUT_SDL_GLSL) { - init_sdl_glsl_window(p.sdl_width, p.sdl_height, p.sdl_x, p.sdl_y, p.vertex_shader, - p.fragment_shader); + init_sdl_glsl_window(p.sdl_width, p.sdl_height, p.sdl_x, p.sdl_y, p.sdl_full_screen, + p.vertex_shader, p.fragment_shader); height = p.sdl_height; width = p.sdl_width; } diff --git a/config.c b/config.c index 6f2f0fd2..6274c522 100644 --- a/config.c +++ b/config.c @@ -639,6 +639,7 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->sdl_height = iniparser_getint(ini, "output:sdl_height", 500); p->sdl_x = iniparser_getint(ini, "output:sdl_x", -1); p->sdl_y = iniparser_getint(ini, "output:sdl_y", -1); + p->sdl_full_screen = iniparser_getint(ini, "output:sdl_full_screen", 0); if (strcmp(outputMethod, "sdl") == 0 || strcmp(outputMethod, "sdl_glsl") == 0) { free(p->color); diff --git a/config.h b/config.h index dc515efd..1e43ffee 100644 --- a/config.h +++ b/config.h @@ -98,8 +98,8 @@ struct config_params { int userEQ_keys, userEQ_enabled, col, bgcol, autobars, stereo, raw_format, ascii_range, bit_format, gradient, gradient_count, fixedbars, framerate, bar_width, bar_spacing, bar_height, autosens, overshoot, waves, fifoSample, fifoSampleBits, sleep_timer, sdl_width, - sdl_height, sdl_x, sdl_y, draw_and_quit, zero_test, non_zero_test, reverse, sync_updates, - continuous_rendering; + sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, non_zero_test, reverse, + sync_updates, continuous_rendering; }; struct error_s { diff --git a/example_files/config b/example_files/config index 45c5e149..645dd20d 100644 --- a/example_files/config +++ b/example_files/config @@ -146,6 +146,7 @@ ; sdl_height = 500 ; sdl_x = -1 ; sdl_y= -1 +; sdl_full_screen = 0 # set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none' # 'frequency' displays the lower cut off frequency of the bar above. diff --git a/output/sdl_cava.c b/output/sdl_cava.c index 6273993c..8e501b52 100644 --- a/output/sdl_cava.c +++ b/output/sdl_cava.c @@ -35,7 +35,7 @@ static void parse_color(char *color_string, struct colors *color) { } } -void init_sdl_window(int width, int height, int x, int y) { +void init_sdl_window(int width, int height, int x, int y, int full_screen) { if (x == -1) x = SDL_WINDOWPOS_UNDEFINED; @@ -45,8 +45,12 @@ void init_sdl_window(int width, int height, int x, int y) { if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); } else { - gWindow = - SDL_CreateWindow("cava", x, y, width, height, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + Uint32 sdl_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; + + if (full_screen == 1) + sdl_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + + gWindow = SDL_CreateWindow("cava", x, y, width, height, sdl_flags); if (gWindow == NULL) { printf("Window could not be created! SDL_Error: %s\n", SDL_GetError()); } else { @@ -185,6 +189,10 @@ int draw_sdl(int bars_count, int bar_width, int bar_spacing, int remainder, int free(gradient_colors_sdl); } } + if (e.type == SDL_KEYDOWN) { + if (e.key.keysym.sym == SDLK_q || e.key.keysym.sym == SDLK_ESCAPE) + rc = -2; + } if (e.type == SDL_QUIT) rc = -2; } diff --git a/output/sdl_cava.h b/output/sdl_cava.h index 47a81668..c80a024d 100644 --- a/output/sdl_cava.h +++ b/output/sdl_cava.h @@ -1,6 +1,6 @@ #include "../config.h" -void init_sdl_window(int width, int height, int x, int y); +void init_sdl_window(int width, int height, int x, int y, int full_screen); void init_sdl_surface(int *width, int *height, char *const fg_color_string, char *const bg_color_string, int gradient, int gradient_count, char **gradient_color_strings); diff --git a/output/sdl_glsl.c b/output/sdl_glsl.c index b6cd8eb1..53988c98 100644 --- a/output/sdl_glsl.c +++ b/output/sdl_glsl.c @@ -41,8 +41,8 @@ const char *read_file(const char *); GLuint compile_shader(GLenum type, const char **); GLuint program_check(GLuint); -void init_sdl_glsl_window(int width, int height, int x, int y, char *const vertex_shader, - char *const fragmnet_shader) { +void init_sdl_glsl_window(int width, int height, int x, int y, int full_screen, + char *const vertex_shader, char *const fragmnet_shader) { if (x == -1) x = SDL_WINDOWPOS_UNDEFINED; @@ -60,8 +60,12 @@ void init_sdl_glsl_window(int width, int height, int x, int y, char *const verte SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); #endif - glWindow = SDL_CreateWindow("cava", x, y, width, height, - SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + Uint32 sdl_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; + + if (full_screen == 1) + sdl_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + + glWindow = SDL_CreateWindow("cava", x, y, width, height, sdl_flags); if (glWindow == NULL) { fprintf(stderr, "Window could not be created! SDL_Error: %s\n", SDL_GetError()); exit(1); @@ -213,6 +217,10 @@ int draw_sdl_glsl(int bars_count, const float bars[], int frame_time, int re_pai glViewport(0, 0, event.window.data1, event.window.data2); rc = -1; } + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_q || event.key.keysym.sym == SDLK_ESCAPE) + rc = -2; + } if (event.type == SDL_QUIT) rc = -2; diff --git a/output/sdl_glsl.h b/output/sdl_glsl.h index 37f416a0..621f1e6d 100644 --- a/output/sdl_glsl.h +++ b/output/sdl_glsl.h @@ -1,7 +1,7 @@ #include "../config.h" -void init_sdl_glsl_window(int width, int height, int x, int y, char *const vertex_shader, - char *const fragment_shader); +void init_sdl_glsl_window(int width, int height, int x, int y, int full_screen, + char *const vertex_shader, char *const fragment_shader); void init_sdl_glsl_surface(int *width, int *height, char *const fg_color_string, char *const bg_color_string, int bar_width, int bar_spacing, int gradient, int gradient_count, char **gradient_color_strings); From 80814c2629ec309d4de648381c978d659d5d0aa9 Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 24 Nov 2023 20:08:14 +0100 Subject: [PATCH 02/25] make disable console blank optional and dont enable it if disabled, this closes #535 --- cava.c | 5 +++-- config.c | 2 ++ config.h | 2 +- example_files/config | 3 +++ output/terminal_ncurses.c | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cava.c b/cava.c index 2d147a02..e30310a0 100644 --- a/cava.c +++ b/cava.c @@ -290,7 +290,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co output_mode = p.output; #ifndef _MSC_VER - if (output_mode != OUTPUT_RAW && output_mode != OUTPUT_NORITAKE) { + if (output_mode != OUTPUT_NCURSES || output_mode != OUTPUT_NONCURSES) { // Check if we're running in a tty if (strncmp(ttyname(0), "/dev/tty", 8) == 0 || strcmp(ttyname(0), "/dev/console") == 0) inAtty = 1; @@ -309,7 +309,8 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co // if not it might still be available, we dont know, must try system("setfont cava.psf >/dev/null 2>&1"); } - system("setterm -blank 0"); + if (p.disable_blanking) + system("setterm -blank 0"); } // We use unicode block characters to draw the bars and diff --git a/config.c b/config.c index 6274c522..c7bb269c 100644 --- a/config.c +++ b/config.c @@ -652,6 +652,8 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->continuous_rendering = iniparser_getint(ini, "output:continuous_rendering", 0); + p->disable_blanking = iniparser_getint(ini, "output:disable_blanking", 0); + p->sync_updates = iniparser_getint(ini, "output:alacritty_sync", 0); vertexShader = strdup(iniparser_getstring(ini, "output:vertex_shader", "pass_through.vert")); diff --git a/config.h b/config.h index 1e43ffee..5bbb7002 100644 --- a/config.h +++ b/config.h @@ -99,7 +99,7 @@ struct config_params { bit_format, gradient, gradient_count, fixedbars, framerate, bar_width, bar_spacing, bar_height, autosens, overshoot, waves, fifoSample, fifoSampleBits, sleep_timer, sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, non_zero_test, reverse, - sync_updates, continuous_rendering; + sync_updates, continuous_rendering, disable_blanking; }; struct error_s { diff --git a/example_files/config b/example_files/config index 645dd20d..ab4381eb 100644 --- a/example_files/config +++ b/example_files/config @@ -165,6 +165,9 @@ ; for glsl output mode, keep rendering even if no audio ; continuous_rendering = 0 +# disable console blank (screen saver) in tty +; disable_blanking = 0 + [color] # Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow. diff --git a/output/terminal_ncurses.c b/output/terminal_ncurses.c index 70587140..96786b0b 100644 --- a/output/terminal_ncurses.c +++ b/output/terminal_ncurses.c @@ -313,7 +313,7 @@ void cleanup_terminal_ncurses(void) { echo(); system("setfont >/dev/null 2>&1"); system("setfont /usr/share/consolefonts/Lat2-Fixed16.psf.gz >/dev/null 2>&1"); - system("setterm -blank 10 >/dev/null 2>&1"); + /*for(int i = 0; i < gradient_size; ++i) { if(the_color_redefinitions[i].color) { init_color(the_color_redefinitions[i].color, From 49a137e9cc0d17043b6c8418cb5641d37fd9f638 Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 24 Nov 2023 20:29:20 +0100 Subject: [PATCH 03/25] added info on alsa dmix #534 --- README.md | 8 ++- example_files/etc/asound_dmix.conf | 90 ++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 example_files/etc/asound_dmix.conf diff --git a/README.md b/README.md index 2662b8ad..e738e6ab 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ To make it persistent across boot add the line `snd-aloop` to "/etc/modules". To Playing the audio through your Loopback interface makes it possible for cava to capture it, but there will be no sound in your speakers. In order to play audio on the loopback interface and your actual interface you must make use of the ALSA multi channel. -Look at the included example file `example_files/etc/asound.conf` on how to use the multi channel. I was able to make this work on my laptop (an Asus UX31 running Ubuntu), but I had no luck with the ALSA method on my Raspberry Pi (Rasbian) with an USB DAC. The PulseAudio method however works perfectly on my Pi. +Look at the included example file `example_files/etc/asound.conf` on how to use the multi channel. I was able to make this work with a HDA Intel PCH sound card, but I had no luck with the an USB DAC. Read more about the ALSA method [here](http://stackoverflow.com/questions/12984089/capture-playback-on-play-only-sound-card-with-alsa). @@ -285,6 +285,12 @@ If you are having problems with the alsa method on Rasberry PI, try enabling `mm dtoverlay=i2s-mmap ``` +#### dmix + +@reluekiss, was able to make cava work with dmix. Check out the example config in `example_files/etc/asound_dmix.conf` and issue [534](https://github.com/karlstav/cava/issues/534). + + + ### mpd Add these lines in mpd: diff --git a/example_files/etc/asound_dmix.conf b/example_files/etc/asound_dmix.conf new file mode 100644 index 00000000..26e80676 --- /dev/null +++ b/example_files/etc/asound_dmix.conf @@ -0,0 +1,90 @@ +pcm.dmixed { + type dmix + ipc_key 1024 + ipc_key_add_uid 0 + slave { + pcm "hw:1,0" #your hardware speaker device + period_time 0 + period_size 1024 + buffer_size 4096 + channels 2 + } + bindings { + 0 0 + 1 1 + } +} + +pcm.dsnooped { + type dsnoop + ipc_key 1025 + slave { + pcm "hw:1,6" #your hardware input device (mic) + period_time 0 + period_size 1024 + buffer_size 4096 + channels 2 + } + bindings { + 0 0 + 1 1 + } +} + +pcm.dmixerloop { + type dmix + ipc_key 2048 + ipc_perm 0666 # allow other users + slave.pcm "hw:Loopback,0,0" + slave { + period_time 0 + period_size 1024 + buffer_size 4096 + channels 2 # must match bindings + } + bindings { + 0 0 + 1 1 + } +} + +pcm.out { + type plug + route_policy "duplicate" + slave.pcm { + type multi + slaves { + a { channels 2 pcm "dmixed" } + b { channels 2 pcm "dmixerloop" } + } + bindings { + 0 { slave a channel 0 } + 1 { slave a channel 1 } + 2 { slave b channel 0 } + 3 { slave b channel 1 } + } + } + ttable [ + [ 1 0 1 0 ] + [ 0 1 0 1 ] + ] +} + +pcm.looprec { + type hw + card "Loopback" + device 1 + subdevice 0 +} + +pcm.!default { + type asym + playback.pcm "out" + #capture.pcm "looprec" + capture.pcm "dsnooped" +} + +ctl.!default { + type hw + card 1 +} \ No newline at end of file From 638fefe4771e7745ca05a07d6debf81cb6f37f7c Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 24 Nov 2023 20:41:33 +0100 Subject: [PATCH 04/25] disable blanking, for real this time --- cava.c | 2 +- output/terminal_noncurses.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cava.c b/cava.c index e30310a0..5d4359a1 100644 --- a/cava.c +++ b/cava.c @@ -290,7 +290,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co output_mode = p.output; #ifndef _MSC_VER - if (output_mode != OUTPUT_NCURSES || output_mode != OUTPUT_NONCURSES) { + if (output_mode == OUTPUT_NCURSES || output_mode == OUTPUT_NONCURSES) { // Check if we're running in a tty if (strncmp(ttyname(0), "/dev/tty", 8) == 0 || strcmp(ttyname(0), "/dev/console") == 0) inAtty = 1; diff --git a/output/terminal_noncurses.c b/output/terminal_noncurses.c index df61c597..431346da 100644 --- a/output/terminal_noncurses.c +++ b/output/terminal_noncurses.c @@ -154,7 +154,6 @@ int init_terminal_noncurses(int tty, char *const fg_color_string, char *const bg #else system("setterm -cursor off"); - system("setterm -blank 0"); system("clear"); #endif @@ -417,7 +416,6 @@ void cleanup_terminal_noncurses(void) { system("setfont >/dev/null 2>&1"); system("setfont /usr/share/consolefonts/Lat2-Fixed16.psf.gz >/dev/null 2>&1"); system("setterm -cursor on"); - system("setterm -blank 10"); system("clear"); #endif printf("\033[0m\n"); From a95614bdb2a3dab59bc2d3fee149f907fd234925 Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 24 Nov 2023 20:48:21 +0100 Subject: [PATCH 05/25] use noncurses by default Uses less resources and is less prone to tearing (vsync issues) than 'ncurses' --- config.c | 5 ----- example_files/config | 7 ++++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/config.c b/config.c index c7bb269c..8446e426 100644 --- a/config.c +++ b/config.c @@ -580,12 +580,7 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors } #ifndef _MSC_VER -#ifdef NCURSES - outputMethod = strdup(iniparser_getstring(ini, "output:method", "ncurses")); -#endif -#ifndef NCURSES outputMethod = strdup(iniparser_getstring(ini, "output:method", "noncurses")); -#endif orientation = strdup(iniparser_getstring(ini, "output:orientation", "bottom")); xaxisScale = strdup(iniparser_getstring(ini, "output:xaxis", "none")); diff --git a/example_files/config b/example_files/config index ab4381eb..ecd3b97b 100644 --- a/example_files/config +++ b/example_files/config @@ -94,8 +94,9 @@ # Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl' # or 'sdl_glsl'. -# 'noncurses' uses a custom framebuffer technique and prints only changes -# from frame to frame in the terminal. 'ncurses' is default if supported. +# 'noncurses' (default) uses a buffer and cursor movements to only print +# changes from frame to frame in the terminal. Uses less resources and is less +# prone to tearing (vsync issues) than 'ncurses'. # # 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data # stream of the bar heights that can be used to send to other applications. @@ -107,7 +108,7 @@ # 'sdl' uses the Simple DirectMedia Layer to render in a graphical context. # 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or # use one of the predefined ones. -; method = ncurses +; method = noncurses # Orientation of the visualization. Can be 'bottom', 'top', 'left' or 'right'. # Default is 'bottom'. Other orientations are only supported on sdl and ncruses From c5d36aa55b3e3382fbd120468c127baa6d321794 Mon Sep 17 00:00:00 2001 From: karl Date: Tue, 28 Nov 2023 20:33:12 +0100 Subject: [PATCH 06/25] re-implement keyboard commands in noncurses #536 uses read() with VMIN and VTIME set to 0 have yet to notice the same issues we had with #379 --- cava.c | 10 ++++++---- output/terminal_noncurses.c | 9 +++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cava.c b/cava.c index 5d4359a1..6c5658f5 100644 --- a/cava.c +++ b/cava.c @@ -767,11 +767,11 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co if (output_mode == OUTPUT_NCURSES) ch = getch(); #endif - /* - // disabled key controls in non-curses mode, caused garbage on screen + +#ifndef _MSC_VER if (output_mode == OUTPUT_NONCURSES) - ch = fgetc(stdin); - */ + read(0, &ch, sizeof(ch)); +#endif switch (ch) { case 65: // key up @@ -815,6 +815,8 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co should_quit = 1; } + ch = 0; + if (should_reload) { reloadConf = true; diff --git a/output/terminal_noncurses.c b/output/terminal_noncurses.c index 431346da..4a20eea0 100644 --- a/output/terminal_noncurses.c +++ b/output/terminal_noncurses.c @@ -42,10 +42,15 @@ int setecho(int fd, int onoff) { if (tcgetattr(fd, &t) == -1) return -1; - if (onoff == 0) + if (onoff == 0) { t.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON); - else + t.c_cc[VTIME] = 0; + t.c_cc[VMIN] = 0; + } else { t.c_lflag |= (ECHO | ECHOE | ECHOK | ECHONL | ICANON); + t.c_cc[VTIME] = 0; + t.c_cc[VMIN] = 1; + } if (tcsetattr(fd, TCSANOW, &t) == -1) return -1; From e22bbb58125f549076e0a593faccadf6919bacd4 Mon Sep 17 00:00:00 2001 From: karl Date: Sun, 10 Dec 2023 10:17:55 +0100 Subject: [PATCH 07/25] make audio rate and format configurable also for pipewire --- cava.c | 8 ++++---- config.c | 5 +++-- config.h | 2 +- example_files/config | 5 +++-- input/pipewire.c | 19 ++++++++++++++++++- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/cava.c b/cava.c index 6c5658f5..3a16e85d 100644 --- a/cava.c +++ b/cava.c @@ -383,8 +383,8 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co #endif case INPUT_FIFO: - audio.rate = p.fifoSample; - audio.format = p.fifoSampleBits; + audio.rate = p.samplerate; + audio.format = p.samplebits; thr_id = pthread_create(&p_thread, NULL, input_fifo, (void *)&audio); break; #ifdef PULSE @@ -417,8 +417,8 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co #endif #ifdef PIPEWIRE case INPUT_PIPEWIRE: - audio.format = 16; - audio.rate = 44100; + audio.format = p.samplebits; + audio.rate = p.samplerate; thr_id = pthread_create(&p_thread, NULL, input_pipewire, (void *)&audio); break; #endif diff --git a/config.c b/config.c index 8446e426..90ace6ce 100644 --- a/config.c +++ b/config.c @@ -676,6 +676,9 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors free(p->audio_source); + p->samplerate = iniparser_getint(ini, "input:sample_rate", 44100); + p->samplebits = iniparser_getint(ini, "input:sample_bits", 16); + enum input_method default_input = INPUT_FIFO; for (size_t i = 0; i < ARRAY_SIZE(default_methods); i++) { enum input_method method = default_methods[i]; @@ -694,8 +697,6 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors #endif case INPUT_FIFO: p->audio_source = strdup(iniparser_getstring(ini, "input:source", "/tmp/mpd.fifo")); - p->fifoSample = iniparser_getint(ini, "input:sample_rate", 44100); - p->fifoSampleBits = iniparser_getint(ini, "input:sample_bits", 16); break; #ifdef PULSE case INPUT_PULSE: diff --git a/config.h b/config.h index 5bbb7002..aa8af234 100644 --- a/config.h +++ b/config.h @@ -97,7 +97,7 @@ struct config_params { enum orientation orientation; int userEQ_keys, userEQ_enabled, col, bgcol, autobars, stereo, raw_format, ascii_range, bit_format, gradient, gradient_count, fixedbars, framerate, bar_width, bar_spacing, - bar_height, autosens, overshoot, waves, fifoSample, fifoSampleBits, sleep_timer, sdl_width, + bar_height, autosens, overshoot, waves, samplerate, samplebits, sleep_timer, sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, non_zero_test, reverse, sync_updates, continuous_rendering, disable_blanking; }; diff --git a/example_files/config b/example_files/config index ecd3b97b..99fa6842 100644 --- a/example_files/config +++ b/example_files/config @@ -80,8 +80,6 @@ ; method = fifo ; source = /tmp/mpd.fifo -; sample_rate = 44100 -; sample_bits = 16 ; method = shmem ; source = /squeezelite-AA:BB:CC:DD:EE:FF @@ -89,6 +87,9 @@ ; method = portaudio ; source = auto +; sample_rate = 44100 +; sample_bits = 16 + [output] diff --git a/input/pipewire.c b/input/pipewire.c index 80919d7f..79333676 100644 --- a/input/pipewire.c +++ b/input/pipewire.c @@ -92,9 +92,26 @@ void *input_pipewire(void *audiodata) { data.stream = pw_stream_new_simple(pw_main_loop_get_loop(data.loop), "cava", props, &stream_events, &data); + enum spa_audio_format audio_format = SPA_AUDIO_FORMAT_S16; + + switch (data.cava_audio->format) { + case 8: + audio_format = SPA_AUDIO_FORMAT_S8; + break; + case 16: + audio_format = SPA_AUDIO_FORMAT_S16; + break; + case 24: + audio_format = SPA_AUDIO_FORMAT_S24; + break; + case 32: + audio_format = SPA_AUDIO_FORMAT_S32; + break; + }; + params[0] = spa_format_audio_raw_build( &b, SPA_PARAM_EnumFormat, - &SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_S16, .rate = data.cava_audio->rate)); + &SPA_AUDIO_INFO_RAW_INIT(.format = audio_format, .rate = data.cava_audio->rate)); pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | From 615d67855f876b4400c44c546e2d2e79d972b5f5 Mon Sep 17 00:00:00 2001 From: karl Date: Sun, 10 Dec 2023 10:18:07 +0100 Subject: [PATCH 08/25] make pipewire default if supported --- config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.c b/config.c index 90ace6ce..a53ce485 100644 --- a/config.c +++ b/config.c @@ -45,7 +45,7 @@ const char *default_shader_name[NUMBER_OF_SHADERS] = {"northern_lights.frag", "p double smoothDef[5] = {1, 1, 1, 1, 1}; enum input_method default_methods[] = { - INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PIPEWIRE, INPUT_PULSE, INPUT_WINSCAP, + INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PULSE, INPUT_PIPEWIRE, INPUT_WINSCAP, }; char *outputMethod, *orientation, *channels, *xaxisScale, *monoOption, *fragmentShader, From 2498a5c0456697734ca323c25716e372f79105a9 Mon Sep 17 00:00:00 2001 From: karl Date: Wed, 20 Dec 2023 22:33:17 +0100 Subject: [PATCH 09/25] make cavacore an actual library with cmake #487 --- CAVACORE.md | 13 ++++++++++++- CMakeLists.txt | 6 ++++++ cavacore_test.c | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100755 CMakeLists.txt diff --git a/CAVACORE.md b/CAVACORE.md index 8a52aabc..8d6a5359 100644 --- a/CAVACORE.md +++ b/CAVACORE.md @@ -40,6 +40,17 @@ This feature can be adjusted. The range of an input signal can vary a lot. cavacore can keep the output signal within range in real-time. This feature can be disabled. +# Building + +use the root CMakeLists.txt to build it: + +``` +mkdir build +cd build +cmake .. +cmake --build . +``` + # Usage -See cavacore.h for documentation and the cavacore_test.c application for how to use. \ No newline at end of file +See cavacore.h for documentation and the cavacore_test.c application for how to use. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 00000000..a52d5c46 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +# This is only for the cavacore lib, see CAVACORE.md for details +# to build cava don't use this, use the automake stuff + +cmake_minimum_required(VERSION 3.13.0) +project(cavacore) +add_library(cavacore STATIC cavacore.c) diff --git a/cavacore_test.c b/cavacore_test.c index 709b9e7d..2b16c7d7 100644 --- a/cavacore_test.c +++ b/cavacore_test.c @@ -1,6 +1,6 @@ -// cavacore standalone test app, build cava first and compile with: +// cavacore standalone test app, build cavacore lib first and compile with: // gcc -c -g cavacore_test.c -// gcc -o cavacore_test cavacore_test.o cava-cavacore.o -lm -lfftw3 +// gcc -o cavacore_test cavacore_test.o build/libcavacore.a -lm -lfftw3 #include "cavacore.h" #include From 4cbd13df3fe0467e39979c809a99c074d0c322c0 Mon Sep 17 00:00:00 2001 From: Jeremias Stotter Date: Sat, 30 Dec 2023 20:12:02 +0100 Subject: [PATCH 10/25] Fixed a memory overrun when gradient_count > 8 --- config.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/config.c b/config.c index a53ce485..3af93ee1 100644 --- a/config.c +++ b/config.c @@ -129,6 +129,15 @@ bool validate_colors(void *params, void *err) { } if (p->gradient) { + if (p->gradient_count < 2) { + write_errorf(error, "\nAtleast two colors must be given as gradient!\n"); + return false; + } + if (p->gradient_count > 8) { + write_errorf(error, "\nMaximum 8 colors can be specified as gradient!\n"); + return false; + } + for (int i = 0; i < p->gradient_count; i++) { if (!validate_color(p->gradient_colors[i], p, error)) { write_errorf( @@ -182,17 +191,6 @@ bool validate_colors(void *params, void *err) { if (p->bcolor[0] == '#') p->bgcol = 8; // default if invalid - if (p->gradient) { - - if (p->gradient_count < 2) { - write_errorf(error, "\nAtleast two colors must be given as gradient!\n"); - return false; - } - if (p->gradient_count > 8) { - write_errorf(error, "\nMaximum 8 colors can be specified as gradient!\n"); - return false; - } - } return true; } From cde8d70de9f9f8bac52298a6169eeb40f891545c Mon Sep 17 00:00:00 2001 From: bsdcode <155195419+bsdcode@users.noreply.github.com> Date: Sat, 30 Dec 2023 13:15:51 +0100 Subject: [PATCH 11/25] OSS input backend and general improved FreeBSD support - OSS: add 'enable-oss-input' configure option - OSS: set to default input method if enabled - sndio: add to input method selection sequence with high priority - sndio: fix small bug in channel setting - FreeBSD: add 'enable-cava-font' configure option, which creates vt(4) font cava.fnt from cava.psf - doc: extended documentation in example config file and in README.md --- Makefile.am | 18 +++- README.md | 75 +++++++++++++++ cava.c | 30 +++++- config.c | 12 ++- config.h | 7 ++ configure.ac | 134 ++++++++++++++++++++------ example_files/config | 27 +++++- input/common.h | 8 +- input/oss.c | 185 ++++++++++++++++++++++++++++++++++++ input/oss.h | 5 + input/sndio.c | 2 +- output/terminal_bcircle.c | 4 + output/terminal_ncurses.c | 4 + output/terminal_noncurses.c | 6 ++ 14 files changed, 471 insertions(+), 46 deletions(-) create mode 100644 input/oss.c create mode 100644 input/oss.h diff --git a/Makefile.am b/Makefile.am index a6a2740d..ec723f86 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,7 +7,7 @@ cava_SOURCES = cava.c cavacore.c config.c input/common.c input/fifo.c input/shme output/terminal_noncurses.c output/raw.c output/noritake.c cava_CPPFLAGS = -DPACKAGE=\"$(PACKAGE)\" -DVERSION=\"$(VERSION)\" \ -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE_EXTENDED \ - -DFONTDIR=\"@FONT_DIR@\" + -DFONTDIR=\"@FONT_DIR@\" -DFONTFILE=\"@FONT_FILE@\" cava_CFLAGS = -std=c99 -Wall -Wextra -Wno-unused-result -Wno-unknown-warning-option -Wno-maybe-uninitialized -Wno-vla-parameter if OSX @@ -16,7 +16,17 @@ if OSX else cava_LDADD = -lrt cava_font_dir = @FONT_DIR@ - cava_font__DATA = cava.psf + cava_font__DATA = @FONT_FILE@ +endif + +if FREEBSD +if CAVAFONT + CLEANFILES = cava.bdf cava.fnt + +cava.fnt: ${srcdir}/cava.psf + ${PSF2BDF} --fontname="-gnu-cava-medium-r-normal--16-160-75-75-c-80-iso10646-1" ${srcdir}/cava.psf cava.bdf + ${VTFONTCVT} -o cava.fnt cava.bdf +endif endif if ALSA @@ -39,6 +49,10 @@ if SNDIO cava_SOURCES += input/sndio.c endif +if OSS + cava_SOURCES += input/oss.c +endif + if NCURSES cava_SOURCES += output/terminal_ncurses.c endif diff --git a/README.md b/README.md index e738e6ab..54d806a8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ by [Karl Stavestrand](mailto:karl@stavestrand.no) - [ALSA](#alsa) - [MPD](#mpd) - [sndio](#sndio) + - [OSS](#oss) - [squeezelite](#squeezelite) - [macOS](#macos-1) - [Windows](#windows) @@ -87,6 +88,10 @@ For better a better visual experience ncurses is also recomended. All the requirements can be installed easily in all major distros: +FreeBSD + + pkg install autoconf-archive autotools fftw3 iniparser pkgconf psftools sdl2 sndio + Debian/Ubuntu: sudo apt install build-essential libfftw3-dev libasound2-dev libncursesw5-dev libpulse-dev libtool automake autoconf-archive libiniparser-dev libsdl2-2.0-0 libsdl2-dev libpipewire-0.3-dev pkgconf @@ -182,6 +187,10 @@ Or you can change `PREFIX`, for example: All distro specific instalation sources might be out of date. Please check version before reporting any issues here. +#### FreeBSD + + pkg install cava + #### openSUSE Tumbleweed users have cava in their repo. They can just use: @@ -329,6 +338,72 @@ $ sndiod -dd -s default -m mon -s monitor $ AUDIODEVICE=snd/0.monitor cava ``` +### OSS + +The audio system used on FreeBSD is the Open Sound System (OSS). +The following example demonstrates how to setup CAVA for OSS on FreeBSD: + +```sh +$ cat /dev/sndstat +Installed devices: +pcm0: (play/rec) default +pcm1: (rec) +pcm2: (play/rec) +No devices installed from userspace. +``` + +The system has three `pcm` sound devices, `pcm0`, `pcm1` and `pcm2`. `pcm0` corresponds to the analog +output jack on the rear, in which external stereo speakers are plugged in, and the analog input jack, +in which one could plug in a microphone. Because it encapsulates both, output and input, it is marked +as `play/rec`. It is also set as the `default` sound device. `pcm1` corresponds to another analog input +jack for a mic on the front side and is marked `rec`. A USB headset which an integrated mic is plugged +in an USB port and the system has created the `pcm2` sound device with `play/rec` capabilities for +it. + +In general for every `pcmX` device there is a corresponding `/dev/dspX` audio device. In this example +there are `/dev/dsp0`, `/dev/dsp1` and `/dev/dsp2` (the system creates them when needed, they are not +listet via `ls /dev` if they are currently not in use). The system also creates an implicit `/dev/dsp`, +which acts like a symlink to the `default` audio device, in this example to `/dev/dsp0`. + +Now in order to visualize the mic input in CAVA, the `source` value in the configuration file must +be set to the corresponding audio device, i.e. +```sh +[input] +source = /dev/dsp # or /dev/dsp0 for which /dev/dsp is a symlink in this example +``` +(which is already the default for CAVA) for the `pcm0` mic on the rear, or +```sh +[input] +source = /dev/dsp1 +``` +for the `pcm1` mic on the front, or +```sh +[input] +source = /dev/dsp2 +``` +for the `pcm2` mic on the USB headset. + +OSS can't record the outgoing audio on its own, i.e. the sounds from a music player or a browser which +play on the external stereo speakers through `/dev/dsp0` are not visualized in CAVA. A solution is +to use Virtual OSS. It can create virtual audio devices from existing audio devices and from which +the played back audio can be fed into CAVA: +```sh +$ doas pkg install virtual_oss +$ doas virtual_oss -Q0 -C2 -c2 -r48000 -b16 -s2048 -P /dev/dsp0 -R /dev/null -w vdsp.wav -t vdsp.ctl -T /dev/sndstat -l dsp + +$ cat /dev/sndstat +Installed devices: +pcm0: (play/rec) default +pcm1: (rec) +pcm2: (play/rec) +Installed devices from userspace: +dsp: (play/rec) +``` +It created a virtual device `dsp` from `/dev/dsp0`. Now the audio is visualized in CAVA with the default +`source = /dev/dsp` in the configuration file. Virtual OSS can be configured and started as a service +on FreeBSD. + + ### squeezelite [squeezelite](https://en.wikipedia.org/wiki/Squeezelite) is one of several software clients available for the Logitech Media Server. Squeezelite can export its audio data as shared memory, which is what this input module uses. Just adapt your [config](#configuration): diff --git a/cava.c b/cava.c index 3a16e85d..949210cd 100644 --- a/cava.c +++ b/cava.c @@ -70,6 +70,7 @@ #include "input/alsa.h" #include "input/fifo.h" +#include "input/oss.h" #include "input/pipewire.h" #include "input/portaudio.h" #include "input/pulse.h" @@ -299,18 +300,30 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co if (strncmp(ttyname(0), "/dev/ttys", 9) == 0) inAtty = 0; if (inAtty) { +#ifdef CAVAFONT // checking if cava psf font is installed in FONTDIR FILE *font_file; - font_file = fopen(FONTDIR "/cava.psf", "r"); + font_file = fopen(FONTDIR "/" FONTFILE, "r"); if (font_file) { fclose(font_file); - system("setfont " FONTDIR "/cava.psf >/dev/null 2>&1"); +#ifdef __FreeBSD__ + system("vidcontrol -f " FONTDIR "/" FONTFILE " >/dev/null 2>&1"); +#else + system("setfont " FONTDIR "/" FONTFILE " >/dev/null 2>&1"); +#endif } else { // if not it might still be available, we dont know, must try - system("setfont cava.psf >/dev/null 2>&1"); +#ifdef __FreeBSD__ + system("vidcontrol -f " FONTFILE " >/dev/null 2>&1"); +#else + system("setfont " FONTFILE " >/dev/null 2>&1"); +#endif } +#endif // CAVAFONT +#ifndef __FreeBSD__ if (p.disable_blanking) system("setterm -blank 0"); +#endif } // We use unicode block characters to draw the bars and @@ -348,6 +361,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co audio.cava_in = (double *)malloc(audio.cava_buffer_size * sizeof(double)); memset(audio.cava_in, 0, sizeof(int) * audio.cava_buffer_size); + audio.threadparams = 0; // most input threads don't adjust the parameters audio.terminate = 0; debug("starting audio thread\n"); @@ -403,6 +417,14 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co audio.rate = 44100; thr_id = pthread_create(&p_thread, NULL, input_sndio, (void *)&audio); break; +#endif +#ifdef OSS + case INPUT_OSS: + audio.format = p.samplebits; + audio.rate = p.samplerate; + audio.threadparams = 1; // OSS can adjust parameters + thr_id = pthread_create(&p_thread, NULL, input_oss, (void *)&audio); + break; #endif case INPUT_SHMEM: audio.format = 16; @@ -440,7 +462,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co nanosleep(&timeout_timer, NULL); #endif pthread_mutex_lock(&audio.lock); - if (audio.format != -1 && audio.rate != 0) + if ((audio.threadparams == 0) && (audio.format != -1) && (audio.rate != 0)) break; pthread_mutex_unlock(&audio.lock); diff --git a/config.c b/config.c index 3af93ee1..f28c7dea 100644 --- a/config.c +++ b/config.c @@ -45,19 +45,20 @@ const char *default_shader_name[NUMBER_OF_SHADERS] = {"northern_lights.frag", "p double smoothDef[5] = {1, 1, 1, 1, 1}; enum input_method default_methods[] = { - INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PULSE, INPUT_PIPEWIRE, INPUT_WINSCAP, + INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PULSE, + INPUT_PIPEWIRE, INPUT_WINSCAP, INPUT_SNDIO, INPUT_OSS, }; char *outputMethod, *orientation, *channels, *xaxisScale, *monoOption, *fragmentShader, *vertexShader; const char *input_method_names[] = { - "fifo", "portaudio", "pipewire", "alsa", "pulse", "sndio", "shmem", "winscap", + "fifo", "portaudio", "pipewire", "alsa", "pulse", "sndio", "oss", "shmem", "winscap", }; const bool has_input_method[] = { HAS_FIFO, /** Always have at least FIFO and shmem input. */ - HAS_PORTAUDIO, HAS_PIPEWIRE, HAS_ALSA, HAS_PULSE, HAS_SNDIO, HAS_SHMEM, HAS_WINSCAP, + HAS_PORTAUDIO, HAS_PIPEWIRE, HAS_ALSA, HAS_PULSE, HAS_SNDIO, HAS_OSS, HAS_SHMEM, HAS_WINSCAP, }; enum input_method input_method_by_name(const char *str) { @@ -710,6 +711,11 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors case INPUT_SNDIO: p->audio_source = strdup(iniparser_getstring(ini, "input:source", SIO_DEVANY)); break; +#endif +#ifdef OSS + case INPUT_OSS: + p->audio_source = strdup(iniparser_getstring(ini, "input:source", "/dev/dsp")); + break; #endif case INPUT_SHMEM: p->audio_source = diff --git a/config.h b/config.h index aa8af234..c60bca6c 100644 --- a/config.h +++ b/config.h @@ -37,6 +37,12 @@ #define HAS_SNDIO false #endif +#ifdef OSS +#define HAS_OSS true +#else +#define HAS_OSS false +#endif + #ifdef _MSC_VER #define HAS_WINSCAP true #define SDL true @@ -59,6 +65,7 @@ enum input_method { INPUT_ALSA, INPUT_PULSE, INPUT_SNDIO, + INPUT_OSS, INPUT_SHMEM, INPUT_WINSCAP, INPUT_MAX, diff --git a/configure.ac b/configure.ac index 35b49c54..d3fb41e0 100644 --- a/configure.ac +++ b/configure.ac @@ -9,6 +9,39 @@ AC_PROG_CC_STDC AM_PROG_LIBTOOL +AC_CANONICAL_HOST + +build_linux=no +build_windows=no +build_mac=no +build_freebsd=no + +AC_MSG_NOTICE([Checking OS]) +# Detect the target system +case "${host_os}" in + linux*) + AC_MSG_NOTICE([Linux detected]) + build_linux=yes + ;; + darwin*) + AC_MSG_NOTICE([OSX detected]) + build_mac=yes + ;; + freebsd*) + AC_MSG_NOTICE([FreeBSD detected]) + build_freebsd=yes + ;; + *) + AC_MSG_ERROR(["OS $host_os is not supported"]) + ;; +esac + +# Pass the conditionals to automake +AM_CONDITIONAL([LINUX], [test "$build_linux" = "yes"]) +AM_CONDITIONAL([OSX], [test "$build_mac" = "yes"]) +AM_CONDITIONAL([FREEBSD], [test "$build_freebsd" = "yes"]) + + dnl ############################ dnl checking if debug is enabled dnl ############################ @@ -184,11 +217,33 @@ AS_IF([test "x$enable_input_sndio" != "xno"], [ if [[ $have_sndio = "no" ]] ; then AC_MSG_NOTICE([WARNING: No sndio dev files found building without sndio support]) fi], - [have_portaudio=no] + [have_sndio=no] ) AM_CONDITIONAL([SNDIO], [test "x$have_sndio" = "xyes"]) +dnl ###################### +dnl checking for oss dev +dnl ###################### +AC_ARG_ENABLE([input_oss], + AS_HELP_STRING([--disable-input-oss], + [do not include support for input from oss]) +) + +AS_IF([test "x$enable_input_oss" != "xno"], [ + AC_CHECK_HEADERS(sys/soundcard.h, have_oss=yes, have_oss=no) + if [[ $have_oss = "yes" ]] ; then + CPPFLAGS="$CPPFLAGS -DOSS -D__BSD_VISIBLE" + fi + + if [[ $have_oss = "no" ]] ; then + AC_MSG_NOTICE([WARNING: No oss dev files found building without oss support]) + fi], + [have_oss=no] +) + +AM_CONDITIONAL([OSS], [test "x$have_oss" = "xyes"]) + dnl ###################### dnl checking for math lib dnl ###################### @@ -303,6 +358,39 @@ AS_IF([test "x$enable_output_ncurses" != "xno"], [ AM_CONDITIONAL([NCURSES], [test "x$have_ncurses" = "xyes"]) +dnl ###################### +dnl checking for cava font +dnl ###################### +AC_ARG_ENABLE([cava_font], + AS_HELP_STRING([--disable-cava-font], + [do not include support for the console cava font]) +) + +AS_IF([test "x$enable_cava_font" != "xno"], [ + have_cava_font=yes + + if [[ $build_freebsd = "yes" ]] ; then + AC_PATH_PROG(VTFONTCVT, vtfontcvt) + if [[ -z "$VTFONTCVT" ]] ; then + AC_MSG_NOTICE([WARNING: vtfontcvt not found]) + have_cava_font=no + fi + AC_PATH_PROG(PSF2BDF, psf2bdf) + if [[ -z "$PSF2BDF" ]] ; then + AC_MSG_NOTICE([WARNING: psf2bdf not found]) + have_cava_font=no + fi + if [[ $have_cava_font = "no" ]] ; then + AC_MSG_NOTICE([WARNING: Font conversion tool missing. Building without cava font supported!]) + fi + fi], + [have_cava_font=no] +) + +AS_IF([test "x$have_cava_font" = "xyes"], [CPPFLAGS="$CPPFLAGS -DCAVAFONT"], []) +AM_CONDITIONAL([CAVAFONT], [test "x$have_cava_font" = "xyes"]) + + dnl ###################### dnl checking for iniparser dnl ###################### @@ -326,41 +414,25 @@ AC_CHECK_LIB(iniparser,iniparser_load, have_iniparser=yes, have_iniparser=no) dnl ############################ dnl Set font directory dnl ############################ -DEFAULT_FONT_DIR="${datarootdir}/consolefonts" AC_ARG_VAR(FONT_DIR, [Directory where the font will be installed.]) +AC_SUBST([FONT_FILE]) + +AS_IF([test "x$have_cava_font" = "xyes"], [ + if [[ "$build_freebsd" = "yes" ]] ; then + DEFAULT_FONT_DIR="${datarootdir}/cava" + FONT_FILE="cava.fnt" + else + DEFAULT_FONT_DIR="${datarootdir}/consolefonts" + FONT_FILE="cava.psf" + fi], [ + DEFAULT_FONT_DIR= + FONT_FILE=] +) + if test -z "$FONT_DIR" ; then FONT_DIR="$DEFAULT_FONT_DIR" fi -AC_CANONICAL_HOST - -build_linux=no -build_windows=no -build_mac=no - -AC_MSG_NOTICE([Checking OS]) -# Detect the target system -case "${host_os}" in - linux*) - AC_MSG_NOTICE([Linux detected]) - build_linux=yes - ;; - darwin*) - AC_MSG_NOTICE([OSX detected]) - build_mac=yes - ;; - freebsd*) - AC_MSG_NOTICE([FreeBSD detected]) - build_linux=yes - ;; - *) - AC_MSG_ERROR(["OS $host_os is not supported"]) - ;; -esac - -# Pass the conditionals to automake -AM_CONDITIONAL([LINUX], [test "$build_linux" = "yes"]) -AM_CONDITIONAL([OSX], [test "$build_mac" = "yes"]) AC_CONFIG_FILES([Makefile]) diff --git a/example_files/config b/example_files/config index 99fa6842..6d92567c 100644 --- a/example_files/config +++ b/example_files/config @@ -52,8 +52,8 @@ [input] -# Audio capturing method. Possible methods are: 'pulse', 'alsa', 'fifo', 'sndio' or 'shmem' -# Defaults to 'pulse', 'pipewire', 'alsa' or 'fifo', in that order, dependent on what support cava was built with. +# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss' or 'shmem' +# Defaults to 'oss', 'sndio', 'pipewire', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with. # On Mac it defaults to 'portaudio' or 'fifo' # On windows this is automatic and no input settings are needed. # @@ -69,6 +69,13 @@ # For alsa 'source' will be the capture device. # For fifo 'source' will be the path to fifo-file. # For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address +# +# For sndio 'source' will be a monitor sub-device, e.g. 'snd/0.monitor'. Default: 'default', in which case a device +# should be specified with the environment variable AUDIODEVICE, e.g. on the commandline: AUDIODEVICE=snd/0.monitor cava. +# +# For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device. +# README.md contains further information on how to setup CAVA for OSS on FreeBSD. +# ; method = pulse ; source = auto @@ -87,6 +94,21 @@ ; method = portaudio ; source = auto +; method = sndio +; source = default + +; method = oss +; source = /dev/dsp + +# The sample rate and format can be configured for some input methods. Currently +# the following methods support such a configuration: 'fifo', 'pipewire' and 'oss'. +# Other methods ignore these settings. +# +# For 'oss' they are only preferred values, i.e. if the values are not supported +# by the chosen audio device, the device will use other supported values instead. +# Example: 48000 and 32, but the device only supports 44100 and 16, then it will +# use 44100 and 16. +# ; sample_rate = 44100 ; sample_bits = 16 @@ -168,6 +190,7 @@ ; continuous_rendering = 0 # disable console blank (screen saver) in tty +# (Not supported on FreeBSD) ; disable_blanking = 0 [color] diff --git a/input/common.h b/input/common.h index e7469142..3eaed7a7 100644 --- a/input/common.h +++ b/input/common.h @@ -24,9 +24,11 @@ struct audio_data { int format; unsigned int rate; unsigned int channels; - char *source; // alsa device, fifo path or pulse source - int im; // input mode alsa, fifo, pulse, portaudio, shmem or sndio - int terminate; // shared variable used to terminate audio thread + int threadparams; // shared variable used to prevent main thread from cava_init before input + // threads have finalized parameters + char *source; // alsa device, fifo path or pulse source + int im; // input mode alsa, fifo, pulse, portaudio, shmem or sndio + int terminate; // shared variable used to terminate audio thread char error_message[1024]; int samples_counter; int IEEE_FLOAT; diff --git a/input/oss.c b/input/oss.c new file mode 100644 index 00000000..3c2f6a5e --- /dev/null +++ b/input/oss.c @@ -0,0 +1,185 @@ +#include +#include + +#include +#include + +#include "input/common.h" +#include "input/oss.h" + +static bool set_format(int fd, struct audio_data *audio) { + // CAVA favors signed and little endian (sle) formats. It might actually not work correctly if + // we feed it an unsigned or big endian format. Therefore we prefer to select one of the + // supported sle formats, if any exist. + + // The favored sle formats. 16bit has priority, followed by "bigger is better". + static const int fmts_sle[] = {AFMT_S16_LE, AFMT_S32_LE, AFMT_S24_LE, AFMT_S8}; + + // Bitmasks of formats categorized by bitlength. + static const int fmts_8bits = AFMT_U8 | AFMT_S8; + static const int fmts_16bits = AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE; + static const int fmts_24bits = AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE; + static const int fmts_32bits = AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE; + + int fmts; + int fmt; + + // Get all supported formats from the audio device. + if (ioctl(fd, SNDCTL_DSP_GETFMTS, &fmts) == -1) { + fprintf(stderr, __FILE__ ": ioctl(SNDCTL_DSP_GETFMTS) failed: %s\n", strerror(errno)); + return false; + } + + // Determine the sle format for the requested bitlength. + if (audio->format <= 8) + fmt = AFMT_S8; + else if (audio->format <= 16) + fmt = AFMT_S16_LE; + else if (audio->format <= 24) + fmt = AFMT_S24_LE; + else + fmt = AFMT_S32_LE; + + // If the requested format is not available then test for the other sle formats. + if (!(fmts & fmt)) { + for (size_t i = 0; i < sizeof(fmts_sle) / sizeof(fmts_sle[0]); ++i) { + if (fmts & fmts_sle[i]) { + fmt = fmts_sle[i]; + break; + } + } + } + + // Set the format of the device. If not supported then OSS will adjust to a supported format. + if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) == -1) { + fprintf(stderr, __FILE__ ": ioctl(SNDCTL_DSP_SETFMT) failed: %s\n", strerror(errno)); + return false; + } + + // Determine the actual bitlength of the returned format. + if (fmts_8bits & fmt) + audio->format = 8; + else if (fmts_16bits & fmt) + audio->format = 16; + else if (fmts_24bits & fmt) + audio->format = 24; + else if (fmts_32bits & fmt) + audio->format = 32; + else { + fprintf(stderr, __FILE__ ": No support for 8, 16, 24 or 32 bits in OSS source '%s'.\n", + audio->source); + return false; + } + + return true; +} + +static bool set_channels(int fd, struct audio_data *audio) { + // Try to set the requested channels, OSS will adjust to a supported value. If CAVA doesn't + // support the final value then it will complain later. + int channels = audio->channels; + + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) == -1) { + fprintf(stderr, __FILE__ ": ioctl(SNDCTL_DSP_CHANNELS) failed: %s\n", strerror(errno)); + return false; + } + + audio->channels = channels; + + return true; +} + +static bool set_rate(int fd, struct audio_data *audio) { + // Try to set the requested rate, OSS will adjust to a supported value. If CAVA doesn't support + // the final value then it will complain later. + int rate = audio->rate; + + if (ioctl(fd, SNDCTL_DSP_SPEED, &rate) == -1) { + fprintf(stderr, __FILE__ ": ioctl(SNDCTL_DSP_SPEED) failed: %s\n", strerror(errno)); + return false; + } + + audio->rate = rate; + + return true; +} + +static void signal_threadparams(struct audio_data *audio) { + pthread_mutex_lock(&audio->lock); + audio->threadparams = 0; + pthread_mutex_unlock(&audio->lock); +} + +static void signal_terminate(struct audio_data *audio) { + pthread_mutex_lock(&audio->lock); + audio->terminate = 1; + pthread_mutex_unlock(&audio->lock); +} + +void *input_oss(void *data) { + static const int flags = O_RDONLY; + + struct audio_data *audio = (struct audio_data *)data; + int bytes; + size_t buf_size; + ssize_t rd; + + int fd = -1; + void *buf = NULL; + + bool success = false; + + if ((fd = open(audio->source, flags, 0)) == -1) { + fprintf(stderr, __FILE__ ": Could not open OSS source '%s': %s\n", audio->source, + strerror(errno)); + goto cleanup; + } + + // For OSS it's adviced to determine format, channels and rate in this order. + if (!(set_format(fd, audio) && set_channels(fd, audio) && set_rate(fd, audio))) + goto cleanup; + + // Parameters finalized. Signal main thread. + signal_threadparams(audio); + + // OSS uses 32 bits for 24bit. + if (audio->format == 24) + bytes = 4; // = 32 / 8 + else + bytes = audio->format / 8; + + buf_size = audio->input_buffer_size * bytes; + + if ((buf = malloc(buf_size)) == NULL) { + fprintf(stderr, __FILE__ ": malloc() failed: %s\n", strerror(errno)); + goto cleanup; + } + + while (audio->terminate != 1) { + if ((rd = read(fd, buf, buf_size)) == -1) { + fprintf(stderr, __FILE__ ": read() failed: %s\n", strerror(errno)); + goto cleanup; + } else if (rd == 0) + signal_terminate(audio); + else + write_to_cava_input_buffers(rd / bytes, buf, audio); + } + + success = true; + +cleanup: + free(buf); + + if ((fd >= 0) && (close(fd) == -1)) { + fprintf(stderr, __FILE__ ": close() failed: %s\n", strerror(errno)); + success = false; + } + + signal_threadparams(audio); + signal_terminate(audio); + + if (!success) + exit(EXIT_FAILURE); + + return NULL; +} diff --git a/input/oss.h b/input/oss.h new file mode 100644 index 00000000..ab37508a --- /dev/null +++ b/input/oss.h @@ -0,0 +1,5 @@ +// header file for oss, part of cava. + +#pragma once + +void *input_oss(void *data); diff --git a/input/sndio.c b/input/sndio.c index cd602e49..a050b14c 100644 --- a/input/sndio.c +++ b/input/sndio.c @@ -15,7 +15,7 @@ void *input_sndio(void *data) { par.le = 1; par.rate = audio->rate; ; - par.rchan = 2; + par.rchan = audio->channels; par.appbufsz = sizeof(buf) / par.rchan; if ((hdl = sio_open(audio->source, SIO_REC, 0)) == NULL) { diff --git a/output/terminal_bcircle.c b/output/terminal_bcircle.c index 764e5144..0e2bb9cb 100644 --- a/output/terminal_bcircle.c +++ b/output/terminal_bcircle.c @@ -77,9 +77,13 @@ int draw_terminal_bcircle(int tty, int h, int w, int f[]) { // general: cleanup void cleanup_terminal_bcircle(void) { echo(); +#ifdef __FreeBSD__ + system("vidcontrol -f >/dev/null 2>&1"); +#else system("setfont >/dev/null 2>&1"); system("setfont /usr/share/consolefonts/Lat2-Fixed16.psf.gz >/dev/null 2>&1"); system("setterm -blank 10"); +#endif endwin(); system("clear"); } diff --git a/output/terminal_ncurses.c b/output/terminal_ncurses.c index 96786b0b..2bd3e6ac 100644 --- a/output/terminal_ncurses.c +++ b/output/terminal_ncurses.c @@ -311,8 +311,12 @@ int draw_terminal_ncurses(int is_tty, int dimension_value, int dimension_bar, in // general: cleanup void cleanup_terminal_ncurses(void) { echo(); +#ifdef __FreeBSD__ + system("vidcontrol -f >/dev/null 2>&1"); +#else system("setfont >/dev/null 2>&1"); system("setfont /usr/share/consolefonts/Lat2-Fixed16.psf.gz >/dev/null 2>&1"); +#endif /*for(int i = 0; i < gradient_size; ++i) { if(the_color_redefinitions[i].color) { diff --git a/output/terminal_noncurses.c b/output/terminal_noncurses.c index 4a20eea0..0772fe26 100644 --- a/output/terminal_noncurses.c +++ b/output/terminal_noncurses.c @@ -158,7 +158,9 @@ int init_terminal_noncurses(int tty, char *const fg_color_string, char *const bg system("cls"); #else +#ifndef __FreeBSD__ system("setterm -cursor off"); +#endif system("clear"); #endif @@ -418,9 +420,13 @@ void cleanup_terminal_noncurses(void) { system("cls"); #else setecho(STDIN_FILENO, 1); +#ifdef __FreeBSD__ + system("vidcontrol -f >/dev/null 2>&1"); +#else system("setfont >/dev/null 2>&1"); system("setfont /usr/share/consolefonts/Lat2-Fixed16.psf.gz >/dev/null 2>&1"); system("setterm -cursor on"); +#endif system("clear"); #endif printf("\033[0m\n"); From 1e3708a9874fcdb4b2db701ff08716dba9329ff5 Mon Sep 17 00:00:00 2001 From: bsdcode <155195419+bsdcode@users.noreply.github.com> Date: Sun, 31 Dec 2023 12:26:53 +0100 Subject: [PATCH 12/25] Limit OSS to FreeBSD build --- configure.ac | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index d3fb41e0..5020225c 100644 --- a/configure.ac +++ b/configure.ac @@ -231,13 +231,17 @@ AC_ARG_ENABLE([input_oss], ) AS_IF([test "x$enable_input_oss" != "xno"], [ - AC_CHECK_HEADERS(sys/soundcard.h, have_oss=yes, have_oss=no) - if [[ $have_oss = "yes" ]] ; then - CPPFLAGS="$CPPFLAGS -DOSS -D__BSD_VISIBLE" - fi + have_oss=no - if [[ $have_oss = "no" ]] ; then - AC_MSG_NOTICE([WARNING: No oss dev files found building without oss support]) + if [[ $build_freebsd = "yes" ]] ; then + AC_CHECK_HEADERS(sys/soundcard.h, have_oss=yes, have_oss=no) + if [[ $have_oss = "yes" ]] ; then + CPPFLAGS="$CPPFLAGS -DOSS -D__BSD_VISIBLE" + fi + + if [[ $have_oss = "no" ]] ; then + AC_MSG_NOTICE([WARNING: No oss dev files found building without oss support]) + fi fi], [have_oss=no] ) From db3084be224932e14f385e912da77c8f825800aa Mon Sep 17 00:00:00 2001 From: karl Date: Tue, 9 Jan 2024 19:26:04 +0100 Subject: [PATCH 13/25] bumped hardcoded version to 0.10.0 --- autogen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen.sh b/autogen.sh index 161b26d1..2ecfdfa0 100755 --- a/autogen.sh +++ b/autogen.sh @@ -3,7 +3,7 @@ if [ -d .git ]; then git describe --always --tags --dirty > version # get version from git else - echo 0.9.1 > version # hard coded versions + echo 0.10.0 > version # hard coded versions fi libtoolize From f25c17ac701830a7e30c8a5eaa1a49662c200a56 Mon Sep 17 00:00:00 2001 From: bsdcode <155195419+bsdcode@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:34:05 +0100 Subject: [PATCH 14/25] JACK support - JACK input backend support - JACK priority after sndio (and both after pipewire) - doc: extended documentation in README.md - fix getPulseDefaultSink compilation warning --- Makefile.am | 4 + README.md | 120 ++++++++++++++--- cava.c | 9 ++ config.c | 24 +++- config.h | 13 +- configure.ac | 23 ++++ example_files/config | 19 ++- input/common.c | 14 +- input/common.h | 6 +- input/jack.c | 304 +++++++++++++++++++++++++++++++++++++++++++ input/jack.h | 5 + input/oss.c | 38 +++--- input/pulse.h | 2 +- 13 files changed, 526 insertions(+), 55 deletions(-) create mode 100644 input/jack.c create mode 100644 input/jack.h diff --git a/Makefile.am b/Makefile.am index ec723f86..a4f7525c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,6 +53,10 @@ if OSS cava_SOURCES += input/oss.c endif +if JACK + cava_SOURCES += input/jack.c +endif + if NCURSES cava_SOURCES += output/terminal_ncurses.c endif diff --git a/README.md b/README.md index 54d806a8..d9ac72b9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ by [Karl Stavestrand](mailto:karl@stavestrand.no) - [MPD](#mpd) - [sndio](#sndio) - [OSS](#oss) + - [JACK](#jack) - [squeezelite](#squeezelite) - [macOS](#macos-1) - [Windows](#windows) @@ -77,11 +78,12 @@ The development lib of one of these audio frameworks, depending on your distro: * Pipewire * Portaudio * Sndio +* JACK Optional components: * SDL2 dev files -Only FFTW and the other build tools are actually required for CAVA to compile, but this will only give you the ability to read from fifo files. To more easly grab audio from your system pulseaudio, alsa, sndio or portaudio dev files are recommended (depending on what audio system you are using). Not sure how to get the pulseaudio dev files for other distros than debian/ubuntu or if they are bundled in pulseaudio. +Only FFTW and the other build tools are actually required for CAVA to compile, but this will only give you the ability to read from fifo files. To more easly grab audio from your system pulseaudio, alsa, sndio, jack or portaudio dev files are recommended (depending on what audio system you are using). Not sure how to get the pulseaudio dev files for other distros than debian/ubuntu or if they are bundled in pulseaudio. For better a better visual experience ncurses is also recomended. @@ -90,11 +92,17 @@ All the requirements can be installed easily in all major distros: FreeBSD - pkg install autoconf-archive autotools fftw3 iniparser pkgconf psftools sdl2 sndio + pkg install autoconf autoconf-archive automake fftw3 iniparser jackit libglvnd libtool pkgconf psftools sdl2 sndio + +Additionally, run these commands on FreeBSD before building: + + export CFLAGS="-I/usr/local/include" + export LDFLAGS="-L/usr/local/lib" + Debian/Ubuntu: - sudo apt install build-essential libfftw3-dev libasound2-dev libncursesw5-dev libpulse-dev libtool automake autoconf-archive libiniparser-dev libsdl2-2.0-0 libsdl2-dev libpipewire-0.3-dev pkgconf + sudo apt install build-essential libfftw3-dev libasound2-dev libncursesw5-dev libpulse-dev libtool automake autoconf-archive libiniparser-dev libsdl2-2.0-0 libsdl2-dev libpipewire-0.3-dev libjack-jackd2-dev pkgconf ArchLinux: @@ -340,9 +348,12 @@ $ AUDIODEVICE=snd/0.monitor cava ### OSS +Set + + method = oss + The audio system used on FreeBSD is the Open Sound System (OSS). The following example demonstrates how to setup CAVA for OSS on FreeBSD: - ```sh $ cat /dev/sndstat Installed devices: @@ -351,12 +362,11 @@ pcm1: (rec) pcm2: (play/rec) No devices installed from userspace. ``` - The system has three `pcm` sound devices, `pcm0`, `pcm1` and `pcm2`. `pcm0` corresponds to the analog output jack on the rear, in which external stereo speakers are plugged in, and the analog input jack, in which one could plug in a microphone. Because it encapsulates both, output and input, it is marked as `play/rec`. It is also set as the `default` sound device. `pcm1` corresponds to another analog input -jack for a mic on the front side and is marked `rec`. A USB headset which an integrated mic is plugged +jack for a mic on the front side and is marked `rec`. A USB headset with an integrated mic is plugged in an USB port and the system has created the `pcm2` sound device with `play/rec` capabilities for it. @@ -367,20 +377,17 @@ which acts like a symlink to the `default` audio device, in this example to `/de Now in order to visualize the mic input in CAVA, the `source` value in the configuration file must be set to the corresponding audio device, i.e. -```sh -[input] -source = /dev/dsp # or /dev/dsp0 for which /dev/dsp is a symlink in this example -``` + + source = /dev/dsp # or /dev/dsp0 for which /dev/dsp is a symlink in this example + (which is already the default for CAVA) for the `pcm0` mic on the rear, or -```sh -[input] -source = /dev/dsp1 -``` + + source = /dev/dsp1 + for the `pcm1` mic on the front, or -```sh -[input] -source = /dev/dsp2 -``` + + source = /dev/dsp2 + for the `pcm2` mic on the USB headset. OSS can't record the outgoing audio on its own, i.e. the sounds from a music player or a browser which @@ -403,6 +410,83 @@ It created a virtual device `dsp` from `/dev/dsp0`. Now the audio is visualized `source = /dev/dsp` in the configuration file. Virtual OSS can be configured and started as a service on FreeBSD. +### JACK + +Set + + method = jack + +The JACK Audio Connection Kit (JACK) is a professional sound server API which is available on several +operating systems, e.g. FreeBSD and Linux. + +CAVA is a JACK client with the base client name `cava` and adheres to the standard server start and +stop behaviour, i.e. CAVA starts a JACK server if none is already running and the environment variable +`JACK_START_SERVER` is defined, in which case the server also stops when all clients have exited. The +`source` in the CAVA configuration file specifies the name of the JACK server to which CAVA tries to +connect to. The default value is `default`, which is also the default JACK server name. The value can +be empty, in which case it implies `default`. Therefore the following three entries are equivalent: + + ; source = default + source = default + source = + +One exception is the combination of an empty `source` entry and the environment variable `JACK_DEFAULT_SERVER`. +If the environment variable is defined, e.g. `export JACK_DEFAULT_SERVER=foo`, then the following entries +are equivalent: + + source = foo + source = + +Consult the manpage `jackd(1)` for further information regarding configuration and startup of a JACK +server. + +CAVA creates terminal audio-typed (so no MIDI support) input ports. These ports can connect to output +ports of other JACK clients, e.g. connect to the output ports of a music player and CAVA will visualize +the music. Currently CAVA supports up to two input ports, i.e. it supports mono and stereo. The number +of input ports can be controlled via the `channels` option in the input section of the configuration +file: + + channels = 1 # one input port, mono + +or + + channels = 2 # two input ports, stereo + +The port's short name is simply `M` for mono, and `L` and `R` for stereo. The full name of the input +port according to the base client name is `cava:M` for mono, and `cava:L` and `cava:R` for stereo. + +Currently CAVA doesn't connect its ports to other client's ports automatically, i.e. on program start +CAVA isn't connected to any other program and won't connect during execution on its own. There are +connection management programs in order to control and manage the connection between ports of the different +client programs. Some well known connection managers with a graphical user interface include QjackCtl +and Cadence. The JACK package itself often comes with CLI tools. Depending on the operating system +they might need to get installed separately, e.g. on FreeBSD +```sh +$ doas pkg install jack-example-tools +``` +Among the tools are the programs `jack_lsp` and `jack_connect`. These two tools are enough to list +and connect ports on the commandline. The following example demonstrates how to setup connections with +these tools: +```sh +$ jack_lsp +system:capture_1 +system:capture_2 +system:playback_1 +system:playback_2 +cava:L +moc:output0 +moc:output1 +cava:R +``` +This listing shows all full port names that are currently available. These correspond to two external +JACK clients (cava and moc) and one internal JACK client (system). There are also `-p` and `-c` switches +for `jack_lsp`, with which the types and current connections between the ports are listed. Connect +the ports of cava and moc: +```sh +$ jack_connect cava:L moc:output0 +$ jack_connect cava:R moc:output1 +``` +Now CAVA visualizes the outgoing audio from MOC. ### squeezelite [squeezelite](https://en.wikipedia.org/wiki/Squeezelite) is one of several software clients available for the Logitech Media Server. Squeezelite can export its audio data as shared memory, which is what this input module uses. diff --git a/cava.c b/cava.c index 949210cd..93dc478c 100644 --- a/cava.c +++ b/cava.c @@ -70,6 +70,7 @@ #include "input/alsa.h" #include "input/fifo.h" +#include "input/jack.h" #include "input/oss.h" #include "input/pipewire.h" #include "input/portaudio.h" @@ -422,9 +423,17 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co case INPUT_OSS: audio.format = p.samplebits; audio.rate = p.samplerate; + audio.channels = p.channels; audio.threadparams = 1; // OSS can adjust parameters thr_id = pthread_create(&p_thread, NULL, input_oss, (void *)&audio); break; +#endif +#ifdef JACK + case INPUT_JACK: + audio.channels = p.channels; + audio.threadparams = 1; // JACK server provides parameters + thr_id = pthread_create(&p_thread, NULL, input_jack, (void *)&audio); + break; #endif case INPUT_SHMEM: audio.format = 16; diff --git a/config.c b/config.c index f28c7dea..4a9d58d8 100644 --- a/config.c +++ b/config.c @@ -45,20 +45,21 @@ const char *default_shader_name[NUMBER_OF_SHADERS] = {"northern_lights.frag", "p double smoothDef[5] = {1, 1, 1, 1, 1}; enum input_method default_methods[] = { - INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PULSE, - INPUT_PIPEWIRE, INPUT_WINSCAP, INPUT_SNDIO, INPUT_OSS, + INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PULSE, INPUT_JACK, + INPUT_SNDIO, INPUT_PIPEWIRE, INPUT_WINSCAP, INPUT_OSS, }; char *outputMethod, *orientation, *channels, *xaxisScale, *monoOption, *fragmentShader, *vertexShader; const char *input_method_names[] = { - "fifo", "portaudio", "pipewire", "alsa", "pulse", "sndio", "oss", "shmem", "winscap", + "fifo", "portaudio", "pipewire", "alsa", "pulse", "sndio", "oss", "jack", "shmem", "winscap", }; const bool has_input_method[] = { HAS_FIFO, /** Always have at least FIFO and shmem input. */ - HAS_PORTAUDIO, HAS_PIPEWIRE, HAS_ALSA, HAS_PULSE, HAS_SNDIO, HAS_OSS, HAS_SHMEM, HAS_WINSCAP, + HAS_PORTAUDIO, HAS_PIPEWIRE, HAS_ALSA, HAS_PULSE, HAS_SNDIO, + HAS_OSS, HAS_JACK, HAS_SHMEM, HAS_WINSCAP, }; enum input_method input_method_by_name(const char *str) { @@ -328,7 +329,7 @@ bool validate_config(struct config_params *p, struct error_s *error) { if (p->stereo == -1) { write_errorf( error, - "output channels %s is not supported, supported channelss are: 'mono' and 'stereo'\n", + "output channels %s is not supported, supported channels are: 'mono' and 'stereo'\n", channels); return false; } @@ -389,6 +390,12 @@ bool validate_config(struct config_params *p, struct error_s *error) { } p->sens = p->sens / 100; + // validate: channels + if (p->channels <= 1) + p->channels = 1; + else + p->channels = 2; + return validate_colors(p, error); } @@ -546,7 +553,6 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors monoOption = malloc(sizeof(char) * 32); p->raw_target = malloc(sizeof(char) * 129); p->data_format = malloc(sizeof(char) * 32); - channels = malloc(sizeof(char) * 32); orientation = malloc(sizeof(char) * 32); vertexShader = malloc(sizeof(char) * PATH_MAX / 2); fragmentShader = malloc(sizeof(char) * PATH_MAX / 2); @@ -677,6 +683,7 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->samplerate = iniparser_getint(ini, "input:sample_rate", 44100); p->samplebits = iniparser_getint(ini, "input:sample_bits", 16); + p->channels = iniparser_getint(ini, "input:channels", 2); enum input_method default_input = INPUT_FIFO; for (size_t i = 0; i < ARRAY_SIZE(default_methods); i++) { @@ -716,6 +723,11 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors case INPUT_OSS: p->audio_source = strdup(iniparser_getstring(ini, "input:source", "/dev/dsp")); break; +#endif +#ifdef JACK + case INPUT_JACK: + p->audio_source = strdup(iniparser_getstring(ini, "input:source", "default")); + break; #endif case INPUT_SHMEM: p->audio_source = diff --git a/config.h b/config.h index c60bca6c..165f9f5a 100644 --- a/config.h +++ b/config.h @@ -43,6 +43,12 @@ #define HAS_OSS false #endif +#ifdef JACK +#define HAS_JACK true +#else +#define HAS_JACK false +#endif + #ifdef _MSC_VER #define HAS_WINSCAP true #define SDL true @@ -66,6 +72,7 @@ enum input_method { INPUT_PULSE, INPUT_SNDIO, INPUT_OSS, + INPUT_JACK, INPUT_SHMEM, INPUT_WINSCAP, INPUT_MAX, @@ -104,9 +111,9 @@ struct config_params { enum orientation orientation; int userEQ_keys, userEQ_enabled, col, bgcol, autobars, stereo, raw_format, ascii_range, bit_format, gradient, gradient_count, fixedbars, framerate, bar_width, bar_spacing, - bar_height, autosens, overshoot, waves, samplerate, samplebits, sleep_timer, sdl_width, - sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, non_zero_test, reverse, - sync_updates, continuous_rendering, disable_blanking; + bar_height, autosens, overshoot, waves, samplerate, samplebits, channels, sleep_timer, + sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, + non_zero_test, reverse, sync_updates, continuous_rendering, disable_blanking; }; struct error_s { diff --git a/configure.ac b/configure.ac index 5020225c..c70a5860 100644 --- a/configure.ac +++ b/configure.ac @@ -248,6 +248,29 @@ AS_IF([test "x$enable_input_oss" != "xno"], [ AM_CONDITIONAL([OSS], [test "x$have_oss" = "xyes"]) +dnl ###################### +dnl checking for jack dev +dnl ###################### +AC_ARG_ENABLE([input_jack], + AS_HELP_STRING([--disable-input-jack], + [do not include support for input from jack]) +) + +AS_IF([test "x$enable_input_jack" != "xno"], [ + PKG_CHECK_MODULES(JACK, jack, have_jack=yes, have_jack=no) + if [[ $have_jack = "yes" ]] ; then + LIBS="$LIBS $JACK_LIBS" + CPPFLAGS="$CPPFLAGS -DJACK $JACK_CFLAGS" + fi + + if [[ $have_jack = "no" ]] ; then + AC_MSG_NOTICE([WARNING: No jack dev files found building without jack support]) + fi], + [have_jack=no] +) + +AM_CONDITIONAL([JACK], [test "x$have_jack" = "xyes"]) + dnl ###################### dnl checking for math lib dnl ###################### diff --git a/example_files/config b/example_files/config index 6d92567c..4c6ca32a 100644 --- a/example_files/config +++ b/example_files/config @@ -52,8 +52,8 @@ [input] -# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss' or 'shmem' -# Defaults to 'oss', 'sndio', 'pipewire', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with. +# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem' +# Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with. # On Mac it defaults to 'portaudio' or 'fifo' # On windows this is automatic and no input settings are needed. # @@ -76,6 +76,9 @@ # For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device. # README.md contains further information on how to setup CAVA for OSS on FreeBSD. # +# For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'. +# README.md contains further information on how to setup CAVA for JACK. +# ; method = pulse ; source = auto @@ -100,17 +103,25 @@ ; method = oss ; source = /dev/dsp +; method = jack +; source = default + # The sample rate and format can be configured for some input methods. Currently # the following methods support such a configuration: 'fifo', 'pipewire' and 'oss'. # Other methods ignore these settings. # +# The channels can be configured for some input methods. Currently the following +# methods support such a configuration: 'oss' and 'jack'. +# Other methods ignore these settings. +# # For 'oss' they are only preferred values, i.e. if the values are not supported # by the chosen audio device, the device will use other supported values instead. -# Example: 48000 and 32, but the device only supports 44100 and 16, then it will -# use 44100 and 16. +# Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it +# will use 44100, 16 and 1. # ; sample_rate = 44100 ; sample_bits = 16 +; channels = 2 [output] diff --git a/input/common.c b/input/common.c index 9acbcb9f..2f1d9585 100644 --- a/input/common.c +++ b/input/common.c @@ -52,4 +52,16 @@ void reset_output_buffers(struct audio_data *data) { audio->cava_in[n] = 0; } pthread_mutex_unlock(&audio->lock); -} \ No newline at end of file +} + +void signal_threadparams(struct audio_data *audio) { + pthread_mutex_lock(&audio->lock); + audio->threadparams = 0; + pthread_mutex_unlock(&audio->lock); +} + +void signal_terminate(struct audio_data *audio) { + pthread_mutex_lock(&audio->lock); + audio->terminate = 1; + pthread_mutex_unlock(&audio->lock); +} diff --git a/input/common.h b/input/common.h index 3eaed7a7..a98f97f5 100644 --- a/input/common.h +++ b/input/common.h @@ -25,17 +25,19 @@ struct audio_data { unsigned int rate; unsigned int channels; int threadparams; // shared variable used to prevent main thread from cava_init before input - // threads have finalized parameters + // threads have finalized parameters (0=allow cava_init, 1=disallow) char *source; // alsa device, fifo path or pulse source int im; // input mode alsa, fifo, pulse, portaudio, shmem or sndio int terminate; // shared variable used to terminate audio thread char error_message[1024]; int samples_counter; - int IEEE_FLOAT; + int IEEE_FLOAT; // format for 32bit (0=int, 1=float) pthread_mutex_t lock; }; void reset_output_buffers(struct audio_data *data); +void signal_threadparams(struct audio_data *data); +void signal_terminate(struct audio_data *data); int write_to_cava_input_buffers(int16_t size, unsigned char *buf, void *data); diff --git a/input/jack.c b/input/jack.c new file mode 100644 index 00000000..cfa85336 --- /dev/null +++ b/input/jack.c @@ -0,0 +1,304 @@ +#include +#include + +#include + +#include "input/common.h" +#include "input/jack.h" + +// CAVA is hard-coded to a maximum of 2 channels, i.e. stereo. +#define MAX_CHANNELS 2 + +typedef jack_default_audio_sample_t sample_t; + +struct jack_data { + struct audio_data *audio; // CAVA ctx + + jack_client_t *client; // JACK client + jack_port_t *port[MAX_CHANNELS]; // input ports + jack_nframes_t nframes; // number of samples per port + sample_t *buf; // samples buffer + + int shutdown; // JACK shutdown signal (0=keep, 1=shutdown) +}; + +static bool set_rate(struct jack_data *jack) { + // Query sample rate from JACK server. If CAVA doesn't support the final value then it will + // complain later. + jack_nframes_t rate = jack_get_sample_rate(jack->client); + + if (rate <= 0) { + fprintf(stderr, __FILE__ ": jack_get_sample_rate() failed.\n"); + return false; + } + + jack->audio->rate = rate; + + return true; +} + +static bool set_format(struct jack_data *jack) { + // JACK returns 32bit float data. + jack->audio->format = 32; + jack->audio->IEEE_FLOAT = 1; + return true; +} + +static bool set_channels(struct jack_data *jack) { + // Try to create terminal audio-typed input ports for the requested number of channels. These + // ports can receive the audio data from other JACK clients. + static const char *port_name[MAX_CHANNELS][MAX_CHANNELS] = {/* mono */ {"M", NULL}, + /* stereo */ {"L", "R"}}; + static const char port_type[] = JACK_DEFAULT_AUDIO_TYPE; + static const unsigned long flags = JackPortIsInput | JackPortIsTerminal; + + int channels; + int chtype; + int ch; + + // Limit the requested channels in case CAVA becomes surround-aware and MAX_CHANNELS hasn't + // adapted yet. + channels = jack->audio->channels > MAX_CHANNELS ? MAX_CHANNELS : jack->audio->channels; + + // Determines the row in the 'port_name' table, i.e. mono, stereo, etc... + chtype = channels - 1; + + // Try to create a port for every requested channel. After the for-loop the variable 'ch' holds + // the number of actual created ports. + for (ch = 0; ch < channels; ++ch) { + if ((jack->port[ch] = jack_port_register(jack->client, port_name[chtype][ch], port_type, + flags, 0)) == NULL) + break; + } + + // If not even one port was created, then there was a deeper problem. + if (ch == 0) { + fprintf(stderr, __FILE__ ": jack_port_register('%s') failed.\n", port_name[chtype][0]); + return false; + } + + // If less ports were created than channels requested, then the JACK server is probably not + // configured for the requested channels, e.g. we requested stereo but the server only provides + // mono. In this case we rename the created ports according to the resulting table row. + if (ch < channels) { + int chtype_new; + + channels = ch; + chtype_new = channels - 1; + + for (ch = 0; ch < channels; ++ch) { + int err; + + if ((err = jack_port_rename(jack->client, jack->port[ch], port_name[chtype_new][ch])) != + 0) { + fprintf(stderr, __FILE__ ": jack_port_rename('%s', '%s') failed: 0x%x\n", + port_name[chtype][ch], port_name[chtype_new][ch], err); + return false; + } + } + } + + jack->audio->channels = channels; + + return true; +} + +static int on_buffer_size(jack_nframes_t nframes, void *arg) { + // Buffersize changes should never happen in CAVA! + struct jack_data *jack = arg; + + if ((jack->shutdown == 1) || (jack->audio->terminate == 1)) + return 0; + + if (jack->nframes != nframes) { + fprintf(stderr, __FILE__ ": Unexpected change of JACK port buffersize! Aborting!\n"); + jack->shutdown = 1; + return 1; + } + + return 0; +} + +static int on_process(jack_nframes_t nframes, void *arg) { + // Interleave samples from separate ports and feed them to CAVA. + struct jack_data *jack = arg; + sample_t *buf[MAX_CHANNELS]; + unsigned char *buf_cava; + + if ((jack->shutdown == 1) || (jack->audio->terminate == 1)) + return 0; + + for (unsigned int i = 0; i < jack->audio->channels; ++i) { + if ((buf[i] = jack_port_get_buffer(jack->port[i], nframes)) == NULL) { + fprintf(stderr, __FILE__ ": jack_port_get_buffer('%s') failed.\n", + jack_port_name(jack->port[i])); + jack->shutdown = 1; + return 1; + } + } + + switch (jack->audio->channels) { + case 1: + // If mono then no interleaving needed, feed into CAVA directly. + buf_cava = (unsigned char *)buf[0]; + break; + case 2: + // If stereo then unroll interleaving manually. + for (jack_nframes_t i = 0; i < nframes; ++i) { + jack->buf[2 * i + 0] = buf[0][i]; + jack->buf[2 * i + 1] = buf[1][i]; + } + + buf_cava = (unsigned char *)jack->buf; + break; + default: + // Else to the loops. + for (jack_nframes_t i = 0; i < nframes; ++i) { + for (unsigned int j = 0; j < jack->audio->channels; ++j) + jack->buf[jack->audio->channels * i + j] = buf[j][i]; + } + + buf_cava = (unsigned char *)jack->buf; + break; + } + + write_to_cava_input_buffers(nframes * jack->audio->channels, buf_cava, jack->audio); + + return 0; +} + +static int on_sample_rate(jack_nframes_t nframes, void *arg) { + // Sample rate changes are not supported in CAVA! + struct jack_data *jack = arg; + + if ((jack->shutdown == 1) || (jack->audio->terminate == 1)) + return 0; + + if (jack->audio->rate != nframes) { + fprintf(stderr, __FILE__ ": Unexpected change of JACK sample rate! Aborting!\n"); + jack->shutdown = 1; + return 1; + } + + return 0; +} + +static void on_shutdown(void *arg) { ((struct jack_data *)arg)->shutdown = 1; } + +void *input_jack(void *data) { + static const char client_name[] = "cava"; + static const jack_options_t options = JackNullOption | JackServerName; + static const struct timespec rqtp = {.tv_sec = 0, .tv_nsec = 1000000}; // 1ms nsleep + + struct audio_data *audio = data; + char *server_name; + jack_status_t status; + int err; + + struct jack_data jack = {0}; + + bool is_jack_activated = false; + bool success = false; + + jack.audio = audio; + + // JACK server selection by source or implicit default. + if (strlen(audio->source) == 0) + server_name = NULL; + else + server_name = audio->source; + + if ((jack.client = jack_client_open(client_name, options, &status, server_name)) == NULL) { + fprintf(stderr, __FILE__ ": Could not open JACK source '%s': 0x%x\n", server_name, status); + goto cleanup; + } + + if (!set_rate(&jack) || !set_format(&jack) || !set_channels(&jack)) + goto cleanup; + + // Parameters finalized. Signal main thread. + signal_threadparams(audio); + + // JACK returns samples per channel. Adjust its buffersize to fit within CAVA. + // Must be a power of 2. + jack.nframes = 1 << 31; + + while (jack.nframes > audio->input_buffer_size / audio->channels) + jack.nframes >>= 1; + + if ((err = jack_set_buffer_size(jack.client, jack.nframes)) != 0) { + fprintf(stderr, __FILE__ ": jack_set_buffer_size() failed: 0x%x\n", err); + goto cleanup; + } + + // Work buffer for interleaving if not mono. + if ((audio->channels > 1) && + ((jack.buf = malloc(jack.nframes * audio->channels * sizeof(sample_t))) == NULL)) { + fprintf(stderr, __FILE__ ": malloc() failed: %s\n", strerror(errno)); + goto cleanup; + } + + // Set JACK callbacks before JACK activation. + if ((err = jack_set_buffer_size_callback(jack.client, on_buffer_size, &jack)) != 0) { + fprintf(stderr, __FILE__ ": jack_set_buffer_size_callback() failed: 0x%x\n", err); + goto cleanup; + } + + if ((err = jack_set_process_callback(jack.client, on_process, &jack)) != 0) { + fprintf(stderr, __FILE__ ": jack_set_process_callback() failed: 0x%x\n", err); + goto cleanup; + } + + if ((err = jack_set_sample_rate_callback(jack.client, on_sample_rate, &jack)) != 0) { + fprintf(stderr, __FILE__ ": jack_set_sample_rate_callback() failed: 0x%x\n", err); + goto cleanup; + } + + jack_on_shutdown(jack.client, on_shutdown, &jack); + + if ((err = jack_activate(jack.client)) != 0) { + fprintf(stderr, __FILE__ ": jack_activate() failed: 0x%x\n", err); + goto cleanup; + } + + is_jack_activated = true; + + while (audio->terminate != 1) { + if (jack.shutdown == 1) + signal_terminate(audio); + + nanosleep(&rqtp, NULL); + } + + success = true; + +cleanup: + if (is_jack_activated && ((err = jack_deactivate(jack.client)) != 0)) { + fprintf(stderr, __FILE__ ": jack_deactivate() failed: 0x%x\n", err); + success = false; + } + + free(jack.buf); + + for (int i = 0; i < MAX_CHANNELS; ++i) { + if ((jack.port[i] != NULL) && + ((err = jack_port_unregister(jack.client, jack.port[i])) != 0)) { + fprintf(stderr, __FILE__ ": jack_port_unregister('%s') failed: 0x%x\n", + jack_port_name(jack.port[i]), err); + success = false; + } + } + + if ((jack.client != NULL) && ((err = jack_client_close(jack.client)) != 0)) { + fprintf(stderr, __FILE__ ": jack_client_close() failed: 0x%x\n", err); + success = false; + } + + signal_threadparams(audio); + signal_terminate(audio); + + if (!success) + exit(EXIT_FAILURE); + + return NULL; +} diff --git a/input/jack.h b/input/jack.h new file mode 100644 index 00000000..b6121490 --- /dev/null +++ b/input/jack.h @@ -0,0 +1,5 @@ +// header file for jack, part of cava. + +#pragma once + +void *input_jack(void *data); diff --git a/input/oss.c b/input/oss.c index 3c2f6a5e..96467a0f 100644 --- a/input/oss.c +++ b/input/oss.c @@ -31,14 +31,23 @@ static bool set_format(int fd, struct audio_data *audio) { } // Determine the sle format for the requested bitlength. - if (audio->format <= 8) + switch (audio->format) { + case 8: fmt = AFMT_S8; - else if (audio->format <= 16) + break; + case 16: fmt = AFMT_S16_LE; - else if (audio->format <= 24) + break; + case 24: fmt = AFMT_S24_LE; - else + break; + case 32: fmt = AFMT_S32_LE; + break; + default: + fprintf(stderr, __FILE__ ": Invalid format: %d\n", audio->format); + return false; + } // If the requested format is not available then test for the other sle formats. if (!(fmts & fmt)) { @@ -104,25 +113,12 @@ static bool set_rate(int fd, struct audio_data *audio) { return true; } -static void signal_threadparams(struct audio_data *audio) { - pthread_mutex_lock(&audio->lock); - audio->threadparams = 0; - pthread_mutex_unlock(&audio->lock); -} - -static void signal_terminate(struct audio_data *audio) { - pthread_mutex_lock(&audio->lock); - audio->terminate = 1; - pthread_mutex_unlock(&audio->lock); -} - void *input_oss(void *data) { static const int flags = O_RDONLY; - struct audio_data *audio = (struct audio_data *)data; + struct audio_data *audio = data; int bytes; size_t buf_size; - ssize_t rd; int fd = -1; void *buf = NULL; @@ -136,7 +132,7 @@ void *input_oss(void *data) { } // For OSS it's adviced to determine format, channels and rate in this order. - if (!(set_format(fd, audio) && set_channels(fd, audio) && set_rate(fd, audio))) + if (!set_format(fd, audio) || !set_channels(fd, audio) || !set_rate(fd, audio)) goto cleanup; // Parameters finalized. Signal main thread. @@ -156,7 +152,9 @@ void *input_oss(void *data) { } while (audio->terminate != 1) { - if ((rd = read(fd, buf, buf_size)) == -1) { + ssize_t rd; + + if ((rd = read(fd, buf, buf_size)) < 0) { fprintf(stderr, __FILE__ ": read() failed: %s\n", strerror(errno)); goto cleanup; } else if (rd == 0) diff --git a/input/pulse.h b/input/pulse.h index 155da473..f93955bc 100644 --- a/input/pulse.h +++ b/input/pulse.h @@ -3,4 +3,4 @@ #pragma once void *input_pulse(void *data); -void getPulseDefaultSink(); +void getPulseDefaultSink(void *data); From b28d8674be820e1b51626d15a0fd68c4c3b0d9d9 Mon Sep 17 00:00:00 2001 From: bsdcode <155195419+bsdcode@users.noreply.github.com> Date: Sat, 20 Jan 2024 01:00:01 +0100 Subject: [PATCH 15/25] JACK: add autoconnect option for automatic port connections --- README.md | 31 +++++++++------- cava.c | 2 + config.c | 1 + config.h | 4 +- example_files/config | 12 +++--- input/common.h | 3 +- input/jack.c | 87 +++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 117 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index d9ac72b9..4a0de158 100644 --- a/README.md +++ b/README.md @@ -447,20 +447,25 @@ of input ports can be controlled via the `channels` option in the input section file: channels = 1 # one input port, mono - -or - - channels = 2 # two input ports, stereo + channels = 2 # two input ports, stereo (default) The port's short name is simply `M` for mono, and `L` and `R` for stereo. The full name of the input port according to the base client name is `cava:M` for mono, and `cava:L` and `cava:R` for stereo. -Currently CAVA doesn't connect its ports to other client's ports automatically, i.e. on program start -CAVA isn't connected to any other program and won't connect during execution on its own. There are -connection management programs in order to control and manage the connection between ports of the different -client programs. Some well known connection managers with a graphical user interface include QjackCtl -and Cadence. The JACK package itself often comes with CLI tools. Depending on the operating system -they might need to get installed separately, e.g. on FreeBSD +The option `autoconnect` controls the connection strategy for CAVA's ports to other client's ports: + + autoconnect = 0 # don't connect to other ports automatically + autoconnect = 1 # only connect to other ports during startup + autoconnect = 2 # reconnect to new ports regularly (default) + +The automatic connection strategies scan the physical terminal input-ports, i.e. the real audio device +which actually outputs the sound, and applies the same connections to CAVA's ports. In this way CAVA +visualizes the played audio of JACK clients by default. + +In order to control and manage the connection between CAVA's ports and ports of other client programs, +there are connection management programs for JACK. Some well known connection managers with a graphical +user interface are QjackCtl and Cadence. The JACK package itself often comes with CLI tools. Depending +on the operating system it could be necessary to install them separately, e.g. on FreeBSD: ```sh $ doas pkg install jack-example-tools ``` @@ -479,9 +484,9 @@ moc:output1 cava:R ``` This listing shows all full port names that are currently available. These correspond to two external -JACK clients (cava and moc) and one internal JACK client (system). There are also `-p` and `-c` switches -for `jack_lsp`, with which the types and current connections between the ports are listed. Connect -the ports of cava and moc: +JACK clients, `cava` and `moc`, and one internal JACK client `system`. The types and current active +connections between the ports can be listed With the `-p` and `-c` switches for `jack_lsp`. In order +to connect the ports of CAVA and MOC, `jack_connect` is used: ```sh $ jack_connect cava:L moc:output0 $ jack_connect cava:R moc:output1 diff --git a/cava.c b/cava.c index 93dc478c..bd8c905d 100644 --- a/cava.c +++ b/cava.c @@ -355,6 +355,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co audio.samples_counter = 0; audio.channels = 2; audio.IEEE_FLOAT = 0; + audio.autoconnect = 0; audio.input_buffer_size = BUFFER_SIZE * audio.channels; audio.cava_buffer_size = audio.input_buffer_size * 8; @@ -431,6 +432,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co #ifdef JACK case INPUT_JACK: audio.channels = p.channels; + audio.autoconnect = p.autoconnect; audio.threadparams = 1; // JACK server provides parameters thr_id = pthread_create(&p_thread, NULL, input_jack, (void *)&audio); break; diff --git a/config.c b/config.c index 4a9d58d8..7ac19bec 100644 --- a/config.c +++ b/config.c @@ -684,6 +684,7 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->samplerate = iniparser_getint(ini, "input:sample_rate", 44100); p->samplebits = iniparser_getint(ini, "input:sample_bits", 16); p->channels = iniparser_getint(ini, "input:channels", 2); + p->autoconnect = iniparser_getint(ini, "input:autoconnect", 2); enum input_method default_input = INPUT_FIFO; for (size_t i = 0; i < ARRAY_SIZE(default_methods); i++) { diff --git a/config.h b/config.h index 165f9f5a..857b6569 100644 --- a/config.h +++ b/config.h @@ -111,8 +111,8 @@ struct config_params { enum orientation orientation; int userEQ_keys, userEQ_enabled, col, bgcol, autobars, stereo, raw_format, ascii_range, bit_format, gradient, gradient_count, fixedbars, framerate, bar_width, bar_spacing, - bar_height, autosens, overshoot, waves, samplerate, samplebits, channels, sleep_timer, - sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, + bar_height, autosens, overshoot, waves, samplerate, samplebits, channels, autoconnect, + sleep_timer, sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, non_zero_test, reverse, sync_updates, continuous_rendering, disable_blanking; }; diff --git a/example_files/config b/example_files/config index 4c6ca32a..038df1ce 100644 --- a/example_files/config +++ b/example_files/config @@ -106,12 +106,11 @@ ; method = jack ; source = default -# The sample rate and format can be configured for some input methods. Currently -# the following methods support such a configuration: 'fifo', 'pipewire' and 'oss'. -# Other methods ignore these settings. -# -# The channels can be configured for some input methods. Currently the following -# methods support such a configuration: 'oss' and 'jack'. +# The options 'sample rate', 'format', 'channels' and 'autoconnect' can be configured for some input methods: +# sample rate: 'fifo', 'pipewire', 'oss' +# format: 'fifo', 'pipewire', 'oss' +# channels: 'oss', 'jack' +# autoconnect: 'jack' # Other methods ignore these settings. # # For 'oss' they are only preferred values, i.e. if the values are not supported @@ -122,6 +121,7 @@ ; sample_rate = 44100 ; sample_bits = 16 ; channels = 2 +; autoconnect = 2 [output] diff --git a/input/common.h b/input/common.h index a98f97f5..33d8c90a 100644 --- a/input/common.h +++ b/input/common.h @@ -31,7 +31,8 @@ struct audio_data { int terminate; // shared variable used to terminate audio thread char error_message[1024]; int samples_counter; - int IEEE_FLOAT; // format for 32bit (0=int, 1=float) + int IEEE_FLOAT; // format for 32bit (0=int, 1=float) + int autoconnect; // auto connect to audio source (0=off, 1=once at startup, 2=regularly) pthread_mutex_t lock; }; diff --git a/input/jack.c b/input/jack.c index cfa85336..ef91ab6a 100644 --- a/input/jack.c +++ b/input/jack.c @@ -19,7 +19,8 @@ struct jack_data { jack_nframes_t nframes; // number of samples per port sample_t *buf; // samples buffer - int shutdown; // JACK shutdown signal (0=keep, 1=shutdown) + int graphorder; // JACK graph ordering signal (0=unchanged, 1=changed) + int shutdown; // JACK shutdown signal (0=keep, 1=shutdown) }; static bool set_rate(struct jack_data *jack) { @@ -119,6 +120,11 @@ static int on_buffer_size(jack_nframes_t nframes, void *arg) { return 0; } +static int on_graph_order(void *arg) { + ((struct jack_data *)arg)->graphorder = 1; + return 0; +} + static int on_process(jack_nframes_t nframes, void *arg) { // Interleave samples from separate ports and feed them to CAVA. struct jack_data *jack = arg; @@ -185,6 +191,68 @@ static int on_sample_rate(jack_nframes_t nframes, void *arg) { static void on_shutdown(void *arg) { ((struct jack_data *)arg)->shutdown = 1; } +static bool auto_connect(struct jack_data *jack) { + // Get all physical terminal input-ports and mirror their connections to CAVA. + static const char type_name_pattern[] = JACK_DEFAULT_AUDIO_TYPE; + static const unsigned long flags = JackPortIsInput | JackPortIsPhysical | JackPortIsTerminal; + + unsigned int channels; + + const char **ports = NULL; + + bool success = false; + + if ((jack->shutdown == 1) || (jack->audio->terminate == 1)) + return true; + + if ((ports = jack_get_ports(jack->client, NULL, type_name_pattern, flags)) == NULL) { + fprintf(stderr, + __FILE__ ": jack_get_ports() failed: No physical terminal input-ports found!\n"); + goto cleanup; + } + + // If CAVA is configured for mono, then we connect everything to the one mono port. If we have + // more channels, then we limit the number of connection ports to the number of channels. + for (channels = 0; ports[channels] != NULL; ++channels) + ; + + if ((jack->audio->channels > 1) && (channels > jack->audio->channels)) + channels = jack->audio->channels; + + // Visit the physical terminal input-ports, get their connections and apply them to CAVA's + // input-ports. + for (unsigned int i = 0; i < channels; ++i) { + const char **connections; + jack_port_t *port; + + if ((connections = jack_port_get_all_connections( + jack->client, jack_port_by_name(jack->client, ports[i]))) == NULL) + continue; + + if (jack->audio->channels == 1) + port = jack->port[0]; + else + port = jack->port[i]; + + for (int j = 0; connections[j] != NULL; ++j) { + if (jack_port_connected_to(port, connections[j]) == 0) + jack_connect(jack->client, connections[j], jack_port_name(port)); + } + + jack_free(connections); + } + + success = true; + +cleanup: + if (!success) + jack->shutdown = 1; + + jack_free(ports); + + return success; +} + void *input_jack(void *data) { static const char client_name[] = "cava"; static const jack_options_t options = JackNullOption | JackServerName; @@ -244,6 +312,17 @@ void *input_jack(void *data) { goto cleanup; } + if (audio->autoconnect > 0) { + if (audio->autoconnect == 1) + jack.graphorder = 1; + else { + if ((err = jack_set_graph_order_callback(jack.client, on_graph_order, &jack)) != 0) { + fprintf(stderr, __FILE__ ": jack_set_graph_order_callback() failed: 0x%x\n", err); + goto cleanup; + } + } + } + if ((err = jack_set_process_callback(jack.client, on_process, &jack)) != 0) { fprintf(stderr, __FILE__ ": jack_set_process_callback() failed: 0x%x\n", err); goto cleanup; @@ -266,6 +345,12 @@ void *input_jack(void *data) { while (audio->terminate != 1) { if (jack.shutdown == 1) signal_terminate(audio); + else if (jack.graphorder == 1) { + if (!auto_connect(&jack)) + goto cleanup; + + jack.graphorder = 0; + } nanosleep(&rqtp, NULL); } From 69874168c71d7343d6a0317b7cd17d312f19a9fb Mon Sep 17 00:00:00 2001 From: bsdcode <155195419+bsdcode@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:37:11 +0100 Subject: [PATCH 16/25] Sndio: configurable parameters - rework sndio backend to accept variable parameters - enhance documentation --- README.md | 110 +++++++++++++++++++++++++++------------ cava.c | 8 +-- example_files/config | 16 +++--- input/jack.c | 5 +- input/sndio.c | 119 +++++++++++++++++++++++++++++++++---------- 5 files changed, 187 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 4a0de158..76987fd3 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ by [Karl Stavestrand](mailto:karl@stavestrand.no) - [Pipewire](#pipewire) - [ALSA](#alsa) - [MPD](#mpd) - - [sndio](#sndio) + - [Sndio](#sndio) - [OSS](#oss) - [JACK](#jack) - [squeezelite](#squeezelite) @@ -331,20 +331,69 @@ I had some trouble with sync (the visualizer was ahead of the sound). Reducing t buffer_time "50000" # (50ms); default is 500000 microseconds (0.5s) } -### sndio +### Sndio -sndio is the audio framework used on OpenBSD, but it's also available on -FreeBSD and Linux. So far this is only tested on FreeBSD. +Set + + method = sndio + +Sndio is the audio framework used on OpenBSD, but it's also available on FreeBSD, NetBSD and Linux. +So far this is only tested on FreeBSD, but it's probably very similar on other operating systems. The +following example demonstrates how to setup CAVA for sndio on FreeBSD (please consult the [OSS](#oss) +section for a deeper explanation of the various `pcmX` sound devices and the corresponding `/dev/dspX` +audio devices in this example). +```sh +$ cat /dev/sndstat +Installed devices: +pcm0: (play/rec) default +pcm1: (rec) +pcm2: (play/rec) +No devices installed from userspace. +``` +Sndio operates on device descriptors. In general for every `/dev/dspX` audio device there is a corresponding +`rsnd/X` sndio raw device descriptor. In this example there are `rsnd/0`, `rsnd/1` and `rsnd/2` (they +are not listed in `/dev`, sndio uses these descriptors to access the corresponding audio devices internally). +Sndio also handles the implicit `default` device descriptor, which acts like a symlink to the raw device +descriptor corresponding to the default audio device `/dev/dsp`. In this example it acts like a symlink +to `rsnd/0` because the default audio device `/dev/dsp` symlinks to `/dev/dsp0`. Sndio also evaluates +the environment variables `AUDIODEVICE` and `AUDIORECDEVICE`. If one of these is set (`AUDIORECDEVICE` +overrides `AUDIODEVICE` if both are set) and a sndio-aware program tries to open the `default` device +descriptor or an unspecified device descriptor, then the program will use the device descriptor specified +in the environment variable. + +Now in order to visualize the mic input in CAVA, the `source` value in the configuration file must +be set to the corresponding audio descriptor: -To test it -```bash -# Start sndiod with a monitor sub-device -$ sndiod -dd -s default -m mon -s monitor + source = default # default; symlink to rsnd/0 in this example; AUDIORECDEVICE and AUDIODEVICE evaluation + source = # unspecified device descriptor; same as default above + source = rsnd/0 # for the pcm0 mic on the rear + source = rsnd/1 # for the pcm1 mic on the front + source = rsnd/2 # for the pcm2 mic on the USB headset -# Set the AUDIODEVICE environment variable to override the default -# sndio device and run cava -$ AUDIODEVICE=snd/0.monitor cava +With `source = default` one can switch the visualization on the commandline without changing the configuration +file again: +```sh +$ AUDIODEVICE=rsnd/0 cava +$ AUDIODEVICE=rsnd/1 cava +$ AUDIODEVICE=rsnd/2 cava +``` +Sndio can't record the played back audio with just the raw device descriptors, i.e. the sounds from +a music player or a browser which play on the external stereo speakers through `rsnd/0` are not visualized +in CAVA. For this to work the sndio server has to be started and a monitoring sub-device has to be +created. The following example shows how to start the server and create a monitoring sub-device `snd/0` +from `rsnd/0` and then start CAVA with `AUDIODEVICE` pointing to the new monitoring sub-device: +```sh +$ sndiod -f rsnd/0 -m play,mon +$ AUDIODEVICE=snd/0 cava +``` +Switch between the speakers and the USB headset: +```sh +$ sndiod -f rsnd/1 -m play,mon -s usb -f rsnd/0 -m play,mon -s speakers +$ AUDIODEVICE=snd/usb cava +$ AUDIODEVICE=snd/speakers cava ``` +Consult the manpage `sndiod(8)` for further information regarding configuration and startup of a sndio +server. ### OSS @@ -372,31 +421,25 @@ it. In general for every `pcmX` device there is a corresponding `/dev/dspX` audio device. In this example there are `/dev/dsp0`, `/dev/dsp1` and `/dev/dsp2` (the system creates them when needed, they are not -listet via `ls /dev` if they are currently not in use). The system also creates an implicit `/dev/dsp`, +listed via `ls /dev` if they are currently not in use). The system also creates an implicit `/dev/dsp`, which acts like a symlink to the `default` audio device, in this example to `/dev/dsp0`. Now in order to visualize the mic input in CAVA, the `source` value in the configuration file must -be set to the corresponding audio device, i.e. - - source = /dev/dsp # or /dev/dsp0 for which /dev/dsp is a symlink in this example - -(which is already the default for CAVA) for the `pcm0` mic on the rear, or - - source = /dev/dsp1 - -for the `pcm1` mic on the front, or - - source = /dev/dsp2 +be set to the corresponding audio device: -for the `pcm2` mic on the USB headset. + source = /dev/dsp # default; symlink to /dev/dsp0 in this example + source = /dev/dsp0 # for the pcm0 mic on the rear + source = /dev/dsp1 # for the pcm1 mic on the front + source = /dev/dsp2 # for the pcm2 mic on the USB headset OSS can't record the outgoing audio on its own, i.e. the sounds from a music player or a browser which play on the external stereo speakers through `/dev/dsp0` are not visualized in CAVA. A solution is -to use Virtual OSS. It can create virtual audio devices from existing audio devices and from which -the played back audio can be fed into CAVA: +to use Virtual OSS. It can create virtual audio devices from existing audio devices, in particular +it can create a loopback audio device from `/dev/dsp0` and from which the played back audio can be +fed into CAVA: ```sh $ doas pkg install virtual_oss -$ doas virtual_oss -Q0 -C2 -c2 -r48000 -b16 -s2048 -P /dev/dsp0 -R /dev/null -w vdsp.wav -t vdsp.ctl -T /dev/sndstat -l dsp +$ doas virtual_oss -r44100 -b16 -c2 -s4ms -O /dev/dsp0 -R /dev/null -T /dev/sndstat -l dsp.cava $ cat /dev/sndstat Installed devices: @@ -404,11 +447,12 @@ pcm0: (play/rec) default pcm1: (rec) pcm2: (play/rec) Installed devices from userspace: -dsp: (play/rec) +dsp.cava: (play/rec) ``` -It created a virtual device `dsp` from `/dev/dsp0`. Now the audio is visualized in CAVA with the default -`source = /dev/dsp` in the configuration file. Virtual OSS can be configured and started as a service -on FreeBSD. +It created a virtual loopback device `/dev/dsp.cava` from `/dev/dsp0`. Now the audio is visualized +in CAVA with `source = /dev/dsp.cava` in the configuration file. The playback program must have a configuration +to use the `/dev/dsp.cava` device. For programs where this is not possible, e.g. which always use `/dev/dsp`, +replace `-l dsp.cava` with `-l dsp`. Virtual OSS can be configured and started as a service on FreeBSD. ### JACK @@ -460,7 +504,7 @@ The option `autoconnect` controls the connection strategy for CAVA's ports to ot The automatic connection strategies scan the physical terminal input-ports, i.e. the real audio device which actually outputs the sound, and applies the same connections to CAVA's ports. In this way CAVA -visualizes the played audio of JACK clients by default. +visualizes the played back audio from JACK clients by default. In order to control and manage the connection between CAVA's ports and ports of other client programs, there are connection management programs for JACK. Some well known connection managers with a graphical @@ -485,7 +529,7 @@ cava:R ``` This listing shows all full port names that are currently available. These correspond to two external JACK clients, `cava` and `moc`, and one internal JACK client `system`. The types and current active -connections between the ports can be listed With the `-p` and `-c` switches for `jack_lsp`. In order +connections between the ports can be listed with the `-p` and `-c` switches for `jack_lsp`. In order to connect the ports of CAVA and MOC, `jack_connect` is used: ```sh $ jack_connect cava:L moc:output0 diff --git a/cava.c b/cava.c index bd8c905d..ab8b7327 100644 --- a/cava.c +++ b/cava.c @@ -415,8 +415,10 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co #endif #ifdef SNDIO case INPUT_SNDIO: - audio.format = 16; - audio.rate = 44100; + audio.format = p.samplebits; + audio.rate = p.samplerate; + audio.channels = p.channels; + audio.threadparams = 1; // Sndio can adjust parameters thr_id = pthread_create(&p_thread, NULL, input_sndio, (void *)&audio); break; #endif @@ -492,7 +494,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co if (p.upper_cut_off > audio.rate / 2) { cleanup(); - fprintf(stderr, "higher cuttoff frequency can't be higher than sample rate / 2"); + fprintf(stderr, "higher cutoff frequency can't be higher than sample rate / 2"); exit(EXIT_FAILURE); } diff --git a/example_files/config b/example_files/config index 038df1ce..c564cc30 100644 --- a/example_files/config +++ b/example_files/config @@ -70,8 +70,8 @@ # For fifo 'source' will be the path to fifo-file. # For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address # -# For sndio 'source' will be a monitor sub-device, e.g. 'snd/0.monitor'. Default: 'default', in which case a device -# should be specified with the environment variable AUDIODEVICE, e.g. on the commandline: AUDIODEVICE=snd/0.monitor cava. +# For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'. +# README.md contains further information on how to setup CAVA for sndio. # # For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device. # README.md contains further information on how to setup CAVA for OSS on FreeBSD. @@ -106,14 +106,14 @@ ; method = jack ; source = default -# The options 'sample rate', 'format', 'channels' and 'autoconnect' can be configured for some input methods: -# sample rate: 'fifo', 'pipewire', 'oss' -# format: 'fifo', 'pipewire', 'oss' -# channels: 'oss', 'jack' -# autoconnect: 'jack' +# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods: +# sample_rate: fifo, pipewire, sndio, oss +# sample_bits: fifo, pipewire, sndio, oss +# channels: sndio, oss, jack +# autoconnect: jack # Other methods ignore these settings. # -# For 'oss' they are only preferred values, i.e. if the values are not supported +# For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported # by the chosen audio device, the device will use other supported values instead. # Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it # will use 44100, 16 and 1. diff --git a/input/jack.c b/input/jack.c index ef91ab6a..eb52a0bc 100644 --- a/input/jack.c +++ b/input/jack.c @@ -224,6 +224,7 @@ static bool auto_connect(struct jack_data *jack) { for (unsigned int i = 0; i < channels; ++i) { const char **connections; jack_port_t *port; + const char *port_name; if ((connections = jack_port_get_all_connections( jack->client, jack_port_by_name(jack->client, ports[i]))) == NULL) @@ -234,9 +235,11 @@ static bool auto_connect(struct jack_data *jack) { else port = jack->port[i]; + port_name = jack_port_name(port); + for (int j = 0; connections[j] != NULL; ++j) { if (jack_port_connected_to(port, connections[j]) == 0) - jack_connect(jack->client, connections[j], jack_port_name(port)); + jack_connect(jack->client, connections[j], port_name); } jack_free(connections); diff --git a/input/sndio.c b/input/sndio.c index a050b14c..094a2a5f 100644 --- a/input/sndio.c +++ b/input/sndio.c @@ -1,50 +1,117 @@ -#include "input/sndio.h" -#include "input/common.h" +#include +#include #include +#include "input/common.h" +#include "input/sndio.h" + void *input_sndio(void *data) { - struct audio_data *audio = (struct audio_data *)data; + static const unsigned int mode = SIO_REC; + + struct audio_data *audio = data; struct sio_par par; - struct sio_hdl *hdl; - unsigned char buf[audio->input_buffer_size * audio->format / 8]; + int bytes; + size_t buf_size; + + struct sio_hdl *hdl = NULL; + void *buf = NULL; + bool is_sio_started = false; + bool success = false; + + if ((hdl = sio_open(audio->source, mode, 0)) == NULL) { + fprintf(stderr, __FILE__ ": Could not open sndio source '%s'.\n", audio->source); + goto cleanup; + } + + // The recommended approach to negotiate device parameters is to try to set them with preferred + // values and check what sndio returns for actual supported values. If CAVA doesn't support the + // final values for rate and channels then it will complain later. We test the resulting format + // explicitly here. sio_initpar(&par); - par.sig = 1; par.bits = audio->format; + par.sig = 1; par.le = 1; - par.rate = audio->rate; - ; par.rchan = audio->channels; - par.appbufsz = sizeof(buf) / par.rchan; + par.rate = audio->rate; + par.appbufsz = audio->input_buffer_size * SIO_BPS(audio->format) / audio->channels; - if ((hdl = sio_open(audio->source, SIO_REC, 0)) == NULL) { - fprintf(stderr, __FILE__ ": Could not open sndio source: %s\n", audio->source); - exit(EXIT_FAILURE); + if (sio_setpar(hdl, &par) == 0) { + fprintf(stderr, __FILE__ ": sio_setpar() failed.\n"); + goto cleanup; } - if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par) || par.sig != 1 || par.le != 1 || - par.rate != 44100 || par.rchan != audio->channels) { - fprintf(stderr, __FILE__ ": Could not set required audio parameters\n"); - exit(EXIT_FAILURE); + if (sio_getpar(hdl, &par) == 0) { + fprintf(stderr, __FILE__ ": sio_getpar() failed.\n"); + goto cleanup; } - if (!sio_start(hdl)) { - fprintf(stderr, __FILE__ ": sio_start() failed\n"); - exit(EXIT_FAILURE); + switch (par.bits) { + case 8: + case 16: + case 24: + case 32: + audio->format = par.bits; + break; + default: + fprintf(stderr, __FILE__ ": No support for 8, 16, 24 or 32 bits in sndio source '%s'.\n", + audio->source); + goto cleanup; } + audio->channels = par.rchan; + audio->rate = par.rate; + + // Parameters finalized. Signal main thread. + signal_threadparams(audio); + + // Get the correct number of bytes per sample. Sndio uses 32 bits for 24bit, thankfully SIO_BPS + // handles this. + bytes = SIO_BPS(audio->format); + buf_size = audio->input_buffer_size * bytes; + + if ((buf = malloc(buf_size)) == NULL) { + fprintf(stderr, __FILE__ ": malloc() failed: %s\n", strerror(errno)); + goto cleanup; + } + + if (sio_start(hdl) == 0) { + fprintf(stderr, __FILE__ ": sio_start() failed.\n"); + goto cleanup; + } + + is_sio_started = true; + while (audio->terminate != 1) { - if (sio_read(hdl, buf, sizeof(buf)) == 0) { - fprintf(stderr, __FILE__ ": sio_read() failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); + size_t rd; + + if ((rd = sio_read(hdl, buf, buf_size)) == 0) { + fprintf(stderr, __FILE__ ": sio_read() failed.\n"); + goto cleanup; } - write_to_cava_input_buffers(audio->input_buffer_size, buf, audio); + write_to_cava_input_buffers(rd / bytes, buf, audio); + } + + success = true; + +cleanup: + if (is_sio_started && (sio_stop(hdl) == 0)) { + fprintf(stderr, __FILE__ ": sio_stop() failed.\n"); + success = false; } - sio_stop(hdl); - sio_close(hdl); + free(buf); + + if (hdl != NULL) + sio_close(hdl); + + signal_threadparams(audio); + signal_terminate(audio); + + if (!success) + exit(EXIT_FAILURE); - return 0; + return NULL; } From 9b7dec407028b99afcdf633fc107f2fda4425b85 Mon Sep 17 00:00:00 2001 From: bsdcode <155195419+bsdcode@users.noreply.github.com> Date: Mon, 22 Jan 2024 01:09:40 +0100 Subject: [PATCH 17/25] Correct minor error in sndio documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76987fd3..8d00c3b7 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ $ AUDIODEVICE=snd/0 cava ``` Switch between the speakers and the USB headset: ```sh -$ sndiod -f rsnd/1 -m play,mon -s usb -f rsnd/0 -m play,mon -s speakers +$ sndiod -f rsnd/2 -m play,mon -s usb -f rsnd/0 -m play,mon -s speakers $ AUDIODEVICE=snd/usb cava $ AUDIODEVICE=snd/speakers cava ``` From a84e2500d7d9d7c44bef8d9b224092a4435d573b Mon Sep 17 00:00:00 2001 From: karl Date: Mon, 29 Jan 2024 11:05:40 +0100 Subject: [PATCH 18/25] #354 prioritize pulseaudio before sndio on arch some people seem to have sndio installed but not pipewire. Therefore cava defaults to sndio. #538 --- config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.c b/config.c index 7ac19bec..6277ddf7 100644 --- a/config.c +++ b/config.c @@ -45,8 +45,8 @@ const char *default_shader_name[NUMBER_OF_SHADERS] = {"northern_lights.frag", "p double smoothDef[5] = {1, 1, 1, 1, 1}; enum input_method default_methods[] = { - INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_PULSE, INPUT_JACK, - INPUT_SNDIO, INPUT_PIPEWIRE, INPUT_WINSCAP, INPUT_OSS, + INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_SNDIO, INPUT_JACK, + INPUT_PULSE, INPUT_PIPEWIRE, INPUT_WINSCAP, INPUT_OSS, }; char *outputMethod, *orientation, *channels, *xaxisScale, *monoOption, *fragmentShader, From 27a36483386876603f2a4edf62e76ca7d8d38bda Mon Sep 17 00:00:00 2001 From: karl Date: Mon, 29 Jan 2024 11:50:59 +0100 Subject: [PATCH 19/25] fix lint --- config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.c b/config.c index 6277ddf7..c134ccb1 100644 --- a/config.c +++ b/config.c @@ -45,7 +45,7 @@ const char *default_shader_name[NUMBER_OF_SHADERS] = {"northern_lights.frag", "p double smoothDef[5] = {1, 1, 1, 1, 1}; enum input_method default_methods[] = { - INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_SNDIO, INPUT_JACK, + INPUT_FIFO, INPUT_PORTAUDIO, INPUT_ALSA, INPUT_SNDIO, INPUT_JACK, INPUT_PULSE, INPUT_PIPEWIRE, INPUT_WINSCAP, INPUT_OSS, }; From 069cad4e6e8dfeb87be93c473aa7fb4898ac942c Mon Sep 17 00:00:00 2001 From: karl Date: Thu, 1 Feb 2024 23:11:33 +0100 Subject: [PATCH 20/25] clarify build requirements --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d00c3b7..703dc5ec 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,6 @@ Required components: Recomended components: -* [ncursesw dev files](http://www.gnu.org/software/ncurses/) (bundled in ncurses in arch) The development lib of one of these audio frameworks, depending on your distro: * ALSA @@ -80,13 +79,14 @@ The development lib of one of these audio frameworks, depending on your distro: * Sndio * JACK + Optional components: * SDL2 dev files +* [ncursesw dev files](http://www.gnu.org/software/ncurses/) (bundled in ncurses in arch) -Only FFTW and the other build tools are actually required for CAVA to compile, but this will only give you the ability to read from fifo files. To more easly grab audio from your system pulseaudio, alsa, sndio, jack or portaudio dev files are recommended (depending on what audio system you are using). Not sure how to get the pulseaudio dev files for other distros than debian/ubuntu or if they are bundled in pulseaudio. - +Only FFTW, iniparser and the build tools are actually required for CAVA to compile, but this will only give you the ability to read from fifo files. To capture audio directlty from your system pipewire, pulseaudio, alsa, sndio, jack or portaudio dev files are required (depending on what audio system you are using). -For better a better visual experience ncurses is also recomended. +Ncurses can be used as an alternative output method if you have issues with the default one. But it is not required. All the requirements can be installed easily in all major distros: From fdc1706361aa4e6d8723275a8b6c016108a46c4a Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 2 Feb 2024 18:47:27 +0100 Subject: [PATCH 21/25] bump hardcoded version to 0.10.1 --- autogen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen.sh b/autogen.sh index 2ecfdfa0..c4ad8291 100755 --- a/autogen.sh +++ b/autogen.sh @@ -3,7 +3,7 @@ if [ -d .git ]; then git describe --always --tags --dirty > version # get version from git else - echo 0.10.0 > version # hard coded versions + echo 0.10.1 > version # hard coded versions fi libtoolize From 32fc24aa06589f8aa6ef3fac8f7af9eeffa89d81 Mon Sep 17 00:00:00 2001 From: karl Date: Sat, 10 Feb 2024 19:12:17 +0100 Subject: [PATCH 22/25] add option to remove idle bar heads #550 --- cava.c | 5 +++-- config.c | 3 +++ config.h | 3 ++- example_files/config | 3 +++ output/terminal_ncurses.c | 38 +++++++++++++++++++------------------- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/cava.c b/cava.c index ab8b7327..f5a5903e 100644 --- a/cava.c +++ b/cava.c @@ -1013,8 +1013,9 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co #endif for (int n = 0; n < number_of_bars; n++) { bars[n] = bars_raw[n]; - // zero values causes divided by zero segfault (if not raw) - if (output_mode != OUTPUT_RAW && output_mode != OUTPUT_NORITAKE && bars[n] < 1) + // show idle bar heads + if (output_mode != OUTPUT_RAW && output_mode != OUTPUT_NORITAKE && + bars[n] < 1 && p.show_idle_bar_heads == 1) bars[n] = 1; #ifdef SDL_GLSL diff --git a/config.c b/config.c index c134ccb1..4d07b721 100644 --- a/config.c +++ b/config.c @@ -654,6 +654,8 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->disable_blanking = iniparser_getint(ini, "output:disable_blanking", 0); + p->show_idle_bar_heads = iniparser_getint(ini, "output:show_idle_bar_heads", 1); + p->sync_updates = iniparser_getint(ini, "output:alacritty_sync", 0); vertexShader = strdup(iniparser_getstring(ini, "output:vertex_shader", "pass_through.vert")); @@ -800,6 +802,7 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->sdl_y = GetPrivateProfileInt("output", "sdl_y", -1, configPath); p->sync_updates = GetPrivateProfileInt("output", "alacritty_sync", 0, configPath); + p->show_idle_bar_heads = GetPrivateProfileInt("output", "show_idle_bar_heads", 1, configPath); p->userEQ_enabled = 0; diff --git a/config.h b/config.h index 857b6569..0daaa097 100644 --- a/config.h +++ b/config.h @@ -113,7 +113,8 @@ struct config_params { bit_format, gradient, gradient_count, fixedbars, framerate, bar_width, bar_spacing, bar_height, autosens, overshoot, waves, samplerate, samplebits, channels, autoconnect, sleep_timer, sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, - non_zero_test, reverse, sync_updates, continuous_rendering, disable_blanking; + non_zero_test, reverse, sync_updates, continuous_rendering, disable_blanking, + show_idle_bar_heads; }; struct error_s { diff --git a/example_files/config b/example_files/config index c564cc30..af0e5664 100644 --- a/example_files/config +++ b/example_files/config @@ -204,6 +204,9 @@ # (Not supported on FreeBSD) ; disable_blanking = 0 +# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off +; show_idle_bar_heads = 1 + [color] # Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow. diff --git a/output/terminal_ncurses.c b/output/terminal_ncurses.c index 2bd3e6ac..9127c836 100644 --- a/output/terminal_ncurses.c +++ b/output/terminal_ncurses.c @@ -77,24 +77,24 @@ static NCURSES_COLOR_T change_color_definition(NCURSES_COLOR_T color_number, return return_color_number; } -static void get_screen_coords(int val, int col, int max_value, enum orientation orientation, int *x, - int *y) { +static void get_screen_coords(int line, int col, int max_value, enum orientation orientation, + int *x, int *y) { switch (orientation) { case ORIENT_LEFT: - *x = val; + *x = line; *y = col; break; case ORIENT_RIGHT: - *x = max_value - val; + *x = max_value - line; *y = col; break; case ORIENT_TOP: *x = col; - *y = val; + *y = line; break; default: *x = col; - *y = max_value - val; + *y = max_value - line; break; } } @@ -254,9 +254,9 @@ int draw_terminal_ncurses(int is_tty, int dimension_value, int dimension_bar, in max_update_value = (max_update_value + num_bar_heights) / num_bar_heights; - for (int val = 0; val < max_update_value; val++) { + for (int line = 0; line < max_update_value; line++) { if (gradient) { - change_colors(val, max_value); + change_colors(line, max_value); } for (int bar = 0; bar < bars_count; bar++) { @@ -265,17 +265,17 @@ int draw_terminal_ncurses(int is_tty, int dimension_value, int dimension_bar, in } int cur_col = bar * bar_width + bar * bar_spacing + rest; - int f_cell = (bars[bar] - 1) / num_bar_heights; - int f_last_cell = (previous_frame[bar] - 1) / num_bar_heights; + int bar_line_height = bars[bar] / num_bar_heights; + int previous_bar_line_heigh = previous_frame[bar] / num_bar_heights; - if (f_cell >= val) { + if (bars[bar] >= line * num_bar_heights + 1) { int bar_step; - if (f_cell == val) { - // The "cap" of the bar occurs at this [val]. - bar_step = (bars[bar] - 1) % num_bar_heights; - } else if (f_last_cell <= val) { - // The bar is full at this [val]. + if (bar_line_height == line) { + // The "cap" of the bar occurs at this [line]. + bar_step = bars[bar] % num_bar_heights - 1; + } else if (previous_bar_line_heigh <= line) { + // The bar is full at this line and wasn't before. bar_step = num_bar_heights - 1; } else { // No update necessary since last frame. @@ -284,7 +284,7 @@ int draw_terminal_ncurses(int is_tty, int dimension_value, int dimension_bar, in for (int col = cur_col, i = 0; i < bar_width; i++, col++) { int x, y; - get_screen_coords(val, col, max_value, orientation, &x, &y); + get_screen_coords(line, col, max_value, orientation, &x, &y); if (is_tty) { mvaddch(y, x, 0x41 + bar_step); @@ -292,12 +292,12 @@ int draw_terminal_ncurses(int is_tty, int dimension_value, int dimension_bar, in mvaddwstr(y, x, bar_heights[orientation][bar_step]); } } - } else if (f_last_cell >= val) { + } else if (previous_bar_line_heigh >= line) { // This bar was taller during the last frame than during this frame, so // clear the excess characters. for (int col = cur_col, i = 0; i < bar_width; i++, col++) { int x, y; - get_screen_coords(val, col, max_value, orientation, &x, &y); + get_screen_coords(line, col, max_value, orientation, &x, &y); mvaddch(y, x, ' '); } } From 77c99c3d61b1b605f09d8087e581787805f1caea Mon Sep 17 00:00:00 2001 From: karl Date: Mon, 12 Feb 2024 19:32:14 +0100 Subject: [PATCH 23/25] add waveform visualization #484 --- cava.c | 172 +++++++++++++++++++++++++------------------ config.c | 5 ++ config.h | 2 +- example_files/config | 3 + 4 files changed, 110 insertions(+), 72 deletions(-) diff --git a/cava.c b/cava.c index f5a5903e..c9fa711c 100644 --- a/cava.c +++ b/cava.c @@ -667,7 +667,10 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co if (number_of_bars % 2 != 0) number_of_bars--; } - + int raw_number_of_bars = (number_of_bars / output_channels) * audio_channels; + if (p.waveform) { + raw_number_of_bars = number_of_bars; + } // checks if there is stil extra room, will use this to center remainder = (*dimension_bar - number_of_bars * p.bar_width - number_of_bars * p.bar_spacing + p.bar_spacing) / @@ -910,99 +913,126 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co // process: execute cava pthread_mutex_lock(&audio.lock); - cava_execute(audio.cava_in, audio.samples_counter, cava_out, plan); + if (p.waveform) { + for (int n = 0; n < audio.samples_counter; n++) { + + for (int i = number_of_bars - 1; i > 0; i--) { + cava_out[i] = cava_out[i - 1]; + } + if (audio_channels == 2) { + cava_out[0] = + p.sens * (audio.cava_in[n] / 2 + audio.cava_in[n + 1] / 2); + n++; + } else { + cava_out[0] = p.sens * audio.cava_in[n]; + } + } + } else { + cava_execute(audio.cava_in, audio.samples_counter, cava_out, plan); + } if (audio.samples_counter > 0) { audio.samples_counter = 0; } pthread_mutex_unlock(&audio.lock); - for (int n = 0; n < (number_of_bars / output_channels) * audio_channels; n++) { + for (int n = 0; n < raw_number_of_bars; n++) { - if (output_mode != OUTPUT_SDL_GLSL) { - cava_out[n] *= *dimension_value; + if (!p.waveform) { + cava_out[n] *= p.sens; + } else { + if (cava_out[n] > 1.0) + p.sens *= 0.999; + else + p.sens *= 1.00001; + cava_out[n] = (cava_out[n] + 1.0) / 2.0; } - cava_out[n] *= p.sens; - if (output_mode == OUTPUT_SDL_GLSL) { if (cava_out[n] > 1.0) cava_out[n] = 1.0; else if (cava_out[n] < 0.0) cava_out[n] = 0.0; + } else { + cava_out[n] *= *dimension_value; } - } - - if (audio_channels == 2) { - for (int n = 0; n < number_of_bars / output_channels; n++) { - if (p.userEQ_enabled) - cava_out[n] *= - p.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)]; - bars_left[n] = cava_out[n]; - } - for (int n = 0; n < number_of_bars / output_channels; n++) { - if (p.userEQ_enabled) - cava_out[n + number_of_bars / output_channels] *= - p.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)]; - bars_right[n] = cava_out[n + number_of_bars / output_channels]; - } - } else { - for (int n = 0; n < number_of_bars; n++) { - if (p.userEQ_enabled) - cava_out[n] *= - p.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)]; + if (p.waveform) { bars_raw[n] = cava_out[n]; } } - - // process [filter] - if (p.monstercat) { + if (!p.waveform) { if (audio_channels == 2) { - bars_left = monstercat_filter(bars_left, number_of_bars / output_channels, - p.waves, p.monstercat); - bars_right = monstercat_filter(bars_right, number_of_bars / output_channels, - p.waves, p.monstercat); + for (int n = 0; n < number_of_bars / output_channels; n++) { + if (p.userEQ_enabled) + cava_out[n] *= + p.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)]; + bars_left[n] = cava_out[n]; + } + for (int n = 0; n < number_of_bars / output_channels; n++) { + if (p.userEQ_enabled) + cava_out[n + number_of_bars / output_channels] *= + p.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)]; + bars_right[n] = cava_out[n + number_of_bars / output_channels]; + } } else { - bars_raw = - monstercat_filter(bars_raw, number_of_bars, p.waves, p.monstercat); - } - } - if (audio_channels == 2) { - if (p.stereo) { - // mirroring stereo channels for (int n = 0; n < number_of_bars; n++) { - if (n < number_of_bars / 2) { - if (p.reverse) { - bars_raw[n] = bars_left[n]; + if (p.userEQ_enabled) + cava_out[n] *= + p.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)]; + bars_raw[n] = cava_out[n]; + } + } + + // process [filter] + if (p.monstercat) { + if (audio_channels == 2) { + bars_left = monstercat_filter( + bars_left, number_of_bars / output_channels, p.waves, p.monstercat); + bars_right = + monstercat_filter(bars_right, number_of_bars / output_channels, + p.waves, p.monstercat); + } else { + bars_raw = + monstercat_filter(bars_raw, number_of_bars, p.waves, p.monstercat); + } + } + if (audio_channels == 2) { + if (p.stereo) { + // mirroring stereo channels + for (int n = 0; n < number_of_bars; n++) { + if (n < number_of_bars / 2) { + if (p.reverse) { + bars_raw[n] = bars_left[n]; + } else { + bars_raw[n] = bars_left[number_of_bars / 2 - n - 1]; + } } else { - bars_raw[n] = bars_left[number_of_bars / 2 - n - 1]; + if (p.reverse) { + bars_raw[n] = bars_right[number_of_bars - n - 1]; + } else { + bars_raw[n] = bars_right[n - number_of_bars / 2]; + } } - } else { + } + } else { + // stereo mono output + for (int n = 0; n < number_of_bars; n++) { if (p.reverse) { - bars_raw[n] = bars_right[number_of_bars - n - 1]; + if (p.mono_opt == AVERAGE) { + bars_raw[number_of_bars - n - 1] = + (bars_left[n] + bars_right[n]) / 2; + } else if (p.mono_opt == LEFT) { + bars_raw[number_of_bars - n - 1] = bars_left[n]; + } else if (p.mono_opt == RIGHT) { + bars_raw[number_of_bars - n - 1] = bars_right[n]; + } } else { - bars_raw[n] = bars_right[n - number_of_bars / 2]; - } - } - } - } else { - // stereo mono output - for (int n = 0; n < number_of_bars; n++) { - if (p.reverse) { - if (p.mono_opt == AVERAGE) { - bars_raw[number_of_bars - n - 1] = - (bars_left[n] + bars_right[n]) / 2; - } else if (p.mono_opt == LEFT) { - bars_raw[number_of_bars - n - 1] = bars_left[n]; - } else if (p.mono_opt == RIGHT) { - bars_raw[number_of_bars - n - 1] = bars_right[n]; - } - } else { - if (p.mono_opt == AVERAGE) { - bars_raw[n] = (bars_left[n] + bars_right[n]) / 2; - } else if (p.mono_opt == LEFT) { - bars_raw[n] = bars_left[n]; - } else if (p.mono_opt == RIGHT) { - bars_raw[n] = bars_right[n]; + if (p.mono_opt == AVERAGE) { + bars_raw[n] = (bars_left[n] + bars_right[n]) / 2; + } else if (p.mono_opt == LEFT) { + bars_raw[n] = bars_left[n]; + } else if (p.mono_opt == RIGHT) { + bars_raw[n] = bars_right[n]; + } } } } @@ -1015,7 +1045,7 @@ as of 0.4.0 all options are specified in config file, see in '/home/username/.co bars[n] = bars_raw[n]; // show idle bar heads if (output_mode != OUTPUT_RAW && output_mode != OUTPUT_NORITAKE && - bars[n] < 1 && p.show_idle_bar_heads == 1) + bars[n] < 1 && p.waveform == 0 && p.show_idle_bar_heads == 1) bars[n] = 1; #ifdef SDL_GLSL diff --git a/config.c b/config.c index 4d07b721..d2161eef 100644 --- a/config.c +++ b/config.c @@ -656,6 +656,10 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->show_idle_bar_heads = iniparser_getint(ini, "output:show_idle_bar_heads", 1); + p->waveform = iniparser_getint(ini, "output:waveform", 0); + + p->waveform_smoothing_factor = iniparser_getdouble(ini, "output:waveform_smoothing_factor", 2); + p->sync_updates = iniparser_getint(ini, "output:alacritty_sync", 0); vertexShader = strdup(iniparser_getstring(ini, "output:vertex_shader", "pass_through.vert")); @@ -803,6 +807,7 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->sync_updates = GetPrivateProfileInt("output", "alacritty_sync", 0, configPath); p->show_idle_bar_heads = GetPrivateProfileInt("output", "show_idle_bar_heads", 1, configPath); + p->waveform = GetPrivateProfileInt("output", "waveform", 0, configPath); p->userEQ_enabled = 0; diff --git a/config.h b/config.h index 0daaa097..e4885c6c 100644 --- a/config.h +++ b/config.h @@ -114,7 +114,7 @@ struct config_params { bar_height, autosens, overshoot, waves, samplerate, samplebits, channels, autoconnect, sleep_timer, sdl_width, sdl_height, sdl_x, sdl_y, sdl_full_screen, draw_and_quit, zero_test, non_zero_test, reverse, sync_updates, continuous_rendering, disable_blanking, - show_idle_bar_heads; + show_idle_bar_heads, waveform; }; struct error_s { diff --git a/example_files/config b/example_files/config index af0e5664..7f3f41c4 100644 --- a/example_files/config +++ b/example_files/config @@ -207,6 +207,9 @@ # show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off ; show_idle_bar_heads = 1 +# show waveform instead of frequency spectrum, 1 = on, 0 = off +; waveform = 0 + [color] # Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow. From 5d5e1375ac0dc977ed05207c7f740954758c1679 Mon Sep 17 00:00:00 2001 From: karl Date: Mon, 12 Feb 2024 19:39:01 +0100 Subject: [PATCH 24/25] pulse: clean up confusion regarding buffer size and fragsize now these are set to the same value earlier frag_size was set to doble buffer size because of cpu performance issues but this was causing some issues with the recording --- input/pulse.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/input/pulse.c b/input/pulse.c index 2a56fbf9..6837a5b4 100644 --- a/input/pulse.c +++ b/input/pulse.c @@ -101,17 +101,14 @@ void getPulseDefaultSink(void *data) { void *input_pulse(void *data) { struct audio_data *audio = (struct audio_data *)data; - uint16_t frames = audio->input_buffer_size / audio->channels; - unsigned char buf[audio->input_buffer_size * audio->format / 8]; + uint16_t buffer_size = audio->input_buffer_size * audio->format / 8; + unsigned char buf[buffer_size]; /* The sample type to use */ static const pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2}; - const int frag_size = frames * audio->channels * audio->format / 8 * - 2; // we double this because of cpu performance issues with pulseaudio - pa_buffer_attr pb = {.maxlength = (uint32_t)-1, // BUFSIZE * 2, - .fragsize = frag_size}; + .fragsize = buffer_size}; pa_simple *s = NULL; int error; From 34179d63b8eb652a56c98e1b0b578698710df371 Mon Sep 17 00:00:00 2001 From: karl Date: Mon, 12 Feb 2024 20:01:04 +0100 Subject: [PATCH 25/25] build fix, remove unused parameter #484 --- config.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/config.c b/config.c index d2161eef..c1238a7e 100644 --- a/config.c +++ b/config.c @@ -658,8 +658,6 @@ bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colors p->waveform = iniparser_getint(ini, "output:waveform", 0); - p->waveform_smoothing_factor = iniparser_getdouble(ini, "output:waveform_smoothing_factor", 2); - p->sync_updates = iniparser_getint(ini, "output:alacritty_sync", 0); vertexShader = strdup(iniparser_getstring(ini, "output:vertex_shader", "pass_through.vert"));