Skip to content

reduce memory usage for Echo effect#1302

Closed
Be-ing wants to merge 2 commits intomixxxdj:masterfrom
Be-ing:fix_max_sampling_rate_constant
Closed

reduce memory usage for Echo effect#1302
Be-ing wants to merge 2 commits intomixxxdj:masterfrom
Be-ing:fix_max_sampling_rate_constant

Conversation

@Be-ing
Copy link
Copy Markdown
Contributor

@Be-ing Be-ing commented Jul 4, 2017

The Echo effect refers to this constant to determine the size of the buffers it allocates, resulting in a huge waste of memory when this constant is 192 kHz. When effects maintain state for both the master output and headphones output in PR #1254, this resulted in the Echo effect allocating about 400 MB!

There is no reason Mixxx should support 192 kHz, even if a user's sound card does. It provides no benefit and is likely to introduce introduce intermodulation distortion when playing ultrasonic
frequencies. Refer to http://xiph.org/~xiphmont/demo/neil-young.html for details.

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Jul 4, 2017

The reference to this constant in the Echo effect was introduced by @rryan in de43d2d to fix Bug #1658508.

@Be-ing Be-ing force-pushed the fix_max_sampling_rate_constant branch from 9684140 to 7d9b9f1 Compare July 4, 2017 03:52
This was referenced Jul 4, 2017
@daschuer
Copy link
Copy Markdown
Member

daschuer commented Jul 6, 2017

Unfortunately, we cannot do this without rejecting valid 192 kHz tracks.
Mixxx has the concept to pass them bit perfect into the tempo and pitch shift engine (enginebuffer) to have only one re-sampling stage.
The following audio processing, including effects is done with the output sample rate which is currently limited to 92 kHz.

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Jul 6, 2017

How about using two constants? We could call them something like kSamplingRateFileMax and kSamplingRatePlaybackMax.

@daschuer
Copy link
Copy Markdown
Member

daschuer commented Jul 6, 2017

That works for me. These constants value should be used here as well:

m_samplerates.push_back(96000);

To be honest I have not understood the source of 400 MB
This is good for 136 Echo effect states @192 kHz.
192kHz * size_off(float) * 2 s * 2 ch = ~3 MB

Instead of allocation the maximum possible buffer in echo, we should allocate a reasonable buffer and if one day someone enables an insane sample rate, the echo should limit its delay without the risk of a memory overflow.

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Jul 6, 2017

To be honest I have not understood the source of 400 MB

(4 decks + 4 mics + 4 aux + 64 samplers) x 2 [master and headphone outputs] group states

@daschuer
Copy link
Copy Markdown
Member

daschuer commented Jul 6, 2017

We are doing something else wrong ..

@uklotzde
Copy link
Copy Markdown
Contributor

uklotzde commented Jul 8, 2017

The maximum sample rate is also used for validating metadata read from audio files. We should not adjust this, just because some internal filter is badly designed.

I agree with Daniel:

  • Allocate a buffer for 48 kHz
  • Adjust the size of the buffer later if required (copying the currently buffered data) and log an informational message

Allocating additional memory in a real-time scenario is not ideal, but otherwise the effects framework would need to be redesigned to handle varying sample rates. Changing the internal sample rate of the engine should only happen in a non-real-time situation.

@uklotzde
Copy link
Copy Markdown
Contributor

uklotzde commented Jul 8, 2017

And, of course, the buffer currently only has a size of 3 MB. Ok, for each buffer.

@uklotzde
Copy link
Copy Markdown
Contributor

uklotzde commented Jul 8, 2017

...and constants for specific use cases should be defined in the corresponding context instead of in a common place. 192 kHz is the maximum that Mixxx supports. If the engine has a lower limit it should define it's own constant.

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Jul 8, 2017

Allocating additional memory in a real-time scenario is not ideal, but otherwise the effects framework would need to be redesigned to handle varying sample rates.

I think we should redesign the engine and engine side of the effects system to reallocate buffers when the playback sample rate changes. But I think that could be a large task. For now we need a quick fix. IMO allocating memory in the audio callback thread is a non-starter.

Changing the internal sample rate of the engine should only happen in a non-real-time situation.

Correct, which would make it possible to tell the engine and effects system to reallocate their buffers from DlgPrefSound.

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Jul 8, 2017

that is a large task

Hmm, it might not be so difficult actually. I'll try implementing it in #1254 after #1279 is merged.

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Nov 19, 2017

So how shall we address this for now? Two options occur to me:

  1. Add a new constant for max playback sample rate
  2. Hardcode 96 kHz in the Echo code

@uklotzde
Copy link
Copy Markdown
Contributor

I vote for a separate constant kMaxPlaybackSampleRate that explicitly states the limits of the engine.

Constants, types, and version numbers are not limited resources and should be used whenever needed. The constant doesn't magically disappear if we hide it somewhere inside the echo code.

@Be-ing Be-ing changed the title reduce AudioSignal::kSamplingRateMax to the max in preferences reduce memory usage for Echo effect Nov 19, 2017
@Be-ing Be-ing force-pushed the fix_max_sampling_rate_constant branch from 7d9b9f1 to 48ab2e9 Compare November 19, 2017 22:40
@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Nov 20, 2017

Okay, I have introduced a new constant and used it for Echo.

Comment thread src/util/audiosignal.h Outdated
static constexpr SINT kSamplingRate96kHz = 96000;
static constexpr SINT kSamplingRate192kHz = 192000;
static constexpr SINT kSamplingRateMax = kSamplingRate192kHz; // upper bound
static constexpr SINT kSamplingRateMaxPlayback = kSamplingRate96kHz;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see this constant at some central location within the engine code base instead of in this unbiased header file. If the engine decides to restrict the sample rate for playback it should define its own constant for this purpose.

@uklotzde
Copy link
Copy Markdown
Contributor

uklotzde commented Nov 20, 2017

You may copy the new engine.h header file that I added in my audiosourcev2 branch. I used it for explicitly defining the (in)famous kEngineChannelCount = 2 constant ;) The data type is not yet available, SINT should work.

@daschuer
Copy link
Copy Markdown
Member

I am currently trying to get my head around this, with #1254 in mind.

I have read a bit how others solves this, an I cannot get rid of the feeling we are doing something wrong. I can't express this very well, but this issue here can be easily solved using the classic hardware solution of a send bus. In this case we need only one or two instances of an classic send effect like echo.

The drawback is, that it requires some more brain how to make his easy accessable.
We have no big problems here since we can tweak our internal effects, but if we think to high quality LV2 reverbs, with external GUI, a mandatory 136 instances solution sounds bad.

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Nov 21, 2017

There may be a better solution, but nothing comes to mind immediately. At least for 2.1, I think only allocating buffers for 96 kHz maximum and not letting samplers be routed to effects (which was a capability of the engine not exposed to users in Mixxx 2.0, so I don't think anyone is going to miss it) is a good enough solution.

@daschuer
Copy link
Copy Markdown
Member

Reducing the buffer here is a good thing in any way. The root cause for the huge memory consumption is the amount if instances though.

@uklotzde uklotzde added this to the 2.1.0 milestone Nov 25, 2017
@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Nov 26, 2017

Ack, this is trickier than I thought. Although we only list 96k as the max sample rate in the preferences, it is possible to use Mixxx with JACK using higher sample rates...

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Nov 26, 2017

Ehhhh this would require some changes to the effects system that would conflict with #1254, so let's wait until that is merged... :/

The Echo effect referred to the
mixxx::AudioSignal::SampleRate::max() constant, equal to 192 kHz,
to determine the size of the buffers it allocates. This resulted
in a huge waste of memory.

There is no reason Mixxx should support 192 kHz, even if a user's
sound card does. It provides no benefit and is likely to introduce
introduce intermodulation distortion when playing ultrasonic
frequencies. Refer to
http://xiph.org/~xiphmont/demo/neil-young.html
for details.
@Be-ing Be-ing force-pushed the fix_max_sampling_rate_constant branch from 48ab2e9 to 26cec56 Compare December 8, 2017 00:18
@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Dec 8, 2017

Okay, I've done a bit of refactoring so the Echo effect and preferences refer to the same max sampling rate constant for playback.

There is a tradeoff between the maximum delay time and the memory required
by the effect. The higher the maximum delay time, the lower the minimum
BPM that can use the full range of 2 beats. The cutoff point is arbitrary.
The default minimum BPM for analysis is set at 70, so I think it is fairly
unlikely that a user would use the Echo effect with the maximum delay time
of 2 beats with a track less than 60 BPM.
@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Dec 8, 2017

We are doing something else wrong ..

As a quick proof-of-concept hack, I lowered the MAX_BUFFER_LEN constant in src/util/defs.h to from 160000 (where did that number come from? There is no information in the source code) to 4096 (which I think is the default) on this branch. That saved 97 MB of memory. I tested again on my personal branch with this and #1254 and still 91 MB saved. We should get rid of this lazy MAX_BUFFER_LEN constant and allocate buffer sizes actually needed with the user's configuration.

EchoGroupState()
: delay_buf(mixxx::AudioSignal::SampleRate::max() * kMaxDelaySeconds *
kChannelCount) {
: delay_buf(kMaxDelaySeconds * kChannelCount *
Copy link
Copy Markdown
Contributor

@uklotzde uklotzde Dec 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add some static functions to SoundManager to avoid leaking internal storage and ordering and to make the code more readable:
static constexpr SINT minSupportedSampleRate()
static constexpr SINT maxSupportedSampleRate()
static bool isSupportedSampleRate(SINT sampleRate)

// introduce introduce intermodulation distortion when playing ultrasonic
// frequencies. Refer to http://xiph.org/~xiphmont/demo/neil-young.html
// for details.
constexpr static const unsigned int s_iSupportedSampleRates[3] = {
Copy link
Copy Markdown
Contributor

@uklotzde uklotzde Dec 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be private. Do you explicitly need to state the size here?

bool SoundManagerConfig::checkSampleRate(const SoundManager &soundManager) {
if (!soundManager.getSampleRates(m_api).contains(m_sampleRate)) {
return false;
bool SoundManagerConfig::checkSampleRate() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced by:
return SoundManager::isSupportedSampleRate(m_sampleRate);

m_sampleRate = sampleRates.first();
} else {
qWarning() << "got empty sample rate list from SoundManager, this is a bug";
m_sampleRate = defaultSampleRate;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (SoundManager::isSupportedSampleRate(defaultSampleRate)) {
   m_sampleRate =  defaultSampleRate;
} else {
   DEBUG_ASSERT(SoundManager::isSupportedSampleRate(kFallbackSampleRate));
   m_sampleRate = kFallbackSampleRate;
}

@uklotzde
Copy link
Copy Markdown
Contributor

uklotzde commented Dec 8, 2017

A good step into the right direction to get rid of all those implicit constants and assumptions that are scattered everywhere in the code! Please just make the code more safe and readable and we will end up with even less code. Less code = less bugs.

We should publish more of these small, easy to review PRs that improve the maintainability and readability of existing code before introducing new features! I must confess that my own PRs always get much too big. If we would have more committers that are able to review and integrate small PRs fast it would be a big improvement ;)

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Dec 11, 2017

This conflicts with ongoing work on #1254. I'll merge the changes to SoundManager and SoundManagerConfig from this branch onto that.

@Be-ing Be-ing closed this Dec 11, 2017
@daschuer
Copy link
Copy Markdown
Member

I am a bit concerned, that #1254 will get to big.
There is no strong need for the changes here if we create the memory on demand. Can't we do it later in a separate PR?

@Be-ing
Copy link
Copy Markdown
Contributor Author

Be-ing commented Dec 11, 2017

Yeah, there's no need to rush this anymore.

@Be-ing Be-ing deleted the fix_max_sampling_rate_constant branch April 24, 2019 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants