Program Listing for File audio_recorder.hpp#

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

#pragma once
#include <QAudioFormat>
#include <QAudioSource>
#include <QByteArray>
#include <QObject>
#include <atomic>
#include <memory>

#include "audio/wav_writer.hpp"
#include "core/settings.hpp"

namespace mosaic {

// Records one microphone device to a WAV file.
// One AudioRecorder is created per MicrophoneParameters entry.
//
// Level metering: emits level_rms in the range [0.0, 1.0] after every
// incoming audio buffer so callers can drive a VU meter, and envelope_changed
// with a signed min/max pair (see audio_envelope.hpp) so callers can drive a
// real bipolar waveform display.

class AudioRecorder : public QObject {
    Q_OBJECT
   public:
    explicit AudioRecorder(const MicrophoneParameters& params, QObject* parent = nullptr);
    ~AudioRecorder() override;

    // Finds the device matching params.deviceId and starts recording.
    // Falls back to the system default input if the device is not found.
    [[nodiscard]] bool start(const QString& filePath);
    void stop();

    [[nodiscard]] bool is_recording() const;
    [[nodiscard]] float level_rms() const; // 0.0–1.0, updated every buffer
    [[nodiscard]] double duration_sec() const;

   signals:
    void level_rms_changed(float rms); // 10–20 × per second, main-thread safe
    void envelope_changed(float minSample, float maxSample); // same cadence, [-1, 1]
    // Same cadence as envelope_changed — the already-16-bit-PCM-normalized
    // `data` from on_data_ready() (see m_captureFormat's doc comment),
    // before it's discarded. sampleRate/channels are whatever was actually
    // negotiated by start() (see its own doc comment — NOT guaranteed to
    // match MicrophoneParameters' requested values).
    void raw_pcm_ready(QByteArray pcm16, int sampleRate, int channels);
    void error_occurred(QString message);

   private slots:
    void on_data_ready();

   private:
    const MicrophoneParameters& m_params;
    std::unique_ptr<QAudioSource> m_source;
    QIODevice* m_ioDevice{nullptr};
    WavWriter m_writer;
    std::atomic<float> m_level{0.0f};
    bool m_monitorOnly{false}; // true when filePath is "" (no file writing)

    // Whichever sample format the device actually ended up capturing in —
    // not every device supports 16-bit PCM at all (many professional/USB
    // Audio Class 2.0 interfaces only offer 32-bit float or 32-bit int).
    // on_data_ready() converts every buffer to 16-bit PCM based on this
    // before it reaches m_writer/compute_rms()/compute_envelope(), so
    // nothing downstream needs to know the device's native format.
    QAudioFormat::SampleFormat m_captureFormat{QAudioFormat::Int16};
    int m_sampleRate{0}; // actually negotiated rate, set in start()
    int m_channels{0};   // actually negotiated channel count, set in start()
};

} // namespace mosaic