| 
						 
							
							
							
						 
					 | 
				
			
			 | 
			 | 
			
				@@ -1,7 +1,7 @@
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				/*
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				 * nncmpp -- the MPD client you never knew you needed
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				 *
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				 * Copyright (c) 2016 - 2020, Přemysl Eric Janouch <p@janouch.name>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				 * Copyright (c) 2016 - 2021, Přemysl Eric Janouch <p@janouch.name>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				 *
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				 * Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				 * purpose with or without fee is hereby granted.
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -39,6 +39,8 @@
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					 * Can't use A_REVERSE because bold'd be bright.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					 * Unfortunately ran out of B&W attributes.       */ \
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					XX( MULTISELECT, multiselect,  -1,  6, 0           ) \
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					/* This ought to be indicative enough.            */ \
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					XX( DEFOCUSED,   defocused,    -1, -1, A_UNDERLINE ) \
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					XX( SCROLLBAR,   scrollbar,    -1, -1, 0           ) \
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					/* These are for debugging only                   */ \
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					XX( WARNING,     warning,       3, -1, 0           ) \
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -76,15 +78,13 @@ enum
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#include <math.h>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#include <locale.h>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#include <termios.h>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifndef TIOCGWINSZ
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#include <sys/ioctl.h>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // ! TIOCGWINSZ
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// ncurses is notoriously retarded for input handling, we need something
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// different if only to receive mouse events reliably.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				//
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// 2020 update: ncurses is mostly reliable now but rxvt-unicode needs to start
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// supporting 1006, or ncurses needs to start supporting the 1015 mode.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// 2021 update: ncurses is mostly reliable now, though rxvt-unicode only
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// supports the 1006 mode that ncurses also supports mode starting with 9.25.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#include "termo.h"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -93,6 +93,13 @@ enum
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#include <curl/curl.h>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// The spectrum analyser requires a DFT transform.  The FFTW library is fairly
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// efficient, and doesn't have a requirement on the number of bins.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifdef WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#include <fftw3.h>
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#define APP_TITLE  PROGRAM_NAME         ///< Left top corner
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// --- Utilities ---------------------------------------------------------------
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -101,7 +108,7 @@ enum
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				update_curses_terminal_size (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#if defined HAVE_RESIZETERM && defined TIOCGWINSZ
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct winsize size;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -558,6 +565,301 @@ item_list_resize (struct item_list *self, size_t len)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					self->len = len;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// --- Spectrum analyzer -------------------------------------------------------
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// See http://www.zytrax.com/tech/audio/equalization.html
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// for a good write-up about this problem domain
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifdef WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				struct spectrum
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int sampling_rate;                  ///< Number of samples per seconds
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int channels;                       ///< Number of sampled channels
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int bits;                           ///< Number of bits per sample
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int bars;                           ///< Number of output vertical bars
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int bins;                           ///< Number of DFT bins
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int useful_bins;                    ///< Bins up to the Nyquist frequency
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int samples;                        ///< Number of windows to average
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float accumulator_scale;            ///< Scaling factor for accum. values
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int *top_bins;                      ///< Top DFT bin index for each bar
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					char *spectrum;                     ///< String buffer for the "render"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					void *buffer;                       ///< Input buffer
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					size_t buffer_len;                  ///< Input buffer fill level
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					size_t buffer_size;                 ///< Input buffer size
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					/// Decode the respective part of the buffer into the second half of data
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					void (*decode) (struct spectrum *, int sample);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float *data;                        ///< Normalized audio data
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float *window;                      ///< Sampled window function
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float *windowed;                    ///< data * window
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					fftwf_complex *out;                 ///< DFT output
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					fftwf_plan p;                       ///< DFT plan/FFTW configuration
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float *accumulator;                 ///< Accumulated powers of samples
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				};
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// - - Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// Out: float[n] of 0..1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				window_hann (float *coefficients, size_t n)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (size_t i = 0; i < n; i++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						float sine = sin (M_PI * i / n);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						coefficients[i] = sine * sine;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// In: float[n] of -1..1, float[n] of 0..1; out: float[n] of -1..1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				window_apply (const float *in, const float *coefficients, float *out, size_t n)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (size_t i = 0; i < n; i++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						out[i] = in[i] * coefficients[i];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// In: float[n] of 0..1; out: float 0..n, describing the coherent gain
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static float
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				window_coherent_gain (const float *in, size_t n)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float sum = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (size_t i = 0; i < n; i++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						sum += in[i];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return sum;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// - - Decoding  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_decode_8 (struct spectrum *s, int sample)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					size_t n = s->useful_bins;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float *data = s->data + n;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (int8_t *p = (int8_t *) s->buffer + sample * n * s->channels;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						n--; p += s->channels)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						int32_t acc = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						for (int ch = 0; ch < s->channels; ch++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							acc += p[ch];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						*data++ = (float) acc / s->channels / -INT8_MIN;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_decode_16 (struct spectrum *s, int sample)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					size_t n = s->useful_bins;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float *data = s->data + n;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (int16_t *p = (int16_t *) s->buffer + sample * n * s->channels;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						n--; p += s->channels)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						int32_t acc = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						for (int ch = 0; ch < s->channels; ch++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							acc += p[ch];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						*data++ = (float) acc / s->channels / -INT16_MIN;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_decode_16_2 (struct spectrum *s, int sample)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					size_t n = s->useful_bins;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float *data = s->data + n;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (int16_t *p = (int16_t *) s->buffer + sample * n * 2; n--; p += 2)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						*data++ = ((int32_t) p[0] + p[1]) / 2. / -INT16_MIN;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// - - Spectrum analysis - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static const char *spectrum_bars[] =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{ " ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				/// Assuming the input buffer is full, updates the rendered spectrum
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_sample (struct spectrum *s)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					memset (s->accumulator, 0, sizeof *s->accumulator * s->useful_bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Credit for the algorithm goes to Audacity's /src/SpectrumAnalyst.cpp,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// apparently Welch's method
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (int sample = 0; sample < s->samples; sample++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// We use 50% overlap and start with data from the last run (if any)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						memmove (s->data, s->data + s->useful_bins,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							sizeof *s->data * s->useful_bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						s->decode (s, sample);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						window_apply (s->data, s->window, s->windowed, s->bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						fftwf_execute (s->p);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						for (int bin = 0; bin < s->useful_bins; bin++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							// out[0][0] is the DC component, not useful to us
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							float re = s->out[bin + 1][0];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							float im = s->out[bin + 1][1];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							s->accumulator[bin] += re * re + im * im;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int last_bin = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					char *p = s->spectrum;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (int bar = 0; bar < s->bars; bar++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						int top_bin = s->top_bins[bar];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// Think of this as accumulating energies within bands,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// so that it matches our non-linear hearing--there's no averaging.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// For more precision, we could employ an "equal loudness contour".
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						float acc = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						for (int bin = last_bin; bin < top_bin; bin++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							acc += s->accumulator[bin];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						last_bin = top_bin;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						float db = 10 * log10f (acc * s->accumulator_scale);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (db > 0)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							db = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// Assuming decibels are always negative (i.e., properly normalized).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// The division defines the cutoff: 9 * 7 = 63 dB of range.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						int height = N_ELEMENTS (spectrum_bars) - 1 + (int) (db / 7);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						p += strlen (strcpy (p, spectrum_bars[MAX (height, 0)]));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static bool
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_init (struct spectrum *s, char *format, int bars, struct error **e)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					errno = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					long sampling_rate, bits, channels;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (!format
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					 || (sampling_rate = strtol (format, &format, 10), *format++ != ':')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					 || (bits          = strtol (format, &format, 10), *format++ != ':')
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					 || (channels      = strtol (format, &format, 10), *format)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					 || errno != 0)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return error_set (e, "invalid format, expected RATE:BITS:CHANNELS");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (sampling_rate < 20000 || sampling_rate > INT_MAX)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return error_set (e, "unsupported sampling rate (%ld)", sampling_rate);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (bits != 8 && bits != 16)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return error_set (e, "unsupported bit count (%ld)", bits);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (channels < 1 || channels > INT_MAX)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return error_set (e, "no channels to sample (%ld)", channels);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (bars < 1 || bars > 12)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return error_set (e, "requested too few or too many bars (%d)", bars);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// All that can fail henceforth is memory allocation
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					*s = (struct spectrum)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						.sampling_rate = sampling_rate,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						.bits          = bits,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						.channels      = channels,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						.bars          = bars,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					};
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// The number of bars is always smaller than that of the samples (bins).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Let's start with the equation of the top FFT bin to use for a given bar:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//   top_bin = (num_bins + 1) ^ (bar / num_bars) - 1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// N.b. if we didn't subtract, the power function would make this ≥ 1.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// N.b. we then also need to extend the range by the same amount.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// We need the amount of bins for the first bar to be at least one:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//         1 ≤ (num_bins + 1) ^   (1 / num_bars) - 1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Solving with Wolfram Alpha gives us:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//   num_bins ≥ (2 ^ num_bars) - 1  [for y > 0]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// And we need to remember that half of the FFT bins are useless/missing--
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// FFTW skips useless points past the Nyquist frequency.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int necessary_bins = 2 << s->bars;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Discard frequencies above 20 kHz, which take up a constant ratio
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// of all bins, given by the sampling rate.  A more practical/efficient
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// solution would be to just handle 96/192/... kHz rates as bitshifts.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Filtering out sub-20 Hz frequencies would be even more wasteful than
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// this wild DFT size, so we don't even try.  While we may just shift
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// the lowest used bin easily within the extra range provided by this
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// extension (the Nyquist is usually above 22 kHz, and it hardly matters
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// if we go a bit beyond 20 kHz in the last bin), for a small number of bars
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// the first bin already includes audible frequencies, and even for larger
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// numbers it wouldn't be too accurate.  An exact solution would require
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// having the amount of bins be strictly a factor of Nyquist / 20 (stemming
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// from the equation 20 = Nyquist / bins).  Since log2(44100 / 2 / 20) > 10,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// it would be fairly expensive, and somewhat slowly updating.  Always.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// (Note that you can increase window overlap to get smoother framerates,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// but it would remain laggy.)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					double audible_ratio = s->sampling_rate / 2. / 20000;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->bins = ceil (necessary_bins * MAX (audible_ratio, 1));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->useful_bins = s->bins / 2;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int used_bins = necessary_bins / 2;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->spectrum = xcalloc (sizeof *s->spectrum, s->bars * 3 + 1);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->top_bins = xcalloc (sizeof *s->top_bins, s->bars);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (int bar = 0; bar < s->bars; bar++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						int top_bin = floor (pow (used_bins + 1, (bar + 1.) / s->bars)) - 1;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						s->top_bins[bar] = MIN (top_bin, used_bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Limit updates to 30 times per second to limit CPU load
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->samples = s->sampling_rate / s->bins * 2 / 30;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (s->samples < 1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						s->samples = 1;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// XXX: we average the channels but might want to average the DFT results
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (s->bits == 8)   s->decode = spectrum_decode_8;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (s->bits == 16)  s->decode = spectrum_decode_16;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Micro-optimize to achieve some piece of mind; it's weak but measurable
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (s->bits == 16 && s->channels == 2)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						s->decode = spectrum_decode_16_2;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->buffer_size = s->samples * s->useful_bins * s->bits / 8 * s->channels;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->buffer = xcalloc (1, s->buffer_size);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Prepare the window
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->window = xcalloc (sizeof *s->window, s->bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					window_hann (s->window, s->bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Multiply by 2 for only using half of the DFT's result, then adjust to
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// the total energy of the window.  Both squared, because the accumulator
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// contains squared values.  Compute the average, and convert to decibels.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// See also the mildly confusing https://dsp.stackexchange.com/a/14945.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					float coherent_gain = window_coherent_gain (s->window, s->bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->accumulator_scale = 2 * 2 / coherent_gain / coherent_gain / s->samples;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->data = xcalloc (sizeof *s->data, s->bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->windowed = fftw_malloc (sizeof *s->windowed * s->bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->out = fftw_malloc (sizeof *s->out * (s->useful_bins + 1));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->p = fftwf_plan_dft_r2c_1d (s->bins, s->windowed, s->out, FFTW_MEASURE);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					s->accumulator = xcalloc (sizeof *s->accumulator, s->useful_bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return true;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_free (struct spectrum *s)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					free (s->accumulator);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					fftwf_destroy_plan (s->p);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					fftw_free (s->out);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					fftw_free (s->windowed);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					free (s->data);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					free (s->window);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					free (s->spectrum);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					free (s->top_bins);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					free (s->buffer);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					memset (s, 0, sizeof *s);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// --- Application -------------------------------------------------------------
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// Function names are prefixed mostly because of curses which clutters the
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -658,6 +960,7 @@ static struct app_context
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct config config;               ///< Program configuration
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct strv streams;                ///< List of "name NUL URI NUL"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct strv enqueue;                ///< Items to enqueue once connected
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct tab *help_tab;               ///< Special help tab
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct tab *tabs;                   ///< All other tabs
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -673,6 +976,13 @@ static struct app_context
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int gauge_offset;                   ///< Offset to the gauge or -1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int gauge_width;                    ///< Width of the gauge, if present
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifdef WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct spectrum spectrum;           ///< Spectrum analyser
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int spectrum_fd;                    ///< FIFO file descriptor (non-blocking)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int spectrum_column, spectrum_row;  ///< Position for fast refresh
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct poller_fd spectrum_event;    ///< FIFO watcher
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct line_editor editor;          ///< Line editor
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct poller_idle refresh_event;   ///< Refresh the screen
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -682,6 +992,7 @@ static struct app_context
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct poller_timer tk_timer;       ///< termo timeout timer
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					bool locale_is_utf8;                ///< The locale is Unicode
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					bool use_partial_boxes;             ///< Use Unicode box drawing chars
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					bool focused;                       ///< Whether the terminal has focus
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct attrs attrs[ATTRIBUTE_COUNT];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -747,6 +1058,22 @@ static struct config_schema g_config_settings[] =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .comment   = "Where all the files MPD is playing are located",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .type      = CONFIG_ITEM_STRING },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifdef WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{ .name      = "spectrum_path",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .comment   = "Visualizer feed path to a FIFO audio output",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .type      = CONFIG_ITEM_STRING },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// MPD's "outputs" command doesn't include this information
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{ .name      = "spectrum_format",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .comment   = "Visualizer feed data format",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .type      = CONFIG_ITEM_STRING,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .default_  = "\"44100:16:2\"" },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// 10 is about the useful limit, then it gets too computationally expensive
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{ .name      = "spectrum_bars",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .comment   = "Number of computed audio spectrum bars",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .type      = CONFIG_ITEM_INTEGER,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					  .default_  = "8" },
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Disabling this minimises MPD traffic and has the following caveats:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//  - when MPD stalls on retrieving audio data, we keep ticking
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//  - when the "play" succeeds in ACTION_MPD_REPLACE for the same item as
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -896,11 +1223,17 @@ app_init_context (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.client = mpd_client_make (&g.poller);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.config = config_make ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.streams = strv_make ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.enqueue = strv_make ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.playlist = item_list_make ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.playback_info = str_map_make (free);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.playback_info.key_xfrm = tolower_ascii_strxfrm;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifdef WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.spectrum_fd = -1;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.spectrum_row = g.spectrum_column = -1;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// This is also approximately what libunistring does internally,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// since the locale name is canonicalized by locale_charset().
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Note that non-Unicode locales are handled pretty inefficiently.
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -910,6 +1243,9 @@ app_init_context (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// TODO: make this configurable
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.use_partial_boxes = g.locale_is_utf8;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Presumably, although not necessarily; unsure if queryable at all
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.focused = true;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_init_attributes ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -918,9 +1254,9 @@ app_init_terminal (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					TERMO_CHECK_VERSION;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (!(g.tk = termo_new (STDIN_FILENO, NULL, 0)))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						abort ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						exit_fatal ("failed to set up the terminal");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (!initscr () || nonl () == ERR)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						abort ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						exit_fatal ("failed to set up the terminal");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// By default we don't use any colors so they're not required...
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (start_color () == ERR
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -949,8 +1285,18 @@ app_free_context (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mpd_client_free (&g.client);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					str_map_free (&g.playback_info);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					strv_free (&g.streams);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					strv_free (&g.enqueue);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					item_list_free (&g.playlist);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifdef WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					spectrum_free (&g.spectrum);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (g.spectrum_fd != -1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						poller_fd_reset (&g.spectrum_event);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						xclose (g.spectrum_fd);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					line_editor_free (&g.editor);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					config_free (&g.config);
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1212,6 +1558,21 @@ app_draw_header (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.tabs_offset = g.header_height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					LIST_FOR_EACH (struct tab, iter, g.tabs)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						row_buffer_append (&buf, iter->name, attrs[iter == g.active_tab]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifdef WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// This seems like the most reasonable, otherwise unoccupied space
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (g.spectrum_fd != -1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// Find some space and remember where it was, for fast refreshes
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						row_buffer_ellipsis (&buf, COLS - g.spectrum.bars - 1);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						row_buffer_align (&buf, COLS - g.spectrum.bars, attrs[false]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						g.spectrum_row = g.header_height;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						g.spectrum_column = buf.total_width;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						row_buffer_append (&buf, g.spectrum.spectrum, attrs[false]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_flush_header (&buf, attrs[false]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					const char *header = g.active_tab->header;
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1338,11 +1699,13 @@ app_draw_view (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						bool override_colors = true;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (item_index == tab->item_selected)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							row_attrs = APP_ATTR (SELECTION);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							row_attrs = g.focused
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								? APP_ATTR (SELECTION) : APP_ATTR (DEFOCUSED);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						else if (tab->item_mark > -1 &&
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						   ((item_index >= tab->item_mark && item_index <= tab->item_selected)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						 || (item_index >= tab->item_selected && item_index <= tab->item_mark)))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							row_attrs = APP_ATTR (MULTISELECT);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							row_attrs = g.focused
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								? APP_ATTR (MULTISELECT) : APP_ATTR (DEFOCUSED);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						else
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							override_colors = false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -2223,6 +2586,14 @@ app_init_bindings (const char *keymap,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static bool
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				app_process_termo_event (termo_key_t *event)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					bool handled = false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if ((handled = event->type == TERMO_TYPE_FOCUS))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						g.focused = !!event->code.focused;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						app_invalidate ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// Senseless fall-through
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct binding dummy = { *event, 0, 0 }, *binding;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (g.editor.line)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -2230,7 +2601,7 @@ app_process_termo_event (termo_key_t *event)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							sizeof *binding, app_binding_cmp)))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return app_editor_process_action (binding->action);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (event->type != TERMO_TYPE_KEY || event->modifiers != 0)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return handled;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						line_editor_insert (&g.editor, event->code.codepoint);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						app_invalidate ();
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -2249,7 +2620,7 @@ app_process_termo_event (termo_key_t *event)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (app_goto_tab ((n == 0 ? 10 : n) - 1))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return true;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return handled;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// --- Current tab -------------------------------------------------------------
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -3407,6 +3778,143 @@ debug_tab_init (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return super;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// --- Spectrum analyser -------------------------------------------------------
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#ifdef WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_redraw (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// A full refresh would be too computationally expensive,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// let's hack around it in this case
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (g.spectrum_row != -1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// Don't mess up the line editor caret, when it's shown
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						int last_x, last_y;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						getyx (stdscr, last_y, last_x);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						attrset (APP_ATTR (TAB_BAR));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						mvaddstr (g.spectrum_row, g.spectrum_column, g.spectrum.spectrum);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						attrset (0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						move (last_y, last_x);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						refresh ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						app_invalidate ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// When any problem occurs with the FIFO, we'll just give up on it completely
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_discard_fifo (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (g.spectrum_fd != -1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						poller_fd_reset (&g.spectrum_event);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						xclose (g.spectrum_fd);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						g.spectrum_fd = -1;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_free (&g.spectrum);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						g.spectrum_row = g.spectrum_column = -1;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						app_invalidate ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_on_fifo_readable (const struct pollfd *pfd, void *user_data)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					(void) user_data;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct spectrum *s = &g.spectrum;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					bool update = false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					ssize_t n;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				restart:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					while ((n = read (pfd->fd,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						s->buffer + s->buffer_len, s->buffer_size - s->buffer_len)) > 0)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if ((s->buffer_len += n) == s->buffer_size)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							update = true;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							spectrum_sample (s);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							s->buffer_len = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (!n)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_discard_fifo ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else if (errno == EINTR)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						goto restart;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else if (errno != EAGAIN)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						print_error ("spectrum: %s", strerror (errno));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_discard_fifo ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else if (update)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_redraw ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// When playback is stopped, we need to feed the analyser some zeroes ourselves.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// We could also just hide it.  Hard to say which is simpler or better.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_clear (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (g.spectrum_fd != -1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						struct spectrum *s = &g.spectrum;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						memset (s->buffer, 0, s->buffer_size);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_sample (s);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_sample (s);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						s->buffer_len = 0;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_redraw ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				spectrum_setup_fifo (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					const char *spectrum_path =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						get_config_string (g.config.root, "settings.spectrum_path");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					const char *spectrum_format =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						get_config_string (g.config.root, "settings.spectrum_format");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct config_item *spectrum_bars =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						config_item_get (g.config.root, "settings.spectrum_bars", NULL);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (!spectrum_path)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct error *e = NULL;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					char *path = resolve_filename
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						(spectrum_path, resolve_relative_config_filename);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (!path)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						print_error ("spectrum: %s", "FIFO path could not be resolved");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else if (!g.locale_is_utf8)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						print_error ("spectrum: %s", "UTF-8 locale required");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else if (!spectrum_init (&g.spectrum,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						(char *) spectrum_format, spectrum_bars->value.integer, &e))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						print_error ("spectrum: %s", e->message);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						error_free (e);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else if ((g.spectrum_fd = open (path, O_RDONLY | O_NONBLOCK)) == -1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						print_error ("spectrum: %s: %s", path, strerror (errno));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_free (&g.spectrum);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						g.spectrum_event = poller_fd_make (&g.poller, g.spectrum_fd);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						g.spectrum_event.dispatcher = spectrum_on_fifo_readable;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						poller_fd_set (&g.spectrum_event, POLLIN);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					free (path);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#else  // ! WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#define spectrum_setup_fifo()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#define spectrum_clear()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#define spectrum_discard_fifo()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				#endif  // ! WITH_FFTW
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// --- MPD interface -----------------------------------------------------------
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -3467,6 +3975,10 @@ mpd_update_playback_state (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (!strcmp (state, "play"))   g.state = PLAYER_PLAYING;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (!strcmp (state, "pause"))  g.state = PLAYER_PAUSED;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (g.state == PLAYER_STOPPED)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spectrum_clear ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Values in "time" are always rounded.  "elapsed", introduced in MPD 0.16,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// is in millisecond precision and "duration" as well, starting with 0.20.
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -3683,6 +4195,57 @@ mpd_queue_reconnect (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					poller_timer_set (&g.connect_event, 5 * 1000);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// On an error, MPD discards the rest of our enqueuing commands--work it around
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void mpd_enqueue_step (size_t start_offset);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				mpd_on_enqueue_response (const struct mpd_response *response,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					const struct strv *data, void *user_data)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					(void) data;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					intptr_t start_offset = (intptr_t) user_data;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (response->success)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						strv_reset (&g.enqueue);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// Their addition may also overflow, but YOLO
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						hard_assert (start_offset >= 0 && response->list_offset >= 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						print_error ("%s: %s", response->message_text,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							g.enqueue.vector[start_offset + response->list_offset]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						mpd_enqueue_step (start_offset + response->list_offset + 1);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				mpd_enqueue_step (size_t start_offset)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct mpd_client *c = &g.client;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (start_offset >= g.enqueue.len)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						strv_reset (&g.enqueue);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// TODO: might want to consider using addid and autoplaying
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mpd_client_list_begin (c);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (size_t i = start_offset; i < g.enqueue.len; i++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						mpd_client_send_command (c, "add", g.enqueue.vector[i], NULL);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mpd_client_list_end (c);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mpd_client_add_task (c, mpd_on_enqueue_response, (void *) start_offset);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mpd_client_idle (c, 0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				mpd_on_ready (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mpd_request_info ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					library_tab_reload (NULL);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					spectrum_setup_fifo ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mpd_enqueue_step (0);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				mpd_on_password_response (const struct mpd_response *response,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					const struct strv *data, void *user_data)
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -3692,10 +4255,7 @@ mpd_on_password_response (const struct mpd_response *response,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct mpd_client *c = &g.client;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (response->success)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						mpd_request_info ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						library_tab_reload (NULL);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						mpd_on_ready ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						print_error ("%s: %s",
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -3718,10 +4278,7 @@ mpd_on_connected (void *user_data)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						mpd_client_add_task (c, mpd_on_password_response, NULL);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						mpd_request_info ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						library_tab_reload (NULL);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						mpd_on_ready ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -3738,6 +4295,8 @@ mpd_on_failure (void *user_data)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mpd_update_playback_state ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					current_tab_update ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					info_tab_update ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					spectrum_discard_fifo ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -4018,6 +4577,28 @@ app_init_poller_events (void)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.refresh_event.dispatcher = app_on_refresh;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				static void
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				app_init_enqueue (char *argv[], int argc)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// TODO: MPD is unwilling to play directories, so perhaps recurse ourselves
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					char cwd[4096] = "";
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for (int i = 0; i < argc; i++)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// This is a super-trivial method of URL detection, however anything
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// contaning the scheme and authority delimiters in a sequence is most
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// certainly not a filesystem path, and thus it will work as expected.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// Error handling may be done by MPD.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						const char *path_or_URL = argv[i];
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (*path_or_URL == '/' || strstr (path_or_URL, "://"))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							strv_append (&g.enqueue, path_or_URL);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						else if (!*cwd && !getcwd (cwd, sizeof cwd))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							exit_fatal ("getcwd: %s", strerror (errno));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						else
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							strv_append_owned (&g.enqueue,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								xstrdup_printf ("%s/%s", cwd, path_or_URL));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				int
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				main (int argc, char *argv[])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				{
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -4030,7 +4611,8 @@ main (int argc, char *argv[])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					};
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					struct opt_handler oh =
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						opt_handler_make (argc, argv, opts, NULL, "Terminal-based MPD client.");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						opt_handler_make (argc, argv, opts,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							"[URL | PATH]...", "Terminal-based MPD client.");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					int c;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					while ((c = opt_handler_get (&oh)) != -1)
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -4053,12 +4635,6 @@ main (int argc, char *argv[])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					argc -= optind;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					argv += optind;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (argc)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						opt_handler_usage (&oh, stderr);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						exit (EXIT_FAILURE);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					opt_handler_free (&oh);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// We only need to convert to and from the terminal encoding
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -4066,10 +4642,11 @@ main (int argc, char *argv[])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						print_warning ("failed to set the locale");
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_init_context ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_init_enqueue (argv, argc);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_load_configuration ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_init_terminal ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					signals_setup_handlers ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_init_poller_events ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_init_terminal ();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g_normal_keys = app_init_bindings ("normal",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						g_normal_defaults, N_ELEMENTS (g_normal_defaults), &g_normal_keys_len);
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -4089,6 +4666,11 @@ main (int argc, char *argv[])
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_prepend_tab (current_tab_init ());
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					app_switch_tab ((g.help_tab = help_tab_init ()));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// TODO: the help tab should be the default for new users only,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//   so provide a configuration option to flip this
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if (argc)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						app_switch_tab (&g_current_tab);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					g.polling = true;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					while (g.polling)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						poller_run (&g.poller);
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				 
 |