wmstatus: add brown noise generation capabilities
This commit is contained in:
parent
32a28fcaa3
commit
8e0e84825f
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2015 - 2018, Přemysl Eric Janouch <p@janouch.name>
|
Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted.
|
purpose with or without fee is hereby granted.
|
||||||
|
|
198
wmstatus.c
198
wmstatus.c
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* wmstatus.c: simple PulseAudio-enabled status setter for dwm and i3
|
* wmstatus.c: simple PulseAudio-enabled status setter for dwm and i3
|
||||||
*
|
*
|
||||||
* Copyright (c) 2015 - 2017, Přemysl Eric Janouch <p@janouch.name>
|
* Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
|
@ -31,6 +31,10 @@
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <spawn.h>
|
#include <spawn.h>
|
||||||
|
|
||||||
|
#ifdef BSD
|
||||||
|
#include <sys/endian.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <X11/keysym.h>
|
#include <X11/keysym.h>
|
||||||
#include <X11/XF86keysym.h>
|
#include <X11/XF86keysym.h>
|
||||||
|
@ -41,6 +45,8 @@
|
||||||
#include <pulse/error.h>
|
#include <pulse/error.h>
|
||||||
#include <pulse/introspect.h>
|
#include <pulse/introspect.h>
|
||||||
#include <pulse/subscribe.h>
|
#include <pulse/subscribe.h>
|
||||||
|
#include <pulse/stream.h>
|
||||||
|
#include <pulse/sample.h>
|
||||||
|
|
||||||
#include <dbus/dbus.h>
|
#include <dbus/dbus.h>
|
||||||
|
|
||||||
|
@ -861,12 +867,22 @@ struct app_context
|
||||||
|
|
||||||
bool failed; ///< General PulseAudio failure
|
bool failed; ///< General PulseAudio failure
|
||||||
|
|
||||||
|
pa_sample_spec sink_sample_spec; ///< Sink sample spec
|
||||||
pa_cvolume sink_volume; ///< Current volume
|
pa_cvolume sink_volume; ///< Current volume
|
||||||
bool sink_muted; ///< Currently muted?
|
bool sink_muted; ///< Currently muted?
|
||||||
struct strv sink_ports; ///< All sink port names
|
struct strv sink_ports; ///< All sink port names
|
||||||
char *sink_port_active; ///< Active sink port
|
char *sink_port_active; ///< Active sink port
|
||||||
|
|
||||||
bool source_muted; ///< Currently muted?
|
bool source_muted; ///< Currently muted?
|
||||||
|
|
||||||
|
// Noise playback:
|
||||||
|
|
||||||
|
struct poller_timer noise_timer; ///< Update noise timer display, or stop
|
||||||
|
pa_stream *noise_stream; ///< PulseAudio stream for noise playing
|
||||||
|
time_t noise_end_time; ///< End time of noise production, or 0
|
||||||
|
float noise_state[2]; ///< Brownian noise state
|
||||||
|
int noise_fadeout_iterator; ///< Fadeout iterator, in samples
|
||||||
|
int noise_fadeout_samples; ///< Sample count for fadeout
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -953,6 +969,7 @@ app_context_free (struct app_context *self)
|
||||||
poller_fd_reset (&self->x_event);
|
poller_fd_reset (&self->x_event);
|
||||||
cstr_set (&self->layout, NULL);
|
cstr_set (&self->layout, NULL);
|
||||||
|
|
||||||
|
if (self->noise_stream) pa_stream_unref (self->noise_stream);
|
||||||
if (self->context) pa_context_unref (self->context);
|
if (self->context) pa_context_unref (self->context);
|
||||||
if (self->dpy) XCloseDisplay (self->dpy);
|
if (self->dpy) XCloseDisplay (self->dpy);
|
||||||
|
|
||||||
|
@ -1173,6 +1190,14 @@ make_volume_status (struct app_context *ctx)
|
||||||
return str_steal (&s);
|
return str_steal (&s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
make_noise_status (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
int diff = difftime (ctx->noise_end_time, time (NULL));
|
||||||
|
return xstrdup_printf ("\x01" "Playing noise" "\x01 (%d:%02d)",
|
||||||
|
diff / 3600, diff / 60 % 60);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
refresh_status (struct app_context *ctx)
|
refresh_status (struct app_context *ctx)
|
||||||
{
|
{
|
||||||
|
@ -1181,6 +1206,13 @@ refresh_status (struct app_context *ctx)
|
||||||
if (ctx->mpd_status) ctx->backend->add (ctx->backend, ctx->mpd_status);
|
if (ctx->mpd_status) ctx->backend->add (ctx->backend, ctx->mpd_status);
|
||||||
else if (ctx->mpd_song) ctx->backend->add (ctx->backend, ctx->mpd_song);
|
else if (ctx->mpd_song) ctx->backend->add (ctx->backend, ctx->mpd_song);
|
||||||
|
|
||||||
|
if (ctx->noise_end_time)
|
||||||
|
{
|
||||||
|
char *noise = make_noise_status (ctx);
|
||||||
|
ctx->backend->add (ctx->backend, noise);
|
||||||
|
free (noise);
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx->failed) ctx->backend->add (ctx->backend, "PA failure");
|
if (ctx->failed) ctx->backend->add (ctx->backend, "PA failure");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1851,6 +1883,160 @@ on_nut_reconnect (void *user_data)
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static inline float
|
||||||
|
noise_next_brownian (float last)
|
||||||
|
{
|
||||||
|
// Leaky integrators have a side effect on the signal, making noise white
|
||||||
|
// on the lower end of the spectrum, which can be heard as reduced rumbling
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// 0.9375 is the guaranteed to be safe value, not very pleasant
|
||||||
|
float f = last * 0.99 + ((double) rand () / RAND_MAX - 0.5) / 8;
|
||||||
|
if (f >= -1 && f <= 1)
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
noise_generate_stereo (struct app_context *ctx, int16_t *data, size_t n)
|
||||||
|
{
|
||||||
|
float brown_l = ctx->noise_state[0];
|
||||||
|
float brown_r = ctx->noise_state[1];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n / 2; i++)
|
||||||
|
{
|
||||||
|
// We do not want to use a linear transition, and a decreasing geometric
|
||||||
|
// sequence would have a limit in infinity, so use powers of normalized
|
||||||
|
// time deltas--in particular 2 up to 6 are said to work
|
||||||
|
float gain = 1;
|
||||||
|
if (ctx->noise_fadeout_samples)
|
||||||
|
{
|
||||||
|
float remaining = (float) (ctx->noise_fadeout_samples
|
||||||
|
- ctx->noise_fadeout_iterator++) / ctx->noise_fadeout_samples;
|
||||||
|
if (remaining <= 0)
|
||||||
|
gain = 0;
|
||||||
|
else
|
||||||
|
gain = remaining * remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[i * 2 + 0] =
|
||||||
|
(brown_l = noise_next_brownian (brown_l)) * gain * INT16_MAX;
|
||||||
|
data[i * 2 + 1] =
|
||||||
|
(brown_r = noise_next_brownian (brown_r)) * gain * INT16_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->noise_state[0] = brown_l;
|
||||||
|
ctx->noise_state[1] = brown_r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
noise_abort (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
ctx->noise_end_time = 0;
|
||||||
|
poller_timer_reset (&ctx->noise_timer);
|
||||||
|
|
||||||
|
if (ctx->noise_stream)
|
||||||
|
{
|
||||||
|
(void) pa_stream_disconnect (ctx->noise_stream);
|
||||||
|
pa_stream_unref (ctx->noise_stream);
|
||||||
|
ctx->noise_stream = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_noise_writeable (pa_stream *stream, size_t nbytes, void *userdata)
|
||||||
|
{
|
||||||
|
struct app_context *ctx = userdata;
|
||||||
|
int16_t data[nbytes / 2];
|
||||||
|
noise_generate_stereo (ctx, data, N_ELEMENTS (data));
|
||||||
|
|
||||||
|
int err;
|
||||||
|
if ((err = pa_stream_write (stream,
|
||||||
|
data, sizeof data, NULL, 0, PA_SEEK_RELATIVE)))
|
||||||
|
{
|
||||||
|
print_error ("noise playback failed: %s", pa_strerror (err));
|
||||||
|
noise_abort (ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const pa_sample_spec noise_default_spec =
|
||||||
|
{
|
||||||
|
.channels = 2,
|
||||||
|
.format = BYTE_ORDER == LITTLE_ENDIAN ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE,
|
||||||
|
.rate = 48000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool
|
||||||
|
noise_start (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx->context)
|
||||||
|
{
|
||||||
|
print_error ("not playing noise, not connected to PulseAudio");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid unnecessary, and fairly CPU-intensive resampling
|
||||||
|
pa_sample_spec spec = noise_default_spec;
|
||||||
|
if (ctx->sink_sample_spec.rate == 44100)
|
||||||
|
spec.rate = ctx->sink_sample_spec.rate;
|
||||||
|
|
||||||
|
ctx->noise_stream =
|
||||||
|
pa_stream_new (ctx->context, PROGRAM_NAME "/noise", &spec, NULL);
|
||||||
|
pa_stream_set_write_callback (ctx->noise_stream, on_noise_writeable, ctx);
|
||||||
|
|
||||||
|
int err;
|
||||||
|
if ((err = pa_stream_connect_playback (ctx->noise_stream,
|
||||||
|
NULL, NULL, 0, NULL, NULL)))
|
||||||
|
{
|
||||||
|
print_error ("failed to connect noise playback stream: %s",
|
||||||
|
pa_strerror (err));
|
||||||
|
noise_abort (ctx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
time (&ctx->noise_end_time);
|
||||||
|
ctx->noise_state[0] = ctx->noise_state[1] = 0;
|
||||||
|
ctx->noise_fadeout_samples = 0;
|
||||||
|
ctx->noise_fadeout_iterator = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_noise_timer (void *user_data)
|
||||||
|
{
|
||||||
|
struct app_context *ctx = user_data;
|
||||||
|
int diff = difftime (ctx->noise_end_time, time (NULL));
|
||||||
|
if (diff <= 0)
|
||||||
|
noise_abort (ctx);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
poller_timer_set (&ctx->noise_timer, (diff % 60 + 1) * 1000);
|
||||||
|
|
||||||
|
// XXX: this is inaccurate, since we don't take into account buffering,
|
||||||
|
// however it shouldn't pose a major issue
|
||||||
|
if (diff <= 60 && !ctx->noise_fadeout_samples)
|
||||||
|
ctx->noise_fadeout_samples =
|
||||||
|
diff * pa_stream_get_sample_spec (ctx->noise_stream)->rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_status (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_noise_adjust (struct app_context *ctx, int arg)
|
||||||
|
{
|
||||||
|
ctx->noise_fadeout_samples = 0;
|
||||||
|
ctx->noise_fadeout_iterator = 0;
|
||||||
|
if (!ctx->noise_end_time && (arg < 0 || !noise_start (ctx)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// The granularity of noise playback is whole minutes
|
||||||
|
ctx->noise_end_time += arg * 60;
|
||||||
|
on_noise_timer (ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
#define DEFAULT_SOURCE "@DEFAULT_SOURCE@"
|
#define DEFAULT_SOURCE "@DEFAULT_SOURCE@"
|
||||||
#define DEFAULT_SINK "@DEFAULT_SINK@"
|
#define DEFAULT_SINK "@DEFAULT_SINK@"
|
||||||
|
|
||||||
|
@ -1863,6 +2049,7 @@ on_sink_info (pa_context *context, const pa_sink_info *info, int eol,
|
||||||
if (info && !eol)
|
if (info && !eol)
|
||||||
{
|
{
|
||||||
struct app_context *ctx = userdata;
|
struct app_context *ctx = userdata;
|
||||||
|
ctx->sink_sample_spec = info->sample_spec;
|
||||||
ctx->sink_volume = info->volume;
|
ctx->sink_volume = info->volume;
|
||||||
ctx->sink_muted = !!info->mute;
|
ctx->sink_muted = !!info->mute;
|
||||||
|
|
||||||
|
@ -1935,6 +2122,9 @@ on_context_state_change (pa_context *context, void *userdata)
|
||||||
{
|
{
|
||||||
case PA_CONTEXT_FAILED:
|
case PA_CONTEXT_FAILED:
|
||||||
case PA_CONTEXT_TERMINATED:
|
case PA_CONTEXT_TERMINATED:
|
||||||
|
// The stream depends on the context, and would keep its object alive
|
||||||
|
noise_abort (ctx);
|
||||||
|
|
||||||
ctx->failed = true;
|
ctx->failed = true;
|
||||||
refresh_status (ctx);
|
refresh_status (ctx);
|
||||||
|
|
||||||
|
@ -2246,6 +2436,10 @@ g_keys[] =
|
||||||
{ ShiftMask, XF86XK_AudioLowerVolume, on_volume_set, -1 },
|
{ ShiftMask, XF86XK_AudioLowerVolume, on_volume_set, -1 },
|
||||||
{ 0, XF86XK_AudioMute, on_volume_mute, 0 },
|
{ 0, XF86XK_AudioMute, on_volume_mute, 0 },
|
||||||
{ 0, XF86XK_AudioMicMute, on_volume_mic_mute, 0 },
|
{ 0, XF86XK_AudioMicMute, on_volume_mic_mute, 0 },
|
||||||
|
|
||||||
|
// Noise playback
|
||||||
|
{ ControlMask, XF86XK_AudioRaiseVolume, on_noise_adjust, 60 },
|
||||||
|
{ ControlMask, XF86XK_AudioLowerVolume, on_noise_adjust, -60 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -2505,6 +2699,8 @@ main (int argc, char *argv[])
|
||||||
on_mpd_reconnect, &ctx);
|
on_mpd_reconnect, &ctx);
|
||||||
poller_timer_init_and_set (&ctx.nut_reconnect, &ctx.poller,
|
poller_timer_init_and_set (&ctx.nut_reconnect, &ctx.poller,
|
||||||
on_nut_reconnect, &ctx);
|
on_nut_reconnect, &ctx);
|
||||||
|
poller_timer_init_and_set (&ctx.noise_timer, &ctx.poller,
|
||||||
|
on_noise_timer, &ctx);
|
||||||
|
|
||||||
init_xlib_events (&ctx);
|
init_xlib_events (&ctx);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue