audioconvert: refactor volume ramping

We don't actually have to store the ramp parameters so allocate them on
the stack and then use them to generate the sequence.

Make it possible to generate a sequence into a custom buffer as well.

Make sure we use the right rate (the graph rate) to calculate the number
of samples when converting from time to samples.
This commit is contained in:
Wim Taymans 2025-06-16 16:00:54 +02:00
parent 8047a37b02
commit cad0523617

View file

@ -80,6 +80,9 @@ struct volume_ramp_params {
unsigned int volume_ramp_time; unsigned int volume_ramp_time;
unsigned int volume_ramp_step_time; unsigned int volume_ramp_step_time;
enum spa_audio_volume_ramp_scale scale; enum spa_audio_volume_ramp_scale scale;
float start;
float end;
uint32_t rate;
}; };
struct props { struct props {
@ -92,7 +95,6 @@ struct props {
struct volumes channel; struct volumes channel;
struct volumes soft; struct volumes soft;
struct volumes monitor; struct volumes monitor;
struct volume_ramp_params vrp;
unsigned int have_soft_volume:1; unsigned int have_soft_volume:1;
unsigned int mix_disabled:1; unsigned int mix_disabled:1;
unsigned int resample_disabled:1; unsigned int resample_disabled:1;
@ -295,6 +297,7 @@ struct impl {
struct volume volume; struct volume volume;
double rate_scale; double rate_scale;
struct spa_pod_sequence *vol_ramp_sequence; struct spa_pod_sequence *vol_ramp_sequence;
void *vol_ramp_sequence_data;
uint32_t vol_ramp_offset; uint32_t vol_ramp_offset;
uint32_t in_offset; uint32_t in_offset;
@ -1513,125 +1516,123 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params)
return changed; return changed;
} }
static int get_ramp_samples(struct impl *this) static int get_ramp_samples(struct impl *this, struct volume_ramp_params *vrp)
{ {
struct volume_ramp_params *vrp = &this->props.vrp;
int samples = -1; int samples = -1;
if (vrp->volume_ramp_samples) if (vrp->volume_ramp_samples)
samples = vrp->volume_ramp_samples; samples = vrp->volume_ramp_samples;
else if (vrp->volume_ramp_time) { else if (vrp->volume_ramp_time) {
struct dir *d = &this->dir[SPA_DIRECTION_OUTPUT]; samples = (vrp->volume_ramp_time * vrp->rate) / 1000;
unsigned int sample_rate = d->format.info.raw.rate;
samples = (vrp->volume_ramp_time * sample_rate) / 1000;
spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples); spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples);
} }
if (!samples) if (!samples)
samples = -1; samples = -1;
return samples; return samples;
} }
static int get_ramp_step_samples(struct impl *this) static int get_ramp_step_samples(struct impl *this, struct volume_ramp_params *vrp)
{ {
struct volume_ramp_params *vrp = &this->props.vrp;
int samples = -1; int samples = -1;
if (vrp->volume_ramp_step_samples) if (vrp->volume_ramp_step_samples)
samples = vrp->volume_ramp_step_samples; samples = vrp->volume_ramp_step_samples;
else if (vrp->volume_ramp_step_time) { else if (vrp->volume_ramp_step_time) {
struct dir *d = &this->dir[SPA_DIRECTION_OUTPUT];
int sample_rate = d->format.info.raw.rate;
/* convert the step time which is in nano seconds to seconds */ /* convert the step time which is in nano seconds to seconds */
samples = (vrp->volume_ramp_step_time/1000) * (sample_rate/1000); samples = (vrp->volume_ramp_step_time/1000) * (vrp->rate/1000);
spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples); spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples);
} }
if (!samples) if (!samples)
samples = -1; samples = -1;
return samples; return samples;
} }
static double get_volume_at_scale(struct impl *this, double value) static float get_volume_at_scale(struct volume_ramp_params *vrp, float value)
{ {
struct volume_ramp_params *vrp = &this->props.vrp;
if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_LINEAR || vrp->scale == SPA_AUDIO_VOLUME_RAMP_INVALID) if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_LINEAR || vrp->scale == SPA_AUDIO_VOLUME_RAMP_INVALID)
return value; return value;
else if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_CUBIC) else if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_CUBIC)
return (value * value * value); return (value * value * value);
return 0.0; return 0.0;
} }
static struct spa_pod *generate_ramp_up_seq(struct impl *this) static struct spa_pod *generate_ramp_up_seq(struct impl *this, struct volume_ramp_params *vrp,
void *buffer, size_t size)
{ {
struct spa_pod_dynamic_builder b; struct spa_pod_dynamic_builder b;
struct spa_pod_frame f[1]; struct spa_pod_frame f[1];
struct props *p = &this->props; float start = vrp->start, end = vrp->end, volume_accum = start;
double volume_accum = p->prev_volume; int ramp_samples = get_ramp_samples(this, vrp);
int ramp_samples = get_ramp_samples(this); int ramp_step_samples = get_ramp_step_samples(this, vrp);
int ramp_step_samples = get_ramp_step_samples(this); float volume_step = ((end - start) / (ramp_samples / ramp_step_samples));
double volume_step = ((p->volume - p->prev_volume) / (ramp_samples / ramp_step_samples));
uint32_t volume_offs = 0; uint32_t volume_offs = 0;
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); spa_pod_dynamic_builder_init(&b, buffer, size, 4096);
spa_pod_builder_push_sequence(&b.b, &f[0], 0); spa_pod_builder_push_sequence(&b.b, &f[0], 0);
spa_log_info(this->log, "generating ramp up sequence from %f to %f with a" spa_log_info(this->log, "generating ramp up sequence from %f to %f with a"
" step value %f at scale %d", p->prev_volume, p->volume, volume_step, p->vrp.scale); " step value %f at scale %d", start, end, volume_step, vrp->scale);
do { do {
spa_log_trace(this->log, "volume accum %f", get_volume_at_scale(this, volume_accum)); float vas = get_volume_at_scale(vrp, volume_accum);
spa_log_trace(this->log, "volume accum %f", vas);
spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties); spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties);
spa_pod_builder_add_object(&b.b, spa_pod_builder_add_object(&b.b,
SPA_TYPE_OBJECT_Props, 0, SPA_TYPE_OBJECT_Props, 0,
SPA_PROP_volume, SPA_PROP_volume, SPA_POD_Float(vas));
SPA_POD_Float(get_volume_at_scale(this, volume_accum)));
volume_accum += volume_step; volume_accum += volume_step;
volume_offs += ramp_step_samples; volume_offs += ramp_step_samples;
} while (volume_accum < p->volume); } while (volume_accum < end);
return spa_pod_builder_pop(&b.b, &f[0]); return spa_pod_builder_pop(&b.b, &f[0]);
} }
static struct spa_pod *generate_ramp_down_seq(struct impl *this) static struct spa_pod *generate_ramp_down_seq(struct impl *this, struct volume_ramp_params *vrp,
void *buffer, size_t size)
{ {
struct spa_pod_dynamic_builder b; struct spa_pod_dynamic_builder b;
struct spa_pod_frame f[1]; struct spa_pod_frame f[1];
int ramp_samples = get_ramp_samples(this); int ramp_samples = get_ramp_samples(this, vrp);
int ramp_step_samples = get_ramp_step_samples(this); int ramp_step_samples = get_ramp_step_samples(this, vrp);
struct props *p = &this->props; float start = vrp->start, end = vrp->end, volume_accum = start;
double volume_accum = p->prev_volume; float volume_step = ((start - end) / (ramp_samples / ramp_step_samples));
double volume_step = ((p->prev_volume - p->volume) / (ramp_samples / ramp_step_samples));
uint32_t volume_offs = 0; uint32_t volume_offs = 0;
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); spa_pod_dynamic_builder_init(&b, buffer, size, 4096);
spa_pod_builder_push_sequence(&b.b, &f[0], 0); spa_pod_builder_push_sequence(&b.b, &f[0], 0);
spa_log_info(this->log, "generating ramp down sequence from %f to %f with a" spa_log_info(this->log, "generating ramp down sequence from %f to %f with a"
" step value %f at scale %d", p->prev_volume, p->volume, volume_step, p->vrp.scale); " step value %f at scale %d", start, end, volume_step, vrp->scale);
do { do {
spa_log_trace(this->log, "volume accum %f", get_volume_at_scale(this, volume_accum)); float vas = get_volume_at_scale(vrp, volume_accum);
spa_log_trace(this->log, "volume accum %f", vas);
spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties); spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties);
spa_pod_builder_add_object(&b.b, spa_pod_builder_add_object(&b.b,
SPA_TYPE_OBJECT_Props, 0, SPA_TYPE_OBJECT_Props, 0,
SPA_PROP_volume, SPA_PROP_volume, SPA_POD_Float(vas));
SPA_POD_Float(get_volume_at_scale(this, volume_accum)));
volume_accum -= volume_step; volume_accum -= volume_step;
volume_offs += ramp_step_samples; volume_offs += ramp_step_samples;
} while (volume_accum > p->volume); } while (volume_accum > end);
return spa_pod_builder_pop(&b.b, &f[0]); return spa_pod_builder_pop(&b.b, &f[0]);
} }
static struct volume_ramp_params *reset_volume_ramp_params(struct impl *this) static void generate_volume_ramp(struct impl *this, struct volume_ramp_params *vrp,
void *buffer, size_t size)
{ {
if (!this->vol_ramp_sequence) { void *sequence = NULL;
struct volume_ramp_params *vrp = &this->props.vrp; if (vrp->start == vrp->end)
spa_zero(this->props.vrp); spa_log_error(this->log, "no change in volume, cannot ramp volume");
return vrp; else if (vrp->end > vrp->start)
} sequence = generate_ramp_up_seq(this, vrp, buffer, size);
return 0; else
sequence = generate_ramp_down_seq(this, vrp, buffer, size);
if (!sequence)
spa_log_error(this->log, "unable to generate sequence");
this->vol_ramp_sequence = (struct spa_pod_sequence *) sequence;
this->vol_ramp_sequence_data = (void*)sequence == buffer ? NULL : sequence;
this->vol_ramp_offset = 0;
this->recalc = true;
} }
static int apply_props(struct impl *this, const struct spa_pod *param) static int apply_props(struct impl *this, const struct spa_pod *param)
@ -1643,11 +1644,13 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
bool have_soft_volume = false; bool have_soft_volume = false;
int changed = 0; int changed = 0;
int vol_ramp_params_changed = 0; int vol_ramp_params_changed = 0;
struct volume_ramp_params *vrp = reset_volume_ramp_params(this); struct volume_ramp_params vrp;
uint32_t n; uint32_t n;
int32_t value; int32_t value;
uint32_t id; uint32_t id;
spa_zero(vrp);
SPA_POD_OBJECT_FOREACH(obj, prop) { SPA_POD_OBJECT_FOREACH(obj, prop) {
switch (prop->key) { switch (prop->key) {
case SPA_PROP_volume: case SPA_PROP_volume:
@ -1674,7 +1677,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
} }
if (spa_pod_get_int(&prop->value, &value) == 0 && value) { if (spa_pod_get_int(&prop->value, &value) == 0 && value) {
vrp->volume_ramp_samples = value; vrp.volume_ramp_samples = value;
spa_log_info(this->log, "%p volume ramp samples %d", this, value); spa_log_info(this->log, "%p volume ramp samples %d", this, value);
vol_ramp_params_changed++; vol_ramp_params_changed++;
} }
@ -1687,7 +1690,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
} }
if (spa_pod_get_int(&prop->value, &value) == 0 && value) { if (spa_pod_get_int(&prop->value, &value) == 0 && value) {
vrp->volume_ramp_step_samples = value; vrp.volume_ramp_step_samples = value;
spa_log_info(this->log, "%p volume ramp step samples is %d", spa_log_info(this->log, "%p volume ramp step samples is %d",
this, value); this, value);
} }
@ -1700,7 +1703,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
} }
if (spa_pod_get_int(&prop->value, &value) == 0 && value) { if (spa_pod_get_int(&prop->value, &value) == 0 && value) {
vrp->volume_ramp_time = value; vrp.volume_ramp_time = value;
spa_log_info(this->log, "%p volume ramp time %d", this, value); spa_log_info(this->log, "%p volume ramp time %d", this, value);
vol_ramp_params_changed++; vol_ramp_params_changed++;
} }
@ -1713,7 +1716,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
} }
if (spa_pod_get_int(&prop->value, &value) == 0 && value) { if (spa_pod_get_int(&prop->value, &value) == 0 && value) {
vrp->volume_ramp_step_time = value; vrp.volume_ramp_step_time = value;
spa_log_info(this->log, "%p volume ramp time %d", this, value); spa_log_info(this->log, "%p volume ramp time %d", this, value);
} }
break; break;
@ -1725,7 +1728,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
} }
if (spa_pod_get_id(&prop->value, &id) == 0 && id) { if (spa_pod_get_id(&prop->value, &id) == 0 && id) {
vrp->scale = id; vrp.scale = id;
spa_log_info(this->log, "%p volume ramp scale %d", this, id); spa_log_info(this->log, "%p volume ramp scale %d", this, id);
} }
break; break;
@ -1799,20 +1802,11 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
} }
if (!p->lock_volumes && vol_ramp_params_changed) { if (!p->lock_volumes && vol_ramp_params_changed) {
void *sequence = NULL; struct dir *dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)];
if (p->volume == p->prev_volume) vrp.start = p->prev_volume;
spa_log_error(this->log, "no change in volume, cannot ramp volume"); vrp.end = p->volume;
else if (p->volume > p->prev_volume) vrp.rate = dir->format.info.raw.rate;
sequence = generate_ramp_up_seq(this); generate_volume_ramp(this, &vrp, NULL, 0);
else
sequence = generate_ramp_down_seq(this);
if (!sequence)
spa_log_error(this->log, "unable to generate sequence");
this->vol_ramp_sequence = (struct spa_pod_sequence *) sequence;
this->vol_ramp_offset = 0;
this->recalc = true;
} }
if (changed) { if (changed) {
this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
@ -3566,7 +3560,8 @@ static void run_channelmix_stage(struct stage *s, struct stage_context *c)
} else if (impl->vol_ramp_sequence) { } else if (impl->vol_ramp_sequence) {
if (channelmix_process_apply_sequence(impl, impl->vol_ramp_sequence, if (channelmix_process_apply_sequence(impl, impl->vol_ramp_sequence,
&impl->vol_ramp_offset, out_datas, in_datas, c->n_samples) == 1) { &impl->vol_ramp_offset, out_datas, in_datas, c->n_samples) == 1) {
free(impl->vol_ramp_sequence); free(impl->vol_ramp_sequence_data);
impl->vol_ramp_sequence_data = NULL;
impl->vol_ramp_sequence = NULL; impl->vol_ramp_sequence = NULL;
} }
} else { } else {
@ -4193,7 +4188,7 @@ static int impl_clear(struct spa_handle *handle)
resample_free(&this->resample); resample_free(&this->resample);
if (this->wav_file != NULL) if (this->wav_file != NULL)
wav_file_close(this->wav_file); wav_file_close(this->wav_file);
free (this->vol_ramp_sequence); free (this->vol_ramp_sequence_data);
return 0; return 0;
} }