Refactoring
This commit is contained in:
@@ -1,520 +0,0 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* <2006,2011> Stefan Kost <ensonic@users.sf.net>
|
||||
* <2007-2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||
* <2018-2024> Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/gstaudiofilter.h>
|
||||
|
||||
#include <fftw3.h>
|
||||
|
||||
#include "gstfastspectrum.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC(gst_strawberry_fastspectrum_debug);
|
||||
|
||||
namespace {
|
||||
|
||||
// Spectrum properties
|
||||
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
|
||||
constexpr auto DEFAULT_BANDS = 128;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_INTERVAL,
|
||||
PROP_BANDS
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
G_DEFINE_TYPE(GstStrawberryFastSpectrum, gst_strawberry_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
static void gst_strawberry_fastspectrum_finalize(GObject *object);
|
||||
static void gst_strawberry_fastspectrum_set_property(GObject *object, const guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||
static void gst_strawberry_fastspectrum_get_property(GObject *object, const guint prop_id, GValue *value, GParamSpec *pspec);
|
||||
static gboolean gst_strawberry_fastspectrum_start(GstBaseTransform *transform);
|
||||
static gboolean gst_strawberry_fastspectrum_stop(GstBaseTransform *transform);
|
||||
static GstFlowReturn gst_strawberry_fastspectrum_transform_ip(GstBaseTransform *transform, GstBuffer *buffer);
|
||||
static gboolean gst_strawberry_fastspectrum_setup(GstAudioFilter *audio_filter, const GstAudioInfo *audio_info);
|
||||
|
||||
static void gst_strawberry_fastspectrum_class_init(GstStrawberryFastSpectrumClass *klass) {
|
||||
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
||||
GstBaseTransformClass *transform_class = GST_BASE_TRANSFORM_CLASS(klass);
|
||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
|
||||
|
||||
gobject_class->set_property = gst_strawberry_fastspectrum_set_property;
|
||||
gobject_class->get_property = gst_strawberry_fastspectrum_get_property;
|
||||
gobject_class->finalize = gst_strawberry_fastspectrum_finalize;
|
||||
|
||||
transform_class->start = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_start);
|
||||
transform_class->stop = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_stop);
|
||||
transform_class->transform_ip = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_transform_ip);
|
||||
transform_class->passthrough_on_same_caps = TRUE;
|
||||
|
||||
filter_class->setup = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_setup);
|
||||
|
||||
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT(gst_strawberry_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||
|
||||
gst_element_class_set_static_metadata(element_class,
|
||||
"Fast spectrum analyzer using FFTW",
|
||||
"Filter/Analyzer/Audio",
|
||||
"Run an FFT on the audio signal, output spectrum data",
|
||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||
"Stefan Kost <ensonic@users.sf.net>, "
|
||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>, "
|
||||
"Jonas Kvinge <jonas@jkvinge.net>");
|
||||
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
GstCaps *caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
|
||||
#else
|
||||
GstCaps *caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16BE, S24BE, S32BE, F32BE, F64BE }") ", layout = (string) interleaved, channels = 1");
|
||||
#endif
|
||||
|
||||
gst_audio_filter_class_add_pad_templates(filter_class, caps);
|
||||
gst_caps_unref(caps);
|
||||
|
||||
g_mutex_init(&klass->fftw_lock);
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_init(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
fastspectrum->interval = DEFAULT_INTERVAL;
|
||||
fastspectrum->bands = DEFAULT_BANDS;
|
||||
|
||||
fastspectrum->channel_data_initialized = false;
|
||||
|
||||
g_mutex_init(&fastspectrum->lock);
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_alloc_channel_data(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
const guint bands = fastspectrum->bands;
|
||||
const guint nfft = 2 * bands - 2;
|
||||
|
||||
fastspectrum->input_ring_buffer = new double[nfft];
|
||||
fastspectrum->fft_input = reinterpret_cast<double*>(fftw_malloc(sizeof(double) * nfft));
|
||||
fastspectrum->fft_output = reinterpret_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)));
|
||||
|
||||
fastspectrum->spect_magnitude = new double[bands] {};
|
||||
|
||||
GstStrawberryFastSpectrumClass *klass = reinterpret_cast<GstStrawberryFastSpectrumClass*>(G_OBJECT_GET_CLASS(fastspectrum));
|
||||
{
|
||||
g_mutex_lock(&klass->fftw_lock);
|
||||
fastspectrum->plan = fftw_plan_dft_r2c_1d(static_cast<int>(nfft), fastspectrum->fft_input, fastspectrum->fft_output, FFTW_ESTIMATE);
|
||||
g_mutex_unlock(&klass->fftw_lock);
|
||||
}
|
||||
fastspectrum->channel_data_initialized = true;
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_free_channel_data(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
GstStrawberryFastSpectrumClass *klass = reinterpret_cast<GstStrawberryFastSpectrumClass*>(G_OBJECT_GET_CLASS(fastspectrum));
|
||||
|
||||
if (fastspectrum->channel_data_initialized) {
|
||||
{
|
||||
g_mutex_lock(&klass->fftw_lock);
|
||||
fftw_destroy_plan(fastspectrum->plan);
|
||||
g_mutex_unlock(&klass->fftw_lock);
|
||||
}
|
||||
fftw_free(fastspectrum->fft_input);
|
||||
fftw_free(fastspectrum->fft_output);
|
||||
delete[] fastspectrum->input_ring_buffer;
|
||||
delete[] fastspectrum->spect_magnitude;
|
||||
|
||||
fastspectrum->channel_data_initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_flush(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
fastspectrum->num_frames = 0;
|
||||
fastspectrum->num_fft = 0;
|
||||
fastspectrum->accumulated_error = 0;
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_reset_state(GstStrawberryFastSpectrum *fastspectrum) {
|
||||
|
||||
GST_DEBUG_OBJECT(fastspectrum, "resetting state");
|
||||
|
||||
gst_strawberry_fastspectrum_free_channel_data(fastspectrum);
|
||||
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_finalize(GObject *object) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||
|
||||
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||
g_mutex_clear(&fastspectrum->lock);
|
||||
|
||||
G_OBJECT_CLASS(gst_strawberry_fastspectrum_parent_class)->finalize(object);
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_set_property(GObject *object, const guint prop_id, const GValue *value, GParamSpec *pspec) {
|
||||
|
||||
GstStrawberryFastSpectrum *filter = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL: {
|
||||
const guint64 interval = g_value_get_uint64(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (filter->interval != interval) {
|
||||
filter->interval = interval;
|
||||
gst_strawberry_fastspectrum_reset_state(filter);
|
||||
}
|
||||
g_mutex_unlock(&filter->lock);
|
||||
break;
|
||||
}
|
||||
case PROP_BANDS: {
|
||||
const guint bands = g_value_get_uint(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (filter->bands != bands) {
|
||||
filter->bands = bands;
|
||||
gst_strawberry_fastspectrum_reset_state(filter);
|
||||
}
|
||||
g_mutex_unlock(&filter->lock);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_get_property(GObject *object, const guint prop_id, GValue *value, GParamSpec *pspec) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL:
|
||||
g_value_set_uint64(value, fastspectrum->interval);
|
||||
break;
|
||||
case PROP_BANDS:
|
||||
g_value_set_uint(value, fastspectrum->bands);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_strawberry_fastspectrum_start(GstBaseTransform *transform) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||
|
||||
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_strawberry_fastspectrum_stop(GstBaseTransform *transform) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||
|
||||
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
// Mixing data readers
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_float(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
(void) max_value;
|
||||
|
||||
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
out[op] = in[ip++];
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_double(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
(void) max_value;
|
||||
|
||||
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
out[op] = in[ip++];
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_int32_max(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
const gint32 *in = reinterpret_cast<const gint32*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_int24_max(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
guint32 value = GST_READ_UINT24_BE(_in);
|
||||
#else
|
||||
guint32 value = GST_READ_UINT24_LE(_in);
|
||||
#endif
|
||||
if (value & 0x00800000) {
|
||||
value |= 0xff000000;
|
||||
}
|
||||
|
||||
out[op] = value / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
_in += 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_input_data_mixed_int16_max(const guint8 *_in, double *out, const guint64 len, const double max_value, guint op, const guint nfft) {
|
||||
|
||||
const gint16 *in = reinterpret_cast<const gint16*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (guint64 j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_strawberry_fastspectrum_setup(GstAudioFilter *audio_filter, const GstAudioInfo *audio_info) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(audio_filter);
|
||||
GstStrawberryFastSpectrumInputData input_data = nullptr;
|
||||
|
||||
g_mutex_lock(&fastspectrum->lock);
|
||||
switch (GST_AUDIO_INFO_FORMAT(audio_info)) {
|
||||
case GST_AUDIO_FORMAT_S16:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_int16_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_S24:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_int24_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_S32:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_int32_max;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_F32:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_float;
|
||||
break;
|
||||
case GST_AUDIO_FORMAT_F64:
|
||||
input_data = gst_strawberry_fastspectrum_input_data_mixed_double;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
fastspectrum->input_data = input_data;
|
||||
|
||||
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||
g_mutex_unlock(&fastspectrum->lock);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static void gst_strawberry_fastspectrum_run_fft(GstStrawberryFastSpectrum *fastspectrum, const guint input_pos) {
|
||||
|
||||
const guint bands = fastspectrum->bands;
|
||||
const guint nfft = 2 * bands - 2;
|
||||
|
||||
for (guint i = 0; i < nfft; i++) {
|
||||
fastspectrum->fft_input[i] = fastspectrum->input_ring_buffer[(input_pos + i) % nfft];
|
||||
}
|
||||
|
||||
// Should be safe to execute the same plan multiple times in parallel.
|
||||
fftw_execute(fastspectrum->plan);
|
||||
|
||||
// Calculate magnitude in db
|
||||
for (guint i = 0; i < bands; i++) {
|
||||
gdouble value = fastspectrum->fft_output[i][0] * fastspectrum->fft_output[i][0];
|
||||
value += fastspectrum->fft_output[i][1] * fastspectrum->fft_output[i][1];
|
||||
value /= nfft * nfft;
|
||||
fastspectrum->spect_magnitude[i] += value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static GstFlowReturn gst_strawberry_fastspectrum_transform_ip(GstBaseTransform *transform, GstBuffer *buffer) {
|
||||
|
||||
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||
|
||||
const guint rate = GST_AUDIO_FILTER_RATE(fastspectrum);
|
||||
const guint bps = GST_AUDIO_FILTER_BPS(fastspectrum);
|
||||
const guint64 bpf = GST_AUDIO_FILTER_BPF(fastspectrum);
|
||||
const double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
|
||||
const guint bands = fastspectrum->bands;
|
||||
const guint nfft = 2 * bands - 2;
|
||||
|
||||
g_mutex_lock(&fastspectrum->lock);
|
||||
|
||||
GstMapInfo map;
|
||||
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||
const guint8 *data = map.data;
|
||||
gsize size = map.size;
|
||||
|
||||
GST_LOG_OBJECT(fastspectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
||||
|
||||
if (GST_BUFFER_IS_DISCONT(buffer)) {
|
||||
GST_DEBUG_OBJECT(fastspectrum, "Discontinuity detected -- flushing");
|
||||
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||
}
|
||||
|
||||
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
|
||||
if (!fastspectrum->channel_data_initialized) {
|
||||
GST_DEBUG_OBJECT(fastspectrum, "allocating for bands %u", bands);
|
||||
|
||||
gst_strawberry_fastspectrum_alloc_channel_data(fastspectrum);
|
||||
|
||||
// Number of sample frames we process before posting a message interval is in ns
|
||||
fastspectrum->frames_per_interval = gst_util_uint64_scale(fastspectrum->interval, rate, GST_SECOND);
|
||||
fastspectrum->frames_todo = fastspectrum->frames_per_interval;
|
||||
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
|
||||
fastspectrum->error_per_interval = (fastspectrum->interval * rate) % GST_SECOND;
|
||||
if (fastspectrum->frames_per_interval == 0) {
|
||||
fastspectrum->frames_per_interval = 1;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT(fastspectrum, "interval %" GST_TIME_FORMAT ", fpi %" G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT, GST_TIME_ARGS(fastspectrum->interval), fastspectrum->frames_per_interval, GST_TIME_ARGS(fastspectrum->error_per_interval));
|
||||
|
||||
fastspectrum->input_pos = 0;
|
||||
|
||||
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||
}
|
||||
|
||||
if (fastspectrum->num_frames == 0) {
|
||||
fastspectrum->message_ts = GST_BUFFER_TIMESTAMP(buffer);
|
||||
}
|
||||
|
||||
guint input_pos = fastspectrum->input_pos;
|
||||
GstStrawberryFastSpectrumInputData input_data = fastspectrum->input_data;
|
||||
|
||||
while (size >= bpf) {
|
||||
// Run input_data for a chunk of data
|
||||
guint64 fft_todo = nfft - (fastspectrum->num_frames % nfft);
|
||||
guint64 msg_todo = fastspectrum->frames_todo - fastspectrum->num_frames;
|
||||
GST_LOG_OBJECT(fastspectrum, "message frames todo: %" G_GUINT64_FORMAT ", fft frames todo: %" G_GUINT64_FORMAT ", input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, static_cast<gsize>(size / bpf));
|
||||
guint64 block_size = msg_todo;
|
||||
if (block_size > (size / bpf)) {
|
||||
block_size = (size / bpf);
|
||||
}
|
||||
if (block_size > fft_todo) {
|
||||
block_size = fft_todo;
|
||||
}
|
||||
|
||||
// Move the current frames into our ringbuffers
|
||||
input_data(data, fastspectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
|
||||
|
||||
data += block_size * bpf;
|
||||
size -= block_size * bpf;
|
||||
input_pos = (input_pos + block_size) % nfft;
|
||||
fastspectrum->num_frames += block_size;
|
||||
|
||||
gboolean have_full_interval = (fastspectrum->num_frames == fastspectrum->frames_todo);
|
||||
|
||||
GST_LOG_OBJECT(fastspectrum, "size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size, (fastspectrum->num_frames % nfft == 0), have_full_interval);
|
||||
|
||||
// If we have enough frames for an FFT or we have all frames required for the interval and we haven't run a FFT, then run an FFT
|
||||
if ((fastspectrum->num_frames % nfft == 0) || (have_full_interval && !fastspectrum->num_fft)) {
|
||||
gst_strawberry_fastspectrum_run_fft(fastspectrum, input_pos);
|
||||
fastspectrum->num_fft++;
|
||||
}
|
||||
|
||||
// Do we have the FFTs for one interval?
|
||||
if (have_full_interval) {
|
||||
GST_DEBUG_OBJECT(fastspectrum, "nfft: %u frames: %" G_GUINT64_FORMAT " fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft, fastspectrum->num_frames, fastspectrum->frames_per_interval, GST_TIME_ARGS(fastspectrum->accumulated_error));
|
||||
|
||||
fastspectrum->frames_todo = fastspectrum->frames_per_interval;
|
||||
if (fastspectrum->accumulated_error >= GST_SECOND) {
|
||||
fastspectrum->accumulated_error -= GST_SECOND;
|
||||
fastspectrum->frames_todo++;
|
||||
}
|
||||
fastspectrum->accumulated_error += fastspectrum->error_per_interval;
|
||||
|
||||
if (fastspectrum->output_callback) {
|
||||
// Calculate average
|
||||
for (guint i = 0; i < fastspectrum->bands; i++) {
|
||||
fastspectrum->spect_magnitude[i] /= static_cast<double>(fastspectrum->num_fft);
|
||||
}
|
||||
|
||||
fastspectrum->output_callback(fastspectrum->spect_magnitude, static_cast<int>(fastspectrum->bands));
|
||||
|
||||
// Reset spectrum accumulators
|
||||
memset(fastspectrum->spect_magnitude, 0, fastspectrum->bands * sizeof(double));
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID(fastspectrum->message_ts)) {
|
||||
fastspectrum->message_ts += gst_util_uint64_scale(fastspectrum->num_frames, GST_SECOND, rate);
|
||||
}
|
||||
|
||||
fastspectrum->num_frames = 0;
|
||||
fastspectrum->num_fft = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fastspectrum->input_pos = input_pos;
|
||||
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
g_mutex_unlock(&fastspectrum->lock);
|
||||
|
||||
g_assert(size == 0);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||
* Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||
* Copyright (C) <2018-2024> Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// Adapted from gstspectrum for Clementine with the following changes:
|
||||
// - Uses fftw instead of kiss fft (2x faster).
|
||||
// - Hardcoded to 1 channel (use an audioconvert element to do the work
|
||||
// instead, simplifies this code a lot).
|
||||
// - Send output via a callback instead of GST messages (less overhead).
|
||||
// - Removed all properties except interval and band.
|
||||
|
||||
#ifndef GST_STRAWBERRY_FASTSPECTRUM_H
|
||||
#define GST_STRAWBERRY_FASTSPECTRUM_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/gstaudiofilter.h>
|
||||
#include <fftw3.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_STRAWBERRY_FASTSPECTRUM (gst_strawberry_fastspectrum_get_type())
|
||||
#define GST_STRAWBERRY_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstStrawberryFastSpectrum))
|
||||
#define GST_IS_STRAWBERRY_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
|
||||
#define GST_STRAWBERRY_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstStrawberryFastSpectrumClass))
|
||||
#define GST_IS_STRAWBERRY_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
|
||||
|
||||
typedef void (*GstStrawberryFastSpectrumInputData)(const guint8 *in, double *out, guint64 len, double max_value, guint op, guint nfft);
|
||||
|
||||
using GstStrawberryFastSpectrumOutputCallback = std::function<void(double *magnitudes, int size)>;
|
||||
|
||||
struct GstStrawberryFastSpectrum {
|
||||
GstAudioFilter parent;
|
||||
|
||||
// Properties
|
||||
guint64 interval; // How many nanoseconds between emits
|
||||
guint64 frames_per_interval; // How many frames per interval
|
||||
guint64 frames_todo;
|
||||
guint bands; // Number of spectrum bands
|
||||
gboolean multi_channel; // Send separate channel results
|
||||
|
||||
guint64 num_frames; // Frame count (1 sample per channel) since last emit
|
||||
guint64 num_fft; // Number of FFTs since last emit
|
||||
GstClockTime message_ts; // Starttime for next message
|
||||
|
||||
// <private>
|
||||
bool channel_data_initialized;
|
||||
double *input_ring_buffer;
|
||||
double *fft_input;
|
||||
fftw_complex *fft_output;
|
||||
double *spect_magnitude;
|
||||
fftw_plan plan;
|
||||
|
||||
guint input_pos;
|
||||
guint64 error_per_interval;
|
||||
guint64 accumulated_error;
|
||||
|
||||
GMutex lock;
|
||||
|
||||
GstStrawberryFastSpectrumInputData input_data;
|
||||
GstStrawberryFastSpectrumOutputCallback output_callback;
|
||||
};
|
||||
|
||||
struct GstStrawberryFastSpectrumClass {
|
||||
GstAudioFilterClass parent_class;
|
||||
GMutex fftw_lock;
|
||||
};
|
||||
|
||||
GType gst_strawberry_fastspectrum_get_type(void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // GST_STRAWBERRY_FASTSPECTRUM_H
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstfastspectrum.h"
|
||||
#include "gstfastspectrumplugin.h"
|
||||
|
||||
static gboolean gst_strawberry_fastspectrum_plugin_init(GstPlugin *plugin) {
|
||||
|
||||
GstRegistry *reg = gst_registry_get();
|
||||
if (reg) {
|
||||
GstPluginFeature *fastspectrum = gst_registry_lookup_feature(reg, "strawberry-fastspectrum");
|
||||
if (fastspectrum) {
|
||||
gst_object_unref(fastspectrum);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return gst_element_register(plugin, "strawberry-fastspectrum", GST_RANK_NONE, GST_TYPE_STRAWBERRY_FASTSPECTRUM);
|
||||
|
||||
}
|
||||
|
||||
int gst_strawberry_fastspectrum_register_static() {
|
||||
|
||||
return gst_plugin_register_static(
|
||||
GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
"strawberry-fastspectrum",
|
||||
"Fast spectrum analyzer for generating Moodbars",
|
||||
gst_strawberry_fastspectrum_plugin_init,
|
||||
"0.1",
|
||||
"GPL",
|
||||
"FastSpectrum",
|
||||
"gst-strawberry-fastspectrum",
|
||||
"https://www.strawberrymusicplayer.org");
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GST_STRAWBERRY_FASTSPECTRUM_PLUGIN_H
|
||||
#define GST_STRAWBERRY_FASTSPECTRUM_PLUGIN_H
|
||||
|
||||
extern "C" {
|
||||
int gst_strawberry_fastspectrum_register_static();
|
||||
}
|
||||
|
||||
#endif // GST_STRAWBERRY_FASTSPECTRUM_PLUGIN_H
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
#include "moodbarbuilder.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/arraysize.h"
|
||||
#include "includes/arraysize.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -23,26 +23,22 @@
|
||||
#include <QByteArray>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/player.h"
|
||||
#include "core/song.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/player.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "settings/moodbarsettingspage.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "constants/moodbarsettings.h"
|
||||
|
||||
#include "moodbarcontroller.h"
|
||||
#include "moodbarloader.h"
|
||||
#include "moodbarpipeline.h"
|
||||
|
||||
MoodbarController::MoodbarController(Application *app, QObject *parent)
|
||||
MoodbarController::MoodbarController(const SharedPtr<Player> player, const SharedPtr<MoodbarLoader> moodbar_loader, QObject *parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
player_(player),
|
||||
moodbar_loader_(moodbar_loader),
|
||||
enabled_(false) {
|
||||
|
||||
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &MoodbarController::CurrentSongChanged);
|
||||
QObject::connect(&*app_->player(), &Player::Stopped, this, &MoodbarController::PlaybackStopped);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
@@ -50,8 +46,8 @@ MoodbarController::MoodbarController(Application *app, QObject *parent)
|
||||
void MoodbarController::ReloadSettings() {
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
|
||||
enabled_ = s.value("enabled", false).toBool();
|
||||
s.beginGroup(MoodbarSettings::kSettingsGroup);
|
||||
enabled_ = s.value(MoodbarSettings::kEnabled, false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
@@ -62,7 +58,7 @@ void MoodbarController::CurrentSongChanged(const Song &song) {
|
||||
|
||||
QByteArray data;
|
||||
MoodbarPipeline *pipeline = nullptr;
|
||||
const MoodbarLoader::Result result = app_->moodbar_loader()->Load(song.url(), song.has_cue(), &data, &pipeline);
|
||||
const MoodbarLoader::Result result = moodbar_loader_->Load(song.url(), song.has_cue(), &data, &pipeline);
|
||||
|
||||
switch (result) {
|
||||
case MoodbarLoader::Result::CannotLoad:
|
||||
@@ -95,12 +91,12 @@ void MoodbarController::PlaybackStopped() {
|
||||
void MoodbarController::AsyncLoadComplete(MoodbarPipeline *pipeline, const QUrl &url) {
|
||||
|
||||
// Is this song still playing?
|
||||
PlaylistItemPtr current_item = app_->player()->GetCurrentItem();
|
||||
PlaylistItemPtr current_item = player_->GetCurrentItem();
|
||||
if (current_item && current_item->Url() != url) {
|
||||
return;
|
||||
}
|
||||
// Did we stop the song?
|
||||
switch (app_->player()->GetState()) {
|
||||
switch (player_->GetState()) {
|
||||
case EngineBase::State::Error:
|
||||
case EngineBase::State::Empty:
|
||||
case EngineBase::State::Idle:
|
||||
|
||||
@@ -27,28 +27,35 @@
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
class Application;
|
||||
#include "includes/shared_ptr.h"
|
||||
|
||||
class MoodbarLoader;
|
||||
class MoodbarPipeline;
|
||||
class Song;
|
||||
class Player;
|
||||
|
||||
class MoodbarController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MoodbarController(Application *app, QObject *parent = nullptr);
|
||||
explicit MoodbarController(const SharedPtr<Player> player, const SharedPtr<MoodbarLoader> moodbar_loader, QObject *parent = nullptr);
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
Q_SIGNALS:
|
||||
void CurrentMoodbarDataChanged(const QByteArray &data);
|
||||
void StyleChanged();
|
||||
|
||||
private Q_SLOTS:
|
||||
public Q_SLOTS:
|
||||
void CurrentSongChanged(const Song &song);
|
||||
void PlaybackStopped();
|
||||
|
||||
private Q_SLOTS:
|
||||
void AsyncLoadComplete(MoodbarPipeline *pipeline, const QUrl &url);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
const SharedPtr<Player> player_;
|
||||
const SharedPtr<MoodbarLoader> moodbar_loader_;
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/settings.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playlist/playlistview.h"
|
||||
@@ -47,18 +46,20 @@
|
||||
#include "moodbarpipeline.h"
|
||||
#include "moodbarrenderer.h"
|
||||
|
||||
#include "settings/moodbarsettingspage.h"
|
||||
#include "constants/moodbarsettings.h"
|
||||
|
||||
MoodbarItemDelegate::Data::Data() : state_(State::None) {}
|
||||
|
||||
MoodbarItemDelegate::MoodbarItemDelegate(Application *app, PlaylistView *view, QObject *parent)
|
||||
MoodbarItemDelegate::MoodbarItemDelegate(const SharedPtr<MoodbarLoader> moodbar_loader, PlaylistView *playlist_view, QObject *parent)
|
||||
: QItemDelegate(parent),
|
||||
app_(app),
|
||||
view_(view),
|
||||
moodbar_loader_(moodbar_loader),
|
||||
playlist_view_(playlist_view),
|
||||
enabled_(false),
|
||||
style_(MoodbarRenderer::MoodbarStyle::Normal) {
|
||||
style_(MoodbarSettings::Style::Normal) {
|
||||
|
||||
QObject::connect(&*moodbar_loader, &MoodbarLoader::SettingsReloaded, this, &MoodbarItemDelegate::ReloadSettings);
|
||||
QObject::connect(&*moodbar_loader, &MoodbarLoader::StyleChanged, this, &MoodbarItemDelegate::ReloadSettings);
|
||||
|
||||
QObject::connect(app_, &Application::SettingsChanged, this, &MoodbarItemDelegate::ReloadSettings);
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
@@ -66,9 +67,9 @@ MoodbarItemDelegate::MoodbarItemDelegate(Application *app, PlaylistView *view, Q
|
||||
void MoodbarItemDelegate::ReloadSettings() {
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
|
||||
enabled_ = s.value("enabled", false).toBool();
|
||||
const MoodbarRenderer::MoodbarStyle new_style = static_cast<MoodbarRenderer::MoodbarStyle>(s.value("style", static_cast<int>(MoodbarRenderer::MoodbarStyle::Normal)).toInt());
|
||||
s.beginGroup(MoodbarSettings::kSettingsGroup);
|
||||
enabled_ = s.value(MoodbarSettings::kEnabled, false).toBool();
|
||||
const MoodbarSettings::Style new_style = static_cast<MoodbarSettings::Style>(s.value(MoodbarSettings::kStyle, static_cast<int>(MoodbarSettings::Style::Normal)).toInt());
|
||||
s.endGroup();
|
||||
|
||||
if (!enabled_) {
|
||||
@@ -151,7 +152,7 @@ void MoodbarItemDelegate::StartLoadingData(const QUrl &url, const bool has_cue,
|
||||
// Load a mood file for this song and generate some colors from it
|
||||
QByteArray bytes;
|
||||
MoodbarPipeline *pipeline = nullptr;
|
||||
switch (app_->moodbar_loader()->Load(url, has_cue, &bytes, &pipeline)) {
|
||||
switch (moodbar_loader_->Load(url, has_cue, &bytes, &pipeline)) {
|
||||
case MoodbarLoader::Result::CannotLoad:
|
||||
data->state_ = Data::State::CannotLoad;
|
||||
break;
|
||||
@@ -278,7 +279,7 @@ void MoodbarItemDelegate::ImageLoaded(const QUrl &url, const QImage &image) {
|
||||
data->pixmap_ = QPixmap::fromImage(image);
|
||||
data->state_ = Data::State::Loaded;
|
||||
|
||||
Playlist *playlist = view_->playlist();
|
||||
Playlist *playlist = playlist_view_->playlist();
|
||||
const PlaylistFilter *filter = playlist->filter();
|
||||
|
||||
// Update all the indices with the new pixmap.
|
||||
|
||||
@@ -36,10 +36,11 @@
|
||||
#include <QSize>
|
||||
#include <QStyleOption>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "constants/moodbarsettings.h"
|
||||
|
||||
class QPainter;
|
||||
class QModelIndex;
|
||||
class QPersistentModelIndex;
|
||||
class Application;
|
||||
class MoodbarLoader;
|
||||
class MoodbarPipeline;
|
||||
class PlaylistView;
|
||||
|
||||
@@ -47,7 +48,7 @@ class MoodbarItemDelegate : public QItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MoodbarItemDelegate(Application *app, PlaylistView *view, QObject *parent = nullptr);
|
||||
explicit MoodbarItemDelegate(const SharedPtr<MoodbarLoader> moodbar_loader, PlaylistView *view, QObject *parent = nullptr);
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
|
||||
|
||||
@@ -58,6 +59,9 @@ class MoodbarItemDelegate : public QItemDelegate {
|
||||
void ColorsLoaded(const QUrl &url, const ColorVector &colors);
|
||||
void ImageLoaded(const QUrl &url, const QImage &image);
|
||||
|
||||
Q_SIGNALS:
|
||||
void StyleChanged();
|
||||
|
||||
private:
|
||||
struct Data {
|
||||
Data();
|
||||
@@ -90,12 +94,12 @@ class MoodbarItemDelegate : public QItemDelegate {
|
||||
void ReloadAllColors();
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
PlaylistView *view_;
|
||||
const SharedPtr<MoodbarLoader> moodbar_loader_;
|
||||
PlaylistView *playlist_view_;
|
||||
QCache<QUrl, Data> data_;
|
||||
|
||||
bool enabled_;
|
||||
MoodbarRenderer::MoodbarStyle style_;
|
||||
MoodbarSettings::Style style_;
|
||||
};
|
||||
|
||||
#endif // MOODBARITEMDELEGATE_H
|
||||
|
||||
@@ -41,14 +41,13 @@
|
||||
#include <QUrl>
|
||||
#include <QSettings>
|
||||
|
||||
#include "includes/scoped_ptr.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/scoped_ptr.h"
|
||||
#include "core/application.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "moodbarpipeline.h"
|
||||
|
||||
#include "settings/moodbarsettingspage.h"
|
||||
#include "constants/moodbarsettings.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
@@ -57,7 +56,7 @@ using namespace Qt::Literals::StringLiterals;
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
MoodbarLoader::MoodbarLoader(Application *app, QObject *parent)
|
||||
MoodbarLoader::MoodbarLoader(QObject *parent)
|
||||
: QObject(parent),
|
||||
cache_(new QNetworkDiskCache(this)),
|
||||
thread_(new QThread(this)),
|
||||
@@ -67,7 +66,6 @@ MoodbarLoader::MoodbarLoader(Application *app, QObject *parent)
|
||||
cache_->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/moodbar"_s);
|
||||
cache_->setMaximumCacheSize(60LL * 1024LL * 1024LL); // 60MB - enough for 20,000 moodbars
|
||||
|
||||
QObject::connect(app, &Application::SettingsChanged, this, &MoodbarLoader::ReloadSettings);
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
@@ -80,12 +78,14 @@ MoodbarLoader::~MoodbarLoader() {
|
||||
void MoodbarLoader::ReloadSettings() {
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
|
||||
save_ = s.value("save", false).toBool();
|
||||
s.beginGroup(MoodbarSettings::kSettingsGroup);
|
||||
save_ = s.value(MoodbarSettings::kSave, false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
MaybeTakeNextRequest();
|
||||
|
||||
Q_EMIT SettingsReloaded();
|
||||
|
||||
}
|
||||
|
||||
QStringList MoodbarLoader::MoodFilenames(const QString &song_filename) {
|
||||
|
||||
@@ -33,14 +33,13 @@
|
||||
class QThread;
|
||||
class QByteArray;
|
||||
class QNetworkDiskCache;
|
||||
class Application;
|
||||
class MoodbarPipeline;
|
||||
|
||||
class MoodbarLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MoodbarLoader(Application *app, QObject *parent = nullptr);
|
||||
explicit MoodbarLoader(QObject *parent = nullptr);
|
||||
~MoodbarLoader() override;
|
||||
|
||||
enum class Result {
|
||||
@@ -56,11 +55,11 @@ class MoodbarLoader : public QObject {
|
||||
WillLoadAsync
|
||||
};
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
Result Load(const QUrl &url, const bool has_cue, QByteArray *data, MoodbarPipeline **async_pipeline);
|
||||
|
||||
private Q_SLOTS:
|
||||
void ReloadSettings();
|
||||
|
||||
void RequestFinished(MoodbarPipeline *request, const QUrl &url);
|
||||
void MaybeTakeNextRequest();
|
||||
|
||||
@@ -68,6 +67,11 @@ class MoodbarLoader : public QObject {
|
||||
static QStringList MoodFilenames(const QString &song_filename);
|
||||
static QUrl CacheUrlEntry(const QString &filename);
|
||||
|
||||
Q_SIGNALS:
|
||||
void MoodbarEnabled(const bool enabled);
|
||||
void StyleChanged();
|
||||
void SettingsReloaded();
|
||||
|
||||
private:
|
||||
QNetworkDiskCache *cache_;
|
||||
QThread *thread_;
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
#include "core/signalchecker.h"
|
||||
#include "utilities/threadutils.h"
|
||||
#include "moodbar/moodbarbuilder.h"
|
||||
|
||||
#include "gstfastspectrum.h"
|
||||
#include "engine/gstfastspectrum.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
using std::make_unique;
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include <glib-object.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "core/scoped_ptr.h"
|
||||
#include "includes/scoped_ptr.h"
|
||||
|
||||
class MoodbarBuilder;
|
||||
|
||||
|
||||
@@ -40,12 +40,11 @@
|
||||
#include <QEvent>
|
||||
#include <QContextMenuEvent>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "moodbarproxystyle.h"
|
||||
#include "moodbarrenderer.h"
|
||||
#include "settings/moodbarsettingspage.h"
|
||||
#include "constants/moodbarsettings.h"
|
||||
|
||||
namespace {
|
||||
constexpr int kMarginSize = 3;
|
||||
@@ -54,12 +53,11 @@ constexpr int kArrowWidth = 17;
|
||||
constexpr int kArrowHeight = 13;
|
||||
} // namespace
|
||||
|
||||
MoodbarProxyStyle::MoodbarProxyStyle(Application *app, QSlider *slider, QObject *parent)
|
||||
MoodbarProxyStyle::MoodbarProxyStyle(QSlider *slider, QObject *parent)
|
||||
: QProxyStyle(nullptr),
|
||||
app_(app),
|
||||
slider_(slider),
|
||||
enabled_(true),
|
||||
moodbar_style_(MoodbarRenderer::MoodbarStyle::Normal),
|
||||
show_(true),
|
||||
moodbar_style_(MoodbarSettings::Style::Normal),
|
||||
state_(State::MoodbarOff),
|
||||
fade_timeline_(new QTimeLine(1000, this)),
|
||||
moodbar_colors_dirty_(true),
|
||||
@@ -75,8 +73,6 @@ MoodbarProxyStyle::MoodbarProxyStyle(Application *app, QSlider *slider, QObject
|
||||
|
||||
QObject::connect(fade_timeline_, &QTimeLine::valueChanged, this, &MoodbarProxyStyle::FaderValueChanged);
|
||||
|
||||
QObject::connect(app, &Application::SettingsChanged, this, &MoodbarProxyStyle::ReloadSettings);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
@@ -84,14 +80,13 @@ MoodbarProxyStyle::MoodbarProxyStyle(Application *app, QSlider *slider, QObject
|
||||
void MoodbarProxyStyle::ReloadSettings() {
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
|
||||
// Get the enabled/disabled setting, and start the timelines if there's a change.
|
||||
enabled_ = s.value("show", false).toBool();
|
||||
s.beginGroup(MoodbarSettings::kSettingsGroup);
|
||||
show_ = s.value(MoodbarSettings::kEnabled, false).toBool() && s.value(MoodbarSettings::kShow, false).toBool();
|
||||
|
||||
NextState();
|
||||
|
||||
// Get the style, and redraw if there's a change.
|
||||
const MoodbarRenderer::MoodbarStyle new_style = static_cast<MoodbarRenderer::MoodbarStyle>(s.value("style", static_cast<int>(MoodbarRenderer::MoodbarStyle::Normal)).toInt());
|
||||
const MoodbarSettings::Style new_style = static_cast<MoodbarSettings::Style>(s.value(MoodbarSettings::kStyle, static_cast<int>(MoodbarSettings::Style::Normal)).toInt());
|
||||
|
||||
s.endGroup();
|
||||
|
||||
@@ -111,23 +106,25 @@ void MoodbarProxyStyle::SetMoodbarData(const QByteArray &data) {
|
||||
|
||||
}
|
||||
|
||||
void MoodbarProxyStyle::SetMoodbarEnabled(const bool enabled) {
|
||||
void MoodbarProxyStyle::SetShowMoodbar(const bool show) {
|
||||
|
||||
enabled_ = enabled;
|
||||
if (show != show_) {
|
||||
|
||||
// Save the enabled setting.
|
||||
Settings s;
|
||||
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
|
||||
s.setValue("show", enabled);
|
||||
s.endGroup();
|
||||
show_ = show;
|
||||
|
||||
app_->ReloadSettings();
|
||||
Settings s;
|
||||
s.beginGroup(MoodbarSettings::kSettingsGroup);
|
||||
s.setValue(MoodbarSettings::kShow, show);
|
||||
s.endGroup();
|
||||
|
||||
ReloadSettings();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MoodbarProxyStyle::NextState() {
|
||||
|
||||
const bool visible = enabled_ && !data_.isEmpty();
|
||||
const bool visible = show_ && !data_.isEmpty();
|
||||
|
||||
// While the regular slider should stay at the standard size (Fixed),
|
||||
// moodbars should use all available space (MinimumExpanding).
|
||||
@@ -135,7 +132,7 @@ void MoodbarProxyStyle::NextState() {
|
||||
slider_->updateGeometry();
|
||||
|
||||
if (show_moodbar_action_) {
|
||||
show_moodbar_action_->setChecked(enabled_);
|
||||
show_moodbar_action_->setChecked(show_);
|
||||
}
|
||||
|
||||
if ((visible && (state_ == State::MoodbarOn || state_ == State::FadingToOn)) || (!visible && (state_ == State::MoodbarOff || state_ == State::FadingToOff))) {
|
||||
@@ -376,16 +373,16 @@ void MoodbarProxyStyle::ShowContextMenu(const QPoint pos) {
|
||||
|
||||
if (!context_menu_) {
|
||||
context_menu_ = new QMenu(slider_);
|
||||
show_moodbar_action_ = context_menu_->addAction(tr("Show moodbar"), this, &MoodbarProxyStyle::SetMoodbarEnabled);
|
||||
show_moodbar_action_ = context_menu_->addAction(tr("Show moodbar"), this, &MoodbarProxyStyle::SetShowMoodbar);
|
||||
|
||||
show_moodbar_action_->setCheckable(true);
|
||||
show_moodbar_action_->setChecked(enabled_);
|
||||
show_moodbar_action_->setChecked(show_);
|
||||
|
||||
QMenu *styles_menu = context_menu_->addMenu(tr("Moodbar style"));
|
||||
style_action_group_ = new QActionGroup(styles_menu);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(MoodbarRenderer::MoodbarStyle::StyleCount); ++i) {
|
||||
const MoodbarRenderer::MoodbarStyle style = static_cast<MoodbarRenderer::MoodbarStyle>(i);
|
||||
for (int i = 0; i < static_cast<int>(MoodbarSettings::Style::StyleCount); ++i) {
|
||||
const MoodbarSettings::Style style = static_cast<MoodbarSettings::Style>(i);
|
||||
|
||||
QAction *action = style_action_group_->addAction(MoodbarRenderer::StyleName(style));
|
||||
action->setCheckable(true);
|
||||
@@ -394,13 +391,13 @@ void MoodbarProxyStyle::ShowContextMenu(const QPoint pos) {
|
||||
|
||||
styles_menu->addActions(style_action_group_->actions());
|
||||
|
||||
QObject::connect(styles_menu, &QMenu::triggered, this, &MoodbarProxyStyle::ChangeStyle);
|
||||
QObject::connect(styles_menu, &QMenu::triggered, this, &MoodbarProxyStyle::SetStyle);
|
||||
}
|
||||
|
||||
// Update the currently selected style
|
||||
const QList<QAction*> actions = style_action_group_->actions();
|
||||
for (QAction *action : actions) {
|
||||
if (static_cast<MoodbarRenderer::MoodbarStyle>(action->data().toInt()) == moodbar_style_) {
|
||||
if (static_cast<MoodbarSettings::Style>(action->data().toInt()) == moodbar_style_) {
|
||||
action->setChecked(true);
|
||||
break;
|
||||
}
|
||||
@@ -410,13 +407,15 @@ void MoodbarProxyStyle::ShowContextMenu(const QPoint pos) {
|
||||
|
||||
}
|
||||
|
||||
void MoodbarProxyStyle::ChangeStyle(QAction *action) {
|
||||
void MoodbarProxyStyle::SetStyle(QAction *action) {
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(MoodbarSettingsPage::kSettingsGroup);
|
||||
s.setValue("style", action->data().toInt());
|
||||
s.beginGroup(MoodbarSettings::kSettingsGroup);
|
||||
s.setValue(MoodbarSettings::kStyle, action->data().toInt());
|
||||
s.endGroup();
|
||||
|
||||
app_->ReloadSettings();
|
||||
ReloadSettings();
|
||||
|
||||
Q_EMIT StyleChanged();
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <QPoint>
|
||||
#include <QStyle>
|
||||
|
||||
#include "constants/moodbarsettings.h"
|
||||
#include "moodbarrenderer.h"
|
||||
|
||||
class QAction;
|
||||
@@ -46,13 +47,13 @@ class QTimeLine;
|
||||
class QWidget;
|
||||
class QEvent;
|
||||
|
||||
class Application;
|
||||
|
||||
class MoodbarProxyStyle : public QProxyStyle {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MoodbarProxyStyle(Application *app, QSlider *slider, QObject *parent = nullptr);
|
||||
explicit MoodbarProxyStyle(QSlider *slider, QObject *parent = nullptr);
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
// QProxyStyle
|
||||
void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const override;
|
||||
@@ -66,7 +67,11 @@ class MoodbarProxyStyle : public QProxyStyle {
|
||||
void SetMoodbarData(const QByteArray &data);
|
||||
|
||||
// If the moodbar is disabled then a normal slider will always be shown.
|
||||
void SetMoodbarEnabled(const bool enabled);
|
||||
void SetShowMoodbar(const bool show);
|
||||
|
||||
Q_SIGNALS:
|
||||
void MoodbarShow(const bool show);
|
||||
void StyleChanged();
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
@@ -87,17 +92,18 @@ class MoodbarProxyStyle : public QProxyStyle {
|
||||
static QPixmap MoodbarPixmap(const ColorVector &colors, const QSize size, const QPalette &palette, const QStyleOptionSlider *opt);
|
||||
|
||||
private Q_SLOTS:
|
||||
void ReloadSettings();
|
||||
void FaderValueChanged(qreal value);
|
||||
void ChangeStyle(QAction *action);
|
||||
void SetStyle(QAction *action);
|
||||
|
||||
Q_SIGNALS:
|
||||
void SettingsChanged();
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
QSlider *slider_;
|
||||
|
||||
bool enabled_;
|
||||
bool show_;
|
||||
QByteArray data_;
|
||||
MoodbarRenderer::MoodbarStyle moodbar_style_;
|
||||
MoodbarSettings::Style moodbar_style_;
|
||||
|
||||
State state_;
|
||||
QTimeLine *fade_timeline_;
|
||||
|
||||
@@ -32,27 +32,28 @@
|
||||
#include <QSize>
|
||||
|
||||
#include "moodbarrenderer.h"
|
||||
#include "constants/moodbarsettings.h"
|
||||
|
||||
ColorVector MoodbarRenderer::Colors(const QByteArray &data, const MoodbarStyle style, const QPalette &palette) {
|
||||
ColorVector MoodbarRenderer::Colors(const QByteArray &data, const MoodbarSettings::Style style, const QPalette &palette) {
|
||||
|
||||
const int samples = static_cast<int>(data.size() / 3);
|
||||
|
||||
// Set some parameters based on the moodbar style
|
||||
StyleProperties properties;
|
||||
switch (style) {
|
||||
case MoodbarStyle::Angry:
|
||||
case MoodbarSettings::Style::Angry:
|
||||
properties = StyleProperties(samples / 360 * 9, 45, -45, 200, 100);
|
||||
break;
|
||||
case MoodbarStyle::Frozen:
|
||||
case MoodbarSettings::Style::Frozen:
|
||||
properties = StyleProperties(samples / 360 * 1, 140, 160, 50, 100);
|
||||
break;
|
||||
case MoodbarStyle::Happy:
|
||||
case MoodbarSettings::Style::Happy:
|
||||
properties = StyleProperties(samples / 360 * 2, 0, 359, 150, 250);
|
||||
break;
|
||||
case MoodbarStyle::Normal:
|
||||
case MoodbarSettings::Style::Normal:
|
||||
properties = StyleProperties(samples / 360 * 3, 0, 359, 100, 100);
|
||||
break;
|
||||
case MoodbarStyle::SystemPalette:
|
||||
case MoodbarSettings::Style::SystemPalette:
|
||||
default:{
|
||||
const QColor highlight_color(palette.color(QPalette::Active, QPalette::Highlight));
|
||||
|
||||
@@ -162,18 +163,18 @@ QImage MoodbarRenderer::RenderToImage(const ColorVector &colors, const QSize siz
|
||||
|
||||
}
|
||||
|
||||
QString MoodbarRenderer::StyleName(const MoodbarStyle style) {
|
||||
QString MoodbarRenderer::StyleName(const MoodbarSettings::Style style) {
|
||||
|
||||
switch (style) {
|
||||
case MoodbarStyle::Normal:
|
||||
case MoodbarSettings::Style::Normal:
|
||||
return QObject::tr("Normal");
|
||||
case MoodbarStyle::Angry:
|
||||
case MoodbarSettings::Style::Angry:
|
||||
return QObject::tr("Angry");
|
||||
case MoodbarStyle::Frozen:
|
||||
case MoodbarSettings::Style::Frozen:
|
||||
return QObject::tr("Frozen");
|
||||
case MoodbarStyle::Happy:
|
||||
case MoodbarSettings::Style::Happy:
|
||||
return QObject::tr("Happy");
|
||||
case MoodbarStyle::SystemPalette:
|
||||
case MoodbarSettings::Style::SystemPalette:
|
||||
return QObject::tr("System colors");
|
||||
|
||||
default:
|
||||
|
||||
@@ -32,25 +32,17 @@
|
||||
#include <QRect>
|
||||
#include <QSize>
|
||||
|
||||
#include "constants/moodbarsettings.h"
|
||||
|
||||
class QPainter;
|
||||
|
||||
using ColorVector = QList<QColor>;
|
||||
|
||||
class MoodbarRenderer {
|
||||
public:
|
||||
// These values are persisted. Remember to change moodbarsettingspage.ui when changing them.
|
||||
enum class MoodbarStyle {
|
||||
Normal = 0,
|
||||
Angry,
|
||||
Frozen,
|
||||
Happy,
|
||||
SystemPalette,
|
||||
StyleCount
|
||||
};
|
||||
static QString StyleName(const MoodbarSettings::Style style);
|
||||
|
||||
static QString StyleName(const MoodbarStyle style);
|
||||
|
||||
static ColorVector Colors(const QByteArray &data, const MoodbarStyle style, const QPalette &palette);
|
||||
static ColorVector Colors(const QByteArray &data, const MoodbarSettings::Style style, const QPalette &palette);
|
||||
static void Render(const ColorVector &colors, QPainter *p, const QRect rect);
|
||||
static QImage RenderToImage(const ColorVector &colors, const QSize size);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user