## -*- texinfo -*-
## @deftypefn {Function File} dsp_audio_demo (@var{parent_fig}, @var{b}, @var{a}, @var{Fs_filter})
## Open an audio demo window.  Load a WAV file, apply the designed filter,
## play original / filtered audio, and display waveforms and spectrograms
## side by side.
##
## The loaded audio is cached on @var{parent_fig}, so going back to the main
## window, changing the design and reopening the demo brings the same file
## straight back -- re-filtered with the new coefficients, without a second
## trip through the file dialog.  The cache lives and dies with the main
## window.
##
## The spectrogram frequency axis follows the tool-wide unit setting
## (@code{dsp_freq_axis_hz}); the demo carries its own toggle button, like
## the detailed analysis window.
## @end deftypefn

function dsp_audio_demo(parent_fig, b, a, Fs_filter)
    h.fig = dsp_new_window("Audio Demo - dsppack");
    set(h.fig, "CloseRequestFcn", @cb_back);

    h.b = b;
    h.a = a;
    h.Fs_filter = Fs_filter;
    h.signal   = [];
    h.filtered = [];
    h.Fs_audio = 0;

    % --- Top control bar ---
    uicontrol(h.fig, "Style", "pushbutton", ...
              "String", "< BACK", ...
              "Units", "normalized", ...
              "Position", [0.02, 0.955, 0.08, 0.035], ...
              "Callback", @cb_back);

    h.btn_load = uicontrol(h.fig, "Style", "pushbutton", ...
                           "String", "Load WAV", ...
                           "Units", "normalized", ...
                           "Position", [0.12, 0.955, 0.10, 0.035], ...
                           "FontWeight", "bold", ...
                           "Callback", @cb_load);

    h.btn_play_orig = uicontrol(h.fig, "Style", "pushbutton", ...
                                "String", "Play Original", ...
                                "Units", "normalized", ...
                                "Position", [0.24, 0.955, 0.11, 0.035], ...
                                "Enable", "off", ...
                                "Callback", @cb_play_orig);

    h.btn_play_filt = uicontrol(h.fig, "Style", "pushbutton", ...
                                "String", "Play Filtered", ...
                                "Units", "normalized", ...
                                "Position", [0.37, 0.955, 0.11, 0.035], ...
                                "Enable", "off", ...
                                "Callback", @cb_play_filt);

    h.btn_stop = uicontrol(h.fig, "Style", "pushbutton", ...
                           "String", "Stop", ...
                           "Units", "normalized", ...
                           "Position", [0.50, 0.955, 0.07, 0.035], ...
                           "Enable", "off", ...
                           "Callback", @cb_stop);

    h.btn_save = uicontrol(h.fig, "Style", "pushbutton", ...
                           "String", "Save WAV", ...
                           "Units", "normalized", ...
                           "Position", [0.58, 0.955, 0.09, 0.035], ...
                           "FontWeight", "bold", ...
                           "Enable", "off", ...
                           "Callback", @cb_save_wav);

    [~, freq_lbl] = dsp_freq_axis_hz();
    h.btn_FreqAxis = uicontrol(h.fig, "Style", "pushbutton", ...
                               "String", freq_lbl, ...
                               "Units", "normalized", ...
                               "Position", [0.77, 0.955, 0.21, 0.035], ...
                               "TooltipString", ["Toggle the spectrogram frequency " ...
                                                 "axis between normalized units " ...
                                                 "(0 to pi rad/sample) and absolute " ...
                                                 "frequency (0 to Fs/2 Hz)"], ...
                               "Callback", @cb_freqaxis);

    % --- Volume control (bottom strip) ---
    uicontrol(h.fig, "Style", "text", ...
              "String", "Volume:", ...
              "Units", "normalized", ...
              "Position", [0.02, 0.03, 0.05, 0.03], ...
              "HorizontalAlignment", "left", ...
              "FontWeight", "bold");

    h.slider_vol = uicontrol(h.fig, "Style", "slider", ...
                             "Units", "normalized", ...
                             "Position", [0.07, 0.03, 0.20, 0.03], ...
                             "Min", 0, "Max", 1, "Value", 0.8, ...
                             "Callback", @cb_volume);

    h.txt_vol = uicontrol(h.fig, "Style", "text", ...
                           "String", "80%", ...
                           "Units", "normalized", ...
                           "Position", [0.28, 0.03, 0.05, 0.03], ...
                           "HorizontalAlignment", "left");

    h.txt_info = uicontrol(h.fig, "Style", "text", ...
                           "String", "No file loaded. Click 'Load WAV' to begin.", ...
                           "Units", "normalized", ...
                           "Position", [0.36, 0.03, 0.62, 0.03], ...
                           "HorizontalAlignment", "left", ...
                           "FontSize", 9);

    h.volume = 0.8;

    % --- Labels ---
    uicontrol(h.fig, "Style", "text", ...
              "String", "Original", ...
              "Units", "normalized", ...
              "Position", [0.06, 0.92, 0.42, 0.03], ...
              "FontSize", 12, "FontWeight", "bold", ...
              "ForegroundColor", [0.00, 0.45, 0.74], ...
              "HorizontalAlignment", "center");

    uicontrol(h.fig, "Style", "text", ...
              "String", "Filtered", ...
              "Units", "normalized", ...
              "Position", [0.56, 0.92, 0.42, 0.03], ...
              "FontSize", 12, "FontWeight", "bold", ...
              "ForegroundColor", [0.85, 0.33, 0.10], ...
              "HorizontalAlignment", "center");

    % --- Axes: 2x2 grid ---
    h.ax_orig_wave = axes("Units", "normalized", "Position", [0.06, 0.54, 0.42, 0.36]);
    title("Waveform"); xlabel("Time (s)"); ylabel("Amplitude"); grid on;

    h.ax_filt_wave = axes("Units", "normalized", "Position", [0.56, 0.54, 0.42, 0.36]);
    title("Waveform"); xlabel("Time (s)"); ylabel("Amplitude"); grid on;

    h.ax_orig_spec = axes("Units", "normalized", "Position", [0.06, 0.08, 0.42, 0.36]);
    title("Spectrogram");
    xlabel("Time (s)");
    dsp_freq_ticks(h.ax_orig_spec, [], "y");

    h.ax_filt_spec = axes("Units", "normalized", "Position", [0.56, 0.08, 0.42, 0.36]);
    title("Spectrogram");
    xlabel("Time (s)");
    dsp_freq_ticks(h.ax_filt_spec, [], "y");

    guidata(h.fig, h);

    % Bring back the file from a previous visit to this window, re-filtered
    % with whatever coefficients the caller just passed in.
    cached = audio_cache();
    if ~isempty(cached)
        render_audio(h, cached.y, cached.fs, cached.name);
    end

    % ------------------------------------------------------------------
    function c = audio_cache()
        c = [];
        if ishandle(parent_fig) && isappdata(parent_fig, "dsppack_audio")
            c = getappdata(parent_fig, "dsppack_audio");
        end
    end

    function cb_load(src, ~)
        [fname, fpath] = uigetfile({"*.wav", "WAV Files (*.wav)"; ...
                                     "*.flac", "FLAC Files (*.flac)"; ...
                                     "*.*", "All Files (*.*)"}, ...
                                    "Load Audio File");
        if isequal(fname, 0); return; end

        filepath = fullfile(fpath, fname);
        try
            [y, fs] = audioread(filepath);
        catch err
            errordlg(sprintf("Cannot read file:\n%s", err.message), "Load Error");
            return;
        end

        if size(y, 2) > 1
            y = mean(y, 2);
        end

        h = guidata(src);

        if fs ~= h.Fs_filter
            warndlg(sprintf( ...
                "Audio sample rate (%d Hz) differs from filter design rate (%d Hz).\nCutoff frequencies will shift relative to the audio content.", ...
                fs, h.Fs_filter), "Sample Rate Mismatch");
        end

        % Keep the decoded samples so reopening the demo after a design
        % change does not need another trip through the file dialog.
        if ishandle(parent_fig)
            setappdata(parent_fig, "dsppack_audio", ...
                       struct("y", y, "fs", fs, "name", fname));
        end

        render_audio(h, y, fs, fname);
    end

    % st is the handles struct; it is deliberately not named h, which is a
    % variable shared with the enclosing scope and so cannot be a parameter.
    function render_audio(st, y, fs, fname)
        st.signal   = y;
        st.Fs_audio = fs;
        st.filtered = filter(st.b, st.a, y);

        peak_filt = max(abs(st.filtered));
        if peak_filt > 1
            st.filtered = st.filtered / peak_filt;
        end

        guidata(st.fig, st);

        duration_s = length(y) / fs;
        set(st.txt_info, "String", sprintf("%s  |  Fs: %d Hz  |  %.2f s  |  %d samples", ...
                                            fname, fs, duration_s, length(y)));

        set(st.btn_play_orig, "Enable", "on");
        set(st.btn_play_filt, "Enable", "on");
        set(st.btn_stop,      "Enable", "on");
        set(st.btn_save,      "Enable", "on");

        t = (0:length(y)-1) / fs;

        axes(st.ax_orig_wave);
        plot(t, y, "Color", [0.00, 0.45, 0.74]);
        title("Waveform"); xlabel("Time (s)"); ylabel("Amplitude");
        grid on; xlim([0, t(end)]);

        axes(st.ax_filt_wave);
        plot(t, st.filtered, "Color", [0.85, 0.33, 0.10]);
        title("Waveform"); xlabel("Time (s)"); ylabel("Amplitude");
        grid on; xlim([0, t(end)]);

        target_cols = 1500;
        hop = max(256, floor(length(y) / target_cols));
        nfft = 2^nextpow2(hop * 2);
        nfft = max(nfft, 512);
        nfft = min(nfft, 16384);
        win  = hanning(nfft);
        olap = nfft - hop;

        [S1, f1, t1] = specgram(y,           nfft, fs, win, olap);
        [S2, f2, t2] = specgram(st.filtered, nfft, fs, win, olap);

        % Convert spectrogram frequency axes from Hz to normalized (×π rad/sample).
        % f/(fs/2) maps [0, Nyquist] -> [0, 1], where 1 corresponds to π rad/sample.
        wn1 = f1 / (fs / 2);
        wn2 = f2 / (fs / 2);

        S1_db = 20 * log10(abs(S1) + eps);
        S2_db = 20 * log10(abs(S2) + eps);
        peak_db = max(max(S1_db(:)), max(S2_db(:)));
        clims = [peak_db - 80, peak_db];

        axes(st.ax_orig_spec);
        imagesc(t1, wn1, S1_db, clims);
        axis xy; colorbar;
        title("Spectrogram");
        xlabel("Time (s)");
        ylim([0, 1]);

        axes(st.ax_filt_spec);
        imagesc(t2, wn2, S2_db, clims);
        axis xy; colorbar;
        title("Spectrogram");
        xlabel("Time (s)");
        ylim([0, 1]);

        label_spectrograms(st);
    end

    function label_spectrograms(st)
        % The spectrogram data stays in normalized units; only the tick
        % labels follow the Hz/normalized setting, exactly as the response
        % plots do on their x-axis.
        dsp_freq_ticks(st.ax_orig_spec, st.Fs_audio, "y");
        dsp_freq_ticks(st.ax_filt_spec, st.Fs_audio, "y");
    end

    function cb_freqaxis(src, ~)
        hh = guidata(src);
        [~, lbl] = dsp_freq_axis_hz(~dsp_freq_axis_hz());
        set(hh.btn_FreqAxis, "String", lbl);
        label_spectrograms(hh);
    end

    function cb_volume(src, ~)
        h = guidata(src);
        h.volume = get(src, "value");
        set(h.txt_vol, "String", sprintf("%.0f%%", h.volume * 100));
        guidata(h.fig, h);
    end

    function cb_play_orig(src, ~)
        h = guidata(src);
        if isfield(h, "player") && isobject(h.player)
            stop(h.player);
        end
        h.player = audioplayer(h.signal * h.volume, h.Fs_audio);
        guidata(h.fig, h);
        play(h.player);
    end

    function cb_play_filt(src, ~)
        h = guidata(src);
        if isfield(h, "player") && isobject(h.player)
            stop(h.player);
        end
        h.player = audioplayer(h.filtered * h.volume, h.Fs_audio);
        guidata(h.fig, h);
        play(h.player);
    end

    function cb_save_wav(src, ~)
        h = guidata(src);
        [fname, fpath] = uiputfile("*.wav", "Save Filtered Audio");
        if isequal(fname, 0); return; end
        filepath = fullfile(fpath, fname);
        audiowrite(filepath, h.filtered, h.Fs_audio);
        msgbox(sprintf("Filtered audio saved to:\n%s", filepath), "Save Successful");
    end

    function cb_stop(src, ~)
        h = guidata(src);
        if isfield(h, "player") && isobject(h.player)
            stop(h.player);
        end
    end

    function cb_back(src, ~)
        h = guidata(src);
        if isfield(h, "player") && isobject(h.player)
            stop(h.player);
        end
        delete(h.fig);
        if ishandle(parent_fig)
            set(parent_fig, "Visible", "on");
        end
    end
end
