Program Listing for File audio_manager.hpp#

Return to documentation for file (src/audio/audio_manager.hpp)

#pragma once
#include <QAudioDevice>
#include <QByteArray>
#include <QList>
#include <QObject>
#include <memory>
#include <vector>

#include "core/settings.hpp"

namespace mosaic {

/// @brief Coordinates all AudioRecorder instances for a recording session.
///
/// AudioManager is owned by Application and shared between RecordManager
/// (recording lifecycle) and AudioSettingsW (device enumeration and live
/// level display).
///
/// @par Per-microphone file naming
/// Files are written to @c \<sessionDir\>/\<basename\>_N.wav.
/// When only one microphone is configured the @c _0 suffix is omitted.
///
/// @par RMS metering
/// Each recorder emits level_rms_changed() approximately 10–20 times per second.
/// Connect to this signal from the main thread to drive a VU meter widget:
/// @code{.cpp}
/// connect(audioMgr, &AudioManager::level_rms_changed, vuMeter,
///         [vuMeter](int micIdx, float rms) {
///             if (micIdx == 0) vuMeter->set_value(rms);
///         });
/// @endcode
///
/// @see AudioRecorder, MicrophoneParameters
class AudioManager : public QObject {
    Q_OBJECT
   public:
    explicit AudioManager(QObject* parent = nullptr);
    ~AudioManager() override;

    // ── Device enumeration ─────────────────────────────────────────────────

    /// @returns All available audio input devices on this system.
    [[nodiscard]] static QList<QAudioDevice> available_inputs();

    /// @returns The system default audio input device.
    [[nodiscard]] static QAudioDevice default_input();

    // ── Recording lifecycle ────────────────────────────────────────────────

    /// @brief Starts one AudioRecorder per entry in @p microphones.
    ///
    /// Stops any previous monitoring session first.
    /// Recorders that fail to start are logged but do not abort the others.
    ///
    /// @param sessionDir   Absolute path to the session folder.
    /// @param basename     File basename, e.g. @c "audio".
    /// @param microphones  Per-microphone configuration from AppSettings.
    void start(const QString& sessionDir, const QString& basename,
               const std::vector<MicrophoneParameters>& microphones);

    /// @brief Starts level-metering recorders without writing any files.
    ///
    /// Used to drive the live waveform display outside of a recording session.
    /// Call start() (the recording version) to replace monitoring with actual recording.
    void start_monitoring(const std::vector<MicrophoneParameters>& microphones);

    /// @brief Stops monitoring-only recorders.
    void stop_monitoring();

    /// @brief Stops all recording recorders and flushes WAV headers.
    void stop();

    /// @returns @c true while at least one recording recorder is active.
    [[nodiscard]] bool is_recording() const;

    /// @returns @c true while monitoring-only recorders are running.
    [[nodiscard]] bool is_monitoring() const;

    /// @returns The number of recorders that were successfully started.
    [[nodiscard]] int recorder_count() const;

   signals:
    /// Emitted from the main thread (queued from the audio capture thread).
    /// @param micIndex  Zero-based index into the active microphone list.
    /// @param rms       Normalised RMS level in the range [0.0, 1.0].
    void level_rms_changed(int micIndex, float rms);

    /// Same cadence as level_rms_changed(), for a bipolar waveform display.
    /// @param micIndex   Zero-based index into the active microphone list.
    /// @param minSample  Most negative sample in the buffer, normalised [-1, 0].
    /// @param maxSample  Most positive sample in the buffer, normalised [0, 1].
    void envelope_changed(int micIndex, float minSample, float maxSample);

    /// Forwarded from every active AudioRecorder (both start() and
    /// start_monitoring() paths) with the mic index — same treatment as
    /// envelope_changed. Emitted regardless of monitor-only vs. real
    /// recording (matches VideoManager::frame_preview's own always-on
    /// behavior that feeds PoseWorker) — recording-time gating is done at
    /// the consumer (TranscriptWorker::set_paused()), not here.
    /// @param micIndex    Zero-based index into the active microphone list.
    /// @param pcm16       Interleaved 16-bit PCM, already format-normalized.
    /// @param sampleRate  Actually-negotiated sample rate (see AudioRecorder::start()).
    /// @param channels    Actually-negotiated channel count.
    void raw_pcm_ready(int micIndex, QByteArray pcm16, int sampleRate, int channels);

    /// Emitted when a recorder encounters a device error.
    /// @param micIndex  Zero-based index of the failing recorder.
    /// @param message   Human-readable error string.
    void recorder_error(int micIndex, QString message);

   private:
    struct Impl;
    std::unique_ptr<Impl> d;
};

} // namespace mosaic