LancePutnam / Gamma

Generic (Sound) Synthesis Library
Other
458 stars 54 forks source link

STFT failure after ~30 minutes #45

Closed mhetrick closed 2 years ago

mhetrick commented 8 years ago

The STFT class is failing after approximately 20-30 minutes of use. I'm wondering if this is a similar rounding error as the CSine issue that I reported. Here's an example to test (just drop this into a file in the Examples folder).

Essentially, this example processes the first 7,000,000 samples offline before the audio callback turns on. I'm using SamplePlayer for the first 7,000,000, as it runs more quickly than the Sine oscillator.

`

include "../AudioApp.h"

include "Gamma/Oscillator.h"

include "Gamma/DFT.h"

include "Gamma/SamplePlayer.h"

using namespace gam;

class MyApp : public AudioApp{ public:

STFT stft;
SamplePlayer<> play;
Sine<> osc;

enum
{
    PREV_MAG=0,
    TEMP_MAG,
    TEMP_FRQ
};

MyApp()
    // STFT(winSize, hopSize, padSize, winType, sampType, auxBufs)
:   stft(1024, 1024/4, 0, gam::HAMMING, gam::MAG_FREQ, 3)
{
    play.load("../../sounds/count.wav");
    osc.freq(220);
    gam::Domain::master().spu(44100);

    for (int i = 0; i < 70000000; ++i)
    {
        float s = play(); play.loop();
        //float s = osc();

        if(stft(s))
        {
            //nothing
        }
        s = stft();
    }
}

void onAudio(AudioIOData& io)
{
    while(io())
    {
        float s = osc() * 0.6;

        if(stft(s))
        {
            //nothing   
        }

        io.out(0) = s;
        io.out(1) = stft();
    }
}

};

int main(){ MyApp().start(); } `

mhetrick commented 8 years ago

Also, this is occurring on both Mac and Windows.

mhetrick commented 8 years ago

With further investigation, MAG_FREQ format appears to be the suspect. Without that mode enabled, the above code isn't noisy.

mhetrick commented 8 years ago

After running a number of tests, I've figured out that this occurs when the internal phase accumulators aren't reset for a period of time.

This is generally missed when using something like the example pitch shifter code, which resets phases based on flux. However, that code still fails if the shifter works on a fairly constant drone for an extended period of time.

Our current workaround is to reset the phases manually every 20 minutes if they haven't been reset by flux.

mhetrick commented 2 years ago

Going to reopen this issue, as I think this should probably be fixed at the Gamma level instead of with our "every 20 minutes" hack.

The code in question is line 367 of DFT.cpp, in the STFT::inverse function:

for(unsigned k=1; k<numBins()-1; ++k){
            double t = bin(k)[1];       // freq
            t -= k*fund;                // freq to freq deviation
            t *= factor;                // freq deviation to phase diff
            t += k*expdp1;              // add expected phase diff due to overlap
            mAccums[k] += t;            // accumulate phase diff
            //bin(k)[1] = mAccums[k];       // copy accum phase for inverse xfm
            bufInvFrq()[2*k] = bin(k)[0];
            bufInvFrq()[2*k+1] = mAccums[k];
        }

mAccums[k] += t; should have a boundary check of some sort, or else each mAccum eventually overflows (leading to loud, dangerous spectral noise).

LancePutnam commented 2 years ago

I agree this should be fixed upstream. I see artifacts forming in the upper spectrum after only around 8 min. I tried your suggestion of wrapping the phases and that seems to fix it. Pushed as 70ba31c92db6a.

mhetrick commented 2 years ago

Great, thanks! I'll close this. Looks like you could close issue #33 as well as a duplicate.