LCOV - code coverage report
Current view: top level - src/audio/recorder - recorder_wav.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 87.1 % 93 81
Test Date: 2026-06-03 09:13:19 Functions: 100.0 % 3 3

            Line data    Source code
       1              : #include "audio/recorder/recorder.h"
       2              : #include "audio/engine/audio_engine.h"
       3              : #include <nlohmann/json.hpp>
       4              : #include <iostream>
       5              : #include <fstream>
       6              : #include <ctime>
       7              : #include <cstring>
       8              : 
       9              : namespace Amplitron {
      10              : 
      11           72 : void Recorder::write_wav_header() {
      12              :     // Write a placeholder WAV header (44 bytes)
      13              :     // Will be finalized when recording stops
      14           24 :     char header[44];
      15           72 :     std::memset(header, 0, 44);
      16              : 
      17           72 :     int byte_rate = sample_rate_ * channels_ * 2; // 16-bit = 2 bytes
      18           72 :     int block_align = channels_ * 2;
      19              : 
      20              :     // RIFF header
      21           72 :     std::memcpy(header + 0, "RIFF", 4);
      22              :     // Chunk size (placeholder — filled in finalize)
      23           72 :     std::memcpy(header + 8, "WAVE", 4);
      24              : 
      25              :     // fmt sub-chunk
      26           72 :     std::memcpy(header + 12, "fmt ", 4);
      27           72 :     int fmt_size = 16;
      28           72 :     std::memcpy(header + 16, &fmt_size, 4);
      29           72 :     int16_t audio_format = 1; // PCM
      30           72 :     std::memcpy(header + 20, &audio_format, 2);
      31           72 :     int16_t num_channels = static_cast<int16_t>(channels_);
      32           72 :     std::memcpy(header + 22, &num_channels, 2);
      33           72 :     std::memcpy(header + 24, &sample_rate_, 4);
      34           72 :     std::memcpy(header + 28, &byte_rate, 4);
      35           72 :     int16_t block_align_16 = static_cast<int16_t>(block_align);
      36           72 :     std::memcpy(header + 32, &block_align_16, 2);
      37           72 :     int16_t bits_per_sample = 16;
      38           72 :     std::memcpy(header + 34, &bits_per_sample, 2);
      39              : 
      40              :     // data sub-chunk
      41           72 :     std::memcpy(header + 36, "data", 4);
      42              :     // Data size (placeholder — filled in finalize)
      43              : 
      44           72 :     file_.write(header, 44);
      45           72 : }
      46              : 
      47           72 : void Recorder::finalize_wav_header() {
      48           72 :     if (!file_.is_open()) return;
      49              : 
      50           72 :     int64_t total_samples = samples_written_.load();
      51              : // Clamp channels_ to a safe positive value before multiplication to prevent
      52              : // signed overflow or negative data_size_64 if channels_ is zero or negative.
      53           72 : int64_t safe_channels = (channels_ > 0 && channels_ <= 8)
      54           96 :                         ? static_cast<int64_t>(channels_)
      55              :                         : 1LL;
      56              : // Compute data size in 64-bit to avoid overflow
      57           72 : int64_t data_size_64 = total_samples * safe_channels * 2LL;
      58              :     // WAV format uses 32-bit sizes; clamp to max int32
      59              :     // Use uint32_t to match the WAV spec (unsigned 32-bit chunk sizes)
      60              : // and avoid signed integer overflow when data_size approaches INT32_MAX.
      61              : // Cap at 0xFFFFFFD8 to leave room for the 36-byte RIFF overhead.
      62           72 : uint32_t data_size = static_cast<uint32_t>(
      63           24 :     (data_size_64 > static_cast<int64_t>(0xFFFFFFD8LL))
      64              :         ? 0xFFFFFFD8u
      65           24 :         : static_cast<uint32_t>(data_size_64));
      66           72 : uint32_t riff_size = data_size + 36u;
      67              : 
      68              : // Seek back and write correct sizes
      69           72 : file_.seekp(4, std::ios::beg);
      70           72 : file_.write(reinterpret_cast<const char*>(&riff_size), 4);
      71              : 
      72           72 : file_.seekp(40, std::ios::beg);
      73           72 : file_.write(reinterpret_cast<const char*>(&data_size), 4);
      74              : 
      75              :     // Seek to end
      76           72 :     file_.seekp(0, std::ios::end);
      77           24 : }
      78              : 
      79            9 : void Recorder::write_metadata(const std::string& wav_path, AudioEngine& engine) {
      80              :     // Write a JSON sidecar file with recording details
      81            9 :     std::string meta_path = wav_path;
      82              :     // Replace .wav with .meta.json
      83            9 :     size_t dot = meta_path.rfind('.');
      84            9 :     if (dot != std::string::npos) {
      85            9 :         meta_path = meta_path.substr(0, dot);
      86            3 :     }
      87            9 :     meta_path += ".meta.json";
      88              : 
      89            9 :     std::ofstream meta(meta_path);
      90            9 :     if (!meta.is_open()) {
      91            0 :         std::cerr << "Could not write metadata: " << meta_path << std::endl;
      92            0 :         return;
      93              :     }
      94              : 
      95            9 :     std::time_t now = std::time(nullptr);
      96            3 :     char timebuf[64];
      97            3 :     std::tm time_info;
      98              : #ifdef _WIN32
      99            3 :     localtime_s(&time_info, &now);
     100              : #else
     101            6 :     localtime_r(&now, &time_info);
     102              : #endif
     103            9 :     std::strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", &time_info);
     104              : 
     105            9 :     float duration = get_duration();
     106              : 
     107              :     // Build the recording metadata object
     108            9 :     nlohmann::ordered_json recording = nlohmann::ordered_json::object();
     109            9 :     recording["filename"]         = wav_path;
     110            9 :     recording["recorded_at"]      = timebuf;
     111            9 :     recording["duration_seconds"] = duration;
     112           12 :     recording["total_samples"]    = samples_written_.load();
     113            9 :     recording["format"]           = "WAV PCM 16-bit";
     114            9 :     recording["sample_rate"]      = sample_rate_;
     115            9 :     recording["channels"]         = channels_;
     116            9 :     recording["bit_depth"]        = 16;
     117              : 
     118              :     // Build the audio settings object
     119            9 :     nlohmann::ordered_json audio_settings = nlohmann::ordered_json::object();
     120            9 :     audio_settings["input_device"]       = engine.get_input_device_name();
     121            9 :     audio_settings["output_device"]      = engine.get_output_device_name();
     122            9 :     audio_settings["engine_sample_rate"] = engine.get_sample_rate();
     123            9 :     audio_settings["buffer_size"]        = engine.get_buffer_size();
     124            9 :     audio_settings["input_gain"]         = engine.get_input_gain();
     125            9 :     audio_settings["output_gain"]        = engine.get_output_gain();
     126              : 
     127              :     // Build the signal chain array
     128            9 :     nlohmann::ordered_json signal_chain = nlohmann::ordered_json::array();
     129           12 :     for (auto& fx : engine.effects()) {
     130            0 :         nlohmann::ordered_json jfx = nlohmann::ordered_json::object();
     131            0 :         jfx["name"]    = fx->name();
     132            0 :         jfx["enabled"] = fx->is_enabled();
     133            0 :         jfx["mix"]     = fx->get_mix();
     134              : 
     135            0 :         nlohmann::ordered_json params_obj = nlohmann::ordered_json::object();
     136            0 :         for (auto& p : fx->params()) {
     137            0 :             params_obj[p.name] = p.value;
     138              :         }
     139            0 :         jfx["parameters"] = std::move(params_obj);
     140              : 
     141            0 :         signal_chain.push_back(std::move(jfx));
     142            0 :     }
     143              : 
     144              :     // Assemble root object
     145            9 :     nlohmann::ordered_json root = nlohmann::ordered_json::object();
     146            9 :     root["recording"]      = std::move(recording);
     147            9 :     root["audio_settings"] = std::move(audio_settings);
     148            9 :     root["signal_chain"]   = std::move(signal_chain);
     149              : 
     150           12 :     meta << root.dump(2) << "\n";
     151            9 :     meta.close();
     152           12 :     std::cout << "Metadata written: " << meta_path << std::endl;
     153            9 : }
     154              : 
     155              : } // namespace Amplitron
     156              : 
        

Generated by: LCOV version 2.0-1