0
mirror of https://github.com/torvalds/GuitarPedal.git synced 2026-06-21 05:37:59 +00:00
Files
Linus Torvalds 587c0e5ded Improve the tuner update frequency with a sliding FFT window
The tuner code seems to be in reasonably good shape, so let's attack the
slow screen updates by making the sample acquisition side do a simple
circular buffer, and instead of doing the Hanning function at sample
acquisition time, do it at analysis time.

Then we can do the analysis by sliding over the pristine sample buffer
in smaller increments - doing the Hanning at that point as we copy it to
the FFT buffer and turn it into the complex domain.

That way we can get more frequent FFT's for those sliding windows and a
more responsive UI - and a natural smoothing of the results.

The total latency is not really any better, but it *feels* much better,
and instead of getting a jerky update with no smoothing, you get a much
more realistic feel for how close the tuning is when the display updates
with the sliding results.

And the occasional bad tuning results when you were unlucky and hit some
timeframe when you had bad interactions with picking or happened to get
some other effect now feel like small blips rather than horrible big
events that you have to wait a second to fix themselves, because the
screen update has gone from 1.5 frames per second to being about 23
frames per second.

And the code isn't hardly any more complicated, so it's an unambiguous
win.

.. except for the added memory use.  Now we need that circular buffer of
samples to be separate from the FFT buffer, and it needs to have more
samples in it too.  So this makes that tuner - that was already using a
fair amount of precious RAM in the rp2354 - use even more RAM.

Of course, I think the FFT size is probably unnecessarily large, and so
maybe the answer is to make FFT_SHIFT smaller.  We could use a 8x
downsampling too.  So there are solutions to the memory use, and right
now it all fits so I won't worry about it.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-06-11 15:14:48 -07:00

62 lines
1.6 KiB
C

#define FFT_SHIFT 13
#define FFT_SIZE (1 << FFT_SHIFT)
#define ANALYZE_RING_SHIFT 14
#define ANALYZE_RING_SIZE (1 << ANALYZE_RING_SHIFT)
#define ANALYZE_RING_MASK (ANALYZE_RING_SIZE - 1)
//
// NOTE! This is accessed from both cores, but the
// logic is that the audio core only writes to 'ring_buf'
// and increments 'write_index'. The UI core independently
// reads from the ring buffer and maintains the read index.
//
struct analyze_state {
float ring_buf[ANALYZE_RING_SIZE];
volatile unsigned int write_index;
unsigned int read_index;
} analyzer;
// Hann function using the quarter_sine table. We don't
// do the standard "(1-cos(x))/2", we do "sin^2(x/2)"
// instead, and only use half the sine cycle.
//
// Half a sine cycle is the same as walking the quarter
// cycle forward and then backward.
static inline float hanning(unsigned int idx)
{
const int fractional_bits = FFT_SHIFT - QUARTER_SINE_STEP_SHIFT -1;
float frac = u32_to_fraction(idx << (32 - fractional_bits));
idx >>= fractional_bits;
unsigned int next = idx+1;
if (idx >= QUARTER_SINE_STEPS) {
idx = QUARTER_SINE_STEPS*2 - idx;
next = idx-1;
}
float sin = linear(frac, quarter_sin[idx], quarter_sin[next]);
return sin*sin;
}
// 4x downsampled data into continuous lock-free ring buffer
static inline void analyze_process_sample(float sample)
{
// Downsample by 4x
static float sample_sum;
static int count;
sample += sample_sum;
if (3 & ++count) {
sample_sum = sample;
return;
}
sample_sum = 0;
unsigned int idx = analyzer.write_index;
analyzer.ring_buf[idx & ANALYZE_RING_MASK] = sample;
analyzer.write_index = idx + 1;
}