Audio cuts out - overflowing floats?

Hi all,

I’m finding the audio on the Daisy cuts out when it gets too loud. I have written a program that uses delayline.h, and I’m putting a feedback signal back in with a maximum possible multiplier of 1.25, so it goes into self oscillation. This is great, however if I leave it set like this for too long the audio cuts out, and won’t come back until I reset the board.

Am I overflowing the variables, or is something else going on? And any suggestions how to limit the signal so it stays within the maximum range of a float? Would it be as simple as capping the feedback and output signals somehow?

Thanks!

I definitely ran into the same behavior on other hardware. Overflow is something that happens to integers, this issue is probably float value becoming rounded to infinity. This would be the typical result from feedback >1. So you could either use a safe FB range up to 0.7-0.8 or allow higher levels, but reduce it gradually if signal gets too hot (basically use envelope follower as negative feedback source).

Right, that makes sense. Haven’t tried using an envelope follower yet, but it sounds like a great idea. Thanks!

I would try to determine what state the system is in when audio cuts out. Is the program still running?

Yes the program is still going, it’s responding to button presses. There’s 2 audio channels, and they cut out individually, so if the left channel exceeds the threshold the right channel will still make noise

Makes me wonder what is cutting out the audio?

Is there a simple program I could try on a Pod that would do this?

You could try the delayline example:

Set the feedback to anything over 1.0

Very interesting. Only the delay line goes silent. I hacked a a button to bypass the delay:

        // Output
        out[LEFT]  = buttonpress ? osc_out : sig_out;
        out[RIGHT] = buttonpress ? osc_out : sig_out;

After the level is reached where things get quiet, I can press the button, and hear the oscillator. So, it’s not some strange state in the codec.

Then I added:

        // Write to the delay
        del.Write(buttonpress ? 0.0f : feedback);

        // Output
        out[LEFT]  = buttonpress ? osc_out : sig_out;
        out[RIGHT] = buttonpress ? osc_out : sig_out;

If you let things go silent, then press the button for about a second, it recovers until next time the feedback goes out of range.

(I’ve been a fan of the C/C++ ternary operator for many, many years.)

So, yes, the solution will be to keep the values in the delay line within range. Though I don’t yet know what that range is.

This is very likely a numerical range issue. As antisvin wrote, the usual method to tame an exponential feedback growth caused by a gain > 1 is to use a limiter within the feedback loop or even just a soft clipper. Both can be combined, their effect on the sound are different. The thresholds or shapes depend entirely on your application. If you want to chain the delay line with other effects, don’t let the signal go too far from 0 dBFS (for example if an effect is temporarily squaring the signal to extract an RMS value, the INF will be reached at ~10^19 instead of ~10^38). Anyway the values will be hard-clipped by libDaisy before being sent to the DAC, so it’s probably better to keep everything under control by your own means.

Hi! I had this problem (the killing of the sound) too with my feedback delay line. I think the cause is a increasing DC offset due to the feedback. And at some point the DC offset will reach the outer ranges of the 32-bit floating point (or the DAC). I solved it by incorporating a simple DC block in the feedback line. I didn’t need any limiting or attenuation.

I think that DC blocking won’t always be sufficient. If you have DC offset in you signal, then you surely need to do it. But you may also have signal without DC offset that would be exponentially growing due to large feedback coefficient. The difference is that any signal with DC ofset would be always increasing with feedback (if feedback is positive), while without it your audio signal would start resonating only if delayed and input signals match phases - otherwise they partially cancel each other.

How would I end up with DC in the signal btw? Don’t the 10uF caps in series with the audio inputs and outputs to the codec block any DC? I suppose any processing on the signal could leave a DC offset, but I’m just using a combination of delay lines and filters at the moment.

The DC does not need to be pure to build up. And even if the input is perfectly DC-blocked, the content of the delay line may exhibit a “local” DC offset at a given time. But the recursive amplification will cause silence at the output only if it is big enough to shift the whole AC signal above or below 0 (or the DC is amplified more than the AC because of some filtering). So it might be two different issues.

Ok so I managed to write an envelope follower class, based on this:
http://sam-koblenski.blogspot.com/2015/10/everyday-dsp-for-programmers-signal.html

#ifndef ENV_FOLLOW_H
#define ENV_FOLLOW_H
#include <math.h>

namespace daisysp
{

class EnvFollow
{
    private:
        
    float avg;      //exp average of input
    float pos_sample;   //positive sample
    float sample_noDC;  //no DC sample
    float avg_env;  //average envelope
    float w;        //weighting
    float w_env;    //envelope weighting

    public:

    EnvFollow() //default constructor
    {
        avg = 0.0f;      //exp average of input
        pos_sample = 0.0f;   //positive sample
        avg_env = 0.0f;  //average envelope
        w = 0.0001f;        //weighting
        w_env = 0.0001f;    //envelope weighting
        sample_noDC = 0.0f;
    }  
    ~EnvFollow() {}

    float GetEnv(float sample)
    {
        //remove average DC offset:
        avg = (w * sample) + ((1-w) * avg);
        sample_noDC = sample - avg;

        //take absolute
        pos_sample = fabsf(sample_noDC);

        //remove ripple
        avg_env = (w_env * pos_sample) + ((1-w_env) * avg_env);

        return avg_env;
    }
};

} // namespace daisysp
#endif

This works pretty well, perhaps I could tweak the weighting values a little to improve the response, but it’s not bad. I then output this on the Daisy DAC, so I could view it on my scope as a DC voltage. I noticed that the range of my envelope follower float doesn’t go much above a value of 1.0. I know this won’t pick up all the peaks as it is averaging the signal, but shouldn’t this be in the ballpark of the magnitude of the audio signal? I was surprised, because I assumed the floats would have a maximum value of around 3 *10^38!

Anyhow, I managed to implement an inverse feedback using the envelope follower signal, and this works, managing the runaway feedback effectively. However if I put a really hot input signal in I can still get the audio to cutout.

What would the best way to hard limit the audio signal? I tried this:

out[i] = (mixL > HardLimit) ? HardLimit : mixL;

But still the audio cuts out. I used a value of 0.7f for the HardLimit.

Any tips would be much appreciated!

1 Like

Something similar happens if you include the DaisySP pitchshifter in a feedback loop.

I’ve hard limited the feedback signal and it still occurs at low volume.

I hadn’t considered a DC offset however, so I will have to give that a shot.

The cutting out isn’t caused by your mixL bring too hot, a limiter at that point won’t help.

You should be limiting feedback loop and using feedback signal as input for env follower. You should probably upload full source to github or somewhere else, the follower code doesn’t have any obvious issues.

Hard limiting output signal the way you describe would do nothing - Daisy’s codec would clip anything outisde of -1.0, 1.0 anyway. You should be clipping feedback signal rather than output signal. But with correctly working env-based limiter it shouldn’t be necessary.

Ok sure - I was using the output mix signal as the envelope follower input, but I’ll try using the feedback signal.

How would you recommend implementing the negative feedback? So far I’ve just calculated the difference between the envelope output and a constant threshold value; if this is positive I have multiplied this by a constant factor and used the result to reduce the amount of feedback signal written to the delay line. Maybe there’s a better way, along the lines of a PID controller? I find that once you overshoot the threshold the signal oscillates in amplitude around it for a little while, with quite big over and undershoots.

I actually already hard limited the feedback signal as well - I can see why limiting the output mix isn’t very helpful if Daisy already clips it.

Very happy to upload the EnvFollow class to GitHub, and contribute it to DaisySP if other people would find it useful.

Thanks!

That makes sense mathematically. But you may also consider using some kind of non-linear lookup table to have results that sounds more interesting.

Also, generally, you should avoid naive hard clipping, it adds aliasing artifacts to your signal. This would be especially bad in feedback loop, as those artifacts won’t go away immediately when signal stops getting clipped. Soft clipping would sound better, but it adds distortion to unclipped signal and is not free of aliasing either (but less than hard clipped signal). There are ways to perform clipping with aliasing suppression, which may be something worth researching.

Oh yeah, my intention is set it up so the hard clipping only happens if you overload the input to stop it from cutting out, in normal operation (even with feedback on max) I’d want it to stabilise below that value.

Talking of overloading the input - I disabled feedback entirely and hit the input with a very hot unfiltered sawtooth, and the audio cuts out on the first repeat! So I guess it must be writing or reading from the delayline is causing the signal to cutout? Or is something else going on?