//
// Real spectrum analysis driving the visualizer.
//
// We read directly from the same s16 PCM buffer Sound_Player is mixing from
// (sd.samples), centered on play_cursor. No second decoder, no seeking, no
// drift between bars and audio.
//
// Sound_Player advances play_cursor at sd.sampling_rate * current_rate per
// second, in *frame* units (not interleaved samples). To pull FFT_SIZE
// frames from sd.samples (which is interleaved), index by
// frame_index * nchannels.
//

MIN_VISUAL_FREQ :: 30.0;

update_audio_analysis :: () {
    fft_init();

    if !app.current_stream || !app.current_stream.sound_data || app.paused {
        decay_spectrum(0.10);
        return;
    }

    sd := app.current_stream.sound_data;
    if sd.type != .LINEAR_SAMPLE_ARRAY || !sd.samples || sd.sampling_rate == 0 || sd.nchannels == 0 {
        decay_spectrum(0.10);
        return;
    }

    nch          := cast(s64) sd.nchannels;
    total_frames := sd.nsamples_times_nchannels / nch;

    cursor_frame := cast(s64) app.current_stream.play_cursor;
    start_frame  := cursor_frame - FFT_SIZE / 2;
    if start_frame < 0  start_frame = 0;
    if start_frame + FFT_SIZE > total_frames  start_frame = total_frames - FFT_SIZE;
    if start_frame < 0 {
        decay_spectrum(0.10);
        return;
    }

    // Mix to mono and apply Hann window.
    inv_chan := 1.0 / cast(float) nch;
    base     := start_frame * nch;
    for k: 0..FFT_SIZE-1 {
        sum: float = 0;
        for ch: 0..nch-1  sum += cast(float) sd.samples[base + k * nch + ch];
        mono     := sum * inv_chan / 32768.0;
        fft_re[k] = mono * fft_window[k];
        fft_im[k] = 0;
    }

    fft();

    rate     := cast(float) sd.sampling_rate;
    nyquist  := rate * 0.5;
    log_lo   := log(MIN_VISUAL_FREQ);
    log_hi   := log(nyquist);
    log_step := (log_hi - log_lo) / cast(float) SPECTRUM_BINS;

    for b: 0..SPECTRUM_BINS-1 {
        f_lo := exp(log_lo + log_step * cast(float) b);
        f_hi := exp(log_lo + log_step * cast(float) (b + 1));

        bin_lo := cast(int) (f_lo * FFT_SIZE / rate);
        bin_hi := cast(int) (f_hi * FFT_SIZE / rate);
        if bin_hi <= bin_lo  bin_hi = bin_lo + 1;
        if bin_hi > HALF_FFT  bin_hi = HALF_FFT;
        if bin_lo >= HALF_FFT  continue;

        peak: float = 0;
        for k: bin_lo..bin_hi-1 {
            mag := sqrt(fft_re[k] * fft_re[k] + fft_im[k] * fft_im[k]);
            if mag > peak  peak = mag;
        }

        v := peak / (cast(float) FFT_SIZE * 0.25);
        v = sqrt(v);
        if v > 1  v = 1;

        prev      := app.spectrum[b];
        rate_lerp := ifx v > prev then 0.45 else 0.10;
        app.spectrum[b] = lerp(prev, v, rate_lerp);
    }
}

#scope_file

decay_spectrum :: (rate: float) {
    for 0..SPECTRUM_BINS-1 {
        app.spectrum[it] = lerp(app.spectrum[it], 0, rate);
    }
}
