Skip to content

New sound engine#740

Closed
davepl wants to merge 43 commits into
mainfrom
audio
Closed

New sound engine#740
davepl wants to merge 43 commits into
mainfrom
audio

Conversation

@davepl

@davepl davepl commented Aug 9, 2025

Copy link
Copy Markdown
Contributor

Description

Replaces the old SoundAnalyzer class with a new interface and implementation. Updates all callers to use the new interface.

  • I read the contribution guidelines in CONTRIBUTING.md.
  • I understand the BlinkenPerBit metric, and maximized it in this PR.
  • I selected main as the target branch.
  • All code herein is subjected to the license terms in COPYING.txt.

Comment thread include/effects/strip/faneffects.h Outdated
{
int iFan = random(0, NUM_FANS);
int passes = random(1, g_Analyzer._VURatio);
int passes = random(1, (int)g_Analyzer.VURatio());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Plase avoid shadows.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry, not familiar with the term. What's wrong here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

shadow variables are like when you have a variable named passes inside a lower scope that already has a variable named passes. This is legal, but unnecessarily confusing. (References always get the token of closest scope.

for (int i : eyes)
for (int i : more_eyes)
for (int i : still_more_eyes
if (something) return i;
print i;

The last two lines will always refer to something in still_more_eyes[]. It can lead to maintenance hassles. You decide to rename the second varible and 150 lines down you are writing to it and forget to change that reference. Now you're writing to the outer one.

It's nefarious enough that gcc has a collection of -Wshadow warning options.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I get that, but I guess I just see it? Which variable is duplicated in an inner scope?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

i guess shadows are aliasing as opposed to a brightness alpha spectra

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@davepl It's passes. It's redefined in a for loop that compares its indexer variable with a passes that's defined in an outer context.

Comment thread include/soundanalyzer.h
{ // reference them in g_Analyzer
};
#ifndef NUM_BANDS
#define NUM_BANDS 16 // Default value for projects without audio

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Consider describing why we need bands in a file named "sound" in a block "!ENABLE_AUDIO"

Comment thread include/soundanalyzer.h Outdated
Comment thread include/soundanalyzer.h
static constexpr size_t _sampling_period_us = PERIOD_FROM_FREQ(SAMPLING_FREQUENCY);
static inline size_t SamplingPeriodMicros()
{
return (size_t)PERIOD_FROM_FREQ(SAMPLING_FREQUENCY);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

P_F_F() is only used twice, and both in integer context. Why not just make it in inline function that's strongly typed?

Comment thread include/soundanalyzer.h Outdated
if (frameMax > _energyMaxEnv)
_energyMaxEnv = frameMax;
else
_energyMaxEnv = std::max(ENERGY_MIN_ENV, _energyMaxEnv * ENERGY_ENV_DECAY);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this block actually:
eME = std::max({E_M_E, frameMax, _eME * E_E_D});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why do you ask?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

So much for passive voice.

"Please replace with the clearer, and possibly more efficent as we have a 'max' opcode, suggestion."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I admit I haven't reasoned through all the code that determines frameMax to determine if what follows will ever happen, but the outcome of the current logic differs from std::max({_energyMaxEnv, frameMax, _energyMaxEnv * ENERGY_ENV_DECAY}) if frameMax is smaller than energyMaxEnv * ENERGY_ENV_DECAY.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah, now that I can get behind. Thank you. The key isn't using the second max to enforce a maximum, it's to enforce that the decayed rate never falls below MIN_ENV. I saw "max" and was reading that this was computing a maximum of a big number. It's just the maximum of a small number. Got it.

Withdrawn.

Comment thread include/soundanalyzer.h Outdated
_vPeaks[i] = 0.0f;
float vNorm = _vPeaks[b] * invEnv;
if (vNorm > vMaxNorm)
vMaxNorm = vNorm;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this vMaxNorm = max_element(_vpeaks) * invEnv?

(Technically not as there are some differences in the overflow case, but I suspect if you're overflowing floats in this, you're living wrong.

Comment thread include/soundanalyzer.h
ESP_ERROR_CHECK( i2s_start(I2S_NUM_0) );

#else
#elif ELECROW || USE_I2S_AUDIO

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

While you're in the mode to test all the things AND have your head around config, if you can figure out what "ELECTROW" really really means here and can push I2S_foo_PIN into that config file and OUT of this code, that'd be great. Similarly, if yo can fold this case and the MESMERIZER/SPECTRUM . I whacked one of three nearly identical copies of this, but lacked the confidence/background to get it down to one. I don't think they're really different enough to to be two different blocks just to set the pin number via pin_config block

Comment thread include/soundanalyzer.h
_vPeaks[i] = _Peaks._Level[i];
float sum = 0.0f;
for (int i = 0; i < NUM_BANDS; i++)
sum += _vPeaks[i];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can this be done in one pass?
float sum = 0.0f;
for (int i = 0; i < NUM_BANDS; ++i) {
_vPeaks[i] = _Peaks._Level[i];
sum += _vPeaks[i];
}

Comment thread include/soundanalyzer.h
#elif USE_M5
_MicMode = PeakData::M5;
#else
_MicMode = PeakData::MESMERIZERMIC;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Naming nit that's always bugged me: Is the else really "MESMERIZER" or is it "everything else"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's really "setups that run analog audio to a GPIO rather than an I2S source". But that's usually a mesmerizer, unless it's an umbrella :-)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In your garage, that may well be true. When someone else is trying to figure out what their prefab boad with a mic should map to or are soldering up a board of their own and trying to decide between an INMP441 or a MAX4466, it's probably not obvious to them that they need to send this code down the path named "mesmerizer" for an analog mic and "not m5 and not mesmerizer" for the 441.

We know these things were named for whatever you were building at the time, but it's an challenge to the DIY'ers in our discussion groups to get audio going.

Comment thread include/soundanalyzer.h
sum += _Peaks[i];

// Scale it so that it is not always in the top red
sum += _Peaks._Level[i];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There are lots of these loops that can be:
float sum = std::accumulate(_Peaks._Level, _Peaks._Level + NUM_BANDS, 0.0f);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Are you certain /Ofast isn't already that clever? Would be good to know. I don't mind optimizing it, but it's more readable expressed as two passes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It can't be that clever under the "as if" rule. It would have to prove that sum never overflows. Poking it, range analysis also seems to factor in, though I think it can prove that sum isn't aliased and that the arrays don't overlap, but you can see foryourself:

https://godbolt.org/z/x8e9xnPf1

Unfortunately, teh zero-overhead-loop end label is eaten by Goldbolt. There's an open BR on that at compiler-explorer/compiler-explorer#6496. Because this code is so trivial, if you know the return value (tally) is in a2, just imagine every add with a destination of a2 to be followed by the closes preceeding LOOP insn and it'll all make sense.

Even without that, it's hopefully clear that in a code path where fps matters, one loop over NUM_BANDS is better than two loops over NUM_BANDS.

@robertlipe

robertlipe commented Aug 11, 2025 via email

Copy link
Copy Markdown
Collaborator

@davepl

davepl commented Aug 11, 2025 via email

Copy link
Copy Markdown
Contributor Author

@davepl

davepl commented Aug 11, 2025 via email

Copy link
Copy Markdown
Contributor Author

@rbergen

rbergen commented Aug 11, 2025

Copy link
Copy Markdown
Collaborator

These e-mail responses at the end of the comment chain always get me...

I agree the limited scope of the code that is commented on doesn't help. (But in a very pedantic stance I do have to note that the passes in the condition clause of the for statement that is included in the excerpt refers to the outer passes - which contributes to the potential confusion in this particular case.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants