From 5eeed5bc5059543a01187b478afb1dfefc7d42a2 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Wed, 11 Dec 2019 16:42:11 +0200 Subject: [PATCH 1/5] Volume: Fix handling of mute and unmute control This patch fixes the issue of audio left muted after a cycle of mute and unmute commands. The additional improvements are applying of gain ramp for mute that helps to avoid annoying click sounds. Also since the driver issues both volume and switch commands back to back the duplicate scheduling of ramp update is prevented if there is already a ramp happening. The old ramp will operate based on new step and direction if it was changed. Some variable names were changed in function volume_set_chan() for better readability. The impacted trace print commands were also updated to not split strings unnecessarily. Signed-off-by: Seppo Ingalsuo --- src/audio/volume/volume.c | 106 +++++++++++++++++++-------------- src/include/sof/audio/volume.h | 2 + 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index 9565e8345139..f3829e03556b 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -81,6 +81,8 @@ static enum task_state vol_work(void *data) int again = 0; int i; + cd->vol_ramp_active = true; + /* inc/dec each volume if it's not at target */ for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) { /* skip if target reached */ @@ -96,6 +98,7 @@ static enum task_state vol_work(void *data) /* ramp up, check if ramp completed */ if (vol >= cd->tvolume[i] || vol >= cd->vol_max) { vol_update(cd, i); + cd->ramp_increment[i] = 0; } else { cd->volume[i] = vol; again = 1; @@ -105,11 +108,13 @@ static enum task_state vol_work(void *data) if (vol <= 0) { /* cannot ramp down below 0 */ vol_update(cd, i); + cd->ramp_increment[i] = 0; } else { /* ramp completed ? */ if (vol <= cd->tvolume[i] || vol <= cd->vol_min) { vol_update(cd, i); + cd->ramp_increment[i] = 0; } else { cd->volume[i] = vol; again = 1; @@ -122,7 +127,11 @@ static enum task_state vol_work(void *data) } /* do we need to continue ramping */ - return again ? SOF_TASK_STATE_RESCHEDULE : SOF_TASK_STATE_COMPLETED; + if (again) + return SOF_TASK_STATE_RESCHEDULE; + + cd->vol_ramp_active = 0; + return SOF_TASK_STATE_COMPLETED; } /** @@ -212,9 +221,12 @@ static struct comp_dev *volume_new(struct sof_ipc_comp *comp) for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) { cd->volume[i] = MAX(MIN(cd->vol_max, VOL_ZERO_DB), cd->vol_min); - cd->tvolume[i] = cd->volume[i]; + cd->tvolume[i] = cd->volume[i]; + cd->mvolume[i] = cd->volume[i]; + cd->muted[i] = false; } + cd->vol_ramp_active = false; trace_volume_with_ids(dev, "vol->initial_ramp = %d, vol->ramp = %d, " "vol->min_value = %d, vol->max_value = %d", @@ -360,10 +372,11 @@ static inline void volume_set_chan_mute(struct comp_dev *dev, int chan) { struct comp_data *cd = comp_get_drvdata(dev); - /* Check if not muted already */ - if (cd->volume[chan] != 0) - cd->mvolume[chan] = cd->volume[chan]; - cd->tvolume[chan] = 0; + if (!cd->muted[chan]) { + cd->mvolume[chan] = cd->tvolume[chan]; + volume_set_chan(dev, chan, 0); + cd->muted[chan] = true; + } } /** @@ -375,9 +388,10 @@ static inline void volume_set_chan_unmute(struct comp_dev *dev, int chan) { struct comp_data *cd = comp_get_drvdata(dev); - /* Check if muted */ - if (cd->volume[chan] == 0) - cd->tvolume[chan] = cd->mvolume[chan]; + if (cd->muted[chan]) { + cd->muted[chan] = false; + volume_set_chan(dev, chan, cd->mvolume[chan]); + } } /** @@ -390,7 +404,8 @@ static int volume_ctrl_set_cmd(struct comp_dev *dev, struct sof_ipc_ctrl_data *cdata) { struct comp_data *cd = comp_get_drvdata(dev); - int i; + uint32_t val; + int ch; int j; int ret = 0; @@ -408,29 +423,29 @@ static int volume_ctrl_set_cmd(struct comp_dev *dev, "cdata->comp_id = %u", cdata->comp_id); for (j = 0; j < cdata->num_elems; j++) { - trace_volume_with_ids(dev, "volume_ctrl_set_cmd(), " - "SOF_CTRL_CMD_VOLUME, " - "channel = %u, value = %u", - cdata->chanv[j].channel, - cdata->chanv[j].value); - i = cdata->chanv[j].channel; - if (i >= 0 && i < SOF_IPC_MAX_CHANNELS) { - ret = volume_set_chan(dev, i, - cdata->chanv[j].value); - } else { + ch = cdata->chanv[j].channel; + val = cdata->chanv[j].value; + trace_volume_with_ids(dev, "volume_ctrl_set_cmd(), channel = %d" + ", value = %u", ch, val); + if (ch < 0 || ch >= SOF_IPC_MAX_CHANNELS) { trace_volume_error_with_ids(dev, - "volume_ctrl_set_cmd() " - "error: " - "SOF_CTRL_CMD_VOLUME, " - "invalid i = %u", - i); + "volume_ctrl_set_cmd(), illegal channel = %d", + ch); + return -EINVAL; + } + + if (cd->muted[ch]) { + cd->mvolume[ch] = val; + } else { + ret = volume_set_chan(dev, ch, val); + if (ret) + return ret; } - if (ret) - return ret; } - schedule_task(&cd->volwork, VOL_RAMP_UPDATE_US, - VOL_RAMP_UPDATE_US); + if (!cd->vol_ramp_active) + schedule_task(&cd->volwork, VOL_RAMP_UPDATE_US, + VOL_RAMP_UPDATE_US); break; case SOF_CTRL_CMD_SWITCH: @@ -438,27 +453,26 @@ static int volume_ctrl_set_cmd(struct comp_dev *dev, "SOF_CTRL_CMD_SWITCH, " "cdata->comp_id = %u", cdata->comp_id); for (j = 0; j < cdata->num_elems; j++) { - trace_volume_with_ids(dev, "volume_ctrl_set_cmd(), " - "SOF_CTRL_CMD_SWITCH, " - "channel = %u, value = %u", - cdata->chanv[j].channel, - cdata->chanv[j].value); - i = cdata->chanv[j].channel; - if (i >= 0 && i < SOF_IPC_MAX_CHANNELS) { - if (cdata->chanv[j].value) - volume_set_chan_unmute(dev, i); - else - volume_set_chan_mute(dev, i); - } else { + ch = cdata->chanv[j].channel; + val = cdata->chanv[j].value; + trace_volume_with_ids(dev, "volume_ctrl_set_cmd(), channel = %d" + ", value = %u", ch, val); + if (ch < 0 || ch >= SOF_IPC_MAX_CHANNELS) { trace_volume_error_with_ids(dev, - "volume_ctrl_set_cmd() error: " - "SOF_CTRL_CMD_SWITCH, invalid i = %u", - i); + "volume_ctrl_set_cmd(), illegal channel = %d", + ch); + return -EINVAL; } + + if (val) + volume_set_chan_unmute(dev, ch); + else + volume_set_chan_mute(dev, ch); } - schedule_task(&cd->volwork, VOL_RAMP_UPDATE_US, - VOL_RAMP_UPDATE_US); + if (!cd->vol_ramp_active) + schedule_task(&cd->volwork, VOL_RAMP_UPDATE_US, + VOL_RAMP_UPDATE_US); break; default: diff --git a/src/include/sof/audio/volume.h b/src/include/sof/audio/volume.h index 446bc2c86be6..d57948e3dcff 100644 --- a/src/include/sof/audio/volume.h +++ b/src/include/sof/audio/volume.h @@ -110,6 +110,8 @@ struct comp_data { int32_t vol_min; /**< minimum volume */ int32_t vol_max; /**< maximum volume */ int32_t vol_ramp_range; /**< max ramp transition */ + bool muted[SOF_IPC_MAX_CHANNELS]; /**< set if channel is muted */ + bool vol_ramp_active; /**< set if volume is ramped */ /**< volume processing function */ void (*scale_vol)(struct comp_dev *dev, struct comp_buffer *sink, struct comp_buffer *source, uint32_t frames); From b403f8361d1d63a033ea36e6c489bf8fb3bacaa2 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 5 Dec 2019 11:54:07 +0200 Subject: [PATCH 2/5] Volume: Fix for gain ramp step macro value This patch fixes the calculation of macro VOL_RAMP_STEP_CONST that is used in linear step size calculation. The previous version calculated incorrectly the value as inverse. However with used ramp update rate of 1000 us there were no issues seen because the value and inverse were both 1.0. The bug would have triggered with with a faster or slower ramp update rate. Signed-off-by: Seppo Ingalsuo --- src/include/sof/audio/volume.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/include/sof/audio/volume.h b/src/include/sof/audio/volume.h index d57948e3dcff..6597a9e82851 100644 --- a/src/include/sof/audio/volume.h +++ b/src/include/sof/audio/volume.h @@ -75,12 +75,13 @@ struct sof_ipc_ctrl_value_chan; /** * \brief Macro for volume linear gain ramp step computation * Volume gain ramp step as Q1.16 is computed with equation - * step = VOL_RAMP_STEP_CONST/ SOF_TKN_VOLUME_RAMP_STEP_MS. This + * step = VOL_RAMP_STEP_CONST / SOF_TKN_VOLUME_RAMP_STEP_MS. This * macro defines as Q1.16 value the constant term - * (1000 / VOL_RAMP_UPDATE) for step calculation. + * VOL_RAMP_UPDATE / 1000.0 for step calculation. The value 1000 + * is used to to convert microseconds to milliseconds. */ #define VOL_RAMP_STEP_CONST \ - Q_CONVERT_FLOAT(1000.0 / VOL_RAMP_UPDATE_US, VOL_QXY_Y) + Q_CONVERT_FLOAT(VOL_RAMP_UPDATE_US / 1000.0, VOL_QXY_Y) /** * \brief Volume maximum value. From 776b6284738a9f42366e71d42edaf71021c216a7 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 5 Dec 2019 13:24:14 +0200 Subject: [PATCH 3/5] Volume: Always launch ramp in beginning of copy() operations This patch ensures that a ramp will happen in beginning of streaming even if the driver control sequence would not launch it by successive volume requests for mute and actual volume. Also a check is added into volume ramp update to prevent a volume command in component idle state to schedule repeatedly ramp updates. In that case the volume can be set immediately to ramp end value. Also the ramp is updated for active channels only. There's no need to update for the maximum channels count. Signed-off-by: Seppo Ingalsuo --- src/audio/volume/volume.c | 49 +++++++++++++++++++++++++++++----- src/include/sof/audio/volume.h | 2 ++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index f3829e03556b..cd59d8a2b061 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -81,10 +81,24 @@ static enum task_state vol_work(void *data) int again = 0; int i; + /* No need to ramp in idle state, jump volume to request. */ + if (dev->state == COMP_STATE_READY) { + for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) + cd->volume[i] = cd->tvolume[i]; + + vol_sync_host(cd, PLATFORM_MAX_CHANNELS); + return SOF_TASK_STATE_COMPLETED; + } + + /* The first is set and cleared to indicate ongoing ramp, the + * latter is set once to enable self launched ramp only once + * in stream start. + */ cd->vol_ramp_active = true; + cd->ramp_started = true; - /* inc/dec each volume if it's not at target */ - for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) { + /* inc/dec each volume if it's not at target for active channels */ + for (i = 0; i < cd->channels; i++) { /* skip if target reached */ if (cd->volume[i] == cd->tvolume[i]) continue; @@ -227,6 +241,8 @@ static struct comp_dev *volume_new(struct sof_ipc_comp *comp) } cd->vol_ramp_active = false; + cd->channels = 0; /* To be set in prepare() */ + trace_volume_with_ids(dev, "vol->initial_ramp = %d, vol->ramp = %d, " "vol->min_value = %d, vol->max_value = %d", @@ -274,8 +290,12 @@ static int volume_params(struct comp_dev *dev, * \param[in,out] dev Volume base component device. * \param[in] chan Channel number. * \param[in] vol Target volume. + * \param[in] constant_rate_ramp When true do a constant rate + * and variable time length ramp. When false do + * a fixed length and variable rate ramp. */ -static inline int volume_set_chan(struct comp_dev *dev, int chan, uint32_t vol) +static inline int volume_set_chan(struct comp_dev *dev, int chan, + int32_t vol, bool constant_rate_ramp) { struct comp_data *cd = comp_get_drvdata(dev); struct sof_ipc_comp_volume *pga = @@ -374,7 +394,7 @@ static inline void volume_set_chan_mute(struct comp_dev *dev, int chan) if (!cd->muted[chan]) { cd->mvolume[chan] = cd->tvolume[chan]; - volume_set_chan(dev, chan, 0); + volume_set_chan(dev, chan, 0, true); cd->muted[chan] = true; } } @@ -390,7 +410,7 @@ static inline void volume_set_chan_unmute(struct comp_dev *dev, int chan) if (cd->muted[chan]) { cd->muted[chan] = false; - volume_set_chan(dev, chan, cd->mvolume[chan]); + volume_set_chan(dev, chan, cd->mvolume[chan], true); } } @@ -437,7 +457,7 @@ static int volume_ctrl_set_cmd(struct comp_dev *dev, if (cd->muted[ch]) { cd->mvolume[ch] = val; } else { - ret = volume_set_chan(dev, ch, val); + ret = volume_set_chan(dev, ch, val, true); if (ret) return ret; } @@ -578,6 +598,10 @@ static int volume_copy(struct comp_dev *dev) tracev_volume_with_ids(dev, "volume_copy()"); + if (!cd->ramp_started) + schedule_task(&cd->volwork, VOL_RAMP_UPDATE_US, + VOL_RAMP_UPDATE_US); + /* Get source, sink, number of frames etc. to process. */ ret = comp_get_copy_limits(dev, &c); if (ret < 0) { @@ -654,6 +678,19 @@ static int volume_prepare(struct comp_dev *dev) for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) vol_sync_host(cd, i); + /* Set current volume to min to ensure ramp starts from minimum + * to previous volume request. Copy() checks for ramp started + * and schedules it if it has not yet started as result of + * driver commands. Ramp is not constant rate to ensure it lasts + * for entire topology specified time. + */ + cd->ramp_started = false; + cd->channels = sinkb->channels; + for (i = 0; i < cd->channels; i++) { + cd->volume[i] = cd->vol_min; + volume_set_chan(dev, i, cd->tvolume[i], false); + } + return 0; err: diff --git a/src/include/sof/audio/volume.h b/src/include/sof/audio/volume.h index 6597a9e82851..b71c09da4f45 100644 --- a/src/include/sof/audio/volume.h +++ b/src/include/sof/audio/volume.h @@ -111,8 +111,10 @@ struct comp_data { int32_t vol_min; /**< minimum volume */ int32_t vol_max; /**< maximum volume */ int32_t vol_ramp_range; /**< max ramp transition */ + unsigned int channels; /**< current channels count */ bool muted[SOF_IPC_MAX_CHANNELS]; /**< set if channel is muted */ bool vol_ramp_active; /**< set if volume is ramped */ + bool ramp_started; /**< control ramp launch */ /**< volume processing function */ void (*scale_vol)(struct comp_dev *dev, struct comp_buffer *sink, struct comp_buffer *source, uint32_t frames); From 2621b7d573da6c63be8c6fc7944b9208c0d17dcf Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 5 Dec 2019 14:23:28 +0200 Subject: [PATCH 4/5] Volume: Simplify ramp calculation and provide constant rate on/off The earlier version of ramp step calculation was more complex than necessary and there was no way to ensure the max. topology defined ramp time happens in beginning of streaming so the step equation was rewritten. The step value is rounded for better match with topology defined ramp length. Normally the ramp is constant rate type where the topology define time happens for vol min-max transition. A smaller transition is completed in less time. The constant rate mode is not used for the first copy() triggered ramp to ensure the ramp from vol min to used volume takes the entire topology defined time. This mode is also as earlier used for legacy kernels where the vol min/max information is not passed. Signed-off-by: Seppo Ingalsuo --- src/audio/volume/volume.c | 60 +++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index cd59d8a2b061..6911e5fd4a23 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -38,6 +38,9 @@ #include #include +/* Shift to use in volume fractional multiplications */ +#define Q_MUL_SHIFT Q_SHIFT_BITS_32(VOL_QXY_Y, VOL_QXY_Y, VOL_QXY_Y) + /** * \brief Synchronize host mmap() volume with real value. * \param[in,out] cd Volume component private data. @@ -304,7 +307,6 @@ static inline int volume_set_chan(struct comp_dev *dev, int chan, int32_t delta; int32_t delta_abs; int32_t inc; - int32_t t_us; /* Limit received volume gain to MIN..MAX range before applying it. * MAX is needed for now for the generic C gain arithmetics to prevent @@ -330,39 +332,41 @@ static inline int volume_set_chan(struct comp_dev *dev, int chan, /* Check ramp type */ switch (pga->ramp) { case SOF_VOLUME_LINEAR: - - /* Assume the ramp length (initial_ramp [ms]) describes - * time of mute to 0 dB ramp. The actual volume scale min/max - * is not known. If initial_ramp is zero (or negative) use - * step size that reaches 0 dB gain in one step (VOL_ZERO_DB). - */ - if (pga->initial_ramp > 0) - inc = MAX(VOL_RAMP_STEP_CONST / pga->initial_ramp, 1); - else - inc = VOL_ZERO_DB; - - /* Scale the increment with actual volume range if it was - * passed via IPC. - */ - if (cd->vol_ramp_range) - inc = q_multsr_32x32(inc, cd->vol_ramp_range, - Q_SHIFT_BITS_32(VOL_QXY_Y, - VOL_QXY_Y, - VOL_QXY_Y)); - + /* Get volume transition delta and absolute value */ delta = cd->tvolume[chan] - cd->volume[chan]; delta_abs = ABS(delta); - /* If the ramp completion would take longer than initial_ramp - * increase the step size to make this ramp complete at - * initial_ramp time. Calculate estimated ramp length [us]. + /* The ramp length (initial_ramp [ms]) describes time of mute + * to vol_max unmuting. Normally the volume ramp has a + * constant linear slope defined this way and variable + * completion time. However in streaming start it is feasible + * to apply the entire topology defined ramp time to unmute to + * any used volume. In this case the ramp rate is not constant. + * Note also the legacy mode without known vol_ramp_range where + * the volume transition always uses the topology defined time. */ - t_us = delta_abs * VOL_RAMP_UPDATE_US / inc; - if (t_us > pga->initial_ramp * 1000) { - inc = (int32_t)((int64_t)inc * delta_abs / VOL_ZERO_DB); - inc = MAX(inc, 1); + if (pga->initial_ramp > 0) { + if (constant_rate_ramp && cd->vol_ramp_range > 0) + inc = q_multsr_32x32(cd->vol_ramp_range, + VOL_RAMP_STEP_CONST, + Q_MUL_SHIFT); + else + inc = q_multsr_32x32(delta_abs, + VOL_RAMP_STEP_CONST, + Q_MUL_SHIFT); + + /* Divide and round to nearest. Note that there will + * be some accumulated error in ramp time the longer + * the ramp and the smaller the transition is. + */ + inc = (2 * inc / pga->initial_ramp + 1) >> 1; + } else { + inc = delta_abs; } + /* Ensure inc is at least one */ + inc = MAX(inc, 1); + /* Invert sign for volume down ramp step */ if (delta < 0) inc = -inc; From 7dacf653d6e1f5141fe005225a5f16e66ea6c49b Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 5 Dec 2019 14:37:52 +0200 Subject: [PATCH 5/5] Volume: Simplify volume synchronization to host This patch removes a trivial function vol_update() and alters vol_sync_host() to update all channels the same time into the host data structure instead of per channel updates. There is no other change to component operation. Signed-off-by: Seppo Ingalsuo --- src/audio/volume/volume.c | 45 +++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/audio/volume/volume.c b/src/audio/volume/volume.c index 6911e5fd4a23..c9cbb19f9080 100644 --- a/src/audio/volume/volume.c +++ b/src/audio/volume/volume.c @@ -44,32 +44,27 @@ /** * \brief Synchronize host mmap() volume with real value. * \param[in,out] cd Volume component private data. - * \param[in] chan Channel number. + * \param[in] num_channels Update channels 0 to num_channels -1. */ -static void vol_sync_host(struct comp_data *cd, uint32_t chan) +static void vol_sync_host(struct comp_data *cd, unsigned int num_channels) { - if (!cd->hvol) + int n; + + if (!cd->hvol) { + tracev_volume("vol_sync_host() Warning: null hvol, no update"); return; + } - if (chan < SOF_IPC_MAX_CHANNELS) { - cd->hvol[chan].value = cd->volume[chan]; + if (num_channels < SOF_IPC_MAX_CHANNELS) { + for (n = 0; n < num_channels; n++) + cd->hvol[n].value = cd->volume[n]; } else { - trace_volume_error("vol_sync_host() error: " - "chan = %u < SOF_IPC_MAX_CHANNELS", chan); + trace_volume_error("vol_sync_host() error: channels count %d" + " exceeds SOF_IPC_MAX_CHANNELS", + num_channels); } } -/** - * \brief Update volume with target value. - * \param[in,out] cd Volume component private data. - * \param[in] chan Channel number. - */ -static void vol_update(struct comp_data *cd, uint32_t chan) -{ - cd->volume[chan] = cd->tvolume[chan]; - vol_sync_host(cd, chan); -} - /** * \brief Ramps volume changes over time. * \param[in,out] data Volume base component device. @@ -114,8 +109,8 @@ static enum task_state vol_work(void *data) if (cd->volume[i] < cd->tvolume[i]) { /* ramp up, check if ramp completed */ if (vol >= cd->tvolume[i] || vol >= cd->vol_max) { - vol_update(cd, i); cd->ramp_increment[i] = 0; + cd->volume[i] = cd->tvolume[i]; } else { cd->volume[i] = vol; again = 1; @@ -124,14 +119,14 @@ static enum task_state vol_work(void *data) /* ramp down */ if (vol <= 0) { /* cannot ramp down below 0 */ - vol_update(cd, i); cd->ramp_increment[i] = 0; + cd->volume[i] = cd->tvolume[i]; } else { /* ramp completed ? */ if (vol <= cd->tvolume[i] || vol <= cd->vol_min) { - vol_update(cd, i); cd->ramp_increment[i] = 0; + cd->volume[i] = cd->tvolume[i]; } else { cd->volume[i] = vol; again = 1; @@ -139,10 +134,11 @@ static enum task_state vol_work(void *data) } } - /* sync host with new value */ - vol_sync_host(cd, i); } + /* sync host with new value */ + vol_sync_host(cd, cd->channels); + /* do we need to continue ramping */ if (again) return SOF_TASK_STATE_RESCHEDULE; @@ -679,8 +675,7 @@ static int volume_prepare(struct comp_dev *dev) goto err; } - for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) - vol_sync_host(cd, i); + vol_sync_host(cd, PLATFORM_MAX_CHANNELS); /* Set current volume to min to ensure ramp starts from minimum * to previous volume request. Copy() checks for ramp started