|
|
|
@@ -569,6 +569,9 @@ item_list_resize (struct item_list *self, size_t len)
|
|
|
|
|
|
|
|
|
|
|
|
// --- Spectrum analyzer -------------------------------------------------------
|
|
|
|
// --- Spectrum analyzer -------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// See http://www.zytrax.com/tech/audio/equalization.html
|
|
|
|
|
|
|
|
// for a good write-up about this problem domain
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_FFTW
|
|
|
|
#ifdef WITH_FFTW
|
|
|
|
|
|
|
|
|
|
|
|
struct spectrum
|
|
|
|
struct spectrum
|
|
|
|
@@ -589,7 +592,7 @@ struct spectrum
|
|
|
|
size_t buffer_len; ///< Input buffer fill level
|
|
|
|
size_t buffer_len; ///< Input buffer fill level
|
|
|
|
size_t buffer_size; ///< Input buffer size
|
|
|
|
size_t buffer_size; ///< Input buffer size
|
|
|
|
|
|
|
|
|
|
|
|
/// Decode the respective part of the buffer into the last 1/3 of data
|
|
|
|
/// Decode the respective part of the buffer into the second half of data
|
|
|
|
void (*decode) (struct spectrum *, int sample);
|
|
|
|
void (*decode) (struct spectrum *, int sample);
|
|
|
|
|
|
|
|
|
|
|
|
float *data; ///< Normalized audio data
|
|
|
|
float *data; ///< Normalized audio data
|
|
|
|
@@ -638,13 +641,13 @@ spectrum_decode_8 (struct spectrum *s, int sample)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
size_t n = s->useful_bins;
|
|
|
|
size_t n = s->useful_bins;
|
|
|
|
float *data = s->data + n;
|
|
|
|
float *data = s->data + n;
|
|
|
|
int8_t *p = (int8_t *) s->buffer + sample * n * s->channels;
|
|
|
|
for (int8_t *p = (int8_t *) s->buffer + sample * n * s->channels;
|
|
|
|
while (n--)
|
|
|
|
n--; p += s->channels)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int32_t acc = 0;
|
|
|
|
int32_t acc = 0;
|
|
|
|
for (int ch = 0; ch < s->channels; ch++)
|
|
|
|
for (int ch = 0; ch < s->channels; ch++)
|
|
|
|
acc += *p++;
|
|
|
|
acc += p[ch];
|
|
|
|
*data++ = (float) acc / -INT8_MIN / s->channels;
|
|
|
|
*data++ = (float) acc / s->channels / -INT8_MIN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -653,16 +656,25 @@ spectrum_decode_16 (struct spectrum *s, int sample)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
size_t n = s->useful_bins;
|
|
|
|
size_t n = s->useful_bins;
|
|
|
|
float *data = s->data + n;
|
|
|
|
float *data = s->data + n;
|
|
|
|
int16_t *p = (int16_t *) s->buffer + sample * n * s->channels;
|
|
|
|
for (int16_t *p = (int16_t *) s->buffer + sample * n * s->channels;
|
|
|
|
while (n--)
|
|
|
|
n--; p += s->channels)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int32_t acc = 0;
|
|
|
|
int32_t acc = 0;
|
|
|
|
for (int ch = 0; ch < s->channels; ch++)
|
|
|
|
for (int ch = 0; ch < s->channels; ch++)
|
|
|
|
acc += *p++;
|
|
|
|
acc += p[ch];
|
|
|
|
*data++ = (float) acc / -INT16_MIN / s->channels;
|
|
|
|
*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 - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
// - - Spectrum analysis - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
|
|
static const char *spectrum_bars[] =
|
|
|
|
static const char *spectrum_bars[] =
|
|
|
|
@@ -781,6 +793,8 @@ spectrum_init (struct spectrum *s, char *format, int bars, struct error **e)
|
|
|
|
// having the amount of bins be strictly a factor of Nyquist / 20 (stemming
|
|
|
|
// 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,
|
|
|
|
// from the equation 20 = Nyquist / bins). Since log2(44100 / 2 / 20) > 10,
|
|
|
|
// it would be fairly expensive, and somewhat slowly updating. Always.
|
|
|
|
// 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;
|
|
|
|
double audible_ratio = s->sampling_rate / 2. / 20000;
|
|
|
|
s->bins = ceil (necessary_bins * MAX (audible_ratio, 1));
|
|
|
|
s->bins = ceil (necessary_bins * MAX (audible_ratio, 1));
|
|
|
|
s->useful_bins = s->bins / 2;
|
|
|
|
s->useful_bins = s->bins / 2;
|
|
|
|
@@ -799,9 +813,14 @@ spectrum_init (struct spectrum *s, char *format, int bars, struct error **e)
|
|
|
|
if (s->samples < 1)
|
|
|
|
if (s->samples < 1)
|
|
|
|
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 == 8) s->decode = spectrum_decode_8;
|
|
|
|
if (s->bits == 16) s->decode = spectrum_decode_16;
|
|
|
|
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_size = s->samples * s->useful_bins * s->bits / 8 * s->channels;
|
|
|
|
s->buffer = xcalloc (1, s->buffer_size);
|
|
|
|
s->buffer = xcalloc (1, s->buffer_size);
|
|
|
|
|
|
|
|
|
|
|
|
@@ -943,6 +962,7 @@ static struct app_context
|
|
|
|
|
|
|
|
|
|
|
|
struct config config; ///< Program configuration
|
|
|
|
struct config config; ///< Program configuration
|
|
|
|
struct strv streams; ///< List of "name NUL URI NUL"
|
|
|
|
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 *help_tab; ///< Special help tab
|
|
|
|
struct tab *tabs; ///< All other tabs
|
|
|
|
struct tab *tabs; ///< All other tabs
|
|
|
|
@@ -1205,6 +1225,7 @@ app_init_context (void)
|
|
|
|
g.client = mpd_client_make (&g.poller);
|
|
|
|
g.client = mpd_client_make (&g.poller);
|
|
|
|
g.config = config_make ();
|
|
|
|
g.config = config_make ();
|
|
|
|
g.streams = strv_make ();
|
|
|
|
g.streams = strv_make ();
|
|
|
|
|
|
|
|
g.enqueue = strv_make ();
|
|
|
|
g.playlist = item_list_make ();
|
|
|
|
g.playlist = item_list_make ();
|
|
|
|
|
|
|
|
|
|
|
|
g.playback_info = str_map_make (free);
|
|
|
|
g.playback_info = str_map_make (free);
|
|
|
|
@@ -1235,9 +1256,9 @@ app_init_terminal (void)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
TERMO_CHECK_VERSION;
|
|
|
|
TERMO_CHECK_VERSION;
|
|
|
|
if (!(g.tk = termo_new (STDIN_FILENO, NULL, 0)))
|
|
|
|
if (!(g.tk = termo_new (STDIN_FILENO, NULL, 0)))
|
|
|
|
abort ();
|
|
|
|
exit_fatal ("failed to set up the terminal");
|
|
|
|
if (!initscr () || nonl () == ERR)
|
|
|
|
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...
|
|
|
|
// By default we don't use any colors so they're not required...
|
|
|
|
if (start_color () == ERR
|
|
|
|
if (start_color () == ERR
|
|
|
|
@@ -1266,6 +1287,7 @@ app_free_context (void)
|
|
|
|
mpd_client_free (&g.client);
|
|
|
|
mpd_client_free (&g.client);
|
|
|
|
str_map_free (&g.playback_info);
|
|
|
|
str_map_free (&g.playback_info);
|
|
|
|
strv_free (&g.streams);
|
|
|
|
strv_free (&g.streams);
|
|
|
|
|
|
|
|
strv_free (&g.enqueue);
|
|
|
|
item_list_free (&g.playlist);
|
|
|
|
item_list_free (&g.playlist);
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_FFTW
|
|
|
|
#ifdef WITH_FFTW
|
|
|
|
@@ -3767,9 +3789,15 @@ spectrum_redraw (void)
|
|
|
|
// let's hack around it in this case
|
|
|
|
// let's hack around it in this case
|
|
|
|
if (g.spectrum_row != -1)
|
|
|
|
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));
|
|
|
|
attrset (APP_ATTR (TAB_BAR));
|
|
|
|
mvaddstr (g.spectrum_row, g.spectrum_column, g.spectrum.spectrum);
|
|
|
|
mvaddstr (g.spectrum_row, g.spectrum_column, g.spectrum.spectrum);
|
|
|
|
attrset (0);
|
|
|
|
attrset (0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
move (last_y, last_x);
|
|
|
|
refresh ();
|
|
|
|
refresh ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
else
|
|
|
|
@@ -4167,6 +4195,57 @@ mpd_queue_reconnect (void)
|
|
|
|
poller_timer_set (&g.connect_event, 5 * 1000);
|
|
|
|
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
|
|
|
|
static void
|
|
|
|
mpd_on_password_response (const struct mpd_response *response,
|
|
|
|
mpd_on_password_response (const struct mpd_response *response,
|
|
|
|
const struct strv *data, void *user_data)
|
|
|
|
const struct strv *data, void *user_data)
|
|
|
|
@@ -4176,10 +4255,7 @@ mpd_on_password_response (const struct mpd_response *response,
|
|
|
|
struct mpd_client *c = &g.client;
|
|
|
|
struct mpd_client *c = &g.client;
|
|
|
|
|
|
|
|
|
|
|
|
if (response->success)
|
|
|
|
if (response->success)
|
|
|
|
{
|
|
|
|
mpd_on_ready ();
|
|
|
|
mpd_request_info ();
|
|
|
|
|
|
|
|
library_tab_reload (NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
print_error ("%s: %s",
|
|
|
|
print_error ("%s: %s",
|
|
|
|
@@ -4202,12 +4278,7 @@ mpd_on_connected (void *user_data)
|
|
|
|
mpd_client_add_task (c, mpd_on_password_response, NULL);
|
|
|
|
mpd_client_add_task (c, mpd_on_password_response, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mpd_on_ready ();
|
|
|
|
mpd_request_info ();
|
|
|
|
|
|
|
|
library_tab_reload (NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
spectrum_setup_fifo ();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
static void
|
|
|
|
@@ -4506,6 +4577,28 @@ app_init_poller_events (void)
|
|
|
|
g.refresh_event.dispatcher = app_on_refresh;
|
|
|
|
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
|
|
|
|
int
|
|
|
|
main (int argc, char *argv[])
|
|
|
|
main (int argc, char *argv[])
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@@ -4518,7 +4611,8 @@ main (int argc, char *argv[])
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct opt_handler oh =
|
|
|
|
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;
|
|
|
|
int c;
|
|
|
|
while ((c = opt_handler_get (&oh)) != -1)
|
|
|
|
while ((c = opt_handler_get (&oh)) != -1)
|
|
|
|
@@ -4541,12 +4635,6 @@ main (int argc, char *argv[])
|
|
|
|
|
|
|
|
|
|
|
|
argc -= optind;
|
|
|
|
argc -= optind;
|
|
|
|
argv += optind;
|
|
|
|
argv += optind;
|
|
|
|
|
|
|
|
|
|
|
|
if (argc)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
opt_handler_usage (&oh, stderr);
|
|
|
|
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
opt_handler_free (&oh);
|
|
|
|
opt_handler_free (&oh);
|
|
|
|
|
|
|
|
|
|
|
|
// We only need to convert to and from the terminal encoding
|
|
|
|
// We only need to convert to and from the terminal encoding
|
|
|
|
@@ -4554,10 +4642,11 @@ main (int argc, char *argv[])
|
|
|
|
print_warning ("failed to set the locale");
|
|
|
|
print_warning ("failed to set the locale");
|
|
|
|
|
|
|
|
|
|
|
|
app_init_context ();
|
|
|
|
app_init_context ();
|
|
|
|
|
|
|
|
app_init_enqueue (argv, argc);
|
|
|
|
app_load_configuration ();
|
|
|
|
app_load_configuration ();
|
|
|
|
app_init_terminal ();
|
|
|
|
|
|
|
|
signals_setup_handlers ();
|
|
|
|
signals_setup_handlers ();
|
|
|
|
app_init_poller_events ();
|
|
|
|
app_init_poller_events ();
|
|
|
|
|
|
|
|
app_init_terminal ();
|
|
|
|
|
|
|
|
|
|
|
|
g_normal_keys = app_init_bindings ("normal",
|
|
|
|
g_normal_keys = app_init_bindings ("normal",
|
|
|
|
g_normal_defaults, N_ELEMENTS (g_normal_defaults), &g_normal_keys_len);
|
|
|
|
g_normal_defaults, N_ELEMENTS (g_normal_defaults), &g_normal_keys_len);
|
|
|
|
@@ -4577,6 +4666,11 @@ main (int argc, char *argv[])
|
|
|
|
app_prepend_tab (current_tab_init ());
|
|
|
|
app_prepend_tab (current_tab_init ());
|
|
|
|
app_switch_tab ((g.help_tab = help_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;
|
|
|
|
g.polling = true;
|
|
|
|
while (g.polling)
|
|
|
|
while (g.polling)
|
|
|
|
poller_run (&g.poller);
|
|
|
|
poller_run (&g.poller);
|
|
|
|
|