#include #ifdef HAVE_ALLOCA_H #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debug.h" #include "util.h" #ifdef NCURSES #include "output/terminal_bcircle.h" #include "output/terminal_ncurses.h" #include #endif #include "output/raw.h" #include "output/terminal_noncurses.h" #include "input/alsa.h" #include "input/common.h" #include "input/fifo.h" #include "input/portaudio.h" #include "input/pulse.h" #include "input/shmem.h" #include "input/sndio.h" #include "config.h" #ifdef __GNUC__ // curses.h or other sources may already define #undef GCC_UNUSED #define GCC_UNUSED __attribute__((unused)) #else #define GCC_UNUSED /* nothing */ #endif #define LEFT_CHANNEL 1 #define RIGHT_CHANNEL 2 // struct termios oldtio, newtio; // int M = 8 * 1024; // used by sig handler // needs to know output mode in orer to clean up terminal int output_mode; // whether we should reload the config or not int should_reload = 0; // whether we should only reload colors or not int reload_colors = 0; // these variables are used only in main, but making them global // will allow us to not free them on exit without ASan complaining struct config_params p; fftw_complex *out_bass_l, *out_bass_r; fftw_plan p_bass_l, p_bass_r; fftw_complex *out_mid_l, *out_mid_r; fftw_plan p_mid_l, p_mid_r; fftw_complex *out_treble_l, *out_treble_r; fftw_plan p_treble_l, p_treble_r; // general: cleanup void cleanup(void) { if (output_mode == OUTPUT_NCURSES) { #ifdef NCURSES cleanup_terminal_ncurses(); #else ; #endif } else if (output_mode == OUTPUT_NONCURSES) { cleanup_terminal_noncurses(); } } // general: handle signals void sig_handler(int sig_no) { if (sig_no == SIGUSR1) { should_reload = 1; return; } if (sig_no == SIGUSR2) { reload_colors = 1; return; } cleanup(); if (sig_no == SIGINT) { printf("CTRL-C pressed -- goodbye\n"); } signal(sig_no, SIG_DFL); raise(sig_no); } #ifdef ALSA static bool is_loop_device_for_sure(const char *text) { const char *const LOOPBACK_DEVICE_PREFIX = "hw:Loopback,"; return strncmp(text, LOOPBACK_DEVICE_PREFIX, strlen(LOOPBACK_DEVICE_PREFIX)) == 0; } static bool directory_exists(const char *path) { DIR *const dir = opendir(path); if (dir == NULL) return false; closedir(dir); return true; } #endif int *separate_freq_bands(int FFTbassbufferSize, fftw_complex out_bass[FFTbassbufferSize / 2 + 1], int FFTmidbufferSize, fftw_complex out_mid[FFTmidbufferSize / 2 + 1], int FFTtreblebufferSize, fftw_complex out_treble[FFTtreblebufferSize / 2 + 1], int bass_cut_off_bar, int treble_cut_off_bar, int number_of_bars, int FFTbuffer_lower_cut_off[256], int FFTbuffer_upper_cut_off[256], double eq[256], int channel, double sens, double ignore) { int n, i; double peak[257]; static int bars_left[256]; static int bars_right[256]; double y[FFTbassbufferSize / 2 + 1]; double temp; // process: separate frequency bands for (n = 0; n < number_of_bars; n++) { peak[n] = 0; i = 0; // process: get peaks for (i = FFTbuffer_lower_cut_off[n]; i <= FFTbuffer_upper_cut_off[n]; i++) { if (n <= bass_cut_off_bar) { y[i] = hypot(out_bass[i][0], out_bass[i][1]); } else if (n > bass_cut_off_bar && n <= treble_cut_off_bar) { y[i] = hypot(out_mid[i][0], out_mid[i][1]); } else if (n > treble_cut_off_bar) { y[i] = hypot(out_treble[i][0], out_treble[i][1]); } peak[n] += y[i]; // adding upp band } peak[n] = peak[n] / (FFTbuffer_upper_cut_off[n] - FFTbuffer_lower_cut_off[n] + 1); // getting average temp = peak[n] * sens * eq[n]; // multiplying with k and sens // printf("%d peak o: %f * sens: %f * k: %f = f: %f\n", o, peak[o], sens, eq[o], temp); if (temp <= ignore) temp = 0; if (channel == LEFT_CHANNEL) bars_left[n] = temp; else bars_right[n] = temp; } if (channel == LEFT_CHANNEL) return bars_left; else return bars_right; } int *monstercat_filter(int *bars, int number_of_bars, int waves, double monstercat) { int z; // process [smoothing]: monstercat-style "average" int m_y, de; if (waves > 0) { for (z = 0; z < number_of_bars; z++) { // waves bars[z] = bars[z] / 1.25; // if (bars[z] < 1) bars[z] = 1; for (m_y = z - 1; m_y >= 0; m_y--) { de = z - m_y; bars[m_y] = max(bars[z] - pow(de, 2), bars[m_y]); } for (m_y = z + 1; m_y < number_of_bars; m_y++) { de = m_y - z; bars[m_y] = max(bars[z] - pow(de, 2), bars[m_y]); } } } else if (monstercat > 0) { for (z = 0; z < number_of_bars; z++) { // if (bars[z] < 1)bars[z] = 1; for (m_y = z - 1; m_y >= 0; m_y--) { de = z - m_y; bars[m_y] = max(bars[z] / pow(monstercat, de), bars[m_y]); } for (m_y = z + 1; m_y < number_of_bars; m_y++) { de = m_y - z; bars[m_y] = max(bars[z] / pow(monstercat, de), bars[m_y]); } } } return bars; } // general: entry point int main(int argc, char **argv) { // general: define variables pthread_t p_thread; int thr_id GCC_UNUSED; float cut_off_frequency[256]; float relative_cut_off[256]; int bars[256], FFTbuffer_lower_cut_off[256], FFTbuffer_upper_cut_off[256]; int *bars_left, *bars_right, *bars_mono; int bars_mem[256]; int bars_last[256]; int previous_frame[256]; int sleep = 0; int n, height, lines, width, c, rest, inAtty, fp, fptest, rc; bool silence; // int cont = 1; int fall[256]; // float temp; float bars_peak[256]; double eq[256]; float g; struct timespec req = {.tv_sec = 0, .tv_nsec = 0}; struct timespec sleep_mode_timer = {.tv_sec = 0, .tv_nsec = 0}; char configPath[PATH_MAX]; char *usage = "\n\ Usage : " PACKAGE " [options]\n\ Visualize audio input in terminal. \n\ \n\ Options:\n\ -p path to config file\n\ -v print version\n\ \n\ Keys:\n\ Up Increase sensitivity\n\ Down Decrease sensitivity\n\ Left Decrease number of bars\n\ Right Increase number of bars\n\ r Reload config\n\ c Reload colors only\n\ f Cycle foreground color\n\ b Cycle background color\n\ q Quit\n\ \n\ as of 0.4.0 all options are specified in config file, see in '/home/username/.config/cava/' \n"; char ch = '\0'; int number_of_bars = 25; int sourceIsAuto = 1; double userEQ_keys_to_bars_ratio; struct audio_data audio; memset(&audio, 0, sizeof(audio)); #ifndef NDEBUG int maxvalue = 0; int minvalue = 0; #endif // general: console title printf("%c]0;%s%c", '\033', PACKAGE, '\007'); configPath[0] = '\0'; // general: handle Ctrl+C struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = &sig_handler; sigaction(SIGINT, &action, NULL); sigaction(SIGTERM, &action, NULL); sigaction(SIGUSR1, &action, NULL); sigaction(SIGUSR2, &action, NULL); // general: handle command-line arguments while ((c = getopt(argc, argv, "p:vh")) != -1) { switch (c) { case 'p': // argument: fifo path snprintf(configPath, sizeof(configPath), "%s", optarg); break; case 'h': // argument: print usage printf("%s", usage); return 1; case '?': // argument: print usage printf("%s", usage); return 1; case 'v': // argument: print version printf(PACKAGE " " VERSION "\n"); return 0; default: // argument: no arguments; exit abort(); } n = 0; } // general: main loop while (1) { debug("loading config\n"); // config: load struct error_s error; error.length = 0; if (!load_config(configPath, &p, 0, &error)) { fprintf(stderr, "Error loading config. %s", error.message); exit(EXIT_FAILURE); } output_mode = p.om; if (output_mode != OUTPUT_RAW) { // Check if we're running in a tty inAtty = 0; if (strncmp(ttyname(0), "/dev/tty", 8) == 0 || strcmp(ttyname(0), "/dev/console") == 0) inAtty = 1; // in macos vitual terminals are called ttys(xyz) and there are no ttys if (strncmp(ttyname(0), "/dev/ttys", 9) == 0) inAtty = 0; if (inAtty) { system("setfont cava.psf >/dev/null 2>&1"); system("setterm -blank 0"); } // We use unicode block characters to draw the bars and // the locale var LANG must be set to use unicode chars. // For some reason this var can't be retrieved with // setlocale(LANG, NULL), so we get it with getenv. // Also we can't set it with setlocale(LANG "") so we // must set LC_ALL instead. // Attempting to set to en_US if not set, if that lang // is not installed and LANG is not set there will be // no output, for mor info see #109 #344 if (!getenv("LANG")) setlocale(LC_ALL, "en_US.utf8"); else setlocale(LC_ALL, ""); } // input: init int bass_cut_off = 150; int treble_cut_off = 1500; audio.source = malloc(1 + strlen(p.audio_source)); strcpy(audio.source, p.audio_source); audio.format = -1; audio.rate = 0; audio.FFTbassbufferSize = 4096; audio.FFTmidbufferSize = 1024; audio.FFTtreblebufferSize = 512; audio.terminate = 0; if (p.stereo) audio.channels = 2; if (!p.stereo) audio.channels = 1; audio.average = false; audio.left = false; audio.right = false; if (strcmp(p.mono_option, "average") == 0) audio.average = true; if (strcmp(p.mono_option, "left") == 0) audio.left = true; if (strcmp(p.mono_option, "right") == 0) audio.right = true; audio.bass_index = 0; audio.mid_index = 0; audio.treble_index = 0; // BASS // audio.FFTbassbufferSize = audio.rate / 20; // audio.FFTbassbufferSize; audio.in_bass_r = fftw_alloc_real(2 * (audio.FFTbassbufferSize / 2 + 1)); audio.in_bass_l = fftw_alloc_real(2 * (audio.FFTbassbufferSize / 2 + 1)); memset(audio.in_bass_r, 0, 2 * (audio.FFTbassbufferSize / 2 + 1) * sizeof(double)); memset(audio.in_bass_l, 0, 2 * (audio.FFTbassbufferSize / 2 + 1) * sizeof(double)); out_bass_l = fftw_alloc_complex(2 * (audio.FFTbassbufferSize / 2 + 1)); out_bass_r = fftw_alloc_complex(2 * (audio.FFTbassbufferSize / 2 + 1)); memset(out_bass_l, 0, 2 * (audio.FFTbassbufferSize / 2 + 1) * sizeof(fftw_complex)); memset(out_bass_r, 0, 2 * (audio.FFTbassbufferSize / 2 + 1) * sizeof(fftw_complex)); p_bass_l = fftw_plan_dft_r2c_1d(audio.FFTbassbufferSize, audio.in_bass_l, out_bass_l, FFTW_MEASURE); p_bass_r = fftw_plan_dft_r2c_1d(audio.FFTbassbufferSize, audio.in_bass_r, out_bass_r, FFTW_MEASURE); // MID // audio.FFTmidbufferSize = audio.rate / bass_cut_off; // audio.FFTbassbufferSize; audio.in_mid_r = fftw_alloc_real(2 * (audio.FFTmidbufferSize / 2 + 1)); audio.in_mid_l = fftw_alloc_real(2 * (audio.FFTmidbufferSize / 2 + 1)); memset(audio.in_mid_r, 0, 2 * (audio.FFTmidbufferSize / 2 + 1) * sizeof(double)); memset(audio.in_mid_l, 0, 2 * (audio.FFTmidbufferSize / 2 + 1) * sizeof(double)); out_mid_l = fftw_alloc_complex(2 * (audio.FFTmidbufferSize / 2 + 1)); out_mid_r = fftw_alloc_complex(2 * (audio.FFTmidbufferSize / 2 + 1)); memset(out_mid_l, 0, 2 * (audio.FFTmidbufferSize / 2 + 1) * sizeof(fftw_complex)); memset(out_mid_r, 0, 2 * (audio.FFTmidbufferSize / 2 + 1) * sizeof(fftw_complex)); p_mid_l = fftw_plan_dft_r2c_1d(audio.FFTmidbufferSize, audio.in_mid_l, out_mid_l, FFTW_MEASURE); p_mid_r = fftw_plan_dft_r2c_1d(audio.FFTmidbufferSize, audio.in_mid_r, out_mid_r, FFTW_MEASURE); // TRIEBLE // audio.FFTtreblebufferSize = audio.rate / treble_cut_off; // audio.FFTbassbufferSize; audio.in_treble_r = fftw_alloc_real(2 * (audio.FFTtreblebufferSize / 2 + 1)); audio.in_treble_l = fftw_alloc_real(2 * (audio.FFTtreblebufferSize / 2 + 1)); memset(audio.in_treble_r, 0, 2 * (audio.FFTtreblebufferSize / 2 + 1) * sizeof(double)); memset(audio.in_treble_l, 0, 2 * (audio.FFTtreblebufferSize / 2 + 1) * sizeof(double)); out_treble_l = fftw_alloc_complex(2 * (audio.FFTtreblebufferSize / 2 + 1)); out_treble_r = fftw_alloc_complex(2 * (audio.FFTtreblebufferSize / 2 + 1)); memset(out_treble_l, 0, 2 * (audio.FFTtreblebufferSize / 2 + 1) * sizeof(fftw_complex)); memset(out_treble_r, 0, 2 * (audio.FFTtreblebufferSize / 2 + 1) * sizeof(fftw_complex)); p_treble_l = fftw_plan_dft_r2c_1d(audio.FFTtreblebufferSize, audio.in_treble_l, out_treble_l, FFTW_MEASURE); p_treble_r = fftw_plan_dft_r2c_1d(audio.FFTtreblebufferSize, audio.in_treble_r, out_treble_r, FFTW_MEASURE); debug("got buffer size: %d, %d, %d", audio.FFTbassbufferSize, audio.FFTmidbufferSize, audio.FFTtreblebufferSize); reset_output_buffers(&audio); debug("starting audio thread\n"); switch (p.im) { #ifdef ALSA case INPUT_ALSA: // input_alsa: wait for the input to be ready if (is_loop_device_for_sure(audio.source)) { if (directory_exists("/sys/")) { if (!directory_exists("/sys/module/snd_aloop/")) { cleanup(); fprintf(stderr, "Linux kernel module \"snd_aloop\" does not seem to be loaded.\n" "Maybe run \"sudo modprobe snd_aloop\".\n"); exit(EXIT_FAILURE); } } } thr_id = pthread_create(&p_thread, NULL, input_alsa, (void *)&audio); // starting alsamusic listener n = 0; while (audio.format == -1 || audio.rate == 0) { req.tv_sec = 0; req.tv_nsec = 1000000; nanosleep(&req, NULL); n++; if (n > 2000) { cleanup(); fprintf(stderr, "could not get rate and/or format, problems with audio thread? " "quiting...\n"); exit(EXIT_FAILURE); } } debug("got format: %d and rate %d\n", audio.format, audio.rate); break; #endif case INPUT_FIFO: // starting fifomusic listener thr_id = pthread_create(&p_thread, NULL, input_fifo, (void *)&audio); audio.rate = p.fifoSample; audio.format = p.fifoSampleBits; break; #ifdef PULSE case INPUT_PULSE: if (strcmp(audio.source, "auto") == 0) { getPulseDefaultSink((void *)&audio); sourceIsAuto = 1; } else sourceIsAuto = 0; // starting pulsemusic listener thr_id = pthread_create(&p_thread, NULL, input_pulse, (void *)&audio); audio.rate = 44100; break; #endif #ifdef SNDIO case INPUT_SNDIO: thr_id = pthread_create(&p_thread, NULL, input_sndio, (void *)&audio); audio.rate = 44100; break; #endif case INPUT_SHMEM: thr_id = pthread_create(&p_thread, NULL, input_shmem, (void *)&audio); // audio.rate = 44100; break; #ifdef PORTAUDIO case INPUT_PORTAUDIO: thr_id = pthread_create(&p_thread, NULL, input_portaudio, (void *)&audio); audio.rate = 44100; break; #endif default: exit(EXIT_FAILURE); // Can't happen. } if (p.upper_cut_off > audio.rate / 2) { cleanup(); fprintf(stderr, "higher cuttoff frequency can't be higher then sample rate / 2"); exit(EXIT_FAILURE); } bool reloadConf = false; while (!reloadConf) { // jumbing back to this loop means that you resized the screen for (n = 0; n < 256; n++) { bars_last[n] = 0; previous_frame[n] = 0; fall[n] = 0; bars_peak[n] = 0; bars_mem[n] = 0; bars[n] = 0; } switch (output_mode) { #ifdef NCURSES // output: start ncurses mode case OUTPUT_NCURSES: init_terminal_ncurses(p.color, p.bcolor, p.col, p.bgcol, p.gradient, p.gradient_count, p.gradient_colors, &width, &lines); // we have 8 times as much height due to using 1/8 block characters height = lines * 8; break; #endif case OUTPUT_NONCURSES: get_terminal_dim_noncurses(&width, &lines); init_terminal_noncurses(inAtty, p.col, p.bgcol, width, lines, p.bar_width); height = (lines - 1) * 8; break; case OUTPUT_RAW: if (strcmp(p.raw_target, "/dev/stdout") != 0) { // checking if file exists if (access(p.raw_target, F_OK) != -1) { // testopening in case it's a fifo fptest = open(p.raw_target, O_RDONLY | O_NONBLOCK, 0644); if (fptest == -1) { printf("could not open file %s for writing\n", p.raw_target); exit(1); } } else { printf("creating fifo %s\n", p.raw_target); if (mkfifo(p.raw_target, 0664) == -1) { printf("could not create fifo %s\n", p.raw_target); exit(1); } // fifo needs to be open for reading in order to write to it fptest = open(p.raw_target, O_RDONLY | O_NONBLOCK, 0644); } } fp = open(p.raw_target, O_WRONLY | O_NONBLOCK | O_CREAT, 0644); if (fp == -1) { printf("could not open file %s for writing\n", p.raw_target); exit(1); } printf("open file %s for writing raw output\n", p.raw_target); // width must be hardcoded for raw output. width = 256; if (strcmp(p.data_format, "binary") == 0) { height = pow(2, p.bit_format) - 1; } else { height = p.ascii_range; } break; default: exit(EXIT_FAILURE); // Can't happen. } // handle for user setting too many bars if (p.fixedbars) { p.autobars = 0; if (p.fixedbars * p.bar_width + p.fixedbars * p.bar_spacing - p.bar_spacing > width) p.autobars = 1; } // getting orignial numbers of barss incase of resize if (p.autobars == 1) { number_of_bars = (width + p.bar_spacing) / (p.bar_width + p.bar_spacing); // if (p.bar_spacing != 0) number_of_bars = (width - number_of_bars * p.bar_spacing // + p.bar_spacing) / bar_width; } else number_of_bars = p.fixedbars; if (number_of_bars < 1) number_of_bars = 1; // must have at least 1 bars if (number_of_bars > 256) number_of_bars = 256; // cant have more than 256 bars if (p.stereo) { // stereo must have even numbers of bars if (number_of_bars % 2 != 0) number_of_bars--; } // checks if there is stil extra room, will use this to center rest = (width - number_of_bars * p.bar_width - number_of_bars * p.bar_spacing + p.bar_spacing) / 2; if (rest < 0) rest = 0; // process [smoothing]: calculate gravity g = p.gravity * ((float)height / 2160) * pow((60 / (float)p.framerate), 2.5); // calculate integral value, must be reduced with height double integral = p.integral; if (height > 320) integral = p.integral * 1 / sqrt((log10((float)height / 10))); #ifndef NDEBUG debug("height: %d width: %d bars:%d bar width: %d rest: %d\n", height, width, number_of_bars, p.bar_width, rest); #endif if (p.stereo) number_of_bars = number_of_bars / 2; // in stereo onle half number of number_of_bars per channel if (p.userEQ_enabled && (number_of_bars > 0)) { userEQ_keys_to_bars_ratio = (double)(((double)p.userEQ_keys) / ((double)number_of_bars)); } // calculate frequency constant (used to distribute bars across the frequency band) double frequency_constant = log10((float)p.lower_cut_off / (float)p.upper_cut_off) / (1 / ((float)number_of_bars + 1) - 1); // process: calculate cutoff frequencies and eq int bass_cut_off_bar = -1; int treble_cut_off_bar = -1; bool first_bar = false; int first_treble_bar = 0; for (n = 0; n < number_of_bars + 1; n++) { double bar_distribution_coefficient = frequency_constant * (-1); bar_distribution_coefficient += ((float)n + 1) / ((float)number_of_bars + 1) * frequency_constant; cut_off_frequency[n] = p.upper_cut_off * pow(10, bar_distribution_coefficient); relative_cut_off[n] = cut_off_frequency[n] / (audio.rate / 2); // remember nyquist!, pr my calculations this should be rate/2 // and nyquist freq in M/2 but testing shows it is not... // or maybe the nq freq is in M/4 eq[n] = pow(cut_off_frequency[n], 1); eq[n] *= (float)height / pow(2, 28); if (p.userEQ_enabled) eq[n] *= p.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)]; eq[n] /= log2(audio.FFTbassbufferSize); if (cut_off_frequency[n] < bass_cut_off) { // BASS FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTbassbufferSize / 2) + 1; bass_cut_off_bar++; treble_cut_off_bar++; eq[n] *= log2(audio.FFTbassbufferSize); } else if (cut_off_frequency[n] > bass_cut_off && cut_off_frequency[n] < treble_cut_off) { // MID FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTmidbufferSize / 2) + 1; treble_cut_off_bar++; if ((treble_cut_off_bar - bass_cut_off_bar) == 1) { first_bar = true; FFTbuffer_upper_cut_off[n - 1] = relative_cut_off[n] * (audio.FFTbassbufferSize / 2); if (FFTbuffer_upper_cut_off[n - 1] < FFTbuffer_lower_cut_off[n - 1]) FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n - 1]; } else { first_bar = false; } eq[n] *= log2(audio.FFTmidbufferSize); } else { // TREBLE FFTbuffer_lower_cut_off[n] = relative_cut_off[n] * (audio.FFTtreblebufferSize / 2) + 1; first_treble_bar++; if (first_treble_bar == 1) { first_bar = true; FFTbuffer_upper_cut_off[n - 1] = relative_cut_off[n] * (audio.FFTmidbufferSize / 2); if (FFTbuffer_upper_cut_off[n - 1] < FFTbuffer_lower_cut_off[n - 1]) FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n - 1]; } else { first_bar = false; } eq[n] *= log2(audio.FFTtreblebufferSize); } if (n != 0 && !first_bar) { FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n] - 1; // pushing the spectrum up if the exponential function gets "clumped" in the // bass if (FFTbuffer_lower_cut_off[n] <= FFTbuffer_lower_cut_off[n - 1]) FFTbuffer_lower_cut_off[n] = FFTbuffer_lower_cut_off[n - 1] + 1; FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n] - 1; } #ifndef NDEBUG initscr(); curs_set(0); timeout(0); if (n != 0) { mvprintw(n, 0, "%d: %f -> %f (%d -> %d) bass: %d, treble:%d \n", n, cut_off_frequency[n - 1], cut_off_frequency[n], FFTbuffer_lower_cut_off[n - 1], FFTbuffer_upper_cut_off[n - 1], bass_cut_off_bar, treble_cut_off_bar); } #endif } if (p.stereo) number_of_bars = number_of_bars * 2; bool resizeTerminal = false; fcntl(0, F_SETFL, O_NONBLOCK); if (p.framerate <= 1) { req.tv_sec = 1 / (float)p.framerate; } else { req.tv_sec = 0; req.tv_nsec = (1 / (float)p.framerate) * 1e9; } while (!resizeTerminal) { // general: keyboard controls #ifdef NCURSES if (output_mode == OUTPUT_NCURSES) ch = getch(); #endif if (output_mode == OUTPUT_NONCURSES) ch = fgetc(stdin); switch (ch) { case 65: // key up p.sens = p.sens * 1.05; break; case 66: // key down p.sens = p.sens * 0.95; break; case 68: // key right p.bar_width++; resizeTerminal = true; break; case 67: // key left if (p.bar_width > 1) p.bar_width--; resizeTerminal = true; break; case 'r': // reload config should_reload = 1; break; case 'c': // reload colors reload_colors = 1; break; case 'f': // change forground color if (p.col < 7) p.col++; else p.col = 0; resizeTerminal = true; break; case 'b': // change backround color if (p.bgcol < 7) p.bgcol++; else p.bgcol = 0; resizeTerminal = true; break; case 'q': if (sourceIsAuto) free(audio.source); cleanup(); return EXIT_SUCCESS; } if (should_reload) { reloadConf = true; resizeTerminal = true; should_reload = 0; } if (reload_colors) { struct error_s error; error.length = 0; if (!load_config(configPath, (void *)&p, 1, &error)) { cleanup(); fprintf(stderr, "Error loading config. %s", error.message); exit(EXIT_FAILURE); } resizeTerminal = true; reload_colors = 0; } // if (cont == 0) break; #ifndef NDEBUG // clear(); refresh(); #endif // process: check if input is present silence = true; for (n = 0; n < audio.FFTbassbufferSize; n++) { if (audio.in_bass_l[n] || audio.in_bass_r[n]) { silence = false; break; } } if (silence) sleep++; else sleep = 0; // process: if input was present for the last 5 seconds apply FFT to it if (sleep < p.framerate * 5) { // process: execute FFT and sort frequency bands if (p.stereo) { fftw_execute(p_bass_l); fftw_execute(p_bass_r); fftw_execute(p_mid_l); fftw_execute(p_mid_r); fftw_execute(p_treble_l); fftw_execute(p_treble_r); bars_left = separate_freq_bands( audio.FFTbassbufferSize, out_bass_l, audio.FFTmidbufferSize, out_mid_l, audio.FFTtreblebufferSize, out_treble_l, bass_cut_off_bar, treble_cut_off_bar, number_of_bars / 2, FFTbuffer_lower_cut_off, FFTbuffer_upper_cut_off, eq, LEFT_CHANNEL, p.sens, p.ignore); bars_right = separate_freq_bands( audio.FFTbassbufferSize, out_bass_r, audio.FFTmidbufferSize, out_mid_r, audio.FFTtreblebufferSize, out_treble_r, bass_cut_off_bar, treble_cut_off_bar, number_of_bars / 2, FFTbuffer_lower_cut_off, FFTbuffer_upper_cut_off, eq, RIGHT_CHANNEL, p.sens, p.ignore); } else { fftw_execute(p_bass_l); fftw_execute(p_mid_l); fftw_execute(p_treble_l); bars_mono = separate_freq_bands( audio.FFTbassbufferSize, out_bass_l, audio.FFTmidbufferSize, out_mid_l, audio.FFTtreblebufferSize, out_treble_l, bass_cut_off_bar, treble_cut_off_bar, number_of_bars, FFTbuffer_lower_cut_off, FFTbuffer_upper_cut_off, eq, LEFT_CHANNEL, p.sens, p.ignore); } } else { //**if in sleep mode wait and continue**// #ifndef NDEBUG printw("no sound detected for 5 sec, going to sleep mode\n"); #endif // wait 0.1 sec, then check sound again. sleep_mode_timer.tv_sec = 0; sleep_mode_timer.tv_nsec = 100000000; nanosleep(&sleep_mode_timer, NULL); continue; } // process [filter] if (p.monstercat) { if (p.stereo) { bars_left = monstercat_filter(bars_left, number_of_bars / 2, p.waves, p.monstercat); bars_right = monstercat_filter(bars_right, number_of_bars / 2, p.waves, p.monstercat); } else { bars_mono = monstercat_filter(bars_mono, number_of_bars, p.waves, p.monstercat); } } // processing signal bool senselow = true; for (n = 0; n < number_of_bars; n++) { // mirroring stereo channels if (p.stereo) { if (n < number_of_bars / 2) { bars[n] = bars_left[number_of_bars / 2 - n - 1]; } else { bars[n] = bars_right[n - number_of_bars / 2]; } } else { bars[n] = bars_mono[n]; } // process [smoothing]: falloff if (g > 0) { if (bars[n] < bars_last[n]) { bars[n] = bars_peak[n] - (g * fall[n] * fall[n]); if (bars[n] < 0) bars[n] = 0; fall[n]++; } else { bars_peak[n] = bars[n]; fall[n] = 0; } bars_last[n] = bars[n]; } // process [smoothing]: integral if (p.integral > 0) { bars[n] = bars_mem[n] * integral + bars[n]; bars_mem[n] = bars[n]; int diff = height - bars[n]; if (diff < 0) diff = 0; double div = 1 / (diff + 1); // bars[n] = bars[n] - pow(div, 10) * (height + 1); bars_mem[n] = bars_mem[n] * (1 - div / 20); } #ifndef NDEBUG mvprintw(n, 0, "%d: f:%f->%f (%d->%d), eq:\ %15e, peak:%d \n", n, cut_off_frequency[n], cut_off_frequency[n + 1], FFTbuffer_lower_cut_off[n], FFTbuffer_upper_cut_off[n], eq[n], bars[n]); if (bars[n] < minvalue) { minvalue = bars[n]; debug("min value: %d\n", minvalue); // checking maxvalue 10000 } if (bars[n] > maxvalue) { maxvalue = bars[n]; } if (bars[n] < 0) { debug("negative bar value!! %d\n", bars[n]); // exit(EXIT_FAILURE); // Can't happen. } #endif // zero values causes divided by zero segfault (if not raw) if (output_mode != OUTPUT_RAW && bars[n] < 1) bars[n] = 1; // autmatic sens adjustment if (p.autosens) { if (bars[n] > height && senselow) { p.sens = p.sens * 0.98; senselow = false; } } } if (p.autosens && !silence && senselow) p.sens = p.sens * 1.001; #ifndef NDEBUG mvprintw(n + 1, 0, "sensitivity %.10e", p.sens); mvprintw(n + 2, 0, "min value: %d\n", minvalue); // checking maxvalue 10000 mvprintw(n + 3, 0, "max value: %d\n", maxvalue); // checking maxvalue 10000 #endif // output: draw processed input #ifdef NDEBUG switch (output_mode) { case OUTPUT_NCURSES: #ifdef NCURSES rc = draw_terminal_ncurses(inAtty, lines, width, number_of_bars, p.bar_width, p.bar_spacing, rest, bars, previous_frame, p.gradient); break; #endif case OUTPUT_NONCURSES: rc = draw_terminal_noncurses(inAtty, lines, width, number_of_bars, p.bar_width, p.bar_spacing, rest, bars, previous_frame); break; case OUTPUT_RAW: rc = print_raw_out(number_of_bars, fp, p.is_bin, p.bit_format, p.ascii_range, p.bar_delim, p.frame_delim, bars); break; default: exit(EXIT_FAILURE); // Can't happen. } // terminal has been resized breaking to recalibrating values if (rc == -1) resizeTerminal = true; #endif memcpy(previous_frame, bars, 256 * sizeof(int)); // checking if audio thread has exited unexpectedly if (audio.terminate == 1) { cleanup(); fprintf(stderr, "Audio thread exited unexpectedly. %s\n", audio.error_message); exit(EXIT_FAILURE); } nanosleep(&req, NULL); } // resize terminal } // reloading config req.tv_sec = 0; req.tv_nsec = 100; // waiting some time to make shure audio is ready nanosleep(&req, NULL); //**telling audio thread to terminate**// audio.terminate = 1; pthread_join(p_thread, NULL); if (p.userEQ_enabled) free(p.userEQ); if (sourceIsAuto) free(audio.source); fftw_free(audio.in_bass_r); fftw_free(audio.in_bass_l); fftw_free(out_bass_r); fftw_free(out_bass_l); fftw_destroy_plan(p_bass_l); fftw_destroy_plan(p_bass_r); fftw_free(audio.in_mid_r); fftw_free(audio.in_mid_l); fftw_free(out_mid_r); fftw_free(out_mid_l); fftw_destroy_plan(p_mid_l); fftw_destroy_plan(p_mid_r); fftw_free(audio.in_treble_r); fftw_free(audio.in_treble_l); fftw_free(out_treble_r); fftw_free(out_treble_l); fftw_destroy_plan(p_treble_l); fftw_destroy_plan(p_treble_r); cleanup(); // fclose(fp); } }